The arrangement engine of this web sampler keeps its clock on the audio side. Sound is scheduled against audio time, and the visuals follow separately. The sections below cover the data model, the main clock, how sound and visuals divide the work, the lookAhead trade-off, section switching during playback, and the time conversion for groove.
Exporting to MP3 is a separate topic in the same series. The offline rendering approach is covered in Offline rendering and MP3 export in the browser.
JavaScript timers do not trigger sound directly
setTimeout, setInterval and requestAnimationFrame all queue on the browser's main thread. When the page is busy painting or computing, callbacks arrive late and the rhythm drifts along. In A tale of two clocks, Chris Wilson recommends pairing a JavaScript timer with the audio hardware's own scheduling: the timer wakes up at intervals and schedules the upcoming sounds ahead of time on the audio clock.
All scheduling times in Web Audio are relative to the AudioContext's currentTime, and `start(when)` plays a sound at an exact time; see the W3C Web Audio API. On this foundation, the Tone.js Transport provides musical time, including BPM, bars and ticks, where one quarter note (a 4th-note beat) equals 192 ticks (Tone.js Time).

Arrangement data model
For one cassette of the sampler, the arrangement part consists of the following fields.
- 4 sequences (SEQ 1 to 4), each with 12 sound cells.
- Each sound cell holds 32 boolean values. Each value is one step, and a lit step produces sound.
- One step is an eighth note, a bar has 8 steps, and 32 steps make 4 bars.
- The loop length of each sequence is 1 to 4 bars, stored as an "N:0:0" string. The engine multiplies the bar count by 8 to get the number of steps.
- BPM defaults to 120, and can be set from 30 to 300.
Each sound cell also carries volume, reverse, pitch, filter, swing and groove parameters, plus the start and end seconds of the sample. These parameters are read only at trigger time and do not affect the clock itself.
One main clock drives every sound
The main clock is a single `Tone.Loop` with an interval of 12 ticks, driven by `Tone.Transport`. With PPQ at 192, an eighth note is half a quarter note (the 4th-note beat), so every 8 callbacks advance by one eighth note. At 120 BPM, one callback takes about 31.25 ms.
The callback maintains a `currentStep`, which increases by 1 each time and wraps at the sequence length times 8, giving the current position within the sequence. All 12 sound cells share this one counter. Inside the callback, each cell is checked for whether the step is lit, and a lit step is triggered with the `time` passed into the callback.
A single clock has three benefits. The relative position of the cells is decided by step count alone, so separate timing cannot make the cells drift. Changing BPM touches only one place, the Transport. Sequences of different lengths each wrap on the same counter, so section switching and loop length handling can all live inside the callback.
Sound and visuals are scheduled separately
The `time` that a Loop callback receives is audio time, and it lies slightly before the moment the sound is actually heard. Sound calls `start` with this `time` directly, and the audio side plays it at the exact moment.
Visuals cannot change at that same moment. When the callback fires, the sound has not come out yet, so changing the step light now would light it before the sound. The step light and progress bar instead use `Tone.Draw.schedule(fn, time)`, which postpones the work until near that time, when the screen is about to repaint, and React state changes only then. A requestAnimationFrame callback already runs before the next repaint (MDN), so the screen update and the repaint land on the same beat.
The step light is scheduled once per callback, and its position is the eighth-note step count wrapped at the sequence length. The progress bar updates only when the counter is even, which halves the update rate. Visual state during a section switch goes through the same Draw path.
Compiled from the engine design. Timing is based on the time passed into the Loop callback.
lookAhead trade-offs
The lookAhead of Tone.js decides how far ahead of playback scheduling happens. The official Performance notes state that this gives the scheduler time to finish before the sound is heard, at the cost of latency. To reduce latency, lookAhead can be lowered or set to 0.
The arrangement engine sets lookAhead to 0.15 seconds each time a cassette is initialized, to avoid audio dropouts. When a callback arrives late because the main thread is busy, there is still a 0.15-second margin to finish scheduling before the sound plays.
Keypress preview has no scheduling problem. What matters is the delay between pressing and hearing. Each sound cell therefore has two independent `Tone.Player` instances. The preview player skips pitch and filter, and its start time is `Tone.immediate() + lookAhead / 8`, about 18.75 ms, leaving just enough margin to avoid clicks. The arrangement player has pitch and filter and goes through scheduling. The two can sound at the same time, and either one can be stopped alone.
- A sound cell needs to playEach cell has a preview and an arrangement Player
- User presses a sound cellPreview playerPlays after about 18.75 ms, skipping pitch and filter
- Scheduled by the Loop callbackArrangement playerUses the callback's time with 0.15 s lookAhead, through pitch and filter
Values are program settings, not measurements.

Section switching during playback takes effect at the loop start
When another sequence is pressed during playback, the engine only records the pending sequence and updates the selection shown on screen, while the sound stays unchanged. When the callback reaches a `currentStep` of 0 and a sequence is pending, it switches and clears the record.
This 0 is the loop start of the current sequence, not every bar. For a sequence 4 bars long, the switch waits until all 4 bars have played. The current phrase is therefore never cut in the middle, and the next sequence joins from the beginning. When playback is stopped, pressing a sequence switches immediately.
Converting groove into a time offset
Each sound cell can pick a groove. Groove data holds one offset per step, measured as a fraction of an eighth note, where a negative value is earlier and a positive value is later. Each cell also has its own swing setting.
The trigger time equals the scheduled time plus the step's offset times the cell's groove amount, plus swing times 0.5, with the whole result multiplied by the length of one eighth note in seconds. That length is 60 divided by BPM, divided by 2. Because the offset is converted to seconds from BPM, the feel of groove and swing scales when the tempo changes. The offset is added to the `time` handed to Tone, so it is still resolved inside the audio clock, with no extra timer.
When this clock is not needed
Playing a single sound effect or interface cue, such as a button click, only needs the sound to start at the moment of the action, with no clock or lookahead scheduling. For live monitoring of microphone input with playback, the latency from lookAhead is extra overhead and needs a separate evaluation.
Further reading and related topics
The official tutorial videos can be compared against the sequences and section switching in this article: 1-5 Building a Basic Loop Section demonstrates a basic loop, and 2-3 Section Design demonstrates arranging multiple sections. The overall approach to the sampler is in the TMC Cloud Sampler case.
Web audio also includes sample loading and caching, the processing chain of each sound cell, and offline rendering of output files; the output part is in Offline rendering and MP3 export in the browser. For planning web audio, a sampler or an arrangement tool, see Web audio and sampler development, or contact the development team to discuss scope.




