Safeguard
Supply Chain Security•

Fourier Transform Explained: From Heat Flow to Wi-Fi OFDM, JPEG and MP3

The Fourier transform splits a signal into its frequencies. See how it went from Fourier's heat equation to the FFT inside Wi-Fi, 5G, JPEG and MP3.

Safeguard Security Team
44 min read

The Fourier transform rewrites a signal as a sum of sine and cosine waves, telling you how much of each frequency the signal contains. Joseph Fourier built the idea between 1807 and 1822 to solve the equation for heat flowing through a solid, and the fast Fourier transform (FFT), published by Cooley and Tukey in 1965, made it cheap enough to run billions of times a second inside Wi-Fi radios, 5G phones, JPEG image codecs and MP3 players.

This post goes one level deeper than our overview of famous equations that changed the world. It covers the heat problem Fourier was solving, the Fourier series, the continuous transform, the discrete transform and the FFT, then shows exactly where each one sits in OFDM radio, JPEG and MP3, with worked examples, NumPy code you can run and the numbers it prints. Safeguard builds security software, not maths tools, and nothing here suggests otherwise; the security section near the end is about the code that computes these transforms.

Key takeaways

  • A Fourier series writes a repeating function as a sum of sines and cosines at multiples of one base frequency. Fourier used it to solve the heat equation.
  • The continuous Fourier transform handles signals that never repeat. A short pulse has a wide spectrum and a long pulse a narrow one.
  • The discrete Fourier transform (DFT) is what computers run: N samples in, N complex values out. The FFT computes it in about N log N operations instead of N squared, roughly 52,000 times less work at a million points.
  • Sampling must run at more than twice the highest frequency present, or high frequencies fold down and masquerade as low ones (aliasing). Shannon stated the sampling theorem in 1949.
  • OFDM (Wi-Fi since 802.11a, 4G and 5G) puts data on many narrow subcarriers by running an inverse FFT at the transmitter and an FFT at the receiver. In 802.11a a 20 MHz channel is a 64 point FFT with 312.5 kHz between subcarriers.
  • JPEG applies a close cousin, the discrete cosine transform (DCT), to 8 by 8 pixel blocks and then throws away detail the eye does not miss. MP3 splits sound into 32 subbands and then 576 frequency lines with a modified DCT.
  • Side-channel research uses spectrograms to spot physical leaks from cryptographic code. The defence ships as library updates.

What problem was Fourier solving between 1807 and 1822?

Fourier wanted to predict how temperature changes inside a solid over time. Heat flows from hot to cold at a rate set by how sharply temperature varies from point to point, which gives a partial differential equation now called the heat equation. The hard part was solving it for realistic starting conditions, such as a bar with a sharp edge between two temperatures.

His insight: write the starting profile as a sum of sine waves, and each wave evolves independently, keeping its shape and shrinking exponentially, fine waves faster than broad ones. Add them back together at any later time and you have the temperature.

The claim that almost any starting profile, even one with sharp corners, could be written as such a sum was what made the work controversial. According to the MacTutor History of Mathematics biography, Fourier's memoir On the Propagation of Heat in Solid Bodies was read to the Paris Institute on 21 December 1807, and a committee of Lagrange, Laplace, Monge and Lacroix was set up to report on it. Lagrange and Laplace objected in 1808 to his expansions of functions as trigonometric series.

Timeline: from the heat memoir to the FFT in your phone

DateEventSource
21 March 1768Joseph Fourier born in Auxerre, FranceMacTutor biography
1805Gauss already knows a fast method for computing Fourier coefficientsNumPy and SciPy FFT documentation
21 December 1807Memoir On the Propagation of Heat in Solid Bodies read to the Paris InstituteMacTutor biography
1808Lagrange and Laplace object to the trigonometric series expansionsMacTutor biography
1811Fourier wins the Institute's mathematics prize on heat propagation; the report criticises the rigourMacTutor biography
1822Théorie analytique de la chaleur published in Paris by F. DidotMacTutor biography; Internet Archive scan
16 May 1830Fourier dies in ParisMacTutor biography
1949Shannon's Communication in the Presence of Noise states the sampling theorem as Theorem 1Shannon paper
1965Cooley and Tukey, An Algorithm for the Machine Calculation of Complex Fourier Series, Mathematics of Computation vol. 19, no. 90, pp. 297 to 301Crossref record
18 September 1992CCITT Recommendation T.81 (JPEG) approvedT.81 text
23 April 2017The mp3 patent licensing programme run for Fraunhofer IIS patents endsFraunhofer IIS
September 20263GPP TS 38.211 V19.5.0 lists 5G NR numerologies from 15 kHz to 960 kHz subcarrier spacing3GPP TS 38.211

The 1811 prize report, as MacTutor quotes it, said his analysis still left something to be desired "on the score of generality and even rigour." The Académie published the full prize essay as Théorie analytique de la chaleur in 1822, shortly after Fourier became its secretary. A scan of the 1822 Paris edition, printed by F. Didot, is on the Internet Archive.

Worked example 1: a hot bar cooling down

Take a thin bar of length L = 1 metre, ends held at 0 degrees, interior starting at 1 degree. The heat equation for temperature u at position x and time t is:

du/dt = alpha * d2u/dx2

Here alpha is the material's thermal diffusivity in square metres per second. Try a solution sin(n pi x / L) times a function of time only, and the time factor must be:

exp(-alpha * (n * pi / L)^2 * t)

Each mode decays at a rate that grows with n squared. The sine coefficients of the constant starting profile are:

b_n = 4 / (n * pi)   for odd n
b_n = 0              for even n

