| --- |
| license: gpl-3.0 |
| language: [en, ne, hi, vi, id, zh] |
| pipeline_tag: text-to-speech |
| tags: [text-to-speech, tiny, microcontroller, esp32, wasm, piper, distillation] |
| library_name: sanotts |
| --- |
| |
| # sanoTTS — a tiny neural voice that runs anywhere |
|
|
| ***sano*** (सानो) — Nepali for **"small."** A family of tiny neural |
| text-to-speech voices — **745k to 1.8M parameters** — that run with |
| **no cloud and no NPU**: real-time on a ~$3 ESP32-S3 (out a GPIO into an |
| LM386 and a speaker), or live in the browser via WASM. |
|
|
|  |
|
|
| | | | |
| | --- | --- | |
| | Smallest neural TTS family known | **745k – 1.8M parameters** | |
| | Runs real-time on a $3 microcontroller | ESP32-S3, out a GPIO into an LM386 | |
| | Runs in the browser | WebAssembly, no server | |
| | Per-voice footprint | under **4 MB**, zero dependencies (espeak-ng phonemizer included) | |
| | Coverage | **9 voices** across **6 languages** — English, Nepali (नेपाली), Hindi (हिन्दी), Vietnamese (Tiếng Việt), Indonesian (Bahasa), Chinese (中文) | |
| | License | open source, **GPL-3.0** | |
|
|
| **Live demo:** [ampixa.github.io/sanoTTS](https://ampixa.github.io/sanoTTS/) — |
| every voice synthesizes your text live in the browser, no server, no upload. |
|
|
| ## Samples |
|
|
| One clip per voice below (a second clip per voice is in this repo's |
| `samples/` folder). "Package here" means this HF repo carries that voice's |
| raw fp16 weights; the three without one currently only ship through the |
| [browser demo](https://ampixa.github.io/sanoTTS/) and the GitHub repo's |
| `web/voices/` — their HF packages haven't been exported yet. |
|
|
| | Voice | Language | Params | SCOREQ | Package here | Sample | |
| | --- | --- | ---: | :---: | :---: | --- | |
| | amy | English 🇺🇸 | 1.46 M | **4.13** | [`amy-en-1p46m/`](https://huggingface.co/ampixa/sanoTTS/tree/main/amy-en-1p46m) | <audio controls src="https://huggingface.co/ampixa/sanoTTS/resolve/main/samples/english-amy-1.mp3"></audio> | |
| | kristin | English 🇺🇸 | 1.40 M | 4.09 | [`kristin-en-1p4m/`](https://huggingface.co/ampixa/sanoTTS/tree/main/kristin-en-1p4m) | <audio controls src="https://huggingface.co/ampixa/sanoTTS/resolve/main/samples/english-kristin-1.mp3"></audio> | |
| | hfc | English 🇺🇸 | 1.83 M | 3.94 | [`hfc-en-1p8m/`](https://huggingface.co/ampixa/sanoTTS/tree/main/hfc-en-1p8m) | <audio controls src="https://huggingface.co/ampixa/sanoTTS/resolve/main/samples/english-hfc-1.mp3"></audio> | |
| | amy-small | English 🇺🇸 | 1.08 M | 3.70 | [`amy-en-1p1m/`](https://huggingface.co/ampixa/sanoTTS/tree/main/amy-en-1p1m) | <audio controls src="https://huggingface.co/ampixa/sanoTTS/resolve/main/samples/english-amy-small-1.mp3"></audio> | |
| | robot (on-device, int8) | English 🇺🇸 | 745 k | — | not packaged here (int8 MCU format, not fp16) | <audio controls src="https://huggingface.co/ampixa/sanoTTS/resolve/main/samples/mcu-745k.mp3"></audio> | |
| | Indonesian | Bahasa | 1.46 M | — | [`id-newstts-1p46m/`](https://huggingface.co/ampixa/sanoTTS/tree/main/id-newstts-1p46m) | <audio controls src="https://huggingface.co/ampixa/sanoTTS/resolve/main/samples/indonesian-1.mp3"></audio> | |
| | Vietnamese | Tiếng Việt | 1.46 M | — | [`vi-vais1000-1p46m/`](https://huggingface.co/ampixa/sanoTTS/tree/main/vi-vais1000-1p46m) | <audio controls src="https://huggingface.co/ampixa/sanoTTS/resolve/main/samples/vietnamese-1.mp3"></audio> | |
| | Nepali | नेपाली | 1.47 M | — | not exported yet — see `web/voices/nepali/` in the GitHub repo | <audio controls src="https://huggingface.co/ampixa/sanoTTS/resolve/main/samples/nepali-1.mp3"></audio> | |
| | Hindi | हिन्दी | 1.50 M | — | not exported yet — see `web/voices/hindi/` in the GitHub repo | <audio controls src="https://huggingface.co/ampixa/sanoTTS/resolve/main/samples/hindi-1.mp3"></audio> | |
| | Chinese | 中文 | 1.50 M | — | not exported yet — see `web/voices/chinese/` in the GitHub repo | <audio controls src="https://huggingface.co/ampixa/sanoTTS/resolve/main/samples/chinese-1.mp3"></audio> | |
|
|
| The "robot" row is the same 745k-parameter model that runs on the ESP32-S3 — |
| bit-exact with the chip's own output. SCOREQ is only reported for the |
| English voices, which share a common eval set; the other languages haven't |
| been scored against a comparable reference yet. |
|
|
| ## Install & use |
|
|
| | Platform | Install | Then | |
| | --- | --- | --- | |
| | Python | `pip install sanotts` | `sanotts say "Hello" --voice amy -o hello.wav` | |
| | Web (npm) | `npm install sanotts-web` | `const tts = await SanoTTS.load(); await tts.synthesize('Hello', {voice:'amy'})` | |
| | Web (no build) | copy `dist/` + `voices/` | see [Deploy on your own site](https://github.com/Ampixa/sanoTTS#deploy-on-your-own-site) in the GitHub README | |
| | Arduino / PlatformIO | zip-install or `lib_deps = https://github.com/Ampixa/sanoTTS.git` | [`arduino/README.md`](https://github.com/Ampixa/sanoTTS/blob/master/arduino/README.md) | |
| | Hugging Face | this repo | voice packages above, `manifest.json` + `weights.fp16.bin` per voice | |
| | Browser | nothing | [ampixa.github.io/sanoTTS](https://ampixa.github.io/sanoTTS/) | |
|
|
| Pip voices: `amy`, `amy-1p1m`, `amy-1p8m`, `kristin`, `hfc`, `vi`, `id` — |
| fetched from the [voices-v1 release](https://github.com/Ampixa/sanoTTS/releases/tag/voices-v1) |
| into `~/.cache/sanotts/`. Pure numpy inference, no torch, no onnxruntime. |
|
|
| ## How it stacks up |
|
|
| Open small-scale TTS on an honest gate — a diverse 24-sentence set scored |
| with the **same** no-reference suite (SCOREQ / UTMOS are naturalness |
| predictors, DNSMOS-SIG is signal quality; higher is better). Parameter |
| counts are inference-time and exclude the shared external G2P. |
|
|
|  |
|
|
| Kokoro is 45x larger than our largest voice, and 110x larger than our |
| smallest. Shipped-file sizes: sanoTTS amy 2.8 MB fp16 and TinyTTS 3.5 MB |
| fp16, both verified from the released files; Kokoro's ~330 MB fp32 is its |
| widely cited public figure. |
|
|
| | System | Params | SCOREQ | UTMOS | DNS-SIG | |
| | --- | ---: | :---: | :---: | :---: | |
| | **sanoTTS (amy)** | **1.46 M** | **4.13** | **4.10** | 3.61 | |
| | TinyTTS | 1.62 M | 3.94 | 3.65 | **3.62** | |
| | Inflect Nano | 4.63 M | 3.81 | 3.65 | 3.58 | |
| | Kitten TTS nano | 15 M | 3.02 | 3.58 | 3.43 | |
| | _reference (~15 M)_ | _~15 M_ | _4.71_ | _4.47_ | _3.65_ | |
| | _Kokoro_ | _82 M_ | _4.89_ | _4.52_ | _3.69_ | |
|
|
| sanoTTS is the **smallest** model here and the **best on naturalness |
| (SCOREQ and UTMOS) among everything up to 15M params** — beating TinyTTS |
| while being smaller. On DNSMOS-SIG, TinyTTS edges us by 0.01 — no single |
| metric tells the whole story. It's the only one that runs a full neural |
| stack on a $3 MCU. The frontier only pulls ahead at ~15M-class models and |
| Kokoro (82M, 60x larger) — a gap we don't claim to close. Reproduce it with |
| `tools/eval_mos_all.py` + `tools/eval_scorecard.py` in the GitHub repo. |
|
|
| ## How it works |
|
|
|  |
|
|
| espeak-ng provides phoneme IDs; a duration model predicts timing; an |
| acoustic model predicts generator latents; a decoder renders 22 kHz audio. |
| The web voices (amy, kristin, hfc, and the other languages) use a compact |
| time-domain decoder running in fp32; the 745k on-device model instead uses |
| a quantized int8 iSTFT decoder, sized to fit and run in real time on the |
| ESP32-S3. All models are distilled from a Piper/VITS teacher — see |
| [`docs/distillation-recipe.md`](https://github.com/Ampixa/sanoTTS/blob/master/docs/distillation-recipe.md) |
| in the GitHub repo for the full recipe. |
|
|
| ## Deploy |
|
|
| - **ESP32-S3 talking device** — a standalone WiFi dashboard: type text, the |
| board phonemizes (on-chip espeak-ng) and speaks. See |
| [`mcu/ports/esp32s3/`](https://github.com/Ampixa/sanoTTS/tree/master/mcu/ports/esp32s3). |
| - **Browser** — the full stack in WASM, no server. **[▶ Hear and synthesize |
| all 9 voices live](https://ampixa.github.io/sanoTTS/)**; source in |
| [`web/`](https://github.com/Ampixa/sanoTTS/tree/master/web). |
| - **Other MCUs** — which chips can run it and how well: |
| [`docs/mcu-classes-and-porting.md`](https://github.com/Ampixa/sanoTTS/blob/master/docs/mcu-classes-and-porting.md). |
|
|
| ## Links |
|
|
| - Source, recipes, eval tooling: [github.com/Ampixa/sanoTTS](https://github.com/Ampixa/sanoTTS) |
| - Live browser demo: [ampixa.github.io/sanoTTS](https://ampixa.github.io/sanoTTS/) |
| - npm package: [sanotts-web](https://www.npmjs.com/package/sanotts-web) |
| - PyPI package: [sanotts](https://pypi.org/project/sanotts/) |
|
|
| ## License |
|
|
| GPLv3 — see [`LICENSE`](https://github.com/Ampixa/sanoTTS/blob/master/LICENSE). |
| The pipeline builds on GPLv3 components (notably |
| [espeak-ng](https://github.com/espeak-ng/espeak-ng) for G2P, and |
| [piper](https://github.com/OHF-Voice/piper1-gpl)), so the project as a |
| whole is GPLv3. |
|
|
| Copyright (C) 2026 Ampixa. |
|
|
| ## Files here |
|
|
| Each voice directory is a self-contained fp16 package (`weights.fp16.bin` + |
| `manifest.json` + `piper-phoneme-config.json` + sibilant-injection |
| calibration where applicable), consumable by the `sanotts` Python package |
| and the portable C runtime. `samples/` holds the audio clips embedded |
| above (mp3, one or two per voice + the on-device `mcu-745k.mp3`). |
|
|