凹みTips

C++、JavaScript、Unity、ガジェット等の Tips について雑多に書いています。

uLipSync で解析結果を使ったカスタム挙動を作るためのチュートリアル

はじめに

uLipSync では AudioClip やマイクから得た音声を解析するモジュールと、その解析結果を受け取りリップシンクを動作させるためのコンポーネントが分かれています。

github.com

この解析結果を受け取る部分を自作することで色々と遊ぶことができるので、本記事ではそのやり方をご紹介します。

導入

いくつか方法はありますが、Package Manager から次の URL を Add package from git URL... するのが簡単だと思います。

  • https://github.com/hecomi/uLipSync.git#upm

直接 .unitypackage を導入する場合は Package Manager から Unity.MathematicsUnity.Burst の導入を忘れずにおこなって下さい。その他基本的な概念や機能説明は、必要に応じて以下の記事をご参照下さい。

tips.hecomi.com

解析結果を受け取る手順

uLipSync コンポーネントを追加

まず適当な GameObject を作成し、uLipSync コンポーネントをアタッチします。

uLipSync コンポーネントProfile に何かしらの Profile オブジェクトを指定します。Profile の作成の仕方は解説記事に載せていますが、ひとまずはパッケージに含まれているサンプル(uLipSync-Profile-Sample など)を指定すると動きます。

解析結果を受け取るコンポーネントの作成

次のようなコンポーネントを作ります。

using UnityEngine;

public class CustomLipSyncEventHandler : MonoBehaviour
{
    public void OnLipSyncUpdate(uLipSync.LipSyncInfo info)
    {
        if (info.volume < Mathf.Epsilon) return;

        Debug.LogFormat($"PHENOME: {info.phoneme}, VOL: {info.volume} ");
    }
}

そして、これを任意の GameObject にアタッチします。次にこのイベントハンドラuLipSync に登録します。On Lip Sync Update (LipSyncInfo) で + ボタンを押し、イベントを追加、ここに先程の GameObject を指定してプルダウンリストから該当のイベントを選択します。

これで登録完了です。

AudioClip またはマイクコンポーネントの追加

マイクで喋る場合は、uLipSyncMicrophone コンポーネントuLipSync と同じ GameObject にアタッチしておきます。

実行

実行してマイクに喋ると次のように認識された母音とボリュームが表示されます(AudioClip の場合はその音に応じたものが表示されます)。

渡ってくる情報

イベントに渡ってくる LipSyncInfo の中身は次のようになっています。

public struct LipSyncInfo
{
    // 認識された音素
    public string phoneme;
    // ボリューム(0 ~ 1)
    public float volume;
    // ボリュームの生値
    public float rawVolume;
    // 登録された音素と含まれる割合のテーブル(A が 80%、I が 20% など)
    public Dictionary<string, float> phonemeRatios;
}

rawVolumeRMS(Root Mean Square)ボリュームで、各サンプルの二乗和の平方根なので、比較的小さな値が出てきます。そのため、デシベル空間で適当な扱いやすい値に変更したものを volume として返しています。より拘りたい方は、rawVolume を使ってみて下さい。なお uLipSyncBlendShape などのコンポーネントでは、そのコンポーネント内でこのデシベル空間での最小 / 最大値を調整できるようにし、rawVolume を使って計算しています。

試しになにか作ってみる

それでは試しに phonemeRatios を活用して、認識した音を使ってオブジェクトのカラーを変えるコンポーネントを作ってみます。

using UnityEngine;
using System.Collections.Generic;

public class CustomLipSyncEventHandler : MonoBehaviour
{
    [System.Serializable]
    public struct PhonemeColor
    {
        public string phoneme;
        public Color color;
    }

    [SerializeField] 
    List<PhonemeColor> phonemeColors;

    [SerializeField, Range(0.01f, 1f)] 
    float minVolume = 0.2f;

    [SerializeField, Range(0f, 1f)] 
    float smooth = 0.7f;

    [SerializeField] 
    Renderer targetRenderer = null;

    Material _mat;
    Color _color;

    void Start()
    {
        if (!targetRenderer) return;

        _mat = targetRenderer.material;
    }

    void LateUpdate()
    {
        _mat.color += (_color - _mat.color) * (1f - smooth);
    }

    public void OnLipSyncUpdate(uLipSync.LipSyncInfo info)
    {
        if (!_mat) return;
        
        var color = Color.clear;
        foreach (var pc in phonemeColors)
        {
            float ratio = info.phonemeRatios.GetValueOrDefault(pc.phoneme, 0f);
            color += ratio * pc.color;
        }

        var volume = Mathf.Min(info.volume / minVolume, 1f);

        _color = color * volume + Color.white * (1f - volume);
        
        Debug.Log($"<color=\"#{ColorUtility.ToHtmlStringRGB(_color)}\">■</color> ({volume})");
    }
}

予め登録した音素が、認識した音素にどれくらい入っているかを見て色をブレンドし、指定した Renderer コンポーネントのマテリアルのカラーを上書きしています。このコンポーネントを適当なオブジェクトにアタッチしたあと、先程と同じように uLipSync コンポーネントに登録し、次のようにセットアップします。

実行すると次のようになります。

喋った音に応じてオブジェクトの色が変わりました。あとは色々とアイディア次第で面白いものが作れるかもしれません。

おわりに

正確に音を取らないと行けないようなユースケースでは、現状の解析法では外乱に弱いのでいまいちですが、間違っても大丈夫な賑やかしに使いたいようなケースでは面白い使い途が色々あるかもしれません。

余談

先日、VTube Studio さんが uLipSync を音声を使ったリップシンク向けの機能として採用して下さいました。

v3 から追加した UI のための API も使ってくださっていて、キャリブレーションの機構も搭載されています!カメラによるトラッキングではない場合に威力を発揮すると思います。VTube Studio をお使いの方はぜひお試し下さい。