So the full solution is:

u(x, t) = sum over odd n of  (4 / (n pi)) * sin(n pi x) * exp(-alpha (n pi)^2 t)

Put in numbers. Pick alpha = 0.0001 square metres per second as an illustrative value. Then:

Mode nStarting amplitude b_nDecay rate alpha (n pi)^2, per secondHalf-life
11.27320.00099about 702 s
30.42440.00888about 78 s
50.25460.02467about 28 s

The n = 3 mode decays nine times faster than n = 1, and n = 5 twenty five times faster. After a few minutes the sharp edges are gone and the profile is a single smooth arch sinking towards zero. Heat flow is a low pass filter: it removes fine detail first. Splitting a hard problem into independent, easy frequency components is the idea that later spread to radio and compression.

What is a Fourier series?

A Fourier series writes a function that repeats every period T as a constant plus a sum of sines and cosines at the base frequency 1/T and its whole number multiples (the harmonics):

f(t) = a_0 / 2 + sum over n = 1, 2, 3, ... of [ a_n cos(2 pi n t / T) + b_n sin(2 pi n t / T) ]

a_n = (2 / T) * integral over one period of f(t) cos(2 pi n t / T) dt
b_n = (2 / T) * integral over one period of f(t) sin(2 pi n t / T) dt

The coefficient formulas work because sines and cosines at different harmonics are orthogonal: multiply two different ones together and integrate over a period, and you get zero. Multiplying f by one particular cosine and integrating therefore picks out exactly how much of that cosine is in f, the same way a dot product with a unit vector picks out one coordinate.

A tidier form uses complex exponentials. By Euler's formula, exp(i theta) = cos(theta) + i sin(theta), so a cosine and a sine at the same frequency can be packed into one complex coefficient c_n:

f(t) = sum over all integers n of  c_n * exp(i 2 pi n t / T)
c_n  = (1 / T) * integral over one period of f(t) * exp(-i 2 pi n t / T) dt

The magnitude of c_n is how much of frequency n/T is present. The angle of c_n is that component's phase, meaning where in its cycle it starts.

The square wave and the Gibbs overshoot

A square wave that alternates between +1 and -1 has only odd sine harmonics, with amplitudes 4/pi, 4/(3 pi), 4/(5 pi) and so on: the same coefficients as the hot bar above. Adding more terms makes the sum hug the square wave more closely, but near each jump the partial sums always overshoot by about 9 percent of the jump, no matter how many terms you add. The overshoot narrows but never shrinks in height. This is the Gibbs phenomenon, and it is the mathematical reason you see ringing halos around sharp edges in heavily compressed JPEG images and pre-echo artefacts in low bitrate audio: a transform coder that drops high frequency terms leaves exactly this kind of ripple near a step.

What does the continuous Fourier transform do?

In plain words: the Fourier transform redescribes a signal from "value at each moment" to "amount and phase at each frequency." Nothing is lost; the inverse transform recovers the original exactly.

A Fourier series only handles signals that repeat. Let the period T grow without limit, and the spacing between harmonics, 1/T, shrinks to nothing. The sum over harmonics turns into an integral over a continuous range of frequencies. That limit is the Fourier transform. In the convention most engineers use, with frequency f in hertz:

X(f) = integral from -infinity to +infinity of x(t) * exp(-i 2 pi f t) dt
x(t) = integral from -infinity to +infinity of X(f) * exp(+i 2 pi f t) df

Read the first line as a recipe. For each candidate frequency f, multiply the signal by a complex wave spinning at f, and add up the result over all time. If the signal contains a component at f, the two spin together and the sum grows large. If not, the product spins around and averages towards zero.

Conventions differ, and that causes bugs

There is no single standard form. The NIST Digital Library of Mathematical Functions, section 1.14, uses angular frequency, a plus sign in the exponent and a factor of 1 over the square root of 2 pi. Engineering texts usually use hertz, a minus sign and no factor.

ConventionForward exponentFactor on forwardFactor on inverseWhere you see it
Hertz, engineering-i 2 pi f t11Signal processing, communications
Angular, unitarysign varies1/sqrt(2 pi)1/sqrt(2 pi)NIST DLMF 1.14 (plus sign)
Angular, non-unitary-i omega t11/(2 pi)Many physics and control texts
NumPy norm="backward" (DFT)-i 2 pi k n / N11/NNumPy and SciPy default
NumPy norm="ortho" (DFT)-i 2 pi k n / N1/sqrt(N)1/sqrt(N)Energy preserving transforms

When two pieces of code disagree by a factor of N, 2 pi or a complex conjugate, the cause is almost always a convention mismatch, not a bug in the maths.

Worked example 2: a rectangular pulse

Take a pulse that is 1 for a duration tau centred on zero and 0 everywhere else. Its transform is:

X(f) = tau * sin(pi f tau) / (pi f tau)  =  tau * sinc(f tau)

Put in numbers. Let tau = 1 millisecond.

  • At f = 0, X = tau = 0.001. The zero frequency value is just the area under the pulse.
  • The first zero is where pi f tau = pi, so f = 1/tau = 1,000 Hz.
  • The main lobe runs from -1 kHz to +1 kHz, so it is 2 kHz wide.
  • Further zeros fall at every multiple of 1 kHz, with side lobes in between that shrink roughly as 1/f.

Shorten the pulse to tau = 1 microsecond and the first zero moves to 1 MHz. A thousand times shorter in time means a thousand times wider in frequency. That is why fast symbols need wide channels, and why OFDM, below, does the opposite: long symbols on many narrow subcarriers.

