/* -------------------------------------------- MIDI.Plugin : 0.3.2 : 2013/01/26 -------------------------------------------- https://github.com/mudcube/MIDI.js -------------------------------------------- Inspired by javax.sound.midi (albeit a super simple version): http://docs.oracle.com/javase/6/docs/api/javax/sound/midi/package-summary.html -------------------------------------------- Technologies: MIDI.WebMIDI MIDI.WebAudio MIDI.Flash MIDI.AudioTag -------------------------------------------- Helpers: MIDI.GeneralMIDI MIDI.channels MIDI.keyToNote MIDI.noteToKey */ if (typeof (MIDI) === "undefined") var MIDI = {}; (function() { "use strict"; var setPlugin = function(root) { MIDI.api = root.api; MIDI.setVolume = root.setVolume; MIDI.programChange = root.programChange; MIDI.noteOn = root.noteOn; MIDI.noteOff = root.noteOff; MIDI.chordOn = root.chordOn; MIDI.chordOff = root.chordOff; MIDI.stopAllNotes = root.stopAllNotes; MIDI.getInput = root.getInput; MIDI.getOutputs = root.getOutputs; }; /* -------------------------------------------- Web MIDI API - Native Soundbank -------------------------------------------- https://dvcs.w3.org/hg/audio/raw-file/tip/midi/specification.html -------------------------------------------- */ (function () { var plugin = null; var output = null; var channels = []; var root = MIDI.WebMIDI = { api: "webmidi" }; root.setVolume = function (channel, volume) { // set channel volume output.send([0xB0 + channel, 0x07, volume]); }; root.programChange = function (channel, program) { // change channel instrument output.send([0xC0 + channel, program]); }; root.noteOn = function (channel, note, velocity, delay) { output.send([0x90 + channel, note, velocity], delay * 1000); }; root.noteOff = function (channel, note, delay) { output.send([0x80 + channel, note, 0], delay * 1000); }; root.chordOn = function (channel, chord, velocity, delay) { for (var n = 0; n < chord.length; n ++) { var note = chord[n]; output.send([0x90 + channel, note, velocity], delay * 1000); } }; root.chordOff = function (channel, chord, delay) { for (var n = 0; n < chord.length; n ++) { var note = chord[n]; output.send([0x80 + channel, note, 0], delay * 1000); } }; root.stopAllNotes = function () { for (var channel = 0; channel < 16; channel ++) { output.send([0xB0 + channel, 0x7B, 0]); } }; root.getInput = function () { return plugin.getInputs(); }; root.getOutputs = function () { return plugin.getOutputs(); }; root.connect = function (conf) { setPlugin(root); navigator.requestMIDIAccess().then(function (access) { plugin = access; output = plugin.outputs()[0]; if (conf.callback) conf.callback(); }, function (err) { // well at least we tried! if (window.AudioContext || window.webkitAudioContext) { // Chrome conf.api = "webaudio"; } else if (window.Audio) { // Firefox conf.api = "audiotag"; } else { // Internet Explorer conf.api = "flash"; } MIDI.loadPlugin(conf); }); }; })(); /* -------------------------------------------- Web Audio API - OGG or MPEG Soundbank -------------------------------------------- https://dvcs.w3.org/hg/audio/raw-file/tip/webaudio/specification.html -------------------------------------------- */ if (window.AudioContext || window.webkitAudioContext) (function () { var AudioContext = window.AudioContext || window.webkitAudioContext; var root = MIDI.WebAudio = { api: "webaudio" }; var ctx; var sources = {}; var masterVolume = 127; var audioBuffers = {}; var audioLoader = function (instrument, urlList, index, bufferList, callback) { var synth = MIDI.GeneralMIDI.byName[instrument]; var instrumentId = synth.number; var url = urlList[index]; if (!MIDI.Soundfont[instrument][url]) { // missing soundfont return callback(instrument); } var base64 = MIDI.Soundfont[instrument][url].split(",")[1]; var buffer = Base64Binary.decodeArrayBuffer(base64); ctx.decodeAudioData(buffer, function (buffer) { var msg = url; while (msg.length < 3) msg += " "; if (typeof (MIDI.loader) !== "undefined") { MIDI.loader.update(null, synth.instrument + "
Processing: " + (index / 87 * 100 >> 0) + "%
" + msg); } buffer.id = url; bufferList[index] = buffer; // if (bufferList.length === urlList.length) { while (bufferList.length) { buffer = bufferList.pop(); if (!buffer) continue; var nodeId = MIDI.keyToNote[buffer.id]; audioBuffers[instrumentId + "" + nodeId] = buffer; } callback(instrument); } }); }; root.setVolume = function (channel, volume) { masterVolume = volume; }; root.programChange = function (channel, program) { MIDI.channels[channel].instrument = program; }; root.noteOn = function (channel, note, velocity, delay) { /// check whether the note exists if (!MIDI.channels[channel]) return; var instrument = MIDI.channels[channel].instrument; if (!audioBuffers[instrument + "" + note]) return; /// convert relative delay to absolute delay if (delay < ctx.currentTime) delay += ctx.currentTime; /// crate audio buffer var source = ctx.createBufferSource(); sources[channel + "" + note] = source; source.buffer = audioBuffers[instrument + "" + note]; source.connect(ctx.destination); /// if (ctx.createGain) { // firefox source.gainNode = ctx.createGain(); } else { // chrome source.gainNode = ctx.createGainNode(); } var value = (velocity / 127) * (masterVolume / 127) * 2 - 1; source.gainNode.connect(ctx.destination); source.gainNode.gain.value = Math.max(-1, value); source.connect(source.gainNode); if (source.noteOn) { // old api source.noteOn(delay || 0); } else { // new api source.start(delay || 0); } return source; }; root.noteOff = function (channel, note, delay) { delay = delay || 0; if (delay < ctx.currentTime) delay += ctx.currentTime; var source = sources[channel + "" + note]; if (!source) return; if (source.gainNode) { // @Miranet: "the values of 0.2 and 0.3 could ofcourse be used as // a 'release' parameter for ADSR like time settings." // add { "metadata": { release: 0.3 } } to soundfont files var gain = source.gainNode.gain; gain.linearRampToValueAtTime(gain.value, delay); gain.linearRampToValueAtTime(-1, delay + 0.2); } if (source.noteOff) { // old api source.noteOff(delay + 0.3); } else { source.stop(delay + 0.3); } /// delete sources[channel + "" + note]; }; root.chordOn = function (channel, chord, velocity, delay) { var ret = {}, note; for (var n = 0, length = chord.length; n < length; n++) { ret[note = chord[n]] = root.noteOn(channel, note, velocity, delay); } return ret; }; root.chordOff = function (channel, chord, delay) { var ret = {}, note; for (var n = 0, length = chord.length; n < length; n++) { ret[note = chord[n]] = root.noteOff(channel, note, delay); } return ret; }; root.stopAllNotes = function () { for (var source in sources) { var delay = 0; if (delay < ctx.currentTime) delay += ctx.currentTime; // @Miranet: "the values of 0.2 and 0.3 could ofcourse be used as // a 'release' parameter for ADSR like time settings." // add { "metadata": { release: 0.3 } } to soundfont files sources[source].gain.linearRampToValueAtTime(1, delay); sources[source].gain.linearRampToValueAtTime(0, delay + 0.2); sources[source].noteOff(delay + 0.3); delete sources[source]; } }; root.connect = function (conf) { setPlugin(root); // MIDI.Player.ctx = ctx = new AudioContext(); /// var urlList = []; var keyToNote = MIDI.keyToNote; for (var key in keyToNote) urlList.push(key); var bufferList = []; var pending = {}; var oncomplete = function(instrument) { delete pending[instrument]; for (var key in pending) break; if (!key) conf.callback(); }; for (var instrument in MIDI.Soundfont) { pending[instrument] = true; for (var i = 0; i < urlList.length; i++) { audioLoader(instrument, urlList, i, bufferList, oncomplete); } } }; })(); /* -------------------------------------------- AudioTag