Case Study

Teaching Claude to Play the Keys: AI-Assisted Music Production

Ableton MCP
## The Vision What if you could tell an AI "add a filter sweep to the drop" and it just... did it? Not generating audio files or writing code—actually manipulating your DAW in real-time, understanding your session context, reading what's already there. That's Ableton MCP. It connects Claude to Ableton Live through Anthropic's Model Context Protocol, turning natural language into music production actions. Want to humanize a drum pattern? Automate a synth parameter? Copy a groove from one clip to another? Just ask. This isn't a novelty demo. It's a production tool I use in my own workflow, with 50+ MCP tools covering everything from MIDI manipulation to complex automation curves. ## The Architecture Challenge The architecture has three layers, each speaking a different language: ``` ┌─────────────────────────────────────────────────────────────────────┐ │ Claude (via MCP) │ │ Natural language → Tool calls │ └───────────────────────────────┬─────────────────────────────────────┘ │ MCP Protocol (JSON-RPC) ▼ ┌─────────────────────────────────────────────────────────────────────┐ │ Python MCP Server │ │ Tool definitions → TCP commands │ │ (runs outside Ableton) │ └───────────────────────────────┬─────────────────────────────────────┘ │ TCP Socket (JSON) ▼ ┌─────────────────────────────────────────────────────────────────────┐ │ Ableton Remote Script │ │ TCP listener → Live Object Model API │ │ (runs inside Ableton Live) │ └─────────────────────────────────────────────────────────────────────┘ ``` The challenge: Ableton's Python environment is sandboxed. You can't import external libraries, make HTTP requests, or run an MCP server directly inside it. The only way to communicate with the outside world is through a Remote Script—a special Python module that Ableton loads at startup. My solution: a two-process architecture. The MCP server runs as a normal Python process, handling Claude's tool calls. The Remote Script runs inside Ableton, listening on a TCP socket. They communicate via JSON commands over localhost. This separation has benefits beyond necessity. The MCP server can be updated without restarting Ableton. The Remote Script stays simple—just a command executor. And debugging is easier when you can test each layer independently. ## Bidirectional MIDI: The Key Differentiator Most Ableton automation tools can *write* MIDI. Mine can also *read* it. This sounds trivial. It's not. Ableton's Live Object Model exposes clips and notes, but the API is designed for playback, not inspection. Getting reliable note data requires understanding how Ableton stores MIDI internally: ```python # Getting notes from a clip isn't just clip.get_notes() # You need to specify a time range and pitch range notes = clip.get_notes_extended( from_pitch=0, pitch_span=128, from_time=0, time_span=clip.length ) # Returns: ((pitch, start_time, duration, velocity, mute, probability), ...) ``` Why does reading matter? Because Claude can now *understand* your existing music: - "Make the hi-hats in bar 3 match the groove of bar 1" - "What notes are in this chord?" - "Add harmony a third above the melody" Without reading, Claude is blind. It can only append, never analyze or modify intelligently. Bidirectional MIDI turns Claude from a dictation machine into a collaborator. ## Tool Design Philosophy 50+ tools sounds like feature creep. It's actually domain-driven organization: ``` ┌─────────────────────────────────────────────────────────────────────┐ │ Session Domain │ Clip Domain │ Device Domain │ │ ───────────────── │ ──────────────── │ ──────────────── │ │ • get_session_info │ • create_clip │ • load_instrument │ │ • set_tempo │ • get_notes │ • load_effect │ │ • set_time_signature │ • add_notes │ • get_parameters │ │ • fire_scene │ • delete_notes │ • set_parameter │ │ • stop_all_clips │ • duplicate_clip │ • get_device_chain │ ├─────────────────────────────────────────────────────────────────────┤ │ Track Domain │ Automation Domain │ Groove Domain │ │ ───────────────── │ ──────────────── │ ──────────────── │ │ • create_track │ • create_ramp │ • apply_groove │ │ • delete_track │ • create_lfo │ • humanize_timing │ │ • set_track_volume │ • create_step_seq │ • humanize_velocity│ │ • set_track_pan │ • filter_sweep │ • extract_groove │ │ • get_routing_info │ • clear_automation │ • list_grooves │ └─────────────────────────────────────────────────────────────────────┘ ``` Each domain maps to a mental model producers already have. You think in tracks, clips, and devices—so the tools do too. The automation tools deserve special mention. `create_lfo` doesn't just set values—it generates a mathematically correct waveform (sine, saw, square, triangle) mapped to any parameter. `filter_sweep` creates that classic EDM buildup with configurable resonance curves. These are production patterns encoded as single commands. Tool naming follows a simple convention: `{verb}_{noun}`. Get, set, create, delete, apply. This predictability helps Claude (and me) remember what's available without constantly checking documentation. ## Real-Time Communication Music production demands low latency. A 500ms delay between "play" and hearing sound breaks the creative flow. The TCP architecture prioritizes responsiveness: ```python class AbletonBridge: def __init__(self, host='127.0.0.1', port=9877): self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self.socket.settimeout(5.0) # Fail fast, don't hang def send_command(self, command: dict) -> dict: # JSON + newline delimiter for message framing message = json.dumps(command) + '\n' self.socket.sendall(message.encode()) # Read until newline (complete response) response = self._read_until_newline() return json.loads(response) def reconnect(self): # Auto-reconnect if Ableton restarts for attempt in range(3): try: self.socket.connect((self.host, self.port)) return True except ConnectionRefusedError: time.sleep(1) return False ``` The auto-reconnect logic handles the inevitable: Ableton crashes, or you restart it to test a plugin. Rather than requiring manual server restarts, the MCP server gracefully reconnects when Ableton comes back online. Command latency averages 15-30ms for simple operations (get tempo, set volume). Complex operations like reading all notes from a 64-bar clip take longer, but still under 200ms. This is fast enough that Claude's response time dominates—by the time you read the message, Ableton has already done the thing. ## Example Workflows Abstract tools become real in workflows. Here's what I actually use: **Drum Programming** ``` User: "Create an 8-bar drum pattern with kick on 1 and 3, snare on 2 and 4" Claude: [create_clip, add_notes (kick), add_notes (snare)] User: "Too mechanical. Humanize the hi-hats and add some ghost notes" Claude: [humanize_timing, humanize_velocity, add_notes (ghost snares)] ``` **Sound Design Automation** ``` User: "Add a filter sweep building up over 8 bars before the drop" Claude: [filter_sweep on track 3, from 200Hz to 18kHz, exponential curve] User: "And automate the reverb wet/dry to pull back at the same time" Claude: [create_ramp on reverb wet, from 60% to 10%] ``` **Arrangement Analysis** ``` User: "What's the chord progression in the intro?" Claude: [get_notes from piano clip] → "Analyzing... Dm - G - C - F (vi-II-V-I in F major)" User: "Add a bass line following the root notes" Claude: [create_clip, add_notes with extracted roots, octave shifted] ``` The power isn't in any single tool—it's in chaining them with context. Claude remembers what it just created, so "make it louder" or "duplicate that to track 5" work naturally. ## Lessons Learned **1. Ableton's API is powerful but underdocumented.** The Live Object Model can do almost anything, but you'll learn it through experimentation, not official docs. I spent hours in the Python console discovering what properties exist on each object. **2. Remote Scripts have quirks.** They run in a restricted Python environment with no pip packages. Threading is available but dangerous—Ableton's main thread must handle UI updates. I learned to keep the Remote Script as thin as possible, moving logic to the MCP server. **3. MCP tool design is UX design.** Too few tools means Claude can't express complex operations. Too many means it picks the wrong one. The sweet spot: enough granularity to be precise, enough abstraction to be natural. **4. Latency budgets matter.** I initially tried batching multiple operations into single commands. This reduced round trips but made debugging harder and increased perceived latency. Fast individual commands with clear responses beat clever batching. ## What's Next The current implementation handles Session View well, but Arrangement View support is basic. I want to add: - **Scene-aware automation**: Copy automation from Session clips to Arrangement - **Clip launching sequences**: Program complex live performance patterns - **Audio analysis**: Read transients and pitch from audio clips, not just MIDI - **Plugin state snapshots**: Save and recall device settings by name The bigger vision: a production assistant that understands your creative intent, not just your commands. "Make this sound more like Disclosure" is vague, but with enough context about your session and music theory knowledge, Claude could make meaningful suggestions. For now, Ableton MCP solves a real problem I have: the gap between thinking about music and manipulating software to create it. Every tool I add is one less context switch between creative and technical mode. --- *Building this taught me that the best developer tools feel like telepathy. The goal isn't replacing musicianship—it's removing the friction between hearing something in your head and making it real.*

Interested in working together?

Get in touch →
View all projects