Properties that make the transform useful

PropertyTime domainFrequency domainWhy it matters
Linearitya x(t) + b y(t)a X(f) + b Y(f)Mix signals, transform separately
Time shiftx(t - t0)X(f) exp(-i 2 pi f t0)A delay only changes phase
Modulationx(t) exp(i 2 pi f0 t)X(f - f0)Shifts a baseband signal to a radio carrier
Convolutionx convolved with hX(f) H(f)Filtering and echoes become multiplication
Parsevalenergy in x(t)energy in X(f)Energy is the same in both views
Scalingx(a t)X(f / a) / abs(a)Squeeze time, stretch frequency

The convolution row matters most. Radio echoes, lens blur and audio filters are convolutions in time and simple multiplications in frequency. The NIST DLMF gives the convolution theorem and Parseval's formula in section 1.14.

What is the discrete Fourier transform, and why does the FFT matter?

A computer cannot integrate over all time. It has N samples taken at a fixed rate, x[0] to x[N-1]. The discrete Fourier transform (DFT) maps those N numbers to N complex frequency values:

X[k] = sum from n = 0 to N-1 of  x[n] * exp(-i 2 pi k n / N)      for k = 0, 1, ..., N-1
x[n] = (1 / N) * sum from k = 0 to N-1 of  X[k] * exp(+i 2 pi k n / N)

This is the convention NumPy uses by default. Its documentation says the default normalisation, called "backward", leaves the forward transform unscaled and scales the inverse by 1/N.

A few facts about the output that trip people up:

  • Bin k corresponds to frequency k times fs / N, where fs is the sample rate. The spacing between bins, fs / N, is the frequency resolution. To resolve finer detail you need a longer recording, not a faster sample rate.
  • X[0] is the sum of all samples, the zero frequency (DC) term. NumPy's documentation notes it is always purely real for real input.
  • For real input, the upper half mirrors the lower half as complex conjugates. The bins above N/2 represent negative frequencies. NumPy's rfft returns only the N/2 + 1 non redundant bins.
  • The DFT assumes the N samples repeat forever. If the signal does not complete a whole number of cycles in the window, the jump at the wraparound spreads energy into neighbouring bins. This is spectral leakage, and a window function such as a Hann window reduces it.

Worked example 3: a four point DFT by hand

Take x = [1, 2, 0, -1]. With N = 4, the complex factor exp(-i 2 pi k n / 4) only takes the values 1, -i, -1 and i.

X[0] = 1 + 2 + 0 + (-1)                       = 2
X[1] = 1 + 2(-i) + 0(-1) + (-1)(i)            = 1 - 3i
X[2] = 1 + 2(-1) + 0(1) + (-1)(-1)            = 0
X[3] = 1 + 2(i) + 0(-1) + (-1)(-i)            = 1 + 3i

X[3] is the complex conjugate of X[1], as expected for real input. X[2], the Nyquist bin, is zero: this signal has no component that alternates sign on every sample. NumPy agrees:

import numpy as np
print(np.fft.fft(np.array([1, 2, 0, -1])))
# [2.+0.j 1.-3.j 0.+0.j 1.+3.j]

Why the FFT changed everything

Computing each X[k] directly takes N multiplications, so N squared in total. The FFT gets the identical answer in about N log2 N. The classic radix 2 version splits the inputs into even and odd positions, computes two half size DFTs, and combines them with one extra multiply per output (a twiddle factor), recursing down to size 1: log2 N levels of about N operations each.

The saving grows with N:

NDirect DFT, about N^2FFT, about N log2 NRatio
64 (802.11a OFDM symbol)4,096384about 11
1,0241,048,57610,240about 102
4,09616,777,21649,152about 341
1,048,576about 1.1 trillionabout 21 millionabout 52,000

These are operation counts, not timings, but the shape is the point: at a million points, a day of direct computation becomes under two seconds.

James Cooley and John Tukey published the algorithm as An Algorithm for the Machine Calculation of Complex Fourier Series in Mathematics of Computation, volume 19, issue 90, pages 297 to 301, in 1965, according to the journal's Crossref record. NumPy's and SciPy's FFT documentation both note that the method was known to Gauss in 1805 and was "brought to light in its current form" by Cooley and Tukey. Gauss did not publish it in a form that spread, and the idea was rediscovered several times before 1965.

The FFT is a family of algorithms covering any N, and production libraries choose among them at run time. FFTW, a free C library released under the GNU General Public License according to its home page, computes DFTs of arbitrary size in one or more dimensions for real and complex data, along with discrete cosine and sine transforms. Its home page lists version 3.3.11 as the latest official release.

How do the transforms compare?

TransformInputOutputTypical use
Fourier seriesPeriodic continuous functionDiscrete list of harmonic coefficientsHeat equation, vibrating strings, analysing waveforms
Continuous Fourier transformNon periodic continuous functionContinuous spectrumTheory of filters, antennas, optics
Discrete-time Fourier transformInfinite sequence of samplesContinuous periodic spectrumAnalysing digital filters
Discrete Fourier transformN samplesN complex valuesWhat software actually computes
FFTN samplesSame as DFT, much fasterEvery practical spectrum, OFDM modem
DCT type IIN real samplesN real valuesJPEG blocks, many video codecs
Modified DCT2N samples with 50 percent overlapN valuesMP3 (second stage), AAC

The DCT variants use only cosines and mirror the signal at its edges instead of wrapping it, which removes the artificial jump at block boundaries and packs energy into fewer coefficients. SciPy's documentation notes there are eight types of DCT, and that "the" DCT usually means type II and "the" inverse DCT usually means type III.

