using System;
using System.Runtime.InteropServices;
namespace BizHawk.Emulation.Common
{
///
/// junk wrapper around LibSpeexDSP. quite inefficient. will be replaced
///
public class SpeexResampler : IDisposable, ISyncSoundProvider
{
static class LibSpeexDSP
{
public const int QUALITY_MAX = 10;
public const int QUALITY_MIN = 0;
public const int QUALITY_DEFAULT = 4;
public const int QUALITY_VOIP = 3;
public const int QUALITY_DESKTOP = 5;
public enum RESAMPLER_ERR
{
SUCCESS = 0,
ALLOC_FAILED = 1,
BAD_STATE = 2,
INVALID_ARG = 3,
PTR_OVERLAP = 4,
MAX_ERROR
};
///
/// Create a new resampler with integer input and output rates.
///
/// Number of channels to be processed
/// Input sampling rate (integer number of Hz).
/// Output sampling rate (integer number of Hz).
/// Resampling quality between 0 and 10, where 0 has poor quality and 10 has very high quality.
///
/// Newly created resampler state
[DllImport("libspeexdsp.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern IntPtr speex_resampler_init(uint nb_channels, uint in_rate, uint out_rate, int quality, ref RESAMPLER_ERR err);
///
/// Create a new resampler with fractional input/output rates. The sampling
/// rate ratio is an arbitrary rational number with both the numerator and
/// denominator being 32-bit integers.
///
/// Number of channels to be processed
/// Numerator of the sampling rate ratio
/// Denominator of the sampling rate ratio
/// Input sampling rate rounded to the nearest integer (in Hz).
/// Output sampling rate rounded to the nearest integer (in Hz).
/// Resampling quality between 0 and 10, where 0 has poor quality and 10 has very high quality.
///
/// Newly created resampler state
[DllImport("libspeexdsp.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern IntPtr speex_resampler_init_frac(uint nb_channels, uint ratio_num, uint ratio_den, uint in_rate, uint out_rate, int quality, ref RESAMPLER_ERR err);
///
/// Destroy a resampler state.
///
/// Resampler state
[DllImport("libspeexdsp.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern void speex_resampler_destroy(IntPtr st);
///
/// Resample a float array. The input and output buffers must *not* overlap.
///
/// Resampler state
/// Index of the channel to process for the multi-channel base (0 otherwise)
/// Input buffer
/// Number of input samples in the input buffer. Returns the number of samples processed
/// Output buffer
/// Size of the output buffer. Returns the number of samples written
///
[DllImport("libspeexdsp.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern RESAMPLER_ERR speex_resampler_process_float(IntPtr st, uint channel_index, float[] inp, ref uint in_len, float[] outp, ref uint out_len);
///
/// Resample an int array. The input and output buffers must *not* overlap.
///
/// Resampler state
/// Index of the channel to process for the multi-channel base (0 otherwise)
/// Input buffer
/// Number of input samples in the input buffer. Returns the number of samples processed
/// Output buffer
/// Size of the output buffer. Returns the number of samples written
///
[DllImport("libspeexdsp.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern RESAMPLER_ERR speex_resampler_process_int(IntPtr st, uint channel_index, short[] inp, ref uint in_len, short[] outp, ref uint out_len);
///
/// Resample an interleaved float array. The input and output buffers must *not* overlap.
///
/// Resampler state
/// Input buffer
/// Number of input samples in the input buffer. Returns the number of samples processed. This is all per-channel.
/// Output buffer
/// Size of the output buffer. Returns the number of samples written. This is all per-channel.
///
[DllImport("libspeexdsp.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern RESAMPLER_ERR speex_resampler_process_interleaved_float(IntPtr st, float[] inp, ref uint in_len, float[] outp, ref uint out_len);
///
/// Resample an interleaved int array. The input and output buffers must *not* overlap.
///
/// Resampler state
/// Input buffer
/// Number of input samples in the input buffer. Returns the number of samples processed. This is all per-channel.
/// Output buffer
/// Size of the output buffer. Returns the number of samples written. This is all per-channel.
///
[DllImport("libspeexdsp.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern RESAMPLER_ERR speex_resampler_process_interleaved_int(IntPtr st, short[] inp, ref uint in_len, short[] outp, ref uint out_len);
///
/// Set (change) the input/output sampling rates (integer value).
///
/// Resampler state
/// Input sampling rate (integer number of Hz).
/// Output sampling rate (integer number of Hz).
///
[DllImport("libspeexdsp.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern RESAMPLER_ERR speex_resampler_set_rate(IntPtr st, uint in_rate, uint out_rate);
///
/// Get the current input/output sampling rates (integer value).
///
/// Resampler state
/// Input sampling rate (integer number of Hz) copied.
/// Output sampling rate (integer number of Hz) copied.
[DllImport("libspeexdsp.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern void speex_resampler_get_rate(IntPtr st, ref uint in_rate, ref uint out_rate);
///
/// Set (change) the input/output sampling rates and resampling ratio (fractional values in Hz supported).
///
/// esampler state
/// Numerator of the sampling rate ratio
/// Denominator of the sampling rate ratio
/// Input sampling rate rounded to the nearest integer (in Hz).
/// Output sampling rate rounded to the nearest integer (in Hz).
///
[DllImport("libspeexdsp.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern RESAMPLER_ERR speex_resampler_set_rate_frac(IntPtr st, uint ratio_num, uint ratio_den, uint in_rate, uint out_rate);
///
/// Get the current resampling ratio. This will be reduced to the least common denominator.
///
/// Resampler state
/// Numerator of the sampling rate ratio copied
/// Denominator of the sampling rate ratio copied
[DllImport("libspeexdsp.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern void speex_resampler_get_ratio(IntPtr st, ref uint ratio_num, ref uint ratio_den);
///
/// Set (change) the conversion quality.
///
/// Resampler state
/// Resampling quality between 0 and 10, where 0 has poor quality and 10 has very high quality.
///
[DllImport("libspeexdsp.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern RESAMPLER_ERR speex_resampler_set_quality(IntPtr st, int quality);
///
/// Get the conversion quality.
///
/// Resampler state
/// Resampling quality between 0 and 10, where 0 has poor quality and 10 has very high quality.
[DllImport("libspeexdsp.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern void speex_resampler_get_quality(IntPtr st, ref int quality);
///
/// Set (change) the input stride.
///
/// Resampler state
/// Input stride
[DllImport("libspeexdsp.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern void speex_resampler_set_input_stride(IntPtr st, uint stride);
///
/// Get the input stride.
///
/// Resampler state
/// Input stride copied
[DllImport("libspeexdsp.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern void speex_resampler_get_input_stride(IntPtr st, ref uint stride);
///
/// Set (change) the output stride.
///
/// Resampler state
/// Output stride
[DllImport("libspeexdsp.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern void speex_resampler_set_output_stride(IntPtr st, uint stride);
///
/// Get the output stride.
///
/// Resampler state
/// Output stride copied
[DllImport("libspeexdsp.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern void speex_resampler_get_output_stride(IntPtr st, ref uint stride);
/*these two functions don't exist in our version of the dll
///
/// Get the latency in input samples introduced by the resampler.
///
/// Resampler state
///
[DllImport("libspeexdsp.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern int speex_resampler_get_input_latency(IntPtr st);
///
/// Get the latency in output samples introduced by the resampler.
///
/// Resampler state
///
[DllImport("libspeexdsp.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern int speex_resampler_get_output_latency(IntPtr st);
*/
///
/// Make sure that the first samples to go out of the resamplers don't have
/// leading zeros. This is only useful before starting to use a newly created
/// resampler. It is recommended to use that when resampling an audio file, as
/// it will generate a file with the same length. For real-time processing,
/// it is probably easier not to use this call (so that the output duration
/// is the same for the first frame).
///
/// Resampler state
///
[DllImport("libspeexdsp.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern RESAMPLER_ERR speex_resampler_skip_zeroes(IntPtr st);
///
/// Reset a resampler so a new (unrelated) stream can be processed.
///
/// Resampler state
///
[DllImport("libspeexdsp.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern RESAMPLER_ERR speex_resampler_reset_mem(IntPtr st);
///
/// Returns the English meaning for an error code
///
/// Error code
/// English string
[DllImport("libspeexdsp.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern string speex_resampler_strerror(RESAMPLER_ERR err);
}
///
/// opaque pointer to state
///
private IntPtr st = IntPtr.Zero;
///
/// function to call to dispatch output
///
private readonly Action drainer;
// TODO: this size is roughly based on how big you can make the buffer before the snes resampling (32040.5 -> 44100) gets screwed up
private short[] inbuf = new short[512]; //[8192]; // [512];
private short[] outbuf;
// for ISyncSoundProvider
private short[] outbuf2 = new short[16];
private int outbuf2pos = 0;
// to accept an ISyncSoundProvder input
private readonly ISyncSoundProvider input;
///
/// in buffer position in samples (not sample pairs)
///
private int inbufpos = 0;
///
/// throw an exception based on error state
///
///
static void CheckError(LibSpeexDSP.RESAMPLER_ERR e)
{
switch (e)
{
case LibSpeexDSP.RESAMPLER_ERR.SUCCESS:
return;
case LibSpeexDSP.RESAMPLER_ERR.ALLOC_FAILED:
throw new InsufficientMemoryException("LibSpeexDSP: Alloc failed");
case LibSpeexDSP.RESAMPLER_ERR.BAD_STATE:
throw new Exception("LibSpeexDSP: Bad state");
case LibSpeexDSP.RESAMPLER_ERR.INVALID_ARG:
throw new ArgumentException("LibSpeexDSP: Bad Argument");
case LibSpeexDSP.RESAMPLER_ERR.PTR_OVERLAP:
throw new Exception("LibSpeexDSP: Buffers cannot overlap");
}
}
///
///
///
/// 0 to 10
/// numerator of srate change ratio (inrate / outrate)
/// demonenator of srate change ratio (inrate / outrate)
/// sampling rate in, rounded to nearest hz
/// sampling rate out, rounded to nearest hz
/// function which accepts output as produced. if null, act as an ISyncSoundProvider
/// source to take input from when output is requested. if null, no autofetching
public SpeexResampler(int quality, uint rationum, uint ratioden, uint sratein, uint srateout, Action drainer = null, ISyncSoundProvider input = null)
{
if (drainer != null && input != null)
throw new ArgumentException("Can't autofetch without being an ISyncSoundProvider?");
LibSpeexDSP.RESAMPLER_ERR err = LibSpeexDSP.RESAMPLER_ERR.SUCCESS;
st = LibSpeexDSP.speex_resampler_init_frac(2, rationum, ratioden, sratein, srateout, quality, ref err);
if (st == IntPtr.Zero)
throw new Exception("LibSpeexDSP returned null!");
CheckError(err);
this.drainer = drainer ?? InternalDrain;
this.input = input;
outbuf = new short[inbuf.Length * ratioden / rationum / 2 * 2 + 128];
}
/// change sampling rate on the fly
/// numerator of srate change ratio (inrate / outrate)
/// demonenator of srate change ratio (inrate / outrate)
/// sampling rate in, rounded to nearest hz
/// sampling rate out, rounded to nearest hz
public void ChangeRate(uint rationum, uint ratioden, uint sratein, uint srateout)
{
CheckError(LibSpeexDSP.speex_resampler_set_rate_frac(st, rationum, ratioden, sratein, srateout));
outbuf = new short[inbuf.Length * ratioden / rationum / 2 * 2 + 128];
}
///
/// add a sample to the queue
///
///
///
public void EnqueueSample(short left, short right)
{
inbuf[inbufpos++] = left;
inbuf[inbufpos++] = right;
if (inbufpos == inbuf.Length)
Flush();
}
///
/// add multiple samples to the queue
///
/// interleaved stereo samples
/// number of sample pairs
public void EnqueueSamples(short[] userbuf, int nsamp)
{
int numused = 0;
while (numused < nsamp)
{
int shortstocopy = Math.Min(inbuf.Length - inbufpos, (nsamp - numused) * 2);
Buffer.BlockCopy(userbuf, numused * 2 * sizeof(short), inbuf, inbufpos * sizeof(short), shortstocopy * sizeof(short));
inbufpos += shortstocopy;
numused += shortstocopy / 2;
if (inbufpos == inbuf.Length)
Flush();
}
}
///
/// flush as many input samples as possible, generating output samples right now
///
public void Flush()
{
uint inal = (uint)inbufpos / 2;
uint outal = (uint)outbuf.Length / 2;
LibSpeexDSP.speex_resampler_process_interleaved_int(st, inbuf, ref inal, outbuf, ref outal);
// reset inbuf
if (inal != inbufpos / 2)
throw new Exception("Speexresampler didn't eat the whole array?");
inbufpos = 0;
//Buffer.BlockCopy(inbuf, (int)inal * 2 * sizeof(short), inbuf, 0, inbufpos - (int)inal * 2);
//inbufpos -= (int)inal * 2;
// dispatch outbuf
drainer(outbuf, (int)outal);
}
public void Dispose()
{
LibSpeexDSP.speex_resampler_destroy(st);
st = IntPtr.Zero;
}
void InternalDrain(short[] buf, int nsamp)
{
if (outbuf2pos + nsamp * 2 > outbuf2.Length)
{
short[] newbuf = new short[outbuf2pos + nsamp * 2];
Buffer.BlockCopy(outbuf2, 0, newbuf, 0, outbuf2pos * sizeof(short));
outbuf2 = newbuf;
}
Buffer.BlockCopy(buf, 0, outbuf2, outbuf2pos * sizeof(short), nsamp * 2 * sizeof(short));
outbuf2pos += nsamp * 2;
}
public void GetSamples(out short[] samples, out int nsamp)
{
if (input != null)
{
short[] sampin;
int nsampin;
input.GetSamples(out sampin, out nsampin);
EnqueueSamples(sampin, nsampin);
}
Flush();
nsamp = outbuf2pos / 2;
samples = outbuf2;
outbuf2pos = 0;
}
public void DiscardSamples()
{
outbuf2pos = 0;
}
}
}