Skip to main content
Glama

Waveform MCP

License: MIT Python

An MCP server that gives an LLM agent control over Tracktion Waveform. Ask Claude to write a song, balance a mix, or render to MP3 — and watch Waveform do it.

Synthwave arrangement built end-to-end via the MCP A 64-bar synthwave instrumental composed via MCP tool calls — drums, bass, two pads, counter, arp, lead. Section markers, tempo automation, sidechain pump, plate reverb, clip-level fades, full master chain.


TL;DR

  • 150+ MCP tools spanning composition, MIDI, audio clips, plugins, automation, mixing, mastering, render, and Waveform UI control

  • Six composers (synthwave, lofi, lofi-generative, djent, djent-real with VST amp sims, rainstorm) plus compose_from_reference and compose_variations

  • Music theory + mix balance knowledge baked in — scales, progressions, voice leading, per-genre reference levels, plugin-aware fader calibration

  • Verified XML round-trip between the in-memory model and Waveform's .tracktionedit format, with 250 regression tests guarding every fix


Related MCP server: ableton-mind

What this gives you

  • 150+ MCP tools across edit lifecycle, tracks/buses, MIDI, audio clips, plugins, automation, music theory, mix balance, composition primitives, drum pattern library, progression generator, voice leading, arrangement planner, audio analysis (LUFS / tempo / key detection), reference song database, mastering chain templates, MIDI export, chord chart export, A/B snapshots, tool-call history, render, loop library, VST discovery, schema capture, and Waveform UI control

  • Six composers — compose_lofi_track, compose_synthwave_track, compose_djent_track, compose_djent_real (with VST amp sims), compose_lofi_generative (every seed = a different song), compose_rainstorm

  • Reference-driven composition pipeline — point at any audio file (compose_from_reference) and the MCP analyzes it, builds a CompositionSpec of its tempo / key / sections / chord progression / instrumentation, optionally transcribes the polyphonic content (BasicPitch), detects reverb tail and sidechain pumping, then writes a Waveform edit modeled on it

  • Plugin-aware fader calibration — mix_apply_reference knows that a sforzando-fed SFZ is ~16 dB quieter than a sample-based drum kit at the same fader, so role-target dB lookups land at the audible level you asked for instead of the literal fader value

  • compose_variations to spin off N seeded variations of any composer

  • A music-theory knowledge layer — scales, chord progressions, cadences, song forms, voice-leading rules, mix-balance reference levels per genre, reference song database

  • State-aware Waveform launch — waveform_launch detects whether the file is already loaded, what view Waveform is on, whether a modal dialog is blocking, then chooses the right path (cold-start spawn, focus + revert from disk, or real OLE drag-drop from Explorer)

  • Verified round-trip between the in-memory model and Waveform's .tracktionedit XML

  • Clip-level fades, gain, offset, automation curves — proven primitives the LLM can use to iterate musically

  • Reliable workflow loop — compose → write → reload via File → Revert to saved → listen → tweak

  • 250 tests passing — every fix has a regression test guarding it


Status

Working end-to-end on Windows + Waveform 13. macOS / Linux paths exist for content discovery (presets, loop library, VST list) but Windows-only UI control via UIA / pywinauto for now.

Built and tested through ~30 hours of human-in-the-loop iteration with Claude. Both composers have been rendered to MP3 multiple times and the user has signed off on the resulting tracks.


Quick start

Install

cd "C:\path\to\waveform MCP"
python -m venv .venv
.venv\Scripts\Activate.ps1
pip install -e .

Wire to Claude (Code, Desktop, or any MCP client)

~/.claude.json or your client's MCP config:

{
  "mcpServers": {
    "waveform": {
      "command": "waveform-mcp"
    }
  }
}

Try it

Open Waveform, then ask Claude:

  "Use compose_synthwave_track to make a synthwave song,
   save it to my Documents/Waveform folder, and reload it
   in Waveform so I can hear it."

The LLM calls compose_synthwave_track → waveform_revert_to_saved and you press play. Then iterate: "turn down the bass" → the LLM updates MIX_BALANCE["synthwave"]["bass"] and reloads.


Architecture

Four layers, each with a clear contract:

┌──────────────────────────────────────────────────────────────────┐
│  LLM (Claude / any MCP client)                                   │
└────────────────────────────┬─────────────────────────────────────┘
                             │ MCP stdio
┌────────────────────────────▼─────────────────────────────────────┐
│  MCP server (server.py) — 150+ tools                             │
└────────────────────────────┬─────────────────────────────────────┘
                             │
              ┌──────────────┼─────────────────────┬──────────────┐
              ▼              ▼                     ▼              ▼
      ┌──────────────┐ ┌──────────┐ ┌─────────────────────┐ ┌──────────────┐
      │ Edit model   │ │ Knowledge│ │ Reference pipeline  │ │  Waveform    │
      │ (in-memory)  │ │  (data)  │ │ (audio file → song) │ │  UI control  │
      ├──────────────┤ ├──────────┤ ├─────────────────────┤ ├──────────────┤
      │ Tracks       │ │ Scales   │ │ music-analyzer       │ │ pywinauto +  │
      │ Clips        │ │ Chords   │ │ reference_adapter    │ │ UIA + ffmpeg │
      │ Notes        │ │ Forms    │ │ poly_transcribe (BP) │ │ Drag-drop    │
      │ Plugins      │ │ Mix      │ │ production_analysis  │ │ Menu invoke  │
      │ Automation   │ │ Velocity │ │ automation_features  │ │ Revert       │
      │ Markers      │ │ Rhythm   │ │ → CompositionSpec    │ │ Render→MP3   │
      └──────┬───────┘ └──────────┘ └──────┬──────────────┘ └──────┬───────┘
             │                             │                       │
             │           ┌─────────────────┘                       │
             ▼           ▼                                         ▼
   ┌────────────────────────┐                          ┌────────────────────┐
   │ compose_from_spec      │                          │ Waveform 13        │
   │ + mix_calibration      │                          │ (the running app)  │
   │ ↓ xml_writer.py        │                          └────────────────────┘
   │ ↓ .tracktionedit       │ ◀──────────── State-aware launch ──────────┘
   └────────────────────────┘

Key design choice: The model is not a 1:1 of the Tracktion ValueTree — it's the shape the LLM wants to work with, projected onto the XML on save and projected back on load. This makes tools simple (audio_clip_import(track_id, file_path, start_beats, length_beats, fade_in_beats, ...)) instead of forcing the LLM to think in JUCE-internals.


Tool catalog

Edit lifecycle (edit.py)

edit_create · edit_open · edit_save · flush · edit_summary · edit_inspect · undo · narrate

Tracks + mix + bus routing (tracks.py)

track_add · track_remove · mix_set · mix_apply_reference · send_add · marker_add · tempo_set · key_set · bus_create · bus_route

mix_apply_reference(track_id, genre, role) looks up the dB target from a curated mix-balance table (MIX_BALANCE[genre][role]) — the "kick is the anchor, bass 5-6 dB under" reference distilled into a callable.

bus_create("Drum Bus") + bus_route(kick_track, drum_bus) lets you stack tracks onto a submix for shared compression / EQ.

MIDI (midi.py)

midi_clip_add · midi_notes_add · midi_notes_clear · midi_clip_quantize

Audio clips (audio.py, clips.py)

audio_clip_import (with gain_db, fade_in_beats, fade_out_beats, offset_in_source_beats) clip_list · clip_set · clip_move · clip_resize · clip_duplicate · clip_remove

Plugins (plugins.py, preset_library.py)

plugin_list · plugin_add · plugin_set_param · plugin_remove plugin_add_reverb (plate / natural / non-linear with sensible defaults) plugin_add_drum_kit (Sampler-backed, one SOUND per pad) plugin_add_modifier (LFO / envelope / sidechain — schema TBD on full Waveform support) plugin_discover (parses knownPluginList64.settings to list installed VSTs) EQ + compressor helpers: eq_high_pass · eq_low_pass · eq_tilt · compress_glue · compress_smash waveform_preset_list · waveform_preset_read · waveform_plugin_types

