# GRANULAR SPRINT PART 2 — Tareas T101–T200
## Enfoque: Pro DJ Level — Mastering, Transiciones, Melodía Generativa, Validación

> **Contexto obligatorio para GLM-5:**
> - Continúa desde GRANULAR_SPRINT_PART1_T001_T100.md. Haz T001–T100 antes de empezar aquí.
> - MCP corre en WSL. Remote Script corre en Windows. No uses time.sleep() en abletonmcp_init.py.
> - Proyecto: `C:\Users\ren\Desktop\song Project\song.als` 95 BPM, Am, 16 tracks.
> - Si una herramienta MCP falla con "Arrangement clip was not materialized", documenta y continúa con audio patterns.

---

## BLOQUE F — GAIN STAGING PROFESIONAL (T101–T120)

### T101 — Implementar LUFS normalizer en sample_selector.py
**Archivo:** `sample_selector.py`
**Acción:** Añade función `estimate_lufs_from_rms(rms: float) -> float` que convierte RMS (0–1) a LUFS aproximado:
```python
def estimate_lufs_from_rms(rms: float) -> float:
    if rms <= 0: return -70.0
    import math
    return 20 * math.log10(max(rms, 1e-10)) - 3.0  # -3 dB offset para mono→stereo
```
Úsala en el scoring para dar bonus a samples que estén cerca de los targets de LUFS por rol.

### T102 — Definir LUFS targets por rol para reggaeton
**Archivo:** `sample_selector.py`
**Acción:** Añade dict de targets:
```python
REGGAETON_LUFS_TARGETS = {
    'kick':      -12.0,  # golpe fuerte, kick dembow
    'snare':     -14.0,
    'clap':      -14.0,
    'hat':       -20.0,  # hats suaves, percusión latina
    'bass_loop': -10.0,  # reese bajo muy prominente
    'perc_loop': -16.0,
    'top_loop':  -18.0,
    'synth_loop':-14.0,  # armónico principal
}
```

### T103 — Añadir LUFS bonus al score de selección
**Archivo:** `sample_selector.py`
**Acción:** En el scorer, para reggaeton, calcula:
```python
lufs_estimated = estimate_lufs_from_rms(profile.rms_energy)
lufs_target = REGGAETON_LUFS_TARGETS.get(role, -16.0)
lufs_delta = abs(lufs_estimated - lufs_target)
lufs_bonus = max(0.0, 1.0 - lufs_delta / 12.0)  # 0→1, 0 si difiere > 12 LUFS
score = score * 0.85 + lufs_bonus * 0.15
```

### T104 — Implementar side-chain kick→bass como metadata en manifest
**Archivo:** `server.py`
**Acción:** Al generar el manifest, añade sección `sidechain_config`:
```python
manifest['sidechain_config'] = {
    'kick_to_bass': {'source': 'kick_track_index', 'amount_db': -8, 'attack_ms': 5, 'release_ms': 100},
    'kick_to_pad':  {'source': 'kick_track_index', 'amount_db': -4, 'attack_ms': 10, 'release_ms': 150},
}
```
Esto no activa el sidechain automáticamente (requiere Live API que no está disponible), pero lo documenta para el operador.

### T105 — Añadir MCP tool: get_gain_staging_report
**Archivo:** `server.py`
**Acción:** Tool que hace `get_track_info` para todos los tracks y reporta el volumen de cada uno vs el target por rol, sugiriendo ajustes:
```python
@mcp.tool()
async def get_gain_staging_report() -> str:
    """Reporta el gain staging actual de todos los tracks vs targets pro."""
    ...
```

### T106 — Implementar set_track_volume_by_role en abletonmcp_init.py
**Archivo:** `abletonmcp_init.py`
**Acción:** Añade método que, dado un track_index y su rol, setea el volumen al valor calibrado:
```python
ROLE_VOLUME_TARGETS = {
    'kick': 0.85, 'clap': 0.80, 'hat': 0.65,
    'bass': 0.90, 'perc': 0.75, 'synth': 0.78,
    'top_loop': 0.70, 'harmony': 0.72,
}
```

### T107 — Verificar mono sub en AUDIO BASS (track 9)
**Acción:** `get_track_info(track_index=9)`. Si el track tiene ancho estéreo > 0, añade una nota en el manifest que el operador debe insertar un `Utility` plugin con Mono activado abajo de 200 Hz.

### T108–T115 — Bus architecture verification
**T108:** Llama a `get_track_info` para los tracks 1–5 (DRUM BUS, BASS BUS, MUSIC BUS, VOCAL BUS, FX BUS). Reporta si tienen clips o dispositivos configurados.
**T109:** Si DRUM BUS no tiene dispositivos, registra en el manifest `bus_status: requires_manual_setup`.
**T110:** Verifica que los returns (A–D) tienen al menos 1 dispositivo cada uno.
**T111:** Si A-MCP SPACE (return 0) no tiene reverb, documenta en el reporte qué falta.
**T112:** Añade MCP tool `audit_bus_architecture() -> str` que consolida T108–T111.
**T113:** En `server.py`, después de crear buses en generate_song, ejecuta `audit_bus_architecture` y añade el resultado al manifest.
**T114:** En `coherence_analyzer.py`, añade `BusArchitectureMetric` que lee el manifest y verifica si los buses están presentes y configurados.
**T115:** Añade `bus_architecture` al `CoherenceReport.to_dict()`.

### T116–T120 — Volume automation básica
**T116:** Añade MCP tool `set_section_volume_automation(track_index, section_start, section_end, volume_start, volume_end)` que crea una automación de volumen básica en el track.
**T117:** Para el track synth_loop (13), aplica: intro 0.5, build ramp 0.5→0.85, drop 0.85, break 0.4, drop_b 0.9, outro 0.4.
**T118:** Para top_loop (12): intro 0, build ramp 0→0.7, drop 0.75, break 0, drop_b 0.8, outro 0.
**T119:** Para perc_alt (11): intro 0.5, build 0.7, drop 0.85, break 0.3, drop_b 0.9.
**T120:** Documenta las automatizaciones aplicadas en el reporte de validación.

---

## BLOQUE G — GENERACIÓN MELÓDICA PROCEDURAL (T121–T145)

