Scheduling Complex Events with Tone.Part in Tone.js
In web audio development, scheduling complex and non-repetitive
musical structures requires precise timing controls. This article
explains how Tone.Part in Tone.js handles complex musical
events at absolute time locations. We will explore how to initialize a
Tone.Part with an array of time-stamped events, trigger
custom callbacks, and manage playback along the global transport
timeline.
Understanding Tone.Part
Tone.Part is a specialized class in the Tone.js library
designed to handle a score-like collection of events. Unlike
Tone.Sequence or Tone.Loop, which are built
for repeating, grid-based patterns, Tone.Part is optimized
for events that occur at irregular, absolute time intervals. This makes
it the ideal tool for rendering complete musical compositions, MIDI file
transcriptions, or complex ambient generative structures where notes do
not follow a simple loop.
Structuring Events with Absolute Times
To schedule events with Tone.Part, you define an array
of objects. Each object must contain a time property, which
dictates its absolute playback position relative to the start of the
part. The remaining properties in the object can contain custom data,
such as note names, duration, and velocity.
Time values can be expressed in various Tone.js formats: *
BBS (Bars:Beats:Sixteenths): "0:2:0"
(measure 0, beat 2, sixteenth 0). * Seconds:
1.5 or "1.5s". * Notation:
"4n" (quarter note), "8t" (eighth-note
triplet).
Here is an example of an event array structured for
Tone.Part:
const compositionEvents = [
{ time: "0:0:0", note: "C4", duration: "8n" },
{ time: "0:1:2", note: "E4", duration: "16n" },
{ time: "0:3:0", note: "G4", duration: "4n" },
{ time: "1:2:0", note: "C5", duration: "2n" }
];Initializing and Triggering the Part
Once your event data is ready, you instantiate Tone.Part
by passing a callback function and the array of events to the
constructor. The callback function is executed whenever an event is
reached on the timeline. It receives two parameters: the absolute Web
Audio timeline time at which the event should be scheduled,
and the custom event object itself.
const synth = new Tone.Synth().toDestination();
const part = new Tone.Part((time, event) => {
// Use the absolute time provided by the callback for sample-accurate scheduling
synth.triggerAttackRelease(event.note, event.duration, time);
}, compositionEvents);Using the time parameter passed into the callback is
critical. Rather than triggering the synthesizer immediately, passing
the time variable to triggerAttackRelease
ensures sample-accurate timing, preventing latency and jitter.
Aligning with the Transport Timeline
A Tone.Part does not play automatically; it is bound to
the global Tone.Transport. You must schedule the part to
start at a specific time on the transport timeline and then start the
transport itself.
// Start the part at the very beginning of the Transport timeline
part.start(0);
// Start the Transport to begin playback
Tone.Transport.start();The absolute times defined inside your event array are offsets
relative to the start time passed to part.start(). For
example, if you call part.start("1:0:0"), an event defined
at { time: "0:2:0" } will actually play at
"1:2:0" on the global transport timeline. This allows you
to easily move, shift, or reuse entire blocks of complex musical
arrangements.