A tactile, screen-first MP3 player for the Radxa Zero, built around an ILI9488 480×320 IPS SPI display and a five-button physical interface. No touchscreen, no menus-within-menus — just music.
Licensed under the Hippocratic License HL3-FULL.
The Mishmann Player takes inspiration from classic tape decks like the 1979 Sony Walkman TPS-L2 and rebuilds the idea on modern embedded hardware. It runs headless on a Radxa Zero, drives its display over SPI, and is controlled entirely through five tactile buttons: play/pause, next, previous, volume up, and volume down.
The interface is drawn dynamically rather than skinned: colors are extracted from each track's album art, animated cassette spools spin during playback, and a split-screen library view lets you browse artists and albums without ever touching a settings page.
- Dynamic HSV palette engine — Extracts dominant colors from embedded album art and scales hue, saturation, and value to produce a dark background with a high-contrast complementary accent. Every album gets its own theme, automatically.
- Mechanical cassette spools — Twin rotating spools mimic tape hubs. All 36 rotation frames are pre-rendered into RAM at boot, giving a smooth 25 fps spin during playback without SPI bottlenecks.
- Split-screen library — A 44 px text list on the left, a live "deck preview" on the right with metadata and an album-art collage (a 50/50 vertical split for two albums, a 2×2 grid for three or four).
- See-through volume HUD — Volume changes appear as a translucent overlay alpha-composited onto the album art. When the HUD hides, the original backdrop slice is restored exactly — no ghosting.
- Phone-assisted Wi-Fi setup & web portal — If no network is found at boot, the player starts a
Walkman Setuphotspot and walks you through connecting from your phone. The mobile-friendly web console handles track uploads (MP3, FLAC, M4A, and more) and Wi-Fi configuration. - MusicBrainz auto-tagging — A background thread fills in missing genre tags via the MusicBrainz API whenever Wi-Fi is available, writing them back to the files with
mutagenwithout interrupting playback. - Boot diagnostics —
boot.pyverifies the SPI display, GPIO lines, backlight PWM, PulseAudio pipeline, and music directories before launch, showing a pass/fail checklist on screen.
A terminal-style checklist on a dark slate background with real-time pass/fail markers for each hardware interface.
Album art with an 8 px drop shadow and 2 px border, twin spinning spools below, and track title and timer rendered in the extracted accent color.
The selected row highlights in the dynamic theme color on the left; the right pane shows a collage preview and genre for the selected artist.
The software runs as a cluster of lightweight, decoupled Python threads communicating via D-Bus and NetworkManager signals:
+--------------------------------+
| boot.py |
| (Initializes & self-checks) |
+---------------+----------------+
|
v
+--------------------------------+
| music_player.py | <------+
| (Primary UI and state loop) | |
+-------+--------------+---------+ |
| | |
(Controls audio stream)| | (BlueZ D-Bus) | (Signals rescan)
v v |
+---------------+ +----+---------+ |
| GStreamer API | | bt_manager.py | |
+---------------+ +--------------+ |
|
+--------------------------------+ |
| upload_server.py | -------+
| (Flask server & Wi-Fi Setup) |
+---------------+----------------+
|
(Polls Wi-Fi) v
+---------------+
| genre_fill.py | (MusicBrainz thread)
+---------------+
boot.py— Diagnostic manager. Verifies critical interfaces before playback; on failure it draws an on-screen warning and halts safely instead of failing silently.music_player.py— Core runtime. Handles SPI address windowing, in-memory PIL rendering, spool animation, and button input.bt_manager.py— Native D-Bus Bluetooth driver. Talks directly tobluetoothdfor scans, pairing, connections, and trust — no shell subprocesses.upload_server.py— Multi-threaded Flask portal. Usesnmclito bring up theWalkman Setuphotspot when needed, and serves the uploader, library management, and Wi-Fi configuration pages.genre_fill.py— Asynchronous tagging daemon. Queries the MusicBrainz API (respecting its 1 request/second policy) and writes genres back into file metadata viamutagen.
Built for the Radxa Zero using the Linux kernel GPIO character device API (gpiod) and spidev.
RADXA ZERO 40-PIN HEADER
+---------+
3V3 | 1 2 | 5V
SDA0 | 3 4 | 5V
SCL0 | 5 6 | GND
... | . . | ...
* LINE1 | 11 12 | ... * LINE1 = Data/Command (DC)
* LINE2 | 13 14 | GND * LINE2 = Play/Pause Button
... | . . | ...
* LINE4 | 16 17 | 3V3 * LINE4 = Previous Button
... | . 18 | * LINE10 * LINE10 = Backlight PWM
* MOSI | 19 20 | * LINE20 * MOSI = SPI MOSI / LINE20 = Next Button
* LINE8 | 21 22 | ... * LINE8 = Reset (RST)
* SCLK | 23 24 | * CS0 * SCLK = SPI SCLK / CS0 = SPI CS0
GND | 25 26 | ...
... | . . | ...
* LINE11| 29 30 | GND * LINE11 = Volume Up Button
* LINE12| 31 32 | ... * LINE12 = Volume Down Button
+---------+
| Hardware Component | Device Pin / Label | Radxa Zero Header Pin | Line Number (gpiochip3) |
|---|---|---|---|
| ILI9488 Display | SPI MOSI | Pin 19 (GPIOC_1) | — (Hardware SPI) |
| SPI SCLK | Pin 23 (GPIOC_3) | — (Hardware SPI) | |
| SPI CS0 | Pin 24 (GPIOC_4) | — (Hardware SPI) | |
| Data / Command (DC) | Pin 11 (GPIOA_1) | Line 1 | |
| Reset (RST) | Pin 21 (GPIOA_8) | Line 8 | |
| Backlight PWM | Pin 18 (GPIOB_10) | Line 10 (PWM Channel 0) | |
| Tactile Buttons | Play / Pause | Pin 13 (GPIOA_2) | Line 2 |
| Next Track | Pin 38 (GPIOA_20) | Line 20 | |
| Previous Track | Pin 16 (GPIOA_4) | Line 4 | |
| Volume Up | Pin 29 (GPIOA_11) | Line 11 | |
| Volume Down | Pin 31 (GPIOA_12) | Line 12 |
The player needs GStreamer, GLib, PulseAudio, and Python bindings for D-Bus and GPIO access. On a Radxa Zero running Debian/Ubuntu:
sudo apt update
sudo apt install python3-pip python3-dbus python3-gi python3-flask python3-mutagen python3-pil
sudo apt install gstreamer1.0-plugins-good gstreamer1.0-plugins-bad gstreamer1.0-plugins-ugly gstreamer1.0-pulseaudiogit clone https://github.com/SpacemannFinn/Mishmann.git
cd Mishmann
# Create the music directory
mkdir -p /home/rock/musicNote: The upload server drives NetworkManager via
nmclito create hotspots and join networks, so the user running the player needs the appropriate permissions or sudo privileges.
Start the full player platform with diagnostic checks:
python3 boot.pyTo run it automatically at boot, install the included systemd unit:
sudo cp mishmann-player.service /etc/systemd/system/
sudo systemctl enable --now mishmann-player| Symptom | Root Cause | Resolution |
|---|---|---|
| Boot hangs at "Display & GPIO" | SPI device node or GPIO lines locked | Check whether /dev/spidev3.0 is busy: sudo lsof /dev/spidev*, then kill conflicting Python instances |
| Audio fails or plays with static | PulseAudio sink misconfigured | Check routing with pactl list sinks and make sure the GStreamer audio sink matches your active PulseAudio sink |
| Wi-Fi scan returns an empty list | Single-radio chip in AP mode | Single-antenna modules can't scan while hosting a hotspot. Use the web GUI's Manual Connection Card to enter the SSID/password directly |
| D-Bus interface exceptions | bluetoothd not running |
sudo systemctl status bluetooth, and confirm the bindings import cleanly: python3 -c "import dbus" |
This project is licensed under the Hippocratic License HL3-FULL. See the LICENSE file for details.