0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

Oculus Quest 2 で話してみよう!Part1

Posted at

#目次

##やること

今回は前回作成した、Oculus Quest 2のアプリに追加機能をつけていきたいと思います。

実際に付ける内容は、

  • 一定時間話しかけた後で、頷く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つのグループのMicVoiceを追加してください。

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

スクリーンショット 2021-06-04 164417.png

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

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

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

###Animatorを準備しよう!

Oculus Quest 2でいちゃいちゃしてみよう!Part4でのやりかたと、同一のやり方でアニメーションを作成していきます。

まず、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でのAnimationClipを作成したときとやり方は同じです。

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

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

Oculus Quest 2でいちゃいちゃしてみよう!Part4で使用した、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の値に設定してください。

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

お疲れ様でした。

次回に続きます。

今回のアプリのGitHub (git_temp2)

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?