UnityからVOICEVOXを簡単に利用できるライブラリを作った

UnityからVOICEVOX Engineのサーバーを利用して、音声合成を利用できるシンプルなライブラリを作ってみました。基本的な利用方法はリポジトリの[README](https://github.com/mikito/unity-voicevox-bridge)から参照できますが、本記事ではもう少し詳しく解説します。

## Unity VOICEVOX Bridge
https://github.com/mikito/unity-voicevox-bridge

- 機能と特徴
  - VOICEVOX Engineサーバーと通信して合成した音声を再生
  - シンプルな利用Interface（async/await）
  - VOICEVOX以外のライブラリには非依存
  - ある程度の異常系やリソース管理を考慮
 
元々、UnityからVOICEVOXを利用するコードはそこまで難しくはないですが、整理して使いやすくまとめてみました。

## どのような使用感？

まず、何かしらの方法でVOICEVOX Engineのサーバーを起動します（こちらは後述します）。

Unityプロジェクト側では、ライブラリが用意しているVOICEVOXコンポーネントを任意のGameObjectにアタッチします。
<img src=https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/97161/347f1a16-966e-c78a-ed56-c09b99b9a27a.png width=320px>

スクリプト側では、先ほどのVOICEVOXコンポーネントに対して、読み上げたいテキストを渡せば音声合成と再生が開始されます。

```cs:Example.cs
using UnityEngine;
using VoicevoxBridge;

public class Example : MonoBehaviour
{
    [SerializeField] VOICEVOX voicevox;

    async void Start()
    {
        int speaker = 1; // ずんだもん あまあま
        string text = "ずんだもんなのだ";
        await voicevox.PlayOneShot(speaker, text);
        Debug.Log("ボイス再生終了");
    }
}
```

例えば、このtextの部分をChatGPT APIなどを利用して生成したテキストにすると、動的な文章の読み上げができるようになります。この辺りは[こちら](https://qiita.com/mikito/items/e3f897e641e4e6400ebe)の記事を参考にしていただけると幸いです。

## ライブラリの導入手順

Package Managerから追加すると楽です。Package ManagerウィンドウからAdd package from git URLを選択し、`https://github.com/mikito/unity-voicevox-bridge.git?path=Assets/VoicevoxBridge/`と入力してください。

<img src=https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/97161/f697057e-832b-038b-d100-f59b8402e668.png width=480px>

<img src=https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/97161/fe54d2d5-0730-4e9c-cace-b1509bd0d90f.png width=480px>

もしくは、こちらからUnityパッケージをDLしインポートしてください。
https://github.com/mikito/unity-voicevox-bridge/releases

## VOICEVOX Engineの起動方法

基本的には[VOICEVOX](https://voicevox.hiroshiba.jp/)のアプリケーションを立ち上げておけばOKですが、いつくか補足します。

### VOICEVOXの基本的な構成とEngine起動方法

VOICEVOXはシステムの構成として、Editor、Engine、Coreに分かれており、本ライブラリに必要なコンポーネントはEngineです。

https://github.com/VOICEVOX/voicevox/blob/main/docs/%E5%85%A8%E4%BD%93%E6%A7%8B%E6%88%90.md

ただし、[こちら](https://voicevox.hiroshiba.jp/)からダウンロードできるVOICEVOXのアプリケーション（Editor）にもEngine/Coreが内包されており、起動中はEngineが立ち上がります。そのため、本ライブラリを利用する際は、単純にVOICEVOXのGUIアプリケーションを立ち上げておけばOKです。

その他の起動方法（Dockerなど）はREADMEにも記載してあるので参考にしてください。

https://github.com/mikito/unity-voicevox-bridge#voicevox-engine%E3%81%AE%E8%B5%B7%E5%8B%95

### ローカルPC上以外でEngineを立てる場合

VOICEVOXは深層学習を利用した音声合成システムであり、生成処理にも結構な計算リソースを食います。可能であればGPU版で起動できるとよいです（自分は試せてはいません）。手元のPCにてCPU版で起動すると、結構他の処理と計算リソースを食い合い処理に少し時間がかかります。

作業用PCのローカルにVOICEVOX Engineを立てるのではなく、別のPCにサーバーとして立ち上げることもできます。処理負荷が気になる場合や、完成したシステムとしてstagingするときはこのような環境を構築したほうが良さそうです。

この辺りの手順は少しまとめてみました。以下のドキュメントは自分都合でMac miniにホストにしていますが、linuxでもwindowsでも同じような環境は作れる思います。

https://qiita.com/mikito/items/21aa74c3850a70c647f7

Engineのホストがデフォルト以外の場所であれば、VOICEVOXコンポーネントのVocevox Engine URLを変更してください。
<img src=https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/97161/2eee01d8-6197-f937-90ec-81567a57d365.png width=520px>

## VOICEVOX Bridgeの利用方法

本ライブラリのもう少し具体的な利用法や、コーディングに関する補足事項について解説します。

### CreateVoice関数とPlay関数
PlayOneShot関数は非常にシンプルですが、少し扱いにくいところもあります。それは、音声合成処理には少し時間がかかるため、関数呼び出しから実際に再生が完了するまで、それらの処理を呼び出し側で管理できないということです。

例えばVOICEVOX EngineをCPU版で起動し、長い文章をPlayOneShotで呼び出すと、十数秒経ってから音声が再生がされることもあります。

もう少し自前で管理したい場合は、CreateVoice関数とPlay関数を分けて利用するとよいです。こちらのパターンだと、例えば時間がかかる音声合成処理中にインジケーターを出したり、複数の音声合成処理が終わってから連続して再生するなどの制御ができます。

```cs:Example2.cs
using UnityEngine;
using VoicevoxBridge;

public class Example2 : MonoBehaviour
{
    [SerializeField] VOICEVOX voicevox;
    [SerializeField] GameObject activityIndicator;

    async void Start()
    {
        int speaker = 1; // ずんだもん あまあま
        string text = "ずんだもんなのだ";

        activityIndicator.SetActive(true); //　くるくるの表示
        Voice voice = await voicevox.CreateVoice(speaker, text);
        activityIndicator.SetActive(false);

        await voicevox.Play(voice);
        Debug.Log("ボイス再生終了");
    }
}
```

### Voiceオブジェクト

CreateVoice関数で返却されるVoiceオブジェクトは、UnityのリソースであるAudioClipを保持しています。Play関数内では一度再生したVoiceオブジェクトを無効化しAudioClipも破棄しますが、もし生成したVoiceオブジェクトを使い回したいときはautoReleaseVoiceオプションにfalseを指定してください。

```cs
await voicevox.Play(voice, autoReleaseVoice: false);
```

この場合、Voiceオブジェクトを利用しなくなったタイミングでvoice.Disose()を呼んでください。

また、Voiceオブジェクトを生成後、なんらかの理由で処理が異常中断してしまうことを考えると、以下のようにVoiceオブジェクトをusingで囲うとより安全です。

```cs
using(var voice = await voicevox.CreateVoice(speaker, text))
{
    await voicevox.Play(voice);
}
```

### async/awaitとCancellationToken
各コンポーネントの非同期処理はasync/awaitパターンになっています。このままでも利用できますが、アプリケーション側で実践的に利用する場合はUniTaskの導入をお勧めします。

https://github.com/Cysharp/UniTask

また、各非同期メソッドにはCancellationTokenを渡せるため、基本的にはこちらを利用して中断をハンドリングしてください。UniTaskのGetCancellationTokenOnDestroy()メソッドを利用してGameObjectにバインディングする方法がお手軽です。

まとめると、以下のようにコーディングするのが基本系になってくると思います。

```cs
using System;
using UnityEngine;
using VoicevoxBridge;
using Cysharp.Threading.Tasks;

public class Example3 : MonoBehaviour
{
    [SerializeField] VOICEVOX voicevox;

    void Start()
    {
        Play().Forget();
    }

    async UniTask Play()
    {
        int speaker = 1; // ずんだもん あまあま
        string text = "ずんだもんなのだ。";
   
        try
        {
            Debug.Log("音声合成・再生開始");
            await voicevox.PlayOneShot(speaker, text, this.GetCancellationTokenOnDestroy());
            Debug.Log("音声合成・再生完了");
        }
        catch (Exception e)
        {
            Debug.Log(e);
        }
    }
}
```

もしくは、処理は投げっぱなしで良いという場合は以下のようにも書けます。
```cs
    void Start
    {
        int speaker = 1; // ずんだもん あまあま
        string text = "ずんだもんなのだ。こんにちはなのだ。こんばんはなのだ。";
        voicevox.PlayOneShot(speaker, text, this.GetCancellationTokenOnDestroy()).AsUniTask().Forget();
    }
```

またasync/awaitわからないよ！という方は以下のようにすればコルーチンで扱えそうです。
```cs
using System.Collections;
using UnityEngine;
using VoicevoxBridge;

public class Example : MonoBehaviour
{
    [SerializeField] VOICEVOX voicevox;

    void Start()
    {
        StartCoroutine(Play2());
    }

    IEnumerator Play()
    {
        int speaker = 1; // ずんだもん あまあま
        string text = "ずんだもんなのだ。";

        Debug.Log("音声合成・再生開始");
        var task = voicevox.PlayOneShot(speaker, text);
        yield return new WaitUntil(() => task.IsCompleted);
        Debug.Log("音声合成・再生完了");
    }
```

なお、コルーチンへの変換はUniTaskを使うのであれば以下でもできます。
```cs
        yield return voicevox.PlayOneShot(speaker, text).AsUniTask().ToCoroutine();
```


### VoicevoxPlayer
MonoBehaviourにVOICEVOXコンポーネントをつける方法が一番簡単ですが、それすらも面倒な場合は、内部的に利用しているVoicevoxPlayerを直接利用すれば、GameObjectの準備は不要です。内部でAudioSouceなどの生成も自動でやります。この場合、playerインスタンスは適宜Disposeしてください。

```cs
    async void Play()
    {
        int speaker = 1; // ずんだもん あまあま
        string text = "ずんだもんなのだ。";
        using(var player = new VoicevoxPlayer("http://localhost:50021/"))
        {
            await voicevox.PlayOneShot(speaker, text);
        }
    }
```

### AudioSourceやAudioMixerとの連携

VOICEVOXコンポーネントに予めAudioSourceを設定しておけば、Unityの機能をそのまま利用できます。例えば3Dオーディオにしたり、AudioMixerでフィルターをかけたり、色々できそうですね。

![スクリーンショット 2023-03-09 20.52.54.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/97161/a1a8e282-381b-d00b-a334-8b3da42dc256.png)


## 補足: Speaker IDについて

VOICEVOX Engineが起動している状態で以下にアクセスすると、speakerの一覧を返してくれます。パラメータを渡す時の参考になるかと思います。

http://localhost:50021/speakers

この辺りの連携は、将来的にはライブラリ側で実装しても良さそうです。

## まとめ

以上、Unity VOICEVOX Bridgeの紹介と解説でした。本当に簡単に利用できるので、ぜひ使ってみてください。自分もこれからChatGPT APIと組み合わせて、ずんだもんに何か喋らせてみます。