├── .gitignore
├── LICENSE.md
├── index.html
├── lib
├── MIDI
│ ├── AudioDetect.js
│ ├── Base64.js
│ ├── DOMLoader.XMLHttp.js
│ ├── LoadPlugin.js
│ ├── Player.js
│ ├── Plugin.js
│ ├── base64binary.js
│ └── jasmid
│ │ ├── LICENSE
│ │ ├── midifile.js
│ │ ├── replayer.js
│ │ └── stream.js
├── dropzone.js
├── instruments.js
├── jquery.min.js
├── jsmidgen.js
├── midi-converter.js
└── midi-file-parser.js
├── scripts
├── defaultsong.js
├── midifile.js
└── sequencer.js
├── soundfont
├── acoustic_grand_piano-mp3.js
├── acoustic_grand_piano-ogg.js
├── synth_drum-mp3.js
└── synth_drum-ogg.js
└── styles
└── style.css
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2015 Maximillian von Briesen
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | MIDI Seqeuncer
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
31 |
32 |
36 |
37 |
38 |
39 |
40 |
41 |
--------------------------------------------------------------------------------
/lib/MIDI/AudioDetect.js:
--------------------------------------------------------------------------------
1 | /*
2 | -------------------------------------
3 | MIDI.audioDetect : 0.3
4 | -------------------------------------
5 | https://github.com/mudcube/MIDI.js
6 | -------------------------------------
7 | Probably, Maybe, No... Absolutely!
8 | -------------------------------------
9 | Test to see what types of MIME types are playable by the browser.
10 | -------------------------------------
11 | */
12 |
13 | if (typeof(MIDI) === "undefined") var MIDI = {};
14 |
15 | (function() { "use strict";
16 |
17 | var supports = {};
18 | var pending = 0;
19 | var canPlayThrough = function (src) {
20 | pending ++;
21 | var audio = new Audio();
22 | var mime = src.split(";")[0];
23 | audio.id = "audio";
24 | audio.setAttribute("preload", "auto");
25 | audio.setAttribute("audiobuffer", true);
26 | audio.addEventListener("error", function() {
27 | supports[mime] = false;
28 | pending --;
29 | }, false);
30 | audio.addEventListener("canplaythrough", function() {
31 | supports[mime] = true;
32 | pending --;
33 | }, false);
34 | audio.src = "data:" + src;
35 | document.body.appendChild(audio);
36 | };
37 |
38 | MIDI.audioDetect = function(callback) {
39 | // check whether tag is supported
40 | if (typeof(Audio) === "undefined") return callback({});
41 | // check whether canPlayType is supported
42 | var audio = new Audio();
43 | if (typeof(audio.canPlayType) === "undefined") return callback(supports);
44 | // see what we can learn from the browser
45 | var vorbis = audio.canPlayType('audio/ogg; codecs="vorbis"');
46 | vorbis = (vorbis === "probably" || vorbis === "maybe");
47 | var mpeg = audio.canPlayType('audio/mpeg');
48 | mpeg = (mpeg === "probably" || mpeg === "maybe");
49 | // maybe nothing is supported
50 | if (!vorbis && !mpeg) {
51 | callback(supports);
52 | return;
53 | }
54 | // or maybe something is supported
55 | 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=");
56 | if (mpeg) canPlayThrough("audio/mpeg;base64,/+MYxAAAAANIAUAAAASEEB/jwOFM/0MM/90b/+RhST//w4NFwOjf///PZu////9lns5GFDv//l9GlUIEEIAAAgIg8Ir/JGq3/+MYxDsLIj5QMYcoAP0dv9HIjUcH//yYSg+CIbkGP//8w0bLVjUP///3Z0x5QCAv/yLjwtGKTEFNRTMuOTeqqqqqqqqqqqqq/+MYxEkNmdJkUYc4AKqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq");
57 | // lets find out!
58 | var time = (new Date()).getTime();
59 | var interval = window.setInterval(function() {
60 | var now = (new Date()).getTime();
61 | var maxExecution = now - time > 5000;
62 | if (!pending || maxExecution) {
63 | window.clearInterval(interval);
64 | callback(supports);
65 | }
66 | }, 1);
67 | };
68 |
69 | })();
--------------------------------------------------------------------------------
/lib/MIDI/Base64.js:
--------------------------------------------------------------------------------
1 | // http://ntt.cc/2008/01/19/base64-encoder-decoder-with-javascript.html
2 |
3 | // window.atob and window.btoa
4 |
5 | (function (window) {
6 |
7 | var keyStr = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
8 |
9 | window.btoa || (window.btoa = function encode64(input) {
10 | input = escape(input);
11 | var output = "";
12 | var chr1, chr2, chr3 = "";
13 | var enc1, enc2, enc3, enc4 = "";
14 | var i = 0;
15 | do {
16 | chr1 = input.charCodeAt(i++);
17 | chr2 = input.charCodeAt(i++);
18 | chr3 = input.charCodeAt(i++);
19 | enc1 = chr1 >> 2;
20 | enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);
21 | enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);
22 | enc4 = chr3 & 63;
23 | if (isNaN(chr2)) {
24 | enc3 = enc4 = 64;
25 | } else if (isNaN(chr3)) {
26 | enc4 = 64;
27 | }
28 | output = output + keyStr.charAt(enc1) + keyStr.charAt(enc2) + keyStr.charAt(enc3) + keyStr.charAt(enc4);
29 | chr1 = chr2 = chr3 = "";
30 | enc1 = enc2 = enc3 = enc4 = "";
31 | } while (i < input.length);
32 | return output;
33 | });
34 |
35 | window.atob || (window.atob = function(input) {
36 | var output = "";
37 | var chr1, chr2, chr3 = "";
38 | var enc1, enc2, enc3, enc4 = "";
39 | var i = 0;
40 | // remove all characters that are not A-Z, a-z, 0-9, +, /, or =
41 | var base64test = /[^A-Za-z0-9\+\/\=]/g;
42 | if (base64test.exec(input)) {
43 | alert("There were invalid base64 characters in the input text.\n" + "Valid base64 characters are A-Z, a-z, 0-9, '+', '/',and '='\n" + "Expect errors in decoding.");
44 | }
45 | input = input.replace(/[^A-Za-z0-9\+\/\=]/g, "");
46 | do {
47 | enc1 = keyStr.indexOf(input.charAt(i++));
48 | enc2 = keyStr.indexOf(input.charAt(i++));
49 | enc3 = keyStr.indexOf(input.charAt(i++));
50 | enc4 = keyStr.indexOf(input.charAt(i++));
51 | chr1 = (enc1 << 2) | (enc2 >> 4);
52 | chr2 = ((enc2 & 15) << 4) | (enc3 >> 2);
53 | chr3 = ((enc3 & 3) << 6) | enc4;
54 | output = output + String.fromCharCode(chr1);
55 | if (enc3 != 64) {
56 | output = output + String.fromCharCode(chr2);
57 | }
58 | if (enc4 != 64) {
59 | output = output + String.fromCharCode(chr3);
60 | }
61 | chr1 = chr2 = chr3 = "";
62 | enc1 = enc2 = enc3 = enc4 = "";
63 | } while (i < input.length);
64 | return unescape(output);
65 | });
66 |
67 | }(this));
68 |
--------------------------------------------------------------------------------
/lib/MIDI/DOMLoader.XMLHttp.js:
--------------------------------------------------------------------------------
1 | /*
2 |
3 | DOMLoader.XMLHttp
4 | --------------------------
5 | DOMLoader.sendRequest({
6 | url: "./dir/something.extension",
7 | data: "test!",
8 | onerror: function(event) {
9 | console.log(event);
10 | },
11 | onload: function(response) {
12 | console.log(response.responseText);
13 | },
14 | onprogress: function (event) {
15 | var percent = event.loaded / event.total * 100 >> 0;
16 | loader.message("loading: " + percent + "%");
17 | }
18 | });
19 |
20 | */
21 |
22 | if (typeof(DOMLoader) === "undefined") var DOMLoader = {};
23 |
24 | // Add XMLHttpRequest when not available
25 |
26 | if (typeof (XMLHttpRequest) === "undefined") {
27 | var XMLHttpRequest;
28 | (function () { // find equivalent for IE
29 | var factories = [
30 | function () {
31 | return new ActiveXObject("Msxml2.XMLHTTP")
32 | }, function () {
33 | return new ActiveXObject("Msxml3.XMLHTTP")
34 | }, function () {
35 | return new ActiveXObject("Microsoft.XMLHTTP")
36 | }];
37 | for (var i = 0; i < factories.length; i++) {
38 | try {
39 | factories[i]();
40 | } catch (e) {
41 | continue;
42 | }
43 | break;
44 | }
45 | XMLHttpRequest = factories[i];
46 | })();
47 | }
48 |
49 | if (typeof ((new XMLHttpRequest()).responseText) === "undefined") {
50 | // http://stackoverflow.com/questions/1919972/how-do-i-access-xhr-responsebody-for-binary-data-from-javascript-in-ie
51 | var IEBinaryToArray_ByteStr_Script =
52 | "\r\n"+
53 | "\r\n";
67 |
68 | // inject VBScript
69 | document.write(IEBinaryToArray_ByteStr_Script);
70 |
71 | DOMLoader.sendRequest = function(conf) {
72 | // helper to convert from responseBody to a "responseText" like thing
73 | function getResponseText(binary) {
74 | var byteMapping = {};
75 | for (var i = 0; i < 256; i++) {
76 | for (var j = 0; j < 256; j++) {
77 | byteMapping[String.fromCharCode(i + j * 256)] = String.fromCharCode(i) + String.fromCharCode(j);
78 | }
79 | }
80 | // call into VBScript utility fns
81 | var rawBytes = IEBinaryToArray_ByteStr(binary);
82 | var lastChr = IEBinaryToArray_ByteStr_Last(binary);
83 | return rawBytes.replace(/[\s\S]/g, function (match) {
84 | return byteMapping[match];
85 | }) + lastChr;
86 | };
87 | //
88 | var req = XMLHttpRequest();
89 | req.open("GET", conf.url, true);
90 | if (conf.responseType) req.responseType = conf.responseType;
91 | if (conf.onerror) req.onerror = conf.onerror;
92 | if (conf.onprogress) req.onprogress = conf.onprogress;
93 | req.onreadystatechange = function (event) {
94 | if (req.readyState === 4) {
95 | if (req.status === 200) {
96 | req.responseText = getResponseText(req.responseBody);
97 | } else {
98 | req = false;
99 | }
100 | if (conf.onload) conf.onload(req);
101 | }
102 | };
103 | req.setRequestHeader("Accept-Charset", "x-user-defined");
104 | req.send(null);
105 | return req;
106 | }
107 | } else {
108 | DOMLoader.sendRequest = function(conf) {
109 | var req = new XMLHttpRequest();
110 | req.open(conf.data ? "POST" : "GET", conf.url, true);
111 | if (req.overrideMimeType) req.overrideMimeType("text/plain; charset=x-user-defined");
112 | if (conf.data) req.setRequestHeader('Content-type','application/x-www-form-urlencoded');
113 | if (conf.responseType) req.responseType = conf.responseType;
114 | if (conf.onerror) req.onerror = conf.onerror;
115 | if (conf.onprogress) req.onprogress = conf.onprogress;
116 | req.onreadystatechange = function (event) {
117 | if (req.readyState === 4) {
118 | if (req.status !== 200 && req.status != 304) {
119 | if (conf.onerror) conf.onerror(event, false);
120 | return;
121 | }
122 | if (conf.onload) {
123 | conf.onload(req);
124 | }
125 | }
126 | };
127 | req.send(conf.data);
128 | return req;
129 | };
130 | }
--------------------------------------------------------------------------------
/lib/MIDI/LoadPlugin.js:
--------------------------------------------------------------------------------
1 | /*
2 | -----------------------------------------------------------
3 | MIDI.loadPlugin : 0.1.2 : 01/22/2014
4 | -----------------------------------------------------------
5 | https://github.com/mudcube/MIDI.js
6 | -----------------------------------------------------------
7 | MIDI.loadPlugin({
8 | targetFormat: "mp3", // optionally can force to use MP3 (for instance on mobile networks)
9 | instrument: "acoustic_grand_piano", // or 1 (default)
10 | instruments: [ "acoustic_grand_piano", "acoustic_guitar_nylon" ], // or multiple instruments
11 | callback: function() { }
12 | });
13 | */
14 |
15 | if (typeof (MIDI) === "undefined") var MIDI = {};
16 | if (typeof (MIDI.Soundfont) === "undefined") MIDI.Soundfont = {};
17 |
18 | (function() { "use strict";
19 |
20 | var USE_JAZZMIDI = false; // Turn on to support JazzMIDI Plugin
21 |
22 | MIDI.loadPlugin = function(conf) {
23 | if (typeof(conf) === "function") conf = {
24 | callback: conf
25 | };
26 | /// Get the instrument name.
27 | var instruments = conf.instruments || conf.instrument || "acoustic_grand_piano";
28 | if (typeof(instruments) !== "object") instruments = [ instruments ];
29 | ///
30 | for (var n = 0; n < instruments.length; n ++) {
31 | var instrument = instruments[n];
32 | if (typeof(instrument) === "number") {
33 | instruments[n] = MIDI.GeneralMIDI.byId[instrument];
34 | }
35 | };
36 | ///
37 | MIDI.soundfontUrl = conf.soundfontUrl || MIDI.soundfontUrl || "./soundfont/";
38 | /// Detect the best type of audio to use.
39 | MIDI.audioDetect(function(types) {
40 | var api = "";
41 | // use the most appropriate plugin if not specified
42 | if (apis[conf.api]) {
43 | api = conf.api;
44 | } else if (apis[window.location.hash.substr(1)]) {
45 | api = window.location.hash.substr(1);
46 | } else if (USE_JAZZMIDI && navigator.requestMIDIAccess) {
47 | api = "webmidi";
48 | } else if (window.webkitAudioContext || window.AudioContext) { // Chrome
49 | api = "webaudio";
50 | } else if (window.Audio) { // Firefox
51 | api = "audiotag";
52 | } else { // Internet Explorer
53 | api = "flash";
54 | }
55 | ///
56 | if (!connect[api]) return;
57 | // use audio/ogg when supported
58 | if (conf.targetFormat) {
59 | var filetype = conf.targetFormat;
60 | } else { // use best quality
61 | var filetype = types["audio/ogg"] ? "ogg" : "mp3";
62 | }
63 | // load the specified plugin
64 | MIDI.lang = api;
65 | MIDI.supports = types;
66 | connect[api](filetype, instruments, conf);
67 | });
68 | };
69 |
70 | ///
71 |
72 | var connect = {};
73 |
74 | connect.webmidi = function(filetype, instruments, conf) {
75 | if (MIDI.loader) MIDI.loader.message("Web MIDI API...");
76 | MIDI.WebMIDI.connect(conf);
77 | };
78 |
79 | connect.flash = function(filetype, instruments, conf) {
80 | // fairly quick, but requires loading of individual MP3s (more http requests).
81 | if (MIDI.loader) MIDI.loader.message("Flash API...");
82 | DOMLoader.script.add({
83 | src: conf.soundManagerUrl || "./inc/SoundManager2/script/soundmanager2.js",
84 | verify: "SoundManager",
85 | callback: function () {
86 | MIDI.Flash.connect(instruments, conf);
87 | }
88 | });
89 | };
90 |
91 | connect.audiotag = function(filetype, instruments, conf) {
92 | if (MIDI.loader) MIDI.loader.message("HTML5 Audio API...");
93 | // works ok, kinda like a drunken tuna fish, across the board.
94 | var queue = createQueue({
95 | items: instruments,
96 | getNext: function(instrumentId) {
97 | DOMLoader.sendRequest({
98 | url: MIDI.soundfontUrl + instrumentId + "-" + filetype + ".js",
99 | onprogress: getPercent,
100 | onload: function (response) {
101 | addSoundfont(response.responseText);
102 | if (MIDI.loader) MIDI.loader.update(null, "Downloading", 100);
103 | queue.getNext();
104 | }
105 | });
106 | },
107 | onComplete: function() {
108 | MIDI.AudioTag.connect(conf);
109 | }
110 | });
111 | };
112 |
113 | connect.webaudio = function(filetype, instruments, conf) {
114 | if (MIDI.loader) MIDI.loader.message("Web Audio API...");
115 | // works awesome! safari, chrome and firefox support.
116 | var queue = createQueue({
117 | items: instruments,
118 | getNext: function(instrumentId) {
119 | DOMLoader.sendRequest({
120 | url: MIDI.soundfontUrl + instrumentId + "-" + filetype + ".js",
121 | onprogress: getPercent,
122 | onload: function(response) {
123 | addSoundfont(response.responseText);
124 | if (MIDI.loader) MIDI.loader.update(null, "Downloading...", 100);
125 | queue.getNext();
126 | }
127 | });
128 | },
129 | onComplete: function() {
130 | MIDI.WebAudio.connect(conf);
131 | }
132 | });
133 | };
134 |
135 | /// Helpers
136 |
137 | var apis = {
138 | "webmidi": true,
139 | "webaudio": true,
140 | "audiotag": true,
141 | "flash": true
142 | };
143 |
144 | var addSoundfont = function(text) {
145 | var script = document.createElement("script");
146 | script.language = "javascript";
147 | script.type = "text/javascript";
148 | script.text = text;
149 | document.body.appendChild(script);
150 | };
151 |
152 | var getPercent = function(event) {
153 | if (!this.totalSize) {
154 | if (this.getResponseHeader("Content-Length-Raw")) {
155 | this.totalSize = parseInt(this.getResponseHeader("Content-Length-Raw"));
156 | } else {
157 | this.totalSize = event.total;
158 | }
159 | }
160 | ///
161 | var percent = this.totalSize ? Math.round(event.loaded / this.totalSize * 100) : "";
162 | if (MIDI.loader) MIDI.loader.update(null, "Downloading...", percent);
163 | };
164 |
165 | var createQueue = function(conf) {
166 | var self = {};
167 | self.queue = [];
168 | for (var key in conf.items) {
169 | if (conf.items.hasOwnProperty(key)) {
170 | self.queue.push(conf.items[key]);
171 | }
172 | }
173 | self.getNext = function() {
174 | if (!self.queue.length) return conf.onComplete();
175 | conf.getNext(self.queue.shift());
176 | };
177 | setTimeout(self.getNext, 1);
178 | return self;
179 | };
180 |
181 | })();
--------------------------------------------------------------------------------
/lib/MIDI/Player.js:
--------------------------------------------------------------------------------
1 | /*
2 | -------------------------------------
3 | MIDI.Player : 0.3
4 | -------------------------------------
5 | https://github.com/mudcube/MIDI.js
6 | -------------------------------------
7 | #jasmid
8 | -------------------------------------
9 | */
10 |
11 | if (typeof (MIDI) === "undefined") var MIDI = {};
12 | if (typeof (MIDI.Player) === "undefined") MIDI.Player = {};
13 |
14 | (function() { "use strict";
15 |
16 | var root = MIDI.Player;
17 | root.callback = undefined; // your custom callback goes here!
18 | root.currentTime = 0;
19 | root.endTime = 0;
20 | root.restart = 0;
21 | root.playing = false;
22 | root.timeWarp = 1;
23 |
24 | //
25 | root.start =
26 | root.resume = function () {
27 | if (root.currentTime < -1) root.currentTime = -1;
28 | startAudio(root.currentTime);
29 | };
30 |
31 | root.pause = function () {
32 | var tmp = root.restart;
33 | stopAudio();
34 | root.restart = tmp;
35 | };
36 |
37 | root.stop = function () {
38 | stopAudio();
39 | root.restart = 0;
40 | root.currentTime = 0;
41 | };
42 |
43 | root.addListener = function(callback) {
44 | onMidiEvent = callback;
45 | };
46 |
47 | root.removeListener = function() {
48 | onMidiEvent = undefined;
49 | };
50 |
51 | root.clearAnimation = function() {
52 | if (root.interval) {
53 | window.clearInterval(root.interval);
54 | }
55 | };
56 |
57 | root.setAnimation = function(config) {
58 | var callback = (typeof(config) === "function") ? config : config.callback;
59 | var interval = config.interval || 30;
60 | var currentTime = 0;
61 | var tOurTime = 0;
62 | var tTheirTime = 0;
63 | //
64 | root.clearAnimation();
65 | root.interval = window.setInterval(function () {
66 | if (root.endTime === 0) return;
67 | if (root.playing) {
68 | currentTime = (tTheirTime === root.currentTime) ? tOurTime - (new Date).getTime() : 0;
69 | if (root.currentTime === 0) {
70 | currentTime = 0;
71 | } else {
72 | currentTime = root.currentTime - currentTime;
73 | }
74 | if (tTheirTime !== root.currentTime) {
75 | tOurTime = (new Date).getTime();
76 | tTheirTime = root.currentTime;
77 | }
78 | } else { // paused
79 | currentTime = root.currentTime;
80 | }
81 | var endTime = root.endTime;
82 | var percent = currentTime / endTime;
83 | var total = currentTime / 1000;
84 | var minutes = total / 60;
85 | var seconds = total - (minutes * 60);
86 | var t1 = minutes * 60 + seconds;
87 | var t2 = (endTime / 1000);
88 | if (t2 - t1 < -1) return;
89 | callback({
90 | now: t1,
91 | end: t2,
92 | events: noteRegistrar
93 | });
94 | }, interval);
95 | };
96 |
97 | // helpers
98 |
99 | root.loadMidiFile = function() { // reads midi into javascript array of events
100 | root.replayer = new Replayer(MidiFile(root.currentData), root.timeWarp);
101 | root.data = root.replayer.getData();
102 | root.endTime = getLength();
103 | };
104 |
105 | root.loadFile = function (file, callback) {
106 | root.stop();
107 | if (file.indexOf("base64,") !== -1) {
108 | var data = window.atob(file.split(",")[1]);
109 | root.currentData = data;
110 | root.loadMidiFile();
111 | if (callback) callback(data);
112 | return;
113 | }
114 | ///
115 | var fetch = new XMLHttpRequest();
116 | fetch.open('GET', file);
117 | fetch.overrideMimeType("text/plain; charset=x-user-defined");
118 | fetch.onreadystatechange = function () {
119 | if (this.readyState === 4 && this.status === 200) {
120 | var t = this.responseText || "";
121 | var ff = [];
122 | var mx = t.length;
123 | var scc = String.fromCharCode;
124 | for (var z = 0; z < mx; z++) {
125 | ff[z] = scc(t.charCodeAt(z) & 255);
126 | }
127 | var data = ff.join("");
128 | root.currentData = data;
129 | root.loadMidiFile();
130 | if (callback) callback(data);
131 | }
132 | };
133 | fetch.send();
134 | };
135 |
136 | // Playing the audio
137 |
138 | var eventQueue = []; // hold events to be triggered
139 | var queuedTime; //
140 | var startTime = 0; // to measure time elapse
141 | var noteRegistrar = {}; // get event for requested note
142 | var onMidiEvent = undefined; // listener callback
143 | var scheduleTracking = function (channel, note, currentTime, offset, message, velocity) {
144 | var interval = window.setTimeout(function () {
145 | var data = {
146 | channel: channel,
147 | note: note,
148 | now: currentTime,
149 | end: root.endTime,
150 | message: message,
151 | velocity: velocity
152 | };
153 | //
154 | if (message === 128) {
155 | delete noteRegistrar[note];
156 | } else {
157 | noteRegistrar[note] = data;
158 | }
159 | if (onMidiEvent) {
160 | onMidiEvent(data);
161 | }
162 | root.currentTime = currentTime;
163 | if (root.currentTime === queuedTime && queuedTime < root.endTime) { // grab next sequence
164 | startAudio(queuedTime, true);
165 | }
166 | }, currentTime - offset);
167 | return interval;
168 | };
169 |
170 | var getContext = function() {
171 | if (MIDI.lang === 'WebAudioAPI') {
172 | return MIDI.Player.ctx;
173 | } else if (!root.ctx) {
174 | root.ctx = { currentTime: 0 };
175 | }
176 | return root.ctx;
177 | };
178 |
179 | var getLength = function() {
180 | var data = root.data;
181 | var length = data.length;
182 | var totalTime = 0.5;
183 | for (var n = 0; n < length; n++) {
184 | totalTime += data[n][1];
185 | }
186 | return totalTime;
187 | };
188 |
189 | var startAudio = function (currentTime, fromCache) {
190 | if (!root.replayer) return;
191 | if (!fromCache) {
192 | if (typeof (currentTime) === "undefined") currentTime = root.restart;
193 | if (root.playing) stopAudio();
194 | root.playing = true;
195 | root.data = root.replayer.getData();
196 | root.endTime = getLength();
197 | }
198 | var note;
199 | var offset = 0;
200 | var messages = 0;
201 | var data = root.data;
202 | var ctx = getContext();
203 | var length = data.length;
204 | //
205 | queuedTime = 0.5;
206 | startTime = ctx.currentTime;
207 | //
208 | for (var n = 0; n < length && messages < 100; n++) {
209 | queuedTime += data[n][1];
210 | if (queuedTime < currentTime) {
211 | offset = queuedTime;
212 | continue;
213 | }
214 | currentTime = queuedTime - offset;
215 | var event = data[n][0].event;
216 | if (event.type !== "channel") continue;
217 | var channel = event.channel;
218 | switch (event.subtype) {
219 | case 'noteOn':
220 | if (MIDI.channels[channel].mute) break;
221 | note = event.noteNumber - (root.MIDIOffset || 0);
222 | eventQueue.push({
223 | event: event,
224 | source: MIDI.noteOn(channel, event.noteNumber, event.velocity, currentTime / 1000 + ctx.currentTime),
225 | interval: scheduleTracking(channel, note, queuedTime, offset, 144, event.velocity)
226 | });
227 | messages ++;
228 | break;
229 | case 'noteOff':
230 | if (MIDI.channels[channel].mute) break;
231 | note = event.noteNumber - (root.MIDIOffset || 0);
232 | eventQueue.push({
233 | event: event,
234 | source: MIDI.noteOff(channel, event.noteNumber, currentTime / 1000 + ctx.currentTime),
235 | interval: scheduleTracking(channel, note, queuedTime, offset, 128)
236 | });
237 | break;
238 | default:
239 | break;
240 | }
241 | }
242 | };
243 |
244 | var stopAudio = function () {
245 | var ctx = getContext();
246 | root.playing = false;
247 | root.restart += (ctx.currentTime - startTime) * 1000;
248 | // stop the audio, and intervals
249 | while (eventQueue.length) {
250 | var o = eventQueue.pop();
251 | window.clearInterval(o.interval);
252 | if (!o.source) continue; // is not webaudio
253 | if (typeof(o.source) === "number") {
254 | window.clearTimeout(o.source);
255 | } else { // webaudio
256 | o.source.disconnect(0);
257 | }
258 | }
259 | // run callback to cancel any notes still playing
260 | for (var key in noteRegistrar) {
261 | var o = noteRegistrar[key]
262 | if (noteRegistrar[key].message === 144 && onMidiEvent) {
263 | onMidiEvent({
264 | channel: o.channel,
265 | note: o.note,
266 | now: o.now,
267 | end: o.end,
268 | message: 128,
269 | velocity: o.velocity
270 | });
271 | }
272 | }
273 | // reset noteRegistrar
274 | noteRegistrar = {};
275 | };
276 |
277 | })();
--------------------------------------------------------------------------------
/lib/MIDI/Plugin.js:
--------------------------------------------------------------------------------
1 | /*
2 | --------------------------------------------
3 | MIDI.Plugin : 0.3.2 : 2013/01/26
4 | --------------------------------------------
5 | https://github.com/mudcube/MIDI.js
6 | --------------------------------------------
7 | Inspired by javax.sound.midi (albeit a super simple version):
8 | http://docs.oracle.com/javase/6/docs/api/javax/sound/midi/package-summary.html
9 | --------------------------------------------
10 | Technologies:
11 | MIDI.WebMIDI
12 | MIDI.WebAudio
13 | MIDI.Flash
14 | MIDI.AudioTag
15 | --------------------------------------------
16 | Helpers:
17 | MIDI.GeneralMIDI
18 | MIDI.channels
19 | MIDI.keyToNote
20 | MIDI.noteToKey
21 | */
22 |
23 | if (typeof (MIDI) === "undefined") var MIDI = {};
24 |
25 | (function() { "use strict";
26 |
27 | var setPlugin = function(root) {
28 | MIDI.api = root.api;
29 | MIDI.setVolume = root.setVolume;
30 | MIDI.programChange = root.programChange;
31 | MIDI.noteOn = root.noteOn;
32 | MIDI.noteOff = root.noteOff;
33 | MIDI.chordOn = root.chordOn;
34 | MIDI.chordOff = root.chordOff;
35 | MIDI.stopAllNotes = root.stopAllNotes;
36 | MIDI.getInput = root.getInput;
37 | MIDI.getOutputs = root.getOutputs;
38 | };
39 |
40 | /*
41 | --------------------------------------------
42 | Web MIDI API - Native Soundbank
43 | --------------------------------------------
44 | https://dvcs.w3.org/hg/audio/raw-file/tip/midi/specification.html
45 | --------------------------------------------
46 | */
47 |
48 | (function () {
49 | var plugin = null;
50 | var output = null;
51 | var channels = [];
52 | var root = MIDI.WebMIDI = {
53 | api: "webmidi"
54 | };
55 | root.setVolume = function (channel, volume) { // set channel volume
56 | output.send([0xB0 + channel, 0x07, volume]);
57 | };
58 |
59 | root.programChange = function (channel, program) { // change channel instrument
60 | output.send([0xC0 + channel, program]);
61 | };
62 |
63 | root.noteOn = function (channel, note, velocity, delay) {
64 | output.send([0x90 + channel, note, velocity], delay * 1000);
65 | };
66 |
67 | root.noteOff = function (channel, note, delay) {
68 | output.send([0x80 + channel, note, 0], delay * 1000);
69 | };
70 |
71 | root.chordOn = function (channel, chord, velocity, delay) {
72 | for (var n = 0; n < chord.length; n ++) {
73 | var note = chord[n];
74 | output.send([0x90 + channel, note, velocity], delay * 1000);
75 | }
76 | };
77 |
78 | root.chordOff = function (channel, chord, delay) {
79 | for (var n = 0; n < chord.length; n ++) {
80 | var note = chord[n];
81 | output.send([0x80 + channel, note, 0], delay * 1000);
82 | }
83 | };
84 |
85 | root.stopAllNotes = function () {
86 | for (var channel = 0; channel < 16; channel ++) {
87 | output.send([0xB0 + channel, 0x7B, 0]);
88 | }
89 | };
90 |
91 | root.getInput = function () {
92 | return plugin.getInputs();
93 | };
94 |
95 | root.getOutputs = function () {
96 | return plugin.getOutputs();
97 | };
98 |
99 | root.connect = function (conf) {
100 | setPlugin(root);
101 | navigator.requestMIDIAccess().then(function (access) {
102 | plugin = access;
103 | output = plugin.outputs()[0];
104 | if (conf.callback) conf.callback();
105 | }, function (err) { // well at least we tried!
106 | if (window.AudioContext || window.webkitAudioContext) { // Chrome
107 | conf.api = "webaudio";
108 | } else if (window.Audio) { // Firefox
109 | conf.api = "audiotag";
110 | } else { // Internet Explorer
111 | conf.api = "flash";
112 | }
113 | MIDI.loadPlugin(conf);
114 | });
115 | };
116 | })();
117 |
118 | /*
119 | --------------------------------------------
120 | Web Audio API - OGG or MPEG Soundbank
121 | --------------------------------------------
122 | https://dvcs.w3.org/hg/audio/raw-file/tip/webaudio/specification.html
123 | --------------------------------------------
124 | */
125 |
126 | if (window.AudioContext || window.webkitAudioContext) (function () {
127 |
128 | var AudioContext = window.AudioContext || window.webkitAudioContext;
129 | var root = MIDI.WebAudio = {
130 | api: "webaudio"
131 | };
132 | var ctx;
133 | var sources = {};
134 | var masterVolume = 127;
135 | var audioBuffers = {};
136 | var audioLoader = function (instrument, urlList, index, bufferList, callback) {
137 | var synth = MIDI.GeneralMIDI.byName[instrument];
138 | var instrumentId = synth.number;
139 | var url = urlList[index];
140 | if (!MIDI.Soundfont[instrument][url]) { // missing soundfont
141 | return callback(instrument);
142 | }
143 | var base64 = MIDI.Soundfont[instrument][url].split(",")[1];
144 | var buffer = Base64Binary.decodeArrayBuffer(base64);
145 | ctx.decodeAudioData(buffer, function (buffer) {
146 | var msg = url;
147 | while (msg.length < 3) msg += " ";
148 | if (typeof (MIDI.loader) !== "undefined") {
149 | MIDI.loader.update(null, synth.instrument + " Processing: " + (index / 87 * 100 >> 0) + "% " + msg);
150 | }
151 | buffer.id = url;
152 | bufferList[index] = buffer;
153 | //
154 | if (bufferList.length === urlList.length) {
155 | while (bufferList.length) {
156 | buffer = bufferList.pop();
157 | if (!buffer) continue;
158 | var nodeId = MIDI.keyToNote[buffer.id];
159 | audioBuffers[instrumentId + "" + nodeId] = buffer;
160 | }
161 | callback(instrument);
162 | }
163 | });
164 | };
165 |
166 | root.setVolume = function (channel, volume) {
167 | masterVolume = volume;
168 | };
169 |
170 | root.programChange = function (channel, program) {
171 | MIDI.channels[channel].instrument = program;
172 | };
173 |
174 | root.noteOn = function (channel, note, velocity, delay) {
175 | /// check whether the note exists
176 | if (!MIDI.channels[channel]) return;
177 | var instrument = MIDI.channels[channel].instrument;
178 | if (!audioBuffers[instrument + "" + note]) return;
179 | /// convert relative delay to absolute delay
180 | if (delay < ctx.currentTime) delay += ctx.currentTime;
181 | /// crate audio buffer
182 | var source = ctx.createBufferSource();
183 | sources[channel + "" + note] = source;
184 | source.buffer = audioBuffers[instrument + "" + note];
185 | source.connect(ctx.destination);
186 | ///
187 | if (ctx.createGain) { // firefox
188 | source.gainNode = ctx.createGain();
189 | } else { // chrome
190 | source.gainNode = ctx.createGainNode();
191 | }
192 | var value = (velocity / 127) * (masterVolume / 127) * 2 - 1;
193 | source.gainNode.connect(ctx.destination);
194 | source.gainNode.gain.value = Math.max(-1, value);
195 | source.connect(source.gainNode);
196 | if (source.noteOn) { // old api
197 | source.noteOn(delay || 0);
198 | } else { // new api
199 | source.start(delay || 0);
200 | }
201 | return source;
202 | };
203 |
204 | root.noteOff = function (channel, note, delay) {
205 | delay = delay || 0;
206 | if (delay < ctx.currentTime) delay += ctx.currentTime;
207 | var source = sources[channel + "" + note];
208 | if (!source) return;
209 | if (source.gainNode) {
210 | // @Miranet: "the values of 0.2 and 0.3 could ofcourse be used as
211 | // a 'release' parameter for ADSR like time settings."
212 | // add { "metadata": { release: 0.3 } } to soundfont files
213 | var gain = source.gainNode.gain;
214 | gain.linearRampToValueAtTime(gain.value, delay);
215 | gain.linearRampToValueAtTime(-1, delay + 0.2);
216 | }
217 | if (source.noteOff) { // old api
218 | source.noteOff(delay + 0.3);
219 | } else {
220 | source.stop(delay + 0.3);
221 | }
222 | ///
223 | delete sources[channel + "" + note];
224 | };
225 |
226 | root.chordOn = function (channel, chord, velocity, delay) {
227 | var ret = {}, note;
228 | for (var n = 0, length = chord.length; n < length; n++) {
229 | ret[note = chord[n]] = root.noteOn(channel, note, velocity, delay);
230 | }
231 | return ret;
232 | };
233 |
234 | root.chordOff = function (channel, chord, delay) {
235 | var ret = {}, note;
236 | for (var n = 0, length = chord.length; n < length; n++) {
237 | ret[note = chord[n]] = root.noteOff(channel, note, delay);
238 | }
239 | return ret;
240 | };
241 |
242 | root.stopAllNotes = function () {
243 | for (var source in sources) {
244 | var delay = 0;
245 | if (delay < ctx.currentTime) delay += ctx.currentTime;
246 | // @Miranet: "the values of 0.2 and 0.3 could ofcourse be used as
247 | // a 'release' parameter for ADSR like time settings."
248 | // add { "metadata": { release: 0.3 } } to soundfont files
249 | sources[source].gain.linearRampToValueAtTime(1, delay);
250 | sources[source].gain.linearRampToValueAtTime(0, delay + 0.2);
251 | sources[source].noteOff(delay + 0.3);
252 | delete sources[source];
253 | }
254 | };
255 |
256 | root.connect = function (conf) {
257 | setPlugin(root);
258 | //
259 | MIDI.Player.ctx = ctx = new AudioContext();
260 | ///
261 | var urlList = [];
262 | var keyToNote = MIDI.keyToNote;
263 | for (var key in keyToNote) urlList.push(key);
264 | var bufferList = [];
265 | var pending = {};
266 | var oncomplete = function(instrument) {
267 | delete pending[instrument];
268 | for (var key in pending) break;
269 | if (!key) conf.callback();
270 | };
271 | for (var instrument in MIDI.Soundfont) {
272 | pending[instrument] = true;
273 | for (var i = 0; i < urlList.length; i++) {
274 | audioLoader(instrument, urlList, i, bufferList, oncomplete);
275 | }
276 | }
277 | };
278 | })();
279 |
280 | /*
281 | --------------------------------------------
282 | AudioTag - OGG or MPEG Soundbank
283 | --------------------------------------------
284 | http://dev.w3.org/html5/spec/Overview.html#the-audio-element
285 | --------------------------------------------
286 | */
287 |
288 | if (window.Audio) (function () {
289 |
290 | var root = MIDI.AudioTag = {
291 | api: "audiotag"
292 | };
293 | var note2id = {};
294 | var volume = 127; // floating point
295 | var channel_nid = -1; // current channel
296 | var channels = []; // the audio channels
297 | var channelInstrumentNoteIds = []; // instrumentId + noteId that is currently playing in each 'channel', for routing noteOff/chordOff calls
298 | var notes = {}; // the piano keys
299 | for (var nid = 0; nid < 12; nid++) {
300 | channels[nid] = new Audio();
301 | }
302 |
303 | var playChannel = function (channel, note) {
304 | if (!MIDI.channels[channel]) return;
305 | var instrument = MIDI.channels[channel].instrument;
306 | var instrumentId = MIDI.GeneralMIDI.byId[instrument].id;
307 | var note = notes[note];
308 | if (!note) return;
309 | var instrumentNoteId = instrumentId + "" + note.id;
310 | var nid = (channel_nid + 1) % channels.length;
311 | var audio = channels[nid];
312 | channelInstrumentNoteIds[ nid ] = instrumentNoteId;
313 | audio.src = MIDI.Soundfont[instrumentId][note.id];
314 | audio.volume = volume / 127;
315 | audio.play();
316 | channel_nid = nid;
317 | };
318 |
319 | var stopChannel = function (channel, note) {
320 | if (!MIDI.channels[channel]) return;
321 | var instrument = MIDI.channels[channel].instrument;
322 | var instrumentId = MIDI.GeneralMIDI.byId[instrument].id;
323 | var note = notes[note];
324 | if (!note) return;
325 | var instrumentNoteId = instrumentId + "" + note.id;
326 |
327 | for(var i=0;i> 0;
617 | var name = number2key[n % 12] + octave;
618 | MIDI.keyToNote[name] = n;
619 | MIDI.noteToKey[n] = name;
620 | }
621 | })();
622 |
623 | })();
--------------------------------------------------------------------------------
/lib/MIDI/base64binary.js:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (c) 2011, Daniel Guerrero
3 | All rights reserved.
4 |
5 | Redistribution and use in source and binary forms, with or without
6 | modification, are permitted provided that the following conditions are met:
7 | * Redistributions of source code must retain the above copyright
8 | notice, this list of conditions and the following disclaimer.
9 | * Redistributions in binary form must reproduce the above copyright
10 | notice, this list of conditions and the following disclaimer in the
11 | documentation and/or other materials provided with the distribution.
12 | * Neither the name of the Daniel Guerrero nor the
13 | names of its contributors may be used to endorse or promote products
14 | derived from this software without specific prior written permission.
15 |
16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
17 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
19 | DISCLAIMED. IN NO EVENT SHALL DANIEL GUERRERO BE LIABLE FOR ANY
20 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
21 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
22 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
23 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
25 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26 | */
27 |
28 | var Base64Binary = {
29 | _keyStr : "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=",
30 |
31 | /* will return a Uint8Array type */
32 | decodeArrayBuffer: function(input) {
33 | var bytes = Math.ceil( (3*input.length) / 4.0);
34 | var ab = new ArrayBuffer(bytes);
35 | this.decode(input, ab);
36 |
37 | return ab;
38 | },
39 |
40 | decode: function(input, arrayBuffer) {
41 | //get last chars to see if are valid
42 | var lkey1 = this._keyStr.indexOf(input.charAt(input.length-1));
43 | var lkey2 = this._keyStr.indexOf(input.charAt(input.length-1));
44 |
45 | var bytes = Math.ceil( (3*input.length) / 4.0);
46 | if (lkey1 == 64) bytes--; //padding chars, so skip
47 | if (lkey2 == 64) bytes--; //padding chars, so skip
48 |
49 | var uarray;
50 | var chr1, chr2, chr3;
51 | var enc1, enc2, enc3, enc4;
52 | var i = 0;
53 | var j = 0;
54 |
55 | if (arrayBuffer)
56 | uarray = new Uint8Array(arrayBuffer);
57 | else
58 | uarray = new Uint8Array(bytes);
59 |
60 | input = input.replace(/[^A-Za-z0-9\+\/\=]/g, "");
61 |
62 | for (i=0; i> 4);
70 | chr2 = ((enc2 & 15) << 4) | (enc3 >> 2);
71 | chr3 = ((enc3 & 3) << 6) | enc4;
72 |
73 | uarray[i] = chr1;
74 | if (enc3 != 64) uarray[i+1] = chr2;
75 | if (enc4 != 64) uarray[i+2] = chr3;
76 | }
77 |
78 | return uarray;
79 | }
80 | };
--------------------------------------------------------------------------------
/lib/MIDI/jasmid/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2010, Matt Westcott & Ben Firshman
2 | All rights reserved.
3 |
4 | Redistribution and use in source and binary forms, with or without
5 | modification, are permitted provided that the following conditions are met:
6 |
7 | * Redistributions of source code must retain the above copyright notice, this
8 | list of conditions and the following disclaimer.
9 | * Redistributions in binary form must reproduce the above copyright notice,
10 | this list of conditions and the following disclaimer in the documentation
11 | and/or other materials provided with the distribution.
12 | * The names of its contributors may not be used to endorse or promote products
13 | derived from this software without specific prior written permission.
14 |
15 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
16 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
17 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
18 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
19 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
20 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
21 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
22 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
23 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
24 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25 |
--------------------------------------------------------------------------------
/lib/MIDI/jasmid/midifile.js:
--------------------------------------------------------------------------------
1 | /*
2 | class to parse the .mid file format
3 | (depends on stream.js)
4 | */
5 | function MidiFile(data) {
6 | function readChunk(stream) {
7 | var id = stream.read(4);
8 | var length = stream.readInt32();
9 | return {
10 | 'id': id,
11 | 'length': length,
12 | 'data': stream.read(length)
13 | };
14 | }
15 |
16 | var lastEventTypeByte;
17 |
18 | function readEvent(stream) {
19 | var event = {};
20 | event.deltaTime = stream.readVarInt();
21 | var eventTypeByte = stream.readInt8();
22 | if ((eventTypeByte & 0xf0) == 0xf0) {
23 | /* system / meta event */
24 | if (eventTypeByte == 0xff) {
25 | /* meta event */
26 | event.type = 'meta';
27 | var subtypeByte = stream.readInt8();
28 | var length = stream.readVarInt();
29 | switch(subtypeByte) {
30 | case 0x00:
31 | event.subtype = 'sequenceNumber';
32 | if (length != 2) throw "Expected length for sequenceNumber event is 2, got " + length;
33 | event.number = stream.readInt16();
34 | return event;
35 | case 0x01:
36 | event.subtype = 'text';
37 | event.text = stream.read(length);
38 | return event;
39 | case 0x02:
40 | event.subtype = 'copyrightNotice';
41 | event.text = stream.read(length);
42 | return event;
43 | case 0x03:
44 | event.subtype = 'trackName';
45 | event.text = stream.read(length);
46 | return event;
47 | case 0x04:
48 | event.subtype = 'instrumentName';
49 | event.text = stream.read(length);
50 | return event;
51 | case 0x05:
52 | event.subtype = 'lyrics';
53 | event.text = stream.read(length);
54 | return event;
55 | case 0x06:
56 | event.subtype = 'marker';
57 | event.text = stream.read(length);
58 | return event;
59 | case 0x07:
60 | event.subtype = 'cuePoint';
61 | event.text = stream.read(length);
62 | return event;
63 | case 0x20:
64 | event.subtype = 'midiChannelPrefix';
65 | if (length != 1) throw "Expected length for midiChannelPrefix event is 1, got " + length;
66 | event.channel = stream.readInt8();
67 | return event;
68 | case 0x2f:
69 | event.subtype = 'endOfTrack';
70 | if (length != 0) throw "Expected length for endOfTrack event is 0, got " + length;
71 | return event;
72 | case 0x51:
73 | event.subtype = 'setTempo';
74 | if (length != 3) throw "Expected length for setTempo event is 3, got " + length;
75 | event.microsecondsPerBeat = (
76 | (stream.readInt8() << 16)
77 | + (stream.readInt8() << 8)
78 | + stream.readInt8()
79 | )
80 | return event;
81 | case 0x54:
82 | event.subtype = 'smpteOffset';
83 | if (length != 5) throw "Expected length for smpteOffset event is 5, got " + length;
84 | var hourByte = stream.readInt8();
85 | event.frameRate = {
86 | 0x00: 24, 0x20: 25, 0x40: 29, 0x60: 30
87 | }[hourByte & 0x60];
88 | event.hour = hourByte & 0x1f;
89 | event.min = stream.readInt8();
90 | event.sec = stream.readInt8();
91 | event.frame = stream.readInt8();
92 | event.subframe = stream.readInt8();
93 | return event;
94 | case 0x58:
95 | event.subtype = 'timeSignature';
96 | if (length != 4) throw "Expected length for timeSignature event is 4, got " + length;
97 | event.numerator = stream.readInt8();
98 | event.denominator = Math.pow(2, stream.readInt8());
99 | event.metronome = stream.readInt8();
100 | event.thirtyseconds = stream.readInt8();
101 | return event;
102 | case 0x59:
103 | event.subtype = 'keySignature';
104 | if (length != 2) throw "Expected length for keySignature event is 2, got " + length;
105 | event.key = stream.readInt8(true);
106 | event.scale = stream.readInt8();
107 | return event;
108 | case 0x7f:
109 | event.subtype = 'sequencerSpecific';
110 | event.data = stream.read(length);
111 | return event;
112 | default:
113 | // console.log("Unrecognised meta event subtype: " + subtypeByte);
114 | event.subtype = 'unknown'
115 | event.data = stream.read(length);
116 | return event;
117 | }
118 | event.data = stream.read(length);
119 | return event;
120 | } else if (eventTypeByte == 0xf0) {
121 | event.type = 'sysEx';
122 | var length = stream.readVarInt();
123 | event.data = stream.read(length);
124 | return event;
125 | } else if (eventTypeByte == 0xf7) {
126 | event.type = 'dividedSysEx';
127 | var length = stream.readVarInt();
128 | event.data = stream.read(length);
129 | return event;
130 | } else {
131 | throw "Unrecognised MIDI event type byte: " + eventTypeByte;
132 | }
133 | } else {
134 | /* channel event */
135 | var param1;
136 | if ((eventTypeByte & 0x80) == 0) {
137 | /* running status - reuse lastEventTypeByte as the event type.
138 | eventTypeByte is actually the first parameter
139 | */
140 | param1 = eventTypeByte;
141 | eventTypeByte = lastEventTypeByte;
142 | } else {
143 | param1 = stream.readInt8();
144 | lastEventTypeByte = eventTypeByte;
145 | }
146 | var eventType = eventTypeByte >> 4;
147 | event.channel = eventTypeByte & 0x0f;
148 | event.type = 'channel';
149 | switch (eventType) {
150 | case 0x08:
151 | event.subtype = 'noteOff';
152 | event.noteNumber = param1;
153 | event.velocity = stream.readInt8();
154 | return event;
155 | case 0x09:
156 | event.noteNumber = param1;
157 | event.velocity = stream.readInt8();
158 | if (event.velocity == 0) {
159 | event.subtype = 'noteOff';
160 | } else {
161 | event.subtype = 'noteOn';
162 | }
163 | return event;
164 | case 0x0a:
165 | event.subtype = 'noteAftertouch';
166 | event.noteNumber = param1;
167 | event.amount = stream.readInt8();
168 | return event;
169 | case 0x0b:
170 | event.subtype = 'controller';
171 | event.controllerType = param1;
172 | event.value = stream.readInt8();
173 | return event;
174 | case 0x0c:
175 | event.subtype = 'programChange';
176 | event.programNumber = param1;
177 | return event;
178 | case 0x0d:
179 | event.subtype = 'channelAftertouch';
180 | event.amount = param1;
181 | return event;
182 | case 0x0e:
183 | event.subtype = 'pitchBend';
184 | event.value = param1 + (stream.readInt8() << 7);
185 | return event;
186 | default:
187 | throw "Unrecognised MIDI event type: " + eventType
188 | /*
189 | console.log("Unrecognised MIDI event type: " + eventType);
190 | stream.readInt8();
191 | event.subtype = 'unknown';
192 | return event;
193 | */
194 | }
195 | }
196 | }
197 |
198 | stream = Stream(data);
199 | var headerChunk = readChunk(stream);
200 | if (headerChunk.id != 'MThd' || headerChunk.length != 6) {
201 | throw "Bad .mid file - header not found";
202 | }
203 | var headerStream = Stream(headerChunk.data);
204 | var formatType = headerStream.readInt16();
205 | var trackCount = headerStream.readInt16();
206 | var timeDivision = headerStream.readInt16();
207 |
208 | if (timeDivision & 0x8000) {
209 | throw "Expressing time division in SMTPE frames is not supported yet"
210 | } else {
211 | ticksPerBeat = timeDivision;
212 | }
213 |
214 | var header = {
215 | 'formatType': formatType,
216 | 'trackCount': trackCount,
217 | 'ticksPerBeat': ticksPerBeat
218 | }
219 | var tracks = [];
220 | for (var i = 0; i < header.trackCount; i++) {
221 | tracks[i] = [];
222 | var trackChunk = readChunk(stream);
223 | if (trackChunk.id != 'MTrk') {
224 | throw "Unexpected chunk - expected MTrk, got "+ trackChunk.id;
225 | }
226 | var trackStream = Stream(trackChunk.data);
227 | while (!trackStream.eof()) {
228 | var event = readEvent(trackStream);
229 | tracks[i].push(event);
230 | //console.log(event);
231 | }
232 | }
233 |
234 | return {
235 | 'header': header,
236 | 'tracks': tracks
237 | }
238 | }
--------------------------------------------------------------------------------
/lib/MIDI/jasmid/replayer.js:
--------------------------------------------------------------------------------
1 | var clone = function (o) {
2 | if (typeof o != 'object') return (o);
3 | if (o == null) return (o);
4 | var ret = (typeof o.length == 'number') ? [] : {};
5 | for (var key in o) ret[key] = clone(o[key]);
6 | return ret;
7 | };
8 |
9 | function Replayer(midiFile, timeWarp, eventProcessor) {
10 | var trackStates = [];
11 | var beatsPerMinute = 120;
12 | var ticksPerBeat = midiFile.header.ticksPerBeat;
13 |
14 | for (var i = 0; i < midiFile.tracks.length; i++) {
15 | trackStates[i] = {
16 | 'nextEventIndex': 0,
17 | 'ticksToNextEvent': (
18 | midiFile.tracks[i].length ?
19 | midiFile.tracks[i][0].deltaTime :
20 | null
21 | )
22 | };
23 | }
24 |
25 | var nextEventInfo;
26 | var samplesToNextEvent = 0;
27 |
28 | function getNextEvent() {
29 | var ticksToNextEvent = null;
30 | var nextEventTrack = null;
31 | var nextEventIndex = null;
32 |
33 | for (var i = 0; i < trackStates.length; i++) {
34 | if (
35 | trackStates[i].ticksToNextEvent != null
36 | && (ticksToNextEvent == null || trackStates[i].ticksToNextEvent < ticksToNextEvent)
37 | ) {
38 | ticksToNextEvent = trackStates[i].ticksToNextEvent;
39 | nextEventTrack = i;
40 | nextEventIndex = trackStates[i].nextEventIndex;
41 | }
42 | }
43 | if (nextEventTrack != null) {
44 | /* consume event from that track */
45 | var nextEvent = midiFile.tracks[nextEventTrack][nextEventIndex];
46 | if (midiFile.tracks[nextEventTrack][nextEventIndex + 1]) {
47 | trackStates[nextEventTrack].ticksToNextEvent += midiFile.tracks[nextEventTrack][nextEventIndex + 1].deltaTime;
48 | } else {
49 | trackStates[nextEventTrack].ticksToNextEvent = null;
50 | }
51 | trackStates[nextEventTrack].nextEventIndex += 1;
52 | /* advance timings on all tracks by ticksToNextEvent */
53 | for (var i = 0; i < trackStates.length; i++) {
54 | if (trackStates[i].ticksToNextEvent != null) {
55 | trackStates[i].ticksToNextEvent -= ticksToNextEvent
56 | }
57 | }
58 | return {
59 | "ticksToEvent": ticksToNextEvent,
60 | "event": nextEvent,
61 | "track": nextEventTrack
62 | }
63 | } else {
64 | return null;
65 | }
66 | };
67 | //
68 | var midiEvent;
69 | var temporal = [];
70 | //
71 | function processEvents() {
72 | function processNext() {
73 | if ( midiEvent.event.type == "meta" && midiEvent.event.subtype == "setTempo" ) {
74 | // tempo change events can occur anywhere in the middle and affect events that follow
75 | beatsPerMinute = 60000000 / midiEvent.event.microsecondsPerBeat;
76 | }
77 | if (midiEvent.ticksToEvent > 0) {
78 | var beatsToGenerate = midiEvent.ticksToEvent / ticksPerBeat;
79 | var secondsToGenerate = beatsToGenerate / (beatsPerMinute / 60);
80 | }
81 | var time = (secondsToGenerate * 1000 * timeWarp) || 0;
82 | temporal.push([ midiEvent, time]);
83 | midiEvent = getNextEvent();
84 | };
85 | //
86 | if (midiEvent = getNextEvent()) {
87 | while(midiEvent) processNext(true);
88 | }
89 | };
90 | processEvents();
91 | return {
92 | "getData": function() {
93 | return clone(temporal);
94 | }
95 | };
96 | };
97 |
--------------------------------------------------------------------------------
/lib/MIDI/jasmid/stream.js:
--------------------------------------------------------------------------------
1 | /* Wrapper for accessing strings through sequential reads */
2 | function Stream(str) {
3 | var position = 0;
4 |
5 | function read(length) {
6 | var result = str.substr(position, length);
7 | position += length;
8 | return result;
9 | }
10 |
11 | /* read a big-endian 32-bit integer */
12 | function readInt32() {
13 | var result = (
14 | (str.charCodeAt(position) << 24)
15 | + (str.charCodeAt(position + 1) << 16)
16 | + (str.charCodeAt(position + 2) << 8)
17 | + str.charCodeAt(position + 3));
18 | position += 4;
19 | return result;
20 | }
21 |
22 | /* read a big-endian 16-bit integer */
23 | function readInt16() {
24 | var result = (
25 | (str.charCodeAt(position) << 8)
26 | + str.charCodeAt(position + 1));
27 | position += 2;
28 | return result;
29 | }
30 |
31 | /* read an 8-bit integer */
32 | function readInt8(signed) {
33 | var result = str.charCodeAt(position);
34 | if (signed && result > 127) result -= 256;
35 | position += 1;
36 | return result;
37 | }
38 |
39 | function eof() {
40 | return position >= str.length;
41 | }
42 |
43 | /* read a MIDI-style variable-length integer
44 | (big-endian value in groups of 7 bits,
45 | with top bit set to signify that another byte follows)
46 | */
47 | function readVarInt() {
48 | var result = 0;
49 | while (true) {
50 | var b = readInt8();
51 | if (b & 0x80) {
52 | result += (b & 0x7f);
53 | result <<= 7;
54 | } else {
55 | /* b is the last byte */
56 | return result + b;
57 | }
58 | }
59 | }
60 |
61 | return {
62 | 'eof': eof,
63 | 'read': read,
64 | 'readInt32': readInt32,
65 | 'readInt16': readInt16,
66 | 'readInt8': readInt8,
67 | 'readVarInt': readVarInt
68 | }
69 | }
--------------------------------------------------------------------------------
/lib/dropzone.js:
--------------------------------------------------------------------------------
1 | ;(function(){
2 |
3 | /**
4 | * Require the given path.
5 | *
6 | * @param {String} path
7 | * @return {Object} exports
8 | * @api public
9 | */
10 |
11 | function require(path, parent, orig) {
12 | var resolved = require.resolve(path);
13 |
14 | // lookup failed
15 | if (null == resolved) {
16 | orig = orig || path;
17 | parent = parent || 'root';
18 | var err = new Error('Failed to require "' + orig + '" from "' + parent + '"');
19 | err.path = orig;
20 | err.parent = parent;
21 | err.require = true;
22 | throw err;
23 | }
24 |
25 | var module = require.modules[resolved];
26 |
27 | // perform real require()
28 | // by invoking the module's
29 | // registered function
30 | if (!module._resolving && !module.exports) {
31 | var mod = {};
32 | mod.exports = {};
33 | mod.client = mod.component = true;
34 | module._resolving = true;
35 | module.call(this, mod.exports, require.relative(resolved), mod);
36 | delete module._resolving;
37 | module.exports = mod.exports;
38 | }
39 |
40 | return module.exports;
41 | }
42 |
43 | /**
44 | * Registered modules.
45 | */
46 |
47 | require.modules = {};
48 |
49 | /**
50 | * Registered aliases.
51 | */
52 |
53 | require.aliases = {};
54 |
55 | /**
56 | * Resolve `path`.
57 | *
58 | * Lookup:
59 | *
60 | * - PATH/index.js
61 | * - PATH.js
62 | * - PATH
63 | *
64 | * @param {String} path
65 | * @return {String} path or null
66 | * @api private
67 | */
68 |
69 | require.resolve = function(path) {
70 | if (path.charAt(0) === '/') path = path.slice(1);
71 |
72 | var paths = [
73 | path,
74 | path + '.js',
75 | path + '.json',
76 | path + '/index.js',
77 | path + '/index.json'
78 | ];
79 |
80 | for (var i = 0; i < paths.length; i++) {
81 | var path = paths[i];
82 | if (require.modules.hasOwnProperty(path)) return path;
83 | if (require.aliases.hasOwnProperty(path)) return require.aliases[path];
84 | }
85 | };
86 |
87 | /**
88 | * Normalize `path` relative to the current path.
89 | *
90 | * @param {String} curr
91 | * @param {String} path
92 | * @return {String}
93 | * @api private
94 | */
95 |
96 | require.normalize = function(curr, path) {
97 | var segs = [];
98 |
99 | if ('.' != path.charAt(0)) return path;
100 |
101 | curr = curr.split('/');
102 | path = path.split('/');
103 |
104 | for (var i = 0; i < path.length; ++i) {
105 | if ('..' == path[i]) {
106 | curr.pop();
107 | } else if ('.' != path[i] && '' != path[i]) {
108 | segs.push(path[i]);
109 | }
110 | }
111 |
112 | return curr.concat(segs).join('/');
113 | };
114 |
115 | /**
116 | * Register module at `path` with callback `definition`.
117 | *
118 | * @param {String} path
119 | * @param {Function} definition
120 | * @api private
121 | */
122 |
123 | require.register = function(path, definition) {
124 | require.modules[path] = definition;
125 | };
126 |
127 | /**
128 | * Alias a module definition.
129 | *
130 | * @param {String} from
131 | * @param {String} to
132 | * @api private
133 | */
134 |
135 | require.alias = function(from, to) {
136 | if (!require.modules.hasOwnProperty(from)) {
137 | throw new Error('Failed to alias "' + from + '", it does not exist');
138 | }
139 | require.aliases[to] = from;
140 | };
141 |
142 | /**
143 | * Return a require function relative to the `parent` path.
144 | *
145 | * @param {String} parent
146 | * @return {Function}
147 | * @api private
148 | */
149 |
150 | require.relative = function(parent) {
151 | var p = require.normalize(parent, '..');
152 |
153 | /**
154 | * lastIndexOf helper.
155 | */
156 |
157 | function lastIndexOf(arr, obj) {
158 | var i = arr.length;
159 | while (i--) {
160 | if (arr[i] === obj) return i;
161 | }
162 | return -1;
163 | }
164 |
165 | /**
166 | * The relative require() itself.
167 | */
168 |
169 | function localRequire(path) {
170 | var resolved = localRequire.resolve(path);
171 | return require(resolved, parent, path);
172 | }
173 |
174 | /**
175 | * Resolve relative to the parent.
176 | */
177 |
178 | localRequire.resolve = function(path) {
179 | var c = path.charAt(0);
180 | if ('/' == c) return path.slice(1);
181 | if ('.' == c) return require.normalize(p, path);
182 |
183 | // resolve deps by returning
184 | // the dep in the nearest "deps"
185 | // directory
186 | var segs = parent.split('/');
187 | var i = lastIndexOf(segs, 'deps') + 1;
188 | if (!i) i = 0;
189 | path = segs.slice(0, i + 1).join('/') + '/deps/' + path;
190 | return path;
191 | };
192 |
193 | /**
194 | * Check if module is defined at `path`.
195 | */
196 |
197 | localRequire.exists = function(path) {
198 | return require.modules.hasOwnProperty(localRequire.resolve(path));
199 | };
200 |
201 | return localRequire;
202 | };
203 | require.register("component-emitter/index.js", function(exports, require, module){
204 |
205 | /**
206 | * Expose `Emitter`.
207 | */
208 |
209 | module.exports = Emitter;
210 |
211 | /**
212 | * Initialize a new `Emitter`.
213 | *
214 | * @api public
215 | */
216 |
217 | function Emitter(obj) {
218 | if (obj) return mixin(obj);
219 | };
220 |
221 | /**
222 | * Mixin the emitter properties.
223 | *
224 | * @param {Object} obj
225 | * @return {Object}
226 | * @api private
227 | */
228 |
229 | function mixin(obj) {
230 | for (var key in Emitter.prototype) {
231 | obj[key] = Emitter.prototype[key];
232 | }
233 | return obj;
234 | }
235 |
236 | /**
237 | * Listen on the given `event` with `fn`.
238 | *
239 | * @param {String} event
240 | * @param {Function} fn
241 | * @return {Emitter}
242 | * @api public
243 | */
244 |
245 | Emitter.prototype.on = function(event, fn){
246 | this._callbacks = this._callbacks || {};
247 | (this._callbacks[event] = this._callbacks[event] || [])
248 | .push(fn);
249 | return this;
250 | };
251 |
252 | /**
253 | * Adds an `event` listener that will be invoked a single
254 | * time then automatically removed.
255 | *
256 | * @param {String} event
257 | * @param {Function} fn
258 | * @return {Emitter}
259 | * @api public
260 | */
261 |
262 | Emitter.prototype.once = function(event, fn){
263 | var self = this;
264 | this._callbacks = this._callbacks || {};
265 |
266 | function on() {
267 | self.off(event, on);
268 | fn.apply(this, arguments);
269 | }
270 |
271 | fn._off = on;
272 | this.on(event, on);
273 | return this;
274 | };
275 |
276 | /**
277 | * Remove the given callback for `event` or all
278 | * registered callbacks.
279 | *
280 | * @param {String} event
281 | * @param {Function} fn
282 | * @return {Emitter}
283 | * @api public
284 | */
285 |
286 | Emitter.prototype.off =
287 | Emitter.prototype.removeListener =
288 | Emitter.prototype.removeAllListeners = function(event, fn){
289 | this._callbacks = this._callbacks || {};
290 | var callbacks = this._callbacks[event];
291 | if (!callbacks) return this;
292 |
293 | // remove all handlers
294 | if (1 == arguments.length) {
295 | delete this._callbacks[event];
296 | return this;
297 | }
298 |
299 | // remove specific handler
300 | var i = callbacks.indexOf(fn._off || fn);
301 | if (~i) callbacks.splice(i, 1);
302 | return this;
303 | };
304 |
305 | /**
306 | * Emit `event` with the given args.
307 | *
308 | * @param {String} event
309 | * @param {Mixed} ...
310 | * @return {Emitter}
311 | */
312 |
313 | Emitter.prototype.emit = function(event){
314 | this._callbacks = this._callbacks || {};
315 | var args = [].slice.call(arguments, 1)
316 | , callbacks = this._callbacks[event];
317 |
318 | if (callbacks) {
319 | callbacks = callbacks.slice(0);
320 | for (var i = 0, len = callbacks.length; i < len; ++i) {
321 | callbacks[i].apply(this, args);
322 | }
323 | }
324 |
325 | return this;
326 | };
327 |
328 | /**
329 | * Return array of callbacks for `event`.
330 | *
331 | * @param {String} event
332 | * @return {Array}
333 | * @api public
334 | */
335 |
336 | Emitter.prototype.listeners = function(event){
337 | this._callbacks = this._callbacks || {};
338 | return this._callbacks[event] || [];
339 | };
340 |
341 | /**
342 | * Check if this emitter has `event` handlers.
343 | *
344 | * @param {String} event
345 | * @return {Boolean}
346 | * @api public
347 | */
348 |
349 | Emitter.prototype.hasListeners = function(event){
350 | return !! this.listeners(event).length;
351 | };
352 |
353 | });
354 | require.register("dropzone/index.js", function(exports, require, module){
355 |
356 |
357 | /**
358 | * Exposing dropzone
359 | */
360 | module.exports = require("./lib/dropzone.js");
361 |
362 | });
363 | require.register("dropzone/lib/dropzone.js", function(exports, require, module){
364 | /*
365 | #
366 | # More info at [www.dropzonejs.com](http://www.dropzonejs.com)
367 | #
368 | # Copyright (c) 2012, Matias Meno
369 | #
370 | # Permission is hereby granted, free of charge, to any person obtaining a copy
371 | # of this software and associated documentation files (the "Software"), to deal
372 | # in the Software without restriction, including without limitation the rights
373 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
374 | # copies of the Software, and to permit persons to whom the Software is
375 | # furnished to do so, subject to the following conditions:
376 | #
377 | # The above copyright notice and this permission notice shall be included in
378 | # all copies or substantial portions of the Software.
379 | #
380 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
381 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
382 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
383 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
384 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
385 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
386 | # THE SOFTWARE.
387 | #
388 | */
389 |
390 |
391 | (function() {
392 | var Dropzone, Em, camelize, contentLoaded, detectVerticalSquash, drawImageIOSFix, noop, without,
393 | __hasProp = {}.hasOwnProperty,
394 | __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
395 | __slice = [].slice;
396 |
397 | Em = typeof Emitter !== "undefined" && Emitter !== null ? Emitter : require("emitter");
398 |
399 | noop = function() {};
400 |
401 | Dropzone = (function(_super) {
402 | var extend;
403 |
404 | __extends(Dropzone, _super);
405 |
406 | /*
407 | This is a list of all available events you can register on a dropzone object.
408 |
409 | You can register an event handler like this:
410 |
411 | dropzone.on("dragEnter", function() { });
412 | */
413 |
414 |
415 | Dropzone.prototype.events = ["drop", "dragstart", "dragend", "dragenter", "dragover", "dragleave", "addedfile", "removedfile", "thumbnail", "error", "errormultiple", "processing", "processingmultiple", "uploadprogress", "totaluploadprogress", "sending", "sendingmultiple", "success", "successmultiple", "canceled", "canceledmultiple", "complete", "completemultiple", "reset", "maxfilesexceeded", "maxfilesreached"];
416 |
417 | Dropzone.prototype.defaultOptions = {
418 | url: null,
419 | method: "post",
420 | withCredentials: false,
421 | parallelUploads: 2,
422 | uploadMultiple: false,
423 | maxFilesize: 256,
424 | paramName: "file",
425 | createImageThumbnails: true,
426 | maxThumbnailFilesize: 10,
427 | thumbnailWidth: 100,
428 | thumbnailHeight: 100,
429 | maxFiles: null,
430 | params: {},
431 | clickable: true,
432 | ignoreHiddenFiles: true,
433 | acceptedFiles: null,
434 | acceptedMimeTypes: null,
435 | autoProcessQueue: true,
436 | addRemoveLinks: false,
437 | previewsContainer: null,
438 | dictDefaultMessage: "Drop files here to upload",
439 | dictFallbackMessage: "Your browser does not support drag'n'drop file uploads.",
440 | dictFallbackText: "Please use the fallback form below to upload your files like in the olden days.",
441 | dictFileTooBig: "File is too big ({{filesize}}MiB). Max filesize: {{maxFilesize}}MiB.",
442 | dictInvalidFileType: "You can't upload files of this type.",
443 | dictResponseError: "Server responded with {{statusCode}} code.",
444 | dictCancelUpload: "Cancel upload",
445 | dictCancelUploadConfirmation: "Are you sure you want to cancel this upload?",
446 | dictRemoveFile: "Remove file",
447 | dictRemoveFileConfirmation: null,
448 | dictMaxFilesExceeded: "You can not upload any more files.",
449 | accept: function(file, done) {
450 | return done();
451 | },
452 | init: function() {
453 | return noop;
454 | },
455 | forceFallback: false,
456 | fallback: function() {
457 | var child, messageElement, span, _i, _len, _ref;
458 | this.element.className = "" + this.element.className + " dz-browser-not-supported";
459 | _ref = this.element.getElementsByTagName("div");
460 | for (_i = 0, _len = _ref.length; _i < _len; _i++) {
461 | child = _ref[_i];
462 | if (/(^| )dz-message($| )/.test(child.className)) {
463 | messageElement = child;
464 | child.className = "dz-message";
465 | continue;
466 | }
467 | }
468 | if (!messageElement) {
469 | messageElement = Dropzone.createElement("
");
470 | this.element.appendChild(messageElement);
471 | }
472 | span = messageElement.getElementsByTagName("span")[0];
473 | if (span) {
474 | span.textContent = this.options.dictFallbackMessage;
475 | }
476 | return this.element.appendChild(this.getFallbackForm());
477 | },
478 | resize: function(file) {
479 | var info, srcRatio, trgRatio;
480 | info = {
481 | srcX: 0,
482 | srcY: 0,
483 | srcWidth: file.width,
484 | srcHeight: file.height
485 | };
486 | srcRatio = file.width / file.height;
487 | trgRatio = this.options.thumbnailWidth / this.options.thumbnailHeight;
488 | if (file.height < this.options.thumbnailHeight || file.width < this.options.thumbnailWidth) {
489 | info.trgHeight = info.srcHeight;
490 | info.trgWidth = info.srcWidth;
491 | } else {
492 | if (srcRatio > trgRatio) {
493 | info.srcHeight = file.height;
494 | info.srcWidth = info.srcHeight * trgRatio;
495 | } else {
496 | info.srcWidth = file.width;
497 | info.srcHeight = info.srcWidth / trgRatio;
498 | }
499 | }
500 | info.srcX = (file.width - info.srcWidth) / 2;
501 | info.srcY = (file.height - info.srcHeight) / 2;
502 | return info;
503 | },
504 | /*
505 | Those functions register themselves to the events on init and handle all
506 | the user interface specific stuff. Overwriting them won't break the upload
507 | but can break the way it's displayed.
508 | You can overwrite them if you don't like the default behavior. If you just
509 | want to add an additional event handler, register it on the dropzone object
510 | and don't overwrite those options.
511 | */
512 |
513 | drop: function(e) {
514 | return this.element.classList.remove("dz-drag-hover");
515 | },
516 | dragstart: noop,
517 | dragend: function(e) {
518 | return this.element.classList.remove("dz-drag-hover");
519 | },
520 | dragenter: function(e) {
521 | return this.element.classList.add("dz-drag-hover");
522 | },
523 | dragover: function(e) {
524 | return this.element.classList.add("dz-drag-hover");
525 | },
526 | dragleave: function(e) {
527 | return this.element.classList.remove("dz-drag-hover");
528 | },
529 | paste: noop,
530 | reset: function() {
531 | return this.element.classList.remove("dz-started");
532 | },
533 | addedfile: function(file) {
534 | var node, removeFileEvent, removeLink, _i, _j, _k, _len, _len1, _len2, _ref, _ref1, _ref2, _results,
535 | _this = this;
536 | if (this.element === this.previewsContainer) {
537 | this.element.classList.add("dz-started");
538 | }
539 | file.previewElement = Dropzone.createElement(this.options.previewTemplate.trim());
540 | file.previewTemplate = file.previewElement;
541 | this.previewsContainer.appendChild(file.previewElement);
542 | _ref = file.previewElement.querySelectorAll("[data-dz-name]");
543 | for (_i = 0, _len = _ref.length; _i < _len; _i++) {
544 | node = _ref[_i];
545 | node.textContent = file.name;
546 | }
547 | _ref1 = file.previewElement.querySelectorAll("[data-dz-size]");
548 | for (_j = 0, _len1 = _ref1.length; _j < _len1; _j++) {
549 | node = _ref1[_j];
550 | node.innerHTML = this.filesize(file.size);
551 | }
552 | if (this.options.addRemoveLinks) {
553 | file._removeLink = Dropzone.createElement("" + this.options.dictRemoveFile + " ");
554 | file.previewElement.appendChild(file._removeLink);
555 | }
556 | removeFileEvent = function(e) {
557 | e.preventDefault();
558 | e.stopPropagation();
559 | if (file.status === Dropzone.UPLOADING) {
560 | return Dropzone.confirm(_this.options.dictCancelUploadConfirmation, function() {
561 | return _this.removeFile(file);
562 | });
563 | } else {
564 | if (_this.options.dictRemoveFileConfirmation) {
565 | return Dropzone.confirm(_this.options.dictRemoveFileConfirmation, function() {
566 | return _this.removeFile(file);
567 | });
568 | } else {
569 | return _this.removeFile(file);
570 | }
571 | }
572 | };
573 | _ref2 = file.previewElement.querySelectorAll("[data-dz-remove]");
574 | _results = [];
575 | for (_k = 0, _len2 = _ref2.length; _k < _len2; _k++) {
576 | removeLink = _ref2[_k];
577 | _results.push(removeLink.addEventListener("click", removeFileEvent));
578 | }
579 | return _results;
580 | },
581 | removedfile: function(file) {
582 | var _ref;
583 | if ((_ref = file.previewElement) != null) {
584 | _ref.parentNode.removeChild(file.previewElement);
585 | }
586 | return this._updateMaxFilesReachedClass();
587 | },
588 | thumbnail: function(file, dataUrl) {
589 | var thumbnailElement, _i, _len, _ref, _results;
590 | file.previewElement.classList.remove("dz-file-preview");
591 | file.previewElement.classList.add("dz-image-preview");
592 | _ref = file.previewElement.querySelectorAll("[data-dz-thumbnail]");
593 | _results = [];
594 | for (_i = 0, _len = _ref.length; _i < _len; _i++) {
595 | thumbnailElement = _ref[_i];
596 | thumbnailElement.alt = file.name;
597 | _results.push(thumbnailElement.src = dataUrl);
598 | }
599 | return _results;
600 | },
601 | error: function(file, message) {
602 | var node, _i, _len, _ref, _results;
603 | file.previewElement.classList.add("dz-error");
604 | if (typeof message !== "String" && message.error) {
605 | message = message.error;
606 | }
607 | _ref = file.previewElement.querySelectorAll("[data-dz-errormessage]");
608 | _results = [];
609 | for (_i = 0, _len = _ref.length; _i < _len; _i++) {
610 | node = _ref[_i];
611 | _results.push(node.textContent = message);
612 | }
613 | return _results;
614 | },
615 | errormultiple: noop,
616 | processing: function(file) {
617 | file.previewElement.classList.add("dz-processing");
618 | if (file._removeLink) {
619 | return file._removeLink.textContent = this.options.dictCancelUpload;
620 | }
621 | },
622 | processingmultiple: noop,
623 | uploadprogress: function(file, progress, bytesSent) {
624 | var node, _i, _len, _ref, _results;
625 | _ref = file.previewElement.querySelectorAll("[data-dz-uploadprogress]");
626 | _results = [];
627 | for (_i = 0, _len = _ref.length; _i < _len; _i++) {
628 | node = _ref[_i];
629 | _results.push(node.style.width = "" + progress + "%");
630 | }
631 | return _results;
632 | },
633 | totaluploadprogress: noop,
634 | sending: noop,
635 | sendingmultiple: noop,
636 | success: function(file) {
637 | return file.previewElement.classList.add("dz-success");
638 | },
639 | successmultiple: noop,
640 | canceled: function(file) {
641 | return this.emit("error", file, "Upload canceled.");
642 | },
643 | canceledmultiple: noop,
644 | complete: function(file) {
645 | if (file._removeLink) {
646 | return file._removeLink.textContent = this.options.dictRemoveFile;
647 | }
648 | },
649 | completemultiple: noop,
650 | maxfilesexceeded: noop,
651 | maxfilesreached: noop,
652 | previewTemplate: "\n
\n
\n
\n
\n
\n
\n
✔
\n
✘
\n
\n
"
653 | };
654 |
655 | extend = function() {
656 | var key, object, objects, target, val, _i, _len;
657 | target = arguments[0], objects = 2 <= arguments.length ? __slice.call(arguments, 1) : [];
658 | for (_i = 0, _len = objects.length; _i < _len; _i++) {
659 | object = objects[_i];
660 | for (key in object) {
661 | val = object[key];
662 | target[key] = val;
663 | }
664 | }
665 | return target;
666 | };
667 |
668 | function Dropzone(element, options) {
669 | var elementOptions, fallback, _ref;
670 | this.element = element;
671 | this.version = Dropzone.version;
672 | this.defaultOptions.previewTemplate = this.defaultOptions.previewTemplate.replace(/\n*/g, "");
673 | this.clickableElements = [];
674 | this.listeners = [];
675 | this.files = [];
676 | if (typeof this.element === "string") {
677 | this.element = document.querySelector(this.element);
678 | }
679 | if (!(this.element && (this.element.nodeType != null))) {
680 | throw new Error("Invalid dropzone element.");
681 | }
682 | if (this.element.dropzone) {
683 | throw new Error("Dropzone already attached.");
684 | }
685 | Dropzone.instances.push(this);
686 | this.element.dropzone = this;
687 | elementOptions = (_ref = Dropzone.optionsForElement(this.element)) != null ? _ref : {};
688 | this.options = extend({}, this.defaultOptions, elementOptions, options != null ? options : {});
689 | if (this.options.forceFallback || !Dropzone.isBrowserSupported()) {
690 | return this.options.fallback.call(this);
691 | }
692 | if (this.options.url == null) {
693 | this.options.url = this.element.getAttribute("action");
694 | }
695 | if (!this.options.url) {
696 | throw new Error("No URL provided.");
697 | }
698 | if (this.options.acceptedFiles && this.options.acceptedMimeTypes) {
699 | throw new Error("You can't provide both 'acceptedFiles' and 'acceptedMimeTypes'. 'acceptedMimeTypes' is deprecated.");
700 | }
701 | if (this.options.acceptedMimeTypes) {
702 | this.options.acceptedFiles = this.options.acceptedMimeTypes;
703 | delete this.options.acceptedMimeTypes;
704 | }
705 | this.options.method = this.options.method.toUpperCase();
706 | if ((fallback = this.getExistingFallback()) && fallback.parentNode) {
707 | fallback.parentNode.removeChild(fallback);
708 | }
709 | if (this.options.previewsContainer) {
710 | this.previewsContainer = Dropzone.getElement(this.options.previewsContainer, "previewsContainer");
711 | } else {
712 | this.previewsContainer = this.element;
713 | }
714 | if (this.options.clickable) {
715 | if (this.options.clickable === true) {
716 | this.clickableElements = [this.element];
717 | } else {
718 | this.clickableElements = Dropzone.getElements(this.options.clickable, "clickable");
719 | }
720 | }
721 | this.init();
722 | }
723 |
724 | Dropzone.prototype.getAcceptedFiles = function() {
725 | var file, _i, _len, _ref, _results;
726 | _ref = this.files;
727 | _results = [];
728 | for (_i = 0, _len = _ref.length; _i < _len; _i++) {
729 | file = _ref[_i];
730 | if (file.accepted) {
731 | _results.push(file);
732 | }
733 | }
734 | return _results;
735 | };
736 |
737 | Dropzone.prototype.getRejectedFiles = function() {
738 | var file, _i, _len, _ref, _results;
739 | _ref = this.files;
740 | _results = [];
741 | for (_i = 0, _len = _ref.length; _i < _len; _i++) {
742 | file = _ref[_i];
743 | if (!file.accepted) {
744 | _results.push(file);
745 | }
746 | }
747 | return _results;
748 | };
749 |
750 | Dropzone.prototype.getQueuedFiles = function() {
751 | var file, _i, _len, _ref, _results;
752 | _ref = this.files;
753 | _results = [];
754 | for (_i = 0, _len = _ref.length; _i < _len; _i++) {
755 | file = _ref[_i];
756 | if (file.status === Dropzone.QUEUED) {
757 | _results.push(file);
758 | }
759 | }
760 | return _results;
761 | };
762 |
763 | Dropzone.prototype.getUploadingFiles = function() {
764 | var file, _i, _len, _ref, _results;
765 | _ref = this.files;
766 | _results = [];
767 | for (_i = 0, _len = _ref.length; _i < _len; _i++) {
768 | file = _ref[_i];
769 | if (file.status === Dropzone.UPLOADING) {
770 | _results.push(file);
771 | }
772 | }
773 | return _results;
774 | };
775 |
776 | Dropzone.prototype.init = function() {
777 | var eventName, noPropagation, setupHiddenFileInput, _i, _len, _ref, _ref1,
778 | _this = this;
779 | if (this.element.tagName === "form") {
780 | this.element.setAttribute("enctype", "multipart/form-data");
781 | }
782 | if (this.element.classList.contains("dropzone") && !this.element.querySelector(".dz-message")) {
783 | this.element.appendChild(Dropzone.createElement("" + this.options.dictDefaultMessage + "
"));
784 | }
785 | if (this.clickableElements.length) {
786 | setupHiddenFileInput = function() {
787 | if (_this.hiddenFileInput) {
788 | document.body.removeChild(_this.hiddenFileInput);
789 | }
790 | _this.hiddenFileInput = document.createElement("input");
791 | _this.hiddenFileInput.setAttribute("type", "file");
792 | if ((_this.options.maxFiles == null) || _this.options.maxFiles > 1) {
793 | _this.hiddenFileInput.setAttribute("multiple", "multiple");
794 | }
795 | if (_this.options.acceptedFiles != null) {
796 | _this.hiddenFileInput.setAttribute("accept", _this.options.acceptedFiles);
797 | }
798 | _this.hiddenFileInput.style.visibility = "hidden";
799 | _this.hiddenFileInput.style.position = "absolute";
800 | _this.hiddenFileInput.style.top = "0";
801 | _this.hiddenFileInput.style.left = "0";
802 | _this.hiddenFileInput.style.height = "0";
803 | _this.hiddenFileInput.style.width = "0";
804 | document.body.appendChild(_this.hiddenFileInput);
805 | return _this.hiddenFileInput.addEventListener("change", function() {
806 | var file, files, _i, _len;
807 | files = _this.hiddenFileInput.files;
808 | if (files.length) {
809 | for (_i = 0, _len = files.length; _i < _len; _i++) {
810 | file = files[_i];
811 | _this.addFile(file);
812 | }
813 | }
814 | return setupHiddenFileInput();
815 | });
816 | };
817 | setupHiddenFileInput();
818 | }
819 | this.URL = (_ref = window.URL) != null ? _ref : window.webkitURL;
820 | _ref1 = this.events;
821 | for (_i = 0, _len = _ref1.length; _i < _len; _i++) {
822 | eventName = _ref1[_i];
823 | this.on(eventName, this.options[eventName]);
824 | }
825 | this.on("uploadprogress", function() {
826 | return _this.updateTotalUploadProgress();
827 | });
828 | this.on("removedfile", function() {
829 | return _this.updateTotalUploadProgress();
830 | });
831 | this.on("canceled", function(file) {
832 | return _this.emit("complete", file);
833 | });
834 | this.on("complete", function(file) {
835 | if (_this.getUploadingFiles().length === 0 && _this.getQueuedFiles().length === 0) {
836 | return setTimeout((function() {
837 | return _this.emit("queuecomplete");
838 | }), 0);
839 | }
840 | });
841 | noPropagation = function(e) {
842 | e.stopPropagation();
843 | if (e.preventDefault) {
844 | return e.preventDefault();
845 | } else {
846 | return e.returnValue = false;
847 | }
848 | };
849 | this.listeners = [
850 | {
851 | element: this.element,
852 | events: {
853 | "dragstart": function(e) {
854 | return _this.emit("dragstart", e);
855 | },
856 | "dragenter": function(e) {
857 | noPropagation(e);
858 | return _this.emit("dragenter", e);
859 | },
860 | "dragover": function(e) {
861 | var efct;
862 | try {
863 | efct = e.dataTransfer.effectAllowed;
864 | } catch (_error) {}
865 | e.dataTransfer.dropEffect = 'move' === efct || 'linkMove' === efct ? 'move' : 'copy';
866 | noPropagation(e);
867 | return _this.emit("dragover", e);
868 | },
869 | "dragleave": function(e) {
870 | return _this.emit("dragleave", e);
871 | },
872 | "drop": function(e) {
873 | noPropagation(e);
874 | return _this.drop(e);
875 | },
876 | "dragend": function(e) {
877 | return _this.emit("dragend", e);
878 | }
879 | }
880 | }
881 | ];
882 | this.clickableElements.forEach(function(clickableElement) {
883 | return _this.listeners.push({
884 | element: clickableElement,
885 | events: {
886 | "click": function(evt) {
887 | if ((clickableElement !== _this.element) || (evt.target === _this.element || Dropzone.elementInside(evt.target, _this.element.querySelector(".dz-message")))) {
888 | return _this.hiddenFileInput.click();
889 | }
890 | }
891 | }
892 | });
893 | });
894 | this.enable();
895 | return this.options.init.call(this);
896 | };
897 |
898 | Dropzone.prototype.destroy = function() {
899 | var _ref;
900 | this.disable();
901 | this.removeAllFiles(true);
902 | if ((_ref = this.hiddenFileInput) != null ? _ref.parentNode : void 0) {
903 | this.hiddenFileInput.parentNode.removeChild(this.hiddenFileInput);
904 | this.hiddenFileInput = null;
905 | }
906 | delete this.element.dropzone;
907 | return Dropzone.instances.splice(Dropzone.instances.indexOf(this), 1);
908 | };
909 |
910 | Dropzone.prototype.updateTotalUploadProgress = function() {
911 | var acceptedFiles, file, totalBytes, totalBytesSent, totalUploadProgress, _i, _len, _ref;
912 | totalBytesSent = 0;
913 | totalBytes = 0;
914 | acceptedFiles = this.getAcceptedFiles();
915 | if (acceptedFiles.length) {
916 | _ref = this.getAcceptedFiles();
917 | for (_i = 0, _len = _ref.length; _i < _len; _i++) {
918 | file = _ref[_i];
919 | totalBytesSent += file.upload.bytesSent;
920 | totalBytes += file.upload.total;
921 | }
922 | totalUploadProgress = 100 * totalBytesSent / totalBytes;
923 | } else {
924 | totalUploadProgress = 100;
925 | }
926 | return this.emit("totaluploadprogress", totalUploadProgress, totalBytes, totalBytesSent);
927 | };
928 |
929 | Dropzone.prototype.getFallbackForm = function() {
930 | var existingFallback, fields, fieldsString, form;
931 | if (existingFallback = this.getExistingFallback()) {
932 | return existingFallback;
933 | }
934 | fieldsString = "";
939 | fields = Dropzone.createElement(fieldsString);
940 | if (this.element.tagName !== "FORM") {
941 | form = Dropzone.createElement("");
942 | form.appendChild(fields);
943 | } else {
944 | this.element.setAttribute("enctype", "multipart/form-data");
945 | this.element.setAttribute("method", this.options.method);
946 | }
947 | return form != null ? form : fields;
948 | };
949 |
950 | Dropzone.prototype.getExistingFallback = function() {
951 | var fallback, getFallback, tagName, _i, _len, _ref;
952 | getFallback = function(elements) {
953 | var el, _i, _len;
954 | for (_i = 0, _len = elements.length; _i < _len; _i++) {
955 | el = elements[_i];
956 | if (/(^| )fallback($| )/.test(el.className)) {
957 | return el;
958 | }
959 | }
960 | };
961 | _ref = ["div", "form"];
962 | for (_i = 0, _len = _ref.length; _i < _len; _i++) {
963 | tagName = _ref[_i];
964 | if (fallback = getFallback(this.element.getElementsByTagName(tagName))) {
965 | return fallback;
966 | }
967 | }
968 | };
969 |
970 | Dropzone.prototype.setupEventListeners = function() {
971 | var elementListeners, event, listener, _i, _len, _ref, _results;
972 | _ref = this.listeners;
973 | _results = [];
974 | for (_i = 0, _len = _ref.length; _i < _len; _i++) {
975 | elementListeners = _ref[_i];
976 | _results.push((function() {
977 | var _ref1, _results1;
978 | _ref1 = elementListeners.events;
979 | _results1 = [];
980 | for (event in _ref1) {
981 | listener = _ref1[event];
982 | _results1.push(elementListeners.element.addEventListener(event, listener, false));
983 | }
984 | return _results1;
985 | })());
986 | }
987 | return _results;
988 | };
989 |
990 | Dropzone.prototype.removeEventListeners = function() {
991 | var elementListeners, event, listener, _i, _len, _ref, _results;
992 | _ref = this.listeners;
993 | _results = [];
994 | for (_i = 0, _len = _ref.length; _i < _len; _i++) {
995 | elementListeners = _ref[_i];
996 | _results.push((function() {
997 | var _ref1, _results1;
998 | _ref1 = elementListeners.events;
999 | _results1 = [];
1000 | for (event in _ref1) {
1001 | listener = _ref1[event];
1002 | _results1.push(elementListeners.element.removeEventListener(event, listener, false));
1003 | }
1004 | return _results1;
1005 | })());
1006 | }
1007 | return _results;
1008 | };
1009 |
1010 | Dropzone.prototype.disable = function() {
1011 | var file, _i, _len, _ref, _results;
1012 | this.clickableElements.forEach(function(element) {
1013 | return element.classList.remove("dz-clickable");
1014 | });
1015 | this.removeEventListeners();
1016 | _ref = this.files;
1017 | _results = [];
1018 | for (_i = 0, _len = _ref.length; _i < _len; _i++) {
1019 | file = _ref[_i];
1020 | _results.push(this.cancelUpload(file));
1021 | }
1022 | return _results;
1023 | };
1024 |
1025 | Dropzone.prototype.enable = function() {
1026 | this.clickableElements.forEach(function(element) {
1027 | return element.classList.add("dz-clickable");
1028 | });
1029 | return this.setupEventListeners();
1030 | };
1031 |
1032 | Dropzone.prototype.filesize = function(size) {
1033 | var string;
1034 | if (size >= 1024 * 1024 * 1024 * 1024 / 10) {
1035 | size = size / (1024 * 1024 * 1024 * 1024 / 10);
1036 | string = "TiB";
1037 | } else if (size >= 1024 * 1024 * 1024 / 10) {
1038 | size = size / (1024 * 1024 * 1024 / 10);
1039 | string = "GiB";
1040 | } else if (size >= 1024 * 1024 / 10) {
1041 | size = size / (1024 * 1024 / 10);
1042 | string = "MiB";
1043 | } else if (size >= 1024 / 10) {
1044 | size = size / (1024 / 10);
1045 | string = "KiB";
1046 | } else {
1047 | size = size * 10;
1048 | string = "b";
1049 | }
1050 | return "" + (Math.round(size) / 10) + " " + string;
1051 | };
1052 |
1053 | Dropzone.prototype._updateMaxFilesReachedClass = function() {
1054 | if ((this.options.maxFiles != null) && this.getAcceptedFiles().length >= this.options.maxFiles) {
1055 | if (this.getAcceptedFiles().length === this.options.maxFiles) {
1056 | this.emit('maxfilesreached', this.files);
1057 | }
1058 | return this.element.classList.add("dz-max-files-reached");
1059 | } else {
1060 | return this.element.classList.remove("dz-max-files-reached");
1061 | }
1062 | };
1063 |
1064 | Dropzone.prototype.drop = function(e) {
1065 | var files, items;
1066 | if (!e.dataTransfer) {
1067 | return;
1068 | }
1069 | this.emit("drop", e);
1070 | files = e.dataTransfer.files;
1071 | if (files.length) {
1072 | items = e.dataTransfer.items;
1073 | if (items && items.length && (items[0].webkitGetAsEntry != null)) {
1074 | this._addFilesFromItems(items);
1075 | } else {
1076 | this.handleFiles(files);
1077 | }
1078 | }
1079 | };
1080 |
1081 | Dropzone.prototype.paste = function(e) {
1082 | var items, _ref;
1083 | if ((e != null ? (_ref = e.clipboardData) != null ? _ref.items : void 0 : void 0) == null) {
1084 | return;
1085 | }
1086 | this.emit("paste", e);
1087 | items = e.clipboardData.items;
1088 | if (items.length) {
1089 | return this._addFilesFromItems(items);
1090 | }
1091 | };
1092 |
1093 | Dropzone.prototype.handleFiles = function(files) {
1094 | var file, _i, _len, _results;
1095 | _results = [];
1096 | for (_i = 0, _len = files.length; _i < _len; _i++) {
1097 | file = files[_i];
1098 | _results.push(this.addFile(file));
1099 | }
1100 | return _results;
1101 | };
1102 |
1103 | Dropzone.prototype._addFilesFromItems = function(items) {
1104 | var entry, item, _i, _len, _results;
1105 | _results = [];
1106 | for (_i = 0, _len = items.length; _i < _len; _i++) {
1107 | item = items[_i];
1108 | if ((item.webkitGetAsEntry != null) && (entry = item.webkitGetAsEntry())) {
1109 | if (entry.isFile) {
1110 | _results.push(this.addFile(item.getAsFile()));
1111 | } else if (entry.isDirectory) {
1112 | _results.push(this._addFilesFromDirectory(entry, entry.name));
1113 | } else {
1114 | _results.push(void 0);
1115 | }
1116 | } else if (item.getAsFile != null) {
1117 | if ((item.kind == null) || item.kind === "file") {
1118 | _results.push(this.addFile(item.getAsFile()));
1119 | } else {
1120 | _results.push(void 0);
1121 | }
1122 | } else {
1123 | _results.push(void 0);
1124 | }
1125 | }
1126 | return _results;
1127 | };
1128 |
1129 | Dropzone.prototype._addFilesFromDirectory = function(directory, path) {
1130 | var dirReader, entriesReader,
1131 | _this = this;
1132 | dirReader = directory.createReader();
1133 | entriesReader = function(entries) {
1134 | var entry, _i, _len;
1135 | for (_i = 0, _len = entries.length; _i < _len; _i++) {
1136 | entry = entries[_i];
1137 | if (entry.isFile) {
1138 | entry.file(function(file) {
1139 | if (_this.options.ignoreHiddenFiles && file.name.substring(0, 1) === '.') {
1140 | return;
1141 | }
1142 | file.fullPath = "" + path + "/" + file.name;
1143 | return _this.addFile(file);
1144 | });
1145 | } else if (entry.isDirectory) {
1146 | _this._addFilesFromDirectory(entry, "" + path + "/" + entry.name);
1147 | }
1148 | }
1149 | };
1150 | return dirReader.readEntries(entriesReader, function(error) {
1151 | return typeof console !== "undefined" && console !== null ? typeof console.log === "function" ? console.log(error) : void 0 : void 0;
1152 | });
1153 | };
1154 |
1155 | Dropzone.prototype.accept = function(file, done) {
1156 | if (file.size > this.options.maxFilesize * 1024 * 1024) {
1157 | return done(this.options.dictFileTooBig.replace("{{filesize}}", Math.round(file.size / 1024 / 10.24) / 100).replace("{{maxFilesize}}", this.options.maxFilesize));
1158 | } else if (!Dropzone.isValidFile(file, this.options.acceptedFiles)) {
1159 | return done(this.options.dictInvalidFileType);
1160 | } else if ((this.options.maxFiles != null) && this.getAcceptedFiles().length >= this.options.maxFiles) {
1161 | done(this.options.dictMaxFilesExceeded.replace("{{maxFiles}}", this.options.maxFiles));
1162 | return this.emit("maxfilesexceeded", file);
1163 | } else {
1164 | return this.options.accept.call(this, file, done);
1165 | }
1166 | };
1167 |
1168 | Dropzone.prototype.addFile = function(file) {
1169 | var _this = this;
1170 | file.upload = {
1171 | progress: 0,
1172 | total: file.size,
1173 | bytesSent: 0
1174 | };
1175 | this.files.push(file);
1176 | file.status = Dropzone.ADDED;
1177 | this.emit("addedfile", file);
1178 | this._enqueueThumbnail(file);
1179 | return this.accept(file, function(error) {
1180 | if (error) {
1181 | file.accepted = false;
1182 | _this._errorProcessing([file], error);
1183 | } else {
1184 | _this.enqueueFile(file);
1185 | }
1186 | return _this._updateMaxFilesReachedClass();
1187 | });
1188 | };
1189 |
1190 | Dropzone.prototype.enqueueFiles = function(files) {
1191 | var file, _i, _len;
1192 | for (_i = 0, _len = files.length; _i < _len; _i++) {
1193 | file = files[_i];
1194 | this.enqueueFile(file);
1195 | }
1196 | return null;
1197 | };
1198 |
1199 | Dropzone.prototype.enqueueFile = function(file) {
1200 | var _this = this;
1201 | file.accepted = true;
1202 | if (file.status === Dropzone.ADDED) {
1203 | file.status = Dropzone.QUEUED;
1204 | if (this.options.autoProcessQueue) {
1205 | return setTimeout((function() {
1206 | return _this.processQueue();
1207 | }), 0);
1208 | }
1209 | } else {
1210 | throw new Error("This file can't be queued because it has already been processed or was rejected.");
1211 | }
1212 | };
1213 |
1214 | Dropzone.prototype._thumbnailQueue = [];
1215 |
1216 | Dropzone.prototype._processingThumbnail = false;
1217 |
1218 | Dropzone.prototype._enqueueThumbnail = function(file) {
1219 | var _this = this;
1220 | if (this.options.createImageThumbnails && file.type.match(/image.*/) && file.size <= this.options.maxThumbnailFilesize * 1024 * 1024) {
1221 | this._thumbnailQueue.push(file);
1222 | return setTimeout((function() {
1223 | return _this._processThumbnailQueue();
1224 | }), 0);
1225 | }
1226 | };
1227 |
1228 | Dropzone.prototype._processThumbnailQueue = function() {
1229 | var _this = this;
1230 | if (this._processingThumbnail || this._thumbnailQueue.length === 0) {
1231 | return;
1232 | }
1233 | this._processingThumbnail = true;
1234 | return this.createThumbnail(this._thumbnailQueue.shift(), function() {
1235 | _this._processingThumbnail = false;
1236 | return _this._processThumbnailQueue();
1237 | });
1238 | };
1239 |
1240 | Dropzone.prototype.removeFile = function(file) {
1241 | if (file.status === Dropzone.UPLOADING) {
1242 | this.cancelUpload(file);
1243 | }
1244 | this.files = without(this.files, file);
1245 | this.emit("removedfile", file);
1246 | if (this.files.length === 0) {
1247 | return this.emit("reset");
1248 | }
1249 | };
1250 |
1251 | Dropzone.prototype.removeAllFiles = function(cancelIfNecessary) {
1252 | var file, _i, _len, _ref;
1253 | if (cancelIfNecessary == null) {
1254 | cancelIfNecessary = false;
1255 | }
1256 | _ref = this.files.slice();
1257 | for (_i = 0, _len = _ref.length; _i < _len; _i++) {
1258 | file = _ref[_i];
1259 | if (file.status !== Dropzone.UPLOADING || cancelIfNecessary) {
1260 | this.removeFile(file);
1261 | }
1262 | }
1263 | return null;
1264 | };
1265 |
1266 | Dropzone.prototype.createThumbnail = function(file, callback) {
1267 | var fileReader,
1268 | _this = this;
1269 | fileReader = new FileReader;
1270 | fileReader.onload = function() {
1271 | var img;
1272 | img = document.createElement("img");
1273 | img.onload = function() {
1274 | var canvas, ctx, resizeInfo, thumbnail, _ref, _ref1, _ref2, _ref3;
1275 | file.width = img.width;
1276 | file.height = img.height;
1277 | resizeInfo = _this.options.resize.call(_this, file);
1278 | if (resizeInfo.trgWidth == null) {
1279 | resizeInfo.trgWidth = _this.options.thumbnailWidth;
1280 | }
1281 | if (resizeInfo.trgHeight == null) {
1282 | resizeInfo.trgHeight = _this.options.thumbnailHeight;
1283 | }
1284 | canvas = document.createElement("canvas");
1285 | ctx = canvas.getContext("2d");
1286 | canvas.width = resizeInfo.trgWidth;
1287 | canvas.height = resizeInfo.trgHeight;
1288 | drawImageIOSFix(ctx, img, (_ref = resizeInfo.srcX) != null ? _ref : 0, (_ref1 = resizeInfo.srcY) != null ? _ref1 : 0, resizeInfo.srcWidth, resizeInfo.srcHeight, (_ref2 = resizeInfo.trgX) != null ? _ref2 : 0, (_ref3 = resizeInfo.trgY) != null ? _ref3 : 0, resizeInfo.trgWidth, resizeInfo.trgHeight);
1289 | thumbnail = canvas.toDataURL("image/png");
1290 | _this.emit("thumbnail", file, thumbnail);
1291 | if (callback != null) {
1292 | return callback();
1293 | }
1294 | };
1295 | return img.src = fileReader.result;
1296 | };
1297 | return fileReader.readAsDataURL(file);
1298 | };
1299 |
1300 | Dropzone.prototype.processQueue = function() {
1301 | var i, parallelUploads, processingLength, queuedFiles;
1302 | parallelUploads = this.options.parallelUploads;
1303 | processingLength = this.getUploadingFiles().length;
1304 | i = processingLength;
1305 | if (processingLength >= parallelUploads) {
1306 | return;
1307 | }
1308 | queuedFiles = this.getQueuedFiles();
1309 | if (!(queuedFiles.length > 0)) {
1310 | return;
1311 | }
1312 | if (this.options.uploadMultiple) {
1313 | return this.processFiles(queuedFiles.slice(0, parallelUploads - processingLength));
1314 | } else {
1315 | while (i < parallelUploads) {
1316 | if (!queuedFiles.length) {
1317 | return;
1318 | }
1319 | this.processFile(queuedFiles.shift());
1320 | i++;
1321 | }
1322 | }
1323 | };
1324 |
1325 | Dropzone.prototype.processFile = function(file) {
1326 | return this.processFiles([file]);
1327 | };
1328 |
1329 | Dropzone.prototype.processFiles = function(files) {
1330 | var file, _i, _len;
1331 | for (_i = 0, _len = files.length; _i < _len; _i++) {
1332 | file = files[_i];
1333 | file.processing = true;
1334 | file.status = Dropzone.UPLOADING;
1335 | this.emit("processing", file);
1336 | }
1337 | if (this.options.uploadMultiple) {
1338 | this.emit("processingmultiple", files);
1339 | }
1340 | return this.uploadFiles(files);
1341 | };
1342 |
1343 | Dropzone.prototype._getFilesWithXhr = function(xhr) {
1344 | var file, files;
1345 | return files = (function() {
1346 | var _i, _len, _ref, _results;
1347 | _ref = this.files;
1348 | _results = [];
1349 | for (_i = 0, _len = _ref.length; _i < _len; _i++) {
1350 | file = _ref[_i];
1351 | if (file.xhr === xhr) {
1352 | _results.push(file);
1353 | }
1354 | }
1355 | return _results;
1356 | }).call(this);
1357 | };
1358 |
1359 | Dropzone.prototype.cancelUpload = function(file) {
1360 | var groupedFile, groupedFiles, _i, _j, _len, _len1, _ref;
1361 | if (file.status === Dropzone.UPLOADING) {
1362 | groupedFiles = this._getFilesWithXhr(file.xhr);
1363 | for (_i = 0, _len = groupedFiles.length; _i < _len; _i++) {
1364 | groupedFile = groupedFiles[_i];
1365 | groupedFile.status = Dropzone.CANCELED;
1366 | }
1367 | file.xhr.abort();
1368 | for (_j = 0, _len1 = groupedFiles.length; _j < _len1; _j++) {
1369 | groupedFile = groupedFiles[_j];
1370 | this.emit("canceled", groupedFile);
1371 | }
1372 | if (this.options.uploadMultiple) {
1373 | this.emit("canceledmultiple", groupedFiles);
1374 | }
1375 | } else if ((_ref = file.status) === Dropzone.ADDED || _ref === Dropzone.QUEUED) {
1376 | file.status = Dropzone.CANCELED;
1377 | this.emit("canceled", file);
1378 | if (this.options.uploadMultiple) {
1379 | this.emit("canceledmultiple", [file]);
1380 | }
1381 | }
1382 | if (this.options.autoProcessQueue) {
1383 | return this.processQueue();
1384 | }
1385 | };
1386 |
1387 | Dropzone.prototype.uploadFile = function(file) {
1388 | return this.uploadFiles([file]);
1389 | };
1390 |
1391 | Dropzone.prototype.uploadFiles = function(files) {
1392 | var file, formData, handleError, headerName, headerValue, headers, input, inputName, inputType, key, option, progressObj, response, updateProgress, value, xhr, _i, _j, _k, _l, _len, _len1, _len2, _len3, _len4, _m, _ref, _ref1, _ref2, _ref3, _ref4,
1393 | _this = this;
1394 | xhr = new XMLHttpRequest();
1395 | for (_i = 0, _len = files.length; _i < _len; _i++) {
1396 | file = files[_i];
1397 | file.xhr = xhr;
1398 | }
1399 | xhr.open(this.options.method, this.options.url, true);
1400 | xhr.withCredentials = !!this.options.withCredentials;
1401 | response = null;
1402 | handleError = function() {
1403 | var _j, _len1, _results;
1404 | _results = [];
1405 | for (_j = 0, _len1 = files.length; _j < _len1; _j++) {
1406 | file = files[_j];
1407 | _results.push(_this._errorProcessing(files, response || _this.options.dictResponseError.replace("{{statusCode}}", xhr.status), xhr));
1408 | }
1409 | return _results;
1410 | };
1411 | updateProgress = function(e) {
1412 | var allFilesFinished, progress, _j, _k, _l, _len1, _len2, _len3, _results;
1413 | if (e != null) {
1414 | progress = 100 * e.loaded / e.total;
1415 | for (_j = 0, _len1 = files.length; _j < _len1; _j++) {
1416 | file = files[_j];
1417 | file.upload = {
1418 | progress: progress,
1419 | total: e.total,
1420 | bytesSent: e.loaded
1421 | };
1422 | }
1423 | } else {
1424 | allFilesFinished = true;
1425 | progress = 100;
1426 | for (_k = 0, _len2 = files.length; _k < _len2; _k++) {
1427 | file = files[_k];
1428 | if (!(file.upload.progress === 100 && file.upload.bytesSent === file.upload.total)) {
1429 | allFilesFinished = false;
1430 | }
1431 | file.upload.progress = progress;
1432 | file.upload.bytesSent = file.upload.total;
1433 | }
1434 | if (allFilesFinished) {
1435 | return;
1436 | }
1437 | }
1438 | _results = [];
1439 | for (_l = 0, _len3 = files.length; _l < _len3; _l++) {
1440 | file = files[_l];
1441 | _results.push(_this.emit("uploadprogress", file, progress, file.upload.bytesSent));
1442 | }
1443 | return _results;
1444 | };
1445 | xhr.onload = function(e) {
1446 | var _ref;
1447 | if (files[0].status === Dropzone.CANCELED) {
1448 | return;
1449 | }
1450 | if (xhr.readyState !== 4) {
1451 | return;
1452 | }
1453 | response = xhr.responseText;
1454 | if (xhr.getResponseHeader("content-type") && ~xhr.getResponseHeader("content-type").indexOf("application/json")) {
1455 | try {
1456 | response = JSON.parse(response);
1457 | } catch (_error) {
1458 | e = _error;
1459 | response = "Invalid JSON response from server.";
1460 | }
1461 | }
1462 | updateProgress();
1463 | if (!((200 <= (_ref = xhr.status) && _ref < 300))) {
1464 | return handleError();
1465 | } else {
1466 | return _this._finished(files, response, e);
1467 | }
1468 | };
1469 | xhr.onerror = function() {
1470 | if (files[0].status === Dropzone.CANCELED) {
1471 | return;
1472 | }
1473 | return handleError();
1474 | };
1475 | progressObj = (_ref = xhr.upload) != null ? _ref : xhr;
1476 | progressObj.onprogress = updateProgress;
1477 | headers = {
1478 | "Accept": "application/json",
1479 | "Cache-Control": "no-cache",
1480 | "X-Requested-With": "XMLHttpRequest"
1481 | };
1482 | if (this.options.headers) {
1483 | extend(headers, this.options.headers);
1484 | }
1485 | for (headerName in headers) {
1486 | headerValue = headers[headerName];
1487 | xhr.setRequestHeader(headerName, headerValue);
1488 | }
1489 | formData = new FormData();
1490 | if (this.options.params) {
1491 | _ref1 = this.options.params;
1492 | for (key in _ref1) {
1493 | value = _ref1[key];
1494 | formData.append(key, value);
1495 | }
1496 | }
1497 | for (_j = 0, _len1 = files.length; _j < _len1; _j++) {
1498 | file = files[_j];
1499 | this.emit("sending", file, xhr, formData);
1500 | }
1501 | if (this.options.uploadMultiple) {
1502 | this.emit("sendingmultiple", files, xhr, formData);
1503 | }
1504 | if (this.element.tagName === "FORM") {
1505 | _ref2 = this.element.querySelectorAll("input, textarea, select, button");
1506 | for (_k = 0, _len2 = _ref2.length; _k < _len2; _k++) {
1507 | input = _ref2[_k];
1508 | inputName = input.getAttribute("name");
1509 | inputType = input.getAttribute("type");
1510 | if (input.tagName === "SELECT" && input.hasAttribute("multiple")) {
1511 | _ref3 = input.options;
1512 | for (_l = 0, _len3 = _ref3.length; _l < _len3; _l++) {
1513 | option = _ref3[_l];
1514 | if (option.selected) {
1515 | formData.append(inputName, option.value);
1516 | }
1517 | }
1518 | } else if (!inputType || ((_ref4 = inputType.toLowerCase()) !== "checkbox" && _ref4 !== "radio") || input.checked) {
1519 | formData.append(inputName, input.value);
1520 | }
1521 | }
1522 | }
1523 | for (_m = 0, _len4 = files.length; _m < _len4; _m++) {
1524 | file = files[_m];
1525 | formData.append("" + this.options.paramName + (this.options.uploadMultiple ? "[]" : ""), file, file.name);
1526 | }
1527 | return xhr.send(formData);
1528 | };
1529 |
1530 | Dropzone.prototype._finished = function(files, responseText, e) {
1531 | var file, _i, _len;
1532 | for (_i = 0, _len = files.length; _i < _len; _i++) {
1533 | file = files[_i];
1534 | file.status = Dropzone.SUCCESS;
1535 | this.emit("success", file, responseText, e);
1536 | this.emit("complete", file);
1537 | }
1538 | if (this.options.uploadMultiple) {
1539 | this.emit("successmultiple", files, responseText, e);
1540 | this.emit("completemultiple", files);
1541 | }
1542 | if (this.options.autoProcessQueue) {
1543 | return this.processQueue();
1544 | }
1545 | };
1546 |
1547 | Dropzone.prototype._errorProcessing = function(files, message, xhr) {
1548 | var file, _i, _len;
1549 | for (_i = 0, _len = files.length; _i < _len; _i++) {
1550 | file = files[_i];
1551 | file.status = Dropzone.ERROR;
1552 | this.emit("error", file, message, xhr);
1553 | this.emit("complete", file);
1554 | }
1555 | if (this.options.uploadMultiple) {
1556 | this.emit("errormultiple", files, message, xhr);
1557 | this.emit("completemultiple", files);
1558 | }
1559 | if (this.options.autoProcessQueue) {
1560 | return this.processQueue();
1561 | }
1562 | };
1563 |
1564 | return Dropzone;
1565 |
1566 | })(Em);
1567 |
1568 | Dropzone.version = "3.8.4";
1569 |
1570 | Dropzone.options = {};
1571 |
1572 | Dropzone.optionsForElement = function(element) {
1573 | if (element.getAttribute("id")) {
1574 | return Dropzone.options[camelize(element.getAttribute("id"))];
1575 | } else {
1576 | return void 0;
1577 | }
1578 | };
1579 |
1580 | Dropzone.instances = [];
1581 |
1582 | Dropzone.forElement = function(element) {
1583 | if (typeof element === "string") {
1584 | element = document.querySelector(element);
1585 | }
1586 | if ((element != null ? element.dropzone : void 0) == null) {
1587 | throw new Error("No Dropzone found for given element. This is probably because you're trying to access it before Dropzone had the time to initialize. Use the `init` option to setup any additional observers on your Dropzone.");
1588 | }
1589 | return element.dropzone;
1590 | };
1591 |
1592 | Dropzone.autoDiscover = true;
1593 |
1594 | Dropzone.discover = function() {
1595 | var checkElements, dropzone, dropzones, _i, _len, _results;
1596 | if (document.querySelectorAll) {
1597 | dropzones = document.querySelectorAll(".dropzone");
1598 | } else {
1599 | dropzones = [];
1600 | checkElements = function(elements) {
1601 | var el, _i, _len, _results;
1602 | _results = [];
1603 | for (_i = 0, _len = elements.length; _i < _len; _i++) {
1604 | el = elements[_i];
1605 | if (/(^| )dropzone($| )/.test(el.className)) {
1606 | _results.push(dropzones.push(el));
1607 | } else {
1608 | _results.push(void 0);
1609 | }
1610 | }
1611 | return _results;
1612 | };
1613 | checkElements(document.getElementsByTagName("div"));
1614 | checkElements(document.getElementsByTagName("form"));
1615 | }
1616 | _results = [];
1617 | for (_i = 0, _len = dropzones.length; _i < _len; _i++) {
1618 | dropzone = dropzones[_i];
1619 | if (Dropzone.optionsForElement(dropzone) !== false) {
1620 | _results.push(new Dropzone(dropzone));
1621 | } else {
1622 | _results.push(void 0);
1623 | }
1624 | }
1625 | return _results;
1626 | };
1627 |
1628 | Dropzone.blacklistedBrowsers = [/opera.*Macintosh.*version\/12/i];
1629 |
1630 | Dropzone.isBrowserSupported = function() {
1631 | var capableBrowser, regex, _i, _len, _ref;
1632 | capableBrowser = true;
1633 | if (window.File && window.FileReader && window.FileList && window.Blob && window.FormData && document.querySelector) {
1634 | if (!("classList" in document.createElement("a"))) {
1635 | capableBrowser = false;
1636 | } else {
1637 | _ref = Dropzone.blacklistedBrowsers;
1638 | for (_i = 0, _len = _ref.length; _i < _len; _i++) {
1639 | regex = _ref[_i];
1640 | if (regex.test(navigator.userAgent)) {
1641 | capableBrowser = false;
1642 | continue;
1643 | }
1644 | }
1645 | }
1646 | } else {
1647 | capableBrowser = false;
1648 | }
1649 | return capableBrowser;
1650 | };
1651 |
1652 | without = function(list, rejectedItem) {
1653 | var item, _i, _len, _results;
1654 | _results = [];
1655 | for (_i = 0, _len = list.length; _i < _len; _i++) {
1656 | item = list[_i];
1657 | if (item !== rejectedItem) {
1658 | _results.push(item);
1659 | }
1660 | }
1661 | return _results;
1662 | };
1663 |
1664 | camelize = function(str) {
1665 | return str.replace(/[\-_](\w)/g, function(match) {
1666 | return match[1].toUpperCase();
1667 | });
1668 | };
1669 |
1670 | Dropzone.createElement = function(string) {
1671 | var div;
1672 | div = document.createElement("div");
1673 | div.innerHTML = string;
1674 | return div.childNodes[0];
1675 | };
1676 |
1677 | Dropzone.elementInside = function(element, container) {
1678 | if (element === container) {
1679 | return true;
1680 | }
1681 | while (element = element.parentNode) {
1682 | if (element === container) {
1683 | return true;
1684 | }
1685 | }
1686 | return false;
1687 | };
1688 |
1689 | Dropzone.getElement = function(el, name) {
1690 | var element;
1691 | if (typeof el === "string") {
1692 | element = document.querySelector(el);
1693 | } else if (el.nodeType != null) {
1694 | element = el;
1695 | }
1696 | if (element == null) {
1697 | throw new Error("Invalid `" + name + "` option provided. Please provide a CSS selector or a plain HTML element.");
1698 | }
1699 | return element;
1700 | };
1701 |
1702 | Dropzone.getElements = function(els, name) {
1703 | var e, el, elements, _i, _j, _len, _len1, _ref;
1704 | if (els instanceof Array) {
1705 | elements = [];
1706 | try {
1707 | for (_i = 0, _len = els.length; _i < _len; _i++) {
1708 | el = els[_i];
1709 | elements.push(this.getElement(el, name));
1710 | }
1711 | } catch (_error) {
1712 | e = _error;
1713 | elements = null;
1714 | }
1715 | } else if (typeof els === "string") {
1716 | elements = [];
1717 | _ref = document.querySelectorAll(els);
1718 | for (_j = 0, _len1 = _ref.length; _j < _len1; _j++) {
1719 | el = _ref[_j];
1720 | elements.push(el);
1721 | }
1722 | } else if (els.nodeType != null) {
1723 | elements = [els];
1724 | }
1725 | if (!((elements != null) && elements.length)) {
1726 | throw new Error("Invalid `" + name + "` option provided. Please provide a CSS selector, a plain HTML element or a list of those.");
1727 | }
1728 | return elements;
1729 | };
1730 |
1731 | Dropzone.confirm = function(question, accepted, rejected) {
1732 | if (window.confirm(question)) {
1733 | return accepted();
1734 | } else if (rejected != null) {
1735 | return rejected();
1736 | }
1737 | };
1738 |
1739 | Dropzone.isValidFile = function(file, acceptedFiles) {
1740 | var baseMimeType, mimeType, validType, _i, _len;
1741 | if (!acceptedFiles) {
1742 | return true;
1743 | }
1744 | acceptedFiles = acceptedFiles.split(",");
1745 | mimeType = file.type;
1746 | baseMimeType = mimeType.replace(/\/.*$/, "");
1747 | for (_i = 0, _len = acceptedFiles.length; _i < _len; _i++) {
1748 | validType = acceptedFiles[_i];
1749 | validType = validType.trim();
1750 | if (validType.charAt(0) === ".") {
1751 | if (file.name.toLowerCase().indexOf(validType.toLowerCase(), file.name.length - validType.length) !== -1) {
1752 | return true;
1753 | }
1754 | } else if (/\/\*$/.test(validType)) {
1755 | if (baseMimeType === validType.replace(/\/.*$/, "")) {
1756 | return true;
1757 | }
1758 | } else {
1759 | if (mimeType === validType) {
1760 | return true;
1761 | }
1762 | }
1763 | }
1764 | return false;
1765 | };
1766 |
1767 | if (typeof jQuery !== "undefined" && jQuery !== null) {
1768 | jQuery.fn.dropzone = function(options) {
1769 | return this.each(function() {
1770 | return new Dropzone(this, options);
1771 | });
1772 | };
1773 | }
1774 |
1775 | if (typeof module !== "undefined" && module !== null) {
1776 | module.exports = Dropzone;
1777 | } else {
1778 | window.Dropzone = Dropzone;
1779 | }
1780 |
1781 | Dropzone.ADDED = "added";
1782 |
1783 | Dropzone.QUEUED = "queued";
1784 |
1785 | Dropzone.ACCEPTED = Dropzone.QUEUED;
1786 |
1787 | Dropzone.UPLOADING = "uploading";
1788 |
1789 | Dropzone.PROCESSING = Dropzone.UPLOADING;
1790 |
1791 | Dropzone.CANCELED = "canceled";
1792 |
1793 | Dropzone.ERROR = "error";
1794 |
1795 | Dropzone.SUCCESS = "success";
1796 |
1797 | /*
1798 |
1799 | Bugfix for iOS 6 and 7
1800 | Source: http://stackoverflow.com/questions/11929099/html5-canvas-drawimage-ratio-bug-ios
1801 | based on the work of https://github.com/stomita/ios-imagefile-megapixel
1802 | */
1803 |
1804 |
1805 | detectVerticalSquash = function(img) {
1806 | var alpha, canvas, ctx, data, ey, ih, iw, py, ratio, sy;
1807 | iw = img.naturalWidth;
1808 | ih = img.naturalHeight;
1809 | canvas = document.createElement("canvas");
1810 | canvas.width = 1;
1811 | canvas.height = ih;
1812 | ctx = canvas.getContext("2d");
1813 | ctx.drawImage(img, 0, 0);
1814 | data = ctx.getImageData(0, 0, 1, ih).data;
1815 | sy = 0;
1816 | ey = ih;
1817 | py = ih;
1818 | while (py > sy) {
1819 | alpha = data[(py - 1) * 4 + 3];
1820 | if (alpha === 0) {
1821 | ey = py;
1822 | } else {
1823 | sy = py;
1824 | }
1825 | py = (ey + sy) >> 1;
1826 | }
1827 | ratio = py / ih;
1828 | if (ratio === 0) {
1829 | return 1;
1830 | } else {
1831 | return ratio;
1832 | }
1833 | };
1834 |
1835 | drawImageIOSFix = function(ctx, img, sx, sy, sw, sh, dx, dy, dw, dh) {
1836 | var vertSquashRatio;
1837 | vertSquashRatio = detectVerticalSquash(img);
1838 | return ctx.drawImage(img, sx, sy, sw, sh, dx, dy, dw, dh / vertSquashRatio);
1839 | };
1840 |
1841 | /*
1842 | # contentloaded.js
1843 | #
1844 | # Author: Diego Perini (diego.perini at gmail.com)
1845 | # Summary: cross-browser wrapper for DOMContentLoaded
1846 | # Updated: 20101020
1847 | # License: MIT
1848 | # Version: 1.2
1849 | #
1850 | # URL:
1851 | # http://javascript.nwbox.com/ContentLoaded/
1852 | # http://javascript.nwbox.com/ContentLoaded/MIT-LICENSE
1853 | */
1854 |
1855 |
1856 | contentLoaded = function(win, fn) {
1857 | var add, doc, done, init, poll, pre, rem, root, top;
1858 | done = false;
1859 | top = true;
1860 | doc = win.document;
1861 | root = doc.documentElement;
1862 | add = (doc.addEventListener ? "addEventListener" : "attachEvent");
1863 | rem = (doc.addEventListener ? "removeEventListener" : "detachEvent");
1864 | pre = (doc.addEventListener ? "" : "on");
1865 | init = function(e) {
1866 | if (e.type === "readystatechange" && doc.readyState !== "complete") {
1867 | return;
1868 | }
1869 | (e.type === "load" ? win : doc)[rem](pre + e.type, init, false);
1870 | if (!done && (done = true)) {
1871 | return fn.call(win, e.type || e);
1872 | }
1873 | };
1874 | poll = function() {
1875 | var e;
1876 | try {
1877 | root.doScroll("left");
1878 | } catch (_error) {
1879 | e = _error;
1880 | setTimeout(poll, 50);
1881 | return;
1882 | }
1883 | return init("poll");
1884 | };
1885 | if (doc.readyState !== "complete") {
1886 | if (doc.createEventObject && root.doScroll) {
1887 | try {
1888 | top = !win.frameElement;
1889 | } catch (_error) {}
1890 | if (top) {
1891 | poll();
1892 | }
1893 | }
1894 | doc[add](pre + "DOMContentLoaded", init, false);
1895 | doc[add](pre + "readystatechange", init, false);
1896 | return win[add](pre + "load", init, false);
1897 | }
1898 | };
1899 |
1900 | Dropzone._autoDiscoverFunction = function() {
1901 | if (Dropzone.autoDiscover) {
1902 | return Dropzone.discover();
1903 | }
1904 | };
1905 |
1906 | contentLoaded(window, Dropzone._autoDiscoverFunction);
1907 |
1908 | }).call(this);
1909 |
1910 | });
1911 | require.alias("component-emitter/index.js", "dropzone/deps/emitter/index.js");
1912 | require.alias("component-emitter/index.js", "emitter/index.js");
1913 | if (typeof exports == "object") {
1914 | module.exports = require("dropzone");
1915 | } else if (typeof define == "function" && define.amd) {
1916 | define(function(){ return require("dropzone"); });
1917 | } else {
1918 | this["Dropzone"] = require("dropzone");
1919 | }})();
--------------------------------------------------------------------------------
/lib/instruments.js:
--------------------------------------------------------------------------------
1 | var instruments = [
2 | {"hexcode":"0x00", "family":"Piano", "instrument":"Acoustic Grand Piano"},
3 | {"hexcode":"0x01", "family":"Piano", "instrument":"Bright Acoustic Piano"},
4 | {"hexcode":"0x02", "family":"Piano", "instrument":"Electric Grand Piano"},
5 | {"hexcode":"0x03", "family":"Piano", "instrument":"Honky-tonk Piano"},
6 | {"hexcode":"0x04", "family":"Piano", "instrument":"Electric Piano 1"},
7 | {"hexcode":"0x05", "family":"Piano", "instrument":"Electric Piano 2"},
8 | {"hexcode":"0x06", "family":"Piano", "instrument":"Harpsichord"},
9 | {"hexcode":"0x07", "family":"Piano", "instrument":"Clavichord"},
10 | {"hexcode":"0x08", "family":"Chromatic Percussion", "instrument":"Celesta"},
11 | {"hexcode":"0x09", "family":"Chromatic Percussion", "instrument":"Glockenspiel"},
12 | {"hexcode":"0x0A", "family":"Chromatic Percussion", "instrument":"Music Box"},
13 | {"hexcode":"0x0B", "family":"Chromatic Percussion", "instrument":"Vibraphone"},
14 | {"hexcode":"0x0C", "family":"Chromatic Percussion", "instrument":"Marimba"},
15 | {"hexcode":"0x0D", "family":"Chromatic Percussion", "instrument":"Xylophone"},
16 | {"hexcode":"0x0E", "family":"Chromatic Percussion", "instrument":"Tubular bells"},
17 | {"hexcode":"0x0F", "family":"Chromatic Percussion", "instrument":"Dulcimer"},
18 | {"hexcode":"0x10", "family":"Organ", "instrument":"Drawbar Organ"},
19 | {"hexcode":"0x11", "family":"Organ", "instrument":"Percussive Organ"},
20 | {"hexcode":"0x12", "family":"Organ", "instrument":"Rock Organ"},
21 | {"hexcode":"0x13", "family":"Organ", "instrument":"Church Organ"},
22 | {"hexcode":"0x14", "family":"Organ", "instrument":"Reed Organ"},
23 | {"hexcode":"0x15", "family":"Organ", "instrument":"Accordion"},
24 | {"hexcode":"0x16", "family":"Organ", "instrument":"Harmonica"},
25 | {"hexcode":"0x17", "family":"Organ", "instrument":"Tango Accordion"},
26 | {"hexcode":"0x18", "family":"Guitar", "instrument":"Acoustic Guitar (nylon)"},
27 | {"hexcode":"0x19", "family":"Guitar", "instrument":"Acoustic Guitar (steel)"},
28 | {"hexcode":"0x1A", "family":"Guitar", "instrument":"Electric Guitar (jazz)"},
29 | {"hexcode":"0x1B", "family":"Guitar", "instrument":"Electric Guitar (clean)"},
30 | {"hexcode":"0x1C", "family":"Guitar", "instrument":"Electric Guitar (muted)"},
31 | {"hexcode":"0x1D", "family":"Guitar", "instrument":"Overdriven Guitar"},
32 | {"hexcode":"0x1E", "family":"Guitar", "instrument":"Distortion Guitar"},
33 | {"hexcode":"0x1F", "family":"Guitar", "instrument":"Guitar harmonics"},
34 | {"hexcode":"0x20", "family":"Bass", "instrument":"Acoustic Bass"},
35 | {"hexcode":"0x21", "family":"Bass", "instrument":"Electric Bass (finger)"},
36 | {"hexcode":"0x22", "family":"Bass", "instrument":"Electric Bass (pick)"},
37 | {"hexcode":"0x23", "family":"Bass", "instrument":"Fretless Bass"},
38 | {"hexcode":"0x24", "family":"Bass", "instrument":"Slap Bass 1"},
39 | {"hexcode":"0x25", "family":"Bass", "instrument":"Slap bass 2"},
40 | {"hexcode":"0x26", "family":"Bass", "instrument":"Synth Bass 1"},
41 | {"hexcode":"0x27", "family":"Bass", "instrument":"Synth Bass 2"},
42 | {"hexcode":"0x28", "family":"Strings", "instrument":"Violin"},
43 | {"hexcode":"0x29", "family":"Strings", "instrument":"Viola"},
44 | {"hexcode":"0x2A", "family":"Strings", "instrument":"Cello"},
45 | {"hexcode":"0x2B", "family":"Strings", "instrument":"Contrabass"},
46 | {"hexcode":"0x2C", "family":"Strings", "instrument":"Tremolo Strings"},
47 | {"hexcode":"0x2D", "family":"Strings", "instrument":"Pizzicato Strings"},
48 | {"hexcode":"0x2E", "family":"Strings", "instrument":"Orchestral Harp"},
49 | {"hexcode":"0x2F", "family":"Strings", "instrument":"Timpani"},
50 | {"hexcode":"0x30", "family":"Ensemble", "instrument":"String Ensemble 1"},
51 | {"hexcode":"0x31", "family":"Ensemble", "instrument":"String Ensemble 2"},
52 | {"hexcode":"0x32", "family":"Ensemble", "instrument":"SynthStrings 1"},
53 | {"hexcode":"0x33", "family":"Ensemble", "instrument":"SynthStrings 2"},
54 | {"hexcode":"0x34", "family":"Ensemble", "instrument":"Choir Aahs"},
55 | {"hexcode":"0x35", "family":"Ensemble", "instrument":"Voice Oohs"},
56 | {"hexcode":"0x36", "family":"Ensemble", "instrument":"Synth Voice"},
57 | {"hexcode":"0x37", "family":"Ensemble", "instrument":"Orchestra Hit"},
58 | {"hexcode":"0x38", "family":"Brass", "instrument":"Trumpet"},
59 | {"hexcode":"0x39", "family":"Brass", "instrument":"Trombone"},
60 | {"hexcode":"0x3A", "family":"Brass", "instrument":"Tuba"},
61 | {"hexcode":"0x3B", "family":"Brass", "instrument":"Muted Trombone"},
62 | {"hexcode":"0x3C", "family":"Brass", "instrument":"French Horn"},
63 | {"hexcode":"0x3D", "family":"Brass", "instrument":"Brass Section"},
64 | {"hexcode":"0x3E", "family":"Brass", "instrument":"SynthBrass 1"},
65 | {"hexcode":"0x3F", "family":"Brass", "instrument":"SynthBrass 2"},
66 | {"hexcode":"0x40", "family":"Reed", "instrument":"Soprano Sax"},
67 | {"hexcode":"0x41", "family":"Reed", "instrument":"Alto Sax"},
68 | {"hexcode":"0x42", "family":"Reed", "instrument":"Tenor Sax"},
69 | {"hexcode":"0x43", "family":"Reed", "instrument":"Baritone Sax"},
70 | {"hexcode":"0x44", "family":"Reed", "instrument":"Oboe"},
71 | {"hexcode":"0x45", "family":"Reed", "instrument":"English Horn"},
72 | {"hexcode":"0x46", "family":"Reed", "instrument":"Bassoon"},
73 | {"hexcode":"0x47", "family":"Reed", "instrument":"Clarinet"},
74 | {"hexcode":"0x48", "family":"Pipe", "instrument":"Piccolo"},
75 | {"hexcode":"0x49", "family":"Pipe", "instrument":"Flute"},
76 | {"hexcode":"0x4A", "family":"Pipe", "instrument":"Recorder"},
77 | {"hexcode":"0x4B", "family":"Pipe", "instrument":"Pan Flute"},
78 | {"hexcode":"0x4C", "family":"Pipe", "instrument":"Blown Bottle"},
79 | {"hexcode":"0x4D", "family":"Pipe", "instrument":"Shakuhachi"},
80 | {"hexcode":"0x4E", "family":"Pipe", "instrument":"Whistle"},
81 | {"hexcode":"0x4F", "family":"Pipe", "instrument":"Ocarina"},
82 | {"hexcode":"0x50", "family":"Synth Lead", "instrument":"Lead 1 (square)"},
83 | {"hexcode":"0x51", "family":"Synth Lead", "instrument":"Lead 2 (sawtooth)"},
84 | {"hexcode":"0x52", "family":"Synth Lead", "instrument":"Lead 3 (calliope)"},
85 | {"hexcode":"0x53", "family":"Synth Lead", "instrument":"Lead 4 (chiff)"},
86 | {"hexcode":"0x54", "family":"Synth Lead", "instrument":"Lead 5 (charang)"},
87 | {"hexcode":"0x55", "family":"Synth Lead", "instrument":"Lead 6 (voice)"},
88 | {"hexcode":"0x56", "family":"Synth Lead", "instrument":"Lead 7 (fifths)"},
89 | {"hexcode":"0x57", "family":"Synth Lead", "instrument":"Lead 8 (bass + lead)"},
90 | {"hexcode":"0x58", "family":"Synth Pad", "instrument":"Pad 1 (new age)"},
91 | {"hexcode":"0x59", "family":"Synth Pad", "instrument":"Pad 2 (warm)"},
92 | {"hexcode":"0x5A", "family":"Synth Pad", "instrument":"Pad 3 (polysynth)"},
93 | {"hexcode":"0x5B", "family":"Synth Pad", "instrument":"Pad 4 (choir)"},
94 | {"hexcode":"0x5C", "family":"Synth Pad", "instrument":"Pad 5 (bowed)"},
95 | {"hexcode":"0x5D", "family":"Synth Pad", "instrument":"Pad 6 (metallic)"},
96 | {"hexcode":"0x5E", "family":"Synth Pad", "instrument":"Pad 7 (halo)"},
97 | {"hexcode":"0x5F", "family":"Synth Pad", "instrument":"Pad 8 (sweep)"},
98 | {"hexcode":"0x60", "family":"Synth Effects", "instrument":"FX 1 (rain)"},
99 | {"hexcode":"0x61", "family":"Synth Effects", "instrument":"FX 2 (soundtrack)"},
100 | {"hexcode":"0x62", "family":"Synth Effects", "instrument":"FX 3 (crystal)"},
101 | {"hexcode":"0x63", "family":"Synth Effects", "instrument":"FX 4 (atmosphere)"},
102 | {"hexcode":"0x64", "family":"Synth Effects", "instrument":"FX 5 (brightness)"},
103 | {"hexcode":"0x65", "family":"Synth Effects", "instrument":"FX 6 (goblins)"},
104 | {"hexcode":"0x66", "family":"Synth Effects", "instrument":"FX 7 (echoes)"},
105 | {"hexcode":"0x67", "family":"Synth Effects", "instrument":"FX 8 (sci-fi)"},
106 | {"hexcode":"0x68", "family":"Ethnic", "instrument":"Sitar"},
107 | {"hexcode":"0x69", "family":"Ethnic", "instrument":"Banjo"},
108 | {"hexcode":"0x6A", "family":"Ethnic", "instrument":"Shamisen"},
109 | {"hexcode":"0x6B", "family":"Ethnic", "instrument":"Koto"},
110 | {"hexcode":"0x6C", "family":"Ethnic", "instrument":"Kalimba"},
111 | {"hexcode":"0x6D", "family":"Ethnic", "instrument":"Bag pipe"},
112 | {"hexcode":"0x6E", "family":"Ethnic", "instrument":"Fiddle"},
113 | {"hexcode":"0x6F", "family":"Ethnic", "instrument":"Shanai"},
114 | {"hexcode":"0x70", "family":"Percussive", "instrument":"Tinkle Bell"},
115 | {"hexcode":"0x71", "family":"Percussive", "instrument":"Agogo"},
116 | {"hexcode":"0x72", "family":"Percussive", "instrument":"Steel Drums"},
117 | {"hexcode":"0x73", "family":"Percussive", "instrument":"Woodblock"},
118 | {"hexcode":"0x74", "family":"Percussive", "instrument":"Taiko Drum"},
119 | {"hexcode":"0x75", "family":"Percussive", "instrument":"Melodic Tom"},
120 | {"hexcode":"0x76", "family":"Percussive", "instrument":"Synth Drum"},
121 | {"hexcode":"0x77", "family":"Percussive", "instrument":"Reverse Cymbal"},
122 | {"hexcode":"0x78", "family":"Sound Effects", "instrument":"Guitar Fret Noise"},
123 | {"hexcode":"0x79", "family":"Sound Effects", "instrument":"Breath Noise"},
124 | {"hexcode":"0x7A", "family":"Sound Effects", "instrument":"Seashore"},
125 | {"hexcode":"0x7B", "family":"Sound Effects", "instrument":"Bird Tweet"},
126 | {"hexcode":"0x7C", "family":"Sound Effects", "instrument":"Telephone Ring"},
127 | {"hexcode":"0x7D", "family":"Sound Effects", "instrument":"Helicopter"},
128 | {"hexcode":"0x7E", "family":"Sound Effects", "instrument":"Applause"},
129 | {"hexcode":"0x7F", "family":"Sound Effects", "instrument":"Gunshot"}
130 | ];
131 |
--------------------------------------------------------------------------------
/lib/jsmidgen.js:
--------------------------------------------------------------------------------
1 | var DEFAULT_VOLUME = 90;
2 | var DEFAULT_DURATION = 128;
3 | var DEFAULT_CHANNEL = 0;
4 |
5 | /* ******************************************************************
6 | * Utility functions
7 | ****************************************************************** */
8 |
9 | var Util = {
10 |
11 | midi_letter_pitches: { a:21, b:23, c:12, d:14, e:16, f:17, g:19 },
12 |
13 | midiPitchFromNote: function(n) {
14 | var matches = /([a-g])(#+|b+)?([0-9]+)$/i.exec(n);
15 | var note = matches[1].toLowerCase(), accidental = matches[2] || '', octave = parseInt(matches[3], 10);
16 | return (12 * octave) + Util.midi_letter_pitches[note] + (accidental.substr(0,1)=='#'?1:-1) * accidental.length;
17 | },
18 |
19 | ensureMidiPitch: function(p) {
20 | if (typeof p == 'number' || !/[^0-9]/.test(p)) {
21 | // numeric pitch
22 | return parseInt(p, 10);
23 | } else {
24 | // assume it's a note name
25 | return Util.midiPitchFromNote(p);
26 | }
27 | },
28 |
29 | /* TODO:
30 | noteFromMidiPitch: function(n) {
31 | },
32 | */
33 |
34 | mpqnFromBpm: function(bpm) {
35 | var mpqn = Math.floor(60000000 / bpm);
36 | var ret=[];
37 | do {
38 | ret.unshift(mpqn & 0xFF);
39 | mpqn >>= 8;
40 | } while (mpqn);
41 | while (ret.length < 3) {
42 | ret.push(0);
43 | }
44 | return ret;
45 | },
46 |
47 | bpmFromMpqn: function(mpqn) {
48 | var m = mpqn;
49 | if (typeof mpqn[0] != 'undefined') {
50 | m = 0;
51 | for (var i=0, l=mpqn.length-1; l >= 0; ++i, --l) {
52 | m |= mpqn[i] << l;
53 | }
54 | }
55 | return Math.floor(60000000 / mpqn);
56 | },
57 |
58 | /*
59 | * Converts an array of bytes to a string of hexadecimal characters. Prepares
60 | * it to be converted into a base64 string.
61 | *
62 | * @param byteArray {Array} array of bytes that will be converted to a string
63 | * @returns hexadecimal string
64 | */
65 | codes2Str: function(byteArray) {
66 | return String.fromCharCode.apply(null, byteArray);
67 | },
68 |
69 | /*
70 | * Converts a String of hexadecimal values to an array of bytes. It can also
71 | * add remaining "0" nibbles in order to have enough bytes in the array as the
72 | * |finalBytes| parameter.
73 | *
74 | * @param str {String} string of hexadecimal values e.g. "097B8A"
75 | * @param finalBytes {Integer} Optional. The desired number of bytes that the returned array should contain
76 | * @returns array of nibbles.
77 | */
78 |
79 | str2Bytes: function (str, finalBytes) {
80 | if (finalBytes) {
81 | while ((str.length / 2) < finalBytes) { str = "0" + str; }
82 | }
83 |
84 | var bytes = [];
85 | for (var i=str.length-1; i>=0; i = i-2) {
86 | var chars = i === 0 ? str[i] : str[i-1] + str[i];
87 | bytes.unshift(parseInt(chars, 16));
88 | }
89 |
90 | return bytes;
91 | },
92 |
93 | /**
94 | * Translates number of ticks to MIDI timestamp format, returning an array of
95 | * bytes with the time values. Midi has a very particular time to express time,
96 | * take a good look at the spec before ever touching this function.
97 | *
98 | * @param ticks {Integer} Number of ticks to be translated
99 | * @returns Array of bytes that form the MIDI time value
100 | */
101 | translateTickTime: function(ticks) {
102 | var buffer = ticks & 0x7F;
103 |
104 | while (ticks = ticks >> 7) {
105 | buffer <<= 8;
106 | buffer |= ((ticks & 0x7F) | 0x80);
107 | }
108 |
109 | var bList = [];
110 | while (true) {
111 | bList.push(buffer & 0xff);
112 |
113 | if (buffer & 0x80) { buffer >>= 8; }
114 | else { break; }
115 | }
116 | return bList;
117 | },
118 |
119 | };
120 |
121 | /* ******************************************************************
122 | * Event class
123 | ****************************************************************** */
124 |
125 | var MidiEvent = function(params) {
126 | if (!this) return new MidiEvent(params);
127 | if (params &&
128 | (params.type !== null || params.type !== undefined) &&
129 | (params.channel !== null || params.channel !== undefined) &&
130 | (params.param1 !== null || params.param1 !== undefined)) {
131 | this.setTime(params.time);
132 | this.setType(params.type);
133 | this.setChannel(params.channel);
134 | this.setParam1(params.param1);
135 | this.setParam2(params.param2);
136 | }
137 | };
138 |
139 | // event codes
140 | MidiEvent.NOTE_OFF = 0x80;
141 | MidiEvent.NOTE_ON = 0x90;
142 | MidiEvent.AFTER_TOUCH = 0xA0;
143 | MidiEvent.CONTROLLER = 0xB0;
144 | MidiEvent.PROGRAM_CHANGE = 0xC0;
145 | MidiEvent.CHANNEL_AFTERTOUCH = 0xD0;
146 | MidiEvent.PITCH_BEND = 0xE0;
147 |
148 |
149 | MidiEvent.prototype.setTime = function(ticks) {
150 | this.time = Util.translateTickTime(ticks || 0);
151 | };
152 |
153 | MidiEvent.prototype.setType = function(type) {
154 | if (type < MidiEvent.NOTE_OFF || type > MidiEvent.PITCH_BEND) {
155 | throw new Error("Trying to set an unknown event: " + type);
156 | }
157 |
158 | this.type = type;
159 | };
160 |
161 | MidiEvent.prototype.setChannel = function(channel) {
162 | if (channel < 0 || channel > 15) {
163 | throw new Error("Channel is out of bounds.");
164 | }
165 |
166 | this.channel = channel;
167 | };
168 |
169 | MidiEvent.prototype.setParam1 = function(p) {
170 | this.param1 = p;
171 | };
172 |
173 | MidiEvent.prototype.setParam2 = function(p) {
174 | this.param2 = p;
175 | };
176 |
177 | MidiEvent.prototype.toBytes = function() {
178 | var byteArray = [];
179 |
180 | var typeChannelByte = this.type | (this.channel & 0xF);
181 |
182 | byteArray.push.apply(byteArray, this.time);
183 | byteArray.push(typeChannelByte);
184 | byteArray.push(this.param1);
185 |
186 | // Some events don't have a second parameter
187 | if (this.param2 !== undefined && this.param2 !== null) {
188 | byteArray.push(this.param2);
189 | }
190 | return byteArray;
191 | };
192 |
193 | /* ******************************************************************
194 | * MetaEvent class
195 | ****************************************************************** */
196 |
197 | var MetaEvent = function(params) {
198 | if (!this) return new MetaEvent(params);
199 | var p = params || {};
200 | this.setTime(params.time);
201 | this.setType(params.type);
202 | this.setData(params.data);
203 | };
204 |
205 | MetaEvent.SEQUENCE = 0x00;
206 | MetaEvent.TEXT = 0x01;
207 | MetaEvent.COPYRIGHT = 0x02;
208 | MetaEvent.TRACK_NAME = 0x03;
209 | MetaEvent.INSTRUMENT = 0x04;
210 | MetaEvent.LYRIC = 0x05;
211 | MetaEvent.MARKER = 0x06;
212 | MetaEvent.CUE_POINT = 0x07;
213 | MetaEvent.CHANNEL_PREFIX = 0x20;
214 | MetaEvent.END_OF_TRACK = 0x2f;
215 | MetaEvent.TEMPO = 0x51;
216 | MetaEvent.SMPTE = 0x54;
217 | MetaEvent.TIME_SIG = 0x58;
218 | MetaEvent.KEY_SIG = 0x59;
219 | MetaEvent.SEQ_EVENT = 0x7f;
220 |
221 | MetaEvent.prototype.setTime = function(ticks) {
222 | this.time = Util.translateTickTime(ticks || 0);
223 | };
224 |
225 | MetaEvent.prototype.setType = function(t) {
226 | this.type = t;
227 | };
228 |
229 | MetaEvent.prototype.setData = function(d) {
230 | this.data = d;
231 | };
232 |
233 | MetaEvent.prototype.toBytes = function() {
234 | if (!this.type || !this.data || !this.time) {
235 | throw new Error("Type or data for meta-event not specified.");
236 | }
237 |
238 | var byteArray = [];
239 | byteArray.push.apply(byteArray, this.time);
240 | byteArray.push(0xFF, this.type);
241 |
242 | // If data is an array, we assume that it contains several bytes. We
243 | // apend them to byteArray.
244 | if (Array.isArray(this.data)) {
245 | byteArray.push(this.data.length);
246 | byteArray.push.apply(byteArray, this.data);
247 | } else if (this.data !== null && this.data !== undefined) {
248 | byteArray.push(1, this.data);
249 | }
250 |
251 | return byteArray;
252 | };
253 |
254 | /* ******************************************************************
255 | * Track class
256 | ****************************************************************** */
257 |
258 | var Track = function(config) {
259 | if (!this) return new Track(config);
260 | var c = config || {};
261 | this.events = c.events || [];
262 | };
263 |
264 | Track.START_BYTES = [0x4d, 0x54, 0x72, 0x6b];
265 | Track.END_BYTES = [0x00, 0xFF, 0x2F, 0x00];
266 |
267 | Track.prototype.addEvent = function(event) {
268 | this.events.push(event);
269 | };
270 |
271 | Track.prototype.addNoteOn = Track.prototype.noteOn = function(channel, pitch, time, velocity) {
272 | this.events.push(new MidiEvent({
273 | type: MidiEvent.NOTE_ON,
274 | channel: channel,
275 | param1: Util.ensureMidiPitch(pitch),
276 | param2: velocity || DEFAULT_VOLUME,
277 | time: time || 0,
278 | }));
279 | return this;
280 | };
281 |
282 | Track.prototype.addNoteOff = Track.prototype.noteOff = function(channel, pitch, time, velocity) {
283 | this.events.push(new MidiEvent({
284 | type: MidiEvent.NOTE_OFF,
285 | channel: channel,
286 | param1: Util.ensureMidiPitch(pitch),
287 | param2: velocity || DEFAULT_VOLUME,
288 | time: time || 0,
289 | }));
290 | return this;
291 | };
292 |
293 | Track.prototype.addNote = Track.prototype.note = function(channel, pitch, dur, time) {
294 | this.noteOn(channel, pitch, time, DEFAULT_VOLUME);
295 | if (dur) {
296 | this.noteOff(channel, pitch, dur, DEFAULT_VOLUME);
297 | }
298 | return this;
299 | };
300 |
301 | Track.prototype.setInstrument = Track.prototype.instrument = function(channel, instrument, time) {
302 | this.events.push(new MidiEvent({
303 | type: MidiEvent.PROGRAM_CHANGE,
304 | channel: channel,
305 | param1: instrument,
306 | time: time || 0,
307 | }));
308 | return this;
309 | };
310 |
311 | Track.prototype.setTempo = Track.prototype.tempo = function(bpm, time) {
312 | this.events.push(new MetaEvent({
313 | type: MetaEvent.TEMPO,
314 | data: Util.mpqnFromBpm(bpm),
315 | time: time || 0,
316 | }));
317 | return this;
318 | };
319 |
320 | Track.prototype.toBytes = function() {
321 | var trackLength = 0;
322 | var eventBytes = [];
323 | var startBytes = Track.START_BYTES;
324 | var endBytes = Track.END_BYTES;
325 |
326 | var addEventBytes = function(event) {
327 | var bytes = event.toBytes();
328 | trackLength += bytes.length;
329 | eventBytes.push.apply(eventBytes, bytes);
330 | };
331 |
332 | this.events.forEach(addEventBytes);
333 |
334 | // Add the end-of-track bytes to the sum of bytes for the track, since
335 | // they are counted (unlike the start-of-track ones).
336 | trackLength += endBytes.length;
337 |
338 | // Makes sure that track length will fill up 4 bytes with 0s in case
339 | // the length is less than that (the usual case).
340 | var lengthBytes = Util.str2Bytes(trackLength.toString(16), 4);
341 |
342 | return startBytes.concat(lengthBytes, eventBytes, endBytes);
343 | };
344 |
345 | /* ******************************************************************
346 | * File class
347 | ****************************************************************** */
348 |
349 | var File = function(config){
350 | if (!this) return new File(config);
351 | var c = config || {};
352 | this.tracks = c.tracks || [];
353 | };
354 |
355 | File.HDR_CHUNKID = "MThd"; // File magic cookie
356 | File.HDR_CHUNK_SIZE = "\x00\x00\x00\x06"; // Header length for SMF
357 | File.HDR_TYPE0 = "\x00\x00"; // Midi Type 0 id
358 | File.HDR_TYPE1 = "\x00\x01"; // Midi Type 1 id
359 | File.HDR_SPEED = "\x00\x80"; // Defaults to 128 ticks per beat
360 |
361 | File.prototype.addTrack = function(track) {
362 | if (track) {
363 | this.tracks.push(track);
364 | return this;
365 | } else {
366 | track = new Track();
367 | this.tracks.push(track);
368 | return track;
369 | }
370 | };
371 |
372 | File.prototype.toBytes = function() {
373 | var trackCount = this.tracks.length.toString(16);
374 |
375 | // prepare the file header
376 | var bytes = File.HDR_CHUNKID + File.HDR_CHUNK_SIZE + File.HDR_TYPE0;
377 |
378 | // add the number of tracks (2 bytes)
379 | bytes += Util.codes2Str(Util.str2Bytes(trackCount, 2));
380 | // add the number of ticks per beat (currently hardcoded)
381 | bytes += File.HDR_SPEED;
382 |
383 | // iterate over the tracks, converting to bytes too
384 | this.tracks.forEach(function(track) {
385 | bytes += Util.codes2Str(track.toBytes());
386 | });
387 |
388 | return bytes;
389 | };
390 |
391 | var Midi = {
392 | Util:Util,
393 | File:File,
394 | Track:Track,
395 | Event:MidiEvent,
396 | MetaEvent:MetaEvent
397 | };
398 |
--------------------------------------------------------------------------------
/lib/midi-converter.js:
--------------------------------------------------------------------------------
1 | var midiConverter = {
2 | midiToJson: function(midi) {
3 | return midiParser(midi);
4 | },
5 | jsonToMidi: function(songJson) {
6 | var file = new Midi.File();
7 |
8 | songJson.tracks.forEach(function(t) {
9 | var track = new Midi.Track();
10 | file.addTrack(track);
11 |
12 | t.forEach(function(note) {
13 | if(note.subtype === 'programChange') {
14 | var instrument = instruments[note.programNumber].hexcode;
15 | track.setInstrument(note.channel, instrument);
16 | }
17 | else if(note.subtype === 'setTempo') {
18 | var microsecondsPerBeat = note.microsecondsPerBeat;
19 | var microsecondsPerMin = 60000000;
20 | var ticksPerBeat = songJson.header.ticksPerBeat;
21 | var bpm = (ticksPerBeat/128)*microsecondsPerMin/microsecondsPerBeat;
22 | track.setTempo(bpm, note.deltaTime);
23 | }
24 | else if(note.subtype === 'noteOn') {
25 | var noteStr = noteFromMidiPitch(note.noteNumber);
26 | track.addNoteOn(note.channel, noteStr, note.deltaTime, note.velocity);
27 | }
28 | else if(note.subtype === 'noteOff') {
29 | var noteStr = noteFromMidiPitch(note.noteNumber);
30 | track.addNoteOff(note.channel, noteStr, note.deltaTime);
31 | }
32 | });
33 | });
34 |
35 | return file.toBytes();
36 |
37 | function noteFromMidiPitch(p) {
38 | var noteDict = ['c', 'c#', 'd', 'd#', 'e', 'f', 'f#', 'g', 'g#', 'a', 'a#', 'b'];
39 | var octave = Math.floor((p-12)/12);
40 | var note = noteDict[p-octave*12-12];
41 | return note+octave;
42 | }
43 | }
44 | };
45 |
--------------------------------------------------------------------------------
/lib/midi-file-parser.js:
--------------------------------------------------------------------------------
1 | var midiParser = function(file){
2 | return MidiFile(file)
3 | };
4 |
5 | function MidiFile(data) {
6 | function readChunk(stream) {
7 | var id = stream.read(4);
8 | var length = stream.readInt32();
9 | return {
10 | 'id': id,
11 | 'length': length,
12 | 'data': stream.read(length)
13 | };
14 | }
15 |
16 | var lastEventTypeByte;
17 |
18 | function readEvent(stream) {
19 | var event = {};
20 | event.deltaTime = stream.readVarInt();
21 | var eventTypeByte = stream.readInt8();
22 | if ((eventTypeByte & 0xf0) == 0xf0) {
23 | /* system / meta event */
24 | if (eventTypeByte == 0xff) {
25 | /* meta event */
26 | event.type = 'meta';
27 | var subtypeByte = stream.readInt8();
28 | var length = stream.readVarInt();
29 | switch(subtypeByte) {
30 | case 0x00:
31 | event.subtype = 'sequenceNumber';
32 | if (length != 2) throw "Expected length for sequenceNumber event is 2, got " + length;
33 | event.number = stream.readInt16();
34 | return event;
35 | case 0x01:
36 | event.subtype = 'text';
37 | event.text = stream.read(length);
38 | return event;
39 | case 0x02:
40 | event.subtype = 'copyrightNotice';
41 | event.text = stream.read(length);
42 | return event;
43 | case 0x03:
44 | event.subtype = 'trackName';
45 | event.text = stream.read(length);
46 | return event;
47 | case 0x04:
48 | event.subtype = 'instrumentName';
49 | event.text = stream.read(length);
50 | return event;
51 | case 0x05:
52 | event.subtype = 'lyrics';
53 | event.text = stream.read(length);
54 | return event;
55 | case 0x06:
56 | event.subtype = 'marker';
57 | event.text = stream.read(length);
58 | return event;
59 | case 0x07:
60 | event.subtype = 'cuePoint';
61 | event.text = stream.read(length);
62 | return event;
63 | case 0x20:
64 | event.subtype = 'midiChannelPrefix';
65 | if (length != 1) throw "Expected length for midiChannelPrefix event is 1, got " + length;
66 | event.channel = stream.readInt8();
67 | return event;
68 | case 0x2f:
69 | event.subtype = 'endOfTrack';
70 | if (length != 0) throw "Expected length for endOfTrack event is 0, got " + length;
71 | return event;
72 | case 0x51:
73 | event.subtype = 'setTempo';
74 | if (length != 3) throw "Expected length for setTempo event is 3, got " + length;
75 | event.microsecondsPerBeat = (
76 | (stream.readInt8() << 16)
77 | + (stream.readInt8() << 8)
78 | + stream.readInt8()
79 | )
80 | return event;
81 | case 0x54:
82 | event.subtype = 'smpteOffset';
83 | if (length != 5) throw "Expected length for smpteOffset event is 5, got " + length;
84 | var hourByte = stream.readInt8();
85 | event.frameRate = {
86 | 0x00: 24, 0x20: 25, 0x40: 29, 0x60: 30
87 | }[hourByte & 0x60];
88 | event.hour = hourByte & 0x1f;
89 | event.min = stream.readInt8();
90 | event.sec = stream.readInt8();
91 | event.frame = stream.readInt8();
92 | event.subframe = stream.readInt8();
93 | return event;
94 | case 0x58:
95 | event.subtype = 'timeSignature';
96 | if (length != 4) throw "Expected length for timeSignature event is 4, got " + length;
97 | event.numerator = stream.readInt8();
98 | event.denominator = Math.pow(2, stream.readInt8());
99 | event.metronome = stream.readInt8();
100 | event.thirtyseconds = stream.readInt8();
101 | return event;
102 | case 0x59:
103 | event.subtype = 'keySignature';
104 | if (length != 2) throw "Expected length for keySignature event is 2, got " + length;
105 | event.key = stream.readInt8(true);
106 | event.scale = stream.readInt8();
107 | return event;
108 | case 0x7f:
109 | event.subtype = 'sequencerSpecific';
110 | event.data = stream.read(length);
111 | return event;
112 | default:
113 | // console.log("Unrecognised meta event subtype: " + subtypeByte);
114 | event.subtype = 'unknown'
115 | event.data = stream.read(length);
116 | return event;
117 | }
118 | event.data = stream.read(length);
119 | return event;
120 | } else if (eventTypeByte == 0xf0) {
121 | event.type = 'sysEx';
122 | var length = stream.readVarInt();
123 | event.data = stream.read(length);
124 | return event;
125 | } else if (eventTypeByte == 0xf7) {
126 | event.type = 'dividedSysEx';
127 | var length = stream.readVarInt();
128 | event.data = stream.read(length);
129 | return event;
130 | } else {
131 | throw "Unrecognised MIDI event type byte: " + eventTypeByte;
132 | }
133 | } else {
134 | /* channel event */
135 | var param1;
136 | if ((eventTypeByte & 0x80) == 0) {
137 | /* running status - reuse lastEventTypeByte as the event type.
138 | eventTypeByte is actually the first parameter
139 | */
140 | param1 = eventTypeByte;
141 | eventTypeByte = lastEventTypeByte;
142 | } else {
143 | param1 = stream.readInt8();
144 | lastEventTypeByte = eventTypeByte;
145 | }
146 | var eventType = eventTypeByte >> 4;
147 | event.channel = eventTypeByte & 0x0f;
148 | event.type = 'channel';
149 | switch (eventType) {
150 | case 0x08:
151 | event.subtype = 'noteOff';
152 | event.noteNumber = param1;
153 | event.velocity = stream.readInt8();
154 | return event;
155 | case 0x09:
156 | event.noteNumber = param1;
157 | event.velocity = stream.readInt8();
158 | if (event.velocity == 0) {
159 | event.subtype = 'noteOff';
160 | } else {
161 | event.subtype = 'noteOn';
162 | }
163 | return event;
164 | case 0x0a:
165 | event.subtype = 'noteAftertouch';
166 | event.noteNumber = param1;
167 | event.amount = stream.readInt8();
168 | return event;
169 | case 0x0b:
170 | event.subtype = 'controller';
171 | event.controllerType = param1;
172 | event.value = stream.readInt8();
173 | return event;
174 | case 0x0c:
175 | event.subtype = 'programChange';
176 | event.programNumber = param1;
177 | return event;
178 | case 0x0d:
179 | event.subtype = 'channelAftertouch';
180 | event.amount = param1;
181 | return event;
182 | case 0x0e:
183 | event.subtype = 'pitchBend';
184 | event.value = param1 + (stream.readInt8() << 7);
185 | return event;
186 | default:
187 | throw "Unrecognised MIDI event type: " + eventType
188 | /*
189 | console.log("Unrecognised MIDI event type: " + eventType);
190 | stream.readInt8();
191 | event.subtype = 'unknown';
192 | return event;
193 | */
194 | }
195 | }
196 | }
197 |
198 | stream = Stream(data);
199 | var headerChunk = readChunk(stream);
200 | if (headerChunk.id != 'MThd' || headerChunk.length != 6) {
201 | throw "Bad .mid file - header not found";
202 | }
203 | var headerStream = Stream(headerChunk.data);
204 | var formatType = headerStream.readInt16();
205 | var trackCount = headerStream.readInt16();
206 | var timeDivision = headerStream.readInt16();
207 |
208 | if (timeDivision & 0x8000) {
209 | throw "Expressing time division in SMTPE frames is not supported yet"
210 | } else {
211 | ticksPerBeat = timeDivision;
212 | }
213 |
214 | var header = {
215 | 'formatType': formatType,
216 | 'trackCount': trackCount,
217 | 'ticksPerBeat': ticksPerBeat
218 | }
219 | var tracks = [];
220 | for (var i = 0; i < header.trackCount; i++) {
221 | tracks[i] = [];
222 | var trackChunk = readChunk(stream);
223 | if (trackChunk.id != 'MTrk') {
224 | throw "Unexpected chunk - expected MTrk, got "+ trackChunk.id;
225 | }
226 | var trackStream = Stream(trackChunk.data);
227 | while (!trackStream.eof()) {
228 | var event = readEvent(trackStream);
229 | tracks[i].push(event);
230 | //console.log(event);
231 | }
232 | }
233 |
234 | return {
235 | 'header': header,
236 | 'tracks': tracks
237 | }
238 | };
239 |
240 | /* Wrapper for accessing strings through sequential reads */
241 | function Stream(str) {
242 | var position = 0;
243 |
244 | function read(length) {
245 | var result = str.substr(position, length);
246 | position += length;
247 | return result;
248 | }
249 |
250 | /* read a big-endian 32-bit integer */
251 | function readInt32() {
252 | var result = (
253 | (str.charCodeAt(position) << 24)
254 | + (str.charCodeAt(position + 1) << 16)
255 | + (str.charCodeAt(position + 2) << 8)
256 | + str.charCodeAt(position + 3));
257 | position += 4;
258 | return result;
259 | }
260 |
261 | /* read a big-endian 16-bit integer */
262 | function readInt16() {
263 | var result = (
264 | (str.charCodeAt(position) << 8)
265 | + str.charCodeAt(position + 1));
266 | position += 2;
267 | return result;
268 | }
269 |
270 | /* read an 8-bit integer */
271 | function readInt8(signed) {
272 | var result = str.charCodeAt(position);
273 | if (signed && result > 127) result -= 256;
274 | position += 1;
275 | return result;
276 | }
277 |
278 | function eof() {
279 | return position >= str.length;
280 | }
281 |
282 | /* read a MIDI-style variable-length integer
283 | (big-endian value in groups of 7 bits,
284 | with top bit set to signify that another byte follows)
285 | */
286 | function readVarInt() {
287 | var result = 0;
288 | while (true) {
289 | var b = readInt8();
290 | if (b & 0x80) {
291 | result += (b & 0x7f);
292 | result <<= 7;
293 | } else {
294 | /* b is the last byte */
295 | return result + b;
296 | }
297 | }
298 | }
299 |
300 | return {
301 | 'eof': eof,
302 | 'read': read,
303 | 'readInt32': readInt32,
304 | 'readInt16': readInt16,
305 | 'readInt8': readInt8,
306 | 'readVarInt': readVarInt
307 | }
308 | }
309 |
--------------------------------------------------------------------------------
/scripts/midifile.js:
--------------------------------------------------------------------------------
1 | var midiUpload = new Dropzone('#midi-upload', {url:'/', autoProcessQueue:false});
2 | var song = {};
3 | Blob = (function() {
4 | var nativeBlob = Blob;
5 |
6 | // Add unprefixed slice() method.
7 | if (Blob.prototype.webkitSlice) {
8 | Blob.prototype.slice = Blob.prototype.webkitSlice;
9 | }
10 | else if (Blob.prototype.mozSlice) {
11 | Blob.prototype.slice = Blob.prototype.mozSlice;
12 | }
13 |
14 | // Temporarily replace Blob() constructor with one that checks support.
15 | return function(parts, properties) {
16 | try {
17 | // Restore native Blob() constructor, so this check is only evaluated once.
18 | Blob = nativeBlob;
19 | return new Blob(parts || [], properties || {});
20 | }
21 | catch (e) {
22 | // If construction fails provide one that uses BlobBuilder.
23 | Blob = function (parts, properties) {
24 | var bb = new (WebKitBlobBuilder || MozBlobBuilder), i;
25 | for (i in parts) {
26 | bb.append(parts[i]);
27 | }
28 | return bb.getBlob(properties && properties.type ? properties.type : undefined);
29 | };
30 | }
31 | };
32 | }());
33 |
34 | midiUpload.on('addedfile', function(file) {
35 | uploadMidiFile(file);
36 | });
37 |
38 | function uploadMidiFile(file) {
39 | if (file) {
40 | var reader = new FileReader();
41 | reader.onload = function(e) {
42 | var buffer = e.target.result;
43 | song = midiConverter.midiToJson(buffer);
44 | drawSong(song);
45 | updateDownloadLink();
46 | };
47 | reader.readAsBinaryString(file);
48 | }
49 | }
50 |
51 | function updateDownloadLink() {
52 | var midi = midiConverter.jsonToMidi(song);
53 | var blob = new Blob([stringToArrayBuffer(midi)], {type:'audio/midi'});
54 | if(window.webkitURL) window.URL = window.webkitURL;
55 | url = window.URL.createObjectURL(blob);
56 | $('#midi-download').attr('href', url);
57 | }
58 |
59 | function stringToArrayBuffer(string) {
60 | return stringToUint8Array(string).buffer;
61 | }
62 | function stringToBinary(string) {
63 | var chars, code, i, isUCS2, len, _i;
64 |
65 | len = string.length;
66 | chars = [];
67 | isUCS2 = false;
68 | for (i = _i = 0; 0 <= len ? _i < len : _i > len; i = 0 <= len ? ++_i : --_i) {
69 | code = String.prototype.charCodeAt.call(string, i);
70 | if (code > 255) {
71 | isUCS2 = true;
72 | chars = null;
73 | break;
74 | } else {
75 | chars.push(code);
76 | }
77 | }
78 | if (isUCS2 === true) {
79 | return unescape(encodeURIComponent(string));
80 | } else {
81 | return String.fromCharCode.apply(null, Array.prototype.slice.apply(chars));
82 | }
83 | }
84 | function stringToUint8Array(string) {
85 | var binary, binLen, buffer, chars, i, _i;
86 | binary = stringToBinary(string);
87 | binLen = binary.length;
88 | buffer = new ArrayBuffer(binLen);
89 | chars = new Uint8Array(buffer);
90 | for (i = _i = 0; 0 <= binLen ? _i < binLen : _i > binLen; i = 0 <= binLen ? ++_i : --_i) {
91 | chars[i] = String.prototype.charCodeAt.call(binary, i);
92 | }
93 | return chars;
94 | }
95 |
--------------------------------------------------------------------------------
/scripts/sequencer.js:
--------------------------------------------------------------------------------
1 | var keys = $('.keys');
2 | var allNotes = [];
3 | var allEvents = [];
4 | var pxPerMillis = 0;
5 | var millisPerTick = 0;
6 | var endTime = 0;
7 |
8 | for(var pitch=108; pitch>=21; pitch--) { // A0 to C8
9 | var note = noteFromMidiPitch(pitch);
10 | var keyClass = note.indexOf('#')>-1 ? 'key sharp':'key';
11 | var keyHtml = ''+note+'
';
12 | keys.append(''+keyHtml+'
');
13 | }
14 | keys.scrollTop(keys.offset().top + (keys.height() / 2));
15 |
16 | function noteFromMidiPitch(p) {
17 | var noteDict = ['C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#', 'A', 'A#', 'B'];
18 | var octave = Math.floor((p-12)/12);
19 | var note = noteDict[p-octave*12-12];
20 | return note+octave;
21 | }
22 |
23 | function drawSong() {
24 | allEvents = [];
25 | allNotes = [];
26 | endTime = 0;
27 | var openNotes = [];
28 | song.tracks.forEach(function(track) {
29 | var time = 0;
30 | track.forEach(function(event) {
31 | time += parseInt(event.deltaTime*millisPerTick);
32 | if(event.subtype === 'noteOn') {
33 | var pitch = event.noteNumber;
34 | openNotes[pitch] = time;
35 | }
36 | else if(event.subtype === 'noteOff') {
37 | var pitch = event.noteNumber;
38 | var channel = event.channel;
39 | var startTime = openNotes[pitch];
40 | openNotes[pitch] = null;
41 | var newNote = {
42 | pitch: pitch,
43 | startTime: startTime,
44 | deltaTime: time-startTime,
45 | channel: channel
46 | };
47 | allNotes.push(newNote);
48 | }
49 | else if(event.subtype === 'setTempo') {
50 | // FIXME account for multiple changes in tempo throughout song
51 | millisPerTick = event.microsecondsPerBeat/(1000*song.header.ticksPerBeat);
52 | }
53 | allEvents.push({
54 | subtype:event.subtype,
55 | pitch:event.noteNumber,
56 | channel:event.channel,
57 | velocity:event.velocity,
58 | time:time
59 | });
60 | });
61 | if(endTime';
71 | $('.keys li[pitch="'+note.pitch+'"] .notes').append(noteHtml);
72 | });
73 | allEvents = allEvents.sort(function(a, b) {
74 | if(a.time === b.time) {
75 | if(a.subtype === 'noteOff') {
76 | return -1;
77 | }
78 | else {
79 | return 1;
80 | }
81 | }
82 | return a.time-b.time;
83 | });
84 | }
85 |
86 | function playSong() {
87 | var time = 0;
88 | var currentTransform = 0;
89 | MIDI.loadPlugin({
90 | soundfontUrl: "./soundfont/",
91 | instrument: 0,
92 | callback: function() {
93 | $('.notes').css('transform', 'translate(-10000px)');
94 | $('.notes').css('transition', 'linear all ' + endTime + 'ms');
95 | nextEvent(0, 0, Date.now());
96 | }
97 | });
98 |
99 | function nextEvent(i, prevTime, startTime) {
100 | var event = allEvents[i];
101 | var time = event.time;
102 | var dt = Date.now()-startTime;
103 | if(dt > time) {
104 | time -= dt-time; // adjust for time delay
105 | }
106 | setTimeout(function() {
107 | if(event.subtype === 'noteOn') {
108 | $('.keys li[pitch="'+event.pitch+'"] .key').addClass('active');
109 | MIDI.noteOn(event.channel, event.pitch, event.velocity, 0);
110 | }
111 | else if(event.subtype === 'noteOff') {
112 | $('.keys li[pitch="'+event.pitch+'"] .key').removeClass('active');
113 | MIDI.noteOff(event.channel, event.pitch, 0);
114 | }
115 | if(i