Web Audio APIに対応したウェブ・ブラウザーでこのページを開き、楽器名のボタンをクリックしてからキーボードで演奏します。 遅延が気になる場合は「Buffer Size」をノイズが出ない範囲で一番小さい値に設定してから再度楽器名のボタンをクリックして下さい。
Buffer Size:
キーボード対応表
ソースコード
これらのソースコードは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 = [];
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 KEYS = 29;
var synth = new Synthesizer();
var command = new Fifo();
var randf = new RandF();
var scaleBufferSize = 16;
var scaleTable = [];
var toneDetune = [];
var toneOct = [];
var toneScale = [];
var bufferSize = 0;
var operators = 1;
var keyStatus = [];
var evKeyUp = function(ev)
{
var i;
if ((ev.key === 'Escape') || (ev.key === 'Esc'))
{
self.stop();
}
var n = noteData[ev.key];
if (n)
{
for (i = 0; i < operators; i++)
{
var ch = n.n + 12 * n.o + i * KEYS;
synth.playNote(ch, false);
}
keyStatus[ev.key] = false;
}
};
var evKeyDown = function(ev)
{
var i, pitch;
var n = noteData[ev.key];
if (n)
{
if (keyStatus[ev.key] !== true)
{
for (i = 0; i < operators; i++)
{
var ch = n.n + 12 * n.o + i * KEYS;
pitch = Math.floor(scaleTable[n.n] * (1 << (n.o + toneOct[i])) * toneScale[i] + toneDetune[i] * (randf.get() * 0.4 + 0.8));
synth.getParams(ch).pitch = pitch;
synth.playNote(ch, true);
}
keyStatus[ev.key] = true;
}
}
};