Reverbs · Signal track · Stanford ASPMA · Module 3

Fourier theorems
& the windowing you can't skip

You built the DFT and watched it name a chord. But point it at a real, untruncated tone and it smears — energy bleeds across every bin. This module is the fine print that makes the DFT trustworthy, and the one preparatory move — windowing — you must make before every transform.

Scroll to begin
00 why a transform needs rules

The DFT works. Now: how does it behave?

In The DFT you built the machine: for every bin k, correlate the signal against a circling test wave and sum. You proved it names a single sine cleanly. But a working machine isn't the same as a usable one.

Real questions arrive immediately. If I delay a sound by 3 milliseconds, what happens to its spectrum? If I add two signals, can I add their spectra? If I multiply two signals — say a tone times a fade — what does the spectrum do? And the uncomfortable one: when I feed the DFT a tone whose frequency falls between two bins, why does the clean spike explode into a blurry mess?

The answers are the Fourier theorems — a small set of properties that turn the DFT from a black box into a tool you can reason with. The last of them forces a confession: the DFT secretly assumes your signal repeats forever, and chopping out a finite window breaks that assumption. The fix is windowing, and it's the gateway to the next module.

what this lesson covers

Linearity and the shift theorem (a time delay is a phase ramp), symmetry (real signals fold), the convolution theorem (multiply in one domain = convolve in the other), Parseval (energy is conserved) worked by hand, then the big reveal — spectral leakage derived from a truncated sine — and the windows (Hann / Hamming / Blackman) that tame it, with the main-lobe-vs-side-lobe tradeoff, and why zero-padding interpolates but never sharpens.

01 the two easiest rules

Add freely. Delay = a spinning phase.

Start with the rule so obvious it's easy to overlook. The DFT is just a weighted sum, and sums distribute. So if you mix two signals, the spectrum of the mix is the sum of the spectra. This is linearity:

DFT{ a·x[n] + b·y[n] } = a·X[k] + b·Y[k] scale and add in time → scale and add in frequency. The whole reason additive synthesis and analysis line up.

That single rule is why the DFT could pull a chord apart into separate notes back in the last lesson: a chord is a sum of tones, so its spectrum is the sum of their spikes. No interaction, no cross-terms. Each note lands in its own bin.

Now the first surprising one. Take a signal and delay it by m samples — slide the whole thing later in time. Intuitively nothing about which frequencies are present should change; you didn't add or remove any tones, you just started later. And indeed the magnitude spectrum doesn't move at all. What changes is phase — and it changes in a beautifully regular way. This is the shift theorem. Let's derive it.

The delayed signal is y[n] = x[n − m]. Its DFT is Y[k] = Σn x[n−m]·e−i2πkn/N.
Substitute p = n − m (the delay just relabels the index), so n = p + m:
Y[k] = Σp x[p]·e−i2πk(p+m)/N.
Split the exponent — e−i2πk(p+m)/N = e−i2πkp/N·e−i2πkm/N — and pull the constant m term out of the sum:
Y[k] = e−i2πkm/N · Σp x[p]·e−i2πkp/N.
That leftover sum is just X[k]. So Y[k] = e−i2πkm/N · X[k]. A pure unit-magnitude twist.
DFT{ x[n − m] } = e−i·2πkm/N · X[k] a time shift of m samples multiplies every bin by a phase that ramps linearly with k

Read the result physically. The factor e−i2πkm/N has magnitude exactly 1 — it can't change |X[k]|. It only rotates each bin's phasor by an angle −2πkm/N that grows linearly with k. Low bins barely rotate; high bins spin fast. A delay in time is a phase ramp in frequency. This is the principle behind every phase-vocoder time-stretch and every delay line you'll build later.

three lenses on the shift theorem

Mathematician: shifting the index factors a constant exponential out of the sum — a one-line proof. Musician: sliding a note later in the bar doesn't change which pitch it is, only its timing — exactly why magnitude is untouched and only phase moves. Artist: picture each bin as a clock hand; a delay spins the fast hands more than the slow ones, so the whole spectrum shears into a smooth twist.

02 folding & the deepest theorem

Real signals fold. Multiply ⇄ convolve.

Recall a fact you proved by hand in the DFT lesson: for the sine x = [0,1,0,−1], bin 3 came out the conjugate of bin 1. That wasn't luck. Any real-valued signal has a conjugate-symmetric spectrum — the top half of the bins mirrors the bottom:

x[n] real  ⟹  X[N − k] = X[k]* so |X[N−k]| = |X[k]| — the magnitude spectrum is mirror-symmetric, and we only ever plot the first half

Why it must be true: the only difference between the test wave for bin k and the one for bin N−k is the sign of the angle (e−i2πkn/N vs e+i2πkn/N). For a real x[n], conjugating the whole sum just flips that sign — so bin N−k is the conjugate of bin k. That's why an N-point real DFT only carries N/2 + 1 independent numbers, and why np.fft.rfft returns exactly that many. Half the array is redundant.

