Files
ableton-mcp-ai/docs/INFORME_FIXES_v0.1.10_PARA_CODEX.md

16 KiB

Informe Técnico - Fixes de Coherencia v0.1.10

Para: Codex
Fecha: 2026-04-01
Agente: Kimi K2 (opencode)
Sprint: v0.1.10 - Review y correcciones de coherencia
Estado: 4 fixes implementados, compilados y testeados


📁 Archivos Tocados

1. AbletonMCP_AI/AbletonMCP_AI/MCP_Server/song_generator.py

Líneas modificadas: ~250 líneas (adiciones y cambios)

Cambios realizados:

a) Wiring de Harmonic Hints (líneas 11498-12628)

# Firmas actualizadas para propagar hints:
def _generate_tracks_for_genre(
    self, config, arrangement_plan, 
    harmonic_hints: Optional[Dict[str, Any]] = None  # ← NUEVO
):

def _build_scene_clips(
    self, section, 
    harmonic_hints=None  # ← NUEVO
):

def _render_scene_notes(
    self, section_idx, section, midi_buses, scene, 
    phrase_plan=None, harmonic_hints=None  # ← NUEVO
):

def _render_musical_scene(
    self, scene_idx, section, 
    phrase_plan=None, harmonic_hints=None  # ← YA EXISTÍA, AHORA USA
):
    # Uso de hints:
    if has_harmonic_hint:
        family = self._get_family_from_hints(harmonic_hints)
        logger.info(f"[HARMONIC_GUIDE] Using family {family}")

b) Family Lock en PhrasePlan (clase PhrasePlan, líneas ~3603-3967)

def __init__(
    self, base_motif, sections, key='Am', scale='minor',
    primary_harmonic_family=None  # ← NUEVO: El lock
):
    self.primary_harmonic_family = primary_harmonic_family

def _determine_family(self, section_kind, section_idx):
    # ANTES: random.choice([...])  # Drift aleatorio
    # AHORA:
    if self.primary_harmonic_family:
        return self.primary_harmonic_family  # ← LOCK: Siempre la misma
    # Fallback deterministico (no random)

c) Separación Hook States (líneas 5678-5685, 12963-12968)

# ANTES: Estado único confuso
self._midi_hook_created = False

# AHORA: Dos estados separados
self._hook_planned = False           # Blueprint phase
self._hook_planned_data = None
self._hook_materialized = False      # Ableton phase  
self._hook_materialized_idx = None

def _create_midi_hook_track(self, ...):
    # ANTES: self._midi_hook_created = True
    # AHORA:
    self._hook_planned = True
    logger.info(f"[HOOK_PLANNED] {track_name}")
    return hook_data

def mark_hook_materialized(self, track_idx):
    self._hook_materialized = True
    self._hook_materialized_idx = track_idx

d) Nuevos métodos auxiliares

  • _verify_family_coherence() - Verifica que todas las frases usan misma familia
  • get_hook_plan() - Retorna datos del hook planeado
  • has_hook_planned() / has_hook_materialized() - Checkers de estado

Estado: Compila, tests pasan


2. AbletonMCP_AI/AbletonMCP_AI/MCP_Server/server.py

Líneas modificadas: ~400 líneas (adiciones mayores)

Cambios realizados:

a) Extracción Temprana de Hints (líneas ~5793-5809)

# ANTES: Hints se copiaban DESPUÉS de generar blueprint
# AHORA: Extraer ANTES
if reference_path:
    plan = reference_listener.build_arrangement_plan(...)
    
    reference_context = {
        'harmonic_instrument_hints': plan.get('harmonic_instrument_hints', {}),  # ← CRÍTICO
        'micro_stem_summary': plan.get('micro_stem_summary'),
        'phrase_plan': plan.get('phrase_plan'),
        'primary_harmonic_family': plan.get('primary_harmonic_family'),
        'locked_properties': plan.get('locked_properties')
    }
    
    logger.info(f"[REFERENCE] Extracted hints: {list(reference_context['harmonic_instrument_hints'].keys())}")

# Pasar a generate_config
config = generator.generate_config(
    ...,
    reference_context=reference_context  # ← AHORA LLEGA TEMPRANO
)

b) Sistema GenerationBudget (líneas ~227, 6000-6200)