What are sampling, Nyquist and aliasing?

To process a continuous signal digitally you take samples at a fixed rate fs. The question is how fast is fast enough. Claude Shannon's 1949 paper Communication in the Presence of Noise states it as Theorem 1: if a function contains no frequencies higher than W cycles per second, it is completely determined by its values at points spaced 1/(2W) seconds apart. Shannon added that this was "common knowledge in the communication art", and credited earlier mathematicians and Nyquist's work on telegraph signalling.

Two terms come out of that theorem:

  • The Nyquist rate is twice the highest frequency in the signal: the minimum sample rate that can capture it.
  • The Nyquist frequency is half the sample rate: the highest frequency a given sample rate can represent.

If the signal contains energy above the Nyquist frequency, sampling does not just lose it. It folds that energy down into the band below, where it is indistinguishable from a real low frequency component. That is aliasing. You see the visual version when a car wheel on film seems to spin backwards.

Worked example 4: three tones that look identical

Sample at fs = 1,000 Hz, so the Nyquist frequency is 500 Hz. Feed in three pure cosines at 100 Hz, 900 Hz and 1,100 Hz, and ask the FFT where each one peaks:

import numpy as np
fs = 1000
n = np.arange(1000)
for f in (100, 900, 1100):
    x = np.cos(2 * np.pi * f * n / fs)
    k = np.argmax(np.abs(np.fft.rfft(x)))
    print(f"true {f:5d} Hz -> FFT peak at {np.fft.rfftfreq(n.size, 1/fs)[k]:.0f} Hz")

Output:

true   100 Hz -> FFT peak at 100 Hz
true   900 Hz -> FFT peak at 100 Hz
true  1100 Hz -> FFT peak at 100 Hz

All three give identical samples: 900 Hz folds to 1,000 minus 900 = 100 Hz, and 1,100 Hz to 1,100 minus 1,000 = 100 Hz. The only fix is an analog anti-aliasing filter before the converter, with some sample rate headroom because real filters are not perfectly sharp.

A short NumPy example that finds the frequencies in a signal

This is the step by step walkthrough. It builds a noisy signal containing two tones, takes its FFT, and reports which frequencies are present.

Step 1: create an isolated environment

python3 -m venv fft-demo
source fft-demo/bin/activate
pip install numpy
python -c "import numpy; print(numpy.__version__)"

Pinning the version you tested with, for example in a requirements.txt with an exact numpy== line, keeps the result reproducible. We ran the examples in this post with NumPy 2.0.2.

Step 2: write the script

Save this as find_freqs.py:

import numpy as np

fs = 1000                      # samples per second
t = np.arange(0, 1.0, 1 / fs)  # 1 second, 1000 samples
rng = np.random.default_rng(seed=7)
x = (1.0 * np.sin(2 * np.pi * 50 * t)
     + 0.5 * np.sin(2 * np.pi * 120 * t)
     + 0.3 * rng.standard_normal(t.size))

X = np.fft.rfft(x)                         # real-input FFT
freqs = np.fft.rfftfreq(x.size, d=1 / fs)  # bin centres in Hz
amps = 2 * np.abs(X) / x.size              # single-sided amplitude

top = np.argsort(amps)[-2:][::-1]
for k in top:
    print(f"{freqs[k]:6.1f} Hz  amplitude {amps[k]:.3f}")
print("bins:", X.size, " resolution:", freqs[1] - freqs[0], "Hz")

Step 3: run it

python find_freqs.py

Output from our run:

  50.0 Hz  amplitude 1.016
 120.0 Hz  amplitude 0.510
bins: 501  resolution: 1.0 Hz

Step 4: read the result

  • The peaks sit at exactly 50 Hz and 120 Hz, with amplitudes near the 1.0 and 0.5 we put in; noise explains the small error.
  • 1,000 real samples give 501 bins (N/2 + 1), at fs / N = 1 Hz resolution. Both tones fit whole cycles in one second, so there is no leakage.
  • The factor 2/N turns FFT magnitude into sine amplitude. It does not apply to the DC or Nyquist bins.

Step 5: try the failure cases

Change 50 to 50.5 Hz: the peak splits between bins and the amplitude drops. Multiplying x by np.hanning(x.size) reduces that leakage (and the amplitude, which you correct for). Change 120 Hz to 620 Hz and it appears at 380 Hz, aliased.

How does OFDM in Wi-Fi and 4G/5G use the FFT?

Orthogonal frequency division multiplexing (OFDM) splits one wide channel into many narrow subcarriers, puts a little data on each, and sends them all at once.

The trick is in the word orthogonal. If the subcarriers are spaced exactly 1/T apart, where T is the symbol duration, then over one symbol each subcarrier completes a whole number of cycles and the subcarriers do not interfere, even though their spectra overlap. And the operation that turns a list of N complex values, one per subcarrier, into a time signal made of exactly those N subcarriers is the inverse DFT. The receiver's job of separating them again is the forward DFT. Both run as FFTs.

The transmit and receive chain

StepTransmitterReceiver
1Map coded bits to complex symbols (BPSK, QPSK, QAM)Sample the incoming signal
2Place one symbol on each data subcarrier, known pilots on pilot subcarriers, zeros on guard and DC subcarriersFind the symbol boundary using the preamble
3Inverse FFT to get the time domain symbolRemove the cyclic prefix
4Copy the last part of the symbol to the front (cyclic prefix)FFT back to subcarriers
5Convert to analog and up to the radio frequencyDivide each subcarrier by its channel estimate (one tap equalisation)
6TransmitDecide which symbol was sent, then decode

