- 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.
100 lines
4.5 KiB
Markdown
100 lines
4.5 KiB
Markdown
# 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
|
||
|
||
```python
|
||
# 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.
|