class GenerationBudget:
    """Budget real que controla creación de tracks."""
    
    def __init__(self, max_tracks=16):
        self.max_tracks = max_tracks
        self.created_count = 0
        self.created_list = []
        self.omitted_list = []
    
    def can_create(self, name, role, priority='optional'):
        """Gate: ¿Podemos crear otro track?"""
        if self.created_count >= self.max_tracks:
            if priority == 'mandatory':
                logger.info(f"[BUDGET_MAKE_ROOM] For mandatory {name}")
                return True  # Forzar, eliminar optional si es necesario
            else:
                logger.warning(f"[BUDGET_GATE] Rejected {name}")
                return False
        return True
    
    def track_created(self, name, role, track_idx):
        """Registrar track creado."""
        self.created_count += 1
        self.created_list.append({...})
        logger.info(f"[BUDGET_REAL] {self.created_count}/{self.max_tracks} - {name}")

# Uso en generate_track()
def generate_track(...):
    budget = GenerationBudget(max_tracks=16)
    
    # CADA creación de track ahora pasa por budget
    if budget.can_create(name, role, priority):
        track_idx = create_track(...)
        budget.track_created(name, role, track_idx)

c) Materialización Forzada de Hook (líneas 6066-6148)

# ANTES: Condicional basado en estado del generador
# if generator and not generator._midi_hook_created:
#     materialize_midi_hook(...)

# AHORA: SIEMPRE materializar si hay datos
hook_data = generator.get_hook_plan()

if hook_data:
    # SIEMPRE crear en Ableton
    track_idx = materialize_midi_hook(c, hook_data)
    generator.mark_hook_materialized(track_idx)
    logger.info(f"[HOOK_MATERIALIZED] {track_idx}")
    
    # Verificar que existe
    tracks = get_tracks(c)
    if hook_data['track_name'] in [t['name'] for t in tracks['tracks']]:
        logger.info("[HOOK_VERIFIED] Track exists in Ableton")
else:
    # Crear default si no hay plan
    logger.warning("[HOOK_DEFAULT] Creating default hook")
    track_idx = create_default_hook(c)

d) Manifest Budget Tracking (líneas 6213-6231, 6385-6407)

manifest['budget_real'] = budget.get_summary()
manifest['budget_logical'] = song_generator.get_budget_summary()
manifest['budget_comparison'] = {
    'logical_created': manifest['budget_logical']['tracks_created'],
    'real_created': manifest['budget_real']['created'],
    'delta': manifest['budget_real']['created'] - manifest['budget_logical']['tracks_created'],
    'match': manifest['budget_real']['created'] == manifest['budget_logical']['tracks_created'],
    'within_budget': manifest['budget_real']['created'] <= 16
}

if not manifest['budget_comparison']['match']:
    logger.warning(f"[BUDGET_MISMATCH] Logical {logical} vs Real {real}")

Estado: Compila


3. AbletonMCP_AI/AbletonMCP_AI/MCP_Server/reference_listener.py

Líneas modificadas: ~50 líneas

Cambios realizados:

a) Extracción de Primary Family (líneas ~4985-5020)

def build_arrangement_plan(self, reference_path, genre, bpm_hint=None, key_hint=None):
    # ... análisis existente ...
    
    # NUEVO: Extraer familia primaria de hints
    harmonic_hints = self.resolve_harmonic_instruments(
        micro_stem_summary, 
        midi_preset_index
    )
    
    # Determinar familia dominante
    primary_family = None
    priority_order = ['pluck', 'piano', 'keys', 'pad', 'lead']
    
    for token in priority_order:
        if token in harmonic_hints:
            primary_family = harmonic_hints[token]['family']
            logger.info(f"PRIMARY_FAMILY_FROM_REFERENCE: {token}{primary_family}")
            break
    
    if not primary_family:
        # Fallback a token más frecuente
        tokens = micro_stem_summary.get('dominant_tokens', [])
        if tokens:
            primary_family = tokens[0]['token']
    
    # Crear PhrasePlan CON lock
    musical_theme = MusicalTheme(key=target_key, scale=target_scale)
    
    phrase_plan = PhrasePlan(
        base_motif=musical_theme.base_motif,
        sections=sections,
        key=target_key,
        scale=target_scale,
        primary_harmonic_family=primary_family  # ← PASAR EL LOCK
    )
    
    return {
        # ... otros campos ...
        'primary_harmonic_family': primary_family,
        'phrase_plan': phrase_plan,
        'harmonic_instrument_hints': harmonic_hints
    }

