From b73a500d6b22522bc3756440ed0b82edf3a7efaf Mon Sep 17 00:00:00 2001 From: adelikat Date: Sun, 11 Dec 2016 11:14:42 -0600 Subject: [PATCH] Major sound provider refactor. Create a unified interface ISoundProvider, that is an IEmulator service and break it off of IEmulator. IAsyncSoundProvider was kept as a shim for now, for legacy cores that were inherintly async. --- BizHawk.Client.EmuHawk/AVOut/AVSync.cs | 20 +- BizHawk.Client.EmuHawk/MainForm.cs | 40 +- BizHawk.Client.EmuHawk/Sound/Sound.cs | 68 +- .../Sound/Utilities/SoundOutputProvider.cs | 18 +- BizHawk.Client.EmuHawk/tools/BatchRunner.cs | 7 +- .../Base Implementations/NullEmulator.cs | 37 +- .../Base Implementations/NullSound.cs | 48 +- .../BizHawk.Emulation.Common.csproj | 4 +- BizHawk.Emulation.Common/Extensions.cs | 15 + .../Interfaces/IAsyncSoundProvider.cs | 74 + .../Interfaces/IEmulator.cs | 18 - .../Interfaces/ISoundProvider.cs | 8 - .../Interfaces/ISyncSoundProvider.cs | 43 - .../Interfaces/Services/ISoundProvider.cs | 44 + .../Sound/Utilities/BufferedAsync.cs | 54 +- .../Sound/Utilities/DCFilter.cs | 88 +- .../Sound/Utilities/Metaspu.cs | 81 +- .../Sound/Utilities/SoundMixer.cs | 4 + .../Sound/Utilities/SpeexResampler.cs | 36 +- .../BizHawk.Emulation.Cores.csproj | 33 +- .../Calculator/TI83.IEmulator.cs | 17 - BizHawk.Emulation.Cores/Calculator/TI83.cs | 7 +- .../AppleII/AppleII.IAudioProvider.cs | 21 - .../Computers/AppleII/AppleII.IEmulator.cs | 31 +- .../AppleII/AppleII.ISoundProvider.cs | 41 + .../Computers/AppleII/AppleII.cs | 2 +- .../Computers/Commodore64/C64.cs | 26 +- .../Commodore64/MOS/Sid.SoundProvider.cs | 80 +- .../Consoles/Atari/2600/Atari2600.Core.cs | 6 +- .../Consoles/Atari/2600/Atari2600.cs | 16 +- .../Consoles/Atari/2600/Tia/TIA.cs | 3242 +++++++++-------- .../Consoles/Atari/7800/Atari7800.cs | 42 +- .../Atari/lynx/Lynx.ISoundProvider.cs | 45 + .../Consoles/Atari/lynx/Lynx.cs | 24 +- .../Coleco/ColecoVision.ISoundProvider.cs | 15 + .../Consoles/Coleco/ColecoVision.cs | 9 +- .../Intellivision/Intellivision.IEmulator.cs | 22 - .../Consoles/Intellivision/Intellivision.cs | 2 - .../Consoles/Intellivision/PSG.cs | 3 +- .../Consoles/Nintendo/GBA/MGBAHawk.cs | 33 +- .../Consoles/Nintendo/GBA/Meteor.cs | 32 +- .../Nintendo/GBA/VBANext.ISoundProvider.cs | 44 + .../Consoles/Nintendo/GBA/VBANext.cs | 31 +- .../Gameboy/Gambatte.ISoundProvider.cs | 124 + .../Consoles/Nintendo/Gameboy/Gambatte.cs | 106 +- .../Consoles/Nintendo/Gameboy/GambatteLink.cs | 38 +- .../Consoles/Nintendo/N64/N64.cs | 9 +- .../Consoles/Nintendo/NES/NES.Core.cs | 46 +- .../Consoles/Nintendo/NES/NES.cs | 5 +- .../QuickNES/QuickNES.ISoundProvider.cs | 69 + .../Consoles/Nintendo/QuickNES/QuickNES.cs | 55 +- .../Consoles/Nintendo/SNES/LibsnesCore.cs | 5 +- .../Consoles/Nintendo/SNES9X/Snes9x.cs | 35 +- .../Consoles/PC Engine/ADPCM.cs | 6 +- .../Consoles/PC Engine/PCEngine.cs | 21 +- .../Consoles/Sega/Genesis/Genesis.cs | 6 +- .../Consoles/Sega/SMS/SMS.IEmulator.cs | 17 - .../Consoles/Sega/SMS/SMS.ISoundProvider.cs | 13 + .../Consoles/Sega/SMS/SMS.cs | 5 +- .../Consoles/Sega/Saturn/Yabause.cs | 35 +- .../Consoles/Sega/gpgx/GPGX.IEmulator.cs | 14 - .../Consoles/Sega/gpgx/GPGX.ISoundProvider.cs | 57 + .../Consoles/Sega/gpgx/GPGX.cs | 27 +- .../Consoles/Sega/gpgx64/GPGX.IEmulator.cs | 16 +- .../Sega/gpgx64/GPGX.ISoundProvider.cs | 57 + .../Consoles/Sega/gpgx64/GPGX.cs | 27 +- .../Consoles/Sony/PSP/PSP.cs | 34 +- .../Consoles/Sony/PSX/Octoshock.cs | 32 +- .../WonderSwan/WonderSwan.ISoundProvider.cs | 45 + .../Consoles/WonderSwan/WonderSwan.cs | 25 +- .../Libretro/LibRetroEmulator.cs | 6 +- BizHawk.Emulation.Cores/Sound/HuC6280PSG.cs | 2 + 72 files changed, 3016 insertions(+), 2452 deletions(-) create mode 100644 BizHawk.Emulation.Common/Interfaces/IAsyncSoundProvider.cs delete mode 100644 BizHawk.Emulation.Common/Interfaces/ISoundProvider.cs delete mode 100644 BizHawk.Emulation.Common/Interfaces/ISyncSoundProvider.cs create mode 100644 BizHawk.Emulation.Common/Interfaces/Services/ISoundProvider.cs delete mode 100644 BizHawk.Emulation.Cores/Computers/AppleII/AppleII.IAudioProvider.cs create mode 100644 BizHawk.Emulation.Cores/Computers/AppleII/AppleII.ISoundProvider.cs create mode 100644 BizHawk.Emulation.Cores/Consoles/Atari/lynx/Lynx.ISoundProvider.cs create mode 100644 BizHawk.Emulation.Cores/Consoles/Coleco/ColecoVision.ISoundProvider.cs create mode 100644 BizHawk.Emulation.Cores/Consoles/Nintendo/GBA/VBANext.ISoundProvider.cs create mode 100644 BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/Gambatte.ISoundProvider.cs create mode 100644 BizHawk.Emulation.Cores/Consoles/Nintendo/QuickNES/QuickNES.ISoundProvider.cs create mode 100644 BizHawk.Emulation.Cores/Consoles/Sega/SMS/SMS.ISoundProvider.cs create mode 100644 BizHawk.Emulation.Cores/Consoles/Sega/gpgx/GPGX.ISoundProvider.cs create mode 100644 BizHawk.Emulation.Cores/Consoles/Sega/gpgx64/GPGX.ISoundProvider.cs create mode 100644 BizHawk.Emulation.Cores/Consoles/WonderSwan/WonderSwan.ISoundProvider.cs diff --git a/BizHawk.Client.EmuHawk/AVOut/AVSync.cs b/BizHawk.Client.EmuHawk/AVOut/AVSync.cs index bd6e813ebf..9f14c85ada 100644 --- a/BizHawk.Client.EmuHawk/AVOut/AVSync.cs +++ b/BizHawk.Client.EmuHawk/AVOut/AVSync.cs @@ -17,8 +17,14 @@ namespace BizHawk.Client.EmuHawk private long _soundRemainder; // audio timekeeping for video dumping - public void DumpAV(IVideoProvider v, IAsyncSoundProvider s, out short[] samples, out int samplesprovided) + public void DumpAV(IVideoProvider v, ISoundProvider asyncSoundProvider, out short[] samples, out int samplesprovided) { + // Sound refactor TODO: we could try set it here, but we want the client to be responsible for mode switching? There may be non-trivial complications with when to switch modes that we don't want this object worrying about + if (asyncSoundProvider.SyncMode != SyncSoundMode.Async) + { + throw new InvalidOperationException("Only sync mode is supported, set sync mode before passing in the sound provider"); + } + if (!aset || !vset) throw new InvalidOperationException("Must set params first!"); @@ -29,7 +35,7 @@ namespace BizHawk.Client.EmuHawk _soundRemainder = nsampnum % fpsnum; samples = new short[nsamp * channels]; - s.GetSamples(samples); + asyncSoundProvider.GetSamplesAsync(samples); samplesprovided = (int)nsamp; w.AddFrame(v); @@ -73,10 +79,16 @@ namespace BizHawk.Client.EmuHawk } } - public void DumpAV(IVideoProvider v, ISyncSoundProvider s, out short[] samples, out int samplesprovided) + public void DumpAV(IVideoProvider v, ISoundProvider syncSoundProvider, out short[] samples, out int samplesprovided) { + // Sound refactor TODO: we could just set it here, but we want the client to be responsible for mode switching? There may be non-trivial complications with when to switch modes that we don't want this object worrying about + if (syncSoundProvider.SyncMode != SyncSoundMode.Sync) + { + throw new InvalidOperationException("Only sync mode is supported, set sync mode before passing in the sound provider"); + } + VerifyParams(); - s.GetSamples(out samples, out samplesprovided); + syncSoundProvider.GetSamplesSync(out samples, out samplesprovided); exaudio_num += samplesprovided * (long)fpsnum; // todo: scan for duplicate frames (ie, video content exactly matches previous frame) and for them, skip the threshone step diff --git a/BizHawk.Client.EmuHawk/MainForm.cs b/BizHawk.Client.EmuHawk/MainForm.cs index 178bb0dab2..589b5d946d 100644 --- a/BizHawk.Client.EmuHawk/MainForm.cs +++ b/BizHawk.Client.EmuHawk/MainForm.cs @@ -733,11 +733,24 @@ namespace BizHawk.Client.EmuHawk { _currentVideoProvider = NullVideo.Instance; } + + if (Global.Emulator.HasSoundProvider()) + { + _currentSoundProvider = Global.Emulator.AsSoundProvider(); + } + else + { + // Set the samples per frame based on VSync rate (which is 60 unless the core states otherwise) + // Use 44.1 khz because we like to do that + _currentSoundProvider = new NullSound((int)(44100 / Global.Emulator.CoreComm.VsyncRate)); + } } } private IVideoProvider _currentVideoProvider = NullVideo.Instance; + private ISoundProvider _currentSoundProvider = new NullSound(44100 / 60); // Reasonable default until we have a core instance + protected override void OnActivated(EventArgs e) { base.OnActivated(e); @@ -1345,7 +1358,10 @@ namespace BizHawk.Client.EmuHawk // AVI/WAV state private IVideoWriter _currAviWriter; private HashSet _currAviWriterFrameList; - private IAsyncSoundProvider _aviSoundInput; + + // Sound refator TODO: we can enforce async mode here with a property that gets/sets this but does an async check + private ISoundProvider _aviSoundInputAsync; // Note: This sound provider must be in async mode! + private MetaspuSoundProvider _dumpProxy; // an audio proxy used for dumping private bool _dumpaudiosync; // set true to for experimental AV dumping private int _avwriterResizew; @@ -1620,8 +1636,7 @@ namespace BizHawk.Client.EmuHawk } else { - Emulator.EndAsyncSound(); - GlobalWin.Sound.SetSyncInputPin(Emulator.SyncSoundProvider); + GlobalWin.Sound.SetSyncInputPin(_currentSoundProvider); } } @@ -3096,13 +3111,14 @@ namespace BizHawk.Client.EmuHawk if (_dumpaudiosync) { - Emulator.EndAsyncSound(); + _currentSoundProvider.SetSyncMode(SyncSoundMode.Sync); } else { - _aviSoundInput = !Emulator.StartAsyncSound() - ? new MetaspuAsync(Emulator.SyncSoundProvider, ESynchMethod.ESynchMethod_V) - : Emulator.SoundProvider; + _aviSoundInputAsync = _currentSoundProvider.CanProvideAsync + ? _currentSoundProvider + : new MetaspuAsync(_currentSoundProvider, ESynchMethod.ESynchMethod_V); + } _dumpProxy = new MetaspuSoundProvider(ESynchMethod.ESynchMethod_V); RewireSound(); @@ -3123,7 +3139,7 @@ namespace BizHawk.Client.EmuHawk AVIStatusLabel.Image = Properties.Resources.Blank; AVIStatusLabel.ToolTipText = string.Empty; AVIStatusLabel.Visible = false; - _aviSoundInput = null; + _aviSoundInputAsync = null; _dumpProxy = null; // return to normal sound output RewireSound(); } @@ -3144,7 +3160,7 @@ namespace BizHawk.Client.EmuHawk AVIStatusLabel.Image = Properties.Resources.Blank; AVIStatusLabel.ToolTipText = string.Empty; AVIStatusLabel.Visible = false; - _aviSoundInput = null; + _aviSoundInputAsync = null; _dumpProxy = null; // return to normal sound output RewireSound(); } @@ -3226,11 +3242,11 @@ namespace BizHawk.Client.EmuHawk int nsamp; if (_dumpaudiosync) { - (_currAviWriter as VideoStretcher).DumpAV(output, Emulator.SyncSoundProvider, out samp, out nsamp); + (_currAviWriter as VideoStretcher).DumpAV(output, _currentSoundProvider, out samp, out nsamp); } else { - (_currAviWriter as AudioStretcher).DumpAV(output, _aviSoundInput, out samp, out nsamp); + (_currAviWriter as AudioStretcher).DumpAV(output, _aviSoundInputAsync, out samp, out nsamp); } if (disposableOutput != null) @@ -3238,7 +3254,7 @@ namespace BizHawk.Client.EmuHawk disposableOutput.Dispose(); } - _dumpProxy.buffer.enqueue_samples(samp, nsamp); + _dumpProxy.Buffer.enqueue_samples(samp, nsamp); } catch (Exception e) { diff --git a/BizHawk.Client.EmuHawk/Sound/Sound.cs b/BizHawk.Client.EmuHawk/Sound/Sound.cs index 50b3e0f73e..0d10b98800 100644 --- a/BizHawk.Client.EmuHawk/Sound/Sound.cs +++ b/BizHawk.Client.EmuHawk/Sound/Sound.cs @@ -15,8 +15,9 @@ namespace BizHawk.Client.EmuHawk private bool _disposed; private ISoundOutput _soundOutput; - private ISyncSoundProvider _syncSoundProvider; - private IAsyncSoundProvider _asyncSoundProvider; + + private ISoundProvider _soundProvider; + private SoundOutputProvider _outputProvider; private readonly BufferedAsync _semiSync = new BufferedAsync(); @@ -61,16 +62,11 @@ namespace BizHawk.Client.EmuHawk _outputProvider = new SoundOutputProvider(); _outputProvider.MaxSamplesDeficit = _soundOutput.MaxSamplesDeficit; - _outputProvider.BaseSoundProvider = _syncSoundProvider; + _outputProvider.BaseSoundProvider = _soundProvider; Global.SoundMaxBufferDeficitMs = (int)Math.Ceiling(SamplesToMilliseconds(_soundOutput.MaxSamplesDeficit)); IsStarted = true; - - //ApplyVolumeSettings(); - - //LogUnderruns = true; - //_outputProvider.LogDebug = true; } public void StopSound() @@ -86,46 +82,28 @@ namespace BizHawk.Client.EmuHawk IsStarted = false; } - //public void ApplyVolumeSettings() - //{ - // if (!IsStarted) return; - - // double volume = Global.Config.SoundVolume / 100.0; - // if (volume < 0.0) volume = 0.0; - // if (volume > 1.0) volume = 1.0; - // _soundOutput.ApplyVolumeSettings(volume); - //} - - public void SetSyncInputPin(ISyncSoundProvider source) + // Sound refactor TODO: combine these methods, and check the SyncMode for behavior + public void SetSyncInputPin(ISoundProvider source) { - if (_asyncSoundProvider != null) - { - _asyncSoundProvider.DiscardSamples(); - _asyncSoundProvider = null; - } _semiSync.DiscardSamples(); - _semiSync.BaseSoundProvider = null; - _syncSoundProvider = source; + _semiSync.ClearSoundProvider(); + _soundProvider = source; if (_outputProvider != null) { _outputProvider.BaseSoundProvider = source; } } - public void SetAsyncInputPin(IAsyncSoundProvider source) + public void SetAsyncInputPin(ISoundProvider source) { - if (_syncSoundProvider != null) - { - _syncSoundProvider.DiscardSamples(); - _syncSoundProvider = null; - } if (_outputProvider != null) { _outputProvider.DiscardSamples(); _outputProvider.BaseSoundProvider = null; } - _asyncSoundProvider = source; - _semiSync.BaseSoundProvider = source; + + _soundProvider = source; + _semiSync.SetAsyncSoundProvider(source); _semiSync.RecalculateMagic(Global.Emulator.CoreComm.VsyncRate); } @@ -169,8 +147,10 @@ namespace BizHawk.Client.EmuHawk { if (!Global.Config.SoundEnabled || !IsStarted || _disposed) { - if (_asyncSoundProvider != null) _asyncSoundProvider.DiscardSamples(); - if (_syncSoundProvider != null) _syncSoundProvider.DiscardSamples(); + if (_soundProvider != null) _soundProvider.DiscardSamples(); + // Sound refactor TODO: delete me + //if (_asyncSoundProvider != null) _asyncSoundProvider.DiscardSamples(); + //if (_syncSoundProvider != null) _syncSoundProvider.DiscardSamples(); if (_outputProvider != null) _outputProvider.DiscardSamples(); return; } @@ -188,15 +168,18 @@ namespace BizHawk.Client.EmuHawk samples = new short[samplesNeeded * ChannelCount]; samplesProvided = samplesNeeded; - if (_asyncSoundProvider != null) _asyncSoundProvider.DiscardSamples(); - if (_syncSoundProvider != null) _syncSoundProvider.DiscardSamples(); + if (_soundProvider != null) _soundProvider.DiscardSamples(); + // Sound refactor TODO: delete me + //if (_asyncSoundProvider != null) _asyncSoundProvider.DiscardSamples(); + //if (_syncSoundProvider != null) _syncSoundProvider.DiscardSamples(); if (_outputProvider != null) _outputProvider.DiscardSamples(); } - else if (_syncSoundProvider != null) + else if ( _soundProvider.SyncMode == SyncSoundMode.Sync) + //else if (_syncSoundProvider != null) // Sound refactor TODO: delete me { if (Global.Config.SoundThrottle) { - _syncSoundProvider.GetSamples(out samples, out samplesProvided); + _soundProvider.GetSamplesSync(out samples, out samplesProvided); while (samplesNeeded < samplesProvided && !Global.DisableSecondaryThrottling) { @@ -213,11 +196,12 @@ namespace BizHawk.Client.EmuHawk _outputProvider.GetSamples(samplesNeeded, out samples, out samplesProvided); } } - else if (_asyncSoundProvider != null) + else if (_soundProvider != null && _soundProvider.SyncMode == SyncSoundMode.Sync) + //else if (_asyncSoundProvider != null) // Sound refactor TODO: delete me { samples = new short[samplesNeeded * ChannelCount]; - _semiSync.GetSamples(samples); + _semiSync.GetSamplesAsync(samples); samplesProvided = samplesNeeded; } diff --git a/BizHawk.Client.EmuHawk/Sound/Utilities/SoundOutputProvider.cs b/BizHawk.Client.EmuHawk/Sound/Utilities/SoundOutputProvider.cs index d8e7c2096c..98a4fff764 100644 --- a/BizHawk.Client.EmuHawk/Sound/Utilities/SoundOutputProvider.cs +++ b/BizHawk.Client.EmuHawk/Sound/Utilities/SoundOutputProvider.cs @@ -54,7 +54,21 @@ namespace BizHawk.Client.EmuHawk public int MaxSamplesDeficit { get; set; } - public ISyncSoundProvider BaseSoundProvider { get; set; } + // Sound Refactor TODO: is this sync mode check necessary? + private ISoundProvider _baseSoundProvider; + public ISoundProvider BaseSoundProvider + { + get { return _baseSoundProvider; } + set + { + if (value != null && value.SyncMode != SyncSoundMode.Sync) + { + throw new InvalidOperationException("Sync mode is required"); + } + + _baseSoundProvider = value; + } + } public void DiscardSamples() { @@ -164,7 +178,7 @@ namespace BizHawk.Client.EmuHawk short[] samples; int count; - BaseSoundProvider.GetSamples(out samples, out count); + BaseSoundProvider.GetSamplesSync(out samples, out count); bool correctedEmptyFrame = false; if (count == 0) diff --git a/BizHawk.Client.EmuHawk/tools/BatchRunner.cs b/BizHawk.Client.EmuHawk/tools/BatchRunner.cs index 5f3409b378..4d9b080c73 100644 --- a/BizHawk.Client.EmuHawk/tools/BatchRunner.cs +++ b/BizHawk.Client.EmuHawk/tools/BatchRunner.cs @@ -180,8 +180,13 @@ namespace BizHawk.Client.EmuHawk int nsamp; short[] samp; emu.FrameAdvance(true, true); + // some cores really really really like it if you drain their audio every frame - emu.SyncSoundProvider.GetSamples(out samp, out nsamp); + if (emu.HasSoundProvider()) + { + emu.AsSoundProvider().GetSamplesSync(out samp, out nsamp); + } + current.Frames++; if (emu.CanPollInput() && emu.AsInputPollable().IsLagFrame) current.LaggedFrames++; diff --git a/BizHawk.Emulation.Common/Base Implementations/NullEmulator.cs b/BizHawk.Emulation.Common/Base Implementations/NullEmulator.cs index 27d7aaac6e..0b89fe25b8 100644 --- a/BizHawk.Emulation.Common/Base Implementations/NullEmulator.cs +++ b/BizHawk.Emulation.Common/Base Implementations/NullEmulator.cs @@ -9,10 +9,11 @@ namespace BizHawk.Emulation.Common [CoreAttributes("NullHawk", "", false, true)] [ServiceNotApplicable(typeof(IStatable), typeof(ISaveRam), typeof(IDriveLight), typeof(ICodeDataLogger), typeof(IMemoryDomains), typeof(IDebuggable), typeof(IDisassemblable), typeof(IInputPollable), typeof(IRegionable), typeof(ITraceable))] - public class NullEmulator : IEmulator, IVideoProvider, ISyncSoundProvider, IAsyncSoundProvider, ISettable + public class NullEmulator : IEmulator, IVideoProvider, ISoundProvider, ISettable { public NullEmulator(CoreComm comm, object settings) { + SyncMode = SyncSoundMode.Sync; ServiceProvider = new BasicServiceProvider(this); CoreComm = comm; _settings = (NullEmulatorSettings)settings ?? new NullEmulatorSettings(); @@ -34,10 +35,6 @@ namespace BizHawk.Emulation.Common private readonly int[] frameBuffer = new int[256 * 192]; private readonly Random rand = new Random(); public CoreComm CoreComm { get; private set; } - public IAsyncSoundProvider SoundProvider { get { return this; } } - public ISyncSoundProvider SyncSoundProvider { get { return this; } } - public bool StartAsyncSound() { return true; } - public void EndAsyncSound() { } public void ResetCounters() { @@ -94,8 +91,15 @@ namespace BizHawk.Emulation.Common private short[] sampbuff = new short[735 * 2]; - public void GetSamples(out short[] samples, out int nsamp) + #region ISoundProvider + + public void GetSamplesSync(out short[] samples, out int nsamp) { + if (SyncMode != SyncSoundMode.Sync) + { + throw new InvalidOperationException("Attempt to call a Sync method in async mode"); + } + nsamp = 735; samples = sampbuff; if (!_settings.SnowyDisplay) @@ -108,20 +112,33 @@ namespace BizHawk.Emulation.Common { } - public void GetSamples(short[] samples) + public void GetSamplesAsync(short[] samples) { + if (SyncMode != SyncSoundMode.Async) + { + throw new InvalidOperationException("Attempt to call an Async method in sync mode"); + } + if (!_settings.SnowyDisplay) return; if (xmas) pleg.Generate(samples); } - public int MaxVolume + public bool CanProvideAsync { - get; - set; + get { return true; } } + public SyncSoundMode SyncMode { get; private set; } + + public void SetSyncMode(SyncSoundMode mode) + { + SyncMode = mode; + } + + #endregion + private NullEmulatorSettings _settings; public class NullEmulatorSettings diff --git a/BizHawk.Emulation.Common/Base Implementations/NullSound.cs b/BizHawk.Emulation.Common/Base Implementations/NullSound.cs index 5d8d0d527e..b785b1f52c 100644 --- a/BizHawk.Emulation.Common/Base Implementations/NullSound.cs +++ b/BizHawk.Emulation.Common/Base Implementations/NullSound.cs @@ -1,10 +1,46 @@ -namespace BizHawk.Emulation.Common -{ - public class NullSound : IAsyncSoundProvider - { - public static readonly NullSound SilenceProvider = new NullSound(); +using System; + +namespace BizHawk.Emulation.Common +{ + public class NullSound : ISoundProvider + { + private readonly int _spf; + + public NullSound(int spf) + { + _spf = spf; + } + + public bool CanProvideAsync + { + get { return false; } + } + + public SyncSoundMode SyncMode + { + get { return SyncSoundMode.Sync; } + } + + public void GetSamplesSync(out short[] samples, out int nsamp) + { + short[] ret = new short[_spf * 2]; + samples = ret; + nsamp = _spf; + } - public void GetSamples(short[] samples) { } public void DiscardSamples() { } + + public void SetSyncMode(SyncSoundMode mode) + { + if (mode == SyncSoundMode.Async) + { + throw new NotSupportedException("Async mode is not supported."); + } + } + + public void GetSamplesAsync(short[] samples) + { + throw new InvalidOperationException("Async mode is not supported."); + } } } diff --git a/BizHawk.Emulation.Common/BizHawk.Emulation.Common.csproj b/BizHawk.Emulation.Common/BizHawk.Emulation.Common.csproj index 77d5982a8b..0ca128175d 100644 --- a/BizHawk.Emulation.Common/BizHawk.Emulation.Common.csproj +++ b/BizHawk.Emulation.Common/BizHawk.Emulation.Common.csproj @@ -123,8 +123,8 @@ - - + + diff --git a/BizHawk.Emulation.Common/Extensions.cs b/BizHawk.Emulation.Common/Extensions.cs index f75d2063b9..83b97d3342 100644 --- a/BizHawk.Emulation.Common/Extensions.cs +++ b/BizHawk.Emulation.Common/Extensions.cs @@ -33,6 +33,21 @@ namespace BizHawk.Emulation.Common.IEmulatorExtensions return core.ServiceProvider.GetService(); } + public static bool HasSoundProvider(this IEmulator core) + { + if (core == null) + { + return false; + } + + return core.ServiceProvider.HasService(); + } + + public static ISoundProvider AsSoundProvider(this IEmulator core) + { + return core.ServiceProvider.GetService(); + } + public static bool HasMemoryDomains(this IEmulator core) { if (core == null) diff --git a/BizHawk.Emulation.Common/Interfaces/IAsyncSoundProvider.cs b/BizHawk.Emulation.Common/Interfaces/IAsyncSoundProvider.cs new file mode 100644 index 0000000000..1ebd908646 --- /dev/null +++ b/BizHawk.Emulation.Common/Interfaces/IAsyncSoundProvider.cs @@ -0,0 +1,74 @@ +using System; + +namespace BizHawk.Emulation.Common +{ + public interface IAsyncSoundProvider + { + void GetSamples(short[] samples); + void DiscardSamples(); + } + + /// + /// TODO: this is a shim for now + /// turns an IAsyncSoundPRovider into a full ISoundProvider + /// This is used in cores that have an async only sound implementation + /// better is impleemnt a sync sound option in those cores without the need for + /// this class or an IAsyncSoundPRovider interface + /// + public class FakeSyncSound : ISoundProvider + { + private readonly IAsyncSoundProvider source; + private readonly int spf; + /// + /// + /// + /// + /// number of sample pairs to request and provide on each GetSamples() call + public FakeSyncSound(IAsyncSoundProvider source, int spf) + { + this.source = source; + this.spf = spf; + SyncMode = SyncSoundMode.Sync; + } + + public void GetSamplesSync(out short[] samples, out int nsamp) + { + if (SyncMode != SyncSoundMode.Sync) + { + throw new InvalidOperationException("Must be in sync mode to call a sync method"); + } + + short[] ret = new short[spf * 2]; + source.GetSamples(ret); + samples = ret; + nsamp = spf; + } + + public void DiscardSamples() + { + source.DiscardSamples(); + } + + public void GetSamplesAsync(short[] samples) + { + if (SyncMode != SyncSoundMode.Async) + { + throw new InvalidOperationException("Must be in async mode to call an async method"); + } + + source.GetSamples(samples); + } + + public bool CanProvideAsync + { + get { return true; } + } + + public SyncSoundMode SyncMode { get; private set; } + + public void SetSyncMode(SyncSoundMode mode) + { + SyncMode = mode; + } + } +} diff --git a/BizHawk.Emulation.Common/Interfaces/IEmulator.cs b/BizHawk.Emulation.Common/Interfaces/IEmulator.cs index 16ce8c056b..8cb6e24a8c 100644 --- a/BizHawk.Emulation.Common/Interfaces/IEmulator.cs +++ b/BizHawk.Emulation.Common/Interfaces/IEmulator.cs @@ -12,24 +12,6 @@ namespace BizHawk.Emulation.Common /// IEmulatorServiceProvider ServiceProvider { get; } - /// - /// Sound provider for async operation. this is optional, and is only required after StartAsyncSound() is called and returns true - /// - IAsyncSoundProvider SoundProvider { get; } - - /// - /// sound provider for sync operation. this is manditory - /// - ISyncSoundProvider SyncSoundProvider { get; } - - /// start async operation. (on construct, sync operation is assumed). - /// false if core doesn't support async sound; SyncSoundProvider will continue to be used in that case - bool StartAsyncSound(); - /// - /// end async operation, returning to sync operation. after this, all sound requests will go to the SyncSoundProvider - /// - void EndAsyncSound(); - /// /// Defines all the possible inputs and types that the core can receive /// diff --git a/BizHawk.Emulation.Common/Interfaces/ISoundProvider.cs b/BizHawk.Emulation.Common/Interfaces/ISoundProvider.cs deleted file mode 100644 index 19bdd18493..0000000000 --- a/BizHawk.Emulation.Common/Interfaces/ISoundProvider.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace BizHawk.Emulation.Common -{ - public interface IAsyncSoundProvider - { - void GetSamples(short[] samples); - void DiscardSamples(); - } -} diff --git a/BizHawk.Emulation.Common/Interfaces/ISyncSoundProvider.cs b/BizHawk.Emulation.Common/Interfaces/ISyncSoundProvider.cs deleted file mode 100644 index 1f8125ac3c..0000000000 --- a/BizHawk.Emulation.Common/Interfaces/ISyncSoundProvider.cs +++ /dev/null @@ -1,43 +0,0 @@ -namespace BizHawk.Emulation.Common -{ - public interface ISyncSoundProvider - { - /// - /// - /// number of sample PAIRS available - void GetSamples(out short[] samples, out int nsamp); - - void DiscardSamples(); - } - - /// - /// wraps an ISyncSoundProvider around an ISoundProvider - /// - public class FakeSyncSound : ISyncSoundProvider - { - private readonly IAsyncSoundProvider source; - private readonly int spf; - - /// - /// - /// number of sample pairs to request and provide on each GetSamples() call - public FakeSyncSound(IAsyncSoundProvider source, int spf) - { - this.source = source; - this.spf = spf; - } - - public void GetSamples(out short[] samples, out int nsamp) - { - short[] ret = new short[spf * 2]; - source.GetSamples(ret); - samples = ret; - nsamp = spf; - } - - public void DiscardSamples() - { - source.DiscardSamples(); - } - } -} diff --git a/BizHawk.Emulation.Common/Interfaces/Services/ISoundProvider.cs b/BizHawk.Emulation.Common/Interfaces/Services/ISoundProvider.cs new file mode 100644 index 0000000000..909fa8365d --- /dev/null +++ b/BizHawk.Emulation.Common/Interfaces/Services/ISoundProvider.cs @@ -0,0 +1,44 @@ +namespace BizHawk.Emulation.Common +{ + public enum SyncSoundMode { Sync, Async }; + + public interface ISoundProvider : IEmulatorService + { + /// + /// Returns true if a core can provide Async sound + /// + bool CanProvideAsync { get; } + + /// + /// Sets sync or async sound mode, + /// Sync should be the default mode if not set + /// All implementations must provide sync + /// If a core can not provide async sound and the mode is set to sync, + /// an NotSupportedException should be thrown + /// + void SetSyncMode(SyncSoundMode mode); + + /// + /// Reports which mode the sound provider is currently in + /// + SyncSoundMode SyncMode { get; } + + /// + /// Provides samples in syncmode + /// If the core is not in sync mode, this should throw an InvalidOperationException + /// + void GetSamplesSync(out short[] samples, out int nsamp); + + /// + /// Provides samples in async mode + /// If the core is not in async mode, this shoudl throw an InvalidOperationException + /// + /// + void GetSamplesAsync(short[] samples); + + /// + /// Discards stuff, is there anything more to say here? + /// + void DiscardSamples(); + } +} diff --git a/BizHawk.Emulation.Common/Sound/Utilities/BufferedAsync.cs b/BizHawk.Emulation.Common/Sound/Utilities/BufferedAsync.cs index 6d31352619..85cda2f786 100644 --- a/BizHawk.Emulation.Common/Sound/Utilities/BufferedAsync.cs +++ b/BizHawk.Emulation.Common/Sound/Utilities/BufferedAsync.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; namespace BizHawk.Emulation.Common { @@ -27,10 +28,24 @@ namespace BizHawk.Emulation.Common * TODO: For systems that _really_ don't need BufferedAsync (pce not turbocd, sms), make a way to signal * that and then bypass the BufferedAsync. */ - - public sealed class BufferedAsync : IAsyncSoundProvider + public sealed class BufferedAsync : ISoundProvider { - public IAsyncSoundProvider BaseSoundProvider; + private ISoundProvider _baseSoundProvider; + + public void SetAsyncSoundProvider(ISoundProvider provider) + { + if (provider.SyncMode != SyncSoundMode.Async) + { + throw new InvalidOperationException("a sound provider in async mode is required"); + } + + _baseSoundProvider = provider; + } + + public void ClearSoundProvider() + { + _baseSoundProvider = null; + } readonly Queue buffer = new Queue(4096); @@ -50,11 +65,11 @@ namespace BizHawk.Emulation.Common public void DiscardSamples() { - if (BaseSoundProvider != null) - BaseSoundProvider.DiscardSamples(); + if (_baseSoundProvider != null) + _baseSoundProvider.DiscardSamples(); } - public void GetSamples(short[] samples) + public void GetSamplesAsync(short[] samples) { int samplesToGenerate = SamplesInOneFrame; if (buffer.Count > samples.Length + MaxExcessSamples) @@ -66,7 +81,7 @@ namespace BizHawk.Emulation.Common var mySamples = new short[samplesToGenerate]; - BaseSoundProvider.GetSamples(mySamples); + _baseSoundProvider.GetSamplesAsync(mySamples); foreach (short s in mySamples) { @@ -78,5 +93,28 @@ namespace BizHawk.Emulation.Common samples[i] = buffer.Dequeue(); } } + + public bool CanProvideAsync + { + get { return true; } + } + + public SyncSoundMode SyncMode + { + get { return SyncSoundMode.Async; } + } + + public void SetSyncMode(SyncSoundMode mode) + { + if (mode != SyncSoundMode.Async) + { + throw new NotSupportedException("Async mode is not supported."); + } + } + + public void GetSamplesSync(out short[] samples, out int nsamp) + { + throw new InvalidOperationException("Async mode is not supported."); + } } } \ No newline at end of file diff --git a/BizHawk.Emulation.Common/Sound/Utilities/DCFilter.cs b/BizHawk.Emulation.Common/Sound/Utilities/DCFilter.cs index a11870daf9..d4516a8bf0 100644 --- a/BizHawk.Emulation.Common/Sound/Utilities/DCFilter.cs +++ b/BizHawk.Emulation.Common/Sound/Utilities/DCFilter.cs @@ -5,24 +5,18 @@ namespace BizHawk.Emulation.Common /// /// implements a DC block filter on top of an ISoundProvider. rather simple. /// - sealed public class DCFilter : IAsyncSoundProvider, ISyncSoundProvider + sealed public class DCFilter : ISoundProvider { - /* - * A note about accuracy: - * - * DCFilter can be added to the final output of any console, and this change will be faithful to the original hardware. - * Analog output hardware ALWAYS has dc blocking caps. - */ + private ISoundProvider _soundProvider; - IAsyncSoundProvider input; - ISyncSoundProvider syncinput; + private int latchL = 0; + private int latchR = 0; + private int accumL = 0; + private int accumR = 0; - int latchL = 0; - int latchR = 0; - int accumL = 0; - int accumR = 0; + private int depth; - static int DepthFromFilterwidth(int filterwidth) + private static int DepthFromFilterwidth(int filterwidth) { int ret = -2; while (filterwidth > 0) @@ -33,34 +27,34 @@ namespace BizHawk.Emulation.Common return ret; } - int depth; - - public static DCFilter AsISoundProvider(IAsyncSoundProvider input, int filterwidth) + public DCFilter(ISoundProvider input, int filterwidth) { if (input == null) + { throw new ArgumentNullException(); - return new DCFilter(input, null, filterwidth); + } + + if (filterwidth < 8 || filterwidth > 65536) + { + throw new ArgumentOutOfRangeException(); + } + + depth = DepthFromFilterwidth(filterwidth); + + _soundProvider = input; } - public static DCFilter AsISyncSoundProvider(ISyncSoundProvider syncinput, int filterwidth) - { - if (syncinput == null) - throw new ArgumentNullException(); - return new DCFilter(null, syncinput, filterwidth); - } - - public static DCFilter DetatchedMode(int filterwidth) - { - return new DCFilter(null, null, filterwidth); - } - - DCFilter(IAsyncSoundProvider input, ISyncSoundProvider syncinput, int filterwidth) + // Detached mode + public DCFilter(int filterwidth) { if (filterwidth < 8 || filterwidth > 65536) + { throw new ArgumentOutOfRangeException(); - this.input = input; - this.syncinput = syncinput; + } + depth = DepthFromFilterwidth(filterwidth); + + _soundProvider = null; } /// @@ -104,31 +98,43 @@ namespace BizHawk.Emulation.Common } } - void IAsyncSoundProvider.GetSamples(short[] samples) + public void GetSamplesAsync(short[] samples) { - input.GetSamples(samples); + _soundProvider.GetSamplesAsync(samples); PushThroughSamples(samples, samples.Length); } - void IAsyncSoundProvider.DiscardSamples() + public void DiscardSamples() { - input.DiscardSamples(); + _soundProvider.DiscardSamples(); } - void ISyncSoundProvider.GetSamples(out short[] samples, out int nsamp) + public void GetSamplesSync(out short[] samples, out int nsamp) { short[] sampin; int nsampin; - syncinput.GetSamples(out sampin, out nsampin); + + _soundProvider.GetSamplesSync(out sampin, out nsampin); + short[] ret = new short[nsampin * 2]; PushThroughSamples(sampin, ret, nsampin * 2); samples = ret; nsamp = nsampin; } - void ISyncSoundProvider.DiscardSamples() + public SyncSoundMode SyncMode { - syncinput.DiscardSamples(); + get { return _soundProvider.SyncMode; } + } + + public bool CanProvideAsync + { + get { return _soundProvider.CanProvideAsync; } + } + + public void SetSyncMode(SyncSoundMode mode) + { + _soundProvider.SetSyncMode(mode); } } } diff --git a/BizHawk.Emulation.Common/Sound/Utilities/Metaspu.cs b/BizHawk.Emulation.Common/Sound/Utilities/Metaspu.cs index f2b81d7877..be230af925 100644 --- a/BizHawk.Emulation.Common/Sound/Utilities/Metaspu.cs +++ b/BizHawk.Emulation.Common/Sound/Utilities/Metaspu.cs @@ -4,23 +4,26 @@ using System.Collections.Generic; namespace BizHawk.Emulation.Common { /// - /// uses Metaspu to have an ISyncSoundProvider input to a ISoundProvider + /// uses Metaspu to provide async sound to an ISoundProvider that does not provide its own async implementation /// - public class MetaspuAsync : IAsyncSoundProvider + // Sound Refactor TODO: rename me to MetaspuAsyncSoundProvider + public class MetaspuAsync : ISoundProvider { private readonly ISynchronizingAudioBuffer buffer; - private readonly ISyncSoundProvider input; - public MetaspuAsync(ISyncSoundProvider input, ESynchMethod method) + private readonly ISoundProvider input; + + public MetaspuAsync(ISoundProvider input, ESynchMethod method) { + input.SetSyncMode(SyncSoundMode.Sync); buffer = Metaspu.metaspu_construct(method); this.input = input; } - public void GetSamples(short[] samples) + public void GetSamplesAsync(short[] samples) { short[] sampin; int numsamp; - input.GetSamples(out sampin, out numsamp); + input.GetSamplesSync(out sampin, out numsamp); buffer.enqueue_samples(sampin, numsamp); buffer.output_samples(samples, samples.Length / 2); } @@ -30,37 +33,86 @@ namespace BizHawk.Emulation.Common input.DiscardSamples(); buffer.clear(); } + + public bool CanProvideAsync + { + get { return true; } + } + + public SyncSoundMode SyncMode + { + get { return SyncSoundMode.Async; } + } + + public void SetSyncMode(SyncSoundMode mode) + { + if (mode != SyncSoundMode.Async) + { + throw new NotSupportedException("Only Async mode is supported"); + } + } + + public void GetSamplesSync(out short[] samples, out int nsamp) + { + throw new InvalidOperationException("Sync mode not supported"); + } } - public class MetaspuSoundProvider : IAsyncSoundProvider + // An async sound provider + // Sound refactor TODO: can this be combined with the other Metaspu? + public class MetaspuSoundProvider : ISoundProvider { - public ISynchronizingAudioBuffer buffer; public MetaspuSoundProvider(ESynchMethod method) { - buffer = Metaspu.metaspu_construct(method); + Buffer = Metaspu.metaspu_construct(method); } + public ISynchronizingAudioBuffer Buffer { get; set; } + private readonly short[] pullBuffer = new short[1470]; + public MetaspuSoundProvider() : this(ESynchMethod.ESynchMethod_V) { } - private readonly short[] pullBuffer = new short[1470]; public void PullSamples(IAsyncSoundProvider source) { Array.Clear(pullBuffer, 0, 1470); source.GetSamples(pullBuffer); - buffer.enqueue_samples(pullBuffer, 735); + Buffer.enqueue_samples(pullBuffer, 735); } - public void GetSamples(short[] samples) + public bool CanProvideAsync { - buffer.output_samples(samples, samples.Length / 2); + get { return true; } + } + + public SyncSoundMode SyncMode + { + get { return SyncSoundMode.Async; } + } + + public void SetSyncMode(SyncSoundMode mode) + { + if (mode != SyncSoundMode.Async) + { + throw new NotSupportedException("Only Async mode is supported."); + } + } + + public void GetSamplesSync(out short[] samples, out int nsamp) + { + throw new InvalidOperationException("Sync mode is not supported."); + } + + public void GetSamplesAsync(short[] samples) + { + Buffer.output_samples(samples, samples.Length / 2); } public void DiscardSamples() { - buffer.clear(); + Buffer.clear(); } } @@ -104,7 +156,6 @@ namespace BizHawk.Emulation.Common } } - class ZeromusSynchronizer : ISynchronizingAudioBuffer { public ZeromusSynchronizer() diff --git a/BizHawk.Emulation.Common/Sound/Utilities/SoundMixer.cs b/BizHawk.Emulation.Common/Sound/Utilities/SoundMixer.cs index b3e25556d2..d4ab8b8e27 100644 --- a/BizHawk.Emulation.Common/Sound/Utilities/SoundMixer.cs +++ b/BizHawk.Emulation.Common/Sound/Utilities/SoundMixer.cs @@ -33,13 +33,17 @@ namespace BizHawk.Emulation.Common public void DiscardSamples() { foreach (var soundSource in SoundProviders) + { soundSource.DiscardSamples(); + } } public void GetSamples(short[] samples) { foreach (var soundSource in SoundProviders) + { soundSource.GetSamples(samples); + } } // Splits the volume space equally between available sources. diff --git a/BizHawk.Emulation.Common/Sound/Utilities/SpeexResampler.cs b/BizHawk.Emulation.Common/Sound/Utilities/SpeexResampler.cs index 7dd47c42b3..3edc4aa6ff 100644 --- a/BizHawk.Emulation.Common/Sound/Utilities/SpeexResampler.cs +++ b/BizHawk.Emulation.Common/Sound/Utilities/SpeexResampler.cs @@ -6,7 +6,7 @@ namespace BizHawk.Emulation.Common /// /// junk wrapper around LibSpeexDSP. quite inefficient. will be replaced /// - public class SpeexResampler : IDisposable, ISyncSoundProvider + public class SpeexResampler : IDisposable, ISoundProvider { static class LibSpeexDSP { @@ -250,7 +250,6 @@ namespace BizHawk.Emulation.Common public static extern string speex_resampler_strerror(RESAMPLER_ERR err); } - /// /// opaque pointer to state /// @@ -266,12 +265,12 @@ namespace BizHawk.Emulation.Common private short[] outbuf; - // for ISyncSoundProvider + // for sync private short[] outbuf2 = new short[16]; private int outbuf2pos = 0; // to accept an ISyncSoundProvder input - private readonly ISyncSoundProvider input; + private readonly ISoundProvider input; /// /// in buffer position in samples (not sample pairs) @@ -309,7 +308,7 @@ namespace BizHawk.Emulation.Common /// sampling rate out, rounded to nearest hz /// function which accepts output as produced. if null, act as an ISyncSoundProvider /// source to take input from when output is requested. if null, no autofetching - public SpeexResampler(int quality, uint rationum, uint ratioden, uint sratein, uint srateout, Action drainer = null, ISyncSoundProvider input = null) + public SpeexResampler(int 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 ISyncSoundProvider?"); @@ -417,13 +416,13 @@ namespace BizHawk.Emulation.Common outbuf2pos += nsamp * 2; } - public void GetSamples(out short[] samples, out int nsamp) + public void GetSamplesSync(out short[] samples, out int nsamp) { if (input != null) { short[] sampin; int nsampin; - input.GetSamples(out sampin, out nsampin); + input.GetSamplesSync(out sampin, out nsampin); EnqueueSamples(sampin, nsampin); } Flush(); @@ -436,6 +435,29 @@ namespace BizHawk.Emulation.Common { outbuf2pos = 0; } + + public bool CanProvideAsync + { + get { return false; } + } + + public SyncSoundMode SyncMode + { + get { return SyncSoundMode.Sync; } + } + + public void GetSamplesAsync(short[] samples) + { + throw new InvalidOperationException("Async mode is not supported."); + } + + public void SetSyncMode(SyncSoundMode mode) + { + if (mode == SyncSoundMode.Async) + { + throw new NotSupportedException("Async mode is not supported."); + } + } } } diff --git a/BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj b/BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj index 3843490ddf..3e039f2b53 100644 --- a/BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj +++ b/BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj @@ -160,9 +160,6 @@ - - AppleII.cs - AppleII.cs @@ -181,6 +178,9 @@ AppleII.cs + + AppleII.cs + AppleII.cs @@ -375,6 +375,9 @@ Lynx.cs + + Lynx.cs + Lynx.cs @@ -397,6 +400,9 @@ ColecoVision.cs + + ColecoVision.cs + @@ -439,6 +445,9 @@ Gambatte.cs + + Gambatte.cs + Gambatte.cs @@ -514,6 +523,9 @@ VBANext.cs + + VBANext.cs + VBANext.cs @@ -871,6 +883,9 @@ QuickNES.cs + + QuickNES.cs + QuickNES.cs @@ -905,6 +920,9 @@ + + GPGX.cs + @@ -934,6 +952,9 @@ GPGX.cs + + GPGX.cs + GPGX.cs @@ -1034,6 +1055,9 @@ SMS.cs + + SMS.cs + SMS.cs @@ -1077,6 +1101,9 @@ WonderSwan.cs + + WonderSwan.cs + WonderSwan.cs diff --git a/BizHawk.Emulation.Cores/Calculator/TI83.IEmulator.cs b/BizHawk.Emulation.Cores/Calculator/TI83.IEmulator.cs index cb4b2df065..3db6ff3bfe 100644 --- a/BizHawk.Emulation.Cores/Calculator/TI83.IEmulator.cs +++ b/BizHawk.Emulation.Cores/Calculator/TI83.IEmulator.cs @@ -6,23 +6,6 @@ namespace BizHawk.Emulation.Cores.Calculators { public IEmulatorServiceProvider ServiceProvider { get; private set; } - public IAsyncSoundProvider SoundProvider - { - get { return NullSound.SilenceProvider; } - } - - public ISyncSoundProvider SyncSoundProvider - { - get { return new FakeSyncSound(NullSound.SilenceProvider, 735); } - } - - public bool StartAsyncSound() - { - return true; - } - - public void EndAsyncSound() { } - public ControllerDefinition ControllerDefinition { get { return TI83Controller; } diff --git a/BizHawk.Emulation.Cores/Calculator/TI83.cs b/BizHawk.Emulation.Cores/Calculator/TI83.cs index eddf4c7b2a..2a496c9ca6 100644 --- a/BizHawk.Emulation.Cores/Calculator/TI83.cs +++ b/BizHawk.Emulation.Cores/Calculator/TI83.cs @@ -1,10 +1,5 @@ using System; using System.Globalization; -using System.IO; -using System.Collections.Generic; - -using BizHawk.Common; -using BizHawk.Common.NumberExtensions; using BizHawk.Emulation.Common; using BizHawk.Emulation.Cores.Components.Z80; @@ -19,7 +14,7 @@ namespace BizHawk.Emulation.Cores.Calculators isPorted: false, isReleased: true )] - [ServiceNotApplicable(typeof(ISaveRam), typeof(IRegionable), typeof(IDriveLight))] + [ServiceNotApplicable(typeof(ISoundProvider), typeof(ISaveRam), typeof(IRegionable), typeof(IDriveLight))] public partial class TI83 : IEmulator, IVideoProvider, IStatable, IDebuggable, IInputPollable, ISettable { [CoreConstructor("TI83")] diff --git a/BizHawk.Emulation.Cores/Computers/AppleII/AppleII.IAudioProvider.cs b/BizHawk.Emulation.Cores/Computers/AppleII/AppleII.IAudioProvider.cs deleted file mode 100644 index c22f997767..0000000000 --- a/BizHawk.Emulation.Cores/Computers/AppleII/AppleII.IAudioProvider.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using BizHawk.Emulation.Common; - -namespace BizHawk.Emulation.Cores.Computers.AppleII -{ - partial class AppleII : ISyncSoundProvider - { - void ISyncSoundProvider.GetSamples(out short[] samples, out int nsamp) - { - _machine.Speaker.AudioService.GetSamples(out samples, out nsamp); - } - - void ISyncSoundProvider.DiscardSamples() - { - _machine.Speaker.AudioService.Clear(); - } - } -} diff --git a/BizHawk.Emulation.Cores/Computers/AppleII/AppleII.IEmulator.cs b/BizHawk.Emulation.Cores/Computers/AppleII/AppleII.IEmulator.cs index a51505f033..62eac4baf5 100644 --- a/BizHawk.Emulation.Cores/Computers/AppleII/AppleII.IEmulator.cs +++ b/BizHawk.Emulation.Cores/Computers/AppleII/AppleII.IEmulator.cs @@ -6,27 +6,6 @@ namespace BizHawk.Emulation.Cores.Computers.AppleII { public IEmulatorServiceProvider ServiceProvider { get; private set; } - [FeatureNotImplemented] - public IAsyncSoundProvider SoundProvider - { - get { return null; } - } - - [FeatureNotImplemented] - public ISyncSoundProvider SyncSoundProvider - { - get { return this; } - } - - [FeatureNotImplemented] - public bool StartAsyncSound() - { - return false; - } - - [FeatureNotImplemented] - public void EndAsyncSound() { } - public ControllerDefinition ControllerDefinition { get { return AppleIIController; } @@ -36,9 +15,15 @@ namespace BizHawk.Emulation.Cores.Computers.AppleII public int Frame { get; set; } - public string SystemId { get { return "AppleII"; } } + public string SystemId + { + get { return "AppleII"; } + } - public bool DeterministicEmulation { get { return true; } } + public bool DeterministicEmulation + { + get { return true; } + } public void FrameAdvance(bool render, bool rendersound) { diff --git a/BizHawk.Emulation.Cores/Computers/AppleII/AppleII.ISoundProvider.cs b/BizHawk.Emulation.Cores/Computers/AppleII/AppleII.ISoundProvider.cs new file mode 100644 index 0000000000..c1bf7a9971 --- /dev/null +++ b/BizHawk.Emulation.Cores/Computers/AppleII/AppleII.ISoundProvider.cs @@ -0,0 +1,41 @@ +using System; +using BizHawk.Emulation.Common; + +namespace BizHawk.Emulation.Cores.Computers.AppleII +{ + public partial class AppleII : ISoundProvider + { + public bool CanProvideAsync + { + get { return false; } + } + + public void GetSamplesSync(out short[] samples, out int nsamp) + { + _machine.Speaker.AudioService.GetSamples(out samples, out nsamp); + } + + public void DiscardSamples() + { + _machine.Speaker.AudioService.Clear(); + } + + public SyncSoundMode SyncMode + { + get { return SyncSoundMode.Sync; } + } + + public void SetSyncMode(SyncSoundMode mode) + { + if (mode == SyncSoundMode.Async) + { + throw new NotSupportedException("Async mode is not supported."); + } + } + + public void GetSamplesAsync(short[] samples) + { + throw new InvalidOperationException("Async mode is not supported."); + } + } +} diff --git a/BizHawk.Emulation.Cores/Computers/AppleII/AppleII.cs b/BizHawk.Emulation.Cores/Computers/AppleII/AppleII.cs index 6809383508..53eca5a958 100644 --- a/BizHawk.Emulation.Cores/Computers/AppleII/AppleII.cs +++ b/BizHawk.Emulation.Cores/Computers/AppleII/AppleII.cs @@ -13,7 +13,7 @@ namespace BizHawk.Emulation.Cores.Computers.AppleII isReleased: true )] [ServiceNotApplicable(typeof(ISaveRam), typeof(IRegionable))] - public partial class AppleII : IEmulator, IDriveLight + public partial class AppleII : IEmulator, ISoundProvider, IVideoProvider, IStatable, IDriveLight { public AppleII(CoreComm comm, IEnumerable gameInfoSet, IEnumerable romSet, Settings settings) : this(comm, gameInfoSet.First(), romSet.First(), settings) diff --git a/BizHawk.Emulation.Cores/Computers/Commodore64/C64.cs b/BizHawk.Emulation.Cores/Computers/Commodore64/C64.cs index b3330e59e8..cb3aa2a51e 100644 --- a/BizHawk.Emulation.Cores/Computers/Commodore64/C64.cs +++ b/BizHawk.Emulation.Cores/Computers/Commodore64/C64.cs @@ -83,8 +83,10 @@ namespace BizHawk.Emulation.Cores.Computers.Commodore64 if (_board.Sid != null) { - SyncSoundProvider = DCFilter.AsISyncSoundProvider(_board.Sid, 512); - } + _soundProvider = new DCFilter(_board.Sid, 512); + ((BasicServiceProvider)ServiceProvider).Register(_soundProvider); + } + DeterministicEmulation = true; ((BasicServiceProvider) ServiceProvider).Register(_board.Vic); @@ -136,13 +138,8 @@ namespace BizHawk.Emulation.Cores.Computers.Commodore64 public bool DeterministicEmulation { get; set; } [SaveState.SaveWithName("Frame")] public int Frame { get; set; } - public bool StartAsyncSound() { return false; } - public void EndAsyncSound() { } - [SaveState.DoNotSave] - public IAsyncSoundProvider SoundProvider { get { return null; } } - [SaveState.DoNotSave] - public ISyncSoundProvider SyncSoundProvider { get; private set; } - [SaveState.DoNotSave] + + [SaveState.DoNotSave] public ControllerDefinition ControllerDefinition { get { return C64ControllerDefinition; } } [SaveState.DoNotSave] public IController Controller { get { return _board.Controller; } set { _board.Controller = value; } } @@ -167,9 +164,16 @@ namespace BizHawk.Emulation.Cores.Computers.Commodore64 while (_frameCycles != 0); } - #endregion + #endregion - private void DoCycle() + #region ISoundProvider + + [SaveState.DoNotSave] + public ISoundProvider _soundProvider { get; private set; } + + #endregion + + private void DoCycle() { if (_frameCycles == 0) { _board.InputRead = false; diff --git a/BizHawk.Emulation.Cores/Computers/Commodore64/MOS/Sid.SoundProvider.cs b/BizHawk.Emulation.Cores/Computers/Commodore64/MOS/Sid.SoundProvider.cs index 8fdade9814..bb19ff969f 100644 --- a/BizHawk.Emulation.Cores/Computers/Commodore64/MOS/Sid.SoundProvider.cs +++ b/BizHawk.Emulation.Cores/Computers/Commodore64/MOS/Sid.SoundProvider.cs @@ -1,41 +1,57 @@ using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; using BizHawk.Emulation.Common; namespace BizHawk.Emulation.Cores.Computers.Commodore64.MOS { - public sealed partial class Sid : IAsyncSoundProvider, ISyncSoundProvider - { - public int MaxVolume - { - get { return short.MaxValue; } - set { } - } + public sealed partial class Sid : ISoundProvider + { + public bool CanProvideAsync + { + get { return false; } + } - public void DiscardSamples() - { - _outputBufferIndex = 0; - } + public SyncSoundMode SyncMode + { + get { return SyncSoundMode.Sync; } + } - public void GetSamples(short[] samples) - { - Flush(); - var length = Math.Min(samples.Length, _outputBufferIndex); - for (var i = 0; i < length; i++) - { - samples[i] = _outputBuffer[i]; - } - _outputBufferIndex = 0; - } + public void SetSyncMode(SyncSoundMode mode) + { + if (mode != SyncSoundMode.Sync) + { + throw new InvalidOperationException("Only Sync mode is supported."); + } + } - public void GetSamples(out short[] samples, out int nsamp) - { - Flush(); - samples = _outputBuffer; - nsamp = _outputBufferIndex >> 1; - _outputBufferIndex = 0; - } - } + public void GetSamplesAsync(short[] samples) + { + throw new NotSupportedException("Async is not available"); + } + + public void DiscardSamples() + { + _outputBufferIndex = 0; + } + + // Expose this as GetSamplesAsync to support async sound + // There's not need to do this though unless this core wants to handle async in its own way (the client can handle these situations if not available from the core) + private void GetSamples(short[] samples) + { + Flush(); + var length = Math.Min(samples.Length, _outputBufferIndex); + for (var i = 0; i < length; i++) + { + samples[i] = _outputBuffer[i]; + } + _outputBufferIndex = 0; + } + + public void GetSamplesSync(out short[] samples, out int nsamp) + { + Flush(); + samples = _outputBuffer; + nsamp = _outputBufferIndex >> 1; + _outputBufferIndex = 0; + } + } } diff --git a/BizHawk.Emulation.Cores/Consoles/Atari/2600/Atari2600.Core.cs b/BizHawk.Emulation.Cores/Consoles/Atari/2600/Atari2600.Core.cs index eb4b1f49af..bc014ec085 100644 --- a/BizHawk.Emulation.Cores/Consoles/Atari/2600/Atari2600.Core.cs +++ b/BizHawk.Emulation.Cores/Consoles/Atari/2600/Atari2600.Core.cs @@ -310,11 +310,11 @@ namespace BizHawk.Emulation.Cores.Atari.Atari2600 _pal = DetectPal(_game, Rom); } - _tia = new TIA(this, _pal, Settings.SECAMColors); + // dcfilter coefficent is from real observed hardware behavior: a latched "1" will fully decay by ~170 or so tia sound cycles + _tia = new TIA(this, _pal, Settings.SECAMColors, CoreComm.VsyncRate > 55.0 ? 735 : 882); _tia.GetFrameRate(out CoreComm.VsyncNum, out CoreComm.VsyncDen); - // dcfilter coefficent is from real observed hardware behavior: a latched "1" will fully decay by ~170 or so tia sound cycles - _dcfilter = DCFilter.AsISoundProvider(_tia, 256); + _dcfilter = new DCFilter(_tia, 256); M6532 = new M6532(this); diff --git a/BizHawk.Emulation.Cores/Consoles/Atari/2600/Atari2600.cs b/BizHawk.Emulation.Cores/Consoles/Atari/2600/Atari2600.cs index 41db6d6185..b777ef74ee 100644 --- a/BizHawk.Emulation.Cores/Consoles/Atari/2600/Atari2600.cs +++ b/BizHawk.Emulation.Cores/Consoles/Atari/2600/Atari2600.cs @@ -65,6 +65,7 @@ namespace BizHawk.Emulation.Cores.Atari.Atari2600 ser.Register(Cpu); ser.Register(Tracer); ser.Register(_tia); + ser.Register(_dcfilter); } public IEmulatorServiceProvider ServiceProvider { get; private set; } @@ -80,17 +81,6 @@ namespace BizHawk.Emulation.Cores.Atari.Atari2600 public CoreComm CoreComm { get; private set; } - public IAsyncSoundProvider SoundProvider { get { return _dcfilter; } } - - // todo: make this not so ugly - public ISyncSoundProvider SyncSoundProvider - { - get - { - return new FakeSyncSound(_dcfilter, CoreComm.VsyncRate > 55.0 ? 735 : 882); - } - } - public ControllerDefinition ControllerDefinition { get { return Atari2600ControllerDefinition; } } public IController Controller { get; set; } @@ -123,10 +113,6 @@ namespace BizHawk.Emulation.Cores.Atari.Atari2600 }; } - public bool StartAsyncSound() { return true; } - - public void EndAsyncSound() { } - public void ResetCounters() { _frame = 0; diff --git a/BizHawk.Emulation.Cores/Consoles/Atari/2600/Tia/TIA.cs b/BizHawk.Emulation.Cores/Consoles/Atari/2600/Tia/TIA.cs index 01f520baad..724ad65783 100644 --- a/BizHawk.Emulation.Cores/Consoles/Atari/2600/Tia/TIA.cs +++ b/BizHawk.Emulation.Cores/Consoles/Atari/2600/Tia/TIA.cs @@ -6,1607 +6,1647 @@ using System.Numerics; namespace BizHawk.Emulation.Cores.Atari.Atari2600 { - // Emulates the TIA - public partial class TIA : IVideoProvider, IAsyncSoundProvider - { - - #region palette - - const int BackColor = unchecked((int)0xff000000); - - static TIA() - { - // add alpha to palette entries - for (int i = 0; i < PALPalette.Length; i++) - PALPalette[i] |= unchecked((int)0xff000000); - for (int i = 0; i < NTSCPalette.Length; i++) - NTSCPalette[i] |= unchecked((int)0xff000000); - } - - private static readonly int[] PALPalette = - { - 0x000000, 0x000000, 0x2b2b2b, 0x2b2b2b, - 0x525252, 0x525252, 0x767676, 0x767676, - 0x979797, 0x979797, 0xb6b6b6, 0xb6b6b6, - 0xd2d2d2, 0xd2d2d2, 0xececec, 0xececec, - - 0x000000, 0x000000, 0x2b2b2b, 0x2b2b2b, - 0x525252, 0x525252, 0x767676, 0x767676, - 0x979797, 0x979797, 0xb6b6b6, 0xb6b6b6, - 0xd2d2d2, 0xd2d2d2, 0xececec, 0xececec, - - 0x805800, 0x000000, 0x96711a, 0x2b2b2b, - 0xab8732, 0x525252, 0xbe9c48, 0x767676, - 0xcfaf5c, 0x979797, 0xdfc06f, 0xb6b6b6, - 0xeed180, 0xd2d2d2, 0xfce090, 0xececec, - - 0x445c00, 0x000000, 0x5e791a, 0x2b2b2b, - 0x769332, 0x525252, 0x8cac48, 0x767676, - 0xa0c25c, 0x979797, 0xb3d76f, 0xb6b6b6, - 0xc4ea80, 0xd2d2d2, 0xd4fc90, 0xececec, - - 0x703400, 0x000000, 0x89511a, 0x2b2b2b, - 0xa06b32, 0x525252, 0xb68448, 0x767676, - 0xc99a5c, 0x979797, 0xdcaf6f, 0xb6b6b6, - 0xecc280, 0xd2d2d2, 0xfcd490, 0xececec, - - 0x006414, 0x000000, 0x1a8035, 0x2b2b2b, - 0x329852, 0x525252, 0x48b06e, 0x767676, - 0x5cc587, 0x979797, 0x6fd99e, 0xb6b6b6, - 0x80ebb4, 0xd2d2d2, 0x90fcc8, 0xececec, - - 0x700014, 0x000000, 0x891a35, 0x2b2b2b, - 0xa03252, 0x525252, 0xb6486e, 0x767676, - 0xc95c87, 0x979797, 0xdc6f9e, 0xb6b6b6, - 0xec80b4, 0xd2d2d2, 0xfc90c8, 0xececec, - - 0x005c5c, 0x000000, 0x1a7676, 0x2b2b2b, - 0x328e8e, 0x525252, 0x48a4a4, 0x767676, - 0x5cb8b8, 0x979797, 0x6fcbcb, 0xb6b6b6, - 0x80dcdc, 0xd2d2d2, 0x90ecec, 0xececec, - - 0x70005c, 0x000000, 0x841a74, 0x2b2b2b, - 0x963289, 0x525252, 0xa8489e, 0x767676, - 0xb75cb0, 0x979797, 0xc66fc1, 0xb6b6b6, - 0xd380d1, 0xd2d2d2, 0xe090e0, 0xececec, - - 0x003c70, 0x000000, 0x195a89, 0x2b2b2b, - 0x2f75a0, 0x525252, 0x448eb6, 0x767676, - 0x57a5c9, 0x979797, 0x68badc, 0xb6b6b6, - 0x79ceec, 0xd2d2d2, 0x88e0fc, 0xececec, - - 0x580070, 0x000000, 0x6e1a89, 0x2b2b2b, - 0x8332a0, 0x525252, 0x9648b6, 0x767676, - 0xa75cc9, 0x979797, 0xb76fdc, 0xb6b6b6, - 0xc680ec, 0xd2d2d2, 0xd490fc, 0xececec, - - 0x002070, 0x000000, 0x193f89, 0x2b2b2b, - 0x2f5aa0, 0x525252, 0x4474b6, 0x767676, - 0x578bc9, 0x979797, 0x68a1dc, 0xb6b6b6, - 0x79b5ec, 0xd2d2d2, 0x88c8fc, 0xececec, - - 0x340080, 0x000000, 0x4a1a96, 0x2b2b2b, - 0x5f32ab, 0x525252, 0x7248be, 0x767676, - 0x835ccf, 0x979797, 0x936fdf, 0xb6b6b6, - 0xa280ee, 0xd2d2d2, 0xb090fc, 0xececec, - - 0x000088, 0x000000, 0x1a1a9d, 0x2b2b2b, - 0x3232b0, 0x525252, 0x4848c2, 0x767676, - 0x5c5cd2, 0x979797, 0x6f6fe1, 0xb6b6b6, - 0x8080ef, 0xd2d2d2, 0x9090fc, 0xececec, - - 0x000000, 0x000000, 0x2b2b2b, 0x2b2b2b, - 0x525252, 0x525252, 0x767676, 0x767676, - 0x979797, 0x979797, 0xb6b6b6, 0xb6b6b6, - 0xd2d2d2, 0xd2d2d2, 0xececec, 0xececec, - - 0x000000, 0x000000, 0x2b2b2b, 0x2b2b2b, - 0x525252, 0x525252, 0x767676, 0x767676, - 0x979797, 0x979797, 0xb6b6b6, 0xb6b6b6, - 0xd2d2d2, 0xd2d2d2, 0xececec, 0xececec - }; - - private static readonly int[] NTSCPalette = - { - 0x000000, 0, 0x4a4a4a, 0, 0x6f6f6f, 0, 0x8e8e8e, 0, - 0xaaaaaa, 0, 0xc0c0c0, 0, 0xd6d6d6, 0, 0xececec, 0, - 0x484800, 0, 0x69690f, 0, 0x86861d, 0, 0xa2a22a, 0, - 0xbbbb35, 0, 0xd2d240, 0, 0xe8e84a, 0, 0xfcfc54, 0, - 0x7c2c00, 0, 0x904811, 0, 0xa26221, 0, 0xb47a30, 0, - 0xc3903d, 0, 0xd2a44a, 0, 0xdfb755, 0, 0xecc860, 0, - 0x901c00, 0, 0xa33915, 0, 0xb55328, 0, 0xc66c3a, 0, - 0xd5824a, 0, 0xe39759, 0, 0xf0aa67, 0, 0xfcbc74, 0, - 0x940000, 0, 0xa71a1a, 0, 0xb83232, 0, 0xc84848, 0, - 0xd65c5c, 0, 0xe46f6f, 0, 0xf08080, 0, 0xfc9090, 0, - 0x840064, 0, 0x97197a, 0, 0xa8308f, 0, 0xb846a2, 0, - 0xc659b3, 0, 0xd46cc3, 0, 0xe07cd2, 0, 0xec8ce0, 0, - 0x500084, 0, 0x68199a, 0, 0x7d30ad, 0, 0x9246c0, 0, - 0xa459d0, 0, 0xb56ce0, 0, 0xc57cee, 0, 0xd48cfc, 0, - 0x140090, 0, 0x331aa3, 0, 0x4e32b5, 0, 0x6848c6, 0, - 0x7f5cd5, 0, 0x956fe3, 0, 0xa980f0, 0, 0xbc90fc, 0, - 0x000094, 0, 0x181aa7, 0, 0x2d32b8, 0, 0x4248c8, 0, - 0x545cd6, 0, 0x656fe4, 0, 0x7580f0, 0, 0x8490fc, 0, - 0x001c88, 0, 0x183b9d, 0, 0x2d57b0, 0, 0x4272c2, 0, - 0x548ad2, 0, 0x65a0e1, 0, 0x75b5ef, 0, 0x84c8fc, 0, - 0x003064, 0, 0x185080, 0, 0x2d6d98, 0, 0x4288b0, 0, - 0x54a0c5, 0, 0x65b7d9, 0, 0x75cceb, 0, 0x84e0fc, 0, - 0x004030, 0, 0x18624e, 0, 0x2d8169, 0, 0x429e82, 0, - 0x54b899, 0, 0x65d1ae, 0, 0x75e7c2, 0, 0x84fcd4, 0, - 0x004400, 0, 0x1a661a, 0, 0x328432, 0, 0x48a048, 0, - 0x5cba5c, 0, 0x6fd26f, 0, 0x80e880, 0, 0x90fc90, 0, - 0x143c00, 0, 0x355f18, 0, 0x527e2d, 0, 0x6e9c42, 0, - 0x87b754, 0, 0x9ed065, 0, 0xb4e775, 0, 0xc8fc84, 0, - 0x303800, 0, 0x505916, 0, 0x6d762b, 0, 0x88923e, 0, - 0xa0ab4f, 0, 0xb7c25f, 0, 0xccd86e, 0, 0xe0ec7c, 0, - 0x482c00, 0, 0x694d14, 0, 0x866a26, 0, 0xa28638, 0, - 0xbb9f47, 0, 0xd2b656, 0, 0xe8cc63, 0, 0xfce070, 0 - }; - - private static readonly int[] SECAMPalette = - { - 0x000000,0x000000,0x2121FF,0x2121FF, - 0xF03C79,0xF03C79,0xFF50FF,0xFF50FF, - 0x7FFF00,0x7FFF00,0x7FFFFF,0x7FFFFF, - 0xFFFF3F,0xFFFF3F,0xffffff,0xffffff, - - 0x000000,0x000000,0x2121FF,0x2121FF, - 0xF03C79,0xF03C79,0xFF50FF,0xFF50FF, - 0x7FFF00,0x7FFF00,0x7FFFFF,0x7FFFFF, - 0xFFFF3F,0xFFFF3F,0xffffff,0xffffff, - - 0x000000,0x000000,0x2121FF,0x2121FF, - 0xF03C79,0xF03C79,0xFF50FF,0xFF50FF, - 0x7FFF00,0x7FFF00,0x7FFFFF,0x7FFFFF, - 0xFFFF3F,0xFFFF3F,0xffffff,0xffffff, - - 0x000000,0x000000,0x2121FF,0x2121FF, - 0xF03C79,0xF03C79,0xFF50FF,0xFF50FF, - 0x7FFF00,0x7FFF00,0x7FFFFF,0x7FFFFF, - 0xFFFF3F,0xFFFF3F,0xffffff,0xffffff, - - 0x000000,0x000000,0x2121FF,0x2121FF, - 0xF03C79,0xF03C79,0xFF50FF,0xFF50FF, - 0x7FFF00,0x7FFF00,0x7FFFFF,0x7FFFFF, - 0xFFFF3F,0xFFFF3F,0xffffff,0xffffff, - - 0x000000,0x000000,0x2121FF,0x2121FF, - 0xF03C79,0xF03C79,0xFF50FF,0xFF50FF, - 0x7FFF00,0x7FFF00,0x7FFFFF,0x7FFFFF, - 0xFFFF3F,0xFFFF3F,0xffffff,0xffffff, - - 0x000000,0x000000,0x2121FF,0x2121FF, - 0xF03C79,0xF03C79,0xFF50FF,0xFF50FF, - 0x7FFF00,0x7FFF00,0x7FFFFF,0x7FFFFF, - 0xFFFF3F,0xFFFF3F,0xffffff,0xffffff, - - 0x000000,0x000000,0x2121FF,0x2121FF, - 0xF03C79,0xF03C79,0xFF50FF,0xFF50FF, - 0x7FFF00,0x7FFF00,0x7FFFFF,0x7FFFFF, - 0xFFFF3F,0xFFFF3F,0xffffff,0xffffff, - - 0x000000,0x000000,0x2121FF,0x2121FF, - 0xF03C79,0xF03C79,0xFF50FF,0xFF50FF, - 0x7FFF00,0x7FFF00,0x7FFFFF,0x7FFFFF, - 0xFFFF3F,0xFFFF3F,0xffffff,0xffffff, - - 0x000000,0x000000,0x2121FF,0x2121FF, - 0xF03C79,0xF03C79,0xFF50FF,0xFF50FF, - 0x7FFF00,0x7FFF00,0x7FFFFF,0x7FFFFF, - 0xFFFF3F,0xFFFF3F,0xffffff,0xffffff, - - 0x000000,0x000000,0x2121FF,0x2121FF, - 0xF03C79,0xF03C79,0xFF50FF,0xFF50FF, - 0x7FFF00,0x7FFF00,0x7FFFFF,0x7FFFFF, - 0xFFFF3F,0xFFFF3F,0xffffff,0xffffff, - - 0x000000,0x000000,0x2121FF,0x2121FF, - 0xF03C79,0xF03C79,0xFF50FF,0xFF50FF, - 0x7FFF00,0x7FFF00,0x7FFFFF,0x7FFFFF, - 0xFFFF3F,0xFFFF3F,0xffffff,0xffffff, - - 0x000000,0x000000,0x2121FF,0x2121FF, - 0xF03C79,0xF03C79,0xFF50FF,0xFF50FF, - 0x7FFF00,0x7FFF00,0x7FFFFF,0x7FFFFF, - 0xFFFF3F,0xFFFF3F,0xffffff,0xffffff, - - 0x000000,0x000000,0x2121FF,0x2121FF, - 0xF03C79,0xF03C79,0xFF50FF,0xFF50FF, - 0x7FFF00,0x7FFF00,0x7FFFFF,0x7FFFFF, - 0xFFFF3F,0xFFFF3F,0xffffff,0xffffff, - - 0x000000,0x000000,0x2121FF,0x2121FF, - 0xF03C79,0xF03C79,0xFF50FF,0xFF50FF, - 0x7FFF00,0x7FFF00,0x7FFFFF,0x7FFFFF, - 0xFFFF3F,0xFFFF3F,0xffffff,0xffffff, - - 0x000000,0x000000,0x2121FF,0x2121FF, - 0xF03C79,0xF03C79,0xFF50FF,0xFF50FF, - 0x7FFF00,0x7FFF00,0x7FFFFF,0x7FFFFF, - 0xFFFF3F,0xFFFF3F,0xffffff,0xffffff, - }; - - #endregion - - // in all cases, the TIA has 228 clocks per scanline - // the NTSC TIA has a clock rate of 3579575hz - // the PAL/SECAM TIA has a clock rate of 3546894hz - - private bool _pal; - - public int NominalNumScanlines - { - get - { - return _pal ? 312 : 262; - } - } - - public void GetFrameRate(out int num, out int den) - { - // TODO when sound timing is made exact: - // NTSC refclock is actually 315 / 88 mhz - //3546895 - - int clockrate = _pal ? 3546895 : 3579545; - int clocksperframe = 228 * NominalNumScanlines; - int gcd = (int)BigInteger.GreatestCommonDivisor(clockrate, clocksperframe); - num = clockrate / gcd; - den = clocksperframe / gcd; - } - - private const int ScreenWidth = 160; - private const int MaxScreenHeight = 312; - - private const byte CXP0 = 0x01; - private const byte CXP1 = 0x02; - private const byte CXM0 = 0x04; - private const byte CXM1 = 0x08; - private const byte CXPF = 0x10; - private const byte CXBL = 0x20; - - private readonly Atari2600 _core; - private int[] _scanlinebuffer = new int[ScreenWidth * MaxScreenHeight]; - - private int[] _palette; - - public int bus_state; - - private byte pf0_update; - private byte pf1_update; - private byte pf2_update; - private bool pf0_updater; - private bool pf1_updater; - private bool pf2_updater; - private byte pf0_delay_clock; - private byte pf1_delay_clock; - private byte pf2_delay_clock; - private byte pf0_max_delay; - private byte pf1_max_delay; - private byte pf2_max_delay; - - private int enam0_delay; - private int enam1_delay; - private int enamb_delay; - private bool enam0_val; - private bool enam1_val; - private bool enamb_val; - - private int vblank_delay; - private byte vblank_value; - - private bool p0_stuff; - private bool p1_stuff; - private bool m0_stuff; - private bool m1_stuff; - private bool b_stuff; - - private int HMP0_delay; - private byte HMP0_val; - private int HMP1_delay; - private byte HMP1_val; - - private int prg0_delay; - private int prg1_delay; - private byte prg0_val; - private byte prg1_val; - - private bool do_ticks; - - private byte _hsyncCnt; - private int _capChargeStart; - private bool _capCharging; - private bool _vblankEnabled; - private bool _vsyncEnabled; - private int _CurrentScanLine; - public int _audioClocks; // not savestated - - private PlayerData _player0; - private PlayerData _player1; - private PlayfieldData _playField; - private HMoveData _hmove; - private BallData _ball; - - public Audio[] AUD = { new Audio(), new Audio() }; - - // current audio register state used to sample correct positions in the scanline (clrclk 0 and 114) - //public byte[] current_audio_register = new byte[6]; - public short[] _local_audio_cycles = new short[2000]; - - public TIA(Atari2600 core, bool pal, bool secam) - { - _core = core; - _player0.ScanCnt = 8; - _player1.ScanCnt = 8; - _pal = pal; - SetSECAM(secam); - } - - public void SetSECAM(bool secam) - { - _palette = _pal ? secam ? SECAMPalette : PALPalette : NTSCPalette; - } - - public int CurrentScanLine - { - get { return _CurrentScanLine; } - } - - public bool IsVBlank - { - get { return _vblankEnabled; } - } - - public bool IsVSync - { - get { return _vsyncEnabled; } - } - - /// - /// a count of lines emulated; incremented by the TIA but not used by it - /// - public int LineCount { get; set; } - - /// - /// called at the end of a video frame. used internally - /// - public Action FrameEndCallBack { get; set; } - - public int VirtualWidth - { - // TODO: PAL? - get - { - if (_pal) - { - return 320; - } - - return 275; // 275 comes from NTSC specs and the actual pixel clock of a 2600 TIA - } - } - - public int VirtualHeight - { - get { return BufferHeight; } - } - - public int BufferWidth - { - get { return ScreenWidth; } - } - - public int BufferHeight - { - get - { - if (_pal) - return _core.Settings.PALBottomLine - _core.Settings.PALTopLine; - else - return _core.Settings.NTSCBottomLine - _core.Settings.NTSCTopLine; - } - } - - public int BackgroundColor - { - get { return _core.Settings.BackgroundColor.ToArgb(); } - } - - public int[] GetVideoBuffer() - { - return FrameBuffer; - } - - public void Reset() - { - _hsyncCnt = 0; - _capChargeStart = 0; - _capCharging = false; - _vblankEnabled = false; - vblank_delay = 0; - vblank_value = 0; - _vsyncEnabled = false; - _CurrentScanLine = 0; - _audioClocks = 0; - - bus_state = 0; - - pf0_update = 0; - pf1_update = 0; - pf2_update = 0; - pf0_updater = false; - pf1_updater = false; - pf2_updater = false; - pf0_delay_clock = 0; - pf1_delay_clock = 0; - pf2_delay_clock = 0; - pf0_max_delay = 0; - pf1_max_delay = 0; - pf2_max_delay = 0; - - enam0_delay = 0; - enam1_delay = 0; - enamb_delay = 0; - enam0_val = false; - enam1_val = false; - enamb_val = false; - - p0_stuff = false; - p1_stuff = false; - m0_stuff = false; - m1_stuff = false; - b_stuff = false; - - HMP0_delay = 0; - HMP0_val = 0; - HMP1_delay = 0; - HMP1_val = 0; - - prg0_delay = 0; - prg1_delay = 0; - prg0_val = 0; - prg1_val = 0; - - do_ticks = false; - - _player0 = new PlayerData(); - _player1 = new PlayerData(); - _playField = new PlayfieldData(); - _hmove = new HMoveData(); - _ball = new BallData(); - - _player0.ScanCnt = 8; - _player1.ScanCnt = 8; - } - - // Execute TIA cycles - public void Execute(int cycles) - { - // Still ignoring cycles... - - // delay vblank latch - if (vblank_delay > 0) - { - vblank_delay++; - if (vblank_delay==3) - { - _vblankEnabled = (vblank_value & 0x02) != 0; - vblank_delay = 0; - } - - } - - - - //delay latch to new playfield register - if (pf0_updater == true) - { - pf0_delay_clock++; - if (pf0_delay_clock > pf0_max_delay) - { - _playField.Grp = (uint)((_playField.Grp & 0x0FFFF) + ((ReverseBits(pf0_update, 8) & 0x0F) << 16)); - pf0_updater = false; - } - } - if (pf1_updater == true) - { - pf1_delay_clock++; - if (pf1_delay_clock > pf1_max_delay) - { - _playField.Grp = (uint)((_playField.Grp & 0xF00FF) + (pf1_update << 8)); - pf1_updater = false; - } - } - if (pf2_updater == true) - { - pf2_delay_clock++; - if (pf2_delay_clock > pf2_max_delay) - { - _playField.Grp = (uint)((_playField.Grp & 0xFFF00) + ReverseBits(pf2_update, 8)); - pf2_updater = false; - } - } - - //delay latch to missile enable - if (enam0_delay > 0) - { - enam0_delay++; - if (enam0_delay == 3) - { - enam0_delay = 0; - _player0.Missile.Enabled = enam0_val; - } - - } - - if (enam1_delay > 0) - { - enam1_delay++; - if (enam1_delay == 3) - { - enam1_delay = 0; - _player1.Missile.Enabled = enam1_val; - } - - } - - // delay latch to ball enable - if (enamb_delay > 0) - { - enamb_delay++; - if (enamb_delay == 3) - { - enamb_delay = 0; - _ball.Enabled = enamb_val; - } - - } - - // delay latch to player graphics registers - if (prg0_delay > 0) - { - prg0_delay++; - if (prg0_delay == 3) - { - prg0_delay = 0; - _player0.Grp = prg0_val; - _player1.Dgrp = _player1.Grp; - } - - } - - if (prg1_delay > 0) - { - prg1_delay++; - if (prg1_delay == 3) - { - prg1_delay = 0; - _player1.Grp = prg1_val; - _player0.Dgrp = _player0.Grp; - - // TODO: Find a game that uses this functionality and test it - _ball.Denabled = _ball.Enabled; - } - - } - - // HMP write delay - if (HMP0_delay > 0) - { - HMP0_delay++; - if (HMP0_delay == 4) - { - HMP0_delay = 0; - _player0.HM = HMP0_val; - } - } - - if (HMP1_delay > 0) - { - HMP1_delay++; - if (HMP1_delay == 3) - { - HMP1_delay = 0; - _player1.HM = HMP1_val; - } - } - - // Reset the RDY flag when we reach hblank - if (_hsyncCnt <= 0) - { - _core.Cpu.RDY = true; - - } - - // Assume we're on the left side of the screen for now - var rightSide = false; - - // ---- Things that happen only in the drawing section ---- - // TODO: Remove this magic number (17). It depends on the HMOVE - if ((_hsyncCnt) >= (_hmove.LateHBlankReset ? 76 : 68)) - { - do_ticks = false; - - // TODO: Remove this magic number - if ((_hsyncCnt / 4) >= 37) - { - rightSide = true; - } - - // The bit number of the PF data which we want - int pfBit = ((_hsyncCnt / 4) - 17) % 20; - - // Create the mask for the bit we want - // Note that bits are arranged 0 1 2 3 4 .. 19 - int pfMask = 1 << (20 - 1 - pfBit); - - // Reverse the mask if on the right and playfield is reflected - if (rightSide && _playField.Reflect) - { - pfMask = ReverseBits(pfMask, 20); - } - - // Calculate collisions - byte collisions = 0x00; - - if ((_playField.Grp & pfMask) != 0) - { - collisions |= CXPF; - } - - - // ---- Player 0 ---- - collisions |= _player0.Tick() ? CXP0 : (byte)0x00; - - // ---- Missile 0 ---- - collisions |= _player0.Missile.Tick() ? CXM0 : (byte)0x00; - - // ---- Player 1 ---- - collisions |= _player1.Tick() ? CXP1 : (byte)0x00; - - // ---- Missile 0 ---- - collisions |= _player1.Missile.Tick() ? CXM1 : (byte)0x00; - - // ---- Ball ---- - collisions |= _ball.Tick() ? CXBL : (byte)0x00; - - - // Pick the pixel color from collisions - int pixelColor = BackColor; - if (_core.Settings.ShowBG) - { - pixelColor = _palette[_playField.BkColor]; - } - - if ((collisions & CXPF) != 0 && _core.Settings.ShowPlayfield) - { - if (_playField.Score) - { - if (!rightSide) - { - pixelColor = _palette[_player0.Color]; - } - else - { - pixelColor = _palette[_player1.Color]; - } - } - else - { - pixelColor = _palette[_playField.PfColor]; - } - } - - if ((collisions & CXBL) != 0) - { - _ball.Collisions |= collisions; - if (_core.Settings.ShowBall) - { - pixelColor = _palette[_playField.PfColor]; - } - } - - if ((collisions & CXM1) != 0) - { - _player1.Missile.Collisions |= collisions; - if (_core.Settings.ShowMissle2) - { - pixelColor = _palette[_player1.Color]; - } - } - - if ((collisions & CXP1) != 0) - { - _player1.Collisions |= collisions; - if (_core.Settings.ShowPlayer2) - { - pixelColor = _palette[_player1.Color]; - } - } - - if ((collisions & CXM0) != 0) - { - _player0.Missile.Collisions |= collisions; - if (_core.Settings.ShowMissle1) - { - pixelColor = _palette[_player0.Color]; - } - } - - if ((collisions & CXP0) != 0) - { - _player0.Collisions |= collisions; - if (_core.Settings.ShowPlayer1) - { - pixelColor = _palette[_player0.Color]; - } - } - - if (_playField.Score && !_playField.Priority && ((collisions & CXPF) != 0) && _core.Settings.ShowPlayfield) - { - pixelColor = !rightSide ? _palette[_player0.Color] : _palette[_player1.Color]; - } - - if (_playField.Priority && (collisions & CXPF) != 0 && _core.Settings.ShowPlayfield) - { - - pixelColor = _palette[_playField.PfColor]; - - } - - // Handle vblank - if (_vblankEnabled) - { - pixelColor = BackColor; - } - - // Add the pixel to the scanline - // TODO: Remove this magic number (68) - - int y = _CurrentScanLine; - // y >= max screen height means lag frame or game crashed, but is a legal situation. - // either way, there's nothing to display - if (y < MaxScreenHeight) - { - int x = _hsyncCnt - 68; - if (x < 0 || x > 159) // this can't happen, right? - throw new Exception(); // TODO - _scanlinebuffer[_CurrentScanLine * ScreenWidth + x] = pixelColor; - } - } - else - { - do_ticks = true; - } - - // if extended HBLank is active, the screen area still needs a color - if (_hsyncCnt >= 68 && _hsyncCnt < 76 && _hmove.LateHBlankReset) - { - int pixelColor = 0; - - // Add the pixel to the scanline - // TODO: Remove this magic number (68) - - int y = _CurrentScanLine; - // y >= max screen height means lag frame or game crashed, but is a legal situation. - // either way, there's nothing to display - if (y < MaxScreenHeight) - { - int x = _hsyncCnt - 68; - if (x < 0 || x > 159) // this can't happen, right? - throw new Exception(); // TODO - _scanlinebuffer[_CurrentScanLine * ScreenWidth + x] = pixelColor; - } - } - - - - - - // Handle HMOVE - if (_hmove.HMoveEnabled) - { - - if (_hmove.DecCntEnabled) - { - - - // Actually do stuff only evey 4 pulses - if (_hmove.HMoveCnt == 0) - { - // If the latch is still set - if (_hmove.Player0Latch) - { - // If the move counter still has a bit in common with the HM register - if (((15 - _hmove.Player0Cnt) ^ ((_player0.HM & 0x07) | ((~(_player0.HM & 0x08)) & 0x08))) != 0x0F) - { - p0_stuff = true; - } - else - { - _hmove.Player0Latch = false; - } - } - - if (_hmove.Missile0Latch) - { - - // If the move counter still has a bit in common with the HM register - if (((15 - _hmove.Missile0Cnt) ^ ((_player0.Missile.Hm & 0x07) | ((~(_player0.Missile.Hm & 0x08)) & 0x08))) != 0x0F) - { - m0_stuff = true; - } - else - { - _hmove.Missile0Latch = false; - - } - } - - if (_hmove.Player1Latch) - { - // If the move counter still has a bit in common with the HM register - if (((15 - _hmove.Player1Cnt) ^ ((_player1.HM & 0x07) | ((~(_player1.HM & 0x08)) & 0x08))) != 0x0F) - { - p1_stuff = true; - } - else - { - _hmove.Player1Latch = false; - } - } - - if (_hmove.Missile1Latch) - { - // If the move counter still has a bit in common with the HM register - if (((15 - _hmove.Missile1Cnt) ^ ((_player1.Missile.Hm & 0x07) | ((~(_player1.Missile.Hm & 0x08)) & 0x08))) != 0x0F) - { - m1_stuff = true; - } - else - { - _hmove.Missile1Latch = false; - } - } - - if (_hmove.BallLatch) - { - // If the move counter still has a bit in common with the HM register - if (((15 - _hmove.BallCnt) ^ ((_ball.HM & 0x07) | ((~(_ball.HM & 0x08)) & 0x08))) != 0x0F) - { - b_stuff = true; - } - else - { - _hmove.BallLatch = false; - } - } - - if (!_hmove.Player0Latch && !_hmove.Player1Latch && !_hmove.BallLatch && !_hmove.Missile0Latch && !_hmove.Missile1Latch) - { - _hmove.HMoveEnabled = false; - _hmove.DecCntEnabled = false; - _hmove.HMoveDelayCnt = 0; - } - } - - _hmove.HMoveCnt++; - _hmove.HMoveCnt %= 4; - - if (p0_stuff == true && _hsyncCnt % 4 == 0) - { - p0_stuff = false; - // "Clock-Stuffing" - if (do_ticks == true) - { - _player0.Tick(); - } - - - // Increase by 1, max of 15 - - _hmove.test_count_p0++; - if (_hmove.test_count_p0 < 16) - { - _hmove.Player0Cnt++; - } - else - { - _hmove.Player0Cnt = 0; - } - } - if (p1_stuff == true && _hsyncCnt % 4 == 0) - { - p1_stuff = false; - // "Clock-Stuffing" - if (do_ticks == true) - { - _player1.Tick(); - } - // Increase by 1, max of 15 - _hmove.test_count_p1++; - if (_hmove.test_count_p1 < 16) - { - _hmove.Player1Cnt++; - } - else - { - _hmove.Player1Cnt = 0; - } - - } - if (m0_stuff == true && _hsyncCnt % 4 == 0) - { - m0_stuff = false; - // "Clock-Stuffing" - if (do_ticks == true) - { - _player0.Missile.Tick(); - } - // Increase by 1, max of 15 - - - _hmove.test_count_m0++; - if (_hmove.test_count_m0 < 16) - { - _hmove.Missile0Cnt++; - } - else - { - _hmove.Missile0Cnt = 0; - } - } - if (m1_stuff == true && _hsyncCnt % 4 == 0) - { - m1_stuff = false; - // "Clock-Stuffing" - if (do_ticks == true) - { - _player1.Missile.Tick(); - } - // Increase by 1, max of 15 - _hmove.test_count_m1++; - if (_hmove.test_count_m1 < 16) - { - _hmove.Missile1Cnt++; - } - else - { - _hmove.Missile1Cnt = 0; - } - } - if (b_stuff == true && _hsyncCnt % 4 == 0) - { - b_stuff = false; - // "Clock-Stuffing" - if (do_ticks == true) - { - _ball.Tick(); - } - // Increase by 1, max of 15 - _hmove.test_count_b++; - if (_hmove.test_count_b < 16) - { - _hmove.BallCnt++; - } - else - { - _hmove.BallCnt = 0; - } - } - } - - if (_hmove.HMoveDelayCnt < 5) - { - _hmove.HMoveDelayCnt++; - } - - if (_hmove.HMoveDelayCnt == 5) - { - _hmove.HMoveDelayCnt++; - _hmove.HMoveCnt = 0; - _hmove.DecCntEnabled = true; - - _hmove.test_count_p0 = 0; - _hmove.test_count_p1 = 0; - _hmove.test_count_m0 = 0; - _hmove.test_count_m1 = 0; - _hmove.test_count_b = 0; - - _hmove.Player0Latch = true; - _hmove.Player0Cnt = 0; - - _hmove.Missile0Latch = true; - _hmove.Missile0Cnt = 0; - - _hmove.Player1Latch = true; - _hmove.Player1Cnt = 0; - - _hmove.Missile1Latch = true; - _hmove.Missile1Cnt = 0; - - _hmove.BallLatch = true; - _hmove.BallCnt = 0; - - _hmove.LateHBlankReset = true; - } - } - - - // do the audio sampling - if (_hsyncCnt==36 || _hsyncCnt==148) - { - _local_audio_cycles[_audioClocks] += (short)(AUD[0].Cycle()/2); - _local_audio_cycles[_audioClocks] += (short)(AUD[1].Cycle()/2); - _audioClocks++; - } - - // Increment the hsync counter - _hsyncCnt++; - _hsyncCnt %= 228; - - // End of the line? Add it to the buffer! - if (_hsyncCnt == 0) - { - _hmove.LateHBlankReset = false; - _CurrentScanLine++; - LineCount++; - } - } - - public int[] FrameBuffer = new int[ScreenWidth * MaxScreenHeight]; - - void OutputFrame(int validlines) - { - int topLine = _pal ? _core.Settings.PALTopLine : _core.Settings.NTSCTopLine; - int bottomLine = _pal ? _core.Settings.PALBottomLine : _core.Settings.NTSCBottomLine; - - // if vsync occured unexpectedly early, black out the remainer - for (; validlines < bottomLine; validlines++) - { - for (int i = 0; i < 160; i++) - _scanlinebuffer[validlines * 160 + i] = BackColor; - } - - int srcbytes = sizeof(int) * ScreenWidth * topLine; - int count = bottomLine - topLine; // no +1, as the bottom line number is not inclusive - count *= sizeof(int) * ScreenWidth; - - Buffer.BlockCopy(_scanlinebuffer, srcbytes, FrameBuffer, 0, count); - } - - public byte ReadMemory(ushort addr, bool peek) - { - var maskedAddr = (ushort)(addr & 0x000F); - byte coll = 0; - int mask = 0; - - if (maskedAddr == 0x00) // CXM0P - { - coll=(byte)((((_player0.Missile.Collisions & CXP1) != 0) ? 0x80 : 0x00) | (((_player0.Missile.Collisions & CXP0) != 0) ? 0x40 : 0x00)); - mask = 0x3f; - } - - if (maskedAddr == 0x01) // CXM1P - { - coll = (byte)((((_player1.Missile.Collisions & CXP0) != 0) ? 0x80 : 0x00) | (((_player1.Missile.Collisions & CXP1) != 0) ? 0x40 : 0x00)); - mask = 0x3f; - } - - if (maskedAddr == 0x02) // CXP0FB - { - coll = (byte)((((_player0.Collisions & CXPF) != 0) ? 0x80 : 0x00) | (((_player0.Collisions & CXBL) != 0) ? 0x40 : 0x00)); - mask = 0x3f; - } - - if (maskedAddr == 0x03) // CXP1FB - { - coll = (byte)((((_player1.Collisions & CXPF) != 0) ? 0x80 : 0x00) | (((_player1.Collisions & CXBL) != 0) ? 0x40 : 0x00)); - mask = 0x3f; - } - - if (maskedAddr == 0x04) // CXM0FB - { - coll = (byte)((((_player0.Missile.Collisions & CXPF) != 0) ? 0x80 : 0x00) | (((_player0.Missile.Collisions & CXBL) != 0) ? 0x40 : 0x00)); - mask = 0x3f; - } - - if (maskedAddr == 0x05) // CXM1FB - { - coll = (byte)((((_player1.Missile.Collisions & CXPF) != 0) ? 0x80 : 0x00) | (((_player1.Missile.Collisions & CXBL) != 0) ? 0x40 : 0x00)); - mask = 0x3f; - } - - if (maskedAddr == 0x06) // CXBLPF - { - coll = (byte)(((_ball.Collisions & CXPF) != 0) ? 0x80 : 0x00); - mask = 0x7f; - } - - if (maskedAddr == 0x07) // CXPPMM - { - coll = (byte)((((_player0.Collisions & CXP1) != 0) ? 0x80 : 0x00) | (((_player0.Missile.Collisions & CXM1) != 0) ? 0x40 : 0x00)); - mask = 0x3f; - } - - // inputs 0-3 are measured by a charging capacitor, these inputs are used with the paddles and the keyboard - // Changing the hard coded value will change the paddle position. The range seems to be roughly 0-56000 according to values from stella - // 6105 roughly centers the paddle in Breakout - if (maskedAddr == 0x08) // INPT0 - { - if (_capCharging && _core.Cpu.TotalExecutedCycles - _capChargeStart >= 6105) - { - coll=0x80; - } else - { - coll = 0x00; - } - mask = 0x7f; - } - - if (maskedAddr == 0x09) // INPT1 - { - if (_capCharging && _core.Cpu.TotalExecutedCycles - _capChargeStart >= 6105) - { - coll = 0x80; - } - else - { - coll = 0x00; - } - mask = 0x7f; - } - - if (maskedAddr == 0x0A) // INPT2 - { - if (_capCharging && _core.Cpu.TotalExecutedCycles - _capChargeStart >= 6105) - { - coll = 0x80; - } - else - { - coll = 0x00; - } - mask = 0x7f; - } - - if (maskedAddr == 0x0B) // INPT3 - { - if (_capCharging && _core.Cpu.TotalExecutedCycles - _capChargeStart >= 6105) - { - coll = 0x80; - } - else - { - coll = 0x00; - } - mask = 0x7f; - } - - if (maskedAddr == 0x0C) // INPT4 - { - coll = (byte)((_core.ReadControls1(peek) & 0x08) != 0 ? 0x80 : 0x00); - mask = 0x7f; - } - - if (maskedAddr == 0x0D) // INPT5 - { - coll = (byte)((_core.ReadControls2(peek) & 0x08) != 0 ? 0x80 : 0x00); - mask = 0x7f; - } - - //some bits of the databus will be undriven when a read call is made. Our goal here is to sort out what - // happens to the undriven pins. Most of the time, they will be in whatever state they were when previously - //assigned in some other bus access, so let's go with that. - coll+=(byte)(mask & bus_state); - - if (!peek) bus_state = (int)coll; - return coll; - } - - public void WriteMemory(ushort addr, byte value, bool poke) - { - var maskedAddr = (ushort)(addr & 0x3f); - if (!poke) bus_state = value; - - if (maskedAddr == 0x00) // VSYNC - { - if ((value & 0x02) != 0) - { - // Frame is complete, output to buffer - _vsyncEnabled = true; - } - else if (_vsyncEnabled) - { - // When VSYNC is disabled, this will be the first line of the new frame - - // write to frame buffer - OutputFrame(_CurrentScanLine); - - if (FrameEndCallBack != null) - FrameEndCallBack(_CurrentScanLine); - - // Clear all from last frame - _CurrentScanLine = 0; - - // Frame is done - _vsyncEnabled = false; - - // Do not reset hsync, since we're on the first line of the new frame - // hsyncCnt = 0; - } - } - else if (maskedAddr == 0x01) // VBLANK - { - vblank_delay = 1; - vblank_value = value; - _capCharging = (value & 0x80) == 0; - if ((value & 0x80) == 0) - { - _capChargeStart = _core.Cpu.TotalExecutedCycles; - } - } - else if (maskedAddr == 0x02) // WSYNC - { - // Halt the CPU until we reach hblank - _core.Cpu.RDY = false; - } - else if (maskedAddr == 0x04) // NUSIZ0 - { - _player0.Nusiz = (byte)(value & 0x37); - _player0.Missile.Size = (byte)((value & 0x30) >> 4); - _player0.Missile.Number = (byte)(value & 0x07); - } - else if (maskedAddr == 0x05) // NUSIZ1 - { - _player1.Nusiz = (byte)(value & 0x37); - _player1.Missile.Size = (byte)((value & 0x30) >> 4); - _player1.Missile.Number = (byte)(value & 0x07); - } - else if (maskedAddr == 0x06) // COLUP0 - { - _player0.Color = (byte)(value & 0xFE); - } - else if (maskedAddr == 0x07) // COLUP1 - { - _player1.Color = (byte)(value & 0xFE); - } - else if (maskedAddr == 0x08) // COLUPF - { - _playField.PfColor = (byte)(value & 0xFE); - } - else if (maskedAddr == 0x09) // COLUBK - { - _playField.BkColor = (byte)(value & 0xFE); - } - else if (maskedAddr == 0x0A) // CTRLPF - { - _playField.Reflect = (value & 0x01) != 0; - _playField.Score = (value & 0x02) != 0; - _playField.Priority = (value & 0x04) != 0; - - _ball.Size = (byte)((value & 0x30) >> 4); - } - else if (maskedAddr == 0x0B) // REFP0 - { - _player0.Reflect = (value & 0x08) != 0; - } - else if (maskedAddr == 0x0C) // REFP1 - { - _player1.Reflect = (value & 0x08) != 0; - } - else if (maskedAddr == 0x0D) // PF0 - { - pf0_update = value; - pf0_updater = true; - pf0_delay_clock = 0; - if (((_hsyncCnt / 3) & 3) == 0) - { - pf0_max_delay = 4; - } - if (((_hsyncCnt / 3) & 3) == 1) - { - pf0_max_delay = 5; - } - if (((_hsyncCnt / 3) & 3) == 2) - { - pf0_max_delay = 2; - } - if (((_hsyncCnt / 3) & 3) == 3) - { - pf0_max_delay = 3; - } - - //_playField.Grp = (uint)((_playField.Grp & 0x0FFFF) + ((ReverseBits(value, 8) & 0x0F) << 16)); - } - else if (maskedAddr == 0x0E) // PF1 - { - pf1_update = value; - pf1_updater = true; - pf1_delay_clock = 0; - if (((_hsyncCnt / 3) & 3) == 0) - { - pf1_max_delay = 4; - } - if (((_hsyncCnt / 3) & 3) == 1) - { - pf1_max_delay = 5; - } - if (((_hsyncCnt / 3) & 3) == 2) - { - pf1_max_delay = 2; - } - if (((_hsyncCnt / 3) & 3) == 3) - { - pf1_max_delay = 3; - } - //_playField.Grp = (uint)((_playField.Grp & 0xF00FF) + (value << 8)); - } - else if (maskedAddr == 0x0F) // PF2 - { - pf2_update = value; - pf2_updater = true; - pf2_delay_clock = 0; - if (((_hsyncCnt / 3) & 3) == 0) - { - pf2_max_delay = 4; - } - if (((_hsyncCnt / 3) & 3) == 1) - { - pf2_max_delay = 5; - } - if (((_hsyncCnt / 3) & 3) == 2) - { - pf2_max_delay = 2; - } - if (((_hsyncCnt / 3) & 3) == 3) - { - pf2_max_delay = 3; - } - //_playField.Grp = (uint)((_playField.Grp & 0xFFF00) + ReverseBits(value, 8)); - } - else if (maskedAddr == 0x10) // RESP0 - { - // Resp depends on HMOVE - if (!_hmove.LateHBlankReset) - { - _player0.HPosCnt = (byte)(_hsyncCnt < 68 ? 160 - 2 : 160 - 4); - if (_hsyncCnt == 67) _player0.HPosCnt = 160 - 3; - } - else - { - _player0.HPosCnt = (byte)(_hsyncCnt < 76 ? 160 - 2 : 160 - 4); - if (_hsyncCnt == 75) _player0.HPosCnt = 160 - 3; - } - } - else if (maskedAddr == 0x11) // RESP1 - { - // RESP depends on HMOVE - if (!_hmove.LateHBlankReset) - { - _player1.HPosCnt = (byte)(_hsyncCnt < 68 ? 160 - 2 : 160 - 4); - if (_hsyncCnt == 67) _player1.HPosCnt = 160 - 3; - } - else - { - _player1.HPosCnt = (byte)(_hsyncCnt < 76 ? 160 - 2 : 160 - 4); - if (_hsyncCnt == 75) _player1.HPosCnt = 160 - 3; - } - - } - else if (maskedAddr == 0x12) // RESM0 - { - if (!_hmove.LateHBlankReset) - { - _player0.Missile.HPosCnt = (byte)(_hsyncCnt < 68 ? 160 - 2 : 160 - 4); - if (_hsyncCnt == 67) _player0.Missile.HPosCnt = 160 - 3; - } - else - { - _player0.Missile.HPosCnt = (byte)(_hsyncCnt < 76 ? 160 - 2 : 160 - 4); - if (_hsyncCnt == 75) _player0.Missile.HPosCnt = 160 - 3; - } - - } - else if (maskedAddr == 0x13) // RESM1 - { - if (!_hmove.LateHBlankReset) - { - _player1.Missile.HPosCnt = (byte)(_hsyncCnt < 68 ? 160 - 2 : 160 - 4); - if (_hsyncCnt == 67) _player1.Missile.HPosCnt = 160 - 3; - } - else - { - _player1.Missile.HPosCnt = (byte)(_hsyncCnt < 76 ? 160 - 2 : 160 - 4); - if (_hsyncCnt == 75) _player1.Missile.HPosCnt = 160 - 3; - } - } - else if (maskedAddr == 0x14) // RESBL - { - if (!_hmove.LateHBlankReset) - { - _ball.HPosCnt = (byte)(_hsyncCnt < 68 ? 160 - 2 : 160 - 4); - if (_hsyncCnt == 67) _ball.HPosCnt = 160 - 3; - } - else - { - _ball.HPosCnt = (byte)(_hsyncCnt < 76 ? 160 - 2 : 160 - 4); - if (_hsyncCnt == 75) _ball.HPosCnt = 160 - 3; - } - - } - else if (maskedAddr == 0x15) // AUDC0 - { - AUD[0].AUDC= (byte)(value & 15); - } - else if (maskedAddr == 0x16) // AUDC1 - { - AUD[1].AUDC = (byte)(value & 15); - } - else if (maskedAddr == 0x17) // AUDF0 - { - AUD[0].AUDF = (byte)((value & 31) + 1); - } - else if (maskedAddr == 0x18) // AUDF1 - { - AUD[1].AUDF = (byte)((value & 31) + 1); - } - else if (maskedAddr == 0x19) // AUDV0 - { - AUD[0].AUDV = (byte)(value & 15); - } - else if (maskedAddr == 0x1A) // AUDV1 - { - AUD[1].AUDV = (byte)(value & 15); - } - else if (maskedAddr == 0x1B) // GRP0 - { - prg0_val = value; - prg0_delay = 1; - - } - else if (maskedAddr == 0x1C) // GRP1 - { - prg1_val = value; - prg1_delay = 1; - - } - else if (maskedAddr == 0x1D) // ENAM0 - { - enam0_val = (value & 0x02) != 0; - enam0_delay = 1; - } - else if (maskedAddr == 0x1E) // ENAM1 - { - enam1_val = (value & 0x02) != 0; - enam1_delay = 1; - } - else if (maskedAddr == 0x1F) // ENABL - { - enamb_val = (value & 0x02) != 0; - enamb_delay = 1; - } - else if (maskedAddr == 0x20) // HMP0 - { - HMP0_val = (byte)((value & 0xF0) >> 4); - HMP0_delay = 1; - } - else if (maskedAddr == 0x21) // HMP1 - { - HMP1_val = (byte)((value & 0xF0) >> 4); - HMP1_delay = 1; - } - else if (maskedAddr == 0x22) // HMM0 - { - _player0.Missile.Hm = (byte)((value & 0xF0) >> 4); - } - else if (maskedAddr == 0x23) // HMM1 - { - _player1.Missile.Hm = (byte)((value & 0xF0) >> 4); - } - else if (maskedAddr == 0x24) // HMBL - { - _ball.HM = (byte)((value & 0xF0) >> 4); - } - else if (maskedAddr == 0x25) // VDELP0 - { - _player0.Delay = (value & 0x01) != 0; - } - else if (maskedAddr == 0x26) // VDELP1 - { - _player1.Delay = (value & 0x01) != 0; - } - else if (maskedAddr == 0x27) // VDELBL - { - _ball.Delay = (value & 0x01) != 0; - } - else if (maskedAddr == 0x28) // RESMP0 - { - _player0.Missile.ResetToPlayer = (value & 0x02) != 0; - } - else if (maskedAddr == 0x29) // RESMP1 - { - _player1.Missile.ResetToPlayer = (value & 0x02) != 0; - } - else if (maskedAddr == 0x2A) // HMOVE - { - _hmove.HMoveEnabled = true; - _hmove.HMoveDelayCnt = 0; - } - else if (maskedAddr == 0x2B) // HMCLR - { - _player0.HM = 0; - _player0.Missile.Hm = 0; - _player1.HM = 0; - _player1.Missile.Hm = 0; - _ball.HM = 0; - } - else if (maskedAddr == 0x2C) // CXCLR - { - _player0.Collisions = 0; - _player0.Missile.Collisions = 0; - _player1.Collisions = 0; - _player1.Missile.Collisions = 0; - _ball.Collisions = 0; - } - } - - private static int ReverseBits(int value, int bits) - { - int result = 0; - for (int i = 0; i < bits; i++) - { - result = (result << 1) | ((value >> i) & 0x01); - } - - return result; - } - - #region Audio bits - - private enum AudioRegister : byte { AUDC, AUDF, AUDV } - - private int frameStartCycles, frameEndCycles; - - public void BeginAudioFrame() - { - frameStartCycles = _core.Cpu.TotalExecutedCycles; - } - - public void CompleteAudioFrame() - { - frameEndCycles = _core.Cpu.TotalExecutedCycles; - } - - public void GetSamples(short[] samples) - { - if (_audioClocks > 0) - { - var samples31khz = new short[_audioClocks]; // mono - - for (int i=0;i<_audioClocks;i++) - { - samples31khz[i] = _local_audio_cycles[i]; - _local_audio_cycles[i] = 0; - } - - // convert from 31khz to 44khz - for (var i = 0; i < samples.Length / 2; i++) - { - samples[i * 2] = samples31khz[(int)(((double)samples31khz.Length / (double)(samples.Length / 2)) * i)]; - samples[(i * 2) + 1] = samples[i * 2]; - } - } - _audioClocks = 0; - } - - public void DiscardSamples() - { - _audioClocks = 0; - } - - #endregion - - public void SyncState(Serializer ser) + // Emulates the TIA + public partial class TIA : IVideoProvider, ISoundProvider + { + + #region palette + + const int BackColor = unchecked((int)0xff000000); + + static TIA() + { + // add alpha to palette entries + for (int i = 0; i < PALPalette.Length; i++) + PALPalette[i] |= unchecked((int)0xff000000); + for (int i = 0; i < NTSCPalette.Length; i++) + NTSCPalette[i] |= unchecked((int)0xff000000); + } + + private static readonly int[] PALPalette = + { + 0x000000, 0x000000, 0x2b2b2b, 0x2b2b2b, + 0x525252, 0x525252, 0x767676, 0x767676, + 0x979797, 0x979797, 0xb6b6b6, 0xb6b6b6, + 0xd2d2d2, 0xd2d2d2, 0xececec, 0xececec, + + 0x000000, 0x000000, 0x2b2b2b, 0x2b2b2b, + 0x525252, 0x525252, 0x767676, 0x767676, + 0x979797, 0x979797, 0xb6b6b6, 0xb6b6b6, + 0xd2d2d2, 0xd2d2d2, 0xececec, 0xececec, + + 0x805800, 0x000000, 0x96711a, 0x2b2b2b, + 0xab8732, 0x525252, 0xbe9c48, 0x767676, + 0xcfaf5c, 0x979797, 0xdfc06f, 0xb6b6b6, + 0xeed180, 0xd2d2d2, 0xfce090, 0xececec, + + 0x445c00, 0x000000, 0x5e791a, 0x2b2b2b, + 0x769332, 0x525252, 0x8cac48, 0x767676, + 0xa0c25c, 0x979797, 0xb3d76f, 0xb6b6b6, + 0xc4ea80, 0xd2d2d2, 0xd4fc90, 0xececec, + + 0x703400, 0x000000, 0x89511a, 0x2b2b2b, + 0xa06b32, 0x525252, 0xb68448, 0x767676, + 0xc99a5c, 0x979797, 0xdcaf6f, 0xb6b6b6, + 0xecc280, 0xd2d2d2, 0xfcd490, 0xececec, + + 0x006414, 0x000000, 0x1a8035, 0x2b2b2b, + 0x329852, 0x525252, 0x48b06e, 0x767676, + 0x5cc587, 0x979797, 0x6fd99e, 0xb6b6b6, + 0x80ebb4, 0xd2d2d2, 0x90fcc8, 0xececec, + + 0x700014, 0x000000, 0x891a35, 0x2b2b2b, + 0xa03252, 0x525252, 0xb6486e, 0x767676, + 0xc95c87, 0x979797, 0xdc6f9e, 0xb6b6b6, + 0xec80b4, 0xd2d2d2, 0xfc90c8, 0xececec, + + 0x005c5c, 0x000000, 0x1a7676, 0x2b2b2b, + 0x328e8e, 0x525252, 0x48a4a4, 0x767676, + 0x5cb8b8, 0x979797, 0x6fcbcb, 0xb6b6b6, + 0x80dcdc, 0xd2d2d2, 0x90ecec, 0xececec, + + 0x70005c, 0x000000, 0x841a74, 0x2b2b2b, + 0x963289, 0x525252, 0xa8489e, 0x767676, + 0xb75cb0, 0x979797, 0xc66fc1, 0xb6b6b6, + 0xd380d1, 0xd2d2d2, 0xe090e0, 0xececec, + + 0x003c70, 0x000000, 0x195a89, 0x2b2b2b, + 0x2f75a0, 0x525252, 0x448eb6, 0x767676, + 0x57a5c9, 0x979797, 0x68badc, 0xb6b6b6, + 0x79ceec, 0xd2d2d2, 0x88e0fc, 0xececec, + + 0x580070, 0x000000, 0x6e1a89, 0x2b2b2b, + 0x8332a0, 0x525252, 0x9648b6, 0x767676, + 0xa75cc9, 0x979797, 0xb76fdc, 0xb6b6b6, + 0xc680ec, 0xd2d2d2, 0xd490fc, 0xececec, + + 0x002070, 0x000000, 0x193f89, 0x2b2b2b, + 0x2f5aa0, 0x525252, 0x4474b6, 0x767676, + 0x578bc9, 0x979797, 0x68a1dc, 0xb6b6b6, + 0x79b5ec, 0xd2d2d2, 0x88c8fc, 0xececec, + + 0x340080, 0x000000, 0x4a1a96, 0x2b2b2b, + 0x5f32ab, 0x525252, 0x7248be, 0x767676, + 0x835ccf, 0x979797, 0x936fdf, 0xb6b6b6, + 0xa280ee, 0xd2d2d2, 0xb090fc, 0xececec, + + 0x000088, 0x000000, 0x1a1a9d, 0x2b2b2b, + 0x3232b0, 0x525252, 0x4848c2, 0x767676, + 0x5c5cd2, 0x979797, 0x6f6fe1, 0xb6b6b6, + 0x8080ef, 0xd2d2d2, 0x9090fc, 0xececec, + + 0x000000, 0x000000, 0x2b2b2b, 0x2b2b2b, + 0x525252, 0x525252, 0x767676, 0x767676, + 0x979797, 0x979797, 0xb6b6b6, 0xb6b6b6, + 0xd2d2d2, 0xd2d2d2, 0xececec, 0xececec, + + 0x000000, 0x000000, 0x2b2b2b, 0x2b2b2b, + 0x525252, 0x525252, 0x767676, 0x767676, + 0x979797, 0x979797, 0xb6b6b6, 0xb6b6b6, + 0xd2d2d2, 0xd2d2d2, 0xececec, 0xececec + }; + + private static readonly int[] NTSCPalette = + { + 0x000000, 0, 0x4a4a4a, 0, 0x6f6f6f, 0, 0x8e8e8e, 0, + 0xaaaaaa, 0, 0xc0c0c0, 0, 0xd6d6d6, 0, 0xececec, 0, + 0x484800, 0, 0x69690f, 0, 0x86861d, 0, 0xa2a22a, 0, + 0xbbbb35, 0, 0xd2d240, 0, 0xe8e84a, 0, 0xfcfc54, 0, + 0x7c2c00, 0, 0x904811, 0, 0xa26221, 0, 0xb47a30, 0, + 0xc3903d, 0, 0xd2a44a, 0, 0xdfb755, 0, 0xecc860, 0, + 0x901c00, 0, 0xa33915, 0, 0xb55328, 0, 0xc66c3a, 0, + 0xd5824a, 0, 0xe39759, 0, 0xf0aa67, 0, 0xfcbc74, 0, + 0x940000, 0, 0xa71a1a, 0, 0xb83232, 0, 0xc84848, 0, + 0xd65c5c, 0, 0xe46f6f, 0, 0xf08080, 0, 0xfc9090, 0, + 0x840064, 0, 0x97197a, 0, 0xa8308f, 0, 0xb846a2, 0, + 0xc659b3, 0, 0xd46cc3, 0, 0xe07cd2, 0, 0xec8ce0, 0, + 0x500084, 0, 0x68199a, 0, 0x7d30ad, 0, 0x9246c0, 0, + 0xa459d0, 0, 0xb56ce0, 0, 0xc57cee, 0, 0xd48cfc, 0, + 0x140090, 0, 0x331aa3, 0, 0x4e32b5, 0, 0x6848c6, 0, + 0x7f5cd5, 0, 0x956fe3, 0, 0xa980f0, 0, 0xbc90fc, 0, + 0x000094, 0, 0x181aa7, 0, 0x2d32b8, 0, 0x4248c8, 0, + 0x545cd6, 0, 0x656fe4, 0, 0x7580f0, 0, 0x8490fc, 0, + 0x001c88, 0, 0x183b9d, 0, 0x2d57b0, 0, 0x4272c2, 0, + 0x548ad2, 0, 0x65a0e1, 0, 0x75b5ef, 0, 0x84c8fc, 0, + 0x003064, 0, 0x185080, 0, 0x2d6d98, 0, 0x4288b0, 0, + 0x54a0c5, 0, 0x65b7d9, 0, 0x75cceb, 0, 0x84e0fc, 0, + 0x004030, 0, 0x18624e, 0, 0x2d8169, 0, 0x429e82, 0, + 0x54b899, 0, 0x65d1ae, 0, 0x75e7c2, 0, 0x84fcd4, 0, + 0x004400, 0, 0x1a661a, 0, 0x328432, 0, 0x48a048, 0, + 0x5cba5c, 0, 0x6fd26f, 0, 0x80e880, 0, 0x90fc90, 0, + 0x143c00, 0, 0x355f18, 0, 0x527e2d, 0, 0x6e9c42, 0, + 0x87b754, 0, 0x9ed065, 0, 0xb4e775, 0, 0xc8fc84, 0, + 0x303800, 0, 0x505916, 0, 0x6d762b, 0, 0x88923e, 0, + 0xa0ab4f, 0, 0xb7c25f, 0, 0xccd86e, 0, 0xe0ec7c, 0, + 0x482c00, 0, 0x694d14, 0, 0x866a26, 0, 0xa28638, 0, + 0xbb9f47, 0, 0xd2b656, 0, 0xe8cc63, 0, 0xfce070, 0 + }; + + private static readonly int[] SECAMPalette = + { + 0x000000,0x000000,0x2121FF,0x2121FF, + 0xF03C79,0xF03C79,0xFF50FF,0xFF50FF, + 0x7FFF00,0x7FFF00,0x7FFFFF,0x7FFFFF, + 0xFFFF3F,0xFFFF3F,0xffffff,0xffffff, + + 0x000000,0x000000,0x2121FF,0x2121FF, + 0xF03C79,0xF03C79,0xFF50FF,0xFF50FF, + 0x7FFF00,0x7FFF00,0x7FFFFF,0x7FFFFF, + 0xFFFF3F,0xFFFF3F,0xffffff,0xffffff, + + 0x000000,0x000000,0x2121FF,0x2121FF, + 0xF03C79,0xF03C79,0xFF50FF,0xFF50FF, + 0x7FFF00,0x7FFF00,0x7FFFFF,0x7FFFFF, + 0xFFFF3F,0xFFFF3F,0xffffff,0xffffff, + + 0x000000,0x000000,0x2121FF,0x2121FF, + 0xF03C79,0xF03C79,0xFF50FF,0xFF50FF, + 0x7FFF00,0x7FFF00,0x7FFFFF,0x7FFFFF, + 0xFFFF3F,0xFFFF3F,0xffffff,0xffffff, + + 0x000000,0x000000,0x2121FF,0x2121FF, + 0xF03C79,0xF03C79,0xFF50FF,0xFF50FF, + 0x7FFF00,0x7FFF00,0x7FFFFF,0x7FFFFF, + 0xFFFF3F,0xFFFF3F,0xffffff,0xffffff, + + 0x000000,0x000000,0x2121FF,0x2121FF, + 0xF03C79,0xF03C79,0xFF50FF,0xFF50FF, + 0x7FFF00,0x7FFF00,0x7FFFFF,0x7FFFFF, + 0xFFFF3F,0xFFFF3F,0xffffff,0xffffff, + + 0x000000,0x000000,0x2121FF,0x2121FF, + 0xF03C79,0xF03C79,0xFF50FF,0xFF50FF, + 0x7FFF00,0x7FFF00,0x7FFFFF,0x7FFFFF, + 0xFFFF3F,0xFFFF3F,0xffffff,0xffffff, + + 0x000000,0x000000,0x2121FF,0x2121FF, + 0xF03C79,0xF03C79,0xFF50FF,0xFF50FF, + 0x7FFF00,0x7FFF00,0x7FFFFF,0x7FFFFF, + 0xFFFF3F,0xFFFF3F,0xffffff,0xffffff, + + 0x000000,0x000000,0x2121FF,0x2121FF, + 0xF03C79,0xF03C79,0xFF50FF,0xFF50FF, + 0x7FFF00,0x7FFF00,0x7FFFFF,0x7FFFFF, + 0xFFFF3F,0xFFFF3F,0xffffff,0xffffff, + + 0x000000,0x000000,0x2121FF,0x2121FF, + 0xF03C79,0xF03C79,0xFF50FF,0xFF50FF, + 0x7FFF00,0x7FFF00,0x7FFFFF,0x7FFFFF, + 0xFFFF3F,0xFFFF3F,0xffffff,0xffffff, + + 0x000000,0x000000,0x2121FF,0x2121FF, + 0xF03C79,0xF03C79,0xFF50FF,0xFF50FF, + 0x7FFF00,0x7FFF00,0x7FFFFF,0x7FFFFF, + 0xFFFF3F,0xFFFF3F,0xffffff,0xffffff, + + 0x000000,0x000000,0x2121FF,0x2121FF, + 0xF03C79,0xF03C79,0xFF50FF,0xFF50FF, + 0x7FFF00,0x7FFF00,0x7FFFFF,0x7FFFFF, + 0xFFFF3F,0xFFFF3F,0xffffff,0xffffff, + + 0x000000,0x000000,0x2121FF,0x2121FF, + 0xF03C79,0xF03C79,0xFF50FF,0xFF50FF, + 0x7FFF00,0x7FFF00,0x7FFFFF,0x7FFFFF, + 0xFFFF3F,0xFFFF3F,0xffffff,0xffffff, + + 0x000000,0x000000,0x2121FF,0x2121FF, + 0xF03C79,0xF03C79,0xFF50FF,0xFF50FF, + 0x7FFF00,0x7FFF00,0x7FFFFF,0x7FFFFF, + 0xFFFF3F,0xFFFF3F,0xffffff,0xffffff, + + 0x000000,0x000000,0x2121FF,0x2121FF, + 0xF03C79,0xF03C79,0xFF50FF,0xFF50FF, + 0x7FFF00,0x7FFF00,0x7FFFFF,0x7FFFFF, + 0xFFFF3F,0xFFFF3F,0xffffff,0xffffff, + + 0x000000,0x000000,0x2121FF,0x2121FF, + 0xF03C79,0xF03C79,0xFF50FF,0xFF50FF, + 0x7FFF00,0x7FFF00,0x7FFFFF,0x7FFFFF, + 0xFFFF3F,0xFFFF3F,0xffffff,0xffffff, + }; + + #endregion + + // in all cases, the TIA has 228 clocks per scanline + // the NTSC TIA has a clock rate of 3579575hz + // the PAL/SECAM TIA has a clock rate of 3546894hz + + private bool _pal; + + public int NominalNumScanlines + { + get + { + return _pal ? 312 : 262; + } + } + + public void GetFrameRate(out int num, out int den) + { + // TODO when sound timing is made exact: + // NTSC refclock is actually 315 / 88 mhz + //3546895 + + int clockrate = _pal ? 3546895 : 3579545; + int clocksperframe = 228 * NominalNumScanlines; + int gcd = (int)BigInteger.GreatestCommonDivisor(clockrate, clocksperframe); + num = clockrate / gcd; + den = clocksperframe / gcd; + } + + private const int ScreenWidth = 160; + private const int MaxScreenHeight = 312; + + private const byte CXP0 = 0x01; + private const byte CXP1 = 0x02; + private const byte CXM0 = 0x04; + private const byte CXM1 = 0x08; + private const byte CXPF = 0x10; + private const byte CXBL = 0x20; + + private readonly Atari2600 _core; + private int[] _scanlinebuffer = new int[ScreenWidth * MaxScreenHeight]; + + private int[] _palette; + + public int bus_state; + + private byte pf0_update; + private byte pf1_update; + private byte pf2_update; + private bool pf0_updater; + private bool pf1_updater; + private bool pf2_updater; + private byte pf0_delay_clock; + private byte pf1_delay_clock; + private byte pf2_delay_clock; + private byte pf0_max_delay; + private byte pf1_max_delay; + private byte pf2_max_delay; + + private int enam0_delay; + private int enam1_delay; + private int enamb_delay; + private bool enam0_val; + private bool enam1_val; + private bool enamb_val; + + private int vblank_delay; + private byte vblank_value; + + private bool p0_stuff; + private bool p1_stuff; + private bool m0_stuff; + private bool m1_stuff; + private bool b_stuff; + + private int HMP0_delay; + private byte HMP0_val; + private int HMP1_delay; + private byte HMP1_val; + + private int prg0_delay; + private int prg1_delay; + private byte prg0_val; + private byte prg1_val; + + private bool do_ticks; + + private byte _hsyncCnt; + private int _capChargeStart; + private bool _capCharging; + private bool _vblankEnabled; + private bool _vsyncEnabled; + private int _CurrentScanLine; + public int _audioClocks; // not savestated + + private PlayerData _player0; + private PlayerData _player1; + private PlayfieldData _playField; + private HMoveData _hmove; + private BallData _ball; + + public Audio[] AUD = { new Audio(), new Audio() }; + + // current audio register state used to sample correct positions in the scanline (clrclk 0 and 114) + //public byte[] current_audio_register = new byte[6]; + public short[] _local_audio_cycles = new short[2000]; + + public TIA(Atari2600 core, bool pal, bool secam, int spf) + { + _core = core; + _player0.ScanCnt = 8; + _player1.ScanCnt = 8; + _pal = pal; + SetSECAM(secam); + _spf = spf; + } + + public void SetSECAM(bool secam) + { + _palette = _pal ? secam ? SECAMPalette : PALPalette : NTSCPalette; + } + + public int CurrentScanLine + { + get { return _CurrentScanLine; } + } + + public bool IsVBlank + { + get { return _vblankEnabled; } + } + + public bool IsVSync + { + get { return _vsyncEnabled; } + } + + /// + /// a count of lines emulated; incremented by the TIA but not used by it + /// + public int LineCount { get; set; } + + /// + /// called at the end of a video frame. used internally + /// + public Action FrameEndCallBack { get; set; } + + public int VirtualWidth + { + // TODO: PAL? + get + { + if (_pal) + { + return 320; + } + + return 275; // 275 comes from NTSC specs and the actual pixel clock of a 2600 TIA + } + } + + public int VirtualHeight + { + get { return BufferHeight; } + } + + public int BufferWidth + { + get { return ScreenWidth; } + } + + public int BufferHeight + { + get + { + if (_pal) + return _core.Settings.PALBottomLine - _core.Settings.PALTopLine; + else + return _core.Settings.NTSCBottomLine - _core.Settings.NTSCTopLine; + } + } + + public int BackgroundColor + { + get { return _core.Settings.BackgroundColor.ToArgb(); } + } + + public int[] GetVideoBuffer() + { + return FrameBuffer; + } + + public void Reset() + { + _hsyncCnt = 0; + _capChargeStart = 0; + _capCharging = false; + _vblankEnabled = false; + vblank_delay = 0; + vblank_value = 0; + _vsyncEnabled = false; + _CurrentScanLine = 0; + _audioClocks = 0; + + bus_state = 0; + + pf0_update = 0; + pf1_update = 0; + pf2_update = 0; + pf0_updater = false; + pf1_updater = false; + pf2_updater = false; + pf0_delay_clock = 0; + pf1_delay_clock = 0; + pf2_delay_clock = 0; + pf0_max_delay = 0; + pf1_max_delay = 0; + pf2_max_delay = 0; + + enam0_delay = 0; + enam1_delay = 0; + enamb_delay = 0; + enam0_val = false; + enam1_val = false; + enamb_val = false; + + p0_stuff = false; + p1_stuff = false; + m0_stuff = false; + m1_stuff = false; + b_stuff = false; + + HMP0_delay = 0; + HMP0_val = 0; + HMP1_delay = 0; + HMP1_val = 0; + + prg0_delay = 0; + prg1_delay = 0; + prg0_val = 0; + prg1_val = 0; + + do_ticks = false; + + _player0 = new PlayerData(); + _player1 = new PlayerData(); + _playField = new PlayfieldData(); + _hmove = new HMoveData(); + _ball = new BallData(); + + _player0.ScanCnt = 8; + _player1.ScanCnt = 8; + } + + // Execute TIA cycles + public void Execute(int cycles) + { + // Still ignoring cycles... + + // delay vblank latch + if (vblank_delay > 0) + { + vblank_delay++; + if (vblank_delay == 3) + { + _vblankEnabled = (vblank_value & 0x02) != 0; + vblank_delay = 0; + } + + } + + + + //delay latch to new playfield register + if (pf0_updater == true) + { + pf0_delay_clock++; + if (pf0_delay_clock > pf0_max_delay) + { + _playField.Grp = (uint)((_playField.Grp & 0x0FFFF) + ((ReverseBits(pf0_update, 8) & 0x0F) << 16)); + pf0_updater = false; + } + } + if (pf1_updater == true) + { + pf1_delay_clock++; + if (pf1_delay_clock > pf1_max_delay) + { + _playField.Grp = (uint)((_playField.Grp & 0xF00FF) + (pf1_update << 8)); + pf1_updater = false; + } + } + if (pf2_updater == true) + { + pf2_delay_clock++; + if (pf2_delay_clock > pf2_max_delay) + { + _playField.Grp = (uint)((_playField.Grp & 0xFFF00) + ReverseBits(pf2_update, 8)); + pf2_updater = false; + } + } + + //delay latch to missile enable + if (enam0_delay > 0) + { + enam0_delay++; + if (enam0_delay == 3) + { + enam0_delay = 0; + _player0.Missile.Enabled = enam0_val; + } + + } + + if (enam1_delay > 0) + { + enam1_delay++; + if (enam1_delay == 3) + { + enam1_delay = 0; + _player1.Missile.Enabled = enam1_val; + } + + } + + // delay latch to ball enable + if (enamb_delay > 0) + { + enamb_delay++; + if (enamb_delay == 3) + { + enamb_delay = 0; + _ball.Enabled = enamb_val; + } + + } + + // delay latch to player graphics registers + if (prg0_delay > 0) + { + prg0_delay++; + if (prg0_delay == 3) + { + prg0_delay = 0; + _player0.Grp = prg0_val; + _player1.Dgrp = _player1.Grp; + } + + } + + if (prg1_delay > 0) + { + prg1_delay++; + if (prg1_delay == 3) + { + prg1_delay = 0; + _player1.Grp = prg1_val; + _player0.Dgrp = _player0.Grp; + + // TODO: Find a game that uses this functionality and test it + _ball.Denabled = _ball.Enabled; + } + + } + + // HMP write delay + if (HMP0_delay > 0) + { + HMP0_delay++; + if (HMP0_delay == 4) + { + HMP0_delay = 0; + _player0.HM = HMP0_val; + } + } + + if (HMP1_delay > 0) + { + HMP1_delay++; + if (HMP1_delay == 3) + { + HMP1_delay = 0; + _player1.HM = HMP1_val; + } + } + + // Reset the RDY flag when we reach hblank + if (_hsyncCnt <= 0) + { + _core.Cpu.RDY = true; + + } + + // Assume we're on the left side of the screen for now + var rightSide = false; + + // ---- Things that happen only in the drawing section ---- + // TODO: Remove this magic number (17). It depends on the HMOVE + if ((_hsyncCnt) >= (_hmove.LateHBlankReset ? 76 : 68)) + { + do_ticks = false; + + // TODO: Remove this magic number + if ((_hsyncCnt / 4) >= 37) + { + rightSide = true; + } + + // The bit number of the PF data which we want + int pfBit = ((_hsyncCnt / 4) - 17) % 20; + + // Create the mask for the bit we want + // Note that bits are arranged 0 1 2 3 4 .. 19 + int pfMask = 1 << (20 - 1 - pfBit); + + // Reverse the mask if on the right and playfield is reflected + if (rightSide && _playField.Reflect) + { + pfMask = ReverseBits(pfMask, 20); + } + + // Calculate collisions + byte collisions = 0x00; + + if ((_playField.Grp & pfMask) != 0) + { + collisions |= CXPF; + } + + + // ---- Player 0 ---- + collisions |= _player0.Tick() ? CXP0 : (byte)0x00; + + // ---- Missile 0 ---- + collisions |= _player0.Missile.Tick() ? CXM0 : (byte)0x00; + + // ---- Player 1 ---- + collisions |= _player1.Tick() ? CXP1 : (byte)0x00; + + // ---- Missile 0 ---- + collisions |= _player1.Missile.Tick() ? CXM1 : (byte)0x00; + + // ---- Ball ---- + collisions |= _ball.Tick() ? CXBL : (byte)0x00; + + + // Pick the pixel color from collisions + int pixelColor = BackColor; + if (_core.Settings.ShowBG) + { + pixelColor = _palette[_playField.BkColor]; + } + + if ((collisions & CXPF) != 0 && _core.Settings.ShowPlayfield) + { + if (_playField.Score) + { + if (!rightSide) + { + pixelColor = _palette[_player0.Color]; + } + else + { + pixelColor = _palette[_player1.Color]; + } + } + else + { + pixelColor = _palette[_playField.PfColor]; + } + } + + if ((collisions & CXBL) != 0) + { + _ball.Collisions |= collisions; + if (_core.Settings.ShowBall) + { + pixelColor = _palette[_playField.PfColor]; + } + } + + if ((collisions & CXM1) != 0) + { + _player1.Missile.Collisions |= collisions; + if (_core.Settings.ShowMissle2) + { + pixelColor = _palette[_player1.Color]; + } + } + + if ((collisions & CXP1) != 0) + { + _player1.Collisions |= collisions; + if (_core.Settings.ShowPlayer2) + { + pixelColor = _palette[_player1.Color]; + } + } + + if ((collisions & CXM0) != 0) + { + _player0.Missile.Collisions |= collisions; + if (_core.Settings.ShowMissle1) + { + pixelColor = _palette[_player0.Color]; + } + } + + if ((collisions & CXP0) != 0) + { + _player0.Collisions |= collisions; + if (_core.Settings.ShowPlayer1) + { + pixelColor = _palette[_player0.Color]; + } + } + + if (_playField.Score && !_playField.Priority && ((collisions & CXPF) != 0) && _core.Settings.ShowPlayfield) + { + pixelColor = !rightSide ? _palette[_player0.Color] : _palette[_player1.Color]; + } + + if (_playField.Priority && (collisions & CXPF) != 0 && _core.Settings.ShowPlayfield) + { + + pixelColor = _palette[_playField.PfColor]; + + } + + // Handle vblank + if (_vblankEnabled) + { + pixelColor = BackColor; + } + + // Add the pixel to the scanline + // TODO: Remove this magic number (68) + + int y = _CurrentScanLine; + // y >= max screen height means lag frame or game crashed, but is a legal situation. + // either way, there's nothing to display + if (y < MaxScreenHeight) + { + int x = _hsyncCnt - 68; + if (x < 0 || x > 159) // this can't happen, right? + throw new Exception(); // TODO + _scanlinebuffer[_CurrentScanLine * ScreenWidth + x] = pixelColor; + } + } + else + { + do_ticks = true; + } + + // if extended HBLank is active, the screen area still needs a color + if (_hsyncCnt >= 68 && _hsyncCnt < 76 && _hmove.LateHBlankReset) + { + int pixelColor = 0; + + // Add the pixel to the scanline + // TODO: Remove this magic number (68) + + int y = _CurrentScanLine; + // y >= max screen height means lag frame or game crashed, but is a legal situation. + // either way, there's nothing to display + if (y < MaxScreenHeight) + { + int x = _hsyncCnt - 68; + if (x < 0 || x > 159) // this can't happen, right? + throw new Exception(); // TODO + _scanlinebuffer[_CurrentScanLine * ScreenWidth + x] = pixelColor; + } + } + + + + + + // Handle HMOVE + if (_hmove.HMoveEnabled) + { + + if (_hmove.DecCntEnabled) + { + + + // Actually do stuff only evey 4 pulses + if (_hmove.HMoveCnt == 0) + { + // If the latch is still set + if (_hmove.Player0Latch) + { + // If the move counter still has a bit in common with the HM register + if (((15 - _hmove.Player0Cnt) ^ ((_player0.HM & 0x07) | ((~(_player0.HM & 0x08)) & 0x08))) != 0x0F) + { + p0_stuff = true; + } + else + { + _hmove.Player0Latch = false; + } + } + + if (_hmove.Missile0Latch) + { + + // If the move counter still has a bit in common with the HM register + if (((15 - _hmove.Missile0Cnt) ^ ((_player0.Missile.Hm & 0x07) | ((~(_player0.Missile.Hm & 0x08)) & 0x08))) != 0x0F) + { + m0_stuff = true; + } + else + { + _hmove.Missile0Latch = false; + + } + } + + if (_hmove.Player1Latch) + { + // If the move counter still has a bit in common with the HM register + if (((15 - _hmove.Player1Cnt) ^ ((_player1.HM & 0x07) | ((~(_player1.HM & 0x08)) & 0x08))) != 0x0F) + { + p1_stuff = true; + } + else + { + _hmove.Player1Latch = false; + } + } + + if (_hmove.Missile1Latch) + { + // If the move counter still has a bit in common with the HM register + if (((15 - _hmove.Missile1Cnt) ^ ((_player1.Missile.Hm & 0x07) | ((~(_player1.Missile.Hm & 0x08)) & 0x08))) != 0x0F) + { + m1_stuff = true; + } + else + { + _hmove.Missile1Latch = false; + } + } + + if (_hmove.BallLatch) + { + // If the move counter still has a bit in common with the HM register + if (((15 - _hmove.BallCnt) ^ ((_ball.HM & 0x07) | ((~(_ball.HM & 0x08)) & 0x08))) != 0x0F) + { + b_stuff = true; + } + else + { + _hmove.BallLatch = false; + } + } + + if (!_hmove.Player0Latch && !_hmove.Player1Latch && !_hmove.BallLatch && !_hmove.Missile0Latch && !_hmove.Missile1Latch) + { + _hmove.HMoveEnabled = false; + _hmove.DecCntEnabled = false; + _hmove.HMoveDelayCnt = 0; + } + } + + _hmove.HMoveCnt++; + _hmove.HMoveCnt %= 4; + + if (p0_stuff == true && _hsyncCnt % 4 == 0) + { + p0_stuff = false; + // "Clock-Stuffing" + if (do_ticks == true) + { + _player0.Tick(); + } + + + // Increase by 1, max of 15 + + _hmove.test_count_p0++; + if (_hmove.test_count_p0 < 16) + { + _hmove.Player0Cnt++; + } + else + { + _hmove.Player0Cnt = 0; + } + } + if (p1_stuff == true && _hsyncCnt % 4 == 0) + { + p1_stuff = false; + // "Clock-Stuffing" + if (do_ticks == true) + { + _player1.Tick(); + } + // Increase by 1, max of 15 + _hmove.test_count_p1++; + if (_hmove.test_count_p1 < 16) + { + _hmove.Player1Cnt++; + } + else + { + _hmove.Player1Cnt = 0; + } + + } + if (m0_stuff == true && _hsyncCnt % 4 == 0) + { + m0_stuff = false; + // "Clock-Stuffing" + if (do_ticks == true) + { + _player0.Missile.Tick(); + } + // Increase by 1, max of 15 + + + _hmove.test_count_m0++; + if (_hmove.test_count_m0 < 16) + { + _hmove.Missile0Cnt++; + } + else + { + _hmove.Missile0Cnt = 0; + } + } + if (m1_stuff == true && _hsyncCnt % 4 == 0) + { + m1_stuff = false; + // "Clock-Stuffing" + if (do_ticks == true) + { + _player1.Missile.Tick(); + } + // Increase by 1, max of 15 + _hmove.test_count_m1++; + if (_hmove.test_count_m1 < 16) + { + _hmove.Missile1Cnt++; + } + else + { + _hmove.Missile1Cnt = 0; + } + } + if (b_stuff == true && _hsyncCnt % 4 == 0) + { + b_stuff = false; + // "Clock-Stuffing" + if (do_ticks == true) + { + _ball.Tick(); + } + // Increase by 1, max of 15 + _hmove.test_count_b++; + if (_hmove.test_count_b < 16) + { + _hmove.BallCnt++; + } + else + { + _hmove.BallCnt = 0; + } + } + } + + if (_hmove.HMoveDelayCnt < 5) + { + _hmove.HMoveDelayCnt++; + } + + if (_hmove.HMoveDelayCnt == 5) + { + _hmove.HMoveDelayCnt++; + _hmove.HMoveCnt = 0; + _hmove.DecCntEnabled = true; + + _hmove.test_count_p0 = 0; + _hmove.test_count_p1 = 0; + _hmove.test_count_m0 = 0; + _hmove.test_count_m1 = 0; + _hmove.test_count_b = 0; + + _hmove.Player0Latch = true; + _hmove.Player0Cnt = 0; + + _hmove.Missile0Latch = true; + _hmove.Missile0Cnt = 0; + + _hmove.Player1Latch = true; + _hmove.Player1Cnt = 0; + + _hmove.Missile1Latch = true; + _hmove.Missile1Cnt = 0; + + _hmove.BallLatch = true; + _hmove.BallCnt = 0; + + _hmove.LateHBlankReset = true; + } + } + + + // do the audio sampling + if (_hsyncCnt == 36 || _hsyncCnt == 148) + { + _local_audio_cycles[_audioClocks] += (short)(AUD[0].Cycle() / 2); + _local_audio_cycles[_audioClocks] += (short)(AUD[1].Cycle() / 2); + _audioClocks++; + } + + // Increment the hsync counter + _hsyncCnt++; + _hsyncCnt %= 228; + + // End of the line? Add it to the buffer! + if (_hsyncCnt == 0) + { + _hmove.LateHBlankReset = false; + _CurrentScanLine++; + LineCount++; + } + } + + public int[] FrameBuffer = new int[ScreenWidth * MaxScreenHeight]; + + void OutputFrame(int validlines) + { + int topLine = _pal ? _core.Settings.PALTopLine : _core.Settings.NTSCTopLine; + int bottomLine = _pal ? _core.Settings.PALBottomLine : _core.Settings.NTSCBottomLine; + + // if vsync occured unexpectedly early, black out the remainer + for (; validlines < bottomLine; validlines++) + { + for (int i = 0; i < 160; i++) + _scanlinebuffer[validlines * 160 + i] = BackColor; + } + + int srcbytes = sizeof(int) * ScreenWidth * topLine; + int count = bottomLine - topLine; // no +1, as the bottom line number is not inclusive + count *= sizeof(int) * ScreenWidth; + + Buffer.BlockCopy(_scanlinebuffer, srcbytes, FrameBuffer, 0, count); + } + + public byte ReadMemory(ushort addr, bool peek) + { + var maskedAddr = (ushort)(addr & 0x000F); + byte coll = 0; + int mask = 0; + + if (maskedAddr == 0x00) // CXM0P + { + coll = (byte)((((_player0.Missile.Collisions & CXP1) != 0) ? 0x80 : 0x00) | (((_player0.Missile.Collisions & CXP0) != 0) ? 0x40 : 0x00)); + mask = 0x3f; + } + + if (maskedAddr == 0x01) // CXM1P + { + coll = (byte)((((_player1.Missile.Collisions & CXP0) != 0) ? 0x80 : 0x00) | (((_player1.Missile.Collisions & CXP1) != 0) ? 0x40 : 0x00)); + mask = 0x3f; + } + + if (maskedAddr == 0x02) // CXP0FB + { + coll = (byte)((((_player0.Collisions & CXPF) != 0) ? 0x80 : 0x00) | (((_player0.Collisions & CXBL) != 0) ? 0x40 : 0x00)); + mask = 0x3f; + } + + if (maskedAddr == 0x03) // CXP1FB + { + coll = (byte)((((_player1.Collisions & CXPF) != 0) ? 0x80 : 0x00) | (((_player1.Collisions & CXBL) != 0) ? 0x40 : 0x00)); + mask = 0x3f; + } + + if (maskedAddr == 0x04) // CXM0FB + { + coll = (byte)((((_player0.Missile.Collisions & CXPF) != 0) ? 0x80 : 0x00) | (((_player0.Missile.Collisions & CXBL) != 0) ? 0x40 : 0x00)); + mask = 0x3f; + } + + if (maskedAddr == 0x05) // CXM1FB + { + coll = (byte)((((_player1.Missile.Collisions & CXPF) != 0) ? 0x80 : 0x00) | (((_player1.Missile.Collisions & CXBL) != 0) ? 0x40 : 0x00)); + mask = 0x3f; + } + + if (maskedAddr == 0x06) // CXBLPF + { + coll = (byte)(((_ball.Collisions & CXPF) != 0) ? 0x80 : 0x00); + mask = 0x7f; + } + + if (maskedAddr == 0x07) // CXPPMM + { + coll = (byte)((((_player0.Collisions & CXP1) != 0) ? 0x80 : 0x00) | (((_player0.Missile.Collisions & CXM1) != 0) ? 0x40 : 0x00)); + mask = 0x3f; + } + + // inputs 0-3 are measured by a charging capacitor, these inputs are used with the paddles and the keyboard + // Changing the hard coded value will change the paddle position. The range seems to be roughly 0-56000 according to values from stella + // 6105 roughly centers the paddle in Breakout + if (maskedAddr == 0x08) // INPT0 + { + if (_capCharging && _core.Cpu.TotalExecutedCycles - _capChargeStart >= 6105) + { + coll = 0x80; + } else + { + coll = 0x00; + } + mask = 0x7f; + } + + if (maskedAddr == 0x09) // INPT1 + { + if (_capCharging && _core.Cpu.TotalExecutedCycles - _capChargeStart >= 6105) + { + coll = 0x80; + } + else + { + coll = 0x00; + } + mask = 0x7f; + } + + if (maskedAddr == 0x0A) // INPT2 + { + if (_capCharging && _core.Cpu.TotalExecutedCycles - _capChargeStart >= 6105) + { + coll = 0x80; + } + else + { + coll = 0x00; + } + mask = 0x7f; + } + + if (maskedAddr == 0x0B) // INPT3 + { + if (_capCharging && _core.Cpu.TotalExecutedCycles - _capChargeStart >= 6105) + { + coll = 0x80; + } + else + { + coll = 0x00; + } + mask = 0x7f; + } + + if (maskedAddr == 0x0C) // INPT4 + { + coll = (byte)((_core.ReadControls1(peek) & 0x08) != 0 ? 0x80 : 0x00); + mask = 0x7f; + } + + if (maskedAddr == 0x0D) // INPT5 + { + coll = (byte)((_core.ReadControls2(peek) & 0x08) != 0 ? 0x80 : 0x00); + mask = 0x7f; + } + + //some bits of the databus will be undriven when a read call is made. Our goal here is to sort out what + // happens to the undriven pins. Most of the time, they will be in whatever state they were when previously + //assigned in some other bus access, so let's go with that. + coll += (byte)(mask & bus_state); + + if (!peek) bus_state = (int)coll; + return coll; + } + + public void WriteMemory(ushort addr, byte value, bool poke) + { + var maskedAddr = (ushort)(addr & 0x3f); + if (!poke) bus_state = value; + + if (maskedAddr == 0x00) // VSYNC + { + if ((value & 0x02) != 0) + { + // Frame is complete, output to buffer + _vsyncEnabled = true; + } + else if (_vsyncEnabled) + { + // When VSYNC is disabled, this will be the first line of the new frame + + // write to frame buffer + OutputFrame(_CurrentScanLine); + + if (FrameEndCallBack != null) + FrameEndCallBack(_CurrentScanLine); + + // Clear all from last frame + _CurrentScanLine = 0; + + // Frame is done + _vsyncEnabled = false; + + // Do not reset hsync, since we're on the first line of the new frame + // hsyncCnt = 0; + } + } + else if (maskedAddr == 0x01) // VBLANK + { + vblank_delay = 1; + vblank_value = value; + _capCharging = (value & 0x80) == 0; + if ((value & 0x80) == 0) + { + _capChargeStart = _core.Cpu.TotalExecutedCycles; + } + } + else if (maskedAddr == 0x02) // WSYNC + { + // Halt the CPU until we reach hblank + _core.Cpu.RDY = false; + } + else if (maskedAddr == 0x04) // NUSIZ0 + { + _player0.Nusiz = (byte)(value & 0x37); + _player0.Missile.Size = (byte)((value & 0x30) >> 4); + _player0.Missile.Number = (byte)(value & 0x07); + } + else if (maskedAddr == 0x05) // NUSIZ1 + { + _player1.Nusiz = (byte)(value & 0x37); + _player1.Missile.Size = (byte)((value & 0x30) >> 4); + _player1.Missile.Number = (byte)(value & 0x07); + } + else if (maskedAddr == 0x06) // COLUP0 + { + _player0.Color = (byte)(value & 0xFE); + } + else if (maskedAddr == 0x07) // COLUP1 + { + _player1.Color = (byte)(value & 0xFE); + } + else if (maskedAddr == 0x08) // COLUPF + { + _playField.PfColor = (byte)(value & 0xFE); + } + else if (maskedAddr == 0x09) // COLUBK + { + _playField.BkColor = (byte)(value & 0xFE); + } + else if (maskedAddr == 0x0A) // CTRLPF + { + _playField.Reflect = (value & 0x01) != 0; + _playField.Score = (value & 0x02) != 0; + _playField.Priority = (value & 0x04) != 0; + + _ball.Size = (byte)((value & 0x30) >> 4); + } + else if (maskedAddr == 0x0B) // REFP0 + { + _player0.Reflect = (value & 0x08) != 0; + } + else if (maskedAddr == 0x0C) // REFP1 + { + _player1.Reflect = (value & 0x08) != 0; + } + else if (maskedAddr == 0x0D) // PF0 + { + pf0_update = value; + pf0_updater = true; + pf0_delay_clock = 0; + if (((_hsyncCnt / 3) & 3) == 0) + { + pf0_max_delay = 4; + } + if (((_hsyncCnt / 3) & 3) == 1) + { + pf0_max_delay = 5; + } + if (((_hsyncCnt / 3) & 3) == 2) + { + pf0_max_delay = 2; + } + if (((_hsyncCnt / 3) & 3) == 3) + { + pf0_max_delay = 3; + } + + //_playField.Grp = (uint)((_playField.Grp & 0x0FFFF) + ((ReverseBits(value, 8) & 0x0F) << 16)); + } + else if (maskedAddr == 0x0E) // PF1 + { + pf1_update = value; + pf1_updater = true; + pf1_delay_clock = 0; + if (((_hsyncCnt / 3) & 3) == 0) + { + pf1_max_delay = 4; + } + if (((_hsyncCnt / 3) & 3) == 1) + { + pf1_max_delay = 5; + } + if (((_hsyncCnt / 3) & 3) == 2) + { + pf1_max_delay = 2; + } + if (((_hsyncCnt / 3) & 3) == 3) + { + pf1_max_delay = 3; + } + //_playField.Grp = (uint)((_playField.Grp & 0xF00FF) + (value << 8)); + } + else if (maskedAddr == 0x0F) // PF2 + { + pf2_update = value; + pf2_updater = true; + pf2_delay_clock = 0; + if (((_hsyncCnt / 3) & 3) == 0) + { + pf2_max_delay = 4; + } + if (((_hsyncCnt / 3) & 3) == 1) + { + pf2_max_delay = 5; + } + if (((_hsyncCnt / 3) & 3) == 2) + { + pf2_max_delay = 2; + } + if (((_hsyncCnt / 3) & 3) == 3) + { + pf2_max_delay = 3; + } + //_playField.Grp = (uint)((_playField.Grp & 0xFFF00) + ReverseBits(value, 8)); + } + else if (maskedAddr == 0x10) // RESP0 + { + // Resp depends on HMOVE + if (!_hmove.LateHBlankReset) + { + _player0.HPosCnt = (byte)(_hsyncCnt < 68 ? 160 - 2 : 160 - 4); + if (_hsyncCnt == 67) _player0.HPosCnt = 160 - 3; + } + else + { + _player0.HPosCnt = (byte)(_hsyncCnt < 76 ? 160 - 2 : 160 - 4); + if (_hsyncCnt == 75) _player0.HPosCnt = 160 - 3; + } + } + else if (maskedAddr == 0x11) // RESP1 + { + // RESP depends on HMOVE + if (!_hmove.LateHBlankReset) + { + _player1.HPosCnt = (byte)(_hsyncCnt < 68 ? 160 - 2 : 160 - 4); + if (_hsyncCnt == 67) _player1.HPosCnt = 160 - 3; + } + else + { + _player1.HPosCnt = (byte)(_hsyncCnt < 76 ? 160 - 2 : 160 - 4); + if (_hsyncCnt == 75) _player1.HPosCnt = 160 - 3; + } + + } + else if (maskedAddr == 0x12) // RESM0 + { + if (!_hmove.LateHBlankReset) + { + _player0.Missile.HPosCnt = (byte)(_hsyncCnt < 68 ? 160 - 2 : 160 - 4); + if (_hsyncCnt == 67) _player0.Missile.HPosCnt = 160 - 3; + } + else + { + _player0.Missile.HPosCnt = (byte)(_hsyncCnt < 76 ? 160 - 2 : 160 - 4); + if (_hsyncCnt == 75) _player0.Missile.HPosCnt = 160 - 3; + } + + } + else if (maskedAddr == 0x13) // RESM1 + { + if (!_hmove.LateHBlankReset) + { + _player1.Missile.HPosCnt = (byte)(_hsyncCnt < 68 ? 160 - 2 : 160 - 4); + if (_hsyncCnt == 67) _player1.Missile.HPosCnt = 160 - 3; + } + else + { + _player1.Missile.HPosCnt = (byte)(_hsyncCnt < 76 ? 160 - 2 : 160 - 4); + if (_hsyncCnt == 75) _player1.Missile.HPosCnt = 160 - 3; + } + } + else if (maskedAddr == 0x14) // RESBL + { + if (!_hmove.LateHBlankReset) + { + _ball.HPosCnt = (byte)(_hsyncCnt < 68 ? 160 - 2 : 160 - 4); + if (_hsyncCnt == 67) _ball.HPosCnt = 160 - 3; + } + else + { + _ball.HPosCnt = (byte)(_hsyncCnt < 76 ? 160 - 2 : 160 - 4); + if (_hsyncCnt == 75) _ball.HPosCnt = 160 - 3; + } + + } + else if (maskedAddr == 0x15) // AUDC0 + { + AUD[0].AUDC = (byte)(value & 15); + } + else if (maskedAddr == 0x16) // AUDC1 + { + AUD[1].AUDC = (byte)(value & 15); + } + else if (maskedAddr == 0x17) // AUDF0 + { + AUD[0].AUDF = (byte)((value & 31) + 1); + } + else if (maskedAddr == 0x18) // AUDF1 + { + AUD[1].AUDF = (byte)((value & 31) + 1); + } + else if (maskedAddr == 0x19) // AUDV0 + { + AUD[0].AUDV = (byte)(value & 15); + } + else if (maskedAddr == 0x1A) // AUDV1 + { + AUD[1].AUDV = (byte)(value & 15); + } + else if (maskedAddr == 0x1B) // GRP0 + { + prg0_val = value; + prg0_delay = 1; + + } + else if (maskedAddr == 0x1C) // GRP1 + { + prg1_val = value; + prg1_delay = 1; + + } + else if (maskedAddr == 0x1D) // ENAM0 + { + enam0_val = (value & 0x02) != 0; + enam0_delay = 1; + } + else if (maskedAddr == 0x1E) // ENAM1 + { + enam1_val = (value & 0x02) != 0; + enam1_delay = 1; + } + else if (maskedAddr == 0x1F) // ENABL + { + enamb_val = (value & 0x02) != 0; + enamb_delay = 1; + } + else if (maskedAddr == 0x20) // HMP0 + { + HMP0_val = (byte)((value & 0xF0) >> 4); + HMP0_delay = 1; + } + else if (maskedAddr == 0x21) // HMP1 + { + HMP1_val = (byte)((value & 0xF0) >> 4); + HMP1_delay = 1; + } + else if (maskedAddr == 0x22) // HMM0 + { + _player0.Missile.Hm = (byte)((value & 0xF0) >> 4); + } + else if (maskedAddr == 0x23) // HMM1 + { + _player1.Missile.Hm = (byte)((value & 0xF0) >> 4); + } + else if (maskedAddr == 0x24) // HMBL + { + _ball.HM = (byte)((value & 0xF0) >> 4); + } + else if (maskedAddr == 0x25) // VDELP0 + { + _player0.Delay = (value & 0x01) != 0; + } + else if (maskedAddr == 0x26) // VDELP1 + { + _player1.Delay = (value & 0x01) != 0; + } + else if (maskedAddr == 0x27) // VDELBL + { + _ball.Delay = (value & 0x01) != 0; + } + else if (maskedAddr == 0x28) // RESMP0 + { + _player0.Missile.ResetToPlayer = (value & 0x02) != 0; + } + else if (maskedAddr == 0x29) // RESMP1 + { + _player1.Missile.ResetToPlayer = (value & 0x02) != 0; + } + else if (maskedAddr == 0x2A) // HMOVE + { + _hmove.HMoveEnabled = true; + _hmove.HMoveDelayCnt = 0; + } + else if (maskedAddr == 0x2B) // HMCLR + { + _player0.HM = 0; + _player0.Missile.Hm = 0; + _player1.HM = 0; + _player1.Missile.Hm = 0; + _ball.HM = 0; + } + else if (maskedAddr == 0x2C) // CXCLR + { + _player0.Collisions = 0; + _player0.Missile.Collisions = 0; + _player1.Collisions = 0; + _player1.Missile.Collisions = 0; + _ball.Collisions = 0; + } + } + + private static int ReverseBits(int value, int bits) + { + int result = 0; + for (int i = 0; i < bits; i++) + { + result = (result << 1) | ((value >> i) & 0x01); + } + + return result; + } + + #region Audio bits + + private enum AudioRegister : byte { AUDC, AUDF, AUDV } + + private int frameStartCycles, frameEndCycles; + + public void BeginAudioFrame() + { + frameStartCycles = _core.Cpu.TotalExecutedCycles; + } + + public void CompleteAudioFrame() + { + frameEndCycles = _core.Cpu.TotalExecutedCycles; + } + + #endregion + + #region ISoundProvider + + private int _spf; + + public void GetSamplesSync(out short[] samples, out int nsamp) + { + short[] ret = new short[_spf * 2]; + GetSamples(ret); + samples = ret; + nsamp = _spf; + } + + // Exposing this as GetSamplesAsync would allow this to provide async sound + // However, it does nothing special for async sound so I don't see a point + private void GetSamples(short[] samples) + { + if (_audioClocks > 0) + { + var samples31khz = new short[_audioClocks]; // mono + + for (int i = 0; i < _audioClocks; i++) + { + samples31khz[i] = _local_audio_cycles[i]; + _local_audio_cycles[i] = 0; + } + + // convert from 31khz to 44khz + for (var i = 0; i < samples.Length / 2; i++) + { + samples[i * 2] = samples31khz[(int)(((double)samples31khz.Length / (double)(samples.Length / 2)) * i)]; + samples[(i * 2) + 1] = samples[i * 2]; + } + } + _audioClocks = 0; + } + + public void DiscardSamples() + { + _audioClocks = 0; + } + + public void GetSamplesAsync(short[] samples) + { + throw new NotSupportedException("Async is not available"); + } + + public bool CanProvideAsync + { + get { return false; } + } + + public SyncSoundMode SyncMode + { + get { return SyncSoundMode.Sync; } + } + + public void SetSyncMode(SyncSoundMode mode) + { + if (mode != SyncSoundMode.Sync) + { + throw new InvalidOperationException("Only Sync mode is supported."); + } + } + + #endregion + + public void SyncState(Serializer ser) { ser.BeginSection("TIA"); _ball.SyncState(ser); diff --git a/BizHawk.Emulation.Cores/Consoles/Atari/7800/Atari7800.cs b/BizHawk.Emulation.Cores/Consoles/Atari/7800/Atari7800.cs index 2a354f3aa4..ead949a047 100644 --- a/BizHawk.Emulation.Cores/Consoles/Atari/7800/Atari7800.cs +++ b/BizHawk.Emulation.Cores/Consoles/Atari/7800/Atari7800.cs @@ -35,6 +35,7 @@ namespace BizHawk.Emulation.Cores.Atari.Atari7800 { ServiceProvider = new BasicServiceProvider(this); (ServiceProvider as BasicServiceProvider).Register(avProvider); + (ServiceProvider as BasicServiceProvider).Register(avProvider); InputCallbacks = new InputCallbackSystem(); CoreComm = comm; byte[] highscoreBIOS = comm.CoreFileProvider.GetFirmware("A78", "Bios_HSC", false, "Some functions may not work without the high score BIOS."); @@ -206,14 +207,9 @@ namespace BizHawk.Emulation.Cores.Atari.Atari7800 #region audio\video - public ISyncSoundProvider SyncSoundProvider { get { return avProvider; } } - public bool StartAsyncSound() { return false; } - public void EndAsyncSound() { } - public IAsyncSoundProvider SoundProvider { get { return null; } } - MyAVProvider avProvider = new MyAVProvider(); - class MyAVProvider : IVideoProvider, ISyncSoundProvider, IDisposable + class MyAVProvider : IVideoProvider, ISoundProvider, IDisposable { public FrameBuffer framebuffer { get; private set; } public void ConnectToMachine(MachineBase m, EMU7800.Win.GameProgram g) @@ -231,7 +227,7 @@ namespace BizHawk.Emulation.Cores.Atari.Atari7800 resampler.Dispose(); resampler = new SpeexResampler(3, newsamplerate, 44100, newsamplerate, 44100, null, null); samplerate = newsamplerate; - dcfilter = DCFilter.DetatchedMode(256); + dcfilter = new DCFilter(256); } if (g.MachineType == MachineType.A7800PAL || g.MachineType == MachineType.A2600PAL) palette = TIATables.PALPalette; @@ -274,7 +270,14 @@ namespace BizHawk.Emulation.Cores.Atari.Atari7800 public int BufferHeight { get; private set; } public int BackgroundColor { get { return unchecked((int)0xff000000); } } - public void GetSamples(out short[] samples, out int nsamp) + #region ISoundProvider + + public bool CanProvideAsync + { + get { return false; } + } + + public void GetSamplesSync(out short[] samples, out int nsamp) { int nsampin = framebuffer.SoundBufferByteLength; unsafe @@ -291,16 +294,36 @@ namespace BizHawk.Emulation.Cores.Atari.Atari7800 } } - resampler.GetSamples(out samples, out nsamp); + resampler.GetSamplesSync(out samples, out nsamp); dcfilter.PushThroughSamples(samples, nsamp * 2); } + public SyncSoundMode SyncMode + { + get { return SyncSoundMode.Sync; } + } + + public void SetSyncMode(SyncSoundMode mode) + { + if (mode == SyncSoundMode.Async) + { + throw new NotSupportedException("Async mode is not supported."); + } + } + + public void GetSamplesAsync(short[] samples) + { + throw new InvalidOperationException("Async mode is not supported."); + } + public void DiscardSamples() { if (resampler != null) resampler.DiscardSamples(); } + #endregion + public void Dispose() { if (resampler != null) @@ -310,6 +333,7 @@ namespace BizHawk.Emulation.Cores.Atari.Atari7800 } } } + #endregion } } diff --git a/BizHawk.Emulation.Cores/Consoles/Atari/lynx/Lynx.ISoundProvider.cs b/BizHawk.Emulation.Cores/Consoles/Atari/lynx/Lynx.ISoundProvider.cs new file mode 100644 index 0000000000..79f3761a9a --- /dev/null +++ b/BizHawk.Emulation.Cores/Consoles/Atari/lynx/Lynx.ISoundProvider.cs @@ -0,0 +1,45 @@ +using System; +using BizHawk.Emulation.Common; + +namespace BizHawk.Emulation.Cores.Atari.Lynx +{ + public partial class Lynx : ISoundProvider + { + private short[] soundbuff = new short[2048]; + private int numsamp; + + public bool CanProvideAsync + { + get { return false; } + } + + public void GetSamplesSync(out short[] samples, out int nsamp) + { + samples = soundbuff; + nsamp = numsamp; + } + + public void DiscardSamples() + { + // Nothing to do + } + + public void SetSyncMode(SyncSoundMode mode) + { + if (mode == SyncSoundMode.Async) + { + throw new NotSupportedException("Async mode is not supported."); + } + } + + public SyncSoundMode SyncMode + { + get { return SyncSoundMode.Sync; } + } + + public void GetSamplesAsync(short[] samples) + { + throw new InvalidOperationException("Async mode is not supported."); + } + } +} diff --git a/BizHawk.Emulation.Cores/Consoles/Atari/lynx/Lynx.cs b/BizHawk.Emulation.Cores/Consoles/Atari/lynx/Lynx.cs index 18c8f93b9d..943f596379 100644 --- a/BizHawk.Emulation.Cores/Consoles/Atari/lynx/Lynx.cs +++ b/BizHawk.Emulation.Cores/Consoles/Atari/lynx/Lynx.cs @@ -13,7 +13,7 @@ namespace BizHawk.Emulation.Cores.Atari.Lynx { [CoreAttributes("Handy", "K. Wilkins", true, true, "mednafen 0-9-34-1", "http://mednafen.sourceforge.net/")] [ServiceNotApplicable(typeof(ISettable<,>), typeof(IDriveLight), typeof(IRegionable))] - public partial class Lynx : IEmulator, IVideoProvider, ISyncSoundProvider, ISaveRam, IStatable, IInputPollable + public partial class Lynx : IEmulator, IVideoProvider, ISoundProvider, ISaveRam, IStatable, IInputPollable { IntPtr Core; @@ -192,27 +192,5 @@ namespace BizHawk.Emulation.Cores.Atari.Lynx } #endregion - - #region SoundProvider - - short[] soundbuff = new short[2048]; - int numsamp; - - public IAsyncSoundProvider SoundProvider { get { return null; } } - public ISyncSoundProvider SyncSoundProvider { get { return this; } } - public bool StartAsyncSound() { return false; } - public void EndAsyncSound() { } - - public void GetSamples(out short[] samples, out int nsamp) - { - samples = soundbuff; - nsamp = numsamp; - } - - public void DiscardSamples() - { - } - - #endregion } } diff --git a/BizHawk.Emulation.Cores/Consoles/Coleco/ColecoVision.ISoundProvider.cs b/BizHawk.Emulation.Cores/Consoles/Coleco/ColecoVision.ISoundProvider.cs new file mode 100644 index 0000000000..ff11587cf4 --- /dev/null +++ b/BizHawk.Emulation.Cores/Consoles/Coleco/ColecoVision.ISoundProvider.cs @@ -0,0 +1,15 @@ +using System; +using BizHawk.Emulation.Common; +using BizHawk.Emulation.Cores.Components; + +namespace BizHawk.Emulation.Cores.ColecoVision +{ + // Sound refactor TODO: Implement ISoundProvider here and sort this mess out + public partial class ColecoVision + { + public SN76489 PSG; + + public IAsyncSoundProvider SoundProvider { get { return PSG; } } + //public ISyncSoundProvider SyncSoundProvider { get { return new FakeSyncSound(SoundProvider, 735); } } + } +} diff --git a/BizHawk.Emulation.Cores/Consoles/Coleco/ColecoVision.cs b/BizHawk.Emulation.Cores/Consoles/Coleco/ColecoVision.cs index 8332bb722e..8eda990bae 100644 --- a/BizHawk.Emulation.Cores/Consoles/Coleco/ColecoVision.cs +++ b/BizHawk.Emulation.Cores/Consoles/Coleco/ColecoVision.cs @@ -16,13 +16,12 @@ namespace BizHawk.Emulation.Cores.ColecoVision // ROM public byte[] RomData; public int RomLength; - public byte[] BiosRom; // Machine public Z80A Cpu; public TMS9918A VDP; - public SN76489 PSG; + public byte[] Ram = new byte[1024]; private readonly TraceBuffer Tracer = new TraceBuffer(); @@ -167,12 +166,6 @@ namespace BizHawk.Emulation.Cores.ColecoVision public string SystemId { get { return "Coleco"; } } public GameInfo game; public CoreComm CoreComm { get; private set; } - public IAsyncSoundProvider SoundProvider { get { return PSG; } } - public string BoardName { get { return null; } } - - public ISyncSoundProvider SyncSoundProvider { get { return new FakeSyncSound(SoundProvider, 735); } } - public bool StartAsyncSound() { return true; } - public void EndAsyncSound() { } } } \ No newline at end of file diff --git a/BizHawk.Emulation.Cores/Consoles/Intellivision/Intellivision.IEmulator.cs b/BizHawk.Emulation.Cores/Consoles/Intellivision/Intellivision.IEmulator.cs index c9f97c41b2..cc5faa7bf0 100644 --- a/BizHawk.Emulation.Cores/Consoles/Intellivision/Intellivision.IEmulator.cs +++ b/BizHawk.Emulation.Cores/Consoles/Intellivision/Intellivision.IEmulator.cs @@ -7,28 +7,6 @@ namespace BizHawk.Emulation.Cores.Intellivision { public IEmulatorServiceProvider ServiceProvider { get; private set; } - private DCFilter _dcfilter; - - public IAsyncSoundProvider SoundProvider - { - get { return _dcfilter; } - } - - public ISyncSoundProvider SyncSoundProvider - { - get { return new FakeSyncSound(_dcfilter, 735); } - } - - public bool StartAsyncSound() - { - return true; - } - - public void EndAsyncSound() - { - - } - public ControllerDefinition ControllerDefinition { get { return ControllerDeck.Definition; } diff --git a/BizHawk.Emulation.Cores/Consoles/Intellivision/Intellivision.cs b/BizHawk.Emulation.Cores/Consoles/Intellivision/Intellivision.cs index f4da5e6892..21421b1191 100644 --- a/BizHawk.Emulation.Cores/Consoles/Intellivision/Intellivision.cs +++ b/BizHawk.Emulation.Cores/Consoles/Intellivision/Intellivision.cs @@ -64,8 +64,6 @@ namespace BizHawk.Emulation.Cores.Intellivision (ServiceProvider as BasicServiceProvider).Register(Tracer); SetupMemoryDomains(); - - _dcfilter = DCFilter.AsISoundProvider(_psg, 256); // not sure how the 256 will change for intellivision I just copied it from Atari for now } public IntellivisionControllerDeck ControllerDeck { get; private set; } diff --git a/BizHawk.Emulation.Cores/Consoles/Intellivision/PSG.cs b/BizHawk.Emulation.Cores/Consoles/Intellivision/PSG.cs index e6cc9296f9..b8cb09f14b 100644 --- a/BizHawk.Emulation.Cores/Consoles/Intellivision/PSG.cs +++ b/BizHawk.Emulation.Cores/Consoles/Intellivision/PSG.cs @@ -5,7 +5,8 @@ using BizHawk.Emulation.Common; namespace BizHawk.Emulation.Cores.Intellivision { - public sealed class PSG : IAsyncSoundProvider + // Sound refactor todo: Implement ISoundProvider, and register _psg in the Intellivision core + public sealed class PSG { public ushort[] Register = new ushort[16]; diff --git a/BizHawk.Emulation.Cores/Consoles/Nintendo/GBA/MGBAHawk.cs b/BizHawk.Emulation.Cores/Consoles/Nintendo/GBA/MGBAHawk.cs index b3ff06aa78..962945dc34 100644 --- a/BizHawk.Emulation.Cores/Consoles/Nintendo/GBA/MGBAHawk.cs +++ b/BizHawk.Emulation.Cores/Consoles/Nintendo/GBA/MGBAHawk.cs @@ -12,7 +12,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.GBA { [CoreAttributes("mGBA", "endrift", true, true, "0.5.0", "https://mgba.io/", false)] [ServiceNotApplicable(typeof(IDriveLight), typeof(IRegionable))] - public class MGBAHawk : IEmulator, IVideoProvider, ISyncSoundProvider, IGBAGPUViewable, + public class MGBAHawk : IEmulator, IVideoProvider, ISoundProvider, IGBAGPUViewable, ISaveRam, IStatable, IInputPollable, ISettable { private IntPtr _core; @@ -192,9 +192,10 @@ namespace BizHawk.Emulation.Cores.Nintendo.GBA #endregion #region ISoundProvider + private readonly short[] soundbuff = new short[2048]; private int nsamp; - public void GetSamples(out short[] samples, out int nsamp) + public void GetSamplesSync(out short[] samples, out int nsamp) { nsamp = this.nsamp; samples = soundbuff; @@ -204,10 +205,30 @@ namespace BizHawk.Emulation.Cores.Nintendo.GBA { nsamp = 0; } - public IAsyncSoundProvider SoundProvider { get { throw new InvalidOperationException(); } } - public ISyncSoundProvider SyncSoundProvider { get { return this; } } - public bool StartAsyncSound() { return false; } - public void EndAsyncSound() { } + + public bool CanProvideAsync + { + get { return false; } + } + + public void SetSyncMode(SyncSoundMode mode) + { + if (mode == SyncSoundMode.Async) + { + throw new NotSupportedException("Async mode is not supported."); + } + } + + public SyncSoundMode SyncMode + { + get { return SyncSoundMode.Sync; } + } + + public void GetSamplesAsync(short[] samples) + { + throw new InvalidOperationException("Async mode is not supported."); + } + #endregion #region IMemoryDomains diff --git a/BizHawk.Emulation.Cores/Consoles/Nintendo/GBA/Meteor.cs b/BizHawk.Emulation.Cores/Consoles/Nintendo/GBA/Meteor.cs index 82d48d8de5..c957a44ec7 100644 --- a/BizHawk.Emulation.Cores/Consoles/Nintendo/GBA/Meteor.cs +++ b/BizHawk.Emulation.Cores/Consoles/Nintendo/GBA/Meteor.cs @@ -16,7 +16,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.GBA singleInstance: true )] [ServiceNotApplicable(typeof(IDriveLight), typeof(IRegionable))] - public partial class GBA : IEmulator, IVideoProvider, ISyncSoundProvider, IGBAGPUViewable, ISaveRam, IStatable, IInputPollable + public partial class GBA : IEmulator, IVideoProvider, ISoundProvider, IGBAGPUViewable, ISaveRam, IStatable, IInputPollable { [CoreConstructor("GBA")] public GBA(CoreComm comm, byte[] file) @@ -293,12 +293,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.GBA short[] soundbuffer; GCHandle soundhandle; - public IAsyncSoundProvider SoundProvider { get { return null; } } - public ISyncSoundProvider SyncSoundProvider { get { return this; } } - public bool StartAsyncSound() { return false; } - public void EndAsyncSound() { } - - public void GetSamples(out short[] samples, out int nsamp) + public void GetSamplesSync(out short[] samples, out int nsamp) { uint nbytes = LibMeteor.libmeteor_emptysound(); samples = soundbuffer; @@ -313,6 +308,29 @@ namespace BizHawk.Emulation.Cores.Nintendo.GBA LibMeteor.libmeteor_emptysound(); } + public bool CanProvideAsync + { + get { return false; } + } + + public void SetSyncMode(SyncSoundMode mode) + { + if (mode == SyncSoundMode.Async) + { + throw new NotSupportedException("Async mode is not supported."); + } + } + + public SyncSoundMode SyncMode + { + get { return SyncSoundMode.Sync; } + } + + public void GetSamplesAsync(short[] samples) + { + throw new InvalidOperationException("Async mode is not supported."); + } + #endregion } } diff --git a/BizHawk.Emulation.Cores/Consoles/Nintendo/GBA/VBANext.ISoundProvider.cs b/BizHawk.Emulation.Cores/Consoles/Nintendo/GBA/VBANext.ISoundProvider.cs new file mode 100644 index 0000000000..3a9896c4a4 --- /dev/null +++ b/BizHawk.Emulation.Cores/Consoles/Nintendo/GBA/VBANext.ISoundProvider.cs @@ -0,0 +1,44 @@ +using System; +using BizHawk.Emulation.Common; + +namespace BizHawk.Emulation.Cores.Nintendo.GBA +{ + public partial class VBANext : ISoundProvider + { + private short[] soundbuff = new short[2048]; + private int numsamp; + + public void GetSamplesSync(out short[] samples, out int nsamp) + { + samples = soundbuff; + nsamp = numsamp; + } + + public void DiscardSamples() + { + } + + public bool CanProvideAsync + { + get { return false; } + } + + public void SetSyncMode(SyncSoundMode mode) + { + if (mode == SyncSoundMode.Async) + { + throw new NotSupportedException("Async mode is not supported."); + } + } + + public SyncSoundMode SyncMode + { + get { return SyncSoundMode.Sync; } + } + + public void GetSamplesAsync(short[] samples) + { + throw new InvalidOperationException("Async mode is not supported."); + } + } +} diff --git a/BizHawk.Emulation.Cores/Consoles/Nintendo/GBA/VBANext.cs b/BizHawk.Emulation.Cores/Consoles/Nintendo/GBA/VBANext.cs index 5fd4b5b2eb..7a5ec95e2f 100644 --- a/BizHawk.Emulation.Cores/Consoles/Nintendo/GBA/VBANext.cs +++ b/BizHawk.Emulation.Cores/Consoles/Nintendo/GBA/VBANext.cs @@ -1,22 +1,15 @@ using System; -using System.Collections.Generic; using System.Linq; using System.Text; -using System.IO; - -using BizHawk.Common.BufferExtensions; using BizHawk.Emulation.Common; -using Newtonsoft.Json; -using System.ComponentModel; -using BizHawk.Common; using BizHawk.Emulation.Cores.Components.ARM; namespace BizHawk.Emulation.Cores.Nintendo.GBA { [CoreAttributes("VBA-Next", "many authors", true, true, "cd508312a29ed8c29dacac1b11c2dce56c338a54", "https://github.com/libretro/vba-next")] [ServiceNotApplicable(typeof(IDriveLight), typeof(IRegionable))] - public partial class VBANext : IEmulator, IVideoProvider, ISyncSoundProvider, IInputPollable, + public partial class VBANext : IEmulator, IVideoProvider, ISoundProvider, IInputPollable, IGBAGPUViewable, ISaveRam, IStatable, IDebuggable, ISettable { IntPtr Core; @@ -226,27 +219,5 @@ namespace BizHawk.Emulation.Cores.Nintendo.GBA } #endregion - - #region SoundProvider - - short[] soundbuff = new short[2048]; - int numsamp; - - public IAsyncSoundProvider SoundProvider { get { return null; } } - public ISyncSoundProvider SyncSoundProvider { get { return this; } } - public bool StartAsyncSound() { return false; } - public void EndAsyncSound() { } - - public void GetSamples(out short[] samples, out int nsamp) - { - samples = soundbuff; - nsamp = numsamp; - } - - public void DiscardSamples() - { - } - - #endregion } } diff --git a/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/Gambatte.ISoundProvider.cs b/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/Gambatte.ISoundProvider.cs new file mode 100644 index 0000000000..df1f052613 --- /dev/null +++ b/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/Gambatte.ISoundProvider.cs @@ -0,0 +1,124 @@ +using System; +using BizHawk.Emulation.Common; + +namespace BizHawk.Emulation.Cores.Nintendo.Gameboy +{ + public partial class Gameboy : ISoundProvider + { + public bool CanProvideAsync + { + get { return false; } + } + + public void DiscardSamples() + { + soundoutbuffcontains = 0; + } + + public void GetSamplesSync(out short[] samples, out int nsamp) + { + samples = soundoutbuff; + nsamp = soundoutbuffcontains; + } + + public void SetSyncMode(SyncSoundMode mode) + { + if (mode == SyncSoundMode.Async) + { + throw new NotSupportedException("Async mode is not supported."); + } + } + + public SyncSoundMode SyncMode + { + get { return SyncSoundMode.Sync; } + } + + public void GetSamplesAsync(short[] samples) + { + throw new InvalidOperationException("Async mode is not supported."); + } + + internal bool Muted + { + get { return _settings.Muted; } + } + + // sample pairs before resampling + private short[] soundbuff = new short[(35112 + 2064) * 2]; + + private int soundoutbuffcontains = 0; + + private short[] soundoutbuff = new short[2048]; + + private int latchL = 0; + private int latchR = 0; + + private BlipBuffer blipL, blipR; + private uint blipAccumulate; + + private void ProcessSound(int nsamp) + { + for (uint i = 0; i < nsamp; i++) + { + int curr = soundbuff[i * 2]; + + if (curr != latchL) + { + int diff = latchL - curr; + latchL = curr; + blipL.AddDelta(blipAccumulate, diff); + } + + curr = soundbuff[i * 2 + 1]; + + if (curr != latchR) + { + int diff = latchR - curr; + latchR = curr; + blipR.AddDelta(blipAccumulate, diff); + } + + blipAccumulate++; + } + } + + private void ProcessSoundEnd() + { + blipL.EndFrame(blipAccumulate); + blipR.EndFrame(blipAccumulate); + blipAccumulate = 0; + + soundoutbuffcontains = blipL.SamplesAvailable(); + if (soundoutbuffcontains != blipR.SamplesAvailable()) + { + throw new InvalidOperationException("Audio processing error"); + } + + blipL.ReadSamplesLeft(soundoutbuff, soundoutbuffcontains); + blipR.ReadSamplesRight(soundoutbuff, soundoutbuffcontains); + } + + private void InitSound() + { + blipL = new BlipBuffer(1024); + blipL.SetRates(TICKSPERSECOND, 44100); + blipR = new BlipBuffer(1024); + blipR.SetRates(TICKSPERSECOND, 44100); + } + + private void DisposeSound() + { + if (blipL != null) + { + blipL.Dispose(); + blipL = null; + } + if (blipR != null) + { + blipR.Dispose(); + blipR = null; + } + } + } +} diff --git a/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/Gambatte.cs b/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/Gambatte.cs index ebab363ab7..58cb177729 100644 --- a/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/Gambatte.cs +++ b/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/Gambatte.cs @@ -1,13 +1,7 @@ using System; -using System.Collections.Generic; -using System.ComponentModel; -using System.IO; using BizHawk.Common.BufferExtensions; using BizHawk.Emulation.Common; -using BizHawk.Common; - -using Newtonsoft.Json; namespace BizHawk.Emulation.Cores.Nintendo.Gameboy { @@ -23,7 +17,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.Gameboy portedUrl: "http://gambatte.sourceforge.net/" )] [ServiceNotApplicable(typeof(IDriveLight), typeof(IDriveLight))] - public partial class Gameboy : IEmulator, IVideoProvider, ISyncSoundProvider, ISaveRam, IStatable, IInputPollable, ICodeDataLogger, + public partial class Gameboy : IEmulator, IVideoProvider, ISoundProvider, ISaveRam, IStatable, IInputPollable, ICodeDataLogger, IDebuggable, ISettable { /// @@ -566,103 +560,5 @@ namespace BizHawk.Emulation.Cores.Nintendo.Gameboy } #endregion - - #region ISoundProvider - - public IAsyncSoundProvider SoundProvider { get { return null; } } - public ISyncSoundProvider SyncSoundProvider { get { return this; } } - public bool StartAsyncSound() { return false; } - public void EndAsyncSound() { } - - /// - /// sample pairs before resampling - /// - short[] soundbuff = new short[(35112 + 2064) * 2]; - - int soundoutbuffcontains = 0; - - short[] soundoutbuff = new short[2048]; - - int latchL = 0; - int latchR = 0; - - BlipBuffer blipL, blipR; - uint blipAccumulate; - - private void ProcessSound(int nsamp) - { - for (uint i = 0; i < nsamp; i++) - { - int curr = soundbuff[i * 2]; - - if (curr != latchL) - { - int diff = latchL - curr; - latchL = curr; - blipL.AddDelta(blipAccumulate, diff); - } - curr = soundbuff[i * 2 + 1]; - - if (curr != latchR) - { - int diff = latchR - curr; - latchR = curr; - blipR.AddDelta(blipAccumulate, diff); - } - - blipAccumulate++; - } - } - - private void ProcessSoundEnd() - { - blipL.EndFrame(blipAccumulate); - blipR.EndFrame(blipAccumulate); - blipAccumulate = 0; - - soundoutbuffcontains = blipL.SamplesAvailable(); - if (soundoutbuffcontains != blipR.SamplesAvailable()) - throw new InvalidOperationException("Audio processing error"); - - blipL.ReadSamplesLeft(soundoutbuff, soundoutbuffcontains); - blipR.ReadSamplesRight(soundoutbuff, soundoutbuffcontains); - } - - void InitSound() - { - blipL = new BlipBuffer(1024); - blipL.SetRates(TICKSPERSECOND, 44100); - blipR = new BlipBuffer(1024); - blipR.SetRates(TICKSPERSECOND, 44100); - } - - void DisposeSound() - { - if (blipL != null) - { - blipL.Dispose(); - blipL = null; - } - if (blipR != null) - { - blipR.Dispose(); - blipR = null; - } - } - - public void DiscardSamples() - { - soundoutbuffcontains = 0; - } - - public void GetSamples(out short[] samples, out int nsamp) - { - samples = soundoutbuff; - nsamp = soundoutbuffcontains; - } - - public bool Muted { get { return _settings.Muted; } } - - #endregion } } diff --git a/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/GambatteLink.cs b/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/GambatteLink.cs index 7ef9122079..92464ffc9e 100644 --- a/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/GambatteLink.cs +++ b/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/GambatteLink.cs @@ -1,14 +1,8 @@ using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using BizHawk.Common.BufferExtensions; using BizHawk.Emulation.Common; using BizHawk.Emulation.Cores.Nintendo.SNES; -using Newtonsoft.Json; - namespace BizHawk.Emulation.Cores.Nintendo.Gameboy { [CoreAttributes( @@ -18,7 +12,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.Gameboy isReleased: true )] [ServiceNotApplicable(typeof(IDriveLight))] - public partial class GambatteLink : IEmulator, IVideoProvider, ISyncSoundProvider, IInputPollable, ISaveRam, IStatable, ILinkable, + public partial class GambatteLink : IEmulator, IVideoProvider, ISoundProvider, IInputPollable, ISaveRam, IStatable, ILinkable, IDebuggable, ISettable, ICodeDataLogger { public GambatteLink(CoreComm comm, GameInfo leftinfo, byte[] leftrom, GameInfo rightinfo, byte[] rightrom, object Settings, object SyncSettings, bool deterministic) @@ -89,11 +83,6 @@ namespace BizHawk.Emulation.Cores.Nintendo.Gameboy public IEmulatorServiceProvider ServiceProvider { get; private set; } - public IAsyncSoundProvider SoundProvider { get { return null; } } - public ISyncSoundProvider SyncSoundProvider { get { return this; } } - public bool StartAsyncSound() { return false; } - public void EndAsyncSound() { } - public static readonly ControllerDefinition DualGbController = new ControllerDefinition { Name = "Dual Gameboy Controller", @@ -327,7 +316,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.Gameboy SampleBufferContains = count; } - public void GetSamples(out short[] samples, out int nsamp) + public void GetSamplesSync(out short[] samples, out int nsamp) { nsamp = SampleBufferContains; samples = SampleBuffer; @@ -338,6 +327,29 @@ namespace BizHawk.Emulation.Cores.Nintendo.Gameboy SampleBufferContains = 0; } + public bool CanProvideAsync + { + get { return false; } + } + + public void SetSyncMode(SyncSoundMode mode) + { + if (mode == SyncSoundMode.Async) + { + throw new NotSupportedException("Async mode is not supported."); + } + } + + public SyncSoundMode SyncMode + { + get { return SyncSoundMode.Sync; } + } + + public void GetSamplesAsync(short[] samples) + { + throw new InvalidOperationException("Async mode is not supported."); + } + #endregion } } diff --git a/BizHawk.Emulation.Cores/Consoles/Nintendo/N64/N64.cs b/BizHawk.Emulation.Cores/Consoles/Nintendo/N64/N64.cs index 50560030b5..2a047a2bd0 100644 --- a/BizHawk.Emulation.Cores/Consoles/Nintendo/N64/N64.cs +++ b/BizHawk.Emulation.Cores/Consoles/Nintendo/N64/N64.cs @@ -130,6 +130,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.N64 _audioProvider = new N64Audio(api); _inputProvider = new N64Input(this.AsInputPollable(), api, comm, this._syncSettings.Controllers); (ServiceProvider as BasicServiceProvider).Register(_videoProvider); + (ServiceProvider as BasicServiceProvider).Register(_audioProvider.Resampler); string rsp; switch (_syncSettings.Rsp) @@ -268,14 +269,6 @@ namespace BizHawk.Emulation.Cores.Nintendo.N64 public DisplayType Region { get { return _display_type; } } - public IAsyncSoundProvider SoundProvider { get { return null; } } - - public ISyncSoundProvider SyncSoundProvider { get { return _audioProvider.Resampler; } } - - public bool StartAsyncSound() { return false; } - - public void EndAsyncSound() { } - public ControllerDefinition ControllerDefinition { get { return _inputProvider.ControllerDefinition; } diff --git a/BizHawk.Emulation.Cores/Consoles/Nintendo/NES/NES.Core.cs b/BizHawk.Emulation.Cores/Consoles/Nintendo/NES/NES.Core.cs index d12b5a9011..da1575055b 100644 --- a/BizHawk.Emulation.Cores/Consoles/Nintendo/NES/NES.Core.cs +++ b/BizHawk.Emulation.Cores/Consoles/Nintendo/NES/NES.Core.cs @@ -79,7 +79,9 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES magicSoundProvider = null; } - class MagicSoundProvider : IAsyncSoundProvider, ISyncSoundProvider, IDisposable + // Sound refactor todo: do we want to support async? + class MagicSoundProvider + : ISoundProvider, IDisposable { BlipBuffer blip; NES nes; @@ -99,30 +101,30 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES //output = new Sound.Utilities.DCFilter(actualMetaspu); } - public void GetSamples(short[] samples) + public bool CanProvideAsync { - //Console.WriteLine("Sync: {0}", nes.apu.dlist.Count); - int nsamp = samples.Length / 2; - if (nsamp > blipbuffsize) // oh well. - nsamp = blipbuffsize; - uint targetclock = (uint)blip.ClocksNeeded(nsamp); - uint actualclock = nes.apu.sampleclock; - foreach (var d in nes.apu.dlist) - blip.AddDelta(d.time * targetclock / actualclock, d.value); - nes.apu.dlist.Clear(); - blip.EndFrame(targetclock); - nes.apu.sampleclock = 0; - - blip.ReadSamples(samples, nsamp, true); - // duplicate to stereo - for (int i = 0; i < nsamp * 2; i += 2) - samples[i + 1] = samples[i]; - - //mix in the cart's extra sound circuit - nes.Board.ApplyCustomAudio(samples); + get { return false; } } - public void GetSamples(out short[] samples, out int nsamp) + public SyncSoundMode SyncMode + { + get { return SyncSoundMode.Sync; } + } + + public void SetSyncMode(SyncSoundMode mode) + { + if (mode != SyncSoundMode.Sync) + { + throw new NotSupportedException("Only sync mode is supported"); + } + } + + public void GetSamplesAsync(short[] samples) + { + throw new NotSupportedException("Async not supported"); + } + + public void GetSamplesSync(out short[] samples, out int nsamp) { //Console.WriteLine("ASync: {0}", nes.apu.dlist.Count); foreach (var d in nes.apu.dlist) diff --git a/BizHawk.Emulation.Cores/Consoles/Nintendo/NES/NES.cs b/BizHawk.Emulation.Cores/Consoles/Nintendo/NES/NES.cs index ba2dddd43c..d3b11d0c57 100644 --- a/BizHawk.Emulation.Cores/Consoles/Nintendo/NES/NES.cs +++ b/BizHawk.Emulation.Cores/Consoles/Nintendo/NES/NES.cs @@ -70,6 +70,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES Tracer = new TraceBuffer { Header = cpu.TraceHeader }; ser.Register(Tracer); ser.Register(videoProvider); + ser.Register(magicSoundProvider); if (Board is BANDAI_FCG_1) { @@ -334,10 +335,6 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES } MyVideoProvider videoProvider; - public IAsyncSoundProvider SoundProvider { get { return magicSoundProvider; } } - public ISyncSoundProvider SyncSoundProvider { get { return magicSoundProvider; } } - public bool StartAsyncSound() { return true; } - public void EndAsyncSound() { } [Obsolete] // with the changes to both nes and quicknes cores, nothing uses this anymore public static readonly ControllerDefinition NESController = diff --git a/BizHawk.Emulation.Cores/Consoles/Nintendo/QuickNES/QuickNES.ISoundProvider.cs b/BizHawk.Emulation.Cores/Consoles/Nintendo/QuickNES/QuickNES.ISoundProvider.cs new file mode 100644 index 0000000000..1aa0f14568 --- /dev/null +++ b/BizHawk.Emulation.Cores/Consoles/Nintendo/QuickNES/QuickNES.ISoundProvider.cs @@ -0,0 +1,69 @@ +using System; +using BizHawk.Emulation.Common; + +namespace BizHawk.Emulation.Cores.Consoles.Nintendo.QuickNES +{ + public partial class QuickNES : ISoundProvider + { + private short[] MonoBuff = new short[1024]; + private short[] StereoBuff = new short[2048]; + private int NumSamples = 0; + + public bool CanProvideAsync + { + get { return false; } + } + + public void GetSamplesSync(out short[] samples, out int nsamp) + { + samples = StereoBuff; + nsamp = NumSamples; + } + + public void DiscardSamples() + { + // Nothing to do + } + + public void SetSyncMode(SyncSoundMode mode) + { + if (mode == SyncSoundMode.Async) + { + throw new NotSupportedException("Async mode is not supported."); + } + } + + public SyncSoundMode SyncMode + { + get { return SyncSoundMode.Sync; } + } + + public void GetSamplesAsync(short[] samples) + { + throw new InvalidOperationException("Async mode is not supported."); + } + + private void InitAudio() + { + LibQuickNES.ThrowStringError(QN.qn_set_sample_rate(Context, 44100)); + } + + private void DrainAudio() + { + NumSamples = QN.qn_read_audio(Context, MonoBuff, MonoBuff.Length); + unsafe + { + fixed (short* _src = &MonoBuff[0], _dst = &StereoBuff[0]) + { + short* src = _src; + short* dst = _dst; + for (int i = 0; i < NumSamples; i++) + { + *dst++ = *src; + *dst++ = *src++; + } + } + } + } + } +} diff --git a/BizHawk.Emulation.Cores/Consoles/Nintendo/QuickNES/QuickNES.cs b/BizHawk.Emulation.Cores/Consoles/Nintendo/QuickNES/QuickNES.cs index 049b1426a2..9c1e126929 100644 --- a/BizHawk.Emulation.Cores/Consoles/Nintendo/QuickNES/QuickNES.cs +++ b/BizHawk.Emulation.Cores/Consoles/Nintendo/QuickNES/QuickNES.cs @@ -1,16 +1,9 @@ using System; using System.Collections.Generic; -using System.ComponentModel; using System.Linq; using System.Runtime.InteropServices; -using System.Text; -using Newtonsoft.Json; - -using BizHawk.Common.BufferExtensions; using BizHawk.Emulation.Common; -using BizHawk.Common; -using BizHawk.Common.CollectionExtensions; using BizHawk.Emulation.Common.BizInvoke; namespace BizHawk.Emulation.Cores.Consoles.Nintendo.QuickNES @@ -24,7 +17,7 @@ namespace BizHawk.Emulation.Cores.Consoles.Nintendo.QuickNES portedUrl: "https://github.com/kode54/QuickNES" )] [ServiceNotApplicable(typeof(IDriveLight))] - public partial class QuickNES : IEmulator, IVideoProvider, ISyncSoundProvider, ISaveRam, IInputPollable, + public partial class QuickNES : IEmulator, IVideoProvider, ISoundProvider, ISaveRam, IInputPollable, IStatable, IDebuggable, ISettable, Cores.Nintendo.NES.INESPPUViewable { static readonly LibQuickNES QN; @@ -320,52 +313,6 @@ namespace BizHawk.Emulation.Cores.Consoles.Nintendo.QuickNES throw new ObjectDisposedException(GetType().Name); } - #region SoundProvider - - public IAsyncSoundProvider SoundProvider { get { return null; } } - public ISyncSoundProvider SyncSoundProvider { get { return this; } } - public bool StartAsyncSound() { return false; } - public void EndAsyncSound() { } - - void InitAudio() - { - LibQuickNES.ThrowStringError(QN.qn_set_sample_rate(Context, 44100)); - } - - void DrainAudio() - { - NumSamples = QN.qn_read_audio(Context, MonoBuff, MonoBuff.Length); - unsafe - { - fixed (short* _src = &MonoBuff[0], _dst = &StereoBuff[0]) - { - short* src = _src; - short* dst = _dst; - for (int i = 0; i < NumSamples; i++) - { - *dst++ = *src; - *dst++ = *src++; - } - } - } - } - - short[] MonoBuff = new short[1024]; - short[] StereoBuff = new short[2048]; - int NumSamples = 0; - - public void GetSamples(out short[] samples, out int nsamp) - { - samples = StereoBuff; - nsamp = NumSamples; - } - - public void DiscardSamples() - { - } - - #endregion - #region Blacklist // These games are known to not work in quicknes but quicknes thinks it can run them, bail out if one of these is loaded diff --git a/BizHawk.Emulation.Cores/Consoles/Nintendo/SNES/LibsnesCore.cs b/BizHawk.Emulation.Cores/Consoles/Nintendo/SNES/LibsnesCore.cs index 1c9fbbf090..de3845cc00 100644 --- a/BizHawk.Emulation.Cores/Consoles/Nintendo/SNES/LibsnesCore.cs +++ b/BizHawk.Emulation.Cores/Consoles/Nintendo/SNES/LibsnesCore.cs @@ -84,6 +84,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.SNES // start up audio resampler InitAudio(); + (ServiceProvider as BasicServiceProvider).Register(resampler); //strip header if (romData != null) @@ -1300,10 +1301,6 @@ namespace BizHawk.Emulation.Cores.Nintendo.SNES resampler.EnqueueSample((short)left, (short)right); } - public IAsyncSoundProvider SoundProvider { get { return null; } } - public ISyncSoundProvider SyncSoundProvider { get { return resampler; } } - public bool StartAsyncSound() { return false; } - public void EndAsyncSound() { } #endregion audio stuff diff --git a/BizHawk.Emulation.Cores/Consoles/Nintendo/SNES9X/Snes9x.cs b/BizHawk.Emulation.Cores/Consoles/Nintendo/SNES9X/Snes9x.cs index 6cd9409863..5b8dcaf782 100644 --- a/BizHawk.Emulation.Cores/Consoles/Nintendo/SNES9X/Snes9x.cs +++ b/BizHawk.Emulation.Cores/Consoles/Nintendo/SNES9X/Snes9x.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Text; using BizHawk.Common; using BizHawk.Common.BufferExtensions; @@ -11,7 +10,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.SNES9X { [CoreAttributes("Snes9x", "FIXME", true, false, "5e0319ab3ef9611250efb18255186d0dc0d7e125", "https://github.com/snes9xgit/snes9x", true)] [ServiceNotApplicable(typeof(IDriveLight))] - public class Snes9x : IEmulator, IVideoProvider, ISyncSoundProvider + public class Snes9x : IEmulator, IVideoProvider, ISoundProvider { #region controller @@ -72,15 +71,11 @@ namespace BizHawk.Emulation.Cores.Nintendo.SNES9X #endregion - #region ISyncSoundProvider + #region ISoundProvider private short[] _sbuff = new short[2048]; - public IAsyncSoundProvider SoundProvider { get { return null; } } - public ISyncSoundProvider SyncSoundProvider { get { return this; } } - public bool StartAsyncSound() { return false; } - public void EndAsyncSound() { } - public void GetSamples(out short[] samples, out int nsamp) + public void GetSamplesSync(out short[] samples, out int nsamp) { samples = _sbuff; nsamp = 735; @@ -88,6 +83,30 @@ namespace BizHawk.Emulation.Cores.Nintendo.SNES9X public void DiscardSamples() { + // Nothing to do + } + + public void SetSyncMode(SyncSoundMode mode) + { + if (mode == SyncSoundMode.Async) + { + throw new NotSupportedException("Async mode is not supported."); + } + } + + public bool CanProvideAsync + { + get { return false; } + } + + public SyncSoundMode SyncMode + { + get { return SyncSoundMode.Sync; } + } + + public void GetSamplesAsync(short[] samples) + { + throw new InvalidOperationException("Async mode is not supported."); } #endregion diff --git a/BizHawk.Emulation.Cores/Consoles/PC Engine/ADPCM.cs b/BizHawk.Emulation.Cores/Consoles/PC Engine/ADPCM.cs index 8f19ee1bce..3bace6afb6 100644 --- a/BizHawk.Emulation.Cores/Consoles/PC Engine/ADPCM.cs +++ b/BizHawk.Emulation.Cores/Consoles/PC Engine/ADPCM.cs @@ -283,7 +283,7 @@ namespace BizHawk.Emulation.Cores.PCEngine void AdpcmEmitSample() { if (AdpcmIsPlaying == false) - SoundProvider.buffer.enqueue_sample(0, 0); + SoundProvider.Buffer.enqueue_sample(0, 0); else { if (nextSampleTimer <= 0) @@ -303,13 +303,13 @@ namespace BizHawk.Emulation.Cores.PCEngine } short adjustedSample = (short)((playingSample - 2048) * MaxVolume / 2048); - SoundProvider.buffer.enqueue_sample(adjustedSample, adjustedSample); + SoundProvider.Buffer.enqueue_sample(adjustedSample, adjustedSample); } } public void GetSamples(short[] samples) { - SoundProvider.GetSamples(samples); + SoundProvider.GetSamplesAsync(samples); } public void DiscardSamples() diff --git a/BizHawk.Emulation.Cores/Consoles/PC Engine/PCEngine.cs b/BizHawk.Emulation.Cores/Consoles/PC Engine/PCEngine.cs index 5666d13d46..d37f151e25 100644 --- a/BizHawk.Emulation.Cores/Consoles/PC Engine/PCEngine.cs +++ b/BizHawk.Emulation.Cores/Consoles/PC Engine/PCEngine.cs @@ -43,7 +43,7 @@ namespace BizHawk.Emulation.Cores.PCEngine public HuC6280PSG PSG; public CDAudio CDAudio; public SoundMixer SoundMixer; - public MetaspuSoundProvider SoundSynchronizer; + //public MetaspuSoundProvider SoundSynchronizer; bool TurboGrafx { get { return Type == NecSystemType.TurboGrafx; } } bool SuperGrafx { get { return Type == NecSystemType.SuperGrafx; } } @@ -171,7 +171,7 @@ namespace BizHawk.Emulation.Cores.PCEngine Cpu.ReadMemory21 = ReadMemory; Cpu.WriteMemory21 = WriteMemory; Cpu.WriteVDC = VDC1.WriteVDC; - soundProvider = PSG; + soundProvider = new FakeSyncSound(PSG, 735); CDAudio = new CDAudio(null, 0); } @@ -183,7 +183,7 @@ namespace BizHawk.Emulation.Cores.PCEngine Cpu.ReadMemory21 = ReadMemorySGX; Cpu.WriteMemory21 = WriteMemorySGX; Cpu.WriteVDC = VDC1.WriteVDC; - soundProvider = PSG; + soundProvider = new FakeSyncSound(PSG, 735); CDAudio = new CDAudio(null, 0); } @@ -199,8 +199,7 @@ namespace BizHawk.Emulation.Cores.PCEngine SetCDAudioCallback(); PSG.MaxVolume = short.MaxValue * 3 / 4; SoundMixer = new SoundMixer(PSG, CDAudio, ADPCM); - SoundSynchronizer = new MetaspuSoundProvider(ESynchMethod.ESynchMethod_V); - soundProvider = SoundSynchronizer; + soundProvider = new FakeSyncSound(SoundMixer, 735); Cpu.ThinkAction = (cycles) => { SCSI.Think(); ADPCM.Think(cycles); }; } @@ -305,6 +304,7 @@ namespace BizHawk.Emulation.Cores.PCEngine ser.Register(Tracer); ser.Register(Cpu); ser.Register((IVideoProvider)VPC ?? VDC1); + ser.Register(soundProvider); SetupMemoryDomains(); } @@ -384,8 +384,6 @@ namespace BizHawk.Emulation.Cores.PCEngine VDC1.ExecFrame(render); PSG.EndFrame(Cpu.TotalExecutedCycles); - if (TurboCD) - SoundSynchronizer.PullSamples(SoundMixer); if (lagged) { @@ -406,14 +404,7 @@ namespace BizHawk.Emulation.Cores.PCEngine public CoreComm CoreComm { get; private set; } - IAsyncSoundProvider soundProvider; - public IAsyncSoundProvider SoundProvider - { - get { return soundProvider; } - } - public ISyncSoundProvider SyncSoundProvider { get { return new FakeSyncSound(soundProvider, 735); } } - public bool StartAsyncSound() { return true; } - public void EndAsyncSound() { } + private ISoundProvider soundProvider; public string SystemId { get { return systemid; } } public string Region { get; set; } diff --git a/BizHawk.Emulation.Cores/Consoles/Sega/Genesis/Genesis.cs b/BizHawk.Emulation.Cores/Consoles/Sega/Genesis/Genesis.cs index ca693f00c0..33a73126d3 100644 --- a/BizHawk.Emulation.Cores/Consoles/Sega/Genesis/Genesis.cs +++ b/BizHawk.Emulation.Cores/Consoles/Sega/Genesis/Genesis.cs @@ -301,13 +301,15 @@ namespace BizHawk.Emulation.Cores.Sega.Genesis public CoreComm CoreComm { get; private set; } + // Sound refactor todo: Implement ISoundProvider + /* public IAsyncSoundProvider SoundProvider { get { return SoundMixer; } } + public ISyncSoundProvider SyncSoundProvider { get { return new FakeSyncSound(SoundMixer, 735); } } - public bool StartAsyncSound() { return true; } - public void EndAsyncSound() { } + */ public int Frame { get; set; } public int LagCount { get { return _lagcount; } set { _lagcount = value; } } diff --git a/BizHawk.Emulation.Cores/Consoles/Sega/SMS/SMS.IEmulator.cs b/BizHawk.Emulation.Cores/Consoles/Sega/SMS/SMS.IEmulator.cs index 4fb8361a9a..a8deefb692 100644 --- a/BizHawk.Emulation.Cores/Consoles/Sega/SMS/SMS.IEmulator.cs +++ b/BizHawk.Emulation.Cores/Consoles/Sega/SMS/SMS.IEmulator.cs @@ -6,23 +6,6 @@ namespace BizHawk.Emulation.Cores.Sega.MasterSystem { public IEmulatorServiceProvider ServiceProvider { get; private set; } - public IAsyncSoundProvider SoundProvider - { - get { return ActiveSoundProvider; } - } - - public ISyncSoundProvider SyncSoundProvider - { - get { return new FakeSyncSound(ActiveSoundProvider, 735); } - } - - public bool StartAsyncSound() - { - return true; - } - - public void EndAsyncSound() { } - public ControllerDefinition ControllerDefinition { get diff --git a/BizHawk.Emulation.Cores/Consoles/Sega/SMS/SMS.ISoundProvider.cs b/BizHawk.Emulation.Cores/Consoles/Sega/SMS/SMS.ISoundProvider.cs new file mode 100644 index 0000000000..ba64537826 --- /dev/null +++ b/BizHawk.Emulation.Cores/Consoles/Sega/SMS/SMS.ISoundProvider.cs @@ -0,0 +1,13 @@ +using System; +using BizHawk.Emulation.Common; + +namespace BizHawk.Emulation.Cores.Sega.MasterSystem +{ + // Sound refactor TODO: Implement ISoundProvider without the need for FakeSyncSound + public sealed partial class SMS + { + private FakeSyncSound _fakeSyncSound; + private IAsyncSoundProvider ActiveSoundProvider; + private SoundMixer SoundMixer; + } +} diff --git a/BizHawk.Emulation.Cores/Consoles/Sega/SMS/SMS.cs b/BizHawk.Emulation.Cores/Consoles/Sega/SMS/SMS.cs index 696da68ceb..75190da025 100644 --- a/BizHawk.Emulation.Cores/Consoles/Sega/SMS/SMS.cs +++ b/BizHawk.Emulation.Cores/Consoles/Sega/SMS/SMS.cs @@ -42,7 +42,6 @@ namespace BizHawk.Emulation.Cores.Sega.MasterSystem public VDP Vdp; private SN76489 PSG; private YM2413 YM2413; - private SoundMixer SoundMixer; public bool IsGameGear { get; set; } public bool IsSG1000 { get; set; } @@ -119,6 +118,8 @@ namespace BizHawk.Emulation.Cores.Sega.MasterSystem if (HasYM2413 && game["WhenFMDisablePSG"]) SoundMixer.DisableSource(PSG); ActiveSoundProvider = HasYM2413 ? (IAsyncSoundProvider)SoundMixer : PSG; + _fakeSyncSound = new FakeSyncSound(ActiveSoundProvider, 735); + (ServiceProvider as BasicServiceProvider).Register(_fakeSyncSound); SystemRam = new byte[0x2000]; @@ -310,8 +311,6 @@ namespace BizHawk.Emulation.Cores.Sega.MasterSystem else if (port == 0xF2 && HasYM2413) YM2413.DetectionValue = value; } - private IAsyncSoundProvider ActiveSoundProvider; - private string _region; private string RegionStr { diff --git a/BizHawk.Emulation.Cores/Consoles/Sega/Saturn/Yabause.cs b/BizHawk.Emulation.Cores/Consoles/Sega/Saturn/Yabause.cs index ef1bf91d4b..54a33c8713 100644 --- a/BizHawk.Emulation.Cores/Consoles/Sega/Saturn/Yabause.cs +++ b/BizHawk.Emulation.Cores/Consoles/Sega/Saturn/Yabause.cs @@ -24,7 +24,7 @@ namespace BizHawk.Emulation.Cores.Sega.Saturn portedUrl: "http://yabause.org", singleInstance: true )] - public partial class Yabause : IEmulator, IVideoProvider, ISyncSoundProvider, ISaveRam, IStatable, IInputPollable, + public partial class Yabause : IEmulator, IVideoProvider, ISoundProvider, ISaveRam, IStatable, IInputPollable, ISettable, IDriveLight { public static ControllerDefinition SaturnController = new ControllerDefinition @@ -354,20 +354,39 @@ namespace BizHawk.Emulation.Cores.Sega.Saturn #region ISyncSoundProvider - short[] SoundBuffer = new short[44100 * 2]; - int SoundNSamp = 0; + private short[] SoundBuffer = new short[44100 * 2]; + private int SoundNSamp = 0; - public void GetSamples(out short[] samples, out int nsamp) + public void GetSamplesSync(out short[] samples, out int nsamp) { nsamp = SoundNSamp; samples = SoundBuffer; } public void DiscardSamples() { } - public IAsyncSoundProvider SoundProvider { get { return null; } } - public ISyncSoundProvider SyncSoundProvider { get { return this; } } - public bool StartAsyncSound() { return false; } - public void EndAsyncSound() { } + + public bool CanProvideAsync + { + get { return false; } + } + + public void SetSyncMode(SyncSoundMode mode) + { + if (mode == SyncSoundMode.Async) + { + throw new NotSupportedException("Async mode is not supported."); + } + } + + public SyncSoundMode SyncMode + { + get { return SyncSoundMode.Sync; } + } + + public void GetSamplesAsync(short[] samples) + { + throw new InvalidOperationException("Async mode is not supported."); + } #endregion diff --git a/BizHawk.Emulation.Cores/Consoles/Sega/gpgx/GPGX.IEmulator.cs b/BizHawk.Emulation.Cores/Consoles/Sega/gpgx/GPGX.IEmulator.cs index 3c30f7b605..96f9b39dc2 100644 --- a/BizHawk.Emulation.Cores/Consoles/Sega/gpgx/GPGX.IEmulator.cs +++ b/BizHawk.Emulation.Cores/Consoles/Sega/gpgx/GPGX.IEmulator.cs @@ -7,20 +7,6 @@ namespace BizHawk.Emulation.Cores.Consoles.Sega.gpgx { public IEmulatorServiceProvider ServiceProvider { get; private set; } - public IAsyncSoundProvider SoundProvider { get { return null; } } - - public ISyncSoundProvider SyncSoundProvider - { - get { return this; } - } - - public bool StartAsyncSound() - { - return false; - } - - public void EndAsyncSound() { } - public ControllerDefinition ControllerDefinition { get; private set; } public IController Controller { get; set; } diff --git a/BizHawk.Emulation.Cores/Consoles/Sega/gpgx/GPGX.ISoundProvider.cs b/BizHawk.Emulation.Cores/Consoles/Sega/gpgx/GPGX.ISoundProvider.cs new file mode 100644 index 0000000000..9e46a1b20a --- /dev/null +++ b/BizHawk.Emulation.Cores/Consoles/Sega/gpgx/GPGX.ISoundProvider.cs @@ -0,0 +1,57 @@ +using System; +using BizHawk.Emulation.Common; +using System.Runtime.InteropServices; + +namespace BizHawk.Emulation.Cores.Consoles.Sega.gpgx +{ + public partial class GPGX : ISoundProvider + { + private short[] samples = new short[4096]; + private int nsamp = 0; + + public bool CanProvideAsync + { + get { return false; } + } + + public void GetSamplesSync(out short[] samples, out int nsamp) + { + nsamp = this.nsamp; + samples = this.samples; + this.nsamp = 0; + } + + public void DiscardSamples() + { + this.nsamp = 0; + } + + public void SetSyncMode(SyncSoundMode mode) + { + if (mode == SyncSoundMode.Async) + { + throw new NotSupportedException("Async mode is not supported."); + } + } + + public SyncSoundMode SyncMode + { + get { return SyncSoundMode.Sync; } + } + + public void GetSamplesAsync(short[] samples) + { + throw new InvalidOperationException("Async mode is not supported."); + } + + private void update_audio() + { + IntPtr src = IntPtr.Zero; + LibGPGX.gpgx_get_audio(ref nsamp, ref src); + if (src != IntPtr.Zero) + { + Marshal.Copy(src, samples, 0, nsamp * 2); + } + } + } +} diff --git a/BizHawk.Emulation.Cores/Consoles/Sega/gpgx/GPGX.cs b/BizHawk.Emulation.Cores/Consoles/Sega/gpgx/GPGX.cs index 4153d47f4d..39196adc99 100644 --- a/BizHawk.Emulation.Cores/Consoles/Sega/gpgx/GPGX.cs +++ b/BizHawk.Emulation.Cores/Consoles/Sega/gpgx/GPGX.cs @@ -14,7 +14,7 @@ namespace BizHawk.Emulation.Cores.Consoles.Sega.gpgx portedUrl: "https://code.google.com/p/genplus-gx/", singleInstance: true )] - public partial class GPGX : IEmulator, ISyncSoundProvider, IVideoProvider, ISaveRam, IStatable, IRegionable, + public partial class GPGX : IEmulator, IVideoProvider, ISaveRam, IStatable, IRegionable, IInputPollable, IDebuggable, IDriveLight, ICodeDataLogger, IDisassemblable { static GPGX AttachedCore = null; @@ -372,31 +372,6 @@ namespace BizHawk.Emulation.Cores.Consoles.Sega.gpgx LibGPGX.gpgx_flush_vram(); // fully regenerate internal caches as needed } - short[] samples = new short[4096]; - int nsamp = 0; - - public void GetSamples(out short[] samples, out int nsamp) - { - nsamp = this.nsamp; - samples = this.samples; - this.nsamp = 0; - } - - public void DiscardSamples() - { - this.nsamp = 0; - } - - void update_audio() - { - IntPtr src = IntPtr.Zero; - LibGPGX.gpgx_get_audio(ref nsamp, ref src); - if (src != IntPtr.Zero) - { - Marshal.Copy(src, samples, 0, nsamp * 2); - } - } - public DisplayType Region { get; private set; } } } diff --git a/BizHawk.Emulation.Cores/Consoles/Sega/gpgx64/GPGX.IEmulator.cs b/BizHawk.Emulation.Cores/Consoles/Sega/gpgx64/GPGX.IEmulator.cs index e6b745f7e9..5b8ee3323f 100644 --- a/BizHawk.Emulation.Cores/Consoles/Sega/gpgx64/GPGX.IEmulator.cs +++ b/BizHawk.Emulation.Cores/Consoles/Sega/gpgx64/GPGX.IEmulator.cs @@ -3,24 +3,10 @@ using BizHawk.Emulation.Common; namespace BizHawk.Emulation.Cores.Consoles.Sega.gpgx64 { - public partial class GPGX : IEmulator + public partial class GPGX : IEmulator, ISoundProvider { public IEmulatorServiceProvider ServiceProvider { get; private set; } - public IAsyncSoundProvider SoundProvider { get { return null; } } - - public ISyncSoundProvider SyncSoundProvider - { - get { return this; } - } - - public bool StartAsyncSound() - { - return false; - } - - public void EndAsyncSound() { } - public ControllerDefinition ControllerDefinition { get; private set; } public IController Controller { get; set; } diff --git a/BizHawk.Emulation.Cores/Consoles/Sega/gpgx64/GPGX.ISoundProvider.cs b/BizHawk.Emulation.Cores/Consoles/Sega/gpgx64/GPGX.ISoundProvider.cs new file mode 100644 index 0000000000..f979cd11bf --- /dev/null +++ b/BizHawk.Emulation.Cores/Consoles/Sega/gpgx64/GPGX.ISoundProvider.cs @@ -0,0 +1,57 @@ +using System; +using BizHawk.Emulation.Common; +using System.Runtime.InteropServices; + +namespace BizHawk.Emulation.Cores.Consoles.Sega.gpgx64 +{ + public partial class GPGX : ISoundProvider + { + private short[] samples = new short[4096]; + private int nsamp = 0; + + public bool CanProvideAsync + { + get { return false; } + } + + public void GetSamplesSync(out short[] samples, out int nsamp) + { + nsamp = this.nsamp; + samples = this.samples; + this.nsamp = 0; + } + + public void DiscardSamples() + { + this.nsamp = 0; + } + + public void SetSyncMode(SyncSoundMode mode) + { + if (mode == SyncSoundMode.Async) + { + throw new NotSupportedException("Async mode is not supported."); + } + + } + public SyncSoundMode SyncMode + { + get { return SyncSoundMode.Sync; } + } + + public void GetSamplesAsync(short[] samples) + { + throw new InvalidOperationException("Async mode is not supported."); + } + + private void update_audio() + { + IntPtr src = IntPtr.Zero; + Core.gpgx_get_audio(ref nsamp, ref src); + if (src != IntPtr.Zero) + { + Marshal.Copy(src, samples, 0, nsamp * 2); + } + } + } +} diff --git a/BizHawk.Emulation.Cores/Consoles/Sega/gpgx64/GPGX.cs b/BizHawk.Emulation.Cores/Consoles/Sega/gpgx64/GPGX.cs index 9cbb7ac2f2..647f335587 100644 --- a/BizHawk.Emulation.Cores/Consoles/Sega/gpgx64/GPGX.cs +++ b/BizHawk.Emulation.Cores/Consoles/Sega/gpgx64/GPGX.cs @@ -17,7 +17,7 @@ namespace BizHawk.Emulation.Cores.Consoles.Sega.gpgx64 portedUrl: "https://code.google.com/p/genplus-gx/", singleInstance: false )] - public partial class GPGX : IEmulator, ISyncSoundProvider, IVideoProvider, ISaveRam, IStatable, IRegionable, + public partial class GPGX : IEmulator, IVideoProvider, ISaveRam, IStatable, IRegionable, IInputPollable, IDebuggable, IDriveLight, ICodeDataLogger, IDisassemblable { LibGPGX Core; @@ -374,31 +374,6 @@ namespace BizHawk.Emulation.Cores.Consoles.Sega.gpgx64 Core.gpgx_flush_vram(); // fully regenerate internal caches as needed } - short[] samples = new short[4096]; - int nsamp = 0; - - public void GetSamples(out short[] samples, out int nsamp) - { - nsamp = this.nsamp; - samples = this.samples; - this.nsamp = 0; - } - - public void DiscardSamples() - { - this.nsamp = 0; - } - - void update_audio() - { - IntPtr src = IntPtr.Zero; - Core.gpgx_get_audio(ref nsamp, ref src); - if (src != IntPtr.Zero) - { - Marshal.Copy(src, samples, 0, nsamp * 2); - } - } - public DisplayType Region { get; private set; } } } diff --git a/BizHawk.Emulation.Cores/Consoles/Sony/PSP/PSP.cs b/BizHawk.Emulation.Cores/Consoles/Sony/PSP/PSP.cs index d5c24b5474..60b7283c15 100644 --- a/BizHawk.Emulation.Cores/Consoles/Sony/PSP/PSP.cs +++ b/BizHawk.Emulation.Cores/Consoles/Sony/PSP/PSP.cs @@ -1,8 +1,5 @@ using System; using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Runtime.InteropServices; using BizHawk.Emulation.Common; @@ -15,7 +12,7 @@ namespace BizHawk.Emulation.Cores.Sony.PSP isReleased: false, singleInstance: true )] - public class PSP : IEmulator, IVideoProvider, ISyncSoundProvider + public class PSP : IEmulator, IVideoProvider, ISoundProvider { public static readonly ControllerDefinition PSPController = new ControllerDefinition { @@ -41,10 +38,6 @@ namespace BizHawk.Emulation.Cores.Sony.PSP } }; - public IAsyncSoundProvider SoundProvider { get { return null; } } - public ISyncSoundProvider SyncSoundProvider { get { return this; } } - public bool StartAsyncSound() { return false; } - public void EndAsyncSound() { } public ControllerDefinition ControllerDefinition { get { return PSPController; } } public IController Controller { get; set; } public bool DeterministicEmulation { get { return true; } } @@ -186,7 +179,7 @@ namespace BizHawk.Emulation.Cores.Sony.PSP readonly short[] audiobuffer = new short[2048 * 2]; int nsampavail = 0; - public void GetSamples(out short[] samples, out int nsamp) + public void GetSamplesSync(out short[] samples, out int nsamp) { samples = audiobuffer; nsamp = nsampavail; @@ -195,5 +188,28 @@ namespace BizHawk.Emulation.Cores.Sony.PSP public void DiscardSamples() { } + + public bool CanProvideAsync + { + get { return false; } + } + + public void SetSyncMode(SyncSoundMode mode) + { + if (mode == SyncSoundMode.Async) + { + throw new NotSupportedException("Async mode is not supported."); + } + } + + public SyncSoundMode SyncMode + { + get { return SyncSoundMode.Sync; } + } + + public void GetSamplesAsync(short[] samples) + { + throw new InvalidOperationException("Async mode is not supported."); + } } } diff --git a/BizHawk.Emulation.Cores/Consoles/Sony/PSX/Octoshock.cs b/BizHawk.Emulation.Cores/Consoles/Sony/PSX/Octoshock.cs index 9b6096fdec..b6fe7299e3 100644 --- a/BizHawk.Emulation.Cores/Consoles/Sony/PSX/Octoshock.cs +++ b/BizHawk.Emulation.Cores/Consoles/Sony/PSX/Octoshock.cs @@ -31,7 +31,7 @@ namespace BizHawk.Emulation.Cores.Sony.PSX isPorted: true, isReleased: true )] - public unsafe partial class Octoshock : IEmulator, IVideoProvider, ISyncSoundProvider, ISaveRam, IStatable, IDriveLight, ISettable, IRegionable, IInputPollable + public unsafe partial class Octoshock : IEmulator, IVideoProvider, ISoundProvider, ISaveRam, IStatable, IDriveLight, ISettable, IRegionable, IInputPollable { public string SystemId { get { return "PSX"; } } @@ -861,12 +861,7 @@ namespace BizHawk.Emulation.Cores.Sony.PSX private short[] sbuff = new short[1611 * 2]; //need this for pal private int sbuffcontains = 0; - public IAsyncSoundProvider SoundProvider { get { throw new InvalidOperationException(); } } - public ISyncSoundProvider SyncSoundProvider { get { return this; } } - public bool StartAsyncSound() { return false; } - public void EndAsyncSound() { } - - public void GetSamples(out short[] samples, out int nsamp) + public void GetSamplesSync(out short[] samples, out int nsamp) { samples = sbuff; nsamp = sbuffcontains; @@ -877,6 +872,29 @@ namespace BizHawk.Emulation.Cores.Sony.PSX sbuffcontains = 0; } + public bool CanProvideAsync + { + get { return false; } + } + + public void SetSyncMode(SyncSoundMode mode) + { + if (mode == SyncSoundMode.Async) + { + throw new NotSupportedException("Async mode is not supported."); + } + } + + public SyncSoundMode SyncMode + { + get { return SyncSoundMode.Sync; } + } + + public void GetSamplesAsync(short[] samples) + { + throw new InvalidOperationException("Async mode is not supported."); + } + #endregion #region ISaveRam diff --git a/BizHawk.Emulation.Cores/Consoles/WonderSwan/WonderSwan.ISoundProvider.cs b/BizHawk.Emulation.Cores/Consoles/WonderSwan/WonderSwan.ISoundProvider.cs new file mode 100644 index 0000000000..fdefe2af5f --- /dev/null +++ b/BizHawk.Emulation.Cores/Consoles/WonderSwan/WonderSwan.ISoundProvider.cs @@ -0,0 +1,45 @@ +using System; +using BizHawk.Emulation.Common; + +namespace BizHawk.Emulation.Cores.WonderSwan +{ + public partial class WonderSwan : ISoundProvider + { + private short[] sbuff = new short[1536]; + private int sbuffcontains = 0; + + public bool CanProvideAsync + { + get { return false; } + } + + public void GetSamplesSync(out short[] samples, out int nsamp) + { + samples = sbuff; + nsamp = sbuffcontains; + } + + public void DiscardSamples() + { + sbuffcontains = 0; + } + + public void SetSyncMode(SyncSoundMode mode) + { + if (mode == SyncSoundMode.Async) + { + throw new NotSupportedException("Async mode is not supported."); + } + } + + public SyncSoundMode SyncMode + { + get { return SyncSoundMode.Sync; } + } + + public void GetSamplesAsync(short[] samples) + { + throw new InvalidOperationException("Async mode is not supported."); + } + } +} diff --git a/BizHawk.Emulation.Cores/Consoles/WonderSwan/WonderSwan.cs b/BizHawk.Emulation.Cores/Consoles/WonderSwan/WonderSwan.cs index 60a62c9c23..93a187d25e 100644 --- a/BizHawk.Emulation.Cores/Consoles/WonderSwan/WonderSwan.cs +++ b/BizHawk.Emulation.Cores/Consoles/WonderSwan/WonderSwan.cs @@ -12,7 +12,7 @@ namespace BizHawk.Emulation.Cores.WonderSwan { [CoreAttributes("Cygne/Mednafen", "Dox", true, true, "0.9.36.5", "http://mednafen.sourceforge.net/")] [ServiceNotApplicable(typeof(IDriveLight), typeof(IRegionable))] - public partial class WonderSwan : IEmulator, IVideoProvider, ISyncSoundProvider, + public partial class WonderSwan : IEmulator, IVideoProvider, ISoundProvider, IInputPollable, IDebuggable { [CoreConstructor("WSWAN")] @@ -215,28 +215,5 @@ namespace BizHawk.Emulation.Cores.WonderSwan public int BackgroundColor { get { return unchecked((int)0xff000000); } } #endregion - - #region ISoundProvider - - private short[] sbuff = new short[1536]; - private int sbuffcontains = 0; - - public IAsyncSoundProvider SoundProvider { get { throw new InvalidOperationException(); } } - public ISyncSoundProvider SyncSoundProvider { get { return this; } } - public bool StartAsyncSound() { return false; } - public void EndAsyncSound() { } - - public void GetSamples(out short[] samples, out int nsamp) - { - samples = sbuff; - nsamp = sbuffcontains; - } - - public void DiscardSamples() - { - sbuffcontains = 0; - } - - #endregion } } diff --git a/BizHawk.Emulation.Cores/Libretro/LibRetroEmulator.cs b/BizHawk.Emulation.Cores/Libretro/LibRetroEmulator.cs index 4068e1dd08..a6c4f2a5aa 100644 --- a/BizHawk.Emulation.Cores/Libretro/LibRetroEmulator.cs +++ b/BizHawk.Emulation.Cores/Libretro/LibRetroEmulator.cs @@ -496,6 +496,7 @@ namespace BizHawk.Emulation.Cores CoreComm.VsyncDen = 10000000; SetupResampler(av.timing.fps, av.timing.sample_rate); + (ServiceProvider as BasicServiceProvider).Register(resampler); ControllerDefinition = CreateControllerDefinition(_SyncSettings); @@ -737,11 +738,6 @@ namespace BizHawk.Emulation.Cores #region ISoundProvider - public IAsyncSoundProvider SoundProvider { get { return null; } } - public ISyncSoundProvider SyncSoundProvider { get { return resampler; } } - public bool StartAsyncSound() { return false; } - public void EndAsyncSound() { } - SpeexResampler resampler; short[] sampbuff = new short[0]; diff --git a/BizHawk.Emulation.Cores/Sound/HuC6280PSG.cs b/BizHawk.Emulation.Cores/Sound/HuC6280PSG.cs index 91587cbf20..0aa5cac72f 100644 --- a/BizHawk.Emulation.Cores/Sound/HuC6280PSG.cs +++ b/BizHawk.Emulation.Cores/Sound/HuC6280PSG.cs @@ -10,6 +10,7 @@ namespace BizHawk.Emulation.Cores.Components // Emulates PSG audio unit of a PC Engine / Turbografx-16 / SuperGrafx. // It is embedded on the CPU and doesn't have its own part number. None the less, it is emulated separately from the 6280 CPU. + // Sound refactor TODO: IMixedSoundProvider must inherit ISoundProvider public sealed class HuC6280PSG : IMixedSoundProvider { public class PSGChannel @@ -144,6 +145,7 @@ namespace BizHawk.Emulation.Cores.Components } public void DiscardSamples() { } + public void GetSamples(short[] samples) { int elapsedCycles = (int)(frameStopTime - frameStartTime);