|
miniDSP
A small C library for audio DSP
|
This tutorial walks through the examples/magnitude_spectrum.c program, which generates a multi-tone signal, windows it, computes the magnitude spectrum with MD_magnitude_spectrum(), and visualises the result.
Every real-valued signal can be decomposed into a sum of sinusoids at different frequencies. The magnitude spectrum tells you the amplitude of each sinusoidal component.
The tool for this decomposition is the Discrete Fourier Transform (DFT). Given \(N\) samples \(x[n]\), the DFT produces \(N\) complex coefficients:
\[ X(k) = \sum_{n=0}^{N-1} x[n] \, e^{-j\,2\pi\,k\,n/N}, \qquad k = 0, 1, \ldots, N-1 \]
The magnitude spectrum is simply the absolute value of these coefficients: \(|X(k)|\). Each bin \(k\) corresponds to frequency \(f_k = k \cdot f_s / N\), where \(f_s\) is the sample rate.
For a real signal, the spectrum is symmetric around \(N/2\), so only the first \(N/2 + 1\) bins (the "one-sided" spectrum) carry unique information.
We create a signal with three known sinusoidal components (440 Hz, 1000 Hz, and 2500 Hz) at different amplitudes, plus a small DC offset. This lets us verify that the spectrum shows peaks at exactly those frequencies.
Before computing the FFT, we multiply the signal by a Hanning window. Why?
The DFT assumes the input is one period of a periodic signal. In reality, our signal chunk rarely starts and ends at a perfect zero crossing. This mismatch creates an artificial discontinuity at the boundaries, which causes energy to "leak" from the true frequency into neighbouring bins – an effect called spectral leakage.
A window function tapers the signal smoothly to zero at both edges, eliminating the discontinuity:
\[ w[n] = 0.5 \left(1 - \cos\!\left(\frac{2\pi\,n}{N-1}\right)\right) \]
The trade-off: windowing widens the main lobe of each peak slightly (reducing frequency resolution) but dramatically suppresses the side lobes (improving dynamic range).
With the windowed signal ready, a single call to MD_magnitude_spectrum() computes \(|X(k)|\) for bins \(k = 0, 1, \ldots, N/2\):
Internally, miniDSP uses FFTW to compute the real-to-complex FFT, then takes the absolute value of each complex coefficient.
The raw magnitudes \(|X(k)|\) are unnormalised (they scale with \(N\)). To recover the actual signal amplitudes, we:
After this normalisation, a pure sine wave at amplitude \(A\) produces a peak of height \(A\) in the spectrum.
The linear-scale plot shows clear spikes at the three input frequencies. The 440 Hz tone (amplitude 1.0) is tallest, followed by 1000 Hz (0.6) and 2500 Hz (0.3). The tiny spike at 0 Hz is the DC offset (0.1).
The dB plot reveals low-level details invisible on the linear scale. The Hanning window's side lobes appear as the gradually decaying skirt around each peak. Without windowing, these lobes would be much higher and wider, obscuring nearby weak signals.