perf(dsp): cache the resample kernel when outputs land on input samples
2:1 decimation (44.1k -> 22.05k, used to render the warped MIDI for the alignment page) recomputed 65 windowed-sinc taps per output sample: ~17 s for a 4.7 min stereo render. Outputs at integer input positions now reuse one tap table; arithmetic is identical, so results are bit-identical. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
99a1076547
commit
e5773e0c33
2 files changed
+60
-6
No files matched your search
@@ -0,0 +1,39 @@
|
||||
import { test, expect } from "bun:test";
|
||||
import { resample } from "../../src/dsp/resample.ts";
|
||||
|
||||
// Reference: the direct windowed-sinc evaluation (the original implementation), used to check the cached path is exact.
|
||||
function reference(x: Float32Array, from: number, to: number): Float32Array {
|
||||
const sinc = (v: number) => (v === 0 ? 1 : Math.sin(Math.PI * v) / (Math.PI * v));
|
||||
const ratio = to / from, outLen = Math.round(x.length * ratio), out = new Float32Array(outLen);
|
||||
const cutoff = Math.min(1, ratio), half = 16 / cutoff;
|
||||
for (let j = 0; j < outLen; j++) {
|
||||
const t = j / ratio;
|
||||
const lo = Math.max(0, Math.ceil(t - half)), hi = Math.min(x.length - 1, Math.floor(t + half));
|
||||
let acc = 0, wsum = 0;
|
||||
for (let i = lo; i <= hi; i++) {
|
||||
const d = i - t;
|
||||
const w = 0.42 + 0.5 * Math.cos((Math.PI * d) / half) + 0.08 * Math.cos((2 * Math.PI * d) / half);
|
||||
const k = cutoff * sinc(cutoff * d) * w;
|
||||
acc += x[i]! * k; wsum += k;
|
||||
}
|
||||
out[j] = wsum !== 0 ? acc / wsum : 0;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
const noise = (n: number) => { let s = 1; return Float32Array.from({ length: n }, () => ((s = (s * 16807) % 2147483647) / 2147483647) * 2 - 1); };
|
||||
|
||||
test("2:1 decimation is bit-identical to the direct evaluation and fast", () => {
|
||||
const x = noise(44100);
|
||||
expect(Array.from(resample(x, 44100, 22050))).toEqual(Array.from(reference(x, 44100, 22050)));
|
||||
const big = noise(44100 * 60);
|
||||
const t0 = performance.now();
|
||||
resample(big, 44100, 22050);
|
||||
expect(performance.now() - t0).toBeLessThan(500); // one minute of audio: ~50 ms cached vs ~1.7 s direct
|
||||
});
|
||||
|
||||
test("non-integer ratios are unchanged", () => {
|
||||
const x = noise(4800);
|
||||
expect(Array.from(resample(x, 48000, 44100))).toEqual(Array.from(reference(x, 48000, 44100)));
|
||||
expect(Array.from(resample(x, 22050, 44100))).toEqual(Array.from(reference(x, 22050, 44100)));
|
||||
});
|
||||
Reference in new issue
Block a user