Files
ableton-mcp-ai/AbletonMCP_AI/MCP_Server/bus_routing_fix.py
renato97 4332ff65da Implement FASE 3, 4, 6 - 15 new MCP tools, 76/110 tasks complete
FASE 3 - Human Feel & Dynamics (10/11 tasks):
- apply_clip_fades() - T041: Fade automation per section
- write_volume_automation() - T042: Curves (linear, exp, s_curve, punch)
- apply_sidechain_pump() - T045: Sidechain by intensity/style
- inject_pattern_fills() - T048: Snare rolls, fills by density
- humanize_set() - T050: Timing + velocity + groove automation

FASE 4 - Key Compatibility & Tonal (9/12 tasks):
- audio_key_compatibility.py: Full KEY_COMPATIBILITY_MATRIX
- analyze_key_compatibility() - T053: Harmonic compatibility scoring
- suggest_key_change() - T054: Circle of fifths modulation
- validate_sample_key() - T055: Sample key validation
- analyze_spectral_fit() - T057/T062: Spectral role matching

FASE 6 - Mastering & QA (8/13 tasks):
- calibrate_gain_staging() - T079: Auto gain by bus targets
- run_mix_quality_check() - T085: LUFS, peaks, L/R balance
- export_stem_mixdown() - T087: 24-bit/44.1kHz stem export

New files:
- audio_key_compatibility.py (T052)
- bus_routing_fix.py (T101-T104)
- validation_system_fix.py (T105-T106)

Total: 76/110 tasks (69%), 71 MCP tools exposed

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-29 00:59:24 -03:00

279 lines
8.9 KiB
Python

"""
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