diff --git a/src/BizHawk.Emulation.Common/Sound/Utilities/BlipBuffer.cs b/src/BizHawk.Emulation.Common/Sound/BlipBuffer.cs similarity index 97% rename from src/BizHawk.Emulation.Common/Sound/Utilities/BlipBuffer.cs rename to src/BizHawk.Emulation.Common/Sound/BlipBuffer.cs index 84aed3d122..00c1b571a9 100644 --- a/src/BizHawk.Emulation.Common/Sound/Utilities/BlipBuffer.cs +++ b/src/BizHawk.Emulation.Common/Sound/BlipBuffer.cs @@ -1,181 +1,181 @@ -using System; -using System.Runtime.InteropServices; - -// ReSharper disable StyleCop.SA1300 -// ReSharper disable InconsistentNaming -namespace BizHawk.Emulation.Common -{ - /// - /// wrapper around blargg's unmanaged blip_buf - /// - public sealed class BlipBuffer : IDisposable - { - // this is transitional only. if the band-limited synthesis idea works out, i'll - // make a managed MIT implementation - private static class BlipBufDll - { - /** Creates new buffer that can hold at most sample_count samples. Sets rates - so that there are blip_max_ratio clocks per sample. Returns pointer to new - buffer, or NULL if insufficient memory. */ - [DllImport("blip_buf", CallingConvention = CallingConvention.Cdecl)] - public static extern IntPtr blip_new(int sample_count); - - /** Sets approximate input clock rate and output sample rate. For every - clock_rate input clocks, approximately sample_rate samples are generated. */ - [DllImport("blip_buf", CallingConvention = CallingConvention.Cdecl)] - public static extern void blip_set_rates(IntPtr context, double clock_rate, double sample_rate); - - /** Maximum clock_rate/sample_rate ratio. For a given sample_rate, - clock_rate must not be greater than sample_rate*blip_max_ratio. */ - public const int BlipMaxRatio = 1 << 20; - - /** Clears entire buffer. Afterwards, blip_samples_avail() == 0. */ - [DllImport("blip_buf", CallingConvention = CallingConvention.Cdecl)] - public static extern void blip_clear(IntPtr context); - - /** Adds positive/negative delta into buffer at specified clock time. */ - [DllImport("blip_buf", CallingConvention = CallingConvention.Cdecl)] - public static extern void blip_add_delta(IntPtr context, uint clock_time, int delta); - - /** Same as blip_add_delta(), but uses faster, lower-quality synthesis. */ - [DllImport("blip_buf", CallingConvention = CallingConvention.Cdecl)] - public static extern void blip_add_delta_fast(IntPtr context, uint clock_time, int delta); - - /** Length of time frame, in clocks, needed to make sample_count additional - samples available. */ - [DllImport("blip_buf", CallingConvention = CallingConvention.Cdecl)] - public static extern int blip_clocks_needed(IntPtr context, int sample_count); - - /** Maximum number of samples that can be generated from one time frame. */ - public const int BlipMaxFrame = 4000; - - /** Makes input clocks before clock_duration available for reading as output - samples. Also begins new time frame at clock_duration, so that clock time 0 in - the new time frame specifies the same clock as clock_duration in the old time - frame specified. Deltas can have been added slightly past clock_duration (up to - however many clocks there are in two output samples). */ - [DllImport("blip_buf", CallingConvention = CallingConvention.Cdecl)] - public static extern void blip_end_frame(IntPtr context, uint clock_duration); - - /** Number of buffered samples available for reading. */ - [DllImport("blip_buf", CallingConvention = CallingConvention.Cdecl)] - public static extern int blip_samples_avail(IntPtr context); - - /** Reads and removes at most 'count' samples and writes them to 'out'. If - 'stereo' is true, writes output to every other element of 'out', allowing easy - interleaving of two buffers into a stereo sample stream. Outputs 16-bit signed - samples. Returns number of samples actually read. */ - [DllImport("blip_buf", CallingConvention = CallingConvention.Cdecl)] - public static extern int blip_read_samples(IntPtr context, short[] @out, int count, int stereo); - [DllImport("blip_buf", CallingConvention = CallingConvention.Cdecl)] - public static extern int blip_read_samples(IntPtr context, IntPtr @out, int count, int stereo); - - /** Frees buffer. No effect if NULL is passed. */ - [DllImport("blip_buf", CallingConvention = CallingConvention.Cdecl)] - public static extern void blip_delete(IntPtr context); - } - - private IntPtr _context; - - /// unmanaged call failed - public BlipBuffer(int sampleCount) - { - _context = BlipBufDll.blip_new(sampleCount); - if (_context == IntPtr.Zero) - { - throw new Exception("blip_new returned NULL!"); - } - } - - ~BlipBuffer() - { - Dispose(); - } - - public void Dispose() - { - if (_context != IntPtr.Zero) - { - BlipBufDll.blip_delete(_context); - _context = IntPtr.Zero; - GC.SuppressFinalize(this); - } - } - - public void SetRates(double clockRate, double sampleRate) - { - BlipBufDll.blip_set_rates(_context, clockRate, sampleRate); - } - - public const int MaxRatio = BlipBufDll.BlipMaxRatio; - - public void Clear() - { - BlipBufDll.blip_clear(_context); - } - - public void AddDelta(uint clockTime, int delta) - { - BlipBufDll.blip_add_delta(_context, clockTime, delta); - } - - public void AddDeltaFast(uint clockTime, int delta) - { - BlipBufDll.blip_add_delta_fast(_context, clockTime, delta); - } - - public int ClocksNeeded(int sampleCount) - { - return BlipBufDll.blip_clocks_needed(_context, sampleCount); - } - - public const int MaxFrame = BlipBufDll.BlipMaxFrame; - - public void EndFrame(uint clockDuration) - { - BlipBufDll.blip_end_frame(_context, clockDuration); - } - - public int SamplesAvailable() - { - return BlipBufDll.blip_samples_avail(_context); - } - - /// can't hold samples (or twice that if is ) - public int ReadSamples(short[] output, int count, bool stereo) - { - if (output.Length < count * (stereo ? 2 : 1)) - { - throw new ArgumentOutOfRangeException(); - } - - return BlipBufDll.blip_read_samples(_context, output, count, stereo ? 1 : 0); - } - - /// can't hold 2 * samples - public int ReadSamplesLeft(short[] output, int count) - { - if (output.Length < count * 2) - { - throw new ArgumentOutOfRangeException(); - } - - return BlipBufDll.blip_read_samples(_context, output, count, 1); - } - - /// can't hold 2 * samples - public int ReadSamplesRight(short[] output, int count) - { - if (output.Length < count * 2) - { - throw new ArgumentOutOfRangeException(); - } - - unsafe - { - fixed (short* s = &output[1]) - return BlipBufDll.blip_read_samples(_context, new IntPtr(s), count, 1); - } - } - } -} +using System; +using System.Runtime.InteropServices; + +// ReSharper disable StyleCop.SA1300 +// ReSharper disable InconsistentNaming +namespace BizHawk.Emulation.Common +{ + /// + /// wrapper around blargg's unmanaged blip_buf + /// + public sealed class BlipBuffer : IDisposable + { + // this is transitional only. if the band-limited synthesis idea works out, i'll + // make a managed MIT implementation + private static class BlipBufDll + { + /** Creates new buffer that can hold at most sample_count samples. Sets rates + so that there are blip_max_ratio clocks per sample. Returns pointer to new + buffer, or NULL if insufficient memory. */ + [DllImport("blip_buf", CallingConvention = CallingConvention.Cdecl)] + public static extern IntPtr blip_new(int sample_count); + + /** Sets approximate input clock rate and output sample rate. For every + clock_rate input clocks, approximately sample_rate samples are generated. */ + [DllImport("blip_buf", CallingConvention = CallingConvention.Cdecl)] + public static extern void blip_set_rates(IntPtr context, double clock_rate, double sample_rate); + + /** Maximum clock_rate/sample_rate ratio. For a given sample_rate, + clock_rate must not be greater than sample_rate*blip_max_ratio. */ + public const int BlipMaxRatio = 1 << 20; + + /** Clears entire buffer. Afterwards, blip_samples_avail() == 0. */ + [DllImport("blip_buf", CallingConvention = CallingConvention.Cdecl)] + public static extern void blip_clear(IntPtr context); + + /** Adds positive/negative delta into buffer at specified clock time. */ + [DllImport("blip_buf", CallingConvention = CallingConvention.Cdecl)] + public static extern void blip_add_delta(IntPtr context, uint clock_time, int delta); + + /** Same as blip_add_delta(), but uses faster, lower-quality synthesis. */ + [DllImport("blip_buf", CallingConvention = CallingConvention.Cdecl)] + public static extern void blip_add_delta_fast(IntPtr context, uint clock_time, int delta); + + /** Length of time frame, in clocks, needed to make sample_count additional + samples available. */ + [DllImport("blip_buf", CallingConvention = CallingConvention.Cdecl)] + public static extern int blip_clocks_needed(IntPtr context, int sample_count); + + /** Maximum number of samples that can be generated from one time frame. */ + public const int BlipMaxFrame = 4000; + + /** Makes input clocks before clock_duration available for reading as output + samples. Also begins new time frame at clock_duration, so that clock time 0 in + the new time frame specifies the same clock as clock_duration in the old time + frame specified. Deltas can have been added slightly past clock_duration (up to + however many clocks there are in two output samples). */ + [DllImport("blip_buf", CallingConvention = CallingConvention.Cdecl)] + public static extern void blip_end_frame(IntPtr context, uint clock_duration); + + /** Number of buffered samples available for reading. */ + [DllImport("blip_buf", CallingConvention = CallingConvention.Cdecl)] + public static extern int blip_samples_avail(IntPtr context); + + /** Reads and removes at most 'count' samples and writes them to 'out'. If + 'stereo' is true, writes output to every other element of 'out', allowing easy + interleaving of two buffers into a stereo sample stream. Outputs 16-bit signed + samples. Returns number of samples actually read. */ + [DllImport("blip_buf", CallingConvention = CallingConvention.Cdecl)] + public static extern int blip_read_samples(IntPtr context, short[] @out, int count, int stereo); + [DllImport("blip_buf", CallingConvention = CallingConvention.Cdecl)] + public static extern int blip_read_samples(IntPtr context, IntPtr @out, int count, int stereo); + + /** Frees buffer. No effect if NULL is passed. */ + [DllImport("blip_buf", CallingConvention = CallingConvention.Cdecl)] + public static extern void blip_delete(IntPtr context); + } + + private IntPtr _context; + + /// unmanaged call failed + public BlipBuffer(int sampleCount) + { + _context = BlipBufDll.blip_new(sampleCount); + if (_context == IntPtr.Zero) + { + throw new Exception("blip_new returned NULL!"); + } + } + + ~BlipBuffer() + { + Dispose(); + } + + public void Dispose() + { + if (_context != IntPtr.Zero) + { + BlipBufDll.blip_delete(_context); + _context = IntPtr.Zero; + GC.SuppressFinalize(this); + } + } + + public void SetRates(double clockRate, double sampleRate) + { + BlipBufDll.blip_set_rates(_context, clockRate, sampleRate); + } + + public const int MaxRatio = BlipBufDll.BlipMaxRatio; + + public void Clear() + { + BlipBufDll.blip_clear(_context); + } + + public void AddDelta(uint clockTime, int delta) + { + BlipBufDll.blip_add_delta(_context, clockTime, delta); + } + + public void AddDeltaFast(uint clockTime, int delta) + { + BlipBufDll.blip_add_delta_fast(_context, clockTime, delta); + } + + public int ClocksNeeded(int sampleCount) + { + return BlipBufDll.blip_clocks_needed(_context, sampleCount); + } + + public const int MaxFrame = BlipBufDll.BlipMaxFrame; + + public void EndFrame(uint clockDuration) + { + BlipBufDll.blip_end_frame(_context, clockDuration); + } + + public int SamplesAvailable() + { + return BlipBufDll.blip_samples_avail(_context); + } + + /// can't hold samples (or twice that if is ) + public int ReadSamples(short[] output, int count, bool stereo) + { + if (output.Length < count * (stereo ? 2 : 1)) + { + throw new ArgumentOutOfRangeException(); + } + + return BlipBufDll.blip_read_samples(_context, output, count, stereo ? 1 : 0); + } + + /// can't hold 2 * samples + public int ReadSamplesLeft(short[] output, int count) + { + if (output.Length < count * 2) + { + throw new ArgumentOutOfRangeException(); + } + + return BlipBufDll.blip_read_samples(_context, output, count, 1); + } + + /// can't hold 2 * samples + public int ReadSamplesRight(short[] output, int count) + { + if (output.Length < count * 2) + { + throw new ArgumentOutOfRangeException(); + } + + unsafe + { + fixed (short* s = &output[1]) + return BlipBufDll.blip_read_samples(_context, new IntPtr(s), count, 1); + } + } + } +} diff --git a/src/BizHawk.Emulation.Common/Sound/Utilities/DCFilter.cs b/src/BizHawk.Emulation.Common/Sound/DCFilter.cs similarity index 95% rename from src/BizHawk.Emulation.Common/Sound/Utilities/DCFilter.cs rename to src/BizHawk.Emulation.Common/Sound/DCFilter.cs index 4720764aba..3d4e7a9c36 100644 --- a/src/BizHawk.Emulation.Common/Sound/Utilities/DCFilter.cs +++ b/src/BizHawk.Emulation.Common/Sound/DCFilter.cs @@ -1,134 +1,134 @@ -using System; - -namespace BizHawk.Emulation.Common -{ - /// - /// implements a DC block filter on top of an ISoundProvider. rather simple. - /// - public sealed class DCFilter : ISoundProvider - { - private readonly ISoundProvider _soundProvider; - private readonly int _depth; - - private int _latchL; - private int _latchR; - private int _accumL; - private int _accumR; - - private static int DepthFromFilterWidth(int filterWidth) - { - int ret = -2; - while (filterWidth > 0) - { - filterWidth >>= 1; - ret++; - } - - return ret; - } - - /// is null - /// is not in 8..65536 - public DCFilter(ISoundProvider input, int filterWidth) - { - if (input == null) - { - throw new ArgumentNullException(); - } - - if (filterWidth < 8 || filterWidth > 65536) - { - throw new ArgumentOutOfRangeException(); - } - - _depth = DepthFromFilterWidth(filterWidth); - - _soundProvider = input; - } - - /// - /// pass a set of samples through the filter. should only be used in detached mode - /// - /// sample buffer to modify - /// number of samples (not pairs). stereo - public void PushThroughSamples(short[] samples, int length) - { - PushThroughSamples(samples, samples, length); - } - - private void PushThroughSamples(short[] samplesIn, short[] samplesOut, int length) - { - for (int i = 0; i < length; i += 2) - { - int l = samplesIn[i] << 12; - int r = samplesIn[i + 1] << 12; - _accumL -= _accumL >> _depth; - _accumR -= _accumR >> _depth; - _accumL += l - _latchL; - _accumR += r - _latchR; - _latchL = l; - _latchR = r; - - int bigL = _accumL >> 12; - int bigR = _accumR >> 12; - - // check for clipping - if (bigL > 32767) - { - samplesOut[i] = 32767; - } - else if (bigL < -32768) - { - samplesOut[i] = -32768; - } - else - { - samplesOut[i] = (short)bigL; - } - - if (bigR > 32767) - { - samplesOut[i + 1] = 32767; - } - else if (bigR < -32768) - { - samplesOut[i + 1] = -32768; - } - else - { - samplesOut[i + 1] = (short)bigR; - } - } - } - - public void GetSamplesAsync(short[] samples) - { - _soundProvider.GetSamplesAsync(samples); - PushThroughSamples(samples, samples.Length); - } - - public void DiscardSamples() - { - _soundProvider.DiscardSamples(); - } - - public void GetSamplesSync(out short[] samples, out int nsamp) - { - _soundProvider.GetSamplesSync(out var sampIn, out var nsampIn); - - short[] ret = new short[nsampIn * 2]; - PushThroughSamples(sampIn, ret, nsampIn * 2); - samples = ret; - nsamp = nsampIn; - } - - public SyncSoundMode SyncMode => _soundProvider.SyncMode; - - public bool CanProvideAsync => _soundProvider.CanProvideAsync; - - public void SetSyncMode(SyncSoundMode mode) - { - _soundProvider.SetSyncMode(mode); - } - } -} +using System; + +namespace BizHawk.Emulation.Common +{ + /// + /// implements a DC block filter on top of an ISoundProvider. rather simple. + /// + public sealed class DCFilter : ISoundProvider + { + private readonly ISoundProvider _soundProvider; + private readonly int _depth; + + private int _latchL; + private int _latchR; + private int _accumL; + private int _accumR; + + private static int DepthFromFilterWidth(int filterWidth) + { + int ret = -2; + while (filterWidth > 0) + { + filterWidth >>= 1; + ret++; + } + + return ret; + } + + /// is null + /// is not in 8..65536 + public DCFilter(ISoundProvider input, int filterWidth) + { + if (input == null) + { + throw new ArgumentNullException(); + } + + if (filterWidth < 8 || filterWidth > 65536) + { + throw new ArgumentOutOfRangeException(); + } + + _depth = DepthFromFilterWidth(filterWidth); + + _soundProvider = input; + } + + /// + /// pass a set of samples through the filter. should only be used in detached mode + /// + /// sample buffer to modify + /// number of samples (not pairs). stereo + public void PushThroughSamples(short[] samples, int length) + { + PushThroughSamples(samples, samples, length); + } + + private void PushThroughSamples(short[] samplesIn, short[] samplesOut, int length) + { + for (int i = 0; i < length; i += 2) + { + int l = samplesIn[i] << 12; + int r = samplesIn[i + 1] << 12; + _accumL -= _accumL >> _depth; + _accumR -= _accumR >> _depth; + _accumL += l - _latchL; + _accumR += r - _latchR; + _latchL = l; + _latchR = r; + + int bigL = _accumL >> 12; + int bigR = _accumR >> 12; + + // check for clipping + if (bigL > 32767) + { + samplesOut[i] = 32767; + } + else if (bigL < -32768) + { + samplesOut[i] = -32768; + } + else + { + samplesOut[i] = (short)bigL; + } + + if (bigR > 32767) + { + samplesOut[i + 1] = 32767; + } + else if (bigR < -32768) + { + samplesOut[i + 1] = -32768; + } + else + { + samplesOut[i + 1] = (short)bigR; + } + } + } + + public void GetSamplesAsync(short[] samples) + { + _soundProvider.GetSamplesAsync(samples); + PushThroughSamples(samples, samples.Length); + } + + public void DiscardSamples() + { + _soundProvider.DiscardSamples(); + } + + public void GetSamplesSync(out short[] samples, out int nsamp) + { + _soundProvider.GetSamplesSync(out var sampIn, out var nsampIn); + + short[] ret = new short[nsampIn * 2]; + PushThroughSamples(sampIn, ret, nsampIn * 2); + samples = ret; + nsamp = nsampIn; + } + + public SyncSoundMode SyncMode => _soundProvider.SyncMode; + + public bool CanProvideAsync => _soundProvider.CanProvideAsync; + + public void SetSyncMode(SyncSoundMode mode) + { + _soundProvider.SetSyncMode(mode); + } + } +} diff --git a/src/BizHawk.Emulation.Common/Sound/Utilities/SpeexResampler.cs b/src/BizHawk.Emulation.Common/Sound/SpeexResampler.cs similarity index 97% rename from src/BizHawk.Emulation.Common/Sound/Utilities/SpeexResampler.cs rename to src/BizHawk.Emulation.Common/Sound/SpeexResampler.cs index f170ef1704..8a168f224a 100644 --- a/src/BizHawk.Emulation.Common/Sound/Utilities/SpeexResampler.cs +++ b/src/BizHawk.Emulation.Common/Sound/SpeexResampler.cs @@ -1,460 +1,460 @@ -using System; -using System.Runtime.InteropServices; - -// ReSharper disable UnusedMember.Local -// ReSharper disable UnusedMember.Global -// ReSharper disable IdentifierTypo -// ReSharper disable StyleCop.SA1300 -// ReSharper disable InconsistentNaming -namespace BizHawk.Emulation.Common -{ - /// - /// junk wrapper around LibSpeexDSP. quite inefficient. will be replaced - /// - public class SpeexResampler : IDisposable, ISoundProvider - { - // to accept an ISyncSoundProvider input - private readonly ISoundProvider _input; - - // 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 readonly short[] _inbuf = new short[512]; // [8192]; // [512]; - - /// - /// quality of the resampler. values other than those listed are valid, provided they are between MIN and MAX - /// - public enum Quality - { - QUALITY_MAX = 10, - QUALITY_MIN = 0, - QUALITY_DEFAULT = 4, - QUALITY_VOIP = 3, - QUALITY_DESKTOP = 5 - } - - private static class LibSpeexDSP - { - 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. - /// The error state - /// 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, Quality 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. - /// The error state - /// 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, Quality 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). - /// - /// resampler 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, Quality 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 Quality 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 resampler 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; - - private short[] _outbuf; - - // for sync - private short[] _outbuf2 = new short[16]; - private int _outbuf2pos; - - // in buffer position in samples (not sample pairs) - private int _inbufpos; - - /// - /// throw an exception based on error state - /// - private static void CheckError(LibSpeexDSP.RESAMPLER_ERR e) - { - switch (e) - { - case LibSpeexDSP.RESAMPLER_ERR.SUCCESS: - return; - case LibSpeexDSP.RESAMPLER_ERR.ALLOC_FAILED: - throw new InsufficientMemoryException($"{nameof(LibSpeexDSP)}: Alloc failed"); - case LibSpeexDSP.RESAMPLER_ERR.BAD_STATE: - throw new Exception($"{nameof(LibSpeexDSP)}: Bad state"); - case LibSpeexDSP.RESAMPLER_ERR.INVALID_ARG: - throw new ArgumentException($"{nameof(LibSpeexDSP)}: Bad Argument"); - case LibSpeexDSP.RESAMPLER_ERR.PTR_OVERLAP: - throw new Exception($"{nameof(LibSpeexDSP)}: Buffers cannot overlap"); - } - } - - /// 0 to 10 - /// numerator of sample rate change ratio (inrate / outrate) - /// denominator of sample rate 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 - /// source to take input from when output is requested. if null, no auto-fetching - /// and are both non-null - /// unmanaged call failed - public SpeexResampler(Quality quality, uint rationum, uint ratioden, uint sratein, uint srateout, Action drainer = null, ISoundProvider input = null) - { - if (drainer != null && input != null) - { - throw new ArgumentException($"Can't autofetch without being an {nameof(ISoundProvider)}?"); - } - - var 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($"{nameof(LibSpeexDSP)} returned null!"); - } - - CheckError(err); - - _drainer = drainer ?? InternalDrain; - _input = input; - - _outbuf = new short[(_inbuf.Length * ratioden / rationum / 2 * 2) + 128]; - } - - /// change sampling rate on the fly - /// numerator of sample rate change ratio (inrate / outrate) - /// denominator of sample rate 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 - /// unmanaged call failed - 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; - _drainer(_outbuf, (int)outal); - } - - public void Dispose() - { - if (_st != IntPtr.Zero) - { - LibSpeexDSP.speex_resampler_destroy(_st); - _st = IntPtr.Zero; - GC.SuppressFinalize(this); - } - } - - ~SpeexResampler() - { - Dispose(); - } - - private 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 GetSamplesSync(out short[] samples, out int nsamp) - { - if (_input != null) - { - _input.GetSamplesSync(out var sampin, out int nsampin); - EnqueueSamples(sampin, nsampin); - } - - Flush(); - nsamp = _outbuf2pos / 2; - samples = _outbuf2; - _outbuf2pos = 0; - } - - public void DiscardSamples() - { - _outbuf2pos = 0; - } - - public bool CanProvideAsync => false; - - public SyncSoundMode SyncMode => SyncSoundMode.Sync; - - /// always - public void GetSamplesAsync(short[] samples) - { - throw new InvalidOperationException("Async mode is not supported."); - } - - /// is - public void SetSyncMode(SyncSoundMode mode) - { - if (mode == SyncSoundMode.Async) - { - throw new NotSupportedException("Async mode is not supported."); - } - } - } -} +using System; +using System.Runtime.InteropServices; + +// ReSharper disable UnusedMember.Local +// ReSharper disable UnusedMember.Global +// ReSharper disable IdentifierTypo +// ReSharper disable StyleCop.SA1300 +// ReSharper disable InconsistentNaming +namespace BizHawk.Emulation.Common +{ + /// + /// junk wrapper around LibSpeexDSP. quite inefficient. will be replaced + /// + public class SpeexResampler : IDisposable, ISoundProvider + { + // to accept an ISyncSoundProvider input + private readonly ISoundProvider _input; + + // 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 readonly short[] _inbuf = new short[512]; // [8192]; // [512]; + + /// + /// quality of the resampler. values other than those listed are valid, provided they are between MIN and MAX + /// + public enum Quality + { + QUALITY_MAX = 10, + QUALITY_MIN = 0, + QUALITY_DEFAULT = 4, + QUALITY_VOIP = 3, + QUALITY_DESKTOP = 5 + } + + private static class LibSpeexDSP + { + 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. + /// The error state + /// 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, Quality 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. + /// The error state + /// 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, Quality 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). + /// + /// resampler 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, Quality 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 Quality 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 resampler 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; + + private short[] _outbuf; + + // for sync + private short[] _outbuf2 = new short[16]; + private int _outbuf2pos; + + // in buffer position in samples (not sample pairs) + private int _inbufpos; + + /// + /// throw an exception based on error state + /// + private static void CheckError(LibSpeexDSP.RESAMPLER_ERR e) + { + switch (e) + { + case LibSpeexDSP.RESAMPLER_ERR.SUCCESS: + return; + case LibSpeexDSP.RESAMPLER_ERR.ALLOC_FAILED: + throw new InsufficientMemoryException($"{nameof(LibSpeexDSP)}: Alloc failed"); + case LibSpeexDSP.RESAMPLER_ERR.BAD_STATE: + throw new Exception($"{nameof(LibSpeexDSP)}: Bad state"); + case LibSpeexDSP.RESAMPLER_ERR.INVALID_ARG: + throw new ArgumentException($"{nameof(LibSpeexDSP)}: Bad Argument"); + case LibSpeexDSP.RESAMPLER_ERR.PTR_OVERLAP: + throw new Exception($"{nameof(LibSpeexDSP)}: Buffers cannot overlap"); + } + } + + /// 0 to 10 + /// numerator of sample rate change ratio (inrate / outrate) + /// denominator of sample rate 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 + /// source to take input from when output is requested. if null, no auto-fetching + /// and are both non-null + /// unmanaged call failed + public SpeexResampler(Quality quality, uint rationum, uint ratioden, uint sratein, uint srateout, Action drainer = null, ISoundProvider input = null) + { + if (drainer != null && input != null) + { + throw new ArgumentException($"Can't autofetch without being an {nameof(ISoundProvider)}?"); + } + + var 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($"{nameof(LibSpeexDSP)} returned null!"); + } + + CheckError(err); + + _drainer = drainer ?? InternalDrain; + _input = input; + + _outbuf = new short[(_inbuf.Length * ratioden / rationum / 2 * 2) + 128]; + } + + /// change sampling rate on the fly + /// numerator of sample rate change ratio (inrate / outrate) + /// denominator of sample rate 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 + /// unmanaged call failed + 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; + _drainer(_outbuf, (int)outal); + } + + public void Dispose() + { + if (_st != IntPtr.Zero) + { + LibSpeexDSP.speex_resampler_destroy(_st); + _st = IntPtr.Zero; + GC.SuppressFinalize(this); + } + } + + ~SpeexResampler() + { + Dispose(); + } + + private 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 GetSamplesSync(out short[] samples, out int nsamp) + { + if (_input != null) + { + _input.GetSamplesSync(out var sampin, out int nsampin); + EnqueueSamples(sampin, nsampin); + } + + Flush(); + nsamp = _outbuf2pos / 2; + samples = _outbuf2; + _outbuf2pos = 0; + } + + public void DiscardSamples() + { + _outbuf2pos = 0; + } + + public bool CanProvideAsync => false; + + public SyncSoundMode SyncMode => SyncSoundMode.Sync; + + /// always + public void GetSamplesAsync(short[] samples) + { + throw new InvalidOperationException("Async mode is not supported."); + } + + /// is + public void SetSyncMode(SyncSoundMode mode) + { + if (mode == SyncSoundMode.Async) + { + throw new NotSupportedException("Async mode is not supported."); + } + } + } +} diff --git a/src/BizHawk.Emulation.Common/Sound/Utilities/Waves.cs b/src/BizHawk.Emulation.Common/Sound/Waves.cs similarity index 96% rename from src/BizHawk.Emulation.Common/Sound/Utilities/Waves.cs rename to src/BizHawk.Emulation.Common/Sound/Waves.cs index 466d3a60a5..28d4ba2868 100644 --- a/src/BizHawk.Emulation.Common/Sound/Utilities/Waves.cs +++ b/src/BizHawk.Emulation.Common/Sound/Waves.cs @@ -1,57 +1,57 @@ -namespace BizHawk.Emulation.Common -{ - public static class Waves - { - public static short[] SquareWave; - public static short[] ImperfectSquareWave; - public static short[] NoiseWave; - public static short[] PeriodicWave16; - - public static void InitWaves() - { - SquareWave = new short[] - { - -32768, -32768, -32768, -32768, -32768, -32768, -32768, -32768, -32768, -32768, -32768, -32768, -32768, -32768, -32768, -32768, - 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767 - }; - - ImperfectSquareWave = new short[] - { - -32768, -30145, -27852, -26213, -24902, -23592, -22282, -20971, -19988, -19005, -18350, -17694, -17366, -17039, -16711, -16711, - 32767, 30145, 27852, 26213, 24902, 23592, 22282, 20971, 19988, 19005, 18350, 17694, 17366, 17039, 16711, 16711 - }; - - PeriodicWave16 = new short[] { 32767, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; - - NoiseWave = new short[0x1000]; - var rnd = new System.Random(unchecked((int)0xDEADBEEF)); - for (int i = 0; i < NoiseWave.Length; i++) - { - int r = rnd.Next(); - if ((r & 1) > 0) - { - NoiseWave[i] = short.MaxValue; - } - } - -#if false - TriangleWave = new short[512]; - for (int i = 0; i < 256; i++) - TriangleWave[i] = (short)((ushort.MaxValue*i/256)-short.MinValue); - for (int i = 0; i < 256; i++) - TriangleWave[256+i] = TriangleWave[256-i]; - TriangleWave[256] = short.MaxValue; - - SawWave = new short[512]; - for (int i = 0; i < 512; i++) - SawWave[i] = (short)((ushort.MaxValue * i / 512) - short.MinValue); - - SineWave = new short[1024]; - for (int i=0; i<1024; i++) - { - SineWave[i] = (short) (Math.Sin(i*Math.PI*2/1024d)*32767); - } -#endif - } - } +namespace BizHawk.Emulation.Common +{ + public static class Waves + { + public static short[] SquareWave; + public static short[] ImperfectSquareWave; + public static short[] NoiseWave; + public static short[] PeriodicWave16; + + public static void InitWaves() + { + SquareWave = new short[] + { + -32768, -32768, -32768, -32768, -32768, -32768, -32768, -32768, -32768, -32768, -32768, -32768, -32768, -32768, -32768, -32768, + 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767 + }; + + ImperfectSquareWave = new short[] + { + -32768, -30145, -27852, -26213, -24902, -23592, -22282, -20971, -19988, -19005, -18350, -17694, -17366, -17039, -16711, -16711, + 32767, 30145, 27852, 26213, 24902, 23592, 22282, 20971, 19988, 19005, 18350, 17694, 17366, 17039, 16711, 16711 + }; + + PeriodicWave16 = new short[] { 32767, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; + + NoiseWave = new short[0x1000]; + var rnd = new System.Random(unchecked((int)0xDEADBEEF)); + for (int i = 0; i < NoiseWave.Length; i++) + { + int r = rnd.Next(); + if ((r & 1) > 0) + { + NoiseWave[i] = short.MaxValue; + } + } + +#if false + TriangleWave = new short[512]; + for (int i = 0; i < 256; i++) + TriangleWave[i] = (short)((ushort.MaxValue*i/256)-short.MinValue); + for (int i = 0; i < 256; i++) + TriangleWave[256+i] = TriangleWave[256-i]; + TriangleWave[256] = short.MaxValue; + + SawWave = new short[512]; + for (int i = 0; i < 512; i++) + SawWave[i] = (short)((ushort.MaxValue * i / 512) - short.MinValue); + + SineWave = new short[1024]; + for (int i=0; i<1024; i++) + { + SineWave[i] = (short) (Math.Sin(i*Math.PI*2/1024d)*32767); + } +#endif + } + } } \ No newline at end of file