hydra

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

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.