replace junk and bad license resamplers with libspeexdsp (using p/invoke). MIT licensed, sounds pretty decent.

the p/invoke wrapper class is a hackjob i was using for testing and will be replaced.
This commit is contained in:
goyuken 2012-09-07 18:49:18 +00:00
parent 2a41b8eda7
commit 001b28c60e
11 changed files with 322 additions and 1265 deletions

View File

@ -356,14 +356,8 @@
<Compile Include="Properties\svnrev.cs" />
<Compile Include="QuickCollections.cs" />
<Compile Include="Sound\CDAudio.cs" />
<Compile Include="Sound\Utilities\CubicResampler.cs" />
<Compile Include="Sound\Utilities\DualSound.cs" />
<Compile Include="Sound\Utilities\Equalizer.cs" />
<Compile Include="Sound\Utilities\FilterKit.cs" />
<Compile Include="Sound\Utilities\IStereoResampler.cs" />
<Compile Include="Sound\Utilities\LinearResampler.cs" />
<Compile Include="Sound\Utilities\Resampler.cs" />
<Compile Include="Sound\Utilities\SampleBuffers.cs" />
<Compile Include="Sound\Utilities\SpeexResampler.cs" />
<Compile Include="Sound\VRC6.cs" />
<Compile Include="Sound\Utilities\BufferedAsync.cs" />
<Compile Include="Sound\Utilities\Metaspu.cs" />

View File

