(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
typeof define === 'function' && define.amd ? define(factory) :
(global = global || self, global.AC = factory());
}(this, function () { 'use strict';
const WeightTranslation = (str) => {
const z = ["unweighted", 'notweighted', "flat", "none", "not", "notweighted", 'noweighting', 'uncorrected'];
const regex = /^(a|b|c|d|z)$|(?:^(?:db[-\s_\.\(]*)(a|b|c|d|z)[\)]*$)|(?:^(a|b|c|d|z)[-\s_\.]*weight(?:ed)?(?:ing)?$)/gmi;
const subst = `\$1\$2\$3`;
if (str.match(regex)) {
return str.replace(regex, subst).toLowerCase();
} else if (z.filter(
verbose => verbose === str.replace(/[\s\t\-\_]+/gmi, '').toLowerCase()).length != 0) {
return 'z';
} else {
const err = 'could not figure out what you meant by \'' + str + '\'';
console.error(err);
return null;
}
};
function R_a(f) {
let f2 = f * f;
let f4 = f2 * f2;
return (
(148693636 * f4) /
((f2 + 424.36) *
Math.sqrt((f2 + 11599.29) * (f2 + 544496.41)) *
(f2 + 148693636))
);
}
/** Calculates the A-weight values for the specified frequency/frequencies
* @function A
* @param {number|number[]} f frequency/frequencies
*/
function A(f) {
if (typeof f === "number") {
return 20 * Math.log10(R_a(f)) + 2.0;
}
else if (f instanceof Array) {
return f.map(function (freq) {
return 20 * Math.log10(R_a(freq)) + 2.0;
})
}
}
const pow2 = function pow2(x) {
return Math.pow(x, 2);
};
const pow3 = function pow3(x) {
return Math.pow(x, 3);
};
function R_b(f) {
return pow2(12194) * pow3(f) / ((pow2(f) + pow2(20.6)) * Math.sqrt(pow2(f) + pow2(158)) * (pow2(f) + pow2(12194)))
}
/** Calculates the B-weight values for the specified frequency/frequencies
* @function B
* @param {number|number[]} f frequency/frequencies
*/
function B(f) {
if (typeof f == "number")
return 20 * Math.log10(R_b(f)) + 0.17;
else if (typeof f == "object")
return f.map(function (freq) {
return 20 * Math.log10(R_b(freq)) + 0.17;
})
}
/* https://en.wikipedia.org/wiki/A-weighting#Function_realisation_of_some_common_weightings */
const pow2$1 = (x) => Math.pow(x, 2);
const Weight = {
A,
B,
R_c: (f) => (pow2$1(12194) * pow2$1(f)) / ((pow2$1(f) + pow2$1(20.6)) * (pow2$1(f) + pow2$1(12194))),
C: (f) => {
if (typeof f == "number")
return 20 * Math.log10(Weight.R_c(f)) + 0.06;
else if (typeof f == "object")
return f.map(freq => 20 * Math.log10(Weight.R_c(freq)) + 0.06);
},
h: (f) => (Math.pow((1037918.48 - f * f), 2) + 1080768.16 * f * f) / (Math.pow((9837328 - f * f), 2) + 11723776 * f * f),
R_d: (f) => ((f) / (6.8966888496476e-5)) * Math.sqrt(Weight.h(f) / ((f * f + 79919.29) * (f * f + 1345600))),
D: (f) => 20 * Math.log10(Weight.R_d(f)),
convert: (freq_db_pairs) => {
return new _WeightConverter(freq_db_pairs);
}
};
class _WeightConverter {
constructor(freq_db_pairs) {
this.frequencies = freq_db_pairs.map(fdb => fdb[0]);
this.dbs = freq_db_pairs.map(fdb => fdb[1]);
}
from(originalWeight) {
const ow = WeightTranslation(originalWeight);
if (ow !== null) {
switch (ow) {
case 'a':
const a = this.frequencies.map(f => Weight.A(f));
this.dbz = this.dbs.map((x, i) => x - a[i]);
break;
case 'b':
const b = this.frequencies.map(f => Weight.B(f));
this.dbz = this.dbs.map((x, i) => x - b[i]);
break;
case 'c':
const c = this.frequencies.map(f => Weight.C(f));
this.dbz = this.dbs.map((x, i) => x - c[i]);
break;
case 'd':
const d = this.frequencies.map(f => Weight.D(f));
this.dbz = this.dbs.map((x, i) => x - d[i]);
break;
case 'z':
this.dbz = this.dbs;
break;
default:
break;
}
return this;
} else {
return null;
}
}
to(desiredWeight) {
if (!this.dbz) {
const err = 'You much call .from() before calling .to()';
console.error(err);
return null;
} else {
const dw = WeightTranslation(desiredWeight);
if (dw !== null) {
switch (dw) {
case 'a':
const a = this.frequencies.map(f => Weight.A(f));
return this.dbz.map((x, i) => x + a[i]);
case 'b':
const b = this.frequencies.map(f => Weight.B(f));
return this.dbz.map((x, i) => x - b[i]);
case 'c':
const c = this.frequencies.map(f => Weight.C(f));
return this.dbz.map((x, i) => x - c[i]);
case 'd':
const d = this.frequencies.map(f => Weight.D(f));
return this.dbz.map((x, i) => x - d[i]);
case 'z':
return this.dbz;
default:
return null;
}
} else {
return null;
}
}
}
}
const Conversion = {
LpFromLw: (Lw, r = 1, Q = 1) => {
if (typeof Lw === "number")
Lw = [Lw];
return Lw.map(lw => lw - Math.abs(10 * Math.log10(Q / (4 * Math.PI * r * r))));
},
LnFromLp: (Lp, Ar, Ao = 108) => {
if (typeof Lp === "number")
Lp = [Lp];
return Lp.map(lp => lp - 10 * Math.log10(Ao / Ar));
}
};
var octave_bands = [{
"Lower": 11,
"Center": 16,
"Upper": 22
},
{
"Lower": 22,
"Center": 31.5,
"Upper": 44
},
{
"Lower": 44,
"Center": 63,
"Upper": 88
},
{
"Lower": 88,
"Center": 125,
"Upper": 177
},
{
"Lower": 177,
"Center": 250,
"Upper": 355
},
{
"Lower": 355,
"Center": 500,
"Upper": 710
},
{
"Lower": 710,
"Center": 1000,
"Upper": 1420
},
{
"Lower": 1420,
"Center": 2000,
"Upper": 2840
},
{
"Lower": 2840,
"Center": 4000,
"Upper": 5680
},
{
"Lower": 5680,
"Center": 8000,
"Upper": 11360
},
{
"Lower": 11360,
"Center": 16000,
"Upper": 22720
}
];
var third_octave_bands = [{
"Lower": 11.2,
"Center": 12.5,
"Upper": 14.1
},
{
"Lower": 14.1,
"Center": 16,
"Upper": 17.8
},
{
"Lower": 17.8,
"Center": 20,
"Upper": 22.4
},
{
"Lower": 22.4,
"Center": 25,
"Upper": 28.2
},
{
"Lower": 28.2,
"Center": 31.5,
"Upper": 35.5
},
{
"Lower": 35.5,
"Center": 40,
"Upper": 44.7
},
{
"Lower": 44.7,
"Center": 50,
"Upper": 56.2
},
{
"Lower": 56.2,
"Center": 63,
"Upper": 70.8
},
{
"Lower": 70.8,
"Center": 80,
"Upper": 89.1
},
{
"Lower": 89.1,
"Center": 100,
"Upper": 112
},
{
"Lower": 112,
"Center": 125,
"Upper": 141
},
{
"Lower": 141,
"Center": 160,
"Upper": 178
},
{
"Lower": 178,
"Center": 200,
"Upper": 224
},
{
"Lower": 224,
"Center": 250,
"Upper": 282
},
{
"Lower": 282,
"Center": 315,
"Upper": 355
},
{
"Lower": 355,
"Center": 400,
"Upper": 447
},
{
"Lower": 447,
"Center": 500,
"Upper": 562
},
{
"Lower": 562,
"Center": 630,
"Upper": 708
},
{
"Lower": 708,
"Center": 800,
"Upper": 891
},
{
"Lower": 891,
"Center": 1000,
"Upper": 1122
},
{
"Lower": 1122,
"Center": 1250,
"Upper": 1413
},
{
"Lower": 1413,
"Center": 1600,
"Upper": 1778
},
{
"Lower": 1778,
"Center": 2000,
"Upper": 2239
},
{
"Lower": 2239,
"Center": 2500,
"Upper": 2818
},
{
"Lower": 2818,
"Center": 3150,
"Upper": 3548
},
{
"Lower": 3548,
"Center": 4000,
"Upper": 4467
},
{
"Lower": 4467,
"Center": 5000,
"Upper": 5623
},
{
"Lower": 5623,
"Center": 6300,
"Upper": 7079
},
{
"Lower": 7079,
"Center": 8000,
"Upper": 8913
},
{
"Lower": 8913,
"Center": 10000,
"Upper": 11220
},
{
"Lower": 11220,
"Center": 12500,
"Upper": 14130
},
{
"Lower": 14130,
"Center": 16000,
"Upper": 17780
},
{
"Lower": 17780,
"Center": 20000,
"Upper": 22390
}
];
/** Returns the nominal octave band frequencies between a given range (inclusive)
* @function OctaveBands
* @param {number} [start] start frequency
* @param {number} [end] end frequency
*/
function OctaveBands(start, end) {
return octave_bands.map(x => x.Center).filter(x => x >= Number(start||0) && x <= Number(end||20000));
}
/** Returns the nominal third octave band frequencies between a given range (inclusive)
* @function ThirdOctaveBands
* @param {number} [start] start frequency
* @param {number} [end] end frequency
*/
function ThirdOctaveBands(start, end) {
return third_octave_bands.map(x => x.Center).filter(x => x >= Number(start||0) && x <= Number(end||20000));
}
/** Returns the lower band limit of a frequency band
* @function Flower
* @param {*} k inverse fraction (i.e. third = 3, sixth = 6, etc.)
* @param {*} fc center frequency
*/
function Flower(k, fc) {
if (typeof fc === "number")
fc = [fc];
return fc.map(f => f / Math.pow(2, 1 / (2 * k)));
}
/** Returns the upper band limit of a frequency band
* @function Fupper
* @param {*} k inverse fraction (i.e. third = 3, sixth = 6, etc.)
* @param {*} fc center frequency
*/
function Fupper(k, fc) {
if (typeof fc === "number")
fc = [fc];
return fc.map(f => f * Math.pow(2, 1 / (2 * k)));
}
const Bands = {
Octave: {
Nominal: octave_bands.map(x=>x.Center),
fromRange: (start, end) => octave_bands.map(x => x.Center).filter(x => x >= Number(start) && x <= Number(end)),
withLimits: octave_bands,
},
ThirdOctave: {
Nominal: third_octave_bands.map(x => x.Center),
fromRange: (start, end) => {
return third_octave_bands.map(x => x.Center).filter(x => x >= Number(start) && x <= Number(end));
},
withLimits: third_octave_bands
},
Flower: (k, fc) => {
if (typeof fc === "number")
fc = [fc];
return fc.map(f => f / Math.pow(2, 1 / (2 * k)));
},
Fupper: (k, fc) => {
if (typeof fc === "number")
fc = [fc];
return fc.map(f=>f * Math.pow(2, 1 / (2 * k)));
}
};
/**
* Returns the dB sum of every array passed in, rounded to an optional precision.
* @param {Number[]} dBs - An n-dimmensional array of numbers
* @param {Number} decimalPrecision - Number of decimals places to round the output to
* @returns {Number|Number[]} A number or an (n-1)-dimmensional array of numbers
*/
const dBsum = (dBs, decimalPrecision = 1) => {
let _sum_;
let precision = '1';
for (let i = 0; i < decimalPrecision; i++){
precision+="0";
} precision = Number(precision);
if (typeof dBs === "number") {
throw "dBsum requires an array, not a single number";
} else if (typeof dBs === "string") {
throw "dBsum requires an array, not a single string";
} else if (typeof dBs === "object") {
if (typeof dBs[0] === "number") {
_sum_ = Math.round(10 * Math.log10(dBs.map(x => Math.pow(10, x / 10)).reduce((acc, a) => acc + a))*precision)/precision;
} else if (typeof dBs[0] === "string") {
try {
_sum_ = Math.round(10 * Math.log10(dBs.map(x => Math.pow(10, Number(x) / 10)).reduce((acc, a) => acc + a)) * precision) / precision;
} catch (error) {
throw error
}
} else if (typeof dBs[0] === "object") {
try {
_sum_ = dBs.map(x => dBsum(x,decimalPrecision));
} catch (error) {
throw error
}
}
return _sum_;
} else {
return null;
}
};
const Transmission = {
NR: ({ TL, absorption,area, Lsource, Lreciever }) => {
if(Lsource && Lreciever){
return Lsource-Lreciever;
}
else if (TL && area && absorption) {
return TL + 10 * Math.log10(absorption / area);
}
else{
throw "Not enough input parameters"
}
},
TL: ({ tau, NR, area, absorption, Z, m, f }) => {
if (tau) {
return 10 * Math.log10(1 / tau);
} else if (NR && area && absorption) {
return NR + 10 * Math.log10(area/absorption);
} else if (m && f) {
return 20 * Math.log10(m) + 20 * Math.log10(f) - 47;
}
else {
throw "Not enough input parameters"
}
},
coefficient: ({ TL, Z, m, f, rho, L, c }) => {
if (TL) {
return 1 / (Math.pow(10, TL / 10));
}
},
compositeTL: (wallElements) => {
wallElements.forEach((elt, i, arr) => {
if (elt.TL) {
arr[i].tau = transmission.coefficient({ TL: elt.TL });
}
if (!elt.area) {
throw "need area";
}
});
let num = wallElements.map(elt => elt.area).reduce((a, b) => a + b);
let den = wallElements.map(elt => elt.tau * elt.area).reduce((a, b) => a + b);
return 10 * Math.log10(num / den);
}
};
const Properties = {
//todo provide material properties from page 59 of fundamentals of acoustics, table 2.3.
/**
* Calculate the speed of sound in a given material
* using Young's Modulus 'E' and density 'rho'
* @param E Young's Modulus
* @param rho Density
* @returns Speed of Sound
*/
SpeedOfSound: (E, rho) => {
return Math.sqrt(E / rho);
},
/**
* Calculate wave number 'k'
* @param omega Angular Frequency
* @param c Speed of Sound
* @param lambda wavelength
* @returns Wave Number 'k'
*/
WaveNumber: ({ omega, c, lambda }) => {
if (lambda) {
return 2 * Math.PI / lambda;
}
else if (omega && c) {
return omega / c;
}
else {
throw 'Not enough parameters!'
}
},
/**
* Calculate acoustic impedance 'Z'
* @param rho Density
* @param c Speed of Sound
* @returns Acoustic Impedance 'Z'
*/
Impedance: (rho, c) => {
return rho * c;
},
Air: {
SpeedOfSound: (props) => {
let temp = props.temp.value || 273.15;
let tempunits = props.temp.units || "K";
let returnunits = props.units || "m/s";
let c = 20.04 * Math.sqrt(temp);
switch (tempunits) {
case "K":
c = 20.04 * Math.sqrt(temp);
break;
case "C":
c = 20.04 * Math.sqrt(273.15+temp);
break;
case "F":
c = 20.04 * Math.sqrt((temp - 32) * (5 / 9) + 273.15);
break;
default:
break;
}
switch (returnunits) {
case "ft/s":
c *= 3.281;
break;
default:
break;
}
return c;
}
}
};
const mean = x => x.reduce((p, c) => p + c, 0) / x.length;
const std = (x, normalization) => {
let norm = normalization || 'unbiased';
let m = mean(x);
let n = [];
n['unbiased'] = -1;
n['uncorrected'] = 0;
n['biased'] = 1;
let s = Math.sqrt(x.map(u => Math.pow(u - m, 2)).reduce((p, c) => p + c) / (x.length + n[norm]));
return s;
};
/*
sort.js was not written by me,
check out fast-sort at https://www.npmjs.com/package/fast-sort
*/
const sorter = function (direction, a, b) {
if (a === b) return 0;
if (a < b) return -direction;
if (a == null) return 1;
if (b == null) return -1;
return direction;
};
/**
* stringSorter does not support nested property.
* For nested properties or value transformation (e.g toLowerCase) we should use functionSorter
* Based on benchmark testing using stringSorter is bit faster then using equivalent function sorter
* @example sort(users).asc('firstName')
*/
const stringSorter = function (direction, sortBy, a, b) {
return sorter(direction, a[sortBy], b[sortBy]);
};
/**
* @example sort(users).asc(p => p.address.city)
*/
const functionSorter = function (direction, sortBy, a, b) {
return sorter(direction, sortBy(a), sortBy(b));
};
/**
* Used when we have sorting by multyple properties and when current sorter is function
* @example sort(users).asc([p => p.address.city, p => p.firstName])
*/
const multiPropFunctionSorter = function (sortBy, thenBy, depth, direction, a, b) {
return multiPropEqualityHandler(sortBy(a), sortBy(b), thenBy, depth, direction, a, b);
};
/**
* Used when we have sorting by multiple properties and when current sorter is string
* @example sort(users).asc(['firstName', 'lastName'])
*/
const multiPropStringSorter = function (sortBy, thenBy, depth, direction, a, b) {
return multiPropEqualityHandler(a[sortBy], b[sortBy], thenBy, depth, direction, a, b);
};
/**
* Used with 'by' sorter when we have sorting in multiple direction
* @example sort(users).asc(['firstName', 'lastName'])
*/
const multiPropObjectSorter = function (sortByObj, thenBy, depth, _direction, a, b) {
const sortBy = sortByObj.asc || sortByObj.desc;
const direction = sortByObj.asc ? 1 : -1;
if (!sortBy) {
throw Error(`sort: Invalid 'by' sorting configuration.
Expecting object with 'asc' or 'desc' key`);
}
const multiSorter = getMultiPropertySorter(sortBy);
return multiSorter(sortBy, thenBy, depth, direction, a, b);
};
// >>> HELPERS <<<
/**
* Return multiProperty sort handler based on sortBy value
*/
const getMultiPropertySorter = function (sortBy) {
const type = typeof sortBy;
if (type === 'string') {
return multiPropStringSorter;
} else if (type === 'function') {
return multiPropFunctionSorter;
}
return multiPropObjectSorter;
};
const multiPropEqualityHandler = function (valA, valB, thenBy, depth, direction, a, b) {
if (valA === valB || (valA == null && valB == null)) {
if (thenBy.length > depth) {
const multiSorter = getMultiPropertySorter(thenBy[depth]);
return multiSorter(thenBy[depth], thenBy, depth + 1, direction, a, b);
}
return 0;
}
return sorter(direction, valA, valB);
};
/**
* Pick sorter based on provided sortBy value
*/
const sort = function (direction, ctx, sortBy) {
if (!Array.isArray(ctx)) return ctx;
// Unwrap sortBy if array with only 1 value
if (Array.isArray(sortBy) && sortBy.length < 2) {
[sortBy] = sortBy;
}
let _sorter;
if (!sortBy) {
_sorter = sorter.bind(undefined, direction);
} else if (typeof sortBy === 'string') {
_sorter = stringSorter.bind(undefined, direction, sortBy);
} else if (typeof sortBy === 'function') {
_sorter = functionSorter.bind(undefined, direction, sortBy);
} else {
_sorter = getMultiPropertySorter(sortBy[0])
.bind(undefined, sortBy.shift(), sortBy, 0, direction);
}
return ctx.sort(_sorter);
};
function sort$1 (ctx) {
return {
asc: (sortBy) => sort(1, ctx, sortBy),
desc: (sortBy) => sort(-1, ctx, sortBy),
by: (sortBy) => {
if (!Array.isArray(ctx)) return ctx;
if (!Array.isArray(sortBy)) {
throw Error(`sort: Invalid usage of 'by' sorter. Array syntax is required.
Did you mean to use 'asc' or 'desc' sorter instead?`);
}
// Unwrap sort by to faster path
if (sortBy.length === 1) {
const direction = sortBy[0].asc ? 1 : -1;
const sortOnProp = sortBy[0].asc || sortBy[0].desc;
if (!sortOnProp) {
throw Error(`sort: Invalid 'by' sorting configuration.
Expecting object with 'asc' or 'desc' key`);
}
return sort(direction, ctx, sortOnProp);
}
const _sorter = multiPropObjectSorter.bind(undefined, sortBy.shift(), sortBy, 0, undefined);
return ctx.sort(_sorter);
}
};
}
/**
* @function RoomModes
* @description Calculates the modal frequencies of a room of specified dimmensions
* @param {Object} params - Solver parameters
* @param {Number} params.length - Room length (default units of feet)
* @param {Number} params.width - Room width (default units of feet)
* @param {Number} params.height - Room height (default units of feet)
* @param {String} [params.units] - Can be either "english" for feet, or "si" for meters. Defaults to "english"
* @param {Number} [params.c] - Speed of sound in "ft/s" for "english", or "m/s" for "si"
* @param {Number[]} [params.frequencyRange] - Frequency limits as an array (i.e. [minFrequency, maxFrequency]). Defaults to [16, 500];
* @param {String} [params.stdNormalization] - Normalization for standard deviation calculation. Can be 'unbiased' (default), 'uncorrected', or 'biased';
* @param {String} [params.overlapPenalty] - Penalty for overlapping modes (used to calculate score). Can be '*' (default), '+', or 'none'.
* @param {Number} [params.overlapWidth] - Used to calculate score (i.e. overlapping = nextFrequency < overlapWidth * currentFrequency). Defaults to 0.1;
* @param {Number} [params.modeLimit] - Used to calculate the maximum mode number. Defaults to 15 (which should be plenty)
* @param {Boolean} [params.sortFrequencies] - Whether or not the frequencies should be sorted. Defaults to true
* @param {Boolean} [params.sortBonello] - Whether or not the bonello data should be sorted. Defaults to true
*/
const RoomModes = (params) => {
let units = params.units || "english";
let c = params.c || Properties.Air.SpeedOfSound({
temp: {
value: 70,
units: "F"
},
units: "ft/s"
});
let length = params.length;
let width = params.width;
let height = params.height;
let freqlimits = params.frequencyRange || [16, 500];
let stdNormalization = params.stdNormalization || 'unbiased';
let overlapPenalty = params.overlapPenalty || '*';
let overlapWidth = params.overlapWidth || 0.1;
let modeLimit = params.modeLimit || 15;
let sortFrequencies = params.sortFrequencies || true;
let sortBonello = params.sortBonello || true;
let bands = Bands.ThirdOctave.withLimits;
let N = [];
for (let i = 0; i <= modeLimit; i++) {
for (let j = 0; j <= modeLimit; j++) {
for (let k = 0; k <= modeLimit; k++) {
N.push([i, j, k]);
}
}
}
N = N.splice(1);
let freq = [];
const getBand = (f, limit = "Center") => {
let _band;
for (let b = 0; b < bands.length; b++) {
if (f >= bands[b].Lower && f < bands[b].Upper) {
_band = bands[b][limit];
}
}
return _band;
};
const ModeTypes = ["Oblique", "Tangential", "Axial", "Unknown"];
const derivative = (arr) => {
let d = [];
for (let i = 0; i < arr.length - 1; i++) {
d.push(arr[i + 1] - arr[i]);
}
return d;
};
for (let i = 0; i < N.length; i++){
let n = N[i];
let f = c / 2 * Math.sqrt(Math.pow(n[0] / length, 2) + Math.pow(n[1] / width, 2) + Math.pow(n[2] / height, 2));
if (f <= freqlimits[1]) {
if (f >= freqlimits[0]) {
let modeIndex = n.filter(x => x == 0).length;
freq.push({
frequency: f,
mode: n,
modeType: ModeTypes[modeIndex],
modeTypeNumber: modeIndex + 1,
band: getBand(f)
});
}
}
}
let bonelloBands = [...new Set(freq.map(x => x.band))];
let bonello = bonelloBands.map(freq_band => {
return {
band: freq_band,
count: freq.filter(f => f.band == freq_band).length
}
});
if (sortFrequencies) freq = sort$1(freq).asc(u => u.frequency);
if (sortBonello) bonello = sort$1(bonello).asc(u => u.band);
let stdDifference = std(derivative(freq.map(x => x.frequency)), stdNormalization);
let overlapCount = 0;
for (let i = 1; i < freq.length - 1; i++) {
if (freq[i + 1].frequency - freq[i].frequency < overlapWidth * freq[i].frequency) {
overlapCount++;
}
}
let score;
switch (overlapPenalty) {
case "+":
score = stdDifference + Math.log10(overlapCount + 1);
break;
case "*":
score = stdDifference * Math.log10(overlapCount + 1);
break;
case "none":
score = stdDifference;
break;
default:
score = stdDifference;
break;
}
return {
length: Number(length.toFixed(2)),
width: Number(width.toFixed(2)),
height: Number(height.toFixed(2)),
modes: freq,
bonello: bonello,
score: score,
overlapCount: overlapCount
};
};
const buffer = (arr, n, fill) => {
fill = fill || false;
const N = Math.ceil(arr.length / n);
if (fill) {
return (
Array
.from(Array(N), (_, i) => arr.slice(i * n, i * n + n))
.map(x => x.length == n ? x : x.concat(new Array(n - x.length).fill(0)))
)
}
else {
return (
Array
.from(Array(N), (_, i) => arr.slice(i * n, i * n + n))
)
}
};
const pref = {
value: 2e-5,
units: 'Pa'
};
const Wref = {
value: 1e-12,
units: 'W'
};
const Iref = {
value: 1e-12,
units: 'W/m2'
};
/**
* lodash (Custom Build) <https://lodash.com/>
* Build: `lodash modularize exports="npm" -o ./`
* Copyright jQuery Foundation and other contributors <https://jquery.org/>
* Released under MIT license <https://lodash.com/license>
* Based on Underscore.js 1.8.3 <http://underscorejs.org/LICENSE>
* Copyright Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
*/
/** Used as references for various `Number` constants. */
var MAX_SAFE_INTEGER = 9007199254740991;
/** `Object#toString` result references. */
var argsTag = '[object Arguments]',
funcTag = '[object Function]',
genTag = '[object GeneratorFunction]';
/** Used to detect unsigned integer values. */
var reIsUint = /^(?:0|[1-9]\d*)$/;
/**
* The base implementation of `_.times` without support for iteratee shorthands
* or max array length checks.
*
* @private
* @param {number} n The number of times to invoke `iteratee`.
* @param {Function} iteratee The function invoked per iteration.
* @returns {Array} Returns the array of results.
*/
function baseTimes(n, iteratee) {
var index = -1,
result = Array(n);
while (++index < n) {
result[index] = iteratee(index);
}
return result;
}
/**
* Creates a unary function that invokes `func` with its argument transformed.
*
* @private
* @param {Function} func The function to wrap.
* @param {Function} transform The argument transform.
* @returns {Function} Returns the new function.
*/
function overArg(func, transform) {
return function(arg) {
return func(transform(arg));
};
}
/** Used for built-in method references. */
var objectProto = Object.prototype;
/** Used to check objects for own properties. */
var hasOwnProperty = objectProto.hasOwnProperty;
/**
* Used to resolve the
* [`toStringTag`](http://ecma-international.org/ecma-262/7.0/#sec-object.prototype.tostring)
* of values.
*/
var objectToString = objectProto.toString;
/** Built-in value references. */
var propertyIsEnumerable = objectProto.propertyIsEnumerable;
/* Built-in method references for those with the same name as other `lodash` methods. */
var nativeKeys = overArg(Object.keys, Object);
/**
* Creates an array of the enumerable property names of the array-like `value`.
*
* @private
* @param {*} value The value to query.
* @param {boolean} inherited Specify returning inherited property names.
* @returns {Array} Returns the array of property names.
*/
function arrayLikeKeys(value, inherited) {
// Safari 8.1 makes `arguments.callee` enumerable in strict mode.
// Safari 9 makes `arguments.length` enumerable in strict mode.
var result = (isArray(value) || isArguments(value))
? baseTimes(value.length, String)
: [];
var length = result.length,
skipIndexes = !!length;
for (var key in value) {
if ((inherited || hasOwnProperty.call(value, key)) &&
!(skipIndexes && (key == 'length' || isIndex(key, length)))) {
result.push(key);
}
}
return result;
}
/**
* The base implementation of `_.keys` which doesn't treat sparse arrays as dense.
*
* @private
* @param {Object} object The object to query.
* @returns {Array} Returns the array of property names.
*/
function baseKeys(object) {
if (!isPrototype(object)) {
return nativeKeys(object);
}
var result = [];
for (var key in Object(object)) {
if (hasOwnProperty.call(object, key) && key != 'constructor') {
result.push(key);
}
}
return result;
}
/**
* Checks if `value` is a valid array-like index.
*
* @private
* @param {*} value The value to check.
* @param {number} [length=MAX_SAFE_INTEGER] The upper bounds of a valid index.
* @returns {boolean} Returns `true` if `value` is a valid index, else `false`.
*/
function isIndex(value, length) {
length = length == null ? MAX_SAFE_INTEGER : length;
return !!length &&
(typeof value == 'number' || reIsUint.test(value)) &&
(value > -1 && value % 1 == 0 && value < length);
}
/**
* Checks if `value` is likely a prototype object.
*
* @private
* @param {*} value The value to check.
* @returns {boolean} Returns `true` if `value` is a prototype, else `false`.
*/
function isPrototype(value) {
var Ctor = value && value.constructor,
proto = (typeof Ctor == 'function' && Ctor.prototype) || objectProto;
return value === proto;
}
/**
* Checks if `value` is likely an `arguments` object.
*
* @static
* @memberOf _
* @since 0.1.0
* @category Lang
* @param {*} value The value to check.
* @returns {boolean} Returns `true` if `value` is an `arguments` object,
* else `false`.
* @example
*
* _.isArguments(function() { return arguments; }());
* // => true
*
* _.isArguments([1, 2, 3]);
* // => false
*/
function isArguments(value) {
// Safari 8.1 makes `arguments.callee` enumerable in strict mode.
return isArrayLikeObject(value) && hasOwnProperty.call(value, 'callee') &&
(!propertyIsEnumerable.call(value, 'callee') || objectToString.call(value) == argsTag);
}
/**
* Checks if `value` is classified as an `Array` object.
*
* @static
* @memberOf _
* @since 0.1.0
* @category Lang
* @param {*} value The value to check.
* @returns {boolean} Returns `true` if `value` is an array, else `false`.
* @example
*
* _.isArray([1, 2, 3]);
* // => true
*
* _.isArray(document.body.children);
* // => false
*
* _.isArray('abc');
* // => false
*
* _.isArray(_.noop);
* // => false
*/
var isArray = Array.isArray;
/**
* Checks if `value` is array-like. A value is considered array-like if it's
* not a function and has a `value.length` that's an integer greater than or
* equal to `0` and less than or equal to `Number.MAX_SAFE_INTEGER`.
*
* @static
* @memberOf _
* @since 4.0.0
* @category Lang
* @param {*} value The value to check.
* @returns {boolean} Returns `true` if `value` is array-like, else `false`.
* @example
*
* _.isArrayLike([1, 2, 3]);
* // => true
*
* _.isArrayLike(document.body.children);
* // => true
*
* _.isArrayLike('abc');
* // => true
*
* _.isArrayLike(_.noop);
* // => false
*/
function isArrayLike(value) {
return value != null && isLength(value.length) && !isFunction(value);
}
/**
* This method is like `_.isArrayLike` except that it also checks if `value`
* is an object.
*
* @static
* @memberOf _
* @since 4.0.0
* @category Lang
* @param {*} value The value to check.
* @returns {boolean} Returns `true` if `value` is an array-like object,
* else `false`.
* @example
*
* _.isArrayLikeObject([1, 2, 3]);
* // => true
*
* _.isArrayLikeObject(document.body.children);
* // => true
*
* _.isArrayLikeObject('abc');
* // => false
*
* _.isArrayLikeObject(_.noop);
* // => false
*/
function isArrayLikeObject(value) {
return isObjectLike(value) && isArrayLike(value);
}
/**
* Checks if `value` is classified as a `Function` object.
*
* @static
* @memberOf _
* @since 0.1.0
* @category Lang
* @param {*} value The value to check.
* @returns {boolean} Returns `true` if `value` is a function, else `false`.
* @example
*
* _.isFunction(_);
* // => true
*
* _.isFunction(/abc/);
* // => false
*/
function isFunction(value) {
// The use of `Object#toString` avoids issues with the `typeof` operator
// in Safari 8-9 which returns 'object' for typed array and other constructors.
var tag = isObject(value) ? objectToString.call(value) : '';
return tag == funcTag || tag == genTag;
}
/**
* Checks if `value` is a valid array-like length.
*
* **Note:** This method is loosely based on
* [`ToLength`](http://ecma-international.org/ecma-262/7.0/#sec-tolength).
*
* @static
* @memberOf _
* @since 4.0.0
* @category Lang
* @param {*} value The value to check.
* @returns {boolean} Returns `true` if `value` is a valid length, else `false`.
* @example
*
* _.isLength(3);
* // => true
*
* _.isLength(Number.MIN_VALUE);
* // => false
*
* _.isLength(Infinity);
* // => false
*
* _.isLength('3');
* // => false
*/
function isLength(value) {
return typeof value == 'number' &&
value > -1 && value % 1 == 0 && value <= MAX_SAFE_INTEGER;
}
/**
* Checks if `value` is the
* [language type](http://www.ecma-international.org/ecma-262/7.0/#sec-ecmascript-language-types)
* of `Object`. (e.g. arrays, functions, objects, regexes, `new Number(0)`, and `new String('')`)
*
* @static
* @memberOf _
* @since 0.1.0
* @category Lang
* @param {*} value The value to check.
* @returns {boolean} Returns `true` if `value` is an object, else `false`.
* @example
*
* _.isObject({});
* // => true
*
* _.isObject([1, 2, 3]);
* // => true
*
* _.isObject(_.noop);
* // => true
*
* _.isObject(null);
* // => false
*/
function isObject(value) {
var type = typeof value;
return !!value && (type == 'object' || type == 'function');
}
/**
* Checks if `value` is object-like. A value is object-like if it's not `null`
* and has a `typeof` result of "object".
*
* @static
* @memberOf _
* @since 4.0.0
* @category Lang
* @param {*} value The value to check.
* @returns {boolean} Returns `true` if `value` is object-like, else `false`.
* @example
*
* _.isObjectLike({});
* // => true
*
* _.isObjectLike([1, 2, 3]);
* // => true
*
* _.isObjectLike(_.noop);
* // => false
*
* _.isObjectLike(null);
* // => false
*/
function isObjectLike(value) {
return !!value && typeof value == 'object';
}
/**
* Creates an array of the own enumerable property names of `object`.
*
* **Note:** Non-object values are coerced to objects. See the
* [ES spec](http://ecma-international.org/ecma-262/7.0/#sec-object.keys)
* for more details.
*
* @static
* @since 0.1.0
* @memberOf _
* @category Object
* @param {Object} object The object to query.
* @returns {Array} Returns the array of property names.
* @example
*
* function Foo() {
* this.a = 1;
* this.b = 2;
* }
*
* Foo.prototype.c = 3;
*
* _.keys(new Foo);
* // => ['a', 'b'] (iteration order is not guaranteed)
*
* _.keys('hi');
* // => ['0', '1']
*/
function keys(object) {
return isArrayLike(object) ? arrayLikeKeys(object) : baseKeys(object);
}
var lodash_keys = keys;
/**
* lodash (Custom Build) <https://lodash.com/>
* Build: `lodash modularize exports="npm" -o ./`
* Copyright jQuery Foundation and other contributors <https://jquery.org/>
* Released under MIT license <https://lodash.com/license>
* Based on Underscore.js 1.8.3 <http://underscorejs.org/LICENSE>
* Copyright Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
*/
/** Used as references for various `Number` constants. */
var MAX_SAFE_INTEGER$1 = 9007199254740991;
/** `Object#toString` result references. */
var argsTag$1 = '[object Arguments]',
funcTag$1 = '[object Function]',
genTag$1 = '[object GeneratorFunction]';
/** Used to detect unsigned integer values. */
var reIsUint$1 = /^(?:0|[1-9]\d*)$/;
/**
* A specialized version of `_.forEach` for arrays without support for
* iteratee shorthands.
*
* @private
* @param {Array} [array] The array to iterate over.
* @param {Function} iteratee The function invoked per iteration.
* @returns {Array} Returns `array`.
*/
function arrayEach(array, iteratee) {
var index = -1,
length = array ? array.length : 0;
while (++index < length) {
if (iteratee(array[index], index, array) === false) {
break;
}
}
return array;
}
/**
* The base implementation of `_.times` without support for iteratee shorthands
* or max array length checks.
*
* @private
* @param {number} n The number of times to invoke `iteratee`.
* @param {Function} iteratee The function invoked per iteration.
* @returns {Array} Returns the array of results.
*/
function baseTimes$1(n, iteratee) {
var index = -1,
result = Array(n);
while (++index < n) {
result[index] = iteratee(index);
}
return result;
}
/**
* Creates a unary function that invokes `func` with its argument transformed.
*
* @private
* @param {Function} func The function to wrap.
* @param {Function} transform The argument transform.
* @returns {Function} Returns the new function.
*/
function overArg$1(func, transform) {
return function(arg) {
return func(transform(arg));
};
}
/** Used for built-in method references. */
var objectProto$1 = Object.prototype;
/** Used to check objects for own properties. */
var hasOwnProperty$1 = objectProto$1.hasOwnProperty;
/**
* Used to resolve the
* [`toStringTag`](http://ecma-international.org/ecma-262/7.0/#sec-object.prototype.tostring)
* of values.
*/
var objectToString$1 = objectProto$1.toString;
/** Built-in value references. */
var propertyIsEnumerable$1 = objectProto$1.propertyIsEnumerable;
/* Built-in method references for those with the same name as other `lodash` methods. */
var nativeKeys$1 = overArg$1(Object.keys, Object);
/**
* Creates an array of the enumerable property names of the array-like `value`.
*
* @private
* @param {*} value The value to query.
* @param {boolean} inherited Specify returning inherited property names.
* @returns {Array} Returns the array of property names.
*/
function arrayLikeKeys$1(value, inherited) {
// Safari 8.1 makes `arguments.callee` enumerable in strict mode.
// Safari 9 makes `arguments.length` enumerable in strict mode.
var result = (isArray$1(value) || isArguments$1(value))
? baseTimes$1(value.length, String)
: [];
var length = result.length,
skipIndexes = !!length;
for (var key in value) {
if ((inherited || hasOwnProperty$1.call(value, key)) &&
!(skipIndexes && (key == 'length' || isIndex$1(key, length)))) {
result.push(key);
}
}
return result;
}
/**
* The base implementation of `_.forEach` without support for iteratee shorthands.
*
* @private
* @param {Array|Object} collection The collection to iterate over.
* @param {Function} iteratee The function invoked per iteration.
* @returns {Array|Object} Returns `collection`.
*/
var baseEach = createBaseEach(baseForOwn);
/**
* The base implementation of `baseForOwn` which iterates over `object`
* properties returned by `keysFunc` and invokes `iteratee` for each property.
* Iteratee functions may exit iteration early by explicitly returning `false`.
*
* @private
* @param {Object} object The object to iterate over.
* @param {Function} iteratee The function invoked per iteration.
* @param {Function} keysFunc The function to get the keys of `object`.
* @returns {Object} Returns `object`.
*/
var baseFor = createBaseFor();
/**
* The base implementation of `_.forOwn` without support for iteratee shorthands.
*
* @private
* @param {Object} object The object to iterate over.
* @param {Function} iteratee The function invoked per iteration.
* @returns {Object} Returns `object`.
*/
function baseForOwn(object, iteratee) {
return object && baseFor(object, iteratee, keys$1);
}
/**
* The base implementation of `_.keys` which doesn't treat sparse arrays as dense.
*
* @private
* @param {Object} object The object to query.
* @returns {Array} Returns the array of property names.
*/
function baseKeys$1(object) {
if (!isPrototype$1(object)) {
return nativeKeys$1(object);
}
var result = [];
for (var key in Object(object)) {
if (hasOwnProperty$1.call(object, key) && key != 'constructor') {
result.push(key);
}
}
return result;
}
/**
* Creates a `baseEach` or `baseEachRight` function.
*
* @private
* @param {Function} eachFunc The function to iterate over a collection.
* @param {boolean} [fromRight] Specify iterating from right to left.
* @returns {Function} Returns the new base function.
*/
function createBaseEach(eachFunc, fromRight) {
return function(collection, iteratee) {
if (collection == null) {
return collection;
}
if (!isArrayLike$1(collection)) {
return eachFunc(collection, iteratee);
}
var length = collection.length,
index = fromRight ? length : -1,
iterable = Object(collection);
while ((fromRight ? index-- : ++index < length)) {
if (iteratee(iterable[index], index, iterable) === false) {
break;
}
}
return collection;
};
}
/**
* Creates a base function for methods like `_.forIn` and `_.forOwn`.
*
* @private
* @param {boolean} [fromRight] Specify iterating from right to left.
* @returns {Function} Returns the new base function.
*/
function createBaseFor(fromRight) {
return function(object, iteratee, keysFunc) {
var index = -1,
iterable = Object(object),
props = keysFunc(object),
length = props.length;
while (length--) {
var key = props[fromRight ? length : ++index];
if (iteratee(iterable[key], key, iterable) === false) {
break;
}
}
return object;
};
}
/**
* Checks if `value` is a valid array-like index.
*
* @private
* @param {*} value The value to check.
* @param {number} [length=MAX_SAFE_INTEGER] The upper bounds of a valid index.
* @returns {boolean} Returns `true` if `value` is a valid index, else `false`.
*/
function isIndex$1(value, length) {
length = length == null ? MAX_SAFE_INTEGER$1 : length;
return !!length &&
(typeof value == 'number' || reIsUint$1.test(value)) &&
(value > -1 && value % 1 == 0 && value < length);
}
/**
* Checks if `value` is likely a prototype object.
*
* @private
* @param {*} value The value to check.
* @returns {boolean} Returns `true` if `value` is a prototype, else `false`.
*/
function isPrototype$1(value) {
var Ctor = value && value.constructor,
proto = (typeof Ctor == 'function' && Ctor.prototype) || objectProto$1;
return value === proto;
}
/**
* Iterates over elements of `collection` and invokes `iteratee` for each element.
* The iteratee is invoked with three arguments: (value, index|key, collection).
* Iteratee functions may exit iteration early by explicitly returning `false`.
*
* **Note:** As with other "Collections" methods, objects with a "length"
* property are iterated like arrays. To avoid this behavior use `_.forIn`
* or `_.forOwn` for object iteration.
*
* @static
* @memberOf _
* @since 0.1.0
* @alias each
* @category Collection
* @param {Array|Object} collection The collection to iterate over.
* @param {Function} [iteratee=_.identity] The function invoked per iteration.
* @returns {Array|Object} Returns `collection`.
* @see _.forEachRight
* @example
*
* _([1, 2]).forEach(function(value) {
* console.log(value);
* });
* // => Logs `1` then `2`.
*
* _.forEach({ 'a': 1, 'b': 2 }, function(value, key) {
* console.log(key);
* });
* // => Logs 'a' then 'b' (iteration order is not guaranteed).
*/
function forEach(collection, iteratee) {
var func = isArray$1(collection) ? arrayEach : baseEach;
return func(collection, typeof iteratee == 'function' ? iteratee : identity);
}
/**
* Checks if `value` is likely an `arguments` object.
*
* @static
* @memberOf _
* @since 0.1.0
* @category Lang
* @param {*} value The value to check.
* @returns {boolean} Returns `true` if `value` is an `arguments` object,
* else `false`.
* @example
*
* _.isArguments(function() { return arguments; }());
* // => true
*
* _.isArguments([1, 2, 3]);
* // => false
*/
function isArguments$1(value) {
// Safari 8.1 makes `arguments.callee` enumerable in strict mode.
return isArrayLikeObject$1(value) && hasOwnProperty$1.call(value, 'callee') &&
(!propertyIsEnumerable$1.call(value, 'callee') || objectToString$1.call(value) == argsTag$1);
}
/**
* Checks if `value` is classified as an `Array` object.
*
* @static
* @memberOf _
* @since 0.1.0
* @category Lang
* @param {*} value The value to check.
* @returns {boolean} Returns `true` if `value` is an array, else `false`.
* @example
*
* _.isArray([1, 2, 3]);
* // => true
*
* _.isArray(document.body.children);
* // => false
*
* _.isArray('abc');
* // => false
*
* _.isArray(_.noop);
* // => false
*/
var isArray$1 = Array.isArray;
/**
* Checks if `value` is array-like. A value is considered array-like if it's
* not a function and has a `value.length` that's an integer greater than or
* equal to `0` and less than or equal to `Number.MAX_SAFE_INTEGER`.
*
* @static
* @memberOf _
* @since 4.0.0
* @category Lang
* @param {*} value The value to check.
* @returns {boolean} Returns `true` if `value` is array-like, else `false`.
* @example
*
* _.isArrayLike([1, 2, 3]);
* // => true
*
* _.isArrayLike(document.body.children);
* // => true
*
* _.isArrayLike('abc');
* // => true
*
* _.isArrayLike(_.noop);
* // => false
*/
function isArrayLike$1(value) {
return value != null && isLength$1(value.length) && !isFunction$1(value);
}
/**
* This method is like `_.isArrayLike` except that it also checks if `value`
* is an object.
*
* @static
* @memberOf _
* @since 4.0.0
* @category Lang
* @param {*} value The value to check.
* @returns {boolean} Returns `true` if `value` is an array-like object,
* else `false`.
* @example
*
* _.isArrayLikeObject([1, 2, 3]);
* // => true
*
* _.isArrayLikeObject(document.body.children);
* // => true
*
* _.isArrayLikeObject('abc');
* // => false
*
* _.isArrayLikeObject(_.noop);
* // => false
*/
function isArrayLikeObject$1(value) {
return isObjectLike$1(value) && isArrayLike$1(value);
}
/**
* Checks if `value` is classified as a `Function` object.
*
* @static
* @memberOf _
* @since 0.1.0
* @category Lang
* @param {*} value The value to check.
* @returns {boolean} Returns `true` if `value` is a function, else `false`.
* @example
*
* _.isFunction(_);
* // => true
*
* _.isFunction(/abc/);
* // => false
*/
function isFunction$1(value) {
// The use of `Object#toString` avoids issues with the `typeof` operator
// in Safari 8-9 which returns 'object' for typed array and other constructors.
var tag = isObject$1(value) ? objectToString$1.call(value) : '';
return tag == funcTag$1 || tag == genTag$1;
}
/**
* Checks if `value` is a valid array-like length.
*
* **Note:** This method is loosely based on
* [`ToLength`](http://ecma-international.org/ecma-262/7.0/#sec-tolength).
*
* @static
* @memberOf _
* @since 4.0.0
* @category Lang
* @param {*} value The value to check.
* @returns {boolean} Returns `true` if `value` is a valid length, else `false`.
* @example
*
* _.isLength(3);
* // => true
*
* _.isLength(Number.MIN_VALUE);
* // => false
*
* _.isLength(Infinity);
* // => false
*
* _.isLength('3');
* // => false
*/
function isLength$1(value) {
return typeof value == 'number' &&
value > -1 && value % 1 == 0 && value <= MAX_SAFE_INTEGER$1;
}
/**
* Checks if `value` is the
* [language type](http://www.ecma-international.org/ecma-262/7.0/#sec-ecmascript-language-types)
* of `Object`. (e.g. arrays, functions, objects, regexes, `new Number(0)`, and `new String('')`)
*
* @static
* @memberOf _
* @since 0.1.0
* @category Lang
* @param {*} value The value to check.
* @returns {boolean} Returns `true` if `value` is an object, else `false`.
* @example
*
* _.isObject({});
* // => true
*
* _.isObject([1, 2, 3]);
* // => true
*
* _.isObject(_.noop);
* // => true
*
* _.isObject(null);
* // => false
*/
function isObject$1(value) {
var type = typeof value;
return !!value && (type == 'object' || type == 'function');
}
/**
* Checks if `value` is object-like. A value is object-like if it's not `null`
* and has a `typeof` result of "object".
*
* @static
* @memberOf _
* @since 4.0.0
* @category Lang
* @param {*} value The value to check.
* @returns {boolean} Returns `true` if `value` is object-like, else `false`.
* @example
*
* _.isObjectLike({});
* // => true
*
* _.isObjectLike([1, 2, 3]);
* // => true
*
* _.isObjectLike(_.noop);
* // => false
*
* _.isObjectLike(null);
* // => false
*/
function isObjectLike$1(value) {
return !!value && typeof value == 'object';
}
/**
* Creates an array of the own enumerable property names of `object`.
*
* **Note:** Non-object values are coerced to objects. See the
* [ES spec](http://ecma-international.org/ecma-262/7.0/#sec-object.keys)
* for more details.
*
* @static
* @since 0.1.0
* @memberOf _
* @category Object
* @param {Object} object The object to query.
* @returns {Array} Returns the array of property names.
* @example
*
* function Foo() {
* this.a = 1;
* this.b = 2;
* }
*
* Foo.prototype.c = 3;
*
* _.keys(new Foo);
* // => ['a', 'b'] (iteration order is not guaranteed)
*
* _.keys('hi');
* // => ['0', '1']
*/
function keys$1(object) {
return isArrayLike$1(object) ? arrayLikeKeys$1(object) : baseKeys$1(object);
}
/**
* This method returns the first argument it receives.
*
* @static
* @since 0.1.0
* @memberOf _
* @category Util
* @param {*} value Any value.
* @returns {*} Returns `value`.
* @example
*
* var object = { 'a': 1 };
*
* console.log(_.identity(object) === object);
* // => true
*/
function identity(value) {
return value;
}
var lodash_foreach = forEach;
var metric, imperial;
metric = {
mm: {
name: {
singular: 'Millimeter',
plural: 'Millimeters'
},
to_anchor: 1 / 1000
},
cm: {
name: {
singular: 'Centimeter',
plural: 'Centimeters'
},
to_anchor: 1 / 100
},
m: {
name: {
singular: 'Meter',
plural: 'Meters'
},
to_anchor: 1
},
km: {
name: {
singular: 'Kilometer',
plural: 'Kilometers'
},
to_anchor: 1000
}
};
imperial = {
'in': {
name: {
singular: 'Inch',
plural: 'Inches'
},
to_anchor: 1 / 12
},
yd: {
name: {
singular: 'Yard',
plural: 'Yards'
},
to_anchor: 3
},
'ft-us': {
name: {
singular: 'US Survey Foot',
plural: 'US Survey Feet'
},
to_anchor: 1.000002
},
ft: {
name: {
singular: 'Foot',
plural: 'Feet'
},
to_anchor: 1
},
fathom: {
name: {
singular: 'Fathom',
plural: 'Fathoms'
},
to_anchor: 6
},
mi: {
name: {
singular: 'Mile',
plural: 'Miles'
},
to_anchor: 5280
},
nMi: {
name: {
singular: 'Nautical Mile',
plural: 'Nautical Miles'
},
to_anchor: 6076.12
}
};
var length = {
metric: metric,
imperial: imperial,
_anchors: {
metric: {
unit: 'm',
ratio: 3.28084
},
imperial: {
unit: 'ft',
ratio: 1 / 3.28084
}
}
};
var metric$1, imperial$1;
metric$1 = {
mm2: {
name: {
singular: 'Square Millimeter',
plural: 'Square Millimeters'
},
to_anchor: 1 / 1000000
},
cm2: {
name: {
singular: 'Centimeter',
plural: 'Centimeters'
},
to_anchor: 1 / 10000
},
m2: {
name: {
singular: 'Square Meter',
plural: 'Square Meters'
},
to_anchor: 1
},
ha: {
name: {
singular: 'Hectare',
plural: 'Hectares'
},
to_anchor: 10000
},
km2: {
name: {
singular: 'Square Kilometer',
plural: 'Square Kilometers'
},
to_anchor: 1000000
}
};
imperial$1 = {
'in2': {
name: {
singular: 'Square Inch',
plural: 'Square Inches'
},
to_anchor: 1 / 144
},
yd2: {
name: {
singular: 'Square Yard',
plural: 'Square Yards'
},
to_anchor: 9
},
ft2: {
name: {
singular: 'Square Foot',
plural: 'Square Feet'
},
to_anchor: 1
},
ac: {
name: {
singular: 'Acre',
plural: 'Acres'
},
to_anchor: 43560
},
mi2: {
name: {
singular: 'Square Mile',
plural: 'Square Miles'
},
to_anchor: 27878400
}
};
var area = {
metric: metric$1,
imperial: imperial$1,
_anchors: {
metric: {
unit: 'm2',
ratio: 10.7639
},
imperial: {
unit: 'ft2',
ratio: 1 / 10.7639
}
}
};
var metric$2, imperial$2;
metric$2 = {
mcg: {
name: {
singular: 'Microgram',
plural: 'Micrograms'
},
to_anchor: 1 / 1000000
},
mg: {
name: {
singular: 'Milligram',
plural: 'Milligrams'
},
to_anchor: 1 / 1000
},
g: {
name: {
singular: 'Gram',
plural: 'Grams'
},
to_anchor: 1
},
kg: {
name: {
singular: 'Kilogram',
plural: 'Kilograms'
},
to_anchor: 1000
},
mt: {
name: {
singular: 'Metric Tonne',
plural: 'Metric Tonnes'
},
to_anchor: 1000000
}
};
imperial$2 = {
oz: {
name: {
singular: 'Ounce',
plural: 'Ounces'
},
to_anchor: 1 / 16
},
lb: {
name: {
singular: 'Pound',
plural: 'Pounds'
},
to_anchor: 1
},
t: {
name: {
singular: 'Ton',
plural: 'Tons'
},
to_anchor: 2000
}
};
var mass = {
metric: metric$2,
imperial: imperial$2,
_anchors: {
metric: {
unit: 'g',
ratio: 1 / 453.592
},
imperial: {
unit: 'lb',
ratio: 453.592
}
}
};
var metric$3, imperial$3;
metric$3 = {
mm3: {
name: {
singular: 'Cubic Millimeter',
plural: 'Cubic Millimeters'
},
to_anchor: 1 / 1000000
},
cm3: {
name: {
singular: 'Cubic Centimeter',
plural: 'Cubic Centimeters'
},
to_anchor: 1 / 1000
},
ml: {
name: {
singular: 'Millilitre',
plural: 'Millilitres'
},
to_anchor: 1 / 1000
},
cl: {
name: {
singular: 'Centilitre',
plural: 'Centilitres'
},
to_anchor: 1 / 100
},
dl: {
name: {
singular: 'Decilitre',
plural: 'Decilitres'
},
to_anchor: 1 / 10
},
l: {
name: {
singular: 'Litre',
plural: 'Litres'
},
to_anchor: 1
},
kl: {
name: {
singular: 'Kilolitre',
plural: 'Kilolitres'
},
to_anchor: 1000
},
m3: {
name: {
singular: 'Cubic meter',
plural: 'Cubic meters'
},
to_anchor: 1000
},
km3: {
name: {
singular: 'Cubic kilometer',
plural: 'Cubic kilometers'
},
to_anchor: 1000000000000 // Swedish units
},
krm: {
name: {
singular: 'Matsked',
plural: 'Matskedar'
},
to_anchor: 1 / 1000
},
tsk: {
name: {
singular: 'Tesked',
plural: 'Teskedar'
},
to_anchor: 5 / 1000
},
msk: {
name: {
singular: 'Matsked',
plural: 'Matskedar'
},
to_anchor: 15 / 1000
},
kkp: {
name: {
singular: 'Kaffekopp',
plural: 'Kaffekoppar'
},
to_anchor: 150 / 1000
},
glas: {
name: {
singular: 'Glas',
plural: 'Glas'
},
to_anchor: 200 / 1000
},
kanna: {
name: {
singular: 'Kanna',
plural: 'Kannor'
},
to_anchor: 2.617
}
};
imperial$3 = {
tsp: {
name: {
singular: 'Teaspoon',
plural: 'Teaspoons'
},
to_anchor: 1 / 6
},
Tbs: {
name: {
singular: 'Tablespoon',
plural: 'Tablespoons'
},
to_anchor: 1 / 2
},
in3: {
name: {
singular: 'Cubic inch',
plural: 'Cubic inches'
},
to_anchor: 0.55411
},
'fl-oz': {
name: {
singular: 'Fluid Ounce',
plural: 'Fluid Ounces'
},
to_anchor: 1
},
cup: {
name: {
singular: 'Cup',
plural: 'Cups'
},
to_anchor: 8
},
pnt: {
name: {
singular: 'Pint',
plural: 'Pints'
},
to_anchor: 16
},
qt: {
name: {
singular: 'Quart',
plural: 'Quarts'
},
to_anchor: 32
},
gal: {
name: {
singular: 'Gallon',
plural: 'Gallons'
},
to_anchor: 128
},
ft3: {
name: {
singular: 'Cubic foot',
plural: 'Cubic feet'
},
to_anchor: 957.506
},
yd3: {
name: {
singular: 'Cubic yard',
plural: 'Cubic yards'
},
to_anchor: 25852.7
}
};
var volume = {
metric: metric$3,
imperial: imperial$3,
_anchors: {
metric: {
unit: 'l',
ratio: 33.8140226
},
imperial: {
unit: 'fl-oz',
ratio: 1 / 33.8140226
}
}
};
var metric$4;
metric$4 = {
ea: {
name: {
singular: 'Each',
plural: 'Each'
},
to_anchor: 1
},
dz: {
name: {
singular: 'Dozen',
plural: 'Dozens'
},
to_anchor: 12
}
};
var each = {
metric: metric$4,
imperial: {},
_anchors: {
metric: {
unit: 'ea',
ratio: 1
}
}
};
var metric$5, imperial$4;
metric$5 = {
C: {
name: {
singular: 'degree Celsius',
plural: 'degrees Celsius'
},
to_anchor: 1,
anchor_shift: 0
},
K: {
name: {
singular: 'degree Kelvin',
plural: 'degrees Kelvin'
},
to_anchor: 1,
anchor_shift: 273.15
}
};
imperial$4 = {
F: {
name: {
singular: 'degree Fahrenheit',
plural: 'degrees Fahrenheit'
},
to_anchor: 1
},
R: {
name: {
singular: 'degree Rankine',
plural: 'degrees Rankine'
},
to_anchor: 1,
anchor_shift: 459.67
}
};
var temperature = {
metric: metric$5,
imperial: imperial$4,
_anchors: {
metric: {
unit: 'C',
transform: function (C) {
return C / (5 / 9) + 32;
}
},
imperial: {
unit: 'F',
transform: function (F) {
return (F - 32) * (5 / 9);
}
}
}
};
var time;
var daysInYear = 365.25;
time = {
ns: {
name: {
singular: 'Nanosecond',
plural: 'Nanoseconds'
},
to_anchor: 1 / 1000000000
},
mu: {
name: {
singular: 'Microsecond',
plural: 'Microseconds'
},
to_anchor: 1 / 1000000
},
ms: {
name: {
singular: 'Millisecond',
plural: 'Milliseconds'
},
to_anchor: 1 / 1000
},
s: {
name: {
singular: 'Second',
plural: 'Seconds'
},
to_anchor: 1
},
min: {
name: {
singular: 'Minute',
plural: 'Minutes'
},
to_anchor: 60
},
h: {
name: {
singular: 'Hour',
plural: 'Hours'
},
to_anchor: 60 * 60
},
d: {
name: {
singular: 'Day',
plural: 'Days'
},
to_anchor: 60 * 60 * 24
},
week: {
name: {
singular: 'Week',
plural: 'Weeks'
},
to_anchor: 60 * 60 * 24 * 7
},
month: {
name: {
singular: 'Month',
plural: 'Months'
},
to_anchor: 60 * 60 * 24 * daysInYear / 12
},
year: {
name: {
singular: 'Year',
plural: 'Years'
},
to_anchor: 60 * 60 * 24 * daysInYear
}
};
var time_1 = {
metric: time,
_anchors: {
metric: {
unit: 's',
ratio: 1
}
}
};
var bits, bytes;
bits = {
b: {
name: {
singular: 'Bit',
plural: 'Bits'
},
to_anchor: 1
},
Kb: {
name: {
singular: 'Kilobit',
plural: 'Kilobits'
},
to_anchor: 1024
},
Mb: {
name: {
singular: 'Megabit',
plural: 'Megabits'
},
to_anchor: 1048576
},
Gb: {
name: {
singular: 'Gigabit',
plural: 'Gigabits'
},
to_anchor: 1073741824
},
Tb: {
name: {
singular: 'Terabit',
plural: 'Terabits'
},
to_anchor: 1099511627776
}
};
bytes = {
B: {
name: {
singular: 'Byte',
plural: 'Bytes'
},
to_anchor: 1
},
KB: {
name: {
singular: 'Kilobyte',
plural: 'Kilobytes'
},
to_anchor: 1024
},
MB: {
name: {
singular: 'Megabyte',
plural: 'Megabytes'
},
to_anchor: 1048576
},
GB: {
name: {
singular: 'Gigabyte',
plural: 'Gigabytes'
},
to_anchor: 1073741824
},
TB: {
name: {
singular: 'Terabyte',
plural: 'Terabytes'
},
to_anchor: 1099511627776
}
};
var digital = {
bits: bits,
bytes: bytes,
_anchors: {
bits: {
unit: 'b',
ratio: 1 / 8
},
bytes: {
unit: 'B',
ratio: 8
}
}
};
var metric$6;
metric$6 = {
ppm: {
name: {
singular: 'Part-per Million',
plural: 'Parts-per Million'
},
to_anchor: 1
},
ppb: {
name: {
singular: 'Part-per Billion',
plural: 'Parts-per Billion'
},
to_anchor: .001
},
ppt: {
name: {
singular: 'Part-per Trillion',
plural: 'Parts-per Trillion'
},
to_anchor: .000001
},
ppq: {
name: {
singular: 'Part-per Quadrillion',
plural: 'Parts-per Quadrillion'
},
to_anchor: .000000001
}
};
var partsPer = {
metric: metric$6,
imperial: {},
_anchors: {
metric: {
unit: 'ppm',
ratio: .000001
}
}
};
var metric$7, imperial$5;
metric$7 = {
'm/s': {
name: {
singular: 'meter per second',
plural: 'meters per second'
},
to_anchor: 3.6
},
'km/h': {
name: {
singular: 'Kilometer per hour',
plural: 'Kilometers per hour'
},
to_anchor: 1
}
};
imperial$5 = {
'm/h': {
name: {
singular: 'Mile per hour',
plural: 'Miles per hour'
},
to_anchor: 1
},
knot: {
name: {
singular: 'Knot',
plural: 'Knots'
},
to_anchor: 1.150779
},
'ft/s': {
name: {
singular: 'Foot per second',
plural: 'Feet per second'
},
to_anchor: 0.681818
}
};
var speed = {
metric: metric$7,
imperial: imperial$5,
_anchors: {
metric: {
unit: 'km/h',
ratio: 1 / 1.609344
},
imperial: {
unit: 'm/h',
ratio: 1.609344
}
}
};
var metric$8, imperial$6;
metric$8 = {
'min/km': {
name: {
singular: 'Minute per kilometer',
plural: 'Minutes per kilometer'
},
to_anchor: 0.06
},
's/m': {
name: {
singular: 'Second per meter',
plural: 'Seconds per meter'
},
to_anchor: 1
}
};
imperial$6 = {
'min/mi': {
name: {
singular: 'Minute per mile',
plural: 'Minutes per mile'
},
to_anchor: 0.0113636
},
's/ft': {
name: {
singular: 'Second per foot',
plural: 'Seconds per foot'
},
to_anchor: 1
}
};
var pace = {
metric: metric$8,
imperial: imperial$6,
_anchors: {
metric: {
unit: 's/m',
ratio: 0.3048
},
imperial: {
unit: 's/ft',
ratio: 1 / 0.3048
}
}
};
var metric$9, imperial$7;
metric$9 = {
Pa: {
name: {
singular: 'pascal',
plural: 'pascals'
},
to_anchor: 1 / 1000
},
kPa: {
name: {
singular: 'kilopascal',
plural: 'kilopascals'
},
to_anchor: 1
},
MPa: {
name: {
singular: 'megapascal',
plural: 'megapascals'
},
to_anchor: 1000
},
hPa: {
name: {
singular: 'hectopascal',
plural: 'hectopascals'
},
to_anchor: 1 / 10
},
uPa: {
name: {
singular: 'micropascal',
plural: 'micropascals'
},
to_anchor: 1 / 1e9
},
bar: {
name: {
singular: 'bar',
plural: 'bar'
},
to_anchor: 100
},
torr: {
name: {
singular: 'torr',
plural: 'torr'
},
to_anchor: 101325 / 760000
}
};
imperial$7 = {
psi: {
name: {
singular: 'pound per square inch',
plural: 'pounds per square inch'
},
to_anchor: 1 / 1000
},
ksi: {
name: {
singular: 'kilopound per square inch',
plural: 'kilopound per square inch'
},
to_anchor: 1
}
};
var pressure = {
metric: metric$9,
imperial: imperial$7,
_anchors: {
metric: {
unit: 'kPa',
ratio: 0.00014503768078
},
imperial: {
unit: 'psi',
ratio: 1 / 0.00014503768078
}
}
};
var current;
current = {
A: {
name: {
singular: 'Ampere',
plural: 'Amperes'
},
to_anchor: 1
},
mA: {
name: {
singular: 'Milliampere',
plural: 'Milliamperes'
},
to_anchor: .001
},
kA: {
name: {
singular: 'Kiloampere',
plural: 'Kiloamperes'
},
to_anchor: 1000
}
};
var current_1 = {
metric: current,
_anchors: {
metric: {
unit: 'A',
ratio: 1
}
}
};
var voltage;
voltage = {
V: {
name: {
singular: 'Volt',
plural: 'Volts'
},
to_anchor: 1
},
mV: {
name: {
singular: 'Millivolt',
plural: 'Millivolts'
},
to_anchor: .001
},
kV: {
name: {
singular: 'Kilovolt',
plural: 'Kilovolts'
},
to_anchor: 1000
}
};
var voltage_1 = {
metric: voltage,
_anchors: {
metric: {
unit: 'V',
ratio: 1
}
}
};
var power;
power = {
W: {
name: {
singular: 'Watt',
plural: 'Watts'
},
to_anchor: 1
},
mW: {
name: {
singular: 'Milliwatt',
plural: 'Milliwatts'
},
to_anchor: .001
},
kW: {
name: {
singular: 'Kilowatt',
plural: 'Kilowatts'
},
to_anchor: 1000
},
MW: {
name: {
singular: 'Megawatt',
plural: 'Megawatts'
},
to_anchor: 1000000
},
GW: {
name: {
singular: 'Gigawatt',
plural: 'Gigawatts'
},
to_anchor: 1000000000
}
};
var power_1 = {
metric: power,
_anchors: {
metric: {
unit: 'W',
ratio: 1
}
}
};
var reactivePower;
reactivePower = {
VAR: {
name: {
singular: 'Volt-Ampere Reactive',
plural: 'Volt-Amperes Reactive'
},
to_anchor: 1
},
mVAR: {
name: {
singular: 'Millivolt-Ampere Reactive',
plural: 'Millivolt-Amperes Reactive'
},
to_anchor: .001
},
kVAR: {
name: {
singular: 'Kilovolt-Ampere Reactive',
plural: 'Kilovolt-Amperes Reactive'
},
to_anchor: 1000
},
MVAR: {
name: {
singular: 'Megavolt-Ampere Reactive',
plural: 'Megavolt-Amperes Reactive'
},
to_anchor: 1000000
},
GVAR: {
name: {
singular: 'Gigavolt-Ampere Reactive',
plural: 'Gigavolt-Amperes Reactive'
},
to_anchor: 1000000000
}
};
var reactivePower_1 = {
metric: reactivePower,
_anchors: {
metric: {
unit: 'VAR',
ratio: 1
}
}
};
var apparentPower;
apparentPower = {
VA: {
name: {
singular: 'Volt-Ampere',
plural: 'Volt-Amperes'
},
to_anchor: 1
},
mVA: {
name: {
singular: 'Millivolt-Ampere',
plural: 'Millivolt-Amperes'
},
to_anchor: .001
},
kVA: {
name: {
singular: 'Kilovolt-Ampere',
plural: 'Kilovolt-Amperes'
},
to_anchor: 1000
},
MVA: {
name: {
singular: 'Megavolt-Ampere',
plural: 'Megavolt-Amperes'
},
to_anchor: 1000000
},
GVA: {
name: {
singular: 'Gigavolt-Ampere',
plural: 'Gigavolt-Amperes'
},
to_anchor: 1000000000
}
};
var apparentPower_1 = {
metric: apparentPower,
_anchors: {
metric: {
unit: 'VA',
ratio: 1
}
}
};
var energy;
energy = {
Wh: {
name: {
singular: 'Watt-hour',
plural: 'Watt-hours'
},
to_anchor: 3600
},
mWh: {
name: {
singular: 'Milliwatt-hour',
plural: 'Milliwatt-hours'
},
to_anchor: 3.6
},
kWh: {
name: {
singular: 'Kilowatt-hour',
plural: 'Kilowatt-hours'
},
to_anchor: 3600000
},
MWh: {
name: {
singular: 'Megawatt-hour',
plural: 'Megawatt-hours'
},
to_anchor: 3600000000
},
GWh: {
name: {
singular: 'Gigawatt-hour',
plural: 'Gigawatt-hours'
},
to_anchor: 3600000000000
},
J: {
name: {
singular: 'Joule',
plural: 'Joules'
},
to_anchor: 1
},
kJ: {
name: {
singular: 'Kilojoule',
plural: 'Kilojoules'
},
to_anchor: 1000
}
};
var energy_1 = {
metric: energy,
_anchors: {
metric: {
unit: 'J',
ratio: 1
}
}
};
var reactiveEnergy;
reactiveEnergy = {
VARh: {
name: {
singular: 'Volt-Ampere Reactive Hour',
plural: 'Volt-Amperes Reactive Hour'
},
to_anchor: 1
},
mVARh: {
name: {
singular: 'Millivolt-Ampere Reactive Hour',
plural: 'Millivolt-Amperes Reactive Hour'
},
to_anchor: .001
},
kVARh: {
name: {
singular: 'Kilovolt-Ampere Reactive Hour',
plural: 'Kilovolt-Amperes Reactive Hour'
},
to_anchor: 1000
},
MVARh: {
name: {
singular: 'Megavolt-Ampere Reactive Hour',
plural: 'Megavolt-Amperes Reactive Hour'
},
to_anchor: 1000000
},
GVARh: {
name: {
singular: 'Gigavolt-Ampere Reactive Hour',
plural: 'Gigavolt-Amperes Reactive Hour'
},
to_anchor: 1000000000
}
};
var reactiveEnergy_1 = {
metric: reactiveEnergy,
_anchors: {
metric: {
unit: 'VARh',
ratio: 1
}
}
};
var metric$a, imperial$8;
metric$a = {
'mm3/s': {
name: {
singular: 'Cubic Millimeter per second',
plural: 'Cubic Millimeters per second'
},
to_anchor: 1 / 1000000
},
'cm3/s': {
name: {
singular: 'Cubic Centimeter per second',
plural: 'Cubic Centimeters per second'
},
to_anchor: 1 / 1000
},
'ml/s': {
name: {
singular: 'Millilitre per second',
plural: 'Millilitres per second'
},
to_anchor: 1 / 1000
},
'cl/s': {
name: {
singular: 'Centilitre per second',
plural: 'Centilitres per second'
},
to_anchor: 1 / 100
},
'dl/s': {
name: {
singular: 'Decilitre per second',
plural: 'Decilitres per second'
},
to_anchor: 1 / 10
},
'l/s': {
name: {
singular: 'Litre per second',
plural: 'Litres per second'
},
to_anchor: 1
},
'l/min': {
name: {
singular: 'Litre per minute',
plural: 'Litres per minute'
},
to_anchor: 1 / 60
},
'l/h': {
name: {
singular: 'Litre per hour',
plural: 'Litres per hour'
},
to_anchor: 1 / 3600
},
'kl/s': {
name: {
singular: 'Kilolitre per second',
plural: 'Kilolitres per second'
},
to_anchor: 1000
},
'kl/min': {
name: {
singular: 'Kilolitre per minute',
plural: 'Kilolitres per minute'
},
to_anchor: 50 / 3
},
'kl/h': {
name: {
singular: 'Kilolitre per hour',
plural: 'Kilolitres per hour'
},
to_anchor: 5 / 18
},
'm3/s': {
name: {
singular: 'Cubic meter per second',
plural: 'Cubic meters per second'
},
to_anchor: 1000
},
'm3/min': {
name: {
singular: 'Cubic meter per minute',
plural: 'Cubic meters per minute'
},
to_anchor: 50 / 3
},
'm3/h': {
name: {
singular: 'Cubic meter per hour',
plural: 'Cubic meters per hour'
},
to_anchor: 5 / 18
},
'km3/s': {
name: {
singular: 'Cubic kilometer per second',
plural: 'Cubic kilometers per second'
},
to_anchor: 1000000000000
}
};
imperial$8 = {
'tsp/s': {
name: {
singular: 'Teaspoon per second',
plural: 'Teaspoons per second'
},
to_anchor: 1 / 6
},
'Tbs/s': {
name: {
singular: 'Tablespoon per second',
plural: 'Tablespoons per second'
},
to_anchor: 1 / 2
},
'in3/s': {
name: {
singular: 'Cubic inch per second',
plural: 'Cubic inches per second'
},
to_anchor: 0.55411
},
'in3/min': {
name: {
singular: 'Cubic inch per minute',
plural: 'Cubic inches per minute'
},
to_anchor: 0.55411 / 60
},
'in3/h': {
name: {
singular: 'Cubic inch per hour',
plural: 'Cubic inches per hour'
},
to_anchor: 0.55411 / 3600
},
'fl-oz/s': {
name: {
singular: 'Fluid Ounce per second',
plural: 'Fluid Ounces per second'
},
to_anchor: 1
},
'fl-oz/min': {
name: {
singular: 'Fluid Ounce per minute',
plural: 'Fluid Ounces per minute'
},
to_anchor: 1 / 60
},
'fl-oz/h': {
name: {
singular: 'Fluid Ounce per hour',
plural: 'Fluid Ounces per hour'
},
to_anchor: 1 / 3600
},
'cup/s': {
name: {
singular: 'Cup per second',
plural: 'Cups per second'
},
to_anchor: 8
},
'pnt/s': {
name: {
singular: 'Pint per second',
plural: 'Pints per second'
},
to_anchor: 16
},
'pnt/min': {
name: {
singular: 'Pint per minute',
plural: 'Pints per minute'
},
to_anchor: 4 / 15
},
'pnt/h': {
name: {
singular: 'Pint per hour',
plural: 'Pints per hour'
},
to_anchor: 1 / 225
},
'qt/s': {
name: {
singular: 'Quart per second',
plural: 'Quarts per second'
},
to_anchor: 32
},
'gal/s': {
name: {
singular: 'Gallon per second',
plural: 'Gallons per second'
},
to_anchor: 128
},
'gal/min': {
name: {
singular: 'Gallon per minute',
plural: 'Gallons per minute'
},
to_anchor: 32 / 15
},
'gal/h': {
name: {
singular: 'Gallon per hour',
plural: 'Gallons per hour'
},
to_anchor: 8 / 225
},
'ft3/s': {
name: {
singular: 'Cubic foot per second',
plural: 'Cubic feet per second'
},
to_anchor: 957.506
},
'ft3/min': {
name: {
singular: 'Cubic foot per minute',
plural: 'Cubic feet per minute'
},
to_anchor: 957.506 / 60
},
'ft3/h': {
name: {
singular: 'Cubic foot per hour',
plural: 'Cubic feet per hour'
},
to_anchor: 957.506 / 3600
},
'yd3/s': {
name: {
singular: 'Cubic yard per second',
plural: 'Cubic yards per second'
},
to_anchor: 25852.7
},
'yd3/min': {
name: {
singular: 'Cubic yard per minute',
plural: 'Cubic yards per minute'
},
to_anchor: 25852.7 / 60
},
'yd3/h': {
name: {
singular: 'Cubic yard per hour',
plural: 'Cubic yards per hour'
},
to_anchor: 25852.7 / 3600
}
};
var volumeFlowRate = {
metric: metric$a,
imperial: imperial$8,
_anchors: {
metric: {
unit: 'l/s',
ratio: 33.8140227
},
imperial: {
unit: 'fl-oz/s',
ratio: 1 / 33.8140227
}
}
};
var metric$b, imperial$9;
metric$b = {
'lx': {
name: {
singular: 'Lux',
plural: 'Lux'
},
to_anchor: 1
}
};
imperial$9 = {
'ft-cd': {
name: {
singular: 'Foot-candle',
plural: 'Foot-candles'
},
to_anchor: 1
}
};
var illuminance = {
metric: metric$b,
imperial: imperial$9,
_anchors: {
metric: {
unit: 'lx',
ratio: 1 / 10.76391
},
imperial: {
unit: 'ft-cd',
ratio: 10.76391
}
}
};
var frequency;
frequency = {
mHz: {
name: {
singular: 'millihertz',
plural: 'millihertz'
},
to_anchor: 1 / 1000
},
Hz: {
name: {
singular: 'hertz',
plural: 'hertz'
},
to_anchor: 1
},
kHz: {
name: {
singular: 'kilohertz',
plural: 'kilohertz'
},
to_anchor: 1000
},
MHz: {
name: {
singular: 'megahertz',
plural: 'megahertz'
},
to_anchor: 1000 * 1000
},
GHz: {
name: {
singular: 'gigahertz',
plural: 'gigahertz'
},
to_anchor: 1000 * 1000 * 1000
},
THz: {
name: {
singular: 'terahertz',
plural: 'terahertz'
},
to_anchor: 1000 * 1000 * 1000 * 1000
},
rpm: {
name: {
singular: 'rotation per minute',
plural: 'rotations per minute'
},
to_anchor: 1 / 60
},
"deg/s": {
name: {
singular: 'degree per second',
plural: 'degrees per second'
},
to_anchor: 1 / 360
},
"rad/s": {
name: {
singular: 'radian per second',
plural: 'radians per second'
},
to_anchor: 1 / (Math.PI * 2)
}
};
var frequency_1 = {
metric: frequency,
_anchors: {
frequency: {
unit: 'hz',
ratio: 1
}
}
};
var angle;
angle = {
rad: {
name: {
singular: 'radian',
plural: 'radians'
},
to_anchor: 180 / Math.PI
},
deg: {
name: {
singular: 'degree',
plural: 'degrees'
},
to_anchor: 1
},
grad: {
name: {
singular: 'gradian',
plural: 'gradians'
},
to_anchor: 9 / 10
},
arcmin: {
name: {
singular: 'arcminute',
plural: 'arcminutes'
},
to_anchor: 1 / 60
},
arcsec: {
name: {
singular: 'arcsecond',
plural: 'arcseconds'
},
to_anchor: 1 / 3600
}
};
var angle_1 = {
metric: angle,
_anchors: {
metric: {
unit: 'deg',
ratio: 1
}
}
};
var metric$c;
metric$c = {
c: {
name: {
singular: 'Coulomb',
plural: 'Coulombs'
},
to_anchor: 1
},
mC: {
name: {
singular: 'Millicoulomb',
plural: 'Millicoulombs'
},
to_anchor: 1 / 1000
},
μC: {
name: {
singular: 'Microcoulomb',
plural: 'Microcoulombs'
},
to_anchor: 1 / 1000000
},
nC: {
name: {
singular: 'Nanocoulomb',
plural: 'Nanocoulombs'
},
to_anchor: 1e-9
},
pC: {
name: {
singular: 'Picocoulomb',
plural: 'Picocoulombs'
},
to_anchor: 1e-12
}
};
var charge = {
metric: metric$c,
imperial: {},
_anchors: {
metric: {
unit: 'c',
ratio: 1
}
}
};
var metric$d;
metric$d = {
N: {
name: {
singular: 'Newton',
plural: 'Newtons'
},
to_anchor: 1
},
kN: {
name: {
singular: 'Kilonewton',
plural: 'Kilonewtons'
},
to_anchor: 1000
},
lbf: {
name: {
singular: 'Pound-force',
plural: 'Pound-forces'
},
to_anchor: 4.44822
}
};
var force = {
metric: metric$d,
imperial: {},
_anchors: {
metric: {
unit: 'N',
ratio: 1
}
}
};
var metric$e;
metric$e = {
'g-force': {
name: {
singular: 'g-force',
plural: 'g-forces'
},
to_anchor: 9.80665
},
'm/s2': {
name: {
singular: 'meter per second squared',
plural: 'meters per second squared'
},
to_anchor: 1
}
};
var acceleration = {
metric: metric$e,
imperial: {},
_anchors: {
metric: {
unit: 'g-force',
ratio: 1
}
}
};
var intensity;
intensity = {
'W/m2': {
name: {
singular: 'watt per meter squared',
plural: 'watts per meters squared'
},
to_anchor: 1
}
};
var intensity_1 = {
metric: intensity,
_anchors: {
metric: {
unit: 'W/m2',
ratio: 1
}
}
};
/**
* Modified version of 'convert-units' which was written by Ben Ng
* @see http://benng.me
* @see https://www.npmjs.com/package/convert-units
*/
var convert,
measures = {
length: length,
area: area,
mass: mass,
volume: volume,
each: each,
temperature: temperature,
time: time_1,
digital: digital,
partsPer: partsPer,
speed: speed,
pace: pace,
pressure: pressure,
current: current_1,
voltage: voltage_1,
power: power_1,
reactivePower: reactivePower_1,
apparentPower: apparentPower_1,
energy: energy_1,
reactiveEnergy: reactiveEnergy_1,
volumeFlowRate: volumeFlowRate,
illuminance: illuminance,
frequency: frequency_1,
angle: angle_1,
charge: charge,
force: force,
acceleration: acceleration,
intensity: intensity_1
},
Converter;
Converter = function (numerator, denominator) {
if (denominator) this.val = numerator / denominator;else this.val = numerator;
};
/**
* Lets the converter know the source unit abbreviation
*/
Converter.prototype.from = function (from) {
if (this.destination) throw new Error('.from must be called before .to');
this.origin = this.getUnit(from);
if (!this.origin) {
this.throwUnsupportedUnitError(from);
}
return this;
};
/**
* Converts the unit and returns the value
*/
Converter.prototype.to = function (to) {
if (!this.origin) throw new Error('.to must be called after .from');
this.destination = this.getUnit(to);
var result, transform;
if (!this.destination) {
this.throwUnsupportedUnitError(to);
} // Don't change the value if origin and destination are the same
if (this.origin.abbr === this.destination.abbr) {
return this.val;
} // You can't go from liquid to mass, for example
if (this.destination.measure != this.origin.measure) {
throw new Error('Cannot convert incompatible measures of ' + this.destination.measure + ' and ' + this.origin.measure);
}
/**
* Convert from the source value to its anchor inside the system
*/
result = this.val * this.origin.unit.to_anchor;
/**
* For some changes it's a simple shift (C to K)
* So we'll add it when convering into the unit (later)
* and subtract it when converting from the unit
*/
if (this.origin.unit.anchor_shift) {
result -= this.origin.unit.anchor_shift;
}
/**
* Convert from one system to another through the anchor ratio. Some conversions
* aren't ratio based or require more than a simple shift. We can provide a custom
* transform here to provide the direct result
*/
if (this.origin.system != this.destination.system) {
transform = measures[this.origin.measure]._anchors[this.origin.system].transform;
if (typeof transform === 'function') {
result = transform(result);
} else {
result *= measures[this.origin.measure]._anchors[this.origin.system].ratio;
}
}
/**
* This shift has to be done after the system conversion business
*/
if (this.destination.unit.anchor_shift) {
result += this.destination.unit.anchor_shift;
}
/**
* Convert to another unit inside the destination system
*/
return result / this.destination.unit.to_anchor;
};
/**
* Converts the unit to the best available unit.
*/
Converter.prototype.toBest = function (options) {
if (!this.origin) throw new Error('.toBest must be called after .from');
var options = Object.assign({
exclude: [],
cutOffNumber: 1
}, options);
var best;
/**
Looks through every possibility for the 'best' available unit.
i.e. Where the value has the fewest numbers before the decimal point,
but is still higher than 1.
*/
lodash_foreach(this.possibilities(), function (possibility) {
var unit = this.describe(possibility);
var isIncluded = options.exclude.indexOf(possibility) === -1;
if (isIncluded && unit.system === this.origin.system) {
var result = this.to(possibility);
if (!best || result >= options.cutOffNumber && result < best.val) {
best = {
val: result,
unit: possibility,
singular: unit.singular,
plural: unit.plural
};
}
}
}.bind(this));
return best;
};
/**
* Finds the unit
*/
Converter.prototype.getUnit = function (abbr) {
var found;
lodash_foreach(measures, function (systems, measure) {
lodash_foreach(systems, function (units, system) {
if (system == '_anchors') return false;
lodash_foreach(units, function (unit, testAbbr) {
if (testAbbr == abbr) {
found = {
abbr: abbr,
measure: measure,
system: system,
unit: unit
};
return false;
}
});
if (found) return false;
});
if (found) return false;
});
return found;
};
var describe = function (resp) {
return {
abbr: resp.abbr,
measure: resp.measure,
system: resp.system,
singular: resp.unit.name.singular,
plural: resp.unit.name.plural
};
};
/**
* An alias for getUnit
*/
Converter.prototype.describe = function (abbr) {
var resp = Converter.prototype.getUnit(abbr);
var desc = null;
try {
desc = describe(resp);
} catch (err) {
this.throwUnsupportedUnitError(abbr);
}
return desc;
};
/**
* Detailed list of all supported units
*/
Converter.prototype.list = function (measure) {
var list = [];
lodash_foreach(measures, function (systems, testMeasure) {
if (measure && measure !== testMeasure) return;
lodash_foreach(systems, function (units, system) {
if (system == '_anchors') return false;
lodash_foreach(units, function (unit, abbr) {
list = list.concat(describe({
abbr: abbr,
measure: testMeasure,
system: system,
unit: unit
}));
});
});
});
return list;
};
Converter.prototype.throwUnsupportedUnitError = function (what) {
var validUnits = [];
lodash_foreach(measures, function (systems, measure) {
lodash_foreach(systems, function (units, system) {
if (system == '_anchors') return false;
validUnits = validUnits.concat(lodash_keys(units));
});
});
throw new Error('Unsupported unit ' + what + ', use one of: ' + validUnits.join(', '));
};
/**
* Returns the abbreviated measures that the value can be
* converted to.
*/
Converter.prototype.possibilities = function (measure) {
var possibilities = [];
if (!this.origin && !measure) {
lodash_foreach(lodash_keys(measures), function (measure) {
lodash_foreach(measures[measure], function (units, system) {
if (system == '_anchors') return false;
possibilities = possibilities.concat(lodash_keys(units));
});
});
} else {
measure = measure || this.origin.measure;
lodash_foreach(measures[measure], function (units, system) {
if (system == '_anchors') return false;
possibilities = possibilities.concat(lodash_keys(units));
});
}
return possibilities;
};
/**
* Returns the abbreviated measures that the value can be
* converted to.
*/
Converter.prototype.measures = function () {
return lodash_keys(measures);
};
convert = function (value) {
return new Converter(value);
};
var lib = convert;
const units = {
convert: lib,
checkConversion: (fromUnit, toUnit) => lib().from(fromUnit).possibilities().filter(x => x === toUnit).length > 0
};
const clamp = (v, a, b) => v < a ? a : v > b ? b : v;
const ID = {
Generate: (len) => {
return Math.round((Math.random() * 1e15)).toString(16).slice(0, clamp(len,1,16))
}
};
const numCheck = (num) => Number.isFinite(Number(num));
const arrCheck = (arr) => Array.isArray(arr);
const numArrayCheck = (numArray) => arrCheck(numArray) ? numArray.filter(n => !numCheck(n)).length == 0 : false;
const computable = (v) => numCheck(v) || numArrayCheck(v);
var num = { numCheck, arrCheck, numArrayCheck, computable };
function numarrayfunction(v, f) {
if (Number.isFinite(v)) {
return f(v);
}
else if (v instanceof Array) {
return v.map(x => f(x));
}
}
const round = (value, precision = 1) => numarrayfunction(value,x => Math.round(x / precision) * precision);
class Data {
constructor(id) {
this.id = id || ID.Generate(8);
this.name = id ? id : "";
this.data = undefined;
this.type = undefined;
this.unit = undefined;
}
setName(name) {
this.name = name;
return this;
}
setData(data) {
if (num.computable(data)) {
this.data = data;
if (num.numCheck(this.data)) {
this._data_is = "number";
} else if (num.numArrayCheck(this.data)) {
this._data_is = "array";
}
}
else {
console.error('data needs to be a number or an array of numbers');
}
return this;
}
setID(id) {
this.id = id;
return this;
}
setType(type) {
this.type = type;
return this;
}
setUnit(unit) {
let error = false;
try {
units.checkConversion(unit, this.unit);
} catch (err) {
error = true;
console.error(err);
} finally {
if (!error) {
this.unit = unit;
return this;
} else {
return this;
}
}
}
convertTo(unit) {
if (units.checkConversion(unit, this.unit)) {
switch (this._data_is) {
case "number":
this.data = units.convert(this.data).from(this.unit).to(unit);
this.unit = unit;
break;
case "array":
this.data = this.data.map(x => units.convert(x).from(this.unit).to(unit));
this.unit = unit;
default:
break;
}
}
return this;
}
roundTo(decimalPrecision) {
switch (this._data_is) {
case "number":
this.data = round(this.data,decimalPrecision);
break;
case "array":
this.data = this.data.map(x => round(x, decimalPrecision));
default:
break;
}
return this;
}
tabulate(orientation = "vertical", order = ["name", "type", "unit", "data"]) {
let tabular = "";
let temp = {
data: this.data,
unit: this.unit,
type: this.type,
name: this.name
};
switch (this._data_is) {
case "number":
temp.data = [this.data];
break;
case "array":
break;
default:
break;
}
if (orientation === 'column') {
orientation = "vertical";
}
if (orientation === 'row') {
orientation = 'horizontal';
}
switch (orientation) {
case "vertical":
order.forEach(item => {
if (item === "data") {
temp.data.forEach(d => {
tabular += `${d}\n`;
});
}
else {
tabular += `${temp[item]}\n`;
}
});
break;
case "horizontal":
order.forEach(item => {
if (item === "data") {
temp.data.forEach(d => {
tabular += `${d}\t`;
});
} else {
tabular += `${temp[item]}\t`;
}
});
break;
default:
break;
}
console.log(tabular);
return tabular;
}
}
class SoundPressure extends Data{
constructor(id) {
super(id);
this.setType('SoundPressure');
this.unit = 'Pa';
this.ref = units.convert(20).from('uPa').to('Pa');
}
setUnit(unit) {
let error = false;
try {
units.checkConversion(unit, this.unit);
} catch (err) {
error = true;
console.error(err);
} finally {
if (!error) {
this.unit = unit;
this.ref = units.convert(20).from('uPa').to(this.unit);
return this;
} else {
return this;
}
}
}
convertTo(unit) {
if (units.checkConversion(unit, this.unit)) {
switch (this._data_is) {
case "number":
this.data = units.convert(this.data).from(this.unit).to(unit);
this.unit = unit;
this.ref = units.convert(20).from('uPa').to(this.unit);
break;
case "array":
this.data = this.data.map(x => units.convert(x).from(this.unit).to(unit));
this.unit = unit;
this.ref = units.convert(20).from('uPa').to(this.unit);
default:
break;
}
}
return this;
}
}
class SoundIntensity extends Data {
constructor(id) {
super(id);
this.setType('SoundIntensity');
this.unit = 'W/m2';
this.ref = 1e-12;
}
}
class SoundPower extends Data {
constructor(id) {
super(id);
this.setType('SoundPower');
this.unit = 'W/m2';
this.ref = 1e-12;
}
}
class SoundPressureLevel extends Data {
constructor(id) {
super(id);
this.setType('SoundPressureLevel');
this.unit = 'dB';
this.weight = undefined;
this.ref = units.convert(20).from('uPa').to('Pa');
}
setUnit(unit) {
let error = false;
try {
units.checkConversion(unit, this.unit);
} catch (err) {
error = true;
console.error(err);
} finally {
if (!error) {
this.unit = unit;
this.ref = units.convert(20).from('uPa').to(this.unit);
return this;
} else {
return this;
}
}
}
setWeight(weight) {
const w = WeightTranslation(weight);
if (w !== null) {
this.weight = w;
this.unit = `dB-${this.weight.toUpperCase()} (re: 20uPa)`;
}
return this;
}
changeWeight(desiredWeight,frequencies) {
if (!this.weight) {
const err = 'You must call .setWeight() before calling .to()';
console.error(err);
return null;
} else {
const dw = WeightTranslation(desiredWeight);
if (dw !== null) {
let _data = this.data;
if (num.numCheck(this.data)) {
_data = [_data];
}
let dbz = [];
switch (this.weight) {
case 'a':
const a = frequencies.map(f => Weight.A(f));
dbz = _data.map((x, i) => x - a[i]);
break;
case 'b':
const b = frequencies.map(f => Weight.B(f));
dbz = _data.map((x, i) => x - b[i]);
break;
case 'c':
const c = frequencies.map(f => Weight.C(f));
dbz = _data.map((x, i) => x - c[i]);
break;
case 'd':
const d = frequencies.map(f => Weight.D(f));
dbz = _data.map((x, i) => x - d[i]);
break;
case 'z':
dbz = _data;
break;
default:
break;
}
switch (dw) {
case 'a':
const a = frequencies.map(f => Weight.A(f));
this.data = dbz.map((x, i) => x + a[i]);
this.weight = dw;
this.unit = `dB-${this.weight.toUpperCase()} (re: 20uPa)`;
break;
case 'b':
const b = frequencies.map(f => Weight.B(f));
this.data = dbz.map((x, i) => x + b[i]);
this.weight = dw;
this.unit = `dB-${this.weight.toUpperCase()} (re: 20uPa)`;
break;
case 'c':
const c = frequencies.map(f => Weight.C(f));
this.data = dbz.map((x, i) => x + c[i]);
this.weight = dw;
this.unit = `dB-${this.weight.toUpperCase()} (re: 20uPa)`;
break;
case 'd':
const d = frequencies.map(f => Weight.D(f));
this.data = dbz.map((x, i) => x + d[i]);
this.weight = dw;
this.unit = `dB-${this.weight.toUpperCase()} (re: 20uPa)`;
break;
case 'z':
this.data = dbz;
this.weight = dw;
this.unit = `dB-${this.weight.toUpperCase()} (re: 20uPa)`;
break;
default:
break;
}
return this;
} else {
return null;
}
}
}
convertTo(unit) {
if (units.checkConversion(unit, this.unit)) {
switch (this._data_is) {
case "number":
this.data = units.convert(this.data).from(this.unit).to(unit);
this.unit = unit;
this.ref = units.convert(20).from('uPa').to(this.unit);
break;
case "array":
this.data = this.data.map(x => units.convert(x).from(this.unit).to(unit));
this.unit = unit;
this.ref = units.convert(20).from('uPa').to(this.unit);
default:
break;
}
}
return this;
}
}
class SoundIntensityLevel extends Data {
constructor(id) {
super(id);
this.setType('SoundIntensityLevel');
this.unit = 'dB';
this.ref = 1e-12;
}
setUnit(unit) {
let error = false;
try {
units.checkConversion(unit, this.unit);
} catch (err) {
error = true;
console.error(err);
} finally {
if (!error) {
this.unit = unit;
return this;
} else {
return this;
}
}
}
convertTo(unit) {
if (units.checkConversion(unit, this.unit)) {
switch (this._data_is) {
case "number":
this.data = units.convert(this.data).from(this.unit).to(unit);
this.unit = unit;
break;
case "array":
this.data = this.data.map(x => units.convert(x).from(this.unit).to(unit));
this.unit = unit;
default:
break;
}
}
return this;
}
}
class SoundPowerLevel extends Data {
constructor(id) {
super(id);
this.setType('SoundPowerLevel');
this.unit = 'dB';
this.ref = 1e-12;
}
setUnit(unit) {
let error = false;
try {
units.checkConversion(unit, this.unit);
} catch (err) {
error = true;
console.error(err);
} finally {
if (!error) {
this.unit = unit;
return this;
} else {
return this;
}
}
}
convertTo(unit) {
if (units.checkConversion(unit, this.unit)) {
switch (this._data_is) {
case "number":
this.data = units.convert(this.data).from(this.unit).to(unit);
this.unit = unit;
break;
case "array":
this.data = this.data.map(x => units.convert(x).from(this.unit).to(unit));
this.unit = unit;
default:
break;
}
}
return this;
}
}
const Types = {
SoundPressure,
SoundIntensity,
SoundPower,
SoundPressureLevel,
SoundIntensityLevel,
SoundPowerLevel
};
const Translations = {
SoundPressureLevel: ['spl', 'soundpressurelevel', 'lp'],
SoundIntensityLevel: ['sil', 'soundintensitylevel', 'li'],
SoundPowerLevel: ['swl', 'soundpowerlevel', 'lw'],
SoundPower: ['soundpower', 'w', 'power'],
SoundIntensity: ['soundintensity', 'i', 'intensity'],
SoundPressure: ['soundpressure','p','pressure']
};
const TypeTranslation = (str) => {
let _str = str.toLowerCase().replace(/[-_\s\t]+/gmi, '');
let type = undefined;
Object.keys(Translations).forEach(key => {
Translations[key].forEach(translation => {
if (_str === translation) {
type = key;
}
});
});
return type;
};
class Measurement {
constructor(id) {
this.id = id || "";
this.datachain = [];
this.name = "";
this.type = "";
this.operationHistory = ['constructor'];
}
lastOperation() {
return this.operationHistory[this.operationHistory.length - 1];
}
setName(name) {
this.name = name;
return this;
}
setType(type) {
this.type = type;
return this;
}
setFrequency(frequency) {
this.frequency = frequency;
this.operationHistory.push('setFrequency');
return this;
}
addData(data) {
this.pendingData = data;
this.operationHistory.push('addData');
return this;
}
ofType(type) {
if (!this.pendingData) {
console.error('must set data with \'.addData([*your-data*])\' before setting its type');
return this;
}
let translation = TypeTranslation(type);
if (translation) {
let newdata = new Types[translation]();
if (this.pendingName) {
newdata.setName(this.pendingName);
this.pendingName = undefined;
}
newdata.setData(this.pendingData);
this.pendingData = undefined;
this.datachain.push(newdata);
}
this.operationHistory.push('ofType');
return this;
}
withName(name) {
if (this.lastOperation() === 'ofType') {
this.datachain[this.datachain.length - 1].setName(name);
}
else if (this.pendingData) {
this.pendingName = name;
}
else if (!this.pendingData) {
console.error('must set data with \'.addData([*your-data*])\' before setting its name');
return this;
}
else
this.operationHistory.push('withName');
return this;
}
withWeight(weight) {
if (this.datachain[this.datachain.length - 1].type === "SoundPressureLevel") {
this.datachain[this.datachain.length - 1].setWeight(weight);
}
else {
const err = `Must have type SoundPressureLevel!`;
console.error(err);
}
return this;
}
changeWeight(desiredWeight) {
if (!this.frequency) {
const err = `Must set frequency array before converting`;
console.error(err);
}
else if (this.datachain.length==0) {
const err = `Must add data before converting`;
console.error(err);
}
else if (!(this.frequency.length == this.datachain[this.datachain.length-1].data.length)) {
const err = `array mismatch: frequency array has length [${this.frequency.length}] while data array has length [${this.datachain[this.datachain.length-1].data.length}]. They must be the same size.`;
console.error(err);
}
else if (this.datachain[this.datachain.length-1].type!=="SoundPressureLevel"){
const err = `Cannot change the weight of ${this.datachain[this.datachain.length - 1].type} data. must have type SoundPressureLevel`;
console.error(err);
}
else {
const newdata = new Types.SoundPressureLevel();
newdata.setData(this.datachain[this.datachain.length - 1].data);
newdata.setWeight(this.datachain[this.datachain.length - 1].weight);
newdata.setName(this.datachain[this.datachain.length - 1].name);
newdata.changeWeight(desiredWeight, this.frequency);
this.datachain.push(newdata);
return this;
}
}
}
class Complex {
constructor() {
if (arguments.length == 1) {
this.real = arguments[0];
if (Number.isFinite(this.real)) {
this.imag = 0;
} else if (this.real instanceof Array) {
this.imag = new Array(this.real.length).fill(0);
} else {
console.log(this.real);
throw "unsupported data type";
}
}
else if (arguments.length == 2) {
this.real = arguments[0];
this.imag = arguments[1];
}
else if (arguments.length == 0 || arguments.length > 2) {
throw "too many arguments";
}
}
isComplex(n) {
return n?(n instanceof Complex):true;
}
}
const Signal = {
PureTone: (params) => {
let frequency = params.frequency || 440;
let length = params.length || 1;
let gain = params.gain || 1;
let samplerate = params.samplerate || 44100;
let buffersize = params.buffersize || 1024;
let n = samplerate * length;
let s = [];
let j = [];
for (let i = 0; i < n; i++) {
j.push(gain * Math.sin(2 * Math.PI * frequency * i / samplerate));
if (j.length == buffersize) {
s.push(j);
j = [];
}
}
s.push(j);
return s;
}
};
function mul(k, v) {
return new Vector(k * v.x, k * v.y, k * v.z);
}class Vector {
constructor(x, y, z) {
this.x = x;
this.y = y;
this.z = z;
}
mul(k, v) {
return new Vector(k * v.x, k * v.y, k * v.z);
}
sub(v1, v2) {
return new Vector(v1.x - v2.x, v1.y - v2.y, v1.z - v2.z);
}
add(v1, v2) {
return new Vector(v1.x + v2.x, v1.y + v2.y, v1.z + v2.z);
}
dot(v1, v2) {
return v1.x * v2.x + v1.y * v2.y + v1.z * v2.z;
}
mag(v) {
return Math.sqrt(v.x * v.x + v.y * v.y + v.z * v.z);
}
norm(v) {
var _mag = Math.sqrt(v.x * v.x + v.y * v.y + v.z * v.z);
var div = (_mag === 0) ? Infinity : 1.0 / _mag;
return mul(div, v);
}
cross(v1, v2) {
return new Vector(v1.y * v2.z - v1.z * v2.y, v1.z * v2.x - v1.x * v2.z, v1.x * v2.y - v1.y * v2.x);
}
}
const triangleArea = (r1, r2, r3) => {
let a = Vector.prototype.mag(Vector.prototype.sub(r1, r2));
let b = Vector.prototype.mag(Vector.prototype.sub(r2, r3));
let c = Vector.prototype.mag(Vector.prototype.sub(r3, r1));
let p = a + b + c;
let s = p / 2;
return Math.sqrt(s * (s - a) * (s - b) * (s - c));
};
class Surface {
constructor({
name,
faces,
surfaceArea,
materialNumber,
isVar,
hasParent,
children,
materialCoefficients
} = {}) {
this.name = name || "new surface";
this.faces = faces || [];
this.surfaceArea = surfaceArea;
this.modifiedSurfaceArea = surfaceArea;
this.materialNumber = materialNumber;
this.isVar = isVar || false;
this.hasParent = hasParent || false;
this.children = children || [];
this.materialCoefficients = materialCoefficients || [];
this.resolveSabins();
}
resolveSabins() {
if (this.materialCoefficients.length > 0) {
this.sabins = this.materialCoefficients.map(
x => x * this.modifiedSurfaceArea
);
}
}
addChild(surface) {
if (surface.hasParent) {
throw surface.name + " Cannot have more than one parent!"
} else {
this.children.push(surface);
this.children[this.children.length - 1].hasParent = true;
}
return this;
}
setVar(isVar) {
this.isVar = isVar;
return this;
}
}
/*
* Computes the discrete Fourier transform (DFT) of the given complex vector, storing the result back into the vector.
* The vector can have any length. This is a wrapper function.
*/
function transform(real, imag) {
var n = real.length;
if (n != imag.length)
throw "Mismatched lengths";
if (n == 0)
return;
else if ((n & (n - 1)) == 0) // Is power of 2
transformRadix2(real, imag);
else // More complicated algorithm for arbitrary sizes
transformBluestein(real, imag);
}
/*
* Computes the inverse discrete Fourier transform (IDFT) of the given complex vector, storing the result back into the vector.
* The vector can have any length. This is a wrapper function. This transform does not perform scaling, so the inverse is not a true inverse.
*/
function inverseTransform(real, imag) {
transform(imag, real);
}
/*
* Computes the discrete Fourier transform (DFT) of the given complex vector, storing the result back into the vector.
* The vector's length must be a power of 2. Uses the Cooley-Tukey decimation-in-time radix-2 algorithm.
*/
function transformRadix2(real, imag) {
// Length variables
var n = real.length;
if (n != imag.length)
throw "Mismatched lengths";
if (n == 1) // Trivial transform
return;
var levels = -1;
for (var i = 0; i < 32; i++) {
if (1 << i == n)
levels = i; // Equal to log2(n)
}
if (levels == -1)
throw "Length is not a power of 2";
// Trigonometric tables
var cosTable = new Array(n / 2);
var sinTable = new Array(n / 2);
for (var i = 0; i < n / 2; i++) {
cosTable[i] = Math.cos(2 * Math.PI * i / n);
sinTable[i] = Math.sin(2 * Math.PI * i / n);
}
// Bit-reversed addressing permutation
for (var i = 0; i < n; i++) {
var j = reverseBits(i, levels);
if (j > i) {
var temp = real[i];
real[i] = real[j];
real[j] = temp;
temp = imag[i];
imag[i] = imag[j];
imag[j] = temp;
}
}
// Cooley-Tukey decimation-in-time radix-2 FFT
for (var size = 2; size <= n; size *= 2) {
var halfsize = size / 2;
var tablestep = n / size;
for (var i = 0; i < n; i += size) {
for (var j = i, k = 0; j < i + halfsize; j++, k += tablestep) {
var l = j + halfsize;
var tpre = real[l] * cosTable[k] + imag[l] * sinTable[k];
var tpim = -real[l] * sinTable[k] + imag[l] * cosTable[k];
real[l] = real[j] - tpre;
imag[l] = imag[j] - tpim;
real[j] += tpre;
imag[j] += tpim;
}
}
}
// Returns the integer whose value is the reverse of the lowest 'bits' bits of the integer 'x'.
function reverseBits(x, bits) {
var y = 0;
for (var i = 0; i < bits; i++) {
y = (y << 1) | (x & 1);
x >>>= 1;
}
return y;
}
}
/*
* Computes the discrete Fourier transform (DFT) of the given complex vector, storing the result back into the vector.
* The vector can have any length. This requires the convolution function, which in turn requires the radix-2 FFT function.
* Uses Bluestein's chirp z-transform algorithm.
*/
function transformBluestein(real, imag) {
// Find a power-of-2 convolution length m such that m >= n * 2 + 1
var n = real.length;
if (n != imag.length)
throw "Mismatched lengths";
var m = 1;
while (m < n * 2 + 1)
m *= 2;
// Trignometric tables
var cosTable = new Array(n);
var sinTable = new Array(n);
for (var i = 0; i < n; i++) {
var j = i * i % (n * 2); // This is more accurate than j = i * i
cosTable[i] = Math.cos(Math.PI * j / n);
sinTable[i] = Math.sin(Math.PI * j / n);
}
// Temporary vectors and preprocessing
var areal = newArrayOfZeros(m);
var aimag = newArrayOfZeros(m);
for (var i = 0; i < n; i++) {
areal[i] = real[i] * cosTable[i] + imag[i] * sinTable[i];
aimag[i] = -real[i] * sinTable[i] + imag[i] * cosTable[i];
}
var breal = newArrayOfZeros(m);
var bimag = newArrayOfZeros(m);
breal[0] = cosTable[0];
bimag[0] = sinTable[0];
for (var i = 1; i < n; i++) {
breal[i] = breal[m - i] = cosTable[i];
bimag[i] = bimag[m - i] = sinTable[i];
}
// Convolution
var creal = new Array(m);
var cimag = new Array(m);
convolveComplex(areal, aimag, breal, bimag, creal, cimag);
// Postprocessing
for (var i = 0; i < n; i++) {
real[i] = creal[i] * cosTable[i] + cimag[i] * sinTable[i];
imag[i] = -creal[i] * sinTable[i] + cimag[i] * cosTable[i];
}
}
/*
* Computes the circular convolution of the given complex vectors. Each vector's length must be the same.
*/
function convolveComplex(xreal, ximag, yreal, yimag, outreal, outimag) {
var n = xreal.length;
if (n != ximag.length || n != yreal.length || n != yimag.length
|| n != outreal.length || n != outimag.length)
throw "Mismatched lengths";
xreal = xreal.slice();
ximag = ximag.slice();
yreal = yreal.slice();
yimag = yimag.slice();
transform(xreal, ximag);
transform(yreal, yimag);
for (var i = 0; i < n; i++) {
var temp = xreal[i] * yreal[i] - ximag[i] * yimag[i];
ximag[i] = ximag[i] * yreal[i] + xreal[i] * yimag[i];
xreal[i] = temp;
}
inverseTransform(xreal, ximag);
for (var i = 0; i < n; i++) { // Scaling (because this FFT implementation omits it)
outreal[i] = xreal[i] / n;
outimag[i] = ximag[i] / n;
}
}
function newArrayOfZeros(n) {
var result = [];
for (var i = 0; i < n; i++)
result.push(0);
return result;
}
const FFT = (real) => (imag) => {
const re = [];
real.forEach((v, i, a) => {
re.push(v);
});
if (!imag) {
const im = [];
real.forEach((v, i, a) => {
im.push(0);
});
const transformdata = {
real: re,
imag: im
};
transform(transformdata.real, transformdata.imag);
return transformdata;
}
else {
const im = [];
if (imag) {
imag.forEach((v, i, a) => {
im.push(v);
});
}
const transformdata = {
real: Object.assign(real, re),
imag: Object.assign(real, im)
};
transform(transformdata.real, transformdata.imag);
return transformdata;
}
};
const IFFT = real => imag => {
const re = [];
real.forEach((v, i, a) => {
re.push(v);
});
if (!imag) {
const im = [];
real.forEach((v, i, a) => {
im.push(0);
});
const transformdata = {
real: re,
imag: im
};
inverseTransform(transformdata.real, transformdata.imag);
return transformdata;
} else {
const im = [];
if (imag) {
imag.forEach((v, i, a) => {
im.push(v);
});
}
const transformdata = {
real: Object.assign(real, re),
imag: Object.assign(real, im)
};
inverseTransform(transformdata.real, transformdata.imag);
return transformdata;
}
};
const RMS = (samples) => Math.sqrt(samples.map(p => p * p).reduce((a, b) => a + b) / samples.length);
/** Energy Density Calculation
* Architectural Acoustics pg. 64 'Energy Density' Marshal Long, Second Edition
* @function EnergyDensity
* @param {Number} E Energy Contained in a Sound Wave
* @param {Number} S Measurement Area
* @param {Number} c Speed of Sound
* @param {Number} t Time
* @param {Number} W Power
* @param {Number} I Intensity
* @param {Number} p Pressure
* @param {Number} rho Bulk Density of Medium
*/
function EnergyDensity({ E, S, c, t, W, I, p, rho, help } = {}) {
if (E && S && c && t) {
return E / (S * c * t);
} else if (W && S && c) {
return W / (S * c);
} else if (I && c) {
return I / c;
} else if (p && rho && c) {
return (p * p) / (rho * c * c);
} else throw "Not enough input parameters given";
}
/** Converts Sound Pressure in (Pa) to Sound Pressure Level in (dB)
* @function p2dB
* @param {Number|Number[]} p Sound Pressure
* @param {String} [units] Units for the result
*/
function p2dB({ p, units } = {}) {
if (p) {
if (units) {
if (Number.isFinite(p)) {
return 20 * Math.log10(lib(p).from(units).to('Pa') / pref.value);
} else if (p instanceof Array) {
return p.map(x => 20 * Math.log10(lib(x).from(units).to('Pa') / pref.value));
} else {
throw "p needs to be a number or an array"
}
} else {
if (Number.isFinite(p)) {
return 20 * Math.log10(p / pref.value);
} else if (p instanceof Array) {
return p.map(x => 20 * Math.log10(x/ pref.value));
} else {
throw "p needs to be a number or an array"
}
}
}
}
/** Converts Sound Pressure Level(Lp) in dB to Sound Pressure in (Pa)
* @function dB2p
* @param {Number|Number[]} dB Sound Pressure Level
* @param {String} [units] Units for the result
*/
function dB2p({ dB, units } = {}) {
if (dB) {
if (units) {
if (Number.isFinite(dB)) {
return Math.pow(10,dB/20) * lib(pref.value).from('Pa').to(units);
} else if (dB instanceof Array) {
return dB.map(x => Math.pow(10, x / 20) * lib(pref.value).from('Pa').to(units));
} else {
throw "dB needs to be a number or an array"
}
} else {
if (Number.isFinite(dB)) {
return Math.pow(10, dB / 20) * pref.value;
} else if (dB instanceof Array) {
return dB.map(x => Math.pow(10, x / 20) * pref.value);
} else {
throw "dB needs to be a number or an array"
}
}
}
}
/** Converts Sound Intensity in (W) to Sound Intensity Level in (dB)
* @function I2dB
* @param {Number|Number[]} I Sound Intensity
* @param {String} [units] Units for the result
*/
function I2dB({ I, units } = {}) {
if (I) {
if (units) {
if (Number.isFinite(I)) {
return 10 * Math.log10(lib(I).from(units).to('W/m2') / Iref.value);
} else if (I instanceof Array) {
return I.map(x => 10 * Math.log10(lib(x).from(units).to('W/m2') / Iref.value));
} else {
throw "I needs to be a number or an array"
}
} else {
if (Number.isFinite(I)) {
return 10 * Math.log10(I / Iref.value);
} else if (I instanceof Array) {
return I.map(x => 10 * Math.log10(x / Iref.value));
} else {
throw "I needs to be a number or an array"
}
}
}
}
/** Converts Sound Intensity Level (LI) in dB to Sound Power in (W)
* @function dB2I
* @param {Number|Number[]} dB Sound Intensity Level
* @param {String} [units] Units for the result
*/
function dB2I({ dB, units } = {}) {
if (dB) {
if (units) {
if (Number.isFinite(dB)) {
return Math.pow(10, dB / 10) * lib(Iref.value).from('W/m2').to(units);
} else if (dB instanceof Array) {
return dB.map(x => Math.pow(10, x / 10) * lib(Iref.value).from('W/m2').to(units));
} else {
throw "dB needs to be a number or an array"
}
} else {
if (Number.isFinite(dB)) {
return Math.pow(10, dB / 10) * Iref.value;
} else if (dB instanceof Array) {
return dB.map(x => Math.pow(10, x / 10) * Iref.value);
} else {
throw "dB needs to be a number or an array"
}
}
}
}
/** Converts Sound Power in (W) to Sound Power Level(Lw) in dB
* @function W2dB
* @param {Number|Number[]} W Sound Power
* @param {String} [units] Units for the result
*/
function W2dB({ W, units } = {}) {
if (W) {
if (units) {
if (Number.isFinite(W)) {
return 10 * Math.log10(lib(W).from(units).to('W') / Wref.value);
} else if (W instanceof Array) {
return W.map(x => 10 * Math.log10(lib(x).from(units).to('W') / Wref.value));
} else {
throw "W needs to be a number or an array"
}
} else {
if (Number.isFinite(W)) {
return 10 * Math.log10(W / Iref.value);
} else if (W instanceof Array) {
return W.map(x => 10 * Math.log10(x / Wref.value));
} else {
throw "W needs to be a number or an array"
}
}
}
}
/** Converts Sound Power Level (Lw) in dB to Sound Power in (W)
* @function dB2W
* @param {Number|Number[]} dB Sound Power Level
* @param {String} [units] Units for the result
*/
function dB2W({ dB, units } = {}) {
if (dB) {
if (units) {
if (Number.isFinite(dB)) {
return Math.pow(10, dB / 10) * lib(Wref.value).from('W').to(units);
} else if (dB instanceof Array) {
return dB.map(x => Math.pow(10, x / 10) * lib(Wref.value).from('W').to(units));
} else {
throw "dB needs to be a number or an array"
}
} else {
if (Number.isFinite(dB)) {
return Math.pow(10, dB / 10) * Wref.value;
} else if (dB instanceof Array) {
return dB.map(x => Math.pow(10, x / 10) * Wref.value);
} else {
throw "dB needs to be a number or an array"
}
}
}
}
/** Determination of sound power levels of noise sources using sound intensity by scanning
* @param {Number[]} freq Array of frequencies
* @param {Object[]} surfaces Array of objects with members SurfaceArea: Number and SoundIntensityLevel: Number[]
*/
function SoundPowerScan({ frequency, surfaceData } = {}) {
let sum = Array(frequency.length).fill(0);
surfaceData.forEach((surface, index, arr) => {
arr[index].SoundIntensity = dB2I({
dB: surface.SoundIntensityLevel
});
arr[index].SoundPower = arr[index].SoundIntensity.map(x => x * surface.SurfaceArea);
arr[index].SoundPower.forEach((s, i) => {
sum[i] += s;
});
});
let SoundPowerLevel = W2dB({
W: sum
});
return SoundPowerLevel;
}
/** Calcualtes the electrical power required by an amplifier
* Marshal Long pg. 689
* @function PowerDemand
* @param {Number} channels - number of amplifier channels (assuming 2 channels per amplifier)
* @param {Number} J - rated amplifier output power for one channel in Watts
* @param {Number} duty - duty cycle
* @param {Number} efficieny - amplifier efficiency
* @param {Number} Jq - quiescent power for zero input voltage (defaults to 90W)
*/
function PowerDemand({ channels, J, duty, efficieny, Jq }) {
return channels * j * duty * (1 / efficieny) + channels * (Jq||90) * (1 / 2);
}
/** Calculates the electrical current from the AC Main (A)
* @function CurrentDemand
* @param {Number} Je - Power Demand
* @param {Number} Ve - electrical voltage from the AC Main (V)
* @param {Number} f - Power factor (defaults to 0.83)
*/
function CurrentDemand({ Je, Ve, f }) {
return Je / (Ve * (f || 0.83));
}
const hann = (n, N) => Math.pow(Math.sin(Math.PI * n / (N - 1)), 2);
/** Hann window
* @function Hann
* @param {number} N Length of the window
* @returns {number[]} a Hann window of length N
*/
function Hann(N) {
return Object.keys(Array(N).fill(0)).map(x => hann(Number(x), N));
}
const readTextFile = ({ element, loaded, error } = {}) => {
const reader = new FileReader();
reader.onload = event => loaded(event.target.result);
reader.onerror = err => error(err);
reader.readAsText(element.files[0]);
};
/**
* @function sum - Calculates the sum of a number array;
* @param {Number[]} arr - Array of numbers;
* @returns {Number} - Returns the sum of a number array
*/
const sum = arr => arr.reduce((a, b) => a + b);
/** Calculates the signed volume of a triangle for 3D mesh calc
* @function triangleVolume
* @param {Object|Vector} p1 - Vector p1 containing components x,y,z;
* @param {Object|Vector} p2 - Vector p1 containing components x,y,z;
* @param {Object|Vector} p3 - Vector p1 containing components x,y,z;
* @returns {Number} Returns signed volume of a triangle
* @see https://stackoverflow.com/questions/1406029/how-to-calculate-the-volume-of-a-3d-mesh-object-the-surface-of-which-is-made-up
* @see http://chenlab.ece.cornell.edu/Publication/Cha/icip01_Cha.pdf
*/
const triangleVolume = (p1, p2, p3) => {
const v321 = p3.x * p2.y * p1.z;
const v231 = p2.x * p3.y * p1.z;
const v312 = p3.x * p1.y * p2.z;
const v132 = p1.x * p3.y * p2.z;
const v213 = p2.x * p1.y * p3.z;
const v123 = p1.x * p2.y * p3.z;
return (1.0 / 6.0 ) * (-v321 + v231 + v312 - v132 - v213 + v123);
};
/** Calculates the volume of a mesh of triangles
* @function meshVolume
* @param {Object[]} mesh - Array of triangles of the form [ {x,y,z}, {x,y,z}, {x,y,z} ]
*/
const meshVolume = (triangles) => {
const vols = triangles.map(tri => triangleVolume(...Object.keys(tri).map(u => tri[u])));
return Math.abs(sum(vols));
};
var _createClass = function () {
function defineProperties(target, props) {
for (var i = 0; i < props.length; i++) {
var descriptor = props[i];
descriptor.enumerable = descriptor.enumerable || false;
descriptor.configurable = true;
if ("value" in descriptor) descriptor.writable = true;
Object.defineProperty(target, descriptor.key, descriptor);
}
}
return function (Constructor, protoProps, staticProps) {
if (protoProps) defineProperties(Constructor.prototype, protoProps);
if (staticProps) defineProperties(Constructor, staticProps);
return Constructor;
};
}();
function _classCallCheck(instance, Constructor) {
if (!(instance instanceof Constructor)) {
throw new TypeError("Cannot call a class as a function");
}
}
var OBJFile = function () {
function OBJFile(fileContents, defaultModelName) {
_classCallCheck(this, OBJFile);
this._reset();
this.fileContents = fileContents;
this.defaultModelName = defaultModelName || 'untitled';
}
_createClass(OBJFile, [{
key: '_reset',
value: function _reset() {
this.result = {
models: [],
materialLibraries: []
};
this.currentMaterial = '';
this.currentGroup = '';
this.smoothingGroup = 0;
}
}, {
key: 'parse',
value: function parse() {
this._reset();
var _stripComments = function _stripComments(lineString) {
var commentIndex = lineString.indexOf('#');
if (commentIndex > -1) {
return lineString.substring(0, commentIndex);
}
return lineString;
};
var lines = this.fileContents.split('\n');
for (var i = 0; i < lines.length; i += 1) {
var line = _stripComments(lines[i]);
var lineItems = line.replace(/\s\s+/g, ' ').trim().split(' ');
switch (lineItems[0].toLowerCase()) {
case 'o':
// Start A New Model
this._parseObject(lineItems);
break;
case 'g':
// Start a new polygon group
this._parseGroup(lineItems);
break;
case 'v':
// Define a vertex for the current model
this._parseVertexCoords(lineItems);
break;
case 'vt':
// Texture Coords
this._parseTextureCoords(lineItems);
break;
case 'vn':
// Define a vertex normal for the current model
this._parseVertexNormal(lineItems);
break;
case 's':
// Smooth shading statement
this._parseSmoothShadingStatement(lineItems);
break;
case 'f':
// Define a Face/Polygon
this._parsePolygon(lineItems);
break;
case 'mtllib':
// Reference to a material library file (.mtl)
this._parseMtlLib(lineItems);
break;
case 'usemtl':
// Sets the current material to be applied to polygons defined from this point forward
this._parseUseMtl(lineItems);
break;
}
}
this.result.vertices = [
{
x: 0,
y: 0,
z: 0
}].concat(
this.result.models
.map(x => x.vertices)
.flat()
).map(x => {
return new Vector(x.x, x.y, x.z);
});
const tempVerts = this.result.vertices;
this.result.models.forEach((x,i,a) => {
let sum = 0;
a[i].faces.forEach((f, j, b) => {
b[j].verts = f.vertices.map(v => tempVerts[v.vertexIndex]);
b[j].area = triangleArea(b[j].verts[0], b[j].verts[1], b[j].verts[2]);
b[j].triangle = [b[j].verts[0], b[j].verts[1], b[j].verts[2]];
sum += b[j].area;
});
a[i].surfaceArea = sum;
});
this.result.surfaceArea = this.result.models.map(x => x.surfaceArea).reduce((a, b) => a + b);
this.result.volume = meshVolume(this.result.models.map(x => x.faces.map(f => f.triangle)).flat());
return this.result;
}
}, {
key: '_currentModel',
value: function _currentModel() {
if (this.result.models.length == 0) {
this.result.models.push({
name: this.defaultModelName,
vertices: [],
textureCoords: [],
vertexNormals: [],
faces: []
});
this.currentGroup = '';
this.smoothingGroup = 0;
}
return this.result.models[this.result.models.length - 1];
}
}, {
key: '_parseObject',
value: function _parseObject(lineItems) {
var modelName = lineItems.length >= 2 ? lineItems[1] : this._getDefaultModelName();
this.result.models.push({
name: modelName,
vertices: [],
textureCoords: [],
vertexNormals: [],
faces: []
});
this.currentGroup = '';
this.smoothingGroup = 0;
}
}, {
key: '_parseGroup',
value: function _parseGroup(lineItems) {
if (lineItems.length != 2) {
throw 'Group statements must have exactly 1 argument (eg. g group_1)';
}
this.currentGroup = lineItems[1];
}
}, {
key: '_parseVertexCoords',
value: function _parseVertexCoords(lineItems) {
var x = lineItems.length >= 2 ? parseFloat(lineItems[1]) : 0.0;
var y = lineItems.length >= 3 ? parseFloat(lineItems[2]) : 0.0;
var z = lineItems.length >= 4 ? parseFloat(lineItems[3]) : 0.0;
this._currentModel().vertices.push({
x: x,
y: y,
z: z
});
}
}, {
key: '_parseTextureCoords',
value: function _parseTextureCoords(lineItems) {
var u = lineItems.length >= 2 ? parseFloat(lineItems[1]) : 0.0;
var v = lineItems.length >= 3 ? parseFloat(lineItems[2]) : 0.0;
var w = lineItems.length >= 4 ? parseFloat(lineItems[3]) : 0.0;
this._currentModel().textureCoords.push({
u: u,
v: v,
w: w
});
}
}, {
key: '_parseVertexNormal',
value: function _parseVertexNormal(lineItems) {
var x = lineItems.length >= 2 ? parseFloat(lineItems[1]) : 0.0;
var y = lineItems.length >= 3 ? parseFloat(lineItems[2]) : 0.0;
var z = lineItems.length >= 4 ? parseFloat(lineItems[3]) : 0.0;
this._currentModel().vertexNormals.push({
x: x,
y: y,
z: z
});
}
}, {
key: '_parsePolygon',
value: function _parsePolygon(lineItems) {
var totalVertices = lineItems.length - 1;
if (totalVertices < 3) {
throw 'Face statement has less than 3 vertices' + this.filePath + this.lineNumber;
}
var face = {
material: this.currentMaterial,
group: this.currentGroup,
smoothingGroup: this.smoothingGroup,
vertices: []
};
for (var i = 0; i < totalVertices; i += 1) {
var vertexString = lineItems[i + 1];
var vertexValues = vertexString.split('/');
if (vertexValues.length < 1 || vertexValues.length > 3) {
throw 'Two many values (separated by /) for a single vertex' + this.filePath + this.lineNumber;
}
var vertexIndex = 0;
var textureCoordsIndex = 0;
var vertexNormalIndex = 0;
vertexIndex = parseInt(vertexValues[0]);
if (vertexValues.length > 1 && !vertexValues[1] == '') {
textureCoordsIndex = parseInt(vertexValues[1]);
}
if (vertexValues.length > 2) {
vertexNormalIndex = parseInt(vertexValues[2]);
}
if (vertexIndex == 0) {
throw 'Faces uses invalid vertex index of 0';
}
// Negative vertex indices refer to the nth last defined vertex
// convert these to postive indices for simplicity
if (vertexIndex < 0) {
vertexIndex = this._currentModel().vertices.length + 1 + vertexIndex;
}
face.vertices.push({
vertexIndex: vertexIndex,
textureCoordsIndex: textureCoordsIndex,
vertexNormalIndex: vertexNormalIndex
});
}
this._currentModel().faces.push(face);
}
}, {
key: '_parseMtlLib',
value: function _parseMtlLib(lineItems) {
if (lineItems.length >= 2) {
this.result.materialLibraries.push(lineItems[1]);
}
}
}, {
key: '_parseUseMtl',
value: function _parseUseMtl(lineItems) {
if (lineItems.length >= 2) {
this.currentMaterial = lineItems[1];
}
}
}, {
key: '_parseSmoothShadingStatement',
value: function _parseSmoothShadingStatement(lineItems) {
if (lineItems.length != 2) {
throw 'Smoothing group statements must have exactly 1 argument (eg. s <number|off>)';
}
var groupNumber = lineItems[1].toLowerCase() == 'off' ? 0 : parseInt(lineItems[1]);
this.smoothingGroup = groupNumber;
}
}]);
return OBJFile;
}();
// module.exports = OBJFile;
const ParseOBJ = (obj) => new OBJFile(obj).parse();
const ParseOBJ_dom = ({ element, loaded, error } = {}) => {
readTextFile({
element,
loaded: e => {
loaded(new OBJFile(e).parse());
},
error
});
};
/** Calculates air attenuation
* ANSI Standard S1-26:1995, or ISO 9613-1:1996.
* @see https://www.mne.psu.edu/lamancusa/me458/10_osp.pdf
*
* @param {Number|Number[]} frequency - frequency (or frequencies) to evaluate at
* @param {Number} [temperature] - Temperature (defaults to 70F)
* @param {String} [temperatureUnits] - units for the input temperature (defaults to F)
* @param {Number} [humidity] - relative humidity as a percentage (defaults to 50)
* @param {Number} [pressure] - atmospheric pressure in atm
* @param {String} [attenuationUnits] - either "ft" or "m" (defaults to ft);
* @returns {Number|Number[]} air attenuation in dB/(ft or m);
*/
function airAttenuation({
frequency,
temperature,
temperatureUnits,
humidity,
pressure,
attenuationUnits
}) {
humidity = humidity || 40;
attenuationUnits = attenuationUnits || "ft";
temperature = temperature || 68;
temperatureUnits = temperatureUnits || "F";
temperature = units.convert(temperature).from(temperatureUnits).to('K');
const _airAttenuation = (f) => {
let C_humid = 4.6151 - 6.8346 * Math.pow((273.15 / temperature), 1.261);
let hum = humidity * Math.pow(10, C_humid);
let f2 = f * f;
let coef1 = 869 * f2;
let trel = temperature / 293.15;
let f_relax_oxygen = (24 + 4.04e4 * hum * (0.02 + hum) / (0.391 + hum));
let f_relax_nitrogen = Math.pow(trel, -0.5) * (9 + 280 * hum * Math.exp(-4.17 * (Math.pow(trel, -1 / 3) - 1)));
let denominator_oxygen = f_relax_oxygen + f2 / f_relax_oxygen;
let denominator_nitrogen = f_relax_nitrogen + f2 / f_relax_nitrogen;
let root_relativeTemp = Math.sqrt(trel);
let eq_coef = Math.pow(trel, -2.5);
let eq_oxygen = 0.01275 * Math.exp(-2239.1 / temperature) / denominator_oxygen;
let eq_nitrogen = 0.10680 * Math.exp(-3352.0 / temperature) / denominator_nitrogen;
let alpha = 0.001 * coef1 * (1.84e-11 * root_relativeTemp + eq_coef * (eq_oxygen + eq_nitrogen));
if (attenuationUnits === "ft") {
alpha = alpha / 3.28;
}
return alpha;
};
if (frequency instanceof Array) {
return frequency.map(f => _airAttenuation(f));
} else if (Number.isFinite(frequency)) {
return _airAttenuation(frequency);
}
}
class RT {
constructor({
surfaces,
volume,
units,
frequency
} = {}) {
this.surfaces = surfaces || [];
this.units = units || "m";
this.volume = volume || undefined;
this.setFrequency(frequency || Bands.Octave.fromRange(125, 4000));
this.resolveUnitConstant();
this.resolveSurfaceArea();
this.resolveRelations();
}
addSurface(surface) {
if (surface instanceof Surface) {
this.surfaces.push(surface);
this.resolveSurfaceArea();
this.resolveRelations();
}
return this;
}
setFrequency(frequency) {
this.frequency = frequency;
this.setAirAbsorption(airAttenuation({
frequency: frequency
}));
return this;
}
setVolume(volume) {
this.volume = volume;
return this;
}
setUnits(units) {
this.units = units;
this.resolveUnitConstant();
return this;
}
setAirAbsorption(m) {
this.AirAbsorption = m;
return this;
}
resolveUnitConstant() {
switch (this.units) {
case "ft":
this.unitConstant = 0.049;
break;
case "m":
this.unitConstant = 0.161;
break;
default:
break;
}
}
resolveRelations() {
if (this.surfaces.length > 0) {
this.varIndices = this.surfaces.map((x, i) => x.isVar ? i : null).filter(x => x !== null);
}
}
resolveSurfaceArea() {
if (this.surfaces.length > 0) {
this.surfaceArea = this.surfaces.map(x => x.modifiedSurfaceArea).reduce((a, b) => a + b);
}
}
getSurfaceByName(name) {
return this.surfaces.filter(x => x.name === name)[0];
}
calculateRT() {
let num = this.unitConstant * this.volume;
if (this.frequency) {
if (this.frequency instanceof Array) {
let sum = Array(this.frequency.length).fill(0);
this.surfaces.forEach(s => {
s.sabins.forEach((m, i) => {
sum[i] += m;
});
});
this.absorption = sum;
this.meanAlpha = sum.map(x => x / this.surfaceArea);
this.T60 = this.meanAlpha.map((x, i, a) => {
if (x < 0.2) {
return num / (this.surfaceArea * x + (4 * (this.AirAbsorption[i] || 0) * this.volume));
} else {
return num / (-this.surfaceArea * Math.log(1 - x) + (4 * (this.AirAbsorption[i] || 0) * this.volume));
}
});
console.log(this.T60);
}
}
}
}
class RTOptimizer extends RT {
constructor(props) {
super(props);
}
setStepSize(stepSize) {
this.stepSize = stepSize;
return this;
}
setIterRate(iterRate) {
this.iterRate = iterRate;
return this;
}
setGoal(goal) {
this.goal = goal;
this.goalprime = [];
for (let i = 0; i < this.goal.length - 1; i++) {
this.goalprime.push(this.goal[i + 1] - this.goal[i]);
}
return this;
}
setIterCount(iterCount) {
this.iterCount = iterCount;
return this;
}
calculateMSE(shouldSetMSE, t60, goal) {
let error = t60.map((t, i) => goal[i] - t);
let mse = error.map(e => e * e).reduce((a, b) => a + b) / error.length;
if (shouldSetMSE) this.MSE = mse;
return mse;
}
calculateMSEQuiet(arr1, arr2) {
let error = arr1.map((t, i) => arr2[i] - t);
let mse = error.map(e => e * e).reduce((a, b) => a + b) / error.length;
return mse;
}
calculateRT(shouldSetT60) {
this.resolveSurfaceArea();
let num = this.unitConstant * this.volume;
if (this.frequency) {
if (this.frequency instanceof Array) {
let sum = Array(this.frequency.length).fill(0);
this.surfaces.forEach(s => {
//s.resolveSabins();
s.materialCoefficients.forEach((m, i) => {
sum[i] += m * s.modifiedSurfaceArea;
});
});
this.absorption = sum;
this.meanAlpha = sum.map(x => x / this.surfaceArea);
let t60 = this.meanAlpha.map((x, i, a) => {
if (x < 0.2) {
return num / (this.surfaceArea * x + (4 * (this.AirAbsorption[i] || 0) * this.volume));
} else {
return num / (-this.surfaceArea * Math.log(1 - x) + (4 * (this.AirAbsorption[i] || 0) * this.volume));
}
});
if (shouldSetT60) {
this.T60 = t60;
this.T60Prime = this.calculateRTPrime(this.T60);
} return t60;
}
}
}
calculateRTPrime(rt) {
let rtprime = [];
for (let i = 0; i < rt.length - 1; i++) {
rtprime.push(rt[i + 1] - rt[i]);
}
return rtprime;
}
optimize(N, timeout = 0) {
Array(N).fill(0).forEach(x => {
let mse = this.calculateMSE(true, this.T60, this.goal);
let tempt60prime;
let tempMSE;
for (let i = 0; i < this.surfaces.length; i++) {
if (this.surfaces[i].children.length > 0) {
for (let j = 0; j < this.surfaces[i].children.length; j++) {
this.surfaces[i].children[j].modifiedSurfaceArea = clamp(this.surfaces[i].children[j].modifiedSurfaceArea + this.stepSize, 0, this.surfaces[i].surfaceArea);
this.surfaces[i].modifiedSurfaceArea = clamp(this.surfaces[i].modifiedSurfaceArea - this.stepSize, 0, this.surfaces[i].surfaceArea);
tempt60prime = this.calculateRTPrime(this.calculateRT(false));
tempMSE = this.calculateMSEQuiet(tempt60prime, this.goalprime);
if (tempMSE > mse) {
this.surfaces[i].children[j].modifiedSurfaceArea = clamp(this.surfaces[i].children[j].modifiedSurfaceArea - 2 * this.stepSize, 0, this.surfaces[i].surfaceArea);
this.surfaces[i].modifiedSurfaceArea = clamp(this.surfaces[i].modifiedSurfaceArea + 2 * this.stepSize, 0, this.surfaces[i].surfaceArea);
tempt60prime = this.calculateRTPrime(this.calculateRT(false));
tempMSE = this.calculateMSEQuiet(tempt60prime, this.goalprime);
if (tempMSE > mse) {
this.surfaces[i].children[j].modifiedSurfaceArea = clamp(this.surfaces[i].children[j].modifiedSurfaceArea + this.stepSize, 0, this.surfaces[i].surfaceArea);
this.surfaces[i].modifiedSurfaceArea = clamp(this.surfaces[i].modifiedSurfaceArea - this.stepSize, 0, this.surfaces[i].surfaceArea);
tempt60prime = this.calculateRTPrime(this.calculateRT(false));
tempMSE = this.calculateMSEQuiet(tempt60prime, this.goalprime);
}
}
this.T60Prime = tempt60prime;
mse = tempMSE;
}
}
}
console.log(this.surfaces[1].modifiedSurfaceArea, this.surfaces[7].modifiedSurfaceArea);
});
console.log(this.surfaces);
}
optimizePrime(N) {
Array(N).fill(0).forEach(x => {
let mse = this.calculateMSEQuiet(this.T60Prime, this.goalprime);
let tempt60prime;
let tempMSE;
for (let i = 0; i < this.surfaces.length; i++) {
if (this.surfaces[i].children.length > 0) {
for (let j = 0; j < this.surfaces[i].children.length; j++) {
var tempChildSA = clamp(this.surfaces[i].children[j].modifiedSurfaceArea + this.stepSize, 0, this.surfaces[i].surfaceArea);
var tempParentSA = clamp(this.surfaces[i].modifiedSurfaceArea - this.stepSize, 0, this.surfaces[i].surfaceArea);
if (tempChildSA + tempParentSA == this.surfaces[i].children[j].surfaceArea + this.surfaces[i].surfaceArea) {
this.surfaces[i].children[j].modifiedSurfaceArea = tempChildSA;
this.surfaces[i].modifiedSurfaceArea = tempParentSA;
}
tempt60prime = this.calculateRT(false);
tempMSE = this.calculateMSE(false, tempt60prime, this.goal);
if (tempMSE > mse) {
tempChildSA = clamp(this.surfaces[i].children[j].modifiedSurfaceArea - 2 * this.stepSize, 0, this.surfaces[i].surfaceArea);
tempParentSA = clamp(this.surfaces[i].modifiedSurfaceArea + 2 * this.stepSize, 0, this.surfaces[i].surfaceArea);
if (tempChildSA + tempParentSA == this.surfaces[i].children[j].surfaceArea + this.surfaces[i].surfaceArea) {
this.surfaces[i].children[j].modifiedSurfaceArea = tempChildSA;
this.surfaces[i].modifiedSurfaceArea = tempParentSA;
}
tempt60prime = this.calculateRT(false);
tempMSE = this.calculateMSE(false, tempt60prime, this.goal);
if (tempMSE > mse) {
var tempChildSA = clamp(this.surfaces[i].children[j].modifiedSurfaceArea + this.stepSize, 0, this.surfaces[i].surfaceArea);
var tempParentSA = clamp(this.surfaces[i].modifiedSurfaceArea - this.stepSize, 0, this.surfaces[i].surfaceArea);
if (tempChildSA + tempParentSA == this.surfaces[i].children[j].surfaceArea + this.surfaces[i].surfaceArea) {
this.surfaces[i].children[j].modifiedSurfaceArea = tempChildSA;
this.surfaces[i].modifiedSurfaceArea = tempParentSA;
}
tempt60prime = this.calculateRT(false);
tempMSE = this.calculateMSE(false, tempt60prime, this.goal);
}
}
this.T60 = tempt60prime;
mse = tempMSE;
this.surfaces[i].children[j].delta = this.surfaces[i].children[j].modifiedSurfaceArea - this.surfaces[i].children[j].surfaceArea;
this.surfaces[i].delta = this.surfaces[i].modifiedSurfaceArea - this.surfaces[i].surfaceArea;
}
}
}
// console.log(this.surfaces[1].modifiedSurfaceArea+this.surfaces[7].modifiedSurfaceArea);
});
console.log(this.surfaces.map(x => x.delta).filter(x => typeof x !== "undefined"));
}
}
var functionalAcoustics = {
A,
B,
Weight,
Conversion,
Bands,
OctaveBands,
ThirdOctaveBands,
Flower,
Fupper,
dBsum,
Transmission,
Properties,
RoomModes,
pref,
Wref,
Iref,
Measurement,
Types,
FFT,
IFFT,
RT,
RTOptimizer,
Surface,
Buffer: buffer,
RMS,
units,
Complex,
Signal,
EnergyDensity,
p2dB,
dB2p,
I2dB,
dB2I,
W2dB,
dB2W,
SoundPowerScan,
round,
Hann,
PowerDemand,
CurrentDemand,
readTextFile,
ParseOBJ,
ParseOBJ_dom,
Vector,
airAttenuation
};
return functionalAcoustics;
}));