diff --git a/BizHawk.Emulation.Common/BizHawk.Emulation.Common.csproj b/BizHawk.Emulation.Common/BizHawk.Emulation.Common.csproj
index f59446d021..a88d8f7e8d 100644
--- a/BizHawk.Emulation.Common/BizHawk.Emulation.Common.csproj
+++ b/BizHawk.Emulation.Common/BizHawk.Emulation.Common.csproj
@@ -147,6 +147,7 @@
+
diff --git a/BizHawk.Emulation.Common/Sound/Utilities/ISynchronizingAudioBuffer.cs b/BizHawk.Emulation.Common/Sound/Utilities/ISynchronizingAudioBuffer.cs
new file mode 100644
index 0000000000..7df1a7c3a6
--- /dev/null
+++ b/BizHawk.Emulation.Common/Sound/Utilities/ISynchronizingAudioBuffer.cs
@@ -0,0 +1,652 @@
+using System;
+using System.Collections.Generic;
+
+namespace BizHawk.Emulation.Common
+{
+ public interface ISynchronizingAudioBuffer
+ {
+ void EnqueueSamples(short[] buf, int samplesProvided);
+ 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 ISynchronzingAudioBuffer
+ // is to provide exact amounts of output samples,
+ // even when the input provided varies....
+ int OutputSamples(short[] buf, int samplesRequested);
+ }
+
+ internal class ZeromusSynchronizer : ISynchronizingAudioBuffer
+ {
+ public ZeromusSynchronizer()
+ {
+ ////#ifdef NDEBUG
+ _adjustobuf = new Adjustobuf(200, 1000);
+ ////#else
+ ////adjustobuf = new Adjustobuf(22000, 44000);
+ ////#endif
+ }
+
+ // adjustobuf(200,1000)
+ private bool _mixqueueGo;
+
+ public void Clear()
+ {
+ _adjustobuf.Clear();
+ }
+
+ public void EnqueueSample(short left, short right)
+ {
+ _adjustobuf.Enqueue(left, right);
+ }
+
+ public void EnqueueSamples(short[] buf, int samplesProvided)
+ {
+ int ctr = 0;
+ for (int i = 0; i < samplesProvided; i++)
+ {
+ short left = buf[ctr++];
+ short right = buf[ctr++];
+ _adjustobuf.Enqueue(left, right);
+ }
+ }
+
+ // returns the number of samples actually supplied, which may not match the number requested
+ public int OutputSamples(short[] buf, int samplesRequested)
+ {
+ int ctr = 0;
+ int done = 0;
+ if (!_mixqueueGo)
+ {
+ if (_adjustobuf.Size > 200)
+ {
+ _mixqueueGo = true;
+ }
+ }
+ else
+ {
+ for (int i = 0; i < samplesRequested; i++)
+ {
+ if (_adjustobuf.Size == 0)
+ {
+ _mixqueueGo = false;
+ break;
+ }
+
+ done++;
+ short left, right;
+ _adjustobuf.Dequeue(out left, out right);
+ buf[ctr++] = left;
+ buf[ctr++] = right;
+ }
+ }
+
+ return done;
+ }
+
+ private readonly Adjustobuf _adjustobuf;
+
+ private class Adjustobuf
+ {
+ public Adjustobuf(int minLatency, int maxLatency)
+ {
+ _minLatency = minLatency;
+ _maxLatency = maxLatency;
+ Clear();
+ }
+
+ private readonly int _minLatency;
+ private readonly int _maxLatency;
+ private readonly short[] _curr = new short[2];
+
+ private readonly Queue _buffer = new Queue();
+ private readonly Queue _statsHistory = new Queue();
+
+ private float _rate, _cursor;
+ private int _targetLatency;
+ private long _rollingTotalSize;
+ private uint _kAverageSize;
+
+ public int Size { get; private set; }
+
+ public void Clear()
+ {
+ _buffer.Clear();
+ _statsHistory.Clear();
+ _rollingTotalSize = 0;
+ _targetLatency = (_maxLatency + _minLatency) / 2;
+ _rate = 1.0f;
+ _cursor = 0.0f;
+ _curr[0] = _curr[1] = 0;
+ _kAverageSize = 80000;
+ Size = 0;
+ }
+
+ public void Enqueue(short left, short right)
+ {
+ _buffer.Enqueue(left);
+ _buffer.Enqueue(right);
+ Size++;
+ }
+
+ private void AddStatistic()
+ {
+ _statsHistory.Enqueue(Size);
+ _rollingTotalSize += Size;
+ if (_statsHistory.Count > _kAverageSize)
+ {
+ _rollingTotalSize -= _statsHistory.Peek();
+ _statsHistory.Dequeue();
+
+ float averageSize = (float)(_rollingTotalSize / _kAverageSize);
+ ////static int ctr=0; ctr++; if((ctr&127)==0) printf("avg size: %f curr size: %d rate: %f\n",averageSize,size,rate);
+ {
+ float targetRate;
+ if (averageSize < _targetLatency)
+ {
+ targetRate = 1.0f - ((_targetLatency - averageSize) / _kAverageSize);
+ }
+ else if (averageSize > _targetLatency)
+ {
+ targetRate = 1.0f + ((averageSize - _targetLatency) / _kAverageSize);
+ }
+ else
+ {
+ targetRate = 1.0f;
+ }
+
+ ////rate = moveValueTowards(rate,targetRate,0.001f);
+ _rate = targetRate;
+ }
+ }
+ }
+
+ public void Dequeue(out short left, out short right)
+ {
+ left = right = 0;
+ AddStatistic();
+ if (Size == 0)
+ {
+ return;
+ }
+
+ _cursor += _rate;
+ while (_cursor > 1.0f)
+ {
+ _cursor -= 1.0f;
+ if (Size > 0)
+ {
+ _curr[0] = _buffer.Dequeue();
+ _curr[1] = _buffer.Dequeue();
+ Size--;
+ }
+ }
+
+ left = _curr[0];
+ right = _curr[1];
+ }
+ }
+ }
+
+ internal class NitsujaSynchronizer : ISynchronizingAudioBuffer
+ {
+ private struct Ssamp
+ {
+ public readonly short L, R;
+
+ public Ssamp(short left, short right)
+ {
+ L = left;
+ R = right;
+ }
+ }
+
+ private readonly List _sampleQueue = new List();
+
+ // returns values going between 0 and y-1 in a saw wave pattern, based on x
+ private static int Pingpong(int x, int y)
+ {
+ x %= 2 * y;
+ if (x >= y)
+ {
+ x = (2 * y) - x - 1;
+ }
+
+ return x;
+
+ // in case we want to switch to odd buffer sizes for more sharpness
+ ////x %= 2*(y-1);
+ ////if(x >= y)
+ ////x = 2*(y-1) - x;
+ ////return x;
+ }
+
+ private static Ssamp Crossfade(Ssamp lhs, Ssamp rhs, int cur, int start, int end)
+ {
+ if (cur <= start)
+ {
+ return lhs;
+ }
+
+ if (cur >= end)
+ {
+ return rhs;
+ }
+
+ // in case we want sine wave interpolation instead of linear here
+ ////float ang = 3.14159f * (float)(cur - start) / (float)(end - start);
+ ////cur = start + (int)((1-cosf(ang))*0.5f * (end - start));
+
+ int inNum = cur - start;
+ int outNum = end - cur;
+ int denom = end - start;
+
+ int lrv = ((int)lhs.L * outNum + (int)rhs.L * inNum) / denom;
+ int rrv = ((int)lhs.R * outNum + (int)rhs.R * inNum) / denom;
+
+ return new Ssamp((short)lrv, (short)rrv);
+ }
+
+ public void Clear()
+ {
+ _sampleQueue.Clear();
+ }
+
+ private static void EmitSample(short[] outbuf, ref int cursor, Ssamp sample)
+ {
+ outbuf[cursor++] = sample.L;
+ outbuf[cursor++] = sample.R;
+ }
+
+ private static void EmitSamples(short[] outbuf, ref int outcursor, Ssamp[] samplebuf, int incursor, int samples)
+ {
+ for (int i = 0; i < samples; i++)
+ {
+ EmitSample(outbuf, ref outcursor, samplebuf[i + incursor]);
+ }
+ }
+
+ private static short Abs(short value)
+ {
+ if (value < 0)
+ {
+ return (short)-value;
+ }
+
+ return value;
+ }
+
+ private static int Abs(int value)
+ {
+ if (value < 0)
+ {
+ return -value;
+ }
+
+ return value;
+ }
+
+ public void EnqueueSamples(short[] buf, int samplesProvided)
+ {
+ int cursor = 0;
+ for (int i = 0; i < samplesProvided; i++)
+ {
+ _sampleQueue.Add(new Ssamp(buf[cursor + 0], buf[cursor + 1]));
+ cursor += 2;
+ }
+ }
+
+ public void EnqueueSample(short left, short right)
+ {
+ _sampleQueue.Add(new Ssamp(left, right));
+ }
+
+ public int OutputSamples(short[] buf, int samplesRequested)
+ {
+ Console.WriteLine("{0} {1}", samplesRequested, _sampleQueue.Count); // add this line
+
+ int bufcursor = 0;
+ int audiosize = samplesRequested;
+ int queued = _sampleQueue.Count;
+
+ // I am too lazy to deal with odd numbers
+ audiosize &= ~1;
+ queued &= ~1;
+
+ if (queued > 0x200 && audiosize > 0) // is there any work to do?
+ {
+ // are we going at normal speed?
+ // or more precisely, are the input and output queues/buffers of similar size?
+ if (queued > 900 || audiosize > queued * 2)
+ {
+ // not normal speed. we have to resample it somehow in this case.
+ if (audiosize <= queued)
+ {
+ // fast forward speed
+ // this is the easy case, just crossfade it and it sounds ok
+ for (int i = 0; i < audiosize; i++)
+ {
+ int j = i + queued - audiosize;
+ Ssamp outsamp = Crossfade(_sampleQueue[i], _sampleQueue[j], i, 0, audiosize);
+ EmitSample(buf, ref bufcursor, outsamp);
+ }
+ }
+ else
+ {
+ // slow motion speed
+ // here we take a very different approach,
+ // instead of crossfading it, we select a single sample from the queue
+ // and make sure that the index we use to select a sample is constantly moving
+ // and that it starts at the first sample in the queue and ends on the last one.
+ //
+ // hopefully the index doesn't move discontinuously or we'll get slight crackling
+ // (there might still be a minor bug here that causes this occasionally)
+ //
+ // here's a diagram of how the index we sample from moves:
+ //
+ // queued (this axis represents the index we sample from. the top means the end of the queue)
+ // ^
+ // | --> audiosize (this axis represents the output index we write to, right meaning forward in output time/position)
+ // | A C C end
+ // A A B C C C
+ // A A A B C C C
+ // A A A B C C
+ // A A C
+ // start
+ //
+ // yes, this means we are spending some stretches of time playing the sound backwards,
+ // but the stretches are short enough that this doesn't sound weird.
+ // this lets us avoid most crackling problems due to the endpoints matching up.
+
+ // first calculate a shorter-than-full window
+ // that has minimal slope at the endpoints
+ // (to further reduce crackling, especially in sine waves)
+ int beststart = 0, extraAtEnd = 0;
+ {
+ int bestend = queued;
+ const int Worstdiff = 99999999;
+ int beststartdiff = Worstdiff;
+ int bestenddiff = Worstdiff;
+ for (int i = 0; i < 128; i += 2)
+ {
+ int diff = Abs(_sampleQueue[i].L - _sampleQueue[i + 1].L) + Abs(_sampleQueue[i].R - _sampleQueue[i + 1].R);
+ if (diff < beststartdiff)
+ {
+ beststartdiff = diff;
+ beststart = i;
+ }
+ }
+
+ for (int i = queued - 3; i > queued - 3 - 128; i -= 2)
+ {
+ int diff = Abs(_sampleQueue[i].L - _sampleQueue[i + 1].L) + Abs(_sampleQueue[i].R - _sampleQueue[i + 1].R);
+ if (diff < bestenddiff)
+ {
+ bestenddiff = diff;
+ bestend = i + 1;
+ }
+ }
+
+ extraAtEnd = queued - bestend;
+ queued = bestend - beststart;
+
+ int oksize = queued;
+ while (oksize + (queued * 2) + beststart + extraAtEnd <= samplesRequested)
+ {
+ oksize += queued * 2;
+ }
+
+ audiosize = oksize;
+
+ for (int x = 0; x < beststart; x++)
+ {
+ EmitSample(buf, ref bufcursor, _sampleQueue[x]);
+ }
+
+ // sampleQueue.erase(sampleQueue.begin(), sampleQueue.begin() + beststart);
+ _sampleQueue.RemoveRange(0, beststart); // zero 08-nov-2010: did i do this right?
+ }
+
+ int midpointX = audiosize >> 1;
+ int midpointY = queued >> 1;
+
+ // all we need to do here is calculate the X position of the leftmost "B" in the above diagram.
+ // TODO: we should calculate it with a simple equation like
+ // midpointXOffset = min(something,somethingElse);
+ // but it's a little difficult to work it out exactly
+ // so here's a stupid search for the value for now:
+ int prevA = 999999;
+ int midpointXOffset = queued / 2;
+ while (true)
+ {
+ int a = Abs(Pingpong(midpointX - midpointXOffset, queued) - midpointY) - midpointXOffset;
+ if (((a > 0) != (prevA > 0) || (a < 0) != (prevA < 0)) && prevA != 999999)
+ {
+ if (((a + prevA) & 1) != 0) // there's some sort of off-by-one problem with this search since we're moving diagonally...
+ {
+ midpointXOffset++; // but this fixes it most of the time...
+ }
+
+ break; // found it
+ }
+
+ prevA = a;
+ midpointXOffset--;
+ if (midpointXOffset < 0)
+ {
+ midpointXOffset = 0;
+ break; // failed to find it. the two sides probably meet exactly in the center.
+ }
+ }
+
+ int leftMidpointX = midpointX - midpointXOffset;
+ int rightMidpointX = midpointX + midpointXOffset;
+ int leftMidpointY = Pingpong(leftMidpointX, queued);
+ int rightMidpointY = (queued - 1) - Pingpong((int)audiosize - 1 - rightMidpointX + (queued * 2), queued);
+
+ // output the left almost-half of the sound (section "A")
+ for (int x = 0; x < leftMidpointX; x++)
+ {
+ int i = Pingpong(x, queued);
+ EmitSample(buf, ref bufcursor, _sampleQueue[i]);
+ }
+
+ // output the middle stretch (section "B")
+ int y = leftMidpointY;
+ int dyMidLeft = (leftMidpointY < midpointY) ? 1 : -1;
+ int dyMidRight = (rightMidpointY > midpointY) ? 1 : -1;
+ for (int x = leftMidpointX; x < midpointX; x++, y += dyMidLeft)
+ {
+ EmitSample(buf, ref bufcursor, _sampleQueue[y]);
+ }
+
+ for (int x = midpointX; x < rightMidpointX; x++, y += dyMidRight)
+ {
+ EmitSample(buf, ref bufcursor, _sampleQueue[y]);
+ }
+
+ // output the end of the queued sound (section "C")
+ for (int x = rightMidpointX; x < audiosize; x++)
+ {
+ int i = (queued - 1) - Pingpong((int)audiosize - 1 - x + (queued * 2), queued);
+ EmitSample(buf, ref bufcursor, _sampleQueue[i]);
+ }
+
+ for (int x = 0; x < extraAtEnd; x++)
+ {
+ int i = queued + x;
+ EmitSample(buf, ref bufcursor, _sampleQueue[i]);
+ }
+
+ queued += extraAtEnd;
+ audiosize += beststart + extraAtEnd;
+ } // end else
+
+ // sampleQueue.erase(sampleQueue.begin(), sampleQueue.begin() + queued);
+ _sampleQueue.RemoveRange(0, queued);
+
+ // zero 08-nov-2010: did i do this right?
+ return audiosize;
+ }
+ else
+ {
+ // normal speed
+ // just output the samples straightforwardly.
+ //
+ // at almost-full speeds (like 50/60 FPS)
+ // what will happen is that we rapidly fluctuate between entering this branch
+ // and entering the "slow motion speed" branch above.
+ // but that's ok! because all of these branches sound similar enough that we can get away with it.
+ // so the two cases actually complement each other.
+ if (audiosize >= queued)
+ {
+ EmitSamples(buf, ref bufcursor, _sampleQueue.ToArray(), 0, queued);
+
+ // sampleQueue.erase(sampleQueue.begin(), sampleQueue.begin() + queued);
+ _sampleQueue.RemoveRange(0, queued);
+
+ // zero 08-nov-2010: did i do this right?
+ return queued;
+ }
+ else
+ {
+ EmitSamples(buf, ref bufcursor, _sampleQueue.ToArray(), 0, audiosize);
+
+ // sampleQueue.erase(sampleQueue.begin(), sampleQueue.begin()+audiosize);
+ _sampleQueue.RemoveRange(0, audiosize);
+
+ // zero 08-nov-2010: did i do this right?
+ return audiosize;
+ }
+ } // end normal speed
+ } // end if there is any work to do
+ else
+ {
+ return 0;
+ }
+ } // output_samples
+ } // NitsujaSynchronizer
+
+ internal 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 fastforwarding, it will discard samples above the maximum excess buffer.
+
+ // When underflowing, it will attempt to resample to a certain threshhold.
+ // If it underflows beyond that threshhold, 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 SamplesInOneFrame = 735;
+ private const int MaxExcessSamples = 2048;
+
+ private readonly Queue _buffer;
+ private readonly Sample[] _resampleBuffer;
+
+ public VecnaSynchronizer()
+ {
+ _buffer = new Queue(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 EnqueueSamples(short[] buf, int samplesProvided)
+ {
+ int ctr = 0;
+ for (int i = 0; i < samplesProvided; i++)
+ {
+ short left = buf[ctr++];
+ short right = buf[ctr++];
+ EnqueueSample(left, right);
+ }
+ }
+
+ 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
+ ////Console.WriteLine("samples in buffer {0}, requested {1}", buffer.Count, samples_requested);
+ 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;
+ }
+ }
+}
diff --git a/BizHawk.Emulation.Common/Sound/Utilities/Metaspu.cs b/BizHawk.Emulation.Common/Sound/Utilities/Metaspu.cs
index 76e8165fa3..5a839d3912 100644
--- a/BizHawk.Emulation.Common/Sound/Utilities/Metaspu.cs
+++ b/BizHawk.Emulation.Common/Sound/Utilities/Metaspu.cs
@@ -1,5 +1,4 @@
using System;
-using System.Collections.Generic;
namespace BizHawk.Emulation.Common
{
@@ -15,7 +14,7 @@ namespace BizHawk.Emulation.Common
public MetaspuAsync(ISoundProvider input, ESynchMethod method)
{
input.SetSyncMode(SyncSoundMode.Sync);
- _buffer = Metaspu.metaspu_construct(method);
+ _buffer = Metaspu.MetaspuConstruct(method);
_input = input;
}
@@ -52,31 +51,17 @@ namespace BizHawk.Emulation.Common
}
}
- public interface ISynchronizingAudioBuffer
- {
- void EnqueueSamples(short[] buf, int samplesProvided);
- 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 ISynchronzingAudioBuffer
- // is to provide exact amounts of output samples,
- // even when the input provided varies....
- int OutputSamples(short[] buf, int samplesRequested);
- }
-
public enum ESynchMethod
{
ESynchMethod_N, // nitsuja's
ESynchMethod_Z, // zero's
- //ESynchMethod_P, //PCSX2 spu2-x //ohno! not available yet in c#
+ ////ESynchMethod_P, //PCSX2 spu2-x //ohno! not available yet in c#
ESynchMethod_V // vecna
}
public static class Metaspu
{
- public static ISynchronizingAudioBuffer metaspu_construct(ESynchMethod method)
+ public static ISynchronizingAudioBuffer MetaspuConstruct(ESynchMethod method)
{
switch (method)
{
@@ -91,629 +76,4 @@ namespace BizHawk.Emulation.Common
}
}
}
-
- internal class ZeromusSynchronizer : ISynchronizingAudioBuffer
- {
- public ZeromusSynchronizer()
- {
- //#ifdef NDEBUG
- adjustobuf = new Adjustobuf(200, 1000);
- //#else
- //adjustobuf = new Adjustobuf(22000, 44000);
- //#endif
-
- }
-
- //adjustobuf(200,1000)
- private bool mixqueue_go;
-
- public void Clear()
- {
- adjustobuf.clear();
- }
-
- public void EnqueueSample(short left, short right)
- {
- adjustobuf.enqueue(left, right);
- }
-
- public void EnqueueSamples(short[] buf, int samples_provided)
- {
- int ctr = 0;
- for (int i = 0; i < samples_provided; i++)
- {
- short left = buf[ctr++];
- short right = buf[ctr++];
- adjustobuf.enqueue(left, right);
- }
- }
-
- // returns the number of samples actually supplied, which may not match the number requested
- public int OutputSamples(short[] buf, int samples_requested)
- {
- int ctr = 0;
- int done = 0;
- if (!mixqueue_go)
- {
- if (adjustobuf.size > 200)
- {
- mixqueue_go = true;
- }
- }
- else
- {
- for (int i = 0; i < samples_requested; i++)
- {
- if (adjustobuf.size == 0)
- {
- mixqueue_go = false;
- break;
- }
-
- done++;
- short left, right;
- adjustobuf.dequeue(out left, out right);
- buf[ctr++] = left;
- buf[ctr++] = right;
- }
- }
-
- return done;
- }
-
- private readonly Adjustobuf adjustobuf;
-
- private class Adjustobuf
- {
- public Adjustobuf(int _minLatency, int _maxLatency)
- {
- minLatency = _minLatency;
- maxLatency = _maxLatency;
- clear();
- }
-
- private float rate, cursor;
- private int minLatency, targetLatency, maxLatency;
- private readonly Queue buffer = new Queue();
- private readonly Queue statsHistory = new Queue();
- public int size = 0;
- private readonly short[] curr = new short[2];
-
- public void clear()
- {
- buffer.Clear();
- statsHistory.Clear();
- rollingTotalSize = 0;
- targetLatency = (maxLatency + minLatency) / 2;
- rate = 1.0f;
- cursor = 0.0f;
- curr[0] = curr[1] = 0;
- kAverageSize = 80000;
- size = 0;
- }
-
- public void enqueue(short left, short right)
- {
- buffer.Enqueue(left);
- buffer.Enqueue(right);
- size++;
- }
-
- private long rollingTotalSize;
-
- private uint kAverageSize;
-
- private void addStatistic()
- {
- statsHistory.Enqueue(size);
- rollingTotalSize += size;
- if (statsHistory.Count > kAverageSize)
- {
- rollingTotalSize -= statsHistory.Peek();
- statsHistory.Dequeue();
-
- float averageSize = (float)(rollingTotalSize / kAverageSize);
- //static int ctr=0; ctr++; if((ctr&127)==0) printf("avg size: %f curr size: %d rate: %f\n",averageSize,size,rate);
- {
- float targetRate;
- if (averageSize < targetLatency)
- {
- targetRate = 1.0f - (targetLatency - averageSize) / kAverageSize;
- }
- else if (averageSize > targetLatency)
- {
- targetRate = 1.0f + (averageSize - targetLatency) / kAverageSize;
- }
- else targetRate = 1.0f;
-
- //rate = moveValueTowards(rate,targetRate,0.001f);
- rate = targetRate;
- }
-
- }
- }
-
- public void dequeue(out short left, out short right)
- {
- left = right = 0;
- addStatistic();
- if (size == 0)
- {
- return;
- }
-
- cursor += rate;
- while (cursor > 1.0f)
- {
- cursor -= 1.0f;
- if (size > 0)
- {
- curr[0] = buffer.Dequeue();
- curr[1] = buffer.Dequeue();
- size--;
- }
- }
-
- left = curr[0];
- right = curr[1];
- }
- }
- }
-
- internal class NitsujaSynchronizer : ISynchronizingAudioBuffer
- {
- private struct ssamp
- {
- public short l, r;
-
- public ssamp(short ll, short rr)
- {
- l = ll; r = rr;
- }
- }
-
- private readonly List sampleQueue = new List();
-
- // returns values going between 0 and y-1 in a saw wave pattern, based on x
- private static int pingpong(int x, int y)
- {
- x %= 2 * y;
- if (x >= y)
- {
- x = (2 * y) - x - 1;
- }
-
- return x;
-
- // in case we want to switch to odd buffer sizes for more sharpness
- //x %= 2*(y-1);
- //if(x >= y)
- // x = 2*(y-1) - x;
- //return x;
- }
-
- private static ssamp crossfade(ssamp lhs, ssamp rhs, int cur, int start, int end)
- {
- if (cur <= start)
- {
- return lhs;
- }
-
- if (cur >= end)
- {
- return rhs;
- }
-
- // in case we want sine wave interpolation instead of linear here
- //float ang = 3.14159f * (float)(cur - start) / (float)(end - start);
- //cur = start + (int)((1-cosf(ang))*0.5f * (end - start));
-
- int inNum = cur - start;
- int outNum = end - cur;
- int denom = end - start;
-
- int lrv = ((int)lhs.l * outNum + (int)rhs.l * inNum) / denom;
- int rrv = ((int)lhs.r * outNum + (int)rhs.r * inNum) / denom;
-
- return new ssamp((short)lrv, (short)rrv);
- }
-
- public void Clear()
- {
- sampleQueue.Clear();
- }
-
- private static void emit_sample(short[] outbuf, ref int cursor, ssamp sample)
- {
- outbuf[cursor++] = sample.l;
- outbuf[cursor++] = sample.r;
- }
-
- private static void emit_samples(short[] outbuf, ref int outcursor, ssamp[] samplebuf, int incursor, int samples)
- {
- for (int i = 0; i < samples; i++)
- {
- emit_sample(outbuf, ref outcursor, samplebuf[i + incursor]);
- }
- }
-
- private static short abs(short value)
- {
- if (value < 0)
- {
- return (short)-value;
- }
-
- return value;
- }
-
- private static int abs(int value)
- {
- if (value < 0)
- {
- return -value;
- }
-
- return value;
- }
-
- public void EnqueueSamples(short[] buf, int samples_provided)
- {
- int cursor = 0;
- for (int i = 0; i < samples_provided; i++)
- {
- sampleQueue.Add(new ssamp(buf[cursor + 0], buf[cursor + 1]));
- cursor += 2;
- }
- }
-
- public void EnqueueSample(short left, short right)
- {
- sampleQueue.Add(new ssamp(left, right));
- }
-
- public int OutputSamples(short[] buf, int samples_requested)
- {
- Console.WriteLine("{0} {1}", samples_requested, sampleQueue.Count); //add this line
-
-
- int bufcursor = 0;
- int audiosize = samples_requested;
- int queued = sampleQueue.Count;
-
- // I am too lazy to deal with odd numbers
- audiosize &= ~1;
- queued &= ~1;
-
- if (queued > 0x200 && audiosize > 0) // is there any work to do?
- {
- // are we going at normal speed?
- // or more precisely, are the input and output queues/buffers of similar size?
- if (queued > 900 || audiosize > queued * 2)
- {
- // not normal speed. we have to resample it somehow in this case.
- if (audiosize <= queued)
- {
- // fast forward speed
- // this is the easy case, just crossfade it and it sounds ok
- for (int i = 0; i < audiosize; i++)
- {
- int j = i + queued - audiosize;
- ssamp outsamp = crossfade(sampleQueue[i], sampleQueue[j], i, 0, audiosize);
- emit_sample(buf, ref bufcursor, outsamp);
- }
- }
- else
- {
- // slow motion speed
- // here we take a very different approach,
- // instead of crossfading it, we select a single sample from the queue
- // and make sure that the index we use to select a sample is constantly moving
- // and that it starts at the first sample in the queue and ends on the last one.
- //
- // hopefully the index doesn't move discontinuously or we'll get slight crackling
- // (there might still be a minor bug here that causes this occasionally)
- //
- // here's a diagram of how the index we sample from moves:
- //
- // queued (this axis represents the index we sample from. the top means the end of the queue)
- // ^
- // | --> audiosize (this axis represents the output index we write to, right meaning forward in output time/position)
- // | A C C end
- // A A B C C C
- // A A A B C C C
- // A A A B C C
- // A A C
- // start
- //
- // yes, this means we are spending some stretches of time playing the sound backwards,
- // but the stretches are short enough that this doesn't sound weird.
- // this lets us avoid most crackling problems due to the endpoints matching up.
-
- // first calculate a shorter-than-full window
- // that has minimal slope at the endpoints
- // (to further reduce crackling, especially in sine waves)
- int beststart = 0, extraAtEnd = 0;
- {
- int bestend = queued;
- const int worstdiff = 99999999;
- int beststartdiff = worstdiff;
- int bestenddiff = worstdiff;
- for (int i = 0; i < 128; i += 2)
- {
- int diff = abs(sampleQueue[i].l - sampleQueue[i + 1].l) + abs(sampleQueue[i].r - sampleQueue[i + 1].r);
- if (diff < beststartdiff)
- {
- beststartdiff = diff;
- beststart = i;
- }
- }
-
- for (int i = queued - 3; i > queued - 3 - 128; i -= 2)
- {
- int diff = abs(sampleQueue[i].l - sampleQueue[i + 1].l) + abs(sampleQueue[i].r - sampleQueue[i + 1].r);
- if (diff < bestenddiff)
- {
- bestenddiff = diff;
- bestend = i + 1;
- }
- }
-
- extraAtEnd = queued - bestend;
- queued = bestend - beststart;
-
- int oksize = queued;
- while (oksize + queued * 2 + beststart + extraAtEnd <= samples_requested)
- {
- oksize += queued * 2;
- }
-
- audiosize = oksize;
-
- for (int x = 0; x < beststart; x++)
- {
- emit_sample(buf, ref bufcursor, sampleQueue[x]);
- }
-
- // sampleQueue.erase(sampleQueue.begin(), sampleQueue.begin() + beststart);
- sampleQueue.RemoveRange(0, beststart);
- // zero 08-nov-2010: did i do this right?
- }
-
-
- int midpointX = audiosize >> 1;
- int midpointY = queued >> 1;
-
- // all we need to do here is calculate the X position of the leftmost "B" in the above diagram.
- // TODO: we should calculate it with a simple equation like
- // midpointXOffset = min(something,somethingElse);
- // but it's a little difficult to work it out exactly
- // so here's a stupid search for the value for now:
- int prevA = 999999;
- int midpointXOffset = queued / 2;
- while (true)
- {
- int a = abs(pingpong(midpointX - midpointXOffset, queued) - midpointY) - midpointXOffset;
- if (((a > 0) != (prevA > 0) || (a < 0) != (prevA < 0)) && prevA != 999999)
- {
- if (((a + prevA) & 1) != 0) // there's some sort of off-by-one problem with this search since we're moving diagonally...
- midpointXOffset++; // but this fixes it most of the time...
- break; // found it
- }
- prevA = a;
- midpointXOffset--;
- if (midpointXOffset < 0)
- {
- midpointXOffset = 0;
- break; // failed to find it. the two sides probably meet exactly in the center.
- }
- }
-
- int leftMidpointX = midpointX - midpointXOffset;
- int rightMidpointX = midpointX + midpointXOffset;
- int leftMidpointY = pingpong(leftMidpointX, queued);
- int rightMidpointY = (queued - 1) - pingpong((int)audiosize - 1 - rightMidpointX + queued * 2, queued);
-
- // output the left almost-half of the sound (section "A")
- for (int x = 0; x < leftMidpointX; x++)
- {
- int i = pingpong(x, queued);
- emit_sample(buf, ref bufcursor, sampleQueue[i]);
- }
-
- // output the middle stretch (section "B")
- int y = leftMidpointY;
- int dyMidLeft = (leftMidpointY < midpointY) ? 1 : -1;
- int dyMidRight = (rightMidpointY > midpointY) ? 1 : -1;
- for (int x = leftMidpointX; x < midpointX; x++, y += dyMidLeft)
- {
- emit_sample(buf, ref bufcursor, sampleQueue[y]);
- }
-
- for (int x = midpointX; x < rightMidpointX; x++, y += dyMidRight)
- {
- emit_sample(buf, ref bufcursor, sampleQueue[y]);
- }
-
- // output the end of the queued sound (section "C")
- for (int x = rightMidpointX; x < audiosize; x++)
- {
- int i = (queued - 1) - pingpong((int)audiosize - 1 - x + queued * 2, queued);
- emit_sample(buf, ref bufcursor, sampleQueue[i]);
- }
-
- for (int x = 0; x < extraAtEnd; x++)
- {
- int i = queued + x;
- emit_sample(buf, ref bufcursor, sampleQueue[i]);
- }
-
- queued += extraAtEnd;
- audiosize += beststart + extraAtEnd;
- } // end else
-
- // sampleQueue.erase(sampleQueue.begin(), sampleQueue.begin() + queued);
- sampleQueue.RemoveRange(0, queued);
-
- // zero 08-nov-2010: did i do this right?
- return audiosize;
- }
- else
- {
- // normal speed
- // just output the samples straightforwardly.
- //
- // at almost-full speeds (like 50/60 FPS)
- // what will happen is that we rapidly fluctuate between entering this branch
- // and entering the "slow motion speed" branch above.
- // but that's ok! because all of these branches sound similar enough that we can get away with it.
- // so the two cases actually complement each other.
- if (audiosize >= queued)
- {
- emit_samples(buf, ref bufcursor, sampleQueue.ToArray(), 0, queued);
-
- // sampleQueue.erase(sampleQueue.begin(), sampleQueue.begin() + queued);
- sampleQueue.RemoveRange(0, queued);
-
- // zero 08-nov-2010: did i do this right?
- return queued;
- }
- else
- {
- emit_samples(buf, ref bufcursor, sampleQueue.ToArray(), 0, audiosize);
-
- // sampleQueue.erase(sampleQueue.begin(), sampleQueue.begin()+audiosize);
- sampleQueue.RemoveRange(0, audiosize);
-
- // zero 08-nov-2010: did i do this right?
- return audiosize;
- }
- } // end normal speed
- } // end if there is any work to do
- else
- {
- return 0;
- }
- } // output_samples
- } // NitsujaSynchronizer
-
- internal 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 fastforwarding, it will discard samples above the maximum excess buffer.
-
- // When underflowing, it will attempt to resample to a certain threshhold.
- // If it underflows beyond that threshhold, 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 readonly Queue buffer;
- private readonly Sample[] resampleBuffer;
-
- private const int SamplesInOneFrame = 735;
- private const int MaxExcessSamples = 2048;
-
- public VecnaSynchronizer()
- {
- buffer = new Queue(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 EnqueueSamples(short[] buf, int samples_provided)
- {
- int ctr = 0;
- for (int i = 0; i < samples_provided; i++)
- {
- short left = buf[ctr++];
- short right = buf[ctr++];
- EnqueueSample(left, right);
- }
- }
-
- 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 samples_requested)
- {
- if (samples_requested > buffer.Count)
- {
- // underflow!
- if (buffer.Count > samples_requested * 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 samples_available = buffer.Count;
- for (int i = 0; buffer.Count > 0; i++)
- resampleBuffer[i] = buffer.Dequeue();
-
- int index = 0;
- for (int i = 0; i < samples_requested; i++)
- {
- Sample sample = resampleBuffer[i * samples_available / samples_requested];
- 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
- //Console.WriteLine("samples in buffer {0}, requested {1}", buffer.Count, samples_requested);
- int index = 0;
- for (int i = 0; i < samples_requested && buffer.Count > 0; i++)
- {
- Sample sample = buffer.Dequeue();
- buf[index++] += sample.Left;
- buf[index++] += sample.Right;
- }
- }
-
- return samples_requested;
- }
- }
}
\ No newline at end of file
diff --git a/BizHawk.Emulation.Cores/Sound/IAsyncSoundProvider.cs b/BizHawk.Emulation.Cores/Sound/IAsyncSoundProvider.cs
index 198ce80457..817df23700 100644
--- a/BizHawk.Emulation.Cores/Sound/IAsyncSoundProvider.cs
+++ b/BizHawk.Emulation.Cores/Sound/IAsyncSoundProvider.cs
@@ -11,28 +11,29 @@ namespace BizHawk.Emulation.Cores.Components
{
void GetSamples(short[] samples);
void DiscardSamples();
- }
-
+ }
+
///
/// TODO: this is a shim for now, and needs to go away
- /// turns an IAsyncSoundPRovider into a full ISoundProvider
+ /// turns an into a full ISoundProvider
/// This is used in cores that have an async only sound implementation
/// better is implement a sync sound option in those cores without the need for
- /// this class or an IAsyncSoundPRovider interface
+ /// this class or an IAsyncSoundProvider interface
///
internal class FakeSyncSound : ISoundProvider
{
- private readonly IAsyncSoundProvider source;
- private readonly int spf;
+ private readonly IAsyncSoundProvider _source;
+ private readonly int _spf;
+
///
- ///
+ /// Initializes a new instance of the class.
///
- ///
+ /// The async sound provider
/// number of sample pairs to request and provide on each GetSamples() call
public FakeSyncSound(IAsyncSoundProvider source, int spf)
{
- this.source = source;
- this.spf = spf;
+ _source = source;
+ _spf = spf;
SyncMode = SyncSoundMode.Sync;
}
@@ -43,15 +44,15 @@ namespace BizHawk.Emulation.Cores.Components
throw new InvalidOperationException("Must be in sync mode to call a sync method");
}
- short[] ret = new short[spf * 2];
- source.GetSamples(ret);
+ short[] ret = new short[_spf * 2];
+ _source.GetSamples(ret);
samples = ret;
- nsamp = spf;
+ nsamp = _spf;
}
public void DiscardSamples()
{
- source.DiscardSamples();
+ _source.DiscardSamples();
}
public void GetSamplesAsync(short[] samples)
@@ -61,13 +62,10 @@ namespace BizHawk.Emulation.Cores.Components
throw new InvalidOperationException("Must be in async mode to call an async method");
}
- source.GetSamples(samples);
+ _source.GetSamples(samples);
}
- public bool CanProvideAsync
- {
- get { return true; }
- }
+ public bool CanProvideAsync => true;
public SyncSoundMode SyncMode { get; private set; }
@@ -81,14 +79,15 @@ namespace BizHawk.Emulation.Cores.Components
// This class needs to go away, it takes an IAsyncSoundProvider
// and is only used by legacy sound implementations
internal class MetaspuSoundProvider : ISoundProvider
- {
+ {
+ private readonly short[] _pullBuffer = new short[1470];
+
public MetaspuSoundProvider(ESynchMethod method)
{
- Buffer = Metaspu.metaspu_construct(method);
+ Buffer = Metaspu.MetaspuConstruct(method);
}
- public ISynchronizingAudioBuffer Buffer { get; set; }
- private readonly short[] pullBuffer = new short[1470];
+ public ISynchronizingAudioBuffer Buffer { get; }
public MetaspuSoundProvider()
: this(ESynchMethod.ESynchMethod_V)
@@ -97,20 +96,14 @@ namespace BizHawk.Emulation.Cores.Components
public void PullSamples(IAsyncSoundProvider source)
{
- Array.Clear(pullBuffer, 0, 1470);
- source.GetSamples(pullBuffer);
- Buffer.EnqueueSamples(pullBuffer, 735);
+ Array.Clear(_pullBuffer, 0, 1470);
+ source.GetSamples(_pullBuffer);
+ Buffer.EnqueueSamples(_pullBuffer, 735);
}
- public bool CanProvideAsync
- {
- get { return true; }
- }
+ public bool CanProvideAsync => true;
- public SyncSoundMode SyncMode
- {
- get { return SyncSoundMode.Async; }
- }
+ public SyncSoundMode SyncMode => SyncSoundMode.Async;
public void SetSyncMode(SyncSoundMode mode)
{