@ -490,15 +490,17 @@ namespace BizHawk.Emulation.Consoles.Nintendo.SNES
/// <summary>actual sampling factor used</summary>
const double resamplingfactor = 44100.0 / 32040.5;
Sound.Utilities.IStereoResampler resampler = new Sound.Utilities.BizhawkResampler(false);
//Sound.Utilities.IStereoResampler resampler = new Sound.Utilities.BizhawkResampler(false);
//Sound.Utilities.IStereoResampler resampler = new Sound.Utilities.SinkResampler(12);
//Sound.Utilities.IStereoResampler resampler = new Sound.Utilities.CubicResampler();
//Sound.Utilities.IStereoResampler resampler = new Sound.Utilities.LinearResampler();
Sound.Utilities.SpeexResampler resampler = new Sound.Utilities.SpeexResampler(6);
Sound.MetaspuSoundProvider metaspu = new Sound.MetaspuSoundProvider(Sound.ESynchMethod.ESynchMethod_V);
void snes_audio_sample(ushort left, ushort right)
{
AudioInBuffer.Enqueue((short)left);
AudioInBuffer.Enqueue((short)right);
@ -537,8 +539,10 @@ namespace BizHawk.Emulation.Consoles.Nintendo.SNES
if (true)
{
resampler.ResampleChunk(AudioInBuffer, AudioOutBuffer, false);
//lock (AudioInBuffer)
//{
resampler.ResampleChunk(AudioInBuffer, AudioOutBuffer, false);
//}
// drain into the metaspu immediately
// we could skip this step and drain directly by changing SampleBuffers implementation
while (AudioOutBuffer.Count > 0)

View File

@ -1,87 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace BizHawk.Emulation.Sound.Utilities
{
/// <summary>
/// a simple cubic interpolation resampler. no lowpass. original code
/// </summary>
public class CubicResampler : IStereoResampler
{
int[] data = new int[8];
double mu;
/// <summary>input rate / output rate</summary>
double ratio;
public CubicResampler()
{
}
public void StartSession(double ratio)
{
this.ratio = 1.0 / ratio;
mu = 0.0;
for (int i = 0; i < data.Length; i++)
data[i] = 0;
}
public void ResampleChunk(Queue<short> input, Queue<short> output, bool finish)
{
while (true)
{
while (mu >= 1.0 && input.Count >= 2)
{
mu -= 1.0;
for (int i = 0; i < 6; i++)
data[i] = data[i + 2];
data[6] = input.Dequeue();
data[7] = input.Dequeue();
}
if (mu >= 1.0)
return;
double mu2 = mu * mu;
double mu3 = mu2 * mu2;
int l0 = data[6] - data[4] - data[0] + data[2];
int l1 = data[0] - data[2] - l0;
int l2 = data[4] - data[0];
int l3 = data[2];
int r0 = data[7] - data[5] - data[1] + data[3];
int r1 = data[1] - data[3] - r0;
int r2 = data[5] - data[1];
int r3 = data[3];
double ls = l0 * mu3 + l1 * mu2 + l2 * mu + l3;
double rs = r0 * mu3 + r1 * mu2 + r2 * mu + r3;
short l, r;
if (ls > 32767.0)
l = 32767;
else if (ls < -32768.0)
l = -32768;
else
l = (short)ls;
if (rs > 32767.0)
r = 32767;
else if (ls < -32768.0)
r = -32768;
else
r = (short)ls;
output.Enqueue(l);
output.Enqueue(r);
mu += ratio;
}
}
}
}

View File

@ -1,109 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace BizHawk.Emulation.Sound.Utilities
{
/// <summary>
/// provides pass-through sound for the dumping tool to use, while making a "best effort"
/// to have something available for audio output
/// </summary>
public class DualSound : ISoundProvider
{
/// <summary>
/// implementation of a "slave" ISoundProvider that recieves best effort audio
/// </summary>
class SecondPin : ISoundProvider
{
/// <summary>
/// the source to draw from
/// </summary>
DualSound master;
public SecondPin(DualSound master)
{
this.master = master;
}
public void GetSamples(short[] samples)
{
int i;
for (i = 0; i < Math.Min(samples.Length, master.ringbuffer.Count); i++)
samples[i] = master.ringbuffer.Dequeue();
for (; i < samples.Length; i++)
// underflow
samples[i] = 0;
}
public void DiscardSamples()
{
master.ringbuffer.Clear();
}
public int MaxVolume
{
// ignored
get;
set;
}
}
/// <summary>
/// original input source
/// </summary>
ISoundProvider input;
/// <summary>
/// threshold at which to discard samples
/// </summary>
int killsize;
/// <summary>
/// storage of samples waiting to go to second pin
/// </summary>
Queue<short> ringbuffer;
/// <summary>
/// get the slave pin
/// </summary>
public ISoundProvider secondpin
{
get;
private set;
}
/// <summary>
/// default constructor
/// </summary>
/// <param name="input">the ISoundProvider to use as input</param>
/// <param name="buffsize">how many sample pairs to save for the second pin</param>
public DualSound(ISoundProvider input, int buffsize)
{
this.input = input;
killsize = buffsize * 2;
ringbuffer = new Queue<short>(killsize);
secondpin = new SecondPin(this);
}
public void GetSamples(short[] samples)
{
input.GetSamples(samples);
if (ringbuffer.Count >= killsize)
ringbuffer.Clear();
foreach (var sample in samples)
ringbuffer.Enqueue(sample);
}
public void DiscardSamples()
{
throw new Exception("Dumpers should never discard samples!");
}
public int MaxVolume
{
get { return input.MaxVolume; }
set { input.MaxVolume = value; }
}
}
}

View File

@ -1,263 +0,0 @@
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.
*
* <pre>
* 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
* </pre>
*/
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;
}
}
/// <summary>
///
/// </summary>
/// <param name="Imp">impulse response</param>
/// <param name="ImpD">impulse response deltas</param>
/// <param name="Nwing">length of one wing of filter</param>
/// <param name="Interp">Interpolate coefs using deltas?</param>
/// <param name="Xp_array">Current sample array</param>
/// <param name="Xp_index">Current sample index</param>
/// <param name="Ph">Phase</param>
/// <param name="Inc">increment (1 for right wing or -1 for left)</param>
/// <returns></returns>
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;
}
/// <summary>
///
/// </summary>
/// <param name="Imp">impulse response</param>
/// <param name="ImpD">impulse response deltas</param>
/// <param name="Nwing">length of one wing of filter</param>
/// <param name="Interp">Interpolate coefs using deltas?</param>
/// <param name="Xp_array">Current sample array</param>
/// <param name="Xp_index">Current sample index</param>
/// <param name="Ph">Phase</param>
/// <param name="Inc">increment (1 for right wing or -1 for left)</param>
/// <param name="dhb">filter sampling period</param>
/// <returns></returns>
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;
}
}
}

View File

