VOX: apply rate/pitch as vendor tags (NetTTS-style); full profile in both hives; docs

This commit is contained in:
Fisto
2026-09-21 11:37:44 +02:00
parent 1cc18c812b
commit 3d82a98b69
3 changed files with 148 additions and 30 deletions
+63 -7
View File
@@ -412,14 +412,68 @@ Kerry Watson at factory values. The wrapper implements it as a preset:
- 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'`.
- The NetTTS profile also sets `Gender`, `Line Mode`, and a set of text-class DWORDs
(address/date/money classes, abbreviation expansion, and so on). Those affect text
normalization rather than the audible voice, so the wrapper does not write them.
- 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`).
- Measured with the wrapper on the same line: VOX preset 5.83 s, HEV 3.79 s, factory
3.35 s (the VOX preset is slower and gains the VOX cadence).
#### 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)
@@ -487,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