Suzuna — TTS Engine Selection / Irodori-TTS v3 対応仕様
作成日: 2026-05-18
目的
TTS スタジオのバックエンド TTS を AivisSpeech 固定から、ユーザーが TTS エンジンを選択できる設計へ拡張する。初期対応エンジンは既存の AivisSpeech と、別プロセスで起動済みの Irodori-TTS-Server v3 とする。
この仕様書は実装方針のみを定義する。コード変更は含まない。
調査対象
既存コード
| 領域 | ファイル | 現状 |
|---|---|---|
| TTS バックエンド | python/routers/tts.py | AivisSpeech 固定。/aivis/status、/aivis/speakers、POST /tts、履歴 API を同一ファイルで提供 |
| フロント API 型 | src/lib/api.ts | TTSRequest / TTSEntry が aivis_speaker_id 前提 |
| TTS UI | src/pages/TTSPage.tsx | 接続確認、ベース音声、スタイル、RVC、詳細パラメータ、テキスト入力、履歴を単一画面で管理 |
| 設定永続化 | python/core/dataset_store.py | %APPDATA%/Suzuna/settings.json または $XDG_CONFIG_HOME/Suzuna/settings.json を UTF-8 JSON として読み書き |
外部仕様
Irodori-TTS-Server 公式 README: https://github.com/Aratako/Irodori-TTS-Server
pykakasi API: https://pykakasi.readthedocs.io/ja/latest/api.html
現在の AivisSpeech 連携
エンドポイント
python/routers/tts.py は AivisSpeech の接続先を固定値 http://127.0.0.1:10101 として持つ。
| Suzuna API | 内部呼び出し | 用途 |
|---|---|---|
GET /aivis/status | GET {AIVIS_BASE}/version | AivisSpeech の起動確認 |
GET /aivis/speakers | GET {AIVIS_BASE}/speakers | キャラクター / スタイル取得 |
POST /tts | POST /audio_query → POST /synthesis | テキストを WAV 化し、必要なら RVC 変換 |
リクエスト形式
現在の TTSRequest は AivisSpeech 前提で、必須フィールドに aivis_speaker_id を持つ。
{
text: string
aivis_speaker_id: number
rvc_model_path?: string
index_path?: string
pitch_shift: number
speed?: number
speed_scale: number
pitch_scale: number
intonation_scale: number
volume_scale: number
}
生成時は split_text() で 500 文字単位に分割し、各チャンクに対して audio_query と synthesis を呼ぶ。複数チャンクは soundfile と numpy で WAV 結合する。
既存フロー
flowchart TD
A["TTSPage: テキスト入力"] --> B["POST /tts"]
B --> C["AivisSpeech /audio_query"]
C --> D["AivisSpeech /synthesis"]
D --> E["一時 WAV 保存"]
E --> F{"RVC モデル指定あり"}
F -->|あり| G["RVC 変換"]
F -->|なし| H["そのまま保存"]
G --> I["履歴 history.json に追加"]
H --> I
Irodori-TTS-Server v3 API
前提
Suzuna 側では Irodori-TTS-Server を自動起動しない。ユーザーが別プロセスで起動済みとする。
公式 README の既定起動例は http://localhost:8088。OpenAI 互換 API のベース URL は http://localhost:8088/v1。
主なエンドポイント
| エンドポイント | 用途 |
|---|---|
GET /health | サーバー状態と現在設定の取得。モデルはロードしない |
GET /v1/models | POST /v1/audio/speech で受け付けるモデル ID を返す |
POST /v1/audio/speech | 音声合成。音声 bytes を直接返す |
GET /v1/audio/voices | 登録済み参照音声の一覧 |
POST /v1/audio/voices | multipart/form-data で参照音声をアップロード。file と任意の voice_id |
PUT /v1/audio/voices/{voice_id} | 参照音声の置き換え |
DELETE /v1/audio/voices/{voice_id} | 参照音声の削除 |
POST /v1/audio/speech リクエスト
{
"model": "irodori-tts",
"input": "こんにちは。",
"voice": "sample",
"response_format": "wav",
"speed": 1.0,
"irodori": {
"num_steps": 40,
"cfg_scale_text": 3.0,
"cfg_scale_speaker": 5.0,
"seed": 1234,
"t_schedule_mode": "linear",
"sway_coeff": -1.0,
"chunking_enabled": true,
"chunk_min_chars": 80
}
}
AivisSpeech との差分
| 観点 | AivisSpeech | Irodori-TTS-Server |
|---|---|---|
| API 形式 | VOICEVOX 系互換。audio_query → synthesis の 2 段階 | OpenAI TTS 互換。/v1/audio/speech 1 回 |
| 話者指定 | 数値の speaker / style id | 参照音声 voice ID または { "id": "..." } |
| 参照音声 | AivisSpeech 側の内蔵話者 | ファイル / voices.json / HTTP upload |
| 出力形式 | WAV bytes | wav, mp3, flac, opus, aac, pcm。Suzuna では WAV 固定推奨 |
| 長文分割 | Suzuna 側で 500 文字分割 | サーバー側の自動チャンクあり |
| 話速 | speedScale | speed。内部で duration scale に変換 |
| ピッチ / 抑揚 / 音量 | pitchScale, intonationScale, volumeScale | OpenAI 互換 API には直接対応なし。Irodori 固有 CFG / schedule を別管理 |
| 接続確認 | /version | /health |
| VRAM | AivisSpeech プロセス側 | Irodori-TTS-Server プロセス側。Suzuna は自動起動せず同時合成を増やさない |
設計方針
基本方針
TTS 生成後の RVC 変換、保存、履歴管理はエンジン非依存なので維持する。変更点は「テキストからベース WAV bytes を得る」部分に閉じ込める。
flowchart TD
A["POST /tts"] --> B["設定から TTS エンジン解決"]
B --> C{"engine"}
C -->|aivis| D["AivisSpeechEngine.synthesize()"]
C -->|irodori| E["IrodoriTTSEngine.synthesize()"]
D --> F["base WAV bytes"]
E --> F
F --> G["一時 WAV 保存"]
G --> H["既存 RVC 変換"]
H --> I["既存履歴保存"]
抽象インターフェース
Python 側に TTSEngine 抽象を置く。配置候補は python/core/tts_engines.py。python/routers/tts.py から合成ロジックを剥がし、ルーターは API 入出力と履歴処理に集中させる。
class TTSEngine(Protocol):
id: str
display_name: str
async def status(self) -> EngineStatus:
...
async def list_voices(self) -> list[VoiceOption]:
...
async def synthesize(self, req: TTSRequest, settings: TTSSettings) -> bytes:
...
共通モデル案
class EngineStatus(BaseModel):
engine: Literal["aivis", "irodori"]
connected: bool
version: str | None = None
message: str | None = None
class VoiceOption(BaseModel):
id: str
name: str
engine: Literal["aivis", "irodori"]
styles: list[VoiceStyle] = []
class VoiceStyle(BaseModel):
id: str
name: str
type: str | None = None
エンジン実装
AivisSpeechEngine
既存の挙動を維持する。
| 項目 | 内容 |
|---|---|
| base URL | 既定 http://127.0.0.1:10101 |
| status | GET /version |
| voices | GET /speakers の結果を既存 UI 互換構造へ正規化 |
| synthesize | 現行 _synthesize_aivis_chunk() 相当。audio_query → query パラメータ上書き → synthesis |
| 長文 | 現行 split_text() と combine_wav_segments() を維持 |
IrodoriTTSEngine
Irodori-TTS-Server の OpenAI 互換 API を呼び出す。
| 項目 | 内容 |
|---|---|
| base URL | 既定 http://127.0.0.1:8088。ユーザー設定で変更可能 |
| status | GET /health |
| voices | GET /v1/audio/voices。レスポンス差異に備え、id / voice_id / name を許容 |
| upload voice | POST /v1/audio/voices に file と voice_id を multipart 送信 |
| synthesize | POST /v1/audio/speech。response_format は wav 固定 |
| 長文 | 既定は Irodori 側チャンクに任せる。Suzuna 側の 500 文字分割は AivisSpeech のみに適用 |
Irodori リクエストでは、AivisSpeech 固有の pitch_scale / intonation_scale / volume_scale は送らない。話速のみ speed へ写像する。
API 変更仕様
互換性方針
既存フロントや履歴との互換性のため、aivis_speaker_id は当面残す。ただし新規フィールドとして engine と voice_id を追加する。
既存リクエストに engine がない場合は aivis とみなす。
TTSRequest 変更案
type TTSEngineId = 'aivis' | 'irodori'
interface TTSRequest {
text: string
engine?: TTSEngineId
// AivisSpeech 互換
aivis_speaker_id?: number
// Irodori
voice_id?: string
irodori?: {
num_steps?: number
cfg_scale_text?: number
cfg_scale_speaker?: number
seed?: number
t_schedule_mode?: 'linear' | 'sway'
sway_coeff?: number
chunking_enabled?: boolean
chunk_min_chars?: number
preprocess_reading?: boolean
}
// RVC
rvc_model_path?: string
index_path?: string
pitch_shift: number
// 共通または AivisSpeech 用
speed?: number
speed_scale: number
pitch_scale: number
intonation_scale: number
volume_scale: number
}
新規 / 変更 API
| API | 内容 |
|---|---|
GET /tts/settings | TTS エンジン設定を取得 |
PUT /tts/settings | TTS エンジン設定を保存 |
GET /tts/engines/status | 全エンジンの接続状態取得 |
| `GET /tts/voices?engine=aivis | irodori` |
POST /tts/voices/irodori | Irodori 参照音声アップロード |
POST /tts | engine に応じて AivisSpeechEngine / IrodoriTTSEngine を切り替え |
既存の GET /aivis/status と GET /aivis/speakers は互換用に残す。フロント実装後も、外部利用者を壊さないため即削除しない。
設定永続化
保存先
既存の Suzuna 設定ファイルは python/core/dataset_store.py の CONFIG_PATH で管理されている。README でも %APPDATA%/Suzuna/settings.json と説明済みなので、TTS 設定も同じ UTF-8 JSON に追加する。
ユーザー要件の「config.json」は「Suzuna の設定 JSON」として扱い、既存の settings.json を拡張する。別ファイル config.json を新設すると、設定保存先が二重化するため避ける。
スキーマ案
{
"datasets_dir": "C:\\Users\\...\\Documents\\Suzuna\\datasets",
"tts": {
"engine": "aivis",
"aivis": {
"base_url": "http://127.0.0.1:10101",
"speaker_id": 888753760
},
"irodori": {
"base_url": "http://127.0.0.1:8088",
"model": "irodori-tts",
"voice_id": "",
"response_format": "wav",
"num_steps": 40,
"cfg_scale_text": 3.0,
"cfg_scale_speaker": 5.0,
"chunking_enabled": true,
"chunk_min_chars": 80,
"preprocess_reading": false
}
}
}
UTF-8 / 文字化け対策
実装時は既存の read_text(encoding="utf-8")、write_text(..., encoding="utf-8")、json.dumps(..., ensure_ascii=False) を踏襲する。UI 文字列は既存ルールどおり日本語のまま維持し、英語化しない。
Irodori の読み前処理
背景
Irodori で漢字読みの誤りが目立つ場合に備え、読み変換をオプション化する。ただし常時ひらがな化すると、漢字表記から得られる文脈・アクセント・語境界情報を失うリスクがあるため、既定は OFF とする。
推奨仕様
| 項目 | 仕様 |
|---|---|
| UI 名 | 読み補助 |
| 既定 | OFF |
| 対象 | Irodori 選択時のみ表示 |
| 方式 | pykakasi が利用可能な場合のみ、漢字かな混じり文をひらがな寄りに変換 |
| フォールバック | pykakasi 未インストール時は元テキストのまま送信し、設定画面に軽い注意表示 |
実装注意
pykakasi は単純なかな変換であり、固有名詞や文脈依存の読みを完全には解決しない。既存の AivisSpeech ユーザー辞書構想とは別物として扱い、「誤読が減る場合がある補助機能」に留める。
フロントエンド UI 仕様
追加位置
src/pages/TTSPage.tsx の左設定パネル先頭に TTSエンジン セクションを追加する。既存の ベース音声 より上に置く。
エンジン選択
| UI | 値 |
|---|---|
| セレクトまたはセグメント | AivisSpeech / Irodori-TTS |
| 保存 | 変更時に PUT /tts/settings |
| 接続確認 | 選択中エンジンの status を表示 |
表示文字列は日本語 UI を維持する。
候補:
| 用途 | 表示文字列 |
|---|---|
| ラベル | TTSエンジン |
| 選択肢 | AivisSpeech / Irodori-TTS |
| 接続中 | 接続済み |
| 未接続 | 未接続 |
| 確認ボタン | 接続確認 |
AivisSpeech 選択時
既存 UI をそのまま表示する。
| セクション | 表示 |
|---|---|
| ベース音声 | 表示 |
| スタイル | 表示 |
| 詳細パラメータ | 話速 / 音高 / 抑揚 / 音量を表示 |
| 未接続バナー | AivisSpeech が起動していません を維持 |
Irodori 選択時
Irodori 固有の UI に切り替える。
| セクション | 表示 |
|---|---|
| 参照音声 | 表示 |
| 参照音声アップロード | 表示 |
| 詳細パラメータ | 話速、ステップ数、テキストCFG、話者CFG、seed、読み補助 |
| ベース音声 / スタイル | 非表示または Irodori 参照音声一覧に置換 |
参照音声アップロードは既存の Tauri dialog パターンを使い、WAV音声 フィルタを基本に wav, flac, mp3, m4a, ogg, opus, aac, webm を許可する。
デザインシステム
既存 TTS ページはインライン style で、var(--color-*) / var(--space-*) / var(--radius-md) を使っている。追加 UI も同じトークンを使う。
パネル幅は現状 300px。Irodori 詳細項目が増えるため、横幅を広げず、折りたたみセクションを増やして縦スクロールで吸収する。
履歴仕様
履歴にはエンジン識別子を追加する。既存履歴は engine がないため aivis とみなす。
{
"id": "abcd1234",
"engine": "irodori",
"text": "こんにちは。",
"voice_id": "sample",
"aivis_speaker_id": null,
"rvc_model_path": null,
"pitch_shift": 0,
"speed_scale": 1.0,
"chunk_count": 1,
"output_path": "...",
"duration_ms": 1234,
"created_at": "2026-05-18T..."
}
UI の履歴メタには Irodori / AivisSpeech タグを追加して、どのエンジンで生成したか分かるようにする。
VRAM / パフォーマンス方針
RTX 3070 8GB を想定し、Suzuna 側で Irodori-TTS-Server を起動・ロードしない。並列合成も Suzuna 側から増やさない。
推奨:
| 項目 | 方針 |
|---|---|
| 同時リクエスト | UI 側は生成中ボタンを無効化し、現行の単発生成を維持 |
Irodori num_steps | 既定 40。高品質設定はユーザー明示変更のみ |
| response format | WAV 固定。圧縮変換による FFmpeg 依存を Suzuna 側に増やさない |
| RVC 併用 | Irodori 生成完了後に現行 RVC を実行。Irodori と RVC を同時に走らせない |
| 接続確認 | /health を使い、モデルロードを誘発しない |
実装順序
1. バックエンド分離
python/core/tts_engines.pyを新設AivisSpeechEngineに既存_synthesize_aivis_chunk()と status/speakers を移動IrodoriTTSEngineを追加python/routers/tts.pyは engine 解決、保存、RVC、履歴だけを担当- 既存
/aivis/*API は互換の薄いラッパーとして維持
2. 設定 API
dataset_store.pyの_load_settings()/_save_settings()を共用GET /tts/settingsとPUT /tts/settingsを追加- 未設定時は AivisSpeech 既定値を返す
- 保存時は未知キーを破壊しない
3. Irodori 参照音声 API
GET /tts/voices?engine=irodoriで/v1/audio/voicesをプロキシPOST /tts/voices/irodoriで multipart upload をプロキシ- アップロード後に voice list を再取得
4. フロント型更新
TTSEngineId,TTSSettings,TTSVoice,IrodoriOptionsを追加TTSRequestのaivis_speaker_idを optional にするTTSEntryにengineとvoice_idを追加- API client に settings / voices / upload を追加
5. TTSPage 更新
- エンジン state を追加
- 初期ロード時に settings と engine status を取得
- AivisSpeech 選択時は現行 UI を維持
- Irodori 選択時は参照音声 UI と Irodori 詳細設定を表示
generate()で engine ごとの必須条件を検証
テスト観点
AivisSpeech 互換
engineなしの旧リクエストで従来どおり生成できる/aivis/statusと/aivis/speakersが従来レスポンスを返す- 500 文字超の分割と WAV 結合が維持される
- RVC 指定あり / なしの保存結果が変わらない
Irodori
- Irodori 未起動時に未接続表示になる
/healthの接続確認でモデルロードを誘発しない- 参照音声をアップロードでき、一覧に出る
POST /ttsが/v1/audio/speechにresponse_format: "wav"で送信されるvoice_id未選択時は生成ボタンを無効化する。ただし設定でvoice: "none"を許可する場合は例外- 生成後の WAV が既存 AudioPlayer /
/audioで再生できる - RVC 併用時も Irodori → RVC の順に直列実行される
設定
- エンジン選択がアプリ再起動後も復元される
- 既存
datasets_dirが破壊されない - JSON は UTF-8 /
ensure_ascii=Falseで保存される
UI
- 既存の日本語 UI 文字列を英語へ変更しない
- Irodori 選択時のみ参照音声アップロード欄が表示される
- 300px 設定パネル内でラベルがはみ出さない
- 生成中は二重送信できない
未決事項
| 項目 | 推奨 |
|---|---|
Irodori voice: "none" を UI で許可するか | 初期版では非表示。参照音声必須にして品質を安定させる |
Irodori の seconds 固定長 | 初期版では扱わない。話速 speed のみにする |
| LoRA adapter | 初期版では UI 非表示。VRAM とパス管理の複雑さが大きい |
| 参照音声の削除 UI | 初期版はアップロードと選択のみ。削除は後続で追加 |
| 読み補助の既定 | OFF |
参考リンク
- Irodori-TTS-Server README: https://github.com/Aratako/Irodori-TTS-Server
- Irodori-TTS v3 model: https://huggingface.co/Aratako/Irodori-TTS-500M-v3
- pykakasi API: https://pykakasi.readthedocs.io/ja/latest/api.html