Unityでマイクから取得した音をVFX Graphで使用する

Unityでマイクから取得した音をVFX Graphで使用してみます。

基本的にはAudioSourceにマイクを設定し、そこから取得できる波形とスペクトラムを動的に生成したテクスチャに書き込んでVFX Graphのプロパティに渡すという流れになります。

マイクからの音の入力と解析に関しては以下の記事を参考にしました。

- [Unity:マイクの音量を取得する\(2\) \- Qiita](https://qiita.com/ELIXIR/items/4a647b2d450246cfd3d4)
- [Unity のオーディオの再生・エフェクト・解析周りについてまとめてみた \- 凹みTips](http://tips.hecomi.com/entry/2014/11/11/021147)
- [Unityでマイク入力からピッチ検出をするまで \- Qiita](https://qiita.com/niusounds/items/b8858a2b043676185a54)

スクリプトでテクスチャを動的に生成してVFX Graphに渡す方法に関しては私が以前に書いた記事も参考にしてみてください。

- [UnityのVFX Graphで使用するAttribute Mapを動的に生成する \- Qiita](https://qiita.com/aa_debdeb/items/cd46d571dcb20f75817b)

使用したUnity等のバージョンは以下のようになっています。

- Unity: 2020.1.4f1 Personal
- Universal RP: 8.2.0
- Visual Effect Graph: 8.2.0

# C#スクリプト

以下、C#スクリプトです。これをAudioSourceコンポーネントとVisualEffectコンポーネントを持つGameObjectに追加します。

```csharp
using UnityEngine;
using UnityEngine.VFX;

[RequireComponent(typeof(AudioSource), typeof(VisualEffect))]
public class VfxSoundSample : MonoBehaviour
{
    AudioSource audioSource;
    VisualEffect visualEffect;

    // 波形データ長
    // AudioSource.GetOutputDataから2の累乗である必要がある
    int outputLength = 256;

    // スペクトラムデータ長
    // AudioSource.GetSpectrumDataから2の累乗である必要がある
    int spectrumLength = 256;

    // 波形データを格納する配列
    float[] outputSamples;
    // スペクトラムデータを格納する配列
    float[] spectrumSamples; 

    // VFX Graphに渡す波形データを格納するテクスチャ
    Texture2D outputMap;
    // VFX Graphに渡すスペクトラムデータを格納するテクスチャ
    Texture2D spectrumMap;

    // 波形データのテクスチャ更新時に使用する配列
    Color[] outputColors;
    // スペクトラムデータのテクスチャ更新時に使用する配列
    Color[] spectrumColors;

    void Start()
    {
        audioSource = GetComponent<AudioSource>();
        visualEffect = GetComponent<VisualEffect>();

        // 配列の初期化
        outputSamples = new float[outputLength];
        spectrumSamples = new float[spectrumLength];
        outputColors = new Color[outputLength];
        spectrumColors = new Color[spectrumLength];

        // 波形データのテクスチャの作成
        outputMap = new Texture2D(outputLength, 1, TextureFormat.RFloat, false);
        outputMap.filterMode = FilterMode.Bilinear;
        outputMap.wrapMode = TextureWrapMode.Clamp;
        // スペクトラムデータのテクスチャの作成
        spectrumMap = new Texture2D(spectrumLength, 1, TextureFormat.RFloat, false);
        spectrumMap.filterMode = FilterMode.Bilinear;
        spectrumMap.wrapMode = TextureWrapMode.Clamp;
        // 波形データのテクスチャをVFX Graphの「Output Map」というプロパティに設定
        visualEffect.SetTexture("Output Map", outputMap);
        // スペクトラムデータのテクスチャをVFX Graphの「Spectrum Map」というプロパティに設定
        visualEffect.SetTexture("Spectrum Map", spectrumMap);

        // AudioSourceにマイクを設定する
        audioSource.clip = Microphone.Start(null, true, 10, 44100);
        audioSource.loop = true;
        while(!(Microphone.GetPosition(null) > 0)) {} // 開始後すぐにマイク入力が取得できるように待つ
        audioSource.Play();
    }

    void Update()
    {
        // 波形データの取得
        audioSource.GetOutputData(outputSamples, 0);
        // スペクトラムデータの取得
        audioSource.GetSpectrumData(spectrumSamples, 0, FFTWindow.Hamming);

        // 波形データのテクスチャを更新
        for (var i = 0;  i < outputLength; i++)
        {
            outputColors[i].r = outputSamples[i];
        }
        outputMap.SetPixels(outputColors);
        outputMap.Apply();

        // スペクトラムデータのテクスチャを更新
        for (var i = 0; i < spectrumLength; i++)
        {
            spectrumColors[i].r = spectrumSamples[i];
        }
        spectrumMap.SetPixels(spectrumColors);
        spectrumMap.Apply();

    }
}
```

# VFX Graph

使用したVFX Graphです。今回は単純に波形とスペクトラムを線グラフっぽくビジュアライズしています。スクリプトから設定するために`Output Map`と`Spectrum Map`という名前のTexture2D形式のプロパティが必要になります。このテクスチャから横方向の位置をもとにサンプリングし、高さにと色に反映しています。上が波形データ、下がスペクトラムデータに対応しています。表示の都合上、スクリーンショットを分割していますが、実際には一つのVFX Graphの中に作成しています。

![output-graph.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/61569/bae2bc7b-c51d-f5bb-2889-aaf782019da9.png)
![spectrum-graph.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/61569/ea704383-dc94-4f71-03e8-efff0ef94448.png)


# 結果
以下のようにマイクからの音声入力をVFX Graphでビジュアライズできました。
![gif_animation_003.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/61569/08121ba3-44ca-9609-7c2e-7340794bf763.gif)