Files
Veskslop-t/microslopsam/docs/NOTES.md
T

8.7 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 *.vce in 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-...} with Dirty = 1 (REG_DWORD). The runtime rewrites Count + Mode0..N and clears Dirty after 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\Directories RootDirectory = 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\Options Gender=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) and AE2F08LC.*, AE2F11LC.* (female); .INV inventory, .NDX index, .COD codebook, plus shared DICT_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 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)

  • FUN_635747a4 reads HKLM\Software\Voice\TextToSpeech\Engine values (value name = engine name, data = CLSID string), validates the GUID with FUN_635712e4, then opens a subkey named by the CLSID string.
  • FUN_635758b1 (TTS mode-list ctor) reads Dirty; when non-zero/missing it calls CoCreateInstance(CLSID, ..., IID @635791c0) and pulls modes via vtable+0xC into 0xaf0-byte records, writes Mode%d (REG_BINARY), Count, then clears Dirty. When Dirty=0 it reads the cached Mode%d blobs.
  • Practical rule: to add an engine, register the Engine value + the CLSID InprocServer32 + an engine subkey with Dirty=1 (a missing subkey also triggers enumeration).

Environment gotchas

  • Wine needs a working X display (the runtime creates windows). xvfb-run -a proved flaky here; setup/run.sh starts a persistent Xvfb :77 instead.
  • Wine reports err:ole:apartment_add_dll couldn't load in-process dll when the InprocServer32 path does not exist. Check the file copy first (this bit us: a bad unshield run created empty directories).
  • Generate registry .reg files with printf, never echo: dash's echo interprets \2 in ...\Watson\2.0\... as 0x02, corrupting the key so that wine reg import is 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).
  • sudo on 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; Expression was 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.py reimplements the NetTTS VOX prosody encoder (vendor tags \!wH1/\!wH0, \!br, \!sfNN); the host already passes TTSDATAFLAG_TAGGED, so the engine honors them.
  • Validation: the reference vox_parser.cpp from a local clone of h4rm0n1c/NetTTS was 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=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.

Decompiled corpora (Ghidra)

  • /mnt/ghidra/decomp/msttssyn.dll/ (786 functions, engine)
  • /mnt/ghidra/decomp/speech.dll/ (1030 functions, SAPI4 runtime)