#!/usr/bin/env python3 # Phase 0 offline render test: `daw_vst_bridge --render --out ` # SF2 + SFZ through the same engines as realtime. Asserts: # - exit 0, WAV exists, valid RIFF, exact duration (totalNeeded frames) # - audio non-silent (RMS > 0.001) # - error paths: missing --out -> rc 1; bad plugin_path -> rc 2 # Usage: python tests/test_offline_render.py import json, math, os, struct, subprocess, sys, tempfile BRIDGE = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "build", "Release", "daw_vst_bridge.exe")) SF2 = r"C:\Users\locpham\SonicForgeStudio\app\storage\soundfonts\518e850f-a5d3-4790-b1f9-0c90c203c524.sf2" SFZ = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "sfizz-src", "tests", "TestFiles", "g2_test.sfz")) BPM, SR = 120.0, 44100 BEAT_SEC = 60.0 / BPM TOTAL = int(max(1024, round((0.0 + 1.0) * BEAT_SEC * SR))) # 22050 def read_wav(path): b = open(path, "rb").read() assert b[:4] == b"RIFF" and b[8:12] == b"WAVE", "not a RIFF/WAVE file" assert b[36:40] == b"data", "no data chunk" data_size = struct.unpack_from(" 0.001, f"rms={rms:.5f}") else: check("SF2 render exit 0", False, f"rc={rc} {log[-400:]}") # 2. SFZ render (g2_test.sfz one-shot decays ~200ms; 0.5s clip still has signal) job = os.path.join(td, "sfz.json"); out = os.path.join(td, "sfz.wav") rc, log = run_render(make_job(3, SFZ, td), out) if rc == 0 and os.path.exists(out): frames, rms = read_wav(out) check("SFZ render exit 0", rc == 0, log.strip().splitlines()[-1:] or "") check("SFZ exact duration", frames == TOTAL, f"{frames} == {TOTAL}") check("SFZ non-silent", rms > 0.001, f"rms={rms:.5f}") else: check("SFZ render exit 0", False, f"rc={rc} {log[-400:]}") # 3. Missing --out -> rc 1 job = os.path.join(td, "noout.json") with open(job, "w", encoding="utf-8") as f: json.dump(make_job(2, SF2, td), f) r = subprocess.run([BRIDGE, "--render", job], capture_output=True, text=True, timeout=30) check("missing --out -> rc 1", r.returncode == 1, f"rc={r.returncode}") # 4. Bad plugin path -> rc 2 job = os.path.join(td, "bad.json"); out = os.path.join(td, "bad.wav") rc, log = run_render(make_job(2, r"C:\nonexistent\no.sf2", td), out) check("bad plugin_path -> rc 2", rc == 2, f"rc={rc}") check("bad render removes partial out", not os.path.exists(out), "no clip left behind") # 5. VST3 render (Nexus) - baseline without preset NEXUS = r"C:\Program Files\Common Files\VST3\Nexus.vst3" GOLDEN = os.path.abspath(os.path.join(os.path.dirname(__file__), "golden", "nexus_default.vstpreset")) if os.path.exists(NEXUS) and os.path.exists(GOLDEN): import base64 with open(GOLDEN, "rb") as f: preset_b64 = base64.b64encode(f.read()).decode("ascii") job = os.path.join(td, "vst3.json"); out = os.path.join(td, "vst3.wav") rc, log = run_render(make_job(0, NEXUS, td), out) if rc == 0 and os.path.exists(out): frames, rms = read_wav(out) check("VST3 render exit 0", rc == 0, log.strip().splitlines()[-1:] or "") check("VST3 exact duration", frames == TOTAL, f"{frames} == {TOTAL}") check("VST3 non-silent", rms > 0.001, f"rms={rms:.5f}") else: check("VST3 render exit 0", False, f"rc={rc} {log[-400:]}") # 6. VST3 render WITH .vstpreset - same default state, must still # render (proves the parse + loadSerializedState path runs). job = os.path.join(td, "vst3p.json"); out = os.path.join(td, "vst3p.wav") jobdict = make_job(0, NEXUS, td); jobdict["preset"] = preset_b64 rc, log = run_render(jobdict, out) if rc == 0 and os.path.exists(out): frames, rms = read_wav(out) check("VST3+preset exit 0", rc == 0, log.strip().splitlines()[-1:] or "") check("VST3+preset exact duration", frames == TOTAL, f"{frames} == {TOTAL}") check("VST3+preset non-silent", rms > 0.001, f"rms={rms:.5f}") check("VST3+preset applied (log)", "preset imported" in log, "log has preset imported") else: check("VST3+preset exit 0", False, f"rc={rc} {log[-400:]}") # 7. Corrupt preset (not base64) -> rc 1, no partial clip job = os.path.join(td, "vst3bad.json"); out = os.path.join(td, "vst3bad.wav") jobdict = make_job(0, NEXUS, td); jobdict["preset"] = "!!!not-base64!!!" rc, log = run_render(jobdict, out) check("VST3 bad preset -> rc 1", rc == 1, f"rc={rc}") check("bad preset removes partial out", not os.path.exists(out), "no clip left behind") # 8. Non-VST3 with preset: ignored (warning), still renders fine job = os.path.join(td, "sf2p.json"); out = os.path.join(td, "sf2p.wav") jobdict = make_job(2, SF2, td); jobdict["preset"] = preset_b64 rc, log = run_render(jobdict, out) if rc == 0 and os.path.exists(out): frames, rms = read_wav(out) check("SF2+preset exit 0 (ignored)", rc == 0, log.strip().splitlines()[-1:] or "") check("SF2+preset non-silent", rms > 0.001, f"rms={rms:.5f}") else: check("SF2+preset exit 0 (ignored)", False, f"rc={rc} {log[-400:]}") failed = [n for n, ok, _ in results if not ok] print() print(f"{len(results) - len(failed)}/{len(results)} passed") return 1 if failed else 0 if __name__ == "__main__": sys.exit(main())