A browser metronome built on setInterval drifts audibly within seconds. A metronome built on the WebAudio AudioContext.currentTime clock is sample-accurate — better than ±1 ms at 44.1 kHz. Zro7 Metronome uses the WebAudio approach, so 120 BPM stays 120 BPM even under browser jank.
Why setInterval is wrong
setInterval(tick, 500) promises a callback every 500 ms — the browser makes no such promise. GC pauses, background tabs, and OS scheduling push the callback by 10–100 ms, and the drift accumulates.
The WebAudio pattern (Chris Wilson's scheduler)
- Keep a lookahead of ~25 ms and schedule ~100 ms of clicks in advance.
- Use
setTimeoutonly as a coarse pump — every 25 ms wake up and schedule any clicks that fall inside the next 100 ms. - Each click is
osc.start(nextClickTime)— WebAudio guarantees sample-accurate playback. - Advance
nextClickTime += 60 / bpm. Jitter in the pump does not affect the actual click time.
Latency in practice
- Chrome / Edge: baseLatency 5–10 ms, outputLatency 15–40 ms.
- Firefox: similar figures with WASAPI on Windows, CoreAudio on macOS.
- Safari: comparable on macOS; iOS depends on route (Bluetooth adds 100+ ms — nothing the browser can fix).
Steps
- Open Metronome.
- Set tempo (30–300 BPM) and time signature.
- Play — click the tempo dial to fine-tune while running.
- Optionally record yourself with Voice Recorder and check timing.
Zro7