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.
This commit is contained in:
renato97
2026-05-03 23:54:29 -03:00
parent 48bc271afc
commit 014e636889
51 changed files with 11394 additions and 113 deletions

View File

@@ -0,0 +1,88 @@
# Design: Transitions FX
## Technical Approach
Add `build_fx_track()` to `scripts/compose.py` that places audio FX clips from the sample library at 7 section boundaries. Uses `SampleSelector.select_one(role="fx")` with per-type character hints. Reuses `ClipDef.fade_in/out`. New track inserted after Clap, before Pad — after main tracks, before sends are wired.
## Architecture Decisions
| Decision | Choice | Rejected | Rationale |
|----------|--------|----------|-----------|
| One FX track vs per-section | Single dedicated track | Per-section tracks | Simpler; one import per sample in REAPER; manageable clip count (79) |
| Sample selection | Weighted random top-5 | Pinned specific files | Variety across runs; selector scoring already works |
| Boundary timing | Hardcoded beat-offset map | Audio analysis | Section structure is deterministic; bar counts are fixed |
| Riser+impact at chorus | Two clips, same boundary | Single combined clip | Requires different timing; riser before boundary, impact on it |
## Data Flow
```
SECTIONS → offsets (bar → beat)
FX_TRANSITIONS map: {boundary_idx: (type, start_offset, length, fade_in, fade_out)}
build_fx_track(sections, offsets, selector, seed)
├── for each entry in FX_TRANSITIONS:
│ ├── boundary_beat = offsets[boundary_idx] * 4
│ ├── position = boundary_beat + start_offset
│ ├── sample = selector.select_one(role="fx", seed=seed+idx)
│ └── ClipDef(position, length, audio_path, fade_in, fade_out)
TrackDef("Transition FX", volume=0.72, clips=[...], send_level={...})
```
## Boundary → FX Map
| # | Boundary | Beat | FX Type | Position | Length | Fade In | Fade Out |
|---|----------|------|---------|----------|--------|---------|----------|
| 2 | verse→build | 48 | sweep | 46 | 2 | 0.3 | 0.0 |
| 3 | build→chorus | 64 | **riser** | 60 | 4 | 1.5 | 0.0 |
| 3 | build→chorus | 64 | **impact** | 64 | 2 | 0.0 | 0.3 |
| 4 | chorus→verse2 | 96 | transition | 94 | 2 | 0.2 | 0.2 |
| 5 | verse2→chorus2 | 128 | riser | 124 | 4 | 1.0 | 0.0 |
| 6 | chorus2→bridge | 160 | sweep | 158 | 2 | 0.2 | 0.2 |
| 7 | bridge→final | 176 | riser | 172 | 4 | 1.0 | 0.0 |
| 8 | final→outro | 208 | sweep | 206 | 2 | 0.3 | 0.5 |
## File Changes
| File | Action | Description |
|------|--------|-------------|
| `scripts/compose.py` | Modify | Add `FX_TRANSITIONS` dict + `build_fx_track()` (~50 lines); call in `main()` after clap track, before return tracks |
## Key Implementation Detail
`SampleSelector.select_one()` has a `seed` kwarg — new in the selector API. If not yet supported, use `select(role="fx", limit=5)` with manual `random.choice()`. Since FX is in `ATONAL_ROLES`, key compatibility scoring is skipped (neutral 0.5).
## Track Ordering
```
tracks = [
build_drumloop_track(...), # 0
build_perc_track(...), # 1
build_bass_track(...), # 2
build_chords_track(...), # 3
build_lead_track(...), # 4
build_clap_track(...), # 5
build_fx_track(...), # 6 ← NEW
build_pad_track(...), # 7
]
return_tracks = create_return_tracks() # 8 (Reverb), 9 (Delay)
```
Send wiring applies to all non-return tracks automatically via existing loop. FX track sends: Reverb=0.08, Delay=0.05.
## Testing Strategy
| Layer | What | How |
|-------|------|-----|
| Unit | `build_fx_track` returns TrackDef with 8 clips | Mock selector via `SampleSelector.__init__` patching |
| Unit | Clip positions match boundary map | Assert `clip.position` values equal expected beats |
| Integration | End-to-end .rpp output | `compose.py --bpm 99 --key Am --output test.rpp`; grep for "Transition FX" `<TRACK` block |
| Existing | 110 tests pass | `pytest` before/after regression |
## Open Questions
None — all dependencies exist today (`SampleSelector`, `ClipDef.fade_in/out`, `SECTIONS` structure).