Why the cyclic prefix works

Real channels have echoes, which are a convolution in time, and an echo spilling into the next symbol causes interference. The cyclic prefix fixes this. Because the start of each transmitted symbol is a copy of its end, the channel's echoes make the received symbol look like a circular convolution, as long as the echoes are shorter than the prefix. And the DFT turns circular convolution into plain multiplication. So after the FFT, every subcarrier has simply been multiplied by one complex number, the channel's response at that frequency, and the receiver undoes it with one complex division per subcarrier.

The numbers for 802.11a and 802.11g

The 802.11a and 802.11g OFDM physical layer is the cleanest example. The subcarrier figures and symbol rate below come from the CWNP article Understanding OFDM, Part 2; the timings follow from them by arithmetic:

ParameterValueHow it follows
Channel bandwidth20 MHzSample rate of the FFT
FFT size6464 subcarrier slots
Subcarrier spacing312.5 kHz20 MHz / 64
Used subcarriers5248 data plus 4 pilot
Unused subcarriers1211 guard subcarriers at the channel edges plus the DC (centre) subcarrier
FFT period3.2 microseconds1 / 312.5 kHz
Guard interval (cyclic prefix)0.8 microseconds4 minus 3.2, one quarter of the FFT period
Symbol time4 microseconds250,000 symbols (baud) per second

Worked numbers: 48 data subcarriers times 6 bits (64-QAM) is 288 coded bits per symbol, or 72 Mbit/s at one symbol every 4 microseconds. Rate 3/4 coding leaves 54 Mbit/s, the top 802.11a rate. The 0.8 microsecond guard interval covers echo delays up to about 240 metres of extra path, ample indoors.

Later Wi-Fi generations keep the same FFT core and change the parameters. The Wi-Fi Alliance lists orthogonal frequency division multiple access (OFDMA), which shares one channel's subcarriers among several devices at once, as a key Wi-Fi 6 feature, and describes Wi-Fi 7's 4K-QAM as about 20 percent faster than Wi-Fi 6's 1024-QAM. Packing more bits per subcarrier needs a cleaner signal, because the constellation points sit closer together. For the generation by generation detail, see our post on Wi-Fi generations from 802.11b to Wi-Fi 7 and Wi-Fi 8.

5G NR numerologies

4G LTE and 5G New Radio also use OFDM on the downlink. 5G NR makes the subcarrier spacing a configurable "numerology". In 3GPP TS 38.211 V19.5.0 (September 2026), Table 4.2-1 lists the supported values as 15 kHz times 2 to the power mu:

muSubcarrier spacingUseful symbol length (1 / spacing)Cyclic prefix options
015 kHzabout 66.7 microsecondsNormal
130 kHzabout 33.3 microsecondsNormal
260 kHzabout 16.7 microsecondsNormal, extended
3120 kHzabout 8.33 microsecondsNormal
4240 kHzabout 4.17 microsecondsNormal
5480 kHzabout 2.08 microsecondsNormal
6960 kHzabout 1.04 microsecondsNormal

Symbol lengths exclude the cyclic prefix. Narrow spacing gives long symbols that tolerate long echoes in large cells; wide spacing gives short symbols that suit high frequency radios and lower latency.

A toy OFDM link in NumPy

This simulates one 802.11a sized symbol (64 subcarriers, 16 sample cyclic prefix, which is one quarter of 64) through a five tap echo channel with a little noise:

import numpy as np
rng = np.random.default_rng(1)
N, CP = 64, 16
bits = rng.integers(0, 2, size=(N, 2))
qpsk = ((1 - 2 * bits[:, 0]) + 1j * (1 - 2 * bits[:, 1])) / np.sqrt(2)
tx = np.fft.ifft(qpsk)                     # one OFDM symbol in time
tx_cp = np.concatenate([tx[-CP:], tx])     # cyclic prefix
h = np.array([1.0, 0.0, 0.45 - 0.2j, 0.0, 0.1j])  # 5-tap multipath channel
rx = np.convolve(tx_cp, h)[: N + CP]
rx += 0.01 * (rng.standard_normal(rx.size) + 1j * rng.standard_normal(rx.size))
Y = np.fft.fft(rx[CP:])                    # drop CP, back to subcarriers
H = np.fft.fft(h, N)                       # channel per subcarrier
eq = Y / H                                 # one complex divide per subcarrier
bits_hat = np.stack([(eq.real < 0), (eq.imag < 0)], axis=1).astype(int)
print("bit errors:", int(np.sum(bits_hat != bits)), "of", bits.size)
print("|H| min/max:", round(float(np.abs(H).min()), 3), round(float(np.abs(H).max()), 3))

Output:

bit errors: 0 of 128
|H| min/max: 0.425 1.453

The channel gain varies more than threefold across the subcarriers, yet a single division per subcarrier recovers every bit. Delete the line that adds the cyclic prefix (and the [CP:] slice) and errors appear, because the echo now smears across the symbol edge. For simplicity this toy puts data on all 64 subcarriers and assumes the receiver knows the channel perfectly; a real receiver estimates H from the preamble and pilots.

How do JPEG and MP3 use frequency transforms?

Natural images and sounds concentrate their energy in a few frequency components, and eyes and ears ignore some frequencies. Both formats transform, keep what matters, coarsen the rest and pack the result.

JPEG: the DCT on 8 by 8 blocks

JPEG is ITU-T Recommendation T.81, published jointly as ISO/IEC 10918-1. The T.81 text says the CCITT recommendation was approved on 18 September 1992. The JPEG committee's own overview says the standard was created in 1992 (latest version 1994) after a process that began in 1986, and that Part 1 specifies the core Huffman coded, DCT based lossy format.

Here is the baseline JPEG path from camera pixels to a file, as a step table:

StepWhat happensWhat T.81 or JFIF specifies
1. Colour conversionRGB converted to luminance (Y) and two colour difference channels (Cb, Cr)JFIF (JPEG Part 5)
2. Chroma subsampling (optional)Colour channels often stored at half resolution, since the eye is less sensitive to colour detailSampling factors in the frame header
3. Block splitEach channel divided into 8 by 8 blocks of samplesT.81
4. Level shiftSubtract 128 from 8 bit samples so they centre on zeroT.81 A.3.1: subtract 2^(P-1), so 128 when P = 8
5. Forward DCTEach block becomes 64 coefficients: 1 DC and 63 ACT.81 A.3.3 (informative ideal definition)
6. QuantisationDivide each coefficient by its table entry and roundT.81 A.3.4; example tables in Annex K
7. Zig-zag orderingReorder coefficients from lowest to highest spatial frequencyT.81 zig-zag sequence
8. Entropy codingHuffman (or arithmetic) code the runs of zeros and the valuesT.81

The ideal two dimensional forward DCT in T.81 section A.3.3 is:

S(v,u) = (1/4) C(u) C(v) * sum over x = 0..7, y = 0..7 of
         s(y,x) * cos((2x + 1) u pi / 16) * cos((2y + 1) v pi / 16)

C(u), C(v) = 1 / sqrt(2) when u, v = 0, and 1 otherwise

T.81 notes that real implementations cannot represent these terms with perfect accuracy, and leaves the accuracy requirements to Part 2, the compliance testing part.

Worked example 5: one row of pixels through the DCT

A full 8 by 8 block is 64 numbers, so here is one row of eight luminance samples, transformed with the one dimensional DCT (the 2D transform in T.81 is this applied along rows, then columns):

import numpy as np
N = 8
k = np.arange(N)
C = np.sqrt(2 / N) * np.cos(np.pi * (2 * k[None, :] + 1) * k[:, None] / (2 * N))
C[0] /= np.sqrt(2)                       # orthonormal DCT-II matrix
row = np.array([52, 55, 61, 66, 70, 61, 64, 73]) - 128   # level shift
coef = C @ row
print(np.round(coef, 1))
q = np.array([16, 11, 10, 16, 24, 40, 51, 61])
print(np.round(coef / q).astype(int))

Output:

[-184.6  -14.4   -5.7   -6.7    7.1   -3.1   -0.7    2.4]
[-12  -1  -1   0   0   0   0   0]

The first coefficient, -184.6, carries the row's average brightness. The rest are small because neighbouring pixels are similar. Dividing by the first row of the example luminance quantisation table in T.81 Annex K (16, 11, 10, 16, 24, 40, 51, 61) and rounding leaves three non zero values out of eight. In a real block, the entire bottom right of the 8 by 8 grid usually rounds to zero, and the zig-zag order groups those zeros into one long run that the entropy coder stores in a few bits. Using the first row of a 2D table on a 1D row is a simplification for illustration; a real encoder quantises 2D coefficients with the full table.

Larger table entries at high frequencies mean coarser steps where the eye notices least. The loss is all in the rounding at step 6, and when it removes high frequency terms near an edge, the Gibbs ringing described earlier appears.

MP3: a polyphase filter bank plus a modified DCT

MP3 is MPEG-1 (and MPEG-2) Audio Layer III, specified in ISO/IEC 11172-3 for MPEG-1. Its frequency analysis has two stages.

According to Julius O. Smith's Spectral Audio Signal Processing (Stanford CCRMA), MPEG Layers I and II use a 32 band polyphase filter bank, each band about 650 Hz wide, with a hop of 32 samples. Layer III, based on the ASPEC coder, appends a second stage to the output of each of the 32 subbands that splits it further into 6 or 18 bands. That gives:

ModeSubbandsWhen the encoder uses it
Long blocks32 times 18 = 576Steady tones: fine frequency resolution
Short blocks32 times 6 = 192Transients such as drum hits: fine time resolution

The encoder computes a perceptual entropy measure to decide when to switch. The second stage is a modified DCT (MDCT), which uses 50 percent overlapping windows so that block edges cancel out when the decoder adds them back together. A CSIRO technical report on MPEG-1 compressed domain features describes the encoder transforming a sequence of 1,152 PCM samples per frame, and notes that the psychoacoustic filtering is encoder specific and not standardised. At a 44,100 Hz sample rate, 1,152 samples is 1,152 / 44,100 = about 26.1 milliseconds of audio per frame.

The psychoacoustic model estimates, per band, how much quantisation noise a listener would not notice, since a loud tone masks quieter sounds near it in frequency. Because it is left to the encoder, two encoders can differ in quality at the same bitrate.

The CCRMA text notes that AAC replaced the hybrid with a single MDCT switching between 128 and 1,024 bands, a large part of its coding gain over MP3. Fraunhofer IIS, one of the institutions behind mp3, states that the licensing programme for certain mp3 related patents and software of Technicolor and Fraunhofer IIS was terminated on 23 April 2017.

How does side-channel research use frequency analysis?

This section is conceptual and defensive. Power draw, electromagnetic emissions and even sound vary with what a processor is doing, so code whose path depends on a secret key can leak it physically. Researchers compute short FFTs over sliding windows to build a spectrogram, signal strength by frequency over time, and look for patterns that line up with the computation.

