A Voicemeeter-style virtual audio mixer for PipeWire, driven by a MIDI control surface. Route each application to its own channel, then control those channels with real faders and buttons.
- Virtual channels — sinks that applications play into (System, Game, Music, Browser, Chat, or whatever you name them), each with its own fader, mute, solo and per-output routing.
- Inputs — a hardware microphone is published back as a virtual capture device, so Discord and OBS pick up a mic with this mixer's volume and mute already applied.
- Physical outputs — send any channel to any combination of real output devices, each with its own fader, mute and solo.
- MIDI control — Learn any fader or button on a control surface, with LED feedback on boards that support it.
- Application routing — assign running applications to channels from inside the app; the choice is remembered next time they start.
The mixer runs as a background service, because a control surface is useless if the software behind it is not running:
- It starts with your graphical session and is restarted automatically if it ever stops.
- Launching it from the menu shows the window of the already-running instance rather than starting a second copy.
- Closing the window does not stop it. MIDI handling, routing and the link watchdog keep going; only the meters stop, so an idle background instance costs almost nothing.
System packages (not installable from PyPI in any sane way):
| Arch | Debian/Ubuntu | |
|---|---|---|
| GTK bindings | python-gobject gtk3 |
python3-gi gir1.2-gtk-3.0 |
| PipeWire tools | pipewire |
pipewire-bin |
| PulseAudio tools | libpulse |
pulseaudio-utils |
Python dependencies (python-rtmidi, numpy) are installed automatically.
./packaging/install-user.shInstalls into ~/.local, registers the desktop entry and icon, then enables and
starts the background service.
cd packaging && makepkg -si
systemctl --user enable --now pipeworkspipeworks # show the window (or reveal the running instance)
pipeworks --daemon # background only, no window (what the service runs)Service management:
systemctl --user status pipeworks
systemctl --user restart pipeworks
journalctl --user -u pipeworks -f~/.config/pipeworks/config.json holds channels, routing and MIDI bindings, and
is written whenever something changes in the UI.
Virtual devices themselves are declared in
~/.config/pipewire/pipewire.conf.d/10-virtual-channels.conf, regenerated by
the app when channels or inputs are added or removed. Because PipeWire reads
that file at startup, those two operations restart PipeWire and briefly
interrupt audio; adding an output does not.
pipeworks/
settings.py paths and tunables
config.py schema, migration, persistence
runner.py the one place external commands are executed
autostart.py systemd unit / desktop entry strategies
service.py the always-on part: mixer, MIDI, link watchdog
application.py GTK application lifecycle (resident, single instance)
audio/ domain: mixing rules, metering, backend abstraction
pipewire/ PipeWire implementation of that abstraction
midi/ control-surface bindings and I/O
ui/ GTK presentation
The domain layer depends on an AudioBackend protocol rather than on PipeWire,
so the mixing rules can be exercised with fakes and no audio server present.