neutts-rs
Rust port of NeuTTS — on-device voice-cloning TTS built on a GGUF LLM backbone and the NeuCodec neural audio codec.
Pure Rust — no ONNX Runtime, no native ML dependencies.
The codec runs as a self-contained CPU inference engine (safetensors + ndarray + rustfft).
Quick start
1. Install system dependencies
# macOS
# Ubuntu / Debian
# Alpine
2. Convert codec weights (one-time, ~2 min)
Downloads neuphonic/neucodec, extracts the decoder weights, and saves them as
models/neucodec_decoder.safetensors.
3. Build
4. Clone a voice and synthesise
The simplest path — point at any WAV file and say what you want:
On the first run the reference WAV is encoded via the Python neucodec package
and cached as my_voice.npy beside the WAV. Every subsequent run loads the
cache and skips encoding entirely.
One-time Python install for encoding:
Examples
| Example | What it does |
|---|---|
speak |
Recommended. WAV in → synthesised audio out. Encodes on first run, caches .npy beside the WAV. Supports --list-models, --list-files, --gguf-file. |
basic |
Synthesise from a pre-encoded .npy reference |
clone_voice |
Full voice cloning — .npy or raw WAV + SHA-256 cache |
encode_reference |
Stub — returns a helpful error; use Python for now |
download_models |
Download / stage weights |
test_pipeline |
Smoke-test every component without model files |
speak
# Minimal — encodes reference on first run, cached after that
# Use a bundled sample voice (pre-encoded, no Python needed)
# Skip directly to a pre-encoded .npy
# List all known backbone models
# List GGUF files available in a specific repo
# Pick a specific GGUF quantisation
# Different language backbone
# CPU-only (no wgpu)
speak flags:
| Flag | Short | Purpose |
|---|---|---|
--wav PATH |
-w |
WAV file of the voice to clone |
--codes PATH |
-c |
Pre-encoded .npy (skips encoding) |
--ref-text TEXT|PATH |
-r |
Transcript of the reference WAV (file or literal string). Auto-detected from <stem>.txt if omitted. |
--text TEXT |
-t |
Text to synthesise |
--out PATH |
-o |
Output WAV (default: output.wav) |
--backbone REPO |
-b |
HuggingFace backbone repo (see --list-models) |
--gguf-file FILE |
-g |
Specific .gguf filename within the repo |
--list-files |
Print all .gguf files in --backbone and exit |
|
--list-models |
Print table of all known backbone repos and exit |
basic
# Default: NeuTTS-Nano Q4, bundled Jo voice
# Custom text and reference
# Different backbone
clone_voice
# First run: encodes + SHA-256 caches
# Second run: cache hit, encoder skipped
# Pre-encoded .npy
Available models
Run --list-models to see the full table at any time:
| Repo | Name | Language | Params | GGUF |
|---|---|---|---|---|
neuphonic/neutts-nano-q4-gguf |
NeuTTS Nano Q4 | en-us | 0.2B | ✅ |
neuphonic/neutts-nano-q8-gguf |
NeuTTS Nano Q8 | en-us | 0.2B | ✅ |
neuphonic/neutts-nano |
NeuTTS Nano (full) | en-us | 0.2B | |
neuphonic/neutts-air-q4-gguf |
NeuTTS Air Q4 | en-us | 0.7B | ✅ |
neuphonic/neutts-air-q8-gguf |
NeuTTS Air Q8 | en-us | 0.7B | ✅ |
neuphonic/neutts-air |
NeuTTS Air (full) | en-us | 0.7B | |
neuphonic/neutts-nano-german-q4-gguf |
NeuTTS Nano German Q4 | de | 0.2B | ✅ |
neuphonic/neutts-nano-german-q8-gguf |
NeuTTS Nano German Q8 | de | 0.2B | ✅ |
neuphonic/neutts-nano-german |
NeuTTS Nano German (full) | de | 0.2B | |
neuphonic/neutts-nano-french-q4-gguf |
NeuTTS Nano French Q4 | fr-fr | 0.2B | ✅ |
neuphonic/neutts-nano-french-q8-gguf |
NeuTTS Nano French Q8 | fr-fr | 0.2B | ✅ |
neuphonic/neutts-nano-french |
NeuTTS Nano French (full) | fr-fr | 0.2B | |
neuphonic/neutts-nano-spanish-q4-gguf |
NeuTTS Nano Spanish Q4 | es | 0.2B | ✅ |
neuphonic/neutts-nano-spanish-q8-gguf |
NeuTTS Nano Spanish Q8 | es | 0.2B | ✅ |
neuphonic/neutts-nano-spanish |
NeuTTS Nano Spanish (full) | es | 0.2B | ✅ |
To discover which specific GGUF quantisation variants are in a repo:
Then pick one with --gguf-file:
Architecture
text ──► espeak-ng ──► IPA ──┐
├──► prompt builder ──► GGUF backbone ──► speech tokens
ref_codes (.npy) ─────────────┘ │
▼
NeuCodec decoder
│
▼
audio (Vec<f32>, 24 kHz)
GGUF backbone
Small causal LM in GGUF format, run via llama-cpp-2. Takes a phonemized text
prompt and pre-encoded reference speaker codes, generates <|speech_N|> tokens.
NeuCodec decoder (pure Rust)
XCodec2-based architecture loaded at runtime from models/neucodec_decoder.safetensors:
codes [T]
└─► FSQ decode (integer → 8 scaled digits → project_out Linear 8→2048)
│
fc_post_a (Linear 2048→1024)
│
VocosBackbone
├─ Conv1d(k=7)
├─ 2 × ResnetBlock (GroupNorm → SiLU → Conv1d)
├─ 12 × TransformerBlock (RMSNorm → MHA + RoPE → SiLU MLP)
└─ 2 × ResnetBlock + LayerNorm
│
ISTFTHead
├─ Linear(1024 → n_fft+2)
└─ ISTFT (same padding, Hann window)
│
audio [T × hop_length] (24 kHz)
| Property | Value |
|---|---|
| Output sample rate | 24 000 Hz |
| Tokens / second | 50 |
| Samples / token | 480 (hop_length) |
| FSQ codebook size | 4⁸ = 65 536 codes |
| Encoder input | 16 000 Hz mono WAV |
Bundled reference voices
Five pre-encoded voices are included and work without any Python encoding step:
| Files | Voice | Language |
|---|---|---|
samples/jo.* |
Jo | English |
samples/dave.* |
Dave | English |
samples/juliette.* |
Juliette | French |
samples/greta.* |
Greta | German |
samples/mateo.* |
Mateo | Spanish |
Each has a .wav (original audio), .npy (pre-encoded tokens), and .txt (transcript).
Feature flags
| Feature | Default | Description |
|---|---|---|
backbone |
✓ | GGUF backbone via llama-cpp-2 (requires cmake + C++) |
espeak |
Raw-text input via libespeak-ng |
|
wgpu |
Reserved for future GPU codec acceleration (currently no-op) | |
metal |
macOS Metal GPU for the backbone | |
cuda |
NVIDIA CUDA for the backbone |
Without backbone — codec-only mode; use NeuCodecDecoder::decode() directly.
Without espeak — pass pre-phonemized IPA via tts.infer_from_ipa().
Build requirements
| Platform | Backbone | Codec | Phonemizer |
|---|---|---|---|
| Linux / macOS | cmake + C++ (auto) | pure Rust | libespeak-ng-dev / brew install espeak-ng |
| iOS / Android | cross-compile llama.cpp | pure Rust | cross-compile espeak-ng; set ESPEAK_LIB_DIR |
Using the library
Full pipeline
use ;
use Path;
// Download backbone from HuggingFace (cached after first run).
// Pass None to auto-select the first GGUF in the repo,
// or Some("filename.gguf") to pick a specific quantisation.
let tts = load_from_hub_cb.unwrap;
// Load pre-encoded reference codes
let ref_codes = tts.load_ref_codes.unwrap;
// Synthesise — returns Vec<f32> at 24 kHz mono
let audio = tts.infer.unwrap;
// Save to WAV
tts.write_wav.unwrap;
Discover models programmatically
use ;
// Iterate the registry
for m in BACKBONE_MODELS
// Find a specific repo
if let Some = find_model
// List GGUF files available in a repo (network call)
let files = list_gguf_files.unwrap;
for f in &files
IPA passthrough (without espeak)
let audio = tts.infer_from_ipa.unwrap;
Decoder only
use NeuCodecDecoder;
// Loads models/neucodec_decoder.safetensors at runtime
let dec = new.unwrap;
println!; // "cpu (ndarray)"
println!;
let codes: = vec!;
let audio: = dec.decode.unwrap;
Reference-code cache
use RefCodeCache;
use Path;
let cache = new?;
if let Some = cache.try_load?
Mobile / C FFI
A practical mobile architecture runs the backbone server-side and only the NeuCodec decoder on-device:
NeuTtsHandle *codec = ;
float *audio = ;
;
;
;
See include/neutts.h for the full C header.
Pipeline stages
- Text preprocessing — numbers, currencies, abbreviations → spoken words
- Phonemisation — espeak-ng converts text to IPA phonemes
- Prompt construction — reference codes + IPA → GGUF prompt
- Backbone inference — GGUF LLM generates
<|speech_N|>tokens - Token extraction — regex extracts integer IDs from generated text
- Codec decode — NeuCodec decoder converts IDs to 24 kHz audio
Status
| Component | Status |
|---|---|
| GGUF backbone inference | ✅ |
| NeuCodec decoder (pure Rust, safetensors) | ✅ |
| NeuCodec encoder (pure Rust) | ⏳ not yet — speak example falls back to Python neucodec |
| English backbones (Nano / Air, Q4 / Q8) | ✅ |
| German / French / Spanish backbones | ✅ |
| Full (non-GGUF) model repos | ✅ in registry; GGUF files detected automatically |
| GPU acceleration (codec) | ⏳ planned via wgpu feature |
| iOS / Android build | ✅ codec is pure Rust; backbone needs cross-compile |
Citation
If you use this software in your research or project, please cite it as:
If you also use the underlying NeuTTS model or NeuCodec, please cite those works directly via their respective HuggingFace repositories at huggingface.co/neuphonic.
License
MIT