ampixa/sanoTTS
sanoTTS — a tiny neural voice that runs anywhere
*sano (सानो) — Nepali for "small." A family of tiny neural text-to-speech voices — 294k to 2.27M 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.

Live demo: tts.ampixa.com/sanoTTS — every voice synthesizes your text live in the browser, no server, no upload.
Download
Both packages stream their weights from this repo by default.
import sanotts
sanotts.synthesize("Hello world", voice="heart") # downloads heart/ from hereimport { SanoTTS } from 'sanotts-web'; // fetches web/voices/ from here
const tts = await SanoTTS.load();
const result = await tts.synthesize('Hello world', { voice: 'heart' });Python needs sanotts >= 0.3.0, the browser sanotts-web >= 0.3.0. Both fall back to the GitHub releases or the Pages host if Hugging Face cannot be reached, so an outage here cannot break an install. Python packages land in ~/.cache/sanotts/; set SANOTTS_VOICE_SOURCE=hf or =github to pin one host. In the browser, passing voiceBase yourself turns the fallback off, so a self-hosted deployment never quietly reaches back to our servers.
Samples
Two clips per voice are in this repo's samples/ folder; one is embedded below. "Package here" means this repo carries that voice's weights. The three languages without one currently ship only through the browser demo and the GitHub repo's web/voices/ — their packages haven't been exported yet.
SCOREQ is a no-reference naturalness predictor, higher is better; it is only reported for the English voices, which share one 24-sentence eval set. The other languages haven't been scored against a comparable reference yet.
Size does not order this table, and we are not going to pretend it does. amy at 1.46M scores higher than heart at 2.27M because they come from different teachers and different architectures, not because one is bigger. amy and the voices above it are the piperlite lineage, distilled from a Piper/VITS teacher at 22.05 kHz. heart and heart-nano are the newer nano lineage — 24 kHz, mel-100 → ConvNeXt1D → iSTFT, distilled from a Kokoro teacher through a frozen Vocos — which is the one that quantizes to int8 and runs on a microcontroller. The two are not interchangeable and the sanotts package picks the right runtime for you.
The heart and heart-nano scores were re-measured on 2026-09-04; the eval set, checkpoint hashes, exact commands and all 24 per-clip scores are in `evidence/heart-diverse24-remeasure-20260904.json`. They were scored on the float32 reference render. The shipped heart-nano int8 export tracks that render at 0.981 waveform correlation, and heart ships as float32 because its int8 export reached only 0.951 against a 0.98 gate.
The "robot" row is the 567,008-parameter model that runs on the ESP32-S3.
Install & use
Pip voices: heart, hfc, amy-1p8m, amy, kristin, vi, id, amy-1p1m, heart-nano. 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.
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 Piper, the ~15M teacher we distilled from, and Kokoro (82M, 56× 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.
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.
How it works

espeak-ng provides phoneme IDs; a duration model predicts timing; an acoustic model predicts generator latents; a decoder renders audio. The piperlite voices (amy, kristin, hfc, and the other languages) use a compact time-domain decoder running in fp32 at 22.05 kHz. The nano voices (heart, heart-nano) and the 567,008-parameter on-device model use an iSTFT decoder at 24 kHz, quantized to int8 where it has to fit and run in real time on the ESP32-S3. Models are distilled from a Piper/VITS or Kokoro teacher — see `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/`. Board-by-board measurements are in `BOARDS.md`; the silicon figures there are for the
en_us_e12nanolineage, which is a sibling ofheart-nano, not the same weights. - Browser — the full stack in WASM, no server. [▶ Hear and synthesize all 11 voices live](https://tts.ampixa.com/sanoTTS); source in `web/`.
- Other MCUs — which chips can run it and how well: `docs/mcu-classes-and-porting.md`.
Links
- Source, recipes, eval tooling: github.com/Ampixa/sanoTTS
- Live browser demo: tts.ampixa.com/sanoTTS
- npm package: sanotts-web
- PyPI package: sanotts
License
GPLv3 — see `LICENSE`. The pipeline builds on GPLv3 components (notably espeak-ng for G2P, and piper), so the project as a whole is GPLv3.
Copyright (C) 2026 Ampixa.
Files here
Two package layouts, because there are two graphs.
piperlite (amy-en-1p46m/, kristin-en-1p4m/, hfc-en-1p8m/, amy-en-1p1m/, id-newstts-1p46m/, vi-vais1000-1p46m/) — a flat fp16 blob addressed by manifest offsets:
manifest.json weights.fp16.bin
piper-phoneme-config.json (+ sibilant-injection calibration where applicable)nano (heart/, heartnano/) — the mel-100 stack, two blobs plus the generated offset header:
meta.json lineage, per-file sha256, sample rate, vocab
front_*.bin duration + acoustic
model_*.bin decoder
nano_q8_meta.h tensor offsetsheartnano/ ships *_q8.bin (int8, 345,232 bytes total); heart/ ships *_f32.bin (float32, 9,137,920 bytes) because its int8 export missed the fidelity gate. Both layouts are consumed by the sanotts Python package and the portable C runtime.
`web/voices/` is a third thing and exists for the browser. The piperlite voices ship there as front_f32.bin + dec_f32.bin, a different artifact from the weights.fp16.bin the Python package reads, which is why the same voice appears twice in this repo under two names. It mirrors web/ in the GitHub repo byte for byte and is what sanotts-web fetches. The nano voices are not duplicated: web/voices/heart/ and heart/ hold the same blobs.
samples/ holds the audio clips embedded above (mp3, one or two per voice, plus the on-device mcu-567k.mp3), and evidence/ the eval report behind the heart scores.
