Psst... Lumi the AI assistant says hi! You're awesome for using accessibility tools.Skip to content
luinbytes.dev
← Back to projects
CLI ToolOpen SourceLinux

linux-sonar.

SteelSeries Sonar doesn't exist on Linux. So I built it.

Five virtual audio channels, per-app routing enforced by a daemon, a ChatMix slider with hardware wheel support, and a full mic effects chain (RNNoise, gate, EQ, compressor, limiter) running as an isolated PipeWire filter-chain. GTK4 GUI, waybar integration, no proprietary software anywhere in the path.

Channels5
Mic stages5
Daemon poll1.5s
LicenseFOSS
01 / Channels

Five sinks. One headset.

Each channel is its own PipeWire virtual sink, pinned to the headset via target.object. Apps are routed in by name; the daemon enforces routing every 1.5s so launches, restarts, and streaming sessions don't leak across channels.

ChannelWhat lands here
Game
Default destination for game processes. Routed by daemon polling against per-app rules so launches and respawns don't escape the channel.
Chat
Voice clients (Discord, Equibop, TeamSpeak) routed here. ChatMix slider balances this against the Game channel against the headset.
Media
Browsers, music players, video. Anything that's not a game and not a voice client lands here by default.
Aux
Free slot — bind it to whatever you want. Useful for OBS monitoring, secondary mixes, or apps you want isolated from the rest.
Mic
Output of the mic effects chain. RNNoise → gate → 8-band EQ → compressor → limiter, all running as a PipeWire filter-chain subprocess.
02 / Features

What it actually does

Sonar feature parity, no proprietary software, no kernel modules, stock PipeWire on Arch.

01

Per-app routing

Five virtual sinks pinned to your headset via target.object. A small daemon polls wpctl/pactl every 1.5s and enforces per-app routing rules, so apps stay where they belong even after relaunches.

  • libpipewire-module-filter-chain virtual sinks, one per channel
  • WirePlumber persists app→channel mappings across reboots
  • Daemon catches sink-input regressions inside ~1.5s
02

ChatMix

Software slider rebalances Game against Chat in real time. Hardware ChatMix wheel works too — read straight off the device over USB-HID, no SteelSeries software in the loop.

  • Slider driven from the GTK4 GUI or your waybar module
  • Hardware wheel via USB-HID — direct read from the headset
  • Game and Chat volumes scale inversely from a single input
03

Mic effects chain

RNNoise, noise gate, 8-band EQ, compressor, and limiter, in that order. Runs as an isolated pipewire filter-chain subprocess so a bad config can't take the whole graph with it.

  • Static-stereo capture.props avoids RnNoiseStereo SEGV on mic swap
  • Service-restart swap pattern — no live-relink edge cases
  • Each stage tuneable independently in the GUI
04

GTK4 GUI + waybar

libadwaita panel for live tuning of every channel and the mic chain. Optional waybar module exposes ChatMix and per-channel volumes on the bar — no GUI needed for hot-path adjustments.

  • Native GTK4 / libadwaita — fits Hyprland and other Wayland setups
  • Waybar module ships per-channel volumes + ChatMix
  • GUI stays optional; daemon and CLI work standalone
03 / Mic Chain

Five stages, one filter-chain subprocess

The mic effects pipeline runs as an isolated libpipewire-module-filter-chain subprocess. Each stage is independently tuneable. Live re-link on mono ↔ stereo audioconvert renegotiation used to SEGV the RnNoise stage — fixed by static-stereo capture props and a service-restart swap pattern instead of in-place relinks.

01
RNNoise

ML-based noise suppression — strips fan, keyboard, and ambient noise

02
Gate

Closes when below threshold so room tone never makes it onto the wire

03
8-band EQ

Per-band shelf and peak filters — clean up the mic curve to taste

04
Compressor

Even out levels between quiet and loud delivery without losing dynamics

05
Limiter

Hard ceiling so the chain can't overshoot and clip downstream

04 / Tech

How it's built

Python

Daemon, GUI, and tooling

PipeWire

filter-chain modules for the five virtual sinks and the mic chain

WirePlumber

Persists per-app routing decisions across reboots

GTK4 / libadwaita

Native Wayland-friendly GUI

RNNoise

ML-based noise suppression as a PipeWire filter

USB-HID

Direct read of hardware ChatMix wheel

Routing daemon

A small Python daemon polls wpctl and pactl on a ~1.5s cadence. When a sink-input doesn't match its expected channel (because the app just launched, restarted, or spawned a child stream), the daemon moves it. WirePlumber persists those moves so they survive reboots without rerunning setup.

Compatibility

Tested on Arch with stock PipeWire and WirePlumber. Works with any stereo output sink — analog headphone jack, USB headset, HDMI audio, Bluetooth A2DP. EasyEffects' output pipeline must be disabled for per-channel routing to take effect (its global capture sits in front of the per-app sinks).

05 / Setup

Getting it running

01

Install dependencies

PipeWire, WirePlumber, GTK4, libadwaita, and Python 3.11+. On Arch: pacman -S pipewire wireplumber gtk4 libadwaita python.

02

Clone and run setup

Clone the repo, run the install script. It drops the filter-chain configs into ~/.config/pipewire/pipewire.conf.d/ and seeds the routing daemon's app→channel rules.

03

Disable EasyEffects output

If you run EasyEffects, turn off its output pipeline — it captures everything before the per-channel sinks can route it. The mic side is fine; only the output stage conflicts.

04

Launch the GUI

Open the GTK4 panel to verify all five sinks show up and to tune the mic chain. Add the waybar module if you want ChatMix and per-channel volumes on your bar.

06 / Source

Get linux-sonar

FOSS under GPL-3.0. Clone, hack, and route. Issues and PRs welcome.