音階波形の生成

環境

Unity2021.3.4f1

概要

プロシージャルに音階の波形を生成して、かえるのうたを流してみました。

そのままだとノイズがひどいので、AudioMixerを作成してLowpassフィルタを追加して値調整するとノイズが軽減されます。

using UnityEngine;

[RequireComponent(typeof(AudioSource))]
public class ProcedualAudio : MonoBehaviour
{
    private float _outputSampleRate;
    private float _bpm = 120;   //BPM60=1分間に四分音符を60回鳴らす速度

    private class NoteParam
    {
        public enum Note
        {
            C4,         //ド
            D4,         //レ
            E4,         //ミ
            F4,         //ファ
            G4,         //ソ
            A4,         //ラ
            B4,         //シ
        }
        private static float[] _tones = new float[]
        {
            261.626f,
            293.665f,
            329.628f,
            349.228f,
            391.995f,
            440.000f,
            493.883f,
        };
        public Note note;
        public float tone;
        public float beat;
        public NoteParam(Note note_, float beat_)
        {
            note = note_;
            tone = _tones[(int)note];
            beat = beat_;
        }

        public float GetTime(float bpm)
        {
            return (beat * 4.0f) / (bpm / 60.0f);
        }
    }
    private NoteParam[] _notes = new NoteParam[]
    {
        new NoteParam(NoteParam.Note.C4, 1.0f / 4.0f),
        new NoteParam(NoteParam.Note.D4, 1.0f / 4.0f),
        new NoteParam(NoteParam.Note.E4, 1.0f / 4.0f),
        new NoteParam(NoteParam.Note.F4, 1.0f / 4.0f),
        new NoteParam(NoteParam.Note.E4, 1.0f / 4.0f),
        new NoteParam(NoteParam.Note.D4, 1.0f / 4.0f),
        new NoteParam(NoteParam.Note.C4, 1.0f / 2.0f),
        new NoteParam(NoteParam.Note.E4, 1.0f / 4.0f),
        new NoteParam(NoteParam.Note.F4, 1.0f / 4.0f),
        new NoteParam(NoteParam.Note.G4, 1.0f / 4.0f),
        new NoteParam(NoteParam.Note.A4, 1.0f / 4.0f),
        new NoteParam(NoteParam.Note.G4, 1.0f / 4.0f),
        new NoteParam(NoteParam.Note.F4, 1.0f / 4.0f),
        new NoteParam(NoteParam.Note.E4, 1.0f / 2.0f),
        new NoteParam(NoteParam.Note.C4, 1.0f / 2.0f),
        new NoteParam(NoteParam.Note.C4, 1.0f / 2.0f),
        new NoteParam(NoteParam.Note.C4, 1.0f / 2.0f),
        new NoteParam(NoteParam.Note.C4, 1.0f / 2.0f),
        new NoteParam(NoteParam.Note.C4, 1.0f / 8.0f),
        new NoteParam(NoteParam.Note.C4, 1.0f / 8.0f),
        new NoteParam(NoteParam.Note.D4, 1.0f / 8.0f),
        new NoteParam(NoteParam.Note.D4, 1.0f / 8.0f),
        new NoteParam(NoteParam.Note.E4, 1.0f / 8.0f),
        new NoteParam(NoteParam.Note.E4, 1.0f / 8.0f),
        new NoteParam(NoteParam.Note.F4, 1.0f / 8.0f),
        new NoteParam(NoteParam.Note.F4, 1.0f / 8.0f),
        new NoteParam(NoteParam.Note.E4, 1.0f / 4.0f),
        new NoteParam(NoteParam.Note.D4, 1.0f / 4.0f),
        new NoteParam(NoteParam.Note.C4, 1.0f / 2.0f),
    };
    private int _noteIndex = 0;
    private double _prevDspTime;

    private void Start()
    {
        _outputSampleRate = AudioSettings.outputSampleRate;
        _noteIndex = 0;
        _prevDspTime = AudioSettings.dspTime;
    }

    private void OnAudioFilterRead(float[] data, int channels)
    {
        var note = _notes[_noteIndex];
        var sec = note.GetTime(_bpm);
        var dspTime = AudioSettings.dspTime;
        var elapsedDspTime = dspTime - _prevDspTime;
        if(elapsedDspTime > sec)
        {
            _prevDspTime = dspTime;
            _noteIndex ++;
            _noteIndex %= _notes.Length;
            note = _notes[_noteIndex];
            sec = note.GetTime(_bpm);
        }
        //Debug.Log($"noteIndex:{_noteIndex} dspTime:{dspTime} sec:{sec} elapsedDspTime:{elapsedDspTime}");
        var begin = (float)(AudioSettings.dspTime % (1.0 / (double)note.tone));
        int currentSampleIndex = 0;
        var volume = Mathf.Lerp(1.0f, 0.5f,  (float)elapsedDspTime / sec);
        for (int i = 0; i< data.Length; i++)
        {
            float time = begin + (float)currentSampleIndex / _outputSampleRate;
            data[i] = volume * Mathf.Sin(2.0f * Mathf.PI * time * note.tone);
            currentSampleIndex++;
            if (channels == 2)
            {
                data[i + 1] = data[i];
                i++;
            }
        }
    }
}

感想

これをもっとちゃんとやったものがmidi再生ってことになるのでしょうか。

参考

音階周波数

Pythonで演奏してみた【Python】 - Fabeee Blog