<?xml version="1.0" encoding="UTF-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
<title>hydra, branch HEAD</title>
<subtitle>Terminal replacement for Loopback — virtual audio devices and routing on macOS, from a ratatui TUI.
</subtitle>
<entry>
<id>c5530eaf25e6c24c5f5eaf223103126ca6b20a2d</id>
<published>2026-06-02T04:52:58Z</published>
<updated>2026-06-02T04:52:58Z</updated>
<title>fix: bundle.sh builds the hydra TUI too, not just hydrad</title>
<link rel="alternate" type="text/html" href="commit/c5530eaf25e6c24c5f5eaf223103126ca6b20a2d.html" />
<author>
<name>Matthew Gantenbein</name>
<email>ganten1998@gmail.com</email>
</author>
<content>commit c5530eaf25e6c24c5f5eaf223103126ca6b20a2d
parent f6829bf02ce67a9a7de647eaff6983bcd05d4a12
Author: Matthew Gantenbein &lt;ganten1998@gmail.com&gt;
Date:   Mon,  1 Jun 2026 23:52:58 -0500

fix: bundle.sh builds the hydra TUI too, not just hydrad

The `hydra` launcher symlinks to target/release/hydra, but bundle.sh only
rebuilt hydrad — so refreshing via bundle.sh left a stale TUI on PATH (showed
the old &#39;default&#39; theme after the abyssal rename). Build both binaries.

Co-Authored-By: Claude Opus 4.8 (1M context) &lt;noreply@anthropic.com&gt;

</content>
</entry>
<entry>
<id>f6829bf02ce67a9a7de647eaff6983bcd05d4a12</id>
<published>2026-06-02T04:19:52Z</published>
<updated>2026-06-02T04:19:52Z</updated>
<title>themes: &quot;Abyssal&quot; default (distinct from transparent) + persist transparency on start</title>
<link rel="alternate" type="text/html" href="commit/f6829bf02ce67a9a7de647eaff6983bcd05d4a12.html" />
<author>
<name>Matthew Gantenbein</name>
<email>ganten1998@gmail.com</email>
</author>
<content>commit f6829bf02ce67a9a7de647eaff6983bcd05d4a12
parent 85a303fa45a6b1c42c91a9314ee638c2497e916d
Author: Matthew Gantenbein &lt;ganten1998@gmail.com&gt;
Date:   Mon,  1 Jun 2026 23:19:52 -0500

themes: &quot;Abyssal&quot; default (distinct from transparent) + persist transparency on start

Three fixes:
1. Transparency now persists across launches. Bug: picking a theme saved only its
   name, so a stale `transparent` toggle in ui.toml clobbered a transparent
   theme&#39;s own setting on load — it came back opaque. save_active now records the
   theme&#39;s transparency too; load() applies it. Regression-tested.
2. Deleted the old `default` theme — it duplicated `transparent`&#39;s slate palette.
3. New built-in default &quot;Abyssal&quot;: Hydra&#39;s signature deep-sea palette (abyssal
   blue-green, bioluminescent seafoam highlight, coral accent) — fits the
   water-serpent. Shipped as both the built-in and an editable themes/abyssal.toml.
   by_name prefers a user abyssal.toml if present; &quot;default&quot; stays a back-compat
   alias. Still 10 themes.

30 tests green (added: transparent-loads-transparent, abyssal-distinct), 0 warns.

Co-Authored-By: Claude Opus 4.8 (1M context) &lt;noreply@anthropic.com&gt;

</content>
</entry>
<entry>
<id>85a303fa45a6b1c42c91a9314ee638c2497e916d</id>
<published>2026-06-01T22:59:41Z</published>
<updated>2026-06-01T22:59:41Z</updated>
<title>feat: route hardware inputs (mic / line-in / interface) to any output</title>
<link rel="alternate" type="text/html" href="commit/85a303fa45a6b1c42c91a9314ee638c2497e916d.html" />
<author>
<name>Matthew Gantenbein</name>
<email>ganten1998@gmail.com</email>
</author>
<content>commit 85a303fa45a6b1c42c91a9314ee638c2497e916d
parent e89e691ab77a9835614c4a6a496335701b5bfe32
Author: Matthew Gantenbein &lt;ganten1998@gmail.com&gt;
Date:   Mon,  1 Jun 2026 17:59:41 -0500

feat: route hardware inputs (mic / line-in / interface) to any output

New source type alongside app-capture: route a hardware INPUT device to an
output. No process tap, no TCC — a plain [input, output] aggregate with the
shared IOProc reading the input device&#39;s own stream.

- tap_shim.m: factored the realtime IOProc into shared hydra_render(useLast) +
  hydra_publish_rate (process tap = last input buffer, hardware input = first);
  new hydra_input_start builds the input→output aggregate. ~70 lines of realtime
  code now shared, not duplicated.
- shim.rs: MonitorRoute::start_input + new_params() helper (keeps the Rust/C
  layout assertion in one place). engine::start_input. IPC StartInput command.
  daemon dispatch + input_label.
- TUI: &#39;i&#39; opens an input-source picker (lists input-capable devices, excludes
  Hydra to avoid feedback); ⏎ routes the chosen input to the current output.
  Reuses the shared overlay; footer hint added.

VERIFIED: input route builds an aggregate whose IOProc clocks (callbacks climb);
picker lists real inputs (Scarlett 20ch, MacBook mic, etc.). NOT yet confirmed:
actual mic audio flowing — needs mic TCC consent + (for built-in mic) lid open;
that&#39;s a user manual test. 28 tests, 0 warnings.

Co-Authored-By: Claude Opus 4.8 (1M context) &lt;noreply@anthropic.com&gt;

</content>
</entry>
<entry>
<id>e89e691ab77a9835614c4a6a496335701b5bfe32</id>
<published>2026-06-01T21:33:55Z</published>
<updated>2026-06-01T21:33:55Z</updated>
<title>release: v0.1.0 pipeline — .dmg + source tarball, LICENSE, NOTICE</title>
<link rel="alternate" type="text/html" href="commit/e89e691ab77a9835614c4a6a496335701b5bfe32.html" />
<author>
<name>Matthew Gantenbein</name>
<email>ganten1998@gmail.com</email>
</author>
<content>commit e89e691ab77a9835614c4a6a496335701b5bfe32
parent 85b46269e2f8a3882561e2cb125b29bd6e3aaa4f
Author: Matthew Gantenbein &lt;ganten1998@gmail.com&gt;
Date:   Mon,  1 Jun 2026 16:33:55 -0500

release: v0.1.0 pipeline — .dmg + source tarball, LICENSE, NOTICE

scripts/make-release.sh builds a distributable release: universal (arm64+x86_64)
hydra + hydrad + driver + the prebuilt installer + 10 themes + README, packaged
as Hydra-&lt;ver&gt;.dmg, plus a git-archive source tarball. The dmg installer signs
the daemon into ~/Applications/Hydra.app, loads the LaunchAgent, installs the
driver, symlinks `hydra` — no cargo/rustc needed on the user&#39;s machine. Verified:
dmg mounts, both binaries + driver are universal, all 10 themes present.

LICENSE (GPL-3.0, from the vendored BlackHole) + NOTICE.md added — mandatory for
distributing the GPL driver fork. README documents the Neocities download path
(dmg hosted there; stagit stays the source mirror) + the not-notarized caveat.

NOT notarized (no Apple Developer ID yet): Gatekeeper warns on other Macs;
HYDRA_SIGN_ID=&lt;Developer ID&gt; later makes this same layout notarizable.

Co-Authored-By: Claude Opus 4.8 (1M context) &lt;noreply@anthropic.com&gt;

