waveform-MCP

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.

Category
Visit Server

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.

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.


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 composerscompose_lofi_track, compose_synthwave_track, compose_rainstorm
  • 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
  • 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 loopcompose → 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_trackwaveform_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 chain
  • compose_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 fades
  • compose_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:


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.

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

License

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

Recommended Servers

playwright-mcp

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.

Official
Featured
TypeScript
Magic Component Platform (MCP)

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.

Official
Featured
Local
TypeScript
Audiense Insights MCP Server

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.

Official
Featured
Local
TypeScript
VeyraX MCP

VeyraX MCP

Single MCP tool to connect all your favorite tools: Gmail, Calendar and 40 more.

Official
Featured
Local
graphlit-mcp-server

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.

Official
Featured
TypeScript
Kagi MCP Server

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.

Official
Featured
Python
E2B

E2B

Using MCP to run code via e2b.

Official
Featured
Neon Database

Neon Database

MCP server for interacting with Neon Management API and databases

Official
Featured
Qdrant Server

Qdrant Server

This repository is an example of how to create a MCP server for Qdrant, a vector search engine.

Official
Featured
Exa Search

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.

Official
Featured