@thi.ng/dsp
Version:
Composable signal generators, oscillators, filters, FFT, spectrum, windowing & related DSP utils
211 lines (210 loc) • 4.87 kB
JavaScript
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
};