dsp-filter-library
Version:
A comprehensive DSP library with 23 window functions, advanced FIR/IIR filter design, biquad combination, and interactive visualization
262 lines (228 loc) • 9.55 kB
JavaScript
// windows.js — Windowing functions for DSP
// Author: Davit Akobia <dav.akobia@gmail.com>
// License: MIT
/**
* Windowing functions for digital signal processing
*/
export class Windows {
static rect(N) {
return Array.from({ length: N }, () => 1);
}
static rectangle(N) {
return Windows.rect(N);
}
static hann(N) {
return Array.from({ length: N }, (_, n) => 0.5 - 0.5*Math.cos(2*Math.PI*n/(N-1)));
}
static hamming(N) {
return Array.from({ length: N }, (_, n) => 0.54 - 0.46*Math.cos(2*Math.PI*n/(N-1)));
}
static blackman(N) {
const a0 = 0.42, a1 = 0.5, a2 = 0.08;
return Array.from({ length: N }, (_, n) =>
a0 - a1*Math.cos(2*Math.PI*n/(N-1)) + a2*Math.cos(4*Math.PI*n/(N-1)));
}
static blackmanHarris(N) {
const a0 = 0.35875, a1 = 0.48829, a2 = 0.14128, a3 = 0.01168;
return Array.from({ length: N }, (_, n) =>
a0 - a1*Math.cos(2*Math.PI*n/(N-1)) + a2*Math.cos(4*Math.PI*n/(N-1)) - a3*Math.cos(6*Math.PI*n/(N-1)));
}
static blackmanNuttall(N) {
const a0 = 0.3635819, a1 = 0.4891775, a2 = 0.1365995, a3 = 0.0106411;
return Array.from({ length: N }, (_, n) =>
a0 - a1*Math.cos(2*Math.PI*n/(N-1)) + a2*Math.cos(4*Math.PI*n/(N-1)) - a3*Math.cos(6*Math.PI*n/(N-1)));
}
static kaiser(N, beta = 8.6) {
const denom = _i0(beta), M = N - 1;
return Array.from({ length: N }, (_, n) => {
const t = (2*n)/M - 1;
return _i0(beta * Math.sqrt(1 - t*t)) / denom;
});
}
static tukey(N, alpha = 0.5) {
const M = N - 1;
return Array.from({ length: N }, (_, n) => {
const x = n / M;
if (alpha <= 0) return 1;
if (alpha >= 1) return 0.5*(1 - Math.cos(2*Math.PI*x));
if (x < alpha/2) return 0.5*(1 + Math.cos(Math.PI*(2*x/alpha - 1)));
if (x <= 1 - alpha/2) return 1;
return 0.5*(1 + Math.cos(Math.PI*(2*x/alpha - 2/alpha + 1)));
});
}
static gauss(N, sigma = 0.4) {
const M = N - 1, m2 = M/2;
return Array.from({ length: N }, (_, n) => {
const k = (n - m2) / (sigma * m2);
return Math.exp(-0.5*k*k);
});
}
static bartlett(N) {
const M = N - 1;
return Array.from({ length: N }, (_, n) => 1 - Math.abs((n - M/2)/(M/2)));
}
static bartlettHann(N) {
const M = N - 1;
return Array.from({ length: N }, (_, n) => {
const x = n / M;
return 0.62 - 0.48*Math.abs(x - 0.5) - 0.38*Math.cos(2*Math.PI*x);
});
}
static cosine(N) {
const M = N - 1;
return Array.from({ length: N }, (_, n) => Math.sin(Math.PI*n/M));
}
static lanczos(N) {
const M = N - 1, m2 = M/2;
const sinc = (x) => x === 0 ? 1 : Math.sin(Math.PI*x)/(Math.PI*x);
return Array.from({ length: N }, (_, n) => sinc((n - m2)/m2));
}
static bohman(N) {
const M = N - 1, m2 = M/2;
return Array.from({ length: N }, (_, n) => {
const x = Math.abs(n - m2) / m2;
return (x <= 1) ? (1 - x)*Math.cos(Math.PI*x) + (1/Math.PI)*Math.sin(Math.PI*x) : 0;
});
}
static flatTop(N) {
const a0 = 1.0, a1 = 1.93, a2 = 1.29, a3 = 0.388, a4 = 0.028;
const M = N - 1;
return Array.from({ length: N }, (_, n) =>
a0 - a1*Math.cos(2*Math.PI*n/M) + a2*Math.cos(4*Math.PI*n/M) -
a3*Math.cos(6*Math.PI*n/M) + a4*Math.cos(8*Math.PI*n/M)
);
}
// Additional window functions
static nuttall(N) {
const a0 = 0.355768, a1 = 0.487396, a2 = 0.144232, a3 = 0.012604;
return Array.from({ length: N }, (_, n) =>
a0 - a1*Math.cos(2*Math.PI*n/(N-1)) + a2*Math.cos(4*Math.PI*n/(N-1)) - a3*Math.cos(6*Math.PI*n/(N-1)));
}
static parzen(N) {
const M = N - 1, m2 = M/2;
return Array.from({ length: N }, (_, n) => {
const x = Math.abs(n - m2) / m2;
if (x <= 0.5) return 1 - 6*x*x*(1 - x);
return 2*Math.pow(1 - x, 3);
});
}
static welch(N) {
const M = N - 1;
return Array.from({ length: N }, (_, n) => 1 - Math.pow((n - M/2)/(M/2), 2));
}
static triangular(N) {
const M = N - 1;
return Array.from({ length: N }, (_, n) => 1 - Math.abs((2*n - M)/M));
}
static poisson(N, alpha = 2) {
const M = N - 1, m2 = M/2;
return Array.from({ length: N }, (_, n) => Math.exp(-alpha * Math.abs(n - m2)/m2));
}
static hanning(N) {
return Windows.hann(N); // Alias for Hann window
}
static vonHann(N) {
return Windows.hann(N); // Another alias for Hann window
}
static nuttallBlackmanHarris(N) {
return Windows.blackmanNuttall(N); // Alias
}
static exactBlackman(N) {
const a0 = 0.42659, a1 = 0.49656, a2 = 0.076849;
return Array.from({ length: N }, (_, n) =>
a0 - a1*Math.cos(2*Math.PI*n/(N-1)) + a2*Math.cos(4*Math.PI*n/(N-1)));
}
static dolphChebyshev(N, alpha = 50) {
const M = N - 1, m2 = M/2;
const beta = Math.cosh(Math.acosh(Math.pow(10, alpha/20)) / M);
const chebyshev = (n, x) => {
if (Math.abs(x) <= 1) return Math.cos(n * Math.acos(x));
return Math.cosh(n * Math.acosh(x));
};
const w = new Array(N);
for (let k = 0; k < N; k++) {
let sum = 0;
for (let m = 0; m < N; m++) {
const x = beta * Math.cos(Math.PI * m / N);
sum += chebyshev(M, x) * Math.cos(2 * Math.PI * k * m / N);
}
w[k] = sum / N;
}
// Normalize
const max = Math.max(...w);
return w.map(x => x / max);
}
static taylor(N, nbar = 4, sll = -30) {
const M = N - 1, m2 = M/2;
const A = Math.acosh(Math.pow(10, -sll/20)) / Math.PI;
const sigma = nbar / Math.sqrt(A*A + (nbar - 0.5)*(nbar - 0.5));
return Array.from({ length: N }, (_, n) => {
const x = (n - m2) / m2;
let w = 1;
for (let m = 1; m < nbar; m++) {
const fm = Math.sqrt(1 - (m/(A*A + (m - 0.5)*(m - 0.5))));
w += 2 * fm * Math.cos(2 * Math.PI * m * x);
}
return w / (2 * nbar - 1);
});
}
static gaussian(N, sigma = 0.4) {
return Windows.gauss(N, sigma); // Alias
}
static raisedCosine(N, alpha = 0.5) {
return Windows.tukey(N, alpha); // Alias for Tukey window
}
/** @param {keyof Windows| string} name */
static byName(name, N, opts = {}) {
const { beta = 8.6, alpha = 0.5, sigma = 0.4, nbar = 4, sll = -30 } = opts || {};
switch (name) {
case 'hann':
case 'hanning':
case 'vonHann': return Windows.hann(N);
case 'hamming': return Windows.hamming(N);
case 'blackman': return Windows.blackman(N);
case 'blackmanHarris': return Windows.blackmanHarris(N);
case 'blackmanNuttall':
case 'nuttallBlackmanHarris': return Windows.blackmanNuttall(N);
case 'nuttall': return Windows.nuttall(N);
case 'exactBlackman': return Windows.exactBlackman(N);
case 'rectangle':
case 'rect': return Windows.rect(N);
case 'bartlett': return Windows.bartlett(N);
case 'bartlettHann': return Windows.bartlettHann(N);
case 'cosine': return Windows.cosine(N);
case 'lanczos': return Windows.lanczos(N);
case 'bohman': return Windows.bohman(N);
case 'gauss':
case 'gaussian': return Windows.gauss(N, sigma);
case 'tukey':
case 'raisedCosine': return Windows.tukey(N, alpha);
case 'kaiser': return Windows.kaiser(N, beta);
case 'flatTop':
case 'flattop': return Windows.flatTop(N);
case 'parzen': return Windows.parzen(N);
case 'welch': return Windows.welch(N);
case 'triangular': return Windows.triangular(N);
case 'poisson': return Windows.poisson(N, alpha);
case 'dolphChebyshev': return Windows.dolphChebyshev(N, alpha);
case 'taylor': return Windows.taylor(N, nbar, sll);
default: return Windows.hamming(N);
}
}
}
// Modified Bessel function of the first kind, order zero
const _i0 = (x) => {
const ax = Math.abs(x);
if (ax < 3.75) {
const t = x / 3.75, t2 = t*t;
return 1 + t2 * (3.5156229 + t2 * (3.0899424 + t2 * (1.2067492 +
t2 * (0.2659732 + t2 * (0.0360768 + t2 * 0.0045813)))));
} else {
const t = 3.75 / ax;
return (Math.exp(ax) / Math.sqrt(ax)) * (
0.39894228 + t * (0.01328592 + t * (0.00225319 + t * (-0.00157565 +
t * (0.00916281 + t * (-0.02057706 + t * (0.02635537 + t *
(-0.01647633 + t * 0.00392377)))))))
);
}
};