From d4e01643fa8bc7cd89db979af0bc6fa377746024 Mon Sep 17 00:00:00 2001 From: tomcw Date: Sun, 10 Nov 2019 15:52:07 +0000 Subject: [PATCH] Mockingboard: improved sound-buffer updating . Changed to 6522.TIMER underflowing at 0x0000 -> 0xFFFF (#652) . Changed MB_Update() to be based on cycle delta (was TIMER1 interval) . this improves support for small 6522.T1C interval . removed MB_GetFramePeriod() . removed overly-complex dual-timer support . Replaced MB_EndOfVideoFrame() with MB_PeriodicUpdate() --- source/Applewin.cpp | 3 +- source/Mockingboard.cpp | 164 ++++++++++++++++------------------------ source/Mockingboard.h | 2 +- 3 files changed, 68 insertions(+), 101 deletions(-) diff --git a/source/Applewin.cpp b/source/Applewin.cpp index 01579acd..84cfabe7 100644 --- a/source/Applewin.cpp +++ b/source/Applewin.cpp @@ -330,6 +330,7 @@ static void ContinueExecution(void) sg_Disk2Card.UpdateDriveState(uActualCyclesExecuted); JoyUpdateButtonLatch(nExecutionPeriodUsec); // Button latch time is independent of CPU clock frequency PrintUpdate(uActualCyclesExecuted); + MB_PeriodicUpdate(uActualCyclesExecuted); // @@ -364,8 +365,6 @@ static void ContinueExecution(void) VideoRedrawScreenDuringFullSpeed(g_dwCyclesThisFrame); else VideoRefreshScreen(); // Just copy the output of our Apple framebuffer to the system Back Buffer - - MB_EndOfVideoFrame(); } if ((g_nAppMode == MODE_RUNNING && !g_bFullSpeed) || bModeStepping_WaitTimer) diff --git a/source/Mockingboard.cpp b/source/Mockingboard.cpp index 996543bd..8fab6cd6 100644 --- a/source/Mockingboard.cpp +++ b/source/Mockingboard.cpp @@ -204,24 +204,16 @@ static const int g_nNumEvents = 2; static HANDLE g_hSSI263Event[g_nNumEvents] = {NULL}; // 1: Phoneme finished playing, 2: Exit thread static DWORD g_dwMaxPhonemeLen = 0; -// When 6522 IRQ is *not* active use 60Hz update freq for MB voices -// NB. Not important if NTSC or PAL - just need to pick a sensible period -static const double g_f6522TimerPeriod_NoIRQ = CLK_6502_NTSC / 60.0; // Constant whatever the CLK is set to - static bool g_bCritSectionValid = false; // Deleting CritialSection when not valid causes crash on Win98 static CRITICAL_SECTION g_CriticalSection; // To guard 6522's IFR -// If we have 2 timer ints: 50Hz and 60Hz, then need to be able to determine the AY8910 reg update freq -static bool g_waitFirstAYWriteAfterTimer1Int = false; -static UINT64 g_lastAY8910cycleAccess = 0; -static UINT64 g_AYWriteAccessTimer1IntPeriod = 0; +static UINT g_cyclesThisAudioFrame = 0; //--------------------------------------------------------------------------- // Forward refs: static DWORD WINAPI SSI263Thread(LPVOID); static void Votrax_Write(BYTE nDevice, BYTE nValue); -static double MB_GetFramePeriod(void); static void MB_Update(void); //--------------------------------------------------------------------------- @@ -394,19 +386,6 @@ static void SY6522_Write(BYTE nDevice, BYTE nReg, BYTE nValue) break; } - if (g_waitFirstAYWriteAfterTimer1Int) // GH#685: Multiple TIMER1 interrupts - { - g_waitFirstAYWriteAfterTimer1Int = false; - //CpuCalcCycles(uExecutedCycles); // Done in parent MB_Write() via MB_UpdateCycles() - - g_AYWriteAccessTimer1IntPeriod = g_nCumulativeCycles - g_lastAY8910cycleAccess; - if (g_AYWriteAccessTimer1IntPeriod > 0xffff) - g_AYWriteAccessTimer1IntPeriod = (UINT64)g_f6522TimerPeriod_NoIRQ; - g_lastAY8910cycleAccess = g_nCumulativeCycles; - - MB_Update(); - } - if(g_bPhasorEnable) { int nAY_CS = (g_nPhasorMode & 1) ? (~(nValue >> 3) & 3) : 1; @@ -785,14 +764,15 @@ static void Votrax_Write(BYTE nDevice, BYTE nValue) //=========================================================================== +//#define DBG_MB_UPDATE +static UINT64 g_uLastMBUpdateCycle = 0; + // Called by: // . MB_UpdateCycles() - when g_nMBTimerDevice == {0,1,2,3} -// . MB_EndOfVideoFrame() - when g_nMBTimerDevice == kTIMERDEVICE_INVALID +// . MB_PeriodicUpdate() - when g_nMBTimerDevice == kTIMERDEVICE_INVALID // . SY6522_Write() - when multiple TIMER1s (interrupt sources) are active static void MB_Update(void) { - //char szDbg[200]; - if (!MockingboardVoice.bActive) return; @@ -835,33 +815,48 @@ static void MB_Update(void) // - static DWORD dwByteOffset = (DWORD)-1; - static int nNumSamplesError = 0; + // 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) - const double n6522TimerPeriod = MB_GetFramePeriod(); + if (g_uLastMBUpdateCycle == 0) + g_uLastMBUpdateCycle = g_uLastCumulativeCycles; // Initial call to MB_Update() after reset/power-cycle - const double nIrqFreq = g_fCurrentCLK6502 / n6522TimerPeriod + 0.5; // Round-up + _ASSERT(g_uLastCumulativeCycles >= g_uLastMBUpdateCycle); + double updateInterval = (double)(g_uLastCumulativeCycles - g_uLastMBUpdateCycle); + if (updateInterval < kMinimumUpdateInterval) + return; + if (updateInterval > kMaximumUpdateInterval) + updateInterval = kMaximumUpdateInterval; + + g_uLastMBUpdateCycle = g_uLastCumulativeCycles; + + const double nIrqFreq = g_fCurrentCLK6502 / updateInterval + 0.5; // Round-up const int nNumSamplesPerPeriod = (int) ((double)SAMPLE_RATE / nIrqFreq); // Eg. For 60Hz this is 735 + + static int nNumSamplesError = 0; int nNumSamples = nNumSamplesPerPeriod + nNumSamplesError; // Apply correction if(nNumSamples <= 0) nNumSamples = 0; if(nNumSamples > 2*nNumSamplesPerPeriod) nNumSamples = 2*nNumSamplesPerPeriod; + if (nNumSamples > SAMPLE_RATE) + nNumSamples = SAMPLE_RATE; // Clamp to prevent buffer overflow (bufferSize = SAMPLE_RATE) + if(nNumSamples) for(int nChip=0; nChipGetCurrentPosition(&dwCurrentPlayCursor, &dwCurrentWriteCursor); if(FAILED(hr)) return; + static DWORD dwByteOffset = (DWORD)-1; if(dwByteOffset == (DWORD)-1) { // First time in this func @@ -877,12 +872,12 @@ static void MB_Update(void) // |-----PxxxxxW-----| if((dwByteOffset > dwCurrentPlayCursor) && (dwByteOffset < dwCurrentWriteCursor)) { +#ifdef DBG_MB_UPDATE double fTicksSecs = (double)GetTickCount() / 1000.0; - //sprintf(szDbg, "%010.3f: [MBUpdt] PC=%08X, WC=%08X, Diff=%08X, Off=%08X, NS=%08X xxx\n", fTicksSecs, dwCurrentPlayCursor, dwCurrentWriteCursor, dwCurrentWriteCursor-dwCurrentPlayCursor, dwByteOffset, nNumSamples); - //OutputDebugString(szDbg); - //if (g_fh) fprintf(g_fh, "%s", szDbg); - + LogOutput("%010.3f: [MBUpdt] PC=%08X, WC=%08X, Diff=%08X, Off=%08X, NS=%08X xxx\n", fTicksSecs, dwCurrentPlayCursor, dwCurrentWriteCursor, dwCurrentWriteCursor-dwCurrentPlayCursor, dwByteOffset, nNumSamples); +#endif dwByteOffset = dwCurrentWriteCursor; + nNumSamplesError = 0; } } else @@ -890,12 +885,12 @@ static void MB_Update(void) // |xxW----------Pxxx| if((dwByteOffset > dwCurrentPlayCursor) || (dwByteOffset < dwCurrentWriteCursor)) { +#ifdef DBG_MB_UPDATE double fTicksSecs = (double)GetTickCount() / 1000.0; - //sprintf(szDbg, "%010.3f: [MBUpdt] PC=%08X, WC=%08X, Diff=%08X, Off=%08X, NS=%08X XXX\n", fTicksSecs, dwCurrentPlayCursor, dwCurrentWriteCursor, dwCurrentWriteCursor-dwCurrentPlayCursor, dwByteOffset, nNumSamples); - //OutputDebugString(szDbg); - //if (g_fh) fprintf(g_fh, "%s", szDbg); - + LogOutput("%010.3f: [MBUpdt] PC=%08X, WC=%08X, Diff=%08X, Off=%08X, NS=%08X XXX\n", fTicksSecs, dwCurrentPlayCursor, dwCurrentWriteCursor, dwCurrentWriteCursor-dwCurrentPlayCursor, dwByteOffset, nNumSamples); +#endif dwByteOffset = dwCurrentWriteCursor; + nNumSamplesError = 0; } } } @@ -913,6 +908,11 @@ static void MB_Update(void) else nNumSamplesError = 0; // Acceptable amount of data in buffer +#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 + if(nNumSamples == 0) return; @@ -954,6 +954,9 @@ static void MB_Update(void) // + DWORD dwDSLockedBufferSize0, dwDSLockedBufferSize1; + SHORT *pDSLockedBuffer0, *pDSLockedBuffer1; + if(!DSGetLock(MockingboardVoice.lpDSBvoice, dwByteOffset, (DWORD)nNumSamples*sizeof(short)*g_nMB_NumChannels, &pDSLockedBuffer0, &dwDSLockedBufferSize0, @@ -1500,9 +1503,8 @@ static void ResetState() g_nPhasorMode = 0; g_PhasorClockScaleFactor = 1; - g_waitFirstAYWriteAfterTimer1Int = false; - g_lastAY8910cycleAccess = 0; - g_AYWriteAccessTimer1IntPeriod = 0; + g_uLastMBUpdateCycle = 0; + g_cyclesThisAudioFrame = 0; // Not these, as they don't change on a CTRL+RESET or power-cycle: // g_bMBAvailable = false; @@ -1758,21 +1760,31 @@ void MB_StartOfCpuExecute() g_uLastCumulativeCycles = g_nCumulativeCycles; } -// Called by ContinueExecution() at the end of every video frame -void MB_EndOfVideoFrame() +// Called by ContinueExecution() at the end of every execution period (~1000 cycles or ~3 cycle when MODE_STEPPING) +// NB. Required for FT's TEST LAB #1 player +void MB_PeriodicUpdate(UINT executedCycles) { if (g_SoundcardType == CT_Empty) return; - if (g_nMBTimerDevice == kTIMERDEVICE_INVALID) - MB_Update(); + if (g_nMBTimerDevice != kTIMERDEVICE_INVALID) + return; + + const UINT kCyclesPerAudioFrame = 1000; + g_cyclesThisAudioFrame += executedCycles; + if (g_cyclesThisAudioFrame < kCyclesPerAudioFrame) + return; + + g_cyclesThisAudioFrame %= kCyclesPerAudioFrame; + + MB_Update(); } //----------------------------------------------------------------------------- static bool CheckTimerUnderflowAndIrq(USHORT& timerCounter, int& timerIrqDelay, const USHORT nClocks, bool* pTimerUnderflow=NULL) { - int oldTimer = timerCounter; // Catch the case for 0x0000 -> -ve, as this isn't an underflow + int oldTimer = timerCounter; int timer = timerCounter; timer -= nClocks; timerCounter = (USHORT)timer; @@ -1787,17 +1799,17 @@ static bool CheckTimerUnderflowAndIrq(USHORT& timerCounter, int& timerIrqDelay, timerIrqDelay = 0; timerIrq = true; } - // don't re-underflow if TIMER = 0x0000 or 0xFFFF (so just return) + // don't re-underflow if TIMER = 0xFFFF or 0xFFFE (so just return) } - else if (oldTimer > 0 && timer <= 0) // Underflow occurs for 0x0001 -> 0x0000 + else if (oldTimer >= 0 && timer < 0) // Underflow occurs for 0x0000 -> 0xFFFF { if (pTimerUnderflow) *pTimerUnderflow = true; // Just for Willy Byte! - if (timer <= -2) + if (timer < -2) timerIrq = true; - else // TIMER = 0x0000 or 0xFFFF - timerIrqDelay = 2 + timer; // ...so 2 or 1 cycles until IRQ + else // TIMER = 0xFFFF or 0xFFFE + timerIrqDelay = 3 + timer; // ...so 2 or 1 cycles until IRQ } return timerIrq; @@ -1818,10 +1830,6 @@ void MB_UpdateCycles(ULONG uExecutedCycles) _ASSERT(uCycles < 0x10000); USHORT nClocks = (USHORT) uCycles; - UINT numActiveTimer1s = 0; - for (int i=0; isy6522.ACR & RUNMODE) == RM_ONESHOT) { @@ -1873,7 +1870,6 @@ void MB_UpdateCycles(ULONG uExecutedCycles) // - Ultima4/5 change ACCESS_TIMER1 after a couple of IRQs into tune pMB->sy6522.TIMER1_COUNTER.w += pMB->sy6522.TIMER1_LATCH.w; // GH#651: account for underflowed cycles too pMB->sy6522.TIMER1_COUNTER.w += 2; // GH#652: account for extra 2 cycles (Rockwell, Fig.16: period=N+2cycles) - // - or maybe the counter doesn't count down during these 2 cycles? if (pMB->sy6522.TIMER1_COUNTER.w > pMB->sy6522.TIMER1_LATCH.w) { if (pMB->sy6522.TIMER1_LATCH.w) @@ -1911,34 +1907,6 @@ void MB_UpdateCycles(ULONG uExecutedCycles) //----------------------------------------------------------------------------- -static double MB_GetFramePeriod(void) -{ - // TODO: Ideally remove this (slot-4) Phasor-IFR check: [*1] - // . It's for Phasor music player, which runs in one-shot mode: - // . MB_UpdateCycles() - // -> Timer1 underflows & StopTimer1() is called, which sets g_nMBTimerDevice == kTIMERDEVICE_INVALID - // . MB_EndOfVideoFrame(), and g_nMBTimerDevice == kTIMERDEVICE_INVALID - // -> MB_Update() - // -> MB_GetFramePeriod() - // NB. Removing this Phasor-IFR check means the occasional 'g_f6522TimerPeriod_NoIRQ' gets returned. - - if (g_AYWriteAccessTimer1IntPeriod) - return (double)g_AYWriteAccessTimer1IntPeriod; - - if ((g_nMBTimerDevice != kTIMERDEVICE_INVALID) || - (g_bPhasorEnable && (g_MB[0].sy6522.IFR & IxR_TIMER1))) // [*1] - { - if (!g_n6522TimerPeriod) - return (double)0x10000; - - return (double)g_n6522TimerPeriod; - } - else - { - return g_f6522TimerPeriod_NoIRQ; - } -} - bool MB_IsActive() { if (!MockingboardVoice.bActive) diff --git a/source/Mockingboard.h b/source/Mockingboard.h index c5dd6db2..40da37ef 100644 --- a/source/Mockingboard.h +++ b/source/Mockingboard.h @@ -9,7 +9,7 @@ void MB_InitializeIO(LPBYTE pCxRomPeripheral, UINT uSlot4, UINT uSlot5); void MB_Mute(); void MB_Demute(); void MB_StartOfCpuExecute(); -void MB_EndOfVideoFrame(); +void MB_PeriodicUpdate(UINT executedCycles); void MB_CheckIRQ(); void MB_UpdateCycles(ULONG uExecutedCycles); SS_CARDTYPE MB_GetSoundcardType();