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,121 @@
# Delta for melody-engine
## ADDED Requirements
| # | Requirement | RFC |
|---|------------|-----|
| R1 | Motif generation with 3 reggaeton styles | MUST |
| R2 | Deterministic output from seed | MUST |
| R3 | Call-and-response phrase structure | MUST |
| R4 | Chord-aware note selection | MUST |
| R5 | Motif variation via transpose/rhythmic shift | SHOULD |
| R6 | build_lead_track() delegation | MUST |
### Requirement: Motif Generation (R1)
`build_motif(key_root, key_minor, style, bars, seed)` MUST generate a 24 bar repeating motif using scale-aware note selection. Three styles:
- **hook**: Arch contour (ascend then descend), chord tones on beats 0, 2, 4..., 48 notes
- **stabs**: Short 16th-duration hits on dembow grid positions [1.0, 2.5, 3.0, 3.5] per bar
- **smooth**: Stepwise scalar motion at eighth-note density, ≤2 semitones between consecutive notes
MUST accept `bars` parameter (28) defaulting to 4. MUST return `list[MidiNote]`.
#### Scenario: hook style generates arch contour with chord tones
- GIVEN key Am, style "hook", bars=4, seed=42
- WHEN `build_motif("A", True, "hook", 4, 42)` is called
- THEN returns 412 MidiNote objects
- AND notes on quarter-beat positions (0, 2, 4, …) are within the i-VI-III-VII chord tones ≥70% of the time
#### Scenario: stabs style generates dembow-positioned hits
- GIVEN key Am, style "stabs", bars=2, seed=1
- WHEN `build_motif("A", True, "stabs", 2, 1)` is called
- THEN all note start times are within {1.0, 2.5, 3.0, 3.5} per bar
- AND each note duration ≤ 0.25 beats (16th note)
#### Scenario: smooth style generates stepwise motion
- GIVEN key Am, style "smooth", bars=4, seed=7
- WHEN `build_motif("A", True, "smooth", 4, 7)` is called
- THEN pitch difference between consecutive notes ≤ 2 semitones
#### Scenario: invalid style raises ValueError
- GIVEN an unrecognized style string
- WHEN `build_motif("A", True, "invalid", 4, 42)` is called
- THEN raises ValueError with message containing valid styles
### Requirement: Deterministic Output (R2)
`build_motif()` and `apply_variation()` MUST produce identical output for identical input parameters (key, style, bars, seed). MUST NOT rely on global RNG state.
#### Scenario: same seed produces identical output
- GIVEN fixed parameters
- WHEN `build_motif("A", True, "hook", 4, 42)` is called twice
- THEN both calls return identical lists of MidiNote objects
#### Scenario: different seeds produce different output
- GIVEN same key and style but different seeds
- WHEN `build_motif("A", True, "hook", 4, 42)` and `build_motif("A", True, "hook", 4, 99)` are called
- THEN the returned note lists differ
### Requirement: Call-and-Response Structure (R3)
`build_call_response(motif, bars, key_root, key_minor, seed)` MUST generate two halves: **call** (motif + variation, ending on V or VII degree) and **response** (motif, resolving to tonic i). Total length MUST equal `bars` parameter. SHALL repeat motif to fill section length.
#### Scenario: call ends on tension, response resolves
- GIVEN an Am hook motif, bars=8, seed=42
- WHEN `build_call_response(motif, 8, "A", True, 42)` is called
- THEN the last note of the first 4 bars has pitch in {E, G} (V or VII of Am)
- AND the last note of the final bar (bar 8) has pitch in {A} (tonic)
#### Scenario: fills section with motif repetition
- GIVEN a 2-bar motif and bars=8
- WHEN `build_call_response(motif, 8, "A", True, 42)` is called
- THEN returns notes spanning 8 bars total
- AND motif content repeats at least 2 times within the 8 bars
### Requirement: Chord-Aware Notes (R4)
Note selection on strong beats (quarter note positions 0, 4, 8, 12 per bar in 16th-note grid) MUST favor chord tones from `CHORD_PROGRESSION`. Weak beats (all other positions) MAY use any scale degree.
#### Scenario: strong beats favor chord tones
- GIVEN key Am (CHORD_PROGRESSION = i-VI-III-VII), style "hook", bars=8
- WHEN a motif is generated
- THEN ≥70% of notes starting on quarter-beat boundaries belong to active chord tones
### Requirement: Motif Variation (R5)
`apply_variation(motif, shift_beats, transpose_semitones)` SHOULD produce a recognizable variant of the input motif. `shift_beats` offsets all start times within the loop. `transpose_semitones` shifts pitches within the scale. MUST return `list[MidiNote]`.
#### Scenario: rhythmic shift preserves note count and structure
- GIVEN a 4-bar hook motif
- WHEN `apply_variation(motif, shift_beats=0.25)` is called
- THEN note count equals original
- AND all note durations equal original
- AND inter-onset intervals are preserved
#### Scenario: transpose within scale preserves motif contour
- GIVEN a 4-bar hook motif in Am
- WHEN `apply_variation(motif, transpose_semitones=3)` is called
- THEN all pitches are offset by ±3 semitones (within pentatonic scale)
### Requirement: build_lead_track() Delegation (R6)
`build_lead_track()` in `compose.py` MUST delegate to `melody_engine.build_call_response()` instead of generating random pentatonic notes directly. MUST keep identical function signature. MUST pass existing tests after adjusting expected note counts.
#### Scenario: build_lead_track uses call-response structure
- GIVEN seed=42, key Am, sections containing "chorus" and "final"
- WHEN `build_lead_track(sections, offsets, "A", True, 42)` is called
- THEN returned TrackDef clips contain notes organized as call-response phrases
- AND at least one clip has notes ending on tonic pitch