Now the theorem that quietly powers all of DSP: the convolution theorem. Convolution is the smear-one-signal-along-another operation — it's how a filter's impulse response shapes a sound, and how an echo gets stamped onto a voice. It's expensive in time. But Fourier turns it into ordinary multiplication:

DFT{ x ∗ h } = X[k] · H[k] convolution in time becomes pointwise multiplication in frequency — the entire reason fast filtering exists

It runs both directions, and the dual is the one that matters for windowing. Multiplying two signals in time convolves their spectra in frequency:

DFT{ x[n] · w[n] } = (1/N) · ( X ∗ W )[k] multiply in time = convolve in frequency. Hold onto this — it IS spectral leakage, in one line.

Why both directions are the same theorem: time and frequency are symmetric mirror-images in the Fourier world, so any rule about one domain has a twin in the other. We'll use exactly this dual in Chapter 4: chopping a signal to N samples is multiplying it by a rectangular window — so its spectrum gets convolved with the rectangle's spectrum. That convolution is the leakage. We're already holding the murder weapon.

03 energy is conserved

The transform can't lose or invent power.

Here's a sanity rule with teeth. The DFT shuffles a signal from time into frequency, but it can't create or destroy energy — the total squared size of the signal. Whatever power is in the samples must equal the power summed over the bins. This is Parseval's theorem:

