Original source
---
id: signals-fourier
title: Complex Numbers, Fourier and Signals
code: CN
subtitle: Complex rotations, DFT and FFT, windows, sampling, spectrograms, mel filterbanks and MFCCs. Python from the lessons.
hue: 290
sources: phases/01-math-foundations/19-complex-numbers, phases/01-math-foundations/20-fourier-transform, phases/06-speech-and-audio/01-audio-fundamentals, phases/06-speech-and-audio/02-spectrograms-mel-features
source_hash: 0f1df4b429171683
---
## Cooley-Tukey FFT, 8 points
type: architecture
from: phases/01-math-foundations/20-fourier-transform/docs/en.md
```arch
node | x | 1,1 | x[0..7] | 8 samples
node | even | 2,1 | Even half | x[0,2,4,6]
node | odd | 2,2 | Odd half | x[1,3,5,7]
node | ek | 3,1 | E[0..3] | 4-pt FFT
node | ok | 3,2 | O[0..3] | 4-pt FFT
node | out | 4,1 | X[0..7] | E[k] +/- twiddle * O[k]
edge | x | even | split
edge | x | odd | split
edge | even | ek |
edge | odd | ok |
edge | ek | out | combine
edge | ok | out | twiddle
```
## Waveform to log-mel and MFCC
type: flow
from: phases/06-speech-and-audio/02-spectrograms-mel-features/docs/en.md, phases/06-speech-and-audio/02-spectrograms-mel-features/code/main.py
1. **Frame**: cut 25 ms windows with a 10 ms hop. At 16 kHz that is 400 and 160 samples.
2. **Window**: multiply each frame by `hann(N)`. It tapers the edges to zero.
3. **STFT**: take the FFT magnitude of each frame. Shape is `(n_frames, n_freq_bins)`.
4. **Mel**: multiply by the triangular filterbank. 80 mels over 0 to 8 kHz is the ASR standard.
5. **Log**: `log(mel_spec + 1e-10)` compresses the dynamic range.
6. **MFCC**: apply `dct_ii` to each log-mel frame. Keep the first 13 coefficients.
## Tasks in code
type: task-table
from: phases/01-math-foundations/19-complex-numbers/docs/en.md, phases/01-math-foundations/20-fourier-transform/docs/en.md, phases/01-math-foundations/20-fourier-transform/code/fourier.py, phases/06-speech-and-audio/02-spectrograms-mel-features/docs/en.md
| Task | Code |
|---|---|
| Rotate a point by 45 degrees | `point * euler(math.pi / 4)` |
| N points on the unit circle | `roots_of_unity(N)` |
| Transform and invert, O(N^2) | `dft(x)`, then `idft(X)` |
| Fast transform, power of 2 | `fft(x)` |
| Energy per frequency | `power_spectrum(X)` |
| Linear convolution by FFT | `convolve_fft(x, h)` |
| Split a signal into frames | `frame(signal, frame_len, hop)` |
| Mel filterbank matrix | `mel_filterbank(n_mels, n_fft, sr)` |
## Spectra in NumPy and SciPy
type: annotated-code
lang: python
from: phases/01-math-foundations/19-complex-numbers/docs/en.md, phases/01-math-foundations/20-fourier-transform/docs/en.md, phases/06-speech-and-audio/01-audio-fundamentals/docs/en.md
```python
waveform, sr = sf.read("clip.wav", dtype="float32") # float32 in [-1, 1]
spectrum = np.fft.fft(signal)
freqs = np.fft.fftfreq(256, d=1/256) # frequency of each bin
power = np.abs(spectrum) ** 2 # power spectrum
window = windows.hann(256) # from scipy.signal
result = fftconvolve(signal, kernel, mode='full')
frequencies, times, Zxx = stft(signal, fs=sample_rate, nperseg=256)
spectrogram = np.abs(Zxx) ** 2 # (n_frequencies, n_time_frames)
print(cmath.exp(1j * cmath.pi)) # Euler: close to -1
```
## Mel features from scratch
type: annotated-code
lang: python
from: phases/06-speech-and-audio/02-spectrograms-mel-features/docs/en.md, phases/06-speech-and-audio/02-spectrograms-mel-features/code/main.py
```python
def hz_to_mel(f):
return 2595.0 * math.log10(1.0 + f / 700.0)
mag = stft_magnitude(signal, frame_len=400, hop=160) # Hann + DFT per frame
fb = mel_filterbank(n_mels, n_fft, sr) # 80 mels, n_fft=400 gives (80, 201)
mel_spec = apply_filterbank(cmag, fb) # one weighted sum per mel bin
lm = log_transform(mel_spec) # eps=1e-10
mfcc = dct_ii(lm[len(lm) // 2], 13) # coefficient 0 is overall energy
```
## Key formulas
type: concepts
from: phases/01-math-foundations/19-complex-numbers/docs/en.md, phases/01-math-foundations/20-fourier-transform/docs/en.md, phases/06-speech-and-audio/02-spectrograms-mel-features/docs/en.md
- **Multiply**: $(a + bi)(c + di) = (ac - bd) + (ad + bc)i$. Magnitudes multiply, angles add.
- **Polar**: $r = \sqrt{a^2 + b^2}$ and $\theta = \mathop{\mathrm{atan2}}(b, a)$.
- **Euler**: $e^{i\theta} = \cos\theta + i \sin\theta$. Multiply by it to rotate by $\theta$.
- **DFT**: $X[k] = \sum_{n=0}^{N-1} x[n] \, e^{-2\pi i k n/N}$.
- **Inverse DFT**: positive exponent and a $1/N$ factor. Reconstruction is exact.
- **Bins**: $f_k = k \cdot f_s / N$, resolution $f_s / N$, maximum $f_s / 2$.
- **Convolution theorem**: $x * h = \text{IFFT}(\text{FFT}(x) \cdot \text{FFT}(h))$.
- **Mel**: $m = 2595 \cdot \log_{10}(1 + f / 700)$.
## Sample rates
type: command-table
from: phases/06-speech-and-audio/01-audio-fundamentals/docs/en.md
| Command | What it does |
|---|---|
| 8 kHz | Telephony. Nyquist at 4 kHz kills consonants. Avoid for ASR |
| 16 kHz | ASR standard. A 10 s clip is 160,000 floats |
| 22.05 kHz | TTS vocoder training for older models |
| 24 kHz | Modern TTS |
| 44.1 kHz | CD audio, music |
| 48 kHz | Film, pro audio, high-fidelity TTS |
## Window functions
type: command-table
from: phases/01-math-foundations/20-fourier-transform/docs/en.md
| Command | What it does |
|---|---|
| Rectangular | No window. Side lobes -13 dB. Signal exactly periodic in N |
| Hann | 0.5 (1 - cos(2 pi n / (N-1))). -31 dB. General purpose |
| Hamming | 0.54 - 0.46 cos(2 pi n / (N-1)). -42 dB. Speech analysis |
| Blackman | Wide main lobe. -58 dB. Side lobe suppression is critical |
## Numbers and defaults
type: defaults
from: phases/06-speech-and-audio/01-audio-fundamentals/docs/en.md, phases/06-speech-and-audio/02-spectrograms-mel-features/docs/en.md
| Setting | Default | Note |
|---|---|---|
| Frame length at 16 kHz | 400 | 25 ms window |
| Hop at 16 kHz | 160 | 10 ms. 10 s gives 998 frames |
| Mel bins for ASR | 80 | Audio classification uses 128 |
| MFCC coefficients | 13 | Keyword spotting uses 40 |
| Log-mel epsilon | 1e-10 | Inside log(mel_spec + eps) |
| Log-magnitude epsilon | 1e-6 | Inside log(abs(X) + eps) |
| 1024-point FFT at 16 kHz | 15.6 Hz | 512 usable bins over 0 to 8 kHz |
| PCM exchange format | 16-bit | int16, range plus or minus 32,767 |
## Troubleshooting
type: troubleshooting
from: phases/06-speech-and-audio/01-audio-fundamentals/docs/en.md, phases/06-speech-and-audio/02-spectrograms-mel-features/docs/en.md, phases/01-math-foundations/20-fourier-transform/docs/en.md
| Symptom | Cause | Fix |
|---|---|---|
| Garbage output that looks like a model bug | Sample rate mismatch | Match sample rate first. 16 kHz mono float32 |
| 7 kHz tone peaks at 3 kHz | Aliasing above Nyquist | Low-pass filter before downsampling |
| Spurious high frequencies | Spectral leakage at the frame edges | Apply a Hann or Hamming window |
| Silent failure at inference | Mel count mismatch, 80 against 128 | Log the feature shape at both ends |
| Wrong input for Whisper | dB-mel, not log-mel | Feed log-mel |
| WER doubles in production | Normalization drift | Use the same normalization in training and inference |
| Flat spectrum in the last frames | Zero padding at the end | Pad symmetrically or replicate |
| FFT convolution wraps around | The DFT computes circular convolution | Zero-pad to N + M - 1 |
## Questions
type: qa
from: phases/01-math-foundations/19-complex-numbers/docs/en.md, phases/01-math-foundations/20-fourier-transform/docs/en.md, phases/06-speech-and-audio/02-spectrograms-mel-features/docs/en.md
- **Does zero-padding add resolution?** No. Resolution needs $T = 1 / \Delta f$ seconds of data.
- **Why keep only the first N/2 + 1 bins?** For real input, $X[N-k]$ is the conjugate of $X[k]$.
- **Why is i a rotation?** Multiply by i to turn 90 degrees. Two turns give $i^2 = -1$.
- **How does RoPE use this?** It rotates queries and keys. Relative position becomes an angle.
- **Why mel, not Hz?** People hear pitch on a log scale. 100 to 200 Hz sounds like 1000 to 2000 Hz.
- **What does Parseval say?** Energy is the same in the time and frequency domains.
## Tips
type: tips
from: phases/01-math-foundations/19-complex-numbers/docs/en.md, phases/01-math-foundations/20-fourier-transform/docs/en.md, phases/06-speech-and-audio/02-spectrograms-mel-features/docs/en.md
- Start with 80 log-mels unless you work on music.
- A larger FFT gives better frequency resolution and worse time resolution.
- Use a power of 2 length for the FFT. Zero-pad to the next power of 2.
- The N roots of unity have magnitude 1 and sum to zero.
- Check `idft(dft(x))` against x. It must match to machine precision.
- MFCC coefficient 0 encodes overall energy. Models usually drop it.