The clearest published example is acoustic cryptanalysis by Daniel Genkin, Adi Shamir and Eran Tromer, first published on 18 December 2013. Their project page shows spectrograms spanning 40 kHz in which individual RSA decryptions stand out. Microphones capture under 20 kHz (a few hundred kHz with ultrasound microphones), far below gigahertz clock rates, but long operations such as modular exponentiation leave a characteristic spectral signature over many clock cycles.

The defensive outcome is the important part:

  • The researchers disclosed the issue to GnuPG developers as CVE-2013-4576 and worked with them on countermeasures. New versions of GnuPG 1.x and of libgcrypt, which underlies GnuPG 2.x, were released at the same time as the public results.
  • The project page notes that implementations using ciphertext blinding, a common countermeasure that randomises the values the key is applied to, appear less vulnerable.
  • It argues that algorithm specific mitigation in the software, which stops the leak from carrying useful information, is often cheap and effective.
  • A follow-up paper in August 2014 extended the work to other physical channels.

The lesson for a software team: these leaks are closed in the cryptographic library, so the library version you ship matters, and an upstream fix protects you only once it is in your build.

Common mistakes

  • Reading bin numbers as hertz. Bin k is k times fs / N hertz. Forgetting the sample rate is the most common FFT bug in practice.
  • Ignoring the normalisation convention. NumPy's default leaves the forward transform unscaled. Comparing magnitudes across tools that scale by 1/N, 1/sqrt(N) or nothing gives answers off by large factors.
  • Doubling the DC and Nyquist bins. The factor of 2 that converts a single sided spectrum to sine amplitude applies only to bins that have a negative frequency twin.
  • Expecting more samples per second to sharpen resolution. Resolution is fs / N, which is 1 divided by the recording length. Only a longer recording gives finer frequency detail.
  • Skipping the window. A tone that does not fit a whole number of cycles in the window leaks into neighbours. A Hann or similar window trades a slightly wider peak for much lower leakage.
  • Sampling without an anti-aliasing filter. Software cannot remove aliasing after the fact; the folded energy is indistinguishable from real signal.
  • Zero padding to "increase resolution". Padding interpolates the spectrum on a finer grid and makes plots smoother, but it does not separate two tones closer than 1 over the recording length.
  • Assuming the cyclic prefix is wasted. In 802.11a it costs 20 percent of air time (0.8 of 4 microseconds), and buys the one tap equalisation that makes OFDM practical.

FAQ

What is the Fourier transform in simple terms?

It describes a signal by the frequencies it contains, with their strengths and phases, instead of by its value at each moment. Think of a prism splitting light, except the transform is fully reversible.

What is the difference between the Fourier series and the Fourier transform?

A series handles repeating signals and gives a list of harmonics. The transform handles any signal and gives a continuous spectrum; it is the series in the limit of an infinite period.

What is the difference between the DFT and the FFT?

The DFT is the definition: N samples in, N frequency values out. The FFT is a family of algorithms that computes the identical result in about N log N steps instead of N squared.

Why is the Fourier transform used in Wi-Fi?

Wi-Fi since 802.11a uses OFDM. An inverse FFT builds each symbol from many subcarriers and the receiver's FFT separates them; with a cyclic prefix, room echoes reduce to one complex multiplication per subcarrier.

Does JPEG use the Fourier transform?

It uses the discrete cosine transform, a cosine only relative of the DFT, on 8 by 8 blocks, then quantises and entropy codes the coefficients, as ITU-T T.81 specifies.

How does MP3 compression work?

A polyphase filter bank makes 32 subbands, a modified DCT splits them into 576 lines (192 for transients), and an encoder specific psychoacoustic model gives fewer bits to what the ear will not notice.

What is the Nyquist frequency?

Half the sample rate: the highest frequency a sampled signal can represent. At 1,000 Hz sampling it is 500 Hz.

What is aliasing and how do you prevent it?

Frequencies above the Nyquist frequency fold down into false lower ones. Prevent it with an analog low pass filter before the converter and some sample rate headroom; software cannot fix it afterwards.

Who invented the fast Fourier transform?

James Cooley and John Tukey published it in Mathematics of Computation in 1965. NumPy's and SciPy's documentation note that Gauss knew the method in 1805.

Can the Fourier transform be used to break encryption?

Not by itself. Published side-channel research, such as the 2013 acoustic work on RSA, used frequency analysis to observe physical leaks; the fixes shipped as library updates, so current crypto libraries are the practical defence.

Where the Fourier transform meets software security

Safeguard's models are single purpose security models, and Safeguard's model benchmarks page says a frontier general purpose LLM will out-write any Safeguard model on math word problems. Safeguard does not compute transforms or solve maths problems. The overlap is in the software that does.

Transform libraries are dependencies. NumPy, SciPy, FFTW and DSP code in radio firmware are open source components installed from registries or baked into images, and they get fixes like any other code.

Codecs parse untrusted input. JPEG and MP3 decoders read files from anywhere, and the parsing around the transform (headers, Huffman tables, block counts) must validate every field. Image and audio libraries receive security fixes for exactly this reason.

Crypto libraries carry side-channel fixes. A statically linked old crypto library keeps its leak until you rebuild.

This is where Safeguard applies, and only here. It scans repositories, container images and SBOM files, matches the components it finds against vulnerability databases, shows which projects carry a given component and version, and uses reachability analysis to rank the vulnerable code paths your application actually calls. For the reachability idea in plain words, see what reachability analysis is. It does not analyse radio signals, test emissions, scan firmware binaries or inspect hardware.

