ボイスクローニングは学習ではなくプロンプトだ — 今どきのTTSは音をトークンにしたLLM
Fish Audio APIのフィールドを読んで分かったこと — 声はモデルidではなく参照音声で渡す
「ボイスクローニング」と聞くと、自分の声でAIを何時間も学習させる場面を思い浮かべがちだ。自分もそうだった。ところがapp10にFish Audioのテスターを作りながらAPIドキュメントを読むと、声を指定する欄に入るのはモデルidではなく参照音声だった。
この記事を読めば、今どきのTTS(テキストを読み上げる技術)がなぜLLMと同じ作りなのか、クローニングを組み込むときに何を用意すればいいのかが見えてくる。
音を単語のように扱う
出発点はニューラルコーデックだ。音声波形を短い区間ごとに決まった番号(コード)ひとつに置き換える。音が辞書の単語のようになる。これをオーディオトークンと呼ぶ。
音がトークンになれば、TTSはテキストのLLMと同じ問題になる。前のトークンを見て次のトークンを当てる、それだけだ。
クローニングはこの仕組みからおまけで出てくる。参照音声をトークンにしてプロンプトの前に置くと、モデルは「この声の続きで話す」をやる。ChatGPTに例文をいくつか渡して同じ文体で書いてもらうのと同じ理屈だ。
証拠はAPIの形
Fish Audioの /v1/tts リクエストを見るとかなり露骨だ。2026-10-19に公式ドキュメントで改めて確認した。
| フィールド | 役割 | LLMでいうと |
|---|---|---|
reference_id |
事前にアップロードした声 | 保存済みのシステムプロンプト |
references |
参照音声とその台本をリクエストに直接添付 | few-shotの例 |
temperature ・ top_p |
デフォルト0.7。上げると抑揚がばらける | サンプリング温度 |
repetition_penalty ・ max_new_tokens |
繰り返しの抑制、チャンクごとのオーディオトークン上限 | 名前まで同じ |
chunk_length ・ condition_on_previous_chunks |
長文を分割し、前のチャンクの音声を文脈として渡す | コンテキストウィンドウ |
max_new_tokens の説明は「オーディオトークン」の最大数だ。この一行で確信した。
ひとつ引っかかった所がある。references で音声を直接添付するときは、リクエストをJSONではなくMessagePackで送れとドキュメントに書いてある。音声のバイト列をそのまま載せるからだ。テスターをJSON前提で書いているとここで一度つまずく。
昔のTTSと何が違うか
| 昔のTTS(波形接続・パラメトリック) | LLM型TTS |
|---|---|
| 音素辞書・発音ルール | テキストをそのままトークンに |
| 話者ごとにモデルを学習 | 参照音声をプロンプトに |
| 抑揚が平坦 | temperature で抑揚の幅を調整 |
| 同じ入力なら同じ出力 | サンプリングなので毎回少し違う |
最後の行が実務では地味に効く。同じ文を作り直すと抑揚が変わることがある。毎回同じ音声でないと困る案内音声なら、一度作ったファイルを保存して使い回すほうがいい。
では、ファインチューニングはいつやるのか
GPT-SoVITS のように自分のデータでモデルを追加学習(ファインチューニング)するやり方も残っている。選び方はシンプルだ。
とりあえず自分の声で読ませたい → 参照音声(プロンプト)から。用意するのは短い録音ひとつ
参照音声では狙った声色にならない、またはオフラインで動かしたい → そこで初めてファインチューニング型を検討する
正直に言うと、まだ実際に音声を生成して品質を比べてはいない。temperature 0.1と0.9の抑揚の差、参照音声の長さによる差は、自分の耳で聞いてから別に書くつもりだ。この記事はAPIの形から読み取った構造の話である。
他人の声を本人の同意なしに複製しない。作るのが簡単になったぶん、この線ははっきり引いておく。
TTSをサービスに組み込みたいが、プロンプト型でいくかファインチューニングまでやるか迷っているなら、negabaroに気軽に相談してほしい(お問い合わせ)。
キーコンセプト
ニューラルコーデックが音声を短い区間ごとに番号ひとつへ置き換える — 音がトークンになる
トランスフォーマーがテキストと前のオーディオトークンを見て次を当てる
参照音声のトークンをプロンプトの前に置くと、その声で続けて話す — これがクローニング
サンプリングなので同じ文でも毎回少し違う — 固定が必要ならファイルを保存して使う