#!/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. tests/gate/audioGate.c 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; build/sdl3/bin/AUDIO (make sdl3). set -uo pipefail repo=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd) source "$repo/scripts/lib/joeyEnv.sh" grader=$(joeyTool audioGate) || exit 1 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; 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; } "$grader" wav "$work/out.raw" "$work/out.wav" 44100 # Thresholds sit at about half the measured levels (music ~6150, paused 0, # sfx ~310, noise ~3260, mod ~3990, measured after the 2026-10-16 mix; the # grader averages the two channels) so the verdict has room either way. "$grader" phases "$label" "$work/out.wav" \ "music:1:5:3000:" \ "paused:8:13::100" \ "sfx:15:22:150:" \ "resumed:25:29:3000:" \ "noise:31.5:34:1600:" \ "mod:37:43:2000:" status=$? if [ -n "${SDL3_AUDIO_KEEP:-}" ]; then cp "$work/out.wav" "$repo/build/sdl3/headless-audio.wav" fi exit $status