Skip to content

Commit 94889fc

Browse files
committed
Add Max for Live and CV Tools support (125 total tools)
Features: - 5 new M4L-specific tools for device detection and control - Detect Max for Live devices by class name - Get all M4L devices or CV Tools devices on a track - Control M4L device parameters by name (easier than index) - Get/set parameter values for CV Tools (LFO, Shaper, Envelope Follower, etc.) New Tools: - is_max_device: Check if a device is Max for Live - get_m4l_devices: Get all M4L devices on a track - set_device_param_by_name: Set parameter by name (not index) - get_m4l_param_by_name: Get parameter info by name - get_cv_tools_devices: Get CV Tools devices specifically Documentation: - MAX4LIVE_INTEGRATION.md: Complete M4L integration guide - examples/cv_tools_control.py: Working example controlling CV LFO - Updated README with M4L/CV Tools section Total tools: 120 → 125 Closes support for Max for Live devices and CV Tools pack
1 parent 653e4ef commit 94889fc

5 files changed

Lines changed: 827 additions & 4 deletions

File tree

ClaudeMCP_Remote/__init__.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -516,6 +516,18 @@ def _process_command(self, command):
516516
elif action == 'randomize_device':
517517
return self.tools.randomize_device(command.get('track_index', 0), command.get('device_index', 0))
518518

