mirror of https://github.com/JorjBauer/aiie.git
partial sound rewrite: queue transitions while CPU is fast-forwarding, and then play them back at specific cycle counts
This commit is contained in:
parent
753a9a5f24
commit
a103a8ffa4
|
@ -474,7 +474,7 @@ uint8_t AppleMMU::readSwitches(uint16_t address)
|
|||
|
||||
|
||||
case 0xC030: // SPEAKER
|
||||
g_speaker->toggle();
|
||||
g_speaker->toggle(g_cpu->cycles);
|
||||
break;
|
||||
|
||||
case 0xC050: // CLRTEXT
|
||||
|
@ -615,6 +615,12 @@ void AppleMMU::writeSwitches(uint16_t address, uint8_t v)
|
|||
keyboardStrobe &= 0x7F;
|
||||
return;
|
||||
|
||||
case 0xC030: // SPEAKER
|
||||
// Writes toggle the speaker twice
|
||||
g_speaker->toggle(g_cpu->cycles);
|
||||
g_speaker->toggle(g_cpu->cycles);
|
||||
break;
|
||||
|
||||
case 0xC050: // graphics mode
|
||||
if (switches & S_TEXT) {
|
||||
switches &= ~S_TEXT;
|
||||
|
|
20
cpu.cpp
20
cpu.cpp
|
@ -770,13 +770,13 @@ uint8_t Cpu::step()
|
|||
}
|
||||
#endif
|
||||
pc = param;
|
||||
cycles++;
|
||||
cyclesThisStep++;
|
||||
}
|
||||
break;
|
||||
case O_BVS:
|
||||
if (flags & F_V) {
|
||||
pc = param;
|
||||
cycles++;
|
||||
cyclesThisStep++;
|
||||
}
|
||||
break;
|
||||
case O_BRK:
|
||||
|
@ -806,7 +806,7 @@ uint8_t Cpu::step()
|
|||
}
|
||||
#endif
|
||||
pc = param;
|
||||
cycles++;
|
||||
cyclesThisStep++;
|
||||
}
|
||||
break;
|
||||
case O_TXA:
|
||||
|
@ -852,7 +852,7 @@ uint8_t Cpu::step()
|
|||
case O_BPL:
|
||||
if (!(flags & F_N)) {
|
||||
pc = param;
|
||||
cycles++;
|
||||
cyclesThisStep++;
|
||||
}
|
||||
break;
|
||||
case O_CLC:
|
||||
|
@ -882,7 +882,7 @@ uint8_t Cpu::step()
|
|||
case O_BCC:
|
||||
if (!(flags & F_C)) {
|
||||
pc = param;
|
||||
cycles++;
|
||||
cyclesThisStep++;
|
||||
}
|
||||
break;
|
||||
case O_PLA:
|
||||
|
@ -904,13 +904,13 @@ uint8_t Cpu::step()
|
|||
case O_BCS:
|
||||
if (flags & F_C) {
|
||||
pc = param;
|
||||
cycles++;
|
||||
cyclesThisStep++;
|
||||
}
|
||||
break;
|
||||
case O_BMI:
|
||||
if (flags & F_N) {
|
||||
pc = param;
|
||||
cycles++;
|
||||
cyclesThisStep++;
|
||||
}
|
||||
break;
|
||||
case O_TAY:
|
||||
|
@ -924,7 +924,7 @@ uint8_t Cpu::step()
|
|||
case O_BVC:
|
||||
if (!(flags & F_V)) {
|
||||
pc = param;
|
||||
cycles++;
|
||||
cyclesThisStep++;
|
||||
}
|
||||
break;
|
||||
case O_INY:
|
||||
|
@ -1088,7 +1088,7 @@ uint8_t Cpu::step()
|
|||
int16_t c;
|
||||
uint8_t v;
|
||||
if (flags & F_D) {
|
||||
cycles++;
|
||||
cyclesThisStep++;
|
||||
c = (a & 0x0F) + (memTemp & 0x0F) + (flags & F_C);
|
||||
if (c < 0x10)
|
||||
c = (c - 0x06) & 0x0f;
|
||||
|
@ -1120,7 +1120,7 @@ uint8_t Cpu::step()
|
|||
int16_t c;
|
||||
uint8_t v;
|
||||
if (flags & F_D) {
|
||||
cycles++;
|
||||
cyclesThisStep++;
|
||||
c = (a & 0x0F) + (memTemp & 0x0F) + (flags & F_C);
|
||||
if (c > 0x09)
|
||||
c = (c - 0x0a) | 0x10;
|
||||
|
|
|
@ -7,7 +7,7 @@ class PhysicalSpeaker {
|
|||
public:
|
||||
virtual ~PhysicalSpeaker() {}
|
||||
|
||||
virtual void toggle() = 0;
|
||||
virtual void toggle(uint32_t c) = 0;
|
||||
virtual void maintainSpeaker(uint32_t c) = 0;
|
||||
virtual void beginMixing() = 0;
|
||||
virtual void mixOutput(uint8_t v) = 0;
|
||||
|
|
129
sdl/aiie.cpp
129
sdl/aiie.cpp
|
@ -22,8 +22,6 @@
|
|||
//#define SHOWMEMPAGE
|
||||
|
||||
static struct timespec nextInstructionTime, startTime;
|
||||
uint64_t hitcount = 0;
|
||||
uint64_t misscount = 0;
|
||||
|
||||
#define NB_ENABLE 1
|
||||
#define NB_DISABLE 0
|
||||
|
@ -91,6 +89,7 @@ static void *cpu_thread(void *dummyptr) {
|
|||
|
||||
_init_darwin_shim();
|
||||
do_gettime(&startTime);
|
||||
printf("Start time: %lu,%lu\n", startTime.tv_sec, startTime.tv_nsec);
|
||||
do_gettime(&nextInstructionTime);
|
||||
|
||||
printf("free-running\n");
|
||||
|
@ -110,31 +109,93 @@ static void *cpu_thread(void *dummyptr) {
|
|||
wantResume = false;
|
||||
}
|
||||
|
||||
// cycle down the CPU...
|
||||
// Would like to do the old nanosleep thing, but the speaker needs
|
||||
// to run. FIXME: do something more intelligent here - sleep 'til speakertime+1? (Obv. to do this below, not right here)
|
||||
do_gettime(¤tTime);
|
||||
// tsSubtract doesn't return negatives; it bounds at 0.
|
||||
struct timespec diff = tsSubtract(nextInstructionTime, currentTime);
|
||||
if (diff.tv_sec >= 0 && diff.tv_nsec >= 0) {
|
||||
hitcount++;
|
||||
nanosleep(&diff, NULL);
|
||||
} else {
|
||||
misscount++;
|
||||
|
||||
// do_gettime(¤tTime);
|
||||
struct timespec runtime = tsSubtract(currentTime, startTime);
|
||||
double speakerCycle = cycles_since_time(&runtime);
|
||||
|
||||
uint8_t executed = 0;
|
||||
if (diff.tv_sec == 0 && diff.tv_nsec == 0) {
|
||||
// okay to run CPU
|
||||
// If speakerCycle == 0, we're still starting up
|
||||
// If speakerCycle > cycles, the CPU is running behind; don't bother with that just yet
|
||||
// If we're about to run the CPU then we *should* have caught up the speaker - how could it possibly be this far out of skew?
|
||||
if (speakerCycle && speakerCycle < g_cpu->cycles && abs(g_cpu->cycles - speakerCycle) > 24) {
|
||||
#if 0
|
||||
printf("Start time: %lu,%lu\n", startTime.tv_sec, startTime.tv_nsec);
|
||||
printf("runtime: %lu,%lu\n", runtime.tv_sec, runtime.tv_nsec);
|
||||
printf("Current time: %lu,%lu\n", currentTime.tv_sec, currentTime.tv_nsec);
|
||||
printf("Next time: %lu,%lu\n", nextInstructionTime.tv_sec, nextInstructionTime.tv_nsec);
|
||||
printf("Speaker calc / cycle count: %lf / %d [e %d; d %f]\n", speakerCycle, g_cpu->cycles, executed, abs(g_cpu->cycles - speakerCycle));
|
||||
#endif
|
||||
|
||||
// If we're okay to run the CPU, then the speaker should be caught up. Not sure how it wouldn't be.
|
||||
printf("About to run cpu but speaker diff > 24 - how, exactly?\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
#ifdef DEBUGCPU
|
||||
uint8_t executed = g_cpu->Run(1);
|
||||
#else
|
||||
uint8_t executed = g_cpu->Run(24);
|
||||
executed = g_cpu->Run(24);
|
||||
#endif
|
||||
timespec_add_cycles(&startTime, g_cpu->cycles + executed, &nextInstructionTime);
|
||||
|
||||
g_speaker->beginMixing();
|
||||
// calculate the real time that we should be at now, and schedule
|
||||
// that as our next instruction time
|
||||
timespec_add_cycles(&startTime, g_cpu->cycles, &nextInstructionTime);
|
||||
|
||||
// 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);
|
||||
#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
|
||||
{
|
||||
|
@ -154,13 +215,11 @@ static void *cpu_thread(void *dummyptr) {
|
|||
#endif
|
||||
|
||||
if (send_rst) {
|
||||
|
||||
|
||||
#if 0
|
||||
printf("Scheduling suspend request...\n");
|
||||
wantSuspend = true;
|
||||
#endif
|
||||
#if 1
|
||||
#if 0
|
||||
printf("Scheduling resume resume request...\n");
|
||||
wantResume = true;
|
||||
#endif
|
||||
|
@ -223,6 +282,34 @@ static void *cpu_thread(void *dummyptr) {
|
|||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
#if 0
|
||||
// Timing consistency check
|
||||
|
||||
sleep(2); // kinda random, hopefully sloppy? - to make startTime != 0,0
|
||||
printf("starting time consistency check\n");
|
||||
do_gettime(&startTime);
|
||||
for (int i=0; i<10000000; i++) {
|
||||
|
||||
// Calculate the time delta from startTime to cycle # i
|
||||
timespec_add_cycles(&startTime, i, &nextInstructionTime);
|
||||
|
||||
// Recalculate the time difference between nextInstructionTime and startTime
|
||||
struct timespec runtime = tsSubtract(nextInstructionTime, startTime);
|
||||
|
||||
// See if it's the same as cycles_since_time
|
||||
double guesstimate = cycles_since_time(&runtime);
|
||||
printf("cycle %d guesstimate %f\n", i, guesstimate);
|
||||
if (guesstimate != i) {
|
||||
printf("FAILED: cycle %d has guesstimate %f\n", i, guesstimate);
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
printf("All ok\n");
|
||||
|
||||
exit(1);
|
||||
#endif
|
||||
|
||||
SDL_Init(SDL_INIT_EVERYTHING);
|
||||
|
||||
g_speaker = new SDLSpeaker();
|
||||
|
@ -284,10 +371,10 @@ int main(int argc, char *argv[])
|
|||
|
||||
while (1) {
|
||||
static uint32_t usleepcycles = 16384; // step-down for display drawing. FIXME: this constant works well for *my* machine. Dynamically generate?
|
||||
static uint32_t ctr = 0;
|
||||
if (++ctr == 0) {
|
||||
printf("hit: %llu; miss: %llu; pct: %f\n", hitcount, misscount, (double)misscount / (double)(misscount + hitcount));
|
||||
}
|
||||
// static uint32_t ctr = 0;
|
||||
// if (++ctr == 0) {
|
||||
// printf("hit: %llu; miss: %llu; pct: %f\n", hitcount, misscount, (double)misscount / (double)(misscount + hitcount));
|
||||
// }
|
||||
|
||||
// fill disk buffer when needed
|
||||
((AppleVM*)g_vm)->disk6->fillDiskBuffer();
|
||||
|
|
|
@ -17,7 +17,9 @@ static pthread_t speakerThreadID;
|
|||
static uint8_t curSpeakerData = 0x00;
|
||||
static volatile uint16_t bufIdx = 0;
|
||||
static uint8_t soundBuf[4096];
|
||||
static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
|
||||
static pthread_mutex_t sndmutex = PTHREAD_MUTEX_INITIALIZER;
|
||||
|
||||
static pthread_mutex_t togmutex = PTHREAD_MUTEX_INITIALIZER;
|
||||
|
||||
|
||||
static uint64_t hitcount;
|
||||
|
@ -27,7 +29,7 @@ static uint64_t copycount = 0;
|
|||
|
||||
static void audioCallback(void *unused, Uint8 *stream, int len)
|
||||
{
|
||||
pthread_mutex_lock(&mutex);
|
||||
pthread_mutex_lock(&sndmutex);
|
||||
if (bufIdx >= len) {
|
||||
memcpy(stream, soundBuf, len);
|
||||
if (bufIdx > len) {
|
||||
|
@ -40,7 +42,7 @@ static void audioCallback(void *unused, Uint8 *stream, int len)
|
|||
// Audio underrun
|
||||
memset(stream, 0, len);
|
||||
}
|
||||
pthread_mutex_unlock(&mutex);
|
||||
pthread_mutex_unlock(&sndmutex);
|
||||
}
|
||||
|
||||
static void *speaker_thread(void *dummyptr) {
|
||||
|
@ -48,7 +50,7 @@ static void *speaker_thread(void *dummyptr) {
|
|||
struct timespec startTime;
|
||||
struct timespec nextSampleTime;
|
||||
|
||||
pthread_mutex_init(&mutex, NULL);
|
||||
pthread_mutex_init(&sndmutex, NULL);
|
||||
|
||||
SDL_AudioSpec audioDevice;
|
||||
SDL_AudioSpec audioActual;
|
||||
|
@ -85,13 +87,13 @@ static void *speaker_thread(void *dummyptr) {
|
|||
printf("sound hit: %lld miss: %lld copy: %lld\n", hitcount, misscount, copycount);
|
||||
}
|
||||
|
||||
pthread_mutex_lock(&mutex);
|
||||
pthread_mutex_lock(&sndmutex);
|
||||
soundBuf[bufIdx++] = curSpeakerData & 0xFF;
|
||||
if (bufIdx >= sizeof(soundBuf)) {
|
||||
// Audio overrun; start dropping data
|
||||
bufIdx--;
|
||||
}
|
||||
pthread_mutex_unlock(&mutex);
|
||||
pthread_mutex_unlock(&sndmutex);
|
||||
|
||||
// set nextSampleTime to the absolute reference time of when the
|
||||
// next sample should start (based on our start time).
|
||||
|
@ -104,10 +106,13 @@ static void *speaker_thread(void *dummyptr) {
|
|||
SDLSpeaker::SDLSpeaker()
|
||||
{
|
||||
toggleState = false;
|
||||
needsToggle = 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");
|
||||
}
|
||||
|
@ -118,48 +123,59 @@ SDLSpeaker::~SDLSpeaker()
|
|||
pclose(f);
|
||||
}
|
||||
|
||||
void SDLSpeaker::toggle()
|
||||
void SDLSpeaker::toggle(uint32_t c)
|
||||
{
|
||||
needsToggle = true;
|
||||
pthread_mutex_lock(&togmutex);
|
||||
|
||||
toggleTimes[toggleWritePtr] = c;
|
||||
if (toggleCount < SPEAKERQUEUESIZE-1) {
|
||||
toggleWritePtr++;
|
||||
if (toggleWritePtr >= SPEAKERQUEUESIZE)
|
||||
toggleWritePtr = 0;
|
||||
toggleCount++;
|
||||
} else {
|
||||
printf("speaker overflow @ cycle %d\n", c);
|
||||
for (int i=0; i<SPEAKERQUEUESIZE; i++) {
|
||||
printf(" %d [%d]\n", toggleTimes[(toggleReadPtr + i)%SPEAKERQUEUESIZE],
|
||||
toggleTimes[(toggleReadPtr + i - 1)%SPEAKERQUEUESIZE] -
|
||||
toggleTimes[(toggleReadPtr + i)%SPEAKERQUEUESIZE]
|
||||
);
|
||||
}
|
||||
exit(1);
|
||||
}
|
||||
pthread_mutex_unlock(&togmutex);
|
||||
}
|
||||
|
||||
void SDLSpeaker::maintainSpeaker(uint32_t c)
|
||||
{
|
||||
if (needsToggle) {
|
||||
bool didChange = false;
|
||||
pthread_mutex_lock(&togmutex);
|
||||
while (toggleCount && c >= toggleTimes[toggleReadPtr]) {
|
||||
// Override the mixer with a 1-bit "Terribad" audio sample change
|
||||
toggleState = !toggleState;
|
||||
needsToggle = false;
|
||||
toggleCount--;
|
||||
toggleReadPtr++;
|
||||
if (toggleReadPtr >= SPEAKERQUEUESIZE)
|
||||
toggleReadPtr = 0;
|
||||
didChange = true;
|
||||
}
|
||||
// The "terribad" audio gets two shares. THis means we've got 8 total
|
||||
// "voices" -- 6 from the mockingboard and 2 from the built-in. 8 is
|
||||
// a great number for dividing. :)
|
||||
mixerValue += (toggleState ? 0x1FF : 0x00);
|
||||
numMixed += 2;
|
||||
pthread_mutex_unlock(&togmutex);
|
||||
|
||||
#if 0
|
||||
if (numMixed != 8) {
|
||||
printf("SPEAKER FAIL - should always be 8\n");
|
||||
}
|
||||
|
||||
mixerValue >>= 3; // divide by 8
|
||||
#else
|
||||
mixerValue /= numMixed;
|
||||
#endif
|
||||
// FIXME: removed all the mixing code
|
||||
|
||||
if (didChange) {
|
||||
mixerValue = (toggleState ? 0x1FF : 0x00);
|
||||
|
||||
// FIXME: g_volume
|
||||
|
||||
curSpeakerData = (mixerValue & 0xFF) >> 4;
|
||||
}
|
||||
}
|
||||
|
||||
void SDLSpeaker::beginMixing()
|
||||
{
|
||||
mixerValue = 0;
|
||||
numMixed = 0;
|
||||
}
|
||||
|
||||
void SDLSpeaker::mixOutput(uint8_t v)
|
||||
{
|
||||
mixerValue += v;
|
||||
numMixed++;
|
||||
}
|
||||
|
|
|
@ -5,12 +5,14 @@
|
|||
#include <stdint.h>
|
||||
#include "physicalspeaker.h"
|
||||
|
||||
#define SPEAKERQUEUESIZE 64
|
||||
|
||||
class SDLSpeaker : public PhysicalSpeaker {
|
||||
public:
|
||||
SDLSpeaker();
|
||||
virtual ~SDLSpeaker();
|
||||
|
||||
virtual void toggle();
|
||||
virtual void toggle(uint32_t c);
|
||||
virtual void maintainSpeaker(uint32_t c);
|
||||
virtual void beginMixing();
|
||||
virtual void mixOutput(uint8_t v);
|
||||
|
@ -18,7 +20,11 @@ class SDLSpeaker : public PhysicalSpeaker {
|
|||
uint32_t mixerValue;
|
||||
uint8_t numMixed;
|
||||
bool toggleState;
|
||||
bool needsToggle;
|
||||
|
||||
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
|
||||
|
||||
FILE *f;
|
||||
};
|
||||
|
|
|
@ -7,7 +7,6 @@
|
|||
#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;
|
||||
|
@ -26,7 +25,7 @@ static int do_gettime(struct timespec *tp) {
|
|||
return 0;
|
||||
}
|
||||
|
||||
// adds the number of microseconds that 'cycles' takes to *start and
|
||||
// adds the number of nanoseconds that 'cycles' takes to *start and
|
||||
// returns it in *out
|
||||
static void timespec_add_cycles(struct timespec *start,
|
||||
uint32_t cycles,
|
||||
|
@ -35,16 +34,24 @@ static void timespec_add_cycles(struct timespec *start,
|
|||
out->tv_sec = start->tv_sec;
|
||||
out->tv_nsec = start->tv_nsec;
|
||||
|
||||
uint64_t nanosToAdd = NANOSECONDS_PER_CYCLE * cycles;
|
||||
uint64_t nanosToAdd = (double)((double)cycles * (double) (NANOSECONDS_PER_SECOND) / (double)1023000);
|
||||
|
||||
out->tv_sec += (nanosToAdd / NANOSECONDS_PER_SECOND);
|
||||
out->tv_nsec += (nanosToAdd % NANOSECONDS_PER_SECOND);
|
||||
|
||||
if (out->tv_nsec >= 1000000000L) {
|
||||
if (out->tv_nsec >= NANOSECONDS_PER_SECOND) {
|
||||
out->tv_sec++ ;
|
||||
out->tv_nsec -= 1000000000L;
|
||||
out->tv_nsec -= NANOSECONDS_PER_SECOND;
|
||||
}
|
||||
}
|
||||
|
||||
static unsigned long cycles_since_time(struct timespec *start)
|
||||
{
|
||||
unsigned long ret = start->tv_sec * CYCLES_PER_SECOND;
|
||||
ret += (double)((double)start->tv_nsec * (double)0.001023 + (double) 0.01); // 0.01 for rounding error; one cycle ~= 977517nS, and 977517 * .000001023 is only 0.999999891.
|
||||
return ret;
|
||||
}
|
||||
|
||||
// adds the number of microseconds given to *start and
|
||||
// returns it in *out
|
||||
static void timespec_add_us(struct timespec *start,
|
||||
|
@ -121,6 +128,7 @@ static int8_t tsCompare(struct timespec *A, struct timespec *B)
|
|||
return 0;
|
||||
}
|
||||
|
||||
// return time1 - time2. If time1 <= time2, then return 0.
|
||||
static struct timespec tsSubtract(struct timespec time1, struct timespec time2)
|
||||
{
|
||||
struct timespec result;
|
||||
|
|
|
@ -6,45 +6,60 @@
|
|||
TeensySpeaker::TeensySpeaker(uint8_t pinNum) : PhysicalSpeaker()
|
||||
{
|
||||
toggleState = false;
|
||||
needsToggle = false;
|
||||
speakerPin = pinNum;
|
||||
pinMode(speakerPin, OUTPUT); // analog speaker output, used as digital volume control
|
||||
mixerValue = numMixed = 0;
|
||||
|
||||
toggleCount = toggleReadPtr = toggleWritePtr = 0;
|
||||
}
|
||||
|
||||
TeensySpeaker::~TeensySpeaker()
|
||||
{
|
||||
}
|
||||
|
||||
void TeensySpeaker::toggle()
|
||||
void TeensySpeaker::toggle(uint32_t c)
|
||||
{
|
||||
needsToggle = true;
|
||||
toggleTimes[toggleWritePtr] = c;
|
||||
if (toggleCount < SPEAKERQUEUESIZE-1) {
|
||||
toggleWritePtr++;
|
||||
if (toggleWritePtr >= SPEAKERQUEUESIZE)
|
||||
toggleWritePtr = 0;
|
||||
toggleCount++;
|
||||
} else {
|
||||
// speaker overflow
|
||||
Serial.println("spkr overflow");
|
||||
}
|
||||
}
|
||||
|
||||
void TeensySpeaker::maintainSpeaker(uint32_t c)
|
||||
{
|
||||
if (needsToggle) {
|
||||
bool didChange = false;
|
||||
|
||||
while (toggleCount && c >= toggleTimes[toggleReadPtr]) {
|
||||
toggleState = !toggleState;
|
||||
needsToggle = false;
|
||||
toggleCount--;
|
||||
toggleReadPtr++;
|
||||
if (toggleReadPtr >= SPEAKERQUEUESIZE)
|
||||
toggleReadPtr = 0;
|
||||
didChange = true;
|
||||
}
|
||||
|
||||
mixerValue += (toggleState ? 0x1FF : 0x00);
|
||||
|
||||
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()
|
||||
{
|
||||
mixerValue = 0;
|
||||
numMixed = 0;
|
||||
// unused
|
||||
}
|
||||
|
||||
void TeensySpeaker::mixOutput(uint8_t v)
|
||||
{
|
||||
mixerValue += v;
|
||||
numMixed++;
|
||||
// unused
|
||||
}
|
||||
|
||||
|
|
|
@ -3,12 +3,15 @@
|
|||
|
||||
#include "physicalspeaker.h"
|
||||
|
||||
// FIXME: 64 enough?
|
||||
#define SPEAKERQUEUESIZE 64
|
||||
|
||||
class TeensySpeaker : public PhysicalSpeaker {
|
||||
public:
|
||||
TeensySpeaker(uint8_t pinNum);
|
||||
virtual ~TeensySpeaker();
|
||||
|
||||
virtual void toggle();
|
||||
virtual void toggle(uint32_t c);
|
||||
virtual void maintainSpeaker(uint32_t c);
|
||||
|
||||
virtual void beginMixing();
|
||||
|
@ -18,10 +21,14 @@ class TeensySpeaker : public PhysicalSpeaker {
|
|||
uint8_t speakerPin;
|
||||
|
||||
bool toggleState;
|
||||
bool needsToggle;
|
||||
|
||||
uint32_t mixerValue;
|
||||
uint8_t numMixed;
|
||||
|
||||
uint32_t toggleTimes[SPEAKERQUEUESIZE];
|
||||
uint8_t toggleCount; // # of entries still in queue
|
||||
uint8_t toggleReadPtr; // ring buffer pointer in queue
|
||||
uint8_t toggleWritePtr; // ring buffer pointer in queue
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
|
@ -197,6 +197,8 @@ void biosInterrupt()
|
|||
g_cpu->cycles = 0;
|
||||
nextInstructionMicros = micros();
|
||||
startMicros = micros();
|
||||
// Drain the speaker queue (FIXME: a little hacky)
|
||||
g_speaker->maintainSpeaker(-1);
|
||||
|
||||
// Force the display to redraw
|
||||
((AppleDisplay*)(g_vm->vmdisplay))->modeChange();
|
||||
|
@ -217,20 +219,22 @@ void runCPU()
|
|||
//debugState = !debugState;
|
||||
// digitalWrite(56, debugState);
|
||||
|
||||
g_cpu->Run(24);
|
||||
|
||||
// These are timing-critical, for the audio and paddles.
|
||||
// There's also a keyboard repeat in here that hopefully is
|
||||
// minimal overhead...
|
||||
g_speaker->beginMixing();
|
||||
((AppleVM *)g_vm)->cpuMaintenance(g_cpu->cycles);
|
||||
g_speaker->maintainSpeaker(g_cpu->cycles);
|
||||
uint8_t executed = g_cpu->Run(24);
|
||||
|
||||
// The CPU of the Apple //e ran at 1.023 MHz. Adjust when we think
|
||||
// the next instruction should run based on how long the execution
|
||||
// was ((1000/1023) * numberOfCycles) - which is about 97.8%.
|
||||
nextInstructionMicros = startMicros + (float)g_cpu->cycles * 0.978;
|
||||
|
||||
// Timing-critical paddle and keyboard handling
|
||||
((AppleVM *)g_vm)->cpuMaintenance(g_cpu->cycles);
|
||||
}
|
||||
|
||||
// Timing-crtical audio handling
|
||||
g_speaker->beginMixing();
|
||||
// estimate of current cpu cycle counter, delayed a bit
|
||||
float speakerTick = ((float)micros() - 100.0 - (float)startMicros) / 0.978;
|
||||
g_speaker->maintainSpeaker(speakerTick);
|
||||
}
|
||||
|
||||
void loop()
|
||||
|
|
Loading…
Reference in New Issue