### T121 — Crear módulo melody_generator.py
**Archivo nuevo:** `melody_generator.py`
**Propósito:** Generación de melodías MIDI procedurales para el track harmónico (track 15).
```python
"""melody_generator.py — Generación melódica procedural para reggaeton."""
from typing import List, Dict, Tuple, Optional
import random

# Escala Am natural (semitones relativos a A)
AM_SCALE = [0, 2, 3, 5, 7, 8, 10]  # A B C D E F G
AM_ROOT = 57  # A3 en MIDI

def scale_notes(root_midi: int = AM_ROOT, octaves: int = 2) -> List[int]:
    notes = []
    for oct in range(octaves):
        for interval in AM_SCALE:
            notes.append(root_midi + interval + oct*12)
    return notes

CHORD_TONES = {
    'Am': [57, 60, 64],  # A C E
    'F':  [53, 57, 60],  # F A C
    'G':  [55, 59, 62],  # G B D
    'Em': [52, 55, 59],  # E G B
    'Dm': [50, 53, 57],  # D F A
    'C':  [48, 52, 55],  # C E G
}

@dataclass
class MidiNote:
    pitch: int
    start_beat: float
    duration_beats: float
    velocity: int = 80

def generate_chord_block(chord: str, start_beat: float, length_beats: float,
                          style: str = 'block') -> List[MidiNote]:
    """Genera un bloque de acordes MIDI."""
    tones = CHORD_TONES.get(chord, CHORD_TONES['Am'])
    notes = []
    if style == 'block':
        for tone in tones:
            notes.append(MidiNote(pitch=tone, start_beat=start_beat,
                                   duration_beats=length_beats*0.9, velocity=75))
    elif style == 'arpegio_up':
        step = length_beats / len(tones)
        for i, tone in enumerate(tones):
            notes.append(MidiNote(pitch=tone, start_beat=start_beat + i*step,
                                   duration_beats=step*0.8, velocity=70+i*3))
    elif style == 'arpegio_down':
        step = length_beats / len(tones)
        for i, tone in enumerate(reversed(tones)):
            notes.append(MidiNote(pitch=tone, start_beat=start_beat + i*step,
                                   duration_beats=step*0.8, velocity=80-i*3))
    return notes

def generate_motif(scale: List[int], start_beat: float, bars: int = 2,
                   seed: int = 42) -> List[MidiNote]:
    """Genera un motivo melódico de 2–4 notas que se repite."""
    rng = random.Random(seed)
    notes = []
    motif_notes = rng.choices(scale[:7], k=3)  # 3 notas del motif
    durations = [0.5, 1.0, 0.5]
    for bar in range(bars):
        pos = start_beat + bar * 4.0
        for note, dur in zip(motif_notes, durations):
            notes.append(MidiNote(pitch=note, start_beat=pos,
                                   duration_beats=dur, velocity=rng.randint(70,90)))
            pos += dur
    return notes

def generate_reggaeton_harmony(bpm: float = 95.0, total_beats: float = 288.0) -> Dict[str, List[MidiNote]]:
    """Genera el mapa completo de armonía reggaeton para song.als."""
    scale = scale_notes()
    progression = [
        (0,   32, 'Am', 'block'),
        (32,  32, 'F',  'arpegio_up'),
        (64,  32, 'G',  'block'),
        (96,  32, 'Em', 'arpegio_down'),
        (128, 16, 'Am', 'block'),
        (144, 16, 'F',  'block'),
        (160, 32, 'G',  'arpegio_up'),
        (192, 32, 'Am', 'block'),
        (224, 32, 'F',  'block'),
        (256, 32, 'Am', 'block'),
    ]
    result = {}
    for start, length, chord, style in progression:
        if start >= total_beats:
            break
        clip_key = f"clip_{start}"
        result[clip_key] = {
            'start_beat': start,
            'length_beats': length,
            'chord': chord,
            'notes': generate_chord_block(chord, 0, length, style)
        }
    return result
```

### T122 — Integrar melody_generator con populate_harmony_track
**Archivo:** `server.py`
**Acción:** Modifica `populate_harmony_track` (T055) para usar `melody_generator.generate_reggaeton_harmony()` en vez de la progresión hardcodeada. Esto hace que la herramienta sea configurable por estilo.

### T123 — Añadir MCP tool: generate_motif_sequence
**Archivo:** `server.py`
```python
@mcp.tool()
async def generate_motif_sequence(track_index: int, start_beat: float,
                                   bars: int = 4, seed: int = 42) -> str:
    """Genera y coloca un motivo melódico Am de 2-4 notas en el track MIDI."""
    from melody_generator import generate_motif, scale_notes, AM_ROOT
    scale = scale_notes(AM_ROOT)
    notes = generate_motif(scale, 0, bars, seed)
    r = ableton.send_command("create_arrangement_clip",
                              {"track_index": track_index, "start_time": start_beat, "length": bars*4})
    if _is_error_response(r):
        return f"[ERROR] {r.get('message','')}"
    midi_notes = [{"pitch": n.pitch, "start_time": n.start_beat, "duration": n.duration_beats, "velocity": n.velocity} for n in notes]
    ableton.send_command("add_notes_to_arrangement_clip",
                          {"track_index": track_index, "start_time": start_beat, "notes": midi_notes})
    return f"OK: {len(notes)} notes placed at beat {start_beat}"
```

