听力补偿新增左右耳曲线
This commit is contained in:
@@ -0,0 +1,112 @@
|
||||
/** Hearing compensation curve helpers: map levels → dB and cubic-spline smooth. */
|
||||
|
||||
export const HEARING_FREQ_LABELS = ["125", "250", "500", "1k", "2k", "4k", "8k"] as const;
|
||||
|
||||
/** Y = n + (-70). Values outside [-80, 0] are kept as-is (axis still -80…0). */
|
||||
export function mapHearingLevelToDb(n: number): number {
|
||||
return n - 70;
|
||||
}
|
||||
|
||||
/**
|
||||
* Natural cubic spline for monotonically spaced `xs`.
|
||||
* Returns an interpolator defined on [xs[0], xs[n-1]] (extrapolates flat at ends).
|
||||
*/
|
||||
export function createNaturalCubicSpline(
|
||||
xs: number[],
|
||||
ys: number[],
|
||||
): (x: number) => number {
|
||||
const n = xs.length;
|
||||
if (n === 0) return () => 0;
|
||||
if (n === 1) return () => ys[0]!;
|
||||
if (n === 2) {
|
||||
const x0 = xs[0]!;
|
||||
const x1 = xs[1]!;
|
||||
const y0 = ys[0]!;
|
||||
const y1 = ys[1]!;
|
||||
return (x: number) => {
|
||||
if (x1 === x0) return y0;
|
||||
const t = (x - x0) / (x1 - x0);
|
||||
return y0 + t * (y1 - y0);
|
||||
};
|
||||
}
|
||||
|
||||
const h = new Array<number>(n - 1);
|
||||
for (let i = 0; i < n - 1; i++) h[i] = xs[i + 1]! - xs[i]!;
|
||||
|
||||
const alpha = new Array<number>(n).fill(0);
|
||||
for (let i = 1; i < n - 1; i++) {
|
||||
alpha[i] =
|
||||
(3 / h[i]!) * (ys[i + 1]! - ys[i]!) -
|
||||
(3 / h[i - 1]!) * (ys[i]! - ys[i - 1]!);
|
||||
}
|
||||
|
||||
const l = new Array<number>(n).fill(1);
|
||||
const mu = new Array<number>(n).fill(0);
|
||||
const z = new Array<number>(n).fill(0);
|
||||
|
||||
for (let i = 1; i < n - 1; i++) {
|
||||
l[i] = 2 * (xs[i + 1]! - xs[i - 1]!) - h[i - 1]! * mu[i - 1]!;
|
||||
mu[i] = h[i]! / l[i]!;
|
||||
z[i] = (alpha[i]! - h[i - 1]! * z[i - 1]!) / l[i]!;
|
||||
}
|
||||
|
||||
const c = new Array<number>(n).fill(0);
|
||||
const b = new Array<number>(n - 1).fill(0);
|
||||
const d = new Array<number>(n - 1).fill(0);
|
||||
|
||||
for (let j = n - 2; j >= 0; j--) {
|
||||
c[j] = z[j]! - mu[j]! * c[j + 1]!;
|
||||
b[j] =
|
||||
(ys[j + 1]! - ys[j]!) / h[j]! -
|
||||
(h[j]! * (c[j + 1]! + 2 * c[j]!)) / 3;
|
||||
d[j] = (c[j + 1]! - c[j]!) / (3 * h[j]!);
|
||||
}
|
||||
|
||||
return (x: number) => {
|
||||
let i = 0;
|
||||
if (x <= xs[0]!) {
|
||||
i = 0;
|
||||
} else if (x >= xs[n - 1]!) {
|
||||
i = n - 2;
|
||||
} else {
|
||||
let lo = 0;
|
||||
let hi = n - 2;
|
||||
while (lo <= hi) {
|
||||
const mid = (lo + hi) >> 1;
|
||||
if (x < xs[mid]!) {
|
||||
hi = mid - 1;
|
||||
} else if (x > xs[mid + 1]!) {
|
||||
lo = mid + 1;
|
||||
} else {
|
||||
i = mid;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (x > xs[i + 1]!) i = Math.min(n - 2, lo);
|
||||
}
|
||||
|
||||
const dx = x - xs[i]!;
|
||||
return ys[i]! + b[i]! * dx + c[i]! * dx * dx + d[i]! * dx * dx * dx;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Build densely sampled [xIndex, dB] pairs for a smooth ECharts line.
|
||||
* X uses equal spacing 0…6 matching HEARING_FREQ_LABELS.
|
||||
*/
|
||||
export function buildHearingSplineSeries(
|
||||
levels: number[],
|
||||
samplesPerSegment = 16,
|
||||
): [number, number][] {
|
||||
const xs = levels.map((_, i) => i);
|
||||
const ys = levels.map(mapHearingLevelToDb);
|
||||
const spline = createNaturalCubicSpline(xs, ys);
|
||||
const maxX = xs[xs.length - 1] ?? 0;
|
||||
const steps = Math.max(1, maxX * samplesPerSegment);
|
||||
const points: [number, number][] = [];
|
||||
for (let s = 0; s <= steps; s++) {
|
||||
const x = (s / steps) * maxX;
|
||||
points.push([x, spline(x)]);
|
||||
}
|
||||
return points;
|
||||
}
|
||||
Reference in New Issue
Block a user