# 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
