Piper
Overview
Section titled “Overview”Piper is a real-time audio processing tool that captures audio from any system input device, pipes it through configurable FFmpeg filter chains, and plays the result back on a chosen output device. The core engine is written in Rust and exposed to Node.js as a native addon via NAPI-RS, combining Rust’s low-level performance with a simple JavaScript API.
The primary use case is live noise reduction — the bundled RNNoise deep-learning model removes background noise in real time — but the architecture supports any FFmpeg audio filter chain, making it a flexible building block for audio processing workflows.
How It Works
Section titled “How It Works”The data flow is straightforward but involves careful multi-threaded coordination:
- SDL2 captures raw audio from the selected input device (48 kHz, 16-bit stereo)
- The Rust
AudioCallbackconverts samples to bytes and writes them to FFmpeg’s stdin - FFmpeg applies the configured filter chain (e.g.
arnndnnoise reduction) and writes processed audio to stdout - A dedicated reader thread parses FFmpeg’s stdout back into samples and pushes them into a custom ring buffer
- SDL2’s playback callback reads from the ring buffer and sends processed audio to the output device
┌──────────┐ raw PCM ┌────────┐ filtered PCM ┌─────────────┐│ Mic/Input├───────────────►│ FFmpeg ├──────────────────►│ Ring Buffer ││ (SDL2) │ via stdin │ arnndn │ via stdout │ (Rust) │└──────────┘ └────────┘ └──────┬──────┘ │ ┌──────▼──────┐ │ Speakers │ │ (SDL2) │ └─────────────┘Features
Section titled “Features”- Native Rust core — Performance-critical audio I/O and buffering implemented in Rust, compiled to a native
.nodeaddon with NAPI-RS and LTO-optimized release builds - Live noise reduction — Ships with an RNNoise model (
bd.rnnn) for ML-based denoising via FFmpeg’sarnndnfilter - Pluggable FFmpeg filters — Configure any FFmpeg audio filter chain in
Piper.toml; the engine builds the FFmpeg command dynamically from thefiltersarray - Custom ring buffer — A lock-guarded circular buffer (
RingBufferin lib.rs) with configurable capacity to balance latency against dropout risk - Cross-platform device routing — SDL2 enumerates system audio devices; select input/output by name or index, or get an interactive prompt at startup
- TOML + JSON config — Configuration loaded via
serdewith support for both formats, platform-specific FFmpeg paths, and sensible defaults - npx-runnable — Install and run in one step with
npx pipe-through-ffmpeg
Configuration
Section titled “Configuration”All settings live in a Piper.toml file at the project root:
# Path to FFmpeg binary (auto-detected if on PATH)# ffmpeg_path = "node_modules/ffmpeg-helper/win32-x64.exe"
# Ring buffer capacity — higher = more latency, fewer dropoutsbuffer_size = 10240
# FFmpeg audio filters to applyfilters = ["arnndn=m=bd.rnnn"]
# Device selection (omit to get an interactive prompt)# input_device_name = "BlackHole 2ch"# output_device_name = "MacBook Pro Speakers"The Rust engine assembles this into an FFmpeg command like:
-hide_banner -f s16le -ar 48000 -ac 2 -i pipe:0 -filter:a arnndn=m=bd.rnnn -f s16le -ac 2 -ar 48000 -Tech Stack
Section titled “Tech Stack”| Core Language | Rust |
| Node.js Binding | NAPI-RS |
| Audio I/O | SDL2 (via sdl2 crate) |
| Audio Processing | FFmpeg (subprocess, piped stdin/stdout) |
| Serialization | serde + toml + serde_json |
| Frontend | Node.js / JavaScript (thin wrapper) |
| Platforms | macOS (ARM64), Windows (x64) |
Source Code
Section titled “Source Code”The source code is available on the project’s GitHub repository.