Skip to content

About

No description, website, or topics provided.

Resources

Stars

1 star

Watchers

0 watching

Forks

Repository files navigation

Plugger

Native macOS menu-bar Audio Unit effect host. Open plugger2.xcodeproj, select the plugger2 scheme, and Run. Product name: Plugger. Requires macOS 14.2+. Swift 6, Apple frameworks, no package dependencies.

Plugger menu-bar app showing stereo meters and a chain of Audio Unit effects

Verification status — 18 September 2026

  • Debug and Release builds succeeded with Xcode 27 / macOS 27 SDK, targeting macOS 14.2.
  • Eight XCTest business-logic/error-description tests passed.
  • Offline production-renderer tests passed with Apple's actual AULowpass: stereo dry transfer, audible-band filtering, per-insert/global bypass ramps, AU render-error fallback, and invalid-input failure signaling.
  • The locally signed app launched and remained running. The built plist includes LSUIElement and NSAudioCaptureUsageDescription.
  • Not yet production-validated: the live capture permission dialog, audible end-to-end routing, physical device disconnects, Bluetooth/AirPlay, AUv3 service crashes, editor interaction, popup dismissal, appearance and accessibility across OS versions. Computer-use automation timed out; do not interpret compilation or offline tests as acceptance of these cases.

Render/input and popup fixes

The first launched Debug process predated the AUv2 input callback fix. Offline reproduction with the connection deliberately removed produced Pro-Q 3 error -10876 (kAudioUnitErr_NoConnection) and excessive ValhallaRoom output. The current renderer connects AUv2 input explicitly, isolates the dry/input buffers, and clears output scratch buffers before every render. Installed Pro-Q 3 and ValhallaRoom now pass the offline six-second tone/tail regression (input peak 0.03; output peaks 0.03 and approximately 0.0347 respectively).

Render errors are reported per insert with exact OSStatus and a readable reason. Editors can remain usable after a DSP failure; the UI no longer calls every render error “stopped responding” or sends plug-in errors to Privacy Settings. Each failed row has Retry Audio.

A non-finite sample or output above +12 dBFS immediately latches silent output in the callback; the controller stops capture to restore ordinary playback. The safety stop also prevents automatic restart on the next launch. This is an extreme-level guard, not a reliable feedback detector or a guarantee against loud sound.

Successful insertion closes the plug-in browser and leaves the main panel open. Failed insertion stays in the browser with the plug-in name, error domain/code and loading guidance. Clicks in Plugger-owned editors/sheets do not dismiss the main panel. Outside-app clicks still follow the selected popup preference. The panel uses a hidden native title bar, removes its hosting-view safe-area inset, and uses a 16-point content inset with 14-point section spacing. The compact layout was visually verified in the running app. The popup is 390 points wide and stays anchored during dragging. Effect menus show only an ellipsis. Clicking an effect toggles its editor, including cancellation of a pending editor request. Drag the handle to preview order with spring animations (respecting Reduce Motion); the audio graph commits the order once on release. VoiceOver Move up/down actions are also available. Manual checks verified dragging first-to-last and back, and that a second click closes Pro-Q 3 (confirmed in the Window menu).

Run AudioTests/run-installed.sh for the two locally installed plug-ins; it uses no HAL route or speakers. Its optional --reproduce-old-disconnected-input diagnostic deliberately removes the input connection and is expected to fail. Quit the older running app and launch the rebuilt app before retesting: rebuilding an executable does not replace a process already running it.

Architecture

AudioEngineController creates a private device-stream process tap for the selected stereo output, excluding Plugger's Core Audio process object. Outputs whose preferred stereo pair does not match a two-channel stream use a global stereo mixdown fallback. It uses mutedWhenTapped, adds the tap to a private transient aggregate with the existing default output as clock source, and starts a single duplex HAL IOProc. Plugger never sets the system's default output. Input stream usage disables physical microphone streams. Output honors the device's preferred stereo channels. Layout/rate validation fails closed before capture starts, leaving ordinary playback available.

PGRenderer is Objective-C++: preallocated buffers, cached AU render blocks, v2 input callbacks where required, sequential processing, 10 ms dry/wet ramps, atomic peak/DSP/error handoff. It performs no discovery, logging, UI updates, disk I/O, graph edits or locking in the host render callback. Third-party code can still violate real-time constraints. On a render error the affected insert falls back to dry; malformed HAL data signals the controller to stop capture.

AppModel serializes control changes on the main actor. Audio Unit instantiation is asynchronous and uses Apple's default out-of-process policy for AUv3. Resource allocation, persistence and editor presentation happen off the render thread. AudioDeviceMonitor watches output, device availability, sample rate, frame size and stream configuration; workspace notifications handle sleep/wake. Private aggregate notifications are filtered to prevent rebuild loops.

