From ba28ca53a8402b2d810d2b3f733ef9b28246b240 Mon Sep 17 00:00:00 2001 From: adelikat Date: Tue, 25 Apr 2017 11:06:50 -0500 Subject: [PATCH] GambatteLink cleanup --- .../Interfaces/Services/ISoundProvider.cs | 11 +- .../BizHawk.Emulation.Cores.csproj | 8 +- .../Consoles/Nintendo/Gameboy/GBColors.cs | 7 +- .../Nintendo/Gameboy/GBDisassembler.cs | 14 +- .../Nintendo/Gameboy/Gambatte.IEmulator.cs | 6 +- .../Consoles/Nintendo/Gameboy/Gambatte.cs | 8 +- .../Gameboy/GambatteLink.IEmulator.cs | 192 +++++++++++ .../Gameboy/GambatteLink.IStatable.cs | 48 +-- .../Consoles/Nintendo/Gameboy/GambatteLink.cs | 308 ++---------------- .../Gameboy/GamebatteLink.ISoundProvider.cs | 110 +++++++ 10 files changed, 379 insertions(+), 333 deletions(-) create mode 100644 BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/GambatteLink.IEmulator.cs create mode 100644 BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/GamebatteLink.ISoundProvider.cs diff --git a/BizHawk.Emulation.Common/Interfaces/Services/ISoundProvider.cs b/BizHawk.Emulation.Common/Interfaces/Services/ISoundProvider.cs index 0cae9fb3ee..08fe412400 100644 --- a/BizHawk.Emulation.Common/Interfaces/Services/ISoundProvider.cs +++ b/BizHawk.Emulation.Common/Interfaces/Services/ISoundProvider.cs @@ -4,14 +4,14 @@ /// /// This service provides the ability to output sound from the client, - /// If available the client will provide sound ouput + /// If available the client will provide sound output /// If unavailable the client will fallback to a default sound implementation /// that generates empty samples (silence) /// public interface ISoundProvider : IEmulatorService { /// - /// Returns true if a core can provide Async sound + /// Gets a value indicating whether a core can provide Async sound /// bool CanProvideAsync { get; } @@ -25,21 +25,20 @@ void SetSyncMode(SyncSoundMode mode); /// - /// Reports which mode the sound provider is currently in + /// Gets which mode the sound provider is currently in /// SyncSoundMode SyncMode { get; } /// - /// Provides samples in syncmode + /// Provides samples in sync mode /// 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 + /// If the core is not in async mode, this should throw an InvalidOperationException /// - /// void GetSamplesAsync(short[] samples); /// diff --git a/BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj b/BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj index 7904687d90..9515bc1629 100644 --- a/BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj +++ b/BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj @@ -448,7 +448,7 @@ Gambatte.cs - Gambatte.cs + Gambatte.cs Gambatte.cs @@ -478,6 +478,9 @@ GambatteLink.cs + + GambatteLink.cs + GambatteLink.cs @@ -496,6 +499,9 @@ GambatteLink.cs + + GambatteLink.cs + diff --git a/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/GBColors.cs b/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/GBColors.cs index 28bd968dd1..7e20de9573 100644 --- a/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/GBColors.cs +++ b/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/GBColors.cs @@ -64,10 +64,11 @@ namespace BizHawk.Emulation.Cores.Nintendo.Gameboy } // "gameboy colors" mode on older versions of VBA - static int gbGetValue(int min, int max, int v) + private static int gbGetValue(int min, int max, int v) { return (int)(min + (float)(max - min) * (2.0 * (v / 31.0) - (v / 31.0) * (v / 31.0))); } + public static Triple OldVBAColor(Triple c) { Triple ret; @@ -102,10 +103,11 @@ namespace BizHawk.Emulation.Cores.Nintendo.Gameboy // possibly represents a GBA screen, more or less // but probably not (black point?) - static int GBAGamma(int input) + private static int GBAGamma(int input) { return (int)Math.Round(Math.Pow(input / 31.0, 3.5 / 2.2) * 255.0); } + public static Triple GBAColor(Triple c) { Triple ret; @@ -144,6 +146,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.Gameboy case ColorType.vbabgbold: f = OldVBAColor; break; case ColorType.gba: f = GBAColor; break; } + int i = 0; for (int b = 0; b < 32; b++) for (int g = 0; g < 32; g++) diff --git a/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/GBDisassembler.cs b/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/GBDisassembler.cs index 49b70e768c..2f2fdf81c5 100644 --- a/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/GBDisassembler.cs +++ b/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/GBDisassembler.cs @@ -1,8 +1,7 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; +using System.Collections.Generic; + using BizHawk.Emulation.Common; +using BizHawk.Emulation.Common.Components.Z80GB; namespace BizHawk.Emulation.Cores.Nintendo.Gameboy { @@ -13,15 +12,12 @@ namespace BizHawk.Emulation.Cores.Nintendo.Gameboy get { yield return "Z80GB"; } } - public override string PCRegisterName - { - get { return "PC"; } - } + public override string PCRegisterName => "PC"; public override string Disassemble(MemoryDomain m, uint addr, out int length) { ushort tmp; - string ret = Common.Components.Z80GB.NewDisassembler.Disassemble((ushort)addr, (a) => m.PeekByte(a), out tmp); + string ret = NewDisassembler.Disassemble((ushort)addr, a => m.PeekByte(a), out tmp); length = tmp; return ret; } diff --git a/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/Gambatte.IEmulator.cs b/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/Gambatte.IEmulator.cs index 2854a82acf..25ea29a6c9 100644 --- a/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/Gambatte.IEmulator.cs +++ b/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/Gambatte.IEmulator.cs @@ -22,8 +22,8 @@ namespace BizHawk.Emulation.Cores.Nintendo.Gameboy { // target number of samples to emit: length of 1 frame minus whatever overflow uint samplesEmitted = TICKSINFRAME - frameOverflow; - Debug.Assert(samplesEmitted * 2 <= soundbuff.Length); - if (LibGambatte.gambatte_runfor(GambatteState, soundbuff, ref samplesEmitted) > 0) + Debug.Assert(samplesEmitted * 2 <= _soundbuff.Length); + if (LibGambatte.gambatte_runfor(GambatteState, _soundbuff, ref samplesEmitted) > 0) { LibGambatte.gambatte_blitto(GambatteState, VideoBuffer, 160); } @@ -50,7 +50,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.Gameboy // runfor() always ends after creating a video frame, so sync-up is guaranteed // when the display has been off, some frames can be markedly shorter than expected uint samplesEmitted = TICKSINFRAME; - if (LibGambatte.gambatte_runfor(GambatteState, soundbuff, ref samplesEmitted) > 0) + if (LibGambatte.gambatte_runfor(GambatteState, _soundbuff, ref samplesEmitted) > 0) { LibGambatte.gambatte_blitto(GambatteState, VideoBuffer, 160); } diff --git a/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/Gambatte.cs b/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/Gambatte.cs index a3db6902ad..46faa1b150 100644 --- a/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/Gambatte.cs +++ b/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/Gambatte.cs @@ -179,7 +179,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.Gameboy TimeCallback = new LibGambatte.RTCCallback(GetCurrentTime); LibGambatte.gambatte_setrtccallback(GambatteState, TimeCallback); - CDCallback = new LibGambatte.CDCallback(CDCallbackProc); + _cdCallback = new LibGambatte.CDCallback(CDCallbackProc); NewSaveCoreSetBuff(); } @@ -291,14 +291,14 @@ namespace BizHawk.Emulation.Cores.Nintendo.Gameboy if (Tracer.Enabled) { - tracecb = MakeTrace; + _tracecb = MakeTrace; } else { - tracecb = null; + _tracecb = null; } - LibGambatte.gambatte_settracecallback(GambatteState, tracecb); + LibGambatte.gambatte_settracecallback(GambatteState, _tracecb); LibGambatte.gambatte_setlayers(GambatteState, (_settings.DisplayBG ? 1 : 0) | (_settings.DisplayOBJ ? 2 : 0) | (_settings.DisplayWindow ? 4 : 0 ) ); } diff --git a/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/GambatteLink.IEmulator.cs b/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/GambatteLink.IEmulator.cs new file mode 100644 index 0000000000..817505e0ae --- /dev/null +++ b/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/GambatteLink.IEmulator.cs @@ -0,0 +1,192 @@ +using System; +using BizHawk.Emulation.Common; + +namespace BizHawk.Emulation.Cores.Nintendo.Gameboy +{ + public partial class GambatteLink : IEmulator + { + public IEmulatorServiceProvider ServiceProvider { get; } + + public ControllerDefinition ControllerDefinition => DualGbController; + + public IController Controller { get; set; } + + public void FrameAdvance(bool render, bool rendersound = true) + { + LCont.Clear(); + RCont.Clear(); + + foreach (var s in DualGbController.BoolButtons) + { + if (Controller.IsPressed(s)) + { + if (s.Contains("P1 ")) + { + LCont.Set(s.Replace("P1 ", string.Empty)); + } + else if (s.Contains("P2 ")) + { + RCont.Set(s.Replace("P2 ", string.Empty)); + } + } + } + + bool cablediscosignalNew = Controller.IsPressed("Toggle Cable"); + if (cablediscosignalNew && !_cablediscosignal) + { + _cableconnected ^= true; + Console.WriteLine("Cable connect status to {0}", _cableconnected); + LinkConnected = _cableconnected; + } + + _cablediscosignal = cablediscosignalNew; + + Frame++; + L.FrameAdvancePrep(); + R.FrameAdvancePrep(); + + unsafe + { + fixed (int* leftvbuff = &VideoBuffer[0]) + { + // use pitch to have both cores write to the same video buffer, interleaved + int* rightvbuff = leftvbuff + 160; + const int Pitch = 160 * 2; + + fixed (short* leftsbuff = LeftBuffer, rightsbuff = RightBuffer) + { + const int Step = 32; // could be 1024 for GB + + int nL = _overflowL; + int nR = _overflowR; + + // slowly step our way through the frame, while continually checking and resolving link cable status + for (int target = 0; target < SampPerFrame;) + { + target += Step; + if (target > SampPerFrame) + { + target = SampPerFrame; // don't run for slightly too long depending on step + } + + // gambatte_runfor() aborts early when a frame is produced, but we don't want that, hence the while() + while (nL < target) + { + uint nsamp = (uint)(target - nL); + if (LibGambatte.gambatte_runfor(L.GambatteState, leftsbuff + (nL * 2), ref nsamp) > 0) + { + LibGambatte.gambatte_blitto(L.GambatteState, leftvbuff, Pitch); + } + + nL += (int)nsamp; + } + + while (nR < target) + { + uint nsamp = (uint)(target - nR); + if (LibGambatte.gambatte_runfor(R.GambatteState, rightsbuff + (nR * 2), ref nsamp) > 0) + { + LibGambatte.gambatte_blitto(R.GambatteState, rightvbuff, Pitch); + } + + nR += (int)nsamp; + } + + // poll link cable statuses, but not when the cable is disconnected + if (!_cableconnected) + { + continue; + } + + if (LibGambatte.gambatte_linkstatus(L.GambatteState, 256) != 0) // ClockTrigger + { + LibGambatte.gambatte_linkstatus(L.GambatteState, 257); // ack + int lo = LibGambatte.gambatte_linkstatus(L.GambatteState, 258); // GetOut + int ro = LibGambatte.gambatte_linkstatus(R.GambatteState, 258); + LibGambatte.gambatte_linkstatus(L.GambatteState, ro & 0xff); // ShiftIn + LibGambatte.gambatte_linkstatus(R.GambatteState, lo & 0xff); // ShiftIn + } + + if (LibGambatte.gambatte_linkstatus(R.GambatteState, 256) != 0) // ClockTrigger + { + LibGambatte.gambatte_linkstatus(R.GambatteState, 257); // ack + int lo = LibGambatte.gambatte_linkstatus(L.GambatteState, 258); // GetOut + int ro = LibGambatte.gambatte_linkstatus(R.GambatteState, 258); + LibGambatte.gambatte_linkstatus(L.GambatteState, ro & 0xff); // ShiftIn + LibGambatte.gambatte_linkstatus(R.GambatteState, lo & 0xff); // ShiftIn + } + } + + _overflowL = nL - SampPerFrame; + _overflowR = nR - SampPerFrame; + if (_overflowL < 0 || _overflowR < 0) + { + throw new Exception("Timing problem?"); + } + + if (rendersound) + { + PrepSound(); + } + + // copy extra samples back to beginning + for (int i = 0; i < _overflowL * 2; i++) + { + LeftBuffer[i] = LeftBuffer[i + (SampPerFrame * 2)]; + } + + for (int i = 0; i < _overflowR * 2; i++) + { + RightBuffer[i] = RightBuffer[i + (SampPerFrame * 2)]; + } + } + } + } + + L.FrameAdvancePost(); + R.FrameAdvancePost(); + IsLagFrame = L.IsLagFrame && R.IsLagFrame; + if (IsLagFrame) + { + LagCount++; + } + } + + public int Frame { get; private set; } + + public string SystemId => "DGB"; + + public bool DeterministicEmulation => L.DeterministicEmulation && R.DeterministicEmulation; + + public string BoardName => L.BoardName + '|' + R.BoardName; + + public void ResetCounters() + { + Frame = 0; + LagCount = 0; + IsLagFrame = false; + } + + public CoreComm CoreComm { get; } + + public void Dispose() + { + if (!_disposed) + { + L.Dispose(); + L = null; + + R.Dispose(); + R = null; + + _blipLeft.Dispose(); + _blipLeft = null; + + _blipRight.Dispose(); + _blipRight = null; + + _disposed = true; + } + } + } +} diff --git a/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/GambatteLink.IStatable.cs b/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/GambatteLink.IStatable.cs index b84396a207..44d477940f 100644 --- a/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/GambatteLink.IStatable.cs +++ b/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/GambatteLink.IStatable.cs @@ -19,12 +19,12 @@ namespace BizHawk.Emulation.Cores.Nintendo.Gameboy IsLagFrame = IsLagFrame, LagCount = LagCount, Frame = Frame, - overflowL = overflowL, - overflowR = overflowR, - LatchL = LatchL, - LatchR = LatchR, - cableconnected = cableconnected, - cablediscosignal = cablediscosignal + overflowL = _overflowL, + overflowR = _overflowR, + LatchL = _latchLeft, + LatchR = _latchRight, + cableconnected = _cableconnected, + cablediscosignal = _cablediscosignal }; ser.Serialize(writer, s); // write extra copy of stuff we don't use @@ -41,12 +41,12 @@ namespace BizHawk.Emulation.Cores.Nintendo.Gameboy IsLagFrame = s.IsLagFrame; LagCount = s.LagCount; Frame = s.Frame; - overflowL = s.overflowL; - overflowR = s.overflowR; - LatchL = s.LatchL; - LatchR = s.LatchR; - cableconnected = s.cableconnected; - cablediscosignal = s.cablediscosignal; + _overflowL = s.overflowL; + _overflowR = s.overflowR; + _latchLeft = s.LatchL; + _latchRight = s.LatchR; + _cableconnected = s.cableconnected; + _cablediscosignal = s.cablediscosignal; } public void SaveStateBinary(BinaryWriter writer) @@ -57,12 +57,12 @@ namespace BizHawk.Emulation.Cores.Nintendo.Gameboy writer.Write(IsLagFrame); writer.Write(LagCount); writer.Write(Frame); - writer.Write(overflowL); - writer.Write(overflowR); - writer.Write(LatchL); - writer.Write(LatchR); - writer.Write(cableconnected); - writer.Write(cablediscosignal); + writer.Write(_overflowL); + writer.Write(_overflowR); + writer.Write(_latchLeft); + writer.Write(_latchRight); + writer.Write(_cableconnected); + writer.Write(_cablediscosignal); } public void LoadStateBinary(BinaryReader reader) @@ -73,12 +73,12 @@ namespace BizHawk.Emulation.Cores.Nintendo.Gameboy IsLagFrame = reader.ReadBoolean(); LagCount = reader.ReadInt32(); Frame = reader.ReadInt32(); - overflowL = reader.ReadInt32(); - overflowR = reader.ReadInt32(); - LatchL = reader.ReadInt32(); - LatchR = reader.ReadInt32(); - cableconnected = reader.ReadBoolean(); - cablediscosignal = reader.ReadBoolean(); + _overflowL = reader.ReadInt32(); + _overflowR = reader.ReadInt32(); + _latchLeft = reader.ReadInt32(); + _latchRight = reader.ReadInt32(); + _cableconnected = reader.ReadBoolean(); + _cablediscosignal = reader.ReadBoolean(); } public byte[] SaveStateBinary() diff --git a/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/GambatteLink.cs b/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/GambatteLink.cs index 06bfcfe30b..6c585cd6a4 100644 --- a/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/GambatteLink.cs +++ b/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/GambatteLink.cs @@ -1,7 +1,5 @@ using System; - using BizHawk.Emulation.Common; -using BizHawk.Emulation.Cores.Nintendo.SNES; namespace BizHawk.Emulation.Cores.Nintendo.Gameboy { @@ -9,8 +7,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.Gameboy "DualGambatte", "sinamas/natt", isPorted: true, - isReleased: true - )] + isReleased: true)] [ServiceNotApplicable(typeof(IDriveLight))] public partial class GambatteLink : IEmulator, IVideoProvider, ISoundProvider, IInputPollable, ISaveRam, IStatable, ILinkable, IDebuggable, ISettable, ICodeDataLogger @@ -18,12 +15,12 @@ namespace BizHawk.Emulation.Cores.Nintendo.Gameboy public GambatteLink(CoreComm comm, GameInfo leftinfo, byte[] leftrom, GameInfo rightinfo, byte[] rightrom, object Settings, object SyncSettings, bool deterministic) { ServiceProvider = new BasicServiceProvider(this); - GambatteLinkSettings _Settings = (GambatteLinkSettings)Settings ?? new GambatteLinkSettings(); - GambatteLinkSyncSettings _SyncSettings = (GambatteLinkSyncSettings)SyncSettings ?? new GambatteLinkSyncSettings(); + GambatteLinkSettings settings = (GambatteLinkSettings)Settings ?? new GambatteLinkSettings(); + GambatteLinkSyncSettings syncSettings = (GambatteLinkSyncSettings)SyncSettings ?? new GambatteLinkSyncSettings(); CoreComm = comm; - L = new Gameboy(new CoreComm(comm.ShowMessage, comm.Notify), leftinfo, leftrom, _Settings.L, _SyncSettings.L, deterministic); - R = new Gameboy(new CoreComm(comm.ShowMessage, comm.Notify), rightinfo, rightrom, _Settings.R, _SyncSettings.R, deterministic); + L = new Gameboy(new CoreComm(comm.ShowMessage, comm.Notify), leftinfo, leftrom, settings.L, syncSettings.L, deterministic); + R = new Gameboy(new CoreComm(comm.ShowMessage, comm.Notify), rightinfo, rightrom, settings.R, syncSettings.R, deterministic); // connect link cable LibGambatte.gambatte_linkstatus(L.GambatteState, 259); @@ -49,41 +46,42 @@ namespace BizHawk.Emulation.Cores.Nintendo.Gameboy LagCount = 0; IsLagFrame = false; - blip_left = new BlipBuffer(1024); - blip_right = new BlipBuffer(1024); - blip_left.SetRates(2097152 * 2, 44100); - blip_right.SetRates(2097152 * 2, 44100); + _blipLeft = new BlipBuffer(1024); + _blipRight = new BlipBuffer(1024); + _blipLeft.SetRates(2097152 * 2, 44100); + _blipRight.SetRates(2097152 * 2, 44100); SetMemoryDomains(); } public bool LinkConnected { get; private set; } - bool disposed = false; + private bool _disposed = false; + + private Gameboy L; + private Gameboy R; - Gameboy L; - Gameboy R; // counter to ensure we do 35112 samples per frame - int overflowL = 0; - int overflowR = 0; - /// if true, the link cable is currently connected - bool cableconnected = true; - /// if true, the link cable toggle signal is currently asserted - bool cablediscosignal = false; + private int _overflowL = 0; + private int _overflowR = 0; + + // if true, the link cable is currently connected + private bool _cableconnected = true; + + // if true, the link cable toggle signal is currently asserted + private bool _cablediscosignal = false; const int SampPerFrame = 35112; - SaveController LCont = new SaveController(Gameboy.GbController); - SaveController RCont = new SaveController(Gameboy.GbController); + private readonly SaveController LCont = new SaveController(Gameboy.GbController); + private readonly SaveController RCont = new SaveController(Gameboy.GbController); public bool IsCGBMode(bool right) { return right ? R.IsCGBMode() : L.IsCGBMode(); } - public IEmulatorServiceProvider ServiceProvider { get; private set; } - - public static readonly ControllerDefinition DualGbController = new ControllerDefinition + private static readonly ControllerDefinition DualGbController = new ControllerDefinition { Name = "Dual Gameboy Controller", BoolButtons = @@ -93,263 +91,5 @@ namespace BizHawk.Emulation.Cores.Nintendo.Gameboy "Toggle Cable" } }; - - public ControllerDefinition ControllerDefinition { get { return DualGbController; } } - public IController Controller { get; set; } - - public void FrameAdvance(bool render, bool rendersound = true) - { - LCont.Clear(); - RCont.Clear(); - - foreach (var s in DualGbController.BoolButtons) - { - if (Controller.IsPressed(s)) - { - if (s.Contains("P1 ")) - LCont.Set(s.Replace("P1 ", "")); - else if (s.Contains("P2 ")) - RCont.Set(s.Replace("P2 ", "")); - } - } - bool cablediscosignal_new = Controller.IsPressed("Toggle Cable"); - if (cablediscosignal_new && !cablediscosignal) - { - cableconnected ^= true; - Console.WriteLine("Cable connect status to {0}", cableconnected); - LinkConnected = cableconnected; - } - cablediscosignal = cablediscosignal_new; - - Frame++; - L.FrameAdvancePrep(); - R.FrameAdvancePrep(); - - unsafe - { - fixed (int* leftvbuff = &VideoBuffer[0]) - { - // use pitch to have both cores write to the same video buffer, interleaved - int* rightvbuff = leftvbuff + 160; - const int pitch = 160 * 2; - - fixed (short* leftsbuff = LeftBuffer, rightsbuff = RightBuffer) - { - - const int step = 32; // could be 1024 for GB - - int nL = overflowL; - int nR = overflowR; - - // slowly step our way through the frame, while continually checking and resolving link cable status - for (int target = 0; target < SampPerFrame;) - { - target += step; - if (target > SampPerFrame) - target = SampPerFrame; // don't run for slightly too long depending on step - - // gambatte_runfor() aborts early when a frame is produced, but we don't want that, hence the while() - while (nL < target) - { - uint nsamp = (uint)(target - nL); - if (LibGambatte.gambatte_runfor(L.GambatteState, leftsbuff + nL * 2, ref nsamp) > 0) - LibGambatte.gambatte_blitto(L.GambatteState, leftvbuff, pitch); - nL += (int)nsamp; - } - while (nR < target) - { - uint nsamp = (uint)(target - nR); - if (LibGambatte.gambatte_runfor(R.GambatteState, rightsbuff + nR * 2, ref nsamp) > 0) - LibGambatte.gambatte_blitto(R.GambatteState, rightvbuff, pitch); - nR += (int)nsamp; - } - - // poll link cable statuses, but not when the cable is disconnected - if (!cableconnected) - continue; - - if (LibGambatte.gambatte_linkstatus(L.GambatteState, 256) != 0) // ClockTrigger - { - LibGambatte.gambatte_linkstatus(L.GambatteState, 257); // ack - int lo = LibGambatte.gambatte_linkstatus(L.GambatteState, 258); // GetOut - int ro = LibGambatte.gambatte_linkstatus(R.GambatteState, 258); - LibGambatte.gambatte_linkstatus(L.GambatteState, ro & 0xff); // ShiftIn - LibGambatte.gambatte_linkstatus(R.GambatteState, lo & 0xff); // ShiftIn - } - if (LibGambatte.gambatte_linkstatus(R.GambatteState, 256) != 0) // ClockTrigger - { - LibGambatte.gambatte_linkstatus(R.GambatteState, 257); // ack - int lo = LibGambatte.gambatte_linkstatus(L.GambatteState, 258); // GetOut - int ro = LibGambatte.gambatte_linkstatus(R.GambatteState, 258); - LibGambatte.gambatte_linkstatus(L.GambatteState, ro & 0xff); // ShiftIn - LibGambatte.gambatte_linkstatus(R.GambatteState, lo & 0xff); // ShiftIn - } - } - overflowL = nL - SampPerFrame; - overflowR = nR - SampPerFrame; - if (overflowL < 0 || overflowR < 0) - throw new Exception("Timing problem?"); - - if (rendersound) - { - PrepSound(); - } - // copy extra samples back to beginning - for (int i = 0; i < overflowL * 2; i++) - LeftBuffer[i] = LeftBuffer[i + SampPerFrame * 2]; - for (int i = 0; i < overflowR * 2; i++) - RightBuffer[i] = RightBuffer[i + SampPerFrame * 2]; - - } - - } - } - - L.FrameAdvancePost(); - R.FrameAdvancePost(); - IsLagFrame = L.IsLagFrame && R.IsLagFrame; - if (IsLagFrame) - LagCount++; - } - - public int Frame { get; private set; } - - public string SystemId { get { return "DGB"; } } - public bool DeterministicEmulation { get { return L.DeterministicEmulation && R.DeterministicEmulation; } } - - public string BoardName { get { return L.BoardName + '|' + R.BoardName; } } - - public void ResetCounters() - { - Frame = 0; - LagCount = 0; - IsLagFrame = false; - } - - public CoreComm CoreComm { get; private set; } - - public void Dispose() - { - if (!disposed) - { - L.Dispose(); - L = null; - R.Dispose(); - R = null; - blip_left.Dispose(); - blip_left = null; - blip_right.Dispose(); - blip_right = null; - - disposed = true; - } - } - - #region SoundProvider - - // i tried using the left and right buffers and then mixing them together... it was kind of a mess of code, and slow - - BlipBuffer blip_left; - BlipBuffer blip_right; - - short[] LeftBuffer = new short[(35112 + 2064) * 2]; - short[] RightBuffer = new short[(35112 + 2064) * 2]; - - short[] SampleBuffer = new short[1536]; - int SampleBufferContains = 0; - - int LatchL; - int LatchR; - - unsafe void PrepSound() - { - fixed (short* sl = LeftBuffer, sr = RightBuffer) - { - for (uint i = 0; i < SampPerFrame * 2; i += 2) - { - int s = (sl[i] + sl[i + 1]) / 2; - if (s != LatchL) - { - blip_left.AddDelta(i, s - LatchL); - LatchL = s; - } - s = (sr[i] + sr[i + 1]) / 2; - if (s != LatchR) - { - blip_right.AddDelta(i, s - LatchR); - LatchR = s; - } - } - - } - - blip_left.EndFrame(SampPerFrame * 2); - blip_right.EndFrame(SampPerFrame * 2); - int count = blip_left.SamplesAvailable(); - if (count != blip_right.SamplesAvailable()) - throw new Exception("Sound problem?"); - - // calling blip.Clear() causes rounding fractions to be reset, - // and if only one channel is muted, in subsequent frames we can be off by a sample or two - // not a big deal, but we didn't account for it. so we actually complete the entire - // audio read and then stamp it out if muted. - - blip_left.ReadSamplesLeft(SampleBuffer, count); - if (L.Muted) - { - fixed (short* p = SampleBuffer) - { - for (int i = 0; i < SampleBuffer.Length; i += 2) - p[i] = 0; - } - } - - blip_right.ReadSamplesRight(SampleBuffer, count); - if (R.Muted) - { - fixed (short* p = SampleBuffer) - { - for (int i = 1; i < SampleBuffer.Length; i += 2) - p[i] = 0; - } - } - SampleBufferContains = count; - } - - public void GetSamplesSync(out short[] samples, out int nsamp) - { - nsamp = SampleBufferContains; - samples = SampleBuffer; - } - - public void DiscardSamples() - { - 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/Gameboy/GamebatteLink.ISoundProvider.cs b/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/GamebatteLink.ISoundProvider.cs new file mode 100644 index 0000000000..b958c79aff --- /dev/null +++ b/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/GamebatteLink.ISoundProvider.cs @@ -0,0 +1,110 @@ +using System; +using BizHawk.Emulation.Common; + +namespace BizHawk.Emulation.Cores.Nintendo.Gameboy +{ + public partial class GambatteLink : ISoundProvider + { + public bool CanProvideAsync => false; + + public void SetSyncMode(SyncSoundMode mode) + { + if (mode == SyncSoundMode.Async) + { + throw new NotSupportedException("Async mode is not supported."); + } + } + + public SyncSoundMode SyncMode => SyncSoundMode.Sync; + + public void GetSamplesSync(out short[] samples, out int nsamp) + { + nsamp = _sampleBufferContains; + samples = SampleBuffer; + } + + public void GetSamplesAsync(short[] samples) + { + throw new InvalidOperationException("Async mode is not supported."); + } + + public void DiscardSamples() + { + _sampleBufferContains = 0; + } + + // i tried using the left and right buffers and then mixing them together... it was kind of a mess of code, and slow + private BlipBuffer _blipLeft; + private BlipBuffer _blipRight; + + private readonly short[] LeftBuffer = new short[(35112 + 2064) * 2]; + private readonly short[] RightBuffer = new short[(35112 + 2064) * 2]; + + private readonly short[] SampleBuffer = new short[1536]; + private int _sampleBufferContains = 0; + + private int _latchLeft; + private int _latchRight; + + private unsafe void PrepSound() + { + fixed (short* sl = LeftBuffer, sr = RightBuffer) + { + for (uint i = 0; i < SampPerFrame * 2; i += 2) + { + int s = (sl[i] + sl[i + 1]) / 2; + if (s != _latchLeft) + { + _blipLeft.AddDelta(i, s - _latchLeft); + _latchLeft = s; + } + + s = (sr[i] + sr[i + 1]) / 2; + if (s != _latchRight) + { + _blipRight.AddDelta(i, s - _latchRight); + _latchRight = s; + } + } + } + + _blipLeft.EndFrame(SampPerFrame * 2); + _blipRight.EndFrame(SampPerFrame * 2); + int count = _blipLeft.SamplesAvailable(); + if (count != _blipRight.SamplesAvailable()) + { + throw new Exception("Sound problem?"); + } + + // calling blip.Clear() causes rounding fractions to be reset, + // and if only one channel is muted, in subsequent frames we can be off by a sample or two + // not a big deal, but we didn't account for it. so we actually complete the entire + // audio read and then stamp it out if muted. + _blipLeft.ReadSamplesLeft(SampleBuffer, count); + if (L.Muted) + { + fixed (short* p = SampleBuffer) + { + for (int i = 0; i < SampleBuffer.Length; i += 2) + { + p[i] = 0; + } + } + } + + _blipRight.ReadSamplesRight(SampleBuffer, count); + if (R.Muted) + { + fixed (short* p = SampleBuffer) + { + for (int i = 1; i < SampleBuffer.Length; i += 2) + { + p[i] = 0; + } + } + } + + _sampleBufferContains = count; + } + } +}