Unityで音声認識＋読み上げ

# はじめに
Vtuberの[のらきゃっと](https://twitter.com/vr_girl_noracat)さんっていらっしゃるじゃないですか。
彼女の「声を認識して読み上げソフトで読み上げさせる」っていうのがすごいいいなーって思ったので自分も作ってみました。
琴葉葵ちゃんになれます。

# できたもの
マイクの声を拾ってVOICEROID2で葵ちゃんに再生させるプログラム
ついでにUnity内に字幕も生成する

![NoName_2018-1-31_6-18-18_No-00_LI.jpg](https://qiita-image-store.s3.amazonaws.com/0/47220/5a56010f-0326-3a6a-fb03-8bf981a521db.jpeg)

# 方法
力技です。

1. `Windows.Speech`で音声認識
2. クリップボードに貼り付け
3. VOICEROIDのウィンドウをアクティブにする
4. ペースト＆再生
5. 元のソフトをまたアクティブにする

という感じです。

# 必要なもの

- Unity (2017.3)
- VOICEROID2

# 実装
## VOICEROID再生部分
いきなり苦肉の策なんですが、UnityでのWin32APIの使い方がわからなかった（このままだとプロセスをアクティブにできない）ので、「プロセスのアクティブ化」「コピペ」だけをするプログラムを書き出します。

```C#:Program.cs
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Text.RegularExpressions;
using System.Windows.Forms;

namespace SendTextToVoiceroid
{
    class Program
    {
        string PROCESSNAME = "<ビルドしたUnityのプログラム名>"; // program.exeとビルドしたなら"program"
        string WINDOWTITLE = "<ビルドしたUnityのウィンドウ名>"; // PlayerSettingsのProductName

        [DllImport("user32.dll")]
        [return: MarshalAs(UnmanagedType.Bool)]
        static extern bool SetForegroundWindow(IntPtr hWnd);

        static void Main(string[] args)
        {
            bool isFoundGameProcess = false;
            bool isPlayedVoice = false;
            Process game = null;

            foreach (var p in System.Diagnostics.Process.GetProcesses())
            {
                if (!isFoundGameProcess && p.ProcessName == PROCESSNAME && p.MainWindowTitle == WINDOWTITLE)
                {
                    game = p;
                    isFoundGameProcess = true;
                }

                if (!isPlayedVoice && Regex.IsMatch(p.ProcessName, @"VoiceroidEditor"))
                {
                    SetForegroundWindow(p.MainWindowHandle);
                    SendKeys.SendWait("^a");
                    SendKeys.SendWait("^v");
                    SendKeys.SendWait("{F5}");
                    isPlayedVoice = true;
                }

                if (isFoundGameProcess && isPlayedVoice)
                {
                    SetForegroundWindow(game.MainWindowHandle);
                    break;
                }
            }
        }
    }
}

```

ちょっとややこしくなりましたが、`System.Diagnostics.Process.GetProcesses()`で稼働中のプロセスを一覧でもってきて、プロセス名とメインウィンドウ名から目的のウィンドウ（VOICEROIDとUnityのプログラム）を持ってきます。
あとはVOICEROIDの方をアクティブにして`Ctrl+A` `Ctrl+V` `F5`と送信して全選択→貼り付け→再生を実現しています。
あとはまたUnityのプロセスをアクティブにするだけ。
これを`こぴぺ.exe`とかなんとか適当なexeファイルとして書き出しておきます。

## Unity部分

```C#:Hoge.cs
using UnityEngine;
using UnityEngine.Windows.Speech;

public class Hoge : MonoBehaviour
{
    const string path = @"<さっきのこぴぺ.exeのパス>";

    private DictationRecognizer dicRecognizer;

    void Start()
    {
        dicRecognizer = new DictationRecognizer();
        dicRecognizer.InitialSilenceTimeoutSeconds = 10;
        // 確定
        dicRecognizer.DictationResult += (text, confidence) =>
        {
            gameObject.GetComponent<UnityEngine.UI.Text>().text = text;
            GUIUtility.systemCopyBuffer = text;
            System.Diagnostics.Process.Start(path);
        };
        // 推測
        dicRecognizer.DictationHypothesis += (text) => {
            // 推測時にする処理
        };
        // 停止時
        dicRecognizer.DictationComplete += (completeCause) =>
        {
            // 要因がタイムアウトなら再び起動
            if (completeCause == DictationCompletionCause.TimeoutExceeded)
                dicRecognizer.Start();
        };
        dicRecognizer.Start();

    }
}
```
これをGUITextオブジェクトにアタッチしておわりです。

`DictationRecognizer`オブジェクトで音声認識ができます。
DictationResultイベントにリスナーを登録しておけば認識される度に処理ができるので、GUIText（字幕）の文字を書き換え、クリップボードにコピーし、さっきのプログラムを呼び出しています。

# 補足
このままだと使いにくいので、実際には推測候補を表示したり認識を再起動させるボタンを追加したりしました。
あとはモーションキャプチャで3Dモデルを動かしたりしたら完璧そうですね。
Unityに組み込める合成音声ソフトとかがあればこんな面倒な事をしなくてもUnityだけで完結できるんですけどね…。

出力をDiscordに繋げば葵ちゃんの声で通話したりできますね！



# 参考

- [Holoの嫁と会話するためのステップバイステップ - Qiita](https://qiita.com/decchi/items/f4766d6f45da0e59639b)
- [Unity - スクリプトリファレンス: DictationRecognizer](https://docs.unity3d.com/jp/540/ScriptReference/Windows.Speech.DictationRecognizer.html)
- [VRアプリで使える音声コマンドを簡単に実装してみる（Oculus Rift CV1 + Windows10) - Qiita](https://qiita.com/decchi/items/c9ae04b049ace49f91bc)
- [【Unity】音声認識エンジンについて調べてみた - Qiita](https://qiita.com/tomoriaki/items/af1e0d00fc6f6f00e40e)
- [動作中のプロセスにキー入力をするフォームアプリケーション - yattの日記](動作中のプロセスにキー入力をするフォームアプリケーション - yattの日記)