Library, Chain, PluginRecord, and PresetStore preserve versioned binary property-list state under ~/Library/Application Support/Plugger/Library.plist. Missing plug-ins remain as records with their state. Current state is snapshotted every 15 seconds, before mutations and on clean quit. Saved named chains are explicit snapshots. Preferences/favorites/recents use a versioned UserDefaults value. Device links use UIDs and are opt-in.

AppDelegate owns an NSStatusItem and adaptive-height utility panel. SwiftUI provides the browser, meters, chain controls and Settings. Native AU editors are separate windows; a parameter editor is the fallback. Login registration uses SMAppService and reflects actual registration status.

Development-signature loading diagnosis

Before the signing fix, the development-signed app (team 4BRN73FCD8) failed to load the installed ValhallaRoom and ValhallaDelay AUv2 components. macOS kernel/CFBundle logs explicitly report library validation rejection because the host and plug-in have different Team IDs (the installed Valhalla binaries report no Team ID). This is the confirmed cause of these instances of OSStatus -1; that status alone does not diagnose every plug-in load failure.

Offline DSP tests do not validate the app's signing restrictions. Manual testing used the same development signature as Xcode after enabling the explicitly approved entitlement. ValhallaRoom and Pro-Q 3 both added successfully: the browser dismissed, the main panel stayed open, and ValhallaRoom's native editor opened and closed. Live processing of a quiet stereo tone showed −40 dBFS input and approximately −37 dBFS output with both effects enabled, without a render error or safety cutoff. This verifies signal flow and bounded output, not a subjective listening assessment. Processing was stopped after testing.

A manual Pro-Q 3 add also exposed a synchronous vendor constructor blocking the main thread inside a filesystem call. Initialization and saved-state restoration now run off the main actor. A 20-second deadline reports a useful loading error; one outstanding initialization is allowed so repeated clicks cannot pile up stuck constructors. Late results never enter the graph. This does not terminate vendor code; restarting the host may be necessary after a genuine constructor hang.

Signing and privacy

The project is unsandboxed and uses Hardened Runtime. No driver, microphone permission or persistent virtual device is installed. The configured development team comes from the original template; choose your own team for distribution. Release distribution requires normal Developer ID signing and notarization; this repository has not been notarized.

Third-party AUv2 signing: With the user's explicit approval, Configuration/Plugger.entitlements enables com.apple.security.cs.disable-library-validation for Plugger in Debug and Release. Hardened Runtime remains enabled. This app-specific entitlement permits in-process Audio Units from other developers to load; no system-wide security setting is changed.

AUv2 crashes are not contained. AUv3 render failures are bypassed when returned by Apple's bridge, but arbitrary service stalls/crashes still require real-world validation. No XPC AUv2 helper or extra-buffer transport is implemented. mutedWhenTapped avoids a permanently muted tap; explicit teardown stops/destroys the IOProc, destroys the aggregate and destroys the tap. A kernel/driver fault or hung third-party plug-in is outside a guarantee of uninterrupted playback.

System Settings → Privacy & Security → Screen & System Audio Recording is the recovery path for denied capture. Silence alone cannot distinguish permission denial from an idle source. Only press Start when ready to grant the app capture access.

Known engineering limits

  • Graph edits and chain switches stop/rebuild the private route. Normal dry playback resumes during the transition; switching is not guaranteed gapless or click-free. Bypass controls alone preserve the route and ramp in the renderer.
  • Bypass dry/wet paths have no plug-in latency compensation. A high-latency effect can cause phasing/time discontinuities during a crossfade. No guarantee of perceptual transparency for every AU is made.
  • Effects must accept stereo noninterleaved Float32 at the hardware sample rate; maximum callback size is 8192 frames. Incompatible effects or hardware formats fail open with an error. MIDI/sidechain inputs and mono-only/multichannel-only effects are not supported.
  • Footer latency tooltip uses the running Core Audio input-to-output timestamp span plus reported AU latency (configured buffer duration when stopped). This is not measured acoustic latency; upstream source buffering and transport may add delay.
  • AU factory presets and complete document state are supported. External .aupreset file import/export and a dedicated AU user-preset library are not implemented.
  • UI meters refresh at 60 Hz; 120 Hz display synchronization and suspended updates while the panel is hidden remain optimization work.
  • Registration changes refresh discovery; use Settings → Plug-ins → Retry unavailable effects to retry missing/failed records.

Build and tests

xcodebuild -project plugger2.xcodeproj -scheme plugger2 \
  -configuration Release -derivedDataPath /tmp/PluggerBuild \
  CODE_SIGN_IDENTITY=- build
swift test --scratch-path /tmp/PluggerCoreBuild
AudioTests/run.sh

