""" Preset System - Sistema de Presets y Templates para AbletonMCP_AI (T061-T065) Gestión completa de presets para reggaeton: predefinidos, personalizados, importación/exportación, y aplicación a proyectos. """ import json import logging import os from dataclasses import dataclass, field, asdict from datetime import datetime from pathlib import Path from typing import Any, Dict, List, Optional, Union logger = logging.getLogger("PresetSystem") PRESETS_DIR = Path(r"C:\ProgramData\Ableton\Live 12 Suite\Resources\MIDI Remote Scripts\AbletonMCP_AI\presets") # ============================================================================= # DATACLASSES # ============================================================================= @dataclass class TrackPreset: """Configuración de preset para una pista individual.""" name: str track_type: str # "midi" o "audio" role: str sample_criteria: Dict[str, Any] = field(default_factory=dict) device_chain: List[Dict[str, Any]] = field(default_factory=list) volume: float = 0.8 pan: float = 0.0 mute: bool = False solo: bool = False color: int = 0 def to_dict(self) -> Dict[str, Any]: return asdict(self) @classmethod def from_dict(cls, data: Dict[str, Any]) -> "TrackPreset": return cls(**data) @dataclass class MixingConfig: """Configuración de mezcla para un preset.""" eq_low_gain: float = 0.0 eq_mid_gain: float = 0.0 eq_high_gain: float = 0.0 compressor_threshold: float = -6.0 compressor_ratio: float = 3.0 compressor_makeup: float = 3.0 send_reverb: float = 0.3 send_delay: float = 0.2 master_volume: float = 0.85 def to_dict(self) -> Dict[str, Any]: return asdict(self) @classmethod def from_dict(cls, data: Dict[str, Any]) -> "MixingConfig": return cls(**data) @dataclass class SampleSelectionCriteria: """Criterios de selección de samples para un preset.""" preferred_packs: List[str] = field(default_factory=list) excluded_packs: List[str] = field(default_factory=list) min_bpm: float = 0.0 max_bpm: float = 0.0 preferred_key: str = "" use_similarity_selection: bool = False similarity_reference: str = "" priority_roles: List[str] = field(default_factory=lambda: ["kick", "snare", "bass", "hat_closed"]) def to_dict(self) -> Dict[str, Any]: return asdict(self) @classmethod def from_dict(cls, data: Dict[str, Any]) -> "SampleSelectionCriteria": return cls(**data) @dataclass class Preset: """Preset completo de configuración de canción.""" name: str description: str version: str = "1.0" created_at: str = field(default_factory=lambda: datetime.now().isoformat()) updated_at: str = field(default_factory=lambda: datetime.now().isoformat()) bpm: float = 95.0 key: str = "Am" style: str = "dembow" structure: str = "standard" tracks_config: List[TrackPreset] = field(default_factory=list) mixing_config: MixingConfig = field(default_factory=MixingConfig) sample_selection: SampleSelectionCriteria = field(default_factory=SampleSelectionCriteria) tags: List[str] = field(default_factory=list) author: str = "" is_builtin: bool = False def to_dict(self) -> Dict[str, Any]: return { "name": self.name, "description": self.description, "version": self.version, "created_at": self.created_at, "updated_at": self.updated_at, "bpm": self.bpm, "key": self.key, "style": self.style, "structure": self.structure, "tracks_config": [t.to_dict() for t in self.tracks_config], "mixing_config": self.mixing_config.to_dict(), "sample_selection": self.sample_selection.to_dict(), "tags": self.tags, "author": self.author, "is_builtin": self.is_builtin, } @classmethod def from_dict(cls, data: Dict[str, Any]) -> "Preset": tracks = [TrackPreset.from_dict(t) for t in data.get("tracks_config", [])] mixing = MixingConfig.from_dict(data.get("mixing_config", {})) samples = SampleSelectionCriteria.from_dict(data.get("sample_selection", {})) return cls( name=data["name"], description=data.get("description", ""), version=data.get("version", "1.0"), created_at=data.get("created_at", datetime.now().isoformat()), updated_at=data.get("updated_at", datetime.now().isoformat()), bpm=data.get("bpm", 95.0), key=data.get("key", "Am"), style=data.get("style", "dembow"), structure=data.get("structure", "standard"), tracks_config=tracks, mixing_config=mixing, sample_selection=samples, tags=data.get("tags", []), author=data.get("author", ""), is_builtin=data.get("is_builtin", False), ) # ============================================================================= # PRESETS PREDEFINIDOS # ============================================================================= def create_builtin_presets() -> Dict[str, Preset]: """Crea el diccionario de presets predefinidos del sistema.""" # 1. Reggaeton Clásico 95 BPM reggaeton_classic = Preset( name="reggaeton_classic_95bpm", description="Reggaeton clásico con dembow puro. Ideal para pistas de club.", bpm=95.0, key="Am", style="dembow", structure="standard", tags=["classic", "club", "dembow", "standard"], is_builtin=True, tracks_config=[ TrackPreset(name="Kick", track_type="midi", role="kick", volume=0.9, sample_criteria={"role": "kick", "pack_preference": "classic"}), TrackPreset(name="Snare", track_type="midi", role="snare", volume=0.75, sample_criteria={"role": "snare"}), TrackPreset(name="Hi-Hats", track_type="midi", role="hat_closed", volume=0.65, sample_criteria={"role": "hat_closed"}), TrackPreset(name="Bass", track_type="midi", role="bass", volume=0.85, sample_criteria={"role": "bass", "pack_preference": "classic"}), TrackPreset(name="Synth Lead", track_type="midi", role="synth_lead", volume=0.7, sample_criteria={"role": "synth"}), ], mixing_config=MixingConfig(eq_low_gain=2.0, compressor_threshold=-4.0, compressor_ratio=2.5, send_reverb=0.25, master_volume=0.88), ) # 2. Perreo Intenso 100 BPM perreo_intenso = Preset( name="perreo_intenso_100bpm", description="Perreo intenso con kick heavy y bajo prominente. Alto impacto.", bpm=100.0, key="Em", style="perreo", structure="standard", tags=["perreo", "heavy", "club", "energetic"], is_builtin=True, tracks_config=[ TrackPreset(name="Kick Heavy", track_type="midi", role="kick", volume=0.95, sample_criteria={"role": "kick", "character": "heavy"}), TrackPreset(name="Snare", track_type="midi", role="snare", volume=0.8), TrackPreset(name="Clap", track_type="midi", role="clap", volume=0.7), TrackPreset(name="Hi-Hats", track_type="midi", role="hat_closed", volume=0.7), TrackPreset(name="Bass Deep", track_type="midi", role="bass", volume=0.9, sample_criteria={"role": "bass", "character": "deep"}), TrackPreset(name="Lead", track_type="midi", role="synth_lead", volume=0.75), ], mixing_config=MixingConfig(eq_low_gain=4.0, compressor_threshold=-6.0, compressor_ratio=3.5, send_reverb=0.2, master_volume=0.9), ) # 3. Reggaeton Romántico 90 BPM reggaeton_romantico = Preset( name="reggaeton_romantico_90bpm", description="Reggaeton romántico con reverb abundante y mezcla balanceada.", bpm=90.0, key="Gm", style="romantico", structure="extended", tags=["romantico", "smooth", "reverb", "extended"], is_builtin=True, tracks_config=[ TrackPreset(name="Kick Soft", track_type="midi", role="kick", volume=0.75, sample_criteria={"role": "kick", "character": "soft"}), TrackPreset(name="Snare", track_type="midi", role="snare", volume=0.65), TrackPreset(name="Hi-Hats", track_type="midi", role="hat_closed", volume=0.55), TrackPreset(name="Bass Smooth", track_type="midi", role="bass", volume=0.7, sample_criteria={"role": "bass", "character": "smooth"}), TrackPreset(name="Pad", track_type="midi", role="synth_pad", volume=0.6), TrackPreset(name="Lead Melodic", track_type="midi", role="synth_lead", volume=0.65), ], mixing_config=MixingConfig(eq_low_gain=0.0, compressor_threshold=-8.0, compressor_ratio=2.0, send_reverb=0.5, send_delay=0.35, master_volume=0.82), ) # 4. Moombahton 108 BPM moombahton = Preset( name="moombahton_108bpm", description="Moombahton con variación de dembow y estructura minimal.", bpm=108.0, key="Dm", style="moombahton", structure="minimal", tags=["moombahton", "dembow", "minimal", "electronic"], is_builtin=True, tracks_config=[ TrackPreset(name="Kick Moombah", track_type="midi", role="kick", volume=0.9, sample_criteria={"role": "kick", "style": "moombahton"}), TrackPreset(name="Snare", track_type="midi", role="snare", volume=0.75), TrackPreset(name="Tom", track_type="midi", role="perc", volume=0.6, sample_criteria={"role": "perc"}), TrackPreset(name="Hi-Hats", track_type="midi", role="hat_closed", volume=0.65), TrackPreset(name="Bass", track_type="midi", role="bass", volume=0.8), TrackPreset(name="Stabs", track_type="midi", role="synth_lead", volume=0.7, sample_criteria={"role": "synth", "character": "stab"}), ], mixing_config=MixingConfig(eq_low_gain=3.0, compressor_threshold=-5.0, compressor_ratio=3.0, send_reverb=0.3, master_volume=0.87), ) # 5. Trapeton 140 BPM trapeton = Preset( name="trapeton_140bpm", description="Trapeton con 808s pesados y hi-hat rolls. Fusión trap-reggaeton.", bpm=140.0, key="Cm", style="trapeton", structure="standard", tags=["trapeton", "trap", "808", "hihat_rolls", "hard"], is_builtin=True, tracks_config=[ TrackPreset(name="808 Kick", track_type="midi", role="kick", volume=0.95, sample_criteria={"role": "kick", "character": "808"}), TrackPreset(name="Snare", track_type="midi", role="snare", volume=0.8, sample_criteria={"role": "snare", "character": "trap"}), TrackPreset(name="Hi-Hats", track_type="midi", role="hat_closed", volume=0.75, sample_criteria={"role": "hat_closed", "style": "trap"}), TrackPreset(name="Hi-Hat Rolls", track_type="midi", role="hat_open", volume=0.65, sample_criteria={"role": "hat_open", "style": "trap_rolls"}), TrackPreset(name="808 Bass", track_type="midi", role="bass", volume=0.9, sample_criteria={"role": "bass", "character": "808"}), TrackPreset(name="Lead Hard", track_type="midi", role="synth_lead", volume=0.75, sample_criteria={"role": "synth", "character": "aggressive"}), ], mixing_config=MixingConfig(eq_low_gain=5.0, eq_high_gain=2.0, compressor_threshold=-8.0, compressor_ratio=4.0, compressor_makeup=4.0, send_reverb=0.15, send_delay=0.25, master_volume=0.92), ) return { reggaeton_classic.name: reggaeton_classic, perreo_intenso.name: perreo_intenso, reggaeton_romantico.name: reggaeton_romantico, moombahton.name: moombahton, trapeton.name: trapeton, } # ============================================================================= # PRESET MANAGER # ============================================================================= class PresetManager: """Gestor de presets para AbletonMCP_AI.""" def __init__(self, presets_dir: Optional[str] = None): self._presets_dir = Path(presets_dir) if presets_dir else PRESETS_DIR self._builtin_presets: Dict[str, Preset] = create_builtin_presets() self._custom_presets: Dict[str, Preset] = {} self._ensure_presets_dir() self._load_custom_presets() def _ensure_presets_dir(self): if not self._presets_dir.exists(): try: self._presets_dir.mkdir(parents=True, exist_ok=True) logger.info("Created presets directory: %s", self._presets_dir) except Exception as e: logger.error("Failed to create presets directory: %s", e) def _get_preset_path(self, preset_name: str) -> Path: safe_name = preset_name.replace(" ", "_").lower() return self._presets_dir / f"{safe_name}.json" def _load_custom_presets(self): if not self._presets_dir.exists(): return for preset_file in self._presets_dir.glob("*.json"): try: with open(preset_file, "r", encoding="utf-8") as f: data = json.load(f) preset = Preset.from_dict(data) if not preset.is_builtin: self._custom_presets[preset.name] = preset except Exception as e: logger.warning("Failed to load preset %s: %s", preset_file, e) logger.info("Loaded %d custom presets", len(self._custom_presets)) def load_preset(self, preset_name: str) -> Optional[Preset]: """Carga un preset por nombre. Busca primero en builtins, luego custom.""" if preset_name in self._builtin_presets: logger.info("Loaded builtin preset: %s", preset_name) return self._builtin_presets[preset_name] if preset_name in self._custom_presets: logger.info("Loaded custom preset: %s", preset_name) return self._custom_presets[preset_name] preset_name_lower = preset_name.lower() for name, preset in {**self._builtin_presets, **self._custom_presets}.items(): if name.lower() == preset_name_lower: return preset logger.warning("Preset not found: %s", preset_name) return None def save_as_preset(self, config: Dict[str, Any], preset_name: str) -> bool: """Guarda una configuración como preset personalizado.""" try: preset = self._config_to_preset(config, preset_name) preset.is_builtin = False preset.updated_at = datetime.now().isoformat() preset_path = self._get_preset_path(preset_name) with open(preset_path, "w", encoding="utf-8") as f: json.dump(preset.to_dict(), f, indent=2, ensure_ascii=False) self._custom_presets[preset_name] = preset logger.info("Saved preset: %s", preset_name) return True except Exception as e: logger.error("Failed to save preset %s: %s", preset_name, e) return False def _config_to_preset(self, config: Dict[str, Any], name: str) -> Preset: """Convierte un diccionario de configuración a un Preset.""" tracks_config = [] for track_data in config.get("tracks", []): tracks_config.append(TrackPreset( name=track_data.get("name", "Track"), track_type=track_data.get("track_type", "midi"), role=track_data.get("instrument_role", "synth"), volume=track_data.get("volume", 0.8), pan=track_data.get("pan", 0.0), device_chain=track_data.get("device_chain", []), )) mixing_data = config.get("mixing_config", {}) mixing_config = MixingConfig( eq_low_gain=mixing_data.get("eq_low_gain", 0.0), eq_mid_gain=mixing_data.get("eq_mid_gain", 0.0), eq_high_gain=mixing_data.get("eq_high_gain", 0.0), compressor_threshold=mixing_data.get("compressor_threshold", -6.0), compressor_ratio=mixing_data.get("compressor_ratio", 3.0), send_reverb=mixing_data.get("send_reverb", 0.3), send_delay=mixing_data.get("send_delay", 0.2), master_volume=mixing_data.get("master_volume", 0.85), ) return Preset( name=name, description=config.get("description", f"Custom preset: {name}"), bpm=config.get("bpm", 95.0), key=config.get("key", "Am"), style=config.get("style", "dembow"), structure=config.get("structure", "standard"), tracks_config=tracks_config, mixing_config=mixing_config, tags=config.get("tags", ["custom"]), ) def list_presets(self, include_builtin: bool = True, filter_tags: Optional[List[str]] = None) -> List[Dict[str, Any]]: """Lista todos los presets disponibles.""" all_presets: Dict[str, Preset] = {} if include_builtin: all_presets.update(self._builtin_presets) all_presets.update(self._custom_presets) if filter_tags: all_presets = {n: p for n, p in all_presets.items() if any(t in p.tags for t in filter_tags)} result = [ {"name": n, "description": p.description, "bpm": p.bpm, "key": p.key, "style": p.style, "structure": p.structure, "tags": p.tags, "is_builtin": p.is_builtin, "track_count": len(p.tracks_config)} for n, p in all_presets.items() ] result.sort(key=lambda x: (not x["is_builtin"], x["name"])) return result def create_custom_preset(self, current_config: Dict[str, Any], name: str, description: str = "", tags: Optional[List[str]] = None) -> Optional[Preset]: """Crea un nuevo preset personalizado desde una configuración.""" try: preset = self._config_to_preset(current_config, name) preset.description = description or f"Custom preset: {name}" preset.tags = tags or ["custom"] preset.is_builtin = False preset.author = current_config.get("author", "") if self.save_as_preset(current_config, name): return preset return None except Exception as e: logger.error("Failed to create custom preset: %s", e) return None def delete_preset(self, preset_name: str) -> bool: """Elimina un preset personalizado. No se pueden eliminar builtins.""" if preset_name in self._builtin_presets: logger.warning("Cannot delete builtin preset: %s", preset_name) return False if preset_name not in self._custom_presets: logger.warning("Preset not found for deletion: %s", preset_name) return False try: preset_path = self._get_preset_path(preset_name) if preset_path.exists(): preset_path.unlink() del self._custom_presets[preset_name] logger.info("Deleted preset: %s", preset_name) return True except Exception as e: logger.error("Failed to delete preset %s: %s", preset_name, e) return False def export_preset(self, preset_name: str, export_path: str) -> bool: """Exporta un preset a un archivo externo.""" preset = self.load_preset(preset_name) if not preset: logger.warning("Cannot export non-existent preset: %s", preset_name) return False try: export_path = Path(export_path) if not export_path.suffix == ".json": export_path = export_path.with_suffix(".json") with open(export_path, "w", encoding="utf-8") as f: json.dump(preset.to_dict(), f, indent=2, ensure_ascii=False) logger.info("Exported preset %s to %s", preset_name, export_path) return True except Exception as e: logger.error("Failed to export preset %s: %s", preset_name, e) return False def import_preset(self, import_path: str, preset_name: Optional[str] = None) -> Optional[Preset]: """Importa un preset desde un archivo externo.""" try: import_path = Path(import_path) if not import_path.exists(): logger.error("Import file not found: %s", import_path) return None with open(import_path, "r", encoding="utf-8") as f: data = json.load(f) preset = Preset.from_dict(data) preset.is_builtin = False if preset_name: preset.name = preset_name preset_path = self._get_preset_path(preset.name) with open(preset_path, "w", encoding="utf-8") as f: json.dump(preset.to_dict(), f, indent=2, ensure_ascii=False) self._custom_presets[preset.name] = preset logger.info("Imported preset: %s", preset.name) return preset except Exception as e: logger.error("Failed to import preset from %s: %s", import_path, e) return None def get_preset_details(self, preset_name: str) -> Optional[Dict[str, Any]]: """Obtiene detalles completos de un preset.""" preset = self.load_preset(preset_name) if not preset: return None return { "name": preset.name, "description": preset.description, "version": preset.version, "created_at": preset.created_at, "updated_at": preset.updated_at, "bpm": preset.bpm, "key": preset.key, "style": preset.style, "structure": preset.structure, "tracks": [{"name": t.name, "type": t.track_type, "role": t.role, "volume": t.volume, "pan": t.pan} for t in preset.tracks_config], "mixing": preset.mixing_config.to_dict(), "sample_selection": preset.sample_selection.to_dict(), "tags": preset.tags, "author": preset.author, "is_builtin": preset.is_builtin, } def duplicate_preset(self, source_name: str, new_name: str) -> bool: """Duplica un preset existente con un nuevo nombre.""" source = self.load_preset(source_name) if not source: return False try: new_preset = Preset.from_dict(source.to_dict()) new_preset.name = new_name new_preset.is_builtin = False new_preset.description = f"Copy of {source_name}: {source.description}" new_preset.created_at = datetime.now().isoformat() new_preset.updated_at = datetime.now().isoformat() preset_path = self._get_preset_path(new_name) with open(preset_path, "w", encoding="utf-8") as f: json.dump(new_preset.to_dict(), f, indent=2, ensure_ascii=False) self._custom_presets[new_name] = new_preset logger.info("Duplicated preset %s to %s", source_name, new_name) return True except Exception as e: logger.error("Failed to duplicate preset: %s", e) return False # ============================================================================= # FUNCIONES DE CONVENIENCIA # ============================================================================= _manager: Optional[PresetManager] = None def get_preset_manager() -> PresetManager: """Retorna la instancia singleton del PresetManager.""" global _manager if _manager is None: _manager = PresetManager() return _manager def apply_preset_to_project(preset_name: str) -> Dict[str, Any]: """Aplica un preset completo al proyecto actual.""" manager = get_preset_manager() preset = manager.load_preset(preset_name) if not preset: return {"success": False, "error": f"Preset not found: {preset_name}"} config = { "bpm": preset.bpm, "key": preset.key, "style": preset.style, "structure": preset.structure, "tracks": [{"name": t.name, "track_type": t.track_type, "instrument_role": t.role, "volume": t.volume, "pan": t.pan, "device_chain": t.device_chain} for t in preset.tracks_config], "mixing_config": preset.mixing_config.to_dict(), "sample_criteria": preset.sample_selection.to_dict(), } return { "success": True, "preset_name": preset_name, "config": config, "message": f"Preset '{preset_name}' loaded and ready to apply", } def get_default_preset() -> str: """Retorna el nombre del preset por defecto.""" return "reggaeton_classic_95bpm" def list_available_presets(style_filter: Optional[str] = None) -> List[Dict[str, Any]]: """Lista todos los presets disponibles, opcionalmente filtrados por estilo.""" manager = get_preset_manager() presets = manager.list_presets() if style_filter: presets = [p for p in presets if p.get("style") == style_filter] return presets def quick_apply_preset(preset_name: Optional[str] = None) -> Dict[str, Any]: """Aplica rápidamente un preset (o el default si no se especifica).""" if preset_name is None: preset_name = get_default_preset() return apply_preset_to_project(preset_name) # ============================================================================= # HANDLERS MCP # ============================================================================= def _cmd_load_preset(params: Dict[str, Any]) -> Dict[str, Any]: """Handler MCP: Carga un preset por nombre.""" preset_name = params.get("preset_name", "") if not preset_name: return {"success": False, "error": "Missing preset_name parameter"} manager = get_preset_manager() preset = manager.load_preset(preset_name) if not preset: return {"success": False, "error": f"Preset not found: {preset_name}"} return {"success": True, "preset": preset.to_dict()} def _cmd_save_as_preset(params: Dict[str, Any]) -> Dict[str, Any]: """Handler MCP: Guarda configuración actual como preset.""" config, preset_name = params.get("config", {}), params.get("preset_name", "") if not preset_name: return {"success": False, "error": "Missing preset_name parameter"} success = get_preset_manager().save_as_preset(config, preset_name) return {"success": success, "preset_name": preset_name, "message": f"Preset '{preset_name}' saved" if success else "Failed to save"} def _cmd_list_presets(params: Dict[str, Any]) -> Dict[str, Any]: """Handler MCP: Lista todos los presets disponibles.""" manager = get_preset_manager() presets = manager.list_presets(include_builtin=params.get("include_builtin", True), filter_tags=params.get("filter_tags")) return {"success": True, "count": len(presets), "presets": presets} def _cmd_create_custom_preset(params: Dict[str, Any]) -> Dict[str, Any]: """Handler MCP: Crea un preset personalizado.""" current_config, name = params.get("current_config", {}), params.get("name", "") if not name: return {"success": False, "error": "Missing name parameter"} preset = get_preset_manager().create_custom_preset(current_config, name, params.get("description", ""), params.get("tags")) return {"success": preset is not None, "preset_name": name, "preset": preset.to_dict() if preset else None} def _cmd_delete_preset(params: Dict[str, Any]) -> Dict[str, Any]: """Handler MCP: Elimina un preset personalizado.""" preset_name = params.get("preset_name", "") if not preset_name: return {"success": False, "error": "Missing preset_name parameter"} success = get_preset_manager().delete_preset(preset_name) return {"success": success, "message": f"Preset '{preset_name}' deleted" if success else f"Failed to delete '{preset_name}'"} def _cmd_export_preset(params: Dict[str, Any]) -> Dict[str, Any]: """Handler MCP: Exporta un preset a archivo.""" preset_name, export_path = params.get("preset_name", ""), params.get("export_path", "") if not preset_name or not export_path: return {"success": False, "error": "Missing preset_name or export_path"} success = get_preset_manager().export_preset(preset_name, export_path) return {"success": success, "message": f"Exported to {export_path}" if success else "Export failed"} def _cmd_import_preset(params: Dict[str, Any]) -> Dict[str, Any]: """Handler MCP: Importa un preset desde archivo.""" import_path = params.get("import_path", "") if not import_path: return {"success": False, "error": "Missing import_path parameter"} preset = get_preset_manager().import_preset(import_path, params.get("preset_name")) return {"success": preset is not None, "preset_name": preset.name if preset else None, "preset": preset.to_dict() if preset else None} def _cmd_get_preset_details(params: Dict[str, Any]) -> Dict[str, Any]: """Handler MCP: Obtiene detalles completos de un preset.""" preset_name = params.get("preset_name", "") if not preset_name: return {"success": False, "error": "Missing preset_name parameter"} details = get_preset_manager().get_preset_details(preset_name) return {"success": details is not None, "preset": details, "error": f"Preset not found: {preset_name}" if not details else None} def _cmd_duplicate_preset(params: Dict[str, Any]) -> Dict[str, Any]: """Handler MCP: Duplica un preset existente.""" source_name, new_name = params.get("source_name", ""), params.get("new_name", "") if not source_name or not new_name: return {"success": False, "error": "Missing source_name or new_name"} success = get_preset_manager().duplicate_preset(source_name, new_name) return {"success": success, "message": f"Duplicated: {source_name} -> {new_name}" if success else "Duplication failed"} # Mapa de handlers disponibles para el MCP server MCP_HANDLERS = { "load_preset": _cmd_load_preset, "save_as_preset": _cmd_save_as_preset, "list_presets": _cmd_list_presets, "create_custom_preset": _cmd_create_custom_preset, "delete_preset": _cmd_delete_preset, "export_preset": _cmd_export_preset, "import_preset": _cmd_import_preset, "get_preset_details": _cmd_get_preset_details, "duplicate_preset": _cmd_duplicate_preset, "apply_preset": lambda p: apply_preset_to_project(p.get("preset_name", "")), } # ============================================================================= # MAIN / TEST # ============================================================================= if __name__ == "__main__": logging.basicConfig(level=logging.INFO) print("=" * 70) print("PRESET SYSTEM - AbletonMCP_AI") print("=" * 70) print("\n1. Inicializando PresetManager...") manager = get_preset_manager() print(f" OK - Directorio: {manager._presets_dir}") print("\n2. Presets predefinidos:") for name, preset in manager._builtin_presets.items(): print(f" - {name}: {preset.description[:45]}...") print("\n3. Listando todos los presets...") all_presets = manager.list_presets() print(f" Total: {len(all_presets)} presets") for p in all_presets[:5]: print(f" - {p['name']} ({p['style']}, {p['bpm']} BPM, {p['track_count']} tracks)") print("\n4. Cargando 'reggaeton_classic_95bpm'...") classic = manager.load_preset("reggaeton_classic_95bpm") if classic: print(f" BPM: {classic.bpm}, Key: {classic.key}, Tracks: {len(classic.tracks_config)}") print("\n5. Detalles de 'perreo_intenso_100bpm'...") details = manager.get_preset_details("perreo_intenso_100bpm") if details: print(f" EQ Low: {details['mixing']['eq_low_gain']} dB, Comp: {details['mixing']['compressor_threshold']} dB") print("\n6. Aplicando preset default...") result = quick_apply_preset() print(f" Success: {result['success']}, Preset: {result.get('preset_name')}") print("\n" + "=" * 70) print("Tests completados!") print("=" * 70)