How Tone.js Schedules Events on the Transport Timeline

Tone.js manages the scheduling of audio events through its global Transport timeline, which acts as a master musical clock for synchronizing synthesizers, samplers, and effects. By combining the high-precision clock of the Web Audio API with JavaScript’s event loop, Tone.js allows developers to schedule notes, loops, and coordinate tempo-relative playback with sample-accurate precision. This article explores how the Transport works, how time is represented, and the different scheduling methods available for creating interactive web audio applications.

The Role of Tone.Transport

At the core of Tone.js scheduling is Tone.Transport. Unlike the default Web Audio API AudioContext which starts at zero when the page loads and runs continuously, the Transport can be started, stopped, paused, and looped. It maintains its own internal timeline and handles tempo (BPM), time signature, and swing adjustments.

When you schedule an event on the Transport, its execution time is defined relative to this timeline rather than the absolute real-world time. If the tempo changes, the scheduled events automatically speed up or slow down to maintain their correct musical positions.

Time Formats in Tone.js

To schedule events relative to the transport timeline, Tone.js supports various flexible time formats. These formats are automatically parsed into seconds under the hood:

Low-Level Scheduling Methods

Tone.js provides direct methods on the Transport object to schedule callbacks at specific times.

Tone.Transport.schedule()

The schedule method triggers a callback at a specific point on the timeline. It returns a unique ID that can be used to cancel the event later.

// Schedule a note to play at the start of the second bar
Tone.Transport.schedule((time) => {
    synth.triggerAttackRelease("C4", "8n", time);
}, "1:0:0");

Tone.Transport.scheduleRepeat()

To create repeating events, such as a metronome click, use scheduleRepeat. This repeats a callback at a specified interval.

// Repeat a hi-hat sound every quarter note
Tone.Transport.scheduleRepeat((time) => {
    hat.triggerAttackRelease(time);
}, "4n");

High-Level Event Classes

For complex musical structures, Tone.js offers specialized classes that wrap the low-level transport timeline. These classes make it easier to manage groups of notes and patterns.

// Example of a Tone.Sequence playing a bassline
const sequence = new Tone.Sequence((time, note) => {
    synth.triggerAttackRelease(note, "8n", time);
}, ["C2", "E2", "G2", "B2"], "4n").start(0);

The Look-Ahead Scheduler

To achieve perfect musical timing, Tone.js addresses a fundamental limitation of web browsers: JavaScript is single-threaded and prone to UI-induced lag, while the Web Audio API runs on a separate, high-priority system thread.

If Tone.js attempted to play an audio event at the exact millisecond JavaScript executed the callback, UI rendering or user interaction could cause audible stuttering and jitter.

Tone.js solves this by using a look-ahead scheduler. It looks slightly ahead into the future (typically around 100 milliseconds) and schedules the Web Audio API nodes in advance. The time parameter passed into your transport callbacks represents the precise audio clock time when the sound should play, not the CPU time when the JavaScript function runs. By passing this time argument directly into your instrument triggers (e.g., synth.triggerAttackRelease("C4", "8n", time)), you ensure jitter-free, sample-accurate audio playback regardless of main-thread performance.