Real-time, synchronized lyrics for every song around you — on a screen that fits on your dashboard, shelf, or anywhere you make music part of the moment.
LyriChi is a proof-of-concept embedded device that listens to whatever music is playing around you and displays the lyrics, line by line, in perfect sync with the song. I built it for the Musixmatch Musicathon using only hardware I already had at home.
Because the device has no microphone, audio is streamed over the network from another device (a laptop or phone). LyriChi recognizes the song in real time with AudD, fetches time-synced lyrics from Musixmatch, and scrolls them across the LCD as the song plays.
For the full product vision, see description.md.
flowchart LR
A["Audio source<br/>(radio / phone / laptop)"] --> B["server.py<br/>(laptop)"]
B -->|"raw PCM audio over TCP"| C["rpi.py<br/>(Raspberry Pi)"]
C -->|"4s WAV chunk"| D["AudD API<br/>song recognition"]
D -->|"Spotify ID + timecode"| C
C -->|"Spotify ID"| E["Musixmatch API<br/>synced lyrics"]
E -->|"LRC lyrics"| C
C -->|"line by line, in sync"| F["20x4 I2C LCD"]
- Capture & stream —
server.pyruns on a laptop, captures mono 16-bit audio at 48 kHz, and streams it as raw PCM over a TCP socket to the Raspberry Pi. - Recognize —
rpi.pyruns on the Pi. It buffers the incoming stream into ~4-second chunks, saves each as a WAV file, and sends it to AudD for song recognition. AudD returns the matched track, a Spotify ID, and a playback timecode. - Fetch lyrics — Using the Spotify ID, the Pi requests time-synced (LRC-style) lyrics from the Musixmatch subtitles API.
- Display in sync — The Pi computes where the song currently is (using AudD's timecode plus the time spent on API calls) and pushes each lyric line to the 20x4 character LCD at the right moment. When a new song is recognized or the result shifts the timeline significantly, the display thread is restarted to re-sync.
Everything here was salvaged from "what I had at home":
| Component | Details |
|---|---|
| Raspberry Pi | ~10-year-old board (Pi 3 class) running the recognition + display logic |
| Display | 20x4 character LCD (2004A, HD44780-compatible) |
| LCD interface | PCF8574 I2C backpack at address 0x27, on I2C bus 1 (SDA/SCL) |
| Audio source | A separate laptop or phone — the device itself has no microphone |
| Network | Local network link between the audio source and the Pi |
Future hardware (the dream): a high-resolution color OLED, working WiFi module, battery-powered with a readable font, smooth animations, an integrated SIM for connectivity on the go. Or QR-code hotspot sharing so it works anywhere.
| File | Role |
|---|---|
| rpi.py | Runs on the Raspberry Pi. Receives the audio stream, chunks it, runs recognition + lyrics fetch, and drives the LCD in sync. |
| server.py | Runs on the laptop. Captures system/microphone audio and streams it to the Pi. |
| main.py | Shared library of helpers (AudD, Musixmatch, lyric parsing, LCD writing, sync logic) and a standalone CLI with a --dry-run mode for testing lyric sync. |
| lcd.py | Minimal "hello world" script to verify the LCD wiring. |
| lcd-test/lcd-test.ino | Alternative Arduino sketch that drives the same LCD over serial (experimental path). |
lyrics.txt |
Not included (see below) — local LRC-style lyrics used only by --dry-run. |
Note: this is a hackathon project, so some code paths are experimental or unused. That's intentional.
- Python 3.14+
- uv for dependency management
- API keys for AudD and Musixmatch
Dependencies are managed with uv (see pyproject.toml and uv.lock):
uv syncCreate a .env file (it is git-ignored) with your API keys:
AUDD_API_KEY=your_audd_api_key
MXM_API_KEY=your_musixmatch_api_keyThese are read by main.py via AUDD_API_KEY and MXM_API_KEY.
The LCD uses I2C, so make sure it's enabled:
sudo raspi-config # Interface Options -> I2C -> EnableYou can confirm the display address with i2cdetect -y 1 (expected: 0x27).
uv run rpi.pyThe Pi listens on port 5000 and waits for an incoming audio connection.
Set the Pi's IP address in server.py (pi_host), then run:
uv run server.pyOnce connected, play music near (or through) the laptop and lyrics will begin appearing on the LCD as soon as the song is recognized.
main.py includes a --dry-run mode that skips recording and all API calls, using a local lyrics.txt file with a mocked timecode instead. This is handy for tuning the sync logic on a regular computer.
uv run main.py --dry-runlyrics.txt is intentionally not committed (I don't hold the rights to redistribute lyrics). To test, create your own lyrics.txt in the project root using LRC-style timestamps:
[00:12.50] First line of the song
[00:16.20] Second line
[00:19.80] Third line
Format: [MM:SS.xx] where xx is hundredths of a second, followed by the lyric text. The dry-run assumes the song is currently at 00:57, so add lines around that timecode to see them appear.
If no LCD is connected, lyrics are printed to the console instead.
- Python — recognition pipeline, lyric parsing, and sync engine
- AudD API — real-time song recognition from raw audio
- Musixmatch API — time-synced (subtitle) lyrics
- RPLCD — I2C driver for the 20x4 character LCD
- sounddevice / soundcard / soundfile — audio capture and WAV handling
- TCP sockets — low-latency audio streaming from laptop to Pi
- uv — dependency management
- Recognition latency depends on AudD; lyrics appear a few seconds into a song.
- Cyrillic lyrics are transliterated to Latin so they render on the character LCD.
- Planned: OLED display, on-device connectivity via SIM, and QR-code hotspot sharing for true standalone, no-cables use.