joeylib2/scripts/verify-audio-conformance.sh

558 lines
24 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-audio-conformance.sh - Audio conformance gate: every port must
# sound alike. Runs AUDPAR (examples/audpar, a fixed timeline of tones,
# voice and noise steps, sound effects, a module's end and tick rates,
# logged as it plays) on one port, records what the port played, and grades
# the recording against the audio contract with
# tests/gate/audioGate.c: pitch, 2 dB attenuation steps, noise rate,
# effect rates and pans, the module ending and saying so, tick counts, and
# silence where there should be silence.
#
# How each port is recorded:
# sdl3 SDL's disk audio driver, the mix exactly as it leaves the program
# dos DOSBox through a PulseAudio null sink of its own (parec)
# amiga FS-UAE (a bare A500) through the same
# atarist Hatari in real time through the same; ste on an STe
# x68000 MAME's -wavwrite, unthrottled, in the machine's own time
# iigs the same, AUDPAR opened from the Finder by keys
# dvx DVX in DOSBox Staging through a null sink, as dos
# win16 Windows 3.11 in DOSBox through the same
# win32 Wine through the same
# web headless Chromium: a copy of every buffer the page plays
# psp PPSSPPHeadless's --dump-audio, unthrottled, in the machine's
# own time
# haiku QEMU's wav audio driver: the VM's HD Audio card in the
# machine's own time; haiku32 the same
# beos none: BeOS R5's VM has no sound card, and with QEMU's ES1370
# or AC97 (R5's es137x and i801 drivers) R5's media server
# hangs, AUDPAR with it
#
# PORT_STEREO lists the ports with stereo output, whose pans are graded.
# DOS is stereo on DOSBox's own Sound Blaster, an SB16; AUDPAR_DOS_SBTYPE
# runs it on another (sbpro2, sb2: the mono cards), graded in mono. Not
# sbpro1: DOSBox makes its two OPL2s the halves of one OPL3, whose rhythm
# mode is in the first half only, so its noise plays on the left alone.
# Win16 mixes in mono; beos is listed for the Be backend it shares with
# Haiku, whose stereo is graded there. The IIgs is graded in mono, as a
# stock IIgs plays (its output sums every DOC channel): its stereo is a
# card's, routing even DOC channels right and odd ones left, which the HAL
# follows, and MAME 0.264's ES5503 does not record it so (a sound on
# channel 0 alone comes out on the left, and one on both is 9.6 dB louder
# on the left).
# Checks a port's hardware cannot pass are graded SKIP, by name: the
# Amiga's tone and voices each play on one Paula channel, which feeds one
# side only, so none of them can be centred (its centred sound effects
# take a channel each side and are graded).
# A port whose chip cannot land every 2 dB step gets a level tolerance of
# its own: the YM2149's sixteen levels are about 3.5 dB apart, so
# the nearest one can be 1.75 dB off, and on the STe, whose voices start at
# level 14 (the mix's level for them), 1.95: level 14 to 13 is 3.9 dB.
#
# Usage: scripts/verify-audio-conformance.sh PORT
# Env: AUDPAR_KEEP set to keep the recording and the log in
# build/<port>/audpar/
# AUDPAR_DOS_SBTYPE DOSBox's sbtype for the DOS run (default its
# own, sb16)
# Exit: 0 PASS, 1 FAIL, 2 cannot run
set -uo pipefail
repo=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)
source "$repo/scripts/lib/joeyEnv.sh"
port=${1:-}
label="verify-audio-conformance ($port)"
grader=$(joeyTool audioGate) || exit 1
PORT_STEREO="amiga dos dvx ste sdl3 web win32 psp haiku haiku32 beos"
DOS_SBTYPE=${AUDPAR_DOS_SBTYPE:-}
if [ -n "$DOS_SBTYPE" ] && [ "$DOS_SBTYPE" != sb16 ]; then
PORT_STEREO=${PORT_STEREO/ dos / }
label="verify-audio-conformance ($port, $DOS_SBTYPE)"
fi
RUN_SECONDS=120
X68K_RUN_SECONDS=110 # emulated: Human68k's boot and AUDPAR's run
IIGS_RUN_FRAMES=7800 # emulated, 60 a second: GS/OS, the Finder and AUDPAR
HAIKU_BOOT_SECONDS=120 # a Haiku VM booting until its job runner starts
RECORD_RATE=44100
QEMU_WAV_HEADER=44 # bytes: QEMU's wav audio driver's header, its sizes left 0
# Where Haiku's debug server reads what to do with a program that crashes.
HAIKU_DEBUG_SETTINGS=/boot/home/config/settings/system/debug_server
work=$(mktemp -d -t joeylib-audpar.XXXXXX)
pids=()
sinkModule=""
# Set by a run function when the program went wrong where the grader does
# not look (a crash as it ended): the recording is still graded, then the
# gate fails.
runFault=""
cleanup() {
local pid
for pid in "${pids[@]}"; do
kill "$pid" 2>/dev/null
wait "$pid" 2>/dev/null
done
# The recorder too, when a run failed before pulseStop.
if [ -n "${parecPid:-}" ]; then
pulseStop
fi
if [ -n "$sinkModule" ]; then
pulseUnloadSink "$sinkModule"
fi
# The web run's own sink (webSoundSink), should it have failed early.
if [ -n "${WEB_SINK_MODULE:-}" ]; then
pulseUnloadSink "$WEB_SINK_MODULE"
fi
if [ -n "${AUDPAR_KEEP:-}" ] && [ -n "$port" ]; then
mkdir -p "$repo/build/$port/audpar"
cp "$work"/out.wav "$work"/log.txt "$repo/build/$port/audpar/" 2>/dev/null
echo "$label: kept the recording and the log in build/$port/audpar" >&2
fi
rm -rf "$work"
return 0
}
trap cleanup EXIT
fail() {
echo "$label: FAIL - $1"
exit 1
}
cannot() {
echo "$label: cannot run - $1" >&2
exit 2
}
# Raw signed 16-bit stereo at RECORD_RATE -> out.wav.
rawToWav() {
"$grader" wav "$1" "$work/out.wav" "$RECORD_RATE"
}
xvfbStart() {
local display=$1
Xvfb "$display" -screen 0 1024x768x24 -nolisten tcp >/dev/null 2>&1 &
pids+=($!)
sleep 1
}
pulseStart() {
# shellcheck source=scripts/pulseRecordLib.sh
. "$repo/scripts/pulseRecordLib.sh"
pulseEnsureServer || cannot "no PulseAudio/PipeWire server to record from"
sinkName="joeyAudpar$$"
sinkModule=$(pulseCreateSink "$sinkName") || sinkModule=""
[ -n "$sinkModule" ] || cannot "could not create a null sink to record from"
# A stream of silence keeps the sink running: an emulator that stops
# sending sound while it is quiet (DOSBox does) would otherwise leave
# gaps the recording closes up, and the timeline would shrink.
pacat --playback --raw --device="$sinkName" --format=s16le --rate="$RECORD_RATE" --channels=2 < /dev/zero > /dev/null 2>&1 &
pids+=($!)
# A short record latency: at the default, the last 1.8 s still in
# parec's buffer were lost when it was stopped (measured).
parec --latency-msec=20 --device="$sinkName.monitor" --format=s16le --rate="$RECORD_RATE" --channels=2 \
--file-format=wav "$work/out.wav" > "$work/parec.log" 2>&1 &
parecPid=$!
}
# parec writes the WAV header lazily: it must exit before the file parses.
pulseStop() {
kill "$parecPid" 2>/dev/null
wait "$parecPid" 2>/dev/null
parecPid=""
}
# Wait for PID to end by itself, up to RUN_SECONDS.
waitFor() {
local pid=$1
local waited=0
while kill -0 "$pid" 2>/dev/null; do
if [ "$waited" -ge "$RUN_SECONDS" ]; then
fail "AUDPAR did not finish in $RUN_SECONDS s"
fi
sleep 1
waited=$((waited + 1))
done
}
runSdl3() {
local dir=$repo/build/sdl3/probe
local display=":$((90 + RANDOM % 9))"
make -s -C "$repo" -f make/sdl3.mk program-AUDPAR > "$work/build.log" 2>&1 || fail "the build failed (make -f make/sdl3.mk program-AUDPAR)"
rm -f "$dir/joeylog.txt"
xvfbStart "$display"
(cd "$dir" && 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="$RECORD_RATE" \
timeout "$RUN_SECONDS" ./AUDPAR > "$work/run.log" 2>&1)
rawToWav "$work/out.raw"
cp "$dir/joeylog.txt" "$work/log.txt" 2>/dev/null
}
runDos() {
local dir=$repo/build/dos/probe
local display=":$((90 + RANDOM % 9))"
local dosPid
joeyNeed DOSBOX dosbox
make -s -C "$repo" -f make/dos.mk program-AUDPAR > "$work/build.log" 2>&1 || fail "the build failed (make -f make/dos.mk program-AUDPAR)"
rm -f "$dir/JOEYLOG.TXT"
xvfbStart "$display"
pulseStart
XDG_CONFIG_HOME="$work/xdg" DISPLAY="$display" PULSE_SINK="$sinkName" SDL_AUDIODRIVER=pulse \
"$DOSBOX" "${JOEY_DOSBOX_ISOLATE[@]}" --conf "$repo/scripts/dosbox-386sx16.conf" ${DOS_SBTYPE:+--set "sbtype=$DOS_SBTYPE"} \
-c "MOUNT C $dir" -c "C:" -c "AUDPAR.EXE" -c "EXIT" > "$work/dosbox.log" 2>&1 &
dosPid=$!
pids+=("$dosPid")
waitFor "$dosPid"
pulseStop
cp "$dir/JOEYLOG.TXT" "$work/log.txt" 2>/dev/null
}
# Wait until one of the LOG files holds AUDPAR's "done", up to RUN_SECONDS:
# for emulators that keep running after the program ends. Prints the log.
# Its callers take that through $(...), so a failure is reported on stderr
# (a FAIL on stdout would be captured with the log name) and returns 1.
waitDone() {
local pid=$1
shift
local waited=0
local log
while true; do
for log in "$@"; do
if grep -qs 'AUDPAR [0-9]* done' "$log"; then
sleep 1
echo "$log"
return 0
fi
done
if [ "$waited" -ge "$RUN_SECONDS" ]; then
fail "AUDPAR did not finish in $RUN_SECONDS s" >&2
fi
kill -0 "$pid" 2>/dev/null || fail "the emulator exited before AUDPAR finished" >&2
sleep 1
waited=$((waited + 1))
done
}
runAmiga() {
local dir=$repo/build/amiga/probe
local display=":$((90 + RANDOM % 9))"
local fsPid
local log
joeyNeed FSUAE fsuae
source "$repo/scripts/lib/amigaGate.sh"
amigaMachine
make -s -C "$repo" -f make/amiga.mk program-AUDPAR > "$work/build.log" 2>&1 || fail "the build failed (make -f make/amiga.mk program-AUDPAR)"
mkdir -p "$work/hd/s"
cp -r "$dir/Audpar" "$dir/DATA" "$work/hd/"
printf 'stack 32768\n:Audpar\n' > "$work/hd/s/startup-sequence"
xvfbStart "$display"
pulseStart
# A bare A500, the port's floor, as the Amiga audio gate runs it.
DISPLAY="$display" PULSE_SINK="$sinkName" ALSOFT_DRIVERS=pulse SDL_AUDIODRIVER=pulse \
"$FSUAE" "${AMIGA_MACHINE_ARGS[@]}" --hard_drive_0="$work/hd" --hard_drive_0_label=JOEYLIB \
--fullscreen=1 --initial_input_grab=1 --fade_out_duration=0 --fast_memory=0 --slow_memory=0 --stereo_separation=100 --floppy_drive_volume=0 \
> "$work/fsuae.log" 2>&1 &
fsPid=$!
pids+=("$fsPid")
log=$(waitDone "$fsPid" "$work/hd/joeylog.txt") || exit 1
pulseStop
cp "$log" "$work/log.txt"
}
# The ST (atarist) or the STe (ste) build on its machine, under Hatari in
# real time (not fast-forwarded: the recording is its sound output).
runAtari() {
local build=$1
local machine=st
local dir=$repo/build/$1/probe
local hatariPid
local log
joeyNeed HATARI hatari
joeyNeed EMUTOS_IMG emutos
[ "$build" = ste ] && machine=ste
make -s -C "$repo" -f "make/$build.mk" program-AUDPAR > "$work/build.log" 2>&1 || fail "the build failed (make -f make/$build.mk program-AUDPAR)"
rm -f "$dir/joeylog.txt" "$dir/JOEYLOG.TXT"
pulseStart
SDL_VIDEODRIVER=dummy PULSE_SINK="$sinkName" SDL_AUDIODRIVER=pulse "$HATARI" \
--machine "$machine" --tos "$EMUTOS_IMG" --harddrive "$dir" --gemdos-drive C \
--drive-a off --drive-b off --sound "$RECORD_RATE" --memsize 4 \
--auto "C:\\AUDPAR.PRG" > "$work/hatari.log" 2>&1 &
hatariPid=$!
pids+=("$hatariPid")
# GEMDOS writes the log in whichever case TOS gave the name.
log=$(waitDone "$hatariPid" "$dir/joeylog.txt" "$dir/JOEYLOG.TXT") || exit 1
pulseStop
cp "$log" "$work/log.txt"
# Hatari ignores SIGTERM.
kill -KILL "$hatariPid" 2>/dev/null
}
# A Lua script that ends MAME at emulated second $1.
mameStopAt() {
cat > "$work/stop.lua" <<LUA
emu.register_frame_done(function()
if manager.machine.time:as_double() >= $1 then
manager.machine:exit()
end
end)
LUA
}
# Under MAME, unthrottled: -wavwrite records the machine's sound in its own
# time, so the recording and the program's clock agree exactly. The probe
# and its log ride the second floppy (B:), as the X68000 golden gate does.
runX68000() {
local bin=$repo/build/x68000/probe/AUDPAR.X
joeyNeed MAME mame
joeyNeed X68K_DIR x68kFirmware
source "$repo/scripts/lib/x68kDisk.sh"
make -s -C "$repo" -f make/x68000.mk program-AUDPAR > "$work/build.log" 2>&1 || fail "the build failed (make -f make/x68000.mk program-AUDPAR)"
x68kBootDisk "$work/boot.xdf" 'B:' 'B:\AUDPAR.X' || fail "could not make the boot disk"
x68kDataDisk "$work/data.xdf" || fail "could not make the data disk"
x68kXdf add "$work/data.xdf" "$bin" AUDPAR.X > /dev/null || fail "could not put AUDPAR on the data disk"
x68kAddTree "$work/data.xdf" "$(dirname "$bin")/DATA" DATA || fail "could not put DATA on the data disk"
mameStopAt "$X68K_RUN_SECONDS"
(cd "$work" && SDL_AUDIODRIVER=dummy timeout -s KILL 900 "$MAME" x68000 -bios ipl10 -rompath "$X68K_DIR/roms" \
-flop1 boot.xdf -flop2 data.xdf -video none -sound none -nothrottle -skip_gameinfo \
-samplerate "$RECORD_RATE" -wavwrite out.wav -autoboot_script stop.lua < /dev/null > mame.log 2>&1)
x68kXdf extract "$work/data.xdf" joeylog.txt "$work/log.txt" > /dev/null 2>&1 || fail "AUDPAR left no joeylog.txt on the disk"
}
# GS/OS boots from the 3.5 drive and the Finder opens AUDPAR from the
# JOEYLIB disk by keys (J, Open Apple-O, its name, Open Apple-O), as the IIgs
# audio gate opens its program; -wavwrite records, unthrottled. The log comes
# off the disk with cadius.
runIigs() {
joeyNeed MAME mame
joeyNeed GSOS_SYSTEM_DISK iigsFirmware
joeyNeed CADIUS cadius
make -s -C "$repo" -f make/iigs.mk program-AUDPAR > "$work/build.log" 2>&1 || fail "the build failed (make -f make/iigs.mk program-AUDPAR)"
JOEY_DISK_EXAMPLES=AUDPAR BINDIR="$repo/build/iigs/probe/bin" JOEY_DISK_DATA="$repo/build/iigs/data" \
"$repo/scripts/make-iigs-disk.sh" "$work/joey.2mg" > "$work/disk.log" 2>&1 || fail "could not make the JOEYLIB disk"
cp "$GSOS_SYSTEM_DISK" "$work/boot.po"
cat > "$work/run.lua" <<LUA
local nat = manager.machine.natkeyboard
local frame = 0
local idx = 1
local function field(port, name)
local p = manager.machine.ioport.ports[port]
if p == nil then return nil end
return p.fields[name]
end
local cmd = field(":macadb:KEY3", "Command / Open Apple")
local function press(f) if f then f:set_value(1) end end
local function release(f) if f then f:set_value(0) end end
local steps = {
{3000, function() nat:post("J") end},
{3120, function() press(cmd) end},
{3126, function() nat:post("o") end},
{3180, function() release(cmd) end},
{3540, function() nat:post("AUDPAR") end},
{3660, function() press(cmd) end},
{3666, function() nat:post("o") end},
{3720, function() release(cmd) end},
{$IIGS_RUN_FRAMES, function() manager.machine:exit() end},
}
emu.register_frame_done(function()
frame = frame + 1
while idx <= #steps and frame >= steps[idx][1] do
steps[idx][2]()
idx = idx + 1
end
end)
LUA
(cd "$work" && QT_QPA_PLATFORM=offscreen SDL_VIDEODRIVER=dummy SDL_AUDIODRIVER=dummy timeout -s KILL 900 "$MAME" apple2gs \
-rompath "$IIGS_ROMPATH" -flop3 boot.po -flop4 joey.2mg -video none -sound none -nothrottle \
-samplerate "$RECORD_RATE" -wavwrite out.wav -autoboot_script run.lua < /dev/null > mame.log 2>&1)
"$CADIUS" EXTRACTFILE "$work/joey.2mg" /JOEYLIB/JOEYLOG.TXT "$work/" > /dev/null 2>&1
tr -d '\r' < "$(find "$work" -maxdepth 1 -iname 'joeylog.txt*' | head -1)" > "$work/log.txt" 2>/dev/null || fail "AUDPAR left no JOEYLOG.TXT on the disk"
}
# A scratch DVX in DOSBox Staging with a Sound Blaster 16, AUDPAR its
# desktop (scripts/lib/dvxGate.sh), recorded as the DVX audio gate records
# it. DVX stays up when AUDPAR ends; the log is in AUDPAR's settings folder.
runDvx() {
local display=":$((90 + RANDOM % 9))"
local dosPid
local log
joeyNeed DOSBOX dosbox
joeyNeed DVX_ROOT dvx
source "$repo/scripts/lib/dvxGate.sh"
make -s -C "$repo" -f make/dvx.mk program-AUDPAR > "$work/build.log" 2>&1 || fail "the build failed (make -f make/dvx.mk program-AUDPAR)"
dvxStage "$work/c" "$repo/build/dvx/probe/apps/$DVX_VENDOR/audpar"
dvxDosboxConf "$work/dosbox.conf" "$work/c" sb16
xvfbStart "$display"
pulseStart
XDG_CONFIG_HOME="$work/xdg" DISPLAY="$display" PULSE_SINK="$sinkName" SDL_AUDIODRIVER=pulse \
"$DOSBOX" "${JOEY_DOSBOX_ISOLATE[@]}" --conf "$work/dosbox.conf" > "$work/dosbox.log" 2>&1 &
dosPid=$!
pids+=("$dosPid")
log=$(dvxAwaitLog "$work/c" audpar 'AUDPAR [0-9]* done' "$RUN_SECONDS" "$dosPid") || fail "AUDPAR did not finish in $RUN_SECONDS s"
sleep 1
pulseStop
cp "$log" "$work/log.txt"
joeyStop "$dosPid"
}
# Windows 3.11 in DOSBox with Windows' own Sound Blaster driver, AUDPAR its
# shell (scripts/lib/win16Gate.sh), recorded as the Win16 audio gate records
# it. Windows, and DOSBox with it, ends when AUDPAR does; the log is beside
# AUDPAR, on drive D:.
runWin16() {
local dir=$repo/build/win16/probe
source "$repo/scripts/lib/win16Gate.sh"
win16Need
make -s -C "$repo" -f make/win16.mk program-AUDPAR > "$work/build.log" 2>&1 || fail "the build failed (make -f make/win16.mk program-AUDPAR)"
rm -f "$dir/JOEYLOG.TXT" "$dir/joeylog.txt"
win16Stage "$work" "$dir" AUDPAR.EXE
pulseStart
WIN16_SOUND=1 PULSE_SINK="$sinkName" SDL_AUDIODRIVER=pulse win16Start "$work"
pids+=("$WIN16_PID" "$WIN16_GUARD" "$WIN16_XVFB_PID")
waitFor "$WIN16_GUARD"
pulseStop
win16Stop
cp "$(win16Log "$dir")" "$work/log.txt" 2>/dev/null
}
# Wine with a prefix and a display of its own (scripts/lib/wineGate.sh),
# waveOut through Wine's PulseAudio driver, recorded as the Win32 audio gate
# records it. Wine ends when AUDPAR does; the log is beside AUDPAR.
runWin32() {
local dir=$repo/build/win32/probe
source "$repo/scripts/lib/wineGate.sh"
wineNeed
make -s -C "$repo" -f make/win32.mk program-AUDPAR > "$work/build.log" 2>&1 || fail "the build failed (make -f make/win32.mk program-AUDPAR)"
rm -f "$dir/joeylog.txt"
wineStart "$work"
pids+=("$WINE_XVFB_PID")
pulseStart
PULSE_SINK="$sinkName" wineRun "$dir/AUDPAR.EXE"
pids+=("$WINE_PID")
waitFor "$WINE_PID"
pulseStop
wineStop
cp "$dir/joeylog.txt" "$work/log.txt" 2>/dev/null
}
# Headless Chromium (scripts/lib/webGate.sh), whose driver keeps a copy of
# every buffer the page plays, as the web audio gate records it, until the
# log in the page's own file system says AUDPAR is done.
runWeb() {
source "$repo/scripts/lib/webGate.sh"
webNeed
make -s -C "$repo" -f make/web.mk program-AUDPAR > "$work/build.log" 2>&1 || fail "the build failed (make -f make/web.mk program-AUDPAR)"
webServe "$repo/build/web/probe/AUDPAR"
pids+=("$WEB_SERVER_PID")
# Chromium plays to a null sink of its own, whose clock paces the page's
# sound: muted, its audio runs on a fake clock that drifts off real time
# (the timeline stretched by up to 15%). The page's own buffers are what
# is recorded.
webSoundSink || cannot "no PulseAudio/PipeWire null sink for Chromium to play to"
webDrive AUDPAR.html --seconds "$RUN_SECONDS" --until " done" --audio "$work/out.wav" --log "$work/log.txt" > "$work/web.log" 2>&1 || fail "AUDPAR did not finish in $RUN_SECONDS s ($(tail -1 "$work/web.log"))"
webStop
}
# PPSSPPHeadless (scripts/lib/pspGate.sh) and its --dump-audio, PPSSPP's own
# recording on the emulated clock, as the PSP audio gate records it, with
# AUDPAR run from its folder on a private memory stick. The run ends when
# AUDPAR does; the log is in that folder.
runPsp() {
local dir=$repo/build/psp/probe/AUDPAR
local folder
source "$repo/scripts/lib/pspGate.sh"
pspNeed
make -s -C "$repo" -f make/psp.mk program-AUDPAR > "$work/build.log" 2>&1 || fail "the build failed (make -f make/psp.mk program-AUDPAR)"
folder=$(pspStage "$work" "$dir")
rm -f "$folder/joeylog.txt"
pspRun "$work" "$folder" "$RUN_SECONDS" --dump-audio
pids+=("$PSP_PID")
pspWait
pspStrictCheck "$work" "$dir" || fail "AUDPAR stopped at a bad memory access"
if grep -q '^TIMEOUT' "$work/ppsspp.log"; then
fail "AUDPAR did not finish in $RUN_SECONDS s"
fi
cp "$(find "$work/.ppsspp/PSP/AUDIO" -name '*.wav' -print -quit 2>/dev/null)" "$work/out.wav" 2>/dev/null
cp "$folder/joeylog.txt" "$work/log.txt" 2>/dev/null
}
# A Haiku port (haiku, haiku32) in its VM under QEMU (scripts/lib/beGate.sh),
# AUDPAR run by the job runner from the job disk. The VM's HD Audio card
# plays to QEMU's wav audio driver, which records it in the machine's own
# time from when the card first plays, but never fills in its header's
# sizes: the samples are taken as they are. Haiku's debug server is told to
# kill a program that crashes rather than ask what to do (its alert would
# hold the job open); a crash still fails the gate. The machine powers off
# when the job ends; the log comes back on the job disk.
runHaiku() {
local dir=$repo/build/$1/probe
local exitStatus
source "$repo/scripts/lib/beGate.sh"
beNeed "$1"
make -s -C "$repo" -f "make/$1.mk" program-AUDPAR > "$work/build.log" 2>&1 || fail "the build failed (make -f make/$1.mk program-AUDPAR)"
mkdir -p "$work/job/AUDPAR"
cp -r "$dir/AUDPAR" "$dir/DATA" "$work/job/AUDPAR/"
{
printf 'mkdir -p %s\n' "$HAIKU_DEBUG_SETTINGS"
printf 'echo "default_action kill" > %s/settings\n' "$HAIKU_DEBUG_SETTINGS"
beJobScript AUDPAR AUDPAR "" "$RUN_SECONDS"
printf 'echo "AUDPAR exit status $?"\n'
} > "$work/job/job.sh"
beJobDisk "$work/job.img" "$work/job" || fail "could not make the job disk"
BE_AUDIODEV="wav,id=beSound,path=$work/qemu.wav,out.frequency=$RECORD_RATE,out.channels=2,out.format=s16" beStart "$work" "$work/job.img"
pids+=("$BE_PID")
beWaitJob "$work" "$HAIKU_BOOT_SECONDS" || fail "the VM did not start AUDPAR in $HAIKU_BOOT_SECONDS s"
beWaitExit "$work" "$RUN_SECONDS" || fail "AUDPAR did not finish in $RUN_SECONDS s"
tail -c +$((QEMU_WAV_HEADER + 1)) "$work/qemu.wav" > "$work/out.raw" 2>/dev/null
if [ -s "$work/out.raw" ]; then
rawToWav "$work/out.raw"
fi
beJobGet "$work/job.img" AUDPAR/joeylog.txt "$work/log.txt"
beJobGet "$work/job.img" job.log "$work/job.log"
exitStatus=$(sed -n 's/^AUDPAR exit status //p' "$work/job.log" 2>/dev/null)
if [ "$exitStatus" != 0 ]; then
runFault="AUDPAR did not exit cleanly: exit status ${exitStatus:-unknown} (149 is a crash the debug server killed)"
fi
}
case "$port" in
sdl3) runSdl3 ;;
iigs) runIigs ;;
x68000) runX68000 ;;
dos) runDos ;;
amiga) runAmiga ;;
atarist|ste) runAtari "$port" ;;
dvx) runDvx ;;
win16) runWin16 ;;
win32) runWin32 ;;
web) runWeb ;;
psp) runPsp ;;
haiku|haiku32) runHaiku "$port" ;;
beos) cannot "BeOS R5's VM has no sound card to record (with QEMU's ES1370 or AC97, R5's media server hangs)" ;;
*) cannot "no recorder for '$port' (sdl3, dos, amiga, atarist, ste, x68000, iigs, dvx, win16, win32, web, psp, haiku, haiku32)" ;;
esac
[ -s "$work/out.wav" ] || fail "nothing was recorded"
[ -s "$work/log.txt" ] || fail "AUDPAR left no log"
flags=()
case " $PORT_STEREO " in
*" $port "*) flags+=(--stereo) ;;
esac
case "$port" in
atarist) flags+=(--level-tolerance 1.75) ;;
ste) flags+=(--level-tolerance 1.95) ;;
amiga) flags+=(--skip "tone centred" --skip "voice 0 centred" --skip "voice 1 centred" --skip "voice 2 centred") ;;
esac
"$grader" conformance "$port" "$work/log.txt" "$work/out.wav" "${flags[@]}" > "$work/grade.txt"
status=$?
grep -v '^PASS' "$work/grade.txt"
summary=$(tail -1 "$work/grade.txt")
if [ -n "$runFault" ]; then
echo "$label: FAIL - $runFault ($summary)"
exit 1
fi
if [ "$status" -ne 0 ]; then
echo "$label: FAIL ($summary)"
exit 1
fi
echo "$label: PASS ($summary: every tone, step, effect, the module's end and the ticks as the contract says)"