Server TTS: Difference between revisions
Clarify all supported TTS languages; neutral voice-profile examples and separate Ukrainian backend note |
Release 0.1.7 / BUILD199 / Protocol 39: current API availability, setup and improvements |
||
| Line 1: | Line 1: | ||
''' | '''Available since 0.1.7.''' Use matching TTS-capable server and clients. | ||
Generate server-owned speech, pre-render reusable dialogue, and play it for selected players or in 3D. See [[TTS configuration]] for Windows/Linux setup and [[TTS voices]] for permitted downloaded samples, own voices and presets. | Generate server-owned speech, pre-render reusable dialogue, and play it for selected players or in 3D. See [[TTS configuration]] for Windows/Linux setup and [[TTS voices]] for permitted downloaded samples, own voices and presets. | ||
| Line 7: | Line 7: | ||
== Installation on Windows and Linux == | == Installation on Windows and Linux == | ||
Two package variants are | Two public package variants are available for each OS on the [https://g1r-mp.com/download/ Download page], with WITH TTS listed first and WITHOUT TTS second. Both ship with TTS disabled; enable it explicitly when needed. '''WITH TTS''' includes an application-local Python 3.12 interpreter, isolated CPU inference libraries, both engine models and language data. Extract the entire package and use the normal server launcher. No system Python, pip, Git or CUDA installation is required. '''WITHOUT TTS''' omits the Python/ML runtime and models; TTS defaults to disabled. To add it later, copy the complete <code>tts/runtime</code> from the matching full variant, then enable TTS. Never overwrite saved recordings, own voices or credentials during this operation. | ||
All runtime paths are package-relative. Windows uses an isolated Python module path; Linux uses a relocatable standalone interpreter and private dependency directories. Startup checks bundled dependencies/model files without loading a speech model. Missing files produce a specific error with recovery instructions. System requirements and dependency details are in <code>TTS-PACKAGES.txt</code>; exact bundled package versions and file hashes are in <code>tts/runtime/runtime-manifest.json</code>. Game clients do not need any of these dependencies. | All runtime paths are package-relative. Windows uses an isolated Python module path; Linux uses a relocatable standalone interpreter and private dependency directories. Startup checks bundled dependencies/model files without loading a speech model. Missing files produce a specific error with recovery instructions. System requirements and dependency details are in <code>TTS-PACKAGES.txt</code>; exact bundled package versions and file hashes are in <code>tts/runtime/runtime-manifest.json</code>. Game clients do not need any of these dependencies. | ||
| Line 257: | Line 257: | ||
== Server Lua API == | == Server Lua API == | ||
'''Since 0.1.7 | '''Since 0.1.7.''' Every function and event in this section is server-side. All 33 functions listed below share this availability; no client-side model installation is required. | ||
Invalid requests return <code>false, reason</code>. A missing named voice/preset returns <code>nil</code>; deleting an absent profile or cancelling a non-pending job can return <code>false</code> without an error string. Job/playback IDs are resource-owned, generation-safe numbers. Never reuse them after stopping a resource. Successful create/update profile calls return the validated profile table. | Invalid requests return <code>false, reason</code>. A missing named voice/preset returns <code>nil</code>; deleting an absent profile or cancelling a non-pending job can return <code>false</code> without an error string. Job/playback IDs are resource-owned, generation-safe numbers. Never reuse them after stopping a resource. Successful create/update profile calls return the validated profile table. | ||
Latest revision as of 23:49, 10 October 2026
Available since 0.1.7. Use matching TTS-capable server and clients.
Generate server-owned speech, pre-render reusable dialogue, and play it for selected players or in 3D. See TTS configuration for Windows/Linux setup and TTS voices for permitted downloaded samples, own voices and presets.
TTS is optional. With [tts] enabled=false (the default), the server does not create a TTS service, start TTS threads, read models or contact a worker. Clients have no TTS session when the server catalog has no TTS signing key. The ordinary resource audio APIs are unchanged.
Installation on Windows and Linux
Two public package variants are available for each OS on the Download page, with WITH TTS listed first and WITHOUT TTS second. Both ship with TTS disabled; enable it explicitly when needed. WITH TTS includes an application-local Python 3.12 interpreter, isolated CPU inference libraries, both engine models and language data. Extract the entire package and use the normal server launcher. No system Python, pip, Git or CUDA installation is required. WITHOUT TTS omits the Python/ML runtime and models; TTS defaults to disabled. To add it later, copy the complete tts/runtime from the matching full variant, then enable TTS. Never overwrite saved recordings, own voices or credentials during this operation.
All runtime paths are package-relative. Windows uses an isolated Python module path; Linux uses a relocatable standalone interpreter and private dependency directories. Startup checks bundled dependencies/model files without loading a speech model. Missing files produce a specific error with recovery instructions. System requirements and dependency details are in TTS-PACKAGES.txt; exact bundled package versions and file hashes are in tts/runtime/runtime-manifest.json. Game clients do not need any of these dependencies.
Start only Start-G1RServer.cmd on Windows or ./start-g1r-server.sh on Linux. Run as an unprivileged account. Ordinary restarts do not download anything, and models remain unloaded until needed. Model processes unload after the configured idle interval. The small package works with no Python present when TTS is disabled.
Advanced source/CUDA installation (not required by the complete CPU package)
Use Python 3.11 or 3.12 and Git. Run the explicit installer from the server directory containing serverconf.cfg; it creates a separate environment for each engine under tts/runtime and initially downloads several gigabytes. It does not start anything or modify a system Python installation. Only the server owner installs these dependencies; players do not.
Windows examples:
py -3.12 tts-service/install.py --engine chatterbox_v3 --root tts/runtime --device cpu
py -3.12 tts-service/install.py --engine styletts2_uk --root tts/runtime --device cpu
Linux examples:
python3.12 tts-service/install.py --engine chatterbox_v3 --root tts/runtime --device cpu
python3.12 tts-service/install.py --engine styletts2_uk --root tts/runtime --device cpu
Use --device cuda only on a compatible NVIDIA machine with a working driver. The GPU need not be on the game server: a separate worker can be reached over authenticated HTTPS. CPU generation is asynchronous but is not guaranteed to be fast enough for live conversations. Pre-render repeated lines before players need them.
Install the engine(s) once. Set workerInstallDirectory in serverconf.cfg to that installation directory, choose workerEngines, and change the single enabled switch to true. Start only Start-G1RServer.cmd on Windows or ./start-g1r-server.sh on Linux. The ordinary launcher starts the private workers automatically, waits for authenticated readiness, then starts the interactive dedicated server. No separate TTS console or start command is required. Run as an unprivileged account, not root/Administrator.
The default configuration stays workerInstallDirectory=tts/runtime on both platforms, resolved relative to serverconf.cfg, regardless of the shell's working directory. No developer-machine path belongs in a distributed configuration. The installer also records models inside that runtime using relative paths. A manually created Python venv is host-specific; it is not the relocatable runtime supplied in the complete CPU package. Do not redistribute developer venvs or generated tts/managed files.
The installer writes per-engine JSON metadata. Managed startup reuses the recorded model directories (including --reuse-model-directory); it never runs pip, installs software, downloads weights, or generates speech during startup. Models are loaded lazily. Missing installation/configuration produces a specific startup error instead of an implicit download. Starting g1rmp_server directly bypasses this process supervision: use the supplied launcher for managed TTS.
For another isolated environment on the same machine, --reuse-model-directory /path/to/prepared/engine reuses an existing model/frontend store without changing it or downloading the weights again. Both manifests must already exist. Runtime still checks the recorded hashes before loading.
Workers bind to 127.0.0.1 only. Do not expose their plain HTTP port publicly. Advanced deployments may use workerMode=external and separately managed authenticated HTTPS workers. This is not needed for normal single-launcher use. Never put the worker token or reference voices in downloadable resources.
Game server configuration
Add this section to serverconf.cfg, adjusting paths. Paths are relative to that configuration file unless absolute.
[tts]
enabled=false
workerInstallDirectory=tts/runtime
workerEngines=chatterbox_v3,styletts2_uk
voiceDirectory=tts/voices
device=cpu
cpuThreads=2
idleUnloadSeconds=120
chatterboxUrl=http://127.0.0.1:7792
ukrainianUrl=http://127.0.0.1:7793
tokenFile=tts/worker.token
dataDirectory=tts/data
timeoutSeconds=300
queueCapacity=128
pendingPerResource=16
cacheMiB=256
diskCacheMiB=2048
tokenFile must point to the same secret used by the workers. Protect it with filesystem permissions. The game server requires internal resource HTTP mode for live audio; a static external catalog alone cannot serve newly generated clips. The client resource transport currently uses HTTP. Audio integrity is protected by a session signature and SHA-256, but the audio transport is not confidential against network observers.
The shipped default is enabled=false; the launcher then does not even invoke Python. Set enabled=true after installation. workerEngines defaults to Chatterbox alone; add styletts2_uk after installing Ukrainian support. With TTS enabled, idle workers have small Python HTTP processes but no loaded models. Each model loads on its first cache miss and its entire subprocess exits after idleUnloadSeconds (default 120). The server launcher closes its workers when the server stops. A matching already-running worker is reused, not killed by a different server; a port occupied by a worker with different credentials/settings is rejected. Concurrent servers sharing a worker need a common token and identical worker/voice settings, but separate writable dataDirectory values.
Managed worker logs/configs live under tts/managed next to the server configuration. Do not edit those generated JSON files: change serverconf.cfg. Ordinary restarts preserve the secret, own voice files and stored recordings. A crashed generator reports an error; saved clips remain playable. Restart the server after correcting its worker log. Settings are read at server startup, not hot-reloaded.
Configuration reference (since 0.1.7)
| Setting | Purpose |
|---|---|
enabled |
The master switch; default false. No TTS runtime is started when disabled.
|
workerInstallDirectory |
Complete runtime for the current OS, normally tts/runtime; not a path to a single model file.
|
workerEngines |
Comma-separated engines started by the launcher. PL needs chatterbox_v3; UK needs styletts2_uk and a reference voice.
|
voiceDirectory |
Private operator-provisioned samples, shared by both managed engines. |
device, cpuThreads |
cpu in the bundled package; default two CPU threads per model process. More threads are not guaranteed to improve overall server performance.
|
idleUnloadSeconds |
Unload an idle model subprocess; next cache miss must load it again. Saved clips do not require loading it. |
chatterboxUrl, ukrainianUrl |
Authenticated worker endpoints; defaults are loopback-only. Do not forward these ports to game players. |
tokenFile |
Private shared worker secret, generated on first managed startup. Preserve it, never put it in a resource or Lua event. |
dataDirectory |
Persistent generated recordings, their index and resource-private names. Back up the entire directory. |
timeoutSeconds |
Backend request timeout, not an audio duration or a server tick timeout. |
queueCapacity, pendingPerResource |
Bounded pending work globally / per resource. Reject excessive submissions instead of allocating unbounded inference work. |
cacheMiB, diskCacheMiB |
Compressed memory cache / persistent disk budget, not model RAM limits. Disk exhaustion is reported, not resolved by silently deleting saved dialogue. |
The optional advanced workerMode=external skips managed local workers. It requires an operator-managed compatible worker; it is not needed for the supplied all-in-one packages. Server configuration is not editable by game clients. Keep test commands out of a public gamemode, or protect them with permissions and per-player quotas.
Render once and keep the recording
generateTts and renderTts are asynchronous aliases. Every successful generation is saved in the persistent disk cache before the job becomes ready. The cache key includes the exact request text, engine/model revision, language, complete parameters, seed, reference path and reference SHA-256. Identical queued requests for the same engine are reused, not generated twice. The cache survives server restarts.
Add saveAs to give a recording a stable resource-private name:
local rendering = renderTts("Witaj w kolonii.", {
engine = "chatterbox_v3",
language = "pl",
seed = 12345,
saveAs = "guard_intro"
})
addEventHandler("onTtsReady", resourceRoot, function(job, info)
if job ~= rendering then return end
outputDebugString("Recording saved: " .. info.name .. ", source=" .. info.source)
releaseTtsJob(job)
end)
After a restart, or with the Python workers stopped, load the saved clip:
local loading = loadTtsAudio("guard_intro")
addEventHandler("onTtsReady", resourceRoot, function(job, info)
if job ~= loading then return end
local sound, why = playTts3D(job, {
position = {1200.0, -910.0, -52.0}, -- metres, matching Lua positions
volume = 0.8,
minDistance = 1,
maxDistance = 30
})
if not sound then outputDebugString(why) end
releaseTtsJob(job) -- active playback retains the recording
end)
Keep the game server TTS feature enabled for playback; only inference workers can be stopped. Names use 1–64 ASCII letters, digits, underscores or hyphens. A different recording cannot silently overwrite an existing name: use a new name such as guard_intro_v2. Names belong to the resource name and persist across resource restarts. Back up the entire dataDirectory together, including audio, index and names-*.
source is generated, disk or memory. Disk files are content-addressed OGG Vorbis with checked metadata. Full disk quota, corrupt files and conflicting names are explicit errors; the system does not erase recordings automatically. Manage/archive the data directory while the server is stopped. Do not share one writable directory between multiple server processes.
Voice and preset configuration
Chatterbox Multilingual V3 supports 23 languages: Arabic (ar), Danish (da), Dutch (nl), English (en), Finnish (fi), French (fr), German (de), Greek (el), Hebrew (he), Hindi (hi), Italian (it), Japanese (ja), Korean (ko), Malay (ms), Norwegian (no), Polish (pl), Portuguese (pt), Russian (ru), Spanish (es), Swahili (sw), Swedish (sv), Turkish (tr) and Chinese (zh). Select any supported language in a voice or preset; use getTtsLanguages("chatterbox_v3") to read the available codes.
Ukrainian backend note: Ukrainian (uk) is supported through the separate StyleTTS2 engine: engine="styletts2_uk", language="uk". It requires that engine to be installed/enabled and an authorized reference sample. It is not a Chatterbox language; its engine-specific parameters are listed below.
Both engines support a server-owned reference sample. Place it under the worker's voices directory, not under game resources. Use WAV, FLAC or OGG, 1–30 seconds, mono/stereo, 8–96 kHz, at most 8 MiB. Supply its lowercase SHA-256. Use recordings you are authorized to use. Ukrainian requires a reference; Chatterbox can use its built-in voice.
Adding a downloaded or recorded voice
Step 1. Obtain an audio sample that you have permission to use for synthesized speech, for example a speaker's explicitly permitted sample or your own recording. Being publicly downloadable does not itself grant that permission. Keep the source and permission information in your operator records. Do not impersonate a person without their consent.
Step 2. Prefer clean speech from one speaker, without music, overlapping voices or long silence. A practical starting point is 5–15 seconds; the accepted range remains 1–30 seconds. Voice similarity and accent are not guaranteed. For Ukrainian, start with a clearly spoken Ukrainian reference.
Step 3. If necessary, convert the actual audio to WAV/OGG/FLAC in an audio editor. Renaming .mp3 to .wav does not convert it. For a simple interoperable sample, use mono PCM WAV. Keep it within the limits above.
Step 4. Choose a simple ASCII filename with a lowercase extension, e.g. guard.wav or guards/voice.ogg. Avoid spaces, accented filenames, absolute paths, .., symlinks and URLs. Copy it into tts/voices on the machine running the worker. An external worker needs its own identical copy, not just a file on the game server.
Step 5. Calculate the hash of the final file after conversion/copying:
(Get-FileHash -Algorithm SHA256 .\tts\voices\guard.wav).Hash.ToLowerInvariant()
sha256sum tts/voices/guard.wav
Step 6. Put the relative filename and the 64-character lowercase hash in a server-side voice/preset below, or use the private /ttstest voice commands. The owner provisions the file; the game client never uploads a voice through this API. A new filename under the existing voice directory can be used by the next request; changing startup configuration requires restarting the server.
Step 7. Render a short sentence and listen before rendering a whole dialogue library. Replacing the sample requires updating its hash. For a changed permanently named clip, also choose a new saveAs name.
A voice sample is not a downloadable TTS model. Arbitrary .pt, .pth, .safetensors, .onnx, Python scripts or executables are not accepted by the voice API. Do not install a downloaded “voice pack” into tts/runtime: the supported engine weights are provisioned separately. ZIPs must not be passed as references; if an authorized archive contains audio, extract and inspect the actual audio sample outside the server first.
A ready-made spoken recording that you want to play unchanged is different again: add it as a normal audio asset to a resource and use the existing client resource-audio API. Do not pass it through TTS just to play it, and do not manually insert it into tts/data; that directory contains generated clips with validated metadata. loadTtsAudio loads a name previously created by saveAs, not an arbitrary file path or internet URL.
Voice profiles and language selection
These are examples in a server-side Lua resource. The first profile works with any language listed for Chatterbox: change language to the supported code you need (en below is only an example). Replace each hash with the hash of the corresponding private sample. createTtsVoice does not train a model or render a recording: it stores a validated reusable configuration.
local voice, why = createTtsVoice("guard", {
engine = "chatterbox_v3", language = "en", seed = 12345,
reference = "guard.wav", referenceHash = "REPLACE_WITH_64_LOWERCASE_HEX_CHARACTERS",
parameters = {exaggeration=0.5, cfgWeight=0.5, temperature=0.8}
})
if not voice then outputDebugString("TTS profile rejected: " .. tostring(why)) end
For Ukrainian only, select the separate backend and its parameters instead:
local preset, why = createTtsPreset("guard_uk", {
engine = "styletts2_uk", language = "uk", seed = 12345,
reference = "guards/guard_uk.ogg", referenceHash = "REPLACE_WITH_64_LOWERCASE_HEX_CHARACTERS",
parameters = {speed=1.0, alpha=0.0, beta=0.0, embeddingScale=1.0, diffusionSteps=10}
})
if not preset then outputDebugString("TTS preset rejected: " .. tostring(why)) end
Then select {voice="guard"} or the corresponding preset in generation options. Use updateTtsVoice / updateTtsPreset when the name already exists. Multiple profiles can point to one reference with different languages/parameters; changing those options creates a different cache request. Switching profiles never changes a player's game voice-language setting.
createTtsPreset("guard", {
engine = "chatterbox_v3", language = "en", seed = 12345,
reference = "guard.ogg", referenceHash = "REPLACE_WITH_64_LOWERCASE_HEX_CHARACTERS",
parameters = {exaggeration=0.5, cfgWeight=0.5, temperature=0.8}
})
local job = renderTts("Stop!", {preset="guard", saveAs="guard_stop"})
Presets and voices store a complete generation configuration, not engine pointers. Request overrides replace top-level fields. Use either voice or preset, not both. Replacing the parameters table uses defaults for omitted values. getTtsCapabilities().engines[engine].parameters[name] returns a three-element array: [1] default, [2] minimum, [3] maximum (not named fields):
| Engine | Parameter | Default | Allowed range |
|---|---|---|---|
| Chatterbox V3 | exaggeration |
0.5 | 0–2 |
| Chatterbox V3 | cfgWeight |
0.5 | 0–1 |
| Chatterbox V3 | temperature |
0.8 | 0.05–5 |
| Chatterbox V3 | repetitionPenalty |
1.2 | 1–3 |
| Chatterbox V3 | minP |
0.05 | 0–1 |
| Chatterbox V3 | topP |
1.0 | 0.01–1 |
| Ukrainian StyleTTS2 | speed |
1.0 | 0.5–2 |
| Ukrainian StyleTTS2 | alpha, beta |
0.0 each | 0–1 |
| Ukrainian StyleTTS2 | embeddingScale |
1.0 | 0.1–3 |
| Ukrainian StyleTTS2 | diffusionSteps |
10 | integer 1–50 |
seed is an integer 0–4294967295 (default 0). Start with defaults and change one setting at a time. There is no separate pitch, gender, emotion-name or universal speed control in this API; in particular, speed is not a Chatterbox parameter. Select a different authorized voice sample for a different speaker, rather than passing unsupported keys.
Only actual engine parameters are accepted. Unsupported keys fail instead of being silently ignored. Voices/presets live in the resource VM session. Persist them with the existing resource storage API:
writeResourceData("tts_profiles", toJSON(exportTtsPresets()))
local text = readResourceData("tts_profiles")
if type(text) == "string" then
local profiles = fromJSON(text)
local ok, why = importTtsPresets(profiles)
if not ok then outputDebugString(why) end
end
Import validates the entire document before replacing that resource's collections. It never installs Python, loads a URL, or accepts executable model files from Lua.
Server Lua API
Since 0.1.7. Every function and event in this section is server-side. All 33 functions listed below share this availability; no client-side model installation is required.
Invalid requests return false, reason. A missing named voice/preset returns nil; deleting an absent profile or cancelling a non-pending job can return false without an error string. Job/playback IDs are resource-owned, generation-safe numbers. Never reuse them after stopping a resource. Successful create/update profile calls return the validated profile table.
| Functions | Arguments and result |
|---|---|
| isTtsAvailable, getTtsCapabilities | No arguments. Availability means configured/enabled, not proof that a worker is reachable. |
| getTtsLanguages | Optional engine name, default chatterbox_v3; returns language array.
|
| createTtsVoice, updateTtsVoice, createTtsPreset, updateTtsPreset | (name, configuration)
|
| getTtsVoice, getTtsPreset, deleteTtsVoice, deleteTtsPreset | (name)
|
| getTtsVoices, getTtsPresets, exportTtsPresets | No arguments; returns tables. |
| cloneTtsPreset | (newName, sourceName)
|
| importTtsPresets | (exportedTable); atomic replacement.
|
| generateTts, renderTts | (text, options); returns job ID. Optional saveAs creates a persistent name.
|
| loadTtsAudio | (name); returns an asynchronous disk-load job; no inference.
|
| getTtsJobState, getTtsAudioInfo | (job); state, error, source, hash, bytes, duration when ready.
|
| cancelTtsJob, releaseTtsJob | (job); cancel pending work or release a handle. Neither deletes stored recordings.
|
| preloadTtsAudio, playTts, playTts3D, playTtsAttached | (readyJob, playbackOptions); returns playback ID.
|
| getTtsPlaybackState, stopTts | (playback); active state or stop. Finished handles are no longer valid.
|
| setTtsPaused, setTtsVolume | (playback, boolean) / (playback, volume)
|
| setTtsPosition | (playback, {x,y,z}); independent 3D sound only.
|
| setTtsDistance | (playback, minimumMetres, maximumMetres)
|
Events use (id, info) on resourceRoot: onTtsJobStateChange, onTtsReady, onTtsError, onTtsPlaybackStateChange. Generation states are queued, running, ready, failed, cancelled, expired. Check the ready event before playing. Keep handles only as long as needed, then release them.
Playback and synchronization
Playback options: players={id1,id2} (omit for everyone currently connected), volume=0..1, position={x,y,z} in metres, minDistance, maxDistance, looped, lateJoin, readyTimeoutMs. Default distances are 1–30 m; maximum is 1000 m. Explicit recipient lists stay explicit even with lateJoin=true.
playTtsAttached(job,{attachment={type="npc",id=npcId},maxDistance=30}) follows a server NPC. Type player follows a player. Missing/despawned sources stop their sound. Custom Lua map objects do not share this numeric NPC/player registry: use playTts3D at their position and setTtsPosition when moving them.
Live NPC dialogue for nearby remote players
After configuring the guard_pl voice above, put this in the same server resource.
Call sayNpcLine(npcId, text) from trusted gamemode logic with an existing server NPC.
It does not register an unrestricted client event. Authorize and rate-limit player-driven
requests before calling it. Event handlers are registered once, not once per sentence:
local pending, playing = {}, {}
addEventHandler("onTtsReady", resourceRoot, function(job, info)
local npcId = pending[job]
if not npcId then return end
pending[job] = nil
local playback, why = playTtsAttached(job, {
attachment = {type="npc", id=npcId},
volume = 0.8, minDistance = 1, maxDistance = 30, lateJoin = true
-- players omitted: current listeners; lateJoin enables new listeners too
})
if playback then playing[playback] = true
else outputDebugString("NPC TTS playback rejected: " .. tostring(why)) end
releaseTtsJob(job) -- active playback keeps the audio, even after releasing this handle
end)
addEventHandler("onTtsError", resourceRoot, function(job, info)
if not pending[job] then return end
pending[job] = nil
outputDebugString("NPC TTS failed: " .. tostring(info.error))
releaseTtsJob(job)
end)
addEventHandler("onTtsPlaybackStateChange", resourceRoot, function(id, info)
if not playing[id] then return end
outputDebugString("NPC TTS playback: " .. tostring(info.state))
if info.state == "no-listener-ready" or info.state == "stopped" or info.state == "finished" then
playing[id] = nil
end
end)
function sayNpcLine(npcId, text)
local job, why = renderTts(text, {voice="guard_pl"})
if not job then return false, why end
pending[job] = npcId
return job
end
Repeated identical requests reuse the cache. Different live lines intentionally have no
fixed saveAs, so they cannot conflict with a previously named permanent recording.
For pre-authored dialogue, render named clips ahead of time, then replace the call to
renderTts with loadTtsAudio(savedName) and keep the same readiness/error handling.
The playing notification means a common start has been scheduled (normally 500 ms
ahead), not that a speaker has already emitted sound; listen on another client as well.
This API does not animate NPC lips or automatically synchronize subtitles.
Clients first download, verify and decode off the gameplay loop. The server schedules a common start after readiness acknowledgements, with a bounded readiness deadline. A late listener seeks to the current clip position rather than replaying its beginning. Slow/unavailable clients cannot stall the sequence indefinitely; if no listener is ready, playback fails with no-listener-ready. Readiness is a technical check, not a guarantee that the user's speakers are audible.
Clients persist verified OGG recordings under resource-cache/tts-audio-v1, keyed by SHA-256. Rejoining or restarting the client/server reuses unchanged files without another download, after validating a fresh signed session descriptor and checking the file size, hash and decoded duration. Corrupt files are fetched again and repaired. This is an optional 512 MiB / 8192-file LRU cache; eviction, manual clearing or an unwritable cache can require another download. No source dialogue text, tokens or voice reference samples are stored there. Server recordings and installed model files are separate and are not evicted by this client cache.
Stopping the resource stops its sounds, cancels work and revokes download tokens. The server sends bounded leases; disconnecting removes lingering audio. Generated clips cannot choose arbitrary download URLs or file extensions. Only server scripts can request synthesis; a gamemode must still authenticate and rate-limit any player command/event that calls these APIs.
Testing
Start the optional tts_test resource on a private server. /ttstest start runs Polish, English, German, cache, disk-load, 3D, player attachment, pause/resume and volume checks for the requester. /ttstest all uses all connected players; its final looping stage permits checking a reconnecting listener. /ttstest stop cancels the requester's sequence.
Configure Ukrainian first with /ttstest ukvoice sample.ogg <sha256>. Without a configured reference that step explicitly reports SKIP. The sample must exist in the Ukrainian worker's private voice directory. Confirm pronunciation, source position and remote audibility manually; the test does not claim audible success merely because an API returned a handle.
Custom text: /ttstest say pl Witaj w kolonii! plays only for you; /ttstest sayall uk Вітаю у колонії! plays for everyone. Identical text/options reuse the persistent server cache. These commands do not force a fixed saveAs name, so different texts cannot collide with the named QA recordings.
For an own voice in either engine, upload/copy a permitted recording into voiceDirectory (tts/voices by default), e.g. guard.ogg. Calculate its SHA-256: (Get-FileHash -Algorithm SHA256 .\tts\voices\guard.ogg).Hash.ToLowerInvariant() on Windows, or sha256sum tts/voices/guard.ogg on Linux. Then use /ttstest voice pl guard.ogg <sha256> or /ttstest voice uk guard.ogg <sha256> followed by say/sayall for that language. The selected references persist in the test resource's private storage. /ttstest voice pl default restores Chatterbox's built-in voice; Ukrainian always requires a reference. Updating a sample requires its new hash; unchanged samples/configuration do not invalidate the cache. The QA script automatically separates named samples by voice hash. In a gamemode, choose a new saveAs name when changing a permanently named recording.
This is operator-side sample provisioning, not unrestricted file upload from game clients. Samples must pass the same path, format, duration and hash checks in both engines. Ukrainian presets additionally expose speed, alpha, beta, embeddingScale and diffusionSteps; they can be stored/reloaded with exportTtsPresets / importTtsPresets just like Chatterbox profiles.
/ttstest render only renders and saves the four language samples (Ukrainian requires its reference). /ttstest saved plays the saved Polish sample to everyone without generating anything. Stop the workers before this second command to verify disk-only use. Missing recordings report an error rather than starting inference.
Troubleshooting and upgrade checklist
| Symptom | Check |
|---|---|
tts-disabled |
Set the master switch deliberately and restart with the supplied launcher. |
| Missing Python/module/model at startup | Extract the complete WITH TTS runtime for the correct OS; do not install random global pip packages to patch the portable bundle. |
unsupported-language |
Use pl with Chatterbox, uk with StyleTTS2; enable/install the chosen engine.
|
reference-required |
Ukrainian needs a private reference sample even if Polish works without one. |
reference-hash-required, invalid-reference-hash, reference-changed |
Hash the final sample, lowercase SHA-256, then update the profile. |
invalid-reference-path |
Use a relative ASCII filename and lowercase .wav, .flac or .ogg, with no spaces or traversal.
|
| Invalid reference audio / duration | Verify the actual audio format, duration, channel count, rate and size; an extension alone is not validation. |
| Named clip differs from the new sentence/voice | saveAs names cannot silently replace different audio. Use a new versioned name.
|
| First sentence takes a long time | Cold model load and CPU synthesis take time. Pre-render repeatable lines; do not retry the same request every tick. |
no-listener-ready or silent remote |
Use matching TTS-capable clients, inspect playback events, resource HTTP reachability, audio settings and 3D distance. An accepted job alone does not prove playback. |
| Download occurs again after reconnect | Check client cache eviction, corruption or permissions; current-session signatures are still required even for cached audio. |
Before upgrading, stop the server and back up serverconf.cfg, tts/voices, tts/data, tts/worker.token and the resources' private storage. Preserve the resource name to retain its named recordings/profiles. Replace program/runtime files with the matching platform package, keeping private data; use the normal launcher. Never publish that backup or an operator-configured test package. Source version labels alone do not upgrade existing binaries or enable TTS in public 0.1.6 clients.
Resource budgets and operation
Defaults: 128 queued jobs globally, 16 pending per resource, 2048 retained job handles, 128 server playbacks, 32 client playbacks, 256 MiB compressed server cache, 256 MiB client decoded cache, and 2 GiB persistent disk storage. A generated clip is at most 180 seconds and 8 MiB; text is at most 4000 Unicode characters and 12000 UTF-8 bytes. Each resource has up to 256 voices and 256 presets. These budgets bound faulty scripts and untrusted network data; generation does not run on the server tick thread.
Changing model dependencies requires repeating real inference tests. The installer pins primary source revisions and model content, records the resolved environment, and provisions tokenizer resources explicitly. Model processes deny runtime network connections. The Ukrainian installer converts one checksum-verified legacy embedding into a restricted-load-compatible format; arbitrary user-supplied checkpoints are not accepted.
Model sources: Chatterbox, Ukrainian StyleTTS2 inference. No OmniVoice backend is included.