# ANÁLISIS CRÍTICO - AbletonMCP_AI v2.0

> **Fecha**: 2026-04-11  
> **Agentes desplegados**: 5 (análisis paralelo)  
> **Archivo analizado**: `AbletonMCP_AI/__init__.py` (4,428 líneas)  
> **Problema**: Clips no visibles en Arrangement View  
> **Estado**: CRÍTICO - Requiere fixes inmediatos

---

## RESUMEN EJECUTIVO

**Diagnóstico**: El sistema MCP está **funcional técnicamente** pero tiene **problemas de integración con la UI de Ableton Live 12**.

| Problema | Causa Raíz | Impacto |
|----------|-----------|---------|
| **Clips no visibles** | Se crean en Session View, usuario ve Arrangement View | 🔴 CRÍTICO |
| **`produce_with_library: 0`** | `SampleSelector` no encuentra samples | 🟡 ALTO |
| **Arrangement handlers engañosos** | Nombre dice "arrangement" pero crea en Session | 🟡 ALTO |
| **Race condition en dispatch** | Tareas se encolan pero UI puede no refrescar | 🟠 MEDIO |
| **Inconsistencias de reporte** | Diferentes tools reportan diferentes cantidades de tracks | 🟠 MEDIO |

---

## PROBLEMA #1: Clips Creados en Session View (NO Arrangement)

### 🔴 CRÍTICO - Usuario no ve contenido

**Estado Actual**:
- ✅ Comandos retornan "success"
- ✅ Tracks se crean correctamente
- ❌ **Clips NO visibles en Arrangement View**
- ❌ **Usuario no puede ver ni escuchar el contenido**

### Análisis Técnico

**Handler**: `_cmd_generate_midi_clip()` (líneas 1,816-1,860)

```python
def _cmd_generate_midi_clip(self, track_index, clip_index, notes, **kw):
    t = self._song.tracks[int(track_index)]
    slot = t.clip_slots[int(clip_index)]  # ← SESSION VIEW
    
    if slot.has_clip:
        slot.delete_clip()
    
    slot.create_clip(float(clip_length))  # ← CREA EN SESSION
    slot.clip.set_notes(tuple(live_notes))  # ← NOTAS EN SESSION
```

**Handler**: `_cmd_load_sample_direct()` (líneas 3,822-3,877)

```python
def _cmd_load_sample_direct(self, track_index, file_path, slot_index=0, ...):
    t = self._song.tracks[int(track_index)]
    slot = t.clip_slots[int(slot_index)]  # ← SESSION VIEW
    
    clip = slot.create_audio_clip(fpath)  # ← CREA EN SESSION
```

**La API de Ableton Live Python NO tiene método directo para crear clips en Arrangement View.**

La única forma es:
1. Crear clips en Session View (`clip_slots`)
2. Activar `arrangement_overdub = True`
3. Disparar clips con `slot.fire()`
4. Live captura automáticamente a Arrangement durante playback

### Solución Propuesta

#### Opción A: Parámetro `arrangement=True` (Recomendada)

Modificar `_cmd_generate_midi_clip()` para intentar primero Arrangement:

```python
def _cmd_generate_midi_clip(self, track_index, clip_index, notes, 
                              arrangement=False, start_time=0.0, **kw):
    t = self._song.tracks[int(track_index)]
    
    # Intentar crear en Arrangement View primero
    if arrangement:
        arr_clips = getattr(t, "arrangement_clips", None)
        if arr_clips is not None:
            try:
                beats_per_bar = int(self._song.signature_numerator)
                start_beat = start_time * beats_per_bar
                end_beat = start_beat + 4.0 * beats_per_bar
                
                # Live 12+ API
                new_clip = arr_clips.add_new_clip(start_beat, end_beat)
                if new_clip and notes:
                    new_clip.set_notes(tuple(live_notes))
                    return {
                        "created": True, 
                        "track_index": track_index,
                        "start_time": start_time,
                        "notes_added": len(notes),
                        "view": "arrangement"  # ← EXPLÍCITO
                    }
            except Exception:
                pass  # Fallback a Session
    
    # Fallback: Session View (comportamiento actual)
    slot = t.clip_slots[int(clip_index)]
    slot.create_clip(4.0)
    # ... resto del código
    return {
        "created": True,
        "view": "session",  # ← EXPLÍCITO
        "note": "Clip created in Session View. Use fire_clip + record_to_arrangement to capture."
    }
```

