Task 4 of 5
"Use a Hann window" is folklore until you can say what it costs. Two numbers settle it, and both are in the picture above.
Main-lobe width is how many bins a single pure tone occupies — the frequency resolution you have left. Peak side-lobe level is how far down, in dB, the worst ripple outside that lobe sits — the dynamic range you have bought. The rectangular window (which is all "no window" ever meant: multiply by 1 and stop dead at the edges) wins the first and loses the second, badly. Every other window trades one for the other, and choosing between them is the whole skill.
To see either you have to look between the bins, because a windowed on-bin tone lands on integer bins where these curves are exactly zero. The standard trick is zero padding: run a 1,024-point transform over a 256-sample signal, so 768 of the terms are zero. Nothing is added and nothing is lost, but the spectrum is now sampled four times per bin. Be clear about what that buys — interpolation, never resolution. Zero padding draws the same curve with more dots; only a longer window makes the curve narrower.
blackman with output: [256]: the sample times 0.42 - 0.5·cos(2πi / n) + 0.08·cos(4πi / n)2 * steps / 420 * Math.log10(worst / peak), with worst the largest magnitude from that null out to grid index 512rect / hann / blackman, carrying its width in bins and its side-lobe level in dBThree cosine terms, and the third runs at double the frequency — so name the angle once and the whole window is one line:
const i = this.thread.x;
const a = 2 * Math.PI * i / this.constants.n;
const w = 0.42 - 0.5 * Math.cos(a) + 0.08 * Math.cos(2 * a);
return signal[i] * w;
One gpu.js trap while you are in here: do not name a kernel local after one of
your own constants. const n = this.constants.n; transpiles to
const constants_n = constants_n; and the kernel dies with "cannot
access before initialization".
A main lobe falls monotonically to its first zero, and only then do the side lobes start climbing again. So keep stepping while the next sample is smaller than this one:
let i = peakAt;
while (mag[i + 1] < mag[i]) i++;
const bins = 2 * (i - peakAt) / 4; // 4 grid points per binWidths of 2, 4 and 6 bins, and side
lobes at roughly -13, -32 and -58 dB. Read
it as a menu: three extra bins of width buy 45 dB of dynamic range. Hamming, not
asked for here, sits between Hann and Blackman at 4 bins and about
-41 dB.
This page is an interactive exercise — the editor, the GPU runner and your saved progress need JavaScript. The text above is the full brief.