本文暂无简体中文版本,以下为英文版。

(文章)

Clock Design for a Web Step Sequencer: Audio Clock, Lookahead Scheduling and Visual Sync

A single Tone.Loop clock schedules every sound. Audio uses audio time, visuals update later through Draw, and lookAhead is 0.15 seconds.

Polish Design engineering team・2026-02-13

Contents

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).

Illustration of several gears meshing and turning

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.

Fig. 1Sound is scheduled on audio time, and visuals wait for the same moment before updating
Schedule ahead
Trigger
Display
Audio clock
Loop callback
Visuals
1
Audio clockTransport advances ticks by BPMCallback fires every 12 ticks
2
Loop callbackCallback receives audio timeThe time lies before the sound is heard
3
Loop callbackTrigger sound cells by stepstart(time), played by the audio side
4
VisualsDraw.schedule(fn, time)Only registers, does not change the screen yet
5
VisualsStep light and progress bar updateReact state changes only at repaint

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.

Fig. 2Keypress preview and arrangement playback take two separate paths
  1. A sound cell needs to playEach cell has a preview and an arrangement Player
  • User presses a sound cell
    Preview playerPlays after about 18.75 ms, skipping pitch and filter
  • Scheduled by the Loop callback
    Arrangement playerUses the callback's time with 0.15 s lookAhead, through pitch and filter

Values are program settings, not measurements.

Illustration of objects moving forward in order on a conveyor belt

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.

(联系)

Start a project.

三个步骤,取得可判断的做法与预算区间。

  1. 01预约第一场会议:选择时段,不需要完整规格。
  2. 02在线咨询 15 至 30 分钟:取得做法方向、预算区间与时程草案。
  3. 03查看素材后,约 3 至 5 个工作日提供报价单暨交付规格明细表。
邮箱
hello@polish-design.com.tw
通讯地址
(104) 台北市中山区植福路191-1号3F

预约第一场会议

周一至周五 16:00–18:00线上 30 分钟

先选时段,再用 Google 账号或邮箱确认,预约完成可加入日历。

选择时段

或先提交资料

还没想好时段,可以先留下项目内容,专人以邮箱回复。提交后也能接着预约。

补充资料(选填):服务项目、目前阶段、现有链接、得知渠道

数据仅用于本次项目评估。

ISO/IEC 27001:2022 证书

(Certificate)

ISO/IEC 27001:2022

认证机构
ARES International Certification
认证范围
互动沉浸式 3D 系统之开发流程,及网络与基础设施之运维与管理。

到认证机构网站查证