The Free Encyclopedia

OmniVoice TTS Setup

Revision as of Jun 25, 2026 23:03 by migration.

OmniVoice is theLAB's text-to-speech service — a VibeVoice / Qwen3-TTS-family model running on the host apexlabs-voicebox on our private Tailscale net. It's the final stage of ADI's voice loop: an LLM produces a reply, and OmniVoice turns that text into the audio the user actually hears. The whole pipeline upstream of it lives in the fully-local voice stack.

The nice part is that OmniVoice speaks the same dialect as everything else in the fleet: an OpenAI-compatible speech endpoint. If you've called OpenAI's /v1/audio/speech before, you already know the shape — point it at our host and you get audio bytes back.

01Hit the speech endpoint

The endpoint is POST /v1/audio/speech. On ADI the base is http://<host>:8881, so the full URL is http://<host>:8881/v1/audio/speech where <host> is apexlabs-voicebox on the tailnet. The request body is plain JSON; the response is raw audio bytes.

curl -s http://<host>:8881/v1/audio/speech \
  -H "Content-Type: application/json" \
  -d '{
    "model":  "omnivoice",
    "voice":  "adi_ref",
    "input":  "Welcome back. Your garage box is online.",
    "format": "wav"
  }' \
  -o out.wav        # response body is the audio itself

Four fields carry the request. model is omnivoice; voice is adi_ref, our reference/cloned voice; input is the text to speak; and format defaults to wav. Swap format for mp3, ogg, or webm when you want a compressed stream, and swap voice if you register another reference voice later.

No auth needed locally The endpoint accepts an optional Authorization: Bearer <key> header for providers that require one — but OmniVoice running locally on the tailnet does not. Leave it off. Content-Type: application/json is the only header that matters here.

02Go through ADI's tts.php instead

In production we rarely call the raw endpoint. ADI ships a PHP provider abstraction, tts.php, that fronts several backends — omnivoice, cosyvoice2, kokoro, groq, google_cloud, and browser — behind one interface. For omnivoice it defaults model to "omnivoice" and voice to the OMNIVOICE_VOICE constant ("adi_ref"), then posts {model, voice, input, ...} to {base}/v1/audio/speech — exactly the call from step 01, just centralized.

# conceptually, what tts.php does for the omnivoice provider
$payload = [
    "model"  => "omnivoice",
    "voice"  => OMNIVOICE_VOICE,   # "adi_ref"
    "input"  => $text,
    "format" => $format,           # wav by default
];
$audio = http_post_json("{$base}/v1/audio/speech", $payload);

Routing through tts.php means a single call site can fall back to another provider, or switch a user from OmniVoice to a cloud voice, without the caller changing a line.

03Lean on the disk cache

TTS is slow and our phrasing repeats — "online", confirmations, error chimes. So tts.php caches every response on disk, keyed by sha256(provider|base|model|voice|format|language|text). A repeated phrase is a cache hit: the audio comes back instantly and the response carries an X-TTS-Cache: hit header. This is the single biggest latency win in the whole voice loop, since synthesis cost drops to a disk read for anything ADI has said before.

The cache key is exact Because the key folds in model, voice, format, and language alongside the text, changing any one of them — e.g. asking for mp3 instead of wav, or a different reference voice — is a different key and a fresh miss. Keep your defaults stable to keep the hit rate high.

Verify it works

  • out.wav plays back clean speech in the adi_ref voice — not silence or a JSON error body.
  • A second identical request returns near-instantly and reports X-TTS-Cache: hit.
  • Through tts.php, a phrase the LLM just generated is audible end-to-end with no per-request key or auth.