A streamer's audio mixer for Linux, built on PipeWire — with a KDE window, a web UI, a CLI and a Stream Deck plugin on one service.
Route every application into its own channel, build as many output mixes as you need (what you hear vs. what the stream hears vs. …), set each channel's level independently per mix, put effects on anything, and drive it from whatever is in reach — the desktop, a phone on the LAN, a shell script, a Stream Deck. Think Elgato Wave Link, for Linux, without the hardware lock-in and without the artificial limits.
Qt 6 / Kirigami · PipeWire-native · GPL-3.0-or-later · v0.4.1
There is no Wave Link / VoiceMeeter / Sonar equivalent on Linux. PipeWire has every primitive needed (virtual sinks, loopbacks, per-app routing), but the tooling stops at patchbays (qpwgraph, Helvum) and volume controls (pavucontrol). The one project that tried to fill the gap, Sonusmix, is unmaintained since 2025 and never solved independent per-mix levels (#72). ADR 0001 says why this is a new project rather than a fork.
- Channels × mixes. Channels (Game, System, Voice, …) as rows, mixes (Monitor, Stream, any number) as columns; one fader per intersection, levels that never leak between mixes. Measured on the wire, not assumed (−12.0 / +6.0 / −∞ dB, ADR 0002).
- Per-mix outputs and capture sources. Each mix plays to one or several hardware outputs and is exposed as
kmixdeck.source.<mix>for OBS, Discord, anything that records. A fallback output covers the unplugged headset. - Ports, not just devices. A 32-in/32-out interface is 32 addressable ports: any port or pair can be a channel, a
mix can go to
AUX3,AUX4, a side can be bound left-only (ADR 0009). - Effects. Ordered insert chains on channels and mixes — builtin plus LADSPA (noise suppression, gate, compressor), presets, bypass, live controls (ADR 0008).
- Named scenes. Store a whole mix state and recall it from any frontend — stream night, just recording, talkback on. Stored per name, survives a restart, and recall is one undoable graph pass. Deliberately independent of the channel set and the wiring, so a scene still fits after a rename or a repatch (CLI).
- Soundboard. Samples on pads, played by the service into the graph — so a jingle lands in the stream and in your headphones like any other source, not in a player only you can hear. Per-sample gain, one board per channel (CLI).
- Loudness per mix. EBU R128 measurement with a target line, plus a broadcast limiter that works on the summed mix instead of the individual channels — so the stream lands where it should without riding the faders.
- Devices that survive real life. Identified by stable
node.name; unplug, replug, sleep, wake — routing and levels come back without a restart. Tested with every device vanishing at once. - Live meters everywhere. Cells, apps, inputs, outputs — 25 Hz over one D-Bus signal that only runs while somebody looks (ADR 0006).
- Kill every kmixdeck process, audio keeps flowing. The graph is plain PipeWire objects from a config the service renders; PipeWire builds it at login on its own. The service is only the control plane.
- Edit the config while it runs.
layout.jsonis watched, not just read at startup: an edit by hand, by config management or by a synced copy from another machine is applied in about half a second — the graph reconciles and every frontend redraws. A half-written or corrupt file is refused and the running layout stays up, and an edit is never overwritten by a save the daemon happens to make at the same moment. - One service, N frontends. Window, tray, web UI, CLI and Stream Deck speak the same D-Bus contract; a change in one is in all the others within a frame. A feature counts as done only when every frontend has it (ADR 0010) — and a machine-checked requirements table enforces that.
Build dependencies: CMake ≥ 3.20, a C++20 compiler, Qt ≥ 6.6 (Core, Gui, Widgets, Qml, Quick, QuickControls2, Svg,
DBus), KDE Frameworks ≥ 6.8 (CoreAddons, Config, I18n, Kirigami, KirigamiAddons, QQC2DesktopStyle, IconThemes,
GlobalAccel, StatusNotifierItem, Notifications, DBusAddons, Crash), libpipewire-0.3 ≥ 1.0, libebur128 ≥ 1.2
(EBU R128 loudness metering, UX-18 — libebur128-dev on Debian/Ubuntu, libebur128 on Arch), the LADSPA header
(ladspa-sdk on Debian/Ubuntu, ladspa on Arch, ladspa-devel on Fedora). Recommended: LayerShellQt
(liblayershellqtinterface-dev on Debian/Ubuntu, layer-shell-qt on Arch) — without it the tray popover exists on X11
only, and a click on the tray under Wayland opens the window instead.
Runtime: PipeWire + WirePlumber (any distro of 2024 or later). Optional: swh-plugins/rnnoise for LADSPA effects;
python3-gi + python3-websockets for the web UI.
PipeWire 1.6.0–1.6.2 (Ubuntu 26.04 ships 1.6.2) have a filter-chain bug that crashes kmixdeckd and the PipeWire
server (#5202). On exactly those versions the build
also compiles upstream's fixed 1.6.3 module as libpipewire-module-kmixdeck-filter-chain and installs it into
PipeWire's module directory; kmixdeck uses it until your PipeWire is 1.6.3 or newer, then PipeWire's own again
(ADR 0015). The configure summary says whether it
is built. With a prefix inside $HOME, install it separately: sudo cmake --install build --component pipewire-module.
cmake -S . -B build -G Ninja -DCMAKE_BUILD_TYPE=Release
ninja -C build
sudo ninja -C build install
systemctl --user daemon-reload && systemctl --user enable --now kmixdeckd # or let D-Bus start it on first use
kmixdeck-kdeHeadless host? -DBUILD_KDE_FRONTEND=OFF builds the service, the CLI and the web bridge without the desktop
stack — no Kirigami, no KDE Frameworks beyond the basics. That is the build for a streaming box with the interface on
USB and no monitor attached; drive it from the web UI on your phone or over the CLI via SSH.
The first start offers to set up defaults (Monitor → your speakers, Voice ← your mic, running apps by role). From then on
PipeWire builds the graph at every login from ~/.config/pipewire/pipewire.conf.d/kmixdeck.conf, which the service keeps
in sync with your layout.
| Frontend | Start | Docs |
|---|---|---|
| Window + tray (Plasma) | kmixdeck-kde |
docs/window.md |
| Web UI (phone, tablet, headless box) | kmixdeck-web / kmixdeck-web --lan / systemctl --user enable --now kmixdeck-web |
docs/web.md |
| CLI (scripts, tests) | kmixdeck status, kmixdeck --json … |
docs/cli.md |
| Stream Deck (OpenDeck) | kmixdeck streamdeck install |
streamdeck/README.md |
| Your own | D-Bus org.kmixdeck1 |
docs/frontend-guide.md, contract in interfaces/ |
Thirty seconds of CLI:
kmixdeck cell set game stream -12dB # game quieter in the stream only
kmixdeck mix output stream alsa_output.usb-Focusrite_Scarlett-00.analog-stereo
kmixdeck app move firefox browser # remembered for next time firefox starts
kmixdeck export > backup.json # layout + every levelapplications ──▶ channel sinks ──(loopback = one fader per cell)──▶ mix sinks ──▶ headphones / USB / …
hardware inputs ─┘ └▶ kmixdeck.source.<mix> → OBS
kmixdeckd ── owns the graph, renders it to PipeWire config, exports org.kmixdeck1 on the session bus
▲ ▲ ▲ ▲ ▲
kmixdeck kmixdeck-kde kmixdeck-web Stream Deck your frontend
The long version with diagrams: docs/architecture.md. What it must do and how we know it does: docs/spec/requirements.md — 102 numbered requirements, 101 proven by a named test, the count written by the audit that fails the build otherwise. Why it is built this way: docs/adr/.
cmake -S . -B build -G Ninja -DCMAKE_BUILD_TYPE=Debug -DCMAKE_COMPILE_WARNING_AS_ERROR=ON && ninja -C build
pip install pytest pulsectl # integration tests
sudo apt install pipewire wireplumber pipewire-pulse ffmpeg gettext swh-plugins libebur128-dev ladspa-sdk # what the suites need
# plus Google Chrome or Chromium for the browser tests — Ubuntu 26.04 has no chromium .deb (see .github/workflows/ci.yml)
ctest --test-dir build --output-on-failure # ~32 min serial: 199 integration tests in 14 suites against a
# private PipeWire per suite, real audio measured, four frontends driven
ruff check . # Python; the C++ build is -WerrorEvery requirement row names its test; ctest -R sot-audit checks that and fails on a ✅ without proof. Read
CONTRIBUTING.md for the two rules (backend/API first; a feature is done when every frontend has it) and
the review that shaped v0.1: docs/review/2026-09-18-v0.1-review.md.
src/ kmixdeckd (service) · kmixdeck (CLI) · kmixdeck-kde (window, tray) · frontend/ (MixerClient lib) · qml/
web/ kmixdeck-web (bridge) · static/ (the browser UI, plain ES modules)
streamdeck/ OpenAction plugin for OpenDeck
interfaces/ the D-Bus introspection XML — the API contract
docs/ window.md · web.md · cli.md · architecture.md · frontend-guide.md · spec/ · adr/ · review/
tests/ QTest units · pytest integration (private PipeWire sandbox per suite) · tools/
po/ translations (English source, German)
data/ systemd user units, D-Bus service, desktop/metainfo/notifyrc
English is the source language; German ships in po/de/. sh tools/extract-messages.sh refreshes the catalogs; ctest
fails on a stale .pot or an incomplete German one.
GPL-3.0-or-later. See LICENSE.
