public static void WriteCallback(SoundIOOutStream stream, int frameCountMin, int frameCountMax) { SoundIoError err; DateTime callbackTime = DateTime.Now; // loop to utilize as much as the frameCountMax as possible while (frameCountMax > 0) { int frameCount = frameCountMax; if ((err = stream.BeginWrite(out SoundIOChannelAreas areas, ref frameCount)) != SoundIoError.None) { Console.WriteLine("unrecoverable stream error: {0}", err.GetErrorMessage()); _fileDone = true; } if (frameCount == 0) { break; } var bufferCount = frameCount * stream.Layout.ChannelCount; // if buffer is done add silence based on latency to allow stream to complete through // audio path before stream is Disposed() if (_waveFile.Position >= _waveFile.Length) { if (_startSilence) { // windows latency is a little higher (using DateTime to determine the callback time delay) // and needs to be accoutned for... _latencySeconds -= (DateTime.Now - callbackTime).TotalMilliseconds / 1000.0; _silentSamplesRemaining = (int)((stream.SampleRate * stream.Layout.ChannelCount) * _latencySeconds); _silentSamplesRemaining -= _silentSamplesAlreadySent; _startSilence = false; } int silentBufferSize; if (_silentSamplesRemaining > bufferCount) { silentBufferSize = bufferCount; _silentSamplesRemaining -= bufferCount; } else { silentBufferSize = _silentSamplesRemaining; _silentSamplesRemaining = 0; } if (silentBufferSize > 0) { // create a new buffer initialized to 0 and copy to native buffer var silenceBuffer = new float[silentBufferSize]; stream.CopyTo(silenceBuffer, 0, areas, silentBufferSize); } if (_silentSamplesRemaining == 0) { _fileDone = true; stream.EndWrite(); stream.Pause(true); return; } // if the remaining audioBuffer will only partially fill the frameCount // copy the remaining amount and set the startSilence flag to allow // stream to play to the end. } else if (_waveFile.Position + (frameCount * _waveFile.WaveFormat.Channels) >= _waveFile.Length) { float[] audioBuffer = new float[bufferCount]; var actualSamplesRead = _sampleProvider.Read(audioBuffer, 0, bufferCount); stream.CopyTo(audioBuffer, 0, areas, bufferCount); _silentSamplesAlreadySent = bufferCount - actualSamplesRead; _latencySeconds = 0.0; _startSilence = true; } else { // copy audioBuffer data to native buffer and advance the bufferPos float[] audioBuffer = new float[bufferCount]; var actualSamplesRead = _sampleProvider.Read(audioBuffer, 0, bufferCount); stream.CopyTo(audioBuffer, 0, areas, actualSamplesRead); if (_waveFile.Position >= _waveFile.Length) { _latencySeconds = 0.0; _startSilence = true; } } if ((err = stream.EndWrite()) != SoundIoError.None) { if (err == SoundIoError.Underflow) { return; } Console.WriteLine("Unrecoverable stream error: {0}", err.GetErrorMessage()); _fileDone = true; } if (_startSilence) { // get actual latency in order to compute number of silent frames stream.GetLatency(out _latencySeconds); callbackTime = DateTime.Now; } // loop until frameCountMax is used up frameCountMax -= frameCount; } return; }