I'm using NAudio and the WasapiLoopbackCapture in combination with WaveOut in order to create an echo effect that is global, i.e. affects the current audio output.
However, my implementation, which seems pretty poor to me, doesn't work well at all. On every machine it sounds different, sometimes it even crashes.
This is the code I came up with. It records the audio output and every 75ms it plays the recorded stream using WaveOut.
What am I doing wrong here, how can this be improved?
private static void CreateReverb()
{
MemoryStream loopbackWave = new MemoryStream();
DateTime lastEcho = DateTime.Now;
WasapiLoopbackCapture waveIn = new WasapiLoopbackCapture();
waveIn.StartRecording();
waveIn.DataAvailable += delegate(object sender, WaveInEventArgs e)
{
loopbackWave.Write(e.Buffer, 0, e.BytesRecorded);
if ((DateTime.Now - lastEcho).TotalMilliseconds > 75)
{
lastEcho = DateTime.Now;
byte[] loopbackBytes = loopbackWave.ToArray();
loopbackWave.Dispose();
loopbackWave = new MemoryStream();
using (MemoryStream waveStream = new MemoryStream())
{
using (WaveFileWriter waveFileWriter = new WaveFileWriter(waveStream, waveIn.WaveFormat))
{
waveFileWriter.Write(loopbackBytes, 0, loopbackBytes.Length);
}
loopbackBytes = waveStream.ToArray();
}
for (int i = 0; i < 3; i++)
{
ThreadPool.QueueUserWorkItem(delegate
{
try
{
WaveOut waveOut = new WaveOut();
waveOut.Init(new WaveFileReader(new MemoryStream(loopbackBytes)));
waveOut.Volume = ReverbIntensity * .5f;
waveOut.Play();
Thread.Sleep(200);
waveOut.Dispose();
}
catch { }
});
}
}
};
}
Edit: Second attempt:
This time, I tried using BufferedWaveProvider
as suggested by Mark. The problem is that the playback has no delay, thus creating a feedback loop instead of an echo. The output has to be about .5f
in volume and have to have about 75ms delay in order to make it work.
private static void CreateReverb()
{
DateTime lastEcho = DateTime.Now;
WasapiLoopbackCapture waveIn = new WasapiLoopbackCapture();
BufferedWaveProvider bufferedWaveProvider = new BufferedWaveProvider(waveIn.WaveFormat);
WasapiOut wasapiOut = new WasapiOut(AudioClientShareMode.Shared, 0);
bufferedWaveProvider.DiscardOnBufferOverflow = true;
wasapiOut.Init(bufferedWaveProvider);
wasapiOut.Play();
waveIn.StartRecording();
waveIn.DataAvailable += delegate(object sender, WaveInEventArgs e)
{
bufferedWaveProvider.AddSamples(e.Buffer, 0, e.BytesRecorded);
};
}
Don't keep opening hundreds of instances of WaveOut
. You should have just one instance of WaveOut
, that is playing from a custom stream that starts with the recorded audio (a BufferedWaveProvider
would be a good starting point), and then pass that through your effect. The best way to do this is to create an implementation of ISampleProvider
that in its Read
method reads from a source provider, and then performs the effect.
Finally, you are capturing sound with loopback audio and then playing it back out of the same device, which in itself is likely to be creating a nasty feedback loop.