- 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.
5.4 KiB
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 2–4 bar repeating motif using scale-aware note selection. Three styles:
- hook: Arch contour (ascend then descend), chord tones on beats 0, 2, 4..., 4–8 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 (2–8) 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 4–12 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)andbuild_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