Time, frequency, and the basis.
On this page
The Fourier transform turns a list of samples into a list of frequencies: for each one, how much of it the signal holds and where its wave starts. The fast Fourier transform (FFT) computes it once per clip, or once per block of samples for a long signal, and the inverse turns the frequencies back into the same samples.
A recording is a list of samples x[n], where n counts the samples and the sample rate fs is how many arrive each second. The FFT of N samples gives one value per frequency, called a bin; X[k] is the value of bin k. Each bin is a complex number, a size and an angle: how much of that frequency the signal holds, and where its wave starts (Section 4). The size is written |X[k]|. Figure 1 plots the sizes scaled as 2|X[k]|/N, so each tone reads its own amplitude.
The FFT also lists negative frequencies, waves that turn the other way round the circle. For a real signal they mirror the positive ones, so a tone at frequency f puts half its size at +f and half at −f. np.fft.rfft keeps only 0 to fs/2, N/2 + 1 = 501 bins, the one-sided spectrum, and the plot adds the two halves back by doubling each size, 2|X[k]|/N. Here fs = 1,000 Hz and N = 1,000, so bin k is k Hz; Section 5 shows why.
rfft keeps 0 to fs/2, halves added.The NumPy code that draws Figure 1.
import numpy as np
import matplotlib.pyplot as plt
fs, N = 1000, 1000 # 1 s at 1,000 samples per second
t = np.arange(N) / fs
tones = 0.7 * np.sin(2 * np.pi * 50 * t) + np.sin(2 * np.pi * 120 * t)
x = tones + 0.2 * np.random.default_rng(0).standard_normal(N)
X = np.fft.rfft(x) # bins 0 to N/2
f_k = np.fft.rfftfreq(N, d=1 / fs) # k * fs / N, in Hz
fig, (top, bottom) = plt.subplots(2, 1)
top.plot(t[:100], x[:100])
bottom.plot(f_k, 2 * np.abs(X) / N)
bottom.set_xlabel("frequency (Hz)")
plt.show()
How do you find which tones are inside? Compare the signal with a set of basis waves, one at a time, and measure how much of each one it contains. For N samples there are N basis waves.
Each basis wave has a cosine part and a sine part, because a tone can start at any point of its cycle. One complex number holds both: a real part plus a part times j, the square root of −1. The complex number written ejθ is the point at angle θ on a circle of radius 1.
This is a change of basis, the same data described along new axes. PCA learns its axes from the data; the Fourier basis is the same fixed set of waves for every signal.
The discrete Fourier transform (DFT) measures this. Four samples of a 4 kHz tone taken at 16 kHz, x = [1, 0, −1, 0], give X = [0, 2, 0, 2]: all of it sits in bin 1 and its mirror, bin 3.
The DFT as one matrix product. Row k of F is basis wave k, and the last line checks the product against np.fft.fft, which does not scale (np.fft.ifft divides by N).
import numpy as np
N = 8
n = np.arange(N)
k = n[:, None] # bins as a column
F = np.exp(-2j * np.pi * k * n / N) # shape (N, N)
x = np.random.default_rng(0).standard_normal(N)
print(np.allclose(F @ x, np.fft.fft(x)))
F is basis wave k, so F @ x is the DFT (eq card in Section 3, line 1).np.fft.fft on random input.Figure 5. Tones in bins 5 and 12. 1Signal times cosine. 2Bin k, large on a match.
Each X[k] is a complex number, so it has a size and an angle.
Because phase sets timing, How FIR and IIR Filters Shape Magnitude and Phase tracks what a filter does to it, and Spectral Subtraction for Denoising edits the magnitude and keeps the noisy phase.
A 32 ms speech frame at fs = 16 kHz holds N = 512 samples. Its bins sit 31.25 Hz apart, so 1 kHz lands in bin 32 and the Nyquist limit, 8 kHz, in bin 256.
For a real signal, bins above N/2 mirror the ones below, the same fold as in Aliasing and Anti-Aliasing Filters.
Two tones 4 Hz apart that start in phase, at 100 and 104 Hz, show as one peak in a 0.15 s record and as two in a 0.20 s one.
Figure 7. 1Two tones (dashed). 2Spectrum of T seconds; dots are bins.
In the signal of Figure 1, bin 50 alone brings back the 50 Hz sine and bin 120 the 120 Hz wave (Figure 8); all 501 bins together give every sample back.
rfft bins.rfft drops, so 2N undoes both. Bins 0 and N2 have no mirror and use |X[k]|N.Keep every magnitude and scramble the phases, and the same cosines peak at different moments and add to a different signal, the noise of Figure 6 instead of its click. In Figure 9 each target repeats every 8 ms, a base frequency of 125 Hz, so wave k sits at 125k Hz. A square needs only odd harmonics, waves at odd multiples of its base; its even waves have size zero, so they count in K but add nothing.
Figure 9. 1The sum of K waves. 2Each wave’s size, filled if used.
The round trip in NumPy, with the signal of Figure 1.
import numpy as np
fs, N = 1000, 1000
t = np.arange(N) / fs
rng = np.random.default_rng(0)
x = 0.7 * np.sin(2 * np.pi * 50 * t) + np.sin(2 * np.pi * 120 * t) + 0.2 * rng.standard_normal(N)
X = np.fft.rfft(x) # 501 bins
x_back = np.fft.irfft(X, n=N) # n=N keeps the original length
print(np.allclose(x, x_back), np.max(np.abs(x - x_back))) # True 8.9e-16
n=N keeps the length when N is odd.Done directly, the DFT costs N multiply-adds for each of N bins. The fast Fourier transform (FFT) gets the same numbers with far less work. It is not an approximation of the DFT but exactly the DFT, regrouped (Anushiravani 2016). It splits the samples into even and odd, does two half-size DFTs and combines them. Each half splits again, until only pairs are left.
Figure 10. 1Even and odd splits down to pairs. 2Butterflies join them into 8 bins.
Each split halves the size, so there are log2 N stages (log2 N is how many times you can halve N before reaching 1), and each stage costs about N operations. At N = 1,024 that is 10 stages of about 1,024 operations each.
James Cooley and John Tukey published this algorithm in 1965; their version needs fewer than 2N log2 N operations. Every np.fft call uses an FFT.
A filter’s sliding weighted sum, the convolution x ∗ h, is also what a convolution layer computes (CNN Basics: Convolution, Stride and Pooling). In frequency the same operation is much simpler, a bin-by-bin product of the two spectra.
The DFT’s waves repeat every N samples, so the product gives a circular convolution, where the end of the output wraps onto the start. Zero-pad both signals to the full output length first. A convolution reverb, which adds a room’s echo to a dry recording, does exactly this; Figure 13 runs a tiny case.
The FFT runs inside every spectrum, STFT, PSD and fast convolution later in the series, once per block of samples.
| Step | How often, on what data | State it keeps | Cost |
|---|---|---|---|
| DFT, direct | once per block of samples, on one block | none | block length squared multiplies |
| FFT | once per block of samples, on one block | none | block length times its log2 |
| Setting | Default | Source |
|---|---|---|
| norm | "backward", forward transform unscaled, inverse divided by N | numpy.fft.rfft |
rfft output | N//2 + 1 bins for N real samples | numpy.fft.rfft |
| Knob, where it enters | Raise it | Lower it | Default |
|---|---|---|---|
| FFT size N, the bin spacing fs/N | Finer bins and more cost (about N log2 N); zero padding only interpolates | Coarser bins; N = 1 leaves only the mean | Every sample, N = len(x) (numpy.fft.rfft) |
Zero-padding, n in np.fft.rfft(x, n) | More points drawn on the same curve, more cost; close tones still merge | n = N plots only the bins, Δf apart | No padding, n = len(x) (numpy.fft.rfft) |
NumPy documents numpy.fft.rfft and its scaling.
rfft drops the mirror half, so power sums double bins 1 to N/2 − 1.| What you see | Likely cause | What to do |
|---|---|---|
| Peaks at frequencies that make no sense | Reading bin k as k Hz | Bin k is k fs/N; use rfftfreq |
| The rebuilt signal is N times too big | The 1/N went missing | numpy puts it on the inverse; keep fft and ifft as a pair |
| A mirror image in the top half | fft on a real signal | Use rfft and keep bins 0 to N/2 |
| One tone spreads over many bins | The tone falls between bins (leakage) | Use a window; see The STFT and Spectrograms |
| Filtered output wraps around at the edges | Circular convolution | Zero-pad to the full output length |
How do you keep some frequencies and drop others? That is How FIR and IIR Filters Shape Magnitude and Phase.