frost-fft
Version:
Fast Fourier Transform (FFT) implementation in TypeScript using the Cooley–Tukey algorithm for power-of-2 input lengths
441 lines • 18.5 kB
JavaScript
const COSINES = [];
const SINES = [];
const SQRT1_2 = Math.SQRT1_2;
COSINES[2] = [NaN, 0];
SINES[2] = [NaN, 1];
/**
* Reset internal tables. Used during benchmarking.
*/
export function _resetTables() {
COSINES.length = 0;
SINES.length = 0;
COSINES[2] = [NaN, 0];
SINES[2] = [NaN, 1];
}
function getTables(M) {
const cosines = COSINES[M] ?? [];
const sines = SINES[M] ?? [];
if (cosines.length) {
return [cosines, sines];
}
cosines.length = M;
sines.length = M;
const L = M >>> 1;
const [c, s] = getTables(L);
// i = 0 intentionally left undefined
for (let i = 1; i < L; ++i) {
cosines[i << 1] = c[i];
sines[i << 1] = s[i];
}
const PI_OVER_M = Math.PI / M;
for (let i = 1; i < L; i += 2) {
sines[i] = sines[M - i] = cosines[L - i] = Math.sin(i * PI_OVER_M);
cosines[L + i] = -cosines[L - i];
}
return [cosines, sines];
}
function splitComplexEvenOdd(realIn, imagIn, M) {
const realEvensIn = new Float64Array(M);
const imagEvensIn = new Float64Array(M);
const realOddsIn = new Float64Array(M);
const imagOddsIn = new Float64Array(M);
for (let i = 0, j = 0; i < M; ++i, j += 2) {
realEvensIn[i] = realIn[j];
imagEvensIn[i] = imagIn[j];
realOddsIn[i] = realIn[j + 1];
imagOddsIn[i] = imagIn[j + 1];
}
return [realEvensIn, imagEvensIn, realOddsIn, imagOddsIn];
}
function splitRealEvenOdd(realIn, M) {
const realEvensIn = new Float64Array(M);
const realOddsIn = new Float64Array(M);
for (let i = 0, j = 0; i < M; ++i, j += 2) {
realEvensIn[i] = realIn[j];
realOddsIn[i] = realIn[j + 1];
}
return [realEvensIn, realOddsIn];
}
/**
* Calculate the smallest power of two greater or equal to the input value.
* @param x Integer to compare to.
* @returns Smallest `2**n` such that `x <= 2**n`.
*/
export function ceilPow2(x) {
return 1 << (32 - Math.clz32(x - 1));
}
/**
* Calculate the unnormalized forward discrete Fourier transform.
* @param realIn Real components of the signal.
* @param imagIn Imaginary components of the signal (all zeros assumed if missing).
* @returns Array of [real coefficients, imaginary coefficients].
*/
export function fft(realIn, imagIn) {
const N = realIn.length;
if (N !== ceilPow2(N)) {
throw new Error('Length must be a power of two.');
}
if (imagIn === undefined) {
return _fftNoImag(realIn);
}
if (imagIn.length !== N) {
throw new Error('Must have an equal number of real and imaginary components');
}
const realOut = new Float64Array(N);
const imagOut = new Float64Array(N);
if (N === 4) {
realOut[0] = realIn[0] + realIn[1] + realIn[2] + realIn[3];
realOut[1] = realIn[0] + imagIn[1] - realIn[2] - imagIn[3];
realOut[2] = realIn[0] - realIn[1] + realIn[2] - realIn[3];
realOut[3] = realIn[0] - imagIn[1] - realIn[2] + imagIn[3];
imagOut[0] = imagIn[0] + imagIn[1] + imagIn[2] + imagIn[3];
imagOut[1] = imagIn[0] - realIn[1] - imagIn[2] + realIn[3];
imagOut[2] = imagIn[0] - imagIn[1] + imagIn[2] - imagIn[3];
imagOut[3] = imagIn[0] + realIn[1] - imagIn[2] - realIn[3];
return [realOut, imagOut];
}
if (N === 2) {
realOut[0] = realIn[0] + realIn[1];
realOut[1] = realIn[0] - realIn[1];
imagOut[0] = imagIn[0] + imagIn[1];
imagOut[1] = imagIn[0] - imagIn[1];
return [realOut, imagOut];
}
if (N === 1) {
realOut[0] = realIn[0];
imagOut[0] = imagIn[0];
return [realOut, imagOut];
}
return _fft(realIn, imagIn);
}
function _fft(realIn, imagIn) {
const N = realIn.length;
const realOut = new Float64Array(N);
const imagOut = new Float64Array(N);
if (N === 8) {
const realEvens0 = realIn[0] + realIn[2] + realIn[4] + realIn[6];
const imagEvens0 = imagIn[0] + imagIn[2] + imagIn[4] + imagIn[6];
const realOdds0 = realIn[1] + realIn[3] + realIn[5] + realIn[7];
const imagOdds0 = imagIn[1] + imagIn[3] + imagIn[5] + imagIn[7];
realOut[0] = realEvens0 + realOdds0;
imagOut[0] = imagEvens0 + imagOdds0;
realOut[4] = realEvens0 - realOdds0;
imagOut[4] = imagEvens0 - imagOdds0;
const realEvens1 = realIn[0] + imagIn[2] - realIn[4] - imagIn[6];
const imagEvens1 = imagIn[0] - realIn[2] - imagIn[4] + realIn[6];
const realOdds1 = realIn[1] + imagIn[3] - realIn[5] - imagIn[7];
const imagOdds1 = imagIn[1] - realIn[3] - imagIn[5] + realIn[7];
const realQ1 = (realOdds1 + imagOdds1) * SQRT1_2;
const imagQ1 = (realOdds1 - imagOdds1) * SQRT1_2;
realOut[1] = realEvens1 + realQ1;
imagOut[1] = imagEvens1 - imagQ1;
realOut[5] = realEvens1 - realQ1;
imagOut[5] = imagEvens1 + imagQ1;
const realEvens2 = realIn[0] - realIn[2] + realIn[4] - realIn[6];
const imagEvens2 = imagIn[0] - imagIn[2] + imagIn[4] - imagIn[6];
const realOdds2 = realIn[1] - realIn[3] + realIn[5] - realIn[7];
const imagOdds2 = imagIn[1] - imagIn[3] + imagIn[5] - imagIn[7];
realOut[2] = realEvens2 + imagOdds2;
imagOut[2] = imagEvens2 - realOdds2;
realOut[6] = realEvens2 - imagOdds2;
imagOut[6] = imagEvens2 + realOdds2;
const realEvens3 = realIn[0] - imagIn[2] - realIn[4] + imagIn[6];
const imagEvens3 = imagIn[0] + realIn[2] - imagIn[4] - realIn[6];
const realOdds3 = realIn[1] - imagIn[3] - realIn[5] + imagIn[7];
const imagOdds3 = imagIn[1] + realIn[3] - imagIn[5] - realIn[7];
const realQ3 = (realOdds3 - imagOdds3) * SQRT1_2;
const imagQ3 = (realOdds3 + imagOdds3) * SQRT1_2;
realOut[3] = realEvens3 - realQ3;
imagOut[3] = imagEvens3 - imagQ3;
realOut[7] = realEvens3 + realQ3;
imagOut[7] = imagEvens3 + imagQ3;
return [realOut, imagOut];
}
const M = N >>> 1;
const [realEvensIn, imagEvensIn, realOddsIn, imagOddsIn] = splitComplexEvenOdd(realIn, imagIn, M);
const [realEvens, imagEvens] = _fft(realEvensIn, imagEvensIn);
const [realOdds, imagOdds] = _fft(realOddsIn, imagOddsIn);
realOut[0] = realEvens[0] + realOdds[0];
realOut[M] = realEvens[0] - realOdds[0];
imagOut[0] = imagEvens[0] + imagOdds[0];
imagOut[M] = imagEvens[0] - imagOdds[0];
const [cosines, sines] = getTables(M);
for (let k = 1; k < M; ++k) {
const realZ = cosines[k];
const imagZ = sines[k];
const realOdd = realOdds[k];
const imagOdd = imagOdds[k];
const realQ = realOdd * realZ + imagOdd * imagZ;
const imagQ = imagOdd * realZ - realOdd * imagZ;
realOut[k] = realEvens[k] + realQ;
imagOut[k] = imagEvens[k] + imagQ;
realOut[k + M] = realEvens[k] - realQ;
imagOut[k + M] = imagEvens[k] - imagQ;
}
return [realOut, imagOut];
}
function _fftNoImag(realIn) {
const N = realIn.length;
const realOut = new Float64Array(N);
const imagOut = new Float64Array(N);
if (N === 4) {
realOut[0] = realIn[0] + realIn[1] + realIn[2] + realIn[3];
realOut[1] = realIn[0] - realIn[2];
realOut[2] = realIn[0] - realIn[1] + realIn[2] - realIn[3];
realOut[3] = realIn[0] - realIn[2];
imagOut[1] = realIn[3] - realIn[1];
imagOut[3] = realIn[1] - realIn[3];
return [realOut, imagOut];
}
if (N === 2) {
realOut[0] = realIn[0] + realIn[1];
realOut[1] = realIn[0] - realIn[1];
return [realOut, imagOut];
}
if (N === 1) {
realOut[0] = realIn[0];
return [realOut, imagOut];
}
return _fftNoImagInner(realIn);
}
function _fftNoImagInner(realIn) {
const N = realIn.length;
const realOut = new Float64Array(N);
const imagOut = new Float64Array(N);
if (N === 8) {
const realEvens0 = realIn[0] + realIn[2] + realIn[4] + realIn[6];
const realOdds0 = realIn[1] + realIn[3] + realIn[5] + realIn[7];
realOut[0] = realEvens0 + realOdds0;
realOut[4] = realEvens0 - realOdds0;
const realEvens1 = realIn[0] - realIn[4];
const imagEvens1 = realIn[6] - realIn[2];
const realOdds1 = realIn[1] - realIn[5];
const imagOdds1 = realIn[7] - realIn[3];
const realQ1 = (realOdds1 + imagOdds1) * SQRT1_2;
const imagQ1 = (realOdds1 - imagOdds1) * SQRT1_2;
realOut[1] = realEvens1 + realQ1;
imagOut[1] = imagEvens1 - imagQ1;
realOut[5] = realEvens1 - realQ1;
imagOut[5] = imagEvens1 + imagQ1;
const realEvens2 = realIn[0] - realIn[2] + realIn[4] - realIn[6];
const realOdds2 = realIn[1] - realIn[3] + realIn[5] - realIn[7];
realOut[2] = realEvens2;
imagOut[2] = -realOdds2;
realOut[6] = realEvens2;
imagOut[6] = realOdds2;
const realEvens3 = realIn[0] - realIn[4];
const imagEvens3 = realIn[2] - realIn[6];
const realOdds3 = realIn[1] - realIn[5];
const imagOdds3 = realIn[3] - realIn[7];
const realQ3 = (realOdds3 - imagOdds3) * SQRT1_2;
const imagQ3 = (realOdds3 + imagOdds3) * SQRT1_2;
realOut[3] = realEvens3 - realQ3;
imagOut[3] = imagEvens3 - imagQ3;
realOut[7] = realEvens3 + realQ3;
imagOut[7] = imagEvens3 + imagQ3;
return [realOut, imagOut];
}
const M = N >>> 1;
const [realEvensIn, realOddsIn] = splitRealEvenOdd(realIn, M);
const [realEvens, imagEvens] = _fftNoImagInner(realEvensIn);
const [realOdds, imagOdds] = _fftNoImagInner(realOddsIn);
realOut[0] = realEvens[0] + realOdds[0];
realOut[M] = realEvens[0] - realOdds[0];
imagOut[0] = imagEvens[0] + imagOdds[0];
imagOut[M] = imagEvens[0] - imagOdds[0];
const [cosines, sines] = getTables(M);
for (let k = 1; k < M; ++k) {
const realZ = cosines[k];
const imagZ = sines[k];
const realOdd = realOdds[k];
const imagOdd = imagOdds[k];
const realQ = realOdd * realZ + imagOdd * imagZ;
const imagQ = imagOdd * realZ - realOdd * imagZ;
realOut[k] = realEvens[k] + realQ;
imagOut[k] = imagEvens[k] + imagQ;
realOut[k + M] = realEvens[k] - realQ;
imagOut[k + M] = imagEvens[k] - imagQ;
}
return [realOut, imagOut];
}
/**
* Calculate the unnormalized reverse discrete Fourier transform.
* @param realIn Real coefficients of a forward transform.
* @param imagIn Imaginary coefficients of a forward transform.
* @returns Array of [real signal, imaginary signal] scaled by the length of the original signal.
*/
export function ifft(realIn, imagIn) {
const N = realIn.length;
if (N !== ceilPow2(N)) {
throw new Error('Length must be a power of two.');
}
if (imagIn.length !== N) {
throw new Error('Must have an equal number of real and imaginary components');
}
const realOut = new Float64Array(N);
const imagOut = new Float64Array(N);
if (N === 4) {
realOut[0] = realIn[0] + realIn[1] + realIn[2] + realIn[3];
realOut[1] = realIn[0] - imagIn[1] - realIn[2] + imagIn[3];
realOut[2] = realIn[0] - realIn[1] + realIn[2] - realIn[3];
realOut[3] = realIn[0] + imagIn[1] - realIn[2] - imagIn[3];
imagOut[0] = imagIn[0] + imagIn[1] + imagIn[2] + imagIn[3];
imagOut[1] = imagIn[0] + realIn[1] - imagIn[2] - realIn[3];
imagOut[2] = imagIn[0] - imagIn[1] + imagIn[2] - imagIn[3];
imagOut[3] = imagIn[0] - realIn[1] - imagIn[2] + realIn[3];
return [realOut, imagOut];
}
if (N === 2) {
realOut[0] = realIn[0] + realIn[1];
realOut[1] = realIn[0] - realIn[1];
imagOut[0] = imagIn[0] + imagIn[1];
imagOut[1] = imagIn[0] - imagIn[1];
return [realOut, imagOut];
}
if (N === 1) {
realOut[0] = realIn[0];
imagOut[0] = imagIn[0];
return [realOut, imagOut];
}
return _ifft(realIn, imagIn);
}
function _ifft(realIn, imagIn) {
const N = realIn.length;
const realOut = new Float64Array(N);
const imagOut = new Float64Array(N);
if (N === 8) {
const realEvens0 = realIn[0] + realIn[2] + realIn[4] + realIn[6];
const imagEvens0 = imagIn[0] + imagIn[2] + imagIn[4] + imagIn[6];
const realOdds0 = realIn[1] + realIn[3] + realIn[5] + realIn[7];
const imagOdds0 = imagIn[1] + imagIn[3] + imagIn[5] + imagIn[7];
realOut[0] = realEvens0 + realOdds0;
imagOut[0] = imagEvens0 + imagOdds0;
realOut[4] = realEvens0 - realOdds0;
imagOut[4] = imagEvens0 - imagOdds0;
const realEvens1 = realIn[0] - imagIn[2] - realIn[4] + imagIn[6];
const imagEvens1 = imagIn[0] + realIn[2] - imagIn[4] - realIn[6];
const realOdds1 = realIn[1] - imagIn[3] - realIn[5] + imagIn[7];
const imagOdds1 = imagIn[1] + realIn[3] - imagIn[5] - realIn[7];
const realQ1 = (realOdds1 - imagOdds1) * SQRT1_2;
const imagQ1 = (realOdds1 + imagOdds1) * SQRT1_2;
realOut[1] = realEvens1 + realQ1;
imagOut[1] = imagEvens1 + imagQ1;
realOut[5] = realEvens1 - realQ1;
imagOut[5] = imagEvens1 - imagQ1;
const realEvens2 = realIn[0] - realIn[2] + realIn[4] - realIn[6];
const imagEvens2 = imagIn[0] - imagIn[2] + imagIn[4] - imagIn[6];
const realOdds2 = realIn[1] - realIn[3] + realIn[5] - realIn[7];
const imagOdds2 = imagIn[1] - imagIn[3] + imagIn[5] - imagIn[7];
realOut[2] = realEvens2 - imagOdds2;
imagOut[2] = imagEvens2 + realOdds2;
realOut[6] = realEvens2 + imagOdds2;
imagOut[6] = imagEvens2 - realOdds2;
const realEvens3 = realIn[0] + imagIn[2] - realIn[4] - imagIn[6];
const imagEvens3 = imagIn[0] - realIn[2] - imagIn[4] + realIn[6];
const realOdds3 = realIn[1] + imagIn[3] - realIn[5] - imagIn[7];
const imagOdds3 = imagIn[1] - realIn[3] - imagIn[5] + realIn[7];
const realQ3 = (realOdds3 + imagOdds3) * SQRT1_2;
const imagQ3 = (realOdds3 - imagOdds3) * SQRT1_2;
realOut[3] = realEvens3 - realQ3;
imagOut[3] = imagEvens3 + imagQ3;
realOut[7] = realEvens3 + realQ3;
imagOut[7] = imagEvens3 - imagQ3;
return [realOut, imagOut];
}
const M = N >>> 1;
const [realEvensIn, imagEvensIn, realOddsIn, imagOddsIn] = splitComplexEvenOdd(realIn, imagIn, M);
const [realEvens, imagEvens] = _ifft(realEvensIn, imagEvensIn);
const [realOdds, imagOdds] = _ifft(realOddsIn, imagOddsIn);
realOut[0] = realEvens[0] + realOdds[0];
realOut[M] = realEvens[0] - realOdds[0];
imagOut[0] = imagEvens[0] + imagOdds[0];
imagOut[M] = imagEvens[0] - imagOdds[0];
const [cosines, sines] = getTables(M);
for (let k = 1; k < M; ++k) {
const realZ = cosines[k];
const imagZ = sines[k];
const realOdd = realOdds[k];
const imagOdd = imagOdds[k];
const realQ = realOdd * realZ - imagOdd * imagZ;
const imagQ = realOdd * imagZ + imagOdd * realZ;
realOut[k] = realEvens[k] + realQ;
imagOut[k] = imagEvens[k] + imagQ;
realOut[k + M] = realEvens[k] - realQ;
imagOut[k + M] = imagEvens[k] - imagQ;
}
return [realOut, imagOut];
}
/**
* Calculate the unnormalized reverse discrete Fourier transform.
* @param realIn Real coefficients of a forward transform.
* @param imagIn Imaginary coefficients of a forward transform.
* @returns Real signal scaled by the length of the original signal.
*/
export function ifftReal(realIn, imagIn) {
const N = realIn.length;
if (N !== ceilPow2(N)) {
throw new Error('Length must be a power of two.');
}
if (imagIn.length !== N) {
throw new Error('Must have an equal number of real and imaginary components');
}
const realOut = new Float64Array(N);
if (N === 4) {
realOut[0] = realIn[0] + realIn[1] + realIn[2] + realIn[3];
realOut[1] = realIn[0] - imagIn[1] - realIn[2] + imagIn[3];
realOut[2] = realIn[0] - realIn[1] + realIn[2] - realIn[3];
realOut[3] = realIn[0] + imagIn[1] - realIn[2] - imagIn[3];
return realOut;
}
if (N === 2) {
realOut[0] = realIn[0] + realIn[1];
realOut[1] = realIn[0] - realIn[1];
return realOut;
}
if (N === 1) {
realOut[0] = realIn[0];
return realOut;
}
return _ifftReal(realIn, imagIn);
}
function _ifftReal(realIn, imagIn) {
const N = realIn.length;
const realOut = new Float64Array(N);
if (N === 8) {
const realEvens0 = realIn[0] + realIn[2] + realIn[4] + realIn[6];
const realOdds0 = realIn[1] + realIn[3] + realIn[5] + realIn[7];
realOut[0] = realEvens0 + realOdds0;
realOut[4] = realEvens0 - realOdds0;
const realEvens1 = realIn[0] - imagIn[2] - realIn[4] + imagIn[6];
const realOdds1 = realIn[1] - imagIn[3] - realIn[5] + imagIn[7];
const imagOdds1 = imagIn[1] + realIn[3] - imagIn[5] - realIn[7];
const realQ1 = (realOdds1 - imagOdds1) * SQRT1_2;
realOut[1] = realEvens1 + realQ1;
realOut[5] = realEvens1 - realQ1;
const realEvens2 = realIn[0] - realIn[2] + realIn[4] - realIn[6];
const imagOdds2 = imagIn[1] - imagIn[3] + imagIn[5] - imagIn[7];
realOut[2] = realEvens2 - imagOdds2;
realOut[6] = realEvens2 + imagOdds2;
const realEvens3 = realIn[0] + imagIn[2] - realIn[4] - imagIn[6];
const realOdds3 = realIn[1] + imagIn[3] - realIn[5] - imagIn[7];
const imagOdds3 = imagIn[1] - realIn[3] - imagIn[5] + realIn[7];
const realQ3 = (realOdds3 + imagOdds3) * SQRT1_2;
realOut[3] = realEvens3 - realQ3;
realOut[7] = realEvens3 + realQ3;
return realOut;
}
const M = N >>> 1;
const [realEvensIn, imagEvensIn, realOddsIn, imagOddsIn] = splitComplexEvenOdd(realIn, imagIn, M);
const realEvens = _ifftReal(realEvensIn, imagEvensIn);
const [realOdds, imagOdds] = _ifft(realOddsIn, imagOddsIn);
realOut[0] = realEvens[0] + realOdds[0];
realOut[M] = realEvens[0] - realOdds[0];
const [cosines, sines] = getTables(M);
for (let k = 1; k < M; ++k) {
const realQ = realOdds[k] * cosines[k] - imagOdds[k] * sines[k];
realOut[k] = realEvens[k] + realQ;
realOut[k + M] = realEvens[k] - realQ;
}
return realOut;
}
//# sourceMappingURL=index.js.map