summaryrefslogtreecommitdiffstats
path: root/Processing-js/libs/js
diff options
context:
space:
mode:
Diffstat (limited to 'Processing-js/libs/js')
-rw-r--r--Processing-js/libs/js/MIDI/AudioDetect.js69
-rw-r--r--Processing-js/libs/js/MIDI/LoadPlugin.js179
-rw-r--r--Processing-js/libs/js/MIDI/Player.js277
-rw-r--r--Processing-js/libs/js/MIDI/Plugin.js623
-rw-r--r--Processing-js/libs/js/Window/DOMLoader.XMLHttp.js130
-rw-r--r--Processing-js/libs/js/Window/Event.js1901
-rw-r--r--Processing-js/libs/js/Window/Queue.js87
7 files changed, 3266 insertions, 0 deletions
diff --git a/Processing-js/libs/js/MIDI/AudioDetect.js b/Processing-js/libs/js/MIDI/AudioDetect.js
new file mode 100644
index 0000000..a5dd465
--- /dev/null
+++ b/Processing-js/libs/js/MIDI/AudioDetect.js
@@ -0,0 +1,69 @@
+/*
+ -------------------------------------
+ MIDI.audioDetect : 0.3
+ -------------------------------------
+ https://github.com/mudcube/MIDI.js
+ -------------------------------------
+ Probably, Maybe, No... Absolutely!
+ -------------------------------------
+ Test to see what types of <audio> MIME types are playable by the browser.
+ -------------------------------------
+*/
+
+if (typeof(MIDI) === "undefined") var MIDI = {};
+
+(function() { "use strict";
+
+var supports = {};
+var pending = 0;
+var canPlayThrough = function (src) {
+ pending ++;
+ var audio = new Audio();
+ var mime = src.split(";")[0];
+ audio.id = "audio";
+ audio.setAttribute("preload", "auto");
+ audio.setAttribute("audiobuffer", true);
+ audio.addEventListener("error", function() {
+ supports[mime] = false;
+ pending --;
+ }, false);
+ audio.addEventListener("canplaythrough", function() {
+ supports[mime] = true;
+ pending --;
+ }, false);
+ audio.src = "data:" + src;
+ document.body.appendChild(audio);
+};
+
+MIDI.audioDetect = function(callback) {
+ // check whether <audio> tag is supported
+ if (typeof(Audio) === "undefined") return callback({});
+ // check whether canPlayType is supported
+ var audio = new Audio();
+ if (typeof(audio.canPlayType) === "undefined") return callback(supports);
+ // see what we can learn from the browser
+ var vorbis = audio.canPlayType('audio/ogg; codecs="vorbis"');
+ vorbis = (vorbis === "probably" || vorbis === "maybe");
+ var mpeg = audio.canPlayType('audio/mpeg');
+ mpeg = (mpeg === "probably" || mpeg === "maybe");
+ // maybe nothing is supported
+ if (!vorbis && !mpeg) {
+ callback(supports);
+ return;
+ }
+ // or maybe something is supported
+ if (vorbis) canPlayThrough("audio/ogg;base64,T2dnUwACAAAAAAAAAADqnjMlAAAAAOyyzPIBHgF2b3JiaXMAAAAAAUAfAABAHwAAQB8AAEAfAACZAU9nZ1MAAAAAAAAAAAAA6p4zJQEAAAANJGeqCj3//////////5ADdm9yYmlzLQAAAFhpcGguT3JnIGxpYlZvcmJpcyBJIDIwMTAxMTAxIChTY2hhdWZlbnVnZ2V0KQAAAAABBXZvcmJpcw9CQ1YBAAABAAxSFCElGVNKYwiVUlIpBR1jUFtHHWPUOUYhZBBTiEkZpXtPKpVYSsgRUlgpRR1TTFNJlVKWKUUdYxRTSCFT1jFloXMUS4ZJCSVsTa50FkvomWOWMUYdY85aSp1j1jFFHWNSUkmhcxg6ZiVkFDpGxehifDA6laJCKL7H3lLpLYWKW4q91xpT6y2EGEtpwQhhc+211dxKasUYY4wxxsXiUyiC0JBVAAABAABABAFCQ1YBAAoAAMJQDEVRgNCQVQBABgCAABRFcRTHcRxHkiTLAkJDVgEAQAAAAgAAKI7hKJIjSZJkWZZlWZameZaouaov+64u667t6roOhIasBACAAAAYRqF1TCqDEEPKQ4QUY9AzoxBDDEzGHGNONKQMMogzxZAyiFssLqgQBKEhKwKAKAAAwBjEGGIMOeekZFIi55iUTkoDnaPUUcoolRRLjBmlEluJMYLOUeooZZRCjKXFjFKJscRUAABAgAMAQICFUGjIigAgCgCAMAYphZRCjCnmFHOIMeUcgwwxxiBkzinoGJNOSuWck85JiRhjzjEHlXNOSuekctBJyaQTAAAQ4AAAEGAhFBqyIgCIEwAwSJKmWZomipamiaJniqrqiaKqWp5nmp5pqqpnmqpqqqrrmqrqypbnmaZnmqrqmaaqiqbquqaquq6nqrZsuqoum65q267s+rZru77uqapsm6or66bqyrrqyrbuurbtS56nqqKquq5nqq6ruq5uq65r25pqyq6purJtuq4tu7Js664s67pmqq5suqotm64s667s2rYqy7ovuq5uq7Ks+6os+75s67ru2rrwi65r66os674qy74x27bwy7ouHJMnqqqnqq7rmarrqq5r26rr2rqmmq5suq4tm6or26os67Yry7aumaosm64r26bryrIqy77vyrJui67r66Ys67oqy8Lu6roxzLat+6Lr6roqy7qvyrKuu7ru+7JuC7umqrpuyrKvm7Ks+7auC8us27oxuq7vq7It/KosC7+u+8Iy6z5jdF1fV21ZGFbZ9n3d95Vj1nVhWW1b+V1bZ7y+bgy7bvzKrQvLstq2scy6rSyvrxvDLux8W/iVmqratum6um7Ksq/Lui60dd1XRtf1fdW2fV+VZd+3hV9pG8OwjK6r+6os68Jry8ov67qw7MIvLKttK7+r68ow27qw3L6wLL/uC8uq277v6rrStXVluX2fsSu38QsAABhwAAAIMKEMFBqyIgCIEwBAEHIOKQahYgpCCKGkEEIqFWNSMuakZM5JKaWUFEpJrWJMSuaclMwxKaGUlkopqYRSWiqlxBRKaS2l1mJKqcVQSmulpNZKSa2llGJMrcUYMSYlc05K5pyUklJrJZXWMucoZQ5K6iCklEoqraTUYuacpA46Kx2E1EoqMZWUYgupxFZKaq2kFGMrMdXUWo4hpRhLSrGVlFptMdXWWqs1YkxK5pyUzDkqJaXWSiqtZc5J6iC01DkoqaTUYiopxco5SR2ElDLIqJSUWiupxBJSia20FGMpqcXUYq4pxRZDSS2WlFosqcTWYoy1tVRTJ6XFklKMJZUYW6y5ttZqDKXEVkqLsaSUW2sx1xZjjqGkFksrsZWUWmy15dhayzW1VGNKrdYWY40x5ZRrrT2n1mJNMdXaWqy51ZZbzLXnTkprpZQWS0oxttZijTHmHEppraQUWykpxtZara3FXEMpsZXSWiypxNhirLXFVmNqrcYWW62ltVprrb3GVlsurdXcYqw9tZRrrLXmWFNtBQAADDgAAASYUAYKDVkJAEQBAADGMMYYhEYpx5yT0ijlnHNSKucghJBS5hyEEFLKnINQSkuZcxBKSSmUklJqrYVSUmqttQIAAAocAAACbNCUWByg0JCVAEAqAIDBcTRNFFXVdX1fsSxRVFXXlW3jVyxNFFVVdm1b+DVRVFXXtW3bFn5NFFVVdmXZtoWiqrqybduybgvDqKqua9uybeuorqvbuq3bui9UXVmWbVu3dR3XtnXd9nVd+Bmzbeu2buu+8CMMR9/4IeTj+3RCCAAAT3AAACqwYXWEk6KxwEJDVgIAGQAAgDFKGYUYM0gxphhjTDHGmAAAgAEHAIAAE8pAoSErAoAoAADAOeecc84555xzzjnnnHPOOeecc44xxhhjjDHGGGOMMcYYY4wxxhhjjDHGGGOMMcYYY0wAwE6EA8BOhIVQaMhKACAcAABACCEpKaWUUkoRU85BSSmllFKqFIOMSkoppZRSpBR1lFJKKaWUIqWgpJJSSimllElJKaWUUkoppYw6SimllFJKKaWUUkoppZRSSimllFJKKaWUUkoppZRSSimllFJKKaWUUkoppZRSSimllFJKKaWUUkoppZRSSimllFJKKaVUSimllFJKKaWUUkoppRQAYPLgAACVYOMMK0lnhaPBhYasBAByAwAAhRiDEEJpraRUUkolVc5BKCWUlEpKKZWUUqqYgxBKKqmlklJKKbXSQSihlFBKKSWUUkooJYQQSgmhlFRCK6mEUkoHoYQSQimhhFRKKSWUzkEoIYUOQkmllNRCSB10VFIpIZVSSiklpZQ6CKGUklJLLZVSWkqpdBJSKamV1FJqqbWSUgmhpFZKSSWl0lpJJbUSSkklpZRSSymFVFJJJYSSUioltZZaSqm11lJIqZWUUkqppdRSSiWlkEpKqZSSUmollZRSaiGVlEpJKaTUSimlpFRCSamlUlpKLbWUSkmptFRSSaWUlEpJKaVSSksppRJKSqmllFpJKYWSUkoplZJSSyW1VEoKJaWUUkmptJRSSymVklIBAEAHDgAAAUZUWoidZlx5BI4oZJiAAgAAQABAgAkgMEBQMApBgDACAQAAAADAAAAfAABHARAR0ZzBAUKCwgJDg8MDAAAAAAAAAAAAAACAT2dnUwAEAAAAAAAAAADqnjMlAgAAADzQPmcBAQA=");
+ if (mpeg) canPlayThrough("audio/mpeg;base64,/+MYxAAAAANIAUAAAASEEB/jwOFM/0MM/90b/+RhST//w4NFwOjf///PZu////9lns5GFDv//l9GlUIEEIAAAgIg8Ir/JGq3/+MYxDsLIj5QMYcoAP0dv9HIjUcH//yYSg+CIbkGP//8w0bLVjUP///3Z0x5QCAv/yLjwtGKTEFNRTMuOTeqqqqqqqqqqqqq/+MYxEkNmdJkUYc4AKqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq");
+ // lets find out!
+ var time = (new Date()).getTime();
+ var interval = window.setInterval(function() {
+ var now = (new Date()).getTime();
+ var maxExecution = now - time > 5000;
+ if (!pending || maxExecution) {
+ window.clearInterval(interval);
+ callback(supports);
+ }
+ }, 1);
+};
+
+})(); \ No newline at end of file
diff --git a/Processing-js/libs/js/MIDI/LoadPlugin.js b/Processing-js/libs/js/MIDI/LoadPlugin.js
new file mode 100644
index 0000000..dad5cbd
--- /dev/null
+++ b/Processing-js/libs/js/MIDI/LoadPlugin.js
@@ -0,0 +1,179 @@
+/*
+ -----------------------------------------------------------
+ MIDI.loadPlugin : 0.1.2 : 01/22/2014
+ -----------------------------------------------------------
+ https://github.com/mudcube/MIDI.js
+ -----------------------------------------------------------
+ MIDI.loadPlugin({
+ targetFormat: "mp3", // optionally can force to use MP3 (for instance on mobile networks)
+ instrument: "acoustic_grand_piano", // or 1 (default)
+ instruments: [ "acoustic_grand_piano", "acoustic_guitar_nylon" ], // or multiple instruments
+ callback: function() { }
+ });
+*/
+
+if (typeof (MIDI) === "undefined") var MIDI = {};
+if (typeof (MIDI.Soundfont) === "undefined") MIDI.Soundfont = {};
+
+(function() { "use strict";
+
+var USE_JAZZMIDI = false; // Turn on to support JazzMIDI Plugin
+
+MIDI.loadPlugin = function(conf) {
+ if (typeof(conf) === "function") conf = {
+ callback: conf
+ };
+ /// Get the instrument name.
+ var instruments = conf.instruments || conf.instrument || "acoustic_grand_piano";
+ if (typeof(instruments) !== "object") instruments = [ instruments ];
+ ///
+ for (var n = 0; n < instruments.length; n ++) {
+ var instrument = instruments[n];
+ if (typeof(instrument) === "number") {
+ instruments[n] = MIDI.GeneralMIDI.byId[instrument];
+ }
+ };
+ ///
+ MIDI.soundfontUrl = conf.soundfontUrl || MIDI.soundfontUrl || "./soundfont/";
+ /// Detect the best type of audio to use.
+ MIDI.audioDetect(function(types) {
+ var api = "";
+ // use the most appropriate plugin if not specified
+ if (apis[conf.api]) {
+ api = conf.api;
+ } else if (apis[window.location.hash.substr(1)]) {
+ api = window.location.hash.substr(1);
+ } else if (USE_JAZZMIDI && navigator.requestMIDIAccess) {
+ api = "webmidi";
+ } else if (window.webkitAudioContext || window.AudioContext) { // Chrome
+ api = "webaudio";
+ } else if (window.Audio) { // Firefox
+ api = "audiotag";
+ } else { // Internet Explorer
+ api = "flash";
+ }
+ ///
+ if (!connect[api]) return;
+ // use audio/ogg when supported
+ if (conf.targetFormat) {
+ var filetype = conf.targetFormat;
+ } else { // use best quality
+ var filetype = types["audio/ogg"] ? "ogg" : "mp3";
+ }
+ // load the specified plugin
+ MIDI.lang = api;
+ MIDI.supports = types;
+ connect[api](filetype, instruments, conf);
+ });
+};
+
+///
+
+var connect = {};
+
+connect.webmidi = function(filetype, instruments, conf) {
+ if (MIDI.loader) MIDI.loader.message("Web MIDI API...");
+ MIDI.WebMIDI.connect(conf);
+};
+
+connect.flash = function(filetype, instruments, conf) {
+ // fairly quick, but requires loading of individual MP3s (more http requests).
+ if (MIDI.loader) MIDI.loader.message("Flash API...");
+ DOMLoader.script.add({
+ src: conf.soundManagerUrl || "./inc/SoundManager2/script/soundmanager2.js",
+ verify: "SoundManager",
+ callback: function () {
+ MIDI.Flash.connect(instruments, conf);
+ }
+ });
+};
+
+connect.audiotag = function(filetype, instruments, conf) {
+ if (MIDI.loader) MIDI.loader.message("HTML5 Audio API...");
+ // works ok, kinda like a drunken tuna fish, across the board.
+ var queue = createQueue({
+ items: instruments,
+ getNext: function(instrumentId) {
+ DOMLoader.sendRequest({
+ url: MIDI.soundfontUrl + instrumentId + "-" + filetype + ".js",
+ onprogress: getPercent,
+ onload: function (response) {
+ addSoundfont(response.responseText);
+ if (MIDI.loader) MIDI.loader.update(null, "Downloading", 100);
+ queue.getNext();
+ }
+ });
+ },
+ onComplete: function() {
+ MIDI.AudioTag.connect(conf);
+ }
+ });
+};
+
+connect.webaudio = function(filetype, instruments, conf) {
+ if (MIDI.loader) MIDI.loader.message("Web Audio API...");
+ // works awesome! safari, chrome and firefox support.
+ var queue = createQueue({
+ items: instruments,
+ getNext: function(instrumentId) {
+ DOMLoader.sendRequest({
+ url: MIDI.soundfontUrl + instrumentId + "-" + filetype + ".js",
+ onprogress: getPercent,
+ onload: function(response) {
+ addSoundfont(response.responseText);
+ if (MIDI.loader) MIDI.loader.update(null, "Downloading...", 100);
+ queue.getNext();
+ }
+ });
+ },
+ onComplete: function() {
+ MIDI.WebAudio.connect(conf);
+ }
+ });
+};
+
+/// Helpers
+
+var apis = {
+ "webmidi": true,
+ "webaudio": true,
+ "audiotag": true,
+ "flash": true
+};
+
+var addSoundfont = function(text) {
+ var script = document.createElement("script");
+ script.language = "javascript";
+ script.type = "text/javascript";
+ script.text = text;
+ document.body.appendChild(script);
+};
+
+var getPercent = function(event) {
+ if (!this.totalSize) {
+ if (this.getResponseHeader("Content-Length-Raw")) {
+ this.totalSize = parseInt(this.getResponseHeader("Content-Length-Raw"));
+ } else {
+ this.totalSize = event.total;
+ }
+ }
+ ///
+ var percent = this.totalSize ? Math.round(event.loaded / this.totalSize * 100) : "";
+ if (MIDI.loader) MIDI.loader.update(null, "Downloading...", percent);
+};
+
+var createQueue = function(conf) {
+ var self = {};
+ self.queue = [];
+ for (var key in conf.items) {
+ self.queue.push(conf.items[key]);
+ }
+ self.getNext = function() {
+ if (!self.queue.length) return conf.onComplete();
+ conf.getNext(self.queue.shift());
+ };
+ setTimeout(self.getNext, 1);
+ return self;
+};
+
+})(); \ No newline at end of file
diff --git a/Processing-js/libs/js/MIDI/Player.js b/Processing-js/libs/js/MIDI/Player.js
new file mode 100644
index 0000000..bfa4e3b
--- /dev/null
+++ b/Processing-js/libs/js/MIDI/Player.js
@@ -0,0 +1,277 @@
+/*
+ -------------------------------------
+ MIDI.Player : 0.3
+ -------------------------------------
+ https://github.com/mudcube/MIDI.js
+ -------------------------------------
+ #jasmid
+ -------------------------------------
+*/
+
+if (typeof (MIDI) === "undefined") var MIDI = {};
+if (typeof (MIDI.Player) === "undefined") MIDI.Player = {};
+
+(function() { "use strict";
+
+var root = MIDI.Player;
+root.callback = undefined; // your custom callback goes here!
+root.currentTime = 0;
+root.endTime = 0;
+root.restart = 0;
+root.playing = false;
+root.timeWarp = 1;
+
+//
+root.start =
+root.resume = function () {
+ if (root.currentTime < -1) root.currentTime = -1;
+ startAudio(root.currentTime);
+};
+
+root.pause = function () {
+ var tmp = root.restart;
+ stopAudio();
+ root.restart = tmp;
+};
+
+root.stop = function () {
+ stopAudio();
+ root.restart = 0;
+ root.currentTime = 0;
+};
+
+root.addListener = function(callback) {
+ onMidiEvent = callback;
+};
+
+root.removeListener = function() {
+ onMidiEvent = undefined;
+};
+
+root.clearAnimation = function() {
+ if (root.interval) {
+ window.clearInterval(root.interval);
+ }
+};
+
+root.setAnimation = function(config) {
+ var callback = (typeof(config) === "function") ? config : config.callback;
+ var interval = config.interval || 30;
+ var currentTime = 0;
+ var tOurTime = 0;
+ var tTheirTime = 0;
+ //
+ root.clearAnimation();
+ root.interval = window.setInterval(function () {
+ if (root.endTime === 0) return;
+ if (root.playing) {
+ currentTime = (tTheirTime === root.currentTime) ? tOurTime - (new Date).getTime() : 0;
+ if (root.currentTime === 0) {
+ currentTime = 0;
+ } else {
+ currentTime = root.currentTime - currentTime;
+ }
+ if (tTheirTime !== root.currentTime) {
+ tOurTime = (new Date).getTime();
+ tTheirTime = root.currentTime;
+ }
+ } else { // paused
+ currentTime = root.currentTime;
+ }
+ var endTime = root.endTime;
+ var percent = currentTime / endTime;
+ var total = currentTime / 1000;
+ var minutes = total / 60;
+ var seconds = total - (minutes * 60);
+ var t1 = minutes * 60 + seconds;
+ var t2 = (endTime / 1000);
+ if (t2 - t1 < -1) return;
+ callback({
+ now: t1,
+ end: t2,
+ events: noteRegistrar
+ });
+ }, interval);
+};
+
+// helpers
+
+root.loadMidiFile = function() { // reads midi into javascript array of events
+ root.replayer = new Replayer(MidiFile(root.currentData), root.timeWarp);
+ root.data = root.replayer.getData();
+ root.endTime = getLength();
+};
+
+root.loadFile = function (file, callback) {
+ root.stop();
+ if (file.indexOf("base64,") !== -1) {
+ var data = window.atob(file.split(",")[1]);
+ root.currentData = data;
+ root.loadMidiFile();
+ if (callback) callback(data);
+ return;
+ }
+ ///
+ var fetch = new XMLHttpRequest();
+ fetch.open('GET', file);
+ fetch.overrideMimeType("text/plain; charset=x-user-defined");
+ fetch.onreadystatechange = function () {
+ if (this.readyState === 4 && this.status === 200) {
+ var t = this.responseText || "";
+ var ff = [];
+ var mx = t.length;
+ var scc = String.fromCharCode;
+ for (var z = 0; z < mx; z++) {
+ ff[z] = scc(t.charCodeAt(z) & 255);
+ }
+ var data = ff.join("");
+ root.currentData = data;
+ root.loadMidiFile();
+ if (callback) callback(data);
+ }
+ };
+ fetch.send();
+};
+
+// Playing the audio
+
+var eventQueue = []; // hold events to be triggered
+var queuedTime; //
+var startTime = 0; // to measure time elapse
+var noteRegistrar = {}; // get event for requested note
+var onMidiEvent = undefined; // listener callback
+var scheduleTracking = function (channel, note, currentTime, offset, message, velocity) {
+ var interval = window.setTimeout(function () {
+ var data = {
+ channel: channel,
+ note: note,
+ now: currentTime,
+ end: root.endTime,
+ message: message,
+ velocity: velocity
+ };
+ //
+ if (message === 128) {
+ delete noteRegistrar[note];
+ } else {
+ noteRegistrar[note] = data;
+ }
+ if (onMidiEvent) {
+ onMidiEvent(data);
+ }
+ root.currentTime = currentTime;
+ if (root.currentTime === queuedTime && queuedTime < root.endTime) { // grab next sequence
+ startAudio(queuedTime, true);
+ }
+ }, currentTime - offset);
+ return interval;
+};
+
+var getContext = function() {
+ if (MIDI.lang === 'WebAudioAPI') {
+ return MIDI.Player.ctx;
+ } else if (!root.ctx) {
+ root.ctx = { currentTime: 0 };
+ }
+ return root.ctx;
+};
+
+var getLength = function() {
+ var data = root.data;
+ var length = data.length;
+ var totalTime = 0.5;
+ for (var n = 0; n < length; n++) {
+ totalTime += data[n][1];
+ }
+ return totalTime;
+};
+
+var startAudio = function (currentTime, fromCache) {
+ if (!root.replayer) return;
+ if (!fromCache) {
+ if (typeof (currentTime) === "undefined") currentTime = root.restart;
+ if (root.playing) stopAudio();
+ root.playing = true;
+ root.data = root.replayer.getData();
+ root.endTime = getLength();
+ }
+ var note;
+ var offset = 0;
+ var messages = 0;
+ var data = root.data;
+ var ctx = getContext();
+ var length = data.length;
+ //
+ queuedTime = 0.5;
+ startTime = ctx.currentTime;
+ //
+ for (var n = 0; n < length && messages < 100; n++) {
+ queuedTime += data[n][1];
+ if (queuedTime < currentTime) {
+ offset = queuedTime;
+ continue;
+ }
+ currentTime = queuedTime - offset;
+ var event = data[n][0].event;
+ if (event.type !== "channel") continue;
+ var channel = event.channel;
+ switch (event.subtype) {
+ case 'noteOn':
+ if (MIDI.channels[channel].mute) break;
+ note = event.noteNumber - (root.MIDIOffset || 0);
+ eventQueue.push({
+ event: event,
+ source: MIDI.noteOn(channel, event.noteNumber, event.velocity, currentTime / 1000 + ctx.currentTime),
+ interval: scheduleTracking(channel, note, queuedTime, offset, 144, event.velocity)
+ });
+ messages ++;
+ break;
+ case 'noteOff':
+ if (MIDI.channels[channel].mute) break;
+ note = event.noteNumber - (root.MIDIOffset || 0);
+ eventQueue.push({
+ event: event,
+ source: MIDI.noteOff(channel, event.noteNumber, currentTime / 1000 + ctx.currentTime),
+ interval: scheduleTracking(channel, note, queuedTime, offset, 128)
+ });
+ break;
+ default:
+ break;
+ }
+ }
+};
+
+var stopAudio = function () {
+ var ctx = getContext();
+ root.playing = false;
+ root.restart += (ctx.currentTime - startTime) * 1000;
+ // stop the audio, and intervals
+ while (eventQueue.length) {
+ var o = eventQueue.pop();
+ window.clearInterval(o.interval);
+ if (!o.source) continue; // is not webaudio
+ if (typeof(o.source) === "number") {
+ window.clearTimeout(o.source);
+ } else { // webaudio
+ o.source.disconnect(0);
+ }
+ }
+ // run callback to cancel any notes still playing
+ for (var key in noteRegistrar) {
+ var o = noteRegistrar[key]
+ if (noteRegistrar[key].message === 144 && onMidiEvent) {
+ onMidiEvent({
+ channel: o.channel,
+ note: o.note,
+ now: o.now,
+ end: o.end,
+ message: 128,
+ velocity: o.velocity
+ });
+ }
+ }
+ // reset noteRegistrar
+ noteRegistrar = {};
+};
+
+})(); \ No newline at end of file
diff --git a/Processing-js/libs/js/MIDI/Plugin.js b/Processing-js/libs/js/MIDI/Plugin.js
new file mode 100644
index 0000000..7647f96
--- /dev/null
+++ b/Processing-js/libs/js/MIDI/Plugin.js
@@ -0,0 +1,623 @@
+/*
+ --------------------------------------------
+ MIDI.Plugin : 0.3.2 : 2013/01/26
+ --------------------------------------------
+ https://github.com/mudcube/MIDI.js
+ --------------------------------------------
+ Inspired by javax.sound.midi (albeit a super simple version):
+ http://docs.oracle.com/javase/6/docs/api/javax/sound/midi/package-summary.html
+ --------------------------------------------
+ Technologies:
+ MIDI.WebMIDI
+ MIDI.WebAudio
+ MIDI.Flash
+ MIDI.AudioTag
+ --------------------------------------------
+ Helpers:
+ MIDI.GeneralMIDI
+ MIDI.channels
+ MIDI.keyToNote
+ MIDI.noteToKey
+*/
+
+if (typeof (MIDI) === "undefined") var MIDI = {};
+
+(function() { "use strict";
+
+var setPlugin = function(root) {
+ MIDI.api = root.api;
+ MIDI.setVolume = root.setVolume;
+ MIDI.programChange = root.programChange;
+ MIDI.noteOn = root.noteOn;
+ MIDI.noteOff = root.noteOff;
+ MIDI.chordOn = root.chordOn;
+ MIDI.chordOff = root.chordOff;
+ MIDI.stopAllNotes = root.stopAllNotes;
+ MIDI.getInput = root.getInput;
+ MIDI.getOutputs = root.getOutputs;
+};
+
+/*
+ --------------------------------------------
+ Web MIDI API - Native Soundbank
+ --------------------------------------------
+ https://dvcs.w3.org/hg/audio/raw-file/tip/midi/specification.html
+ --------------------------------------------
+*/
+
+(function () {
+ var plugin = null;
+ var output = null;
+ var channels = [];
+ var root = MIDI.WebMIDI = {
+ api: "webmidi"
+ };
+ root.setVolume = function (channel, volume) { // set channel volume
+ output.send([0xB0 + channel, 0x07, volume]);
+ };
+
+ root.programChange = function (channel, program) { // change channel instrument
+ output.send([0xC0 + channel, program]);
+ };
+
+ root.noteOn = function (channel, note, velocity, delay) {
+ output.send([0x90 + channel, note, velocity], delay * 1000);
+ };
+
+ root.noteOff = function (channel, note, delay) {
+ output.send([0x80 + channel, note, 0], delay * 1000);
+ };
+
+ root.chordOn = function (channel, chord, velocity, delay) {
+ for (var n = 0; n < chord.length; n ++) {
+ var note = chord[n];
+ output.send([0x90 + channel, note, velocity], delay * 1000);
+ }
+ };
+
+ root.chordOff = function (channel, chord, delay) {
+ for (var n = 0; n < chord.length; n ++) {
+ var note = chord[n];
+ output.send([0x80 + channel, note, 0], delay * 1000);
+ }
+ };
+
+ root.stopAllNotes = function () {
+ for (var channel = 0; channel < 16; channel ++) {
+ output.send([0xB0 + channel, 0x7B, 0]);
+ }
+ };
+
+ root.getInput = function () {
+ return plugin.getInputs();
+ };
+
+ root.getOutputs = function () {
+ return plugin.getOutputs();
+ };
+
+ root.connect = function (conf) {
+ setPlugin(root);
+ navigator.requestMIDIAccess().then(function (access) {
+ plugin = access;
+ output = plugin.outputs()[0];
+ if (conf.callback) conf.callback();
+ }, function (err) { // well at least we tried!
+ if (window.AudioContext || window.webkitAudioContext) { // Chrome
+ conf.api = "webaudio";
+ } else if (window.Audio) { // Firefox
+ conf.api = "audiotag";
+ } else { // Internet Explorer
+ conf.api = "flash";
+ }
+ MIDI.loadPlugin(conf);
+ });
+ };
+})();
+
+/*
+ --------------------------------------------
+ Web Audio API - OGG or MPEG Soundbank
+ --------------------------------------------
+ https://dvcs.w3.org/hg/audio/raw-file/tip/webaudio/specification.html
+ --------------------------------------------
+*/
+
+if (window.AudioContext || window.webkitAudioContext) (function () {
+
+ var AudioContext = window.AudioContext || window.webkitAudioContext;
+ var root = MIDI.WebAudio = {
+ api: "webaudio"
+ };
+ var ctx;
+ var sources = {};
+ var masterVolume = 127;
+ var audioBuffers = {};
+ var audioLoader = function (instrument, urlList, index, bufferList, callback) {
+ var synth = MIDI.GeneralMIDI.byName[instrument];
+ var instrumentId = synth.number;
+ var url = urlList[index];
+ if (!MIDI.Soundfont[instrument][url]) { // missing soundfont
+ return callback(instrument);
+ }
+ var base64 = MIDI.Soundfont[instrument][url].split(",")[1];
+ var buffer = Base64Binary.decodeArrayBuffer(base64);
+ ctx.decodeAudioData(buffer, function (buffer) {
+ var msg = url;
+ while (msg.length < 3) msg += "&nbsp;";
+ if (typeof (MIDI.loader) !== "undefined") {
+ MIDI.loader.update(null, synth.instrument + "<br>Processing: " + (index / 87 * 100 >> 0) + "%<br>" + msg);
+ }
+ buffer.id = url;
+ bufferList[index] = buffer;
+ //
+ if (bufferList.length === urlList.length) {
+ while (bufferList.length) {
+ buffer = bufferList.pop();
+ if (!buffer) continue;
+ var nodeId = MIDI.keyToNote[buffer.id];
+ audioBuffers[instrumentId + "" + nodeId] = buffer;
+ }
+ callback(instrument);
+ }
+ });
+ };
+
+ root.setVolume = function (channel, volume) {
+ masterVolume = volume;
+ };
+
+ root.programChange = function (channel, program) {
+ MIDI.channels[channel].instrument = program;
+ };
+
+ root.noteOn = function (channel, note, velocity, delay) {
+ /// check whether the note exists
+ if (!MIDI.channels[channel]) return;
+ var instrument = MIDI.channels[channel].instrument;
+ if (!audioBuffers[instrument + "" + note]) return;
+ /// convert relative delay to absolute delay
+ if (delay < ctx.currentTime) delay += ctx.currentTime;
+ /// crate audio buffer
+ var source = ctx.createBufferSource();
+ sources[channel + "" + note] = source;
+ source.buffer = audioBuffers[instrument + "" + note];
+ source.connect(ctx.destination);
+ ///
+ if (ctx.createGain) { // firefox
+ source.gainNode = ctx.createGain();
+ } else { // chrome
+ source.gainNode = ctx.createGainNode();
+ }
+ var value = (velocity / 127) * (masterVolume / 127) * 2 - 1;
+ source.gainNode.connect(ctx.destination);
+ source.gainNode.gain.value = Math.max(-1, value);
+ source.connect(source.gainNode);
+ if (source.noteOn) { // old api
+ source.noteOn(delay || 0);
+ } else { // new api
+ source.start(delay || 0);
+ }
+ return source;
+ };
+
+ root.noteOff = function (channel, note, delay) {
+ delay = delay || 0;
+ if (delay < ctx.currentTime) delay += ctx.currentTime;
+ var source = sources[channel + "" + note];
+ if (!source) return;
+ if (source.gainNode) {
+ // @Miranet: "the values of 0.2 and 0.3 could ofcourse be used as
+ // a 'release' parameter for ADSR like time settings."
+ // add { "metadata": { release: 0.3 } } to soundfont files
+ var gain = source.gainNode.gain;
+ gain.linearRampToValueAtTime(gain.value, delay);
+ gain.linearRampToValueAtTime(-1, delay + 0.2);
+ }
+ if (source.noteOff) { // old api
+ source.noteOff(delay + 0.3);
+ } else {
+ source.stop(delay + 0.3);
+ }
+ ///
+ delete sources[channel + "" + note];
+ };
+
+ root.chordOn = function (channel, chord, velocity, delay) {
+ var ret = {}, note;
+ for (var n = 0, length = chord.length; n < length; n++) {
+ ret[note = chord[n]] = root.noteOn(channel, note, velocity, delay);
+ }
+ return ret;
+ };
+
+ root.chordOff = function (channel, chord, delay) {
+ var ret = {}, note;
+ for (var n = 0, length = chord.length; n < length; n++) {
+ ret[note = chord[n]] = root.noteOff(channel, note, delay);
+ }
+ return ret;
+ };
+
+ root.stopAllNotes = function () {
+ for (var source in sources) {
+ var delay = 0;
+ if (delay < ctx.currentTime) delay += ctx.currentTime;
+ // @Miranet: "the values of 0.2 and 0.3 could ofcourse be used as
+ // a 'release' parameter for ADSR like time settings."
+ // add { "metadata": { release: 0.3 } } to soundfont files
+ sources[source].gain.linearRampToValueAtTime(1, delay);
+ sources[source].gain.linearRampToValueAtTime(0, delay + 0.2);
+ sources[source].noteOff(delay + 0.3);
+ delete sources[source];
+ }
+ };
+
+ root.connect = function (conf) {
+ setPlugin(root);
+ //
+ MIDI.Player.ctx = ctx = new AudioContext();
+ ///
+ var urlList = [];
+ var keyToNote = MIDI.keyToNote;
+ for (var key in keyToNote) urlList.push(key);
+ var bufferList = [];
+ var pending = {};
+ var oncomplete = function(instrument) {
+ delete pending[instrument];
+ for (var key in pending) break;
+ if (!key) conf.callback();
+ };
+ for (var instrument in MIDI.Soundfont) {
+ pending[instrument] = true;
+ for (var i = 0; i < urlList.length; i++) {
+ audioLoader(instrument, urlList, i, bufferList, oncomplete);
+ }
+ }
+ };
+})();
+
+/*
+ --------------------------------------------
+ AudioTag <audio> - OGG or MPEG Soundbank
+ --------------------------------------------
+ http://dev.w3.org/html5/spec/Overview.html#the-audio-element
+ --------------------------------------------
+*/
+
+if (window.Audio) (function () {
+
+ var root = MIDI.AudioTag = {
+ api: "audiotag"
+ };
+ var note2id = {};
+ var volume = 127; // floating point
+ var channel_nid = -1; // current channel
+ var channels = []; // the audio channels
+ var channelInstrumentNoteIds = []; // instrumentId + noteId that is currently playing in each 'channel', for routing noteOff/chordOff calls
+ var notes = {}; // the piano keys
+ for (var nid = 0; nid < 12; nid++) {
+ channels[nid] = new Audio();
+ }
+
+ var playChannel = function (channel, note) {
+ if (!MIDI.channels[channel]) return;
+ var instrument = MIDI.channels[channel].instrument;
+ var instrumentId = MIDI.GeneralMIDI.byId[instrument].id;
+ var note = notes[note];
+ if (!note) return;
+ var instrumentNoteId = instrumentId + "" + note.id;
+ var nid = (channel_nid + 1) % channels.length;
+ var audio = channels[nid];
+ channelInstrumentNoteIds[ nid ] = instrumentNoteId;
+ audio.src = MIDI.Soundfont[instrumentId][note.id];
+ audio.volume = volume / 127;
+ audio.play();
+ channel_nid = nid;
+ };
+
+ var stopChannel = function (channel, note) {
+ if (!MIDI.channels[channel]) return;
+ var instrument = MIDI.channels[channel].instrument;
+ var instrumentId = MIDI.GeneralMIDI.byId[instrument].id;
+ var note = notes[note];
+ if (!note) return;
+ var instrumentNoteId = instrumentId + "" + note.id;
+
+ for(var i=0;i<channels.length;i++){
+ var nid = (i + channel_nid + 1) % channels.length;
+ var cId = channelInstrumentNoteIds[nid];
+
+ if(cId && cId == instrumentNoteId){
+ channels[nid].pause();
+ channelInstrumentNoteIds[nid] = null;
+ return;
+ }
+ }
+ };
+
+ root.programChange = function (channel, program) {
+ MIDI.channels[channel].instrument = program;
+ };
+
+ root.setVolume = function (channel, n) {
+ volume = n; //- should be channel specific volume
+ };
+
+ root.noteOn = function (channel, note, velocity, delay) {
+ var id = note2id[note];
+ if (!notes[id]) return;
+ if (delay) {
+ return window.setTimeout(function () {
+ playChannel(channel, id);
+ }, delay * 1000);
+ } else {
+ playChannel(channel, id);
+ }
+ };
+
+ root.noteOff = function (channel, note, delay) {
+ var id = note2id[note];
+ if (!notes[id]) return;
+ if (delay) {
+ return setTimeout(function() {
+ stopChannel(channel, id);
+ }, delay * 1000)
+ } else {
+ stopChannel(channel, id);
+ }
+ };
+
+ root.chordOn = function (channel, chord, velocity, delay) {
+ for (var idx = 0; idx < chord.length; idx ++) {
+ var n = chord[idx];
+ var id = note2id[n];
+ if (!notes[id]) continue;
+ if (delay) {
+ return window.setTimeout(function () {
+ playChannel(channel, id);
+ }, delay * 1000);
+ } else {
+ playChannel(channel, id);
+ }
+ }
+ };
+
+ root.chordOff = function (channel, chord, delay) {
+ for (var idx = 0; idx < chord.length; idx ++) {
+ var n = chord[idx];
+ var id = note2id[n];
+ if (!notes[id]) continue;
+ if (delay) {
+ return window.setTimeout(function () {
+ stopChannel(channel, id);
+ }, delay * 1000);
+ } else {
+ stopChannel(channel, id);
+ }
+ }
+ };
+
+ root.stopAllNotes = function () {
+ for (var nid = 0, length = channels.length; nid < length; nid++) {
+ channels[nid].pause();
+ }
+ };
+
+ root.connect = function (conf) {
+ for (var key in MIDI.keyToNote) {
+ note2id[MIDI.keyToNote[key]] = key;
+ notes[key] = {
+ id: key
+ };
+ }
+ setPlugin(root);
+ ///
+ if (conf.callback) conf.callback();
+ };
+})();
+
+/*
+ --------------------------------------------
+ Flash - MP3 Soundbank
+ --------------------------------------------
+ http://www.schillmania.com/projects/soundmanager2/
+ --------------------------------------------
+*/
+
+(function () {
+
+ var root = MIDI.Flash = {
+ api: "flash"
+ };
+ var noteReverse = {};
+ var notes = {};
+
+ root.programChange = function (channel, program) {
+ MIDI.channels[channel].instrument = program;
+ };
+
+ root.setVolume = function (channel, note) {
+
+ };
+
+ root.noteOn = function (channel, note, velocity, delay) {
+ if (!MIDI.channels[channel]) return;
+ var instrument = MIDI.channels[channel].instrument;
+ var id = MIDI.GeneralMIDI.byId[instrument].number;
+ note = id + "" + noteReverse[note];
+ if (!notes[note]) return;
+ if (delay) {
+ return window.setTimeout(function() {
+ notes[note].play({ volume: velocity * 2 });
+ }, delay * 1000);
+ } else {
+ notes[note].play({ volume: velocity * 2 });
+ }
+ };
+
+ root.noteOff = function (channel, note, delay) {
+
+ };
+
+ root.chordOn = function (channel, chord, velocity, delay) {
+ if (!MIDI.channels[channel]) return;
+ var instrument = MIDI.channels[channel].instrument;
+ var id = MIDI.GeneralMIDI.byId[instrument].number;
+ for (var key in chord) {
+ var n = chord[key];
+ var note = id + "" + noteReverse[n];
+ if (notes[note]) {
+ notes[note].play({ volume: velocity * 2 });
+ }
+ }
+ };
+
+ root.chordOff = function (channel, chord, delay) {
+
+ };
+
+ root.stopAllNotes = function () {
+
+ };
+
+ root.connect = function (instruments, conf) {
+ soundManager.flashVersion = 9;
+ soundManager.useHTML5Audio = true;
+ soundManager.url = conf.soundManagerSwfUrl || '../inc/SoundManager2/swf/';
+ soundManager.useHighPerformance = true;
+ soundManager.wmode = 'transparent';
+ soundManager.flashPollingInterval = 1;
+ soundManager.debugMode = false;
+ soundManager.onload = function () {
+ var createBuffer = function(instrument, id, onload) {
+ var synth = MIDI.GeneralMIDI.byName[instrument];
+ var instrumentId = synth.number;
+ notes[instrumentId+""+id] = soundManager.createSound({
+ id: id,
+ url: MIDI.soundfontUrl + instrument + "-mp3/" + id + ".mp3",
+ multiShot: true,
+ autoLoad: true,
+ onload: onload
+ });
+ };
+ var loaded = [];
+ var samplesPerInstrument = 88;
+ var samplesToLoad = instruments.length * samplesPerInstrument;
+
+ for (var i = 0; i < instruments.length; i++) {
+ var instrument = instruments[i];
+ var onload = function () {
+ loaded.push(this.sID);
+ if (typeof (MIDI.loader) === "undefined") return;
+ MIDI.loader.update(null, "Processing: " + this.sID);
+ };
+ for (var j = 0; j < samplesPerInstrument; j++) {
+ var id = noteReverse[j + 21];
+ createBuffer(instrument, id, onload);
+ }
+ }
+ ///
+ setPlugin(root);
+ //
+ var interval = window.setInterval(function () {
+ if (loaded.length < samplesToLoad) return;
+ window.clearInterval(interval);
+ if (conf.callback) conf.callback();
+ }, 25);
+ };
+ soundManager.onerror = function () {
+
+ };
+ for (var key in MIDI.keyToNote) {
+ noteReverse[MIDI.keyToNote[key]] = key;
+ }
+ };
+})();
+
+/*
+ helper functions
+*/
+
+// instrument-tracker
+MIDI.GeneralMIDI = (function (arr) {
+ var clean = function(v) {
+ return v.replace(/[^a-z0-9 ]/gi, "").replace(/[ ]/g, "_").toLowerCase();
+ };
+ var ret = {
+ byName: {},
+ byId: {},
+ byCategory: {}
+ };
+ for (var key in arr) {
+ var list = arr[key];
+ for (var n = 0, length = list.length; n < length; n++) {
+ var instrument = list[n];
+ if (!instrument) continue;
+ var num = parseInt(instrument.substr(0, instrument.indexOf(" ")), 10);
+ instrument = instrument.replace(num + " ", "");
+ ret.byId[--num] =
+ ret.byName[clean(instrument)] =
+ ret.byCategory[clean(key)] = {
+ id: clean(instrument),
+ instrument: instrument,
+ number: num,
+ category: key
+ };
+ }
+ }
+ return ret;
+})({
+ 'Piano': ['1 Acoustic Grand Piano', '2 Bright Acoustic Piano', '3 Electric Grand Piano', '4 Honky-tonk Piano', '5 Electric Piano 1', '6 Electric Piano 2', '7 Harpsichord', '8 Clavinet'],
+ 'Chromatic Percussion': ['9 Celesta', '10 Glockenspiel', '11 Music Box', '12 Vibraphone', '13 Marimba', '14 Xylophone', '15 Tubular Bells', '16 Dulcimer'],
+ 'Organ': ['17 Drawbar Organ', '18 Percussive Organ', '19 Rock Organ', '20 Church Organ', '21 Reed Organ', '22 Accordion', '23 Harmonica', '24 Tango Accordion'],
+ 'Guitar': ['25 Acoustic Guitar (nylon)', '26 Acoustic Guitar (steel)', '27 Electric Guitar (jazz)', '28 Electric Guitar (clean)', '29 Electric Guitar (muted)', '30 Overdriven Guitar', '31 Distortion Guitar', '32 Guitar Harmonics'],
+ 'Bass': ['33 Acoustic Bass', '34 Electric Bass (finger)', '35 Electric Bass (pick)', '36 Fretless Bass', '37 Slap Bass 1', '38 Slap Bass 2', '39 Synth Bass 1', '40 Synth Bass 2'],
+ 'Strings': ['41 Violin', '42 Viola', '43 Cello', '44 Contrabass', '45 Tremolo Strings', '46 Pizzicato Strings', '47 Orchestral Harp', '48 Timpani'],
+ 'Ensemble': ['49 String Ensemble 1', '50 String Ensemble 2', '51 Synth Strings 1', '52 Synth Strings 2', '53 Choir Aahs', '54 Voice Oohs', '55 Synth Choir', '56 Orchestra Hit'],
+ 'Brass': ['57 Trumpet', '58 Trombone', '59 Tuba', '60 Muted Trumpet', '61 French Horn', '62 Brass Section', '63 Synth Brass 1', '64 Synth Brass 2'],
+ 'Reed': ['65 Soprano Sax', '66 Alto Sax', '67 Tenor Sax', '68 Baritone Sax', '69 Oboe', '70 English Horn', '71 Bassoon', '72 Clarinet'],
+ 'Pipe': ['73 Piccolo', '74 Flute', '75 Recorder', '76 Pan Flute', '77 Blown Bottle', '78 Shakuhachi', '79 Whistle', '80 Ocarina'],
+ 'Synth Lead': ['81 Lead 1 (square)', '82 Lead 2 (sawtooth)', '83 Lead 3 (calliope)', '84 Lead 4 (chiff)', '85 Lead 5 (charang)', '86 Lead 6 (voice)', '87 Lead 7 (fifths)', '88 Lead 8 (bass + lead)'],
+ 'Synth Pad': ['89 Pad 1 (new age)', '90 Pad 2 (warm)', '91 Pad 3 (polysynth)', '92 Pad 4 (choir)', '93 Pad 5 (bowed)', '94 Pad 6 (metallic)', '95 Pad 7 (halo)', '96 Pad 8 (sweep)'],
+ 'Synth Effects': ['97 FX 1 (rain)', '98 FX 2 (soundtrack)', '99 FX 3 (crystal)', '100 FX 4 (atmosphere)', '101 FX 5 (brightness)', '102 FX 6 (goblins)', '103 FX 7 (echoes)', '104 FX 8 (sci-fi)'],
+ 'Ethnic': ['105 Sitar', '106 Banjo', '107 Shamisen', '108 Koto', '109 Kalimba', '110 Bagpipe', '111 Fiddle', '112 Shanai'],
+ 'Percussive': ['113 Tinkle Bell', '114 Agogo', '115 Steel Drums', '116 Woodblock', '117 Taiko Drum', '118 Melodic Tom', '119 Synth Drum'],
+ 'Sound effects': ['120 Reverse Cymbal', '121 Guitar Fret Noise', '122 Breath Noise', '123 Seashore', '124 Bird Tweet', '125 Telephone Ring', '126 Helicopter', '127 Applause', '128 Gunshot']
+});
+
+// channel-tracker
+MIDI.channels = (function () { // 0 - 15 channels
+ var channels = {};
+ for (var n = 0; n < 16; n++) {
+ channels[n] = { // default values
+ instrument: 0,
+ // Acoustic Grand Piano
+ mute: false,
+ mono: false,
+ omni: false,
+ solo: false
+ };
+ }
+ return channels;
+})();
+
+//
+MIDI.pianoKeyOffset = 21;
+
+// note conversions
+MIDI.keyToNote = {}; // C8 == 108
+MIDI.noteToKey = {}; // 108 == C8
+(function () {
+ var A0 = 0x15; // first note
+ var C8 = 0x6C; // last note
+ var number2key = ["C", "Db", "D", "Eb", "E", "F", "Gb", "G", "Ab", "A", "Bb", "B"];
+ for (var n = A0; n <= C8; n++) {
+ var octave = (n - 12) / 12 >> 0;
+ var name = number2key[n % 12] + octave;
+ MIDI.keyToNote[name] = n;
+ MIDI.noteToKey[n] = name;
+ }
+})();
+
+})(); \ No newline at end of file
diff --git a/Processing-js/libs/js/Window/DOMLoader.XMLHttp.js b/Processing-js/libs/js/Window/DOMLoader.XMLHttp.js
new file mode 100644
index 0000000..8384051
--- /dev/null
+++ b/Processing-js/libs/js/Window/DOMLoader.XMLHttp.js
@@ -0,0 +1,130 @@
+/*
+
+ DOMLoader.XMLHttp
+ --------------------------
+ DOMLoader.sendRequest({
+ url: "./dir/something.extension",
+ data: "test!",
+ onerror: function(event) {
+ console.log(event);
+ },
+ onload: function(response) {
+ console.log(response.responseText);
+ },
+ onprogress: function (event) {
+ var percent = event.loaded / event.total * 100 >> 0;
+ loader.message("loading: " + percent + "%");
+ }
+ });
+
+*/
+
+if (typeof(DOMLoader) === "undefined") var DOMLoader = {};
+
+// Add XMLHttpRequest when not available
+
+if (typeof (XMLHttpRequest) === "undefined") {
+ var XMLHttpRequest;
+ (function () { // find equivalent for IE
+ var factories = [
+ function () {
+ return new ActiveXObject("Msxml2.XMLHTTP")
+ }, function () {
+ return new ActiveXObject("Msxml3.XMLHTTP")
+ }, function () {
+ return new ActiveXObject("Microsoft.XMLHTTP")
+ }];
+ for (var i = 0; i < factories.length; i++) {
+ try {
+ factories[i]();
+ } catch (e) {
+ continue;
+ }
+ break;
+ }
+ XMLHttpRequest = factories[i];
+ })();
+}
+
+if (typeof ((new XMLHttpRequest()).responseText) === "undefined") {
+ // http://stackoverflow.com/questions/1919972/how-do-i-access-xhr-responsebody-for-binary-data-from-javascript-in-ie
+ var IEBinaryToArray_ByteStr_Script =
+ "<!-- IEBinaryToArray_ByteStr -->\r\n"+
+ "<script type='text/vbscript'>\r\n"+
+ "Function IEBinaryToArray_ByteStr(Binary)\r\n"+
+ " IEBinaryToArray_ByteStr = CStr(Binary)\r\n"+
+ "End Function\r\n"+
+ "Function IEBinaryToArray_ByteStr_Last(Binary)\r\n"+
+ " Dim lastIndex\r\n"+
+ " lastIndex = LenB(Binary)\r\n"+
+ " if lastIndex mod 2 Then\r\n"+
+ " IEBinaryToArray_ByteStr_Last = Chr( AscB( MidB( Binary, lastIndex, 1 ) ) )\r\n"+
+ " Else\r\n"+
+ " IEBinaryToArray_ByteStr_Last = "+'""'+"\r\n"+
+ " End If\r\n"+
+ "End Function\r\n"+
+ "</script>\r\n";
+
+ // inject VBScript
+ document.write(IEBinaryToArray_ByteStr_Script);
+
+ DOMLoader.sendRequest = function(conf) {
+ // helper to convert from responseBody to a "responseText" like thing
+ function getResponseText(binary) {
+ var byteMapping = {};
+ for (var i = 0; i < 256; i++) {
+ for (var j = 0; j < 256; j++) {
+ byteMapping[String.fromCharCode(i + j * 256)] = String.fromCharCode(i) + String.fromCharCode(j);
+ }
+ }
+ // call into VBScript utility fns
+ var rawBytes = IEBinaryToArray_ByteStr(binary);
+ var lastChr = IEBinaryToArray_ByteStr_Last(binary);
+ return rawBytes.replace(/[\s\S]/g, function (match) {
+ return byteMapping[match];
+ }) + lastChr;
+ };
+ //
+ var req = XMLHttpRequest();
+ req.open("GET", conf.url, true);
+ if (conf.responseType) req.responseType = conf.responseType;
+ if (conf.onerror) req.onerror = conf.onerror;
+ if (conf.onprogress) req.onprogress = conf.onprogress;
+ req.onreadystatechange = function (event) {
+ if (req.readyState === 4) {
+ if (req.status === 200) {
+ req.responseText = getResponseText(req.responseBody);
+ } else {
+ req = false;
+ }
+ if (conf.onload) conf.onload(req);
+ }
+ };
+ req.setRequestHeader("Accept-Charset", "x-user-defined");
+ req.send(null);
+ return req;
+ }
+} else {
+ DOMLoader.sendRequest = function(conf) {
+ var req = new XMLHttpRequest();
+ req.open(conf.data ? "POST" : "GET", conf.url, true);
+ if (req.overrideMimeType) req.overrideMimeType("text/plain; charset=x-user-defined");
+ if (conf.data) req.setRequestHeader('Content-type','application/x-www-form-urlencoded');
+ if (conf.responseType) req.responseType = conf.responseType;
+ if (conf.onerror) req.onerror = conf.onerror;
+ if (conf.onprogress) req.onprogress = conf.onprogress;
+ req.onreadystatechange = function (event) {
+ if (req.readyState === 4) {
+ if (req.status !== 200 && req.status != 304) {
+ if (conf.onerror) conf.onerror(event, false);
+ return;
+ }
+ if (conf.onload) {
+ conf.onload(req);
+ }
+ }
+ };
+ req.send(conf.data);
+ return req;
+ };
+} \ No newline at end of file
diff --git a/Processing-js/libs/js/Window/Event.js b/Processing-js/libs/js/Window/Event.js
new file mode 100644
index 0000000..db9215b
--- /dev/null
+++ b/Processing-js/libs/js/Window/Event.js
@@ -0,0 +1,1901 @@
+/*:
+ ----------------------------------------------------
+ event.js : 1.1.5 : 2013/12/12 : MIT License
+ ----------------------------------------------------
+ https://github.com/mudcube/Event.js
+ ----------------------------------------------------
+ 1 : click, dblclick, dbltap
+ 1+ : tap, longpress, drag, swipe
+ 2+ : pinch, rotate
+ : mousewheel, devicemotion, shake
+ ----------------------------------------------------
+ Ideas for the future
+ ----------------------------------------------------
+ * GamePad, and other input abstractions.
+ * Event batching - i.e. for every x fingers down a new gesture is created.
+ ----------------------------------------------------
+ http://www.w3.org/TR/2011/WD-touch-events-20110505/
+ ----------------------------------------------------
+*/
+
+if (typeof(eventjs) === "undefined") var eventjs = {};
+
+(function(root) { "use strict";
+
+// Add custom *EventListener commands to HTMLElements (set false to prevent funkiness).
+root.modifyEventListener = false;
+
+// Add bulk *EventListener commands on NodeLists from querySelectorAll and others (set false to prevent funkiness).
+root.modifySelectors = false;
+
+// Event maintenance.
+root.add = function(target, type, listener, configure) {
+ return eventManager(target, type, listener, configure, "add");
+};
+
+root.remove = function(target, type, listener, configure) {
+ return eventManager(target, type, listener, configure, "remove");
+};
+
+root.returnFalse = function(event) {
+ return false;
+};
+
+root.stop = function(event) {
+ if (!event) return;
+ if (event.stopPropagation) event.stopPropagation();
+ event.cancelBubble = true; // <= IE8
+ event.cancelBubbleCount = 0;
+};
+
+root.prevent = function(event) {
+ if (!event) return;
+ if (event.preventDefault) {
+ event.preventDefault();
+ } else if (event.preventManipulation) {
+ event.preventManipulation(); // MS
+ } else {
+ event.returnValue = false; // <= IE8
+ }
+};
+
+root.cancel = function(event) {
+ root.stop(event);
+ root.prevent(event);
+};
+
+root.blur = function() { // Blurs the focused element. Useful when using eventjs.cancel as canceling will prevent focused elements from being blurred.
+ var node = document.activeElement;
+ if (!node) return;
+ var nodeName = document.activeElement.nodeName;
+ if (nodeName === "INPUT" || nodeName === "TEXTAREA" || node.contentEditable === "true") {
+ if (node.blur) node.blur();
+ }
+};
+
+// Check whether event is natively supported (via @kangax)
+root.getEventSupport = function (target, type) {
+ if (typeof(target) === "string") {
+ type = target;
+ target = window;
+ }
+ type = "on" + type;
+ if (type in target) return true;
+ if (!target.setAttribute) target = document.createElement("div");
+ if (target.setAttribute && target.removeAttribute) {
+ target.setAttribute(type, "");
+ var isSupported = typeof target[type] === "function";
+ if (typeof target[type] !== "undefined") target[type] = null;
+ target.removeAttribute(type);
+ return isSupported;
+ }
+};
+
+var clone = function (obj) {
+ if (!obj || typeof (obj) !== 'object') return obj;
+ var temp = new obj.constructor();
+ for (var key in obj) {
+ if (!obj[key] || typeof (obj[key]) !== 'object') {
+ temp[key] = obj[key];
+ } else { // clone sub-object
+ temp[key] = clone(obj[key]);
+ }
+ }
+ return temp;
+};
+
+/// Handle custom *EventListener commands.
+var eventManager = function(target, type, listener, configure, trigger, fromOverwrite) {
+ configure = configure || {};
+ // Check whether target is a configuration variable;
+ if (String(target) === "[object Object]") {
+ var data = target;
+ target = data.target; delete data.target;
+ ///
+ if (data.type && data.listener) {
+ type = data.type; delete data.type;
+ listener = data.listener; delete data.listener;
+ for (var key in data) {
+ configure[key] = data[key];
+ }
+ } else { // specialness
+ for (var param in data) {
+ var value = data[param];
+ if (typeof(value) === "function") continue;
+ configure[param] = value;
+ }
+ ///
+ var ret = {};
+ for (var key in data) {
+ var param = key.split(",");
+ var o = data[key];
+ var conf = {};
+ for (var k in configure) { // clone base configuration
+ conf[k] = configure[k];
+ }
+ ///
+ if (typeof(o) === "function") { // without configuration
+ var listener = o;
+ } else if (typeof(o.listener) === "function") { // with configuration
+ var listener = o.listener;
+ for (var k in o) { // merge configure into base configuration
+ if (typeof(o[k]) === "function") continue;
+ conf[k] = o[k];
+ }
+ } else { // not a listener
+ continue;
+ }
+ ///
+ for (var n = 0; n < param.length; n ++) {
+ ret[key] = eventjs.add(target, param[n], listener, conf, trigger);
+ }
+ }
+ return ret;
+ }
+ }
+ ///
+ if (!target || !type || !listener) return;
+ // Check for element to load on interval (before onload).
+ if (typeof(target) === "string" && type === "ready") {
+ if (window.eventjs_stallOnReady) { /// force stall for scripts to load
+ type = "load";
+ target = window;
+ } else { //
+ var time = (new Date()).getTime();
+ var timeout = configure.timeout;
+ var ms = configure.interval || 1000 / 60;
+ var interval = window.setInterval(function() {
+ if ((new Date()).getTime() - time > timeout) {
+ window.clearInterval(interval);
+ }
+ if (document.querySelector(target)) {
+ window.clearInterval(interval);
+ setTimeout(listener, 1);
+ }
+ }, ms);
+ return;
+ }
+ }
+ // Get DOM element from Query Selector.
+ if (typeof(target) === "string") {
+ target = document.querySelectorAll(target);
+ if (target.length === 0) return createError("Missing target on listener!", arguments); // No results.
+ if (target.length === 1) { // Single target.
+ target = target[0];
+ }
+ }
+
+ /// Handle multiple targets.
+ var event;
+ var events = {};
+ if (target.length > 0 && target !== window) {
+ for (var n0 = 0, length0 = target.length; n0 < length0; n0 ++) {
+ event = eventManager(target[n0], type, listener, clone(configure), trigger);
+ if (event) events[n0] = event;
+ }
+ return createBatchCommands(events);
+ }
+
+ /// Check for multiple events in one string.
+ if (typeof(type) === "string") {
+ type = type.toLowerCase();
+ if (type.indexOf(" ") !== -1) {
+ type = type.split(" ");
+ } else if (type.indexOf(",") !== -1) {
+ type = type.split(",");
+ }
+ }
+
+ /// Attach or remove multiple events associated with a target.
+ if (typeof(type) !== "string") { // Has multiple events.
+ if (typeof(type.length) === "number") { // Handle multiple listeners glued together.
+ for (var n1 = 0, length1 = type.length; n1 < length1; n1 ++) { // Array [type]
+ event = eventManager(target, type[n1], listener, clone(configure), trigger);
+ if (event) events[type[n1]] = event;
+ }
+ } else { // Handle multiple listeners.
+ for (var key in type) { // Object {type}
+ if (typeof(type[key]) === "function") { // without configuration.
+ event = eventManager(target, key, type[key], clone(configure), trigger);
+ } else { // with configuration.
+ event = eventManager(target, key, type[key].listener, clone(type[key]), trigger);
+ }
+ if (event) events[key] = event;
+ }
+ }
+ return createBatchCommands(events);
+ } else if (type.indexOf("on") === 0) { // to support things like "onclick" instead of "click"
+ type = type.substr(2);
+ }
+
+ // Ensure listener is a function.
+ if (typeof(target) !== "object") return createError("Target is not defined!", arguments);
+ if (typeof(listener) !== "function") return createError("Listener is not a function!", arguments);
+
+ // Generate a unique wrapper identifier.
+ var useCapture = configure.useCapture || false;
+ var id = getID(target) + "." + getID(listener) + "." + (useCapture ? 1 : 0);
+ // Handle the event.
+ if (root.Gesture && root.Gesture._gestureHandlers[type]) { // Fire custom event.
+ id = type + id;
+ if (trigger === "remove") { // Remove event listener.
+ if (!wrappers[id]) return; // Already removed.
+ wrappers[id].remove();
+ delete wrappers[id];
+ } else if (trigger === "add") { // Attach event listener.
+ if (wrappers[id]) {
+ wrappers[id].add();
+ return wrappers[id]; // Already attached.
+ }
+ // Retains "this" orientation.
+ if (configure.useCall && !root.modifyEventListener) {
+ var tmp = listener;
+ listener = function(event, self) {
+ for (var key in self) event[key] = self[key];
+ return tmp.call(target, event);
+ };
+ }
+ // Create listener proxy.
+ configure.gesture = type;
+ configure.target = target;
+ configure.listener = listener;
+ configure.fromOverwrite = fromOverwrite;
+ // Record wrapper.
+ wrappers[id] = root.proxy[type](configure);
+ }
+ return wrappers[id];
+ } else { // Fire native event.
+ var eventList = getEventList(type);
+ for (var n = 0, eventId; n < eventList.length; n ++) {
+ type = eventList[n];
+ eventId = type + "." + id;
+ if (trigger === "remove") { // Remove event listener.
+ if (!wrappers[eventId]) continue; // Already removed.
+ target[remove](type, listener, useCapture);
+ delete wrappers[eventId];
+ } else if (trigger === "add") { // Attach event listener.
+ if (wrappers[eventId]) return wrappers[eventId]; // Already attached.
+ target[add](type, listener, useCapture);
+ // Record wrapper.
+ wrappers[eventId] = {
+ id: eventId,
+ type: type,
+ target: target,
+ listener: listener,
+ remove: function() {
+ for (var n = 0; n < eventList.length; n ++) {
+ root.remove(target, eventList[n], listener, configure);
+ }
+ }
+ };
+ }
+ }
+ return wrappers[eventId];
+ }
+};
+
+/// Perform batch actions on multiple events.
+var createBatchCommands = function(events) {
+ return {
+ remove: function() { // Remove multiple events.
+ for (var key in events) {
+ events[key].remove();
+ }
+ },
+ add: function() { // Add multiple events.
+ for (var key in events) {
+ events[key].add();
+ }
+ }
+ };
+};
+
+/// Display error message in console.
+var createError = function(message, data) {
+ if (typeof(console) === "undefined") return;
+ if (typeof(console.error) === "undefined") return;
+ console.error(message, data);
+};
+
+/// Handle naming discrepancies between platforms.
+var pointerDefs = {
+ "msPointer": [ "MSPointerDown", "MSPointerMove", "MSPointerUp" ],
+ "touch": [ "touchstart", "touchmove", "touchend" ],
+ "mouse": [ "mousedown", "mousemove", "mouseup" ]
+};
+
+var pointerDetect = {
+ // MSPointer
+ "MSPointerDown": 0,
+ "MSPointerMove": 1,
+ "MSPointerUp": 2,
+ // Touch
+ "touchstart": 0,
+ "touchmove": 1,
+ "touchend": 2,
+ // Mouse
+ "mousedown": 0,
+ "mousemove": 1,
+ "mouseup": 2
+};
+
+var getEventSupport = (function() {
+ root.supports = {};
+ if (window.navigator.msPointerEnabled) {
+ root.supports.msPointer = true;
+ }
+ if (root.getEventSupport("touchstart")) {
+ root.supports.touch = true;
+ }
+ if (root.getEventSupport("mousedown")) {
+ root.supports.mouse = true;
+ }
+})();
+
+var getEventList = (function() {
+ return function(type) {
+ var prefix = document.addEventListener ? "" : "on"; // IE
+ var idx = pointerDetect[type];
+ if (isFinite(idx)) {
+ var types = [];
+ for (var key in root.supports) {
+ types.push(prefix + pointerDefs[key][idx]);
+ }
+ return types;
+ } else {
+ return [ prefix + type ];
+ }
+ };
+})();
+
+/// Event wrappers to keep track of all events placed in the window.
+var wrappers = {};
+var counter = 0;
+var getID = function(object) {
+ if (object === window) return "#window";
+ if (object === document) return "#document";
+ if (!object.uniqueID) object.uniqueID = "e" + counter ++;
+ return object.uniqueID;
+};
+
+/// Detect platforms native *EventListener command.
+var add = document.addEventListener ? "addEventListener" : "attachEvent";
+var remove = document.removeEventListener ? "removeEventListener" : "detachEvent";
+
+/*
+ Pointer.js
+ ----------------------------------------
+ Modified from; https://github.com/borismus/pointer.js
+*/
+
+root.createPointerEvent = function (event, self, preventRecord) {
+ var eventName = self.gesture;
+ var target = self.target;
+ var pts = event.changedTouches || root.proxy.getCoords(event);
+ if (pts.length) {
+ var pt = pts[0];
+ self.pointers = preventRecord ? [] : pts;
+ self.pageX = pt.pageX;
+ self.pageY = pt.pageY;
+ self.x = self.pageX;
+ self.y = self.pageY;
+ }
+ ///
+ var newEvent = document.createEvent("Event");
+ newEvent.initEvent(eventName, true, true);
+ newEvent.originalEvent = event;
+ for (var k in self) {
+ if (k === "target") continue;
+ newEvent[k] = self[k];
+ }
+ ///
+ var type = newEvent.type;
+ if (root.Gesture && root.Gesture._gestureHandlers[type]) { // capture custom events.
+// target.dispatchEvent(newEvent);
+ self.oldListener.call(target, newEvent, self, false);
+ }
+};
+
+/// Allows *EventListener to use custom event proxies.
+if (root.modifyEventListener && window.HTMLElement) (function() {
+ var augmentEventListener = function(proto) {
+ var recall = function(trigger) { // overwrite native *EventListener's
+ var handle = trigger + "EventListener";
+ var handler = proto[handle];
+ proto[handle] = function (type, listener, useCapture) {
+ if (root.Gesture && root.Gesture._gestureHandlers[type]) { // capture custom events.
+ var configure = useCapture;
+ if (typeof(useCapture) === "object") {
+ configure.useCall = true;
+ } else { // convert to configuration object.
+ configure = {
+ useCall: true,
+ useCapture: useCapture
+ };
+ }
+ eventManager(this, type, listener, configure, trigger, true);
+// handler.call(this, type, listener, useCapture);
+ } else { // use native function.
+ var types = getEventList(type);
+ for (var n = 0; n < types.length; n ++) {
+ handler.call(this, types[n], listener, useCapture);
+ }
+ }
+ };
+ };
+ recall("add");
+ recall("remove");
+ };
+ // NOTE: overwriting HTMLElement doesn't do anything in Firefox.
+ if (navigator.userAgent.match(/Firefox/)) {
+ // TODO: fix Firefox for the general case.
+ augmentEventListener(HTMLDivElement.prototype);
+ augmentEventListener(HTMLCanvasElement.prototype);
+ } else {
+ augmentEventListener(HTMLElement.prototype);
+ }
+ augmentEventListener(document);
+ augmentEventListener(window);
+})();
+
+/// Allows querySelectorAll and other NodeLists to perform *EventListener commands in bulk.
+if (root.modifySelectors) (function() {
+ var proto = NodeList.prototype;
+ proto.removeEventListener = function(type, listener, useCapture) {
+ for (var n = 0, length = this.length; n < length; n ++) {
+ this[n].removeEventListener(type, listener, useCapture);
+ }
+ };
+ proto.addEventListener = function(type, listener, useCapture) {
+ for (var n = 0, length = this.length; n < length; n ++) {
+ this[n].addEventListener(type, listener, useCapture);
+ }
+ };
+})();
+
+return root;
+
+})(eventjs);
+/*:
+ ----------------------------------------------------
+ eventjs.proxy : 0.4.2 : 2013/07/17 : MIT License
+ ----------------------------------------------------
+ https://github.com/mudcube/eventjs.js
+ ----------------------------------------------------
+*/
+
+if (typeof(eventjs) === "undefined") var eventjs = {};
+if (typeof(eventjs.proxy) === "undefined") eventjs.proxy = {};
+
+eventjs.proxy = (function(root) { "use strict";
+
+/*
+ Create a new pointer gesture instance.
+*/
+
+root.pointerSetup = function(conf, self) {
+ /// Configure.
+ conf.target = conf.target || window;
+ conf.doc = conf.target.ownerDocument || conf.target; // Associated document.
+ conf.minFingers = conf.minFingers || conf.fingers || 1; // Minimum required fingers.
+ conf.maxFingers = conf.maxFingers || conf.fingers || Infinity; // Maximum allowed fingers.
+ conf.position = conf.position || "relative"; // Determines what coordinate system points are returned.
+ delete conf.fingers; //-
+ /// Convenience data.
+ self = self || {};
+ self.enabled = true;
+ self.gesture = conf.gesture;
+ self.target = conf.target;
+ self.env = conf.env;
+ ///
+ if (eventjs.modifyEventListener && conf.fromOverwrite) {
+ conf.oldListener = conf.listener;
+ conf.listener = eventjs.createPointerEvent;
+ }
+ /// Convenience commands.
+ var fingers = 0;
+ var type = self.gesture.indexOf("pointer") === 0 && eventjs.modifyEventListener ? "pointer" : "mouse";
+ if (conf.oldListener) self.oldListener = conf.oldListener;
+ ///
+ self.listener = conf.listener;
+ self.proxy = function(listener) {
+ self.defaultListener = conf.listener;
+ conf.listener = listener;
+ listener(conf.event, self);
+ };
+ self.add = function() {
+ if (self.enabled === true) return;
+ if (conf.onPointerDown) eventjs.add(conf.target, type + "down", conf.onPointerDown);
+ if (conf.onPointerMove) eventjs.add(conf.doc, type + "move", conf.onPointerMove);
+ if (conf.onPointerUp) eventjs.add(conf.doc, type + "up", conf.onPointerUp);
+ self.enabled = true;
+ };
+ self.remove = function() {
+ if (self.enabled === false) return;
+ if (conf.onPointerDown) eventjs.remove(conf.target, type + "down", conf.onPointerDown);
+ if (conf.onPointerMove) eventjs.remove(conf.doc, type + "move", conf.onPointerMove);
+ if (conf.onPointerUp) eventjs.remove(conf.doc, type + "up", conf.onPointerUp);
+ self.reset();
+ self.enabled = false;
+ };
+ self.pause = function(opt) {
+ if (conf.onPointerMove && (!opt || opt.move)) eventjs.remove(conf.doc, type + "move", conf.onPointerMove);
+ if (conf.onPointerUp && (!opt || opt.up)) eventjs.remove(conf.doc, type + "up", conf.onPointerUp);
+ fingers = conf.fingers;
+ conf.fingers = 0;
+ };
+ self.resume = function(opt) {
+ if (conf.onPointerMove && (!opt || opt.move)) eventjs.add(conf.doc, type + "move", conf.onPointerMove);
+ if (conf.onPointerUp && (!opt || opt.up)) eventjs.add(conf.doc, type + "up", conf.onPointerUp);
+ conf.fingers = fingers;
+ };
+ self.reset = function() {
+ conf.tracker = {};
+ conf.fingers = 0;
+ };
+ ///
+ return self;
+};
+
+/*
+ Begin proxied pointer command.
+*/
+
+var sp = eventjs.supports; // Default pointerType
+///
+eventjs.isMouse = !!sp.mouse;
+eventjs.isMSPointer = !!sp.touch;
+eventjs.isTouch = !!sp.msPointer;
+///
+root.pointerStart = function(event, self, conf) {
+ /// tracks multiple inputs
+ var type = (event.type || "mousedown").toUpperCase();
+ if (type.indexOf("MOUSE") === 0) {
+ eventjs.isMouse = true;
+ eventjs.isTouch = false;
+ eventjs.isMSPointer = false;
+ } else if (type.indexOf("TOUCH") === 0) {
+ eventjs.isMouse = false;
+ eventjs.isTouch = true;
+ eventjs.isMSPointer = false;
+ } else if (type.indexOf("MSPOINTER") === 0) {
+ eventjs.isMouse = false;
+ eventjs.isTouch = false;
+ eventjs.isMSPointer = true;
+ }
+ ///
+ var addTouchStart = function(touch, sid) {
+ var bbox = conf.bbox;
+ var pt = track[sid] = {};
+ ///
+ switch(conf.position) {
+ case "absolute": // Absolute from within window.
+ pt.offsetX = 0;
+ pt.offsetY = 0;
+ break;
+ case "differenceFromLast": // Since last coordinate recorded.
+ pt.offsetX = touch.pageX;
+ pt.offsetY = touch.pageY;
+ break;
+ case "difference": // Relative from origin.
+ pt.offsetX = touch.pageX;
+ pt.offsetY = touch.pageY;
+ break;
+ case "move": // Move target element.
+ pt.offsetX = touch.pageX - bbox.x1;
+ pt.offsetY = touch.pageY - bbox.y1;
+ break;
+ default: // Relative from within target.
+ pt.offsetX = bbox.x1 - bbox.scrollLeft;
+ pt.offsetY = bbox.y1 - bbox.scrollTop;
+ break;
+ }
+ ///
+ var x = touch.pageX - pt.offsetX;
+ var y = touch.pageY - pt.offsetY;
+ ///
+ pt.rotation = 0;
+ pt.scale = 1;
+ pt.startTime = pt.moveTime = (new Date()).getTime();
+ pt.move = { x: x, y: y };
+ pt.start = { x: x, y: y };
+ ///
+ conf.fingers ++;
+ };
+ ///
+ conf.event = event;
+ if (self.defaultListener) {
+ conf.listener = self.defaultListener;
+ delete self.defaultListener;
+ }
+ ///
+ var isTouchStart = !conf.fingers;
+ var track = conf.tracker;
+ var touches = event.changedTouches || root.getCoords(event);
+ var length = touches.length;
+ // Adding touch events to tracking.
+ for (var i = 0; i < length; i ++) {
+ var touch = touches[i];
+ var sid = touch.identifier || Infinity; // Touch ID.
+ // Track the current state of the touches.
+ if (conf.fingers) {
+ if (conf.fingers >= conf.maxFingers) {
+ var ids = [];
+ for (var sid in conf.tracker) ids.push(sid);
+ self.identifier = ids.join(",");
+ return isTouchStart;
+ }
+ var fingers = 0; // Finger ID.
+ for (var rid in track) {
+ // Replace removed finger.
+ if (track[rid].up) {
+ delete track[rid];
+ addTouchStart(touch, sid);
+ conf.cancel = true;
+ break;
+ }
+ fingers ++;
+ }
+ // Add additional finger.
+ if (track[sid]) continue;
+ addTouchStart(touch, sid);
+ } else { // Start tracking fingers.
+ track = conf.tracker = {};
+ self.bbox = conf.bbox = root.getBoundingBox(conf.target);
+ conf.fingers = 0;
+ conf.cancel = false;
+ addTouchStart(touch, sid);
+ }
+ }
+ ///
+ var ids = [];
+ for (var sid in conf.tracker) ids.push(sid);
+ self.identifier = ids.join(",");
+ ///
+ return isTouchStart;
+};
+
+/*
+ End proxied pointer command.
+*/
+
+root.pointerEnd = function(event, self, conf, onPointerUp) {
+ // Record changed touches have ended (iOS changedTouches is not reliable).
+ var touches = event.touches || [];
+ var length = touches.length;
+ var exists = {};
+ for (var i = 0; i < length; i ++) {
+ var touch = touches[i];
+ var sid = touch.identifier;
+ exists[sid || Infinity] = true;
+ }
+ for (var sid in conf.tracker) {
+ var track = conf.tracker[sid];
+ if (exists[sid] || track.up) continue;
+ if (onPointerUp) { // add changedTouches to mouse.
+ onPointerUp({
+ pageX: track.pageX,
+ pageY: track.pageY,
+ changedTouches: [{
+ pageX: track.pageX,
+ pageY: track.pageY,
+ identifier: sid === "Infinity" ? Infinity : sid
+ }]
+ }, "up");
+ }
+ track.up = true;
+ conf.fingers --;
+ }
+/* // This should work but fails in Safari on iOS4 so not using it.
+ var touches = event.changedTouches || root.getCoords(event);
+ var length = touches.length;
+ // Record changed touches have ended (this should work).
+ for (var i = 0; i < length; i ++) {
+ var touch = touches[i];
+ var sid = touch.identifier || Infinity;
+ var track = conf.tracker[sid];
+ if (track && !track.up) {
+ if (onPointerUp) { // add changedTouches to mouse.
+ onPointerUp({
+ changedTouches: [{
+ pageX: track.pageX,
+ pageY: track.pageY,
+ identifier: sid === "Infinity" ? Infinity : sid
+ }]
+ }, "up");
+ }
+ track.up = true;
+ conf.fingers --;
+ }
+ } */
+ // Wait for all fingers to be released.
+ if (conf.fingers !== 0) return false;
+ // Record total number of fingers gesture used.
+ var ids = [];
+ conf.gestureFingers = 0;
+ for (var sid in conf.tracker) {
+ conf.gestureFingers ++;
+ ids.push(sid);
+ }
+ self.identifier = ids.join(",");
+ // Our pointer gesture has ended.
+ return true;
+};
+
+/*
+ Returns mouse coords in an array to match event.*Touches
+ ------------------------------------------------------------
+ var touch = event.changedTouches || root.getCoords(event);
+*/
+
+root.getCoords = function(event) {
+ if (typeof(event.pageX) !== "undefined") { // Desktop browsers.
+ root.getCoords = function(event) {
+ return Array({
+ type: "mouse",
+ x: event.pageX,
+ y: event.pageY,
+ pageX: event.pageX,
+ pageY: event.pageY,
+ identifier: event.pointerId || Infinity // pointerId is MS
+ });
+ };
+ } else { // Internet Explorer <= 8.0
+ root.getCoords = function(event) {
+ var doc = document.documentElement;
+ event = event || window.event;
+ return Array({
+ type: "mouse",
+ x: event.clientX + doc.scrollLeft,
+ y: event.clientY + doc.scrollTop,
+ pageX: event.clientX + doc.scrollLeft,
+ pageY: event.clientY + doc.scrollTop,
+ identifier: Infinity
+ });
+ };
+ }
+ return root.getCoords(event);
+};
+
+/*
+ Returns single coords in an object.
+ ------------------------------------------------------------
+ var mouse = root.getCoord(event);
+*/
+
+root.getCoord = function(event) {
+ if ("ontouchstart" in window) { // Mobile browsers.
+ var pX = 0;
+ var pY = 0;
+ root.getCoord = function(event) {
+ var touches = event.changedTouches;
+ if (touches && touches.length) { // ontouchstart + ontouchmove
+ return {
+ x: pX = touches[0].pageX,
+ y: pY = touches[0].pageY
+ };
+ } else { // ontouchend
+ return {
+ x: pX,
+ y: pY
+ };
+ }
+ };
+ } else if(typeof(event.pageX) !== "undefined" && typeof(event.pageY) !== "undefined") { // Desktop browsers.
+ root.getCoord = function(event) {
+ return {
+ x: event.pageX,
+ y: event.pageY
+ };
+ };
+ } else { // Internet Explorer <=8.0
+ root.getCoord = function(event) {
+ var doc = document.documentElement;
+ event = event || window.event;
+ return {
+ x: event.clientX + doc.scrollLeft,
+ y: event.clientY + doc.scrollTop
+ };
+ };
+ }
+ return root.getCoord(event);
+};
+
+/*
+ Get target scale and position in space.
+*/
+
+var getPropertyAsFloat = function(o, type) {
+ var n = parseFloat(o.getPropertyValue(type), 10);
+ return isFinite(n) ? n : 0;
+};
+
+root.getBoundingBox = function(o) {
+ if (o === window || o === document) o = document.body;
+ ///
+ var bbox = {};
+ var bcr = o.getBoundingClientRect();
+ bbox.width = bcr.width;
+ bbox.height = bcr.height;
+ bbox.x1 = bcr.left;
+ bbox.y1 = bcr.top;
+ bbox.scaleX = bcr.width / o.offsetWidth || 1;
+ bbox.scaleY = bcr.height / o.offsetHeight || 1;
+ bbox.scrollLeft = 0;
+ bbox.scrollTop = 0;
+ ///
+ var style = window.getComputedStyle(o);
+ var borderBox = style.getPropertyValue("box-sizing") === "border-box";
+ ///
+ if (borderBox === false) {
+ var left = getPropertyAsFloat(style, "border-left-width");
+ var right = getPropertyAsFloat(style, "border-right-width");
+ var bottom = getPropertyAsFloat(style, "border-bottom-width");
+ var top = getPropertyAsFloat(style, "border-top-width");
+ bbox.border = [ left, right, top, bottom ];
+ bbox.x1 += left;
+ bbox.y1 += top;
+ bbox.width -= right + left;
+ bbox.height -= bottom + top;
+ }
+
+/* var left = getPropertyAsFloat(style, "padding-left");
+ var right = getPropertyAsFloat(style, "padding-right");
+ var bottom = getPropertyAsFloat(style, "padding-bottom");
+ var top = getPropertyAsFloat(style, "padding-top");
+ bbox.padding = [ left, right, top, bottom ];*/
+ ///
+ bbox.x2 = bbox.x1 + bbox.width;
+ bbox.y2 = bbox.y1 + bbox.height;
+
+ /// Get the scroll of container element.
+ var position = style.getPropertyValue("position");
+ var tmp = position === "fixed" ? o : o.parentNode;
+ while (tmp !== null) {
+ if (tmp === document.body) break;
+ if (tmp.scrollTop === undefined) break;
+ var style = window.getComputedStyle(tmp);
+ var position = style.getPropertyValue("position");
+ if (position === "absolute") {
+
+ } else if (position === "fixed") {
+// bbox.scrollTop += document.body.scrollTop;
+// bbox.scrollLeft += document.body.scrollLeft;
+ bbox.scrollTop -= tmp.parentNode.scrollTop;
+ bbox.scrollLeft -= tmp.parentNode.scrollLeft;
+ break;
+ } else {
+ bbox.scrollLeft += tmp.scrollLeft;
+ bbox.scrollTop += tmp.scrollTop;
+ }
+ ///
+ tmp = tmp.parentNode;
+ };
+ ///
+ bbox.scrollBodyLeft = (window.pageXOffset !== undefined) ? window.pageXOffset : (document.documentElement || document.body.parentNode || document.body).scrollLeft;
+ bbox.scrollBodyTop = (window.pageYOffset !== undefined) ? window.pageYOffset : (document.documentElement || document.body.parentNode || document.body).scrollTop;
+ ///
+ bbox.scrollLeft -= bbox.scrollBodyLeft;
+ bbox.scrollTop -= bbox.scrollBodyTop;
+ ///
+ return bbox;
+};
+
+/*
+ Keep track of metaKey, the proper ctrlKey for users platform.
+ ----------------------------------------------------
+ http://www.quirksmode.org/js/keys.html
+*/
+
+(function() {
+ var agent = navigator.userAgent.toLowerCase();
+ var mac = agent.indexOf("macintosh") !== -1;
+ var metaKeys;
+ if (mac && agent.indexOf("khtml") !== -1) { // chrome, safari.
+ metaKeys = { 91: true, 93: true };
+ } else if (mac && agent.indexOf("firefox") !== -1) { // mac firefox.
+ metaKeys = { 224: true };
+ } else { // windows, linux, or mac opera.
+ metaKeys = { 17: true };
+ }
+ (root.metaTrackerReset = function() {
+ eventjs.fnKey = root.fnKey = false;
+ eventjs.metaKey = root.metaKey = false;
+ eventjs.ctrlKey = root.ctrlKey = false;
+ eventjs.shiftKey = root.shiftKey = false;
+ eventjs.altKey = root.altKey = false;
+ })();
+ root.metaTracker = function(event) {
+ var metaCheck = !!metaKeys[event.keyCode];
+ if (metaCheck) eventjs.metaKey = root.metaKey = event.type === "keydown";
+ eventjs.ctrlKey = root.ctrlKey = event.ctrlKey;
+ eventjs.shiftKey = root.shiftKey = event.shiftKey;
+ eventjs.altKey = root.altKey = event.altKey;
+ return metaCheck;
+ };
+})();
+
+return root;
+
+})(eventjs.proxy);
+/*:
+ ----------------------------------------------------
+ "MutationObserver" event proxy.
+ ----------------------------------------------------
+ author: Selvakumar Arumugam - MIT LICENSE
+ src: http://stackoverflow.com/questions/10868104/can-you-have-a-javascript-hook-trigger-after-a-dom-elements-style-object-change
+ ----------------------------------------------------
+*/
+if (typeof(eventjs) === "undefined") var eventjs = {};
+
+eventjs.MutationObserver = (function() {
+ var MutationObserver = window.MutationObserver || window.WebKitMutationObserver || window.MozMutationObserver;
+ var DOMAttrModifiedSupported = !MutationObserver && (function() {
+ var p = document.createElement("p");
+ var flag = false;
+ var fn = function() { flag = true };
+ if (p.addEventListener) {
+ p.addEventListener("DOMAttrModified", fn, false);
+ } else if (p.attachEvent) {
+ p.attachEvent("onDOMAttrModified", fn);
+ } else {
+ return false;
+ }
+ ///
+ p.setAttribute("id", "target");
+ ///
+ return flag;
+ })();
+ ///
+ return function(container, callback) {
+ if (MutationObserver) {
+ var options = {
+ subtree: false,
+ attributes: true
+ };
+ var observer = new MutationObserver(function(mutations) {
+ mutations.forEach(function(e) {
+ callback.call(e.target, e.attributeName);
+ });
+ });
+ observer.observe(container, options)
+ } else if (DOMAttrModifiedSupported) {
+ eventjs.add(container, "DOMAttrModified", function(e) {
+ callback.call(container, e.attrName);
+ });
+ } else if ("onpropertychange" in document.body) {
+ eventjs.add(container, "propertychange", function(e) {
+ callback.call(container, window.event.propertyName);
+ });
+ }
+ }
+})();
+/*:
+ "Click" event proxy.
+ ----------------------------------------------------
+ eventjs.add(window, "click", function(event, self) {});
+*/
+
+if (typeof(eventjs) === "undefined") var eventjs = {};
+if (typeof(eventjs.proxy) === "undefined") eventjs.proxy = {};
+
+eventjs.proxy = (function(root) { "use strict";
+
+root.click = function(conf) {
+ conf.gesture = conf.gesture || "click";
+ conf.maxFingers = conf.maxFingers || conf.fingers || 1;
+ /// Tracking the events.
+ conf.onPointerDown = function (event) {
+ if (root.pointerStart(event, self, conf)) {
+ eventjs.add(conf.target, "mouseup", conf.onPointerUp);
+ }
+ };
+ conf.onPointerUp = function(event) {
+ if (root.pointerEnd(event, self, conf)) {
+ eventjs.remove(conf.target, "mouseup", conf.onPointerUp);
+ var pointers = event.changedTouches || root.getCoords(event);
+ var pointer = pointers[0];
+ var bbox = conf.bbox;
+ var newbbox = root.getBoundingBox(conf.target);
+ var y = pointer.pageY - newbbox.scrollBodyTop;
+ var x = pointer.pageX - newbbox.scrollBodyLeft;
+ ////
+ if (x > bbox.x1 && y > bbox.y1 &&
+ x < bbox.x2 && y < bbox.y2 &&
+ bbox.scrollTop === newbbox.scrollTop) { // has not been scrolled
+ ///
+ for (var key in conf.tracker) break; //- should be modularized? in dblclick too
+ var point = conf.tracker[key];
+ self.x = point.start.x;
+ self.y = point.start.y;
+ ///
+ conf.listener(event, self);
+ }
+ }
+ };
+ // Generate maintenance commands, and other configurations.
+ var self = root.pointerSetup(conf);
+ self.state = "click";
+ // Attach events.
+ eventjs.add(conf.target, "mousedown", conf.onPointerDown);
+ // Return this object.
+ return self;
+};
+
+eventjs.Gesture = eventjs.Gesture || {};
+eventjs.Gesture._gestureHandlers = eventjs.Gesture._gestureHandlers || {};
+eventjs.Gesture._gestureHandlers.click = root.click;
+
+return root;
+
+})(eventjs.proxy);
+/*:
+ "Double-Click" aka "Double-Tap" event proxy.
+ ----------------------------------------------------
+ eventjs.add(window, "dblclick", function(event, self) {});
+ ----------------------------------------------------
+ Touch an target twice for <= 700ms, with less than 25 pixel drift.
+*/
+
+if (typeof(eventjs) === "undefined") var eventjs = {};
+if (typeof(eventjs.proxy) === "undefined") eventjs.proxy = {};
+
+eventjs.proxy = (function(root) { "use strict";
+
+root.dbltap =
+root.dblclick = function(conf) {
+ conf.gesture = conf.gesture || "dbltap";
+ conf.maxFingers = conf.maxFingers || conf.fingers || 1;
+ // Setting up local variables.
+ var delay = 700; // in milliseconds
+ var time0, time1, timeout;
+ var pointer0, pointer1;
+ // Tracking the events.
+ conf.onPointerDown = function (event) {
+ var pointers = event.changedTouches || root.getCoords(event);
+ if (time0 && !time1) { // Click #2
+ pointer1 = pointers[0];
+ time1 = (new Date()).getTime() - time0;
+ } else { // Click #1
+ pointer0 = pointers[0];
+ time0 = (new Date()).getTime();
+ time1 = 0;
+ clearTimeout(timeout);
+ timeout = setTimeout(function() {
+ time0 = 0;
+ }, delay);
+ }
+ if (root.pointerStart(event, self, conf)) {
+ eventjs.add(conf.target, "mousemove", conf.onPointerMove).listener(event);
+ eventjs.add(conf.target, "mouseup", conf.onPointerUp);
+ }
+ };
+ conf.onPointerMove = function (event) {
+ if (time0 && !time1) {
+ var pointers = event.changedTouches || root.getCoords(event);
+ pointer1 = pointers[0];
+ }
+ var bbox = conf.bbox;
+ var ax = (pointer1.pageX - bbox.x1);
+ var ay = (pointer1.pageY - bbox.y1);
+ if (!(ax > 0 && ax < bbox.width && // Within target coordinates..
+ ay > 0 && ay < bbox.height &&
+ Math.abs(pointer1.pageX - pointer0.pageX) <= 25 && // Within drift deviance.
+ Math.abs(pointer1.pageY - pointer0.pageY) <= 25)) {
+ // Cancel out this listener.
+ eventjs.remove(conf.target, "mousemove", conf.onPointerMove);
+ clearTimeout(timeout);
+ time0 = time1 = 0;
+ }
+ };
+ conf.onPointerUp = function(event) {
+ if (root.pointerEnd(event, self, conf)) {
+ eventjs.remove(conf.target, "mousemove", conf.onPointerMove);
+ eventjs.remove(conf.target, "mouseup", conf.onPointerUp);
+ }
+ if (time0 && time1) {
+ if (time1 <= delay) { // && !(event.cancelBubble && ++event.cancelBubbleCount > 1)) {
+ self.state = conf.gesture;
+ for (var key in conf.tracker) break;
+ var point = conf.tracker[key];
+ self.x = point.start.x;
+ self.y = point.start.y;
+ conf.listener(event, self);
+ }
+ clearTimeout(timeout);
+ time0 = time1 = 0;
+ }
+ };
+ // Generate maintenance commands, and other configurations.
+ var self = root.pointerSetup(conf);
+ self.state = "dblclick";
+ // Attach events.
+ eventjs.add(conf.target, "mousedown", conf.onPointerDown);
+ // Return this object.
+ return self;
+};
+
+eventjs.Gesture = eventjs.Gesture || {};
+eventjs.Gesture._gestureHandlers = eventjs.Gesture._gestureHandlers || {};
+eventjs.Gesture._gestureHandlers.dbltap = root.dbltap;
+eventjs.Gesture._gestureHandlers.dblclick = root.dblclick;
+
+return root;
+
+})(eventjs.proxy);
+/*:
+ "Drag" event proxy (1+ fingers).
+ ----------------------------------------------------
+ CONFIGURE: maxFingers, position.
+ ----------------------------------------------------
+ eventjs.add(window, "drag", function(event, self) {
+ console.log(self.gesture, self.state, self.start, self.x, self.y, self.bbox);
+ });
+*/
+
+if (typeof(eventjs) === "undefined") var eventjs = {};
+if (typeof(eventjs.proxy) === "undefined") eventjs.proxy = {};
+
+eventjs.proxy = (function(root) { "use strict";
+
+root.dragElement = function(that, event) {
+ root.drag({
+ event: event,
+ target: that,
+ position: "move",
+ listener: function(event, self) {
+ that.style.left = self.x + "px";
+ that.style.top = self.y + "px";
+ eventjs.prevent(event);
+ }
+ });
+};
+
+root.drag = function(conf) {
+ conf.gesture = "drag";
+ conf.onPointerDown = function (event) {
+ if (root.pointerStart(event, self, conf)) {
+ if (!conf.monitor) {
+ eventjs.add(conf.doc, "mousemove", conf.onPointerMove);
+ eventjs.add(conf.doc, "mouseup", conf.onPointerUp);
+ }
+ }
+ // Process event listener.
+ conf.onPointerMove(event, "down");
+ };
+ conf.onPointerMove = function (event, state) {
+ if (!conf.tracker) return conf.onPointerDown(event);
+//alertify.log('move')
+ var bbox = conf.bbox;
+ var touches = event.changedTouches || root.getCoords(event);
+ var length = touches.length;
+ for (var i = 0; i < length; i ++) {
+ var touch = touches[i];
+ var identifier = touch.identifier || Infinity;
+ var pt = conf.tracker[identifier];
+ // Identifier defined outside of listener.
+ if (!pt) continue;
+ pt.pageX = touch.pageX;
+ pt.pageY = touch.pageY;
+ // Record data.
+ self.state = state || "move";
+ self.identifier = identifier;
+ self.start = pt.start;
+ self.fingers = conf.fingers;
+ if (conf.position === "differenceFromLast") {
+ self.x = (pt.pageX - pt.offsetX);
+ self.y = (pt.pageY - pt.offsetY);
+ pt.offsetX = pt.pageX;
+ pt.offsetY = pt.pageY;
+ } else {
+ self.x = (pt.pageX - pt.offsetX);
+ self.y = (pt.pageY - pt.offsetY);
+ }
+ ///
+ conf.listener(event, self);
+ }
+ };
+ conf.onPointerUp = function(event) {
+ // Remove tracking for touch.
+ if (root.pointerEnd(event, self, conf, conf.onPointerMove)) {
+ if (!conf.monitor) {
+ eventjs.remove(conf.doc, "mousemove", conf.onPointerMove);
+ eventjs.remove(conf.doc, "mouseup", conf.onPointerUp);
+ }
+ }
+ };
+ // Generate maintenance commands, and other configurations.
+ var self = root.pointerSetup(conf);
+ // Attach events.
+ if (conf.event) {
+ conf.onPointerDown(conf.event);
+ } else { //
+ eventjs.add(conf.target, "mousedown", conf.onPointerDown);
+ if (conf.monitor) {
+ eventjs.add(conf.doc, "mousemove", conf.onPointerMove);
+ eventjs.add(conf.doc, "mouseup", conf.onPointerUp);
+ }
+ }
+ // Return this object.
+ return self;
+};
+
+eventjs.Gesture = eventjs.Gesture || {};
+eventjs.Gesture._gestureHandlers = eventjs.Gesture._gestureHandlers || {};
+eventjs.Gesture._gestureHandlers.drag = root.drag;
+
+return root;
+
+})(eventjs.proxy);
+/*:
+ "Gesture" event proxy (2+ fingers).
+ ----------------------------------------------------
+ CONFIGURE: minFingers, maxFingers.
+ ----------------------------------------------------
+ eventjs.add(window, "gesture", function(event, self) {
+ console.log(
+ self.x, // centroid
+ self.y,
+ self.rotation,
+ self.scale,
+ self.fingers,
+ self.state
+ );
+ });
+*/
+
+if (typeof(eventjs) === "undefined") var eventjs = {};
+if (typeof(eventjs.proxy) === "undefined") eventjs.proxy = {};
+
+eventjs.proxy = (function(root) { "use strict";
+
+var RAD_DEG = Math.PI / 180;
+var getCentroid = function(self, points) {
+ var centroidx = 0;
+ var centroidy = 0;
+ var length = 0;
+ for (var sid in points) {
+ var touch = points[sid];
+ if (touch.up) continue;
+ centroidx += touch.move.x;
+ centroidy += touch.move.y;
+ length ++;
+ }
+ self.x = centroidx /= length;
+ self.y = centroidy /= length;
+ return self;
+};
+
+root.gesture = function(conf) {
+ conf.gesture = conf.gesture || "gesture";
+ conf.minFingers = conf.minFingers || conf.fingers || 2;
+ // Tracking the events.
+ conf.onPointerDown = function (event) {
+ var fingers = conf.fingers;
+ if (root.pointerStart(event, self, conf)) {
+ eventjs.add(conf.doc, "mousemove", conf.onPointerMove);
+ eventjs.add(conf.doc, "mouseup", conf.onPointerUp);
+ }
+ // Record gesture start.
+ if (conf.fingers === conf.minFingers && fingers !== conf.fingers) {
+ self.fingers = conf.minFingers;
+ self.scale = 1;
+ self.rotation = 0;
+ self.state = "start";
+ var sids = ""; //- FIXME(mud): can generate duplicate IDs.
+ for (var key in conf.tracker) sids += key;
+ self.identifier = parseInt(sids);
+ getCentroid(self, conf.tracker);
+ conf.listener(event, self);
+ }
+ };
+ ///
+ conf.onPointerMove = function (event, state) {
+ var bbox = conf.bbox;
+ var points = conf.tracker;
+ var touches = event.changedTouches || root.getCoords(event);
+ var length = touches.length;
+ // Update tracker coordinates.
+ for (var i = 0; i < length; i ++) {
+ var touch = touches[i];
+ var sid = touch.identifier || Infinity;
+ var pt = points[sid];
+ // Check whether "pt" is used by another gesture.
+ if (!pt) continue;
+ // Find the actual coordinates.
+ pt.move.x = (touch.pageX - bbox.x1);
+ pt.move.y = (touch.pageY - bbox.y1);
+ }
+ ///
+ if (conf.fingers < conf.minFingers) return;
+ ///
+ var touches = [];
+ var scale = 0;
+ var rotation = 0;
+
+ /// Calculate centroid of gesture.
+ getCentroid(self, points);
+ ///
+ for (var sid in points) {
+ var touch = points[sid];
+ if (touch.up) continue;
+ var start = touch.start;
+ if (!start.distance) {
+ var dx = start.x - self.x;
+ var dy = start.y - self.y;
+ start.distance = Math.sqrt(dx * dx + dy * dy);
+ start.angle = Math.atan2(dx, dy) / RAD_DEG;
+ }
+ // Calculate scale.
+ var dx = touch.move.x - self.x;
+ var dy = touch.move.y - self.y;
+ var distance = Math.sqrt(dx * dx + dy * dy);
+ scale += distance / start.distance;
+ // Calculate rotation.
+ var angle = Math.atan2(dx, dy) / RAD_DEG;
+ var rotate = (start.angle - angle + 360) % 360 - 180;
+ touch.DEG2 = touch.DEG1; // Previous degree.
+ touch.DEG1 = rotate > 0 ? rotate : -rotate; // Current degree.
+ if (typeof(touch.DEG2) !== "undefined") {
+ if (rotate > 0) {
+ touch.rotation += touch.DEG1 - touch.DEG2;
+ } else {
+ touch.rotation -= touch.DEG1 - touch.DEG2;
+ }
+ rotation += touch.rotation;
+ }
+ // Attach current points to self.
+ touches.push(touch.move);
+ }
+ ///
+ self.touches = touches;
+ self.fingers = conf.fingers;
+ self.scale = scale / conf.fingers;
+ self.rotation = rotation / conf.fingers;
+ self.state = "change";
+ conf.listener(event, self);
+ };
+ conf.onPointerUp = function(event) {
+ // Remove tracking for touch.
+ var fingers = conf.fingers;
+ if (root.pointerEnd(event, self, conf)) {
+ eventjs.remove(conf.doc, "mousemove", conf.onPointerMove);
+ eventjs.remove(conf.doc, "mouseup", conf.onPointerUp);
+ }
+ // Check whether fingers has dropped below minFingers.
+ if (fingers === conf.minFingers && conf.fingers < conf.minFingers) {
+ self.fingers = conf.fingers;
+ self.state = "end";
+ conf.listener(event, self);
+ }
+ };
+ // Generate maintenance commands, and other configurations.
+ var self = root.pointerSetup(conf);
+ // Attach events.
+ eventjs.add(conf.target, "mousedown", conf.onPointerDown);
+ // Return this object.
+ return self;
+};
+
+eventjs.Gesture = eventjs.Gesture || {};
+eventjs.Gesture._gestureHandlers = eventjs.Gesture._gestureHandlers || {};
+eventjs.Gesture._gestureHandlers.gesture = root.gesture;
+
+return root;
+
+})(eventjs.proxy);
+/*:
+ "Pointer" event proxy (1+ fingers).
+ ----------------------------------------------------
+ CONFIGURE: minFingers, maxFingers.
+ ----------------------------------------------------
+ eventjs.add(window, "gesture", function(event, self) {
+ console.log(self.rotation, self.scale, self.fingers, self.state);
+ });
+*/
+
+if (typeof(eventjs) === "undefined") var eventjs = {};
+if (typeof(eventjs.proxy) === "undefined") eventjs.proxy = {};
+
+eventjs.proxy = (function(root) { "use strict";
+
+root.pointerdown =
+root.pointermove =
+root.pointerup = function(conf) {
+ conf.gesture = conf.gesture || "pointer";
+ if (conf.target.isPointerEmitter) return;
+ // Tracking the events.
+ var isDown = true;
+ conf.onPointerDown = function (event) {
+ isDown = false;
+ self.gesture = "pointerdown";
+ conf.listener(event, self);
+ };
+ conf.onPointerMove = function (event) {
+ self.gesture = "pointermove";
+ conf.listener(event, self, isDown);
+ };
+ conf.onPointerUp = function (event) {
+ isDown = true;
+ self.gesture = "pointerup";
+ conf.listener(event, self, true);
+ };
+ // Generate maintenance commands, and other configurations.
+ var self = root.pointerSetup(conf);
+ // Attach events.
+ eventjs.add(conf.target, "mousedown", conf.onPointerDown);
+ eventjs.add(conf.target, "mousemove", conf.onPointerMove);
+ eventjs.add(conf.doc, "mouseup", conf.onPointerUp);
+ // Return this object.
+ conf.target.isPointerEmitter = true;
+ return self;
+};
+
+eventjs.Gesture = eventjs.Gesture || {};
+eventjs.Gesture._gestureHandlers = eventjs.Gesture._gestureHandlers || {};
+eventjs.Gesture._gestureHandlers.pointerdown = root.pointerdown;
+eventjs.Gesture._gestureHandlers.pointermove = root.pointermove;
+eventjs.Gesture._gestureHandlers.pointerup = root.pointerup;
+
+return root;
+
+})(eventjs.proxy);
+/*:
+ "Device Motion" and "Shake" event proxy.
+ ----------------------------------------------------
+ http://developer.android.com/reference/android/hardware/Sensoreventjs.html#values
+ ----------------------------------------------------
+ eventjs.add(window, "shake", function(event, self) {});
+ eventjs.add(window, "devicemotion", function(event, self) {
+ console.log(self.acceleration, self.accelerationIncludingGravity);
+ });
+*/
+
+if (typeof(eventjs) === "undefined") var eventjs = {};
+if (typeof(eventjs.proxy) === "undefined") eventjs.proxy = {};
+
+eventjs.proxy = (function(root) { "use strict";
+
+root.shake = function(conf) {
+ // Externally accessible data.
+ var self = {
+ gesture: "devicemotion",
+ acceleration: {},
+ accelerationIncludingGravity: {},
+ target: conf.target,
+ listener: conf.listener,
+ remove: function() {
+ window.removeEventListener('devicemotion', onDeviceMotion, false);
+ }
+ };
+ // Setting up local variables.
+ var threshold = 4; // Gravitational threshold.
+ var timeout = 1000; // Timeout between shake events.
+ var timeframe = 200; // Time between shakes.
+ var shakes = 3; // Minimum shakes to trigger event.
+ var lastShake = (new Date()).getTime();
+ var gravity = { x: 0, y: 0, z: 0 };
+ var delta = {
+ x: { count: 0, value: 0 },
+ y: { count: 0, value: 0 },
+ z: { count: 0, value: 0 }
+ };
+ // Tracking the events.
+ var onDeviceMotion = function(e) {
+ var alpha = 0.8; // Low pass filter.
+ var o = e.accelerationIncludingGravity;
+ gravity.x = alpha * gravity.x + (1 - alpha) * o.x;
+ gravity.y = alpha * gravity.y + (1 - alpha) * o.y;
+ gravity.z = alpha * gravity.z + (1 - alpha) * o.z;
+ self.accelerationIncludingGravity = gravity;
+ self.acceleration.x = o.x - gravity.x;
+ self.acceleration.y = o.y - gravity.y;
+ self.acceleration.z = o.z - gravity.z;
+ ///
+ if (conf.gesture === "devicemotion") {
+ conf.listener(e, self);
+ return;
+ }
+ var data = "xyz";
+ var now = (new Date()).getTime();
+ for (var n = 0, length = data.length; n < length; n ++) {
+ var letter = data[n];
+ var ACCELERATION = self.acceleration[letter];
+ var DELTA = delta[letter];
+ var abs = Math.abs(ACCELERATION);
+ /// Check whether another shake event was recently registered.
+ if (now - lastShake < timeout) continue;
+ /// Check whether delta surpasses threshold.
+ if (abs > threshold) {
+ var idx = now * ACCELERATION / abs;
+ var span = Math.abs(idx + DELTA.value);
+ // Check whether last delta was registered within timeframe.
+ if (DELTA.value && span < timeframe) {
+ DELTA.value = idx;
+ DELTA.count ++;
+ // Check whether delta count has enough shakes.
+ if (DELTA.count === shakes) {
+ conf.listener(e, self);
+ // Reset tracking.
+ lastShake = now;
+ DELTA.value = 0;
+ DELTA.count = 0;
+ }
+ } else {
+ // Track first shake.
+ DELTA.value = idx;
+ DELTA.count = 1;
+ }
+ }
+ }
+ };
+ // Attach events.
+ if (!window.addEventListener) return;
+ window.addEventListener('devicemotion', onDeviceMotion, false);
+ // Return this object.
+ return self;
+};
+
+eventjs.Gesture = eventjs.Gesture || {};
+eventjs.Gesture._gestureHandlers = eventjs.Gesture._gestureHandlers || {};
+eventjs.Gesture._gestureHandlers.shake = root.shake;
+
+return root;
+
+})(eventjs.proxy);
+/*:
+ "Swipe" event proxy (1+ fingers).
+ ----------------------------------------------------
+ CONFIGURE: snap, threshold, maxFingers.
+ ----------------------------------------------------
+ eventjs.add(window, "swipe", function(event, self) {
+ console.log(self.velocity, self.angle);
+ });
+*/
+
+if (typeof(eventjs) === "undefined") var eventjs = {};
+if (typeof(eventjs.proxy) === "undefined") eventjs.proxy = {};
+
+eventjs.proxy = (function(root) { "use strict";
+
+var RAD_DEG = Math.PI / 180;
+
+root.swipe = function(conf) {
+ conf.snap = conf.snap || 90; // angle snap.
+ conf.threshold = conf.threshold || 1; // velocity threshold.
+ conf.gesture = conf.gesture || "swipe";
+ // Tracking the events.
+ conf.onPointerDown = function (event) {
+ if (root.pointerStart(event, self, conf)) {
+ eventjs.add(conf.doc, "mousemove", conf.onPointerMove).listener(event);
+ eventjs.add(conf.doc, "mouseup", conf.onPointerUp);
+ }
+ };
+ conf.onPointerMove = function (event) {
+ var touches = event.changedTouches || root.getCoords(event);
+ var length = touches.length;
+ for (var i = 0; i < length; i ++) {
+ var touch = touches[i];
+ var sid = touch.identifier || Infinity;
+ var o = conf.tracker[sid];
+ // Identifier defined outside of listener.
+ if (!o) continue;
+ o.move.x = touch.pageX;
+ o.move.y = touch.pageY;
+ o.moveTime = (new Date()).getTime();
+ }
+ };
+ conf.onPointerUp = function(event) {
+ if (root.pointerEnd(event, self, conf)) {
+ eventjs.remove(conf.doc, "mousemove", conf.onPointerMove);
+ eventjs.remove(conf.doc, "mouseup", conf.onPointerUp);
+ ///
+ var velocity1;
+ var velocity2
+ var degree1;
+ var degree2;
+ /// Calculate centroid of gesture.
+ var start = { x: 0, y: 0 };
+ var endx = 0;
+ var endy = 0;
+ var length = 0;
+ ///
+ for (var sid in conf.tracker) {
+ var touch = conf.tracker[sid];
+ var xdist = touch.move.x - touch.start.x;
+ var ydist = touch.move.y - touch.start.y;
+ ///
+ endx += touch.move.x;
+ endy += touch.move.y;
+ start.x += touch.start.x;
+ start.y += touch.start.y;
+ length ++;
+ ///
+ var distance = Math.sqrt(xdist * xdist + ydist * ydist);
+ var ms = touch.moveTime - touch.startTime;
+ var degree2 = Math.atan2(xdist, ydist) / RAD_DEG + 180;
+ var velocity2 = ms ? distance / ms : 0;
+ if (typeof(degree1) === "undefined") {
+ degree1 = degree2;
+ velocity1 = velocity2;
+ } else if (Math.abs(degree2 - degree1) <= 20) {
+ degree1 = (degree1 + degree2) / 2;
+ velocity1 = (velocity1 + velocity2) / 2;
+ } else {
+ return;
+ }
+ }
+ ///
+ var fingers = conf.gestureFingers;
+ if (conf.minFingers <= fingers && conf.maxFingers >= fingers) {
+ if (velocity1 > conf.threshold) {
+ start.x /= length;
+ start.y /= length;
+ self.start = start;
+ self.x = endx / length;
+ self.y = endy / length;
+ self.angle = -((((degree1 / conf.snap + 0.5) >> 0) * conf.snap || 360) - 360);
+ self.velocity = velocity1;
+ self.fingers = fingers;
+ self.state = "swipe";
+ conf.listener(event, self);
+ }
+ }
+ }
+ };
+ // Generate maintenance commands, and other configurations.
+ var self = root.pointerSetup(conf);
+ // Attach events.
+ eventjs.add(conf.target, "mousedown", conf.onPointerDown);
+ // Return this object.
+ return self;
+};
+
+eventjs.Gesture = eventjs.Gesture || {};
+eventjs.Gesture._gestureHandlers = eventjs.Gesture._gestureHandlers || {};
+eventjs.Gesture._gestureHandlers.swipe = root.swipe;
+
+return root;
+
+})(eventjs.proxy);
+/*:
+ "Tap" and "Longpress" event proxy.
+ ----------------------------------------------------
+ CONFIGURE: delay (longpress), timeout (tap).
+ ----------------------------------------------------
+ eventjs.add(window, "tap", function(event, self) {
+ console.log(self.fingers);
+ });
+ ----------------------------------------------------
+ multi-finger tap // touch an target for <= 250ms.
+ multi-finger longpress // touch an target for >= 500ms
+*/
+
+if (typeof(eventjs) === "undefined") var eventjs = {};
+if (typeof(eventjs.proxy) === "undefined") eventjs.proxy = {};
+
+eventjs.proxy = (function(root) { "use strict";
+
+root.longpress = function(conf) {
+ conf.gesture = "longpress";
+ return root.tap(conf);
+};
+
+root.tap = function(conf) {
+ conf.delay = conf.delay || 500;
+ conf.timeout = conf.timeout || 250;
+ conf.driftDeviance = conf.driftDeviance || 10;
+ conf.gesture = conf.gesture || "tap";
+ // Setting up local variables.
+ var timestamp, timeout;
+ // Tracking the events.
+ conf.onPointerDown = function (event) {
+ if (root.pointerStart(event, self, conf)) {
+ timestamp = (new Date()).getTime();
+ // Initialize event listeners.
+ eventjs.add(conf.doc, "mousemove", conf.onPointerMove).listener(event);
+ eventjs.add(conf.doc, "mouseup", conf.onPointerUp);
+ // Make sure this is a "longpress" event.
+ if (conf.gesture !== "longpress") return;
+ timeout = setTimeout(function() {
+ if (event.cancelBubble && ++event.cancelBubbleCount > 1) return;
+ // Make sure no fingers have been changed.
+ var fingers = 0;
+ for (var key in conf.tracker) {
+ var point = conf.tracker[key];
+ if (point.end === true) return;
+ if (conf.cancel) return;
+ fingers ++;
+ }
+ // Send callback.
+ if (conf.minFingers <= fingers && conf.maxFingers >= fingers) {
+ self.state = "start";
+ self.fingers = fingers;
+ self.x = point.start.x;
+ self.y = point.start.y;
+ conf.listener(event, self);
+ }
+ }, conf.delay);
+ }
+ };
+ conf.onPointerMove = function (event) {
+ var bbox = conf.bbox;
+ var touches = event.changedTouches || root.getCoords(event);
+ var length = touches.length;
+ for (var i = 0; i < length; i ++) {
+ var touch = touches[i];
+ var identifier = touch.identifier || Infinity;
+ var pt = conf.tracker[identifier];
+ if (!pt) continue;
+ var x = (touch.pageX - bbox.x1);
+ var y = (touch.pageY - bbox.y1);
+ ///
+ var dx = x - pt.start.x;
+ var dy = y - pt.start.y;
+ var distance = Math.sqrt(dx * dx + dy * dy);
+ if (!(x > 0 && x < bbox.width && // Within target coordinates..
+ y > 0 && y < bbox.height &&
+ distance <= conf.driftDeviance)) { // Within drift deviance.
+ // Cancel out this listener.
+ eventjs.remove(conf.doc, "mousemove", conf.onPointerMove);
+ conf.cancel = true;
+ return;
+ }
+ }
+ };
+ conf.onPointerUp = function(event) {
+ if (root.pointerEnd(event, self, conf)) {
+ clearTimeout(timeout);
+ eventjs.remove(conf.doc, "mousemove", conf.onPointerMove);
+ eventjs.remove(conf.doc, "mouseup", conf.onPointerUp);
+ if (event.cancelBubble && ++event.cancelBubbleCount > 1) return;
+ // Callback release on longpress.
+ if (conf.gesture === "longpress") {
+ if (self.state === "start") {
+ self.state = "end";
+ conf.listener(event, self);
+ }
+ return;
+ }
+ // Cancel event due to movement.
+ if (conf.cancel) return;
+ // Ensure delay is within margins.
+ if ((new Date()).getTime() - timestamp > conf.timeout) return;
+ // Send callback.
+ var fingers = conf.gestureFingers;
+ if (conf.minFingers <= fingers && conf.maxFingers >= fingers) {
+ self.state = "tap";
+ self.fingers = conf.gestureFingers;
+ conf.listener(event, self);
+ }
+ }
+ };
+ // Generate maintenance commands, and other configurations.
+ var self = root.pointerSetup(conf);
+ // Attach events.
+ eventjs.add(conf.target, "mousedown", conf.onPointerDown);
+ // Return this object.
+ return self;
+};
+
+eventjs.Gesture = eventjs.Gesture || {};
+eventjs.Gesture._gestureHandlers = eventjs.Gesture._gestureHandlers || {};
+eventjs.Gesture._gestureHandlers.tap = root.tap;
+eventjs.Gesture._gestureHandlers.longpress = root.longpress;
+
+return root;
+
+})(eventjs.proxy);
+/*:
+ "Mouse Wheel" event proxy.
+ ----------------------------------------------------
+ eventjs.add(window, "wheel", function(event, self) {
+ console.log(self.state, self.wheelDelta);
+ });
+*/
+
+if (typeof(eventjs) === "undefined") var eventjs = {};
+if (typeof(eventjs.proxy) === "undefined") eventjs.proxy = {};
+
+eventjs.proxy = (function(root) { "use strict";
+
+root.wheelPreventElasticBounce = function(el) {
+ if (!el) return;
+ if (typeof(el) === "string") el = document.querySelector(el);
+ eventjs.add(el, "wheel", function(event, self) {
+ self.preventElasticBounce();
+ eventjs.stop(event);
+ });
+};
+
+root.wheel = function(conf) {
+ // Configure event listener.
+ var interval;
+ var timeout = conf.timeout || 150;
+ var count = 0;
+ // Externally accessible data.
+ var self = {
+ gesture: "wheel",
+ state: "start",
+ wheelDelta: 0,
+ target: conf.target,
+ listener: conf.listener,
+ preventElasticBounce: function(event) {
+ var target = this.target;
+ var scrollTop = target.scrollTop;
+ var top = scrollTop + target.offsetHeight;
+ var height = target.scrollHeight;
+ if (top === height && this.wheelDelta <= 0) eventjs.cancel(event);
+ else if (scrollTop === 0 && this.wheelDelta >= 0) eventjs.cancel(event);
+ eventjs.stop(event);
+ },
+ add: function() {
+ conf.target[add](type, onMouseWheel, false);
+ },
+ remove: function() {
+ conf.target[remove](type, onMouseWheel, false);
+ }
+ };
+ // Tracking the events.
+ var onMouseWheel = function(event) {
+ event = event || window.event;
+ self.state = count++ ? "change" : "start";
+ self.wheelDelta = event.detail ? event.detail * -20 : event.wheelDelta;
+ conf.listener(event, self);
+ clearTimeout(interval);
+ interval = setTimeout(function() {
+ count = 0;
+ self.state = "end";
+ self.wheelDelta = 0;
+ conf.listener(event, self);
+ }, timeout);
+ };
+ // Attach events.
+ var add = document.addEventListener ? "addEventListener" : "attachEvent";
+ var remove = document.removeEventListener ? "removeEventListener" : "detachEvent";
+ var type = eventjs.getEventSupport("mousewheel") ? "mousewheel" : "DOMMouseScroll";
+ conf.target[add](type, onMouseWheel, false);
+ // Return this object.
+ return self;
+};
+
+eventjs.Gesture = eventjs.Gesture || {};
+eventjs.Gesture._gestureHandlers = eventjs.Gesture._gestureHandlers || {};
+eventjs.Gesture._gestureHandlers.wheel = root.wheel;
+
+return root;
+
+})(eventjs.proxy);
+
+///
+var addEvent = eventjs.add;
+var removeEvent = eventjs.remove;
+///
+(function() {
+ for (var key in eventjs) {
+ Event[key] = eventjs[key];
+ }
+ for (var key in eventjs.proxy) {
+ addEvent[key] = eventjs.proxy[key];
+ }
+})(); \ No newline at end of file
diff --git a/Processing-js/libs/js/Window/Queue.js b/Processing-js/libs/js/Window/Queue.js
new file mode 100644
index 0000000..890b61b
--- /dev/null
+++ b/Processing-js/libs/js/Window/Queue.js
@@ -0,0 +1,87 @@
+/*
+ ----------------------------------------
+ window.Queue : 0.1.1 : http://mudcu.be
+ ----------------------------------------
+ var queue = new Queue({
+ items: list,
+ oncomplete: function() {
+ queue.reset(); // infinite loop!
+ queue.next();
+ },
+ next: function(item) {
+ if (item[0] !== "." && item.indexOf(".") === -1) {
+ readDir(dir + item + "/", queue.next);
+ } else {
+ setTimeout(queue.next, 1)
+ }
+ }
+ });
+*/
+
+if (typeof(window) === "undefined") window = {};
+
+window.Queue = function(conf) {
+ var that = this;
+ /// Request the next item in stack.
+ this.next = function() {
+ var arr = that.queue;
+ /// Emit the progress of the queue.
+ if (conf.onprogress) {
+ conf.onprogress(that.length ? 1 - that.remaining / that.length : 1);
+ }
+ /// Check whether the queue is complete.
+ if (!arr.length) {
+ if (conf.oncomplete) {
+ conf.oncomplete();
+ }
+ return;
+ }
+ /// Indicate previous element as processed.
+ that.remaining --;
+ /// Cleanup previous completed dimension.
+
+ if (String(arr[0]) === "[object Object]" && !arr[0].length) {
+ arr.shift();
+ }
+ /// Process next item in multi-dimensional stack.
+ if (String(arr[0]) === "[object Object]" && arr[0].length) {
+ conf.next(arr[0].shift());
+ } else { // ditto for single-dimensional stack.
+ conf.next(arr.shift());
+ }
+ };
+ ///
+ this.reset = function(items) {
+ items = items || conf.items;
+ this.length = 0;
+ this.remaining = -1;
+ this.queue = [];
+ /// Flatten multi-dimensional objects.
+ for (var key in items) {
+ if (String(items[key]) === "[object Object]") {
+ var sub = [];
+ this.queue.push(sub);
+ for (var id in items[key]) {
+ sub.push(items[key][id]);
+ this.length ++;
+ this.remaining ++;
+ }
+ } else {
+ this.queue.push(items[key]);
+ this.length ++;
+ this.remaining ++;
+ }
+ }
+ };
+ ///
+ this.reset();
+ /// Escape event loop.
+ setTimeout(this.next, 1);
+ ///
+ return this;
+};
+
+/// For NodeJS
+if (typeof (module) !== "undefined" && module.exports) {
+ module.exports = window.Queue;
+} \ No newline at end of file