Sync wrapper with Veskslop: streaming/crunch modes, vox/hev voice IDs
The Veskslop integration copy had grown word streaming (VESKSLOP_TTS_STREAM, stream gain), 1990s crunch filters (VESKSLOP_TTS_CRUNCH[_BITS|_RATE]), echo timing/level, and first-class vox/hev voice IDs with preset-aware default metadata. Merge those back into the standalone repo, keeping the tuned VOX preset (pitch 0, rate 15, head size 34, lowering 50) in the preset and in the published defaults. Update the wrapper regression test to the tuned registry values, add __pycache__ to .gitignore, and document the new variables, voice IDs, placeholder set and the word-mode-over-VOX precedence in README, NOTES and TTS-DESIGN.
This commit is contained in:
@@ -10,3 +10,4 @@ setup/out-*.reg
|
|||||||
*.wav
|
*.wav
|
||||||
setup/microslopsam.env
|
setup/microslopsam.env
|
||||||
third_party/flextalk/extracted/
|
third_party/flextalk/extracted/
|
||||||
|
__pycache__/
|
||||||
|
|||||||
@@ -73,8 +73,8 @@ Options: `-v` voice, `-t` text, `-o` output WAV, `-p` pitch, `-s` speed, `-V` vo
|
|||||||
export VESKSLOP_TTS_CMD="$PWD/setup/veskslop-tts.sh {out} {text_file}"
|
export VESKSLOP_TTS_CMD="$PWD/setup/veskslop-tts.sh {out} {text_file}"
|
||||||
export VESKSLOP_TTS_VOICES="$(setup/veskslop-tts.sh --voices)"
|
export VESKSLOP_TTS_VOICES="$(setup/veskslop-tts.sh --voices)"
|
||||||
|
|
||||||
The wrapper maps voice IDs (`sam`, `mary`, `mike`, `keith-bell`, `kerry-watson`, plus the
|
The wrapper maps voice IDs (`sam`, `mary`, `mike`, `keith-bell`, `kerry-watson`, `vox`,
|
||||||
MSTTS variants), reads `VESKSLOP_TTS_VOICE`, `VESKSLOP_TTS_SPEED` (multiplier),
|
`hev`, plus the MSTTS variants), reads `VESKSLOP_TTS_VOICE`, `VESKSLOP_TTS_SPEED` (multiplier),
|
||||||
`VESKSLOP_TTS_PITCH` (semitones) and `VESKSLOP_TTS_EXPRESSION` (0-100, Flextalk voices
|
`VESKSLOP_TTS_PITCH` (semitones) and `VESKSLOP_TTS_EXPRESSION` (0-100, Flextalk voices
|
||||||
only), starts Xvfb when `DISPLAY` is unset, and writes a 48 kHz mono s16le WAV trimmed,
|
only), starts Xvfb when `DISPLAY` is unset, and writes a 48 kHz mono s16le WAV trimmed,
|
||||||
faded and normalized to about -16 LUFS (-3 dBFS true peak).
|
faded and normalized to about -16 LUFS (-3 dBFS true peak).
|
||||||
@@ -95,10 +95,17 @@ so the first word is not gated; tune with `VESKSLOP_TTS_HUM_MS` (0 = off),
|
|||||||
|
|
||||||
**Half-Life presets** (Keith Bell): `VESKSLOP_TTS_PRESET=vox` applies the Valve VOX
|
**Half-Life presets** (Keith Bell): `VESKSLOP_TTS_PRESET=vox` applies the Valve VOX
|
||||||
profile (pitch 0, rate 15, head size 34, lowering 50) and enables VOX prosody;
|
profile (pitch 0, rate 15, head size 34, lowering 50) and enables VOX prosody;
|
||||||
`VESKSLOP_TTS_PRESET=hev` is a starting point for the HEV suit voice. The VOX values were
|
`VESKSLOP_TTS_PRESET=hev` is a starting point for the HEV suit voice. The `vox` and `hev`
|
||||||
|
voice IDs select these directly. The VOX values were
|
||||||
tuned against the game's word assets with `tools/vox_compare.py`; details in
|
tuned against the game's word assets with `tools/vox_compare.py`; details in
|
||||||
`docs/TTS-DESIGN.md`.
|
`docs/TTS-DESIGN.md`.
|
||||||
|
|
||||||
|
`VESKSLOP_TTS_CRUNCH` (`hl90`, `hl90hq`, `telephone`, `adpcm`, `crush`) adds 1990s lossy
|
||||||
|
character to the output; `VESKSLOP_TTS_CRUNCH_BITS`/`_RATE` override its bit depth and
|
||||||
|
sample rate. `VESKSLOP_TTS_STREAM=1` makes word mode write the WAV as it goes (no
|
||||||
|
measurement pass; `VESKSLOP_TTS_STREAM_GAIN_DB` sets the gain). Echo timing and level are
|
||||||
|
`VESKSLOP_TTS_ECHO_DELAY_MS` and `VESKSLOP_TTS_ECHO_LEVEL`.
|
||||||
|
|
||||||
## Layout
|
## Layout
|
||||||
|
|
||||||
- `host/sapi4host.c` — host: voice select, pitch/speed, text -> WAV
|
- `host/sapi4host.c` — host: voice select, pitch/speed, text -> WAV
|
||||||
@@ -109,6 +116,7 @@ tuned against the game's word assets with `tools/vox_compare.py`; details in
|
|||||||
- `setup/inf2reg.py` — INF AddReg -> `.reg` converter
|
- `setup/inf2reg.py` — INF AddReg -> `.reg` converter
|
||||||
- `setup/run.sh` — run helper (prefix + Xvfb)
|
- `setup/run.sh` — run helper (prefix + Xvfb)
|
||||||
- `setup/veskslop-tts.sh` — Veskslop command-provider wrapper (post-processing included)
|
- `setup/veskslop-tts.sh` — Veskslop command-provider wrapper (post-processing included)
|
||||||
|
- `setup/test-wrapper.sh` — wrapper regression test (stubbed Wine/host, no prefix needed)
|
||||||
- `setup/vox_parser.py` — NetTTS-style VOX prosody encoder (Flextalk voices)
|
- `setup/vox_parser.py` — NetTTS-style VOX prosody encoder (Flextalk voices)
|
||||||
- `tools/vox_compare.py` — reference-vs-synthesis metrics for preset tuning
|
- `tools/vox_compare.py` — reference-vs-synthesis metrics for preset tuning
|
||||||
- `docs/NOTES.md` — registry/CLSID facts, runtime RE notes, gotchas
|
- `docs/NOTES.md` — registry/CLSID facts, runtime RE notes, gotchas
|
||||||
|
|||||||
+2
-2
@@ -127,8 +127,8 @@ without them.
|
|||||||
the Python port; outputs are identical on the 31-line corpus. Re-check after any
|
the Python port; outputs are identical on the 31-line corpus. Re-check after any
|
||||||
parser change while the clone is available.
|
parser change while the clone is available.
|
||||||
- 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); word mode
|
||||||
over `VESKSLOP_TTS_WORDS`.
|
(`VESKSLOP_TTS_WORDS=1`) wins and skips VOX prosody.
|
||||||
- Half-Life presets (`VESKSLOP_TTS_PRESET=vox|hev`) target Keith Bell only. VOX starts
|
- Half-Life presets (`VESKSLOP_TTS_PRESET=vox|hev`) target Keith Bell only. VOX starts
|
||||||
from NetTTS `nettts_flextalk_vox_profile.reg` (pitch 3, rate 17, head 52, lowering 40)
|
from NetTTS `nettts_flextalk_vox_profile.reg` (pitch 3, rate 17, head 52, lowering 40)
|
||||||
and is tuned against the game's word assets from `suchipi/half-life-vox-console`:
|
and is tuned against the game's word assets from `suchipi/half-life-vox-console`:
|
||||||
|
|||||||
+16
-6
@@ -254,6 +254,9 @@ record what the engine needs to interpret, add, or hide._
|
|||||||
- Assigned stable IDs: `sam`, `mary`, `mike`, `keith-bell`, `kerry-watson`; variants get
|
- Assigned stable IDs: `sam`, `mary`, `mike`, `keith-bell`, `kerry-watson`; variants get
|
||||||
`mike-telephone`, `mike-hall`, `mary-stadium`, `mike-space`, and so on. Unknown IDs fall
|
`mike-telephone`, `mike-hall`, `mary-stadium`, `mike-space`, and so on. Unknown IDs fall
|
||||||
back to `sam`.
|
back to `sam`.
|
||||||
|
- `vox` (VOX (Half-Life)) and `hev` (HEV suit) are Keith Bell with a fixed preset profile;
|
||||||
|
selecting one implies `VESKSLOP_TTS_PRESET` (`vox` also enables VOX prosody). Their
|
||||||
|
`defaults=` metadata follows the active preset so the UI snap points match.
|
||||||
- `/voices` (HTTP mode) reports `language: "en"`, gender, and the speed/pitch ranges below.
|
- `/voices` (HTTP mode) reports `language: "en"`, gender, and the speed/pitch ranges below.
|
||||||
|
|
||||||
### Speed and pitch mapping (measured 2026-09-15)
|
### Speed and pitch mapping (measured 2026-09-15)
|
||||||
@@ -372,15 +375,16 @@ Requirements and scope:
|
|||||||
The host passes `TTSDATAFLAG_TAGGED`, so the engine honors them. Other voices ignore
|
The host passes `TTSDATAFLAG_TAGGED`, so the engine honors them. Other voices ignore
|
||||||
`VESKSLOP_TTS_VOX`.
|
`VESKSLOP_TTS_VOX`.
|
||||||
- Needs `python3` at runtime; the wrapper fails loudly when it is missing.
|
- Needs `python3` at runtime; the wrapper fails loudly when it is missing.
|
||||||
- Mutually exclusive with `VESKSLOP_TTS_WORDS` (VOX wins).
|
- Combines with `VESKSLOP_TTS_WORDS`: word mode wins and VOX prosody is skipped.
|
||||||
- Measured on the dev host for "Now approaching Sector C test lab. Please stand clear
|
- Measured on the dev host for "Now approaching Sector C test lab. Please stand clear
|
||||||
of the test chamber.": plain 4.70 s with 1 pause, VOX 7.81 s with 8 pauses.
|
of the test chamber.": plain 4.70 s with 1 pause, VOX 7.81 s with 8 pauses.
|
||||||
|
|
||||||
Other NetTTS features and their relevance:
|
Other NetTTS features and their relevance:
|
||||||
|
|
||||||
- Inline `/rate N`, `/pitch N`, `[[pause N]]`: not ported. Veskslop has speed/pitch
|
- Inline `/rate N`, `/pitch N` are handled by the wrapper at the start of a line
|
||||||
controls and the profile window; `[[pause N]]` could be added as a wrapper-level
|
(percent, or a multiplier at or below 2.0); they override the env sliders for that
|
||||||
text splitter later (NetTTS supports it only outside VOX mode).
|
line and are not spoken. `[[pause N]]` is not ported; it could be added as a
|
||||||
|
wrapper-level text splitter later.
|
||||||
- TCP server with `START`/`STOP` status sockets: not needed; Veskslop owns playback
|
- TCP server with `START`/`STOP` status sockets: not needed; Veskslop owns playback
|
||||||
and knows when a line ends.
|
and knows when a line ends.
|
||||||
- Winetricks prefix automation (`winxp`, `vcrun6`, `mfc42`, `riched20`): not needed;
|
- Winetricks prefix automation (`winxp`, `vcrun6`, `mfc42`, `riched20`): not needed;
|
||||||
@@ -702,10 +706,16 @@ Inputs: `{out}`, `{text_file}`, optional positional voice/speed/pitch/expression
|
|||||||
| --- | --- |
|
| --- | --- |
|
||||||
| `VESKSLOP_TTS_PROFILE` | `pitch:27:...`/`key=value,...` overrides; the wrapper writes the full profile (defaults + overrides) to the Wine registry with `wine reg import` before every synthesis. |
|
| `VESKSLOP_TTS_PROFILE` | `pitch:27:...`/`key=value,...` overrides; the wrapper writes the full profile (defaults + overrides) to the Wine registry with `wine reg import` before every synthesis. |
|
||||||
| `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 0-100 (`aecho`), default 0. |
|
| `VESKSLOP_TTS_ECHO` | Mono echo 0-100 (`aecho`), default 0. The echo is mixed under the dry voice, so the voice level does not change. |
|
||||||
|
| `VESKSLOP_TTS_ECHO_DELAY_MS` / `VESKSLOP_TTS_ECHO_LEVEL` | Echo delay 10-400 ms (default 60) and repeat level 0-0.95 relative to the dry voice (default 0.85). |
|
||||||
|
| `VESKSLOP_TTS_CRUNCH` | `off` (default) or `hl90`, `hl90hq`, `telephone`, `adpcm`, `crush`: 1990s lossy character, applied last. |
|
||||||
|
| `VESKSLOP_TTS_CRUNCH_BITS` / `VESKSLOP_TTS_CRUNCH_RATE` | Bit depth 1-16 and sample rate 4000-48000 overrides for the crunch stage. |
|
||||||
|
| `VESKSLOP_TTS_STREAM` / `VESKSLOP_TTS_STREAM_GAIN_DB` | `1` = word mode writes the WAV as it goes (no measurement pass); fixed gain in dB for that path. |
|
||||||
| `VESKSLOP_TTS_WORDS` / `VESKSLOP_TTS_WORD_GAP_MS` | Word-by-word synthesis with silence gaps (Flextalk only). |
|
| `VESKSLOP_TTS_WORDS` / `VESKSLOP_TTS_WORD_GAP_MS` | Word-by-word synthesis with silence gaps (Flextalk only). |
|
||||||
|
|
||||||
Placeholders `{profile}`, `{highpass}`, `{echo}`, `{words}`, `{word_gap_ms}` mirror the env vars.
|
Placeholders `{profile}`, `{highpass}`, `{echo}`, `{echo_delay_ms}`, `{echo_level}`,
|
||||||
|
`{words}`, `{word_gap_ms}`, `{crunch}`, `{crunch_bits}`, `{crunch_rate}`, `{stream}`,
|
||||||
|
`{vox}`, `{vox_clean}`, `{preset}` mirror the env vars.
|
||||||
`wine regedit` fails headlessly but returns 0: use `wine reg import`. A missing profile value
|
`wine regedit` fails headlessly but returns 0: use `wine reg import`. A missing profile value
|
||||||
must always write the engine default so no stale registry state survives.
|
must always write the engine default so no stale registry state survives.
|
||||||
|
|
||||||
|
|||||||
@@ -94,8 +94,8 @@ check "vox preset implies VOX parser" "1" "$(grep -c 'wH1' "$STUB_LOG")"
|
|||||||
# The preset writes the full audible profile to both hives.
|
# The preset writes the full audible profile to both hives.
|
||||||
VESKSLOP_TTS_VOICE=keith-bell VESKSLOP_TTS_PRESET=vox run_wrapper
|
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 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 pitch (both hives)" "2" "$(grep -c '"Pitch"=dword:00000000' "$STUB_REG")"
|
||||||
check "preset rate (both hives)" "2" "$(grep -c '"Speaking Rate"=dword:00000011' "$STUB_REG")"
|
check "preset rate (both hives)" "2" "$(grep -c '"Speaking Rate"=dword:0000000f' "$STUB_REG")"
|
||||||
check "preset gender and line mode" "4" "$(grep -cE '"Gender"=dword:00000002|"Line Mode"=dword:00000001' "$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")"
|
check "preset default speaker" "2" "$(grep -c '"Default Speaker"' "$STUB_REG")"
|
||||||
|
|
||||||
@@ -111,7 +111,7 @@ check "word mode with preset synthesizes every word" "3" "$(grep -c '^ARGV:' "$S
|
|||||||
check "word mode skips the VOX parser" "0" "$(grep -c 'wH1' "$STUB_LOG")"
|
check "word mode skips the VOX parser" "0" "$(grep -c 'wH1' "$STUB_LOG")"
|
||||||
check "word mode tags every word" "3" \
|
check "word mode tags every word" "3" \
|
||||||
"$(sed -n 's/.*\[-t\] \[\(.*\)\] \[-o\].*/\1/p' "$STUB_LOG" | grep -c '^\\!R0.80 ')"
|
"$(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 keeps the preset profile" "2" "$(grep -c '"Pitch"=dword:00000000' "$STUB_REG")"
|
||||||
check "word mode emits no attributes" "0" "$(attr_count)"
|
check "word mode emits no attributes" "0" "$(attr_count)"
|
||||||
|
|
||||||
# The vox preset implies VOX when the variable is unset, but word mode still wins.
|
# The vox preset implies VOX when the variable is unset, but word mode still wins.
|
||||||
@@ -139,7 +139,7 @@ check "stream has a gap per word break" "2" \
|
|||||||
|
|
||||||
# The Half-Life voices carry their profile preset.
|
# The Half-Life voices carry their profile preset.
|
||||||
VESKSLOP_TTS_VOICE=vox VESKSLOP_TTS_VOX=1 run_wrapper
|
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 writes the VOX profile (both hives)" "2" "$(grep -c '"Pitch"=dword:00000000' "$STUB_REG")"
|
||||||
check "vox voice runs VOX prosody" "1" "$(grep -c 'wH1' "$STUB_LOG")"
|
check "vox voice runs VOX prosody" "1" "$(grep -c 'wH1' "$STUB_LOG")"
|
||||||
VESKSLOP_TTS_VOICE=hev run_wrapper
|
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 writes the HEV profile (both hives)" "2" "$(grep -c '"Pitch"=dword:0000000f' "$STUB_REG")"
|
||||||
|
|||||||
+173
-11
@@ -16,13 +16,24 @@
|
|||||||
# 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_ECHO_DELAY_MS echo delay 10-400 (default 60)
|
||||||
|
# VESKSLOP_TTS_ECHO_LEVEL echo repeat level 0-0.95, relative to the
|
||||||
|
# dry voice (default 0.85)
|
||||||
|
# VESKSLOP_TTS_CRUNCH off | hl90 | hl90hq | telephone | adpcm | crush
|
||||||
|
# (1990s lossy character, applied last)
|
||||||
|
# VESKSLOP_TTS_CRUNCH_BITS 1-16, overrides the preset bit depth
|
||||||
|
# VESKSLOP_TTS_CRUNCH_RATE 4000-48000 Hz, overrides the preset rate
|
||||||
|
# VESKSLOP_TTS_STREAM 1 = word mode writes the WAV as it goes
|
||||||
|
# VESKSLOP_TTS_STREAM_GAIN_DB fixed gain for streaming (no measurement)
|
||||||
|
# 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_VOX 1 = VOX prosody, NetTTS-style (Flextalk)
|
# VESKSLOP_TTS_VOX 1 = VOX prosody, NetTTS-style (Flextalk)
|
||||||
# VESKSLOP_TTS_VOX_CLEAN 1 = VOX without the \!wH1/\!wH0 stutter
|
# VESKSLOP_TTS_VOX_CLEAN 1 = VOX without the \!wH1/\!wH0 stutter
|
||||||
# VESKSLOP_TTS_HUM_MS leading low hum in ms, 0 = off (default 80)
|
# VESKSLOP_TTS_HUM_MS leading low hum in ms, 0 = off (default 80)
|
||||||
# VESKSLOP_TTS_HUM_HZ hum frequency, 30-200 (default 75)
|
# VESKSLOP_TTS_HUM_HZ hum frequency, 30-200 (default 75)
|
||||||
# VESKSLOP_TTS_HUM_DB hum peak level, -80..-6 (default -48)
|
# VESKSLOP_TTS_HUM_DB hum peak level, -80..-6 (default -48)
|
||||||
|
# voice `vox`/`hev` Half-Life profiles as first-class voices
|
||||||
# VESKSLOP_TTS_PRESET vox | hev (Half-Life profiles, Keith Bell;
|
# VESKSLOP_TTS_PRESET vox | hev (Half-Life profiles, Keith Bell;
|
||||||
# vox also enables VOX prosody)
|
# vox also enables VOX prosody)
|
||||||
#
|
#
|
||||||
@@ -33,11 +44,21 @@ HERE=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
|
|||||||
. "$HERE/config.sh"
|
. "$HERE/config.sh"
|
||||||
ROOT=$MICROSLOP_ROOT
|
ROOT=$MICROSLOP_ROOT
|
||||||
|
|
||||||
|
# The Keith Bell defaults follow the active preset so the UI's "default" snap points
|
||||||
|
# match the values the preset actually writes.
|
||||||
|
VOX_DEFAULTS="pitch:0:rate:15:prominences:22:head_size:34:lowering:50:expression:59"
|
||||||
|
HEV_DEFAULTS="pitch:15:rate:24:prominences:22:head_size:52:lowering:40:expression:59"
|
||||||
|
case "${VESKSLOP_TTS_PRESET:-}" in
|
||||||
|
vox) KB_DEFAULTS="$VOX_DEFAULTS" ;;
|
||||||
|
hev) KB_DEFAULTS="$HEV_DEFAULTS" ;;
|
||||||
|
*) KB_DEFAULTS="pitch:27:rate:33:prominences:17:head_size:46:lowering:40:expression:59" ;;
|
||||||
|
esac
|
||||||
|
|
||||||
# VESKSLOP_TTS_VOICES entries: id|Name|gender|language[|flags]
|
# VESKSLOP_TTS_VOICES entries: id|Name|gender|language[|flags]
|
||||||
# `expression=NN` marks a Flextalk voice with an Expression profile parameter and
|
# `expression=NN` marks a Flextalk voice with an Expression profile parameter and
|
||||||
# its registry default. Expression is applied at voice load, so the wrapper writes
|
# its registry default. Expression is applied at voice load, so the wrapper writes
|
||||||
# it into the Wine registry before synthesis.
|
# it into the Wine registry before synthesis.
|
||||||
VOICES="sam|Sam|male|en,mary|Mary|female|en,mike|Mike|male|en,keith-bell|Keith Bell|male|en|expression=59+defaults=pitch:27:rate:33:prominences:17:head_size:46:lowering:40:expression:59,kerry-watson|Kerry Watson|female|en|expression=100+defaults=pitch:65:rate:32:prominences:17:head_size:45:lowering:40:expression:100,mike-telephone|Mike (for Telephone)|male|en,mary-telephone|Mary (for Telephone)|female|en,mike-hall|Mike in Hall|male|en,mike-stadium|Mike in Stadium|male|en,mike-space|Mike in Space|male|en,mary-hall|Mary in Hall|female|en,mary-stadium|Mary in Stadium|female|en,mary-space|Mary in Space|female|en"
|
VOICES="sam|Sam|male|en,mary|Mary|female|en,mike|Mike|male|en,keith-bell|Keith Bell|male|en|expression=59+defaults=$KB_DEFAULTS,vox|VOX (Half-Life)|male|en|expression=59+defaults=$VOX_DEFAULTS,hev|HEV suit|male|en|expression=59+defaults=$HEV_DEFAULTS,kerry-watson|Kerry Watson|female|en|expression=100+defaults=pitch:65:rate:32:prominences:17:head_size:45:lowering:40:expression:100,mike-telephone|Mike (for Telephone)|male|en,mary-telephone|Mary (for Telephone)|female|en,mike-hall|Mike in Hall|male|en,mike-stadium|Mike in Stadium|male|en,mike-space|Mike in Space|male|en,mary-hall|Mary in Hall|female|en,mary-stadium|Mary in Stadium|female|en,mary-space|Mary in Space|female|en"
|
||||||
|
|
||||||
if [ "${1:-}" = "--voices" ]; then
|
if [ "${1:-}" = "--voices" ]; then
|
||||||
echo "$VOICES"
|
echo "$VOICES"
|
||||||
@@ -53,11 +74,25 @@ EXPRESSION=${VESKSLOP_TTS_EXPRESSION:-${6:-}}
|
|||||||
PROFILE=${VESKSLOP_TTS_PROFILE:-}
|
PROFILE=${VESKSLOP_TTS_PROFILE:-}
|
||||||
HIGHPASS=${VESKSLOP_TTS_HIGHPASS:-40}
|
HIGHPASS=${VESKSLOP_TTS_HIGHPASS:-40}
|
||||||
ECHO=${VESKSLOP_TTS_ECHO:-0}
|
ECHO=${VESKSLOP_TTS_ECHO:-0}
|
||||||
|
ECHO_DELAY=${VESKSLOP_TTS_ECHO_DELAY_MS:-60}
|
||||||
|
ECHO_LEVEL=${VESKSLOP_TTS_ECHO_LEVEL:-0.85}
|
||||||
|
CRUNCH=${VESKSLOP_TTS_CRUNCH:-off}
|
||||||
|
CRUNCH_BITS=${VESKSLOP_TTS_CRUNCH_BITS:-}
|
||||||
|
CRUNCH_RATE=${VESKSLOP_TTS_CRUNCH_RATE:-}
|
||||||
|
STREAM=${VESKSLOP_TTS_STREAM:-0}
|
||||||
|
STREAM_GAIN=${VESKSLOP_TTS_STREAM_GAIN_DB:-0}
|
||||||
WORDS=${VESKSLOP_TTS_WORDS:-0}
|
WORDS=${VESKSLOP_TTS_WORDS:-0}
|
||||||
WORD_GAP=${VESKSLOP_TTS_WORD_GAP_MS:-120}
|
WORD_GAP=${VESKSLOP_TTS_WORD_GAP_MS:-120}
|
||||||
VOX=${VESKSLOP_TTS_VOX:-0}
|
VOX=${VESKSLOP_TTS_VOX:-0}
|
||||||
VOX_CLEAN=${VESKSLOP_TTS_VOX_CLEAN:-0}
|
VOX_CLEAN=${VESKSLOP_TTS_VOX_CLEAN:-0}
|
||||||
PRESET=${VESKSLOP_TTS_PRESET:-}
|
PRESET=${VESKSLOP_TTS_PRESET:-}
|
||||||
|
|
||||||
|
# The Half-Life voices are Keith Bell with a fixed profile preset; selecting one
|
||||||
|
# stands in for VESKSLOP_TTS_PRESET.
|
||||||
|
case "$VOICE" in
|
||||||
|
vox) [ -n "$PRESET" ] || PRESET=vox ;;
|
||||||
|
hev) [ -n "$PRESET" ] || PRESET=hev ;;
|
||||||
|
esac
|
||||||
# The VOX preset is the Half-Life announcement voice: it implies VOX prosody
|
# The VOX preset is the Half-Life announcement voice: it implies VOX prosody
|
||||||
# unless the caller explicitly disables it.
|
# unless the caller explicitly disables it.
|
||||||
if [ "$PRESET" = "vox" ] && [ -z "${VESKSLOP_TTS_VOX:-}" ]; then
|
if [ "$PRESET" = "vox" ] && [ -z "${VESKSLOP_TTS_VOX:-}" ]; then
|
||||||
@@ -72,6 +107,8 @@ case "$VOICE" in
|
|||||||
mary) ENGVOICE="Mary" ;;
|
mary) ENGVOICE="Mary" ;;
|
||||||
mike) ENGVOICE="Mike" ;;
|
mike) ENGVOICE="Mike" ;;
|
||||||
keith-bell) ENGVOICE="Keith Bell" ;;
|
keith-bell) ENGVOICE="Keith Bell" ;;
|
||||||
|
vox) ENGVOICE="Keith Bell" ;;
|
||||||
|
hev) ENGVOICE="Keith Bell" ;;
|
||||||
kerry-watson) ENGVOICE="Kerry Watson" ;;
|
kerry-watson) ENGVOICE="Kerry Watson" ;;
|
||||||
mike-telephone) ENGVOICE="Mike (for Telephone)" ;;
|
mike-telephone) ENGVOICE="Mike (for Telephone)" ;;
|
||||||
mary-telephone) ENGVOICE="Mary (for Telephone)" ;;
|
mary-telephone) ENGVOICE="Mary (for Telephone)" ;;
|
||||||
@@ -350,9 +387,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: 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"
|
||||||
@@ -399,10 +438,19 @@ try_run_host() {
|
|||||||
exit 1
|
exit 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Streaming word mode writes each word as it is synthesized (see below), after the
|
||||||
|
# filter chain exists, so the classic all-at-once path is skipped for it.
|
||||||
|
STREAM_WORDS=0
|
||||||
|
if [ "$STREAM" = "1" ] && [ "$WORDS" = "1" ] && [ -n "$SPEAKER_GUID" ]; then
|
||||||
|
STREAM_WORDS=1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$STREAM_WORDS" != "1" ]; then
|
||||||
# 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
|
||||||
@@ -432,6 +480,7 @@ else
|
|||||||
try_run_host "$RAW" "$TAG_PREFIX$(cat "$TXT")"
|
try_run_host "$RAW" "$TAG_PREFIX$(cat "$TXT")"
|
||||||
fi
|
fi
|
||||||
[ -s "$RAW" ] || { echo "engine produced no audio" >&2; exit 1; }
|
[ -s "$RAW" ] || { echo "engine produced no audio" >&2; exit 1; }
|
||||||
|
fi
|
||||||
|
|
||||||
# Filter chain: adjustable high-pass, silence trim, fades, optional mono echo.
|
# Filter chain: adjustable high-pass, silence trim, fades, optional mono echo.
|
||||||
case "$HIGHPASS" in *[!0-9]*|"") HIGHPASS=40 ;; esac
|
case "$HIGHPASS" in *[!0-9]*|"") HIGHPASS=40 ;; esac
|
||||||
@@ -441,17 +490,65 @@ case "$HIGHPASS" in *[!0-9]*|"") HIGHPASS=40 ;; esac
|
|||||||
case "$ECHO" in *[!0-9]*|"") ECHO=0 ;; esac
|
case "$ECHO" in *[!0-9]*|"") ECHO=0 ;; esac
|
||||||
[ "$ECHO" -gt 100 ] && ECHO=100
|
[ "$ECHO" -gt 100 ] && ECHO=100
|
||||||
|
|
||||||
|
case "$ECHO_DELAY" in *[!0-9]*|"") ECHO_DELAY=60 ;; esac
|
||||||
|
[ "$ECHO_DELAY" -lt 10 ] && ECHO_DELAY=10
|
||||||
|
[ "$ECHO_DELAY" -gt 400 ] && ECHO_DELAY=400
|
||||||
|
case "$ECHO_LEVEL" in *[!0-9.]*|""|".") ECHO_LEVEL=0.85 ;; esac
|
||||||
|
ECHO_LEVEL=$(awk -v v="$ECHO_LEVEL" 'BEGIN{ if (v<0) v=0; if (v>0.95) v=0.95; printf "%.2f", v }')
|
||||||
|
|
||||||
SIL="silenceremove=start_periods=1:start_duration=0.05:start_silence=0.03:start_threshold=-50dB"
|
SIL="silenceremove=start_periods=1:start_duration=0.05:start_silence=0.03:start_threshold=-50dB"
|
||||||
PRE="highpass=f=$HIGHPASS,$SIL,afade=t=in:st=0:d=0.008,areverse,$SIL,afade=t=in:st=0:d=0.008,areverse"
|
PRE="highpass=f=$HIGHPASS,$SIL,afade=t=in:st=0:d=0.008,areverse,$SIL,afade=t=in:st=0:d=0.008,areverse"
|
||||||
|
# aecho's in_gain/out_gain scale the dry signal as well, so a low ECHO_LEVEL used to
|
||||||
|
# quiet the whole line (and the streaming path, which skips the loudness measurement,
|
||||||
|
# had nothing to make it up). Keep both gains at unity and let the repeats' decay carry
|
||||||
|
# the level; the limiter absorbs the summed peaks.
|
||||||
if [ "$ECHO" -gt 0 ]; then
|
if [ "$ECHO" -gt 0 ]; then
|
||||||
DECAY=$(awk -v e="$ECHO" 'BEGIN{printf "%.2f", 0.2 + e * 0.006}')
|
DECAY=$(awk -v e="$ECHO" -v l="$ECHO_LEVEL" 'BEGIN{
|
||||||
PRE="$PRE,aecho=0.8:0.85:60:${DECAY}"
|
d = (0.2 + e * 0.006) * (l / 0.85);
|
||||||
|
if (d < 0.02) d = 0.02;
|
||||||
|
if (d > 0.95) d = 0.95;
|
||||||
|
printf "%.2f", d
|
||||||
|
}')
|
||||||
|
PRE="$PRE,aecho=1:1:${ECHO_DELAY}:${DECAY},alimiter=limit=0.98:level=disabled"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Compression stage ("crunch"): the lossy 1990s codec character, built as its own
|
||||||
|
# stage that runs after the rest of the chain so it colours the echo tails too. The
|
||||||
|
# signal is quantized at a low rate / bit depth and comes back to 48 kHz so the
|
||||||
|
# daemon's output contract holds.
|
||||||
|
CRUNCH_FILTERS=""
|
||||||
|
case "$CRUNCH" in
|
||||||
|
hl90|vox90) CRUNCH_BAND="highpass=f=200,lowpass=f=5000"; CRUNCH_RATE=${CRUNCH_RATE:-11025}; CRUNCH_BITS=${CRUNCH_BITS:-8} ;;
|
||||||
|
hl90hq) CRUNCH_BAND="highpass=f=200,lowpass=f=7000"; CRUNCH_RATE=${CRUNCH_RATE:-22050}; CRUNCH_BITS=${CRUNCH_BITS:-8} ;;
|
||||||
|
telephone|phone) CRUNCH_BAND="highpass=f=300,lowpass=f=3400"; CRUNCH_RATE=${CRUNCH_RATE:-8000}; CRUNCH_BITS=${CRUNCH_BITS:-8} ;;
|
||||||
|
adpcm) CRUNCH_BAND="highpass=f=200,lowpass=f=5000"; CRUNCH_RATE=${CRUNCH_RATE:-11025}; CRUNCH_BITS=${CRUNCH_BITS:-4} ;;
|
||||||
|
crush|custom) CRUNCH_BAND="${VESKSLOP_TTS_CRUNCH_BAND:-}"; CRUNCH_RATE=${CRUNCH_RATE:-22050}; CRUNCH_BITS=${CRUNCH_BITS:-6} ;;
|
||||||
|
off|"") ;;
|
||||||
|
*) echo "unknown crunch preset: $CRUNCH" >&2; exit 2 ;;
|
||||||
|
esac
|
||||||
|
if [ "$CRUNCH" != "off" ] && [ -n "$CRUNCH" ]; then
|
||||||
|
case "$CRUNCH_RATE" in *[!0-9]*|"") CRUNCH_RATE=22050 ;; esac
|
||||||
|
[ "$CRUNCH_RATE" -lt 4000 ] && CRUNCH_RATE=4000
|
||||||
|
[ "$CRUNCH_RATE" -gt 48000 ] && CRUNCH_RATE=48000
|
||||||
|
case "$CRUNCH_BITS" in *[!0-9]*|"") CRUNCH_BITS=8 ;; esac
|
||||||
|
[ "$CRUNCH_BITS" -lt 1 ] && CRUNCH_BITS=1
|
||||||
|
[ "$CRUNCH_BITS" -gt 16 ] && CRUNCH_BITS=16
|
||||||
|
[ -n "$CRUNCH_BAND" ] && CRUNCH_FILTERS="$CRUNCH_BAND,"
|
||||||
|
CRUNCH_FILTERS="${CRUNCH_FILTERS}aresample=$CRUNCH_RATE"
|
||||||
|
if [ "$CRUNCH_BITS" = "8" ]; then
|
||||||
|
CRUNCH_FILTERS="$CRUNCH_FILTERS,aformat=sample_fmts=u8"
|
||||||
|
else
|
||||||
|
CRUNCH_FILTERS="$CRUNCH_FILTERS,acrusher=bits=$CRUNCH_BITS:mode=log:mix=1"
|
||||||
|
fi
|
||||||
|
CRUNCH_FILTERS="$CRUNCH_FILTERS,aresample=48000,aformat=sample_fmts=s16"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Fixed-gain normalization: single-pass loudnorm misbehaves on short lines
|
# Fixed-gain normalization: single-pass loudnorm misbehaves on short lines
|
||||||
# (LRA=0 causes heavy attenuation), so measure EBU R128 integrated loudness and
|
# (LRA=0 causes heavy attenuation), so measure EBU R128 integrated loudness and
|
||||||
# true peak, then apply one gain clamped by the -3 dBFS peak target.
|
# true peak, then apply one gain clamped by the -3 dBFS peak target.
|
||||||
M=$("$MICROSLOP_FFMPEG" -hide_banner -nostats -i "$RAW" -af "$PRE,ebur128=peak=true" \
|
MEASURE_CHAIN="$PRE"
|
||||||
|
[ -n "$CRUNCH_FILTERS" ] && MEASURE_CHAIN="$PRE,$CRUNCH_FILTERS"
|
||||||
|
M=$("$MICROSLOP_FFMPEG" -hide_banner -nostats -i "$RAW" -af "$MEASURE_CHAIN,ebur128=peak=true" \
|
||||||
-f null - 2>&1 || true)
|
-f null - 2>&1 || true)
|
||||||
I=$(printf '%s' "$M" | sed -n 's/ *I: *\(-\{0,1\}[0-9.]*\) *LUFS.*/\1/p' | tail -1)
|
I=$(printf '%s' "$M" | sed -n 's/ *I: *\(-\{0,1\}[0-9.]*\) *LUFS.*/\1/p' | tail -1)
|
||||||
TP=$(printf '%s' "$M" | sed -n 's/ *Peak: *\(-\{0,1\}[0-9.]*\) *dBFS.*/\1/p' | tail -1)
|
TP=$(printf '%s' "$M" | sed -n 's/ *Peak: *\(-\{0,1\}[0-9.]*\) *dBFS.*/\1/p' | tail -1)
|
||||||
@@ -468,6 +565,71 @@ else
|
|||||||
GAIN=0
|
GAIN=0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# Streaming word mode: write a WAV header with unknown sizes, then append each
|
||||||
|
# word's PCM as it is ready, so a player can start on the first word instead of
|
||||||
|
# waiting for the whole line. The loudness measurement is skipped; set
|
||||||
|
# VESKSLOP_TTS_STREAM_GAIN_DB if the engine levels need a nudge.
|
||||||
|
if [ "$STREAM_WORDS" = "1" ]; then
|
||||||
|
SGAIN=""
|
||||||
|
case "$STREAM_GAIN" in
|
||||||
|
''|*[!0-9.-]*|'.'|'-') ;;
|
||||||
|
*) SGAIN=",volume=${STREAM_GAIN}dB" ;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
# RIFF/????/WAVE, fmt 16 bytes, PCM mono 48000 Hz s16le, data/????
|
||||||
|
printf 'RIFF\377\377\377\377WAVEfmt \020\000\000\000\001\000\001\000\200\273\000\000\000w\001\000\002\000\020\000data\377\377\377\377' > "$OUT" \
|
||||||
|
|| { echo "could not write $OUT" >&2; exit 1; }
|
||||||
|
|
||||||
|
set -f
|
||||||
|
# shellcheck disable=SC2046
|
||||||
|
set -- $(cat "$TXT")
|
||||||
|
set +f
|
||||||
|
WORD_COUNT=$#
|
||||||
|
INDEX=0
|
||||||
|
GAP=$WORD_GAP
|
||||||
|
case "$GAP" in *[!0-9]*|"") GAP=120 ;; esac
|
||||||
|
[ "$GAP" -gt 500 ] && GAP=500
|
||||||
|
|
||||||
|
# Leading hum, written once before the first word (same settings as the classic path).
|
||||||
|
if [ "$HUM_MS" -gt 0 ]; then
|
||||||
|
HUM_S=$(awk -v ms="$HUM_MS" 'BEGIN{printf "%.3f", ms/1000}')
|
||||||
|
HUM_HZ_C=$(awk -v v="$HUM_HZ" 'BEGIN{ if (v<30) v=30; if (v>200) v=200; printf "%.1f", v }')
|
||||||
|
HUM_DB_C=$(awk -v v="$HUM_DB" 'BEGIN{ if (v>-6) v=-6; if (v<-80) v=-80; printf "%.1f", v }')
|
||||||
|
HUM_AMP_C=$(awk -v db="$HUM_DB_C" 'BEGIN{printf "%.6f", 10^(db/20)}')
|
||||||
|
if "$MICROSLOP_FFMPEG" -hide_banner -loglevel error -f lavfi \
|
||||||
|
-i "aevalsrc=exprs='$HUM_AMP_C*sin(2*PI*$HUM_HZ_C*t)':s=48000:d=$HUM_S" \
|
||||||
|
-f s16le -ac 1 -ar 48000 "$TMP/hum.raw"; then
|
||||||
|
cat "$TMP/hum.raw" >> "$OUT"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
for word in "$@"; do
|
||||||
|
INDEX=$((INDEX + 1))
|
||||||
|
try_run_host "$TMP/sw$INDEX.wav" "${TAG_PREFIX}${word}"
|
||||||
|
if ! "$MICROSLOP_FFMPEG" -hide_banner -loglevel error -y -i "$TMP/sw$INDEX.wav" \
|
||||||
|
-af "$PRE$SGAIN${CRUNCH_FILTERS:+,$CRUNCH_FILTERS}" \
|
||||||
|
-f s16le -ac 1 -ar 48000 "$TMP/sw$INDEX.raw"; then
|
||||||
|
echo "streaming post-processing failed" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
cat "$TMP/sw$INDEX.raw" >> "$OUT" 2>/dev/null || { echo "stream write failed" >&2; exit 1; }
|
||||||
|
|
||||||
|
# The player waits for this marker so it does not start on the bare header.
|
||||||
|
if [ "$INDEX" = "1" ]; then : > "$OUT.ready"; fi
|
||||||
|
|
||||||
|
if [ "$GAP" -gt 0 ] && [ "$INDEX" -lt "$WORD_COUNT" ]; then
|
||||||
|
if "$MICROSLOP_FFMPEG" -hide_banner -loglevel error -y -f lavfi -i "anullsrc=r=48000:cl=mono" \
|
||||||
|
-t "$(awk -v g="$GAP" 'BEGIN{printf "%.3f", g/1000}')" \
|
||||||
|
-f s16le -ac 1 -ar 48000 "$TMP/gap.raw"; then
|
||||||
|
cat "$TMP/gap.raw" >> "$OUT"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
[ "$INDEX" -gt 0 ] || { echo "no words to stream" >&2; exit 1; }
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
TMPOUT="$TMP/out.wav"
|
TMPOUT="$TMP/out.wav"
|
||||||
# Leading hum: a quiet low tone in the pre-speech padding, to prime Discord's
|
# Leading hum: a quiet low tone in the pre-speech padding, to prime Discord's
|
||||||
# audio pipeline so the first syllable is not gated. Level/frequency/duration
|
# audio pipeline so the first syllable is not gated. Level/frequency/duration
|
||||||
@@ -488,12 +650,12 @@ if [ "$HUM_MS" -gt 0 ]; then
|
|||||||
if ! "$MICROSLOP_FFMPEG" -hide_banner -nostats -y \
|
if ! "$MICROSLOP_FFMPEG" -hide_banner -nostats -y \
|
||||||
-f lavfi -i "aevalsrc=exprs='$HUM_AMP*sin(2*PI*$HUM_HZ*t)':s=48000:d=$HUM_S" \
|
-f lavfi -i "aevalsrc=exprs='$HUM_AMP*sin(2*PI*$HUM_HZ*t)':s=48000:d=$HUM_S" \
|
||||||
-i "$RAW" \
|
-i "$RAW" \
|
||||||
-filter_complex "[0:a]afade=t=in:st=0:d=$HUM_FADE,afade=t=out:st=$HUM_FADE_OUT:d=$HUM_FADE,aformat=sample_fmts=s16:channel_layouts=mono:sample_rates=48000[h];[1:a]$PRE,volume=${GAIN}dB,aresample=48000,aformat=sample_fmts=s16:channel_layouts=mono[a];[h][a]concat=n=2:v=0:a=1[out]" \
|
-filter_complex "[0:a]afade=t=in:st=0:d=$HUM_FADE,afade=t=out:st=$HUM_FADE_OUT:d=$HUM_FADE,aformat=sample_fmts=s16:channel_layouts=mono:sample_rates=48000[h];[1:a]$PRE,volume=${GAIN}dB,${CRUNCH_FILTERS:+$CRUNCH_FILTERS,}aresample=48000,aformat=sample_fmts=s16:channel_layouts=mono[a];[h][a]concat=n=2:v=0:a=1[out]" \
|
||||||
-map "[out]" -c:a pcm_s16le "$TMPOUT" >/dev/null 2>"$TMP/ff"; then
|
-map "[out]" -c:a pcm_s16le "$TMPOUT" >/dev/null 2>"$TMP/ff"; then
|
||||||
head -c 400 "$TMP/ff" >&2
|
head -c 400 "$TMP/ff" >&2
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
elif ! "$MICROSLOP_FFMPEG" -hide_banner -nostats -y -i "$RAW" -af "$PRE,volume=${GAIN}dB" \
|
elif ! "$MICROSLOP_FFMPEG" -hide_banner -nostats -y -i "$RAW" -af "$PRE,volume=${GAIN}dB,${CRUNCH_FILTERS:+$CRUNCH_FILTERS,}aresample=48000,aformat=sample_fmts=s16" \
|
||||||
-ar 48000 -ac 1 -c:a pcm_s16le "$TMPOUT" >/dev/null 2>"$TMP/ff"; then
|
-ar 48000 -ac 1 -c:a pcm_s16le "$TMPOUT" >/dev/null 2>"$TMP/ff"; then
|
||||||
head -c 400 "$TMP/ff" >&2
|
head -c 400 "$TMP/ff" >&2
|
||||||
exit 1
|
exit 1
|
||||||
|
|||||||
Reference in New Issue
Block a user