mirror of
https://github.com/AppleWin/AppleWin.git
synced 2025-01-22 01:31:25 +00:00
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()
This commit is contained in:
parent
eb59c52dc4
commit
d4e01643fa
@ -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)
|
||||
|
@ -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; nChip<NUM_AY8910; nChip++)
|
||||
AY8910Update(nChip, &ppAYVoiceBuffer[nChip*NUM_VOICES_PER_AY8910], nNumSamples);
|
||||
|
||||
//
|
||||
|
||||
DWORD dwDSLockedBufferSize0, dwDSLockedBufferSize1;
|
||||
SHORT *pDSLockedBuffer0, *pDSLockedBuffer1;
|
||||
|
||||
DWORD dwCurrentPlayCursor, dwCurrentWriteCursor;
|
||||
HRESULT hr = MockingboardVoice.lpDSBvoice->GetCurrentPosition(&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; i<NUM_SY6522; i++)
|
||||
numActiveTimer1s += g_MB[i].bTimer1Active ? 1 : 0;
|
||||
|
||||
for (int i=0; i<NUM_SY6522; i++)
|
||||
{
|
||||
SY6522_AY8910* pMB = &g_MB[i];
|
||||
@ -1847,18 +1855,7 @@ void MB_UpdateCycles(ULONG uExecutedCycles)
|
||||
{
|
||||
UpdateIFR(pMB, 0, IxR_TIMER1);
|
||||
|
||||
if (numActiveTimer1s == 1)
|
||||
{
|
||||
// Do MB_Update() before StopTimer1()
|
||||
if (g_nMBTimerDevice == i)
|
||||
MB_Update();
|
||||
}
|
||||
else // GH#685: Multiple TIMER1 interrupts
|
||||
{
|
||||
// Only allow when not in interrupt handler (ie. only allow when interrupts are enabled)
|
||||
if (Is6502InterruptEnabled())
|
||||
g_waitFirstAYWriteAfterTimer1Int = true; // Defer MB_Update() until MB_Write()
|
||||
}
|
||||
MB_Update();
|
||||
|
||||
if ((pMB->sy6522.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)
|
||||
|
@ -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();
|
||||
|
Loading…
x
Reference in New Issue
Block a user