545 lines
23 KiB
Bash
Executable file
545 lines
23 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
|
|
# tools/gradeAudioConformance.py: 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.
|
|
# 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.
|
|
#
|
|
# Usage: scripts/verify-audio-conformance.sh PORT
|
|
# Env: AUDPAR_KEEP set to keep the recording and the log in
|
|
# build/<port>/audpar/
|
|
# 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=$repo/tools/gradeAudioConformance.py
|
|
PORT_STEREO="amiga dvx sdl3 web win32 psp haiku haiku32 beos"
|
|
RUN_SECONDS=120
|
|
X68K_RUN_SECONDS=110 # emulated: Human68k's boot and AUDPAR's run
|
|
IIGS_RUN_FRAMES=7200 # 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
|
|
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() {
|
|
python3 - "$1" "$work/out.wav" "$RECORD_RATE" <<'PY'
|
|
import sys
|
|
import wave
|
|
raw = open(sys.argv[1], 'rb').read()
|
|
with wave.open(sys.argv[2], 'wb') as out:
|
|
out.setnchannels(2)
|
|
out.setsampwidth(2)
|
|
out.setframerate(int(sys.argv[3]))
|
|
out.writeframes(raw[:len(raw) // 4 * 4])
|
|
PY
|
|
}
|
|
|
|
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" \
|
|
-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")
|
|
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|ste) flags+=(--level-tolerance 1.75) ;;
|
|
amiga) flags+=(--skip "tone centred" "voice 0 centred" "voice 1 centred" "voice 2 centred") ;;
|
|
esac
|
|
python3 "$grader" "$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)"
|