151 lines
5.6 KiB
Bash
Executable file
151 lines
5.6 KiB
Bash
Executable file
#!/usr/bin/env bash
|
|
# Part of JoeyLib, Copyright (c) 2026 Scott Duensing, under the MIT License.
|
|
# See LICENSE.md for license information.
|
|
# SPDX-License-Identifier: MIT
|
|
#
|
|
# 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 awk -v tolerance="$tolerance" '
|
|
BEGIN {
|
|
for (i = 0; i < 256; i++) {
|
|
byteOf[sprintf("%02X", i)] = i
|
|
}
|
|
}
|
|
FNR == 1 {
|
|
name = ""
|
|
}
|
|
{
|
|
gsub(/^[ \t\r]+|[ \t\r]+$/, "")
|
|
}
|
|
/^name=/ {
|
|
delete fields
|
|
for (i = 1; i <= NF; i++) {
|
|
at = index($i, "=")
|
|
fields[substr($i, 1, at - 1)] = substr($i, at + 1)
|
|
}
|
|
name = fields["name"]
|
|
order[ARGIND, ++count[ARGIND]] = name
|
|
hash[ARGIND, name] = fields["hash"]
|
|
slots[ARGIND, name] = fields["slots"] + 0
|
|
raw[ARGIND, name] = ""
|
|
seen[ARGIND, name] = 1
|
|
}
|
|
/^raw=/ && name != "" {
|
|
raw[ARGIND, name] = toupper(substr($0, 5))
|
|
}
|
|
END {
|
|
same = count[1] == count[2]
|
|
for (i = 1; i <= count[1] && same; i++) {
|
|
same = order[1, i] == order[2, i]
|
|
}
|
|
if (!same) {
|
|
print "check-sfxmix: scenario list changed vs baseline"
|
|
line = " baseline:"
|
|
for (i = 1; i <= count[1]; i++) {
|
|
line = line " " order[1, i]
|
|
}
|
|
print (count[1] > 0 ? line : line " ")
|
|
line = " current: "
|
|
for (i = 1; i <= count[2]; i++) {
|
|
line = line " " order[2, i]
|
|
}
|
|
print (count[2] > 0 ? line : line " ")
|
|
failed = 1
|
|
}
|
|
for (i = 1; i <= count[1]; i++) {
|
|
name = order[1, i]
|
|
if (!((2, name) in seen)) {
|
|
continue
|
|
}
|
|
label = sprintf(" %-24s", name ":")
|
|
baseLen = length(raw[1, name]) / 2
|
|
curLen = length(raw[2, name]) / 2
|
|
if (baseLen != curLen) {
|
|
printf "%s FAIL (length %d -> %d)\n", label, baseLen, curLen
|
|
failed = 1
|
|
continue
|
|
}
|
|
if (tolerance == "") {
|
|
if (hash[1, name] == hash[2, name] && raw[1, name] == raw[2, name]) {
|
|
printf "%s PASS (hash %s)\n", label, hash[2, name]
|
|
} else {
|
|
printf "%s FAIL (hash %s -> %s)\n", label, hash[1, name], hash[2, name]
|
|
failed = 1
|
|
}
|
|
continue
|
|
}
|
|
maxDelta = 0
|
|
for (b = 1; b <= baseLen; b++) {
|
|
delta = byteOf[substr(raw[1, name], 2 * b - 1, 2)] - byteOf[substr(raw[2, name], 2 * b - 1, 2)]
|
|
delta = delta < 0 ? -delta : delta
|
|
maxDelta = delta > maxDelta ? delta : maxDelta
|
|
}
|
|
allowed = tolerance * slots[1, name]
|
|
printf "%s maxAbsDelta=%d (allowed %d) %s\n", label, maxDelta, allowed, (maxDelta <= allowed ? "PASS" : "FAIL")
|
|
failed = failed || maxDelta > allowed
|
|
}
|
|
exit failed ? 1 : 0
|
|
}' "$baseline" "$work/out1.txt"
|
|
then
|
|
echo "check-sfxmix: PASS"
|
|
else
|
|
echo "check-sfxmix: FAIL (mixer output diverged from tests/goldens/sfxmix-baseline.txt)" >&2
|
|
exit 1
|
|
fi
|