From e15db839e34218974b897baf8f1de36172da03b5 Mon Sep 17 00:00:00 2001 From: Jorj Bauer Date: Wed, 20 Feb 2019 17:49:51 -0500 Subject: [PATCH] rebuilt SDL speaker driver --- physicalspeaker.h | 4 + sdl/aiie.cpp | 57 +++-------- sdl/sdl-speaker.cpp | 230 ++++++++++++++++++++++++-------------------- sdl/sdl-speaker.h | 10 +- 4 files changed, 149 insertions(+), 152 deletions(-) diff --git a/physicalspeaker.h b/physicalspeaker.h index b8f5f6b..1967992 100644 --- a/physicalspeaker.h +++ b/physicalspeaker.h @@ -7,11 +7,15 @@ class PhysicalSpeaker { public: virtual ~PhysicalSpeaker() {} + virtual void begin() = 0; + virtual void toggle(uint32_t c) = 0; virtual void maintainSpeaker(uint32_t c, uint64_t microseconds) = 0; virtual void beginMixing() = 0; virtual void mixOutput(uint8_t v) = 0; + virtual uint32_t bufferedContentSize() = 0; + }; #endif diff --git a/sdl/aiie.cpp b/sdl/aiie.cpp index 82773e7..e5c2dc7 100644 --- a/sdl/aiie.cpp +++ b/sdl/aiie.cpp @@ -27,7 +27,7 @@ BIOS bios; Debugger debugger; -static struct timespec nextInstructionTime, startTime; +struct timespec nextInstructionTime, startTime; #define NB_ENABLE 1 #define NB_DISABLE 0 @@ -91,8 +91,6 @@ void write(void *arg, uint16_t address, uint8_t v) static void *cpu_thread(void *dummyptr) { struct timespec currentTime; - struct timespec nextSpeakerCycleTime; - uint32_t nextSpeakerCycle = 0; #if 0 int policy; @@ -109,10 +107,9 @@ static void *cpu_thread(void *dummyptr) { printf("free-running\n"); - // In this loop, we determine when the next CPU or Speaker event is; - // sleep until that event; and then perform the event. There are a - // number of maintenance tasks that also happen to be sure that - // peripherals are updated appropriately. + // In this loop, we determine when the next CPU event is; sleep until + // that event; and then perform the event. There are also peripheral + // maintenance calls embedded in the loop... while (1) { if (g_biosInterrupt) { @@ -140,55 +137,24 @@ static void *cpu_thread(void *dummyptr) { do_gettime(¤tTime); - // FIXME: these first two can go in their respective loops after execution - - // Determine the next speaker runtime (nextSpeakerCycle). - // The speaker runs 48 cycles behind the CPU (an arbitrary number). - timespec_add_cycles(&startTime, nextSpeakerCycle-48, &nextSpeakerCycleTime); - // Determine the next CPU runtime (nextInstructionTime) timespec_add_cycles(&startTime, g_cpu->cycles, &nextInstructionTime); - // Sleep until one of them is ready to run. + // Sleep until the CPU is ready to run. // tsSubtract doesn't return negatives; it bounds at zero. So if // either result is zero then it's time to run something. struct timespec cpudiff = tsSubtract(nextInstructionTime, currentTime); - struct timespec spkrdiff = tsSubtract(nextSpeakerCycleTime, currentTime); - struct timespec mindiff; - if (cpudiff.tv_sec < spkrdiff.tv_sec) { - memcpy(&mindiff, &cpudiff, sizeof(struct timespec)); - } else if (spkrdiff.tv_sec < cpudiff.tv_sec) { - memcpy(&mindiff, &spkrdiff, sizeof(struct timespec)); - } else if (cpudiff.tv_nsec < spkrdiff.tv_nsec) { - memcpy(&mindiff, &cpudiff, sizeof(struct timespec)); - } else { - memcpy(&mindiff, &spkrdiff, sizeof(struct timespec)); - } - - if (mindiff.tv_sec > 0 || mindiff.tv_nsec > 0) { - // Sleep until the first of them is ready & loop... - nanosleep(&mindiff, NULL); + if (cpudiff.tv_sec > 0 || cpudiff.tv_nsec > 0) { + // Sleep until the it's ready and loop... + nanosleep(&cpudiff, NULL); continue; } - // Now we know either the speaker or the CPU is ready to - // run. Figure out which and run it. - - if (spkrdiff.tv_sec == 0 && spkrdiff.tv_nsec == 0) { - // Run the speaker - - uint64_t microseconds = nextSpeakerCycleTime.tv_sec * 1000000 + - (double)nextSpeakerCycleTime.tv_nsec / 1000.0; - g_speaker->maintainSpeaker(nextSpeakerCycle-48, microseconds); - - nextSpeakerCycle++; - } - if (cpudiff.tv_sec == 0 && cpudiff.tv_nsec == 0) { - // Run the CPU + // Run the CPU; it's caught up to "real time" uint8_t executed = 0; if (debugger.active()) { @@ -288,6 +254,8 @@ int main(int argc, char *argv[]) // pthread_setschedparam(cpuThreadID, SCHED_RR, PTHREAD_MAX_PRIORITY); } + g_speaker->begin(); + uint32_t lastCycleCount = -1; while (1) { @@ -311,8 +279,7 @@ int main(int argc, char *argv[]) do_gettime(&startTime); do_gettime(&nextInstructionTime); - // Drain the speaker queue (FIXME: a little hacky) - g_speaker->maintainSpeaker(-1, -1); + // FIXME: drain whatever's in the speaker queue /* FIXME // Force the display to redraw diff --git a/sdl/sdl-speaker.cpp b/sdl/sdl-speaker.cpp index e7c4053..f529366 100644 --- a/sdl/sdl-speaker.cpp +++ b/sdl/sdl-speaker.cpp @@ -12,26 +12,37 @@ extern "C" #include "timeutil.h" +// FIXME: 4096 is the right value here, I'm just debugging +#define SDLSIZE (4096) + // FIXME: Globals; ick. static volatile uint32_t bufIdx = 0; -static uint8_t soundBuf[44100]; // 1 second of audio -static pthread_mutex_t sndmutex = PTHREAD_MUTEX_INITIALIZER; +static uint8_t soundBuf[44100]; static pthread_mutex_t togmutex = PTHREAD_MUTEX_INITIALIZER; +static struct timespec sdlEmptyTime, sdlStartTime; +extern struct timespec startTime; // defined in aiie (main) static void audioCallback(void *unused, Uint8 *stream, int len) { - pthread_mutex_lock(&sndmutex); - + pthread_mutex_lock(&togmutex); if (g_biosInterrupt) { // While the BIOS is running, we don't put samples in the audio // queue. memset(stream, 0x80, len); - pthread_mutex_unlock(&sndmutex); + pthread_mutex_unlock(&togmutex); return; } + // calculate when the buffer will be empty again + do_gettime(&sdlEmptyTime); + timespec_add_us(&sdlEmptyTime, ((float)len * (float)1000000)/(float)44100, &sdlEmptyTime); + sdlEmptyTime = tsSubtract(sdlEmptyTime, sdlStartTime); + + static uint8_t lastKnownSample = 0; // saved for when the apple is quiescent + if (bufIdx >= len) { memcpy(stream, soundBuf, len); + lastKnownSample = stream[len-1]; if (bufIdx > len) { // move the remaining data down @@ -39,17 +50,20 @@ static void audioCallback(void *unused, Uint8 *stream, int len) bufIdx -= len; } } else { - // Audio underrun - static uint8_t occurrenceCount = 0; - if (++occurrenceCount < 10) { - printf("Audio underrun!\n"); - if (occurrenceCount == 9) { - printf(" (Suppressing further audio errors)\n"); - } + if (bufIdx) { + // partial buffer exists + memcpy(stream, soundBuf, bufIdx); + + // and it's a partial underrun + memset(&stream[bufIdx], lastKnownSample, len-bufIdx); + bufIdx = 0; + } else { + // Total audio underrun. This is normal if nothing is toggling the + // speaker; we stay at the last known level. + memset(stream, lastKnownSample, len); } - memset(stream, 0, len); } - pthread_mutex_unlock(&sndmutex); + pthread_mutex_unlock(&togmutex); } void ResetDCFilter(); // FIXME: remove @@ -59,10 +73,7 @@ SDLSpeaker::SDLSpeaker() toggleState = false; mixerValue = 0x80; - toggleCount = toggleReadPtr = toggleWritePtr = 0; - pthread_mutex_init(&togmutex, NULL); - pthread_mutex_init(&sndmutex, NULL); _init_darwin_shim(); @@ -70,48 +81,112 @@ SDLSpeaker::SDLSpeaker() lastCycleCount = 0; lastSampleCount = 0; - - SDL_AudioSpec audioDevice; - SDL_AudioSpec audioActual; - SDL_memset(&audioDevice, 0, sizeof(audioDevice)); - audioDevice.freq = 44100; - audioDevice.format = AUDIO_U8; - audioDevice.channels = 1; - audioDevice.samples = 4096; // 4096 bytes @ 44100Hz is about 1/10th second out of sync - should be okay for this testing - audioDevice.callback = audioCallback; - audioDevice.userdata = NULL; - - SDL_OpenAudio(&audioDevice, &audioActual); // FIXME retval - printf("Actual: freq %d channels %d samples %d\n", - audioActual.freq, audioActual.channels, audioActual.samples); - - SDL_PauseAudio(0); } SDLSpeaker::~SDLSpeaker() { } +void SDLSpeaker::begin() +{ + do_gettime(&sdlStartTime); + do_gettime(&sdlEmptyTime); + sdlEmptyTime = tsSubtract(sdlEmptyTime, sdlStartTime); + + SDL_AudioSpec audioDevice; + SDL_AudioSpec audioActual; + SDL_memset(&audioDevice, 0, sizeof(audioDevice)); + audioDevice.freq = 44100; // count of 8-bit samples + audioDevice.format = AUDIO_U8; + audioDevice.channels = 1; + audioDevice.samples = SDLSIZE; // SDLSIZE 8-bit samples @ 44100Hz: 4096 is about 1/10th second out of sync + audioDevice.callback = audioCallback; + audioDevice.userdata = NULL; + + memset(&soundBuf[0], 0, SDLSIZE); + bufIdx = SDLSIZE/2; + + SDL_OpenAudio(&audioDevice, &audioActual); // FIXME retval + printf("Actual: freq %d channels %d samples %d\n", + audioActual.freq, audioActual.channels, audioActual.samples); + SDL_PauseAudio(0); +} + void SDLSpeaker::toggle(uint32_t c) { pthread_mutex_lock(&togmutex); - toggleTimes[toggleWritePtr] = c; - if (toggleCount < SPEAKERQUEUESIZE-1) { - toggleWritePtr++; - if (toggleWritePtr >= SPEAKERQUEUESIZE) - toggleWritePtr = 0; - toggleCount++; - } else { - printf("speaker overflow @ cycle %d\n", c); - for (int i=0; i= sizeof(soundBuf)) { + // Buffer overrun + printf("ERROR: buffer overrun, dropping data\n"); + newIdx = sizeof(soundBuf)-1; } + + // Flip the toggle state + toggleState = !toggleState; + + // Fill from bufIdx .. newIdx and set bufIdx to newIdx when done + if (newIdx > bufIdx) { + long count = (long)newIdx - bufIdx; + memset(&soundBuf[bufIdx], toggleState ? 127 : 0, count); + bufIdx = newIdx; + } else { + // Why are we backtracking? This does happen, and it's a bug. + if (newIdx >= 1) { + bufIdx = newIdx-1; + long count = (long)newIdx - bufIdx; + memset(&soundBuf[bufIdx], toggleState ? 127 : 0, count); + bufIdx = newIdx; + } else { + // ... and it's zero? + } + } + pthread_mutex_unlock(&togmutex); } @@ -139,60 +214,6 @@ int16_t DCFilter(int16_t in) void SDLSpeaker::maintainSpeaker(uint32_t c, uint64_t microseconds) { - bool didChange = false; - - pthread_mutex_lock(&togmutex); - - if (c == -1 && microseconds == -1) { - // flushing - printf("Flush sound output\n"); - toggleReadPtr = toggleWritePtr = 0; - toggleCount = 0; - } else { - while (toggleCount && c >= toggleTimes[toggleReadPtr]) { - // Override the mixer with a 1-bit "Terribad" audio sample change - toggleState = !toggleState; - toggleCount--; - toggleReadPtr++; - if (toggleReadPtr >= SPEAKERQUEUESIZE) - toggleReadPtr = 0; - didChange = true; - } - } - - pthread_mutex_unlock(&togmutex); - - // FIXME: removed all the mixing code - - // Add samples from the last time to this time - // mixerValue = (toggleState ? 0x1FF : 0x00); - mixerValue = (toggleState ? 0x00 : ~0x80); - // FIXME: DC filter isn't correct yet - // mixerValue = DCFilter(mixerValue); - - uint64_t sampleCount = (microseconds * 44100) / 1000000; - uint64_t numSamples = sampleCount - lastSampleCount; - - if (numSamples) { - lastSampleCount = sampleCount; - - pthread_mutex_lock(&sndmutex); - - if (bufIdx + numSamples >= sizeof(soundBuf)) { - static uint8_t errcnt = 0; - if (++errcnt <= 10) { - printf("Sound overrun!\n"); - } - numSamples = sizeof(soundBuf) - bufIdx - 1; - } - - mixerValue >>= (8-(g_volume/2)); - - memset(&soundBuf[bufIdx], mixerValue, numSamples); - bufIdx += numSamples; - pthread_mutex_unlock(&sndmutex); - } - } void SDLSpeaker::beginMixing() @@ -202,3 +223,8 @@ void SDLSpeaker::beginMixing() void SDLSpeaker::mixOutput(uint8_t v) { } + +uint32_t SDLSpeaker::bufferedContentSize() +{ + return bufIdx; +} diff --git a/sdl/sdl-speaker.h b/sdl/sdl-speaker.h index f85efe7..8d4010b 100644 --- a/sdl/sdl-speaker.h +++ b/sdl/sdl-speaker.h @@ -12,19 +12,19 @@ class SDLSpeaker : public PhysicalSpeaker { SDLSpeaker(); virtual ~SDLSpeaker(); + virtual void begin(); + virtual void toggle(uint32_t c); virtual void maintainSpeaker(uint32_t c, uint64_t microseconds); virtual void beginMixing(); virtual void mixOutput(uint8_t v); + + virtual uint32_t bufferedContentSize(); + private: uint8_t mixerValue; bool toggleState; - uint32_t toggleTimes[SPEAKERQUEUESIZE]; - uint8_t toggleCount; // # of entries still in queue - uint8_t toggleReadPtr; // ring buffer pointer in queue - uint8_t toggleWritePtr; // ring buffer pointer in queue - uint64_t lastCycleCount; uint64_t lastSampleCount; };