Skip to main content
Glama

REAPER MCP Server

by itsuzef
midi_tools.py11.3 kB
import reapy from reapy import reascript_api as RPR class MidiTools: """Tools for MIDI composition and editing in REAPER.""" def __init__(self, config): """ Initialize MidiTools with configuration. Args: config (dict): Configuration dictionary """ self.config = config # Define standard MIDI note mappings for drum patterns self.drum_mappings = { "kick": 36, # C1 "snare": 38, # D1 "hihat_closed": 42, # F#1 "hihat_open": 46, # A#1 "tom_low": 41, # F1 "tom_mid": 45, # A1 "tom_high": 48, # C2 "crash": 49, # C#2 "ride": 51 # D#2 } # Define chord mappings self.chord_types = { "maj": [0, 4, 7], "min": [0, 3, 7], "dim": [0, 3, 6], "aug": [0, 4, 8], "maj7": [0, 4, 7, 11], "min7": [0, 3, 7, 10], "dom7": [0, 4, 7, 10], "dim7": [0, 3, 6, 9], "hdim7": [0, 3, 6, 10], "sus2": [0, 2, 7], "sus4": [0, 5, 7] } # Note name to MIDI note number mapping self.note_to_number = { "C": 0, "C#": 1, "Db": 1, "D": 2, "D#": 3, "Eb": 3, "E": 4, "F": 5, "F#": 6, "Gb": 6, "G": 7, "G#": 8, "Ab": 8, "A": 9, "A#": 10, "Bb": 10, "B": 11 } def create_midi_item(self, track_id, start_position, length): """ Create a new MIDI item on the specified track. Args: track_id (int): Track ID start_position (float): Start position in seconds length (float): Length in seconds Returns: dict: MIDI item information """ try: # Get track by ID track = reapy.Track.from_id(track_id) # Create new MIDI item item = track.add_midi_item(start_position, start_position + length) # Get the MIDI take take = item.active_take return { "success": True, "item_id": item.id, "take_id": take.id, "position": item.position, "length": item.length, "track_id": track_id } except Exception as e: return { "success": False, "error": str(e) } def add_midi_note(self, item_id, pitch, start, length, velocity=100): """ Add a MIDI note to the specified MIDI item. Args: item_id (int): MIDI item ID pitch (int): MIDI note number (0-127) start (float): Start position in seconds relative to item start length (float): Note length in seconds velocity (int): Note velocity (1-127) Returns: dict: Result of the operation """ try: # Get item by ID item = reapy.Item.from_id(item_id) # Get the active take take = item.active_take # Ensure take is MIDI if not take.is_midi: return { "success": False, "error": "Take is not MIDI" } # Add MIDI note # Note: start is relative to item start take.add_note( start=start, end=start + length, pitch=pitch, velocity=velocity, channel=0 # Default MIDI channel ) return { "success": True, "item_id": item_id, "pitch": pitch, "start": start, "length": length, "velocity": velocity } except Exception as e: return { "success": False, "error": str(e) } def _parse_chord(self, chord_str): """ Parse a chord string into MIDI note numbers. Args: chord_str (str): Chord string (e.g., "C", "Dm", "G7") Returns: list: List of MIDI note numbers relative to root """ # Extract root note and chord type if len(chord_str) == 1: root = chord_str chord_type = "maj" elif len(chord_str) >= 2: if chord_str[1] in ["#", "b"]: root = chord_str[0:2] chord_type = chord_str[2:] or "maj" else: root = chord_str[0] chord_type = chord_str[1:] or "maj" # Map chord type to intervals if chord_type == "m": chord_type = "min" elif chord_type == "7": chord_type = "dom7" # Get intervals for chord type intervals = self.chord_types.get(chord_type, self.chord_types["maj"]) # Get root note number root_num = self.note_to_number.get(root, 0) # Return intervals (will be transposed in the calling function) return intervals, root_num def create_chord_progression(self, track_id, chords, start_position, beats_per_chord=4): """ Create a chord progression on the specified track. Args: track_id (int): Track ID chords (str): Comma-separated list of chords (e.g., "C,G,Am,F") start_position (float): Start position in seconds beats_per_chord (int): Number of beats per chord Returns: dict: Result of the operation """ try: # Get project and track project = reapy.Project() track = reapy.Track.from_id(track_id) # Parse chord list chord_list = [c.strip() for c in chords.split(",")] # Calculate seconds per beat seconds_per_beat = 60.0 / project.bpm chord_length = seconds_per_beat * beats_per_chord # Create MIDI item for the entire progression total_length = chord_length * len(chord_list) item = track.add_midi_item(start_position, start_position + total_length) take = item.active_take # Add chords added_chords = [] for i, chord_str in enumerate(chord_list): try: # Parse chord intervals, root_num = self._parse_chord(chord_str) # Calculate position within item chord_start = i * chord_length # Add chord notes (in middle C octave) for interval in intervals: note_num = 60 + root_num + interval # Middle C (60) + root + interval take.add_note( start=chord_start, end=chord_start + chord_length * 0.95, # Slightly shorter than full length pitch=note_num, velocity=80, channel=0 ) added_chords.append({ "chord": chord_str, "position": chord_start, "length": chord_length }) except Exception as e: # Continue with next chord if one fails print(f"Error adding chord {chord_str}: {e}") return { "success": True, "item_id": item.id, "chords": added_chords, "start_position": start_position, "total_length": total_length } except Exception as e: return { "success": False, "error": str(e) } def create_drum_pattern(self, track_id, pattern, start_position, beats=4, repeats=1): """ Create a drum pattern on the specified track. Args: track_id (int): Track ID pattern (str): Drum pattern string (e.g., "k...k...s...k.s.") k=kick, s=snare, h=hihat_closed, o=hihat_open, t=tom_low, m=tom_mid, f=tom_high, c=crash, r=ride start_position (float): Start position in seconds beats (int): Number of beats in the pattern repeats (int): Number of times to repeat the pattern Returns: dict: Result of the operation """ try: # Get project and track project = reapy.Project() track = reapy.Track.from_id(track_id) # Calculate timing seconds_per_beat = 60.0 / project.bpm pattern_length = seconds_per_beat * beats total_length = pattern_length * repeats # Create MIDI item item = track.add_midi_item(start_position, start_position + total_length) take = item.active_take # Map pattern characters to drum notes char_to_drum = { "k": "kick", "s": "snare", "h": "hihat_closed", "o": "hihat_open", "t": "tom_low", "m": "tom_mid", "f": "tom_high", "c": "crash", "r": "ride" } # Calculate time per character time_per_char = pattern_length / len(pattern) # Add notes for each repeat for repeat in range(repeats): offset = repeat * pattern_length for i, char in enumerate(pattern): if char in char_to_drum: drum_name = char_to_drum[char] note_num = self.drum_mappings[drum_name] # Position within the pattern note_start = offset + (i * time_per_char) # Add note (short duration for drums) take.add_note( start=note_start, end=note_start + (time_per_char * 0.5), pitch=note_num, velocity=100, channel=9 # Standard MIDI drum channel ) return { "success": True, "item_id": item.id, "pattern": pattern, "repeats": repeats, "start_position": start_position, "total_length": total_length } except Exception as e: return { "success": False, "error": str(e) }

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/itsuzef/reaper-mcp'

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