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:
parent
1a760096bc
commit
0430ec91c8
|
@ -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" />
|
||||
|
|
|
@ -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
|
||||
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue