DevLog #002 — Wavepunk OS: Building the UI

Quick clarification before diving in: Wavepunk is not an operating system. ByteWave MK.I runs Raspberry Pi OS underneath, like any other Pi. Wavepunk is the media player application I built from scratch on top of it — a fully custom DAP experience that runs on the Pi and takes over the screen when you want it to.

The OS question is something I’m still thinking through. Ideally I want both modes available: use ByteWave as a regular Pi desktop when you need it, and switch cleanly into the full DAP experience when you just want music. The exact implementation of that switching mechanism isn’t locked in yet — that’ll be a future DevLog. For now, Wavepunk launches on boot and runs as your primary interface.

With that out of the way — here’s how the UI actually got built.


The Vision: A Media Player That Feels Like Hardware

Most media player UIs feel like an afterthought. A file browser, a progress bar, some buttons. Generic. I wanted Wavepunk to feel like the interface was physically part of the device — something you’d see in a high-end piece of audio hardware from a fictional 2077 product line.

The aesthetic direction: dark backgrounds, precise geometry, amber and red accents, UI elements that feel like a military-grade audio instrument. Not neon chaos — disciplined cyberpunk.

The glass card concept came from wanting depth without clutter. Track cards layered with gradients, subtle glowing borders, controlled visual weight. Everything hand-painted, nothing generated by a generic widget library.


Ghost Mode — Power Saving, Not Just a Look

Wavepunk has two operating modes. Ghost Mode is the one I get asked about most, and it’s more than just a minimal UI.

When you switch into Ghost Mode, several things happen simultaneously:

  • CPU frequency is reduced via cpufreq-set — drops the Pi 4B from its full 1.8GHz down to a lower governor profile. Less processing power, significantly less heat and power draw.
  • Non-essential processes are suspended — anything that doesn’t need to run for audio playback gets paused. The visualiser thread stops. The recommendation engine goes idle. UI repaints drop to minimum.
  • Display brightness is reduced — screen is one of the biggest power consumers on a portable device.
  • The UI strips back to the essentials — album art, track title, basic playback controls. Everything else fades out.

The result: meaningfully extended battery runtime in exchange for a stripped-back experience. Perfect for when you’ve got your playlist queued, headphones in, and just want music to run as long as possible.

Think of it as the equivalent of your phone’s Low Power Mode — except built specifically around audio playback, not general use.


Operative Mode — Everything Running

Operative Mode is the full experience. CPU at full clock, all threads active, complete UI rendered:

  • Beat visualiser running at 60fps
  • Track queue visible and scrollable
  • AI recommendation panel active
  • Full album art with animated transitions
  • System stats overlay available

Switching between modes is a single button press. The transition is animated — elements fade and slide, not a hard cut.


PyQt5 Custom Widgets

Standard Qt widgets look nothing like what Wavepunk needed. So I replaced them all. Every component is a custom-painted widget built using PyQt5’s QPainter API directly.

Custom paint events give me full control over every pixel — gradients, glow effects, borders, text layout, everything drawn exactly as designed. No stylesheets approximating the look. No compromises from trying to override default widget behaviour. Raw paint calls, full control.

This is more work upfront. But the result is a UI that looks and behaves exactly the same across any display resolution or DPI, because nothing is relying on the system’s default rendering.


FadingStack Transitions

One of the signature details in Wavepunk is how screens transition. Standard Qt swaps pages instantly. I built FadingStack — a custom widget stack that crossfades between views.

How it works: when switching views, the outgoing widget is rendered to a QPixmap cache. The incoming widget renders on top with decreasing opacity on a timer tick. When the animation completes, the outgoing pixmap is discarded. Runs at 60fps on the Pi 4B without frame drops — the compositing is lightweight, just alpha blending on cached bitmaps, no shaders involved.

Small detail, but it’s the kind of thing that makes the difference between software that feels like software and hardware that feels like a product.


Beat Visualiser Architecture

The real-time beat visualiser is what gets attention in videos. Here’s exactly how it works:

  • MPD pipes a copy of the audio stream to a named FIFO simultaneously with playback
  • A dedicated Python thread reads raw PCM samples from the FIFO continuously
  • Every frame, an FFT runs via numpy.fft on the latest sample window (1024 samples)
  • Frequency output is grouped into 16 bands, weighted toward bass-heavy ranges
  • Band values are smoothed with an exponential moving average to prevent jitter
  • A QTimer at 60fps triggers repaints; paintEvent draws bars scaled to current band values

Total CPU overhead on a Pi 4B: 4–6%. In Ghost Mode, the visualiser thread is fully suspended — zero overhead, saving that headroom for the audio pipeline and battery.


Performance on Pi 4B

Key lessons keeping Wavepunk smooth on constrained hardware:

  • Pre-render static elements to QPixmap cache — don’t redraw backgrounds on every paint call
  • Keep animation logic in separate threads from the main UI thread
  • Use setUpdatesEnabled(False) during batch UI changes to prevent mid-update repaints
  • In Ghost Mode, drop the visualiser, drop the recommendation polling, drop everything not directly needed for playback

The result in Operative Mode: 60fps UI, zero audio dropouts. In Ghost Mode: audio continues cleanly at reduced system load, battery runtime extended noticeably.


Next DevLog: how I taught ByteWave to understand your music taste — locally, privately, with no cloud and no ML frameworks beyond numpy.

— Gurteshwar Sandhu, Founder, IronLabs Tech

gurteshwar.sandhu
Written by
gurteshwar.sandhu

Leave a Reply

Your email address will not be published. Required fields are marked *