Σn=0N−1 |x[n]|² = (1/N) · Σk=0N−1 |X[k]|² total energy in time = total energy in frequency (with a 1/N bookkeeping factor for the DFT's scaling)

The 1/N is just the DFT's un-normalized convention — the same factor that shows up when you run the inverse. Don't take it on faith; grind the smallest real case. Reuse the signal from the DFT lesson: x = [0, 1, 0, −1], whose transform we computed by hand as X = [0, −2i, 0, +2i].

Left side (time energy): 0² + 1² + 0² + (−1)² = 0 + 1 + 0 + 1 = 2.
Bin magnitudes squared: |0|² = 0, |−2i|² = 4, |0|² = 0, |+2i|² = 4.
Right side: (1/N)·Σ|X[k]|² = (1/4)·(0 + 4 + 0 + 4) = (1/4)·8 = 2.
2 = 2. Energy in = energy out. The transform is a lossless change of coordinates, not a leak.

Parseval is more than a check — it tells you the spectrum is a faithful redistribution of the signal's power, which is why a spectrum can be read as "how much energy lives at each frequency." It also recasts the decibel. Loudness is logarithmic, so we report magnitudes as dB = 20·log₁₀(|X[k]| / ref) — power-wise that's 10·log₁₀(|X[k]|²/ref²), the same number. Every spectrum plot you'll meet from here on is in dB, because that's the only scale on which a −60 dB side-lobe (a thousandth of the peak) is even visible next to the main peak.

A side-lobe at 1/10 the peak amplitude → 20·log₁₀(0.1) = −20 dB.
At 1/1000 the peak → 20·log₁₀(0.001) = −60 dB. On a linear plot it's invisible; in dB it's a clear shelf.
04 the uncomfortable truth

An off-bin tone smears across everything.

Here is the failure that motivates the whole module. The DFT assumes your N samples are one period of a signal that repeats forever. If a sine completes an integer number of cycles inside the window, that assumption holds: the end joins the beginning seamlessly, the tone lands in exactly one bin, and you get a single clean spike — just like the worked example in the DFT lesson.

But what if the sine completes, say, 8.5 cycles in the window? Now the last sample doesn't connect to the first. When the DFT imagines the signal repeating, it sees a jump discontinuity at every window boundary — and a jump is full of high frequencies that were never in your tone. The energy that belonged in one bin sprays into all of them. This is spectral leakage.

Let's prove why it's a sinc, using the convolution theorem from Chapter 2. Chopping an infinite sine down to N samples is exactly multiplying it by a rectangular window w[n] = 1 for 0 ≤ n < N, zero elsewhere. By the dual, multiplying in time convolves the spectra. The infinite sine's spectrum is a single perfect spike (a delta) at its frequency. Convolving a delta with anything just stamps that thing down at the spike's location. So the measured spectrum is the rectangle's own spectrum, recentered on the tone. And the rectangle's spectrum is the Dirichlet/sinc kernel:

W(f) = Σn=0N−1 e−i2πfn/N = e−iπf(N−1)/N · ( sin(πf) / sin(πf/N) ) a geometric series sums to a ratio of sines — a tall main lobe at f=0 with decaying side lobes, the sampled sinc
The window's DFT is a geometric series Σ rn with r = e−i2πf/N, which sums to (1 − rN)/(1 − r).
Plug in and simplify with 1 − e−iθ = 2i·sin(θ/2)·e−iθ/2: the magnitude collapses to |sin(πf)| / |sin(πf/N)| — the Dirichlet kernel.
For small f/N, sin(πf/N) ≈ πf/N, so |W(f)| ≈ N·|sin(πf)/(πf)| = N·|sinc(f)| — a main lobe plus side lobes.
The side lobes of this rectangular sinc peak at only −13 dB below the main lobe and die slowly. That −13 dB floor is the leakage you can't avoid with a hard cut.

So the result is exact, not hand-wavy: the DFT of a truncated sinusoid is a sinc-shaped main lobe with side lobes, centered on the tone. When the tone sits exactly on a bin, every other bin lands precisely on a zero of that sinc, so you see only the main spike — leakage hides perfectly. When the tone sits between bins, the bins sample the sinc off its zeros, and the side lobes light up everywhere. Same sinc; leakage was always there — integer cycles just hide it.

The hand-worked case. Compare two tones in an N=16 window at sample rate fs=16 kHz, so each bin is fs/N = 1 kHz wide:

8 cycles — on a bin

A tone of 8 kHz completes exactly 8 cycles in 16 samples. It lands dead-center on bin k=8. Sample 15 connects smoothly to sample 0 — no discontinuity. The sinc's zeros fall on every other bin, so all energy is in bin 8 and the rest read ≈ 0.

cycles = 8.0 → bin 8 → one clean spike

8.5 cycles — between bins

A tone of 8.5 kHz completes 8.5 cycles — halfway between bins 8 and 9. The wrap-around has a half-cycle jump. Bins now sample the sinc between its zeros, so bin 8 and bin 9 split the peak and the side lobes light every bin down to the −13 dB floor.

cycles = 8.5 → between bins → smeared everywhere

the big reveal

Leakage isn't a bug in the DFT — it's the unavoidable price of looking at a finite chunk of an infinite world. The rectangular cut has the narrowest possible main lobe (good resolution) but the ugliest side lobes (−13 dB). The next chapter buys quieter side lobes by giving up some of that narrowness. There is no free lunch — only a knob.

05 taming the leak

Soften the edges, shrink the side lobes.

The leakage came from a hard discontinuity at the window edges. The cure is obvious once you see the cause: don't cut hard — fade in and out. Multiply your N samples by a smooth bump w[n] that's zero at both ends and one in the middle. Now the windowed signal tapers to zero at the boundaries, the wrap-around is seamless, and the discontinuity — the source of the high-frequency spray — is gone. This is windowing.

By the convolution theorem, you're trading one window's spectrum for another's. A smoother taper has a spectrum with much lower side lobes — at the cost of a wider main lobe. Three classic tapers, each a sum of cosines:

Hann:  w[n] = 0.5 − 0.5·cos( 2πn / (N−1) ) a raised cosine — zero at both ends, smooth everywhere; side lobes drop to ~−31 dB
Hamming:  w[n] = 0.54 − 0.46·cos( 2πn / (N−1) ) a tweaked raised cosine that doesn't quite reach zero — cancels the nearest side lobe to ~−43 dB
Blackman:  w[n] = 0.42 − 0.5·cos(2πn/(N−1)) + 0.08·cos(4πn/(N−1)) adds a second cosine to push side lobes to ~−58 dB — widest main lobe of the three

That is the one tradeoff to internalise — the main-lobe-width vs side-lobe-level dial. Narrow main lobe = you can separate two close tones (resolution). Low side lobes = a quiet tone next to a loud one isn't buried under leakage (dynamic range). You can't max both:

WindowMain lobe widthPeak side lobeBest for
Rectangular≈ 2 bins (narrowest)−13 dB (worst)resolving two equal, close tones
Hann≈ 4 bins−31 dBthe all-round default
Hamming≈ 4 bins−43 dBwhen the nearest side lobe matters
Blackman≈ 6 bins (widest)−58 dB (best)spotting a faint tone beside a loud one

In code, windowing is one elementwise multiply before the transform — and every serious spectrum analyzer does it:

JavaScript · window then transform
// a Hann window: smooth taper to zero at both ends
function hann(N) {
  const w = new Float32Array(N);
  for (let n = 0; n < N; n++)
    w[n] = 0.5 - 0.5 * Math.cos(2 * Math.PI * n / (N - 1));
  return w;
}

function windowedSpectrum(x) {
  const N = x.length, w = hann(N), xw = new Float32Array(N);
  for (let n = 0; n < N; n++) xw[n] = x[n] * w[n];   // ← the whole fix, one line
  return dft(xw);                                    // the DFT you built last lesson
}
Python · NumPy / SciPy
import numpy as np

N = 256
x = np.sin(2 * np.pi * 8.5 * np.arange(N) / N)   # 8.5 cycles → off-bin, will leak

w = np.hanning(N)              # Hann; np.hamming / np.blackman for the others
X = np.fft.rfft(x * w)         # window BEFORE the transform — one multiply
mag_db = 20 * np.log10(np.abs(X) / np.abs(X).max() + 1e-12)

# rectangular (no window) leaves −13 dB side lobes; Hann drops them past −31 dB.
three lenses on a window

Mathematician: a window is a smooth taper whose spectrum has small side lobes; convolving with it suppresses leakage. Musician: it's a tiny fade-in/fade-out on each analysis frame so the chunk doesn't "click" at its edges. Artist: picture the rectangular cut as a brick wall and the Hann window as a soft spotlight that dims toward the edges — the soft light has no harsh shadow (no side lobes), but its beam is a little broader.

06 the resolution illusion

Zero-padding interpolates. It does not sharpen.

There's a tempting trick: glue a pile of zeros onto the end of your N samples, making the buffer 2N or 4N long, then transform. The spectrum comes back with twice or four times as many bins — finer-looking, smoother. It feels like you bought resolution for free. You didn't.

Here's the precise distinction, and it's worth getting exactly right. Resolution — your ability to tell two nearby tones apart — is set only by how much real signal you observed, the N non-zero samples. The main-lobe width is fixed by that observation length. Zero-padding changes the bin spacing (fs/(N·pad) instead of fs/N), so it places more sample points along the same underlying sinc — it interpolates the spectrum, drawing the leakage curve in finer detail. It does not narrow the main lobe.

Resolution (can you separate two tones?) ← set by N real samples. Main-lobe width ≈ fs/N. Unchanged by padding.
Bin spacing (how finely the spectrum is sampled) ← set by total length N·pad. Spacing = fs/(N·pad). Padding shrinks this.
So padding gives you more points on the same blurry lobe — a smoother picture of leakage, not a sharper peak. To truly resolve closer tones you must capture a longer window (bigger real N).

Why it's still useful: with only N bins, a peak that falls between bins is hard to pin down — you see two stubby bars and have to guess the true frequency. Zero-padding samples the sinc densely enough that the peak's true location becomes clear, which is handy for peak interpolation. So: pad to read a peak's position more precisely; lengthen the actual window to resolve two peaks. Two different jobs, two different fixes.

resolution ≈ fs / Nreal  ·  bin spacing = fs / (Nreal · pad) two different denominators — that gap is the whole illusion. Padding only moves the right one.
see it, hear it

Make leakage appear — then make it vanish.

Every claim in this lesson collapses into one instrument. Pick a window. Slide the test tone onto a bin (integer cycles) and watch the spectrum snap to a single clean spike — then nudge it between bins and watch leakage spray across every bin down to the −13 dB floor. With leakage showing, switch from Rectangular to Hann to Blackman: the side lobes collapse while the main lobe visibly widens — the tradeoff, felt with your hand. Then crank zero-padding and watch the same lobe get drawn in finer detail without ever getting narrower. Press play and the tone you're steering is the sound in your ears.

window →
on binbetween
zero-pad×4
tone 8.0 cyc ON bin 8 pad ×1 clean — one spike

The DFT here is the real two-loop transform from the last lesson, run live over the windowed 256-sample buffer. The teal outline is the window envelope; the warm bar is the true tone's bin. Slide to a half-cycle and switch windows to feel the tradeoff.

one signal, three arts

Math (Stanford): the bars are a genuine DFT, the envelope is a window function, the dB floor exposes the sinc side lobes you derived. Music (Ableton): the slider sets a real tone you can hear, and a window is just a per-frame fade. Art (Nature of Code): the windowed wave and its spectrum are the same signal drawn twice — taper the wave and the spectrum's wild fringe calms in response.

where this goes next

This windowed single-frame analysis is one tile of a much bigger picture. Slide the window along a whole song — slice, window, DFT; slide, window, DFT — and you get the Short-Time Fourier Transform, whose stack of spectra is the spectrogram. Every slice it takes uses exactly the windowing you just built, for exactly the leakage reason you just derived. That's the next lesson in the Signal track — and the view a Lyria-class model's output will paint across the cinematic canvas.

The DFT never lied to you. It only ever sees a finite window — and now you know how to hold that window so it tells the truth.

Sources go deeper

Serra et al., Audio Signal Processing for Music Applications — Module 3, Fourier theorems & the STFT (Stanford / UPF). · F. J. Harris, "On the Use of Windows for Harmonic Analysis with the DFT", Proc. IEEE 1978 — the definitive window catalog. · The Scientist & Engineer's Guide to DSP, Smith — leakage & windows from the ground up. · J. O. Smith, Spectral Audio Signal Processing (CCRMA) — windows, zero-padding, the Dirichlet kernel.

← Back to Reverbs