Skip to content
idle-intelligencePublic

About

Text to Speech on the web

Resources

Stars

3 stars

Watchers

0 watching

Forks

Latest commit

 

History

182 Commits

Folders and files

Repository files navigation

tts-web

Text-to-speech that runs entirely in the browser, with Rust compiled to WebAssembly. No server: the text never leaves the machine.

Try the demo

Models

Model Size Params Architecture License
Pocket TTS ~134MB per language (Q8_0) ~97M Autoregressive + Mimi codec MIT (code), CC-BY-4.0 (weights)
KittenTTS ~56MB (F32) 14M StyleTTS 2 distilled, single forward pass Apache 2.0

Weights are on Hugging Face: Pocket TTS GGUF, KittenTTS safetensors.

Languages

Pocket TTS speaks six languages. Each is a separate Q8_0 quantization of Kyutai's checkpoint for that language, from kyutai/pocket-tts-without-voice-cloning at revision 4e1e0a3.

Language Default voice
English alba
French estelle
German juergen
Spanish lola
Portuguese rafael
Italian giovanni

A Pocket TTS voice is a KV cache computed by one specific model, so voices are always fetched from the same Kyutai revision as the weights. Voices from a different revision produce broken speech.

Command line

You need Rust, installed with rustup. The repo pins its toolchain in rust-toolchain.toml, and rustup installs it on the first cargo command. The first build takes a minute or two.

Pick a language and a voice by name. The weights, tokenizer and voice are downloaded on first use and cached under ~/.cache/tts-web.

git clone https://github.com/idle-intelligence/tts-web.git
cd tts-web

# Italian, voice giovanni
cargo run --example tts_generate -p tts-core --release -- \
  --language italian --voice giovanni \
  --text "Ciao, questo è un test." \
  --output italian.wav

# Every language and voice available
cargo run --example tts_generate -p tts-core --release -- --list

--model, --tokenizer and --voice also accept local file paths, for offline use. It prints the generation time and the real-time factor. The built binary, target/release/examples/tts_generate, runs on its own:

target/release/examples/tts_generate --language french --voice estelle \
  --text "Bonjour, ceci est un test." --output french.wav

KittenTTS (English) works the same way, with 8 voices: bella, jasper, luna, bruno, rosie, hugo, kiki, leo. The model and voices (~56MB) are downloaded into models/ in the current directory on first run.

cargo run --example kitten_generate -p kitten-core --release --features espeak -- \
  --voice bruno --text "Hello, this is a test." --output bruno.wav

The espeak feature bundles a pure-Rust port of espeak-ng with English data, so text to phonemes works with no system dependency. The built binary, target/release/examples/kitten_generate, runs on its own.

KittenTTS without the espeak feature

The espeak-ng port is GPL. To avoid it, use a system espeak-ng or pass IPA directly:

# System espeak-ng (macOS: brew install espeak-ng; Debian/Ubuntu: sudo apt install espeak-ng)
cargo run --example kitten_generate -p kitten-core --release -- --text "Hello world" --output hello.wav

# IPA input
cargo run --example kitten_generate -p kitten-core --release -- --ipa "həlˈəʊ wˈɜːld" --output hello.wav

Converting the original KittenTTS ONNX weights

To convert the weights yourself instead of using the safetensors on Hugging Face:

hf download KittenML/KittenTTS-nano --local-dir models/kitten-nano
python scripts/kitten/convert_kitten_to_safetensors.py models/kitten-nano   # needs onnx, safetensors, numpy

This writes kitten-nano.safetensors and kitten-voices.safetensors next to the ONNX file.

Browser demo

scripts/build.sh                     # builds both WASM packages and assembles the site
python3 scripts/serve.py --port 8082

Open http://localhost:8082/web/.

CI (.github/workflows/pages.yml) runs these on the pinned toolchain:

cargo test --workspace --release     # non-GPU tests only; parity gates need model files
cargo clippy --workspace --all-targets -- -D warnings
ENGINE_BUILD=<tag> scripts/build.sh

scripts/build.sh rewrites the ?v= build tag on every wasm and model loading URL to ENGINE_BUILD (CI passes the commit sha, local builds default to dev). Use a new tag for every real deploy, otherwise browsers keep running the cached module. It needs wasm-pack and a wasm-bindgen-cli matching the version in Cargo.lock.

Architecture

crates/
  kitten-core/     # KittenTTS inference (BERT, text encoder, predictor, decoder) on Candle
  kitten-wasm/     # KittenTTS WASM bindings
  tts-core/        # Pocket TTS inference
  tts-wasm/        # Pocket TTS WASM bindings

web/
  index.html       # Demo page (model and language selector)
  kitten-worker.js # KittenTTS Web Worker
  worker.js        # Pocket TTS Web Worker
  tts-client.js    # Shared client class
  • Pocket TTS: autoregressive, with the Mimi codec decoder; audio is streamed in chunks for real-time playback.
  • KittenTTS: a single forward pass. Text goes to espeak IPA, phoneme IDs, the model, then 24kHz audio.
  • mimi-rs: the shared Mimi audio codec library.

Performance (M-series Mac)

Model Native RTF WASM (Chrome)
Pocket TTS 0.23 2.28x realtime (first audio after 0.41s)
KittenTTS 0.24 1.81x realtime

RTF is generation time divided by audio duration (lower is faster). WASM speed is audio duration divided by wall time (higher is faster).

Building from source

Dependencies come straight from git; no other checkout is needed:

  • candle, branch wasm-simd-opt: a fork with an optimized WASM SIMD128 quantized matmul, applied through [patch.crates-io] in Cargo.toml.
  • mimi-rs: the Mimi codec (encoder, decoder, streaming transformer).

The Rust toolchain is pinned in rust-toolchain.toml.

About

Text to Speech on the web

Resources

Stars

3 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages