Streaming speech, on your device.
Phonon brings natural text-to-speech to phones, laptops, browsers, and local services. It is built for offline assistants, accessibility tools, and interactive experiences that need speech without a cloud round trip. The runtime is written in Rust and needs no PyTorch.
What you get · Choose an integration · Packages and downloads · Quickstart · Guides
Runtime preview available: Python, Rust, browser packages, and native downloads are published. See the runtime preview release for installation instructions. The unversioned registry commands below target the upcoming stable release and currently select older packages. Phonon model files are available separately from the runtime packages. Docker public access is being finalized.
The browser runs synthesis on your device; it downloads model files on first use.
- Small enough to ship: Choose a 40M or 90M parameter model. Both can ship inside a mobile app or load in a browser tab.
- Five languages in one model: Speak English, French, German, Spanish, and Portuguese, with more languages planned.
- Fast across devices: A custom inference stack runs in browsers and on phones, laptops, and embedded hardware, with Core ML on Apple devices, a QNN preview for supported Snapdragon NPUs, and GPU support. Multiple backends deliver faster inference than Kokoro and llama.cpp-based runtimes on the same hardware.
- Voices ready to use or make your own: Choose from ready-to-use voices, or explore voice design and cloning in Gradium Studio.
- Control the delivery: Adjust speaking speed and start playing streamed audio before the full utterance is ready.
| You want to… | Start here | What you need |
|---|---|---|
| Generate a speech file | Command line | A desktop binary and a checkpoint. No Python or Rust required for a published binary. |
| Add speech to a script or backend | Python | A Python wheel and a checkpoint. |
| Run speech in a web app | Browser | phonon-tts and model files served with your app. |
| Add speech to an iPhone or Mac app | Swift | The ptts Swift package and a prepared Core ML bundle. |
| Add speech to an Android app | Android | A Kotlin AAR preview for supported Snapdragon NPUs, or the CPU native library. Source build and model files required. |
| Connect an existing app or self-host | Docker and OpenAI-compatible API | Docker and a local checkpoint or HF repo. |
| Embed the runtime in Rust | Rust | The ptts crate and a checkpoint folder. |
Phonon is the product and model identity. The Rust, Python, Swift, and planned Android Maven packages are named ptts; the browser npm package is phonon-tts. This repository remains xn-ptts. Swift apps use import PhononTTS.
Packages and native downloads are available as a runtime preview. Follow the runtime preview release to install the preview. The unversioned commands below are the installation paths for the upcoming stable release.
| Package | Get it | Details |
|---|---|---|
Desktop ptts command |
GitHub Releases or Homebrew tap | Linux x64/ARM64, Mac Apple silicon/Intel, and Windows x64 archives, with checksums. Download guide and Homebrew. |
Python ptts |
PyPI | Wheels for supported platforms; CPython 3.9+. Python guide. |
Browser phonon-tts |
npm | Worker, single-thread and threaded Wasm builds, and TypeScript declarations. Browser guide. |
Swift ptts |
GitHub Releases | ptts-swift-<version>.zip and its matching compiled framework. Swift guide. |
Android ptts (QNN preview) |
QNN Android guide | Kotlin API and AAR for supported Snapdragon NPUs. Not published to Maven Central yet. |
| Android CPU library | Build guide | libptts_ffi.so and a Kotlin wrapper, built from source with Rust and the Android NDK. |
ptts-openai-server |
ghcr.io/gradium-ai/ptts-openai-server:<version> |
CPU image for amd64 and arm64; model weights downloaded or mounted separately. Server guide. |
Rust ptts |
crates.io | Library API; add the cli feature to install the command. Rust guide. |
Desktop downloads and Python wheels do not need a Rust compiler. Older x86 CPUs may need a source build: x86 wheels and desktop downloads target x86-64-v3. See the platform requirements in the CLI guide and Python guide.
Every integration needs an explicitly selected checkpoint. No model is chosen automatically.
The local examples below use a q8 checkpoint folder supplied to you, for example:
model/
config.json
tokenizer.json
model.q8.gguf
default-voice.safetensors
Voice files may instead live in voices/ or embeddings/. Use the files and voice names your checkpoint supplies; do not substitute another model's tokenizer or config. The examples use q8 weights. For f32 weights, omit --quant q8 or quant="q8", use Quant::F32 in Rust, and set quant: 'f32' with weights: { f32: '/model/model.safetensors' } in the browser.
Already received a model from us? Extract it locally and use the matching integration below. You do not need a public Hugging Face repo or an HF token for local files. Runtime packages contain no model weights.
| Files you received | Use them with |
|---|---|
A checkpoint with config.json, tokenizer.json, weights and voices |
Command line, Python, Rust, browser, servers or Android CPU. Use a runtime version compatible with your checkpoint. |
| A prepared Core ML bundle | Swift. Add it to your app as Models, or host it for the Swift download API. A raw checkpoint needs exporting first. |
A QNN bundle with metadata.json and compiled context binaries |
Android NPU. It must match the phone's SoC and the package's QNN runtime. Older bundles may need their target metadata updated; see the preview guide. A raw checkpoint needs exporting and compiling first. |
For the shell examples below, set the folder once:
export MODEL_DIR=/absolute/path/to/modelThe command line and Python can also acquire a checkpoint from Hugging Face. Use its repo ID and a fixed revision when you want a repeatable model version. Private repos require HF_TOKEN or a saved HF login. Local folders require no Hub access.
lang is required and selects text normalization, such as how numbers and symbols are spoken. Choose en, fr, de, es, pt, or none to pass text through unchanged. This setting does not establish which languages a checkpoint can speak.
These examples use the published runtime preview and a supplied checkpoint. Install the preview using the release instructions, or follow the source setup. The unversioned uvx and registry commands target the upcoming stable release.
Show command line quickstart
After the stable release, uv can run the Python command in an isolated environment. For the current preview, use the command in the release instructions:
uvx ptts --model "$MODEL_DIR" --lang en --quant q8 "Hello from Phonon." -o speech.wavuvx manages the Python environment and dependencies.
On Mac or Linux, install the prebuilt desktop command from Gradium's Homebrew tap. It currently installs the 0.4.0-rc.1 runtime preview:
brew install gradium-ai/tap/pttsYou can also extract a desktop archive and put ptts on your PATH. Neither path needs Python or Rust. Generate a WAV with a supplied checkpoint:
ptts --dir "$MODEL_DIR" --lang en --quant q8 "Hello from Phonon." -o speech.wavThe output is a mono 24 kHz WAV. Open it in your audio player.
If you prefer Cargo, install the command with cargo install ptts --locked --features cli after the matching stable release is published.
To download a checkpoint instead, replace the example repo and revision with your own:
ptts --repo OWNER/MODEL --revision COMMIT_SHA --lang en --quant q8 \
"Hello from Phonon." -o speech.wavUse --voice NAME to choose a voice; omitting it uses the checkpoint's default selection. Run ptts --help for the remaining options. CLI guide →
Show Python quickstart
For the stable release, install the Python package into your environment. Current preview users should follow the release instructions:
python -m pip install pttsThen save speech in four lines:
import os
import ptts
tts = ptts.TTS(config=os.environ["MODEL_DIR"], lang="en", quant="q8")
tts.save("speech.wav", "Hello from Phonon.")Reuse tts. tts.synth(text) returns a float32 NumPy array; tts.stream(text) yields audio chunks:
with tts.stream("Speech can play while the rest is being generated.") as audio:
for pcm in audio:
# Send each chunk to your player at audio.sample_rate.
print(pcm.shape)Leaving the with block stops generation and releases its workers. For Hub loading, use config="OWNER/MODEL" and revision="COMMIT_SHA".
The wheel also provides a command:
python -m ptts --model "$MODEL_DIR" --lang en --quant q8 \
"Hello from Phonon." -o speech.wavRust and Python both install a command named ptts, with different flags. python -m ptts explicitly selects Python. Python guide →
Show browser quickstart
For the stable release, install the browser package into your web app. Current preview users should follow the release instructions:
npm install phonon-ttsServe the checkpoint's files under /model/, then generate a WAV:
import { PhononTTS } from 'phonon-tts';
const tts = await PhononTTS.load({
lang: 'en',
model: {
config: '/model/config.json',
tokenizer: '/model/tokenizer.json',
weights: { q8: '/model/model.q8.gguf' },
voices: { default: '/model/default-voice.safetensors' },
},
});
const wav = await tts.synthWav('Hello from Phonon.'); // WAV BlobUse the returned Blob for a download link or your audio player. tts.stream(text) yields mono Float32Array chunks for streaming playback; the browser guide includes a complete Web Audio example. Call tts.dispose() when finished with the model.
The first load downloads and caches the files. onProgress reports download progress. Serve over HTTPS or localhost for caching, and use a browser with WebAssembly SIMD. CPU is the default. Threading and WebGPU setup are covered in the browser guide →.
Show Swift quickstart
Extract ptts-swift-<version>.zip, add the folder to Xcode as a local package, and select the ptts library product. Swift Package Manager downloads the matching compiled framework. This path needs no Rust build.
Add the prepared Core ML model bundle to your app as a folder reference named Models:
import Foundation
import PhononTTS
guard let bundled = Bundle.main.url(forResource: "Models", withExtension: nil) else {
throw PhononError(description: "Add the model folder to your app as Models.")
}
let models = try PhononModels.install(bundled: bundled)
let tts = try await Phonon.load(models: models, language: .english)
let player = try PhononPlayer()
try await player.play(tts.stream("Hello from Phonon."))Keep tts and player in your app's state while audio plays. You can also download models on first run. Requires iOS 18+ or macOS 15+ on Apple silicon. The public Core ML model bundle is still being prepared. Swift guide and Stop example →
Show Android integration options
Snapdragon NPU: The QNN Android preview provides a Kotlin API and an AAR around the optimized QNN engine. It requires Android 12+, ARM64, working QNN HTP support and a model bundle compiled for the phone's SoC. Build the AAR from this repository using its build guide. The planned Maven package is ai.gradium:ptts; it is not published yet.
Load a supplied compiled model folder on a worker thread and stream audio to your player:
import ai.gradium.phonon.PhononTTS
val tts = PhononTTS.load(context, modelDirectory, lang = "en")
val sampleRate = tts.sampleRate
tts.speak("Hello from Phonon.") { pcm ->
audioSink.write(pcm) // Mono float PCM at sampleRate.
true // Return false to stop; tts.stop() also works from another thread.
}
// Reuse tts. Stop and wait for speech to finish before calling tts.close().The preview guide covers native library extraction, model bundles and the runnable Speak/Stop example.
CPU: The existing Android guide provides a Kotlin wrapper and C API. From the repository root:
cargo install cargo-ndk
export ANDROID_NDK_HOME=/path/to/ndk
./android/build.shCopy android/jniLibs into your app's src/main/, then add the Kotlin wrapper and JNA dependency as described in the guide. Supply a checkpoint with its own config, tokenizer, weights and voices. Load PhononTTS(modelDir, "en") on a worker thread and reuse it. The guide includes AudioTrack playback, CPU requirements and callback cancellation.
Show Docker and API quickstart
Choose an image version from GitHub Releases and replace <release-version> below, without the leading v. Public GHCR access is being finalized; until then use an image you can access or the source setup. The Docker commands below use Bash or another POSIX shell. Mount your model folder and start the speech server:
PTTS_IMAGE="ghcr.io/gradium-ai/ptts-openai-server:<release-version>"
docker run --rm -p 127.0.0.1:8880:8880 -v "$MODEL_DIR:/models:ro" \
-e PTTS_CONFIG=/models -e PTTS_LANG=en -e PTTS_QUANT=q8 \
"$PTTS_IMAGE"In a second terminal:
curl http://localhost:8880/v1/audio/speech \
-H "Content-Type: application/json" \
-d '{"input":"Hello from Phonon.","voice":"default","response_format":"wav"}' \
-o speech.wavFor applications with an OpenAI-compatible TTS setting, use http://localhost:8880/v1 as the base URL and a voice your checkpoint supports. The server guide includes client setup recipes, supported formats, and limits.
For HF acquisition and a cache that survives container replacement, use the Hub Compose file. Supply the repo, revision, and normalization language explicitly. The image contains no model weights. Deployment and access controls are in the server guide →.
Show Rust quickstart
Add ptts with tokenizer support to your Rust project. This command targets the upcoming stable release; preview installation is in the release instructions, and source development can use a path dependency:
cargo add ptts --features hfuse ptts::checkpoint::{Checkpoint, ResolveOptions};
use ptts::preprocess::Lang;
use ptts::synth::{DeviceKind, Quant};
fn main() -> Result<(), Box<dyn std::error::Error>> {
let checkpoint = Checkpoint::resolve(
std::env::var("MODEL_DIR")?,
ResolveOptions { quant: Quant::Q80, weights: None },
)?;
let mut tts = checkpoint.builder(Lang::En).device(DeviceKind::Cpu).build()?;
checkpoint.register_voices(&mut tts);
let pcm = tts.say("Hello from Phonon.")?;
ptts::wav::write_wav_file("speech.wav", &pcm, tts.sample_rate())?;
Ok(())
}Build with --release and reuse the model. tts.stream(text)? yields audio chunks for your own playback. The library reads local files; Hub acquisition belongs to the caller. Rust API and small example →
| Topic | Guide |
|---|---|
| Desktop downloads, Homebrew, CPU requirements, CLI flags | Command line |
| Python installation, voices, streaming, and wheels | Python |
| Browser playback, download progress, caching, threads, and WebGPU | Browser |
| Apple installation, model bundles, playback, and cancellation | Swift |
| Android CPU library, Kotlin, NDK, and playback | Android |
| Android Snapdragon NPU AAR, Kotlin API, and compiled bundles | QNN Android |
| Docker, OpenAI-compatible clients, and deployment | HTTP server |
| Streaming text and audio over one connection | WebSocket server |
Rust library and the say example |
Rust |
| Build packages, run the local demo, and develop from source | Development |
Native desktop packages cover the targets listed in the CLI guide. Apple apps use Core ML. Android apps can use the CPU library or the QNN AAR preview on supported Snapdragon NPUs. QNN requires a matching compiled bundle and has no automatic CPU fallback. Browser apps use Wasm on CPU by default. Browser WebGPU is opt in. The npm package targets browsers, not native Node.js inference.
Performance and memory use depend on the checkpoint and device.
Report bugs or request integrations through GitHub Issues. Include your runtime version, device/OS, model revision, weight format, normalization language, and a small reproduction.
Phonon builds on Pocket TTS, developed by Kyutai, and uses the xn Rust tensor runtime. Gradium's Phonon checkpoints are the primary target. Compatible Pocket TTS checkpoints use the same explicit loading interface when they supply their own config, tokenizer JSON, weights, and voices.
The code is MIT OR Apache-2.0, at your option: MIT · Apache-2.0.
Model weights and voices are distributed separately and have their own licenses. Check the model license before redistribution.
Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in this repository is dual licensed as above, without additional terms or conditions.