UNPKG

@thi.ng/dsp

Version:

Composable signal generators, oscillators, filters, FFT, spectrum, windowing & related DSP utils

211 lines (210 loc) 4.87 kB
import { isComplex } from "./complex.js"; import { magDb } from "./convert.js"; import { invPowerScale, powerScale } from "./power.js"; import { applyWindow } from "./window.js"; const complexArray = (n) => [ new Float64Array(n), new Float64Array(n) ]; const copyComplex = (complex) => [ complex[0].slice(), complex[1].slice() ]; function conjugate(src, isImg = true) { if (isComplex(src)) { const n = src[0].length; const res = complexArray(n * 2); const [sreal, simg] = src; const [dreal, dimg] = res; dreal.set(sreal); dimg.set(simg); for (let i = 1, j = n * 2 - 1; i < n; i++, j--) { dreal[j] = sreal[i]; dimg[j] = -simg[i]; } return res; } else { const n = src.length; const dest = new Float64Array(n * 2); dest.set(src); for (let i = 1, j = n * 2 - 1; i < n; i++, j--) { dest[j] = isImg ? -src[i] : src[i]; } return dest; } } const __swapR = (real, n) => { const n2 = n >> 1; let ii; let jj; let k; let t; for (let i = 1, j = 1; i < n; i++) { if (i < j) { ii = i - 1; jj = j - 1; t = real[jj]; real[jj] = real[ii]; real[ii] = t; } k = n2; while (k < j) { j -= k; k >>= 1; } j += k; } }; const __swapRI = (real, img, n) => { const n2 = n >> 1; let ii; let jj; let k; let t; for (let i = 1, j = 1; i < n; i++) { if (i < j) { ii = i - 1; jj = j - 1; t = real[jj]; real[jj] = real[ii]; real[ii] = t; t = img[jj]; img[jj] = img[ii]; img[ii] = t; } k = n2; while (k < j) { j -= k; k >>= 1; } j += k; } }; const __transform = (real, img, n) => { let step = 1; let prevStep; let i, j, ii, ip; let tr, ti; let ur, ui; let wr, wi; let t; for (let b = Math.ceil(Math.log2(n)); b-- > 0; ) { prevStep = step; step <<= 1; ur = 1; ui = 0; t = Math.PI / prevStep; wr = Math.cos(t); wi = -Math.sin(t); for (j = 1; j <= prevStep; j++) { for (i = j; i <= n; i += step) { ip = i + prevStep - 1; ii = i - 1; tr = real[ip] * ur - img[ip] * ui; ti = real[ip] * ui + img[ip] * ur; real[ip] = real[ii] - tr; img[ip] = img[ii] - ti; real[ii] += tr; img[ii] += ti; } t = ur; ur = t * wr - ui * wi; ui = t * wi + ui * wr; } } }; const fft = (complex, window) => { let real, img; if (isComplex(complex)) { real = complex[0]; img = complex[1]; } else { real = complex; } if (window) { applyWindow(real, window); } const n = real.length; if (img) { __swapRI(real, img, n); } else { __swapR(real, n); img = new Float64Array(n); } __transform(real, img, n); return [real, img]; }; const ifft = (src) => { let complex = isComplex(src) ? src : [new Float64Array(src.length), src]; fft([complex[1], complex[0]]); return scaleFFT(complex, 1 / complex[0].length); }; const scaleFFT = (complex, scale) => { const [real, img] = complex; const n = real.length; for (let i = 0; i < n; i++) { real[i] *= scale; img[i] *= scale; } return [real, img]; }; const normalizeFFT = (complex, window = 2 / complex[0].length) => scaleFFT(complex, powerScale(window, 2)); const denormalizeFFT = (complex, window = complex[0].length / 2) => scaleFFT(complex, invPowerScale(window, 2)); const thresholdFFT = (complex, eps = 1e-12) => { const [real, img] = complex; for (let i = 0, n = real.length; i < n; i++) { if (Math.hypot(real[i], img[i]) < eps) { real[i] = img[i] = 0; } } return complex; }; const spectrumMag = (complex, n = complex[0].length / 2, out = []) => { const [real, img] = complex; for (let i = 0; i < n; i++) { out[i] = Math.hypot(real[i], img[i]); } return out; }; const spectrumPow = (complex, db = false, window = 2 / complex[0].length, n = complex[0].length / 2, out = []) => { const [real, img] = complex; const scale = powerScale(window, 2); for (let i = 0; i < n; i++) { const p = real[i] ** 2 + img[i] ** 2; out[i] = db ? magDb(Math.sqrt(p) * scale) : p * scale; } return out; }; const spectrumPhase = (complex, n = complex[0].length / 2, out = []) => { const [real, img] = complex; for (let i = 0; i < n; i++) { out[i] = Math.atan2(img[i], real[i]); } return out; }; const freqBin = (f, fs, n) => f * n / fs | 0; const binFreq = (bin, fs, n) => bin * fs / n; const fftFreq = (n, fs, m = n / 2) => { const res = new Float64Array(m); for (let i = 0; i <= m; i++) { res[i] = binFreq(i, fs, n); } return res; }; export { binFreq, complexArray, conjugate, copyComplex, denormalizeFFT, fft, fftFreq, freqBin, ifft, normalizeFFT, scaleFFT, spectrumMag, spectrumPhase, spectrumPow, thresholdFFT };