scar-mix-server/public/a1/lib/util.js

321 lines
9.8 KiB
JavaScript

// 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);
}