This document describes how to interact with Max for Live (M4L) devices, including CV Tools, through the ClaudeMCP Remote Script.
The Remote Script includes 5 M4L-specific tools that provide simplified access to Max for Live devices using parameter names instead of indices.
Important: The M4L implementation is generic and future-proof - it works with ANY Max for Live device without hardcoded parameter knowledge.
The implementation uses runtime introspection to discover devices and parameters dynamically:
✅ Works with ANY M4L device:
- Built-in Ableton M4L devices (CV LFO, CV Shaper, Envelope Follower, etc.)
- Third-party M4L devices from Max for Live packs
- Custom user-created M4L devices
- Future M4L devices Ableton adds in updates
✅ Only hardcoded knowledge:
- 3 M4L device class names from LiveAPI spec:
MxDeviceAudioEffect- M4L audio effectsMxDeviceMidiEffect- M4L MIDI effectsMxDeviceInstrument- M4L instruments
✅ Parameter discovery:
- Uses
device.parametersto enumerate all parameters at runtime - User/client provides parameter names (e.g., "Rate", "Depth", "Attack")
- No maintenance needed when new M4L devices are released
def set_device_param_by_name(self, track_index, device_index, param_name, value):
"""Set device parameter by name (useful for M4L devices)"""
device = track.devices[device_index]
# Runtime discovery - iterate through ALL parameters
for i, param in enumerate(device.parameters):
if str(param.name) == param_name: # User provides the name
param.value = float(value)
return {"ok": True, "param_name": param_name, "value": float(param.value)}
return {"ok": False, "error": "Parameter not found"}Key insight: No hardcoded parameter lists - discovers parameters dynamically using LiveAPI introspection.
Max for Live devices are identified by their class_name:
- M4L Audio Effects:
class_name == "MxDeviceAudioEffect" - M4L MIDI Effects:
class_name == "MxDeviceMidiEffect" - M4L Instruments:
class_name == "MxDeviceInstrument"
Simple workflow using M4L-specific tools:
import socket
import json
def send_command(action, **params):
"""Send command to Ableton via port 9004"""
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.connect(('127.0.0.1', 9004))
command = {'action': action, **params}
message = json.dumps(command) + '\n'
sock.sendall(message.encode('utf-8'))
response = b''
while b'\n' not in response:
response += sock.recv(4096)
sock.close()
return json.loads(response.decode('utf-8'))
# 1. Find CV LFO device using CV Tools filter
cv_devices = send_command('get_cv_tools_devices', track_index=0)
lfo = cv_devices['cv_devices'][0] # First CV device
device_index = lfo['index']
# 2. Set LFO rate by parameter name (no index lookup needed!)
send_command('set_device_param_by_name',
track_index=0,
device_index=device_index,
param_name='Rate',
value=0.5)
# 3. Get current rate value
rate = send_command('get_m4l_param_by_name',
track_index=0,
device_index=device_index,
param_name='Rate')
print("LFO Rate:", rate['value'])Common CV Tools devices and their purposes:
| Device | Purpose | Key Parameters |
|---|---|---|
| CV LFO | Generate CV modulation | Rate, Shape, Depth |
| CV Shaper | Shape CV signals | Drive, Curve, Bias |
| CV Envelope Follower | Audio to CV | Attack, Release, Gain |
| CV Instrument | CV to MIDI | Range, Quantize |
| CV Triggers | Generate triggers | Rate, Probability |
| CV Utility | CV routing/mixing | Mix, Offset, Scale |
{
"action": "is_max_device",
"track_index": 0,
"device_index": 2
}Response:
{
"ok": true,
"is_m4l": true,
"class_name": "MxDeviceAudioEffect",
"class_display_name": "Max Audio Effect",
"device_name": "CV LFO"
}{
"action": "get_m4l_devices",
"track_index": 0
}Response:
{
"ok": true,
"track_index": 0,
"track_name": "1 Audio",
"devices": [
{
"index": 2,
"name": "CV LFO",
"class_name": "MxDeviceAudioEffect",
"type": "audio_effect",
"is_active": true,
"num_parameters": 12
}
],
"count": 1
}{
"action": "get_m4l_param_by_name",
"track_index": 0,
"device_index": 2,
"param_name": "Rate"
}Response:
{
"ok": true,
"param_index": 5,
"name": "Rate",
"value": 0.5,
"min": 0.0,
"max": 1.0,
"is_enabled": true
}Works with ANY device, but especially useful for M4L devices:
{
"action": "set_device_param_by_name",
"track_index": 0,
"device_index": 2,
"param_name": "Rate",
"value": 0.75
}Response:
{
"ok": true,
"track_index": 0,
"device_index": 2,
"param_name": "Rate",
"param_index": 5,
"value": 0.75
}Convenience filter for CV Tools pack devices:
{
"action": "get_cv_tools_devices",
"track_index": 0
}Response:
{
"ok": true,
"track_index": 0,
"track_name": "1 Audio",
"cv_devices": [
{
"index": 2,
"name": "CV LFO",
"class_name": "MxDeviceAudioEffect",
"is_active": true,
"num_parameters": 12
},
{
"index": 3,
"name": "CV Shaper",
"class_name": "MxDeviceAudioEffect",
"is_active": true,
"num_parameters": 8
}
],
"count": 2
}You can also use standard device tools with M4L devices:
- List devices:
get_track_devices- Returns all devices including M4L - Get parameters:
get_device_parameters- Lists all parameters with indices - Set parameters:
set_device_param- Set by parameter index - Get device info:
get_device_info- Get device details
Using M4L-specific tools for simplified access:
#!/usr/bin/env python
"""Control CV Tools LFO device using M4L-specific tools"""
import socket
import json
import time
def send_command(action, **params):
"""Send command to Ableton via port 9004"""
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.connect(('127.0.0.1', 9004))
command = {'action': action, **params}
message = json.dumps(command) + '\n'
sock.sendall(message.encode('utf-8'))
response = b''
while b'\n' not in response:
response += sock.recv(4096)
sock.close()
return json.loads(response.decode('utf-8'))
# 1. Find all M4L devices on track 0
result = send_command('get_m4l_devices', track_index=0)
print("M4L devices on track 0:", result['devices'])
# 2. Find CV Tools devices specifically
cv_result = send_command('get_cv_tools_devices', track_index=0)
if cv_result['count'] > 0:
lfo_device = cv_result['cv_devices'][0]
device_index = lfo_device['index']
print("Found CV LFO at device index:", device_index)
# 3. Get current Rate parameter value
rate_info = send_command('get_m4l_param_by_name',
track_index=0,
device_index=device_index,
param_name='Rate')
print("Current Rate:", rate_info)
# 4. Animate LFO rate using parameter name (no index lookup needed!)
for i in range(10):
rate = i / 10.0
result = send_command('set_device_param_by_name',
track_index=0,
device_index=device_index,
param_name='Rate',
value=rate)
print("Set LFO rate to " + str(rate) + ": " + str(result['ok']))
time.sleep(0.5)
else:
print("No CV Tools devices found on track 0")The implementation uses LiveAPI's device introspection:
# M4L Device Detection
m4l_classes = ['MxDeviceAudioEffect', 'MxDeviceMidiEffect', 'MxDeviceInstrument']
is_m4l = device.class_name in m4l_classes
# Parameter Discovery (Runtime)
for i, param in enumerate(device.parameters):
if str(param.name) == param_name: # User-provided name
param.value = float(value) # Set value directlyKey advantages:
- No hardcoded parameter databases
- Works with all M4L devices (built-in, third-party, custom)
- No maintenance when new devices are added
- User/client provides parameter names from UI
The ClaudeMCP Remote Script provides complete M4L support through 5 specialized tools:
✅ Implemented (5 tools):
is_max_device- Check if device is M4Lget_m4l_devices- Get all M4L devices on trackget_m4l_param_by_name- Get parameter by nameset_device_param_by_name- Set parameter by name (works with ANY device)get_cv_tools_devices- Get CV Tools pack devices
✅ Generic implementation:
- Works with ALL M4L devices (built-in, third-party, custom, future)
- No hardcoded parameter databases
- Runtime parameter discovery using LiveAPI introspection
- CV modulation mapping introspection (Live 11.1+ API if available)
- M4L device preset loading/saving
- M4L patch file (.amxd) metadata reading
- Additional CV Tools workflow examples