Role of Tone.now in Tone.js Event Scheduling
This article explores the crucial role that Tone.now()
plays in audio scheduling within the Tone.js framework. It explains how
this method accesses the underlying Web Audio API clock, why it is
essential for millisecond-precise timing, and how developers can use it
to schedule immediate or relative audio events without experiencing
latency-induced glitches.
Understanding the Audio Context Clock
Unlike standard JavaScript applications that rely on the CPU-bound
system clock (like Date.now() or
performance.now()), Web Audio API applications operate on a
separate, highly precise hardware-stitched clock. In Tone.js,
Tone.now() returns the current time of this audio context
clock in seconds as a floating-point number. Because the audio thread
runs independently of the main JavaScript thread, using
Tone.now() is the only way to ensure that audio events are
synchronized perfectly with what the user hears.
Scheduling Relative Events
In Tone.js, scheduling an event to happen “immediately” cannot be
done by passing a time of 0 or omitting the time parameter
entirely, as this can lead to unpredictable timing and audio artifacts.
Instead, Tone.now() serves as the baseline for all relative
scheduling.
For example, if you want to trigger a synthesizer note to play immediately, you fetch the current audio time and pass it to the trigger method:
const synth = new Tone.Synth().toDestination();
const now = Tone.now();
// Trigger a note immediately
synth.triggerAttackRelease("C4", "8n", now);
// Trigger a second note exactly one second later
synth.triggerAttackRelease("E4", "8n", now + 1.0);By using now + 1.0, you guarantee that the second note
will play precisely one second after the first, regardless of any
main-thread JavaScript congestion or UI slowdowns.
Preventing Audio Glitches with Look-Ahead
The web browser requires a small amount of time to process audio data
before sending it to the speakers. If you schedule an event at the exact
instant returned by Tone.now(), the browser might receive
the instruction too late, resulting in a click, pop, or a delayed
start.
To prevent this, it is common practice to schedule events slightly in the future. This is known as look-ahead scheduling. Adding a small buffer (typically between 50 to 100 milliseconds) gives the Web Audio API enough time to render the audio block smoothly:
const now = Tone.now();
const safeBuffer = 0.1; // 100 milliseconds
// Scheduled safely in the near future to prevent glitching
synth.triggerAttackRelease("G4", "8n", now + safeBuffer);Tone.now() vs. The Transport
It is important to distinguish Tone.now() from
Tone.Transport. While Tone.now() represents
the absolute, ever-incrementing time since the audio context was
started, Tone.Transport represents a relative timeline used
for musical notation, tempos, loops, and beats. Tone.now()
is best used for direct, one-off interactions (like playing a sound when
a user clicks a button), whereas the Transport is used for synchronized,
looping musical arrangements.