(function() {
var onDOMContentLoaded = function() {
window.AudioContext = window.AudioContext || window.webkitAudioContext;
try {
// Create the instance of AudioContext
var context = new AudioContext();
} catch (error) {
window.alert(error.message + ' : Please use Chrome or Safari.');
return;
}
if (!navigator.requestMIDIAccess) {
window.alert('Cannot use Web MIDI API.');
return;
}
var displayProperties = function(node, tableid, caption) {
var html = '<caption>' + caption + '</caption>';
html += '<thead>';
html += '<tr>';
html += '<th scope="col">Property</th>';
html += '<th scope="col">Value</th>';
html += '<th scope="col">hasOwnProperty</th>';
html += '</tr>';
html += '</thead>';
html += '<tbody>';
for (var key in node) {
html += '<tr>';
html += '<td>' + key + '</td>';
html += '<td>' + node[key] + '</td>';
html += '<td>' + node.hasOwnProperty(key) + '</td>';
html += '</tr>';
}
html += '</tbody>';
document.getElementById(tableid).innerHTML = html;
if (document.getElementById(tableid).parentNode.previousElementSibling !== null) {
document.getElementById(tableid).parentNode.previousElementSibling.style.display = 'block';
}
};
// for legacy browsers
context.createGain = context.createGain || context.createGainNode;
var NOTE_NUMBER_OFFSET = 21;
var PIANO_88S = [
'A-4', 'A-4h', 'B-4',
'C-3', 'C-3h', 'D-3', 'D-3h', 'E-3', 'F-3', 'F-3h', 'G-3', 'G-3h', 'A-3', 'A-3h', 'B-3',
'C-2', 'C-2h', 'D-2', 'D-2h', 'E-2', 'F-2', 'F-2h', 'G-2', 'G-2h', 'A-2', 'A-2h', 'B-2',
'C-1', 'C-1h', 'D-1', 'D-1h', 'E-1', 'F-1', 'F-1h', 'G-1', 'G-1h', 'A-1', 'A-1h', 'B-1',
'C', 'Ch', 'D', 'Dh', 'E', 'F', 'Fh', 'G', 'Gh', 'A', 'Ah', 'B',
'C1', 'C1h', 'D1', 'D1h', 'E1', 'F1', 'F1h', 'G1', 'G1h', 'A1', 'A1h', 'B1',
'C2', 'C2h', 'D2', 'D2h', 'E2', 'F2', 'F2h', 'G2', 'G2h', 'A2', 'A2h', 'B2',
'C3', 'C3h', 'D3', 'D3h', 'E3', 'F3', 'F3h', 'G3', 'G3h', 'A3', 'A3h', 'B3',
'C4'
];
var oscillators = [];
var envelopegenerators = [];
var intervalids = [];
for (var i = 0, len = PIANO_88S.length; i < len; i++) {
oscillators[i] = null;
envelopegenerators[i] = context.createGain();
intervalids[i] = null;
// for legacy browsers
envelopegenerators[i].gain.setTargetAtTime = envelopegenerators[i].gain.setTargetAtTime || envelopegenerators[i].gain.setTargetValueAtTime;
}
var gain = context.createGain();
// Parameters
var type = 'sine';
var masterVolume = document.getElementById('range-volume').valueAsNumber;
var glide = document.getElementById('range-glide').valueAsNumber;
var attack = document.getElementById('range-attack').valueAsNumber;
var decay = document.getElementById('range-decay').valueAsNumber;
var sustain = document.getElementById('range-sustain').valueAsNumber;
var release = document.getElementById('range-release').valueAsNumber;
var startFrequency = -1;
var successCallback = function(midiAccess) {
var inputs = [];
var outputs = [];
if (typeof midiAccess === 'function') {
// Legacy Chrome
inputs = midiAccess.inputs();
outputs = midiAccess.outputs();
} else {
// Chrome 39 and later
var inputIterator = midiAccess.inputs.values();
var outputIterator = midiAccess.outputs.values();
for (var i = inputIterator.next(); !i.done; i = inputIterator.next()) {
inputs.push(i.value);
}
for (var o = outputIterator.next(); !o.done; o = outputIterator.next()) {
outputs.push(o.value);
}
}
inputs.forEach(function(element, index) {
var option = document.createElement('option');
option.appendChild(document.createTextNode(element.name));
option.setAttribute('value', index);
document.getElementById('select-midi-input-device').appendChild(option);
});
document.getElementById('select-midi-input-device').onchange = function() {
inputs.forEach(function(input) {
input.onmidimessage = null;
});
if (this.value === '') {
return;
}
inputs[this.value].onmidimessage = function(event) {
switch (event.data[0] & 0xf0) {
case 0x90 :
noteOn(event.data[1], event.data[2]);
break;
case 0x80 :
noteOff(event.data[1], event.data[2]);
break;
default :
break;
}
displayProperties(event, 'midimessageevent-properties', 'MIDIMessageEvent');
};
};
};
var errorCallback = function(error) {
console.dir(error);
};
var noteOn = function(noteNumber, velocity) {
if ((noteNumber < NOTE_NUMBER_OFFSET) || (noteNumber > (PIANO_88S.length + NOTE_NUMBER_OFFSET - 1))) {
return;
}
if (oscillators[noteNumber - NOTE_NUMBER_OFFSET] instanceof OscillatorNode) {
oscillators[noteNumber - NOTE_NUMBER_OFFSET].stop(0);
oscillators[noteNumber - NOTE_NUMBER_OFFSET].disconnect(0);
oscillators[noteNumber - NOTE_NUMBER_OFFSET] = null;
}
var FREQUENCY_RATIO = Math.pow(2, (1 / 12)); // about 1.059463
var MIN_A = 27.5;
var MAX_VELOCITY = 127;
var oscillator = context.createOscillator();
oscillator.start = oscillator.start || oscillator.noteOn;
oscillator.stop = oscillator.stop || oscillator.noteOff;
oscillator.frequency.setTargetAtTime = oscillator.frequency.setTargetAtTime || oscillator.frequency.setTargetValueAtTime;
oscillator.type = type;
gain.gain.value = (velocity / MAX_VELOCITY) * masterVolume;
// Envelope Generator
var envelopegenerator = envelopegenerators[noteNumber - NOTE_NUMBER_OFFSET];
var t0 = context.currentTime;
var t1 = t0 + attack;
var t2 = decay;
envelopegenerator.gain.cancelScheduledValues(t0);
envelopegenerator.gain.setValueAtTime(0, t0);
envelopegenerator.gain.linearRampToValueAtTime(1, t1);
envelopegenerator.gain.setTargetAtTime(sustain, t1, t2);
// Glide
if (startFrequency === -1) {
oscillator.frequency.value = MIN_A * Math.pow(FREQUENCY_RATIO, (noteNumber - NOTE_NUMBER_OFFSET));
startFrequency = oscillator.frequency.value;
} else {
oscillator.frequency.cancelScheduledValues(t0);
oscillator.frequency.setValueAtTime(startFrequency, t0);
oscillator.frequency.linearRampToValueAtTime(MIN_A * Math.pow(FREQUENCY_RATIO, (noteNumber - NOTE_NUMBER_OFFSET)), (t0 + glide));
}
oscillator.connect(envelopegenerator);
envelopegenerator.connect(gain);
gain.connect(context.destination);
oscillator.start(0);
oscillators[noteNumber - NOTE_NUMBER_OFFSET] = oscillator;
envelopegenerators[noteNumber - NOTE_NUMBER_OFFSET] = envelopegenerator;
var keyboard = document.getElementById(PIANO_88S[noteNumber - NOTE_NUMBER_OFFSET]);
keyboard.classList.remove('key-off');
keyboard.classList.add('key-on');
};
var noteOff = function(noteNumber, velocity) {
if ((noteNumber < NOTE_NUMBER_OFFSET) || (noteNumber > (PIANO_88S.length + NOTE_NUMBER_OFFSET - 1))) {
return;
}
if (!(oscillators[noteNumber - NOTE_NUMBER_OFFSET] instanceof OscillatorNode)) {
return;
}
// Envelope Generator
var envelopegenerator = envelopegenerators[noteNumber - NOTE_NUMBER_OFFSET];
var t3 = context.currentTime;
var t4 = release;
envelopegenerator.gain.cancelScheduledValues(t3);
envelopegenerator.gain.setValueAtTime(envelopegenerator.gain.value, t3);
envelopegenerator.gain.setTargetAtTime(0, t3, t4);
intervalids[noteNumber - NOTE_NUMBER_OFFSET] = window.setInterval(function() {
var MIN_GAIN = 1e-3;
if (envelopegenerator.gain.value < MIN_GAIN) {
// Stop sound
if (oscillators[noteNumber - NOTE_NUMBER_OFFSET] instanceof OscillatorNode) {
oscillators[noteNumber - NOTE_NUMBER_OFFSET].stop(0);
oscillators[noteNumber - NOTE_NUMBER_OFFSET].disconnect(0);
oscillators[noteNumber - NOTE_NUMBER_OFFSET] = null;
}
if (intervalids[noteNumber - NOTE_NUMBER_OFFSET] !== null) {
window.clearInterval(intervalids[noteNumber - NOTE_NUMBER_OFFSET]);
intervalids[noteNumber - NOTE_NUMBER_OFFSET] = null;
}
}
}, 0);
var keyboard = document.getElementById(PIANO_88S[noteNumber - NOTE_NUMBER_OFFSET]);
keyboard.classList.remove('key-on');
keyboard.classList.add('key-off');
};
navigator.requestMIDIAccess({sysex : true}).then(successCallback, errorCallback);
document.getElementById('form-type').addEventListener('change', function() {
for (var i = 0, len = this.elements['radio-type'].length; i < len; i++) {
if (this.elements['radio-type'][i].checked) {
type = this.elements['radio-type'][i].value;
break;
}
}
}, false);
document.getElementById('range-volume').addEventListener('input', function() {
masterVolume = this.valueAsNumber;
document.getElementById('output-volume').textContent = this.value;
});
document.getElementById('range-glide').addEventListener('input', function() {
glide = this.valueAsNumber;
document.getElementById('output-glide').textContent = this.value;
});
document.getElementById('range-attack').addEventListener('input', function() {
attack = this.valueAsNumber;
document.getElementById('output-attack').textContent = this.value;
});
document.getElementById('range-decay').addEventListener('input', function() {
decay = this.valueAsNumber;
document.getElementById('output-decay').textContent = this.value;
});
document.getElementById('range-sustain').addEventListener('input', function() {
sustain = this.valueAsNumber;
document.getElementById('output-sustain').textContent = this.value;
});
document.getElementById('range-release').addEventListener('input', function() {
release = this.valueAsNumber;
document.getElementById('output-release').textContent = this.value;
});
};
if ((document.readyState === 'interactive') || (document.readyState === 'complete')) {
onDOMContentLoaded();
} else {
document.addEventListener('DOMContentLoaded', onDOMContentLoaded, true);
}
})();