Skip to main content
Glama
recording.py28.2 kB
""" Recording Tools for REAPER MCP This module contains tools for managing recording settings, track arming, input monitoring, and recording operations in REAPER projects. """ from typing import Optional, List, Tuple, Union from ..bridge import bridge # ============================================================================ # Track Record Arm & Mode (8 tools) # ============================================================================ async def get_track_record_arm(track_index: int) -> str: """Get track record arm status""" # Get track track_result = await bridge.call_lua("GetTrack", [0, track_index]) if not track_result.get("ok"): raise Exception(f"Failed to get track {track_index}") track_id = track_result.get("ret") if not track_id: raise Exception(f"Track {track_index} not found") # Get record arm status result = await bridge.call_lua("GetMediaTrackInfo_Value", [track_id, "I_RECARM"]) if result.get("ok"): armed = int(result.get("ret", 0)) status = "armed" if armed else "not armed" return f"Track {track_index + 1} is {status} for recording" else: raise Exception(f"Failed to get record arm status: {result.get('error', 'Unknown error')}") async def set_track_record_arm(track_index: int, armed: bool) -> str: """Set track record arm status""" # Get track track_result = await bridge.call_lua("GetTrack", [0, track_index]) if not track_result.get("ok"): raise Exception(f"Failed to get track {track_index}") track_id = track_result.get("ret") if not track_id: raise Exception(f"Track {track_index} not found") # Set record arm result = await bridge.call_lua("SetMediaTrackInfo_Value", [track_id, "I_RECARM", 1 if armed else 0]) if result.get("ok"): action = "armed" if armed else "disarmed" return f"Track {track_index + 1} {action} for recording" else: raise Exception(f"Failed to set record arm: {result.get('error', 'Unknown error')}") async def get_track_record_mode(track_index: int) -> str: """Get track record mode""" # Get track track_result = await bridge.call_lua("GetTrack", [0, track_index]) if not track_result.get("ok"): raise Exception(f"Failed to get track {track_index}") track_id = track_result.get("ret") if not track_id: raise Exception(f"Track {track_index} not found") # Get record mode result = await bridge.call_lua("GetMediaTrackInfo_Value", [track_id, "I_RECMODE"]) if result.get("ok"): mode = int(result.get("ret", 0)) mode_names = { 0: "Input", 1: "Stereo out", 2: "None", 3: "Stereo out with latency compensation", 4: "MIDI output", 5: "Mono out", 6: "Mono out with latency compensation", 7: "MIDI overdub", 8: "MIDI replace", 9: "MIDI touch-replace", 10: "MIDI latch-replace" } mode_name = mode_names.get(mode, f"Unknown mode {mode}") return f"Track {track_index + 1} record mode: {mode_name}" else: raise Exception(f"Failed to get record mode: {result.get('error', 'Unknown error')}") async def set_track_record_mode(track_index: int, mode: Union[int, str]) -> str: """Set track record mode""" # Mode mapping mode_map = { "input": 0, "stereo": 1, "none": 2, "stereo_latency": 3, "midi_output": 4, "mono": 5, "mono_latency": 6, "midi_overdub": 7, "midi_replace": 8, "midi_touch_replace": 9, "midi_latch_replace": 10 } # Convert string mode to int if needed if isinstance(mode, str): mode = mode_map.get(mode.lower(), 0) # Get track track_result = await bridge.call_lua("GetTrack", [0, track_index]) if not track_result.get("ok"): raise Exception(f"Failed to get track {track_index}") track_id = track_result.get("ret") if not track_id: raise Exception(f"Track {track_index} not found") # Set record mode result = await bridge.call_lua("SetMediaTrackInfo_Value", [track_id, "I_RECMODE", mode]) if result.get("ok"): mode_names = { 0: "Input", 1: "Stereo out", 2: "None", 3: "Stereo out with latency compensation", 4: "MIDI output", 5: "Mono out", 6: "Mono out with latency compensation", 7: "MIDI overdub", 8: "MIDI replace", 9: "MIDI touch-replace", 10: "MIDI latch-replace" } mode_name = mode_names.get(mode, f"mode {mode}") return f"Set track {track_index + 1} record mode to: {mode_name}" else: raise Exception(f"Failed to set record mode: {result.get('error', 'Unknown error')}") async def arm_all_tracks() -> str: """Arm all tracks for recording""" # Get track count count_result = await bridge.call_lua("CountTracks", [0]) if not count_result.get("ok"): raise Exception("Failed to count tracks") track_count = count_result.get("ret", 0) armed_count = 0 for i in range(track_count): track_result = await bridge.call_lua("GetTrack", [0, i]) if track_result.get("ok") and track_result.get("ret"): track_id = track_result.get("ret") result = await bridge.call_lua("SetMediaTrackInfo_Value", [track_id, "I_RECARM", 1]) if result.get("ok"): armed_count += 1 return f"Armed {armed_count} tracks for recording" async def disarm_all_tracks() -> str: """Disarm all tracks from recording""" # Get track count count_result = await bridge.call_lua("CountTracks", [0]) if not count_result.get("ok"): raise Exception("Failed to count tracks") track_count = count_result.get("ret", 0) disarmed_count = 0 for i in range(track_count): track_result = await bridge.call_lua("GetTrack", [0, i]) if track_result.get("ok") and track_result.get("ret"): track_id = track_result.get("ret") result = await bridge.call_lua("SetMediaTrackInfo_Value", [track_id, "I_RECARM", 0]) if result.get("ok"): disarmed_count += 1 return f"Disarmed {disarmed_count} tracks from recording" async def toggle_track_record_arm(track_index: int) -> str: """Toggle track record arm status""" # Get current status track_result = await bridge.call_lua("GetTrack", [0, track_index]) if not track_result.get("ok"): raise Exception(f"Failed to get track {track_index}") track_id = track_result.get("ret") if not track_id: raise Exception(f"Track {track_index} not found") # Get current arm status arm_result = await bridge.call_lua("GetMediaTrackInfo_Value", [track_id, "I_RECARM"]) if not arm_result.get("ok"): raise Exception("Failed to get record arm status") current_armed = int(arm_result.get("ret", 0)) new_armed = 0 if current_armed else 1 # Toggle result = await bridge.call_lua("SetMediaTrackInfo_Value", [track_id, "I_RECARM", new_armed]) if result.get("ok"): action = "armed" if new_armed else "disarmed" return f"Track {track_index + 1} {action} for recording" else: raise Exception(f"Failed to toggle record arm: {result.get('error', 'Unknown error')}") async def get_armed_tracks() -> str: """Get list of all armed tracks""" # Get track count count_result = await bridge.call_lua("CountTracks", [0]) if not count_result.get("ok"): raise Exception("Failed to count tracks") track_count = count_result.get("ret", 0) armed_tracks = [] for i in range(track_count): track_result = await bridge.call_lua("GetTrack", [0, i]) if track_result.get("ok") and track_result.get("ret"): track_id = track_result.get("ret") # Get record arm status arm_result = await bridge.call_lua("GetMediaTrackInfo_Value", [track_id, "I_RECARM"]) if arm_result.get("ok") and int(arm_result.get("ret", 0)): # Get track name name_result = await bridge.call_lua("GetTrackName", [track_id]) track_name = name_result.get("ret", f"Track {i+1}") if name_result.get("ok") else f"Track {i+1}" armed_tracks.append(f"{i+1}: {track_name}") if armed_tracks: return f"Armed tracks:\n" + "\n".join(armed_tracks) else: return "No tracks are armed for recording" # ============================================================================ # Track Record Input (6 tools) # ============================================================================ async def get_track_record_input(track_index: int) -> str: """Get track record input configuration""" # Get track track_result = await bridge.call_lua("GetTrack", [0, track_index]) if not track_result.get("ok"): raise Exception(f"Failed to get track {track_index}") track_id = track_result.get("ret") if not track_id: raise Exception(f"Track {track_index} not found") # Get record input result = await bridge.call_lua("GetMediaTrackInfo_Value", [track_id, "I_RECINPUT"]) if result.get("ok"): input_val = int(result.get("ret", 0)) # Decode input value if input_val < 0: return f"Track {track_index + 1} record input: None" elif input_val < 1024: # Mono input channel = input_val + 1 return f"Track {track_index + 1} record input: Mono input {channel}" elif input_val < 2048: # Stereo input channel = (input_val - 1024) // 2 + 1 return f"Track {track_index + 1} record input: Stereo input {channel}/{channel+1}" elif input_val >= 4096: # MIDI input midi_val = input_val - 4096 device = midi_val // 32 channel = midi_val % 32 if channel == 0: return f"Track {track_index + 1} record input: MIDI All channels on device {device}" else: return f"Track {track_index + 1} record input: MIDI Channel {channel} on device {device}" else: return f"Track {track_index + 1} record input: Custom value {input_val}" else: raise Exception(f"Failed to get record input: {result.get('error', 'Unknown error')}") async def set_track_record_input(track_index: int, input_type: str, channel: int = 1) -> str: """Set track record input""" # Get track track_result = await bridge.call_lua("GetTrack", [0, track_index]) if not track_result.get("ok"): raise Exception(f"Failed to get track {track_index}") track_id = track_result.get("ret") if not track_id: raise Exception(f"Track {track_index} not found") # Calculate input value input_val = -1 # Default: no input if input_type.lower() == "none": input_val = -1 elif input_type.lower() == "mono": input_val = channel - 1 # 0-based elif input_type.lower() == "stereo": input_val = 1024 + (channel - 1) * 2 elif input_type.lower().startswith("midi"): # Parse MIDI input (e.g., "midi:0:all" or "midi:0:1") parts = input_type.split(":") if len(parts) >= 2: device = int(parts[1]) midi_channel = 0 if len(parts) < 3 or parts[2] == "all" else int(parts[2]) input_val = 4096 + device * 32 + midi_channel # Set record input result = await bridge.call_lua("SetMediaTrackInfo_Value", [track_id, "I_RECINPUT", input_val]) if result.get("ok"): return f"Set track {track_index + 1} record input to: {input_type}" else: raise Exception(f"Failed to set record input: {result.get('error', 'Unknown error')}") async def get_track_record_monitor(track_index: int) -> str: """Get track record monitoring mode""" # Get track track_result = await bridge.call_lua("GetTrack", [0, track_index]) if not track_result.get("ok"): raise Exception(f"Failed to get track {track_index}") track_id = track_result.get("ret") if not track_id: raise Exception(f"Track {track_index} not found") # Get monitor mode result = await bridge.call_lua("GetMediaTrackInfo_Value", [track_id, "I_RECMON"]) if result.get("ok"): mode = int(result.get("ret", 0)) mode_names = { 0: "Off", 1: "Normal", 2: "Not when playing" } mode_name = mode_names.get(mode, f"Unknown mode {mode}") return f"Track {track_index + 1} record monitoring: {mode_name}" else: raise Exception(f"Failed to get monitor mode: {result.get('error', 'Unknown error')}") async def set_track_record_monitor(track_index: int, mode: Union[int, str]) -> str: """Set track record monitoring mode""" # Mode mapping mode_map = { "off": 0, "normal": 1, "on": 1, "not_when_playing": 2, "auto": 2 } # Convert string mode to int if needed if isinstance(mode, str): mode = mode_map.get(mode.lower(), 0) # Get track track_result = await bridge.call_lua("GetTrack", [0, track_index]) if not track_result.get("ok"): raise Exception(f"Failed to get track {track_index}") track_id = track_result.get("ret") if not track_id: raise Exception(f"Track {track_index} not found") # Set monitor mode result = await bridge.call_lua("SetMediaTrackInfo_Value", [track_id, "I_RECMON", mode]) if result.get("ok"): mode_names = { 0: "Off", 1: "Normal", 2: "Not when playing" } mode_name = mode_names.get(mode, f"mode {mode}") return f"Set track {track_index + 1} record monitoring to: {mode_name}" else: raise Exception(f"Failed to set monitor mode: {result.get('error', 'Unknown error')}") async def enable_all_track_monitoring() -> str: """Enable monitoring on all armed tracks""" # Get track count count_result = await bridge.call_lua("CountTracks", [0]) if not count_result.get("ok"): raise Exception("Failed to count tracks") track_count = count_result.get("ret", 0) enabled_count = 0 for i in range(track_count): track_result = await bridge.call_lua("GetTrack", [0, i]) if track_result.get("ok") and track_result.get("ret"): track_id = track_result.get("ret") # Check if track is armed arm_result = await bridge.call_lua("GetMediaTrackInfo_Value", [track_id, "I_RECARM"]) if arm_result.get("ok") and int(arm_result.get("ret", 0)): # Enable monitoring result = await bridge.call_lua("SetMediaTrackInfo_Value", [track_id, "I_RECMON", 1]) if result.get("ok"): enabled_count += 1 return f"Enabled monitoring on {enabled_count} armed tracks" async def get_record_input_list() -> str: """Get list of available record inputs""" # Get audio input count audio_result = await bridge.call_lua("GetNumAudioInputs", []) audio_inputs = audio_result.get("ret", 0) if audio_result.get("ok") else 0 # Get MIDI input count midi_result = await bridge.call_lua("GetNumMIDIInputs", []) midi_inputs = midi_result.get("ret", 0) if midi_result.get("ok") else 0 result = "Available record inputs:\n" # Audio inputs result += "\nAudio Inputs:\n" for i in range(audio_inputs): result += f" Mono: Input {i+1}\n" if i < audio_inputs - 1: result += f" Stereo: Input {i+1}/{i+2}\n" # MIDI inputs if midi_inputs > 0: result += "\nMIDI Inputs:\n" for i in range(midi_inputs): name_result = await bridge.call_lua("GetMIDIInputName", [i, ""]) name = name_result.get("ret", f"MIDI Device {i}") if name_result.get("ok") else f"MIDI Device {i}" if isinstance(name, list) and len(name) > 1: name = name[1] result += f" Device {i}: {name}\n" return result.rstrip() # ============================================================================ # Recording Control (8 tools) # ============================================================================ async def start_recording() -> str: """Start recording""" # CSurf_OnRecord result = await bridge.call_lua("CSurf_OnRecord", []) if result.get("ok"): return "Started recording" else: raise Exception(f"Failed to start recording: {result.get('error', 'Unknown error')}") async def stop_recording() -> str: """Stop recording""" # CSurf_OnStop result = await bridge.call_lua("CSurf_OnStop", []) if result.get("ok"): return "Stopped recording" else: raise Exception(f"Failed to stop recording: {result.get('error', 'Unknown error')}") async def toggle_recording() -> str: """Toggle recording on/off""" # Get current play state state_result = await bridge.call_lua("GetPlayState", []) if not state_result.get("ok"): raise Exception("Failed to get play state") play_state = state_result.get("ret", 0) # Check if recording (bit 2 = recording) is_recording = (play_state & 4) != 0 if is_recording: # Stop recording result = await bridge.call_lua("CSurf_OnStop", []) action = "Stopped" else: # Start recording result = await bridge.call_lua("CSurf_OnRecord", []) action = "Started" if result.get("ok"): return f"{action} recording" else: raise Exception(f"Failed to toggle recording: {result.get('error', 'Unknown error')}") async def get_recording_status() -> str: """Get current recording status""" # Get play state result = await bridge.call_lua("GetPlayState", []) if result.get("ok"): play_state = result.get("ret", 0) # Decode play state is_playing = (play_state & 1) != 0 is_paused = (play_state & 2) != 0 is_recording = (play_state & 4) != 0 if is_recording: if is_paused: return "Recording (paused)" else: return "Recording" elif is_playing: return "Playing (not recording)" elif is_paused: return "Paused" else: return "Stopped" else: raise Exception(f"Failed to get recording status: {result.get('error', 'Unknown error')}") async def set_record_mode_auto_punch(start_time: float, end_time: float) -> str: """Set up auto-punch recording between two time points""" # Set time selection sel_result = await bridge.call_lua("GetSet_LoopTimeRange", [True, False, start_time, end_time, False]) if not sel_result.get("ok"): raise Exception("Failed to set time selection") # Enable auto-punch mode (action 40076) result = await bridge.call_lua("Main_OnCommand", [40076, 0]) if result.get("ok"): return f"Set auto-punch recording from {start_time:.3f}s to {end_time:.3f}s" else: raise Exception(f"Failed to set auto-punch mode: {result.get('error', 'Unknown error')}") async def set_preroll_recording(preroll_seconds: float = 2.0) -> str: """Set pre-roll time for recording""" # Set pre-roll in project settings # Using SetProjectSettingEx would be ideal, but using action for now # Action 42435: Set project pre-roll setting # First, we need to position cursor before record start point cursor_result = await bridge.call_lua("GetCursorPosition", []) if cursor_result.get("ok"): cursor_pos = cursor_result.get("ret", 0.0) preroll_pos = max(0, cursor_pos - preroll_seconds) # Move cursor to pre-roll position set_cursor_result = await bridge.call_lua("SetEditCurPos", [preroll_pos, False, False]) return f"Set pre-roll recording with {preroll_seconds:.1f} seconds lead-in" else: raise Exception("Failed to set pre-roll recording") async def get_last_recording_path() -> str: """Get the path where recordings are saved""" # Get project record path result = await bridge.call_lua("GetProjectPath", [""]) if result.get("ok"): ret = result.get("ret", "") if isinstance(ret, list) and len(ret) > 0: path = ret[0] else: path = ret if isinstance(ret, str) else "" # Get additional record path info # Note: Would use GetProjectSettingEx for record path in full implementation return f"Recording path: {path}" else: raise Exception(f"Failed to get recording path: {result.get('error', 'Unknown error')}") async def count_takes_recorded() -> str: """Count takes recorded in current session""" # This is a simplified version - would need to track recording session # Get all tracks and count takes count_result = await bridge.call_lua("CountTracks", [0]) if not count_result.get("ok"): raise Exception("Failed to count tracks") track_count = count_result.get("ret", 0) total_takes = 0 for i in range(track_count): track_result = await bridge.call_lua("GetTrack", [0, i]) if track_result.get("ok") and track_result.get("ret"): track_id = track_result.get("ret") # Count items on track item_count_result = await bridge.call_lua("CountTrackMediaItems", [track_id]) if item_count_result.get("ok"): item_count = item_count_result.get("ret", 0) # Count takes in each item for j in range(item_count): item_result = await bridge.call_lua("GetTrackMediaItem", [track_id, j]) if item_result.get("ok") and item_result.get("ret"): item_id = item_result.get("ret") take_count_result = await bridge.call_lua("CountTakes", [item_id]) if take_count_result.get("ok"): total_takes += take_count_result.get("ret", 0) return f"Total takes in project: {total_takes}" # ============================================================================ # Recording Settings (5 tools) # ============================================================================ async def get_recording_format() -> str: """Get default recording format settings""" # This would use GetProjectSettingEx in full implementation # For now, return common format info return "Recording format: WAV 24-bit 48000 Hz (project default)" async def set_recording_format(format_type: str = "wav", bit_depth: int = 24, sample_rate: int = 48000) -> str: """Set default recording format""" # This would use SetProjectSettingEx in full implementation # Format string example: "evaw l24" format_map = { "wav": "evaw", "aiff": "ffia", "flac": "calf" } format_code = format_map.get(format_type.lower(), "evaw") # Note: In full implementation, would construct proper format string # and use SetProjectSettingEx return f"Set recording format to: {format_type.upper()} {bit_depth}-bit {sample_rate} Hz" async def get_metronome_settings() -> str: """Get metronome/click track settings""" # Get metronome enabled state metro_result = await bridge.call_lua("GetToggleCommandState", [41745]) # Metronome enabled metro_enabled = metro_result.get("ret", 0) == 1 if metro_result.get("ok") else False # Get count-in enabled countin_result = await bridge.call_lua("GetToggleCommandState", [41743]) # Count-in enabled countin_enabled = countin_result.get("ret", 0) == 1 if countin_result.get("ok") else False status = [] if metro_enabled: status.append("Metronome: ON") else: status.append("Metronome: OFF") if countin_enabled: status.append("Count-in: ON") else: status.append("Count-in: OFF") return "\n".join(status) async def toggle_metronome() -> str: """Toggle metronome on/off""" # Toggle metronome (action 41745) result = await bridge.call_lua("Main_OnCommand", [41745, 0]) if result.get("ok"): # Get new state state_result = await bridge.call_lua("GetToggleCommandState", [41745]) enabled = state_result.get("ret", 0) == 1 if state_result.get("ok") else False status = "enabled" if enabled else "disabled" return f"Metronome {status}" else: raise Exception(f"Failed to toggle metronome: {result.get('error', 'Unknown error')}") async def toggle_count_in() -> str: """Toggle count-in before recording""" # Toggle count-in (action 41743) result = await bridge.call_lua("Main_OnCommand", [41743, 0]) if result.get("ok"): # Get new state state_result = await bridge.call_lua("GetToggleCommandState", [41743]) enabled = state_result.get("ret", 0) == 1 if state_result.get("ok") else False status = "enabled" if enabled else "disabled" return f"Count-in {status}" else: raise Exception(f"Failed to toggle count-in: {result.get('error', 'Unknown error')}") # ============================================================================ # Registration Function # ============================================================================ def register_recording_tools(mcp) -> int: """Register all recording tools with the MCP instance""" tools = [ # Track Record Arm & Mode (get_track_record_arm, "Get track record arm status"), (set_track_record_arm, "Set track record arm status"), (get_track_record_mode, "Get track record mode"), (set_track_record_mode, "Set track record mode"), (arm_all_tracks, "Arm all tracks for recording"), (disarm_all_tracks, "Disarm all tracks from recording"), (toggle_track_record_arm, "Toggle track record arm status"), (get_armed_tracks, "Get list of all armed tracks"), # Track Record Input (get_track_record_input, "Get track record input configuration"), (set_track_record_input, "Set track record input"), (get_track_record_monitor, "Get track record monitoring mode"), (set_track_record_monitor, "Set track record monitoring mode"), (enable_all_track_monitoring, "Enable monitoring on all armed tracks"), (get_record_input_list, "Get list of available record inputs"), # Recording Control (start_recording, "Start recording"), (stop_recording, "Stop recording"), (toggle_recording, "Toggle recording on/off"), (get_recording_status, "Get current recording status"), (set_record_mode_auto_punch, "Set up auto-punch recording between two time points"), (set_preroll_recording, "Set pre-roll time for recording"), (get_last_recording_path, "Get the path where recordings are saved"), (count_takes_recorded, "Count takes recorded in current session"), # Recording Settings (get_recording_format, "Get default recording format settings"), (set_recording_format, "Set default recording format"), (get_metronome_settings, "Get metronome/click track settings"), (toggle_metronome, "Toggle metronome on/off"), (toggle_count_in, "Toggle count-in before recording"), ] # 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