Why fast tones look slow.
On this page
Sampling keeps one value at fixed intervals and throws away everything in between. A tone faster than half the sampling rate then leaves exactly the same samples as a slower tone, so it shows up at the wrong frequency, and nothing after the analog-to-digital converter (ADC) can undo that. The cure is to remove those fast tones before they are sampled, with an analog low-pass filter, or to sample fast and filter digitally before slowing the rate down. The analog filter acts continuously on the input; the digital one acts once per kept sample.
Film takes 24 pictures per second and shows you nothing in between, so your eye joins each spoke to the nearest spoke in the next picture. The spokes in Figure 1 are 45° apart, and the wheel turns 40° per picture, a little less than one gap. The nearest spoke then sits 5° behind, so the wheel seems to creep backward.
A sampled sine has the same problem. Between two samples a fast sine can turn almost a full cycle, and the dots then trace a slow wave instead. That false, slower tone is called an alias. The line marked Nyquist in Figure 2 sits at half the sampling rate, the limit Section 2 explains.
The wheel looked right as long as it turned less than half a spoke gap per picture. For a sine the limit is half a cycle per sample, which puts the highest safe frequency at half the sampling rate fs.
Three common sampling rates and the highest frequency each can keep.
| Recording | Sampling rate fs | Highest frequency, fs/2 |
|---|---|---|
| Phone call | 8 kHz | 4 kHz |
| Speech recognizer input | 16 kHz | 8 kHz |
| Music CD | 44.1 kHz | 22.05 kHz |
Shannon proved this sampling theorem in 1949, building on Nyquist (1928): a signal with nothing above fs/2 is fully pinned down by its samples.
Why does a fast tone land at a specific slower one? Sampling copies the whole spectrum to every multiple of fs. In Figure 4 each triangle stands for the band of frequencies a signal holds.
The copy that lands between 0 and fs/2 is the one you see. That gives the fold rule, and at 16 kHz it moves two tones from above Nyquist to well below it.
| Tone | Frequency f | Lands at fs − f |
|---|---|---|
| A high test tone | 10 kHz | 6 kHz |
| An old TV’s whine | 15.73 kHz | 0.27 kHz |
Illustrative tones at fs = 16 kHz, Nyquist 8 kHz. The whine sits just below fs, so it folds to near 0.
Twenty samples of a 600 Hz tone and its 400 Hz alias at fs = 1 kHz; the last line prints True because the samples match.
import numpy as np
FS, F_TONE = 1000, 600
F_ALIAS = abs(F_TONE - FS) # 400 Hz
n = np.arange(20) # sample index n
tone = np.cos(2 * np.pi * F_TONE * n / FS)
alias = np.cos(2 * np.pi * F_ALIAS * n / FS)
print(np.allclose(tone, alias))
Playback rebuilds the one smooth wave below Nyquist through the dots, so a tone past Nyquist plays back as its alias even though every sample was right.
Figure 5. 1The alias hits the same sample dots as the gray true tone.
Take an illustrative recording at fs = 1 kHz with a wanted signal at 150 Hz and a 600 Hz interferer. Once the interferer has folded to 400 Hz, no digital filter can tell it from a real 400 Hz signal. So an analog low-pass anti-aliasing filter in front of the ADC removes what lies above Nyquist before sampling.
|H(f)| is how much of frequency f a filter lets through; How FIR and IIR Filters Shape Magnitude and Phase builds it from scratch. The Butterworth low-pass stays as flat as possible below its cutoff fc. Its order p counts its poles, the factors in the bottom of its response, and each pole adds 20 dB per decade (ten times the frequency) of fall past the cutoff.
At fs = 1 kHz, a 500 Hz cutoff is only 3 dB down at Nyquist, so a 600 Hz interferer still gets through and becomes a 400 Hz alias.
The Butterworth card in decibels for four cutoff and order pairs, plus the wanted band; a lower cutoff and a higher order both push the 600 Hz interferer down.
import numpy as np
def butter_db(f, f_c, p):
return -10 * np.log10(1 + (f / f_c) ** (2 * p))
wanted = butter_db(150, 400, 4) # the wanted band
CASES = [(500, 4), (400, 4), (400, 8), (400, 12)] # (cutoff, order)
interferer = [butter_db(600, f_c, p) for f_c, p in CASES]
Higher order has a price. The anti-aliasing Butterworth is an analog circuit, and its group delay, how long each frequency is held back, changes with frequency, more so at higher order. Its digital copy is an IIR filter, one that feeds its own output back, and it also delays frequencies unevenly. How FIR and IIR Filters Shape Magnitude and Phase shows why that bends a waveform.
The dB axis grows in 60 dB steps.
A lower cutoff or higher order shrinks the 400 Hz alias.
Figure 8. Bars show the amplitude left. 1The wanted 150 Hz. 2600 Hz interferer.
Section 4 filtered the signal before the converter. Once audio is digital, its rate often still has to change, to store fewer samples or to feed a model built for one rate. Two recordings must also share a rate before they can be compared or matched (Anushiravani 2016).
A steep analog filter is expensive, and its group delay grows more uneven as it gets steeper. Sampling much faster than needed lets a gentle analog filter do the job; a digital low-pass then removes the rest, and the downsampling factor Q says how many samples go in for each one kept. That low-pass followed by keeping one sample in Q is called decimation.
For example, keeping one sample in Q = 4 takes 8 kHz audio to 2 kHz, the case Figure 11 runs on a rising tone.
A spectrogram stacks short spectra side by side over time, with frequency going up and brightness for strength.
Figure 11. 8 kHz down to 2 kHz. 1The true tone, dashed. 2The folded tone.
Whisper, OpenAI’s speech recognizer, resamples all audio to 16,000 Hz before it computes its spectrogram features (Radford et al. 2022, section 2.2). An 8 kHz phone recording therefore has to be upsampled by a factor L = 2. Upsampling puts L − 1 zeros between neighboring samples, so [1, 2, 3] becomes [1, 0, 2, 0, 3, 0]. The zeros create an image, a mirror copy of each tone, because the spectrum copy centered on the old rate (Section 3) now lies inside the new band.
Nothing folds, so upsampling causes no aliasing; only the image has to be removed.
SciPy’s resample_poly does both steps; with its default filter the 5 kHz image comes out about 57 dB below the tone.
Music at 44.1 kHz going into that 16 kHz recognizer is not a whole-number step either way. The way through is to upsample by one whole number, low-pass once, and keep one sample in another.
Anti-aliasing sits in two places, an analog low-pass right before the ADC and a digital low-pass before every downsampling step in software.
| Step | How often, on what data | State it keeps | Cost |
|---|---|---|---|
| Anti-aliasing filter | continuously, before the sampler; designed once, on the analog input | the charge on its capacitors | more parts for a steeper slope |
| Decimation | once per kept output sample, on the recent input samples | the filter’s past inputs | one low-pass per kept sample |
| Resampling by a ratio | once per output sample, on the recent input samples | the filter’s past inputs | one low-pass per output sample; the inserted zeros are skipped |
Rates, state and cost come from the sources in References.
A rising tone downsampled by 4 with SciPy, once by slicing (bad) and twice with a low-pass first (good, good2), then a sweep taken from 44.1 kHz to 16 kHz.
import numpy as np
from scipy import signal
FS, FACTOR, DUR_S = 8000, 4, 2
t = np.arange(DUR_S * FS) / FS
sweep = signal.chirp(t, f0=0, t1=DUR_S, f1=3800)
bad = sweep[::FACTOR] # no filter, folds
good = signal.decimate(sweep, FACTOR)
good2 = signal.resample_poly(sweep, up=1, down=FACTOR)
x44 = signal.chirp(np.arange(DUR_S * 44100) / 44100, f0=0, t1=DUR_S, f1=20000)
x16 = signal.resample_poly(x44, 160, 441) # one low-pass at 8 kHz
| Knob, where it enters | Raise it | Lower it | Default |
|---|---|---|---|
| Sample rate fs, which sets the Nyquist frequency fs/2 | The Nyquist frequency moves up, so more of the band is safe | More tones fold; a tone at exactly fs/2 can read as all zeros | 16 kHz for speech (torchaudio kaldi.mfcc) |
| Cutoff fc in the Butterworth card | Keeps more of the top band, lets more fold through the slope | Safer against folding, dulls the top of the band; a cutoff far below the band removes the signal too | No default; Wn is required and sits at the −3 dB point (scipy.signal.butter) |
| Order p, the exponent 2p in the Butterworth card | Steeper slope, 20 dB per decade per pole, more cost and ringing | Gentler slope; p = 1 lets much of the fold through | No default; N is required (scipy.signal.butter) |
| Downsampling factor Q in the decimation card | Fewer samples to store and process; the new Nyquist limit drops Q times, so the digital low-pass must cut lower | More samples kept; Q = 1 keeps every sample and changes nothing | No default; q is required. decimate low-passes with an order 8 IIR filter run forward and back (zero_phase=True), and SciPy advises several calls for factors above 13 (scipy.signal.decimate) |
SciPy documents scipy.signal.decimate; resample_poly does the same with a polyphase filter, one that computes only the samples it keeps.
zero_phase=False.| What you see | Likely cause | What to do |
|---|---|---|
| Tones at frequencies you never had | Aliasing | Put a low-pass in front of the ADC, below Nyquist |
| A tone that falls while the real one rises | It crossed Nyquist and folded | Same fix, or sample faster |
| Downsampled data full of strange tones | Slicing with x[::Q] and no filter | Use SciPy’s decimate or its polyphase resampler |
| A tone mirrored above the old Nyquist after upsampling | Zeros were inserted and the interpolation low-pass was skipped | Low-pass at the old Nyquist with gain L, or use resample_poly |
What is a spectrum, exactly? That is Fourier Transform and the FFT.