Monday, 20 October 2008

Volume Metering and Audio Waveform Display in NAudio

I spent a couple of hours this evening adding two features to NAudio that I have been meaning to add for a long time. They are two Windows Forms controls, one to display volume levels, and the other to display audio waveforms. They are still in a very raw state, and I will probably make some enhancements to their appearance as well as how they calculate their volume levels in the future, but they work pretty well for basic visualisation purposes. I'm also looking forward to creating their equivalents in WPF.

winforms-waveform

I've made a very short screencast to show them in action. Watch it here: http://screencast.com/t/m13tSFGAG

11 comments:

Anonymous said...

Great application!

Can you display the complete waveform of the audio track in a control?

Mark H said...

that's not a specific feature, although the code wouldn't be too hard to change to do it.
I'm hoping to add some better waveform drawing support in the future including some WPF rendering

Iron Yuppie said...

this really is cool... did you ever get the waveform working in SL?

Mark H said...

I have WaveForm display working in WPF. We need to wait for Silverlight 3 before there is any point doing waveform display, as only in SL3 do we get access to the samples during playback.

splaxtek said...

Hi Mark been looking to use your Volume meter but i can't get them working. Tried also doing the ones with kaxaml but without success. Yours seems to be easier to use. But the thing is i need them to preview the input level before i start recording. can you please help me how i'm going to do it pls

thanks

Mark H said...

hi Splaxtek,
have a look at my .NET Voice Recorder project to see how you preview volume with NAudio. It's quite simple really, just start 'recording' but only use the data you get to update the meter, don't write it to a file.

Anonymous said...

Hi Mark!

May I clarify that what you mean for your waveform painter is that the amplitude (I assume thats what it is painting) will never become negative? Because for waveforms, their shape when zoomed in on is usually an 'up-down' shape from the middle (which when looked at normally will result in your waveform shape).

So basically, what it does is, you read data from the WAV file, get a block from the data, and get and display the maximum amplitude or peak for that block. Is this right?

Thanks!

Mark H said...

@anonymous, yes this makes a symetric waveform based on the absolute peak over the period aggregated.

Anonymous said...

hi mark,
may i ask, 'have a look at my .NET Voice Recorder project to see how you preview volume with NAudio' as u stated above, i've seen them but i cant really get it. can u pls pinpoint out the functions for me? thanks a lot.

ben said...

private float lastPeak;

void recorder_MaximumCalculated(object sender, MaxSampleEventArgs e)
{
lastPeak = Math.Max(e.MaxSample, Math.Abs(e.MinSample));
}

public float CurrentInputLevel { get { return lastPeak * 100; } }

void waveIn_DataAvailable(object sender, WaveInEventArgs e)
{
if (this.InvokeRequired)
{
this.BeginInvoke(new EventHandler(waveIn_DataAvailable), sender, e);
}
else
{
writer.WriteData(e.Buffer, 0, e.BytesRecorded);
int secondsRecorded = (int)(writer.Length / writer.WaveFormat.AverageBytesPerSecond);
waveformPainter2.AddMax(CurrentInputLevel);
if (secondsRecorded >= 30)
{
StopRecording();
}
}
}

I'm wondering y this does not work?

ben said...

private float lastPeak;

void recorder_MaximumCalculated(object sender, MaxSampleEventArgs e)
{
lastPeak = Math.Max(e.MaxSample, Math.Abs(e.MinSample));
}

public float CurrentInputLevel { get { return lastPeak * 100; } }

void waveIn_DataAvailable(object sender, WaveInEventArgs e)
{
if (this.InvokeRequired)
{
this.BeginInvoke(new EventHandler(waveIn_DataAvailable), sender, e);
}
else
{
writer.WriteData(e.Buffer, 0, e.BytesRecorded);
int secondsRecorded = (int)(writer.Length / writer.WaveFormat.AverageBytesPerSecond);
waveformPainter2.AddMax(CurrentInputLevel);
if (secondsRecorded >= 30)
{
StopRecording();
}
}
}

I'm wondering y this does not work?