diff --git a/src/BizHawk.Emulation.Cores/Consoles/Atari/Stella/Stella.IEmulator.cs b/src/BizHawk.Emulation.Cores/Consoles/Atari/Stella/Stella.IEmulator.cs index 894f10d9b2..4eb21a32f6 100644 --- a/src/BizHawk.Emulation.Cores/Consoles/Atari/Stella/Stella.IEmulator.cs +++ b/src/BizHawk.Emulation.Cores/Consoles/Atari/Stella/Stella.IEmulator.cs @@ -58,6 +58,7 @@ namespace BizHawk.Emulation.Cores.Atari.Stella public void Dispose() { _elf.Dispose(); + DisposeSound(); } } } diff --git a/src/BizHawk.Emulation.Cores/Consoles/Atari/Stella/Stella.ISoundProvider.cs b/src/BizHawk.Emulation.Cores/Consoles/Atari/Stella/Stella.ISoundProvider.cs index e606d7809e..413c2d363f 100644 --- a/src/BizHawk.Emulation.Cores/Consoles/Atari/Stella/Stella.ISoundProvider.cs +++ b/src/BizHawk.Emulation.Cores/Consoles/Atari/Stella/Stella.ISoundProvider.cs @@ -1,12 +1,13 @@ -using System.Runtime.InteropServices; - -using BizHawk.Emulation.Common; +using BizHawk.Emulation.Common; using BizHawk.Common; namespace BizHawk.Emulation.Cores.Atari.Stella { public partial class Stella : ISoundProvider { + private BlipBuffer _blipL, _blipR; + private int _latchL, _latchR; + private readonly short[] _samples = new short[4096]; private int _nsamp; @@ -20,9 +21,7 @@ namespace BizHawk.Emulation.Cores.Atari.Stella } public void DiscardSamples() - { - _nsamp = 0; - } + => _nsamp = 0; public void SetSyncMode(SyncSoundMode mode) { @@ -30,26 +29,89 @@ namespace BizHawk.Emulation.Cores.Atari.Stella { throw new NotSupportedException("Async mode is not supported."); } - } + public SyncSoundMode SyncMode => SyncSoundMode.Sync; public void GetSamplesAsync(short[] samples) - { - throw new InvalidOperationException("Async mode is not supported."); - } + => throw new InvalidOperationException("Async mode is not supported."); - private void UpdateAudio() + private unsafe void UpdateAudio() { var src = IntPtr.Zero; - Core.stella_get_audio(ref _nsamp, ref src); + var nsamp = 0; + Core.stella_get_audio(ref nsamp, ref src); if (src != IntPtr.Zero) { using (_elf.EnterExit()) { - Marshal.Copy(src, _samples, 0, _nsamp * 2); + var samplePtr = (ushort*)src.ToPointer(); + for (uint i = 0; i < nsamp; i++) + { + int sample = *samplePtr++; + if (sample != _latchL) + { + var diff = _latchL - sample; + _latchL = sample; + _blipL.AddDelta(i, diff); + } + + sample = *samplePtr++; + if (sample != _latchR) + { + var diff = _latchR - sample; + _latchR = sample; + _blipR.AddDelta(i, diff); + } + } } + + _blipL.EndFrame((uint)nsamp); + _blipR.EndFrame((uint)nsamp); + + _nsamp = _blipL.SamplesAvailable(); + if (_nsamp != _blipR.SamplesAvailable()) + { + throw new InvalidOperationException("Audio processing error"); + } + + _blipL.ReadSamplesLeft(_samples, _nsamp); + _blipR.ReadSamplesRight(_samples, _nsamp); + } + else + { + _nsamp = 0; + } + } + + private void InitSound(int fps) + { + var sampleRate = fps switch + { + 60 => 262 * 76 * 60 / 38, // 31440Hz + 50 => 312 * 76 * 50 / 38, // 31200Hz + _ => throw new InvalidOperationException() + }; + + _blipL = new(2048); + _blipL.SetRates(sampleRate, 44100); + _blipR = new(2048); + _blipR.SetRates(sampleRate, 44100); + } + + private void DisposeSound() + { + if (_blipL != null) + { + _blipL.Dispose(); + _blipL = null; + } + + if (_blipR != null) + { + _blipR.Dispose(); + _blipR = null; } } } diff --git a/src/BizHawk.Emulation.Cores/Consoles/Atari/Stella/Stella.cs b/src/BizHawk.Emulation.Cores/Consoles/Atari/Stella/Stella.cs index cdc9ed27da..842dedb5ce 100644 --- a/src/BizHawk.Emulation.Cores/Consoles/Atari/Stella/Stella.cs +++ b/src/BizHawk.Emulation.Cores/Consoles/Atari/Stella/Stella.cs @@ -62,6 +62,8 @@ namespace BizHawk.Emulation.Cores.Atari.Stella Core.stella_get_frame_rate(out var fps); + InitSound(fps); + var regionId = Core.stella_get_region(); Region = regionId switch { @@ -85,7 +87,6 @@ namespace BizHawk.Emulation.Cores.Atari.Stella Core.stella_set_input_callback(_inputCallback); - // Getting cartridge type var ptr = Core.stella_get_cart_type(); var cartType = Marshal.PtrToStringAnsi(ptr); Console.WriteLine($"[Stella] Cart type loaded: {cartType}");