add resampler based on libresample4j (LGPL)

hook that up to libsnes, so libsnes audio now goes through libresample4j and then metaspu
it sounds ok
This commit is contained in:
goyuken 2012-09-05 18:52:17 +00:00
parent 1a760096bc
commit 0430ec91c8
5 changed files with 1032 additions and 72 deletions

View File

@ -357,6 +357,9 @@
<Compile Include="Sound\CDAudio.cs" /> <Compile Include="Sound\CDAudio.cs" />
<Compile Include="Sound\Utilities\DualSound.cs" /> <Compile Include="Sound\Utilities\DualSound.cs" />
<Compile Include="Sound\Utilities\Equalizer.cs" /> <Compile Include="Sound\Utilities\Equalizer.cs" />
<Compile Include="Sound\Utilities\FilterKit.cs" />
<Compile Include="Sound\Utilities\Resampler.cs" />
<Compile Include="Sound\Utilities\SampleBuffers.cs" />
<Compile Include="Sound\VRC6.cs" /> <Compile Include="Sound\VRC6.cs" />
<Compile Include="Sound\Utilities\BufferedAsync.cs" /> <Compile Include="Sound\Utilities\BufferedAsync.cs" />
<Compile Include="Sound\Utilities\Metaspu.cs" /> <Compile Include="Sound\Utilities\Metaspu.cs" />

View File

