Home記事一覧フォーラム

JavaScriptピアノ

【更新履歴】
2017/08/16 新規公開

JavaScriptで実装したピアノです。

Web Audio APIに対応したウェブ・ブラウザーでこのページを開き、楽器名のボタンをクリックしてからキーボードで演奏します。
遅延が気になる場合は「Buffer Size」をノイズが出ない範囲で一番小さい値に設定してから再度楽器名のボタンをクリックして下さい。

Buffer Size:

キーボード対応表


ソースコード

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

synth.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.
*/


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 DEFAULT_LEVEL = (Math.floor(FIXED_SCALE / 4));
  var bufferLength = 0;

  var oscs = 4;
  var reverbLLength = 0.5111;
  var reverbRLength = 0.5;
  var reverbLSize = 0;
  var reverbRSize = 0;
  var reverbVolume = (Math.floor(FIXED_SCALE * 0.3));
  var reverbDecay = 0.8;
  var outVolume = (1.0 / FIXED_SCALE);

  var self = this;
  var callback;
  var callbackRate = 4096;
  var callbackCounter = 0;
  var reverbCounter = 0;
  var reverbAddrL = 0;
  var reverbAddrR = 0;
  var outL = 0;
  var outR = 0;
  var waac = new (window.AudioContext || window.webkitAudioContext)();
  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.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.noteOnSave = 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;
    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 = DEFAULT_LEVEL;
      params[i].levelR = DEFAULT_LEVEL;
      params[i].levelRev = reverbVolume;
      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;
    reverbCounter = 0;
  };

  // from callback
  var render = function()
  {
    var i, waveAddrF, waveAddrR, oscOutF, oscOutR, waveAddrM, oscOut, limitValue, valueDiff, limitGt, 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
        if ((params[i].noteOn === true) && (params[i].noteOnSave !== params[i].noteOn))
        {
          params[i].state = STATE_ATTACK;
        }
        if ((params[i].noteOn === false) && (params[i].noteOnSave !== params[i].noteOn))
        {
          params[i].state = STATE_RELEASE;
        }
        params[i].noteOnSave = params[i].noteOn;

        if (params[i].state === STATE_SLEEP)
        {
          params[i].count = 0;
          activeCh[i] = false;
        }

        limitValue = 0;
        valueDiff = 0;
        limitGt = false;

        if (params[i].state === STATE_ATTACK)
        {
          limitValue = params[i].envelopeLevelA;
          valueDiff = params[i].envelopeDiffA;
          limitGt = true;
        }
        else if (params[i].state === STATE_DECAY)
        {
          limitValue = params[i].envelopeLevelS;
          valueDiff = params[i].envelopeDiffD;
          limitGt = false;
        }
        else if (params[i].state === STATE_RELEASE)
        {
          limitValue = 0;
          valueDiff = params[i].envelopeDiffR;
          limitGt = false;
        }

        params[i].currentLevel += valueDiff;

        if (((limitGt === true) && (params[i].currentLevel > limitValue)) ||
            ((limitGt === false) && (params[i].currentLevel < limitValue)))
        {
          params[i].currentLevel = limitValue;
          params[i].state++;
        }

        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()
  {
    var i;
    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.getOscs = function()
  {
    return oscs;
  };

  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, reverbVolume_arg, reverbDecay_arg, outVolume_arg, bufferLength_arg)
  {
    oscs = oscs_arg;
    reverbLLength = reverbLLength_arg;
    reverbRLength = reverbRLength_arg;
    reverbVolume = reverbVolume_arg;
    reverbDecay = reverbDecay_arg;
    outVolume = outVolume_arg;
    bufferLength = bufferLength_arg;
  };

  this.setActiveCh = function(ch, value)
  {
    activeCh[ch] = value;
  };
}
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, 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 SynthCommand(osc_arg, noteOn_arg, note_arg, oct_arg)
{
  this.osc = osc_arg;
  this.noteOn = noteOn_arg;
  this.note = note_arg;
  this.oct = oct_arg;
}

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 KEYS = 29;

  var synth = new Synthesizer();
  var command = new Fifo();
  var scaleBufferSize = 16;
  var scaleTable = [];
  var toneDetune = [];
  var toneOct = [];
  var bufferSize = 0;
  var operators = 1;

  var noteData = {
    z: {n:0, o:0},
    s: {n:1, o:0},
    x: {n:2, o:0},
    d: {n:3, o:0},
    c: {n:4, o:0},
    v: {n:5, o:0},
    g: {n:6, o:0},
    b: {n:7, o:0},
    h: {n:8, o:0},
    n: {n:9, o:0},
    j: {n:10, o:0},
    m: {n:11, o:0},
    q: {n:0, o:1},
    2: {n:1, o:1},
    w: {n:2, o:1},
    3: {n:3, o:1},
    e: {n:4, o:1},
    r: {n:5, o:1},
    5: {n:6, o:1},
    t: {n:7, o:1},
    6: {n:8, o:1},
    y: {n:9, o:1},
    7: {n:10, o:1},
    u: {n:11, o:1},
    i: {n:0, o:2},
    9: {n:1, o:2},
    o: {n:2, o:2},
    0: {n:3, o:2},
    p: {n:4, o:2}
  };

  // callback
  var processCommand = function(delaySamples)
  {
    while (true)
    {
      var i;
      var c = command.pop();
      var ch, note;
      if (c !== undefined)
      {
        for (i = 0; i < operators; i++)
        {
          ch = c.note + 12 * c.oct + i * KEYS;
          synth.getParams(ch).pitch = scaleTable[c.note] * (1 << (c.oct + toneOct[i])) + toneDetune[i];
          synth.getParams(ch).noteOn = c.noteOn;
          synth.setActiveCh(ch, true);
        }
      }
      else
      {
        break;
      }
    }
  };

  this.playBrass = function()
  {
    var i, j, k;
    var MOD_LEVEL_MAX = (Math.floor(FIXED_SCALE * 0.52));
    var REVERB_VOLUME = (Math.floor(FIXED_SCALE * 0.3));
    var OUT_VOLUME = (1.0 / FIXED_SCALE);
    var DEFAULT_LEVEL = (Math.floor(FIXED_SCALE / 6));
    operators = 2;
    var oscs = KEYS * operators;
    self.stop();
    init();
    command.init(256);
    synth.setSynthParam(oscs, 0.278639455782, 0.136533333333, REVERB_VOLUME, 0.5, OUT_VOLUME, bufferSize);
    synth.init();
    for (i = 0; i < KEYS; i++)
    {
      k = i;
      synth.getParams(k).envelopeLevelA = ENV_VALUE_MAX;
      synth.getParams(k).envelopeLevelS = ENV_VALUE_MAX;
      synth.getParams(k).envelopeDiffA = ENV_VALUE_MAX >> 9;
      synth.getParams(k).envelopeDiffD = (- ENV_VALUE_MAX) >> 18;
      synth.getParams(k).envelopeDiffR = (- ENV_VALUE_MAX) >> 12;
      synth.getParams(k).modLevel0 = MOD_LEVEL_MAX * 12;
      synth.getParams(k).modPatch0 = k + KEYS;
      synth.getParams(k).levelL = DEFAULT_LEVEL;
      synth.getParams(k).levelR = DEFAULT_LEVEL;
      synth.getParams(k).mixOut = true;
      k = i + KEYS;
      synth.getParams(k).envelopeLevelA = ENV_VALUE_MAX;
      synth.getParams(k).envelopeLevelS = Math.floor(ENV_VALUE_MAX * 0.5);
      synth.getParams(k).envelopeDiffA = ENV_VALUE_MAX >> 10;
      synth.getParams(k).envelopeDiffD = (- ENV_VALUE_MAX) >> 18;
      synth.getParams(k).envelopeDiffR = (- ENV_VALUE_MAX) >> 13;
      synth.getParams(k).modLevel0 = MOD_LEVEL_MAX * 5;
      synth.getParams(k).modPatch0 = k;
      synth.getParams(k).levelL = DEFAULT_LEVEL;
      synth.getParams(k).levelR = DEFAULT_LEVEL;
      synth.getParams(k).mixOut = false;
    }
    synth.setCallbackRate(bufferSize);
    synth.setCallback(processCommand);
    toneOct = [6, 6];
    for (i = 0; i < operators; i++)
    {
      toneDetune[i] = 0xffffffff / synth.getSampleRate() * (i * 2.0);
    }
    synth.start();
  };

  this.stop = function()
  {
    window.removeEventListener('keyup', evKeyUp);
    window.removeEventListener('keydown', evKeyDown);
    window.removeEventListener('click', evClick);
    synth.stop();
  };

  this.playOrgan = function()
  {
    var i, j, k;
    var MOD_LEVEL_MAX = (Math.floor(FIXED_SCALE * 0.52));
    var REVERB_VOLUME = (Math.floor(FIXED_SCALE * 0.3));
    var OUT_VOLUME = (1.0 / FIXED_SCALE);
    var DEFAULT_LEVEL = (Math.floor(FIXED_SCALE / 18));
    operators = 4;
    var oscs = KEYS * operators;
    self.stop();
    init();
    command.init(256);
    synth.setSynthParam(oscs, 0.278639455782, 0.136533333333, REVERB_VOLUME, 0.7, OUT_VOLUME, bufferSize);
    synth.init();
    for (i = 0; i < oscs; i++)
    {
      k = i;
      synth.getParams(k).envelopeLevelA = ENV_VALUE_MAX;
      synth.getParams(k).envelopeLevelS = ENV_VALUE_MAX;
      synth.getParams(k).envelopeDiffA = ENV_VALUE_MAX >> 9;
      synth.getParams(k).envelopeDiffD = (- ENV_VALUE_MAX) >> 12;
      synth.getParams(k).envelopeDiffR = (- ENV_VALUE_MAX) >> 12;
      synth.getParams(k).modLevel0 = MOD_LEVEL_MAX * 6;
      synth.getParams(k).modPatch0 = k;
      synth.getParams(k).levelL = DEFAULT_LEVEL;
      synth.getParams(k).levelR = DEFAULT_LEVEL;
      synth.getParams(k).mixOut = true;
    }
    synth.setCallbackRate(bufferSize);
    synth.setCallback(processCommand);
    for (i = 0; i < operators; i++)
    {
      toneOct[i] = 5 + i;
      toneDetune[i] = 0xffffffff / synth.getSampleRate() * (i * 1.234567);
    }
    synth.start();
  };

  this.playPiano = function()
  {
    var i, j, k;
    var MOD_LEVEL_MAX = (Math.floor(FIXED_SCALE * 0.52));
    var REVERB_VOLUME = (Math.floor(FIXED_SCALE * 0.3));
    var OUT_VOLUME = (1.0 / FIXED_SCALE);
    var DEFAULT_LEVEL = (Math.floor(FIXED_SCALE / 10));
    operators = 2;
    var oscs = KEYS * operators;
    self.stop();
    init();
    command.init(256);
    synth.setSynthParam(oscs, 0.278639455782, 0.136533333333, REVERB_VOLUME, 0.5, OUT_VOLUME, bufferSize);
    synth.init();
    for (i = 0; i < oscs; i++)
    {
      k = i;
      synth.getParams(k).envelopeLevelA = ENV_VALUE_MAX;
      synth.getParams(k).envelopeLevelS = 0;
      synth.getParams(k).envelopeDiffA = ENV_VALUE_MAX >> 5;
      synth.getParams(k).envelopeDiffD = (- ENV_VALUE_MAX) >> 18;
      synth.getParams(k).envelopeDiffR = (- ENV_VALUE_MAX) >> 14;
      synth.getParams(k).modLevel0 = MOD_LEVEL_MAX * 2;
      synth.getParams(k).modPatch0 = k;
      synth.getParams(k).levelL = DEFAULT_LEVEL;
      synth.getParams(k).levelR = DEFAULT_LEVEL;
      synth.getParams(k).mixOut = true;
    }
    synth.setCallbackRate(bufferSize);
    synth.setCallback(processCommand);
    for (i = 0; i < operators; i++)
    {
      toneOct[i] = 7;
      toneDetune[i] = 0xffffffff / synth.getSampleRate() * (i * 0.5);
    }
    synth.start();
  };

  this.stop = function()
  {
    window.removeEventListener('keyup', evKeyUp);
    window.removeEventListener('keydown', evKeyDown);
    window.removeEventListener('click', evClick);
    synth.stop();
  };

  this.setBufferSize = function(value)
  {
    bufferSize = value;
  };

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

    window.addEventListener('keyup', evKeyUp);
    window.addEventListener('keydown', evKeyDown);
    window.addEventListener('click', evClick);
  };

  var evKeyUp = function(ev)
  {
    if ((ev.key === 'Escape') || (ev.key === 'Esc'))
    {
      self.stop();
    }
    var n = noteData[ev.key];
    if (n)
    {
      command.push(new SynthCommand(n.n, false, n.n, n.o));
    }
  };

  var evKeyDown = function(ev)
  {
    var n = noteData[ev.key];
    if (n)
    {
      command.push(new SynthCommand(n.n, true, n.n, n.o));
    }
  };

  var evClick = function(ev)
  {
  };
}

var main = new Main();
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Piano</title>
<script src="stlib.js"></script>
<script src="synth.js"></script>
<script src="main.js"></script>
</head>
<body>

<h1>Piano</h1>

<input type="button" value="Brass" onclick="main.playBrass();" />
<input type="button" value="Organ" onclick="main.playOrgan();" />
<input type="button" value="Piano" onclick="main.playPiano();" />
<input type="button" value="stop" onclick="main.stop();" />
Buffer Size: <select name="buffersize" onchange="main.setBufferSize(this.value);">
<option value="0">Auto</option>
<option value="256">256</option>
<option value="512">512</option>
<option value="1024">1024</option>
<option value="2048">2048</option>
<option value="4096">4096</option>
<option value="8192">8192</option>
<option value="16384">16384</option>
</select>
<br />

</body>
</html>

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