I made a metronome inspired by the famous Chris Wilson's article using React, Hooks, and the Web Audio API.
The metronome works but there's a delay between the moment I hit 'play' and the sound itself.
This is clearly noticeable if the BPM is very low (e.g. 40 BPM).
At first, I thought I needed to isolate the logic from the UI rendering using a Worker but now I start to think it's something else.
I think in the timer function I need an else calling sound with a 0 value.
But I haven't found a solution yet.
Does anybody have an idea what's wrong and how to fix it?
Thanks!
import { useState } from 'react';
let ac;
let lastNote = 0;
let nextNote = 0;
let engine;
function App() {
  const [isPlaying, setIsPlaying] = useState(false);
  const [bpm] = useState(40);
  const oneBeatInSeconds = 60000 / bpm / 1000;
  ac = new AudioContext();
  const sound = (ac: AudioContext, time: number, dur: number) => {
    // creates the sound, connects it and decides when it starts and stops
    const osc = ac.createOscillator();
    osc.connect(ac.destination);
    osc.start(time);
    osc.stop(time + dur);
  };
  const timer = () => {
    // Calculates how long it was in ms from loading the browser to clicking the play button
    const diff = ac.currentTime - lastNote;
    // Schedules the next note if the diff is larger then the setInterval
    if (diff >= oneBeatInSeconds) {
      nextNote = lastNote + oneBeatInSeconds;
      lastNote = nextNote;
      sound(ac, lastNote, 0.025);
    }
    ac.resume();
  };
  if (isPlaying) {
    // If the metronome is playing resumes the audio context
    ac.resume();
    clearInterval(engine);
    engine = setInterval(timer, oneBeatInSeconds);
  } else {
    // If the metronome is stopped, resets all the values
    ac.suspend();
    clearInterval(engine);
    lastNote = 0;
    nextNote = 0;
  }
  const toggleButton = () =>
    isPlaying === true ? setIsPlaying(false) : setIsPlaying(true);
  return (
    <div className="App">
      <div className="Bpm">
        <label className="Bpm_label" htmlFor="Bpm_input">
          {bpm} BPM
        </label>
        <input type="range" min="40" max="200" step="1" value={bpm} />
      </div>
      <button type="button" className="PlayButton" onClick={toggleButton}>
        {!isPlaying ? 'play' : 'stop'}
      </button>
    </div>
  );
}
export default App;