# Proposal: presets-pack ## Intent All plugins use the SAME flat preset regardless of track role (bass/lead/chords/pad) or genre context. A Serum_2 on a bass track gets the same sound as Serum_2 on a lead track. Professional reggaeton needs role-specific timbres: deep sine 808 for bass, detuned saw for lead, warm pad for chords, evolving texture for pad. Same for FX: Decapitator on drums needs aggressive drive, on bass needs subtle warmth. ## Scope ### In Scope - Restructure `PLUGIN_PRESETS` from flat `{plugin: [chunks]}` to role-aware `{plugin: {role: [chunks]}}` - Create role-specific presets for plugins used in multiple roles: **Serum_2** (bass/lead), **Omnisphere** (chords/pad), **Decapitator** (drums/bass) - Programmatically derive new presets by base64-decoding existing presets (Serum=JSON, SoundToys=key=value), modifying genre-specific parameters, re-encoding - Update `make_plugin()` in `compose.py` and `_build_plugin()` in `__init__.py` to resolve role-aware presets - Add fallback: if no role-specific preset exists, use existing default preset ### Out of Scope - Creating presets from scratch in REAPER (requires GUI — can't programmatically) - ReaScript-based preset capture (Phase 2) - Presets for all 113 plugins — only multi-role targets initially - Pro-Q 3 reggaeton EQ curve (no decodable format available) ## Capabilities ### New Capabilities - `presets-pack`: Role-specific plugin preset resolution and preset data management ### Modified Capabilities None — existing plugin resolution unchanged; backward-compatible fallback to default preset. ## Approach **Option B — Programmatic modification of decodable presets:** 1. **Serum_2**: Decode base64 → JSON. Serum preset JSON has `component: "processor"` block with oscillator/wavetable/filter data. Create variants by modifying oscillator type (sine for bass, saw for lead), filter cutoff, envelope settings. Re-encode. 2. **Decapitator (SoundToys)**: Decode base64 → key=value text (`WIDGET = Decapitator;...`). Create "aggressive" (high Drive, Tone bright) for drums, "warm" (low Drive, Tone dark) for bass. Re-encode. 3. **Omnisphere**: Decode base64 → `SynthMaster` text block. Create "warm pad" variant with slow attack, filter modulation; "evolving texture" with movement/LFO. Re-encode. No GUI or REAPER needed — pure Python string processing over decoded preset text. ## Affected Areas | Area | Impact | Description | |------|--------|-------------| | `src/reaper_builder/__init__.py` | Modified | `PLUGIN_PRESETS` restructured; `_build_plugin()` accepts role param | | `src/composer/templates.py` | Modified | `_parse_vst_block()`, `make_plugin()` resolution updated | | `scripts/compose.py` | Modified | `make_plugin()` passes role; `FX_CHAINS` keys used for role | | `src/core/schema.py` | Unchanged | `PluginDef` already has `preset_data` field | ## Risks | Risk | Likelihood | Mitigation | |------|------------|------------| | Modified preset crashes plugin on load | Low | Each variant derived from working ground-truth preset; only tweak known-safe params | | Base64 decode/re-encode breaks binary integrity | Low | Round-trip test per plugin: decode → encode → bytes equal | | Omnisphere text format undocumented | Med | Preserve structure, only modify known `ATTRIBUTE` values visible in decoded text | ## Rollback Plan Revert `PLUGIN_PRESETS` to flat dict. Remove role param from `_build_plugin()` and `make_plugin()`. Existing tests verify preset injection still works. ## Dependencies - `data/sample_index.json` (independent — not affected) - Existing ground-truth presets in `PLUGIN_PRESETS` (source material for variants) ## Success Criteria - [ ] `python scripts/compose.py --bpm 99 --key Am` produces .rpp where Serum_2 on bass track has different preset data than Serum_2 on lead track - [ ] 110 existing tests continue to pass (backward-compatible fallback) - [ ] Round-trip test: decode → modify → encode produces valid base64 matching original length structure - [ ] At least 3 (plugin, role) combinations have distinct preset variants