UNPKG

compoze

Version:

music writer

1,280 lines (1,168 loc) 40.7 kB
/* ------------------------------------- MIDI.audioDetect : 0.3 ------------------------------------- https://github.com/mudcube/MIDI.js ------------------------------------- Probably, Maybe, No... Absolutely! ------------------------------------- Test to see what types of <audio> MIME types are playable by the browser. ------------------------------------- */ if (typeof(MIDI) === "undefined") var MIDI = {}; (function() { "use strict"; var supports = {}; var pending = 0; var canPlayThrough = function (src) { pending ++; var audio = new Audio(); var mime = src.split(";")[0]; audio.id = "audio"; audio.setAttribute("preload", "auto"); audio.setAttribute("audiobuffer", true); audio.addEventListener("error", function() { supports[mime] = false; pending --; }, false); audio.addEventListener("canplaythrough", function() { supports[mime] = true; pending --; }, false); audio.src = "data:" + src; document.body.appendChild(audio); }; MIDI.audioDetect = function(callback) { // check whether <audio> tag is supported if (typeof(Audio) === "undefined") return callback({}); // check whether canPlayType is supported var audio = new Audio(); if (typeof(audio.canPlayType) === "undefined") return callback(supports); // see what we can learn from the browser var vorbis = audio.canPlayType('audio/ogg; codecs="vorbis"'); vorbis = (vorbis === "probably" || vorbis === "maybe"); var mpeg = audio.canPlayType('audio/mpeg'); mpeg = (mpeg === "probably" || mpeg === "maybe"); // maybe nothing is supported if (!vorbis && !mpeg) { callback(supports); return; } // or maybe something is supported if (vorbis) canPlayThrough("audio/ogg;base64,T2dnUwACAAAAAAAAAADqnjMlAAAAAOyyzPIBHgF2b3JiaXMAAAAAAUAfAABAHwAAQB8AAEAfAACZAU9nZ1MAAAAAAAAAAAAA6p4zJQEAAAANJGeqCj3//////////5ADdm9yYmlzLQAAAFhpcGguT3JnIGxpYlZvcmJpcyBJIDIwMTAxMTAxIChTY2hhdWZlbnVnZ2V0KQAAAAABBXZvcmJpcw9CQ1YBAAABAAxSFCElGVNKYwiVUlIpBR1jUFtHHWPUOUYhZBBTiEkZpXtPKpVYSsgRUlgpRR1TTFNJlVKWKUUdYxRTSCFT1jFloXMUS4ZJCSVsTa50FkvomWOWMUYdY85aSp1j1jFFHWNSUkmhcxg6ZiVkFDpGxehifDA6laJCKL7H3lLpLYWKW4q91xpT6y2EGEtpwQhhc+211dxKasUYY4wxxsXiUyiC0JBVAAABAABABAFCQ1YBAAoAAMJQDEVRgNCQVQBABgCAABRFcRTHcRxHkiTLAkJDVgEAQAAAAgAAKI7hKJIjSZJkWZZlWZameZaouaov+64u667t6roOhIasBACAAAAYRqF1TCqDEEPKQ4QUY9AzoxBDDEzGHGNONKQMMogzxZAyiFssLqgQBKEhKwKAKAAAwBjEGGIMOeekZFIi55iUTkoDnaPUUcoolRRLjBmlEluJMYLOUeooZZRCjKXFjFKJscRUAABAgAMAQICFUGjIigAgCgCAMAYphZRCjCnmFHOIMeUcgwwxxiBkzinoGJNOSuWck85JiRhjzjEHlXNOSuekctBJyaQTAAAQ4AAAEGAhFBqyIgCIEwAwSJKmWZomipamiaJniqrqiaKqWp5nmp5pqqpnmqpqqqrrmqrqypbnmaZnmqrqmaaqiqbquqaquq6nqrZsuqoum65q267s+rZru77uqapsm6or66bqyrrqyrbuurbtS56nqqKquq5nqq6ruq5uq65r25pqyq6purJtuq4tu7Js664s67pmqq5suqotm64s667s2rYqy7ovuq5uq7Ks+6os+75s67ru2rrwi65r66os674qy74x27bwy7ouHJMnqqqnqq7rmarrqq5r26rr2rqmmq5suq4tm6or26os67Yry7aumaosm64r26bryrIqy77vyrJui67r66Ys67oqy8Lu6roxzLat+6Lr6roqy7qvyrKuu7ru+7JuC7umqrpuyrKvm7Ks+7auC8us27oxuq7vq7It/KosC7+u+8Iy6z5jdF1fV21ZGFbZ9n3d95Vj1nVhWW1b+V1bZ7y+bgy7bvzKrQvLstq2scy6rSyvrxvDLux8W/iVmqratum6um7Ksq/Lui60dd1XRtf1fdW2fV+VZd+3hV9pG8OwjK6r+6os68Jry8ov67qw7MIvLKttK7+r68ow27qw3L6wLL/uC8uq277v6rrStXVluX2fsSu38QsAABhwAAAIMKEMFBqyIgCIEwBAEHIOKQahYgpCCKGkEEIqFWNSMuakZM5JKaWUFEpJrWJMSuaclMwxKaGUlkopqYRSWiqlxBRKaS2l1mJKqcVQSmulpNZKSa2llGJMrcUYMSYlc05K5pyUklJrJZXWMucoZQ5K6iCklEoqraTUYuacpA46Kx2E1EoqMZWUYgupxFZKaq2kFGMrMdXUWo4hpRhLSrGVlFptMdXWWqs1YkxK5pyUzDkqJaXWSiqtZc5J6iC01DkoqaTUYiopxco5SR2ElDLIqJSUWiupxBJSia20FGMpqcXUYq4pxRZDSS2WlFosqcTWYoy1tVRTJ6XFklKMJZUYW6y5ttZqDKXEVkqLsaSUW2sx1xZjjqGkFksrsZWUWmy15dhayzW1VGNKrdYWY40x5ZRrrT2n1mJNMdXaWqy51ZZbzLXnTkprpZQWS0oxttZijTHmHEppraQUWykpxtZara3FXEMpsZXSWiypxNhirLXFVmNqrcYWW62ltVprrb3GVlsurdXcYqw9tZRrrLXmWFNtBQAADDgAAASYUAYKDVkJAEQBAADGMMYYhEYpx5yT0ijlnHNSKucghJBS5hyEEFLKnINQSkuZcxBKSSmUklJqrYVSUmqttQIAAAocAAACbNCUWByg0JCVAEAqAIDBcTRNFFXVdX1fsSxRVFXXlW3jVyxNFFVVdm1b+DVRVFXXtW3bFn5NFFVVdmXZtoWiqrqybduybgvDqKqua9uybeuorqvbuq3bui9UXVmWbVu3dR3XtnXd9nVd+Bmzbeu2buu+8CMMR9/4IeTj+3RCCAAAT3AAACqwYXWEk6KxwEJDVgIAGQAAgDFKGYUYM0gxphhjTDHGmAAAgAEHAIAAE8pAoSErAoAoAADAOeecc84555xzzjnnnHPOOeecc44xxhhjjDHGGGOMMcYYY4wxxhhjjDHGGGOMMcYYY0wAwE6EA8BOhIVQaMhKACAcAABACCEpKaWUUkoRU85BSSmllFKqFIOMSkoppZRSpBR1lFJKKaWUIqWgpJJSSimllElJKaWUUkoppYw6SimllFJKKaWUUkoppZRSSimllFJKKaWUUkoppZRSSimllFJKKaWUUkoppZRSSimllFJKKaWUUkoppZRSSimllFJKKaVUSimllFJKKaWUUkoppRQAYPLgAACVYOMMK0lnhaPBhYasBAByAwAAhRiDEEJpraRUUkolVc5BKCWUlEpKKZWUUqqYgxBKKqmlklJKKbXSQSihlFBKKSWUUkooJYQQSgmhlFRCK6mEUkoHoYQSQimhhFRKKSWUzkEoIYUOQkmllNRCSB10VFIpIZVSSiklpZQ6CKGUklJLLZVSWkqpdBJSKamV1FJqqbWSUgmhpFZKSSWl0lpJJbUSSkklpZRSSymFVFJJJYSSUioltZZaSqm11lJIqZWUUkqppdRSSiWlkEpKqZSSUmollZRSaiGVlEpJKaTUSimlpFRCSamlUlpKLbWUSkmptFRSSaWUlEpJKaVSSksppRJKSqmllFpJKYWSUkoplZJSSyW1VEoKJaWUUkmptJRSSymVklIBAEAHDgAAAUZUWoidZlx5BI4oZJiAAgAAQABAgAkgMEBQMApBgDACAQAAAADAAAAfAABHARAR0ZzBAUKCwgJDg8MDAAAAAAAAAAAAAACAT2dnUwAEAAAAAAAAAADqnjMlAgAAADzQPmcBAQA="); if (mpeg) canPlayThrough("audio/mpeg;base64,/+MYxAAAAANIAUAAAASEEB/jwOFM/0MM/90b/+RhST//w4NFwOjf///PZu////9lns5GFDv//l9GlUIEEIAAAgIg8Ir/JGq3/+MYxDsLIj5QMYcoAP0dv9HIjUcH//yYSg+CIbkGP//8w0bLVjUP///3Z0x5QCAv/yLjwtGKTEFNRTMuOTeqqqqqqqqqqqqq/+MYxEkNmdJkUYc4AKqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq"); // lets find out! var time = (new Date()).getTime(); var interval = window.setInterval(function() { var now = (new Date()).getTime(); var maxExecution = now - time > 5000; if (!pending || maxExecution) { window.clearInterval(interval); callback(supports); } }, 1); }; })(); /* ----------------------------------------------------------- MIDI.loadPlugin : 0.1.2 : 01/22/2014 ----------------------------------------------------------- https://github.com/mudcube/MIDI.js ----------------------------------------------------------- MIDI.loadPlugin({ targetFormat: "mp3", // optionally can force to use MP3 (for instance on mobile networks) instrument: "acoustic_grand_piano", // or 1 (default) instruments: [ "acoustic_grand_piano", "acoustic_guitar_nylon" ], // or multiple instruments callback: function() { } }); */ if (typeof (MIDI) === "undefined") var MIDI = {}; if (typeof (MIDI.Soundfont) === "undefined") MIDI.Soundfont = {}; (function() { "use strict"; var USE_JAZZMIDI = false; // Turn on to support JazzMIDI Plugin MIDI.loadPlugin = function(conf) { if (typeof(conf) === "function") conf = { callback: conf }; /// Get the instrument name. var instruments = conf.instruments || conf.instrument || "acoustic_grand_piano"; if (typeof(instruments) !== "object") instruments = [ instruments ]; /// for (var n = 0; n < instruments.length; n ++) { var instrument = instruments[n]; if (typeof(instrument) === "number") { instruments[n] = MIDI.GeneralMIDI.byId[instrument]; } }; /// MIDI.soundfontUrl = conf.soundfontUrl || MIDI.soundfontUrl || "./soundfont/"; /// Detect the best type of audio to use. MIDI.audioDetect(function(types) { var api = ""; // use the most appropriate plugin if not specified if (apis[conf.api]) { api = conf.api; } else if (apis[window.location.hash.substr(1)]) { api = window.location.hash.substr(1); } else if (USE_JAZZMIDI && navigator.requestMIDIAccess) { api = "webmidi"; } else if (window.webkitAudioContext || window.AudioContext) { // Chrome api = "webaudio"; } else if (window.Audio) { // Firefox api = "audiotag"; } else { // Internet Explorer api = "flash"; } /// if (!connect[api]) return; // use audio/ogg when supported if (conf.targetFormat) { var filetype = conf.targetFormat; } else { // use best quality var filetype = types["audio/ogg"] ? "ogg" : "mp3"; } // load the specified plugin MIDI.lang = api; MIDI.supports = types; connect[api](filetype, instruments, conf); }); }; /// var connect = {}; connect.webmidi = function(filetype, instruments, conf) { if (MIDI.loader) MIDI.loader.message("Web MIDI API..."); MIDI.WebMIDI.connect(conf); }; connect.flash = function(filetype, instruments, conf) { // fairly quick, but requires loading of individual MP3s (more http requests). if (MIDI.loader) MIDI.loader.message("Flash API..."); DOMLoader.script.add({ src: conf.soundManagerUrl || "./inc/SoundManager2/script/soundmanager2.js", verify: "SoundManager", callback: function () { MIDI.Flash.connect(instruments, conf); } }); }; connect.audiotag = function(filetype, instruments, conf) { if (MIDI.loader) MIDI.loader.message("HTML5 Audio API..."); // works ok, kinda like a drunken tuna fish, across the board. var queue = createQueue({ items: instruments, getNext: function(instrumentId) { DOMLoader.sendRequest({ url: MIDI.soundfontUrl + instrumentId + "-" + filetype + ".js", onprogress: getPercent, onload: function (response) { addSoundfont(response.responseText); if (MIDI.loader) MIDI.loader.update(null, "Downloading", 100); queue.getNext(); } }); }, onComplete: function() { MIDI.AudioTag.connect(conf); } }); }; connect.webaudio = function(filetype, instruments, conf) { if (MIDI.loader) MIDI.loader.message("Web Audio API..."); // works awesome! safari, chrome and firefox support. var queue = createQueue({ items: instruments, getNext: function(instrumentId) { DOMLoader.sendRequest({ url: MIDI.soundfontUrl + instrumentId + "-" + filetype + ".js", onprogress: getPercent, onload: function(response) { addSoundfont(response.responseText); if (MIDI.loader) MIDI.loader.update(null, "Downloading...", 100); queue.getNext(); } }); }, onComplete: function() { MIDI.WebAudio.connect(conf); } }); }; /// Helpers var apis = { "webmidi": true, "webaudio": true, "audiotag": true, "flash": true }; var addSoundfont = function(text) { var script = document.createElement("script"); script.language = "javascript"; script.type = "text/javascript"; script.text = text; document.body.appendChild(script); }; var getPercent = function(event) { if (!this.totalSize) { this.totalSize = event.total; // if (this.getResponseHeader("Content-Length-Raw")) { // this.totalSize = parseInt(this.getResponseHeader("Content-Length-Raw")); // } else { // } } /// var percent = this.totalSize ? Math.round(event.loaded / this.totalSize * 100) : ""; if (MIDI.loader) MIDI.loader.update(null, "Downloading...", percent); }; var createQueue = function(conf) { var self = {}; self.queue = []; for (var key in conf.items) { if (conf.items.hasOwnProperty(key)) { self.queue.push(conf.items[key]); } } self.getNext = function() { if (!self.queue.length) return conf.onComplete(); conf.getNext(self.queue.shift()); }; setTimeout(self.getNext, 1); return self; }; })(); /* -------------------------------------------- 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 += "&nbsp;"; if (typeof (MIDI.loader) !== "undefined") { MIDI.loader.update(null, synth.instrument + "<br>Processing: " + (index / 87 * 100 >> 0) + "%<br>" + 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 <audio> - OGG or MPEG Soundbank -------------------------------------------- http://dev.w3.org/html5/spec/Overview.html#the-audio-element -------------------------------------------- */ if (window.Audio) (function () { var root = MIDI.AudioTag = { api: "audiotag" }; var note2id = {}; var volume = 127; // floating point var channel_nid = -1; // current channel var channels = []; // the audio channels var channelInstrumentNoteIds = []; // instrumentId + noteId that is currently playing in each 'channel', for routing noteOff/chordOff calls var notes = {}; // the piano keys for (var nid = 0; nid < 12; nid++) { channels[nid] = new Audio(); } var playChannel = function (channel, note) { if (!MIDI.channels[channel]) return; var instrument = MIDI.channels[channel].instrument; var instrumentId = MIDI.GeneralMIDI.byId[instrument].id; var note = notes[note]; if (!note) return; var instrumentNoteId = instrumentId + "" + note.id; var nid = (channel_nid + 1) % channels.length; var audio = channels[nid]; channelInstrumentNoteIds[ nid ] = instrumentNoteId; audio.src = MIDI.Soundfont[instrumentId][note.id]; audio.volume = volume / 127; audio.play(); channel_nid = nid; }; var stopChannel = function (channel, note) { if (!MIDI.channels[channel]) return; var instrument = MIDI.channels[channel].instrument; var instrumentId = MIDI.GeneralMIDI.byId[instrument].id; var note = notes[note]; if (!note) return; var instrumentNoteId = instrumentId + "" + note.id; for(var i=0;i<channels.length;i++){ var nid = (i + channel_nid + 1) % channels.length; var cId = channelInstrumentNoteIds[nid]; if(cId && cId == instrumentNoteId){ channels[nid].pause(); channelInstrumentNoteIds[nid] = null; return; } } }; root.programChange = function (channel, program) { MIDI.channels[channel].instrument = program; }; root.setVolume = function (channel, n) { volume = n; //- should be channel specific volume }; root.noteOn = function (channel, note, velocity, delay) { var id = note2id[note]; if (!notes[id]) return; if (delay) { return window.setTimeout(function () { playChannel(channel, id); }, delay * 1000); } else { playChannel(channel, id); } }; root.noteOff = function (channel, note, delay) { var id = note2id[note]; if (!notes[id]) return; if (delay) { return setTimeout(function() { stopChannel(channel, id); }, delay * 1000) } else { stopChannel(channel, id); } }; root.chordOn = function (channel, chord, velocity, delay) { for (var idx = 0; idx < chord.length; idx ++) { var n = chord[idx]; var id = note2id[n]; if (!notes[id]) continue; if (delay) { return window.setTimeout(function () { playChannel(channel, id); }, delay * 1000); } else { playChannel(channel, id); } } }; root.chordOff = function (channel, chord, delay) { for (var idx = 0; idx < chord.length; idx ++) { var n = chord[idx]; var id = note2id[n]; if (!notes[id]) continue; if (delay) { return window.setTimeout(function () { stopChannel(channel, id); }, delay * 1000); } else { stopChannel(channel, id); } } }; root.stopAllNotes = function () { for (var nid = 0, length = channels.length; nid < length; nid++) { channels[nid].pause(); } }; root.connect = function (conf) { for (var key in MIDI.keyToNote) { note2id[MIDI.keyToNote[key]] = key; notes[key] = { id: key }; } setPlugin(root); /// if (conf.callback) conf.callback(); }; })(); /* -------------------------------------------- Flash - MP3 Soundbank -------------------------------------------- http://www.schillmania.com/projects/soundmanager2/ -------------------------------------------- */ (function () { var root = MIDI.Flash = { api: "flash" }; var noteReverse = {}; var notes = {}; root.programChange = function (channel, program) { MIDI.channels[channel].instrument = program; }; root.setVolume = function (channel, note) { }; root.noteOn = function (channel, note, velocity, delay) { if (!MIDI.channels[channel]) return; var instrument = MIDI.channels[channel].instrument; var id = MIDI.GeneralMIDI.byId[instrument].number; note = id + "" + noteReverse[note]; if (!notes[note]) return; if (delay) { return window.setTimeout(function() { notes[note].play({ volume: velocity * 2 }); }, delay * 1000); } else { notes[note].play({ volume: velocity * 2 }); } }; root.noteOff = function (channel, note, delay) { }; root.chordOn = function (channel, chord, velocity, delay) { if (!MIDI.channels[channel]) return; var instrument = MIDI.channels[channel].instrument; var id = MIDI.GeneralMIDI.byId[instrument].number; for (var key in chord) { var n = chord[key]; var note = id + "" + noteReverse[n]; if (notes[note]) { notes[note].play({ volume: velocity * 2 }); } } }; root.chordOff = function (channel, chord, delay) { }; root.stopAllNotes = function () { }; root.connect = function (instruments, conf) { soundManager.flashVersion = 9; soundManager.useHTML5Audio = true; soundManager.url = conf.soundManagerSwfUrl || '../inc/SoundManager2/swf/'; soundManager.useHighPerformance = true; soundManager.wmode = 'transparent'; soundManager.flashPollingInterval = 1; soundManager.debugMode = false; soundManager.onload = function () { var createBuffer = function(instrument, id, onload) { var synth = MIDI.GeneralMIDI.byName[instrument]; var instrumentId = synth.number; notes[instrumentId+""+id] = soundManager.createSound({ id: id, url: MIDI.soundfontUrl + instrument + "-mp3/" + id + ".mp3", multiShot: true, autoLoad: true, onload: onload }); }; var loaded = []; var samplesPerInstrument = 88; var samplesToLoad = instruments.length * samplesPerInstrument; for (var i = 0; i < instruments.length; i++) { var instrument = instruments[i]; var onload = function () { loaded.push(this.sID); if (typeof (MIDI.loader) === "undefined") return; MIDI.loader.update(null, "Processing: " + this.sID); }; for (var j = 0; j < samplesPerInstrument; j++) { var id = noteReverse[j + 21]; createBuffer(instrument, id, onload); } } /// setPlugin(root); // var interval = window.setInterval(function () { if (loaded.length < samplesToLoad) return; window.clearInterval(interval); if (conf.callback) conf.callback(); }, 25); }; soundManager.onerror = function () { }; for (var key in MIDI.keyToNote) { noteReverse[MIDI.keyToNote[key]] = key; } }; })(); /* helper functions */ // instrument-tracker MIDI.GeneralMIDI = (function (arr) { var clean = function(v) { return v.replace(/[^a-z0-9 ]/gi, "").replace(/[ ]/g, "_").toLowerCase(); }; var ret = { byName: {}, byId: {}, byCategory: {} }; for (var key in arr) { var list = arr[key]; for (var n = 0, length = list.length; n < length; n++) { var instrument = list[n]; if (!instrument) continue; var num = parseInt(instrument.substr(0, instrument.indexOf(" ")), 10); instrument = instrument.replace(num + " ", ""); ret.byId[--num] = ret.byName[clean(instrument)] = ret.byCategory[clean(key)] = { id: clean(instrument), instrument: instrument, number: num, category: key }; } } return ret; })({ 'Piano': ['1 Acoustic Grand Piano', '2 Bright Acoustic Piano', '3 Electric Grand Piano', '4 Honky-tonk Piano', '5 Electric Piano 1', '6 Electric Piano 2', '7 Harpsichord', '8 Clavinet'], 'Chromatic Percussion': ['9 Celesta', '10 Glockenspiel', '11 Music Box', '12 Vibraphone', '13 Marimba', '14 Xylophone', '15 Tubular Bells', '16 Dulcimer'], 'Organ': ['17 Drawbar Organ', '18 Percussive Organ', '19 Rock Organ', '20 Church Organ', '21 Reed Organ', '22 Accordion', '23 Harmonica', '24 Tango Accordion'], 'Guitar': ['25 Acoustic Guitar (nylon)', '26 Acoustic Guitar (steel)', '27 Electric Guitar (jazz)', '28 Electric Guitar (clean)', '29 Electric Guitar (muted)', '30 Overdriven Guitar', '31 Distortion Guitar', '32 Guitar Harmonics'], 'Bass': ['33 Acoustic Bass', '34 Electric Bass (finger)', '35 Electric Bass (pick)', '36 Fretless Bass', '37 Slap Bass 1', '38 Slap Bass 2', '39 Synth Bass 1', '40 Synth Bass 2'], 'Strings': ['41 Violin', '42 Viola', '43 Cello', '44 Contrabass', '45 Tremolo Strings', '46 Pizzicato Strings', '47 Orchestral Harp', '48 Timpani'], 'Ensemble': ['49 String Ensemble 1', '50 String Ensemble 2', '51 Synth Strings 1', '52 Synth Strings 2', '53 Choir Aahs', '54 Voice Oohs', '55 Synth Choir', '56 Orchestra Hit'], 'Brass': ['57 Trumpet', '58 Trombone', '59 Tuba', '60 Muted Trumpet', '61 French Horn', '62 Brass Section', '63 Synth Brass 1', '64 Synth Brass 2'], 'Reed': ['65 Soprano Sax', '66 Alto Sax', '67 Tenor Sax', '68 Baritone Sax', '69 Oboe', '70 English Horn', '71 Bassoon', '72 Clarinet'], 'Pipe': ['73 Piccolo', '74 Flute', '75 Recorder', '76 Pan Flute', '77 Blown Bottle', '78 Shakuhachi', '79 Whistle', '80 Ocarina'], 'Synth Lead': ['81 Lead 1 (square)', '82 Lead 2 (sawtooth)', '83 Lead 3 (calliope)', '84 Lead 4 (chiff)', '85 Lead 5 (charang)', '86 Lead 6 (voice)', '87 Lead 7 (fifths)', '88 Lead 8 (bass + lead)'], 'Synth Pad': ['89 Pad 1 (new age)', '90 Pad 2 (warm)', '91 Pad 3 (polysynth)', '92 Pad 4 (choir)', '93 Pad 5 (bowed)', '94 Pad 6 (metallic)', '95 Pad 7 (halo)', '96 Pad 8 (sweep)'], 'Synth Effects': ['97 FX 1 (rain)', '98 FX 2 (soundtrack)', '99 FX 3 (crystal)', '100 FX 4 (atmosphere)', '101 FX 5 (brightness)', '102 FX 6 (goblins)', '103 FX 7 (echoes)', '104 FX 8 (sci-fi)'], 'Ethnic': ['105 Sitar', '106 Banjo', '107 Shamisen', '108 Koto', '109 Kalimba', '110 Bagpipe', '111 Fiddle', '112 Shanai'], 'Percussive': ['113 Tinkle Bell', '114 Agogo', '115 Steel Drums', '116 Woodblock', '117 Taiko Drum', '118 Melodic Tom', '119 Synth Drum'], 'Sound effects': ['120 Reverse Cymbal', '121 Guitar Fret Noise', '122 Breath Noise', '123 Seashore', '124 Bird Tweet', '125 Telephone Ring', '126 Helicopter', '127 Applause', '128 Gunshot'] }); // channel-tracker MIDI.channels = (function () { // 0 - 15 channels var channels = {}; for (var n = 0; n < 16; n++) { channels[n] = { // default values instrument: 0, // Acoustic Grand Piano mute: false, mono: false, omni: false, solo: false }; } return channels; })(); // MIDI.pianoKeyOffset = 21; // note conversions MIDI.keyToNote = {}; // C8 == 108 MIDI.noteToKey = {}; // 108 == C8 (function () { var A0 = 0x15; // first note var C8 = 0x6C; // last note var number2key = ["C", "Db", "D", "Eb", "E", "F", "Gb", "G", "Ab", "A", "Bb", "B"]; for (var n = A0; n <= C8; n++) { var octave = (n - 12) / 12 >> 0; var name = number2key[n % 12] + octave; MIDI.keyToNote[name] = n; MIDI.noteToKey[n] = name; } })(); })(); /* ------------------------------------- MIDI.Player : 0.3 ------------------------------------- https://github.com/mudcube/MIDI.js ------------------------------------- #jasmid ------------------------------------- */ if (typeof (MIDI) === "undefined") var MIDI = {}; if (typeof (MIDI.Player) === "undefined") MIDI.Player = {}; (function() { "use strict"; var root = MIDI.Player; root.callback = undefined; // your custom callback goes here! root.currentTime = 0; root.endTime = 0; root.restart = 0; root.playing = false; root.timeWarp = 1; // root.start = root.resume = function () { if (root.currentTime < -1) root.currentTime = -1; startAudio(root.currentTime); }; root.pause = function () { var tmp = root.restart; stopAudio(); root.restart = tmp; }; root.stop = function () { stopAudio(); root.restart = 0; root.currentTime = 0; }; root.addListener = function(callback) { onMidiEvent = callback; }; root.removeListener = function() { onMidiEvent = undefined; }; root.clearAnimation = function() { if (root.interval) { window.clearInterval(root.interval); } }; root.setAnimation = function(config) { var callback = (typeof(config) === "function") ? config : config.callback; var interval = config.interval || 30; var currentTime = 0; var tOurTime = 0; var tTheirTime = 0; // root.clearAnimation(); root.interval = window.setInterval(function () { if (root.endTime === 0) return; if (root.playing) { currentTime = (tTheirTime === root.currentTime) ? tOurTime - (new Date).getTime() : 0; if (root.currentTime === 0) { currentTime = 0; } else { currentTime = root.currentTime - currentTime; } if (tTheirTime !== root.currentTime) { tOurTime = (new Date).getTime(); tTheirTime = root.currentTime; } } else { // paused currentTime = root.currentTime; } var endTime = root.endTime; var percent = currentTime / endTime; var total = currentTime / 1000; var minutes = total / 60; var seconds = total - (minutes * 60); var t1 = minutes * 60 + seconds; var t2 = (endTime / 1000); if (t2 - t1 < -1) return; callback({ now: t1, end: t2, events: noteRegistrar }); }, interval); }; // helpers root.loadMidiFile = function() { // reads midi into javascript array of events root.replayer = new Replayer(MidiFile(root.currentData), root.timeWarp); root.data = root.replayer.getData(); root.endTime = getLength(); }; root.loadFile = function (file, callback) { root.stop(); if (file.indexOf("base64,") !== -1) { var data = window.atob(file.split(",")[1]); root.currentData = data; root.loadMidiFile(); if (callback) callback(data); return; } /// var fetch = new XMLHttpRequest(); fetch.open('GET', file); fetch.overrideMimeType("text/plain; charset=x-user-defined"); fetch.onreadystatechange = function () { if (this.readyState === 4 && this.status === 200) { var t = this.responseText || ""; var ff = []; var mx = t.length; var scc = String.fromCharCode; for (var z = 0; z < mx; z++) { ff[z] = scc(t.charCodeAt(z) & 255); } var data = ff.join(""); root.currentData = data; root.loadMidiFile(); if (callback) callback(data); } }; fetch.send(); }; // Playing the audio var eventQueue = []; // hold events to be triggered var queuedTime; // var startTime = 0; // to measure time elapse var noteRegistrar = {}; // get event for requested note var onMidiEvent = undefined; // listener callback var scheduleTracking = function (channel, note, currentTime, offset, message, velocity) { var interval = window.setTimeout(function () { var data = { channel: channel, note: note, now: currentTime, end: root.endTime, message: message, velocity: velocity }; // if (message === 128) { delete noteRegistrar[note]; } else { noteRegistrar[note] = data; } if (onMidiEvent) { onMidiEvent(data); } root.currentTime = currentTime; if (root.currentTime === queuedTime && queuedTime < root.endTime) { // grab next sequence startAudio(queuedTime, true); } }, currentTime - offset); return interval; }; var getContext = function() { if (MIDI.lang === 'WebAudioAPI') { return MIDI.Player.ctx; } else if (!root.ctx) { root.ctx = { currentTime: 0 }; } return root.ctx; }; var getLength = function() { var data = root.data; var length = data.length; var totalTime = 0.5; for (var n = 0; n < length; n++) { totalTime += data[n][1]; } return totalTime; }; var startAudio = function (currentTime, fromCache) { if (!root.replayer) return; if (!fromCache) { if (typeof (currentTime) === "undefined") currentTime = root.restart; if (root.playing) stopAudio(); root.playing = true; root.data = root.replayer.getData(); root.endTime = getLength(); } var note; var offset = 0; var messages = 0; var data = root.data; var ctx = getContext(); var length = data.length; // queuedTime = 0.5; startTime = ctx.currentTime; // for (var n = 0; n < length && messages < 100; n++) { queuedTime += data[n][1]; if (queuedTime < currentTime) { offset = queuedTime; continue; } currentTime = queuedTime - offset; var event = data[n][0].event; if (event.type !== "channel") continue; var channel = event.channel; switch (event.subtype) { case 'noteOn': if (MIDI.channels[channel].mute) break; note = event.noteNumber - (root.MIDIOffset || 0); eventQueue.push({ event: event, source: MIDI.noteOn(channel, event.noteNumber, event.velocity, currentTime / 1000 + ctx.currentTime), interval: scheduleTracking(channel, note, queuedTime, offset, 144, event.velocity) }); messages ++; break; case 'noteOff': if (MIDI.channels[channel].mute) break; note = event.noteNumber - (root.MIDIOffset || 0); eventQueue.push({ event: event, source: MIDI.noteOff(channel, event.noteNumber, currentTime / 1000 + ctx.currentTime), interval: scheduleTracking(channel, note, queuedTime, offset, 128) }); break; default: break; } } }; var stopAudio = function () { var ctx = getContext(); root.playing = false; root.restart += (ctx.currentTime - startTime) * 1000; // stop the audio, and intervals while (eventQueue.length) { var o = eventQueue.pop(); window.clearInterval(o.interval); if (!o.source) continue; // is not webaudio if (typeof(o.source) === "number") { window.clearTimeout(o.source); } else { // webaudio o.source.disconnect(0); } } // run callback to cancel any notes still playing for (var key in noteRegistrar) { var o = noteRegistrar[key] if (noteRegistrar[key].message === 144 && onMidiEvent) { onMidiEvent({ channel: o.channel, note: o.note, now: o.now, end: o.end, message: 128, velocity: o.velocity }); } } // reset noteRegistrar noteRegistrar = {}; }; })(); /* DOMLoader.XMLHttp -------------------------- DOMLoader.sendRequest({ url: "./dir/something.extension", data: "test!", onerror: function(event) { console.log(event); }, onload: function(response) { console.log(response.responseText); }, onprogress: function (event) { var percent = event.loaded / event.total * 100 >> 0; loader.message("loading: " + percent + "%"); } }); */ if (typeof(DOMLoader) === "undefined") var DOMLoader = {}; // Add XMLHttpRequest when not available if (typeof (XMLHttpRequest) === "undefined") { var XMLHttpRequest; (function () { // find equivalent for IE var factories = [ function () { return new ActiveXObject("Msxml2.XMLHTTP") }, function () { return new ActiveXObject("Msxml3.XMLHTTP") }, function () { return new ActiveXObject("Microsoft.XMLHTTP") }]; for (var i = 0; i < factories.length; i++) { try { factories[i](); } catch (e) { continue; } break; } XMLHttpRequest = factories[i]; })(); } if (typeof ((new XMLHttpRequest()).responseText) === "undefined") { // http://stackoverflow.com/questions/1919972/how-do-i-access-xhr-responsebody-for-binary-data-from-javascript-in-ie var IEBinaryToArray_ByteStr_Script = "<!-- IEBinaryToArray_ByteStr -->\r\n"+ "<script type='text/vbscript'>\r\n"+ "Function IEBinaryToArray_ByteStr(Binary)\r\n"+ " IEBinaryToArray_ByteStr = CStr(Binary)\r\n"+ "End Function\r\n"+ "Function IEBinaryToArray_ByteStr_Last(Binary)\r\n"+ " Dim lastIndex\r\n"+ " lastIndex = LenB(Binary)\r\n"+ " if lastIndex mod 2 Then\r\n"+ " IEBinaryToArray_ByteStr_Last = Chr( AscB( MidB( Binary, lastIndex, 1 ) ) )\r\n"+ " Else\r\n"+ " IEBinaryToArray_ByteStr_Last = "+'""'+"\r\n"+ " End If\r\n"+ "End Function\r\n"+ "</script>\r\n"; // inject VBScript document.write(IEBinaryToArray_ByteStr_Script); DOMLoader.sendRequest = function(conf) { // helper to convert from responseBody to a "responseText" like thing function getResponseText(binary) { var byteMapping = {}; for (var i = 0; i < 256; i++) { for (var j = 0; j < 256; j++) { byteMapping[String.fromCharCode(i + j * 256)] = String.fromCharCode(i) + String.fromCharCode(j); } } // call into VBScript utility fns var rawBytes = IEBinaryToArray_ByteStr(binary); var lastChr = IEBinaryToArray_ByteStr_Last(binary); return rawBytes.replace(/[\s\S]/g, function (match) { return byteMapping[match]; }) + lastChr; }; // var req = XMLHttpRequest(); req.open("GET", conf.url, true); if (conf.responseType) req.responseType = conf.responseType; if (conf.onerror) req.onerror = conf.onerror; if (conf.onprogress) req.onprogress = conf.onprogress; req.onreadystatechange = function (event) { if (req.readyState === 4) { if (req.status === 200) { req.responseText = getResponseText(req.responseBody); } else { req = false; } if (conf.onload) conf.onload(req); } }; req.setRequestHeader("Accept-Charset", "x-user-defined"); req.send(null); return req; } } else { DOMLoader.sendRequest = function(conf) { var req = new XMLHttpRequest(); req.open(conf.data ? "POST" : "GET", conf.url, true); if (req.overrideMimeType) req.overrideMimeType("text/plain; charset=x-user-defined"); if (conf.data) req.setRequestHeader('Content-type','application/x-www-form-urlencoded'); if (conf.responseType) req.responseType = conf.responseType; if (conf.onerror) req.onerror = conf.onerror; if (conf.onprogress) req.onprogress = conf.onprogress; req.onreadystatechange = function (event) { if (req.readyState === 4) { if (req.status !== 200 && req.status != 304) { if (conf.onerror) conf.onerror(event, false); return; } if (conf.onload) { conf.onload(req); } } }; req.send(conf.data); return req; }; }