2011-01-11 02:55:51 +00:00
|
|
|
|
using System;
|
|
|
|
|
using System.Collections.Generic;
|
|
|
|
|
|
|
|
|
|
namespace BizHawk.Emulation.Sound
|
|
|
|
|
{
|
sound api changes. added a new ISyncSoundProvider, which works similarly to ISoundProvider except the source (not the sink) determines the number of samples to process. Added facilities to metaspu, dcfilter, speexresampler to work with ISyncSoundProvider. Add ISyncSoundProvider to IEmulator. All IEmulators must provide sync sound, but they need not provide async sound. When async is needed and an IEmulator doesn't provide it, the frontend will wrap it in a vecna metaspu. SNES, GB changed to provide sync sound only. All other emulator cores mostly unchanged; they just provide stub fakesync alongside async, for now. For the moment, the only use of the sync sound is for realtime audio throttling, where it works and sounds quite nice. In the future, sync sound will be supported for AV dumping as well.
2012-10-11 00:44:59 +00:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// uses Metaspu to have an ISyncSoundProvider input to a ISoundProvider
|
|
|
|
|
/// </summary>
|
|
|
|
|
public class MetaspuAsync : ISoundProvider
|
|
|
|
|
{
|
|
|
|
|
ISynchronizingAudioBuffer buffer;
|
|
|
|
|
ISyncSoundProvider input;
|
|
|
|
|
public MetaspuAsync(ISyncSoundProvider input, ESynchMethod method)
|
|
|
|
|
{
|
|
|
|
|
buffer = Metaspu.metaspu_construct(method);
|
|
|
|
|
this.input = input;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void GetSamples(short[] samples)
|
|
|
|
|
{
|
|
|
|
|
short[] sampin;
|
|
|
|
|
int numsamp;
|
|
|
|
|
input.GetSamples(out sampin, out numsamp);
|
|
|
|
|
buffer.enqueue_samples(sampin, numsamp);
|
|
|
|
|
buffer.output_samples(samples, samples.Length / 2);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void DiscardSamples()
|
|
|
|
|
{
|
|
|
|
|
input.DiscardSamples();
|
|
|
|
|
buffer.clear();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public int MaxVolume { get; set; }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2011-01-11 02:55:51 +00:00
|
|
|
|
public class MetaspuSoundProvider : ISoundProvider
|
|
|
|
|
{
|
2011-03-13 08:13:32 +00:00
|
|
|
|
public ISynchronizingAudioBuffer buffer;
|
|
|
|
|
public MetaspuSoundProvider(ESynchMethod method)
|
|
|
|
|
{
|
|
|
|
|
buffer = Metaspu.metaspu_construct(method);
|
|
|
|
|
}
|
2011-08-14 18:20:13 +00:00
|
|
|
|
|
|
|
|
|
public MetaspuSoundProvider() : this(ESynchMethod.ESynchMethod_V)
|
2011-01-11 02:55:51 +00:00
|
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
|
2011-09-05 17:45:01 +00:00
|
|
|
|
short[] pullBuffer = new short[1470];
|
2011-08-14 18:20:13 +00:00
|
|
|
|
public void PullSamples(ISoundProvider source)
|
|
|
|
|
{
|
|
|
|
|
Array.Clear(pullBuffer, 0, 1470);
|
|
|
|
|
source.GetSamples(pullBuffer);
|
|
|
|
|
buffer.enqueue_samples(pullBuffer, 735);
|
|
|
|
|
}
|
|
|
|
|
|
2011-01-11 02:55:51 +00:00
|
|
|
|
public void GetSamples(short[] samples)
|
|
|
|
|
{
|
|
|
|
|
buffer.output_samples(samples, samples.Length / 2);
|
|
|
|
|
}
|
2011-08-14 18:20:13 +00:00
|
|
|
|
|
2011-03-19 09:12:56 +00:00
|
|
|
|
public void DiscardSamples()
|
|
|
|
|
{
|
|
|
|
|
buffer.clear();
|
|
|
|
|
}
|
2011-09-04 04:38:11 +00:00
|
|
|
|
|
|
|
|
|
public int MaxVolume { get; set; }
|
|
|
|
|
}
|
2011-01-11 02:55:51 +00:00
|
|
|
|
|
|
|
|
|
public interface ISynchronizingAudioBuffer
|
|
|
|
|
{
|
|
|
|
|
void enqueue_samples(short[] buf, int samples_provided);
|
2011-03-13 08:13:32 +00:00
|
|
|
|
void enqueue_sample(short left, short right);
|
2011-03-19 09:12:56 +00:00
|
|
|
|
void clear();
|
2011-01-11 02:55:51 +00:00
|
|
|
|
|
|
|
|
|
//returns the number of samples actually supplied, which may not match the number requested
|
2012-09-07 20:12:47 +00:00
|
|
|
|
// ^^ 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....
|
2011-01-11 02:55:51 +00:00
|
|
|
|
int output_samples(short[] buf, int samples_requested);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
public enum ESynchMethod
|
|
|
|
|
{
|
|
|
|
|
ESynchMethod_N, //nitsuja's
|
|
|
|
|
ESynchMethod_Z, //zero's
|
|
|
|
|
//ESynchMethod_P, //PCSX2 spu2-x //ohno! not available yet in c#
|
2011-08-14 01:42:54 +00:00
|
|
|
|
ESynchMethod_V // vecna
|
2011-01-11 02:55:51 +00:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
public static class Metaspu
|
|
|
|
|
{
|
|
|
|
|
public static ISynchronizingAudioBuffer metaspu_construct(ESynchMethod method)
|
|
|
|
|
{
|
|
|
|
|
switch (method)
|
|
|
|
|
{
|
|
|
|
|
case ESynchMethod.ESynchMethod_Z:
|
|
|
|
|
return new ZeromusSynchronizer();
|
|
|
|
|
case ESynchMethod.ESynchMethod_N:
|
|
|
|
|
return new NitsujaSynchronizer();
|
2011-08-14 01:42:54 +00:00
|
|
|
|
case ESynchMethod.ESynchMethod_V:
|
|
|
|
|
return new VecnaSynchronizer();
|
2011-01-11 02:55:51 +00:00
|
|
|
|
default:
|
|
|
|
|
return new NitsujaSynchronizer();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class ZeromusSynchronizer : ISynchronizingAudioBuffer
|
|
|
|
|
{
|
|
|
|
|
public ZeromusSynchronizer()
|
|
|
|
|
{
|
|
|
|
|
//#ifdef NDEBUG
|
|
|
|
|
adjustobuf = new Adjustobuf(200, 1000);
|
|
|
|
|
//#else
|
|
|
|
|
//adjustobuf = new Adjustobuf(22000, 44000);
|
|
|
|
|
//#endif
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//adjustobuf(200,1000)
|
|
|
|
|
bool mixqueue_go = false;
|
|
|
|
|
|
2011-03-19 09:12:56 +00:00
|
|
|
|
public void clear()
|
|
|
|
|
{
|
|
|
|
|
adjustobuf.clear();
|
|
|
|
|
}
|
|
|
|
|
|
2011-03-13 08:13:32 +00:00
|
|
|
|
public void enqueue_sample(short left, short right)
|
|
|
|
|
{
|
|
|
|
|
adjustobuf.enqueue(left, right);
|
|
|
|
|
}
|
|
|
|
|
|
2011-01-11 02:55:51 +00:00
|
|
|
|
public void enqueue_samples(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 output_samples(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;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Adjustobuf adjustobuf;
|
|
|
|
|
class Adjustobuf
|
|
|
|
|
{
|
|
|
|
|
public Adjustobuf(int _minLatency, int _maxLatency)
|
|
|
|
|
{
|
|
|
|
|
minLatency = _minLatency;
|
|
|
|
|
maxLatency = _maxLatency;
|
2011-03-19 09:12:56 +00:00
|
|
|
|
clear();
|
2011-01-11 02:55:51 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
float rate, cursor;
|
|
|
|
|
int minLatency, targetLatency, maxLatency;
|
|
|
|
|
Queue<short> buffer = new Queue<short>();
|
|
|
|
|
Queue<int> statsHistory = new Queue<int>();
|
|
|
|
|
public int size = 0;
|
|
|
|
|
short[] curr = new short[2];
|
|
|
|
|
|
2011-03-19 09:12:56 +00:00
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
2011-01-11 02:55:51 +00:00
|
|
|
|
public void enqueue(short left, short right)
|
|
|
|
|
{
|
|
|
|
|
buffer.Enqueue(left);
|
|
|
|
|
buffer.Enqueue(right);
|
|
|
|
|
size++;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
long rollingTotalSize;
|
|
|
|
|
|
|
|
|
|
uint kAverageSize;
|
|
|
|
|
|
|
|
|
|
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];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
class NitsujaSynchronizer : ISynchronizingAudioBuffer
|
|
|
|
|
{
|
|
|
|
|
struct ssamp
|
|
|
|
|
{
|
|
|
|
|
public short l, r;
|
|
|
|
|
public ssamp(short ll, short rr) { l = ll; r = rr; }
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
List<ssamp> sampleQueue = new List<ssamp>();
|
|
|
|
|
|
|
|
|
|
// returns values going between 0 and y-1 in a saw wave pattern, based on x
|
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
|
2011-03-19 09:12:56 +00:00
|
|
|
|
public void clear()
|
|
|
|
|
{
|
|
|
|
|
sampleQueue.Clear();
|
|
|
|
|
}
|
|
|
|
|
|
2011-01-11 02:55:51 +00:00
|
|
|
|
static void emit_sample(short[] outbuf, ref int cursor, ssamp sample)
|
|
|
|
|
{
|
|
|
|
|
outbuf[cursor++] = sample.l;
|
|
|
|
|
outbuf[cursor++] = sample.r;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static short abs(short value)
|
|
|
|
|
{
|
|
|
|
|
if (value < 0) return (short)-value;
|
|
|
|
|
else return value;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static int abs(int value)
|
|
|
|
|
{
|
|
|
|
|
if (value < 0) return -value;
|
|
|
|
|
else return value;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void enqueue_samples(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;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2011-03-13 08:13:32 +00:00
|
|
|
|
public void enqueue_sample(short left, short right)
|
|
|
|
|
{
|
|
|
|
|
sampleQueue.Add(new ssamp(left,right));
|
|
|
|
|
}
|
|
|
|
|
|
2011-01-11 02:55:51 +00:00
|
|
|
|
public int output_samples(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
|
|
|
|
|
|
2011-08-14 01:42:54 +00:00
|
|
|
|
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.
|
|
|
|
|
|
|
|
|
|
struct Sample
|
|
|
|
|
{
|
|
|
|
|
public short left, right;
|
|
|
|
|
public Sample(short l, short r)
|
|
|
|
|
{
|
|
|
|
|
left = l;
|
|
|
|
|
right = r;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2011-09-03 20:32:18 +00:00
|
|
|
|
Queue<Sample> buffer;
|
|
|
|
|
Sample[] resampleBuffer;
|
2011-08-14 01:42:54 +00:00
|
|
|
|
|
2011-09-03 20:32:18 +00:00
|
|
|
|
const int SamplesInOneFrame = 735;
|
|
|
|
|
const int MaxExcessSamples = 2048;
|
2011-08-14 01:42:54 +00:00
|
|
|
|
|
|
|
|
|
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 enqueue_samples(short[] buf, int samples_provided)
|
|
|
|
|
{
|
2011-08-14 18:20:13 +00:00
|
|
|
|
int ctr = 0;
|
|
|
|
|
for (int i = 0; i < samples_provided; i++)
|
|
|
|
|
{
|
|
|
|
|
short left = buf[ctr++];
|
|
|
|
|
short right = buf[ctr++];
|
|
|
|
|
enqueue_sample(left, right);
|
|
|
|
|
}
|
2011-08-14 01:42:54 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void enqueue_sample(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 output_samples(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];
|
2011-09-03 20:32:18 +00:00
|
|
|
|
buf[index++] += sample.left;
|
|
|
|
|
buf[index++] += sample.right;
|
2011-08-14 01:42:54 +00:00
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
// we're outside of a "reasonable" underflow. Give up and output silence.
|
2011-08-14 18:20:13 +00:00
|
|
|
|
// Do nothing. The whole frame will be excess buffer.
|
2011-08-14 01:42:54 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
// normal operation
|
2011-08-14 18:20:13 +00:00
|
|
|
|
//Console.WriteLine("samples in buffer {0}, requested {1}", buffer.Count, samples_requested);
|
2011-08-14 01:42:54 +00:00
|
|
|
|
int index = 0;
|
|
|
|
|
for (int i = 0; i < samples_requested && buffer.Count > 0; i++)
|
|
|
|
|
{
|
|
|
|
|
Sample sample = buffer.Dequeue();
|
2011-09-03 20:32:18 +00:00
|
|
|
|
buf[index++] += sample.left;
|
|
|
|
|
buf[index++] += sample.right;
|
2011-08-14 01:42:54 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return samples_requested;
|
|
|
|
|
}
|
|
|
|
|
}
|
2011-01-11 02:55:51 +00:00
|
|
|
|
}
|