Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 | 666315x 666315x 666315x 10953831x 10953831x 10953831x 666315x 51x 51x 51x 51x 51x 51x 51x 3315x 3315x 3315x 666315x 666315x 666315x 666315x 666315x 666315x 666315x 666315x 51x 51x 15514x 15514x 41x 41x 41x 15514x 74600x 15514x 15514x 1979968x 1979968x 1979968x 1979968x 1979968x 1979968x 1979968x 1979968x 1979968x 1979968x 397973568x 397973568x 397973568x 1979968x 15514x | function besselI0(x) {
let sum = 1;
let term = 1;
for (let k = 1; k < 50; ++k) {
term *= (x / (2 * k)) ** 2;
sum += term;
if (term < sum * 1e-12) break;
}
return sum;
}
/**
* Rate conversion by a Kaiser-windowed sinc evaluated only where an output
* sample falls, so the cost scales with the output rate, not the input rate.
* The kernel is tabulated at `phases` fractional offsets and interpolated
* between them. Each quantum the caller reserves room for its input, renders
* into `buffer` at `inputOffset`, reads the output with `read`, then calls
* `commit` to carry the last `taps` input samples over as the next quantum's
* history. Nothing is allocated once the buffer has reached its working size.
*/
export class PolyphaseResampler {
constructor(inputRate, cutoffHz, { taps = 201, phases = 64, beta = 8.5 } = {}) {
this.taps = taps;
this.phases = phases;
this.half = (taps - 1) / 2;
this.table = new Float32Array((phases + 1) * taps);
const scale = (2 * cutoffHz) / inputRate;
const norm = besselI0(beta);
for (let p = 0; p <= phases; ++p) {
const row = p * taps;
let sum = 0;
for (let k = 0; k < taps; ++k) {
const t = k - this.half - p / phases;
const x = Math.PI * scale * t;
const sinc = x === 0 ? 1 : Math.sin(x) / x;
const u = t / (this.half + 1);
const window = u <= -1 || u >= 1 ? 0 : besselI0(beta * Math.sqrt(1 - u * u)) / norm;
this.table[row + k] = scale * sinc * window;
sum += this.table[row + k];
}
for (let k = 0; k < taps; ++k) this.table[row + k] /= sum;
}
this.buffer = new Float32Array(taps);
this.newSamples = 0;
}
/** Make room for `count` new input samples after the history. */
reserve(count) {
const needed = this.taps + count + 1;
if (this.buffer.length < needed) {
const grown = new Float32Array(needed * 2);
grown.set(this.buffer.subarray(0, this.taps));
this.buffer = grown;
}
this.newSamples = count;
}
get inputOffset() {
return this.taps;
}
/**
* Fill `out` with samples taken `ratio` input samples apart, the first at
* `phase` (0 to 1) past the last sample of the previous quantum. The
* output lags the input by `half` samples, which the kernel needs ahead.
*/
read(out, phase, ratio) {
const { buffer, table, taps, phases } = this;
for (let i = 0; i < out.length; ++i) {
const pos = phase + i * ratio;
const loc = Math.floor(pos);
const fracPhase = (pos - loc) * phases;
const p = Math.min(Math.floor(fracPhase), phases - 1);
const mix = fracPhase - p;
const rowA = p * taps;
const rowB = rowA + taps;
let a = 0;
let b = 0;
for (let k = 0; k < taps; ++k) {
const x = buffer[loc + k];
a += table[rowA + k] * x;
b += table[rowB + k] * x;
}
out[i] = a + (b - a) * mix;
}
}
/** Keep the last `taps` input samples as history for the next quantum. */
commit() {
this.buffer.copyWithin(0, this.newSamples, this.newSamples + this.taps);
}
}
|