#!/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