# mix-calibration Specification ## Purpose Post-processing calibrator that applies role-aware volume, EQ, stereo width, sends, and mastering chain to a `SongDefinition` before `.rpp` generation. ## Requirements ### Requirement: Calibrator Post-Processing The system MUST provide a `Calibrator.apply(song: SongDefinition) -> SongDefinition` method that mutates and returns the song with calibrated mix settings. Calibration MUST run as a distinct step between track construction and `RPPBuilder.build()`. #### Scenario: Happy path — full calibration - GIVEN a complete `SongDefinition` with 7 tracks (Drumloop, Perc, 808 Bass, Chords, Lead, Clap, Pad) and 2 return tracks - WHEN `Calibrator.apply(song)` is called - THEN `song.tracks[].volume` matches role-based LUFS targets - AND each non-return track has a ReaEQ plugin prepended to its `plugins` list - AND `song.tracks[].pan` follows stereo-width rules - AND `song.tracks[].send_level` contains calibrated reverb/delay values - AND `song.master_plugins` contains Ozone 12 Equalizer, Dynamics, Maximizer ### Requirement: Role-Based Volumes The system SHALL set track volumes from a preset table keyed by track role (name → role mapping). Volumes MUST be in the REAPER-compatible 0.0–1.0 range. | Role | Volume | Target | |------|--------|--------| | drumloop | 0.85 | kick prominence | | bass | 0.72 | sub-presence | | chords | 0.78 | harmonic support | | lead | 0.78 | melody clarity | | clap | 0.75 | transient punch | | pad | 0.68 | ambient depth | | perc | 0.72 | groove feel | #### Scenario: Unknown track role - GIVEN a track with name not matching any preset role - WHEN calibrated - THEN the track's volume and pan remain unchanged (preserved as-is) ### Requirement: HPF/LPF EQ per Role The system SHALL prepend a ReaEQ `PluginDef` to each non-return track's `plugins` list with appropriate HPF or LPF parameters. Bass tracks (808 Bass) SHALL receive LPF. All other tracks SHALL receive HPF. #### Scenario: HPF on lead/chords/pad tracks - GIVEN a track named "Chords", "Lead", "Pad", "Clap", "Perc", or "Drumloop" - WHEN calibrated - THEN a ReaEQ plugin is inserted at `plugins[0]` with param `0=1` (band enabled), `1=1` (HPF type), `2=200.0` (frequency for melodic) or `2=60.0` (drums) #### Scenario: LPF on bass track - GIVEN a track named "808 Bass" - WHEN calibrated - THEN a ReaEQ plugin is inserted at `plugins[0]` with param `0=1`, `1=0` (LPF type), `2=300.0` (frequency) #### Scenario: Return tracks excluded - GIVEN tracks named "Reverb" or "Delay" - WHEN calibrated - THEN no ReaEQ plugin is added (return tracks are skipped) ### Requirement: Stereo Width per Role The system SHALL set track pan values to role-specific defaults. | Role | Pan | Rationale | |------|-----|-----------| | drumloop | 0.0 | mono center | | bass | 0.0 | mono sub | | chords | +0.5 | wide right | | lead | +0.3 | right-leaning | | clap | -0.15 | off-center left | | pad | -0.5 | wide left | | perc | +0.12 | slight right | ### Requirement: Send Calibration The system SHALL set `send_level` dict entries for reverb (index=return_track_count) and delay (index=return_track_count+1) on each non-return track. | Role | Reverb | Delay | |------|--------|-------| | drumloop | 0.10 | 0.00 | | bass | 0.05 | 0.02 | | chords | 0.30 | 0.10 | | lead | 0.25 | 0.15 | | clap | 0.10 | 0.00 | | pad | 0.40 | 0.20 | | perc | 0.10 | 0.00 | ### Requirement: Master Chain Upgrade The system SHALL replace `master_plugins` with `["Ozone_12_Equalizer","Ozone_12_Dynamics","Ozone_12_Maximizer"]`. If registry lookup for any Ozone plugin fails, the system MUST fall back to `["Pro-Q_3","Pro-C_2","Pro-L_2"]`. ### Requirement: Calibration Toggle The system SHALL support a `--no-calibrate` CLI flag. When passed, `Calibrator.apply()` MUST NOT be called. When omitted (default), calibration MUST run. `SongMeta` MAY include an optional `calibrate: bool` field defaulting to `True`. #### Scenario: --no-calibrate preserves existing behavior - GIVEN `compose.py --no-calibrate -o out.rpp` - WHEN the song is built - THEN `Calibrator.apply()` is never invoked - AND the generated `.rpp` matches the pre-calibration baseline