From d0e4cf72abe47561d7e2c371b104ea77a4b6a086 Mon Sep 17 00:00:00 2001 From: Shamus Hammons Date: Mon, 16 Feb 2009 16:22:33 +0000 Subject: [PATCH] Changes to sound system relating to the new threaded CPU core. It works, mostly, but still plays back too fast. Dunno why. :-/ --- src/apple2.cpp | 103 +++++++++++++++++++++++++++----------- src/sound.cpp | 131 +++++++++++++++++++++++++++++++++++++++++++++---- src/sound.h | 3 +- 3 files changed, 196 insertions(+), 41 deletions(-) diff --git a/src/apple2.cpp b/src/apple2.cpp index 520d37b..31c043b 100755 --- a/src/apple2.cpp +++ b/src/apple2.cpp @@ -54,13 +54,19 @@ #include "gui/draggablewindow2.h" #include "gui/textedit.h" -//using namespace std; +// Debug and misc. defines + +#define THREADED_65C02 +//#define CPU_THREAD_OVERFLOW_COMPENSATION +//#define DEBUG_LC +#define CPU_CLOCK_CHECKING // Global variables uint8 ram[0x10000], rom[0x10000]; // RAM & ROM spaces +uint8 ram2[0x10000]; uint8 diskRom[0x100]; // Disk ROM space -V65C02REGS mainCPU; +V65C02REGS mainCPU; // v65C02 execution context uint8 appleType = APPLE_TYPE_II; FloppyDrive floppyDrive; @@ -95,7 +101,6 @@ static bool LoadApple2State(const char * filename); static void FrameCallback(void); static void BlinkTimer(void); -#define THREADED_65C02 #ifdef THREADED_65C02 // Test of threaded execution of 6502 static SDL_Thread * cpuThread = NULL; @@ -115,18 +120,41 @@ int CPUThreadFunc(void * data) // Also, must be created in the thread that uses it... SDL_mutex * cpuMutex = SDL_CreateMutex(); +#ifdef CPU_THREAD_OVERFLOW_COMPENSATION + float overflow = 0.0; +#endif + do { if (cpuSleep) SDL_CondWait(cpuCond, cpuMutex); - Execute65C02(&mainCPU, 17066); // how much? 1 frame (after 1 s, off by 40 cycles) + uint32 cycles = 17066; +#ifdef CPU_THREAD_OVERFLOW_COMPENSATION +// ODD! It's closer *without* this overflow compensation. ??? WHY ??? + overflow += 0.666666667; + + if (overflow > 1.0) + { + overflow -= 1.0; + cycles++; + } +#endif + + Execute65C02(&mainCPU, cycles); // how much? 1 frame (after 1 s, off by 40 cycles) not any more--it's off by as much as 240 now! SDL_mutexP(cpuMutex); + // Adjust the sound routine's last cycle toggled time base + // Also, since we're finished executing, .clock is now valid + AdjustLastToggleCycles(mainCPU.clock); +#if 0 if (SDL_CondWait(cpuCond, cpuMutex) != 0) { printf("SDL_CondWait != 0! (Error: '%s')\n", SDL_GetError()); exit(-1); } +#else + SDL_CondWait(cpuCond, cpuMutex); +#endif SDL_mutexV(cpuMutex); } while (!cpuFinished); @@ -189,11 +217,11 @@ if (addr >= 0xC080 && addr <= 0xC08F) WriteLog("\n*** Read at I/O address %04X\n", addr); #endif - if ((addr & 0xFFF0) == 0xC000) + if ((addr & 0xFFF0) == 0xC000) // Read $C000-$C00F { return lastKeyPressed | (keyDown ? 0x80 : 0x00); } - else if ((addr & 0xFFF0) == 0xC010) + else if ((addr & 0xFFF0) == 0xC010) // Read $C010-$C01F { //This is bogus: keyDown is set to false, so return val NEVER is set... //Fixed... @@ -202,7 +230,7 @@ if (addr >= 0xC080 && addr <= 0xC08F) keyDown = false; return retVal; } - else if ((addr & 0xFFF0) == 0xC030) + else if ((addr & 0xFFF0) == 0xC030) // Read $C030-$C03F { /* This is problematic, mainly because the v65C02 removes actual cycles run after each call. @@ -225,35 +253,35 @@ deltaT to zero. In the sound IRQ, if deltaT > buffer size, then subtract buffer //should it return something else here??? return 0x00; } - else if (addr == 0xC050) + else if (addr == 0xC050) // Read $C050 { textMode = false; } - else if (addr == 0xC051) + else if (addr == 0xC051) // Read $C051 { textMode = true; } - else if (addr == 0xC052) + else if (addr == 0xC052) // Read $C052 { mixedMode = false; } - else if (addr == 0xC053) + else if (addr == 0xC053) // Read $C053 { mixedMode = true; } - else if (addr == 0xC054) + else if (addr == 0xC054) // Read $C054 { displayPage2 = false; } - else if (addr == 0xC055) + else if (addr == 0xC055) // Read $C055 { displayPage2 = true; } - else if (addr == 0xC056) + else if (addr == 0xC056) // Read $C056 { hiRes = false; } - else if (addr == 0xC057) + else if (addr == 0xC057) // Read $C057 { hiRes = true; } @@ -297,7 +325,6 @@ deltaT to zero. In the sound IRQ, if deltaT > buffer size, then subtract buffer A = PEEK($C082) */ -//#define DEBUG_LC else if ((addr & 0xFFFB) == 0xC080) { #ifdef DEBUG_LC @@ -560,6 +587,8 @@ if (addr == 0x7F47) WriteLog("\n*** Byte %02X written at address %04X\n", b, addr); #endif /* +I think this is IIc/IIe only... + CLR80STORE=$C000 ;80STORE Off- disable 80-column memory mapping (Write) SET80STORE=$C001 ;80STORE On- enable 80-column memory mapping (WR-only) @@ -727,6 +756,7 @@ WriteLog("LC(R): $C08B 49291 OECG RR Read/Write RAM bank 1\n"); else if (addr == 0xC0EC) { //change this to Write()? (and the other to Read()?) Dunno. Seems to work OK, but still... +//or DoIO floppyDrive.ReadWrite(); } else if (addr == 0xC0ED) @@ -794,10 +824,10 @@ static bool LoadApple2State(const char * filename) return false; } -//#define CPU_CLOCK_CHECKING #ifdef CPU_CLOCK_CHECKING uint8 counter = 0; uint32 totalCPU = 0; +uint64 lastClock = 0; #endif // // Main loop @@ -812,6 +842,7 @@ int main(int /*argc*/, char * /*argv*/[]) //Need to bankify this stuff for the IIe emulation... memset(ram, 0, 0x10000); memset(rom, 0, 0x10000); + memset(ram2, 0, 0x10000); // Set up V65C02 execution context memset(&mainCPU, 0, sizeof(V65C02REGS)); @@ -914,19 +945,8 @@ memcpy(ram + 0xD000, ram + 0xC000, 0x1000); startTicks = SDL_GetTicks(); #ifdef THREADED_65C02 -//cpuMutex = SDL_CreateMutex(); -//mutex must be locked for conditional to work... -//if (SDL_mutexP(cpuMutex) == -1) -//{ -// printf("Couldn't lock CPU mutex!\n"); -// exit(-1); -//} cpuCond = SDL_CreateCond(); -//printf("mutex=$%08X, cond=$%08X\n", cpuMutex, cpuCond); -//cpuSleep = true; cpuThread = SDL_CreateThread(CPUThreadFunc, NULL); -//cpuSleep = false; -//SDL_CondSignal(cpuCond); #endif WriteLog("Entering main loop...\n"); @@ -941,7 +961,9 @@ cpuThread = SDL_CreateThread(CPUThreadFunc, NULL); //Fixed, but mainCPU.clock is destroyed in the bargain. Oh well. // mainCPU.clock -= USEC_TO_M6502_CYCLES(timeToNextEvent); #ifdef CPU_CLOCK_CHECKING +#ifndef THREADED_65C02 totalCPU += USEC_TO_M6502_CYCLES(timeToNextEvent); +#endif #endif // Handle CPU time delta for sound... //Don't need this anymore now that we use absolute time... @@ -951,10 +973,10 @@ totalCPU += USEC_TO_M6502_CYCLES(timeToNextEvent); #ifdef THREADED_65C02 cpuFinished = true; -SDL_CondSignal(cpuCond);//thread is asleep, wake it up +SDL_CondSignal(cpuCond);//thread is probably asleep, wake it up SDL_WaitThread(cpuThread, NULL); +//nowok:SDL_WaitThread(CPUThreadFunc, NULL); SDL_DestroyCond(cpuCond); -//SDL_DestroyMutex(cpuMutex); #endif if (settings.autoStateSaving) @@ -1119,6 +1141,10 @@ else if (event.key.keysym.sym == SDLK_F10) SetCallbackTime(FrameCallback, 16666.66666667); #ifdef CPU_CLOCK_CHECKING +//We know it's stopped, so we can get away with this... +uint64 clock = GetCurrentV65C02Clock(); +totalCPU += (uint32)(clock - lastClock); +lastClock = clock; counter++; if (counter == 60) { @@ -1152,4 +1178,21 @@ of the threads? o Have the CPU thread manage the timer mechanism? (need to have a method of carrying remainder CPU cycles over...) +One way would be to use a fractional accumulator, then subtract 1 every +time it overflows. Like so: + +double overflow = 0; +uint32 time = 20; +while (!done) +{ + Execute6808(&soundCPU, time); + overflow += 0.289115646; + if (overflow > 1.0) + { + overflow -= 1.0; + time = 21; + } + else + time = 20; +} */ diff --git a/src/sound.cpp b/src/sound.cpp index 3fffcae..53c6fb0 100755 --- a/src/sound.cpp +++ b/src/sound.cpp @@ -26,13 +26,21 @@ #include #include "log.h" +// Useful defines + +//#define DEBUG //#define SAMPLE_RATE (44100.0) #define SAMPLE_RATE (48000.0) #define SAMPLES_PER_FRAME (SAMPLE_RATE / 60.0) -#define CYCLES_PER_SAMPLE (1024000.0 / SAMPLE_RATE) -#define SOUND_BUFFER_SIZE (8192) -//#define AMPLITUDE (16) // -32 - +32 seems to be plenty loud! +// ~ 21 +//#define CYCLES_PER_SAMPLE (1024000.0 / SAMPLE_RATE) +// ~ 17 (lower pitched than above...!) +// Makes sense, as this is the divisor for # of cycles passed +#define CYCLES_PER_SAMPLE (800000.0 / SAMPLE_RATE) +//nope, too high #define CYCLES_PER_SAMPLE (960000.0 / SAMPLE_RATE) +//#define SOUND_BUFFER_SIZE (8192) +#define SOUND_BUFFER_SIZE (16384) // Global variables @@ -50,7 +58,8 @@ static uint64 samplePosition; static SDL_cond * conditional = NULL; static SDL_mutex * mutex = NULL; static SDL_mutex * mutex2 = NULL; -static uint8 ampPtr = 5; +static int8 sample; +static uint8 ampPtr = 5; // Start with -16 - +16 static uint16 amplitude[17] = { 0, 1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096, 8192, 16384, 32768 }; @@ -63,7 +72,7 @@ static void SDLSoundCallback(void * userdata, Uint8 * buffer, int length); // void SoundInit(void) { -#if 1 +#if 0 // To weed out problems for now... return; #endif @@ -91,6 +100,7 @@ return; sampleBase = 0; lastToggleCycles = 0; samplePosition = 0; + sample = desired.silence; // ? wilwok ? yes SDL_PauseAudio(false); // Start playback! soundInitialized = true; @@ -123,6 +133,7 @@ static void SDLSoundCallback(void * userdata, Uint8 * buffer, int length) // (Actually, this should never happen since we fill the buffer beforehand.) // (But, then again, if the sound hasn't been toggled for a while, then this // makes perfect sense as the buffer won't have been filled at all!) + // (Should NOT starve now, now that we properly handle frame edges...) // Let's try using a mutex for shared resource consumption... SDL_mutexP(mutex2); @@ -135,8 +146,8 @@ static void SDLSoundCallback(void * userdata, Uint8 * buffer, int length) buffer[i] = soundBuffer[i]; // Fill buffer with last value - memset(buffer + soundBufferPos, (uint8)(speakerState ? amplitude[ampPtr] : -amplitude[ampPtr]), length - soundBufferPos); - soundBufferPos = 0; // Reset soundBufferPos to start of buffer... +// memset(buffer + soundBufferPos, (uint8)(speakerState ? amplitude[ampPtr] : -amplitude[ampPtr]), length - soundBufferPos); + memset(buffer + soundBufferPos, (uint8)sample, length - soundBufferPos); soundBufferPos = 0; // Reset soundBufferPos to start of buffer... sampleBase = 0; // & sampleBase... //Ick. This should never happen! //Actually, this probably happens a lot. (?) @@ -218,10 +229,21 @@ Could check current CPU clock, take delta. If delta > 1024, then ... Could add # of cycles in IRQ to lastToggleCycles, then currentPos will be guaranteed to fall within acceptable limits. + +This *should* work, but if the IRQ isn't scheduled & etc, could screw timing up. +Need to have a way to suspend IRQ thread as well as CPU thread when in the GUI, +for example + +Another method would be to add to lastToggleCycles on every timeslice of the CPU, +just like we used to. + +Or, run the CPU for CYCLES_PER_SAMPLE and take a sample, then copy the buffer over +at the end of the timeslice. That way, we could just fill the buffer and let the +IRQ handle draining it. No muss, no fuss. */ - if (currentPos > SOUND_BUFFER_SIZE - 1) + if ((soundBufferPos + currentPos) > (SOUND_BUFFER_SIZE - 1)) { #if 0 WriteLog("ToggleSpeaker() about to go into spinlock at time: %08X (%u) (sampleBase=%u)!\n", time, time, sampleBase); @@ -242,23 +264,30 @@ Seems like it's OK now that I've fixed the buffer-less-than-length bug... // SDL_CondWait(conditional, mutex); // SDL_LockAudio(); // Hm. +// This might not empty the buffer enough, causing hash and trash. !!! FIX !!! SDL_mutexV(mutex2);//Release it so sound thread can get it, SDL_CondWait(conditional, mutex);//Sleep/wait for the sound thread SDL_mutexP(mutex2);//Re-lock it until we're done with it... - currentPos = sampleBase + (uint32)((double)elapsedCycles / CYCLES_PER_SAMPLE); +// currentPos = sampleBase + (uint32)((double)deltaCycles / CYCLES_PER_SAMPLE); + currentPos = (uint32)((double)deltaCycles / CYCLES_PER_SAMPLE); #if 0 WriteLog("--> after spinlock (sampleBase=%u)...\n", sampleBase); #endif } - int8 sample = (speakerState ? amplitude[ampPtr] : -amplitude[ampPtr]); + sample = (speakerState ? amplitude[ampPtr] : -amplitude[ampPtr]); + + // currentPos is position from "zero" or soundBufferPos... + currentPos += soundBufferPos; while (soundBufferPos < currentPos) soundBuffer[soundBufferPos++] = (uint8)sample; // This is done *after* in case the buffer had a long dead spot (I think...) speakerState = !speakerState; + sample = (speakerState ? amplitude[ampPtr] : -amplitude[ampPtr]); + lastToggleCycles = elapsedCycles; SDL_mutexV(mutex2); // SDL_UnlockAudio(); } @@ -275,6 +304,88 @@ void AddToSoundTimeBase(uint32 cycles) // SDL_UnlockAudio(); } +void AdjustLastToggleCycles(uint64 elapsedCycles) +{ +#if 0 + if (!soundInitialized) + return; + + SDL_mutexP(mutex2); + lastToggleCycles += elapsedCycles; + SDL_mutexV(mutex2); + +// We should also fill the buffer here as well, even if the speaker +// didn't toggle... !!! FIX !!! +#else +/* +BOOKKEEPING + +We need to know the following: + + o Where in the sound buffer the base or "zero" time is + o At what CPU timestamp the speaker was last toggled + NOTE: we keep things "right" by advancing this number every frame, even + if nothing happened! That way, we can keep track without having + to detect whether or not several frames have gone by without any + activity. + +How to do it: + +Every time the speaker is toggled, we move the base or "zero" time to the +current spot in the buffer. We also backfill the buffer up to that point with +the old toggle value. The next time the speaker is toggled, we measure the +difference in time between the last time it was toggled (the "zero") and now, +and repeat the cycle. + +We handle dead spots by backfilling the buffer with the current toggle value +every frame--this way we don't have to worry about keeping current time and +crap like that. So, we have to move the "zero" the right amount, just like +in ToggleSpeaker(), and backfill only without toggling. +*/ +#warning "This is VERY similar to ToggleSpeaker(); merge into common function. !!! FIX !!!" + if (!soundInitialized) + return; + +#ifdef DEBUG +printf("SOUND: AdjustLastToggleCycles() start...\n"); +#endif + // Step 1: Calculate delta time + uint64 deltaCycles = elapsedCycles - lastToggleCycles; + + // Step 2: Calculate new buffer position + uint32 currentPos = (uint32)((double)deltaCycles / CYCLES_PER_SAMPLE); + + // Step 3: Make sure there's room for it + // We need to lock since we touch both soundBuffer and soundBufferPos + SDL_mutexP(mutex2); + while ((soundBufferPos + currentPos) > (SOUND_BUFFER_SIZE - 1)) + { + // Hm. + // This might not empty the buffer enough, causing hash and trash. !!! FIX !!! [DONE] + SDL_mutexV(mutex2);//Release it so sound thread can get it, + SDL_CondWait(conditional, mutex);//Sleep/wait for the sound thread + SDL_mutexP(mutex2);//Re-lock it until we're done with it... + +//HMM, this doesn't need to lock or recalculate this value +// currentPos = (uint32)((double)deltaCycles / CYCLES_PER_SAMPLE); + } + + // Step 4: Backfill and adjust lastToggleCycles + // currentPos is position from "zero" or soundBufferPos... + currentPos += soundBufferPos; + + // Backfill with current toggle state + while (soundBufferPos < currentPos) + soundBuffer[soundBufferPos++] = (uint8)sample; + + SDL_mutexV(mutex2); + lastToggleCycles = elapsedCycles; +#ifdef DEBUG +printf("SOUND: AdjustLastToggleCycles() end...\n"); +#endif +#endif +} + void VolumeUp(void) { // Currently set for 8-bit samples diff --git a/src/sound.h b/src/sound.h index 9983eaa..cbf6702 100755 --- a/src/sound.h +++ b/src/sound.h @@ -18,7 +18,8 @@ void SoundInit(void); void SoundDone(void); void ToggleSpeaker(uint64 elapsedCycles); -void AddToSoundTimeBase(uint64 cycles); +//void AddToSoundTimeBase(uint64 cycles); +void AdjustLastToggleCycles(uint64 elapsedCycles); void VolumeUp(void); void VolumeDown(void); uint8 GetVolume(void); -- 2.37.2