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(); } ///