#### Opción B: Grabación Automática (produce_with_library)

En `_cmd_produce_with_library()`, después de crear todos los clips:

```python
def _cmd_produce_with_library(self, genre="reggaeton", tempo=95, ...):
    # ... crear tracks y clips en Session View ...
    
    # GRABAR AUTOMÁTICAMENTE A ARRANGEMENT
    if record_arrangement:
        self._enable_arrangement_overdub()
        self._song.current_song_time = 0.0
        
        # Disparar todos los clips
        for track in tracks_creados:
            if track.clip_slots[0].has_clip:
                track.clip_slots[0].fire()
        
        # Iniciar grabación
        self._song.start_playing()
        
        # Detener después de bars
        import threading, time
        def stop_after():
            time.sleep(bars * 4 * 60.0 / tempo)
            self._song.stop_playing()
            self._song.arrangement_overdub = False
            # Cambiar a Arrangement View
            app = self._get_app()
            if app:
                app.view.show_view("Arranger")
        
        threading.Thread(target=stop_after, daemon=True).start()
```

#### Opción C: Cambiar a Session View (mostrar al usuario)

Después de crear clips, forzar Ableton a mostrar Session View:

```python
def _cmd_generate_midi_clip(self, track_index, clip_index, notes, **kw):
    # ... crear clip ...
    
    # CAMBIAR A SESSION VIEW para que sea visible
    app = self._get_app()
    if app and hasattr(app, "view"):
        app.view.show_view("Session")
    
    return {"created": True, "view": "session"}
```

---

## PROBLEMA #2: `produce_with_library` Reporta 0 Samples

### 🟡 ALTO - Pipeline de producción incompleto

**Estado Actual**:
- ✅ Pipeline ejecuta sin errores
- ❌ **0 samples cargados de la librería**
- ❌ Tracks creados pero vacíos

### Análisis Técnico

**Handler**: `_cmd_produce_with_library()` (líneas 3,879-3,980)

Flujo de ejecución:
```
1. produce_with_library()
   ↓
2. Llama _cmd_load_samples_for_genre()
   ↓
3. SampleSelector.select_for_genre() retorna objeto 'group'
   ↓
4. Intenta acceder a: group.drums.kick, group.drums.snare, etc.
   ↓
5. Si group.drums es None → CONTINUE (skip silencioso)
   ↓
6. Resultado: 0 tracks creados, 0 samples cargados
```

**Causas posibles**:
1. **Import de SampleSelector falla** (línea 1,608) - Si hay error, continúa con `group = None`
2. **`group.drums` es None** - Todos los drums fallan
3. **Paths de samples no existen** - Verificación `os.path.isfile()` falla
4. **`group.bass`, `group.synths`, `group.fx` son None o vacíos**

### Código Problemático

```python
def _cmd_load_samples_for_genre(self, genre, key="", bpm=0, ...):
    try:
        from engines.sample_selector import SampleSelector
        selector = SampleSelector()
        group = selector.select_for_genre(str(genre), str(key) if key else None, ...)
    except Exception as e:
        self.log_message("T008 selector error: %s" % str(e))
        return {"error": "SampleSelector failed: %s" % str(e)}  # ← Retorna error
    
    # ... si hay error arriba, nunca llega aquí ...
    
    drum_map = [
        ("Kick", getattr(group.drums, "kick", None), 36),   # ← Si group.drums es None → None
        ("Snare", getattr(group.drums, "snare", None), 38), # ← Todos fallan
        # ...
    ]
    for name, info, pad in drum_map:
        if info is None or not os.path.isfile(info.path):  # ← SKIP si None
            continue  # ← SILENCIOSO
```

### Solución Propuesta

#### Fix: Agregar validación y fallback

