見出し画像

【Unity】マイク音量の検出

音シリーズとしてUnityを使ってマイク音量を検出し、音量に応じてキューブの大きさを変えてみたいと思います。マイク入力を検出する方法はいくつかありますが、今回はAudioClipとAudioSourceの2つの方法を解説します。

環境

Unity 2019.4.5f1

1. スクリプト : AudioClip編

ヒエラルキー上にCubeを配置し、以下のスクリプトをCubeまたは適当なオブジェクトにアタッチします。スクリプトのm_CubeにはCubeオブジェクトをアタッチします。

using System;
using System.Linq;
using UnityEngine;

public class Controller : MonoBehaviour {
    
    [SerializeField] private string m_DeviceName;
    private AudioClip m_AudioClip;
    private int m_LastAudioPos;
    private float m_AudioLevel;

    [SerializeField] private GameObject m_Cube;
    [SerializeField, Range(10, 100)] private float m_AmpGain = 10;
    
    void Start() {
        string targetDevice = "";
        
        foreach (var device in Microphone.devices) {
            Debug.Log($"Device Name: {device}");
            if (device.Contains(m_DeviceName)) {
                targetDevice = device;
            }
        }
        
        Debug.Log($"=== Device Set: {targetDevice} ===");
        m_AudioClip = Microphone.Start(targetDevice, true, 10, 48000);
    }

    void Update() {
        float[] waveData = GetUpdatedAudio();
        if (waveData.Length == 0) return;
        
        m_AudioLevel = waveData.Average(Mathf.Abs);
        m_Cube.transform.localScale = new Vector3(1, 1 + m_AmpGain * m_AudioLevel, 1);
    }
    
    private float[] GetUpdatedAudio() {
        
        int nowAudioPos = Microphone.GetPosition(null);// nullでデフォルトデバイス
        
        float[] waveData = Array.Empty<float>();

        if (m_LastAudioPos < nowAudioPos) {
            int audioCount = nowAudioPos - m_LastAudioPos;
            waveData = new float[audioCount];
            m_AudioClip.GetData(waveData, m_LastAudioPos);
        } else if (m_LastAudioPos > nowAudioPos) {
            int audioBuffer = m_AudioClip.samples * m_AudioClip.channels;
            int audioCount = audioBuffer - m_LastAudioPos;
            
            float[] wave1 = new float[audioCount];
            m_AudioClip.GetData(wave1, m_LastAudioPos);
            
            float[] wave2 = new float[nowAudioPos];
            if (nowAudioPos != 0) {
                m_AudioClip.GetData(wave2, 0);
            }

            waveData = new float[audioCount + nowAudioPos];
            wave1.CopyTo(waveData, 0);
            wave2.CopyTo(waveData, audioCount);
        }

        m_LastAudioPos = nowAudioPos;

        return waveData;
    }
}

2. 実行 : AudioClip編

実行するとコンソールログにMicrophoneデバイスの一覧が表示されるので、マイクとつながっているデバイス名をコピーして、作成したスクリプトのインスペクタの「m_DeviceName」に貼り付けます。

Microphoneデバイス一覧

デバイス名を設定した状態で、一度実行を止めて再び実行してマイクに話しかけるとCubeの大きさが変わることが確認できます。

マイク音の検出

3. スクリプト : AudioSource編

次はAudioSourceからマイクの入力を受け取ります。スクリプトはbibinba_devさんの記事を参考にさせていただきました。

AudioClip編と同様にヒエラルキー上にCubeを配置し、以下のスクリプトを適当なオブジェクトにアタッチします。

using UnityEngine;
using System.Linq;

public class MicAudioSource : MonoBehaviour {
    [SerializeField] private string m_DeviceName;
    private const int SAMPLE_RATE = 48000;
    private const float MOVING_AVE_TIME = 0.05f;

    //MOVING_AVE_TIMEに相当するサンプル数
    private const int MOVING_AVE_SAMPLE = (int)(SAMPLE_RATE * MOVING_AVE_TIME);
    
    private AudioSource m_MicAudioSource;

    [SerializeField] private GameObject m_Cube;
    [SerializeField, Range(10, 300)] private float m_AmpGain = 100;

    private void Awake() {
        m_MicAudioSource = GetComponent<AudioSource>();
    }

    void Start() {
        string targetDevice = "";
        
        foreach (var device in Microphone.devices) {
            Debug.Log($"Device Name: {device}");
            if (device.Equals(m_DeviceName)) {
                targetDevice = device;
            }
        }
        
        Debug.Log($"=== Device Set: {targetDevice} ===");
        MicStart(targetDevice);
    }

    void Update() {
        if (!m_MicAudioSource.isPlaying) return;
        
        float[] waveData = new float[MOVING_AVE_SAMPLE];
        m_MicAudioSource.GetOutputData(waveData, 0);

        //バッファ内の平均振幅を取得(絶対値を平均する)
        float audioLevel = waveData.Average(Mathf.Abs);
        m_Cube.transform.localScale = new Vector3(1, 1 + m_AmpGain * audioLevel, 1);
    }
    
    private void MicStart(string device) {
        if (device.Equals("")) return;
        
        m_MicAudioSource.clip = Microphone.Start(device, true, 1, SAMPLE_RATE);

        //マイクデバイスの準備ができるまで待つ
        while (!(Microphone.GetPosition("") > 0)) { }
        
        m_MicAudioSource.Play();
    }
}

AudioClip編と異なる点として、AudioSourceで行う場合はAudioSouceコンポーネントが必要になるため、上記のスクリプトアタッチしたオブジェクトと同じオブジェクトに「AudioSource」をアタッチしてください。

その後、AudioSourceの「Loop」にチェックを入れます。これで常にマイク入力を受け取る状態になります。

AudioSourceのLoop設定

4. 実行 : AudioSource編

一度実行してMicrophoneデバイスを設定後、スクリプトのインスペクタにCubeオブジェクトをアタッチして再実行します。

正常に動作していればマイク入力に応じてCubeの大きさが変わります。スクリプトの「m_AmpGain」を大きくするとマイク音量に応じてCubeの大きさが大きく変わります。

Microphoneデバイスの指定を間違えると、デバイス準備完了待機のループでロックしてしまいUnity(エディタ)が固まってしまうので注意してください。
今回は解説用のためエラー処理は省いています。

5. おわりに

前回の記事ではマイク入力からタイムコードを受け取る方法を解説しましたが、今回は単純にマイク音量を解析するだけのため、コードの量は少なく済みました。

マイク音量を扱えるだけでも、様々なインタラクティブ処理ができようになるので、音とUnityを連動させてみたいときは参考にしてみてください。🌱

6. 参考


この記事が気に入ったらサポートをしてみませんか?