Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
5f130b8
refactor(virtual-device): split macos impl into platform-gated module
Horuse May 24, 2026
0af2159
fix(device): use default config on linux instead of max-sample-rate
Horuse May 28, 2026
bd048a8
feat(volume): control device volume on linux
Horuse May 28, 2026
21e9619
feat(system-audio): list pipewire apps
Horuse May 28, 2026
878bf5e
feat(capture): system + per-app audio capture on linux via pipewire
Horuse May 28, 2026
425e45a
feat(virtual-device): create pipewire null-sinks on linux
Horuse May 28, 2026
cd9655d
fix(virtual-devices): hide driver install ui on linux
Horuse May 28, 2026
a75b752
feat(linux): native pipewire device i/o for mic and speaker
Horuse May 30, 2026
c3de5bb
refactor(audio): split into per-os submodules
Horuse May 30, 2026
205a958
ci: linux build
Horuse May 30, 2026
93100b8
fix: Potential fix for pull request finding 'CodeQL / Workflow does n…
Horuse May 30, 2026
881e984
fix: move macos-only bundle config to tauri.macos.conf.json
Horuse May 30, 2026
ab32c7b
Merge branch 'linux' of github.com:Horuse/Splitwave into linux
Horuse May 30, 2026
247227b
ci(linux): build rpm and split appimage/deb/rpm into separate artifacts
Horuse May 30, 2026
f57fb61
feat: linux support mentions and UI tweaks
Horuse May 31, 2026
2cc5453
ci(linux): strip bundled wayland libs from appimage
Horuse May 31, 2026
0ed49ec
ci(linux): strip bundled libpipewire/libspa from appimage
Horuse May 31, 2026
dbb6fdc
fix(linux): create virtual sinks via pw-cli instead of pactl
Horuse May 31, 2026
b2d53b5
feat(linux): custom titlebar with window controls and app menu
Horuse May 31, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
111 changes: 111 additions & 0 deletions .github/workflows/build-linux.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
name: Build Linux

# Runs only on a `linux-*` tag (e.g. `git tag linux-1 && git push origin linux-1`)
# or a manual dispatch -- never on every commit, and separate from the `v*`
# release tags so it won't trigger the macOS Release workflow. Produces a
# downloadable artifact (no GitHub release).
on:
push:
tags:
- 'linux-*'
workflow_dispatch:

permissions:
contents: read
packages: read

jobs:
build:
# ubuntu-22.04 = oldest base shipping WebKitGTK 4.1, so glibc stays 2.35
# for broad AppImage compatibility (newer bases raise the glibc floor).
runs-on: ubuntu-22.04
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Setup Bun
uses: oven-sh/setup-bun@v2
with:
bun-version: latest

- name: Setup Rust toolchain
uses: dtolnay/rust-toolchain@stable

- name: Cache cargo build
uses: Swatinem/rust-cache@v2
with:
workspaces: src-tauri

- name: Install Linux system deps
# 22.04 ships libpipewire 0.3.48, too old for the libspa crate
# (spa_video_info_raw.flags). The upstream PPA backports a current
# libpipewire while keeping the glibc 2.35 base.
run: |
sudo add-apt-repository -y ppa:pipewire-debian/pipewire-upstream
sudo apt-get update
sudo apt-get install -y --no-install-recommends \
libwebkit2gtk-4.1-dev libjavascriptcoregtk-4.1-dev \
libgtk-3-dev librsvg2-dev librsvg2-bin libgtk-3-bin \
libayatana-appindicator3-dev libsoup-3.0-dev libssl-dev \
libpipewire-0.3-dev clang libclang-dev \
libasound2-dev libopus-dev \
desktop-file-utils xdg-utils patchelf rpm

- name: Install JS deps
run: bun install --frozen-lockfile

- name: Build AppImage + deb + rpm
uses: tauri-apps/tauri-action@v0
with:
projectPath: src-tauri
args: '--bundles appimage,deb,rpm -c {"bundle":{"createUpdaterArtifacts":false}}'

