Let word mode combine with the VOX preset
This commit is contained in:
@@ -40,6 +40,12 @@ mobile draft at 390px.
|
|||||||
- [x] C11 preview caching: Discord-hosted image/GIF previews (WebP 320px, hash-addressed, 5 MiB
|
- [x] C11 preview caching: Discord-hosted image/GIF previews (WebP 320px, hash-addressed, 5 MiB
|
||||||
cap, one attempt) run after every walk in both modes. Live run `...-20260916090722`:
|
cap, one attempt) run after every walk in both modes. Live run `...-20260916090722`:
|
||||||
`previewed: 2`; thumbs served over `/api/media/<scrape>/<item>/thumb` (200).
|
`previewed: 2`; thumbs served over `/api/media/<scrape>/<item>/thumb` (200).
|
||||||
|
- [x] C65 Word mode with the Half-Life preset. Word mode no longer yields to VOX prosody: with
|
||||||
|
a preset it synthesizes each word with the preset profile (per-word `\!R`/`\!%` tags), and the
|
||||||
|
VOX parser is skipped because its tags cannot survive a word split. The daemon always exports
|
||||||
|
`VESKSLOP_TTS_VOX` (0/1) so an explicit off is not overridden by the preset implication, and
|
||||||
|
the TTS panel allows preset + VOX + word mode together (word mode wins for delivery). Wrapper
|
||||||
|
tests cover preset+word mode with tags, the implied-VOX case, and the profile.
|
||||||
- [x] C64 Correct preset application. The design's bug-fix section landed in the wrapper:
|
- [x] C64 Correct preset application. The design's bug-fix section landed in the wrapper:
|
||||||
Flextalk rate/pitch now ride as sticky `\!R` / `\!%` vendor tags (attributes only for MSTTS),
|
Flextalk rate/pitch now ride as sticky `\!R` / `\!%` vendor tags (attributes only for MSTTS),
|
||||||
neutral sliders emit neither, word mode tags every word, and presets write the audible profile
|
neutral sliders emit neither, word mode tags every word, and presets write the audible profile
|
||||||
|
|||||||
+8
-4
@@ -158,8 +158,10 @@ from the TTS panel:
|
|||||||
default 0), and word mode (`tts.words`) with a gap in ms (`tts.wordGapMs`) for the radio/robot
|
default 0), and word mode (`tts.words`) with a gap in ms (`tts.wordGapMs`) for the radio/robot
|
||||||
character. **VOX prosody** (`tts.vox`) routes the line through the NetTTS-style encoder first -
|
character. **VOX prosody** (`tts.vox`) routes the line through the NetTTS-style encoder first -
|
||||||
sentence breaks, lead-ins, weighted pauses, and Flextalk vendor tags - for Black Mesa
|
sentence breaks, lead-ins, weighted pauses, and Flextalk vendor tags - for Black Mesa
|
||||||
announcement energy; **clean** (`tts.voxClean`) drops the staccato wrapper. VOX outranks word
|
announcement energy; **clean** (`tts.voxClean`) drops the staccato wrapper. Word mode **combines
|
||||||
mode and needs `python3` on the host. Both travel to the command provider as
|
with the presets** and takes precedence over VOX prosody: each word is its own engine instance,
|
||||||
|
so the parser is skipped and the vendor tags go on every word (`\!R` rate, `\!%` pitch). VOX
|
||||||
|
needs `python3` on the host. Both travel to the command provider as
|
||||||
`VESKSLOP_TTS_VOX` / `VESKSLOP_TTS_VOX_CLEAN` (or the `{vox}` / `{vox_clean}` placeholders).
|
`VESKSLOP_TTS_VOX` / `VESKSLOP_TTS_VOX_CLEAN` (or the `{vox}` / `{vox_clean}` placeholders).
|
||||||
A **preset** select (`tts.preset`: factory, VOX, HEV) shifts the Keith Bell speaker profile to
|
A **preset** select (`tts.preset`: factory, VOX, HEV) shifts the Keith Bell speaker profile to
|
||||||
the Half-Life approximation; `vox` also implies VOX prosody unless VOX is explicitly off, and
|
the Half-Life approximation; `vox` also implies VOX prosody unless VOX is explicitly off, and
|
||||||
@@ -168,8 +170,10 @@ from the TTS panel:
|
|||||||
both registry hives (plus the preset's Gender/Line Mode and a Default Speaker pointer), and for
|
both registry hives (plus the preset's Gender/Line Mode and a Default Speaker pointer), and for
|
||||||
Flextalk it sends rate/pitch as the sticky `\!R` / `\!%` vendor tags rather than
|
Flextalk it sends rate/pitch as the sticky `\!R` / `\!%` vendor tags rather than
|
||||||
`ITTSAttributes`; attributes only apply to MSTTS voices. Neutral sliders emit neither, so the
|
`ITTSAttributes`; attributes only apply to MSTTS voices. Neutral sliders emit neither, so the
|
||||||
preset is reproduced exactly. `sh microslopsam/setup/test-wrapper.sh` checks this contract with
|
preset is reproduced exactly. The daemon always exports `VESKSLOP_TTS_VOX` (0 or 1) so an
|
||||||
stubbed wine and engine binaries, no Wine install needed. The preset values also show up in the
|
explicit off is honored, and the panel lets a preset, VOX, and word mode be combined.
|
||||||
|
`sh microslopsam/setup/test-wrapper.sh` checks this contract with stubbed wine and engine
|
||||||
|
binaries, no Wine install needed. The preset values also show up in the
|
||||||
voice metadata defaults, so the panel's "default" snap points match what the engine gets.
|
voice metadata defaults, so the panel's "default" snap points match what the engine gets.
|
||||||
- **Test lines** - Attention, Laboratory, Critical, Health, and Radiation, plus two custom lines
|
- **Test lines** - Attention, Laboratory, Critical, Health, and Radiation, plus two custom lines
|
||||||
kept in `localStorage`. Each button speaks the line with the current profile, so a change can be
|
kept in `localStorage`. Each button speaks the line with the current profile, so a change can be
|
||||||
|
|||||||
@@ -103,6 +103,22 @@ VESKSLOP_TTS_VOICE=keith-bell run_wrapper
|
|||||||
check "plain run writes both hives" "2" "$(grep -c 'Speakers\\{11260610' "$STUB_REG")"
|
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")"
|
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.800 ')"
|
||||||
|
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")"
|
||||||
|
|
||||||
# Voice metadata follows the active preset.
|
# Voice metadata follows the active preset.
|
||||||
check "voices reflect hev" "1" \
|
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')"
|
"$(VESKSLOP_TTS_PRESET=hev sh "$WRAP" --voices | tr ',' '\n' | grep -c 'keith-bell|Keith Bell|male|en|expression=59+defaults=pitch:15:rate:24')"
|
||||||
|
|||||||
@@ -16,7 +16,8 @@
|
|||||||
# prominences, head_size, lowering
|
# prominences, head_size, lowering
|
||||||
# VESKSLOP_TTS_HIGHPASS high-pass Hz (40-400, default 40)
|
# VESKSLOP_TTS_HIGHPASS high-pass Hz (40-400, default 40)
|
||||||
# VESKSLOP_TTS_ECHO mono echo amount 0-100 (default 0)
|
# VESKSLOP_TTS_ECHO mono echo amount 0-100 (default 0)
|
||||||
# VESKSLOP_TTS_WORDS 1 = synthesize each word, merge (Flextalk)
|
# VESKSLOP_TTS_WORDS 1 = synthesize each word, merge (Flextalk);
|
||||||
|
# combines with a preset and wins over VOX prosody
|
||||||
# VESKSLOP_TTS_WORD_GAP_MS silence between words in ms (0-500)
|
# VESKSLOP_TTS_WORD_GAP_MS silence between words in ms (0-500)
|
||||||
# VESKSLOP_TTS_PRESET vox | hev (Keith Bell profile preset; vox implies VOX)
|
# VESKSLOP_TTS_PRESET vox | hev (Keith Bell profile preset; vox implies VOX)
|
||||||
# VESKSLOP_TTS_VOX 1 = VOX prosody, NetTTS-style (Flextalk)
|
# VESKSLOP_TTS_VOX 1 = VOX prosody, NetTTS-style (Flextalk)
|
||||||
@@ -305,9 +306,11 @@ 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. Word mode builds
|
||||||
|
# its own cadence from single-word instances, so the parser is skipped there: the
|
||||||
|
# VOX tags cannot survive a word split.
|
||||||
TEXT_SRC="$TEXT_IN"
|
TEXT_SRC="$TEXT_IN"
|
||||||
if [ "$VOX" = "1" ] && [ -n "$SPEAKER_GUID" ]; then
|
if [ "$VOX" = "1" ] && [ -n "$SPEAKER_GUID" ] && [ "$WORDS" != "1" ]; then
|
||||||
if command -v "$MICROSLOP_PYTHON" >/dev/null 2>&1 && [ -f "$MICROSLOP_ROOT/setup/vox_parser.py" ]; then
|
if command -v "$MICROSLOP_PYTHON" >/dev/null 2>&1 && [ -f "$MICROSLOP_ROOT/setup/vox_parser.py" ]; then
|
||||||
if [ "$VOX_CLEAN" = "1" ]; then
|
if [ "$VOX_CLEAN" = "1" ]; then
|
||||||
"$MICROSLOP_PYTHON" "$MICROSLOP_ROOT/setup/vox_parser.py" --clean < "$TEXT_IN" > "$TMP/vox.txt"
|
"$MICROSLOP_PYTHON" "$MICROSLOP_ROOT/setup/vox_parser.py" --clean < "$TEXT_IN" > "$TMP/vox.txt"
|
||||||
@@ -356,8 +359,9 @@ try_run_host() {
|
|||||||
|
|
||||||
# Word mode (Flextalk): synthesize each word and merge with silence gaps, which
|
# Word mode (Flextalk): synthesize each word and merge with silence gaps, which
|
||||||
# gives the staccato radio cadence the Half-Life VOX/HEV voices are known for.
|
# gives the staccato radio cadence the Half-Life VOX/HEV voices are known for.
|
||||||
# VOX mode takes precedence: it produces its own cadence with vendor tags.
|
# It combines with a preset (the profile is already written) and takes precedence
|
||||||
if [ "$WORDS" = "1" ] && [ -n "$SPEAKER_GUID" ] && [ "$VOX" != "1" ]; then
|
# over VOX prosody, which cannot survive a word split.
|
||||||
|
if [ "$WORDS" = "1" ] && [ -n "$SPEAKER_GUID" ]; then
|
||||||
GAP=$WORD_GAP
|
GAP=$WORD_GAP
|
||||||
case "$GAP" in *[!0-9]*|"") GAP=120 ;; esac
|
case "$GAP" in *[!0-9]*|"") GAP=120 ;; esac
|
||||||
[ "$GAP" -gt 500 ] && GAP=500
|
[ "$GAP" -gt 500 ] && GAP=500
|
||||||
|
|||||||
+2
-6
@@ -2031,7 +2031,6 @@ function fillProfileControls() {
|
|||||||
$("#tts-vox").checked = state.speech.vox === true;
|
$("#tts-vox").checked = state.speech.vox === true;
|
||||||
$("#tts-vox-clean").checked = state.speech.voxClean === true;
|
$("#tts-vox-clean").checked = state.speech.voxClean === true;
|
||||||
$("#tts-vox-clean").disabled = state.speech.vox !== true;
|
$("#tts-vox-clean").disabled = state.speech.vox !== true;
|
||||||
$("#tts-words").disabled = state.speech.vox === true;
|
|
||||||
const gap = Number(state.speech.wordGapMs);
|
const gap = Number(state.speech.wordGapMs);
|
||||||
$("#tts-word-gap").value = Number.isFinite(gap) && state.speech.wordGapMs !== null ? gap : 120;
|
$("#tts-word-gap").value = Number.isFinite(gap) && state.speech.wordGapMs !== null ? gap : 120;
|
||||||
$("#tts-word-gap-value").textContent = `${$("#tts-word-gap").value}ms`;
|
$("#tts-word-gap-value").textContent = `${$("#tts-word-gap").value}ms`;
|
||||||
@@ -2744,17 +2743,14 @@ function init() {
|
|||||||
$("#tts-words").addEventListener("change", () => void saveSpeech());
|
$("#tts-words").addEventListener("change", () => void saveSpeech());
|
||||||
$("#tts-vox").addEventListener("change", () => {
|
$("#tts-vox").addEventListener("change", () => {
|
||||||
$("#tts-vox-clean").disabled = !$("#tts-vox").checked;
|
$("#tts-vox-clean").disabled = !$("#tts-vox").checked;
|
||||||
$("#tts-words").disabled = $("#tts-vox").checked;
|
|
||||||
// The vox preset implies VOX; turning VOX off means "factory/custom" again.
|
|
||||||
if (!$("#tts-vox").checked && $("#tts-preset").value === "vox") $("#tts-preset").value = "";
|
|
||||||
void saveSpeech();
|
void saveSpeech();
|
||||||
});
|
});
|
||||||
$("#tts-preset").addEventListener("change", () => {
|
$("#tts-preset").addEventListener("change", () => {
|
||||||
// Picking the VOX preset turns on VOX prosody so the panel matches the engine.
|
// Picking the VOX preset turns on VOX prosody as a default. Word mode still wins
|
||||||
|
// for word-by-word delivery, and turning VOX off keeps the preset.
|
||||||
if ($("#tts-preset").value === "vox") {
|
if ($("#tts-preset").value === "vox") {
|
||||||
$("#tts-vox").checked = true;
|
$("#tts-vox").checked = true;
|
||||||
$("#tts-vox-clean").disabled = false;
|
$("#tts-vox-clean").disabled = false;
|
||||||
$("#tts-words").disabled = true;
|
|
||||||
}
|
}
|
||||||
void saveSpeech();
|
void saveSpeech();
|
||||||
});
|
});
|
||||||
|
|||||||
+3
-1
@@ -381,7 +381,9 @@ export class Tts {
|
|||||||
...(options.wordGapMs !== undefined
|
...(options.wordGapMs !== undefined
|
||||||
? { VESKSLOP_TTS_WORD_GAP_MS: String(options.wordGapMs) }
|
? { VESKSLOP_TTS_WORD_GAP_MS: String(options.wordGapMs) }
|
||||||
: {}),
|
: {}),
|
||||||
...(options.vox ? { VESKSLOP_TTS_VOX: "1" } : {}),
|
// Always defined, so an explicit "off" is not overridden by the wrapper's
|
||||||
|
// preset implication (which only fires when the variable is unset).
|
||||||
|
VESKSLOP_TTS_VOX: options.vox ? "1" : "0",
|
||||||
...(options.voxClean ? { VESKSLOP_TTS_VOX_CLEAN: "1" } : {}),
|
...(options.voxClean ? { VESKSLOP_TTS_VOX_CLEAN: "1" } : {}),
|
||||||
...(options.preset ? { VESKSLOP_TTS_PRESET: options.preset } : {})
|
...(options.preset ? { VESKSLOP_TTS_PRESET: options.preset } : {})
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user