joeylib2/scripts/check-sfxmix.sh

128 lines
4.6 KiB
Bash
Executable file

#!/usr/bin/env bash
# check-sfxmix.sh - Host-cc differential harness for the shared SFX
# overlay mixer (src/core/audioSfxMix.c), built on the check-blank.sh /
# check-millis.sh pattern. Compiles the CURRENT mixer TU with the host
# cc plus tests/host/sfxMixHost.c and runs a fixed-seed scenario set
# (fixed-buffer + streaming slots, rate up/down, multi-slot, mid-mix
# refill and end-of-stream, amplitude 0/max, >64k read positions).
#
# First run captures tests/goldens/sfxmix-baseline.txt (capture it
# against the CURRENT mixer (the go-forward reference)); later runs diff per scenario:
# default exact mode: FNV-1a hash + raw bytes must match
# the baseline (gate for findings #7 and #33,
# which must be bit-identical).
# JL_SFX_TOLERANCE=1 per-sample mode for finding #6: prints each
# scenario's max-abs-delta and allows a deviation
# of tolerance * slots per output sample (the
# 1-LSB interp deviation stacks across
# simultaneous slots before the clamp).
#
# The harness binary is also run twice and the outputs compared, so a
# nondeterministic harness (or mixer reading uninitialized memory)
# fails loudly instead of producing a flaky baseline.
set -euo pipefail
repo=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)
cc=${CC:-cc}
work=$(mktemp -d -t joeylib-sfxmix.XXXXXX)
trap 'rm -rf "$work"' EXIT
baseline="$repo/tests/goldens/sfxmix-baseline.txt"
cd "$repo"
# BLANK platform: audioSfxMix.c is portable C with no jlp* hooks; the
# stream fill callback is mocked inside sfxMixHost.c.
"$cc" -DJOEYLIB_PLATFORM_BLANK \
-Iinclude -Iinclude/joey -Isrc/core -Wall \
src/core/audioSfxMix.c tests/host/sfxMixHost.c \
-o "$work/sfxMixHost"
"$work/sfxMixHost" > "$work/out1.txt"
"$work/sfxMixHost" > "$work/out2.txt"
if ! cmp -s "$work/out1.txt" "$work/out2.txt"; then
echo "check-sfxmix: FAIL - harness output differs across two runs (nondeterministic)" >&2
exit 1
fi
if [ ! -f "$baseline" ]; then
mkdir -p "$(dirname "$baseline")"
cp "$work/out1.txt" "$baseline"
count=$(grep -c '^name=' "$baseline")
echo "check-sfxmix: baseline captured ($count scenarios) -> tests/goldens/sfxmix-baseline.txt"
exit 0
fi
tolerance="${JL_SFX_TOLERANCE:-}"
if [ -n "$tolerance" ]; then
echo "check-sfxmix: comparing against baseline (per-sample, tolerance ${tolerance} LSB * slots)"
else
echo "check-sfxmix: comparing against baseline (exact hash mode)"
fi
if python3 - "$baseline" "$work/out1.txt" "$tolerance" <<'PY'
import sys
def parse(path):
order = []
scen = {}
name = None
with open(path) as f:
for line in f:
line = line.strip()
if line.startswith("name="):
fields = dict(p.split("=", 1) for p in line.split())
name = fields["name"]
order.append(name)
scen[name] = {"hash": fields["hash"], "slots": int(fields["slots"]), "raw": b""}
elif line.startswith("raw=") and name is not None:
scen[name]["raw"] = bytes.fromhex(line[4:])
return order, scen
baseOrder, base = parse(sys.argv[1])
curOrder, cur = parse(sys.argv[2])
tolerance = int(sys.argv[3]) if sys.argv[3] else None
fail = False
if curOrder != baseOrder:
print("check-sfxmix: scenario list changed vs baseline")
print(" baseline: " + " ".join(baseOrder))
print(" current: " + " ".join(curOrder))
fail = True
for name in baseOrder:
if name not in cur:
continue
b = base[name]
c = cur[name]
label = " %-24s" % (name + ":")
if len(b["raw"]) != len(c["raw"]):
print("%s FAIL (length %d -> %d)" % (label, len(b["raw"]), len(c["raw"])))
fail = True
continue
if tolerance is None:
ok = (b["hash"] == c["hash"]) and (b["raw"] == c["raw"])
if ok:
print("%s PASS (hash %s)" % (label, c["hash"]))
else:
print("%s FAIL (hash %s -> %s)" % (label, b["hash"], c["hash"]))
fail = True
else:
maxDelta = 0
for x, y in zip(b["raw"], c["raw"]):
d = abs(x - y)
if d > maxDelta:
maxDelta = d
allowed = tolerance * b["slots"]
ok = maxDelta <= allowed
print("%s maxAbsDelta=%d (allowed %d) %s" % (label, maxDelta, allowed, "PASS" if ok else "FAIL"))
fail = fail or not ok
sys.exit(1 if fail else 0)
PY
then
echo "check-sfxmix: PASS"
else
echo "check-sfxmix: FAIL (mixer output diverged from tests/goldens/sfxmix-baseline.txt)" >&2
exit 1
fi