|
| 1 | +# sanoTTS voice family |
| 2 | + |
| 3 | +`sanotts` provides native GGML inference for the |
| 4 | +[sanoTTS](https://github.com/Ampixa/sanoTTS) voice family — very small |
| 5 | +text-to-speech models, the smallest of which also runs on microcontrollers. |
| 6 | +All packages download from Hugging Face |
| 7 | +([ampixa/sanoTTS](https://huggingface.co/ampixa/sanoTTS) `gguf/`) as |
| 8 | +standalone FP32 GGUFs with embedded model specs. Offline FP32 inference only. |
| 9 | + |
| 10 | +Two graphs share one family: |
| 11 | + |
| 12 | +- **nano** — duration student → contextual acoustic student → mel-100 → |
| 13 | + noise-fed ConvNeXt-1D decoder → [log-magnitude | phase] head → inverse |
| 14 | + STFT. 24 kHz. A seed picks one of many valid renderings. |
| 15 | +- **piperlite** — duration student → contextual acoustic student → 192-channel |
| 16 | + latent → 3-stage ConvTranspose1d decoder with dilated residual banks → |
| 17 | + tanh waveform. 22.05 kHz. Fully deterministic (no seed). |
| 18 | + |
| 19 | +| Package | Voice | Graph | Params | Language | Notes | |
| 20 | +|---|---|---|---:|---|---| |
| 21 | +| `sanotts_heart_orig` | heart | nano | 2,272,145 | en | best quality of the nano pair | |
| 22 | +| `sanotts_heart_nano_orig` | heart-nano | nano | 294,279 | en | microcontroller-class | |
| 23 | +| `sanotts_amy_orig` | amy | piperlite | 1,454,284 | en | Piper-distilled | |
| 24 | +| `sanotts_hfc_orig` | hfc | piperlite | 1,834,380 | en | largest piperlite voice | |
| 25 | +| `sanotts_kristin_orig` | kristin | piperlite | 1,396,151 | en | carries a learned post filter | |
| 26 | +| `sanotts_vi_orig` | vi | piperlite | 1,565,484 | vi | Vietnamese | |
| 27 | +| `sanotts_id_orig` | id | piperlite | 1,562,124 | id | Indonesian | |
| 28 | + |
| 29 | +## Install |
| 30 | + |
| 31 | +Install eSpeak-ng and its voice data first. On Debian or Ubuntu: |
| 32 | + |
| 33 | +```bash |
| 34 | +sudo apt install espeak-ng libespeak-ng1 |
| 35 | +``` |
| 36 | + |
| 37 | +On macOS: |
| 38 | + |
| 39 | +```bash |
| 40 | +brew install espeak-ng |
| 41 | +``` |
| 42 | + |
| 43 | +Then install any package, e.g.: |
| 44 | + |
| 45 | +```bash |
| 46 | +python3 tools/model_manager_v2.py install sanotts_heart_orig --models-root models |
| 47 | +python3 tools/model_manager_v2.py install sanotts_amy_orig --models-root models |
| 48 | +``` |
| 49 | + |
| 50 | +## Run |
| 51 | + |
| 52 | +```bash |
| 53 | +audiocpp_cli --task tts --family sanotts \ |
| 54 | + --model models/sanoTTS-heart-GGUF --backend cpu \ |
| 55 | + --text "Hello from sano T T S, a very small neural text to speech model." \ |
| 56 | + --out sanotts.wav |
| 57 | +``` |
| 58 | + |
| 59 | +Swap `--model` for any installed package directory |
| 60 | +(`models/sanoTTS-amy-GGUF`, `models/sanoTTS-vi-GGUF`, ...). The Vietnamese |
| 61 | +and Indonesian voices accept `--language vi` / `--language id`; a session |
| 62 | +rejects text tagged with a language the voice was not trained on. |
| 63 | + |
| 64 | +eSpeak-ng is loaded dynamically at runtime, never linked. If it is not on the |
| 65 | +default library path: |
| 66 | + |
| 67 | +```bash |
| 68 | +audiocpp_cli --task tts --family sanotts \ |
| 69 | + --model models/sanoTTS-heart-GGUF --backend cpu \ |
| 70 | + --session-option sanotts.espeak_library_path=/path/to/libespeak-ng.so \ |
| 71 | + --session-option sanotts.espeak_data_path=/path/to/espeak-ng-data \ |
| 72 | + --text "A configured eSpeak installation." --out sanotts.wav |
| 73 | +``` |
| 74 | + |
| 75 | +## Options |
| 76 | + |
| 77 | +- `speaking_rate` (request, 0.5..2.0, default 1.0) — duration multiplier on |
| 78 | + the voice's tuned length scale; larger is slower. |
| 79 | +- `seed` (request, default 0) — nano voices only: the decoder is noise-fed, |
| 80 | + so a given seed picks one of many valid renderings. `0` derives the seed |
| 81 | + from each text chunk as `sha256(text)[:8]`, which is what the reference |
| 82 | + implementations do; an explicit seed advances by one per long-form chunk. |
| 83 | + Piperlite voices are deterministic and ignore the seed. |
| 84 | +- `text_chunk_size` (request, default 280) — maximum codepoints per long-form |
| 85 | + chunk; chunks split on sentence punctuation first, and a chunk that |
| 86 | + phonemizes past the voice's token limit is bisected at whitespace. |
| 87 | + |
| 88 | +## Determinism and parity |
| 89 | + |
| 90 | +The runtimes reproduce the reference implementations' exact semantics: |
| 91 | + |
| 92 | +- Front ends: the phonemizer punctuation-preservation pipeline through the |
| 93 | + same eSpeak-ng library. The nano voices add the misaki E2M rewrite with |
| 94 | + tie characters; the piperlite voices use Piper's NFD-decompose-to- |
| 95 | + codepoints convention, per-voice `phoneme_id_map`, `[BOS, PAD, (id, PAD)…, |
| 96 | + EOS]` framing, and the schwa fallback for ids outside a component's |
| 97 | + trained vocabulary. |
| 98 | +- nano: ATen-compatible MT19937 noise (24-bit uniform, Box–Muller in blocks |
| 99 | + of 16), torch.istft window normalisation and centre trim, and the |
| 100 | + reference's DC blocker `H(z) = (1 - z^-1)/(1 - 0.9973 z^-1)`. |
| 101 | +- Shared: torch.linspace / expand_features float behaviour, LayerNorm eps |
| 102 | + 1e-6 (nano), ties-to-even duration rounding. |
| 103 | + |
| 104 | +Measured against the project's numpy references (same text, same |
| 105 | +eSpeak-ng build), every voice: **correlation ≥ 0.99999996 with identical |
| 106 | +sample counts**; max sample delta ~1.7e-05 is the WAV's own int16 |
| 107 | +quantisation. The numpy references are themselves gated ≥ 0.987 against the |
| 108 | +float PyTorch models. |
| 109 | + |
| 110 | +## Performance |
| 111 | + |
| 112 | +CPU-only, 12-thread x86 (default 4 backend threads), FP32, the shared 6 kB |
| 113 | +long-form text: |
| 114 | + |
| 115 | +| Voice | Audio | Wall | vs real time | Peak RSS | |
| 116 | +|---|---:|---:|---:|---:| |
| 117 | +| heart-nano | 373 s | 1.3 s | ~283× | 220 MB | |
| 118 | +| amy | 394 s | 18.5 s | ~21× | 497 MB | |
| 119 | + |
| 120 | +The nano decoder runs at frame rate with a host iSTFT; the piperlite decoder |
| 121 | +runs convolutions at audio rate, which is why it is heavier. Graphs are |
| 122 | +cached per token count (duration and token stages) and per frame count |
| 123 | +(decoder); `--log` prints cache hits and per-stage timings. |
| 124 | + |
| 125 | +## Licensing |
| 126 | + |
| 127 | +The sanoTTS runtimes and weights are MIT-licensed. eSpeak-ng is GPL-3.0 and |
| 128 | +is therefore opened with `dlopen` at runtime and never linked, matching how |
| 129 | +`inflect_v2` treats it. |
0 commit comments