128 lines
3.6 KiB
C#
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;
|
|
}
|
|
}
|
|
}
|