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\Utilities\DualSound.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\Utilities\BufferedAsync.cs" />
<Compile Include="Sound\Utilities\Metaspu.cs" />

View File

@ -193,7 +193,7 @@ namespace BizHawk.Emulation.Consoles.Nintendo.SNES
string key = "P" + (1 + port) + " ";
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_B: key += "B"; break;
@ -262,11 +262,11 @@ namespace BizHawk.Emulation.Consoles.Nintendo.SNES
int IVideoProvider.BackgroundColor { get { return 0; } }
int[] IVideoProvider.GetVideoBuffer() { return vidBuffer; }
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[] vidBuffer = new int[256*256];
int vidWidth=256, vidHeight=256;
int[] vidBuffer = new int[256 * 256];
int vidWidth = 256, vidHeight = 256;
public IVideoProvider VideoProvider { 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",
}
};
int timeFrameCounter;
public int Frame { get { return timeFrameCounter; } }
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;
}
}
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)
{
@ -310,7 +310,7 @@ namespace BizHawk.Emulation.Consoles.Nintendo.SNES
if (size == 0) return new byte[0];
var data = LibsnesDll.snes_get_memory_data(id);
var ret = new byte[size];
Marshal.Copy(data,ret,0,size);
Marshal.Copy(data, ret, 0, size);
return ret;
}
@ -335,7 +335,7 @@ namespace BizHawk.Emulation.Consoles.Nintendo.SNES
state.ReadFromHex(hex);
LoadStateBinary(new BinaryReader(new MemoryStream(state)));
}
public void SaveStateBinary(BinaryWriter writer)
{
int size = LibsnesDll.snes_serialize_size();
@ -376,7 +376,7 @@ namespace BizHawk.Emulation.Consoles.Nintendo.SNES
int mask = size - 1;
byte* blockptr = (byte*)block.ToPointer();
MemoryDomain md;
//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))
{
@ -406,7 +406,7 @@ namespace BizHawk.Emulation.Consoles.Nintendo.SNES
void SetupMemoryDomains(byte[] romData)
{
MemoryDomains = new List<MemoryDomain>();
var romDomain = new MemoryDomain("CARTROM", romData.Length, Endian.Little,
(addr) => romData[addr],
(addr, value) => romData[addr] = value);
@ -423,88 +423,88 @@ namespace BizHawk.Emulation.Consoles.Nintendo.SNES
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;
/// <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)
{
AudioBuffer.Enqueue((short)left);
AudioBuffer.Enqueue((short)right);
}
AudioInBuffer.Enqueue((short)left);
AudioInBuffer.Enqueue((short)right);
/// <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++)
try
{
// exact position on input stream
double inpos = i * samplefactor;
// selected interpolation points and weights
int pt0 = (int)inpos; // pt1 = pt0 + 1
double wt1 = inpos - pt0; // wt0 = 1 - wt1
double wt0 = 1.0 - wt1;
// fixme: i get all sorts of crashes if i do the resampling in here. what?
output[i * 2 + 0] = (short)(input[pt0 * 2 + 0] * wt0 + input[pt0 * 2 + 2] * wt1);
output[i * 2 + 1] = (short)(input[pt0 * 2 + 1] * wt0 + input[pt0 * 2 + 3] * wt1);
/*
if (AudioInBuffer.Count >= resamplechunk)
{
resampler.process(sfactor, false, AudioInBuffer, AudioOutBuffer);
// drain into the metaspu immediately
// we could skip this step and drain directly by changing SampleBuffers
while (AudioOutBuffer.Count > 0)
metaspu.buffer.enqueue_sample(AudioOutBuffer.Dequeue(), AudioOutBuffer.Dequeue());
}
*/
}
catch (Exception e)
{
System.Windows.Forms.MessageBox.Show(e.ToString());
AudioOutBuffer.Clear();
}
}
// TODO: replace this with something that took more than 5 seconds to write
//BinaryWriter dbgs = new BinaryWriter(File.Open("dbgwav.raw", FileMode.Create, FileAccess.Write));
public void GetSamples(short[] samples)
{
// resample approximately 32k->44k
int inputcount = samples.Length * 32040 / 44100;
inputcount /= 2;
// do resampling here, instead of when the samples are generated; see "fixme" comment in snes_audio_sample()
if (inputcount < 2) inputcount = 2;
short[] input = new short[inputcount * 2];
int i;
for (i = 0; i < inputcount * 2 && AudioBuffer.Count > 0; i++)
input[i] = AudioBuffer.Dequeue();
short lastl, lastr;
if (i >= 2)
if (true)
{
lastl = input[i - 2];
lastr = input[i - 1];
resampler.process(sfactor, false, AudioInBuffer, AudioOutBuffer);
// drain into the metaspu immediately
// we could skip this step and drain directly by changing SampleBuffers implementation
while (AudioOutBuffer.Count > 0)
{
short l = AudioOutBuffer.Dequeue();
short r = AudioOutBuffer.Dequeue();
//metaspu.buffer.enqueue_sample(AudioOutBuffer.Dequeue(), AudioOutBuffer.Dequeue());
metaspu.buffer.enqueue_sample(l, r);
//dbgs.Write(l);
//dbgs.Write(r);
}
}
else
lastl = lastr = 0;
for (; i < inputcount * 2; )
{
input[i++] = lastl;
input[i++] = lastr;
}
LinearDownsampler(input, samples);
// drop if too many
if (AudioBuffer.Count > samples.Length * 3)
AudioBuffer.Clear();
metaspu.GetSamples(samples);
}
public void DiscardSamples()
{
AudioBuffer.Clear();
metaspu.DiscardSamples();
}
// ignore for now
public int MaxVolume
{
get;
set;
}
public int MaxVolume { get; set; }
#endregion audio stuff
}
}

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);
}
}