
プロジェクトが解決する課題
このプロジェクトは、すぐに使えるソフトウェアパッケージではなく、実際の試聴と反復改善を通じて確立したAI音声制作の方法です。呼吸音が無音の中に孤立して落ちる、息と台詞の間が空きすぎる、間の後に声が急にかしこまる、モデルが文を繰り返したり読み落としたりする、台詞をカットすると環境音が自然につながらない、といった一般的な問題を対象にしています。
完全なワークフローは、次の4つを中心に構成されています。ElevenLabs v4で音声を合成し、ffmpegで音声を処理し、Whisperで文字起こしをして内容を確認し、NumPyで音量、音高、サンプル位置を分析します。
重要なのは「呼吸を増やす」ことではなく、「正しい位置に息を置く」ことです。口を開く前の短い息は最初の一語に寄せ、文末では自然に息を添え、文中の吸気は文の途中に残します。
主な機能と手法
- 呼吸を台詞に寄せる:発話前の軽く短い息を識別し、無音だけを移動して、台詞から約0.08秒の位置に息を置きます。
- 不自然な音量を補正:各有声音ブロックの音量を測定し、中央値より4 dB以上大きいブロックを適度に抑えます。
- 劣化を抑えた台詞カット:ドライ音声の無音部分で切り、ループする環境音を再配置して、元々評価されていた演技をできるだけ保ちます。
- 繰り返しと読み落としの検出:合成後にWhisperで文字起こしし、「聞こえた文字」と「読むべき文字」を比較して、異常があれば再生成します。
- 動作音を同期:動作音のマーカーを、台詞の一巡の冒頭ではなく、実際に動作が始まる文に記述します。
- 日常的な声質の設計:声を柔らかくするだけでなく、人物の速度、音高、自信の度合い、話し方の癖を明確にします。
環境の準備
必要なツール
- ElevenLabs v4:テキストを音声に変換します。プロジェクトでは実際にv4を使用しています。
- ffmpeg:音声のデコード、カット、結合、リサンプリング、書き出しに使用します。
- Whisper:生成結果を文字起こしし、繰り返し、文の欠落、内容のずれを検出します。
- NumPy:音声配列の処理、相互相関、音量計算、セグメント移動に使用します。
READMEで検証されている環境はmacOSです。固定のインストールスクリプトは用意されていないため、各ツールの公式手順に従って個別にインストールし、ffmpeg、Whisper、Python環境が正常に動作することを確認してください。
分析を始める前に、音声を統一したサンプルレートのモノラル波形へデコードし、固定ステップで計算した2種類のデータを用意します。各フレームの音高f0と、各フレームの二乗平均平方根音量rmsです。元の処理ではフレームステップを10ミリ秒としており、音量分析の窓によって境界が数十ミリ秒ぼやけるため、セグメントを移動する際は少し余裕を残す必要があります。
常に2種類の音声を保存する
生成するたびに、次のバージョンを保存します。
- ドライ音声:人の声だけで、環境音や動作音を含みません。
- ウェット音声:人の声の下に環境音を敷いた最終バージョンです。
ドライ音声は呼吸の測定に便利なだけでなく、後から文を削除したり、部分的に差し替えたり、背景音を再配置したりする際にも元の演技を保てます。最終ミックスだけを保存しないでください。
ステップ1:呼吸プロンプトを正しく書く
長い無音の間に「嗯」「哈」や呼吸タグを一行ずつ並べないでください。この方法で生成した音声は、明確な音高を持ち、音量も通常の発話に近く、前後に長い完全な無音が残ることが多いため、独立した効果音のように聞こえます。
呼吸は台詞のある行に書き込むことを推奨します。
[low, breathy, wet]
[inhales] 私を見て…… [exhales]
[long pause]
ん… 逃げないで…… ん…はぁ…- 行頭では、
[inhales]、[exhales]、[sighs]など短い呼吸タグだけを使います。 - タグと台詞の間に、さらに省略記号を連ねないでください。
- 文末には自然に息を添えるか、軽い声を2、3回続けて入れられます。
- 空白部分には
[long pause]を1行だけ書き、独立したハミング音を並べないでください。 - 文中にも吸気を置けます。行頭、文中、文末から1、2か所を選べば十分です。
[long pause]を2つ続けて書いても、間が明確に長くなるとは限りません。プロジェクトの実測では、タグ1つで約0.96秒の無音が生じ、2つ続けても約1.04秒でした。そのため、タグの文字面だけに正確な長さの制御を任せないでください。
ステップ2:音声を測定可能な「ブロック」に分ける
ここでいう「ブロック」とは、連続した有声音の区間です。各ブロックについて、継続時間、平均音量、音高が存在するフレームの割合という3つの指標を測定します。これにより、通常の台詞、本当の呼吸、音高を持つ独立したハミング音を区別できます。
def is_breath(f0, rms, a, b):
dur = (b - a) * 0.01
voiced = (f0[a:b] > 0).mean()
db = 20 * np.log10(rms[a:b].mean() + 1e-9)
return 0.12 <= dur <= 0.9 and voiced < 0.15 and db < -29元のプロジェクトにおける代表的な範囲は次のとおりです。
- 抑えた、息の多い台詞:約0.5〜2秒、音量は約−22〜−27 dB、音高フレームは約2〜4割。
- 吸気、呼気、または摩擦音:約0.3〜0.65秒、音量は約−32〜−40 dB、音高フレームは約1割。
- 分離した独立したハミング音:約0.4〜0.5秒、音量は−25 dBに達することがあり、音高フレームが6割を超え、前後に0.6〜1秒の無音があることが多い。
これらのしきい値は特定の声質とサンプルから得られたもので、すべての声にそのまま適用できる基準ではありません。より確実なのは、まずリスナーに問題を指摘してもらい、隣接する正常な区間からその声質独自の範囲を作ることです。
ステップ3:発話前の息を台詞のそばに移動する
プロンプトが正しくても、モデルが呼吸と台詞の間に0.3秒以上の無音を残すことがあります。プロンプトだけでは数十ミリ秒単位の位置を制御しにくいため、生成後に音声セグメントを移動します。
処理前後の構造は次のように考えられます。
処理前:前の文 → 無音A → 呼吸 → 無音B → 次の文
処理後:前の文 → より長い無音 → 呼吸 → 0.08秒 → 次の文作業では呼吸と無音の位置だけを入れ替え、呼吸や台詞自体は変更せず、セグメント全体の長さも変えません。これにより、既存のタイムライン、効果音の配置、発話速度の統計を壊さずに済みます。
移動に適した候補ブロックは、次の条件を満たすものです。
- 軽く短く、明確な音高のない呼吸と判定されている。
- その後約0.14〜1.5秒以内に発話ブロックが現れる。
- その前に少なくとも約0.3秒の空白がある。
- 文中で前半の文に続く吸気は移動しない。元々自然な文中の呼吸を壊さないためです。
READMEでの実測目標間隔は約0.08秒です。移動時には呼吸の先頭を約0.02秒、末尾を約0.03秒余分に含め、新しい位置で前後に約4ミリ秒のフェードを施すと、接合部の違和感を抑えられます。
ステップ4:間の後の「かしこまった声」を補正する
長い間の後に、声がより明瞭で大きく、息の少ない通常の話し方へ戻ることがあります。事例では、異常な短文が周囲より6 dB以上大きく、音高フレームの割合も約3割から6割に上昇していました。
まずプロンプトで発生率を下げる
- 間の後にまず一息添えるか、低いハミングから台詞へ滑り込ませます。
- 文末は伸ばして収め、毎回きっぱりした疑問符や句点で終わらせないでください。
- 2、3語の短文は隣の文と結合し、単独で明瞭なナレーション調に読まれるのを避けます。
- 声質を再度強調する必要がある場合は、短いタグを発話の冒頭に入れ、毎回単独の行にしないでください。
[long pause]
ん… [low, breathy] 逃げないで……セグメント分割器が各行頭の声質タグで新しいセグメントを作る場合、このように行内へ書くことで、接合部や音高の急変も減らせます。
次にプログラムで異常な音量を抑える
ブロックごとに音量を計算し、現在の段落にある各発話ブロックの中央値を基準にします。あるブロックが4 dB以上大きい場合は、基準より約2 dB高い程度まで抑え、減衰量は最大6 dBとし、前後に約25ミリ秒のグラデーションエンベロープを使用します。
文単位ではなく、必ずブロック単位で測定してください。一瞬だけ突出する0.5秒の音声を後続の長い文と一緒に平均すると、6 dBの異常が薄まってしきい値を下回る可能性があります。プログラムで補正できるのは音量だけで、息の少ない音色そのものを直接戻すことはできません。そのため、プロンプトと後処理を併用する必要があります。
ステップ5:合成後すぐに文字起こしで品質確認する
モデルが文を2、3回繰り返したり、最後の文を丸ごと読み落としたりすることがあります。原稿自体が完全に正しくても、各合成セグメントは一度文字起こしして確認するべきです。
プロジェクトでは2種類の信号を使います。
- 角括弧のタグと一般的な間投詞を除いたうえで、6文字以上の長さがあり、文字起こしでは繰り返されているものの、原稿には1回しかない部分を探します。
- 文字起こしの文字数と原稿の文字数を比較し、予想より約2割少ない場合は、文を飲み込んだ可能性があると判断します。
異常を検出したら一度再生成し、2つのバージョンを採点して、スコアの低い方を残します。間投詞は正確に文字起こしされないことが多いため、「嗯、哈、啊、呃」などの語を主な照合基準にしないでください。
リアルタイム通話では、Whisperによる20〜30秒の音声の文字起こしに約3秒かかることがあります。後続セグメントは現在のセグメントを再生している間にバックグラウンドで検査できます。最初のセグメントには遅延を隠す再生時間がないため、まず「長さ÷有効文字数」で簡易スクリーニングし、範囲外の場合に完全な文字起こしを開始できます。元の事例での正常範囲は1文字あたり約0.41〜0.62秒で、範囲を超えた場合に完全な文字起こしを行います。
ステップ6:再生成せずに1文を削除する
リスナーが気に入っているのが特定のテイクの演技なら、1文を削除するためだけに全体を読み直させないでください。生成するたびに新しいテイクになるため、呼吸、間、文末の細部を正確に再現するのは困難です。
- 無音を特定する:まず約50ミリ秒単位で音量を描画し、候補となる短い区間を個別に文字起こしして、台詞、呼吸、空白のどれかを確認します。
- ドライ音声から切る:接合部の両端を最も静かな位置に置き、各クリップの前後に十数ミリ秒のフェードイン・フェードアウトを残します。
- 環境音を再配置する:すでに背景音を敷いた完成品を直接カットしないでください。接合部でループ音の拍がずれる可能性があります。
- 再度文字起こしする:完成品に目的の文だけが残っていることを確認します。
- サンプル単位で比較する:変更していない部分が元の音声と一致していることを確認します。
ウェット音声で使われているループ素材が分からない場合は、人の声が完全に止まっている区間から完成品の波形を抽出し、候補素材をループさせて相互相関を計算します。相関が最大になる位置から素材の位相を求められ、内積から背景音のゲインも推定できます。事例では2種類の素材の相関度が0.99に達し、推定ゲインはそれぞれ約0.200と0.241でした。
比較はサンプル単位で正確に行う必要があります。十数サンプルのずれでも類似度が大きく低下し、「素材の配置を間違えた」という誤判定につながります。位置合わせ後、事例の変更していない区間の類似度は0.996〜0.998に達しました。
ステップ7:正しいタイミングで効果音を始める
プロジェクトでは、プロンプト内のカスタムマーカーで背景音の種類を決め、ElevenLabsへ送る前にこれらのマーカーを削除します。重要なルールは、音がどの文から始まるかに合わせて、マーカーもその文に書くことです。
licking:ゆっくりした湿った音に対応します。fingering:やや粘りのある動作音に対応します。速い状態にはfast fingeringと書けます。sucking:ゆっくりした湿った音に対応します。thrust:動作が実際に始まる文の前にだけ置き、台詞全体の冒頭に先回りして置かないでください。still:現在のループ音を停止するために使います。
マーカーがセグメント分割を引き起こす場合、動作が実際に始まる位置には新しい接合部を残す価値があります。一方、声質の再確認だけに使う短いタグは、できるだけ発話の冒頭の行内に置きます。
ステップ8:普段の話し声の声質を設計する
「不自然」は、必ずしも声が柔らかさに欠けるという意味ではありません。元のプロジェクトでは、まず旧モデルをElevenLabs v4にアップグレードし、よりリラックスした柔らかいプロンプトを試しましたが、リスナーはキャラクターが別人になったと感じました。最終的に有効だったのは、人物自身の話し方を明確にする方向でした。
採用された基本プロンプトは次のとおりです。
[low, slow, confident, a little teasing]設定はv4、安定性0.35です。文頭には[exhales]、[chuckles softly]、1つの「ん……」、または何も加えない状態をランダムに入れられます。毎回追加すると、自然な変化が決まりきった口癖になってしまうため、常に入れないでください。
- 合成に送るテキストだけを変更し、チャット画面や履歴にある原文は変更しません。
- 文が元々間投詞で始まっている場合は、重ねて追加しません。
- 既存の声質タグや純粋な英語の内容は、自動ラッピングをそのままスキップできます。
- 電話音声には独立したプロンプト体系を使い、日常音声のランダムな文頭処理を適用しません。
高度なテクニックとよくある落とし穴
プロンプトのルールには「何をすべきか」を書く
「特定の音を書かないでください」とだけ書くと、テキスト生成器がその音を完全に使わなくなることがあります。必要な種類、位置、最低頻度を明確に書き、たまに生じる高音や異常な読み方はプログラムに検出させる方が確実です。
同じ呼吸を繰り返し使わない
気に入った元の区間を新しいバージョンへ移す場合は、同じ生成結果から素材を取れます。ただし、同じ息を2つの位置で繰り返し使わないでください。まったく同じ細部がコピーされていることにリスナーが気づく可能性があります。
検索可能な素材ボックスを作る
認められた吸気音、吸気の音、短い呼吸を、元ファイル、時間範囲、使用位置とともに保存します。後で、ある呼吸タグが無音しか生成しなかった場合は、素材ボックスから呼吸音を一つ補えます。README では、自動的な呼吸補完にはタイムスタンプ付きのインターフェースが必要であることも示しています。「文中に無音がある」という情報だけでは、呼吸の欠落と通常の省略記号による間を区別できません。
リアルタイム通話では割り込みに対処する
システムがまだ返答を再生しておらず、相手が話し始めた場合は、まだ発声されていない古い返答を破棄し、新しい内容を直接処理します。新しい発話を古い返答の再生終了までバッファリングしてはいけません。同じ発話に二度応答する可能性があるためです。テキストとして認識できない短い音については、元の処理で自動的に挿入するフィラー音の待機時間を 2.5 秒から 4.5 秒に延長し、実際の発話より先にトリガーされる確率を下げています。
まず問題を聞き取り、何を測るかを決める
音量、音高、無音の長さ、単語数の比率、相互相関は、すでに発見した問題を検証することしかできません。これらだけで「ニュアンスが正しいか」を判断することはできません。最も効果的な手順は、まず聞き手に不自然な箇所を具体的に指摘してもらい、それに対応する指標を設定することです。
推奨する完全なワークフロー
- 声質、呼吸、間、効果音のマーカーを含むテキストを作成します。
- 後処理専用の効果音マーカーを削除し、残ったテキストを ElevenLabs v4 に送ります。
- ドライ音声を保存し、Whisper で文字起こしして、重複、文の欠落、内容のずれを確認します。
f0とrmsを計算し、音声を発話ブロック、呼吸ブロック、その他の有声音ブロックに分割します。- 発話開始前の呼吸を台詞から約 0.08 秒の位置に移動し、文中の呼吸は残します。
- ブロックごとに音量の急変を検出し、異常なブロックを滑らかにフェードさせます。
- 動作マーカーに従ってループする効果音を配置し、ウェット音声版を書き出します。
- 再度文字起こしを行い、総再生時間、長い無音の数、呼吸の位置、未変更部分のサンプル類似度を確認します。
- 聞き手が認めた元のクリップとその時間範囲を保存し、最終ミックスだけを残さないようにします。
まとめ
自然な AI 音声は、呼吸タグを増やして積み重ねるだけでは得られません。より信頼できる方法は、息を言葉に沿わせ、動作音を正しいタイミングから開始し、各合成結果を文字起こしで照合し、編集が必要な場合も元の演技をできる限り残すことです。
プロンプトは「何を書くか」と「どのような口調で話すか」を決め、プログラムは数十ミリ秒単位の位置、異常な音量、重複や読み漏れ、サンプルレベルの接続を担当します。両者を組み合わせることで、偶然生成された一つの良い音声を、繰り返し実行できる制作ワークフローに変えられます。詳しい背景については、ElevenLabs プロンプトガイド、ElevenLabs の呼吸音に関する説明、および発話時の呼吸に関する研究を参照してください。
