- 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.
3.4 KiB
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
ClipDefwithmidi_cc=[CCEvent(11, 0.0, 50), CCEvent(11, 0.18, 127)] - WHEN clip is processed by builder
- THEN builder sees
midi_ccfield 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_ccis 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)]andmidi_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_ccwith 2×3 CC events (one envelope per kick in range) - AND note generation unchanged from existing behavior
Scenario: No kick cache provided
- GIVEN
kick_cacheis{}or omitted - THEN
midi_ccis empty — zero behavioral change from current output