/*
* This file is part of AUX.
*
* AUX is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* AUX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General
* Public License along with this program; if not, write to the
* Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301 USA
*/
import { dBToScale, scaleToDB, freqToScale, scaleToFreq } from './audiomath.js';
/**
* Clamps <code>value</code> into the range
* between <code>min</code> and <code>max</code>,
* inclusively.
*/
export function clamp(min, max, value) {
if (!(value > min)) return min;
if (!(value < max)) return max;
return value;
}
/**
* A factory function which creates a snap module which
* snaps to values which are distributed at equal distance
* on a range.
*/
export function LinearSnapModule({ base, clip, max, min, step }) {
min = +min;
max = +max;
step = +step;
base = +base;
clip = !!clip;
function lowSnap(v, direction) {
v = +v;
direction = +direction;
let n = 0.0;
let t = 0.0;
if (clip) {
if (!(v > min)) {
v = min;
direction = 1.0;
} else if (!(v < max)) {
v = max;
direction = +1.0;
}
}
t = (v - base) / step;
if (direction > 0.0) n = Math.ceil(t);
else if (direction < 0.0) n = Math.floor(t);
else {
if (t - Math.floor(t) < 0.5) {
n = Math.floor(t);
} else {
n = Math.ceil(t);
}
}
return base + step * n;
}
/**
* Returns the nearest value on the grid which is bigger than <code>value</code>.
*
* @method Ranged#snapUp
*
* @param {number} value - The value to snap.
*
* @returns {number} The snapped value.
*/
function snapUp(v) {
v = +v;
return +lowSnap(v, 1.0);
}
/**
* Returns the nearest value on the grid which is smaller than <code>value</code>.
*
* @method Ranged#snapDown
*
* @param {number} value - The value to snap.
*
* @returns {number} The snapped value.
*/
function snapDown(v) {
v = +v;
return +lowSnap(v, -1.0);
}
/**
* Returns the nearest value on the grid. Its rounding behavior is similar to that
* of <code>Math.round</code>.
*
* @method Ranged#snap
*
* @param {number} value - The value to snap.
*
* @returns {number} The snapped value.
*/
function snap(v) {
v = +v;
return +lowSnap(v, 0.0);
}
return {
snapUp: snapUp,
snapDown: snapDown,
snap: snap,
};
}
/**
* A factory function which creates a snap module which snaps to values
* in a sorted list.
*/
export function ArraySnapModule({ clip, max, min }, heap) {
const values = new Float64Array(heap);
const len = (heap.byteLength >> 3) | 0;
if (min !== void 0) {
min = +min;
} else {
min = values[0];
}
if (max !== void 0) {
max = +max;
} else {
max = values[len - 1];
}
clip = !!clip;
function lowSnap(v, direction) {
v = +v;
direction = +direction;
let a = 0;
let mid = 0;
let b = 0;
let t = 0.0;
b = len - 1;
if (clip) {
v = clamp(min, max, v);
}
if (!(v < +values[(b << 3) >> 3])) return +values[(b << 3) >> 3];
if (!(v > +values[0])) return +values[0];
do {
mid = (a + b) >>> 1;
t = +values[(mid << 3) >> 3];
if (v > t) a = mid;
else if (v < t) b = mid;
else return t;
} while (((b - a) | 0) > 1);
if (direction > 0.0) return +values[(b << 3) >> 3];
else if (direction < 0.0) return +values[(a << 3) >> 3];
if (values[(b << 3) >> 3] - v <= v - values[(a << 3) >> 3])
return +values[(b << 3) >> 3];
return +values[(a << 3) >> 3];
}
function snapUp(v) {
v = +v;
return +lowSnap(v, 1.0);
}
function snapDown(v) {
v = +v;
return +lowSnap(v, -1.0);
}
function snap(v) {
v = +v;
return +lowSnap(v, 0.0);
}
return {
snapUp: snapUp,
snapDown: snapDown,
snap: snap,
};
}
/**
* A factory function which creates a snap modules which does
* no snapping. If <code>options.clip</code> is <code>true</code>,
* it will clip to <code>options.min</code> and <code>options.max</code>.
*/
export function NullSnapModule({ max, min, clip }) {
min = +min;
max = +max;
clip = !!clip;
function snap(v) {
v = +v;
if (clip) {
if (!(v < max)) v = max;
if (!(v > min)) v = min;
}
return v;
}
return {
snap: snap,
snapUp: snap,
snapDown: snap,
};
}
/**
* Does not snapping or clipping at all.
*/
export const TrivialSnapModule = {
snap: function (v) {
return +v;
},
snapUp: function (v) {
return +v;
},
snapDown: function (v) {
return +v;
},
};
/**
* Factory function which creates a piecewise linear transformation.
*/
export function makePiecewiseLinearTransformation({ basis, reverse }, heap) {
reverse |= 0;
basis = +basis;
const l = heap.byteLength >> 4;
const X = new Float64Array(heap, 0, l);
const Y = new Float64Array(heap, l * 8, l);
if (!(l >= 2))
throw new TypeError(
'piece-wise linear transformations need at least 2 entries.'
);
const min = Y[0];
const max = Y[l - 1];
function valueToBased(coef, size) {
let a = 0,
b = (l - 1) | 0,
mid = 0,
t = 0.0;
coef = +coef;
size = +size;
if (!(coef > +Y[0])) return +X[0] * size;
if (!(coef < +Y[(b << 3) >> 3])) return +X[(b << 3) >> 3] * size;
do {
mid = (a + b) >>> 1;
t = +Y[(mid << 3) >> 3];
if (coef > t) a = mid;
else if (coef < t) b = mid;
else return +X[(mid << 3) >> 3] * size;
} while (((b - a) | 0) > 1);
/* value lies between a and b */
t =
(+X[(b << 3) >> 3] - +X[(a << 3) >> 3]) /
(+Y[(b << 3) >> 3] - +Y[(a << 3) >> 3]);
t = +X[(a << 3) >> 3] + (coef - +Y[(a << 3) >> 3]) * t;
t *= size;
if (reverse) t = size - t;
return t;
}
function basedToValue(coef, size) {
let a = 0,
b = (l - 1) | 0,
mid = 0,
t = 0.0;
coef = +coef;
size = +size;
if (reverse) coef = size - coef;
coef /= size;
if (!(coef > 0)) return Y[0];
if (!(coef < 1)) return Y[(b << 3) >> 3];
do {
mid = (a + b) >>> 1;
t = +X[(mid << 3) >> 3];
if (coef > t) a = mid;
else if (coef < t) b = mid;
else return +Y[(mid << 3) >> 3];
} while (((b - a) | 0) > 1);
/* value lies between a and b */
t =
(+Y[(b << 3) >> 3] - +Y[(a << 3) >> 3]) /
(+X[(b << 3) >> 3] - +X[(a << 3) >> 3]);
return +Y[(a << 3) >> 3] + (coef - +X[(a << 3) >> 3]) * t;
}
function valueToPixel(n) {
return valueToBased(n, basis || 1);
}
function pixelToValue(n) {
return basedToValue(n, basis || 1);
}
function valueToCoef(n) {
return valueToBased(n, 1);
}
function coefToValue(n) {
return basedToValue(n, 1);
}
function clampValue(value) {
return clamp(min, max, value);
}
function clampPixel(pos) {
return clamp(0, basis, pos);
}
return {
valueToBased: valueToBased,
basedToValue: basedToValue,
valueToPixel: valueToPixel,
pixelToValue: pixelToValue,
valueToCoef: valueToCoef,
coefToValue: coefToValue,
clampValue: clampValue,
clampPixel: clampPixel,
};
}
/**
* Factory function which creates a transformation from generic function.
*/
export function makeFunctionTransformation({ reverse, scale, basis }, options) {
reverse |= 0;
basis = +basis;
const { min, max } = options;
function valueToBased(value, size) {
value = +value;
size = +size;
value = scale(value, options, false) * size;
if (reverse) value = size - value;
return value;
}
function basedToValue(coef, size) {
coef = +coef;
size = +size;
if (reverse) coef = size - coef;
coef = scale(coef / size, options, true);
return coef;
}
function valueToPixel(n) {
return valueToBased(n, basis || 1);
}
function pixelToValue(n) {
return basedToValue(n, basis || 1);
}
function valueToCoef(n) {
return valueToBased(n, 1);
}
function coefToValue(n) {
return basedToValue(n, 1);
}
function clampValue(value) {
return clamp(min, max, value);
}
function clampPixel(pos) {
return clamp(0, basis, pos);
}
return {
valueToBased: valueToBased,
basedToValue: basedToValue,
valueToPixel: valueToPixel,
pixelToValue: pixelToValue,
valueToCoef: valueToCoef,
coefToValue: coefToValue,
clampValue: clampValue,
clampPixel: clampPixel,
};
}
/**
* Creates a linear transformation.
*/
export function makeLinearTransformation({ reverse, min, max, basis }) {
reverse |= 0;
min = +min;
max = +max;
basis = +basis || 1.0;
function valueToBased(value, size) {
value = +value;
size = +size;
value = ((value - min) / (max - min)) * size;
if (reverse) value = size - value;
return value;
}
function basedToValue(coef, size) {
coef = +coef;
size = +size;
if (reverse) coef = size - coef;
coef = (coef / size) * (max - min) + min;
return coef;
}
function valueToPixel(n) {
n = +n;
return +valueToBased(n, basis);
}
function pixelToValue(n) {
n = +n;
return +basedToValue(n, basis);
}
// calculates a coefficient for the value
function valueToCoef(n) {
n = +n;
return +valueToBased(n, 1.0);
}
// calculates a value from a coefficient
function coefToValue(n) {
n = +n;
return +basedToValue(n, 1.0);
}
function clampValue(value) {
return clamp(min, max, value);
}
function clampPixel(pos) {
return clamp(0, basis, pos);
}
return {
/**
* Transforms a value from the coordinate system to the interval <code>0</code>...<code>basis</code>.
*
* @method Ranged#valueToBased
*
* @param {number} value
* @param {number} [basis=1]
*
* @returns {number}
*/
valueToBased: valueToBased,
/**
* Transforms a value from the interval <code>0</code>...<code>basis</code> to the coordinate system.
*
* @method Ranged#basedToValue
*
* @param {number} value
* @param {number} [basis=1]
*
* @returns {number}
*/
basedToValue: basedToValue,
/**
* This is an alias for {@link Ranged#valueToPixel}.
*
* @method Ranged#valueToPixel
*
* @param {number} value
*
* @returns {number}
*/
valueToPixel: valueToPixel,
/**
* This is an alias for {@link Ranged#pixelToValue}.
*
* @method Ranged#pixelToValue
*
* @param {number} value
*
* @returns {number}
*/
pixelToValue: pixelToValue,
/**
* Calls {@link basedToValue} with <code>basis = 1</code>.
*
* @method Ranged#valueToCoef
*
* @param {number} value
*
* @returns {number}
*/
valueToCoef: valueToCoef,
/**
* Calls {@link basedToValue} with <code>basis = 1</code>.
*
* @method Ranged#coefToValue
*
* @param {number} value
*
* @returns {number}
*/
coefToValue: coefToValue,
/**
* Clamps the given value into the value range of this transformation.
*/
clampValue: clampValue,
/**
* Clamps the given value into the pixel range of this transformation.
*/
clampPixel: clampPixel,
};
}
/**
* Factory function which creates a logarithmic transformation.
*/
export function makeLogarithmicTransformation({
basis,
log_factor,
max,
min,
reverse,
trafo_reverse,
}) {
reverse |= 0;
min = +min;
max = +max;
log_factor = +log_factor;
trafo_reverse |= 0;
basis = +basis;
function valueToBased(value, size) {
value = +value;
size = +size;
value = +dBToScale(value, min, max, size, trafo_reverse, log_factor);
if (reverse) value = size - value;
return value;
}
function basedToValue(coef, size) {
coef = +coef;
size = +size;
if (reverse) coef = size - coef;
coef = +scaleToDB(coef, min, max, size, trafo_reverse, log_factor);
return coef;
}
function valueToPixel(n) {
return valueToBased(n, basis || 1);
}
function pixelToValue(n) {
return basedToValue(n, basis || 1);
}
function valueToCoef(n) {
return valueToBased(n, 1);
}
function coefToValue(n) {
return basedToValue(n, 1);
}
function clampValue(value) {
return clamp(min, max, value);
}
function clampPixel(pos) {
return clamp(0, basis, pos);
}
return {
valueToBased: valueToBased,
basedToValue: basedToValue,
valueToPixel: valueToPixel,
pixelToValue: pixelToValue,
valueToCoef: valueToCoef,
coefToValue: coefToValue,
clampValue: clampValue,
clampPixel: clampPixel,
};
}
/**
* Factory function which creates a transformation for frequency scales.
*/
export function makeFrequencyTransformation({
basis,
max,
min,
reverse,
trafo_reverse,
}) {
reverse |= 0;
min = +min;
max = +max;
trafo_reverse |= 0;
basis = +basis;
function valueToBased(value, size) {
value = +value;
size = +size;
value = +freqToScale(value, min, max, size, trafo_reverse);
if (reverse) value = size - value;
return value;
}
function basedToValue(coef, size) {
coef = +coef;
size = +size;
if (reverse) coef = size - coef;
coef = +scaleToFreq(coef, min, max, size, trafo_reverse);
return coef;
}
function valueToPixel(n) {
return valueToBased(n, basis || 1);
}
function pixelToValue(n) {
return basedToValue(n, basis || 1);
}
function valueToCoef(n) {
return valueToBased(n, 1);
}
function coefToValue(n) {
return basedToValue(n, 1);
}
function clampValue(value) {
return clamp(min, max, value);
}
function clampPixel(pos) {
return clamp(0, basis, pos);
}
return {
valueToBased: valueToBased,
basedToValue: basedToValue,
valueToPixel: valueToPixel,
pixelToValue: pixelToValue,
valueToCoef: valueToCoef,
coefToValue: coefToValue,
clampValue: clampValue,
clampPixel: clampPixel,
};
}