Home記事一覧フォーラム

無限に即興演奏し続けるFMシンセ(JavaScript版)

【更新履歴】
2019/08/30 「Euphoria」を追加
2019/05/30 「Pentagon」を追加
2018/05/25 Chromeの仕様変更に合わせて修正
2017/07/31 映像と音声の同期を向上
2017/07/15 ビジュアライザーを追加
2017/05/23 インスタンス解放時の不具合を修正
2017/05/22 曲を追加
2017/04/11 リバーブの仕様を変更
2017/04/03 新規公開

無限に即興演奏し続けるFMシンセのJavaScript版です。

Web Audio API、WebGLに対応したウェブ・ブラウザーでこのページを開き、ボタンをクリックすると演奏します。
クリックすると黒画面になり、数秒後に音と絵が出ます。
クリック、もしくはEscキーで元の画面に戻ります。
「FullScreen」ボタンでフルスクリーンモードになります。

          

ソースコード

これらのソースコードはBSD 2-Clauseライセンスで公開します。

synth.js
/*
  Copyright (c) 2017-2019, miya
  All rights reserved.

  Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:

  1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.

  2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.

  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
  IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
  PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/


function Synthesizer()
{
  var STATE_ATTACK = 0;
  var STATE_DECAY = 1;
  var STATE_SUSTAIN = 2;
  var STATE_RELEASE = 3;
  var STATE_SLEEP = 4;
  var WAVE_BUFFER_BITS = 8;
  var INT_BITS = 32;
  var FIXED_BITS = 14;
  var FIXED_BITS_ENV = 8;
  var WAVE_ADDR_SHIFT = (INT_BITS - WAVE_BUFFER_BITS);
  var WAVE_ADDR_SHIFT_M = (WAVE_ADDR_SHIFT - FIXED_BITS);
  var FIXED_SCALE = (1 << FIXED_BITS);
  var FIXED_SCALE_M1 = (FIXED_SCALE - 1);
  var WAVE_BUFFER_SIZE = (1 << WAVE_BUFFER_BITS);
  var WAVE_BUFFER_SIZE_M1 = (WAVE_BUFFER_SIZE - 1);
  var ENV_VALUE_MAX = (1 << FIXED_BITS << FIXED_BITS_ENV);
  var MOD_LEVEL_MAX = (Math.floor(FIXED_SCALE * 0.52));
  var bufferLength = 0;

  var oscs = 4;
  var reverbLLength = 0.5111;
  var reverbRLength = 0.5;
  var reverbLSize = 0;
  var reverbRSize = 0;
  var reverbDecay = 0.8;
  var outVolume = (1.0 / FIXED_SCALE);

  var self = this;
  var callback;
  var callbackRate = 4096;
  var callbackCounter = 0;
  var reverbAddrL = 0;
  var reverbAddrR = 0;
  var outL = 0;
  var outR = 0;
  var waac = null;
  var wasp = null;
  var waveData = [];

  var reverbBufferL = [];
  var reverbBufferR = [];
  var params = [];
  var activeCh = [];

  function Param()
  {
    this.envelopeLevelA = 0;
    this.envelopeLevelS = 0;
    this.envelopeDiffA = 0;
    this.envelopeDiffD = 0;
    this.envelopeDiffR = 0;
    this.modPatch0 = 0;
    this.modPatch1 = 0;
    this.modLevel0 = 0;
    this.modLevel1 = 0;
    this.levelL = 0;
    this.levelR = 0;
    this.levelRev = 0;

    this.state = 0;
    this.stateSave = 0;
    this.count = 0;
    this.currentLevel = 0;
    this.pitch = 0;
    this.mod0 = 0;
    this.mod1 = 0;
    this.outData = 0;
    this.outWaveL = 0;
    this.outWaveR = 0;
    this.outRevL = 0;
    this.outRevR = 0;
    this.mixOut = false;
    this.noteOn = false;
    this.limitValue = 0;
    this.valueDiff = 0;
    this.limitGt = false;
  }

  var sp_process = function(ev)
  {
    var i;
    var bufL = ev.outputBuffer.getChannelData(0);
    var bufR = ev.outputBuffer.getChannelData(1);
    for (i = 0; i < bufferLength; i++)
    {
      callbackCounter++;
      if (callbackCounter > callbackRate)
      {
        callbackCounter = 0;
        callback(i);
      }
      render();
      bufL[i] = outL;
      bufR[i] = outR;
    }
  };

  // default callback
  var dummy = function(delaySample)
  {
  };

  this.init = function()
  {
    var i, reverbLBufferSize, reverbRBufferSize;
    if (!waac)
    {
      waac = new (window.AudioContext || window.webkitAudioContext)();
    }
    callback = dummy;
    reverbLSize = Math.floor(this.getSampleRate() * reverbLLength);
    reverbRSize = Math.floor(this.getSampleRate() * reverbRLength);
    reverbLBufferSize = (reverbLSize + 0x800) & (~0x7ff);
    reverbRBufferSize = (reverbRSize + 0x800) & (~0x7ff);
    reverbBufferL = new Array(reverbLBufferSize);
    reverbBufferR = new Array(reverbRBufferSize);
    for (i = 0; i < reverbLBufferSize; i++)
    {
      reverbBufferL[i] = 0;
    }
    for (i = 0; i < reverbRBufferSize; i++)
    {
      reverbBufferR[i] = 0;
    }

    for (i = 0; i < WAVE_BUFFER_SIZE; i++)
    {
      waveData[i] = Math.floor(Math.sin(Math.PI * 2.0 / WAVE_BUFFER_SIZE * i) * FIXED_SCALE);
    }

    for (i = 0; i < oscs; i++)
    {
      activeCh[i] = false;

      params[i] = new Param();

      params[i].state = STATE_SLEEP;
      params[i].pitch = 10000000;
      params[i].envelopeLevelA = ENV_VALUE_MAX;
      params[i].envelopeLevelS = 0;
      params[i].envelopeDiffA = ENV_VALUE_MAX >> 3;
      params[i].envelopeDiffD = (- ENV_VALUE_MAX) >> 15;
      params[i].envelopeDiffR = (- ENV_VALUE_MAX) >> 13;
      params[i].levelL = FIXED_SCALE;
      params[i].levelR = FIXED_SCALE;
      params[i].levelRev = FIXED_SCALE;
      params[i].mixOut = true;
      params[i].modPatch0 = i;
      params[i].modPatch1 = i;
      params[i].modLevel0 = MOD_LEVEL_MAX;
      params[i].modLevel1 = 0;
    }

    reverbAddrL = 0;
    reverbAddrR = 0;
  };

  // from callback
  var render = function()
  {
    var i, waveAddrF, waveAddrR, oscOutF, oscOutR, waveAddrM, oscOut, waveAddr, mixL, mixR, mixRevL, mixRevR, reverbL, reverbR;

    mixL = 0;
    mixR = 0;
    mixRevL = 0;
    mixRevR = 0;

    // synth
    for (i = 0; i < oscs; i++)
    {
      if (activeCh[i] === true)
      {
        // envelope generator
        switch (params[i].state)
        {
          case STATE_SLEEP:
          {
            break;
          }

          case STATE_ATTACK:
          {
            params[i].currentLevel += params[i].envelopeDiffA;
            if (params[i].currentLevel > params[i].envelopeLevelA)
            {
              params[i].currentLevel = params[i].envelopeLevelA;
              params[i].state = STATE_DECAY;
            }
            break;
          }

          case STATE_DECAY:
          {
            params[i].currentLevel += params[i].envelopeDiffD;
            if (params[i].currentLevel < params[i].envelopeLevelS)
            {
              params[i].currentLevel = params[i].envelopeLevelS;
              params[i].state = STATE_SUSTAIN;
            }
            break;
          }

          case STATE_SUSTAIN:
          {
            break;
          }

          case STATE_RELEASE:
          {
            params[i].currentLevel += params[i].envelopeDiffR;
            if (params[i].currentLevel < 0)
            {
              params[i].currentLevel = 0;
              activeCh[i] = false;
              params[i].state = STATE_SLEEP;
            }
            break;
          }
        }

        params[i].mod0 = params[params[i].modPatch0].outData;
        params[i].mod1 = params[params[i].modPatch1].outData;

        waveAddr = (params[i].count +
                    (params[i].mod0 * params[i].modLevel0) +
                    (params[i].mod1 * params[i].modLevel1)) >>> WAVE_ADDR_SHIFT_M;

        // fetch wave data
        waveAddrF = waveAddr >>> FIXED_BITS;
        waveAddrR = (waveAddrF + 1) & WAVE_BUFFER_SIZE_M1;
        oscOutF = waveData[waveAddrF];
        oscOutR = waveData[waveAddrR];
        waveAddrM = waveAddr & FIXED_SCALE_M1;
        oscOut = ((oscOutF * (FIXED_SCALE - waveAddrM)) >> FIXED_BITS) + ((oscOutR * waveAddrM) >> FIXED_BITS);
        params[i].outData = (oscOut * (params[i].currentLevel >> FIXED_BITS_ENV)) >> FIXED_BITS;
        params[i].count += params[i].pitch;

        // mix
        if (params[i].mixOut === false)
        {
          params[i].outWaveL = 0;
          params[i].outWaveR = 0;
          params[i].outRevL = 0;
          params[i].outRevR = 0;
        }
        else
        {
          params[i].outWaveL = (params[i].outData * params[i].levelL) >> FIXED_BITS;
          params[i].outWaveR = (params[i].outData * params[i].levelR) >> FIXED_BITS;
          params[i].outRevL = (params[i].outWaveL * params[i].levelRev) >> FIXED_BITS;
          params[i].outRevR = (params[i].outWaveR * params[i].levelRev) >> FIXED_BITS;
        }

        mixL += params[i].outWaveL;
        mixR += params[i].outWaveR;
        mixRevL += params[i].outRevL;
        mixRevR += params[i].outRevR;
      }
    }

    // reverb
    reverbL = reverbBufferR[reverbAddrR] * reverbDecay;
    reverbR = reverbBufferL[reverbAddrL] * reverbDecay;
    reverbL += mixRevR;
    reverbR += mixRevL;
    reverbBufferL[reverbAddrL] = reverbL;
    reverbBufferR[reverbAddrR] = reverbR;
    reverbAddrL++;
    if (reverbAddrL > reverbLSize)
    {
      reverbAddrL = 0;
    }
    reverbAddrR++;
    if (reverbAddrR > reverbRSize)
    {
      reverbAddrR = 0;
    }
    outL = (mixL + reverbBufferL[reverbAddrL]) * outVolume;
    outR = (mixR + reverbBufferR[reverbAddrR]) * outVolume;
  };

  this.start = function()
  {
    wasp = waac.createScriptProcessor(bufferLength, 0, 2);
    bufferLength = wasp.bufferSize;
    wasp.onaudioprocess = sp_process;
    wasp.connect(waac.destination);
  };

  this.stop = function()
  {
    if (wasp)
    {
      wasp.disconnect();
    }
  };

  this.getSampleRate = function()
  {
    return waac.sampleRate;
  };

  this.getBufferLength = function()
  {
    return bufferLength;
  };

  this.getWaveBufferSize = function()
  {
    return WAVE_BUFFER_SIZE;
  };

  this.getEnvValueMax = function()
  {
    return ENV_VALUE_MAX;
  };

  this.getModLevelMax = function()
  {
    return MOD_LEVEL_MAX;
  };

  this.getParams = function(osc)
  {
    return params[osc];
  };

  this.setCallback = function(callback_arg)
  {
    callback = callback_arg;
  };

  this.setCallbackRate = function(callbackRate_arg)
  {
    callbackRate = callbackRate_arg;
    callbackCounter = 0;
  };

  this.setSynthParam = function(oscs_arg, reverbLLength_arg, reverbRLength_arg, reverbDecay_arg, outVolume_arg, bufferLength_arg)
  {
    oscs = oscs_arg;
    reverbLLength = reverbLLength_arg;
    reverbRLength = reverbRLength_arg;
    reverbDecay = reverbDecay_arg;
    outVolume = outVolume_arg;
    bufferLength = bufferLength_arg;
  };

  this.playNote = function(ch, noteon)
  {
    if (noteon === true)
    {
      params[ch].state = STATE_ATTACK;
      params[ch].noteOn = true;
      activeCh[ch] = true;
    }
    else
    {
      params[ch].state = STATE_RELEASE;
      params[ch].noteOn = false;
    }
  };
}
sequencer.js
/*
  Copyright (c) 2017-2019, miya
  All rights reserved.

  Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:

  1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.

  2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.

  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
  IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
  PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/


function DataSeq(satz, ch, chord, note, oct, delay)
{
  this.satz = satz;
  this.ch = ch;
  this.chord = chord;
  this.note = note;
  this.oct = oct;
  this.delay = delay;
}

function Sequencer()
{
  var self = this;
  var tempo = 5.0;
  var seqLength = 8;
  var scaleBufferSize = 16;
  var seqCounter = 0;
  var barCounter = 0;
  var satzCounter = 0;
  var deleteCounter = 0;
  var chord = 0;
  var octMin = 5;
  var octRange = 3;
  var modMax = 6;
  var addFrequency = 4;
  var deleteFrequency = 16;
  var repeat = 4;
  var toneChange = true;
  var appendBass = true;
  var deletePrev = true;
  var ops = 1;
  var channels = 4;
  var tempoCount = 0;
  var chordLength = 3;
  var browserType = '';
  var scaleTable = [];
  var chordData = [];
  var progressionData = [];
  var bassData = [];
  var bassPattern = [];
  var seqData = [];
  var toneData = [];

  this.synth = new Synthesizer();

  // default callback
  var dummy = function()
  {
  };
  var callbackNoteOn = dummy;

  function NoteData()
  {
    this.note = 0;
    this.oct = 0;
  }

  function toneParam()
  {
    this.oct = 0;
  }

  this.stop = function()
  {
    if (self.synth)
    {
      self.synth.stop();
    }
  };

  this.init = function()
  {
    var i, j;
    browserType = queryBrowserType();
    self.synth.init();
    tempoCount = Math.floor(self.synth.getSampleRate() / tempo);

    for (i = 0; i < scaleBufferSize; i++)
    {
      scaleTable[i] = Math.floor(Math.pow(2.0, i / 12.0) * 440.0 * (0x100000000 / self.synth.getSampleRate() / self.synth.getWaveBufferSize()));
    }

    for (i = 0; i < channels; i++)
    {
      seqData[i] = [];
      for (j = 0; j < seqLength; j++)
      {
        seqData[i][j] = new NoteData();
      }
      toneData[i] = [];
      for (j = 0; j < ops; j++)
      {
        toneData[i][j] = new toneParam();
        toneData[i][j].oct = 0;
      }
    }
  };

  this.start = function()
  {
    seqCounter = 0;
    barCounter = 0;
    deleteCounter = 0;
    chord = 0;

    self.synth.setCallback(loop);
    self.synth.setCallbackRate(tempoCount);
    self.synth.start();
  };

  var tone_change = function()
  {
    var i, j, osc;
    for (i = 0; i < channels; i++)
    {
      for (j = 0; j < ops; j++)
      {
        osc = i * ops + j;
        self.synth.getParams(osc).envelopeDiffA = self.synth.getEnvValueMax() >> (randI(10) + 3);
        self.synth.getParams(osc).modLevel0 = self.synth.getModLevelMax() * randI(modMax);
        if (ops > 1)
        {
          self.synth.getParams(osc).envelopeDiffD = - self.synth.getEnvValueMax() >> (randI(9) + 13);
          toneData[i][j].oct = randI(3) - 1;
        }
      }
    }
  }

  var add_note = function(num)
  {
    var i, ch, beat, beat_prev;
    for (i = 0; i < num; i++)
    {
      ch = randI(channels);
      beat = randI(seqLength);
      beat_prev = beat - 1;
      if (beat_prev < 0)
      {
        beat_prev += seqLength;
      }
      seqData[ch][beat].note = randI(chordLength);
      seqData[ch][beat].oct = randI(octRange) + octMin;
      if (deletePrev === true)
      {
        seqData[ch][beat_prev].oct = 0;
      }
    }
  }

  // callback
  var loop = function(delaySample)
  {
    // sequencer
    var i, j, osc, n;
    var timeNow = Date.now();
    var buflen = self.synth.getBufferLength();
    if (browserType === "firefox")
    {
      buflen = 0;
    }
    var soundDelay = 1000.0 * (delaySample + buflen) / self.synth.getSampleRate();

    if (seqCounter >= seqLength)
    {
      seqCounter = 0;
      if (barCounter >= repeat)
      {
        barCounter = 0;
        if (toneChange === true)
        {
          tone_change();
        }
        chord = progressionData[chord][randI(progressionData[chord][0]) + 1];
        deleteCounter = randI(deleteFrequency);
        satzCounter++;
      }
      // delete note
      for (i = 0; i < deleteCounter; i++)
      {
        seqData[randI(channels)][randI(seqLength)].oct = 0;
      }
      add_note(addFrequency);
      if (appendBass === true)
      {
        for (i = 0; i < seqLength; i++)
        {
          if (bassPattern[i] === 1)
          {
            seqData[0][i].note = bassData[chord];
            seqData[0][i].oct = octMin;
          }
        }
      }
      barCounter++;
    }
    for (i = 0; i < channels; i++)
    {
      if (seqData[i][seqCounter].oct !== 0)
      {
        n = chordData[chord][seqData[i][seqCounter].note];
        for (j = 0; j < ops; j++)
        {
          osc = i * ops + j;
          self.synth.getParams(osc).pitch = scaleTable[n] << (seqData[i][seqCounter].oct + toneData[i][j].oct);
          self.synth.playNote(osc, true);
        }
        callbackNoteOn(new DataSeq(satzCounter, i, chord, n, seqData[i][seqCounter].oct, timeNow + soundDelay));
      }
      else
      {
        for (j = 0; j < ops; j++)
        {
          osc = i * ops + j;
          self.synth.playNote(osc, false);
        }
      }
    }
    seqCounter++;
  };

  this.setSeqParam = function(tempo_arg, seqLength_arg, octMin_arg, octRange_arg, addFrequency_arg, deleteFrequency_arg, repeat_arg, toneChange_arg, appendBass_arg, deletePrev_arg, ops_arg, channels_arg, modMax_arg)
  {
    tempo = tempo_arg;
    seqLength = seqLength_arg;
    octMin = octMin_arg;
    octRange = octRange_arg;
    addFrequency = addFrequency_arg;
    deleteFrequency = deleteFrequency_arg;
    repeat = repeat_arg;
    toneChange = toneChange_arg;
    appendBass = appendBass_arg;
    deletePrev = deletePrev_arg;
    ops = ops_arg;
    channels = channels_arg;
    modMax = modMax_arg;
  };

  this.setChordData = function(chordLength_arg, chordData_arg, progressionData_arg, bassData_arg, bassPattern_arg)
  {
    chordLength = chordLength_arg;
    chordData = chordData_arg;
    progressionData = progressionData_arg;
    bassData = bassData_arg;
    bassPattern = bassPattern_arg;
  };

  this.setCallback = function(callbackNoteOn_arg)
  {
    callbackNoteOn = callbackNoteOn_arg;
  };

  this.setToneData = function(channel_arg, op_arg, oct_arg)
  {
    toneData[channel_arg][op_arg].oct = oct_arg;
  };
}
visualizer.js
/*
  Copyright (c) 2017-2019, miya
  All rights reserved.

  Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:

  1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.

  2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.

  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
  IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
  PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/


function Visualizer()
{
  var vs_s =
      "uniform float time;\n" +
      "uniform float dir;\n" +
      "attribute vec2 pos_s;\n" +
      "attribute vec2 pos_e;\n" +
      "attribute vec3 col;\n" +
      "attribute float otime;\n" +
      "varying vec4 fcol;\n" +
      "void main(void)\n" +
      "{\n" +
      " vec2 d2 = vec2(dir, 1.0);\n" +
      " float t1 = clamp(otime - time, 0.0, 1.0);\n" +
      " vec2 p1 = mix(pos_s * d2, pos_e * d2, vec2(t1, t1));\n" +
      " gl_Position = vec4(p1, 0.0, 1.0);\n" +
      " vec3 c1 = mix(vec3(0.0, 0.0, 0.0), col, vec3(t1, t1, t1));\n" +
      " fcol = vec4(c1, 1.0);\n" +
      "}\n";

  var fs_s =
      "precision mediump float;\n" +
      "varying vec4 fcol;\n" +
      "void main(void)\n" +
      "{\n" +
      " gl_FragColor = fcol;\n" +
      "}\n";

  var self = this;
  var webgl = new WebGLLib();
  var sprog;
  var vboPosS;
  var vboPosE;
  var vboCol;
  var vboOtime;
  var objects = 0;
  var time = 0.0;
  var vdataPosS = [];
  var vdataPosE = [];
  var vdataCol = [];
  var vdataOtime = [];
  var objPtr = 0;
  var speed = 0.01;
  var masterSeed = randI(0xffffffff);
  var playing = false;

  this.fifo = new Fifo();
  this.randf = new RandF();

  // callback sub
  var addObj = function(data)
  {
    var i;
    var pos_s = [];
    var pos_e = [];
    var col = [];
    var otime = [];
    self.randf.seed((data.ch << 24) ^ (data.satz << 20) ^ (data.chord << 16) ^ (data.note << 8) ^ data.oct ^ masterSeed);
    var x = self.randf.get() * 2.0 - 1.0;
    var y = self.randf.get() * 2.0 - 1.0;
    var r = self.randf.get();
    var g = self.randf.get();
    var b = self.randf.get();
    for (i = 0; i < 3; i++)
    {
      pos_s.push(x + self.randf.get() - 0.5);
      pos_s.push(y + self.randf.get() - 0.5);
      pos_e.push(x + self.randf.get() - 0.5);
      pos_e.push(y + self.randf.get() - 0.5);
      col.push(r);
      col.push(g);
      col.push(b);
      otime.push(time + 1.0);
    }
    webgl.updateBuffer(vboPosS, pos_s, objPtr * 6);
    webgl.updateBuffer(vboPosE, pos_e, objPtr * 6);
    webgl.updateBuffer(vboCol, col, objPtr * 9);
    webgl.updateBuffer(vboOtime, otime, objPtr * 3);
    objPtr++;
    if (objPtr >= objects)
    {
      objPtr = 0;
    }
  };

  // callback
  var draw = function()
  {
    var peek, data;
    var timeNow = Date.now();
    while (true)
    {
      peek = self.fifo.peek();
      if (peek === undefined)
      {
        break;
      }
      else if (peek.delay > timeNow)
      {
        break;
      }
      data = self.fifo.pop();
      if (data === undefined)
      {
        break;
      }
      addObj(data);
    }
    webgl.clearScreen();
    webgl.uniform1f(sprog, 'time', time);
    webgl.uniform1f(sprog, 'dir', 1.0);
    webgl.drawTriangles(0, objects * 3);
    webgl.uniform1f(sprog, 'dir', -1.0);
    webgl.drawTriangles(0, objects * 3);
    time += speed;
    if (playing === true)
    {
      webgl.nextFrame();
    }
  };

  this.init = function(objects_arg, speed_arg)
  {
    var i, j, k;
    objects = objects_arg;
    speed = speed_arg;
    self.fifo.init(objects * 16);
    webgl.init('canvas1', draw);
    sprog = webgl.createProgram(vs_s, fs_s);
    webgl.useProgram(sprog);

    for (i = 0; i < objects; i++)
    {
      for (k = 0; k < 3; k++)
      {
        for (j = 0; j < 2; j++)
        {
          vdataPosS.push(0.0);
          vdataPosE.push(0.0);
        }
        for (j = 0; j < 3; j++)
        {
          vdataCol.push(0.0);
        }
        vdataOtime.push(0.0);
      }
    }

    vboPosS = webgl.createBuffer(sprog, 'pos_s', vdataPosS, 2, true);
    vboPosE = webgl.createBuffer(sprog, 'pos_e', vdataPosE, 2, true);
    vboCol = webgl.createBuffer(sprog, 'col', vdataCol, 3, true);
    vboOtime = webgl.createBuffer(sprog, 'otime', vdataOtime, 1, true);

    webgl.setBlendMode(1);
  };

  this.start = function()
  {
    playing = true;
    draw();
  };

  this.stop = function()
  {
    playing = false;
    webgl.close();
  };
}
webgl_lib.js
/*
  Copyright (c) 2017-2019, miya
  All rights reserved.

  Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:

  1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.

  2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.

  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
  IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
  PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/


function WebGLLib()
{
  var canvas;
  var gl;
  var width;
  var height;
  var callback;
  var resized = true;
  var averageTime = 16.666666;
  var fpsCounter = 0;
  var dateTime;

  var clearScreen = function()
  {
    gl.clearColor(0.0, 0.0, 0.0, 1.0);
    gl.enable(gl.DEPTH_TEST);
    gl.depthFunc(gl.LEQUAL);
    gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
  };

  this.clearScreen = function()
  {
    clearScreen();
  };

  this.getWidth = function()
  {
    return width;
  };

  this.getHeight = function()
  {
    return height;
  };

  var delayResize = function()
  {
    if (resized === true)
    {
      resized = false;
      setTimeout(resize, 1000);
    }
  };

  var resize = function()
  {
    width = window.innerWidth;
    height = window.innerHeight;
    canvas.width = width;
    canvas.height = height;
    gl.viewport(0, 0, width, height);
    clearScreen();
    resized = true;
  };

  var createShader = function(source, type)
  {
    var shader = gl.createShader(type);
    gl.shaderSource(shader, source);
    gl.compileShader(shader);

    if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS))
    {
      console.log("Error: Create Shader");
      shader = null;
    }
    return shader;
  };

  this.nextFrame = function()
  {
    var now = Date.now();
    if (fpsCounter >= 60)
    {
      fpsCounter = 0;
      averageTime = (now - dateTime) * 0.0166666666667;
      dateTime = now;
    }
    fpsCounter++;
    window.requestAnimationFrame(callback);
  };

  this.drawTriangles = function(start, count)
  {
    gl.drawArrays(gl.TRIANGLES, start, count);
  };

  this.uniform1f = function(program, name, value)
  {
    gl.uniform1f(gl.getUniformLocation(program, name), value);
  };

  this.useProgram = function(pg_arg)
  {
    gl.useProgram(pg_arg);
  };

  this.createProgram = function(vs_source, fs_source)
  {
    var vs = createShader(vs_source, gl.VERTEX_SHADER);
    var fs = createShader(fs_source, gl.FRAGMENT_SHADER);
    var sprog = gl.createProgram();
    gl.attachShader(sprog, vs);
    gl.attachShader(sprog, fs);
    gl.linkProgram(sprog);

    if (!gl.getProgramParameter(sprog, gl.LINK_STATUS))
    {
      console.log("Error: Create Program");
      sprog = null;
    }
    return sprog;
  };

  this.setBlendMode = function(mode)
  {
    switch (mode)
    {
      case 0:
      gl.disable(gl.BLEND);
      break;
      case 1:
      gl.enable(gl.BLEND);
      gl.blendFuncSeparate(gl.SRC_ALPHA, gl.ONE, gl.ONE, gl.ONE);
      gl.blendEquationSeparate(gl.FUNC_ADD, gl.FUNC_ADD);
      break;
    }
  };

  this.createBuffer = function(program, name, array, size, isDynamic)
  {
    var usage;
    if (isDynamic)
    {
      usage = gl.DYNAMIC_DRAW;
    }
    else
    {
      usage = gl.STATIC_DRAW;
    }
    var vbo = gl.createBuffer();
    gl.bindBuffer(gl.ARRAY_BUFFER, vbo);
    gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(array), usage);
    var location = gl.getAttribLocation(program, name);
    gl.enableVertexAttribArray(location);
    gl.vertexAttribPointer(location, size, gl.FLOAT, false, 0, 0);
    gl.bindBuffer(gl.ARRAY_BUFFER, null);
    return vbo;
  };

  this.bindBuffer = function(buffer)
  {
    gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
  };

  this.updateBuffer = function(buffer, array, offset)
  {
    gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
    gl.bufferSubData(gl.ARRAY_BUFFER, offset * 4, new Float32Array(array));
    gl.bindBuffer(gl.ARRAY_BUFFER, null);
  };

  this.deleteBuffer = function(buffer)
  {
    gl.deleteBuffer(buffer);
  };

  this.init = function(canvas_arg, callback_arg)
  {
    canvas = document.getElementById(canvas_arg);
    callback = callback_arg;
    fpsCounter = 0;
    dateTime = Date.now();

    if (canvas)
    {
      gl = canvas.getContext("webgl") || canvas.getContext("experimental-webgl");
    }
    else
    {
      console.log("Error: Get Canvas");
    }

    if (gl)
    {
      resize();
      window.addEventListener('resize', delayResize);
    }
    else
    {
      console.log("Error: Get webgl");
    }
  };

  this.close = function()
  {
    window.removeEventListener('resize', delayResize);
  };

  this.getAverageTime = function()
  {
    return averageTime;
  };
}
stlib.js
/*
  Copyright (c) 2017, miya
  All rights reserved.

  Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:

  1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.

  2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.

  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
  IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
  PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/


var queryBrowserType = function()
{
  var type = '';
  var ua = window.navigator.userAgent.toLowerCase();
  if ((ua.indexOf('msie') !== -1) || (ua.indexOf('trident') !== -1))
  {
    type = 'msie';
  }
  else if (ua.indexOf('edge') !== -1)
  {
    type = 'edge';
  }
  else if (ua.indexOf('safari') !== -1)
  {
    type = 'safari';
  }
  else if (ua.indexOf('firefox') !== -1)
  {
    type = 'firefox';
  }
  else if (ua.indexOf('opera') !== -1)
  {
    type = 'opera';
  }
  else if (ua.indexOf('chrome') !== -1)
  {
    type = 'chrome';
  }
  return type;
};

var isFullScreen = function()
{
  var r = false;
  if (document.fullscreenElement)
  {
    if (document.fullscreenElement !== null)
    {
      r = true;
    }
  }
  else if (document.msFullscreenElement)
  {
    if (document.msFullscreenElement !== null)
    {
      r = true;
    }
  }
  else if (document.webkitFullscreenElement)
  {
    if (document.webkitFullscreenElement !== null)
    {
      r = true;
    }
  }
  else if (document.mozFullScreenElement)
  {
    if (document.mozFullScreenElement !== null)
    {
      r = true;
    }
  }
  else if (document.mozFullScreen || document.webkitIsFullScreen)
  {
    r = true;
  }
  return r;
};

var requestFullScreen = function()
{
  var elem = document.documentElement;
  if (elem.requestFullscreen)
  {
    elem.requestFullscreen();
  }
  else if (elem.webkitRequestFullscreen)
  {
    elem.webkitRequestFullscreen();
  }
  else if (elem.mozRequestFullScreen)
  {
    elem.mozRequestFullScreen();
  }
  else if (elem.msRequestFullscreen)
  {
    elem.msRequestFullscreen();
  }
};

var exitFullScreen = function()
{
  if (document.exitFullscreen)
  {
    document.exitFullscreen();
  }
  else if (document.webkitExitFullscreen)
  {
    document.webkitExitFullscreen();
  }
  else if (document.webkitCancelFullScreen)
  {
    document.webkitCancelFullScreen();
  }
  else if (document.mozCancelFullScreen)
  {
    document.mozCancelFullScreen();
  }
  else if (document.msExitFullscreen)
  {
    document.msExitFullscreen();
  }
};

var toggleFullScreen = function()
{
  if (isFullScreen() === true)
  {
    exitFullScreen();
  }
  else
  {
    requestFullScreen();
  }
};

var randI = function(max)
{
  return Math.floor(Math.random() * max);
};

function RandF()
{
  var r = 0x92d68ca2;

  this.seed = function(s)
  {
    r = 0x92d68ca2 ^ s;
  };

  this.get = function()
  {
    r = r ^ (r << 13);
    r = r ^ (r >> 17);
    r = r ^ (r << 5);
    return ((r & 0x7fffffff) * 4.656612873e-10);
  };
}

function Fifo()
{
  var self = this;
  var objects = 0;
  this.ptr_in = 0;
  this.ptr_out = 0;
  this.data = [];

  this.init = function(size)
  {
    objects = size;
    var i;
    for (i = 0; i < objects; i++)
    {
      self.data[i] = null;
    }
    self.ptr_in = 0;
    self.ptr_out = 0;
  };

  this.push = function(data_arg)
  {
    var pi = self.ptr_in;
    var po = self.ptr_out;
    pi++;
    if (pi >= objects)
    {
      pi -= objects;
    }
    if (pi === po)
    {
      return;
    }
    self.data[pi] = data_arg;
    self.ptr_in = pi;
    return true;
  };

  this.pop = function()
  {
    var pi = self.ptr_in;
    var po = self.ptr_out;
    if (pi === po)
    {
      return;
    }
    po++;
    if (po >= objects)
    {
      po -= objects;
    }
    var d = self.data[po];
    self.ptr_out = po;
    return d;
  };

  this.peek = function()
  {
    var pi = self.ptr_in;
    var po = self.ptr_out;
    if (pi === po)
    {
      return;
    }
    po++;
    if (po >= objects)
    {
      po -= objects;
    }
    var d = self.data[po];
    return d;
  };
}
main.js
/*
  Copyright (c) 2017-2019, miya
  All rights reserved.

  Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:

  1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.

  2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.

  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
  IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
  PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/


function Main ()
{
  var self = this;
  var FIXED_BITS = 14;
  var FIXED_BITS_ENV = 8;
  var FIXED_SCALE = (1 << FIXED_BITS);
  var ENV_VALUE_MAX = (1 << FIXED_BITS << FIXED_BITS_ENV);
  var VSPEED = 0.001;
  var OBJECTS = 32;
  var saveDocument = '';

  var seq = new Sequencer();
  var visualizer = new Visualizer();

  this.start = function()
  {
    visualizer.start();
    seq.start();
  };

  this.stop = function()
  {
    window.removeEventListener('keyup', evKeyUp);
    window.removeEventListener('click', evClick);
    seq.stop();
    visualizer.stop();
    document.body.innerHTML = saveDocument;
  };

  // callback
  var noteOnCallback = function(data)
  {
    visualizer.fifo.push(data);
  };

  var delayAddEvent = function()
  {
    window.addEventListener('keyup', evKeyUp);
    window.addEventListener('click', evClick);
  };

  var init = function()
  {
    saveDocument = document.body.innerHTML;
    document.body.innerHTML = '<canvas id="canvas1"></canvas>';
    setTimeout(delayAddEvent, 3000);
  };

  this.playMonologue = function()
  {
    var i;
    var OPS = 1;
    var CHANNELS = 4;
    var TEMPO = 5.85;
    var PART_VOLUME = (Math.floor(FIXED_SCALE / CHANNELS));
    var MOD_LEVEL_MAX = (Math.floor(FIXED_SCALE * 0.52));
    var REVERB_VOLUME = (Math.floor(PART_VOLUME * 0.8));
    var OUT_VOLUME = (1.0 / FIXED_SCALE);
    var chordData = [
      [0,2,4,7,11,0,0,0],
      [2,4,5,7,11,0,0,0],
      [0,4,7,9,11,0,0,0],
      [0,4,5,7,9,0,0,0],
      [0,2,4,5,9,0,0,0],
      [2,5,7,9,11,0,0,0]
    ];
    var progressionData = [
      [6, 0,1,2,3,4,5,0],
      [6, 0,1,2,3,4,5,0],
      [6, 0,1,2,3,4,5,0],
      [6, 0,1,2,3,4,5,0],
      [6, 0,1,2,3,4,5,0],
      [3, 0,1,2,0,0,0,0]
    ];
    var bassData = [0,1,3,2,1,2];
    var bassPattern = [1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0];
    for (i = 0; i < 3; i++)
    {
      bassPattern[randI(12)] = 1;
    }
    init();
    seq.setSeqParam(TEMPO, 16, 5, 3, 4, 16, 4, true, true, false, OPS, CHANNELS, 6);
    seq.setChordData(5, chordData, progressionData, bassData, bassPattern);
    seq.synth.setSynthParam(OPS * CHANNELS, 0.233, 0.181, 0.6, OUT_VOLUME, 0);
    seq.setCallback(noteOnCallback);
    seq.init();
    visualizer.init(OBJECTS, TEMPO * VSPEED);
    for (i = 0; i < OPS * CHANNELS; i++)
    {
      seq.synth.getParams(i).envelopeDiffA = ENV_VALUE_MAX >> 5;
      seq.synth.getParams(i).envelopeDiffD = (- ENV_VALUE_MAX) >> 15;
      seq.synth.getParams(i).envelopeDiffR = (- ENV_VALUE_MAX) >> 13;
      seq.synth.getParams(i).modLevel0 = MOD_LEVEL_MAX;
      seq.synth.getParams(i).levelL = PART_VOLUME;
      seq.synth.getParams(i).levelR = PART_VOLUME;
      seq.synth.getParams(i).levelRev = REVERB_VOLUME;
    }
    seq.synth.getParams(1).levelL = PART_VOLUME >> 1;
    seq.synth.getParams(1).levelR = PART_VOLUME;
    seq.synth.getParams(2).levelL = PART_VOLUME;
    seq.synth.getParams(2).levelR = PART_VOLUME >> 1;
    self.start();
  };

  this.playLuna = function()
  {
    var i;
    var OPS = 1;
    var CHANNELS = 4;
    var TEMPO = 1.0;
    var PART_VOLUME = (Math.floor(FIXED_SCALE / CHANNELS * 0.8));
    var MOD_LEVEL_MAX = (Math.floor(FIXED_SCALE * 0.3));
    var REVERB_VOLUME = (Math.floor(PART_VOLUME * 1.0));
    var OUT_VOLUME = (1.0 / FIXED_SCALE);
    var chordData = [
      [0,2,4,5,7,0,0,0],
      [0,2,5,7,9,0,0,0],
      [0,2,5,7,11,0,0,0]
    ];
    var progressionData = [
      [3, 0,1,2,0,0,0,0],
      [3, 0,1,2,0,0,0,0],
      [1, 0,0,0,0,0,0,0]
    ];
    var bassData = [0,2,3];
    var bassPattern = [1,0,0,0,1,0,0,0];
    init();
    seq.setSeqParam(TEMPO, 8, 6, 3, 4, 16, 4, false, true, false, OPS, CHANNELS, 6);
    seq.setChordData(5, chordData, progressionData, bassData, bassPattern);
    seq.synth.setSynthParam(OPS * CHANNELS, 0.557278911565, 0.519439673469, 0.8, OUT_VOLUME, 0);
    seq.setCallback(noteOnCallback);
    seq.init();
    visualizer.init(OBJECTS, TEMPO * VSPEED);
    for (i = 0; i < OPS * CHANNELS; i++)
    {
      seq.synth.getParams(i).envelopeDiffA = ENV_VALUE_MAX >> 7;
      seq.synth.getParams(i).envelopeDiffD = (- ENV_VALUE_MAX) >> 17;
      seq.synth.getParams(i).envelopeDiffR = (- ENV_VALUE_MAX) >> 15;
      seq.synth.getParams(i).modLevel0 = MOD_LEVEL_MAX;
      seq.synth.getParams(i).levelL = PART_VOLUME;
      seq.synth.getParams(i).levelR = PART_VOLUME;
      seq.synth.getParams(i).levelRev = REVERB_VOLUME;
    }
    seq.synth.getParams(1).levelL = PART_VOLUME >> 1;
    seq.synth.getParams(1).levelR = PART_VOLUME;
    seq.synth.getParams(2).levelL = PART_VOLUME;
    seq.synth.getParams(2).levelR = PART_VOLUME >> 1;
    self.start();
  };

  this.playIntoxication = function()
  {
    var i;
    var OPS = 1;
    var CHANNELS = 4;
    var TEMPO = 5.0;
    var PART_VOLUME = (Math.floor(FIXED_SCALE / CHANNELS * 0.8));
    var MOD_LEVEL_MAX = (Math.floor(FIXED_SCALE * 0.52));
    var REVERB_VOLUME = (Math.floor(PART_VOLUME * 1.5));
    var OUT_VOLUME = (1.0 / FIXED_SCALE);
    var chordData = [
      [0,4,7,0,0,0,0,0],
      [4,7,11,0,0,0,0,0],
      [0,4,9,0,0,0,0,0],
      [0,5,9,0,0,0,0,0],
      [2,5,9,0,0,0,0,0],
      [2,7,11,0,0,0,0,0]
    ];
    var progressionData = [
      [6, 0,1,2,3,4,5,0],
      [6, 0,1,2,3,4,5,0],
      [6, 0,1,2,3,4,5,0],
      [6, 0,1,2,3,4,5,0],
      [6, 0,1,2,3,4,5,0],
      [3, 0,1,2,0,0,0,0]
    ];
    var bassData = [0,0,2,1,0,1];
    var bassPattern = [1,0,0,1,0,0];
    init();
    seq.setSeqParam(TEMPO, 6, 5, 3, 4, 16, 8, true, true, false, OPS, CHANNELS, 6);
    seq.setChordData(3, chordData, progressionData, bassData, bassPattern);
    seq.synth.setSynthParam(OPS * CHANNELS, 0.4, 0.8123, 0.8, OUT_VOLUME, 0);
    seq.setCallback(noteOnCallback);
    seq.init();
    visualizer.init(OBJECTS, TEMPO * VSPEED);
    for (i = 0; i < OPS * CHANNELS; i++)
    {
      seq.synth.getParams(i).envelopeDiffA = ENV_VALUE_MAX >> 4;
      seq.synth.getParams(i).envelopeDiffD = (- ENV_VALUE_MAX) >> 15;
      seq.synth.getParams(i).envelopeDiffR = (- ENV_VALUE_MAX) >> 13;
      seq.synth.getParams(i).modLevel0 = MOD_LEVEL_MAX * randI(6);
      seq.synth.getParams(i).levelL = PART_VOLUME;
      seq.synth.getParams(i).levelR = PART_VOLUME;
      seq.synth.getParams(i).levelRev = REVERB_VOLUME;
    }
    seq.synth.getParams(1).levelL = PART_VOLUME >> 1;
    seq.synth.getParams(1).levelR = PART_VOLUME;
    seq.synth.getParams(2).levelL = PART_VOLUME;
    seq.synth.getParams(2).levelR = PART_VOLUME >> 1;
    self.start();
  };

  this.playPentagon = function()
  {
    var i, osc;
    var OPS = 2;
    var CHANNELS = 4;
    var TEMPO = 5.85;
    var PART_VOLUME = (Math.floor(FIXED_SCALE / CHANNELS));
    var MOD_LEVEL_MAX = (Math.floor(FIXED_SCALE * 0.52));
    var REVERB_VOLUME = (Math.floor(PART_VOLUME * 0.9));
    var OUT_VOLUME = (1.0 / FIXED_SCALE);

    var chordData = [
      [0,2,4,7,9,0,0,0],
      [0,2,4,7,9,0,0,0],
      [0,2,4,7,9,0,0,0]
    ];
    var progressionData = [
      [3, 0,1,2,0,0,0,0],
      [3, 0,1,2,0,0,0,0],
      [3, 0,1,2,0,0,0,0]
    ];
    var bassData = [4,2,1];
    var bassPattern = [1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0];
    for (i = 0; i < 3; i++)
    {
      bassPattern[randI(12)] = 1;
    }
    init();
    seq.setSeqParam(TEMPO, 16, 6, 2, 6, 12, 4, true, true, true, OPS, CHANNELS, 6);
    seq.setChordData(5, chordData, progressionData, bassData, bassPattern);
    seq.synth.setSynthParam(OPS * CHANNELS, 0.33792, 0.1553, 0.7, OUT_VOLUME, 0);
    seq.setCallback(noteOnCallback);
    seq.init();
    visualizer.init(OBJECTS, TEMPO * VSPEED);
    for (i = 0; i < OPS * CHANNELS; i++)
    {
      seq.synth.getParams(i).envelopeDiffA = ENV_VALUE_MAX >> (randI(9) + 4);
      seq.synth.getParams(i).envelopeDiffD = (- ENV_VALUE_MAX) >> (randI(9) + 13);
      seq.synth.getParams(i).envelopeDiffR = (- ENV_VALUE_MAX) >> 13;
      seq.synth.getParams(i).modLevel0 = MOD_LEVEL_MAX * randI(6);
      seq.synth.getParams(i).levelL = PART_VOLUME;
      seq.synth.getParams(i).levelR = PART_VOLUME;
      seq.synth.getParams(i).levelRev = REVERB_VOLUME;
    }
    for (i = 0; i < CHANNELS; i++)
    {
      osc = i * OPS;
      seq.synth.getParams(osc).modPatch0 = osc + 1;
      seq.synth.getParams(osc + 1).modPatch0 = osc + 1;
      seq.synth.getParams(osc + 1).modLevel0 = 0;
      seq.synth.getParams(osc + 1).mixOut = false;
    }
    seq.synth.getParams(2).levelL = PART_VOLUME >> 1;
    seq.synth.getParams(2).levelR = PART_VOLUME;
    seq.synth.getParams(4).levelL = PART_VOLUME;
    seq.synth.getParams(4).levelR = PART_VOLUME >> 1;
    self.start();
  };

  this.playEuphoria = function()
  {
    var i, osc, j;
    var OPS = 2;
    var CHANNELS = 4;
    var TEMPO = 3.8;
    var PART_VOLUME = (Math.floor(FIXED_SCALE / CHANNELS * 0.5));
    var MOD_LEVEL_MAX = (Math.floor(FIXED_SCALE * 0.52));
    var REVERB_VOLUME = (Math.floor(PART_VOLUME * 1.5));
    var OUT_VOLUME = (1.0 / FIXED_SCALE);

    var chordData = [
      [0,2,4,7,0,0,0,0],
      [0,4,7,9,0,0,0,0],
      [0,2,5,9,0,0,0,0],
      [0,5,7,9,0,0,0,0],
      [2,4,7,11,0,0,0,0],
      [2,5,7,11,0,0,0,0]
    ];
    var progressionData = [
      [4, 2,3,4,5,0,0,0],
      [4, 2,3,4,5,0,0,0],
      [4, 0,1,4,5,0,0,0],
      [4, 0,1,4,5,0,0,0],
      [2, 0,1,0,0,0,0,0],
      [2, 0,1,0,0,0,0,0]
    ];
    var bassData = [0,0,2,1,2,2];
    var bassPattern = [1,0,0,0,0,0,0,0];
    for (i = 0; i < 2; i++)
    {
      bassPattern[randI(6)] = 1;
    }
    init();
    seq.setSeqParam(TEMPO, 8, 6, 4, 6, 32, 4, false, true, false, OPS, CHANNELS, 3);
    seq.setChordData(4, chordData, progressionData, bassData, bassPattern);
    seq.synth.setSynthParam(OPS * CHANNELS, 0.33792, 0.1553, 0.8, OUT_VOLUME, 0);
    seq.setCallback(noteOnCallback);
    seq.init();
    visualizer.init(OBJECTS, TEMPO * VSPEED);
    for (i = 0; i < OPS * CHANNELS; i++)
    {
      seq.synth.getParams(i).envelopeDiffA = ENV_VALUE_MAX >> 5;
      seq.synth.getParams(i).envelopeDiffD = (- ENV_VALUE_MAX) >> 14;
      seq.synth.getParams(i).envelopeDiffR = (- ENV_VALUE_MAX) >> 16;
      seq.synth.getParams(i).modLevel0 = MOD_LEVEL_MAX * 4;
      seq.synth.getParams(i).levelL = PART_VOLUME;
      seq.synth.getParams(i).levelR = PART_VOLUME;
      seq.synth.getParams(i).levelRev = REVERB_VOLUME;
    }
    for (i = 0; i < CHANNELS; i++)
    {
      osc = i * OPS;
      seq.synth.getParams(osc).modPatch0 = osc + 1;
      seq.synth.getParams(osc + 1).modPatch0 = osc + 1;
      seq.synth.getParams(osc + 1).modLevel0 = MOD_LEVEL_MAX * 1;
      seq.synth.getParams(osc + 1).mixOut = false;
      seq.synth.getParams(osc + 1).envelopeDiffD = (- ENV_VALUE_MAX) >> 13;
      seq.setToneData(i, 1, 1);
    }
    seq.synth.getParams(2).levelL = PART_VOLUME >> 1;
    seq.synth.getParams(2).levelR = PART_VOLUME;
    seq.synth.getParams(4).levelL = PART_VOLUME;
    seq.synth.getParams(4).levelR = PART_VOLUME >> 1;
    self.start();
  };

  var evKeyUp = function(ev)
  {
    if ((ev.key === 'Escape') || (ev.key === 'Esc'))
    {
      self.stop();
    }
  };

  var evClick = function(ev)
  {
    self.stop();
  };
}

var main = new Main();
synth.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Generative Music and Graphic Art</title>
<link rel="stylesheet" type="text/css" href="synth.css">
<script src="stlib.js"></script>
<script src="synth.js"></script>
<script src="sequencer.js"></script>
<script src="webgl_lib.js"></script>
<script src="visualizer.js"></script>
<script src="main.js"></script>
</head>
<body>

<div class="topmenu">
<div>
<span class="toplogo">Generative Music and Graphic Art</span><br>
<span class="toplink"><span class="barcolor">|</span> <span class="button" onclick="main.playEuphoria();">&quot;Euphoria&quot;</span> <span class="barcolor">|</span> <span class="button" onclick="main.playPentagon();">&quot;Pentagon&quot;</span> <span class="barcolor">|</span> <span class="button" onclick="main.playMonologue();">&quot;Monologue&quot;</span> <span class="barcolor">|</span> <span class="button" onclick="main.playLuna();">&quot;Luna&quot;</span> <span class="barcolor">|</span> <span class="button" onclick="main.playIntoxication();">&quot;Intoxication&quot;</span> <span class="barcolor">|</span><br>
<span class="barcolor">|</span> <span class="button" onclick="toggleFullScreen();">Full Screen</span> <span class="barcolor">|</span>&nbsp;&nbsp;&nbsp;&nbsp;Exit: Esc / Click<br>
&copy; 2017-2019 miya</span></span>
</div>
</div>

</body>
</html>
synth.css
@charset "utf-8";
/* (C) miya. All rights reserved. */

html {
margin: 0;
padding: 0;
height: 100%;
}

body {
margin: 0;
padding: 0;
font-size: 1rem;
background-color: #000000;
height: 100%;
}

canvas {
margin: 0;
padding: 0;
vertical-align: bottom;
}

div.topmenu {
text-align: center;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
color: #ffffff;
}

span.toplogo {
font-family: "Times New Roman", Times, serif;
font-size: 2rem;
}

span.toplink {
font-family: "Times New Roman", Times, serif;
font-size: 1rem;
}

span.barcolor {
font-weight: bold;
color: #0080ff;
}

span.button {
cursor: pointer;
white-space: nowrap;
}

span.button:hover {
text-decoration: underline;
}

ダウンロード:synth_js.tar.gz