Composition primitives (melody.py, drum_patterns.py, composition.py)

melody_generate(scale, contour, density) — rule-based melody from contour shape (arch / descending / wave / question_answer / static) arp_pattern(chord, rate, direction, octaves) — arpeggio over a chord (up / down / up_down / random / octave_alternate) bassline_generate(roots, feel) — bass under a progression (pump / walking / half_time / sub / octave_jumps / dub) motif_develop(notes, transformation) — variations: transpose / invert / retrograde / augment / diminish / sequence_up / sequence_down drum_pattern(genre, role, density, length_bars) — 50+ named grooves indexed by (genre, role); synthwave, lofi, hip_hop, edm, house, techno, trap, dnb, rock, pop, ambient drum_pattern_list — show the catalog progression_generate(root, mode, length, end_cadence, style) — invent chord progressions; styles: pop / jazz / modal / synthwave / sad / uplifting voice_lead(prev_voicing, next_chord) — minimal-motion voice leading arrangement_plan(genre, target_seconds, energy_curve) — produce a section list with bars + role + energy 0..1; curves: slow_burn / immediate_hook / build_drop_build / call_response

Audio analysis (audio_analysis.py, audio_detect.py)

audio_loudness_lufs(file) — integrated LUFS, true peak, LRA via ffmpeg ebur128 audio_spectrum(file, bands=10) — log-spaced RMS-per-band frequency balance audio_compare(a, b) — diff LUFS / peak / per-band spectrum between two files audio_detect_tempo(file) — BPM via onset autocorrelation (~1% accuracy on clear loops) audio_detect_key(file) — root + mode via Krumhansl-Schmuckler chromagram

Reference songs (reference_songs.py)

reference_song_lookup(name) / reference_song_list — curated DB of 11 references across synthwave / pop / house / ambient / lofi (Drive (Kavinsky), Midnight City (M83), Sweet Dreams, Africa, In the Air Tonight, Strobe, Miami, Hide and Seek, Still Alive, generic chillhop, generic outrun) with BPM, key, form hint, energy curve, signature elements.

Mastering / mix workflow (workflow.py)

master_chain_apply(template) — drop-in chains: clean_pop / loud_edm / lofi_warm / cinematic_dynamic / podcast_voice / no_processing snapshot_save(name) / snapshot_recall(name) / snapshot_list — A/B compare versions of an Edit edit_transpose(semitones) — shift all MIDI notes edit_set_tempo(bpm) — change project tempo compose_variations(composer_name, prefix, count) — generate N seeded variations of any composer tool_history(last_n) — list recently-called tools (auto-recorded by the @op decorator) edit_save_stem(track_names, out_path) — save a stem-mix copy with named tracks soloed edit_export_chord_chart(out_path, format) — text chord chart from markers + key render_and_audit(mp3_path, revert_first?) — render → mp3 → analyze → return paths + balance/rhythm summary in ONE call. Replaces the manual revert→render→analyze chain that was the most-repeated incantation during iteration. compose_and_reload(composer, out_path, ...) — dispatch jazz/synthwave/lofi composer AND waveform_revert_to_saved in one call. Avoids the compose-then-manually-revert dance.

MIDI export (midi_export.py)

edit_export_midi(out_path) — Standard MIDI File (format 1) with one track per Edit MIDI track, plus tempo + time-sig in track 0.

Automation (automation.py)

automation_add · automation_envelope · automation_clear · automation_list

Targets: pan and plugin/<plugin_id>/<param>. Volume target is disabled at the API level — Waveform's volume plugin doesn't honor our <AUTOMATIONCURVE> schema and silences the track. Use mix_set/mix_apply_reference for static levels and clip_set(fade_in_beats|fade_out_beats) for fades. (The MCP refuses target="volume" with a clear error pointing to alternatives.)

Music theory knowledge (music_theory.py, music_theory_data.py)

17 query tools: theory_scale · theory_modes · theory_diatonic_chords · theory_chord_progression · theory_cadences · theory_song_form · theory_section · theory_genre · theory_arrangement_layers · theory_velocity · theory_rhythm · theory_voice_leading_rules · theory_heuristics · theory_surprise_devices · theory_borrowed_chords · theory_mix_balance · theory_search

The data behind these:

  • 13 scales (major modes, harmonic minor, pentatonic, blues, etc.)

  • 25+ chord progressions (axis_pop, ii_V_I, andalusian, lament_bass, …)

  • Cadences, song forms, sections with role/density/dynamic profiles

  • 14 genres with typical BPM, key tendencies, instruments, hallmark progressions

  • Velocity / rhythm maps (swing ratios, accent bumps, ghost-note ranges)

  • 13 songwriting heuristics (rule-of-3, contrast-required, surprise quota, …)

  • 7 surprise devices (truck-driver modulation, deceptive cadence, …)

  • Mix balance reference table — 14 genres × ~14 roles, fully annotated. Genres: pop / rock / metal / synthwave / lofi / hip_hop / edm / cinematic / ambient / jazz / jazz_standard / rnb / country / folk / orchestral.

progression_generate(style="jazz") now correctly tags V chords as quality="dom7" so downstream voicing produces dominant 7 (b7) instead of major 7 — the V-as-maj7 voicing was a recurring out-of-tune source.

Composers (composer.py, lofi_generative.py, synthwave_generative.py, jazz_generative.py)

  • compose_lofi_track — 32-bar lofi with drums, bass, keys, pad, melody, counter; section-aware velocity envelopes; tempo automation; lofi master chain. Accepts an optional chord_progression arg to override the default Fmaj7-Am7-Dm7-Cmaj7 vocabulary.

  • compose_lofi_generative — generative lofi composer that drives progression_generate, arrangement_plan, drum_pattern, melody_generate, and bassline_generate. Every seed produces a meaningfully different song. Bass locked to kick onsets for rhythm-section meshing; melody quantized to 8th-note grid; melody pinned to Salamander Grand Piano instead of bare 4OSC default-saw; real multi-sample bass guitar when factory samples available.

  • compose_synthwave_generative — generative synthwave composer with 4 always-on layers (drums, bass, pad, lead) + 7 optional layers per seed roll (sub-bass, pluck, counter, pad B, harmony, chimes, riser); section-aware harmonic variation (chorus V7 sub, bridge modulation); layer-by-layer build through intro/outro; lead/counter call-and-response; tom fills before chorus entries; 6 distinct arp profiles (classic 8th, broken 16th, off-beat, two-note pulse, triplet swirl, chord-tone sparse) so back-to-back songs sound different.

  • compose_jazz_generative — jazz/jaunty composer with 5 tracks (drums, upright bass, Rhodes comp, piano lead, sax counter). Authentic walking bass library (7 patterns from Paul Chambers / Ron Carter vocabulary: 1-3-5-approach, 1-3-6-5, 1-3-6-b6, triplet on beat 4, octave drop, ghost 8th, 1-5-3 inversion). Salamander Grand piano lead with call-and-response motif structure (anchored downbeats, syncopated steps, octave-lifted climax phrases, contour-inverted variations). Drums comp for the soloist — ghost rim shots on piano off-beats during solo phrases, kick drops on beat 3, ride thinned, crash on entry. Per-section bass feels (two-feel for heads, walking for blowing, anticipations for transitions, chromatic at section boundaries). Pizzicato VSCO Contrabass with sample-range normalization. AABA form (intro/A1/A2/B/A3/[A4/B2/A5]/outro depending on length).

  • compose_synthwave_track — older 64-bar synthwave with 7 tracks; 9-section form; per-section bass feels; section-keyed arp themes; sidechain-style filter pump

  • compose_djent_track / compose_djent_real — progressive-metal composers (synthetic 4OSC vs full VST amp-sim chain)

  • compose_rainstorm — ambient soundscape with rain + wind + lowpassed-distant thunder

Ensemble cohesion (ensemble.py)

