import { dBToScale, scaleToDB, freqToScale, scaleToFreq } from './audiomath.js';
/**
* A factory function which creates a snap module which
* snaps to values which are distributed at equal distance
* on a range.
*/
export function LinearSnapModule(options) {
const min = +options.min;
const max = +options.max;
const step = +options.step;
const base = +options.base;
const clip = !!options.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(options, heap) {
const values = new Float64Array(heap);
const len = (heap.byteLength >> 3) | 0;
const min = +(options.min !== void 0 ? options.min : values[0]);
const max = +(options.max !== void 0 ? options.max : values[len - 1]);
const clip = !!options.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) {
if (!(v > min)) v = min;
if (!(v < max)) v = max;
}
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(options) {
const min = +options.min;
const max = +options.max;
const clip = !!options.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(options, heap) {
const reverse = options.reverse | 0;
const l = heap.byteLength >> 4;
const X = new Float64Array(heap, 0, l);
const Y = new Float64Array(heap, l * 8, l);
const basis = +options.basis;
if (!(l >= 2))
throw new TypeError(
'piece-wise linear transformations need at least 2 entries.'
);
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);
}
return {
valueToBased: valueToBased,
basedToValue: basedToValue,
valueToPixel: valueToPixel,
pixelToValue: pixelToValue,
valueToCoef: valueToCoef,
coefToValue: coefToValue,
};
}
/**
* Factory function which creates a transformation from generic function.
*/
export function makeFunctionTransformation(options) {
const reverse = options.reverse | 0;
const scale = options.scale;
const basis = +options.basis;
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);
}
return {
valueToBased: valueToBased,
basedToValue: basedToValue,
valueToPixel: valueToPixel,
pixelToValue: pixelToValue,
valueToCoef: valueToCoef,
coefToValue: coefToValue,
};
}
/**
* Creates a linear transformation.
*/
export function makeLinearTransformation(options) {
const reverse = options.reverse | 0;
const min = +options.min;
const max = +options.max;
let basis = +options.basis;
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;
}
// just a wrapper for having understandable code and backward
// compatibility
function valueToPixel(n) {
n = +n;
if (basis == 0.0) basis = 1.0;
return +valueToBased(n, basis);
}
// just a wrapper for having understandable code and backward
// compatibility
function pixelToValue(n) {
n = +n;
if (basis == 0.0) basis = 1.0;
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);
}
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,
};
}
/**
* Factory function which creates a logarithmic transformation.
*/
export function makeLogarithmicTransformation(options) {
const reverse = options.reverse | 0;
const min = +options.min;
const max = +options.max;
const log_factor = +options.log_factor;
const trafo_reverse = options.trafo_reverse | 0;
const basis = +options.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);
}
return {
valueToBased: valueToBased,
basedToValue: basedToValue,
valueToPixel: valueToPixel,
pixelToValue: pixelToValue,
valueToCoef: valueToCoef,
coefToValue: coefToValue,
};
}
/**
* Factory function which creates a transformation for frequency scales.
*/
export function makeFrequencyTransformation(options) {
const reverse = options.reverse | 0;
const min = +options.min;
const max = +options.max;
const trafo_reverse = options.trafo_reverse | 0;
const basis = +options.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);
}
return {
valueToBased: valueToBased,
basedToValue: basedToValue,
valueToPixel: valueToPixel,
pixelToValue: pixelToValue,
valueToCoef: valueToCoef,
coefToValue: coefToValue,
};
}