Inline /rate and /pitch controls handled by the wrapper; docs
This commit is contained in:
@@ -67,7 +67,8 @@ voices with `expression=NN`, where NN is the registry default. Mapping details a
|
|||||||
|
|
||||||
**VOX prosody** (NetTTS-style, Flextalk voices): set `VESKSLOP_TTS_VOX=1` in the
|
**VOX prosody** (NetTTS-style, Flextalk voices): set `VESKSLOP_TTS_VOX=1` in the
|
||||||
command environment (`VESKSLOP_TTS_VOX_CLEAN=1` omits the `\!wH1/\!wH0` stutter
|
command environment (`VESKSLOP_TTS_VOX_CLEAN=1` omits the `\!wH1/\!wH0` stutter
|
||||||
wrapper). See `docs/TTS-DESIGN.md`.
|
wrapper). Line-leading `/rate N` and `/pitch N` controls are also handled by the
|
||||||
|
wrapper, so the WebUI does not need to implement them. See `docs/TTS-DESIGN.md`.
|
||||||
|
|
||||||
## Layout
|
## Layout
|
||||||
|
|
||||||
|
|||||||
@@ -105,6 +105,9 @@ final file `WatsonUNInstall.dll` is incomplete).
|
|||||||
- VOX mode is Flextalk-only. The wrapper enables it with `VESKSLOP_TTS_VOX=1`
|
- VOX mode is Flextalk-only. The wrapper enables it with `VESKSLOP_TTS_VOX=1`
|
||||||
(`VESKSLOP_TTS_VOX_CLEAN=1` drops the stutter wrapper) and it takes precedence
|
(`VESKSLOP_TTS_VOX_CLEAN=1` drops the stutter wrapper) and it takes precedence
|
||||||
over `VESKSLOP_TTS_WORDS`.
|
over `VESKSLOP_TTS_WORDS`.
|
||||||
|
- Line-leading `/rate N` and `/pitch N` controls are consumed by the wrapper
|
||||||
|
(percent, 100 = normal; `<= 2.0` means multiplier). They override the env
|
||||||
|
speed/pitch and are not spoken; a controls-only line yields 100 ms of silence.
|
||||||
|
|
||||||
## Decompiled corpora (Ghidra)
|
## Decompiled corpora (Ghidra)
|
||||||
|
|
||||||
|
|||||||
@@ -371,6 +371,26 @@ Other NetTTS features and their relevance:
|
|||||||
our prefix runs the engines with builtin Wine libraries.
|
our prefix runs the engines with builtin Wine libraries.
|
||||||
- Win32 GUI and preset sliders: Veskslop provides the UI.
|
- Win32 GUI and preset sliders: Veskslop provides the UI.
|
||||||
|
|
||||||
|
### Inline rate/pitch controls (engine-side)
|
||||||
|
|
||||||
|
The wrapper consumes NetTTS-style, line-leading controls, so Veskslop and the WebUI do
|
||||||
|
not need to parse or implement them:
|
||||||
|
|
||||||
|
/rate 150 Attention all personnel. # 150% rate, then the line
|
||||||
|
/pitch 90 Attention all personnel. # 90% pitch
|
||||||
|
/rate 120 /pitch 110 Inbound tram. # both, in any order
|
||||||
|
|
||||||
|
- `N` is a percent (100 = normal, clamped to 0-200); values `<= 2.0` are multipliers
|
||||||
|
(`/rate 1.5` = 150%).
|
||||||
|
- They override `VESKSLOP_TTS_SPEED` / `VESKSLOP_TTS_PITCH` for that line and are not
|
||||||
|
spoken.
|
||||||
|
- A line that contains only controls yields 100 ms of silence, so the daemon always
|
||||||
|
receives a valid WAV.
|
||||||
|
- Rate maps through the normal speed scale; pitch percent maps to semitones
|
||||||
|
(`st = 12*log2(N/100)`) and then through the per-voice natural pitch.
|
||||||
|
- Verified on the dev host: `/rate 50` doubles the line duration; `/pitch 150` raises
|
||||||
|
the median F0 from 122 Hz to 177 Hz.
|
||||||
|
|
||||||
### Transport and latency
|
### Transport and latency
|
||||||
|
|
||||||
- Installation is one command: `setup/install.sh` (system dependencies with sudo, wine
|
- Installation is one command: `setup/install.sh` (system dependencies with sudo, wine
|
||||||
|
|||||||
+63
-20
@@ -87,6 +87,66 @@ case "$ENGVOICE" in
|
|||||||
"Kerry Watson") SMAX=390; PMAX=250; NAT_PITCH=186 ;;
|
"Kerry Watson") SMAX=390; PMAX=250; NAT_PITCH=186 ;;
|
||||||
*) SMAX=450; PMAX=200; NAT_PITCH=100 ;;
|
*) SMAX=450; PMAX=200; NAT_PITCH=100 ;;
|
||||||
esac
|
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=""
|
SET_ARGS=""
|
||||||
if [ -n "$SPEED" ]; then
|
if [ -n "$SPEED" ]; then
|
||||||
S=$(awk -v m="$SPEED" 'BEGIN{printf "%d", 150*m+0.5}')
|
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"
|
SET_ARGS="$SET_ARGS -p $P"
|
||||||
fi
|
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
|
# 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
|
# 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
|
# 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
|
# 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
|
# vendor tags (\!wH1/\!wH0, \!br, \!sfNN) with setup/vox_parser.py; the parser
|
||||||
# works on UTF-8, so it runs before the CP1252 transliteration.
|
# 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 [ "$VOX" = "1" ] && [ -n "$SPEAKER_GUID" ]; then
|
||||||
if command -v python3 >/dev/null 2>&1 && [ -f "$ROOT/setup/vox_parser.py" ]; then
|
if command -v python3 >/dev/null 2>&1 && [ -f "$ROOT/setup/vox_parser.py" ]; then
|
||||||
if [ "$VOX_CLEAN" = "1" ]; 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
|
else
|
||||||
python3 "$ROOT/setup/vox_parser.py" < "$TEXTFILE" > "$TMP/vox.txt"
|
python3 "$ROOT/setup/vox_parser.py" < "$TEXT_IN" > "$TMP/vox.txt"
|
||||||
fi
|
fi
|
||||||
TEXT_SRC="$TMP/vox.txt"
|
TEXT_SRC="$TMP/vox.txt"
|
||||||
else
|
else
|
||||||
|
|||||||
Reference in New Issue
Block a user