[Unity] ループしていない環境音をクロスフェードしてループにする


...というアセットの販売を開始しました。
AmbientLooper

それに含まれるスクリプトの一部を改変して公開します。
ループ音にしたいAudioSourceがあるオブジェクトに下記スクリプトをアタッチするだけでノンループの環境音がループ音になります。
動画

長い環境音などは比較的時間もかかるので、その場合はAmbientLooperでwav化して使用するのがオススメです。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

[RequireComponent(typeof(AudioSource))]
public class CrossFadeLoop : MonoBehaviour
{
    [Tooltip("cross fade dulation[sec]"), Range(0.1f, 5f)] public float m_crossFadeTime = 1f;

    void Start()
    {
        AudioSource audioSrc = gameObject.GetComponent<AudioSource>();
        AudioClip ac = audioSrc.clip;
        float[] data = new float[ac.samples * ac.channels];
        ac.GetData(data, 0);
        int span = Mathf.FloorToInt(m_crossFadeTime * (float)(ac.frequency * ac.channels));
        span -= (span % ac.channels);
        float[] newData = CrossFade(data, span);
        AudioClip newAc = AudioClip.Create("_loop_" + ac.name, newData.Length / ac.channels, ac.channels, ac.frequency, false);
        newAc.SetData(newData, 0);
        audioSrc.clip = newAc;
        if (audioSrc.playOnAwake)
            audioSrc.Play();
    }

    static public float[] CrossFade(float[] data, int span)
    {
        if (data.Length < span * 2)
            return data;

        float[] retData = new float[data.Length - span];
        for (int i = 0; i < span; ++i)
        {
            float rate = (float)(i + 1) / (float)(span + 1);
            retData[i] = data[i] * rate + data[data.Length - span + i] * (1f - rate);
        }
        for (int i = span; i < data.Length - span; ++i)
            retData[i] = data[i];

        return retData;
    }
}

具体的な操作としては、自分自身をクロスフェードして頭とおしりをつないでいます。

まずは

        float[] data = new float[ac.samples * ac.channels];
        ac.GetData(data, 0);

でclipのデータを取得します。クリップのサイズはサンプル数xチャンネル数で決まります。

次に

        int span = Mathf.FloorToInt(m_crossFadeTime * (float)(ac.frequency * ac.channels));
        span -= (span % ac.channels);

でクロスフェードさせる時間分のデータ長を設定します。
計算で求めたspanだけでは偶数/奇数になる可能性があり、
チャンネル数が2でspanが奇数だと左右逆のチャンネルのデータをフェードして繋いでしまうことになり、音の定位変化やプチノイズの原因になるため、spanをチャンネル数で割り切れる数に調整します。

あとは

        float[] newData = CrossFade(data, span);

でデータをクロスフェードさせます。出力されるデータサイズはクロスフェードの分だけ小さくなります。

        float[] retData = new float[data.Length - span];

新しく作ったAudioClipにデータを読み込ませ、AudioSourceにセットします。

        newAc.SetData(newData, 0);
        audioSrc.clip = newAc;

AudioSourceに新しいclipを読み込ませると鳴っていた音が止まってしまうので、
音が鳴っていた(PlayOnAwakeがオンになっていた)場合はPlayします

        if (audioSrc.playOnAwake)
            audioSrc.Play();