Friday, 9 October 2009

Recording the Soundcard Output to WAV in NAudio

Suppose you want to not just play back some audio, but record what you are playing to a WAV file. This can be achieved in NAudio by creating an IWaveProvider whose read method reads from another IWaveProvider but also writes to disk as it goes. This is very easy to implement, and the WaveRecorder class I present here will be added to NAudio shortly. Our WaveRecorder also needs to be disposable, as we will want to close the WAV file when we are finished.
/// <summary>
/// Utility class to intercept audio from an IWaveProvider and
/// save it to disk
/// </summary>
public class WaveRecorder : IWaveProvider, IDisposable
{
    private WaveFileWriter writer;
    private IWaveProvider source;
 
    /// <summary>
    /// Constructs a new WaveRecorder
    /// </summary>
    /// <param name="destination">The location to write the WAV file to</param>
    /// <param name="source">The source Wave Provider</param>
    public WaveRecorder(IWaveProvider source, string destination)
    {
        this.source = source;
        this.writer = new WaveFileWriter(destination, source.WaveFormat);
    }
     
    /// <summary>
    /// Read simply returns what the source returns, but writes to disk along the way
    /// </summary>
    public int Read(byte[] buffer, int offset, int count)
    {
        int bytesRead = source.Read(buffer, offset, count);
        writer.WriteData(buffer, offset, bytesRead);
        return bytesRead;
    }
 
    /// <summary>
    /// The WaveFormat
    /// </summary>
    public WaveFormat WaveFormat
    {
        get { return source.WaveFormat; }
    }
 
    /// <summary>
    /// Closes the WAV file
    /// </summary>
    public void Dispose()
    {
        if (writer != null)
        {
            writer.Dispose();
            writer = null;
        }
    }
}

Now we have our WaveRecorder, we can insert it anywhere in the chain we like. The most obvious place is right at the end of the chain. So we wrap the WaveStream or WaveProvider we would normally pass to the Init method of our IWavePlayer with the WaveRecorder class. To demonstrate, I will extend the sine wave generating code I created recently, to save the sine wave you are playing to disk. Only three extra lines of code are required:
IWavePlayer waveOut;
WaveRecorder recorder; 

private void button1_Click(object sender, EventArgs e)
{
    StartStopSineWave();
}

void StartStopSineWave()
{
    if (waveOut == null)
    {
        var sineWaveProvider = new SineWaveProvider16();
        sineWaveProvider.SetWaveFormat(16000, 1); // 16kHz mono
        sineWaveProvider.Frequency = 500;
        sineWaveProvider.Amplitude = 0.1f;
        recorder = new WaveRecorder(sineWaveProvider, @"C:\Users\Mark\Documents\sine.wav");
        waveOut = new WaveOut();
        waveOut.Init(recorder);
        waveOut.Play();
    }
    else
    {
        waveOut.Stop();
        waveOut.Dispose();
        waveOut = null;
        recorder.Dispose();
        recorder = null;                
    }
}
Post a Comment