Skip to content

feat: Audio Unit plugin support for per-app and per-device effects#305

Open
iscle wants to merge 1 commit into
ronitsingh10:mainfrom
iscle:feature/audio-unit-plugins
Open

feat: Audio Unit plugin support for per-app and per-device effects#305
iscle wants to merge 1 commit into
ronitsingh10:mainfrom
iscle:feature/audio-unit-plugins

Conversation

@iscle

@iscle iscle commented May 21, 2026

Copy link
Copy Markdown
Contributor

Summary

This PR implements support for Apple's Audio Unit plugins. They can be toggled per-device and per-app basis. Changes persist across reboots, and custom UIs can be opened. The functionality overall is really similar to other competitor paid audio software for macOS ;)

Screenshots

imageimageimage

Features

  • Add support for third-party and system Audio Unit effect plugins, applied per-app or per-device (similar to SoundSource)
  • Plugin discovery from /Library/Audio/Plug-Ins/Components and ~/Library/Audio/Plug-Ins/Components with live detection of newly installed plugins via kAudioComponentRegistrationsChangedNotification
  • RT-safe AU hosting with non-interleaved stereo rendering, pre-allocated deinterleave buffers, monotonic sample time tracking, and heap-allocated AudioBufferList
  • Custom Cocoa AU views loaded via kAudioUnitProperty_CocoaUI with IMP-based invocation; falls back to AUGenericView when unavailable
  • Right-click context menu on effects to switch between custom and generic interface
  • Hierarchical plugin picker with search, favorites (with namespaced ForEach IDs for correct SwiftUI identity), and crash history warnings
  • Effect chain view with per-effect enable/disable, global bypass toggle, factory preset selection, and remove
  • Failed plugin instantiations tracked and shown with warning icon
  • AU parameter state persisted on plugin window close and app quit via ClassInfo plist
  • Bypass state persisted across app restarts
  • Favorites and crash history stored as observable AudioEngine state
  • Crash guard integration: tracks active plugins via FNV-1a hashes in async-signal-safe buffers, writes crash file via POSIX on signal, auto-disables offending plugins on next launch
  • Device AU chains reload correctly on device switch (crossfade, fallback, multi-device)
  • Tail time tracking: continues rendering through AU chain after input goes silent (reverb/delay tails fade naturally)
  • Bypass respects tail time (skips tail rendering when bypassed)
  • New chain rebuilds preserve bypass state
  • AUChainState model consolidates entries, bypass, and failedEntryIDs per scope
  • AudioEngine owns all AU observable state (appAU/deviceAU dictionaries); SettingsManager handles persistence only
  • Popover favorites use @State with namespaced IDs (NSPanel observation boundary prevents @Observable tracking)
  • 45 new unit tests covering descriptor codable, scanner discovery, AU instantiation, RT rendering (lowpass attenuation + reverb tail), bypass passthrough, settings persistence, and crash history

Signal flow

Volume ramp → EQ → AutoEQ → [Per-App AU Chain] → [Per-Device AU Chain] → Loudness EQ → Loudness Comp → SoftLimiter

Test plan

  • Build succeeds with zero errors
  • All 857 existing + 45 new unit tests pass (0 failures)
  • AU rendering verified: AULowPassFilter attenuates 10kHz signal by >40dB at 200Hz cutoff
  • AUReverb2 impulse response produces reverb tail with 100% wet mix
  • Bypass toggle passes audio through unmodified
  • Disabled host passes audio through unchanged
  • Settings persistence round-trip (app chains, device chains, favorites, crash history, bypass state)
  • Factory preset selection updates UI and persists
  • AU parameter changes saved on window close and app quit
  • Favorite star updates immediately in popover
  • Custom Cocoa view loads for AUs that support it; generic fallback works
  • Manual: switch output device with AU effects active, verify no glitches and device chain reloads
  • Manual: add effect to device, verify all apps routed to it get the effect applied

🤖 Generated with Claude Code

@iscle iscle force-pushed the feature/audio-unit-plugins branch 6 times, most recently from 9f9b282 to 670c1aa Compare May 22, 2026 00:22
Enable third-party and system AU effect plugins to be loaded and applied
to individual applications or output devices, similar to SoundSource.

Audio pipeline:
- Non-interleaved stereo rendering with pre-allocated deinterleave buffers
- RT-safe AU hosting via AudioUnitRender with monotonic sample time tracking
- Immutable AUEffectChain with atomic swap + deferred destruction (500ms)
- Signal flow: EQ → AutoEQ → [Per-App AU] → [Per-Device AU] → Loudness → Limiter
- Tail time tracking for reverb/delay effects (continues rendering after silence)
- Crossfade support with independent AU instances per tap
- Device AU chains reload correctly on device switch

Plugin management:
- AUPluginScanner discovers kAudioUnitType_Effect and kAudioUnitType_MusicEffect
- Live detection via kAudioComponentRegistrationsChangedNotification
- Factory preset enumeration and selection per effect
- AU parameter state persisted on window close and app quit (ClassInfo plist)
- Crash guard integration: FNV-1a hash tracking, crash file write via POSIX,
  auto-disable offending plugins on next launch
- Full persistence: per-app chains, per-device chains, bypass state, favorites,
  crash history

UI:
- Hierarchical plugin picker with search, favorites (with namespaced ForEach IDs),
  and crash warnings
- Effect chain view with enable/disable, bypass, factory presets, remove
- Failed plugin instantiation shown with warning icon
- AUGenericView floating windows for parameter editing
- Device FX button with ExpandableGlassRow integration

Architecture:
- AUChainState model consolidates entries, bypass, and failedEntryIDs
- AudioEngine owns observable AU state (appAU/deviceAU dictionaries);
  SettingsManager handles persistence only
- Favorites and crash history use @Observable-tracked closures for views,
  with @State in popover content (NSPanel observation boundary)

Includes 45 unit tests covering descriptor codable, scanner discovery,
AU instantiation, RT rendering (lowpass attenuation, reverb tail),
bypass passthrough, settings persistence, and crash history.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@iscle iscle force-pushed the feature/audio-unit-plugins branch from 670c1aa to 7db9469 Compare May 22, 2026 00:29
@iscle iscle marked this pull request as ready for review May 22, 2026 00:39
@evkaw

evkaw commented Jun 10, 2026

Copy link
Copy Markdown

Is it possible to add the option to add the FX onto any app instead like in SoundSource instead of assigning it to a sound device? Thank you.

@nifhanif

Copy link
Copy Markdown

Please consider including this in the next release.

@mrbeats09

Copy link
Copy Markdown

+1 on that, still waiting on this feature a month later

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants