UNPKG

dsp-collection

Version:

A collection of JavaScript modules for digital signal processing (written in TypeScript)

276 lines 8.19 kB
import ComplexArray from "../math/ComplexArray.js"; import MutableComplex from "../math/MutableComplex.js"; import * as MathUtils from "../math/MathUtils.js"; var cooleyTukeySineTableCache; export function fft(x, direction = true) { const n = x.length; if (n <= 1) { return x.slice(); } const x2 = direction ? x : swapReIm(x); const x3 = MathUtils.isPowerOf2(n) ? fftCooleyTukey(x2) : fftBluestein(x2); const x4 = direction ? x3 : swapReIm(x3); return x4; } function fftCooleyTukey(x) { const n = x.length; const sineTable = getCachedCooleyTukeySineTable(n); const a = copyBitReversed(x); applyButterflies(a, sineTable); return a; } function applyButterflies(a, sineTable) { const temp = new MutableComplex(); const n = a.length; const re = a.re; const im = a.im; for (let mMax = 1; mMax < n; mMax *= 2) { const step = mMax * 2; const sinStep = n / step; for (let m = 0; m < mMax; m++) { const wIndex = m * sinStep; const wRe = sineTable.re[wIndex]; const wIm = sineTable.im[wIndex]; for (let i = m; i < n; i += step) { const j = i + mMax; temp.setMul(re[j], im[j], wRe, wIm); re[j] = re[i] - temp.re; im[j] = im[i] - temp.im; re[i] += temp.re; im[i] += temp.im; } } } } function copyBitReversed(a1) { const n = a1.length; const a2 = new ComplexArray(n); let i1 = 0; for (let i2 = 0; i2 < n; i2++) { a2.re[i2] = a1.re[i1]; a2.im[i2] = a1.im[i1]; i1 = incrementBitReversed(i1, n); } return a2; } function incrementBitReversed(i, n) { let m = n >> 1; let a = i; while (a & m) { a -= m; m >>= 1; } return a | m; } function fftBluestein(x) { const n = x.length; const m = MathUtils.getNextPowerOf2(2 * n - 3); const sineTable = createSineOfSquareTable(n, 2 * n); const a1 = new ComplexArray(m); for (let i = 0; i < n; i++) { a1.setMul(i, x.re[i], x.im[i], sineTable.re[i], -sineTable.im[i]); } const a2 = new ComplexArray(m); for (let i = 0; i < n; i++) { ComplexArray.copy1(sineTable, i, a2, i); } for (let i = 1; i < n; i++) { ComplexArray.copy1(sineTable, i, a2, m - i); } const a3 = convolve(a1, a2); const a4 = new ComplexArray(n); for (let i = 0; i < n; i++) { a4.setMul(i, a3.re[i], a3.im[i], sineTable.re[i], -sineTable.im[i]); } return a4; } function convolve(a1, a2) { const n = a1.length; if (a2.length != n) { throw new Error("Array lengths are not equal."); } const a3 = fft(a1); const a4 = fft(a2); a3.mulByArray(a4); const a5 = fft(a3, false); a5.mulAllByReal(1 / n); return a5; } function getCachedCooleyTukeySineTable(n) { if (!cooleyTukeySineTableCache) { cooleyTukeySineTableCache = new Array(16); } const log2N = MathUtils.floorLog2(n); if (!cooleyTukeySineTableCache[log2N]) { cooleyTukeySineTableCache[log2N] = createCooleyTukeySineTable(n); } return cooleyTukeySineTableCache[log2N]; } function createCooleyTukeySineTable(n) { return createSineTable(n / 2, n, false); } function createSineTable(tableLength, waveLength, rotationalDirection = true) { const w = 2 * Math.PI / waveLength; const a = new ComplexArray(tableLength); for (let i = 0; i < tableLength; i++) { const t = i * w; a.re[i] = Math.cos(t); a.im[i] = rotationalDirection ? Math.sin(t) : -Math.sin(t); } return a; } function createSineOfSquareTable(tableLength, waveLength) { const w = 2 * Math.PI / waveLength; const a = new ComplexArray(tableLength); for (let i = 0; i < tableLength; i++) { const t = (i * i) % waveLength * w; a.re[i] = Math.cos(t); a.im[i] = Math.sin(t); } return a; } function swapReIm(a) { const a2 = new ComplexArray(); a2.length = a.length; a2.re = a.im; a2.im = a.re; return a2; } export function fftReal(x) { return fft(new ComplexArray(x)); } export function fftRealHalf(x, inclNyquist = false) { if (x.length <= 1) { return new ComplexArray(x); } const m = x.length; if (m % 2 != 0) { throw new Error("Input array size is not even."); } const n = m / 2; const a1 = new ComplexArray(n); for (let i = 0; i < n; i++) { a1.re[i] = x[2 * i]; a1.im[i] = x[2 * i + 1]; } const a2 = fft(a1); const a3 = new ComplexArray(n + (inclNyquist ? 1 : 0)); a3.re[0] = a2.re[0] + a2.im[0]; a3.im[0] = 0; if (inclNyquist) { a3.re[n] = a2.re[0] - a2.im[0]; a3.im[n] = 0; } const temp1 = new MutableComplex(); const temp2 = new MutableComplex(); const w = Math.PI / n; for (let i = 1; i < n; i++) { const sRe = Math.sin(i * w); const sIm = Math.cos(i * w); temp1.setMul(a2.re[i], a2.im[i], (1 - sRe) / 2, -sIm / 2); temp2.setMul(a2.re[n - i], a2.im[n - i], (1 + sRe) / 2, -sIm / 2); a3.re[i] = temp1.re + temp2.re; a3.im[i] = temp1.im - temp2.im; } return a3; } export function fftRealSpectrum(x, inclNyquist = false) { const n = x.length; if (n == 0) { throw new Error("Input array must not be empty."); } let a; if (n % 2 == 0) { a = fftRealHalf(x, inclNyquist); } else { const a0 = fftReal(x); a = a0.subarray(0, Math.floor(n / 2) + 1); } for (let i = 0; i < a.length; i++) { const r = (i == 0 || i == n / 2) ? 1 / n : 2 / n; a.mulByReal(i, r); } return a; } export function fftShift(x) { const n = x.length; const d = Math.floor(n / 2); const a = new ComplexArray(n); for (let p = 0; p < n; p++) { ComplexArray.copy1(x, p, a, (p + d) % n); } return a; } export function iFftRealHalfSimple(x, len, inclNyquist = false) { if (x.length == 0 || len <= 0) { return new Float64Array(0); } const x2 = createFullSpectrumFromHalfSpectrum(x, len, inclNyquist); const a = fft(x2, false); return a.re; } function createFullSpectrumFromHalfSpectrum(x, len, inclNyquist) { const x2 = new ComplexArray(len); ComplexArray.copy1(x, 0, x2, 0); if (inclNyquist && len % 2 == 0 && x.length > len / 2) { ComplexArray.copy1(x, len / 2, x2, len / 2); } const n2 = Math.min(x.length - 1, Math.floor((len - 1) / 2)); for (let i = 0; i < n2; i++) { const p1 = 1 + i; const p2 = len - 1 - i; x2.re[p2] = x.re[p1]; x2.im[p2] = -x.im[p1]; } return x2; } export function iFftRealHalfOpt(x, len, inclNyquist = false) { if (len <= 0) { return new Float64Array(0); } if (len % 2 != 0) { throw new Error("output length is not even."); } const n = len / 2; const a1 = new ComplexArray(n); a1.re[0] = xRe(0); a1.im[0] = xRe(0); if (inclNyquist) { a1.re[0] += xRe(n); a1.im[0] -= xRe(n); } const temp1 = new MutableComplex(); const temp2 = new MutableComplex(); const w = Math.PI / n; for (let i = 1; i < n; i++) { const sRe = Math.sin(i * w); const sIm = Math.cos(i * w); temp1.setMul(xRe(i), xIm(i), (1 - sRe) / 2, sIm / 2); temp2.setMul(xRe(n - i), xIm(n - i), (1 + sRe) / 2, sIm / 2); a1.re[i] = temp1.re + temp2.re; a1.im[i] = temp1.im - temp2.im; } const a2 = fft(a1, false); const a3 = new Float64Array(2 * n); for (let i = 0; i < n; i++) { a3[2 * i] = a2.re[i]; a3[2 * i + 1] = a2.im[i]; } return a3; function xRe(i) { return (i < x.length) ? x.re[i] : 0; } function xIm(i) { return (i < x.length) ? x.im[i] : 0; } } export function iFftRealHalf(x, len, inclNyquist = false) { if (len % 2 == 0) { return iFftRealHalfOpt(x, len, inclNyquist); } else { return iFftRealHalfSimple(x, len, inclNyquist); } } //# sourceMappingURL=Fft.js.map