diff --git a/src/engine/audioEngine.js b/src/engine/audioEngine.js index 0239e25..b38d97a 100644 --- a/src/engine/audioEngine.js +++ b/src/engine/audioEngine.js @@ -18,13 +18,8 @@ const keyboardState = { frequency: 440, gate: false }; // Using integers avoids floating-point drift entirely. export const MASTER_TICK_RATE = 480; // Hz — must be high enough for fastest BPM let _masterClock = null; -let _masterTicks = 0; const _tickListeners = new Map(); // id → callback(audioTime, ticks) -export function getMasterTicks() { - return _masterTicks; -} - export function subscribeTick(id, callback) { _tickListeners.set(id, callback); } @@ -35,11 +30,16 @@ export function unsubscribeTick(id) { function startMasterClock() { if (_masterClock) return; - _masterTicks = 0; + let _startTime = 0; + let _started = false; _masterClock = new Tone.Clock((time) => { - _masterTicks++; + if (!_started) { _startTime = time; _started = true; } + // Derive ticks from precise AudioContext.currentTime, not a counter. + // Counters fall behind when callbacks are delayed (GC, UI, tab throttle). + // The time parameter is always accurate regardless of callback jitter. + const ticks = Math.round((time - _startTime) * MASTER_TICK_RATE); for (const cb of _tickListeners.values()) { - cb(time, _masterTicks); + cb(time, ticks); } }, MASTER_TICK_RATE); _masterClock.start();