听力补偿新增左右耳曲线

This commit is contained in:
eafonyang
2026-07-14 14:30:55 +08:00
parent bfc4b45ca3
commit 4f3427b44f
9 changed files with 319 additions and 6 deletions
+112
View File
@@ -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;
}