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 }; }