日本語 · English
559 K パラメータの日本語 TTS を、$3 のマイコン(ESP32-S3)で動かす試み。
🔊 ブラウザで試す → https://ayutaz.github.io/sanoTTS-jp/
インストール不要。漢字かな交じり文をそのまま打つと喋る。
arXiv:2608.21378 "sanoTTS" の蒸留レシピを日本語に適用し、
piper-plus(MB-iSTFT-VITS2)を教師として、
Duration / Acoustic / iSTFT Decoder の 3 つの小さな生徒に蒸留する。
推論は依存ゼロの C99(libm のみ・malloc を呼ばない)。
M5Stack CoreS3(スタックチャン)にファームを 1 本焼くと、シリアルに
今日は良い天気ですね。 と打つだけで喋る。形態素解析もアクセント推定も端末の中で走る。
かな> 今日は良い天気ですね。
saanotts: 経路: 辞書
saanotts: 漢字 G2P: 33 B -> 形態素 7 個 / ids 53 個 / 25.80 ms
saanotts: init 25.29 ms / 53 ids / 106 frames / 27136 sample / 音声 1.231 s
saanotts: プリロール 4 チャンク完了(初回 pull 222.78 ms / 鳴らし始めまで 369 ms)
saanotts: 定常 xRT = 0.474(満チャンク pull の中央値 / 92.88 ms)
saanotts: アンダーラン 0 / 14 チャンク
saanotts: 出力 PCM: 27136 sample / FNV-1a 0x390bf4b2aef8f2ec
(実機の生ログ reports/m147_device/dev_kanji.log から抜粋。時刻と注意書きの行は落としてある。
.bss が 2,704 B 小さい(C-108 で戻した分。PCM も速度も同じ)。
出荷版の同じ文は reports/m147_device/dev_restore_verify.log で
定常 xRT 0.473 / 起動直後の内部 DRAM free 181119 B / 最大ブロック 131072 B / 同じ checksum。
v1.1.0 の出荷ファームは 0.448(M-130。arena 180,224 B)— 遅くなったのは MEM-5 / MEM-6 の arena 削減(M-140 / M-142)のためで、MEM-7 / MEM-8 はむしろ 0.476 → 0.473 と速くしている。checksum は重みの版で変わる)
| モデル | 559 K params / int8 で 654,032 B(flash) |
| 実行時 RAM | 静的 DIRAM 211,535 B(M5 CoreS3・漢字構成の実機実測。DIRAM プール 341,760 B の 61.9%。v1.1.0 は 260,855 B = −49,320(−18.9%)。うち MEM-7 / MEM-8 が −20,480、MEM-5 / MEM-6 が −28,840 = M-142)。起動直後の空き 181,119 B / 最大ブロック 131,072 B。1 発話の実測ピークは 113,072 B(53 ids)〜115,056 B(303 ids)で、saan_stream_arena_peak(n) の予測と 6 点すべて完全一致(M-147) |
| 速度 | xRT 0.473(53 ids・満チャンク 1 pull の定常値。M-147)。v1.1.0 も同じ。平均は 0.499 を越えず、アンダーランは全長で 0)。 |
| 品質 | 教師の 64%(SCOREQ 比 0.636 = v4。v3 は 0.644 で差は検出できない)。 |
| 端末の G2P | 漢字あり 13.7 MB 辞書 / かなだけなら 877 B のテーブル |
| 見るもの | |
|---|---|
| すぐ試す | ↑ のブラウザ版(インストール不要) |
| 好きな文を合成する / 実機で喋らせる / ゲートを回す | はじめかた(A〜E の 5 つの入口) |
| どの板で動くか・辞書の大きさと精度 | 対応状況 |
| 重み・ファーム・辞書を落とす | ダウンロード |
| 自分のスケッチに組み込む | Arduino / PlatformIO ライブラリ |
いちばん短い道(実機を持っているなら):
# 16 MB の ESP32-S3 に、辞書ごと焼くだけ(ESP-IDF は要らない)
esptool.py --chip esp32s3 -p <ポート> write_flash 0x0 esp32s3-firmware-kanji-16mb.bin自分のスケッチから呼ぶなら — Arduino IDE と PlatformIO のライブラリがある
(arduino/):
[env:m5stack-cores3]
; ⚠️ 公式の espressif32 は arduino-esp32 2.0.17 で止まっており動かない。3.x が要る
platform = https://github.com/pioarduino/platform-espressif32/releases/download/55.03.311/platform-espressif32.zip
board = m5stack-cores3
framework = arduino
lib_deps =
https://github.com/ayutaz/sanoTTS-jp/releases/latest/download/sanoTTS-jp-arduino.zip
https://github.com/ayutaz/sanoTTS-jp/releases/latest/download/sanoTTS-jp-voice-tsukuyomi-v4.zip
m5stack/M5Unifiedtts.setSpeaker(&spk); tts.begin(); tts.say("今日は良い天気ですね。");LicenseRef-sanoTTS-jp-Model-1.0)。
英語版のレシピをそのまま移すと G2P で詰まる。 漢字を読むには辞書が要り、 NAIST-JDIC は実測 102 MB でマイコンに載らない。そこで辞書を小さくするのではなく 問題の切り分け方を変えた — 端末は「ひらがな + アクセント記号」だけを受け取り、 877 B のテーブルで音素に変換する。論文にも公式実装にも対応物が無い、 このリポジトリの中心的な設計判断。
ピッチアクセント(箸/橋/端)と無声化母音も英語版に無い問題で、集約スコアでは
検出できないため専用の評価がある(→ MODEL_CARD.md)。
漢字かな交じり文
│ ホスト側・オフライン(OpenJTalk) ┊ 端末側・辞書 13.7 MB(-DSAAN_KANJI=1)
▼ ┊ 438,750 entries を flash に mmap
かな中間表現 きょ][おわよ][いて][んきです°ね [ 上昇 / ] 下降核 / # 句境界 / ° 無声化
│ 端末側・877 B のテーブルのみ ┊ どちらの経路かは saan_g2p_classify() が決める
▼ ┊ (かな / 辞書 / 拒否。両経路とも同じ音素IDに合流)
音素ID ──▶ Duration Dα ──▶ Acoustic Aβ ──▶ iSTFT Decoder Gγ ──▶ 22.05 kHz PCM
33 K params 195 K 331 K
└─ 40 次元の潜在インターフェース(c-line)で接続
3 つの生徒を明示的な潜在インターフェースで繋ぐのが要点。 これを省いて テキスト→波形を 1 本のネットで学ぶと、論文の対照実験では訓練文を丸暗記して 未知の文が読めなくなる。
- C99 推論コア(
csrc/)— 依存は libm のみ。mallocを呼ばず arena を使う。 ストリーミング版は一括版と bit 完全一致(27,136 sample) - オンデバイス G2P(
csrc/g2p.c)— テーブル 877 B / コード 1,549 B / 作業メモリ 0 B - 端末での自由入力(
csrc/line.c369 B)— かなでも漢字かな交じり文でもよく、 辞書を持たないビルドは漢字を喋らずに理由を出す - 蒸留の全経路 — 教師ラベル生成 → 4 段の学習 → 評価(SCOREQ / UTMOS / DNSMOS / かな CER / 音素クラス別スペクトル平坦度)
すべて手元の M5Stack CoreS3(W8A8 + PIE。ESP32-S3 では既定)での実測。
(冒頭の表に無いものだけ。品質・速度・メモリはそちらを見ること)
| 軸 | 値 |
|---|---|
| アクセント | ミニマルペア 37 ペアで教師との符号一致 31/37( |
| 漢字 G2P | 5.51〜66.30 ms(入力 15〜84 B)。MeCab と 1,977/1,977 文一致 |
| かな経路と漢字経路 | 同じ文をどちらで書いても PCM が bit 一致する(端末・ホストとも) |
| アンダーラン | 0(全文)。プリロール完了まで 385 ms、音が出るまで 407〜433 ms(v4 の実機 = M-130) |
速度は作り直して要件(RTF ≤ 0.5)に届いた。 最初の実測は 0.926 で、
1 step の内訳を取ると MAC は 3 割しかなく、活性化の量子化・GELU・毎 step 102 回の
テンソル検索・重みのコピー 489 KB/step が残りを占めていた。それを削って 1 step を
18.38 M → 11.66 M cyc にした。波形は 1 bit も変わっていない(checksum が同一)。
経緯は docs/README.md の年表に。
公式実装 Ampixa/sanoTTS は存在する(GPL-3.0)。
本リポジトリは MIT で、そのソースコードを参照せずに論文本文の数値と piper-plus の実装から
独立に書いた。公式実装は英語・ネパール語・ヒンディー語・ベトナム語・インドネシア語・中国語に
対応しており、日本語は含まれていない。
公式リポジトリの公開ドキュメントに記載された実測値(ESP32-S3 で 0.22× 実時間など)は、
本リポジトリの実測値との突き合わせに使っている。コードは参照していない
(この線引きは機械的に強制している。CONTRIBUTING.md の 4 番)。
索引は docs/README.md。数値が食い違ったら
docs/measurements.md が正(全項目に再現コマンド付き)。
決定と訂正の履歴は docs/decisions.md、
モデルの中身と既知の制約は MODEL_CARD.md、
実機で動かす手順は esp32/TESTING.md。
CONTRIBUTING.md を読んでください。
一番ありがたいのは聴いた感想、次が別の ESP32-S3 での速度実測です。
🙏 聴いた感想にはボードが要りません (
saanotts-jp-v4-samples.zipを再生するだけ)。 「変な音がする」の一言が、n=24 の数字より情報量が多いことがあります。
既知の制約と、それをどう測ったかは MODEL_CARD.md §4 にまとめてあります。
GitHub Sponsors から支援できます。
| 対象 | ライセンス |
|---|---|
| このリポジトリのコードとドキュメント | MIT |
| 配布されるモデルの重み(Releases) | LICENSE-MODEL.md — MIT ではない |
重みは つくよみちゃんコーパスを素材に含む教師からの蒸留物で、そのコーパスの条件が
- 帰属表示を必須とし、出力の用途に禁止事項を課し、義務が下流に伝播する
ため、MIT を名乗ることができない。使う前に
LICENSE-MODEL.md と MODEL_CARD.md を読むこと。
docs/decisions.md C-073)。
教師 base の LibriTTS-R / CML-TTS(CC-BY-4.0)と AISHELL-3(Apache-2.0)が
抜けていたので足し、帰属を要求しない素材(MOE-Speech / CC0 / PD)は
任意ブロック (B) に移した。再配布するなら (A) を丸ごと写すこと。
コーパス本文は配布しない。 素材ごとの一次ソースは NOTICE.md。