- compose-test-sync: fix 3 failing tests (NOTE_TO_MIDI, DrumLoopAnalyzer mock, section name) - generate-song: CLI wrapper + RPP validator (6 structural checks) + 4 e2e tests - reascript-hybrid: ReaScriptGenerator + command protocol + CLI + 16 unit tests - 110/110 tests passing - Full SDD cycle (propose→spec→design→tasks→apply→verify) for all 3 changes
4.2 KiB
4.2 KiB
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)
- Our Python generates a self-contained ReaScript
.pyfile to a watched folder - REAPER runs it via a custom Action (assigned via
__startup__.pyor manually) - ReaScript reads/writes state via a JSON command file for two-way communication
- 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)
- Open .rpp via
Main_openProject - Verify FX loaded: iterate tracks, call
TrackFX_GetFXNamefor each slot - Set track volumes/pans/sends:
SetMediaTrackInfo_Value+CreateTrackSend - Render:
Main_RenderFilewith format settings - Measure loudness:
CalcMediaSrcLoudnesson rendered file - 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
- Revert
src/reaper_scripting/andscripts/run_in_reaper.pydeletions - Phase 1 (.rpp generation) is unaffected — it already works standalone
- 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