Skip to main content
Glama
automation.py22 kB
""" Automation & Envelopes Tools for REAPER MCP This module contains tools for automation and envelope management. """ from typing import Optional from ..bridge import bridge # ============================================================================ # Envelope Management (6 tools) # ============================================================================ async def get_track_envelope_by_name(track_index: int, envelope_name: str) -> str: """Get a track envelope by name (e.g., 'Volume', 'Pan', 'Mute')""" # Force track_index to be an integer if isinstance(track_index, dict) and '__ptr' in track_index: raise ValueError(f"Received track handle instead of track index: {track_index}") track_index = int(track_index) # Pass track index directly - the bridge will handle getting the track result = await bridge.call_lua("GetTrackEnvelopeByName", [track_index, envelope_name]) if result.get("ok"): envelope_handle = result.get("ret") if envelope_handle: return f"Found envelope '{envelope_name}' on track {track_index}" else: # Envelope might not be visible, try to show it first # Select the track await bridge.call_lua("SetTrackSelected", [track_index, True]) # Show the appropriate envelope based on name if envelope_name.lower() == "volume": await bridge.call_lua("Main_OnCommand", [40406, 0]) # Toggle track volume envelope visible elif envelope_name.lower() == "pan": await bridge.call_lua("Main_OnCommand", [40407, 0]) # Toggle track pan envelope visible elif envelope_name.lower() == "mute": await bridge.call_lua("Main_OnCommand", [40867, 0]) # Toggle track mute envelope visible # Try again result = await bridge.call_lua("GetTrackEnvelopeByName", [track_index, envelope_name]) if result.get("ok") and result.get("ret"): return f"Found envelope '{envelope_name}' on track {track_index}" else: return f"Envelope '{envelope_name}' not found on track {track_index}" else: raise Exception(f"Failed to get envelope: {result.get('error', 'Unknown error')}") async def count_envelope_points(track_index: int, envelope_name: str) -> str: """Count the number of points in an envelope""" # Get envelope - pass track index directly env_result = await bridge.call_lua("GetTrackEnvelopeByName", [track_index, envelope_name]) if not env_result.get("ok") or not env_result.get("ret"): raise Exception(f"Failed to find envelope '{envelope_name}' on track {track_index}") envelope_handle = env_result.get("ret") # Count points result = await bridge.call_lua("CountEnvelopePoints", [envelope_handle]) if result.get("ok"): count = result.get("ret", 0) return f"Envelope '{envelope_name}' has {count} points" else: raise Exception(f"Failed to count envelope points: {result.get('error', 'Unknown error')}") async def insert_envelope_point(track_index: int, envelope_name: str, time: float, value: float, shape: int = 0, selected: bool = False) -> str: """Insert an automation point in an envelope""" # Get envelope - pass track index directly env_result = await bridge.call_lua("GetTrackEnvelopeByName", [track_index, envelope_name]) if not env_result.get("ok") or not env_result.get("ret"): raise Exception(f"Failed to find envelope '{envelope_name}' on track {track_index}") envelope_handle = env_result.get("ret") # Insert point result = await bridge.call_lua("InsertEnvelopePoint", [ envelope_handle, time, value, shape, 0, selected, True ]) if result.get("ok"): return f"Inserted point at {time:.3f}s with value {value:.3f} in '{envelope_name}' envelope" else: raise Exception(f"Failed to insert envelope point: {result.get('error', 'Unknown error')}") async def delete_envelope_point(track_index: int, envelope_name: str, point_index: int) -> str: """Delete an automation point from an envelope""" # Get envelope - pass track index directly env_result = await bridge.call_lua("GetTrackEnvelopeByName", [track_index, envelope_name]) if not env_result.get("ok") or not env_result.get("ret"): raise Exception(f"Failed to find envelope '{envelope_name}' on track {track_index}") envelope_handle = env_result.get("ret") # Delete point result = await bridge.call_lua("DeleteEnvelopePointEx", [envelope_handle, -1, point_index]) if result.get("ok"): return f"Deleted point {point_index} from '{envelope_name}' envelope" else: raise Exception(f"Failed to delete envelope point: {result.get('error', 'Unknown error')}") async def get_envelope_point(track_index: int, envelope_name: str, point_index: int) -> str: """Get information about an envelope point""" # Get envelope - pass track index directly env_result = await bridge.call_lua("GetTrackEnvelopeByName", [track_index, envelope_name]) if not env_result.get("ok") or not env_result.get("ret"): raise Exception(f"Failed to find envelope '{envelope_name}' on track {track_index}") envelope_handle = env_result.get("ret") # Get point info result = await bridge.call_lua("GetEnvelopePoint", [envelope_handle, point_index, 0, 0, 0, 0, 0]) if result.get("ok"): ret_values = result.get("ret", []) if isinstance(ret_values, list) and len(ret_values) >= 6: retval, time, value, shape, tension, selected = ret_values[:6] if retval: return f"Point {point_index}: time={time:.3f}s, value={value:.3f}, shape={shape}, selected={bool(selected)}" return f"Point {point_index} not found" else: raise Exception(f"Failed to get envelope point: {result.get('error', 'Unknown error')}") async def set_envelope_point_value(track_index: int, envelope_name: str, point_index: int, value: float, time: Optional[float] = None) -> str: """Set the value of an existing envelope point""" # Get envelope - pass track index directly env_result = await bridge.call_lua("GetTrackEnvelopeByName", [track_index, envelope_name]) if not env_result.get("ok") or not env_result.get("ret"): raise Exception(f"Failed to find envelope '{envelope_name}' on track {track_index}") envelope_handle = env_result.get("ret") # Get current point info get_result = await bridge.call_lua("GetEnvelopePoint", [envelope_handle, point_index, 0, 0, 0, 0, 0]) if get_result.get("ok"): ret_values = get_result.get("ret", []) if isinstance(ret_values, list) and len(ret_values) >= 6: retval, current_time, old_value, shape, tension, selected = ret_values[:6] if retval: # Use new time if provided, otherwise keep existing if time is not None: current_time = time # Set point value result = await bridge.call_lua("SetEnvelopePoint", [ envelope_handle, point_index, current_time, value, shape, tension, selected, True ]) if result.get("ok"): return f"Set point {point_index} value to {value:.3f}" else: raise Exception(f"Failed to set envelope point: {result.get('error', 'Unknown error')}") return f"Point {point_index} not found" else: raise Exception(f"Failed to get envelope point: {get_result.get('error', 'Unknown error')}") # ============================================================================ # Envelope Extended Operations (12 tools) # ============================================================================ async def envelope_evaluate(track_index: int, envelope_name: str, time: float) -> str: """Evaluate envelope value at a specific time""" # Get envelope - pass track index directly env_result = await bridge.call_lua("GetTrackEnvelopeByName", [track_index, envelope_name]) if not env_result.get("ok") or not env_result.get("ret"): raise Exception(f"Failed to find envelope '{envelope_name}' on track {track_index}") envelope_handle = env_result.get("ret") # Evaluate envelope at time result = await bridge.call_lua("Envelope_Evaluate", [envelope_handle, time, 0, 0]) if result.get("ok"): ret = result.get("ret", []) if isinstance(ret, list) and len(ret) >= 3: value = ret[1] return f"Envelope '{envelope_name}' value at {time:.3f}s: {value:.3f}" else: return f"Unable to evaluate envelope at {time:.3f}s" else: raise Exception(f"Failed to evaluate envelope: {result.get('error', 'Unknown error')}") async def delete_envelope_point_range(track_index: int, envelope_name: str, start_time: float, end_time: float) -> str: """Delete all envelope points within a time range""" # Get envelope - pass track index directly env_result = await bridge.call_lua("GetTrackEnvelopeByName", [track_index, envelope_name]) if not env_result.get("ok") or not env_result.get("ret"): raise Exception(f"Failed to find envelope '{envelope_name}' on track {track_index}") envelope_handle = env_result.get("ret") # Delete points in range result = await bridge.call_lua("DeleteEnvelopePointRange", [envelope_handle, start_time, end_time]) if result.get("ok"): return f"Deleted envelope points between {start_time:.3f}s and {end_time:.3f}s" else: raise Exception(f"Failed to delete envelope point range: {result.get('error', 'Unknown error')}") async def envelope_sort_points(track_index: int, envelope_name: str) -> str: """Sort envelope points by time""" # Get envelope - pass track index directly env_result = await bridge.call_lua("GetTrackEnvelopeByName", [track_index, envelope_name]) if not env_result.get("ok") or not env_result.get("ret"): raise Exception(f"Failed to find envelope '{envelope_name}' on track {track_index}") envelope_handle = env_result.get("ret") # Sort points result = await bridge.call_lua("Envelope_SortPoints", [envelope_handle]) if result.get("ok"): return f"Sorted envelope '{envelope_name}' points" else: raise Exception(f"Failed to sort envelope points: {result.get('error', 'Unknown error')}") async def get_envelope_name(track_index: int, envelope_index: int) -> str: """Get the name of an envelope by index""" # Get envelope by index - pass track index directly env_result = await bridge.call_lua("GetTrackEnvelope", [track_index, envelope_index]) if not env_result.get("ok") or not env_result.get("ret"): raise Exception(f"Failed to find envelope at index {envelope_index}") envelope_handle = env_result.get("ret") # Get envelope name result = await bridge.call_lua("GetEnvelopeName", [envelope_handle]) if result.get("ok"): name = result.get("ret", "") return f"Envelope {envelope_index}: {name}" else: raise Exception(f"Failed to get envelope name: {result.get('error', 'Unknown error')}") async def get_envelope_scaling_mode(track_index: int, envelope_name: str) -> str: """Get envelope scaling mode""" # Get envelope - pass track index directly env_result = await bridge.call_lua("GetTrackEnvelopeByName", [track_index, envelope_name]) if not env_result.get("ok") or not env_result.get("ret"): raise Exception(f"Failed to find envelope '{envelope_name}' on track {track_index}") envelope_handle = env_result.get("ret") # Get scaling mode result = await bridge.call_lua("GetEnvelopeScalingMode", [envelope_handle]) if result.get("ok"): mode = result.get("ret", 0) mode_names = {0: "Linear", 1: "Fader Scaling"} mode_name = mode_names.get(mode, f"Unknown ({mode})") return f"Envelope '{envelope_name}' scaling mode: {mode_name}" else: raise Exception(f"Failed to get envelope scaling mode: {result.get('error', 'Unknown error')}") async def get_fx_envelope(track_index: int, fx_index: int, param_index: int) -> str: """Get parameter envelope for a track FX""" # Get FX envelope - pass track index directly result = await bridge.call_lua("GetFXEnvelope", [track_index, fx_index, param_index, False]) if result.get("ok"): envelope_handle = result.get("ret") if envelope_handle: # Get envelope name for info name_result = await bridge.call_lua("GetEnvelopeName", [envelope_handle]) env_name = name_result.get("ret", "") if name_result.get("ok") else "Unknown" return f"Found FX parameter envelope: {env_name}" else: return f"No envelope for FX {fx_index} parameter {param_index}" else: raise Exception(f"Failed to get FX envelope: {result.get('error', 'Unknown error')}") async def insert_envelope_point_ex(track_index: int, envelope_name: str, time: float, value: float, shape: int = 0, tension: float = 0.0, selected: bool = False, no_sort: bool = False) -> str: """Insert an automation point with extended parameters""" # Get envelope - pass track index directly env_result = await bridge.call_lua("GetTrackEnvelopeByName", [track_index, envelope_name]) if not env_result.get("ok") or not env_result.get("ret"): raise Exception(f"Failed to find envelope '{envelope_name}' on track {track_index}") envelope_handle = env_result.get("ret") # Insert point with extended parameters result = await bridge.call_lua("InsertEnvelopePointEx", [ envelope_handle, -1, time, value, shape, tension, selected, no_sort ]) if result.get("ok"): return f"Inserted point at {time:.3f}s with value {value:.3f}, shape={shape}, tension={tension:.3f}" else: raise Exception(f"Failed to insert envelope point: {result.get('error', 'Unknown error')}") async def get_selected_envelope(project_index: int = 0) -> str: """Get the currently selected envelope""" result = await bridge.call_lua("GetSelectedEnvelope", [project_index]) if result.get("ok"): envelope_handle = result.get("ret") if envelope_handle: # Get envelope name name_result = await bridge.call_lua("GetEnvelopeName", [envelope_handle]) env_name = name_result.get("ret", "") if name_result.get("ok") else "Unknown" return f"Selected envelope: {env_name}" else: return "No envelope selected" else: raise Exception(f"Failed to get selected envelope: {result.get('error', 'Unknown error')}") async def get_track_envelope(track_index: int, envelope_index: int) -> str: """Get track envelope by index""" # Get envelope by index - pass track index directly result = await bridge.call_lua("GetTrackEnvelope", [track_index, envelope_index]) if result.get("ok"): envelope_handle = result.get("ret") if envelope_handle: # Get envelope name name_result = await bridge.call_lua("GetEnvelopeName", [envelope_handle]) env_name = name_result.get("ret", "") if name_result.get("ok") else "Unknown" return f"Track {track_index} envelope {envelope_index}: {env_name}" else: return f"No envelope at index {envelope_index} on track {track_index}" else: raise Exception(f"Failed to get track envelope: {result.get('error', 'Unknown error')}") async def count_track_envelopes(track_index: int) -> str: """Count the number of envelopes on a track""" # Count envelopes - pass track index directly result = await bridge.call_lua("CountTrackEnvelopes", [track_index]) if result.get("ok"): count = result.get("ret", 0) return f"Track {track_index} has {count} envelopes" else: raise Exception(f"Failed to count track envelopes: {result.get('error', 'Unknown error')}") async def get_track_automation_mode(track_index: int) -> str: """Get the automation mode for a track""" # Get automation mode - pass track index, not handle result = await bridge.call_lua("GetTrackAutomationMode", [track_index]) if result.get("ok"): mode = result.get("ret", -1) mode_names = { -1: "Global", 0: "Trim/Read", 1: "Read", 2: "Touch", 3: "Write", 4: "Latch" } mode_name = mode_names.get(mode, f"Unknown ({mode})") return f"Track {track_index} automation mode: {mode_name}" else: raise Exception(f"Failed to get track automation mode: {result.get('error', 'Unknown error')}") async def set_track_automation_mode(track_index: int, mode: int) -> str: """Set the automation mode for a track""" # Validate mode mode_names = { -1: "Global", 0: "Trim/Read", 1: "Read", 2: "Touch", 3: "Write", 4: "Latch" } if mode not in mode_names: raise Exception(f"Invalid automation mode: {mode}. Valid modes: {list(mode_names.keys())}") # Set automation mode - pass track index, not handle result = await bridge.call_lua("SetTrackAutomationMode", [track_index, mode]) if result.get("ok"): return f"Set track {track_index} automation mode to: {mode_names[mode]}" else: raise Exception(f"Failed to set track automation mode: {result.get('error', 'Unknown error')}") async def get_global_automation_override() -> str: """Get the global automation override state""" result = await bridge.call_lua("GetGlobalAutomationOverride", []) if result.get("ok"): mode = result.get("ret", -1) mode_names = { -1: "No override", 0: "No override", 1: "Bypass all automation", 2: "Write current values for all writing tracks", 3: "Write current values for all tracks", 4: "All tracks set to touch mode", 5: "All tracks set to read mode", 6: "All tracks set to latch mode" } mode_name = mode_names.get(mode, f"Unknown ({mode})") return f"Global automation override: {mode_name}" else: raise Exception(f"Failed to get global automation override: {result.get('error', 'Unknown error')}") async def set_global_automation_override(mode: int) -> str: """Set the global automation override state""" # Validate mode mode_names = { -1: "No override", 0: "No override", 1: "Bypass all automation", 2: "Write current values for all writing tracks", 3: "Write current values for all tracks", 4: "All tracks set to touch mode", 5: "All tracks set to read mode", 6: "All tracks set to latch mode" } if mode not in mode_names: raise Exception(f"Invalid automation override mode: {mode}. Valid modes: {list(mode_names.keys())}") # Set global automation override result = await bridge.call_lua("SetGlobalAutomationOverride", [mode]) if result.get("ok"): return f"Set global automation override to: {mode_names[mode]}" else: raise Exception(f"Failed to set global automation override: {result.get('error', 'Unknown error')}") # ============================================================================ # Registration Function # ============================================================================ def register_automation_tools(mcp) -> int: """Register all automation and envelope tools with the MCP instance""" tools = [ # Envelope Management (get_track_envelope_by_name, "Get a track envelope by name (e.g., 'Volume', 'Pan', 'Mute')"), (count_envelope_points, "Count the number of points in an envelope"), (insert_envelope_point, "Insert an automation point in an envelope"), (delete_envelope_point, "Delete an automation point from an envelope"), (get_envelope_point, "Get information about an envelope point"), (set_envelope_point_value, "Set the value of an existing envelope point"), # Envelope Extended Operations (envelope_evaluate, "Evaluate envelope value at a specific time"), (delete_envelope_point_range, "Delete all envelope points within a time range"), (envelope_sort_points, "Sort envelope points by time"), (get_envelope_name, "Get the name of an envelope by index"), (get_envelope_scaling_mode, "Get envelope scaling mode"), (get_fx_envelope, "Get parameter envelope for a track FX"), (insert_envelope_point_ex, "Insert an automation point with extended parameters"), (get_selected_envelope, "Get the currently selected envelope"), (get_track_envelope, "Get track envelope by index"), (count_track_envelopes, "Count the number of envelopes on a track"), (get_track_automation_mode, "Get the automation mode for a track"), (set_track_automation_mode, "Set the automation mode for a track"), (get_global_automation_override, "Get the global automation override state"), (set_global_automation_override, "Set the global automation override state"), ] # Register each tool for func, desc in tools: decorated = mcp.tool()(func) return len(tools)

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/shiehn/total-reaper-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server