Skip to content

Latest commit

 

History

5 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

🎙️ Vocalis

Broadcast‑radio voice for your microphone, on pure PipeWire

Point it at your microphone and it publishes a second, processed one that every app can record from — gated, compressed, de‑essed, EQ'd and limited. Hear yourself while you turn the knobs, then close the window: it keeps working from the tray.

PipeWire GTK4 Rust Flatpak No plug-in packs License: GPL-3.0-or-later

Flatpak

Vocalis


✨ What it does

  • 🎚️ A second microphone, not a setting. Vocalis publishes a real Audio/Source named Vocalis (processed mic). Discord, OBS, Zoom, your browser — anything that lists microphones lists this one, and picks up the processing with no per‑app configuration.
  • 🎧 Hear yourself while you tune. The monitor tap sits after the limiter and the output gain, so what lands in your headphones is byte‑for‑byte what the other side receives. Monitor level changes your playback only; it never touches the signal apps record.
  • 🚪 Noise gate. Threshold, range, attack, hold and release, with hysteresis and a hold timer so the tail of a word never gets chopped.
  • 🎛️ Compressor. Soft knee, ratio, attack, release and make‑up gain — the part that makes a voice sound close instead of merely loud.
  • 🐍 De‑esser that doesn't dull you. It splits the band, ducks only the sibilance and adds the rest back untouched: low + gain × high, which reconstructs the input exactly at 0 dB of reduction, so at rest it is audibly transparent.
  • 🎼 Three‑band EQ. Low shelf, parametric mid with adjustable Q, high shelf.
  • 🧱 Peak limiter. Adjustable ceiling, so a laugh or a slammed desk never clips the stream.
  • 🧩 No plug‑in packs. Every filter is implemented inside Vocalis. It does not load LADSPA, LV2 or Calf/LSP bundles, and there is no second audio daemon: the only thing underneath it is the PipeWire you already run.
  • 🎨 Presets that tell you where you are. The header button always names the preset in effect — or Custom the moment you move a knob. Open it and the active one is ticked, Broadcast is labelled Default. Monitoring, the master switch and the input/output gains are your rig, not the preset, so loading one never silences your headphones or undoes your mic calibration.
  • 📊 Live meters. Input and output levels plus per‑stage gain reduction for the gate, the compressor and the de‑esser, so you can see which stage is working.
  • 🖥️ Monochrome tray icon. Closing the window hides it — the processed mic keeps running. The tray toggles processing and monitoring, reopens the window, and quits.
  • 🚀 Start with the system. One switch and Vocalis launches --hidden at login: it is in the tray with the mic already processed, no window in your face. Under Flatpak this goes through the desktop's background portal, so your desktop asks you once and stays in charge of the permission.
  • 🔒 Exactly one instance. Launching it again re‑presents the existing window instead of publishing a second virtual microphone.
  • 📦 Flatpak‑first. Sandboxed, no host packages, no sudo.

Works great with: Discord, OBS, Zoom/Meet/Teams, browser calls, recording into Audacity or Reaper — anything that lets you choose an input device.


🎥 The signal chain

flowchart LR
    MIC["🎤 Your mic<br/>(any PipeWire source)"] --> HPF["High-pass"]
    HPF --> GATE["Noise gate"]
    GATE --> COMP["Compressor"]
    COMP --> DEESS["De-esser"]
    DEESS --> EQ["3-band EQ"]
    EQ --> LIM["Limiter"]
    LIM --> SRC["🎚️ Vocalis<br/>(processed mic)"]
    LIM --> MON["🎧 Monitor<br/>your headphones"]
    SRC --> APPS["💬 Discord · OBS · Zoom · browser"]
    style SRC fill:#1d99f3,stroke:#0b76c4,color:#fff
    style MON fill:#2ea043,stroke:#1a7f37,color:#fff
    style MIC fill:#2a2e32,stroke:#444,color:#eee
    style APPS fill:#2a2e32,stroke:#444,color:#eee
Loading

🤔 Why?

Logitech's Blue VO!CE does this on Windows and has no Linux counterpart. The usual answer here is EasyEffects plus a pile of LSP/Calf plug‑ins — a second application to keep running, a plug‑in ecosystem to install, and a graph to understand before you can make your voice sound better.

The reason people reach for those plug‑ins is that PipeWire's own filter‑chain cannot do this alone: as of 1.6 its built‑ins cover biquads, a parametric EQ, a convolver, a delay and a noisegate — but no compressor, no de‑esser and no limiter, which are exactly the stages that make a voice sound broadcast‑ready.

So Vocalis brings its own. The DSP is a few hundred lines of Rust inside the app, which is what lets the whole thing be one sandboxed program on top of stock PipeWire, with a slider for every parameter and no graph to wire up.


📦 Installation

Option A — the pre‑built bundle (recommended)

Every push builds an installable .flatpak bundle in CI. Grab it from the latest release, or straight from a workflow run with the GitHub CLI:

gh run download --repo geodro/vocalis --name vocalis-flatpak
flatpak install --user --bundle vocalis.flatpak
flatpak run co.dumitres.Vocalis

No sudo, and it brings its own runtime.

Option B — build the Flatpak yourself

git clone https://github.com/geodro/vocalis.git
cd vocalis
flatpak install --user -y flathub org.gnome.Sdk//49 \
  org.freedesktop.Sdk.Extension.rust-stable//25.08 \
  org.freedesktop.Sdk.Extension.llvm20//25.08