### T124–T135 — Melodía y acordes avanzados
**T124:** Añade a `melody_generator.py` la función `generate_call_response(chord_a, chord_b, start, length)` que genera un patrón "pregunta" (arpegio ascendente) y "respuesta" (nota pedal).
**T125:** Añade `generate_bass_motif(style='dembow', root=A2, bars=2)` que retorna notas MIDI de bajo con el patrón tumbao/dembow.
**T126:** Expón `generate_bass_motif` como MCP tool `place_bass_pattern(track_index, start_beat, bars, style)`.
**T127:** Para la sección break (beats 128–160), genera un motivo especial: notas largas y sostenidas (whole notes) de Am, F, para crear tensión antes del build_b.
**T128:** Para build_b (beats 160–192), genera arpegios ascendentes acelerando: empieza con quarter notes, termina con 16th notes.
**T129:** Añade función `quantize_to_scale(pitch: int, scale: List[int]) -> int` que redondea un pitch al grado de escala más cercano.
**T130:** Añade función `add_passing_tones(notes: List[MidiNote], scale: List[int]) -> List[MidiNote]` que inserta notas de paso cromáticas entre saltos de más de 2 semitones.
**T131:** Valida que `melody_generator.py` compila: `python -m py_compile melody_generator.py`.
**T132:** Escribe tests unitarios `test_melody_generator.py`: test de generate_chord_block (4 casos), test de generate_motif (retorna > 0 notas), test de quantize_to_scale.
**T133:** Añade `melody_generator` al `AGENTS.md` en la sección de módulos.
**T134:** Documenta la API de `melody_generator.py` en `docs/MELODY_GENERATOR_README.md`.
**T135:** Ejecuta `populate_harmony_track` usando la nueva lógica y verifica con `get_track_info(15)` que `arrangement_clip_count >= 5`.

### T136–T145 — Síntesis granular desde samples existentes
**T136:** En `spectral_engine.py`, añade método `extract_grain(path: str, position_ratio: float, grain_ms: int = 50) -> np.ndarray` que extrae un fragmento de audio de `grain_ms` milisegundos desde la posición dada.
**T137:** Añade método `stretch_grain(grain: np.ndarray, target_duration_ms: int, sr: int = 44100) -> np.ndarray` que usa interpolación para estirar o comprimir un grano de audio.
**T138:** Añade `create_granular_texture(path: str, duration_s: float, density: float = 0.5) -> np.ndarray` que construye una textura granular mezclando granos aleatorios del sample.
**T139:** Expón `create_granular_texture` como MCP tool `generate_granular_pad(source_path, output_path, duration_s, density)`.
**T140:** Si `librosa` no está disponible en WSL, estas herramientas deben retornar gracefully: `[INFO] librosa no disponible, granular synthesis deshabilitada`.
**T141:** Documenta limitaciones de la síntesis granular en la cabecera del módulo.
**T142:** Añade test: si librosa no está disponible, `create_granular_texture` retorna None sin crash.
**T143:** Si `generate_granular_pad` genera un archivo, copiarlo a la carpeta `libreria/reggaeton/textures/` y reportar el path resultante.
**T144:** Añade el nuevo sample generado al índice espectral (`build_spectral_index.py` puede re-ejecutarse sobre la carpeta textures/).
**T145:** Reporta en `docs/GRANULAR_SYNTHESIS_RESULTS.md` qué muestras fue posible generar y qué se usaron en el proyecto.

---

## BLOQUE H — TRANSICIONES Y FX AUTOMATION (T146–T165)

### T146 — Implementar EQ automation tool
**Archivo:** `server.py`
**Acción:** Añade tool que configura parámetros de un EQ Three o AutoFilter de Ableton:
```python
@mcp.tool()
async def automate_filter_sweep(track_index: int, start_beat: float,
                                 duration_beats: float, filter_type: str = 'highpass',
                                 freq_start: float = 20.0, freq_end: float = 20000.0) -> str:
    """Automatiza un sweep de filtro en el track dado."""
    # Encuentra el dispositivo AutoFilter en el track
    # Setea los parámetros usando set_device_parameter
    ...
```

### T147 — Implementar crash on first beat of drop
**Archivo:** `arrangement_intelligence.py`
**Acción:** Método `place_crash_at_drop(drop_beat: float, crash_sample_path: str)` que usa `create_arrangement_audio_pattern` para colocar el crash exactamente en el beat especificado con duración 4 beats.

