From 81fb36789f95822001138268a99a6f735b1d9e96 Mon Sep 17 00:00:00 2001 From: Jorj Bauer Date: Tue, 2 Jan 2018 20:28:47 -0500 Subject: [PATCH] audio overhaul; added video-or-audio priority for Teensy (can't do both) --- apple/applemmu.cpp | 6 + cpu.cpp | 12 +- cpu.h | 4 + physicalspeaker.h | 2 +- sdl/aiie.cpp | 262 +++++++++++++++++--------------------- sdl/sdl-speaker.cpp | 156 ++++++++++++----------- sdl/sdl-speaker.h | 8 +- sdl/timeutil.h | 2 +- teensy/bios.cpp | 19 ++- teensy/teensy-display.h | 14 +- teensy/teensy-speaker.cpp | 42 ++---- teensy/teensy-speaker.h | 10 +- teensy/teensy.ino | 53 +++++--- 13 files changed, 287 insertions(+), 303 deletions(-) diff --git a/apple/applemmu.cpp b/apple/applemmu.cpp index 34d4152..37a2635 100644 --- a/apple/applemmu.cpp +++ b/apple/applemmu.cpp @@ -475,6 +475,9 @@ uint8_t AppleMMU::readSwitches(uint16_t address) case 0xC030: // SPEAKER g_speaker->toggle(g_cpu->cycles); + g_cpu->realtime(); // cause the CPU to stop processing its outer + // loop b/c the speaker might need attention + // immediately break; case 0xC050: // CLRTEXT @@ -619,6 +622,9 @@ void AppleMMU::writeSwitches(uint16_t address, uint8_t v) // Writes toggle the speaker twice g_speaker->toggle(g_cpu->cycles); g_speaker->toggle(g_cpu->cycles); + g_cpu->realtime(); // cause the CPU to stop processing its outer + // loop b/c the speaker might need attention + // immediately break; case 0xC050: // graphics mode diff --git a/cpu.cpp b/cpu.cpp index 1c36796..294d81b 100644 --- a/cpu.cpp +++ b/cpu.cpp @@ -511,7 +511,9 @@ void Cpu::Reset() sp = 0xFD; - cycles = 6; // according to the datasheet, the reset routine takes 6 clock cycles + cycles = 6; // according to the datasheet, the reset routine takes 6 clock cycles + + realtimeProcessing = false; } void Cpu::nmi() @@ -582,7 +584,8 @@ void Cpu::irq() uint8_t Cpu::Run(uint8_t numSteps) { uint8_t runtime = 0; - while (runtime < numSteps) { + realtimeProcessing = false; + while (runtime < numSteps && !realtimeProcessing) { runtime += step(); } return runtime; @@ -1277,3 +1280,8 @@ void Cpu::stageIRQ() { irqPending = true; } + +void Cpu::realtime() +{ + realtimeProcessing = true; +} diff --git a/cpu.h b/cpu.h index ba04dcc..0aea6ae 100644 --- a/cpu.h +++ b/cpu.h @@ -60,6 +60,8 @@ class Cpu { public: void SetMMU(MMU *mmu) { this->mmu = mmu; } + void realtime(); + public: uint16_t pc; uint8_t sp; @@ -73,6 +75,8 @@ class Cpu { bool irqPending; MMU *mmu; + + bool realtimeProcessing; }; diff --git a/physicalspeaker.h b/physicalspeaker.h index 3203d5a..b8f5f6b 100644 --- a/physicalspeaker.h +++ b/physicalspeaker.h @@ -8,7 +8,7 @@ class PhysicalSpeaker { virtual ~PhysicalSpeaker() {} virtual void toggle(uint32_t c) = 0; - virtual void maintainSpeaker(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; diff --git a/sdl/aiie.cpp b/sdl/aiie.cpp index 4da494e..c2a35b8 100644 --- a/sdl/aiie.cpp +++ b/sdl/aiie.cpp @@ -36,6 +36,8 @@ char disk2name[256] = "\0"; volatile bool wantSuspend = false; volatile bool wantResume = false; +volatile uint64_t hitcount = 0, misscount = 0; + void sigint_handler(int n) { send_rst = 1; @@ -78,6 +80,8 @@ void write(void *arg, uint16_t address, uint8_t v) static void *cpu_thread(void *dummyptr) { struct timespec currentTime; + struct timespec nextCycleTime; + uint32_t nextSpeakerCycle = 0; #if 0 int policy; @@ -109,38 +113,44 @@ static void *cpu_thread(void *dummyptr) { wantResume = false; } - // Would like to do the old nanosleep thing, but the speaker needs - // to run. FIXME: do something more intelligent here - sleep 'til speakertime+1? (Obv. to do this below, not right here) do_gettime(¤tTime); - // tsSubtract doesn't return negatives; it bounds at 0. - struct timespec diff = tsSubtract(nextInstructionTime, currentTime); - // do_gettime(¤tTime); - struct timespec runtime = tsSubtract(currentTime, startTime); - double speakerCycle = cycles_since_time(&runtime); + /* The speaker is our priority. The CPU runs in batches anyway, + sometimes a little behind and sometimes a little ahead; but the + speaker has to be right on time. */ + + // Wait until nextSpeakerCycle + timespec_add_cycles(&startTime, nextSpeakerCycle, &nextCycleTime); + + struct timespec diff = tsSubtract(nextCycleTime, currentTime); + if (diff.tv_sec >= 0 || diff.tv_nsec >= 0) { + hitcount++; + nanosleep(&diff, NULL); + } else { + misscount++; + } + + // Speaker runs 48 cycles behind the CPU (an arbitrary number) + if (nextSpeakerCycle >= 48) { + timespec_add_cycles(&startTime, nextSpeakerCycle-48, &nextCycleTime); + uint64_t microseconds = nextCycleTime.tv_sec * 1000000 + + (double)nextCycleTime.tv_nsec / 1000.0; + g_speaker->maintainSpeaker(nextSpeakerCycle-48, microseconds); + } + + // Bump speaker cycle for next go-round + nextSpeakerCycle++; + + + /* Next up is the CPU. */ + + // tsSubtract doesn't return negatives; it bounds at 0. + diff = tsSubtract(nextInstructionTime, currentTime); uint8_t executed = 0; if (diff.tv_sec == 0 && diff.tv_nsec == 0) { - // okay to run CPU - // If speakerCycle == 0, we're still starting up - // If speakerCycle > cycles, the CPU is running behind; don't bother with that just yet - // If we're about to run the CPU then we *should* have caught up the speaker - how could it possibly be this far out of skew? - if (speakerCycle && speakerCycle < g_cpu->cycles && abs(g_cpu->cycles - speakerCycle) > 24) { -#if 0 - printf("Start time: %lu,%lu\n", startTime.tv_sec, startTime.tv_nsec); - printf("runtime: %lu,%lu\n", runtime.tv_sec, runtime.tv_nsec); - printf("Current time: %lu,%lu\n", currentTime.tv_sec, currentTime.tv_nsec); - printf("Next time: %lu,%lu\n", nextInstructionTime.tv_sec, nextInstructionTime.tv_nsec); - printf("Speaker calc / cycle count: %lf / %d [e %d; d %f]\n", speakerCycle, g_cpu->cycles, executed, abs(g_cpu->cycles - speakerCycle)); -#endif - - // If we're okay to run the CPU, then the speaker should be caught up. Not sure how it wouldn't be. - printf("About to run cpu but speaker diff > 24 - how, exactly?\n"); - exit(1); - } - #ifdef DEBUGCPU - uint8_t executed = g_cpu->Run(1); + executed = g_cpu->Run(1); #else executed = g_cpu->Run(24); #endif @@ -152,130 +162,86 @@ static void *cpu_thread(void *dummyptr) { // clock. That happens from the VM's CPU maintenance poller. ((AppleVM *)g_vm)->cpuMaintenance(g_cpu->cycles); -#if 0 - do_gettime(¤tTime); - printf("Executed %d cycles; count %d; now %lu,%lu; next runtime at %lu,%lu\n", executed, g_cpu->cycles, currentTime.tv_sec, currentTime.tv_nsec, nextInstructionTime.tv_sec, nextInstructionTime.tv_nsec); -#endif - } else { - // printf("delta %lu,%lu\n", diff.tv_sec, diff.tv_nsec); - // printf("Current time: %lu,%lu\n", currentTime.tv_sec, currentTime.tv_nsec); - // printf("Next time: %lu,%lu\n", nextInstructionTime.tv_sec, nextInstructionTime.tv_nsec); - } - - // Run the speaker a short bit delayed, based on real time rather - // than the cpu cycle count - -#if 0 - if (speakerCycle < g_cpu->cycles) { - printf("Start time: %lu,%lu\n", startTime.tv_sec, startTime.tv_nsec); - printf("runtime: %lu,%lu\n", runtime.tv_sec, runtime.tv_nsec); - printf("Current time: %lu,%lu\n", currentTime.tv_sec, currentTime.tv_nsec); - printf("Next time: %lu,%lu\n", nextInstructionTime.tv_sec, nextInstructionTime.tv_nsec); - printf("Speaker calc / cycle count: %lf / %d [e %d; d %f]\n", speakerCycle, g_cpu->cycles, executed, abs(g_cpu->cycles - speakerCycle)); - } -#endif - - int lastdrift = g_cpu->cycles - speakerCycle; - if (speakerCycle && - speakerCycle < g_cpu->cycles && - lastdrift > 64) { - printf("Cycle -> speakercycle drift > 64 [%f]\n", abs(g_cpu->cycles - speakerCycle)); - exit(1); - } - if (speakerCycle == 0) lastdrift = 0; - - g_speaker->maintainSpeaker(speakerCycle-48); - - /* // recalc what the fuck is happening - do_gettime(¤tTime); - sdiff = tsSubtract(currentTime, startTime); - speakerCycle = cycles_since_time(&sdiff); - if (lastdrift && speakerCycle && speakerCycle < g_cpu->cycles && abs(g_cpu->cycles - speakerCycle) > 64) -{ - int newdrift = g_cpu->cycles - speakerCycle; - printf("WTF: was %d, now %d [sc now %f]\n", lastdrift, newdrift, speakerCycle); - exit(1); - }*/ - #ifdef DEBUGCPU - { - uint8_t p = g_cpu->flags; - printf("OP: $%02x A: %02x X: %02x Y: %02x PC: $%04x SP: %02x Flags: %c%cx%c%c%c%c%c\n", - g_vm->getMMU()->read(g_cpu->pc), - g_cpu->a, g_cpu->x, g_cpu->y, g_cpu->pc, g_cpu->sp, - p & (1<<7) ? 'N':' ', - p & (1<<6) ? 'V':' ', - p & (1<<4) ? 'B':' ', - p & (1<<3) ? 'D':' ', - p & (1<<2) ? 'I':' ', - p & (1<<1) ? 'Z':' ', - p & (1<<0) ? 'C':' ' - ); - } + { + uint8_t p = g_cpu->flags; + printf("OP: $%02x A: %02x X: %02x Y: %02x PC: $%04x SP: %02x Flags: %c%cx%c%c%c%c%c\n", + g_vm->getMMU()->read(g_cpu->pc), + g_cpu->a, g_cpu->x, g_cpu->y, g_cpu->pc, g_cpu->sp, + p & (1<<7) ? 'N':' ', + p & (1<<6) ? 'V':' ', + p & (1<<4) ? 'B':' ', + p & (1<<3) ? 'D':' ', + p & (1<<2) ? 'I':' ', + p & (1<<1) ? 'Z':' ', + p & (1<<0) ? 'C':' ' + ); + } #endif - - if (send_rst) { -#if 0 - printf("Scheduling suspend request...\n"); - wantSuspend = true; -#endif -#if 0 - printf("Scheduling resume resume request...\n"); - wantResume = true; -#endif - -#if 0 - printf("Sending reset\n"); - g_cpu->Reset(); - // testing startup keyboard presses - perform Apple //e self-test - //g_vm->getKeyboard()->keyDepressed(RA); - //g_vm->Reset(); - //g_cpu->Reset(); - //((AppleVM *)g_vm)->insertDisk(0, "disks/DIAGS.DSK"); -#endif - + if (send_rst) { #if 0 - // Swap disks - if (disk1name[0] && disk2name[0]) { - printf("Swapping disks\n"); - - printf("Inserting disk %s in drive 1\n", disk2name); - ((AppleVM *)g_vm)->insertDisk(0, disk2name); - printf("Inserting disk %s in drive 2\n", disk1name); - ((AppleVM *)g_vm)->insertDisk(1, disk1name); - } + printf("Scheduling suspend request...\n"); + wantSuspend = true; #endif - #if 0 - MMU *mmu = g_vm->getMMU(); - - printf("PC: 0x%X\n", g_cpu->pc); - for (int i=g_cpu->pc; ipc + 0x100; i++) { - printf("0x%X ", mmu->read(i)); - } - printf("\n"); - - printf("Dropping to monitor\n"); - // drop directly to monitor. - g_cpu->pc = 0xff69; // "call -151" - mmu->read(0xC054); // make sure we're in page 1 - mmu->read(0xC056); // and that hires is off - mmu->read(0xC051); // and text mode is on - mmu->read(0xC08A); // and we have proper rom in place - mmu->read(0xc008); // main zero-page - mmu->read(0xc006); // rom from cards - mmu->write(0xc002 + mmu->read(0xc014)? 1 : 0, 0xff); // make sure aux ram read and write match - mmu->write(0x20, 0); // text window - mmu->write(0x21, 40); - mmu->write(0x22, 0); - mmu->write(0x23, 24); - mmu->write(0x33, '>'); - mmu->write(0x48, 0); // from 0xfb2f: part of text init - */ + printf("Scheduling resume resume request...\n"); + wantResume = true; #endif - - send_rst = 0; + +#if 0 + printf("Sending reset\n"); + g_cpu->Reset(); + + // testing startup keyboard presses - perform Apple //e self-test + //g_vm->getKeyboard()->keyDepressed(RA); + //g_vm->Reset(); + //g_cpu->Reset(); + //((AppleVM *)g_vm)->insertDisk(0, "disks/DIAGS.DSK"); +#endif + +#if 0 + // Swap disks + if (disk1name[0] && disk2name[0]) { + printf("Swapping disks\n"); + + printf("Inserting disk %s in drive 1\n", disk2name); + ((AppleVM *)g_vm)->insertDisk(0, disk2name); + printf("Inserting disk %s in drive 2\n", disk1name); + ((AppleVM *)g_vm)->insertDisk(1, disk1name); + } +#endif + +#if 0 + MMU *mmu = g_vm->getMMU(); + + printf("PC: 0x%X\n", g_cpu->pc); + for (int i=g_cpu->pc; ipc + 0x100; i++) { + printf("0x%X ", mmu->read(i)); + } + printf("\n"); + + printf("Dropping to monitor\n"); + // drop directly to monitor. + g_cpu->pc = 0xff69; // "call -151" + mmu->read(0xC054); // make sure we're in page 1 + mmu->read(0xC056); // and that hires is off + mmu->read(0xC051); // and text mode is on + mmu->read(0xC08A); // and we have proper rom in place + mmu->read(0xc008); // main zero-page + mmu->read(0xc006); // rom from cards + mmu->write(0xc002 + mmu->read(0xc014)? 1 : 0, 0xff); // make sure aux ram read and write match + mmu->write(0x20, 0); // text window + mmu->write(0x21, 40); + mmu->write(0x22, 0); + mmu->write(0x23, 24); + mmu->write(0x33, '>'); + mmu->write(0x48, 0); // from 0xfb2f: part of text init + */ +#endif + + send_rst = 0; + } } } } @@ -370,11 +336,11 @@ int main(int argc, char *argv[]) } while (1) { - static uint32_t usleepcycles = 16384; // step-down for display drawing. FIXME: this constant works well for *my* machine. Dynamically generate? - // static uint32_t ctr = 0; - // if (++ctr == 0) { - // printf("hit: %llu; miss: %llu; pct: %f\n", hitcount, misscount, (double)misscount / (double)(misscount + hitcount)); - // } + static uint32_t usleepcycles = 16384; // step-down for display drawing. Dynamically updated based on FPS calculations. + static uint8_t ctr = 0; + if (++ctr == 0) { + printf("hit: %llu; miss: %llu; pct: %f\n", hitcount, misscount, (double)misscount / (double)(misscount + hitcount)); + } // fill disk buffer when needed ((AppleVM*)g_vm)->disk6->fillDiskBuffer(); diff --git a/sdl/sdl-speaker.cpp b/sdl/sdl-speaker.cpp index 2afac27..31b4311 100644 --- a/sdl/sdl-speaker.cpp +++ b/sdl/sdl-speaker.cpp @@ -1,5 +1,6 @@ #include "sdl-speaker.h" #include +#include extern "C" { @@ -7,115 +8,75 @@ extern "C" #include }; -#include "timeutil.h" - #include "globals.h" +#include "timeutil.h" // FIXME: Globals; ick. -static pthread_t speakerThreadID; -static uint8_t curSpeakerData = 0x00; -static volatile uint16_t bufIdx = 0; -static uint8_t soundBuf[4096]; +static volatile uint32_t bufIdx = 0; +static uint8_t soundBuf[44100]; // 1 second of audio static pthread_mutex_t sndmutex = PTHREAD_MUTEX_INITIALIZER; - static pthread_mutex_t togmutex = PTHREAD_MUTEX_INITIALIZER; - -static uint64_t hitcount; -static uint64_t misscount; - -static uint64_t copycount = 0; - static void audioCallback(void *unused, Uint8 *stream, int len) { + FILE *f = (FILE *)unused; + pthread_mutex_lock(&sndmutex); if (bufIdx >= len) { memcpy(stream, soundBuf, len); + + fwrite(soundBuf, 1, len, f); + if (bufIdx > len) { // move the remaining data down - memcpy(soundBuf, &soundBuf[len], bufIdx - len); + memcpy(soundBuf, &soundBuf[len], bufIdx - len + 1); bufIdx -= len; - copycount += len; } } else { // Audio underrun + printf("Audio underrun!\n"); memset(stream, 0, len); } pthread_mutex_unlock(&sndmutex); } -static void *speaker_thread(void *dummyptr) { - struct timespec currentTime; - struct timespec startTime; - struct timespec nextSampleTime; +void ResetDCFilter(); // FIXME: remove +SDLSpeaker::SDLSpeaker() +{ + toggleState = false; + mixerValue = 0x8000; + + toggleCount = toggleReadPtr = toggleWritePtr = 0; + + pthread_mutex_init(&togmutex, NULL); pthread_mutex_init(&sndmutex, NULL); + _init_darwin_shim(); + + ResetDCFilter(); + + lastCycleCount = 0; + lastSampleCount = 0; + + FILE *f = fopen("out.dat", "w"); + SDL_AudioSpec audioDevice; SDL_AudioSpec audioActual; SDL_memset(&audioDevice, 0, sizeof(audioDevice)); - audioDevice.freq = 22050; + audioDevice.freq = 44100; audioDevice.format = AUDIO_U8; audioDevice.channels = 1; - audioDevice.samples = 2048; // 2048 bytes @ 22050Hz is about 1/10th second out of sync - should be okay for this testing + 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; + audioDevice.userdata = (void *)f; SDL_OpenAudio(&audioDevice, &audioActual); // FIXME retval printf("Actual: freq %d channels %d samples %d\n", audioActual.freq, audioActual.channels, audioActual.samples); - _init_darwin_shim(); - do_gettime(&startTime); - do_gettime(&nextSampleTime); - SDL_PauseAudio(0); - - - uint64_t sampleCount = 0; - while (1) { - do_gettime(¤tTime); - struct timespec diff = tsSubtract(nextSampleTime, currentTime); - if (diff.tv_sec >= 0 && diff.tv_nsec >= 0) { - nanosleep(&diff, NULL); - hitcount++; - } else - misscount++; - - if ((sampleCount & 0xFFFF) == 0) { - printf("sound hit: %lld miss: %lld copy: %lld\n", hitcount, misscount, copycount); - } - - pthread_mutex_lock(&sndmutex); - soundBuf[bufIdx++] = curSpeakerData & 0xFF; - if (bufIdx >= sizeof(soundBuf)) { - // Audio overrun; start dropping data - bufIdx--; - } - pthread_mutex_unlock(&sndmutex); - - // set nextSampleTime to the absolute reference time of when the - // next sample should start (based on our start time). - timespec_add_us(&startTime, (sampleCount * 1000000) / 22050 , &nextSampleTime); - sampleCount++; - } -} - - -SDLSpeaker::SDLSpeaker() -{ - toggleState = false; - mixerValue = 0; - _init_darwin_shim(); // set up the clock interface - - toggleCount = toggleReadPtr = toggleWritePtr = 0; - - pthread_mutex_init(&togmutex, NULL); - - if (!pthread_create(&speakerThreadID, NULL, &speaker_thread, (void *)NULL)) { - printf("speaker thread created\n"); - } } SDLSpeaker::~SDLSpeaker() @@ -139,16 +100,39 @@ void SDLSpeaker::toggle(uint32_t c) printf(" %d [%d]\n", toggleTimes[(toggleReadPtr + i)%SPEAKERQUEUESIZE], toggleTimes[(toggleReadPtr + i - 1)%SPEAKERQUEUESIZE] - toggleTimes[(toggleReadPtr + i)%SPEAKERQUEUESIZE] -); + ); } exit(1); } pthread_mutex_unlock(&togmutex); } -void SDLSpeaker::maintainSpeaker(uint32_t c) +// FIXME: make methods +uint16_t dcFilterState = 0; + +void ResetDCFilter() +{ + dcFilterState = 32768 + 10000; +} + +int16_t DCFilter(int16_t in) +{ + if (dcFilterState == 0) + return 0; + + if (dcFilterState >= 32768) { + dcFilterState--; + return in; + } + + return ( (int32_t)in * (int32_t)dcFilterState-- ) / (int32_t)32768; +} + + +void SDLSpeaker::maintainSpeaker(uint32_t c, uint64_t microseconds) { bool didChange = false; + pthread_mutex_lock(&togmutex); while (toggleCount && c >= toggleTimes[toggleReadPtr]) { // Override the mixer with a 1-bit "Terribad" audio sample change @@ -163,13 +147,31 @@ void SDLSpeaker::maintainSpeaker(uint32_t c) // FIXME: removed all the mixing code - if (didChange) { - mixerValue = (toggleState ? 0x1FF : 0x00); + // Add samples from the last time to this time + // mixerValue = (toggleState ? 0x1FF : 0x00); + mixerValue = (toggleState ? 0x8000 : ~0x8000); + // FIXME: DC filter isn't correct yet + // mixerValue = DCFilter(mixerValue); - // FIXME: g_volume + uint64_t sampleCount = (microseconds * 44100) / 1000000; + uint64_t numSamples = sampleCount - lastSampleCount; + + if (numSamples) { + lastSampleCount = sampleCount; + + mixerValue >>= 12; // convert from 16 bit to 8 bit; then drop volume by 50% - curSpeakerData = (mixerValue & 0xFF) >> 4; + pthread_mutex_lock(&sndmutex); + + if (bufIdx + numSamples >= sizeof(soundBuf)) { + printf("Sound overrun!\n"); + numSamples = sizeof(soundBuf) - bufIdx - 1; + } + memset(&soundBuf[bufIdx], mixerValue, numSamples); + bufIdx += numSamples; + pthread_mutex_unlock(&sndmutex); } + } void SDLSpeaker::beginMixing() diff --git a/sdl/sdl-speaker.h b/sdl/sdl-speaker.h index 6ccd733..10a8ec0 100644 --- a/sdl/sdl-speaker.h +++ b/sdl/sdl-speaker.h @@ -13,12 +13,11 @@ class SDLSpeaker : public PhysicalSpeaker { virtual ~SDLSpeaker(); virtual void toggle(uint32_t c); - virtual void maintainSpeaker(uint32_t c); + virtual void maintainSpeaker(uint32_t c, uint64_t microseconds); virtual void beginMixing(); virtual void mixOutput(uint8_t v); private: - uint32_t mixerValue; - uint8_t numMixed; + int16_t mixerValue; bool toggleState; uint32_t toggleTimes[SPEAKERQUEUESIZE]; @@ -26,6 +25,9 @@ class SDLSpeaker : public PhysicalSpeaker { uint8_t toggleReadPtr; // ring buffer pointer in queue uint8_t toggleWritePtr; // ring buffer pointer in queue + uint64_t lastCycleCount; + uint64_t lastSampleCount; + FILE *f; }; diff --git a/sdl/timeutil.h b/sdl/timeutil.h index 54c29f8..8de3ee7 100644 --- a/sdl/timeutil.h +++ b/sdl/timeutil.h @@ -28,7 +28,7 @@ static int do_gettime(struct timespec *tp) { // adds the number of nanoseconds that 'cycles' takes to *start and // returns it in *out static void timespec_add_cycles(struct timespec *start, - uint32_t cycles, + int32_t cycles, struct timespec *out) { out->tv_sec = start->tv_sec; diff --git a/teensy/bios.cpp b/teensy/bios.cpp index 625ab19..7da6f69 100644 --- a/teensy/bios.cpp +++ b/teensy/bios.cpp @@ -24,8 +24,9 @@ enum { ACT_VOLMINUS = 11, ACT_SUSPEND = 12, ACT_RESTORE = 13, + ACT_PRIMODE = 14, - NUM_ACTIONS = 14 + NUM_ACTIONS = 15 }; const char *titles[NUM_ACTIONS] = { "Resume VM", @@ -41,7 +42,8 @@ const char *titles[NUM_ACTIONS] = { "Resume VM", "Volume +", "Volume -", "Suspend", - "Restore" + "Restore", + "Prioritize %s" }; // FIXME: abstract the pin # rather than repeating it here @@ -49,6 +51,8 @@ const char *titles[NUM_ACTIONS] = { "Resume VM", extern int16_t g_volume; // FIXME: external global. icky. extern uint8_t debugMode; // and another. :/ +extern bool g_prioritizeDisplay; // And a third! + // FIXME: and these need abstracting out of the main .ino ! enum { D_NONE = 0, @@ -112,6 +116,9 @@ bool BIOS::runUntilDone() debugMode++; debugMode %= 8; // FIXME: abstract max # break; + case ACT_PRIMODE: + g_prioritizeDisplay = !g_prioritizeDisplay; + break; case ACT_DISK1: if (((AppleVM *)g_vm)->DiskName(0)[0] != '\0') { ((AppleVM *)g_vm)->ejectDisk(0); @@ -245,6 +252,7 @@ bool BIOS::isActionActive(int8_t action) case ACT_MONITOR: case ACT_DISPLAYTYPE: case ACT_DEBUG: + case ACT_PRIMODE: case ACT_DISK1: case ACT_DISK2: case ACT_HD1: @@ -315,6 +323,11 @@ void BIOS::DrawMainMenu(int8_t selection) sprintf(buf, titles[i], "Show time"); break; } + } else if (i == ACT_PRIMODE) { + if (g_prioritizeDisplay) + sprintf(buf, titles[i], "display"); + else + sprintf(buf, titles[i], "r/t audio"); } else { strcpy(buf, titles[i]); } @@ -328,7 +341,7 @@ void BIOS::DrawMainMenu(int8_t selection) // draw the volume bar uint16_t volCutoff = 300.0 * (float)((float) g_volume / 15.0); - for (uint8_t y=220; y<=230; y++) { + for (uint8_t y=234; y<=235; y++) { ((TeensyDisplay *)g_display)->moveTo(10, y); for (uint16_t x = 0; x< 300; x++) { ((TeensyDisplay *)g_display)->drawNextPixel( x <= volCutoff ? 0xFFFF : 0x0010 ); diff --git a/teensy/teensy-display.h b/teensy/teensy-display.h index 0c524b3..f7bba23 100644 --- a/teensy/teensy-display.h +++ b/teensy/teensy-display.h @@ -19,8 +19,8 @@ enum { #define cbi(reg, bitmask) *reg &= ~bitmask #define sbi(reg, bitmask) *reg |= bitmask -#define pulse_high(reg, bitmask) sbi(reg, bitmask); cbi(reg, bitmask); -#define pulse_low(reg, bitmask) cbi(reg, bitmask); sbi(reg, bitmask); +#define pulse_high(reg, bitmask) { sbi(reg, bitmask); cbi(reg, bitmask); } +#define pulse_low(reg, bitmask) { cbi(reg, bitmask); sbi(reg, bitmask); } #define cport(port, data) port &= data #define sport(port, data) port |= data @@ -72,11 +72,11 @@ class TeensyDisplay : public PhysicalDisplay { void drawPixel(uint16_t x, uint16_t y, uint16_t color); void drawPixel(uint16_t x, uint16_t y, uint8_t r, uint8_t g, uint8_t b); - void LCD_Writ_Bus(uint8_t VH,uint8_t VL); - void LCD_Write_COM(uint8_t VL); - void LCD_Write_DATA(uint8_t VH,uint8_t VL); - void LCD_Write_DATA(uint8_t VL); - void LCD_Write_COM_DATA(uint8_t com1,uint16_t dat1); + inline void LCD_Writ_Bus(uint8_t VH,uint8_t VL) __attribute__((always_inline)); + inline void LCD_Write_COM(uint8_t VL) __attribute__((always_inline)); + inline void LCD_Write_DATA(uint8_t VH,uint8_t VL) __attribute__((always_inline)); + inline void LCD_Write_DATA(uint8_t VL) __attribute__((always_inline)); + inline void LCD_Write_COM_DATA(uint8_t com1,uint16_t dat1) __attribute__((always_inline)); bool needsRedraw; bool driveIndicator[2]; diff --git a/teensy/teensy-speaker.cpp b/teensy/teensy-speaker.cpp index 33fdb00..729774a 100644 --- a/teensy/teensy-speaker.cpp +++ b/teensy/teensy-speaker.cpp @@ -9,8 +9,6 @@ TeensySpeaker::TeensySpeaker(uint8_t pinNum) : PhysicalSpeaker() speakerPin = pinNum; pinMode(speakerPin, OUTPUT); // analog speaker output, used as digital volume control mixerValue = numMixed = 0; - - toggleCount = toggleReadPtr = toggleWritePtr = 0; } TeensySpeaker::~TeensySpeaker() @@ -19,38 +17,20 @@ TeensySpeaker::~TeensySpeaker() void TeensySpeaker::toggle(uint32_t c) { - toggleTimes[toggleWritePtr] = c; - if (toggleCount < SPEAKERQUEUESIZE-1) { - toggleWritePtr++; - if (toggleWritePtr >= SPEAKERQUEUESIZE) - toggleWritePtr = 0; - toggleCount++; - } else { - // speaker overflow - Serial.println("spkr overflow"); - } + toggleState = !toggleState; + + mixerValue = (toggleState ? 0x1FF : 0x00); + mixerValue >>= (16-g_volume); + + // FIXME: glad it's DAC0 and all, but... how does that relate to the pin passed in the constructor? + analogWriteDAC0(mixerValue); } -void TeensySpeaker::maintainSpeaker(uint32_t c) +void TeensySpeaker::maintainSpeaker(uint32_t c, uint64_t runtimeInMicros) { - bool didChange = false; - - while (toggleCount && c >= toggleTimes[toggleReadPtr]) { - toggleState = !toggleState; - toggleCount--; - toggleReadPtr++; - if (toggleReadPtr >= SPEAKERQUEUESIZE) - toggleReadPtr = 0; - didChange = true; - } - - if (didChange) { - mixerValue = (toggleState ? 0x1FF : 0x00); - mixerValue >>= (16-g_volume); - - // FIXME: glad it's DAC0 and all, but... how does that relate to the pin passed in the constructor? - analogWriteDAC0(mixerValue); - } + // Nothing to do here. We can't run the speaker async, b/c not + // enough CPU time. So we run the CPU close to sync and hope that + // the direct pulsing of the speaker is reasonably close to on-time. } void TeensySpeaker::beginMixing() diff --git a/teensy/teensy-speaker.h b/teensy/teensy-speaker.h index 693273f..f6eebab 100644 --- a/teensy/teensy-speaker.h +++ b/teensy/teensy-speaker.h @@ -3,16 +3,13 @@ #include "physicalspeaker.h" -// FIXME: 64 enough? -#define SPEAKERQUEUESIZE 64 - class TeensySpeaker : public PhysicalSpeaker { public: TeensySpeaker(uint8_t pinNum); virtual ~TeensySpeaker(); virtual void toggle(uint32_t c); - virtual void maintainSpeaker(uint32_t c); + virtual void maintainSpeaker(uint32_t c, uint64_t runtimeInMicros); virtual void beginMixing(); virtual void mixOutput(uint8_t v); @@ -24,11 +21,6 @@ class TeensySpeaker : public PhysicalSpeaker { uint32_t mixerValue; uint8_t numMixed; - - 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 }; #endif diff --git a/teensy/teensy.ino b/teensy/teensy.ino index 293c617..5fe48cc 100644 --- a/teensy/teensy.ino +++ b/teensy/teensy.ino @@ -20,8 +20,8 @@ #include "globals.h" #include "teensy-crash.h" -volatile float nextInstructionMicros; -volatile float startMicros; +uint32_t nextInstructionMicros; +uint32_t startMicros; FATFS fatfs; /* File system object */ BIOS bios; @@ -37,6 +37,9 @@ enum { D_SHOWTIME = 7 }; uint8_t debugMode = D_NONE; +bool g_prioritizeDisplay = false; // prioritize real-time audio by default, not the display + +#define SPEEDCTL 0.97751710654936461388 // that's how many microseconds per cycle @ 1.023 MHz static time_t getTeensy3Time() { return Teensy3Clock.get(); } @@ -132,17 +135,19 @@ void setup() Serial.println("free-running"); - startMicros = 0; - nextInstructionMicros = micros(); + startMicros = nextInstructionMicros = micros(); // Debugging: insert a disk on startup... // ((AppleVM *)g_vm)->insertDisk(0, "/A2DISKS/UTIL/mock2dem.dsk", false); // ((AppleVM *)g_vm)->insertDisk(0, "/A2DISKS/JORJ/disk_s6d1.dsk", false); - // ((AppleVM *)g_vm)->insertDisk(0, "/A2DISKS/GAMES/ALIBABA.DSK", false); + // ((AppleVM *)g_vm)->insertDisk(0, "/A2DISKS/GAMES/ALIBABA.DSK", false); pinMode(56, OUTPUT); pinMode(57, OUTPUT); + Serial.print("Free RAM: "); + Serial.println(FreeRamEstimate()); + Timer1.initialize(3); Timer1.attachInterrupt(runCPU); Timer1.start(); @@ -198,7 +203,7 @@ void biosInterrupt() nextInstructionMicros = micros(); startMicros = micros(); // Drain the speaker queue (FIXME: a little hacky) - g_speaker->maintainSpeaker(-1); + g_speaker->maintainSpeaker(-1, -1); // Force the display to redraw ((AppleDisplay*)(g_vm->vmdisplay))->modeChange(); @@ -214,27 +219,30 @@ void biosInterrupt() void runCPU() { - if (micros() >= nextInstructionMicros) { + // Debugging: to watch when the speaker is triggered... + // static bool debugState = false; + // debugState = !debugState; + // digitalWrite(56, debugState); + + // Relatively critical timing: CPU needs to run ahead at least 4 + // cycles, b/c we're calling this interrupt (runCPU, that is) just + // about 1/3 as fast as we should; and the speaker is updated + // directly from within it, so it needs to be real-ish time. + if (micros() > nextInstructionMicros) { // Debugging: to watch when the CPU is triggered... - //debugState = !debugState; - // digitalWrite(56, debugState); + static bool debugState = false; + debugState = !debugState; + digitalWrite(56, debugState); uint8_t executed = g_cpu->Run(24); // The CPU of the Apple //e ran at 1.023 MHz. Adjust when we think // the next instruction should run based on how long the execution // was ((1000/1023) * numberOfCycles) - which is about 97.8%. - nextInstructionMicros = startMicros + (float)g_cpu->cycles * 0.978; + nextInstructionMicros = startMicros + ((double)g_cpu->cycles * (double)SPEEDCTL); - // Timing-critical paddle and keyboard handling ((AppleVM *)g_vm)->cpuMaintenance(g_cpu->cycles); } - - // Timing-crtical audio handling - g_speaker->beginMixing(); - // estimate of current cpu cycle counter, delayed a bit - float speakerTick = ((float)micros() - 100.0 - (float)startMicros) / 0.978; - g_speaker->maintainSpeaker(speakerTick); } void loop() @@ -266,9 +274,11 @@ void loop() // // The Timer1.stop()/start() is bad. Using it, the display doesn't // tear; but the audio is also broken. Taking it out, audio is good - // but the display tears. + // but the display tears. So there's a global - g_prioritizeDisplay - + // which lets the user pick which they want. - Timer1.stop(); + if (g_prioritizeDisplay) + Timer1.stop(); g_vm->vmdisplay->lockDisplay(); if (g_vm->vmdisplay->needsRedraw()) { AiieRect what = g_vm->vmdisplay->getDirtyRect(); @@ -276,8 +286,9 @@ void loop() g_display->blit(what); } g_vm->vmdisplay->unlockDisplay(); - Timer1.start(); - + if (g_prioritizeDisplay) + Timer1.start(); + static unsigned long nextBattCheck = 0; static int batteryLevel = 0; // static for debugging code! When done // debugging, this can become a local