Task 2 of 5
Why should half a cycle wreck a spectrum? Because every basis function the DFT measures against completes a whole number of cycles in the window. What it can represent exactly, then, is a signal that repeats with period n — so that is what it assumes you gave it. Hand it 256 samples and it does not see a 256-sample excerpt of something longer. It sees those 256 samples tiled end to end, forever.
Tile a tone with 8 whole cycles and the copies join invisibly: sample 255 runs into sample 0 exactly as if the cosine had never stopped. Tile one with 8.5 and every join is a cliff — the wave ends near the bottom and restarts at the top, half a cycle skipped. A cliff is broadband: no small set of smooth sinusoids can build a step, so the transform pays for it with a little energy in every bin it has. That is where the smear in the last task came from. It was never in your signal; the transform put it there, faithfully describing a discontinuity you never intended.
You can measure that cliff without transforming anything, and one kernel does it: the step from each sample to the next in the repeated signal. The wrap is the whole point — sample 255's neighbour is sample 0 of the next copy.
output: [256] — cell i holds signal[(i + 1) % 256] - signal[i]onBin / offBin, carrying Math.abs(step[255]) and the largest Math.abs(step[i]) anywhere% works inside a kernel (it transpiles to GLSL's
mod()), so the neighbour of sample i in the repeated
signal is signal[(i + 1) % 256]. At i = 255 that is
signal[0] — the seam.
const i = this.thread.x;
return signal[(i + 1) % this.constants.n] - signal[i];For the tone that fits, the seam step is the smallest kind of step in the window — about a tenth of the biggest. For the one that does not, the seam step is the biggest step, ten times anything the wave does inside the window. That factor of a hundred between the two seams is the leakage, before you have transformed anything at all.
cufftExecR2C, WGSL FFT compute passes and every convolution-theorem
implementation treat the buffer as a ring. It is the same wraparound the Cellular
Automata module uses for a toroidal grid — and exactly why frequency-domain
convolution has to be zero-padded before use, or the tail of the filter wraps round
and lands on the beginning of the signal.
This page is an interactive exercise — the editor, the GPU runner and your saved progress need JavaScript. The text above is the full brief.