- 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
9.3 KiB
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
MAINblock 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
ReaScriptCommandwithaction="calibrate",rpp_path="output/song.rpp",render_path="output/song.wav", andtimeout=120 - WHEN
generator.generate(path, command)is called - THEN a Python file is written to
paththat callsMain_openProject(rpp_path), iterates tracks callingTrackFX_GetCountandTrackFX_GetFXName, callsSetMediaTrackInfo_Valuefor volume/pan, callsCreateTrackSendfor sends, callsMain_RenderFile, and callsCalcMediaSrcLoudnesson 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
ReaScriptCommandwithaction="verify_fx"andrpp_path="output/song.rpp" - WHEN
generator.generate(path, command)is called - THEN the generated script opens the project, iterates all tracks, calls
TrackFX_GetCountandTrackFX_GetFXNamefor each FX slot, and writes a result JSON listing loaded FX names and any slots whereTrackFX_GetFXNamereturned 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:
{
"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:
{
"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
ReaScriptCommanddataclass - WHEN
write_command(path, cmd)is called - THEN a JSON file is written to
pathwith the exact schema above - AND
read_result(path)returns aReaScriptResultwith parsed fields
Scenario: Version mismatch handling
- GIVEN
read_result(path)is called and the result JSON hasversion≠ 1 - THEN a
ProtocolVersionErrorSHALL 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 <rpp_path> [--output <wav_path>] [--timeout <seconds>]
The CLI SHALL:
- Generate a ReaScript file to a watched folder (e.g.
REAPER ResourcePath()/scripts/fl_control_phase2.py) - Write the command JSON to
REAPER ResourcePath()/scripts/fl_control_command.json - Poll
REAPER ResourcePath()/scripts/fl_control_result.jsonuntil it exists or timeout is reached - Parse the result and print loudness metrics
- 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.rppexists with valid tracks - WHEN
python scripts/run_in_reaper.py output/song.rpp --output output/song.wavis executed - THEN the script generates the ReaScript, writes command JSON, waits for result JSON, and prints the LUFS measurement
- AND files
output/song_lufs.jsonandoutput/song_fx_errors.jsonare 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_GetFXNameon that slot - THEN
fx_errorsin the result JSON SHALL contain an entry with the track index, FX index, and empty name - AND the CLI writes
output/song_fx_errors.jsonwith 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.jsonbeyond 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 fileTrackFX_GetCount(track)— get number of FX on trackTrackFX_GetFXName(track, fx, buf, bufsize)— get FX name by indexTrackFX_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 trackMain_RenderFile(proj, filename)— render project to file (or use project render settings)GetProjectName(proj, buf, bufsize)— get current project nameGetTrackNumMediaItems(track)— count items on trackGetMediaItem(track, idx)— get media item by indexGetMediaItemTake(mediaitem, idx)— get take from itemGetMediaItemTake_Source(take)— get source from takeGetMediaSourceType(src, buf)— check if source is audio/videoPCM_Source_GetSectionInfo(src)— get source lengthGetMediaSourceFileName(src, buf, bufsize)— get source file pathSetMediaItemInfo_Value(item, param, value)— set item propertiesSetMediaItemLength(item, length, update)— set item lengthAddMediaItemToTrack(track)— add new media itemCreateNewMediaItem(track)— create new itemGetTrack(guid, flag)— get track by GUID (flag=0)GetTrackGUID(track, buf)— get track GUIDGetItemOwnership(mediaitem)— item ownership checkDeleteTrack(track)— delete trackDeleteMediaItem(mediaitem)— delete media itemUndo_OnStateChange(s)— mark undo pointPreventUIRefresh(PreventCount)— prevent UI refresh during batch opsOnPauseButton()— pause playbackOnPlayButton()— start playbackGetPlayState()— get play state (0=stopped, 1=playing, 2=paused)GetCursorPosition()— get playhead position in secondsGetProjectLengthSeconds(proj)— get project lengthGetUserInputs(title, captions, retvals)— optional: prompt user for inputShowConsoleMsg(msg)— debug output to REAPER consoleTimeMap_cur_qn_to_beats(qn)— convert quarter notes to beatsTimeMap_qn_to_time(qn)— convert quarter notes to time in secondsGetFunctionMetadata(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":
- Open Project: Call
Main_openProject(rpp_path)to load the .rpp - Verify FX: Iterate all tracks, for each call
TrackFX_GetCount(track), then for each FX slot callTrackFX_GetFXName— log empty/missing plugins - Calibrate Tracks: For each entry in
track_calibration, callSetMediaTrackInfo_Value(track, "VOLUME", volume)andSetMediaTrackInfo_Value(track, "PAN", pan), then for each send callCreateTrackSend - Render: Call
Main_RenderFilewith the project andrender_path(or use project render settings ifrender_pathis not provided) - Measure Loudness: Call
CalcMediaSrcLoudnesson the rendered WAV file to obtain integrated LUFS, short-term LUFS - 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: [], andtracks_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_errorsSHALL contain{"track_index": 3, "fx_index": 1, "name": "", "expected": ""} - AND rendering SHALL still proceed (verification does not halt the pipeline)