A Pico 2 W light show that follows the music: 460 LEDs, two strips, Demucs stems

How a Raspberry Pi Pico 2 W runs a 460-LED music-synced light show: Demucs stem separation and beat analysis on the PC, cue-based sync instead of frame streaming, and the Wi-Fi/Bluetooth surprises along the way.

Sharing a hobby build because a few people asked how I sync lights to music without a commercial controller.

Hardware

  • Raspberry Pi Pico 2 W running MicroPython, on Wi-Fi
  • Strip 1: 300 x SK6812 RGBW (the W channel gives real warm white instead of fake RGB white)
  • Strip 2: 160 x WS2801 on SPI, mounted as a U around a cabinet (left side up, across the top, right side down), so the firmware knows which LED index is where in space
  • The Pico sits on its own USB supply. The LED PSU can pull 15 A on white and browns out the Wi-Fi if you share it. Learned that the hard way.

What the PC does

A Python terminal app (Textual) with two decks and a crossfader, like a small DJ setup:

  1. Search a track, download it, run Demucs to split it into drums / bass / vocals / other.
  2. Analyse the stems with librosa: beats snapped to drum onsets, downbeats found by scoring 4/4 phases on kick + bass, hi-hat onsets above 7 kHz, and a slow “hype” curve (0..1) that says how intense the music is right now.
  3. Play the audio with an equal-power crossfade between decks and render the light patterns against the playhead, minus the audio output latency, so the flash lands on the kick and not 100 ms later.

The part that made it stable: stop streaming frames

The first version pushed full LED frames over UDP at 60 fps, which worked but stuttered on fast tracks and any Wi-Fi hiccup was visible. The current version sends only cues:

  • SET: which pattern and palette to run
  • SYNC: music clock, BPM and hype, 4 times a second, with the Pico slewing its clock by at most 2 ms per frame so nothing jumps
  • CUES: a rolling 2.2-second window of upcoming beats, downbeats, kicks, snares and hats with their strength; the Pico deduplicates, so packet loss just means the window overlaps

The Pico renders the show itself on its second core with integer-only patterns (pulse, chase, burst, strobe, sparkle, wave). Measured: about 50 fps on both strips, clock within about 27 ms of the PC playhead, and during 3 seconds of total radio silence it keeps running at 54 fps and drifts 2 ms. Traffic went from 120 packets per second to about 15.

Odd things I hit

  • On this laptop the Wi-Fi and Bluetooth share one Intel chip. With only 15 packets per second the Wi-Fi drops into power-save cycles whose wake-up windows steal the antenna from the Bluetooth headphones, so the audio crackled every 2 seconds. Sending a tiny 3-byte keepalive per tick fixed it. Fewer packets made things worse, not better.
  • MicroPython on RP2 allows exactly one thread on core 1, so the ambient-light engine and the show engine had to be merged into one loop.
  • Uploading a firmware file over 65 KB over HTTP blows the Pico’s RAM, so the source is minified before OTA deploy and the readable copy stays in the repo.
  • Everything talks to the Pico sequentially. Parallel HTTP requests time out its socket backlog.

There is also a plain “ambient” mode: one HTTP request starts a candle, breathe, drift or twinkle effect per strip with no PC involved, which is what the room runs 95 % of the time.

Happy to answer questions on any of it, especially the beat analysis, which was the fiddliest bit.


Write a comment