</content>
</entry>
<entry>
<id>85b46269e2f8a3882561e2cb125b29bd6e3aaa4f</id>
<published>2026-06-01T21:10:43Z</published>
<updated>2026-06-01T21:10:43Z</updated>
<title>feat: ship 10 default themes (public palettes, no Navi)</title>
<link rel="alternate" type="text/html" href="commit/85b46269e2f8a3882561e2cb125b29bd6e3aaa4f.html" />
<author>
<name>Matthew Gantenbein</name>
<email>ganten1998@gmail.com</email>
</author>
<content>commit 85b46269e2f8a3882561e2cb125b29bd6e3aaa4f
parent 42d77038563379854b95c6cde9449f6e8470d6c0
Author: Matthew Gantenbein &lt;ganten1998@gmail.com&gt;
Date:   Mon,  1 Jun 2026 16:10:43 -0500

feat: ship 10 default themes (public palettes, no Navi)

Adds 8 well-known public color schemes alongside default + transparent, for 10
total in the repo: nord, gruvbox, dracula, tokyonight, catppuccin-mocha,
solarized-dark, rose-pine, monochrome. All validated as TOML. None reference
Navi. Copied into the local picker folder so `t` shows the full set.

Co-Authored-By: Claude Opus 4.8 (1M context) &lt;noreply@anthropic.com&gt;

</content>
</entry>
<entry>
<id>42d77038563379854b95c6cde9449f6e8470d6c0</id>
<published>2026-06-01T21:01:11Z</published>
<updated>2026-06-01T21:01:11Z</updated>
<title>feat: drop-folder themes + live picker + transparency; remove Navi from repo</title>
<link rel="alternate" type="text/html" href="commit/42d77038563379854b95c6cde9449f6e8470d6c0.html" />
<author>
<name>Matthew Gantenbein</name>
<email>ganten1998@gmail.com</email>
</author>
<content>commit 42d77038563379854b95c6cde9449f6e8470d6c0
parent 21f92d59929b93ecfbaba98b020465929c672148
Author: Matthew Gantenbein &lt;ganten1998@gmail.com&gt;
Date:   Mon,  1 Jun 2026 16:01:11 -0500

feat: drop-folder themes + live picker + transparency; remove Navi from repo

