#!/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-win32-audio.sh - Win32 audio gate, graded on recorded sound. # # The port mixes in software (src/hosted/audio.c) and plays through waveOut. # Under Wine that reaches PulseAudio, and PULSE_SINK sends this one run to a # null sink made for it (scripts/pulseRecordLib.sh), so the user's # own sound is never touched; parec records the sink's monitor and # tests/gate/audioGate.c grades it through the same six phases as the # SDL3 gate: music, paused, SFX into the pause, resumed, the noise sweep # alone, the MOD. Wine's sound starts some seconds after the program does # (about 8 here, in its driver's start-up, before the first waveOut buffer # plays), so the clock starts when the song is first heard: the gate records # from the moment AUDIO's window is up, waits for the first sound, runs the # keys (held 0.4 s: AUDIO reads jlKeyPressed once a frame) from there, and # grades the recording from that first sound on. # # Usage: scripts/verify-win32-audio.sh # Env: WIN32_AUDIO_KEEP set to keep the recording as # build/win32/headless-audio.wav # Requires: Wine, Xvfb, xdotool, parec/pactl; # build/win32/bin/AUDIO.EXE (make win32). set -uo pipefail repo=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd) source "$repo/scripts/lib/joeyEnv.sh" source "$repo/scripts/lib/wineGate.sh" source "$repo/scripts/pulseRecordLib.sh" wineNeed grader=$(joeyTool audioGate) || exit 1 label=verify-win32-audio program=$(joeyExampleProgram audio win32) [ -f "$program" ] || { echo "$label: build AUDIO.EXE first (make win32)" >&2; exit 2; } for tool in Xvfb xdotool parec pactl; do command -v "$tool" > /dev/null || { echo "$label: $tool not installed" >&2; exit 2; } done pulseEnsureServer || { echo "$label: no PulseAudio/PipeWire server to record from" >&2; exit 2; } work=$(mktemp -d -t "joeylib-win32-audio.XXXXXX") sinkModule="" parecPid="" cleanup() { [ -n "$parecPid" ] && kill "$parecPid" 2> /dev/null wineStop pulseUnloadSink "$sinkModule" rm -rf "$work" return 0 } trap cleanup EXIT sinkName="joeyWin32Gate$$" RECORD_LATENCY_MS=250 sinkModule=$(pulseCreateSink "$sinkName") || sinkModule="" [ -n "$sinkModule" ] || { echo "$label: could not create a null sink to record from" >&2; exit 2; } wineStart "$work" PULSE_SINK="$sinkName" wineRun "$program" win=$(wineWindow AUDIO) || { echo "$label: FAIL - no AUDIO window" >&2; exit 1; } xdotool windowfocus --sync "$win" # A short record latency: the first sound must reach the file when it is # played, as the clock starts there. Left to choose, the server can hand the # recording over in seconds-long fragments, and every key lands seconds late # on the timeline. parec --latency-msec="$RECORD_LATENCY_MS" --device="$sinkName.monitor" --format=s16le --rate=44100 --channels=2 \ > "$work/out.raw" 2> "$work/parec.log" & parecPid=$! # The byte offset of the first sound in the recording so far, or nothing. firstSound() { "$grader" onset "$work/out.raw" "$SOUND_THRESHOLD" } SOUND_THRESHOLD=256 SOUND_WAIT_SECONDS=30 start="" for ((waited = 0; waited < SOUND_WAIT_SECONDS * 4; waited++)); do start=$(firstSound) [ -n "$start" ] && break sleep 0.25 done [ -n "$start" ] || { echo "$label: FAIL - no sound within ${SOUND_WAIT_SECONDS}s" >&2; exit 1; } hold() { # $1 = key name xdotool keydown "$1" sleep 0.4 xdotool keyup "$1" } # The same timeline as verify-sdl3-audio.sh (seconds since the first sound). 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 kill "$parecPid" 2> /dev/null wait "$parecPid" 2> /dev/null parecPid="" # From the first sound on, as a WAV. "$grader" wav "$work/out.raw" "$work/out.wav" 44100 "$start" # Thresholds sit at about half the measured levels (music ~6150, paused 0, # sfx ~310, resumed ~6040, noise ~3160, mod ~3980, measured after the # 2026-10-16 mix; Wine's runs spread, so a little under half of the low # ends, the grader averaging the two channels). "$grader" phases "$label" "$work/out.wav" \ "music:1:5:3000:" \ "paused:8:13::100" \ "sfx:15:22:150:" \ "resumed:25:29:2800:" \ "noise:31.5:34:1200:" \ "mod:37:43:1850:" status=$? if [ -n "${WIN32_AUDIO_KEEP:-}" ]; then cp "$work/out.wav" "$repo/build/win32/headless-audio.wav" fi exit $status