Sunday, April 05, 2009

NAudio Tutorial 5 - Recording Audio

Time for another installment of the NAudio Tutorials; this week we will be looking at how to Record Audio using NAudio from two different recording scenarios. The first being the use of NAudio to record any and all sound coming from the local Sound Card input, whether that be from a Microphone, the Line In Device or the Sound Cards on board wave mixer. The second approach we will be looking at is recording only the Audio that has been mixed by NAudio, regardless of what other audio is being played on the system at the time. This is useful for scenarios where you want to play over a backing track, or play your samples against a click track being played from another program but don't want to record the click track. The additional advantage of recording the audio mixed directly from NAudio is that there is 0 degradation in quality through the process; no audio playing means pure silence, rather than almost silence which for your average audio hardware would be the result, there is always some level of noise when working with an analog signal.

This NAudio Recording audio tutorial builds upon the concepts presented in previous NAudio Tutorials, if you haven't yet had the opportunity to review them I suggest that you venture there first and resume reading this tutorial after you have understood the basic NAudio concepts.

http://opensebj.blogspot.com/2009/02/introduction-to-using-naudio.html
http://opensebj.blogspot.com/2009/02/naudio-tutorial-2-mixing-multiple-wave.html
http://opensebj.blogspot.com/2009/03/naudio-tutorial-3-sample-properties.html
http://opensebj.blogspot.com/2009/03/naudio-tutorials-minor-note.html
http://opensebj.blogspot.com/2009/03/naudio-tutorial-4-sample-reversing.html

Time for another disclaimer, the second approach discussed here, recording the mix directly from NAudio has been suggested as a feature for inclusion in the main branch. I'm not sure if it fits in to the long term direction for the WaveMixerStream32 class, in any case, the code for these modifications have been included in this Tutorial and thanks to the Open Source nature of NAudio you can make these same changes to an instance of the library for yourself. You can find the specific details of this suggest contained in this forum post:

http://naudio.codeplex.com/Thread/View.aspx?ThreadId=52296

If you have any feedback on this tutorial, drop me a line or post a question in the comments section below.

Download the full article (AbiWord and RTF Format), example C#.Net Source Code and tutorial program here.

Recoding from the Sound Card

This is remarkably simple to achieve in NAudio, short of having a big red button which we push before it leaves the factory. First step is to setup.. ah forget the steps here is the code:

// WaveIn Streams for recording
WaveIn waveInStream;
WaveFileWriter writer;

waveInStream = new WaveIn(44100,2);
writer = new WaveFileWriter(outputFilename, waveInStream.WaveFormat);

waveInStream.DataAvailable += new EventHandler<WaveInEventArgs>(waveInStream_DataAvailable);
waveInStream.StartRecording();

No joke that's almost it. The only interesting thing here that we need to consider it that we have added an EventHandler that needs to be setup to handle data when it's ready to be handed off to the WaveFileWriter:

void waveInStream_DataAvailable(object sender, WaveInEventArgs e)
{
   writer.WriteData(e.Buffer, 0, e.BytesRecorded);
}

Er, thats it to start recording. We can stop the recording as such:

waveInStream.StopRecording();
waveInStream.Dispose();
waveInStream = null;
writer.Close();
writer = null;

See it would have been to simple a tutorial if we stopped here but feel free to stop reading and give it a crack. Using this method any audio which isn't muted on you input mixer will be recorded; it's up to you and the windows Mixer API to decide what you want to record, isn't that nice; except that you can't only record Audio from your audio application if there are other applications playing sounds in the background - say you get a call on your VOIP connection right in the middle of the hottest composition ever, or some one PM's you in IRC, or you click around on your PC looking for that cool new sample to load, with all the button clicks and other useless sounds being saved in to your mixed composition - oh no. Lets now look at how this unfortunate situation can be avoided.

Direct-To-Disk recoding via the NAudio WaveMixerStream32 Class

Now this is slightly more complicated but much more fun and presents you with a superior audio recording (especially on lousy or average audio hard ware, say my PC for instance). We will cover the code which will be required within the calling application first and then secondly review the changes required within the NAudio library, just so we have some comparison in amount of effort required for both approaches.

mixer.StreamMixToDisk(outputFilename);
mixer.StartStreamingToDisk();

