Skip to content

Releases: mauriciomorkun/AudioRouterNow

v3.4.4 — Non-ASCII device UID fix

Choose a tag to compare

@mauriciomorkun mauriciomorkun released this 30 Jun 19:17

Fixed

  • Devices whose CoreAudio UID contains non-ASCII characters (e.g. CJK serial-number characters in the Pebble V3) are now found and routed correctly. The Python→C control protocol previously serialised non-ASCII UIDs as \uXXXX escape sequences, which the C helper copied verbatim and then failed to match against the device's real UTF-8 UID ("Device not found" despite the device being present). _send_no_lock() now sends real UTF-8 bytes (ensure_ascii=False).

Reported by bogdanw (MacRumors). No action required — update normally.

Full changelog: https://github.com/mauriciomorkun/AudioRouterNow/blob/main/CHANGELOG.md

v3.4.3 — Persistent Menu for Everyone + Uninstall Fix

Choose a tag to compare

@mauriciomorkun mauriciomorkun released this 30 Jun 05:18

AudioRouterNow — Release Notes

How to read this document

Each release contains two sections:

  • For Everyone — Plain language. What changed, whether it affects you, and what (if anything) you need to do.
  • For Power Users — Technical details: root causes, implementation notes, affected components. Skip this if you just want to know if you should update.

v3.4.3 — June 30, 2026

For Everyone

The persistent menu is now on by default — and the uninstaller no longer freezes.

This release makes the improved menu interface (which stays open while you work) the standard experience for all users, including those updating from an earlier version. It also fixes the uninstaller, which could freeze the app for 30+ seconds and sometimes fail to quit afterward.

  • Persistent menu for everyone. The menu that stays open while you select devices, change sample rates, or explore settings is now the default for all users — including those who had already installed an earlier version. No configuration needed.
  • Uninstaller no longer freezes. Clicking "Uninstall AudioRouterNow…" previously caused a long freeze (macOS spinning wheel, 30+ seconds) because the uninstall process ran on the app's main thread. It now runs in the background — the menu bar shows "Uninstalling…" and the app quits cleanly once done.
  • Double-click protection on Uninstall. Since the menu stays open, it was possible to accidentally trigger the uninstaller twice. This is now prevented.

No action required. Update normally.

For Power Users

