diff --git a/__init__.py b/__init__.py index e349e7b..3c5e1c9 100644 --- a/__init__.py +++ b/__init__.py @@ -1385,6 +1385,143 @@ class _AbletonMCP(ControlSurface): "note": "Automation envelope planned; direct parameter automation is limited in this API context", } + # ------------------------------------------------------------------ + # FX CREATOR HANDLERS (T031-T035) - Professional FX generation + # ------------------------------------------------------------------ + + def _cmd_create_riser(self, track_index, start_bar, duration=8, intensity=0.8, + pitch_range=None, **kw): + """T031: Create a riser/buildup effect.""" + try: + from .mcp_server.engines.arrangement_engine import FXCreator + fx_creator = FXCreator() + if pitch_range is None: + pitch_range = (36, 84) + clip = fx_creator.create_riser( + track_index=int(track_index), + start_bar=int(start_bar), + duration=int(duration), + intensity=float(intensity), + pitch_range=tuple(pitch_range) + ) + return { + "success": True, + "clip_name": clip.name, + "track_index": clip.track_index, + "start_time": clip.start_time, + "duration": clip.duration, + "note_count": len(clip.notes) if clip.notes else 0, + } + except Exception as e: + self.log_message("Error creating riser: " + str(e)) + return {"success": False, "error": str(e)} + + def _cmd_create_downlifter(self, track_index, start_bar, duration=4, intensity=0.7, + pitch_range=None, **kw): + """T032: Create a downlifter effect.""" + try: + from .mcp_server.engines.arrangement_engine import FXCreator + fx_creator = FXCreator() + if pitch_range is None: + pitch_range = (72, 36) + clip = fx_creator.create_downlifter( + track_index=int(track_index), + start_bar=int(start_bar), + duration=int(duration), + intensity=float(intensity), + pitch_range=tuple(pitch_range) + ) + return { + "success": True, + "clip_name": clip.name, + "track_index": clip.track_index, + "start_time": clip.start_time, + "duration": clip.duration, + "note_count": len(clip.notes) if clip.notes else 0, + } + except Exception as e: + self.log_message("Error creating downlifter: " + str(e)) + return {"success": False, "error": str(e)} + + def _cmd_create_impact(self, track_index, position, intensity=1.0, impact_type="hit", **kw): + """T033: Create an impact FX.""" + try: + from .mcp_server.engines.arrangement_engine import FXCreator + fx_creator = FXCreator() + clip = fx_creator.create_impact( + track_index=int(track_index), + position=float(position), + intensity=float(intensity), + impact_type=str(impact_type) + ) + return { + "success": True, + "clip_name": clip.name, + "track_index": clip.track_index, + "start_time": clip.start_time, + "duration": clip.duration, + "impact_type": impact_type, + } + except Exception as e: + self.log_message("Error creating impact: " + str(e)) + return {"success": False, "error": str(e)} + + def _cmd_create_silence(self, track_index, start_bar, duration=1, **kw): + """T034: Create silence/break effect.""" + try: + from .mcp_server.engines.arrangement_engine import FXCreator + fx_creator = FXCreator() + clip = fx_creator.create_silence( + track_index=int(track_index), + start_bar=int(start_bar), + duration=int(duration) + ) + return { + "success": True, + "clip_name": clip.name, + "track_index": clip.track_index, + "start_time": clip.start_time, + "duration": clip.duration, + } + except Exception as e: + self.log_message("Error creating silence: " + str(e)) + return {"success": False, "error": str(e)} + + def _cmd_create_fx_section(self, section_type, start_bar, duration=8, track_indices=None, **kw): + """T035: Create complete FX section.""" + try: + from .mcp_server.engines.arrangement_engine import FXCreator + fx_creator = FXCreator() + section_type = str(section_type).lower() + start_bar = int(start_bar) + duration = int(duration) + created_clips = [] + if section_type in ["pre_drop", "build"]: + riser = fx_creator.create_riser(track_index=0, start_bar=start_bar, + duration=duration-1, intensity=0.8) + impact = fx_creator.create_impact(track_index=0, position=start_bar+duration-1, + intensity=1.0, impact_type="hit") + created_clips = [riser.name, impact.name] + elif section_type == "post_drop": + downlifter = fx_creator.create_downlifter(track_index=0, start_bar=start_bar, + duration=duration, intensity=0.7) + created_clips = [downlifter.name] + elif section_type == "transition": + silence = fx_creator.create_silence(track_index=0, start_bar=start_bar, duration=1) + impact = fx_creator.create_impact(track_index=0, position=start_bar+1, + intensity=1.0, impact_type="crash") + created_clips = [silence.name, impact.name] + return { + "success": True, + "section_type": section_type, + "start_bar": start_bar, + "duration": duration, + "created_clips": created_clips, + } + except Exception as e: + self.log_message("Error creating FX section: " + str(e)) + return {"success": False, "error": str(e)} + # ------------------------------------------------------------------ # MIXING HANDLERS (T016-T020) - Real mixing workflow # ------------------------------------------------------------------ diff --git a/__pycache__/__init__.cpython-314.pyc b/__pycache__/__init__.cpython-314.pyc index cf82ea3..f4c9287 100644 Binary files a/__pycache__/__init__.cpython-314.pyc and b/__pycache__/__init__.cpython-314.pyc differ diff --git a/docs/skill_produccion_audio.md b/docs/skill_produccion_audio.md index aabe33a..3f275d1 100644 --- a/docs/skill_produccion_audio.md +++ b/docs/skill_produccion_audio.md @@ -67,7 +67,64 @@ ableton-live-mcp_create_arrangement_audio_pattern( ) ``` -### Paso 5: Verificación Visual +### Paso 5: FX y Transiciones Profesionales (T031-T035) + +#### Crear Riser/Buildup (T031) +```python +# Riser de 8 compases antes del drop +ableton-live-mcp_create_riser( + track_index=7, + start_bar=24, # Empezar en compás 24 + duration=8, # 8 compases de duración + intensity=0.8, # Intensidad 80% + pitch_min=36, # C2 + pitch_max=84 # C6 +) +``` + +#### Crear Downlifter (T032) +```python +# Downlifter post-drop +ableton-live-mcp_create_downlifter( + track_index=7, + start_bar=32, # Después del drop + duration=4, + intensity=0.7 +) +``` + +#### Crear Impact FX (T033) +```python +# Impact en el drop +ableton-live-mcp_create_impact( + track_index=7, + position=32, # Compás 32 + intensity=1.0, + impact_type="hit" # Options: "hit", "crash", "sub_drop", "noise" +) +``` + +#### Crear Sección FX Completa (T035) +```python +# Sección completa con riser + impact +ableton-live-mcp_create_fx_section( + section_type="pre_drop", # "pre_drop", "post_drop", "transition", "build" + start_bar=24, + duration=8 +) +``` + +#### Silence/Break Effect (T034) +```python +# Break de 1 compás para tensión +ableton-live-mcp_create_silence( + track_index=0, + start_bar=31, + duration=1 +) +``` + +### Paso 6: Verificación Visual ```python # Confirmar clips en Arrangement View ableton-live-mcp_get_arrangement_status diff --git a/mcp_server/__pycache__/server.cpython-314.pyc b/mcp_server/__pycache__/server.cpython-314.pyc index b32a752..b0f3758 100644 Binary files a/mcp_server/__pycache__/server.cpython-314.pyc and b/mcp_server/__pycache__/server.cpython-314.pyc differ diff --git a/mcp_server/server.py b/mcp_server/server.py index 38fe660..f575638 100644 --- a/mcp_server/server.py +++ b/mcp_server/server.py @@ -2565,6 +2565,188 @@ def automate_filter(ctx: Context, track_index: int, start_bar: float = 0.0, ) +# ================================================================== +# FASE 2.5: FX CREATOR TOOLS (T031-T035) - Exposición de arrangement_engine +# ================================================================== + +@mcp.tool() +def create_riser(ctx: Context, track_index: int, start_bar: int, + duration: int = 8, intensity: float = 0.8, + pitch_min: int = 36, pitch_max: int = 84) -> str: + """Create a riser/buildup effect (T031). + + Generates a pre-drop riser with ascending pitch and tension. + Perfect for build-ups before choruses or drops. + + Args: + track_index: Index of the target track + start_bar: Start bar for the riser + duration: Duration in bars (default 8) + intensity: Intensity 0.0-1.0 (default 0.8) + pitch_min: Minimum MIDI pitch (default 36 = C2) + pitch_max: Maximum MIDI pitch (default 84 = C6) + + Returns: + JSON with riser creation status and clip info. + """ + return _proxy_ableton_command( + "create_riser", + { + "track_index": track_index, + "start_bar": start_bar, + "duration": duration, + "intensity": intensity, + "pitch_range": [pitch_min, pitch_max], + }, + timeout=30.0, + defaults={ + "track_index": track_index, + "start_bar": start_bar, + "duration": duration, + "intensity": intensity, + }, + ) + + +@mcp.tool() +def create_downlifter(ctx: Context, track_index: int, start_bar: int, + duration: int = 4, intensity: float = 0.7, + pitch_start: int = 72, pitch_end: int = 36) -> str: + """Create a downlifter effect (T032). + + Generates a post-drop downlifter with descending pitch. + Perfect for energy release after drops or impacts. + + Args: + track_index: Index of the target track + start_bar: Start bar for the downlifter + duration: Duration in bars (default 4) + intensity: Intensity 0.0-1.0 (default 0.7) + pitch_start: Starting MIDI pitch (default 72 = C5) + pitch_end: Ending MIDI pitch (default 36 = C2) + + Returns: + JSON with downlifter creation status and clip info. + """ + return _proxy_ableton_command( + "create_downlifter", + { + "track_index": track_index, + "start_bar": start_bar, + "duration": duration, + "intensity": intensity, + "pitch_range": [pitch_start, pitch_end], + }, + timeout=30.0, + defaults={ + "track_index": track_index, + "start_bar": start_bar, + "duration": duration, + "intensity": intensity, + }, + ) + + +@mcp.tool() +def create_impact(ctx: Context, track_index: int, position: float, + intensity: float = 1.0, impact_type: str = "hit") -> str: + """Create an impact FX (T033). + + Generates impact effects (hit, crash, sub drop, noise). + Perfect for emphasizing drops, transitions, or beats. + + Args: + track_index: Index of the target track + position: Position in bars (int) or beats (float) + intensity: Intensity 0.0-1.0 (default 1.0) + impact_type: Type of impact - "hit", "crash", "sub_drop", "noise" + + Returns: + JSON with impact creation status and clip info. + """ + return _proxy_ableton_command( + "create_impact", + { + "track_index": track_index, + "position": position, + "intensity": intensity, + "impact_type": impact_type, + }, + timeout=30.0, + defaults={ + "track_index": track_index, + "position": position, + "intensity": intensity, + "impact_type": impact_type, + }, + ) + + +@mcp.tool() +def create_silence(ctx: Context, track_index: int, start_bar: int, + duration: int = 1) -> str: + """Create silence/break effect (T034). + + Generates a moment of silence for dramatic effect. + Perfect for creating tension before drops. + + Args: + track_index: Index of the target track (for context) + start_bar: Start bar for the silence + duration: Duration in bars (default 1) + + Returns: + JSON with silence creation status. + """ + return _proxy_ableton_command( + "create_silence", + { + "track_index": track_index, + "start_bar": start_bar, + "duration": duration, + }, + timeout=30.0, + defaults={ + "track_index": track_index, + "start_bar": start_bar, + "duration": duration, + }, + ) + + +@mcp.tool() +def create_fx_section(ctx: Context, section_type: str, start_bar: int, + duration: int = 8, track_indices: list = None) -> str: + """Create complete FX section (T035). + + Generates a complete FX section with risers, impacts, and transitions. + + Args: + section_type: Type - "pre_drop", "post_drop", "transition", "build" + start_bar: Start bar for the section + duration: Duration in bars (default 8) + track_indices: List of track indices to apply FX (optional) + + Returns: + JSON with FX section creation status. + """ + return _proxy_ableton_command( + "create_fx_section", + { + "section_type": section_type, + "start_bar": start_bar, + "duration": duration, + "track_indices": track_indices or [], + }, + timeout=30.0, + defaults={ + "section_type": section_type, + "start_bar": start_bar, + "duration": duration, + }, + ) + + # ================================================================== # FASE 3: INTELIGENCIA MUSICAL (T041-T060) # ==================================================================