### T148 — Implementar snare roll antes del drop
**Archivo:** `arrangement_intelligence.py`
**Acción:** Método `place_snare_roll(pre_drop_beat: float, roll_bars: int = 2, snare_sample: str)` que genera una secuencia de clips de snare con densidad creciente: 1 snare por beat → 2 por beat → 4 por beat → 8 por beat en los últimos 2 bars.

### T149 — Implementar riser placement
**Archivo:** `arrangement_intelligence.py`
**Acción:** Método `place_riser(start_beat: float, drop_beat: float, riser_sample: str)` que coloca el riser desde `start_beat` hasta `drop_beat` con duración exacta.

### T150 — Añadir downlifter al final de drops
**Archivo:** `arrangement_intelligence.py`
**Acción:** Al final del drop_a (beat 128) y drop_b (beat 256), coloca un downlifter de 4 beats para marcar la salida del drop. Usa samples de la carpeta `libreria/reggaeton/fx/` si existen, o la carpeta más cercana.

### T151 — Añadir MCP tool: apply_transition_fx
**Archivo:** `server.py`
```python
@mcp.tool()
async def apply_transition_fx(section: str = 'drop_a') -> str:
    """Aplica FX de transición (crash, riser, snare roll) al section especificado."""
    from arrangement_intelligence import place_crash_at_drop, place_snare_roll, place_riser
    SECTION_BEATS = {'drop_a': 64, 'drop_b': 192}
    drop_beat = SECTION_BEATS.get(section, 64)
    results = []
    # snare roll (8 beats antes)
    results.append(place_snare_roll(drop_beat - 8, 2, snare_sample=_find_sample('snare')))
    # riser (32 beats antes)
    results.append(place_riser(drop_beat - 32, drop_beat, riser_sample=_find_sample('riser')))
    # crash on beat 1
    results.append(place_crash_at_drop(drop_beat, crash_sample=_find_sample('crash')))
    return "\n".join(str(r) for r in results)
```

### T152–T160 — Send automation para builds
**T152:** En el track 14 (AUDIO SYNTH PEAK), durante build_a (beats 32–64), automatiza el send al A-MCP SPACE de 0% a 80% (crea tensión de espacio).
**T153:** En el track 12 (AUDIO TOP LOOP), durante build_b (beats 160–192), automatiza el send al B-MCP ECHO de 0% a 60%.
**T154:** Corta abruptamente todos los sends (a 0%) exactamente en el beat 64 (drop_a) y beat 192 (drop_b).
**T155:** Para las automatizaciones de T152–T154: en `abletonmcp_init.py`, añade método `_create_send_automation(track_index, send_index, beats, values)` que crea puntos de automatización en el send.
**T156:** Verifica que `_create_send_automation` funciona llamándola sobre track 14, send 0, con 3 puntos de automatización.
**T157:** Añade MCP tool `automate_send(track_index, send_index, beats_values)` que usa `_create_send_automation`.
**T158:** Compila `abletonmcp_init.py` y reinicia Ableton.
**T159:** Verifica con `get_track_info(14)` que el track sigue teniendo sus clips intactos tras el reinicio.
**T160:** Documenta las automatizaciones en `docs/FX_AUTOMATION_APPLIED.md`.

### T161–T165 — Verbs/delays send routing
**T161:** Para A-MCP SPACE: el send level de perc_main (track 10) debe estar a 35% en el drop para dar espacio a las percusiones latinas.
**T162:** Para B-MCP ECHO: el send level de AUDIO SYNTH LOOP (track 13) debe estar a 20% en el drop (delay mínimo para no saturar).
**T163:** Para C-MCP HEAT: solo activar en el break (beats 128–160) para el track de bass.
**T164:** Para D-MCP GLUE: siempre activo en todos los tracks de audio, nivel fijo en 40%.
**T165:** Documenta en `docs/SENDS_ROUTING_GUIDE.md` qué send va a qué track y cuánto.

---

## BLOQUE I — MASTERING Y QA (T166–T180)

