2012-09-08 21:36:04 +00:00
using System ;
using System.Collections.Generic ;
2014-07-03 19:20:34 +00:00
using System.ComponentModel ;
2012-09-08 21:36:04 +00:00
using System.IO ;
2014-07-03 19:20:34 +00:00
using BizHawk.Common.BufferExtensions ;
2013-11-04 01:06:36 +00:00
using BizHawk.Emulation.Common ;
2014-07-14 16:10:45 +00:00
using BizHawk.Common ;
2014-07-03 19:20:34 +00:00
2014-05-11 20:48:19 +00:00
using Newtonsoft.Json ;
2013-10-27 22:07:40 +00:00
2013-11-13 03:32:25 +00:00
namespace BizHawk.Emulation.Cores.Nintendo.Gameboy
2012-09-08 21:36:04 +00:00
{
2012-09-08 22:01:47 +00:00
/// <summary>
/// a gameboy/gameboy color emulator wrapped around native C++ libgambatte
/// </summary>
2014-04-25 01:19:57 +00:00
[ CoreAttributes (
"Gambatte" ,
2014-06-01 01:57:22 +00:00
"" ,
2014-04-25 01:19:57 +00:00
isPorted : true ,
2014-06-01 01:57:22 +00:00
isReleased : true ,
portedVersion : "SVN 344" ,
portedUrl : "http://gambatte.sourceforge.net/"
2014-04-25 01:19:57 +00:00
) ]
sound api changes. added a new ISyncSoundProvider, which works similarly to ISoundProvider except the source (not the sink) determines the number of samples to process. Added facilities to metaspu, dcfilter, speexresampler to work with ISyncSoundProvider. Add ISyncSoundProvider to IEmulator. All IEmulators must provide sync sound, but they need not provide async sound. When async is needed and an IEmulator doesn't provide it, the frontend will wrap it in a vecna metaspu. SNES, GB changed to provide sync sound only. All other emulator cores mostly unchanged; they just provide stub fakesync alongside async, for now. For the moment, the only use of the sync sound is for realtime audio throttling, where it works and sounds quite nice. In the future, sync sound will be supported for AV dumping as well.
2012-10-11 00:44:59 +00:00
public class Gameboy : IEmulator , IVideoProvider , ISyncSoundProvider
2012-09-08 21:36:04 +00:00
{
/// <summary>
/// internal gambatte state
/// </summary>
2012-12-29 01:25:06 +00:00
internal IntPtr GambatteState = IntPtr . Zero ;
2012-09-08 21:36:04 +00:00
2012-09-09 14:17:57 +00:00
/// <summary>
/// keep a copy of the input callback delegate so it doesn't get GCed
/// </summary>
LibGambatte . InputGetter InputCallback ;
/// <summary>
/// whatever keys are currently depressed
/// </summary>
LibGambatte . Buttons CurrentButtons = 0 ;
2012-09-08 21:36:04 +00:00
2014-05-12 17:24:43 +00:00
#region RTC
2013-05-09 23:15:59 +00:00
/// <summary>
/// RTC time when emulation begins.
/// </summary>
2013-11-24 17:32:46 +00:00
uint zerotime = 0 ;
2013-05-09 23:15:59 +00:00
2014-05-12 17:24:43 +00:00
/// <summary>
/// if true, RTC will run off of real elapsed time
/// </summary>
bool real_rtc_time = false ;
2013-05-09 23:15:59 +00:00
LibGambatte . RTCCallback TimeCallback ;
2014-05-12 17:24:43 +00:00
static long GetUnixNow ( )
{
// because internally the RTC works off of relative time, we don't need to base
// this off of any particular canonical epoch.
return DateTime . UtcNow . Ticks / 10000000L - 60000000000L ;
}
2013-11-24 17:32:46 +00:00
uint GetCurrentTime ( )
2013-05-09 23:15:59 +00:00
{
2014-05-12 17:24:43 +00:00
if ( real_rtc_time )
{
return ( uint ) GetUnixNow ( ) ;
}
else
{
ulong fn = ( ulong ) Frame ;
// as we're exactly tracking cpu cycles, this can be pretty accurate
fn * = 4389 ;
fn / = 262144 ;
fn + = zerotime ;
return ( uint ) fn ;
}
}
uint GetInitialTime ( )
{
if ( real_rtc_time )
return ( uint ) GetUnixNow ( ) ;
else
// setting the initial boot time to 0 will cause our zerotime
// to function as an initial offset, which is what we want
return 0 ;
2013-05-09 23:15:59 +00:00
}
2014-05-12 17:24:43 +00:00
#endregion
public Gameboy ( CoreComm comm , GameInfo game , byte [ ] romdata , object Settings , object SyncSettings , bool deterministic )
2012-09-08 21:36:04 +00:00
{
2012-12-10 00:43:43 +00:00
CoreComm = comm ;
2012-12-10 01:33:09 +00:00
comm . VsyncNum = 262144 ;
comm . VsyncDen = 4389 ;
comm . RomStatusAnnotation = null ;
comm . RomStatusDetails = null ;
comm . CpuTraceAvailable = true ;
comm . NominalWidth = 160 ;
comm . NominalHeight = 144 ;
2012-09-24 20:20:21 +00:00
ThrowExceptionForBadRom ( romdata ) ;
2013-08-24 16:54:22 +00:00
BoardName = MapperName ( romdata ) ;
2012-09-24 20:20:21 +00:00
2014-05-12 17:24:43 +00:00
DeterministicEmulation = deterministic ;
2012-09-09 21:15:54 +00:00
GambatteState = LibGambatte . gambatte_create ( ) ;
if ( GambatteState = = IntPtr . Zero )
2014-08-01 14:56:23 +00:00
throw new InvalidOperationException ( "gambatte_create() returned null???" ) ;
2012-09-09 21:15:54 +00:00
2013-12-09 20:50:21 +00:00
try
{
2014-07-14 16:10:45 +00:00
this . _SyncSettings = ( GambatteSyncSettings ) SyncSettings ? ? new GambatteSyncSettings ( ) ;
2014-05-12 17:24:43 +00:00
// copy over non-loadflag syncsettings now; they won't take effect if changed later
2014-07-14 16:10:45 +00:00
zerotime = ( uint ) this . _SyncSettings . RTCInitialTime ;
real_rtc_time = DeterministicEmulation ? false : this . _SyncSettings . RealTimeRTC ;
2013-12-23 02:51:41 +00:00
2013-12-09 20:50:21 +00:00
LibGambatte . LoadFlags flags = 0 ;
2012-09-15 16:14:03 +00:00
2014-07-14 16:10:45 +00:00
if ( this . _SyncSettings . ForceDMG )
2013-12-09 20:50:21 +00:00
flags | = LibGambatte . LoadFlags . FORCE_DMG ;
2014-07-14 16:10:45 +00:00
if ( this . _SyncSettings . GBACGB )
2013-12-09 20:50:21 +00:00
flags | = LibGambatte . LoadFlags . GBA_CGB ;
2014-07-14 16:10:45 +00:00
if ( this . _SyncSettings . MulticartCompat )
2013-12-09 20:50:21 +00:00
flags | = LibGambatte . LoadFlags . MULTICART_COMPAT ;
2012-09-15 16:14:03 +00:00
2013-12-09 20:50:21 +00:00
if ( LibGambatte . gambatte_load ( GambatteState , romdata , ( uint ) romdata . Length , GetCurrentTime ( ) , flags ) ! = 0 )
2014-08-01 14:56:23 +00:00
throw new InvalidOperationException ( "gambatte_load() returned non-zero (is this not a gb or gbc rom?)" ) ;
2012-09-08 21:36:04 +00:00
2013-12-09 20:50:21 +00:00
// set real default colors (before anyone mucks with them at all)
2014-07-14 16:56:23 +00:00
PutSettings ( Settings ? ? new GambatteSettings ( ) ) ;
2012-11-18 17:02:55 +00:00
2013-12-09 20:50:21 +00:00
InitSound ( ) ;
2012-09-09 14:17:57 +00:00
2013-12-09 20:50:21 +00:00
Frame = 0 ;
LagCount = 0 ;
IsLagFrame = false ;
2012-09-09 14:17:57 +00:00
2013-12-09 20:50:21 +00:00
InputCallback = new LibGambatte . InputGetter ( ControllerCallback ) ;
2012-09-09 14:17:57 +00:00
2013-12-09 20:50:21 +00:00
LibGambatte . gambatte_setinputgetter ( GambatteState , InputCallback ) ;
2012-09-11 15:28:38 +00:00
2013-12-09 20:50:21 +00:00
InitMemoryDomains ( ) ;
2012-09-26 03:24:00 +00:00
2013-12-09 20:50:21 +00:00
CoreComm . RomStatusDetails = string . Format ( "{0}\r\nSHA1:{1}\r\nMD5:{2}\r\n" ,
game . Name ,
2014-07-03 19:20:34 +00:00
romdata . HashSHA1 ( ) ,
romdata . HashMD5 ( ) ) ;
2013-05-09 23:15:59 +00:00
2014-06-16 15:59:39 +00:00
{
byte [ ] buff = new byte [ 32 ] ;
LibGambatte . gambatte_romtitle ( GambatteState , buff ) ;
string romname = System . Text . Encoding . ASCII . GetString ( buff ) ;
Console . WriteLine ( "Core reported rom name: {0}" , romname ) ;
}
2013-12-09 20:50:21 +00:00
TimeCallback = new LibGambatte . RTCCallback ( GetCurrentTime ) ;
LibGambatte . gambatte_setrtccallback ( GambatteState , TimeCallback ) ;
2014-05-10 04:22:12 +00:00
NewSaveCoreSetBuff ( ) ;
2013-12-09 20:50:21 +00:00
}
catch
{
Dispose ( ) ;
throw ;
}
2012-09-08 21:36:04 +00:00
}
2012-09-09 02:06:07 +00:00
public static readonly ControllerDefinition GbController = new ControllerDefinition
2012-09-08 21:36:04 +00:00
{
Name = "Gameboy Controller" ,
BoolButtons =
{
2013-07-29 02:11:00 +00:00
"Up" , "Down" , "Left" , "Right" , "Start" , "Select" , "B" , "A" , "Power"
2012-09-08 21:36:04 +00:00
}
} ;
public ControllerDefinition ControllerDefinition
{
get { return GbController ; }
}
public IController Controller { get ; set ; }
2012-09-09 21:57:15 +00:00
LibGambatte . Buttons ControllerCallback ( )
{
2013-11-10 18:15:32 +00:00
CoreComm . InputCallback . Call ( ) ;
2012-09-09 21:57:15 +00:00
IsLagFrame = false ;
return CurrentButtons ;
}
2014-04-19 22:23:13 +00:00
public Dictionary < string , int > GetCpuFlagsAndRegisters ( )
2013-11-11 03:20:33 +00:00
{
2013-11-11 18:05:29 +00:00
int [ ] data = new int [ 10 ] ;
LibGambatte . gambatte_getregs ( GambatteState , data ) ;
2014-04-19 22:23:13 +00:00
return new Dictionary < string , int >
{
{ "PC" , data [ ( int ) LibGambatte . RegIndicies . PC ] & 0xffff } ,
{ "SP" , data [ ( int ) LibGambatte . RegIndicies . SP ] & 0xffff } ,
{ "A" , data [ ( int ) LibGambatte . RegIndicies . A ] & 0xff } ,
{ "B" , data [ ( int ) LibGambatte . RegIndicies . B ] & 0xff } ,
{ "C" , data [ ( int ) LibGambatte . RegIndicies . C ] & 0xff } ,
{ "D" , data [ ( int ) LibGambatte . RegIndicies . D ] & 0xff } ,
{ "E" , data [ ( int ) LibGambatte . RegIndicies . E ] & 0xff } ,
{ "F" , data [ ( int ) LibGambatte . RegIndicies . F ] & 0xff } ,
{ "H" , data [ ( int ) LibGambatte . RegIndicies . H ] & 0xff } ,
{ "L" , data [ ( int ) LibGambatte . RegIndicies . L ] & 0xff }
} ;
2013-11-11 03:20:33 +00:00
}
2014-05-31 17:03:21 +00:00
public void SetCpuRegister ( string register , int value )
{
throw new NotImplementedException ( ) ;
}
2012-09-15 18:30:11 +00:00
/// <summary>
/// true if the emulator is currently emulating CGB
/// </summary>
/// <returns></returns>
public bool IsCGBMode ( )
{
return ( LibGambatte . gambatte_iscgb ( GambatteState ) ) ;
}
2012-09-09 14:17:57 +00:00
2012-12-29 15:48:30 +00:00
internal void FrameAdvancePrep ( )
2012-09-08 21:36:04 +00:00
{
2013-12-07 00:53:06 +00:00
Frame + + ;
2012-09-09 14:17:57 +00:00
// update our local copy of the controller data
CurrentButtons = 0 ;
if ( Controller [ "Up" ] )
CurrentButtons | = LibGambatte . Buttons . UP ;
if ( Controller [ "Down" ] )
CurrentButtons | = LibGambatte . Buttons . DOWN ;
if ( Controller [ "Left" ] )
CurrentButtons | = LibGambatte . Buttons . LEFT ;
if ( Controller [ "Right" ] )
CurrentButtons | = LibGambatte . Buttons . RIGHT ;
if ( Controller [ "A" ] )
CurrentButtons | = LibGambatte . Buttons . A ;
if ( Controller [ "B" ] )
CurrentButtons | = LibGambatte . Buttons . B ;
if ( Controller [ "Select" ] )
CurrentButtons | = LibGambatte . Buttons . SELECT ;
if ( Controller [ "Start" ] )
CurrentButtons | = LibGambatte . Buttons . START ;
2012-09-09 21:57:15 +00:00
// the controller callback will set this to false if it actually gets called during the frame
IsLagFrame = true ;
2012-09-16 16:25:54 +00:00
if ( Controller [ "Power" ] )
2013-05-09 23:15:59 +00:00
LibGambatte . gambatte_reset ( GambatteState , GetCurrentTime ( ) ) ;
2012-09-15 02:36:19 +00:00
2012-10-14 15:10:33 +00:00
RefreshMemoryCallbacks ( ) ;
2012-12-10 00:43:43 +00:00
if ( CoreComm . Tracer . Enabled )
2012-11-02 19:44:31 +00:00
tracecb = MakeTrace ;
else
tracecb = null ;
LibGambatte . gambatte_settracecallback ( GambatteState , tracecb ) ;
2012-12-29 15:48:30 +00:00
}
2012-10-14 15:10:33 +00:00
2012-12-29 15:48:30 +00:00
internal void FrameAdvancePost ( )
{
2012-09-09 21:57:15 +00:00
if ( IsLagFrame )
LagCount + + ;
2012-09-08 21:36:04 +00:00
2012-11-06 17:54:04 +00:00
if ( endofframecallback ! = null )
endofframecallback ( LibGambatte . gambatte_cpuread ( GambatteState , 0xff40 ) ) ;
2012-09-08 21:36:04 +00:00
}
2014-04-29 23:31:25 +00:00
private ulong _cycleCount = 0 ;
private uint frameOverflow = 0 ;
private const uint TICKSINFRAME = 35112 ;
2014-04-28 16:02:11 +00:00
public ulong CycleCount { get { return _cycleCount ; } }
2012-12-29 15:48:30 +00:00
public void FrameAdvance ( bool render , bool rendersound )
{
FrameAdvancePrep ( ) ;
2014-04-29 23:31:25 +00:00
while ( true )
{
uint samplesEmitted = TICKSINFRAME - frameOverflow ; // according to gambatte docs, this is the nominal length of a frame in 2mhz clocks
2014-04-30 03:46:37 +00:00
System . Diagnostics . Debug . Assert ( samplesEmitted * 2 < = soundbuff . Length ) ;
2014-05-03 03:05:34 +00:00
if ( LibGambatte . gambatte_runfor ( GambatteState , soundbuff , ref samplesEmitted ) > 0 )
LibGambatte . gambatte_blitto ( GambatteState , VideoBuffer , 160 ) ;
2012-12-29 15:48:30 +00:00
2014-04-29 23:31:25 +00:00
_cycleCount + = ( ulong ) samplesEmitted ;
frameOverflow + = samplesEmitted ;
2012-12-29 15:48:30 +00:00
2014-04-29 23:31:25 +00:00
if ( rendersound )
{
soundbuffcontains = ( int ) samplesEmitted ;
ProcessSound ( ) ;
}
else
{
soundbuffcontains = 0 ;
}
2014-04-28 16:02:11 +00:00
2014-04-29 23:31:25 +00:00
if ( frameOverflow > = TICKSINFRAME )
{
frameOverflow - = TICKSINFRAME ;
break ;
}
2013-10-25 01:00:31 +00:00
}
2012-12-29 15:48:30 +00:00
2014-04-30 03:46:37 +00:00
if ( rendersound )
ProcessSoundEnd ( ) ;
2012-12-29 15:48:30 +00:00
FrameAdvancePost ( ) ;
2014-05-10 04:22:12 +00:00
//DebugStates(); // for maximum fun only
2012-12-29 15:48:30 +00:00
}
2013-08-24 16:54:22 +00:00
static string MapperName ( byte [ ] romdata )
{
switch ( romdata [ 0x147 ] )
{
case 0x00 : return "Plain ROM" ; // = PLAIN; break;
case 0x01 : return "MBC1 ROM" ; // = MBC1; break;
case 0x02 : return "MBC1 ROM+RAM" ; // = MBC1; break;
case 0x03 : return "MBC1 ROM+RAM+BATTERY" ; // = MBC1; break;
case 0x05 : return "MBC2 ROM" ; // = MBC2; break;
case 0x06 : return "MBC2 ROM+BATTERY" ; // = MBC2; break;
case 0x08 : return "Plain ROM+RAM" ; // = PLAIN; break;
case 0x09 : return "Plain ROM+RAM+BATTERY" ; // = PLAIN; break;
case 0x0F : return "MBC3 ROM+TIMER+BATTERY" ; // = MBC3; break;
case 0x10 : return "MBC3 ROM+TIMER+RAM+BATTERY" ; // = MBC3; break;
case 0x11 : return "MBC3 ROM" ; // = MBC3; break;
case 0x12 : return "MBC3 ROM+RAM" ; // = MBC3; break;
case 0x13 : return "MBC3 ROM+RAM+BATTERY" ; // = MBC3; break;
case 0x19 : return "MBC5 ROM" ; // = MBC5; break;
case 0x1A : return "MBC5 ROM+RAM" ; // = MBC5; break;
case 0x1B : return "MBC5 ROM+RAM+BATTERY" ; // = MBC5; break;
case 0x1C : return "MBC5 ROM+RUMBLE" ; // = MBC5; break;
case 0x1D : return "MBC5 ROM+RUMBLE+RAM" ; // = MBC5; break;
case 0x1E : return "MBC5 ROM+RUMBLE+RAM+BATTERY" ; // = MBC5; break;
case 0xFF : return "HuC1 ROM+RAM+BATTERY" ; // = HUC1; break;
default : return "UNKNOWN" ;
}
}
2012-09-24 20:20:21 +00:00
/// <summary>
/// throw exception with intelligible message on some kinds of bad rom
/// </summary>
/// <param name="romdata"></param>
static void ThrowExceptionForBadRom ( byte [ ] romdata )
{
if ( romdata . Length < 0x148 )
2014-08-01 14:56:23 +00:00
throw new ArgumentException ( "ROM is far too small to be a valid GB\\GBC rom!" ) ;
2012-09-09 14:17:57 +00:00
2012-09-24 20:20:21 +00:00
switch ( romdata [ 0x147 ] )
{
case 0x00 : break ;
case 0x01 : break ;
case 0x02 : break ;
case 0x03 : break ;
case 0x05 : break ;
case 0x06 : break ;
case 0x08 : break ;
case 0x09 : break ;
2014-08-01 14:56:23 +00:00
case 0x0b : throw new UnsupportedMapperException ( "\"MM01\" Mapper not supported!" ) ;
case 0x0c : throw new UnsupportedMapperException ( "\"MM01\" Mapper not supported!" ) ;
case 0x0d : throw new UnsupportedMapperException ( "\"MM01\" Mapper not supported!" ) ;
2012-09-24 20:20:21 +00:00
case 0x0f : break ;
case 0x10 : break ;
case 0x11 : break ;
case 0x12 : break ;
case 0x13 : break ;
2014-08-01 14:56:23 +00:00
case 0x15 : throw new UnsupportedMapperException ( "\"MBC4\" Mapper not supported!" ) ;
case 0x16 : throw new UnsupportedMapperException ( "\"MBC4\" Mapper not supported!" ) ;
case 0x17 : throw new UnsupportedMapperException ( "\"MBC4\" Mapper not supported!" ) ;
2012-09-24 20:20:21 +00:00
case 0x19 : break ;
case 0x1a : break ;
case 0x1b : break ;
case 0x1c : break ; // rumble
case 0x1d : break ; // rumble
case 0x1e : break ; // rumble
2014-08-01 14:56:23 +00:00
case 0x20 : throw new UnsupportedMapperException ( "\"MBC6\" Mapper not supported!" ) ;
case 0x22 : throw new UnsupportedMapperException ( "\"MBC7\" Mapper not supported!" ) ;
2012-09-24 20:20:21 +00:00
2014-08-01 14:56:23 +00:00
case 0xfc : throw new UnsupportedMapperException ( "\"Pocket Camera\" Mapper not supported!" ) ;
case 0xfd : throw new UnsupportedMapperException ( "\"Bandai TAMA5\" Mapper not supported!" ) ;
case 0xfe : throw new UnsupportedMapperException ( "\"HuC3\" Mapper not supported!" ) ;
2012-09-24 20:20:21 +00:00
case 0xff : break ;
2014-08-01 14:56:23 +00:00
default : throw new UnsupportedMapperException ( string . Format ( "Unknown mapper: {0:x2}" , romdata [ 0x147 ] ) ) ;
2012-09-24 20:20:21 +00:00
}
return ;
}
2012-09-09 21:57:15 +00:00
2012-09-09 14:17:57 +00:00
public int Frame { get ; set ; }
2012-09-08 21:36:04 +00:00
public int LagCount { get ; set ; }
2012-09-09 21:57:15 +00:00
public bool IsLagFrame { get ; private set ; }
2012-09-08 21:36:04 +00:00
public string SystemId
{
get { return "GB" ; }
}
2013-08-24 16:54:22 +00:00
public string BoardName { get ; private set ; }
2014-05-12 17:24:43 +00:00
public bool DeterministicEmulation { get ; private set ; }
2012-09-08 21:36:04 +00:00
2012-10-14 15:10:33 +00:00
#region saveram
2012-09-14 22:28:38 +00:00
public byte [ ] ReadSaveRam ( )
2012-09-08 21:36:04 +00:00
{
2012-09-14 22:28:38 +00:00
int length = LibGambatte . gambatte_savesavedatalength ( GambatteState ) ;
if ( length > 0 )
2012-09-10 23:40:53 +00:00
{
2012-09-14 22:28:38 +00:00
byte [ ] ret = new byte [ length ] ;
LibGambatte . gambatte_savesavedata ( GambatteState , ret ) ;
return ret ;
2012-09-10 23:40:53 +00:00
}
2012-09-14 22:28:38 +00:00
else
return new byte [ 0 ] ;
2012-09-10 23:40:53 +00:00
}
public void StoreSaveRam ( byte [ ] data )
{
if ( data . Length ! = LibGambatte . gambatte_savesavedatalength ( GambatteState ) )
throw new ArgumentException ( "Size of saveram data does not match expected!" ) ;
LibGambatte . gambatte_loadsavedata ( GambatteState , data ) ;
2012-09-08 21:36:04 +00:00
}
2012-09-14 21:29:35 +00:00
/// <summary>
/// reset cart save ram, if any, to initial state
/// </summary>
public void ClearSaveRam ( )
{
int length = LibGambatte . gambatte_savesavedatalength ( GambatteState ) ;
if ( length = = 0 )
return ;
byte [ ] clear = new byte [ length ] ;
for ( int i = 0 ; i < clear . Length ; i + + )
clear [ i ] = 0xff ; // this exactly matches what gambatte core does
StoreSaveRam ( clear ) ;
}
2012-09-10 23:40:53 +00:00
2012-09-08 21:36:04 +00:00
public bool SaveRamModified
{
2012-09-10 23:40:53 +00:00
get
{
if ( LibGambatte . gambatte_savesavedatalength ( GambatteState ) = = 0 )
return false ;
else
return true ; // need to wire more stuff into the core to actually know this
}
set { }
2012-09-08 21:36:04 +00:00
}
2012-10-14 15:10:33 +00:00
#endregion
2013-11-03 16:29:51 +00:00
public void ResetCounters ( )
2012-09-08 21:36:04 +00:00
{
2012-09-09 21:57:15 +00:00
Frame = 0 ;
LagCount = 0 ;
2012-11-25 15:41:40 +00:00
IsLagFrame = false ;
2012-09-08 21:36:04 +00:00
}
2012-09-09 21:57:15 +00:00
#region savestates
2014-05-10 04:22:12 +00:00
byte [ ] newsavebuff ;
void NewSaveCoreSetBuff ( )
{
newsavebuff = new byte [ LibGambatte . gambatte_newstatelen ( GambatteState ) ] ;
}
byte [ ] NewSaveCoreBinary ( )
{
if ( ! LibGambatte . gambatte_newstatesave ( GambatteState , newsavebuff , newsavebuff . Length ) )
throw new Exception ( "gambatte_newstatesave() returned false" ) ;
return newsavebuff ;
}
void NewLoadCoreBinary ( byte [ ] data )
{
if ( ! LibGambatte . gambatte_newstateload ( GambatteState , data , data . Length ) )
throw new Exception ( "gambatte_newstateload() returned false" ) ;
}
2014-05-11 20:48:19 +00:00
JsonSerializer ser = new JsonSerializer ( ) { Formatting = Formatting . Indented } ;
2014-05-31 04:01:07 +00:00
// other data in the text state besides core
class TextStateData
{
public int Frame ;
public int LagCount ;
public bool IsLagFrame ;
public ulong _cycleCount ;
public uint frameOverflow ;
}
2012-09-08 21:36:04 +00:00
public void SaveStateText ( System . IO . TextWriter writer )
{
2014-05-31 04:01:07 +00:00
var s = new TextState < TextStateData > ( ) ;
2014-05-11 20:48:19 +00:00
s . Prepare ( ) ;
2014-06-04 18:53:57 +00:00
var ff = s . GetFunctionPointersSave ( ) ;
2014-05-31 04:01:07 +00:00
LibGambatte . gambatte_newstatesave_ex ( GambatteState , ref ff ) ;
s . ExtraData . IsLagFrame = IsLagFrame ;
s . ExtraData . LagCount = LagCount ;
s . ExtraData . Frame = Frame ;
s . ExtraData . frameOverflow = frameOverflow ;
s . ExtraData . _cycleCount = _cycleCount ;
2014-05-11 20:48:19 +00:00
ser . Serialize ( writer , s ) ;
2012-09-23 16:56:11 +00:00
// write extra copy of stuff we don't use
2014-05-11 20:48:19 +00:00
writer . WriteLine ( ) ;
2012-09-23 16:56:11 +00:00
writer . WriteLine ( "Frame {0}" , Frame ) ;
2012-09-08 21:36:04 +00:00
}
public void LoadStateText ( System . IO . TextReader reader )
{
2014-05-31 04:01:07 +00:00
var s = ( TextState < TextStateData > ) ser . Deserialize ( reader , typeof ( TextState < TextStateData > ) ) ;
2014-05-11 20:48:19 +00:00
s . Prepare ( ) ;
2014-06-04 18:53:57 +00:00
var ff = s . GetFunctionPointersLoad ( ) ;
2014-05-31 04:01:07 +00:00
LibGambatte . gambatte_newstateload_ex ( GambatteState , ref ff ) ;
IsLagFrame = s . ExtraData . IsLagFrame ;
LagCount = s . ExtraData . LagCount ;
Frame = s . ExtraData . Frame ;
frameOverflow = s . ExtraData . frameOverflow ;
_cycleCount = s . ExtraData . _cycleCount ;
2012-09-08 21:36:04 +00:00
}
public void SaveStateBinary ( System . IO . BinaryWriter writer )
{
2014-05-10 04:22:12 +00:00
//byte[] data = SaveCoreBinary();
byte [ ] data = NewSaveCoreBinary ( ) ;
2012-09-09 18:47:00 +00:00
2012-09-23 16:56:11 +00:00
writer . Write ( data . Length ) ;
2012-09-09 18:47:00 +00:00
writer . Write ( data ) ;
2012-09-11 01:46:57 +00:00
// other variables
writer . Write ( IsLagFrame ) ;
writer . Write ( LagCount ) ;
writer . Write ( Frame ) ;
2014-04-30 17:36:35 +00:00
writer . Write ( frameOverflow ) ;
writer . Write ( _cycleCount ) ;
2012-09-08 21:36:04 +00:00
}
public void LoadStateBinary ( System . IO . BinaryReader reader )
{
2012-09-09 18:47:00 +00:00
int length = reader . ReadInt32 ( ) ;
byte [ ] data = reader . ReadBytes ( length ) ;
2012-09-08 21:48:46 +00:00
2014-05-10 04:22:12 +00:00
//LoadCoreBinary(data);
NewLoadCoreBinary ( data ) ;
2012-09-11 01:46:57 +00:00
// other variables
IsLagFrame = reader . ReadBoolean ( ) ;
LagCount = reader . ReadInt32 ( ) ;
Frame = reader . ReadInt32 ( ) ;
2014-04-30 17:36:35 +00:00
frameOverflow = reader . ReadUInt32 ( ) ;
_cycleCount = reader . ReadUInt64 ( ) ;
2012-09-08 21:36:04 +00:00
}
public byte [ ] SaveStateBinary ( )
{
2012-09-09 18:47:00 +00:00
MemoryStream ms = new MemoryStream ( ) ;
BinaryWriter bw = new BinaryWriter ( ms ) ;
SaveStateBinary ( bw ) ;
bw . Flush ( ) ;
return ms . ToArray ( ) ;
2012-09-08 21:36:04 +00:00
}
2013-05-06 20:51:28 +00:00
public bool BinarySaveStatesPreferred { get { return true ; } }
2014-05-31 04:01:07 +00:00
/ *
2014-05-10 04:22:12 +00:00
void DebugStates ( )
{
var sd = new StateDebug ( ) ;
var Save = new LibGambatte . DataFunction ( sd . Save ) ;
var Load = new LibGambatte . DataFunction ( sd . Load ) ;
var EnterSection = new LibGambatte . SectionFunction ( sd . EnterSection ) ;
var ExitSection = new LibGambatte . SectionFunction ( sd . ExitSection ) ;
LibGambatte . gambatte_newstatesave_ex ( GambatteState , Save , EnterSection , ExitSection ) ;
LibGambatte . gambatte_newstateload_ex ( GambatteState , Load , EnterSection , ExitSection ) ;
2014-05-31 04:01:07 +00:00
} * /
2014-05-10 04:22:12 +00:00
2012-09-09 21:57:15 +00:00
#endregion
2012-10-14 15:10:33 +00:00
#region memorycallback
LibGambatte . MemoryCallback readcb ;
LibGambatte . MemoryCallback writecb ;
2013-11-13 17:08:52 +00:00
LibGambatte . MemoryCallback execcb ;
2012-10-14 15:10:33 +00:00
void RefreshMemoryCallbacks ( )
{
2012-12-10 00:43:43 +00:00
var mcs = CoreComm . MemoryCallbackSystem ;
2012-10-14 15:10:33 +00:00
// we RefreshMemoryCallbacks() after the triggers in case the trigger turns itself off at that point
2013-11-10 21:20:55 +00:00
if ( mcs . HasReads )
readcb = delegate ( uint addr ) { mcs . CallRead ( addr ) ; RefreshMemoryCallbacks ( ) ; } ;
2012-10-14 15:10:33 +00:00
else
readcb = null ;
2013-11-10 21:20:55 +00:00
if ( mcs . HasWrites )
writecb = delegate ( uint addr ) { mcs . CallWrite ( addr ) ; RefreshMemoryCallbacks ( ) ; } ;
2012-10-14 15:10:33 +00:00
else
writecb = null ;
2013-11-13 17:08:52 +00:00
if ( mcs . HasExecutes )
execcb = delegate ( uint addr ) { mcs . CallExecute ( addr ) ; RefreshMemoryCallbacks ( ) ; } ;
else
execcb = null ;
2012-10-14 15:10:33 +00:00
LibGambatte . gambatte_setreadcallback ( GambatteState , readcb ) ;
LibGambatte . gambatte_setwritecallback ( GambatteState , writecb ) ;
2013-11-13 17:08:52 +00:00
LibGambatte . gambatte_setexeccallback ( GambatteState , execcb ) ;
2012-10-14 15:10:33 +00:00
}
2012-11-19 17:59:57 +00:00
2012-10-14 15:10:33 +00:00
#endregion
2012-09-08 21:36:04 +00:00
2012-12-10 00:43:43 +00:00
public CoreComm CoreComm { get ; set ; }
2012-09-08 21:36:04 +00:00
2012-11-02 19:44:31 +00:00
LibGambatte . TraceCallback tracecb ;
void MakeTrace ( IntPtr _s )
{
int [ ] s = new int [ 13 ] ;
System . Runtime . InteropServices . Marshal . Copy ( _s , s , 0 , 13 ) ;
2012-11-02 23:19:16 +00:00
ushort unused ;
2012-11-02 19:44:31 +00:00
2012-12-10 00:43:43 +00:00
CoreComm . Tracer . Put ( string . Format (
2012-11-02 23:19:16 +00:00
"{13} SP:{2:x2} A:{3:x2} B:{4:x2} C:{5:x2} D:{6:x2} E:{7:x2} F:{8:x2} H:{9:x2} L:{10:x2} {11} Cy:{0}" ,
2012-11-02 19:44:31 +00:00
s [ 0 ] ,
s [ 1 ] & 0xffff ,
s [ 2 ] & 0xffff ,
s [ 3 ] & 0xff ,
s [ 4 ] & 0xff ,
s [ 5 ] & 0xff ,
s [ 6 ] & 0xff ,
s [ 7 ] & 0xff ,
s [ 8 ] & 0xff ,
s [ 9 ] & 0xff ,
s [ 10 ] & 0xff ,
s [ 11 ] ! = 0 ? "skip" : "" ,
2012-11-02 23:19:16 +00:00
s [ 12 ] & 0xff ,
2013-11-14 15:01:32 +00:00
Common . Components . Z80GB . NewDisassembler . Disassemble ( ( ushort ) s [ 1 ] , ( addr ) = > LibGambatte . gambatte_cpuread ( GambatteState , addr ) , out unused ) . PadRight ( 30 )
2012-11-02 19:44:31 +00:00
) ) ;
}
2012-09-11 17:37:17 +00:00
#region MemoryDomains
2014-05-07 21:34:19 +00:00
unsafe void CreateMemoryDomain ( LibGambatte . MemoryAreas which , string name )
2012-09-08 21:36:04 +00:00
{
2012-09-11 15:28:38 +00:00
IntPtr data = IntPtr . Zero ;
int length = 0 ;
if ( ! LibGambatte . gambatte_getmemoryarea ( GambatteState , which , ref data , ref length ) )
throw new Exception ( "gambatte_getmemoryarea() failed!" ) ;
2012-09-11 19:05:44 +00:00
// if length == 0, it's an empty block; (usually rambank on some carts); that's ok
2012-09-13 21:03:34 +00:00
// TODO: when length == 0, should we simply not add the memory domain at all?
2012-09-11 19:05:44 +00:00
if ( data = = IntPtr . Zero & & length > 0 )
2012-09-11 15:28:38 +00:00
throw new Exception ( "bad return from gambatte_getmemoryarea()" ) ;
2014-05-07 21:34:19 +00:00
byte * ptr = ( byte * ) data ;
2012-09-11 17:37:17 +00:00
2014-05-07 21:34:19 +00:00
_MemoryDomains . Add ( new MemoryDomain ( name , length , MemoryDomain . Endian . Little ,
delegate ( int addr )
{
if ( addr < 0 | | addr > = length )
throw new ArgumentOutOfRangeException ( ) ;
return ptr [ addr ] ;
} ,
delegate ( int addr , byte val )
{
if ( addr < 0 | | addr > = length )
throw new ArgumentOutOfRangeException ( ) ;
ptr [ addr ] = val ;
} ) ) ;
2012-09-08 21:36:04 +00:00
}
2012-09-11 15:28:38 +00:00
void InitMemoryDomains ( )
2012-09-08 21:36:04 +00:00
{
2012-09-13 21:19:26 +00:00
CreateMemoryDomain ( LibGambatte . MemoryAreas . wram , "WRAM" ) ;
CreateMemoryDomain ( LibGambatte . MemoryAreas . rom , "ROM" ) ;
CreateMemoryDomain ( LibGambatte . MemoryAreas . vram , "VRAM" ) ;
CreateMemoryDomain ( LibGambatte . MemoryAreas . cartram , "Cart RAM" ) ;
CreateMemoryDomain ( LibGambatte . MemoryAreas . oam , "OAM" ) ;
CreateMemoryDomain ( LibGambatte . MemoryAreas . hram , "HRAM" ) ;
2012-09-11 15:28:38 +00:00
2012-09-13 21:03:34 +00:00
// also add a special memory domain for the system bus, where calls get sent directly to the core each time
2013-11-06 02:15:29 +00:00
_MemoryDomains . Add ( new MemoryDomain ( "System Bus" , 65536 , MemoryDomain . Endian . Little ,
2012-09-13 21:03:34 +00:00
delegate ( int addr )
{
2014-02-26 20:18:48 +00:00
if ( addr < 0 | | addr > = 65536 )
throw new ArgumentOutOfRangeException ( ) ;
2012-09-13 21:03:34 +00:00
return LibGambatte . gambatte_cpuread ( GambatteState , ( ushort ) addr ) ;
} ,
delegate ( int addr , byte val )
{
2014-02-26 20:18:48 +00:00
if ( addr < 0 | | addr > = 65536 )
throw new ArgumentOutOfRangeException ( ) ;
2012-09-13 21:03:34 +00:00
LibGambatte . gambatte_cpuwrite ( GambatteState , ( ushort ) addr , val ) ;
} ) ) ;
2013-11-06 02:15:29 +00:00
MemoryDomains = new MemoryDomainList ( _MemoryDomains ) ;
2012-09-08 21:36:04 +00:00
}
2013-11-06 02:15:29 +00:00
private List < MemoryDomain > _MemoryDomains = new List < MemoryDomain > ( ) ;
public MemoryDomainList MemoryDomains { get ; private set ; }
2012-09-11 15:28:38 +00:00
2012-09-11 17:37:17 +00:00
#endregion
2012-11-05 01:34:11 +00:00
#region ppudebug
2012-11-05 20:15:53 +00:00
public bool GetGPUMemoryAreas ( out IntPtr vram , out IntPtr bgpal , out IntPtr sppal , out IntPtr oam )
{
IntPtr _vram = IntPtr . Zero ;
IntPtr _bgpal = IntPtr . Zero ;
IntPtr _sppal = IntPtr . Zero ;
IntPtr _oam = IntPtr . Zero ;
int unused = 0 ;
if ( ! LibGambatte . gambatte_getmemoryarea ( GambatteState , LibGambatte . MemoryAreas . vram , ref _vram , ref unused )
| | ! LibGambatte . gambatte_getmemoryarea ( GambatteState , LibGambatte . MemoryAreas . bgpal , ref _bgpal , ref unused )
| | ! LibGambatte . gambatte_getmemoryarea ( GambatteState , LibGambatte . MemoryAreas . sppal , ref _sppal , ref unused )
| | ! LibGambatte . gambatte_getmemoryarea ( GambatteState , LibGambatte . MemoryAreas . oam , ref _oam , ref unused ) )
{
vram = IntPtr . Zero ;
bgpal = IntPtr . Zero ;
sppal = IntPtr . Zero ;
oam = IntPtr . Zero ;
return false ;
}
vram = _vram ;
bgpal = _bgpal ;
sppal = _sppal ;
oam = _oam ;
return true ;
}
2012-11-05 01:34:11 +00:00
/// <summary>
2012-11-05 04:09:04 +00:00
///
2012-11-05 01:34:11 +00:00
/// </summary>
2012-11-05 20:15:53 +00:00
/// <param name="lcdc">current value of register $ff40 (LCDC)</param>
public delegate void ScanlineCallback ( int lcdc ) ;
2012-11-05 01:34:11 +00:00
/// <summary>
/// set up callback
/// </summary>
/// <param name="callback"></param>
2012-11-06 17:54:04 +00:00
/// <param name="line">scanline. -1 = end of frame, -2 = RIGHT NOW</param>
2012-11-05 01:34:11 +00:00
public void SetScanlineCallback ( ScanlineCallback callback , int line )
{
2012-11-05 20:15:53 +00:00
if ( GambatteState = = IntPtr . Zero )
// not sure how this is being reached. tried the debugger...
return ;
2012-11-06 17:54:04 +00:00
endofframecallback = null ;
if ( callback = = null | | line = = - 1 | | line = = - 2 )
{
2012-11-05 20:15:53 +00:00
scanlinecb = null ;
2012-11-06 17:54:04 +00:00
LibGambatte . gambatte_setscanlinecallback ( GambatteState , null , 0 ) ;
if ( line = = - 1 )
endofframecallback = callback ;
else if ( line = = - 2 )
callback ( LibGambatte . gambatte_cpuread ( GambatteState , 0xff40 ) ) ;
}
else if ( line > = 0 & & line < = 153 )
{
2012-11-05 20:15:53 +00:00
scanlinecb = delegate ( )
{
callback ( LibGambatte . gambatte_cpuread ( GambatteState , 0xff40 ) ) ;
} ;
2012-11-06 17:54:04 +00:00
LibGambatte . gambatte_setscanlinecallback ( GambatteState , scanlinecb , line ) ;
}
else
throw new ArgumentOutOfRangeException ( "line must be in [0, 153]" ) ;
2012-11-05 01:34:11 +00:00
}
2012-11-05 20:15:53 +00:00
LibGambatte . ScanlineCallback scanlinecb ;
2012-11-06 17:54:04 +00:00
ScanlineCallback endofframecallback ;
2012-11-05 20:15:53 +00:00
2012-11-05 01:34:11 +00:00
#endregion
2012-09-08 21:36:04 +00:00
public void Dispose ( )
{
2013-12-09 20:50:21 +00:00
if ( GambatteState ! = IntPtr . Zero )
{
LibGambatte . gambatte_destroy ( GambatteState ) ;
GambatteState = IntPtr . Zero ;
}
2012-09-09 13:35:58 +00:00
DisposeSound ( ) ;
2012-09-08 21:36:04 +00:00
}
#region IVideoProvider
2012-09-09 21:57:15 +00:00
public IVideoProvider VideoProvider
{
get { return this ; }
}
2012-09-08 22:01:47 +00:00
/// <summary>
/// stored image of most recent frame
/// </summary>
2012-09-08 21:36:04 +00:00
int [ ] VideoBuffer = new int [ 160 * 144 ] ;
public int [ ] GetVideoBuffer ( )
{
return VideoBuffer ;
}
public int VirtualWidth
{
2012-09-09 21:57:15 +00:00
// only sgb changes this, which we don't emulate here
2012-09-08 21:36:04 +00:00
get { return 160 ; }
}
2014-04-30 23:48:37 +00:00
public int VirtualHeight
{
get { return 144 ; }
}
2012-09-08 21:36:04 +00:00
public int BufferWidth
{
get { return 160 ; }
}
public int BufferHeight
{
get { return 144 ; }
}
public int BackgroundColor
{
get { return 0 ; }
}
2012-09-14 00:02:37 +00:00
#endregion
#region palette
2012-09-12 22:18:51 +00:00
/// <summary>
/// update gambatte core's internal colors
/// </summary>
2012-09-15 18:30:11 +00:00
public void ChangeDMGColors ( int [ ] colors )
2012-09-12 22:18:51 +00:00
{
2012-09-15 18:30:11 +00:00
for ( int i = 0 ; i < 12 ; i + + )
LibGambatte . gambatte_setdmgpalettecolor ( GambatteState , ( LibGambatte . PalType ) ( i / 4 ) , ( uint ) i % 4 , ( uint ) colors [ i ] ) ;
2012-09-12 22:18:51 +00:00
}
2012-11-18 18:46:57 +00:00
public void SetCGBColors ( GBColors . ColorType type )
2012-11-18 17:02:55 +00:00
{
2012-11-18 18:46:57 +00:00
int [ ] lut = GBColors . GetLut ( type ) ;
2012-11-19 17:59:57 +00:00
LibGambatte . gambatte_setcgbpalette ( GambatteState , lut ) ;
2012-11-18 17:02:55 +00:00
}
2012-09-08 21:36:04 +00:00
#endregion
2012-09-09 00:41:11 +00:00
#region ISoundProvider
2012-11-19 17:59:57 +00:00
public ISoundProvider SoundProvider { get { return null ; } }
2013-10-25 01:00:31 +00:00
public ISyncSoundProvider SyncSoundProvider { get { return this ; } }
sound api changes. added a new ISyncSoundProvider, which works similarly to ISoundProvider except the source (not the sink) determines the number of samples to process. Added facilities to metaspu, dcfilter, speexresampler to work with ISyncSoundProvider. Add ISyncSoundProvider to IEmulator. All IEmulators must provide sync sound, but they need not provide async sound. When async is needed and an IEmulator doesn't provide it, the frontend will wrap it in a vecna metaspu. SNES, GB changed to provide sync sound only. All other emulator cores mostly unchanged; they just provide stub fakesync alongside async, for now. For the moment, the only use of the sync sound is for realtime audio throttling, where it works and sounds quite nice. In the future, sync sound will be supported for AV dumping as well.
2012-10-11 00:44:59 +00:00
public bool StartAsyncSound ( ) { return false ; }
public void EndAsyncSound ( ) { }
2012-09-09 21:57:15 +00:00
2012-09-09 00:41:11 +00:00
/// <summary>
/// sample pairs before resampling
/// </summary>
2014-04-30 03:46:37 +00:00
short [ ] soundbuff = new short [ ( 35112 + 2064 ) * 2 * 4 ] ;
2012-09-09 00:41:11 +00:00
/// <summary>
/// how many sample pairs are in soundbuff
/// </summary>
2012-12-29 17:11:19 +00:00
int soundbuffcontains = 0 ;
2012-09-09 00:41:11 +00:00
2013-10-25 01:00:31 +00:00
int soundoutbuffcontains = 0 ;
short [ ] soundoutbuff = new short [ 2048 ] ;
2014-03-05 05:03:13 +00:00
int latchL = 0 ;
int latchR = 0 ;
2013-10-25 01:00:31 +00:00
2014-03-05 05:03:13 +00:00
BlipBuffer blipL , blipR ;
2014-04-30 03:46:37 +00:00
uint blipAccumulate ;
2013-10-25 01:00:31 +00:00
2014-04-29 23:31:25 +00:00
private void ProcessSound ( )
2013-10-25 01:00:31 +00:00
{
for ( uint i = 0 ; i < soundbuffcontains ; i + + )
{
int curr = soundbuff [ i * 2 ] ;
2014-03-05 05:03:13 +00:00
if ( curr ! = latchL )
2013-10-25 01:00:31 +00:00
{
2014-03-05 05:03:13 +00:00
int diff = latchL - curr ;
latchL = curr ;
2014-04-30 03:46:37 +00:00
blipL . AddDelta ( blipAccumulate , diff ) ;
2014-03-05 05:03:13 +00:00
}
curr = soundbuff [ i * 2 + 1 ] ;
if ( curr ! = latchR )
{
int diff = latchR - curr ;
latchR = curr ;
2014-04-30 03:46:37 +00:00
blipR . AddDelta ( blipAccumulate , diff ) ;
2013-10-25 01:00:31 +00:00
}
2014-04-30 03:46:37 +00:00
blipAccumulate + + ;
2013-10-25 01:00:31 +00:00
}
2014-04-29 23:31:25 +00:00
2014-04-30 03:46:37 +00:00
soundbuffcontains = 0 ;
}
private void ProcessSoundEnd ( )
{
blipL . EndFrame ( ( uint ) blipAccumulate ) ;
blipR . EndFrame ( ( uint ) blipAccumulate ) ;
blipAccumulate = 0 ;
2013-10-25 01:00:31 +00:00
2014-03-05 05:03:13 +00:00
soundoutbuffcontains = blipL . SamplesAvailable ( ) ;
if ( soundoutbuffcontains ! = blipR . SamplesAvailable ( ) )
throw new Exception ( "Audio processing error" ) ;
2013-10-25 01:00:31 +00:00
2014-03-05 05:03:13 +00:00
blipL . ReadSamplesLeft ( soundoutbuff , soundoutbuffcontains ) ;
blipR . ReadSamplesRight ( soundoutbuff , soundoutbuffcontains ) ;
2013-10-25 01:00:31 +00:00
}
2012-09-09 00:41:11 +00:00
void InitSound ( )
{
2014-03-05 05:03:13 +00:00
blipL = new BlipBuffer ( 1024 ) ;
blipL . SetRates ( 2097152 , 44100 ) ;
blipR = new BlipBuffer ( 1024 ) ;
blipR . SetRates ( 2097152 , 44100 ) ;
2012-09-09 00:41:11 +00:00
}
2012-09-09 13:35:58 +00:00
void DisposeSound ( )
{
2014-03-05 05:03:13 +00:00
if ( blipL ! = null )
{
blipL . Dispose ( ) ;
blipL = null ;
}
if ( blipR ! = null )
2013-12-09 20:50:21 +00:00
{
2014-03-05 05:03:13 +00:00
blipR . Dispose ( ) ;
blipR = null ;
2013-12-09 20:50:21 +00:00
}
2012-09-09 13:35:58 +00:00
}
sound api changes. added a new ISyncSoundProvider, which works similarly to ISoundProvider except the source (not the sink) determines the number of samples to process. Added facilities to metaspu, dcfilter, speexresampler to work with ISyncSoundProvider. Add ISyncSoundProvider to IEmulator. All IEmulators must provide sync sound, but they need not provide async sound. When async is needed and an IEmulator doesn't provide it, the frontend will wrap it in a vecna metaspu. SNES, GB changed to provide sync sound only. All other emulator cores mostly unchanged; they just provide stub fakesync alongside async, for now. For the moment, the only use of the sync sound is for realtime audio throttling, where it works and sounds quite nice. In the future, sync sound will be supported for AV dumping as well.
2012-10-11 00:44:59 +00:00
public void DiscardSamples ( )
2012-09-29 22:38:47 +00:00
{
sound api changes. added a new ISyncSoundProvider, which works similarly to ISoundProvider except the source (not the sink) determines the number of samples to process. Added facilities to metaspu, dcfilter, speexresampler to work with ISyncSoundProvider. Add ISyncSoundProvider to IEmulator. All IEmulators must provide sync sound, but they need not provide async sound. When async is needed and an IEmulator doesn't provide it, the frontend will wrap it in a vecna metaspu. SNES, GB changed to provide sync sound only. All other emulator cores mostly unchanged; they just provide stub fakesync alongside async, for now. For the moment, the only use of the sync sound is for realtime audio throttling, where it works and sounds quite nice. In the future, sync sound will be supported for AV dumping as well.
2012-10-11 00:44:59 +00:00
soundbuffcontains = 0 ;
2012-09-29 22:38:47 +00:00
}
sound api changes. added a new ISyncSoundProvider, which works similarly to ISoundProvider except the source (not the sink) determines the number of samples to process. Added facilities to metaspu, dcfilter, speexresampler to work with ISyncSoundProvider. Add ISyncSoundProvider to IEmulator. All IEmulators must provide sync sound, but they need not provide async sound. When async is needed and an IEmulator doesn't provide it, the frontend will wrap it in a vecna metaspu. SNES, GB changed to provide sync sound only. All other emulator cores mostly unchanged; they just provide stub fakesync alongside async, for now. For the moment, the only use of the sync sound is for realtime audio throttling, where it works and sounds quite nice. In the future, sync sound will be supported for AV dumping as well.
2012-10-11 00:44:59 +00:00
public void GetSamples ( out short [ ] samples , out int nsamp )
2012-09-09 00:41:11 +00:00
{
2013-10-25 01:00:31 +00:00
samples = soundoutbuff ;
nsamp = soundoutbuffcontains ;
2012-09-09 00:41:11 +00:00
}
2014-04-29 23:31:25 +00:00
2012-09-09 00:41:11 +00:00
#endregion
2012-09-09 21:57:15 +00:00
2014-04-29 23:31:25 +00:00
#region Settings
2014-07-14 16:10:45 +00:00
GambatteSettings _Settings ;
GambatteSyncSettings _SyncSettings ;
2013-12-23 02:51:41 +00:00
2014-07-14 16:10:45 +00:00
public object GetSettings ( ) { return _Settings . Clone ( ) ; }
public object GetSyncSettings ( ) { return _SyncSettings . Clone ( ) ; }
2013-12-23 02:51:41 +00:00
public bool PutSettings ( object o )
{
2014-07-14 16:10:45 +00:00
_Settings = ( GambatteSettings ) o ;
2013-12-23 02:51:41 +00:00
if ( IsCGBMode ( ) )
2014-07-14 16:10:45 +00:00
SetCGBColors ( _Settings . CGBColors ) ;
2013-12-23 02:51:41 +00:00
else
2014-07-14 16:10:45 +00:00
ChangeDMGColors ( _Settings . GBPalette ) ;
2013-12-23 02:51:41 +00:00
return false ;
}
public bool PutSyncSettings ( object o )
{
2013-12-23 16:58:20 +00:00
var s = ( GambatteSyncSettings ) o ;
bool ret ;
2014-07-14 16:10:45 +00:00
if ( s . ForceDMG ! = _SyncSettings . ForceDMG | |
s . GBACGB ! = _SyncSettings . GBACGB | |
s . MulticartCompat ! = _SyncSettings . MulticartCompat | |
s . RealTimeRTC ! = _SyncSettings . RealTimeRTC | |
s . RTCInitialTime ! = _SyncSettings . RTCInitialTime )
2013-12-23 16:58:20 +00:00
ret = true ;
else
ret = false ;
2014-07-14 16:10:45 +00:00
_SyncSettings = s ;
2013-12-23 16:58:20 +00:00
return ret ;
2013-12-23 02:51:41 +00:00
}
public class GambatteSettings
{
2014-07-14 16:56:23 +00:00
private static readonly int [ ] DefaultPalette = new [ ]
2013-12-23 02:51:41 +00:00
{
10798341 , 8956165 , 1922333 , 337157 ,
10798341 , 8956165 , 1922333 , 337157 ,
10798341 , 8956165 , 1922333 , 337157
} ;
2014-07-14 16:56:23 +00:00
public int [ ] GBPalette ;
public GBColors . ColorType CGBColors ;
public GambatteSettings ( )
{
GBPalette = ( int [ ] ) DefaultPalette . Clone ( ) ;
CGBColors = GBColors . ColorType . gambatte ;
2013-12-23 02:51:41 +00:00
}
public GambatteSettings Clone ( )
{
var ret = ( GambatteSettings ) MemberwiseClone ( ) ;
ret . GBPalette = ( int [ ] ) GBPalette . Clone ( ) ;
return ret ;
}
}
public class GambatteSyncSettings
{
2014-07-20 00:16:40 +00:00
[DisplayName("Force DMG Mode")]
2014-05-12 04:08:22 +00:00
[Description("Force the game to run on DMG hardware, even if it's detected as a CGB game. Relevant for games that are \"CGB Enhanced\" but do not require CGB.")]
2014-05-12 17:24:43 +00:00
[DefaultValue(false)]
2013-12-23 16:58:20 +00:00
public bool ForceDMG { get ; set ; }
2014-07-20 00:16:40 +00:00
[DisplayName("CGB in GBA")]
2014-05-12 04:08:22 +00:00
[Description("Emulate GBA hardware running a CGB game, instead of CGB hardware. Relevant only for titles that detect the presense of a GBA, such as Shantae.")]
2014-05-12 17:24:43 +00:00
[DefaultValue(false)]
2013-12-23 16:58:20 +00:00
public bool GBACGB { get ; set ; }
2014-07-20 00:16:40 +00:00
[DisplayName("Multicart Compatibility")]
2014-05-12 04:08:22 +00:00
[Description("Use special compatibility hacks for certain multicart games. Relevant only for specific multicarts.")]
2014-05-12 17:24:43 +00:00
[DefaultValue(false)]
2013-12-23 16:58:20 +00:00
public bool MulticartCompat { get ; set ; }
2014-07-20 00:16:40 +00:00
[DisplayName("Realtime RTC")]
2014-05-12 17:24:43 +00:00
[Description("If true, the real time clock in MBC3 games will reflect real time, instead of emulated time. Ignored (treated as false) when a movie is recording.")]
[DefaultValue(false)]
public bool RealTimeRTC { get ; set ; }
2014-07-20 00:16:40 +00:00
[DisplayName("RTC Initial Time")]
2014-05-12 17:24:43 +00:00
[Description("Set the initial RTC time in terms of elapsed seconds. Only used when RealTimeRTC is false.")]
[DefaultValue(0)]
public int RTCInitialTime
{
get { return _RTCInitialTime ; }
set { _RTCInitialTime = Math . Max ( 0 , Math . Min ( 1024 * 24 * 60 * 60 , value ) ) ; }
}
2014-07-20 00:16:40 +00:00
2014-05-12 17:24:43 +00:00
[JsonIgnore]
int _RTCInitialTime ;
2013-12-23 02:51:41 +00:00
2014-07-14 16:10:45 +00:00
public GambatteSyncSettings ( )
2013-12-23 02:51:41 +00:00
{
2014-07-14 16:10:45 +00:00
SettingsUtil . SetDefaultValues ( this ) ;
2013-12-23 02:51:41 +00:00
}
public GambatteSyncSettings Clone ( )
{
return ( GambatteSyncSettings ) MemberwiseClone ( ) ;
}
}
2014-04-29 23:31:25 +00:00
#endregion
2012-09-08 21:36:04 +00:00
}
}