Theming, btop-style:
- ~/.config/hydra/themes/*.toml is a dead-simple drop folder. Any .toml there
  appears in the TUI theme picker (&#39;t&#39;), applies live, and persists.
- &#39;T&#39; toggles background transparency: bg fills become Color::Reset so the
  terminal&#39;s own background (Ghostty vibrancy etc.) shows through; chrome stays
  opaque. Per-theme `transparent = true` also supported.
- Theme moved into App so the picker swaps it live; partial theme files merge
  onto the built-in default (any subset of keys). `--theme &lt;path&gt;` one-shot
  override still works. Active choice + transparency persist to
  ~/.config/hydra/ui.toml.
- Generalized the presets overlay into a shared draw_overlay (presets + themes).

Navi removed from the repo entirely (it stays its own standalone repo):
- built-in default is now a neutral slate/teal/amber palette
- repo ships only neutral examples (themes/default.toml, transparent.toml) +
  themes/README; the sketchybar plugin + theme tests no longer carry Navi hexes
- verified: zero Navi hexes / mentions in tracked source
- the user&#39;s Navi is preserved LOCALLY at ~/.config/hydra/themes/navi.toml and
  set active — off the repo, on their machine.

28 tests green, 0 warnings.

Co-Authored-By: Claude Opus 4.8 (1M context) &lt;noreply@anthropic.com&gt;

</content>
</entry>
<entry>
<id>21f92d59929b93ecfbaba98b020465929c672148</id>
<published>2026-06-01T20:31:39Z</published>
<updated>2026-06-01T20:31:39Z</updated>
<title>docs: README reflects full feature set + complete keymap</title>
<link rel="alternate" type="text/html" href="commit/21f92d59929b93ecfbaba98b020465929c672148.html" />
<author>
<name>Matthew Gantenbein</name>
<email>ganten1998@gmail.com</email>
</author>
<content>commit 21f92d59929b93ecfbaba98b020465929c672148
parent b8282cab4a1d246263fbcb6b4475ae7f82e01515
Author: Matthew Gantenbein &lt;ganten1998@gmail.com&gt;
Date:   Mon,  1 Jun 2026 15:31:39 -0500

docs: README reflects full feature set + complete keymap

Co-Authored-By: Claude Opus 4.8 (1M context) &lt;noreply@anthropic.com&gt;

</content>
</entry>
<entry>
<id>b8282cab4a1d246263fbcb6b4475ae7f82e01515</id>
<published>2026-06-01T20:31:02Z</published>
<updated>2026-06-01T20:31:02Z</updated>
<title>UX2: per-source volume via separate routes (&#39;C&#39;) — no realtime rewrite</title>
<link rel="alternate" type="text/html" href="commit/b8282cab4a1d246263fbcb6b4475ae7f82e01515.html" />
<author>
<name>Matthew Gantenbein</name>
<email>ganten1998@gmail.com</email>
</author>
<content>commit b8282cab4a1d246263fbcb6b4475ae7f82e01515
parent c68822d831f1cf8cba9dd9474c5a0bfc25fd86a0
Author: Matthew Gantenbein &lt;ganten1998@gmail.com&gt;
Date:   Mon,  1 Jun 2026 15:31:02 -0500

UX2: per-source volume via separate routes (&#39;C&#39;) — no realtime rewrite

The naive approach (one multi-tap aggregate mixing N sources at independent
gains) meant rewriting the realtime IOProc + shared C struct — pure risk. A
measurement found the better design: N independent single-source routes to the
SAME output device sum cleanly at the device, each keeping its own
gain/mute/record/meter. Verified end-to-end:
- two distinct tones (300/900Hz) → two routes → Hydra: BOTH present at the
  device input (Goertzel 7e12 / 7e13)
- muting one route dropped its tone ~10000x while the other stayed full
  → genuine per-source isolation, using only the already-proven route path.

So UX2 ships as a TUI action, not a C rewrite: &#39;C&#39; starts each marked app as its
own route (vs &#39;c&#39; = one mixed route, shared gain). Footer: &quot;c mix / C separate&quot;.
Keeps the unsafe realtime path untouched.

27 tests green, 0 warnings.

Co-Authored-By: Claude Opus 4.8 (1M context) &lt;noreply@anthropic.com&gt;

</content>
</entry>
<entry>
<id>c68822d831f1cf8cba9dd9474c5a0bfc25fd86a0</id>
<published>2026-06-01T20:27:56Z</published>
<updated>2026-06-01T20:27:56Z</updated>
<title>UX4: dB-scaled meters with peak-hold</title>
<link rel="alternate" type="text/html" href="commit/c68822d831f1cf8cba9dd9474c5a0bfc25fd86a0.html" />
<author>
<name>Matthew Gantenbein</name>
<email>ganten1998@gmail.com</email>
</author>
<content>commit c68822d831f1cf8cba9dd9474c5a0bfc25fd86a0
parent 483a422c8e124775b8da9584de2cbb0bf4bedd60
Author: Matthew Gantenbein &lt;ganten1998@gmail.com&gt;
Date:   Mon,  1 Jun 2026 15:27:56 -0500

UX4: dB-scaled meters with peak-hold

Linear meters are near-useless for audio — a -20 dB signal (peak 0.1) barely
moved one block. Now:
- peak_to_fraction(): maps peak → fill on a dBFS scale (-54..0 dB), so quiet-but-
  present audio reads clearly (-20 dB ≈ 63% fill). Unit-tested.
- peak-hold tick: App tracks a decaying per-route held peak (snap up, gentle
  decay ~0.88/refresh); drawn as an amber ┃ above the fill, like a hardware VU.
- headroom colour: green &lt; -6 dB, amber approaching, red within ~1 dB of clip.

27 tests green, 0 warnings.

Co-Authored-By: Claude Opus 4.8 (1M context) &lt;noreply@anthropic.com&gt;

</content>
</entry>
<entry>
<id>483a422c8e124775b8da9584de2cbb0bf4bedd60</id>
<published>2026-06-01T20:21:33Z</published>
<updated>2026-06-01T20:21:33Z</updated>
<title>UX3 complete: record-to-file TUI binding</title>
<link rel="alternate" type="text/html" href="commit/483a422c8e124775b8da9584de2cbb0bf4bedd60.html" />
<author>
<name>Matthew Gantenbein</name>
<email>ganten1998@gmail.com</email>
</author>
<content>commit 483a422c8e124775b8da9584de2cbb0bf4bedd60
parent 3b772e3d1d8a9921257acd0d64170fa236196394
Author: Matthew Gantenbein &lt;ganten1998@gmail.com&gt;
Date:   Mon,  1 Jun 2026 15:21:33 -0500

UX3 complete: record-to-file TUI binding

Wires the (already-verified) recording backend into the TUI: &#39;R&#39; on a selected
route toggles recording to ~/Music/Hydra/hydra-&lt;route&gt;-&lt;ts&gt;.wav; the route shows
a red ⏺ REC indicator while recording; status line reports the saved path +
duration on stop. Footer hint added. Backend (lock-free ring → WAV drain thread)
was verified earlier via afinfo (valid 2ch/44.1k/Float32 WAV with real signal).

26 tests green, 0 warnings.

Co-Authored-By: Claude Opus 4.8 (1M context) &lt;noreply@anthropic.com&gt;

</content>
</entry>
<entry>
<id>3b772e3d1d8a9921257acd0d64170fa236196394</id>
<published>2026-06-01T20:19:38Z</published>
<updated>2026-06-01T20:19:38Z</updated>
<title>P6: self-healing supervisor — routes survive coreaudiod/driver death</title>
<link rel="alternate" type="text/html" href="commit/3b772e3d1d8a9921257acd0d64170fa236196394.html" />
<author>
<name>Matthew Gantenbein</name>
<email>ganten1998@gmail.com</email>
</author>
<content>commit 3b772e3d1d8a9921257acd0d64170fa236196394
parent 0e5db1afa327fa25d057474b3232294f392525e5
Author: Matthew Gantenbein &lt;ganten1998@gmail.com&gt;
Date:   Mon,  1 Jun 2026 15:19:38 -0500

P6: self-healing supervisor — routes survive coreaudiod/driver death

The &quot;set it and forget it&quot; finish. Every failure that made routing fragile —
coreaudiod restart, driver reinstall, device vanish — silently killed routes
and forced a manual rebuild. Now the daemon heals itself.

- engine::reconcile(): each supervisor tick, (1) rebuild routes whose IOProc
  callback counter froze (aggregate died — the counter is clocked independently
  of whether the app is making sound, so frozen = dead, not silent), re-resolving
  the app&#39;s PID; (2) resurrect saved routes whose app is now running but isn&#39;t live.
- route_is_dead(prev,now): the stall judgment extracted as a pure fn, unit-tested
  (fresh route never dead / advancing=alive / frozen=dead) — 3 tests.
- daemon: supervisor thread ticks every 4s (long enough that a healthy route logs
  several callbacks between ticks so a freeze is unambiguous), persists healed
  state, nudges SketchyBar. Deduped resolve_bundle_to_pid into main (pub(crate)).
- LaunchAgent already RunAtLoad + KeepAlive ⇒ daemon auto-starts on login,
  relaunches on crash. Full stack: crash→KeepAlive, reboot→restore, aggregate
  death→supervisor.

VERIFIED: stall logic unit-tested; supervisor took ZERO actions over 14s (3+
ticks) on a healthy route (no thrashing) — the critical forget-it property.
NOT yet verified live: the actual coreaudiod-kill rebuild (needs sudo, untestable
in this sandbox) — will get its real test on the user&#39;s next driver reinstall.

24 tests green, 0 warnings.

Co-Authored-By: Claude Opus 4.8 (1M context) &lt;noreply@anthropic.com&gt;

</content>
</entry>
<entry>
<id>0e5db1afa327fa25d057474b3232294f392525e5</id>
<published>2026-06-01T20:08:05Z</published>
<updated>2026-06-01T20:08:05Z</updated>
<title>docs: diagnostics/README — layer-by-layer audio debugging method</title>
<link rel="alternate" type="text/html" href="commit/0e5db1afa327fa25d057474b3232294f392525e5.html" />
<author>
<name>Matthew Gantenbein</name>
<email>ganten1998@gmail.com</email>
</author>
<content>commit 0e5db1afa327fa25d057474b3232294f392525e5
parent 6f8b8c69d4f0a6a651b8a755b690151c91582c3b
Author: Matthew Gantenbein &lt;ganten1998@gmail.com&gt;
Date:   Mon,  1 Jun 2026 15:08:05 -0500

docs: diagnostics/README — layer-by-layer audio debugging method

Captures the methodology from the one-ear-in-Discord investigation: measure
each layer (tap → device output → device properties → browser capture → SDP →
Discord DSP → receiver) in order instead of guessing at the device. Notes the
key mistakes: identical-L/R test tones can&#39;t detect mono collapse (use distinct
L/R), and the listener&#39;s own playback is the easiest variable to forget.

Issue resolved (stereo works); can&#39;t attribute to a single change among the
44.1k driver default, a headphone swap, and a voice rejoin. Hydra measured
correct throughout.

Co-Authored-By: Claude Opus 4.8 (1M context) &lt;noreply@anthropic.com&gt;

</content>
</entry>
<entry>
<id>6f8b8c69d4f0a6a651b8a755b690151c91582c3b</id>
<published>2026-06-01T19:48:19Z</published>
<updated>2026-06-01T19:48:19Z</updated>
<title>fix: default Hydra to 44100 Hz — the real cause of one-ear-in-Discord</title>
<link rel="alternate" type="text/html" href="commit/6f8b8c69d4f0a6a651b8a755b690151c91582c3b.html" />
<author>
<name>Matthew Gantenbein</name>
<email>ganten1998@gmail.com</email>
</author>
<content>commit 6f8b8c69d4f0a6a651b8a755b690151c91582c3b
parent e7bb12a18f1380d43ef74a5a8f26d84d8b1bd85f
Author: Matthew Gantenbein &lt;ganten1998@gmail.com&gt;
Date:   Mon,  1 Jun 2026 14:48:19 -0500

fix: default Hydra to 44100 Hz — the real cause of one-ear-in-Discord

Measured A/B (diagnostics/devdiff.m) of Hydra vs Loopback&#39;s working virtual mic,
both 2ch: the ONE difference was sample rate — Hydra 48000, Loopback 44100 — and
the user&#39;s whole setup runs at 44.1k. Hydra at 48k against a 44.1k host forces a
resample in the capture path that collapses stereo to one channel; Loopback
matched the host so it stayed clean. (Channel labels were a red herring: Hydra
already had proper [L,R], Loopback had [Unknown,Unknown], yet Loopback worked.)

- BlackHole.c: startup rate via -DkDefault_SampleRate (defaults to 48000 =
  upstream unchanged; device still supports all rates, this only sets the
  come-up rate).
- build-driver.sh: HYDRA_DRIVER_RATE, default 44100. Corrected the stale comment
  that wrongly blamed the 16ch count for the one-ear bug.
- diagnostics/devdiff.m + hydra_mic_probe.js: the measurement tools that found it
  (device property diff; browser capture probe).

Reinstall driver to apply. Verify: devdiff shows Hydra input ASBD 44100 Hz.

Co-Authored-By: Claude Opus 4.8 (1M context) &lt;noreply@anthropic.com&gt;

</content>
</entry>
<entry>
<id>e7bb12a18f1380d43ef74a5a8f26d84d8b1bd85f</id>
<published>2026-06-01T19:06:26Z</published>
<updated>2026-06-01T19:06:26Z</updated>
<title>fix: install-driver.sh accepts Y/yes, not just lowercase y</title>
<link rel="alternate" type="text/html" href="commit/e7bb12a18f1380d43ef74a5a8f26d84d8b1bd85f.html" />
<author>
<name>Matthew Gantenbein</name>
<email>ganten1998@gmail.com</email>
</author>
<content>commit e7bb12a18f1380d43ef74a5a8f26d84d8b1bd85f
parent f617db31be4e553bf588eee355d3a0039dc523ee
Author: Matthew Gantenbein &lt;ganten1998@gmail.com&gt;
Date:   Mon,  1 Jun 2026 14:06:26 -0500

fix: install-driver.sh accepts Y/yes, not just lowercase y

Co-Authored-By: Claude Opus 4.8 (1M context) &lt;noreply@anthropic.com&gt;

</content>
</entry>
<entry>
<id>f617db31be4e553bf588eee355d3a0039dc523ee</id>
<published>2026-06-01T19:04:23Z</published>
<updated>2026-06-01T19:04:23Z</updated>
<title>fix: Hydra device defaults to 2ch (one-ear bug in Vesktop) + record backend</title>
<link rel="alternate" type="text/html" href="commit/f617db31be4e553bf588eee355d3a0039dc523ee.html" />
<author>
<name>Matthew Gantenbein</name>
<email>ganten1998@gmail.com</email>
</author>
<content>commit f617db31be4e553bf588eee355d3a0039dc523ee
parent 374286c5193145a18b81f79a35b976b602e4ff93
Author: Matthew Gantenbein &lt;ganten1998@gmail.com&gt;
Date:   Mon,  1 Jun 2026 14:04:23 -0500

fix: Hydra device defaults to 2ch (one-ear bug in Vesktop) + record backend

ONE-EAR FIX: routing app→Hydra→Vesktop sent only one ear to the other side.
Controlled comparison: normal mic (2ch) ✓, Loopback device (2ch) ✓, Hydra
(16ch) ✗. Measured Hydra&#39;s own capture with an equal-L/R tone → LEFT 0.0600
RIGHT 0.0600 (perfectly balanced), so the imbalance is downstream: WebRTC/
StereoMic downmixing a 16ch device (signal only on ch0/1, ch2-15 silent) to
stereo doesn&#39;t map cleanly. Fix = match the working reference: default the
driver to 2 channels (HYDRA_DRIVER_CHANNELS overrides for DAW use). Rebuild +
reinstall the driver to apply.

RECORD-TO-FILE BACKEND (UX3, daemon side — TUI key still pending):
- tap_shim.m: lock-free SPSC ring in HydraParams (IOProc writes raw pre-gain
  tap, drops+counts on overrun, never blocks the audio thread); publishes tap
  channels + sample rate. hydra_params_size() for a Rust/C layout assertion.
- shim.rs: Recorder with a drain thread → 32-bit-float WAV (header patched on
  finalize); start/stop join the thread + disarm the IOProc before params free.
  Runtime size assert guards struct-layout drift.
- engine + IPC (StartRecording/StopRecording, RouteSummary.recording) + daemon
  (default path ~/Music/Hydra/hydra-&lt;route&gt;-&lt;ts&gt;.wav).
VERIFIED end-to-end: recorded an afplay route → afinfo reports valid WAVE,
2ch/44100/Float32/2.008s/88576 frames; samples have real signal (peak 0.06).

23 tests green, 0 warnings.

Co-Authored-By: Claude Opus 4.8 (1M context) &lt;noreply@anthropic.com&gt;

</content>
</entry>
<entry>
<id>374286c5193145a18b81f79a35b976b602e4ff93</id>
<published>2026-06-01T18:22:22Z</published>
<updated>2026-06-01T18:22:22Z</updated>
<title>UX1: saved presets / quick-routes</title>
<link rel="alternate" type="text/html" href="commit/374286c5193145a18b81f79a35b976b602e4ff93.html" />
<author>
<name>Matthew Gantenbein</name>
<email>ganten1998@gmail.com</email>
</author>
<content>commit 374286c5193145a18b81f79a35b976b602e4ff93
parent 57f248df365636f86328bb8cb584c9e34a4ca735
Author: Matthew Gantenbein &lt;ganten1998@gmail.com&gt;
Date:   Mon,  1 Jun 2026 13:22:22 -0500

UX1: saved presets / quick-routes

Name a whole routing setup and recall it in one keystroke — beats Loopback&#39;s
build-once-then-it&#39;s-stuck model. A preset is a named bundle of SavedRoutes
(reuses the persistence type), stored at ~/Library/Application Support/hydra/
presets.json.

- hydra-core::presets: PresetStore (upsert/get/remove/names), 4 tests.
- engine: refactored restore() → shared apply_routes() + clear_all(), so
  startup-restore and preset-apply share one code path.
- IPC: ListPresets / SavePreset / ApplyPreset / DeletePreset + Presets /
  PresetApplied responses. Daemon resolves bundle-id→live-PID (apps not running
  are skipped, same as restore).
- TUI: generalized the proven rename text-prompt into a typed Prompt
  (RenameDevice | SavePreset); &#39;P&#39; saves current routing, &#39;p&#39; opens a centered
  presets overlay (↑↓ select, ⏎ apply, d delete, esc close). Auto-refresh pauses
  while either modal is open.

Verified end-to-end over the live socket: save → list [&#39;Test Setup&#39;] → stop all
routes → apply restored 2/2 → delete → empty list → missing-preset clean error.
23 tests green, 0 warnings.

Co-Authored-By: Claude Opus 4.8 (1M context) &lt;noreply@anthropic.com&gt;

</content>
</entry>
<entry>
<id>57f248df365636f86328bb8cb584c9e34a4ca735</id>
<published>2026-06-01T18:14:53Z</published>
<updated>2026-06-01T18:14:53Z</updated>
<title>docs: log P3b multi-device plan + why it&#39;s branch-only</title>
<link rel="alternate" type="text/html" href="commit/57f248df365636f86328bb8cb584c9e34a4ca735.html" />
<author>
<name>Matthew Gantenbein</name>
<email>ganten1998@gmail.com</email>
</author>
<content>commit 57f248df365636f86328bb8cb584c9e34a4ca735
parent 0901577694888493cfc9dc03110f5399c1a186dc
Author: Matthew Gantenbein &lt;ganten1998@gmail.com&gt;
Date:   Mon,  1 Jun 2026 13:14:53 -0500

docs: log P3b multi-device plan + why it&#39;s branch-only

Captures the measured scope (78+41 static object-id refs, 22 channel-count
sites incl. ring buffer + realtime path), the refactor plan, and the reason it
stays off main: driver changes can&#39;t be behavior-tested without sudo install +
coreaudiod restart, so the refactor lives on p3b-multi-device and merges only
after a real installed multi-device round-trip passes.

Co-Authored-By: Claude Opus 4.8 (1M context) &lt;noreply@anthropic.com&gt;

</content>
</entry>
<entry>
<id>0901577694888493cfc9dc03110f5399c1a186dc</id>
<published>2026-06-01T04:39:38Z</published>
<updated>2026-06-01T04:39:38Z</updated>
<title>chore: git-ignore driver/build (compiled .driver is build output)</title>
<link rel="alternate" type="text/html" href="commit/0901577694888493cfc9dc03110f5399c1a186dc.html" />
<author>
<name>Matthew Gantenbein</name>
<email>ganten1998@gmail.com</email>
</author>
<content>commit 0901577694888493cfc9dc03110f5399c1a186dc
parent f3d241bfc78dd5f669b8018660d7dd4bfbc0305f
Author: Matthew Gantenbein &lt;ganten1998@gmail.com&gt;
Date:   Sun, 31 May 2026 23:39:38 -0500

chore: git-ignore driver/build (compiled .driver is build output)

The signed Hydra.driver bundle was accidentally tracked since P2; it&#39;s
regenerated by scripts/build-driver.sh. Ignore it like target/ and dist/.

Co-Authored-By: Claude Opus 4.8 (1M context) &lt;noreply@anthropic.com&gt;

</content>
</entry>
<entry>
<id>f3d241bfc78dd5f669b8018660d7dd4bfbc0305f</id>
<published>2026-06-01T04:39:12Z</published>
<updated>2026-06-01T04:39:12Z</updated>
<title>P3a: rename the Hydra device from the TUI (manifest-driven device name)</title>
<link rel="alternate" type="text/html" href="commit/f3d241bfc78dd5f669b8018660d7dd4bfbc0305f.html" />
<author>
<name>Matthew Gantenbein</name>
<email>ganten1998@gmail.com</email>
</author>
<content>commit f3d241bfc78dd5f669b8018660d7dd4bfbc0305f
parent 8a303ac6a762be9cd7e33b8e17bb161945ef6031
Author: Matthew Gantenbein &lt;ganten1998@gmail.com&gt;
Date:   Sun, 31 May 2026 23:39:12 -0500

P3a: rename the Hydra device from the TUI (manifest-driven device name)

The driver now reads its display name from the manifest the daemon writes, so
the virtual device can be renamed without rebuilding the driver. Full chain,
verified end-to-end on the host (no sudo): TUI &#39;n&#39; → SetDeviceName IPC → daemon
writes /Library/Application Support/hydra/devices.json → driver&#39;s reader parses
it → name applied at next coreaudiod load.

- driver/hydra_cfg.{c,h}: dependency-free manifest reader (no JSON lib vendored).
  Unit-tested 6 cases: real name, missing file, escaped quotes (&quot;My \&quot;Cool\&quot;
  Mix&quot;), malformed JSON, top-level-name decoy (anchors to devices[]), empty
  devices. All non-happy paths fall back to the compile-time default, so a
  bad/missing manifest can NEVER produce a broken driver.
- BlackHole.c get_device_name(): reads cached manifest name, falls through to
  the kDevice_Name default. Only edit to upstream; builds clean (0 errors),
  factory symbol intact, universal+signed.
- build-driver.sh compiles hydra_cfg.c alongside BlackHole.c.
- hydrad: SetDeviceName command writes a 1-device manifest; clear error if the
  manifest dir isn&#39;t writable (&quot;is the driver installed?&quot;).
- install-driver.sh: manifest dir is now admin-group-writable + world-readable
  so the user-session daemon can write it AND _coreaudiod can read it.
- TUI: &#39;n&#39; opens a rename prompt (footer text entry, ⏎ apply / esc cancel);
  auto-refresh pauses while typing.

SCOPE: this is P3a (rename). Multiple devices / arbitrary channel counts (P3b)
is the larger refactor — 78 kObjectID_Device + 41 Device2 refs make BlackHole&#39;s
object model fully static — and I won&#39;t half-build it blind since driver changes
can&#39;t be behavior-tested without your sudo install. Name-rename is the verifiable
slice of P3.

19 tests green, 0 warnings.

Co-Authored-By: Claude Opus 4.8 (1M context) &lt;noreply@anthropic.com&gt;

</content>
</entry>
<entry>
<id>8a303ac6a762be9cd7e33b8e17bb161945ef6031</id>
<published>2026-06-01T04:25:17Z</published>
<updated>2026-06-01T04:25:17Z</updated>
<title>fix: single-instance daemon guard (no more competing-daemon pileup)</title>
<link rel="alternate" type="text/html" href="commit/8a303ac6a762be9cd7e33b8e17bb161945ef6031.html" />
<author>
<name>Matthew Gantenbein</name>
<email>ganten1998@gmail.com</email>
</author>
<content>commit 8a303ac6a762be9cd7e33b8e17bb161945ef6031
parent 05eef3c033b90cf482b966a39dfc89d624796a38
Author: Matthew Gantenbein &lt;ganten1998@gmail.com&gt;
Date:   Sun, 31 May 2026 23:25:17 -0500

fix: single-instance daemon guard (no more competing-daemon pileup)

Startup unconditionally removed the socket then bound it, so a second hydrad
would yank the socket from the first and both would run — that&#39;s how 13
daemons accumulated over a session, causing socket contention and flaky reads.

Now startup probes the existing socket with a 500ms Ping: if a live daemon
answers, log &quot;already running&quot; and exit 0 (don&#39;t disturb it); only if nothing
answers (stale socket from a crash) is it removed and rebound. Verified:
daemon #2 refuses with &quot;hydrad already running (protocol v1); exiting&quot;,
daemon #1 keeps serving, exactly 1 process remains. Stale-socket recovery
still works (crashed daemon doesn&#39;t block restart).

19 tests green, 0 warnings.

Co-Authored-By: Claude Opus 4.8 (1M context) &lt;noreply@anthropic.com&gt;

</content>
</entry>
<entry>
<id>05eef3c033b90cf482b966a39dfc89d624796a38</id>
<published>2026-06-01T04:03:04Z</published>
<updated>2026-06-01T04:03:04Z</updated>
<title>fix: usable capture level — makeup-gain default + multiplicative gain steps</title>
<link rel="alternate" type="text/html" href="commit/05eef3c033b90cf482b966a39dfc89d624796a38.html" />
<author>
<name>Matthew Gantenbein</name>
<email>ganten1998@gmail.com</email>
</author>
<content>commit 05eef3c033b90cf482b966a39dfc89d624796a38
parent bfea67a8601b792059b95d180ffc2a1c41663346
Author: Matthew Gantenbein &lt;ganten1998@gmail.com&gt;
Date:   Sun, 31 May 2026 23:03:04 -0500

fix: usable capture level — makeup-gain default + multiplicative gain steps

Routing to Hydra worked after the tap-buffer fix but was unusably quiet:
process taps attenuate ~-20dB and +/- stepped additively by 0.05, so reaching
~10x makeup gain took ~180 presses (the &quot;too quiet even maxed&quot; report).

- New routes default to gain 10.0 (DEFAULT_GAIN): a 0.6 source now lands at
  peak 0.60 out of the Hydra device immediately — verified.
- +/- are now multiplicative (×1.4 ≈ +3dB/press), spanning 0..16 in a few keys.

Verified through the rebuilt installed bundle.

Co-Authored-By: Claude Opus 4.8 (1M context) &lt;noreply@anthropic.com&gt;

</content>
</entry>
<entry>
<id>bfea67a8601b792059b95d180ffc2a1c41663346</id>
<published>2026-06-01T03:49:52Z</published>
<updated>2026-06-01T03:49:52Z</updated>
<title>fix: IOProc read the wrong input buffer — routes captured silence, not the app</title>
<link rel="alternate" type="text/html" href="commit/bfea67a8601b792059b95d180ffc2a1c41663346.html" />
<author>
<name>Matthew Gantenbein</name>
<email>ganten1998@gmail.com</email>
</author>
<content>commit bfea67a8601b792059b95d180ffc2a1c41663346
parent d5a350c4c2c39f2c6f7ee1d3ca43813282f2dee4
Author: Matthew Gantenbein &lt;ganten1998@gmail.com&gt;
Date:   Sun, 31 May 2026 22:49:52 -0500

fix: IOProc read the wrong input buffer — routes captured silence, not the app

THE core bug behind &quot;no sound transfers to Hydra&quot;. The aggregate&#39;s input buffer
list is [sub-device&#39;s own inputs, ..., TAP]. The IOProc read mBuffers[0] (the
sub-device&#39;s inputs) instead of the tap, so:
  - routes to Hydra captured Hydra&#39;s 16 silent input channels → peak 0.0
  - routes to the Scarlett captured the Scarlett&#39;s live mic/line inputs → peak
    1.0, which I&#39;d wrongly reported as &quot;capture works&quot;. It never was.

Fix: read the LAST input buffer (CoreAudio appends the tap stream after the
sub-device streams) and mix its channels onto the output. Diagnosed by
instrumenting per-input-buffer channel counts + peaks: in_ch=[16,2] with the
signal in buffer[1] (the 2ch tap), confirming the layout.

Verified through the INSTALLED signed bundle: afplay (stable PID) → Hydra,
gain 1.0 → peak 0.06, gain 8.0 → peak 0.48 (linear, clean). Process taps
attenuate ~-20dB, so the gain ceiling is raised 2.0→16.0 for makeup gain.

(Earlier loop-based tests used `afplay file; afplay file` which spawns a new
PID per iteration — tapping an already-exited PID was a test artifact, not a
code bug. Single long-lived source confirms the fix.)

19 tests green, 0 warnings.

Co-Authored-By: Claude Opus 4.8 (1M context) &lt;noreply@anthropic.com&gt;

</content>
</entry>
<entry>
<id>d5a350c4c2c39f2c6f7ee1d3ca43813282f2dee4</id>
<published>2026-06-01T02:02:08Z</published>
<updated>2026-06-01T02:02:08Z</updated>
<title>fix: restart coreaudiod via killall, not kickstart (SIP blocks kickstart)</title>
<link rel="alternate" type="text/html" href="commit/d5a350c4c2c39f2c6f7ee1d3ca43813282f2dee4.html" />
<author>
<name>Matthew Gantenbein</name>
<email>ganten1998@gmail.com</email>
</author>
<content>commit d5a350c4c2c39f2c6f7ee1d3ca43813282f2dee4
parent 40d8e66a9cee902179b6b5d23ad1a32e0fe114e6
Author: Matthew Gantenbein &lt;ganten1998@gmail.com&gt;
Date:   Sun, 31 May 2026 21:02:08 -0500

fix: restart coreaudiod via killall, not kickstart (SIP blocks kickstart)

`launchctl kickstart -k system/com.apple.audio.coreaudiod` fails under SIP with
&quot;Operation not permitted&quot;. `sudo killall coreaudiod` is permitted and launchd
respawns it immediately. Updated install-driver.sh, uninstall.sh, and docs.
(The LaunchAgent kickstart in install-agent.sh is in the gui domain — not
SIP-protected — so it stays.)

Co-Authored-By: Claude Opus 4.8 (1M context) &lt;noreply@anthropic.com&gt;

</content>
</entry>
<entry>
<id>40d8e66a9cee902179b6b5d23ad1a32e0fe114e6</id>
<published>2026-06-01T01:55:28Z</published>
<updated>2026-06-01T01:55:28Z</updated>
<title>fix: complete the combine feature + installer (prior 2 commits didn&#39;t build)</title>
<link rel="alternate" type="text/html" href="commit/40d8e66a9cee902179b6b5d23ad1a32e0fe114e6.html" />
<author>
<name>Matthew Gantenbein</name>
<email>ganten1998@gmail.com</email>
</author>
<content>commit 40d8e66a9cee902179b6b5d23ad1a32e0fe114e6
parent 3e18dcf2055188a06a98262e8439e481e83975df
Author: Matthew Gantenbein &lt;ganten1998@gmail.com&gt;
Date:   Sun, 31 May 2026 20:55:28 -0500

fix: complete the combine feature + installer (prior 2 commits didn&#39;t build)

Commits 190a263 and df058d9 were pushed with a non-compiling tree (a cancelled
tool batch left a duplicate combined_label fn in server.rs and a missing
`marked` field on App). This fixes both forward.

Now genuinely verified: cargo build --workspace clean, 19 tests green, 0
warnings; live StartCombined of two audio PIDs → one route, source_count=2,
peak 1.000 (real mixed audio flowing). install.sh/uninstall.sh syntax-checked;
non-sudo stages (release build, Hydra.app sign, universal Hydra.driver build)
all run clean.

Co-Authored-By: Claude Opus 4.8 (1M context) &lt;noreply@anthropic.com&gt;

</content>
</entry>
<entry>
<id>3e18dcf2055188a06a98262e8439e481e83975df</id>
<published>2026-05-31T22:41:05Z</published>
<updated>2026-05-31T22:41:05Z</updated>
<title>build: one-command install.sh / uninstall.sh + README install section</title>
<link rel="alternate" type="text/html" href="commit/3e18dcf2055188a06a98262e8439e481e83975df.html" />
<author>
<name>Matthew Gantenbein</name>
<email>ganten1998@gmail.com</email>
</author>
<content>commit 3e18dcf2055188a06a98262e8439e481e83975df
parent 52b4f527012273d89595572605c099c000e4052e
Author: Matthew Gantenbein &lt;ganten1998@gmail.com&gt;
Date:   Sun, 31 May 2026 17:41:05 -0500

build: one-command install.sh / uninstall.sh + README install section

install.sh orchestrates the full setup in safe order: cargo release build →
sign Hydra.app → load LaunchAgent → build+install driver (the sudo/coreaudiod
step is last and prompts). --no-driver stops before the virtual mic. Symlinks
a `hydra` launcher to /usr/local/bin. uninstall.sh reverses it. README now
leads with ./install.sh and the TUI keymap.

Verified the non-sudo stages run clean end to end: release binaries built,
Hydra.app signs, universal Hydra.driver builds. The sudo stages (agent load,
driver install) are intentionally left for the user to trigger.

Co-Authored-By: Claude Opus 4.8 (1M context) &lt;noreply@anthropic.com&gt;

</content>
</entry>
<entry>
<id>52b4f527012273d89595572605c099c000e4052e</id>
<published>2026-05-31T22:39:57Z</published>
<updated>2026-05-31T22:39:57Z</updated>
<title>feat: combine multiple sources into one route (Loopback &quot;combine&quot;)</title>
<link rel="alternate" type="text/html" href="commit/52b4f527012273d89595572605c099c000e4052e.html" />
<author>
<name>Matthew Gantenbein</name>
<email>ganten1998@gmail.com</email>
</author>
<content>commit 52b4f527012273d89595572605c099c000e4052e
parent acbe771cab891ab484e45422e7042e32e914eaf6
Author: Matthew Gantenbein &lt;ganten1998@gmail.com&gt;
Date:   Sun, 31 May 2026 17:39:57 -0500

feat: combine multiple sources into one route (Loopback &quot;combine&quot;)

The Obj-C shim already took an array of process objects, so multi-source was a
plumbing job: MonitorRoute::start_combined taps N PIDs via one CATapDescription
(initStereoMixdownOfProcesses), Engine::start_combined tracks source_count +
bundle_ids, new StartCombined IPC command, daemon combined_label (&quot;Safari +
Spotify +1 → Hydra&quot;). TUI: space marks apps (✓), c combines them (or the
highlighted app if none marked); marks shown in amber.

VERIFIED live: combined two afplay PIDs → one route, source_count=2,
peak=0.247 (real mixed audio flowing). Single-source persistence unchanged
(combined routes aren&#39;t persisted yet — would need atomic multi-bundle restore).

19 tests green, 0 warnings.

Co-Authored-By: Claude Opus 4.8 (1M context) &lt;noreply@anthropic.com&gt;

</content>
</entry>
<entry>
<id>acbe771cab891ab484e45422e7042e32e914eaf6</id>
<published>2026-05-31T22:29:56Z</published>
<updated>2026-05-31T22:29:56Z</updated>
<title>feat: real app names + foreground/background ranking in the app list</title>
<link rel="alternate" type="text/html" href="commit/acbe771cab891ab484e45422e7042e32e914eaf6.html" />
<author>
<name>Matthew Gantenbein</name>
<email>ganten1998@gmail.com</email>
</author>
<content>commit acbe771cab891ab484e45422e7042e32e914eaf6
parent 9ab9fcdd09a58c8ab456297248205a6057c0e064
Author: Matthew Gantenbein &lt;ganten1998@gmail.com&gt;
Date:   Sun, 31 May 2026 17:29:56 -0500

feat: real app names + foreground/background ranking in the app list

Addresses live feedback: the picker showed &quot;client&quot;/&quot;pid 1234&quot; and a wall of
system daemons. Now each audio process is resolved via NSRunningApplication
(localizedName + activationPolicy) in the Obj-C shim, falling back to libproc&#39;s
process name, then bundle-id tail, then pid. Adds a `kind` rank (0 foreground
Dock app, 1 menu-bar/accessory, 2 plain process) on AudioApp; the daemon sorts
by kind then name. Verified live: REAPER / Safari / Vesktop now show real names
at the top; daemons sink to the bottom.

TUI: hides kind-2 background processes by default (real apps only); &#39;a&#39; toggles
&quot;all apps&quot;. Foreground apps render in full fg, helpers dimmed. Links AppKit.

19 tests green, 0 warnings.

Co-Authored-By: Claude Opus 4.8 (1M context) &lt;noreply@anthropic.com&gt;

</content>
</entry>
<entry>
<id>9ab9fcdd09a58c8ab456297248205a6057c0e064</id>
<published>2026-05-31T22:20:58Z</published>
<updated>2026-05-31T22:20:58Z</updated>
<title>docs: TESTING.md — verified-vs-user-gated test guide</title>
<link rel="alternate" type="text/html" href="commit/9ab9fcdd09a58c8ab456297248205a6057c0e064.html" />
<author>
<name>Matthew Gantenbein</name>
<email>ganten1998@gmail.com</email>
</author>
<content>commit 9ab9fcdd09a58c8ab456297248205a6057c0e064
parent 17f56429aeb5c07172994fb0196291b626d462d5
Author: Matthew Gantenbein &lt;ganten1998@gmail.com&gt;
Date:   Sun, 31 May 2026 17:20:58 -0500

docs: TESTING.md — verified-vs-user-gated test guide

Lays out the three test stages (tap→speakers needs only TCC; driver install
needs sudo; agent/widget optional) with exact commands, and what&#39;s already
verified headlessly vs. what needs a GUI prompt / sudo / coreaudiod restart.

Co-Authored-By: Claude Opus 4.8 (1M context) &lt;noreply@anthropic.com&gt;

</content>
</entry>
<entry>
<id>17f56429aeb5c07172994fb0196291b626d462d5</id>
<published>2026-05-31T22:18:17Z</published>
<updated>2026-05-31T22:18:17Z</updated>
<title>P4 (persistence): routes survive daemon restart</title>
<link rel="alternate" type="text/html" href="commit/17f56429aeb5c07172994fb0196291b626d462d5.html" />
<author>
<name>Matthew Gantenbein</name>
<email>ganten1998@gmail.com</email>
</author>
<content>commit 17f56429aeb5c07172994fb0196291b626d462d5
parent c53f2f79868d66e9f5c823b4fb38a1acfe4c4414
Author: Matthew Gantenbein &lt;ganten1998@gmail.com&gt;
Date:   Sun, 31 May 2026 17:18:17 -0500

P4 (persistence): routes survive daemon restart

hydra-core::config — Config/SavedRoute persisted as JSON at
~/Library/Application Support/hydra/config.json (house convention: silent
Default on load, best-effort save). Apps keyed by bundle-id (stable across
launches; PIDs aren&#39;t). 4 tests: disk round-trip, missing→default,
corrupt→default, path.

Engine: Entry now tracks bundle_id + output_uid; to_config() projects live
routes to a Config (skips bundle-less helper procs); restore() re-establishes
saved routes by resolving bundle-id→live-PID (apps not running are skipped).

Daemon: persist() after every StartMonitor/StopRoute/SetGain/SetMute; on
startup loads config and restores, resolving bundle-ids via the audio-process
list. VERIFIED: seeded a 1-route config, daemon logged &quot;restored 0/1&quot; for a
not-running app (correct skip behaviour).

Also corrects the prior commit&#39;s test-count wording: actual suite is 19 tests
(was mislabelled 20), all green; 0 warnings.

Co-Authored-By: Claude Opus 4.8 (1M context) &lt;noreply@anthropic.com&gt;

</content>
</entry>
<entry>
<id>c53f2f79868d66e9f5c823b4fb38a1acfe4c4414</id>
<published>2026-05-31T22:01:01Z</published>
<updated>2026-05-31T22:05:01Z</updated>
<title>P2 + P5 + output picker: real driver build, query mode, SketchyBar widget</title>
<link rel="alternate" type="text/html" href="commit/c53f2f79868d66e9f5c823b4fb38a1acfe4c4414.html" />
<author>
<name>Matthew Gantenbein</name>
<email>ganten1998@gmail.com</email>
</author>
<content>commit c53f2f79868d66e9f5c823b4fb38a1acfe4c4414
parent 6aab163e6ee90f2ef457ac6b3b5bb3471ed82827
Author: Matthew Gantenbein &lt;ganten1998@gmail.com&gt;
Date:   Sun, 31 May 2026 17:01:01 -0500

P2 + P5 + output picker: real driver build, query mode, SketchyBar widget

Driver (P2): vendored BlackHole v0.6.1 (GPL-3.0, driver/upstream, .git stripped).
An AudioServerPlugIn is just a loadable bundle, so scripts/build-driver.sh
compiles BlackHole.c with clang (Command Line Tools — no Xcode) into a UNIVERSAL
(arm64+x86_64), ad-hoc-signed driver/build/Hydra.driver. Rebrand (name Hydra,
com.ganten.hydra.driver, Ganten, 16ch) is pure -D overrides; zero upstream edits.
Needs -framework Accelerate (BlackHole uses vDSP for volume). VERIFIED: builds
both arches, codesign --strict valid, _BlackHole_Create factory symbol exported.
install-driver.sh = user-run sudo + coreaudiod restart.

manifest module: writes /Library/Application Support/hydra/devices.json
(world-readable; coreaudiod can&#39;t read ~) — wired for P3.

Output picker: TUI lists output-capable devices; &#39;o&#39; cycles the route target
(speakers to monitor, or &quot;Hydra&quot; to feed the virtual device). This + the engine&#39;s
output_uid is the route-into-Hydra path.

P5 query mode: `hydra query [--json|--sketchybar|plain]`, same hydra-ipc contract.
VERIFIED live: idle → state=idle routes=0 exit 0; plain/json correct.
SketchyBar widget (scripts/sketchybar/hydra.sh + README): amber when routing, dim
idle/down, red on clip — verified via stubbed sketchybar. Daemon best-effort fires
`sketchybar --trigger hydra_route_change` on start/stop/mute.

Reconciled client.rs (free fn request) + main.rs (refresh loop + full keymap) +
ui.rs (two-pane browser) that a cancelled tool batch had left inconsistent.
20 tests green, 0 warnings.

Co-Authored-By: Claude Opus 4.8 (1M context) &lt;noreply@anthropic.com&gt;

</content>
</entry>
<entry>
<id>6aab163e6ee90f2ef457ac6b3b5bb3471ed82827</id>
<published>2026-05-31T16:19:26Z</published>
<updated>2026-05-31T16:19:26Z</updated>
<title>P2 (scaffold): driver/ fork plan for the Hydra HAL driver</title>
<link rel="alternate" type="text/html" href="commit/6aab163e6ee90f2ef457ac6b3b5bb3471ed82827.html" />
<author>
<name>Matthew Gantenbein</name>
<email>ganten1998@gmail.com</email>
</author>
<content>commit 6aab163e6ee90f2ef457ac6b3b5bb3471ed82827
parent f6f94739d09834986837d563529fbca7b76c5559
Author: Matthew Gantenbein &lt;ganten1998@gmail.com&gt;
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&#39;t build a GPL fork against
unverified source. README captures the exact vendoring steps, the key finding
that BlackHole &gt;=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&#39;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) &lt;noreply@anthropic.com&gt;

</content>
</entry>
<entry>
<id>f6f94739d09834986837d563529fbca7b76c5559</id>
<published>2026-05-31T16:15:48Z</published>
<updated>2026-05-31T16:15:48Z</updated>
<title>P1: add signing scripts lost in an earlier cancelled batch</title>
<link rel="alternate" type="text/html" href="commit/f6f94739d09834986837d563529fbca7b76c5559.html" />
<author>
<name>Matthew Gantenbein</name>
<email>ganten1998@gmail.com</email>
</author>
<content>commit f6f94739d09834986837d563529fbca7b76c5559
parent f81577cea054c009d7545b755b1f119e34f55668
Author: Matthew Gantenbein &lt;ganten1998@gmail.com&gt;
Date:   Sun, 31 May 2026 11:15:48 -0500

P1: add signing scripts lost in an earlier cancelled batch

scripts/bundle.sh (build + ad-hoc/Dev-ID codesign Hydra.app with the
audio-input entitlement + NSAudioCaptureUsageDescription Info.plist),
scripts/install-agent.sh (GUI-session LaunchAgent), plus Info.plist and
hydrad.entitlements. Verified: bundle signs clean, identity com.ganten.hydra,
entitlement embedded, bundled daemon launches and serves enumeration.

Co-Authored-By: Claude Opus 4.8 (1M context) &lt;noreply@anthropic.com&gt;

</content>
</entry>
<entry>
<id>f81577cea054c009d7545b755b1f119e34f55668</id>
<published>2026-05-31T16:13:34Z</published>
<updated>2026-05-31T16:13:34Z</updated>
<title>P1: CoreAudio engine — device/app enumeration, per-app tap→monitor, signing</title>
<link rel="alternate" type="text/html" href="commit/f81577cea054c009d7545b755b1f119e34f55668.html" />
<author>
<name>Matthew Gantenbein</name>
<email>ganten1998@gmail.com</email>
</author>
<content>commit f81577cea054c009d7545b755b1f119e34f55668
parent 95cb7f1878f3ea08e22d39f18fb2989ca4ecc1e4
Author: Matthew Gantenbein &lt;ganten1998@gmail.com&gt;
Date:   Sun, 31 May 2026 11:13:34 -0500

P1: CoreAudio engine — device/app enumeration, per-app tap→monitor, signing

hydra-core gains a macOS CoreAudio layer (gated, FFI in src/ffi):
- hal.rs: device enumeration (uid/name/in+out channels/default) via hand-defined
  HAL property selectors over coreaudio-sys — verified against real hardware
  (Scarlett 18i20, built-ins, even the running Loopback virtual mic).
- process.rs: audio-process enumeration + PID→process-object translation
  (macOS 14.4+ tap targets); live count tracks afplay starting/stopping.
- tap_shim.m + build.rs: an Obj-C shim builds the CATapDescription + private
  aggregate (output sub-device + tap list) + IOProc that copies tapped audio to
  the output with live gain/mute and per-channel peak. coreaudio-sys lacks the
  14.4 tap symbols, so the shim owns that surface against the real SDK.
- shim.rs + engine.rs: MonitorRoute (RAII teardown in the order coreaudiod needs)
  and an Engine the daemon drives.

IPC: ListDevices/ListApps/StartMonitor/StopRoute/SetGain/SetMute + AudioDevice/
AudioApp/RouteSummary(gain,muted,peak). Daemon wires them to the engine.

TUI: two-pane browser (audio apps | live routes), ⏎ to monitor, m/+/-/d controls,
peak meters, 500ms refresh, Navi-themed.

Signing: scripts/bundle.sh builds+signs Hydra.app (Info.plist with
NSAudioCaptureUsageDescription, audio-input entitlement); install-agent.sh runs it
as a GUI LaunchAgent. Required because process taps need a TCC-authorizable identity.

Enumeration + the full control path are verified over the socket. Actual capture
audio is gated on the user approving the one-time Audio Recording prompt, which
needs the signed bundle (a bare cargo binary can&#39;t be authorized).

Builds clean (0 warnings), tests green.

Co-Authored-By: Claude Opus 4.8 (1M context) &lt;noreply@anthropic.com&gt;

</content>
</entry>
<entry>
<id>95cb7f1878f3ea08e22d39f18fb2989ca4ecc1e4</id>
<published>2026-05-31T15:50:26Z</published>
<updated>2026-05-31T15:50:26Z</updated>
<title>P0: workspace skeleton — daemon/TUI handshake over Unix socket</title>
<link rel="alternate" type="text/html" href="commit/95cb7f1878f3ea08e22d39f18fb2989ca4ecc1e4.html" />
<author>
<name>Matthew Gantenbein</name>
<email>ganten1998@gmail.com</email>
</author>
<content>commit 95cb7f1878f3ea08e22d39f18fb2989ca4ecc1e4
Author: Matthew Gantenbein &lt;ganten1998@gmail.com&gt;
Date:   Sun, 31 May 2026 10:50:26 -0500

P0: workspace skeleton — daemon/TUI handshake over Unix socket

Four-crate workspace (hydra-ipc / hydra-core / hydrad / hydra) replacing
Loopback, structured like valentine/ivory-rust. NDJSON wire protocol with
zero macOS deps keeps the ratatui TUI off CoreAudio. Daemon binds the control
socket and answers Ping/GetState; TUI renders the Navi-themed shell with live
connection status. Theme is a swappable TOML (Navi default).

7/7 tests green; builds clean.

Co-Authored-By: Claude Opus 4.8 (1M context) &lt;noreply@anthropic.com&gt;

</content>
</entry>
</feed>
