Lab 21: Spectrum of a Real Sound
Time: ~50 minutes | Prerequisites: Lab 20 | Hardware: Pico 2, INMP441, OLED
This is the one you've been building toward
Fourteen labs of groundwork, and now it all points outward. Microphone in, FFT, bars on
the screen. Then whistle at it and slide your pitch around — the peak follows you.
That's a sound becoming a number you can see. Now that's a superpower.
What You'll Build

A live spectrum analyzer: real audio in, a 32-bar display out, with a peak-frequency readout that tracks your whistle.
Learning Objectives
- Connect the microphone, FFT and display into one pipeline
- Import your FFT as a library rather than pasting it
- Convert a complex spectrum to magnitudes, cheaply
- Group bins into display bars and scale them sensibly
- Explain why a quiet room's spectrum slopes downward
- Reject low-frequency rumble when hunting for a peak
Concepts Introduced
| ID | Concept |
|---|---|
| 400 | Magnitude Computation |
| 401 | Fast Magnitude Approximation |
| 402 | Power Versus Magnitude |
| 403 | Bin Averaging For Display |
| 404 | Logarithmic Scaling |
| 405 | Square Root Scaling |
| 406 | Spectrum Bars |
| 407 | Frame Capture |
| 408 | Live Spectrum Display |
| 409 | Whistle Test |
| 410 | Half Spectrum Display |
Background
Your FFT is now a library
The FFT you built in Lab 20 lives in /lib/fftlab.py. Same algorithm, nothing added:
1 2 3 4 5 | |
You're using a library whose entire contents you wrote and validated — a far better position than trusting a black box.
Magnitude, without the square root
True magnitude needs sqrt(re² + im²), and square roots aren't cheap. When the answer becomes a
bar 40 pixels tall, this approximation is plenty:
1 | |
Within about 4%, noticeably faster. Knowing when precision doesn't matter is an engineering skill.
Why N = 256 here
At N = 512 the pure-Python FFT takes 145 ms — about 7 frames per second, which feels sluggish. N = 256 takes ~71 ms and feels alive.
The cost: bins are 50 Hz wide instead of 25. Resolution traded for speed — a tradeoff you'll meet formally in Lab 24 and finally beat in Module 7.
Rooms rumble
Point this at a silent room and the spectrum slopes steeply downward — bin 2 towering
over everything. That's 1/f noise: traffic, ventilation, the building itself. It's
real sound, not a bug. But it means the loudest bin is always low-frequency rumble,
which would drown out any whistle. So we start hunting above 300 Hz.
Procedure
Step 1 — Run it and whistle
Open 21-real-spectrum.py and run it:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 | |
1 2 3 | |
Now whistle. Start low and slide upward. The bar moves right and the number climbs. Slide back down and it follows.
That's the FFT working on the real world, in real time, on a $5 chip.
Step 2 — Try different sounds
| Sound | What to look for |
|---|---|
| Whistle | one sharp peak that tracks your pitch |
| Humming | a peak plus harmonics — evenly spaced taller bars |
| "Sssss" | broad energy spread across high bins, no peak |
| Clapping | everything lights up briefly (Lab 15's impulse!) |
| Silence | rumble at the left, "listening" on screen |
Humming is the interesting one. Your voice isn't a pure tone — it's a fundamental plus overtones, exactly the additive recipe from Lab 12, now visible.
Step 3 — Understand the peak-finder
1 2 | |
Two safeguards. MIN_BIN skips the rumble. PEAK_RATIO demands the peak stand three times above
average before we believe it — otherwise the display says "listening" rather than inventing a
frequency.
Reporting nothing beats reporting nonsense
A meter that always shows a number looks confident and is often lying. One that admits
"no clear tone" is more useful — and it's the same instinct as Lab 9's aliasing lesson.
Know when your instrument has nothing to say.
Step 4 — Square-root scaling
1 | |
Raw magnitudes are dominated by the loudest bin and everything else vanishes. Square root compresses the range so quiet detail stays visible, without the expense of a log. Try removing it — the display goes almost blank between peaks.
Step 5 — Predict, then measure
Prediction: whistle a steady note. How many different bins does the reading jump between? Why doesn't it hold perfectly still?
You've just discovered frequency resolution, which is Lab 23's whole problem.
Expected Output
Thirty-two bars responding to sound, a peak-frequency readout, and a marker under the peak bar. Silence shows "listening".
Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
| Always shows "listening" | PEAK_RATIO too high, or too quiet |
Lower it to 2.0, or whistle louder |
| Peak stuck at the lowest bin | MIN_BIN too low |
Raise toward 8 |
| Bars all maxed out | Normalising to the wrong value | biggest should be the max across bars |
| Very sluggish | N too large | 256 is the sweet spot in pure Python |
| Bin 0 enormous | DC not removed | Subtract the mean (Lab 7) |
ImportError: fftlab |
Library not on the board | It belongs in /lib |
Challenges
- Peak hold. Keep the highest peak seen in the last two seconds and mark it, like Lab 8's VU meter.
- Waterfall. Scroll the display downward each frame, drawing brightness by magnitude. You'll have built a spectrogram.
- Count the harmonics. Hum a steady note and count evenly-spaced peaks. Does the spacing match your fundamental?
Check Your Understanding
- Why is a quiet room's spectrum loudest at low frequencies?
- Why does the peak-finder ignore bins below 300 Hz?
- What does the fast magnitude approximation trade away, and why is that acceptable here?
- Why does square-root scaling make the display more readable?
- Humming shows several evenly-spaced peaks. What are they?
You made a sound into a picture
Fourteen labs ago you couldn't tell a whistle from a rumble. Now your Pico draws the
difference, live. Next lab: why your whistle sometimes smears across several bars
instead of making one clean spike.
Next: Lab 22: Windowing and Spectral Leakage | Previous: Lab 20