Merge the MicroslopSam engine into the repository
This commit is contained in:
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user