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:
76
.sdd/changes/sidechain/spec.md
Normal file
76
.sdd/changes/sidechain/spec.md
Normal 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
|
||||
Reference in New Issue
Block a user