""" bus_routing_fix.py - Fix de enrutamiento de buses T101-T104: Bus Routing System Fix Problemas a resolver: - Drums van a drum rack pero también a master - FX no llegan a los returns correctos - Vocal chops en bus de FX en lugar de Vocal """ import logging from typing import Dict, Any, List, Optional from dataclasses import dataclass logger = logging.getLogger("BusRoutingFix") @dataclass class BusRoute: """Definición de ruta de bus""" source_track: str target_bus: str send_level: float = 0.0 # 0.0 = no send, 1.0 = full send should_go_to_master: bool = True class BusRoutingRules: """T101: Reglas de enrutamiento por tipo de track""" # Mapeo de roles a buses ROLE_TO_BUS = { 'kick': 'drums', 'clap': 'drums', 'snare': 'drums', 'hat': 'drums', 'perc': 'drums', 'ride': 'drums', 'top_loop': 'drums', 'drum_loop': 'drums', 'breakbeat': 'drums', 'sub_bass': 'bass', 'bass': 'bass', 'bass_loop': 'bass', 'chords': 'music', 'pad': 'music', 'pluck': 'music', 'arp': 'music', 'lead': 'music', 'counter': 'music', 'synth': 'music', 'vocal': 'vocal', 'vocal_chop': 'vocal', 'vox': 'vocal', 'voice': 'vocal', 'riser': 'fx', 'downlifter': 'fx', 'impact': 'fx', 'crash': 'fx', 'atmos': 'fx', 'reverse_fx': 'fx', 'texture': 'fx', } # Buses RCA disponibles RCA_BUSES = ['drums', 'bass', 'music', 'vocal', 'fx'] # Returns configurados en Live RETURN_TRACKS = ['Reverb', 'Delay', 'Chorus', 'Spatial'] @classmethod def get_bus_for_role(cls, role: str) -> str: """Retorna el bus RCA apropiado para un rol.""" role_lower = role.lower().replace('_loop', '').replace('loop_', '') # Check direct match if role_lower in cls.ROLE_TO_BUS: return cls.ROLE_TO_BUS[role_lower] # Check partial match for key, bus in cls.ROLE_TO_BUS.items(): if key in role_lower or role_lower in key: return bus # Default por categoría if any(d in role_lower for d in ['drum', 'kick', 'snare', 'hat', 'perc']): return 'drums' if any(b in role_lower for b in ['bass', 'sub', '808', 'low']): return 'bass' if any(s in role_lower for s in ['synth', 'pad', 'chord', 'lead', 'pluck', 'melody']): return 'music' if any(v in role_lower for v in ['vocal', 'vox', 'voice', 'chant']): return 'vocal' if any(f in role_lower for f in ['fx', 'riser', 'impact', 'atmos', 'texture', 'noise']): return 'fx' return 'music' # Default fallback class BusRoutingFixer: """T102-T104: Aplica fixes de enrutamiento""" def __init__(self): self.rules = BusRoutingRules() self.issues_found: List[Dict] = [] self.fixes_applied: List[Dict] = [] def diagnose_routing(self, tracks_data: List[Dict]) -> List[Dict]: """ T102: Diagnostica problemas de enrutamiento. Args: tracks_data: Lista de tracks con sus configuraciones Returns: Lista de problemas encontrados """ issues = [] for track in tracks_data: track_name = track.get('name', 'Unknown') track_role = track.get('role', '') current_bus = track.get('output_bus', 'master') # Determinar bus correcto correct_bus = self.rules.get_bus_for_role(track_role or track_name) # Verificar si está en bus incorrecto if current_bus != correct_bus and current_bus != 'master': issues.append({ 'track': track_name, 'role': track_role, 'current_bus': current_bus, 'correct_bus': correct_bus, 'issue': 'wrong_bus', 'severity': 'high' if correct_bus != 'music' else 'medium' }) # Verificar sends incorrectos (ej: drums enviando a reverb fuerte) sends = track.get('sends', {}) if track_role in ['kick', 'sub_bass']: reverb_send = sends.get('Reverb', 0) if reverb_send > 0.3: issues.append({ 'track': track_name, 'role': track_role, 'issue': 'excessive_reverb_on_low', 'current_send': reverb_send, 'recommended': 0.1, 'severity': 'medium' }) # Verificar que FX tracks no van a master directo if correct_bus == 'fx' and track.get('audio_output') == 'Master': issues.append({ 'track': track_name, 'role': track_role, 'issue': 'fx_to_master_bypass', 'severity': 'low' }) self.issues_found = issues return issues def apply_routing_fixes(self, ableton_connection, tracks_data: List[Dict]) -> Dict: """ T103: Aplica fixes de enrutamiento en Ableton. Args: ableton_connection: Conexión a Ableton Live tracks_data: Datos de tracks a corregir Returns: Reporte de fixes aplicados """ fixes = [] for track in tracks_data: track_name = track.get('name') track_index = track.get('index') track_role = track.get('role', '') # Determinar bus correcto correct_bus = self.rules.get_bus_for_role(track_role or track_name) try: # 1. Cambiar output del track al bus RCA # Esto requiere que los buses RCA existan como tracks de audio self._set_track_output(ableton_connection, track_index, correct_bus) # 2. Ajustar sends si es necesario if track_role in ['kick', 'sub_bass']: self._adjust_send(ableton_connection, track_index, 'Reverb', 0.1) fixes.append({ 'track': track_name, 'action': f'routed_to_{correct_bus}', 'success': True }) except Exception as e: fixes.append({ 'track': track_name, 'action': 'routing_fix', 'success': False, 'error': str(e) }) self.fixes_applied = fixes return { 'total_tracks': len(tracks_data), 'fixes_applied': len([f for f in fixes if f.get('success')]), 'fixes_failed': len([f for f in fixes if not f.get('success')]), 'details': fixes } def _set_track_output(self, ableton_connection, track_index: int, output_bus: str): """Setea output de un track a un bus específico.""" # Comando MCP para cambiar output cmd = { 'command': 'set_track_output', 'track_index': track_index, 'output': output_bus } ableton_connection.send_command(cmd) def _adjust_send(self, ableton_connection, track_index: int, send_name: str, level: float): """Ajusta nivel de send.""" cmd = { 'command': 'set_send_level', 'track_index': track_index, 'send_name': send_name, 'level': level } ableton_connection.send_command(cmd) def validate_routing(self, tracks_data: List[Dict]) -> Dict: """ T104: Valida que el enrutamiento esté correcto. Returns: Reporte de validación """ issues = self.diagnose_routing(tracks_data) critical = [i for i in issues if i.get('severity') == 'high'] warnings = [i for i in issues if i.get('severity') in ['medium', 'low']] return { 'valid': len(critical) == 0, 'critical_issues': len(critical), 'warnings': len(warnings), 'total_issues': len(issues), 'issues': issues } def get_bus_routing_config(self) -> Dict[str, Any]: """Retorna configuración completa de enrutamiento.""" return { 'buses': self.rules.RCA_BUSES, 'returns': self.rules.RETURN_TRACKS, 'role_mapping': self.rules.ROLE_TO_BUS, 'validation_rules': { 'kick_reverb_max': 0.1, 'sub_bass_reverb_max': 0.05, 'drums_to_fx_send': 0.0, } } # Instancia global _routing_fixer: Optional[BusRoutingFixer] = None def get_routing_fixer() -> BusRoutingFixer: """Obtiene instancia global del fixer.""" global _routing_fixer if _routing_fixer is None: _routing_fixer = BusRoutingFixer() return _routing_fixer