127 lines
4.5 KiB
Bash
Executable file
127 lines
4.5 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
|
|
#
|
|
# verify-sdl3-audio.sh - SDL3 audio gate, graded on the sound itself.
|
|
#
|
|
# The SDL3 port mixes everything in software (src/hosted/audio.c), so the
|
|
# gate records the mix exactly as it leaves the program: SDL's disk audio
|
|
# driver writes it to a file at the real-time rate (signed 16-bit stereo at
|
|
# 44.1 kHz, pinned by SDL_AUDIO_FORMAT/CHANNELS/FREQUENCY), with no sound
|
|
# server involved. tools/gradeAudioPhases.py grades it, as the Amiga and DOS
|
|
# gates grade their recordings.
|
|
#
|
|
# The AUDIO example runs on a private Xvfb display (keys through XTEST,
|
|
# HELD for 0.4 s: the demo tests jlKeyPressed, an edge predicate, once a
|
|
# frame) through the DOS gate's four phases and two more:
|
|
#
|
|
# music the baked tracker song on entry (the square-wave voices), loud
|
|
# paused P: near silence
|
|
# sfx SPACE during the pause, so any sound is the digital SFX
|
|
# resumed P again: loud again
|
|
# noise P, then N: the noise voice swept through its 32 pitches alone
|
|
# mod M: the .MOD through the module player (src/core/modPlayer.c)
|
|
#
|
|
# The file starts when the program opens its audio, a fraction of a second
|
|
# after launch, so each phase is graded inside its window with margin.
|
|
#
|
|
# Usage: scripts/verify-sdl3-audio.sh
|
|
# Env: SDL3_AUDIO_KEEP set to keep the recording as build/sdl3/headless-audio.wav
|
|
# Requires: Xvfb, xdotool, python3; build/sdl3/bin/AUDIO (make sdl3).
|
|
set -uo pipefail
|
|
|
|
repo=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)
|
|
source "$repo/scripts/lib/joeyEnv.sh"
|
|
grader=$repo/tools/gradeAudioPhases.py
|
|
label=verify-sdl3-audio
|
|
|
|
program=$(joeyExampleProgram audio sdl3)
|
|
[ -x "$program" ] || { echo "$label: build AUDIO first (make sdl3)" >&2; exit 2; }
|
|
for tool in Xvfb xdotool python3; do
|
|
command -v "$tool" >/dev/null || { echo "$label: $tool not installed" >&2; exit 2; }
|
|
done
|
|
|
|
work=$(mktemp -d -t joeylib-sdl3-audio.XXXXXX)
|
|
displayNum=$((RANDOM % 500 + 300))
|
|
display=":$displayNum"
|
|
cleanup() {
|
|
joeyStop "${audioPid:-}"
|
|
[ -n "${xvfbPid:-}" ] && kill "$xvfbPid" 2>/dev/null
|
|
rm -rf "$work"
|
|
return 0
|
|
}
|
|
trap cleanup EXIT
|
|
|
|
Xvfb "$display" -screen 0 1024x768x24 -nolisten tcp >/dev/null 2>&1 &
|
|
xvfbPid=$!
|
|
sleep 1
|
|
|
|
DISPLAY="$display" SDL_VIDEO_DRIVER=x11 SDL_AUDIO_DRIVER=disk SDL_AUDIO_DISK_OUTPUT_FILE="$work/out.raw" \
|
|
SDL_AUDIO_FORMAT=S16LE SDL_AUDIO_CHANNELS=2 SDL_AUDIO_FREQUENCY=44100 \
|
|
"$program" > "$work/audio.log" 2>&1 &
|
|
audioPid=$!
|
|
sleep 1
|
|
|
|
win=$(joeyFindWindow "$display" "$audioPid") || true
|
|
if [ -z "$win" ]; then
|
|
echo "$label: FAIL - no AUDIO window to send keys to" >&2
|
|
exit 1
|
|
fi
|
|
DISPLAY="$display" xdotool windowfocus --sync "$win"
|
|
|
|
hold() { # $1 = key name
|
|
DISPLAY="$display" xdotool keydown "$1"
|
|
sleep 0.4
|
|
DISPLAY="$display" xdotool keyup "$1"
|
|
}
|
|
|
|
# The phase windows below are graded against these sleeps (seconds since
|
|
# launch); keep them in step.
|
|
sleep 5 # 0-6s music
|
|
hold p # -> paused
|
|
sleep 8 # 6-14s paused
|
|
for _ in 1 2 3; do # 14-24s sfx into silence
|
|
hold space
|
|
sleep 1.5
|
|
done
|
|
sleep 4
|
|
hold p # -> resumed
|
|
sleep 5.6 # 24-30s resumed
|
|
hold p # -> paused again
|
|
sleep 0.6
|
|
hold n # 31-35s noise sweep alone
|
|
sleep 4.6
|
|
hold m # 36-44s the MOD
|
|
sleep 7.6
|
|
hold Escape
|
|
sleep 1
|
|
|
|
[ -s "$work/out.raw" ] || { echo "$label: FAIL - recorded no audio at all" >&2; exit 1; }
|
|
python3 - "$work/out.raw" "$work/out.wav" <<'PY'
|
|
import sys
|
|
import wave
|
|
|
|
raw = open(sys.argv[1], 'rb').read()
|
|
with wave.open(sys.argv[2], 'wb') as w:
|
|
w.setnchannels(2)
|
|
w.setsampwidth(2)
|
|
w.setframerate(44100)
|
|
w.writeframes(raw)
|
|
PY
|
|
|
|
# Thresholds sit at about half the measured levels (music ~3070, paused 0,
|
|
# sfx ~1240, noise ~1630, mod ~7980; the grader averages the two channels)
|
|
# so the verdict has room either way.
|
|
python3 "$grader" "$label" "$work/out.wav" \
|
|
"music:1:5:1500:" \
|
|
"paused:8:13::100" \
|
|
"sfx:15:22:600:" \
|
|
"resumed:25:29:1500:" \
|
|
"noise:31.5:34:800:" \
|
|
"mod:37:43:4000:"
|
|
status=$?
|
|
if [ -n "${SDL3_AUDIO_KEEP:-}" ]; then
|
|
cp "$work/out.wav" "$repo/build/sdl3/headless-audio.wav"
|
|
fi
|
|
exit $status
|