/* 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 Copyright (C) 2006-2007, Tom Charlesworth, Michael Pohoreski 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 * */ // 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" #include "Mockingboard.h" #include "MockingboardDefs.h" #include "6522.h" #include "Core.h" #include "CardManager.h" #include "CPU.h" #include "Log.h" #include "Memory.h" #include "SoundCore.h" #include "SynchronousEventManager.h" #include "YamlHelper.h" #include "AY8910.h" #include "SSI263.h" #define DBG_MB_SS_CARD 0 // NB. From UI, select Mockingboard (not Phasor) //--------------------------------------------------------------------------- MockingboardCard::MockingboardCard(UINT slot, SS_CARDTYPE type) : Card(type, slot), m_MBSubUnit{slot, slot} { m_lastCumulativeCycle = 0; m_lastAYUpdateCycle = 0; for (UINT i = 0; i < NUM_VOICES; i++) m_ppAYVoiceBuffer[NUM_VOICES] = NULL; m_inActiveCycleCount = 0; m_regAccessedFlag = false; m_isActive = false; m_phasorEnable = (QueryType() == CT_Phasor); m_phasorMode = PH_Mockingboard; m_phasorClockScaleFactor = 1; m_lastMBUpdateCycle = 0; m_numSamplesError = 0; // for (int id = 0; id < kNumSyncEvents; id++) { int syncId = (m_slot << 4) + id; // NB. Encode the slot# into the id - used by MB_SyncEventCallback() m_syncEvent[id] = new SyncEvent(syncId, 0, MB_SyncEventCallback); } for (UINT i = 0; i < NUM_VOICES; i++) m_ppAYVoiceBuffer[i] = new short[MAX_SAMPLES]; // Buffer can hold a max of 0.37 seconds worth of samples (16384/44100) for (UINT i = 0; i < NUM_SUBUNITS_PER_MB; i++) { m_MBSubUnit[i].nAY8910Number = i; const UINT id0 = i * SY6522::kNumTimersPer6522 + 0; // TIMER1 const UINT id1 = i * SY6522::kNumTimersPer6522 + 1; // TIMER2 m_MBSubUnit[i].sy6522.InitSyncEvents(m_syncEvent[id0], m_syncEvent[id1]); m_MBSubUnit[i].ssi263.SetDevice(i); } AY8910_InitAll((int)g_fCurrentCLK6502, SAMPLE_RATE); LogFileOutput("MockingboardCard::ctor: AY8910_InitAll()\n"); Reset(true); LogFileOutput("MockingboardCard::ctor: Reset()\n"); } MockingboardCard::~MockingboardCard(void) { Destroy(); } //--------------------------------------------------------------------------- bool MockingboardCard::IsAnyTimer1Active(void) { bool active = false; for (UINT i = 0; i < NUM_SUBUNITS_PER_MB; i++) active |= m_MBSubUnit[i].sy6522.IsTimer1Active(); return active; } //--------------------------------------------------------------------------- #ifdef _DEBUG void MockingboardCard::Get6522IrqDescription(std::string& desc) { bool isIRQ = false; for (UINT i = 0; i < NUM_SUBUNITS_PER_MB; i++) { if (m_MBSubUnit[i].sy6522.GetReg(SY6522::rIFR) & SY6522::IFR_IRQ) { isIRQ = true; break; } } if (!isIRQ) return; // desc += "Slot-"; desc += m_slot; desc += ": "; for (UINT i = 0; i < NUM_SUBUNITS_PER_MB; i++) { if (m_MBSubUnit[i].sy6522.GetReg(SY6522::rIFR) & SY6522::IFR_IRQ) { if (m_MBSubUnit[i].sy6522.GetReg(SY6522::rIFR) & SY6522::IxR_TIMER1) { desc += ((i&1)==0) ? "A:" : "B:"; desc += "TIMER1 "; } if (m_MBSubUnit[i].sy6522.GetReg(SY6522::rIFR) & SY6522::IxR_TIMER2) { desc += ((i&1)==0) ? "A:" : "B:"; desc += "TIMER2 "; } if (m_MBSubUnit[i].sy6522.GetReg(SY6522::rIFR) & SY6522::IxR_VOTRAX) { desc += ((i&1)==0) ? "A:" : "B:"; desc += "VOTRAX "; } if (m_MBSubUnit[i].sy6522.GetReg(SY6522::rIFR) & SY6522::IxR_SSI263) { desc += ((i&1)==0) ? "A:" : "B:"; desc += "SSI263 "; } } } desc += "\n"; } #endif //----------------------------------------------------------------------------- void MockingboardCard::WriteToORB(BYTE subunit) { BYTE value = m_MBSubUnit[subunit].sy6522.Read(SY6522::rORB); if (subunit == 0 && // SC01 only at $Cn00 (not $Cn80) m_MBSubUnit[subunit].sy6522.Read(SY6522::rPCR) == 0xB0) { // Votrax speech data const BYTE DDRB = m_MBSubUnit[subunit].sy6522.Read(SY6522::rDDRB); m_MBSubUnit[subunit].ssi263.Votrax_Write((value & DDRB) | (DDRB ^ 0xff)); // DDRB's zero bits (inputs) are high impedence, so output as 1 (GH#952) return; } #if DBG_MB_SS_CARD if ((subunit & 1) == 1) AY8910_Write(subunit, 0, nValue); #else if (m_phasorEnable) { const int kAY0 = 2; // bit4=0 (active low) selects the 1st AY8913, ie. the only AY8913 in Mockingboard mode (confirmed on real Phasor h/w) const int kAY1 = 1; // bit3=0 (active low) selects the 2nd AY8913 attached to this 6522 (unavailable in Mockingboard mode) int nAY_CS = (m_phasorMode == PH_Phasor) ? (~(value >> 3) & 3) : kAY0; if (nAY_CS & kAY0) AY8910_Write(subunit, AY8913_DEVICE_A, value); if (nAY_CS & kAY1) AY8910_Write(subunit, AY8913_DEVICE_B, value); } else { AY8910_Write(subunit, AY8913_DEVICE_A, value); } #endif } //----------------------------------------------------------------------------- void MockingboardCard::AY8910_Write(BYTE subunit, BYTE ay, BYTE value) { m_regAccessedFlag = true; MB_SUBUNIT* pMB = &m_MBSubUnit[subunit]; if ((value & 4) == 0) { // RESET: Reset AY8910 only AY8910_reset(subunit, ay); } else { // Determine the AY8910 inputs int nBDIR = (value & 2) ? 1 : 0; const int nBC2 = 1; // Hardwired to +5V int nBC1 = value & 1; MockingboardUnitState_e nAYFunc = (MockingboardUnitState_e) ((nBDIR<<2) | (nBC2<<1) | nBC1); MockingboardUnitState_e& state = (ay == AY8913_DEVICE_A) ? pMB->state : pMB->stateB; // GH#659 #if _DEBUG if (!m_phasorEnable) _ASSERT(ay == AY8913_DEVICE_A); if (nAYFunc == AY_WRITE || nAYFunc == AY_LATCH) _ASSERT(state == AY_INACTIVE); #endif if (state == AY_INACTIVE) // GH#320: functions only work from inactive state { switch (nAYFunc) { case AY_INACTIVE: // 4: INACTIVE break; case AY_READ: // 5: READ FROM PSG (need to set DDRA to input) if (m_phasorEnable && m_phasorMode == PH_EchoPlus) 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 else pMB->sy6522.SetRegORA( AYReadReg(subunit, ay, pMB->nAYCurrentRegister) & (pMB->sy6522.GetReg(SY6522::rDDRA) ^ 0xff) ); break; case AY_WRITE: // 6: WRITE TO PSG _AYWriteReg(subunit, ay, pMB->nAYCurrentRegister, pMB->sy6522.GetReg(SY6522::rORA)); break; 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.GetReg(SY6522::rORA) <= 0x0F) pMB->nAYCurrentRegister = pMB->sy6522.GetReg(SY6522::rORA) & 0x0F; // else Pro-Mockingboard (clone from HK) break; } } state = nAYFunc; } } //----------------------------------------------------------------------------- void MockingboardCard::UpdateIFRandIRQ(MB_SUBUNIT* pMB, BYTE clr_mask, BYTE set_mask) { pMB->sy6522.UpdateIFR(clr_mask, set_mask); // which calls UpdateIRQ() } //--------------------------------------------------------------------------- // Called from class SY6522 void MockingboardCard::UpdateIRQ(void) { // Now update the IRQ signal from all 6522s // . OR-sum of all active TIMER1, TIMER2 & SPEECH sources (from all 6522s) UINT bIRQ = 0; for (UINT i = 0; i < NUM_SUBUNITS_PER_MB; i++) bIRQ |= m_MBSubUnit[i].sy6522.GetReg(SY6522::rIFR) & 0x80; // NB. Mockingboard generates IRQ on both 6522s: // . 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) // Phasor's SSI263 IRQ (A/!R) line is *also* wired directly to the 6502's IRQ (as well as the 6522's CA1) if (bIRQ) CpuIrqAssert(IS_6522); else CpuIrqDeassert(IS_6522); } //--------------------------------------------------------------------------- // Called from class SSI263 UINT64 MockingboardCard::GetLastCumulativeCycles(void) { return m_lastCumulativeCycle; } void MockingboardCard::UpdateIFR(BYTE nDevice, BYTE clr_mask, BYTE set_mask) { UpdateIFRandIRQ(&m_MBSubUnit[nDevice], clr_mask, set_mask); } BYTE MockingboardCard::GetPCR(BYTE nDevice) { return m_MBSubUnit[nDevice].sy6522.GetReg(SY6522::rPCR); } //=========================================================================== // Called by: // . MB_SyncEventCallback() -> MockingboardCardManager::UpdateSoundBuffer() on a TIMER1 (not TIMER2) underflow - when IsAnyTimer1Active() == true (for any MB) // . MockingboardCardManager::Update() - when IsAnyTimer1Active() == false (for all MB's) UINT MockingboardCard::MB_Update(void) { 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 /m_lastCumulativeCycle/ was last set 50 frame ago) AY8910UpdateSetCycles(); // TODO: // If any AY regs have changed then push them out to the AY chip return 0; } // if (!m_regAccessedFlag) { if (!m_inActiveCycleCount) { m_inActiveCycleCount = g_nCumulativeCycles; } else if (g_nCumulativeCycles - m_inActiveCycleCount > (unsigned __int64)g_fCurrentCLK6502 / 10) { // After 0.1 sec of Apple time, assume MB is not active m_isActive = false; } } else { m_inActiveCycleCount = 0; m_regAccessedFlag = false; m_isActive = true; } // // 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 (m_lastMBUpdateCycle == 0) m_lastMBUpdateCycle = m_lastCumulativeCycle; // Initial call to MB_Update() after reset/power-cycle _ASSERT(m_lastCumulativeCycle >= m_lastMBUpdateCycle); double updateInterval = (double)(m_lastCumulativeCycle - m_lastMBUpdateCycle); if (updateInterval < kMinimumUpdateInterval) return 0; if (updateInterval > kMaximumUpdateInterval) updateInterval = kMaximumUpdateInterval; m_lastMBUpdateCycle = m_lastCumulativeCycle; const double nIrqFreq = g_fCurrentCLK6502 / updateInterval + 0.5; // Round-up const int nNumSamplesPerPeriod = (int)((double)SAMPLE_RATE / nIrqFreq); // Eg. For 60Hz this is 735 int nNumSamples = nNumSamplesPerPeriod + m_numSamplesError; // Apply correction if (nNumSamples <= 0) nNumSamples = 0; if (nNumSamples > 2 * nNumSamplesPerPeriod) nNumSamples = 2 * nNumSamplesPerPeriod; if (nNumSamples > MAX_SAMPLES) nNumSamples = MAX_SAMPLES; // Clamp to prevent buffer overflow if (nNumSamples) { for (BYTE subunit = 0; subunit < NUM_SUBUNITS_PER_MB; subunit++) { for (BYTE ay = 0; ay < NUM_AY8913_PER_SUBUNIT; ay++) { const UINT chip = subunit * NUM_AY8913_PER_SUBUNIT + ay; AY8910Update(subunit, ay, &m_ppAYVoiceBuffer[chip * NUM_VOICES_PER_AY8913], nNumSamples); } } } return (UINT) nNumSamples; } //----------------------------------------------------------------------------- // NB. Called when /g_fCurrentCLK6502/ changes void MockingboardCard::ReinitializeClock(void) { AY8910_InitClock((int)g_fCurrentCLK6502); // todo: account for g_PhasorClockScaleFactor? // NB. Other calls to AY8910_InitClock() use the constant CLK_6502 } //----------------------------------------------------------------------------- void MockingboardCard::Destroy(void) { for (UINT i = 0; i < NUM_SSI263; i++) m_MBSubUnit[i].ssi263.DSUninit(); for (UINT i = 0; i < NUM_VOICES; i++) { delete[] m_ppAYVoiceBuffer[i]; m_ppAYVoiceBuffer[i] = NULL; } for (UINT id = 0; id < kNumSyncEvents; id++) { if (m_syncEvent[id] && m_syncEvent[id]->m_active) g_SynchronousEventMgr.Remove(m_syncEvent[id]->m_id); delete m_syncEvent[id]; m_syncEvent[id] = NULL; } } //----------------------------------------------------------------------------- void MockingboardCard::Reset(const bool powerCycle) // CTRL+RESET or power-cycle { for (BYTE subunit = 0; subunit < NUM_SUBUNITS_PER_MB; subunit++) { m_MBSubUnit[subunit].sy6522.Reset(powerCycle); for (BYTE ay = 0; ay < NUM_AY8913_PER_SUBUNIT; ay++) AY8910_reset(subunit, ay); m_MBSubUnit[subunit].nAYCurrentRegister = 0; m_MBSubUnit[subunit].state = AY_INACTIVE; m_MBSubUnit[subunit].stateB = AY_INACTIVE; m_MBSubUnit[subunit].ssi263.SetCardMode(m_phasorMode); m_MBSubUnit[subunit].ssi263.Reset(); } // Reset state { SetCumulativeCycles(); m_inActiveCycleCount = 0; m_regAccessedFlag = false; m_isActive = false; m_phasorMode = PH_Mockingboard; m_phasorClockScaleFactor = 1; m_lastMBUpdateCycle = 0; for (int id = 0; id < kNumSyncEvents; id++) { if (m_syncEvent[id] && m_syncEvent[id]->m_active) g_SynchronousEventMgr.Remove(m_syncEvent[id]->m_id); } // Not this, since no change on a CTRL+RESET or power-cycle: // g_bPhasorEnable = false; } ReinitializeClock(); // Reset CLK for AY8910s } //----------------------------------------------------------------------------- // 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) BYTE __stdcall MockingboardCard::IORead(WORD PC, WORD nAddr, BYTE bWrite, BYTE nValue, ULONG nExecutedCycles) { UINT slot = (nAddr >> 8) & 0xf; MockingboardCard* pCard = (MockingboardCard*)MemGetSlotParameters(slot); return pCard->IOReadInternal(PC, nAddr, bWrite, nValue, nExecutedCycles); } BYTE MockingboardCard::IOReadInternal(WORD PC, WORD nAddr, BYTE bWrite, BYTE nValue, ULONG nExecutedCycles) { GetCardMgr().GetMockingboardCardMgr().UpdateCycles(nExecutedCycles); #ifdef _DEBUG if (!IS_APPLE2 && MemCheckINTCXROM()) { _ASSERT(0); // Card ROM disabled, so IO_Cxxx() returns the internal ROM return mem[nAddr]; } #endif if (m_phasorEnable) { int CS = 0; if (m_phasorMode == PH_Mockingboard) CS = ( ( nAddr & 0x80 ) >> 7 ) + 1; // 1 or 2 else if (m_phasorMode == PH_Phasor) CS = ( ( nAddr & 0x80 ) >> 6 ) | ( ( nAddr & 0x10 ) >> 4 ); // 0, 1, 2 or 3 else if (m_phasorMode == PH_EchoPlus) CS = 2; BYTE nRes = 0; if (CS & 1) nRes |= m_MBSubUnit[SY6522_DEVICE_A].sy6522.Read(nAddr & 0xf); if (CS & 2) nRes |= m_MBSubUnit[SY6522_DEVICE_B].sy6522.Read(nAddr & 0xf); bool bAccessedDevice = (CS & 3) ? true : false; bool CS_SSI263 = !(nAddr & 0x10) && (nAddr & 0x60) && !(nAddr & 0x80); // SSI263 at $Cn2x and/or $Cn4x if (m_phasorMode == PH_Phasor && CS_SSI263) // NB. Mockingboard mode: SSI263.bit7 not readable { _ASSERT(!bAccessedDevice); // In Phasor native mode, 6522 & SSI263 are interleaved in $Cn10-$Cn7F card I/O memory if (nAddr & 0x40) // Primary SSI263 nRes = m_MBSubUnit[1].ssi263.Read(nExecutedCycles); // SSI263 only drives bit7 if (nAddr & 0x20) // Secondary SSI263 nRes = m_MBSubUnit[0].ssi263.Read(nExecutedCycles); // SSI263 only drives bit7 bAccessedDevice = true; } return bAccessedDevice ? nRes : MemReadFloatingBus(nExecutedCycles); } #if DBG_MB_SS_CARD if (nMB == 1) return MemReadFloatingBus(nExecutedCycles); #endif // NB. Mockingboard: SSI263.bit7 not readable (TODO: check this with real h/w) const BYTE offset = nAddr & 0xff; const BYTE subunit = (offset < SY6522B_Offset) ? SY6522_DEVICE_A : SY6522_DEVICE_B; const BYTE reg = nAddr & 0xf; return m_MBSubUnit[subunit].sy6522.Read(reg); } //----------------------------------------------------------------------------- BYTE __stdcall MockingboardCard::IOWrite(WORD PC, WORD nAddr, BYTE bWrite, BYTE nValue, ULONG nExecutedCycles) { UINT slot = (nAddr >> 8) & 0xf; MockingboardCard* pCard = (MockingboardCard*)MemGetSlotParameters(slot); return pCard->IOWriteInternal(PC, nAddr, bWrite, nValue, nExecutedCycles); } BYTE MockingboardCard::IOWriteInternal(WORD PC, WORD nAddr, BYTE bWrite, BYTE nValue, ULONG nExecutedCycles) { GetCardMgr().GetMockingboardCardMgr().UpdateCycles(nExecutedCycles); #ifdef _DEBUG if (!IS_APPLE2 && MemCheckINTCXROM()) { _ASSERT(0); // Card ROM disabled, so IO_Cxxx() returns the internal ROM return 0; } #endif // 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) (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) { WORD base; WORD addr16; if (mem[(PC-2)&0xffff] == 0x91) { BYTE zp = mem[(PC-1)&0xffff]; base = (mem[zp] | (mem[(zp+1)&0xff]<<8)); addr16 = base + regs.y; } else { base = mem[(PC-2)&0xffff] | (mem[(PC-1)&0xffff]<<8); addr16 = base + ((mem[(PC-3)&0xffff] == 0x99) ? regs.y : regs.x); } if (((base ^ addr16) >> 8) == 0) // Only the no-PX variant does the false read (to the same I/O SELECT page) { _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 IOReadInternal(PC, nAddr, 0, 0, nExecutedCycles); } } } if (m_phasorEnable) { int CS = 0; if (m_phasorMode == PH_Mockingboard) CS = ( ( nAddr & 0x80 ) >> 7 ) + 1; // 1 or 2 else if (m_phasorMode == PH_Phasor) CS = ( ( nAddr & 0x80 ) >> 6 ) | ( ( nAddr & 0x10 ) >> 4 ); // 0, 1, 2 or 3 else if (m_phasorMode == PH_EchoPlus) CS = 2; if (CS & 1) { const BYTE reg = nAddr & 0xf; m_MBSubUnit[SY6522_DEVICE_A].sy6522.Write(reg, nValue); if (reg == SY6522::rORB) WriteToORB(SY6522_DEVICE_A); } if (CS & 2) { const BYTE reg = nAddr & 0xf; m_MBSubUnit[SY6522_DEVICE_B].sy6522.Write(reg, nValue); if (reg == SY6522::rORB) WriteToORB(SY6522_DEVICE_B); } bool CS_SSI263_A = (m_phasorMode == PH_Phasor) ? !(nAddr & 0x80) && (nAddr & 0x40) // SSI263 at $Cn4x, $Cn6x : nAddr & 0x40; // SSI263 at $Cn4x-Cn7x, $CnCx-CnFx bool CS_SSI263_B = (m_phasorMode == PH_Phasor) ? !(nAddr & 0x80) && (nAddr & 0x20) // SSI263 at $Cn2x, $Cn6x : nAddr & 0x20; // SSI263 at $Cn2x-Cn3x, $Cn6x-Cn7x, $CnAx-CnBx, $CnEx-CnFx if (m_phasorMode == PH_Mockingboard || m_phasorMode == PH_Phasor) // No SSI263 for Echo+ { // NB. Mockingboard mode: writes to $Cn4x/SSI263 also get written to 1st 6522 (have confirmed on real Phasor h/w) if (CS_SSI263_A) // Primary SSI263 m_MBSubUnit[1].ssi263.Write(nAddr&0x7, nValue); // 2nd 6522 is used for 1st speech chip if (CS_SSI263_B) // Secondary SSI263 m_MBSubUnit[0].ssi263.Write(nAddr&0x7, nValue); // 1st 6522 is used for 2nd speech chip } return 0; } const BYTE offset = nAddr & 0xff; const BYTE subunit = (offset < SY6522B_Offset) ? SY6522_DEVICE_A : SY6522_DEVICE_B; const BYTE reg = nAddr & 0xf; m_MBSubUnit[subunit].sy6522.Write(reg, nValue); if (reg == SY6522::rORB) WriteToORB(subunit); #if !DBG_MB_SS_CARD if (nAddr & 0x40) m_MBSubUnit[1].ssi263.Write(nAddr&0x7, nValue); // 2nd 6522 is used for 1st speech chip if (nAddr & 0x20) m_MBSubUnit[0].ssi263.Write(nAddr&0x7, nValue); // 1st 6522 is used for 2nd speech chip #endif return 0; } //----------------------------------------------------------------------------- // 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) // . $C0C5 -> remaining in Echo+ mode (b#111) // So $C0C5 seemingly results in 2 different modes. // BYTE __stdcall MockingboardCard::PhasorIO(WORD PC, WORD nAddr, BYTE bWrite, BYTE nValue, ULONG nExecutedCycles) { UINT slot = ((nAddr & 0xff) >> 4) - 8; MockingboardCard* pCard = (MockingboardCard*)MemGetSlotParameters(slot); return pCard->PhasorIOInternal(PC, nAddr, bWrite, nValue, nExecutedCycles); } BYTE MockingboardCard::PhasorIOInternal(WORD PC, WORD nAddr, BYTE bWrite, BYTE nValue, ULONG nExecutedCycles) { if (!m_phasorEnable) return MemReadFloatingBus(nExecutedCycles); UINT bits = (UINT) m_phasorMode; if (nAddr & 8) bits = 0; bits |= (nAddr & 7); m_phasorMode = (PHASOR_MODE) bits; if (m_phasorMode == PH_Mockingboard || m_phasorMode == PH_EchoPlus) m_phasorClockScaleFactor = 1; else if (m_phasorMode == PH_Phasor) m_phasorClockScaleFactor = 2; AY8910_InitClock((int)(Get6502BaseClock() * m_phasorClockScaleFactor)); for (UINT i = 0; i < NUM_SSI263; i++) m_MBSubUnit[i].ssi263.SetCardMode(m_phasorMode); return MemReadFloatingBus(nExecutedCycles); } //----------------------------------------------------------------------------- void MockingboardCard::InitializeIO(LPBYTE pCxRomPeripheral) { if (QueryType() == CT_MockingboardC) RegisterIoHandler(m_slot, IO_Null, IO_Null, IORead, IOWrite, this, NULL); else // Phasor RegisterIoHandler(m_slot, PhasorIO, PhasorIO, IORead, IOWrite, this, NULL); if (g_bDisableDirectSound || g_bDisableDirectSoundMockingboard) return; #ifdef NO_DIRECT_X #else // NO_DIRECT_X for (UINT i = 0; i < NUM_SSI263; i++) { if (!m_MBSubUnit[i].ssi263.DSInit()) break; } #endif // NO_DIRECT_X } //----------------------------------------------------------------------------- void MockingboardCard::MuteControl(bool mute) { if (mute) { for (UINT i = 0; i < NUM_SSI263; i++) m_MBSubUnit[i].ssi263.Mute(); } else { for (UINT i = 0; i < NUM_SSI263; i++) m_MBSubUnit[i].ssi263.Unmute(); } } //----------------------------------------------------------------------------- #ifdef _DEBUG void MockingboardCard::CheckCumulativeCycles(void) { _ASSERT(m_lastCumulativeCycle == g_nCumulativeCycles); m_lastCumulativeCycle = g_nCumulativeCycles; } #endif // Called by: ResetState() and Snapshot_LoadState_v2() void MockingboardCard::SetCumulativeCycles(void) { m_lastCumulativeCycle = g_nCumulativeCycles; } // Called by ContinueExecution() at the end of every execution period (~1000 cycles or ~3 cycles when MODE_STEPPING) void MockingboardCard::Update(const ULONG executedCycles) { for (UINT i = 0; i < NUM_SSI263; i++) m_MBSubUnit[i].ssi263.PeriodicUpdate(executedCycles); } //----------------------------------------------------------------------------- // Called by: // . CpuExecute() every ~1000 cycles @ 1MHz (or ~3 cycles when MODE_STEPPING) // . MB_SyncEventCallback() on a TIMER1/2 underflow // . MB_Read() / MB_Write() (for both normal & full-speed) void MockingboardCard::UpdateCycles(ULONG executedCycles) { CpuCalcCycles(executedCycles); UINT64 uCycles = g_nCumulativeCycles - m_lastCumulativeCycle; _ASSERT(uCycles >= 0); if (uCycles == 0) return; m_lastCumulativeCycle = g_nCumulativeCycles; _ASSERT(uCycles < 0x10000 || g_nAppMode == MODE_BENCHMARK); USHORT nClocks = (USHORT)uCycles; for (UINT i = 0; i < NUM_SUBUNITS_PER_MB; i++) { m_MBSubUnit[i].sy6522.UpdateTimer1(nClocks); m_MBSubUnit[i].sy6522.UpdateTimer2(nClocks); } } //----------------------------------------------------------------------------- // Called on a 6522 TIMER1/2 underflow int MockingboardCard::MB_SyncEventCallback(int id, int /*cycles*/, ULONG uExecutedCycles) { UINT slot = (id >> 4); MockingboardCard* pCard = (MockingboardCard*)MemGetSlotParameters(slot); return pCard->MB_SyncEventCallbackInternal(id, 0, uExecutedCycles); } int MockingboardCard::MB_SyncEventCallbackInternal(int id, int /*cycles*/, ULONG uExecutedCycles) { //UpdateCycles(uExecutedCycles); // Underflow: so keep TIMER1/2 counters in sync // Update all MBs, so that m_lastCumulativeCycle remains in sync for all GetCardMgr().GetMockingboardCardMgr().UpdateCycles(uExecutedCycles); // Underflow: so keep TIMER1/2 counters in sync MB_SUBUNIT* pMB = &m_MBSubUnit[(id & 0xf) / SY6522::kNumTimersPer6522]; if ((id & 1) == 0) { _ASSERT(pMB->sy6522.IsTimer1Active()); UpdateIFRandIRQ(pMB, 0, SY6522::IxR_TIMER1); GetCardMgr().GetMockingboardCardMgr().UpdateSoundBuffer(); if ((pMB->sy6522.GetReg(SY6522::rACR) & SY6522::ACR_RUNMODE) == SY6522::ACR_RM_FREERUNNING) { pMB->sy6522.StartTimer1(); return pMB->sy6522.GetRegT1C() + SY6522::kExtraTimerCycles; } // One-shot mode // - Phasor's playback code uses one-shot mode pMB->sy6522.StopTimer1(); return 0; // Don't repeat event } else { // NB. Since not calling UpdateSoundBuffer(), 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); pMB->sy6522.StopTimer2(); // TIMER2 only runs in one-shot mode return 0; // Don't repeat event } } //----------------------------------------------------------------------------- bool MockingboardCard::IsActive(void) { bool isSSI263Active = false; for (UINT i = 0; i Hdr.UnitHdr.hdr.v2.Length = sizeof(SS_CARD_MOCKINGBOARD_v1); pSS->Hdr.UnitHdr.hdr.v2.Type = UT_Card; pSS->Hdr.UnitHdr.hdr.v2.Version = 1; pSS->Hdr.Slot = m_slot; pSS->Hdr.Type = CT_MockingboardC; MB_SUBUNIT* pMB = &m_MBSubUnit[0]; for (UINT i=0; isy6522.GetRegs((BYTE*)&pSS->Unit[i].RegsSY6522); // continuous 16-byte array // AY8913 for (UINT j=0; j<16; j++) { pSS->Unit[i].RegsAY8910[j] = AYReadReg(i, 0, j); // FIXME: also support Phasor's 2nd AY8913 } memset(&pSS->Unit[i].RegsSSI263, 0, sizeof(SSI263A)); // Not used by debugger pSS->Unit[i].nAYCurrentRegister = pMB->nAYCurrentRegister; pSS->Unit[i].bTimer1Active = pMB->sy6522.IsTimer1Active(); pSS->Unit[i].bTimer2Active = pMB->sy6522.IsTimer2Active(); pSS->Unit[i].bSpeechIrqPending = false; pMB++; } } //============================================================================= // AY8913 interface BYTE MockingboardCard::AYReadReg(BYTE subunit, BYTE ay, int r) { _ASSERT(subunit < NUM_SUBUNITS_PER_MB && ay < NUM_AY8913_PER_SUBUNIT); return m_MBSubUnit[subunit].ay8913[ay].sound_ay_read(r); } void MockingboardCard::_AYWriteReg(BYTE subunit, BYTE ay, int r, int v) { _ASSERT(subunit < NUM_SUBUNITS_PER_MB && ay < NUM_AY8913_PER_SUBUNIT); libspectrum_dword uOffset = (libspectrum_dword)(g_nCumulativeCycles - m_lastAYUpdateCycle); m_MBSubUnit[subunit].ay8913[ay].sound_ay_write(r, v, uOffset); } void MockingboardCard::AY8910_reset(BYTE subunit, BYTE ay) { // Don't reset the AY CLK, as this is a property of the card (MB/Phasor), not the AY chip _ASSERT(subunit < NUM_SUBUNITS_PER_MB && ay < NUM_AY8913_PER_SUBUNIT); m_MBSubUnit[subunit].ay8913[ay].sound_ay_reset(); // Calls: sound_ay_init(); } void MockingboardCard::AY8910UpdateSetCycles() { m_lastAYUpdateCycle = g_nCumulativeCycles; } void MockingboardCard::AY8910Update(BYTE subunit, BYTE ay, INT16** buffer, int nNumSamples) { _ASSERT(subunit < NUM_SUBUNITS_PER_MB && ay < NUM_AY8913_PER_SUBUNIT); AY8910UpdateSetCycles(); m_MBSubUnit[subunit].ay8913[ay].SetFramesize(nNumSamples); m_MBSubUnit[subunit].ay8913[ay].SetSoundBuffers(buffer); m_MBSubUnit[subunit].ay8913[ay].sound_frame(); } void MockingboardCard::AY8910_InitAll(int nClock, int nSampleRate) { for (UINT subunit = 0; subunit < NUM_SUBUNITS_PER_MB; subunit++) { for (UINT ay = 0; ay < 2; ay++) { m_MBSubUnit[subunit].ay8913[ay].sound_init(NULL); // Inits mainly static members (except ay_tick_incr) m_MBSubUnit[subunit].ay8913[ay].sound_ay_init(); } } } void MockingboardCard::AY8910_InitClock(int nClock) { AY8913::SetCLK((double)nClock); for (UINT subunit = 0; subunit < NUM_SUBUNITS_PER_MB; subunit++) { for (UINT ay = 0; ay < NUM_AY8913_PER_SUBUNIT; ay++) { m_MBSubUnit[subunit].ay8913[ay].sound_init(NULL); // Inits mainly static members (except ay_tick_incr) } } } BYTE* MockingboardCard::AY8910_GetRegsPtr(BYTE subunit, BYTE ay) { _ASSERT(subunit < NUM_SUBUNITS_PER_MB && ay < NUM_AY8913_PER_SUBUNIT); return m_MBSubUnit[subunit].ay8913[ay].GetAYRegsPtr(); } UINT MockingboardCard::AY8910_SaveSnapshot(YamlSaveHelper& yamlSaveHelper, BYTE subunit, BYTE ay, const std::string& suffix) { _ASSERT(subunit < NUM_SUBUNITS_PER_MB && ay < NUM_AY8913_PER_SUBUNIT); m_MBSubUnit[subunit].ay8913[ay].SaveSnapshot(yamlSaveHelper, suffix); return 1; } UINT MockingboardCard::AY8910_LoadSnapshot(YamlLoadHelper& yamlLoadHelper, BYTE subunit, BYTE ay, const std::string& suffix) { _ASSERT(subunit < NUM_SUBUNITS_PER_MB && ay < NUM_AY8913_PER_SUBUNIT); return m_MBSubUnit[subunit].ay8913[ay].LoadSnapshot(yamlLoadHelper, suffix) ? 1 : 0; } //============================================================================= // Unit version history: // 2: Added: Timer1 & Timer2 active // 3: Added: Unit state - GH#320 // 4: Added: 6522 timerIrqDelay - GH#652 // 5: Added: Unit state-B (Phasor only) - GH#659 // 6: Changed SS_YAML_KEY_PHASOR_MODE from (0,1) to (0,5,7) // Added SS_YAML_KEY_VOTRAX_PHONEME // Removed: redundant SS_YAML_KEY_PHASOR_CLOCK_SCALE_FACTOR // 7: Added SS_YAML_KEY_SSI263_REG_ACTIVE_PHONEME to SSI263 sub-unit // 8: Moved Timer1 & Timer2 active to 6522 sub-unit // Removed Timer1/Timer2/Speech IRQ Pending // Changed at AppleWin 1.30.8 // 9: Phasor AY's are swapped (means that AppleWin 1.30.10 and 1.30.11 are wrong) // Changed at AppleWin 1.30.12 const UINT kUNIT_VERSION = 9; #define SS_YAML_KEY_MB_UNIT "Unit" #define SS_YAML_KEY_AY_CURR_REG "AY Current Register" #define SS_YAML_KEY_MB_UNIT_STATE "Unit State" #define SS_YAML_KEY_MB_UNIT_STATE_B "Unit State-B" // Phasor only #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 #define SS_YAML_KEY_PHASOR_UNIT "Unit" #define SS_YAML_KEY_PHASOR_CLOCK_SCALE_FACTOR "Clock Scale Factor" // v6: deprecated #define SS_YAML_KEY_PHASOR_MODE "Mode" #define SS_YAML_KEY_VOTRAX_PHONEME "Votrax Phoneme" std::string MockingboardCard::GetSnapshotCardName(void) { static const std::string name("Mockingboard C"); return name; } std::string MockingboardCard::GetSnapshotCardNamePhasor(void) { static const std::string name("Phasor"); return name; } void MockingboardCard::SaveSnapshot(YamlSaveHelper& yamlSaveHelper) { if (QueryType() == CT_Phasor) return Phasor_SaveSnapshot(yamlSaveHelper); // YamlSaveHelper::Slot slot(yamlSaveHelper, GetSnapshotCardName(), m_slot, kUNIT_VERSION); YamlSaveHelper::Label state(yamlSaveHelper, "%s:\n", SS_YAML_KEY_STATE); yamlSaveHelper.SaveBool(SS_YAML_KEY_VOTRAX_PHONEME, m_MBSubUnit[0].ssi263.GetVotraxPhoneme()); // SC01 only in subunit 0 for (UINT subunit = 0; subunit < NUM_SUBUNITS_PER_MB; subunit++) { MB_SUBUNIT* pMB = &m_MBSubUnit[subunit]; YamlSaveHelper::Label unit(yamlSaveHelper, "%s%d:\n", SS_YAML_KEY_MB_UNIT, subunit); pMB->sy6522.SaveSnapshot(yamlSaveHelper); AY8910_SaveSnapshot(yamlSaveHelper, subunit, AY8913_DEVICE_A, std::string("")); pMB->ssi263.SaveSnapshot(yamlSaveHelper); yamlSaveHelper.SaveHexUint4(SS_YAML_KEY_MB_UNIT_STATE, pMB->state); yamlSaveHelper.SaveHexUint4(SS_YAML_KEY_AY_CURR_REG, pMB->nAYCurrentRegister); } } bool MockingboardCard::LoadSnapshot(YamlLoadHelper& yamlLoadHelper, UINT version) { if (m_slot == 0 || m_slot == 3) throw std::runtime_error("Card: wrong slot"); if (version < 1 || version > kUNIT_VERSION) throw std::runtime_error("Card: wrong version"); if (QueryType() == CT_Phasor) return Phasor_LoadSnapshot(yamlLoadHelper, version); // AY8910UpdateSetCycles(); bool isVotrax = (version >= 6) ? yamlLoadHelper.LoadBool(SS_YAML_KEY_VOTRAX_PHONEME) : false; m_MBSubUnit[0].ssi263.SetVotraxPhoneme(isVotrax); // SC01 only in subunit 0 for (UINT subunit = 0; subunit < NUM_SUBUNITS_PER_MB; subunit++) { MB_SUBUNIT* pMB = &m_MBSubUnit[subunit]; char szNum[2] = {char('0' + subunit), 0}; std::string unit = std::string(SS_YAML_KEY_MB_UNIT) + std::string(szNum); if (!yamlLoadHelper.GetSubMap(unit)) throw std::runtime_error("Card: Expected key: " + unit); pMB->sy6522.LoadSnapshot(yamlLoadHelper, version); UpdateIFRandIRQ(pMB, 0, pMB->sy6522.GetReg(SY6522::rIFR)); // Assert any pending IRQs (GH#677) AY8910_LoadSnapshot(yamlLoadHelper, subunit, AY8913_DEVICE_A, std::string("")); pMB->ssi263.LoadSnapshot(yamlLoadHelper, PH_Mockingboard, version); // Pre: SetVotraxPhoneme() pMB->nAYCurrentRegister = yamlLoadHelper.LoadUint(SS_YAML_KEY_AY_CURR_REG); if (version == 1) { 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 } pMB->state = AY_INACTIVE; pMB->stateB = AY_INACTIVE; if (version >= 3) pMB->state = (MockingboardUnitState_e) (yamlLoadHelper.LoadUint(SS_YAML_KEY_MB_UNIT_STATE) & 7); yamlLoadHelper.PopMap(); } AY8910_InitClock((int)Get6502BaseClock()); // NB. g_bPhasorEnable setup in ctor return true; } void MockingboardCard::Phasor_SaveSnapshot(YamlSaveHelper& yamlSaveHelper) { YamlSaveHelper::Slot slot(yamlSaveHelper, GetSnapshotCardNamePhasor(), m_slot, kUNIT_VERSION); YamlSaveHelper::Label state(yamlSaveHelper, "%s:\n", SS_YAML_KEY_STATE); yamlSaveHelper.SaveUint(SS_YAML_KEY_PHASOR_MODE, m_phasorMode); yamlSaveHelper.SaveBool(SS_YAML_KEY_VOTRAX_PHONEME, m_MBSubUnit[0].ssi263.GetVotraxPhoneme()); // SC01 only in subunit 0 for (UINT subunit = 0; subunit < NUM_SUBUNITS_PER_MB; subunit++) { MB_SUBUNIT* pMB = &m_MBSubUnit[subunit]; YamlSaveHelper::Label unit(yamlSaveHelper, "%s%d:\n", SS_YAML_KEY_PHASOR_UNIT, subunit); pMB->sy6522.SaveSnapshot(yamlSaveHelper); AY8910_SaveSnapshot(yamlSaveHelper, subunit, AY8913_DEVICE_A, std::string("-A")); AY8910_SaveSnapshot(yamlSaveHelper, subunit, AY8913_DEVICE_B, std::string("-B")); pMB->ssi263.SaveSnapshot(yamlSaveHelper); yamlSaveHelper.SaveHexUint4(SS_YAML_KEY_MB_UNIT_STATE, pMB->state); yamlSaveHelper.SaveHexUint4(SS_YAML_KEY_MB_UNIT_STATE_B, pMB->stateB); yamlSaveHelper.SaveHexUint4(SS_YAML_KEY_AY_CURR_REG, pMB->nAYCurrentRegister); } } bool MockingboardCard::Phasor_LoadSnapshot(YamlLoadHelper& yamlLoadHelper, UINT version) { if (version < 6) yamlLoadHelper.LoadUint(SS_YAML_KEY_PHASOR_CLOCK_SCALE_FACTOR); // Consume redundant data UINT phasorMode = yamlLoadHelper.LoadUint(SS_YAML_KEY_PHASOR_MODE); if (version < 6) { if (phasorMode == 0) phasorMode = PH_Mockingboard; else phasorMode = PH_Phasor; } m_phasorMode = (PHASOR_MODE) phasorMode; m_phasorClockScaleFactor = (m_phasorMode == PH_Phasor) ? 2 : 1; AY8910UpdateSetCycles(); UINT nDeviceNum = 0; MB_SUBUNIT* pMB = &m_MBSubUnit[0]; bool isVotrax = (version >= 6) ? yamlLoadHelper.LoadBool(SS_YAML_KEY_VOTRAX_PHONEME) : false; m_MBSubUnit[0].ssi263.SetVotraxPhoneme(isVotrax); // SC01 only in subunit 0 for (UINT subunit = 0; subunit < NUM_SUBUNITS_PER_MB; subunit++) { MB_SUBUNIT* pMB = &m_MBSubUnit[subunit]; char szNum[2] = {char('0' + subunit), 0}; std::string unit = std::string(SS_YAML_KEY_MB_UNIT) + std::string(szNum); if (!yamlLoadHelper.GetSubMap(unit)) throw std::runtime_error("Card: Expected key: " + unit); pMB->sy6522.LoadSnapshot(yamlLoadHelper, version); UpdateIFRandIRQ(pMB, 0, pMB->sy6522.GetReg(SY6522::rIFR)); // Assert any pending IRQs (GH#677) if (version >= 5 && version <= 8) { const UINT phasorSubunit = subunit == 0 ? 1 : 0; AY8910_LoadSnapshot(yamlLoadHelper, phasorSubunit, AY8913_DEVICE_A, std::string("-A")); AY8910_LoadSnapshot(yamlLoadHelper, phasorSubunit, AY8913_DEVICE_B, std::string("-B")); } else { AY8910_LoadSnapshot(yamlLoadHelper, subunit, AY8913_DEVICE_A, std::string("-A")); AY8910_LoadSnapshot(yamlLoadHelper, subunit, AY8913_DEVICE_B, std::string("-B")); } pMB->ssi263.LoadSnapshot(yamlLoadHelper, PH_Phasor, version); // Pre: SetVotraxPhoneme() pMB->nAYCurrentRegister = yamlLoadHelper.LoadUint(SS_YAML_KEY_AY_CURR_REG); if (version == 1) { 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 } pMB->state = AY_INACTIVE; pMB->stateB = AY_INACTIVE; if (version >= 3) pMB->state = (MockingboardUnitState_e) (yamlLoadHelper.LoadUint(SS_YAML_KEY_MB_UNIT_STATE) & 7); if (version >= 5) pMB->stateB = (MockingboardUnitState_e) (yamlLoadHelper.LoadUint(SS_YAML_KEY_MB_UNIT_STATE_B) & 7); yamlLoadHelper.PopMap(); } AY8910_InitClock((int)(Get6502BaseClock() * m_phasorClockScaleFactor)); // NB. g_bPhasorEnable setup in ctor return true; }