0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

WAVファイルにループポイントを埋め込む

Last updated at Posted at 2025-03-17

WAVファイルにループポイントを埋め込むことで、Unity上でイントロ付きBGMを実装してみました。

wavファイルにループポイントを埋め込む(抜粋)
public static void WriteSmplChunk(FileStream outputFile, int loopStart, int loopEnd)
    {
        using (BinaryWriter writer = new BinaryWriter(outputFile))
        {
            // "smpl" chunk ID
            writer.Write(new char[] { 's', 'm', 'p', 'l' });

            // Chunk size (36 bytes for header + 24 bytes per loop)
            writer.Write(60); // Fixed size for one loop

            // Manufacturer, Product, Sample Period
            writer.Write(0); // Manufacturer
            writer.Write(0); // Product
            writer.Write(22675); // 1000000000ns/44.1kHz

            // MIDI Unity Note, Pitch Fraction, SMPTE format/offset
            writer.Write(60); // MIDI Unity Note (Middle C)
            writer.Write(0);  // Pitch Fraction
            writer.Write(0);  // SMPTE Format
            writer.Write(0);  // SMPTE Offset

            // Number of Loops, Sampler Data
            writer.Write(1); // One loop
            writer.Write(0); // Sampler Data

            // Loop information
            writer.Write(0); // Cue Point ID
            writer.Write(0); // Loop type (Forward)
            writer.Write(loopStart); // Loop start (in samples)
            writer.Write(loopEnd);   // Loop end (in samples)
            writer.Write(0); // Fraction
            writer.Write(0xFFFFFFFF); // Infinite loop
        }
    }
}

出力されたWAVをUnityに読み込み、Loop ON かつ PlayOnAwaake ON
にするとちゃんとループはしてくれるのですが、
プレビューすると音の最後にノイズがのってしまい、ループしませんでした。
image.png

またスクリプトからPlay()した時もループポイントを無視してしまう(音の最後から最初にループ)ようなので、audioSource.timeSamplesを参照するなどして素直にイントロループを行うスクリプトを用意するのがよさそうです。

イントロ付きループスクリプト
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Serialization;

namespace ELIX
{
    [RequireComponent(typeof(AudioSource))]
    public class IntroLoopPlayer : MonoBehaviour
    {
        [SerializeField, Tooltip("the end of Intro point"), FormerlySerializedAs("m_LoopSttPoint")] int m_loopSttPoint=-1;
        [SerializeField, Tooltip("the start of Outro point"), FormerlySerializedAs("m_LoopEndPoint")] int m_loopEndPoint=-1;
        [SerializeField, Tooltip("number of loops")] int m_loopNum = 2;
        [SerializeField] UnityEngine.UI.Slider m_slider;
        AudioSource m_audioSrc;
        AudioClip m_ac;
        int m_loopCnt = 0;
        bool m_isLoop = false;
        // Start is called before the first frame update
        void Start()
        {
            m_audioSrc = gameObject.GetComponent<AudioSource>();
            m_isLoop = m_audioSrc.loop;
            m_audioSrc.loop = false;
            m_ac = m_audioSrc.clip;

            if (m_ac == null)
                return;

            if (m_loopSttPoint < 0) { m_loopSttPoint = 0; }
            if (m_loopEndPoint < 0) { m_loopEndPoint = m_ac.samples; }
        }

        // Update is called once per frame
        void Update()
        {
            if (m_ac == null)
                return;

            if (m_loopCnt < m_loopNum)
            {
                //Debug.Log("timeSamples:" + m_audioSrc.timeSamples);
                if (m_audioSrc.timeSamples >= m_loopEndPoint)
                {
                    if(m_loopEndPoint >= m_audioSrc.timeSamples)
                    {
                        m_audioSrc.Play();
                    }
                    m_audioSrc.timeSamples = m_loopSttPoint;
                    m_loopCnt++;
                }
            }
            else if (m_isLoop)
            {
                if (m_audioSrc.timeSamples >= m_ac.samples)
                {
                    m_loopCnt = 0;
                    m_audioSrc.Play();
                }
            }

            if(m_slider != null)
            {
                m_slider.SetValueWithoutNotify((float)m_audioSrc.timeSamples / (float)m_ac.samples);
            }
        }

        private void OnGUI()
        {
            GUI.Label(new Rect(10, 10, 1000, 30), $"Loops {m_loopNum} times between {m_loopSttPoint} and {m_loopEndPoint}. {m_audioSrc.timeSamples}/{m_ac.samples}");
        }


        //private void OnAudioFilterRead(float[] data, int channels)  { }

        public void PlayDelayed(float _delaySec=0f)
        {
            m_audioSrc.PlayDelayed(_delaySec);
        }
    }

}

ちなみに、Unity上で直接AudioClipを加工し、部分切り出しやループ音の作成等が可能なツールをアセットストアで販売中です(ちゃっかり宣伝)。

0
0
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
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?