working on mockingboard support

This commit is contained in:
Jorj Bauer 2017-02-24 10:15:17 -05:00
parent 4c5537a658
commit e942d8a4dd
21 changed files with 756 additions and 127 deletions

View File

@ -4,7 +4,7 @@ CXXFLAGS=-Wall -I .. -I . -I apple -I opencv -O3
TSRC=cpu.cpp util/testharness.cpp 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 ROMS=apple/applemmu-rom.h apple/diskii-rom.h apple/parallel-rom.h

View File

@ -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 data. It's functional, and the Mac build is only about functional
testing (for me). 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 VM
== ==

View File

@ -10,6 +10,7 @@
#include "applemmu-rom.h" #include "applemmu-rom.h"
#include "physicalspeaker.h" #include "physicalspeaker.h"
#include "cpu.h" #include "cpu.h"
#include "mockingboard.h"
#include "globals.h" #include "globals.h"
@ -69,6 +70,11 @@ uint8_t AppleMMU::read(uint16_t address)
address <= 0xC0FF) { address <= 0xC0FF) {
return readSwitches(address); 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]; 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); 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 // Don't allow writes to ROM
if (address >= 0xC100 && address <= 0xCFFF) if (address >= 0xC100 && address <= 0xCFFF)
return; return;

View File

@ -23,6 +23,9 @@ AppleVM::AppleVM()
parallel = new ParallelCard(); parallel = new ParallelCard();
((AppleMMU *)mmu)->setSlot(1, parallel); ((AppleMMU *)mmu)->setSlot(1, parallel);
mockingboard = new Mockingboard();
((AppleMMU *)mmu)->setSlot(4, mockingboard);
#ifdef TEENSYDUINO #ifdef TEENSYDUINO
teensyClock = new TeensyClock((AppleMMU *)mmu); teensyClock = new TeensyClock((AppleMMU *)mmu);
((AppleMMU *)mmu)->setSlot(7, teensyClock); ((AppleMMU *)mmu)->setSlot(7, teensyClock);
@ -35,6 +38,8 @@ AppleVM::~AppleVM()
delete teensyClock; delete teensyClock;
#endif #endif
delete disk6; delete disk6;
delete parallel;
delete mockingboard;
} }
// fixme: make member vars // fixme: make member vars
@ -56,6 +61,7 @@ void AppleVM::cpuMaintenance(uint32_t cycles)
keyboard->maintainKeyboard(cycles); keyboard->maintainKeyboard(cycles);
parallel->update(); parallel->update();
mockingboard->update(cycles);
} }
void AppleVM::Reset() void AppleVM::Reset()

View File

@ -6,6 +6,7 @@
#include "diskii.h" #include "diskii.h"
#include "vmkeyboard.h" #include "vmkeyboard.h"
#include "parallelcard.h" #include "parallelcard.h"
#include "mockingboard.h"
#ifdef TEENSYDUINO #ifdef TEENSYDUINO
#include "teensy-clock.h" #include "teensy-clock.h"
#endif #endif
@ -34,6 +35,7 @@ class AppleVM : public VM {
DiskII *disk6; DiskII *disk6;
VMKeyboard *keyboard; VMKeyboard *keyboard;
ParallelCard *parallel; ParallelCard *parallel;
Mockingboard *mockingboard;
#ifdef TEENSYDUINO #ifdef TEENSYDUINO
TeensyClock *teensyClock; TeensyClock *teensyClock;
#endif #endif

129
apple/ay8910.cpp Normal file
View File

@ -0,0 +1,129 @@
#include "ay8910.h"
#include <stdio.h>
#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);
}
}

28
apple/ay8910.h Normal file
View File

@ -0,0 +1,28 @@
#ifndef __AY8910_H
#define __AY8910_H
#include <stdint.h>
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

View File

@ -411,12 +411,12 @@ const char *DiskII::DiskName(int8_t num)
void DiskII::loadROM(uint8_t *toWhere) void DiskII::loadROM(uint8_t *toWhere)
{ {
#ifdef TEENSYDUINO #ifdef TEENSYDUINO
Serial.println("loading slot rom"); Serial.println("loading DiskII rom");
for (uint16_t i=0; i<=0xFF; i++) { for (uint16_t i=0; i<=0xFF; i++) {
toWhere[i] = pgm_read_byte(&romData[i]); toWhere[i] = pgm_read_byte(&romData[i]);
} }
#else #else
printf("loading slot rom\n"); printf("loading DiskII rom\n");
memcpy(toWhere, romData, 256); memcpy(toWhere, romData, 256);
#endif #endif
} }

69
apple/mockingboard.cpp Normal file
View File

@ -0,0 +1,69 @@
#include "mockingboard.h"
#include <string.h>
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);
}

34
apple/mockingboard.h Normal file
View File

@ -0,0 +1,34 @@
#ifndef __MOCKINGBOARD_H
#define __MOCKINGBOARD_H
#ifdef TEENSYDUINO
#include <Arduino.h>
#else
#include <stdint.h>
#include <stdio.h>
#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

151
apple/sy6522.cpp Normal file
View File

