BizHawk/BizHawk.Emulation.Common/Sound/Utilities/ISynchronizingAudioBuffer.cs

128 lines
3.6 KiB
C#

using System.Collections.Generic;
namespace BizHawk.Emulation.Common
{
public interface ISynchronizingAudioBuffer
{
void EnqueueSample(short left, short right);
void Clear();
// returns the number of samples actually supplied, which may not match the number requested
// ^^ what the hell is that supposed to mean.
// the entire point of an ISynchronizingAudioBuffer
// is to provide exact amounts of output samples,
// even when the input provided varies....
int OutputSamples(short[] buf, int samplesRequested);
}
public class VecnaSynchronizer : ISynchronizingAudioBuffer
{
// vecna's attempt at a fully synchronous sound provider.
// It's similar in philosophy to my "BufferedAsync" provider, but BufferedAsync is not
// fully synchronous.
// Like BufferedAsync, it tries to make most frames 100% correct and just suck it up
// periodically and have a big bad-sounding mistake frame if it has to.
// It is significantly less ambitious and elaborate than the other methods.
// We'll see if it works better or not!
// It has a min and maximum amount of excess buffer to deal with minor overflows.
// When fast-forwarding, it will discard samples above the maximum excess buffer.
// When underflowing, it will attempt to resample to a certain thresh
// old.
// If it underflows beyond that threshold, it will give up and output silence.
// Since it has done this, it will go ahead and generate some excess silence in order
// to restock its excess buffer.
private struct Sample
{
public readonly short Left;
public readonly short Right;
public Sample(short left, short right)
{
Left = left;
Right = right;
}
}
private const int MaxExcessSamples = 2048;
private readonly Queue<Sample> _buffer;
private readonly Sample[] _resampleBuffer;
public VecnaSynchronizer()
{
_buffer = new Queue<Sample>(2048);
_resampleBuffer = new Sample[2730]; // 2048 * 1.25
// Give us a little buffer wiggle-room
for (int i = 0; i < 367; i++)
{
_buffer.Enqueue(new Sample(0, 0));
}
}
public void EnqueueSample(short left, short right)
{
if (_buffer.Count >= MaxExcessSamples - 1)
{
// if buffer is overfull, dequeue old samples to make room for new samples.
_buffer.Dequeue();
}
_buffer.Enqueue(new Sample(left, right));
}
public void Clear()
{
_buffer.Clear();
}
public int OutputSamples(short[] buf, int samplesRequested)
{
if (samplesRequested > _buffer.Count)
{
// underflow!
if (_buffer.Count > samplesRequested * 3 / 4)
{
// if we're within 75% of target, then I guess we suck it up and resample.
// we sample in a goofy way, we could probably do it a bit smarter, if we cared more.
int samplesAvailable = _buffer.Count;
for (int i = 0; _buffer.Count > 0; i++)
{
_resampleBuffer[i] = _buffer.Dequeue();
}
int index = 0;
for (int i = 0; i < samplesRequested; i++)
{
Sample sample = _resampleBuffer[i * samplesAvailable / samplesRequested];
buf[index++] += sample.Left;
buf[index++] += sample.Right;
}
}
else
{
// we're outside of a "reasonable" underflow. Give up and output silence.
// Do nothing. The whole frame will be excess buffer.
}
}
else
{
// normal operation
int index = 0;
for (int i = 0; i < samplesRequested && _buffer.Count > 0; i++)
{
Sample sample = _buffer.Dequeue();
buf[index++] += sample.Left;
buf[index++] += sample.Right;
}
}
return samplesRequested;
}
}
}