- name: Strip bundled host libs from AppImage
# linuxdeploy bundles host-integration libs that aren't on the
# AppImage excludelist. They then shadow the system copies and
# break against the host's plugin ecosystem:
# - libwayland-* -> WebKit EGL init fails (EGL_BAD_PARAMETER /
# blank window) against newer Mesa
# - libpipewire/libspa -> can't load SPA plugins (support.system),
# "pipewire: Creation failed", since the spa-0.2/ plugin dir
# is not bundled
# Remove them so the app links the host's copies + their plugins.
run: |
set -euxo pipefail
export APPIMAGE_EXTRACT_AND_RUN=1
app=$(find src-tauri/target/release/bundle/appimage -name '*.AppImage' | head -1)
"$app" --appimage-extract
rm -fv squashfs-root/usr/lib/libwayland-client.so* \
squashfs-root/usr/lib/libwayland-egl.so* \
squashfs-root/usr/lib/libwayland-cursor.so* \
squashfs-root/usr/lib/libpipewire-0.3.so* \
squashfs-root/usr/lib/libspa-0.2.so*
curl -fsSL -o /tmp/appimagetool \
https://github.com/AppImage/appimagetool/releases/download/continuous/appimagetool-x86_64.AppImage
chmod +x /tmp/appimagetool
rm -f "$app"
ARCH=x86_64 /tmp/appimagetool squashfs-root "$app"
rm -rf squashfs-root

