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.