feat: professional reggaeton production engine — 7 SDD changes, 302 tests

- section-energy: track activity matrix + volume/velocity multipliers per section
- smart-chords: ChordEngine with voice leading, inversions, 4 emotion modes
- hook-melody: melody engine with hook/stabs/smooth styles, call-and-response
- mix-calibration: Calibrator module (LUFS volumes, HPF/LPF, stereo, sends, master)
- transitions-fx: FX track with risers/impacts/sweeps at section boundaries
- sidechain: MIDI CC11 bass ducking on kick hits via DrumLoopAnalyzer
- presets-pack: role-aware plugin presets (Serum/Decapitator/Omnisphere per role)

Full SDD pipeline (propose→spec→design→tasks→apply→verify) for all 7 changes.
302/302 tests passing.
This commit is contained in:
renato97
2026-05-03 23:54:29 -03:00
parent 48bc271afc
commit 014e636889
51 changed files with 11394 additions and 113 deletions

View File

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