Cheat sheets

Complex Numbers, Fourier and Signals

Complex rotations, DFT and FFT, windows, sampling, spectrograms, mel filterbanks and MFCCs. Python from the lessons.

  • Concepts and terms
  • Tasks and commands
  • Annotated code
  • Diagrams
  • Defaults and numbers
  • Troubleshooting
  • Questions and tips

Source

Cooley-Tukey FFT, 8 points#

splitsplitcombinetwiddlex[0..7]8 samplesEven halfx[0,2,4,6]Odd halfx[1,3,5,7]E[0..3]4-pt FFTO[0..3]4-pt FFTX[0..7]E[k] +/- twiddle *O[k]splitsplitcombinetwiddlex[0..7]8 samplesEven halfx[0,2,4,6]Odd halfx[1,3,5,7]E[0..3]4-pt FFTO[0..3]4-pt FFTX[0..7]E[k] +/- twiddle * O[k]

From Lesson 01.20

Waveform to log-mel and MFCC#

1Framecut 25 ms windows with a 10 ms hop. At 16 kHzthat is 400 and 160 samples.2Windowmultiply each frame by hann(N). It tapers theedges to zero.3STFTtake the FFT magnitude of each frame. Shape is(n_frames, n_freq_bins).4Melmultiply by the triangular filterbank. 80 melsover 0 to 8 kHz is the ASR standard.5Loglog(mel_spec + 1e-10) compresses the dynamicrange.6MFCCapply dct_ii to each log-mel frame. Keep thefirst 13 coefficients.

From Lesson 06.02, 06.02 code/main.py

Tasks in code#

TaskCode
Rotate a point by 45 degreespoint * euler(math.pi / 4)
N points on the unit circleroots_of_unity(N)
Transform and invert, O(N^2)dft(x), then idft(X)
Fast transform, power of 2fft(x)
Energy per frequencypower_spectrum(X)
Linear convolution by FFTconvolve_fft(x, h)
Split a signal into framesframe(signal, frame_len, hop)
Mel filterbank matrixmel_filterbank(n_mels, n_fft, sr)

From Lesson 01.19, Lesson 01.20, 01.20 code/fourier.py, Lesson 06.02

Spectra in NumPy and SciPy#

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) ** 2power 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

From Lesson 01.19, Lesson 01.20, Lesson 06.01

Mel features from scratch#

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

From Lesson 06.02, 06.02 code/main.py

Key formulas#

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).

From Lesson 01.19, Lesson 01.20, Lesson 06.02

Sample rates#

CommandWhat it does
8 kHzTelephony. Nyquist at 4 kHz kills consonants. Avoid for ASR
16 kHzASR standard. A 10 s clip is 160,000 floats
22.05 kHzTTS vocoder training for older models
24 kHzModern TTS
44.1 kHzCD audio, music
48 kHzFilm, pro audio, high-fidelity TTS

From Lesson 06.01

Window functions#

CommandWhat it does
RectangularNo window. Side lobes -13 dB. Signal exactly periodic in N
Hann0.5 (1 - cos(2 pi n / (N-1))). -31 dB. General purpose
Hamming0.54 - 0.46 cos(2 pi n / (N-1)). -42 dB. Speech analysis
BlackmanWide main lobe. -58 dB. Side lobe suppression is critical

From Lesson 01.20

Numbers and defaults#

SettingDefaultNote
Frame length at 16 kHz40025 ms window
Hop at 16 kHz16010 ms. 10 s gives 998 frames
Mel bins for ASR80Audio classification uses 128
MFCC coefficients13Keyword spotting uses 40
Log-mel epsilon1e-10Inside log(mel_spec + eps)
Log-magnitude epsilon1e-6Inside log(abs(X) + eps)
1024-point FFT at 16 kHz15.6 Hz512 usable bins over 0 to 8 kHz
PCM exchange format16-bitint16, range plus or minus 32,767

From Lesson 06.01, Lesson 06.02

Troubleshooting#

SymptomCauseFix
Garbage output that looks like a model bugSample rate mismatchMatch sample rate first. 16 kHz mono float32
7 kHz tone peaks at 3 kHzAliasing above NyquistLow-pass filter before downsampling
Spurious high frequenciesSpectral leakage at the frame edgesApply a Hann or Hamming window
Silent failure at inferenceMel count mismatch, 80 against 128Log the feature shape at both ends
Wrong input for WhisperdB-mel, not log-melFeed log-mel
WER doubles in productionNormalization driftUse the same normalization in training and inference
Flat spectrum in the last framesZero padding at the endPad symmetrically or replicate
FFT convolution wraps aroundThe DFT computes circular convolutionZero-pad to N + M - 1

From Lesson 06.01, Lesson 06.02, Lesson 01.20

Questions#

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.

From Lesson 01.19, Lesson 01.20, Lesson 06.02

Tips#

  • 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.

From Lesson 01.19, Lesson 01.20, Lesson 06.02