Blend any audio signals on Linux into one virtual microphone.
Browsers, and so every web call (Meet, Jitsi, Tutorful, Discord in the browser), let a site pick exactly one microphone. An audio interface shows up as several separate inputs, and an amp-sim guitar is not on any of them: the dry signal comes in on one input, but the actual tone only exists inside the DAW after its plugins. If you want the other end of the call to hear your voice and your guitar, you are stuck.
OneMic fixes that. It creates a virtual microphone, feeds as many signals into it as you like (a mic, your DAW, a backing track playing in the browser), gives each one its own volume and mute, and shows you exactly what the call is hearing. Pick the OneMic device in your call and you are done.
It started life as a small shell script (call-mic) for guitar lessons. OneMic is the grown-up version:
named mics you can save and switch between, live waveforms, and it keeps everything linked as devices
and applications come and go.
| Feature | What it does |
|---|---|
| Named virtual mics | Each mic is a real PipeWire device, listed in call apps as <name> (OneMic). Save as many as you like and switch between them. |
| Up to 8 inputs per mic | Any microphone or interface input, any application playing audio (REAPER, a browser), or anything a speaker is playing. |
| Volume, mute and solo per input | Set each signal without touching the source itself, so REAPER still records and monitors exactly as before. |
| Master volume and mute | For the mix as a whole, which is what the call hears. |
| Live waveforms and meters | A smoothly scrolling waveform and a DAW-style level meter for each input and for the mix. Ice blue when fine, yellow when running hot, red where it clipped. |
| Peak readout with hold | The highest recent peak in dBFS next to each waveform, held long enough to read. |
| Noise gate and low-cut | Per input. A 100 Hz low-cut removes rumble, and a gate silences an input below a threshold you drag on its meter. |
| Listen | Hear exactly what the mic is sending, through your default output. Before going live, hear your inputs as they will be sent. |
| Auto-relink | Opened REAPER after going live? Replugged the interface? OneMic links it back within a second. |
| Small and always on top | Sits in a screen corner, shows two inputs and the mix, and expands to show everything. |
| Remembers its place | Drag it to any corner of any screen and it snaps there, and reopens there next time. |
| Safe closing | Closing while live asks whether to keep the mic running for your call or stop it. |
The expanded view, with every input and the add button:
Nothing in OneMic touches audio samples on the way to the call. It only builds and maintains a small piece of the PipeWire graph, so the audio path is exactly as fast and as reliable as PipeWire itself:
voice mic ────► gain stage ──┐
REAPER out 1/2 ► gain stage ──┼──► OneMic virtual mic ──► your call
backing track ─► gain stage ──┘ │
└──► default output (only while Listen is on)
gain stage = low-cut ► volume, mute and solo ► noise gate
| Piece | Built with | Why |
|---|---|---|
| Virtual mic | pactl load-module module-null-sink with the Audio/Source/Virtual class |
The simplest virtual microphone PipeWire offers. It survives OneMic closing. |
| Gain stages | one PipeWire filter chain process per input | A link has no volume or processing of its own. A filter chain sits between the source and the mic, so each input gets a low-cut, volume and gate without changing the source for other applications. Controls change live through pw-cli, so nothing restarts when you move them. |
| Links | pw-link, planned from pw-dump snapshots |
OneMic compares the graph with what the mic should look like once a second, links anything missing and removes anything stale. |
| Levels | wpctl set-volume and set-mute |
The same volume scale as the desktop's own mixer. |
| Waveforms | one pw-record tap per visible signal |
Metering is only another link, so it can never change or interrupt what the call hears. |
The links are copies. Your DAW still plays to your headphones, your interface inputs still feed the DAW, and nothing is recorded any differently.
OneMic runs on Linux with PipeWire only. Every part of it talks to PipeWire through the tools that
ship with it (pipewire, pw-dump, pw-link, pw-cli, pw-record) plus pactl from pipewire-pulse and
wpctl from WirePlumber. That covers every current desktop distribution using PipeWire, which is most of
them. It will not work on a PulseAudio-only or JACK-only system.
It is developed and tested on Arch Linux with KDE Plasma on X11. It has not been tested on Wayland. The audio side does not care, but Wayland does not let applications position their own windows, so dragging and corner snapping will not work there.
- Linux with PipeWire,
pipewire-pulseand WirePlumber - Python 3.12 or newer
pyside66.11 or newerpython-numpy- Optional:
lsp-plugins-lv2, for the noise gate
On Arch Linux:
sudo pacman -S pipewire pipewire-pulse wireplumber pyside6 python-numpy python-pipx lsp-plugins-lv2PipeWire has a built-in noise gate, but the one in current releases measures its own output, so once it
closes it never opens again. OneMic uses the LSP gate instead. Without lsp-plugins-lv2 everything else
works, and the gate button is greyed out with a note saying what to install.
If you use a DAW through JACK, install pipewire-jack as well so it appears in the PipeWire graph.
Download the wheel (onemic-<version>-py3-none-any.whl) from the
releases page, then:
pipx install --system-site-packages onemic-1.0.0-py3-none-any.whl--system-site-packages reuses the distribution's PySide6 and NumPy, which are large and already
installed. To upgrade later, install the newer wheel with pipx install --force.
Clone the repository and install it in editable mode:
git clone https://github.com/VTech-Studios/OneMic.git onemic
cd onemic
pipx install --system-site-packages --editable .--editable makes the installed command run the cloned folder, so pulling changes updates it without
reinstalling. This is the one to use while working on OneMic itself.
To turn the source into the same files a release ships, install the build tool and run it from the repository:
python -m venv --system-site-packages .venv
.venv/bin/pip install build
.venv/bin/python -m buildThis creates two files in dist/:
| File | What it is |
|---|---|
onemic-1.0.0-py3-none-any.whl |
The wheel: a ready-to-install package. Install it with pipx as above. |
onemic-1.0.0.tar.gz |
The source distribution: the source code as released, for packagers. |
A wheel built from a checkout of a release tag is identical to the one on the releases page, so this is
also how to get a specific version without downloading anything. To switch an editable install over to
your built wheel, run pipx install --force --system-site-packages dist/onemic-1.0.0-py3-none-any.whl.
pipx puts an onemic launcher in ~/.local/bin. If command -v onemic finds nothing, run
pipx ensurepath and open a new terminal.
To add OneMic to the application menu:
install -Dm644 /dev/stdin ~/.local/share/applications/onemic.desktop <<'EOF'
[Desktop Entry]
Type=Application
Name=OneMic
Comment=Blend audio signals into one virtual microphone
Exec=onemic
Icon=audio-input-microphone
Categories=AudioVideo;Audio;Mixer;
EOFWithout pipx, you can run it straight from the source folder with python -m onemic.
- Start OneMic. It opens in the bottom-right corner with an empty mic called My Mic.
- Click Add input…, pick a source, and repeat for each signal you want. Sources are grouped into microphones and inputs, applications, and speakers (what they are playing).
- Check the waveforms. Before going live, each input's waveform and Listen show it exactly as it will be sent, so you can set your interface gain and spot a dead input before anything reaches a call.
- Click GO LIVE. The button turns red and reads ● LIVE, and the window gets a red border. From here each input's waveform shows it after its volume and mute, and the mix row shows what the call hears.
- In your call, choose My Mic (OneMic) as the microphone.
To give the mic a better name, open the ⋮ menu, choose Manage mics…, and rename it. Call apps
show whatever name you pick, so something like Guitar Lesson is easy to find in a device list.
Each input row has a mute button (M), a solo button (S), a volume slider from 0 to 150%, the held peak in dBFS, the waveform, and a thin level meter under it. The bottom row is the mix, which is exactly what the call hears.
Solo works as it does in a DAW: while any input is soloed, only soloed inputs are heard, and mute still wins over solo. Because the mix is what the call hears, solo applies to the call too, so it is a quick way to send only your guitar for a moment. Solo is saved with the mic, like mute.
The meter is the live part: it jumps the instant a sound starts and falls back smoothly, with a white tick marking the recent peak, just like a DAW's track meter. The waveform shows the last few seconds scrolling past. As in REAPER, the waveform is linear, so quiet room noise stays close to the centre line and only real playing or speech makes a shape, while the meter uses a decibel scale from -60 dBFS to full scale, so you can still see how loud that room noise is.
| Colour | Peak | Meaning |
|---|---|---|
| Ice blue | below -6 dBFS | Fine. |
| Yellow | -6 dBFS and above | Running hot. The far end's browser adds its own gain on top, so back it off. |
| Red | -0.1 dBFS and above | Clipping. The call will hear distortion. |
Each column of the waveform is coloured by its own level, so a clip shows red exactly where it happened while the rest stays blue. Aim for a mix peaking around -10 dBFS. If a DAW is involved, pulling its master fader down about 10 dB (and turning up your headphones instead) is usually all it takes.
Each input has two small buttons next to its level meter:
- LC switches on a 100 Hz low-cut, which removes rumble from fans, desks and floors without touching a voice or a guitar.
- G switches on a noise gate. Its threshold appears as a white marker on the input's meter. Drag it to just above where the meter sits when you are silent, and the input goes quiet between phrases and opens the moment you speak or play. It opens in 5 ms, holds for 50 ms and closes over 150 ms, so it does not chop a guitar's sustain.
Both run inside the input's gain stage, after the volume, so the threshold is compared with exactly the level the meter shows. Set them up before going live, while the waveforms preview each input.
The headphones button plays through your default output. Once live it plays exactly what the call is getting. Before going live it plays your inputs as they will be sent, with their volume, mute, solo, low-cut and gate, so you can set everything up by ear first. If your interface or DAW already sends a signal to the same headphones (the 2i2's direct monitor, say), you will hear it twice while this is on, which is expected. Listen is disabled if your default output is one of the mic's own inputs, because the mic would then hear itself and howl.
The window normally shows the first two inputs and the mix, which is enough to keep an eye on things during a call. The arrow button expands it to show every input, along with each row's remove button and Add input…. Only visible rows are metered, so the compact view also costs less.
Drag the window by any empty area and let go. It snaps to the nearest corner of that screen, and next time it opens in the same corner of the same screen. Expanding grows the window away from its corner, so it never goes off screen.
Every mic keeps its own inputs and levels, and every change is saved as you make it. Switch mics from the drop-down in the header. If a mic is live when you switch, the one you pick goes live in its place: only one OneMic mic is live at a time, so a call app never shows two half-built devices.
Manage mics… creates, copies, renames and deletes mics. The live mic cannot be renamed or deleted, because its name is part of every PipeWire node it owns and changing it would drop the call.
If nothing is live, closing just closes. If a mic is live, OneMic asks:
- Keep live leaves the mic running for your call. Levels stay as they are. Listen is switched off, because there would be no way to switch it off again with the window closed.
- Stop mic removes the mic and its gain stages.
Opening OneMic again finds a mic that was left live and picks up where it left off.
- Choose the device. Most call apps list it as
<name> (OneMic). In a browser, change it from the site's settings or from the camera and mic icon in the address bar. - Turn off noise suppression and automatic gain in the call app if it allows. They are tuned for speech and will chop the sustain off a guitar.
- Test before the call. meet.jit.si with your phone as the second participant is a quick way to hear what the other end gets.
| File | Contents |
|---|---|
~/.config/onemic/mics.json |
Every mic, its inputs and levels, and which one was last selected. |
~/.config/onemic/window.json |
The window's corner, screen and whether it was expanded. |
Both are plain JSON and safe to edit by hand while OneMic is closed. If a file cannot be read, OneMic
moves it aside to *.broken rather than overwriting it, and starts fresh.
An input says it is waiting for its source. The source is not in the PipeWire graph right now, usually because the application is closed or the device is unplugged. Open or plug it in and OneMic links it within a second.
The call hears nothing. Check the call app is using the OneMic device, the mix row is not muted, and the mix waveform is moving. If the mix moves but the call is silent, the call app is using a different microphone.
A source is missing from Add input. Only nodes with audio outputs are listed. A DAW has to be
running, and a JACK application needs pipewire-jack.
See what OneMic is doing. Run it from a terminal with onemic --verbose to log every link change.
qpwgraph shows the whole graph, where OneMic's nodes all start with onemic..
Remove a mic by hand. If OneMic is not running and a mic is still live, pactl list short modules | grep onemic shows the module to pactl unload-module, and pkill -f 'onemic/stages/' stops the gain
stages. Restarting PipeWire also clears everything.
Create a virtual environment that can see the system's PySide6, and install the development tools:
python -m venv --system-site-packages .venv
.venv/bin/pip install -e '.[dev]'Then:
.venv/bin/ruff check onemic tests # lint
.venv/bin/ruff format onemic tests # format
.venv/bin/mypy # strict type checking
.venv/bin/pytest --cov # tests with coverageThe tests never touch the real audio graph. Session tests run against FakePipeWire in
tests/fakes.py, an in-memory graph that creates nodes and ports, removes them when their process
stops, and records links, so they read like real use. Interface tests use pytest-qt with Qt's
offscreen platform, which is also what CI uses.
Two GitHub Actions workflows live in .github/workflows:
ci.ymlruns the lint, type and test checks on every push tomasterand on every pull request.release.ymlruns when a version tag is pushed. It runs the same checks, confirms the tag matches the package version, builds the wheel and source distribution, and publishes a GitHub release with that version's notes fromCHANGELOG.mdand both files attached.
A release is a git tag plus a GitHub release page with downloadable files. The release workflow does the building and publishing, so a release only needs the version, the changelog and a tag.
-
Choose the new version using Semantic Versioning: bump the patch number (
1.0.1) for fixes, the minor number (1.1.0) for new features, and the major number (2.0.0) for changes that break how people use it, such as a settings file an older version cannot read. -
Set
__version__inonemic/__init__.py. It is the only place the version is written, andpyproject.tomlreads it from there. -
In
CHANGELOG.md, move the entries under[Unreleased]into a new## [x.y.z] - YYYY-MM-DDsection and update the links at the bottom. -
Check the release is consistent, build it locally to be sure it builds, then commit:
.venv/bin/python scripts/release_notes.py --check-tag vX.Y.Z .venv/bin/python scripts/release_notes.py vX.Y.Z .venv/bin/python -m build git commit -am "Release X.Y.Z" -
Tag the commit and push both:
git tag -a vX.Y.Z -m "OneMic X.Y.Z" git push origin master vX.Y.Z
The workflow then publishes the release. If the tag and version disagree, or the changelog has no section for the version, it stops before anything is published.
The code follows a ports and adapters layout. Dependencies only point inwards:
| Layer | Folder | Responsibility |
|---|---|---|
| Domain | onemic/domain |
Plain data and rules: the graph snapshot, mics and inputs, node naming, levels, waveforms. No I/O. |
| Ports | onemic/ports.py |
Protocols for everything outside the process: commands, PipeWire, stores. |
| Services | onemic/services |
The session that keeps a mic wired and levelled, the supervisor that starts and stops its nodes, the routing planner, metering, the mic library, and the background worker. |
| Adapters | onemic/infrastructure |
The only code that runs PipeWire's tools or reads and writes files. |
| Interface | onemic/ui |
Qt widgets that show state and report intent, and the controller that connects them to the services. |
| Composition root | onemic/wiring.py |
The one place that picks concrete implementations. |
A few decisions worth knowing before changing things:
- The graph is the state. Every node name is derived from the mic's name, so OneMic keeps no record of what it started. The session reads the graph, compares it with the saved mic, and converges on it. The same code builds a mic, repairs it, and adopts one left live by an earlier run.
- All graph work runs on one background thread. Reading the graph takes tens of milliseconds, which would stutter the waveforms. Jobs of the same kind replace each other while queued, so dragging a slider can never build up a backlog.
- Old answers are ignored. Each click starts a new generation, and a routine repair that was already running when you clicked cannot report back over it and flip the controls to a state you just left.
- Failures undo themselves. A mic that fails part-way through going live is removed again, and a failed listen change keeps the old setting, so the controls always match what is really running.
- OneMic only ever stops its own gain stages. They run as the
pipewireprogram, the same as the audio server, so a process id from the graph is only signalled after/procconfirms both the program and that its command line carries OneMic's stage directory. - A call app recording the mic is never unlinked. Routing only manages links into OneMic's own nodes, out of its gain stages, and from the mic to the outputs OneMic itself played it through for Listen. A link you make by hand from the mic to anything else is left alone.
MIT. See LICENSE.

