How to Add Swing to Tone.js Transport
Adding a natural groove or swing to your digital audio projects can
make programmed beats sound much more human and musical. This article
explains how to configure and control the swing feel using
Tone.Transport in Tone.js, covering key properties like
swing and swingSubdivision, and providing a
practical code example to get your sequences grooving.
Understanding Swing in Tone.js
In music production, “swing” delays the timing of every second note (the off-beat) in a specific grid. This creates a skipping or shuffling rhythm instead of a rigid, straight beat.
In Tone.js, swing is managed globally via
Tone.Transport. It affects all scheduled events that rely
on the Transport’s timeline—such as Tone.Sequence,
Tone.Loop, and Tone.Part—provided they are
scheduled using subdivisions that match or divide the swing grid.
Key Transport Properties
To implement swing, you need to configure two primary properties on
Tone.Transport:
Tone.Transport.swing: This property takes a normalized value between0(no swing, completely straight) and1(maximum swing). A value of0.5is a common starting point for a classic half-triplet swing feel.Tone.Transport.swingSubdivision: This defines the grid size that will be swung. It is represented as a subdivision string, such as'8n'(eighth notes) or'16n'(sixteenth notes). The default value is'16n'.
Step-by-Step Implementation
To apply swing to your playback, set up your audio source, configure the Transport’s swing settings, schedule your notes, and start the Transport.
import * as Tone from 'tone';
// 1. Create an instrument
const synth = new Tone.MembraneSynth().toDestination();
// 2. Configure the swing settings on the Transport
Tone.Transport.swing = 0.6; // Swing amount (0 to 1)
Tone.Transport.swingSubdivision = '8n'; // Apply swing to eighth notes
Tone.Transport.bpm.value = 120; // Set tempo
// 3. Create a sequence of eighth notes
// The sequence callback receives the precise 'time' parameter
const seq = new Tone.Sequence((time, note) => {
synth.triggerAttackRelease(note, '8n', time);
}, ['C2', 'E2', 'G2', 'B2'], '8n');
// Start the sequence and the transport
seq.start(0);
Tone.Transport.start();Crucial Rules for Swing to Work
For the swing effect to be audible in your application, you must follow these rules:
- Use the
timeargument: When triggering sounds inside a loop or sequence callback, you must pass thetimeparameter (provided by the callback) into your synth’s trigger method (e.g.,synth.triggerAttackRelease(note, duration, time)). If you omit thetimeargument, Tone.js will play the note immediately on the audio thread, bypassing the Transport’s swung schedule. - Match subdivisions: The events in your sequence
must align with the
swingSubdivision. For example, if yourswingSubdivisionis set to'8n', notes scheduled on quarter notes ('4n') will not swing because they land on the strong, on-beat subdivisions. Only the intervening eighth notes will be delayed.