A lightweight macOS menu bar per-app volume mixer, built on Core Audio process taps, so there's no audio driver to install and no microphone indicator.
MacMixer lives in your menu bar and gives you a volume slider for each running app, plus a master volume and an output-device picker:
- Per-app volume - independent 0–200% slider for every app that's playing audio. Above 100% boosts; a bolt icon and orange tint show when an app is boosted. Slider snaps to 100% (the app's normal level).
- Master volume + mute - controls the selected output device's hardware volume, so it affects everything.
- Output picker - switch the system output device from the menu, with an icon that reflects the device type (AirPods, headphones, speaker, display).
- Follow new devices - when you connect AirPods or headphones, MacMixer switches to them automatically (displays are ignored).
- Hide apps - hide apps you don't care about (utilities like Finder and Terminal are hidden by default); your choices are remembered.
- Resizable - drag the bottom edge to make the app list taller; the size is remembered.
macOS has no public API to set another app's output volume. The well-known
solution, Background Music,
is great it ships a virtual audio driver and captures audio by reading it back through a
virtual input stream. Unfortunately macOS classifies this as microphone use, so it
permanently lights the orange microphone indicator, requires Microphone
permission, and needs a sudo driver install.
macOS 14.2 added a first-class Core Audio process-tap API
(CATapDescription + AudioHardwareCreateProcessTap). MacMixer is built on it:
- No virtual driver - nothing installed in
/Library/Audio/Plug-Ins, nosudo, nocoreaudiodrestart. Just an app. - No microphone indicator - a process tap is not classified as microphone input, so the orange mic dot never appears.
- macOS 14.2 or later (developed on macOS 26)
- Nothing else to run it. The app links only system frameworks and the Swift runtime that ships with macOS. (Xcode is only needed to build from source.)
- Download
MacMixer-<version>.zipfrom the latest release, unzip it, and drag MacMixer.app into/Applications. - Open MacMixer. Releases from 2.2.0 are signed with a Developer ID and notarized by Apple, so Gatekeeper opens them without extra steps.
- Click the menu bar icon. The first time it controls an app, macOS asks to allow audio recording - grant it (see Permissions).
MacMixer checks the latest GitHub release when it starts and every 6 hours (Check for Updates Automatically in the right-click menu, on by default). When it finds a new version, it asks you first:
- Install and Restart downloads the new version, checks it, replaces MacMixer, and opens it again.
- Later asks again at the next check.
- Skip This Version does not ask again until a newer version comes out.
The right-click menu also has Check for Updates…, and the popover shows an Update to x.y.z button while an update is available.
Before it installs, MacMixer checks that:
- the SHA-256 of the download matches the checksum that GitHub shows,
- the app has the MacMixer bundle ID and the release version,
- the app is signed by the MacMixer Developer ID (team
3YULF6FJ6W) and notarized by Apple.
MacMixer updates itself only when it runs from /Applications or
~/Applications. Version 2.0.0 has no updater: install a newer version by hand
one time.
git clone https://github.com/burghr/MacMixer.git
cd MacMixer
gem install xcodeproj # one-time; build.sh uses it to generate the project
./build.sh # generates the Xcode project, builds Release, installs to /Applications
open -a MacMixerRequires Xcode 15+. The Xcode project is generated from project.rb (via the
xcodeproj gem) and is git-ignored. The
Swift sources are the source of truth. build.sh signs the app with your
"Developer ID Application" identity if the keychain has one, else with an
ad-hoc signature. Set MACMIXER_ADHOC=1 to force an ad-hoc signature.
To make a release, run ./release-zip.sh. It signs the app with the Developer
ID, notarizes it (keychain profile iodus-notary, or set NOTARY_PROFILE),
staples the ticket, and writes .build/MacMixer-<version>.zip.
The first time MacMixer taps an app, macOS asks for permission to record audio from your Mac. This is required: changing an app's volume means capturing its audio, applying gain, and playing it back, and macOS treats capturing another app's audio as recording. It is not microphone access (no orange mic dot), and MacMixer never touches your microphone. You can manage it in System Settings → Privacy & Security.
For each app you control, MacMixer creates a private process tap with
CATapMutedWhenTapped: the app's own output is muted only while we're reading
it. All taps plus the chosen output device are combined into one private
aggregate device, clocked by the output, and a single realtime callback mixes
every tap into the output with per-app gain applied. Because the aggregate
provides one clock, there's no ring buffer and no sample-rate drift. If the
render ever stalls, the "when tapped" mute releases and apps play normally again.
Chromium-based apps (Chrome, Edge, Electron) and others play audio from helper processes rather than their main process; MacMixer maps every audio process object back to its owning app (by pid or bundle-id prefix) so their sliders work too.
MacMixer/
MacMixerApp.swift @main; menu-bar-only (LSUIElement)
AppDelegate.swift NSStatusItem + transient NSPopover
PopupView.swift SwiftUI UI
MixerModel.swift @MainActor ObservableObject bridging UI <-> engine
TapEngine.swift Core Audio engine: taps + aggregate + render mix
CoreAudioSupport.swift AudioObject / device / tap helpers
- macOS 14.2+ only (the process-tap API doesn't exist on earlier versions).
- Only apps with a visible slider are tapped; hidden apps play natively (master still affects them, since master is the device's hardware volume).
- Master/mute require an output device that exposes a hardware volume control; some devices (e.g. certain HDMI outputs) don't.
MIT © 2026 burghr
This is original code and shares nothing with Background Music; it is not a fork.
