This is a project I made for my girlfriend, Alaina, for Christmas 2025. The music box is built using a Raspberry Pi Zero 2 W, an MCP3202 ADC, three resistors, and a hand crank purchased from Amazon.
For audio output, I used an I2S breakout board for the Raspberry Pi that functions as both an amplifier and DAC:
MAX98357A https://www.adafruit.com/product/3006
This is connected to a 1-watt, 8-ohm speaker. The music box supports mono output only.
The enclosure and buttons were purchased from Amazon, and all other electrical components were sourced from Digi-Key.
Internally, the system functions as a simple music player, but with:
- Crank speed controlling playback speed
- Buttons for volume control
- Playlist navigation
- Mode switching
- Playlist updating via the internet
Above are the internals of the music box.
-
Top right: A portable phone battery bank (from Amazon). I mistakenly bought one without pass-through charging, so it is not wired into the system. The box is powered via a separate Micro-USB cable connected directly to the Pi.
-
Top section: A wooden button board with 7 wires (one per GPIO button plus a common ground). This board is glued to the side. The second image shows the external view.
-
Below the buttons: The speaker, connected to the DAC/amp board, which connects to the Pi.
-
Left side: The MCP3202 ADC mounted on perfboard (secured with double-sided tape). It has 6 connections to the Pi:
- MISO
- MOSI
- CS/SHUTDOWN
- GND
- VIN
- VREF
-
Bottom section: A three-resistor voltage divider that reduces the hand crank output (up to ~25V) down to roughly 0–4.5V for the ADC.
-
Very bottom: The hand crank.
-
The Pi itself is unsecured inside the box. Electrical tape failed, but it has worked fine floating, so it was left as is.
From left to right:
VOLUME UP | VOLUME DOWN | SKIP SONG | PREVIOUS SONG | MODE SWITCH | UPDATE PLAYLIST
Adjusts the strength of the mpv gain filter.
Moves forward or backward in the mpv playlist.
Switches between three playback modes:
-
Crank controls playback speed
- Music stops when the crank stops
-
Crank controls play/pause only
- Playback speed remains at 1.00×
-
Normal playback
- Music plays continuously, no cranking required
Triggers a download thread that (if connected to the internet):
- Runs a
yt-dlpprocess to download new songs from a YouTube playlist - Runs another
yt-dlpprocess to generate aplaylist.m3ufile
yt-dlp uses --download-archive to avoid re-downloading songs.
When the update finishes, mpv reloads the playlist.
If there is no internet connection, an error is printed and nothing happens.
Ironically, the code was supposed to be the easiest part (after the box design and soldering). As the commit history shows, that did not go as planned. Below is an overview of the main files.
Initializes SPI communication with the ADC, reads the 12-bit response, and converts it into a C integer.
An infinite loop that adjusts song playback speed based on the current mode (shared variable).
Initializes the Pi’s GPIO using the wiringPi library (planned migration to libgpiod).
Polls for button presses and performs the corresponding actions. Also checks for playlist updates and reloads the playlist if needed.
Main program entry point. Initializes mpv, starts external threads (Buttons, mpv events, Update thread), and calls speed_loop().
Handles playlist downloading and updating in a separate thread:
-
Checks if another download thread is active and waits if necessary
-
Spawns a
yt-dlpprocess usingposix_spawnp()to download songs -
Waits for completion and reports errors (common issues: age-restricted videos or connection/HTTP errors)
-
Spawns another
yt-dlpprocess using--flat-playlistto print entries to STDOUT -
Uses file descriptors and POSIX file actions to write to a temporary playlist file
-
If this step fails, the program exits (typically indicates no internet connection)
-
Compares the temporary playlist with the old one
- If unchanged, exit
- If changed, replace the old playlist using
unlink()and copy
-
Sets the updated flag and unlocks the mutex
Future improvement: Add an audio cue to indicate errors.