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>
This commit is contained in:
278
AbletonMCP_AI/MCP_Server/bus_routing_fix.py
Normal file
278
AbletonMCP_AI/MCP_Server/bus_routing_fix.py
Normal file
@@ -0,0 +1,278 @@
|
||||
"""
|
||||
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
|
||||
Reference in New Issue
Block a user