### T166 — Implementar loudness estimator en audio_mastering.py
**Archivo:** `audio_mastering.py`
**Acción:** Añade función `estimate_integrated_lufs(rms_array: List[float]) -> float` que dado un array de valores RMS por frame, estima el LUFS integrado usando la fórmula K-weighted simplificada.

### T167 — Añadir MCP tool: get_mix_lufs_estimate
**Archivo:** `server.py`
**Acción:** Tool que hace `get_track_info` para todos los tracks, extrae los valores de volumen, y calcula un estimado de LUFS total del mix usando los volumes de Ableton como proxy.

### T168 — Verificar headroom antes del master
**Acción:** Si el estimado de LUFS > -6 dBFS, emite warning `[MASTERING_WARNING] Mix demasiado alto, reducir gain antes de masterizar`.

### T169 — Implementar mastering preset 'reggaeton_club'
**Archivo:** `audio_mastering.py`
**Acción:** Añade a `MasteringPreset`:
```python
'reggaeton_club': MasteringPreset(
    name='Reggaeton Club',
    target_lufs=-9.0,      # nivel club
    target_true_peak=-0.3,
    low_end_mono_hz=200.0,
    multiband_compression=True,
    tape_saturation=True,
    high_shelf_boost_hz=10000,
    high_shelf_boost_db=0.5,
    style_notes='Kick prominente, bajo en mono, énfasis en graves, ideal para sistemas PA clubs'
)
```

### T170 — Documentar cadena de mastering en manifest
**Archivo:** `server.py`
**Acción:** Al finalizar la generación, añade al manifest `mastering_preset: 'reggaeton_club'` y `mastering_notes: [...]` con las recomendaciones del preset.

### T171–T175 — QA automático post-generación
**T171:** En `server.py`, al completar `generate_song_async`, ejecuta automáticamente `audit_project_coherence()` y adjunta el resultado al manifest.
**T172:** Si el coherence score < 5.0, escribe un warning en el manifest `quality_warning: coherence_below_threshold`.
**T173:** Si `drum_coverage_ratio < 0.55`, ejecuta automáticamente `fill_arrangement_gaps()` como post-proceso.
**T174:** Si `harmonic_coverage_ratio < 0.60`, ejecuta automáticamente `populate_harmony_track()` como post-proceso.
**T175:** Documenta en el manifest qué post-procesos automáticos se ejecutaron.

### T176–T180 — Validación final del proyecto
**T176:** Llama a `get_session_info`. Verifica BPM=95, tracks >= 16.
**T177:** Llama a `get_track_info(15)`. Verifica `arrangement_clip_count >= 5`.
**T178:** Llama a `audit_project_coherence()`. Verifica que el score > 4.0.
**T179:** Llama a `find_similar_samples` sobre el sample de perc principal vs la carpeta `libreria/reggaeton/perc loop/`. Verifica que retorna al menos 3 resultados.
**T180:** Escribe `docs/SPRINT_GRANULAR_PART2_VALIDATION.md` con los outputs exactos de T176–T179.

---

## BLOQUE J — TESTING Y DOCUMENTACIÓN (T181–T200)

### T181 — Crear test_spectral_integration.py
**Archivo:** `AbletonMCP_AI/AbletonMCP_AI/MCP_Server/tests/test_spectral_integration.py`
**Tests:**
- `test_spectral_engine_imports_without_crash`
- `test_similarity_identity` (mismo sample → similitud 1.0)
- `test_similarity_range` (resultado entre 0 y 1)
- `test_basic_analysis_fallback` (sin librosa → devuelve SpectralProfile básico)

### T182 — Crear test_melody_generator.py
**Tests:**
- `test_chord_block_am` → verifica que retorna notas en pitches Am
- `test_motif_seeds` → mismo seed → mismas notas
- `test_generate_reggaeton_harmony` → retorna al menos 5 clips

### T183 — Crear test_arrangement_intelligence.py
**Tests:**
- `test_structure_beats` → verifica que la estructura de T086 no se superpone
- `test_energy_curve` → drop tiene energía > break
- `test_mute_throw_beats` → los beats 61–64 están correctamente marcados