Assuming you already have the mixer defined, thats all that is required to start recording. We can pause the streaming to disk by:

mixer.PauseStreamingToDisk();

Or resume by:

mixer.ResumeStreamingToDisk();

Finally stopping by:

mixer.StopStreamingToDisk();

Easy enough but we should cover whats required for this to actually work right? So lets dive in to the modifications in the WaveMixerStream32.cs file and hack till our hearts are content. In the declaration section of the class we need to add the following:

// Declarations to support the streamToDisk recording methodology
private bool streamToDisk;
private string streamToDiskFileName;
WaveFileWriter writer;

Now we add in the methods that support our calls:

/// <summary>
/// Starts the Strem To Disk recording if a file name to save the stream to has been setup
/// </summary>
public void StartStreamingToDisk()
{
   if (streamToDiskFileName != "")
   {
       streamToDisk = true;
   }
}

/// <summary>
/// Pause's the stream to disk recording (No further blocks are written during the mixing)
/// </summary>
public void PauseStreamingToDisk()
{
   streamToDisk = false;
}

/// <summary>
/// Resume streaming to disk
/// </summary>
public void ResumeStreamingToDisk()
{
   streamToDisk = true;
}

/// <summary>
/// Stop the streaming to disk and clean up
/// </summary>
public void StopStreamingToDisk()
{
   streamToDisk = false;
   writer.Close();
}

/// <summary>
/// Setup the StreamMixToDisk file and initalise the WaveFileWriter
/// </summary>
/// <param name="FileName">FileName to save the mixed stream</param>
public void StreamMixToDisk(string FileName)
{
   streamToDiskFileName = FileName;
   writer = new WaveFileWriter(FileName, this.WaveFormat);
}

/// <summary>
/// Using the final set of data passed through in the overriden read method to also be passed to the WaveFileWriter
/// </summary>
/// <param name="buffer">Data to be written</param>
/// <param name="offset">The Offset, should be 0 as we are taking the mixed data to write and want it all</param>
/// <param name="count">The total count of all the mixed data in the buffer</param>
private void WriteMixStreamOut(byte[] buffer, int offset, int count)
{
   // Write the data to the file
   writer.WriteData(buffer, offset, count);
}

All thats left is the modification to the Read method to pass this data back to the WriteMixStream method. Rather than pasting in the whole read method, even though it may make it look like I've done some extra work, I'll just copy in the last 8 or so lines:

position += count;
// If streamToDisk has been enabled the mixed audio will be streamed directly to a wave file, so we need to send the data to the wave file writer
if (streamToDisk)
{
   WriteMixStreamOut(readBuffer, 0, count);
}
return count;
}

Having jammed the check for streaming out to disk, after the final calculation and before the method is exited gives us everything we need to stream to our file. So now we have two methods of recording audio data and you want to know what my favorite part is?

You can actually use both at the same time and get multi-track / multi-channel audio recording on the same machine with a fairly standard sound card!

I normally refrain from using exclamation points but I was actually quite excited when I tested this. It means that some one can be jamming along on say a C# Audio Synthesizer / Beat Box or composition tool like OpenSebJ while another person is singing vocals or playing in a guitar riff through line in. I guess if your really talented you could be doing both at the same time, perhaps signing to the jam is more ilkley - what ever it is it can actually work; you can record both sets of audio separately - because the NAudio Stream-To-Disk method is not actually using your sound card to save the mixed result. Cool, well I think so.

Download the example program and have a look for yourself.






Conclusion

As pre usual, I've packaged up a copy of the entire article, along with a copy of the example program and source for your consumption. For the modifications required to the NAudio library, I have also copied in to the zip the modified version of WaveMixerStream32.cs for your convenience. Let me know if you have any questions, comments or if your keen to contribute to a project like OpenSebJ.

Until next time, when we look at - well I have actually decided yet. There are two things which are on the list from Tutorial 3 however I don't think they are currently the items which are peaking my interest, so lets assume it will most likely be something from the list below:


  •  Adding Audio Effects to a Stream 

  •  Transposing the frequency of the stream being played back

  •  Using MIDI to trigger audio samples

  • Playing compressed Audio (MP3 & OGG)

  •  Or something else that takes my fancy, write to me and suggest what that may be.



If you haven't already; Download the full article (AbiWord and RTF Format), example C#.Net Source Code and tutorial program here.