feat: Implement senior audio injection with 5 fallback methods

- Add _cmd_create_arrangement_audio_pattern with 5-method fallback chain
- Method 1: track.insert_arrangement_clip() [Live 12+]
- Method 2: track.create_audio_clip() [Live 11+]
- Method 3: arrangement_clips.add_new_clip() [Live 12+]
- Method 4: Session->duplicate_clip_to_arrangement [Legacy]
- Method 5: Session->Recording [Universal]

- Add _cmd_duplicate_clip_to_arrangement for session-to-arrangement workflow
- Update skills documentation
- Verified: 3 clips created at positions [0, 4, 8] in Arrangement View

Closes: Audio injection in Arrangement View
This commit is contained in:
OpenCode Agent
2026-04-12 14:02:32 -03:00
commit 5ce8187c65
118 changed files with 55075 additions and 0 deletions

View File

@@ -0,0 +1,493 @@
# 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