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