Source: dev/beepcore.js

  /**
   * @typedef {number} wavetypeNumber 0:"sine", 1:"square", 2:"sawtooth", 3:"triangle"
   * @example
   * 0:"sine", 1:"square", 2:"sawtooth", 3:"triangle"
   * @description
  * - 以下の数値で設定する<br>\
  * - 0: サイン波 <br>\
  * - 1: 矩形波   <br>\
  * - 2: ノコギリ波<br>\
  * - 3: 三角波   <br>\
  */
  /**
   * LFO setup Paramater
   * @typedef {object} lfoParam  
   * @property {number} Freq LFO周波数
   * @property {waveTypeString } wavetype LFOの波形タイプ
   * @property {number} depth LFO depth
   * @example
   * {Freq:0, wavetype:"none", depth:0};
   */
  /** 
   * waveTypeString
   * @typedef {string} waveTypeString "sine" or "square" or "sawtooth" or "triangle" or "none"
   * @example
   * "sine" or "square" or "sawtooth" or "triangle" or "none"
   * @description
   * - 以下の文字列を設定する<br>\
   * - "sine":サイン波<br>\
   * - "square":矩形波<br>\
   * - "sawtooth":ノコギリ波<br>\
   * - "triangle":三角波<br>\
   * - "none":なし(LFOの波形タイプを選択時のみ)<br>\
   */
  /**
   * numberVolume
   * @typedef {number} numberVolume (bitween 0.0~1.0)
   * @example
   *  (bitween 0.0~1.0)
   * @description
   * ボリュームパラメータ: 0.0から1.0の範囲内で指定すること
  */
/**
 * WebAudio Beep Function \
 * BEEPCORE SOUND SYSTEM
 * @class
 * @classdesc
 * WebAudio APIを利用したサウンドシステムです。<br>\
 * サイン波、矩形波、ノコギリ波、三角波などの波形を生成し、<br>\
 * プログラムで音色や音階を制御してビープ音を鳴らします
 */
