Fare un metronomo con setInterval è la strada per la frustrazione: l'event loop non è puntuale e il click sbanda. La soluzione è schedulare i suoni in anticipo usando l'orologio dell'AudioContext, che è preciso al campione.
let nextNote = ctx.currentTime;
const interval = 0.5; // 120 BPM
function scheduler() {
// guarda 100 ms nel futuro e prenota i click
while (nextNote < ctx.currentTime + 0.1) {
const osc = ctx.createOscillator();
osc.frequency.value = 1000;
osc.connect(ctx.destination);
osc.start(nextNote);
osc.stop(nextNote + 0.03);
nextNote += interval;
}
setTimeout(scheduler, 25);
}
scheduler();
L'idea: un setTimeout impreciso fa da "sveglia" ogni 25 ms, ma i suoni vengono prenotati con osc.start(tempoPreciso) sull'orologio audio. Il timing lo decide la scheda audio, non JavaScript.
È il pattern "lookahead scheduler", la base di ogni sequencer serio nel browser.