hydra

Terminal replacement for Loopback — virtual audio devices and routing on macOS, from a ratatui TUI.
Log | Files | Refs | README | LICENSE

commit 6aab163e6ee90f2ef457ac6b3b5bb3471ed82827
parent f6f94739d09834986837d563529fbca7b76c5559
Author: Matthew Gantenbein <ganten1998@gmail.com>
Date:   Sun, 31 May 2026 11:19:26 -0500

P2 (scaffold): driver/ fork plan for the Hydra HAL driver

Documents the BlackHole fork strategy without vendoring source: this sandbox
has no network to clone upstream, and we won't build a GPL fork against
unverified source. README captures the exact vendoring steps, the key finding
that BlackHole >=0.6 is already env-var-configurable for one device, and the
P2 manifest-reader port plan (JSON at /Library/Application Support/hydra/
devices.json, read by coreaudiod which can't see ~). N-device refactor stays
P3; no-restart live reload stays P6. Driver install remains a manual sudo step.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

Diffstat:
Adriver/README.md | 79+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 79 insertions(+), 0 deletions(-)

diff --git a/driver/README.md b/driver/README.md @@ -0,0 +1,79 @@ +# Hydra virtual audio driver (P2 — fork of BlackHole) + +This directory will hold Hydra's virtual audio driver: a fork of +[BlackHole](https://github.com/ExistentialAudio/BlackHole) (GPL-3.0), an +`AudioServerPlugIn` that publishes virtual audio devices other apps can select as inputs. + +**Status: scaffold only.** The upstream source is *not yet vendored here* — this sandbox +had no network access to clone it, and we will not build against unverified source. The +steps below are the exact plan; nothing in this dir invents BlackHole internals. + +## Why a fork (not a dependency) + +Loopback's headline feature — a device that *other* apps can pick as an audio input — +requires a HAL plug-in. CoreAudio process taps + aggregate devices (which Hydra already +uses for capture/monitoring in `hydra-core`) cannot publish such a device. BlackHole is +the proven GPL-3.0 starting point. + +Because we link/distribute BlackHole-derived code, **the whole Hydra work is GPL-3.0-or-later** +(already set in the workspace `Cargo.toml`). Keep the upstream `LICENSE` and attribution. + +## Vendoring (do this once, on a machine with network) + +```sh +cd driver +git clone https://github.com/ExistentialAudio/BlackHole.git upstream +# Pin a known release for reproducibility: +( cd upstream && git checkout v0.6.1 ) # or latest stable +``` + +Keep `upstream/` as the unmodified baseline; Hydra's changes go in a sibling target +(`Hydra/`) or as a tracked patch set, so upstream updates stay mergeable. + +## What modern BlackHole already gives us + +BlackHole ≥0.6 reads its device configuration at plug-in load from **environment +variables** (`kNumber_Of_Channels`, `kDevice_Name`, `kDevice_UID`, `Device2_*`, …) inside a +`dispatch_once` static initializer, falling back to compile-time `kDevice_*` defaults. That +already makes a *single* device's name/channels runtime-configurable — but env vars on +`coreaudiod` are awkward and still describe only one device (+ an optional mirror). + +## P2 port plan (one dynamic device, manifest-driven) + +Goal: replace the env-var config with a **JSON manifest** the Hydra daemon writes, so the +device's name/UID/channel-count come from Hydra rather than the build. + +1. **Rebrand.** Fork to bundle id `com.ganten.hydra.driver`, product `Hydra.driver`, device + default name "Hydra". Update Info.plist, the `.icns`, and the `kPlugIn_BundleID`. +2. **Manifest reader.** In the static initializer, instead of `getenv(...)`, read + `/Library/Application Support/hydra/devices.json` (world-readable; `coreaudiod` runs as + `_coreaudiod` and cannot read `~`). Parse the first device entry into the existing + `gDevice_Name` / `gNumber_Of_Channels` / `gDevice_UID` globals. Vendor a single-header + GPL-compatible JSON parser (e.g. `cJSON`) for this — no system deps. + Manifest schema (written by `hydra-core::manifest`, to add in P2): + ```json + { "version": 1, + "devices": [ { "uid": "hydra:main", "name": "Hydra Main", "channels": 16, + "sample_rates": [44100, 48000] } ] } + ``` +3. **Keep the single-device path intact first.** Ship P2 as exactly one configurable device; + the N-device array refactor (switching every property handler + `DoIOOperation` on + `objectID`) is **P3**. +4. **Apply changes** by installing the `.driver` and restarting coreaudiod (see below). The + no-restart live-reload via a custom property selector (`'hyrl'`) is **P6**. + +## Build & install (scripts land with the real source) + +- `scripts/build-driver.sh` — `xcodebuild` the fork → `driver/build/Hydra.driver`, then + `codesign --force --deep --sign - Hydra.driver` (ad-hoc OK locally; SIP stays on). +- `scripts/install-driver.sh` — **needs sudo + interrupts all audio ~1s**: + ```sh + sudo mkdir -p "/Library/Application Support/hydra" + sudo cp -R driver/build/Hydra.driver /Library/Audio/Plug-Ins/HAL/ + sudo launchctl kickstart -k system/com.apple.audio.coreaudiod + ``` + Verify: `system_profiler SPAudioDataType | grep -i hydra` or Hydra's own `ListDevices` + (the daemon already enumerates HAL devices — it'll show the new device once loaded). + +> Installing the driver is intentionally a **manual, user-run step**: it requires `sudo` +> and restarts system audio. Hydra's daemon never does this silently.