UNPKG

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
// 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))))))) ); } };