@ -193,7 +193,7 @@ namespace BizHawk.Emulation.Consoles.Nintendo.SNES
string key = "P" + (1 + port) + " "; string key = "P" + (1 + port) + " ";
if ((LibsnesDll.SNES_DEVICE)device == LibsnesDll.SNES_DEVICE.JOYPAD) if ((LibsnesDll.SNES_DEVICE)device == LibsnesDll.SNES_DEVICE.JOYPAD)
{ {
switch((LibsnesDll.SNES_DEVICE_ID)id) switch ((LibsnesDll.SNES_DEVICE_ID)id)
{ {
case LibsnesDll.SNES_DEVICE_ID.JOYPAD_A: key += "A"; break; case LibsnesDll.SNES_DEVICE_ID.JOYPAD_A: key += "A"; break;
case LibsnesDll.SNES_DEVICE_ID.JOYPAD_B: key += "B"; break; case LibsnesDll.SNES_DEVICE_ID.JOYPAD_B: key += "B"; break;
@ -262,11 +262,11 @@ namespace BizHawk.Emulation.Consoles.Nintendo.SNES
int IVideoProvider.BackgroundColor { get { return 0; } } int IVideoProvider.BackgroundColor { get { return 0; } }
int[] IVideoProvider.GetVideoBuffer() { return vidBuffer; } int[] IVideoProvider.GetVideoBuffer() { return vidBuffer; }
int IVideoProvider.VirtualWidth { get { return vidWidth; } } int IVideoProvider.VirtualWidth { get { return vidWidth; } }
int IVideoProvider.BufferWidth { get { return vidWidth; } } int IVideoProvider.BufferWidth { get { return vidWidth; } }
int IVideoProvider.BufferHeight { get { return vidHeight; } } int IVideoProvider.BufferHeight { get { return vidHeight; } }
int[] vidBuffer = new int[256*256]; int[] vidBuffer = new int[256 * 256];
int vidWidth=256, vidHeight=256; int vidWidth = 256, vidHeight = 256;
public IVideoProvider VideoProvider { get { return this; } } public IVideoProvider VideoProvider { get { return this; } }
public ISoundProvider SoundProvider { get { return this; } } public ISoundProvider SoundProvider { get { return this; } }
@ -287,7 +287,7 @@ namespace BizHawk.Emulation.Consoles.Nintendo.SNES
"P1 Up", "P1 Down", "P1 Left", "P1 Right", "P1 Select", "P1 Start", "P1 B", "P1 A", "P1 X", "P1 Y", "P1 L", "P1 R", "Reset", "P1 Up", "P1 Down", "P1 Left", "P1 Right", "P1 Select", "P1 Start", "P1 B", "P1 A", "P1 X", "P1 Y", "P1 L", "P1 R", "Reset",
} }
}; };
int timeFrameCounter; int timeFrameCounter;
public int Frame { get { return timeFrameCounter; } } public int Frame { get { return timeFrameCounter; } }
public int LagCount { get; set; } public int LagCount { get; set; }
@ -302,7 +302,7 @@ namespace BizHawk.Emulation.Consoles.Nintendo.SNES
return LibsnesDll.snes_get_memory_size(LibsnesDll.SNES_MEMORY.CARTRIDGE_RAM) != 0; return LibsnesDll.snes_get_memory_size(LibsnesDll.SNES_MEMORY.CARTRIDGE_RAM) != 0;
} }
} }
public byte[] ReadSaveRam { get { return snes_get_memory_data_read(LibsnesDll.SNES_MEMORY.CARTRIDGE_RAM); } } public byte[] ReadSaveRam { get { return snes_get_memory_data_read(LibsnesDll.SNES_MEMORY.CARTRIDGE_RAM); } }
public static byte[] snes_get_memory_data_read(LibsnesDll.SNES_MEMORY id) public static byte[] snes_get_memory_data_read(LibsnesDll.SNES_MEMORY id)
{ {
@ -310,7 +310,7 @@ namespace BizHawk.Emulation.Consoles.Nintendo.SNES
if (size == 0) return new byte[0]; if (size == 0) return new byte[0];
var data = LibsnesDll.snes_get_memory_data(id); var data = LibsnesDll.snes_get_memory_data(id);
var ret = new byte[size]; var ret = new byte[size];
Marshal.Copy(data,ret,0,size); Marshal.Copy(data, ret, 0, size);
return ret; return ret;
} }
@ -335,7 +335,7 @@ namespace BizHawk.Emulation.Consoles.Nintendo.SNES
state.ReadFromHex(hex); state.ReadFromHex(hex);
LoadStateBinary(new BinaryReader(new MemoryStream(state))); LoadStateBinary(new BinaryReader(new MemoryStream(state)));
} }
public void SaveStateBinary(BinaryWriter writer) public void SaveStateBinary(BinaryWriter writer)
{ {
int size = LibsnesDll.snes_serialize_size(); int size = LibsnesDll.snes_serialize_size();
@ -376,7 +376,7 @@ namespace BizHawk.Emulation.Consoles.Nintendo.SNES
int mask = size - 1; int mask = size - 1;
byte* blockptr = (byte*)block.ToPointer(); byte* blockptr = (byte*)block.ToPointer();
MemoryDomain md; MemoryDomain md;
//have to bitmask these somehow because it's unmanaged memory and we would hate to clobber things or make them nondeterministic //have to bitmask these somehow because it's unmanaged memory and we would hate to clobber things or make them nondeterministic
if (Util.IsPowerOfTwo(size)) if (Util.IsPowerOfTwo(size))
{ {
@ -406,7 +406,7 @@ namespace BizHawk.Emulation.Consoles.Nintendo.SNES
void SetupMemoryDomains(byte[] romData) void SetupMemoryDomains(byte[] romData)
{ {
MemoryDomains = new List<MemoryDomain>(); MemoryDomains = new List<MemoryDomain>();
var romDomain = new MemoryDomain("CARTROM", romData.Length, Endian.Little, var romDomain = new MemoryDomain("CARTROM", romData.Length, Endian.Little,
(addr) => romData[addr], (addr) => romData[addr],
(addr, value) => romData[addr] = value); (addr, value) => romData[addr] = value);
@ -423,88 +423,88 @@ namespace BizHawk.Emulation.Consoles.Nintendo.SNES
public MemoryDomain MainMemory { get; private set; } public MemoryDomain MainMemory { get; private set; }
Queue<short> AudioBuffer = new Queue<short>();
#region audio stuff
/// <summary>stores input samples as they come from the libsnes core</summary>
Queue<short> AudioInBuffer = new Queue<short>();
/// <summary>stores samples that have been converted to 44100hz</summary>
Queue<short> AudioOutBuffer = new Queue<short>();
GCHandle _gc_snes_audio_sample; GCHandle _gc_snes_audio_sample;
/// <summary>total number of samples (left and right combined) in the InBuffer before we ask for resampling</summary>
const int resamplechunk = 1000;
/// <summary>actual sampling factor used</summary>
const double sfactor = 44100.0 / 32040.5;
Sound.Utilities.BizhawkResampler resampler = new Sound.Utilities.BizhawkResampler(false, sfactor, sfactor);
Sound.MetaspuSoundProvider metaspu = new Sound.MetaspuSoundProvider(Sound.ESynchMethod.ESynchMethod_Z);
void snes_audio_sample(ushort left, ushort right) void snes_audio_sample(ushort left, ushort right)
{ {
AudioBuffer.Enqueue((short)left); AudioInBuffer.Enqueue((short)left);
AudioBuffer.Enqueue((short)right); AudioInBuffer.Enqueue((short)right);
}
try
/// <summary>
/// basic linear audio resampler. sampling rate is inferred from buffer sizes
/// </summary>
/// <param name="input">stereo s16</param>
/// <param name="output">stereo s16</param>
static void LinearDownsampler(short[] input, short[] output)
{
// TODO - this also appears in YM2612.cs ... move to common if it's found useful
double samplefactor = (input.Length - 2) / (double)output.Length;
for (int i = 0; i < output.Length / 2; i++)
{ {
// exact position on input stream // fixme: i get all sorts of crashes if i do the resampling in here. what?
double inpos = i * samplefactor;
// selected interpolation points and weights
int pt0 = (int)inpos; // pt1 = pt0 + 1
double wt1 = inpos - pt0; // wt0 = 1 - wt1
double wt0 = 1.0 - wt1;
output[i * 2 + 0] = (short)(input[pt0 * 2 + 0] * wt0 + input[pt0 * 2 + 2] * wt1); /*
output[i * 2 + 1] = (short)(input[pt0 * 2 + 1] * wt0 + input[pt0 * 2 + 3] * wt1); if (AudioInBuffer.Count >= resamplechunk)
{
resampler.process(sfactor, false, AudioInBuffer, AudioOutBuffer);
// drain into the metaspu immediately
// we could skip this step and drain directly by changing SampleBuffers
while (AudioOutBuffer.Count > 0)
metaspu.buffer.enqueue_sample(AudioOutBuffer.Dequeue(), AudioOutBuffer.Dequeue());
}
*/
} }
catch (Exception e)
{
System.Windows.Forms.MessageBox.Show(e.ToString());
AudioOutBuffer.Clear();
}
} }
// TODO: replace this with something that took more than 5 seconds to write
//BinaryWriter dbgs = new BinaryWriter(File.Open("dbgwav.raw", FileMode.Create, FileAccess.Write));
public void GetSamples(short[] samples) public void GetSamples(short[] samples)
{ {
// resample approximately 32k->44k // do resampling here, instead of when the samples are generated; see "fixme" comment in snes_audio_sample()
int inputcount = samples.Length * 32040 / 44100;
inputcount /= 2;
if (inputcount < 2) inputcount = 2; if (true)
short[] input = new short[inputcount * 2];
int i;
for (i = 0; i < inputcount * 2 && AudioBuffer.Count > 0; i++)
input[i] = AudioBuffer.Dequeue();
short lastl, lastr;
if (i >= 2)
{ {
lastl = input[i - 2]; resampler.process(sfactor, false, AudioInBuffer, AudioOutBuffer);
lastr = input[i - 1];
// drain into the metaspu immediately
// we could skip this step and drain directly by changing SampleBuffers implementation
while (AudioOutBuffer.Count > 0)
{
short l = AudioOutBuffer.Dequeue();
short r = AudioOutBuffer.Dequeue();
//metaspu.buffer.enqueue_sample(AudioOutBuffer.Dequeue(), AudioOutBuffer.Dequeue());
metaspu.buffer.enqueue_sample(l, r);
//dbgs.Write(l);
//dbgs.Write(r);
}
} }
else metaspu.GetSamples(samples);
lastl = lastr = 0;
for (; i < inputcount * 2; )
{
input[i++] = lastl;
input[i++] = lastr;
}
LinearDownsampler(input, samples);
// drop if too many
if (AudioBuffer.Count > samples.Length * 3)
AudioBuffer.Clear();
} }
public void DiscardSamples() public void DiscardSamples()
{ {
AudioBuffer.Clear(); metaspu.DiscardSamples();
} }
// ignore for now public int MaxVolume { get; set; }
public int MaxVolume
{ #endregion audio stuff
get;
set;
}
} }
} }

View File

@ -0,0 +1,263 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace BizHawk.Emulation.Sound.Utilities
{
/******************************************************************************
*
* libresample4j
* Copyright (c) 2009 Laszlo Systems, Inc. All Rights Reserved.
*
* libresample4j is a Java port of Dominic Mazzoni's libresample 0.1.3,
* which is in turn based on Julius Smith's Resample 1.7 library.
* http://www-ccrma.stanford.edu/~jos/resample/
*
* License: LGPL -- see the file LICENSE.txt for more information
*
*****************************************************************************/
/**
* This file provides Kaiser-windowed low-pass filter support,
* including a function to create the filter coefficients, and
* two functions to apply the filter at a particular point.
*
* <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

@ -0,0 +1,644 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace BizHawk.Emulation.Sound.Utilities
{
/******************************************************************************
*
* libresample4j
* Copyright (c) 2009 Laszlo Systems, Inc. All Rights Reserved.
*
* libresample4j is a Java port of Dominic Mazzoni's libresample 0.1.3,
* which is in turn based on Julius Smith's Resample 1.7 library.
* http://www-ccrma.stanford.edu/~jos/resample/
*
* License: LGPL -- see the file LICENSE.txt for more information
*
*****************************************************************************/
//import java.nio.FloatBuffer;
public class Resampler
{
public class Result
{
public int inputSamplesConsumed { get; private set; }
public int outputSamplesGenerated { get; private set; }
public Result(int inputSamplesConsumed, int outputSamplesGenerated)
{
this.inputSamplesConsumed = inputSamplesConsumed;
this.outputSamplesGenerated = outputSamplesGenerated;
}
}
/// <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
{
private Resampler left;
private Resampler right;
public BizhawkResampler(bool highQuality, double minFactor, double maxFactor)
{
left = new Resampler(highQuality, minFactor, maxFactor);
right = new Resampler(highQuality, minFactor, maxFactor);
}
/// <summary>
/// resample some audio.
/// </summary>
/// <param name="factor">outrate / inrate</param>
/// <param name="lastBatch">true to finalize (empty buffers and finish)</param>
/// <param name="input">input samples, as interleaved stereo shorts. in general, all won't be used unless <code>lastbatch = true</code></param>
/// <param name="output">recieves output samples, as interleaved stereo shorts</param>
/// <returns>true if processing finished; only possible with <code>lastbatch = true</code></returns>
public bool process(double factor, bool lastBatch, Queue<short> input, Queue<short> output)
{
LeftBuffer lb = new LeftBuffer(input, output);
SampleBuffers rb = lb.GetRightBuffer();
bool doneleft = left.process(factor, lb, lastBatch);
bool doneright = right.process(factor, rb, lastBatch);
return doneleft && doneright;
}
/// <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

@ -0,0 +1,50 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace BizHawk.Emulation.Sound.Utilities
{
/******************************************************************************
*
* libresample4j
* Copyright (c) 2009 Laszlo Systems, Inc. All Rights Reserved.
*
* libresample4j is a Java port of Dominic Mazzoni's libresample 0.1.3,
* which is in turn based on Julius Smith's Resample 1.7 library.
* http://www-ccrma.stanford.edu/~jos/resample/
*
* License: LGPL -- see the file LICENSE.txt for more information
*
*****************************************************************************/
/// <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);
}
}