diff --git a/bin/History.txt b/bin/History.txt index 2e89c4d7..8e9266dc 100644 --- a/bin/History.txt +++ b/bin/History.txt @@ -9,6 +9,21 @@ https://github.com/AppleWin/AppleWin/issues/new Tom Charlesworth +1.29.5.0 - 11 Nov 2019 +---------------------- +. [Bug #711] Fixed Mockingboard initial "stretched" music playback (regression introduced at 1.28.7.0). +. [Bug #707] Fixed ADTPro (running under AppleWin) not working with real COM ports. +. [Bug #680] Fixed video tearing (eg. for FT's "Scroll Scroll Scroll" & "Mad Effect #2"). +. [Bug #659] Fix for Applied Engineering's Phasor song player (noisy playback) (regression introduced at 1.26.3.4). +. [Bug #652] Fixed 6522's TIMER to underflow at 0x0000 -> 0xFFFF + - and T1C=0x0000 now correctly underflows on next cycle. +. [Bug #435] COM ports above COM9 weren't available from the UI. +. [Change #716] Help's troubleshooting section: added an item for no Apple II speaker sound. +. [PR #715] Memory fix for struct ImageInfo. +. [PR #694] Debugger: Symbol loading: Fix random crash upon start up. +. Fixed crash with debug command line switches: -m and -no-mb. + + 1.29.4.0 - 24 Oct 2019 ---------------------- . [Change #702] Debugger: In soft-switch display (row-80), show a red 'x' when AltZP (and AltLC) is enabled. diff --git a/help/Troubleshooting.html b/help/Troubleshooting.html index 91f4be35..cca89842 100644 --- a/help/Troubleshooting.html +++ b/help/Troubleshooting.html @@ -35,14 +35,21 @@
  • '@' is AltGr+2 (for a Canadian-French keyboard).
  • '[' is AltGr+é (for an Italian keyboard).
  • +
  • Some games or productivity software have an action based on the Open Apple (Alt) or Solid Apple (AltGr) keys, eg: -
  • This dual function for AltGr (to both type a key and emulate Solid Apple) can cause problems such that the key (eg. '@') can never be typed. -
  • The workaround is to use the `-no-hook-alt` command line switch and configure Joystick 1 = "Keyboard (numpad)"; and then use the '0' and '.' keys for Open/Solid Apple.
  • +
  • This dual function for AltGr (to both type a key and emulate Solid Apple) can cause problems such that the key (eg. '@') can never be typed.
  • +
  • The workaround is to use the `-no-hook-alt` command line switch and configure Joystick 1 = "Keyboard (numpad)"; and then use the '0' and '.' keys for Open/Solid Apple.
  • + + + No Apple II speaker sound, but Mockingboard sound is working! + diff --git a/resource/version.h b/resource/version.h index 427ae662..8601add5 100644 --- a/resource/version.h +++ b/resource/version.h @@ -1,4 +1,4 @@ -#define APPLEWIN_VERSION 1,29,4,0 +#define APPLEWIN_VERSION 1,29,5,0 #define xstr(a) str(a) #define str(a) #a diff --git a/source/Applewin.cpp b/source/Applewin.cpp index 01579acd..4d76f89a 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); // @@ -356,7 +357,7 @@ static void ContinueExecution(void) // const UINT dwClksPerFrame = NTSC_GetCyclesPerFrame(); - if (g_dwCyclesThisFrame >= dwClksPerFrame && !VideoGetVblBar()) + if (g_dwCyclesThisFrame >= dwClksPerFrame && !VideoGetVblBarEx(g_dwCyclesThisFrame)) { g_dwCyclesThisFrame -= dwClksPerFrame; @@ -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/DiskImage.cpp b/source/DiskImage.cpp index bc286b3e..bf366c8b 100644 --- a/source/DiskImage.cpp +++ b/source/DiskImage.cpp @@ -53,11 +53,8 @@ ImageError_e ImageOpen( const std::string & pszImageFilename, return eIMAGE_ERROR_BAD_POINTER; // CREATE A RECORD FOR THE FILE - *ppImageInfo = (ImageInfo*) VirtualAlloc(NULL, sizeof(ImageInfo), MEM_COMMIT, PAGE_READWRITE); - if (*ppImageInfo == NULL) - return eIMAGE_ERROR_BAD_POINTER; + *ppImageInfo = new ImageInfo(); - ZeroMemory(*ppImageInfo, sizeof(ImageInfo)); ImageInfo* pImageInfo = *ppImageInfo; pImageInfo->bWriteProtected = *pWriteProtected; if (bExpectFloppy) pImageInfo->pImageHelper = &sg_DiskImageHelper; @@ -116,7 +113,7 @@ void ImageClose(ImageInfo* const pImageInfo, const bool bOpenError /*=false*/) pImageInfo->pImageHelper->Close(pImageInfo, bDeleteFile); - VirtualFree(pImageInfo, 0, MEM_RELEASE); + delete pImageInfo; } //=========================================================================== diff --git a/source/DiskImageHelper.cpp b/source/DiskImageHelper.cpp index 2e424467..4f833284 100644 --- a/source/DiskImageHelper.cpp +++ b/source/DiskImageHelper.cpp @@ -38,6 +38,25 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA #include "DiskImageHelper.h" #include "Memory.h" +ImageInfo::ImageInfo() +{ + // this is not a POD as it contains c++ strings + // simply zeroing is not going to work + pImageType = NULL; + pImageHelper = NULL; + FileType = eFileNormal; + hFile = INVALID_HANDLE_VALUE; + uOffset = 0; + bWriteProtected = false; + uImageSize = 0; + ZeroMemory(&zipFileInfo, sizeof(zipFileInfo)); + uNumEntriesInZip = 0; + ZeroMemory(&ValidTrack, sizeof(ValidTrack)); + uNumTracks = 0; + pImageBuffer = NULL; + pTrackMap = NULL; + optimalBitTiming = 0; +} /* DO logical order 0 1 2 3 4 5 6 7 8 9 A B C D E F */ /* physical order 0 D B 9 7 5 3 1 E C A 8 6 4 2 F */ @@ -1058,7 +1077,7 @@ public: m_pWOZEmptyTrack[i] = n; } } - virtual ~CWOZEmptyTrack(void) { delete m_pWOZEmptyTrack; } + virtual ~CWOZEmptyTrack(void) { delete [] m_pWOZEmptyTrack; } void ReadEmptyTrack(LPBYTE pTrackImageBuffer, int* pNibbles, UINT* pBitCount) { @@ -1704,7 +1723,7 @@ void CImageHelperBase::Close(ImageInfo* pImageInfo, const bool bDeleteFile) DeleteFile(pImageInfo->szFilename.c_str()); } - pImageInfo->szFilename[0] = 0; + pImageInfo->szFilename.clear(); delete [] pImageInfo->pImageBuffer; pImageInfo->pImageBuffer = NULL; diff --git a/source/DiskImageHelper.h b/source/DiskImageHelper.h index 4b56b81e..799c6e6b 100644 --- a/source/DiskImageHelper.h +++ b/source/DiskImageHelper.h @@ -37,6 +37,8 @@ struct ImageInfo BYTE* pImageBuffer; BYTE* pTrackMap; // WOZ only BYTE optimalBitTiming; // WOZ only + + ImageInfo(); }; //------------------------------------- diff --git a/source/Mockingboard.cpp b/source/Mockingboard.cpp index 996543bd..8c4c6a8c 100644 --- a/source/Mockingboard.cpp +++ b/source/Mockingboard.cpp @@ -127,7 +127,8 @@ struct SY6522_AY8910 bool bTimer1Active; bool bTimer2Active; SSI263A SpeechChip; - MockingboardUnitState_e state; // Where a unit is a 6522+AY8910 pair (or for Phasor: 6522+2xAY8910) + MockingboardUnitState_e state; // Where a unit is a 6522+AY8910 pair + MockingboardUnitState_e stateB; // Phasor: 6522 & 2nd AY8910 }; @@ -204,24 +205,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); //--------------------------------------------------------------------------- @@ -286,11 +279,12 @@ static void ResetSY6522(SY6522_AY8910* pMB) pMB->nAYCurrentRegister = 0; pMB->state = AY_INACTIVE; + pMB->stateB = AY_INACTIVE; } //----------------------------------------------------------------------------- -static void AY8910_Write(BYTE nDevice, BYTE nReg, BYTE nValue, BYTE nAYDevice) +static void AY8910_Write(BYTE nDevice, BYTE /*nReg*/, BYTE nValue, BYTE nAYDevice) { g_bMB_RegAccessedFlag = true; SY6522_AY8910* pMB = &g_MB[nDevice]; @@ -308,8 +302,16 @@ static void AY8910_Write(BYTE nDevice, BYTE nReg, BYTE nValue, BYTE nAYDevice) int nBC1 = nValue & 1; MockingboardUnitState_e nAYFunc = (MockingboardUnitState_e) ((nBDIR<<2) | (nBC2<<1) | nBC1); + MockingboardUnitState_e& state = (nAYDevice == 0) ? pMB->state : pMB->stateB; // GH#659 - if (pMB->state == AY_INACTIVE) // GH#320: functions only work from inactive state +#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 { switch (nAYFunc) { @@ -335,7 +337,7 @@ static void AY8910_Write(BYTE nDevice, BYTE nReg, BYTE nValue, BYTE nAYDevice) } } - pMB->state = nAYFunc; + state = nAYFunc; } } @@ -394,19 +396,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 +774,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 +825,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 +882,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 +895,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 +918,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 +964,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 +1513,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 +1770,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 +1809,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 +1840,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 +1880,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 +1917,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) @@ -1999,9 +1977,10 @@ void MB_GetSnapshot_v1(SS_CARD_MOCKINGBOARD_v1* const pSS, const DWORD dwSlot) // Unit version history: // 2: Added: Timer1 & Timer2 active -// 3: Added: Unit state -// 4: Added: 6522 timerIrqDelay -const UINT kUNIT_VERSION = 4; +// 3: Added: Unit state - GH#320 +// 4: Added: 6522 timerIrqDelay - GH#652 +// 5: Added: Unit state-B (Phasor only) - GH#659 +const UINT kUNIT_VERSION = 5; const UINT NUM_MB_UNITS = 2; const UINT NUM_PHASOR_UNITS = 2; @@ -2030,6 +2009,7 @@ const UINT NUM_PHASOR_UNITS = 2; #define SS_YAML_KEY_SSI263_REG_CURRENT_MODE "Current Mode" #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" #define SS_YAML_KEY_TIMER2_IRQ "Timer2 IRQ Pending" #define SS_YAML_KEY_SPEECH_IRQ "Speech IRQ Pending" @@ -2206,6 +2186,7 @@ bool MB_LoadSnapshot(YamlLoadHelper& yamlLoadHelper, UINT slot, UINT version) } pMB->state = AY_INACTIVE; + pMB->stateB = AY_INACTIVE; if (version >= 3) pMB->state = (MockingboardUnitState_e) (yamlLoadHelper.LoadUint(SS_YAML_KEY_MB_UNIT_STATE) & 7); @@ -2274,6 +2255,7 @@ void Phasor_SaveSnapshot(YamlSaveHelper& yamlSaveHelper, const UINT uSlot) SaveSnapshotSSI263(yamlSaveHelper, pMB->SpeechChip); 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); yamlSaveHelper.Save("%s: %s # Not supported\n", SS_YAML_KEY_TIMER1_IRQ, "false"); yamlSaveHelper.Save("%s: %s # Not supported\n", SS_YAML_KEY_TIMER2_IRQ, "false"); @@ -2330,8 +2312,11 @@ bool Phasor_LoadSnapshot(YamlLoadHelper& yamlLoadHelper, UINT slot, UINT version } 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(); 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(); diff --git a/source/Video.cpp b/source/Video.cpp index 4440ec30..431b7351 100644 --- a/source/Video.cpp +++ b/source/Video.cpp @@ -873,24 +873,27 @@ WORD VideoGetScannerAddress(DWORD nCycles, VideoScanner_e videoScannerAddr /*= V //=========================================================================== -// TODO: Consider replacing simply with: return g_nVideoClockVert < kVDisplayableScanLines -// - will this work in full-speed mode? -bool VideoGetVblBar(const DWORD uExecutedCycles) +// Called when *outside* of CpuExecute() +bool VideoGetVblBarEx(const DWORD dwCyclesThisFrame) { - // get video scanner position - int nCycles = CpuGetCyclesThisVideoFrame(uExecutedCycles); + if (g_bFullSpeed) + { + // Ensure that NTSC video-scanner gets updated during full-speed, so video screen can be redrawn during Apple II VBL + NTSC_VideoClockResync(dwCyclesThisFrame); + } - // calculate video parameters according to display standard - const int kScanLines = g_bVideoScannerNTSC ? kNTSCScanLines : kPALScanLines; - const int kScanCycles = kScanLines * kHClocks; - nCycles %= kScanCycles; - - // VBL' - return nCycles < kVDisplayableScanLines * kHClocks; + return g_nVideoClockVert < kVDisplayableScanLines; } -bool VideoGetVblBar(void) +// Called when *inside* CpuExecute() +bool VideoGetVblBar(const DWORD uExecutedCycles) { + if (g_bFullSpeed) + { + // Ensure that NTSC video-scanner gets updated during full-speed, so video-dependent Apple II code doesn't hang + NTSC_VideoClockResync(CpuGetCyclesThisVideoFrame(uExecutedCycles)); + } + return g_nVideoClockVert < kVDisplayableScanLines; } diff --git a/source/Video.h b/source/Video.h index 0bb88578..f5112109 100644 --- a/source/Video.h +++ b/source/Video.h @@ -188,8 +188,8 @@ void VideoReinitialize (bool bInitVideoScannerAddress = true); void VideoResetState (); enum VideoScanner_e {VS_FullAddr, VS_PartialAddrV, VS_PartialAddrH}; WORD VideoGetScannerAddress(DWORD nCycles, VideoScanner_e videoScannerAddr = VS_FullAddr); -bool VideoGetVblBar(DWORD uExecutedCycles); -bool VideoGetVblBar(void); +bool VideoGetVblBarEx(const DWORD dwCyclesThisFrame); +bool VideoGetVblBar(const DWORD uExecutedCycles); bool VideoGetSW80COL(void); bool VideoGetSWDHIRES(void);