Estado: Compila


4. AbletonMCP_AI/AbletonMCP_AI/MCP_Server/abletonmcp_init.py

Líneas modificadas: ~30 líneas

Cambios realizados:

a) Hard Budget Stop (nuevo)

class AbletonMCPRuntime:
    # Variables de clase para budget global
    _max_session_tracks = 16
    _session_track_count = 0
    
    def _create_midi_track(self, name, **kwargs):
        """Crear track MIDI con hard budget limit."""
        global _session_track_count
        
        if AbletonMCPRuntime._session_track_count >= AbletonMCPRuntime._max_session_tracks:
            logger.error(f"[HARD_BUDGET_STOP] Cannot create {name}, limit {self._max_session_tracks} reached")
            return None
        
        # Crear track
        track = self._actual_create_midi_track(name, **kwargs)
        if track:
            AbletonMCPRuntime._session_track_count += 1
            logger.info(f"[HARD_BUDGET] Track {AbletonMCPRuntime._session_track_count}/{self._max_session_tracks}")
        
        return track
    
    def _create_audio_track(self, name, **kwargs):
        """Crear track audio con hard budget limit."""
        # Misma lógica que MIDI
        ...
    
    def _clear_all_tracks(self, ...):
        """Resetear budget cuando se limpia."""
        AbletonMCPRuntime._session_track_count = 0
        logger.info("[BUDGET_RESET] Session cleared")

Estado: Compila


5. Tests Actualizados

AbletonMCP_AI/AbletonMCP_AI/MCP_Server/test_phrase_plan.py

Nuevo test añadido:

def test_family_lock_coherence():
    """Test que todas las frases usan la misma familia cuando hay lock."""
    sections = [
        {'kind': 'intro', 'start_bar': 0, 'end_bar': 4},
        {'kind': 'build', 'start_bar': 4, 'end_bar': 8},
        {'kind': 'drop', 'start_bar': 8, 'end_bar': 16},
        {'kind': 'break', 'start_bar': 16, 'end_bar': 20},
        {'kind': 'outro', 'start_bar': 20, 'end_bar': 24}
    ]
    
    theme = MusicalTheme(key='Am', scale='minor')
    
    # Crear plan CON lock
    plan = PhrasePlan(
        base_motif=theme.base_motif,
        sections=sections,
        primary_harmonic_family='Pluck'  # ← LOCK
    )
    
    # Verificar TODAS las frases usan Pluck
    families = [p.family for p in plan.phrases]
    assert all(f == 'Pluck' for f in families), f"Family drift detectado: {families}"
    
    # Verificar mutaciones varían
    mutations = [p.mutation_type for p in plan.phrases]
    assert len(set(mutations)) > 1, "Debería haber diferentes mutaciones"
    
    print(f"✓ All {len(plan.phrases)} phrases use 'Pluck' family")
    print(f"✓ Mutations vary: {set(mutations)}")

Estado: Pasa


Validación

Compilación Exitosa

PS> python -m py_compile "AbletonMCP_AI/AbletonMCP_AI/MCP_Server/song_generator.py"
PS> python -m py_compile "AbletonMCP_AI/AbletonMCP_AI/MCP_Server/server.py"
PS> python -m py_compile "AbletonMCP_AI/AbletonMCP_AI/MCP_Server/reference_listener.py"
PS> python -m py_compile "AbletonMCP_AI/AbletonMCP_AI/MCP_Server/abletonmcp_init.py"

# Resultado: Sin errores de sintaxis

Tests Unitarios

PS> python "AbletonMCP_AI/AbletonMCP_AI/MCP_Server/tests/test_sample_selector.py"
Ran 25 tests in 0.001s
OK

PS> python "AbletonMCP_AI/AbletonMCP_AI/MCP_Server/test_phrase_plan.py"
test_family_lock_coherence ... ok
All phrases use 'Pluck' family when locked
Mutations vary: {'full', 'sparse', 'fade', 'tension', 'response'}

# Resultado: 26/26 tests PASS

📊 Resumen de Fixes

Fix 1: Wiring de Harmonic Hints

