I'm attempting to write a peak volume meter using NAudio. My code is very similar to http://channel9.msdn.com/coding4fun/articles/NET-Voice-Recorder, but both my code and the linked Voice Recorder project suffer from an issue.
When playing a sound of constant frequency and volume, the volume meter initially begins at a reasonable level, but then decays to a very small value. I'm not sure why this is the case, for the peak volume meter in the NAudioDemo does not do this. I attempted to replicate the code from NAudioDemo in my program, but I was unable to find the code file containing the peak volume meter code.
Can somebody guide me to an alternative solution for creating a peak volume meter or help me determine why my solution (and the one provided at the link) both don't work?
public MainWindow()
{
int waveInDevices = WaveIn.DeviceCount;
for (int waveInDevice = 0; waveInDevice < waveInDevices; waveInDevice++)
{
WaveInCapabilities deviceInfo = WaveIn.GetCapabilities(waveInDevice);
Console.WriteLine("Device {0}: {1}, {2} channels",
waveInDevice, deviceInfo.ProductName, deviceInfo.Channels);
WaveIn waveIn = new WaveIn();
waveIn.DeviceNumber = 0; //TODO: Let the user choose which device, this comes from the device numbers above
waveIn.DataAvailable += waveIn_DataAvailable;
int sampleRate = SAMPLE_RATE; // 8 kHz
int channels = 1; // mono
waveIn.WaveFormat = new WaveFormat(sampleRate, channels);
waveIn.StartRecording();
}
}
void waveIn_DataAvailable(object sender, WaveInEventArgs e)
{
for (int index = 0; index < e.BytesRecorded; index += 2)
{
short sample = (short)((e.Buffer[index + 1] << 8) |
e.Buffer[index + 0]);
float sample32 = sample / 32768f;
ProcessSample(sample32);
}
}
void ProcessSample(float sample1)
{
samplenumber += 1;
if (sample1 > maxval)
{
maxval = sample1;
}
if (sample1 < minval)
{
minval = sample1;
}
//Run updateView every few loops
if (samplenumber > (double)SAMPLE_RATE / DISPLAY_UPDATE_RATE)
{
samplenumber = 0;
updateView(); //needs to be fast!
}
}
void updateView()
{
Console.WriteLine(maxval);
Console.WriteLine(minval);
progressBar1.Value = (maxval - minval)*50;
maxval = 0;
minval = 0;
}
All that is happening in that article is that it is finding the maximum audio peak over a small interval (e.g. 20ms) and then plotting that on a decibel scale. To find the peak, examine the value of each sample in the interval and select the max value (It's what the SampleAggregator class is doing). To convert to decibels, take the log base 10 of the maximum value, and multiply by 10. So 0dB is the loudest, and anything below say -96dB is effectively silence. (actually, looking back at the article, I don't think I even bothered to convert to a decibel scale, which I probably should have done)
This was my little solution for getting peak from output device. I'm using NAudio version 1.7.0.15
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
MMDeviceEnumerator enumerator = new MMDeviceEnumerator();
var devices = enumerator.EnumerateAudioEndPoints(DataFlow.All, DeviceState.Active);
comboboxDevices.Items.AddRange(devices.ToArray());
}
private void timer1_Tick(object sender, EventArgs e)
{
if (comboboxDevices.SelectedItem != null)
{
var device = (MMDevice)comboboxDevices.SelectedItem;
progressBar1.Value = (int)(Math.Round(device.AudioMeterInformation.MasterPeakValue * 100));
}
}
}
Trying to get the levels with MasterPeakValue appears to be more complicated than just calling the method, which defeats its simplicity.
I accidentally realized that you have to open the device for recording, even if you don't use with the incoming data. Since you are starting a WaveIn, MasterPeakValue should return a non-0 value.
A simple alternative, just for testing, is to open the properties of system's recording devices (right-click on system volume icon and choose "Recording devices").
(Tested on 2 different computers.)
Related
I want to use the volume of the audio that the user inputs with his/her microphone in Unity3d in a visual representation. So I'd like to get a value between 0 and 1 that tell how loud the user is. I went looking for a script, but the part that handles the volume doesn't work properly, that part is the method LevelMax(). For some reason micPosiotion never becomes higher than 0. I don't know what Microphone.GetPosition really does except for this:
http://docs.unity3d.com/ScriptReference/Microphone.GetPosition.html
Does anyone know what goes wrong in the method LevelMax()? I am getting no errors or anything. And it finds my microphone properly, and it is working. I tested it.
This is the code:
using UnityEngine;
using System.Collections;
public class MicInput : MonoBehaviour{
public float testSound;
public static float MicLoudness;
private string _device;
private AudioClip _clipRecord = new AudioClip();
private int _sampleWindow = 128;
private bool _isInitialized;
void InitMic()
{
if (_device == null) {
_device = Microphone.devices [0];
_clipRecord = Microphone.Start (_device, true, 999, 44100);
Debug.Log (_clipRecord);
}
}
void StopMicrophone()
{
Microphone.End (_device);
}
float LevelMax()
{
float levelMax = 0;
float[] waveData = new float[_sampleWindow];
int micPosition = Microphone.GetPosition (null) - (_sampleWindow + 1);
if (micPosition < 0) {
return 0;
}
_clipRecord.GetData (waveData, micPosition);
for (int i = 0; i < _sampleWindow; ++i) {
float wavePeak = waveData [i] * waveData [i];
if (levelMax < wavePeak) {
levelMax = wavePeak;
}
}
return levelMax;
}
void Update()
{
MicLoudness = LevelMax ();
testSound = MicLoudness;
}
void OnEnable()
{
InitMic ();
_isInitialized = true;
}
void OnDisable()
{
StopMicrophone ();
}
void OnDestory()
{
StopMicrophone ();
}
void OnApplicationFocus(bool focus)
{
if (focus) {
if (!_isInitialized) {
InitMic ();
_isInitialized = true;
}
}
if (!focus) {
StopMicrophone ();
_isInitialized = false;
}
}
}
This script works. I have just tested it and it shows the peak level of the mic in the inspector as the variable testSound. There is something going wrong on your end that is causing it to not begin recording into the audioclip. That is why it is always returning that the micPosition is less than zero.
The only thing that I can see that is slightly off is Microphone.GetPosition(null) inside the LevelMax method. Try changing this to Microphone.GetPosition(_device)
You might also want to try going through your different audio devices by changing the index passed in the line (in the InitMic method):
_device = Microphone.devices [0];
Try changing this to 1,2,3 etc and see if you are just finding the wrong audio device. If you have more than one mic or are not using the default mic then this could be the problem.
Also, I think you are misunderstanding how digital audio works. GetPosition gets the current sample that the microphone is recording into the audioclip (i.e the latest sample/current sample). This basically means that it gets the amount of samples that have been recorded. You are recording at 44.1Khz samples. That means that every second the audio is being checked 441000 times and a level is assigned to that individual sample. This is called the sample rate and it can be changed. For example CD's use the sample rate 44.1kHz where as digital video tends to use 48kHz. The accuracy of the the sample being recorded is defined by the bit-depth (but you don't have to worry about this). For example CD's use 16bit (which needs to be dithered) whereas digital media uses 24bit(generally). The line:
int micPosition = Microphone.GetPosition(null)-(_sampleWindow+1);
Says "find the amount samples we have recorded 129 samples ago". It then iterates through the values of the next 128 samples and finds the 'loudest' sample and returns it. This is then shown in the inspector. If you don't understand anything I've just said then look into how digital audio is recorded. It's not too complicated to understand the basics of it.
You should check out this thread, but here is the code that may help you:
public class MicInput : MonoBehaviour {
public static float MicLoudness;
private string _device;
//mic initialization
void InitMic(){
if(_device == null) _device = Microphone.devices[0];
_clipRecord = Microphone.Start(_device, true, 999, 44100);
}
void StopMicrophone()
{
Microphone.End(_device);
}
AudioClip _clipRecord = new AudioClip();
int _sampleWindow = 128;
//get data from microphone into audioclip
float LevelMax()
{
float levelMax = 0;
float[] waveData = new float[_sampleWindow];
int micPosition = Microphone.GetPosition(null)-(_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;
}
}
}
I'm having a little hard time after navigating around the site to find out solution. The problem I have is that I'm trying to capture an image using mediacapture taken from this url (download here).
I've found a couple threads in SO that deal with resolution already, but what they're doing is using default resolution as following
3024*4992.......4:3
1936*2592...162:121
1536*2048.......4:3
480*640..........4:3
3024*5376.....16:9
1728*3072.....16:9
1456*2592...162:91
(suggeted by this)
However what I want is to capture an image with 800x600 resolution, is this really possible somehow?
You can use 800x600 only if your camera supports it. My camera for example doesn't.
Find available resolutions:
uint[] x_res; // available horizontal resolutions
uint[] y_res; // available vertical resolutions
uint resolutionwidth; //used horizontal resolution
uint resolutionheight; //used vertical resolution
private void get_res_button_click(object sender, RoutedEventArgs e)
{
resolution_listbox.Items.Clear();
IEnumerable<VideoEncodingProperties> available_resolutions = captureManager.VideoDeviceController.GetAvailableMediaStreamProperties(MediaStreamType.Photo).Select(x => x as VideoEncodingProperties);
int total_res = available_resolutions.Count();
x_res = new uint[total_res];
y_res = new uint[total_res]
int i = 0;
foreach (VideoEncodingProperties resolution in available_resolutions)
{
x_res[i] = resolution.Width;
y_res[i] = resolution.Height;
resolution_listbox.Items.Add(x_res[i].ToString() + " x " + y_res[i].ToString());
i++;
}
}
Select the one you want:
private async void resolution_listbox_selectionchanged(object sender, SelectionChangedEventArgs e)
{
if (resolution_listbox.SelectedItem != null)
{
int j = resolution_listbox.SelectedIndex;
resolutionwidth = x_res[j];
resolutionheight = y_res[j];
}
// And apply it:
IReadOnlyList<IMediaEncodingProperties> resolutions = captureManager.VideoDeviceController.GetAvailableMediaStreamProperties(MediaStreamType.Photo);
for (int k = 0; k < resolutions.Count; k++)
{
if (resolutions[k] is VideoEncodingProperties)
{
VideoEncodingProperties vidprops = (VideoEncodingProperties)resolutions[k];
// check which VideoEncodingProperties contains the correct resolution
if (vidprops.Width == resolutionwidth && vidprops.Height == resolutionheight)
{
await captureManager.VideoDeviceController.SetMediaStreamPropertiesAsync(MediaStreamType.Photo, resolutions[k]);
}
}
}
}
Note:in the 1st method I used IEnumerable< VideoEncodingProperties > . This because I only want the numbers.
In the 2nd method I used IReadOnlyList< IMediaEncodingProperties >. This is because only the VideoEncodingProperties which contains the wanted resolution, needs to be applied. Not every IMediaEncodingProperties contains resolution information.
I want to develop an app to match your tinnitus frequency : A frequency is played and the user decrease or increase the freqency by pressing a plus or minus button. (see part of the codes, based on some coding from stackoverflow thx :-))
public static short[] BufferSamples = new short[44100 * 1 * 2];
private SourceVoice sourceVoice;
private AudioBuffer buffer;
private int Tfreq;
public MatchTinn()
{
InitializeComponent();
Loaded += MatchTinn_Loaded;
TFreq = 5000;
}
private void MatchTinn_Loaded(object sender, RoutedEventArgs e)
{
var dataStream = DataStream.Create(BufferSamples, true, true);
buffer = new AudioBuffer
{
LoopCount = AudioBuffer.LoopInfinite,
Stream = dataStream,
AudioBytes = (int)dataStream.Length,
Flags = BufferFlags.EndOfStream
};
FillBuffer(BufferSamples, 44100, Tfreq);
var waveFormat = new WaveFormat();
XAudio2 xaudio = new XAudio2();
MasteringVoice masteringVoice = new MasteringVoice(xaudio);
sourceVoice = new SourceVoice(xaudio, waveFormat, true);
// Submit the buffer
sourceVoice.SubmitSourceBuffer(buffer, null);
}
private void FillBuffer(short[] buffer, int sampleRate, int frequency)
{
if (sourceVoice != null)
{
sourceVoice.FlushSourceBuffers();
}
double totalTime = 0;
for (int i = 0; i < buffer.Length - 1; i += 2)
{
double time = (double)totalTime / (double)sampleRate;
short currentSample = (short)(Math.Sin(2 * Math.PI * frequency * time) * (double)short.MaxValue);
buffer[i] = currentSample;
buffer[i + 1] = currentSample;
totalTime++;
}
private void m1_OnTap(object sender, GestureEventArgs e)
{
Tfreq = Tfreq - 1;
if (Tfreq < 0)
{
Tfreq = 0;
}
FillBuffer(BufferSamples, 44100, Tfreq);
}
private void p1_OnTap(object sender, GestureEventArgs e)
{
Tfreq = Tfreq + 1;
if (Tfreq > 16000)
{
Tfreq = 16000;
}
FillBuffer(BufferSamples, 44100, Tfreq);
}
Playing the frequency is fine, but when the user presses a button you here a clicking sound when the frequency is updated. Do you have any idea what makes the sound and how i can get rid of it?
Thanks.
When you change the frequency, you're causing a discontinuity in the waveform that manifests as a click. Instead of making your signal calculations against absolute time, you should keep track of the phase of your sine calculation (e.g. a value from 0 to 2*pi), and figure out how much you need to add to your phase (subtracting 2*pi every time you exceed 2*pi) for the next sample when playing a specific frequency. This way, when you change frequency, the phase that you supply as a parameter to Math.Sin doesn't change abruptly causing a click.
Expanding on the answer #spender gave (I need 50 rep to add comment to his answer), I had a similar problem with naudio. I was able to solve the issue by adding two bool values that monitored the current sign of the sine value and the previous sign of the sine value. If the previous sine was negative and the current sine is positive, we know we can safely adjust the frequency of the sine wave.
double sine = amplitude * Math.Sin(Math.PI * 2 * frequency * time);
isPreviousSineWaveValPositive = isSineWaveValPositive;
if (sine < 0)
{
isSineWaveValPositive = false;
}
else
{
isSineWaveValPositive = true;
}
// When the time is right, change the frequency
if ( false == isPreviousSineWaveValPositive && true == isSineWaveValPositive )
{
time = 0.0;
frequency = newFrequency;
}
Here's an example how you can get rid of the clicking. Instead of using a time, you should keep track of the current phase and calculate how much the phase is changed on the required frequency. Also this _currentPhase must be persistent so it will have the previous value. (declaring it within the method would result in a click aswell (on most frequencies)
private double _currentPhase = 0;
private void FillBuffer(short[] buffer, int sampleRate, int frequency)
{
if (sourceVoice != null)
{
sourceVoice.FlushSourceBuffers();
}
var phaseStep = ((Math.PI * 2) / (double)sampleRate) * frequency;
for (int i = 0; i < buffer.Length - 1; i += 2)
{
_currentPhase += phaseStep;
short currentSample = (short)(Math.Sin(_currentPhase) * (double)short.MaxValue);
buffer[i] = currentSample;
buffer[i + 1] = currentSample;
}
}
I am currently working on a SR. design project which is a windows forms app which will allow for users to plug in their guitar and create distortions in real time and play it with input and output, and auto tab it into ASCII tabs. Currently I am trying to get the real time listening portion working, I have the recording and implementation of distortions working just fine just having some issues using ASIO. I've looked at this post How to record and playback with NAudio using AsioOut but it was not of much help with my issue, here is my code:
private BufferedWaveProvider buffer;
private AsioOut input;
private AsioOut output;
private void listenBtn_Click(object sender, EventArgs e)
{
input = new AsioOut(RecordInCbox.SelectedIndex);
WaveFormat format = new WaveFormat();
buffer = new BufferedWaveProvider(format);
buffer.DiscardOnBufferOverflow = true;
input.InitRecordAndPlayback(buffer, 1, 44100);
input.AudioAvailable += new EventHandler<AsioAudioAvailableEventArgs>(AudioAvailable);
//output = new AsioOut(RecordInCbox.SelectedIndex);
//output.Init(buffer);
input.Play();
//output.Play();
}
public void AudioAvailable(object sender, AsioAudioAvailableEventArgs e)
{
byte[] buf = new byte[e.SamplesPerBuffer];
e.WrittenToOutputBuffers = true;
for (int i = 0; i < e.InputBuffers.Length; i++)
{
Array.Copy(e.InputBuffers, e.OutputBuffers, 1);
Marshal.Copy(e.InputBuffers[i], buf, 0, e.SamplesPerBuffer);
buffer.AddSamples(buf, 0, buf.Length);
}
}
Currently it is getting the audio and pushing it into the buffer but the output is not working. I am able to get it to play the guitar if I set the recording setting to Listen on windows but feel this is unnecessary as how I want to be able to perform my distortion and hear that as the output. Thanks!
You don't need to add samples in buffer. The buffer only serves to determine the number of output channels you want.
I've did it this way:
[DllImport("Kernel32.dll", EntryPoint = "RtlMoveMemory", SetLastError = false)]
private static unsafe extern void MoveMemory(IntPtr dest, IntPtr src, int size);
private void OnAudioAvailable(object sender, AsioAudioAvailableEventArgs e)
{
for (int i = 0; i < e.InputBuffers.Length; i++)
{
MoveMemory(e.OutputBuffers[i], e.InputBuffers[i], e.SamplesPerBuffer * e.InputBuffers.Length);
}
e.WrittenToOutputBuffers = true;
}
But doing like this feels a bit latency and a bit of echo and I don't know how to solve them. So if you have any ideas I'm here to listen.
Unfortunately I could not find a way to get the ASIO to work, but I have come up with an alternative method which works just as well, as for the latency I got it down to 50 ms, but have been looking into the NAudio source to see if there might be a way to get it below that. (roughly around 20-30 ms) For a better realtime play.
private BufferedWaveProvider buffer;
private WaveOut waveOut;
private WaveIn sourceStream = null;
private bool listen = false;
private void listenBtn_Click(object sender, EventArgs e)
{
listen = !listen;
if (listen)
listenBtn.Text = "Stop listening";
else
{
listenBtn.Text = "Listen";
sourceStream.StopRecording();
return;
}
sourceStream = new WaveIn();
sourceStream.WaveFormat = new WaveFormat(44100, 1);
waveOut = new WaveOut(WaveCallbackInfo.FunctionCallback());
sourceStream.DataAvailable += new EventHandler<WaveInEventArgs>(sourceStream_DataAvailable);
sourceStream.RecordingStopped += new EventHandler<StoppedEventArgs>(sourceStream_RecordingStopped);
buffer = new BufferedWaveProvider(sourceStream.WaveFormat);
buffer.DiscardOnBufferOverflow = true;
waveOut.DesiredLatency = 51;
waveOut.Volume = 1f;
waveOut.Init(buffer);
sourceStream.StartRecording();
}
private void sourceStream_DataAvailable(object sender, WaveInEventArgs e)
{
buffer.AddSamples(e.Buffer, 0, e.BytesRecorded);
waveOut.Play();
}
private void sourceStream_RecordingStopped(object sender, StoppedEventArgs e)
{
sourceStream.Dispose();
waveOut.Dispose();
}
Again I do understand that this is not using ASIO but it was a better alternative based on the resources I had available and the documentation. Instead of using ASIO I am just creating the waveIn and mocking a "recording" but instead of writing that to a file I am taking the stream and pushing it into a waveOut buffer which will allow for it play after I do some sound manipulation.
Probably I am wrong but I have successfully managed simultaneous Asio record and playback using NAudio with very low latencies (on very cheap USB audio hardware ;).
Instead of your event handler method used in your first example you may try this:
private float[] recordingBuffer = null;
private byte[] recordingByteBuffer = null;
private BufferedWaveProvider bufferedWaveProvider;
private BufferedSampleProvider bsp;
private SampleToWaveProvider swp;
// somewhere in e.g. constructor
// set up our signal chain
bufferedWaveProvider = new BufferedWaveProvider(waveFormat);
//bufferedWaveProvider.DiscardOnBufferOverflow = true;
bsp = new BufferedSampleProvider(waveFormat);
swp = new SampleToWaveProvider(bsp);
// ...
private void OnAudioAvailable(object sender, AsioAudioAvailableEventArgs e)
{
this.recordingBuffer = BufferHelpers.Ensure(this.recordingBuffer, e.SamplesPerBuffer * e.InputBuffers.Length);
this.recordingByteBuffer = BufferHelpers.Ensure(this.recordingByteBuffer, e.SamplesPerBuffer * 4 * e.InputBuffers.Length);
int count = e.GetAsInterleavedSamples(this.recordingBuffer);
this.bsp.CurrentBuffer = this.recordingBuffer;
int count2 = this.swp.Read(this.recordingByteBuffer, 0, count * 4);
bufferedWaveProvider.AddSamples(this.recordingByteBuffer, 0, this.recordingByteBuffer.Length);
}
with class BufferedSampleProvider.cs:
public class BufferedSampleProvider : ISampleProvider
{
private WaveFormat waveFormat;
private float[] currentBuffer;
public BufferedSampleProvider(WaveFormat waveFormat)
{
this.waveFormat = waveFormat;
this.currentBuffer = null;
}
public float[] CurrentBuffer
{
get { return this.currentBuffer; }
set { this.currentBuffer = value; }
}
public int Read(float[] buffer, int offset, int count)
{
if (this.currentBuffer != null)
{
if (count <= currentBuffer.Length)
{
for (int i = 0; i < count; i++)
{
buffer[i] = this.currentBuffer[i];
}
return count;
}
}
return 0;
}
public WaveFormat WaveFormat
{
get { return this.waveFormat; }
}
}
I have it done this (messy) way because otherwise I would have to copy the bytes from asio buffers dependent on sample byte count and so on (look at source code from GetAsInterleavedSamples(...) method). To keep it simple for me I have used a BufferedWaveProvider to be really sure there are enough (filled) buffers on the output side of my signal chain even when I'm not really needing it, but it's safe.
After several processing blocks following this provider the chain ends up in the last provider "output". The last provider was passed into
asioOut.InitRecordAndPlayback(output, this.InputChannels, this.SampleRate);
when initializing the whole objects.
Even when I use many processing blocks in my chain, I have no hearable drops or buzzy sounds with asio buffer size of 512 samples. But I think this is really depending on the Asio hardware used.
The most important for me was to be sure to have input and output in sync.
To compare: If I used WaveIn/WaveOutEvent in the same way, I can reach nearly the same latency (on same cheap hardware) but since my tests were on two separate sound devices too, the input buffer duration increases after some time due to some drops or nonsynchronous audio clocks ;) For reaching the very low latency even when using in a WPF application I had to patch WaveOutEvent class to increase priority of playing thread to highest possible, this helps 'against' most of the possible GC interruptions.
Currently it seems with using Asio interface I have sorted out this GC problem at all.
Hope this helps.
I am looking for simple solution which will return integer value of microphone input in c#. I was already checking available samples on net, but none of them worked in a x64 environment. (VS2008 + W7 x64).
Is there any simple solution that will return value of amplitude (or frequency) of microphone input in c#?
I tried NAudio without results and this: http://www.codeproject.com/KB/audio-video/cswavrec.aspx?msg=2155497 without luck.
I reckon the easiest route to go is to use the old Windows multimedia API because it's really straight forward.
Here's the link to MSDN: http://msdn.microsoft.com/en-us/library/dd743586(v=VS.85).aspx
What you do is that you use the waveInOpen function to get an input device. To figure out what device to use you don't enumerate all the devices but you can query each and one of them. The number of installed devices is returned by calling waveInGetNumDevs. You can then call waveInGetDevCaps for each device and inspect those properties.
When you have your device handle you then repeatedly call waveInAddBuffer to get small chunks of data. Depending on the format you specified during waveInOpen the bytes represent the raw audio data. Amplitude in 8 or 16-bit signed or unisnged sampled at some frequency.
You could then apply a rolling average to smooth the signal and just print that.
C# doesn't have a sound API that I know of, so what you do is that you use P/Invoke to get at the Win32 API functions. This is fairly straight forward, you only need to port small versions of the Win32 headers to be able to call them from C# directly.
If you're more hardcore you could write a wrapper library in C++/CLI. That's not that bad of an idea because it let's you use existing Windows C/C++ header files and mix C++ and managed code in intresting ways. Just be careful with the unmanaged resources and you'll have a very powerful intropability library in no time.
But there's also more advanced audio APIs starting with Windows Vista, the Windows Core Audio components which could be more interesting further along the line. But for basic I/O operation the Windows multimedia functions will get you there faster.
I've used these functions on a number of occasions when building simple software synthesizers. Sadly that code is long gone.
I recommend SlimDX since it should work on just about any version of windows (x86 or x64) and provides the most features and flexibility. However, it is a pain to get up and running since there are no good complete code samples. I wrote a wrapper class to simplify its usage though so it can be called like this (I tested this code on Win7 x64):
public void CaptureAudio()
{
using (var source = new SoundCardSource())
{
source.SampleRateKHz = 44.1;
source.SampleDataReady += this.OnSampleDataReady;
source.Start();
// Capture 5 seconds of audio...
Thread.Sleep(5000);
source.Stop();
}
}
private void OnSampleDataReady(object sender, SampleDataEventArgs e)
{
// Do something with e.Data short array on separate thread...
}
Here is the source for the SlimDX wrapper class:
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using SlimDX.DirectSound;
using SlimDX.Multimedia;
public class SampleDataEventArgs : EventArgs
{
public SampleDataEventArgs(short[] data)
{
this.Data = data;
}
public short[] Data { get; private set; }
}
public class SoundCardSource : IDisposable
{
private volatile bool running;
private int bufferSize;
private CaptureBuffer buffer;
private CaptureBufferDescription bufferDescription;
private DirectSoundCapture captureDevice;
private WaveFormat waveFormat;
private Thread captureThread;
private List<NotificationPosition> notifications;
private int bufferPortionCount;
private int bufferPortionSize;
private WaitHandle[] waitHandles;
private double sampleRate;
public SoundCardSource()
{
this.waveFormat = new WaveFormat();
this.SampleRateKHz = 44.1;
this.bufferSize = 2048;
}
public event EventHandler<SampleDataEventArgs> SampleDataReady = delegate { };
public double SampleRateKHz
{
get
{
return this.sampleRate;
}
set
{
this.sampleRate = value;
if (this.running)
{
this.Restart();
}
}
}
public void Start()
{
if (this.running)
{
throw new InvalidOperationException();
}
if (this.captureDevice == null)
{
this.captureDevice = new DirectSoundCapture();
}
this.waveFormat.FormatTag = WaveFormatTag.Pcm; // Change to WaveFormatTag.IeeeFloat for float
this.waveFormat.BitsPerSample = 16; // Set this to 32 for float
this.waveFormat.BlockAlignment = (short)(waveFormat.BitsPerSample / 8);
this.waveFormat.Channels = 1;
this.waveFormat.SamplesPerSecond = (int)(this.SampleRateKHz * 1000D);
this.waveFormat.AverageBytesPerSecond =
this.waveFormat.SamplesPerSecond *
this.waveFormat.BlockAlignment *
this.waveFormat.Channels;
this.bufferPortionCount = 2;
this.bufferDescription.BufferBytes = this.bufferSize * sizeof(short) * bufferPortionCount;
this.bufferDescription.Format = this.waveFormat;
this.bufferDescription.WaveMapped = false;
this.buffer = new CaptureBuffer(this.captureDevice, this.bufferDescription);
this.bufferPortionSize = this.buffer.SizeInBytes / this.bufferPortionCount;
this.notifications = new List<NotificationPosition>();
for (int i = 0; i < this.bufferPortionCount; i++)
{
NotificationPosition notification = new NotificationPosition();
notification.Offset = this.bufferPortionCount - 1 + (bufferPortionSize * i);
notification.Event = new AutoResetEvent(false);
this.notifications.Add(notification);
}
this.buffer.SetNotificationPositions(this.notifications.ToArray());
this.waitHandles = new WaitHandle[this.notifications.Count];
for (int i = 0; i < this.notifications.Count; i++)
{
this.waitHandles[i] = this.notifications[i].Event;
}
this.captureThread = new Thread(new ThreadStart(this.CaptureThread));
this.captureThread.IsBackground = true;
this.running = true;
this.captureThread.Start();
}
public void Stop()
{
this.running = false;
if (this.captureThread != null)
{
this.captureThread.Join();
this.captureThread = null;
}
if (this.buffer != null)
{
this.buffer.Dispose();
this.buffer = null;
}
if (this.notifications != null)
{
for (int i = 0; i < this.notifications.Count; i++)
{
this.notifications[i].Event.Close();
}
this.notifications.Clear();
this.notifications = null;
}
}
public void Restart()
{
this.Stop();
this.Start();
}
private void CaptureThread()
{
int bufferPortionSamples = this.bufferPortionSize / sizeof(float);
// Buffer type must match this.waveFormat.FormatTag and this.waveFormat.BitsPerSample
short[] bufferPortion = new short[bufferPortionSamples];
int bufferPortionIndex;
this.buffer.Start(true);
while (this.running)
{
bufferPortionIndex = WaitHandle.WaitAny(this.waitHandles);
this.buffer.Read(
bufferPortion,
0,
bufferPortionSamples,
bufferPortionSize * Math.Abs((bufferPortionIndex - 1) % bufferPortionCount));
this.SampleDataReady(this, new SampleDataEventArgs(bufferPortion));
}
this.buffer.Stop();
}
public void Dispose()
{
this.Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
this.Stop();
if (this.captureDevice != null)
{
this.captureDevice.Dispose();
this.captureDevice = null;
}
}
}
}
It's fully multi-threaded to minimize latency. I originally wrote it for a real-time signal processing analysis tool and used float output instead of short but I modified the code sample to match your requested usage. If you need frequency data I would use http://www.mathdotnet.com/Neodym.aspx or http://www.exocortex.org/dsp/ for a good C# FFT library.