Shared cross-genre primitives that prevent the "puzzle glued together" feeling where tracks sound great alone but don't mesh:

  • section_at_bar(plan, bar) / section_start_bar(plan, role) / bar_in_phrase(plan, bar) — shared section/phrase position math

  • is_solo_phrase(plan, bar) — every 3rd & 4th of each 4-phrase cycle is a "solo phrase" where the lead peaks (octave-lifted/ornamented). Drums comp louder, bass walks chromatic, rhodes lays sparser.

  • section_dynamic_scale(plan, bar) — velocity multiplier per section role for ensemble breathing (intro 0.55 → A1 0.85 → B 1.05 → outro 0.55). Apply uniformly to drums/bass/keys/lead.

  • scale_velocities(notes, plan) — bulk-apply the dynamic curve to a note list.

  • comp_for_lead_drums(lead_notes, plan, total_bars) — generate a drum part that responds to the soloist: ghost rim shots on lead off-beats during solo phrases, phrase-end tom fills, crash on solo entry, sparse ride during peaks.

  • ensemble_dynamics (MCP tool) — return per-section velocity multipliers as data.

Reference-driven composition (composition_spec.py, reference_adapter.py, tools/compose_from_spec.py, tools/import_song.py)

  • analyze_to_spec(audio_path) — runs music-analyzer on any audio file, returns a CompositionSpec dict (tempo, key, sections, chord progression, instrumentation) without writing any edit. Use to inspect / hand-edit the spec before composing.

  • compose_from_reference(audio_path, out_path) — full pipeline: analyze → adapt → optionally transcribe with BasicPitch → optionally detect reverb / sidechain via production_analysis → build .tracktionedit. Returns fidelity caveats listing what's reproduced exactly versus approximated.

  • compose_from_spec_json(spec | spec_path, out_path) — build an edit from an inline or hand-edited CompositionSpec JSON. Useful when you've inspected an analyzed spec and want to tweak it before building.

  • compose_from_spec(spec, out_path) — internal builder used by all of the above; drives the existing primitives (track_add, plugin_add, midi_clip_add, mix_apply_reference) so output is calibration-aware.

Render (render.py, waveform_workflows.py)

waveform_render_export · waveform_render_to_mp3 (uses bundled ffmpeg + libmp3lame)

Waveform UI control (waveform_workflows.py)

waveform_new_project · waveform_save · waveform_revert_to_saved (the iteration loop unlocker) · waveform_close_active_tab · waveform_active_tab · waveform_project_loaded · waveform_menu_invoke · waveform_add_track · waveform_select_track · waveform_insert_clip_on_track · waveform_build_skeleton

App lifecycle + state-aware launch (waveform_app.py)

waveform_locate · waveform_status · waveform_focus · waveform_quit · waveform_settings_dir

waveform_launch(file_path) is state-aware — it inspects Waveform's UI before deciding what to do:

Detected state

Action

App not running

Cold-start spawn with file as CLI arg

File already loaded as a tab

Focus tab + revert from disk

Modal dialog blocking input

Bail with a clear stage: modal_dialog_blocking error

App on Welcome / Projects / Settings (no edit view)

Auto-switch to an existing edit tab so drop has a target

App on edit timeline, file not loaded

