Text-to-speech that runs on the device
Give your product a natural voice that starts speaking in a fraction of a second, with no cloud, no network, and no per-character bill. LinnetTTS streams audio as it generates it and keeps up in real time on a single phone CPU core.
Type anything and hear it, generated on your own CPU
This is the real model running in this tab through WebAssembly. Nothing you type leaves the page, and no server generates the audio. The first run downloads the 83 MB model once, and your browser keeps it for next time.
Or listen right now, with no download
English only, and write numbers as words for the best result. The browser build was made for this demo and uses about 350 MB of memory, more than the native SDKs need. Speed depends on your device.
What you get
Streams from the first step
Audio arrives in chunks of about 80 ms, so speech starts right away instead of after a whole sentence renders.
Real time on one core
7% of real time on an iPhone 13 Pro Max and 53% on a budget Android phone, on a single CPU core.
Two voices included
A female and a male American English voice, both synthetic, and both free to use as your product's voice.
Custom voices on request
The model speaks in the voice given to it as a short prompt, made from 5–6 seconds of clean speech. We create brand voices under a separate license.
Fully on-device
Works offline, keeps text private, and has no per-character or per-user fees however much your product talks.
Plain text in, 24 kHz out
No phonemizer or pronunciation dictionary to bundle. The whole text frontend is a 102 KB tokenizer.
A voice for your product, with no server behind it
A cloud voice adds a network round trip to every sentence, stops working when the connection drops, and sends a bill that grows with usage. LinnetTTS removes all three. It generates speech on the device your product already runs on, so spoken responses, navigation prompts, and read-aloud features work anywhere, and the text stays with your user.
Pair it with our speech recognition and wake word and you have a complete voice loop, listening and speaking, with no cloud in the path.
Prime the text, then play chunks as they arrive
Every platform exposes the same three objects: a model, a voice, and a streaming session. Prime a session with the text, and each step returns about 80 ms of 24 kHz mono audio to hand to your player. A session covers up to 24 seconds by default, so split longer text into segments. The weights, tokenizer, and voice are separate downloads.
// Android val model = LinnetModel(File(filesDir, "linnet-en-35m.fp16.safetensors"), File(filesDir, "tokenizer.model")) val voice = model.loadVoice(File(filesDir, "linnet-en-f1.npy")) model.session(voice, SynthParams(seed = 0)).use { session -> AudioTrackPlayer(model.sampleRate).use { player -> session.prime("Your package arrives tomorrow.") while (true) { val chunk = session.step() ?: break // about 80 ms of audio player.write(chunk) } player.drain() } }
// iOS let model = try LinnetModel(weightsPath: weights, tokenizerPath: tokenizer) let voice = try model.loadVoice(path: voicePath) let session = try model.session(voice: voice, params: SynthParams(seed: 0)) let player = try LinnetPlayer(sampleRate: model.sampleRate) try session.prime(text: "Your package arrives tomorrow.") while let chunk = try session.step() { // about 80 ms of audio try player.write(chunk) } player.drain()
# pip install linnet-tts (weights, tokenizer, and voice download separately) import linnet_tts model = linnet_tts.Model("linnet-en-35m.fp16.safetensors", "tokenizer.model") voice = model.load_voice("linnet-en-f1.npy") session = model.session(voice, seed=0) session.prime("Your package arrives tomorrow.") for chunk in session: # about 80 ms of audio play(chunk) # your audio output: mono float32 at 24 kHz
Built small on purpose
LinnetTTS is an autoregressive flow language model that emits one continuous latent per 80 ms step, using a single flow evaluation per step, followed by a Vocos-style neural codec decoder. Every operator is causal, with no lookahead anywhere in the chain, which is what makes true streaming possible.
It runs on a from-scratch Rust runtime that contains only the kernels this model uses, with NEON and AVX2+FMA fast paths. There is no ONNX Runtime or PyTorch underneath, and on Linux the library needs nothing beyond the standard system libraries. The code, including the runtime, is open source under Apache-2.0.
Measured on real devices
Real-time factor is the time spent generating as a share of the audio's length, so lower is better and anything under 100% keeps up with playback. Rust runtime, fp16 weights.
| Device | Threads | Per 80 ms step | Real-time factor |
|---|---|---|---|
| iPhone 13 Pro Max (A15) | 1 / 2 | 5.5 / 4.6 ms | 7% / 6% |
| iPhone 11 (A13) | 1 | 6.7–7.1 ms | 8–9% |
| Snapdragon 662, Redmi 9T (budget Android) | 1 / 4 | 42.4 / 14.3 ms | 53% / 18% |
| Raspberry Pi Zero 2 W (Cortex-A53, 1 GHz) | 1 / 4 | 158 / 59–60 ms | 198% / 75% |
| Apple M5 (macOS) | 1 | 3.3 ms | 4% |
| Desktop x86-64, Core i9-13900K (AVX2) | 1 / 4 | 7.0–7.4 / 2.5 ms | 9% / 3% |
Measured without real-time pacing. When synthesis is paced to playback, mobile power management raises the step time 3–4×, which is why the sample apps generate in a burst and let playback drain behind it. First audio arrives 50–130 ms after the text on iPhones. In the Android sample app on the Snapdragon 662 it takes about 250 ms once warm, and 300–430 ms for the first utterance. The Raspberry Pi Zero 2 W needs all four cores to stay under real time. Source: the model card.
The lightest in memory, and it streams inside a sentence
We ran LinnetTTS against three popular CPU text-to-speech models, each through its published package on the same desktop machine. LinnetTTS uses the least memory of the five and starts speaking well before a sentence is finished, which Piper and Kokoro do not do.
| Model | Peak memory | Weights on disk | Streaming | Real-time factor, 1 / 4 threads | First audio, short / long text | Voice cloning |
|---|---|---|---|---|---|---|
| LinnetTTS | 187 MB | 83 MB | Every 80 ms | 10.6% / 3.4% | 87 / 133 ms | Yes, separate license |
| Piper medium | 503 MB | 64 MB | Per sentence | 5.8% / 2.1% | 180 / 283 ms | No |
| Piper high | 639 MB | 114 MB | Per sentence | 42.5% / 12.4% | 1.4 / 2.1 s | No |
| pocket-tts 4.0.1 (Kyutai) | 968 MB | 236 MB | Every 80 ms | 23.8% (2 cores) / 24.1% | 71 / 82 ms | Yes |
| Kokoro-82M v1.0 (ONNX fp32) | 1246 MB | 326 MB + 28 MB voices | No | 51.7% / 17.2% | 1.5 / 9.6 s | No |
Measured by us on 2026-11-04 on a Core i9-13900K under Linux (WSL2), with Python driving each model and the Piper en_US-ljspeech voices. This compares speed and footprint, not voice quality, and desktop numbers don't predict phone numbers. Real-time factor here includes text processing and the Python loop, so Linnet reads higher than in the table above. Peak memory is the whole Python process, including its framework. The table measures English synthesis only. Method and commands to reproduce it, and the same sentence from every system for listening.
One model on every platform
The same model, voices, and streaming session everywhere, each with install instructions and a sample.
| Platform | Minimum OS | CPU architectures | Distribution |
|---|---|---|---|
| Android | Android 7.0 (API 24) | arm64-v8a (NEON); x86_64 | Kotlin AAR on the GitHub release, with a sample app |
| iOS | iOS 15 | arm64; simulator slices | Swift Package Manager, with a SwiftUI sample |
| Linux | glibc 2.17+ | x86_64 (AVX2+FMA); aarch64 (NEON) | C / C++ tarball (.so, .a, header, examples) |
| Python | Python 3.9+; Linux (glibc 2.17+) or macOS 11+ | Linux: x86_64, aarch64. macOS: Apple silicon only | pip install linnet-tts |
| Rust | Rust 1.85+ | Any of the above | Git dependency on the linnet-tts crate |
What it costs on disk and in memory
- Model weights on disk (fp16)83 MB
- Tokenizer + one voice~120 KB
- Native library, Android arm64-v8a~760 KB
- Added to an iOS arm64 app binary~2 MB
- Memory while speaking (weights + session)~105 MB
- Peak memory, measured on Linux~160 MB
The honest trade-offs: v1 is English only. A slight roughness is audible on headphones in some utterances, though not on laptop speakers. The model has no text normalization, so convert prices, dates, and phone numbers to words first. An integer variant for weaker devices is planned for a later release. Evaluate it on your own content, since words can occasionally be misread or dropped, and it is not certified as the sole channel for safety-critical or emergency information.
Free to ship with the built-in voices
The code, including the runtime, is Apache-2.0. The weights, tokenizer, and voices are under the Linnet Weights License, which is not an open-source license. In short:
Free, including commercially
- Using the unmodified weights with the built-in voices inside your own products, on devices and on servers
- Paid and closed-source products
- Speech as part of your product, such as read-aloud, assistants, and notifications
- Shipping the weights with your product
Needs a separate license from VoxRT
- Creating or cloning voices
- Modifying or fine-tuning the weights
- Training other models with the weights or their audio
- Offering general-purpose text-to-speech to others, in any form
- Distributing the weights on their own
Conditions apply, including attribution and shipping a copy of the license. This is only a summary: the Linnet Weights License governs. See also Licensing.
On-device text-to-speech, answered
Does LinnetTTS send text or audio to the cloud?
No. Text goes in and audio comes out on the device, with no network connection needed. The demo on this page works the same way: after the one-time model download, everything runs in your browser tab.
How fast is LinnetTTS on a phone?
On one CPU core it uses 7% of real time on an iPhone 13 Pro Max, 8–9% on an iPhone 11, and 53% on a budget Snapdragon 662, or 18% with all four big cores. First audio arrives 50–130 ms after the text on iPhones. In the Android sample app on the Snapdragon 662 it takes about 250 ms once warm, and 300–430 ms for the first utterance.
What languages and voices does LinnetTTS support?
English, with two built-in voices: one female and one male, both American English. Both are synthetic and designed from a text description, so they are not clones of any real person. Other languages are not supported by this model; additional languages are customer-driven.
Can I use my own voice or a custom brand voice?
Yes, under a separate license. The model speaks in the voice given to it as a short voice-prompt latent, made from about 5–6 seconds of clean speech from one speaker. VoxRT produces those latents from a recording; the encoder is not published, and the standard license covers only the built-in voices and voices VoxRT has authorized in writing. Talk to us to scope a custom voice.
Is LinnetTTS free for commercial use?
Yes, inside your own products with the built-in voices. The code, including the Rust runtime, is Apache-2.0, and the weights are under the Linnet Weights License. Custom voices, modified weights, training other models, and offering general-purpose text-to-speech to others need a separate license. The license text governs.
How does LinnetTTS compare with Piper, Kokoro, and pocket-tts?
In our CPU benchmark, LinnetTTS uses the least memory of the five models tested and streams inside a sentence, which Piper and Kokoro do not. Piper medium is faster and has smaller weights but has no voice cloning, and pocket-tts reaches first audio slightly sooner while using about five times the memory. The benchmark measures speed and footprint, not voice quality. See the numbers.
How does LinnetTTS handle numbers and dates?
The model has no text normalization. Simple digit forms are usually read correctly, but dense raw text such as prices with decimals, percentages, dates, phone numbers, and version strings is sometimes misread or cut short. Convert those to words before synthesis.
Which platforms does LinnetTTS support?
Android 7.0+ (API 24), iOS 15+, Linux on x86-64 and aarch64 through a C library, Python wheels, and a Rust crate. Windows is not shipped today, and neither is a browser SDK; the browser build on this page was made for this demo, and a browser release is planned.