host: volume option; docs: voice adjustments beyond speed (volume, Flextalk profile)

This commit is contained in:
Fisto
2026-09-15 13:38:36 +02:00
parent 6f131b1f9e
commit 2ced066de2
4 changed files with 48 additions and 6 deletions
+2 -1
View File
@@ -46,7 +46,8 @@ Manual steps, if needed:
setup/run.sh -v Sam -t "Hello." -o sam.wav setup/run.sh -v Sam -t "Hello." -o sam.wav
setup/run.sh -v "Keith Bell" -t "Hello." -o keith.wav -p 100 -s 150 setup/run.sh -v "Keith Bell" -t "Hello." -o keith.wav -p 100 -s 150
Options: `-v` voice, `-t` text, `-o` output WAV, `-p` pitch, `-s` speed. Options: `-v` voice, `-t` text, `-o` output WAV, `-p` pitch, `-s` speed, `-V` volume
(volume is engine-specific; not used by the Veskslop wrapper).
## Veskslop integration ## Veskslop integration
+6
View File
@@ -82,6 +82,12 @@ final file `WatsonUNInstall.dll` is incomplete).
Verified with the corrected DWORD registry: Kerry 0 st = 209 Hz, -6 st = 148 Hz, 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. +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. - 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.
## Decompiled corpora (Ghidra) ## Decompiled corpora (Ghidra)
+28 -2
View File
@@ -260,10 +260,36 @@ record what the engine needs to interpret, add, or hide._
### Text handling ### Text handling
- SAPI4 is ANSI/CP1252. The wrapper converts UTF-8 to CP1252 and maps emoji and - SAPI4 is ANSI/CP1252. The wrapper converts UTF-8 to CP1252 and maps emoji and
unmappable characters to spaces. Engine tag parsing is disabled, so input stays plain unmappable characters to spaces. The host passes `TTSDATAFLAG_TAGGED`, so SAPI4 control
text (no SSML), matching the contract. tags in the text are enabled at the engine level; tagged input is not part of this
contract and is untested. Plain text (no SSML) is the contract.
- Lines up to 1000 characters are well within engine limits. - Lines up to 1000 characters are well within engine limits.
### Other voice adjustments (beyond speed)
All voices implement SAPI4 `ITTSAttributes`, so **pitch, speed and volume** exist at the
API level. The Veskslop contract exposes only speed and pitch; volume is available in the
host as `sapi4host -V <value>` and is not sent by Veskslop.
- **Volume** (measured 2026-09-15):
- Flextalk: linear, range 0-65535, default 65535; half (32767) is about -6 dB, 0 is
silence.
- MSTTS: default is 0xFFFFFFFF; 0 is silence, but the response is not linear (for
example 4096 gives about -24 dB while 65535 and larger are full scale). Treat volume
as effectively on/off for MSTTS.
- **Flextalk per-speaker profile** (applied at voice load, not runtime-adjustable):
values live in `HKLM\Software\AT&T\Watson\2.0\FlexTalk\2.0\Speakers\{GUID}` (see
`docs/NOTES.md` for the full list and defaults). `Pitch`/`Speaking Rate` set the natural
voice; **Expression** was verified to change the synthesis audibly (Keith Bell at
Expression 0 / 59 / 100 yields different waveforms with the same duration). Changing a
profile requires a registry edit and re-selecting the voice.
- **MSTTS voice character** is fixed by the `.vce`/`.cfg` data; the engine exposes no
per-voice profile in the registry. Its effect variants (telephone, in Hall, in Stadium,
in Space) are separate voice modes with separate data, exposed as distinct voice IDs
(`mike-telephone`, `mary-hall`, ...), not runtime effects.
- Not exposed by this integration: SSML, prosody/style tags, or SAPI4 attribute
extensions beyond pitch/speed/volume.
### Transport and latency ### Transport and latency
- Installation is one command: `setup/install.sh` (system dependencies with sudo, wine - Installation is one command: `setup/install.sh` (system dependencies with sudo, wine
+12 -3
View File
@@ -157,7 +157,7 @@ static void usage(void)
fprintf(stderr, fprintf(stderr,
"usage: sapi4host -l\n" "usage: sapi4host -l\n"
" sapi4host -v <voice> -t <text> -o <out.wav>" " sapi4host -v <voice> -t <text> -o <out.wav>"
" [-p pitch] [-s speed]\n"); " [-p pitch] [-s speed] [-V volume]\n");
} }
/* ------------------------------------------------------------------ */ /* ------------------------------------------------------------------ */
@@ -168,6 +168,7 @@ int main(int argc, char **argv)
{ {
const char *voice = NULL, *text = NULL, *out = NULL; const char *voice = NULL, *text = NULL, *out = NULL;
int pitch = -1, speed = -1; int pitch = -1, speed = -1;
long volume = -1;
int i; int i;
TTSMODEINFO mode; TTSMODEINFO mode;
@@ -182,6 +183,7 @@ int main(int argc, char **argv)
else if (!strcmp(argv[i], "-o")) out = argv[++i]; else if (!strcmp(argv[i], "-o")) out = argv[++i];
else if (!strcmp(argv[i], "-p")) pitch = atoi(argv[++i]); else if (!strcmp(argv[i], "-p")) pitch = atoi(argv[++i]);
else if (!strcmp(argv[i], "-s")) speed = atoi(argv[++i]); else if (!strcmp(argv[i], "-s")) speed = atoi(argv[++i]);
else if (!strcmp(argv[i], "-V")) volume = strtol(argv[++i], NULL, 0);
else { usage(); return 2; } else { usage(); return 2; }
} }
if (!voice || !text || !out) { usage(); return 2; } if (!voice || !text || !out) { usage(); return 2; }
@@ -257,9 +259,9 @@ int main(int argc, char **argv)
return 1; return 1;
} }
/* Pitch and speed are optional: when unset, leave the engine's own /* Pitch, speed and volume are optional: when unset, leave the engine's own
voice profile untouched (probing/setting would override it). */ voice profile untouched (probing/setting would override it). */
if (pitch >= 0 || speed >= 0) { if (pitch >= 0 || speed >= 0 || volume >= 0) {
WORD pmin = 0, pmax = 0; WORD pmin = 0, pmax = 0;
DWORD smin = 0, smax = 0; DWORD smin = 0, smax = 0;
@@ -284,6 +286,13 @@ int main(int argc, char **argv)
printf("speed: %d (%lu..%lu)\n", printf("speed: %d (%lu..%lu)\n",
speed, (unsigned long)smin, (unsigned long)smax); speed, (unsigned long)smin, (unsigned long)smax);
} }
if (volume >= 0) {
DWORD vdef = 0;
attr->lpVtbl->VolumeGet(attr, &vdef);
attr->lpVtbl->VolumeSet(attr, (DWORD)volume);
printf("volume: %ld (default %lu)\n",
volume, (unsigned long)vdef);
}
} }
{ {