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.
9.0 KiB
9.0 KiB
Notes
Registry facts
From spchapi/SPCHAPI.INF (%49000% = %25%\speech = C:\windows\speech):
CLSID_TTSEnumerator{D67C0280-C743-11cd-80E5-00AA003E4B50}->Speech.dll- "Audio Destination to File Object"
{D4623720-E4B9-11cf-8D56-00A0C9034A7E}->Speech.dll - "Audio Destination Object"
{CB96B400-C743-11cd-80E5-00AA003E4B50}->Speech.dll - Lexicon dialog
{92655FB1-ADF9-11d1-BEB9-006008317CE8}->Speech.dll - Phoneme converter
{B9F11A96-90E3-11d0-8D77-00A0C9034A7E}->Speech.dll
From msttsl/mstts.inf ([TTSReg]):
- Engine
MSTTSSyn={E0725551-286F-11d0-8E73-00A0C9083363}->MSTTSSYN.dll - Engine install dir:
Common Files\microsoft shared\SpeechEngines\TTS. - Enumerates voices by scanning
*.vcein its own directory.
Flextalk registration (from the installer's setup.ins)
- Engine value:
HKLM\Software\Voice\TextToSpeech\Engine"AT&T FlexTalk" = "{FFE78E00-C69C-11CE-AEC1-0000C0E9CB87}" - Engine subkey (forces mode enumeration):
...\Engine\{FFE78E00-...}withDirty = 1(REG_DWORD). The runtime rewritesCount+Mode0..Nand clearsDirtyafter enumerating. HKCR\CLSID\{FFE78E00-C69C-11CE-AEC1-0000C0E9CB87}\InprocServer32=<root>\EngineDLLs\FlexTalk.dll,ThreadingModel = Apartment. Also registered:{778EAAE2-499F-11CF-B0BA-444553540000}-> same DLL.- Directories:
HKLM\Software\AT&T\Watson\2.0\FlexTalk\2.0\DirectoriesRootDirectory = C:\FlexTalk\. - Dictionaries:
...\FlexTalk\2.0\Dictionaries:StandardDictionary="2 ft_sdict.dll",ExceptionDictionary="0 ft_udict.dll",HomographDictionary="1 ft_hgrap.dll",ProperNameDictionary="3 ft_namsa.dll",LetterToSoundDictionary="4 ft_l2s.dll". - Options:
...\FlexTalk\2.0\OptionsGender=Male,SampleRate=11025. - Speakers (per-voice settings; names come from these values). All numeric
values must be REG_DWORD: with REG_SZ the engine reads garbage and
synthesizes a monotone buzz (pitch fixed at max, pitch control dead).
{11260610-C84A-11ce-AEC4-0000C0E9CB87}: Mode Name / Speaker Name "Keith Bell", Pitch 27, Speaking Rate 33, Head Size 46, Expression 59, Lowering 40, Prominences 17, Age 30, Volume 100.{000031CB-3699-551F-AED0-6B4893382D57}: "Kerry Watson", Pitch 65, Speaking Rate 32, Head Size 45, Expression 100, Lowering 40, Prominences 17, Age 30, Volume 100.
- Voice data:
EngineData\AE1M08LC.*,AE1M11LC.*(male, 8/11 kHz) andAE2F08LC.*,AE2F11LC.*(female);.INVinventory,.NDXindex,.CODcodebook, plus sharedDICT_CI.*,phondef,AMPRULES,ENDINGS.
Extraction: unshield -O -d extracted x installer/flextalk/data1.cab
(-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 skipwine 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=0disables 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, soVESKSLOP_TTS_HIGHPASS(up to 400 Hz) cannot remove it (verified at 200 and 400 Hz).
SAPI4 runtime engine discovery (speech.dll)
FUN_635747a4readsHKLM\Software\Voice\TextToSpeech\Enginevalues (value name = engine name, data = CLSID string), validates the GUID withFUN_635712e4, then opens a subkey named by the CLSID string.FUN_635758b1(TTS mode-list ctor) readsDirty; when non-zero/missing it callsCoCreateInstance(CLSID, ..., IID @635791c0)and pulls modes via vtable+0xC into 0xaf0-byte records, writesMode%d(REG_BINARY),Count, then clearsDirty. WhenDirty=0it reads the cachedMode%dblobs.- Practical rule: to add an engine, register the
Enginevalue + the CLSIDInprocServer32+ an engine subkey withDirty=1(a missing subkey also triggers enumeration).
Environment gotchas
- Wine needs a working X display (the runtime creates windows).
xvfb-run -aproved flaky here;setup/run.shstarts a persistentXvfb :77instead. - Wine reports
err:ole:apartment_add_dll couldn't load in-process dllwhen theInprocServer32path does not exist. Check the file copy first (this bit us: a badunshieldrun created empty directories). - Generate registry
.regfiles withprintf, neverecho: dash'sechointerprets\2in...\Watson\2.0\...as 0x02, corrupting the key so thatwine reg importis a silent no-op (it still returns 0). The wrapper rejects a generated profile containing control characters. - 32-bit engines require a 32-bit prefix (
WINEARCH=win32). sudoon this machine requires a password; installs are manual.
Engine units (measured)
- speed: MSTTS range 30-450, Flextalk 50-390; the natural rate is about 150 for every voice (duration at 100/200/300 fits a natural value near 140-150).
- pitch: engine units, roughly linear in F0 near the natural point (~1.1 Hz per unit). Natural values: about 100 (MSTTS), 106 (Keith Bell), 186 (Kerry Watson); Flextalk max 250. Verified with the corrected DWORD registry: Kerry 0 st = 209 Hz, -6 st = 148 Hz, +6 st = 250 Hz (clamped at max); Keith 0 st = 119 Hz, +6 st = 170 Hz.
- The wrapper maps semitones as
natural * 2^(st/12)and clamps to the range. - volume: available through
ITTSAttributes(host flag-V). Flextalk is linear (0-65535, default 65535; half is about -6 dB). MSTTS is non-linear (0 = silence, 4096 about -24 dB, 65535+ full scale); treat it as on/off. - Flextalk per-speaker profile values are applied at voice load;
Expressionwas verified audible (0/59/100 produce different waveforms at equal duration). Changing them requires a registry edit and re-selecting the voice.
VOX parser
setup/vox_parser.pyreimplements the NetTTS VOX prosody encoder (vendor tags\!wH1/\!wH0,\!br,\!sfNN); the host already passesTTSDATAFLAG_TAGGED, so the engine honors them.- Validation: the reference
vox_parser.cppfrom a local clone ofh4rm0n1c/NetTTSwas compiled with g++ in a throwaway harness and diffed against the Python port; outputs are identical on the 31-line corpus. Re-check after any parser change while the clone is available. - VOX mode is Flextalk-only. The wrapper enables it with
VESKSLOP_TTS_VOX=1(VESKSLOP_TTS_VOX_CLEAN=1drops the stutter wrapper); word mode (VESKSLOP_TTS_WORDS=1) wins and skips VOX prosody. - Half-Life presets (
VESKSLOP_TTS_PRESET=vox|hev) target Keith Bell only. VOX starts from NetTTSnettts_flextalk_vox_profile.reg(pitch 3, rate 17, head 52, lowering 40) and is tuned against the game's word assets fromsuchipi/half-life-vox-console: shipped values are pitch 0, rate 15, prominences 22, head size 34, lowering 50, volume 70. Measured on 38 words vs the game assets: F0 82.3 vs 82.3 Hz, duration 22.99 vs 23.36 s, >2 kHz share 0.0172 vs 0.0186 (old approximation: 87.5 Hz / 22.28 s / 0.0137).tools/vox_compare.pyreproduces the comparison.hev(pitch 15, rate 24) remains 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 plusDefault Speakerinto both HKCU and HKLM. - Line-leading
/rate Nand/pitch Ncontrols are consumed by the wrapper (percent, 100 = normal;<= 2.0means multiplier). They override the env speed/pitch and are not spoken; a controls-only line yields 100 ms of silence.
Decompiled corpora (Ghidra)
/mnt/ghidra/decomp/msttssyn.dll/(786 functions, engine)/mnt/ghidra/decomp/speech.dll/(1030 functions, SAPI4 runtime)