# 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`