From f234de116ff87850dcfa991f5281f080150329bb Mon Sep 17 00:00:00 2001 From: Aaron Culliney Date: Sat, 31 Jan 2015 13:57:10 -0800 Subject: [PATCH] Refactor speaker system to prevent audio glitches and to support CPU automatic speed switching Squashed commit of the following: REFACTOR : fix comments, logging, and rename some variables REFACTOR : fix up some commentary, clean deadc0de REFACTOR : mostly fix all the audio glitches - amplitudes of samples are gradually shifted to zero when speaker has fallen silent - simplifies speaker state machine - fullspeed mode only enqueues quiet samples REFACTOR : fix up a number of other functions and comments REFACTOR : clean up code to submit normal speed wave buffer to OpenAL Move some initializations to the cpu_thread() REFACTOR : properly reset the speaker cycles access counter so we don't underflow and assert REFACTOR : should never get a split buffer from our soundcore implementation Fix warning from gcc ... static array size needs to be computed from integer values REFACTOR : gcc (but not clang) complains about these, so just make them preprocessor defines REFACTOR : use unsigned long long because we don't actually care that this counter is 64bit REFACTOR : remainder_buffer and miscellaneous tweaks - Adds implementation commentary to document remainder_buffer purpose - Also adds sample average for square wave boundary in case where remainder_buffer not used (whole-sample boundary) - Variable renaming and code shuffling REFACTOR : do not dynamically alloc remainder buffer - Never attribute to cache-coherancy bugs what is a simple thread race =P REFACTOR : comments and whitespace REFACTOR : rename public speaker API functions REFACTOR : clean up public speaker API REFACTOR : tabs to spaces REFACTOR : moar deadc0de clean up and renaming REFACTOR : remove deadc0de paths from soundcore REFACTOR : fully excise soundtype stuff now that we only support soundcard output Move a file static to function scope REFACTOR : rename more variables and remove deadc0de REFACTOR : samples_buffer naming and change to explict int16_t REFACTOR : removed deadc0de and shuffled code locations REFACTOR : remainder buffer naming and clarify type REFACTOR : move joystick timing to VM module and remove header visibility REFACTOR : clarify speaker variable name REFACTOR : clarify cycle counting codepaths REFACTOR : VBL/timing interfaces - eliminates passing around a common global REFACTOR : names and comments HACK around volume issue REFACTOR : rename speaker feedback variable REFACTOR : rename global total cycle count REFACTOR : rename a constant Fix test builds REFACTOR: rename to is_fullspeed REFACTOR : local variable naming changes REFACTOR : migrate cycle timing variables to correct location and remove header visibility Allow fullspeed codepath to update speaker REFACTOR : remove deadc0de paths in prep for cleanup REFACTOR : speaker now manages its own VM entry point --- configure.ac | 2 +- src/audio/AY8910.c | 8 +- src/audio/mockingboard.c | 54 +- src/audio/mockingboard.h | 2 +- src/audio/soundcore-openal.c | 7 +- src/audio/soundcore.c | 833 ++++------------- src/audio/soundcore.h | 40 +- src/audio/speaker.c | 1664 ++++++++-------------------------- src/audio/speaker.h | 53 +- src/cpu-supp.c | 4 +- src/cpu.h | 1 - src/disk.c | 14 +- src/display.c | 14 +- src/interface.c | 2 +- src/meta/debugger.c | 6 +- src/misc.c | 21 +- src/test/testcommon.c | 4 +- src/test/testcpu.c | 1 + src/timing.c | 228 ++--- src/timing.h | 64 +- src/video/video.h | 6 +- src/vm.c | 112 ++- 22 files changed, 861 insertions(+), 2279 deletions(-) diff --git a/configure.ac b/configure.ac index 4249485f..1e860223 100644 --- a/configure.ac +++ b/configure.ac @@ -226,7 +226,7 @@ AC_ARG_ENABLE([audio], AS_HELP_STRING([--disable-audio], [Disable emulator audio dnl found OpenAL ... AC_DEFINE(AUDIO_OPENAL, 1, [Enable OpenAL audio output]) AC_DEFINE(AUDIO_ENABLED, 1, [Enable sound module]) - AUDIO_GLUE_C="src/audio/mockingboard.c" + AUDIO_GLUE_C="src/audio/speaker.c src/audio/mockingboard.c" AUDIO_O="src/audio/soundcore.o src/audio/soundcore-openal.o src/audio/speaker.o src/audio/win-shim.o src/audio/alhelpers.o src/audio/mockingboard.o src/audio/AY8910.o" ], [ AC_MSG_WARN([Could not find OpenAL libraries, sound will be disabled]) diff --git a/src/audio/AY8910.c b/src/audio/AY8910.c index 7437ac13..e4bf856c 100644 --- a/src/audio/AY8910.c +++ b/src/audio/AY8910.c @@ -137,7 +137,7 @@ void CAY8910_init(CAY8910 *_this) _this->env_first = 1; _this->env_rev = 0; _this->env_counter = 15; - //m_fCurrentCLK_AY8910 = g_fCurrentCLK6502; -- believe this is handled by an initial call to SetCLK() + //m_fCurrentCLK_AY8910 = cycles_persec_target; -- believe this is handled by an initial call to SetCLK() }; @@ -983,7 +983,7 @@ void SetCLK(double CLK) // AY8910 interface #ifndef APPLE2IX -#include "CPU.h" // For g_nCumulativeCycles +#include "CPU.h" // For cycles_count_total #endif static CAY8910 g_AY8910[MAX_8910]; @@ -996,7 +996,7 @@ static unsigned __int64 g_uLastCumulativeCycles = 0; void _AYWriteReg(int chip, int r, int v) { - libspectrum_dword uOffset = (libspectrum_dword) (g_nCumulativeCycles - g_uLastCumulativeCycles); + libspectrum_dword uOffset = (libspectrum_dword) (cycles_count_total - g_uLastCumulativeCycles); sound_ay_write(&g_AY8910[chip], r, v, uOffset); } @@ -1008,7 +1008,7 @@ void AY8910_reset(int chip) void AY8910UpdateSetCycles() { - g_uLastCumulativeCycles = g_nCumulativeCycles; + g_uLastCumulativeCycles = cycles_count_total; } void AY8910Update(int chip, INT16** buffer, int nNumSamples) diff --git a/src/audio/mockingboard.c b/src/audio/mockingboard.c index c3b2b5d2..5469cbc8 100644 --- a/src/audio/mockingboard.c +++ b/src/audio/mockingboard.c @@ -822,13 +822,13 @@ static void MB_Update() return; #endif - if (g_bFullSpeed) + if (is_fullspeed) { // Keep AY reg writes relative to the current 'frame' // - Required for Ultima3: // . Tune ends - // . g_bFullSpeed:=true (disk-spinning) for ~50 frames - // . U3 sets AY_ENABLE:=0xFF (as a side-effect, this sets g_bFullSpeed:=false) + // . is_fullspeed:=true (disk-spinning) for ~50 frames + // . U3 sets AY_ENABLE:=0xFF (as a side-effect, this sets is_fullspeed:=false) // o Without this, the write to AY_ENABLE gets ignored (since AY8910's /g_uLastCumulativeCycles/ was last set 50 frame ago) AY8910UpdateSetCycles(); @@ -844,12 +844,12 @@ static void MB_Update() { if(!g_nMB_InActiveCycleCount) { - g_nMB_InActiveCycleCount = g_nCumulativeCycles; + g_nMB_InActiveCycleCount = cycles_count_total; } #ifdef APPLE2IX - else if(g_nCumulativeCycles - g_nMB_InActiveCycleCount > (uint64_t)g_fCurrentCLK6502/10) + else if(cycles_count_total - g_nMB_InActiveCycleCount > (uint64_t)cycles_persec_target/10) #else - else if(g_nCumulativeCycles - g_nMB_InActiveCycleCount > (unsigned __int64)g_fCurrentCLK6502/10) + else if(cycles_count_total - g_nMB_InActiveCycleCount > (unsigned __int64)cycles_persec_target/10) #endif { // After 0.1 sec of Apple time, assume MB is not active @@ -872,7 +872,7 @@ static void MB_Update() const double n6522TimerPeriod = MB_GetFramePeriod(); - const double nIrqFreq = g_fCurrentCLK6502 / n6522TimerPeriod + 0.5; // Round-up + const double nIrqFreq = cycles_persec_target / n6522TimerPeriod + 0.5; // Round-up const int nNumSamplesPerPeriod = (int) ((double)SAMPLE_RATE / nIrqFreq); // Eg. For 60Hz this is 735 int nNumSamples = nNumSamplesPerPeriod + nNumSamplesError; // Apply correction if(nNumSamples <= 0) @@ -968,7 +968,7 @@ static void MB_Update() if (dbg_print != now) { dbg_print = now; - LOG("g_nCpuCyclesFeedback:%d nNumSamplesError:%d n6522TimerPeriod:%f nIrqFreq:%f nNumSamplesPerPeriod:%d nNumSamples:%d nBytesRemaining:%d ", g_nCpuCyclesFeedback, nNumSamplesError, n6522TimerPeriod, nIrqFreq, nNumSamplesPerPeriod, nNumSamples, nBytesRemaining); + LOG("cycles_speaker_feedback:%d nNumSamplesError:%d n6522TimerPeriod:%f nIrqFreq:%f nNumSamplesPerPeriod:%d nNumSamples:%d nBytesRemaining:%d ", cycles_speaker_feedback, nNumSamplesError, n6522TimerPeriod, nIrqFreq, nNumSamplesPerPeriod, nNumSamples, nBytesRemaining); } #endif @@ -1611,7 +1611,7 @@ void MB_Initialize() #endif } - AY8910_InitAll((int)g_fCurrentCLK6502, SAMPLE_RATE); + AY8910_InitAll((int)cycles_persec_target, SAMPLE_RATE); LogFileOutput("MB_Initialize: AY8910_InitAll()\n"); for(i=0; i= 0x10000) { printf("OOPS!!! Mockingboard failed assert!\n"); @@ -2137,6 +2136,11 @@ DWORD MB_GetVolume() void MB_SetVolume(DWORD dwVolume, DWORD dwVolumeMax) { +#ifdef APPLE2IX +#warning TODO FIXME ... why is OpenAL on my Linux box so damn loud?! + dwVolume >>= 2; + dwVolumeMax >>= 2; +#endif MockingboardVoice.dwUserVolume = dwVolume; MockingboardVoice.nVolume = NewVolume(dwVolume, dwVolumeMax); diff --git a/src/audio/mockingboard.h b/src/audio/mockingboard.h index d5ff3f2f..0cd1ac3b 100644 --- a/src/audio/mockingboard.h +++ b/src/audio/mockingboard.h @@ -105,7 +105,7 @@ void MB_Mute(); void MB_Demute(); void MB_StartOfCpuExecute(); void MB_EndOfVideoFrame(); -void MB_UpdateCycles(ULONG uExecutedCycles); +void MB_UpdateCycles(void); SS_CARDTYPE MB_GetSoundcardType(); void MB_SetSoundcardType(SS_CARDTYPE NewSoundcardType); double MB_GetFramePeriod(); diff --git a/src/audio/soundcore-openal.c b/src/audio/soundcore-openal.c index 1883465e..da30cb76 100644 --- a/src/audio/soundcore-openal.c +++ b/src/audio/soundcore-openal.c @@ -469,7 +469,10 @@ static long ALGetPosition(void *_this, unsigned long *bytes_queued, unsigned lon { ALVoice *voice = (ALVoice*)_this; *bytes_queued = 0; - *unused_write_cursor = 0; + if (unused_write_cursor) + { + *unused_write_cursor = 0; + } ALuint queued = 0; int err = _ALProcessPlayBuffers(voice, &queued); @@ -537,7 +540,7 @@ static long ALBegin(void *_this, unsigned long unused, unsigned long write_bytes static int _ALSubmitBufferToOpenAL(ALVoice *voice) { - int err =0; + int err = 0; ALPlayBuf *node = PlaylistEnqueue(voice, voice->index); if (!node) diff --git a/src/audio/soundcore.c b/src/audio/soundcore.c index b3946678..b5593310 100644 --- a/src/audio/soundcore.c +++ b/src/audio/soundcore.c @@ -28,42 +28,22 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "common.h" - -#ifdef APPLE2IX - #include "audio/win-shim.h" -#else -#include "StdAfx.h" -#endif - //----------------------------------------------------------------------------- #define MAX_SOUND_DEVICES 100 -#ifdef APPLE2IX static char **sound_devices = NULL; -#else -static char *sound_devices[MAX_SOUND_DEVICES]; -static GUID sound_device_guid[MAX_SOUND_DEVICES]; -#endif static long num_sound_devices = 0; -#ifdef APPLE2IX LPDIRECTSOUND g_lpDS = NULL; -#else -static LPDIRECTSOUND g_lpDS = NULL; -#endif //------------------------------------- // Used for muting & fading: -#ifdef APPLE2IX #define uMAX_VOICES 66 -#else -static const UINT uMAX_VOICES = 66; // 64 phonemes + spkr + mockingboard -#endif static UINT g_uNumVoices = 0; static VOICE* g_pVoices[uMAX_VOICES] = {NULL}; @@ -73,7 +53,6 @@ static VOICE* g_pSpeakerVoice = NULL; bool g_bDSAvailable = false; -#ifdef APPLE2IX bool g_bDisableDirectSound = false; FILE *g_fh = NULL; @@ -81,511 +60,209 @@ __attribute__((constructor)) static void _init_soundcore() { g_fh = error_log; } -#endif - -//----------------------------------------------------------------------------- - -#ifndef APPLE2IX -static BOOL CALLBACK DSEnumProc(LPGUID lpGUID, LPCTSTR lpszDesc, LPCTSTR lpszDrvName, LPVOID lpContext) -{ - int i = num_sound_devices; - if(i == MAX_SOUND_DEVICES) - return TRUE; - if(lpGUID != NULL) - memcpy(&sound_device_guid[i], lpGUID, sizeof (GUID)); - sound_devices[i] = _strdup(lpszDesc); - - if(g_fh) fprintf(g_fh, "%d: %s - %s\n",i,lpszDesc,lpszDrvName); - - num_sound_devices++; - return TRUE; -} -#endif - -//----------------------------------------------------------------------------- - -#ifdef _DEBUG -static char *DirectSound_ErrorText (HRESULT error) -{ - switch( error ) - { - case DSERR_ALLOCATED: - return "Allocated"; - case DSERR_CONTROLUNAVAIL: - return "Control Unavailable"; - case DSERR_INVALIDPARAM: - return "Invalid Parameter"; - case DSERR_INVALIDCALL: - return "Invalid Call"; - case DSERR_GENERIC: - return "Generic"; - case DSERR_PRIOLEVELNEEDED: - return "Priority Level Needed"; - case DSERR_OUTOFMEMORY: - return "Out of Memory"; - case DSERR_BADFORMAT: - return "Bad Format"; - case DSERR_UNSUPPORTED: - return "Unsupported"; - case DSERR_NODRIVER: - return "No Driver"; - case DSERR_ALREADYINITIALIZED: - return "Already Initialized"; - case DSERR_NOAGGREGATION: - return "No Aggregation"; - case DSERR_BUFFERLOST: - return "Buffer Lost"; - case DSERR_OTHERAPPHASPRIO: - return "Other Application Has Priority"; - case DSERR_UNINITIALIZED: - return "Uninitialized"; - case DSERR_NOINTERFACE: - return "No Interface"; - default: - return "Unknown"; - } -} -#endif //----------------------------------------------------------------------------- bool DSGetLock(LPDIRECTSOUNDBUFFER pVoice, DWORD dwOffset, DWORD dwBytes, - SHORT** ppDSLockedBuffer0, DWORD* pdwDSLockedBufferSize0, - SHORT** ppDSLockedBuffer1, DWORD* pdwDSLockedBufferSize1) + SHORT** ppDSLockedBuffer0, DWORD* pdwDSLockedBufferSize0, + SHORT** ppDSLockedBuffer1, DWORD* pdwDSLockedBufferSize1) { - DWORD nStatus = 0; -#ifdef APPLE2IX - HRESULT hr = pVoice->GetStatus(pVoice->_this, &nStatus); -#else - HRESULT hr = pVoice->GetStatus(&nStatus); -#endif - if(hr != DS_OK) - return false; + DWORD nStatus = 0; + HRESULT hr = pVoice->GetStatus(pVoice->_this, &nStatus); + if(hr != DS_OK) + return false; - if(nStatus & DSBSTATUS_BUFFERLOST) - { - do - { -#ifdef APPLE2IX - hr = pVoice->Restore(pVoice->_this); -#else - hr = pVoice->Restore(); -#endif - if(hr == DSERR_BUFFERLOST) - Sleep(10); - } - while(hr != DS_OK); - } + if(nStatus & DSBSTATUS_BUFFERLOST) + { + do + { + hr = pVoice->Restore(pVoice->_this); + if(hr == DSERR_BUFFERLOST) + Sleep(10); + } + while(hr != DS_OK); + } - // Get write only pointer(s) to sound buffer - if(dwBytes == 0) - { -#ifdef APPLE2IX - if(FAILED(hr = pVoice->Lock(pVoice->_this, 0, 0, -#else - if(FAILED(hr = pVoice->Lock(0, 0, -#endif - (void**)ppDSLockedBuffer0, pdwDSLockedBufferSize0, - (void**)ppDSLockedBuffer1, pdwDSLockedBufferSize1, - DSBLOCK_ENTIREBUFFER))) - return false; - } - else - { -#ifdef APPLE2IX - if(FAILED(hr = pVoice->Lock(pVoice->_this, dwOffset, dwBytes, -#else - if(FAILED(hr = pVoice->Lock(dwOffset, dwBytes, -#endif - (void**)ppDSLockedBuffer0, pdwDSLockedBufferSize0, - (void**)ppDSLockedBuffer1, pdwDSLockedBufferSize1, - 0))) - return false; - } + // Get write only pointer(s) to sound buffer + if(dwBytes == 0) + { + if(FAILED(hr = pVoice->Lock(pVoice->_this, 0, 0, + (void**)ppDSLockedBuffer0, pdwDSLockedBufferSize0, + (void**)ppDSLockedBuffer1, pdwDSLockedBufferSize1, + DSBLOCK_ENTIREBUFFER))) + return false; + } + else + { + if(FAILED(hr = pVoice->Lock(pVoice->_this, dwOffset, dwBytes, + (void**)ppDSLockedBuffer0, pdwDSLockedBufferSize0, + (void**)ppDSLockedBuffer1, pdwDSLockedBufferSize1, + 0))) + return false; + } - return true; + return true; } //----------------------------------------------------------------------------- HRESULT DSGetSoundBuffer(VOICE* pVoice, DWORD dwFlags, DWORD dwBufferSize, DWORD nSampleRate, int nChannels) { - WAVEFORMATEX wavfmt; - DSBUFFERDESC dsbdesc; + WAVEFORMATEX wavfmt; + DSBUFFERDESC dsbdesc; - wavfmt.wFormatTag = WAVE_FORMAT_PCM; - wavfmt.nChannels = nChannels; - wavfmt.nSamplesPerSec = nSampleRate; - wavfmt.wBitsPerSample = 16; - wavfmt.nBlockAlign = wavfmt.nChannels==1 ? 2 : 4; - wavfmt.nAvgBytesPerSec = wavfmt.nBlockAlign * wavfmt.nSamplesPerSec; + wavfmt.wFormatTag = WAVE_FORMAT_PCM; + wavfmt.nChannels = nChannels; + wavfmt.nSamplesPerSec = nSampleRate; + wavfmt.wBitsPerSample = 16; + wavfmt.nBlockAlign = wavfmt.nChannels==1 ? 2 : 4; + wavfmt.nAvgBytesPerSec = wavfmt.nBlockAlign * wavfmt.nSamplesPerSec; - memset (&dsbdesc, 0, sizeof (dsbdesc)); - dsbdesc.dwSize = sizeof (dsbdesc); - dsbdesc.dwBufferBytes = dwBufferSize; - dsbdesc.lpwfxFormat = &wavfmt; - dsbdesc.dwFlags = dwFlags | DSBCAPS_GETCURRENTPOSITION2 | DSBCAPS_STICKYFOCUS; + memset (&dsbdesc, 0, sizeof (dsbdesc)); + dsbdesc.dwSize = sizeof (dsbdesc); + dsbdesc.dwBufferBytes = dwBufferSize; + dsbdesc.lpwfxFormat = &wavfmt; + dsbdesc.dwFlags = dwFlags | DSBCAPS_GETCURRENTPOSITION2 | DSBCAPS_STICKYFOCUS; - // Are buffers released when g_lpDS OR pVoice->lpDSBvoice is released? - // . From DirectX doc: - // "Buffer objects are owned by the device object that created them. When the - // device object is released, all buffers created by that object are also released..." -#ifdef APPLE2IX + // Are buffers released when g_lpDS OR pVoice->lpDSBvoice is released? + // . From DirectX doc: + // "Buffer objects are owned by the device object that created them. When the + // device object is released, all buffers created by that object are also released..." if (pVoice->lpDSBvoice) { g_lpDS->DestroySoundBuffer(&pVoice->lpDSBvoice); //DSReleaseSoundBuffer(pVoice); } - HRESULT hr = g_lpDS->CreateSoundBuffer(&dsbdesc, &pVoice->lpDSBvoice, g_lpDS); -#else - HRESULT hr = g_lpDS->CreateSoundBuffer(&dsbdesc, &pVoice->lpDSBvoice, NULL); -#endif - if(FAILED(hr)) - return hr; + HRESULT hr = g_lpDS->CreateSoundBuffer(&dsbdesc, &pVoice->lpDSBvoice, g_lpDS); + if(FAILED(hr)) + return hr; - // + // - _ASSERT(g_uNumVoices < uMAX_VOICES); - if(g_uNumVoices < uMAX_VOICES) - g_pVoices[g_uNumVoices++] = pVoice; + _ASSERT(g_uNumVoices < uMAX_VOICES); + if(g_uNumVoices < uMAX_VOICES) + g_pVoices[g_uNumVoices++] = pVoice; - if(pVoice->bIsSpeaker) - g_pSpeakerVoice = pVoice; + if(pVoice->bIsSpeaker) + g_pSpeakerVoice = pVoice; - return hr; + return hr; } void DSReleaseSoundBuffer(VOICE* pVoice) { - if(pVoice->bIsSpeaker) - g_pSpeakerVoice = NULL; + if(pVoice->bIsSpeaker) + g_pSpeakerVoice = NULL; - for(UINT i=0; iDestroySoundBuffer(&pVoice->lpDSBvoice); } -#else - SAFE_RELEASE(pVoice->lpDSBvoice); -#endif } //----------------------------------------------------------------------------- bool DSZeroVoiceBuffer(PVOICE Voice, char* pszDevName, DWORD dwBufferSize) { - DWORD dwDSLockedBufferSize = 0; // Size of the locked DirectSound buffer - SHORT* pDSLockedBuffer; + DWORD dwDSLockedBufferSize = 0; // Size of the locked DirectSound buffer + SHORT* pDSLockedBuffer; -#ifdef APPLE2IX DWORD argX = 0; - HRESULT hr = Voice->lpDSBvoice->Stop(Voice->lpDSBvoice->_this); - if(FAILED(hr)) - { - if(g_fh) fprintf(g_fh, "%s: DSStop failed (%08X)\n",pszDevName,(unsigned int)hr); - return false; - } - hr = !DSGetLock(Voice->lpDSBvoice, 0, 0, &pDSLockedBuffer, &dwDSLockedBufferSize, NULL, &argX); -#else + HRESULT hr = Voice->lpDSBvoice->Stop(Voice->lpDSBvoice->_this); + if(FAILED(hr)) + { + if(g_fh) fprintf(g_fh, "%s: DSStop failed (%08X)\n",pszDevName,(unsigned int)hr); + return false; + } + hr = !DSGetLock(Voice->lpDSBvoice, 0, 0, &pDSLockedBuffer, &dwDSLockedBufferSize, NULL, &argX); + if(FAILED(hr)) + { + if(g_fh) fprintf(g_fh, "%s: DSGetLock failed (%08X)\n",pszDevName,(unsigned int)hr); + return false; + } - hr = DSGetLock(Voice->lpDSBvoice, 0, 0, &pDSLockedBuffer, &dwDSLockedBufferSize, NULL, 0); -#endif - if(FAILED(hr)) - { - if(g_fh) fprintf(g_fh, "%s: DSGetLock failed (%08X)\n",pszDevName,(unsigned int)hr); - return false; - } + _ASSERT(dwDSLockedBufferSize == dwBufferSize); + memset(pDSLockedBuffer, 0x00, dwDSLockedBufferSize); - _ASSERT(dwDSLockedBufferSize == dwBufferSize); - memset(pDSLockedBuffer, 0x00, dwDSLockedBufferSize); + hr = Voice->lpDSBvoice->Unlock(Voice->lpDSBvoice->_this, (void*)pDSLockedBuffer, dwDSLockedBufferSize, NULL, argX); + if(FAILED(hr)) + { + if(g_fh) fprintf(g_fh, "%s: DSUnlock failed (%08X)\n",pszDevName,(unsigned int)hr); + return false; + } -#ifdef APPLE2IX - hr = Voice->lpDSBvoice->Unlock(Voice->lpDSBvoice->_this, (void*)pDSLockedBuffer, dwDSLockedBufferSize, NULL, argX); -#else - hr = Voice->lpDSBvoice->Unlock((void*)pDSLockedBuffer, dwDSLockedBufferSize, NULL, 0); -#endif - if(FAILED(hr)) - { - if(g_fh) fprintf(g_fh, "%s: DSUnlock failed (%08X)\n",pszDevName,(unsigned int)hr); - return false; - } + hr = Voice->lpDSBvoice->Play(Voice->lpDSBvoice->_this,0,0,0); + if(FAILED(hr)) + { + if(g_fh) fprintf(g_fh, "%s: DSPlay failed (%08X)\n",pszDevName,(unsigned int)hr); + return false; + } -#ifdef APPLE2IX - hr = Voice->lpDSBvoice->Play(Voice->lpDSBvoice->_this,0,0,0); -#else - hr = Voice->lpDSBvoice->Play(0,0,DSBPLAY_LOOPING); -#endif - if(FAILED(hr)) - { - if(g_fh) fprintf(g_fh, "%s: DSPlay failed (%08X)\n",pszDevName,(unsigned int)hr); - return false; - } - - return true; + return true; } //----------------------------------------------------------------------------- bool DSZeroVoiceWritableBuffer(PVOICE Voice, char* pszDevName, DWORD dwBufferSize) { - DWORD dwDSLockedBufferSize0=0, dwDSLockedBufferSize1=0; - SHORT *pDSLockedBuffer0, *pDSLockedBuffer1; + DWORD dwDSLockedBufferSize0=0, dwDSLockedBufferSize1=0; + SHORT *pDSLockedBuffer0, *pDSLockedBuffer1; - HRESULT hr = DSGetLock(Voice->lpDSBvoice, - 0, dwBufferSize, - &pDSLockedBuffer0, &dwDSLockedBufferSize0, - &pDSLockedBuffer1, &dwDSLockedBufferSize1); -#ifdef APPLE2IX + HRESULT hr = DSGetLock(Voice->lpDSBvoice, + 0, dwBufferSize, + &pDSLockedBuffer0, &dwDSLockedBufferSize0, + &pDSLockedBuffer1, &dwDSLockedBufferSize1); hr = !hr; -#endif - if(FAILED(hr)) - { - if(g_fh) fprintf(g_fh, "%s: DSGetLock failed (%08X)\n",pszDevName,(unsigned int)hr); - return false; - } + if(FAILED(hr)) + { + if(g_fh) fprintf(g_fh, "%s: DSGetLock failed (%08X)\n",pszDevName,(unsigned int)hr); + return false; + } - memset(pDSLockedBuffer0, 0x00, dwDSLockedBufferSize0); - if(pDSLockedBuffer1) - memset(pDSLockedBuffer1, 0x00, dwDSLockedBufferSize1); + memset(pDSLockedBuffer0, 0x00, dwDSLockedBufferSize0); + if(pDSLockedBuffer1) + memset(pDSLockedBuffer1, 0x00, dwDSLockedBufferSize1); -#ifdef APPLE2IX - hr = Voice->lpDSBvoice->Unlock(Voice->lpDSBvoice->_this, (void*)pDSLockedBuffer0, dwDSLockedBufferSize0, -#else - hr = Voice->lpDSBvoice->Unlock((void*)pDSLockedBuffer0, dwDSLockedBufferSize0, -#endif - (void*)pDSLockedBuffer1, dwDSLockedBufferSize1); - if(FAILED(hr)) - { - if(g_fh) fprintf(g_fh, "%s: DSUnlock failed (%08X)\n",pszDevName,(unsigned int)hr); - return false; - } + hr = Voice->lpDSBvoice->Unlock(Voice->lpDSBvoice->_this, (void*)pDSLockedBuffer0, dwDSLockedBufferSize0, + (void*)pDSLockedBuffer1, dwDSLockedBufferSize1); + if(FAILED(hr)) + { + if(g_fh) fprintf(g_fh, "%s: DSUnlock failed (%08X)\n",pszDevName,(unsigned int)hr); + return false; + } - return true; + return true; } //----------------------------------------------------------------------------- -static bool g_bTimerActive = false; -#ifndef APPLE2IX -static eFADE g_FadeType = FADE_NONE; -static UINT_PTR g_nTimerID = 0; -#endif - -//------------------------------------- - -#ifndef APPLE2IX -static VOID CALLBACK SoundCore_TimerFunc(HWND hwnd, UINT uMsg, UINT_PTR idEvent, DWORD dwTime); -#endif - -#ifndef APPLE2IX -static bool SoundCore_StartTimer() -{ - if(g_bTimerActive) - return true; - - g_nTimerID = SetTimer(NULL, 0, 1, SoundCore_TimerFunc); // 1ms interval - if(g_nTimerID == 0) - { - fprintf(stderr, "Error creating timer\n"); - _ASSERT(0); - return false; - } - - g_bTimerActive = true; - return true; -} -#endif - -static void SoundCore_StopTimer() -{ -#ifdef APPLE2IX - // using our own timing and nanosleep() ... -#else - if(!g_bTimerActive) - return; - - if(KillTimer(NULL, g_nTimerID) == FALSE) - { - fprintf(stderr, "Error killing timer\n"); - _ASSERT(0); - return; - } - - g_bTimerActive = false; -#endif -} - -bool SoundCore_GetTimerState() -{ - return g_bTimerActive; -} - -//------------------------------------- - -// [OLD: Used to fade volume in/out] -// FADE_OUT : Just keep filling speaker soundbuffer with same value -// FADE_IN : Switch to FADE_NONE & StopTimer() - -#ifndef APPLE2IX -static VOID CALLBACK SoundCore_TimerFunc(HWND hwnd, UINT uMsg, UINT_PTR idEvent, DWORD dwTime) -{ - if((g_pSpeakerVoice == NULL) || (g_pSpeakerVoice->bActive == false)) - g_FadeType = FADE_NONE; - - // Timer expired - if(g_FadeType == FADE_NONE) - { - SoundCore_StopTimer(); - return; - } - - // - -#if 1 - if(g_FadeType == FADE_IN) - g_FadeType = FADE_NONE; - else - SpkrUpdate_Timer(); -#else - const LONG nFadeUnit_Fast = (DSBVOLUME_MAX - DSBVOLUME_MIN) / 10; - const LONG nFadeUnit_Slow = (DSBVOLUME_MAX - DSBVOLUME_MIN) / 1000; // Less noisy for 'silence' - - LONG nFadeUnit = g_pSpeakerVoice->bRecentlyActive ? nFadeUnit_Fast : nFadeUnit_Slow; - LONG nFadeVolume = g_pSpeakerVoice->nFadeVolume; - - if(g_FadeType == FADE_IN) - { - if(nFadeVolume == g_pSpeakerVoice->nVolume) - { - g_FadeType = FADE_NONE; - SoundCore_StopTimer(); - return; - } - - nFadeVolume += nFadeUnit; - - if(nFadeVolume > g_pSpeakerVoice->nVolume) - nFadeVolume = g_pSpeakerVoice->nVolume; - } - else // FADE_OUT - { - if(nFadeVolume == DSBVOLUME_MIN) - { - g_FadeType = FADE_NONE; - SoundCore_StopTimer(); - return; - } - - nFadeVolume -= nFadeUnit; - - if(nFadeVolume < DSBVOLUME_MIN) - nFadeVolume = DSBVOLUME_MIN; - } - - g_pSpeakerVoice->nFadeVolume = nFadeVolume; - g_pSpeakerVoice->lpDSBvoice->SetVolume(nFadeVolume); -#endif -} -#endif - -//----------------------------------------------------------------------------- - -#ifndef APPLE2IX -void SoundCore_SetFade(eFADE FadeType) -{ - static int nLastMode = -1; - - if(g_nAppMode == MODE_DEBUG) - return; - - // Fade in/out just for speaker, the others are demuted/muted - if(FadeType != FADE_NONE) - { - for(UINT i=0; iSetVolume() doesn't work without this! - // . See SoundCore_TweakVolumes() - could be this? - if((g_pVoices[i]->bIsSpeaker) && (g_nAppMode != MODE_LOGO) && (nLastMode != MODE_LOGO)) - { - g_pVoices[i]->lpDSBvoice->GetVolume(&g_pVoices[i]->nFadeVolume); - g_FadeType = FadeType; - SoundCore_StartTimer(); - } - else if(FadeType == FADE_OUT) - { - g_pVoices[i]->lpDSBvoice->SetVolume(DSBVOLUME_MIN); - g_pVoices[i]->bMute = true; - } - else // FADE_IN - { - g_pVoices[i]->lpDSBvoice->SetVolume(g_pVoices[i]->nVolume); - g_pVoices[i]->bMute = false; - } - } - } - else // FadeType == FADE_NONE - { - if( (g_FadeType != FADE_NONE) && // Currently fading-in/out - (g_pSpeakerVoice && g_pSpeakerVoice->bActive) ) - { - g_FadeType = FADE_NONE; // TimerFunc will call StopTimer() - g_pSpeakerVoice->lpDSBvoice->SetVolume(g_pSpeakerVoice->nVolume); - } - } - - nLastMode = g_nAppMode; -} -#endif - -//----------------------------------------------------------------------------- - -// If AppleWin started by double-clicking a .dsk, then our window won't have focus when volumes are set (so gets ignored). -// Subsequent setting (to the same volume) will get ignored, as DirectSound thinks that volume is already set. - -#ifndef APPLE2IX -void SoundCore_TweakVolumes() -{ - for (UINT i=0; ilpDSBvoice->SetVolume(g_pVoices[i]->nVolume-1); - g_pVoices[i]->lpDSBvoice->SetVolume(g_pVoices[i]->nVolume); - } -} -#endif - -//----------------------------------------------------------------------------- - static UINT g_uDSInitRefCount = 0; bool DSInit() { -#ifdef APPLE2IX if (!g_fh) { g_fh = stderr; } -#endif - if(g_bDSAvailable) - { - g_uDSInitRefCount++; - return true; // Already initialised successfully - } + if(g_bDSAvailable) + { + g_uDSInitRefCount++; + return true; // Already initialised successfully + } -#ifdef APPLE2IX if (sound_devices) { LOG("Destroying old device names..."); @@ -598,261 +275,107 @@ bool DSInit() FREE(sound_devices); sound_devices = NULL; } - num_sound_devices = SoundSystemEnumerate(&sound_devices, MAX_SOUND_DEVICES); + num_sound_devices = SoundSystemEnumerate(&sound_devices, MAX_SOUND_DEVICES); HRESULT hr = (num_sound_devices <= 0); -#else - HRESULT hr = DirectSoundEnumerate((LPDSENUMCALLBACK)DSEnumProc, NULL); -#endif - if(FAILED(hr)) - { - if(g_fh) fprintf(g_fh, "DSEnumerate failed (%08X)\n",(unsigned int)hr); - return false; - } + if(FAILED(hr)) + { + if(g_fh) fprintf(g_fh, "DSEnumerate failed (%08X)\n",(unsigned int)hr); + return false; + } - if(g_fh) - { -#if !defined(APPLE2IX) - fprintf(g_fh, "Number of sound devices = %d\n",num_sound_devices); -#endif - } + if(g_fh) + { + fprintf(g_fh, "Number of sound devices = %ld\n",num_sound_devices); + } - bool bCreatedOK = false; - for(int x=0; xSetCooperativeLevel(g_hFrameWindow, DSSCL_NORMAL); - if(FAILED(hr)) - { - if(g_fh) fprintf(g_fh, "SetCooperativeLevel failed (%08X)\n",hr); - return false; - } + g_bDSAvailable = true; - DSCAPS DSCaps; - ZeroMemory(&DSCaps, sizeof(DSCAPS)); - DSCaps.dwSize = sizeof(DSCAPS); - hr = g_lpDS->GetCaps(&DSCaps); - if(FAILED(hr)) - { - if(g_fh) fprintf(g_fh, "GetCaps failed (%08X)\n",hr); - // Not fatal: so continue... - } -#endif + g_uDSInitRefCount = 1; - g_bDSAvailable = true; - - g_uDSInitRefCount = 1; - - return true; + return true; } //----------------------------------------------------------------------------- void DSUninit() { - if(!g_bDSAvailable) - return; + if(!g_bDSAvailable) + return; - _ASSERT(g_uDSInitRefCount); + _ASSERT(g_uDSInitRefCount); - if(g_uDSInitRefCount == 0) - return; + if(g_uDSInitRefCount == 0) + return; - g_uDSInitRefCount--; + g_uDSInitRefCount--; - if(g_uDSInitRefCount) - return; + if(g_uDSInitRefCount) + return; - // + // - _ASSERT(g_uNumVoices == 0); + _ASSERT(g_uNumVoices == 0); -#ifdef APPLE2IX SoundSystemDestroy((SoundSystemStruct**)&g_lpDS); -#else - SAFE_RELEASE(g_lpDS); -#endif - g_bDSAvailable = false; - - SoundCore_StopTimer(); + g_bDSAvailable = false; } //----------------------------------------------------------------------------- LONG NewVolume(DWORD dwVolume, DWORD dwVolumeMax) { - float fVol = (float) dwVolume / (float) dwVolumeMax; // 0.0=Max, 1.0=Min + float fVol = (float) dwVolume / (float) dwVolumeMax; // 0.0=Max, 1.0=Min - return (LONG) ((float) DSBVOLUME_MIN * fVol); + return (LONG) ((float) DSBVOLUME_MIN * fVol); } //============================================================================= -static int g_nErrorInc = 20; // Old: 1 -static int g_nErrorMax = 200; // Old: 50 +static int g_nErrorInc = 20; // Old: 1 +static int g_nErrorMax = 200; // Old: 50 int SoundCore_GetErrorInc() { - return g_nErrorInc; + return g_nErrorInc; } void SoundCore_SetErrorInc(const int nErrorInc) { - g_nErrorInc = nErrorInc < g_nErrorMax ? nErrorInc : g_nErrorMax; - if(g_fh) fprintf(g_fh, "Speaker/MB Error Inc = %d\n", g_nErrorInc); + g_nErrorInc = nErrorInc < g_nErrorMax ? nErrorInc : g_nErrorMax; + if(g_fh) fprintf(g_fh, "Speaker/MB Error Inc = %d\n", g_nErrorInc); } int SoundCore_GetErrorMax() { - return g_nErrorMax; + return g_nErrorMax; } void SoundCore_SetErrorMax(const int nErrorMax) { - g_nErrorMax = nErrorMax < MAX_SAMPLES ? nErrorMax : MAX_SAMPLES; - if(g_fh) fprintf(g_fh, "Speaker/MB Error Max = %d\n", g_nErrorMax); + g_nErrorMax = nErrorMax < MAX_SAMPLES ? nErrorMax : MAX_SAMPLES; + if(g_fh) fprintf(g_fh, "Speaker/MB Error Max = %d\n", g_nErrorMax); } -//============================================================================= - -#ifndef APPLE2IX -static DWORD g_dwAdviseToken; -static IReferenceClock *g_pRefClock = NULL; -static HANDLE g_hSemaphore = NULL; -static bool g_bRefClockTimerActive = false; -static DWORD g_dwLastUsecPeriod = 0; -#endif - - -bool SysClk_InitTimer() -{ -#ifdef APPLE2IX - // Not using timers ... - return false; -#else - g_hSemaphore = CreateSemaphore(NULL, 0, 1, NULL); // Max count = 1 - if (g_hSemaphore == NULL) - { - fprintf(stderr, "Error creating semaphore\n"); - return false; - } - - if (CoCreateInstance(CLSID_SystemClock, NULL, CLSCTX_INPROC, - IID_IReferenceClock, (LPVOID*)&g_pRefClock) != S_OK) - { - fprintf(stderr, "Error initialising COM\n"); - return false; // Fails for Win95! - } - - return true; -#endif -} - -void SysClk_UninitTimer() -{ -#ifdef APPLE2IX - // Not using timers ... -#else - SysClk_StopTimer(); - - SAFE_RELEASE(g_pRefClock); - - if (CloseHandle(g_hSemaphore) == 0) - fprintf(stderr, "Error closing semaphore handle\n"); -#endif -} - -// - -void SysClk_WaitTimer() -{ -#ifdef APPLE2IX - // Not using timers ... -#else - if(!g_bRefClockTimerActive) - return; - - WaitForSingleObject(g_hSemaphore, INFINITE); -#endif -} - -// - -void SysClk_StartTimerUsec(DWORD dwUsecPeriod) -{ -#ifdef APPLE2IX - // Not using timers ... -#else - if(g_bRefClockTimerActive && (g_dwLastUsecPeriod == dwUsecPeriod)) - return; - - SysClk_StopTimer(); - - REFERENCE_TIME rtPeriod = (REFERENCE_TIME) (dwUsecPeriod * 10); // In units of 100ns - REFERENCE_TIME rtNow; - - HRESULT hr = g_pRefClock->GetTime(&rtNow); - // S_FALSE : Returned time is the same as the previous value - - if ((hr != S_OK) && (hr != S_FALSE)) - { - _ASSERT(0); - return; - } - - if (g_pRefClock->AdvisePeriodic(rtNow, rtPeriod, g_hSemaphore, &g_dwAdviseToken) != S_OK) - { - fprintf(stderr, "Error creating timer\n"); - _ASSERT(0); - return; - } - - g_dwLastUsecPeriod = dwUsecPeriod; - g_bRefClockTimerActive = true; -#endif -} - -void SysClk_StopTimer() -{ -#ifdef APPLE2IX - // Not using timers ... -#else - if(!g_bRefClockTimerActive) - return; - - if (g_pRefClock->Unadvise(g_dwAdviseToken) != S_OK) - { - fprintf(stderr, "Error deleting timer\n"); - _ASSERT(0); - return; - } - - g_bRefClockTimerActive = false; -#endif -} diff --git a/src/audio/soundcore.h b/src/audio/soundcore.h index e03078b1..508a6ad0 100644 --- a/src/audio/soundcore.h +++ b/src/audio/soundcore.h @@ -21,41 +21,29 @@ #define MAX_SAMPLES (8*1024) -#if defined(APPLE2IX) -#define SAFE_RELEASE(p) FREE(p) -#else -#define SAFE_RELEASE(p) { if(p) { (p)->Release(); (p)=NULL; } } -#endif - -// Define max 1 of these: -//#define RIFF_SPKR -//#define RIFF_MB - -#ifdef APPLE2IX extern bool g_bDisableDirectSound; -#endif typedef struct { - LPDIRECTSOUNDBUFFER lpDSBvoice; + LPDIRECTSOUNDBUFFER lpDSBvoice; #ifdef APPLE2IX // apparently lpDSNotify isn't used... #define LPDIRECTSOUNDNOTIFY void* #endif - LPDIRECTSOUNDNOTIFY lpDSNotify; - bool bActive; // Playback is active - bool bMute; - LONG nVolume; // Current volume (as used by DirectSound) - LONG nFadeVolume; // Current fade volume (as used by DirectSound) - DWORD dwUserVolume; // Volume from slider on Property Sheet (0=Max) - bool bIsSpeaker; - bool bRecentlyActive; // (Speaker only) false after 0.2s of speaker inactivity + LPDIRECTSOUNDNOTIFY lpDSNotify; + bool bActive; // Playback is active + bool bMute; + LONG nVolume; // Current volume (as used by DirectSound) + LONG nFadeVolume; // Current fade volume (as used by DirectSound) + DWORD dwUserVolume; // Volume from slider on Property Sheet (0=Max) + bool bIsSpeaker; + bool bRecentlyActive; // (Speaker only) false after 0.2s of speaker inactivity } VOICE, *PVOICE; bool DSGetLock(LPDIRECTSOUNDBUFFER pVoice, DWORD dwOffset, DWORD dwBytes, - SHORT** ppDSLockedBuffer0, DWORD* pdwDSLockedBufferSize0, - SHORT** ppDSLockedBuffer1, DWORD* pdwDSLockedBufferSize1); + SHORT** ppDSLockedBuffer0, DWORD* pdwDSLockedBufferSize0, + SHORT** ppDSLockedBuffer1, DWORD* pdwDSLockedBufferSize1); HRESULT DSGetSoundBuffer(VOICE* pVoice, DWORD dwFlags, DWORD dwBufferSize, DWORD nSampleRate, int nChannels); void DSReleaseSoundBuffer(VOICE* pVoice); @@ -63,14 +51,8 @@ void DSReleaseSoundBuffer(VOICE* pVoice); bool DSZeroVoiceBuffer(PVOICE Voice, char* pszDevName, DWORD dwBufferSize); bool DSZeroVoiceWritableBuffer(PVOICE Voice, char* pszDevName, DWORD dwBufferSize); -#if defined(APPLE2IX) typedef enum eFADE {FADE_NONE, FADE_IN, FADE_OUT} eFADE; -#else -enum eFADE {FADE_NONE, FADE_IN, FADE_OUT}; -#endif void SoundCore_SetFade(eFADE FadeType); -bool SoundCore_GetTimerState(); -void SoundCore_TweakVolumes(); int SoundCore_GetErrorInc(); void SoundCore_SetErrorInc(const int nErrorInc); diff --git a/src/audio/speaker.c b/src/audio/speaker.c index 13cfdf7a..dcec9dc4 100644 --- a/src/audio/speaker.c +++ b/src/audio/speaker.c @@ -1,1347 +1,431 @@ /* -AppleWin : An Apple //e emulator for Windows - -Copyright (C) 1994-1996, Michael O'Brien -Copyright (C) 1999-2001, Oliver Schmidt -Copyright (C) 2002-2005, Tom Charlesworth -Copyright (C) 2006-2007, Tom Charlesworth, Michael Pohoreski - -AppleWin is free software; you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation; either version 2 of the License, or -(at your option) any later version. - -AppleWin is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with AppleWin; if not, write to the Free Software -Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -/* Description: Speaker emulation + * Apple // emulator for *nix * - * Author: Various - * Linux ALSA/OpenAL Port : Aaron Culliney + * This software package is subject to the GNU General Public License + * version 2 or later (your choice) as published by the Free Software + * Foundation. + * + * THERE ARE NO WARRANTIES WHATSOEVER. + * + */ + +/* Apple //e speaker support. Source inspired/derived from AppleWin. + * + * - ~46 //e cycles per PC sample (played back at 22.050kHz) -- (CLK_6502/SPKR_SAMPLE_RATE) + * + * The soundcard output drives how much 6502 emulation is done in real-time. If the soundcard buffer is running out of + * sample-data, then more 6502 cycles need to be executed to top-up the buffer, and vice-versa. + * + * This is in contrast to the AY8910 voices (mockingboard/phasor), which can simply generate more data if their buffers + * are running low. */ #include "common.h" -#ifdef APPLE2IX - -#include "audio/win-shim.h" -# ifdef __linux -# include -# endif - +#define DEBUG_SPEAKER (!defined(NDEBUG) && 0) // enable to print timing stats +#if DEBUG_SPEAKER +# define SPEAKER_LOG(...) LOG(__VA_ARGS__) #else -#include "StdAfx.h" -#endif -#include - -// Notes: -// -// [OLD: 23.191 Apple CLKs == 44100Hz (CLK_6502/44100)] -// 23 Apple CLKS per PC sample (played back at 44.1KHz) -// -// -// The speaker's wave output drives how much 6502 emulation is done in real-time, eg: -// If the speaker's wave buffer is running out of sample-data, then more 6502 cycles -// need to be executed to top-up the wave buffer. -// This is in contrast to the AY8910 voices, which can simply generate more data if -// their buffers are running low. -// - -#define SOUND_NONE 0 -#define SOUND_DIRECT 1 -#ifndef APPLE2IX -#define SOUND_SMART 2 -#endif -#define SOUND_WAVE 3 - -#ifdef APPLE2IX -#define g_nSPKR_NumChannels 1 -#else -static const unsigned short g_nSPKR_NumChannels = 1; -#endif -static const DWORD g_dwDSSpkrBufferSize = MAX_SAMPLES * sizeof(short) * g_nSPKR_NumChannels; - -//------------------------------------- - -static short* g_pSpeakerBuffer = NULL; - -// Globals (SOUND_WAVE) -#ifndef APPLE2IX -const short SPKR_DATA_INIT = (short)0x8000; +# define SPEAKER_LOG(...) #endif -static short g_nSpeakerData = SPKR_DATA_INIT; -static UINT g_nBufferIdx = 0; +#define MAX_REMAINDER_BUFFER (((CLK_6502_INT*(unsigned int)CPU_SCALE_FASTEST)/SPKR_SAMPLE_RATE)+1) -static short* g_pRemainderBuffer = NULL; -static UINT g_nRemainderBufferSize; // Setup in SpkrInitialize() -static UINT g_nRemainderBufferIdx; // Setup in SpkrInitialize() +#define SOUNDCORE_BUFFER_SIZE (MAX_SAMPLES*sizeof(int16_t)*1/*mono*/) +#define QUARTER_SIZE (SOUNDCORE_BUFFER_SIZE/4) +#define IDEAL_MIN (SOUNDCORE_BUFFER_SIZE/4) // minimum goldilocks zone for samples-in-play +#define IDEAL_MAX (SOUNDCORE_BUFFER_SIZE/2) // maximum goldilocks-zone for samples-in-play +static int16_t samples_buffer[SPKR_SAMPLE_RATE * sizeof(int16_t)] = { 0 }; // holds max 1 second of samples +static int16_t remainder_buffer[MAX_REMAINDER_BUFFER * sizeof(int16_t)] = { 0 }; // holds enough to create one sample (averaged) +static unsigned int samples_buffer_idx = 0; +static unsigned int remainder_buffer_size = 0; +static unsigned int remainder_buffer_idx = 0; -// Application-wide globals: -DWORD soundtype = SOUND_WAVE; -double g_fClksPerSpkrSample; // Setup in SetClksPerSpkrSample() +static int16_t speaker_amplitude = SPKR_DATA_INIT; +static int16_t speaker_data = 0; -// Globals -#ifndef APPLE2IX -static DWORD lastcyclenum = 0; -static DWORD toggles = 0; -#endif -static uint64_t g_nSpkrQuietCycleCount = 0; -static uint64_t g_nSpkrLastCycle = 0; -static bool g_bSpkrToggleFlag = false; -static VOICE SpeakerVoice = {0}; -static bool g_bSpkrAvailable = false; +static double cycles_per_sample = 0.0; +static unsigned long long cycles_last_update = 0; +static unsigned long long cycles_quiet_time = 0; +static bool speaker_accessed_since_last_flush = false; +static bool speaker_recently_active = false; -#ifndef APPLE2IX -// Globals (SOUND_DIRECT/SOUND_SMART) -static BOOL directio = 0; -static DWORD lastdelta[2] = {0,0}; -static DWORD quietcycles = 0; -static DWORD soundeffect = 0; -static DWORD totaldelta = 0; -#endif +static bool speaker_going_silent = false; +static unsigned int speaker_silent_step = 0; -//----------------------------------------------------------------------------- +static int samples_adjustment_counter = 0; -// Forward refs: -#ifdef APPLE2IX -static ULONG Spkr_SubmitWaveBuffer_FullSpeed(short* pSpeakerBuffer, ULONG nNumSamples); -static ULONG Spkr_SubmitWaveBuffer(short* pSpeakerBuffer, ULONG nNumSamples); -#else -ULONG Spkr_SubmitWaveBuffer_FullSpeed(short* pSpeakerBuffer, ULONG nNumSamples); -ULONG Spkr_SubmitWaveBuffer(short* pSpeakerBuffer, ULONG nNumSamples); -#endif -static void Spkr_SetActive(bool bActive); +static VOICE SpeakerVoice = { 0 }; -//============================================================================= +// -------------------------------------------------------------------------------------------------------------------- -#ifndef APPLE2IX -static void DisplayBenchmarkResults () -{ - DWORD totaltime = GetTickCount()-extbench; - VideoRedrawScreen(); - TCHAR buffer[64]; - wsprintf(buffer, - TEXT("This benchmark took %u.%02u seconds."), - (unsigned)(totaltime / 1000), - (unsigned)((totaltime / 10) % 100)); - MessageBox(g_hFrameWindow, - buffer, - TEXT("Benchmark Results"), - MB_ICONINFORMATION | MB_SETFOREGROUND); -} -#endif +/* + * Because disk image loading is slow (AKA close-to-original-//e-speed), we may auto-switch to "fullspeed" for faster + * loading when all the following heuristics hold true: + * - Disk motor is on + * - Speaker has not been toggled in some time (is not "active") + * - The graphics state is not "dirty" + * + * In fullspeed mode we output only quiet samples (zero-amplitude) at such a rate as to prevent the streaming audio from + * either underflowing or overflowing. + * + * We will also auto-switch back to the last configured "scaled" speed when the speaker is toggled. + */ +static void _speaker_init_timing(void) { + // 46.28 //e cycles for 22.05kHz sample rate -//============================================================================= + // AppleWin NOTE : use integer value: Better for MJ Mahon's RT.SYNTH.DSK (integer multiples of 1.023MHz Clk) + cycles_per_sample = (unsigned int)(cycles_persec_target / (double)SPKR_SAMPLE_RATE); -static void InternalBeep (DWORD frequency, DWORD duration) -{ -#ifdef APPLE2IX -# if defined(__i386__) - if (duration) - { - // HACK FIXME TODO : this needs to be properly tested since I don't have a PC with a speaker anymore... - - // 9/8/2013 -- from http://www.johnath.com/beep/beep.c - /* I don't know where this number comes from, I admit that freely. A - * wonderful human named Raine M. Ekman used it in a program that played - * a tune at the console, and apparently, it's how the kernel likes its - * sound requests to be phrased. If you see Raine, thank him for me. - * - * June 28, email from Peter Tirsek (peter at tirsek dot com): - * - * This number represents the fixed frequency of the original PC XT's - * timer chip (the 8254 AFAIR), which is approximately 1.193 MHz. This - * number is divided with the desired frequency to obtain a counter value, - * that is subsequently fed into the timer chip, tied to the PC speaker. - * The chip decreases this counter at every tick (1.193 MHz) and when it - * reaches zero, it toggles the state of the speaker (on/off, or in/out), - * resets the counter to the original value, and starts over. The end - * result of this is a tone at approximately the desired frequency. :) - */ - frequency = 1193180/frequency; - - // http://ibiblio.org/gferg/ldp/GCC-Inline-Assembly-HOWTO.html - asm ( - "pushl %%eax;" - "movb $0x0B6, %%al;" - "outb %%al, $0x43;" - "movl %0, %%eax;" - "outb %%al, $0x42;" - "movb %%ah, %%al;" - "outb %%al, $0x42;" - "inb $0x61, %%al;" - "orb $0x3, %%al;" - "outb %%al, $0x61;" - "popl %%eax;" - : : "r" (frequency) /*%0*/ : ); + unsigned int last_remainder_buffer_size = remainder_buffer_size; + remainder_buffer_size = (unsigned int)cycles_per_sample; + if ((double)remainder_buffer_size != cycles_per_sample) { + ++remainder_buffer_size; } - else - { - asm ( - "pushl %eax;" - "inb $0x61, %al;" - "andb $0x0FC, %al;" - "outb %al, $0x61;" - "popl %eax;" - ); + assert(remainder_buffer_size <= MAX_REMAINDER_BUFFER); + + if (last_remainder_buffer_size == remainder_buffer_size) { + // no change ... insure seamless remainder_buffer + } else { + SPEAKER_LOG("changing remainder buffer size"); + remainder_buffer_idx = 0; } -# elif defined(__x86_64__) - // X64 Direct Access TODO ... -# endif -#else -#ifdef _X86_ - if (directio) - if (duration) { - frequency = 1193180/frequency; - __asm { - push eax - mov al,0B6h - out 43h,al - mov eax,frequency - out 42h,al - mov al,ah - out 42h,al - in al,61h - or al,3 - out 61h,al - pop eax - } + if (cycles_last_update > cycles_count_total) { + SPEAKER_LOG("resetting speaker cycles counter"); + cycles_last_update = 0; } - else - __asm { - push eax - in al,61h - and al,0FCh - out 61h,al - pop eax - } - else -#endif - Beep(frequency,duration); -#endif -} -//============================================================================= - -static void InternalClick () -{ -#ifdef APPLE2IX - // TODO : I don't have a PC with a physical speaker anymore... can I - // test this with a virtual speaker Linux driver? -# if defined(__i386__) - asm ( - "pushl %eax;" - "inb $0x61, %al;" - "xorb $0x2, %al;" - "outb %al, $0x61;" - "popl %eax;" - ); -# elif defined(__x86_64__) - // X64 Direct Access TODO ... -# endif -#else -#ifdef _X86_ - if (directio) - __asm { - push eax - in al,0x61 - xor al,2 - out 0x61,al - pop eax + if (is_fullspeed) { + remainder_buffer_idx = 0; + samples_buffer_idx = 0; } - else { -#endif - Beep(37,(DWORD)-1); - Beep(0,0); -#ifdef _X86_ - } -#endif -#endif } -//============================================================================= +/* + * Adds to the samples_buffer the number of samples since the last invocation of this function. + * + * Speaker output square wave example: + * _______ ____ _____________________! . +speaker_amplitude + * silence _ . + * threshold _ . + * _ remainder _ . + * average _ . + * _______ ____________ ______ ___ . 0 + * + * - When the speaker is accessed by the emulated program, the output (speaker_data) is toggled between the + * positive amplitude or zero + * - Evenly-divisible samples since last function call (cycles_diff/cycles_per_sample) are put directly into the + * samples_buffer for output to audio system backend + * - (+) Fractional samples are put into a remainder_buffer to be averaged and then transfered to the sample_buffer + * when there is enough data for 1 whole sample (possibly on a subsequent invocation of this function) + * - (+) If the speaker has not been toggled with output at +amplitude for a certain number of machine cycles, we + * gradually step the samples down to the zero bound of true quiet. (This is done to avoid glitching when + * pausing/unpausing emulation for GUI/menus and auto-switching between full and configured speeds) + */ +static void _speaker_update(/*bool toggled*/) { -static void SetClksPerSpkrSample() -{ -// // 23.191 clks for 44.1Khz (when 6502 CLK=1.0Mhz) -// g_fClksPerSpkrSample = g_fCurrentCLK6502 / (double)SPKR_SAMPLE_RATE; + if (!is_fullspeed) { - // Use integer value: Better for MJ Mahon's RT.SYNTH.DSK (integer multiples of 1.023MHz Clk) - // . 23 clks @ 1.023MHz - g_fClksPerSpkrSample = (double) (UINT) (g_fCurrentCLK6502 / (double)SPKR_SAMPLE_RATE); -} + unsigned long long cycles_diff = cycles_count_total - cycles_last_update; -//============================================================================= - -static void InitRemainderBuffer() -{ -#ifdef APPLE2IX - free(g_pRemainderBuffer); -#else - delete [] g_pRemainderBuffer; -#endif - - SetClksPerSpkrSample(); - - g_nRemainderBufferSize = (UINT) g_fClksPerSpkrSample; - if ((double)g_nRemainderBufferSize != g_fClksPerSpkrSample) - g_nRemainderBufferSize++; - -#ifdef APPLE2IX - g_pRemainderBuffer = calloc(g_nRemainderBufferSize, sizeof(short)); -#else - g_pRemainderBuffer = new short [g_nRemainderBufferSize]; - memset(g_pRemainderBuffer, 0, g_nRemainderBufferSize); -#endif - - g_nRemainderBufferIdx = 0; -} - -// -// ----- ALL GLOBALLY ACCESSIBLE FUNCTIONS ARE BELOW THIS LINE ----- -// - -//============================================================================= - -void SpkrDestroy () -{ - Spkr_DSUninit(); - - // - - if(soundtype == SOUND_WAVE) - { -#ifdef APPLE2IX - free(g_pSpeakerBuffer); - free(g_pRemainderBuffer); -#else - delete [] g_pSpeakerBuffer; - delete [] g_pRemainderBuffer; -#endif - - g_pSpeakerBuffer = NULL; - g_pRemainderBuffer = NULL; - } - else - { - InternalBeep(0,0); - } -} - -//============================================================================= - -void SpkrInitialize () -{ -#ifdef APPLE2IX - if (soundtype == SOUND_DIRECT) - { -# if defined(__i386__) - if (ioperm(0x42, 1, 1) || ioperm(0x61, 1, 1)) - { - ERRLOG("Cannot get direct port access to PC speaker, attempting to use sound card..."); - soundtype = SOUND_WAVE; - } - else - { - LOG("Audio is direct access to PC speaker..."); - } -# elif defined(__x86_64__) - // X64 Direct Access TODO ... - soundtype = SOUND_WAVE; -# else - soundtype = SOUND_WAVE; -# endif - } -#else - if(g_fh) - { - fprintf(g_fh, "Spkr Config: soundtype = %d ",soundtype); - switch(soundtype) - { - case SOUND_NONE: fprintf(g_fh, "(NONE)\n"); break; - case SOUND_DIRECT: fprintf(g_fh, "(DIRECT)\n"); break; - case SOUND_SMART: fprintf(g_fh, "(SMART)\n"); break; - case SOUND_WAVE: fprintf(g_fh, "(WAVE)\n"); break; - default: fprintf(g_fh, "(UNDEFINED!)\n"); break; - } - } -#endif - - if(g_bDisableDirectSound) - { - SpeakerVoice.bMute = true; - } - else - { - g_bSpkrAvailable = Spkr_DSInit(); - } - - // - - if (soundtype == SOUND_WAVE) - { - InitRemainderBuffer(); - -#ifdef APPLE2IX - g_pSpeakerBuffer = calloc(SPKR_SAMPLE_RATE, sizeof(short)); // Buffer can hold a max of 1 seconds worth of samples -#else - g_pSpeakerBuffer = new short [SPKR_SAMPLE_RATE]; // Buffer can hold a max of 1 seconds worth of samples -#endif - } - - // - -#ifdef APPLE2IX -// initialized direct sound above -#else - // IF NONE IS, THEN DETERMINE WHETHER WE HAVE DIRECT ACCESS TO THE PC SPEAKER PORT - if (soundtype != SOUND_WAVE) // *** TO DO: Need way of determining if DirectX init failed *** - { - if (soundtype == SOUND_WAVE) - soundtype = SOUND_SMART; -#ifdef _X86_ - _try - { - __asm { - in al,0x61 - xor al,2 - out 0x61,al - xor al,2 - out 0x61,al - } - directio = 1; - } - _except (EXCEPTION_EXECUTE_HANDLER) - { - directio = 0; - } -#else - directio = 0; -#endif - if ((!directio) && (soundtype == SOUND_DIRECT)) - soundtype = SOUND_SMART; - } - -#endif -} - -//============================================================================= - -// NB. Called when /g_fCurrentCLK6502/ changes -void SpkrReinitialize () -{ - if (soundtype == SOUND_WAVE) - { - InitRemainderBuffer(); - } -} - -//============================================================================= - -void SpkrReset() -{ - g_nBufferIdx = 0; - g_nSpkrQuietCycleCount = 0; - g_bSpkrToggleFlag = false; - - InitRemainderBuffer(); - Spkr_SubmitWaveBuffer(NULL, 0); - Spkr_SetActive(false); - Spkr_Demute(); -} - -//============================================================================= - -BOOL SpkrSetEmulationType (HWND window, DWORD newtype) -{ - if (soundtype != SOUND_NONE) - SpkrDestroy(); - soundtype = newtype; - if (soundtype != SOUND_NONE) - SpkrInitialize(); - if (soundtype != newtype) - switch (newtype) { - - case SOUND_DIRECT: - MessageBox(window, - TEXT("Direct emulation is not available because the ") - TEXT("operating system you are using does not allow ") - TEXT("direct control of the speaker."), - TEXT("Configuration"), - MB_ICONEXCLAMATION | MB_SETFOREGROUND); - return 0; - - case SOUND_WAVE: - MessageBox(window, - TEXT("The emulator is unable to initialize a waveform ") - TEXT("output device. Make sure you have a sound card ") - TEXT("and a driver installed and that windows is ") - TEXT("correctly configured to use the driver. Also ") - TEXT("ensure that no other program is currently using ") - TEXT("the device."), - TEXT("Configuration"), - MB_ICONEXCLAMATION | MB_SETFOREGROUND); - return 0; - - } - return 1; -} - -//============================================================================= - -static void ReinitRemainderBuffer(UINT nCyclesRemaining) -{ - if(nCyclesRemaining == 0) - return; - - for(g_nRemainderBufferIdx=0; (g_nRemainderBufferIdx 2) || (soundtype == SOUND_DIRECT)) - { - if (directio) - { - __asm - { - push eax - in al,0x61 - xor al,2 - out 0x61,al - pop eax + if (samples_buffer_idx < SPKR_SAMPLE_RATE) { + samples_buffer[samples_buffer_idx++] = (int16_t)sample_mean; + } + } } - } - else - { - Beep(37,(DWORD)-1); - Beep(0,0); - } - } - // SAVE INFORMATION ABOUT THE FREQUENCY OF SPEAKER TOGGLING FOR POSSIBLE - // LATER USE BY SOUND AVERAGING - if (lastcyclenum) - { - toggles++; - DWORD delta = cyclenum-lastcyclenum; + const unsigned long long samples_count = (unsigned long long)((double)cycles_diff / cycles_per_sample); + unsigned long long num_samples = samples_count; + const unsigned long long cycles_remainder = (unsigned long long)((double)cycles_diff - (double)num_samples * cycles_per_sample); - // DETERMINE WHETHER WE ARE PLAYING A SOUND EFFECT - if (directio && - ((delta < 250) || - (lastdelta[0] && lastdelta[1] && - (delta-lastdelta[0] > 250) && (lastdelta[0]-delta > 250) && - (delta-lastdelta[1] > 250) && (lastdelta[1]-delta > 250)))) - soundeffect = MIN(35,soundeffect+2); + // populate samples_buffer with whole samples + while (num_samples && (samples_buffer_idx < SPKR_SAMPLE_RATE)) { + samples_buffer[samples_buffer_idx++] = speaker_data; + if (speaker_going_silent && speaker_data) { + speaker_data -= speaker_silent_step; + } + --num_samples; + } - lastdelta[1] = lastdelta[0]; - lastdelta[0] = delta; - totaldelta += delta; - } - lastcyclenum = cyclenum; - -#endif - } - -#ifndef APPLE2IX - return MemReadFloatingBus(nCyclesLeft); -#endif -} - -//============================================================================= - -// Called by ContinueExecution() -void SpkrUpdate(DWORD totalcycles) -{ - if(!g_bSpkrToggleFlag) - { - if(!g_nSpkrQuietCycleCount) - { - g_nSpkrQuietCycleCount = g_nCumulativeCycles; - } - else if(g_nCumulativeCycles - g_nSpkrQuietCycleCount > (unsigned __int64)g_fCurrentCLK6502/5) - { - // After 0.2 sec of Apple time, deactivate spkr voice - // . This allows emulator to auto-switch to full-speed g_nAppMode for fast disk access - Spkr_SetActive(false); - } - } - else - { - g_nSpkrQuietCycleCount = 0; - g_bSpkrToggleFlag = false; - } - - // - - if (soundtype == SOUND_WAVE) - { - UpdateSpkr(); - ULONG nSamplesUsed; - - if(g_bFullSpeed) - nSamplesUsed = Spkr_SubmitWaveBuffer_FullSpeed(g_pSpeakerBuffer, g_nBufferIdx); - else - nSamplesUsed = Spkr_SubmitWaveBuffer(g_pSpeakerBuffer, g_nBufferIdx); - - _ASSERT(nSamplesUsed <= g_nBufferIdx); - memmove(g_pSpeakerBuffer, &g_pSpeakerBuffer[nSamplesUsed], g_nBufferIdx-nSamplesUsed); // FIXME-TC: _Size * 2 - g_nBufferIdx -= nSamplesUsed; - } -#ifndef APPLE2IX - else - { - - // IF WE ARE NOT PLAYING A SOUND EFFECT, PERFORM FREQUENCY AVERAGING - static DWORD currenthertz = 0; - static BOOL lastfull = 0; - static DWORD lasttoggles = 0; - static DWORD lastval = 0; - if ((soundeffect > 2) || (soundtype == SOUND_DIRECT)) { - lastval = 0; - if (currenthertz && (soundeffect > 4)) { - InternalBeep(0,0); - currenthertz = 0; - } - } - else if (toggles && totaldelta) { - DWORD newval = 1000000*toggles/totaldelta; - if (lastval && lastfull && - (newval-currenthertz > 50) && - (currenthertz-newval > 50)) { - InternalBeep(newval,(DWORD)-1); - currenthertz = newval; - lasttoggles = 0; - } - lastfull = (totaldelta+((totaldelta/toggles) << 1) >= totalcycles); - lasttoggles += toggles; - lastval = newval; - } - else if (currenthertz) { - InternalBeep(0,0); - currenthertz = 0; - lastfull = 0; - lasttoggles = 0; - lastval = 0; - } - else if (lastval) { - currenthertz = (lasttoggles > 4) ? lastval : 0; - if (currenthertz) - InternalBeep(lastval,(DWORD)-1); - else - InternalClick(); - lastfull = 0; - lasttoggles = 0; - lastval = 0; - } - - // RESET THE FREQUENCY GATHERING VARIABLES - lastcyclenum = 0; - lastdelta[0] = 0; - lastdelta[1] = 0; - quietcycles = toggles ? 0 : (quietcycles+totalcycles); - toggles = 0; - totaldelta = 0; - if (soundeffect) - soundeffect--; - - } -#endif -} - -#ifndef APPLE2IX -// Called from SoundCore_TimerFunc() for FADE_OUT -void SpkrUpdate_Timer() -{ - if (soundtype == SOUND_WAVE) - { - UpdateSpkr(); - ULONG nSamplesUsed; - - nSamplesUsed = Spkr_SubmitWaveBuffer_FullSpeed(g_pSpeakerBuffer, g_nBufferIdx); - - _ASSERT(nSamplesUsed <= g_nBufferIdx); - memmove(g_pSpeakerBuffer, &g_pSpeakerBuffer[nSamplesUsed], g_nBufferIdx-nSamplesUsed); // FIXME-TC: _Size * 2 - g_nBufferIdx -= nSamplesUsed; - } -} - -//============================================================================= - -static DWORD dwByteOffset = (DWORD)-1; -#endif -static int nNumSamplesError = 0; -static int nDbgSpkrCnt = 0; - -// FullSpeed g_nAppMode, 2 cases: -// i) Short burst of full-speed, so PlayCursor doesn't complete sound from previous fixed-speed session. -// ii) Long burst of full-speed, so PlayCursor completes sound from previous fixed-speed session. - -// Try to: -// 1) Output remaining samples from SpeakerBuffer (from previous fixed-speed session) -// 2) Output pad samples to keep the VoiceBuffer topped-up - -// If nNumSamples>0 then these are from previous fixed-speed session. -// - Output these before outputting zero-pad samples. - -static ULONG Spkr_SubmitWaveBuffer_FullSpeed(short* pSpeakerBuffer, ULONG nNumSamples) -{ - //char szDbg[200]; - nDbgSpkrCnt++; - - if(!SpeakerVoice.bActive) - return nNumSamples; - - // pSpeakerBuffer can't be NULL, as reset clears g_bFullSpeed, so 1st SpkrUpdate() never calls here - _ASSERT(pSpeakerBuffer != NULL); - - // - - DWORD dwDSLockedBufferSize0, dwDSLockedBufferSize1; - SHORT *pDSLockedBuffer0, *pDSLockedBuffer1; - //bool bBufferError = false; - - DWORD dwCurrentPlayCursor, dwCurrentWriteCursor; -#ifdef APPLE2IX - HRESULT hr = SpeakerVoice.lpDSBvoice->GetCurrentPosition(SpeakerVoice.lpDSBvoice->_this, &dwCurrentPlayCursor, &dwCurrentWriteCursor); -#else - HRESULT hr = SpeakerVoice.lpDSBvoice->GetCurrentPosition(&dwCurrentPlayCursor, &dwCurrentWriteCursor); -#endif - if(FAILED(hr)) - return nNumSamples; + if (cycles_remainder > 0) { + // populate remainder_buffer with fractional samples + assert(remainder_buffer_idx == 0 && "should have already dealt with remainder buffer"); + assert(cycles_remainder < remainder_buffer_size && "otherwise there should have been another whole sample"); + while (remainder_buffer_idx dwCurrentPlayCursor) - { - // |-----PxxxxxW-----| - if((dwByteOffset > dwCurrentPlayCursor) && (dwByteOffset < dwCurrentWriteCursor)) - { - //sprintf(szDbg, "[Submit_FS] PC=%08X, WC=%08X, Diff=%08X, Off=%08X, NS=%08X xxx\n", dwCurrentPlayCursor, dwCurrentWriteCursor, dwCurrentWriteCursor-dwCurrentPlayCursor, dwByteOffset, nNumSamples); OutputDebugString(szDbg); - dwByteOffset = dwCurrentWriteCursor; - } - } - else - { - // |xxW----------Pxxx| - if((dwByteOffset > dwCurrentPlayCursor) || (dwByteOffset < dwCurrentWriteCursor)) - { - //sprintf(szDbg, "[Submit_FS] PC=%08X, WC=%08X, Diff=%08X, Off=%08X, NS=%08X XXX\n", dwCurrentPlayCursor, dwCurrentWriteCursor, dwCurrentWriteCursor-dwCurrentPlayCursor, dwByteOffset, nNumSamples); OutputDebugString(szDbg); - dwByteOffset = dwCurrentWriteCursor; - } - } - } - - // Calc bytes remaining to be played - int nBytesRemaining = dwByteOffset - dwCurrentPlayCursor; -#else - int nBytesRemaining = (int)dwCurrentPlayCursor; + } else if (toggled && samples_count) { + samples_buffer[samples_buffer_idx-1] = 0; #endif - if(nBytesRemaining < 0) - nBytesRemaining += g_dwDSSpkrBufferSize; - if((nBytesRemaining == 0) && (dwCurrentPlayCursor != dwCurrentWriteCursor)) - nBytesRemaining = g_dwDSSpkrBufferSize; // Case when complete buffer is to be played - - // - - UINT nNumPadSamples = 0; - - if(nBytesRemaining < g_dwDSSpkrBufferSize / 4) - { - // < 1/4 of play-buffer remaining (need *more* data) - nNumPadSamples = ((g_dwDSSpkrBufferSize / 4) - nBytesRemaining) / sizeof(short); - - if(nNumPadSamples > nNumSamples) - nNumPadSamples -= nNumSamples; - else - nNumPadSamples = 0; - - // NB. If nNumPadSamples>0 then all nNumSamples are to be used - } - - // - - UINT nBytesFree = g_dwDSSpkrBufferSize - nBytesRemaining; // Calc free buffer space - ULONG nNumSamplesToUse = nNumSamples + nNumPadSamples; - - if(nNumSamplesToUse * sizeof(short) > nBytesFree) - nNumSamplesToUse = nBytesFree / sizeof(short); - - // - - if(nNumSamplesToUse >= 128) // Limit the buffer unlock/locking to a minimum - { - if(!DSGetLock(SpeakerVoice.lpDSBvoice, -#ifdef APPLE2IX - /*unused*/ 0, (DWORD)nNumSamplesToUse*sizeof(short), -#else - dwByteOffset, (DWORD)nNumSamplesToUse*sizeof(short), -#endif - &pDSLockedBuffer0, &dwDSLockedBufferSize0, - &pDSLockedBuffer1, &dwDSLockedBufferSize1)) - return nNumSamples; - - // - - DWORD dwBufferSize0 = 0; - DWORD dwBufferSize1 = 0; - - if(nNumSamples) - { - //sprintf(szDbg, "[Submit_FS] C=%08X, PC=%08X, WC=%08X, Diff=%08X, Off=%08X, NS=%08X ***\n", nDbgSpkrCnt, dwCurrentPlayCursor, dwCurrentWriteCursor, dwCurrentWriteCursor-dwCurrentPlayCursor, dwByteOffset, nNumSamples); OutputDebugString(szDbg); - - if(nNumSamples*sizeof(short) <= dwDSLockedBufferSize0) - { - dwBufferSize0 = nNumSamples*sizeof(short); - dwBufferSize1 = 0; - } - else - { - dwBufferSize0 = dwDSLockedBufferSize0; - dwBufferSize1 = nNumSamples*sizeof(short) - dwDSLockedBufferSize0; - - if(dwBufferSize1 > dwDSLockedBufferSize1) - dwBufferSize1 = dwDSLockedBufferSize1; - } - - memcpy(pDSLockedBuffer0, &pSpeakerBuffer[0], dwBufferSize0); -#ifdef RIFF_SPKR - RiffPutSamples(pDSLockedBuffer0, dwBufferSize0/sizeof(short)); -#endif - nNumSamples = (ULONG)(dwBufferSize0/sizeof(short)); - - if(pDSLockedBuffer1 && dwBufferSize1) - { - memcpy(pDSLockedBuffer1, &pSpeakerBuffer[dwDSLockedBufferSize0/sizeof(short)], dwBufferSize1); -#ifdef RIFF_SPKR - RiffPutSamples(pDSLockedBuffer1, dwBufferSize1/sizeof(short)); -#endif - nNumSamples += dwBufferSize1/sizeof(short); - } - } - - if(nNumPadSamples) - { - //sprintf(szDbg, "[Submit_FS] C=%08X, PC=%08X, WC=%08X, Diff=%08X, Off=%08X, PS=%08X, Data=%04X\n", nDbgSpkrCnt, dwCurrentPlayCursor, dwCurrentWriteCursor, dwCurrentWriteCursor-dwCurrentPlayCursor, dwByteOffset, nNumPadSamples, g_nSpeakerData); OutputDebugString(szDbg); - - dwBufferSize0 = dwDSLockedBufferSize0 - dwBufferSize0; - dwBufferSize1 = dwDSLockedBufferSize1 - dwBufferSize1; - - if(dwBufferSize0) - { - wmemset((wchar_t*)pDSLockedBuffer0, (wchar_t)g_nSpeakerData, dwBufferSize0/sizeof(wchar_t)); -#ifdef RIFF_SPKR - RiffPutSamples(pDSLockedBuffer0, dwBufferSize0/sizeof(short)); -#endif - } - - if(pDSLockedBuffer1) - { - wmemset((wchar_t*)pDSLockedBuffer1, (wchar_t)g_nSpeakerData, dwBufferSize1/sizeof(wchar_t)); -#ifdef RIFF_SPKR - RiffPutSamples(pDSLockedBuffer1, dwBufferSize1/sizeof(short)); -#endif - } - } - - // Commit sound buffer -#ifdef APPLE2IX - hr = SpeakerVoice.lpDSBvoice->Unlock(SpeakerVoice.lpDSBvoice->_this, (void*)pDSLockedBuffer0, dwDSLockedBufferSize0, -#else - hr = SpeakerVoice.lpDSBvoice->Unlock((void*)pDSLockedBuffer0, dwDSLockedBufferSize0, -#endif - (void*)pDSLockedBuffer1, dwDSLockedBufferSize1); - if(FAILED(hr)) - return nNumSamples; - -#ifndef APPLE2IX - dwByteOffset = (dwByteOffset + (DWORD)nNumSamplesToUse*sizeof(short)*g_nSPKR_NumChannels) % g_dwDSSpkrBufferSize; -#endif - } - - return nNumSamples; -} - -//----------------------------------------------------------------------------- - -static ULONG Spkr_SubmitWaveBuffer(short* pSpeakerBuffer, ULONG nNumSamples) -{ -#ifndef APPLE2IX - char szDbg[200]; -#endif - nDbgSpkrCnt++; - - if(!SpeakerVoice.bActive) - return nNumSamples; - - if(pSpeakerBuffer == NULL) - { - // Re-init from SpkrReset() -#ifndef APPLE2IX - // HACK FIXME TODO : believe this whole if statement is unnecessary? - dwByteOffset = (DWORD)-1; -#endif - nNumSamplesError = 0; - - // Don't call DSZeroVoiceBuffer() - get noise with "VIA AC'97 Enhanced Audio Controller" - // . I guess SpeakerVoice.Stop() isn't really working and the new zero buffer causes noise corruption when submitted. -#ifndef APPLE2IX - DSZeroVoiceWritableBuffer(&SpeakerVoice, "Spkr", g_dwDSSpkrBufferSize); -#endif - - return 0; - } - - // - - DWORD dwDSLockedBufferSize0, dwDSLockedBufferSize1; - SHORT *pDSLockedBuffer0, *pDSLockedBuffer1; - bool bBufferError = false; - - DWORD dwCurrentPlayCursor, dwCurrentWriteCursor; -#ifdef APPLE2IX - HRESULT hr = SpeakerVoice.lpDSBvoice->GetCurrentPosition(SpeakerVoice.lpDSBvoice->_this, &dwCurrentPlayCursor, &dwCurrentWriteCursor); -#else - HRESULT hr = SpeakerVoice.lpDSBvoice->GetCurrentPosition(&dwCurrentPlayCursor, &dwCurrentWriteCursor); -#endif - if(FAILED(hr)) - return nNumSamples; - -#if 0 - if(dwByteOffset == (DWORD)-1) - { - // First time in this func (probably after re-init (above)) - - dwByteOffset = dwCurrentPlayCursor + (g_dwDSSpkrBufferSize/8)*3; // Ideal: 0.375 is between 0.25 & 0.50 full - dwByteOffset %= g_dwDSSpkrBufferSize; - //sprintf(szDbg, "[Submit] PC=%08X, WC=%08X, Diff=%08X, Off=%08X [REINIT]\n", dwCurrentPlayCursor, dwCurrentWriteCursor, dwCurrentWriteCursor-dwCurrentPlayCursor, dwByteOffset); OutputDebugString(szDbg); - } - else - { - // Check that our offset isn't between Play & Write positions - - if(dwCurrentWriteCursor > dwCurrentPlayCursor) - { - // |-----PxxxxxW-----| - if((dwByteOffset > dwCurrentPlayCursor) && (dwByteOffset < dwCurrentWriteCursor)) - { - double fTicksSecs = (double)GetTickCount() / 1000.0; -#ifdef APPLE2IX - LOG("%010.3f: [Submit] PC=%08X, WC=%08X, Diff=%08X, Off=%08X, NS=%08X xxx\n", fTicksSecs, dwCurrentPlayCursor, dwCurrentWriteCursor, dwCurrentWriteCursor-dwCurrentPlayCursor, dwByteOffset, nNumSamples); -#else - sprintf(szDbg, "%010.3f: [Submit] 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, szDbg); -#endif - - dwByteOffset = dwCurrentWriteCursor; - nNumSamplesError = 0; - bBufferError = true; - } - } - else - { - // |xxW----------Pxxx| - if((dwByteOffset > dwCurrentPlayCursor) || (dwByteOffset < dwCurrentWriteCursor)) - { - double fTicksSecs = (double)GetTickCount() / 1000.0; -#ifdef APPLE2IX - LOG("%010.3f: [Submit] PC=%08X, WC=%08X, Diff=%08X, Off=%08X, NS=%08X XXX\n", fTicksSecs, dwCurrentPlayCursor, dwCurrentWriteCursor, dwCurrentWriteCursor-dwCurrentPlayCursor, dwByteOffset, nNumSamples); -#else - sprintf(szDbg, "%010.3f: [Submit] 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, szDbg); -#endif - - dwByteOffset = dwCurrentWriteCursor; - nNumSamplesError = 0; - bBufferError = true; - } - } - } - - // Calc bytes remaining to be played - int nBytesRemaining = dwByteOffset - dwCurrentPlayCursor; -#else - int nBytesRemaining = (int)dwCurrentPlayCursor; -#endif - if(nBytesRemaining < 0) - nBytesRemaining += g_dwDSSpkrBufferSize; - if((nBytesRemaining == 0) && (dwCurrentPlayCursor != dwCurrentWriteCursor)) - nBytesRemaining = g_dwDSSpkrBufferSize; // Case when complete buffer is to be played - - // Calc correction factor so that play-buffer doesn't under/overflow - const int nErrorInc = SoundCore_GetErrorInc(); - if(nBytesRemaining < g_dwDSSpkrBufferSize / 4) - { -#ifdef APPLE2IX - //LOG("execute more cycles due to potential underflow..."); -#endif - nNumSamplesError += nErrorInc; // < 1/4 of play-buffer remaining (need *more* data) - } - else if(nBytesRemaining > g_dwDSSpkrBufferSize / 2) - { -#ifdef APPLE2IX - //LOG("execute less cycles due to potential overflow..."); -#endif - nNumSamplesError -= nErrorInc; // > 1/2 of play-buffer remaining (need *less* data) - } - else - { - nNumSamplesError = 0; // Acceptable amount of data in buffer } - const int nErrorMax = SoundCore_GetErrorMax(); // Cap feedback to +/-nMaxError units - if(nNumSamplesError < -nErrorMax) nNumSamplesError = -nErrorMax; - if(nNumSamplesError > nErrorMax) nNumSamplesError = nErrorMax; - g_nCpuCyclesFeedback = (int) ((double)nNumSamplesError * g_fClksPerSpkrSample); + if (UNLIKELY(samples_buffer_idx >= SPKR_SAMPLE_RATE)) { + assert(samples_buffer_idx == SPKR_SAMPLE_RATE && "should be at exactly the end, no further"); + ERRLOG("OOPS, overflow in speaker samples buffer"); + } + } - // + cycles_last_update = cycles_count_total; +} - UINT nBytesFree = g_dwDSSpkrBufferSize - nBytesRemaining; // Calc free buffer space - ULONG nNumSamplesToUse = nNumSamples; +/* + * Submits "quiet" samples to the audio system backend when CPU thread is running fullspeed, to keep the audio streaming + * topped up. + * + * 20150131 NOTE : it seems that there are still cases where we glitch OpenAL (seemingly on transitioning back to + * regular speed sample submissions). I have not been able to fully isolate the cause, but this has been minimized by + * always trending the samples to the zero value when there has been a sufficient amount of speaker silence. + */ +static void _submit_samples_buffer_fullspeed(void) { + samples_adjustment_counter = 0; - if(nNumSamplesToUse * sizeof(short) > nBytesFree) - nNumSamplesToUse = nBytesFree / sizeof(short); + if (!SpeakerVoice.bActive) { + return; + } - if(bBufferError) - pSpeakerBuffer = &pSpeakerBuffer[nNumSamples - nNumSamplesToUse]; + unsigned long bytes_queued = 0; + long err = SpeakerVoice.lpDSBvoice->GetCurrentPosition(SpeakerVoice.lpDSBvoice->_this, &bytes_queued, NULL); + if (err) { + return; + } + assert(bytes_queued <= SOUNDCORE_BUFFER_SIZE); - // + if (bytes_queued >= IDEAL_MAX) { + return; + } - if(nNumSamplesToUse) - { - //sprintf(szDbg, "[Submit] C=%08X, PC=%08X, WC=%08X, Diff=%08X, Off=%08X, NS=%08X +++\n", nDbgSpkrCnt, dwCurrentPlayCursor, dwCurrentWriteCursor, dwCurrentWriteCursor-dwCurrentPlayCursor, dwByteOffset, nNumSamplesToUse); OutputDebugString(szDbg); + unsigned int num_samples_pad = (IDEAL_MAX - bytes_queued) / sizeof(int16_t); + if (num_samples_pad == 0) { + return; + } - if(!DSGetLock(SpeakerVoice.lpDSBvoice, -#ifdef APPLE2IX - /*unused*/ 0, (DWORD)nNumSamplesToUse*sizeof(short), + unsigned long system_buffer_size = 0; + int16_t *system_samples_buffer = NULL; + if (!DSGetLock(SpeakerVoice.lpDSBvoice, /*unused*/ 0, num_samples_pad*sizeof(int16_t), &system_samples_buffer, &system_buffer_size, NULL, NULL)) { + return; + } + assert(num_samples_pad*sizeof(int16_t) <= system_buffer_size); + + //SPEAKER_LOG("bytes_queued:%d enqueueing %d quiet samples", bytes_queued, num_samples_pad); + for (unsigned int i=0; iUnlock(SpeakerVoice.lpDSBvoice->_this, (void*)system_samples_buffer, system_buffer_size, NULL, 0); +} + +// Submits samples from the samples_buffer to the audio system backend when running at a normal scaled-speed. This also +// generates cycles feedback to the main CPU timing routine depending on the needs of the streaming audio (more or less +// data). +static unsigned int _submit_samples_buffer(const unsigned int num_samples) { + + assert(num_samples); + + if (!SpeakerVoice.bActive) { + return num_samples; + } + + unsigned long bytes_queued = 0; + long err = SpeakerVoice.lpDSBvoice->GetCurrentPosition(SpeakerVoice.lpDSBvoice->_this, &bytes_queued, NULL); + if (err) { + return num_samples; + } + assert(bytes_queued <= SOUNDCORE_BUFFER_SIZE); + + // + // calculate CPU cycles feedback adjustment to prevent system audio buffer under/overflow + // + + const int error_increment = SoundCore_GetErrorInc(); + + if (bytes_queued < IDEAL_MIN) { + samples_adjustment_counter += error_increment; // need moar data + } else if (bytes_queued > IDEAL_MAX) { + samples_adjustment_counter -= error_increment; // need less data + } else { + samples_adjustment_counter = 0; // Acceptable amount of data in buffer + } + + const int samples_adjustment_max = SoundCore_GetErrorMax(); // capped to a max/min + if (samples_adjustment_counter < -samples_adjustment_max) { + samples_adjustment_counter = -samples_adjustment_max; + } else if (samples_adjustment_counter > samples_adjustment_max) { + samples_adjustment_counter = samples_adjustment_max; + } + + cycles_speaker_feedback = (int)(samples_adjustment_counter * cycles_per_sample); + + //SPEAKER_LOG("feedback:%d samples_adjustment_counter:%d", cycles_speaker_feedback, samples_adjustment_counter); + + // + // copy samples to audio system backend + // + + const unsigned int bytes_free = SOUNDCORE_BUFFER_SIZE - bytes_queued; + unsigned int num_samples_to_use = num_samples; + + if (num_samples_to_use * sizeof(int16_t) > bytes_free) { + num_samples_to_use = bytes_free / sizeof(int16_t); + } + + if (num_samples_to_use) { + unsigned long system_buffer_size = 0; + int16_t *system_samples_buffer = NULL; + + if (!DSGetLock(SpeakerVoice.lpDSBvoice, /*unused*/0, (unsigned long)num_samples_to_use*sizeof(int16_t), &system_samples_buffer, &system_buffer_size, NULL, NULL)) { + return num_samples; + } + + memcpy(system_samples_buffer, &samples_buffer[0], system_buffer_size); + + err = SpeakerVoice.lpDSBvoice->Unlock(SpeakerVoice.lpDSBvoice->_this, (void*)system_samples_buffer, system_buffer_size, NULL, 0); + if (err) { + return num_samples; + } + } + + return num_samples_to_use; +} + +// -------------------------------------------------------------------------------------------------------------------- +// speaker public API functions + +void speaker_destroy(void) { + if (SpeakerVoice.lpDSBvoice && SpeakerVoice.bActive) { + SpeakerVoice.lpDSBvoice->Stop(SpeakerVoice.lpDSBvoice->_this); + SpeakerVoice.bActive = false; + } + DSReleaseSoundBuffer(&SpeakerVoice); +} + +void speaker_init(void) { + SpeakerVoice.bIsSpeaker = true; + SpeakerVoice.bActive = true; + long err = DSGetSoundBuffer(&SpeakerVoice, DSBCAPS_CTRLVOLUME, SOUNDCORE_BUFFER_SIZE, SPKR_SAMPLE_RATE, 1); + assert(!err); + _speaker_init_timing(); +} + +void speaker_reset(void) { + _speaker_init_timing(); +} + +void speaker_flush(void) { + assert(pthread_self() == cpu_thread_id); + + if (is_fullspeed) { + cycles_quiet_time = cycles_count_total; + speaker_going_silent = false; + speaker_accessed_since_last_flush = false; + } else { + if (speaker_accessed_since_last_flush) { + cycles_quiet_time = 0; + speaker_going_silent = false; + speaker_accessed_since_last_flush = false; + } else { + if (!cycles_quiet_time) { + cycles_quiet_time = cycles_count_total; + } + const unsigned int cycles_diff = (unsigned int)(cycles_persec_target/10); // 0.1sec of cycles + if ((cycles_count_total - cycles_quiet_time) > cycles_diff*2) { + // After 0.2sec of //e cycles time set inactive flag (allows auto-switch to full speed for fast disk access) + speaker_recently_active = false; + speaker_going_silent = false; + speaker_data = 0; + } else if ((speaker_data == speaker_amplitude) && (cycles_count_total - cycles_quiet_time > cycles_diff)) { + // After 0.1sec of //e cycles time start reducing samples to zero (if they aren't there already). This + // process attempts to mask the extraneous clicks when freezing/restarting emulation (GUI access) and + // glitching from the audio system backend + speaker_going_silent = true; + speaker_silent_step = 1; + SPEAKER_LOG("speaker going silent"); + } + } + } + _speaker_update(/*toggled:false*/); + + unsigned int samples_used = 0; + if (is_fullspeed) { + assert(!samples_buffer_idx && "should be all quiet samples"); + _submit_samples_buffer_fullspeed(); + } else if (samples_buffer_idx) { + samples_used = _submit_samples_buffer(samples_buffer_idx); + } + assert(samples_used <= samples_buffer_idx); + + if (samples_used) { + memmove(samples_buffer, &samples_buffer[samples_used], samples_buffer_idx-samples_used); + samples_buffer_idx -= samples_used; + } +} + +bool speaker_is_active(void) { + return SpeakerVoice.bActive && speaker_recently_active; +} + +void speaker_set_volume(int16_t amplitude) { + speaker_amplitude = (amplitude/4); +} + +double speaker_cycles_per_sample(void) { + return cycles_per_sample; +} + +// -------------------------------------------------------------------------------------------------------------------- +// VM system entry point + +GLUE_C_READ(speaker_toggle) +{ + assert(pthread_self() == cpu_thread_id); + + timing_checkpoint_cycles(); + +#if DIRECT_SPEAKER_ACCESS +#error this used to be implemented ... + // AFAIK ... this requires an actual speaker device and ability to access it (usually requiring this program to be + // running setuid-operator or (gasp) setuid-root ... maybe #else - dwByteOffset, (DWORD)nNumSamplesToUse*sizeof(short), -#endif - &pDSLockedBuffer0, &dwDSLockedBufferSize0, - &pDSLockedBuffer1, &dwDSLockedBufferSize1)) - return nNumSamples; + speaker_accessed_since_last_flush = true; + speaker_recently_active = true; - memcpy(pDSLockedBuffer0, &pSpeakerBuffer[0], dwDSLockedBufferSize0); -#ifdef RIFF_SPKR - RiffPutSamples(pDSLockedBuffer0, dwDSLockedBufferSize0/sizeof(short)); + if (timing_should_auto_adjust_speed()) { + is_fullspeed = false; + } + + _speaker_update(/*toggled:true*/); + + if (!is_fullspeed) { + if (speaker_data) { + speaker_data = 0; + } else { + speaker_data = speaker_amplitude; + } + } #endif - if(pDSLockedBuffer1) - { - memcpy(pDSLockedBuffer1, &pSpeakerBuffer[dwDSLockedBufferSize0/sizeof(short)], dwDSLockedBufferSize1); -#ifdef RIFF_SPKR - RiffPutSamples(pDSLockedBuffer1, dwDSLockedBufferSize1/sizeof(short)); -#endif - } - - // Commit sound buffer -#ifdef APPLE2IX - hr = SpeakerVoice.lpDSBvoice->Unlock(SpeakerVoice.lpDSBvoice->_this, (void*)pDSLockedBuffer0, dwDSLockedBufferSize0, -#else - hr = SpeakerVoice.lpDSBvoice->Unlock((void*)pDSLockedBuffer0, dwDSLockedBufferSize0, -#endif - (void*)pDSLockedBuffer1, dwDSLockedBufferSize1); - if(FAILED(hr)) - return nNumSamples; - -#ifndef APPLE2IX - dwByteOffset = (dwByteOffset + (DWORD)nNumSamplesToUse*sizeof(short)*g_nSPKR_NumChannels) % g_dwDSSpkrBufferSize; -#endif - } - - return bBufferError ? nNumSamples : nNumSamplesToUse; + return floating_bus(); } -//----------------------------------------------------------------------------- - -void Spkr_Mute() -{ - if(SpeakerVoice.bActive && !SpeakerVoice.bMute) - { -#ifdef APPLE2IX - SpeakerVoice.lpDSBvoice->SetVolume(SpeakerVoice.lpDSBvoice->_this, DSBVOLUME_MIN); -#else - SpeakerVoice.lpDSBvoice->SetVolume(DSBVOLUME_MIN); -#endif - SpeakerVoice.bMute = true; - } -} - -void Spkr_Demute() -{ - if(SpeakerVoice.bActive && SpeakerVoice.bMute) - { -#ifdef APPLE2IX - SpeakerVoice.lpDSBvoice->SetVolume(SpeakerVoice.lpDSBvoice->_this, SpeakerVoice.nVolume); -#else - SpeakerVoice.lpDSBvoice->SetVolume(SpeakerVoice.nVolume); -#endif - SpeakerVoice.bMute = false; - } -} - -//----------------------------------------------------------------------------- - -static bool g_bSpkrRecentlyActive = false; - -static void Spkr_SetActive(bool bActive) -{ - if(!SpeakerVoice.bActive) - return; - - if(bActive) - { - // Called by SpkrToggle() or SpkrReset() - g_bSpkrRecentlyActive = true; - SpeakerVoice.bRecentlyActive = true; - } - else - { - // Called by SpkrUpdate() after 0.2s of speaker inactivity - g_bSpkrRecentlyActive = false; - SpeakerVoice.bRecentlyActive = false; - } -} - -bool Spkr_IsActive() -{ - return g_bSpkrRecentlyActive; -} - -//----------------------------------------------------------------------------- - -DWORD SpkrGetVolume() -{ - return SpeakerVoice.dwUserVolume; -} - -#ifdef APPLE2IX -// volume is square wave min and max samples -void SpkrSetVolume(short amplitude) -{ - g_nSpeakerData = amplitude; -} -#else -void SpkrSetVolume(DWORD dwVolume, DWORD dwVolumeMax) -{ - SpeakerVoice.dwUserVolume = dwVolume; - - SpeakerVoice.nVolume = NewVolume(dwVolume, dwVolumeMax); - - if(SpeakerVoice.bActive) - SpeakerVoice.lpDSBvoice->SetVolume(SpeakerVoice.nVolume); -} -#endif - -//============================================================================= - -bool Spkr_DSInit() -{ - // - // Create single Apple speaker voice - // - - if(!g_bDSAvailable) - return false; - - SpeakerVoice.bIsSpeaker = true; - - HRESULT hr = DSGetSoundBuffer(&SpeakerVoice, DSBCAPS_CTRLVOLUME, g_dwDSSpkrBufferSize, SPKR_SAMPLE_RATE, 1); - if(FAILED(hr)) - { - if(g_fh) fprintf(g_fh, "Spkr: DSGetSoundBuffer failed (%08X)\n",(unsigned int)hr); - return false; - } - -#ifndef APPLE2IX - if(!DSZeroVoiceBuffer(&SpeakerVoice, "Spkr", g_dwDSSpkrBufferSize)) - return false; -#endif - - SpeakerVoice.bActive = true; - -#ifndef APPLE2IX - // Volume might've been setup from value in Registry - if(!SpeakerVoice.nVolume) - SpeakerVoice.nVolume = DSBVOLUME_MAX; - - SpeakerVoice.lpDSBvoice->SetVolume(SpeakerVoice.nVolume); - - // - - DWORD dwCurrentPlayCursor, dwCurrentWriteCursor; - hr = SpeakerVoice.lpDSBvoice->GetCurrentPosition(&dwCurrentPlayCursor, &dwCurrentWriteCursor); - if(SUCCEEDED(hr) && (dwCurrentPlayCursor == dwCurrentWriteCursor)) - { - // KLUDGE: For my WinXP PC with "VIA AC'97 Enhanced Audio Controller" - // . Not required for my Win98SE/WinXP PC with PCI "Soundblaster Live!" - Sleep(200); - - hr = SpeakerVoice.lpDSBvoice->GetCurrentPosition(&dwCurrentPlayCursor, &dwCurrentWriteCursor); - char szDbg[100]; - sprintf(szDbg, "[DSInit] PC=%08X, WC=%08X, Diff=%08X\n", dwCurrentPlayCursor, dwCurrentWriteCursor, dwCurrentWriteCursor-dwCurrentPlayCursor); OutputDebugString(szDbg); - } -#endif - return true; -} - -void Spkr_DSUninit() -{ - if(SpeakerVoice.lpDSBvoice && SpeakerVoice.bActive) - { -#ifdef APPLE2IX - SpeakerVoice.lpDSBvoice->Stop(SpeakerVoice.lpDSBvoice->_this); -#else - SpeakerVoice.lpDSBvoice->Stop(); -#endif - SpeakerVoice.bActive = false; - } - - DSReleaseSoundBuffer(&SpeakerVoice); -} - -//============================================================================= - -#if !defined(APPLE2IX) -DWORD SpkrGetSnapshot(SS_IO_Speaker* pSS) -{ - pSS->g_nSpkrLastCycle = g_nSpkrLastCycle; - return 0; -} - -DWORD SpkrSetSnapshot(SS_IO_Speaker* pSS) -{ - g_nSpkrLastCycle = pSS->g_nSpkrLastCycle; - return 0; -} -#endif - diff --git a/src/audio/speaker.h b/src/audio/speaker.h index e8587d93..e4369a2c 100644 --- a/src/audio/speaker.h +++ b/src/audio/speaker.h @@ -12,48 +12,21 @@ #ifndef _SPEAKER_H_ #define _SPEAKER_H_ -#ifdef APPLE2IX -#include "audio/win-shim.h" -#endif - -extern DWORD soundtype; -extern double g_fClksPerSpkrSample; - -void SpkrDestroy (); -void SpkrInitialize (); -void SpkrReinitialize (); -void SpkrReset(); -BOOL SpkrSetEmulationType (HWND,DWORD); -void SpkrUpdate (DWORD); -void SpkrUpdate_Timer(); -void Spkr_SetErrorInc(const int nErrorInc); -void Spkr_SetErrorMax(const int nErrorMax); -DWORD SpkrGetVolume(); -#ifdef APPLE2IX +#define SPKR_SAMPLE_RATE 22050 #define SPKR_DATA_INIT 0x4000 -void SpkrSetVolume(short amplitude); -#else -void SpkrSetVolume(DWORD dwVolume, DWORD dwVolumeMax); -#endif -void Spkr_Mute(); -void Spkr_Demute(); -bool Spkr_IsActive(); -bool Spkr_DSInit(); -void Spkr_DSUninit(); -#ifdef APPLE2IX -#define __int64 -typedef struct { - unsigned __int64 g_nSpkrLastCycle; -} SS_IO_Speaker; -#endif -DWORD SpkrGetSnapshot(SS_IO_Speaker* pSS); -DWORD SpkrSetSnapshot(SS_IO_Speaker* pSS); -#ifdef APPLE2IX -void SpkrToggle(); -#else -BYTE __stdcall SpkrToggle (WORD pc, WORD addr, BYTE bWrite, BYTE d, ULONG nCyclesLeft); -#endif +void speaker_init(void); +void speaker_destroy(void); +void speaker_reset(void); +void speaker_flush(void); +void speaker_set_volume(int16_t amplitude); +bool speaker_is_active(void); + +/* + * returns the machine cycles per sample + * - for emulator running at normal speed: CLK_6502 / SPKR_SAMPLE_RATE == ~46 + */ +double speaker_cycles_per_sample(void); #endif /* whole file */ diff --git a/src/cpu-supp.c b/src/cpu-supp.c index 1b8114c5..f490b37c 100644 --- a/src/cpu-supp.c +++ b/src/cpu-supp.c @@ -36,8 +36,6 @@ uint8_t cpu65_rw; uint8_t cpu65_opcode; uint8_t cpu65_opcycles; -int16_t cpu65_cycle_count = 0; -int16_t cpu65_cycles_to_execute = 0; uint8_t cpu65__signal = 0; static pthread_mutex_t irq_mutex = PTHREAD_MUTEX_INITIALIZER; @@ -831,7 +829,7 @@ GLUE_C_WRITE(cpu65_trace_epilogue) void cpu65_trace_checkpoint(void) { if (cpu_trace_fp) { - fprintf(cpu_trace_fp, "---TOTAL CYC:%lu\n",g_nCumulativeCycles); + fprintf(cpu_trace_fp, "---TOTAL CYC:%lu\n",cycles_count_total); fflush(cpu_trace_fp); } } diff --git a/src/cpu.h b/src/cpu.h index caecb240..61591052 100644 --- a/src/cpu.h +++ b/src/cpu.h @@ -58,7 +58,6 @@ extern unsigned char cpu65_flags_encode[256]; extern unsigned char cpu65_flags_decode[256]; extern int16_t cpu65_cycle_count; -extern int16_t cpu65_cycles_to_execute; #if CPU_TRACING void cpu65_trace_begin(const char *trace_file); diff --git a/src/disk.c b/src/disk.c index d20c2317..011dfbc2 100644 --- a/src/disk.c +++ b/src/disk.c @@ -603,33 +603,33 @@ GLUE_C_READ(disk_read_phase) #endif } - return ea == 0xE0 ? 0xFF : floating_bus_hibit(1, cpu65_cycle_count); + return ea == 0xE0 ? 0xFF : floating_bus_hibit(1); } GLUE_C_READ(disk_read_motor_off) { clock_gettime(CLOCK_MONOTONIC, &disk6.motor_time); disk6.motor_off = 1; - return floating_bus_hibit(1, cpu65_cycle_count); + return floating_bus_hibit(1); } GLUE_C_READ(disk_read_motor_on) { clock_gettime(CLOCK_MONOTONIC, &disk6.motor_time); disk6.motor_off = 0; - return floating_bus_hibit(1, cpu65_cycle_count); + return floating_bus_hibit(1); } GLUE_C_READ(disk_read_select_a) { disk6.drive = 0; - return floating_bus(cpu65_cycle_count); + return floating_bus(); } GLUE_C_READ(disk_read_select_b) { disk6.drive = 1; - return floating_bus(cpu65_cycle_count); + return floating_bus(); } GLUE_C_READ(disk_read_latch) @@ -640,13 +640,13 @@ GLUE_C_READ(disk_read_latch) GLUE_C_READ(disk_read_prepare_in) { disk6.ddrw = 0; - return floating_bus_hibit(disk6.disk[disk6.drive].is_protected, cpu65_cycle_count); + return floating_bus_hibit(disk6.disk[disk6.drive].is_protected); } GLUE_C_READ(disk_read_prepare_out) { disk6.ddrw = 1; - return floating_bus_hibit(1, cpu65_cycle_count); + return floating_bus_hibit(1); } GLUE_C_WRITE(disk_write_latch) diff --git a/src/display.c b/src/display.c index e1ed1481..625fc89a 100644 --- a/src/display.c +++ b/src/display.c @@ -1151,8 +1151,8 @@ void video_redraw(void) { // References to Jim Sather's books are given as eg: // UTAIIe:5-7,P3 (Understanding the Apple IIe, chapter 5, page 7, Paragraph 3) -extern unsigned int CpuGetCyclesThisVideoFrame(const unsigned int nExecutedCycles); -uint16_t video_scanner_get_address(bool *vblBarOut, const unsigned int executedCycles) { +extern unsigned int CpuGetCyclesThisVideoFrame(void); +uint16_t video_scanner_get_address(bool *vblBarOut) { const bool SW_HIRES = (softswitches & SS_HIRES); const bool SW_TEXT = (softswitches & SS_TEXT); const bool SW_PAGE2 = (softswitches & SS_PAGE2); @@ -1160,7 +1160,7 @@ uint16_t video_scanner_get_address(bool *vblBarOut, const unsigned int executedC const bool SW_MIXED = (softswitches & SS_MIXED); // get video scanner position - unsigned int nCycles = CpuGetCyclesThisVideoFrame(executedCycles); + unsigned int nCycles = CpuGetCyclesThisVideoFrame(); // machine state switches int nHires = (SW_HIRES && !SW_TEXT) ? 1 : 0; @@ -1258,13 +1258,13 @@ uint16_t video_scanner_get_address(bool *vblBarOut, const unsigned int executedC return (uint16_t)nAddress; } -uint8_t floating_bus(const unsigned int executedCycles) { - uint16_t scanner_addr = video_scanner_get_address(NULL, executedCycles); +uint8_t floating_bus(void) { + uint16_t scanner_addr = video_scanner_get_address(NULL); return apple_ii_64k[0][scanner_addr]; } -uint8_t floating_bus_hibit(const bool hibit, const unsigned int executedCycles) { - uint16_t scanner_addr = video_scanner_get_address(NULL, executedCycles); +uint8_t floating_bus_hibit(const bool hibit) { + uint16_t scanner_addr = video_scanner_get_address(NULL); uint8_t b = apple_ii_64k[0][scanner_addr]; return (b & ~0x80) | (hibit ? 0x80 : 0); } diff --git a/src/interface.c b/src/interface.c index 0f46cbe5..eb5f3ddb 100644 --- a/src/interface.c +++ b/src/interface.c @@ -1280,7 +1280,7 @@ void c_interface_parameters() #ifdef __linux__ LOG("Back to Linux, w00t!\n"); #endif - SpkrDestroy(); + speaker_destroy(); MB_Destroy(); video_shutdown(); diff --git a/src/meta/debugger.c b/src/meta/debugger.c index bad6c412..e7541e75 100644 --- a/src/meta/debugger.c +++ b/src/meta/debugger.c @@ -1140,10 +1140,10 @@ static int begin_cpu_stepping() { // kludgey set max CPU speed... double saved_scale = cpu_scale_factor; double saved_altscale = cpu_altscale_factor; - bool saved_fullspeed = g_bFullSpeed; + bool saved_fullspeed = is_fullspeed; cpu_scale_factor = CPU_SCALE_FASTEST; cpu_altscale_factor = CPU_SCALE_FASTEST; - g_bFullSpeed = true; + is_fullspeed = true; unsigned int idx = 0; size_t textlen = 0; @@ -1197,7 +1197,7 @@ static int begin_cpu_stepping() { cpu_scale_factor = saved_scale; cpu_altscale_factor = saved_altscale; - g_bFullSpeed = saved_fullspeed; + is_fullspeed = saved_fullspeed; return ch; } diff --git a/src/misc.c b/src/misc.c index 691f2114..44a556b4 100644 --- a/src/misc.c +++ b/src/misc.c @@ -520,7 +520,7 @@ void c_initialize_apple_ii_memory() void c_initialize_sound_hooks() { #ifdef AUDIO_ENABLED - SpkrSetVolume(sound_volume * (SPKR_DATA_INIT/10)); + speaker_set_volume(sound_volume * (SPKR_DATA_INIT/10)); #endif for (int i = 0xC030; i < 0xC040; i++) { @@ -581,8 +581,11 @@ void c_initialize_vm() { void c_initialize_firsttime() ------------------------------------------------------------------------- */ -void reinitialize(void) -{ +void reinitialize(void) { + assert(pthread_self() == cpu_thread_id); + + cycles_count_total = 0; + c_initialize_vm(); softswitches = SS_TEXT | SS_IOUDIS | SS_C3ROM | SS_LCWRT | SS_LCSEC; @@ -600,8 +603,7 @@ void reinitialize(void) #endif } -void c_initialize_firsttime() -{ +void c_initialize_firsttime(void) { #ifdef INTERFACE_CLASSIC /* read in system files and calculate system defaults */ c_load_interface_font(); @@ -610,18 +612,9 @@ void c_initialize_firsttime() /* initialize the video system */ video_init(); - // TODO FIXME : sound system never released ... -#ifdef AUDIO_ENABLED - DSInit(); - SpkrInitialize(); - MB_Initialize(); -#endif - #ifdef DEBUGGER c_debugger_init(); #endif - - reinitialize(); } #if !defined(TESTING) && !defined(__APPLE__) diff --git a/src/test/testcommon.c b/src/test/testcommon.c index 820c22fa..77043c64 100644 --- a/src/test/testcommon.c +++ b/src/test/testcommon.c @@ -41,7 +41,7 @@ uint8_t c_PhasorIO(uint16_t addr) { return 0x0; } -void SpkrToggle() { +void c_speaker_toggle(void) { } void c_interface_print(int x, int y, const int cs, const char *s) { @@ -119,7 +119,7 @@ void test_common_init(bool do_cputhread) { // kludgey set max CPU speed... cpu_scale_factor = CPU_SCALE_FASTEST; cpu_altscale_factor = CPU_SCALE_FASTEST; - g_bFullSpeed = true; + is_fullspeed = true; caps_lock = true; diff --git a/src/test/testcpu.c b/src/test/testcpu.c index 71d850c5..323a3081 100644 --- a/src/test/testcpu.c +++ b/src/test/testcpu.c @@ -52,6 +52,7 @@ static void testcpu_setup(void *arg) { //reinitialize(); cpu65_uninterrupt(0xff); + extern int16_t cpu65_cycles_to_execute; cpu65_cycles_to_execute = 1; cpu65_pc = TEST_LOC; diff --git a/src/timing.c b/src/timing.c index 9f4956a9..99ecad7b 100644 --- a/src/timing.c +++ b/src/timing.c @@ -10,9 +10,24 @@ */ /* - * 65c02 CPU Timing Support. Some source derived from AppleWin. + * 65c02 CPU timing support. Source inspired/derived from AppleWin. * - * Copyleft 2013 Aaron Culliney + * Simplified timing loop for each execution period: + * + * ..{...+....[....|..................|.........]....^....|....^....^....}...... + * ti MBB CHK CHK MBE CHX SPK MBX tj ZZZ + * + * - ti : timing sample begin (lock out interface thread) + * - tj : timing sample end (unlock interface thread) + * - [ : cpu65_run() + * - ] : cpu65_run() finished + * - CHK : incoming timing_checkpoint_cycles() call from IO (bumps cycles_count_total) + * - CHX : update remainder of timing_checkpoint_cycles() for execution period + * - MBB : Mockingboard begin + * - MBE : Mockingboard end/flush (output) + * - MBX : Mockingboard end video frame (output) + * - SPK : Speaker output + * - ZZZ : housekeeping+sleep (or not) * */ @@ -29,31 +44,32 @@ #define DISK_MOTOR_QUIET_NSECS 2000000 -static const unsigned int uCyclesPerLine = 65; // 25 cycles of HBL & 40 cycles of HBL' -static const unsigned int uVisibleLinesPerFrame = 64*3; // 192 -static const unsigned int uLinesPerFrame = 262; // 64 in each third of the screen & 70 in VBL -static const unsigned int dwClksPerFrame = uCyclesPerLine * uLinesPerFrame; // 17030 +// VBL constants? +#define uCyclesPerLine 65 // 25 cycles of HBL & 40 cycles of HBL' +#define uVisibleLinesPerFrame (64*3) // 192 +#define uLinesPerFrame (262) // 64 in each third of the screen & 70 in VBL +#define dwClksPerFrame (uCyclesPerLine * uLinesPerFrame) // 17030 -double g_fCurrentCLK6502 = CLK_6502; -bool g_bFullSpeed = false; // HACK TODO FIXME : prolly shouldn't be global anymore -- don't think it's necessary for speaker/soundcore/etc anymore ... -uint64_t g_nCumulativeCycles = 0; // cumulative cycles since emulator (re)start -int g_nCpuCyclesFeedback = 0; +// cycle counting +double cycles_persec_target = CLK_6502; +unsigned long long cycles_count_total = 0; +int cycles_speaker_feedback = 0; +int16_t cpu65_cycles_to_execute = 0; // cycles-to-execute by cpu65_run() +int16_t cpu65_cycle_count = 0; // cycles currently excuted by cpu65_run() +static int16_t cycles_checkpoint_count = 0; static unsigned int g_dwCyclesThisFrame = 0; -static bool alt_speed_enabled = false; - +// scaling and speed adjustments +static bool auto_adjust_speed = true; double cpu_scale_factor = 1.0; double cpu_altscale_factor = 1.0; -bool auto_adjust_speed = true; - -int gc_cycles_timer_0 = 0; -int gc_cycles_timer_1 = 0; +bool is_fullspeed = false; +static bool alt_speed_enabled = false; +// misc volatile uint8_t emul_reinitialize = 0; pthread_t cpu_thread_id = 0; -static unsigned int g_nCyclesExecuted = 0; // # of cycles executed up to last IO access - // ----------------------------------------------------------------------------- struct timespec timespec_diff(struct timespec start, struct timespec end, bool *negative) { @@ -94,68 +110,59 @@ struct timespec timespec_diff(struct timespec start, struct timespec end, bool * static inline struct timespec timespec_add(struct timespec start, unsigned long nsecs) { start.tv_nsec += nsecs; - if (start.tv_nsec > NANOSECONDS) + if (start.tv_nsec > NANOSECONDS_PER_SECOND) { - start.tv_sec += (start.tv_nsec / NANOSECONDS); - start.tv_nsec %= NANOSECONDS; + start.tv_sec += (start.tv_nsec / NANOSECONDS_PER_SECOND); + start.tv_nsec %= NANOSECONDS_PER_SECOND; } return start; } -static void _timing_initialize(double scale) -{ - if (g_bFullSpeed) - { - TIMING_LOG("timing_initialize() emulation at fullspeed ..."); - return; +static void _timing_initialize(double scale) { + is_fullspeed = (scale >= CPU_SCALE_FASTEST); + if (!is_fullspeed) { + cycles_persec_target = CLK_6502 * scale; } - - g_fCurrentCLK6502 = CLK_6502 * scale; - // this is extracted out of SetClksPerSpkrSample -- speaker.c #ifdef AUDIO_ENABLED - g_fClksPerSpkrSample = (double) (UINT) (g_fCurrentCLK6502 / (double)SPKR_SAMPLE_RATE); - SpkrReinitialize(); - - TIMING_LOG("timing_initialize() ... ClockRate:%0.2lf ClockCyclesPerSpeakerSample:%0.2lf", g_fCurrentCLK6502, g_fClksPerSpkrSample); + speaker_reset(); + //TIMING_LOG("ClockRate:%0.2lf ClockCyclesPerSpeakerSample:%0.2lf", cycles_persec_target, speaker_cycles_per_sample()); #endif } -static inline void _switch_to_fullspeed(void) -{ - g_bFullSpeed = true; +void timing_initialize(void) { + _timing_initialize(alt_speed_enabled ? cpu_altscale_factor : cpu_scale_factor); } -static inline void _switch_to_regular_speed(double scale) -{ - g_bFullSpeed = false; - _timing_initialize(scale); -} - -void timing_toggle_cpu_speed(void) -{ +void timing_toggle_cpu_speed(void) { alt_speed_enabled = !alt_speed_enabled; - double scale_factor = alt_speed_enabled ? cpu_altscale_factor : cpu_scale_factor; - if (scale_factor >= CPU_SCALE_FASTEST) - { - _switch_to_fullspeed(); - } - else - { - _switch_to_regular_speed(scale_factor); - } + timing_initialize(); } -void timing_initialize(void) -{ - _timing_initialize(cpu_scale_factor); +void timing_set_auto_adjust_speed(bool auto_adjust) { + auto_adjust_speed = auto_adjust; + timing_initialize(); +} + +bool timing_should_auto_adjust_speed(void) { + double speed = alt_speed_enabled ? cpu_altscale_factor : cpu_scale_factor; + return auto_adjust_speed && (speed < CPU_SCALE_FASTEST); } void *cpu_thread(void *dummyptr) { assert(pthread_self() == cpu_thread_id); +#ifdef AUDIO_ENABLED + DSInit(); + speaker_init(); + MB_Initialize(); +#endif + + reinitialize(); + struct timespec deltat; + struct timespec disk_motor_time; struct timespec t0; // the target timer struct timespec ti, tj; // actual time samples bool negative = false; @@ -173,7 +180,6 @@ void *cpu_thread(void *dummyptr) { do { - g_nCumulativeCycles = 0; LOG("cpu_thread : begin main loop ..."); clock_gettime(CLOCK_MONOTONIC, &t0); @@ -187,7 +193,9 @@ void *cpu_thread(void *dummyptr) { deltat = timespec_diff(t0, ti, &negative); if (deltat.tv_sec) { - TIMING_LOG("NOTE : serious divergence from target time ..."); + if (!is_fullspeed) { + TIMING_LOG("NOTE : serious divergence from target time ..."); + } t0 = ti; deltat = timespec_diff(t0, ti, &negative); } @@ -195,15 +203,15 @@ void *cpu_thread(void *dummyptr) { drift_adj_nsecs = negative ? ~deltat.tv_nsec : deltat.tv_nsec; // set up increment & decrement counters - cpu65_cycles_to_execute = (g_fCurrentCLK6502 / 1000); // g_fCurrentCLK6502 * EXECUTION_PERIOD_NSECS / NANOSECONDS - cpu65_cycles_to_execute += g_nCpuCyclesFeedback; + cpu65_cycles_to_execute = (cycles_persec_target / 1000); // cycles_persec_target * EXECUTION_PERIOD_NSECS / NANOSECONDS_PER_SECOND + if (!is_fullspeed) { + cpu65_cycles_to_execute += cycles_speaker_feedback; + } if (cpu65_cycles_to_execute < 0) { cpu65_cycles_to_execute = 0; } - g_nCyclesExecuted = 0; - #ifdef AUDIO_ENABLED MB_StartOfCpuExecute(); #endif @@ -218,6 +226,7 @@ void *cpu_thread(void *dummyptr) { } cpu65_cycle_count = 0; + cycles_checkpoint_count = 0; cpu65_run(); // run emulation for cpu65_cycles_to_execute cycles ... if (is_debugging) { @@ -243,32 +252,23 @@ void *cpu_thread(void *dummyptr) { #if DEBUG_TIMING dbg_cycles_executed += cpu65_cycle_count; #endif - unsigned int uActualCyclesExecuted = cpu65_cycle_count; - g_dwCyclesThisFrame += uActualCyclesExecuted; + g_dwCyclesThisFrame += cpu65_cycle_count; #ifdef AUDIO_ENABLED - MB_UpdateCycles(uActualCyclesExecuted); // Update 6522s (NB. Do this before updating g_nCumulativeCycles below) + MB_UpdateCycles(); // update 6522s (NOTE: do this before updating cycles_count_total) #endif - // N.B.: IO calls that depend on accurate timing will update g_nCyclesExecuted - const int nRemainingCycles = uActualCyclesExecuted - g_nCyclesExecuted; - assert(nRemainingCycles >= 0); - g_nCumulativeCycles += nRemainingCycles; + timing_checkpoint_cycles(); #if CPU_TRACING cpu65_trace_checkpoint(); #endif - if (!g_bFullSpeed) - { #ifdef AUDIO_ENABLED - SpkrUpdate(uActualCyclesExecuted); // play audio + speaker_flush(); // play audio #endif - } - if (g_dwCyclesThisFrame >= dwClksPerFrame) - { + if (g_dwCyclesThisFrame >= dwClksPerFrame) { g_dwCyclesThisFrame -= dwClksPerFrame; - //VideoEndOfVideoFrame(); #ifdef AUDIO_ENABLED MB_EndOfVideoFrame(); #endif @@ -278,29 +278,21 @@ void *cpu_thread(void *dummyptr) { pthread_mutex_unlock(&interface_mutex); // -UNLOCK--------------------------------------------------------------------------------------- SAMPLE tj - if (auto_adjust_speed) { - deltat = timespec_diff(disk6.motor_time, tj, &negative); + if (timing_should_auto_adjust_speed()) { + disk_motor_time = timespec_diff(disk6.motor_time, tj, &negative); assert(!negative); - if (!g_bFullSpeed && + if (!is_fullspeed && #ifdef AUDIO_ENABLED - !Spkr_IsActive() && + !speaker_is_active() && #endif - !video_dirty() && (!disk6.motor_off && (deltat.tv_sec || deltat.tv_nsec > DISK_MOTOR_QUIET_NSECS)) ) + !video_dirty() && (!disk6.motor_off && (disk_motor_time.tv_sec || disk_motor_time.tv_nsec > DISK_MOTOR_QUIET_NSECS)) ) { TIMING_LOG("auto switching to full speed"); - _switch_to_fullspeed(); - } else if (g_bFullSpeed && ( -#ifdef AUDIO_ENABLED - Spkr_IsActive() || -#endif - video_dirty() || (disk6.motor_off && (deltat.tv_sec || deltat.tv_nsec > DISK_MOTOR_QUIET_NSECS))) ) - { - TIMING_LOG("auto switching to regular speed"); - _switch_to_regular_speed(alt_speed_enabled ? cpu_altscale_factor : cpu_scale_factor); + _timing_initialize(CPU_SCALE_FASTEST); } } - if (!g_bFullSpeed) { + if (!is_fullspeed) { deltat = timespec_diff(ti, tj, &negative); assert(!negative); long sleepfor = 0; @@ -328,19 +320,19 @@ void *cpu_thread(void *dummyptr) { #if DEBUG_TIMING // collect timing statistics - if (speaker_neg_feedback > g_nCpuCyclesFeedback) + if (speaker_neg_feedback > cycles_speaker_feedback) { - speaker_neg_feedback = g_nCpuCyclesFeedback; + speaker_neg_feedback = cycles_speaker_feedback; } - if (speaker_pos_feedback < g_nCpuCyclesFeedback) + if (speaker_pos_feedback < cycles_speaker_feedback) { - speaker_pos_feedback = g_nCpuCyclesFeedback; + speaker_pos_feedback = cycles_speaker_feedback; } dbg_ticks += EXECUTION_PERIOD_NSECS; - if ((dbg_ticks % NANOSECONDS) == 0) + if ((dbg_ticks % NANOSECONDS_PER_SECOND) == 0) { - LOG("tick:(%ld.%ld) real:(%ld.%ld) cycles exe: %d ... speaker feedback: %d/%d", t0.tv_sec, t0.tv_nsec, ti.tv_sec, ti.tv_nsec, dbg_cycles_executed, speaker_neg_feedback, speaker_pos_feedback); + TIMING_LOG("tick:(%ld.%ld) real:(%ld.%ld) cycles exe: %d ... speaker feedback: %d/%d", t0.tv_sec, t0.tv_nsec, ti.tv_sec, ti.tv_nsec, dbg_cycles_executed, speaker_neg_feedback, speaker_pos_feedback); dbg_cycles_executed = 0; dbg_ticks = 0; speaker_neg_feedback = 0; @@ -348,6 +340,21 @@ void *cpu_thread(void *dummyptr) { } #endif } + + if (timing_should_auto_adjust_speed()) { + if (is_fullspeed && ( +#ifdef AUDIO_ENABLED + speaker_is_active() || +#endif + video_dirty() || (disk6.motor_off && (disk_motor_time.tv_sec || disk_motor_time.tv_nsec > DISK_MOTOR_QUIET_NSECS))) ) + { + double speed = alt_speed_enabled ? cpu_altscale_factor : cpu_scale_factor; + if (speed < CPU_SCALE_FASTEST) { + TIMING_LOG("auto switching to configured speed"); + _timing_initialize(speed); + } + } + } } while (!emul_reinitialize); reinitialize(); @@ -356,23 +363,16 @@ void *cpu_thread(void *dummyptr) { return NULL; } -// From AppleWin... - -unsigned int CpuGetCyclesThisVideoFrame(const unsigned int nExecutedCycles) { - CpuCalcCycles(nExecutedCycles); - return g_dwCyclesThisFrame + g_nCyclesExecuted; +unsigned int CpuGetCyclesThisVideoFrame(void) { + timing_checkpoint_cycles(); + return g_dwCyclesThisFrame + cycles_checkpoint_count; } -// Called when an IO-reg is accessed & accurate cycle info is needed -void CpuCalcCycles(const unsigned long nExecutedCycles) { - // Calc # of cycles executed since this func was last called - const long nCycles = nExecutedCycles - g_nCyclesExecuted; - assert(nCycles >= 0); - g_nCumulativeCycles += nCycles; - // HACK FIXME TODO -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wshorten-64-to-32" - g_nCyclesExecuted = nExecutedCycles; -#pragma clang diagnostic pop +// Called when an IO-reg is accessed & accurate global cycle count info is needed +void timing_checkpoint_cycles(void) { + const int16_t d = cpu65_cycle_count - cycles_checkpoint_count; + assert(d >= 0); + cycles_count_total += d; + cycles_checkpoint_count = cpu65_cycle_count; } diff --git a/src/timing.h b/src/timing.h index c63269ff..74336ce6 100644 --- a/src/timing.h +++ b/src/timing.h @@ -21,45 +21,71 @@ #include "common.h" -#define NANOSECONDS 1000000000 +#if !defined(NANOSECONDS_PER_SECOND) +#define NANOSECONDS_PER_SECOND 1000000000 +#endif -// timing values cribbed from AppleWin ... should double-check _Understanding the Apple IIe_ +// timing values cribbed from AppleWin ... reference: Sather's _Understanding the Apple IIe_ +// TODO: revisit this if/when attempting to actually sync up VBL/VSYNC to actual device vsync // 14318181.81... -#define _M14 (157500000.0 / 11.0) +#define _M14 (157500000.0 / 11.0) +#define _M14_INT (157500000 / 11) // 65 cycles per 912 14M clocks = 1020484.45... -#define CLK_6502 ((_M14 * 65.0) / 912.0) +#define CLK_6502 ((_M14 * 65.0) / 912.0) +#define CLK_6502_INT ((_M14_INT * 65) / 912) #define CPU_SCALE_SLOWEST 0.25 #define CPU_SCALE_FASTEST 4.05 -#define CPU_SCALE_STEP_DIV 0.01 -#define CPU_SCALE_STEP 0.05 +#ifdef INTERFACE_CLASSIC +# define CPU_SCALE_STEP_DIV 0.01 +# define CPU_SCALE_STEP 0.05 +#endif -#define SPKR_SAMPLE_RATE 22050 +extern unsigned long long cycles_count_total;// cumulative cycles count from machine reset +extern double cycles_persec_target; // CLK_6502 * current CPU scale +extern int cycles_speaker_feedback; // current -/+ speaker requested feedback -extern double g_fCurrentCLK6502; -extern bool g_bFullSpeed; -extern uint64_t g_nCumulativeCycles; -extern int g_nCpuCyclesFeedback; - -extern double cpu_scale_factor; -extern double cpu_altscale_factor; -extern bool auto_adjust_speed; +extern double cpu_scale_factor; // scale factor #1 +extern double cpu_altscale_factor; // scale factor #2 +extern bool is_fullspeed; // emulation in full native speed? extern pthread_t cpu_thread_id; -extern int gc_cycles_timer_0; -extern int gc_cycles_timer_1; - +/* + * calculate the difference between two timespec structures + */ struct timespec timespec_diff(struct timespec start, struct timespec end, bool *negative); +/* + * toggles CPU speed between configured values + */ void timing_toggle_cpu_speed(void); +/* + * (dis)allow automatic adjusting of CPU speed between presently configured and max (when loading disk images). + */ +void timing_set_auto_adjust_speed(bool auto_adjust); + +/* + * check whether automatic adjusting of CPU speed is configured. + */ +bool timing_should_auto_adjust_speed(void); + +/* + * initialize timing + */ void timing_initialize(void); +/* + * timing/CPU thread entry point + */ void *cpu_thread(void *ignored); -void CpuCalcCycles(const unsigned long nExecutedCycles); +/* + * checkpoints current cycle count and updates total (for timing-dependent I/O) + */ +void timing_checkpoint_cycles(void); #endif // whole file diff --git a/src/video/video.h b/src/video/video.h index e8797124..e6e4bcbe 100644 --- a/src/video/video.h +++ b/src/video/video.h @@ -124,9 +124,9 @@ bool video_dirty(void); /* * VBL routines */ -uint16_t video_scanner_get_address(bool *vblBarOut, const unsigned int executedCycles); -uint8_t floating_bus(const unsigned int executedCycles); -uint8_t floating_bus_hibit(const bool hibit, const unsigned int executedCycles); +uint16_t video_scanner_get_address(bool *vblBarOut); +uint8_t floating_bus(void); +uint8_t floating_bus_hibit(const bool hibit); #endif /* !__ASSEMBLER__ */ diff --git a/src/vm.c b/src/vm.c index c618cf42..63ca78ac 100644 --- a/src/vm.c +++ b/src/vm.c @@ -113,6 +113,10 @@ #include "common.h" +// joystick timer values +int gc_cycles_timer_0 = 0; +int gc_cycles_timer_1 = 0; + #if VM_TRACING FILE *test_vm_fp = NULL; @@ -124,7 +128,7 @@ typedef struct vm_trace_range_t { GLUE_C_READ(ram_nop) { - return (cpu65_rw&MEM_WRITE_FLAG) ? 0x0 : floating_bus(cpu65_cycle_count); + return (cpu65_rw&MEM_WRITE_FLAG) ? 0x0 : floating_bus(); } GLUE_C_READ(read_unmapped_softswitch) @@ -146,21 +150,13 @@ GLUE_C_READ(read_keyboard_strobe) return apple_ii_64k[0][0xC000]; } -GLUE_C_READ(speaker_toggle) -{ -#ifdef AUDIO_ENABLED - SpkrToggle(); -#endif - return floating_bus(cpu65_cycle_count); -} - // ---------------------------------------------------------------------------- // graphics softswitches GLUE_C_READ(iie_page2_off) { if (!(softswitches & SS_PAGE2)) { - return floating_bus(cpu65_cycle_count); + return floating_bus(); } softswitches &= ~(SS_PAGE2|SS_SCREEN); @@ -178,13 +174,13 @@ GLUE_C_READ(iie_page2_off) video_setpage(0); - return floating_bus(cpu65_cycle_count); + return floating_bus(); } GLUE_C_READ(iie_page2_on) { if (softswitches & SS_PAGE2) { - return floating_bus(cpu65_cycle_count); + return floating_bus(); } softswitches |= SS_PAGE2; @@ -203,7 +199,7 @@ GLUE_C_READ(iie_page2_on) video_setpage(1); } - return floating_bus(cpu65_cycle_count); + return floating_bus(); } GLUE_C_READ(iie_check_page2) @@ -217,7 +213,7 @@ GLUE_C_READ(read_switch_graphics) softswitches &= ~SS_TEXT; video_redraw(); } - return floating_bus(cpu65_cycle_count); + return floating_bus(); } GLUE_C_READ(read_switch_text) @@ -226,7 +222,7 @@ GLUE_C_READ(read_switch_text) softswitches |= SS_TEXT; video_redraw(); } - return floating_bus(cpu65_cycle_count); + return floating_bus(); } GLUE_C_READ(iie_check_text) @@ -240,7 +236,7 @@ GLUE_C_READ(read_switch_no_mixed) softswitches &= ~SS_MIXED; video_redraw(); } - return floating_bus(cpu65_cycle_count); + return floating_bus(); } GLUE_C_READ(read_switch_mixed) @@ -249,7 +245,7 @@ GLUE_C_READ(read_switch_mixed) softswitches |= SS_MIXED; video_redraw(); } - return floating_bus(cpu65_cycle_count); + return floating_bus(); } GLUE_C_READ(iie_check_mixed) @@ -262,13 +258,13 @@ GLUE_C_READ(iie_annunciator) if ((ea >= 0xC058) && (ea <= 0xC05B)) { // TODO: alternate joystick management? } - return (cpu65_rw&MEM_WRITE_FLAG) ? 0x0 : floating_bus(cpu65_cycle_count); + return (cpu65_rw&MEM_WRITE_FLAG) ? 0x0 : floating_bus(); } GLUE_C_READ(iie_hires_off) { if (!(softswitches & SS_HIRES)) { - return floating_bus(cpu65_cycle_count); + return floating_bus(); } softswitches &= ~(SS_HIRES|SS_HGRRD|SS_HGRWRT); @@ -286,13 +282,13 @@ GLUE_C_READ(iie_hires_off) } video_redraw(); - return floating_bus(cpu65_cycle_count); + return floating_bus(); } GLUE_C_READ(iie_hires_on) { if (softswitches & SS_HIRES) { - return floating_bus(cpu65_cycle_count); + return floating_bus(); } softswitches |= SS_HIRES; @@ -310,7 +306,7 @@ GLUE_C_READ(iie_hires_on) } video_redraw(); - return floating_bus(cpu65_cycle_count); + return floating_bus(); } GLUE_C_READ(iie_check_hires) @@ -356,7 +352,7 @@ GLUE_C_READ(read_gc_strobe) } // NOTE (possible TODO FIXME): unimplemented GC2 and GC3 timers since they were not wired on the //e ... - return floating_bus(cpu65_cycle_count); + return floating_bus(); } GLUE_C_READ(read_gc0) @@ -381,12 +377,12 @@ GLUE_C_READ(read_gc1) GLUE_C_READ(iie_read_gc2) { - return floating_bus(cpu65_cycle_count); + return floating_bus(); } GLUE_C_READ(iie_read_gc3) { - return floating_bus(cpu65_cycle_count); + return floating_bus(); } // ---------------------------------------------------------------------------- @@ -418,7 +414,7 @@ GLUE_C_READ(iie_c080) if (softswitches & SS_ALTZP) { _lc_to_auxmem(); } - return floating_bus(cpu65_cycle_count); + return floating_bus(); } GLUE_C_READ(iie_c081) @@ -438,7 +434,7 @@ GLUE_C_READ(iie_c081) if (softswitches & SS_ALTZP) { _lc_to_auxmem(); } - return floating_bus(cpu65_cycle_count); + return floating_bus(); } GLUE_C_READ(lc_c082) @@ -452,7 +448,7 @@ GLUE_C_READ(lc_c082) base_d000_wrt = 0; base_e000_wrt = 0; - return floating_bus(cpu65_cycle_count); + return floating_bus(); } GLUE_C_READ(iie_c083) @@ -470,7 +466,7 @@ GLUE_C_READ(iie_c083) if (softswitches & SS_ALTZP) { _lc_to_auxmem(); } - return floating_bus(cpu65_cycle_count); + return floating_bus(); } GLUE_C_READ(iie_c088) @@ -487,7 +483,7 @@ GLUE_C_READ(iie_c088) if (softswitches & SS_ALTZP) { _lc_to_auxmem(); } - return floating_bus(cpu65_cycle_count); + return floating_bus(); } GLUE_C_READ(iie_c089) @@ -507,7 +503,7 @@ GLUE_C_READ(iie_c089) if (softswitches & SS_ALTZP) { _lc_to_auxmem(); } - return floating_bus(cpu65_cycle_count); + return floating_bus(); } GLUE_C_READ(lc_c08a) @@ -520,7 +516,7 @@ GLUE_C_READ(lc_c08a) base_d000_wrt = 0; base_e000_wrt = 0; - return floating_bus(cpu65_cycle_count); + return floating_bus(); } GLUE_C_READ(iie_c08b) @@ -540,7 +536,7 @@ GLUE_C_READ(iie_c08b) if (softswitches & SS_ALTZP) { _lc_to_auxmem(); } - return floating_bus(cpu65_cycle_count); + return floating_bus(); } GLUE_C_READ(iie_check_bank) @@ -559,7 +555,7 @@ GLUE_C_READ(iie_check_lcram) GLUE_C_READ(iie_80store_off) { if (!(softswitches & SS_80STORE)) { - return floating_bus(cpu65_cycle_count); + return floating_bus(); } softswitches &= ~(SS_80STORE|SS_TEXTRD|SS_TEXTWRT|SS_HGRRD|SS_HGRWRT); @@ -586,13 +582,13 @@ GLUE_C_READ(iie_80store_off) video_setpage(1); } - return floating_bus(cpu65_cycle_count); + return floating_bus(); } GLUE_C_READ(iie_80store_on) { if (softswitches & SS_80STORE) { - return floating_bus(cpu65_cycle_count); + return floating_bus(); } softswitches |= SS_80STORE; @@ -619,7 +615,7 @@ GLUE_C_READ(iie_80store_on) softswitches &= ~SS_SCREEN; video_setpage(0); - return floating_bus(cpu65_cycle_count); + return floating_bus(); } GLUE_C_READ(iie_check_80store) @@ -630,7 +626,7 @@ GLUE_C_READ(iie_check_80store) GLUE_C_READ(iie_ramrd_main) { if (!(softswitches & SS_RAMRD)) { - return floating_bus(cpu65_cycle_count); + return floating_bus(); } softswitches &= ~SS_RAMRD; @@ -647,13 +643,13 @@ GLUE_C_READ(iie_ramrd_main) base_hgrrd = apple_ii_64k[0]; } - return floating_bus(cpu65_cycle_count); + return floating_bus(); } GLUE_C_READ(iie_ramrd_aux) { if (softswitches & SS_RAMRD) { - return floating_bus(cpu65_cycle_count); + return floating_bus(); } softswitches |= SS_RAMRD; @@ -670,7 +666,7 @@ GLUE_C_READ(iie_ramrd_aux) base_hgrrd = apple_ii_64k[1]; } - return floating_bus(cpu65_cycle_count); + return floating_bus(); } GLUE_C_READ(iie_check_ramrd) @@ -681,7 +677,7 @@ GLUE_C_READ(iie_check_ramrd) GLUE_C_READ(iie_ramwrt_main) { if (!(softswitches & SS_RAMWRT)) { - return floating_bus(cpu65_cycle_count); + return floating_bus(); } softswitches &= ~SS_RAMWRT; @@ -698,13 +694,13 @@ GLUE_C_READ(iie_ramwrt_main) base_hgrwrt = apple_ii_64k[0]; } - return floating_bus(cpu65_cycle_count); + return floating_bus(); } GLUE_C_READ(iie_ramwrt_aux) { if (softswitches & SS_RAMWRT) { - return floating_bus(cpu65_cycle_count); + return floating_bus(); } softswitches |= SS_RAMWRT; @@ -721,7 +717,7 @@ GLUE_C_READ(iie_ramwrt_aux) base_hgrwrt = apple_ii_64k[1]; } - return floating_bus(cpu65_cycle_count); + return floating_bus(); } GLUE_C_READ(iie_check_ramwrt) @@ -733,7 +729,7 @@ GLUE_C_READ_ALTZP(iie_altzp_main) { if (!(softswitches & SS_ALTZP)) { /* NOTE : test if ALTZP already off - due to d000-bank issues it is *needed*, not just a shortcut */ - return floating_bus(cpu65_cycle_count); + return floating_bus(); } softswitches &= ~SS_ALTZP; @@ -749,14 +745,14 @@ GLUE_C_READ_ALTZP(iie_altzp_main) base_e000_wrt = language_card[0] - 0xE000; } - return floating_bus(cpu65_cycle_count); + return floating_bus(); } GLUE_C_READ_ALTZP(iie_altzp_aux) { if (softswitches & SS_ALTZP) { /* NOTE : test if ALTZP already on - due to d000-bank issues it is *needed*, not just a shortcut */ - return floating_bus(cpu65_cycle_count); + return floating_bus(); } softswitches |= SS_ALTZP; @@ -764,7 +760,7 @@ GLUE_C_READ_ALTZP(iie_altzp_aux) _lc_to_auxmem(); - return floating_bus(cpu65_cycle_count); + return floating_bus(); } GLUE_C_READ(iie_check_altzp) @@ -775,7 +771,7 @@ GLUE_C_READ(iie_check_altzp) GLUE_C_READ(iie_80col_off) { if (!(softswitches & SS_80COL)) { - return floating_bus(cpu65_cycle_count); + return floating_bus(); } softswitches &= ~SS_80COL; @@ -784,13 +780,13 @@ GLUE_C_READ(iie_80col_off) video_redraw(); } - return floating_bus(cpu65_cycle_count); + return floating_bus(); } GLUE_C_READ(iie_80col_on) { if (softswitches & SS_80COL) { - return floating_bus(cpu65_cycle_count); + return floating_bus(); } softswitches |= SS_80COL; @@ -799,7 +795,7 @@ GLUE_C_READ(iie_80col_on) video_redraw(); } - return floating_bus(cpu65_cycle_count); + return floating_bus(); } GLUE_C_READ(iie_check_80col) @@ -813,7 +809,7 @@ GLUE_C_READ(iie_altchar_off) softswitches &= ~SS_ALTCHAR; c_set_primary_char(); } - return floating_bus(cpu65_cycle_count); + return floating_bus(); } GLUE_C_READ(iie_altchar_on) @@ -822,7 +818,7 @@ GLUE_C_READ(iie_altchar_on) softswitches |= SS_ALTCHAR; c_set_altchar(); } - return floating_bus(cpu65_cycle_count); + return floating_bus(); } GLUE_C_READ(iie_check_altchar) @@ -854,7 +850,7 @@ GLUE_C_READ(iie_dhires_on) softswitches |= SS_DHIRES; video_redraw(); } - return floating_bus(cpu65_cycle_count); + return floating_bus(); } GLUE_C_READ(iie_dhires_off) @@ -863,7 +859,7 @@ GLUE_C_READ(iie_dhires_off) softswitches &= ~SS_DHIRES; video_redraw(); } - return floating_bus(cpu65_cycle_count); + return floating_bus(); } GLUE_C_READ(iie_check_dhires) @@ -875,7 +871,7 @@ GLUE_C_READ(iie_check_dhires) GLUE_C_READ(iie_check_vbl) { bool vbl_bar = false; - video_scanner_get_address(&vbl_bar, cpu65_cycle_count); + video_scanner_get_address(&vbl_bar); uint8_t key = apple_ii_64k[0][0xC000]; return (key & ~0x80) | (vbl_bar ? 0x80 : 0x00); }