class Beepcore {
  constructor() {

    const wave = ["sine", "square", "sawtooth", "triangle"];

    window.AudioContext = window.AudioContext || window.webkitAudioContext;
    //const ctx = new AudioContext();
    let masterVolume = 0.2;
    let oscwavetype = wave[0];
    let lfo = null;

    let noteList = [];

    /**
     * SoundNoteClass(AudioContextRapper)
     * @class Beepcore.noteClass
     * @classdesc
     * Beepcoreサウンドシステム内で個々の音源を管理します。<br>\
     * 発振器(oscillator)とゲインノード(gainNode)を制御し、<br>\
     * 音の生成、再生、停止、ボリュームや周波数の変更を行います。 
     */
    class noteClass {
      /**
       * 
       */
      constructor() {

        const ctx = new AudioContext();
        const osc = ctx.createOscillator();
        //osc = ctx.createOscillator();
        const gainNode = ctx.createGain();
        gainNode.gain.value = 0;
        osc.connect(gainNode).connect(ctx.destination);

        let masterVolume;

        let noteList;
        let starttime;

        this.living = false; //生成されて初期化が終わったらtrue/stop後は再利用できないのでfalse
        this.busy = false; //playで譜面があるうちはbusy/譜面終了したらfalse;    

        const noteTable = Table();

        /**
         * note initialize
         * @method
         * @param {number} [Freq=440] 周波数
         * @param {waveTypeString} [osc_wavetype="sine"] オシレーターの波形タイプ
         * @param {lfoParam} [lfop=null] LFO設定
         * @param {numberVolume} [mVol=0.2] マスターボリューム
         * @description
         * 音源を初期化します。<br>\
         * 周波数、オシレーターの波形タイプ、LFO(低周波発振器)の設定、<br>\
         * およびマスターボリュームをパラメータとして設定します。
         */
        this.init = function (Freq = 440, osc_wavetype = "sine", lfop = null, mVol = 0.2) {
          //  lfo param = {Freq:0, wavetype:"none", depth:0};
          masterVolume = mVol;

          osc.type = osc_wavetype;
          osc.frequency.value = Freq;

          if (lfop !== null) {
            // LFO
            const lfo = ctx.createOscillator();
            const depth = ctx.createGain();

            depth.gain.value = lfop.depth;

            lfo.type = lfop.wavetype;
            lfo.frequency.value = lfop.Freq;
            // lfo -> depth -> Osc.Freq
            lfo.connect(depth).connect(osc.frequency);
            lfo.start();
          }
          this.living = true;

          noteList = [];
        };

        /**
         * note on (voice play)
         * @method
         * @param {numberVolume} [volume=1.0] ボリューム
         * @param {number} [delay=0] 遅延時間(秒)
         * @description
         * 音源の再生(ボイスプレイ)を開始します。<br>\
         * 指定されたボリュームと遅延時間(秒)で音を鳴らし<br>\
         * ゲインノードを通じてマスターボリュームが適用されます。
         */
        this.on = function (volume = 1, delay = 0) {
          gainNode.gain.value = volume * masterVolume;
          osc.start(delay);
        };

        /**
         * change note volume
         * @method
         * @param {numberVolume} volume ボリューム 
         * @description
         * 音源のボリュームを変更します。<br>\
         * 新しいボリューム値とマスターボリュームを掛け合わせた値が、<br>\
         * ゲインノードのゲイン値として即座に適用されます。
         */
        this.changeVol = function (volume = 1) {
          gainNode.gain.value = volume * masterVolume;
        };

        /**
         * @method
         * @param {number} Freq 周波数
         * @description
         * 音源の周波数を変更します。<br>\
         * 新しい周波数値をオシレーターに直接設定することで、<br>\
         * リアルタイムに音の高さを調整します。
         */
        this.changeFreq = function (Freq) {
          osc.frequency.value = Freq;
        };

        /**
         * note stop play
         * @method
         * @param {number} dur 遅延時間(秒)
         * @description
         * 音源の再生を停止します。<br>\
         * 指定された遅延時間(秒)後にオシレーターが停止し、<br>\
         * オブジェクトは再利用できない状態(living: false)になります。
         */
        this.off = function (dur) {
          osc?.stop(dur);
          this.living = false;
        };

        /**
         * @method
         * @description
         * 音源のゲインと周波数をゼロに設定し、一時的に音を止めます。<br>\
         * 完全に停止させる`off`とは異なり、 <br>\
         * 音源オブジェクト自体は「生きている」状態を保ちます。
         */
        this.suspend = function () {
          gainNode.gain.value = 0;
          osc.frequency.value = 0;
        };

        /**
         * PlayNoteParamater(UtiltyGenerate)
         * @typedef {object} noteParam NoteParamater
         * @property {string} noteText NoteName A0-A8
         * @property {number} Freq Frequency
         * @property {number} Vol NoteVolume
         * @property {number} time NoteLengthTime
         * @property {boolean} use use check flag
         */
        /**
         * noteScorePlay
         * @method
         * @param {noteParam[]} setList makeScore method create list array
         * @param {number} now play start system time (game.time()) 
         * @description
         * 音符のシーケンス(スコア)を再生します。<br>\
         * `makeScore`メソッドで作成された音符パラメータのリストを受け取り、<br>\
         * 指定された開始システム時刻から再生を開始します。
         */
        this.play = function (setList, now) {

          noteList = setList;
          //[{note:"A4", Freq:0, Vol:0, time:0, use:false} ..]
          for (let i in noteList) {
            if (Boolean(noteList[i].name)) {
              noteList[i].Freq = nameToFreq(noteList[i].name);
            }
          }
          starttime = now;
          this.busy = true;

          function nameToFreq(name) {

            let Freq = 0;
            for (let i in noteTable) {
              if (name == noteTable[i].name) {
                Freq = noteTable[i].Freq;
                break;
              }
            }
            return Freq;
          }
        };

        /**
         * system use internal playcontrol function 
         * @method
         * @param {number} now calltime
         * @description
         * システム内部で使用される再生制御関数です。<br>\
         * 現在時刻に基づいて`noteList`内の音符の状態を更新し、 <br>\
         * 適切なタイミングで音量や周波数を変更します。 
         */
        this.step = function (now) {
          let c = 0; // not use note count
          let st = 0; // playstart time on note
          let et = 0; // play end time on note
          for (let i in noteList) {
            let n = noteList[i];
            et += n.time;
            let pt = now - starttime;
            if (!n.use) {
              if ((st < pt) && (et > pt)) {
                this.changeVol(n.Vol);
                this.changeFreq(n.Freq);
                n.use = true;
              }
              c++;
            }
            st = et;
          }
          if (c == 0) {
            this.suspend();
            noteList = [];
            this.busy = false;
            //演奏終了
          }
        };

        /**
         * @returns noteFreqMappingTable
         * @description
         * A0からG#8までの音名と対応する周波数のマッピングテーブルを生成
         */
        function Table() {

          const notename = ["A", "A#", "B", "C", "C#", "D", "D#", "E", "F", "F#", "G", "G#"];

          let tb = [];
          for (let i = 0; i < 9; i++) { //Octarb
            const startFreq = 27.5 * Math.pow(2, i);
            for (let j = 0; j < 12; j++) {
              const note = {
                name: notename[j] + ((j < 3) ? i : i + 1),
                Freq: startFreq * (Math.pow(2, j / 12))
              };
              tb.push(note);
            }
          }
          return tb;
        }
      }
    }

    /**
     * NOTE CREATE
     * @method
     * @param {number} Freq 周波数
     * @returns {noteClass} 音源オブジェクト
     * @description
     * 新しい`noteClass`オブジェクトを生成して初期化します。<br>\
     * 指定された周波数、グローバルな波形タイプ、LFO設定、<br>\
     * およびマスターボリュームで音源を作成し、リストに追加します
     */
    this.createNote = function (Freq) {

      let note = new noteClass();
      note.init(Freq, oscwavetype, lfo, masterVolume);
      noteList.push(note);
      //console.log(noteList.length);
      return note;
    };
    /**
     * SETUP BEEPCORE SOUND SYSTEM
     * use OSC Wavetype select
     * @method
     * @param {wavetypeNumber} wavetype 波形タイプ[0~3]
     * @description
     * 使用するオシレーターの波形タイプを設定します。 <br>\
     * 正弦波、矩形波、ノコギリ波、三角波の中から選択し、 <br>\
     * 以降作成される音符のデフォルト波形として適用されます。
     */
    this.oscSetup = function (wavetype) {
      oscwavetype = wave[wavetype];
    };
    /**
     * SETUP BEEPCORE SOUND SYSTEM
     * LFO setup
     * @method
     * @param {number} Freq LFO周波数
     * @param {wavetypeNumber} wavetype 波形タイプ[0~3]
     * @param {number} depth LFOデプス
     * @description
     * LFO(低周波発振器)を設定します <br>\
     * LFOの周波数、波形タイプ、デプス(深さ)を指定し、<br>\
     * 音に揺らぎやビブラート効果を加えることができます。
     */
    this.lfoSetup = function (Freq, wavetype, depth) {
      lfo = { Freq: Freq, wavetype: wave[wavetype], depth: depth };
    };
    /**
     * SETUP BEEPCORE SOUND SYSTEM
     * LFO off
     * @method
     * @description
     * 設定されているLFOを無効にします。<br>\
     * これにより、以降作成される音符にLFO効果は適用されなくなり、<br>\
     * 既存のLFO効果も停止します。
     */
    this.lfoReset = function () { lfo = null; };
    /**
     * SETUP BEEPCORE SOUND SYSTEM
     * MasterVolume setup
     * @method
     * @param {numberVolume} vol マスターボリューム
     * @description
     * BEEPCOREのマスターボリュームを設定します。<br>\
     * 0.0(無音)から1.0(最大)の範囲で音量を調整し <br>\
     * システム全体にわたる音量バランスを制御します。
     */
    this.masterVolume = function (vol = 0.2) {
      masterVolume = vol;
    };
    //Taskstep 
    /**
     * system-use
     * @method
     * @param {nunmber} now systemtime 
     * @description
     * BEEPCOREの状態を更新します。<br>\
     * 現在アクティブな全ての音源(`noteClass`インスタンス)の<br>\
     * step`メソッドを呼び出し、再生状態を管理します。
     */
    this.step = function (now) {
      for (let i in noteList) {
        if (noteList[i].living) {
          noteList[i].step(now);
        } else {
          noteList.splice(i, 1);
        }
      }
    };

    //Utility
    /**
     * - play command paramater make utility
     * - 音名の配列からplayコマンドで再生可能なパラメータ配列に変換
     * - noteNameList -> noteParam Convert
     * @method
     * @param {string[]} namelist notename array
     * @param {number} time note interval(ms) 
     * @param {numberVolume} vol note volume
     * @returns {noteParam[]} playコマンドで再生可能なパラメータ配列
     * @example namelist: ["G5","C6","E6","C6","D6","G6"];
     * 4/4拍子 テンポ120 60f 3600f/m
     * 4分音符    30f 500ms
     * 8分音符    15f 250ms
     * 16分音符  7.5f 125ms
     * 32分音符 3.75f 62.5ms
     * @description
     * 再生コマンド用のパラメータリストを作成するユーティリティです。<br>\
     * 音名の配列を受け取り、各音符の周波数、ボリューム、再生時間を設定した、<br>\
     * `noteClass.play`メソッドで利用可能な形式に変換します。
     */  
    this.makeScore = function (namelist, time = 100, vol = 1) {
      //namelist  exmpl. ["G5","C6","E6","C6","D6","G6"];
      let sc = [];
      for (let i in namelist) {
        let n = { name: namelist[i], Freq: 0, Vol: vol, time: time, use: false };
        sc.push(n);
      }
      sc.push({ Freq: 0, Vol: 0, time: 100, use: false });

      return sc;
    };
  }
}