The equations here have no vulnerabilities, but the FFT libraries, image and audio codecs and cryptographic libraries that implement them are open source components that ship inside applications, containers and device firmware, and they receive fixes like any other code. When one of them needs a fix, the guidance below applies to the dependencies and images you build, not to the mathematics or the radio hardware itself.

Fix it without waiting for upstream

Whatever the vulnerability, most of the risk sits in two gaps. The first is the time between a fix being published and that fix actually running in your production builds. The second, and worse, is the time before any fix exists at all, while a maintainer works out a patch and a release schedule. Waiting through both gaps is a habit, not a requirement. Safeguard gives teams three ways to close them.

1. Autonomous remediation: pull requests, not tickets

When a fixed version exists, Safeguard's Griffin models open a tested, policy gated pull request that moves the affected dependency to the safe version. You can run it on one repository or in bulk across every project that carries the vulnerable version. Remediation covers eight categories, including dependencies, licenses, security posture and findings. Reachability analysis ranks the vulnerable code paths your application actually calls first, so the first pull requests you merge are the ones that matter most. Autonomous remediation ships switched off, with every category set to Manual, so your team decides how much runs on its own and what still needs a human approval.

2. Gold Open Source: the safe version in seconds, free

gold.safeguard.sh is free and needs no login. Search any CVE or package to see affected and fixed versions, CVSS, EPSS, CISA KEV status and a recommended safe version, or use the "Am I affected?" checker. The same data is available through a free read API, README status badges and RSS threat feeds. The safeguard-sh/gold-scan-action GitHub Action can fail a build on a known exploited vulnerability, on any known vulnerability, or never, so a version you have just removed cannot quietly come back in next week's merge.

3. Gold Registry: start from components that are already fixed

registry.safeguard.sh provides hardened, drop in replacements for upstream packages and container images. Every Gold artifact is held continuously to zero exploitable critical or high CVEs, zero malware and a completed license check, with SLSA L3 or higher provenance, a Sigstore signature and a public evidence page you can hand to an auditor. An artifact loses its Gold badge if it regresses. Teams can point their own package directory at the registry so hardened equivalents resolve without changing how developers install anything.

When upstream has no fix yet, or will not ship one, Safeguard first works to get the fix accepted upstream. If the issue is severe or upstream will not move, Safeguard builds a fork, fixes the vulnerability there and publishes a hardened Gold build, so you are not stuck on a maintainer's release schedule. You can also request a hardened, compatibility tested build of a specific package. This fork and patch flow is in early access today, so ask which packages it covers before you plan around it.

Your situationWhat to use
A fixed version existsAutonomous remediation opens the upgrade pull request; Gold Open Source confirms the safe version
You need to know today whether you are affectedGold Open Source lookup and "Am I affected?" checker, free and without login
You want vulnerable versions to stop shippingThe Gold scan GitHub Action in CI, plus Safeguard policies and gates
Upstream has no fix yet, or will not ship oneA custom Gold request; fork and patch (early access)
You want to start clean on new projectsPull hardened components and images from Gold Registry

One honest limit: when the vulnerable code lives in a vendor appliance, a hosted service or an operating system runtime you do not build yourself, the fix still has to come from that vendor or distribution. The steps above apply to the open source dependencies and container images you build and ship, which is where most teams carry the largest and least tracked share of their exposure.

See how Safeguard would help

None of these demos compute a Fourier transform. They show how Safeguard handles the libraries that signal processing, image and audio code imports.

The first demo is a real Safeguard CLI session: asked to fix a lodash CVE, it reads the finding and advisory, asks before each command, edits package.json to lodash 4.17.21, runs npm install and npm test with approval, and pushes nothing. The same approve each step flow applies when an image decoding or numerical library pinned in your project needs an upgrade and you want the tests to run before anything changes.

Click through it at your own pace, or open it full screen.

The second demo sets up Safeguard's local runner on your own machine, approves it with its device code, and runs a scan from the terminal. On a workstation used for signal processing or media work, that scan lists the packages that went into the environment, so you know which versions of NumPy, SciPy or codec bindings your scripts actually depend on.

Click through it at your own pace, or open it full screen.

The third demo connects Claude to the Safeguard MCP server with OAuth and lets you choose which tools Claude can use. You can then ask which of your projects carry a particular version of an image or crypto library, while keeping control over what the assistant can touch.

Click through it at your own pace, or open it full screen.

To try it on your own projects, download Safeguard or read the CLI documentation.

Quick check

You sample at 8,000 Hz and take a 2,000 point FFT. What is the frequency resolution, and where does a 5,000 Hz tone appear?

Resolution is 8,000 / 2,000 = 4 Hz per bin. The Nyquist frequency is 4,000 Hz, so a 5,000 Hz tone aliases to 8,000 minus 5,000 = 3,000 Hz.

In 802.11a, why is the subcarrier spacing 312.5 kHz and the useful symbol 3.2 microseconds?

The 20 MHz channel is divided by a 64 point FFT, so 20 MHz / 64 = 312.5 kHz. Orthogonality requires the useful symbol to last 1 / 312.5 kHz = 3.2 microseconds; the 0.8 microsecond cyclic prefix brings the full symbol to 4 microseconds.

Roughly how much less work is a 1,024 point FFT than a direct 1,024 point DFT?

About 100 times less. The direct method needs about 1,024 squared, or 1,048,576 operations; the FFT needs about 1,024 times log2(1,024) = 10,240.

Sources

Last reviewed: 23 October 2026.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.

Self-healing security runs on Safeguard.

Your first fix PR is minutes away.

No sales call required, even your agent can complete the purchase over MCP.