The local ad-hoc signature is for development validation. Run from Xcode with your development identity for stable permission testing. AudioTests/run.sh uses only the production renderer and a real Apple Audio Unit; it captures no system audio and changes no devices.

Full signal-path acceptance

  1. Launch from Xcode. Verify no Dock icon, a plug menu-bar icon and a usable panel in light/dark modes.
  2. Play familiar audio at a moderate volume in another application. Press Start and grant system-audio capture. With no inserts, verify there is one audible signal and both stereo meters respond.
  3. Add Apple's AULowpass. Open its editor and lower cutoff. Verify audible filtering, then compare per-insert and global bypass; both should ramp without disconnecting the route.
  4. Add a second noncommuting effect (for example distortion). Reorder by dropping one row on another. Listen for the changed DSP order. Check factory presets and multiple editor windows.
  5. Save a named chain, modify parameters, save again, quit, relaunch and confirm settings and bypass states. Test Save As, rename, duplicate, delete and missing plug-in placeholders.
  6. Switch speakers/USB/headphones/Bluetooth while audio plays. Disconnect/reconnect hardware, sleep/wake, change the hardware sample rate and verify safe recovery. Confirm default output never becomes a Plugger device.
  7. In Audio Settings choose a supported buffer size and verify actual readback, including devices that reject the requested value.
  8. Link different named chains to two output UIDs. Verify switching with linking on and that the current chain stays active with linking off.
  9. Check close-on-outside-click, Keep Open, Escape, icon toggle, long chains scrolling below fixed meters, VoiceOver, Increase Contrast, Reduce Transparency and Reduce Motion.
  10. Quit while processing. Verify ordinary audio resumes and no private Plugger aggregate/tap remains. Repeat after a deliberate setup failure. Test AUv3 failure with an appropriate test extension; do not assume an in-process AUv2 crash is recoverable.

Source map

  • plugger2/Audio/AudioSystem.swift: HAL lifetime, output queries, buffer changes, observers.
  • plugger2/Audio/RenderBridge.h/.mm: real-time DSP and metering boundary.
  • plugger2/Models/Models.swift: persistence models and testable policies.
  • plugger2/Models/AppModel.swift: serialized host, chains, recovery, editors and settings.
  • plugger2/UI/AppDelegate.swift: status item, plug template artwork, panel/event lifecycle.
  • plugger2/ContentView.swift: main panel, insert rows and meters.
  • plugger2/UI/PluginBrowser.swift: discovery browser and generic parameter editor.
  • plugger2/UI/SettingsView.swift: General, Audio and Plug-ins settings.
  • plugger2/plugger2App.swift: SwiftUI lifecycle bridge (replaces SwiftData template).
  • Configuration/Info.plist, plugger2.xcodeproj/project.pbxproj, plugger2/Plugger-Bridging-Header.h: build/privacy configuration.
  • plugger2/Assets.xcassets/AppIcon.appiconset, Tools/GenerateIcon.swift: app icon and reproducible vector drawing source.
  • Tests/PluggerCoreTests.swift, Package.swift, AudioTests/: core and real-renderer tests.
  • Removed unused SwiftData Item.swift template.

Apple references

Glass, presets, and buffer verification

On macOS 26, effect rows use Apple's native GlassEffectContainer and glassEffect; older systems use solid system-color surfaces. Rows use native clear glass without an opaque drag backing. General Settings includes a persistent 0–100% popup background-opacity slider; text and controls remain opaque. The native panel has a clear backing so desktop content can contribute to the glass. Drop movement settles before the model order changes, and that layout commit disables implicit animations to avoid the second fade. Init Preset loads an empty chain. Chain names are limited to 32 Unicode characters, including normalization of older saved names; the chain menu has a bounded, truncating label.

The processing buffer is explicitly set on the private aggregate device before starting its IOProc, then read back. An atomic renderer measurement reports the actual callback frame count in Settings and the footer. Manual tests on the MacBook Pro speakers at 48 kHz confirmed 32 → 1024 → 32 frames in the running callback (0.67 → 21.33 → 0.67 ms per buffer). This is not an end-to-end latency measurement: the process tap, hardware, transport, safety offsets and effects can add delay. When stopped, the footer shows the configured buffer; Settings identifies that no callback is running. Manual UI checks also verified glass row reordering, clean Init Preset loading, and long-name truncation. All nine core tests and renderer regression tests pass.

Swift formatting and lint

The repository includes .swift-format and uses the swift-format bundled with Xcode. Run Tools/format.sh --write to format app, test, tool and package Swift sources; run Tools/format.sh --check for strict linting (nonzero exit on any finding). The initial scan found formatting/style issues, including statement separators, import ordering and a side-effect-only forEach; these were corrected. The strict check passes. This linter checks Swift style and syntax; builds and tests remain separate validation steps.

