Recording/analyzing data from microphone on Unity
For the game I worked on last GGJ (here) I needed to collect data from the microphone and analyze it real time. I googled around and could find something that helped me after hours of looking (here), so I decided to write a little something to make it easier to find (and add a little something).
First things first, you’ll need to record whatever is coming from the microphone. For that we have the Microphone class. We’ll need a device from Microphone.devices (we’ll just use the first one for this example) and we’ll use Microphone.Start() to save the recording on an AudioClip.
AudioClip _clipRecord = new AudioClip(); public void InitMic() { if (_device == null) _device = Microphone.devices[0]; _clipRecord = Microphone.Start(_device, true, 999, 44100); }
In order to get the data, you can use the function GetData in AudioClip. You need to specify the length and the offset, to find the offset, Microphone.GetPosition will get you the position in the recording for the device, you need to substract the length plus one.
float[] waveData = new float[_sampleWindow]; int micPosition = Microphone.GetPosition(_device) - (_sampleWindow + 1); if (micPosition < 0) return 0; _clipRecord.GetData(waveData, micPosition);
In order to stop the recording while the window isn't focused, you can use OnEnable, OnDisable, OnDestroy and OnApplicationFocus
bool _isInitialized; // start mic when scene starts void OnEnable() { InitMic(); _isInitialized = true; } //stop mic when loading a new level or quit application void OnDisable() { StopMicrophone(); } void OnDestroy() { StopMicrophone(); } // make sure the mic gets started & stopped when application gets focused void OnApplicationFocus(bool focus) { if (focus) { //Debug.Log("Focus"); if (!_isInitialized) { //Debug.Log("Init Mic"); InitMic(); _isInitialized = true; } } if (!focus) { //Debug.Log("Pause"); StopMicrophone(); //Debug.Log("Stop Mic"); _isInitialized = false; } } void StopMicrophone() { Microphone.End(_device); }
I used this script persistently between scenes and I found sometimes it would stop working when a new scene was loaded. It was in the latest hours of the jam so I tried stuff until something worked, and deactivating and activating again seemed to work (though it was probably just stopping and resuming the mic), so I added a callback to SceneManager.sceneLoaded to start a coroutine that would deactivate and reactivate the GameObject
private void Awake() { if (_inst != null && _inst != this) { Destroy(gameObject); return; } _inst = this; DontDestroyOnLoad(gameObject); SceneManager.sceneLoaded += OnSceneLoad; } void OnSceneLoad(Scene aScene, LoadSceneMode aMode) { StartCoroutine(TurnOffAndOn()); } IEnumerator TurnOffAndOn() { gameObject.SetActive(false); yield return null; gameObject.SetActive(true); }
You can use the data, for instance, to get a frequency range thourgh a FFT or to analyze it’s amplitude. I haven’t tried much so I’ll write again if I keep working on it.
Here's the full script I used to get a level of "loudness" on the microphone (I edited it a bit so it would work off the bat)
using System.Collections; using UnityEngine; using UnityEngine.SceneManagement; public class CMicController : MonoBehaviour { public static float MicLoudness; [SerializeField] bool _debug; [SerializeField] int _sampleWindow = 128; private string _device; AudioClip _clipRecord = new AudioClip(); static CMicController _inst; public static CMicController Inst { get { return _inst; } } public object SceneLoader { get; private set; } private void Awake() { if (_inst != null && _inst != this) { Destroy(gameObject); return; } _inst = this; DontDestroyOnLoad(gameObject); SceneManager.sceneLoaded += OnSceneLoad; } void OnSceneLoad(Scene aScene, LoadSceneMode aMode) { StartCoroutine(TurnOffAndOn()); } IEnumerator TurnOffAndOn() { gameObject.SetActive(false); yield return null; gameObject.SetActive(true); } //mic initialization public void InitMic() { if (_device == null) _device = Microphone.devices[0]; _clipRecord = Microphone.Start(_device, true, 999, 44100); Debug.Log(_device); } void StopMicrophone() { Microphone.End(_device); } //get data from microphone into audioclip float LevelMax() { float levelMax = 0; float[] waveData = new float[_sampleWindow]; int micPosition = Microphone.GetPosition(_device) - (_sampleWindow + 1); // null means the first microphone if (micPosition < 0) return 0; _clipRecord.GetData(waveData, micPosition); // Getting a peak on the last 128 samples for (int i = 0; i < _sampleWindow; i++) { float wavePeak = waveData[i] * waveData[i]; if (levelMax < wavePeak) { levelMax = wavePeak; } } return levelMax; } void Update() { // levelMax equals to the highest normalized value power 2, a small number because < 1 // pass the value to a static var so we can access it from anywhere MicLoudness = LevelMax(); } bool _isInitialized; // start mic when scene starts void OnEnable() { InitMic(); _isInitialized = true; } //stop mic when loading a new level or quit application void OnDisable() { StopMicrophone(); } void OnDestroy() { StopMicrophone(); } // make sure the mic gets started & stopped when application gets focused void OnApplicationFocus(bool focus) { if (focus) { //Debug.Log("Focus"); if (!_isInitialized) { //Debug.Log("Init Mic"); InitMic(); _isInitialized = true; } } if (!focus) { //Debug.Log("Pause"); StopMicrophone(); //Debug.Log("Stop Mic"); _isInitialized = false; } } private void OnGUI() { if (_debug) { GUI.Label(new Rect(20, 20, 100, 100), "Loudness: " + MicLoudness); GUI.Box(new Rect(40, MicLoudness * 100 + 30, 20, 20), "sound"); } } }