Oculus Quest 2 で話してみよう！Part１

#目次

- [いいタイミングで頷いてもらおう！(Part1)](https://qiita.com/KayaTakashiro/items/92092259bc666a83762c)
- [「好き」と言ったら「好き」と返して欲しいんだ…。(Part2)](https://qiita.com/KayaTakashiro/items/33e2e420c3094603d171)

##やること

今回は前回作成した、[Oculus Quest 2](https://qiita.com/KayaTakashiro/items/65838261b896117915dd)のアプリに追加機能をつけていきたいと思います。

実際に付ける内容は、
- 一定時間話しかけた後で、頷くor 相槌をうってもらう
- 特定のフレーズや単語に対して、反応してもらう
の2つの機能をつけていきます。

では、さっそくやっていきましょう！

##いいタイミングで頷いてもらおう！

###マイクを取り付けよう！

まず、音声の取得に必要なマイクをunityに入れていきます。

OVRCamearaRigの子に空のオブジェクトを作成して、名前をMICとしてください。

###音声を取得してみよう！

以下のGetVoice.csファイルをMICにアタッチしてください。

```
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using UnityEngine;
using UnityEngine.UI;

public class GetVoice : MonoBehaviour
{
    [SerializeField] 
    AudioSource listenSource;

    [SerializeField]
    Animator animator;

    //private fields
    private float gain = 8000.0f;
    float volume;
    float frequency;
    bool isSpeaking;
    bool isRecording;
    float bufferTimer;　//話した後の余韻の時間 or　言葉に詰まった時の時間　の計測時間
    float speakingTimer;　//実際に話している時間

    //constant values
    const int RECORD_SECONDS = 2;
    const int SAMPLING_FREQUENCY = 16000; // MSのSSTはサンプリングレートが16000Hz
    const int FFTSAMPLES = 2 << 8; //256bitのサンプルをFFTでは選んでとる。

    const float MIN_FREQ = 100.0f; //母音のFrequency は　100Hz 以上 1400Hz以下。　ただし、子音字は異なる。
    const float MAX_FREQ = 1400.0f;
    const float MIN_VOLUME = 1.0f;
    const float BUFFER_TIME = 1.6f;　//話した後の余韻の時間 or　言葉に詰まった時の時間。
    
    
    // Start is called before the first frame update
    void Start()
    {
        #if UNITY_EDITOR
        gain = 2000.0f;
        #endif
        
        GetMic();
    }

    
    void Update()
    {
        Waiting();
    }
    
    
    
    private void GetMic()
    {
        while (Microphone.devices.Length< 1) { }
        string device = Microphone.devices[0];
        listenSource.loop = true;
        listenSource.clip = Microphone.Start(device, true, RECORD_SECONDS, SAMPLING_FREQUENCY);
        while (!(Microphone.GetPosition(device) > 0)) { }
        listenSource.Play();
    }

    async private void Waiting()
    {
        CalculateVowel();
        if (MIN_FREQ < frequency && frequency < MAX_FREQ && MIN_VOLUME < volume) //しゃべり始めの時間
        {
            isSpeaking = true;

            bufferTimer = 0.0f;
            speakingTimer += Time.deltaTime;

            //始めてレコーディングを開始したとき
            if (!isRecording)
            {
                isRecording = true;

                bufferTimer = 0.0f;
                speakingTimer = 0.0f;

            }
        }
        else if (isSpeaking && volume > MIN_VOLUME)
        {
            //子音字をしゃべっていると判定
            bufferTimer = 0.0f;
            speakingTimer += Time.deltaTime;
        }
        else if (isSpeaking && bufferTimer < BUFFER_TIME) // 余韻の時間
        {
            bufferTimer += Time.deltaTime;
            speakingTimer += Time.deltaTime;
        }
        else
        {
            bufferTimer = 0.0f; // 後で、speakingTimerの条件処理あり！

            isSpeaking = false;
            if (isRecording)
            {
                isRecording = false;

                AizuchiAnimation(speakingTimer);
            }

            speakingTimer = 0.0f;
        }

    }

    private void CalculateVowel()
    {

        //ここが処理の重さ的にやばいかも？
        var max_volume = 0.0f;
        var max_index = 0;
        var total_volume = 0.0f;
        //録音時間＊サンプリング周波数の個数のデータがほしい！
        float[] temp = new float[FFTSAMPLES];
        listenSource.GetSpectrumData(temp, 0, FFTWindow.Blackman);
        for (int i = 0; i < temp.Length; i++)
        {
            if (max_volume < temp[i])
            {
                max_index = i;
                max_volume = temp[i];
            }
            total_volume += Mathf.Abs(temp[i]);
        }

        if (temp.Length > 0)
        {
            frequency = max_index * AudioSettings.outputSampleRate / 2 / temp.Length;
            volume = total_volume / temp.Length * gain;
        }
    }

    private void AizuchiAnimation(float speakingTime)
    {
        if (2.0f < speakingTime && speakingTime < 5.0f)
        {
            animator.SetInteger("aizuchi", 1);
        }

        else if (5.0f < speakingTime && speakingTime < 8.0f)
        {
            animator.SetInteger("aizuchi", 2);
        }
        else if (8.0f < speakingTime)
        {
            animator.SetInteger("aizuchi", 3);
        }
    }
}
```

スクリプトをアタッチ後に、AudioSourceに**MIC**をいれて、Animatorには**VRMのModel**を入れてください。

以下に、各機能についての説明です。

- gain
　

取得した音声のVolumeを計算後に何倍するかを示しています。

PCでのテスト時には2000倍、Oculus Quest 2 での実行時には8000倍となるようにしています。

- Waiting 

音声を実際に取っているかを判定するところです。

ある一定の音量以上で、第一周波数(f0)が100Hz以上、1400Hz以下の時にしゃべっていると認識しています。

このf0の基準は日本語の母音の周波数帯が100Hz以上、1400Hz以下だからです。

- AizuchiAnimation

実際に相槌や頷きのアニメーションを行う部分です。

|秒数|動作|
|:------:|:---------|
|2s ~ 5s | 「うん」と頷く|
|5s = 8s | 「へー、そうなんだ。」と相槌をうつ|
|8s ~ |「なるほどね」と相槌をうつ|


###キャラクターの声だけを出力しよう！

上記の設定で、ご自身の声が入力できるようになったと思います。

しかし、同時にキャラクターの声と混じって、自分の声が出力されてしまいます。

なので、AudioMixerを使って、キャラクターの声だけが出力されるようにします。

まずProjectから、右クリックして Create > AudioMixer を作ってください。

つぎに、Audio Mixerを開いて、Groupsの**+**ボタンを押し、2つのグループの*Mic*と*Voice*を追加してください。

そうしましたら、写真のようにAudioMixerを設定してください。

![スクリーンショット 2021-06-04 164417.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1353403/04ec6d4b-07a0-0b00-22ee-b7ec68cab2c6.png)

Micの-80dBにより、自分の声が出力されないようになります。

最後にモデルにアタッチされている Audio Source の Output に **Voice**を指定し、MICにアタッチされている Audio Source の　Output　に **Mic**を指定します。

これで、設定が完了しました。


###Animatorを準備しよう！

[Oculus Quest 2でいちゃいちゃしてみよう！Part4](https://qiita.com/KayaTakashiro/items/307c91c8a40df4eff115)でのやりかたと、同一のやり方でアニメーションを作成していきます。

まず、AnimatorのParametersから、Int型のパラメータを追加し、名前を**aizuchi**にします。

次に、AnimationClipを3つつくり、AnimatorにDrag&Dropします。

Drag&DropしたそれぞれのAnimationClipとWaiting(待機モーション)をそれぞれに双方向につなぎます。

Waiting -> AnimationClipの方向の矢印を右クリックして、InspectorからConditionsを追加します。

Conditionsの内容は **aizuchi** **Equals** **1~3**までの数字です。

つぎに、AninmationClipをクリックして、Add Behaviourからスクリプトを追加します。

前回使用した、ExitAnimation.csに以下の行を追加して、アタッチしてください。

```
animator.SetInteger("aizuchi", 0)
```

###Animation Clipを編集しよう！

[Oculus Quest 2でいちゃいちゃしてみよう！Part4](https://qiita.com/KayaTakashiro/items/307c91c8a40df4eff115)でのAnimationClipを作成したときとやり方は同じです。

TimeLineで音声付きのAnimationClipを編集してください。

###Animation Clipに音声をつけよう！

[Oculus Quest 2でいちゃいちゃしてみよう！Part4](https://qiita.com/KayaTakashiro/items/307c91c8a40df4eff115)で使用した、Voice_Controller.csに以下のスクリプトを追加します。

```
    [SerializeField] 
    public AudioClip[] aizuchiClips;

\\\
    void AizuchiPlayBack(int aizuchiIndex)
    {
        audioSource.PlayOneShot(aizuchiClips[aizuchiIndex-1], 1.0f);
    }
```

AnimationClipをダブルクリックして、AnimationWindowを開き、AddEventをクリックして、イベントを追加してください。

追加したEventについて、Functionを**AizuchiPlayBack**として、Intを**VoiceController中のIndex+1**の値に設定してください。

最後にアプリを起動させて、試してみてください。

お疲れ様でした。

[次回に続きます。](https://qiita.com/KayaTakashiro/items/33e2e420c3094603d171)

[今回のアプリのGitHub (git_temp2) ](https://github.com/blackpopo/MakingOutWith2DGirls/tree/master/git_temp2)