- 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.
4.5 KiB
4.5 KiB
Design: Section Energy Curve
Technical Approach
Add three layers of dynamics: (1) which tracks play per section, (2) MIDI velocity scaling per section, (3) clip-level volume multipliers. Wiring already exists — SectionDef has velocity_mult/vol_mult fields that are never populated. Add the wiring and a centralized activity matrix.
Architecture Decisions
| Decision | Choice | Tradeoff | Reason |
|---|---|---|---|
| Activity source of truth | Module-level TRACK_ACTIVITY dict |
Not configurable per-song (yet) | Proposal explicitly defines it as constant; CLI flag is deferred |
| Section rename | build → pre-chorus in all references |
Requires test fixture updates | Professional reggaeton convention; no external consumers of "build" |
| Clip volume | D_VOL on ITEM (not track fader) |
Per-clip, not per-section | Track fader already used for static mix; D_VOL is REAPER-native item gain |
| MIDI velocity | Scale at note creation (builders), not in RPPBuilder | No post-processing needed | Velocity is a MIDI property best set when notes are created |
Data Flow
build_section_structure()
└─ reads SECTIONS → creates SectionDef(name, bars, velocity_mult, vol_mult)
│
├─→ TRACK_ACTIVITY (module-level dict)
│ └─ _section_active(section, role) → bool
│
└─→ 7 track builders
├─ check _section_active() → skip/mute inactive roles
├─ multiply MIDI note velocity × section.velocity_mult
└─ set clip.vol_mult ← section.vol_mult
│
└─→ RPPBuilder._build_clip()
├─ audio: emit D_VOL if vol_mult ≠ 1.0
└─ MIDI: notes already velocity-scaled
File Changes
| File | Action | Description |
|---|---|---|
src/core/schema.py |
Modify | Add vol_mult: float = 1.0 to ClipDef |
scripts/compose.py |
Modify | Add TRACK_ACTIVITY dict, _section_active() helper, set multipliers in build_section_structure(), rename build→pre-chorus, refactor 7 builders |
src/reaper_builder/__init__.py |
Modify | _build_clip() emits D_VOL for audio clips with vol_mult≠1.0 |
tests/test_section_builder.py |
Modify | Add tests for multiplier population per section type |
tests/test_compose_integration.py |
Modify | Update section-aware tests |
tests/test_reaper_builder.py |
Modify | Add D_VOL emission tests |
Interfaces / Contracts
# New: TRACK_ACTIVITY dict in compose.py
TRACK_ACTIVITY: dict[str, dict[str, bool]] = {
"intro": {"drumloop": True, "perc": False, "bass": False, ...},
"verse": {"drumloop": True, "perc": True, "bass": True, ...},
"pre-chorus": {...},
"chorus": {...}, # all True
"bridge": {"drumloop": True, "chords": True, "pad": True, ...},
"final": {"drumloop": True, "bass": True, "chords": True, "lead": True, "pad": True},
"outro": {}, # all False
}
# New helper
def _section_active(section: SectionDef, role: str, activity: dict) -> bool:
return activity.get(section.name, {}).get(role, False)
# Modified: build_section_structure() sets multipliers
SECTION_MULTIPLIERS = {
"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),
}
# Modified: ClipDef gains vol_mult
@dataclass
class ClipDef:
...
vol_mult: float = 1.0
Testing Strategy
| Layer | What to Test | Approach |
|---|---|---|
| Unit | SectionDef multiplier population | test_section_builder.py — verify velocity_mult/vol_mult by section type |
| Unit | _section_active() helper |
Edge cases: unknown section, unknown role, all known sections |
| Unit | ClipDef.vol_mult default | test_core_schema.py — default is 1.0 |
| Integration | D_VOL in RPP output | test_reaper_builder.py — audio clip with vol_mult≠1.0 emits D_VOL, default vol_mult=1.0 emits none |
| Integration | Builders respect activity | test_compose_integration.py — intro has no bass/chords/lead, chorus has all |
| Integration | Section rename | Grep all .py for "build" section name, CI runs full suite (110 tests) |
Migration / Rollout
No migration required. vol_mult defaults to 1.0 (no behavioral change). Section rename is cosmetic. Revert commit to undo.
Open Questions
None.