@ -1,27 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace BizHawk.Emulation.Sound.Utilities
{
/// <summary>
/// describes an audio resampler that works with stereo streams of shorts (interleaved)
/// </summary>
public interface IStereoResampler
{
/// <summary>
/// start a resampling session, with the given conversion rate
/// </summary>
/// <param name="ratio">outrate / inrate</param>
void StartSession(double ratio);
/// <summary>
/// process any available input
/// </summary>
/// <param name="input">input samples. all might not be consumed unless finish == true</param>
/// <param name="output">where to put output samples.</param>
/// <param name="finish">if true, consume all input and end session</param>
void ResampleChunk(Queue<short> input, Queue<short> output, bool finish);
}
}

View File

@ -1,70 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace BizHawk.Emulation.Sound.Utilities
{
// a simple linear resampler
public class LinearResampler : IStereoResampler
{
short[] data = new short[4];
double mu;
/// <summary>input rate / output rate</summary>
double ratio;
public LinearResampler()
{
}
public void StartSession(double ratio)
{
this.ratio = 1.0 / ratio;
mu = 0.0;
for (int i = 0; i < data.Length; i++)
data[i] = 0;
}
public void ResampleChunk(Queue<short> input, Queue<short> output, bool finish)
{
while (true)
{
while (mu >= 1.0 && input.Count >= 2)
{
mu -= 1.0;
data[0] = data[2];
data[1] = data[3];
data[2] = input.Dequeue();
data[3] = input.Dequeue();
}
if (mu >= 1.0)
return;
double ls = data[0] * (1.0 - mu) + data[1] * mu;
double rs = data[1] * (1.0 - mu) + data[3] * mu;
short l, r;
if (ls > 32767.0)
l = 32767;
else if (ls < -32768.0)
l = -32768;
else
l = (short)ls;
if (rs > 32767.0)
r = 32767;
else if (ls < -32768.0)
r = -32768;
else
r = (short)ls;
output.Enqueue(l);
output.Enqueue(r);
mu += ratio;
}
}
}
}

View File

@ -1,648 +0,0 @@
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;
}
}
/// <summary>number of values per 1/delta in impulse response</summary>
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;
/// <summary>
/// Current "now"-sample pointer for input
/// </summary>
private int Xp;
/// <summary>
/// Position to put new samples
/// </summary>
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);
}
*/
/// <summary>
/// Sampling rate up-conversion only subroutine; Slightly faster than down-conversion
/// </summary>
/// <param name="X"></param>
/// <param name="Y"></param>
/// <param name="factor"></param>
/// <param name="Nx"></param>
/// <param name="Nwing"></param>
/// <param name="LpScl"></param>
/// <param name="Imp"></param>
/// <param name="ImpD"></param>
/// <param name="Interp"></param>
/// <returns></returns>
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
}
}
/// <summary>
/// implements a pair of Resamplers that work off of interleaved stereo buffers of shorts
/// this is what's used inside most of bizhawk
/// </summary>
public class BizhawkResampler : IStereoResampler
{
bool highQuality;
double ratio;
public void StartSession(double ratio)
{
this.ratio = ratio;
left = new Resampler(highQuality, ratio, ratio);
right = new Resampler(highQuality, ratio, ratio);
}
public void ResampleChunk(Queue<short> input, Queue<short> output, bool finish)
{
LeftBuffer lb = new LeftBuffer(input, output);
SampleBuffers rb = lb.GetRightBuffer();
bool doneleft = left.process(ratio, lb, finish);
bool doneright = right.process(ratio, rb, finish);
if (finish)
{
left = null;
right = null;
}
}
private Resampler left;
private Resampler right;
/// <summary>
///
/// </summary>
/// <param name="highQuality">true for high quality resampling</param>
public BizhawkResampler(bool highQuality)
{
this.highQuality = highQuality;
}
/// <summary>
/// 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
/// </summary>
class LeftBuffer : SampleBuffers
{
int inbufferlen;
Queue<short> input;
Queue<short> output;
Queue<short> rightin = new Queue<short>();
Queue<float> leftout = new Queue<float>();
RightBuffer child;
public SampleBuffers GetRightBuffer()
{
return child;
}
public LeftBuffer(Queue<short> input, Queue<short> 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));
}
}
}
}
}
}

View File

