// re-map values from 'from' to 'to' // // usage form #1 : map(val, from, to, loop) // 'from', 'to' should be array with 2 numbers. like: [0, 1], [-10, 30], [0.5, 52.4] ... // 'loop' could be 'true' to extrapolate beyond from[1] and to[1] // // examples // // map(val, [30, 40], [2, 3]); // assuming 'val' is in the range [30, 40], what is propotionally matching number in the range [2, 3]; // // map(32, [30, 40], [2, 3]) -> 2.2 // map(32, [30, 40], [3, 2]) -> 2.8 // flip // map(29, [30, 40], [2, 3]) -> 2 // below mininum input range will forced to minimum output value // map(43, [30, 40], [2, 3]) -> 3 // below maximun input range will forced to maximun output value // map(43, [30, 40], [2, 3], true) -> 2.3 // allowing 'looping' // map(33, [30, 40], [2, 3], true) -> 2.3 // looping portion (43-40 -> 3) will be reapplied (30 + 3) will result same as above line // map(13, [30, 40], [2, 3], true) -> 2 // looping only allow 'beyond maximum'.. not 'under minimum' // // usage form #2 : map(val, object) // 'object' should contain following items // object.from // object.to // object.loop // same as above but use instead 'object' form // // examples // // map(33, { // from: [30, 40], // to: [2, 3], // loop: true // }); function map(val, from, to, loop) { if (typeof from === 'object') { if (Array.isArray(from)) { // direct form (array) from = from || [0, 1]; to = to || [0, 1]; if (val < from[0]) return to[0]; if (loop === true) { // 'looping' return (val - from[0]) % (from[1] - from[0]) * (to[1] - to[0]) / (from[1] - from[0]) + to[0]; } else { if (val > from[1]) return to[1]; return (val - from[0]) * (to[1] - to[0]) / (from[1] - from[0]) + to[0]; } } else { // object form var object = from; object.from = object.from || [0, 1]; object.to = object.to || [0, 1]; if (val < object.from[0]) return object.to[0]; if (object.loop === true) { // 'looping' return (val - object.from[0]) % (object.from[1] - object.from[0]) * (object.to[1] - object.to[0]) / (object.from[1] - object.from[0]) + object.to[0]; } else { if (val > object.from[1]) return object.to[1]; return (val - object.from[0]) * (object.to[1] - object.to[0]) / (object.from[1] - object.from[0]) + object.to[0]; } } } } // re-map values from [0, 1] to 'to' // refer to 'map' description to understand 'loop' // ex) known values lies in [0, 1] -> [2, 3] : map2(val, [2, 3]); function map2(val, to, loop) { if (typeof to === 'object') { if (Array.isArray(to)) { // direct form (array) return map(val, [0, 1], to, loop); } else { // object form return map(val, [0, 1], to.to, to.loop); } } } // get a random real number in range of [min, max) function getRandom(min, max) { return map2(Math.random(), [min, max]); } // get a random integer value in set of {min, ... , max} function getRandomInt(min, max) { return Math.floor(getRandom(min, max + 1)); } //use in onFrame(event) to get loopping 'timeline' for animation build-up // ex) getTimeline(event.time, [0, 30]) will return a changing number from 0 to 1, for 30 seconds. // time: could be event.time or event.count // event.time.. will be more time-wise precise // event.count.. will be more frame-wise precise // ex) getTimeline(event.count, [0, 30]) will return a changing number from 0 to 1, for 30 frames (roughly 0.5 sec. in 60 fps animation). // // examples // // getTimeline(event.time, [0, 30]) // from 0 sec. to 30 sec., change value from 0 to 1 // getTimeline(event.time, [0, 30], [3, 2]) // from 0 sec. to 30 sec., change value from 3 to 2 // getTimeline(event.time, [0, 30], [3, 2], true) // from 0 sec. to 30 sec., change value from 3 to 2, and repeat forever. // getTimeline(event.time, [3, 10]) // from 0 sec. to 3 sec. stay at 0, from 3 sec. to 10 sec., change value from 0 to 1 // getTimeline(event.time, { // object form // from: [3, 10], // to: [4, 15], // loop: true // }); function getTimeline(time, from, to, loop) { return map(time, from, to, loop); } //use in onFrame(event) to get loopping 'timeline' for animation build-up // ex) getTimepoint(event.time, [0, 10], path) will return a moving point along with 'path' for 10 seconds // and use is as a point, like // ex) a.position = getTimepoint(event.time, [0, 10], path); // or get y-coord and use it as a number, like // ex) a.fillColor.hue = getTimepoint(event.time, [0, 10], path).y; // if expected range of y values are.. [0, 100], then.. // a.fillColor.hue = map(getTimepoint(event.time, [0, 10], path).y, [0, 100], [0, 360]); // to fit whole range of hue circle. function getTimepoint(time, path, from, to, loop) { return path.getPointAt(getTimeline(time, from, to, loop) * path.length % path.length); } // //detect 'visibilitychange' in modern browsers (e.g. changing tab) // // ref) https://stackoverflow.com/a/19519701 ("Detect if browser tab is active or user has switched away") // // ex) // // __visibility(function() { // // document.title = __visibility() ? 'Visible' : 'Not visible'; // // }); // var __visibility = (function() { // //investigate property list in the browser // var stateKey, eventKey, keys = { // hidden: "visibilitychange", // webkitHidden: "webkitvisibilitychange", // mozHidden: "mozvisibilitychange", // msHidden: "msvisibilitychange" // }; // //confirm what eventkey to be used // for (stateKey in keys) { // if (stateKey in document) { // eventKey = keys[stateKey]; // break; // } // } // //build the function and then let it be used // return function(c) { // //this function will have 2 usecases // // 1) simple call: __visibility() will return current visibility state // // 2) call with callback: __visibility(function(){}) will assign a callback for the event // //in this callback function use usecase #1 to get actual visibility. // if (c) { // document.addEventListener(eventKey, c); // } // return !document[stateKey]; // } // })(); // // //prepare a new master to be used // var master = new Tone.AmplitudeEnvelope({ // attack: 1, // decay: 0, // sustain: 1, // release: 1 // }).toMaster(); // // function onVisibilityChange() { // //document.title = __visibility() ? 'Visible' : 'Not visible'; // if (__visibility() == true) { // master.triggerAttack(); // } else { // master.triggerRelease(); // } // } // //register handler // __visibility(onVisibilityChange); // //initial state check // onVisibilityChange(); // // //a first touch getter! (only for iphone) // function getTheFirstTouchiOS() { // if (/iPad|iPhone|iPod/.test(navigator.userAgent) && !window.MSStream) { // var _silence = new Tone.Player({ // url: 'lib/_silence.wav', // onload: function() { // new paper.Path.Rectangle({ // point: [0, 0], // size: paper.view.size, // fillColor: 'white', // opacity: 0.8, // onClick: function() { // _silence.start(); // this.remove(); // } // }).bringToFront(); // } // }).toMaster(); // } // } //SVG helpers function SVGSymbol(uri) { return new Promise(function(resolve, reject) { paper.project.importSVG(uri, { expandShapes: true, onLoad: function(svg) { resolve(new paper.Symbol(svg)); } }); }); } function SVGSymbol_size1(uri) { return new Promise(function(resolve, reject) { paper.project.importSVG(uri, { expandShapes: true, onLoad: function(svg) { svg.scale(1 / svg.bounds.size.width); resolve(new paper.Symbol(svg)); } }); }); } function SVGImportSimple(uri) { return new Promise(function(resolve, reject) { paper.project.importSVG(uri, function(svg) { resolve(svg); }); }); } function SVGImport(uri) { return new Promise(function(resolve, reject) { paper.project.importSVG(uri, function(svg) { svg.parent.remove(svg); resolve(svg); }); }); } function SVGImport_size1(uri) { return new Promise(function(resolve, reject) { paper.project.importSVG(uri, function(svg) { svg.parent.remove(svg); svg.scale(1 / svg.bounds.size.width); resolve(svg); }); }); } function SVGNamedImport(uri, name) { return new Promise(function(resolve, reject) { paper.project.importSVG(uri, function(svg) { svg.remove(); var item = svg.getItem({ name: name }); item.applyMatrix = true; resolve(item); }); }); } function SVGNamedSymbol(uri, name) { return new Promise(function(resolve, reject) { paper.project.importSVG(uri, function(svg) { resolve(new paper.Symbol( svg.getItem({ name: name }) )); }); }); } function RasterImport(uri) { return new Promise(function(resolve, reject) { var raster = new paper.Raster(uri); raster.onLoad = function() { raster.remove(); raster.applyMatrix = true; resolve(raster); }; }); } function RasterImport_size1(uri) { return new Promise(function(resolve, reject) { var raster = new paper.Raster(uri); raster.onLoad = function() { raster.remove(); raster.applyMatrix = true; raster.scale(1 / raster.size.width); resolve(raster); }; }); } function AudioImport(url) { return new Promise(function(resolve, reject) { var audio = new Tone.Player(url, function() { resolve(audio); }).toMaster(); }); } // function AudioImport_p5(url) { // return new Promise(function(resolve, reject) { // var audio = new p5.SoundFile(url, function() { // resolve(audio); // }); // }); // } // // a unique string generator // // references: // https://gist.github.com/gordonbrander/2230317 // https://gist.github.com/6174/6062387 // https://stackoverflow.com/questions/105034/create-guid-uuid-in-javascript // function uniqueID() { return Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15); }