#!/usr/bin/env bash # verify-iigs-audio.sh - Headless gate proving NTP is actually running on # the IIgs, modelled on verify-iigs.sh (same Finder launch timeline, same # "read emulated memory, not a snapshot" approach). # # Sound cannot be graded from a -sound none run, so this grades the two # pieces of state that make sound possible: # # irqVec $E1002C is the GS sound-interrupt vector. NTPprepare writes # $5C (JML) + the address of its interrupt_handler + its own # bank there (setup_interrupt), and NTPstop puts the previous # vector back. $5C plus a RAM bank therefore means "NTP's DOC # interrupt is installed". Before the ntpArm change nothing # ever called NTPprepare unless the app played a .NTP module, # so an SFX-only app left this at the system value and every # streamed sample reached the DOC exactly once, unrefilled. # # docVol $E100CA is the Sound Tool Set's copy of the sound control # register, and its LOW NIBBLE is the DOC master volume. NTP # copies this byte into $C03C before every DOC access and # never writes it, so if nothing started the Sound Tool Set it # is whatever GS/OS left behind -- and a zero nibble means the # DOC streams perfectly while outputting SILENCE. Everything # below passed in exactly that state ("I don't hear any sound # other than the IIgs startup beep"), which is why this check # exists: streaming is necessary, not sufficient. # # slotN MAME exposes the Ensoniq 5503's 64KB wavetable RAM as the # ":doc" device's "rom" space. JoeyLib's SFX slots live at DOC # pages 0xC0, 0xC2, ... (SFX_BASE_DOC_PAGE / SFX_DOC_PAGE_STEP # in src/iigs/audio_full.c), and bytes only land there when # NTPstreamsound runs. Every slot is hashed twice, frames # apart. Which slots an application uses is its own business # -- AUDIO's tracker takes 0-2, Space Taxi only ever reaches # the noise slot 3 -- so the gate asks that SOME slot carry a # waveform and that SOME slot's hash move between the probes. # A moving hash is the live-interrupt evidence: NTP streams # the sample through the page in 256-byte halves, so a dead # interrupt leaves whatever the one initial copy wrote. # # Usage: scripts/verify-iigs-audio.sh [example] # example lowercase name, default "audio". A single-example # disk is built for it (joey.2mg cannot hold them all). # Requires: toolchains/env.sh sourced; the example built under build/iigs/bin. set -euo pipefail repo=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd) example=${1:-audio} NAME=${example^^} sys_disk=$repo/toolchains/emulators/support/gsos-system.po rompath="${MAME_ROMPATH:-$HOME/.mame/roms}" probeA="${MAME_PROBE_A:-5400}" probeB="${MAME_PROBE_B:-9000}" # Mirrors JOEY_AUDIO_SFX_SLOTS / SFX_BASE_DOC_PAGE / SFX_DOC_PAGE_STEP # in src/iigs/audio_full.c. slots=5 basePage=0xC0 pageStep=2 [ -f "$sys_disk" ] || { echo "verify-iigs-audio: missing $sys_disk" >&2; exit 2; } [ -f "$repo/build/iigs/bin/$NAME" ] || { echo "verify-iigs-audio: missing build/iigs/bin/$NAME (run 'make iigs-disk')" >&2; exit 2; } work=$(mktemp -d -t joeylib-audio.XXXXXX) trap 'rm -rf "$work"' EXIT # One-example volume: the 800KB floppy cannot hold every example, and the # shared joey.2mg drops AUDIO and STAXI first (see make-iigs-disk.sh). JOEY_DISK_EXAMPLES="$NAME" BINDIR="$repo/build/iigs/bin" \ NTP_BIN="$repo/build/iigs/audio/ntpplayer.bin" \ "$repo/scripts/make-iigs-disk.sh" "$work/joey.2mg" >/dev/null cp "$sys_disk" "$work/boot.po" cat > "$work/audio.lua" <= steps[idx][1] do steps[idx][2]() idx = idx + 1 end end) LUA cd "$work" out=$(QT_QPA_PLATFORM=offscreen SDL_VIDEODRIVER=dummy SDL_AUDIODRIVER=dummy \ timeout 400 mame apple2gs \ -rompath "$rompath" \ -flop3 "$work/boot.po" -flop4 "$work/joey.2mg" \ -video none -sound none -nothrottle \ -autoboot_script "$work/audio.lua" &1) || true lineA=$(echo "$out" | grep -E '^VERIFY-AUDIO .* tag=A ' | tail -1) lineB=$(echo "$out" | grep -E '^VERIFY-AUDIO .* tag=B ' | tail -1) echo "$lineA" echo "$lineB" if [ -z "$lineA" ] || [ -z "$lineB" ]; then echo "verify-iigs-audio: FAIL - no probe report (boot/launch failed)" >&2 echo "$out" | tail -15 >&2 exit 1 fi field() { echo "$2" | sed -E "s/.*$1=([0-9A-Fa-f]+).*/\1/"; } # Launch gate first, exactly as verify-iigs.sh: the Finder desktop alone # would otherwise be graded as if it were the example. scb0=$(field scb0 "$lineB") if [ $((16#$scb0)) -ge 16 ]; then echo "verify-iigs-audio: FAIL ($NAME never launched - SHR SCB[0]=0x$scb0 is the Finder's 640-mode fill)" >&2 exit 1 fi vec=$(field irqVec "$lineB") if [ "${vec:6:2}" != "5C" ]; then echo "verify-iigs-audio: FAIL (sound interrupt vector is $vec, not a JML - NTPprepare never ran, so there is no interrupt to refill a streamed sample)" >&2 exit 1 fi # The vector the ROM leaves in place is a JML too ($5C 62 CD FC on this # machine), so "is it a JML" is not enough -- the control run with the # pre-ntpArm audio_full.c passed that test. NTP lives in a Memory # Manager handle, always in RAM, so a ROM bank ($F0 and up) means the # system vector is still installed and NTPprepare never ran. bank=${vec:0:2} if [ $((16#$bank)) -ge 240 ]; then echo "verify-iigs-audio: FAIL (sound interrupt vector $vec still points into ROM bank \$$bank - NTPprepare never ran)" >&2 exit 1 fi # Audible-output precondition: the DOC master volume must not be zero. docVol=$(field docVol "$lineB") if [ $(( 16#$docVol & 0x0F )) -eq 0 ]; then echo "verify-iigs-audio: FAIL (DOC master volume is 0 -- \$E100CA=0x$docVol. The Sound Tool Set was never started, so NTP copies a zero volume into \$C03C: the DOC streams but nothing is audible)" >&2 exit 1 fi if [ "$(field slot0NZ "$lineA")" = "-1" ]; then echo "verify-iigs-audio: FAIL (MAME exposed no :doc/rom space - the DOC RAM probe read nothing)" >&2 exit 1 fi loud="" moved="" slot=0 while [ "$slot" -lt "$slots" ]; do nz=$(field "slot${slot}NZ" "$lineB") if [ "$nz" -gt 0 ]; then loud="$loud $slot" fi if [ "$(field "slot$slot" "$lineA")" != "$(field "slot$slot" "$lineB")" ]; then moved="$moved $slot" fi slot=$((slot + 1)) done if [ -z "$loud" ]; then echo "verify-iigs-audio: FAIL (no SFX slot's DOC page holds a waveform - NTPstreamsound never reached the DOC)" >&2 exit 1 fi # A live NTP interrupt keeps rewriting the DOC page it streams through. # A slot parked on the resident tone square (toneSquareEnsure) hashes # the same every time -- every refill writes the same square -- so this # only warns when NO slot moved at all. if [ -z "$moved" ]; then echo "verify-iigs-audio: WARN (no DOC slot changed between frames $probeA and $probeB - samples are present but nothing is refilling them)" fi echo "verify-iigs-audio: PASS ($NAME: NTP interrupt installed at bank $bank; DOC slots with a waveform:$loud; refilled between probes:$moved)"