# Delta for reascript-generator ## ADDED Requirements ### Requirement: ReaScriptGenerator — File Output The system SHALL generate a self-contained Python ReaScript file that REAPER can execute via a custom Action. The `ReaScriptGenerator` class MUST expose a `generate(path: Path, command: ReaScriptCommand) -> None` method that writes a valid Python 3.x ReaScript to `path`. The generated script MUST include: - A `MAIN` block that reads a JSON command file, executes the action, and writes a JSON result file - ReaScript API calls for all required operations (open project, verify FX, calibrate, render, measure loudness) - Proper error handling that writes error status to the result JSON #### Scenario: Generate ReaScript for open-verify-render-loudness pipeline - GIVEN a `ReaScriptCommand` with `action="calibrate"`, `rpp_path="output/song.rpp"`, `render_path="output/song.wav"`, and `timeout=120` - WHEN `generator.generate(path, command)` is called - THEN a Python file is written to `path` that calls `Main_openProject(rpp_path)`, iterates tracks calling `TrackFX_GetCount` and `TrackFX_GetFXName`, calls `SetMediaTrackInfo_Value` for volume/pan, calls `CreateTrackSend` for sends, calls `Main_RenderFile`, and calls `CalcMediaSrcLoudness` on the rendered file - AND the script writes a result JSON with fields: `status` ("ok" | "error"), `lufs` (float or null), `fx_errors` (list), `track_count` (int) #### Scenario: Generate ReaScript for FX verification only - GIVEN a `ReaScriptCommand` with `action="verify_fx"` and `rpp_path="output/song.rpp"` - WHEN `generator.generate(path, command)` is called - THEN the generated script opens the project, iterates all tracks, calls `TrackFX_GetCount` and `TrackFX_GetFXName` for each FX slot, and writes a result JSON listing loaded FX names and any slots where `TrackFX_GetFXName` returned an empty string (missing plugin) ### Requirement: Command Protocol — JSON File Contract The system SHALL use a versioned JSON command file for two-way communication with the ReaScript. The command file (written by our Python, read by ReaScript) SHALL have schema: ```json { "version": 1, "action": "calibrate" | "verify_fx" | "render", "rpp_path": "absolute path to .rpp", "render_path": "absolute path for rendered output (wav)", "timeout": 120, "track_calibration": [ { "track_index": 0, "volume": 0.85, "pan": 0.0, "sends": [{"dest_track_index": 5, "level": 0.05}] } ] } ``` The result file (written by ReaScript, read by our Python) SHALL have schema: ```json { "version": 1, "status": "ok" | "error" | "timeout", "message": "optional error message", "lufs": -14.2, "integrated_lufs": -14.2, "short_term_lufs": -12.1, "fx_errors": [ {"track_index": 2, "fx_index": 1, "name": "", "expected": "Serum_2"} ], "tracks_verified": 8 } ``` #### Scenario: Command file round-trip - GIVEN a valid `ReaScriptCommand` dataclass - WHEN `write_command(path, cmd)` is called - THEN a JSON file is written to `path` with the exact schema above - AND `read_result(path)` returns a `ReaScriptResult` with parsed fields #### Scenario: Version mismatch handling - GIVEN `read_result(path)` is called and the result JSON has `version` ≠ 1 - THEN a `ProtocolVersionError` SHALL be raised with a message indicating the mismatch ### Requirement: run_in_reaper.py — CLI Entry Point The system SHALL provide a CLI script at `scripts/run_in_reaper.py` that accepts a `.rpp` path and runs Phase 2 (calibrate + render + measure loudness). Usage: `python scripts/run_in_reaper.py [--output ] [--timeout ]` The CLI SHALL: 1. Generate a ReaScript file to a watched folder (e.g. `REAPER ResourcePath()/scripts/fl_control_phase2.py`) 2. Write the command JSON to `REAPER ResourcePath()/scripts/fl_control_command.json` 3. Poll `REAPER ResourcePath()/scripts/fl_control_result.json` until it exists or timeout is reached 4. Parse the result and print loudness metrics 5. Exit with code 0 on success, non-zero on failure #### Scenario: Successful Phase 2 run - GIVEN REAPER is running with the custom Action registered - AND the .rpp file at `output/song.rpp` exists with valid tracks - WHEN `python scripts/run_in_reaper.py output/song.rpp --output output/song.wav` is executed - THEN the script generates the ReaScript, writes command JSON, waits for result JSON, and prints the LUFS measurement - AND files `output/song_lufs.json` and `output/song_fx_errors.json` are written with results #### Scenario: Missing FX detection - GIVEN a track in the .rpp references a plugin not installed in REAPER - WHEN Phase 2 runs and the ReaScript calls `TrackFX_GetFXName` on that slot - THEN `fx_errors` in the result JSON SHALL contain an entry with the track index, FX index, and empty name - AND the CLI writes `output/song_fx_errors.json` with the error details #### Scenario: Timeout on REAPER non-response - GIVEN REAPER is not running or the Action is not triggered - WHEN the CLI polls for `fl_control_result.json` beyond the timeout - THEN the CLI SHALL exit with code 2 and print a timeout message ### Requirement: ReaScript API Subset The system SHALL only use a stable, documented subset of the ReaScript API to minimize version compatibility issues. Required API functions: - `Main_openProject(path)` — open .rpp project file - `TrackFX_GetCount(track)` — get number of FX on track - `TrackFX_GetFXName(track, fx, buf, bufsize)` — get FX name by index - `TrackFX_AddByName(track, fxname, recFX, instantiate)` — add FX by name (for remediation) - `SetMediaTrackInfo_Value(track, paramname, value)` — set VOLUME, PAN, etc. - `CreateTrackSend(tr, desttr)` — create send to destination track - `Main_RenderFile(proj, filename)` — render project to file (or use project render settings) - `GetProjectName(proj, buf, bufsize)` — get current project name - `GetTrackNumMediaItems(track)` — count items on track - `GetMediaItem(track, idx)` — get media item by index - `GetMediaItemTake(mediaitem, idx)` — get take from item - `GetMediaItemTake_Source(take)` — get source from take - `GetMediaSourceType(src, buf)` — check if source is audio/video - `PCM_Source_GetSectionInfo(src)` — get source length - `GetMediaSourceFileName(src, buf, bufsize)` — get source file path - `SetMediaItemInfo_Value(item, param, value)` — set item properties - `SetMediaItemLength(item, length, update)` — set item length - `AddMediaItemToTrack(track)` — add new media item - `CreateNewMediaItem(track)` — create new item - `GetTrack(guid, flag)` — get track by GUID (flag=0) - `GetTrackGUID(track, buf)` — get track GUID - `GetItemOwnership(mediaitem)` — item ownership check - `DeleteTrack(track)` — delete track - `DeleteMediaItem(mediaitem)` — delete media item - `Undo_OnStateChange(s)` — mark undo point - `PreventUIRefresh(PreventCount)` — prevent UI refresh during batch ops - `OnPauseButton()` — pause playback - `OnPlayButton()` — start playback - `GetPlayState()` — get play state (0=stopped, 1=playing, 2=paused) - `GetCursorPosition()` — get playhead position in seconds - `GetProjectLengthSeconds(proj)` — get project length - `GetUserInputs(title, captions, retvals)` — optional: prompt user for input - `ShowConsoleMsg(msg)` — debug output to REAPER console - `TimeMap_cur_qn_to_beats(qn)` — convert quarter notes to beats - `TimeMap_qn_to_time(qn)` — convert quarter notes to time in seconds - `GetFunctionMetadata(functionName)` — detect if function exists #### Scenario: API availability check on startup - GIVEN the ReaScript is executed inside REAPER - WHEN the script starts - THEN it SHALL call `GetFunctionMetadata("Main_openProject")` to verify the required API functions are available - AND if any required function is missing, write `{"status": "error", "message": "API not available"}` to result JSON and exit ### Requirement: Phase 2 Pipeline Steps The ReaScript SHALL execute these steps in order when `action="calibrate"`: 1. **Open Project**: Call `Main_openProject(rpp_path)` to load the .rpp 2. **Verify FX**: Iterate all tracks, for each call `TrackFX_GetCount(track)`, then for each FX slot call `TrackFX_GetFXName` — log empty/missing plugins 3. **Calibrate Tracks**: For each entry in `track_calibration`, call `SetMediaTrackInfo_Value(track, "VOLUME", volume)` and `SetMediaTrackInfo_Value(track, "PAN", pan)`, then for each send call `CreateTrackSend` 4. **Render**: Call `Main_RenderFile` with the project and `render_path` (or use project render settings if `render_path` is not provided) 5. **Measure Loudness**: Call `CalcMediaSrcLoudness` on the rendered WAV file to obtain integrated LUFS, short-term LUFS 6. **Write Result**: Write the result JSON with status, lufs metrics, and fx_errors #### Scenario: Full pipeline execution - GIVEN a valid .rpp with 8 tracks, each with 1-3 FX plugins, and valid render settings - WHEN the ReaScript executes with `action="calibrate"` - THEN all 6 steps execute in order - AND the result JSON contains `status: "ok"`, `lufs` (integrated LUFS), `fx_errors: []`, and `tracks_verified: 8` #### Scenario: FX verification catches missing plugin - GIVEN track 3 has 2 FX slots but slot 1 contains a missing plugin (empty string from `TrackFX_GetFXName`) - WHEN the ReaScript runs FX verification - THEN `fx_errors` SHALL contain `{"track_index": 3, "fx_index": 1, "name": "", "expected": ""}` - AND rendering SHALL still proceed (verification does not halt the pipeline)