From e942d8a4dd954aeb70525956a5ca43ea0fa96a93 Mon Sep 17 00:00:00 2001 From: Jorj Bauer Date: Fri, 24 Feb 2017 10:15:17 -0500 Subject: [PATCH] working on mockingboard support --- Makefile | 2 +- README.md | 8 ++ apple/applemmu.cpp | 12 +++ apple/applevm.cpp | 6 ++ apple/applevm.h | 2 + apple/ay8910.cpp | 129 ++++++++++++++++++++++++++++++++ apple/ay8910.h | 28 +++++++ apple/diskii.cpp | 4 +- apple/mockingboard.cpp | 69 +++++++++++++++++ apple/mockingboard.h | 34 +++++++++ apple/sy6522.cpp | 151 ++++++++++++++++++++++++++++++++++++++ apple/sy6522.h | 54 ++++++++++++++ opencv/aiie.cpp | 129 ++------------------------------ opencv/dummy-speaker.cpp | 86 ++++++++++++++++++++++ opencv/dummy-speaker.h | 13 ++++ opencv/timeutil.h | 143 ++++++++++++++++++++++++++++++++++++ physicalspeaker.h | 3 + teensy/slot.cpp | 1 - teensy/teensy-speaker.cpp | 5 ++ teensy/teensy-speaker.h | 1 + teensy/teensy.ino | 3 +- 21 files changed, 756 insertions(+), 127 deletions(-) create mode 100644 apple/ay8910.cpp create mode 100644 apple/ay8910.h create mode 100644 apple/mockingboard.cpp create mode 100644 apple/mockingboard.h create mode 100644 apple/sy6522.cpp create mode 100644 apple/sy6522.h create mode 100644 opencv/timeutil.h delete mode 120000 teensy/slot.cpp diff --git a/Makefile b/Makefile index 135bc71..369ce0b 100755 --- a/Makefile +++ b/Makefile @@ -4,7 +4,7 @@ CXXFLAGS=-Wall -I .. -I . -I apple -I opencv -O3 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 +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 ROMS=apple/applemmu-rom.h apple/diskii-rom.h apple/parallel-rom.h diff --git a/README.md b/README.md index 7b413d4..ed71e1c 100644 --- a/README.md +++ b/README.md @@ -218,6 +218,14 @@ another project lying around that directly manipulated OpenCV bitmap data. It's functional, and the Mac build is only about functional testing (for me). +Mockingboard +============ + +Mockingboard support is slowly taking shape, based on the schematic in +the Apple II Documentation Project: + +https://mirrors.apple2.org.za/Apple%20II%20Documentation%20Project/Interface%20Cards/Audio/Sweet%20Microsystems%20Mockingboard/Schematics/Mockingboard%20Schematic.gif + VM == diff --git a/apple/applemmu.cpp b/apple/applemmu.cpp index e0ec1ec..1a5149e 100644 --- a/apple/applemmu.cpp +++ b/apple/applemmu.cpp @@ -10,6 +10,7 @@ #include "applemmu-rom.h" #include "physicalspeaker.h" #include "cpu.h" +#include "mockingboard.h" #include "globals.h" @@ -69,6 +70,11 @@ uint8_t AppleMMU::read(uint16_t address) address <= 0xC0FF) { return readSwitches(address); } + + // FIXME: assumes slot 4 is a mockingboard + if (slots[4] && address >= 0xC400 && address <= 0xC4FF) { + return ((Mockingboard *)slots[4])->read(address); + } uint8_t res = readPages[(address & 0xFF00) >> 8][address & 0xFF]; @@ -88,6 +94,12 @@ void AppleMMU::write(uint16_t address, uint8_t v) return writeSwitches(address, v); } + // FIXME: assumes slot4 is a mockingboard + if (slots[4] && address >= 0xC400 && address <= 0xC4FF) { + ((Mockingboard *)slots[4])->write(address, v); + return; + } + // Don't allow writes to ROM if (address >= 0xC100 && address <= 0xCFFF) return; diff --git a/apple/applevm.cpp b/apple/applevm.cpp index c752186..3b72451 100644 --- a/apple/applevm.cpp +++ b/apple/applevm.cpp @@ -23,6 +23,9 @@ AppleVM::AppleVM() parallel = new ParallelCard(); ((AppleMMU *)mmu)->setSlot(1, parallel); + mockingboard = new Mockingboard(); + ((AppleMMU *)mmu)->setSlot(4, mockingboard); + #ifdef TEENSYDUINO teensyClock = new TeensyClock((AppleMMU *)mmu); ((AppleMMU *)mmu)->setSlot(7, teensyClock); @@ -35,6 +38,8 @@ AppleVM::~AppleVM() delete teensyClock; #endif delete disk6; + delete parallel; + delete mockingboard; } // fixme: make member vars @@ -56,6 +61,7 @@ void AppleVM::cpuMaintenance(uint32_t cycles) keyboard->maintainKeyboard(cycles); parallel->update(); + mockingboard->update(cycles); } void AppleVM::Reset() diff --git a/apple/applevm.h b/apple/applevm.h index 4e1fce4..0f4b4c8 100644 --- a/apple/applevm.h +++ b/apple/applevm.h @@ -6,6 +6,7 @@ #include "diskii.h" #include "vmkeyboard.h" #include "parallelcard.h" +#include "mockingboard.h" #ifdef TEENSYDUINO #include "teensy-clock.h" #endif @@ -34,6 +35,7 @@ class AppleVM : public VM { DiskII *disk6; VMKeyboard *keyboard; ParallelCard *parallel; + Mockingboard *mockingboard; #ifdef TEENSYDUINO TeensyClock *teensyClock; #endif diff --git a/apple/ay8910.cpp b/apple/ay8910.cpp new file mode 100644 index 0000000..7443145 --- /dev/null +++ b/apple/ay8910.cpp @@ -0,0 +1,129 @@ +#include "ay8910.h" +#include + +#include "globals.h" + +AY8910::AY8910() +{ + Reset(); +} + +void AY8910::Reset() +{ + printf("AY8910 reset\n"); + curRegister = 0; + for (uint8_t i=0; i<16; i++) + r[i] = 0xFF; + waveformFlipTimer[0] = waveformFlipTimer[1] = waveformFlipTimer[2] = 0; + outputState[0] = outputState[1] = outputState[2] = 0; +} + +uint8_t AY8910::read(uint8_t reg) +{ + // FIXME: does anything ever need to read from this? + return 0xFF; +} + +// reg represents BC1, BDIR, /RST in bits 0, 1, 2. +// val is the state of those three bits. +// PortA is the state of whatever's currently on PortA when we do it. +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) { + Reset(); + return; + } + + // Bit 0 (1 << 0 == 0x01) is the BC1 pin. BC2 is hard-wired to +5v. + // We can ignore bit 3, b/c that was just checked above & triggered + // a reset. + reg &= ~0x04; + + switch (reg) { + case 0: // 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) + // Contents of the currently addressed register are put in DA. FIXME? + return; + case 2: // 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) { + cycleTime[0] = cycleTimeForPSG(0); + cycleTime[1] = cycleTimeForPSG(1); + cycleTime[2] = cycleTimeForPSG(2); + } + + return; + case 3: // bDir==1 && BC1 == 1 (INTAK) + // Select current register + curRegister = PortA & 0xF; + return; + } +} + +// The lowest frequency the AY8910 makes is 30.6 Hz, which is ~33431 +// clock cycles. +// +// The highest frequency produced is 125kHz, which is ~8 cycles. +// +// The highest practicable, given our 24-cycle-main-loop, is +// 41kHz. Which should be plenty fine. +// +// Conversely: we should be able to call update() as slowly as once +// every 60-ish clock cycles before we start noticing it in the output +// audio. +uint16_t AY8910::cycleTimeForPSG(uint8_t psg) +{ + // Convert the current registers in to a cycle count for how long + // between flips of 0-to-1 from the square wave generator. + + uint16_t regVal = (r[1+(psg*2)] << 8) | (r[0 + (psg*2)]); + if (regVal == 0) regVal++; + + // Ft = 4MHz / (32 * regVal); our clock is 1MHz + // so we should return (32 * regVal) / 4 ? + + return (32 * regVal) / 4; +} + +void AY8910::update(uint32_t cpuCycleCount) +{ + // 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) { + // flip when it's time to flip + waveformFlipTimer[i] += cc; + outputState[i] = !outputState[i]; + } + } + // 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); + } +} + diff --git a/apple/ay8910.h b/apple/ay8910.h new file mode 100644 index 0000000..15f71f5 --- /dev/null +++ b/apple/ay8910.h @@ -0,0 +1,28 @@ +#ifndef __AY8910_H +#define __AY8910_H + +#include + +class AY8910 { + public: + AY8910(); + + void Reset(); + + uint8_t read(uint8_t reg); + void write(uint8_t reg, uint8_t PortA); + + void update(uint32_t cpuCycleCount); + + protected: + uint16_t cycleTimeForPSG(uint8_t psg); + + private: + uint8_t curRegister; + uint8_t r[16]; + uint32_t waveformFlipTimer[3]; + uint8_t outputState[3]; + uint16_t cycleTime[3]; +}; + +#endif diff --git a/apple/diskii.cpp b/apple/diskii.cpp index 5eb8a53..a270cf9 100644 --- a/apple/diskii.cpp +++ b/apple/diskii.cpp @@ -411,12 +411,12 @@ const char *DiskII::DiskName(int8_t num) void DiskII::loadROM(uint8_t *toWhere) { #ifdef TEENSYDUINO - Serial.println("loading slot rom"); + Serial.println("loading DiskII rom"); for (uint16_t i=0; i<=0xFF; i++) { toWhere[i] = pgm_read_byte(&romData[i]); } #else - printf("loading slot rom\n"); + printf("loading DiskII rom\n"); memcpy(toWhere, romData, 256); #endif } diff --git a/apple/mockingboard.cpp b/apple/mockingboard.cpp new file mode 100644 index 0000000..ba8938d --- /dev/null +++ b/apple/mockingboard.cpp @@ -0,0 +1,69 @@ +#include "mockingboard.h" +#include + +Mockingboard::Mockingboard() +{ +} + +Mockingboard::~Mockingboard() +{ +} + +void Mockingboard::Reset() +{ +} + +uint8_t Mockingboard::readSwitches(uint8_t s) +{ + // There are never any reads to the I/O switches + return 0xFF; +} + +void Mockingboard::writeSwitches(uint8_t s, uint8_t v) +{ + // There are never any writes to the I/O switches +} + +void Mockingboard::loadROM(uint8_t *toWhere) +{ + // We don't need a ROM; we're going to work via direct interaction + // with memory 0xC400 - 0xC4FF +} + +uint8_t Mockingboard::read(uint16_t address) +{ + address &= 0xFF; + if ( (address >= 0x00 && + address <= 0x0F) || + (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 0xFF; +} + +void Mockingboard::write(uint16_t address, uint8_t val) +{ + address &= 0xFF; + if ( (address >= 0x00 && + address <= 0x0F) || + (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); + } + } +} + +void Mockingboard::update(uint32_t cycles) +{ + sy6522[0].update(cycles); + // debugging: disabled the second update for the moment + // sy6522[1].update(cycles); +} + diff --git a/apple/mockingboard.h b/apple/mockingboard.h new file mode 100644 index 0000000..33b64aa --- /dev/null +++ b/apple/mockingboard.h @@ -0,0 +1,34 @@ +#ifndef __MOCKINGBOARD_H +#define __MOCKINGBOARD_H + +#ifdef TEENSYDUINO +#include +#else +#include +#include +#endif + +#include "applemmu.h" +#include "slot.h" +#include "SY6522.h" + +class Mockingboard : public Slot { + public: + Mockingboard(); + virtual ~Mockingboard(); + + virtual void Reset(); // used by BIOS cold-boot + virtual uint8_t readSwitches(uint8_t s); + virtual void writeSwitches(uint8_t s, uint8_t v); + virtual void loadROM(uint8_t *toWhere); + + void update(uint32_t cycles); + + uint8_t read(uint16_t address); + void write(uint16_t address, uint8_t val); + + private: + SY6522 sy6522[2]; +}; + +#endif diff --git a/apple/sy6522.cpp b/apple/sy6522.cpp new file mode 100644 index 0000000..aab654a --- /dev/null +++ b/apple/sy6522.cpp @@ -0,0 +1,151 @@ +#include "sy6522.h" +#include + +SY6522::SY6522() +{ + ORB = ORA = 0; + DDRB = DDRA = 0x00; + T1_CTR = T2_CTR = 0; + T1_CTR_LATCH = T2_CTR_LATCH = 0; + ACR = 0x20; // free-running; FIXME: constant? + PCR = 0xB0; // FIXME: ? + IFR = 0x00; // FIXME: ? + IER = 0x90; // FIXME: ? +} + +uint8_t SY6522::read(uint8_t address) +{ + switch (address) { + case SY_ORB: + return ORB; + case SY_ORA: + return ORA; + case SY_DDRB: + return DDRB; + case SY_DDRA: + return DDRA; + case SY_TMR1L: + // FIXME: also updates IFR? + return (T1_CTR & 0xFF); + case SY_TMR1H: + return (T1_CTR >> 8); + case SY_TMR1LL: + return (T1_CTR_LATCH & 0xFF); + case SY_TMR1HL: + return (T1_CTR_LATCH >> 8); + case SY_TMR2L: + // FIXME: alos udpates IFR? + return (T2_CTR & 0xFF); + case SY_TMR2H: + return (T2_CTR >> 8); + case SY_SS: + // FIXME: floating + return 0xFF; + case SY_ACR: + return ACR; + case SY_PCR: + return PCR; + case SY_IFR: + return IFR; + case SY_IER: + return 0x80 | IER; + case SY_ORANOHS: + return ORA; + } + return 0xFF; +} + + void SY6522::write(uint8_t address, uint8_t val) +{ + printf("SY6522: %X = %02X\n", address, val); + switch (address) { + case SY_ORB: + val &= DDRB; + ORB = val; + ay8910[0].write(val, ORA & DDRA); + return; + + case SY_ORA: + ORA = val & DDRA; + return; + + case SY_DDRB: + DDRB = val; + return; + + case SY_DDRA: + DDRA = val; + return; + + case SY_TMR1L: + case SY_TMR1LL: + T1_CTR_LATCH = (T1_CTR_LATCH & 0xFF00) | val; + return; + + case SY_TMR1H: + // FIXME: clear interrupt flag + T1_CTR_LATCH = (T1_CTR_LATCH & 0x00FF) | (val << 8); + T1_CTR = T1_CTR_LATCH; + // FIXME: start timer? + return; + + case SY_TMR1HL: + T1_CTR_LATCH = (T1_CTR_LATCH & 0x00FF) | (val << 8); + // FIXME: clear interrupt flag + return; + + case SY_TMR2L: + T2_CTR_LATCH = (T2_CTR_LATCH & 0xFF00) | val; + return; + + case SY_TMR2H: + // FIXME: clear timer2 interrupt flag + T2_CTR_LATCH = (T2_CTR_LATCH & 0x00FF) | (val << 8); + T2_CTR = T2_CTR_LATCH; + return; + + case SY_SS: + // FIXME: what is this for? + return; + + case SY_ACR: + ACR = val; + break; + + case SY_PCR: + PCR = val; + break; + + case SY_IFR: + // Clear whatever low bits are set in IFR. + val |= 0x80; + val ^= 0x7F; + IFR &= val; + break; + + case SY_IER: + if (val & 0x80) { + // Set bits based on val + val &= 0x7F; + IER |= val; + // FIXME: start timer if necessary? + } else { + // Clear whatever low bits are set in IER. + val |= 0x80; + val ^= 0x7F; + IER &= val; + // FIXME: stop timer if it's running? + } + return; + + case SY_ORANOHS: + // FIXME: what is this for? + return; + } +} + +void SY6522::update(uint32_t cycles) +{ + ay8910[0].update(cycles); +} + diff --git a/apple/sy6522.h b/apple/sy6522.h new file mode 100644 index 0000000..74b83f8 --- /dev/null +++ b/apple/sy6522.h @@ -0,0 +1,54 @@ +#ifndef __SY6522_H +#define __SY6522_H + +#include + +#include "ay8910.h" + +// 6522 interface registers +enum { + SY_ORB = 0x00, // ORB + SY_ORA = 0x01, // ORA + SY_DDRB = 0x02, // DDRB + SY_DDRA = 0x03, // DDRA + SY_TMR1L = 0x04, // TIMER1L_COUNTER + SY_TMR1H = 0x05, // TIMER1H_COUNTER + SY_TMR1LL = 0x06, // TIMER1L_LATCH + SY_TMR1HL = 0x07, // TIMER1H_LATCH + SY_TMR2L = 0x08, // TIMER2L + SY_TMR2H = 0x09, // TIMER2H + SY_SS = 0x0a, // SERIAL_SHIFT + SY_ACR = 0x0b, // ACR + SY_PCR = 0x0c, // PCR + SY_IFR = 0x0d, // IFR + SY_IER = 0x0e, // IER + SY_ORANOHS = 0x0f // ORA_NO_HS +}; + +class SY6522 { + public: + SY6522(); + + uint8_t read(uint8_t address); + void write(uint8_t address, uint8_t val); + + void update(uint32_t cycles); + + private: + uint8_t ORB; // port B + uint8_t ORA; // port A + uint8_t DDRB; // data direction register + uint8_t DDRA; // + uint16_t T1_CTR; // counters + uint16_t T1_CTR_LATCH; + uint16_t T2_CTR; + uint16_t T2_CTR_LATCH; + uint8_t ACR; // Aux Control Register + uint8_t PCR; // Peripheral Control Register + uint8_t IFR; // Interrupt Flag Register + uint8_t IER; // Interrupt Enable Register + + AY8910 ay8910[1]; // FIXME: an array in case we support more than one ... ? +}; + +#endif diff --git a/opencv/aiie.cpp b/opencv/aiie.cpp index 6acb607..fbb456e 100644 --- a/opencv/aiie.cpp +++ b/opencv/aiie.cpp @@ -4,8 +4,6 @@ #include #include -#include -// Derived from http://stackoverflow.com/questions/5167269/clock-gettime-alternative-in-mac-os-x #include "applevm.h" #include "opencv-display.h" #include "opencv-keyboard.h" @@ -16,39 +14,17 @@ #include "globals.h" +#include "timeutil.h" //#define SHOWFPS //#define SHOWPC //#define DEBUGCPU //#define SHOWMEMPAGE -#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) - -struct timespec nextInstructionTime, startTime; +static struct timespec nextInstructionTime, startTime; uint64_t hitcount = 0; uint64_t misscount = 0; -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(); -} - -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; -} - #define NB_ENABLE 1 #define NB_DISABLE 0 @@ -96,102 +72,6 @@ void write(void *arg, uint16_t address, uint8_t v) // no action; this is a dummy function until we've finished initializing... } -// adds the number of microseconds that 'cycles' takes to *start and -// returns it in *out -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; - } -} - -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) -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; -} - -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) ; -} - static void *cpu_thread(void *dummyptr) { struct timespec currentTime; @@ -226,9 +106,14 @@ static void *cpu_thread(void *dummyptr) { #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 { diff --git a/opencv/dummy-speaker.cpp b/opencv/dummy-speaker.cpp index 6bcbd32..01a9012 100644 --- a/opencv/dummy-speaker.cpp +++ b/opencv/dummy-speaker.cpp @@ -1,13 +1,99 @@ #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) { + 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 28e57b5..4baec56 100644 --- a/opencv/dummy-speaker.h +++ b/opencv/dummy-speaker.h @@ -1,15 +1,28 @@ #ifndef __DUMMYSPEAKER_H #define __DUMMYSPEAKER_H +#include #include #include "physicalspeaker.h" class DummySpeaker : public PhysicalSpeaker { public: + DummySpeaker(); virtual ~DummySpeaker(); virtual void toggleAtCycle(uint32_t c); 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/timeutil.h b/opencv/timeutil.h new file mode 100644 index 0000000..fa0b0b1 --- /dev/null +++ b/opencv/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_ms(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; + 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/physicalspeaker.h b/physicalspeaker.h index 176e447..e73f2c6 100644 --- a/physicalspeaker.h +++ b/physicalspeaker.h @@ -9,6 +9,9 @@ class PhysicalSpeaker { virtual void toggleAtCycle(uint32_t c) = 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/teensy/slot.cpp b/teensy/slot.cpp deleted file mode 120000 index 375ae63..0000000 --- a/teensy/slot.cpp +++ /dev/null @@ -1 +0,0 @@ -../apple/slot.cpp \ No newline at end of file diff --git a/teensy/teensy-speaker.cpp b/teensy/teensy-speaker.cpp index 8b5cdda..326365b 100644 --- a/teensy/teensy-speaker.cpp +++ b/teensy/teensy-speaker.cpp @@ -31,3 +31,8 @@ void TeensySpeaker::maintainSpeaker(uint32_t c) analogWriteDAC0(speakerState ? g_volume : 0); // max: 4095 } } + +bool TeensySpeaker::currentState() +{ + return speakerState; +} diff --git a/teensy/teensy-speaker.h b/teensy/teensy-speaker.h index 49910c5..3f39cc9 100644 --- a/teensy/teensy-speaker.h +++ b/teensy/teensy-speaker.h @@ -10,6 +10,7 @@ class TeensySpeaker : public PhysicalSpeaker { virtual void toggleAtCycle(uint32_t c); virtual void maintainSpeaker(uint32_t c); + virtual bool currentState(); private: bool speakerState; diff --git a/teensy/teensy.ino b/teensy/teensy.ino index 9b4dda2..f3fff9d 100644 --- a/teensy/teensy.ino +++ b/teensy/teensy.ino @@ -219,8 +219,9 @@ void runCPU() #endif } - g_speaker->maintainSpeaker(g_cpu->cycles); + g_speaker->beginMixing(); ((AppleVM *)g_vm)->cpuMaintenance(g_cpu->cycles); + g_speaker->maintainSpeaker(g_cpu->cycles); } // FIXME: move these memory-related functions elsewhere...