Scripting the Sequencer via MCP
The full Sequencer surface is MCP-callable — every UI gesture has an action behind it, plus a few that only show up here (sequencer.state, sequencer.notes.transpose, the scene actions). Together they're ≈30 actions covering grid, patterns, scenes, transport, undo, panel layout, and progression insertion.
This section is the catalog + a playable "build a 2-track loop from scratch" chain.
Multi-instance: instance_id
Every action takes an optional instance_id. Omit to target the active sequencer (the one with UI focus / the one a fresh project lands on, sequencer-default). Pass a specific id to address it directly. Discover instances via routing.list — each sequencer is a Source with kind: "midi" and a stable sequencer-<id> shape.
Multiple instances play independently and concurrently (drum-seq + harmony-seq + bass-seq is a normal setup).
The full catalog by area
Transport + state
| Action | Purpose |
|---|---|
sequencer.play | Start the sequencer against the master clock. Per-instance. Returns once a single pass completes in single loop mode, or after the first pass with a "looping in background" message in infinite. |
sequencer.stop | Cancel at the next step boundary. "No sequence playing" when nothing is in flight. |
sequencer.state { instance_id? } | Snapshot of everything: { instance_id, steps_per_pattern, steps_ms, loop_mode, playing, key, active_track_id, tracks, scenes, active_scene_id }. Steps are sparse — only occupied steps are listed, each with its index i. |
sequencer.loop.set { loop: true | false } | Set / clear loop mode (mirrors the panel's Repeat toggle). |
surface.open { surface: "faustwave-bundled/sequencer#roll" | "…#patterns" } | Show the roll or the launching surface (no audio impact). |
Tracks
| Action | Purpose |
|---|---|
sequencer.track.add { name? } | Append. Returns { ok, track_id }. |
sequencer.track.remove { track_id } | Delete. Refuses the last track. |
sequencer.track.rename { track_id, name } | Rename. |
sequencer.track.enabled.set { track_id, enabled } | Mute (cursor still advances). |
sequencer.track.soloed.set { track_id, soloed } | Solo wins over un-muted. |
sequencer.track.midi-channel.set { track_id, channel } | 0 = Omni, 1–16 = specific. |
sequencer.active-track.set { track_id } | Change which track has UI chrome focus. |
Steps
| Action | Purpose |
|---|---|
sequencer.step.set { step, note? | chord? | rest? | clear?, velocity?, track_id? } | Set or clear a cell. Pass exactly one of note / chord / rest / clear. Velocity default 100. |
sequencer.step.duplicate { step, track_id? } | Copy previous step (index-1) onto step. Errors on step 0 or rest source. |
sequencer.step.smear { from, to, track_id? } | Copy from into every REST cell in [from..to]. Never overwrites. |
sequencer.step.gate.set { step, hold, track_id? } | Gate length on an existing note/chord step. |
sequencer.step.length.set { step, ms } | Pattern-level step duration override; ms: null reverts. |
sequencer.step.length.clear-all { instance_id? } | Reset every step's length override. |
sequencer.notes.transpose { semitones, steps?, track_id? } | Pitch shift. Clamped to C0..C8 as a whole. |
sequencer.clear { track_id? } | Reset every step on the track to rest. |
sequencer.pattern.length.set { length: 8 | 16 | 32 | 64 } | Change pattern length live. |
sequencer.key.set { tonic_name? | tonic_pc?, scale_id } | Set the musical key (drives grid highlight + progression transposition). |
Patterns
| Action | Purpose |
|---|---|
sequencer.pattern.add { track_id? } | Append a pattern to a track. Up to 8. Returns { ok, pattern_id }. |
sequencer.pattern.remove { pattern_id, track_id? } | Delete. Refuses the last pattern. |
sequencer.pattern.rename { pattern_id, label, track_id? } | Rename. |
sequencer.pattern.active.set { pattern_id, track_id? } | Launch — queues at quantize boundary when playing, immediate when stopped. |
sequencer.pattern.edit.set { pattern_id, track_id? } | View-only — switches which pattern the piano-roll shows. |
sequencer.pattern.quantize.set { pattern_id, quantize, track_id? } | Per-pattern launch quantize. Five values: instant, next-beat, next-bar (default), next-2-bars, next-4-bars. |
Scenes
| Action | Purpose |
|---|---|
sequencer.scene.add { from_current?, label? } | Snapshot current activePatternId per track as a scene. Returns { ok, scene_id }. |
sequencer.scene.remove { scene_id } | Delete. |
sequencer.scene.rename { scene_id, label } | Rename. |
sequencer.scene.launch { scene_id } | Queue each track's assigned pattern via per-pattern quantize. |
Progressions
| Action | Purpose |
|---|---|
sequencer.progressions.list | Enumerate the 8 documented progressions: axis-major, axis-minor, i-iv-v-major, ii-v-i, i-vi-iv-v, minor-pop-loop, folk-minor, twelve-bar-blues. |
sequencer.progression.insert { progression_id, distribution, key?, track_id? } | Drop onto the track. distribution: "bar" (default, 4 chords/16 steps) or "half-bar" (8 chords). key optionally transposes; omit to use the progression's canonical key. |
Undo
| Action | Purpose |
|---|---|
sequencer.undo | Pop one edit. "No history" when empty. |
sequencer.redo | Repush. Clears whenever a fresh edit is made. |
A build-from-scratch chain
actions:
- action_id: sequencer.track.add
input: { name: "Chords" }
save_as: chords
label: "▶ 1. Add a Chords track (self-contained — your tracks stay untouched)"
- action_id: sequencer.key.set
input: { tonic_name: A, scale_id: aeolian }
label: "▶ 2. Key = A minor"
- action_id: sequencer.progression.insert
input:
progression_id: axis-minor
distribution: bar
track_id: "{{chords.track_id}}"
label: "▶ 3. Drop axis-minor chords on bars"
- action_id: sequencer.track.add
input: { name: Bass }
save_as: bass
label: "▶ 4. Add a Bass track"
- action_id: sequencer.active-track.set
input:
track_id: "{{bass.track_id}}"
label: "▶ 5. Focus Bass"
- action_id: sequencer.step.set
input:
track_id: "{{bass.track_id}}"
index: 0
note: 33
velocity: 110
label: "▶ 6. Root note on step 0 (A1, accent)"
- action_id: sequencer.step.smear
input:
track_id: "{{bass.track_id}}"
from: 0
to: 15
label: "▶ 7. Smear the root across the bar"
- action_id: sequencer.loop.set
input: { loop: true }
label: "▶ 8. Loop mode = infinite"
- action_id: sequencer.play
input: {}
label: "▶ 9. Play"
- action_id: sequencer.stop
input: {}
label: "■ 10. Stop"
Driving multi-instance from MCP
For a setup with two sequencers (e.g. drum sequencer + harmony sequencer), pass instance_id explicitly:
{ "action_id": "sequencer.play", "input": { "instance_id": "sequencer-drum" } }
A combined sequencer.play for every instance is just N parallel calls; they're rate-limited by the master clock so they'll start in lockstep anyway.
A look at sequencer.state
The "what's on the grid right now" call. One call returns the whole sequencer — every track, every pattern, the scene catalog:
{
"instance_id": "sequencer-default",
"steps_per_pattern": 16,
"steps_ms": [null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null],
"loop_mode": "infinite",
"playing": true,
"key": { "tonic_pc": 9, "scale_id": "aeolian" },
"active_track_id": "track-1",
"tracks": [
{
"id": "track-1", "name": "Track 1", "midiChannel": 1, "enabled": true, "soloed": false,
"activePatternId": "pattern-a", "editPatternId": "pattern-a", "pendingPatternId": null,
"patterns": [
{ "id": "pattern-a", "label": "A", "steps": [
{ "i": 0, "kind": "chord", "notes": [57, 60, 64] },
{ "i": 8, "kind": "note", "note": 57, "velocity": 90 }
] },
{ "id": "pattern-b", "label": "B", "steps": [] }
]
}
],
"scenes": [],
"active_scene_id": null
}
Steps are sparse. A grid is mostly rests, so steps lists only the occupied steps, each with its 0-based index i — the same index sequencer.step.set takes. Every index below steps_per_pattern that is not listed is a rest; an empty steps array is an empty pattern. activePatternId is the pattern that plays, editPatternId the one step writes land in.
Useful when you want to render the grid into prose ("track 1 plays an A minor stab on step 0 and rests for the rest of the bar") or when the assistant needs to know the current state before deciding the next move.
Where to go from here
- FaustWave — Patchbay — wire the sequencer to multiple instruments per channel.
- FaustWave — Mixer & Master — mix each instrument the sequencer drives.
- FaustWave — AI Assistant — ask "fill the active track with a chord progression that fits the bass line" and watch the chain run.