All books

Scripting the Sequencer via MCP

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

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

ActionPurpose
sequencer.playStart 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.stopCancel 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

ActionPurpose
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

ActionPurpose
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

ActionPurpose
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

ActionPurpose
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

ActionPurpose
sequencer.progressions.listEnumerate 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

ActionPurpose
sequencer.undoPop one edit. "No history" when empty.
sequencer.redoRepush. 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.
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