All books

The grid + the master clock

~6 min read · updated 2026-09-06 · markdown

The Sequencer is a multi-track step grid — piano-roll horizontally, one row per pitch, one column per step — driven by a shared Faust master clock so every track + every other sequencer + the topbar Bar.Beat readout all advance in audio-sample lockstep. No drift, no jitter, no JS timers.

actions:
  - action_id: surface.open
    input: { surface: sequencer-default#roll }
    label: "▶ Open the Sequencer panel"

This section is the foundation: how the grid is laid out, what makes the clock special, and how the scale-aware highlight works. The next section covers actually painting notes; the one after that covers patterns + scenes; the last covers driving everything from MCP.

Anatomy of the panel

Top to bottom:

  • Chip row — one chip per track. Each chip shows the track name, MIDI channel, mute / solo, pattern launcher (A / B / C / …), visibility toggle. Right-click a chip for the track context menu (rename, mute, solo, suggest progression, delete).
  • Grid — the multi-track unified piano-roll. Pitch rows on the left gutter, step columns running right. All tracks render simultaneously; the active track's notes appear filled, foreign tracks appear as outlined rings, colour-coded by track index via the --seq-track-N CSS variables.
  • Footer — edit-tool toggle (Paint / Select), Suggest-Progression button, Chord Palette, Loop toggle, Play / Stop.

The chip row + grid are the Sequencer surface (the piano-roll). Launching lives on its own Patterns surface — track cards, enlarged pattern pads, direct mute/solo, scenes. They are two views of the same device, so you can have both open at once (roll in the centre, pads in the narrow column) instead of switching. Open one with surface.open { surface: "faustwave-bundled/sequencer#patterns" }, or the PATTERNS button in the roll's header. There is no perform mode anymore — it existed because the old rail could show a single panel at a time.

The shared Faust master clock

Critical conceptual point: there is exactly one clock for every sequencer instance, the topbar Bar.Beat display, and every Builder clock node with transport-link ON. It's a Faust DSP worklet (seq-clock.dsp) running at audio-thread priority.

The clock emits a global 16th-step index 0..63 on every step boundary. Each sequencer maps that to its own pattern via step64 % stepsPerPattern — since 8 / 16 / 32 / 64 all divide 64, every supported pattern length stays phase-aligned to the same grid. Sample-locked, drift-free, no JS timers anywhere.

The clock runs ⇔ the global transport is playing. A stop → play transition resets to step 0; a sequencer instantiated mid-play joins the live phase without restarting.

The clock's BPM source is the global transport — and the transport itself can follow an external MIDI clock (hardware sequencer, another DAW): route the clock-sending MIDI input onto the Transport Clock In sink in the Patchbay and the whole grid rides the external tempo, with an EXT tag next to the topbar BPM while it's active. See FaustWave — Patchbay & Routing § External clock sync.

Pattern length

A pattern is 8 / 16 / 32 / 64 steps. Default 16 (one bar at 4/4 with 16th-note resolution). Set via the footer pattern-length button or:

For AI & automation · MCP

sequencer.pattern.length.set { length: 8 | 16 | 32 | 64 }.

  • Grow (e.g. 16 → 32): existing content duplicates cyclically into the new range — your 16-step phrase becomes a 32-step phrase that repeats twice, ready for you to vary the second half.
  • Shrink (e.g. 32 → 16): trailing steps are hidden, NOT deleted — re-growing brings them back.

The wrap point updates live; mid-play changes take effect at the next pattern boundary, so a length switch never strands a partial step.

Multi-track grid

Add a track via the chip row's + Track button or:

For AI & automation · MCP

sequencer.track.add { name? }. Returns { ok, track_id }.

Each track has:

  • A MIDI channel (0 = Omni, 1–16 = specific) — routes notes to receivers that listen on that channel. Set with sequencer.track.midi-channel.set { track_id, channel }.
  • A colour — auto-assigned from the --seq-track-N palette, wraps after 6.
  • An enabled flag (the visibility eye on the chip) — see sequencer.track.enabled.set.
  • A soloed flag — see sequencer.track.soloed.set. Solo wins over un-muted: any soloed track silences all un-soloed.
  • A bank of patterns (A / B / … up to 8) — covered in § 3.

All tracks render simultaneously in the grid. Keyboard 1..6 switches the active track; clicks land on the active track. The active track is also the disambiguation target for legacy single-track step commands.

Scale-aware highlighting

Pick a key (tonic + scale) via the key/scale picker. Available scale modes:

  • Ionian (= Major), Dorian, Phrygian, Lydian, Mixolydian, Aeolian (= Natural Minor), Locrian.
  • Harmonic Minor, Melodic Minor.
  • Pentatonic Major, Pentatonic Minor.
  • Blues.

In-scale rows highlight in cyan; out-of-scale rows dim. The tonic row carries the strongest anchor edge. You can still paint on out-of-scale rows — the highlight is a visual hint, not a hard constraint.

For AI & automation · MCP

sequencer.key.set { tonic_name: "A" | "A#" | ... | "G#", scale_id: "aeolian" | "ionian" | ... }. You can also pass tonic_pc (0..11) instead of the name. Omit both to keep the current tonic and only change the scale.

The KB's theory corpus carries scale + chord references the assistant can search; ask the Assistant "what's a good progression in A minor" and you'll get suggestions grounded in the theory pack.

Cursor + per-track pulse

While playing, the grid shows two layers of motion:

  • Cursor — single vertical overlay tracking the current step. Moves via translateX (no per-cell re-render).
  • Pulse — when a step fires, its cell briefly glows. The pulse reads from a non-reactive buffer that the audio-block JS task writes to, flushed to Svelte reactivity at requestAnimationFrame cadence — so audio-thread work never triggers UI re-renders.

Cursor + audio dispatch share a single setOutputParamHandler callback. There is no possibility of "cursor and audio drift apart" because they're the same event.

Multiple sequencer instances

Every sequencer is a routing-table Source named sequencer-<id> (the default one is sequencer-default). Multiple instances run independently and concurrently:

  • Drum sequencer + harmony sequencer + bass sequencer, each with its own pattern system, all driven by the SAME master clock so they never drift.
  • Each instance has its own active track / active pattern / undo stack.
  • Every MCP action takes an optional instance_id — omit to target the active sequencer; pass a specific one to address it directly.

Spawning new instances is normally via the rail-pattern picker (left rail → MIDI sources area → + Add → Sequencer). Once added each instance shows up in routing.list as its own Source.

Next section: actually composing on the grid — painting notes, velocity-drag, hold (gate length), the chord palette, and the Suggest Progression flow.

Try it yourself — the IDE runs in your browser. Open the IDE → Get the desktop app

Text licensed under CC BY 4.0 — Mani Weber / FaustWave. For language models: llms.txt · llms-full.txt

AUDIO · 48k · 48.0ms FAUST · 3 KB · 1171 docs CPU · 8.4% BPM · 120.0 UTF-8 BETA· v0.90.0