Files

92 lines
2.9 KiB
JavaScript

export const TAU = Math.PI * 2;
/**
* Ceil a sample position while keeping an analytically integral boundary on
* that integer. IEEE-754 roundoff from expressions such as (0.1 + 0.2) * 1000
* must not add a physically nonexistent sample of latency.
*/
export function ceilSamplePosition(samplePosition) {
if (!Number.isFinite(samplePosition)) {
throw new TypeError("sample position must be finite.");
}
const nearestInteger = Math.round(samplePosition);
const tolerance =
16 * Number.EPSILON * Math.max(1, Math.abs(samplePosition));
const result = Math.abs(samplePosition - nearestInteger) <= tolerance
? nearestInteger
: Math.ceil(samplePosition);
if (!Number.isSafeInteger(result)) {
throw new RangeError("sample position exceeds the safe integer range.");
}
return result;
}
export function ceilSampleIndex(timeSeconds, sampleRate) {
if (!Number.isFinite(timeSeconds)) throw new TypeError("time must be finite.");
if (!(sampleRate > 0) || !Number.isFinite(sampleRate)) {
throw new TypeError("sampleRate must be finite and positive.");
}
return ceilSamplePosition(timeSeconds * sampleRate);
}
export function distance3(a, b) {
return Math.hypot(a[0] - b[0], a[1] - b[1], a[2] - b[2]);
}
export function dbToLinear(decibels) {
return 10 ** (decibels / 20);
}
export function powerToDb(power, floorDb = -120) {
if (!(power > 0)) return floorDb;
return Math.max(floorDb, 10 * Math.log10(power));
}
export function clamp(value, minimum, maximum) {
return Math.min(maximum, Math.max(minimum, value));
}
export function lerp(start, end, amount) {
return start + (end - start) * amount;
}
export function createScanPoints(scan) {
const points = new Float32Array(scan.columns * scan.rows * 4);
let offset = 0;
for (let row = 0; row < scan.rows; row += 1) {
const y = scan.rows === 1 ? scan.yMax : lerp(scan.yMax, scan.yMin, row / (scan.rows - 1));
for (let column = 0; column < scan.columns; column += 1) {
const x = scan.columns === 1 ? scan.xMin : lerp(scan.xMin, scan.xMax, column / (scan.columns - 1));
points[offset] = x;
points[offset + 1] = y;
points[offset + 2] = scan.z;
points[offset + 3] = 0;
offset += 4;
}
}
return points;
}
export function scanIndexToPosition(index, scan) {
const row = Math.floor(index / scan.columns);
const column = index % scan.columns;
return [
scan.columns === 1 ? scan.xMin : lerp(scan.xMin, scan.xMax, column / (scan.columns - 1)),
scan.rows === 1 ? scan.yMax : lerp(scan.yMax, scan.yMin, row / (scan.rows - 1)),
scan.z,
];
}
export function findPeak(values) {
if (values.length === 0) throw new Error("빈 map에서는 peak를 찾을 수 없습니다.");
let peakIndex = 0;
let peakValue = values[0];
for (let index = 1; index < values.length; index += 1) {
if (values[index] > peakValue) {
peakValue = values[index];
peakIndex = index;
}
}
return { index: peakIndex, value: peakValue };
}