GambatteLink cleanup
This commit is contained in:
parent
2426cfa31a
commit
ba28ca53a8
BizHawk.Emulation.Common/Interfaces/Services
BizHawk.Emulation.Cores
|
@ -4,14 +4,14 @@
|
|||
|
||||
/// <summary>
|
||||
/// 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)
|
||||
/// </summary>
|
||||
public interface ISoundProvider : IEmulatorService
|
||||
{
|
||||
/// <summary>
|
||||
/// Returns true if a core can provide Async sound
|
||||
/// Gets a value indicating whether a core can provide Async sound
|
||||
/// </summary>
|
||||
bool CanProvideAsync { get; }
|
||||
|
||||
|
@ -25,21 +25,20 @@
|
|||
void SetSyncMode(SyncSoundMode mode);
|
||||
|
||||
/// <summary>
|
||||
/// Reports which mode the sound provider is currently in
|
||||
/// Gets which mode the sound provider is currently in
|
||||
/// </summary>
|
||||
SyncSoundMode SyncMode { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Provides samples in syncmode
|
||||
/// Provides samples in sync mode
|
||||
/// If the core is not in sync mode, this should throw an InvalidOperationException
|
||||
/// </summary>
|
||||
void GetSamplesSync(out short[] samples, out int nsamp);
|
||||
|
||||
/// <summary>
|
||||
/// 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
|
||||
/// </summary>
|
||||
/// <param name="samples"></param>
|
||||
void GetSamplesAsync(short[] samples);
|
||||
|
||||
/// <summary>
|
||||
|
|
|
@ -448,7 +448,7 @@
|
|||
<DependentUpon>Gambatte.cs</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="Consoles\Nintendo\Gameboy\Gambatte.IEmulator.cs">
|
||||
<DependentUpon>Gambatte.cs</DependentUpon>
|
||||
<DependentUpon>Gambatte.cs</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="Consoles\Nintendo\Gameboy\Gambatte.IMemoryDomains.cs">
|
||||
<DependentUpon>Gambatte.cs</DependentUpon>
|
||||
|
@ -478,6 +478,9 @@
|
|||
<Compile Include="Consoles\Nintendo\Gameboy\GambatteLink.IDebuggable.cs">
|
||||
<DependentUpon>GambatteLink.cs</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="Consoles\Nintendo\Gameboy\GambatteLink.IEmulator.cs">
|
||||
<DependentUpon>GambatteLink.cs</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="Consoles\Nintendo\Gameboy\GambatteLink.IInputPollable.cs">
|
||||
<DependentUpon>GambatteLink.cs</DependentUpon>
|
||||
</Compile>
|
||||
|
@ -496,6 +499,9 @@
|
|||
<Compile Include="Consoles\Nintendo\Gameboy\GambatteLink.IVideoProvider.cs">
|
||||
<DependentUpon>GambatteLink.cs</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="Consoles\Nintendo\Gameboy\GamebatteLink.ISoundProvider.cs">
|
||||
<DependentUpon>GambatteLink.cs</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="Consoles\Nintendo\Gameboy\GBColors.cs" />
|
||||
<Compile Include="Consoles\Nintendo\Gameboy\GBDisassembler.cs" />
|
||||
<Compile Include="Consoles\Nintendo\Gameboy\LibGambatte.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++)
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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 ) );
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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()
|
||||
|
|
|
@ -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<GambatteLink.GambatteLinkSettings, GambatteLink.GambatteLinkSyncSettings>, 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;
|
||||
/// <summary>if true, the link cable is currently connected</summary>
|
||||
bool cableconnected = true;
|
||||
/// <summary>if true, the link cable toggle signal is currently asserted</summary>
|
||||
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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue