ba9ceeea90
- finishWav call sites accumulated n*4 (stereo 16-bit) but float32 writer emits n*8 bytes -> RIFF/data chunk sizes half the real value; players and soundfile truncated renders to half length - gate test now asserts file size == 44 + data_size to catch this
134 lines
5.5 KiB
Python
134 lines
5.5 KiB
Python
#!/usr/bin/env python3
|
|
"""Lossless audio compliance gate (lossless-audio-compliance skill).
|
|
|
|
Automated null-test gate for the SonicForge audio pipeline:
|
|
- Rule 6: phase-inversion null test, peak difference < -140 dBFS.
|
|
- Rule 7: offline renders MUST be IEEE Float32 WAV (format 3) — no 16-bit
|
|
truncation, no dither inside the engine (dither = final export only).
|
|
|
|
Usage:
|
|
python tests/test_lossless_compliance.py [bridge_binary]
|
|
|
|
Bridge render checks are skipped when no bridge binary is found (dev setups
|
|
before `install/daw_vst_bridge` exists); pure-Python checks always run.
|
|
"""
|
|
import json, math, os, struct, subprocess, sys, tempfile
|
|
|
|
import numpy as np
|
|
|
|
THRESHOLD_DB = -140.0
|
|
|
|
|
|
def harness_audit_null_test(original_path, rendered_path, threshold_db=THRESHOLD_DB):
|
|
"""Skill §V: bit-perfect null test between two WAV files."""
|
|
import soundfile as sf
|
|
orig, sr1 = sf.read(original_path, dtype="float32")
|
|
rend, sr2 = sf.read(rendered_path, dtype="float32")
|
|
if sr1 != sr2:
|
|
print(f"[HARNESS FAIL] Sample rate mismatch: {sr1}Hz vs {sr2}Hz")
|
|
return False
|
|
min_len = min(len(orig), len(rend))
|
|
diff = orig[:min_len] - rend[:min_len]
|
|
max_peak = float(np.max(np.abs(diff)))
|
|
diff_db = -np.inf if max_peak == 0.0 else 20 * math.log10(max_peak)
|
|
print(f"[HARNESS AUDIT] Peak Difference: {max_peak:.9f} ({diff_db:.2f} dBFS)")
|
|
if diff_db < threshold_db:
|
|
print("[HARNESS PASS] Audio Pipeline is Bit-Perfect Lossless!")
|
|
return True
|
|
print(f"[HARNESS FAIL] Deviation {diff_db:.2f} dBFS exceeds threshold {threshold_db} dBFS!")
|
|
return False
|
|
|
|
|
|
def wav_format_info(path):
|
|
with open(path, "rb") as f:
|
|
b = f.read(44)
|
|
assert b[:4] == b"RIFF" and b[8:12] == b"WAVE", "not RIFF/WAVE"
|
|
data_size = struct.unpack_from("<I", b, 40)[0]
|
|
assert os.path.getsize(path) == 44 + data_size, "data chunk size mismatch"
|
|
return {
|
|
"fmt_tag": struct.unpack_from("<H", b, 20)[0],
|
|
"channels": struct.unpack_from("<H", b, 22)[0],
|
|
"sr": struct.unpack_from("<I", b, 24)[0],
|
|
"bits": struct.unpack_from("<H", b, 34)[0],
|
|
}
|
|
|
|
|
|
def find_bridge(explicit=None):
|
|
if explicit and os.path.isfile(explicit):
|
|
return explicit
|
|
is_win = os.name == "nt"
|
|
cands = []
|
|
if not is_win:
|
|
cands.append(os.path.join(os.path.dirname(__file__), "..", "install", "daw_vst_bridge"))
|
|
cands.append(os.path.join(os.path.dirname(__file__), "..", "install", "daw_vst_bridge.exe"))
|
|
for c in cands:
|
|
if os.path.isfile(c) and (is_win or os.access(c, os.X_OK)):
|
|
return c
|
|
return ""
|
|
|
|
|
|
def _make_job(itype, path, outdir):
|
|
return {
|
|
"instrument_type": itype,
|
|
"plugin_path": path,
|
|
"sample_rate": 44100,
|
|
"bpm": 120.0,
|
|
"notes": [{"pitch": 60, "velocity": 0.8, "start_beat": 0.0, "duration_beats": 1.0}],
|
|
}
|
|
|
|
|
|
def main():
|
|
results = []
|
|
def check(name, ok, detail=""):
|
|
results.append((name, ok))
|
|
print(("PASS" if ok else "FAIL"), name, "-", detail)
|
|
|
|
# ── 1. Pure-Python null test: float32 round-trip must be bit-exact ────────
|
|
import soundfile as sf
|
|
with tempfile.TemporaryDirectory() as td:
|
|
sr = 44100
|
|
t = np.arange(sr) / sr
|
|
sine = (0.25 * np.sin(2 * np.pi * 440 * t)).astype("float32")
|
|
a = os.path.join(td, "a.wav")
|
|
sf.write(a, sine, sr, subtype="FLOAT")
|
|
info = wav_format_info(a)
|
|
check("float32 WAV format tag 3", info["fmt_tag"] == 3, str(info))
|
|
check("float32 WAV bits 32", info["bits"] == 32, str(info))
|
|
check("null test: float32 round-trip bit-exact", harness_audit_null_test(a, a))
|
|
|
|
# ── 2. Bridge offline render: float32 + deterministic bit-exact ─────────
|
|
bridge = find_bridge(sys.argv[1] if len(sys.argv) > 1 else None)
|
|
sfz = os.path.join(os.path.dirname(__file__), "..", "native_bridge", "tests", "g2_test.sfz")
|
|
if bridge and os.path.exists(sfz):
|
|
j1 = os.path.join(td, "r1.json"); o1 = os.path.join(td, "r1.wav")
|
|
j2 = os.path.join(td, "r2.json"); o2 = os.path.join(td, "r2.wav")
|
|
for j, o in ((j1, o1), (j2, o2)):
|
|
with open(j, "w") as f:
|
|
json.dump(_make_job(3, sfz, td), f)
|
|
r = subprocess.run([bridge, "--render", j, "--out", o],
|
|
capture_output=True, text=True, timeout=120)
|
|
check(f"bridge --render exit 0 ({os.path.basename(o)})",
|
|
r.returncode == 0, (r.stdout + r.stderr)[-200:])
|
|
if os.path.exists(o1):
|
|
info = wav_format_info(o1)
|
|
check("bridge output IEEE float32 (tag 3)", info["fmt_tag"] == 3, str(info))
|
|
data, _ = sf.read(o1, dtype="float32")
|
|
check("bridge output non-silent", float(np.max(np.abs(data))) > 1e-3,
|
|
f"peak={float(np.max(np.abs(data))):.5f}")
|
|
if os.path.exists(o1) and os.path.exists(o2):
|
|
b1, b2 = open(o1, "rb").read(), open(o2, "rb").read()
|
|
check("bridge render deterministic (byte-identical)", b1 == b2,
|
|
f"{len(b1)} vs {len(b2)} bytes")
|
|
check("bridge null test bit-exact", harness_audit_null_test(o1, o2))
|
|
else:
|
|
print("SKIP bridge render checks (no binary; set install/daw_vst_bridge or pass path)")
|
|
|
|
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())
|