Voice in the Browser
A cloned voice that runs entirely locally on this device. Nothing is sent anywhere.
Type something (or keep the example) and press Speak. The first time, the voice model downloads (about 160 MB); after that it starts quickly.
Please be patient: the first time, the voice model (about 160 MB) has to download, and speech is made right here on your device, so it can take a little while to start, longer on phones.
About the “dad” voice
This is my dad's voice. He passed away on January 7, 2026. For a few years he volunteered at WDBX, the community radio station in Carbondale, Illinois, where he was on the air: playing music, sharing news items that caught his eye, and telling stories, including reading his mother's letters about the family's years in Germany after the war. Every voice on this page was trained from those radio recordings.
How the voices are made
Each voice is cloned from a short set of real recordings of one person. The model on this page, Kokoro, is the smallest of several we train per voice. It's the least accurate, but it's small enough to run entirely in your browser, on your own device: nothing is sent to a server. The larger models sound closer to the real person, but they need a graphics card (GPU) and much bigger files.
| Model | Size | Runs on | Notes |
|---|---|---|---|
| Kokoro-82M (this page) | 82M parameters, 164 MB download | any modern browser, CPU or GPU | fastest and smallest; least accurate |
| Qwen3-TTS 0.6B | ~1 GB (8-bit) | a GPU; a mini PC's integrated GPU manages ~1.4× real time | good balance of quality and speed |
| Qwen3-TTS 1.7B | ~2 GB (8-bit) | a GPU; about real time on the mini PC | closer to the real voice, slower |
| Dots.TTS (the teacher) | 2.2B parameters, ~9 GB | a large GPU (48 GB used for training) | the best sounding, 48 kHz; too big for everyday use |
The recipe: a big teacher trains small students
- Real recordings. A few minutes to half an hour of the person speaking, with checked transcripts, cut into short segments. A few segments are held back from all training, so the models can be tested on sentences they never heard.
- The teacher. Dots.TTS, a large open model, is fine-tuned on those recordings (minutes on a rented GPU) until it sounds like the person. We listen to several checkpoints and keep the best, sometimes a mix of them.
- Making more speech. The teacher reads thousands of lines in the cloned voice: everyday sentences, stage-play dialogue and story narration, taken only from sources that are free for any use (public-domain books and plays, Tatoeba's CC0 sentences, and lines written for the project).
- Quality filter. A speech recognizer (Whisper) transcribes every line; takes with misread words, odd speed or clipping are dropped, and everything is levelled to one volume.
- The students. Qwen3-TTS (0.6B and 1.7B) and Kokoro are trained on the teacher's hours of speech plus the real recordings. They learn the voice from hours of examples instead of minutes.
Listen: the real voice next to each model
Each row is one sentence. The first column is the original recording we started from (where one exists); the others are the same sentence spoken by each model. Press play on any of them.
By the numbers
What we measured while making the “dad” voice. Speed is given as seconds of work per second of speech (the “RTF” shown under the player): below 1 is faster than real time, 0.5 means a minute of speech takes 30 seconds. The mini PC is a Beelink SER5 (AMD Ryzen, integrated graphics); the rented GPU is an NVIDIA L40S (48 GB) at about $0.73 an hour.
How fast each model speaks
| Model | Where | Seconds per second of speech |
|---|---|---|
| Dots.TTS 2.2B (teacher) | rented L40S GPU, compiled, 4 copies at once | ~0.2 combined (6 h of speech in ~1.1 h) |
| rented L40S GPU, one copy, not compiled | ~3.2 | |
| mini PC CPU | ~20 (and it runs hot); for tests only | |
| Qwen3-TTS 1.7B | mini PC graphics chip (Vulkan) | ~1.0 (about real time) |
| Qwen3-TTS 0.6B | mini PC graphics chip (Vulkan) | ~0.7, first audio in ~0.15 s |
| Kokoro-82M | mini PC CPU (Python, voice CLI) | ~0.3–0.4 |
| this page, Chrome on the mini PC | ~0.7–0.8 | |
| this page, a Windows desktop (16 threads) | ~1.0 | |
| this page, iPhone 13 Pro (Safari or Messenger) | ~1.9 (one thread on iPhone) | |
| this page, Android phone in Chrome (multi-threaded) | ~1.2 | |
| this page, Android inside Messenger | ~2.3 (in-app browsers allow only one thread) |
Example: narrating a whole book
The Adventures of Tom Sawyer is about 70,000 words, roughly 8 hours of narration.
| Model | Where | Time for ~8 h of audio |
|---|---|---|
| Dots.TTS | rented L40S | ~1.5 h of generating, ~2.5–3 h of rental with setup and re-takes (a few dollars) |
| Qwen3-TTS 1.7B | mini PC graphics chip | ~8 h |
| Qwen3-TTS 0.6B | mini PC graphics chip | ~5.5 h |
| Kokoro-82M | mini PC CPU | ~2.5–3 h |
Training, step by step (one rental session)
| Step | Time on an L40S | Details |
|---|---|---|
| Setup | ~10 min | install the tools for all models |
| Fine-tune the Dots teacher | ~5 min (300 steps) | ~1.2 steps/s; uses ~45 of 48 GB of GPU memory, so it needs a 48 GB card. A longer 500-step run: ~25 min with test lines |
| Teacher reads the text | ~1.1 h | 2,559 lines → 5.7 h of speech (4 copies in parallel, ~6.5 min of compiling first) |
| Whisper quality filter | ~8 min | 5.2 h kept (about 92%) |
| Train Kokoro | ~1.5 h | 6 + 6 epochs at 6–8 min each; ~28 GB of GPU memory |
| Train Qwen 1.7B and 0.6B | ~1 h | 5 epochs each (0.6B ~3 min per epoch) |
| Voice every checkpoint | ~15 min | 7 test lines per checkpoint, for listening |
| Whole project | about $5–8 of GPU rental in total, including earlier experiments | |
Data and file sizes
| What | Amount |
|---|---|
| Real recordings | 9 radio clips, 17.8 min → 111 training clips + 9 held out (3–15 s each) |
| Teacher speech | 5.2 h after filtering, about 17× the real recordings |
| Dots.TTS checkpoint | 9.6 GB (26 GB while training, with optimizer state) |
| Qwen3-TTS 1.7B / 0.6B | 4.3 / 2.4 GB as trained; 2.0 GB / ~1 GB as 8-bit files for the mini PC |
| Kokoro-82M | 328 MB as trained; this page downloads 164 MB (16-bit) or 325 MB (32-bit), cached after the first time |
Side quest: a smaller teacher (LoRA)
The fine-tuned Dots teacher is a 9.6 GB file, and every training session on a rented GPU needs it. Most of those gigabytes are the public base model, which the GPU can download for free; only the change made by fine-tuning is ours. So we tried shipping just that change in compressed form, as a LoRA (low-rank adapter).
We didn't train a LoRA; we extracted one from the finished fine-tune. For every weight we subtracted the base model from the fine-tuned one. The 475 large matrices were compressed with an SVD, keeping only their strongest r directions (the “rank”); the 391 small weights were kept in full. Base model + adapter rebuilds an approximation of the teacher.
| Rank | Adapter size | Share of the change kept | By ear |
|---|---|---|---|
| 32 | 147 MB | 55% | not voiced |
| 64 | 290 MB | 67% | clearly off |
| 128 | 576 MB | 78% | clearly off |
| 256 | 1.15 GB | 88% | closest, but audible artifacts on longer lines |
| full fine-tune | 9.6 GB | 100% | the reference |
Same sentence, same random seed, so the only difference is the model:
| Model | “The causes of the earth's axis tilting…” | The radio intro (15 s) |
|---|---|---|
| full fine-tune (step 300) | ||
| LoRA rank 256 | ||
| LoRA rank 128 | – | |
| LoRA rank 64 | – |
What we did instead: fine-tuning Dots takes only about 5 minutes on the rented GPU, and with the same settings and random seed it lands on the same voice. So each session downloads the public base model and simply retrains the teacher; all we upload is a ~150 MB bundle of scripts, text and the real recordings. The LoRA route is still in the pipeline as an option, and a LoRA trained from the start (rather than extracted afterwards) might do better.
Running the bigger models: the voice CLI
The larger models run on a home PC through a small command-line tool, voice. Every voice is a
folder (voices/<name>/) holding its checkpoints for each engine and a few reference clips;
adding a voice means adding a folder. The tool lists what's there, speaks text with any model and narrates
whole books.
$ voice list dad ├─ qwen-tts (24 kHz, GPU via Vulkan; size = one runnable GGUF; checkpoints are BF16, 2.4/4.3 GB) │ rating params runs as size direct hybrid note │ ├─ 0.6B-student-epoch4 ★★★☆☆ 0.6B Q8_0 GGUF 969 MB ready ready good; volume swings │ └─* 1.7B-student-lev-epoch4 ★★★★☆ 1.7B Q8_0 GGUF 2.0 GB ready ready chosen 1.7B: consistent ├─ dots (48 kHz, CPU only here, slow; the teacher) │ rating params runs as size direct hybrid note │ ├─ retrain500-step300 ★★★★★ 2.2B FP32 9.6 GB ready ready #2: best breathing/pauses │ └─ retrain500-step500 ★★★★★ 2.2B FP32 9.6 GB ready ready #1 teacher: best overall ├─ kokoro (24 kHz, CPU, fast; the web page uses ONNX exports: FP16 164 MB / FP32 325 MB) │ rating params runs as size direct hybrid note │ └─ b4-epoch0 ★★★★☆ 82M FP32 328 MB ready - sounds most like him └─ refs: 10s, 2min $ voice rate "1.7B lev" 5 "my pick" # ratings live next to each model (notes.json) $ voice say -m "1.7B lev" "Good evening, and welcome back to the show." out/voice/20261024-171200-dad-1.7B-student-lev-epoch4.wav (3.4 s audio, RTF 1.0) $ voice say -m "dots retrain500 step500" --seed 2 "..." # best quality, slow on a CPU $ voice say --ref 10s "..." # hybrid: clone from a reference clip $ voice book my-novel.txt --mp3 --tempo 0.9 # chapters -> MP3, resumable $ voice serve # this page, on the home network
Reading the list. params is the model size; runs as is the format it runs in on the PC (Qwen is converted to 8-bit GGUF files for its graphics chip; Dots and Kokoro run at full FP32 precision on the CPU); size is what one runnable copy takes on disk; rating and note are our own listening verdicts, stored next to each model so the best ones are easy to find later.
Direct vs hybrid. A fine-tuned Qwen model can speak the voice two ways. Direct: the voice is built into the model as a named speaker, so it needs nothing else; this is the normal, fastest way. Hybrid: the same fine-tuned model is combined with Qwen's speaker encoder, a part that listens to a short reference recording and clones whatever voice it hears in it. Then you pass a clip (e.g. 10 seconds of the person) with each request, and the model imitates that clip on top of what it learned in training: useful for steering style (a calmer or livelier take), at the cost of processing the clip first. Dots can clone from a reference too; Kokoro can't (its voice is a fixed "voicepack" made from the real recordings).
- Qwen models run on the PC's graphics chip through Vulkan (qwentts.cpp): the 0.6B model is faster than real time on a mini PC with integrated graphics.
- Kokoro runs on the CPU (and in the browser, as on this page). Dots runs on the CPU here only for testing: it needs a big GPU to be practical.
- Every command prints the exact underlying command it runs, so results are easy to reproduce.
Notes: making it work on iPhone
The same page that ran fine on Android, Windows and Linux needed a string of fixes for iPhone and iPad, where every browser (Safari, Chrome, Firefox) runs on Apple's WebKit engine. In the order we hit them:
| What happened | Why | What we did |
|---|---|---|
| Opened from a Messenger link: nothing worked, even the recorded examples | A bug of ours stopped the page script in in-app browsers; the other iPhone problems below applied there too | Fixed the bug; with the fixes below it now runs inside Messenger as well, on iPhone and Android |
| Pressing Speak did nothing, not even a status message | Safari's audio system stalls if it's set up before a tap, at a fixed sample rate | Start audio on the first tap, at the phone's own rate, and never wait on it |
| Stuck on “Generating the first sentence…” forever | The pronunciation engine (eSpeak, inside kokoro-js) unpacks its dictionary with a stream feature Safari doesn't have, so it waits forever without an error | Add the missing feature (a small polyfill) before kokoro-js loads |
| Hangs when the engine ran on the page itself | Multi-threaded WebAssembly and eSpeak wait in ways Safari's main thread can't | Run the speech engine in a background worker, single-threaded on iOS (Safari can't start the extra threads' workers from inside a worker) |
| The tab reloaded itself as soon as the voice model ran (the official kokoro-js demo too) | A WebKit bug in Safari 26.2+: kokoro-js loads ONNX Runtime's WebGPU-capable (“JSEP”) build even on the CPU, and WebKit's compiler runs away on that file until iOS kills the tab (onnxruntime #26827) | On iPhone and iPad, load ONNX Runtime's plain WebAssembly build of the same version instead. It works. |
We weren't the only ones: other projects using Kokoro in the browser report the same Safari stream issue, thread hangs and iPhone tab crashes; the plain WebAssembly build was the workaround that made Kokoro run on iPhone.
References
The open projects, models and data this was built on.
Voice models
- Dots.TTS (the teacher): github.com/studio-dots-ai/dots.tts; model dots-studio/dots.tts-soar
- Qwen3-TTS (0.6B / 1.7B students): github.com/QwenLM/Qwen3-TTS; models Qwen3-TTS-12Hz-0.6B-Base, 1.7B-Base
- Kokoro-82M (the in-browser student): hexgrad/Kokoro-82M; browser build onnx-community/Kokoro-82M-v1.0-ONNX, kokoro-js
- StyleTTS 2 (the architecture Kokoro is based on): github.com/yl4579/StyleTTS2
Where the models are hosted
- The in-browser models are hosted on Hugging Face under huggingface.co/terry-sarana: one repository per training epoch, terry-sarana/kokoro-dad-epoch5 (the default here) and epoch0 · 1 · 2 · 3 · 4 (Advanced tab). Each holds the ONNX model (fp16 and fp32) and the voice file; your browser downloads one once and caches it.
Tools
- kikiri-tts (Kokoro fine-tuning recipe): github.com/semidark/kikiri-tts
- qwentts.cpp (runs Qwen3-TTS on a PC's GPU via Vulkan): github.com/ServeurpersoCom/qwentts.cpp
- Whisper (quality filter): openai/whisper-large-v3-turbo; whisper.cpp for local transcription
- ONNX Runtime Web (runs Kokoro in this page): onnxruntime.ai
Training text (free for any use)
- Project Gutenberg: public-domain books and plays (Twain, Jerome, O. Henry, Leacock, Conan Doyle, Wilde, Shaw)
- Tatoeba: everyday English sentences from its CC0 export
Compute
- Training on a rented NVIDIA L40S (48 GB) via vast.ai; everything else on a small home PC