GambatteLink cleanup

This commit is contained in:
adelikat 2017-04-25 11:06:50 -05:00
parent 2426cfa31a
commit ba28ca53a8
10 changed files with 379 additions and 333 deletions

View File

@ -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>

View File

@ -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" />

View File

@ -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++)

View File

@ -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;
}

View File

@ -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);
}

View File

@ -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 ) );
}

View File

@ -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;
}
}
}
}

View File

@ -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()

View File

@ -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
}
}

View File

@ -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;
}
}
}