waveform-MCP
An MCP server that gives an LLM agent control over Tracktion Waveform, enabling it to compose, mix, and render songs through natural language commands.
README
Waveform MCP
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.
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.
What this gives you
- 141 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
- Three composers —
compose_lofi_track,compose_synthwave_track,compose_rainstorm compose_variationsto 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
- Verified round-trip between the in-memory model and Waveform's
.tracktioneditXML - 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
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) — 107 tools │
└────────────────────────────┬─────────────────────────────────┘
│
┌──────────────┼──────────────┐
▼ ▼ ▼
┌──────────────┐ ┌──────────┐ ┌──────────────┐
│ Edit model │ │ Knowledge│ │ Waveform │
│ (in-memory) │ │ (data) │ │ UI control │
├──────────────┤ ├──────────┤ ├──────────────┤
│ Tracks │ │ Scales │ │ pywinauto + │
│ Clips │ │ Chords │ │ UIA + ffmpeg │
│ Notes │ │ Forms │ │ │
│ Plugins │ │ Mix │ │ Menu invoke │
│ Automation │ │ Velocity │ │ Revert │
│ Markers │ │ Rhythm │ │ Render→MP3 │
└──────┬───────┘ └──────────┘ └──────┬───────┘
│ │
▼ ▼
┌──────────────────┐ ┌────────────────────┐
│ xml_writer.py │ │ Waveform 13 │
│ xml_reader.py │ ◀──────▶ │ (the running app) │
│ ↓ .tracktionedit │ └────────────────────┘
└──────────────────┘
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
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 — 7 genres × 14 roles, fully annotated
Composers (composer.py)
compose_lofi_track— 32-bar lofi with drums, bass, keys, pad, melody, counter; section-aware velocity envelopes; tempo automation; lofi master chaincompose_synthwave_track— 64-bar synthwave with 7 tracks; 9-section form (intro/verse/chorus/verse/chorus/bridge/buildup/chorusFinal/outro); per-section bass feels (half-time / 8th-pump / walking); section-keyed arp themes; sidechain-style filter pump; section markers; clip fadescompose_rainstorm— ambient soundscape with rain + wind + lowpassed-distant thunder; per-clip gain randomization; offset trim; track FX
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 (waveform_app.py)
waveform_locate · waveform_status · waveform_launch · waveform_focus · waveform_quit · waveform_settings_dir
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
│ ├── tools/
│ │ ├── edit.py Edit lifecycle
│ │ ├── tracks.py Tracks + mix balance
│ │ ├── midi.py MIDI clips/notes
│ │ ├── audio.py Audio clip import
│ │ ├── clips.py Clip mutators (move, resize, duplicate, set)
│ │ ├── plugins.py Plugin add + reverb / drum kit / modifier 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
│ │ ├── 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
│ │ ├── composer.py compose_lofi_track, compose_synthwave_track, compose_rainstorm
│ │ └── common.py @op decorator (apply + diff + event)
│ └── preview/
│ ├── app.py FastAPI + websocket
│ └── static/ HTML / JS piano-roll
├── tests/
├── 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:
- Mastering The Mix — How To Balance Kick And Bass
- Mastering The Mix — How To Mix Bass Synth
- eMastered — Balance All The Elements
Known limitations
- Track-volume
AUTOMATIONCURVEis disabled. Waveform'svolumeplugin 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, staticmix_set). - Plugin modifier matrix is exploratory.
plugin_add_modifierwrites 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_mp3drives Waveform's UI export — works, but requires Waveform to be running. A C++ helper linkingtracktion_engineis the eventual fix. - Linux/macOS UI control absent. Content discovery (presets, loop library, VST list) is OS-aware; UI automation is Windows-only.
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/wmctrlonce UIA is no longer the only path
License
GPL-3.0-or-later (matches tracktion_engine if/when the C++ render helper links to it).
Recommended Servers
playwright-mcp
A Model Context Protocol server that enables LLMs to interact with web pages through structured accessibility snapshots without requiring vision models or screenshots.
Magic Component Platform (MCP)
An AI-powered tool that generates modern UI components from natural language descriptions, integrating with popular IDEs to streamline UI development workflow.
Audiense Insights MCP Server
Enables interaction with Audiense Insights accounts via the Model Context Protocol, facilitating the extraction and analysis of marketing insights and audience data including demographics, behavior, and influencer engagement.
VeyraX MCP
Single MCP tool to connect all your favorite tools: Gmail, Calendar and 40 more.
graphlit-mcp-server
The Model Context Protocol (MCP) Server enables integration between MCP clients and the Graphlit service. Ingest anything from Slack to Gmail to podcast feeds, in addition to web crawling, into a Graphlit project - and then retrieve relevant contents from the MCP client.
Kagi MCP Server
An MCP server that integrates Kagi search capabilities with Claude AI, enabling Claude to perform real-time web searches when answering questions that require up-to-date information.
E2B
Using MCP to run code via e2b.
Neon Database
MCP server for interacting with Neon Management API and databases
Qdrant Server
This repository is an example of how to create a MCP server for Qdrant, a vector search engine.
Exa Search
A Model Context Protocol (MCP) server lets AI assistants like Claude use the Exa AI Search API for web searches. This setup allows AI models to get real-time web information in a safe and controlled way.