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 = [];
/*
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 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;
}
}
};
/*
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.
*/
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.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;
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.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);
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);