From: Shamus Hammons Date: Wed, 11 Sep 2013 15:00:36 +0000 (-0500) Subject: Convert sound driving method to direct sampling. X-Git-Url: http://shamusworld.gotdns.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=a2e007c1e012426f3fe8bc48bf8d6ee934420214;p=apple2 Convert sound driving method to direct sampling. Before it was doing some complex timestamp thing, now it simple runs the CPU for ~21 cycles and then stuff the sample into the sound buffer. Sound still lags behind by several frames though, just like with the previous sound setup. Still not sure why. :-/ --- diff --git a/src/apple2.cpp b/src/apple2.cpp index 0c6880f..2a1858e 100755 --- a/src/apple2.cpp +++ b/src/apple2.cpp @@ -113,6 +113,7 @@ static SDL_sem * mainSem = NULL; static bool cpuFinished = false; static bool cpuSleep = false; + // Let's try a thread... /* Here's how it works: Execute 1 frame's worth, then sleep. @@ -141,6 +142,9 @@ WriteLog("CPU: SDL_SemWait(mainSem);\n"); #endif SDL_SemWait(mainSem); +// There are exactly 800 slices of 21.333 cycles per frame, so it works out +// evenly. +#if 0 uint32_t cycles = 17066; #ifdef CPU_THREAD_OVERFLOW_COMPENSATION // ODD! It's closer *without* this overflow compensation. ??? WHY ??? @@ -164,6 +168,26 @@ WriteLog("CPU: Execute65C02(&mainCPU, cycles);\n"); WriteLog("CPU: AdjustLastToggleCycles(mainCPU.clock);\n"); #endif AdjustLastToggleCycles(mainCPU.clock); +#else +#ifdef THREAD_DEBUGGING +WriteLog("CPU: Execute65C02(&mainCPU, cycles);\n"); +#endif + for(int i=0; i<800; i++) + { + uint32_t cycles = 21; + overflow += 0.333333334; + + if (overflow > 1.0) + { + cycles++; + overflow -= 1.0; + } + + Execute65C02(&mainCPU, cycles); + WriteSampleToBuffer(); + } +#endif + #ifdef THREAD_DEBUGGING WriteLog("CPU: SDL_mutexP(cpuMutex);\n"); @@ -202,6 +226,7 @@ WriteLog("CPU: SDL_mutexV(cpuMutex);\n"); } #endif + // Test GUI function Element * TestWindow(void) @@ -212,6 +237,7 @@ Element * TestWindow(void) return win; } + Element * QuitEmulator(void) { gui->Stop(); @@ -220,6 +246,7 @@ Element * QuitEmulator(void) return NULL; } + /* Small Apple II memory map: @@ -845,6 +872,7 @@ if (addr >= 0xD000 && addr <= 0xD00F) ram[addr] = b; } + // // Load a file into RAM/ROM image space // @@ -861,15 +889,18 @@ bool LoadImg(char * filename, uint8_t * ram, int size) return true; } + static void SaveApple2State(const char * filename) { } + static bool LoadApple2State(const char * filename) { return false; } + #ifdef CPU_CLOCK_CHECKING uint8_t counter = 0; uint32_t totalCPU = 0; @@ -996,9 +1027,9 @@ memcpy(ram + 0xD000, ram + 0xC000, 0x1000); #ifdef THREADED_65C02 cpuCond = SDL_CreateCond(); + mainSem = SDL_CreateSemaphore(1); cpuThread = SDL_CreateThread(CPUThreadFunc, NULL, NULL); //Hmm... CPU does POST (+1), wait, then WAIT (-1) - mainSem = SDL_CreateSemaphore(1); // SDL_sem * mainMutex = SDL_CreateMutex(); #endif @@ -1068,6 +1099,7 @@ floppyDrive.SaveImage(); return 0; } + /* Apple II keycodes ----------------- @@ -1300,12 +1332,14 @@ if (counter == 60) #endif } + static void BlinkTimer(void) { flash = !flash; SetCallbackTime(BlinkTimer, 250000); // Set up blinking at 1/4 sec intervals } + /* Next problem is this: How to have events occur and synchronize with the rest of the threads? diff --git a/src/log.cpp b/src/log.cpp index 4a01f80..55d99b7 100755 --- a/src/log.cpp +++ b/src/log.cpp @@ -23,7 +23,6 @@ static FILE * log_stream = NULL; static uint32_t logSize = 0; -static bool logDone = false; bool InitLog(const char * path) @@ -50,7 +49,7 @@ void LogDone(void) // void WriteLog(const char * text, ...) { - if (!log_stream || logDone) + if (!log_stream) return; va_list arg; @@ -65,7 +64,6 @@ void WriteLog(const char * text, ...) { fclose(log_stream); log_stream = NULL; - logDone = true; } } diff --git a/src/sound.cpp b/src/sound.cpp index cd153df..fd6754d 100755 --- a/src/sound.cpp +++ b/src/sound.cpp @@ -61,6 +61,7 @@ // Local variables static SDL_AudioSpec desired, obtained; +static SDL_AudioDeviceID device; static bool soundInitialized = false; static bool speakerState = false; static int16_t soundBuffer[SOUND_BUFFER_SIZE]; @@ -70,7 +71,7 @@ static SDL_cond * conditional = NULL; static SDL_mutex * mutex = NULL; static SDL_mutex * mutex2 = NULL; static int16_t sample; -static uint8_t ampPtr = 14; // Start with -16 - +16 +static uint8_t ampPtr = 12; // Start with -2047 - +2047 static int16_t amplitude[17] = { 0, 1, 2, 3, 7, 15, 31, 63, 127, 255, 511, 1023, 2047, 4095, 8191, 16383, 32767 }; #ifdef WRITE_OUT_WAVE @@ -81,6 +82,7 @@ static FILE * fp = NULL; static void SDLSoundCallback(void * userdata, Uint8 * buffer, int length); + // // Initialize the SDL sound system // @@ -90,20 +92,16 @@ void SoundInit(void) // To weed out problems for now... return; #endif - + SDL_zero(desired); desired.freq = SAMPLE_RATE; // SDL will do conversion on the fly, if it can't get the exact rate. Nice! -// desired.format = AUDIO_S8; // This uses the native endian (for portability)... desired.format = AUDIO_S16SYS; // This uses the native endian (for portability)... desired.channels = 1; -// desired.samples = 4096; // Let's try a 4K buffer (can always go lower) -// desired.samples = 2048; // Let's try a 2K buffer (can always go lower) -// desired.samples = 1024; // Let's try a 1K buffer (can always go lower) desired.samples = 512; // Let's try a 1/2K buffer (can always go lower) desired.callback = SDLSoundCallback; -// if (SDL_OpenAudio(&desired, NULL) < 0) // NULL means SDL guarantees what we want -//When doing it this way, we need to check to see if we got what we asked for... - if (SDL_OpenAudio(&desired, &obtained) < 0) + device = SDL_OpenAudioDevice(NULL, 0, &desired, &obtained, 0); + + if (device == 0) { WriteLog("Sound: Failed to initialize SDL sound.\n"); return; @@ -116,7 +114,7 @@ return; lastToggleCycles = 0; sample = desired.silence; // ? wilwok ? yes - SDL_PauseAudio(false); // Start playback! + SDL_PauseAudioDevice(device, 0); // Start playback! soundInitialized = true; WriteLog("Sound: Successfully initialized.\n"); @@ -125,6 +123,7 @@ return; #endif } + // // Close down the SDL sound subsystem // @@ -132,8 +131,10 @@ void SoundDone(void) { if (soundInitialized) { - SDL_PauseAudio(true); - SDL_CloseAudio(); +// SDL_PauseAudio(true); + SDL_PauseAudioDevice(device, 1); +// SDL_CloseAudio(); + SDL_CloseAudioDevice(device); SDL_DestroyCond(conditional); SDL_DestroyMutex(mutex); SDL_DestroyMutex(mutex2); @@ -145,11 +146,13 @@ void SoundDone(void) } } + // // Sound card callback handler // -static void SDLSoundCallback(void * userdata, Uint8 * buffer8, int length8) +static void SDLSoundCallback(void * /*userdata*/, Uint8 * buffer8, int length8) { +//WriteLog("SDLSoundCallback(): begin (soundBufferPos=%i)\n", soundBufferPos); // The sound buffer should only starve when starting which will cause it to // lag behind the emulation at most by around 1 frame... // (Actually, this should never happen since we fill the buffer beforehand.) @@ -159,12 +162,14 @@ static void SDLSoundCallback(void * userdata, Uint8 * buffer8, int length8) // Let's try using a mutex for shared resource consumption... //Actually, I think Lock/UnlockAudio() does this already... +//WriteLog("SDLSoundCallback(): SDL_mutexP(mutex2)\n"); SDL_mutexP(mutex2); // Recast this as a 16-bit type... int16_t * buffer = (int16_t *)buffer8; uint32_t length = (uint32_t)length8 / 2; +//WriteLog("SDLSoundCallback(): filling buffer...\n"); if (soundBufferPos < length) // The sound buffer is starved... { for(uint32_t i=0; i= (SOUND_BUFFER_SIZE - 1)) + { +//WriteLog("WriteSampleToBuffer(): Waiting for sound thread. soundBufferPos=%i, SOUNDBUFFERSIZE-1=%i\n", soundBufferPos, SOUND_BUFFER_SIZE-1); + SDL_mutexV(mutex2); // Release it so sound thread can get it, + SDL_mutexP(mutex); // Must lock the mutex for the cond to work properly... + SDL_CondWait(conditional, mutex); // Sleep/wait for the sound thread + SDL_mutexV(mutex); // Must unlock the mutex for the cond to work properly... + SDL_mutexP(mutex2); // Re-lock it until we're done with it... + } + + soundBuffer[soundBufferPos++] = sample; +//WriteLog("WriteSampleToBuffer(): SDL_mutexV(mutex2)\n"); + SDL_mutexV(mutex2); +} + + // Need some interface functions here to take care of flipping the // waveform at the correct time in the sound stream... @@ -222,6 +255,7 @@ void HandleBuffer(uint64_t elapsedCycles) // 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)) { SDL_mutexV(mutex2); // Release it so sound thread can get it, @@ -240,7 +274,7 @@ void HandleBuffer(uint64_t elapsedCycles) #endif // Backfill with current toggle state while (soundBufferPos < currentPos) - soundBuffer[soundBufferPos++] = (uint16_t)sample; + soundBuffer[soundBufferPos++] = sample; #ifdef WRITE_OUT_WAVE fwrite(&soundBuffer[sbpSave], sizeof(int16_t), currentPos - sbpSave, fp); @@ -250,16 +284,18 @@ void HandleBuffer(uint64_t elapsedCycles) lastToggleCycles = elapsedCycles; } + void ToggleSpeaker(uint64_t elapsedCycles) { if (!soundInitialized) return; - HandleBuffer(elapsedCycles); +// HandleBuffer(elapsedCycles); speakerState = !speakerState; sample = (speakerState ? amplitude[ampPtr] : -amplitude[ampPtr]); } + void AdjustLastToggleCycles(uint64_t elapsedCycles) { if (!soundInitialized) @@ -292,20 +328,22 @@ in ToggleSpeaker(), and backfill only without toggling. HandleBuffer(elapsedCycles); } + void VolumeUp(void) { - // Currently set for 8-bit samples - // Now 16 + // Currently set for 16-bit samples if (ampPtr < 16) ampPtr++; } + void VolumeDown(void) { if (ampPtr > 0) ampPtr--; } + uint8_t GetVolume(void) { return ampPtr; diff --git a/src/sound.h b/src/sound.h index 1cea703..af06af4 100755 --- a/src/sound.h +++ b/src/sound.h @@ -18,6 +18,7 @@ void SoundInit(void); void SoundDone(void); void ToggleSpeaker(uint64_t elapsedCycles); +void WriteSampleToBuffer(void); //void AddToSoundTimeBase(uint64_t cycles); void AdjustLastToggleCycles(uint64_t elapsedCycles); void VolumeUp(void); diff --git a/src/v65c02.cpp b/src/v65c02.cpp index 5ef1709..216a0af 100755 --- a/src/v65c02.cpp +++ b/src/v65c02.cpp @@ -2807,6 +2807,7 @@ void (* exec_op[256])() = { OpF0, OpF1, OpF2, Op__, Op__, OpF5, OpF6, OpF7, OpF8, OpF9, OpFA, Op__, Op__, OpFD, OpFE, OpFF }; + // // Internal "memcpy" (so we don't have to link with any external libraries!) // @@ -3019,14 +3020,10 @@ WriteLog("\n*** IRQ ***\n\n"); } } -//This is a lame way of doing it, but in the end the simplest--however, it destroys any -//record of elasped CPU time. Not sure that it's important to keep track, but there it is. -// Now we use a 64-bit integer, so it won't wrap for about 500 millenia. ;-) -// regs.clock -= cycles; - myMemcpy(context, ®s, sizeof(V65C02REGS)); } + // // Get the clock of the currently executing CPU // @@ -3034,3 +3031,4 @@ uint64_t GetCurrentV65C02Clock(void) { return regs.clock; } +