using System; using System.IO; using Microsoft.Xna.Framework.Audio; /* * XNA Windows Phone Easy Microphone Access * by Brian MacIntosh for ThunderFish Entertainment/BoneFish Studios * * Version: 1.1 * * 11/23/2011: Version 1.0 * - Created * * 1/5/2012: Version 1.1 * - Adjusted intensity calculation * - Added frequency field * * 11/21/12 * - Distributed * * This library is provided free of charge for commercial and noncommercial use. * No warranty of fitness for any purpose, express or implied, is given. */ /* * This class provides easy access to the Windows Phone microphone * hardware. Simply initialize the microphone with ThunderFish. * TMicrophone.Initialize() when the app is started (OnNavigateTo) * and deinitialize it with ThunderFish.TMicrophone.Deinitialize() * when you are done (OnNavigateFrom). * * You can then record audio from the microphone using StartRecording() * and StopRecording(). * * You can record a specific length of audio from the microphone using * StartRecording(duration). The TimedRecordingDone event will be invoked * when the recording is done. * * You can monitor the intensity of the microphone readings with the * Intesity field. The microphone must be recording for the field to * be updated - you can use StartMonitoring and StopMonitoring to * "fake" recording for this purpose. * * See method descriptions for more information. */ namespace Thunderfish { /// /// This class provides methods for accessing the phone microphone. /// public static class TMicrophone { private static Microphone mic; private static MemoryStream currentData; private static TimeSpan targetRecordTime; private static bool isTimed; private static TimeSpan defaultBufferTime; /// /// Returns true if the microphone is currently recording. /// public static bool IsRecording { get { return mic.State == MicrophoneState.Started; } } /// /// Returns the friendly name of the current microphone. /// public static string MicrophoneName { get { return mic.Name; } } /// /// Returns the sample rate (bytes per second) of the microphone. /// public static int SampleRate { get { return mic.SampleRate; } } /// /// Returns true if the current microphone is a headset. /// public static bool IsHeadset { get { return mic.IsHeadset; } } public delegate void TimedRecordingEvent(SoundEffect record); /// /// Event that occurs when a timed recording has finished. /// public static event TimedRecordingEvent TimedRecordingDone; /// /// Set up the microphone. /// public static void Initialize() { mic = Microphone.Default; defaultBufferTime = mic.BufferDuration; mic.BufferReady += BufferSound; } /// /// Unload the microphone. /// public static void Deinitialize() { if (IsRecording) mic.Stop(); mic.BufferReady -= BufferSound; if (currentData != null) currentData.Close(); } /// /// Switches to recording from a headset microphone if one is /// available. /// /// True if a headset mic was found public static bool UseHeadset() { if (IsRecording) throw new MicrophoneAlreadyRecordingException(); foreach (Microphone m in Microphone.All) { if (m.IsHeadset) { mic = m; defaultBufferTime = mic.BufferDuration; return true; } } return false; } /// /// Switches to recording from the built-in microphone. /// /// True if the built-in mic was found public static bool UseBuiltIn() { if (IsRecording) throw new MicrophoneAlreadyRecordingException(); foreach (Microphone m in Microphone.All) { if (!m.IsHeadset) { mic = m; defaultBufferTime = mic.BufferDuration; return true; } } return false; } /// /// Get the last sound intensity reading from the mic. /// Background noise is about 0.25, significant noises are 0.3 up to 0.5 /// public static float Intensity { get; private set; } /// /// Get the frequency of the last reading from the mic (hz) /// //TODO: public static float Frequency { get; private set; } /// /// Start the mic to monitor for sound /// public static void StartMonitoring() { mic.BufferDuration = mic.GetSampleDuration(1024); mic.Start(); } /// /// Stop monitoring for sound /// public static void StopMonitoring() { mic.Stop(); mic.BufferDuration = defaultBufferTime; } /// /// Begin asynchronously recording data from the microphone. /// public static void StartRecording() { mic.BufferDuration = defaultBufferTime; currentData = new MemoryStream(); mic.Start(); isTimed = false; } /// /// Begin asynchronously recording data from the microphone. /// Recording will stop automatically after the specified duration /// and the TimedRecordingDone event will occur. /// /// public static void StartRecording(TimeSpan duration) { mic.BufferDuration = defaultBufferTime; currentData = new MemoryStream(); mic.Start(); targetRecordTime = duration; isTimed = true; } /// /// Stops recording and returns a SoundEffect of the recorded data. /// /// A SoundEffect of the recorded data public static SoundEffect StopRecording() { if (!IsRecording) throw new MicrophoneNotStartedException(); mic.Stop(); SoundEffect ret = new SoundEffect(currentData.ToArray(), mic.SampleRate, AudioChannels.Mono); currentData.Close(); currentData = null; return ret; } private const int intensityTakeTop = 5; private static void BufferSound(object sender, EventArgs e) { byte[] buffer = new byte[mic.GetSampleSizeInBytes(mic.BufferDuration)]; int bytesRead = 0; while ((bytesRead = mic.GetData(buffer, 0, buffer.Length)) > 0) { //Record intensity (average of top intensityTakeTop values) int[] best = new int[intensityTakeTop]; for (int c = 0; c < bytesRead; c++) for (int d = 0; d < best.Length; d++) if (best[d] < Math.Abs(buffer[c] - 128)) { int hold = best[d]; best[d] = Math.Abs(buffer[c] - 128); if (d > 0) for (int f = d - 1; d >= 0; d--) { int temp = best[f]; best[f] = hold; hold = temp; } break; } int total = 0; foreach (int i in best) total += i; Intensity = (total / (float)best.Length); //Record frequency /*SpeedTest.FFT2 fft = new SpeedTest.FFT2(); fft.init(10); fft.*/ if (currentData != null) { if (isTimed && currentData.Length + bytesRead >= mic.GetSampleSizeInBytes(targetRecordTime)) { currentData.Write(buffer, 0, mic.GetSampleSizeInBytes(targetRecordTime) - (int)currentData.Length); TimedRecordingDone(StopRecording()); } else currentData.Write(buffer, 0, bytesRead); } } } private class MicrophoneAlreadyRecordingException : Exception { public override string Message { get { return "Attempted to change microphones while recording."; } } } private class MicrophoneNotStartedException : Exception { public override string Message { get { return "Attempted to get recording data when microphone was not recording."; } } } } }