From 39fdffd9c4e1c31321fbcb6c56e62abed69758be Mon Sep 17 00:00:00 2001 From: Jorj Bauer Date: Sun, 26 Feb 2017 11:00:41 -0500 Subject: [PATCH] continued work on sound: migrate to SDL for host-based audio debugging --- Makefile | 24 ++- apple/applemmu.cpp | 2 +- apple/applevm.cpp | 1 - apple/ay8910.cpp | 201 +++++++++++++++++++++---- apple/ay8910.h | 46 +++++- apple/fx80.cpp | 9 -- apple/fx80.h | 2 - apple/mockingboard.cpp | 11 +- apple/parallelcard.cpp | 5 - apple/parallelcard.h | 2 - apple/sy6522.cpp | 1 - opencv/aiie.cpp | 5 +- opencv/dummy-speaker.cpp | 75 +--------- opencv/dummy-speaker.h | 11 +- opencv/opencv-display.cpp | 9 +- opencv/opencv-display.h | 2 +- opencv/timeutil.h | 4 +- physicaldisplay.h | 4 +- physicalspeaker.h | 3 +- sdl/aiie.cpp | 297 +++++++++++++++++++++++++++++++++++++ sdl/sdl-display.cpp | 274 ++++++++++++++++++++++++++++++++++ sdl/sdl-display.h | 42 ++++++ sdl/sdl-filemanager.cpp | 189 +++++++++++++++++++++++ sdl/sdl-filemanager.h | 31 ++++ sdl/sdl-keyboard.cpp | 157 ++++++++++++++++++++ sdl/sdl-keyboard.h | 20 +++ sdl/sdl-paddles.cpp | 40 +++++ sdl/sdl-paddles.h | 19 +++ sdl/sdl-printer.cpp | 92 ++++++++++++ sdl/sdl-printer.h | 41 +++++ sdl/sdl-speaker.cpp | 157 ++++++++++++++++++++ sdl/sdl-speaker.h | 26 ++++ sdl/timeutil.h | 143 ++++++++++++++++++ teensy/ay8910.cpp | 1 + teensy/ay8910.h | 1 + teensy/bios.cpp | 3 +- teensy/mockingboard.cpp | 1 + teensy/mockingboard.h | 1 + teensy/sy6522.cpp | 1 + teensy/sy6522.h | 1 + teensy/teensy-display.cpp | 17 +-- teensy/teensy-display.h | 2 +- teensy/teensy-keyboard.cpp | 7 + teensy/teensy-printer.cpp | 6 + teensy/teensy-speaker.cpp | 42 ++++-- teensy/teensy-speaker.h | 12 +- teensy/teensy.ino | 236 ++++++++++++----------------- vm.h | 5 +- vmdisplay.h | 8 + 49 files changed, 1953 insertions(+), 336 deletions(-) create mode 100644 sdl/aiie.cpp create mode 100644 sdl/sdl-display.cpp create mode 100644 sdl/sdl-display.h create mode 100644 sdl/sdl-filemanager.cpp create mode 100644 sdl/sdl-filemanager.h create mode 100644 sdl/sdl-keyboard.cpp create mode 100644 sdl/sdl-keyboard.h create mode 100644 sdl/sdl-paddles.cpp create mode 100644 sdl/sdl-paddles.h create mode 100644 sdl/sdl-printer.cpp create mode 100644 sdl/sdl-printer.h create mode 100644 sdl/sdl-speaker.cpp create mode 100644 sdl/sdl-speaker.h create mode 100644 sdl/timeutil.h create mode 120000 teensy/ay8910.cpp create mode 120000 teensy/ay8910.h create mode 120000 teensy/mockingboard.cpp create mode 120000 teensy/mockingboard.h create mode 120000 teensy/sy6522.cpp create mode 120000 teensy/sy6522.h diff --git a/Makefile b/Makefile index 369ce0b..3adf838 100755 --- a/Makefile +++ b/Makefile @@ -1,20 +1,30 @@ -LDFLAGS=-L/usr/local/lib -lopencv_core -lopencv_highgui -lopencv_imgproc -lopencv_features2d -lopencv_calib3d +LDFLAGS=-L/usr/local/lib -CXXFLAGS=-Wall -I .. -I . -I apple -I opencv -O3 +OPENCVLIBS=-lopencv_core -lopencv_highgui -lopencv_imgproc -lopencv_features2d -lopencv_calib3d + +SDLLIBS=-lSDL2 + +CXXFLAGS=-Wall -I .. -I . -I apple -I opencv -I sdl -I/usr/local/include/SDL2 -O3 -g TSRC=cpu.cpp util/testharness.cpp -OPENCVOBJS=cpu.o opencv/dummy-speaker.o opencv/opencv-display.o opencv/opencv-keyboard.o opencv/opencv-paddles.o opencv/opencv-filemanager.o apple/appledisplay.o apple/applekeyboard.o apple/applemmu.o apple/applevm.o apple/diskii.o apple/nibutil.o RingBuffer.o globals.o opencv/aiie.o apple/parallelcard.o apple/fx80.o opencv/opencv-printer.o apple/mockingboard.o apple/sy6522.o apple/ay8910.o +COMMONOBJS=cpu.o apple/appledisplay.o apple/applekeyboard.o apple/applemmu.o apple/applevm.o apple/diskii.o apple/nibutil.o RingBuffer.o globals.o apple/parallelcard.o apple/fx80.o apple/mockingboard.o apple/sy6522.o apple/ay8910.o + +OPENCVOBJS=opencv/dummy-speaker.o opencv/opencv-display.o opencv/opencv-keyboard.o opencv/opencv-paddles.o opencv/opencv-filemanager.o opencv/aiie.o opencv/opencv-printer.o +SDLOBJS=sdl/sdl-speaker.o sdl/sdl-display.o sdl/sdl-keyboard.o sdl/sdl-paddles.o sdl/sdl-filemanager.o sdl/aiie.o sdl/sdl-printer.o ROMS=apple/applemmu-rom.h apple/diskii-rom.h apple/parallel-rom.h -all: opencv +all: sdl -opencv: roms $(OPENCVOBJS) - g++ $(LDFLAGS) -o aiie-opencv $(OPENCVOBJS) +opencv: roms $(COMMONOBJS) $(OPENCVOBJS) + g++ $(LDFLAGS) $(OPENCVLIBS) -o aiie-opencv $(COMMONOBJS) $(OPENCVOBJS) + +sdl: roms $(COMMONOBJS) $(SDLOBJS) + g++ $(LDFLAGS) $(SDLLIBS) $(OPENCVLIBS) -o aiie-sdl $(COMMONOBJS) $(SDLOBJS) clean: - rm -f *.o *~ */*.o */*~ testharness.basic testharness.verbose testharness.extended aiie-opencv apple/diskii-rom.h apple/applemmu-rom.h apple/parallel-rom.h + rm -f *.o *~ */*.o */*~ testharness.basic testharness.verbose testharness.extended aiie-opencv apple/diskii-rom.h apple/applemmu-rom.h apple/parallel-rom.h aiie-sdl test: $(TSRC) g++ $(CXXFLAGS) -DBASICTEST $(TSRC) -o testharness.basic diff --git a/apple/applemmu.cpp b/apple/applemmu.cpp index 1a5149e..6887ba5 100644 --- a/apple/applemmu.cpp +++ b/apple/applemmu.cpp @@ -373,7 +373,7 @@ uint8_t AppleMMU::readSwitches(uint16_t address) case 0xC030: // SPEAKER - g_speaker->toggleAtCycle(g_cpu->cycles); + g_speaker->toggle(); break; case 0xC050: // CLRTEXT diff --git a/apple/applevm.cpp b/apple/applevm.cpp index 3b72451..cab87a6 100644 --- a/apple/applevm.cpp +++ b/apple/applevm.cpp @@ -60,7 +60,6 @@ void AppleVM::cpuMaintenance(uint32_t cycles) } keyboard->maintainKeyboard(cycles); - parallel->update(); mockingboard->update(cycles); } diff --git a/apple/ay8910.cpp b/apple/ay8910.cpp index 7443145..db874ac 100644 --- a/apple/ay8910.cpp +++ b/apple/ay8910.cpp @@ -3,6 +3,46 @@ #include "globals.h" +// Map our linear 4-bit amplitude to 8-bit output level +static const uint8_t volumeLevels[16] = { 0x00, 0x04, 0x05, 0x07, + 0x0B, 0x10, 0x16, 0x23, + 0x2B, 0x44, 0x5A, 0x73, + 0x92, 0xB0, 0xD9, 0xFF }; + +// Envelope constants +enum { + AY_ENV_HOLD = 1, + AY_ENV_ALT = 2, + AY_ENV_ATTACK = 4, + AY_ENV_CONT = 8 +}; + +/* Envelope handling + * (Per General Instruments AY-3-8910 documentation.) + * + * Envelope period is set in the 16-bit value r[0x0C]:r[0x0B] (where 0 = 1). + * The resulting frequency is from 0.12Hz to 7812.5 Hz. + * + * The shape of the envelope is selected by r[0x0D] and uses the + * constants above. + * + * If AY_ENV_HOLD is set, then when the envelope reaches terminal (0 + * or 15) it stays there. + * + * If AY_ENV_ALT is set, the direction reverses each time it reaches + * terminal. (If both AY_ENV_HOLD and AY_ENV_ALT are set, then the + * envelope counter returns to its initial count before holding.) + * + * If AY_ENV_ATTACK is set, the counter is ascending (0-to-15); otherwise + * it is descending (15-to-0). + * + * If AY_ENV_CONT is *clear* (0), then the counter resets to 0 after + * one cycle and holds there. If it is 1, it does whatever HOLD + * says. (So AY_ENV_CONT==0 takes priority over AY_ENV_HOLD). + * + * + */ + AY8910::AY8910() { Reset(); @@ -10,12 +50,16 @@ AY8910::AY8910() void AY8910::Reset() { - printf("AY8910 reset\n"); curRegister = 0; + + // FIXME: what are the right default values? for (uint8_t i=0; i<16; i++) - r[i] = 0xFF; + r[i] = 0x00; waveformFlipTimer[0] = waveformFlipTimer[1] = waveformFlipTimer[2] = 0; outputState[0] = outputState[1] = outputState[2] = 0; + envCounter = 0; + envelopeTimer = envelopeTime = 0; + envDirection = 1; } uint8_t AY8910::read(uint8_t reg) @@ -31,7 +75,7 @@ void AY8910::write(uint8_t reg, uint8_t PortA) { // Bit 2 (1 << 2 == 0x04) is wired to the Reset pin. If it goes low, // we reset the virtual chip. - if ((reg & 0x04) == 0) { + if ((reg & NRSET) == 0) { Reset(); return; } @@ -42,30 +86,52 @@ void AY8910::write(uint8_t reg, uint8_t PortA) reg &= ~0x04; switch (reg) { - case 0: // bDir==0 && BC1 == 0 (IAB) + case IAB: // bDir==0 && BC1 == 0 (IAB) // Puts the DA bus in high-impedance state. Nothing for us to do? return; - case 1: // bDir==0 && BC1 == 1 (DTB) + case DTB: // bDir==0 && BC1 == 1 (DTB) // Contents of the currently addressed register are put in DA. FIXME? return; - case 2: // bDir==1 && BC1 == 0 (DWS) + case DWS: // bDir==1 && BC1 == 0 (DWS) // Write current PortA to PSG - printf("Set register %d to %X\n", reg, PortA); r[curRegister] = PortA; - if (curRegister <= 1) { - cycleTime[0] = cycleTimeForPSG(0); - } else if (curRegister <= 3) { - cycleTime[1] = cycleTimeForPSG(1); - } else if (curRegister <= 5) { - cycleTime[2] = cycleTimeForPSG(2); - } else if (curRegister == 7) { + if (curRegister <= CHAN_A_COARSE) { cycleTime[0] = cycleTimeForPSG(0); + waveformFlipTimer[0] = g_cpu->cycles + cycleTime[0]; + } else if (curRegister <= CHAN_B_COARSE) { cycleTime[1] = cycleTimeForPSG(1); + waveformFlipTimer[1] = g_cpu->cycles + cycleTime[1]; + } else if (curRegister <= CHAN_C_COARSE) { cycleTime[2] = cycleTimeForPSG(2); + waveformFlipTimer[2] = g_cpu->cycles + cycleTime[2]; + } else if (curRegister == ENAB) { + if (r[ENAB] & ENAB_N_TONEA) { + cycleTime[0] = waveformFlipTimer[0] = 0; + } else { + cycleTime[0] = cycleTimeForPSG(0); + waveformFlipTimer[0] = g_cpu->cycles + cycleTime[0]; + } + if (r[ENAB] & ENAB_N_TONEB) { + cycleTime[1] = waveformFlipTimer[1] = 0; + } else { + cycleTime[1] = cycleTimeForPSG(1); + waveformFlipTimer[1] = g_cpu->cycles + cycleTime[1]; + } + if (r[ENAB] & ENAB_N_TONEC) { + cycleTime[2] = waveformFlipTimer[2] = 0; + } else { + cycleTime[2] = cycleTimeForPSG(2); + waveformFlipTimer[2] = g_cpu->cycles + cycleTime[2]; + } + } else if (curRegister >= ENV_PERIOD_FINE && curRegister <= ENV_SHAPE) { + // Envelope control -- period or shape + // FIXME: should envCounter be initialized to the start position? + envDirection = (r[ENV_SHAPE] & AY_ENV_ATTACK) ? 1 : -1; + envelopeTime = calculateEnvelopeTime(); + envelopeTimer = 0; // reset so it will pick up @ next tick } - return; - case 3: // bDir==1 && BC1 == 1 (INTAK) + case INTAK: // bDir==1 && BC1 == 1 (INTAK) // Select current register curRegister = PortA & 0xF; return; @@ -97,33 +163,108 @@ uint16_t AY8910::cycleTimeForPSG(uint8_t psg) return (32 * regVal) / 4; } +// Similar calculation: this one, for the envelope timer. +// FIXME: I *think* this is right. Not sure. Needs validation. +uint32_t AY8910::calculateEnvelopeTime() +{ + uint16_t regVal = (r[0x0C] << 8) | (r[0x0B]); + if (regVal == 0) regVal++; + + return (512 * regVal) / 4; +} + void AY8910::update(uint32_t cpuCycleCount) { +#if 0 + // Debugging: print state of the 16 registers + printf("AY8910: "); + for (int i=0; i<16; i++) { + printf("%02X ", r[i]); + } + printf("%04X %04X %04X\n", cycleTime[0], cycleTime[1], cycleTime[2]); +#endif + + // update the envelope timer if it's time + if (envelopeTime != 0) { + if (!envelopeTimer) { + // timer wasn't set, so start it running + envelopeTimer = cpuCycleCount + envelopeTime; + } + if (envelopeTimer <= cpuCycleCount) { + // time to update the envelopeCounter. + + switch (r[ENV_SHAPE]) { + // Continue / Attack / Alternate / Hold bits + case 0x00: // 0 / 0 / x / x -- descend once, stay @ bottom + case 0x01: // 0 / 0 / x / x + case 0x02: // 0 / 0 / x / x + case 0x03: // 0 / 0 / x / x + case 0x04: // 0 / 1 / x / x -- ascend once, jump to bottom + case 0x05: // 0 / 1 / x / x + case 0x06: // 0 / 1 / x / x + case 0x07: // 0 / 1 / x / x + case 0x09: // 1 / 0 / 0 / 1 -- descend once, stay @ bottom + case 0x0b: // 1 / 0 / 1 / 1 -- descend once, jump to top + case 0x0d: // 1 / 1 / 0 / 1 -- ascend once, stay @ top + case 0x0f: // 1 / 1 / 1 / 1 -- ascend once, jump to bottom + + // In all these cases, we go from start to finish once. In all + // cases except 0x0b and 0x0d, when we're done, we go low. + + envCounter += envDirection; + if (envDirection > 0) { + // We were ascending: did we hit 16? If so, stop & go terminal + if (envCounter == 16) { + envDirection = 0; + // One ascending case (0x0b) goes high after; all others are low + envCounter = (r[ENV_SHAPE] == 0x0b ? 0x0F : 0x00); + } + } else if (envDirection < 0) { + // We were descending: did we hit 0? If so, stop & go terminal + if (envCounter == 0) { + envDirection = 0; + // One descending case (0x0d) goes high after; all others are low + envCounter = (r[ENV_SHAPE] == 0x0d ? 0x0F : 0x00); + } + } + } + + // Set up the envelope timer for the next transition + // FIXME: can set this to 0 if envDirection is 0, but have to be careful about setup of timer again when envDirection is re-set + envelopeTimer += envelopeTime; + } + } + // For any waveformFlipTimer that is > 0: if cpuCycleCount is larger // than the timer, we'll flip state. (It's a square wave!) for (uint8_t i=0; i<3; i++) { uint32_t cc = cycleTime[i]; - if (cc == 0) { - waveformFlipTimer[i] = 0; - } else { - if (!waveformFlipTimer[i]) { - // start a cycle, if necessary - waveformFlipTimer[i] = cpuCycleCount + cc; - } - - if (waveformFlipTimer[i] && waveformFlipTimer[i] <= cpuCycleCount) { + if (cc) { + if (waveformFlipTimer[i] <= cpuCycleCount) { // flip when it's time to flip waveformFlipTimer[i] += cc; outputState[i] = !outputState[i]; } + } else { + outputState[i] = 0; } - // If any of the square waves is on, then we want to be on. - - // r[i+8] is the amplitude control. - // FIXME: if r[i+8] & 0x10, then it's an envelope-specific amplitude - g_speaker->mixOutput(outputState[i] ? (r[i+8] & 0x0F) : 0x00); + + // Figure out what output comes from this channel and send it to + // the speaker. The output is controlled by outputState[i] (from + // the square wave, above); the amplitude control line for this + // output (r[i+8], below) and the tone/noise selection (FIXME: + // currently unimplemented). + + uint8_t amplitude = r[i+8] & 0xF; + // ... and if bit 0x10 is on, it's controlled by the envelope counter. + if (r[i+8] & 0x10) + amplitude = envCounter; + + g_speaker->mixOutput(outputState[i] ? volumeLevels[amplitude] : 0x00); + } + } diff --git a/apple/ay8910.h b/apple/ay8910.h index 15f71f5..a19a1b5 100644 --- a/apple/ay8910.h +++ b/apple/ay8910.h @@ -3,6 +3,43 @@ #include +// Operations... +enum { + IAB = 0, + DTB = 1, + DWS = 2, + INTAK = 3, + NRSET = 4 +}; + +// Registers... +enum { + CHAN_A_FINE = 0, + CHAN_A_COARSE = 1, + CHAN_B_FINE = 2, + CHAN_B_COARSE = 3, + CHAN_C_FINE = 4, + CHAN_C_COARSE = 5, + NOISE_PERIOD = 6, + ENAB = 7, + CHAN_A_AMP = 8, + CHAN_B_AMP = 9, + CHAN_C_AMP = 10, + ENV_PERIOD_FINE = 11, + ENV_PERIOD_COARSE = 12, + ENV_SHAPE = 13 +}; + +// Enable flags (all negative; enabled-low) +enum { + ENAB_N_TONEA = 1, + ENAB_N_TONEB = 2, + ENAB_N_TONEC = 4, + ENAB_N_NOISEA = 8, + ENAB_N_NOISEB = 16, + ENAB_N_NOISEC = 32 +}; + class AY8910 { public: AY8910(); @@ -16,13 +53,18 @@ class AY8910 { protected: uint16_t cycleTimeForPSG(uint8_t psg); + uint32_t calculateEnvelopeTime(); private: uint8_t curRegister; uint8_t r[16]; - uint32_t waveformFlipTimer[3]; + uint16_t cycleTime[3]; // how long each cycle will last, in clock cycles + uint32_t waveformFlipTimer[3]; // when we're going to flip next uint8_t outputState[3]; - uint16_t cycleTime[3]; + int8_t envCounter; // which bit of the waveform the envelope is on + int8_t envDirection; + uint32_t envelopeTime; + uint32_t envelopeTimer; }; #endif diff --git a/apple/fx80.cpp b/apple/fx80.cpp index 879e7b5..3079df1 100644 --- a/apple/fx80.cpp +++ b/apple/fx80.cpp @@ -429,12 +429,3 @@ void Fx80::emitLine() g_printer->addLine(rowOfBits); } -void Fx80::update() -{ - // The onscreen window needs to update regularly, and we probably - // want to periodically flush the Teensy buffer. - if (g_printer) { - g_printer->update(); - } -} - diff --git a/apple/fx80.h b/apple/fx80.h index 0cc953c..70487df 100644 --- a/apple/fx80.h +++ b/apple/fx80.h @@ -60,8 +60,6 @@ class Fx80 { void input(uint8_t c); - void update(); - private: void lineFeed(); void clearLine(); diff --git a/apple/mockingboard.cpp b/apple/mockingboard.cpp index ba8938d..15b756f 100644 --- a/apple/mockingboard.cpp +++ b/apple/mockingboard.cpp @@ -38,9 +38,7 @@ uint8_t Mockingboard::read(uint16_t address) (address >= 0x80 && address <= 0x8F) ) { uint8_t idx = (address & 0x80 ? 1 : 0); - if (idx == 0) { // FIXME: just debugging; remove this 'if' - return sy6522[idx].read(address & 0x0F); - } + return sy6522[idx].read(address & 0x0F); } return 0xFF; @@ -54,16 +52,13 @@ void Mockingboard::write(uint16_t address, uint8_t val) (address >= 0x80 && address <= 0x8F) ) { uint8_t idx = (address & 0x80 ? 1 : 0); - if (idx == 0) { // FIXME: just debugging; remove this 'if' - return sy6522[idx].write(address & 0x0F, val); - } + return sy6522[idx].write(address & 0x0F, val); } } void Mockingboard::update(uint32_t cycles) { sy6522[0].update(cycles); - // debugging: disabled the second update for the moment - // sy6522[1].update(cycles); + sy6522[1].update(cycles); } diff --git a/apple/parallelcard.cpp b/apple/parallelcard.cpp index c3aede9..696b3d4 100644 --- a/apple/parallelcard.cpp +++ b/apple/parallelcard.cpp @@ -43,8 +43,3 @@ void ParallelCard::loadROM(uint8_t *toWhere) memcpy(toWhere, romData, 256); #endif } - -void ParallelCard::update() -{ - fx80->update(); -} diff --git a/apple/parallelcard.h b/apple/parallelcard.h index 938baf7..dd38177 100644 --- a/apple/parallelcard.h +++ b/apple/parallelcard.h @@ -22,8 +22,6 @@ class ParallelCard : public Slot { virtual uint8_t readSwitches(uint8_t s); virtual void writeSwitches(uint8_t s, uint8_t v); virtual void loadROM(uint8_t *toWhere); - - void update(); private: Fx80 *fx80; diff --git a/apple/sy6522.cpp b/apple/sy6522.cpp index aab654a..059ca25 100644 --- a/apple/sy6522.cpp +++ b/apple/sy6522.cpp @@ -57,7 +57,6 @@ uint8_t SY6522::read(uint8_t address) void SY6522::write(uint8_t address, uint8_t val) { - printf("SY6522: %X = %02X\n", address, val); switch (address) { case SY_ORB: val &= DDRB; diff --git a/opencv/aiie.cpp b/opencv/aiie.cpp index fbb456e..a6960d7 100644 --- a/opencv/aiie.cpp +++ b/opencv/aiie.cpp @@ -210,7 +210,7 @@ int main(int argc, char *argv[]) g_vm->Reset(); g_cpu->rst(); - g_display->blit(); + // g_display->blit(); g_display->redraw(); if (argc >= 2) { @@ -242,10 +242,11 @@ int main(int argc, char *argv[]) // Make this a little friendlier, and the expense of some framerate? // usleep(10000); if (g_vm->vmdisplay->needsRedraw()) { + AiieRect what = g_vm->vmdisplay->getDirtyRect(); // make sure to clear the flag before drawing; there's no lock // on didRedraw, so the other thread might update it g_vm->vmdisplay->didRedraw(); - g_display->blit(); + g_display->blit(what); } g_keyboard->maintainKeyboard(); diff --git a/opencv/dummy-speaker.cpp b/opencv/dummy-speaker.cpp index 01a9012..6a3b32e 100644 --- a/opencv/dummy-speaker.cpp +++ b/opencv/dummy-speaker.cpp @@ -1,99 +1,26 @@ #include "dummy-speaker.h" #include - -#include "timeutil.h" - -// FIXME: Globals; ick. -static pthread_t speakerThreadID; -static uint8_t curSpeakerData = 0x00; - -static uint64_t hitcount; -static uint64_t misscount; - -static void *speaker_thread(void *dummyptr) { - struct timespec currentTime; - struct timespec startTime; - struct timespec nextSampleTime; - - _init_darwin_shim(); - do_gettime(&startTime); - do_gettime(&nextSampleTime); - - FILE *f = popen("play -q -t raw -b 8 -e unsigned-integer -r 8000 -", "w"); - - 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\n", hitcount, misscount); - } - - fputc(curSpeakerData & 0xFF, f); fflush(f); - nextSampleTime = startTime; - timespec_add_ms(&startTime, sampleCount * 1000 / 8000, &nextSampleTime); - sampleCount++; - } -} - - DummySpeaker::DummySpeaker() { - mixerValue = 0; - _init_darwin_shim(); // set up the clock interface - - if (!pthread_create(&speakerThreadID, NULL, &speaker_thread, (void *)NULL)) { - printf("speaker thread created\n"); - } } DummySpeaker::~DummySpeaker() { - pclose(f); } -void DummySpeaker::toggleAtCycle(uint32_t c) +void DummySpeaker::toggle() { - nextTransitionAt = c; } void DummySpeaker::maintainSpeaker(uint32_t c) { - /* if (nextTransitionAt && c >= nextTransitionAt) { - // Override the mixer with a 1-bit "Terribad" audio sample change - mixerValue = speakerState ? 0x00 : (0xFF<<3); // <<3 b/c of the >>=3 below - nextTransitionAt = 0; - }*/ - - if (numMixed) { - mixerValue /= numMixed; - } - speakerState = mixerValue; - - // FIXME: duplication of above? using a global? fix fix fix. - curSpeakerData = mixerValue & 0xFF; -} - -bool DummySpeaker::currentState() -{ - return speakerState; } void DummySpeaker::beginMixing() { - mixerValue = 0; - numMixed = 0; } void DummySpeaker::mixOutput(uint8_t v) { - mixerValue += v; - numMixed++; } diff --git a/opencv/dummy-speaker.h b/opencv/dummy-speaker.h index 4baec56..9d7b1b1 100644 --- a/opencv/dummy-speaker.h +++ b/opencv/dummy-speaker.h @@ -10,19 +10,10 @@ class DummySpeaker : public PhysicalSpeaker { DummySpeaker(); virtual ~DummySpeaker(); - virtual void toggleAtCycle(uint32_t c); + virtual void toggle(); virtual void maintainSpeaker(uint32_t c); - virtual bool currentState(); virtual void beginMixing(); virtual void mixOutput(uint8_t v); - private: - bool speakerState; - - uint32_t mixerValue; - uint8_t numMixed; - uint32_t nextTransitionAt; - - FILE *f; }; #endif diff --git a/opencv/opencv-display.cpp b/opencv/opencv-display.cpp index e00b402..deb603e 100644 --- a/opencv/opencv-display.cpp +++ b/opencv/opencv-display.cpp @@ -10,7 +10,7 @@ using namespace cv; using namespace std; -#define WINDOWNAME "6502core" +#define WINDOWNAME "Aiie!" #include "bios-font.h" #include "display-bg.h" @@ -146,12 +146,11 @@ void OpenCVDisplay::drawBatteryStatus(uint8_t percent) #define BASEX 36 #define BASEY 26 -void OpenCVDisplay::blit() +void OpenCVDisplay::blit(AiieRect r) { uint8_t *videoBuffer = g_vm->videoBuffer; // FIXME: poking deep - - for (uint8_t y=0; y<192; y++) { - for (uint16_t x=0; x<280; x++) { + for (uint8_t y=r.top; y<=r.bottom; y++) { + for (uint16_t x=r.left; x<=r.right; x++) { uint16_t pixel = (y*320+x)/2; uint8_t colorIdx; if (x & 1) { diff --git a/opencv/opencv-display.h b/opencv/opencv-display.h index 298f429..f783bd1 100644 --- a/opencv/opencv-display.h +++ b/opencv/opencv-display.h @@ -23,7 +23,7 @@ class OpenCVDisplay : public PhysicalDisplay { OpenCVDisplay(); virtual ~OpenCVDisplay(); - virtual void blit(); + virtual void blit(AiieRect r); virtual void redraw(); virtual void drawDriveDoor(uint8_t which, bool isOpen); diff --git a/opencv/timeutil.h b/opencv/timeutil.h index fa0b0b1..d5edb94 100644 --- a/opencv/timeutil.h +++ b/opencv/timeutil.h @@ -47,14 +47,14 @@ static void timespec_add_cycles(struct timespec *start, // adds the number of microseconds given to *start and // returns it in *out -static void timespec_add_ms(struct timespec *start, +static void timespec_add_us(struct timespec *start, uint64_t micros, struct timespec *out) { out->tv_sec = start->tv_sec; out->tv_nsec = start->tv_nsec; - uint64_t nanosToAdd = micros * 1000000L; + uint64_t nanosToAdd = micros * 1000L; out->tv_sec += (nanosToAdd / NANOSECONDS_PER_SECOND); out->tv_nsec += (nanosToAdd % NANOSECONDS_PER_SECOND); diff --git a/physicaldisplay.h b/physicaldisplay.h index e9634bc..93e0985 100644 --- a/physicaldisplay.h +++ b/physicaldisplay.h @@ -3,13 +3,15 @@ #include // strncpy +#include "vmdisplay.h" // FIXME: for AiieRect + class PhysicalDisplay { public: PhysicalDisplay() { overlayMessage[0] = '\0'; } virtual ~PhysicalDisplay() {}; virtual void redraw() = 0; // total redraw, assuming nothing - virtual void blit() = 0; // redraw just the VM display area + virtual void blit(AiieRect r) = 0; // redraw just the VM display area virtual void drawDriveDoor(uint8_t which, bool isOpen) = 0; virtual void drawDriveStatus(uint8_t which, bool isRunning) = 0; diff --git a/physicalspeaker.h b/physicalspeaker.h index e73f2c6..a6c1953 100644 --- a/physicalspeaker.h +++ b/physicalspeaker.h @@ -7,11 +7,10 @@ class PhysicalSpeaker { public: virtual ~PhysicalSpeaker() {} - virtual void toggleAtCycle(uint32_t c) = 0; + virtual void toggle() = 0; virtual void maintainSpeaker(uint32_t c) = 0; virtual void beginMixing() = 0; virtual void mixOutput(uint8_t v) = 0; - virtual bool currentState() = 0; }; diff --git a/sdl/aiie.cpp b/sdl/aiie.cpp new file mode 100644 index 0000000..52a471f --- /dev/null +++ b/sdl/aiie.cpp @@ -0,0 +1,297 @@ +#include +#include +#include +#include +#include + +#include "applevm.h" +#include "sdl-display.h" +#include "sdl-keyboard.h" +#include "sdl-speaker.h" +#include "sdl-paddles.h" +#include "sdl-filemanager.h" +#include "sdl-printer.h" + +#include "globals.h" + +#include "timeutil.h" + +//#define SHOWFPS +//#define SHOWPC +//#define DEBUGCPU +//#define SHOWMEMPAGE + +static struct timespec nextInstructionTime, startTime; +uint64_t hitcount = 0; +uint64_t misscount = 0; + +#define NB_ENABLE 1 +#define NB_DISABLE 0 + +int send_rst = 0; + +pthread_t cpuThreadID; + +void sigint_handler(int n) +{ + send_rst = 1; +} + +void nonblock(int state) +{ + struct termios ttystate; + + //get the terminal state + tcgetattr(STDIN_FILENO, &ttystate); + + if (state==NB_ENABLE) + { + //turn off canonical mode + ttystate.c_lflag &= ~ICANON; + //minimum of number input read. + ttystate.c_cc[VMIN] = 1; + } + else if (state==NB_DISABLE) + { + //turn on canonical mode + ttystate.c_lflag |= ICANON; + } + //set the terminal attributes. + tcsetattr(STDIN_FILENO, TCSANOW, &ttystate); + +} + +uint8_t read(void *arg, uint16_t address) +{ + // no action; this is a dummy function until we've finished initializing... + return 0x00; +} + +void write(void *arg, uint16_t address, uint8_t v) +{ + // no action; this is a dummy function until we've finished initializing... +} + +static void *cpu_thread(void *dummyptr) { + struct timespec currentTime; + +#if 0 + int policy; + struct sched_param param; + pthread_getschedparam(pthread_self(), &policy, ¶m); + param.sched_priority = sched_get_priority_max(policy); + pthread_setschedparam(pthread_self(), policy, ¶m); +#endif + + _init_darwin_shim(); + do_gettime(&startTime); + do_gettime(&nextInstructionTime); + + printf("free-running\n"); + while (1) { + // cycle down the CPU... + do_gettime(¤tTime); + struct timespec diff = tsSubtract(nextInstructionTime, currentTime); + if (diff.tv_sec >= 0 && diff.tv_nsec >= 0) { + hitcount++; + nanosleep(&diff, NULL); + } else { + misscount++; + } + +#ifdef DEBUGCPU + uint8_t executed = g_cpu->Run(1); +#else + uint8_t executed = g_cpu->Run(24); +#endif + timespec_add_cycles(&startTime, g_cpu->cycles + executed, &nextInstructionTime); + + g_speaker->beginMixing(); + + // The paddles need to be triggered in real-time on the CPU + // clock. That happens from the VM's CPU maintenance poller. + ((AppleVM *)g_vm)->cpuMaintenance(g_cpu->cycles); + + // cpuMaintenance also maintained the sound card; update the speaker after + g_speaker->maintainSpeaker(g_cpu->cycles); + +#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':' ' + ); + } +#endif + + if (send_rst) { +#if 1 + 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"); + +#else + 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; + } + } +} + +int main(int argc, char *argv[]) +{ + SDL_Init(SDL_INIT_EVERYTHING); + + g_speaker = new SDLSpeaker(); + g_printer = new SDLPrinter(); + + // create the filemanager - the interface to the host file system. + g_filemanager = new SDLFileManager(); + + g_display = new SDLDisplay(); + + // paddles have to be created after g_display created the window + g_paddles = new SDLPaddles(); + + // Next create the virtual CPU. This needs the VM's MMU in order to run, but we don't have that yet. + g_cpu = new Cpu(); + + // Create the virtual machine. This may read from g_filemanager to get ROMs if necessary. + // (The actual Apple VM we've built has them compiled in, though.) It will create its virutal + // hardware (MMU, video driver, floppy, paddles, whatever). + g_vm = new AppleVM(); + + g_keyboard = new SDLKeyboard(g_vm->getKeyboard()); + + // Now that the VM exists and it has created an MMU, we tell the CPU how to access memory through the MMU. + g_cpu->SetMMU(g_vm->getMMU()); + + // Now that all the virtual hardware is glued together, reset the VM + g_vm->Reset(); + g_cpu->rst(); + + // g_display->blit(); + g_display->redraw(); + + if (argc >= 2) { + printf("Inserting disk %s\n", argv[1]); + ((AppleVM *)g_vm)->insertDisk(0, argv[1]); + } + + if (argc == 3) { + printf("Inserting disk %s\n", argv[2]); + ((AppleVM *)g_vm)->insertDisk(1, argv[2]); + } + + nonblock(NB_ENABLE); + + signal(SIGINT, sigint_handler); + + printf("creating CPU thread\n"); + if (!pthread_create(&cpuThreadID, NULL, &cpu_thread, (void *)NULL)) { + printf("thread created\n"); + // pthread_setschedparam(cpuThreadID, SCHED_RR, PTHREAD_MAX_PRIORITY); + } + + while (1) { + static uint32_t ctr = 0; + if (++ctr == 0) { + printf("hit: %llu; miss: %llu; pct: %f\n", hitcount, misscount, (double)misscount / (double)(misscount + hitcount)); + } + + // Make this a little friendlier, and the expense of some framerate? + // usleep(10000); + if (g_vm->vmdisplay->needsRedraw()) { + AiieRect what = g_vm->vmdisplay->getDirtyRect(); + // make sure to clear the flag before drawing; there's no lock + // on didRedraw, so the other thread might update it + g_vm->vmdisplay->didRedraw(); + g_display->blit(what); + } + + g_printer->update(); + g_keyboard->maintainKeyboard(); + g_display->drawBatteryStatus(100); + +#ifdef SHOWFPS + static time_t startAt = time(NULL); + static uint32_t loopCount = 0; + loopCount++; + + time_t lenSecs = time(NULL) - startAt; + if (lenSecs >= 10) { + char buf[25]; + sprintf(buf, "%lu FPS", loopCount / lenSecs); + g_display->debugMsg(buf); + if (lenSecs >= 60) { + startAt = time(NULL); + loopCount = 0; + } + } +#endif +#ifdef SHOWPC + { + char buf[25]; + sprintf(buf, "%X", g_cpu->pc); + g_display->debugMsg(buf); + } +#endif +#ifdef SHOWMEMPAGE + { + char buf[40]; + sprintf(buf, "AUX %c/%c BNK %d BSR %c/%c ZP %c 80 %c INT %c", + g_vm->auxRamRead?'R':'_', + g_vm->auxRamWrite?'W':'_', + g_vm->bank1, + g_vm->readbsr ? 'R':'_', + g_vm->writebsr ? 'W':'_', + g_vm->altzp ? 'Y':'_', + g_vm->_80store ? 'Y' : '_', + g_vm->intcxrom ? 'Y' : '_'); + g_display->debugMsg(buf); + } + +#endif + + + } +} + diff --git a/sdl/sdl-display.cpp b/sdl/sdl-display.cpp new file mode 100644 index 0000000..e56f184 --- /dev/null +++ b/sdl/sdl-display.cpp @@ -0,0 +1,274 @@ +#include // isgraph +#include "sdl-display.h" + +#include "bios-font.h" +#include "display-bg.h" + +#include "globals.h" +#include "applevm.h" + +// RGB map of each of the lowres colors +const uint8_t loresPixelColors[16][3] = { { 0, 0, 0 }, // black + { 195, 0, 48 }, // magenta + { 0, 0, 130 }, // dark blue + { 166, 52, 170 }, // purple + { 0, 146, 0 }, // dark green + { 105, 105, 105 }, // drak grey + { 24, 113, 255 }, // medium blue + { 12, 190, 235 }, // light blue + { 150, 85, 40 }, // brown + { 255, 24, 44 }, // orange + { 150, 170, 170 }, // light gray + { 255, 158, 150 }, // pink + { 0, 255, 0 }, // green + { 255, 255, 0 }, // yellow + { 130, 255, 130 }, // aqua + { 255, 255, 255 } // white +}; + +SDLDisplay::SDLDisplay() +{ + // FIXME: abstract constants + screen = SDL_CreateWindow("Aiie!", + SDL_WINDOWPOS_UNDEFINED, + SDL_WINDOWPOS_UNDEFINED, + 320*2, 240*2, + SDL_WINDOW_SHOWN); + + // SDL_RENDERER_SOFTWARE because, at least on my Mac, this has some + // serious issues with hardware accelerated drawing (flashing and crashing). + renderer = SDL_CreateRenderer(screen, -1, SDL_RENDERER_SOFTWARE); + SDL_SetRenderDrawColor(renderer, 255, 255, 255, 255); // set to white + SDL_RenderClear(renderer); // clear it to the selected color + SDL_RenderPresent(renderer); // perform the render +} + +SDLDisplay::~SDLDisplay() +{ + SDL_Quit(); +} + +void SDLDisplay::redraw() +{ + // primarily for the device, where it's in and out of the + // bios. Draws the background image. + printf("redraw background\n"); + + for (int y=0; y<240; y++) { + for (int x=0; x<320; x++) { + uint8_t *p = &displayBitmap[(y * 320 + x)*3]; + drawPixel(x, y, p[0], p[1], p[2]); + } + } + + if (g_vm) { + drawDriveDoor(0, ((AppleVM *)g_vm)->DiskName(0)[0] == '\0'); + drawDriveDoor(1, ((AppleVM *)g_vm)->DiskName(1)[0] == '\0'); + } +} + +void SDLDisplay::drawDriveStatus(uint8_t which, bool isRunning) +{ + // FIXME: this is a draw from another thread. Can't do that with SDL. + return; + + // location of status indicator for left drive + uint16_t xoff = 125; + uint16_t yoff = 213; + + // and right drive + if (which == 1) + xoff += 135; + + for (int y=0; y<1; y++) { + for (int x=0; x<6; x++) { + drawPixel(x + xoff, y + yoff, isRunning ? 0xF800 : 0x8AA9); + } + } + +} + +void SDLDisplay::drawDriveDoor(uint8_t which, bool isOpen) +{ + // location of drive door for left drive + uint16_t xoff = 55; + uint16_t yoff = 216; + + // location for right drive + if (which == 1) { + xoff += 134; + } + + for (int y=0; y<20; y++) { + for (int x=0; x<43; x++) { + uint8_t *p = &driveLatch[(y * 43 + x)*3]; + if (isOpen) { + p = &driveLatchOpen[(y * 43 + x)*3]; + } + drawPixel(x+xoff, y+yoff, p[0], p[1], p[2]); + } + } +} + +void SDLDisplay::drawBatteryStatus(uint8_t percent) +{ + uint16_t xoff = 300; + uint16_t yoff = 222; + + // the area around the apple is 12 wide + // it's exactly 11 high + // the color is 210/202/159 + + float watermark = ((float)percent / 100.0) * 11; + + for (int y=0; y<11; y++) { + uint8_t bgr = 210; + uint8_t bgg = 202; + uint8_t bgb = 159; + + if (11-y > watermark) { + // black... + bgr = bgg = bgb = 0; + } + + for (int x=0; x<11; x++) { + uint8_t *p = &appleBitmap[(y * 10 + (x-1))*4]; + // It's RGBA; blend w/ background color + + uint8_t r,g,b; + float alpha = (float)p[3] / 255.0; + r = (float)p[0] * alpha + (bgr * (1.0 - alpha)); + g = (float)p[1] * alpha + (bgg * (1.0 - alpha)); + b = (float)p[2] * alpha + (bgb * (1.0 - alpha)); + + drawPixel(x+xoff, y+yoff, r, g, b); + } + } +} + + +#define BASEX 36 +#define BASEY 26 + +void SDLDisplay::blit(AiieRect r) +{ + uint8_t *videoBuffer = g_vm->videoBuffer; // FIXME: poking deep + + for (uint8_t y=0; y<192; y++) { + for (uint16_t x=0; x<280; x++) { + uint16_t pixel = (y*320+x)/2; + uint8_t colorIdx; + if (x & 1) { + colorIdx = videoBuffer[pixel] & 0x0F; + } else { + colorIdx = videoBuffer[pixel] >> 4; + } + for (uint8_t xoff=0; xoff<2; xoff++) { + for (uint8_t yoff=0; yoff<2; yoff++) { + // FIXME: validate BPP >= 3? + SDL_SetRenderDrawColor(renderer, loresPixelColors[colorIdx][0], loresPixelColors[colorIdx][1], loresPixelColors[colorIdx][2], 255); + SDL_RenderDrawPoint(renderer, x*2+xoff+BASEX, y*2+yoff+BASEY); + } + } + } + } + + if (overlayMessage[0]) { + drawString(M_SELECTDISABLED, 1, 240 - 16 - 12, overlayMessage); + } + + SDL_RenderPresent(renderer); +} + +inline void putpixel(SDL_Renderer *renderer, int x, int y, uint8_t r, uint8_t g, uint8_t b) +{ + SDL_SetRenderDrawColor(renderer, r, g, b, 255); + SDL_RenderDrawPoint(renderer, x, y); +} + +void SDLDisplay::drawPixel(uint16_t x, uint8_t y, uint16_t color) +{ + uint8_t + r = (color & 0xF800) >> 8, + g = (color & 0x7E0) >> 3, + b = (color & 0x1F) << 3; + + + // Pixel-doubling + for (int yoff=0; yoff<2; yoff++) { + for (int xoff=0; xoff<2; xoff++) { + putpixel(renderer, xoff+x*2, yoff+y*2, r, g, b); + } + } +} + +void SDLDisplay::drawPixel(uint16_t x, uint8_t y, uint8_t r, uint8_t g, uint8_t b) +{ + // Pixel-doubling + for (int yoff=0; yoff<2; yoff++) { + for (int xoff=0; xoff<2; xoff++) { + putpixel(renderer, xoff+x*2, yoff+y*2, r, g, b); + } + } +} + +void SDLDisplay::drawCharacter(uint8_t mode, uint16_t x, uint8_t y, char c) +{ + int8_t xsize = 8, + ysize = 0x0C, + offset = 0x20; + uint16_t temp; + + c -= offset;// font starts with a space + + uint16_t offPixel, onPixel; + switch (mode) { + case M_NORMAL: + onPixel = 0xFFFF; + offPixel = 0x0010; + break; + case M_SELECTED: + onPixel = 0x0000; + offPixel = 0xFFFF; + break; + case M_DISABLED: + default: + onPixel = 0x7BEF; + offPixel = 0x0000; + break; + case M_SELECTDISABLED: + onPixel = 0x7BEF; + offPixel = 0xFFE0; + break; + } + + temp=(c*ysize); + for (int8_t y_off = 0; y_off <= ysize; y_off++) { + uint8_t ch = BiosFont[temp]; + for (int8_t x_off = 0; x_off <= xsize; x_off++) { + if (ch & (1 << (7-x_off))) { + drawPixel(x + x_off, y + y_off, onPixel); + } else { + drawPixel(x + x_off, y + y_off, offPixel); + } + } + temp++; + } + +} + +void SDLDisplay::drawString(uint8_t mode, uint16_t x, uint8_t y, const char *str) +{ + int8_t xsize = 8; // width of a char in this font + + for (int8_t i=0; i + +#include +#include +#include + +#include "physicaldisplay.h" + +enum { + M_NORMAL = 0, + M_SELECTED = 1, + M_DISABLED = 2, + M_SELECTDISABLED = 3 +}; + +class SDLDisplay : public PhysicalDisplay { + public: + SDLDisplay(); + virtual ~SDLDisplay(); + + virtual void blit(AiieRect r); + virtual void redraw(); + + virtual void drawDriveDoor(uint8_t which, bool isOpen); + virtual void drawDriveStatus(uint8_t which, bool isRunning); + virtual void drawBatteryStatus(uint8_t percent); + + void drawPixel(uint16_t x, uint8_t y, uint16_t color); + void drawPixel(uint16_t x, uint8_t y, uint8_t r, uint8_t g, uint8_t b); + virtual void drawCharacter(uint8_t mode, uint16_t x, uint8_t y, char c); + virtual void drawString(uint8_t mode, uint16_t x, uint8_t y, const char *str); + virtual void debugMsg(const char *msg); + + private: + SDL_Window *screen; + SDL_Renderer *renderer; +}; + +#endif diff --git a/sdl/sdl-filemanager.cpp b/sdl/sdl-filemanager.cpp new file mode 100644 index 0000000..b85520b --- /dev/null +++ b/sdl/sdl-filemanager.cpp @@ -0,0 +1,189 @@ +#include // strcpy +#include +#include +#include +#include +#include + +#include "sdl-filemanager.h" + + +SDLFileManager::SDLFileManager() +{ + numCached = 0; +} + +SDLFileManager::~SDLFileManager() +{ +} + +int8_t SDLFileManager::openFile(const char *name) +{ + // See if there's a hole to re-use... + for (int i=0; i= MAXFILES) + return -1; + + + // No, so we'll add it to the end + strncpy(cachedNames[numCached], name, MAXPATH-1); + cachedNames[numCached][MAXPATH-1] = '\0'; // safety: ensure string terminator + fileSeekPositions[numCached] = 0; + + numCached++; + return numCached-1; +} + +void SDLFileManager::closeFile(int8_t fd) +{ + // invalid fd provided? + if (fd < 0 || fd >= numCached) + return; + + // clear the name + cachedNames[fd][0] = '\0'; +} + +const char *SDLFileManager::fileName(int8_t fd) +{ + if (fd < 0 || fd >= numCached) + return NULL; + + return cachedNames[fd]; +} + +int8_t SDLFileManager::readDir(const char *where, const char *suffix, char *outputFN, int8_t startIdx, uint16_t maxlen) +{ + // not used in this version + return -1; +} + +void SDLFileManager::seekBlock(int8_t fd, uint16_t block, bool isNib) +{ + if (fd < 0 || fd >= numCached) + return; + + if (isNib) { + fileSeekPositions[fd] = block * 416; + } else { + fileSeekPositions[fd] = block * 256; + } +} + + +bool SDLFileManager::readTrack(int8_t fd, uint8_t *toWhere, bool isNib) +{ + if (fd < 0 || fd >= numCached) + return false; + + if (cachedNames[fd][0] == 0) + return false; + + // open, seek, read, close. + bool ret = false; + int ffd = open(cachedNames[fd], O_RDONLY); + if (ffd) { + lseek(ffd, fileSeekPositions[fd], SEEK_SET); + if (isNib) { + ret = (read(ffd, toWhere, 0x1A00) == 0x1A00); + } else { + ret = (read(ffd, toWhere, 256 * 16) == 256 * 16); + } + close(ffd); + } + + return ret; +} + +bool SDLFileManager::readBlock(int8_t fd, uint8_t *toWhere, bool isNib) +{ + // open, seek, read, close. + if (fd < 0 || fd >= numCached) + return false; + + if (cachedNames[fd][0] == 0) + return false; + + // open, seek, read, close. + bool ret = false; + int ffd = open(cachedNames[fd], O_RDONLY); + if (ffd) { + lseek(ffd, fileSeekPositions[fd], SEEK_SET); + if (isNib) { + ret = (read(ffd, toWhere, 416) == 416); + } else { + ret = (read(ffd, toWhere, 256) == 256); + } + close(ffd); + } + + return ret; +} + +bool SDLFileManager::writeBlock(int8_t fd, uint8_t *fromWhere, bool isNib) +{ + // open, seek, write, close. + if (fd < 0 || fd >= numCached) + return false; + + if (cachedNames[fd][0] == 0) + return false; + + // don't know how to do this without seeking through the nibblized + // track data, so just give up for now + if (isNib) + return false; + + // open, seek, write, close. + int ffd = open(cachedNames[fd], O_WRONLY); + if (ffd) { + if (lseek(ffd, fileSeekPositions[fd], SEEK_SET) != fileSeekPositions[fd]) { + printf("ERROR: failed to seek to %lu\n", fileSeekPositions[fd]); + return false; + } + if (write(ffd, fromWhere, 256) != 256) { + printf("ERROR: failed to write 256 bytes\n"); + return false; + } + close(ffd); + } + return true; +} + +bool SDLFileManager::writeTrack(int8_t fd, uint8_t *fromWhere, bool isNib) +{ + // open, seek, write, close. + if (fd < 0 || fd >= numCached) + return false; + + if (cachedNames[fd][0] == 0) + return false; + + // open, seek, write, close. + int ffd = open(cachedNames[fd], O_WRONLY); + if (ffd) { + if (lseek(ffd, fileSeekPositions[fd], SEEK_SET) != fileSeekPositions[fd]) { + printf("ERROR: failed to seek to %lu\n", fileSeekPositions[fd]); + return false; + } + int16_t wrsize = 256 * 16; + if (isNib) + wrsize = 0x1A00; + + if (write(ffd, fromWhere, wrsize) != wrsize) { + printf("ERROR: failed to write bytes\n"); + return false; + } + close(ffd); + } + return true; +} diff --git a/sdl/sdl-filemanager.h b/sdl/sdl-filemanager.h new file mode 100644 index 0000000..9cff777 --- /dev/null +++ b/sdl/sdl-filemanager.h @@ -0,0 +1,31 @@ +#ifndef __SDL_FILEMANAGER_H +#define __SDL_FILEMANAGER_H + +#include "filemanager.h" +#include + +class SDLFileManager : public FileManager { + public: + SDLFileManager(); + virtual ~SDLFileManager(); + + virtual int8_t openFile(const char *name); + virtual void closeFile(int8_t fd); + + virtual const char *fileName(int8_t fd); + + virtual int8_t readDir(const char *where, const char *suffix, char *outputFN, int8_t startIdx, uint16_t maxlen); + virtual void seekBlock(int8_t fd, uint16_t block, bool isNib = false); + virtual bool readTrack(int8_t fd, uint8_t *toWhere, bool isNib = false); + virtual bool readBlock(int8_t fd, uint8_t *toWhere, bool isNib = false); + virtual bool writeBlock(int8_t fd, uint8_t *fromWhere, bool isNib = false); + virtual bool writeTrack(int8_t fd, uint8_t *fromWhere, bool isNib = false); + + private: + int8_t numCached; + char cachedNames[MAXFILES][MAXPATH]; + unsigned long fileSeekPositions[MAXFILES]; + +}; + +#endif diff --git a/sdl/sdl-keyboard.cpp b/sdl/sdl-keyboard.cpp new file mode 100644 index 0000000..9b64924 --- /dev/null +++ b/sdl/sdl-keyboard.cpp @@ -0,0 +1,157 @@ +#include "sdl-keyboard.h" + +#include "sdl-paddles.h" +#include "globals.h" + +SDLKeyboard::SDLKeyboard(VMKeyboard *k) : PhysicalKeyboard(k) +{ +} + +SDLKeyboard::~SDLKeyboard() +{ +} + +typedef struct { + int8_t actualKey; + bool shifted; +} keymapChar; + +void SDLKeyboard::handleKeypress(SDL_KeyboardEvent *key) +{ + bool releaseEvent = key->type == SDL_KEYUP; + + if ( (key->keysym.sym >= 'a' && key->keysym.sym <= 'z') || + (key->keysym.sym >= '0' && key->keysym.sym <= '9') || + key->keysym.sym == '-' || + key->keysym.sym == '=' || + key->keysym.sym == '[' || + key->keysym.sym == '`' || + key->keysym.sym == ']' || + key->keysym.sym == '\\' || + key->keysym.sym == ';' || + key->keysym.sym == '\'' || + key->keysym.sym == ',' || + key->keysym.sym == '.' || + key->keysym.sym == '/' || + key->keysym.sym == ' ' || + key->keysym.sym == 27 || // ESC + key->keysym.sym == 13 || // return + key->keysym.sym == 9) { // tab + + // Simple keypresses + if (key->keysym.mod & (KMOD_LCTRL|KMOD_RCTRL)) { + key->keysym.sym -= ('a'-1); + } + + if (releaseEvent) + vmkeyboard->keyReleased(key->keysym.sym); + else + vmkeyboard->keyDepressed(key->keysym.sym); + + return; + } + + // delete key + if (key->keysym.sym == 8) { + if (releaseEvent) + vmkeyboard->keyReleased(DEL); + else + vmkeyboard->keyDepressed(DEL); + return; + } + + //modifier handling + if (key->keysym.sym == SDLK_CAPSLOCK) { + if (releaseEvent) + vmkeyboard->keyReleased(LOCK); + else + vmkeyboard->keyDepressed(LOCK); + } + + if (key->keysym.sym == SDLK_LSHIFT || + key->keysym.sym == SDLK_RSHIFT) { + if (releaseEvent) + vmkeyboard->keyReleased(LSHFT); + else + vmkeyboard->keyDepressed(LSHFT); + } + + // arrows + if (key->keysym.sym == SDLK_LEFT) { + if (releaseEvent) + vmkeyboard->keyReleased(LARR); + else + vmkeyboard->keyDepressed(LARR); + } + if (key->keysym.sym == SDLK_RIGHT) { + if (releaseEvent) + vmkeyboard->keyReleased(RARR); + else + vmkeyboard->keyDepressed(RARR); + } + + if (key->keysym.sym == SDLK_LEFT) { + if (releaseEvent) + vmkeyboard->keyReleased(LARR); + else + vmkeyboard->keyDepressed(LARR); + } + + if (key->keysym.sym == SDLK_UP) { + if (releaseEvent) + vmkeyboard->keyReleased(UARR); + else + vmkeyboard->keyDepressed(UARR); + } + + if (key->keysym.sym == SDLK_DOWN) { + if (releaseEvent) + vmkeyboard->keyReleased(DARR); + else + vmkeyboard->keyDepressed(DARR); + } + + // Paddles + if (key->keysym.sym == SDLK_LGUI) { + if (releaseEvent) + vmkeyboard->keyReleased(LA); + else + vmkeyboard->keyDepressed(LA); + } + + if (key->keysym.sym == SDLK_RGUI) { + if (releaseEvent) + vmkeyboard->keyReleased(RA); + else + vmkeyboard->keyDepressed(RA); + } +} + + + +void SDLKeyboard::maintainKeyboard() +{ + SDL_Event event; + if (SDL_PollEvent( &event )) { + + // Handle keydown/keyup (and quit, incidentally) + switch (event.type) { + case SDL_KEYDOWN: + case SDL_KEYUP: + // Don't handle repeats; we have our own repeat code + if (event.key.repeat == 0) + handleKeypress(&event.key); + break; + case SDL_MOUSEMOTION: + // We are handling the SDL input loop, so need to pass this off to the paddles. :/ + // FIXME: nasty rooting around in other objects and typecasting. + // FIXME: event.motion.state & SDL_BUTTON_LMASK, et al? + + ((SDLPaddles *)g_paddles)->gotMouseMovement(event.motion.x, event.motion.y); + break; + + case SDL_QUIT: + exit(0); + } + } +} diff --git a/sdl/sdl-keyboard.h b/sdl/sdl-keyboard.h new file mode 100644 index 0000000..f252152 --- /dev/null +++ b/sdl/sdl-keyboard.h @@ -0,0 +1,20 @@ +#ifndef __SDL_KEYBOARD_H +#define __SDL_KEYBOARD_H + +#include "physicalkeyboard.h" +#include "vmkeyboard.h" + +#include + +class SDLKeyboard : public PhysicalKeyboard { + public: + SDLKeyboard(VMKeyboard *k); + virtual ~SDLKeyboard(); + + virtual void maintainKeyboard(); + + private: + void handleKeypress(SDL_KeyboardEvent *key); +}; + +#endif diff --git a/sdl/sdl-paddles.cpp b/sdl/sdl-paddles.cpp new file mode 100644 index 0000000..99bd8d4 --- /dev/null +++ b/sdl/sdl-paddles.cpp @@ -0,0 +1,40 @@ +#include +#include "sdl-paddles.h" + +// FIXME: abstract this somewhere + +#define WINDOWHEIGHT (240*2) +#define WINDOWWIDTH (320*2) + +#include "globals.h" + +SDLPaddles::SDLPaddles() +{ + p0 = p1 = 127; +} + +SDLPaddles::~SDLPaddles() +{ +} + +void SDLPaddles::startReading() +{ + g_vm->triggerPaddleInCycles(0, 12 * p0); + g_vm->triggerPaddleInCycles(1, 12 * p1); +} + +uint8_t SDLPaddles::paddle0() +{ + return p0; +} + +uint8_t SDLPaddles::paddle1() +{ + return p1; +} + +void SDLPaddles::gotMouseMovement(uint16_t x, uint16_t y) +{ + p0 = ((float)x / (float)WINDOWWIDTH) * (float) 255.0; + p1 = ((float)y / (float)WINDOWHEIGHT) * (float) 255.0; +} diff --git a/sdl/sdl-paddles.h b/sdl/sdl-paddles.h new file mode 100644 index 0000000..61653ad --- /dev/null +++ b/sdl/sdl-paddles.h @@ -0,0 +1,19 @@ +#include + +#include "physicalpaddles.h" + +class SDLPaddles : public PhysicalPaddles { + public: + SDLPaddles(); + virtual ~SDLPaddles(); + + virtual void startReading(); + virtual uint8_t paddle0(); + virtual uint8_t paddle1(); + + void gotMouseMovement(uint16_t x, uint16_t y); + + public: + uint8_t p0; + uint8_t p1; +}; diff --git a/sdl/sdl-printer.cpp b/sdl/sdl-printer.cpp new file mode 100644 index 0000000..f44df48 --- /dev/null +++ b/sdl/sdl-printer.cpp @@ -0,0 +1,92 @@ +#include "sdl-printer.h" + +#define WINDOWNAME "printer" + +inline void putpixel(SDL_Renderer *renderer, int x, int y, uint8_t d) +{ + uint8_t v = (d ? 0xFF : 0x00); + + SDL_SetRenderDrawColor(renderer, v, v, v, 255); + SDL_RenderDrawPoint(renderer, x, y); +} + +SDLPrinter::SDLPrinter() +{ + ypos = 0; + isDirty = false; + + memset((void *)_hackyBitmap, 0, sizeof(_hackyBitmap)); + + window = SDL_CreateWindow(WINDOWNAME, + SDL_WINDOWPOS_UNDEFINED, + SDL_WINDOWPOS_UNDEFINED, + WIDTH, HEIGHT, + SDL_WINDOW_SHOWN); + + // SDL_RENDERER_SOFTWARE because, at least on my Mac, this has some + // serious issues with hardware accelerated drawing (flashing and + // crashing). + renderer = SDL_CreateRenderer(window, -1, 0); + SDL_SetRenderDrawColor(renderer, 0, 0, 255, 255); + SDL_RenderClear(renderer); // clear it to the selected color + SDL_RenderPresent(renderer); // perform the render +} + +SDLPrinter::~SDLPrinter() +{ +} + +void SDLPrinter::update() +{ + if (isDirty) { + isDirty = false; // set early in case there's a race + + for (int y=0; y= HEIGHT) { + ypos = 0; + } + + isDirty = true; +} + +void SDLPrinter::moveDownPixels(uint8_t p) +{ + ypos+= p; + if (ypos >= HEIGHT) { + // clear page & restart + SDL_SetRenderDrawColor(renderer, 255, 255, 255, 255); + SDL_RenderClear(renderer); + isDirty = true; + ypos = 0; + } +} diff --git a/sdl/sdl-printer.h b/sdl/sdl-printer.h new file mode 100644 index 0000000..5793ac9 --- /dev/null +++ b/sdl/sdl-printer.h @@ -0,0 +1,41 @@ +#ifndef __SDL_PRINTER_H +#define __SDL_PRINTER_H + +#include + +#include +#include +#include + +#include "physicalprinter.h" + +#define HEIGHT 800 +#define NATIVEWIDTH 960 // FIXME: printer can change density... + + +//#define WIDTH 384 // emulating the teeny printer I've got +#define WIDTH 960 + + +class SDLPrinter : public PhysicalPrinter { + public: + SDLPrinter(); + virtual ~SDLPrinter(); + + virtual void addLine(uint8_t *rowOfBits); // must be 960 pixels wide (120 bytes) + + virtual void update(); + + virtual void moveDownPixels(uint8_t p); + + private: + bool isDirty; + uint16_t ypos; + + SDL_Window *window; + SDL_Renderer *renderer; + bool _hackyBitmap[WIDTH * HEIGHT]; + +}; + +#endif diff --git a/sdl/sdl-speaker.cpp b/sdl/sdl-speaker.cpp new file mode 100644 index 0000000..915f7cf --- /dev/null +++ b/sdl/sdl-speaker.cpp @@ -0,0 +1,157 @@ +#include "sdl-speaker.h" +#include + +extern "C" +{ +#include +#include +}; + +#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 pthread_mutex_t mutex = 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) +{ + pthread_mutex_lock(&mutex); + if (bufIdx >= len) { + memcpy(stream, soundBuf, len); + if (bufIdx > len) { + // move the remaining data down + memcpy(soundBuf, &soundBuf[len], bufIdx - len); + bufIdx -= len; + copycount += len; + } + } else { + // Audio underrun + memset(stream, 0, len); + } + pthread_mutex_unlock(&mutex); +} + +static void *speaker_thread(void *dummyptr) { + struct timespec currentTime; + struct timespec startTime; + struct timespec nextSampleTime; + + pthread_mutex_init(&mutex, NULL); + + SDL_AudioSpec audioDevice; + SDL_AudioSpec audioActual; + SDL_memset(&audioDevice, 0, sizeof(audioDevice)); + audioDevice.freq = 22050; + 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.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); + + _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(&mutex); + soundBuf[bufIdx++] = curSpeakerData & 0xFF; + if (bufIdx >= sizeof(soundBuf)) { + // Audio overrun; start dropping data + bufIdx--; + } + pthread_mutex_unlock(&mutex); + + // 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; + needsToggle = false; + mixerValue = 0; + _init_darwin_shim(); // set up the clock interface + + if (!pthread_create(&speakerThreadID, NULL, &speaker_thread, (void *)NULL)) { + printf("speaker thread created\n"); + } +} + +SDLSpeaker::~SDLSpeaker() +{ + pclose(f); +} + +void SDLSpeaker::toggle() +{ + needsToggle = true; +} + +void SDLSpeaker::maintainSpeaker(uint32_t c) +{ + if (needsToggle) { + // Override the mixer with a 1-bit "Terribad" audio sample change + toggleState = !toggleState; + needsToggle = false; + } + // The "terribad" audio gets two shares. THis means we've got 8 total + // "voices" -- 6 from the mockingboard and 2 from the built-in. 8 is + // a great number for dividing. :) + mixerValue += (toggleState ? 0xFF : 0x00); + numMixed += 2; + +#if DEBUGGING + if (numMixed != 8) { + printf("SPEAKER FAIL - should always be 8\n"); + } +#endif + + mixerValue >>= 3; // divide by 8 + + curSpeakerData = mixerValue & 0xFF; +} + +void SDLSpeaker::beginMixing() +{ + mixerValue = 0; + numMixed = 0; +} + +void SDLSpeaker::mixOutput(uint8_t v) +{ + mixerValue += v; + numMixed++; +} diff --git a/sdl/sdl-speaker.h b/sdl/sdl-speaker.h new file mode 100644 index 0000000..0d103a5 --- /dev/null +++ b/sdl/sdl-speaker.h @@ -0,0 +1,26 @@ +#ifndef __SDLSPEAKER_H +#define __SDLSPEAKER_H + +#include +#include +#include "physicalspeaker.h" + +class SDLSpeaker : public PhysicalSpeaker { + public: + SDLSpeaker(); + virtual ~SDLSpeaker(); + + virtual void toggle(); + virtual void maintainSpeaker(uint32_t c); + virtual void beginMixing(); + virtual void mixOutput(uint8_t v); + private: + uint32_t mixerValue; + uint8_t numMixed; + bool toggleState; + bool needsToggle; + + FILE *f; +}; + +#endif diff --git a/sdl/timeutil.h b/sdl/timeutil.h new file mode 100644 index 0000000..d5edb94 --- /dev/null +++ b/sdl/timeutil.h @@ -0,0 +1,143 @@ +#include +#include +// Derived from +// http://stackoverflow.com/questions/5167269/clock-gettime-alternative-in-mac-os-x + +#define ORWL_NANO (+1.0E-9) +#define ORWL_GIGA UINT64_C(1000000000) +#define NANOSECONDS_PER_SECOND 1000000000UL +#define CYCLES_PER_SECOND 1023000UL +#define NANOSECONDS_PER_CYCLE (NANOSECONDS_PER_SECOND / CYCLES_PER_SECOND) + +static double orwl_timebase = 0.0; +static uint64_t orwl_timestart = 0; +static void _init_darwin_shim(void) { + mach_timebase_info_data_t tb = { 0 }; + mach_timebase_info(&tb); + orwl_timebase = tb.numer; + orwl_timebase /= tb.denom; + orwl_timestart = mach_absolute_time(); +} + +static int do_gettime(struct timespec *tp) { + double diff = (mach_absolute_time() - orwl_timestart) * orwl_timebase; + tp->tv_sec = diff * ORWL_NANO; + tp->tv_nsec = diff - (tp->tv_sec * ORWL_GIGA); + return 0; +} + +// adds the number of microseconds that 'cycles' takes to *start and +// returns it in *out +static void timespec_add_cycles(struct timespec *start, + uint32_t cycles, + struct timespec *out) +{ + out->tv_sec = start->tv_sec; + out->tv_nsec = start->tv_nsec; + + uint64_t nanosToAdd = NANOSECONDS_PER_CYCLE * cycles; + out->tv_sec += (nanosToAdd / NANOSECONDS_PER_SECOND); + out->tv_nsec += (nanosToAdd % NANOSECONDS_PER_SECOND); + + if (out->tv_nsec >= 1000000000L) { + out->tv_sec++ ; + out->tv_nsec -= 1000000000L; + } +} + +// adds the number of microseconds given to *start and +// returns it in *out +static void timespec_add_us(struct timespec *start, + uint64_t micros, + struct timespec *out) +{ + out->tv_sec = start->tv_sec; + out->tv_nsec = start->tv_nsec; + + uint64_t nanosToAdd = micros * 1000L; + out->tv_sec += (nanosToAdd / NANOSECONDS_PER_SECOND); + out->tv_nsec += (nanosToAdd % NANOSECONDS_PER_SECOND); + + if (out->tv_nsec >= 1000000000L) { + out->tv_sec++ ; + out->tv_nsec -= 1000000000L; + } +} + +static void timespec_diff(struct timespec *start, + struct timespec *end, + struct timespec *diff, + bool *negative) { + struct timespec t; + + if (negative) + { + *negative = false; + } + + // if start > end, swizzle... + if ( (start->tv_sec > end->tv_sec) || ((start->tv_sec == end->tv_sec) && (start->tv_nsec > end->tv_nsec)) ) + { + t=*start; + *start=*end; + *end=t; + if (negative) + { + *negative = true; + } + } + + // assuming time_t is signed ... + if (end->tv_nsec < start->tv_nsec) + { + t.tv_sec = end->tv_sec - start->tv_sec - 1; + t.tv_nsec = 1000000000 + end->tv_nsec - start->tv_nsec; + } + else + { + t.tv_sec = end->tv_sec - start->tv_sec; + t.tv_nsec = end->tv_nsec - start->tv_nsec; + } + + diff->tv_sec = t.tv_sec; + diff->tv_nsec = t.tv_nsec; +} + +// tsCompare: return -1, 0, 1 for (a < b), (a == b), (a > b) +static int8_t tsCompare(struct timespec *A, struct timespec *B) +{ + if (A->tv_sec < B->tv_sec) + return -1; + + if (A->tv_sec > B->tv_sec) + return 1; + + if (A->tv_nsec < B->tv_nsec) + return -1; + + if (A->tv_nsec > B->tv_nsec) + return 1; + + return 0; +} + +static struct timespec tsSubtract(struct timespec time1, struct timespec time2) +{ + struct timespec result; + if ((time1.tv_sec < time2.tv_sec) || + ((time1.tv_sec == time2.tv_sec) && + (time1.tv_nsec <= time2.tv_nsec))) {/* TIME1 <= TIME2? */ + result.tv_sec = result.tv_nsec = 0 ; + } else {/* TIME1 > TIME2 */ + result.tv_sec = time1.tv_sec - time2.tv_sec ; + if (time1.tv_nsec < time2.tv_nsec) { + result.tv_nsec = time1.tv_nsec + 1000000000L - time2.tv_nsec ; + result.tv_sec-- ;/* Borrow a second. */ + } else { + result.tv_nsec = time1.tv_nsec - time2.tv_nsec ; + } + } + + return (result) ; +} + diff --git a/teensy/ay8910.cpp b/teensy/ay8910.cpp new file mode 120000 index 0000000..f13be59 --- /dev/null +++ b/teensy/ay8910.cpp @@ -0,0 +1 @@ +../apple/ay8910.cpp \ No newline at end of file diff --git a/teensy/ay8910.h b/teensy/ay8910.h new file mode 120000 index 0000000..a1daa2d --- /dev/null +++ b/teensy/ay8910.h @@ -0,0 +1 @@ +../apple/ay8910.h \ No newline at end of file diff --git a/teensy/bios.cpp b/teensy/bios.cpp index 2428b0a..f789e06 100644 --- a/teensy/bios.cpp +++ b/teensy/bios.cpp @@ -98,6 +98,7 @@ bool BIOS::runUntilDone() case ACT_DISPLAYTYPE: g_displayType++; g_displayType %= 4; // FIXME: abstract max # + ((AppleDisplay*)g_display)->displayTypeChanged(); break; case ACT_DEBUG: debugMode++; @@ -143,7 +144,7 @@ bool BIOS::runUntilDone() done: // Undo whatever damage we've done to the screen g_display->redraw(); - g_display->blit(); + g_display->blit({0, 0, 279, 191}); // return true if any persistent setting changed that we want to store in eeprom return volumeDidChange; diff --git a/teensy/mockingboard.cpp b/teensy/mockingboard.cpp new file mode 120000 index 0000000..9447a5d --- /dev/null +++ b/teensy/mockingboard.cpp @@ -0,0 +1 @@ +../apple/mockingboard.cpp \ No newline at end of file diff --git a/teensy/mockingboard.h b/teensy/mockingboard.h new file mode 120000 index 0000000..21098c2 --- /dev/null +++ b/teensy/mockingboard.h @@ -0,0 +1 @@ +../apple/mockingboard.h \ No newline at end of file diff --git a/teensy/sy6522.cpp b/teensy/sy6522.cpp new file mode 120000 index 0000000..7c598de --- /dev/null +++ b/teensy/sy6522.cpp @@ -0,0 +1 @@ +../apple/sy6522.cpp \ No newline at end of file diff --git a/teensy/sy6522.h b/teensy/sy6522.h new file mode 120000 index 0000000..944e80d --- /dev/null +++ b/teensy/sy6522.h @@ -0,0 +1 @@ +../apple/sy6522.h \ No newline at end of file diff --git a/teensy/teensy-display.cpp b/teensy/teensy-display.cpp index b51b446..f097df0 100644 --- a/teensy/teensy-display.cpp +++ b/teensy/teensy-display.cpp @@ -366,7 +366,7 @@ void TeensyDisplay::drawNextPixel(uint16_t color) setPixel(color); } -void TeensyDisplay::blit() +void TeensyDisplay::blit(AiieRect r) { uint8_t *videoBuffer = g_vm->videoBuffer; // FIXME: poking deep @@ -375,12 +375,12 @@ void TeensyDisplay::blit() #define VOFFSET 13 // Define the horizontal area that we're going to draw in - LCD_Write_COM_DATA(0x45, HOFFSET); // offset by 20 to center it... - LCD_Write_COM_DATA(0x46, 279+HOFFSET); + LCD_Write_COM_DATA(0x45, HOFFSET+r.left); // offset by 20 to center it... + LCD_Write_COM_DATA(0x46, HOFFSET+r.right); // position the "write" address - LCD_Write_COM_DATA(0x4e,0+VOFFSET); // row - LCD_Write_COM_DATA(0x4f,HOFFSET); // col + LCD_Write_COM_DATA(0x4e,VOFFSET+r.top); // row + LCD_Write_COM_DATA(0x4f,HOFFSET+r.left); // col // prepare the LCD to receive data bytes for its RAM LCD_Write_COM(0x22); @@ -388,12 +388,11 @@ void TeensyDisplay::blit() // send the pixel data sbi(P_RS, B_RS); uint16_t pixel; - for (uint8_t y=0; y<192; y++) { // Drawing 192 of the 240 rows - pixel = y * (320/2)-1; - for (uint16_t x=0; x<280; x++) { // Drawing 280 of the 320 pixels + for (uint8_t y=r.top; y<=r.bottom; y++) { + for (uint16_t x=r.left; x<=r.right; x++) { + pixel = y * (DISPLAYWIDTH/2) + (x/2); uint8_t colorIdx; if (!(x & 0x01)) { - pixel++; colorIdx = videoBuffer[pixel] >> 4; } else { colorIdx = videoBuffer[pixel] & 0x0F; diff --git a/teensy/teensy-display.h b/teensy/teensy-display.h index 00163d3..53c0b9b 100644 --- a/teensy/teensy-display.h +++ b/teensy/teensy-display.h @@ -34,7 +34,7 @@ class TeensyDisplay : public PhysicalDisplay { TeensyDisplay(); virtual ~TeensyDisplay(); - virtual void blit(); + virtual void blit(AiieRect r); virtual void redraw(); void clrScr(); diff --git a/teensy/teensy-keyboard.cpp b/teensy/teensy-keyboard.cpp index f1c3e9d..8766ea4 100644 --- a/teensy/teensy-keyboard.cpp +++ b/teensy/teensy-keyboard.cpp @@ -228,4 +228,11 @@ void TeensyKeyboard::maintainKeyboard() } } } + + // For debugging: also allow USB serial to act as a keyboard + if (Serial.available()) { + int c = Serial.read(); + vmkeyboard->keyDepressed(c); + vmkeyboard->keyReleased(c); + } } diff --git a/teensy/teensy-printer.cpp b/teensy/teensy-printer.cpp index a2ce4e5..65efb9e 100644 --- a/teensy/teensy-printer.cpp +++ b/teensy/teensy-printer.cpp @@ -9,6 +9,8 @@ TeensyPrinter::TeensyPrinter() { +#if 0 + // debugging ser = new SoftwareSerial(RXPIN, TXPIN, false); ser->begin(19200); char buf[6] = { 27, '@', // init command '@' @@ -16,6 +18,7 @@ TeensyPrinter::TeensyPrinter() 0 // terminator }; ser->print(buf); +#endif } TeensyPrinter::~TeensyPrinter() @@ -29,6 +32,9 @@ void TeensyPrinter::update() void TeensyPrinter::addLine(uint8_t *rowOfBits) { + if (!ser) + return; + static uint8_t linebuf[WIDTH/8]; // output data for one line of pixels // The rowOfBits is a set of *rows* of bits. The printer needs *columns*. diff --git a/teensy/teensy-speaker.cpp b/teensy/teensy-speaker.cpp index 326365b..87722d5 100644 --- a/teensy/teensy-speaker.cpp +++ b/teensy/teensy-speaker.cpp @@ -5,34 +5,50 @@ extern int16_t g_volume; TeensySpeaker::TeensySpeaker(uint8_t pinNum) : PhysicalSpeaker() { - speakerState = false; + toggleState = false; + needsToggle = false; speakerPin = pinNum; pinMode(speakerPin, OUTPUT); // analog speaker output, used as digital volume control - nextTransitionAt = 0; + mixerValue = numMixed = 0; } TeensySpeaker::~TeensySpeaker() { } -void TeensySpeaker::toggleAtCycle(uint32_t c) +void TeensySpeaker::toggle() { - // FIXME: could tell here if we dropped something - is nextTransitionAt already set? If so, we missed a toggle :( - nextTransitionAt = c; + needsToggle = true; } void TeensySpeaker::maintainSpeaker(uint32_t c) { - if (nextTransitionAt && c >= nextTransitionAt) { - nextTransitionAt = 0; - - speakerState = !speakerState; - // FIXME: glad it's DAC0 and all, but... how does that relate to the pin passed in the constructor? - analogWriteDAC0(speakerState ? g_volume : 0); // max: 4095 + if (needsToggle) { + toggleState = !toggleState; + needsToggle = false; } + + mixerValue += (toggleState ? 0xFFF : 0x00); + // FIXME: Temporarily disabling mixer + /* numMixed += 2; + + if (numMixed > 1) { + mixerValue /= numMixed; + }*/ + + // FIXME: glad it's DAC0 and all, but... how does that relate to the pin passed in the constructor? + analogWriteDAC0(mixerValue); // FIXME: g_volume? } -bool TeensySpeaker::currentState() +void TeensySpeaker::beginMixing() { - return speakerState; + mixerValue = 0; + numMixed = 0; } + +void TeensySpeaker::mixOutput(uint8_t v) +{ + mixerValue += v; + numMixed++; +} + diff --git a/teensy/teensy-speaker.h b/teensy/teensy-speaker.h index 3f39cc9..da013c4 100644 --- a/teensy/teensy-speaker.h +++ b/teensy/teensy-speaker.h @@ -8,16 +8,20 @@ class TeensySpeaker : public PhysicalSpeaker { TeensySpeaker(uint8_t pinNum); virtual ~TeensySpeaker(); - virtual void toggleAtCycle(uint32_t c); + virtual void toggle(); virtual void maintainSpeaker(uint32_t c); - virtual bool currentState(); + + virtual void beginMixing(); + virtual void mixOutput(uint8_t v); private: - bool speakerState; uint8_t speakerPin; - uint32_t nextTransitionAt; + bool toggleState; + bool needsToggle; + uint32_t mixerValue; + uint8_t numMixed; }; #endif diff --git a/teensy/teensy.ino b/teensy/teensy.ino index f3fff9d..716f0aa 100644 --- a/teensy/teensy.ino +++ b/teensy/teensy.ino @@ -1,5 +1,4 @@ #include -#include #include // uSDFS #include #include @@ -17,8 +16,6 @@ #define BATTERYPIN A19 #define SPEAKERPIN A21 -//#define DEBUGCPU - #include "globals.h" #include "teensy-crash.h" @@ -28,7 +25,7 @@ volatile float startMicros; FATFS fatfs; /* File system object */ BIOS bios; -uint8_t videoBuffer[320*240/2]; +uint8_t videoBuffer[DISPLAYWIDTH*DISPLAYHEIGHT/2]; enum { D_NONE = 0, @@ -100,7 +97,8 @@ void setup() analogReference(EXTERNAL); // 3.3v external, instead of 1.7v internal analogReadRes(8); // We only need 8 bits of resolution (0-255) for battery & paddles analogReadAveraging(4); // ?? dunno if we need this or not. - + analogWriteResolution(12); + pinMode(SPEAKERPIN, OUTPUT); // analog speaker output, used as digital volume control pinMode(BATTERYPIN, INPUT); @@ -146,7 +144,7 @@ void setup() g_vm->Reset(); g_display->redraw(); - g_display->blit(); +// g_display->blit(); Serial.println("Reading prefs"); readPrefs(); // read from eeprom and set anything we need setting @@ -155,73 +153,14 @@ void setup() startMicros = 0; nextInstructionMicros = micros(); - Timer1.initialize(3); - Timer1.attachInterrupt(runCPU); - Timer1.start(); -} -/* We're running the timer that calls this at 1/3 "normal" speed, and - * then asking runCPU to run 48 steps (individual opcodes executed) of - * the CPU before returning. Then we figure out how many cycles - * elapsed during that run, and keep track of how many cycles we now - * have to "drain off" (how many extra ran during this attempt -- we - * expected at least 3, but might have gotten more). Then the next - * call here from the interrupt subtracts 3 cycles, on the assumption - * that 3 have passed, and we're good to go. - * - * This approach is reasonable: the 6502 instruction set takes an - * average of 4 clock cycles to execute. This compromise keeps us from - * chewing up the entire CPU on interrupt overhead, allowing us to - * focus on refreshing the LCD as fast as possible while sacrificing - * some small timing differences. Experimentally, paddle values seem - * to still read well up to 48 steps. At 2*48, the paddles drift at - * the low end, meaning there's probably an issue with timing. - */ -void runCPU() -{ - // static bool outputState = false; - // outputState = !outputState; - // digitalWrite(56, outputState); + // 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); - if (micros() >= nextInstructionMicros) { -#ifdef DEBUGCPU - g_cpu->Run(1); -#else - g_cpu->Run(24); -#endif - - // 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%. - -#ifdef DEBUGCPU - // ... have to slow down so the printing all works - nextInstructionMicros = startMicros + (float)g_cpu->cycles * 50; -#else - nextInstructionMicros = startMicros + (float)g_cpu->cycles * 0.978; -#endif - -#ifdef DEBUGCPU - { - uint8_t p = g_cpu->flags; - Serial.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 - } - - g_speaker->beginMixing(); - ((AppleVM *)g_vm)->cpuMaintenance(g_cpu->cycles); - g_speaker->maintainSpeaker(g_cpu->cycles); + pinMode(56, OUTPUT); + pinMode(57, OUTPUT); } // FIXME: move these memory-related functions elsewhere... @@ -252,9 +191,6 @@ int heapSize(){ void biosInterrupt() { - // Shut down the CPU - Timer1.stop(); - // wait for the interrupt button to be released while (digitalRead(RESETPIN) == LOW) ; @@ -280,18 +216,35 @@ void biosInterrupt() // Poll the keyboard before we start, so we can do selftest on startup g_keyboard->maintainKeyboard(); - - // Restart the CPU - Timer1.start(); } +bool debugState = false; +bool debugLCDState = false; void loop() { - static uint16_t ctr = -1; - /* testing the fault handler? uncomment this and it'll crash. */ - // *((int*)0x0) = 1; + if (micros() >= nextInstructionMicros) { + debugState = !debugState; + digitalWrite(56, debugState); + + g_cpu->Run(24); + + // Only update the speaker and CPU when we've moved the CPU forward (or there's nothing to do). + g_speaker->beginMixing(); + ((AppleVM *)g_vm)->cpuMaintenance(g_cpu->cycles); + g_speaker->maintainSpeaker(g_cpu->cycles); + + // 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; + + // Don't update the LCD if we had to run the CPU: if we need + // multiple concurrent runs to catch up, then we want to catch up. + return; + } static unsigned long nextBattCheck = 0; static int batteryLevel = 0; // static for debugging code! When done @@ -320,64 +273,69 @@ void loop() ((AppleVM *)g_vm)->batteryLevel( batteryLevel ); } - if ((++ctr & 0xFF) == 0) { - if (digitalRead(RESETPIN) == LOW) { - // This is the BIOS interrupt. We immediately act on it. - biosInterrupt(); - } + if (digitalRead(RESETPIN) == LOW) { + // This is the BIOS interrupt. We immediately act on it. + biosInterrupt(); + } - if (g_vm->vmdisplay->needsRedraw()) { - // make sure to clear the flag before drawing; there's no lock - // on didRedraw, so the other thread might update it - g_vm->vmdisplay->didRedraw(); - g_display->blit(); - } - - g_keyboard->maintainKeyboard(); - - { - char buf[25]; - switch (debugMode) { - case D_SHOWFPS: - // display some FPS data - static uint32_t startAt = millis(); - static uint32_t loopCount = 0; - loopCount++; - time_t lenSecs; - lenSecs = (millis() - startAt) / 1000; - if (lenSecs >= 5) { - sprintf(buf, "%lu FPS", loopCount / lenSecs); - g_display->debugMsg(buf); - startAt = millis(); - loopCount = 0; - } - break; - case D_SHOWMEMFREE: - sprintf(buf, "%lu %u", FreeRamEstimate(), heapSize()); - g_display->debugMsg(buf); - break; - case D_SHOWPADDLES: - sprintf(buf, "%u %u", g_paddles->paddle0(), g_paddles->paddle1()); - g_display->debugMsg(buf); - break; - case D_SHOWPC: - sprintf(buf, "%X", g_cpu->pc); - g_display->debugMsg(buf); - break; - case D_SHOWCYCLES: - sprintf(buf, "%lX", g_cpu->cycles); - g_display->debugMsg(buf); - break; - case D_SHOWBATTERY: - sprintf(buf, "BAT %d", analogRead(BATTERYPIN)); - g_display->debugMsg(buf); - break; - case D_SHOWTIME: - sprintf(buf, "%.2d:%.2d:%.2d", hour(), minute(), second()); - g_display->debugMsg(buf); - break; - } + if (g_vm->vmdisplay->needsRedraw()) { + digitalWrite(57, HIGH); + AiieRect what = g_vm->vmdisplay->getDirtyRect(); + // Clear the flag before redrawing. Not specifically required + // any longer now that we're not running out of an interrupt, + // but still safe. + g_vm->vmdisplay->didRedraw(); + g_display->blit(what); + digitalWrite(57, LOW); + } + + g_keyboard->maintainKeyboard(); + + doDebugging(); +} + +void doDebugging() +{ + char buf[25]; + switch (debugMode) { + case D_SHOWFPS: + // display some FPS data + static uint32_t startAt = millis(); + static uint32_t loopCount = 0; + loopCount++; + time_t lenSecs; + lenSecs = (millis() - startAt) / 1000; + if (lenSecs >= 5) { + sprintf(buf, "%lu FPS", loopCount / lenSecs); + g_display->debugMsg(buf); + startAt = millis(); + loopCount = 0; } + break; + case D_SHOWMEMFREE: + sprintf(buf, "%lu %u", FreeRamEstimate(), heapSize()); + g_display->debugMsg(buf); + break; + case D_SHOWPADDLES: + sprintf(buf, "%u %u", g_paddles->paddle0(), g_paddles->paddle1()); + g_display->debugMsg(buf); + break; + case D_SHOWPC: + sprintf(buf, "%X", g_cpu->pc); + g_display->debugMsg(buf); + break; + case D_SHOWCYCLES: + sprintf(buf, "%lX", g_cpu->cycles); + g_display->debugMsg(buf); + break; + case D_SHOWBATTERY: + sprintf(buf, "BAT %d", analogRead(BATTERYPIN)); + g_display->debugMsg(buf); + break; + case D_SHOWTIME: + sprintf(buf, "%.2d:%.2d:%.2d", hour(), minute(), second()); + g_display->debugMsg(buf); + break; } } @@ -419,12 +377,8 @@ void readPrefs() g_volume = 0; } -// Writes to EEPROM slow down the Teensy 3.6's CPU to 120MHz automatically. Disable our timer -// while we're doing it and we'll just see a pause. void writePrefs() { - Timer1.stop(); - Serial.println("writing prefs"); prefs p; @@ -436,6 +390,4 @@ void writePrefs() for (uint8_t i=0; immu = mmu; } diff --git a/vmdisplay.h b/vmdisplay.h index c9e6655..d0e2a27 100644 --- a/vmdisplay.h +++ b/vmdisplay.h @@ -3,6 +3,13 @@ class MMU; +typedef struct { + uint8_t top; + uint16_t left; + uint8_t bottom; + uint16_t right; +} AiieRect; + class VMDisplay { public: VMDisplay(uint8_t *vb) { videoBuffer = vb; } @@ -12,6 +19,7 @@ class VMDisplay { virtual bool needsRedraw() = 0; virtual void didRedraw() = 0; + virtual AiieRect getDirtyRect() = 0; MMU *mmu; uint8_t *videoBuffer;