Skip to main content
Glama
test_non_interactive_workflows.py19 kB
"""Non-interactive integration tests that avoid dialogs and popups.""" import pytest import asyncio import time class TestNonInteractiveLoopWorkflows: """Test loop-based workflows without UI interaction.""" @pytest.mark.asyncio async def test_automated_loop_composition(self, reaper_mcp_client): """Test creating a complete loop-based composition programmatically.""" # Create project structure tracks = {} instruments = ["Drums", "Bass", "Lead", "Pad"] for idx, inst in enumerate(instruments): result = await reaper_mcp_client.call_tool("insert_track", { "index": idx, "name": inst }) tracks[inst] = idx # Set up 8-bar loop await reaper_mcp_client.call_tool("set_time_selection", { "start": 0.0, "end": 16.0 # 8 bars at 120 BPM }) # Generate rhythmic patterns await reaper_mcp_client.call_tool("generate_random_rhythm", { "track_index": tracks["Drums"], "pattern_length": 16.0, "density": 0.7, "note_length": 0.25 }) # Create bass pattern with lower density await reaper_mcp_client.call_tool("generate_random_rhythm", { "track_index": tracks["Bass"], "pattern_length": 16.0, "density": 0.3, "note_length": 0.5 }) # Apply humanization await reaper_mcp_client.call_tool("select_all_items", {}) await reaper_mcp_client.call_tool("humanize_items", { "position_amount": 0.02, "velocity_amount": 15, "timing_mode": "random" }) # Duplicate to create arrangement await reaper_mcp_client.call_tool("duplicate_time_selection", { "count": 3 }) # Verify project length length_result = await reaper_mcp_client.call_tool("get_project_length", {}) assert length_result.get("length") >= 64.0 @pytest.mark.asyncio async def test_tempo_adaptive_generation(self, reaper_mcp_client): """Test generating patterns that adapt to tempo changes.""" # Create track track = await reaper_mcp_client.call_tool("insert_track", { "index": 0, "name": "Adaptive Pattern" }) # Set initial tempo (this uses existing transport controls) await reaper_mcp_client.call_tool("set_edit_cursor_position", { "position": 0.0 }) # Generate pattern at current tempo await reaper_mcp_client.call_tool("generate_random_rhythm", { "track_index": 0, "pattern_length": 8.0, "density": 0.5, "note_length": 0.25 }) # Apply different quantization strengths await reaper_mcp_client.call_tool("select_all_items", {}) await reaper_mcp_client.call_tool("quantize_items_to_grid", { "strength": 0.8, "swing": 0.2 }) # Create variations await reaper_mcp_client.call_tool("set_time_selection", { "start": 0.0, "end": 8.0 }) for i in range(3): await reaper_mcp_client.call_tool("duplicate_time_selection", { "count": 1 }) # Shift time selection forward await reaper_mcp_client.call_tool("shift_time_selection", { "offset": 8.0 }) # Apply different shuffle to each section await reaper_mcp_client.call_tool("apply_shuffle", { "amount": 0.1 + (i * 0.1), "pattern": "16th" }) class TestNonInteractiveMixingWorkflows: """Test mixing workflows without dialogs.""" @pytest.mark.asyncio async def test_automated_stem_mixing(self, reaper_mcp_client): """Test creating and processing stems without rendering dialogs.""" # Create multi-track session track_groups = { "Drums": ["Kick", "Snare", "HiHat"], "Bass": ["Bass DI"], "Keys": ["Piano", "Synth"] } all_tracks = {} track_idx = 0 for group, tracks in track_groups.items(): group_tracks = [] for track_name in tracks: result = await reaper_mcp_client.call_tool("insert_track", { "index": track_idx, "name": track_name }) group_tracks.append(track_idx) all_tracks[track_name] = track_idx track_idx += 1 track_groups[group] = group_tracks # Create stems without rendering stems = await reaper_mcp_client.call_tool("create_stem_buses", { "stem_groups": track_groups }) # Add processing to stems for stem in stems.get("stems_created", []): # Add EQ await reaper_mcp_client.call_tool("track_fx_add_by_name", { "track_index": stem["bus_index"], "fx_name": "ReaEQ", "instantiate": -1 }) # Add compression await reaper_mcp_client.call_tool("track_fx_add_by_name", { "track_index": stem["bus_index"], "fx_name": "ReaComp", "instantiate": -1 }) # Create parallel compression for drums drum_stem = next(s for s in stems.get("stems_created", []) if s["name"] == "Drums") pc_result = await reaper_mcp_client.call_tool("create_parallel_compression_bus", { "source_track_indices": [drum_stem["bus_index"]], "bus_name": "Drum Crush", "blend_amount_db": -10.0 }) # Verify routing routing = await reaper_mcp_client.call_tool("analyze_routing_matrix", {}) assert routing.get("track_count") >= track_idx + len(track_groups) + 1 @pytest.mark.asyncio async def test_automated_fx_processing(self, reaper_mcp_client): """Test applying effects and processing without dialogs.""" # Create tracks tracks = [] for i in range(4): result = await reaper_mcp_client.call_tool("insert_track", { "index": i, "name": f"Track {i+1}" }) tracks.append(i) # Add effects programmatically fx_chains = [ ["ReaEQ", "ReaComp"], ["ReaDelay", "ReaVerbate"], ["ReaPitch", "ReaFir"], ["ReaGate", "ReaLimit"] ] for track_idx, fx_chain in enumerate(fx_chains): for fx in fx_chain: await reaper_mcp_client.call_tool("track_fx_add_by_name", { "track_index": track_idx, "fx_name": fx, "instantiate": -1 }) # Create sends between tracks await reaper_mcp_client.call_tool("create_track_send", { "source_track_index": 0, "dest_track_index": 1 }) # Set up sidechain routing await reaper_mcp_client.call_tool("create_sidechain_routing", { "source_track_index": 0, "destination_track_index": 2, "channel_offset": 2 }) # Bypass/unbypass effects for track_idx in tracks: # Get FX count fx_count = await reaper_mcp_client.call_tool("track_fx_get_count", { "track_index": track_idx }) # Toggle bypass on first effect if fx_count.get("count", 0) > 0: await reaper_mcp_client.call_tool("track_fx_set_enabled", { "track_index": track_idx, "fx_index": 0, "enabled": False }) await asyncio.sleep(0.1) await reaper_mcp_client.call_tool("track_fx_set_enabled", { "track_index": track_idx, "fx_index": 0, "enabled": True }) class TestNonInteractiveAutomation: """Test automation workflows without dialogs.""" @pytest.mark.asyncio async def test_automated_envelope_creation(self, reaper_mcp_client): """Test creating and manipulating envelopes programmatically.""" # Create track track = await reaper_mcp_client.call_tool("insert_track", { "index": 0, "name": "Automated Track" }) # Get volume envelope vol_env = await reaper_mcp_client.call_tool("get_track_envelope", { "track_index": 0, "envelope_name": "Volume" }) if not vol_env.get("envelope"): # Create visible volume envelope await reaper_mcp_client.call_tool("main_on_command", { "command": 40406 # Track: Toggle track volume envelope visible }) vol_env = await reaper_mcp_client.call_tool("get_track_envelope", { "track_index": 0, "envelope_name": "Volume" }) # Add automation points if vol_env.get("envelope"): env_handle = vol_env.get("envelope") # Create fade in for i in range(5): time = i * 2.0 value = i / 4.0 # 0 to 1 await reaper_mcp_client.call_tool("insert_envelope_point", { "envelope": env_handle, "time": time, "value": value, "shape": 0, "tension": 0.0 }) # Create fade out for i in range(5): time = 10.0 + (i * 2.0) value = 1.0 - (i / 4.0) # 1 to 0 await reaper_mcp_client.call_tool("insert_envelope_point", { "envelope": env_handle, "time": time, "value": value, "shape": 0, "tension": 0.0 }) @pytest.mark.asyncio async def test_tempo_automation(self, reaper_mcp_client): """Test creating tempo changes programmatically.""" # Enable tempo envelope await reaper_mcp_client.call_tool("main_on_command", { "command": 41138 # View: Show tempo envelope }) # Get tempo envelope from master track master = await reaper_mcp_client.call_tool("get_master_track", {}) tempo_env = await reaper_mcp_client.call_tool("get_track_envelope", { "track_index": -1, # Master track "envelope_name": "Tempo" }) if tempo_env.get("envelope"): env_handle = tempo_env.get("envelope") # Create tempo ramp tempos = [120, 140, 160, 140, 120] for i, bpm in enumerate(tempos): time = i * 8.0 # Every 8 seconds # Normalize BPM to envelope value (0-1 range) value = (bpm - 60) / 200 # Assuming 60-260 BPM range await reaper_mcp_client.call_tool("insert_envelope_point", { "envelope": env_handle, "time": time, "value": value, "shape": 1, # Linear "tension": 0.0 }) class TestNonInteractiveProjectManagement: """Test project management without save dialogs.""" @pytest.mark.asyncio async def test_track_versions_workflow(self, reaper_mcp_client): """Test creating track versions using takes.""" # Create track track = await reaper_mcp_client.call_tool("insert_track", { "index": 0, "name": "Multi-version Track" }) # Create multiple items as "versions" for version in range(3): # Add item item = await reaper_mcp_client.call_tool("add_media_item", { "track_index": 0, "position": 0.0, "length": 8.0 }) # Name the take if item.get("item"): take = await reaper_mcp_client.call_tool("get_active_take", { "item": item.get("item") }) if take.get("take"): await reaper_mcp_client.call_tool("set_take_name", { "take": take.get("take"), "name": f"Version {version + 1}" }) # Comp between versions (select different takes) item = await reaper_mcp_client.call_tool("get_media_item", { "project": 0, "item_index": 0 }) if item.get("item"): # Cycle through takes for i in range(3): await reaper_mcp_client.call_tool("set_active_take", { "item": item.get("item"), "take_index": i }) await asyncio.sleep(0.1) @pytest.mark.asyncio async def test_marker_based_navigation(self, reaper_mcp_client): """Test using markers for navigation without dialogs.""" # Create markers at key positions markers = [ {"time": 0.0, "name": "Intro", "color": 0xFF0000}, {"time": 16.0, "name": "Verse 1", "color": 0x00FF00}, {"time": 32.0, "name": "Chorus", "color": 0x0000FF}, {"time": 48.0, "name": "Verse 2", "color": 0x00FF00}, {"time": 64.0, "name": "Chorus 2", "color": 0x0000FF}, {"time": 80.0, "name": "Outro", "color": 0xFF00FF} ] for marker in markers: await reaper_mcp_client.call_tool("add_project_marker", { "project": 0, "is_region": False, "position": marker["time"], "region_end": 0, "name": marker["name"], "marker_index": -1, "color": marker["color"] }) # Navigate between markers for i in range(len(markers)): # Go to marker await reaper_mcp_client.call_tool("go_to_marker", { "project": 0, "marker_index": i }) # Get cursor position to verify pos = await reaper_mcp_client.call_tool("get_cursor_position", {}) expected_pos = markers[i]["time"] assert abs(pos.get("position", 0) - expected_pos) < 0.01 @pytest.mark.asyncio async def test_region_based_looping(self, reaper_mcp_client): """Test creating regions for loop sections.""" # Create regions for song sections regions = [ {"start": 0.0, "end": 16.0, "name": "Intro Loop"}, {"start": 16.0, "end": 48.0, "name": "Verse Loop"}, {"start": 48.0, "end": 80.0, "name": "Chorus Loop"} ] for region in regions: await reaper_mcp_client.call_tool("add_project_marker", { "project": 0, "is_region": True, "position": region["start"], "region_end": region["end"], "name": region["name"], "marker_index": -1 }) # Set time selection to each region for region in regions: await reaper_mcp_client.call_tool("set_time_selection", { "start": region["start"], "end": region["end"] }) # Enable looping await reaper_mcp_client.call_tool("set_loop_enabled", { "enabled": True }) # Could start playback here if needed # await reaper_mcp_client.call_tool("transport_play", {}) # await asyncio.sleep(2) # await reaper_mcp_client.call_tool("transport_stop", {}) class TestNonInteractiveMIDIGeneration: """Test MIDI generation without dialogs.""" @pytest.mark.asyncio async def test_chord_progression_generation(self, reaper_mcp_client): """Test generating chord progressions programmatically.""" # Create track for chords track = await reaper_mcp_client.call_tool("insert_track", { "index": 0, "name": "Chord Progression" }) # Define chord progression (C-Am-F-G) chords = [ {"root": 60, "notes": [60, 64, 67]}, # C major {"root": 57, "notes": [57, 60, 64]}, # A minor {"root": 65, "notes": [65, 69, 72]}, # F major {"root": 67, "notes": [67, 71, 74]} # G major ] # Generate chord progression for i, chord in enumerate(chords): position = i * 4.0 # Each chord lasts 2 bars # Create MIDI item item = await reaper_mcp_client.call_tool("add_media_item", { "track_index": 0, "position": position, "length": 4.0 }) # Would need CreateNewMIDIItemInProj to add actual notes # For now, we create the structure @pytest.mark.asyncio async def test_polyrhythmic_generation(self, reaper_mcp_client): """Test generating complex polyrhythms without interaction.""" # Create tracks for polyrhythm tracks = [] ratios = [3, 4, 5, 7] # Complex polyrhythm for i, ratio in enumerate(ratios): result = await reaper_mcp_client.call_tool("insert_track", { "index": i, "name": f"{ratio}-beat pattern" }) tracks.append(i) # Generate polyrhythmic patterns await reaper_mcp_client.call_tool("create_polyrhythm", { "track_indices": tracks, "base_division": 0.25, "ratios": ratios }) # Set loop for one complete cycle cycle_length = 16.0 # LCM-based cycle await reaper_mcp_client.call_tool("set_time_selection", { "start": 0.0, "end": cycle_length }) # Apply humanization to make it more musical await reaper_mcp_client.call_tool("select_all_items", {}) await reaper_mcp_client.call_tool("humanize_items", { "position_amount": 0.005, "velocity_amount": 10, "timing_mode": "random" })

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