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"
2022-02-05 18:48:36 +00:00
# include "6522.h"
//#include "SaveState_Structs_v1.h"
2015-02-13 22:40:53 +00:00
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 ;
2022-02-05 18:48:36 +00:00
SSI263 ssi263 ;
2006-02-25 20:50:29 +00:00
BYTE nAY8910Number ;
BYTE nAYCurrentRegister ;
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
SY6522_AY8910 ( void )
{
nAY8910Number = 0 ;
nAYCurrentRegister = 0 ;
state = AY_NOP0 ;
stateB = AY_NOP0 ;
2022-02-05 18:48:36 +00:00
// r6522 & ssi263 have already been default constructed
2021-04-05 16:44:59 +00:00
}
2015-04-11 21:24:54 +00:00
} ;
2006-02-25 20:50:29 +00:00
// Support 2 MB's, each with 2x SY6522/AY8910 pairs.
static SY6522_AY8910 g_MB [ NUM_AY8910 ] ;
2022-02-05 18:48:36 +00:00
const UINT kNumSyncEvents = NUM_SY6522 * SY6522 : : kNumTimersPer6522 ;
2020-10-11 15:08:05 +00:00
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 ;
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
//---------------------------------------------------------------------------
2022-02-12 18:42:58 +00:00
static bool IsAnyTimer1Active ( void )
{
bool active = false ;
for ( UINT i = 0 ; i < NUM_AY8910 ; i + + )
active | = g_MB [ i ] . sy6522 . IsTimer1Active ( ) ;
return active ;
}
//---------------------------------------------------------------------------
2021-03-23 22:01:41 +00:00
void MB_Get6522IrqDescription ( std : : string & desc )
{
for ( UINT i = 0 ; i < NUM_AY8910 ; i + + )
{
2022-02-05 18:48:36 +00:00
if ( g_MB [ i ] . sy6522 . GetReg ( SY6522 : : rIFR ) & SY6522 : : IFR_IRQ )
2021-03-23 22:01:41 +00:00
{
2022-02-05 18:48:36 +00:00
if ( g_MB [ i ] . sy6522 . GetReg ( SY6522 : : rIFR ) & SY6522 : : IxR_TIMER1 )
2021-03-23 22:01:41 +00:00
{
desc + = ( ( i & 1 ) = = 0 ) ? " A: " : " B: " ;
desc + = " TIMER1 " ;
}
2022-02-05 18:48:36 +00:00
if ( g_MB [ i ] . sy6522 . GetReg ( SY6522 : : rIFR ) & SY6522 : : IxR_TIMER2 )
2021-03-23 22:01:41 +00:00
{
desc + = ( ( i & 1 ) = = 0 ) ? " A: " : " B: " ;
desc + = " TIMER2 " ;
}
2022-02-05 18:48:36 +00:00
if ( g_MB [ i ] . sy6522 . GetReg ( SY6522 : : rIFR ) & SY6522 : : IxR_VOTRAX )
2021-03-23 22:01:41 +00:00
{
desc + = ( ( i & 1 ) = = 0 ) ? " A: " : " B: " ;
desc + = " VOTRAX " ;
}
2022-02-05 18:48:36 +00:00
if ( g_MB [ i ] . sy6522 . GetReg ( SY6522 : : rIFR ) & SY6522 : : IxR_SSI263 )
2021-03-23 22:01:41 +00:00
{
desc + = ( ( i & 1 ) = = 0 ) ? " A: " : " B: " ;
desc + = " SSI263 " ;
}
}
}
}
2006-02-25 20:50:29 +00:00
//-----------------------------------------------------------------------------
2022-02-05 18:48:36 +00:00
static void AY8910_Write ( BYTE nDevice , 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 )
2022-02-05 18:48:36 +00:00
pMB - > sy6522 . SetRegORA ( 0xff & ( pMB - > sy6522 . GetReg ( SY6522 : : rDDRA ) ^ 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
2022-02-05 18:48:36 +00:00
pMB - > sy6522 . SetRegORA ( AYReadReg ( nDevice + 2 * nAYDevice , pMB - > nAYCurrentRegister ) & ( pMB - > sy6522 . GetReg ( SY6522 : : rDDRA ) ^ 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
2022-02-05 18:48:36 +00:00
_AYWriteReg ( nDevice + 2 * nAYDevice , pMB - > nAYCurrentRegister , pMB - > sy6522 . GetReg ( SY6522 : : rORA ) ) ;
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_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.
2022-02-05 18:48:36 +00:00
if ( pMB - > sy6522 . GetReg ( SY6522 : : rORA ) < = 0x0F )
pMB - > nAYCurrentRegister = pMB - > sy6522 . GetReg ( SY6522 : : rORA ) & 0x0F ;
2017-10-27 10:10:15 +00:00
// 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
}
}
2022-02-05 18:48:36 +00:00
//-----------------------------------------------------------------------------
2021-02-06 18:02:35 +00:00
2022-02-05 18:48:36 +00:00
static void WriteToORB ( BYTE device )
{
BYTE value = g_MB [ device ] . sy6522 . Read ( SY6522 : : rORB ) ;
2021-02-06 18:02:35 +00:00
2022-02-05 18:48:36 +00:00
if ( ( device & 1 ) = = 0 & & // SC01 only at $Cn00 (not $Cn80)
g_MB [ device ] . sy6522 . Read ( SY6522 : : rPCR ) = = 0xB0 )
2021-02-06 18:02:35 +00:00
{
2022-02-05 18:48:36 +00:00
// Votrax speech data
const BYTE DDRB = g_MB [ device ] . sy6522 . Read ( SY6522 : : rDDRB ) ;
g_MB [ device ] . ssi263 . Votrax_Write ( ( value & DDRB ) | ( DDRB ^ 0xff ) ) ; // DDRB's zero bits (inputs) are high impedence, so output as 1 (GH#952)
return ;
2021-02-06 18:02:35 +00:00
}
2022-02-05 18:48:36 +00:00
# if DBG_MB_SS_CARD
if ( ( nDevice & 1 ) = = 1 )
AY8910_Write ( nDevice , nValue , 0 ) ;
# else
if ( g_bPhasorEnable )
2020-10-11 15:08:05 +00:00
{
2022-02-05 18:48:36 +00:00
int nAY_CS = ( g_phasorMode = = PH_Phasor ) ? ( ~ ( value > > 3 ) & 3 ) : 1 ;
2020-10-11 15:08:05 +00:00
2022-02-05 18:48:36 +00:00
if ( nAY_CS & 1 )
AY8910_Write ( device , value , 0 ) ;
2020-10-11 15:08:05 +00:00
2022-02-05 18:48:36 +00:00
if ( nAY_CS & 2 )
AY8910_Write ( device , value , 1 ) ;
2020-10-11 15:08:05 +00:00
}
else
{
2022-02-05 18:48:36 +00:00
AY8910_Write ( device , value , 0 ) ;
2020-10-11 15:08:05 +00:00
}
2022-02-05 18:48:36 +00:00
# endif
2020-10-11 15:08:05 +00:00
}
2022-02-05 18:48:36 +00:00
//-----------------------------------------------------------------------------
2020-10-11 15:08:05 +00:00
2022-02-05 18:48:36 +00:00
static void UpdateIFRandIRQ ( SY6522_AY8910 * pMB , BYTE clr_mask , BYTE set_mask )
{
pMB - > sy6522 . UpdateIFR ( clr_mask , set_mask ) ; // which calls MB_UpdateIRQ()
2020-10-11 15:08:05 +00:00
}
2022-02-05 18:48:36 +00:00
// Called from class SY6522
void MB_UpdateIRQ ( void )
2006-02-25 20:50:29 +00:00
{
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 + + )
2022-02-05 18:48:36 +00:00
bIRQ | = g_MB [ i ] . sy6522 . GetReg ( SY6522 : : rIFR ) & 0x80 ;
2006-05-02 21:56:28 +00:00
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
}
//===========================================================================
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:
2022-02-12 18:42:58 +00:00
// . MB_SyncEventCallback() on a TIMER1 (not TIMER2) underflow - when IsAnyTimer1Active() == true
// . MB_PeriodicUpdate() - when IsAnyTimer1Active() == false
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 ;
2022-02-12 18:42:58 +00:00
PerfMarker perfMarker ( ! IsAnyTimer1Active ( ) ? g_timeMB_NoTimer : g_timeMB_Timer ) ;
2020-05-23 15:23:06 +00:00
# endif
MB_UpdateInt ( ) ;
}
2006-02-25 20:50:29 +00:00
//-----------------------------------------------------------------------------
static bool MB_DSInit ( )
{
2021-10-18 19:23:46 +00:00
LogFileOutput ( " MB_DSInit \n " ) ;
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
2022-02-05 18:48:36 +00:00
for ( int id = 0 ; id < kNumSyncEvents ; id + + )
{
g_syncEvent [ id ] = new SyncEvent ( id , 0 , MB_SyncEventCallback ) ;
}
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 ;
2022-02-05 18:48:36 +00:00
const UINT id0 = i * SY6522 : : kNumTimersPer6522 + 0 ; // TIMER1
const UINT id1 = i * SY6522 : : kNumTimersPer6522 + 1 ; // TIMER2
g_MB [ i ] . sy6522 . InitSyncEvents ( g_syncEvent [ id0 ] , g_syncEvent [ id1 ] ) ;
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
}
}
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
}
//-----------------------------------------------------------------------------
2022-02-05 18:48:36 +00:00
void MB_Reset ( const bool powerCycle ) // CTRL+RESET or power-cycle
2012-08-12 11:03:38 +00:00
{
2022-02-05 18:48:36 +00:00
if ( ! g_bDSAvailable )
return ;
2019-09-05 19:42:34 +00:00
2022-02-05 18:48:36 +00:00
for ( int i = 0 ; i < NUM_AY8910 ; i + + )
2020-10-11 15:08:05 +00:00
{
2022-02-05 18:48:36 +00:00
g_MB [ i ] . sy6522 . Reset ( powerCycle ) ;
AY8910_reset ( i ) ;
g_MB [ i ] . nAYCurrentRegister = 0 ;
g_MB [ i ] . state = AY_INACTIVE ;
g_MB [ i ] . stateB = AY_INACTIVE ;
2020-10-11 15:08:05 +00:00
2021-03-23 22:01:41 +00:00
g_MB [ i ] . ssi263 . SetCardMode ( g_phasorMode ) ;
g_MB [ i ] . ssi263 . Reset ( ) ;
}
2022-02-05 18:48:36 +00:00
// Reset state
{
MB_SetCumulativeCycles ( ) ;
2012-08-12 11:03:38 +00:00
2022-02-05 18:48:36 +00:00
g_nMB_InActiveCycleCount = 0 ;
g_bMB_RegAccessedFlag = false ;
g_bMB_Active = false ;
2006-02-25 20:50:29 +00:00
2022-02-05 18:48:36 +00:00
g_phasorMode = PH_Mockingboard ;
g_PhasorClockScaleFactor = 1 ;
g_uLastMBUpdateCycle = 0 ;
g_cyclesThisAudioFrame = 0 ;
for ( int id = 0 ; id < kNumSyncEvents ; id + + )
{
if ( g_syncEvent [ id ] & & g_syncEvent [ id ] - > m_active )
g_SynchronousEventMgr . Remove ( id ) ;
}
// Not these, as they don't change on a CTRL+RESET or power-cycle:
// g_bMBAvailable = false;
// g_SoundcardType = CT_Empty; // Don't uncomment, else _ASSERT will fire in MB_Read() after an F2->MB_Reset()
// g_bPhasorEnable = false;
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
2022-02-05 18:48:36 +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
2022-02-05 18:48:36 +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 ;
2022-02-05 18:48:36 +00:00
if ( g_bPhasorEnable )
2006-02-25 20:50:29 +00:00
{
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 ;
2022-02-05 18:48:36 +00:00
if ( CS & 1 )
nRes | = g_MB [ nMB * NUM_DEVS_PER_MB + SY6522_DEVICE_A ] . sy6522 . Read ( nAddr & 0xf ) ;
2006-02-25 20:50:29 +00:00
2022-02-05 18:48:36 +00:00
if ( CS & 2 )
nRes | = g_MB [ nMB * NUM_DEVS_PER_MB + SY6522_DEVICE_B ] . sy6522 . Read ( nAddr & 0xf ) ;
2006-02-25 20:50:29 +00:00
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
2022-02-05 18:48:36 +00:00
nRes = g_MB [ nMB * NUM_DEVS_PER_MB + 1 ] . ssi263 . Read ( nExecutedCycles ) ; // SSI263 only drives bit7
2021-02-21 19:12:36 +00:00
if ( nAddr & 0x20 ) // Secondary SSI263
2022-02-05 18:48:36 +00:00
nRes = g_MB [ nMB * NUM_DEVS_PER_MB + 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)
2022-02-05 18:48:36 +00:00
const BYTE device = nMB * NUM_DEVS_PER_MB + ( ( nOffset < SY6522B_Offset ) ? SY6522_DEVICE_A : SY6522_DEVICE_B ) ;
const BYTE reg = nAddr & 0xf ;
return g_MB [ device ] . sy6522 . Read ( reg ) ;
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
2022-02-05 18:48:36 +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
2022-02-05 18:48:36 +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
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 ;
2022-02-05 18:48:36 +00:00
if ( g_bPhasorEnable )
2006-02-25 20:50:29 +00:00
{
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
2022-02-05 18:48:36 +00:00
if ( CS & 1 )
{
const BYTE device = nMB * NUM_DEVS_PER_MB + SY6522_DEVICE_A ;
const BYTE reg = nAddr & 0xf ;
g_MB [ device ] . sy6522 . Write ( reg , nValue ) ;
if ( reg = = SY6522 : : rORB )
WriteToORB ( device ) ;
}
2006-02-25 20:50:29 +00:00
2022-02-05 18:48:36 +00:00
if ( CS & 2 )
{
const BYTE device = nMB * NUM_DEVS_PER_MB + SY6522_DEVICE_B ;
const BYTE reg = nAddr & 0xf ;
g_MB [ device ] . sy6522 . Write ( reg , nValue ) ;
if ( reg = = SY6522 : : rORB )
WriteToORB ( device ) ;
}
2006-02-25 20:50:29 +00:00
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
2022-02-05 18:48:36 +00:00
g_MB [ nMB * NUM_DEVS_PER_MB + 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
2022-02-05 18:48:36 +00:00
g_MB [ nMB * NUM_DEVS_PER_MB + 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
}
2022-02-05 18:48:36 +00:00
const BYTE device = nMB * NUM_DEVS_PER_MB + ( ( nOffset < SY6522B_Offset ) ? SY6522_DEVICE_A : SY6522_DEVICE_B ) ;
const BYTE reg = nAddr & 0xf ;
g_MB [ device ] . sy6522 . Write ( reg , nValue ) ;
if ( reg = = SY6522 : : rORB )
WriteToORB ( device ) ;
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 )
2022-02-05 18:48:36 +00:00
g_MB [ nMB * NUM_DEVS_PER_MB + 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 )
2022-02-05 18:48:36 +00:00
g_MB [ nMB * NUM_DEVS_PER_MB + 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 ;
}
2022-02-05 18:48:36 +00:00
// Called by ContinueExecution() at the end of every execution period (~1000 cycles or ~3 cycles when MODE_STEPPING)
2019-11-10 15:52:07 +00:00
// 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 ) ;
2022-02-12 18:42:58 +00:00
if ( IsAnyTimer1Active ( ) )
2019-11-10 15:52:07 +00:00
return ;
2022-02-12 18:42:58 +00:00
// No 6522 TIMER1's are active, so periodically update AY8913's here...
2019-11-10 15:52:07 +00:00
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
}
//-----------------------------------------------------------------------------
2017-10-21 17:47:22 +00:00
// Called by:
2022-02-05 18:48:36 +00:00
// . CpuExecute() every ~1000 cycles @ 1MHz (or ~3 cycles when MODE_STEPPING)
2020-10-11 15:08:05 +00:00
// . 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
{
2022-02-05 18:48:36 +00:00
g_MB [ i ] . sy6522 . UpdateTimer1 ( nClocks ) ;
g_MB [ i ] . sy6522 . UpdateTimer2 ( 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
2022-02-05 18:48:36 +00:00
// Called on a 6522 TIMER1/2 underflow
2020-10-11 15:08:05 +00:00
static int MB_SyncEventCallback ( int id , int /*cycles*/ , ULONG uExecutedCycles )
{
2022-02-05 18:48:36 +00:00
MB_UpdateCycles ( uExecutedCycles ) ; // Underflow: so keep TIMER1/2 counters in sync
SY6522_AY8910 * pMB = & g_MB [ id / SY6522 : : kNumTimersPer6522 ] ;
2006-05-02 21:56:28 +00:00
2020-10-11 15:08:05 +00:00
if ( ( id & 1 ) = = 0 )
{
2022-02-05 18:48:36 +00:00
_ASSERT ( pMB - > sy6522 . IsTimer1Active ( ) ) ;
UpdateIFRandIRQ ( pMB , 0 , SY6522 : : IxR_TIMER1 ) ;
2020-10-11 15:08:05 +00:00
MB_Update ( ) ;
2017-10-24 21:28:22 +00:00
2022-02-05 18:48:36 +00:00
if ( ( pMB - > sy6522 . GetReg ( SY6522 : : rACR ) & SY6522 : : ACR_RUNMODE ) = = SY6522 : : ACR_RM_FREERUNNING )
2019-11-16 23:49:21 +00:00
{
2022-02-05 18:48:36 +00:00
pMB - > sy6522 . StartTimer1 ( ) ;
return pMB - > sy6522 . GetRegT1C ( ) + SY6522 : : kExtraTimerCycles ;
2019-11-16 23:49:21 +00:00
}
2022-02-05 18:48:36 +00:00
// One-shot mode
// - Phasor's playback code uses one-shot mode
pMB - > sy6522 . StopTimer1 ( ) ;
return 0 ; // Don't repeat event
2006-02-25 20:50:29 +00:00
}
2020-10-11 15:08:05 +00:00
else
{
2022-02-05 18:48:36 +00:00
// NB. Since not calling MB_Update(), then AppleWin doesn't (accurately?) support AY-playback using T2 (which is one-shot only)
_ASSERT ( pMB - > sy6522 . IsTimer2Active ( ) ) ;
UpdateIFRandIRQ ( pMB , 0 , SY6522 : : IxR_TIMER2 ) ;
2019-11-16 23:49:21 +00:00
2022-02-05 18:48:36 +00:00
pMB - > sy6522 . StopTimer2 ( ) ; // TIMER2 only runs in one-shot mode
2020-10-11 15:08:05 +00:00
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 )
{
2022-02-05 18:48:36 +00:00
UpdateIFRandIRQ ( & g_MB [ nDevice ] , clr_mask , set_mask ) ;
2021-03-23 22:01:41 +00:00
}
BYTE MB_GetPCR ( BYTE nDevice )
{
2022-02-05 18:48:36 +00:00
return g_MB [ nDevice ] . sy6522 . GetReg ( SY6522 : : rPCR ) ;
2006-02-25 20:50:29 +00:00
}
//===========================================================================
2022-02-05 18:48:36 +00:00
# include "SaveState_Structs_v1.h"
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
2022-02-05 18:48:36 +00:00
pMB - > sy6522 . GetRegs ( ( BYTE * ) & pSS - > Unit [ i ] . RegsSY6522 ) ; // continuous 16-byte array
2021-03-07 13:11:22 +00:00
// 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 ;
2022-02-05 18:48:36 +00:00
pSS - > Unit [ i ] . bTimer1Active = pMB - > sy6522 . IsTimer1Active ( ) ;
pSS - > Unit [ i ] . bTimer2Active = pMB - > sy6522 . IsTimer2Active ( ) ;
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
2022-02-05 18:48:36 +00:00
// 8: Moved Timer1 & Timer2 active to 6522 sub-unit
// Removed Timer1/Timer2/Speech IRQ Pending
const UINT kUNIT_VERSION = 8 ;
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_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
2022-02-05 18:48:36 +00:00
# define SS_YAML_KEY_TIMER1_IRQ "Timer1 IRQ Pending" // v8: deprecated
# define SS_YAML_KEY_TIMER2_IRQ "Timer2 IRQ Pending" // v8: deprecated
# define SS_YAML_KEY_SPEECH_IRQ "Speech IRQ Pending" // v8: deprecated
# define SS_YAML_KEY_TIMER1_ACTIVE "Timer1 Active" // v8: move to 6522 sub-unit
# define SS_YAML_KEY_TIMER2_ACTIVE "Timer2 Active" // v8: move to 6522 sub-unit
2015-12-05 16:50:27 +00:00
# define SS_YAML_KEY_PHASOR_UNIT "Unit"
2022-02-05 18:48:36 +00:00
# define SS_YAML_KEY_PHASOR_CLOCK_SCALE_FACTOR "Clock Scale Factor" // v6: deprecated
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 ;
}
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 ) ;
2022-02-05 18:48:36 +00:00
pMB - > sy6522 . SaveSnapshot ( yamlSaveHelper ) ;
2015-12-05 16:50:27 +00:00
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 ) ;
2015-12-05 16:50:27 +00:00
nDeviceNum + + ;
pMB + + ;
}
}
bool MB_LoadSnapshot ( YamlLoadHelper & yamlLoadHelper , UINT slot , UINT version )
{
if ( slot ! = 4 & & slot ! = 5 ) // fixme
2022-01-30 21:25:40 +00:00
Card : : ThrowErrorInvalidSlot ( CT_MockingboardC , slot ) ;
2015-12-05 16:50:27 +00:00
2017-10-27 10:10:15 +00:00
if ( version < 1 | | version > kUNIT_VERSION )
2022-01-30 21:25:40 +00:00
Card : : ThrowErrorInvalidVersion ( CT_MockingboardC , version ) ;
2015-12-05 16:50:27 +00:00
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 ) )
2021-12-18 16:37:28 +00:00
throw std : : runtime_error ( " Card: Expected key: " + unit ) ;
2015-12-05 16:50:27 +00:00
2022-02-05 18:48:36 +00:00
pMB - > sy6522 . LoadSnapshot ( yamlLoadHelper , version ) ;
UpdateIFRandIRQ ( pMB , 0 , pMB - > sy6522 . GetReg ( SY6522 : : rIFR ) ) ; // 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 ) ;
2015-12-05 16:50:27 +00:00
2022-02-05 18:48:36 +00:00
if ( version = = 1 )
2017-10-21 17:47:22 +00:00
{
2022-02-05 18:48:36 +00:00
pMB - > sy6522 . SetTimersActiveFromSnapshot ( false , false , version ) ;
}
else if ( version > = 2 & & version < = 7 )
{
bool timer1Active = yamlLoadHelper . LoadBool ( SS_YAML_KEY_TIMER1_ACTIVE ) ;
bool timer2Active = yamlLoadHelper . LoadBool ( SS_YAML_KEY_TIMER2_ACTIVE ) ;
pMB - > sy6522 . SetTimersActiveFromSnapshot ( timer1Active , timer2Active , version ) ;
}
if ( version < = 7 )
{
yamlLoadHelper . LoadBool ( SS_YAML_KEY_TIMER1_IRQ ) ; // Consume redundant data
yamlLoadHelper . LoadBool ( SS_YAML_KEY_TIMER2_IRQ ) ; // Consume redundant data
yamlLoadHelper . LoadBool ( SS_YAML_KEY_SPEECH_IRQ ) ; // Consume redundant data
2017-10-21 17:47:22 +00:00
}
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 ( ) ;
//
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 )
2021-12-18 16:37:28 +00:00
throw std : : runtime_error ( " Card: Phasor only supported in slot-4 " ) ;
2015-12-05 16:50:27 +00:00
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 ) ;
2022-02-05 18:48:36 +00:00
pMB - > sy6522 . SaveSnapshot ( yamlSaveHelper ) ;
2015-12-05 16:50:27 +00:00
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 ) ;
2015-12-05 16:50:27 +00:00
nDeviceNum + = 2 ;
pMB + + ;
}
}
bool Phasor_LoadSnapshot ( YamlLoadHelper & yamlLoadHelper , UINT slot , UINT version )
{
if ( slot ! = 4 ) // fixme
2022-01-30 21:25:40 +00:00
Card : : ThrowErrorInvalidSlot ( CT_Phasor , slot ) ;
2015-12-05 16:50:27 +00:00
2017-10-27 10:10:15 +00:00
if ( version < 1 | | version > kUNIT_VERSION )
2022-01-30 21:25:40 +00:00
Card : : ThrowErrorInvalidVersion ( CT_Phasor , version ) ;
2015-12-05 16:50:27 +00:00
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 ) )
2021-12-18 16:37:28 +00:00
throw std : : runtime_error ( " Card: Expected key: " + unit ) ;
2015-12-05 16:50:27 +00:00
2022-02-05 18:48:36 +00:00
pMB - > sy6522 . LoadSnapshot ( yamlLoadHelper , version ) ;
UpdateIFRandIRQ ( pMB , 0 , pMB - > sy6522 . GetReg ( SY6522 : : rIFR ) ) ; // 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 ) ;
2015-12-05 16:50:27 +00:00
2022-02-05 18:48:36 +00:00
if ( version = = 1 )
{
pMB - > sy6522 . SetTimersActiveFromSnapshot ( false , false , version ) ;
}
else if ( version > = 2 & & version < = 7 )
2017-10-21 17:47:22 +00:00
{
2022-02-05 18:48:36 +00:00
bool timer1Active = yamlLoadHelper . LoadBool ( SS_YAML_KEY_TIMER1_ACTIVE ) ;
bool timer2Active = yamlLoadHelper . LoadBool ( SS_YAML_KEY_TIMER2_ACTIVE ) ;
pMB - > sy6522 . SetTimersActiveFromSnapshot ( timer1Active , timer2Active , version ) ;
}
if ( version < = 7 )
{
yamlLoadHelper . LoadBool ( SS_YAML_KEY_TIMER1_IRQ ) ; // Consume redundant data
yamlLoadHelper . LoadBool ( SS_YAML_KEY_TIMER2_IRQ ) ; // Consume redundant data
yamlLoadHelper . LoadBool ( SS_YAML_KEY_SPEECH_IRQ ) ; // Consume redundant data
2017-10-21 17:47:22 +00:00
}
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 ( ) ;
//
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 ;
}