Merge remote-tracking branch 'upstream/master'
This commit is contained in:
commit
7d7e46f7ec
11 changed files with 161 additions and 134 deletions
|
@ -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.
|
||||
|
|
|
@ -35,14 +35,21 @@
|
|||
<li>'@' is AltGr+2 (for a Canadian-French keyboard).</li>
|
||||
<li>'[' is AltGr+é (for an Italian keyboard).</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>Some games or productivity software have an action based on the Open Apple (Alt) or Solid Apple (AltGr) keys, eg:
|
||||
<ul>
|
||||
<li>Lode Runner uses CTRL+@ to increase the lives.</li>
|
||||
<li>AppleLogo // and AppleWorks 5.1 have problems when using an Italian keyboard.</li>
|
||||
</ul>
|
||||
<li>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.
|
||||
<li>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.
|
||||
</li>
|
||||
<li>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.</li>
|
||||
<li>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.</li>
|
||||
</ul>
|
||||
|
||||
No Apple II speaker sound, but Mockingboard sound is working!
|
||||
<ul>
|
||||
<li>Check the Volume Control for the Speaker, under the <a href="cfg-sound.html">Configuration->Sound tab</a>.</li>
|
||||
<li>Try installing the Realtek-specific audio driver supplied by ASUS (as opposed to the default Win10 driver).</li>
|
||||
</ul>
|
||||
|
||||
</body>
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
//===========================================================================
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -37,6 +37,8 @@ struct ImageInfo
|
|||
BYTE* pImageBuffer;
|
||||
BYTE* pTrackMap; // WOZ only
|
||||
BYTE optimalBitTiming; // WOZ only
|
||||
|
||||
ImageInfo();
|
||||
};
|
||||
|
||||
//-------------------------------------
|
||||
|
|
|
@ -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; 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 +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; 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 +1865,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 +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();
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
Loading…
Add table
Reference in a new issue