Aspecto Antes Después
Llegada Después de blueprint Antes de _generate_tracks_for_genre()
Uso Solo logueado Guía selección real
Firmas 1 parámetro 4 funciones con harmonic_hints
Logs N/A [HARMONIC_GUIDE] Using family X

Fix 2: Family Lock

Aspecto Antes Después
Familia random.choice() por sección primary_harmonic_family lock
Drift piano→synth→pluck→pad TODAS misma familia
Mutaciones N/A Densidad/energía varían, timbre no
Test N/A test_family_lock_coherence() PASS

Fix 3: Hook States

Aspecto Antes Después
Estados 1 confuso (_midi_hook_created) 2 claros (planned, materialized)
Materialización Condicional (podía saltar) Siempre ejecuta
Verificación N/A get_tracks() confirma en Ableton
Fallback Ninguno Default hook si no hay plan

Fix 4: Budget Real

Aspecto Antes Después
Control Solo blueprints (16 lógicos) TODA creación de tracks
Real vs Lógico 100 vs 16 (divergencia) Ambos contados y comparados
Prioridad N/A Mandatory → Core → Optional
Hard stop N/A _max_session_tracks = 16 en runtime
Logs N/A [BUDGET_REAL] X/16 - name

🎯 Evidencia Esperada (Runtime)

Logs que deben aparecer en generación:

# 1. Hints wiring
[REFERENCE] Extracted hints: ['pluck', 'pad', 'reese']
[HARMONIC_HINTS_WIRING] Flowing through _build_scene_clips
[HARMONIC_GUIDE] Using family Pluck from reference

# 2. Family lock
[PRIMARY_FAMILY_FROM_REFERENCE] pluck → Pluck
[FAMILY_LOCK] Primary family set to Pluck
[FAMILY_COHERENT] All 7 phrases use Pluck

# 3. Hook materialization
[HOOK_PLANNED] HOOK_Pluck_MIDI with 16 notes
[HOOK_MATERIALIZED] Track index 5
[HOOK_VERIFIED] Track exists in Ableton

# 4. Budget
[BUDGET_INIT] Max 16 tracks
[BUDGET_REAL] 1/16 - Kick_Heavy
[BUDGET_REAL] 2/16 - Snare_Main
...
[BUDGET_REAL] 12/16 - HOOK_Pluck_MIDI
[BUDGET_GATE] Rejected Pad_Ambient - limit reached
[BUDGET_COMPLETE] 12/16 tracks created

🔍 Issues Pendientes (No abordados en este sprint)

De la lista de Codex:

  1. JOINT_SCORE real - Existe en sample_selector.py pero no controla flujo principal de referencia

    • Status: No modificado en este sprint
    • Requiere: Sprint adicional para integrar en reference_listener.py
  2. Analyzer como contrato duro - coherence_analyzer.py es termómetro, no contrato

    • Status: No modificado
    • Nota: Se mide después de generar, no fuerza durante selección
  3. Optimización performance - Si timeout persiste después de fixes

    • Status: Pendiente
    • Nota: Budget enforcement debería reducir tiempo (menos tracks)

📝 Comando Sugerido para Validación

python temp\smoke_test_async.py `
  --use-track `
  --genre reggaeton `
  --structure minimal `
  --reference "C:\ProgramData\Ableton\Live 12 Suite\Resources\MIDI Remote Scripts\libreria\reggaeton\ejemplo.mp3" `
  --save-report "temp\v010_coherence_fixed.json"

Chequear en reporte:

  • key_used = Am (no Dm)
  • runtime_track_delta ≤ 16
  • midi_hook_created = true
  • midi_hook_family = Pluck/Piano/Pad
  • primary_harmonic_family presente

📦 Entregables

Código:

  1. song_generator.py - 4 cambios principales, compila
  2. server.py - Budget + wiring, compila
  3. reference_listener.py - Family lock, compila
  4. abletonmcp_init.py - Hard stop, compila

Tests:

  1. test_sample_selector.py - 25/25 PASS
  2. test_phrase_plan.py - 1 nuevo test PASS

Documentación:

  1. Este informe (INFORME_FIXES_v0.1.10.md)

Total líneas modificadas: ~730 líneas
Archivos compilables: 4/4 (100%)
Tests pasando: 26/26 (100%)
Fixes completados: 4/4 (100%)

Listo para: Validación runtime con ejemplo.mp3