```python
def _cmd_produce_with_library(self, genre="reggaeton", tempo=95, ...):
    # ...
    sample_result = self._cmd_load_samples_for_genre(genre=genre, key=key, bpm=float(tempo))
    
    # AGREGAR: Validación de error
    if sample_result.get("error"):
        # FALLBACK: Usar get_recommended_samples
        try:
            from engines.sample_selector import SampleSelector
            selector = SampleSelector()
            
            # Cargar manualmente con get_recommended_samples
            drum_samples = selector.get_recommended_samples("drums", count=4)
            bass_samples = selector.get_recommended_samples("bass", count=2)
            
            for sample_info in drum_samples:
                # Crear track y cargar
                self._song.create_audio_track(-1)
                idx = len(self._song.tracks) - 1
                t = self._song.tracks[idx]
                t.name = sample_info.role
                self._cmd_load_sample_direct(idx, sample_info.path, auto_fire=True)
                
            steps.append("Fallback: loaded %d samples via get_recommended_samples" % len(drum_samples))
        except Exception as fallback_err:
            steps.append("CRITICAL: Both methods failed: %s" % str(fallback_err))
    else:
        steps.append("library: %d tracks, %d samples loaded" % (
            sample_result.get("tracks_created", 0),
            sample_result.get("samples_loaded", 0),
        ))
    
    # AGREGAR: Warning si 0 samples
    if sample_result.get("samples_loaded", 0) == 0:
        steps.append("WARNING: No samples loaded. Check library path: %s" % selector._library)
```

#### Fix: Debug logging en SampleSelector

```python
def _cmd_load_samples_for_genre(self, genre, key="", bpm=0, ...):
    # ...
    group = selector.select_for_genre(str(genre), ...)
    
    # AGREGAR: Debug
    self.log_message("SampleSelector returned group: %s" % str(group))
    if group:
        self.log_message("group.drums: %s" % str(getattr(group, 'drums', None)))
        self.log_message("group.bass: %s" % str(getattr(group, 'bass', None)))
    
    # ... resto del código
```

---

## PROBLEMA #3: Handlers con Nombres Engañosos

### 🟡 ALTO - Documentación incorrecta

**Problema**: Handlers con "arrangement" en el nombre que NO crean en Arrangement View.

### Lista de Handlers Afectados

| Handler | Líneas | Nombre Sugerido | Problema |
|---------|--------|-----------------|----------|
| `_cmd_create_arrangement_midi_clip` | 841-932 | `create_midi_clip_with_fallback` | Intenta Arrangement, fallback a Session |
| `_cmd_create_arrangement_audio_pattern` | 553-575 | `create_audio_pattern_session` | Solo crea en Session (slot 0) |
| `_cmd_duplicate_session_to_arrangement` | 751-777 | `fire_session_clips` | Solo hace fire, no duplica |
| `_cmd_record_to_arrangement` | 3713-3775 | `fire_and_record_session` | Activa overdub pero no garantiza grabación |

### Solución Propuesta

#### Opción A: Renombrar handlers para reflejar comportamiento real

```python
# Antes
def _cmd_create_arrangement_midi_clip(self, ...):  # Engañoso

# Después  
def _cmd_create_midi_clip_arrangement_or_session(self, ...):  # Claro
    """Create MIDI clip - attempts Arrangement, falls back to Session View."""
```

#### Opción B: Implementar comportamiento real de Arrangement

Para `_cmd_record_to_arrangement()`:

```python
def _cmd_record_to_arrangement_fixed(self, duration_bars=8, **kw):
    """ACTUALMENTE: Activa overdub y dispara clips
       NECESITA: Scheduler real que capture a Arrangement"""
    
    # Usar el scheduler ya implementado en build_song (líneas 4314-4403)
    return self._cmd_build_song(bpm=self._song.tempo, key="Am", 
                                record_duration=duration_bars, 
                                only_record=True)
```

---

## PROBLEMA #4: Race Condition en Dispatch

### 🟠 MEDIO - Tareas pueden no ejecutarse inmediatamente

### Análisis Técnico

**Arquitectura de Threads**:
```
MCP Server Thread          Ableton Live UI Thread (Main)
     |                              |
     |── _dispatch()                |── update_display() [~100ms]
     |   └── añade task              |   └── ejecuta task()
     |       a _pending_tasks[]      |
     |                              |
     └── q.get(timeout=30s) ←───────┘
             ↑                          
             └── espera resultado
```

**Problema**: El cliente MCP espera el resultado vía `q.get(timeout=30s)`, pero la tarea solo se ejecuta cuando Live llama `update_display()` (cada ~100ms).

Si Live está ocupado o en background, `update_display()` puede tardar más, causando timeout.

### Solución Propuesta

#### Opción A: Timeout más corto + retry

```python
def _dispatch(self, cmd):
    # ... añadir task a cola ...
    
    # Reducir timeout de 30s a 5s
    try:
        resp = q.get(timeout=5.0)
    except _queue.Empty:
        # Intentar ejecutar directamente como fallback
        try:
            result = task()  # Ejecutar ahora
            return {"status": "success", "result": result}
        except Exception as e:
            return {"status": "error", "message": "Timeout and direct execution failed: %s" % str(e)}
```

