Files
MicroslopSam/setup/test-wrapper.sh
T
Fisto c6b5fb71e8 Revert VOX preset to the NetTTS approximation
The tuned values (pitch 0, rate 15, head size 34, lowering 50) matched
the reference metrics better but sound far off the game voice, so restore
the published NetTTS approximation: pitch 3, rate 17, prominences 22,
head size 52, lowering 40, volume 70. Also revert the test expectations,
the published default metadata, and the tuning notes in the docs; keep
tools/vox_compare.py for future experiments.
2026-09-22 19:56:22 +02:00

153 lines
7.0 KiB
Bash
Executable File

#!/bin/sh
# Regression test for the Veskslop TTS wrapper contract. No Wine needed: the wine
# and engine-host binaries are stubbed, so this checks the arguments the wrapper
# builds, the registry import it generates, and the voice metadata it publishes.
#
# The bug this guards against: rate/pitch sent as ITTSAttributes (-s/-p) override
# the Flextalk speaker profile, so the VOX/HEV presets sounded too fast and not
# deep enough. Flextalk must use the sticky vendor tags (\!R / \!%), and neutral
# sliders must emit neither.
set -eu
HERE=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
WRAP="$HERE/veskslop-tts.sh"
WORK=$(mktemp -d)
trap 'rm -rf "$WORK"' EXIT
mkdir -p "$WORK/build" "$WORK/bin" "$WORK/out"
"${MICROSLOP_FFMPEG:-ffmpeg}" -hide_banner -loglevel error -f lavfi \
-i "sine=frequency=200:duration=0.3" -ar 11025 -ac 1 -c:a pcm_s16le -y "$WORK/sine.wav"
cat > "$WORK/build/sapi4host.exe" <<'STUB'
#!/bin/sh
{ printf 'ARGV:'; for a in "$@"; do printf ' [%s]' "$a"; done; printf '\n'; } >> "$STUB_LOG"
OUT=""
while [ "$#" -ge 2 ]; do
[ "$1" = "-o" ] && OUT=$2
shift
done
cp "$STUB_SINE" "$OUT"
STUB
cat > "$WORK/bin/wine" <<'STUB'
#!/bin/sh
[ "${1:-}" = "reg" ] && [ "${2:-}" = "import" ] && cp "$3" "$STUB_REG"
exit 0
STUB
chmod +x "$WORK/build/sapi4host.exe" "$WORK/bin/wine"
printf 'Attention all personnel.\n' > "$WORK/text.txt"
export MICROSLOP_BUILD="$WORK/build"
export MICROSLOP_WINE="$WORK/bin/wine"
export MICROSLOP_FFMPEG="${MICROSLOP_FFMPEG:-ffmpeg}"
export DISPLAY=:99
export WINEPREFIX="$WORK/prefix"
export STUB_LOG="$WORK/host.log"
export STUB_REG="$WORK/profile.reg"
export STUB_SINE="$WORK/sine.wav"
FAIL=0
check() {
if [ "$2" = "$3" ]; then
echo "ok - $1"
else
echo "FAIL - $1: expected [$2], got [$3]"
FAIL=1
fi
}
run_wrapper() {
: > "$STUB_LOG"
sh "$WRAP" "$WORK/out.wav" "$WORK/text.txt" >/dev/null
}
# The spoken text is the -t argument; the engine host writes to the wrapper's temp file.
text_for() {
sed -n '1s/.*\[-t\] \[\(.*\)\] \[-o\].*/\1/p' "$STUB_LOG"
}
attr_count() {
grep -oE '\[-s\]|\[-p\]' "$STUB_LOG" | wc -l | tr -d ' '
}
# Neutral Flextalk sliders emit neither attributes nor tags.
VESKSLOP_TTS_VOICE=keith-bell run_wrapper
check "neutral flextalk text" "Attention all personnel." "$(text_for)"
check "neutral flextalk has no attributes" "0" "$(attr_count)"
# Adjustments ride as tags; attributes stay out even at non-neutral values.
VESKSLOP_TTS_VOICE=keith-bell VESKSLOP_TTS_VOX=0 \
VESKSLOP_TTS_SPEED=1.3 VESKSLOP_TTS_PITCH=3 run_wrapper
check "flextalk rate tag" "\!R0.77 \!%1.19 Attention all personnel." "$(text_for)"
check "flextalk has no attributes" "0" "$(attr_count)"
# MSTTS has no vendor tags and keeps the attributes.
VESKSLOP_TTS_VOICE=sam VESKSLOP_TTS_SPEED=1.3 VESKSLOP_TTS_PITCH=3 run_wrapper
check "mstts text has no tags" "Attention all personnel." "$(text_for)"
check "mstts keeps attributes" "2" "$(attr_count)"
# The vox preset implies VOX prosody when VESKSLOP_TTS_VOX is unset.
VESKSLOP_TTS_VOICE=keith-bell VESKSLOP_TTS_PRESET=vox run_wrapper
check "vox preset implies VOX parser" "1" "$(grep -c 'wH1' "$STUB_LOG")"
# The preset writes the full audible profile to both hives.
VESKSLOP_TTS_VOICE=keith-bell VESKSLOP_TTS_PRESET=vox run_wrapper
check "preset writes both hives" "2" "$(grep -c 'Speakers\\{11260610' "$STUB_REG")"
check "preset pitch (both hives)" "2" "$(grep -c '"Pitch"=dword:00000003' "$STUB_REG")"
check "preset rate (both hives)" "2" "$(grep -c '"Speaking Rate"=dword:00000011' "$STUB_REG")"
check "preset gender and line mode" "4" "$(grep -cE '"Gender"=dword:00000002|"Line Mode"=dword:00000001' "$STUB_REG")"
check "preset default speaker" "2" "$(grep -c '"Default Speaker"' "$STUB_REG")"
# A plain Flextalk run keeps the defaults and writes no preset extras.
VESKSLOP_TTS_VOICE=keith-bell run_wrapper
check "plain run writes both hives" "2" "$(grep -c 'Speakers\\{11260610' "$STUB_REG")"
check "plain run has no preset extras" "0" "$(grep -cE 'Gender|Line Mode|Default Speaker' "$STUB_REG")"
# Word mode combines with a preset and skips the VOX parser, with tags per word.
VESKSLOP_TTS_VOICE=keith-bell VESKSLOP_TTS_PRESET=vox VESKSLOP_TTS_VOX=1 \
VESKSLOP_TTS_WORDS=1 VESKSLOP_TTS_WORD_GAP_MS=0 VESKSLOP_TTS_SPEED=1.25 run_wrapper
check "word mode with preset synthesizes every word" "3" "$(grep -c '^ARGV:' "$STUB_LOG")"
check "word mode skips the VOX parser" "0" "$(grep -c 'wH1' "$STUB_LOG")"
check "word mode tags every word" "3" \
"$(sed -n 's/.*\[-t\] \[\(.*\)\] \[-o\].*/\1/p' "$STUB_LOG" | grep -c '^\\!R0.80 ')"
check "word mode keeps the preset profile" "2" "$(grep -c '"Pitch"=dword:00000003' "$STUB_REG")"
check "word mode emits no attributes" "0" "$(attr_count)"
# The vox preset implies VOX when the variable is unset, but word mode still wins.
VESKSLOP_TTS_VOICE=keith-bell VESKSLOP_TTS_PRESET=vox VESKSLOP_TTS_WORDS=1 \
VESKSLOP_TTS_WORD_GAP_MS=0 run_wrapper
check "implied VOX yields to word mode" "3" "$(grep -c '^ARGV:' "$STUB_LOG")"
check "implied VOX skipped for words" "0" "$(grep -c 'wH1' "$STUB_LOG")"
# Crunch keeps the output contract and changes the sound.
VESKSLOP_TTS_VOICE=keith-bell run_wrapper
cp "$WORK/out.wav" "$WORK/off.wav"
VESKSLOP_TTS_VOICE=keith-bell VESKSLOP_TTS_CRUNCH=telephone run_wrapper
check "crunch keeps 48k s16le" "pcm_s16le,48000,1" \
"$("${MICROSLOP_FFMPEG:-ffmpeg}" -hide_banner -loglevel error -i "$WORK/out.wav" -f null - 2>&1 >/dev/null; ffprobe -v error -show_entries stream=codec_name,sample_rate,channels -of csv=p=0 "$WORK/out.wav")"
check "crunch changes the audio" "1" "$([ "$(md5sum < "$WORK/off.wav")" != "$(md5sum < "$WORK/out.wav")" ] && echo 1 || echo 0)"
# Streaming word mode writes a live WAV with a gap between each pair of words.
VESKSLOP_TTS_VOICE=keith-bell VESKSLOP_TTS_WORDS=1 VESKSLOP_TTS_WORD_GAP_MS=200 \
VESKSLOP_TTS_STREAM=1 run_wrapper
check "stream header" "RIFF" "$(head -c 4 "$WORK/out.wav")"
check "stream is decodable" "0" \
"$("${MICROSLOP_FFMPEG:-ffmpeg}" -hide_banner -loglevel error -i "$WORK/out.wav" -f null - >/dev/null 2>&1; echo $?)"
check "stream has a gap per word break" "2" \
"$("${MICROSLOP_FFMPEG:-ffmpeg}" -hide_banner -i "$WORK/out.wav" -af silencedetect=noise=-45dB:d=0.15 -f null - 2>&1 | grep -c silence_start)"
# The Half-Life voices carry their profile preset.
VESKSLOP_TTS_VOICE=vox VESKSLOP_TTS_VOX=1 run_wrapper
check "vox voice writes the VOX profile (both hives)" "2" "$(grep -c '"Pitch"=dword:00000003' "$STUB_REG")"
check "vox voice runs VOX prosody" "1" "$(grep -c 'wH1' "$STUB_LOG")"
VESKSLOP_TTS_VOICE=hev run_wrapper
check "hev voice writes the HEV profile (both hives)" "2" "$(grep -c '"Pitch"=dword:0000000f' "$STUB_REG")"
check "hev voice has no prosody" "0" "$(grep -c 'wH1' "$STUB_LOG")"
# Voice metadata follows the active preset.
check "voices reflect hev" "1" \
"$(VESKSLOP_TTS_PRESET=hev sh "$WRAP" --voices | tr ',' '\n' | grep -c 'keith-bell|Keith Bell|male|en|expression=59+defaults=pitch:15:rate:24')"
[ "$FAIL" = "0" ] && echo "wrapper tests passed" || { echo "wrapper tests failed"; exit 1; }