fw16-ledvu
fw16-ledvu is an audio visualizer for the Framework Laptop 16 LED Matrix modules that displays a real-time VU/equalizer driven from PipeWire audio via CAVA (Console Audio Visualizer). Developed by community member Jack Burch (GitHub: JackMBurch), it runs as a lightweight systemd user service on Linux and writes directly to the LED Matrix modules over persistent serial connections for low-latency updates1.
How It Works
The tool pipes audio level data from PipeWire through CAVA's raw ASCII output, processes the frequency bars with configurable gain, gamma, compression, and smoothing, and renders the result as a 9-band equalizer on the LED Matrix modules using Framework's serial protocol1.
The equalizer renders bars centered vertically on the 9x34 display — each of the 9 columns represents a frequency band, growing upward and downward from the middle row1.
Audio Modes
| Mode | Behavior |
|---|---|
mono | Single CAVA instance averages L+R channels; both modules show the same EQ |
stereo | Two CAVA instances (left channel, right channel); left module shows left audio, right module shows right audio |
Rendering Pipeline
The processing chain for each frame of audio data is1:
- CAVA capture — raw ASCII bar values (0–255) from PipeWire
- Gain — linear boost of quiet signals (
gain > 1.0) - Gamma curve — nonlinear mapping (
gamma < 1.0boosts quiet signals,> 1.0tames them) - Compression — simple threshold/ratio compressor to tame loud signals
- Noise gate (floor) — suppresses values below a configurable threshold
- Asymmetric smoothing — separate rise and fall smoothing for snappy attack, slower decay
- Peak hold — optionally holds peaks for a configurable duration for readability
- Mirroring — per-module bar order reversal (
mirror_left,mirror_right) - Serial write — persistent serial connection sends a 39-byte Draw command per module
Battery Saver
When enabled, the service detects AC/battery state via /sys/class/power_supply and turns the LED displays off while on battery, restoring them when AC power returns1.
Features
| Feature | Details |
|---|---|
| Language | Python |
| Audio backend | PipeWire (via pipewire-pulse) + CAVA |
| Rendering | Persistent serial (pyserial) with ledmatrixctl/library fallback |
| EQ bands | 9 (matching the 9-column LED matrix) |
| Stereo support | Separate L/R CAVA instances |
| Bar mirroring | Per-module configurable |
| Config hot-reload | Edits to config.ini auto-apply within ~1 second |
| Battery saver | Blank displays on battery, restore on AC |
| Wake keep-alive | Periodic wake commands prevent module sleep |
| Silence throttling | Reduced update rate during silence |
| Installation | One-command systemd user service install |
Installation
Dependencies (Arch Linux)
sudo pacman -S --needed cava python
The ledmatrixctl command from the official framework16-inputmodule Python package must also be available (used as fallback if pyserial is not present)12.
Install as systemd user service
Clone and run the install script1:
git clone https://github.com/JackMBurch/fw16-ledvu.git
cd fw16-ledvu
bash ./install.sh
To use a specific Python interpreter (e.g., from a venv with framework16-inputmodule)1:
FW16_LED_VU_PYTHON=/path/to/venv/bin/python bash ./install.sh
The install script creates config.ini from config.example.ini (if missing), generates a systemd user unit at ~/.config/systemd/user/fw16-ledvu.service, and enables/starts it immediately1.
Configuration
All settings live in config.ini alongside the repo. Edits are auto-applied by the running service without restart1.
Device paths
Device symlinks are resolved via readlink -f before opening serial connections. This works well with persistent udev symlinks1:
[devices]
left = /dev/fw16-ledmatrix-left
right = /dev/fw16-ledmatrix-right
Equalizer tuning
| Setting | Default | Description |
|---|---|---|
bars | 9 | Number of EQ bands |
max_value | 34 | Maximum bar height (must be ≤ 34, the module height) |
update_hz | 50 | Target update rate in Hz |
gain | 1.0 | Linear gain boost |
gamma | 1.2 | Power curve exponent |
compress_threshold | 1.0 | Normalized threshold (1.0 = disabled) |
compress_ratio | 1.0 | Compression ratio (1.0 = disabled) |
rise_smoothing | 0 | Smoothing for rising values (0 = instant) |
fall_smoothing | 0.25 | Smoothing for falling values |
floor | 0 | Noise gate threshold |
peak_hold_ms | 0 | Peak hold duration (0 = disabled) |
silence_floor | 2 | Threshold to consider audio silent |
silent_update_hz | 2 | Reduced update rate during silence |
brightness | 5 | LED brightness (0–255) |
mirror_left | true | Reverse bar order on left module |
mirror_right | false | Reverse bar order on right module |
Technical Details
| Detail | Value |
|---|---|
| License | No license file (all rights reserved) |
| Serial protocol | Framework magic bytes 0x32 0xAC + command byte + payload |
| Draw command | 0x06 + 39-byte bit-packed 9x34 B&W bitmap |
| Serial baud rate | 115200 |
| CAVA output | Raw ASCII, ; bar delimiter, @ frame delimiter |
| Config format | INI (Python configparser) |
The fast rendering path uses persistent pyserial connections to avoid the latency of reopening /dev/ttyACM* every frame. It falls back to the framework16-inputmodule Python library, and finally to spawning ledmatrixctl subprocess calls1.
Related Projects
| Project | Description |
|---|---|
| inputmodule-rs | Official Framework firmware + CLI + Python tools2 |
| led-matrix-manager | Qt GUI for LED matrix management |
| fw16-led-matrixd | Cross-platform Rust daemon with image rendering and pair mode |
| CAVA | Console-based audio visualizer for Linux (used as audio source) |