---
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) | |
| kristin | English 🇺🇸 | 1.40 M | 4.09 | [`kristin-en-1p4m/`](https://huggingface.co/ampixa/sanoTTS/tree/main/kristin-en-1p4m) | |
| hfc | English 🇺🇸 | 1.83 M | 3.94 | [`hfc-en-1p8m/`](https://huggingface.co/ampixa/sanoTTS/tree/main/hfc-en-1p8m) | |
| amy-small | English 🇺🇸 | 1.08 M | 3.70 | [`amy-en-1p1m/`](https://huggingface.co/ampixa/sanoTTS/tree/main/amy-en-1p1m) | |
| robot (on-device, int8) | English 🇺🇸 | 745 k | — | not packaged here (int8 MCU format, not fp16) | |
| Indonesian | Bahasa | 1.46 M | — | [`id-newstts-1p46m/`](https://huggingface.co/ampixa/sanoTTS/tree/main/id-newstts-1p46m) | |
| Vietnamese | Tiếng Việt | 1.46 M | — | [`vi-vais1000-1p46m/`](https://huggingface.co/ampixa/sanoTTS/tree/main/vi-vais1000-1p46m) | |
| Nepali | नेपाली | 1.47 M | — | not exported yet — see `web/voices/nepali/` in the GitHub repo | |
| Hindi | हिन्दी | 1.50 M | — | not exported yet — see `web/voices/hindi/` in the GitHub repo | |
| Chinese | 中文 | 1.50 M | — | not exported yet — see `web/voices/chinese/` in the GitHub repo | |
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`).