519+
# Max for Live (M4L) operations
520+
elif action == 'is_max_device':
521+
return self.tools.is_max_device(command.get('track_index', 0), command.get('device_index', 0))
522+
elif action == 'get_m4l_devices':
523+
return self.tools.get_m4l_devices(command.get('track_index', 0))
524+
elif action == 'set_device_param_by_name':
525+
return self.tools.set_device_param_by_name(command.get('track_index', 0), command.get('device_index', 0), command.get('param_name', ''), command.get('value', 0.0))
526+
elif action == 'get_m4l_param_by_name':
527+
return self.tools.get_m4l_param_by_name(command.get('track_index', 0), command.get('device_index', 0), command.get('param_name', ''))
528+
elif action == 'get_cv_tools_devices':
529+
return self.tools.get_cv_tools_devices(command.get('track_index', 0))
530+
519531
# Unknown action
520532
else:
521533
return {

ClaudeMCP_Remote/liveapi_tools.py

Lines changed: 164 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2157,6 +2157,166 @@ def randomize_device(self, track_index, device_index):
21572157
except Exception as e:
21582158
return {"ok": False, "error": str(e)}
21592159

2160+
# ========================================================================
2161+
# MAX FOR LIVE (M4L) DEVICE OPERATIONS
2162+
# ========================================================================
2163+
2164+
def is_max_device(self, track_index, device_index):
2165+
"""Check if device is a Max for Live device"""
2166+
try:
2167+
if track_index < 0 or track_index >= len(self.song.tracks):
2168+
return {"ok": False, "error": "Invalid track index"}
2169+
2170+
track = self.song.tracks[track_index]
2171+
if device_index < 0 or device_index >= len(track.devices):
2172+
return {"ok": False, "error": "Invalid device index"}
2173+
2174+
device = track.devices[device_index]
2175+
2176+
# M4L devices have specific class names
2177+
m4l_classes = ['MxDeviceAudioEffect', 'MxDeviceMidiEffect', 'MxDeviceInstrument']
2178+
is_m4l = device.class_name in m4l_classes
2179+
2180+
return {
2181+
"ok": True,
2182+
"is_m4l": is_m4l,
2183+
"class_name": str(device.class_name),
2184+
"class_display_name": str(device.class_display_name) if hasattr(device, 'class_display_name') else str(device.class_name),
2185+
"device_name": str(device.name)
2186+
}
2187+
except Exception as e:
2188+
return {"ok": False, "error": str(e)}
2189+
2190+
def get_m4l_devices(self, track_index):
2191+
"""Get all Max for Live devices on track"""
2192+
try:
2193+
if track_index < 0 or track_index >= len(self.song.tracks):
2194+
return {"ok": False, "error": "Invalid track index"}
2195+
2196+
track = self.song.tracks[track_index]
2197+
m4l_devices = []
2198+
m4l_classes = ['MxDeviceAudioEffect', 'MxDeviceMidiEffect', 'MxDeviceInstrument']
2199+
2200+
for i, device in enumerate(track.devices):
2201+
if device.class_name in m4l_classes:
2202+
device_type = self._get_m4l_type(device.class_name)
2203+
m4l_devices.append({
2204+
"index": i,
2205+
"name": str(device.name),
2206+
"class_name": str(device.class_name),
2207+
"type": device_type,
2208+
"is_active": device.is_active,
2209+
"num_parameters": len(device.parameters)
2210+
})
2211+
2212+
return {
2213+
"ok": True,
2214+
"track_index": track_index,
2215+
"track_name": str(track.name),
2216+
"devices": m4l_devices,
2217+
"count": len(m4l_devices)
2218+
}
2219+
except Exception as e:
2220+
return {"ok": False, "error": str(e)}
2221+
2222+
def _get_m4l_type(self, class_name):
2223+
"""Get M4L device type from class name"""
2224+
type_map = {
2225+
'MxDeviceAudioEffect': 'audio_effect',
2226+
'MxDeviceMidiEffect': 'midi_effect',
2227+
'MxDeviceInstrument': 'instrument'
2228+
}
2229+
return type_map.get(class_name, 'unknown')
2230+
2231+
def set_device_param_by_name(self, track_index, device_index, param_name, value):
2232+
"""Set device parameter by name (useful for M4L devices with custom parameter names)"""
2233+
try:
2234+
if track_index < 0 or track_index >= len(self.song.tracks):
2235+
return {"ok": False, "error": "Invalid track index"}
2236+
2237+
track = self.song.tracks[track_index]
2238+
if device_index < 0 or device_index >= len(track.devices):
2239+
return {"ok": False, "error": "Invalid device index"}
2240+
2241+
device = track.devices[device_index]
2242+
2243+
# Find parameter by name
2244+
for i, param in enumerate(device.parameters):
2245+
if str(param.name) == param_name:
2246+
param.value = float(value)
2247+
return {
2248+
"ok": True,
2249+
"track_index": track_index,
2250+
"device_index": device_index,
2251+
"param_name": param_name,
2252+
"param_index": i,
2253+
"value": float(param.value)
2254+
}
2255+
2256+
return {"ok": False, "error": "Parameter '{}' not found".format(param_name)}
2257+
except Exception as e:
2258+
return {"ok": False, "error": str(e)}
2259+
2260+
def get_m4l_param_by_name(self, track_index, device_index, param_name):
2261+
"""Get M4L device parameter value by name"""
2262+
try:
2263+
if track_index < 0 or track_index >= len(self.song.tracks):
2264+
return {"ok": False, "error": "Invalid track index"}
2265+
2266+
track = self.song.tracks[track_index]
2267+
if device_index < 0 or device_index >= len(track.devices):
2268+
return {"ok": False, "error": "Invalid device index"}
2269+
2270+
device = track.devices[device_index]
2271+
2272+
# Find parameter by name
2273+
for i, param in enumerate(device.parameters):
2274+
if str(param.name) == param_name:
2275+
return {
2276+
"ok": True,
2277+
"param_index": i,
2278+
"name": str(param.name),
2279+
"value": float(param.value),
2280+
"min": float(param.min),
2281+
"max": float(param.max),
2282+
"is_enabled": param.is_enabled if hasattr(param, 'is_enabled') else True
2283+
}
2284+
2285+
return {"ok": False, "error": "Parameter '{}' not found".format(param_name)}
2286+
except Exception as e:
2287+
return {"ok": False, "error": str(e)}
2288+
2289+
def get_cv_tools_devices(self, track_index):
2290+
"""Get all CV Tools devices on track (subset of M4L devices)"""
2291+
try:
2292+
if track_index < 0 or track_index >= len(self.song.tracks):
2293+
return {"ok": False, "error": "Invalid track index"}
2294+
2295+
track = self.song.tracks[track_index]
2296+
cv_devices = []
2297+
2298+
for i, device in enumerate(track.devices):
2299+
device_name = str(device.name)
2300+
# Check if device name contains "CV" (common in CV Tools)
2301+
if 'CV' in device_name or 'cv' in device_name.lower():
2302+
cv_devices.append({
2303+
"index": i,
2304+
"name": device_name,
2305+
"class_name": str(device.class_name),
2306+
"is_active": device.is_active,
2307+
"num_parameters": len(device.parameters)
2308+
})
2309+
2310+
return {
2311+
"ok": True,
2312+
"track_index": track_index,
2313+
"track_name": str(track.name),
2314+
"cv_devices": cv_devices,
2315+
"count": len(cv_devices)
2316+
}
2317+
except Exception as e:
2318+
return {"ok": False, "error": str(e)}
2319+
21602320
# ========================================================================
21612321
# UTILITY
21622322
# ========================================================================
@@ -2225,5 +2385,8 @@ def get_available_tools(self):
22252385
# Track routing extras (3 tools)
22262386
"get_track_output_routing", "set_track_input_sub_routing", "set_track_output_sub_routing",
22272387
# Device extras - missing tool (1 tool)
2228-
"randomize_device"
2388+
"randomize_device",
2389+
# Max for Live (M4L) operations (6 tools)
2390+
"is_max_device", "get_m4l_devices", "set_device_param_by_name",
2391+
"get_m4l_param_by_name", "get_cv_tools_devices"
22292392
]

0 commit comments

Comments
 (0)