diff --git a/source/6522.cpp b/source/6522.cpp index 3215fca9..16f7cc8c 100644 --- a/source/6522.cpp +++ b/source/6522.cpp @@ -110,7 +110,16 @@ USHORT SY6522::SetTimerSyncEvent(BYTE reg, USHORT timerLatch) if (syncEvent->m_active) g_SynchronousEventMgr.Remove(syncEvent->m_id); - syncEvent->SetCycles(timerLatch + kExtraTimerCycles + opcodeCycleAdjust); + if (m_isMegaAudio) + { + if (reg == rT1CH && timerLatch == 0x0000) + timerLatch = 0xFFFF; // MegaAudio && T1.LATCH=0: use 0xFFFF (or maybe 0x10000?) + syncEvent->SetCycles(timerLatch + kExtraMegaAudioTimerCycles + opcodeCycleAdjust); // MegaAudio asserts IRQ 1 cycle late! + } + else + { + syncEvent->SetCycles(timerLatch + kExtraTimerCycles + opcodeCycleAdjust); + } g_SynchronousEventMgr.Insert(syncEvent); // It doesn't matter if this overflows (ie. >0xFFFF), since on completion of current opcode it'll be corrected @@ -260,10 +269,21 @@ bool SY6522::CheckTimerUnderflow(USHORT& counter, int& timerIrqDelay, const USHO if (oldTimer >= 0 && timer < 0) // Underflow occurs for 0x0000 -> 0xFFFF { - if (timer <= -2) // TIMER = 0xFFFE (or less) - timerIrq = true; - else // TIMER = 0xFFFF - timerIrqDelay = 1; // ...so 1 cycle until IRQ + if (m_isMegaAudio) + { + if (timer <= -3) // TIMER = 0xFFFD (or less) + timerIrq = true; + else // TIMER = 0xFFFF or 0xFFFE + timerIrqDelay = 1; // ...so 1 or 2 cycles until IRQ + } + else + { + if (timer <= -2) // TIMER = 0xFFFE (or less) + timerIrq = true; + else // TIMER = 0xFFFF + timerIrqDelay = 1; // ...so 1 cycle until IRQ + } + } return timerIrq; @@ -272,10 +292,19 @@ bool SY6522::CheckTimerUnderflow(USHORT& counter, int& timerIrqDelay, const USHO int SY6522::OnTimer1Underflow(USHORT& counter) { int timer = (int)(short)(counter); - while (timer < -1) - timer += (m_regs.TIMER1_LATCH.w + kExtraTimerCycles); // GH#651: account for underflowed cycles / GH#652: account for extra 2 cycles + if (m_isMegaAudio) + { + const UINT timerLatch = m_regs.TIMER1_LATCH.w ? m_regs.TIMER1_LATCH.w : 0xFFFF; // MegaAudio && T1.LATCH=0: use 0xFFFF (or maybe 0x10000?) + while (timer < -2) + timer += (timerLatch + kExtraMegaAudioTimerCycles); // MegaAudio asserts IRQ 1 cycle late! + } + else + { + while (timer < -1) + timer += (m_regs.TIMER1_LATCH.w + kExtraTimerCycles); // GH#651: account for underflowed cycles / GH#652: account for extra 2 cycles + } counter = (USHORT)timer; - return (timer == -1) ? 1 : 0; // timer1IrqDelay + return (timer < 0) ? 1 : 0; // timer1IrqDelay } //----------------------------------------------------------------------------- @@ -349,9 +378,11 @@ BYTE SY6522::Read(BYTE nReg) case 0x08: // TIMER2L nValue = GetTimer2Counter(nReg) & 0xff; UpdateIFR(IxR_TIMER2); + if (m_isMegaAudio) nValue = 0xFF; // MegaAudio: Timer2 just reads as $00FF break; case 0x09: // TIMER2H nValue = GetTimer2Counter(nReg) >> 8; + if (m_isMegaAudio) nValue = 0x00; // MegaAudio: Timer2 just reads as $00FF break; case 0x0a: // SERIAL_SHIFT break; @@ -370,6 +401,8 @@ BYTE SY6522::Read(BYTE nReg) break; case 0x0e: // IER nValue = 0x80 | m_regs.IER; // GH#567 + if (m_isMegaAudio) + nValue &= 0x7F; break; case 0x0f: // ORA_NO_HS nValue = m_regs.ORA; diff --git a/source/6522.h b/source/6522.h index 52a4c23b..fae30944 100644 --- a/source/6522.h +++ b/source/6522.h @@ -3,7 +3,7 @@ class SY6522 { public: - SY6522(UINT slot) : m_slot(slot) + SY6522(UINT slot, bool isMegaAudio) : m_slot(slot), m_isMegaAudio(isMegaAudio) { for (UINT i = 0; i < kNumTimersPer6522; i++) m_syncEvent[i] = NULL; @@ -143,4 +143,7 @@ private: class SyncEvent* m_syncEvent[kNumTimersPer6522]; UINT m_slot; + bool m_isMegaAudio; + + static const UINT kExtraMegaAudioTimerCycles = kExtraTimerCycles + 1; }; diff --git a/source/Card.cpp b/source/Card.cpp index 96e104b9..4dab1192 100644 --- a/source/Card.cpp +++ b/source/Card.cpp @@ -159,6 +159,8 @@ std::string Card::GetCardName(const SS_CARDTYPE cardType) return VidHDCard::GetSnapshotCardName(); case CT_Uthernet2: return Uthernet2::GetSnapshotCardName(); + case CT_MegaAudio: + return MockingboardCard::GetSnapshotCardNameMegaAudio(); default: return "Unknown"; } @@ -230,6 +232,10 @@ SS_CARDTYPE Card::GetCardType(const std::string & card) { return CT_Uthernet2; } + else if (card == MockingboardCard::GetSnapshotCardNameMegaAudio()) + { + return CT_MegaAudio; + } else { throw std::runtime_error("Slots: Unknown card: " + card); // todo: don't throw - just ignore & continue diff --git a/source/Card.h b/source/Card.h index e42b7794..d64320c9 100644 --- a/source/Card.h +++ b/source/Card.h @@ -25,6 +25,7 @@ enum SS_CARDTYPE CT_SNESMAX, // 2 port Nintendo NES/SNES controller serial interface card CT_VidHD, CT_Uthernet2, + CT_MegaAudio, // Soundcard }; enum SLOTS { SLOT0=0, SLOT1, SLOT2, SLOT3, SLOT4, SLOT5, SLOT6, SLOT7, NUM_SLOTS, SLOT_AUX, GAME_IO_CONNECTOR }; diff --git a/source/CardManager.cpp b/source/CardManager.cpp index 27533791..f4af3e47 100644 --- a/source/CardManager.cpp +++ b/source/CardManager.cpp @@ -66,6 +66,7 @@ void CardManager::InsertInternal(UINT slot, SS_CARDTYPE type) m_slot[slot] = m_pSSC = new CSuperSerialCard(slot); break; case CT_MockingboardC: + case CT_MegaAudio: m_slot[slot] = new MockingboardCard(slot, type); break; case CT_GenericPrinter: diff --git a/source/CmdLine.cpp b/source/CmdLine.cpp index 2665c190..9a7f3d0d 100644 --- a/source/CmdLine.cpp +++ b/source/CmdLine.cpp @@ -175,6 +175,8 @@ bool ProcessCmdLine(LPSTR lpCmdLine) } if (strcmp(lpCmdLine, "hdc") == 0) g_cmdLine.slotInsert[slot] = CT_GenericHDD; + if (strcmp(lpCmdLine, "megaaudio") == 0) + g_cmdLine.slotInsert[slot] = CT_MegaAudio; if (strcmp(lpCmdLine, "parallel") == 0) { if (slot == SLOT1) diff --git a/source/Configuration/PageSound.cpp b/source/Configuration/PageSound.cpp index 3838c052..46f41b6c 100644 --- a/source/Configuration/PageSound.cpp +++ b/source/Configuration/PageSound.cpp @@ -44,6 +44,16 @@ const char CPageSound::m_soundCardChoices[] = "Mockingboard\0" "SAM\0" "Empty\0"; +// Don't reveal MegaAudio card unless it's been specified from the command line. +// The reasons being are that: +// . this card is purely for regression testing against mb-audit +// . it's confusing to offer this to the end user +const char CPageSound::m_soundCardChoicesEx[] = "Mockingboard\0" + "Phasor\0" + "SAM\0" + "Empty\0" + "MEGA Audio\0"; + const char CPageSound::m_soundCardChoice_Unavailable[] = "Unavailable\0\0"; // doubly-null terminate INT_PTR CALLBACK CPageSound::DlgProc(HWND hWnd, UINT message, WPARAM wparam, LPARAM lparam) @@ -105,6 +115,7 @@ INT_PTR CPageSound::DlgProcInternal(HWND hWnd, UINT message, WPARAM wparam, LPAR case SC_PHASOR: newCard = CT_Phasor; break; case SC_SAM: newCard = CT_SAM; break; case SC_EMPTY: newCard = CT_Empty; break; + case SC_MEGAAUDIO: newCard = CT_MegaAudio; break; default: _ASSERT(0); break; } @@ -166,28 +177,44 @@ CPageSound::SOUNDCARDCHOICE CPageSound::CardTypeToComboItem(SS_CARDTYPE card) case CT_Phasor: return SC_PHASOR; case CT_SAM: return SC_SAM; case CT_Empty: return SC_EMPTY; + case CT_MegaAudio: return SC_MEGAAUDIO; default: _ASSERT(0); return SC_EMPTY; } } void CPageSound::InitOptions(HWND hWnd) { - const SS_CARDTYPE slot4 = m_PropertySheetHelper.GetConfigNew().m_Slot[4]; - const SS_CARDTYPE slot5 = m_PropertySheetHelper.GetConfigNew().m_Slot[5]; + const SS_CARDTYPE slot4 = m_PropertySheetHelper.GetConfigNew().m_Slot[SLOT4]; + const SS_CARDTYPE slot5 = m_PropertySheetHelper.GetConfigNew().m_Slot[SLOT5]; bool isSlot4SoundCard = slot4 == CT_MockingboardC || slot4 == CT_Phasor || slot4 == CT_SAM || slot4 == CT_Empty; bool isSlot5SoundCard = slot5 == CT_MockingboardC || slot5 == CT_Phasor || slot5 == CT_SAM || slot5 == CT_Empty; + bool isSlot4SoundCardEx = slot4 == CT_MegaAudio; + bool isSlot5SoundCardEx = slot5 == CT_MegaAudio; + + if (isSlot4SoundCardEx || isSlot5SoundCardEx) + { + isSlot4SoundCardEx = isSlot5SoundCardEx = true; // if MegaAudio is visible in either, then make it available in both menus + isSlot4SoundCard = isSlot5SoundCard = false; + } + if (isSlot4SoundCard) m_PropertySheetHelper.FillComboBox(hWnd, IDC_SOUNDCARD_SLOT4, m_soundCardChoices, (int)CardTypeToComboItem(slot4)); + else if (isSlot4SoundCardEx) + m_PropertySheetHelper.FillComboBox(hWnd, IDC_SOUNDCARD_SLOT4, m_soundCardChoicesEx, (int)CardTypeToComboItem(slot4)); else m_PropertySheetHelper.FillComboBox(hWnd, IDC_SOUNDCARD_SLOT4, m_soundCardChoice_Unavailable, 0); if (isSlot5SoundCard) m_PropertySheetHelper.FillComboBox(hWnd, IDC_SOUNDCARD_SLOT5, m_soundCardChoices, (int)CardTypeToComboItem(slot5)); + else if (isSlot5SoundCardEx) + m_PropertySheetHelper.FillComboBox(hWnd, IDC_SOUNDCARD_SLOT5, m_soundCardChoicesEx, (int)CardTypeToComboItem(slot5)); else m_PropertySheetHelper.FillComboBox(hWnd, IDC_SOUNDCARD_SLOT5, m_soundCardChoice_Unavailable, 0); - bool enableMBVolume = slot4 == CT_MockingboardC || slot5 == CT_MockingboardC || slot4 == CT_Phasor || slot5 == CT_Phasor; + bool enableMBVolume = slot4 == CT_MockingboardC || slot5 == CT_MockingboardC + || slot4 == CT_Phasor || slot5 == CT_Phasor + || slot4 == CT_MegaAudio || slot5 == CT_MegaAudio; EnableWindow(GetDlgItem(hWnd, IDC_MB_VOLUME), enableMBVolume ? TRUE : FALSE); } diff --git a/source/Configuration/PageSound.h b/source/Configuration/PageSound.h index 80d6fb82..53a8bc5c 100644 --- a/source/Configuration/PageSound.h +++ b/source/Configuration/PageSound.h @@ -28,7 +28,7 @@ protected: virtual void DlgCANCEL(HWND hWnd){} private: - enum SOUNDCARDCHOICE { SC_MOCKINGBOARD = 0, SC_PHASOR, SC_SAM, SC_EMPTY, _SOUNDCARD_MAX_CHOICES, SC_UNAVAILABLE }; + enum SOUNDCARDCHOICE { SC_MOCKINGBOARD = 0, SC_PHASOR, SC_SAM, SC_EMPTY, SC_MEGAAUDIO }; void InitOptions(HWND hWnd); SOUNDCARDCHOICE CardTypeToComboItem(SS_CARDTYPE card); @@ -42,5 +42,6 @@ private: static const UINT VOLUME_MAX = 59; static const TCHAR m_soundchoices[]; static const char m_soundCardChoices[]; + static const char m_soundCardChoicesEx[]; static const char m_soundCardChoice_Unavailable[]; }; diff --git a/source/Mockingboard.cpp b/source/Mockingboard.cpp index b94a865e..33a1c022 100644 --- a/source/Mockingboard.cpp +++ b/source/Mockingboard.cpp @@ -62,7 +62,7 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA //--------------------------------------------------------------------------- -MockingboardCard::MockingboardCard(UINT slot, SS_CARDTYPE type) : Card(type, slot), m_MBSubUnit{slot, slot} +MockingboardCard::MockingboardCard(UINT slot, SS_CARDTYPE type) : Card(type, slot), m_MBSubUnit{ {slot, type}, {slot, type} } { m_lastCumulativeCycle = 0; m_lastAYUpdateCycle = 0; @@ -206,7 +206,8 @@ void MockingboardCard::WriteToORB(BYTE subunit) { BYTE value = m_MBSubUnit[subunit].sy6522.Read(SY6522::rORB); - if (subunit == 0 && // SC01 only at $Cn00 (not $Cn80) + if ((QueryType() == CT_MockingboardC || QueryType() == CT_Phasor) && // Not CT_MegaAudio + subunit == 0 && // SC01 only at $Cn00 (not $Cn80) m_MBSubUnit[subunit].sy6522.Read(SY6522::rPCR) == 0xB0) { // Votrax speech data @@ -289,10 +290,17 @@ void MockingboardCard::AY8910_Write(BYTE subunit, BYTE ay, BYTE value) break; case AY_READ: // 5: READ FROM PSG (need to set DDRA to input) - if (pMB->isChipSelected[ay] && pMB->isAYLatchedAddressValid[ay]) - r6522.SetRegORA(AYReadReg(subunit, ay, pMB->nAYCurrentRegister[ay]) & (r6522.GetReg(SY6522::rDDRA) ^ 0xff)); + if (QueryType() != CT_MegaAudio) + { + if (pMB->isChipSelected[ay] && pMB->isAYLatchedAddressValid[ay]) + r6522.SetRegORA(AYReadReg(subunit, ay, pMB->nAYCurrentRegister[ay]) & (r6522.GetReg(SY6522::rDDRA) ^ 0xff)); + else + r6522.UpdatePortAForHiZ(); + } else - r6522.UpdatePortAForHiZ(); + { + r6522.SetRegORA(0x00); // Reads not supported. + } if (m_phasorEnable && m_phasorMode == PH_Phasor) // GH#1192 { @@ -748,10 +756,13 @@ BYTE MockingboardCard::IOWriteInternal(WORD PC, WORD nAddr, BYTE bWrite, BYTE nV 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 + if (QueryType() == CT_MockingboardC || QueryType() == CT_Phasor) // Not CT_MegaAudio + { + 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; @@ -821,10 +832,10 @@ BYTE MockingboardCard::PhasorIOInternal(WORD PC, WORD nAddr, BYTE bWrite, BYTE n void MockingboardCard::InitializeIO(LPBYTE pCxRomPeripheral) { - if (QueryType() == CT_MockingboardC) - RegisterIoHandler(m_slot, IO_Null, IO_Null, IORead, IOWrite, this, NULL); - else // Phasor + if (QueryType() == CT_Phasor) RegisterIoHandler(m_slot, PhasorIO, PhasorIO, IORead, IOWrite, this, NULL); + else // All other Mockingboard variants + RegisterIoHandler(m_slot, IO_Null, IO_Null, IORead, IOWrite, this, NULL); if (g_bDisableDirectSound || g_bDisableDirectSoundMockingboard) return; @@ -932,6 +943,7 @@ int MockingboardCard::MB_SyncEventCallbackInternal(int id, int /*cycles*/, ULONG pMB->sy6522.StartTimer1(); if (pMB->sy6522.IsTimer1IrqDelay()) return 0x0001; // T1C=0xFFFF, which is really -1, as there's 1 cycle until underflow occurs + // TODO: can also be 0x0002 for MegaAudio return pMB->sy6522.GetRegT1C() + SY6522::kExtraTimerCycles; } @@ -1146,6 +1158,12 @@ std::string MockingboardCard::GetSnapshotCardNamePhasor(void) return name; } +std::string MockingboardCard::GetSnapshotCardNameMegaAudio(void) +{ + static const std::string name("MEGA Audio"); + return name; +} + void MockingboardCard::SaveSnapshot(YamlSaveHelper& yamlSaveHelper) { if (QueryType() == CT_Phasor) @@ -1153,7 +1171,11 @@ void MockingboardCard::SaveSnapshot(YamlSaveHelper& yamlSaveHelper) // - YamlSaveHelper::Slot slot(yamlSaveHelper, GetSnapshotCardName(), m_slot, kUNIT_VERSION); + std::string cardName = GetSnapshotCardName(); + if (QueryType() == CT_MegaAudio) + cardName = GetSnapshotCardNameMegaAudio(); + + YamlSaveHelper::Slot slot(yamlSaveHelper, cardName, m_slot, kUNIT_VERSION); YamlSaveHelper::Label state(yamlSaveHelper, "%s:\n", SS_YAML_KEY_STATE); diff --git a/source/Mockingboard.h b/source/Mockingboard.h index 36cb8815..23aa1ca0 100644 --- a/source/Mockingboard.h +++ b/source/Mockingboard.h @@ -68,6 +68,7 @@ public: static std::string GetSnapshotCardName(void); static std::string GetSnapshotCardNamePhasor(void); + static std::string GetSnapshotCardNameMegaAudio(void); private: enum MockingboardUnitState_e { AY_NOP0, AY_NOP1, AY_INACTIVE, AY_READ, AY_NOP4, AY_NOP5, AY_WRITE, AY_LATCH }; @@ -83,7 +84,7 @@ private: bool isAYLatchedAddressValid[NUM_AY8913_PER_SUBUNIT]; bool isChipSelected[NUM_AY8913_PER_SUBUNIT]; - MB_SUBUNIT(UINT slot) : sy6522(slot), ssi263(slot) + MB_SUBUNIT(UINT slot, SS_CARDTYPE type) : sy6522(slot, type == CT_MegaAudio), ssi263(slot) { nAY8910Number = 0; // sy6522 & ssi263 have already been default constructed @@ -95,7 +96,7 @@ private: nAYCurrentRegister[0] = nAYCurrentRegister[1] = 0; // not valid state[0] = state[1] = AY_INACTIVE; isAYLatchedAddressValid[0] = isAYLatchedAddressValid[1] = false; // after AY reset - isChipSelected[0] = type == CT_MockingboardC ? true : false; + isChipSelected[0] = type == CT_Phasor ? false : true; // Only Phasor is false, all other MB variants are true isChipSelected[1] = false; } }; diff --git a/source/MockingboardCardManager.cpp b/source/MockingboardCardManager.cpp index e3eeff5a..7c4741a0 100644 --- a/source/MockingboardCardManager.cpp +++ b/source/MockingboardCardManager.cpp @@ -42,7 +42,7 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA bool MockingboardCardManager::IsMockingboard(UINT slot) { SS_CARDTYPE type = GetCardMgr().QuerySlot(slot); - return type == CT_MockingboardC || type == CT_Phasor; + return type == CT_MockingboardC || type == CT_Phasor || type == CT_MegaAudio; } void MockingboardCardManager::ReinitializeClock(void) diff --git a/source/Windows/AppleWin.cpp b/source/Windows/AppleWin.cpp index 726a3116..77589c8f 100644 --- a/source/Windows/AppleWin.cpp +++ b/source/Windows/AppleWin.cpp @@ -775,6 +775,11 @@ static void RepeatInitialization(void) GetCardMgr().Insert(SLOT3, g_cmdLine.slotInsert[SLOT3]); } + if (g_cmdLine.slotInsert[SLOT4] != CT_Empty) + { + GetCardMgr().Insert(SLOT4, g_cmdLine.slotInsert[SLOT4]); + } + if (g_cmdLine.slotInsert[SLOT5] != CT_Empty) { if (GetCardMgr().QuerySlot(SLOT5) != CT_Disk2) // Ignore if already got Disk2 in slot 5