@ -0,0 +1,151 @@
#include "sy6522.h"
#include <stdio.h>
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);
}

54
apple/sy6522.h Normal file
View File

@ -0,0 +1,54 @@
#ifndef __SY6522_H
#define __SY6522_H
#include <stdint.h>
#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

View File

@ -4,8 +4,6 @@
#include <termios.h> #include <termios.h>
#include <pthread.h> #include <pthread.h>
#include <mach/mach_time.h>
// Derived from http://stackoverflow.com/questions/5167269/clock-gettime-alternative-in-mac-os-x
#include "applevm.h" #include "applevm.h"
#include "opencv-display.h" #include "opencv-display.h"
#include "opencv-keyboard.h" #include "opencv-keyboard.h"
@ -16,39 +14,17 @@
#include "globals.h" #include "globals.h"
#include "timeutil.h"
//#define SHOWFPS //#define SHOWFPS
//#define SHOWPC //#define SHOWPC
//#define DEBUGCPU //#define DEBUGCPU
//#define SHOWMEMPAGE //#define SHOWMEMPAGE
#define ORWL_NANO (+1.0E-9) static struct timespec nextInstructionTime, startTime;
#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;
uint64_t hitcount = 0; uint64_t hitcount = 0;
uint64_t misscount = 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_ENABLE 1
#define NB_DISABLE 0 #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... // 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) { static void *cpu_thread(void *dummyptr) {
struct timespec currentTime; struct timespec currentTime;
@ -226,9 +106,14 @@ static void *cpu_thread(void *dummyptr) {
#endif #endif
timespec_add_cycles(&startTime, g_cpu->cycles + executed, &nextInstructionTime); timespec_add_cycles(&startTime, g_cpu->cycles + executed, &nextInstructionTime);
g_speaker->beginMixing();
// The paddles need to be triggered in real-time on the CPU // The paddles need to be triggered in real-time on the CPU
// clock. That happens from the VM's CPU maintenance poller. // clock. That happens from the VM's CPU maintenance poller.
((AppleVM *)g_vm)->cpuMaintenance(g_cpu->cycles); ((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 #ifdef DEBUGCPU
{ {

View File

@ -1,13 +1,99 @@
#include "dummy-speaker.h" #include "dummy-speaker.h"
#include <pthread.h>
#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(&currentTime);
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() DummySpeaker::~DummySpeaker()
{ {
pclose(f);
} }
void DummySpeaker::toggleAtCycle(uint32_t c) void DummySpeaker::toggleAtCycle(uint32_t c)
{ {
nextTransitionAt = c;
} }
void DummySpeaker::maintainSpeaker(uint32_t 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++;
} }

View File

@ -1,15 +1,28 @@
#ifndef __DUMMYSPEAKER_H #ifndef __DUMMYSPEAKER_H
#define __DUMMYSPEAKER_H #define __DUMMYSPEAKER_H
#include <stdio.h>
#include <stdint.h> #include <stdint.h>
#include "physicalspeaker.h" #include "physicalspeaker.h"
class DummySpeaker : public PhysicalSpeaker { class DummySpeaker : public PhysicalSpeaker {
public: public:
DummySpeaker();
virtual ~DummySpeaker(); virtual ~DummySpeaker();
virtual void toggleAtCycle(uint32_t c); virtual void toggleAtCycle(uint32_t c);
virtual void maintainSpeaker(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 #endif

143
opencv/timeutil.h Normal file
View File

@ -0,0 +1,143 @@
#include <time.h>
#include <mach/mach_time.h>
// 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) ;
}

View File

@ -9,6 +9,9 @@ class PhysicalSpeaker {
virtual void toggleAtCycle(uint32_t c) = 0; virtual void toggleAtCycle(uint32_t c) = 0;
virtual void maintainSpeaker(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;
}; };

View File

@ -1 +0,0 @@
../apple/slot.cpp

View File

@ -31,3 +31,8 @@ void TeensySpeaker::maintainSpeaker(uint32_t c)
analogWriteDAC0(speakerState ? g_volume : 0); // max: 4095 analogWriteDAC0(speakerState ? g_volume : 0); // max: 4095
} }
} }
bool TeensySpeaker::currentState()
{
return speakerState;
}

View File

@ -10,6 +10,7 @@ class TeensySpeaker : public PhysicalSpeaker {
virtual void toggleAtCycle(uint32_t c); virtual void toggleAtCycle(uint32_t c);
virtual void maintainSpeaker(uint32_t c); virtual void maintainSpeaker(uint32_t c);
virtual bool currentState();
private: private:
bool speakerState; bool speakerState;

View File

@ -219,8 +219,9 @@ void runCPU()
#endif #endif
} }
g_speaker->maintainSpeaker(g_cpu->cycles); g_speaker->beginMixing();
((AppleVM *)g_vm)->cpuMaintenance(g_cpu->cycles); ((AppleVM *)g_vm)->cpuMaintenance(g_cpu->cycles);
g_speaker->maintainSpeaker(g_cpu->cycles);
} }
// FIXME: move these memory-related functions elsewhere... // FIXME: move these memory-related functions elsewhere...