# What the Transform Actually Sees

*Task 2 of 5 · [Windowing & Spectral Leakage](https://gpu.rocks/learn/windowing-f563138d.md) · GPU.js Learn*

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.

## Figures

- **half a cycle left over, and every join is a cliff the transform has to describe**

## Goal

**Goal:** write the wrapped-difference kernel and log, for both
tones, the size of the step at the seam and the largest step anywhere in the window.

## Requirements

- `output: [256]` — cell `i` holds `signal[(i + 1) % 256] - signal[i]`
- The last cell wraps rather than clamping: sample 255 is followed by sample 0
- Log one line per signal, labelled `onBin` / `offBin`, carrying `Math.abs(step[255])` and the largest `Math.abs(step[i])` anywhere

## Hint 1 — the wrap

`%` 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.

## Hint 2 — the kernel body

```js
const i = this.thread.x;
return signal[(i + 1) % this.constants.n] - signal[i];
```

## Hint 3 — what you should see

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.

## Same idea elsewhere

Circular boundaries are the default in frequency-domain work everywhere:
`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.

## Starter code

```js
// The DFT tiles your window end to end. Measure the seam.
const gpu = new GPU({ mode });

const wrappedStep = gpu.createKernel(function (signal) {
  const i = this.thread.x;
  // TODO: the step from sample i to the next sample IN THE REPEATED signal.
  // Sample 255's neighbour is sample 0 — use % this.constants.n.
  return 0;
}, { output: [256], constants: { n: 256 } });

function report(label, signal) {
  const step = wrappedStep(signal);
  // TODO: seam    = Math.abs(step[255])
  //       biggest = the largest Math.abs(step[i]) over all 256 cells
  console.log(label, 'seam:', 0, 'biggest step:', 0);
}

report('onBin ', onBin);
report('offBin', offBin);
```

---

Interactive version: https://gpu.rocks/learn/windowing-f563138d/2

[Previous task](https://gpu.rocks/learn/windowing-f563138d/1.md) · [Next task](https://gpu.rocks/learn/windowing-f563138d/3.md)
