音階波形の生成

環境

Unity2021.3.4f1

概要

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

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

using UnityEngine;

[RequireComponent(typeof(AudioSource))]
public class ProcedualAudio : MonoBehaviour
{
    private float _outputSampleRate;
    private float _bpm = 120;   //[BeatsPerMinute] BPM60=1分間に四分音符を60回鳴らす速度 BPM=120の場合は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;  //1小節の中に含まれる拍数
        public NoteParam(Note note_, float beat_)
        {
            note = note_;
            tone = _tones[(int)note];
            beat = beat_;
        }

        public float GetTime(float bpm)
        {
            //BPM=60の場合、四分音符=1秒
            return (4.0f / beat) / (bpm / 60.0f);
        }
    }
    private NoteParam[] _notes = new NoteParam[]
    {
        new NoteParam(NoteParam.Note.C4, 4.0f),
        new NoteParam(NoteParam.Note.D4, 4.0f),
        new NoteParam(NoteParam.Note.E4, 4.0f),
        new NoteParam(NoteParam.Note.F4, 4.0f),
        new NoteParam(NoteParam.Note.E4, 4.0f),
        new NoteParam(NoteParam.Note.D4, 4.0f),
        new NoteParam(NoteParam.Note.C4, 2.0f),
        new NoteParam(NoteParam.Note.E4, 4.0f),
        new NoteParam(NoteParam.Note.F4, 4.0f),
        new NoteParam(NoteParam.Note.G4, 4.0f),
        new NoteParam(NoteParam.Note.A4, 4.0f),
        new NoteParam(NoteParam.Note.G4, 4.0f),
        new NoteParam(NoteParam.Note.F4, 4.0f),
        new NoteParam(NoteParam.Note.E4, 2.0f),
        new NoteParam(NoteParam.Note.C4, 2.0f),
        new NoteParam(NoteParam.Note.C4, 2.0f),
        new NoteParam(NoteParam.Note.C4, 2.0f),
        new NoteParam(NoteParam.Note.C4, 2.0f),
        new NoteParam(NoteParam.Note.C4, 8.0f),
        new NoteParam(NoteParam.Note.C4, 8.0f),
        new NoteParam(NoteParam.Note.D4, 8.0f),
        new NoteParam(NoteParam.Note.D4, 8.0f),
        new NoteParam(NoteParam.Note.E4, 8.0f),
        new NoteParam(NoteParam.Note.E4, 8.0f),
        new NoteParam(NoteParam.Note.F4, 8.0f),
        new NoteParam(NoteParam.Note.F4, 8.0f),
        new NoteParam(NoteParam.Note.E4, 4.0f),
        new NoteParam(NoteParam.Note.D4, 4.0f),
        new NoteParam(NoteParam.Note.C4, 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 noteTime = note.GetTime(_bpm);
        var dspTime = AudioSettings.dspTime;
        var elapsedDspTime = dspTime - _prevDspTime;
        if(elapsedDspTime > noteTime)
        {
            _prevDspTime = dspTime;
            _noteIndex ++;
            _noteIndex %= _notes.Length;
            note = _notes[_noteIndex];
            noteTime = note.GetTime(_bpm);
        }
        Debug.Log($"noteIndex:{_noteIndex} dspTime:{dspTime} noteTime:{noteTime} 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 / noteTime);
        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