@ -1,50 +0,0 @@
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
*
*****************************************************************************/
/// <summary>
/// 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.
/// </summary>
public interface SampleBuffers
{
/// <summary>number of input samples available</summary>
int getInputBufferLength();
/// <summary>number of samples the output buffer has room for</summary>
int getOutputBufferLength();
/// <summary>
/// Copy <code>length</code> 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.
/// </summary>
/// <param name="array">array to hold samples from the input buffer</param>
/// <param name="offset">start writing samples here</param>
/// <param name="length">write this many samples</param>
void produceInput(float[] array, int offset, int length);
/// <summary>
/// Copy <code>length</code> samples from the given array to the output buffer, starting at the given offset.
/// </summary>
/// <param name="array">array to read from</param>
/// <param name="offset">start reading samples here</param>
/// <param name="length">read this many samples</param>
void consumeOutput(float[] array, int offset, int length);
}
}

View File

@ -0,0 +1,313 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.InteropServices;
namespace BizHawk.Emulation.Sound.Utilities
{
/// <summary>
/// junk wrapper around LibSpeexDSP. quite inefficient. will be replaced
/// </summary>
public class SpeexResampler
{
static class LibSpeexDSP
{
public const int QUALITY_MAX = 10;
public const int QUALITY_MIN = 0;
public const int QUALITY_DEFAULT = 4;
public const int QUALITY_VOIP = 3;
public const int QUALITY_DESKTOP = 5;
public enum RESAMPLER_ERR
{
SUCCESS = 0,
ALLOC_FAILED = 1,
BAD_STATE = 2,
INVALID_ARG = 3,
PTR_OVERLAP = 4,
MAX_ERROR
};
/// <summary>
/// Create a new resampler with integer input and output rates.
/// </summary>
/// <param name="nb_channels">Number of channels to be processed</param>
/// <param name="in_rate">Input sampling rate (integer number of Hz).</param>
/// <param name="out_rate">Output sampling rate (integer number of Hz).</param>
/// <param name="quality">Resampling quality between 0 and 10, where 0 has poor quality and 10 has very high quality.</param>
/// <param name="err"></param>
/// <returns>Newly created resampler state</returns>
[DllImport("libspeexdsp.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern IntPtr speex_resampler_init(uint nb_channels, uint in_rate, uint out_rate, int quality, ref RESAMPLER_ERR err);
/// <summary>
/// Create a new resampler with fractional input/output rates. The sampling
/// rate ratio is an arbitrary rational number with both the numerator and
/// denominator being 32-bit integers.
/// </summary>
/// <param name="nb_channels">Number of channels to be processed</param>
/// <param name="ratio_num">Numerator of the sampling rate ratio</param>
/// <param name="ratio_den">Denominator of the sampling rate ratio</param>
/// <param name="in_rate">Input sampling rate rounded to the nearest integer (in Hz).</param>
/// <param name="out_rate">Output sampling rate rounded to the nearest integer (in Hz).</param>
/// <param name="quality">Resampling quality between 0 and 10, where 0 has poor quality and 10 has very high quality.</param>
/// <param name="err"></param>
/// <returns>Newly created resampler state</returns>
[DllImport("libspeexdsp.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern IntPtr speex_resampler_init_frac(uint nb_channels, uint ratio_num, uint ratio_den, uint in_rate, uint out_rate, int quality, ref RESAMPLER_ERR err);
/// <summary>
/// Destroy a resampler state.
/// </summary>
/// <param name="st">Resampler state</param>
[DllImport("libspeexdsp.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern void speex_resampler_destroy(IntPtr st);
/// <summary>
/// Resample a float array. The input and output buffers must *not* overlap.
/// </summary>
/// <param name="st">Resampler state</param>
/// <param name="channel_index">Index of the channel to process for the multi-channel base (0 otherwise)</param>
/// <param name="inp">Input buffer</param>
/// <param name="in_len">Number of input samples in the input buffer. Returns the number of samples processed</param>
/// <param name="outp">Output buffer</param>
/// <param name="out_len">Size of the output buffer. Returns the number of samples written</param>
/// <returns></returns>
[DllImport("libspeexdsp.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern RESAMPLER_ERR speex_resampler_process_float(IntPtr st, uint channel_index, float[] inp, ref uint in_len, float[] outp, ref uint out_len);
/// <summary>
/// Resample an int array. The input and output buffers must *not* overlap.
/// </summary>
/// <param name="st">Resampler state</param>
/// <param name="channel_index">Index of the channel to process for the multi-channel base (0 otherwise)</param>
/// <param name="inp">Input buffer</param>
/// <param name="in_len">Number of input samples in the input buffer. Returns the number of samples processed</param>
/// <param name="outp">Output buffer</param>
/// <param name="out_len">Size of the output buffer. Returns the number of samples written</param>
/// <returns></returns>
[DllImport("libspeexdsp.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern RESAMPLER_ERR speex_resampler_process_int(IntPtr st, uint channel_index, short[] inp, ref uint in_len, short[] outp, ref uint out_len);
/// <summary>
/// Resample an interleaved float array. The input and output buffers must *not* overlap.
/// </summary>
/// <param name="st">Resampler state</param>
/// <param name="inp">Input buffer</param>
/// <param name="in_len">Number of input samples in the input buffer. Returns the number of samples processed. This is all per-channel.</param>
/// <param name="outp">Output buffer</param>
/// <param name="out_len">Size of the output buffer. Returns the number of samples written. This is all per-channel.</param>
/// <returns></returns>
[DllImport("libspeexdsp.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern RESAMPLER_ERR speex_resampler_process_interleaved_float(IntPtr st, float[] inp, ref uint in_len, float[] outp, ref uint out_len);
/// <summary>
/// Resample an interleaved int array. The input and output buffers must *not* overlap.
/// </summary>
/// <param name="st">Resampler state</param>
/// <param name="inp">Input buffer</param>
/// <param name="in_len">Number of input samples in the input buffer. Returns the number of samples processed. This is all per-channel.</param>
/// <param name="outp">Output buffer</param>
/// <param name="out_len">Size of the output buffer. Returns the number of samples written. This is all per-channel.</param>
/// <returns></returns>
[DllImport("libspeexdsp.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern RESAMPLER_ERR speex_resampler_process_interleaved_int(IntPtr st, short[] inp, ref uint in_len, short[] outp, ref uint out_len);
/// <summary>
/// Set (change) the input/output sampling rates (integer value).
/// </summary>
/// <param name="st">Resampler state</param>
/// <param name="in_rate">Input sampling rate (integer number of Hz).</param>
/// <param name="out_rate">Output sampling rate (integer number of Hz).</param>
/// <returns></returns>
[DllImport("libspeexdsp.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern RESAMPLER_ERR speex_resampler_set_rate(IntPtr st, uint in_rate, uint out_rate);
/// <summary>
/// Get the current input/output sampling rates (integer value).
/// </summary>
/// <param name="st">Resampler state</param>
/// <param name="in_rate">Input sampling rate (integer number of Hz) copied.</param>
/// <param name="out_rate">Output sampling rate (integer number of Hz) copied.</param>
[DllImport("libspeexdsp.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern void speex_resampler_get_rate(IntPtr st, ref uint in_rate, ref uint out_rate);
/// <summary>
/// Set (change) the input/output sampling rates and resampling ratio (fractional values in Hz supported).
/// </summary>
/// <param name="st">esampler state</param>
/// <param name="ratio_num">Numerator of the sampling rate ratio</param>
/// <param name="ratio_den">Denominator of the sampling rate ratio</param>
/// <param name="in_rate">Input sampling rate rounded to the nearest integer (in Hz).</param>
/// <param name="out_rate">Output sampling rate rounded to the nearest integer (in Hz).</param>
/// <returns></returns>
[DllImport("libspeexdsp.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern RESAMPLER_ERR speex_resampler_set_rate_frac(IntPtr st, uint ratio_num, uint ratio_den, uint in_rate, uint out_rate);
/// <summary>
/// Get the current resampling ratio. This will be reduced to the least common denominator.
/// </summary>
/// <param name="st">Resampler state</param>
/// <param name="ratio_num">Numerator of the sampling rate ratio copied</param>
/// <param name="ratio_den">Denominator of the sampling rate ratio copied</param>
[DllImport("libspeexdsp.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern void speex_resampler_get_ratio(IntPtr st, ref uint ratio_num, ref uint ratio_den);
/// <summary>
/// Set (change) the conversion quality.
/// </summary>
/// <param name="st">Resampler state</param>
/// <param name="quality">Resampling quality between 0 and 10, where 0 has poor quality and 10 has very high quality.</param>
/// <returns></returns>
[DllImport("libspeexdsp.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern RESAMPLER_ERR speex_resampler_set_quality(IntPtr st, int quality);
/// <summary>
/// Get the conversion quality.
/// </summary>
/// <param name="st">Resampler state</param>
/// <param name="quality">Resampling quality between 0 and 10, where 0 has poor quality and 10 has very high quality.</param>
[DllImport("libspeexdsp.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern void speex_resampler_get_quality(IntPtr st, ref int quality);
/// <summary>
/// Set (change) the input stride.
/// </summary>
/// <param name="st">Resampler state</param>
/// <param name="stride">Input stride</param>
[DllImport("libspeexdsp.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern void speex_resampler_set_input_stride(IntPtr st, uint stride);
/// <summary>
/// Get the input stride.
/// </summary>
/// <param name="st">Resampler state</param>
/// <param name="stride">Input stride copied</param>
[DllImport("libspeexdsp.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern void speex_resampler_get_input_stride(IntPtr st, ref uint stride);
/// <summary>
/// Set (change) the output stride.
/// </summary>
/// <param name="st">Resampler state</param>
/// <param name="stride">Output stride</param>
[DllImport("libspeexdsp.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern void speex_resampler_set_output_stride(IntPtr st, uint stride);
/// <summary>
/// Get the output stride.
/// </summary>
/// <param name="st">Resampler state</param>
/// <param name="stride">Output stride copied</param>
[DllImport("libspeexdsp.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern void speex_resampler_get_output_stride(IntPtr st, ref uint stride);
/// <summary>
/// Get the latency in input samples introduced by the resampler.
/// </summary>
/// <param name="st">Resampler state</param>
/// <returns></returns>
[DllImport("libspeexdsp.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern int speex_resampler_get_input_latency(IntPtr st);
/// <summary>
/// Get the latency in output samples introduced by the resampler.
/// </summary>
/// <param name="st">Resampler state</param>
/// <returns></returns>
[DllImport("libspeexdsp.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern int speex_resampler_get_output_latency(IntPtr st);
/// <summary>
/// Make sure that the first samples to go out of the resamplers don't have
/// leading zeros. This is only useful before starting to use a newly created
/// resampler. It is recommended to use that when resampling an audio file, as
/// it will generate a file with the same length. For real-time processing,
/// it is probably easier not to use this call (so that the output duration
/// is the same for the first frame).
/// </summary>
/// <param name="st">Resampler state</param>
/// <returns></returns>
[DllImport("libspeexdsp.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern RESAMPLER_ERR speex_resampler_skip_zeroes(IntPtr st);
/// <summary>
/// Reset a resampler so a new (unrelated) stream can be processed.
/// </summary>
/// <param name="st">Resampler state</param>
/// <returns></returns>
[DllImport("libspeexdsp.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern RESAMPLER_ERR speex_resampler_reset_mem(IntPtr st);
/// <summary>
/// Returns the English meaning for an error code
/// </summary>
/// <param name="err">Error code</param>
/// <returns>English string</returns>
[DllImport("libspeexdsp.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern string speex_resampler_strerror(RESAMPLER_ERR err);
}
/// <summary>
/// opaque pointer to state
/// </summary>
IntPtr st;
public SpeexResampler(int quality)
{
LibSpeexDSP.RESAMPLER_ERR err = 0;
st = LibSpeexDSP.speex_resampler_init_frac(2, 64081, 88200, 32041, 44100, quality, ref err);
}
public void StartSession(double ratio)
{
}
public void ResampleChunk(Queue<short> input, Queue<short> output, bool finish)
{
while (input.Count > 0)
{
short[] ina = input.ToArray();
short[] outa = new short[8192];
uint inal = (uint)ina.Length / 2;
uint outal = (uint)outa.Length / 2;
// very important: feeding too big a buffer at once causes garbage to come back
// don't know what "too big a buffer" is in general
if (inal > 512) inal = 512;
LibSpeexDSP.speex_resampler_process_interleaved_int(st, ina, ref inal, outa, ref outal);
while (inal > 0)
{
input.Dequeue();
input.Dequeue();
inal--;
}
int i = 0;
if (outal == 0)
{
// resampler refuses to make more data; bail
return;
}
while (outal > 0)
{
output.Enqueue(outa[i++]);
output.Enqueue(outa[i++]);
outal--;
}
}
}
}
}

Binary file not shown.