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:
parent
2a41b8eda7
commit
001b28c60e
|
@ -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" />
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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; }
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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.
Loading…
Reference in New Issue