# Proposal: reascript-hybrid

## Intent

The .rpp generator (RPPBuilder) works offline but has a hard ceiling: no verification that FX loaded in REAPER, no rendering, no loudness validation. This change adds a **Phase 2** that runs inside REAPER via ReaScript, enabling FX verification, precise mix calibration, rendering, and output validation — while keeping Phase 1 offline and composable.

## Scope

### In Scope
- ReaScript generator that opens a .rpp and refines it via REAPER's native API
- FX chain verification (confirm plugins loaded, report failures)
- Track calibration (volume, pan, sends per track)
- Audio rendering to file (using RenderProject)
- Loudness validation (CalcMediaSrcLoudness)
- New module: `src/reaper_scripting/__init__.py` — ReaScript generator
- New script: `scripts/run_in_reaper.py` — CLI to trigger Phase 2

### Out of Scope
- Phase 1 changes (compose.py, RPPBuilder already work)
- REAPER automation via OSC/HTTP (not needed yet)
- Multi-DAW support

## Capabilities

> This change introduces a new spec capability. The contract with sdd-spec will define exact function signatures and error handling.

- `reascript-generator`: Generates Python ReaScript that runs inside REAPER to verify/mix/render .rpp projects

## Approach

### Bridge: ReaScript File + Action Trigger (Option B from discovery)

1. Our Python generates a self-contained ReaScript `.py` file to a watched folder
2. REAPER runs it via a custom Action (assigned via `__startup__.py` or manually)
3. ReaScript reads/writes state via a JSON command file for two-way communication
4. Our external Python polls/waits for completion

**Why not python-reapy?** It requires REAPER running with distant API enabled and adds a network dependency. Option B is more robust: REAPER controls timing, our script is a dumb generator.

### Two-way Communication

```
Our Python                          REAPER (ReaScript)
    │                                      │
    │--- write command.json ------------->│
    │    {action: "calibrate", rpp: "..."}  │
    │                                      │
    │<-- write result.json ---------------│
    │    {status: "ok", lufs: -14.2}       │
```

### Phase 2 Steps (inside REAPER via ReaScript)
1. Open .rpp via `Main_openProject`
2. Verify FX loaded: iterate tracks, call `TrackFX_GetFXName` for each slot
3. Set track volumes/pans/sends: `SetMediaTrackInfo_Value` + `CreateTrackSend`
4. Render: `Main_RenderFile` with format settings
5. Measure loudness: `CalcMediaSrcLoudness` on rendered file
6. Write result.json with status + metrics

### File Layout
```
src/reaper_scripting/
  __init__.py       # ReaScriptGenerator class
  commands.py       # command protocol (read/write JSON)
scripts/
  run_in_reaper.py # CLI: run phase2 on a .rpp file
```

## Affected Areas

| Area | Impact | Description |
|------|--------|-------------|
| `src/reaper_scripting/__init__.py` | New | ReaScript generator + command protocol |
| `scripts/run_in_reaper.py` | New | CLI entry point for Phase 2 |
| `.sdd/changes/reascript-hybrid/` | New | Change artifact folder |

## Risks

| Risk | Likelihood | Mitigation |
|------|------------|------------|
| ReaScript API changed in REAPER version | Medium | Pin to known API subset; log API availability on startup |
| FX fail silently on load | Medium | Verify each FX chain post-load, report missing plugins |
| Rendering blocks REAPER UI | Low | Run render in background thread via ReaScript; poll for completion |
| JSON protocol desync | Low | Version the command protocol; timeout with retry |

## Rollback Plan

1. Revert `src/reaper_scripting/` and `scripts/run_in_reaper.py` deletions
2. Phase 1 (.rpp generation) is unaffected — it already works standalone
3. No schema or compose.py changes

## Dependencies

- REAPER v7+ installed with Python 3.x ReaScript support
- Plugins must be in same paths as .rpp expects (no change to path handling)

## Success Criteria

- [ ] `python scripts/run_in_reaper.py output/song.rpp` — REAPER opens, calibrates, renders, reports LUFS
- [ ] Loudness result written to `output/song_lufs.json`
- [ ] Missing FX logged to `output/song_fx_errors.json`
- [ ] Phase 1 still works standalone: `python scripts/compose.py --output output/song.rpp`