### T184 — Crear test_gain_staging.py
**Tests:**
- `test_estimate_lufs_from_rms` → rms=0.5 da ≈ -9.0 LUFS
- `test_lufs_targets_defined_for_all_roles`
- `test_lufs_bonus_within_range` → bonus entre 0 y 1

### T185 — Ejecutar todos los tests
```powershell
python -m pytest "C:\ProgramData\Ableton\Live 12 Suite\Resources\MIDI Remote Scripts\AbletonMCP_AI\AbletonMCP_AI\MCP_Server\tests\" -v --tb=short 2>&1 | Tee-Object -FilePath "docs\TEST_RESULTS_GRANULAR.txt"
```
Objetivo: todos los tests pasan. Documenta los que fallan.

### T186 — Actualizar AGENTS.md con nuevos módulos
**Acción:** Añade tabla de módulos nuevos al AGENTS.md:
| Módulo | Propósito | Estado |
|---|---|---|
| `spectral_engine.py` | Análisis espectral y similitud tímbrica | Nuevo en Granular Sprint |
| `melody_generator.py` | Generación melódica procedural Am reggaeton | Nuevo en Granular Sprint |
| `arrangement_intelligence.py` | Estructura de arrangement y transiciones DJ | Nuevo en Granular Sprint |
| `build_spectral_index.py` | Indexado offline de la librería de samples | Nuevo en Granular Sprint |

### T187 — Actualizar roadmap.md marcando completados
**Acción:** Marca como `[x]` los ítems del roadmap que estos sprints completaron:
- FASE 4.3: Spectral Fingerprinting (T016–T045)
- FASE 7.1: Scale-aware melody / Motif engine (T121–T135)
- FASE 7.2: Chord progressions reggaeton (T048)
- FASE 7.3: Bass line dembow (T050–T051)
- FASE 2.3: Crash + snare roll (T147–T148)
- FASE 1.1: LUFS estimator (T101–T103)

### T188 — Actualizar KIMI_K2_ACTIVE_HANDOFF.md
**Acción:** Reemplaza "Sprint activo" con referencia a los archivos `GRANULAR_SPRINT_PART1` y `GRANULAR_SPRINT_PART2`. Actualiza "Estado real verificado" con la fecha actual y los módulos creados en este sprint.

### T189 — Crear SPRINT_GRANULAR_VALIDATION_REPORT.md
**Archivo nuevo:** `docs/SPRINT_GRANULAR_VALIDATION_REPORT.md`
**Contenido mínimo:**
- Fecha y sesión
- Lista de tareas T001–T200 con status: ✅ OK / ❌ FAIL / ⚠️ PARTIAL
- Outputs exactos de `get_session_info`, `get_track_info(15)`, `audit_project_coherence()`
- Tests que pasaron y fallaron
- Problemas encontrados y su causa raíz
- Estado del índice espectral (cuántos samples indexados)
- Estado del proyecto musical (clips en arrangement, cobertura, score)

### T190 — Fix final: verificar que ProxyClip no crashea callers
**Archivo:** `abletonmcp_init.py`
**Acción:** La clase `ProxyClip` local debe exponer todos los atributos que el caller de `_record_session_clip_to_arrangement` podría acceder: `name`, `length`, `start_time`, `looping=False`, `is_midi_clip=True`. Añade estos atributos al `__init__` del ProxyClip.

### T191 — Asegurar que el track 0 (1-MIDI) no interfiere
**Acción:** `get_track_info(track_index=0)`. Si tiene clips en beats 0–288, verifica que no se superponen con el harmónico (track 15). Si hay conflicto, documenta.

### T192 — Verificar que los 5 buses (tracks 1–5) están sin clips
**Acción:** `get_track_info` para indices 1–5. Los buses no deben tener arrangement clips. Si los tienen, es un error de generación previo. Documenta.

### T193 — Run compileall completo
```powershell
python -m compileall "C:\ProgramData\Ableton\Live 12 Suite\Resources\MIDI Remote Scripts\AbletonMCP_AI" 2>&1 | Tee-Object "docs\COMPILE_RESULTS.txt"
```
Objetivo: 0 errores de compilación.

