Your audio. Your rules. Always.
SoundLock is a lightweight macOS menu bar app that stops the system from hijacking your audio devices. Define a priority order for your headphones, speakers, and microphones — and SoundLock silently enforces it, every time, in the background.
Its refreshed icon system pairs white headphones with ranked priority lanes, so the app icon and menu bar icon both reflect the same device-priority idea at a glance.
macOS decides which audio device to use, and it changes its mind constantly.
Connect a pair of Bluetooth headphones and macOS switches your output to them — even if you were mid-call on your AirPods. Plug in a USB hub with an audio chip and suddenly your mic is gone. Open a video call and the system reassigns your input without asking. Every time a new device appears, macOS picks a winner based on its own rules, not yours.
The System Settings audio panel lets you change the default device after the fact, but it won't remember your preference the next time things connect or disconnect. There's no concept of priority. There's no lock. There's no protection.
So you fix it manually. And then you fix it again. And again.
SoundLock puts you in charge of a ranked priority list — one for output devices, one for input — and keeps the system honest.
It's always watching. Three CoreAudio listeners run silently in the background, watching for default-device changes, new connections, and disconnections. The moment macOS switches to the wrong device, SoundLock switches it back.
It uses your priority list, not macOS's. You drag your preferred headphones to the top. If they're connected, they stay active. If they disconnect, SoundLock promotes the next device on your list automatically. When the top-priority device reconnects, it takes over again.
It remembers devices even when they're offline. Your AirPods in the priority list but not currently connected? They stay in the list, labelled Offline, ready to take priority the moment they pair.
It recovers from Bluetooth delays. macOS often auto-switches audio after a Bluetooth device finishes pairing — a delayed second event that happens 1–3 seconds after connection. SoundLock schedules multiple recovery checks to catch this, and cancels stale ones if a newer event arrives.
It respects when you want to opt out. The guard toggle in the menu lets you temporarily disable priority enforcement — useful when you deliberately want to try a different device. Toggle it back on and SoundLock immediately reasserts your priority.
- Priority output and input lists — drag to reorder, add/remove any connected physical device
- Offline device persistence — priority devices stay in the list even when disconnected
- Volume control — slider and percentage display for both output and input priority devices
- Mute toggles — one-tap mute for both directions, directly from the menu
- Guard toggle — enable or disable priority enforcement instantly from the menu bar
- Launch at login — runs silently in the background from the moment you log in
- Zero Dock presence — lives entirely in the menu bar, takes no space in your app switcher
- System Settings shortcuts — one-click access to Sound and Bluetooth panels from the footer
- Tiny footprint — event-driven CoreAudio listeners with 0% idle CPU and minimal RAM usage
| macOS | SoundLock | |
|---|---|---|
| Remembers preferred device | ✗ | ✓ |
| Priority order across devices | ✗ | ✓ |
| Auto-restores on reconnect | ✗ | ✓ |
| Recovers from BT pairing delay | ✗ | ✓ |
| Volume control in menu bar | ✓ (system menu only) | ✓ |
| Runs silently in background | — | ✓ |
| No Dock icon | — | ✓ |
SoundLock knows nothing about you. There is no account, no telemetry, no analytics, no network code of any kind. The app never leaves your Mac.
It never listens to your audio. SoundLock does not record, sample, or process any audio data. It uses CoreAudio's Hardware Abstraction Layer (HAL) exclusively — the same low-level API that macOS itself uses to route audio. It sets device metadata (which device is the default, its volume level, its mute state). It reads zero bytes of audio content.
It does not need microphone permission. macOS requires microphone access when an app reads audio stream data. SoundLock never opens an audio stream, so the permission prompt never appears and the entitlement is never requested. You can verify this: open System Settings → Privacy & Security → Microphone — SoundLock will not appear in the list.
No accessibility, location, or any other sensitive permission is used. The only system feature SoundLock opts into is Launch at Login, which is handled through a standard macOS API (SMAppService) and clearly indicated by a toggle in the UI. No background entitlements, no kernel extensions, no privileged helpers.
All preferences stay on your device. Your priority device lists are stored in ~/Library/Preferences/ via standard UserDefaults, the same place macOS stores every app's settings. Nothing is synced, shared, or uploaded.
SoundLock is built around a single principle: do nothing unless the system tells you something changed.
0% CPU at idle. There is no polling loop, no timer, no background thread spinning. The app registers three AudioObjectPropertyListener callbacks with CoreAudio and then goes completely dormant. The CPU wakes up only when macOS fires one of those events — a device connects, disconnects, or the default changes. On a typical day with a stable audio setup, that might be a handful of events total.
Minimal RAM. The entire app — Swift runtime, SwiftUI view tree, CoreAudio state, and persisted priority lists — runs comfortably under 20 MB. There are no image caches, no web views, no large frameworks loaded.
No redundant work. Device enumeration is a single kernel round-trip that builds both the output and input device lists in one pass. Priority list saves are debounced (300 ms), so a drag-reorder that fires multiple didSet callbacks results in exactly one UserDefaults write. Bluetooth reconnect retries use cancellable DispatchWorkItem objects, so a rapid connect/disconnect cycle doesn't pile up stale closures on the main queue.
Event-driven cleanup. Listeners are registered in startMonitoring() and explicitly removed in applicationWillTerminate — so CoreAudio is never left holding a dangling callback. Retry timers are cancelled on every new device-list event, preventing any timer accumulation over time.
Grab the prebuilt app: SoundLock.app.zip
Unzip, drag SoundLock.app to /Applications, launch. macOS Gatekeeper may block first run since the build is unsigned — right-click the app → Open → Open to bypass, or run xattr -dr com.apple.quarantine /Applications/SoundLock.app.
Requires macOS 13 or later and Xcode Command Line Tools.
git clone <repo>
cd SoundLock
bash build.sh
open SoundLock.appTo install permanently:
cp -r SoundLock.app /Applications/Then open SoundLock, enable Launch at login, and forget about it.
SoundLock registers three AudioObjectPropertyListener callbacks with CoreAudio — one for default output changes, one for default input changes, and one for device list changes (connect/disconnect events). All three dispatch to the main thread and check whether the current system default matches the top-priority connected device. If it doesn't, a single AudioObjectSetPropertyData call corrects it.
No polling. No background threads. No timers in steady state. The app sits completely idle until the system tells it something changed.
If SoundLock saved your sanity, consider buying me a coffee.
MIT