Real OLE drag-drop from Explorer (the only mechanism Waveform's JUCE IDropTarget actually honors for .tracktionedit files in a running instance)

Drag-drop is implemented via explorer.exe /select,<file> to pre-select the file in Explorer, UIA-find its on-screen rect, then SendInput-level mouse drag to Waveform's window — same code path JUCE sees from a real user drag.

Loop library (loops.py)

loop_search (by tempo / bars / name) · loop_drop (auto-length, fit-to-tempo)

Schema capture (schema_capture.py)

schema_snapshot_current_edit · schema_diff_snapshots · schema_list_snapshots

Low-level UI / desktop (win_input.py, desktop.py)

18 primitives for window management, UIA inspection, key/click sending, screenshots.


Layout

waveform-mcp/
├── src/waveform_mcp/
│   ├── server.py                       MCP server entry (stdio)
│   ├── model.py                        Edit / Track / Clip / Note / AutomationLane dataclasses
│   ├── xml_writer.py                   Edit → .tracktionedit
│   ├── xml_reader.py                   .tracktionedit → Edit
│   ├── audio_convert.py                ffmpeg-backed MP3→WAV cache for Sampler sources
│   ├── music_theory_data.py            SCALES, PROGRESSIONS, GENRES, MIX_BALANCE, ...
│   ├── events.py                       event bus + JSONL log
│   ├── diff.py                         Edit-diff for change events
│   ├── mix_calibration.py              Plugin-aware fader calibration (offset table + lookup)
│   ├── composition_spec.py             Pure dataclass schema for analysis-driven composition
│   ├── reference_adapter.py            Analyzer JSON → CompositionSpec (chord / section / timing helpers)
│   ├── poly_transcribe.py              Spotify BasicPitch wrapper — polyphonic audio → MIDI
│   ├── production_analysis.py          RT60 / sidechain detection from rendered audio
│   ├── automation_from_features.py     Audio features → automation breakpoints (pan / cutoff / volume rides)
│   ├── tools/
│   │   ├── edit.py                     Edit lifecycle
│   │   ├── tracks.py                   Tracks + mix balance (calibration-aware)
│   │   ├── midi.py                     MIDI clips/notes
│   │   ├── audio.py                    Audio clip import
│   │   ├── clips.py                    Clip mutators (move, resize, duplicate, set)
│   │   ├── plugins.py                  Plugin add + auto-drum-kit-load + reverb / EQ / comp helpers
│   │   ├── automation.py               Automation lanes (pan + plugin params)
│   │   ├── preset_library.py           Factory preset browser
│   │   ├── loops.py                    Loop library search + drop
│   │   ├── render.py                   Render stubs
│   │   ├── waveform_app.py             App lifecycle + state-aware launch (cold-start / revert / drag-drop)
│   │   ├── waveform_workflows.py       UI workflows (revert, render-to-mp3, etc.)
│   │   ├── desktop.py                  Generic desktop primitives
│   │   ├── win_input.py                Windows UIA + keystroke primitives
│   │   ├── schema_capture.py           Hand-fixture capture for schema reverse-engineering
│   │   ├── music_theory.py             Theory query tools
│   │   ├── composition.py              progression_generate / voice_lead / arrangement_plan
│   │   ├── melody.py                   melody_generate / arp_pattern / bassline_generate / motif_develop
│   │   ├── drum_patterns.py            50+ named drum grooves indexed by (genre, role)
│   │   ├── audio_analysis.py           LUFS / spectrum / compare via ffmpeg
│   │   ├── audio_detect.py             BPM + key detection from audio
│   │   ├── reference_songs.py          Curated reference song database
│   │   ├── workflow.py                 Master chain templates / snapshots / variations / chord chart export
│   │   ├── midi_export.py              .mid export
│   │   ├── composer.py                 compose_lofi_track, compose_synthwave_track, compose_djent_*, compose_rainstorm
│   │   ├── lofi_generative.py          compose_lofi_generative (every seed = different song)
│   │   ├── compose_from_spec.py        Generic spec → .tracktionedit builder
│   │   ├── import_song.py              analyze_to_spec / compose_from_reference / compose_from_spec_json
│   │   └── common.py                   @op decorator (apply + diff + event)
│   └── preview/
│       ├── app.py                      FastAPI + websocket
│       └── static/                     HTML / JS piano-roll
├── tests/                              250 tests covering primitives, composers, calibration, adapter, builder, audit regressions
├── scripts/
│   ├── lofi_from_ecstasy.py            Demo: build a lofi track inspired by another song's chord palette
│   └── run_analyzer.py                 Run music-analyzer on a rendered file + measure stems
├── docs/
│   ├── img/synthwave_arrangement.png
│   ├── ARCHITECTURE.md
│   ├── EVENT_SCHEMA.md
│   └── EDIT_MODEL.md
├── pyproject.toml
└── README.md

The iteration loop that actually works

After many false starts, here's the loop that lets the LLM and the user collaborate on a track without restarting Waveform every cycle:

1. Compose / mutate            → composer.compose_*  or clip_set / mix_apply_reference
2. Save to disk                → edit_save / flush  (writes .tracktionedit)
3. Reload in Waveform          → waveform_revert_to_saved
                                  (File → Revert to saved state, auto-confirms popup)
4. User listens                → "turn the arp up"
5. Update MIX_BALANCE or run a clip mutator
6. → goto 2

The killer move was discovering Waveform's File → Revert to saved state menu item: it forces the open Edit to reload from disk, which is what makes external mutation visible without closing/reopening the project. waveform_revert_to_saved automates that path with retry.


Mix balance reference

mix_apply_reference reads from a curated table of "kick is the anchor; bass 5-6 dB under; lead similar to bass; pad/arp 6-9 dB under lead; ambience deepest" — distilled across genre tutorials, mastering blogs, and tuned-by-ear iterations:

MIX_BALANCE["synthwave"] = {
    "drums": -7, "kick": -6, "snare": -10, "hat": -16,
    "bass": -25, "sub_bass": -28,           # background-level texture
    "lead": -15, "pad": -19, "arp": -8,     # arp-driven mix
    "counter": -14, ...
}

Composers call tracks.mix_apply_reference({track_id, genre, role}) once per track. Tweak the table once, every composer rebalances.

Sources informing the table:


Plugin-aware fader calibration

MIX_BALANCE[genre][role] says "rhythm guitar at -8 dB" — but a sample-based drum kit at unity fader is roughly 16 dB louder than a sforzando-driven SFZ at unity fader. So setting drums to -5 dB and rhythm guitar to -8 dB on the literal fader produces a mix where drums are about 13 dB too loud, not 3.

mix_calibration.py solves this. Every plugin chain has a known output-offset relative to a "sample-based drum sampler at unity = 0 dB" reference:

PLUGIN_CHAIN_OFFSETS_DB = {
    ("sampler",):                     0.0,    # reference
    ("4osc",):                       -8.0,
    ("sforzando",):                 -16.0,    # bare sforzando + typical SFZ
    ("sforzando", "anvil", "mt-a", "tsc 1.1"):  0.0,    # full guitar chain ≈ unity
    ("808 kit",):                     0.0,    # named alias for sampler
    ("lofi bass",):                   0.0,    # multi-sample bass guitar
    ...
}

The table is bulk-seeded at registry-build time — every preset known to the instrument registry (94 entries: Salamander Grand variants, all VSCO instruments, all 4OSC presets) gets a sensible default chain offset so the calibrator stops emitting "no match → -10 dB default" warnings. Manual entries in the table take precedence; bulk-seeding only fills gaps. New presets equipped at runtime auto-register too.

mix_apply_reference(track_id, genre, role) now does:

fader_db = role_target_db − chain_offset_db(track.plugins) + extra_offset_db

So "rhythm guitar at -8 dB" lands at the correct audible -8 dB regardless of whether the chain is a single 4OSC patch or sforzando→Anvil→MT-A→TSC — the fader value gets compensated automatically.

Robustness: lookups never raise. Unknown chains fall back to a conservative -10 dB offset and surface a chain_offset_db: ... → no match warning in logs, so any composer that ships a new plugin chain immediately knows to register an offset. Final fader values are clamped to [-32, +8] dB. Identical inputs always produce identical outputs.


Reference-driven composition

Point at any audio file and the MCP can produce a new .tracktionedit modeled on it — using its tempo, key, structure, chord progression, and (optionally) transcribed melodic content.

audio file → music-analyzer → analyzer JSON → reference_adapter
        → CompositionSpec → compose_from_spec → .tracktionedit

Phase A — what's reproduced exactly

  • Tempo + key + time-sig — pulled directly from the analyzer

  • Section timeline — intro / verse / chorus / breakdown / etc. with per-section density and brightness, snapped to bar boundaries so drum loops align with section starts

  • Chord progression — every detected chord placed beat-aligned on the chord track. Bass roots auto-follow.

  • Form fidelity — overlapping or out-of-order sections from the analyzer get resolved to a clean monotonic timeline

  • Plugin-aware mix calibration at the end so the cover comes out balanced

Phase B — fidelity upgrades (opt-in, all gracefully degrade if deps absent)

  • poly_transcribe.py (Spotify BasicPitch) — when installed, transcribes the audio into MIDI notes (any polyphony) and routes them to the right tracks by pitch range (bass / lead / keys). Notes are quantized to a 16th-note grid and same-pitch micro-collisions are merged.

  • production_analysis.py — Schroeder-integration RT60 estimation per stem (auto-applies to plate reverb plugin params), sidechain-pump detection (envelope correlation against drum onsets, applies to a kick-triggered ducking compressor).

  • automation_from_features.py — pan-from-stereo-balance time-series, volume rides from RMS envelope, filter cutoff curves from spectral centroid trajectory.

What it doesn't reproduce

  • Vocals as vocals — no synth-vocal layer. Vocal lines fall through to the Lead track as a synth, or are skipped.

  • Exact polyphonic notes — BasicPitch is the SOTA open transcriber but still ~70-90% F1 on dense mixes; covers will be in the same key with similar shape, not bit-exact.

  • Production tone exactly — first-order match via plugin chain selection + reverb / sidechain heuristics, not a tone clone.

Public tools

analyze_to_spec({"audio_path": "...wav"})
# → {"ok": True, "spec": {...}, "summary": {tempo, key, sections, ...},
#    "fidelity_caveats": [...], "transcription": {used, note_count, ...}}

compose_from_reference({"audio_path": "...wav", "out_path": "...tracktionedit"})
# Full pipeline: analyze + adapt + transcribe + build + save

compose_from_spec_json({"spec": {...}, "out_path": "...tracktionedit"})
# Build from a hand-edited or saved CompositionSpec

To get the Phase B upgrades:

<music-analyzer-venv>\Scripts\pip install basic-pitch onnxruntime

The MCP detects installed deps and uses them when available; without them the pipeline still produces a structurally-faithful cover, just with chord-tone placeholders for the lead/melody instead of real transcribed notes.


Known limitations

  • Track-volume AUTOMATIONCURVE is disabled. Waveform's volume plugin doesn't honor our curve schema and silences the affected track. The MCP refuses the target with a clear error and points to working alternatives (clip fades, multiple clips with per-clip gain, static mix_set).

  • Plugin modifier matrix is exploratory. plugin_add_modifier writes a generic modmatrix shape; needs a hand-edited fixture to confirm the per-plugin schema before LFO modulation works reliably for 4OSC and friends.

  • Headless render not built yet. waveform_render_to_mp3 drives Waveform's UI export — works, but requires Waveform to be running. A C++ helper linking tracktion_engine is the eventual fix.

  • Linux/macOS UI control absent. Content discovery (presets, loop library, VST list) is OS-aware; UI automation is Windows-only.

  • Reference covers don't reproduce vocals as vocals. No singing-synth integration. Vocal melody lines either fall through to a synth Lead or are skipped; lyrics module captures text but it's not played back.

  • Poly transcription is approximate. BasicPitch sits at ~70-90% F1 on clean material, lower on dense metal mixes. Covers will be in the same key with similar shape, not the exact notes.

Solved (kept here for context — these used to be limitations)

  • Drum sampler silent in spec-driven composers — plugin_add now auto-populates a Sampler with the factory 808 kit when added to a track named Drums / Kit / Percussion. Single fix benefits every composer.

  • Drag-drop file open into running Waveform — works via real OLE drag-drop from Explorer (waveform_launch triggers it automatically when the file isn't already loaded). Used to be impossible because Waveform's single-instance handler eats CLI-arg-based forwarding.

  • State-aware launch flow — waveform_launch no longer drags files into the wrong view; it auto-detects modal dialogs, the active tab, and the active view, picks the right action.

  • Plugin-name-mismatch silently breaking calibration — calibration table now covers all named-kit aliases (808 Kit, Lofi Bass, Counter Synth, etc.) so composer-named plugins resolve correctly.


Building blocks for next iterations

  • Capture a real Waveform fixture for <AUTOMATIONCURVE paramID="volume"> so volume automation can be re-enabled

  • C++ headless render helper on tracktion_engine

  • Drum Sampler / Micro Drum Sampler real fixture (currently fall back to plain Sampler)

  • Sidechain modifier capture

  • Clip Launcher (v13) support

  • Linux UI control via xdotool/wmctrl once UIA is no longer the only path

  • Singing-synth integration (Sinsy / SynthV Lite) so compose_from_reference can reproduce vocal melodies

  • Voice leading between Keys and Melody so they don't clash on shared scale degrees

  • Drum-pattern variation per section (fills on section boundaries, ghost-note humanization)

  • Real Jazz Kit sample pack (current _jazz_kit_sounds() remaps 808 Kit samples — fine but limited)

  • Per-stem loudness analysis driven by htdemucs (currently only band-energy %; can't tell "is bass quiet, or just in the wrong frequencies")

  • audition_preset(preset_name, chord) quick-listen tool — one-click preview before committing to a song

  • compose_with_iteration(genre, target_seconds, max_iter) — auto-render, auto-analyze, auto-tune mix offsets toward genre targets in a loop


License

GPL-3.0-or-later (matches tracktion_engine if/when the C++ render helper links to it).

Available Tools

129 tools
arp_patternB

Generate an arpeggio over a chord. Returns notes for the given length. direction ∈ {up, down, up_down, down_up, random, octave_alternate}. rate ∈ {quarter, eighth, sixteenth, triplet}. octaves controls how many octaves the cycle spans.

Inputs: length_beats (required), chord_pitches (required), rate, octaves, +4 more.

arrangement_planA

Produce a section list for a song. genre selects characteristic form; target_length_seconds + tempo set total bars; energy_curve shapes the intensity pattern. Returns sections with bar counts, roles, and an energy 0..1 the composer can use to gate layers / set velocities.

Inputs: genre (required), tempo (required), target_length_seconds (required), energy_curve, +2 more.

audio_clip_importA

Place an audio file as a clip on a track. warp_mode controls time-stretch behavior. Optional clip-level gain (dB), fade in/out (beats), and offset_in_source (beats — skip the first N beats of the source file, useful for trimming silent intros).

Inputs: track_id (required), file_path (required), start_beats (required), length_beats (required), +6 more.

audio_compareA

Compare two audio files: returns LUFS / peak / DR diffs and a per-band spectrum diff in dB. Positive numbers mean file_a is louder/brighter than file_b in that dimension.

Inputs: file_a (required), file_b (required), bands.

audio_detect_keyA

Detect the musical key of an audio file. Returns root note + mode (major/minor) with confidence 0..1. Uses chromagram + Krumhansl-Schmuckler key profiles. Reliable on tonal pop/jazz; less reliable on percussion-heavy or atonal material.

Inputs: file_path (required).

audio_detect_tempoA

Detect the BPM of an audio file via onset autocorrelation. bpm_range bounds the search (default 60..180). Returns a single BPM with confidence 0..1. Works well on EDM/pop loops with a kick on every beat; less reliable on free-tempo or sparse music.

Inputs: file_path (required), bpm_max, bpm_min.

audio_loudness_lufsA

Measure integrated LUFS, true peak (dBTP), and dynamic range (LRA) for an audio file using ffmpeg's loudnorm filter. Reference targets: Spotify -14 LUFS, Apple -16, broadcast -23. True peak should stay under -1.0 dBTP.

Inputs: file_path (required).

audio_spectrumA

Return RMS energy per frequency band. bands defaults to a 10-band log-spaced layout (sub, low, low-mid, mid, hi-mid, presence, brilliance, air). Useful for 'compared to my reference, this song is 4 dB heavier in the low-mid region.'

Inputs: file_path (required), bands.

automation_addA

Add an automation lane to a track. target ∈ {'volume', 'pan', 'plugin/<plugin_id>/'}. points = [{beat, value, curve?}]. Replaces any existing lane on the same target.

Inputs: points (required), target (required), track_id (required).

automation_clearA

Remove all automation lanes from a track, or just the lane on a given target.

Inputs: track_id (required), target.

automation_envelopeA

Convenience: ramp a target from start_value to end_value between start_beat and end_beat. shape ∈ {'linear', 'exp_in', 'exp_out', 'sine'}. exp_in starts slow then speeds up; exp_out is the inverse; sine is a smooth S-curve. Appends to an existing lane on the same target if one exists, otherwise creates a new lane.

Inputs: target (required), end_beat (required), track_id (required), end_value (required), +4 more.

automation_listA

List automation lanes on a track (or all tracks if track_id omitted).

Inputs: track_id.

aux_return_addA

Add an auxreturn plugin to a track so it receives audio from the given bus number (0..15). Pair with send_add(bus_num=N) on source tracks for parallel routing — e.g. drum tracks all send to bus 0, aux-return track applies a single reverb.

Inputs: track_id (required), bus_num.

bassline_generateB

Generate a bassline under a chord progression. feel ∈ {pump, walking, half_time, sub, octave_jumps, dub}. Each chord lasts chord_duration_beats. Roots are the chord-root MIDI pitches (typically octave 2-3).

Inputs: roots (required), chord_duration_beats (required), feel, start_beats, +1 more.

bus_createC

Create a submix / aux track other tracks can route to. kind ∈ {submix, folder}.

Inputs: name (required), kind.

bus_routeA

Route a track's output to a bus instead of master. Stack drums onto a 'Drum Bus' for one comp/EQ.

Inputs: dest_bus_id (required), src_track_id (required).

clip_duplicateA

Clone a clip onto the same track at start_beats + offset_beats. Returns the new clip_id. Notes are deep-copied for MIDI clips.

Inputs: clip_id (required), offset_beats (required).

clip_listA

List clips on a track (or all tracks). Returns id, kind, name, start, length, plus per-clip gain/fade/offset for audio clips.

Inputs: track_id.

clip_moveA

Shorthand: change a clip's start_beats. Length unchanged.

Inputs: clip_id (required), new_start_beats (required).

clip_removeB

Delete a clip by id.

Inputs: clip_id (required).

clip_resizeA

Change a clip's length. anchor='start' (default) keeps start_beats fixed and moves the right edge. anchor='end' keeps the right edge fixed and moves start_beats so total duration changes from the left.

Inputs: clip_id (required), new_length_beats (required), anchor.

clip_setB

Modify any field on an existing clip. Pass only fields you want changed. For AudioClip: name, start_beats, length_beats, gain_db, fade_in_beats, fade_out_beats, offset_in_source_beats, warp_mode. For MidiClip: name, start_beats, length_beats, gain_db.

Inputs: clip_id (required), name, gain_db, warp_mode, +5 more.

compose_and_reloadB

Dispatch one of jazz/synthwave/lofi generative composers AND revert in Waveform with one call. Avoids the compose-then-manually-revert dance during iteration.

Inputs: composer (required), out_path (required), seed, tempo, +3 more.

compose_djent_realA

Djent track using REAL VST3 instruments + amp/cab simulators (sforzando + Karoryfer Emilyguitar SFZ → The Anvil drive → Mercuriall Metal Area amp → TSC cab) instead of synthetic 4OSC. Returns a manual_step_required note explaining you must load the SFZ in sforzando once after opening the project — sforzando state isn't preserved in our .tracktionedit writer yet.

Inputs: out_path (required), seed, tempo.

compose_djent_trackB

Generate a progressive metal / djent instrumental in B minor. Form: intro → riffA → riffB → breakdown → riffA2 → buildup → solo (8 bars) → riffFinal → outro. Tracks: drums (polyrhythmic 3-3-2 kicks), bass, dual rhythm guitars panned hard L/R for the wall, lead solo over solo section, atmospheric pad. ~140 seconds at 110 BPM.

Inputs: out_path (required), seed, tempo.

compose_from_primitivesB

Build a song bottom-up from composition primitives — the recipe demonstrating arrangement_plan + progression_generate + voice_lead + drum_pattern + bassline_generate + arp_pattern + melody_generate + motif_develop wired together. Genre selects drum patterns, bass feels, and mix-balance reference. energy_curve drives velocity scaling per section.

Inputs: out_path (required), mode, root, seed, +4 more.

compose_jazz_generativeA

Generate a jazz/jaunty track with authentic walking bass (7-pattern Paul Chambers vocabulary), Charleston-rhythm Rhodes comping, and Salamander Grand Piano lead treated like a vocal (call/response motifs, vocal range, dynamic phrasing). Drums comp for the soloist (ghost rim shots on piano off-beats, phrase-end fills, sparse ride on solo phrases). Section-wide ensemble dynamics and per-section bass feels (two-feel for heads, walking for blowing, anticipations into the bridge).

Inputs: no named parameters.

compose_lofi_generativeA

Generate a UNIQUE lofi track on every call. Unlike the old compose_lofi_track which hardcoded everything, this one uses progression_generate, arrangement_plan, drum_pattern, melody_generate, and bassline_generate primitives. Each seed produces a meaningfully different song: different chords, section layout, drum density, melody contour, bass feel, and tempo (within lofi range).

Inputs: no named parameters.

compose_lofi_trackB

Generate a complete lofi track .tracktionedit file: 4 tracks (drums, bass, keys, pad), Fmaj7-Am7-Dm7-Cmaj7 progression, real 808 drum samples, master FX bus. Bass / keys / pad presets are configurable so you can A/B sounds without editing the composer. Saves to the given path.

Inputs: out_path (required), bars, seed, tempo, +4 more.

compose_rainstormA

Generate a rainstorm ambient .tracktionedit: continuous rain bed, tiled wind/breeze layer, occasional distant-muffled thunder rumbles (lowpass + EQ + low gain). 60 BPM (1 beat = 1 second). No music.

Inputs: out_path (required), seed, length_seconds.

compose_synthwave_generativeA

Generate a UNIQUE synthwave track on every call. Drives progression_generate / arrangement_plan / drum_pattern / melody_generate primitives with seed-driven variation across tempo (100-128 BPM), key, form, chord progression, drum density, melody contour, and bass feel. Different seeds = meaningfully different songs.

Inputs: no named parameters.

compose_synthwave_trackC

Generate a 64-bar synthwave instrumental .tracktionedit at 110 BPM in A minor. Properly developed song form: intro → verseA → chorus → verseB (variation) → chorus2 (with counter) → bridge (new chord set + walking bass + descending arp) → chorusFinal (peak) → outro (progressive instrument drop with pad chord ring-out). Per-section chord progressions, bass feels (verse=half-time, chorus=8th pump, bridge=walking), and arp themes (verse / verseB syncopated / chorus pump / bridge descending). Showcases the full MCP toolset.

Inputs: out_path (required), seed, tempo.

compose_variationsA

Run a composer N times with different seeds, saving each output to a numbered file. Returns the list of paths so you can render all and pick the best.

Inputs: count (required), composer_name (required), out_path_prefix (required), extra_args.

compress_glueB

Gentle bus 'glue' compressor. amount 0..1 (0=barely-there, 1=noticeable).

Inputs: track_id (required), amount.

compress_smashC

Aggressive parallel-style compressor for drum-bus / NY-style smash. No params — preset values.

Inputs: track_id (required).

desktop_clickA

Click at screen coordinates. Use desktop_screenshot first to know what's where. button ∈ {left, right, middle}.

Inputs: x (required), y (required), button, clicks.

desktop_keyA

Press a single key or chord. Examples: 'enter', 'esc', 'ctrl+s', 'ctrl+shift+n', 'f5'. Sent to whatever window currently has focus.

Inputs: keys (required), presses, interval.

desktop_moveA

Move the mouse to (x, y) without clicking.

Inputs: x (required), y (required), duration.

desktop_screenshotA

Capture the current screen (or a region) to a PNG file under state/screenshots/. Returns the path. Use this to verify that a UI action had the expected effect before sending the next.

Inputs: name, region.

desktop_screen_sizeA

Return current screen resolution.

Inputs: no named parameters.

desktop_typeC

Type a string of text into the focused window.

Inputs: text (required), interval.

desktop_waitA

Sleep for seconds seconds. Use sparingly; prefer screenshot-then-act.

Inputs: seconds (required).

drum_patternA

Generate drum notes for a section. (genre, role) selects from a library of 50+ named patterns. role ∈ {verse, chorus, fill, intro, breakdown, outro, buildup}. density 0..1 scales note count (0.5 drops half the hats; 1.0 is the full pattern). length_bars tiles the pattern. Returns notes ready to drop on a Sampler track.

Inputs: role (required), genre (required), length_bars (required), seed, +4 more.

drum_pattern_listA

List all available (genre, role) patterns in the library.

Inputs: no named parameters.

edit_createA

Create a new empty Edit (song). Sets tempo, time signature, and key. Replaces the in-memory Edit; does not write to disk until edit_save or flush.

Inputs: name, tempo, key_mode, key_root, +3 more.

edit_export_chord_chartA

Export a chord-chart text file from the markers + tempo of the current Edit. format ∈ {lead_sheet, nashville}. Markers should be named with section labels (e.g. 'verse', 'chorus').

Inputs: out_path (required), format.

edit_export_midiB

Export all MIDI clips in the current Edit to a Standard MIDI File (.mid). One MIDI track per Edit MIDI track. Format-1 SMF with the Edit's tempo + time signature in track 0. Drop into any DAW for editing/printing/learning.

Inputs: out_path (required).

edit_inspectA

Detailed structural dump of the current Edit. Heavier than edit_summary — includes every plugin's params, clip gain/fade/offset, automation lane counts, master chain. Use after edit_open to understand a song before mutating it.

Inputs: no named parameters.

edit_openB

Load an existing .tracktionedit file from disk into the model.

Inputs: path (required).

edit_saveA

Serialize the current Edit to a .tracktionedit file. Sets the flush target.

Inputs: path (required).

edit_save_stemA

Save a stem-mix copy of the current Edit: only named tracks audible, all others muted. Pass track_ids or track_names (list). Use in a loop to produce per-track stems for rendering.

Inputs: out_path (required), track_ids, track_names.

edit_set_tempoB

Change the project tempo. preserve_pitch is informational (audio clips don't get pitch-shifted by this tool).

Inputs: bpm (required), preserve_pitch.

edit_summaryA

Return a JSON summary of the current Edit (tracks, clips, markers, tempo).

Inputs: no named parameters.

edit_transposeA

Transpose every MIDI note in the Edit by semitones. Audio clips are NOT pitch-shifted.

Inputs: semitones (required).

ensemble_dynamicsA

Return per-section velocity multipliers for a sections_plan. Apply uniformly to drums, bass, keys, lead notes so the whole ensemble breathes together (quiet intro, building verses, peak chorus/bridge, soft outro).

Inputs: no named parameters.

eq_high_passA

Add a high-pass filter at freq Hz to a track. slope is dB/oct (12, 18, 24).

Inputs: track_id (required), freq, slope.

eq_low_passA

Add a lowpass filter at freq Hz to a track for darkening/muffling.

Inputs: track_id (required), q, freq.

eq_tiltB

Single-knob bright/dark EQ. brightness -1=dark, 0=neutral, +1=bright.

Inputs: track_id (required), brightness.

flushA

Write the current Edit to the previously-opened/saved .tracktionedit path. Use this between batches of mutations so Waveform (when open) sees a single, coherent update instead of mid-edit flicker.

Inputs: no named parameters.

key_setD

Set the project key.

Inputs: mode (required), root (required).

loop_dropA

Drop a loop onto a track. mode='fit' time-stretches to song tempo, 'original' uses natural tempo. start_beats defaults to end of existing clips on that track. Auto-detects file duration.

Inputs: track_id (required), loop_path (required), mode, start_beats.

marker_addB

Drop a marker (verse, chorus, drop, etc.) at a beat position.

Inputs: name (required), start_beats (required), length_beats.

master_chain_applyA

Replace the master chain with a named template. template ∈ {clean_pop, loud_edm, lofi_warm, cinematic_dynamic, podcast_voice, no_processing}. Each is an EQ + Comp + Limiter chain tuned for its target use case.

Inputs: template (required).

melody_generateB

Generate a melody from a scale and contour. Returns notes (pitch/start_beats/length_beats/velocity) ready to drop into a MIDI clip. contour ∈ {arch, descending, ascending, wave, question_answer, static}. density ∈ {sparse, medium, busy}. Notes are rhythmically diverse — quarter, eighth, dotted, with rests — humanized for natural feel.

Inputs: mode (required), root (required), length_beats (required), seed, +3 more.

midi_clip_addC

Create an empty MIDI clip on a track.

Inputs: track_id (required), start_beats (required), length_beats (required), name.

midi_clip_quantizeA

Snap note start times in a clip to the nearest grid division. Grid is in beats: 0.25 = 16th notes, 0.5 = 8ths, etc. Strength 0..1.

Inputs: clip_id (required), grid_beats (required), strength.

midi_notes_addB

Add notes to an existing MIDI clip. Pass an array of {pitch, start_beats, length_beats, velocity?, colour?}. Pitch is MIDI 0-127. Channel is per-clip, set when the clip is created (not per-note).

Inputs: notes (required), clip_id (required).

midi_notes_clearB

Remove all notes from a MIDI clip.

Inputs: clip_id (required).

mix_apply_referenceB

Set a track's volume_db (and optional pan) to the genre+role reference from the MIX_BALANCE table. Use this once per track after creating it instead of hand-tuning levels. Genres: synthwave, lofi, edm, pop, hip_hop, cinematic, ambient. Roles: drums, kick, snare, hat, bass, sub_bass, lead, pad, keys, arp, counter, melody, ambience, fx. Returns the dB value applied.

Inputs: role (required), genre (required), track_id (required), pan, +1 more.

mix_match_referenceA

Compare a rendered file against a genre's reference spectrum and return a per-band diff in dB plus a plain-English diagnosis ('low end is +4 dB hot vs synthwave reference; consider HP filter on bass'). Use after render to inform mix adjustments.

Inputs: genre (required), file_path (required).

mix_setA

Set track volume (dB), pan (-1..1), mute, solo. Pass only the fields to change.

Inputs: track_id (required), pan, mute, solo, +1 more.

motif_developB

Transform a motif (set of notes) into a variation. transformation ∈ {transpose, invert, retrograde, augment, diminish, sequence_up, sequence_down}. transpose: shift by semitones. invert: mirror around axis_pitch. retrograde: reverse note order. augment: stretch durations by factor. sequence_up/down: transpose by semitones AND repeat.

Inputs: notes (required), transformation (required), factor, semitones, +2 more.

narrateA

Tell the user (via the event stream / preview UI) what the agent is about to do, in plain English. Call this before a batch of mutations so the user can follow along.

Inputs: text (required).

plugin_addA

Add a plugin to a track's chain. For built-ins, pass the type name (e.g. '4OSC', 'Wavetable'). For external VSTs, also pass preset_path.

Inputs: type (required), track_id (required), name, preset_path.

plugin_add_drum_kitA

Add a Sampler-backed drum kit to a track. pads is a list of {midi_note, sample_path, name?, gain_db?, pan?, decay?}. Each pad becomes one SOUND mapped to a single MIDI note. Replaces the manual Sampler+sounds boilerplate in compose_lofi_track.

Inputs: pads (required), track_id (required), name.

plugin_add_macroA

Add a MacroParameter (single 0..1 knob) to a plugin. Macros are the canonical Tracktion way to expose a single control that drives multiple underlying params via assignments.

Inputs: name (required), track_id (required), plugin_id (required), value, +1 more.

plugin_add_modifierA

Add a TRACK-LEVEL modifier (LFO/envelope/random/step_lfo) and assign it to a plugin parameter. Real Tracktion schema — wires both on the track AND on the plugin. depth: 0..1 (modulation amount). rate: Hz when rate_type=0, beat multiplier when rate_type=1 (default = beat-synced). shape ∈ {sine, triangle, square, saw}.

Inputs: track_id (required), target_param (required), target_plugin_id (required), rate, +5 more.

plugin_add_reverbA

Add a built-in reverb to a track. type ∈ {plate, natural, non_linear}. All other params are normalized 0..1: size, decay, mix (wet amount), pre_delay, diffusion, definition, high_damp, low_damp, high_cut. Defaults to a tasteful 25% wet plate.

Inputs: track_id (required), mix, name, size, +8 more.

plugin_discoverA

List third-party VST/AU plugins Waveform has scanned. Optional filters: kind ('instrument'|'effect'), format ('VST3'|'VST'|'AU'), manufacturer (substring), name (substring). Use the returned uniqueId as the type arg to plugin_add for VST3 instantiation.

Inputs: kind, name, format, manufacturer.

plugin_listA

Return the curated list of built-in instruments and effects available by name.

Inputs: no named parameters.

plugin_removeB

Remove a plugin from a track's chain by id.

Inputs: track_id (required), plugin_id (required).

plugin_set_paramC

Set a named parameter on a plugin in a track's chain.

Inputs: param (required), value (required), track_id (required), plugin_id (required).

plugin_set_vst_stateA

Apply a base64 VST state blob (from plugin_state_capture) to an existing plugin in the in-memory Edit. Lets you copy one configured VST onto every other instance.

Inputs: state (required), track_id (required), plugin_id (required).

plugin_state_captureA

Read a saved .tracktionedit file and extract a VST plugin's base64 state blob by plugin_name (substring match) or unique_id. Used to copy a manually-loaded plugin's configuration (e.g. an SFZ loaded into sforzando) for re-use programmatically.

Inputs: unique_id, plugin_name, edit_file_path.

progression_generateA

Invent a chord progression in a key and mode. Returns a list of chord dicts ({roman, root_note, root_midi, chord_pitches, quality}). Constraints: end_cadence ∈ {authentic, plagal, deceptive, half}; allow_borrowed enables modal interchange (bIII/bVI/bVII in major, V/VII for minor); style ∈ {pop, jazz, modal, synthwave, sad, uplifting} biases the chord palette.

Inputs: mode (required), root (required), length_chords (required), seed, +4 more.

reference_song_listA

List all known reference songs in the curated database.

Inputs: no named parameters.

reference_song_lookupA

Look up musical hints for a song. Pass name (one of the keys in REFERENCE_SONGS) OR genre to list all references in that genre. Returns BPM, key, form, energy curve, signatures the composer can use as inputs.

Inputs: name, genre.

reference_spectrum_lookupA

Get the target spectral profile (RMS dB per log-spaced band) for a genre — a mastering-engineer-cheat-sheet target for what a well-mixed track in this genre should look like spectrally. Bands: 60 / 133 / 296 / 657 / 1460 / 3243 / 7204 / 16000 Hz.

Inputs: genre (required).

renderA

Render the current Edit to a WAV file. NOT YET IMPLEMENTED — requires the wf-engine-helper binary which is not built. Use this tool to check status; it will return ok=False with a clear message so the agent can fall back to telling the user to render from inside Waveform.

Inputs: out_path (required), bit_depth, sample_rate.

render_and_auditC

Render the active edit to MP3, run the analyzer, and return both paths plus a balance/rhythm summary in one call. Replaces the manual revert→render→analyze chain.

Inputs: bitrate, mp3_path, revert_first, trim_trailing_silence.

render_stemsB

Render per-track stems. NOT YET IMPLEMENTED.

Inputs: out_dir (required).

send_addA

Add an aux send (parallel routing) on a track. Tracktion routes via numbered buses 0..15 — pass bus_num explicitly, or pass dest_track_id to auto-pick a bus. Pair with aux_return_add on the receiving track. Emits a real element with proper slider position from amount_db.

Inputs: src_track_id (required), bus_num, pre_post, amount_db, +1 more.

snapshot_listA

List all saved snapshots in this session.

Inputs: no named parameters.

snapshot_recallC

Restore a previously-saved snapshot as the active Edit.

Inputs: name (required).

snapshot_saveA

Save the current Edit as a named snapshot for later A/B compare or recall.

Inputs: name (required).

step_clip_addB

Add a step clip (drum-machine grid) to a track. channels is a list of {midi_note, name?, velocity?} dicts — one per drum row. patterns is a list of pattern dicts: each pattern has rows parallel to channels, where each row has a pattern string of '0'/'1' steps and optional velocities list. step_length is duration of each step in beats (0.25 = 16th notes). Way cleaner than 80 individual MidiClip notes.

Inputs: channels (required), patterns (required), track_id (required), start_beats (required), +4 more.

tempo_setA

Set the project tempo in BPM (currently single global tempo; per-bar curves later).

Inputs: bpm (required).

theory_arrangement_layersA

Return arrangement layers: foundation, harmonic_glue, lead, counter_melody, atmosphere, ear_candy. Optional section returns per-section presence values for each layer (0..1) from LAYER_PRESENCE_BY_SECTION — drives the 'which layers should be active' decision.

Inputs: section.

theory_borrowed_chordsA

Return the borrowed-chord (modal interchange) palette for major keys with usage notes.

Inputs: no named parameters.

theory_cadencesB

Return cadence types — pattern, feel, and use. Optional name for one specific cadence.

Inputs: name.

theory_chord_progressionA

Look up a named chord progression. Returns the Roman-numeral sequence + tags + examples. Without args, lists all 24 named progressions.

Inputs: tag, name.

theory_diatonic_chordsB

Return the 7 diatonic chords for a key/mode. Set sevenths=true for 7th-chord qualities (Imaj7, ii7, etc.) instead of triads (I, ii, etc.). Returns Roman numerals + the actual chord pitches (MIDI) for each.

Inputs: mode (required), root (required), octave, sevenths.

theory_genreA

Return genre conventions: BPM range, time signature, key tendency, instruments, form, hallmark progressions. Without args, lists all 22 genres.

Inputs: name.

theory_heuristicsA

Return the 13 songwriting heuristics (rule_of_3, contrast_required, etc.).

Inputs: no named parameters.

theory_mix_balanceA

Return reference track-volume dB targets for a genre. Without args, returns the full table of (genre, role) → dB. With genre, returns the role→dB map for that genre. With genre AND role, returns the single dB value with a brief rationale. Roles: drums, kick, snare, hat, bass, sub_bass, lead, pad, keys, arp, counter, melody, ambience, fx. Genres: synthwave, lofi, edm, pop, hip_hop, cinematic, ambient.

Inputs: role, genre.

theory_modesB

List the 7 church modes with their characteristic degree, vibe, and typical use.

Inputs: mode.

theory_rhythmA

Return rhythm/meter info: time signatures, syncopation patterns, swing ratios. Pass time_sig for one specific time signature, syncopation for one pattern, or neither for the full reference.

Inputs: time_sig, syncopation.

theory_scaleA

Return the notes of a scale starting at a root. Returns MIDI numbers, note names, and interval pattern. mode is one of: major, natural_minor, harmonic_minor, melodic_minor_asc, ionian, dorian, phrygian, lydian, mixolydian, aeolian, locrian, major_pentatonic, minor_pentatonic, blues, whole_tone, half_whole_dim, whole_half_dim, chromatic, hungarian_minor, phrygian_dominant.

Inputs: mode (required), root (required), octave.

theory_sectionA

Return song section info: typical bar counts, role, dynamic level, valid next sections. Without args, lists all 15 section types (intro, verse, chorus, bridge, drop, etc.).

Inputs: name.

theory_song_formA

Return a macro song form layout. Without args, lists all 14 forms. With name, returns the section sequence, applicable genres, and total bar range.

Inputs: name.

theory_surprise_devicesC

Return the 7 surprise devices (truck_driver_modulation, deceptive_cadence, etc.).

Inputs: no named parameters.

theory_velocityA

Convert dynamic markings (ppp/pp/p/mp/mf/f/ff/fff) to MIDI velocity values, or look up all of them. Includes ACCENT_BUMP and GHOST_NOTE_MAX_VEL constants.

Inputs: dynamic.

theory_voice_leading_rulesA

Return the 10 codified voice-leading rules + scoring guidance.

Inputs: no named parameters.

tool_historyA

Return the last N tool calls made in this session for self-audit.

Inputs: last_n.

track_addB

Add a new track. kind ∈ {audio, midi, folder, submix, automation}.

Inputs: name (required), pan, kind, volume_db.

track_removeB

Delete a track by id.

Inputs: track_id (required).

undoB

Roll back the most recent mutation.

Inputs: no named parameters.

voice_leadA

Voice the next chord with minimal voice motion from the previous voicing. Standard SATB-style voice leading: each voice moves the smallest interval to a target chord tone. Returns the new voicing as a list of MIDI pitches (same length as prev_voicing).

Inputs: next_chord (required), prev_voicing (required).

waveform_focusA

Bring Waveform's main window to the foreground. Required before desktop_key / desktop_type send keystrokes to the app.

Inputs: no named parameters.

waveform_launchA

Launch Waveform, optionally opening a .tracktionedit file. If Waveform is already running and a file path is provided, opens the file in the existing instance via OS file association (Waveform registers itself as the handler for .tracktionedit on install).

Inputs: exe_path, file_path.

waveform_locateA

Find the installed Waveform binary on disk. Returns the path. Searches running processes first, then well-known install locations. Run this before launch/focus tools so the rest of the chain has a path to use.

Inputs: no named parameters.

waveform_plugin_typesA

Return the friendly-name → Tracktion-type-code map for built-in plugins. These codes are what the .tracktionedit XML expects in .

Inputs: no named parameters.

waveform_preset_listA

List available factory presets. Without args, returns the plugin category folders (4OSC, Compressor, etc.). With category, lists individual .trkpreset files in that category.

Inputs: category.

waveform_preset_readA

Read a .trkpreset XML and return its element contents — the type, parameters, and any nested elements. Use this to grab a known-good instrument or effect configuration to drop into a generated edit.

Inputs: path (required).

waveform_quitA

Politely close Waveform (sends WM_CLOSE to the main window).

Inputs: no named parameters.

waveform_settings_dirA

Return the path to Waveform's settings directory (where Waveform.settings, knownPluginList64.settings, Templates/, etc. live). Useful for the planned settings-file editor surface.

Inputs: no named parameters.

waveform_statusA

Is Waveform currently running? Returns pid + exe path if so.

Inputs: no named parameters.

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

Loading tool schema changelog…

    TDQS

    C2.8/5.0

    Scored across 129 tools

    Disambiguation4/5

    Most tools have clearly distinct purposes, but several near-duplicates exist (tempo_set vs edit_set_tempo, compose_lofi_track vs compose_lofi_generative, audio_clip_import vs loop_drop). Some descriptions reference flows (e.g. 'revert→render→analyze' in render_and_audit) that don't map to explicit tools, which could confuse an agent.

    Naming Consistency4/5

    The dominant snake_case verb_noun / domain_action pattern is consistent (midi_clip_add, automation_clear, waveform_launch, theory_scale). However, a few outliers break the pattern: tempo_set and key_set lack the domain prefix that edit_set_tempo has for the same concept, and verbs are mixed (list/lookup/get/add/create) without a strict rule.

    Tool Count1/5

    With 129 tools, this is far beyond the 3-15 well-scoped range and even beyond the 50+ 'extreme' threshold. The breadth spans DAW control, music theory, generation, audio analysis, and desktop automation, but the sheer count makes it unwieldy for an agent to navigate; many tools (e.g. the numerous compose_*_track variants, granular desktop_* actions) could be consolidated behind parameters.

    Completeness3/5

    Coverage is broad across edit, clip, MIDI, audio, plugin, automation, rendering, theory, generation, and analysis domains. However, notable gaps exist: there is no way to list tracks (only track_add/remove), no bus listing, no marker add/list management, no plugin chain reorder/bypass, and no way to change time signature after edit creation.

    Maintenance

    ActivityInactive
    ResponsivenessOutdated data

    Related MCP Connectors

    Related MCP Servers