feat: reascript-first — built-in REAPER plugin insertion via ReaScript API

Hybrid pipeline: RPPBuilder writes VST3/MIDI/audio skeleton, ReaScript
handles built-in plugins (ReaEQ, ReaComp) via TrackFX_AddByName +
TrackFX_SetParam with multi-action dispatch, adaptive API check, and
builtin plugin auto-detection from PLUGIN_REGISTRY.

326 tests (298 existing + 28 new), 12/12 spec scenarios compliant.
This commit is contained in:
renato97
2026-05-04 09:38:58 -03:00
parent 33bb08270d
commit b08dcccca2
11 changed files with 1470 additions and 83 deletions

View File

@@ -1,6 +1,7 @@
"""CLI to run Phase 2 ReaScript refinement on a .rpp file.
"""CLI to run ReaScript refinement on a .rpp file.
Usage: python scripts/run_in_reaper.py <rpp_path> [--output <wav_path>] [--timeout <seconds>]
[--plugins-config <song_json_path>] [--action <action1 action2 ...>]
"""
from __future__ import annotations
@@ -20,11 +21,70 @@ from src.reaper_scripting.commands import (
read_result,
write_command,
)
from src.reaper_builder import get_builtin_plugins
from src.core.schema import (
SongDefinition,
SongMeta,
TrackDef,
PluginDef,
)
def main():
def _load_song_config(path: Path) -> SongDefinition:
"""Reconstruct a SongDefinition from a JSON file (produced by SongDefinition.to_json)."""
data: dict = json.loads(path.read_text(encoding="utf-8"))
meta_data: dict = data["meta"]
meta = SongMeta(
bpm=float(meta_data["bpm"]),
key=str(meta_data["key"]),
title=str(meta_data.get("title", "")),
ppq=int(meta_data.get("ppq", 960)),
time_sig_num=int(meta_data.get("time_sig_num", 4)),
time_sig_den=int(meta_data.get("time_sig_den", 4)),
calibrate=bool(meta_data.get("calibrate", True)),
)
tracks: list[TrackDef] = []
for t in data.get("tracks", []):
plugins: list[PluginDef] = []
for p in t.get("plugins", []):
params: dict[int, float] = {}
for k, v in p.get("params", {}).items():
params[int(k)] = float(v)
plugins.append(PluginDef(
name=str(p["name"]),
path=str(p.get("path", "")),
index=int(p.get("index", 0)),
params=params,
preset_data=p.get("preset_data"),
role=str(p.get("role", "")),
builtin=bool(p.get("builtin", False)),
))
tracks.append(TrackDef(
name=str(t["name"]),
volume=float(t.get("volume", 0.85)),
pan=float(t.get("pan", 0.0)),
color=int(t.get("color", 0)),
plugins=plugins,
send_reverb=float(t.get("send_reverb", 0.0)),
send_delay=float(t.get("send_delay", 0.0)),
send_level={int(k): float(v) for k, v in t.get("send_level", {}).items()},
))
return SongDefinition(meta=meta, tracks=tracks)
def _normalize_action(action_arg: str | None) -> list[str]:
"""Parse space-separated action names into a list. Defaults to ['calibrate']."""
if not action_arg:
return ["calibrate"]
return [a.strip() for a in action_arg.split() if a.strip()]
def main() -> None:
parser = argparse.ArgumentParser(
description="Run Phase 2 ReaScript refinement on a .rpp file"
description="Run ReaScript refinement on a .rpp file"
)
parser.add_argument("rpp_path", help="Path to .rpp file")
parser.add_argument(
@@ -36,6 +96,16 @@ def main():
default=120,
help="Timeout in seconds for polling result (default: 120)",
)
parser.add_argument(
"--plugins-config",
help="Path to JSON serialized SongDefinition for deriving plugins_to_add",
default=None,
)
parser.add_argument(
"--action",
help="Space-separated action pipeline (e.g. 'add_plugins calibrate render'). Default: calibrate",
default=None,
)
args = parser.parse_args()
rpp_path = Path(args.rpp_path)
@@ -49,14 +119,29 @@ def main():
else:
render_path = rpp_path.parent / f"{rpp_path.stem}_rendered.wav"
# Normalize action to list
actions = _normalize_action(args.action)
# Derive plugins_to_add from song if --plugins-config is provided
plugins_to_add: list[dict] = []
if args.plugins_config:
config_path = Path(args.plugins_config)
if not config_path.exists():
print(f"ERROR: plugins config file not found: {config_path}", file=sys.stderr)
sys.exit(1)
song = _load_song_config(config_path)
plugins_to_add = get_builtin_plugins(song)
print(f"Derived {len(plugins_to_add)} builtin plugins from song config")
# 1. Build command
command = ReaScriptCommand(
version=1,
action="calibrate",
action=actions,
rpp_path=str(rpp_path.resolve()),
render_path=str(render_path.resolve()),
timeout=args.timeout,
track_calibration=[],
plugins_to_add=plugins_to_add,
)
# 2. Generate ReaScript
@@ -112,6 +197,8 @@ def main():
print(f"Tracks verified: {result.tracks_verified}")
if result.fx_errors:
print(f"FX errors: {json.dumps(result.fx_errors, indent=2)}")
if result.added_plugins:
print(f"Added plugins: {json.dumps(result.added_plugins, indent=2)}")
print(f"Message: {result.message}")
# 6. Write output files