README.md (3543B)
1 # Hydra virtual audio driver 2 3 Hydra's virtual audio driver — a fork of [BlackHole](https://github.com/ExistentialAudio/BlackHole) 4 (GPL-3.0) — is an `AudioServerPlugIn` that publishes a virtual audio device other apps can 5 select as an **input**. This is the piece Core Audio process taps + aggregate devices 6 *can't* provide, and it's what makes Hydra a real Loopback replacement. 7 8 Because Hydra links/distributes BlackHole-derived code, **the whole work is GPL-3.0-or-later** 9 (set in the workspace `Cargo.toml`). Upstream `LICENSE` + attribution are preserved in 10 `upstream/`. 11 12 ## Layout 13 14 - `upstream/` — pristine BlackHole, pinned to **v0.6.1**. Never edited (its `.git` is 15 stripped so it vendors as plain files). 16 - `build/` — build output (git-ignored): `Hydra.driver`. 17 18 The rebrand needs **no source edits**: BlackHole exposes its name, bundle id, manufacturer, 19 and channel count as compile-time constants, so `scripts/build-driver.sh` overrides them 20 with `-D` flags. 21 22 ## Building (verified — no Xcode required) 23 24 An `AudioServerPlugIn` is just a loadable bundle (a `-bundle` Mach-O + Info.plist), so 25 `scripts/build-driver.sh` compiles `BlackHole.c` straight with **clang** from the Command 26 Line Tools — no full Xcode install. It produces a **universal (arm64 + x86_64), ad-hoc 27 signed** `build/Hydra.driver`: 28 29 - device name **Hydra**, bundle id `com.ganten.hydra.driver`, manufacturer **Ganten** 30 - **16 channels** (override with `HYDRA_DRIVER_CHANNELS=N`) 31 - factory symbol `_BlackHole_Create` exported (coreaudiod instantiates via the bundle's 32 CFPlugIn UUIDs, unchanged from upstream) 33 34 ```sh 35 ./scripts/build-driver.sh # → driver/build/Hydra.driver 36 ``` 37 38 Verified locally: builds, `lipo` shows both arches, `codesign --verify --strict` passes, 39 bundle id is `com.ganten.hydra.driver`, factory symbol exported. 40 41 ## Installing (user-run; needs sudo + restarts audio ~1s) 42 43 Deliberately a manual step — the daemon never installs a system driver silently. 44 45 ```sh 46 ./scripts/install-driver.sh # copies to /Library/Audio/Plug-Ins/HAL, restarts coreaudiod (killall; SIP blocks kickstart) 47 # verify: 48 system_profiler SPAudioDataType | grep -i hydra # or Hydra's own device list 49 ``` 50 51 Once installed, "Hydra" appears as a 16-channel input device. Hydra's daemon routes 52 captured app audio **into** it by using its UID as the aggregate's output sub-device (the 53 P1 monitor engine already takes an `output_uid`, and the TUI's `o` key picks the target), 54 so other apps (Zoom, OBS, …) can select "Hydra" as their microphone and receive the routed 55 audio. **That is the core Loopback flow, working with this static driver.** 56 57 ## What's still ahead (not required for the core flow) 58 59 - **Manifest-driven config (P3).** Let the user rename the device, set arbitrary channel 60 counts, and publish *multiple* virtual devices at runtime, driven by 61 `/Library/Application Support/hydra/devices.json` (written by `hydra-core::manifest`, 62 already implemented + tested). This is the real source surgery: BlackHole's 63 `kNumber_Of_Channels` is compile-time and threads through every buffer/stream-layout 64 computation, so multi-device support means refactoring the single-device globals into an 65 array and switching on `objectID` in every property handler + `DoIOOperation`. The 66 manifest dir is world-readable because `coreaudiod` runs as `_coreaudiod` and can't read `~`. 67 - **Live reload without a coreaudiod restart (P6).** A custom plug-in property selector 68 (`'hyrl'`) the daemon pokes so device add/remove doesn't glitch audio.