831 lines
30 KiB
HTML
831 lines
30 KiB
HTML
|
<!DOCTYPE html>
|
||
|
<html><head>
|
||
|
<meta name="viewport" content=user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
|
||
|
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
||
|
<title>Musical Keyboard - JS Dynamic Audio Synth</title>
|
||
|
<meta name="description" content="A keyboard! Play with it. ;) A synthesizer created entirely in HTML5 + JS that dynamically generates modulated Waveform Audio dataURIs.">
|
||
|
<meta name="keywords" content="keith, keith horwood, keithwhor, social media, uwo, westernu, start-up, corporation, CEO, science, technology, blog, youtube, dataURI, javascript, JS, java, HTML5, piano, keyboard, HTML5 piano, HTML5 keyboard, waveform audio, .wav, .wav audio, audio">
|
||
|
<meta name="author" content="Keith William Horwood">
|
||
|
<meta name="robots" content="index,follow,archive">
|
||
|
<meta property="og:description" content="A keyboard! Play with it. ;) A synthesizer created entirely in HTML5 + JS that dynamically generates modulated Waveform Audio dataURIs.">
|
||
|
<meta property="og:image" content="http://keithwhor.com/music/opengraph.png">
|
||
|
<style>
|
||
|
html{overflow:hidden}
|
||
|
#mus { font-family: Helvetica; color: rgb(32,32,32); padding:0;height:100%;width:100%;
|
||
|
background:url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAYAAAAGCAYAAADgzO9IAAAAM0lEQVQIW2P8DwQMMPDwIAODvD2YxwiXQBJESKAJQiQeHPgP0w43EsUoZFGgCQg70BwAANTgIZHym5jNAAAAAElFTkSuQmCC') repeat;z-index:2
|
||
|
}
|
||
|
|
||
|
b { font-weight: bold; }
|
||
|
|
||
|
.key { position: absolute; font-family: Helvetica; font-weight: 100; font-size: 12px;
|
||
|
border: 1px solid rgba(32,32,32,0.2);
|
||
|
border-radius: 0px 0px 5px 5px;
|
||
|
cursor:pointer;
|
||
|
box-shadow: 0px 5px 1px rgba(32,32,32,0.2);
|
||
|
-webkit-transition: margin 0.05s ease, background-color 0.05s ease, box-shadow 0.05s ease; }
|
||
|
.key:hover { background-color: rgb(255,192,32); }
|
||
|
|
||
|
.key .label { position: absolute; bottom: 5px; text-align: center; left: 0px; right: 0px; }
|
||
|
|
||
|
.black { background-color: rgb(32,32,32); color: #ffffff; z-index: 1; text-shadow: 0px -1px 1px rgba(255,255,255,0.5); }
|
||
|
|
||
|
.white { background-color: #ffffff; color: rgb(32,32,32); z-index: 0; text-shadow: 0px 1px 1px rgba(32,32,32,0.5); }
|
||
|
|
||
|
.title { text-shadow: 0px 1px 1px rgba(32,32,32,0.2); font-size: 40px; font-weight: bold; font-family: Helvetica; padding: 10px; text-align: center; }
|
||
|
|
||
|
.sub { color: rgb(96,96,96); font-size: 30px; padding: 10px; font-weight:100; margin:10px 0px; text-shadow: 0px 1px 1px rgba(32,32,32,0.2); text-align:center; }
|
||
|
.sub a, .sub a:link, .sub a:visited, .sub a:active { font-weight:bold; color: rgb(128,160,255); text-decoration: none; }
|
||
|
.sub a:hover { color: rgb(160,192,255); }
|
||
|
|
||
|
.source a { color: rgb(255,96,96); }
|
||
|
.source a:link, .source a:visited, .source a:active { color: rgb(255,96,96); }
|
||
|
.source a:hover { color: rgb(255,128,128); }
|
||
|
|
||
|
.small { font-size: 20px; }
|
||
|
|
||
|
.keyboard-options { margin: 0; width: auto; text-align: center; font-size: 12px; font-weight: 200; padding:10px; }
|
||
|
|
||
|
.keyboard-holder { margin: 30px auto; height: 200px; position:relative; user-select:none; -webkit-user-select:none;-moz-user-select:none;-o-user-select:none; }
|
||
|
|
||
|
.about { position: relative; max-width: 700px; margin: 30px auto; }
|
||
|
.about .header { background-color:rgba(32,64,128,0.5); border-radius: 10px 10px 0px 0px; color:rgb(255,255,255); text-shadow:0px 1px 0px rgb(96,96,96);
|
||
|
position: relative; max-width: 600px; margin: 0 auto;
|
||
|
font-size: 30px; font-weight: bold; padding: 20px; text-align:center; }
|
||
|
.about .contents { font-size: 16px; line-height: 20px; background-color: rgb(255,255,255); font-weight: 200; padding: 20px; text-align: left; position: relative;
|
||
|
color: rgb(32,32,32); text-shadow: 0px 1px 0px rgb(224,224,224);
|
||
|
box-shadow: 0px 5px 10px rgba(32,32,32,0.5); -webkit-box-shadow: 0px 5px 10px rgba(32,32,32,0.5); border: 1px solid rgb(192,192,192); }
|
||
|
.about .footer { background-color:rgba(32,64,128,0.5); border-radius: 0px 0px 10px 10px; color:rgb(255,255,255);
|
||
|
position: relative; max-width: 600px; margin: 0 auto; font-weight: bold; padding: 20px; }
|
||
|
|
||
|
.about a, .about a:link, .about a:visited, .about a:active { font-weight:bold; color: rgb(224,96,32); text-decoration: none; }
|
||
|
.about a:hover { color: rgb(224,128,64); }
|
||
|
|
||
|
.code { border: 1px solid rgba(32,32,32,0.2); color: rgb(32,32,32); font-family: Courier New, Courier, monospace; font-size: 12px; white-space:pre; padding: 10px; margin: 10px; }
|
||
|
|
||
|
.image { border: 1px solid rgba(32,32,32,0.2); color: rgb(32,32,32); font-family: Courier New, Courier, monospace; font-size: 12px; white-space:pre; padding: 10px; margin: 10px; text-align: center; }
|
||
|
|
||
|
</style>
|
||
|
|
||
|
<script >
|
||
|
|
||
|
var Synth, AudioSynth, AudioSynthInstrument;
|
||
|
!function(){
|
||
|
var _encapsulated = false;
|
||
|
var AudioSynthInstance = null;
|
||
|
var pack = function(c,arg){return [String.fromCharCode(arg&255,(arg>>8)&255),String.fromCharCode(arg&255,(arg>>8)&255,(arg>>16)&255,(arg>>24)&255)][c];};
|
||
|
var setPrivateVar = function(n,v,w,e){Object.defineProperty(this,n,{value:v,writable:!!w,enumerable:!!e});};
|
||
|
var setPublicVar = function(n,v,w){setPrivateVar.call(this,n,v,w,true);};
|
||
|
AudioSynthInstrument = function AudioSynthInstrument(){this.__init__.apply(this,arguments);};
|
||
|
var setPriv = setPrivateVar.bind(AudioSynthInstrument.prototype);
|
||
|
var setPub = setPublicVar.bind(AudioSynthInstrument.prototype);
|
||
|
setPriv('__init__', function(a,b,c) {
|
||
|
if(!_encapsulated) { throw new Error('AudioSynthInstrument can only be instantiated from the createInstrument method of the AudioSynth object.'); }
|
||
|
setPrivateVar.call(this, '_parent', a);
|
||
|
setPublicVar.call(this, 'name', b);
|
||
|
setPrivateVar.call(this, '_soundID', c);
|
||
|
});
|
||
|
setPub('play', function(note, octave, duration) {
|
||
|
return this._parent.play(this._soundID, note, octave, duration);
|
||
|
});
|
||
|
setPub('generate', function(note, octave, duration) {
|
||
|
return this._parent.generate(this._soundID, note, octave, duration);
|
||
|
});
|
||
|
AudioSynth = function AudioSynth(){if(AudioSynthInstance instanceof AudioSynth){return AudioSynthInstance;}else{ this.__init__(); return this; }};
|
||
|
setPriv = setPrivateVar.bind(AudioSynth.prototype);
|
||
|
setPub = setPublicVar.bind(AudioSynth.prototype);
|
||
|
setPriv('_debug',false,true);
|
||
|
setPriv('_bitsPerSample',16);
|
||
|
setPriv('_channels',1);
|
||
|
setPriv('_sampleRate',44100,true);
|
||
|
setPub('setSampleRate', function(v) {
|
||
|
this._sampleRate = Math.max(Math.min(v|0,44100), 4000);
|
||
|
this._clearCache();
|
||
|
return this._sampleRate;
|
||
|
});
|
||
|
setPub('getSampleRate', function() { return this._sampleRate; });
|
||
|
setPriv('_volume',32768,true);
|
||
|
setPub('setVolume', function(v) {
|
||
|
v = parseFloat(v); if(isNaN(v)) { v = 0; }
|
||
|
v = Math.round(v*32768);
|
||
|
this._volume = Math.max(Math.min(v|0,32768), 0);
|
||
|
this._clearCache();
|
||
|
return this._volume;
|
||
|
});
|
||
|
setPub('getVolume', function() { return Math.round(this._volume/32768*10000)/10000; });
|
||
|
setPriv('_notes',{'C':261.63,'C#':277.18,'D':293.66,'D#':311.13,'E':329.63,'F':346.23,'F#':369.99,'G':392.00,'G#':415.30,'A':440.00,'A#':466.16,'B':493.88});
|
||
|
setPriv('_fileCache',[],true);
|
||
|
setPriv('_temp',{},true);
|
||
|
setPriv('_sounds',[],true);
|
||
|
setPriv('_mod',[function(i,s,f,x){return Math.sin((2 * Math.PI)*(i/s)*f+x);}]);
|
||
|
setPriv('_resizeCache', function() {
|
||
|
var f = this._fileCache;
|
||
|
var l = this._sounds.length;
|
||
|
while(f.length<l) {
|
||
|
var octaveList = [];
|
||
|
for(var i = 0; i < 8; i++) {
|
||
|
var noteList = {};
|
||
|
for(var k in this._notes) {
|
||
|
noteList[k] = {};
|
||
|
}
|
||
|
octaveList.push(noteList);
|
||
|
}
|
||
|
f.push(octaveList);
|
||
|
}
|
||
|
});
|
||
|
setPriv('_clearCache', function() {
|
||
|
this._fileCache = [];
|
||
|
this._resizeCache();
|
||
|
});
|
||
|
setPub('generate', function(sound, note, octave, duration) {
|
||
|
var thisSound = this._sounds[sound];
|
||
|
if(!thisSound) {
|
||
|
for(var i=0;i<this._sounds.length;i++) {
|
||
|
if(this._sounds[i].name==sound) {
|
||
|
thisSound = this._sounds[i];
|
||
|
sound = i;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
if(!thisSound) { throw new Error('Invalid sound or sound ID: ' + sound); }
|
||
|
var t = (new Date).valueOf();
|
||
|
this._temp = {};
|
||
|
octave |= 0;
|
||
|
octave = Math.min(8, Math.max(1, octave));
|
||
|
var time = !duration?2:parseFloat(duration);
|
||
|
if(typeof(this._notes[note])=='undefined') { throw new Error(note + ' is not a valid note.'); }
|
||
|
if(typeof(this._fileCache[sound][octave-1][note][time])!='undefined') {
|
||
|
if(this._debug) { console.log((new Date).valueOf() - t, 'ms to retrieve (cached)'); }
|
||
|
return this._fileCache[sound][octave-1][note][time];
|
||
|
} else {
|
||
|
var frequency = this._notes[note] * Math.pow(2,octave-4);
|
||
|
var data = [];
|
||
|
var sampleRate = this._sampleRate;
|
||
|
var volume = this._volume;
|
||
|
var channels = this._channels;
|
||
|
var bitsPerSample = this._bitsPerSample;
|
||
|
var attack = thisSound.attack(sampleRate, frequency, volume);
|
||
|
var dampen = thisSound.dampen(sampleRate, frequency, volume);
|
||
|
var wave = thisSound.wave.bind({modulate: this._mod, vars: this._temp});
|
||
|
var val = 0;
|
||
|
var curVol = 0;
|
||
|
|
||
|
for (var i = 0; i < (sampleRate * time); i++) {
|
||
|
if(i<=sampleRate*attack) {
|
||
|
curVol = volume * (i/(sampleRate*attack));
|
||
|
} else {
|
||
|
curVol = volume * Math.pow((1-((i-(sampleRate*attack))/(sampleRate*(time-attack)))),dampen);
|
||
|
}
|
||
|
|
||
|
val = curVol * Math.min(Math.max(wave(i, sampleRate, frequency, volume), -1), 1);
|
||
|
val = String.fromCharCode(val&255, (val>>>8)&255);
|
||
|
data.push(val);
|
||
|
}
|
||
|
|
||
|
data = data.join('');
|
||
|
|
||
|
// Format sub-chunk
|
||
|
var chunk1 = [
|
||
|
'fmt ', // Sub-chunk identifier
|
||
|
pack(1, 16), // Chunk length
|
||
|
pack(0, 1), // Audio format (1 is linear quantization)
|
||
|
pack(0, channels),
|
||
|
pack(1, sampleRate),
|
||
|
pack(1, sampleRate * channels * bitsPerSample / 8), // Byte rate
|
||
|
pack(0, channels * bitsPerSample / 8),
|
||
|
pack(0, bitsPerSample)
|
||
|
].join('');
|
||
|
// Data sub-chunk (contains the sound)
|
||
|
var chunk2 = [
|
||
|
'data', // Sub-chunk identifier
|
||
|
pack(1, data.length * channels * bitsPerSample / 8), // Chunk length
|
||
|
data
|
||
|
].join('');
|
||
|
// Header
|
||
|
var header = [
|
||
|
'RIFF',
|
||
|
pack(1, 4 + (8 + chunk1.length) + (8 + chunk2.length)), // Length
|
||
|
'WAVE'
|
||
|
].join('');
|
||
|
var out = [header, chunk1, chunk2].join('');
|
||
|
var dataURI = 'data:audio/wav;base64,' + escape(window.btoa(out));
|
||
|
this._fileCache[sound][octave-1][note][time] = dataURI;
|
||
|
if(this._debug) { console.log((new Date).valueOf() - t, 'ms to generate'); }
|
||
|
return dataURI;
|
||
|
}
|
||
|
});
|
||
|
setPub('play', function(note, octave, duration) {
|
||
|
var src = this.generate(note, octave, duration);
|
||
|
var audio = new Audio(src);
|
||
|
audio.addEventListener('ended', function() { audio = null; });
|
||
|
audio.autoplay = true;
|
||
|
audio.setAttribute('type', 'audio/wav');
|
||
|
return true;
|
||
|
});
|
||
|
setPub('debug', function() { this._debug = true; });
|
||
|
setPub('createInstrument', function(sound) {
|
||
|
var n = 0;
|
||
|
var found = false;
|
||
|
if(typeof(sound)=='string') {
|
||
|
for(var i=0;i<this._sounds.length;i++) {
|
||
|
if(this._sounds[i].name==sound) {
|
||
|
found = true;
|
||
|
n = i;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
} else {
|
||
|
if(this._sounds[sound]) {
|
||
|
n = sound;
|
||
|
sound = this._sounds[n].name;
|
||
|
found = true;
|
||
|
}
|
||
|
}
|
||
|
if(!found) { throw new Error('Invalid sound or sound ID: ' + sound); }
|
||
|
_encapsulated = true;
|
||
|
var ins = new AudioSynthInstrument(this, sound, n);
|
||
|
_encapsulated = false;
|
||
|
return ins;
|
||
|
});
|
||
|
setPub('listSounds', function() {
|
||
|
var r = [];
|
||
|
for(var i=0;i<this._sounds.length;i++) {
|
||
|
r.push(this._sounds[i].name);
|
||
|
}
|
||
|
return r;
|
||
|
});
|
||
|
setPriv('__init__', function(){
|
||
|
this._resizeCache();
|
||
|
});
|
||
|
setPub('loadSoundProfile', function() {
|
||
|
for(var i=0,len=arguments.length;i<len;i++) {
|
||
|
o = arguments[i];
|
||
|
if(!(o instanceof Object)) { throw new Error('Invalid sound profile.'); }
|
||
|
this._sounds.push(o);
|
||
|
}
|
||
|
this._resizeCache();
|
||
|
return true;
|
||
|
});
|
||
|
setPub('loadModulationFunction', function() {
|
||
|
for(var i=0,len=arguments.length;i<len;i++) {
|
||
|
f = arguments[i];
|
||
|
if(typeof(f)!='function') { throw new Error('Invalid modulation function.'); }
|
||
|
this._mod.push(f);
|
||
|
}
|
||
|
return true;
|
||
|
});
|
||
|
AudioSynthInstance = new AudioSynth();
|
||
|
Synth = AudioSynthInstance;
|
||
|
}();
|
||
|
|
||
|
Synth.loadModulationFunction(
|
||
|
function(i, sampleRate, frequency, x) { return 1 * Math.sin(2 * Math.PI * ((i / sampleRate) * frequency) + x); },
|
||
|
function(i, sampleRate, frequency, x) { return 1 * Math.sin(4 * Math.PI * ((i / sampleRate) * frequency) + x); },
|
||
|
function(i, sampleRate, frequency, x) { return 1 * Math.sin(8 * Math.PI * ((i / sampleRate) * frequency) + x); },
|
||
|
function(i, sampleRate, frequency, x) { return 1 * Math.sin(0.5 * Math.PI * ((i / sampleRate) * frequency) + x); },
|
||
|
function(i, sampleRate, frequency, x) { return 1 * Math.sin(0.25 * Math.PI * ((i / sampleRate) * frequency) + x); },
|
||
|
function(i, sampleRate, frequency, x) { return 0.5 * Math.sin(2 * Math.PI * ((i / sampleRate) * frequency) + x); },
|
||
|
function(i, sampleRate, frequency, x) { return 0.5 * Math.sin(4 * Math.PI * ((i / sampleRate) * frequency) + x); },
|
||
|
function(i, sampleRate, frequency, x) { return 0.5 * Math.sin(8 * Math.PI * ((i / sampleRate) * frequency) + x); },
|
||
|
function(i, sampleRate, frequency, x) { return 0.5 * Math.sin(0.5 * Math.PI * ((i / sampleRate) * frequency) + x); },
|
||
|
function(i, sampleRate, frequency, x) { return 0.5 * Math.sin(0.25 * Math.PI * ((i / sampleRate) * frequency) + x); }
|
||
|
);
|
||
|
|
||
|
Synth.loadSoundProfile({
|
||
|
name: 'piano',
|
||
|
attack: function() { return 0.002; },
|
||
|
dampen: function(sampleRate, frequency, volume) {
|
||
|
return Math.pow(0.5*Math.log((frequency*volume)/sampleRate),2);
|
||
|
},
|
||
|
wave: function(i, sampleRate, frequency, volume) {
|
||
|
var base = this.modulate[0];
|
||
|
return this.modulate[1](
|
||
|
i,
|
||
|
sampleRate,
|
||
|
frequency,
|
||
|
Math.pow(base(i, sampleRate, frequency, 0), 2) +
|
||
|
(0.75 * base(i, sampleRate, frequency, 0.25)) +
|
||
|
(0.1 * base(i, sampleRate, frequency, 0.5))
|
||
|
);
|
||
|
}
|
||
|
},
|
||
|
{
|
||
|
name: 'organ',
|
||
|
attack: function() { return 0.3 },
|
||
|
dampen: function(sampleRate, frequency) { return 1+(frequency * 0.01); },
|
||
|
wave: function(i, sampleRate, frequency) {
|
||
|
var base = this.modulate[0];
|
||
|
return this.modulate[1](
|
||
|
i,
|
||
|
sampleRate,
|
||
|
frequency,
|
||
|
base(i, sampleRate, frequency, 0) +
|
||
|
0.5*base(i, sampleRate, frequency, 0.25) +
|
||
|
0.25*base(i, sampleRate, frequency, 0.5)
|
||
|
);
|
||
|
}
|
||
|
},
|
||
|
{
|
||
|
name: 'acoustic',
|
||
|
attack: function() { return 0.002; },
|
||
|
dampen: function() { return 1; },
|
||
|
wave: function(i, sampleRate, frequency) {
|
||
|
|
||
|
var vars = this.vars;
|
||
|
vars.valueTable = !vars.valueTable?[]:vars.valueTable;
|
||
|
if(typeof(vars.playVal)=='undefined') { vars.playVal = 0; }
|
||
|
if(typeof(vars.periodCount)=='undefined') { vars.periodCount = 0; }
|
||
|
|
||
|
var valueTable = vars.valueTable;
|
||
|
var playVal = vars.playVal;
|
||
|
var periodCount = vars.periodCount;
|
||
|
|
||
|
var period = sampleRate/frequency;
|
||
|
var p_hundredth = Math.floor((period-Math.floor(period))*100);
|
||
|
|
||
|
var resetPlay = false;
|
||
|
|
||
|
if(valueTable.length<=Math.ceil(period)) {
|
||
|
|
||
|
valueTable.push(Math.round(Math.random())*2-1);
|
||
|
|
||
|
return valueTable[valueTable.length-1];
|
||
|
|
||
|
} else {
|
||
|
|
||
|
valueTable[playVal] = (valueTable[playVal>=(valueTable.length-1)?0:playVal+1] + valueTable[playVal]) * 0.5;
|
||
|
|
||
|
if(playVal>=Math.floor(period)) {
|
||
|
if(playVal<Math.ceil(period)) {
|
||
|
if((periodCount%100)>=p_hundredth) {
|
||
|
// Reset
|
||
|
resetPlay = true;
|
||
|
valueTable[playVal+1] = (valueTable[0] + valueTable[playVal+1]) * 0.5;
|
||
|
vars.periodCount++;
|
||
|
}
|
||
|
} else {
|
||
|
resetPlay = true;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
var _return = valueTable[playVal];
|
||
|
if(resetPlay) { vars.playVal = 0; } else { vars.playVal++; }
|
||
|
|
||
|
return _return;
|
||
|
|
||
|
}
|
||
|
}
|
||
|
},
|
||
|
{
|
||
|
name: 'edm',
|
||
|
attack: function() { return 0.002; },
|
||
|
dampen: function() { return 1; },
|
||
|
wave: function(i, sampleRate, frequency) {
|
||
|
var base = this.modulate[0];
|
||
|
var mod = this.modulate.slice(1);
|
||
|
return mod[0](
|
||
|
i,
|
||
|
sampleRate,
|
||
|
frequency,
|
||
|
mod[9](
|
||
|
i,
|
||
|
sampleRate,
|
||
|
frequency,
|
||
|
mod[2](
|
||
|
i,
|
||
|
sampleRate,
|
||
|
frequency,
|
||
|
Math.pow(base(i, sampleRate, frequency, 0), 3) +
|
||
|
Math.pow(base(i, sampleRate, frequency, 0.5), 5) +
|
||
|
Math.pow(base(i, sampleRate, frequency, 1), 7)
|
||
|
)
|
||
|
) +
|
||
|
mod[8](
|
||
|
i,
|
||
|
sampleRate,
|
||
|
frequency,
|
||
|
base(i, sampleRate, frequency, 1.75)
|
||
|
)
|
||
|
);
|
||
|
}
|
||
|
});
|
||
|
|
||
|
</script>
|
||
|
<script>
|
||
|
|
||
|
function AudioSynthView() {
|
||
|
|
||
|
var isMobile = !!navigator.userAgent.match(/Android|BlackBerry|iPhone|iPad|iPod|Opera Mini|IEMobile/i);
|
||
|
if(isMobile) { var evtListener = ['touchstart', 'touchend']; } else { var evtListener = ['mousedown', 'mouseup']; }
|
||
|
|
||
|
var __audioSynth = new AudioSynth();
|
||
|
__audioSynth.setVolume(0.5);
|
||
|
var __octave = 4;
|
||
|
|
||
|
// Change octave
|
||
|
var fnChangeOctave = function(x) {
|
||
|
|
||
|
x |= 0;
|
||
|
|
||
|
__octave += x;
|
||
|
|
||
|
__octave = Math.min(5, Math.max(3, __octave));
|
||
|
|
||
|
var octaveName = document.getElementsByName('OCTAVE_LABEL');
|
||
|
var i = octaveName.length;
|
||
|
while(i--) {
|
||
|
var val = parseInt(octaveName[i].getAttribute('value'));
|
||
|
octaveName[i].innerHTML = (val + __octave);
|
||
|
}
|
||
|
|
||
|
document.getElementById('OCTAVE_LOWER').innerHTML = __octave-1;
|
||
|
document.getElementById('OCTAVE_UPPER').innerHTML = __octave+1;
|
||
|
|
||
|
};
|
||
|
|
||
|
// Key bindings, notes to keyCodes.
|
||
|
var keyboard = {
|
||
|
|
||
|
/* 2 */
|
||
|
50: 'C#,-1',
|
||
|
|
||
|
/* 3 */
|
||
|
51: 'D#,-1',
|
||
|
|
||
|
/* 5 */
|
||
|
53: 'F#,-1',
|
||
|
|
||
|
/* 6 */
|
||
|
54: 'G#,-1',
|
||
|
|
||
|
/* 7 */
|
||
|
55: 'A#,-1',
|
||
|
|
||
|
/* 9 */
|
||
|
57: 'C#,0',
|
||
|
|
||
|
/* 0 */
|
||
|
48: 'D#,0',
|
||
|
|
||
|
/* + */
|
||
|
187: 'F#,0',
|
||
|
61: 'F#,0',
|
||
|
|
||
|
/* Q */
|
||
|
81: 'C,-1',
|
||
|
|
||
|
/* W */
|
||
|
87: 'D,-1',
|
||
|
|
||
|
/* E */
|
||
|
69: 'E,-1',
|
||
|
|
||
|
/* R */
|
||
|
82: 'F,-1',
|
||
|
|
||
|
/* T */
|
||
|
84: 'G,-1',
|
||
|
|
||
|
/* Y */
|
||
|
89: 'A,-1',
|
||
|
|
||
|
/* U */
|
||
|
85: 'B,-1',
|
||
|
|
||
|
/* I */
|
||
|
73: 'C,0',
|
||
|
|
||
|
/* O */
|
||
|
79: 'D,0',
|
||
|
|
||
|
/* P */
|
||
|
80: 'E,0',
|
||
|
|
||
|
/* [ */
|
||
|
219: 'F,0',
|
||
|
|
||
|
/* ] */
|
||
|
221: 'G,0',
|
||
|
|
||
|
/* A */
|
||
|
65: 'G#,0',
|
||
|
|
||
|
/* S */
|
||
|
83: 'A#,0',
|
||
|
|
||
|
/* F */
|
||
|
70: 'C#,1',
|
||
|
|
||
|
/* G */
|
||
|
71: 'D#,1',
|
||
|
|
||
|
/* J */
|
||
|
74: 'F#,1',
|
||
|
|
||
|
/* K */
|
||
|
75: 'G#,1',
|
||
|
|
||
|
/* L */
|
||
|
76: 'A#,1',
|
||
|
|
||
|
/* Z */
|
||
|
90: 'A,0',
|
||
|
|
||
|
/* X */
|
||
|
88: 'B,0',
|
||
|
|
||
|
/* C */
|
||
|
67: 'C,1',
|
||
|
|
||
|
/* V */
|
||
|
86: 'D,1',
|
||
|
|
||
|
/* B */
|
||
|
66: 'E,1',
|
||
|
|
||
|
/* N */
|
||
|
78: 'F,1',
|
||
|
|
||
|
/* M */
|
||
|
77: 'G,1',
|
||
|
|
||
|
/* , */
|
||
|
188: 'A,1',
|
||
|
|
||
|
/* . */
|
||
|
190: 'B,1'
|
||
|
|
||
|
};
|
||
|
|
||
|
var reverseLookupText = {};
|
||
|
var reverseLookup = {};
|
||
|
|
||
|
// Create a reverse lookup table.
|
||
|
for(var i in keyboard) {
|
||
|
|
||
|
var val;
|
||
|
|
||
|
switch(i|0) {
|
||
|
|
||
|
case 187:
|
||
|
val = 61;
|
||
|
break;
|
||
|
|
||
|
case 219:
|
||
|
val = 91;
|
||
|
break;
|
||
|
|
||
|
case 221:
|
||
|
val = 93;
|
||
|
break;
|
||
|
|
||
|
case 188:
|
||
|
val = 44;
|
||
|
break;
|
||
|
|
||
|
case 190:
|
||
|
val = 46;
|
||
|
break;
|
||
|
|
||
|
default:
|
||
|
val = i;
|
||
|
break;
|
||
|
|
||
|
}
|
||
|
|
||
|
reverseLookupText[keyboard[i]] = val;
|
||
|
reverseLookup[keyboard[i]] = i;
|
||
|
|
||
|
}
|
||
|
|
||
|
// Keys you have pressed down.
|
||
|
var keysPressed = [];
|
||
|
var visualKeyboard = null;
|
||
|
var selectSound = null;
|
||
|
|
||
|
var fnCreateKeyboard = function(keyboardElement) {
|
||
|
// Generate keyboard
|
||
|
// This is our main keyboard element! It's populated dynamically based on what you've set above.
|
||
|
visualKeyboard = document.getElementById('keyboard');
|
||
|
selectSound = document.getElementById('sound');
|
||
|
|
||
|
var iKeys = 0;
|
||
|
var iWhite = 0;
|
||
|
var notes = __audioSynth._notes;
|
||
|
|
||
|
for(var i=-1;i<=1;i++) {
|
||
|
for(var n in notes) {
|
||
|
if(n[2]!='b') {
|
||
|
var thisKey = document.createElement('div');
|
||
|
if(n.length>1) {
|
||
|
thisKey.className = 'black key';
|
||
|
thisKey.style.width = '30px';
|
||
|
thisKey.style.height = '120px';
|
||
|
thisKey.style.left = (40 * (iWhite - 1)) + 25 + 'px';
|
||
|
} else {
|
||
|
thisKey.className = 'white key';
|
||
|
thisKey.style.width = '40px';
|
||
|
thisKey.style.height = '200px';
|
||
|
thisKey.style.left = 40 * iWhite + 'px';
|
||
|
iWhite++;
|
||
|
}
|
||
|
var label = document.createElement('div');
|
||
|
label.className = 'label';
|
||
|
label.innerHTML = '<b>' + String.fromCharCode(reverseLookupText[n + ',' + i]) + '</b>' + '<br /><br />' + n.substr(0,1) +
|
||
|
'<span name="OCTAVE_LABEL" value="' + i + '">' + (__octave + parseInt(i)) + '</span>' + (n.substr(1,1)?n.substr(1,1):'');
|
||
|
thisKey.appendChild(label);
|
||
|
thisKey.setAttribute('ID', 'KEY_' + n + ',' + i);
|
||
|
thisKey.addEventListener(evtListener[0], (function(_temp) { return function() { fnPlayKeyboard({keyCode:_temp}); } })(reverseLookup[n + ',' + i]));
|
||
|
visualKeyboard[n + ',' + i] = thisKey;
|
||
|
visualKeyboard.appendChild(thisKey);
|
||
|
iKeys++;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
visualKeyboard.style.width = iWhite * 40 + 'px';
|
||
|
|
||
|
window.addEventListener(evtListener[1], function() { n = keysPressed.length; while(n--) { fnRemoveKeyBinding({keyCode:keysPressed[n]}); } });
|
||
|
|
||
|
};
|
||
|
|
||
|
// Creates our audio player
|
||
|
var fnPlayNote = function(note, octave) {
|
||
|
|
||
|
src = __audioSynth.generate(selectSound.value, note, octave, 2);
|
||
|
container = new Audio(src);
|
||
|
container.addEventListener('ended', function() { container = null; });
|
||
|
container.addEventListener('loadeddata', function(e) { e.target.play(); });
|
||
|
container.autoplay = false;
|
||
|
container.setAttribute('type', 'audio/wav');
|
||
|
/*document.body.appendChild(container);*/
|
||
|
container.load();
|
||
|
return container;
|
||
|
|
||
|
};
|
||
|
|
||
|
// Detect keypresses, play notes.
|
||
|
|
||
|
var fnPlayKeyboard = function(e) {
|
||
|
|
||
|
var i = keysPressed.length;
|
||
|
while(i--) {
|
||
|
if(keysPressed[i]==e.keyCode) {
|
||
|
return false;
|
||
|
}
|
||
|
}
|
||
|
keysPressed.push(e.keyCode);
|
||
|
|
||
|
switch(e.keyCode) {
|
||
|
|
||
|
// left
|
||
|
case 37:
|
||
|
fnChangeOctave(-1);
|
||
|
break;
|
||
|
|
||
|
// right
|
||
|
case 39:
|
||
|
fnChangeOctave(1);
|
||
|
break;
|
||
|
|
||
|
// space
|
||
|
case 16:
|
||
|
break;
|
||
|
fnPlaySong([
|
||
|
['E,0', 8],
|
||
|
['D,0', 8],
|
||
|
['C,0', 2],
|
||
|
['C,0', 8],
|
||
|
['D,0', 8],
|
||
|
['C,0', 8],
|
||
|
['E,0', 8],
|
||
|
['D,0', 1],
|
||
|
['C,0', 8],
|
||
|
['D,0', 8],
|
||
|
['E,0', 2],
|
||
|
['A,0', 8],
|
||
|
['G,0', 8],
|
||
|
['E,0', 8],
|
||
|
['C,0', 8],
|
||
|
['D,0', 1],
|
||
|
['A,0', 8],
|
||
|
['B,0', 8],
|
||
|
['C,1', 2],
|
||
|
['B,0', 8],
|
||
|
['C,1', 8],
|
||
|
['D,1', 8],
|
||
|
['C,1', 8],
|
||
|
['A,0', 1],
|
||
|
['G,0', 8],
|
||
|
['A,0', 8],
|
||
|
['B,0', 2],
|
||
|
['C,1', 8],
|
||
|
['B,0', 8],
|
||
|
['A,0', 8],
|
||
|
['G,0', 8],
|
||
|
['A,0', 1]
|
||
|
]);
|
||
|
break;
|
||
|
|
||
|
}
|
||
|
|
||
|
if(keyboard[e.keyCode]) {
|
||
|
if(visualKeyboard[keyboard[e.keyCode]]) {
|
||
|
visualKeyboard[keyboard[e.keyCode]].style.backgroundColor = '#ff0000';
|
||
|
visualKeyboard[keyboard[e.keyCode]].style.marginTop = '5px';
|
||
|
visualKeyboard[keyboard[e.keyCode]].style.boxShadow = 'none';
|
||
|
}
|
||
|
var arrPlayNote = keyboard[e.keyCode].split(',');
|
||
|
var note = arrPlayNote[0];
|
||
|
var octaveModifier = arrPlayNote[1]|0;
|
||
|
fnPlayNote(note, __octave + octaveModifier);
|
||
|
} else {
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
// Remove key bindings once note is done.
|
||
|
|
||
|
var fnRemoveKeyBinding = function(e) {
|
||
|
|
||
|
var i = keysPressed.length;
|
||
|
while(i--) {
|
||
|
if(keysPressed[i]==e.keyCode) {
|
||
|
if(visualKeyboard[keyboard[e.keyCode]]) {
|
||
|
visualKeyboard[keyboard[e.keyCode]].style.backgroundColor = '';
|
||
|
visualKeyboard[keyboard[e.keyCode]].style.marginTop = '';
|
||
|
visualKeyboard[keyboard[e.keyCode]].style.boxShadow = '';
|
||
|
}
|
||
|
keysPressed.splice(i, 1);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
var fnPlaySong = function(arr) {
|
||
|
|
||
|
if(arr.length>0) {
|
||
|
|
||
|
var noteLen = 1000*(1/parseInt(arr[0][1]));
|
||
|
if(!(arr[0][0] instanceof Array)) {
|
||
|
arr[0][0] = [arr[0][0]];
|
||
|
}
|
||
|
var i = arr[0][0].length;
|
||
|
var keys = [];
|
||
|
while(i--) {
|
||
|
keys.unshift(reverseLookup[arr[0][0][i]]);
|
||
|
fnPlayKeyboard({keyCode:keys[0]});
|
||
|
}
|
||
|
arr.shift();
|
||
|
setTimeout(function(array, val){ return function() { var i = val.length; while(i--) { fnRemoveKeyBinding({keyCode:val[i]}); } fnPlaySong(array); } }(arr, keys), noteLen);
|
||
|
|
||
|
}
|
||
|
|
||
|
};
|
||
|
|
||
|
// Set up global event listeners
|
||
|
|
||
|
window.addEventListener('keydown', fnPlayKeyboard);
|
||
|
window.addEventListener('keyup', fnRemoveKeyBinding);
|
||
|
document.getElementById('-_OCTAVE').addEventListener('click', function() { fnChangeOctave(-1); });
|
||
|
document.getElementById('+_OCTAVE').addEventListener('click', function() { fnChangeOctave(1); });
|
||
|
|
||
|
Object.defineProperty(this, 'draw', {
|
||
|
value: fnCreateKeyboard
|
||
|
});
|
||
|
|
||
|
}
|
||
|
|
||
|
</script>
|
||
|
</head>
|
||
|
|
||
|
<div id="mus">
|
||
|
<div class="keyboard-options">
|
||
|
Sound
|
||
|
<select id="sound">
|
||
|
<option value="0" selected="selected">Keyboard</option>
|
||
|
<option value="1">Organ</option>
|
||
|
<option value="2">Acoustic Guitar</option>
|
||
|
<option value="3">EDM, bro!</option>
|
||
|
</select>
|
||
|
<div style="width: 840px;" id="keyboard" class="keyboard-holder"><div id="KEY_C,-1" style="width: 40px; height: 200px; left: 0px;" class="white key"><div class="label"><b>Q</b><br><br>C<span name="OCTAVE_LABEL" value="-1">3</span></div></div><div id="KEY_C#,-1" style="width: 30px; height: 120px; left: 25px;" class="black key"><div class="label"><b>2</b><br><br>C<span name="OCTAVE_LABEL" value="-1">3</span>#</div></div><div id="KEY_D,-1" style="width: 40px; height: 200px; left: 40px;" class="white key"><div class="label"><b>W</b><br><br>D<span name="OCTAVE_LABEL" value="-1">3</span></div></div><div id="KEY_D#,-1" style="width: 30px; height: 120px; left: 65px;" class="black key"><div class="label"><b>3</b><br><br>D<span name="OCTAVE_LABEL" value="-1">3</span>#</div></div><div id="KEY_E,-1" style="width: 40px; height: 200px; left: 80px;" class="white key"><div class="label"><b>E</b><br><br>E<span name="OCTAVE_LABEL" value="-1">3</span></div></div><div id="KEY_F,-1" style="width: 40px; height: 200px; left: 120px;" class="white key"><div class="label"><b>R</b><br><br>F<span name="OCTAVE_LABEL" value="-1">3</span></div></div><div id="KEY_F#,-1" style="width: 30px; height: 120px; left: 145px;" class="black key"><div class="label"><b>5</b><br><br>F<span name="OCTAVE_LABEL" value="-1">3</span>#</div></div><div id="KEY_G,-1" style="width: 40px; height: 200px; left: 160px;" class="white key"><div class="label"><b>T</b><br><br>G<span name="OCTAVE_LABEL" value="-1">3</span></div></div><div id="KEY_G#,-1" style="width: 30px; height: 120px; left: 185px;" class="black key"><div class="label"><b>6</b><br><br>G<span name="OCTAVE_LABEL" value="-1">3</span>#</div></div><div id="KEY_A,-1" style="width: 40px; height: 200px; left: 200px;" class="white key"><div class="label"><b>Y</b><br><br>A<span name="OCTAVE_LABEL" value="-1">3</span></div></div><div id="KEY_A#,-1" style="width: 30px; height: 120px; left: 225px;" class="black key"><div class="label"><b>7</b><br><br>A<span name="OCTAVE_LABEL" value="-1">3</span>#</div></div><div id="KEY_B,-1" style="width: 40px; height: 200px; left: 240px;" class="white key"><div class="label"><b>U</b><br><br>B<span name="OCTAVE_LABEL" value="-1">3</span></div></div><div id="KEY_C,0" style="width: 40px; height: 200px; left: 280px;" class="white key"><div class="label"><b>I</b><br><br>C<span name="OCTAVE_LABEL" value="0">4</span></div></div><div id="KEY_C#,0" style="width: 30px; height: 120px; left: 305px;" class="black key"><div class="label"><b>9</b><br><br>C<span name="OCTAVE_LABEL" value="0">4</span>#</div></div><div id="KEY_D,0" style="width: 40px; height: 200px; left: 320px;" class="white key"><div class="label"><b>O</b><br><br>D<span name="OCTAVE_LABEL" value="0">4</span></div></div><div id="KEY_D#,0" style="width: 30px; height: 120px; left: 345px;" class="black key"><div class="label"><b>0</b><br><br>D<span name="OCTAVE_LABEL" value="0">4</span>#</div></div><div id="KEY_E,0" style="width: 40px; height: 200px; left: 360px;" class="white key"><div class="label"><b>P</b><br><br>E<span name="OCTAVE_LABEL" value="0">4</span></div></div><div id="KEY_F,0" style="width: 40px; height: 200px; left: 400px;" class="white key"><div class="label"><b>[</b><br><br>F<span name="OCTAVE_LABEL" value="0">4</span></div></div><div id="KEY_F#,0" style="width: 30px; height: 120px; left: 425px;" class="black key"><div class="label"><b>=</b><br><br>F<span name="OCTAVE_LABEL" value="0">4</span>#</div></div><div id="KEY_G,0" style="width: 40px; height: 200px; left: 440px;" class="white key"><div class="label"><b>]</b><br><br>G<span name="OCTAVE_LABEL" value="0">4</span></div></div><div id="KEY_G#,0" style="width: 30px; height: 120px; left: 465px;" class="black key"><div class="label"><b>A</b><br><br>G<span name="OCTAVE_LABEL" value="0">4</span>#</div></div><div id="KEY_A,0" style="width: 40px; height: 200px; left: 480px;" class="white key"><div class="label"><b>Z</b><br><br>A<span name="OCTAVE_LABEL" value="0">4</span></div></div><div id="KEY_A#,0" style="width: 30px; height: 120px; left: 505px;" class="black key"><div class="la
|
||
|
<div class="keyboard-options">
|
||
|
Range [C<span id="OCTAVE_LOWER">3</span>-B<span id="OCTAVE_UPPER">5</span>]
|
||
|
<input id="-_OCTAVE" value="-" type="button">
|
||
|
<input id="+_OCTAVE" value="+" type="button"><br>
|
||
|
<i>(Use left/right arrows to adjust with keyboard)</i>
|
||
|
</div>
|
||
|
|
||
|
<div class="footer"></div>
|
||
|
</div>
|
||
|
|
||
|
<script type="text/javascript">
|
||
|
|
||
|
var a = new AudioSynthView();
|
||
|
a.draw();
|
||
|
|
||
|
</script>
|
||
|
|
||
|
</div></html>
|