();
GCHandle _gc_snes_audio_sample;
+
+ /// total number of samples (left and right combined) in the InBuffer before we ask for resampling
+ const int resamplechunk = 1000;
+ /// actual sampling factor used
+ const double sfactor = 44100.0 / 32040.5;
+
+ Sound.Utilities.BizhawkResampler resampler = new Sound.Utilities.BizhawkResampler(false, sfactor, sfactor);
+
+ Sound.MetaspuSoundProvider metaspu = new Sound.MetaspuSoundProvider(Sound.ESynchMethod.ESynchMethod_Z);
+
void snes_audio_sample(ushort left, ushort right)
{
- AudioBuffer.Enqueue((short)left);
- AudioBuffer.Enqueue((short)right);
- }
+ AudioInBuffer.Enqueue((short)left);
+ AudioInBuffer.Enqueue((short)right);
-
- ///
- /// basic linear audio resampler. sampling rate is inferred from buffer sizes
- ///
- /// stereo s16
- /// stereo s16
- static void LinearDownsampler(short[] input, short[] output)
- {
- // TODO - this also appears in YM2612.cs ... move to common if it's found useful
-
- double samplefactor = (input.Length - 2) / (double)output.Length;
-
- for (int i = 0; i < output.Length / 2; i++)
+ try
{
- // exact position on input stream
- double inpos = i * samplefactor;
- // selected interpolation points and weights
- int pt0 = (int)inpos; // pt1 = pt0 + 1
- double wt1 = inpos - pt0; // wt0 = 1 - wt1
- double wt0 = 1.0 - wt1;
+ // fixme: i get all sorts of crashes if i do the resampling in here. what?
- output[i * 2 + 0] = (short)(input[pt0 * 2 + 0] * wt0 + input[pt0 * 2 + 2] * wt1);
- output[i * 2 + 1] = (short)(input[pt0 * 2 + 1] * wt0 + input[pt0 * 2 + 3] * wt1);
+ /*
+ if (AudioInBuffer.Count >= resamplechunk)
+ {
+ resampler.process(sfactor, false, AudioInBuffer, AudioOutBuffer);
+ // drain into the metaspu immediately
+ // we could skip this step and drain directly by changing SampleBuffers
+ while (AudioOutBuffer.Count > 0)
+ metaspu.buffer.enqueue_sample(AudioOutBuffer.Dequeue(), AudioOutBuffer.Dequeue());
+ }
+ */
}
+ catch (Exception e)
+ {
+ System.Windows.Forms.MessageBox.Show(e.ToString());
+ AudioOutBuffer.Clear();
+ }
+
}
- // TODO: replace this with something that took more than 5 seconds to write
+
+ //BinaryWriter dbgs = new BinaryWriter(File.Open("dbgwav.raw", FileMode.Create, FileAccess.Write));
+
public void GetSamples(short[] samples)
{
- // resample approximately 32k->44k
- int inputcount = samples.Length * 32040 / 44100;
- inputcount /= 2;
+ // do resampling here, instead of when the samples are generated; see "fixme" comment in snes_audio_sample()
- if (inputcount < 2) inputcount = 2;
-
- short[] input = new short[inputcount * 2];
-
- int i;
-
- for (i = 0; i < inputcount * 2 && AudioBuffer.Count > 0; i++)
- input[i] = AudioBuffer.Dequeue();
- short lastl, lastr;
- if (i >= 2)
+ if (true)
{
- lastl = input[i - 2];
- lastr = input[i - 1];
+ resampler.process(sfactor, false, AudioInBuffer, AudioOutBuffer);
+
+ // drain into the metaspu immediately
+ // we could skip this step and drain directly by changing SampleBuffers implementation
+ while (AudioOutBuffer.Count > 0)
+ {
+ short l = AudioOutBuffer.Dequeue();
+ short r = AudioOutBuffer.Dequeue();
+ //metaspu.buffer.enqueue_sample(AudioOutBuffer.Dequeue(), AudioOutBuffer.Dequeue());
+ metaspu.buffer.enqueue_sample(l, r);
+ //dbgs.Write(l);
+ //dbgs.Write(r);
+ }
}
- else
- lastl = lastr = 0;
- for (; i < inputcount * 2; )
- {
- input[i++] = lastl;
- input[i++] = lastr;
- }
-
-
- LinearDownsampler(input, samples);
-
- // drop if too many
- if (AudioBuffer.Count > samples.Length * 3)
- AudioBuffer.Clear();
+ metaspu.GetSamples(samples);
}
public void DiscardSamples()
{
- AudioBuffer.Clear();
+ metaspu.DiscardSamples();
}
- // ignore for now
- public int MaxVolume
- {
- get;
- set;
- }
+ public int MaxVolume { get; set; }
+
+ #endregion audio stuff
+
}
}
\ No newline at end of file
diff --git a/BizHawk.Emulation/Sound/Utilities/FilterKit.cs b/BizHawk.Emulation/Sound/Utilities/FilterKit.cs
new file mode 100644
index 0000000000..5a696caedc
--- /dev/null
+++ b/BizHawk.Emulation/Sound/Utilities/FilterKit.cs
@@ -0,0 +1,263 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace BizHawk.Emulation.Sound.Utilities
+{
+ /******************************************************************************
+ *
+ * libresample4j
+ * Copyright (c) 2009 Laszlo Systems, Inc. All Rights Reserved.
+ *
+ * libresample4j is a Java port of Dominic Mazzoni's libresample 0.1.3,
+ * which is in turn based on Julius Smith's Resample 1.7 library.
+ * http://www-ccrma.stanford.edu/~jos/resample/
+ *
+ * License: LGPL -- see the file LICENSE.txt for more information
+ *
+ *****************************************************************************/
+
+ /**
+ * This file provides Kaiser-windowed low-pass filter support,
+ * including a function to create the filter coefficients, and
+ * two functions to apply the filter at a particular point.
+ *
+ *
+ * reference: "Digital Filters, 2nd edition"
+ * R.W. Hamming, pp. 178-179
+ *
+ * Izero() computes the 0th order modified bessel function of the first kind.
+ * (Needed to compute Kaiser window).
+ *
+ * LpFilter() computes the coeffs of a Kaiser-windowed low pass filter with
+ * the following characteristics:
+ *
+ * c[] = array in which to store computed coeffs
+ * frq = roll-off frequency of filter
+ * N = Half the window length in number of coeffs
+ * Beta = parameter of Kaiser window
+ * Num = number of coeffs before 1/frq
+ *
+ * Beta trades the rejection of the lowpass filter against the transition
+ * width from passband to stopband. Larger Beta means a slower
+ * transition and greater stopband rejection. See Rabiner and Gold
+ * (Theory and Application of DSP) under Kaiser windows for more about
+ * Beta. The following table from Rabiner and Gold gives some feel
+ * for the effect of Beta:
+ *
+ * All ripples in dB, width of transition band = D*N where N = window length
+ *
+ * BETA D PB RIP SB RIP
+ * 2.120 1.50 +-0.27 -30
+ * 3.384 2.23 0.0864 -40
+ * 4.538 2.93 0.0274 -50
+ * 5.658 3.62 0.00868 -60
+ * 6.764 4.32 0.00275 -70
+ * 7.865 5.0 0.000868 -80
+ * 8.960 5.7 0.000275 -90
+ * 10.056 6.4 0.000087 -100
+ *
+ */
+ public static class FilterKit
+ {
+
+ // Max error acceptable in Izero
+ private static double IzeroEPSILON = 1E-21;
+
+ private static double Izero(double x)
+ {
+ double sum, u, halfx, temp;
+ int n;
+
+ sum = u = n = 1;
+ halfx = x / 2.0;
+ do
+ {
+ temp = halfx / (double)n;
+ n += 1;
+ temp *= temp;
+ u *= temp;
+ sum += u;
+ } while (u >= IzeroEPSILON * sum);
+ return (sum);
+ }
+
+ public static void lrsLpFilter(double[] c, int N, double frq, double Beta, int Num)
+ {
+ double IBeta, temp, temp1, inm1;
+ int i;
+
+ // Calculate ideal lowpass filter impulse response coefficients:
+ c[0] = 2.0 * frq;
+ for (i = 1; i < N; i++)
+ {
+ temp = Math.PI * (double)i / (double)Num;
+ c[i] = Math.Sin(2.0 * temp * frq) / temp; // Analog sinc function,
+ // cutoff = frq
+ }
+
+ /*
+ * Calculate and Apply Kaiser window to ideal lowpass filter. Note: last
+ * window value is IBeta which is NOT zero. You're supposed to really
+ * truncate the window here, not ramp it to zero. This helps reduce the
+ * first sidelobe.
+ */
+ IBeta = 1.0 / Izero(Beta);
+ inm1 = 1.0 / ((double)(N - 1));
+ for (i = 1; i < N; i++)
+ {
+ temp = (double)i * inm1;
+ temp1 = 1.0 - temp * temp;
+ temp1 = (temp1 < 0 ? 0 : temp1); /*
+ * make sure it's not negative
+ * since we're taking the square
+ * root - this happens on Pentium
+ * 4's due to tiny roundoff errors
+ */
+ c[i] *= Izero(Beta * Math.Sqrt(temp1)) * IBeta;
+ }
+ }
+
+ ///
+ ///
+ ///
+ /// impulse response
+ /// impulse response deltas
+ /// length of one wing of filter
+ /// Interpolate coefs using deltas?
+ /// Current sample array
+ /// Current sample index
+ /// Phase
+ /// increment (1 for right wing or -1 for left)
+ ///
+ public static float lrsFilterUp(float[] Imp, float[] ImpD, int Nwing, bool Interp, float[] Xp_array, int Xp_index, double Ph, int Inc)
+ {
+ double a = 0;
+ float v, t;
+
+ Ph *= Resampler.Npc; // Npc is number of values per 1/delta in impulse
+ // response
+
+ v = 0.0f; // The output value
+
+ float[] Hp_array = Imp;
+ int Hp_index = (int)Ph;
+
+ float[] End_array = Imp;
+ int End_index = Nwing;
+
+ float[] Hdp_array = ImpD;
+ int Hdp_index = (int)Ph;
+
+ if (Interp)
+ {
+ // Hdp = &ImpD[(int)Ph];
+ a = Ph - Math.Floor(Ph); /* fractional part of Phase */
+ }
+
+ if (Inc == 1) // If doing right wing...
+ { // ...drop extra coeff, so when Ph is
+ End_index--; // 0.5, we don't do too many mult's
+ if (Ph == 0) // If the phase is zero...
+ { // ...then we've already skipped the
+ Hp_index += Resampler.Npc; // first sample, so we must also
+ Hdp_index += Resampler.Npc; // skip ahead in Imp[] and ImpD[]
+ }
+ }
+
+ if (Interp)
+ while (Hp_index < End_index)
+ {
+ t = Hp_array[Hp_index]; /* Get filter coeff */
+ t += (float)(Hdp_array[Hdp_index] * a); /* t is now interp'd filter coeff */
+ Hdp_index += Resampler.Npc; /* Filter coeff differences step */
+ t *= Xp_array[Xp_index]; /* Mult coeff by input sample */
+ v += t; /* The filter output */
+ Hp_index += Resampler.Npc; /* Filter coeff step */
+ Xp_index += Inc; /* Input signal step. NO CHECK ON BOUNDS */
+ }
+ else
+ while (Hp_index < End_index)
+ {
+ t = Hp_array[Hp_index]; /* Get filter coeff */
+ t *= Xp_array[Xp_index]; /* Mult coeff by input sample */
+ v += t; /* The filter output */
+ Hp_index += Resampler.Npc; /* Filter coeff step */
+ Xp_index += Inc; /* Input signal step. NO CHECK ON BOUNDS */
+ }
+
+ return v;
+ }
+
+ ///
+ ///
+ ///
+ /// impulse response
+ /// impulse response deltas
+ /// length of one wing of filter
+ /// Interpolate coefs using deltas?
+ /// Current sample array
+ /// Current sample index
+ /// Phase
+ /// increment (1 for right wing or -1 for left)
+ /// filter sampling period
+ ///
+ public static float lrsFilterUD(float[] Imp, float[] ImpD, int Nwing, bool Interp, float[] Xp_array, int Xp_index, double Ph, int Inc, double dhb)
+ {
+ float a;
+ float v, t;
+ double Ho;
+
+ v = 0.0f; // The output value
+ Ho = Ph * dhb;
+
+ float[] End_array = Imp;
+ int End_index = Nwing;
+
+ if (Inc == 1) // If doing right wing...
+ { // ...drop extra coeff, so when Ph is
+ End_index--; // 0.5, we don't do too many mult's
+ if (Ph == 0) // If the phase is zero...
+ Ho += dhb; // ...then we've already skipped the
+ } // first sample, so we must also
+ // skip ahead in Imp[] and ImpD[]
+
+ float[] Hp_array = Imp;
+ int Hp_index;
+
+ if (Interp)
+ {
+ float[] Hdp_array = ImpD;
+ int Hdp_index;
+
+ while ((Hp_index = (int)Ho) < End_index)
+ {
+ t = Hp_array[Hp_index]; // Get IR sample
+ Hdp_index = (int)Ho; // get interp bits from diff table
+ a = (float)(Ho - Math.Floor(Ho)); // a is logically between 0
+ // and 1
+ t += Hdp_array[Hdp_index] * a; // t is now interp'd filter coeff
+ t *= Xp_array[Xp_index]; // Mult coeff by input sample
+ v += t; // The filter output
+ Ho += dhb; // IR step
+ Xp_index += Inc; // Input signal step. NO CHECK ON BOUNDS
+ }
+ }
+ else
+ {
+ while ((Hp_index = (int)Ho) < End_index)
+ {
+ t = Hp_array[Hp_index]; // Get IR sample
+ t *= Xp_array[Xp_index]; // Mult coeff by input sample
+ v += t; // The filter output
+ Ho += dhb; // IR step
+ Xp_index += Inc; // Input signal step. NO CHECK ON BOUNDS
+ }
+ }
+
+ return v;
+ }
+
+ }
+}
diff --git a/BizHawk.Emulation/Sound/Utilities/Resampler.cs b/BizHawk.Emulation/Sound/Utilities/Resampler.cs
new file mode 100644
index 0000000000..f1709f7a56
--- /dev/null
+++ b/BizHawk.Emulation/Sound/Utilities/Resampler.cs
@@ -0,0 +1,644 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace BizHawk.Emulation.Sound.Utilities
+{
+ /******************************************************************************
+ *
+ * libresample4j
+ * Copyright (c) 2009 Laszlo Systems, Inc. All Rights Reserved.
+ *
+ * libresample4j is a Java port of Dominic Mazzoni's libresample 0.1.3,
+ * which is in turn based on Julius Smith's Resample 1.7 library.
+ * http://www-ccrma.stanford.edu/~jos/resample/
+ *
+ * License: LGPL -- see the file LICENSE.txt for more information
+ *
+ *****************************************************************************/
+
+ //import java.nio.FloatBuffer;
+
+ public class Resampler
+ {
+
+ public class Result
+ {
+ public int inputSamplesConsumed { get; private set; }
+ public int outputSamplesGenerated { get; private set; }
+
+ public Result(int inputSamplesConsumed, int outputSamplesGenerated)
+ {
+ this.inputSamplesConsumed = inputSamplesConsumed;
+ this.outputSamplesGenerated = outputSamplesGenerated;
+ }
+ }
+
+ /// number of values per 1/delta in impulse response
+ public const int Npc = 4096;
+
+ private float[] Imp;
+ private float[] ImpD;
+ private float LpScl;
+ private int Nmult;
+ private int Nwing;
+ private double minFactor;
+ private double maxFactor;
+ private int XSize;
+ private float[] X;
+ ///
+ /// Current "now"-sample pointer for input
+ ///
+ private int Xp;
+ ///
+ /// Position to put new samples
+ ///
+ private int Xread;
+ private int Xoff;
+ private float[] Y;
+ private int Yp;
+ private double Time;
+
+ /**
+ * Clone an existing resampling session. Faster than creating one from scratch.
+ *
+ * @param other
+ */
+ public Resampler(Resampler other)
+ {
+ this.Imp = (float[])other.Imp.Clone();
+ this.ImpD = (float[])other.ImpD.Clone();
+ this.LpScl = other.LpScl;
+ this.Nmult = other.Nmult;
+ this.Nwing = other.Nwing;
+ this.minFactor = other.minFactor;
+ this.maxFactor = other.maxFactor;
+ this.XSize = other.XSize;
+ this.X = (float[])other.X.Clone();
+ this.Xp = other.Xp;
+ this.Xread = other.Xread;
+ this.Xoff = other.Xoff;
+ this.Y = (float[])other.Y.Clone();
+ this.Yp = other.Yp;
+ this.Time = other.Time;
+ }
+
+ /**
+ * Create a new resampling session.
+ *
+ * @param highQuality true for better quality, slower processing time
+ * @param minFactor lower bound on resampling factor for this session
+ * @param maxFactor upper bound on resampling factor for this session
+ * @throws IllegalArgumentException if minFactor or maxFactor is not
+ * positive, or if maxFactor is less than minFactor
+ */
+ public Resampler(bool highQuality, double minFactor, double maxFactor)
+ {
+ if (minFactor <= 0.0 || maxFactor <= 0.0)
+ throw new ArgumentException("minFactor and maxFactor must be positive");
+
+ if (maxFactor < minFactor)
+ throw new ArgumentException("minFactor must be <= maxFactor");
+
+ this.minFactor = minFactor;
+ this.maxFactor = maxFactor;
+ this.Nmult = highQuality ? 35 : 11;
+ this.LpScl = 1.0f;
+ this.Nwing = Npc * (this.Nmult - 1) / 2; // # of filter coeffs in right wing
+
+ double Rolloff = 0.90;
+ double Beta = 6;
+
+ double[] Imp64 = new double[this.Nwing];
+
+ FilterKit.lrsLpFilter(Imp64, this.Nwing, 0.5 * Rolloff, Beta, Npc);
+ this.Imp = new float[this.Nwing];
+ this.ImpD = new float[this.Nwing];
+
+ for (int i = 0; i < this.Nwing; i++)
+ {
+ this.Imp[i] = (float)Imp64[i];
+ }
+
+ // Storing deltas in ImpD makes linear interpolation
+ // of the filter coefficients faster
+ for (int i = 0; i < this.Nwing - 1; i++)
+ {
+ this.ImpD[i] = this.Imp[i + 1] - this.Imp[i];
+ }
+
+ // Last coeff. not interpolated
+ this.ImpD[this.Nwing - 1] = -this.Imp[this.Nwing - 1];
+
+ // Calc reach of LP filter wing (plus some creeping room)
+ int Xoff_min = (int)(((this.Nmult + 1) / 2.0) * Math.Max(1.0, 1.0 / minFactor) + 10);
+ int Xoff_max = (int)(((this.Nmult + 1) / 2.0) * Math.Max(1.0, 1.0 / maxFactor) + 10);
+ this.Xoff = Math.Max(Xoff_min, Xoff_max);
+
+ // Make the inBuffer size at least 4096, but larger if necessary
+ // in order to store the minimum reach of the LP filter and then some.
+ // Then allocate the buffer an extra Xoff larger so that
+ // we can zero-pad up to Xoff zeros at the end when we reach the
+ // end of the input samples.
+ this.XSize = Math.Max(2 * this.Xoff + 10, 4096);
+ this.X = new float[this.XSize + this.Xoff];
+ this.Xp = this.Xoff;
+ this.Xread = this.Xoff;
+
+ // Make the outBuffer long enough to hold the entire processed
+ // output of one inBuffer
+ int YSize = (int)(((double)this.XSize) * maxFactor + 2.0);
+ this.Y = new float[YSize];
+ this.Yp = 0;
+
+ this.Time = (double)this.Xoff; // Current-time pointer for converter
+ }
+
+ public int getFilterWidth()
+ {
+ return this.Xoff;
+ }
+
+ /**
+ * Process a batch of samples. There is no guarantee that the input buffer will be drained.
+ *
+ * @param factor factor at which to resample this batch
+ * @param buffers sample buffer for producing input and consuming output
+ * @param lastBatch true if this is known to be the last batch of samples
+ * @return true iff resampling is complete (ie. no input samples consumed and no output samples produced)
+ */
+ public bool process(double factor, SampleBuffers buffers, bool lastBatch)
+ {
+ if (factor < this.minFactor || factor > this.maxFactor)
+ {
+ throw new ArgumentException("factor " + factor + " is not between minFactor=" + minFactor
+ + " and maxFactor=" + maxFactor);
+ }
+
+ int outBufferLen = buffers.getOutputBufferLength();
+ int inBufferLen = buffers.getInputBufferLength();
+
+ float[] Imp = this.Imp;
+ float[] ImpD = this.ImpD;
+ float LpScl = this.LpScl;
+ int Nwing = this.Nwing;
+ bool interpFilt = false; // TRUE means interpolate filter coeffs
+
+ int inBufferUsed = 0;
+ int outSampleCount = 0;
+
+ // Start by copying any samples still in the Y buffer to the output
+ // buffer
+ if ((this.Yp != 0) && (outBufferLen - outSampleCount) > 0)
+ {
+ int len = Math.Min(outBufferLen - outSampleCount, this.Yp);
+
+ buffers.consumeOutput(this.Y, 0, len);
+ //for (int i = 0; i < len; i++) {
+ // outBuffer[outBufferOffset + outSampleCount + i] = this.Y[i];
+ //}
+
+ outSampleCount += len;
+ for (int i = 0; i < this.Yp - len; i++)
+ {
+ this.Y[i] = this.Y[i + len];
+ }
+ this.Yp -= len;
+ }
+
+ // If there are still output samples left, return now - we need
+ // the full output buffer available to us...
+ if (this.Yp != 0)
+ {
+ return inBufferUsed == 0 && outSampleCount == 0;
+ }
+
+ // Account for increased filter gain when using factors less than 1
+ if (factor < 1)
+ {
+ LpScl = (float)(LpScl * factor);
+ }
+
+ while (true)
+ {
+
+ // This is the maximum number of samples we can process
+ // per loop iteration
+
+ /*
+ * #ifdef DEBUG
+ * printf("XSize: %d Xoff: %d Xread: %d Xp: %d lastFlag: %d\n",
+ * this.XSize, this.Xoff, this.Xread, this.Xp, lastFlag); #endif
+ */
+
+ // Copy as many samples as we can from the input buffer into X
+ int len = this.XSize - this.Xread;
+
+ if (len >= inBufferLen - inBufferUsed)
+ {
+ len = inBufferLen - inBufferUsed;
+ }
+
+ buffers.produceInput(this.X, this.Xread, len);
+ //for (int i = 0; i < len; i++) {
+ // this.X[this.Xread + i] = inBuffer[inBufferOffset + inBufferUsed + i];
+ //}
+
+ inBufferUsed += len;
+ this.Xread += len;
+
+ int Nx;
+ if (lastBatch && (inBufferUsed == inBufferLen))
+ {
+ // If these are the last samples, zero-pad the
+ // end of the input buffer and make sure we process
+ // all the way to the end
+ Nx = this.Xread - this.Xoff;
+ for (int i = 0; i < this.Xoff; i++)
+ {
+ this.X[this.Xread + i] = 0;
+ }
+ }
+ else
+ {
+ Nx = this.Xread - 2 * this.Xoff;
+ }
+
+ /*
+ * #ifdef DEBUG fprintf(stderr, "new len=%d Nx=%d\n", len, Nx);
+ * #endif
+ */
+
+ if (Nx <= 0)
+ {
+ break;
+ }
+
+ // Resample stuff in input buffer
+ int Nout;
+ if (factor >= 1)
+ { // SrcUp() is faster if we can use it */
+ Nout = lrsSrcUp(this.X, this.Y, factor, /* &this.Time, */Nx, Nwing, LpScl, Imp, ImpD, interpFilt);
+ }
+ else
+ {
+ Nout = lrsSrcUD(this.X, this.Y, factor, /* &this.Time, */Nx, Nwing, LpScl, Imp, ImpD, interpFilt);
+ }
+
+ /*
+ * #ifdef DEBUG
+ * printf("Nout: %d\n", Nout);
+ * #endif
+ */
+
+ this.Time -= Nx; // Move converter Nx samples back in time
+ this.Xp += Nx; // Advance by number of samples processed
+
+ // Calc time accumulation in Time
+ int Ncreep = (int)(this.Time) - this.Xoff;
+ if (Ncreep != 0)
+ {
+ this.Time -= Ncreep; // Remove time accumulation
+ this.Xp += Ncreep; // and add it to read pointer
+ }
+
+ // Copy part of input signal that must be re-used
+ int Nreuse = this.Xread - (this.Xp - this.Xoff);
+
+ for (int i = 0; i < Nreuse; i++)
+ {
+ this.X[i] = this.X[i + (this.Xp - this.Xoff)];
+ }
+
+ /*
+ #ifdef DEBUG
+ printf("New Xread=%d\n", Nreuse);
+ #endif */
+
+ this.Xread = Nreuse; // Pos in input buff to read new data into
+ this.Xp = this.Xoff;
+
+ this.Yp = Nout;
+
+ // Copy as many samples as possible to the output buffer
+ if (this.Yp != 0 && (outBufferLen - outSampleCount) > 0)
+ {
+ len = Math.Min(outBufferLen - outSampleCount, this.Yp);
+
+ buffers.consumeOutput(this.Y, 0, len);
+ //for (int i = 0; i < len; i++) {
+ // outBuffer[outBufferOffset + outSampleCount + i] = this.Y[i];
+ //}
+
+ outSampleCount += len;
+ for (int i = 0; i < this.Yp - len; i++)
+ {
+ this.Y[i] = this.Y[i + len];
+ }
+ this.Yp -= len;
+ }
+
+ // If there are still output samples left, return now,
+ // since we need the full output buffer available
+ if (this.Yp != 0)
+ {
+ break;
+ }
+ }
+
+ return inBufferUsed == 0 && outSampleCount == 0;
+ }
+
+ /**
+ * Process a batch of samples. Convenience method for when the input and output are both floats.
+ *
+ * @param factor factor at which to resample this batch
+ * @param inputBuffer contains input samples in the range -1.0 to 1.0
+ * @param outputBuffer output samples will be deposited here
+ * @param lastBatch true if this is known to be the last batch of samples
+ * @return true iff resampling is complete (ie. no input samples consumed and no output samples produced)
+ */
+ /*
+ public bool process(double factor, FloatBuffer inputBuffer, bool lastBatch, FloatBuffer outputBuffer) {
+ SampleBuffers sampleBuffers = new SampleBuffers() {
+ public int getInputBufferLength() {
+ return inputBuffer.remaining();
+ }
+
+ public int getOutputBufferLength() {
+ return outputBuffer.remaining();
+ }
+
+ public void produceInput(float[] array, int offset, int length) {
+ inputBuffer.get(array, offset, length);
+ }
+
+ public void consumeOutput(float[] array, int offset, int length) {
+ outputBuffer.put(array, offset, length);
+ }
+ };
+ return process(factor, sampleBuffers, lastBatch);
+ }
+ */
+
+ /**
+ * Process a batch of samples. Alternative interface if you prefer to work with arrays.
+ *
+ * @param factor resampling rate for this batch
+ * @param inBuffer array containing input samples in the range -1.0 to 1.0
+ * @param inBufferOffset offset into inBuffer at which to start processing
+ * @param inBufferLen number of valid elements in the inputBuffer
+ * @param lastBatch pass true if this is the last batch of samples
+ * @param outBuffer array to hold the resampled data
+ * @return the number of samples consumed and generated
+ */
+ /*
+ public Result process(double factor, float[] inBuffer, int inBufferOffset, int inBufferLen, boolean lastBatch, float[] outBuffer, int outBufferOffset, int outBufferLen) {
+ FloatBuffer inputBuffer = FloatBuffer.wrap(inBuffer, inBufferOffset, inBufferLen);
+ FloatBuffer outputBuffer = FloatBuffer.wrap(outBuffer, outBufferOffset, outBufferLen);
+
+ process(factor, inputBuffer, lastBatch, outputBuffer);
+
+ return new Result(inputBuffer.position() - inBufferOffset, outputBuffer.position() - outBufferOffset);
+ }
+ */
+
+ ///
+ /// Sampling rate up-conversion only subroutine; Slightly faster than down-conversion
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ private int lrsSrcUp(float[] X, float[] Y, double factor, int Nx, int Nwing, float LpScl, float[] Imp,
+ float[] ImpD, bool Interp)
+ {
+
+ float[] Xp_array = X;
+ int Xp_index;
+
+ float[] Yp_array = Y;
+ int Yp_index = 0;
+
+ float v;
+
+ double CurrentTime = this.Time;
+ double dt; // Step through input signal
+ double endTime; // When Time reaches EndTime, return to user
+
+ dt = 1.0 / factor; // Output sampling period
+
+ endTime = CurrentTime + Nx;
+ while (CurrentTime < endTime)
+ {
+ double LeftPhase = CurrentTime - Math.Floor(CurrentTime);
+ double RightPhase = 1.0 - LeftPhase;
+
+ Xp_index = (int)CurrentTime; // Ptr to current input sample
+ // Perform left-wing inner product
+ v = FilterKit.lrsFilterUp(Imp, ImpD, Nwing, Interp, Xp_array, Xp_index++, LeftPhase, -1);
+ // Perform right-wing inner product
+ v += FilterKit.lrsFilterUp(Imp, ImpD, Nwing, Interp, Xp_array, Xp_index, RightPhase, 1);
+
+ v *= LpScl; // Normalize for unity filter gain
+
+ Yp_array[Yp_index++] = v; // Deposit output
+ CurrentTime += dt; // Move to next sample by time increment
+ }
+
+ this.Time = CurrentTime;
+ return Yp_index; // Return the number of output samples
+ }
+
+ private int lrsSrcUD(float[] X, float[] Y, double factor, int Nx, int Nwing, float LpScl, float[] Imp,
+ float[] ImpD, bool Interp)
+ {
+
+ float[] Xp_array = X;
+ int Xp_index;
+
+ float[] Yp_array = Y;
+ int Yp_index = 0;
+
+ float v;
+
+ double CurrentTime = this.Time;
+ double dh; // Step through filter impulse response
+ double dt; // Step through input signal
+ double endTime; // When Time reaches EndTime, return to user
+
+ dt = 1.0 / factor; // Output sampling period
+
+ dh = Math.Min(Npc, factor * Npc); // Filter sampling period
+
+ endTime = CurrentTime + Nx;
+ while (CurrentTime < endTime)
+ {
+ double LeftPhase = CurrentTime - Math.Floor(CurrentTime);
+ double RightPhase = 1.0 - LeftPhase;
+
+ Xp_index = (int)CurrentTime; // Ptr to current input sample
+ // Perform left-wing inner product
+ v = FilterKit.lrsFilterUD(Imp, ImpD, Nwing, Interp, Xp_array, Xp_index++, LeftPhase, -1, dh);
+ // Perform right-wing inner product
+ v += FilterKit.lrsFilterUD(Imp, ImpD, Nwing, Interp, Xp_array, Xp_index, RightPhase, 1, dh);
+
+ v *= LpScl; // Normalize for unity filter gain
+
+ Yp_array[Yp_index++] = v; // Deposit output
+
+ CurrentTime += dt; // Move to next sample by time increment
+ }
+
+ this.Time = CurrentTime;
+ return Yp_index; // Return the number of output samples
+ }
+
+ }
+
+ ///
+ /// implements a pair of Resamplers that work off of interleaved stereo buffers of shorts
+ /// this is what's used inside most of bizhawk
+ ///
+ public class BizhawkResampler
+ {
+ private Resampler left;
+ private Resampler right;
+
+ public BizhawkResampler(bool highQuality, double minFactor, double maxFactor)
+ {
+ left = new Resampler(highQuality, minFactor, maxFactor);
+ right = new Resampler(highQuality, minFactor, maxFactor);
+ }
+
+
+
+ ///
+ /// resample some audio.
+ ///
+ /// outrate / inrate
+ /// true to finalize (empty buffers and finish)
+ /// input samples, as interleaved stereo shorts. in general, all won't be used unless lastbatch = true
+ /// recieves output samples, as interleaved stereo shorts
+ /// true if processing finished; only possible with lastbatch = true
+ public bool process(double factor, bool lastBatch, Queue input, Queue output)
+ {
+ LeftBuffer lb = new LeftBuffer(input, output);
+
+ SampleBuffers rb = lb.GetRightBuffer();
+
+ bool doneleft = left.process(factor, lb, lastBatch);
+ bool doneright = right.process(factor, rb, lastBatch);
+
+ return doneleft && doneright;
+ }
+
+ ///
+ /// ugly wrapper class that handles interleaved writes and reads
+ /// made under the assumption that LeftBuffer gets called first
+ /// both buffers must consume and produce the same amount of data, or else
+ ///
+ class LeftBuffer : SampleBuffers
+ {
+ int inbufferlen;
+
+ Queue input;
+ Queue output;
+
+ Queue rightin = new Queue();
+ Queue leftout = new Queue();
+
+ RightBuffer child;
+
+ public SampleBuffers GetRightBuffer()
+ {
+ return child;
+ }
+
+ public LeftBuffer(Queue input, Queue output)
+ {
+ this.input = input;
+ this.output = output;
+ if (input.Count % 2 != 0)
+ throw new ArgumentException("Input must contain an even number of samples! (stereo stacked)");
+ inbufferlen = input.Count / 2;
+ child = new RightBuffer(this);
+ }
+
+ public int getInputBufferLength()
+ {
+ return inbufferlen;
+ }
+
+ public int getOutputBufferLength()
+ {
+ // queues resize, so don't care
+ return 1000000;
+ }
+
+ public void produceInput(float[] array, int offset, int length)
+ {
+ for (int i = offset; i < length + offset; i++)
+ {
+ // remove left sample
+ short left = input.Dequeue();
+ // remove right sample and save for later
+ rightin.Enqueue(input.Dequeue());
+
+ array[i] = left / 32768.0f;
+ }
+ }
+
+ public void consumeOutput(float[] array, int offset, int length)
+ {
+ for (int i = offset; i < length + offset; i++)
+ leftout.Enqueue(array[i]);
+ }
+
+ class RightBuffer : SampleBuffers
+ {
+ LeftBuffer parent;
+ public RightBuffer(LeftBuffer parent)
+ {
+ this.parent = parent;
+ }
+
+ public int getInputBufferLength()
+ {
+ return parent.getInputBufferLength();
+ }
+
+ public int getOutputBufferLength()
+ {
+ return parent.getOutputBufferLength();
+ }
+
+ public void produceInput(float[] array, int offset, int length)
+ {
+ for (int i = offset; i < length + offset; i++)
+ array[i] = parent.rightin.Dequeue() / 32768.0f;
+ }
+
+ public void consumeOutput(float[] array, int offset, int length)
+ {
+ for (int i = offset; i < length + offset; i++)
+ {
+ parent.output.Enqueue((short)(parent.leftout.Dequeue() * 32768.0f));
+ parent.output.Enqueue((short)(array[i] * 32768.0f));
+ }
+ }
+ }
+
+ }
+
+
+ }
+
+}
diff --git a/BizHawk.Emulation/Sound/Utilities/SampleBuffers.cs b/BizHawk.Emulation/Sound/Utilities/SampleBuffers.cs
new file mode 100644
index 0000000000..eee23758ac
--- /dev/null
+++ b/BizHawk.Emulation/Sound/Utilities/SampleBuffers.cs
@@ -0,0 +1,50 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace BizHawk.Emulation.Sound.Utilities
+{
+ /******************************************************************************
+ *
+ * libresample4j
+ * Copyright (c) 2009 Laszlo Systems, Inc. All Rights Reserved.
+ *
+ * libresample4j is a Java port of Dominic Mazzoni's libresample 0.1.3,
+ * which is in turn based on Julius Smith's Resample 1.7 library.
+ * http://www-ccrma.stanford.edu/~jos/resample/
+ *
+ * License: LGPL -- see the file LICENSE.txt for more information
+ *
+ *****************************************************************************/
+
+ ///
+ /// Callback for producing and consuming samples. Enables on-the-fly conversion between sample types
+ /// (signed 16-bit integers to floats, for example) and/or writing directly to an output stream.
+ ///
+ public interface SampleBuffers
+ {
+ /// number of input samples available
+ int getInputBufferLength();
+
+ /// number of samples the output buffer has room for
+ int getOutputBufferLength();
+
+ ///
+ /// Copy length
samples from the input buffer to the given array, starting at the given offset.
+ /// Samples should be in the range -1.0f to 1.0f.
+ ///
+ /// array to hold samples from the input buffer
+ /// start writing samples here
+ /// write this many samples
+ void produceInput(float[] array, int offset, int length);
+
+ ///
+ /// Copy length
samples from the given array to the output buffer, starting at the given offset.
+ ///
+ /// array to read from
+ /// start reading samples here
+ /// read this many samples
+ void consumeOutput(float[] array, int offset, int length);
+ }
+}