Files
soothe2-re/verify_bit_exact.py
T

109 lines
3.2 KiB
Python
Executable File

#!/usr/bin/env python3
"""bit-exact verification — прогон всех свипов, побайтовое сравнение int24 PCM."""
import subprocess
import os
import glob
import wave
import struct
import sys
RENDERER = "/home/m/re-tools/dsp/harness"
TEST_ROOT = "/home/m/soothe-bt"
def read_wav24(path):
"""Read WAV24 file and return numpy array or list of int24 samples."""
with wave.open(path, 'rb') as w:
n = w.getnframes()
data = w.readframes(n * w.getnchannels())
sw = w.getsampwidth()
nc = w.getnchannels()
if sw == 3:
samples = []
for i in range(0, len(data), 3):
val = struct.unpack('<i', data[i:i+3] + b'\x00')[0]
if val & 0x800000:
val -= 0x1000000
samples.append(val)
return samples, nc
else:
return list(struct.unpack(f'<{len(data)//2}h', data)), nc
def run_harness(in_wav, out_wav):
"""Run harness.cpp renderer."""
cmd = [RENDERER, in_wav, out_wav]
try:
result = subprocess.run(cmd, capture_output=True, text=True, timeout=60)
return result.returncode == 0
except Exception as e:
print(f"Error running {in_wav}: {e}")
return False
def compare_bytes(path1, path2):
"""Compare two WAV files byte-by-byte. Returns (match, diff_count, max_diff)."""
with wave.open(path1, 'rb') as w1, wave.open(path2, 'rb') as w2:
if w1.getparams() != w2.getparams():
return False, -1, -1
data1 = w1.readframes(w1.getnframes() * w1.getnchannels())
data2 = w2.readframes(w2.getnframes() * w2.getnchannels())
if len(data1) != len(data2):
return False, abs(len(data1) - len(data2)), -1
diff = sum(1 for i in range(len(data1)) if data1[i] != data2[i])
return diff == 0, diff, 0
def test_sweep(name):
"""Test single sweep: in.wav -> render -> out.wav, compare to ref.wav."""
in_wav = f"{TEST_ROOT}/{name}.wav"
ref_wav = f"{TEST_ROOT}/{name}_ref.wav"
out_wav = f"/tmp/out_{name}.wav"
if not os.path.exists(in_wav):
print(f"SKIP {name}: no input")
return None
if not run_harness(in_wav, out_wav):
print(f"FAIL {name}: renderer error")
return False
match, diff, max_diff = compare_bytes(ref_wav, out_wav)
if match:
print(f"PASS {name}")
return True
else:
print(f"FAIL {name}: {diff} byte differences")
return False
def main():
rpps = sorted(glob.glob(f"{TEST_ROOT}/*.rpp"))
print(f"Found {len(rpps)} RPP files")
passed = 0
failed = 0
skipped = 0
for rpp in rpps[:10]: # Test first 10
name = os.path.splitext(os.path.basename(rpp))[0]
wav = f"{TEST_ROOT}/{name}.wav"
if not os.path.exists(wav):
skipped += 1
continue
result = test_sweep(name)
if result is True:
passed += 1
elif result is False:
failed += 1
else:
skipped += 1
print(f"\nSummary: {passed} passed, {failed} failed, {skipped} skipped")
if __name__ == "__main__":
main()