Merge the MicroslopSam engine into the repository

This commit is contained in:
Fisto
2026-09-21 12:16:33 +02:00
parent 51d42b3a16
commit bbb580deea
30 changed files with 8959 additions and 82 deletions
+36 -2
View File
@@ -47,8 +47,32 @@ From `msttsl/mstts.inf` (`[TTSReg]`):
`.COD` codebook, plus shared `DICT_CI.*`, `phondef`, `AMPRULES`, `ENDINGS`.
Extraction: `unshield -O -d extracted x installer/flextalk/data1.cab`
(`-O` = old compression; the archive.org package has no `data2.cab`, so the
final file `WatsonUNInstall.dll` is incomplete).
(`-O` = old compression). The package is single-volume: the original 1997 upload log
(`WS_FTP.LOG`) lists `data1.cab` only, and `-O` extracts all 450 entries with exact
sizes. Without `-O`, unshield's new decompressor reports a phantom "volume 2" and fails
on `MSVCRT.DLL` / `WatsonUNInstall.dll`; with `-O` both are complete, valid PE files.
The `.ENV` files the engine probes (`ae1m11lc.ENV`) were never shipped; synthesis works
without them.
## Latency and output shape (measured 2026-09-16)
- Warm wrapper time: ~410-425 ms per short line (engine call 190-220 ms, ffmpeg measure
~110 ms, apply ~90 ms, profile write ~57 ms when it runs). First (cold) call ~730 ms.
- The Flextalk profile write is cached per voice in the Wine prefix
(`~/.wine-microslopsam/.microslopsam-profile<GUID>.reg`): unchanged profiles skip
`wine reg import`. Recreating the prefix invalidates the cache.
- Fades: the 8 ms fade-in/out is applied to the trimmed file whose 25-30 ms padding
silence is retained, so it shapes silence, not speech. Measured head: first sample
>30 at 22.6 ms, speech (>500) at 29.6 ms, natural ~15 ms attack; tail decays over
~18 ms. No click, no attenuation of the first word.
- Leading hum (default on): 80 ms of 75 Hz at -48 dBFS peak before the engine padding,
to prime Discord's pipeline. Verified 75.0 Hz / -48.0 dBFS, speech onset ~110 ms,
final -18.6 LUFS / -3.0 dBFS, apply pass +~10 ms. `VESKSLOP_TTS_HUM_MS=0` disables and
reproduces the previous output byte-for-byte. Tuning: `_HUM_HZ` (30-200), `_HUM_DB`
(-80..-6; raise toward -36 if a client still gates the start).
The hum bypasses the high-pass/trim chain, so `VESKSLOP_TTS_HIGHPASS` (up to 400 Hz)
cannot remove it (verified at 200 and 400 Hz).
## SAPI4 runtime engine discovery (`speech.dll`)
@@ -105,6 +129,16 @@ final file `WatsonUNInstall.dll` is incomplete).
- 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
over `VESKSLOP_TTS_WORDS`.
- Half-Life presets (`VESKSLOP_TTS_PRESET=vox|hev`) target Keith Bell only. VOX values
come from NetTTS `nettts_flextalk_vox_profile.reg`: pitch 3, rate 17, prominences 22,
head size 52, volume 70 (factory: 27/33/17/46/100); `vox` also implies VOX prosody.
`hev` (pitch 15, rate 24) is an unverified starting point - HL1 credits the suit
sounds to Kelly Bailey with no published settings.
- Flextalk rate/pitch are applied as **vendor tags** (`\!R` = duration factor,
`\!%` = pitch multiplier), never `-s`/`-p`, because ITTSAttributes override the
registry profile. Neutral values emit no tag, so Veskslop's default sliders
(1.0 / 0) reproduce the preset byte-for-byte. Presets write the full 26-value NetTTS
profile plus `Default Speaker` into both HKCU and HKLM.
- 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.
+121 -7
View File
@@ -229,6 +229,22 @@ record what the engine needs to interpret, add, or hide._
- Verified: Keith Bell -18.4 LUFS, Kerry Watson -17.9 LUFS, Sam -20.6 LUFS; every file is
48 kHz mono s16le at -3.0 dBFS true peak. The WAV handed to Veskslop is compliant; the
"Veskslop does not normalize" rule holds.
- Fades: the 8 ms fade-in/out is applied to the trimmed file, and the retained 25-30 ms
padding silence sits at the edges, so the fade shapes silence, not the first phoneme.
Measured on a Keith Bell line: first sample >30 at 22.6 ms, speech (>500) at 29.6 ms,
attack rising naturally over about 15 ms, tail decaying over about 18 ms. No click and
no attenuation of the first word; Veskslop's 150 ms playback lead-in sits on top.
- Leading hum (on by default): 80 ms of a 75 Hz sine at -48 dBFS peak is prepended
before the engine padding, to keep Discord's audio pipeline open so the first word is
not gated. Configurable: `VESKSLOP_TTS_HUM_MS` (0 disables), `_HUM_HZ` (30-200),
`_HUM_DB` (-80..-6). It sits about 45 dB under the speech peak; raise `_HUM_DB` toward
-36 if a client still gates the start, lower it for stealth. Generated in the same
ffmpeg pass (no extra process) and not part of the EBU measurement, so the documented
loudness/peak targets still hold. The hum is mixed on its own chain and bypasses the
high-pass/trim chain, so raising `VESKSLOP_TTS_HIGHPASS` (40-400 Hz) never removes it
(verified at 200 and 400 Hz: still 75.0 Hz / -48.0 dBFS). Measured: hum 75.0 Hz /
-48.0 dBFS, speech onset at about 110 ms, final -18.6 LUFS / -3.0 dBFS; `HUM_MS=0`
reproduces the previous output byte-for-byte.
### Voice IDs
@@ -371,6 +387,94 @@ Other NetTTS features and their relevance:
our prefix runs the engines with builtin Wine libraries.
- Win32 GUI and preset sliders: Veskslop provides the UI.
### Half-Life VOX / HEV presets
The Half-Life announcement voice (VOX / Black Mesa Announcement System) is the Flextalk
engine's **Keith Bell** voice with an adjusted speaker profile. NetTTS publishes an
approximation of Valve's settings (`scripts/winetricks/nettts_flextalk_vox_profile.reg`,
see its issues #53/#54/#67): the profile targets Keith Bell's speaker key and leaves
Kerry Watson at factory values. The wrapper implements it as a preset:
VESKSLOP_TTS_PRESET=vox # implies VOX prosody unless VESKSLOP_TTS_VOX is set
VESKSLOP_TTS_PRESET=hev # HEV suit starting point (see note below)
| Profile value | Factory (Keith) | VOX preset | HEV preset (approx.) |
| --- | --- | --- | --- |
| Pitch | 27 | 3 | 15 |
| Speaking Rate | 33 | 17 | 24 |
| Prominences | 17 | 22 | 22 |
| Head Size | 46 | 52 | 52 |
| Lowering | 40 | 40 | 40 |
| Volume | 100 | 70 | 70 |
| Expression | 59 | 59 | 59 |
- Presets apply only to `keith-bell`; every other voice keeps its defaults.
- Precedence: preset < `VESKSLOP_TTS_PROFILE` < `VESKSLOP_TTS_EXPRESSION`.
- Equivalent explicit override:
`VESKSLOP_TTS_PROFILE='pitch:3,rate:17,volume:70,prominences:22,head_size:52,lowering:40'`.
- HEV suit: Half-Life 1 credits the suit sounds to Kelly Bailey; no settings were ever
published. The `hev` preset is a starting point (less extreme than VOX); tune pitch and
rate to taste. VOX prosody is optional there (`VESKSLOP_TTS_VOX=1`).
#### How NetTTS applies the settings (reference behaviour)
Checked against the NetTTS sources (`src/main.cpp`, `src/tts_engine.cpp`,
`scripts/winetricks/nettts_flextalk_vox_profile.reg`):
1. **Voice**: the imported profile points `Default Speaker` at Keith Bell's GUID, and
NetTTS selects the default mode (empty `TTSMODEINFO`) rather than a voice name.
2. **Speaker profile**: the full key set is imported into **both** hives
(`HKEY_CURRENT_USER` and `HKEY_LOCAL_MACHINE`) at prefix setup; every numeric value is
`REG_DWORD`.
3. **Rate and pitch are never set through `ITTSAttributes`.** NetTTS keeps UI percentages
and prepends sticky vendor tags to the utterance text:
- `\!R<factor>` = duration factor. Smaller is faster: `0.30` very fast, `1.00` 1.0x,
`1.60` slow. No tag at 100%.
- `\!%<factor>` = pitch multiplier. `0.85` lower, `1.20` higher. No tag at 100%.
4. **Volume** goes through `ITTSAttributes::VolumeSet(percent * 65535 / 100)` (Flextalk
scale; MSTTS uses 0..0xFFFFFFFF).
5. **Prosody**: the VOX parser output wrapped in `\!wH1 ... \!wH0`.
6. The tags are sticky inside an engine instance; NetTTS sends the neutral tag only to
reset. Our wrapper starts a fresh engine per line, so stickiness cannot leak.
#### How to implement it correctly (and the bug this fixed)
Earlier wrapper versions mapped `VESKSLOP_TTS_SPEED`/`_PITCH` to `ITTSAttributes`
`-s`/`-p`. Measured with the VOX profile loaded, Veskslop's neutral slider values
(`speed=1.0, pitch=0`) sent `-s 150 -p 106`, which overrode the registry profile: F0
jumped from 87 Hz back to 120 Hz and the line shortened from 5.83 s to 5.10 s. That is
the "too fast, not deep enough" report.
The correct implementation for Flextalk:
1. Write the speaker profile first (both hives, `REG_DWORD`, full key set for presets;
`Default Speaker` for presets).
2. Translate rate/pitch to vendor tags, **not** attributes:
- speed multiplier `m` -> `\!R(1/m)` (clamp 0.25-4.0)
- pitch semitones `st` -> `\!%(2^(st/12))` (clamp 0.5-2.0)
3. Emit **no tag when neutral** (`m == 1`, `st == 0`). `\!R1.00` is a literal 1.0x rate,
not the profile rate, so emitting it defeats the preset.
4. Prepend the tags to the text (`\!R0.77 \!%1.19 Attention all personnel.`); for word
mode, prepend to each word because every word is a separate engine instance.
5. Keep `-s`/`-p` only for MSTTS voices, which have no vendor tags.
6. Verify with F0 and duration, not by ear: factory Keith ~3.35 s / ~123 Hz; VOX preset
~5.83 s / ~87 Hz; neutral sliders must reproduce the preset byte-for-byte.
Verified matrix (same line, Keith Bell):
| Run | Duration | F0 median |
| --- | --- | --- |
| factory, no adjustments | 3.35 s | 123 Hz |
| VOX preset, no adjustments | 5.83 s | 87 Hz |
| VOX preset + `speed=1.0 pitch=0` | 5.83 s, byte-identical | 87 Hz |
| VOX preset + `speed=1.3` | 4.02 s | 84 Hz |
| VOX preset + `pitch=+3` | 5.83 s | 88 Hz |
| HEV preset, no adjustments | 3.79 s | - |
Registry inspection after a preset run: 26 values in the Keith speaker key in both
hives, `Pitch=0x3`, `Speaking Rate=0x11`, `Gender=0x2`, `Line Mode=0x1`,
`Default Speaker={11260610-C84A-11CE-AEC4-0000C0E9CB87}`.
### Inline rate/pitch controls (engine-side)
The wrapper consumes NetTTS-style, line-leading controls, so Veskslop and the WebUI do
@@ -437,8 +541,10 @@ This tree is written to survive a copy into the Veskslop repository next to its
(`VESKSLOP_TTS_*`, `{out}`, `{text_file}`, voice/speed/pitch/expression/profile/
highpass/echo/words/word_gap_ms). New features are additive and default off.
3. Optional Veskslop-side follow-up: a `tts.vox` preference that exports
`VESKSLOP_TTS_VOX` (like `words`) and a line in the TTS panel help. The inline
`/rate` and `/pitch` controls need no UI work at all.
`VESKSLOP_TTS_VOX` (like `words`) and a line in the TTS panel help. If presets are
exposed, also export `VESKSLOP_TTS_PRESET` and have the voice metadata defaults reflect
the preset values so the UI's "default" snap points match. The inline `/rate` and
`/pitch` controls need no UI work at all.
### Transport and latency
@@ -451,11 +557,15 @@ This tree is written to survive a copy into the Veskslop repository next to its
text file, transliterates UTF-8 to CP1252, starts or reuses an Xvfb when `DISPLAY` is
unset, calls the engine, then post-processes (trim, fades, measured fixed gain, 48 kHz
mono s16le). A stale private Xvfb is retried once.
- Measured on the dev host: engine-only synthesis is about 290 ms wall for Sam and about
40 ms for Flextalk, including process start; the full wrapper adds the ffmpeg
measure/apply passes, about 630-880 ms per short line. That is above the 500 ms target;
the known optimization is a per-voice static gain (skip the measurement pass), expected
to land around 400-500 ms.
- Measured on the dev host (warm): engine call about 190 ms (Flextalk) to 220 ms (Sam)
including Wine start, ffmpeg measure about 110 ms, ffmpeg apply about 90 ms, profile
registry write about 57 ms when it runs. Full wrapper: about 410-425 ms per short line
warm, about 730 ms for the first (cold) call. The 500 ms target is met warm; Veskslop's
"Start engine" warm-up covers the cold case.
- Latency optimization implemented: the Flextalk profile write is cached per voice inside
the Wine prefix (`.microslopsam-profile<GUID>.reg`); an unchanged profile skips the
`wine reg import` round-trip (about 57 ms warm, 300 ms cold). Recreating the prefix
invalidates the cache automatically.
- Manual checks: `setup/run.sh -l`, `setup/run.sh -v "Keith Bell" -t "Hello." -o out.wav`
(raw engine WAV), and the wrapper above for normalized output.
- HTTP provider (later): a persistent process returning `audio/wav` bytes and `/voices`,
@@ -465,6 +575,10 @@ This tree is written to survive a copy into the Veskslop repository next to its
- Flextalk numeric speaker settings must be `REG_DWORD`; `REG_SZ` values make the engine
synthesize a buzz. `setup/setup-flextalk.sh` writes them correctly.
- Flextalk cabinet is single-volume: `unshield -O` extracts all 450 entries with exact
sizes. Without `-O`, the new decompressor reports a phantom "volume 2" and fails on
`MSVCRT.DLL` / `WatsonUNInstall.dll`; both are complete with `-O` (verified against the
original 1997 upload log, which lists `data1.cab` only).
- The mode list includes TruVoice names with no installed voice data; filter those out of
`/voices` (or mark them `unavailable`).
- Wine needs a working X display; the project helper starts a persistent Xvfb.