# Proposal: 808 Bass Sidechain Ducking

## Intent

808 bass and kick drum overlap in low frequencies with zero separation. Professional reggaeton uses sidechain-style ducking — bass dips when kick hits — creating the "pumping" feel and preventing low-frequency mud. Currently `build_bass_track()` generates static-velocity MIDI notes with no awareness of the drumloop's kick pattern.

## Scope

### In Scope
- Pre-analyze drumloop WAV files to extract kick transient positions via `DrumLoopAnalyzer`
- Cache kick beat-positions per drumloop path (same file reused across sections)
- Generate MIDI CC11 (Expression) events on bass clips at kick hit positions
- Duck shape: instantaneous drop to CC11≈50, 80ms release ramp to CC11=127
- `ClipDef` schema extended with `midi_cc: list[CCEvent]` field
- `RPPBuilder._build_midi_source()` emits CC E-lines interleaved with Note events

### Out of Scope
- Track-level volume automation envelopes (`VOLENV2`) — complex binary encoding, deferred
- ReaComp-sidechain routing via ReaScript — Phase 2 enhancement only
- DrumLoopAnalyzer integration at composition time (not pre-cached) — deferred to Phase 2
- Ducking for non-bass tracks (chords, lead, pad)
- User-configurable duck depth/shape — constants only

## Capabilities

### New Capabilities
- `midi-cc-events`: MIDI CC event emission in `.rpp` source — CC11 Expression events interleaved with notes in E-line stream
- `kick-detection-cache`: `DrumLoopAnalyzer` tied into composition pipeline; kick positions cached per drumloop WAV path

### Modified Capabilities
- `bass-generation`: `build_bass_track()` accepts kick position data and generates per-note velocity ducking OR CC11 events synchronized to kick hits
- `rpp-clip-encoding`: `_build_midi_source()` emits `E B0 0B xx` lines alongside Note On/Off

## Approach

**Principle**: MIDI CC11 (Expression) is the simplest `.rpp`-native sidechain. No REAPER-specific features, no binary envelope encoding, no ReaScript bridge. Pure MIDI standard — works with any synth (Serum 2 confirmed).

**Data flow**:
```
Drumloop WAV
  → DrumLoopAnalyzer.analyze() → transient_positions("kick")
  → beat-positions cache (dict[str, list[float]])
  → build_bass_track(sections, offsets, key_root, key_minor, kick_cache={})
  → generates CCEvent objects {controller=11, time, value}
  → ClipDef.midi_cc = [...]
  → RPPBuilder._build_midi_source() emits E-lines
```

**CC11 ducking shape per kick hit** (all times in beats relative to clip start):
| Offset from kick | CC11 Value | Description |
|-----------------|------------|-------------|
| kick_time       | 50         | Instant dip (~-9dB) |
| kick_time + 0.02| 50         | Hold through transient |
| kick_time + 0.18| 127        | Release complete (80ms ≈ 0.16 beats) |

**Key decision — MIDI CC11 vs alternatives**:

| Option | Verdict | Why |
|--------|---------|-----|
| **A: MIDI CC11 (Expression)** | ✅ Chosen | `.rpp` MIDI source format supports `E B0 0B xx` lines. Serum 2, most synths respond. Trivial builder change. |
| B: Track volume envelope (VOLENV2) | ❌ Rejected | Binary/chunk encoding in `.rpp` — fragile, hard to debug, no benefit over CC11 for this use case. |
| C: ReaScript ReaComp sidechain | ⏸️ Deferred | Works only in Phase 2 with REAPER running. Use as future enhancement for non-MIDI audio basses. |

## Affected Areas

| Area | Impact | Description |
|------|--------|-------------|
| `src/core/schema.py` | Modified | Add `CCEvent` dataclass (`controller`, `time`, `value`); add `midi_cc: list[CCEvent]` to `ClipDef` |
| `scripts/compose.py` | Modified | Add `_get_kick_cache()`, pass to `build_bass_track()`, generate CC11 events in bass clips |
| `src/reaper_builder/__init__.py` | Modified | `_build_midi_source()` interleaves CC events into E-line stream |
| `src/composer/drum_analyzer.py` | Unchanged | Already exports `transient_positions("kick")` — zero changes needed |
| `tests/test_compose_integration.py` | Modified | Verify CC events present in bass clips, correct CC11 values at kick positions |
| `tests/test_reaper_builder.py` | Modified | Verify `_build_midi_source()` emits `B0 0B` E-lines |

## Risks

| Risk | Likelihood | Mitigation |
|------|------------|------------|
| Synth doesn't respond to CC11 | Low | Serum 2, Omnisphere, Diva all support it. Add `_CC11_VOLUME_MIN` constant for easy disable (set to 127 = no ducking). |
| DrumloopAnalyzer misclassifies kick transients | Med | Only use transients with `confidence > 0.6`; add `KICK_CONFIDENCE_THRESHOLD = 0.6` constant. |
| CC events overlap MIDI notes in E-line stream | Low | Sort all events (notes + CC) by absolute time; REAPER E-lines are monotonic delta-encoded. |

## Rollback Plan

Delete `midi_cc` from `ClipDef` and revert builder to skip CC events. Remove `_get_kick_cache()` from compose.py. No schema migrations needed — `midi_cc` defaults to empty list (zero behavioral change). One-commit revert.

## Dependencies

- `librosa` (already a project dependency via `drum_analyzer.py`)
- `DrumLoopAnalyzer` (already implemented and tested)
- No new packages, no external APIs.

## Success Criteria

- [ ] Bass MIDI clips contain CC11 (Expression) E-lines at kick hit positions
- [ ] CC11 value drops to ~50 at kick onset, recovers to 127 within 0.18 beats
- [ ] DrumLoopAnalyzer correctly identifies kick transients in all 5 drumloop variants
- [ ] Kick position cache avoids re-analyzing same WAV across sections
- [ ] 110 existing tests pass unchanged
- [ ] `.rpp` output opens in REAPER without errors; bass audibly ducks when kick hits
- [ ] `validate_rpp_output()` reports no regressions
