# Delta Spec: 808 Bass Sidechain Ducking

## ADDED Requirements

### Requirement: MIDI CC11 Event Data Model

The schema MUST support an `CCEvent` dataclass with controller, time, and value fields, and `ClipDef` MUST accept an optional `midi_cc: list[CCEvent]` field defaulting to empty list.

#### Scenario: CCEvent round-trips correctly
- GIVEN `CCEvent(controller=11, time=0.5, value=50)`
- WHEN serialized/deserialized via dataclass
- THEN all fields preserved exactly

#### Scenario: ClipDef with midi_cc
- GIVEN a `ClipDef` with `midi_cc=[CCEvent(11, 0.0, 50), CCEvent(11, 0.18, 127)]`
- WHEN clip is processed by builder
- THEN builder sees `midi_cc` field and can iterate it

### Requirement: Kick Position Cache

A kick-cache dict `{drumloop_wav_path: list[beat_positions]}` SHALL be computed once per session, keyed by WAV path. `DrumLoopAnalyzer.transient_positions("kick")` MUST be the source, filtered by `confidence >= KICK_CONFIDENCE_THRESHOLD` (default 0.6).

#### Scenario: Cache hit
- GIVEN drumloop WAV already analyzed in same session
- WHEN `build_bass_track()` requests kick positions for that path
- THEN cached positions returned without re-analyzing WAV

#### Scenario: Cache miss
- GIVEN drumloop WAV not yet cached
- WHEN kick positions requested
- THEN `DrumLoopAnalyzer.analyze()` runs, positions cached by path key

### Requirement: CC11 Ducking on Kick Hits

For each kick transient position in the bass clip's time span, the system MUST emit CC11 events forming a ducking envelope: instantaneous drop to value 50 at kick time, hold at 50 for 0.02 beats, ramp to 127 by 0.18 beats after kick.

#### Scenario: Single kick duck
- GIVEN kick at beat 1.0 within a 4-beat bass clip
- WHEN CC events generated
- THEN emits `CCEvent(11, 1.0, 50)`, `CCEvent(11, 1.02, 50)`, `CCEvent(11, 1.18, 127)`

#### Scenario: No kicks in clip
- GIVEN drumloop with no kick transients in clip time range
- THEN `midi_cc` is empty list — no CC events emitted

## MODIFIED Requirements

### Requirement: RPPBuilder MIDI Source Encoding

`_build_midi_source()` MUST emit MIDI CC events as `E B0 0B xx` lines interleaved with note events, all sorted by absolute start time. Delta-encoding MUST continue for all E-lines.

#### Scenario: CC events interleaved with notes
- GIVEN clip with `midi_notes=[Note(60, 0.5, 1.0)]` and `midi_cc=[CCEvent(11, 0.0, 50)]`
- WHEN `_build_midi_source()` called
- THEN E-lines emitted in time order: CC at 0.0, Note at 0.5
- AND CC line reads `E 0 B0 0B 32` (delta=0, CC11, value=50=0x32)

#### Scenario: Delta sequencing across note+CC
- GIVEN CC at 0.0, note at 0.5 beats
- WHEN building E-lines
- THEN CC delta = 0×960 = 0; note delta = 0.5×960 - 0 = 480
- AND cursor reset correctly after CC event ticks

### Requirement: Bass Track Generation

`build_bass_track()` SHALL accept an optional `kick_cache: dict[str, list[float]]` parameter. When kick data is present for the drumloop used in each section, `midi_cc` events SHALL be generated and added to the bass `ClipDef`.

#### Scenario: Bass clip with ducking CC
- GIVEN kick cache has `[1.0, 2.5]` for drumloop, section covers beats 0-16
- WHEN bass track built
- THEN bass clip at that section has `midi_cc` with 2×3 CC events (one envelope per kick in range)
- AND note generation unchanged from existing behavior

#### Scenario: No kick cache provided
- GIVEN `kick_cache` is `{}` or omitted
- THEN `midi_cc` is empty — zero behavioral change from current output
