abstract resampling functionality into IStereoResampler for testing purposes
add two example resamplers that implement this: LinearResampler and CubicResampler. both are very basic and sound like shit compared to libresample4j ultimately, a single good resampler with a better license than libresample4j should be selected and used
This commit is contained in:
parent
0430ec91c8
commit
b0e3a332d8
|
@ -355,9 +355,12 @@
|
||||||
<Compile Include="Properties\svnrev.cs" />
|
<Compile Include="Properties\svnrev.cs" />
|
||||||
<Compile Include="QuickCollections.cs" />
|
<Compile Include="QuickCollections.cs" />
|
||||||
<Compile Include="Sound\CDAudio.cs" />
|
<Compile Include="Sound\CDAudio.cs" />
|
||||||
|
<Compile Include="Sound\Utilities\CubicResampler.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\FilterKit.cs" />
|
||||||
|
<Compile Include="Sound\Utilities\IStereoResampler.cs" />
|
||||||
|
<Compile Include="Sound\Utilities\LinearResampler.cs" />
|
||||||
<Compile Include="Sound\Utilities\Resampler.cs" />
|
<Compile Include="Sound\Utilities\Resampler.cs" />
|
||||||
<Compile Include="Sound\Utilities\SampleBuffers.cs" />
|
<Compile Include="Sound\Utilities\SampleBuffers.cs" />
|
||||||
<Compile Include="Sound\VRC6.cs" />
|
<Compile Include="Sound\VRC6.cs" />
|
||||||
|
|
|
@ -173,6 +173,9 @@ namespace BizHawk.Emulation.Consoles.Nintendo.SNES
|
||||||
_gc_snes_audio_sample = GCHandle.Alloc(soundcb);
|
_gc_snes_audio_sample = GCHandle.Alloc(soundcb);
|
||||||
BizHawk.Emulation.Consoles.Nintendo.SNES.LibsnesDll.snes_set_audio_sample(soundcb);
|
BizHawk.Emulation.Consoles.Nintendo.SNES.LibsnesDll.snes_set_audio_sample(soundcb);
|
||||||
|
|
||||||
|
// start up audio resampler
|
||||||
|
resampler.StartSession(resamplingfactor);
|
||||||
|
|
||||||
//strip header
|
//strip header
|
||||||
if ((romData.Length & 0x7FFF) == 512)
|
if ((romData.Length & 0x7FFF) == 512)
|
||||||
{
|
{
|
||||||
|
@ -437,38 +440,44 @@ namespace BizHawk.Emulation.Consoles.Nintendo.SNES
|
||||||
/// <summary>total number of samples (left and right combined) in the InBuffer before we ask for resampling</summary>
|
/// <summary>total number of samples (left and right combined) in the InBuffer before we ask for resampling</summary>
|
||||||
const int resamplechunk = 1000;
|
const int resamplechunk = 1000;
|
||||||
/// <summary>actual sampling factor used</summary>
|
/// <summary>actual sampling factor used</summary>
|
||||||
const double sfactor = 44100.0 / 32040.5;
|
const double resamplingfactor = 44100.0 / 32040.5;
|
||||||
|
|
||||||
Sound.Utilities.BizhawkResampler resampler = new Sound.Utilities.BizhawkResampler(false, sfactor, sfactor);
|
Sound.Utilities.IStereoResampler resampler = new Sound.Utilities.BizhawkResampler(false);
|
||||||
|
//Sound.Utilities.IStereoResampler resampler = new Sound.Utilities.CubicResampler();
|
||||||
|
//Sound.Utilities.IStereoResampler resampler = new Sound.Utilities.LinearResampler();
|
||||||
|
|
||||||
Sound.MetaspuSoundProvider metaspu = new Sound.MetaspuSoundProvider(Sound.ESynchMethod.ESynchMethod_Z);
|
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)
|
||||||
{
|
{
|
||||||
|
|
||||||
AudioInBuffer.Enqueue((short)left);
|
AudioInBuffer.Enqueue((short)left);
|
||||||
AudioInBuffer.Enqueue((short)right);
|
AudioInBuffer.Enqueue((short)right);
|
||||||
|
|
||||||
|
/*
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// fixme: i get all sorts of crashes if i do the resampling in here. what?
|
// fixme: i get all sorts of crashes if i do the resampling in here. what?
|
||||||
|
|
||||||
/*
|
|
||||||
if (AudioInBuffer.Count >= resamplechunk)
|
if (AudioInBuffer.Count >= resamplechunk)
|
||||||
{
|
{
|
||||||
resampler.process(sfactor, false, AudioInBuffer, AudioOutBuffer);
|
resampler.ResampleChunk(AudioInBuffer, AudioOutBuffer, false);
|
||||||
// drain into the metaspu immediately
|
// drain into the metaspu immediately
|
||||||
// we could skip this step and drain directly by changing SampleBuffers
|
// we could skip this step and drain directly by changing SampleBuffers
|
||||||
while (AudioOutBuffer.Count > 0)
|
while (AudioOutBuffer.Count > 0)
|
||||||
metaspu.buffer.enqueue_sample(AudioOutBuffer.Dequeue(), AudioOutBuffer.Dequeue());
|
metaspu.buffer.enqueue_sample(AudioOutBuffer.Dequeue(), AudioOutBuffer.Dequeue());
|
||||||
}
|
}
|
||||||
*/
|
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
System.Windows.Forms.MessageBox.Show(e.ToString());
|
System.Windows.Forms.MessageBox.Show(e.ToString());
|
||||||
AudioOutBuffer.Clear();
|
AudioOutBuffer.Clear();
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -480,7 +489,7 @@ namespace BizHawk.Emulation.Consoles.Nintendo.SNES
|
||||||
|
|
||||||
if (true)
|
if (true)
|
||||||
{
|
{
|
||||||
resampler.process(sfactor, false, AudioInBuffer, AudioOutBuffer);
|
resampler.ResampleChunk(AudioInBuffer, AudioOutBuffer, false);
|
||||||
|
|
||||||
// drain into the metaspu immediately
|
// drain into the metaspu immediately
|
||||||
// we could skip this step and drain directly by changing SampleBuffers implementation
|
// we could skip this step and drain directly by changing SampleBuffers implementation
|
||||||
|
|
|
@ -0,0 +1,87 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace BizHawk.Emulation.Sound.Utilities
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// a simple cubic interpolation resampler. no lowpass. original code
|
||||||
|
/// </summary>
|
||||||
|
public class CubicResampler : IStereoResampler
|
||||||
|
{
|
||||||
|
int[] data = new int[8];
|
||||||
|
|
||||||
|
double mu;
|
||||||
|
/// <summary>input rate / output rate</summary>
|
||||||
|
double ratio;
|
||||||
|
|
||||||
|
public CubicResampler()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public void StartSession(double ratio)
|
||||||
|
{
|
||||||
|
this.ratio = 1.0 / ratio;
|
||||||
|
mu = 0.0;
|
||||||
|
for (int i = 0; i < data.Length; i++)
|
||||||
|
data[i] = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ResampleChunk(Queue<short> input, Queue<short> output, bool finish)
|
||||||
|
{
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
while (mu >= 1.0 && input.Count >= 2)
|
||||||
|
{
|
||||||
|
mu -= 1.0;
|
||||||
|
for (int i = 0; i < 6; i++)
|
||||||
|
data[i] = data[i + 2];
|
||||||
|
data[6] = input.Dequeue();
|
||||||
|
data[7] = input.Dequeue();
|
||||||
|
}
|
||||||
|
if (mu >= 1.0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
double mu2 = mu * mu;
|
||||||
|
double mu3 = mu2 * mu2;
|
||||||
|
|
||||||
|
int l0 = data[6] - data[4] - data[0] + data[2];
|
||||||
|
int l1 = data[0] - data[2] - l0;
|
||||||
|
int l2 = data[4] - data[0];
|
||||||
|
int l3 = data[2];
|
||||||
|
|
||||||
|
int r0 = data[7] - data[5] - data[1] + data[3];
|
||||||
|
int r1 = data[1] - data[3] - r0;
|
||||||
|
int r2 = data[5] - data[1];
|
||||||
|
int r3 = data[3];
|
||||||
|
|
||||||
|
double ls = l0 * mu3 + l1 * mu2 + l2 * mu + l3;
|
||||||
|
double rs = r0 * mu3 + r1 * mu2 + r2 * mu + r3;
|
||||||
|
|
||||||
|
short l, r;
|
||||||
|
|
||||||
|
if (ls > 32767.0)
|
||||||
|
l = 32767;
|
||||||
|
else if (ls < -32768.0)
|
||||||
|
l = -32768;
|
||||||
|
else
|
||||||
|
l = (short)ls;
|
||||||
|
|
||||||
|
if (rs > 32767.0)
|
||||||
|
r = 32767;
|
||||||
|
else if (ls < -32768.0)
|
||||||
|
r = -32768;
|
||||||
|
else
|
||||||
|
r = (short)ls;
|
||||||
|
|
||||||
|
output.Enqueue(l);
|
||||||
|
output.Enqueue(r);
|
||||||
|
|
||||||
|
mu += ratio;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,27 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace BizHawk.Emulation.Sound.Utilities
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// describes an audio resampler that works with stereo streams of shorts (interleaved)
|
||||||
|
/// </summary>
|
||||||
|
public interface IStereoResampler
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// start a resampling session, with the given conversion rate
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="ratio">outrate / inrate</param>
|
||||||
|
void StartSession(double ratio);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// process any available input
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="input">input samples. all might not be consumed unless finish == true</param>
|
||||||
|
/// <param name="output">where to put output samples.</param>
|
||||||
|
/// <param name="finish">if true, consume all input and end session</param>
|
||||||
|
void ResampleChunk(Queue<short> input, Queue<short> output, bool finish);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,70 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace BizHawk.Emulation.Sound.Utilities
|
||||||
|
{
|
||||||
|
// a simple linear resampler
|
||||||
|
public class LinearResampler : IStereoResampler
|
||||||
|
{
|
||||||
|
short[] data = new short[4];
|
||||||
|
|
||||||
|
double mu;
|
||||||
|
/// <summary>input rate / output rate</summary>
|
||||||
|
double ratio;
|
||||||
|
|
||||||
|
public LinearResampler()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public void StartSession(double ratio)
|
||||||
|
{
|
||||||
|
this.ratio = 1.0 / ratio;
|
||||||
|
mu = 0.0;
|
||||||
|
for (int i = 0; i < data.Length; i++)
|
||||||
|
data[i] = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ResampleChunk(Queue<short> input, Queue<short> output, bool finish)
|
||||||
|
{
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
while (mu >= 1.0 && input.Count >= 2)
|
||||||
|
{
|
||||||
|
mu -= 1.0;
|
||||||
|
data[0] = data[2];
|
||||||
|
data[1] = data[3];
|
||||||
|
data[2] = input.Dequeue();
|
||||||
|
data[3] = input.Dequeue();
|
||||||
|
}
|
||||||
|
if (mu >= 1.0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
double ls = data[0] * (1.0 - mu) + data[1] * mu;
|
||||||
|
double rs = data[1] * (1.0 - mu) + data[3] * mu;
|
||||||
|
|
||||||
|
short l, r;
|
||||||
|
|
||||||
|
if (ls > 32767.0)
|
||||||
|
l = 32767;
|
||||||
|
else if (ls < -32768.0)
|
||||||
|
l = -32768;
|
||||||
|
else
|
||||||
|
l = (short)ls;
|
||||||
|
|
||||||
|
if (rs > 32767.0)
|
||||||
|
r = 32767;
|
||||||
|
else if (ls < -32768.0)
|
||||||
|
r = -32768;
|
||||||
|
else
|
||||||
|
r = (short)ls;
|
||||||
|
|
||||||
|
output.Enqueue(l);
|
||||||
|
output.Enqueue(r);
|
||||||
|
|
||||||
|
mu += ratio;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -507,37 +507,44 @@ namespace BizHawk.Emulation.Sound.Utilities
|
||||||
/// implements a pair of Resamplers that work off of interleaved stereo buffers of shorts
|
/// implements a pair of Resamplers that work off of interleaved stereo buffers of shorts
|
||||||
/// this is what's used inside most of bizhawk
|
/// this is what's used inside most of bizhawk
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class BizhawkResampler
|
public class BizhawkResampler : IStereoResampler
|
||||||
{
|
{
|
||||||
private Resampler left;
|
bool highQuality;
|
||||||
private Resampler right;
|
double ratio;
|
||||||
|
|
||||||
public BizhawkResampler(bool highQuality, double minFactor, double maxFactor)
|
public void StartSession(double ratio)
|
||||||
{
|
{
|
||||||
left = new Resampler(highQuality, minFactor, maxFactor);
|
this.ratio = ratio;
|
||||||
right = new Resampler(highQuality, minFactor, maxFactor);
|
left = new Resampler(highQuality, ratio, ratio);
|
||||||
|
right = new Resampler(highQuality, ratio, ratio);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void ResampleChunk(Queue<short> input, Queue<short> output, bool finish)
|
||||||
|
|
||||||
/// <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);
|
LeftBuffer lb = new LeftBuffer(input, output);
|
||||||
|
|
||||||
SampleBuffers rb = lb.GetRightBuffer();
|
SampleBuffers rb = lb.GetRightBuffer();
|
||||||
|
|
||||||
bool doneleft = left.process(factor, lb, lastBatch);
|
bool doneleft = left.process(ratio, lb, finish);
|
||||||
bool doneright = right.process(factor, rb, lastBatch);
|
bool doneright = right.process(ratio, rb, finish);
|
||||||
|
|
||||||
return doneleft && doneright;
|
if (finish)
|
||||||
|
{
|
||||||
|
left = null;
|
||||||
|
right = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Resampler left;
|
||||||
|
private Resampler right;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
///
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="highQuality">true for high quality resampling</param>
|
||||||
|
public BizhawkResampler(bool highQuality)
|
||||||
|
{
|
||||||
|
this.highQuality = highQuality;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -635,10 +642,7 @@ namespace BizHawk.Emulation.Sound.Utilities
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue