128 lines
4.6 KiB
Bash
Executable file
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
|