#### Opción B: Health check de update_display

```python
def update_display(self):
    self._last_update_time = time.time()  # Registrar
    # ... resto del código

# Nuevo comando MCP
def _cmd_health_check_dispatch(self):
    last = getattr(self, '_last_update_time', 0)
    elapsed = time.time() - last
    if elapsed > 5.0:  # No se llamó en 5 segundos
        return {"healthy": False, "issue": "update_display not called in %ds" % elapsed}
    return {"healthy": True, "last_update_ms": int(elapsed * 1000)}
```

---

## PROBLEMA #5: Inconsistencias de Reporte

### 🟠 MEDIO - Diferentes tools reportan diferentes datos

### Inconsistencias Encontradas

| Tool | Tracks Reportados | Estado |
|------|-------------------|--------|
| `get_tracks()` | 4 | ✅ Correcto |
| `get_project_summary()` | 0 | ❌ Incorrecto |
| `validate_project()` | "proyecto sin tracks" | ❌ Incorrecto |
| `full_quality_check()` | 4 tracks vacíos | ✅ Correcto |
| `get_workflow_status()` | 4 tracks con nombres | ✅ Correcto |

### Causa Técnica

`get_project_summary()` no está iterando sobre `self._song.tracks` correctamente:

```python
def _cmd_get_project_summary(self):
    # PROBLEMA: Esto retorna 0
    track_count = len([t for t in self._song.tracks if t.is_visible])  # ← is_visible?
    
    # CORRECCIÓN: Debería ser
    track_count = len(self._song.tracks)  # Todos los tracks
```

### Solución

```python
def _cmd_get_project_summary(self):
    tracks = list(self._song.tracks)  # Convertir a lista explícita
    midi_tracks = [t for t in tracks if hasattr(t, 'has_midi_input') and t.has_midi_input]
    audio_tracks = [t for t in tracks if hasattr(t, 'has_audio_input') and t.has_audio_input]
    
    return {
        "track_count": len(tracks),  # ← CORREGIDO
        "midi_tracks": len(midi_tracks),
        "audio_tracks": len(audio_tracks),
        # ... resto
    }
```

---

## PRIORIDADES DE FIX

### 🔴 URGENTE (Bloquea producción)

1. **Agregar parámetro `arrangement=True`** a `generate_midi_clip()` y `load_sample_direct()`
2. **Implementar grabación real** en `record_to_arrangement()` usando el scheduler de `build_song`
3. **Fix `produce_with_library`** para usar `get_recommended_samples()` como fallback

### 🟡 ALTO (Mejora UX)

4. **Renombrar handlers** o agregar documentación clara sobre Session vs Arrangement
5. **Corregir `get_project_summary()`** para reportar tracks correctamente
6. **Agregar debug logging** en SampleSelector para diagnóstico

### 🟢 MEDIO (Optimización)

7. **Reducir timeout** en dispatch de 30s a 5s
8. **Agregar health check** de update_display
9. **Optimizar** cola de pending_tasks

---

## FLUJO RECOMENDADO POST-FIX

### Para Usuario:

```python
# 1. Setup
/set_tempo 95
/set_time_signature 4 4

# 2. Producción con Arrangement View explícito
/produce_with_library genre=reggaeton key=Am tempo=95 bars=16 record_arrangement=true

# 3. Si produce_with_library falla, modo manual:
/scan_library subfolder=reggaeton/kick
/load_sample_direct track=2 file=.../kick 1.wav arrangement=true start_time=0
/generate_midi_clip track=0 notes=[...] arrangement=true start_time=0

# 4. Verificar en Arrangement View
/show_arrangement_view  # Cambia la vista
/get_arrangement_clips  # Lista clips en Arrangement
```

---

## ARCHIVOS DE REFERENCIA

- **Archivo principal**: `AbletonMCP_AI/__init__.py` (4,428 líneas)
- **Handlers críticos**: Líneas 553-932 (Arrangement), 1,816-1,860 (MIDI), 3,822-3,980 (Samples)
- **Scheduler de grabación**: Líneas 4,314-4,403 (`build_song`)

---

**Generado por**: 5 agentes paralelos (Kimi K2)  
**Fecha**: 2026-04-11  
**Para**: Qwen (Review/Implementation)  
**Status**: Listo para Sprint de Fixes
