【Unity】VOICEVOXエンジンでリアルタイム音声合成する方法を紹介します!
VOICEVOXとはフリーの音声合成ツールです。エディターとエンジンとコア部分に分けられて提供されています。今回はエンジン部分を利用して文字列から音声合成されたWAVEデータを作成しUnity上でリアルタイム再生するプログラムを実装します。VOICEVOXの詳細は本家サイトを参照してください。
VOICEVOXコアを使った音声合成する記事を書きましたのでこちらもあわせてご覧下さい。
VOICEVOXエンジン
VOICEVOXエンジンを利用してリアルタイム音声合成をします。本家サイトのダウンロードページから最新版のZipパッケージをダウンロードして解凍します。
			run.exeをダブルクリックしてエンジンを起動させます。
			エンジンは127.0.0.1ポート番号50021にHTTPリクエストを送ることで利用できます。エンジンを起動した状態で下記アドレスからドキュメントとスピーカー(話し手)一覧を確認できます。
				・ドキュメント http://127.0.0.1:50021/docs
				・スピーカー http://127.0.0.1:50021/speakers
				
ここからはエンジンを利用して音声合成する方法を紹介していきます。HTTPリクエストにはHttpClientをシングルトン化したHttpClientManagerを利用しますので詳細は下記記事をご覧ください。
audio_query
audio_queryで音声合成用のクエリデータを作成します。POSTリクエストでパラメーターにはテキストとスピーカーを渡します。受け取ったJSON形式のデータは音声合成するときに使用します。
以上を踏まえて非同期メソッドAudioQueryAsyncを実装します。
async Task<string> AudioQueryAsync(string text, int speaker)
{
    string uri = "http://127.0.0.1:50021/audio_query?text=" + text + "&speaker=" + speaker;
    var task = await HttpClientManager.Instance.PostAsync(uri);
    return await task.Content.ReadAsStringAsync();
}
			synthesis
synthesisで音声を合成します。POSTリクエストでパラメーターにはスピーカー番号を、コンテンツにはaudio_queryで作成したクエリデータを渡します 。音声合成された結果はWAV形式のバイト配列で受け取れます。そのままファイルに保存すればWAVファイルとして使用できます。
以上を踏まえて音声合成する非同期メソッドSynthesisAsyncを実装します。
async Task<byte[]> SynthesisAsync(int speaker, string data)
{
    string uri = "http://127.0.0.1:50021/synthesis?speaker=" + speaker;
    var content = new StringContent(data, Encoding.UTF8, "application/json");
    var task = await HttpClientManager.Instance.PostAsync(uri, content);
    return await task.Content.ReadAsByteArrayAsync();
}
			音声合成メソッドの作成
AudioQueryAsyncとSynthesisAsyncをまとめてテキストとスピーカーからWAVバイナリを作成する非同期メソッドVoiceAsyncを実装します。
async Task<byte[]> VoiceAsync(string text, int speaker)
{
    string data = await AudioQueryAsync(text, speaker);
    return await SynthesisAsync(speaker, data);
}
			WAVのリアルタイム再生
UnityではWAVデータのバイト配列からAudioClipをリアルタイムで作成する機能を提供していませんので、はなちるのマイノート様で紹介されている方法を利用させていただきます。ソースコードはGitHubで公開されているものをお借りします。
GitHubのパス
以下のコードでバイト配列からAudioClipを作成することができます。
byte[] data = ...;
AudioClip clip = Wav.ToAudioClip(data, "test");
			AudioClipはAudioSourceで再生することができます。
AudioSource source = ...;
source.PlayOneShot(clip);
		確認用クラスの実装
今までのコードをまとめた確認用クラスを作ります。コードを簡潔にするためにタスクをウェイトしています。エラー処理もしていません。本来ならコルーチンなどでタスクの終了を検知するようにしてメインスレッドをブロックせずに実装するのが良いでしょう。
VoicevoxTest.cs
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
using UnityEngine;
public class VoicevoxTest : MonoBehaviour
{
    void Start()
    {
        string text = "こんにちは";
        Task<byte[]> task = Task.Run(() => VoiceAsync(text, 0));
        task.Wait();
        AudioClip clip = Wav.ToAudioClip(task.Result, text);
        AudioSource source = gameObject.AddComponent<AudioSource>();
        source.PlayOneShot(clip);
    }
    async Task<string> AudioQueryAsync(string text, int speaker)
    {
        string uri = "http://127.0.0.1:50021/audio_query?text=" + text + "&speaker=" + speaker;
        var task = await HttpClientManager.Instance.PostAsync(uri);
        return await task.Content.ReadAsStringAsync();
    }
    async Task<byte[]> SynthesisAsync(int speaker, string data)
    {
        string uri = "http://127.0.0.1:50021/synthesis?speaker=" + speaker;
        var content = new StringContent(data, Encoding.UTF8, "application/json");
        var task = await HttpClientManager.Instance.PostAsync(uri, content);
        return await task.Content.ReadAsByteArrayAsync();
    }
    async Task<byte[]> VoiceAsync(string text, int speaker)
    {
        string data = await AudioQueryAsync(text, speaker);
        return await SynthesisAsync(speaker, data);
    }
}
		確認用シーンの作成と実行
確認用シーンを作成し、Unityを実行して動作を確認します。
用意するスクリプトの確認
用意するスクリプトは以下の画像の通りです。
			DontDestroyOnLoadはゲームオブジェクトを常駐化させるためのスクリプトです。
HttpClientManagerはHttpClientをシングルトン化したスクリプトです。
SingletonMonoBehaviourはシングルトンコンポーネントを実装するためのスクリプトです。
シーンの構成
ゲームオブジェクトを作成しインスペクターで先程作ったVoicevoxTestコンポーネントを追加します。
			常駐用のゲームオブジェクトを作成しインスペクターからDontDestroyOnLoadとHttpClientManagerを追加します。
			Unityを実行して「こんにちは」という音声が再生されるか確認して下さい。VOICEVOXエンジンの起動をお忘れなく。
リアルタイムで音声合成するプログラムを作成することができました。
					音声によるナビゲーションツールなどに利用できそうですね。
ファイルに保存する方法
リアルタイムで再生することを解説しましたがファイルに保存すればWAVファイルとして使えます。
string text = "こんにちは";
Task<byte[]> task = Task.Run(() => VoiceAsync(text, 0));
task.Wait();
File.WriteAllBytes(text+".wav", task.Result);
		プライベートネットワークでエンジンを利用する方法
プライベートネットワークでVoicevoxエンジンを利用するにはエンジンを起動するパソコンのIPアドレスを固定する必要があります。
イーサネットのプロパティをクリックします。
			インターネットプロトコルバージョン4(TCP/IPv4)を選択してプロパティ(R)ボタンをクリックします。
			次のIPアドレスを使う(S)を選択して必要な情報を入力します。使用しているネットワーク環境に合わせて入力して下さい。
			これでエンジンを起動するパソコンのIPアドレスを192.168.11.100に固定できました。
続いてホストオプションを追加してエンジンを起動します。
最後にエンジンを起動したパソコン以外からhttp://192.168.11.100:50021/docsにアクセスしてドキュメントが見えるか確認して下さい。
		スピーカーID一覧
VOICEVOX Ver. 0.14.5
| ID | 名前 | スタイル | 
|---|---|---|
| 0 | 四国めたん | あまあま | 
| 1 | ずんだもん | あまあま | 
| 2 | 四国めたん | ノーマル | 
| 3 | ずんだもん | ノーマル | 
| 4 | 四国めたん | セクシー | 
| 5 | ずんだもん | セクシー | 
| 6 | 四国めたん | ツンツン | 
| 7 | ずんだもん | ツンツン | 
| 8 | 春日部つむぎ | ノーマル | 
| 9 | 波音リツ | ノーマル | 
| 10 | 雨晴はう | ノーマル | 
| 11 | 玄野武宏 | ノーマル | 
| 12 | 白上虎太郎 | ふつう | 
| 13 | 青山龍星 | ノーマル | 
| 14 | 冥鳴ひまり | ノーマル | 
| 15 | 九州そら | あまあま | 
| 16 | 九州そら | ノーマル | 
| 17 | 九州そら | セクシー | 
| 18 | 九州そら | ツンツン | 
| 19 | 九州そら | ささやき | 
| 20 | もち子さん | ノーマル | 
| 21 | 剣崎雌雄 | ノーマル | 
| 22 | ずんだもん | ささやき | 
| 23 | WhiteCUL | ノーマル | 
| 24 | WhiteCUL | たのしい | 
| 25 | WhiteCUL | かなしい | 
| 26 | WhiteCUL | びえーん | 
| 27 | 後鬼 | 人間ver. | 
| 28 | 後鬼 | ぬいぐるみver. | 
| 29 | No.7 | ノーマル | 
| 30 | No.7 | アナウンス | 
| 31 | No.7 | 読み聞かせ | 
| 32 | 白上虎太郎 | わーい | 
| 33 | 白上虎太郎 | びくびく | 
| 34 | 白上虎太郎 | おこ | 
| 35 | 白上虎太郎 | びえーん | 
| 36 | 四国めたん | ささやき | 
| 37 | 四国めたん | ヒソヒソ | 
| 38 | ずんだもん | ヒソヒソ | 
| 39 | 玄野武宏 | 喜び | 
| 40 | 玄野武宏 | ツンギレ | 
| 41 | 玄野武宏 | 悲しみ | 
| 42 | ちび式じい | ノーマル | 
| 43 | 櫻歌ミコ | ノーマル | 
| 44 | 櫻歌ミコ | 第二形態 | 
| 45 | 櫻歌ミコ | ロリ | 
| 46 | 小夜/SAYO | ノーマル | 
| 47 | ナースロボ_タイプT | ノーマル | 
| 48 | ナースロボ_タイプT | 楽々 | 
| 49 | ナースロボ_タイプT | 恐怖 | 
| 50 | ナースロボ_タイプT | 内緒話 | 
| 51 | †聖騎士 紅桜† | ノーマル | 
| 52 | 雀松朱司 | ノーマル | 
| 53 | 麒ヶ島宗麟 | ノーマル | 
関連ページ
こちらのページも合わせてご覧下さい。