Persistent popup frosting

The popup now uses regular native Liquid Glass over an AppKit NSVisualEffectView with .popover material, .behindWindow blending and .active state. This retains native desktop frosting independent of window focus. Glass surfaces request appearsActive = true; this changes appearance without stealing keyboard focus. Effect rows remain clear glass for drag overlap, and the opacity control still adjusts background density. The signed build and strict swift-format check pass. The focused appearance was inspected; final cross-app focus verification remains unconfirmed because the UI automation connection repeatedly timed out, including after a session reset. A process sample showed the main event loop running rather than blocked in a plug-in constructor.

Card layering and light-mode verification

Effect cards now have separate native glass containers instead of a shared merging container. During a drag, the source keeps its gesture/layout slot while a hit-testing-disabled copy renders in a top overlay above every other card. The lifted card scales to 1.035, casts a larger shadow, and settles before the graph order commits; Reduce Motion suppresses the scale and spring. Light appearance uses a brighter native glass tint and a fine contrasting border. Popup appearance requests remain active independently of keyboard focus. Computer Use checks exercised both reorder directions in app-only light and dark test appearances, captured screenshots after app switching, and restored the original chain order. These checks supersede the earlier focus-test timeout note. Debug-only --appearance-light and --appearance-dark flags allow repeatable appearance testing without changing macOS preferences.

Display preferences and measured plug-in latency

General Settings includes Show meters (default on) and Theme: System / Light / Dark (default System). Both persist and migrate safely from older settings; theme changes apply immediately to native windows as well as SwiftUI. Manual Computer Use checks verified meter removal and popup resizing, Light, Dark, and return to System. Meter visibility does not disable audio fault monitoring.

Audio Settings now lists enabled effects' reported latency. The total excludes bypassed or failed effects and reflects global bypass. Latency reports refresh while running so changes to a plug-in's quality/lookahead mode can be reflected.

The installed Abbey Road TG Mastering (s) reports 173.021 ms at 48 kHz regardless of maximum render sizes 32, 1024, or 8192. AudioTests/run-latency.sh sends an impulse through the production renderer in 32-frame blocks: the full stereo component first outputs at sample 8275 (172.396 ms); the installed Live stereo component first outputs at sample 0 and reports zero latency. This test does not capture audio or use speakers. An actual app bypass check changes the buffer-plus-effects estimate from 173.7 ms to 0.7 ms. This identifies substantial additional delay in that mastering effect; it does not diagnose or rule out empty-chain routing delay. Total audible latency still includes the system tap, hardware, and transport and is not measured by this offline test. The original chain has been retained pending the user's choice of switching to the Live component, whose processing differs. Existing plug-in parameters are not silently rewritten.

Empty-chain routing latency correction

The global mixdown tap added large safety offsets even with a verified 32-frame callback. A device-stream tap now captures the selected output directly when its preferred stereo channels match a stereo stream. This preserves self-exclusion, muted-when-tapped behavior, and the existing output clock. Other channel layouts retain the global stereo mixdown fallback.

Manual Computer Use verification on MacBook Pro Speakers at 48 kHz compared the original global tap against the device-stream route: the Core Audio input-to-output timestamp span fell from 41.35 ms to 4.40 ms at 32 samples. Aggregate input/output safety offsets fell from 967/954 to 73/74 frames, matching the physical output. Switching the rebuilt app to 1024 samples produced 45.73 ms and returning to 32 restored 4.40 ms. A quiet live stereo signal registered matching −40 dB input/output levels with an empty chain and no render errors. These are route timestamp measurements, not source-to-ear acoustic measurements or guarantees for other devices.

Audio Settings exposes both actual callback frames and capture-to-output timing. The production renderer records timing atomically without allocating or logging in the realtime callback. Debug build, all ten core tests, renderer regression tests, and strict swift-format checks pass.

Packaging a tester build

Build a universal Release app with ARCHS='arm64 x86_64' ONLY_ACTIVE_ARCH=NO, then run Tools/package-release.sh /path/to/Release/Plugger.app. The script verifies the existing signature and creates dist/Plugger.app, a drag-to-Applications DMG, a ZIP, and SHA-256 checksums. Generated artifacts are ignored by Git. See Distribution/Testing.txt for installation and test instructions. The current local identity is Apple Development; these tester packages are not notarized. Broad distribution requires a Developer ID Application certificate and Apple notarization, neither of which this package claims.

The packaged universal Release build was launched locally and restored ValhallaRoom successfully, with a verified 32-frame callback and 4.4 ms route timing. Code signature and DMG checksums were verified. Release disables injected development debugger entitlements while retaining the approved third-party AU loading entitlement. Intel code is included but has not been executed on an Intel Mac here.

About

No description, website, or topics provided.

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages