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.