flatpak run org.flatpak.Builder --force-clean --user --install build-dir co.dumitres.Vocalis.json
flatpak run co.dumitres.Vocalis

Option C — run it from the source tree

cargo build --release
./target/release/vocalis

Needs the libpipewire-0.3, gtk4 and libadwaita development packages, plus libclang for the PipeWire bindings.

Uninstall

flatpak uninstall --user co.dumitres.Vocalis
rm -f ~/.config/autostart/co.dumitres.Vocalis.desktop
rm -rf ~/.var/app/co.dumitres.Vocalis    # or ~/.config/vocalis when run from source

⚙️ Using it

Pick your real microphone at the top, switch Hear myself on, and turn knobs until it sounds right. Then select Vocalis (processed mic) as the input in whatever app you are talking through.

Section What to reach for
Input The raw device to listen to, input gain, and the autostart switch. System default follows whatever the desktop calls the default microphone.
Monitor Hear myself and its level. Playback only — it never changes what other apps receive.
High‑pass filter Rumble, desk thumps, air conditioning. 80–120 Hz suits most voices.
Noise gate Threshold just above your room noise; raise Hold if word endings get clipped; Range is how far a closed gate ducks, not a hard mute.
Compressor Lower Threshold / raise Ratio for a denser voice; Knee softens the onset; Make‑up brings the level back. Watch its gain‑reduction readout — 4–8 dB on peaks is a good place to live.
De‑esser Frequency around 5–8 kHz, then lower Threshold until the readout only moves on "s" sounds.
Equaliser Low shelf for weight, mid for presence or to notch boxiness, high shelf for air.
Limiter Leave it on. -1 dB ceiling is a safe default.
Output Final gain, and the output meter that tells you what apps actually get.

Presets live behind the header button; Broadcast is the default. The switch at the top‑right bypasses the whole chain, so you can A/B against your raw mic instantly.

Settings and the chosen microphone are saved on close and restored next launch.


🧠 How it works

Vocalis is a PipeWire client, not a server module — which is what lets it live in a sandbox. It runs its own main loop on a dedicated thread and owns three streams: a capture stream on your real microphone, an Audio/Source node other apps record from, and a playback stream for monitoring.

The capture callback does the DSP in place and pushes the result into two lock‑free ring buffers, which the source and monitor callbacks drain. Each drain throws away a backlog before reading, so a stretch where nobody was recording cannot turn into permanent delay. Capture and source share a node.link-group: that is what stops the session manager from ever linking Vocalis's own output back into its input when the processed mic becomes your default one.

Parameters cross from the GUI to the audio thread as plain atomics — no locks, no allocation, nothing that can block a realtime callback. The chain snapshots them once per block and only recomputes filter coefficients when something actually changed.

The chain itself, in signal order: high‑pass biquad → gate (peak follower with hysteresis and a hold counter) → feed‑forward compressor with a soft‑knee curve in the log domain → de‑esser splitting at an adjustable frequency and recombining as low + gain × high → low shelf, peaking mid and high shelf → output gain → peak limiter with a fast attack and a hard ceiling.

The tray icon is a StatusNotifierItem. Inside Flatpak it deliberately runs without owning a well‑known D‑Bus name, which is the documented workaround for sandboxed SNI clients.


🩹 Troubleshooting

  • Vocalis isn't in my app's microphone list. It only exists while Vocalis is running. Confirm with pactl list sources short | grep vocalis. Some apps cache their device list — reopen their audio settings, or restart them once.
  • I can't hear myself. Hear myself has to be on, and the monitor follows your default output — if your headphones aren't the default sink, the sound is going somewhere else. Check with wpctl status.
  • It sounds gated off / too quiet. The gate threshold is above your voice. Lower it until the gate's gain‑reduction readout sits at 0.0 dB while you talk.
  • The tray icon is missing. Your desktop needs a StatusNotifier host. KDE Plasma has one built in; on GNOME install the AppIndicator extension. Vocalis still runs fine without one — it just cannot be reopened from the tray.
  • Autostart didn't stick. Under Flatpak the switch asks the background portal, which is allowed to say no — if it flips itself back off, the desktop refused it (GNOME lists it under Settings → Apps → Vocalis → Background). Run from source it writes ~/.config/autostart/co.dumitres.Vocalis.desktop, and any error is on stderr.
  • Nothing at all is running. pw-cli info 0 has to work first — everything here assumes a live PipeWire session.

🛠️ Development

cargo test                                # DSP and preset behaviour
python3 build-aux/gen-cargo-sources.py    # after any dependency change

build-aux/gen-cargo-sources.py regenerates the vendored crate list for Flatpak straight from Cargo.lock; it needs no network, because every checksum is already in the lock file. CI fails if the generated file is stale.

Before submitting to Flathub, check it the way Flathub does:

flatpak run --command=flatpak-builder-lint org.flatpak.Builder manifest co.dumitres.Vocalis.json
flatpak run --command=flatpak-builder-lint org.flatpak.Builder appstream data/co.dumitres.Vocalis.metainfo.xml
flatpak run --command=flatpak-builder-lint org.flatpak.Builder builddir build-dir

🙏 Credits

Inspired by Logitech's Blue VO!CE, and built on the work of the PipeWire project and the pipewire‑rs / gtk‑rs bindings.

📄 License

GPL‑3.0‑or‑later © George Dumitrescu

About

Broadcast voice chain for your microphone, on pure PipeWire — processed virtual mic with gate, compressor, de-esser, EQ and limiter

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages