2006-02-25 20:50:29 +00:00
/*
AppleWin : An Apple //e emulator for Windows
Copyright ( C ) 1994 - 1996 , Michael O ' Brien
Copyright ( C ) 1999 - 2001 , Oliver Schmidt
Copyright ( C ) 2002 - 2005 , Tom Charlesworth
2007-04-01 15:24:52 +00:00
Copyright ( C ) 2006 - 2007 , Tom Charlesworth , Michael Pohoreski
2006-02-25 20:50:29 +00:00
AppleWin is free software ; you can redistribute it and / or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation ; either version 2 of the License , or
( at your option ) any later version .
AppleWin is distributed in the hope that it will be useful ,
but WITHOUT ANY WARRANTY ; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
GNU General Public License for more details .
You should have received a copy of the GNU General Public License
along with AppleWin ; if not , write to the Free Software
Foundation , Inc . , 59 Temple Place , Suite 330 , Boston , MA 02111 - 1307 USA
*/
/* Description: Mockingboard/Phasor emulation
*
* Author : Copyright ( c ) 2002 - 2006 , Tom Charlesworth
*/
// History:
//
// v1.12.07.1 (30 Dec 2005)
// - Update 6522 TIMERs after every 6502 opcode, giving more precise IRQs
// - Minimum TIMER freq is now 0x100 cycles
// - Added Phasor support
//
// v1.12.06.1 (16 July 2005)
// - Reworked 6522's ORB -> AY8910 decoder
// - Changed MB output so L=All voices from AY0 & AY2 & R=All voices from AY1 & AY3
// - Added crude support for Votrax speech chip (by using SSI263 phonemes)
//
// v1.12.04.1 (14 Sep 2004)
// - Switch MB output from dual-mono to stereo.
// - Relaxed TIMER1 freq from ~62Hz (period=0x4000) to ~83Hz (period=0x3000).
//
// 25 Apr 2004:
// - Added basic support for the SSI263 speech chip
//
// 15 Mar 2004:
// - Switched to MAME's AY8910 emulation (includes envelope support)
//
// v1.12.03 (11 Jan 2004)
// - For free-running 6522 timer1 IRQ, reload with current ACCESS_TIMER1 value.
// (Fixes Ultima 4/5 playback speed problem.)
//
// v1.12.01 (24 Nov 2002)
// - Shaped the tone waveform more logarithmically
// - Added support for MB ena/dis switch on Config dialog
// - Added log file support
//
// v1.12.00 (17 Nov 2002)
// - Initial version (no AY8910 envelope support)
//
// Notes on Votrax chip (on original Mockingboards):
// From Crimewave (Penguin Software):
// . Init:
// . DDRB = 0xFF
// . PCR = 0xB0
// . IER = 0x90
// . ORB = 0x03 (PAUSE0) or 0x3F (STOP)
// . IRQ:
// . ORB = Phoneme value
// . IRQ last phoneme complete:
// . IER = 0x10
// . ORB = 0x3F (STOP)
//
# include "StdAfx.h"
2020-11-11 21:15:27 +00:00
# include "Mockingboard.h"
2015-02-13 22:40:53 +00:00
# include "SaveState_Structs_v1.h"
2020-11-26 21:50:06 +00:00
# include "Core.h"
2019-12-19 19:42:30 +00:00
# include "CardManager.h"
2014-08-13 20:30:35 +00:00
# include "CPU.h"
# include "Log.h"
# include "Memory.h"
# include "SoundCore.h"
2020-10-11 15:08:05 +00:00
# include "SynchronousEventManager.h"
2015-12-05 16:50:27 +00:00
# include "YamlHelper.h"
2020-07-01 20:08:18 +00:00
# include "Riff.h"
2014-08-13 20:30:35 +00:00
2009-01-09 23:27:29 +00:00
# include "AY8910.h"
2021-03-23 22:01:41 +00:00
# include "SSI263.h"
2014-08-13 20:30:35 +00:00
2021-05-03 18:58:13 +00:00
# define DBG_MB_SS_CARD 0 // From UI, select Mockingboard (not Phasor)
2006-02-25 20:50:29 +00:00
# define SY6522_DEVICE_A 0
# define SY6522_DEVICE_B 1
# define NUM_MB 2
# define NUM_DEVS_PER_MB 2
# define NUM_AY8910 (NUM_MB*NUM_DEVS_PER_MB)
# define NUM_SY6522 NUM_AY8910
# define NUM_VOICES_PER_AY8910 3
# define NUM_VOICES (NUM_AY8910*NUM_VOICES_PER_AY8910)
// Chip offsets from card base.
# define SY6522A_Offset 0x00
# define SY6522B_Offset 0x80
2021-03-23 22:01:41 +00:00
# define SSI263B_Offset 0x20
# define SSI263A_Offset 0x40
2006-02-25 20:50:29 +00:00
2020-10-11 15:08:05 +00:00
//#define Phasor_SY6522A_CS 4
//#define Phasor_SY6522B_CS 7
//#define Phasor_SY6522A_Offset (1<<Phasor_SY6522A_CS)
//#define Phasor_SY6522B_Offset (1<<Phasor_SY6522B_CS)
2006-02-25 20:50:29 +00:00
2017-10-27 10:10:15 +00:00
enum MockingboardUnitState_e { AY_NOP0 , AY_NOP1 , AY_INACTIVE , AY_READ , AY_NOP4 , AY_NOP5 , AY_WRITE , AY_LATCH } ;
2015-04-11 21:24:54 +00:00
struct SY6522_AY8910
2006-02-25 20:50:29 +00:00
{
SY6522 sy6522 ;
BYTE nAY8910Number ;
BYTE nAYCurrentRegister ;
2017-10-21 17:47:22 +00:00
bool bTimer1Active ;
bool bTimer2Active ;
2021-03-23 22:01:41 +00:00
SSI263 ssi263 ;
2019-11-11 17:35:10 +00:00
MockingboardUnitState_e state ; // Where a unit is a 6522+AY8910 pair
MockingboardUnitState_e stateB ; // Phasor: 6522 & 2nd AY8910
2021-04-05 16:44:59 +00:00
// SSI263 has a constructor, and so SY6522_AY8910 needs one too
// memset(0) is not guaranteed to work
SY6522_AY8910 ( void )
{
memset ( & sy6522 , 0 , sizeof ( sy6522 ) ) ;
nAY8910Number = 0 ;
nAYCurrentRegister = 0 ;
bTimer1Active = false ;
bTimer2Active = false ;
state = AY_NOP0 ;
stateB = AY_NOP0 ;
// ssi263 has already been default constructed
}
2015-04-11 21:24:54 +00:00
} ;
2006-02-25 20:50:29 +00:00
// ACR:
# define RUNMODE (1<<6) // 0 = 1-Shot Mode, 1 = Free Running Mode
# define RM_ONESHOT (0<<6)
# define RM_FREERUNNING (1<<6)
// Support 2 MB's, each with 2x SY6522/AY8910 pairs.
static SY6522_AY8910 g_MB [ NUM_AY8910 ] ;
2020-10-11 15:08:05 +00:00
const UINT kExtraTimerCycles = 2 ; // Rockwell, Fig.16: period = N+2 cycles
const UINT kNumTimersPer6522 = 2 ;
const UINT kNumSyncEvents = NUM_MB * NUM_SY6522 * kNumTimersPer6522 ;
static SyncEvent * g_syncEvent [ kNumSyncEvents ] ;
2006-02-25 20:50:29 +00:00
// Timer vars
2017-10-24 21:28:22 +00:00
static const UINT kTIMERDEVICE_INVALID = - 1 ;
static UINT g_nMBTimerDevice = kTIMERDEVICE_INVALID ; // SY6522 device# which is generating timer IRQ
2007-08-06 21:38:35 +00:00
static UINT64 g_uLastCumulativeCycles = 0 ;
2006-02-25 20:50:29 +00:00
static const DWORD SAMPLE_RATE = 44100 ; // Use a base freq so that DirectX (or sound h/w) doesn't have to up/down-sample
static short * ppAYVoiceBuffer [ NUM_VOICES ] = { 0 } ;
static unsigned __int64 g_nMB_InActiveCycleCount = 0 ;
static bool g_bMB_RegAccessedFlag = false ;
2009-01-09 23:27:29 +00:00
static bool g_bMB_Active = false ;
2006-02-25 20:50:29 +00:00
static bool g_bMBAvailable = false ;
//
2012-03-20 23:17:06 +00:00
static SS_CARDTYPE g_SoundcardType = CT_Empty ; // Use CT_Empty to mean: no soundcard
2006-02-25 20:50:29 +00:00
static bool g_bPhasorEnable = false ;
2020-04-19 20:00:37 +00:00
static PHASOR_MODE g_phasorMode = PH_Mockingboard ;
2015-04-11 21:24:54 +00:00
static UINT g_PhasorClockScaleFactor = 1 ; // for save-state only
2006-02-25 20:50:29 +00:00
//-------------------------------------
static const unsigned short g_nMB_NumChannels = 2 ;
2009-10-07 21:38:42 +00:00
static const DWORD g_dwDSBufferSize = MAX_SAMPLES * sizeof ( short ) * g_nMB_NumChannels ;
2006-02-25 20:50:29 +00:00
static const SHORT nWaveDataMin = ( SHORT ) 0x8000 ;
static const SHORT nWaveDataMax = ( SHORT ) 0x7FFF ;
static short g_nMixBuffer [ g_dwDSBufferSize / sizeof ( short ) ] ;
2021-03-23 22:01:41 +00:00
static VOICE MockingboardVoice ;
2006-02-25 20:50:29 +00:00
2019-11-10 15:52:07 +00:00
static UINT g_cyclesThisAudioFrame = 0 ;
2019-09-05 19:42:34 +00:00
2006-02-25 20:50:29 +00:00
//---------------------------------------------------------------------------
// Forward refs:
2020-10-11 15:08:05 +00:00
static int MB_SyncEventCallback ( int id , int cycles , ULONG uExecutedCycles ) ;
2006-02-25 20:50:29 +00:00
//---------------------------------------------------------------------------
2021-03-23 22:01:41 +00:00
void MB_Get6522IrqDescription ( std : : string & desc )
{
for ( UINT i = 0 ; i < NUM_AY8910 ; i + + )
{
if ( g_MB [ i ] . sy6522 . IFR & 0x80 )
{
if ( g_MB [ i ] . sy6522 . IFR & IxR_TIMER1 )
{
desc + = ( ( i & 1 ) = = 0 ) ? " A: " : " B: " ;
desc + = " TIMER1 " ;
}
if ( g_MB [ i ] . sy6522 . IFR & IxR_TIMER2 )
{
desc + = ( ( i & 1 ) = = 0 ) ? " A: " : " B: " ;
desc + = " TIMER2 " ;
}
if ( g_MB [ i ] . sy6522 . IFR & IxR_VOTRAX )
{
desc + = ( ( i & 1 ) = = 0 ) ? " A: " : " B: " ;
desc + = " VOTRAX " ;
}
if ( g_MB [ i ] . sy6522 . IFR & IxR_SSI263 )
{
desc + = ( ( i & 1 ) = = 0 ) ? " A: " : " B: " ;
desc + = " SSI263 " ;
}
}
}
}
//---------------------------------------------------------------------------
2017-10-21 17:47:22 +00:00
static void StartTimer1 ( SY6522_AY8910 * pMB )
2006-02-25 20:50:29 +00:00
{
2017-10-21 17:47:22 +00:00
pMB - > bTimer1Active = true ;
2006-02-25 20:50:29 +00:00
2017-10-24 21:28:22 +00:00
if ( pMB - > sy6522 . IER & IxR_TIMER1 ) // Using 6522 interrupt
g_nMBTimerDevice = pMB - > nAY8910Number ;
2019-09-05 19:42:34 +00:00
else if ( pMB - > sy6522 . ACR & RM_FREERUNNING ) // Polling 6522 IFR (GH#496)
2017-10-24 21:28:22 +00:00
g_nMBTimerDevice = pMB - > nAY8910Number ;
2017-10-21 17:47:22 +00:00
}
2006-02-25 20:50:29 +00:00
2017-10-21 17:47:22 +00:00
// The assumption was that timer1 was only active if IER.TIMER1=1
// . Not true, since IFR can be polled (with IER.TIMER1=0)
static void StartTimer1_LoadStateV1 ( SY6522_AY8910 * pMB )
{
if ( ( pMB - > sy6522 . IER & IxR_TIMER1 ) = = 0x00 )
return ;
pMB - > bTimer1Active = true ;
2006-02-25 20:50:29 +00:00
g_nMBTimerDevice = pMB - > nAY8910Number ;
}
2017-10-21 17:47:22 +00:00
static void StopTimer1 ( SY6522_AY8910 * pMB )
2006-02-25 20:50:29 +00:00
{
2017-10-21 17:47:22 +00:00
pMB - > bTimer1Active = false ;
2017-10-24 21:28:22 +00:00
g_nMBTimerDevice = kTIMERDEVICE_INVALID ;
}
//-----------------------------------------------------------------------------
static void StartTimer2 ( SY6522_AY8910 * pMB )
{
pMB - > bTimer2Active = true ;
// NB. Can't mimic StartTimer1() as that would stomp on global state
// TODO: Switch to per-device state
}
static void StopTimer2 ( SY6522_AY8910 * pMB )
{
pMB - > bTimer2Active = false ;
2006-02-25 20:50:29 +00:00
}
//-----------------------------------------------------------------------------
2021-02-13 16:56:29 +00:00
static void SY6522_Write ( BYTE nDevice , BYTE nReg , BYTE nValue ) ;
static void ResetSY6522 ( SY6522_AY8910 * pMB , const bool powerCycle )
2006-02-25 20:50:29 +00:00
{
2021-02-13 16:56:29 +00:00
if ( powerCycle )
{
memset ( & pMB - > sy6522 , 0 , sizeof ( SY6522 ) ) ;
pMB - > sy6522 . TIMER1_LATCH . w = 0xffff ; // Some random value (but pick $ffff so it's deterministic)
// . NB. if it's too small (< ~$0007) then MB detection routines will fail!
}
SY6522_Write ( pMB - > nAY8910Number , 0x0b , 0x00 ) ; // ACR = 0x00: T1 one-shot mode
SY6522_Write ( pMB - > nAY8910Number , 0x0d , 0x7f ) ; // IFR = 0x7F: de-assert any IRQs
SY6522_Write ( pMB - > nAY8910Number , 0x0e , 0x7f ) ; // IFE = 0x7F: disable all IRQs
2006-02-25 20:50:29 +00:00
2017-10-24 21:28:22 +00:00
StopTimer1 ( pMB ) ;
StopTimer2 ( pMB ) ;
2006-02-25 20:50:29 +00:00
pMB - > nAYCurrentRegister = 0 ;
2017-10-27 10:10:15 +00:00
pMB - > state = AY_INACTIVE ;
2019-11-11 17:35:10 +00:00
pMB - > stateB = AY_INACTIVE ;
2006-02-25 20:50:29 +00:00
}
//-----------------------------------------------------------------------------
2019-11-11 17:35:10 +00:00
static void AY8910_Write ( BYTE nDevice , BYTE /*nReg*/ , BYTE nValue , BYTE nAYDevice )
2006-02-25 20:50:29 +00:00
{
2009-01-09 23:27:29 +00:00
g_bMB_RegAccessedFlag = true ;
2006-02-25 20:50:29 +00:00
SY6522_AY8910 * pMB = & g_MB [ nDevice ] ;
2017-10-24 21:28:22 +00:00
if ( ( nValue & 4 ) = = 0 )
2006-02-25 20:50:29 +00:00
{
// RESET: Reset AY8910 only
AY8910_reset ( nDevice + 2 * nAYDevice ) ;
}
else
{
// Determine the AY8910 inputs
int nBDIR = ( nValue & 2 ) ? 1 : 0 ;
const int nBC2 = 1 ; // Hardwired to +5V
int nBC1 = nValue & 1 ;
2017-10-27 10:10:15 +00:00
MockingboardUnitState_e nAYFunc = ( MockingboardUnitState_e ) ( ( nBDIR < < 2 ) | ( nBC2 < < 1 ) | nBC1 ) ;
2019-11-11 17:35:10 +00:00
MockingboardUnitState_e & state = ( nAYDevice = = 0 ) ? pMB - > state : pMB - > stateB ; // GH#659
2006-02-25 20:50:29 +00:00
2019-11-11 17:35:10 +00:00
# if _DEBUG
if ( ! g_bPhasorEnable )
_ASSERT ( nAYDevice = = 0 ) ;
if ( nAYFunc = = AY_WRITE | | nAYFunc = = AY_LATCH )
_ASSERT ( state = = AY_INACTIVE ) ;
# endif
if ( state = = AY_INACTIVE ) // GH#320: functions only work from inactive state
2006-02-25 20:50:29 +00:00
{
2017-10-27 10:10:15 +00:00
switch ( nAYFunc )
{
case AY_INACTIVE : // 4: INACTIVE
break ;
2006-02-25 20:50:29 +00:00
2017-10-27 10:10:15 +00:00
case AY_READ : // 5: READ FROM PSG (need to set DDRA to input)
2021-05-16 18:19:04 +00:00
if ( g_bPhasorEnable & & g_phasorMode = = PH_EchoPlus )
pMB - > sy6522 . ORA = 0xff & ( pMB - > sy6522 . DDRA ^ 0xff ) ; // Phasor (Echo+ mode) doesn't support reading AY8913s - it just reads 1's for the input bits
2021-02-21 19:12:36 +00:00
else
pMB - > sy6522 . ORA = AYReadReg ( nDevice + 2 * nAYDevice , pMB - > nAYCurrentRegister ) & ( pMB - > sy6522 . DDRA ^ 0xff ) ;
2017-10-27 10:10:15 +00:00
break ;
2006-02-25 20:50:29 +00:00
2017-10-27 10:10:15 +00:00
case AY_WRITE : // 6: WRITE TO PSG
_AYWriteReg ( nDevice + 2 * nAYDevice , pMB - > nAYCurrentRegister , pMB - > sy6522 . ORA ) ;
break ;
2006-02-25 20:50:29 +00:00
2017-10-27 10:10:15 +00:00
case AY_LATCH : // 7: LATCH ADDRESS
// http://www.worldofspectrum.org/forums/showthread.php?t=23327
// Selecting an unused register number above 0x0f puts the AY into a state where
// any values written to the data/address bus are ignored, but can be read back
// within a few tens of thousands of cycles before they decay to zero.
if ( pMB - > sy6522 . ORA < = 0x0F )
pMB - > nAYCurrentRegister = pMB - > sy6522 . ORA & 0x0F ;
// else Pro-Mockingboard (clone from HK)
break ;
}
2006-02-25 20:50:29 +00:00
}
2017-10-27 10:10:15 +00:00
2019-11-11 17:35:10 +00:00
state = nAYFunc ;
2006-02-25 20:50:29 +00:00
}
}
2021-02-06 18:02:35 +00:00
// TODO: RMW opcodes: dec,inc,asl,lsr,rol,ror (abs16 & abs16,x) + 65C02 trb,tsb (abs16)
static UINT GetOpcodeCyclesForRead ( BYTE reg )
{
UINT opcodeCycles = 0 ;
BYTE opcode = 0 ;
bool abs16 = false ;
bool abs16x = false ;
bool abs16y = false ;
bool indx = false ;
bool indy = false ;
const BYTE opcodeMinus3 = mem [ ( regs . pc - 3 ) & 0xffff ] ;
const BYTE opcodeMinus2 = mem [ ( regs . pc - 2 ) & 0xffff ] ;
if ( ( ( opcodeMinus2 & 0x0f ) = = 0x01 ) & & ( ( opcodeMinus2 & 0x10 ) = = 0x00 ) ) // ora (zp,x), and (zp,x), ..., sbc (zp,x)
{
// NB. this is for read, so don't need to exclude 0x81 / sta (zp,x)
opcodeCycles = 6 ;
opcode = opcodeMinus2 ;
indx = true ;
}
else if ( ( ( opcodeMinus2 & 0x0f ) = = 0x01 ) & & ( ( opcodeMinus2 & 0x10 ) = = 0x10 ) ) // ora (zp),y, and (zp),y, ..., sbc (zp),y
{
// NB. this is for read, so don't need to exclude 0x91 / sta (zp),y
opcodeCycles = 5 ;
opcode = opcodeMinus2 ;
indy = true ;
}
else if ( ( ( opcodeMinus2 & 0x0f ) = = 0x02 ) & & ( ( opcodeMinus2 & 0x10 ) = = 0x10 ) & & GetMainCpu ( ) = = CPU_65C02 ) // ora (zp), and (zp), ..., sbc (zp) : 65C02-only
{
// NB. this is for read, so don't need to exclude 0x92 / sta (zp)
opcodeCycles = 5 ;
opcode = opcodeMinus2 ;
}
else
{
if ( ( ( ( opcodeMinus3 & 0x0f ) = = 0x0D ) & & ( ( opcodeMinus3 & 0x10 ) = = 0x00 ) ) | | // ora abs16, and abs16, ..., sbc abs16
( opcodeMinus3 = = 0x2C ) | | // bit abs16
( opcodeMinus3 = = 0xAC ) | | // ldy abs16
( opcodeMinus3 = = 0xAE ) | | // ldx abs16
( opcodeMinus3 = = 0xCC ) | | // cpy abs16
( opcodeMinus3 = = 0xEC ) ) // cpx abs16
{
}
else if ( ( opcodeMinus3 = = 0xBC ) | | // ldy abs16,x
( ( opcodeMinus3 = = 0x3C ) & & GetMainCpu ( ) = = CPU_65C02 ) ) // bit abs16,x : 65C02-only
{
abs16x = true ;
}
else if ( ( opcodeMinus3 = = 0xBE ) ) // ldx abs16,y
{
abs16y = true ;
}
else if ( ( opcodeMinus3 & 0x10 ) = = 0x10 )
{
if ( ( opcodeMinus3 & 0x0f ) = = 0x0D ) // ora abs16,x, and abs16,x, ..., sbc abs16,x
abs16x = true ;
else if ( ( opcodeMinus3 & 0x0f ) = = 0x09 ) // ora abs16,y, and abs16,y, ..., sbc abs16,y
abs16y = true ;
}
else
{
_ASSERT ( 0 ) ;
opcodeCycles = 0 ;
return 0 ;
}
opcodeCycles = 4 ;
opcode = opcodeMinus3 ;
abs16 = true ;
}
//
WORD addr16 = 0 ;
if ( ! abs16 )
{
BYTE zp = mem [ ( regs . pc - 1 ) & 0xffff ] ;
if ( indx ) zp + = regs . x ;
addr16 = ( mem [ zp ] | ( mem [ ( zp + 1 ) & 0xff ] < < 8 ) ) ;
if ( indy ) addr16 + = regs . y ;
}
else
{
addr16 = mem [ ( regs . pc - 2 ) & 0xffff ] | ( mem [ ( regs . pc - 1 ) & 0xffff ] < < 8 ) ;
if ( abs16y ) addr16 + = regs . y ;
if ( abs16x ) addr16 + = regs . x ;
}
// Check we've reverse looked-up the 6502 opcode correctly
if ( ( addr16 & 0xF80F ) ! = ( 0xC000 + reg ) )
{
_ASSERT ( 0 ) ;
return 0 ;
}
return opcodeCycles ;
}
// TODO: RMW opcodes: dec,inc,asl,lsr,rol,ror (abs16 & abs16,x) + 65C02 trb,tsb (abs16)
static UINT GetOpcodeCyclesForWrite ( BYTE reg )
2020-10-11 15:08:05 +00:00
{
UINT opcodeCycles = 0 ;
BYTE opcode = 0 ;
bool abs16 = false ;
const BYTE opcodeMinus3 = mem [ ( regs . pc - 3 ) & 0xffff ] ;
const BYTE opcodeMinus2 = mem [ ( regs . pc - 2 ) & 0xffff ] ;
if ( ( opcodeMinus3 = = 0x8C ) | | // sty abs16
( opcodeMinus3 = = 0x8D ) | | // sta abs16
( opcodeMinus3 = = 0x8E ) ) // stx abs16
{ // Eg. FT demos: CHIP, MADEF, MAD2
opcodeCycles = 4 ;
opcode = opcodeMinus3 ;
abs16 = true ;
}
else if ( ( opcodeMinus3 = = 0x99 ) | | // sta abs16,y
( opcodeMinus3 = = 0x9D ) ) // sta abs16,x
{ // Eg. Paleotronic microTracker demo
opcodeCycles = 5 ;
opcode = opcodeMinus3 ;
abs16 = true ;
}
else if ( opcodeMinus2 = = 0x81 ) // sta (zp,x)
{
opcodeCycles = 6 ;
opcode = opcodeMinus2 ;
}
else if ( opcodeMinus2 = = 0x91 ) // sta (zp),y
{ // Eg. FT demos: OMT, PLS
opcodeCycles = 6 ;
opcode = opcodeMinus2 ;
}
else if ( opcodeMinus2 = = 0x92 & & GetMainCpu ( ) = = CPU_65C02 ) // sta (zp) : 65C02-only
{
opcodeCycles = 5 ;
opcode = opcodeMinus2 ;
}
2021-02-05 22:30:05 +00:00
else if ( opcodeMinus3 = = 0x9C & & GetMainCpu ( ) = = CPU_65C02 ) // stz abs16 : 65C02-only
{
opcodeCycles = 4 ;
opcode = opcodeMinus3 ;
abs16 = true ;
}
else if ( opcodeMinus3 = = 0x9E & & GetMainCpu ( ) = = CPU_65C02 ) // stz abs16,x : 65C02-only
{
opcodeCycles = 5 ;
opcode = opcodeMinus3 ;
abs16 = true ;
}
2020-10-11 15:08:05 +00:00
else
{
_ASSERT ( 0 ) ;
opcodeCycles = 0 ;
return 0 ;
}
//
WORD addr16 = 0 ;
if ( ! abs16 )
{
BYTE zp = mem [ ( regs . pc - 1 ) & 0xffff ] ;
if ( opcode = = 0x81 ) zp + = regs . x ;
addr16 = ( mem [ zp ] | ( mem [ ( zp + 1 ) & 0xff ] < < 8 ) ) ;
if ( opcode = = 0x91 ) addr16 + = regs . y ;
}
else
{
addr16 = mem [ ( regs . pc - 2 ) & 0xffff ] | ( mem [ ( regs . pc - 1 ) & 0xffff ] < < 8 ) ;
if ( opcode = = 0x99 ) addr16 + = regs . y ;
2021-02-05 22:30:05 +00:00
if ( opcode = = 0x9D | | opcode = = 0x9E ) addr16 + = regs . x ;
2020-10-11 15:08:05 +00:00
}
// Check we've reverse looked-up the 6502 opcode correctly
if ( ( addr16 & 0xF80F ) ! = ( 0xC000 + reg ) )
{
_ASSERT ( 0 ) ;
return 0 ;
}
return opcodeCycles ;
}
// Insert a new synchronous event whenever the 6522 timer's counter is written.
// . NB. it doesn't matter if the timer's interrupt enable (IER) is set or not
// - the state of IER is only important when the counter underflows - see: MB_SyncEventCallback()
static USHORT SetTimerSyncEvent ( UINT id , BYTE reg , USHORT timerLatch )
{
// NB. This TIMER adjustment value gets subtracted when this current opcode completes, so no need to persist to save-state
2021-02-06 18:02:35 +00:00
const UINT opcodeCycleAdjust = GetOpcodeCyclesForWrite ( reg ) ;
2020-10-11 15:08:05 +00:00
SyncEvent * pSyncEvent = g_syncEvent [ id ] ;
if ( pSyncEvent - > m_active )
g_SynchronousEventMgr . Remove ( id ) ;
pSyncEvent - > SetCycles ( timerLatch + kExtraTimerCycles + opcodeCycleAdjust ) ;
g_SynchronousEventMgr . Insert ( pSyncEvent ) ;
// It doesn't matter if this overflows (ie. >0xFFFF), since on completion of current opcode it'll be corrected
return ( USHORT ) ( timerLatch + opcodeCycleAdjust ) ;
}
2017-10-28 17:39:45 +00:00
static void UpdateIFR ( SY6522_AY8910 * pMB , BYTE clr_ifr , BYTE set_ifr = 0 )
2006-02-25 20:50:29 +00:00
{
2021-04-10 15:26:51 +00:00
pMB - > sy6522 . IFR & = ~ clr_ifr ;
pMB - > sy6522 . IFR | = set_ifr ;
2006-02-25 20:50:29 +00:00
2021-04-10 15:26:51 +00:00
if ( pMB - > sy6522 . IFR & pMB - > sy6522 . IER & 0x7F )
pMB - > sy6522 . IFR | = 0x80 ;
else
pMB - > sy6522 . IFR & = 0x7F ;
2006-05-02 21:56:28 +00:00
// Now update the IRQ signal from all 6522s
// . OR-sum of all active TIMER1, TIMER2 & SPEECH sources (from all 6522s)
UINT bIRQ = 0 ;
2017-10-24 21:28:22 +00:00
for ( UINT i = 0 ; i < NUM_SY6522 ; i + + )
2006-05-02 21:56:28 +00:00
bIRQ | = g_MB [ i ] . sy6522 . IFR & 0x80 ;
2006-12-27 19:11:19 +00:00
// NB. Mockingboard generates IRQ on both 6522s:
2021-02-21 19:12:36 +00:00
// . SSI263's IRQ (A/!R) is routed via the 2nd 6522 (at $Cn80) and must generate a 6502 IRQ (not NMI)
// - NB. 2nd SSI263's IRQ is routed via the 1st 6522 (at $Cn00) and again generates a 6502 IRQ
// . SC-01's IRQ (A/!R) is routed via the 6522 at $Cn00 (NB. Only the Mockingboard "Sound/Speech I" card supports the SC-01)
2020-04-19 20:00:37 +00:00
// Phasor's SSI263 IRQ (A/!R) line is *also* wired directly to the 6502's IRQ (as well as the 6522's CA1)
2006-12-27 19:11:19 +00:00
2006-05-02 21:56:28 +00:00
if ( bIRQ )
2006-12-27 19:11:19 +00:00
CpuIrqAssert ( IS_6522 ) ;
2006-05-02 21:56:28 +00:00
else
2006-12-27 19:11:19 +00:00
CpuIrqDeassert ( IS_6522 ) ;
2006-02-25 20:50:29 +00:00
}
static void SY6522_Write ( BYTE nDevice , BYTE nReg , BYTE nValue )
{
g_bMB_Active = true ;
SY6522_AY8910 * pMB = & g_MB [ nDevice ] ;
switch ( nReg )
{
case 0x00 : // ORB
{
nValue & = pMB - > sy6522 . DDRB ;
pMB - > sy6522 . ORB = nValue ;
2021-03-28 21:16:34 +00:00
if ( ( nDevice & 1 ) = = 0 & & // SC01 only at $Cn00 (not $Cn80)
2021-05-16 18:49:34 +00:00
pMB - > sy6522 . PCR = = 0xB0 )
2006-02-25 20:50:29 +00:00
{
// Votrax speech data
2021-05-16 18:49:34 +00:00
pMB - > ssi263 . Votrax_Write ( ( nValue & pMB - > sy6522 . DDRB ) | ( pMB - > sy6522 . DDRB ^ 0xff ) ) ; // DDRB's zero bits (inputs) are high impedence, so output as 1 (GH#952)
2006-02-25 20:50:29 +00:00
break ;
}
2021-05-03 18:58:13 +00:00
# if DBG_MB_SS_CARD
if ( ( nDevice & 1 ) = = 1 )
AY8910_Write ( nDevice , nReg , nValue , 0 ) ;
# else
2006-02-25 20:50:29 +00:00
if ( g_bPhasorEnable )
{
2020-04-19 20:00:37 +00:00
int nAY_CS = ( g_phasorMode = = PH_Phasor ) ? ( ~ ( nValue > > 3 ) & 3 ) : 1 ;
2006-02-25 20:50:29 +00:00
if ( nAY_CS & 1 )
AY8910_Write ( nDevice , nReg , nValue , 0 ) ;
if ( nAY_CS & 2 )
AY8910_Write ( nDevice , nReg , nValue , 1 ) ;
}
else
{
AY8910_Write ( nDevice , nReg , nValue , 0 ) ;
}
2021-05-03 18:58:13 +00:00
# endif
2006-02-25 20:50:29 +00:00
break ;
}
case 0x01 : // ORA
pMB - > sy6522 . ORA = nValue & pMB - > sy6522 . DDRA ;
break ;
case 0x02 : // DDRB
pMB - > sy6522 . DDRB = nValue ;
break ;
case 0x03 : // DDRA
pMB - > sy6522 . DDRA = nValue ;
break ;
case 0x04 : // TIMER1L_COUNTER
case 0x06 : // TIMER1L_LATCH
pMB - > sy6522 . TIMER1_LATCH . l = nValue ;
break ;
case 0x05 : // TIMER1H_COUNTER
2020-10-11 15:08:05 +00:00
{
UpdateIFR ( pMB , IxR_TIMER1 ) ; // Clear Timer1 Interrupt Flag
pMB - > sy6522 . TIMER1_LATCH . h = nValue ;
const UINT id = nDevice * kNumTimersPer6522 + 0 ; // TIMER1
pMB - > sy6522 . TIMER1_COUNTER . w = SetTimerSyncEvent ( id , nReg , pMB - > sy6522 . TIMER1_LATCH . w ) ;
StartTimer1 ( pMB ) ;
}
2006-02-25 20:50:29 +00:00
break ;
case 0x07 : // TIMER1H_LATCH
2020-10-11 15:08:05 +00:00
UpdateIFR ( pMB , IxR_TIMER1 ) ; // Clear Timer1 Interrupt Flag
2006-02-25 20:50:29 +00:00
pMB - > sy6522 . TIMER1_LATCH . h = nValue ;
break ;
case 0x08 : // TIMER2L
pMB - > sy6522 . TIMER2_LATCH . l = nValue ;
break ;
case 0x09 : // TIMER2H
2020-10-11 15:08:05 +00:00
{
UpdateIFR ( pMB , IxR_TIMER2 ) ; // Clear Timer2 Interrupt Flag
pMB - > sy6522 . TIMER2_LATCH . h = nValue ; // NB. Real 6522 doesn't have TIMER2_LATCH.h
const UINT id = nDevice * kNumTimersPer6522 + 1 ; // TIMER2
pMB - > sy6522 . TIMER2_COUNTER . w = SetTimerSyncEvent ( id , nReg , pMB - > sy6522 . TIMER2_LATCH . w ) ;
StartTimer2 ( pMB ) ;
}
2006-02-25 20:50:29 +00:00
break ;
case 0x0a : // SERIAL_SHIFT
break ;
case 0x0b : // ACR
pMB - > sy6522 . ACR = nValue ;
break ;
case 0x0c : // PCR - Used for Speech chip only
pMB - > sy6522 . PCR = nValue ;
break ;
case 0x0d : // IFR
// - Clear those bits which are set in the lower 7 bits.
// - Can't clear bit 7 directly.
2017-10-28 17:39:45 +00:00
UpdateIFR ( pMB , nValue ) ;
2006-02-25 20:50:29 +00:00
break ;
case 0x0e : // IER
if ( ! ( nValue & 0x80 ) )
{
// Clear those bits which are set in the lower 7 bits.
nValue ^ = 0x7F ;
pMB - > sy6522 . IER & = nValue ;
}
else
{
// Set those bits which are set in the lower 7 bits.
nValue & = 0x7F ;
pMB - > sy6522 . IER | = nValue ;
}
2020-03-07 18:08:50 +00:00
UpdateIFR ( pMB , 0 ) ;
2006-02-25 20:50:29 +00:00
break ;
case 0x0f : // ORA_NO_HS
break ;
}
}
//-----------------------------------------------------------------------------
2021-02-12 19:16:14 +00:00
static bool CheckTimerUnderflow ( USHORT & counter , int & timerIrqDelay , const USHORT nClocks ) ;
static int OnTimer1Underflow ( USHORT & counter , USHORT latch ) ;
static USHORT GetTimer1Counter ( BYTE reg , USHORT counter , USHORT latch , int timerIrqDelay )
2021-02-11 22:44:40 +00:00
{
const UINT opcodeCycleAdjust = GetOpcodeCyclesForRead ( reg ) - 1 ; // to compensate for the 4/5/6 cycle read opcode
2021-02-12 19:16:14 +00:00
if ( CheckTimerUnderflow ( counter , timerIrqDelay , opcodeCycleAdjust ) )
OnTimer1Underflow ( counter , latch ) ;
return counter ;
2021-02-11 22:44:40 +00:00
}
static USHORT GetTimer2Counter ( BYTE reg , USHORT counter )
{
const UINT opcodeCycleAdjust = GetOpcodeCyclesForRead ( reg ) - 1 ; // to compensate for the 4/5/6 cycle read opcode
return counter - opcodeCycleAdjust ;
}
2021-04-30 21:23:20 +00:00
static bool IsTimer1Underflowed ( BYTE reg , USHORT counter , USHORT latch , int timerIrqDelay )
{
const UINT opcodeCycleAdjust = GetOpcodeCyclesForRead ( reg ) ; // to compensate for the 4/5/6 cycle read opcode
return CheckTimerUnderflow ( counter , timerIrqDelay , opcodeCycleAdjust ) ;
}
static bool IsTimer2Underflowed ( BYTE reg , USHORT counter )
{
return counter > = 0 & & ( short ) GetTimer2Counter ( reg , counter ) < 0 ;
}
2006-02-25 20:50:29 +00:00
static BYTE SY6522_Read ( BYTE nDevice , BYTE nReg )
{
g_bMB_Active = true ;
SY6522_AY8910 * pMB = & g_MB [ nDevice ] ;
BYTE nValue = 0x00 ;
switch ( nReg )
{
case 0x00 : // ORB
nValue = pMB - > sy6522 . ORB ;
break ;
case 0x01 : // ORA
nValue = pMB - > sy6522 . ORA ;
break ;
case 0x02 : // DDRB
nValue = pMB - > sy6522 . DDRB ;
break ;
case 0x03 : // DDRA
nValue = pMB - > sy6522 . DDRA ;
break ;
case 0x04 : // TIMER1L_COUNTER
2021-02-11 22:44:40 +00:00
// NB. GH#701 (T1C:=0xFFFF, LDA T1C_L[4cy], A==0xFC)
2021-02-12 19:16:14 +00:00
nValue = GetTimer1Counter ( nReg , pMB - > sy6522 . TIMER1_COUNTER . w , pMB - > sy6522 . TIMER1_LATCH . w , pMB - > sy6522 . timer1IrqDelay ) & 0xff ;
2021-02-11 22:44:40 +00:00
UpdateIFR ( pMB , IxR_TIMER1 ) ;
2006-02-25 20:50:29 +00:00
break ;
case 0x05 : // TIMER1H_COUNTER
2021-02-12 19:16:14 +00:00
nValue = GetTimer1Counter ( nReg , pMB - > sy6522 . TIMER1_COUNTER . w , pMB - > sy6522 . TIMER1_LATCH . w , pMB - > sy6522 . timer1IrqDelay ) > > 8 ;
2006-02-25 20:50:29 +00:00
break ;
case 0x06 : // TIMER1L_LATCH
nValue = pMB - > sy6522 . TIMER1_LATCH . l ;
break ;
case 0x07 : // TIMER1H_LATCH
nValue = pMB - > sy6522 . TIMER1_LATCH . h ;
break ;
case 0x08 : // TIMER2L
2021-02-11 22:44:40 +00:00
nValue = GetTimer2Counter ( nReg , pMB - > sy6522 . TIMER2_COUNTER . w ) & 0xff ;
UpdateIFR ( pMB , IxR_TIMER2 ) ;
2006-02-25 20:50:29 +00:00
break ;
case 0x09 : // TIMER2H
2021-02-11 22:44:40 +00:00
nValue = GetTimer2Counter ( nReg , pMB - > sy6522 . TIMER2_COUNTER . w ) > > 8 ;
2006-02-25 20:50:29 +00:00
break ;
case 0x0a : // SERIAL_SHIFT
break ;
case 0x0b : // ACR
nValue = pMB - > sy6522 . ACR ;
break ;
case 0x0c : // PCR
nValue = pMB - > sy6522 . PCR ;
break ;
case 0x0d : // IFR
nValue = pMB - > sy6522 . IFR ;
2021-04-30 21:23:20 +00:00
if ( pMB - > bTimer1Active & & IsTimer1Underflowed ( nReg , pMB - > sy6522 . TIMER1_COUNTER . w , pMB - > sy6522 . TIMER1_LATCH . w , pMB - > sy6522 . timer1IrqDelay ) )
nValue | = IxR_TIMER1 ;
if ( pMB - > bTimer2Active & & IsTimer2Underflowed ( nReg , pMB - > sy6522 . TIMER2_COUNTER . w ) )
nValue | = IxR_TIMER2 ;
2006-02-25 20:50:29 +00:00
break ;
case 0x0e : // IER
2018-06-17 17:47:57 +00:00
nValue = 0x80 | pMB - > sy6522 . IER ; // GH#567
2006-02-25 20:50:29 +00:00
break ;
case 0x0f : // ORA_NO_HS
nValue = pMB - > sy6522 . ORA ;
break ;
}
return nValue ;
}
//===========================================================================
2019-11-10 15:52:07 +00:00
//#define DBG_MB_UPDATE
static UINT64 g_uLastMBUpdateCycle = 0 ;
2017-10-24 21:28:22 +00:00
// Called by:
// . MB_UpdateCycles() - when g_nMBTimerDevice == {0,1,2,3}
2019-11-10 15:52:07 +00:00
// . MB_PeriodicUpdate() - when g_nMBTimerDevice == kTIMERDEVICE_INVALID
2020-05-23 15:23:06 +00:00
static void MB_UpdateInt ( void )
2006-02-25 20:50:29 +00:00
{
2009-01-09 23:27:29 +00:00
if ( ! MockingboardVoice . bActive )
return ;
if ( g_bFullSpeed )
{
// Keep AY reg writes relative to the current 'frame'
// - Required for Ultima3:
// . Tune ends
// . g_bFullSpeed:=true (disk-spinning) for ~50 frames
// . U3 sets AY_ENABLE:=0xFF (as a side-effect, this sets g_bFullSpeed:=false)
// o Without this, the write to AY_ENABLE gets ignored (since AY8910's /g_uLastCumulativeCycles/ was last set 50 frame ago)
AY8910UpdateSetCycles ( ) ;
// TODO:
// If any AY regs have changed then push them out to the AY chip
2006-02-25 20:50:29 +00:00
return ;
2009-01-09 23:27:29 +00:00
}
2006-02-25 20:50:29 +00:00
//
2009-01-09 23:27:29 +00:00
if ( ! g_bMB_RegAccessedFlag )
2006-02-25 20:50:29 +00:00
{
if ( ! g_nMB_InActiveCycleCount )
{
g_nMB_InActiveCycleCount = g_nCumulativeCycles ;
}
else if ( g_nCumulativeCycles - g_nMB_InActiveCycleCount > ( unsigned __int64 ) g_fCurrentCLK6502 / 10 )
{
// After 0.1 sec of Apple time, assume MB is not active
g_bMB_Active = false ;
}
}
else
{
g_nMB_InActiveCycleCount = 0 ;
g_bMB_RegAccessedFlag = false ;
g_bMB_Active = true ;
}
//
2019-11-10 15:52:07 +00:00
// For small timer periods, wait for a period of 500cy before updating DirectSound ring-buffer.
// NB. A timer period of less than 24cy will yield nNumSamplesPerPeriod=0.
const double kMinimumUpdateInterval = 500.0 ; // Arbitary (500 cycles = 21 samples)
const double kMaximumUpdateInterval = ( double ) ( 0xFFFF + 2 ) ; // Max 6522 timer interval (2756 samples)
if ( g_uLastMBUpdateCycle = = 0 )
g_uLastMBUpdateCycle = g_uLastCumulativeCycles ; // Initial call to MB_Update() after reset/power-cycle
_ASSERT ( g_uLastCumulativeCycles > = g_uLastMBUpdateCycle ) ;
double updateInterval = ( double ) ( g_uLastCumulativeCycles - g_uLastMBUpdateCycle ) ;
if ( updateInterval < kMinimumUpdateInterval )
return ;
if ( updateInterval > kMaximumUpdateInterval )
updateInterval = kMaximumUpdateInterval ;
2006-02-25 20:50:29 +00:00
2019-11-10 15:52:07 +00:00
g_uLastMBUpdateCycle = g_uLastCumulativeCycles ;
2006-02-25 20:50:29 +00:00
2019-11-10 15:52:07 +00:00
const double nIrqFreq = g_fCurrentCLK6502 / updateInterval + 0.5 ; // Round-up
2009-01-09 23:27:29 +00:00
const int nNumSamplesPerPeriod = ( int ) ( ( double ) SAMPLE_RATE / nIrqFreq ) ; // Eg. For 60Hz this is 735
2019-11-10 15:52:07 +00:00
static int nNumSamplesError = 0 ;
2009-01-09 23:27:29 +00:00
int nNumSamples = nNumSamplesPerPeriod + nNumSamplesError ; // Apply correction
2006-02-25 20:50:29 +00:00
if ( nNumSamples < = 0 )
nNumSamples = 0 ;
if ( nNumSamples > 2 * nNumSamplesPerPeriod )
nNumSamples = 2 * nNumSamplesPerPeriod ;
2021-07-28 11:22:52 +00:00
if ( nNumSamples > MAX_SAMPLES )
nNumSamples = MAX_SAMPLES ; // Clamp to prevent buffer overflow
2019-11-10 15:52:07 +00:00
2006-02-25 20:50:29 +00:00
if ( nNumSamples )
for ( int nChip = 0 ; nChip < NUM_AY8910 ; nChip + + )
AY8910Update ( nChip , & ppAYVoiceBuffer [ nChip * NUM_VOICES_PER_AY8910 ] , nNumSamples ) ;
//
DWORD dwCurrentPlayCursor , dwCurrentWriteCursor ;
HRESULT hr = MockingboardVoice . lpDSBvoice - > GetCurrentPosition ( & dwCurrentPlayCursor , & dwCurrentWriteCursor ) ;
if ( FAILED ( hr ) )
return ;
2019-11-10 15:52:07 +00:00
static DWORD dwByteOffset = ( DWORD ) - 1 ;
2006-02-25 20:50:29 +00:00
if ( dwByteOffset = = ( DWORD ) - 1 )
{
// First time in this func
dwByteOffset = dwCurrentWriteCursor ;
}
else
{
// Check that our offset isn't between Play & Write positions
if ( dwCurrentWriteCursor > dwCurrentPlayCursor )
{
// |-----PxxxxxW-----|
if ( ( dwByteOffset > dwCurrentPlayCursor ) & & ( dwByteOffset < dwCurrentWriteCursor ) )
2009-10-07 21:38:42 +00:00
{
2019-11-10 15:52:07 +00:00
# ifdef DBG_MB_UPDATE
2009-10-07 21:38:42 +00:00
double fTicksSecs = ( double ) GetTickCount ( ) / 1000.0 ;
2019-11-10 15:52:07 +00:00
LogOutput ( " %010.3f: [MBUpdt] PC=%08X, WC=%08X, Diff=%08X, Off=%08X, NS=%08X xxx \n " , fTicksSecs , dwCurrentPlayCursor , dwCurrentWriteCursor , dwCurrentWriteCursor - dwCurrentPlayCursor , dwByteOffset , nNumSamples ) ;
# endif
2006-02-25 20:50:29 +00:00
dwByteOffset = dwCurrentWriteCursor ;
2019-11-10 15:52:07 +00:00
nNumSamplesError = 0 ;
2009-10-07 21:38:42 +00:00
}
2006-02-25 20:50:29 +00:00
}
else
{
// |xxW----------Pxxx|
if ( ( dwByteOffset > dwCurrentPlayCursor ) | | ( dwByteOffset < dwCurrentWriteCursor ) )
2009-10-07 21:38:42 +00:00
{
2019-11-10 15:52:07 +00:00
# ifdef DBG_MB_UPDATE
2009-10-07 21:38:42 +00:00
double fTicksSecs = ( double ) GetTickCount ( ) / 1000.0 ;
2019-11-10 15:52:07 +00:00
LogOutput ( " %010.3f: [MBUpdt] PC=%08X, WC=%08X, Diff=%08X, Off=%08X, NS=%08X XXX \n " , fTicksSecs , dwCurrentPlayCursor , dwCurrentWriteCursor , dwCurrentWriteCursor - dwCurrentPlayCursor , dwByteOffset , nNumSamples ) ;
# endif
2006-02-25 20:50:29 +00:00
dwByteOffset = dwCurrentWriteCursor ;
2019-11-10 15:52:07 +00:00
nNumSamplesError = 0 ;
2009-10-07 21:38:42 +00:00
}
2006-02-25 20:50:29 +00:00
}
}
int nBytesRemaining = dwByteOffset - dwCurrentPlayCursor ;
if ( nBytesRemaining < 0 )
nBytesRemaining + = g_dwDSBufferSize ;
// Calc correction factor so that play-buffer doesn't under/overflow
2009-10-07 21:38:42 +00:00
const int nErrorInc = SoundCore_GetErrorInc ( ) ;
2006-02-25 20:50:29 +00:00
if ( nBytesRemaining < g_dwDSBufferSize / 4 )
2009-10-07 21:38:42 +00:00
nNumSamplesError + = nErrorInc ; // < 0.25 of buffer remaining
2006-02-25 20:50:29 +00:00
else if ( nBytesRemaining > g_dwDSBufferSize / 2 )
2009-10-07 21:38:42 +00:00
nNumSamplesError - = nErrorInc ; // > 0.50 of buffer remaining
2006-02-25 20:50:29 +00:00
else
nNumSamplesError = 0 ; // Acceptable amount of data in buffer
2019-11-10 15:52:07 +00:00
# ifdef DBG_MB_UPDATE
double fTicksSecs = ( double ) GetTickCount ( ) / 1000.0 ;
LogOutput ( " %010.3f: [MBUpdt] PC=%08X, WC=%08X, Diff=%08X, Off=%08X, NS=%08X, NSE=%08X, Interval=%f \n " , fTicksSecs , dwCurrentPlayCursor , dwCurrentWriteCursor , dwCurrentWriteCursor - dwCurrentPlayCursor , dwByteOffset , nNumSamples , nNumSamplesError , updateInterval ) ;
# endif
2006-02-25 20:50:29 +00:00
if ( nNumSamples = = 0 )
return ;
//
2009-01-09 23:27:29 +00:00
const double fAttenuation = g_bPhasorEnable ? 2.0 / 3.0 : 1.0 ;
2006-02-25 20:50:29 +00:00
for ( int i = 0 ; i < nNumSamples ; i + + )
{
// Mockingboard stereo (all voices on an AY8910 wire-or'ed together)
// L = Address.b7=0, R = Address.b7=1
int nDataL = 0 , nDataR = 0 ;
2009-01-09 23:27:29 +00:00
for ( UINT j = 0 ; j < NUM_VOICES_PER_AY8910 ; j + + )
2006-02-25 20:50:29 +00:00
{
// Slot4
nDataL + = ( int ) ( ( double ) ppAYVoiceBuffer [ 0 * NUM_VOICES_PER_AY8910 + j ] [ i ] * fAttenuation ) ;
nDataR + = ( int ) ( ( double ) ppAYVoiceBuffer [ 1 * NUM_VOICES_PER_AY8910 + j ] [ i ] * fAttenuation ) ;
// Slot5
nDataL + = ( int ) ( ( double ) ppAYVoiceBuffer [ 2 * NUM_VOICES_PER_AY8910 + j ] [ i ] * fAttenuation ) ;
nDataR + = ( int ) ( ( double ) ppAYVoiceBuffer [ 3 * NUM_VOICES_PER_AY8910 + j ] [ i ] * fAttenuation ) ;
}
// Cap the superpositioned output
if ( nDataL < nWaveDataMin )
nDataL = nWaveDataMin ;
else if ( nDataL > nWaveDataMax )
nDataL = nWaveDataMax ;
if ( nDataR < nWaveDataMin )
nDataR = nWaveDataMin ;
else if ( nDataR > nWaveDataMax )
nDataR = nWaveDataMax ;
g_nMixBuffer [ i * g_nMB_NumChannels + 0 ] = ( short ) nDataL ; // L
g_nMixBuffer [ i * g_nMB_NumChannels + 1 ] = ( short ) nDataR ; // R
}
//
2019-11-10 15:52:07 +00:00
DWORD dwDSLockedBufferSize0 , dwDSLockedBufferSize1 ;
SHORT * pDSLockedBuffer0 , * pDSLockedBuffer1 ;
2021-05-16 19:03:59 +00:00
hr = DSGetLock ( MockingboardVoice . lpDSBvoice ,
dwByteOffset , ( DWORD ) nNumSamples * sizeof ( short ) * g_nMB_NumChannels ,
& pDSLockedBuffer0 , & dwDSLockedBufferSize0 ,
& pDSLockedBuffer1 , & dwDSLockedBufferSize1 ) ;
if ( FAILED ( hr ) )
2006-02-25 20:50:29 +00:00
return ;
memcpy ( pDSLockedBuffer0 , & g_nMixBuffer [ 0 ] , dwDSLockedBufferSize0 ) ;
if ( pDSLockedBuffer1 )
memcpy ( pDSLockedBuffer1 , & g_nMixBuffer [ dwDSLockedBufferSize0 / sizeof ( short ) ] , dwDSLockedBufferSize1 ) ;
// Commit sound buffer
hr = MockingboardVoice . lpDSBvoice - > Unlock ( ( void * ) pDSLockedBuffer0 , dwDSLockedBufferSize0 ,
2009-01-09 23:27:29 +00:00
( void * ) pDSLockedBuffer1 , dwDSLockedBufferSize1 ) ;
2006-02-25 20:50:29 +00:00
dwByteOffset = ( dwByteOffset + ( DWORD ) nNumSamples * sizeof ( short ) * g_nMB_NumChannels ) % g_dwDSBufferSize ;
# ifdef RIFF_MB
RiffPutSamples ( & g_nMixBuffer [ 0 ] , nNumSamples ) ;
# endif
}
2020-05-23 15:23:06 +00:00
static void MB_Update ( void )
{
# ifdef LOG_PERF_TIMINGS
extern UINT64 g_timeMB_NoTimer ;
extern UINT64 g_timeMB_Timer ;
PerfMarker perfMarker ( g_nMBTimerDevice = = kTIMERDEVICE_INVALID ? g_timeMB_NoTimer : g_timeMB_Timer ) ;
# endif
MB_UpdateInt ( ) ;
}
2006-02-25 20:50:29 +00:00
//-----------------------------------------------------------------------------
static bool MB_DSInit ( )
{
2013-03-23 13:34:01 +00:00
LogFileOutput ( " MB_DSInit \n " , g_bMBAvailable ) ;
2012-10-05 07:01:27 +00:00
# ifdef NO_DIRECT_X
return false ;
# else // NO_DIRECT_X
2006-02-25 20:50:29 +00:00
//
// Create single Mockingboard voice
//
if ( ! g_bDSAvailable )
return false ;
2021-03-23 22:01:41 +00:00
HRESULT hr = DSGetSoundBuffer ( & MockingboardVoice , DSBCAPS_CTRLVOLUME , g_dwDSBufferSize , SAMPLE_RATE , g_nMB_NumChannels , " MB " ) ;
2013-03-23 13:34:01 +00:00
LogFileOutput ( " MB_DSInit: DSGetSoundBuffer(), hr=0x%08X \n " , hr ) ;
2006-02-25 20:50:29 +00:00
if ( FAILED ( hr ) )
{
2021-03-23 22:01:41 +00:00
LogFileOutput ( " MB_DSInit: DSGetSoundBuffer failed (%08X) \n " , hr ) ;
2006-02-25 20:50:29 +00:00
return false ;
}
2021-03-23 22:01:41 +00:00
bool bRes = DSZeroVoiceBuffer ( & MockingboardVoice , g_dwDSBufferSize ) ;
2013-03-23 13:34:01 +00:00
LogFileOutput ( " MB_DSInit: DSZeroVoiceBuffer(), res=%d \n " , bRes ? 1 : 0 ) ;
if ( ! bRes )
2006-02-25 20:50:29 +00:00
return false ;
MockingboardVoice . bActive = true ;
// Volume might've been setup from value in Registry
if ( ! MockingboardVoice . nVolume )
MockingboardVoice . nVolume = DSBVOLUME_MAX ;
2013-03-23 13:34:01 +00:00
hr = MockingboardVoice . lpDSBvoice - > SetVolume ( MockingboardVoice . nVolume ) ;
LogFileOutput ( " MB_DSInit: SetVolume(), hr=0x%08X \n " , hr ) ;
2006-02-25 20:50:29 +00:00
//---------------------------------
2021-03-23 22:01:41 +00:00
for ( UINT i = 0 ; i < NUM_AY8910 ; i + + )
2006-02-25 20:50:29 +00:00
{
2021-03-23 22:01:41 +00:00
if ( ! g_MB [ i ] . ssi263 . DSInit ( ) )
2006-02-25 20:50:29 +00:00
return false ;
}
return true ;
2012-10-05 07:01:27 +00:00
# endif // NO_DIRECT_X
2006-02-25 20:50:29 +00:00
}
static void MB_DSUninit ( )
{
if ( MockingboardVoice . lpDSBvoice & & MockingboardVoice . bActive )
2021-03-23 22:01:41 +00:00
DSVoiceStop ( & MockingboardVoice ) ;
2006-02-25 20:50:29 +00:00
DSReleaseSoundBuffer ( & MockingboardVoice ) ;
//
2021-03-23 22:01:41 +00:00
for ( UINT i = 0 ; i < NUM_AY8910 ; i + + )
g_MB [ i ] . ssi263 . DSUninit ( ) ;
2006-02-25 20:50:29 +00:00
}
//=============================================================================
//
// ----- ALL GLOBALLY ACCESSIBLE FUNCTIONS ARE BELOW THIS LINE -----
//
//=============================================================================
2019-02-24 10:49:09 +00:00
static void InitSoundcardType ( void )
2019-02-23 10:22:52 +00:00
{
g_SoundcardType = CT_Empty ; // Use CT_Empty to mean: no soundcard
g_bPhasorEnable = false ;
}
2006-02-25 20:50:29 +00:00
void MB_Initialize ( )
{
2019-02-24 10:49:09 +00:00
InitSoundcardType ( ) ;
2019-02-23 10:22:52 +00:00
2013-03-23 13:34:01 +00:00
LogFileOutput ( " MB_Initialize: g_bDisableDirectSound=%d, g_bDisableDirectSoundMockingboard=%d \n " , g_bDisableDirectSound , g_bDisableDirectSoundMockingboard ) ;
2009-10-07 21:38:42 +00:00
if ( g_bDisableDirectSound | | g_bDisableDirectSoundMockingboard )
2006-02-25 20:50:29 +00:00
{
MockingboardVoice . bMute = true ;
}
else
{
2021-03-23 22:01:41 +00:00
for ( UINT i = 0 ; i < NUM_VOICES ; i + + )
2021-07-28 11:22:52 +00:00
ppAYVoiceBuffer [ i ] = new short [ MAX_SAMPLES ] ; // Buffer can hold a max of 0.37 seconds worth of samples (16384/44100)
2006-02-25 20:50:29 +00:00
AY8910_InitAll ( ( int ) g_fCurrentCLK6502 , SAMPLE_RATE ) ;
2013-03-23 13:34:01 +00:00
LogFileOutput ( " MB_Initialize: AY8910_InitAll() \n " ) ;
2006-02-25 20:50:29 +00:00
2021-03-23 22:01:41 +00:00
for ( UINT i = 0 ; i < NUM_AY8910 ; i + + )
{
2021-04-05 16:44:59 +00:00
g_MB [ i ] = SY6522_AY8910 ( ) ;
2006-02-25 20:50:29 +00:00
g_MB [ i ] . nAY8910Number = i ;
2021-03-23 22:01:41 +00:00
g_MB [ i ] . ssi263 . SetDevice ( i ) ;
}
2006-02-25 20:50:29 +00:00
//
g_bMBAvailable = MB_DSInit ( ) ;
2013-03-23 13:34:01 +00:00
LogFileOutput ( " MB_Initialize: MB_DSInit(), g_bMBAvailable=%d \n " , g_bMBAvailable ) ;
2006-02-25 20:50:29 +00:00
2021-02-13 16:56:29 +00:00
MB_Reset ( true ) ;
2013-03-23 13:34:01 +00:00
LogFileOutput ( " MB_Initialize: MB_Reset() \n " ) ;
2006-02-25 20:50:29 +00:00
}
2017-10-28 17:39:45 +00:00
2020-10-11 15:08:05 +00:00
for ( int id = 0 ; id < kNumSyncEvents ; id + + )
{
g_syncEvent [ id ] = new SyncEvent ( id , 0 , MB_SyncEventCallback ) ;
}
2006-02-25 20:50:29 +00:00
}
2020-06-27 13:32:09 +00:00
static void MB_SetSoundcardType ( SS_CARDTYPE NewSoundcardType ) ;
2019-02-24 15:37:15 +00:00
// NB. Mockingboard voice is *already* muted because showing 'Select Load State file' dialog
2021-03-23 22:01:41 +00:00
// . and voice will be unmuted when dialog is closed
2019-02-24 10:49:09 +00:00
void MB_InitializeForLoadingSnapshot ( ) // GH#609
{
2021-02-13 16:56:29 +00:00
MB_Reset ( true ) ;
2019-02-24 10:49:09 +00:00
InitSoundcardType ( ) ;
2019-11-03 14:23:47 +00:00
if ( g_bDisableDirectSound | | g_bDisableDirectSoundMockingboard )
return ;
_ASSERT ( MockingboardVoice . lpDSBvoice ) ;
2021-03-23 22:01:41 +00:00
DSVoiceStop ( & MockingboardVoice ) ; // Reason: 'MB voice is playing' then loading a save-state where 'no MB present'
// NB. ssi263.Stop() already done by MB_Reset()
2019-02-24 10:49:09 +00:00
}
2006-02-25 20:50:29 +00:00
//-----------------------------------------------------------------------------
// NB. Called when /g_fCurrentCLK6502/ changes
void MB_Reinitialize ( )
{
2015-04-11 21:24:54 +00:00
AY8910_InitClock ( ( int ) g_fCurrentCLK6502 ) ; // todo: account for g_PhasorClockScaleFactor?
// NB. Other calls to AY8910_InitClock() use the constant CLK_6502
2006-02-25 20:50:29 +00:00
}
//-----------------------------------------------------------------------------
void MB_Destroy ( )
{
MB_DSUninit ( ) ;
2017-10-28 17:39:45 +00:00
for ( int i = 0 ; i < NUM_VOICES ; i + + )
2006-02-25 20:50:29 +00:00
delete [ ] ppAYVoiceBuffer [ i ] ;
2017-10-28 17:39:45 +00:00
2020-10-11 15:08:05 +00:00
for ( int id = 0 ; id < kNumSyncEvents ; id + + )
{
2020-12-12 17:46:36 +00:00
if ( g_syncEvent [ id ] & & g_syncEvent [ id ] - > m_active )
g_SynchronousEventMgr . Remove ( id ) ;
2020-10-11 15:08:05 +00:00
delete g_syncEvent [ id ] ;
g_syncEvent [ id ] = NULL ;
}
2006-02-25 20:50:29 +00:00
}
//-----------------------------------------------------------------------------
2012-08-12 11:03:38 +00:00
static void ResetState ( )
{
2017-10-24 21:28:22 +00:00
g_nMBTimerDevice = kTIMERDEVICE_INVALID ;
2019-12-24 13:58:20 +00:00
MB_SetCumulativeCycles ( ) ;
2012-08-12 11:03:38 +00:00
g_nMB_InActiveCycleCount = 0 ;
g_bMB_RegAccessedFlag = false ;
g_bMB_Active = false ;
2020-04-19 20:00:37 +00:00
g_phasorMode = PH_Mockingboard ;
2019-02-24 10:49:09 +00:00
g_PhasorClockScaleFactor = 1 ;
2012-08-12 11:03:38 +00:00
2019-11-10 15:52:07 +00:00
g_uLastMBUpdateCycle = 0 ;
g_cyclesThisAudioFrame = 0 ;
2019-09-05 19:42:34 +00:00
2020-10-11 15:08:05 +00:00
for ( int id = 0 ; id < kNumSyncEvents ; id + + )
{
if ( g_syncEvent [ id ] & & g_syncEvent [ id ] - > m_active )
g_SynchronousEventMgr . Remove ( id ) ;
}
2021-03-23 22:01:41 +00:00
for ( UINT i = 0 ; i < NUM_AY8910 ; i + + )
{
g_MB [ i ] . ssi263 . SetCardMode ( g_phasorMode ) ;
g_MB [ i ] . ssi263 . Reset ( ) ;
}
2019-02-24 10:49:09 +00:00
// Not these, as they don't change on a CTRL+RESET or power-cycle:
// g_bMBAvailable = false;
2015-04-11 21:24:54 +00:00
// g_SoundcardType = CT_Empty; // Don't uncomment, else _ASSERT will fire in MB_Read() after an F2->MB_Reset()
// g_bPhasorEnable = false;
2012-08-12 11:03:38 +00:00
}
2021-02-13 16:56:29 +00:00
void MB_Reset ( const bool powerCycle ) // CTRL+RESET or power-cycle
2006-02-25 20:50:29 +00:00
{
2021-02-13 16:56:29 +00:00
if ( ! g_bDSAvailable )
2006-02-25 20:50:29 +00:00
return ;
2021-02-13 16:56:29 +00:00
for ( int i = 0 ; i < NUM_AY8910 ; i + + )
2006-02-25 20:50:29 +00:00
{
2021-02-13 16:56:29 +00:00
ResetSY6522 ( & g_MB [ i ] , powerCycle ) ;
2006-02-25 20:50:29 +00:00
AY8910_reset ( i ) ;
}
2012-08-12 11:03:38 +00:00
ResetState ( ) ;
2006-02-25 20:50:29 +00:00
MB_Reinitialize ( ) ; // Reset CLK for AY8910s
}
//-----------------------------------------------------------------------------
2020-04-19 20:00:37 +00:00
// Echo+ mode - Phasor's 2nd 6522 is mapped to every 16-byte offset in $Cnxx (Echo+ has a single 6522 controlling two AY-3-8913's)
2018-03-03 21:27:50 +00:00
static BYTE __stdcall MB_Read ( WORD PC , WORD nAddr , BYTE bWrite , BYTE nValue , ULONG nExecutedCycles )
2006-02-25 20:50:29 +00:00
{
2020-10-11 15:08:05 +00:00
MB_UpdateCycles ( nExecutedCycles ) ;
2007-05-28 11:16:42 +00:00
2013-03-22 20:54:14 +00:00
# ifdef _DEBUG
2018-04-06 21:36:54 +00:00
if ( ! IS_APPLE2 & & MemCheckINTCXROM ( ) )
2013-03-22 20:54:14 +00:00
{
2017-05-21 21:06:37 +00:00
_ASSERT ( 0 ) ; // Card ROM disabled, so IO_Cxxx() returns the internal ROM
2007-05-28 11:16:42 +00:00
return mem [ nAddr ] ;
2013-03-22 20:54:14 +00:00
}
2007-05-28 11:16:42 +00:00
2013-03-22 20:54:14 +00:00
if ( g_SoundcardType = = CT_Empty )
2012-03-20 23:17:06 +00:00
{
2017-05-21 21:06:37 +00:00
_ASSERT ( 0 ) ; // Card unplugged, so IO_Cxxx() returns the floating bus
2018-03-03 21:27:50 +00:00
return MemReadFloatingBus ( nExecutedCycles ) ;
2012-03-20 23:17:06 +00:00
}
2013-03-22 20:54:14 +00:00
# endif
2006-02-25 20:50:29 +00:00
BYTE nMB = ( nAddr > > 8 ) & 0xf - SLOT4 ;
BYTE nOffset = nAddr & 0xff ;
if ( g_bPhasorEnable )
{
if ( nMB ! = 0 ) // Slot4 only
2018-03-03 21:27:50 +00:00
return MemReadFloatingBus ( nExecutedCycles ) ;
2006-02-25 20:50:29 +00:00
2020-04-19 20:00:37 +00:00
int CS = 0 ;
if ( g_phasorMode = = PH_Mockingboard )
2006-02-25 20:50:29 +00:00
CS = ( ( nAddr & 0x80 ) > > 7 ) + 1 ; // 1 or 2
2020-04-19 20:00:37 +00:00
else if ( g_phasorMode = = PH_Phasor )
CS = ( ( nAddr & 0x80 ) > > 6 ) | ( ( nAddr & 0x10 ) > > 4 ) ; // 0, 1, 2 or 3
else if ( g_phasorMode = = PH_EchoPlus )
CS = 2 ;
2006-02-25 20:50:29 +00:00
2012-01-21 14:36:35 +00:00
BYTE nRes = 0 ;
2006-02-25 20:50:29 +00:00
if ( CS & 1 )
nRes | = SY6522_Read ( nMB * NUM_DEVS_PER_MB + SY6522_DEVICE_A , nAddr & 0xf ) ;
if ( CS & 2 )
nRes | = SY6522_Read ( nMB * NUM_DEVS_PER_MB + SY6522_DEVICE_B , nAddr & 0xf ) ;
2012-01-21 14:36:35 +00:00
bool bAccessedDevice = ( CS & 3 ) ? true : false ;
2021-02-21 19:12:36 +00:00
bool CS_SSI263 = ! ( nAddr & 0x80 ) & & ( nAddr & 0x60 ) ; // SSI263 at $Cn2x and/or $Cn4x
if ( g_phasorMode = = PH_Phasor & & CS_SSI263 ) // NB. Mockingboard mode: SSI263.bit7 not readable
2012-01-21 14:36:35 +00:00
{
2020-04-19 20:00:37 +00:00
_ASSERT ( ! bAccessedDevice ) ;
2021-02-21 19:12:36 +00:00
if ( nAddr & 0x40 ) // Primary SSI263
2021-03-23 22:01:41 +00:00
nRes = g_MB [ nMB * 2 + 1 ] . ssi263 . Read ( nExecutedCycles ) ; // SSI263 only drives bit7
2021-02-21 19:12:36 +00:00
if ( nAddr & 0x20 ) // Secondary SSI263
2021-03-23 22:01:41 +00:00
nRes = g_MB [ nMB * 2 + 0 ] . ssi263 . Read ( nExecutedCycles ) ; // SSI263 only drives bit7
2012-01-21 14:36:35 +00:00
bAccessedDevice = true ;
}
2006-02-25 20:50:29 +00:00
2018-03-03 21:27:50 +00:00
return bAccessedDevice ? nRes : MemReadFloatingBus ( nExecutedCycles ) ;
2006-02-25 20:50:29 +00:00
}
2021-05-03 18:58:13 +00:00
# if DBG_MB_SS_CARD
if ( nMB = = 1 )
return MemReadFloatingBus ( nExecutedCycles ) ;
# endif
2020-04-25 15:42:50 +00:00
// NB. Mockingboard: SSI263.bit7 not readable (TODO: check this with real h/w)
if ( nOffset < SY6522B_Offset )
2006-02-25 20:50:29 +00:00
return SY6522_Read ( nMB * NUM_DEVS_PER_MB + SY6522_DEVICE_A , nAddr & 0xf ) ;
else
2020-04-25 15:42:50 +00:00
return SY6522_Read ( nMB * NUM_DEVS_PER_MB + SY6522_DEVICE_B , nAddr & 0xf ) ;
2006-02-25 20:50:29 +00:00
}
//-----------------------------------------------------------------------------
2018-03-03 21:27:50 +00:00
static BYTE __stdcall MB_Write ( WORD PC , WORD nAddr , BYTE bWrite , BYTE nValue , ULONG nExecutedCycles )
2006-02-25 20:50:29 +00:00
{
2020-10-11 15:08:05 +00:00
MB_UpdateCycles ( nExecutedCycles ) ;
2007-05-28 11:16:42 +00:00
2013-03-22 20:54:14 +00:00
# ifdef _DEBUG
2018-04-06 21:36:54 +00:00
if ( ! IS_APPLE2 & & MemCheckINTCXROM ( ) )
2013-03-22 20:54:14 +00:00
{
2017-05-21 21:06:37 +00:00
_ASSERT ( 0 ) ; // Card ROM disabled, so IO_Cxxx() returns the internal ROM
2007-05-28 11:16:42 +00:00
return 0 ;
2013-03-22 20:54:14 +00:00
}
2007-05-28 11:16:42 +00:00
2012-03-20 23:17:06 +00:00
if ( g_SoundcardType = = CT_Empty )
{
2017-05-21 21:06:37 +00:00
_ASSERT ( 0 ) ; // Card unplugged, so IO_Cxxx() returns the floating bus
2007-05-28 11:16:42 +00:00
return 0 ;
2012-03-20 23:17:06 +00:00
}
2013-03-22 20:54:14 +00:00
# endif
2006-02-25 20:50:29 +00:00
2020-07-24 18:39:31 +00:00
// Support 6502/65C02 false-reads of 6522 (GH#52)
if ( ( ( mem [ ( PC - 2 ) & 0xffff ] = = 0x91 ) & & GetMainCpu ( ) = = CPU_6502 ) | | // sta (zp),y - 6502 only (no-PX variant only) (UTAIIe:4-23)
2020-08-22 15:06:26 +00:00
( mem [ ( PC - 3 ) & 0xffff ] = = 0x99 ) | | // sta abs16,y - 6502/65C02, but for 65C02 only the no-PX variant that does the false-read (UTAIIe:4-27)
( mem [ ( PC - 3 ) & 0xffff ] = = 0x9D ) ) // sta abs16,x - 6502/65C02, but for 65C02 only the no-PX variant that does the false-read (UTAIIe:4-27)
2020-07-24 18:39:31 +00:00
{
2020-08-22 15:06:26 +00:00
WORD base ;
2020-07-24 18:39:31 +00:00
WORD addr16 ;
if ( mem [ ( PC - 2 ) & 0xffff ] = = 0x91 )
{
BYTE zp = mem [ ( PC - 1 ) & 0xffff ] ;
2020-08-22 15:06:26 +00:00
base = ( mem [ zp ] | ( mem [ ( zp + 1 ) & 0xff ] < < 8 ) ) ;
addr16 = base + regs . y ;
2020-07-24 18:39:31 +00:00
}
else
{
2020-08-22 15:06:26 +00:00
base = mem [ ( PC - 2 ) & 0xffff ] | ( mem [ ( PC - 1 ) & 0xffff ] < < 8 ) ;
addr16 = base + ( ( mem [ ( PC - 3 ) & 0xffff ] = = 0x99 ) ? regs . y : regs . x ) ;
2020-07-24 18:39:31 +00:00
}
2020-08-22 15:06:26 +00:00
if ( ( ( base ^ addr16 ) > > 8 ) = = 0 ) // Only the no-PX variant does the false read (to the same I/O SELECT page)
2020-07-24 18:39:31 +00:00
{
2020-08-22 15:06:26 +00:00
_ASSERT ( addr16 = = nAddr ) ;
if ( addr16 = = nAddr ) // Check we've reverse looked-up the 6502 opcode correctly
{
if ( ( ( nAddr & 0xf ) = = 4 ) | | ( ( nAddr & 0xf ) = = 8 ) ) // Only reading 6522 reg-4 or reg-8 actually has an effect
MB_Read ( PC , nAddr , 0 , 0 , nExecutedCycles ) ;
}
2020-07-24 18:39:31 +00:00
}
}
2020-10-25 17:27:59 +00:00
BYTE nMB = ( ( nAddr > > 8 ) & 0xf ) - SLOT4 ;
2006-02-25 20:50:29 +00:00
BYTE nOffset = nAddr & 0xff ;
if ( g_bPhasorEnable )
{
if ( nMB ! = 0 ) // Slot4 only
2007-05-28 11:16:42 +00:00
return 0 ;
2006-02-25 20:50:29 +00:00
2020-11-17 21:31:57 +00:00
int CS = 0 ;
2020-04-19 20:00:37 +00:00
if ( g_phasorMode = = PH_Mockingboard )
2006-02-25 20:50:29 +00:00
CS = ( ( nAddr & 0x80 ) > > 7 ) + 1 ; // 1 or 2
2020-04-19 20:00:37 +00:00
else if ( g_phasorMode = = PH_Phasor )
CS = ( ( nAddr & 0x80 ) > > 6 ) | ( ( nAddr & 0x10 ) > > 4 ) ; // 0, 1, 2 or 3
else if ( g_phasorMode = = PH_EchoPlus )
CS = 2 ;
2006-02-25 20:50:29 +00:00
if ( CS & 1 )
SY6522_Write ( nMB * NUM_DEVS_PER_MB + SY6522_DEVICE_A , nAddr & 0xf , nValue ) ;
if ( CS & 2 )
SY6522_Write ( nMB * NUM_DEVS_PER_MB + SY6522_DEVICE_B , nAddr & 0xf , nValue ) ;
2021-02-21 19:12:36 +00:00
bool CS_SSI263 = ! ( nAddr & 0x80 ) & & ( nAddr & 0x60 ) ; // SSI263 at $Cn2x and/or $Cn4x
2020-04-19 20:00:37 +00:00
2021-02-21 19:12:36 +00:00
if ( ( g_phasorMode = = PH_Mockingboard | | g_phasorMode = = PH_Phasor ) & & CS_SSI263 ) // No SSI263 for Echo+
2020-04-19 20:00:37 +00:00
{
// NB. Mockingboard mode: writes to $Cn4x/SSI263 also get written to 1st 6522 (have confirmed on real Phasor h/w)
_ASSERT ( ( g_phasorMode = = PH_Mockingboard & & ( CS = = 0 | | CS = = 1 ) ) | | ( g_phasorMode = = PH_Phasor & & ( CS = = 0 ) ) ) ;
2021-02-21 19:12:36 +00:00
if ( nAddr & 0x40 ) // Primary SSI263
2021-03-23 22:01:41 +00:00
g_MB [ nMB * 2 + 1 ] . ssi263 . Write ( nAddr & 0x7 , nValue ) ; // 2nd 6522 is used for 1st speech chip
2021-02-21 19:12:36 +00:00
if ( nAddr & 0x20 ) // Secondary SSI263
2021-03-23 22:01:41 +00:00
g_MB [ nMB * 2 + 0 ] . ssi263 . Write ( nAddr & 0x7 , nValue ) ; // 1st 6522 is used for 2nd speech chip
2020-04-19 20:00:37 +00:00
}
2006-02-25 20:50:29 +00:00
2007-05-28 11:16:42 +00:00
return 0 ;
2006-02-25 20:50:29 +00:00
}
2020-04-25 15:42:50 +00:00
if ( nOffset < SY6522B_Offset )
2006-02-25 20:50:29 +00:00
SY6522_Write ( nMB * NUM_DEVS_PER_MB + SY6522_DEVICE_A , nAddr & 0xf , nValue ) ;
2020-04-25 15:42:50 +00:00
else
2006-02-25 20:50:29 +00:00
SY6522_Write ( nMB * NUM_DEVS_PER_MB + SY6522_DEVICE_B , nAddr & 0xf , nValue ) ;
2020-04-25 15:42:50 +00:00
2021-05-03 18:58:13 +00:00
# if !DBG_MB_SS_CARD
2021-02-21 19:12:36 +00:00
if ( nAddr & 0x40 )
2021-03-23 22:01:41 +00:00
g_MB [ nMB * 2 + 1 ] . ssi263 . Write ( nAddr & 0x7 , nValue ) ; // 2nd 6522 is used for 1st speech chip
2021-02-21 19:12:36 +00:00
if ( nAddr & 0x20 )
2021-03-23 22:01:41 +00:00
g_MB [ nMB * 2 + 0 ] . ssi263 . Write ( nAddr & 0x7 , nValue ) ; // 1st 6522 is used for 2nd speech chip
2021-05-03 18:58:13 +00:00
# endif
2007-05-28 11:16:42 +00:00
return 0 ;
2006-02-25 20:50:29 +00:00
}
//-----------------------------------------------------------------------------
2020-04-19 20:00:37 +00:00
// Phasor's DEVICE SELECT' logic:
// . if addr.[b3]==1, then clear the card's mode bits b2:b0
// . if any of addr.[b2:b0] are a logic 1, then set these bits in the card's mode
//
// Example DEVICE SELECT' accesses for Phasor in slot-4: (from empirical observations on real Phasor h/w)
// 1)
// . RESET -> Mockingboard mode (b#000)
// . $C0C5 -> Phasor mode (b#101)
// 2)
// . RESET -> Mockingboard mode (b#000)
// . $C0C1, then $C0C4 (or $C0C4, then $C0C1) -> Phasor mode (b#101)
// . $C0C2 -> Echo+ mode (b#111)
2021-02-10 20:51:11 +00:00
// . $C0C5 -> remaining in Echo+ mode (b#111)
2020-04-19 20:00:37 +00:00
// So $C0C5 seemingly results in 2 different modes.
//
2018-03-03 21:27:50 +00:00
static BYTE __stdcall PhasorIO ( WORD PC , WORD nAddr , BYTE bWrite , BYTE nValue , ULONG nExecutedCycles )
2006-02-25 20:50:29 +00:00
{
2020-04-19 20:00:37 +00:00
if ( ! g_bPhasorEnable )
2018-03-03 21:27:50 +00:00
return MemReadFloatingBus ( nExecutedCycles ) ;
2006-02-25 20:50:29 +00:00
2020-04-19 20:00:37 +00:00
UINT bits = ( UINT ) g_phasorMode ;
if ( nAddr & 8 )
bits = 0 ;
bits | = ( nAddr & 7 ) ;
g_phasorMode = ( PHASOR_MODE ) bits ;
2006-02-25 20:50:29 +00:00
2020-04-19 20:00:37 +00:00
if ( g_phasorMode = = PH_Mockingboard | | g_phasorMode = = PH_EchoPlus )
g_PhasorClockScaleFactor = 1 ;
else if ( g_phasorMode = = PH_Phasor )
g_PhasorClockScaleFactor = 2 ;
2006-02-25 20:50:29 +00:00
2019-06-28 20:34:34 +00:00
AY8910_InitClock ( ( int ) ( Get6502BaseClock ( ) * g_PhasorClockScaleFactor ) ) ;
2006-02-25 20:50:29 +00:00
2021-03-23 22:01:41 +00:00
for ( UINT i = 0 ; i < NUM_AY8910 ; i + + )
g_MB [ i ] . ssi263 . SetCardMode ( g_phasorMode ) ;
2018-03-03 21:27:50 +00:00
return MemReadFloatingBus ( nExecutedCycles ) ;
2006-02-25 20:50:29 +00:00
}
//-----------------------------------------------------------------------------
2019-02-24 15:37:15 +00:00
SS_CARDTYPE MB_GetSoundcardType ( )
{
return g_SoundcardType ;
}
static void MB_SetSoundcardType ( const SS_CARDTYPE NewSoundcardType )
{
if ( NewSoundcardType = = g_SoundcardType )
return ;
if ( NewSoundcardType = = CT_Empty )
MB_Mute ( ) ; // Call MB_Mute() before setting g_SoundcardType = CT_Empty
g_SoundcardType = NewSoundcardType ;
g_bPhasorEnable = ( g_SoundcardType = = CT_Phasor ) ;
}
//-----------------------------------------------------------------------------
2009-04-16 21:18:13 +00:00
void MB_InitializeIO ( LPBYTE pCxRomPeripheral , UINT uSlot4 , UINT uSlot5 )
{
2012-03-20 23:17:06 +00:00
// Mockingboard: Slot 4 & 5
// Phasor : Slot 4
// <other> : Slot 4 & 5
2020-10-11 16:34:44 +00:00
if ( GetCardMgr ( ) . QuerySlot ( SLOT4 ) ! = CT_MockingboardC & & GetCardMgr ( ) . QuerySlot ( SLOT4 ) ! = CT_Phasor )
2012-01-22 13:46:36 +00:00
{
2012-03-20 23:17:06 +00:00
MB_SetSoundcardType ( CT_Empty ) ;
return ;
2012-01-22 13:46:36 +00:00
}
2012-03-20 23:17:06 +00:00
2020-10-11 16:34:44 +00:00
if ( GetCardMgr ( ) . QuerySlot ( SLOT4 ) = = CT_MockingboardC )
2012-03-20 23:17:06 +00:00
RegisterIoHandler ( uSlot4 , IO_Null , IO_Null , MB_Read , MB_Write , NULL , NULL ) ;
else // Phasor
RegisterIoHandler ( uSlot4 , PhasorIO , PhasorIO , MB_Read , MB_Write , NULL , NULL ) ;
2020-10-11 16:34:44 +00:00
if ( GetCardMgr ( ) . QuerySlot ( SLOT5 ) = = CT_MockingboardC )
2012-03-20 23:17:06 +00:00
RegisterIoHandler ( uSlot5 , IO_Null , IO_Null , MB_Read , MB_Write , NULL , NULL ) ;
2020-10-11 16:34:44 +00:00
MB_SetSoundcardType ( GetCardMgr ( ) . QuerySlot ( SLOT4 ) ) ;
2019-02-24 15:37:15 +00:00
2019-11-03 14:23:47 +00:00
if ( g_bDisableDirectSound | | g_bDisableDirectSoundMockingboard )
return ;
2019-02-24 15:37:15 +00:00
// Sound buffer may have been stopped by MB_InitializeForLoadingSnapshot().
// NB. DSZeroVoiceBuffer() also zeros the sound buffer, so it's better than directly calling IDirectSoundBuffer::Play():
// - without zeroing, then the previous sound buffer can be heard for a fraction of a second
// - eg. when doing Mockingboard playback, then loading a save-state which is also doing Mockingboard playback
2021-03-23 22:01:41 +00:00
DSZeroVoiceBuffer ( & MockingboardVoice , g_dwDSBufferSize ) ;
2009-04-16 21:18:13 +00:00
}
//-----------------------------------------------------------------------------
2021-03-23 22:01:41 +00:00
void MB_Mute ( void )
2006-02-25 20:50:29 +00:00
{
2012-03-20 23:17:06 +00:00
if ( g_SoundcardType = = CT_Empty )
2006-02-25 20:50:29 +00:00
return ;
if ( MockingboardVoice . bActive & & ! MockingboardVoice . bMute )
{
MockingboardVoice . lpDSBvoice - > SetVolume ( DSBVOLUME_MIN ) ;
MockingboardVoice . bMute = true ;
}
2021-03-23 22:01:41 +00:00
for ( UINT i = 0 ; i < NUM_AY8910 ; i + + )
g_MB [ i ] . ssi263 . Mute ( ) ;
2006-02-25 20:50:29 +00:00
}
//-----------------------------------------------------------------------------
2021-03-23 22:01:41 +00:00
void MB_Unmute ( void )
2006-02-25 20:50:29 +00:00
{
2012-03-20 23:17:06 +00:00
if ( g_SoundcardType = = CT_Empty )
2006-02-25 20:50:29 +00:00
return ;
if ( MockingboardVoice . bActive & & MockingboardVoice . bMute )
{
MockingboardVoice . lpDSBvoice - > SetVolume ( MockingboardVoice . nVolume ) ;
MockingboardVoice . bMute = false ;
}
2021-03-23 22:01:41 +00:00
for ( UINT i = 0 ; i < NUM_AY8910 ; i + + )
g_MB [ i ] . ssi263 . Unmute ( ) ;
2006-02-25 20:50:29 +00:00
}
//-----------------------------------------------------------------------------
2019-12-24 13:58:20 +00:00
# ifdef _DEBUG
void MB_CheckCumulativeCycles ( )
{
if ( g_SoundcardType = = CT_Empty )
return ;
_ASSERT ( g_uLastCumulativeCycles = = g_nCumulativeCycles ) ;
g_uLastCumulativeCycles = g_nCumulativeCycles ;
}
# endif
// Called by: ResetState() and Snapshot_LoadState_v2()
void MB_SetCumulativeCycles ( )
2007-08-06 21:38:35 +00:00
{
g_uLastCumulativeCycles = g_nCumulativeCycles ;
}
2019-11-10 15:52:07 +00:00
// Called by ContinueExecution() at the end of every execution period (~1000 cycles or ~3 cycle when MODE_STEPPING)
// NB. Required for FT's TEST LAB #1 player
void MB_PeriodicUpdate ( UINT executedCycles )
2006-02-25 20:50:29 +00:00
{
2017-10-24 21:28:22 +00:00
if ( g_SoundcardType = = CT_Empty )
2006-02-25 20:50:29 +00:00
return ;
2021-03-23 22:01:41 +00:00
for ( UINT i = 0 ; i < NUM_AY8910 ; i + + )
g_MB [ i ] . ssi263 . PeriodicUpdate ( executedCycles ) ;
2019-11-10 15:52:07 +00:00
if ( g_nMBTimerDevice ! = kTIMERDEVICE_INVALID )
return ;
const UINT kCyclesPerAudioFrame = 1000 ;
g_cyclesThisAudioFrame + = executedCycles ;
if ( g_cyclesThisAudioFrame < kCyclesPerAudioFrame )
return ;
g_cyclesThisAudioFrame % = kCyclesPerAudioFrame ;
MB_Update ( ) ;
2006-02-25 20:50:29 +00:00
}
//-----------------------------------------------------------------------------
2021-02-12 19:16:14 +00:00
static bool CheckTimerUnderflow ( USHORT & counter , int & timerIrqDelay , const USHORT nClocks )
2019-06-15 16:41:53 +00:00
{
2019-11-17 12:26:14 +00:00
if ( nClocks = = 0 )
return false ;
2021-02-12 19:16:14 +00:00
int oldTimer = counter ;
int timer = counter ;
2019-06-15 16:41:53 +00:00
timer - = nClocks ;
2021-02-12 19:16:14 +00:00
counter = ( USHORT ) timer ;
2019-06-15 16:41:53 +00:00
bool timerIrq = false ;
if ( timerIrqDelay ) // Deal with any previous counter underflow which didn't yet result in an IRQ
{
2019-11-17 12:26:14 +00:00
_ASSERT ( timerIrqDelay = = 1 ) ;
timerIrqDelay = 0 ;
timerIrq = true ;
2019-11-18 15:08:59 +00:00
// if LATCH is very small then could underflow for every opcode...
2019-06-15 16:41:53 +00:00
}
2019-11-18 15:08:59 +00:00
if ( oldTimer > = 0 & & timer < 0 ) // Underflow occurs for 0x0000 -> 0xFFFF
2019-06-15 16:41:53 +00:00
{
2019-11-17 12:26:14 +00:00
if ( timer < = - 2 ) // TIMER = 0xFFFE (or less)
2019-06-15 16:41:53 +00:00
timerIrq = true ;
2019-11-17 12:26:14 +00:00
else // TIMER = 0xFFFF
timerIrqDelay = 1 ; // ...so 1 cycle until IRQ
2019-06-15 16:41:53 +00:00
}
return timerIrq ;
}
2021-02-12 19:16:14 +00:00
static int OnTimer1Underflow ( USHORT & counter , USHORT latch )
{
int timer = ( int ) ( short ) ( counter ) ;
while ( timer < - 1 )
timer + = ( latch + kExtraTimerCycles ) ; // GH#651: account for underflowed cycles / GH#652: account for extra 2 cycles
counter = ( USHORT ) timer ;
return ( timer = = - 1 ) ? 1 : 0 ; // timer1IrqDelay
}
2017-10-21 17:47:22 +00:00
// Called by:
2020-10-11 15:08:05 +00:00
// . CpuExecute() every ~1000 cycles @ 1MHz
// . MB_SyncEventCallback() on a TIMER1/2 underflow
// . MB_Read() / MB_Write() (for both normal & full-speed)
void MB_UpdateCycles ( ULONG uExecutedCycles )
2006-02-25 20:50:29 +00:00
{
2019-06-15 16:41:53 +00:00
if ( g_SoundcardType = = CT_Empty )
2020-10-11 15:08:05 +00:00
return ;
2006-02-25 20:50:29 +00:00
2007-08-06 21:38:35 +00:00
CpuCalcCycles ( uExecutedCycles ) ;
UINT64 uCycles = g_nCumulativeCycles - g_uLastCumulativeCycles ;
2020-10-11 15:08:05 +00:00
_ASSERT ( uCycles > = 0 ) ;
2019-11-16 23:49:21 +00:00
if ( uCycles = = 0 )
2020-10-11 15:08:05 +00:00
return ;
2019-11-16 23:49:21 +00:00
2007-08-06 21:38:35 +00:00
g_uLastCumulativeCycles = g_nCumulativeCycles ;
2020-10-11 15:08:05 +00:00
_ASSERT ( uCycles < 0x10000 | | g_nAppMode = = MODE_BENCHMARK ) ;
USHORT nClocks = ( USHORT ) uCycles ;
2019-11-16 23:49:21 +00:00
2020-10-11 15:08:05 +00:00
for ( int i = 0 ; i < NUM_SY6522 ; i + + )
2006-02-25 20:50:29 +00:00
{
SY6522_AY8910 * pMB = & g_MB [ i ] ;
2020-10-11 15:08:05 +00:00
const bool bTimer1Underflow = CheckTimerUnderflow ( pMB - > sy6522 . TIMER1_COUNTER . w , pMB - > sy6522 . timer1IrqDelay , nClocks ) ;
2021-02-10 20:51:11 +00:00
if ( bTimer1Underflow )
2021-02-12 19:16:14 +00:00
pMB - > sy6522 . timer1IrqDelay = OnTimer1Underflow ( pMB - > sy6522 . TIMER1_COUNTER . w , pMB - > sy6522 . TIMER1_LATCH . w ) ;
2021-02-10 20:51:11 +00:00
// No TIMER2 latch so "after timing out, the counter will continue to decrement"
CheckTimerUnderflow ( pMB - > sy6522 . TIMER2_COUNTER . w , pMB - > sy6522 . timer2IrqDelay , nClocks ) ;
2020-10-11 15:08:05 +00:00
}
}
2019-11-16 23:49:21 +00:00
2020-10-11 15:08:05 +00:00
//-----------------------------------------------------------------------------
2006-02-25 20:50:29 +00:00
2020-10-11 15:08:05 +00:00
static int MB_SyncEventCallback ( int id , int /*cycles*/ , ULONG uExecutedCycles )
{
SY6522_AY8910 * pMB = & g_MB [ id / kNumTimersPer6522 ] ;
2006-05-02 21:56:28 +00:00
2020-10-11 15:08:05 +00:00
if ( ( id & 1 ) = = 0 )
{
_ASSERT ( pMB - > bTimer1Active ) ;
MB_Update ( ) ;
2017-10-24 21:28:22 +00:00
2020-10-11 15:08:05 +00:00
UpdateIFR ( pMB , 0 , IxR_TIMER1 ) ;
2019-06-01 11:21:00 +00:00
2021-02-10 20:51:11 +00:00
MB_UpdateCycles ( uExecutedCycles ) ;
2020-10-11 15:08:05 +00:00
if ( ( pMB - > sy6522 . ACR & RUNMODE ) = = RM_ONESHOT )
2019-11-16 23:49:21 +00:00
{
2020-10-11 15:08:05 +00:00
// One-shot mode
// - Phasor's playback code uses one-shot mode
StopTimer1 ( pMB ) ;
return 0 ; // Don't repeat event
2019-11-16 23:49:21 +00:00
}
2020-10-11 15:08:05 +00:00
StartTimer1 ( pMB ) ;
return pMB - > sy6522 . TIMER1_COUNTER . w + kExtraTimerCycles ;
2006-02-25 20:50:29 +00:00
}
2020-10-11 15:08:05 +00:00
else
{
_ASSERT ( pMB - > bTimer2Active ) ;
UpdateIFR ( pMB , 0 , IxR_TIMER2 ) ;
2019-11-16 23:49:21 +00:00
2020-10-11 15:08:05 +00:00
StopTimer2 ( pMB ) ; // TIMER2 only runs in one-shot mode
return 0 ; // Don't repeat event
}
2006-02-25 20:50:29 +00:00
}
//-----------------------------------------------------------------------------
bool MB_IsActive ( )
{
2017-10-24 21:28:22 +00:00
if ( ! MockingboardVoice . bActive )
2006-02-25 20:50:29 +00:00
return false ;
2021-03-23 22:01:41 +00:00
bool isSSI263Active = false ;
for ( UINT i = 0 ; i < NUM_AY8910 ; i + + )
isSSI263Active | = g_MB [ i ] . ssi263 . IsPhonemeActive ( ) ;
return g_bMB_Active | | isSSI263Active ;
2006-02-25 20:50:29 +00:00
}
//-----------------------------------------------------------------------------
DWORD MB_GetVolume ( )
{
return MockingboardVoice . dwUserVolume ;
}
void MB_SetVolume ( DWORD dwVolume , DWORD dwVolumeMax )
{
MockingboardVoice . dwUserVolume = dwVolume ;
MockingboardVoice . nVolume = NewVolume ( dwVolume , dwVolumeMax ) ;
2020-07-17 21:53:17 +00:00
if ( MockingboardVoice . bActive & & ! MockingboardVoice . bMute )
2006-02-25 20:50:29 +00:00
MockingboardVoice . lpDSBvoice - > SetVolume ( MockingboardVoice . nVolume ) ;
2021-03-23 22:01:41 +00:00
//
for ( UINT i = 0 ; i < NUM_AY8910 ; i + + )
g_MB [ i ] . ssi263 . SetVolume ( dwVolume , dwVolumeMax ) ;
}
//---------------------------------------------------------------------------
// Called from class SSI263
UINT64 MB_GetLastCumulativeCycles ( void )
{
return g_uLastCumulativeCycles ;
}
void MB_UpdateIFR ( BYTE nDevice , BYTE clr_mask , BYTE set_mask )
{
SY6522_AY8910 * pMB = & g_MB [ nDevice ] ;
UpdateIFR ( pMB , clr_mask , set_mask ) ;
}
BYTE MB_GetPCR ( BYTE nDevice )
{
return g_MB [ nDevice ] . sy6522 . PCR ;
2006-02-25 20:50:29 +00:00
}
//===========================================================================
2015-02-13 22:40:53 +00:00
// Called by debugger - Debugger_Display.cpp
2015-04-11 21:24:54 +00:00
void MB_GetSnapshot_v1 ( SS_CARD_MOCKINGBOARD_v1 * const pSS , const DWORD dwSlot )
2006-02-25 20:50:29 +00:00
{
2015-04-11 21:24:54 +00:00
pSS - > Hdr . UnitHdr . hdr . v2 . Length = sizeof ( SS_CARD_MOCKINGBOARD_v1 ) ;
2015-02-13 22:40:53 +00:00
pSS - > Hdr . UnitHdr . hdr . v2 . Type = UT_Card ;
pSS - > Hdr . UnitHdr . hdr . v2 . Version = 1 ;
2006-02-25 20:50:29 +00:00
2015-02-13 22:40:53 +00:00
pSS - > Hdr . Slot = dwSlot ;
pSS - > Hdr . Type = CT_MockingboardC ;
2006-02-25 20:50:29 +00:00
UINT nMbCardNum = dwSlot - SLOT4 ;
UINT nDeviceNum = nMbCardNum * 2 ;
SY6522_AY8910 * pMB = & g_MB [ nDeviceNum ] ;
2021-03-07 13:11:22 +00:00
for ( UINT i = 0 ; i < MB_UNITS_PER_CARD_v1 ; i + + )
2006-02-25 20:50:29 +00:00
{
2021-03-07 13:11:22 +00:00
// 6522
{
BYTE * d = ( BYTE * ) & pSS - > Unit [ i ] . RegsSY6522 ;
BYTE * s = ( BYTE * ) & pMB - > sy6522 ;
for ( UINT j = 0 ; j < = 9 ; j + + ) // regs $00-$09
* d + + = * s + + ;
s = & pMB - > sy6522 . SERIAL_SHIFT ;
for ( UINT j = 0 ; j < = 6 ; j + + ) // regs $0A-$0F
* d + + = * s + + ;
}
// AY8913
for ( UINT j = 0 ; j < 16 ; j + + )
{
pSS - > Unit [ i ] . RegsAY8910 [ j ] = AYReadReg ( nDeviceNum , j ) ;
}
2021-03-23 22:01:41 +00:00
memset ( & pSS - > Unit [ i ] . RegsSSI263 , 0 , sizeof ( SSI263A ) ) ; // Not used by debugger
2006-02-25 20:50:29 +00:00
pSS - > Unit [ i ] . nAYCurrentRegister = pMB - > nAYCurrentRegister ;
2021-05-09 12:38:16 +00:00
pSS - > Unit [ i ] . bTimer1Active = pMB - > bTimer1Active ;
pSS - > Unit [ i ] . bTimer2Active = pMB - > bTimer2Active ;
2015-04-11 21:24:54 +00:00
pSS - > Unit [ i ] . bSpeechIrqPending = false ;
2006-02-25 20:50:29 +00:00
nDeviceNum + + ;
pMB + + ;
}
}
2021-03-23 22:01:41 +00:00
//=============================================================================
2015-02-13 22:40:53 +00:00
2017-10-27 10:10:15 +00:00
// Unit version history:
// 2: Added: Timer1 & Timer2 active
2019-11-11 17:35:10 +00:00
// 3: Added: Unit state - GH#320
// 4: Added: 6522 timerIrqDelay - GH#652
// 5: Added: Unit state-B (Phasor only) - GH#659
2020-04-19 20:00:37 +00:00
// 6: Changed SS_YAML_KEY_PHASOR_MODE from (0,1) to (0,5,7)
2020-04-26 17:07:38 +00:00
// Added SS_YAML_KEY_VOTRAX_PHONEME
// Removed: redundant SS_YAML_KEY_PHASOR_CLOCK_SCALE_FACTOR
2021-03-23 22:01:41 +00:00
// 7: Added SS_YAML_KEY_SSI263_REG_ACTIVE_PHONEME to SSI263 sub-unit
const UINT kUNIT_VERSION = 7 ;
2017-10-27 10:10:15 +00:00
2015-12-05 16:50:27 +00:00
const UINT NUM_MB_UNITS = 2 ;
const UINT NUM_PHASOR_UNITS = 2 ;
# define SS_YAML_KEY_MB_UNIT "Unit"
# define SS_YAML_KEY_SY6522 "SY6522"
# define SS_YAML_KEY_SY6522_REG_ORB "ORB"
# define SS_YAML_KEY_SY6522_REG_ORA "ORA"
# define SS_YAML_KEY_SY6522_REG_DDRB "DDRB"
# define SS_YAML_KEY_SY6522_REG_DDRA "DDRA"
# define SS_YAML_KEY_SY6522_REG_T1_COUNTER "Timer1 Counter"
# define SS_YAML_KEY_SY6522_REG_T1_LATCH "Timer1 Latch"
# define SS_YAML_KEY_SY6522_REG_T2_COUNTER "Timer2 Counter"
# define SS_YAML_KEY_SY6522_REG_T2_LATCH "Timer2 Latch"
# define SS_YAML_KEY_SY6522_REG_SERIAL_SHIFT "Serial Shift"
# define SS_YAML_KEY_SY6522_REG_ACR "ACR"
# define SS_YAML_KEY_SY6522_REG_PCR "PCR"
# define SS_YAML_KEY_SY6522_REG_IFR "IFR"
# define SS_YAML_KEY_SY6522_REG_IER "IER"
# define SS_YAML_KEY_AY_CURR_REG "AY Current Register"
2017-10-27 10:10:15 +00:00
# define SS_YAML_KEY_MB_UNIT_STATE "Unit State"
2019-11-11 17:35:10 +00:00
# define SS_YAML_KEY_MB_UNIT_STATE_B "Unit State-B" // Phasor only
2015-12-05 16:50:27 +00:00
# define SS_YAML_KEY_TIMER1_IRQ "Timer1 IRQ Pending"
# define SS_YAML_KEY_TIMER2_IRQ "Timer2 IRQ Pending"
# define SS_YAML_KEY_SPEECH_IRQ "Speech IRQ Pending"
2017-10-21 17:47:22 +00:00
# define SS_YAML_KEY_TIMER1_ACTIVE "Timer1 Active"
# define SS_YAML_KEY_TIMER2_ACTIVE "Timer2 Active"
2019-06-15 16:41:53 +00:00
# define SS_YAML_KEY_SY6522_TIMER1_IRQ_DELAY "Timer1 IRQ Delay"
# define SS_YAML_KEY_SY6522_TIMER2_IRQ_DELAY "Timer2 IRQ Delay"
2015-12-05 16:50:27 +00:00
# define SS_YAML_KEY_PHASOR_UNIT "Unit"
2020-04-26 17:07:38 +00:00
# define SS_YAML_KEY_PHASOR_CLOCK_SCALE_FACTOR "Clock Scale Factor" // Redundant from v6
2015-12-05 16:50:27 +00:00
# define SS_YAML_KEY_PHASOR_MODE "Mode"
2020-04-26 17:07:38 +00:00
# define SS_YAML_KEY_VOTRAX_PHONEME "Votrax Phoneme"
2015-12-05 16:50:27 +00:00
std : : string MB_GetSnapshotCardName ( void )
{
static const std : : string name ( " Mockingboard C " ) ;
return name ;
}
std : : string Phasor_GetSnapshotCardName ( void )
{
static const std : : string name ( " Phasor " ) ;
return name ;
}
static void SaveSnapshotSY6522 ( YamlSaveHelper & yamlSaveHelper , SY6522 & sy6522 )
{
YamlSaveHelper : : Label label ( yamlSaveHelper , " %s: \n " , SS_YAML_KEY_SY6522 ) ;
2016-03-04 21:26:14 +00:00
yamlSaveHelper . SaveHexUint8 ( SS_YAML_KEY_SY6522_REG_ORB , sy6522 . ORB ) ;
yamlSaveHelper . SaveHexUint8 ( SS_YAML_KEY_SY6522_REG_ORA , sy6522 . ORA ) ;
yamlSaveHelper . SaveHexUint8 ( SS_YAML_KEY_SY6522_REG_DDRB , sy6522 . DDRB ) ;
yamlSaveHelper . SaveHexUint8 ( SS_YAML_KEY_SY6522_REG_DDRA , sy6522 . DDRA ) ;
yamlSaveHelper . SaveHexUint16 ( SS_YAML_KEY_SY6522_REG_T1_COUNTER , sy6522 . TIMER1_COUNTER . w ) ;
yamlSaveHelper . SaveHexUint16 ( SS_YAML_KEY_SY6522_REG_T1_LATCH , sy6522 . TIMER1_LATCH . w ) ;
2019-06-15 16:41:53 +00:00
yamlSaveHelper . SaveUint ( SS_YAML_KEY_SY6522_TIMER1_IRQ_DELAY , sy6522 . timer1IrqDelay ) ; // v4
2016-03-04 21:26:14 +00:00
yamlSaveHelper . SaveHexUint16 ( SS_YAML_KEY_SY6522_REG_T2_COUNTER , sy6522 . TIMER2_COUNTER . w ) ;
yamlSaveHelper . SaveHexUint16 ( SS_YAML_KEY_SY6522_REG_T2_LATCH , sy6522 . TIMER2_LATCH . w ) ;
2019-06-15 16:41:53 +00:00
yamlSaveHelper . SaveUint ( SS_YAML_KEY_SY6522_TIMER2_IRQ_DELAY , sy6522 . timer2IrqDelay ) ; // v4
2016-03-04 21:26:14 +00:00
yamlSaveHelper . SaveHexUint8 ( SS_YAML_KEY_SY6522_REG_SERIAL_SHIFT , sy6522 . SERIAL_SHIFT ) ;
yamlSaveHelper . SaveHexUint8 ( SS_YAML_KEY_SY6522_REG_ACR , sy6522 . ACR ) ;
yamlSaveHelper . SaveHexUint8 ( SS_YAML_KEY_SY6522_REG_PCR , sy6522 . PCR ) ;
yamlSaveHelper . SaveHexUint8 ( SS_YAML_KEY_SY6522_REG_IFR , sy6522 . IFR ) ;
yamlSaveHelper . SaveHexUint8 ( SS_YAML_KEY_SY6522_REG_IER , sy6522 . IER ) ;
2015-12-05 16:50:27 +00:00
// NB. No need to write ORA_NO_HS, since same data as ORA, just without handshake
}
void MB_SaveSnapshot ( YamlSaveHelper & yamlSaveHelper , const UINT uSlot )
{
const UINT nMbCardNum = uSlot - SLOT4 ;
UINT nDeviceNum = nMbCardNum * 2 ;
SY6522_AY8910 * pMB = & g_MB [ nDeviceNum ] ;
2017-10-27 10:10:15 +00:00
YamlSaveHelper : : Slot slot ( yamlSaveHelper , MB_GetSnapshotCardName ( ) , uSlot , kUNIT_VERSION ) ; // fixme: object should be just 1 Mockingboard card & it will know its slot
2015-12-05 16:50:27 +00:00
YamlSaveHelper : : Label state ( yamlSaveHelper , " %s: \n " , SS_YAML_KEY_STATE ) ;
2021-03-23 22:01:41 +00:00
yamlSaveHelper . SaveBool ( SS_YAML_KEY_VOTRAX_PHONEME , pMB - > ssi263 . GetVotraxPhoneme ( ) ) ;
2020-04-26 17:07:38 +00:00
2015-12-05 16:50:27 +00:00
for ( UINT i = 0 ; i < NUM_MB_UNITS ; i + + )
{
YamlSaveHelper : : Label unit ( yamlSaveHelper , " %s%d: \n " , SS_YAML_KEY_MB_UNIT , i ) ;
SaveSnapshotSY6522 ( yamlSaveHelper , pMB - > sy6522 ) ;
AY8910_SaveSnapshot ( yamlSaveHelper , nDeviceNum , std : : string ( " " ) ) ;
2021-03-23 22:01:41 +00:00
pMB - > ssi263 . SaveSnapshot ( yamlSaveHelper ) ;
2015-12-05 16:50:27 +00:00
2017-10-27 10:10:15 +00:00
yamlSaveHelper . SaveHexUint4 ( SS_YAML_KEY_MB_UNIT_STATE , pMB - > state ) ;
2016-03-04 21:26:14 +00:00
yamlSaveHelper . SaveHexUint4 ( SS_YAML_KEY_AY_CURR_REG , pMB - > nAYCurrentRegister ) ;
2016-03-01 22:31:17 +00:00
yamlSaveHelper . Save ( " %s: %s # Not supported \n " , SS_YAML_KEY_TIMER1_IRQ , " false " ) ;
yamlSaveHelper . Save ( " %s: %s # Not supported \n " , SS_YAML_KEY_TIMER2_IRQ , " false " ) ;
yamlSaveHelper . Save ( " %s: %s # Not supported \n " , SS_YAML_KEY_SPEECH_IRQ , " false " ) ;
2017-10-21 17:47:22 +00:00
yamlSaveHelper . SaveBool ( SS_YAML_KEY_TIMER1_ACTIVE , pMB - > bTimer1Active ) ;
yamlSaveHelper . SaveBool ( SS_YAML_KEY_TIMER2_ACTIVE , pMB - > bTimer2Active ) ;
2015-12-05 16:50:27 +00:00
nDeviceNum + + ;
pMB + + ;
}
}
2019-06-15 16:41:53 +00:00
static void LoadSnapshotSY6522 ( YamlLoadHelper & yamlLoadHelper , SY6522 & sy6522 , UINT version )
2015-12-05 16:50:27 +00:00
{
if ( ! yamlLoadHelper . GetSubMap ( SS_YAML_KEY_SY6522 ) )
throw std : : string ( " Card: Expected key: " ) + std : : string ( SS_YAML_KEY_SY6522 ) ;
2016-02-24 22:38:59 +00:00
sy6522 . ORB = yamlLoadHelper . LoadUint ( SS_YAML_KEY_SY6522_REG_ORB ) ;
sy6522 . ORA = yamlLoadHelper . LoadUint ( SS_YAML_KEY_SY6522_REG_ORA ) ;
sy6522 . DDRB = yamlLoadHelper . LoadUint ( SS_YAML_KEY_SY6522_REG_DDRB ) ;
sy6522 . DDRA = yamlLoadHelper . LoadUint ( SS_YAML_KEY_SY6522_REG_DDRA ) ;
sy6522 . TIMER1_COUNTER . w = yamlLoadHelper . LoadUint ( SS_YAML_KEY_SY6522_REG_T1_COUNTER ) ;
sy6522 . TIMER1_LATCH . w = yamlLoadHelper . LoadUint ( SS_YAML_KEY_SY6522_REG_T1_LATCH ) ;
sy6522 . TIMER2_COUNTER . w = yamlLoadHelper . LoadUint ( SS_YAML_KEY_SY6522_REG_T2_COUNTER ) ;
sy6522 . TIMER2_LATCH . w = yamlLoadHelper . LoadUint ( SS_YAML_KEY_SY6522_REG_T2_LATCH ) ;
sy6522 . SERIAL_SHIFT = yamlLoadHelper . LoadUint ( SS_YAML_KEY_SY6522_REG_SERIAL_SHIFT ) ;
sy6522 . ACR = yamlLoadHelper . LoadUint ( SS_YAML_KEY_SY6522_REG_ACR ) ;
sy6522 . PCR = yamlLoadHelper . LoadUint ( SS_YAML_KEY_SY6522_REG_PCR ) ;
sy6522 . IFR = yamlLoadHelper . LoadUint ( SS_YAML_KEY_SY6522_REG_IFR ) ;
sy6522 . IER = yamlLoadHelper . LoadUint ( SS_YAML_KEY_SY6522_REG_IER ) ;
2015-12-05 16:50:27 +00:00
sy6522 . ORA_NO_HS = 0 ; // Not saved
2019-06-15 16:41:53 +00:00
sy6522 . timer1IrqDelay = sy6522 . timer2IrqDelay = 0 ;
if ( version > = 4 )
{
sy6522 . timer1IrqDelay = yamlLoadHelper . LoadUint ( SS_YAML_KEY_SY6522_TIMER1_IRQ_DELAY ) ;
sy6522 . timer2IrqDelay = yamlLoadHelper . LoadUint ( SS_YAML_KEY_SY6522_TIMER2_IRQ_DELAY ) ;
}
2021-04-18 17:33:16 +00:00
if ( version < 7 )
{
// Assume t1_latch was never written to (so had the old default of 0x0000) - this now results in failure of Mockingboard detection!
if ( sy6522 . TIMER1_LATCH . w = = 0x0000 )
sy6522 . TIMER1_LATCH . w = 0xFFFF ; // Allow Mockingboard detection to succeed
}
2015-12-05 16:50:27 +00:00
yamlLoadHelper . PopMap ( ) ;
}
bool MB_LoadSnapshot ( YamlLoadHelper & yamlLoadHelper , UINT slot , UINT version )
{
if ( slot ! = 4 & & slot ! = 5 ) // fixme
throw std : : string ( " Card: wrong slot " ) ;
2017-10-27 10:10:15 +00:00
if ( version < 1 | | version > kUNIT_VERSION )
2015-12-05 16:50:27 +00:00
throw std : : string ( " Card: wrong version " ) ;
AY8910UpdateSetCycles ( ) ;
const UINT nMbCardNum = slot - SLOT4 ;
UINT nDeviceNum = nMbCardNum * 2 ;
SY6522_AY8910 * pMB = & g_MB [ nDeviceNum ] ;
2021-03-23 22:01:41 +00:00
bool isVotrax = ( version > = 6 ) ? yamlLoadHelper . LoadBool ( SS_YAML_KEY_VOTRAX_PHONEME ) : false ;
pMB - > ssi263 . SetVotraxPhoneme ( isVotrax ) ;
2015-12-05 16:50:27 +00:00
for ( UINT i = 0 ; i < NUM_MB_UNITS ; i + + )
{
2020-11-10 20:33:55 +00:00
char szNum [ 2 ] = { char ( ' 0 ' + i ) , 0 } ;
2015-12-05 16:50:27 +00:00
std : : string unit = std : : string ( SS_YAML_KEY_MB_UNIT ) + std : : string ( szNum ) ;
if ( ! yamlLoadHelper . GetSubMap ( unit ) )
throw std : : string ( " Card: Expected key: " ) + std : : string ( unit ) ;
2019-06-15 16:41:53 +00:00
LoadSnapshotSY6522 ( yamlLoadHelper , pMB - > sy6522 , version ) ;
2019-08-26 19:10:33 +00:00
UpdateIFR ( pMB , 0 , pMB - > sy6522 . IFR ) ; // Assert any pending IRQs (GH#677)
2015-12-05 16:50:27 +00:00
AY8910_LoadSnapshot ( yamlLoadHelper , nDeviceNum , std : : string ( " " ) ) ;
2021-03-23 22:01:41 +00:00
pMB - > ssi263 . LoadSnapshot ( yamlLoadHelper , nDeviceNum , PH_Mockingboard , version ) ; // Pre: SetVotraxPhoneme()
2015-12-05 16:50:27 +00:00
2016-02-24 22:38:59 +00:00
pMB - > nAYCurrentRegister = yamlLoadHelper . LoadUint ( SS_YAML_KEY_AY_CURR_REG ) ;
2016-03-01 22:31:17 +00:00
yamlLoadHelper . LoadBool ( SS_YAML_KEY_TIMER1_IRQ ) ; // Consume
yamlLoadHelper . LoadBool ( SS_YAML_KEY_TIMER2_IRQ ) ; // Consume
yamlLoadHelper . LoadBool ( SS_YAML_KEY_SPEECH_IRQ ) ; // Consume
2015-12-05 16:50:27 +00:00
2017-10-21 17:47:22 +00:00
if ( version > = 2 )
{
pMB - > bTimer1Active = yamlLoadHelper . LoadBool ( SS_YAML_KEY_TIMER1_ACTIVE ) ;
pMB - > bTimer2Active = yamlLoadHelper . LoadBool ( SS_YAML_KEY_TIMER2_ACTIVE ) ;
}
2017-10-27 10:10:15 +00:00
pMB - > state = AY_INACTIVE ;
2019-11-11 17:35:10 +00:00
pMB - > stateB = AY_INACTIVE ;
2017-10-27 10:10:15 +00:00
if ( version > = 3 )
pMB - > state = ( MockingboardUnitState_e ) ( yamlLoadHelper . LoadUint ( SS_YAML_KEY_MB_UNIT_STATE ) & 7 ) ;
2015-12-05 16:50:27 +00:00
yamlLoadHelper . PopMap ( ) ;
//
2017-10-21 17:47:22 +00:00
if ( version = = 1 )
{
StartTimer1_LoadStateV1 ( pMB ) ; // Attempt to start timer
}
else // version >= 2
{
if ( pMB - > bTimer1Active )
StartTimer1 ( pMB ) ; // Attempt to start timer
}
2015-12-05 16:50:27 +00:00
2020-10-11 15:08:05 +00:00
if ( pMB - > bTimer1Active )
{
const UINT id = nDeviceNum * kNumTimersPer6522 + 0 ; // TIMER1
SyncEvent * pSyncEvent = g_syncEvent [ id ] ;
pSyncEvent - > SetCycles ( pMB - > sy6522 . TIMER1_COUNTER . w + kExtraTimerCycles ) ; // NB. use COUNTER, not LATCH
g_SynchronousEventMgr . Insert ( pSyncEvent ) ;
}
if ( pMB - > bTimer2Active )
{
const UINT id = nDeviceNum * kNumTimersPer6522 + 1 ; // TIMER2
SyncEvent * pSyncEvent = g_syncEvent [ id ] ;
pSyncEvent - > SetCycles ( pMB - > sy6522 . TIMER2_COUNTER . w + kExtraTimerCycles ) ; // NB. use COUNTER, not LATCH
g_SynchronousEventMgr . Insert ( pSyncEvent ) ;
}
2015-12-05 16:50:27 +00:00
nDeviceNum + + ;
pMB + + ;
}
2019-06-28 20:34:34 +00:00
AY8910_InitClock ( ( int ) Get6502BaseClock ( ) ) ;
2015-12-05 16:50:27 +00:00
2019-02-23 10:22:52 +00:00
// NB. g_SoundcardType & g_bPhasorEnable setup in MB_InitializeIO() -> MB_SetSoundcardType()
2015-12-05 16:50:27 +00:00
return true ;
}
void Phasor_SaveSnapshot ( YamlSaveHelper & yamlSaveHelper , const UINT uSlot )
{
if ( uSlot ! = 4 )
throw std : : string ( " Card: Phasor only supported in slot-4 " ) ;
UINT nDeviceNum = 0 ;
SY6522_AY8910 * pMB = & g_MB [ 0 ] ; // fixme: Phasor uses MB's slot4(2x6522), slot4(2xSSI263), but slot4+5(4xAY8910)
2017-10-27 10:10:15 +00:00
YamlSaveHelper : : Slot slot ( yamlSaveHelper , Phasor_GetSnapshotCardName ( ) , uSlot , kUNIT_VERSION ) ; // fixme: object should be just 1 Mockingboard card & it will know its slot
2015-12-05 16:50:27 +00:00
YamlSaveHelper : : Label state ( yamlSaveHelper , " %s: \n " , SS_YAML_KEY_STATE ) ;
2020-04-19 20:00:37 +00:00
yamlSaveHelper . SaveUint ( SS_YAML_KEY_PHASOR_MODE , g_phasorMode ) ;
2021-03-23 22:01:41 +00:00
yamlSaveHelper . SaveBool ( SS_YAML_KEY_VOTRAX_PHONEME , pMB - > ssi263 . GetVotraxPhoneme ( ) ) ;
2015-12-05 16:50:27 +00:00
for ( UINT i = 0 ; i < NUM_PHASOR_UNITS ; i + + )
{
YamlSaveHelper : : Label unit ( yamlSaveHelper , " %s%d: \n " , SS_YAML_KEY_PHASOR_UNIT , i ) ;
SaveSnapshotSY6522 ( yamlSaveHelper , pMB - > sy6522 ) ;
AY8910_SaveSnapshot ( yamlSaveHelper , nDeviceNum + 0 , std : : string ( " -A " ) ) ;
AY8910_SaveSnapshot ( yamlSaveHelper , nDeviceNum + 1 , std : : string ( " -B " ) ) ;
2021-03-23 22:01:41 +00:00
pMB - > ssi263 . SaveSnapshot ( yamlSaveHelper ) ;
2015-12-05 16:50:27 +00:00
2017-10-27 10:10:15 +00:00
yamlSaveHelper . SaveHexUint4 ( SS_YAML_KEY_MB_UNIT_STATE , pMB - > state ) ;
2019-11-11 17:35:10 +00:00
yamlSaveHelper . SaveHexUint4 ( SS_YAML_KEY_MB_UNIT_STATE_B , pMB - > stateB ) ;
2016-03-04 21:26:14 +00:00
yamlSaveHelper . SaveHexUint4 ( SS_YAML_KEY_AY_CURR_REG , pMB - > nAYCurrentRegister ) ;
2016-03-01 22:31:17 +00:00
yamlSaveHelper . Save ( " %s: %s # Not supported \n " , SS_YAML_KEY_TIMER1_IRQ , " false " ) ;
yamlSaveHelper . Save ( " %s: %s # Not supported \n " , SS_YAML_KEY_TIMER2_IRQ , " false " ) ;
yamlSaveHelper . Save ( " %s: %s # Not supported \n " , SS_YAML_KEY_SPEECH_IRQ , " false " ) ;
2017-10-21 17:47:22 +00:00
yamlSaveHelper . SaveBool ( SS_YAML_KEY_TIMER1_ACTIVE , pMB - > bTimer1Active ) ;
yamlSaveHelper . SaveBool ( SS_YAML_KEY_TIMER2_ACTIVE , pMB - > bTimer2Active ) ;
2015-12-05 16:50:27 +00:00
nDeviceNum + = 2 ;
pMB + + ;
}
}
bool Phasor_LoadSnapshot ( YamlLoadHelper & yamlLoadHelper , UINT slot , UINT version )
{
if ( slot ! = 4 ) // fixme
throw std : : string ( " Card: wrong slot " ) ;
2017-10-27 10:10:15 +00:00
if ( version < 1 | | version > kUNIT_VERSION )
2015-12-05 16:50:27 +00:00
throw std : : string ( " Card: wrong version " ) ;
2020-04-26 17:07:38 +00:00
if ( version < 6 )
yamlLoadHelper . LoadUint ( SS_YAML_KEY_PHASOR_CLOCK_SCALE_FACTOR ) ; // Consume redundant data
2020-04-19 20:00:37 +00:00
UINT phasorMode = yamlLoadHelper . LoadUint ( SS_YAML_KEY_PHASOR_MODE ) ;
if ( version < 6 )
{
if ( phasorMode = = 0 )
phasorMode = PH_Mockingboard ;
else
phasorMode = PH_Phasor ;
}
g_phasorMode = ( PHASOR_MODE ) phasorMode ;
2020-04-26 17:07:38 +00:00
g_PhasorClockScaleFactor = ( g_phasorMode = = PH_Phasor ) ? 2 : 1 ;
2015-12-05 16:50:27 +00:00
AY8910UpdateSetCycles ( ) ;
UINT nDeviceNum = 0 ;
SY6522_AY8910 * pMB = & g_MB [ 0 ] ;
2021-03-23 22:01:41 +00:00
bool isVotrax = ( version > = 6 ) ? yamlLoadHelper . LoadBool ( SS_YAML_KEY_VOTRAX_PHONEME ) : false ;
pMB - > ssi263 . SetVotraxPhoneme ( isVotrax ) ;
2015-12-05 16:50:27 +00:00
for ( UINT i = 0 ; i < NUM_PHASOR_UNITS ; i + + )
{
2020-11-10 20:33:55 +00:00
char szNum [ 2 ] = { char ( ' 0 ' + i ) , 0 } ;
2015-12-05 16:50:27 +00:00
std : : string unit = std : : string ( SS_YAML_KEY_MB_UNIT ) + std : : string ( szNum ) ;
if ( ! yamlLoadHelper . GetSubMap ( unit ) )
throw std : : string ( " Card: Expected key: " ) + std : : string ( unit ) ;
2019-06-15 16:41:53 +00:00
LoadSnapshotSY6522 ( yamlLoadHelper , pMB - > sy6522 , version ) ;
2019-08-26 19:10:33 +00:00
UpdateIFR ( pMB , 0 , pMB - > sy6522 . IFR ) ; // Assert any pending IRQs (GH#677)
2015-12-05 16:50:27 +00:00
AY8910_LoadSnapshot ( yamlLoadHelper , nDeviceNum + 0 , std : : string ( " -A " ) ) ;
AY8910_LoadSnapshot ( yamlLoadHelper , nDeviceNum + 1 , std : : string ( " -B " ) ) ;
2021-03-23 22:01:41 +00:00
pMB - > ssi263 . LoadSnapshot ( yamlLoadHelper , nDeviceNum , PH_Phasor , version ) ; // Pre: SetVotraxPhoneme()
2015-12-05 16:50:27 +00:00
2016-02-24 22:38:59 +00:00
pMB - > nAYCurrentRegister = yamlLoadHelper . LoadUint ( SS_YAML_KEY_AY_CURR_REG ) ;
2016-03-01 22:31:17 +00:00
yamlLoadHelper . LoadBool ( SS_YAML_KEY_TIMER1_IRQ ) ; // Consume
yamlLoadHelper . LoadBool ( SS_YAML_KEY_TIMER2_IRQ ) ; // Consume
yamlLoadHelper . LoadBool ( SS_YAML_KEY_SPEECH_IRQ ) ; // Consume
2015-12-05 16:50:27 +00:00
2017-10-21 17:47:22 +00:00
if ( version > = 2 )
{
pMB - > bTimer1Active = yamlLoadHelper . LoadBool ( SS_YAML_KEY_TIMER1_ACTIVE ) ;
pMB - > bTimer2Active = yamlLoadHelper . LoadBool ( SS_YAML_KEY_TIMER2_ACTIVE ) ;
}
2017-10-27 10:10:15 +00:00
pMB - > state = AY_INACTIVE ;
2019-11-11 17:35:10 +00:00
pMB - > stateB = AY_INACTIVE ;
2017-10-27 10:10:15 +00:00
if ( version > = 3 )
pMB - > state = ( MockingboardUnitState_e ) ( yamlLoadHelper . LoadUint ( SS_YAML_KEY_MB_UNIT_STATE ) & 7 ) ;
2019-11-11 17:35:10 +00:00
if ( version > = 5 )
pMB - > stateB = ( MockingboardUnitState_e ) ( yamlLoadHelper . LoadUint ( SS_YAML_KEY_MB_UNIT_STATE_B ) & 7 ) ;
2017-10-27 10:10:15 +00:00
2015-12-05 16:50:27 +00:00
yamlLoadHelper . PopMap ( ) ;
//
2017-10-21 17:47:22 +00:00
if ( version = = 1 )
{
StartTimer1_LoadStateV1 ( pMB ) ; // Attempt to start timer
}
else // version >= 2
{
if ( pMB - > bTimer1Active )
StartTimer1 ( pMB ) ; // Attempt to start timer
}
2015-12-05 16:50:27 +00:00
2020-10-11 15:08:05 +00:00
if ( pMB - > bTimer1Active )
{
2020-10-18 18:32:18 +00:00
const UINT id = ( nDeviceNum / 2 ) * kNumTimersPer6522 + 0 ; // TIMER1
2020-10-11 15:08:05 +00:00
SyncEvent * pSyncEvent = g_syncEvent [ id ] ;
pSyncEvent - > SetCycles ( pMB - > sy6522 . TIMER1_COUNTER . w + kExtraTimerCycles ) ; // NB. use COUNTER, not LATCH
g_SynchronousEventMgr . Insert ( pSyncEvent ) ;
}
if ( pMB - > bTimer2Active )
{
2020-10-18 18:32:18 +00:00
const UINT id = ( nDeviceNum / 2 ) * kNumTimersPer6522 + 1 ; // TIMER2
2020-10-11 15:08:05 +00:00
SyncEvent * pSyncEvent = g_syncEvent [ id ] ;
pSyncEvent - > SetCycles ( pMB - > sy6522 . TIMER2_COUNTER . w + kExtraTimerCycles ) ; // NB. use COUNTER, not LATCH
g_SynchronousEventMgr . Insert ( pSyncEvent ) ;
}
2015-12-05 16:50:27 +00:00
nDeviceNum + = 2 ;
pMB + + ;
}
2019-06-28 20:34:34 +00:00
AY8910_InitClock ( ( int ) ( Get6502BaseClock ( ) * g_PhasorClockScaleFactor ) ) ;
2015-12-05 16:50:27 +00:00
2019-02-23 10:22:52 +00:00
// NB. g_SoundcardType & g_bPhasorEnable setup in MB_InitializeIO() -> MB_SetSoundcardType()
2015-12-05 16:50:27 +00:00
return true ;
}