Files
renato97 014e636889 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.
2026-05-03 23:54:29 -03:00

77 lines
3.4 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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