# Separate artifacts so each downloads on its own -- grabbing the
# small deb/rpm doesn't pull the heavy AppImage.
- name: Upload AppImage
uses: actions/upload-artifact@v4
with:
name: Splitwave-AppImage-x86_64
path: src-tauri/target/release/bundle/appimage/*.AppImage
if-no-files-found: error

- name: Upload deb
uses: actions/upload-artifact@v4
with:
name: Splitwave-deb-x86_64
path: src-tauri/target/release/bundle/deb/*.deb
if-no-files-found: error

- name: Upload rpm
uses: actions/upload-artifact@v4
with:
name: Splitwave-rpm-x86_64
path: src-tauri/target/release/bundle/rpm/*.rpm
if-no-files-found: error
Comment thread
github-advanced-security[bot] marked this conversation as resolved.
Fixed
69 changes: 51 additions & 18 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
[![Downloads](https://img.shields.io/github/downloads/Horuse/Splitwave/total.svg)](https://github.com/Horuse/Splitwave/releases/latest)
# Splitwave
Splitwave is a node-based audio router for macOS. Wire microphones, system audio, per-app capture, and WAV files into a visual graph, run them through a chain of effects — EQ, compression, reverb, limiting, and more — then send the result to speakers or record it in WAV, FLAC, AIFF, MP3, Opus, or AAC.
Splitwave is a node-based audio router for macOS and Linux. Wire microphones, system audio, per-app capture, and WAV files into a visual graph, run them through a chain of effects — EQ, compression, reverb, limiting, and more — then send the result to speakers or record it in WAV, FLAC, AIFF, MP3, Opus, or AAC.

![Splitwave preview](./preview.png)


## Installation

### macOS

Download the latest `.dmg` from [Releases](https://github.com/Horuse/Splitwave/releases/latest),
open it, and drag Splitwave to Applications.

Expand All @@ -21,10 +23,19 @@ Then open Splitwave normally.

**After each update, Screen Recording permission resets** (macOS revokes it when the binary changes and the app is unsigned). To re-grant it: open System Settings → Privacy & Security → Screen Recording, click **−** to remove Splitwave, then click **+** and add it back.

### Linux

Requires a PipeWire-based audio session (default on most current distros).
Download the build for your system from [Releases](https://github.com/Horuse/Splitwave/releases/latest):

- **AppImage** — `chmod +x Splitwave_*.AppImage && ./Splitwave_*.AppImage`
- **`.deb`** (Debian/Ubuntu) — `sudo apt install ./Splitwave_*.deb`
- **`.rpm`** (Fedora/RHEL/openSUSE) — `sudo rpm -i Splitwave-*.rpm`

## Features

- **Inputs:** microphones, system audio, per-application audio
(ScreenCaptureKit), WAV files, virtual device loopback
- **Inputs:** microphones, system audio, per-application audio, WAV files,
virtual device loopback
- **Outputs:** physical speakers/interfaces, file recording in WAV (16/24-bit
PCM + 32-float), FLAC, AIFF, Opus, MP3, AAC (M4A), virtual devices
- **Effects:** Gain, Mute, Channel Balance, Saturator, 10-band Graphic EQ,
Expand All @@ -35,23 +46,40 @@ Then open Splitwave normally.
Use them to capture loopback audio from any app or to feed processed audio into
apps that accept a microphone input (DAWs, Discord, etc.)

System and per-app capture use **ScreenCaptureKit** on macOS and **PipeWire** on
Linux; virtual devices are AudioServerPlugin drivers on macOS and PipeWire
null-sinks on Linux.

## Stack

- **Frontend:** Svelte, Tauri, @xyflow/svelte
- **Engine:** Rust -- `cpal` (device I/O), `rtrb` (SPSC ring buffers), `rubato`
(resampling), `hound` (WAV), `flac-codec`, `opus`, `mp3lame-encoder`,
`ebur128`
- **macOS-specific:** custom Swift static library for ScreenCaptureKit,
compiled by `build.rs` via `swiftc`; CoreAudio HAL FFI for device
enumeration; libASPL-based AudioServerPlugin for virtual device driver
- **Engine:** Rust -- `rtrb` (SPSC ring buffers), `rubato` (resampling),
`hound` (WAV), `flac-codec`, `opus`, `mp3lame-encoder`, `ebur128`
- **macOS:** `cpal` device I/O; custom Swift static library for ScreenCaptureKit,
compiled by `build.rs` via `swiftc`; CoreAudio HAL FFI for device enumeration;
libASPL-based AudioServerPlugin for the virtual device driver
- **Linux:** `pipewire` for device I/O, system/app capture, and virtual
null-sinks; `freedesktop-desktop-entry` / `freedesktop-icons` for app icons


## Development

### Prerequisites

**macOS:**

- macOS 13+ with Xcode Command Line Tools (`xcode-select --install`) -- gives
you `swiftc` and the SDKs Tauri needs.

**Linux:**

- A PipeWire session and these dev packages (Debian/Ubuntu names):
`libwebkit2gtk-4.1-dev libgtk-3-dev librsvg2-dev libayatana-appindicator3-dev`
`libsoup-3.0-dev libpipewire-0.3-dev clang libclang-dev libasound2-dev`
`libopus-dev`

**Both:**

- [Rust](https://rustup.rs) (stable toolchain)
- [Bun](https://bun.sh) (`curl -fsSL https://bun.sh/install | bash`)

Expand All @@ -65,28 +93,33 @@ bun run tauri dev
### Useful commands

```bash
bun run check # svelte-check + tsc
bun run generate # regenerate TypeScript types from Rust (ts-rs)
bun run check # svelte-check + tsc
bun run generate # regenerate TypeScript types from Rust (ts-rs)
cargo check --manifest-path src-tauri/Cargo.toml
bun run tauri build --bundles app # local .app build
bun run tauri build --bundles app # local .app build (macOS)
bun run tauri build --bundles appimage # local AppImage build (Linux)
```

### Project layout

```
src/lib/modules/
audio/ cpal/SCK bridge, device enumeration, meter store
audio/ device enumeration, meter store
flow/ xyflow node graph editor, sidebar, context menu
pipeline/ pipeline state, snapshot history, ts-rs generated types
form/ shared form primitives (combobox, slider)
error/ global error modal (Rust panics + JS errors)
updater/ auto-update modal + skip-version persistence
app_info/ OS / app version cache
src-tauri/src/
audio/ DSP engine, effects, encoders, pipeline DAG
commands.rs Tauri command surface
lib.rs app entry, plugin wiring, panic hook
src-tauri/src/audio/
capture/ system + per-app capture (macos.rs SCK / linux.rs PipeWire)
device/ device enumeration (macos.rs CoreAudio / linux.rs PipeWire)
volume/ device volume (macos.rs / linux.rs)
virtual_device/ null-sink / driver management per OS
streams/ cpal stream builders (macOS)
playback.rs PipeWire speaker output (Linux)
pipeline/ DSP engine, effects, encoders, pipeline DAG (input/, output/ per OS)
src-tauri/native/virtual_driver/
SplitAudioDriver.cpp AudioServerPlugin implementation (libASPL)
SplitAudioDriver.cpp AudioServerPlugin implementation (libASPL, macOS)
Info.plist CFPlugin manifest
```
Loading
Loading