Self-hosted TTS service backing SStts on the game side. Wraps Piper (VITS + ONNX
on CPU) behind an aiohttp gateway and exposes the HTTP contract the game-side
subsystem expects.
Companion game-side code lives in ReduxStation/ReduxStation:
code/controllers/subsystem/tts.dm(the subsystem)code/datums/http.dm(rust-g HTTP wrapper)code/controllers/configuration/entries/tts_config.dm(config entries)
- Hosts 2 Piper voice models:
en_GB-vctk-medium(109 UK English speakers, the "comical" tg-style sound) anden_US-libritts_r-medium(904 American LibriTTS-R speakers for raw variety). About 1013 voices total. - Implements
GET /tts-voices,GET /pitch-available,GET /tts,GET /tts-blips,GET /health-checkmatching the API contractSSttsuses. - Returns ogg/vorbis audio with an
audio-length: HH:MM:SS.fffheader. - Auth via the
Authorizationheader, constant-string compared, defaults tocoolio(override in production).
- ffmpeg filter chain (radio click splicing, silicon convolution, mask voice
filters). The gateway currently ignores
filterandspecial_filtersquery parameters. - Pitch shift via rubberband.
/pitch-availablereturns 500 soSSttskeepspitch_enabled = FALSEand the URL templating substitutes 0 in. - Real blips synthesis.
/tts-blipscurrently returns the same audio as/tts. - Result caching (sha256 LRU of recent utterances).
docker build -t reduxstation-tts:latest .
The build downloads both voice models from Hugging Face at image build time (about 125 MB). First build takes a couple of minutes; subsequent rebuilds reuse the voice-data layer.
docker run --rm \
-p 127.0.0.1:5500:5500 \
-e TTS_AUTHORIZATION_TOKEN=$(openssl rand -hex 32) \
--name tts \
reduxstation-tts:latest
Set TTS_AUTHORIZATION_TOKEN to a strong random string in production. The
default value coolio is fine for local smoke testing. The game container must
send the same value in TTS_HTTP_TOKEN in hippiestation_config.txt.
TOKEN=coolio
curl -fsS -H "Authorization: $TOKEN" http://localhost:5500/tts-voices | head -5
curl -fsS -H "Authorization: $TOKEN" http://localhost:5500/health-check
curl -fsS -H "Authorization: $TOKEN" \
-H "Content-Type: application/json" \
--data '{"text":"hello there general kenobi"}' \
"http://localhost:5500/tts?voice=VCTK%20Man%2002&identifier=test&filter=&pitch=0&special_filters=" \
-o /tmp/test.ogg
ffprobe /tmp/test.ogg
The first request after start triggers ONNX warmup. Expect 1 to 4 seconds for the cold path. Subsequent requests on the same voice land in tens to low hundreds of milliseconds depending on host CPU.
In the deployment compose file (ReduxStation/config/docker-compose.yml), add a
service alongside the existing game container:
tts:
image: ghcr.io/reduxstation/tts:latest
restart: unless-stopped
expose:
- "5500"
environment:
- TTS_AUTHORIZATION_TOKEN=${TTS_TOKEN}
cpus: "8"
mem_limit: "4g"
healthcheck:
test: ["CMD", "curl", "-fsS", "http://localhost:5500/health-check"]
interval: 5s
timeout: 2s
retries: 3Then in ReduxStation/config/hippiestation_config.txt:
TTS_HTTP_URL http://tts:5500
TTS_HTTP_TOKEN <same value as TTS_TOKEN env>
TTS_MAX_CONCURRENT_REQUESTS 4
TTS_HTTP_TIMEOUT_SECONDS 30
When TTS_HTTP_URL is unset, SStts returns SS_INIT_NO_NEED at boot and the
subsystem disables itself silently. Safe to leave the config keys absent while
iterating on the gateway image.
On the live VPS (AMD EPYC 9645 Zen 5, 16 vCPUs):
- Cold-start per worker: 1 to 4 seconds (ONNX model load).
- Per-request synthesis: 25 to 80 ms for typical SS13 utterances (5 to 50 chars).
- Memory: about 250 MB resident per worker, about 500 MB total for the two voice
models plus Python overhead. Headroom is large; the
mem_limit: 4gabove is loose. - Throughput: comfortably 30 to 60 requests per second sustained per single aiohttp worker.