Spotify on a real vector CRT

I wanted my 1982 Vectrex to do something it was never designed for: appear as a Spotify Connect device, play music through its own speaker, and show the current track on its real vector CRT. The original joystick and buttons control playback. A PiTrex cartridge and Raspberry Pi Zero W connect the old hardware to the modern service; the Vectrex display and audio path remain part of the working system.

Watch the working demonstration and build story in the video below. Gallery photographs are frames from that real demonstration, showing the vector logo, PiTrex/Pi assembly and original controller.

Working demonstration

Architecture

  • Stream: Spotify app → Spotify Connect → go-librespot on Pi Zero W.
  • Audio: PCM FIFO → vectrex_av → shared DAC → Vectrex speaker.
  • Track information: local /status API → nowplaying_poll.py → atomic text file → vectrex_av → vector CRT.
  • Controls: original controller → PiTrex/VIA → vectrex_av → non-real-time HTTP requests → go-librespot.

The tested development setup uses go-librespot 0.7.4, a Linux-based PiTrex environment and the single-core ARMv6 Pi Zero W. PiTrex plugs into the cartridge slot and provides access to the console's VIA/DAC through its vectrexInterface library. This is not an emulator with a replacement LCD. PiTrex and go-librespot are existing projects; my contribution is the audio/video/input bridge and metadata sidecar.

The shared-DAC problem

The important constraint is that beam positioning and sampled audio share one DAC. A beam draw cannot coincide with an audio sample. The C player owns the hardware and interleaves short drawing/input bursts with audio output, rather than letting two independent programs fight over the VIA.

go-librespot supplies signed 16-bit, 44.1 kHz stereo PCM to a named FIFO. A reader converts it to 8-bit mono at 8 kHz and feeds a lock-free ring buffer. The real-time output uses SCHED_FIFO priority 20 and absolute timing deadlines. HTTP requests and metadata file access stay off that timing-critical thread.

When a graphics burst occupies the DAC, the player measures the time taken and advances the audio read pointer by the corresponding sample count. Those samples are dropped rather than replayed late. A typical 5 ms drawing burst at 25 Hz occupies roughly 12.5% of the time budget. This is a deliberate audio/display trade-off: music remains recognisable, but visuals introduce periodic audible artefacts. It is a lo-fi experiment, not a hi-fi player.

In the development tests, 11,025 Hz output consumed too much of the single CPU core and starved the decoder. Reducing output to 8 kHz left enough time for decoding and buffering. That improved the observed underruns; it is not a guarantee across other systems or workloads.

Track information and a 3D vector logo

A Python sidecar polls the local status endpoint about once a second and writes title, artist, playback status and readiness atomically. The C program uses a double-buffered metadata snapshot. Strings are transliterated to uppercase ASCII for the PiTrex font, and long titles scroll.

The Spotify emblem is an extruded wireframe medallion: front and back faces joined by edges. Rotation exposes real geometric depth. The vector count and redraw cadence are constrained to protect the audio budget. Orientation is shared between visual modes, so a manually oriented large logo retains its pose when returning to the combined view.

Controls and four visual modes

  • Button 1: previous track.
  • Button 2: play/pause.
  • Button 3: next track.
  • Button 4: cycle text only → logo plus text → full-screen logo → display off.
  • In text modes, joystick up/down controls volume and left/right controls scrolling.
  • In full-screen logo mode, the joystick changes yaw and pitch.

Logo plus text is the startup default. Display off gives the cleanest audio because it avoids beam drawing. Inputs and visuals continue while paused. An idle spinning coin with “System Ready...” indicates...

Read more »