Skip to main content
Glama
tracks.py45.6 kB
""" Complete Track Management Tools for REAPER MCP This module contains all 63 track-related operations migrated to the modern pattern. Organized into logical groups for better maintainability. """ from typing import Optional, Tuple from ..bridge import bridge import math # ============================================================================ # Basic Track Operations (13 tools) # ============================================================================ async def insert_track(index: int, use_defaults: bool = True) -> str: """Insert a new track at the specified index (0-based)""" result = await bridge.call_lua("InsertTrackAtIndex", [index, use_defaults]) if result.get("ok"): return f"Successfully inserted track at index {index}" else: raise Exception(f"Failed to insert track: {result.get('error', 'Unknown error')}") async def get_track_count() -> str: """Get the number of tracks in the current project""" result = await bridge.call_lua("CountTracks", [0]) if result.get("ok"): count = result.get("ret", 0) return f"Current project has {count} tracks" else: raise Exception(f"Failed to get track count: {result.get('error', 'Unknown error')}") async def get_track(track_index: int) -> str: """Get a track by index from the current project""" if track_index == -1: result = await bridge.call_lua("GetMasterTrack", [0]) else: result = await bridge.call_lua("GetTrack", [0, track_index]) if result.get("ok") and result.get("ret"): return f"Found track at index {track_index}" else: raise Exception(f"Track not found at index {track_index}") async def delete_track(track_index: int) -> str: """Delete a track by index""" # Get track count first count_result = await bridge.call_lua("CountTracks", [0]) if not count_result.get("ok"): raise Exception("Failed to get track count") track_count = count_result.get("ret", 0) if track_index >= track_count: raise ValueError(f"Track index {track_index} out of range. Project has {track_count} tracks.") # Use DeleteTrackByIndex directly to avoid pointer issues delete_result = await bridge.call_lua("DeleteTrackByIndex", [track_index]) if not delete_result.get("ok"): raise Exception(f"Failed to delete track: {delete_result.get('error', 'Unknown error')}") return f"Successfully deleted track at index {track_index}" async def get_master_track() -> str: """Get the master track""" result = await bridge.call_lua("GetMasterTrack", [0]) if result.get("ok") and result.get("ret"): return f"Master track: {result.get('ret')}" else: raise Exception("Failed to get master track") async def insert_track_at_index(index: int, want_defaults: bool = True) -> str: """Insert a track at a specific index (alias for insert_track)""" return await insert_track(index, want_defaults) async def get_track_guid(track_index: int) -> str: """Get the GUID of a track""" # Get the track track_result = await bridge.call_lua("GetTrack", [0, track_index]) if not track_result.get("ok") or not track_result.get("ret"): raise Exception(f"Failed to find track at index {track_index}") # Get its GUID guid_result = await bridge.call_lua("GetTrackGUID", [track_result.get("ret")]) if guid_result.get("ok") and guid_result.get("ret"): return f"Track GUID: {guid_result.get('ret')}" else: raise Exception("Failed to get track GUID") async def get_track_from_guid(guid: str) -> str: """Get track by GUID""" result = await bridge.call_lua("GetTrackByGUID", [guid]) if result.get("ok") and result.get("ret"): return f"Found track with GUID: {guid}" else: raise Exception(f"Track not found with GUID: {guid}") async def get_last_touched_track() -> str: """Get the last touched track""" result = await bridge.call_lua("GetLastTouchedTrack", []) if result.get("ok") and result.get("ret"): # Get track name for better info name_result = await bridge.call_lua("GetTrackName", [result.get("ret")]) if name_result.get("ok") and name_result.get("ret"): return f"Last touched track: {name_result.get('ret')}" else: return "Last touched track found (unnamed)" else: raise Exception("No track has been touched yet") async def count_selected_tracks() -> str: """Count the number of selected tracks""" result = await bridge.call_lua("CountSelectedTracks", [0]) if result.get("ok"): count = result.get("ret", 0) return f"{count} selected tracks" else: raise Exception(f"Failed to count selected tracks: {result.get('error', 'Unknown error')}") async def get_selected_track(index: int) -> str: """Get a selected track by index""" result = await bridge.call_lua("GetSelectedTrack", [0, index]) if result.get("ok"): track_handle = result.get("ret") if track_handle: return f"Selected track handle: {track_handle}" else: return f"No selected track at index {index}" else: raise Exception(f"Failed to get selected track: {result.get('error', 'Unknown error')}") async def set_track_selected(track_index: int, selected: bool) -> str: """Select or deselect a track""" # First get the track pointer track_result = await bridge.call_lua("GetTrack", [0, track_index]) if not track_result.get("ok") or not track_result.get("ret"): raise Exception(f"Failed to find track at index {track_index}") # Then set its selection state result = await bridge.call_lua("SetTrackSelected", [track_index, selected]) if result.get("ok"): action = "selected" if selected else "deselected" return f"Track at index {track_index} has been {action}" else: raise Exception(f"Failed to set track selection: {result.get('error', 'Unknown error')}") async def set_only_track_selected(track_index: int) -> str: """Set only one track selected, deselecting all others""" # Pass track index directly - the Lua bridge will handle getting the track result = await bridge.call_lua("SetOnlyTrackSelected", [track_index]) if result.get("ok"): # Update arrange view await bridge.call_lua("UpdateArrange", []) return f"Track {track_index} is now the only selected track" raise Exception("Failed to set track selection") # ============================================================================ # Track Properties (10 tools) # ============================================================================ async def get_track_name(track_index: int) -> str: """Get the name of a track by index (0-based)""" # Get the track if track_index == -1: track_result = await bridge.call_lua("GetMasterTrack", [0]) else: track_result = await bridge.call_lua("GetTrack", [0, track_index]) if not track_result.get("ok") or not track_result.get("ret"): raise Exception(f"Failed to find track at index {track_index}") # Get its name name_result = await bridge.call_lua("GetTrackName", [track_index]) if name_result.get("ok"): name = name_result.get("ret", "") if name: return f"Track {track_index} name: {name}" else: return f"Track {track_index} has no name" else: raise Exception("Failed to get track name") async def set_track_name(track_index: int, name: str) -> str: """Set the name of a track""" # Get the track if track_index == -1: track_result = await bridge.call_lua("GetMasterTrack", [0]) else: track_result = await bridge.call_lua("GetTrack", [0, track_index]) if not track_result.get("ok") or not track_result.get("ret"): raise Exception(f"Failed to find track at index {track_index}") # Set its name set_result = await bridge.call_lua("GetSetMediaTrackInfo_String", [track_index, "P_NAME", name, True]) if set_result.get("ok"): return f"Set track {track_index} name to: {name}" else: raise Exception("Failed to set track name") async def get_track_mute(track_index: int) -> str: """Get track mute state""" # Pass track index directly - the bridge will handle getting the track mute_result = await bridge.call_lua("GetMediaTrackInfo_Value", [track_index, "B_MUTE"]) if mute_result.get("ok"): is_muted = bool(mute_result.get("ret", 0)) return f"Track {track_index} is {'muted' if is_muted else 'not muted'}" else: raise Exception("Failed to get track mute state") async def set_track_mute(track_index: int, mute: bool) -> str: """Set track mute state""" # Pass track index directly - the bridge will handle getting the track mute_result = await bridge.call_lua("SetMediaTrackInfo_Value", [track_index, "B_MUTE", 1 if mute else 0]) if mute_result.get("ok"): return f"Track {track_index} {'muted' if mute else 'unmuted'}" else: raise Exception("Failed to set track mute state") async def get_track_solo(track_index: int) -> str: """Get track solo state""" # Pass track index directly - the bridge will handle getting the track result = await bridge.call_lua("GetMediaTrackInfo_Value", [track_index, "I_SOLO"]) if result.get("ok"): solo_state = int(result.get("ret", 0)) solo_text = "not soloed" if solo_state == 0 else "soloed" return f"Track {track_index} solo state: {solo_text}" else: raise Exception(f"Failed to get track solo state: {result.get('error', 'Unknown error')}") async def set_track_solo(track_index: int, solo: bool) -> str: """Set track solo state""" # Pass track index directly - the bridge will handle getting the track result = await bridge.call_lua("SetMediaTrackInfo_Value", [track_index, "I_SOLO", 1 if solo else 0]) if result.get("ok"): state = "soloed" if solo else "unsoloed" return f"Track {track_index} {state}" else: raise Exception(f"Failed to set track solo state: {result.get('error', 'Unknown error')}") async def get_track_color(track_index: int) -> str: """Get the color of a track""" result = await bridge.call_lua("GetTrackColor", [track_index]) if result.get("ok"): color = result.get("result", 0) # Extract RGB components r = (color >> 16) & 0xFF g = (color >> 8) & 0xFF b = color & 0xFF return f"Track {track_index} color: RGB({r}, {g}, {b}) (0x{color:06X})" else: raise Exception(f"Failed to get track color: {result.get('error', 'Unknown error')}") async def set_track_color(track_index: int, color: int) -> str: """Set the color of a track""" result = await bridge.call_lua("SetTrackColor", [track_index, color]) if result.get("ok"): if color == 0: return f"Set track {track_index} color to default" else: r = (color >> 16) & 0xFF g = (color >> 8) & 0xFF b = color & 0xFF return f"Set track {track_index} color to RGB({r}, {g}, {b})" else: raise Exception(f"Failed to set track color: {result.get('error', 'Unknown error')}") async def is_track_visible(track_index: int, mixer: bool = False) -> str: """Check if a track is visible in TCP or MCP""" # Pass track index directly - the Lua bridge will handle getting the track # First get the track to ensure it exists track_result = await bridge.call_lua("GetTrack", [0, track_index]) if not track_result.get("ok") or not track_result.get("ret"): raise Exception(f"Failed to get track at index {track_index}") # Check visibility using the track pointer result = await bridge.call_lua("IsTrackVisible", [track_result.get("ret"), mixer]) if result.get("ok"): visible = result.get("ret", False) location = "mixer" if mixer else "track control panel" return f"Track {track_index} is {'visible' if visible else 'not visible'} in {location}" raise Exception("Failed to check track visibility") async def any_track_solo(project_index: int = 0) -> str: """Check if any track is soloed""" result = await bridge.call_lua("AnyTrackSolo", [project_index]) if result.get("ok"): is_soloed = result.get("ret", 0) return f"Any track soloed: {'Yes' if is_soloed else 'No'}" raise Exception("Failed to check if any track is soloed") # ============================================================================ # Track Volume & Pan (6 tools) # ============================================================================ async def get_track_volume(track_index: int) -> str: """Get track volume in dB""" # Get track first track_result = await bridge.call_lua("GetTrack", [0, track_index]) if not track_result.get("ok") or not track_result.get("ret"): raise Exception(f"Failed to find track at index {track_index}") result = await bridge.call_lua("GetMediaTrackInfo_Value", [track_index, "D_VOL"]) if result.get("ok"): vol_linear = result.get("ret", 1.0) # Convert to dB if vol_linear > 0: vol_db = 20 * math.log10(vol_linear) else: vol_db = -math.inf return f"Track {track_index} volume: {vol_db:.2f} dB" else: raise Exception(f"Failed to get track volume: {result.get('error', 'Unknown error')}") async def set_track_volume(track_index: int, volume_db: float) -> str: """Set track volume in dB""" # Convert dB to linear if volume_db > -150: vol_linear = 10 ** (volume_db / 20) else: vol_linear = 0 # Use the index-based function to avoid track handle issues result = await bridge.call_lua("SetMediaTrackInfo_Value", [track_index, "D_VOL", vol_linear]) if result.get("ok"): return f"Track {track_index} volume set to {volume_db:.2f} dB" else: raise Exception(f"Failed to set track volume: {result.get('error', 'Unknown error')}") async def get_track_volume_db(track_index: int) -> str: """Get track volume in dB (alias)""" return await get_track_volume(track_index) async def set_track_volume_db(track_index: int, volume_db: float) -> str: """Set track volume in dB (alias)""" return await set_track_volume(track_index, volume_db) async def get_track_pan(track_index: int) -> str: """Get track pan position (-1.0 to 1.0)""" # Get track first track_result = await bridge.call_lua("GetTrack", [0, track_index]) if not track_result.get("ok") or not track_result.get("ret"): raise Exception(f"Failed to find track at index {track_index}") result = await bridge.call_lua("GetMediaTrackInfo_Value", [track_index, "D_PAN"]) if result.get("ok"): pan = result.get("ret", 0.0) return f"Track {track_index} pan: {pan:.2f}" else: raise Exception(f"Failed to get track pan: {result.get('error', 'Unknown error')}") async def set_track_pan(track_index: int, pan: float) -> str: """Set track pan position (-1.0 to 1.0)""" # Clamp pan value pan = max(-1.0, min(1.0, pan)) # Use the index-based function to avoid track handle issues result = await bridge.call_lua("SetMediaTrackInfo_Value", [track_index, "D_PAN", pan]) if result.get("ok"): return f"Track {track_index} pan set to {pan:.2f}" else: raise Exception(f"Failed to set track pan: {result.get('error', 'Unknown error')}") # ============================================================================ # Track Recording (6 tools) # ============================================================================ async def get_track_record_mode(track_index: int) -> str: """Get track record mode""" result = await bridge.call_lua("GetMediaTrackInfo_Value", [track_index, "I_RECMODE"]) if result.get("ok"): mode = int(result.get("ret", 0)) mode_names = { 0: "Input", 1: "Stereo out", 2: "None (monitoring only)", 3: "Stereo out with latency compensation", 4: "MIDI output", 5: "Mono out", 6: "Mono out with latency compensation", 7: "MIDI overdub", 8: "MIDI replace" } mode_name = mode_names.get(mode, f"Unknown mode {mode}") return f"Track {track_index} record mode: {mode_name} ({mode})" else: raise Exception(f"Failed to get track record mode: {result.get('error', 'Unknown error')}") async def set_track_record_mode(track_index: int, mode: int) -> str: """Set track record mode""" result = await bridge.call_lua("SetMediaTrackInfo_Value", [track_index, "I_RECMODE", mode]) if result.get("ok"): mode_names = { 0: "Input", 1: "Stereo out", 2: "None (monitoring only)", 3: "Stereo out with latency compensation", 4: "MIDI output", 5: "Mono out", 6: "Mono out with latency compensation", 7: "MIDI overdub", 8: "MIDI replace" } mode_name = mode_names.get(mode, f"mode {mode}") return f"Set track {track_index} record mode to: {mode_name}" else: raise Exception(f"Failed to set track record mode: {result.get('error', 'Unknown error')}") async def get_track_record_input(track_index: int) -> str: """Get track record input""" result = await bridge.call_lua("GetMediaTrackInfo_Value", [track_index, "I_RECINPUT"]) if result.get("ok"): input_val = int(result.get("ret", -1)) if input_val == -1: input_name = "None" elif input_val < 512: input_name = f"Mono hardware input {input_val + 1}" elif input_val < 1024: pair = (input_val - 512) // 2 + 1 input_name = f"Stereo hardware input pair {pair}" else: input_name = f"ReaRoute/loopback {input_val - 1024 + 1}" return f"Track {track_index} record input: {input_name} ({input_val})" else: raise Exception(f"Failed to get track record input: {result.get('error', 'Unknown error')}") async def set_track_record_input(track_index: int, input: int) -> str: """Set track record input""" result = await bridge.call_lua("SetMediaTrackInfo_Value", [track_index, "I_RECINPUT", input]) if result.get("ok"): if input == -1: input_name = "None" elif input < 512: input_name = f"Mono hardware input {input + 1}" elif input < 1024: pair = (input - 512) // 2 + 1 input_name = f"Stereo hardware input pair {pair}" else: input_name = f"ReaRoute/loopback {input - 1024 + 1}" return f"Set track {track_index} record input to: {input_name}" else: raise Exception(f"Failed to set track record input: {result.get('error', 'Unknown error')}") async def get_track_record_arm(track_index: int) -> str: """Get track record arm state""" result = await bridge.call_lua("GetMediaTrackInfo_Value", [track_index, "I_RECARM"]) if result.get("ok"): armed = result.get("ret", 0) > 0 return f"Track {track_index} record arm: {'Armed' if armed else 'Not armed'}" else: raise Exception(f"Failed to get track record arm state: {result.get('error', 'Unknown error')}") async def set_track_record_arm(track_index: int, armed: bool) -> str: """Set track record arm state""" result = await bridge.call_lua("SetMediaTrackInfo_Value", [track_index, "I_RECARM", 1 if armed else 0]) if result.get("ok"): return f"Track {track_index} record arm: {'Armed' if armed else 'Disarmed'}" else: raise Exception(f"Failed to set track record arm state: {result.get('error', 'Unknown error')}") # ============================================================================ # Track FX (6 tools) # ============================================================================ async def track_fx_get_count(track_index: int) -> str: """Get the number of FX on a track""" # Get track track_result = await bridge.call_lua("GetTrack", [0, track_index]) if not track_result.get("ok") or not track_result.get("ret"): raise Exception(f"Failed to find track at index {track_index}") track_handle = track_result.get("ret") # Get FX count result = await bridge.call_lua("TrackFX_GetCount", [track_handle]) if result.get("ok"): count = result.get("ret", 0) return f"Track {track_index} has {count} FX" else: raise Exception(f"Failed to get FX count: {result.get('error', 'Unknown error')}") async def track_fx_add_by_name(track_index: int, fx_name: str, instantiate: bool = True) -> str: """Add an FX to a track by name""" # Get track track_result = await bridge.call_lua("GetTrack", [0, track_index]) if not track_result.get("ok") or not track_result.get("ret"): raise Exception(f"Failed to find track at index {track_index}") track_handle = track_result.get("ret") # Add FX result = await bridge.call_lua("TrackFX_AddByName", [track_handle, fx_name, False, -1 if instantiate else -1000]) if result.get("ok"): fx_index = result.get("ret", -1) if fx_index >= 0: return f"Added {fx_name} to track {track_index} at FX index {fx_index}" else: return f"Failed to add {fx_name} to track {track_index}" else: raise Exception(f"Failed to add FX: {result.get('error', 'Unknown error')}") async def track_fx_delete(track_index: int, fx_index: int) -> str: """Delete an FX from a track""" # Get track track_result = await bridge.call_lua("GetTrack", [0, track_index]) if not track_result.get("ok") or not track_result.get("ret"): raise Exception(f"Failed to find track at index {track_index}") track_handle = track_result.get("ret") # Delete FX result = await bridge.call_lua("TrackFX_Delete", [track_handle, fx_index]) if result.get("ok"): return f"Deleted FX at index {fx_index} from track {track_index}" else: raise Exception(f"Failed to delete FX: {result.get('error', 'Unknown error')}") async def track_fx_get_enabled(track_index: int, fx_index: int) -> str: """Get whether an FX is enabled""" # Get track track_result = await bridge.call_lua("GetTrack", [0, track_index]) if not track_result.get("ok") or not track_result.get("ret"): raise Exception(f"Failed to find track at index {track_index}") track_handle = track_result.get("ret") # Get enabled state result = await bridge.call_lua("TrackFX_GetEnabled", [track_handle, fx_index]) if result.get("ok"): enabled = bool(result.get("ret", False)) return f"FX {fx_index} on track {track_index} is {'enabled' if enabled else 'disabled'}" else: raise Exception(f"Failed to get FX enabled state: {result.get('error', 'Unknown error')}") async def track_fx_set_enabled(track_index: int, fx_index: int, enabled: bool) -> str: """Enable or disable an FX""" # Get track track_result = await bridge.call_lua("GetTrack", [0, track_index]) if not track_result.get("ok") or not track_result.get("ret"): raise Exception(f"Failed to find track at index {track_index}") track_handle = track_result.get("ret") # Set enabled state result = await bridge.call_lua("TrackFX_SetEnabled", [track_handle, fx_index, enabled]) if result.get("ok"): return f"FX {fx_index} on track {track_index} {'enabled' if enabled else 'disabled'}" else: raise Exception(f"Failed to set FX enabled state: {result.get('error', 'Unknown error')}") async def track_fx_get_name(track_index: int, fx_index: int) -> str: """Get the name of an FX on a track""" # Get track track_result = await bridge.call_lua("GetTrack", [0, track_index]) if not track_result.get("ok") or not track_result.get("ret"): raise Exception(f"Failed to find track at index {track_index}") track_handle = track_result.get("ret") # Get FX name result = await bridge.call_lua("TrackFX_GetFXName", [track_handle, fx_index, ""]) if result.get("ok"): # The result contains both the return value and the name if isinstance(result.get("ret"), list) and len(result.get("ret", [])) > 1: fx_name = result.get("ret")[1] else: fx_name = "Unknown" return f"FX {fx_index} on track {track_index}: {fx_name}" else: raise Exception(f"Failed to get FX name: {result.get('error', 'Unknown error')}") # ============================================================================ # Track Sends & Routing (7 tools) # ============================================================================ async def create_track_send(source_track_index: int, dest_track_index: int) -> str: """Create a send from one track to another""" # Get source and destination tracks source_result = await bridge.call_lua("GetTrack", [0, source_track_index]) if not source_result.get("ok") or not source_result.get("ret"): raise Exception(f"Failed to find source track at index {source_track_index}") dest_result = await bridge.call_lua("GetTrack", [0, dest_track_index]) if not dest_result.get("ok") or not dest_result.get("ret"): raise Exception(f"Failed to find destination track at index {dest_track_index}") source_track = source_result.get("ret") dest_track = dest_result.get("ret") # Create send result = await bridge.call_lua("CreateTrackSend", [source_track, dest_track]) if result.get("ok"): send_index = result.get("ret", -1) if send_index >= 0: return f"Created send from track {source_track_index} to track {dest_track_index} (send index: {send_index})" else: return f"Failed to create send" else: raise Exception(f"Failed to create send: {result.get('error', 'Unknown error')}") async def remove_track_send(track_index: int, send_index: int) -> str: """Remove a send from a track""" # Get track track_result = await bridge.call_lua("GetTrack", [0, track_index]) if not track_result.get("ok") or not track_result.get("ret"): raise Exception(f"Failed to find track at index {track_index}") track_handle = track_result.get("ret") # Remove send result = await bridge.call_lua("RemoveTrackSend", [track_handle, 0, send_index]) if result.get("ok"): return f"Removed send {send_index} from track {track_index}" else: raise Exception(f"Failed to remove send: {result.get('error', 'Unknown error')}") async def get_track_num_sends(track_index: int) -> str: """Get the number of sends on a track""" # Get track track_result = await bridge.call_lua("GetTrack", [0, track_index]) if not track_result.get("ok") or not track_result.get("ret"): raise Exception(f"Failed to find track at index {track_index}") track_handle = track_result.get("ret") # Get number of sends result = await bridge.call_lua("GetTrackNumSends", [track_handle, 0]) if result.get("ok"): num_sends = result.get("ret", 0) return f"Track {track_index} has {num_sends} sends" else: raise Exception(f"Failed to get number of sends: {result.get('error', 'Unknown error')}") async def set_track_send_volume(track_index: int, send_index: int, volume: float) -> str: """Set the volume of a track send""" # Get track track_result = await bridge.call_lua("GetTrack", [0, track_index]) if not track_result.get("ok") or not track_result.get("ret"): raise Exception(f"Failed to find track at index {track_index}") track_handle = track_result.get("ret") # Set send volume result = await bridge.call_lua("SetTrackSendInfo_Value", [track_handle, 0, send_index, "D_VOL", volume]) if result.get("ok"): return f"Set send {send_index} volume to {volume:.3f}" else: raise Exception(f"Failed to set send volume: {result.get('error', 'Unknown error')}") async def get_track_send_info(track_index: int, send_index: int) -> str: """Get information about a track send""" # Get track track_result = await bridge.call_lua("GetTrack", [0, track_index]) if not track_result.get("ok") or not track_result.get("ret"): raise Exception(f"Failed to find track at index {track_index}") track_handle = track_result.get("ret") # Get send volume vol_result = await bridge.call_lua("GetTrackSendInfo_Value", [track_handle, 0, send_index, "D_VOL"]) # Get destination track dest_result = await bridge.call_lua("GetTrackSendInfo_Value", [track_handle, 0, send_index, "P_DESTTRACK"]) if vol_result.get("ok"): volume = vol_result.get("ret", 0.0) dest_info = "" if dest_result.get("ok") and dest_result.get("ret"): # Try to find destination track index for i in range(100): # Check first 100 tracks check_result = await bridge.call_lua("GetTrack", [0, i]) if check_result.get("ok") and check_result.get("ret") == dest_result.get("ret"): dest_info = f", destination: track {i}" break return f"Send {send_index}: volume={volume:.3f}{dest_info}" else: raise Exception(f"Failed to get send info: {vol_result.get('error', 'Unknown error')}") async def get_track_receive_count(track_index: int) -> str: """Get the number of receives on a track""" result = await bridge.call_lua("GetTrackNumSends", [track_index, -1]) if result.get("ok"): count = result.get("result", 0) return f"Track {track_index} has {count} receives" else: raise Exception(f"Failed to get track receive count: {result.get('error', 'Unknown error')}") async def get_track_receive_info(track_index: int, receive_index: int) -> str: """Get information about a track receive""" result = await bridge.call_lua("GetTrackReceiveInfo", [track_index, receive_index]) if result.get("ok"): info = result.get("result", {}) return f"Receive {receive_index} from track {info.get('src_track', 'unknown')} - Volume: {info.get('volume', 0):.2f} dB, Pan: {info.get('pan', 0):.2f}" else: raise Exception(f"Failed to get track receive info: {result.get('error', 'Unknown error')}") # ============================================================================ # Note: Track automation functions moved to automation.py # ============================================================================ # Note: get_track_envelope_by_name is also defined in automation.py # Keeping this version for now as it may have different behavior 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')""" # Get track track_result = await bridge.call_lua("GetTrack", [0, track_index]) if not track_result.get("ok") or not track_result.get("ret"): raise Exception(f"Failed to find track at index {track_index}") track_handle = track_result.get("ret") # Get envelope by name result = await bridge.call_lua("GetTrackEnvelopeByName", [track_handle, 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: return f"Envelope '{envelope_name}' not found on track {track_index}" else: raise Exception(f"Failed to get envelope: {result.get('error', 'Unknown error')}") # ============================================================================ # Track Media Items (2 tools) - add_media_item_to_track moved to media_items.py # ============================================================================ # Note: add_media_item_to_track is defined in media_items.py async def delete_track_media_item(track_index: int, item_index: int) -> str: """Delete a media item from track""" # Pass indices directly - the bridge will handle getting the track and item result = await bridge.call_lua("DeleteTrackMediaItem", [track_index, item_index]) if result.get("ok"): return f"Deleted media item {item_index} from track {track_index}" else: raise Exception(f"Failed to delete media item: {result.get('error', 'Unknown error')}") async def get_media_item_take_track(item_index: int) -> str: """Get the track of a media item take""" result = await bridge.call_lua("GetMediaItemTrack", [item_index]) if result.get("ok"): track_info = result.get("result", {}) return f"Item {item_index} is on track: {track_info.get('name', 'Unnamed')} (index {track_info.get('index', -1)})" else: raise Exception(f"Failed to get item track: {result.get('error', 'Unknown error')}") # ============================================================================ # Track Analysis & Monitoring (2 tools) # ============================================================================ async def get_track_peak(track_index: int, channel: int = 0) -> str: """Get the current peak level of a track""" result = await bridge.call_lua("Track_GetPeakInfo", [track_index, channel]) if result.get("ok"): peak_value = result.get("result", 0.0) peak_db = 20 * (peak_value / 0.0000000298023223876953125) if peak_value > 0 else -150.0 return f"Track {track_index} peak (channel {channel}): {peak_db:.2f} dB ({peak_value:.6f})" else: raise Exception(f"Failed to get track peak: {result.get('error', 'Unknown error')}") async def get_track_peak_info(track_index: int) -> str: """Get detailed peak information for a track""" result = await bridge.call_lua("Track_GetPeakHoldDB", [track_index]) if result.get("ok"): peak_data = result.get("result", {}) left_peak = peak_data.get("left", -150.0) right_peak = peak_data.get("right", -150.0) return f"Track {track_index} peak info - Left: {left_peak:.2f} dB, Right: {right_peak:.2f} dB" else: raise Exception(f"Failed to get track peak info: {result.get('error', 'Unknown error')}") # ============================================================================ # Track Advanced Operations (7 tools) # ============================================================================ # Note: freeze_track and unfreeze_track moved to rendering.py async def bounce_tracks(add_to_project: bool = True) -> str: """Bounce selected tracks to a new track""" # Bounce selected tracks (action 40914) result = await bridge.call_lua("Main_OnCommand", [40914, 0]) if result.get("ok"): return "Bounced selected tracks" + (" to new track" if add_to_project else "") else: raise Exception(f"Failed to bounce tracks: {result.get('error', 'Unknown error')}") async def add_video_to_track(track_index: int, video_file: str) -> str: """Add a video file to a track""" result = await bridge.call_lua("AddVideoToTrack", [track_index, video_file]) if result.get("ok"): return f"Added video '{video_file}' to track {track_index}" else: raise Exception(f"Failed to add video: {result.get('error', 'Unknown error')}") async def get_track_state_chunk(track_index: int = 0) -> str: """Get the state chunk of a track""" # Get track track_result = await bridge.call_lua("GetTrack", [0, track_index]) if not track_result.get("ok") or not track_result.get("ret"): raise Exception(f"Failed to find track at index {track_index}") track_handle = track_result.get("ret") # Get state chunk result = await bridge.call_lua("GetTrackStateChunk", [track_handle, "", 0]) if result.get("ok") and result.get("chunk"): return result.get("chunk", "") raise Exception("Failed to get track state chunk") async def set_track_state_chunk(track_index: int, chunk: str) -> str: """Set the state chunk of a track""" # Get track track_result = await bridge.call_lua("GetTrack", [0, track_index]) if not track_result.get("ok") or not track_result.get("ret"): raise Exception(f"Failed to find track at index {track_index}") track_handle = track_result.get("ret") # Set state chunk result = await bridge.call_lua("SetTrackStateChunk", [track_handle, chunk, 0]) if result.get("ok"): return "Successfully set track state chunk" raise Exception("Failed to set track state chunk") async def get_track_midi_note_name(track_index: int, pitch: int, channel: int = 0) -> str: """Get MIDI note name for a track""" # Get the track track_result = await bridge.call_lua("GetTrack", [0, track_index]) if not track_result.get("ok") or not track_result.get("ret"): raise Exception(f"Failed to get track at index {track_index}") # Get MIDI note name result = await bridge.call_lua("GetTrackMIDINoteName", [track_result.get("ret"), pitch, channel]) if result.get("ok") and result.get("ret"): note_name = result.get("ret") return f"MIDI note {pitch} on track {track_index} channel {channel}: {note_name}" # Default note name if no custom name note_names = ["C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"] octave = (pitch // 12) - 1 note = note_names[pitch % 12] default_name = f"{note}{octave}" return f"MIDI note {pitch} on track {track_index} channel {channel}: {default_name} (default)" # ============================================================================ # Media Item Selection Functions # ============================================================================ async def select_all_media_items() -> str: """Select all media items in the project""" result = await bridge.call_lua("Main_OnCommand", [40182, 0]) # Item: Select all items if result.get("ok"): return "Select all media items: true" else: raise Exception(f"Failed to select all media items: {result.get('error', 'Unknown error')}") async def unselect_all_media_items() -> str: """Unselect all media items in the project""" result = await bridge.call_lua("Main_OnCommand", [40289, 0]) # Item: Unselect all items if result.get("ok"): return "Unselect all media items: true" else: raise Exception(f"Failed to unselect all media items: {result.get('error', 'Unknown error')}") # ============================================================================ # Additional Helper Functions # ============================================================================ async def get_mixer_scroll() -> str: """Get the leftmost track visible in the mixer""" result = await bridge.call_lua("GetMixerScroll", []) if result.get("ok") and result.get("ret") is not None: leftmost_track = result.get("ret") return f"Leftmost track in mixer: {leftmost_track}" raise Exception("Failed to get mixer scroll position") async def set_mixer_scroll(leftmost_track: int) -> str: """Set the leftmost track visible in the mixer""" # Get the track to verify it exists track_result = await bridge.call_lua("GetTrack", [0, leftmost_track]) if not track_result.get("ok") or not track_result.get("ret"): raise Exception(f"Track {leftmost_track} does not exist") result = await bridge.call_lua("SetMixerScroll", [track_result.get("ret")]) if result.get("ok"): return f"Set mixer scroll to track {leftmost_track}" raise Exception("Failed to set mixer scroll position") # ============================================================================ # Registration Function # ============================================================================ def register_track_tools(mcp) -> int: """Register all track management tools with the MCP instance""" tools = [ # Basic Operations (13) (insert_track, "Insert a new track at the specified index (0-based)"), (get_track_count, "Get the number of tracks in the current project"), (get_track, "Get a track by index from the current project"), (delete_track, "Delete a track by index"), (get_master_track, "Get the master track"), (insert_track_at_index, "Insert a track at a specific index"), (get_track_guid, "Get the GUID of a track"), (get_track_from_guid, "Get track by GUID"), (get_last_touched_track, "Get the last touched track"), (count_selected_tracks, "Count the number of selected tracks"), (get_selected_track, "Get a selected track by index"), (set_track_selected, "Select or deselect a track"), (set_only_track_selected, "Set only one track selected, deselecting all others"), # Properties (10) (get_track_name, "Get the name of a track by index"), (set_track_name, "Set the name of a track"), (get_track_mute, "Get track mute state"), (set_track_mute, "Set track mute state"), (get_track_solo, "Get track solo state"), (set_track_solo, "Set track solo state"), (get_track_color, "Get the color of a track"), (set_track_color, "Set the color of a track"), (is_track_visible, "Check if a track is visible in TCP or MCP"), (any_track_solo, "Check if any track is soloed"), # Volume & Pan (6) (get_track_volume, "Get track volume in dB"), (set_track_volume, "Set track volume in dB"), (get_track_volume_db, "Get track volume in dB"), (set_track_volume_db, "Set track volume in dB"), (get_track_pan, "Get track pan position (-1.0 to 1.0)"), (set_track_pan, "Set track pan position (-1.0 to 1.0)"), # Recording (6) (get_track_record_mode, "Get track record mode"), (set_track_record_mode, "Set track record mode"), (get_track_record_input, "Get track record input"), (set_track_record_input, "Set track record input"), (get_track_record_arm, "Get track record arm state"), (set_track_record_arm, "Set track record arm state"), # FX (6) (track_fx_get_count, "Get the number of FX on a track"), (track_fx_add_by_name, "Add an FX to a track by name"), (track_fx_delete, "Delete an FX from a track"), (track_fx_get_enabled, "Get whether an FX is enabled"), (track_fx_set_enabled, "Enable or disable an FX"), (track_fx_get_name, "Get the name of an FX on a track"), # Sends & Routing (7) (create_track_send, "Create a send from one track to another"), (remove_track_send, "Remove a send from a track"), (get_track_num_sends, "Get the number of sends on a track"), (set_track_send_volume, "Set the volume of a track send"), (get_track_send_info, "Get information about a track send"), (get_track_receive_count, "Get the number of receives on a track"), (get_track_receive_info, "Get information about a track receive"), # Automation (1) - others moved to automation.py (get_track_envelope_by_name, "Get a track envelope by name"), # Media Items (2) - add_media_item_to_track moved to media_items.py (delete_track_media_item, "Delete a media item from track"), (get_media_item_take_track, "Get the track of a media item take"), # Analysis (2) (get_track_peak, "Get the current peak level of a track"), (get_track_peak_info, "Get detailed peak information for a track"), # Advanced (5) - freeze_track and unfreeze_track moved to rendering.py (bounce_tracks, "Bounce selected tracks to a new track"), (add_video_to_track, "Add a video file to a track"), (get_track_state_chunk, "Get the state chunk of a track"), (set_track_state_chunk, "Set the state chunk of a track"), (get_track_midi_note_name, "Get MIDI note name for a track"), # Additional (2) (get_mixer_scroll, "Get the leftmost track visible in the mixer"), (set_mixer_scroll, "Set the leftmost track visible in the mixer"), # Media Item Selection (2) (select_all_media_items, "Select all media items in the project"), (unselect_all_media_items, "Unselect all media items in the project"), ] # Register each tool for func, desc in tools: # The decorator will use the function's docstring if available, # but we can override it with our description decorated = mcp.tool()(func) return len(tools) # Return count of registered 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