# Proposal: Fix Sidechain CC11 — Zero Events

## Intent

`scripts/compose.py` populates `_kick_cache` via `_get_kick_cache()` → `DrumLoopAnalyzer.analyze()` but never passes it to `build_bass_track()` at line 767. Result: `kick_cache` parameter defaults to `{}`, zero CC11 events generated in every `.rpp`. Drumloop WAV files exist on disk (verified — all 5 variants present). 302/302 tests pass because tests call `build_bass_track(kick_cache={...})` directly, bypassing the broken `main()` wiring.

## Scope

### In Scope
- Pass `kick_cache=_kick_cache` to `build_bass_track()` in `main()` (1 line)
- Add diagnostic log: kick count per drumloop after cache population (2 lines)
- Add integration test exercising `_get_kick_cache()` → `build_bass_track()` full path, asserting `midi_cc` non-empty (catches regression)

### Out of Scope
- Refactoring module-level `_kick_cache` state
- Fixing double-build of `drumloop_track` (separate optimization)
- Changing CC11 duck depth, shape, or DrumLoopAnalyzer accuracy

## Capabilities

### New Capabilities
- `sidechain-cc-diagnostics`: diagnostic output logs kick count per analyzed drumloop at composition time

### Modified Capabilities
- `bass-generation`: `build_bass_track()` now receives live kick cache in production path (was always `{}`); bass clips gain CC11 Expression ducking events

## Approach

**Fix (1 line, `scripts/compose.py`):**
```python
build_bass_track(sections, offsets, key_root, key_minor, kick_cache=_kick_cache),
```

**Diagnostic log (after `_get_kick_cache()` call):**
```python
for path, kicks in _kick_cache.items():
    print(f"  Kick cache: {len(kicks)} kicks in {Path(path).name}")
```

**Test:** New `test_bass_track_populates_cc_from_main_path` in `test_compose_integration.py` — calls `_get_kick_cache()` with real drumloop paths, then `build_bass_track(kick_cache=_kick_cache)`, asserts at least one bass clip has `len(midi_cc) > 0`.

**Why existing tests missed it:** All 4 sidechain tests pass `kick_cache` explicitly to `build_bass_track()`, so the `main()` wiring gap is untested.

**Verified non-causes:**
- Pipeline: `CCEvent` dataclass ✅, `ClipDef.midi_cc` ✅, `RPPBuilder` emits `B0 0B` E-lines ✅, `build_bass_track()` filter+triplet logic ✅
- Disk: all 5 drumloop WAV files exist at `ABLETON_DRUMLOOP_DIR`

## Affected Areas

| Area | Impact | Description |
|------|--------|-------------|
| `scripts/compose.py` | 3 lines added/changed | Pass `kick_cache`, diagnostic log |
| `tests/test_compose_integration.py` | +1 test | Full-path kick cache → bass CC regression test |

## Risks

| Risk | Likelihood | Mitigation |
|------|------------|------------|
| DrumLoopAnalyzer fails on WAV | Med | `try/except` returns `[]` per path — no crash, just no ducking for that section |
| librosa unavailable on CI | Low | Already a project dependency; existing analyzer tests pass |
| CC events malform REAPER playback | Very Low | Valid delta-encoded E-lines interleaved with notes |

## Rollback Plan

Remove `kick_cache=_kick_cache` argument. Reverts to zero CC events (current behavior). No schema changes.

## Dependencies

None — pure wiring fix using existing code paths.

## Success Criteria

- [ ] `rg "B0 0B" output/test.rpp` returns non-empty results after `compose.py` run
- [ ] Diagnostic prints kick counts (e.g., "Kick cache: 32 kicks in 90bpm reggaeton antiguo drumloop.wav")
- [ ] All 302 existing tests pass
- [ ] New integration test fails before fix, passes after
