# D25. Free local voices: Kyutai's Pocket TTS, run with ONNX Runtime (8 Oct 2026)

Generated narration (D24) needs a free voice that sounds the same on every computer and whose audio a course may carry commercially, always, next to a paid service later. The operating systems' voices are out: macOS's licence (Sequoia, §2.F) allows them for personal, non-commercial use only and forbids recording and publishing them, Windows' terms say nothing, and they differ from one system to the other. Measured on a narrated sample scene, five takes of each line heard by Whisper: Kyutai's Pocket TTS (code MIT, weights CC-BY 4.0, no phonemizer) reads French best, 24 takes in 25 word for word with its 24-layer French model, the only one to read « 2e étage », and « 9 h 30 », « P-12 », « 8 h 52 » unaided; Supertonic 3 (OpenRAIL-M, archived by its publisher in October 2026) 17, Piper's only French voice not trained on a non-commercial one 6; Kokoro has one French voice. Both need espeak-ng (GPL).
- **The runtime.** Kyutai's generation ported to TypeScript (`@elabora/node` `voice/pocket/`): its SentencePiece Unigram tokenizer read from `tokenizer.json` (the same ids as Hugging Face's tokenizers on 615 sentences), its text preparation and chunking, the flow language model, its one-step flow head and the Mimi decoder, run by **`onnxruntime-node` 1.23.2**, a new runtime dependency, optional: required when a voice speaks, so Elabora runs without it. 1.23.2 is the last version with a binary for Intel Macs (1.24 and later ship darwin/arm64 alone); the graphs need opset 19 at most. Only its install script uses `adm-zip` and `global-agent`, and npm 12 does not run it by default: the root `overrides` raise them to adm-zip 0.6.1 and global-agent 4 (which no longer pulls in roarr and sprintf-js), past their security advisories, and the voices keep 1.23.2. Kyutai's details kept: the text prepared as its configs say (« : » becomes « , », quotes removed; without it a line with a colon came out garbled three times in five), no end of speech before 6 frames, voice prompts ending on 80 ms of silence (`end_on_pause`), the decoder's first 5 ms faded in. Added: a chunk that runs to its longest length without the model ending it, or comes out silent, is said again with other noise, twice at most. Temperature 0.5: Kyutai's 0.3 let some voices fall silent, 0.7 slipped more words. A take is seeded: the same take sounds the same on the same computer.
- **The model files.** KevinAHM's ONNX export of Kyutai's weights (CC-BY 4.0, int8). French is Kyutai's `french_24l` of April 2026 (revision d29db79; its tokenizer published as `tokenizer.json` in revision 00eac05), 354 MB; English `english_2026-04`, 126 MB. Downloaded on the first use of a language into the computer's local data (`<local app data>/voices/pocket-tts/<bundle>/`, or `ELABORA_VOICE_MODELS_DIR`; `ELABORA_VOICE_MODELS_URL` for a mirror), each file pinned to a revision and checked against its SHA-256, resumed where an interruption left it, one download at a time across processes. Never in a course, a repository or the installer (it would more than triple). The voice encoder stays out: voices are made once, by Elabora.
- **The voices.** Made from recordings given for speech synthesis: first the voice donations of Kyutai's Unmute project (CC0; donors read their consent to cloning) and Kyutai's own recordings (CC0); a reader of the CML-TTS corpus (CC-BY 4.0) only to have two women in French, since its readers gave recordings, not their voice to a synthesis, and in France a voice is an attribute of the person. Of the 228 donations, 4 speak French, all men. Every voice kept passed the bench, five takes of six lines heard by Whisper: 80 to 95 % of takes word for word, the misses mostly a first word swallowed, which another take fixes (`--line`; the app's « Prise 1 2 3 »). French: Estelle and Claire, Henri, Rémy and Jérôme; English: Grace, Nora, Iris, Owen and Felix. The names are invented, never a donor's. `packages/node/voices/pocket-tts/`: `sources.json` by hand (each recording pinned by URL and SHA-256, its licence, the credit), `voices.json` and the embeddings (float16, 170 to 310 KB a voice) written by `scripts/pocket-voices.ts`. Authors do not clone their own voice in phase 1: that needs a consent the tool can check.
- **The interface.** `VoiceProvider` (`voices`, `status`, `install`, `synthesize`, `dispose`; `model`, `uninstall` and `hold` since D37), Pocket TTS the first; a paid service with the author's key comes in the same place. `elabora voice list|install|set|generate|takes`, MCP `elabora_voice_list`, `elabora_voice_set`, `elabora_voice_generate`, `elabora_voice_takes`, `elabora_voice_install`; for the app, engine requests `voices`, `installVoices`, `setSpeakerVoice`, `generateVoice`, `voiceTakes`, `cancelVoice`, `voiceSample` and `voiceProgress` events, written through the session's queue. The model speaks in a worker thread (11 Oct 2026, for the app's editor): ONNX Runtime runs each step synchronously, up to 150 ms, and loading the model holds a thread most of a second, which froze the engine's other requests while a screen was said; in its thread the engine's longest wait is a few milliseconds, and a generation stops between two steps (`cancelVoice`; the lines said so far stay). The thread lets the process exit when idle. A speaker's voice is `course.json` `speakers[].voices[<language>]`. Each generation is a take kept in the project's data (`voice-takes/`), one of them the line's audio through `setNarrationAudio` (D24: `take`, `credit`, `model`); `--take` puts an earlier one back. Speed (`settings.speed`, 0.8 to 1.25) by ffmpeg's `atempo`; pauses are the script's (`pauseBefore`); no word times yet (phase 2: forced alignment, which recorded voices need too).
- **Costs.** About 2.7 times real time on an Apple M5 at rest, 2 when other work loads it (two threads as fast as ten: the model steps one frame at a time), estimated 1 to 1.5 on a mid-range Windows laptop: a screen of five lines, 22 s of voice, in 15 to 20 s. About 1.6 GB of memory while a French voice speaks (the model, the voice's state and two working copies of the 200 MB attention cache; English, 6 layers, far less), freed after two idle minutes. On Windows, ONNX Runtime's DLLs need Microsoft's Visual C++ 2015–2022 runtime (MSVCP140, VCRUNTIME140), which Electron does not carry: a computer without it is told what to install until the installer installs it. Kyutai (CC-BY 4.0) is credited in the app's licences (`voix.txt`) and each voice in the credits of the modules that use it; generated audio is marked as such (AI Act, art. 50).
*Revisit if:* Kyutai's newer French weights (September 2026, another tokenizer) get an ONNX export with their voice encoder (in Kyutai's gated repository), authors need their own voice, word times are needed (phase 2), or Intel Macs no longer matter or get a binary again.

*Revised by [D54](D54.md) (17 Oct 2026):* Intel Macs retain media tools and existing narration, but no longer install or run local AI. The older ONNX Runtime constraint motivated by Intel support no longer applies; a runtime upgrade still needs validation on the supported platforms.

*Revised by [D57](D57.md) (19 Oct 2026):* the Pocket TTS graphs are downloaded from Elabora's mirror of KevinAHM's repository (`elabora/pocket-tts-onnx`, the same bytes, sizes and SHA-256, so the installed models stay current); the upstream is named beside the constant. Kyutai's tokenizers stay with Kyutai.
