Unity（WebGL）で、BGMのテンポと音程を違和感なく上げていく

こんにちは、Yoshi3110（フィズ）です。本記事は[NCCアドベントカレンダー](https://qiita.com/advent-calendar/2021/ncc)１２日目の記事です。
#はじめに
先日私がUnity1Week向けに作成したゲーム「リーファーと大きな葉っぱ」では、ゲームを盛り上げるためにBGMを少しずつスピードアップする演出を行っています。

<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">Pyoroライクシューティングゲーム「リーファーと大きな葉っぱ」を公開しました！<br>キーボードとマウスを使った操作で敵を一掃しよう！！<a href="https://twitter.com/hashtag/unity1week?src=hash&amp;ref_src=twsrc%5Etfw">#unity1week</a><a href="https://twitter.com/hashtag/%E3%83%AA%E3%83%BC%E3%83%95%E3%82%A1%E3%83%BC%E3%81%A8%E3%81%8A%E3%81%8A%E3%81%8D%E3%81%AA%E8%91%89%E3%81%A3%E3%81%B1?src=hash&amp;ref_src=twsrc%5Etfw">#リーファーとおおきな葉っぱ</a> <a href="https://twitter.com/hashtag/ReafurAndTheMegaphyll?src=hash&amp;ref_src=twsrc%5Etfw">#ReafurAndTheMegaphyll</a><br>（リンクはツリーにあります） <a href="https://t.co/gmd5YXNLzJ">pic.twitter.com/gmd5YXNLzJ</a></p>&mdash; フィズ🐬(Yoshi3110) (@FizDv) <a href="https://twitter.com/FizDv/status/1437712579715997699?ref_src=twsrc%5Etfw">September 14, 2021</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

元々はピッチを変えずにテンポを速くしたかったのですが、WebGLの制約上ツイートのような表現に落ち着きました。

#手法
　まず、Unityでテンポを変える方法として[AudioEffectを使用した方法](https://qiita.com/hotaro/items/c3c93866a6509e65e79f)があります。この方法ではピッチを変更せずに滑らかに曲のスピードだけを変えたり、逆に曲のスピードを変えずにピッチだけを変えることが可能です。
　しかし、WebGLでは音声周りの制約が非常に多く、AudioEffectの機能はほとんど使うことができません。実質いじれるのは再生スピードのみなので、その制約の中で以下の２点を満たすプログラムを作成してみました。(コードの下にデモあり)

①ピッチが半音ずつ変わるようにする
　（半音よりも狭い間隔でピッチがズレると音が気持ち悪くなってしまうため）
②転調（ピッチ＋テンポの変更）は小節やメロディの区切りで行う
　（キリの悪い、自由な位置で転調するとかなり強引な印象になってしまうため）

```C#:AudioSpeed_Sample.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

public class AudioSpeed_Sample : MonoBehaviour
{
    [SerializeField] AudioClip clip_BGM;
    [SerializeField] float loopStart_BGM;//BGMのループが始まる秒数
    [SerializeField] float loopEnd_BGM;//BGMのループが終わる秒数
    float[] pitches = {0.7937f, 0.8409f, 0.8909f, 0.94387f, 1f,
    1.05946f, 1.12246f, 1.18921f, 1.25992f, 1.33484f, 1.41421f, 1.49831f, 1.5874f, 1.68179f, 1.7818f, 1.88775f, 2f };
    //ピッチが-4から+12までの倍率を格納したテーブル（Indexが4の時に等速）
    //このテーブルは2の（N/12）乗根を計算することで求めることができます。

    int resentBar = 0;
    int nowPitch = 4;
    int aimPitch = 4;
    AudioSource audioSource;
    ////////////////////////////////////////////////////デモ用
    [SerializeField] Text demoText;
    ////////////////////////////////////////////////////
    void Start()
    {
        audioSource = this.gameObject.AddComponent<AudioSource>();
        audioSource.clip = clip_BGM;
        audioSource.loop = true;
        audioSource.Play();
    }

    // Update is called once per frame
    void Update()
    {
        SpeedChange();
        LoopCheck();
        DemoFunc();
    }

    void SpeedChange()
    {
        //BGMが転調可能ポイントに差し掛かった時に、目標ピッチとのずれがあれば転調する
        if (CheckResentBar())
        {
            if (nowPitch != aimPitch)
            {
                nowPitch = aimPitch;
            }
        }
        float _pitch = pitches[nowPitch];
        audioSource.pitch = _pitch;
    }

    void LoopCheck()
    {
        //BGMがループポイントに来た時にループ処理を行う
        float _t = audioSource.time;
        if (_t > loopEnd_BGM)
        {
            audioSource.time = audioSource.time - (loopEnd_BGM - loopStart_BGM);
        }
    }

    bool CheckResentBar(float _t = 10f)
    {
        //BGMが一回ループする間に何回転調可能時間を付けるか？
        //（例）20小節の曲で、2小節に一回転調できるように設定する場合　_t = 20 / 2
        float _f = (loopEnd_BGM - loopStart_BGM) / _t;
        int _i = (int)((audioSource.time - loopStart_BGM) / _f);
        if (resentBar != _i)
        {
            resentBar = _i;
            return true;
        }
        else
        {
            return false;
        }
    }

    void DemoFunc()
    {
        if (nowPitch != aimPitch)
        {
            demoText.text = "転調待機中…\n" + (nowPitch - 4) + " → " + (aimPitch - 4);
        }
        else
        {
            demoText.text = "再生中\nPitch:" + (nowPitch - 4);
        }
    }
    public void PitchChange(int i)
    {
        aimPitch = aimPitch + i;
        aimPitch = Mathf.Clamp(aimPitch, 0, 16);
    }
}

```

<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">WebGLでいいかんじにピッチを変えるデモ <a href="https://t.co/iNJACLoGlH">pic.twitter.com/iNJACLoGlH</a></p>&mdash; フィズ🐬(Yoshi3110) (@FizDv) <a href="https://twitter.com/FizDv/status/1469538226843316229?ref_src=twsrc%5Etfw">December 11, 2021</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>


#おわりに
WebGLでだんだん早くなるBGM、みんなもやってみよう！！