Unity2019～でWindowsの標準音声合成機能を使って音声合成をする


# 実行環境
VisualStudio 2019
Unity2022.2.17
Windows11

# 要約
## 背景
Unityで、完全ローカルで音声合成を行い、作成した音声をファイル保存したい

## 詰まりポイント
普通のC#では動作したSpeechSynthesizerが使用できない

## 解決策
PowerShellのコマンドを書き出して、Unity外で音声合成したものを使用する



以下にて詳しい内容
# 概要
今回行いたかったことは、「ユーザが作成した文字列を、任意の言語で音声合成し、ファイル出力する(そしてそれをUnity内で喋らせる)」

Unity内で喋らせるのは、単にローカルファイルをWebRequesturlで指定してAudioClipに入れるだけなので大きな問題はないが、Windowsで音声合成を行う部分で詰みが発生。

原因は、C#で標準で用意されているSpeechSynthesizerクラスがUnityでは呼べない仕様だかららしい。


そのため、SpeechSynthesizerを使わずにWindowsの音声合成を利用する方法が必要だった。

# 実行しなかった別の解決策
1. AzureのTextToSpeechとか使う(＝Windows標準以外の音声合成以外の方法を試す)
    * 他人に使わせる用だったので、サブスクの登録ができないため却下。他のクラウド系サービスも同様
2. VOICE VOXなど、無料で使える音声合成ソフトを使う
    * 今回は英語/スペイン語など、多言語で喋らせたかったため、日本語以外はほぼ使えない国内ソフトは使用できず(VOICE VOXのベースはOpenJTalkだったはず?)


# Windows標準の音声合成の利用方法
そもそも、Windows標準の音声合成と(勝手に)読んでいるものは、PowerShellなどで利用できる音声合成機能である
自身のPC内にダウンロードしている合成エンジンを使って喋らせることができる
例として、「Hello」を「Microsoft Zira Desktop」という声の種類(標準で入っている英語用の声)で喋らせる場合、WindowsPowerShellを立ち上げて以下のように入力すると、スピーカーから「Hello」と喋っている声が聞こえる

```ps:powershell_Speaker.ps1
Add-Type -AssemblyName System.Speech
$ps_speak = New-Object System.Speech.Synthesis.SpeechSynthesizer
$ps_speak.SelectVoice("Microsoft Zira Desktop")
$ps_speak.Speak("Hello")
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/254776/3d03d31a-b3e3-d190-b81e-907428c844d7.png)


ファイル出力する場合はこう↓

```ps:powershell_OutputFile.ps1
Add-Type -AssemblyName System.Speech
$ps_speak = New-Object System.Speech.Synthesis.SpeechSynthesizer
$ps_speak.SelectVoice("Microsoft Zira Desktop")
$ps_speak.SetOutputToWaveFile("D:\\test.wav")
$ps_speak.Speak("Hello")
$ps_speak.Dispose()
```

# PowerShellについて
C#のSpeechSynthesizerが使えなかったため、最初、このコマンドをC#のProcessで作った別プロセスに一行ずつ入れようとしたが、同期/非同期の管理が面倒くさかったので却下。
PowerSheelには、「ファイルに書き出したコマンドをまとめて実行する」コマンドがあるため、ファイルに上記のコマンドを書き出して出力することに変更


ということで、以下のようなコードを作成

```cs:ConvertToVoiceByPowerShell.cs
static void ConvertToVoiceByPowerShell(string text, int voiceType, string filePath)
    {

        //一時ファイル置き場にpsファイルを作成
        var psFilePath = Application.temporaryCachePath + "\\ps.ps1";
        using (var sw = new System.IO.StreamWriter(psFilePath))
        {
            sw.WriteLine("Add-Type -AssemblyName System.Speech");
            sw.WriteLine("$ps_speak = New-Object System.Speech.Synthesis.SpeechSynthesizer");
            switch (voiceType)
            {
                case 0:
                default:
                    //英語
                    sw.WriteLine("$ps_speak.SelectVoice(\"Microsoft Zira Desktop\")");
                    break;
                case 1:
                    //スペイン語
                    sw.WriteLine("$ps_speak.SelectVoice(\"Microsoft Helena Desktop\")");
                    break;
            }
            sw.WriteLine($"$ps_speak.SetOutputToWaveFile(\"{filePath}\")");
            sw.WriteLine($"$ps_speak.Speak(\"{text}\")");

        }

        //コマンド実行
        Process cmd = new Process();
        cmd.StartInfo.FileName = "PowerShell.exe";
        cmd.StartInfo.WindowStyle = ProcessWindowStyle.Hidden;
        cmd.StartInfo.Arguments = $" powershell -ExecutionPolicy Bypass {psFilePath}";
        cmd.Start();
        //本当はここで非同期処理の諸々をした方がいい
    }
```

この関数で、textに喋らせたい言葉、filePathに出力先のパス、voiceTypeに適当な番号を入れると動作する。

サンプルの中ではスペイン語が入っているが、初期状態の日本人のPCには入っていないので、以下のサイトを参考にしながら入れる
https://aprico-media.com/posts/7751


# あとがき
日本語で行うと、明らかに合成された音声の質が低い。(※VOICE VOXやVOICE PEAKと比べて)
他の言語でもその程度のレベルの音声合成であろうことが予想できる