The Fourier Transform from Scratch — Reading a Signal as a Stack of Pure Tones
A signal drawn against time hides the thing you usually want to know: which frequencies are in it, and how much of each. This article builds the Fourier transform from one idea — measure a signal against a pure tone with an inner product — then takes the continuous integral apart symbol by symbol, does the same for the DFT, separates two tones by hand, and ends on the three traps that bite in practice: aliasing, finite resolution, and spectral leakage.
Key points
- A pure tone is fully described by three numbers, amplitude A, frequency f and phase φ, and can be written as a complex exponential e^{2πift}.
- Over a window of length T, tones whose frequencies differ by k/T are orthogonal, so multiplying by a tone and averaging cancels every other frequency.
- The Fourier transform X(f)=∫x(t)e^{-2πift}dt is the inner product of the signal with a probe tone at frequency f; |X(f)| is the amplitude and its angle is the phase.
- In the DFT, bin index k corresponds to frequency f_k = k f_s / N, and the bin spacing Δf = f_s/N = 1/T depends only on recording length.
- Sampling at or below 2 f_max causes aliasing, tones closer than Δf cannot be separated, and tones not on a bin centre leak across the spectrum.
Théorie analytique de la chaleur
Primary source — what this article is built on
undefined2026-09-23
Théorie analytique de la chaleurFourier 1822Paper pageDiscrete-Time Signal Processing"Oppenheim & Schafer 2010
https://www.pearson.com/en-us/subject-catalog/p/discrete-time-signal-processing/P200000003226"3rd ed.
The graph you have is not the graph you want
Record a second of a piano chord and plot it. What you get is a wobbling line: pressure against time. Somewhere in that wobble are three struck notes, the hum of the room, and the hiss of the microphone — but you cannot see any of them. The plot answers the question "what was the pressure at each instant?" when the question you actually have is "which tones are in here, and how loud is each one?"
The Fourier transform is the machine that converts the first answer into the second. It takes a function of time and returns a function of frequency, and every serious piece of signal work — audio codecs, JPEG, MRI reconstruction, the spectrogram a speech model eats for breakfast — starts by running it.
This article builds it in the order the idea actually assembles: the two prerequisites (the pure tone, and what it means for two tones to be orthogonal), the one measuring device that does all the work (the inner product), the continuous formula taken apart one symbol at a time, the discrete version you can actually compute, a worked example, and then the three things that go wrong.
Prerequisite 1: the pure tone
The atom of this whole subject is the sinusoid — a single pure tone:
Three numbers describe it completely. is the amplitude, how loud. is the frequency in cycles per second (hertz), how high the pitch — turns cycles into radians, so that advancing by seconds advances the angle by exactly one full turn. is the phase, where in its cycle the wave starts at .
In words: a pure tone is fully pinned down by three dials — how loud, how high, and where it starts.
Two conventions make the algebra bearable. First, write the tone as a complex exponential rather than a sine, using Euler's formula:
Read as a picture: is the point on the unit circle at angle , with the cosine as its horizontal coordinate and the sine as its vertical one. So , as runs forward, is a dot going around the unit circle times per second. That single object carries both the cosine and the sine at once, which is why it replaces them: one complex tone instead of two real ones.
Second, let amplitude and phase live together in one complex number. A complex number has a magnitude and an angle — exactly the two things a tone needs. So "the amount of frequency present" will come out as a single complex number whose magnitude is the amplitude and whose angle is the phase. That is not a trick; it is the reason the transform's output is complex-valued.
Prerequisite 2: orthogonality — why tones do not contaminate each other
Here is the fact that makes the whole method possible: different frequencies do not interfere with the measurement of each other. Stated precisely, over a window of length ,
Term by term: the overbar is the complex conjugate (flip the sign of the imaginary part, which for means ); multiplying by adds the exponents, leaving a single tone at the difference frequency ; and integrating a tone over a whole number of its own cycles gives zero, because every rise is cancelled by an equal fall. Only when the difference frequency is zero does the integrand become the constant , whose integral is the window length .
In words: multiply a signal by a tone, average, and every frequency except that one averages itself away. The frequencies — a whole number of cycles inside the window — form an orthogonal family, the continuous analogue of perpendicular axes. The Fourier transform is nothing more than reading off coordinates along those axes, and the "" clause in the formula is the fine print that will come back to bite us later as leakage.
The intuition: a signal is a stack, and we weigh one layer at a time
Now the central claim, which is Fourier's 1822 insight: any reasonable signal can be written as a sum of pure tones. Not approximated by — written as. A square wave, a drum hit, a spoken vowel: each is some particular recipe of sinusoids added together. The signal is a stack of transparent layers, one per frequency, and what you see on the oscilloscope is the stack viewed from above.
If that is true, then "analysing" a signal means one thing: find the recipe. And thanks to orthogonality we can find it one ingredient at a time, without solving for all of them simultaneously. To ask "how much 440 Hz is in this signal?", multiply the signal by a 440 Hz tone and average. The 440 Hz layer, multiplied by itself, is everywhere non-negative and averages to something big. Every other layer, multiplied by the 440 Hz tone, becomes a tone at a nonzero difference frequency and averages to zero. One number falls out, and it is the amount of 440 Hz — regardless of what else was in there.
A geometric way to say the same thing: think of the signal as a vector in a space whose axes are frequencies. Multiplying-and-averaging is the inner product with one axis, i.e. a projection. Just as pulls the -coordinate out of a 3D vector without needing to know or , the inner product against pulls out the -coordinate of a signal. The Fourier transform is the full list of those coordinates.
The continuous Fourier transform, one symbol at a time
Take the inner-product recipe, let the window stretch to the whole time axis, and write it down:
Read left to right:
- is the input signal, a function of time.
- is the output, called the spectrum — a different function, of frequency. Feed it a frequency, get back a complex number.
- is the probe tone at frequency . The minus sign is not decoration: the inner product of complex functions is , and the conjugate of the tone is . So the minus sign is exactly what makes this a projection onto that tone and not something else.
- Multiplying by the probe is the "compare against this frequency" step; is the "average over all time" step that kills every other frequency.
And the output, unpacked: is how much of frequency is present (the amplitude), and is where in its cycle that component sits (the phase). Plotting against gives the picture we wanted at the start — the chord's notes standing up as separate spikes.
The transform is reversible, which is what "the signal is the stack" really means:
In words: to rebuild the signal, take every frequency's tone , scale it by that frequency's coefficient , and add them all up. Note the sign has flipped to — analysis projects onto the conjugate, synthesis adds the tones themselves. Nothing is lost going either way: the time picture and the frequency picture are two descriptions of one object.
One immediate reading: at the probe is , so — the DC component, the signal's total offset from zero. The lowest "frequency" is the average value.
The discrete version: what a computer actually runs
A computer has neither an infinite time axis nor a continuum of samples. It has numbers , taken at a sampling rate samples per second, so sample sits at time and the whole record lasts seconds. Replace the integral by a sum and the continuum of frequencies by the orthogonal family , and the formula becomes the Discrete Fourier Transform (DFT):
In words: for each of test frequencies, multiply the samples by that frequency's tone and add them up; the total is how much of that frequency the record contains.
Every piece maps onto something in the continuous version:
- replaces — the signal, now a finite list.
- replaces — averaging over the record instead of over all time.
- replaces : substituting and gives , since . The exponent is the same exponent; only the bookkeeping changed.
- is called bin , and it means the amount of frequency present.
That last line is the single most useful fact in applied Fourier work: bin index converts to hertz as , and consecutive bins are spaced by
In words: the finest frequency detail you can see is one over the length of the recording.
The spacing depends only on how long you recorded, not how fast you sampled. Want to tell 440 Hz from 441 Hz? You need Hz, so you need at least one full second of audio. No amount of oversampling substitutes for it.
The inverse is the same sum with the sign flipped and a out front:
In words: add every bin's tone back together, each scaled by its coefficient, and you get the original samples exactly.
The is there because the discrete orthogonality relation is when (and otherwise — it is a geometric series whose ratio is an -th root of unity). Projecting onto a basis vector of squared length overcounts by , and the inverse divides it back out. Where that constant sits is pure convention, which is why different libraries normalise differently — a real source of factor-of- bugs.
Computed as written, the DFT costs multiplications for each of bins: . The FFT is the divide-and-conquer reorganisation that gets the same numbers in ; it is a faster route to this formula, not a different transform. That algorithm is the subject of The FFT from Scratch.
Worked example: pulling two tones back apart
Mix two sinusoids, one loud and low, one quiet and higher:
Sample it at Hz for exactly one second, so and Hz. Each bin is one hertz wide, and both tones sit on exact bin centres — 5 Hz lands in bin 5, 12 Hz in bin 12.
import numpy as np
fs, N = 128, 128
t = np.arange(N) / fs
x = np.sin(2*np.pi*5*t) + 0.5*np.sin(2*np.pi*12*t)
X = np.fft.fft(x)
mag = np.abs(X)
for k in np.argsort(mag)[::-1][:4]:
print(f"bin {k:3d} = {k*fs/N:5.1f} Hz |X| = {mag[k]:.1f}")
Out come exactly four peaks, and every one of them is predictable:
bin 5 = 5.0 Hz |X| = 64.0
bin 123 = 123.0 Hz |X| = 64.0
bin 12 = 12.0 Hz |X| = 32.0
bin 116 = 116.0 Hz |X| = 32.0
Three things to read off. First, the magnitudes. A real sinusoid of amplitude produces a peak of height , because splits the tone into two complex exponentials of size , and the sum over samples multiplies each by . So gives and gives . The ratio of the peaks is the ratio of the amplitudes — the transform recovered the recipe.
Second, the mirror. Bins 123 and 116 are the halves: and . For any real-valued input, , so the top half of the spectrum is a reflection of the bottom half and carries no new information. This is why plots stop at and why np.fft.rfft exists.
Third, the phase. Printing the raw values gives and : purely imaginary, no real part. That is the signature of a sine rather than a cosine — a cosine would have come out purely real. Discarding the phase and keeping only throws that away, which is exactly why you cannot reconstruct audio from a magnitude spectrogram alone without some phase-recovery trick.
Three pitfalls, in the order they will bite you
Aliasing: sampling too slowly forges a frequency
The Nyquist–Shannon sampling theorem says a signal containing no frequency above is fully determined by samples taken at . Half the sampling rate, , is the Nyquist frequency — the highest frequency the samples can represent at all.
Break that and the energy does not vanish; it comes back in disguise. A tone at above Nyquist is indistinguishable, at the sample instants, from one at for the integer that brings it into range. Add np.sin(2*np.pi*100*t) to the example above, still at : 100 Hz is past Nyquist (64 Hz), and the peak appears at bin 28 — because . A perfectly clean 100 Hz tone is reported as a perfectly clean 28 Hz tone, and nothing in the data marks it as fake. The only fix is prevention: a low-pass anti-aliasing filter before the sampler, which is why every audio interface has one in hardware.
Resolution: a short window cannot tell close frequencies apart
is a hard floor. Two tones closer together than one bin land in the same bin and merge into a single peak; no post-processing separates them. The standard mistake is to zero-pad the record and celebrate the smoother-looking spectrum: zero-padding interpolates the same underlying information onto a finer grid, making peaks easier to locate, but it does not resolve tones that were already merged. And since is also the interval over which the spectrum is an average, wanting both fine frequency detail and fine timing detail puts you in direct conflict — the time–frequency trade-off that forces a spectrogram to pick a window length and live with it.
Leakage: a tone that is not an exact bin smears across the whole spectrum
Go back to the orthogonality condition: it held only for frequency differences that are whole numbers of cycles per window. A tone that completes a fractional number of cycles inside the window is orthogonal to nothing, and its energy spills into every bin. Take with the same — 5.5 cycles in the window, dead between bins:
bin: 3 4 5 6 7 8
|X|: 10.5 15.7 42.6 39.0 12.0 6.7
Instead of one spike of 64 there are two shoulders of ~41 and a long tail of skirts. Physically, the DFT assumes the record repeats forever, so cutting a wave mid-cycle creates a discontinuity at the seam, and that artificial jump has broadband content.
The standard treatment is a window function: multiply the samples by a curve that tapers smoothly to zero at both ends, removing the seam. Applying a Hann window to the same signal gives
bin: 3 4 5 6 7 8
|X|: 0.81 5.60 27.01 27.03 5.58 0.79
The distant skirts drop by more than an order of magnitude. Note what it cost: the main lobe is still spread over two bins (slightly wider than before), and the peak heights shrank because the window removed signal energy. That is the permanent bargain of windowing — lower sidelobes in exchange for a wider main lobe and an amplitude correction you have to apply yourself.
How this shows up on the job
You will essentially never write the DFT sum by hand; you will call np.fft.rfft, torch.stft, or librosa.melspectrogram. What you will spend real time on is the four parameters those functions expose, and each one is a pitfall above wearing a keyword argument.
n_fft is your frequency resolution. It fixes . At 16 kHz speech with n_fft=512, bins are 31 Hz apart — fine for formants, too coarse to separate the harmonics of a low male voice. Raising it buys frequency detail and pays in time detail.
hop_length and window are leakage management. A spectrogram chops audio into overlapping frames; each frame is a finite window, so each frame leaks. Hann is the default for a reason, and the conventional 50–75 % overlap exists so that the tapered-away edges of one frame are covered by the middle of the next.
Normalisation conventions differ, and the symptom is a clean factor of . NumPy puts the on the inverse; some libraries use on both sides; rfft returns bins, so summing its magnitudes double-counts nothing but summing power without a factor of 2 for the folded half undercounts. When a measured level is off by a suspiciously round factor, check the convention before the physics.
Anything reported below Nyquist can still be an alias. If a spectrum shows a tone you cannot account for, verify the anti-aliasing path before hunting for it in the source. Resampling in software has the same requirement: downsampling without low-passing first folds the discarded top octave back down into the audible band.
Summary
- A time plot answers "what was the value at each instant"; the Fourier transform answers "which frequencies are present, and how much of each"
- The whole method is one measuring device — the inner product — and it works one frequency at a time because distinct frequencies are orthogonal over the window
- Output is complex on purpose: magnitude is amplitude, argument is phase
- The DFT is the same formula on a finite record; bin means , and bin spacing is set by record length alone
- Three hard limits: sample above or frequencies come back disguised (aliasing); tones closer than cannot be separated (resolution); tones off bin centre smear across the spectrum unless you window (leakage)
For the algorithm and the convolution theorem that follows from it, continue to The FFT from Scratch. For the linear-algebra reading of "project onto an orthogonal basis", see Linear Algebra for AI.
Exercises
- You record 0.25 seconds of audio at Hz. What is the bin spacing , how many bins does the DFT produce, and which bin index corresponds to 1000 Hz?
- A signal is sampled at Hz and contains a pure tone at 700 Hz with no anti-aliasing filter. At what frequency will the peak appear, and why can you not detect from the samples alone that it is spurious?
- A colleague needs to distinguish two engine harmonics 3 Hz apart. He records 0.2 s at a very high sampling rate and zero-pads to 65536 points, then reports that the peaks still merge. What is wrong with his approach, and what must he change?
- Taking the DFT of with , gives — purely real — while the same experiment with gives . Explain both the magnitude 32 and the difference in phase.
Answers
- Hz. The record holds samples, so the DFT produces 2000 bins (of which 1001 are independent for a real signal). 1000 Hz sits at .
- Nyquist is Hz, and 700 Hz is above it, so the tone folds to Hz. The samples of a 700 Hz tone and of a 300 Hz tone are numerically identical at these instants, so no analysis of the sample values can tell them apart — the information was destroyed at sampling time, not lost in processing. Only a filter before the sampler prevents it.
- Resolution is fixed by record length: Hz, which is coarser than the 3 Hz gap, so the two tones fall in one bin. Sampling rate raises the highest representable frequency, not the resolution, and zero-padding only interpolates the spectrum onto a finer grid — it cannot recover a distinction the window never captured. He must record longer: at least s, and in practice a few times that for a clean separation.
- Magnitude: a real sinusoid of amplitude splits into two complex exponentials of size , each summing to at its bin — here . Phase: gives two positive halves, so the coefficient is real, whereas carries the factor, rotating the coefficient onto the negative imaginary axis. The magnitude is the same because amplitude is the same; the argument differs by 90° because that is precisely the phase offset between a sine and a cosine.
Comments
Sign in to comment