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.

text in β†’ ESP32 β†’ speech out

Parameter range 294,279 – 2,272,145
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 337 KB to 8.7 MB, zero dependencies (espeak-ng phonemizer included)
Coverage 11 voices across 6 languages β€” English, Nepali (ΰ€¨ΰ₯‡ΰ€ͺΰ€Ύΰ€²ΰ₯€), Hindi (ΰ€Ήΰ€Ώΰ€¨ΰ₯ΰ€¦ΰ₯€), Vietnamese (TiαΊΏng Việt), Indonesian (Bahasa), Chinese (δΈ­ζ–‡)
License open source, GPL-3.0

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 here
import { 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.

Voice Language Params SCOREQ Package here Sample
heart English πŸ‡ΊπŸ‡Έ 2.27 M 3.48 heart/
hfc English πŸ‡ΊπŸ‡Έ 1.83 M 3.94 hfc-en-1p8m/
amy English πŸ‡ΊπŸ‡Έ 1.46 M 4.13 amy-en-1p46m/
kristin English πŸ‡ΊπŸ‡Έ 1.40 M 4.09 kristin-en-1p4m/
amy-small English πŸ‡ΊπŸ‡Έ 1.08 M 3.70 amy-en-1p1m/
robot (on-device, int8) English πŸ‡ΊπŸ‡Έ 567 k β€” not packaged here (int8 MCU format)
heart-nano English πŸ‡ΊπŸ‡Έ 294 k 2.29 heartnano/
Indonesian Bahasa 1.46 M β€” id-newstts-1p46m/
Vietnamese TiαΊΏng Việt 1.46 M β€” 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

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

Platform Install Then
Python pip install sanotts sanotts say "Hello" --voice heart -o hello.wav
Web (npm) npm install sanotts-web const tts = await SanoTTS.load(); await tts.synthesize('Hello', {voice:'heart'})
Web (no build) copy dist/ + voices/ see 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
Hugging Face this repo the voice packages above, downloaded for you by pip install sanotts
Browser nothing tts.ampixa.com/sanoTTS

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.

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
Piper (our teacher) ~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 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

text β†’ duration β†’ acoustic β†’ decoder β†’ audio

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

Links

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 offsets

heartnano/ 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.

Downloads last month
2,512
GGUF
Model size
294k params
Architecture
sanotts
Hardware compatibility
Log In to add your hardware

32-bit

Inference Providers NEW
This model isn't deployed by any Inference Provider. πŸ™‹ Ask for provider support

Model tree for ampixa/sanoTTS

Quantizations
1 model