Task 3 of 5
The ringing came from the cliff. A rectangle in frequency has a discontinuity, and a discontinuity in one domain is a long, slowly-decaying tail in the other. Round the cliff off and the tail goes with it.
The most satisfying way to round it off is a Gaussian, because the Fourier transform of a Gaussian is a Gaussian. The impulse response of this filter is therefore a Gaussian too — strictly positive, with no negative lobes anywhere. A filter that never subtracts cannot overshoot. The ringing does not shrink here; it vanishes, from about 9.2% to about 0.00%.
You pay for it twice. A Gaussian never reaches zero, so it never removes anything
completely: at σ = 12 the bin at k = 26 is not deleted, it is
scaled by exp(−26²/(2·12²)) ≈ 0.096. And it starts attenuating immediately —
the harmonic at k = 18, which the brick wall passed at full strength, comes
through at 0.325. The edge that swung in 7 samples now takes 13. That is the
whole trade, and it is the same one Windowing & Spectral Leakage makes from the other
end, one module back: a smooth taper buys clean tails at the cost of sharpness.
Convolution & Filters convolved a Gaussian blur directly, tap by tap. This is that same filter, arriving from the other side.
rollOff so every bin is scaled by
exp(−dist²/(2σ²)), where dist is the folded distance from DC —
and watch the overshoot collapse.output: [256, 2]; the spectrum arrives as spec[0][k] / spec[1][k]Math.min(k, this.constants.n - k)h = Math.exp(-(dist * dist) / (2 * sigma * sigma)) with sigma from this.constantsh — a response that touches only the real part is not a filterCompute h once, before you look at which plane you are on. Every
thread of a bin must agree on it:
const k = this.thread.x;
const dist = Math.min(k, this.constants.n - k);
const s = this.constants.sigma;
const h = Math.exp(-(dist * dist) / (2 * s * s));No branch on the cutoff at all — there is no cutoff. Every bin is scaled, most of them by almost nothing:
if (this.thread.y === 0) return spec[0][k] * h;
return spec[1][k] * h;
The 2 under the exponent is not decoration. Leave it out and you have a
filter √2 narrower than the one you asked for.
cv::GaussianBlur and every game engine's
bloom pass use a Gaussian rather than a box: the box is a brick wall in time, and it rings
in frequency.
This page is an interactive exercise — the editor, the GPU runner and your saved progress need JavaScript. The text above is the full brief.