/* ---------------------------------------------------------------------------------------
This jsaSound Code is distributed under LGPL 3
Copyright (C) 2012 National University of Singapore
Inquiries: director@anclab.org
This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 3 of the License, or any later version.
This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNULesser General Public License for more details.
You should have received a copy of the GNU General Public License and GNU Lesser General Public License along with this program. If not, see <http://www.gnu.org/licenses/>
------------------------------------------------------------------------------------------*/
/**
* Provides some basic utilities
* @module utils.js
* @main utils.js
*/
/**
* @class utils
*
*/
define(
[],
function () {
var utils = {};
/**
* Converts db values in [-inf, 0] into "gain" values in [0,1]
* for example, 0 dB yields 1, -6 dB yields .5
* @method dB2Ratio
* @param {Number} i_dB
* @return {Number} gain value 10^(dB/20)
*/
utils.dB2Ratio = function (i_dB ){
return Math.pow(10.0, i_dB/20.0);
}
/**
* Get the requestAnimationFunction
* @method getRequestAnimationFrameFunc
* @return {Function} the correct version of the requestAnimationFrame funciton for the browser being used
*/
utils.getRequestAnimationFrameFunc = function() {
try {
return (window.requestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.msRequestAnimationFrame ||
(function (cb) {
setTimeout(cb, 1000/60);
}));
} catch (e) {
return undefined;
}
};
utils.relMouseCoords = function (event) {
var totalOffsetX = 0;
var totalOffsetY = 0;
var canvasX = 0;
var canvasY = 0;
var currentElement = this;
do {
totalOffsetX += currentElement.offsetLeft;
totalOffsetY += currentElement.offsetTop;
currentElement = currentElement.offsetParent;
} while (currentElement);
canvasX = event.pageX - totalOffsetX;
canvasY = event.pageY - totalOffsetY;
return {
"x": canvasX,
"y": canvasY
};
};
// Give the HTMLCanvasElement relative mouse coordinates {[0,1],[0,1]}
HTMLCanvasElement.prototype.relMouseCoords = utils.relMouseCoords;
//http://www.meredithdodge.com/2012/05/30/a-great-little-javascript-function-for-generating-random-gaussiannormalbell-curve-numbers/
/**
* Get a norally distributed random number
* @method nrand
* @param {Number} m mean
* @param {Number} sd standard deviation
* @return {Number} normally distributed random number
*/
utils.nrand = function (m, sd) {
var x1, x2, rad, y1;
do {
x1 = 2 * Math.random() - 1;
x2 = 2 * Math.random() - 1;
rad = x1 * x1 + x2 * x2;
} while (rad >= 1 || rad === 0);
var c = Math.sqrt(-2 * Math.log(rad) / rad);
return (x1 * c) * sd + m;
};
/**
* Convert midi note number (can be floating point) to a frequency value
* @method mtof
* @param {Number} m MIDI note number
* @return {Number} the frequency of the MIDI note number
*/
utils.mtof = function (m) {
return Math.pow(2, (m - 69) / 12) * 440;
};
/**
* Maps a domain of numbers [f1, f2] linearly onto a range [t1, t2]
* @method mapconstrain
* @param {Number} f1 first endpoint of domain
* @param {Number} f2 second endpoint of domain
* @param {Number} f1 first endpoint of range
* @param {Number} f1 second endpoint of range
* @param {Number} x number to map
* @return {Number} result of mapping x (not actually limited to range!)
*/
utils.mapconstrain = function (f1, f2, t1, t2, x) {
var raw = t1 + ((x - f1) / (f2 - f1)) * (t2 - t1);
return Math.max(t1, Math.min(raw, t2));
};
/**
* This is a rational function to approximate a tanh-like soft clipper. It is based on the pade-approximation of the tanh function with tweaked coefficients.
* The function is in the range x=-3..3 and outputs the range y=-1..1. Beyond this range the output must be clamped to -1..1.
* The first to derivatives of the function vanish at -3 and 3, so the transition to the hard clipped region is C2-continuous.
* http://stackoverflow.com/questions/6118028/fast-hyperbolic-tangent-approximation-in-javascript.
* @method rational_tanh
* @param {Number} x number to be mapped
* @return {Number} tanh(x)
*/
utils.rational_tanh = function (x) {
if (x < -3) {
return -1;
}
if (x > 3) {
return 1;
}
return x * (27 + x * x) / (27 + 9 * x * x);
};
utils.objForEach = function (object, func) {
var i;
for (i in object) {
if (object.hasOwnProperty(i)) {
func(object[i], i);
}
}
};
utils.objLength = function (object) {
var i, count = 0;
for (i in object) {
if (object.hasOwnProperty(i)) {
count += 1;
}
}
return count;
};
utils.isInteger = function(n){
return n===+n && n===(n|0);
};
utils.formatURL = function(i_url){
}
//--------------------------------------------------
// This writes to the local client sandboxed area. REally not very helpful.
//---------------------------------------------------
utils.saveToFile = function(data){
if (window.File && window.FileReader && window.FileList && window.Blob) {
window.requestFileSystem = window.requestFileSystem || window.webkitRequestFileSystem;
window.webkitStorageInfo.requestQuota(PERSISTENT, 100*1024, function(grantedBytes) {
window.requestFileSystem(PERSISTENT, grantedBytes, onInitFs, errorHandler);
}, function(e) {
console.log('Error', e);
});
} else {
alert('The File APIs are not fully supported in this browser.');
}
function onInitFs(fs) {
var fname = prompt("pset file name:", "model.pset")
console.log("saving to file " + fname);
fs.root.getFile(fname, {create: true}, function(fileEntry) {
// Create a FileWriter object for our FileEntry (log.txt).
fileEntry.createWriter(function(fileWriter) {
fileWriter.onwriteend = function(e) {
console.log('Write completed.');
};
fileWriter.onerror = function(e) {
console.log('Write failed: ' + e.toString());
};
// Create a new Blob and write it to log.txt.
var blob = new Blob([JSON.stringify(data)], {type: 'text/plain'});
fileWriter.write(blob);
}, errorHandler);
}, errorHandler);
}
}
var errorHandler = function(e) {
var msg = '';
switch (e.code) {
case FileError.QUOTA_EXCEEDED_ERR:
msg = 'QUOTA_EXCEEDED_ERR';
break;
case FileError.NOT_FOUND_ERR:
msg = 'NOT_FOUND_ERR';
break;
case FileError.SECURITY_ERR:
msg = 'SECURITY_ERR';
break;
case FileError.INVALID_MODIFICATION_ERR:
msg = 'INVALID_MODIFICATION_ERR';
break;
case FileError.INVALID_STATE_ERR:
msg = 'INVALID_STATE_ERR';
break;
default:
msg = 'Unknown Error';
break;
};
console.log('Error: ' + msg);
}
//--------------------------------------------------
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
//---------------------------------------------------
//------------------------------------------------------------------
// This takes a list of full-path filenames and splits them into an object with the name and path
// Used for generatingdrop-down selector for loading files
utils.filesToObjectList = function(fl){
objArray=[];
var tname;
for (var i=0;i<fl.length;i++){
tname=fl[i].substring(fl[i].lastIndexOf("/")+1, fl[i].lastIndexOf(".")) ;
objArray.push({"fileName": tname, "fullPath": fl[i]});
}
return objArray;
};
// Print out the array with brackets - for 2D arrarys, print each "sub" array on a separate line
Array.prototype.prettyString = function () {
var s="[";
var i;
for(i=0;i<this.length;i++){
if (Array.isArray(this[i])){
s+=this[i].prettyString();
if (i<(this.length-1)) s+=",\n";
} else{
s+= this[i].toString();
if (i<(this.length-1)) s+=", ";
}
}
s += "]";
return s;
}
Float32Array.prototype.sum = function(){
var s=0;
for(var i=0;i<this.length;i++){
s+=this[i];
}
return s;
}
Float32Array.prototype.scale = function(sval){
for(var i=0;i<this.length;i++){
this[i]=this[i]*sval;
}
}
Float32Array.prototype.rms = function(){
var retval=0;
for(var i=0;i<this.length;i++){
retval=(this[i]*this[i])/this.length;
}
return Math.sqrt(retval);
}
Float32Array.prototype.max = function(){
return Math.max.apply(null, this);
}
// used to decode the quesrystring in the URL
utils.getParameterByName = function (name) {
name = name.replace(/[\[]/, "\\\[").replace(/[\]]/, "\\\]");
var regex = new RegExp("[\\?&]" + name + "=([^&#]*)"),
results = regex.exec(location.search);
return results == null ? "" : decodeURIComponent(results[1].replace(/\+/g, " "));
}
utils.QueryStringToJSON = function() {
var pairs = location.search.slice(1).split('&');
var result = {};
pairs.forEach(function(pair) {
pair = pair.split('=');
result[pair[0]] = decodeURIComponent(pair[1] || '');
});
return JSON.parse(JSON.stringify(result));
}
utils.freesoundfix=function(i_url){
if (i_url.match(/freesound.org/) != null){
var sid = i_url.match("sounds/(.*)/download"); // array containing match and target substring
if (sid && sid.length>1){
console.log("freesound: " + "http://www.freesound.org/api/sounds/" + sid[1] + "/serve/?api_key=e2d5c11c3584432c95e7d4f81ff509e0");
return "http://www.freesound.org/api/sounds/" + sid[1] + "/serve/?api_key=e2d5c11c3584432c95e7d4f81ff509e0";
}
}
return i_url;
}
// see baseSM audioResourceManager
utils.loadAudioResource = function(i_url, config, i_onload){
var xhr = new XMLHttpRequest();
i_url = utils.freesoundfix(i_url);
buffLoaded = false;
xhr.open('GET', i_url, true);
xhr.responseType = 'arraybuffer';
xhr.onerror = function (e) {
console.log("utils.getAudioResource xhr.onload error.")
console.error(e);
};
xhr.onDecode=function(b){
i_onload(b);
}
xhr.onload = function () {
console.log("Sound(s) loaded");
config.audioContext.decodeAudioData(xhr.response, xhr.onDecode, xhr.onerror);
};
xhr.send();
}
//------------------------------------------------------------------------
// This is Douglas Crockfords "composing objects by parts" code from his book
utils.eventuality = function (that) {
var registry = {};
that.fire = function (event) {
// Fire an event on an object. The event can be either
// a string containing the name of the event or an
// object containing a type property containing the
// name of the event. Handlers registered by the 'on'
// method that match the event name will be invoked.
var array,
func,
handler,
i,
type = typeof event === 'string' ?
event : event.type;
// If an array of handlers exist for this event, then
// loop through it and execute the handlers in order.
if (registry.hasOwnProperty(type)) {
array = registry[type];
for (i = 0; i < array.length; i += 1) {
handler = array[i];
// A handler record contains a method and an optional
// array of parameters. If the method is a name, look
// up the function.
func = handler.method;
if (typeof func === 'string') {
func = this[func];
}
// Invoke a handler. If the record contained
// parameters, then pass them. Otherwise, pass the
// event object.
func.apply(this,
handler.parameters || [event]);
}
}
return this;
};
that.on = function (type, method, parameters) {
// Register an event. Make a handler record. Put it
// in a handler array, making one if it doesn't yet
// exist for this type.
var handler = {
method: method,
parameters: parameters
};
if (registry.hasOwnProperty(type)) {
registry[type].push(handler);
} else {
registry[type] = [handler];
}
return this;
};
return that;
}
return utils;
}
);