Inline /rate and /pitch controls handled by the wrapper; docs

This commit is contained in:
Fisto
2026-09-16 08:08:15 +02:00
parent e45e083205
commit 6d010292f2
4 changed files with 88 additions and 21 deletions
+63 -20
View File
@@ -87,6 +87,66 @@ case "$ENGVOICE" in
"Kerry Watson") SMAX=390; PMAX=250; NAT_PITCH=186 ;;
*) SMAX=450; PMAX=200; NAT_PITCH=100 ;;
esac
# The daemon does not export DISPLAY; keep a private Xvfb for the engine.
start_xvfb() {
if ! [ -S /tmp/.X11-unix/X77 ]; then
Xvfb :77 -screen 0 1024x768x24 >/dev/null 2>&1 &
sleep 1
fi
}
if [ -z "${DISPLAY:-}" ]; then
start_xvfb
export DISPLAY=:77
fi
# NetTTS-style line controls, handled here so no caller/WebUI work is needed:
# /rate N and/or /pitch N at the start of the line.
# N is a percent (100 = normal); values <= 2.0 are multipliers (/rate 1.5 = 150%).
# They override the env speed/pitch for this line and are not spoken. A line that
# contains only commands yields 100 ms of silence, so the daemon still gets a WAV.
TMP=$(mktemp -d)
trap 'rm -rf "$TMP"' EXIT
RAW="$TMP/raw.wav"
TXT="$TMP/text.cp1252"
TEXT_IN="$TEXTFILE"
if [ -f "$TEXTFILE" ]; then
set -f
# shellcheck disable=SC2046
set -- $(cat "$TEXTFILE")
set +f
CMD_SPEED=""
CMD_PITCH=""
while [ "$#" -ge 2 ]; do
case "$1" in
/rate|/pitch)
case "$2" in
''|*[!0-9.]*) break ;;
esac
if [ "$1" = "/rate" ]; then CMD_SPEED=$2; else CMD_PITCH=$2; fi
shift 2
;;
*) break ;;
esac
done
if [ -n "$CMD_SPEED" ] || [ -n "$CMD_PITCH" ]; then
if [ -n "$CMD_SPEED" ]; then
SPEED=$(awk -v v="$CMD_SPEED" 'BEGIN{ if (v<=2.0) v*=100; if (v<0) v=0; if (v>200) v=200; printf "%.4f", v/100 }')
fi
if [ -n "$CMD_PITCH" ]; then
PITCH=$(awk -v v="$CMD_PITCH" 'BEGIN{ if (v<=2.0) v*=100; if (v<0) v=0; if (v>200) v=200; printf "%.4f", 12*log(v/100)/log(2) }')
fi
if [ -z "$*" ]; then
ffmpeg -hide_banner -loglevel error -f lavfi -i anullsrc=r=48000:cl=mono \
-t 0.1 -c:a pcm_s16le -y "$OUT" >/dev/null 2>&1 \
|| { echo "could not write silence" >&2; exit 1; }
exit 0
fi
printf '%s\n' "$*" > "$TMP/input.txt"
TEXT_IN="$TMP/input.txt"
fi
fi
# Build the engine arguments from the (possibly command-overridden) speed/pitch.
SET_ARGS=""
if [ -n "$SPEED" ]; then
S=$(awk -v m="$SPEED" 'BEGIN{printf "%d", 150*m+0.5}')
@@ -101,23 +161,6 @@ if [ -n "$PITCH" ]; then
SET_ARGS="$SET_ARGS -p $P"
fi
# The daemon does not export DISPLAY; keep a private Xvfb for the engine.
start_xvfb() {
if ! [ -S /tmp/.X11-unix/X77 ]; then
Xvfb :77 -screen 0 1024x768x24 >/dev/null 2>&1 &
sleep 1
fi
}
if [ -z "${DISPLAY:-}" ]; then
start_xvfb
export DISPLAY=:77
fi
TMP=$(mktemp -d)
trap 'rm -rf "$TMP"' EXIT
RAW="$TMP/raw.wav"
TXT="$TMP/text.cp1252"
# Apply the Flextalk profile before synthesis. Profiles are read at voice load and
# the host loads the engine per request, so the values are written every call: the
# engine defaults plus any VESKSLOP_TTS_PROFILE overrides. A missing value therefore
@@ -204,13 +247,13 @@ fi
# SAPI4 is ANSI/CP1252. VOX mode (Flextalk only) first transforms the text into
# vendor tags (\!wH1/\!wH0, \!br, \!sfNN) with setup/vox_parser.py; the parser
# works on UTF-8, so it runs before the CP1252 transliteration.
TEXT_SRC="$TEXTFILE"
TEXT_SRC="$TEXT_IN"
if [ "$VOX" = "1" ] && [ -n "$SPEAKER_GUID" ]; then
if command -v python3 >/dev/null 2>&1 && [ -f "$ROOT/setup/vox_parser.py" ]; then
if [ "$VOX_CLEAN" = "1" ]; then
python3 "$ROOT/setup/vox_parser.py" --clean < "$TEXTFILE" > "$TMP/vox.txt"
python3 "$ROOT/setup/vox_parser.py" --clean < "$TEXT_IN" > "$TMP/vox.txt"
else
python3 "$ROOT/setup/vox_parser.py" < "$TEXTFILE" > "$TMP/vox.txt"
python3 "$ROOT/setup/vox_parser.py" < "$TEXT_IN" > "$TMP/vox.txt"
fi
TEXT_SRC="$TMP/vox.txt"
else