diff --git a/apple/applemmu.cpp b/apple/applemmu.cpp index ae4f3c7..34d4152 100644 --- a/apple/applemmu.cpp +++ b/apple/applemmu.cpp @@ -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; diff --git a/cpu.cpp b/cpu.cpp index 444b96d..1c36796 100644 --- a/cpu.cpp +++ b/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; diff --git a/physicalspeaker.h b/physicalspeaker.h index a6c1953..3203d5a 100644 --- a/physicalspeaker.h +++ b/physicalspeaker.h @@ -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; diff --git a/sdl/aiie.cpp b/sdl/aiie.cpp index 45ca9a5..4da494e 100644 --- a/sdl/aiie.cpp +++ b/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); + 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); + // 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); - g_speaker->beginMixing(); + // The paddles need to be triggered in real-time on the CPU + // clock. That happens from the VM's CPU maintenance poller. + ((AppleVM *)g_vm)->cpuMaintenance(g_cpu->cycles); - // 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); +#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); + } - // cpuMaintenance also maintained the sound card; update the speaker after - g_speaker->maintainSpeaker(g_cpu->cycles); + // 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(); diff --git a/sdl/sdl-speaker.cpp b/sdl/sdl-speaker.cpp index 5b66dab..2afac27 100644 --- a/sdl/sdl-speaker.cpp +++ b/sdl/sdl-speaker.cpp @@ -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= 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"); + // FIXME: removed all the mixing code + + if (didChange) { + mixerValue = (toggleState ? 0x1FF : 0x00); + + // FIXME: g_volume + + curSpeakerData = (mixerValue & 0xFF) >> 4; } - - mixerValue >>= 3; // divide by 8 -#else - mixerValue /= numMixed; -#endif - - - // FIXME: g_volume - - curSpeakerData = (mixerValue & 0xFF) >> 4; } void SDLSpeaker::beginMixing() { - mixerValue = 0; - numMixed = 0; } void SDLSpeaker::mixOutput(uint8_t v) { - mixerValue += v; - numMixed++; } diff --git a/sdl/sdl-speaker.h b/sdl/sdl-speaker.h index 0d103a5..6ccd733 100644 --- a/sdl/sdl-speaker.h +++ b/sdl/sdl-speaker.h @@ -5,12 +5,14 @@ #include #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; }; diff --git a/sdl/timeutil.h b/sdl/timeutil.h index d5edb94..54c29f8 100644 --- a/sdl/timeutil.h +++ b/sdl/timeutil.h @@ -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; diff --git a/teensy/teensy-speaker.cpp b/teensy/teensy-speaker.cpp index 381b448..33fdb00 100644 --- a/teensy/teensy-speaker.cpp +++ b/teensy/teensy-speaker.cpp @@ -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); - - 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); + 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 } diff --git a/teensy/teensy-speaker.h b/teensy/teensy-speaker.h index da013c4..693273f 100644 --- a/teensy/teensy-speaker.h +++ b/teensy/teensy-speaker.h @@ -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 diff --git a/teensy/teensy.ino b/teensy/teensy.ino index 2465154..293c617 100644 --- a/teensy/teensy.ino +++ b/teensy/teensy.ino @@ -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()