How to know when SoundPlayer has finished playing audio
I am using the following code to dynamically create a frequency tone in memory and play the tone asynchronously:
public static void PlayTone(UInt16 frequency, int msDuration, UInt16 volume = 16383)
{
using (var mStrm = new MemoryStream())
{
using (var writer = new BinaryWriter(mStrm))
{
const double tau = 2*Math.PI;
const int formatChunkSize = 16;
const int headerSize = 8;
const short formatType = 1;
const short tracks = 1;
const int samplesPerSecond = 44100;
const short bitsPerSample = 16;
const short frameSize = (short) (tracks*((bitsPerSample + 7)/8));
const int bytesPerSecond = samplesPerSecond*frameSize;
const int waveSize = 4;
var samples = (int) ((decimal) samplesPerSecond*msDuration/1000);
int dataChunkSize = samples*frameSize;
int fileSize = waveSize + headerSize + formatChunkSize + headerSize + dataChunkSize;
writer.Write(0x46464952);
writer.Write(fileSize);
writer.Write(0x45564157);
writer.Write(0x20746D66);
writer.Write(formatChunkSize);
writer.Write(formatType);
writer.Write(tracks);
writer.Write(samplesPerSecond);
writer.Write(bytesPerSecond);
writer.Write(frameSize);
writer.Write(bitsPerSample);
writer.Write(0x61746164);
writer.Write(dataChunkSize);
double theta = frequency*tau/samplesPerSecond;
double amp = volume >> 2;
for (int step = 0; step < samples; step++)
{
writer.Write((short) (amp*Math.Sin(theta*step)));
}
mStrm.Seek(0, SeekOrigin.Begin);
using (var player = new System.Media.SoundPlayer(mStrm))
{
player.Play();
}
}
}
}
The code works fine. The only problem is how do you know when the tone stops playing? There seems to be no Completed event in the SoundPlayer class that I can subscribe to.
source to share
You know, if all you want to do is play one tone, Console.Beep . Of course it doesn't do it in the background, but the technique below works great for Console.Beep
, and it prevents you from creating a memory stream just to play the tone.
SoundPlayer
Doesn't have the required functionality anyway , but you can simulate it.
First, create an event handler:
void SoundPlayed(object sender, EventArgs e)
{
// do whatever here
}
Modify your method PlayTone
so that it takes a parameter of the callback function:
public static void PlayTone(UInt16 frequency, int msDuration, UInt16 volume = 16383, EventHandler doneCallback = null)
Then change the end of the method so that it calls PlaySync
, not Play
, and calls doneCallback
afterwards:
using (var player = new System.Media.SoundPlayer(mStrm))
{
player.PlaySync();
}
if (doneCallback != null)
{
// the callback is executed on the thread.
doneCallback(this, new EventArgs());
}
Then execute it on a thread or task. For example:
var t = Task.Factory.StartNew(() => {PlayTone(100, 1000, 16383, SoundPlayed));
The main problem is that event notification happens on a background thread. Probably the best thing to do if you need to affect the UI is to synchronize your event handler with the UI thread. So in a method, SoundPlayed
you call Form.Invoke
(for WinForms) or Dispatcher.Invoke
(WPF) to perform any UI action.
source to share