I recently hacked together a delightfully simple micro:bit app that converts ambient light into sound. Think of it as a poor man’s photodiode oscilloscope — or, more accurately, a light-controlled Geiger counter for photons.
The idea is straightforward: more light → faster clicks.
What the Code Does (in Human Terms)
The micro:bit constantly reads the ambient light level using its built-in LED matrix as a sensor. This value ranges from 0 (darkness) to 255 (bright office lighting).
Instead of displaying numbers (boring), we sonify the signal.
Here’s the core loop:
- Read light intensity
lvl = display.read_light_level() - If it’s pitch black, we stop the sound and chill for a moment. No data, no noise.
if lvl <= 0: music.stop() sleep(50) continue - Map brightness to time
This is the fun part. We linearly map light intensity to the pause between clicks:- Bright → short pause → rapid clicking
- Dark → long pause → slow clicking
pause = MAX_PAUSE - (lvl - 1) * (MAX_PAUSE - MIN_PAUSE) // 255 - Emit a click
A short 440 Hz tone acts as our “tick”.music.pitch(440) sleep(CLICK_DURATION) music.stop() - Wait, then repeat forever — because all good embedded systems are infinite loops with opinions.
Why This Is Fun
- It’s signal processing without math anxiety
- It turns a sensor reading into something perceptible
- It’s trivial, deterministic, and oddly satisfying — like watching a queueing system stabilize
Cover the micro:bit with your hand and listen to the system slow down. Shine a phone flashlight on it and it goes full caffeine mode.
Minimal code. Maximum nerd joy. Here comes the code:
CLICK_DURATION = 5 # Duration of the click in ms
MIN_PAUSE = 10 # shortest pause (at lvl = 255)
MAX_PAUSE = 100 # longest pause (at lvl = 1)
while True:
lvl = display.read_light_level()
display.clear()
# If there is no light at all: play nothing
if lvl <= 0:
music.stop()
sleep(50) # small pause to prevent the CPU from running at 100%
continue
# lvl is between 1 and 255
# map lvl=1 -> pause=40ms, lvl=255 -> pause=5ms (linear)
pause = MAX_PAUSE - (lvl - 1) * (MAX_PAUSE - MIN_PAUSE) // 255
# Click (short tone)
music.pitch(440)
sleep(CLICK_DURATION)
music.stop()
# Pause depends on brightness
sleep(pause)