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

135 lines
4.9 KiB
Markdown

# Delta Specs: Section Energy Curve
## ADDED Requirements — section-activity
### Requirement: Centralized Activity Matrix
The system MUST provide a `TRACK_ACTIVITY` dict mapping `section_type → {role: bool}` as the single source of truth for which track roles play in each section. Section types: `intro`, `verse`, `pre-chorus`, `chorus`, `bridge`, `final`, `outro`. Roles: `drumloop`, `perc`, `bass`, `chords`, `lead`, `clap`, `pad`.
| Section | drumloop | perc | bass | chords | lead | clap | pad |
|---------|----------|------|------|--------|------|------|-----|
| intro | true | - | - | - | - | - | - |
| verse | true | true | true | true | - | - | - |
| pre-chorus | true | true | true | true | - | - | true |
| chorus | true | true | true | true | true | true | true |
| bridge | true | - | - | true | - | - | true |
| final | true | - | true | true | true | - | true |
| outro | - | - | - | - | - | - | - |
#### Scenario: Intro is sparse
- GIVEN section_type=`intro`
- WHEN `_section_active("intro", "bass", activity)` is called
- THEN it returns `False`
- AND only `drumloop` returns `True`
#### Scenario: Chorus is full band
- GIVEN section_type=`chorus`
- WHEN `_section_active("chorus", "lead", activity)` is called
- THEN it returns `True`
- AND all 7 roles return `True`
### Requirement: Section Activity Helper
The system MUST provide `_section_active(section: SectionDef, role: str, activity: dict) -> bool` that returns whether a role is active, defaulting to `False` for unknown section/role.
#### Scenario: Unknown section returns False
- GIVEN section_type=`xyz` not in TRACK_ACTIVITY
- WHEN `_section_active(section, "bass", matrix)` is called
- THEN it returns `False`
---
## ADDED Requirements — clip-volume
### Requirement: ClipDef Volume Multiplier
`ClipDef` MUST have an optional `vol_mult` field (float, default 1.0). When `vol_mult != 1.0`, the RPP builder SHALL apply it:
- Audio clips: emit `D_VOL` attribute on ITEM
- MIDI clips: scale all `MidiNote.velocity` by `vol_mult`
#### Scenario: Audio clip with vol_mult emits D_VOL
- GIVEN ClipDef(audio_path="kick.wav", vol_mult=0.7)
- WHEN RPPBuilder writes the ITEM
- THEN the ITEM includes `D_VOL 0.7`
#### Scenario: MIDI clip with vol_mult scales velocity
- GIVEN ClipDef(midi_notes=[MidiNote(velocity=80)], vol_mult=0.5)
- WHEN clip is processed by RPPBuilder
- THEN emitted velocity is 40
### Requirement: RPPBuilder D_VOL Emission
`_build_clip()` MUST append `["D_VOL", str(clip.vol_mult)]` to the ITEM element when `clip.vol_mult != 1.0` and the clip is audio.
#### Scenario: Default vol_mult=1.0 emits no D_VOL
- GIVEN ClipDef(audio_path="loop.wav") with default vol_mult=1.0
- WHEN RPPBuilder writes the ITEM
- THEN no `D_VOL` line is emitted
---
## MODIFIED Requirements — section-structure
### Requirement: SectionDef Multipliers Per Section Type
`build_section_structure()` MUST populate `SectionDef.velocity_mult` and `vol_mult` based on section type, not default to 1.0. Multipliers SHALL follow this table:
| Section | velocity_mult | vol_mult |
|---------|--------------|----------|
| intro | 0.6 | 0.70 |
| verse | 0.7 | 0.85 |
| pre-chorus | 0.85 | 0.95 |
| chorus | 1.0 | 1.00 |
| bridge | 0.6 | 0.75 |
| final | 1.0 | 1.00 |
| outro | 0.4 | 0.60 |
(Previously: velocity_mult and vol_mult always defaulted to 1.0)
#### Scenario: Intro has low velocity and volume
- GIVEN `build_section_structure()` is called
- WHEN the intro section is created
- THEN `velocity_mult=0.6` and `vol_mult=0.70`
#### Scenario: Chorus has full velocity and volume
- GIVEN `build_section_structure()` is called
- WHEN the chorus section is created
- THEN `velocity_mult=1.0` and `vol_mult=1.0`
---
## MODIFIED Requirements — track-generation
### Requirement: Builders Use Centralized Activity + Section Multipliers
All 7 track builders MUST replace ad-hoc section name checks with calls to `_section_active()`. All builders MUST multiply MIDI velocities by `section.velocity_mult`. The `build` section SHALL be renamed to `pre-chorus` in `SECTIONS` and all references.
(Previously: builders used inline `if section.name in (...)` checks and `section.energy` for velocity; section was named `build`)
#### Scenario: Chords not generated in intro
- GIVEN `build_chords_track()` with sections including intro
- WHEN processing the intro section
- THEN `_section_active("intro", "chords", ...)` returns `False`
- AND no clip is created for that section
#### Scenario: Bass velocity scaled by section multiplier
- GIVEN `build_bass_track()` with a verse section (velocity_mult=0.7)
- WHEN MIDI notes are created
- THEN each note velocity is multiplied by 0.7
#### Scenario: Section rename reflects in output
- GIVEN SECTIONS tuple has `("pre-chorus", 4, 0.7, False)`
- WHEN `build_section_structure()` is called
- THEN the section is named `pre-chorus` not `build`