GUIDE

Text to speech in Rust, offline

Rust has crates that wrap the operating system’s speech engines, which is fine for accessibility but gives robotic voices that differ on every platform, and cloud SDKs that bill per character. For a game, a bot or a command-line tool that needs one natural voice everywhere, neither is ideal.

This guide shows how to call the free SuperClonica engine from Rust through a local HTTP server: natural voices, WAV files you keep, and a few lines with reqwest.

Ways to speak from Rust

For screen-reader style output, system voices are right; for a product or game voice, use an engine.

  • Crates that wrap system voices: offline and light, but robotic and different on Windows, macOS and Linux.
  • Cloud SDKs: natural voices, but an account, a key, a bill per character and your text on their servers.
  • SuperClonica: a local HTTP server with natural, character and custom voices, offline, wherever Docker runs.
  • It speaks 31 languages; Chinese is not supported yet.

Hear the voices free online

Before you write code, open the page of any voice, type a line of 30 to 100 characters, choose the language and press Voice it. In a few seconds you hear the voice your app could have, with no account.

Start the speech server

An NVIDIA graphics card makes it faster with the cuda image, but it is not required.

  • Start the server on any CPU: docker run -d -p 8000:8000 -v superclonica-models:/models superclonica/superclonica:cpu.
  • The first start downloads the Supertonic 3 model, about 385 MB, into the volume; GET /health tells you when it is ready.
  • POST /synthesize takes JSON with text, voice, lang, seed and normalize, and answers with a 16-bit mono WAV.
  • voice is a stock name from M1 to M5 and F1 to F5, or the JSON content of a voice file.

Call it from Rust

With the reqwest crate and its blocking feature, inside a function that returns a Result, a request takes a few lines: let voice = std::fs::read_to_string("voice.json")?; let body = format!(r#"{{"text": "Hello from Rust!", "lang": "en", "seed": 7, "voice": {voice}}}"#); let wav = reqwest::blocking::Client::new().post("http://localhost:8000/synthesize").header("Content-Type", "application/json").body(body).send()?.bytes()?; std::fs::write("hello.wav", wav)?; In async code, use reqwest’s async client the same way. For a stock voice, send "voice": "F1" instead of the file. The same text, voice and seed always give the same audio, so cache files for phrases that repeat; serde_json keeps larger requests tidy.

Production habits

Reuse one reqwest client and set a timeout. Do not expose port 8000 to the internet as it is: set SUPERCLONICA_API_KEY and send it as a Bearer token, or keep the server on a private network. Run more copies of the model with SUPERCLONICA_LANES if many requests arrive at once, and measure on your hardware: in our tests a short phrase took 0.12 s on an RTX 5070 Ti and 0.43 s on four CPU threads.