Fix Component Root Cause Resolution
NSPopover as default engine/config.py use_popover_menu defaulted to False; new installs always received the legacy NSMenu. Default flipped to True in both the AppConfig dataclass field and from_dict().
One-time migration engine/config.py Existing users had use_popover_menu: false already persisted in ~/.audiorouter/config.json; a default change alone wouldn't reach them. popover_migrated flag added to AppConfig (default True for new installs; False sentinel for legacy configs). On first load of a pre-3.4.3 config (key absent → data.get("popover_migrated", False) = False), from_dict() forces use_popover_menu=True and sets popover_migrated=True. load_config() immediately persists via save_config() to survive force-quit/crash. After migration, a manual use_popover_menu: false is respected (no re-override).
Uninstall main-thread freeze engine/menu_bar_app.py _uninstall() called first_launch.uninstall_all() synchronously on the main thread. uninstall_all() contains time.sleep(2.0) + subprocess.run(osascript + killall coreaudiod, timeout=60) — together ~30 s of main-thread blocking → macOS spinning wheel, rumps.quit_application() not reached reliably. _uninstall() now starts a daemon worker thread (arn-uninstall) that runs uninstall_all(). A rumps.Timer (0.3 s interval) in _uninstall_poll_result() collects the result on the main thread and calls quit_application(). os._exit(0) added as a 3 s hard-exit fallback (triggered only if terminate_() somehow doesn't propagate). killall coreaudiod shell command is unchanged.
Reentrancy guard engine/menu_bar_app.py NSPopover stays open after a click, making it possible to invoke _uninstall() a second time while the worker is already running — spawning a second thread and a second osascript admin prompt. self._uninstalling flag checked at entry; set on entry, cleared on cancel/error in _uninstall_poll_result(). Success path quits the app, so no reset needed there.

Audit: All fixes passed dual Opus audit (@root-cause-analyst + @validator, parallel, both Opus) in a single iteration with zero critical and zero major issues. One cosmetic finding (getattr fallback) resolved in cleanup commit.

Commits: 55bf7f0 (NSPopover default), 60331cd (uninstall background thread), 6101587 (reentrancy guard + audit cleanup), 5d43f06 (one-time migration v3.4.3), 6689cfb (getattr simplification)


v3.4.2 — June 29, 2026

For Everyone

Audio reliability fixes plus a friendlier, more informative menu.

This release fixes two audio issues that could leave secondary outputs silent, and reworks the menu so it stays open while you work and explains its own status.

Audio fixes:

  • All your outputs are now audible after routing. When AudioRouterNow takes over your system audio, it now carries your current volume level across to every device it routes to, instead of leaving them at whatever (sometimes silent) hardware level they were set to. Outputs you've deliberately set loud are left exactly as they are.
  • No more brief drop-outs when adding a third output. On setups with three or more audio outputs (common on Mac mini), adding another output could cause the others to cut out for about ten seconds while macOS reconfigured its audio transport internally. AudioRouterNow now rides through that transition smoothly. Genuine audio failures are still recovered automatically.

Menu & status improvements:

  • The menu stays open while you set things up. Previously the dropdown closed after every single click, so selecting several outputs or changing sample rates meant reopening it again and again. The menu now stays open as you work and closes when you click away.
  • The menu bar icon no longer cries wolf. If one of your configured devices is unplugged but audio is still routing fine to the others, the icon now stays green instead of turning orange. It only turns orange when no outputs are available at all. The menu still notes "(N unavailable)" so you stay informed.
  • New: Help → Status Guide. A built-in colour legend explains exactly what 🟢 green, 🟡 orange, and 🔴 red mean and when each appears — no need to guess or look it up.

No action required. Update normally.

For Power Users

Audio fixes

Fix ID Component Root Cause Resolution
W2-1 H7 engine/audio_device_control.py, engine/menu_bar_app.py When "Audio Router" becomes the system default, set_default_output_volume() drives only the virtual device's volume_q16. Physical fan-out targets retain their frozen HW volume (often near zero) — silence despite a working IOProc. bogdanw confirmed: pre-selecting the internal speaker (already loud) → audible; U3277WB/Pebble V3 (frozen low) → silent. get_device_volume_scalar() / set_device_volume_scalar() read/write per-device HW volume via kAudioHardwareServiceDeviceProperty_VirtualMainVolume (falling back to kAudioDevicePropertyVolumeScalar per channel). equalize_volume_after_switch(prev_level, target_uids) called at all three switch sites: reads previous default's level before switch, sets virtual device to that level after switch, raises any physical target below LOW_VOLUME_THRESHOLD (15%) to raise_to (or COMFORTABLE_FALLBACK_VOLUME = 50% if prev level itself was sub-15%). Devices without software volume control are skipped silently (get_device_volume_scalar returns -1.0). Devices ≥15% are left untouched (user intent preserved). All CoreAudio calls on main thread; CFStrings released.
W2-2 H8 engine/healer.py, engine/menu_bar_app.py Adding BuiltInSpeakerDevice as a 3rd output to the AVAudioSession forces coreaudiod to restart the IOWorkLoop (HALC_ProxyIOContext:1593 "ending the transport"), transiently stalling already-running outputs. The healer's 1600 ms stall window (1000 ms soft-stall + 3×200 ms persist polls) detected this as a real failure and called reconnect_output — producing a visible ~10 s "Output removed/re-added" churn cycle. notify_output_added() in Healer records time.monotonic() of each genuine output add (detected by set-diff in _apply_active_outputs). Grace-period check in _process_output suppresses reconnect_output for GRACE_PERIOD_S = 2.0 s after any add, provided ioproc_age_ms ≤ HARD_STALL_MS (5000 ms). Hard stalls (device truly gone) still heal immediately even during the window. State is thread-safe under the existing self._lock.

Audit: Both audio fixes passed two independent line-by-line audits (SuperClaude root-cause-analyst + Claude validator, both Opus) in a single iteration with zero critical and zero major issues.

UI / UX changes

Change ID Component Detail
NSPopover menu use_popover_menu engine/menu_bar_app.py The rumps/NSMenu dropdown is replaced by a persistent NSPopover toggled from the NSStatusItem, gated behind a use_popover_menu flag. The popover stays open across clicks (multi-device selection, sample-rate changes, Help navigation) and dismisses only on outside click (transient behavior). All callbacks and dynamic updates preserved; all AppKit calls guarded to the main thread.
NSPopover hardening 3 warnings engine/menu_bar_app.py Follow-up audit fixes: (1) status rows are clickable inside the popover — restart_helper / reinstall_driver / switch_audio dispatched via action_key; (2) the status line updates live while the popover is open (refresh() in _update_status_ui(), main-thread safe); (3) flicker guard on icon toggle — if the popover is shown, close only (no reopen), with a 0.15 s monotonic debounce against the popoverDidClose_ transient-dismiss race.
NSPopover checkbox fix UX engine/popover_menu.py, engine/menu_bar_app.py All Row items defaulted to kind="item", which _viewForRow_ rendered as NSSwitchButton (checkbox). Pure action items (Quit, Status Guide, Open documentation, Check for Updates, Save Diagnostic Report, Uninstall, Support, System Audio toggle, footer) do not have a persistent checked state and looked wrong with checkboxes. Added `ki...
Read more

AudioRouterNow v3.4.2

Choose a tag to compare

@mauriciomorkun mauriciomorkun released this 29 Jun 19:03

What's new in v3.4.2

🎵 Audio Fixes

  • Volume preserved on routing switch — All fan-out outputs now receive your current volume level when AudioRouterNow switches. Previously outputs could stay frozen at a near-zero hardware volume.
  • No more dropout when adding a 3rd output — A 2-second grace period prevents the healer from misidentifying a normal CoreAudio transport restart as a failure. Common on Mac mini setups.

🖥️ Menu Improvements

  • Persistent menu — The menu now stays open after each click (NSPopover). Select multiple outputs and change sample rates without reopening.
  • Channel pairs always visible — Multi-channel device outputs (Ch 1-2, Ch 3-4, …) are now shown grayed-out even before activating a device, so you can see what's available upfront.
  • Green icon when routing is fine — The icon no longer turns orange when a configured device is unplugged but audio is still routing to other devices. Orange = truly broken.
  • Help → Status Guide — A built-in colour legend explains 🟢 green, 🟡 orange, and 🔴 red states.

🔧 Under the Hood

  • Config saves now preserve unknown fields — feature flags from newer versions survive a round-trip through older installed versions
  • Single-source version management — all version strings derived from one place
  • Brand logo assets added to repository

SHA-256: `a805adcf8cc8f3578180f0f520af7bd2f24d8d6885018519a2ceeec7e8fc6d3e`

System requirements: macOS 11.0+, Apple Silicon (M1 or later)


Full technical release notes: [RELEASE_NOTES.md](https://github.com/$(gh repo view --json nameWithOwner -q .nameWithOwner)/blob/main/RELEASE_NOTES.md)

AudioRouterNow v3.4.0

Choose a tag to compare

@mauriciomorkun mauriciomorkun released this 14 Jun 10:30

AudioRouterNow v3.4.0 — June 13, 2026

Critical fix: No audio after fresh installation.

After a fresh install from the DMG, AudioRouterNow appeared to run correctly (green icon, output device selected, system audio set to Audio Router) but produced no sound. This release fixes the root cause.

What's Fixed

  • Audio now works after fresh installation — A macOS-specific limitation made it impossible for the audio driver to access the shared memory channel that carries audio between components. The driver was silently dropping all audio frames.
  • Audio clock deadlock resolved — Even after the access issue, a circular dependency in the audio timing mechanism prevented audio from flowing. The clock now runs freely, as recommended by Apple's CoreAudio documentation.
  • Zombie helper prevention — If an old helper process from a previous version is still running after an update, the app now detects it, shuts it down, and starts a fresh one automatically.

No action required beyond updating. All fixes are automatic on first launch.

Installation

  1. Download AudioRouterNow.dmg
  2. Open the DMG and drag AudioRouterNow to your Applications folder
  3. Launch from Applications (right-click → Open on first launch if needed)

Requires: macOS 11.0 (Big Sur) or later · Apple Silicon or Intel

Verify Download

SHA-256: a4e92463b9e313eed23234ec2886ede596804b4dbf0d732b3263f0b32fdf3fe7

shasum -a 256 AudioRouterNow.dmg

Full technical changelog in RELEASE_NOTES.md.

❤️ If this app saves you money on audio software, consider buying me a coffee.