Task 5 of 5

Give the Amplitude Back

Task 3 left a loose end. The Hann window collapsed the leak by 29 dB and also dropped the measured amplitude from 0.65 to 0.42 — further from the truth than before. That is neither a bug nor a mystery. The window multiplies the signal by something whose average is exactly 0.5, so the peak comes back exactly half size. Divide it back out and you are finished.

The number to divide by is the window's coherent gain, CG = Σw / n — the taper's mean value, 1 for rectangular, 0.5 for Hann, 0.42 for Blackman. Folding it into the amplitude formula from task 1 leaves something pleasantly compact:

amplitude = 2 · |X[k]| / Σw

Miss it and every amplitude your analyser reports is low by a constant factor — 50% for Hann, 58% for Blackman. That is exactly the kind of bug that survives for years, because a clean scale error never looks broken.

Its sister number is the noise gain, Σw² / n, which is 0.375 for Hann. That one is for power: the mean square of a broadband noise floor scales with the sum of the squares, not with the square of the sum. Amplitude of a tone → Σw. Power of a noise floor → Σw². Reach for the wrong one and you are wrong by a factor nothing will ever flag, so it is worth carrying both.

And a tidy way to get either: hand your window kernel a signal of 256 ones, and it hands you back the window.

Goal: build a calibrated amplitude spectrum and recover the true amplitude of one tone through all three windows.

Requirements

Hint 1 — a scalar argument

Kernel arguments do not have to be arrays. sumW arrives as a plain number and is used like one:

const re = spec[0][this.thread.x];
const im = spec[1][this.thread.x];
return 2 * Math.sqrt(re * re + im * im) / sumW;
Hint 2 — the window, from the window kernel

Every window kernel here is "sample × taper", so a signal of ones makes it return the taper:

const w = hann(flat);            // flat = new Array(256).fill(1)
let sumW = 0;
let sumW2 = 0;
for (let i = 0; i < w.length; i++) {
  sumW += w[i];
  sumW2 += w[i] * w[i];
}

Which also gives the rectangular case for free: rect(flat) is 256 ones, so its sumW is 256.

Hint 3 — what you should see

Three raw peaks — 76.8, 38.4, 32.256 — and one amplitude, 0.600, three times over. Hann's coherent gain is 0.5 exactly and its noise gain 0.375 exactly: a periodic Hann window sums to n/2 and its squares to 3n/8, which doubles as a check that your window is the periodic one.

Same idea elsewhere

Coherent gain is why a home-made analyser and a real one disagree by a constant: scipy's welch takes scaling='spectrum' versus 'density' precisely to pick Σw versus Σw² normalisation, and every vendor's spectrum-analyser manual carries a window table with both columns. On the GPU it is one scalar uniform folded into a pass you already run — the cheapest correctness fix in this module, and the most commonly skipped.

All tasks in Windowing & Spectral Leakage

  1. A Tone the Window Does Not Fit
  2. What the Transform Actually Sees
  3. Taper the Edges
  4. Measure the Trade
  5. Give the Amplitude Back

This page is an interactive exercise — the editor, the GPU runner and your saved progress need JavaScript. The text above is the full brief.