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:
| A | driver/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.