dsp-collection
Version:
A collection of JavaScript modules for digital signal processing (written in TypeScript)
276 lines • 8.19 kB
JavaScript
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