suzuna / state suzuna tts irodori v3

Suzuna — TTS Engine Selection / Irodori-TTS v3 対応仕様

作成日: 2026-05-18

目的

TTS スタジオのバックエンド TTS を AivisSpeech 固定から、ユーザーが TTS エンジンを選択できる設計へ拡張する。初期対応エンジンは既存の AivisSpeech と、別プロセスで起動済みの Irodori-TTS-Server v3 とする。

この仕様書は実装方針のみを定義する。コード変更は含まない。

調査対象

既存コード

領域ファイル現状
TTS バックエンドpython/routers/tts.pyAivisSpeech 固定。/aivis/status/aivis/speakersPOST /tts、履歴 API を同一ファイルで提供
フロント API 型src/lib/api.tsTTSRequest / TTSEntryaivis_speaker_id 前提
TTS UIsrc/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/statusGET {AIVIS_BASE}/versionAivisSpeech の起動確認
GET /aivis/speakersGET {AIVIS_BASE}/speakersキャラクター / スタイル取得
POST /ttsPOST /audio_queryPOST /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_querysynthesis を呼ぶ。複数チャンクは soundfilenumpy で 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/modelsPOST /v1/audio/speech で受け付けるモデル ID を返す
POST /v1/audio/speech音声合成。音声 bytes を直接返す
GET /v1/audio/voices登録済み参照音声の一覧
POST /v1/audio/voicesmultipart/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 との差分

観点AivisSpeechIrodori-TTS-Server
API 形式VOICEVOX 系互換。audio_querysynthesis の 2 段階OpenAI TTS 互換。/v1/audio/speech 1 回
話者指定数値の speaker / style id参照音声 voice ID または { "id": "..." }
参照音声AivisSpeech 側の内蔵話者ファイル / voices.json / HTTP upload
出力形式WAV byteswav, mp3, flac, opus, aac, pcm。Suzuna では WAV 固定推奨
長文分割Suzuna 側で 500 文字分割サーバー側の自動チャンクあり
話速speedScalespeed。内部で duration scale に変換
ピッチ / 抑揚 / 音量pitchScale, intonationScale, volumeScaleOpenAI 互換 API には直接対応なし。Irodori 固有 CFG / schedule を別管理
接続確認/version/health
VRAMAivisSpeech プロセス側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.pypython/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
statusGET /version
voicesGET /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。ユーザー設定で変更可能
statusGET /health
voicesGET /v1/audio/voices。レスポンス差異に備え、id / voice_id / name を許容
upload voicePOST /v1/audio/voicesfilevoice_id を multipart 送信
synthesizePOST /v1/audio/speechresponse_formatwav 固定
長文既定は Irodori 側チャンクに任せる。Suzuna 側の 500 文字分割は AivisSpeech のみに適用

Irodori リクエストでは、AivisSpeech 固有の pitch_scale / intonation_scale / volume_scale は送らない。話速のみ speed へ写像する。

API 変更仕様

互換性方針

既存フロントや履歴との互換性のため、aivis_speaker_id は当面残す。ただし新規フィールドとして enginevoice_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/settingsTTS エンジン設定を取得
PUT /tts/settingsTTS エンジン設定を保存
GET /tts/engines/status全エンジンの接続状態取得
`GET /tts/voices?engine=aivisirodori`
POST /tts/voices/irodoriIrodori 参照音声アップロード
POST /ttsengine に応じて AivisSpeechEngine / IrodoriTTSEngine を切り替え

既存の GET /aivis/statusGET /aivis/speakers は互換用に残す。フロント実装後も、外部利用者を壊さないため即削除しない。

設定永続化

保存先

既存の Suzuna 設定ファイルは python/core/dataset_store.pyCONFIG_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 formatWAV 固定。圧縮変換による FFmpeg 依存を Suzuna 側に増やさない
RVC 併用Irodori 生成完了後に現行 RVC を実行。Irodori と RVC を同時に走らせない
接続確認/health を使い、モデルロードを誘発しない

実装順序

1. バックエンド分離

  1. python/core/tts_engines.py を新設
  2. AivisSpeechEngine に既存 _synthesize_aivis_chunk() と status/speakers を移動
  3. IrodoriTTSEngine を追加
  4. python/routers/tts.py は engine 解決、保存、RVC、履歴だけを担当
  5. 既存 /aivis/* API は互換の薄いラッパーとして維持

2. 設定 API

  1. dataset_store.py_load_settings() / _save_settings() を共用
  2. GET /tts/settingsPUT /tts/settings を追加
  3. 未設定時は AivisSpeech 既定値を返す
  4. 保存時は未知キーを破壊しない

3. Irodori 参照音声 API

  1. GET /tts/voices?engine=irodori/v1/audio/voices をプロキシ
  2. POST /tts/voices/irodori で multipart upload をプロキシ
  3. アップロード後に voice list を再取得

4. フロント型更新

  1. TTSEngineId, TTSSettings, TTSVoice, IrodoriOptions を追加
  2. TTSRequestaivis_speaker_id を optional にする
  3. TTSEntryenginevoice_id を追加
  4. API client に settings / voices / upload を追加

5. TTSPage 更新

  1. エンジン state を追加
  2. 初期ロード時に settings と engine status を取得
  3. AivisSpeech 選択時は現行 UI を維持
  4. Irodori 選択時は参照音声 UI と Irodori 詳細設定を表示
  5. generate() で engine ごとの必須条件を検証

テスト観点

AivisSpeech 互換

  • engine なしの旧リクエストで従来どおり生成できる
  • /aivis/status/aivis/speakers が従来レスポンスを返す
  • 500 文字超の分割と WAV 結合が維持される
  • RVC 指定あり / なしの保存結果が変わらない

Irodori

  • Irodori 未起動時に未接続表示になる
  • /health の接続確認でモデルロードを誘発しない
  • 参照音声をアップロードでき、一覧に出る
  • POST /tts/v1/audio/speechresponse_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

参考リンク