### T194 — Verificar conectividad MCP tras todos los cambios
**Acción:** Si hay cambios en `server.py`, reinicia el MCP server. Llama a `get_session_info`. Si falla, revisa `Log.txt` y documenta el error exacto.

### T195 — Crear checklist de "listo para usar" para el usuario
**Archivo:** `docs/READY_CHECKLIST.md`
**Contenido:**
```markdown
# Checklist: AbletonMCP-AI Granular Sprint Listo

## Técnico
- [ ] spectral_engine.py compila
- [ ] melody_generator.py compila
- [ ] arrangement_intelligence.py compila
- [ ] spectral_index.json generado (≥ 50 samples)
- [ ] Tests pasan (ver TEST_RESULTS_GRANULAR.txt)

## Musical (verificar en Ableton)
- [ ] Track 15 (HARMONY_PIANO_MIDI) tiene ≥ 5 clips en arrangement
- [ ] Track 12 (AUDIO TOP LOOP) no tiene gaps > 32 beats
- [ ] Track 11 (AUDIO PERC ALT) no tiene gaps > 32 beats
- [ ] audit_project_coherence() score ≥ 4.0
- [ ] drum_coverage_ratio ≥ 0.55
- [ ] harmonic_coverage_ratio ≥ 0.60

## Herramientas MCP disponibles
- [ ] analyze_sample_spectrum funciona
- [ ] find_similar_samples funciona
- [ ] populate_harmony_track funciona
- [ ] fill_arrangement_gaps funciona
- [ ] apply_transition_fx funciona
- [ ] audit_bus_architecture funciona
```

### T196 — Bonus: Crear smoke test reggaeton granular
**Archivo:** `temp/smoke_test_granular_reggaeton.py`
**Acción:** Script que verifica el pipeline completo del sprint en 5 pasos:
1. Conectar al MCP
2. `analyze_sample_spectrum(perc_sample_path)` → ok
3. `find_similar_samples(perc_sample, reggaeton_folder)` → retorna ≥ 3
4. `populate_harmony_track(15, 'Am', 95.0)` → ok
5. `audit_project_coherence()` → score > 3.0

### T197 — Bonus: Añadir modo verbose a spectral_engine
**Archivo:** `spectral_engine.py`
**Acción:** Añade `SpectralEngine(verbose=True)` que loggea el perfil completo de cada sample analizado en modo `[SPECTRAL_VERBOSE]`.

### T198 — Bonus: Añadir cache invalidation a SpectralEngine
**Archivo:** `spectral_engine.py`
**Acción:** Añade `invalidate_cache(path: str)` y `clear_cache()` para que cuando un sample se modifica, su perfil se recalcula.

### T199 — Bonus: Conectar melody_generator con reference_listener
**Archivo:** `reference_listener.py` + `melody_generator.py`
**Acción:** Si `reference_listener` detecta que la referencia está en Am, pasa `root_midi=57` al `melody_generator`. Si detecta Dm, pasa `root_midi=50`. Esto hace que la melodía generada esté en la misma clave que la referencia.

### T200 — ENTREGA FINAL
**Acción:** Escribe el reporte final `docs/SPRINT_GRANULAR_ENTREGA_FINAL.md` con:
1. Resumen ejecutivo (3 líneas): qué se logró, qué score de coherencia, qué faltó.
2. Tabla de módulos nuevos con tamaño en KB.
3. Outputs de los 5 pasos del smoke test (T196).
4. Recomendaciones para el siguiente sprint.
5. Una línea honesta: si el proyecto `song.als` suena mejor aún, peor o igual, y por qué.

**Criterio de éxito del sprint completo:**
- `audit_project_coherence()` score ≥ 5.0
- `harmonic_coverage_ratio ≥ 0.70`
- `drum_coverage_ratio ≥ 0.60`
- Al menos 3 herramientas MCP nuevas funcionando
- `spectral_index.json` con ≥ 30 samples
- 0 errores de compilación en todos los archivos modificados
