diff --git a/Assets/dll/libspeexdsp.dll b/Assets/dll/libspeexdsp.dll
deleted file mode 100644
index 02ebc18351..0000000000
Binary files a/Assets/dll/libspeexdsp.dll and /dev/null differ
diff --git a/src/BizHawk.Client.EmuHawk/Program.cs b/src/BizHawk.Client.EmuHawk/Program.cs
index 76bc853f02..a19063d780 100644
--- a/src/BizHawk.Client.EmuHawk/Program.cs
+++ b/src/BizHawk.Client.EmuHawk/Program.cs
@@ -126,24 +126,17 @@ namespace BizHawk.Client.EmuHawk
if (!OSTailoredCode.IsUnixHost)
{
- // TODO: We optimally should only require the new VS2015-2022 all in one redist be installed
- // None of this old 2010 runtime garbage
- // (just removing speex completely, perhaps can just use SDL2 as a replacement?)
- foreach (var (dllToLoad, desc) in new[]
- {
- ("vcruntime140_1.dll", "Microsoft Visual C++ Redistributable for Visual Studio 2015, 2017, 2019, and 2022 (x64)"),
- // for libspeexdsp.dll
- ("msvcr100.dll", "Microsoft Visual C++ 2010 SP1 Runtime (x64)"),
- })
+ // Check if we have the C++ VS2015-2022 redist all in one redist be installed
+ var p = OSTailoredCode.LinkedLibManager.LoadOrZero("vcruntime140_1.dll");
+ if (p != IntPtr.Zero)
+ {
+ OSTailoredCode.LinkedLibManager.FreeByPtr(p);
+ }
+ else
{
- var p = OSTailoredCode.LinkedLibManager.LoadOrZero(dllToLoad);
- if (p != IntPtr.Zero)
- {
- OSTailoredCode.LinkedLibManager.FreeByPtr(p);
- continue;
- }
-
// else it's missing or corrupted
+ const string desc =
+ "Microsoft Visual C++ Redistributable for Visual Studio 2015, 2017, 2019, and 2022 (x64)";
MessageBox.Show($"EmuHawk needs {desc} in order to run! See the readme on GitHub for more info. (EmuHawk will now close.) " +
$"Internal error message: {OSTailoredCode.LinkedLibManager.GetErrorMessage()}");
return -1;
diff --git a/src/BizHawk.Emulation.Common/BizHawk.Emulation.Common.csproj b/src/BizHawk.Emulation.Common/BizHawk.Emulation.Common.csproj
index 488f47f7b3..8790dd2cae 100644
--- a/src/BizHawk.Emulation.Common/BizHawk.Emulation.Common.csproj
+++ b/src/BizHawk.Emulation.Common/BizHawk.Emulation.Common.csproj
@@ -9,6 +9,7 @@
+
diff --git a/src/BizHawk.Emulation.Common/Sound/LibSpeexDSP.cs b/src/BizHawk.Emulation.Common/Sound/LibSpeexDSP.cs
deleted file mode 100644
index ec7193c87b..0000000000
--- a/src/BizHawk.Emulation.Common/Sound/LibSpeexDSP.cs
+++ /dev/null
@@ -1,234 +0,0 @@
-#nullable disable
-
-using System;
-using System.Runtime.InteropServices;
-using BizHawk.BizInvoke;
-using static BizHawk.Emulation.Common.SpeexResampler;
-
-namespace BizHawk.Emulation.Common
-{
- public abstract 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
- [BizImport(CallingConvention.Cdecl)]
- public abstract 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
- [BizImport(CallingConvention.Cdecl)]
- public abstract 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
- [BizImport(CallingConvention.Cdecl)]
- public abstract 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
- [BizImport(CallingConvention.Cdecl)]
- public abstract 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
- [BizImport(CallingConvention.Cdecl)]
- public abstract 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.
- [BizImport(CallingConvention.Cdecl)]
- public abstract 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.
- [BizImport(CallingConvention.Cdecl)]
- public abstract 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).
- [BizImport(CallingConvention.Cdecl)]
- public abstract 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.
- [BizImport(CallingConvention.Cdecl)]
- public abstract 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).
- [BizImport(CallingConvention.Cdecl)]
- public abstract 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
- [BizImport(CallingConvention.Cdecl)]
- public abstract 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.
- [BizImport(CallingConvention.Cdecl)]
- public abstract 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.
- [BizImport(CallingConvention.Cdecl)]
- public abstract void speex_resampler_get_quality(IntPtr st, ref Quality quality);
-
- ///
- /// Set (change) the input stride.
- ///
- /// Resampler state
- /// Input stride
- [BizImport(CallingConvention.Cdecl)]
- public abstract void speex_resampler_set_input_stride(IntPtr st, uint stride);
-
- ///
- /// Get the input stride.
- ///
- /// Resampler state
- /// Input stride copied
- [BizImport(CallingConvention.Cdecl)]
- public abstract void speex_resampler_get_input_stride(IntPtr st, ref uint stride);
-
- ///
- /// Set (change) the output stride.
- ///
- /// Resampler state
- /// Output stride
- [BizImport(CallingConvention.Cdecl)]
- public abstract void speex_resampler_set_output_stride(IntPtr st, uint stride);
-
- ///
- /// Get the output stride.
- ///
- /// Resampler state
- /// Output stride copied
- [BizImport(CallingConvention.Cdecl)]
- public abstract 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
- [BizImport(CallingConvention.Cdecl)]
- public abstract int speex_resampler_get_input_latency(IntPtr st);
-
- ///
- /// Get the latency in output samples introduced by the resampler.
- ///
- /// Resampler state
- [BizImport(CallingConvention.Cdecl)]
- public abstract 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
- [BizImport(CallingConvention.Cdecl)]
- public abstract RESAMPLER_ERR speex_resampler_skip_zeros(IntPtr st);
-
- ///
- /// Reset a resampler so a new (unrelated) stream can be processed.
- ///
- /// Resampler state
- [BizImport(CallingConvention.Cdecl)]
- public abstract RESAMPLER_ERR speex_resampler_reset_mem(IntPtr st);
-
- ///
- /// Returns the English meaning for an error code
- ///
- /// Error code
- /// English string
- [BizImport(CallingConvention.Cdecl, Compatibility = true)]
- public abstract string speex_resampler_strerror(RESAMPLER_ERR err);
- }
-}
diff --git a/src/BizHawk.Emulation.Common/Sound/SDLResampler.cs b/src/BizHawk.Emulation.Common/Sound/SDLResampler.cs
new file mode 100644
index 0000000000..f2d4575119
--- /dev/null
+++ b/src/BizHawk.Emulation.Common/Sound/SDLResampler.cs
@@ -0,0 +1,191 @@
+#nullable disable
+
+using System;
+using System.Runtime.InteropServices;
+
+using static SDL2.SDL;
+
+namespace BizHawk.Emulation.Common
+{
+ ///
+ /// Wrapper against SDL's resampler
+ ///
+ public class SDLResampler : IDisposable, ISoundProvider
+ {
+ // to accept an ISyncSoundProvider input
+ private readonly ISoundProvider _input;
+
+ // function to call to dispatch output
+ private readonly Action _drainer;
+
+ private short[] _outBuf = Array.Empty();
+
+ // for ISoundProvider output use
+ private short[] _outSamples = Array.Empty();
+ private int _outNumSamps;
+
+ // opaque pointer to SDL_AudioStream
+ private IntPtr _stream;
+
+ /// sampling rate in by hz
+ /// sampling rate out by 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 SDLResampler(int src_rate, int dst_rate, Action drainer = null, ISoundProvider input = null)
+ {
+ if (drainer is not null && input is not null) throw new ArgumentException(message: $"Can't autofetch without being an {nameof(ISoundProvider)}?", paramName: nameof(input));
+
+ _stream = SDL_NewAudioStream(AUDIO_S16SYS, 2, src_rate, AUDIO_S16SYS, 2, dst_rate);
+
+ if (_stream == IntPtr.Zero)
+ {
+ throw new($"{nameof(SDL_NewAudioStream)} returned null! SDL error: {SDL_GetError()}");
+ }
+
+ _drainer = drainer ?? InternalDrain;
+ _input = input;
+ }
+
+ // SDL bindings don't have this for some reason :(
+ [DllImport("SDL2", CallingConvention = CallingConvention.Cdecl)]
+ private static extern int SDL_AudioStreamFlush(IntPtr stream);
+
+ /// change sampling rate on the fly
+ /// sampling rate in by hz
+ /// sampling rate out by hz
+ public void ChangeRate(int src_rate, int dst_rate)
+ {
+ // force flush the stream, as we'll be destroying it to change the sample rate...
+ if (SDL_AudioStreamFlush(_stream) != 0)
+ {
+ throw new($"{nameof(SDL_AudioStreamFlush)} failed! SDL error: {SDL_GetError()}");
+ }
+
+ Flush();
+
+ SDL_FreeAudioStream(_stream);
+ _stream = SDL_NewAudioStream(AUDIO_S16SYS, 2, src_rate, AUDIO_S16SYS, 2, dst_rate);
+
+ if (_stream == IntPtr.Zero)
+ {
+ throw new($"{nameof(SDL_NewAudioStream)} returned null! SDL error: {SDL_GetError()}");
+ }
+ }
+
+ ///
+ /// add multiple samples to the queue
+ ///
+ /// interleaved stereo samples
+ /// number of sample pairs
+ public unsafe void EnqueueSamples(short[] userbuf, int nsamp)
+ {
+ fixed (short* ub = userbuf)
+ {
+ if (SDL_AudioStreamPut(_stream, (IntPtr)ub, nsamp * 4) != 0)
+ {
+ throw new($"{nameof(SDL_AudioStreamPut)} failed! SDL error: {SDL_GetError()}");
+ }
+ }
+ }
+
+ /// flush as many input samples as possible, generating output samples right now
+ /// unmanaged call failed
+ public unsafe void Flush()
+ {
+ var streamAvail = SDL_AudioStreamAvailable(_stream);
+ if (streamAvail == 0)
+ {
+ return;
+ }
+
+ // stereo s16 audio always has 4 bytes per interleaved sample
+ if (streamAvail % 4 != 0)
+ {
+ throw new("SDL audio stream contained partial sample frames?");
+ }
+
+ if (streamAvail / 2 > _outBuf.Length)
+ {
+ _outBuf = new short[streamAvail / 2];
+ }
+
+ fixed (short* outBuf = _outBuf)
+ {
+ var numRead = SDL_AudioStreamGet(_stream, (IntPtr)outBuf, streamAvail);
+ if (numRead == -1)
+ {
+ throw new($"{nameof(SDL_AudioStreamGet)} failed! SDL error: {SDL_GetError()}");
+ }
+
+ if (numRead != streamAvail)
+ {
+ throw new($"{nameof(SDL_AudioStreamGet)} didn't eat the whole array?");
+ }
+ }
+
+ _drainer(_outBuf, streamAvail / 4);
+ }
+
+ public void Dispose()
+ {
+ if (_stream != IntPtr.Zero)
+ {
+ SDL_FreeAudioStream(_stream);
+ _stream = IntPtr.Zero;
+ }
+ }
+
+ private void InternalDrain(short[] buf, int nsamp)
+ {
+ if (_outNumSamps + nsamp * 2 > _outBuf.Length)
+ {
+ var newbuf = new short[_outNumSamps + nsamp * 2];
+ Buffer.BlockCopy(_outSamples, 0, newbuf, 0, _outNumSamps * sizeof(short));
+ _outSamples = newbuf;
+ }
+
+ Buffer.BlockCopy(buf, 0, _outSamples, _outNumSamps * sizeof(short), nsamp * 2 * sizeof(short));
+ _outNumSamps += nsamp * 2;
+ }
+
+ public void GetSamplesSync(out short[] samples, out int nsamp)
+ {
+ if (_input != null)
+ {
+ _input.GetSamplesSync(out var sampin, out var nsampin);
+ EnqueueSamples(sampin, nsampin);
+ }
+
+ Flush();
+ nsamp = _outNumSamps / 2;
+ samples = _outSamples;
+ _outNumSamps = 0;
+ }
+
+ public void DiscardSamples()
+ {
+ _outNumSamps = 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/SpeexResampler.cs b/src/BizHawk.Emulation.Common/Sound/SpeexResampler.cs
deleted file mode 100644
index 3d15421fb4..0000000000
--- a/src/BizHawk.Emulation.Common/Sound/SpeexResampler.cs
+++ /dev/null
@@ -1,245 +0,0 @@
-#nullable disable
-
-using System;
-using BizHawk.BizInvoke;
-using BizHawk.Common;
-
-// 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
- {
- private static readonly LibSpeexDSP NativeDSP;
- static SpeexResampler()
- {
- var resolver = new DynamicLibraryImportResolver(
- OSTailoredCode.IsUnixHost ? "libspeexdsp.so.1" : "libspeexdsp.dll", hasLimitedLifetime: false);
- NativeDSP = BizInvoker.GetInvoker(resolver, CallingConventionAdapters.Native);
- }
-
- // 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
- }
-
- // 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;
-#pragma warning disable MA0012 // this will crash the .NET host, as it should
- case LibSpeexDSP.RESAMPLER_ERR.ALLOC_FAILED:
- throw new InsufficientMemoryException($"{nameof(LibSpeexDSP)}: Alloc failed");
-#pragma warning restore MA0012
- case LibSpeexDSP.RESAMPLER_ERR.BAD_STATE:
- throw new Exception($"{nameof(LibSpeexDSP)}: Bad state");
- case LibSpeexDSP.RESAMPLER_ERR.INVALID_ARG:
- throw new Exception($"{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 is not null && input is not null) throw new ArgumentException(message: $"Can't autofetch without being an {nameof(ISoundProvider)}?", paramName: nameof(input));
-
- var err = LibSpeexDSP.RESAMPLER_ERR.SUCCESS;
- _st = NativeDSP.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(NativeDSP.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;
-
- NativeDSP.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)
- {
- NativeDSP.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.Cores/Consoles/Nintendo/N64/N64Audio.cs b/src/BizHawk.Emulation.Cores/Consoles/Nintendo/N64/N64Audio.cs
index c16c0e44ff..587b334bd6 100644
--- a/src/BizHawk.Emulation.Cores/Consoles/Nintendo/N64/N64Audio.cs
+++ b/src/BizHawk.Emulation.Cores/Consoles/Nintendo/N64/N64Audio.cs
@@ -16,24 +16,28 @@ namespace BizHawk.Emulation.Cores.Nintendo.N64
///
/// Buffer for audio data
///
- private short[] audioBuffer = new short[0];
- private uint _samplingRate = 0;
+ private short[] audioBuffer = Array.Empty();
+
+ private int _samplingRate;
+
///
/// Currently active sampling rate
///
- public uint SamplingRate
+ public int SamplingRate
{
get => _samplingRate;
private set
{
_samplingRate = value;
- Resampler.ChangeRate(_samplingRate, 44100, _samplingRate, 44100);
+ Resampler.ChangeRate(_samplingRate, 44100);
}
}
+
///
/// Resampler for audio output
///
- public SpeexResampler Resampler { get; private set; }
+ public SDLResampler Resampler { get; private set; }
+
public bool RenderSound { get; set; }
///
@@ -44,8 +48,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.N64
this.api = new mupen64plusAudioApi(core);
_samplingRate = api.GetSamplingRate();
- Resampler = new SpeexResampler((SpeexResampler.Quality)6, SamplingRate, 44100,
- SamplingRate, 44100);
+ Resampler = new(SamplingRate, 44100);
coreAPI = core;
coreAPI.VInterrupt += DoAudioFrame;
@@ -57,19 +60,26 @@ namespace BizHawk.Emulation.Cores.Nintendo.N64
///
public void DoAudioFrame()
{
- uint m64pSamplingRate = api.GetSamplingRate();
+ var m64pSamplingRate = api.GetSamplingRate();
if (m64pSamplingRate != SamplingRate)
+ {
SamplingRate = m64pSamplingRate;
+ }
- int audioBufferSize = api.GetAudioBufferSize();
+ var audioBufferSize = api.GetAudioBufferSize();
if (audioBuffer.Length < audioBufferSize)
+ {
audioBuffer = new short[audioBufferSize];
+ }
if (audioBufferSize > 0)
{
api.GetAudioBuffer(audioBuffer);
if (RenderSound)
+ {
Resampler.EnqueueSamples(audioBuffer, audioBufferSize / 2);
+ Resampler.Flush();
+ }
}
}
diff --git a/src/BizHawk.Emulation.Cores/Consoles/Nintendo/N64/NativeApi/mupen64plusAudioApi.cs b/src/BizHawk.Emulation.Cores/Consoles/Nintendo/N64/NativeApi/mupen64plusAudioApi.cs
index 9e2caaee4b..bd02093057 100644
--- a/src/BizHawk.Emulation.Cores/Consoles/Nintendo/N64/NativeApi/mupen64plusAudioApi.cs
+++ b/src/BizHawk.Emulation.Cores/Consoles/Nintendo/N64/NativeApi/mupen64plusAudioApi.cs
@@ -57,9 +57,9 @@ namespace BizHawk.Emulation.Cores.Nintendo.N64.NativeApi
///
/// Returns currently used sampling rate
///
- public uint GetSamplingRate()
+ public int GetSamplingRate()
{
- return (uint)dllGetAudioRate();
+ return dllGetAudioRate();
}
///