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

4.5 KiB
Raw Permalink Blame History

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 buildpre-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.