diff options
author | piernov <piernov@piernov.org> | 2014-03-13 16:48:55 +0100 |
---|---|---|
committer | piernov <piernov@piernov.org> | 2014-03-13 16:48:55 +0100 |
commit | c9af7bb67bfefc3f37b4cdd4a066a29138f202b3 (patch) | |
tree | 93bbee2437b6f78fb7f46d1193d00279b6852c35 /Processing-js/libs/js | |
download | Synthe3000-master.tar.gz Synthe3000-master.tar.bz2 Synthe3000-master.tar.xz Synthe3000-master.zip |
Diffstat (limited to 'Processing-js/libs/js')
-rw-r--r-- | Processing-js/libs/js/MIDI/AudioDetect.js | 69 | ||||
-rw-r--r-- | Processing-js/libs/js/MIDI/LoadPlugin.js | 179 | ||||
-rw-r--r-- | Processing-js/libs/js/MIDI/Player.js | 277 | ||||
-rw-r--r-- | Processing-js/libs/js/MIDI/Plugin.js | 623 | ||||
-rw-r--r-- | Processing-js/libs/js/Window/DOMLoader.XMLHttp.js | 130 | ||||
-rw-r--r-- | Processing-js/libs/js/Window/Event.js | 1901 | ||||
-rw-r--r-- | Processing-js/libs/js/Window/Queue.js | 87 |
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 += " "; + 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 |