How Vorssaint-utils Handles Per-App Volume Mixing on macOS
Vorssaint-utils implements per-app volume mixing through a dedicated mixer subsystem that reads and controls individual app audio streams using macOS's System Audio Recording permission, applying volume transformations only when needed and routing output to user-selected devices.
Per-app volume mixing allows users to set different volume levels for individual applications rather than relying on a single system volume. In the vorssaint/vorssaint-utils repository, this feature is implemented through a sophisticated mixer subsystem that integrates with macOS audio APIs while keeping all processing local. The solution balances performance with functionality by remaining idle for apps using default volume levels and activating only when custom levels are detected.
Mixer Subsystem Architecture
The core logic resides in MixerRoutingSupport, a helper class utilized throughout the UI and test suites. This component manages the entire lifecycle of per-app audio handling, from parsing user input to persisting settings and controlling audio engine activation.
Key Responsibilities
The helper handles several critical functions:
- Volume Parsing: Converts user-entered percentages into normalized floats via
volumeFraction(fromPercentageText:)(tested inTests/MetricsTests.swiftat line 7423) - Persistence: Stores per-app volumes safely using
Defaults.sanitizedAppVolume(_:)with clamping logic (line 7419) - Engine Management: Determines when an audio engine is required through
requiresEngine(volume:)(line 7613) - Device Routing: Updates mappings when users switch output devices via
preferencesAfterUniversalOutputSwitch(line 7512) - UI State: Controls row interactivity with
rowMayBeTapped(savedVolume:)(line 7663) and restores volumes after device changes usingshouldRestoreOutputVolume(appliedVolume:currentVolume:)
Volume Parsing and Validation
Before any audio processing occurs, user input must be normalized. The volumeFraction(fromPercentageText:) method handles string conversion:
// Convert user input like "40%" or "-10%" to normalized Float
let fraction = MixerRoutingSupport.volumeFraction(fromPercentageText: "75%") // → 0.75
Once parsed, volumes are sanitized through Defaults.sanitizedAppVolume(_:). This method clamps out-of-range values—negative inputs become 0, values greater than 2 are capped at 2, then normalized to 1 after boost processing.
Engine Activation and Performance Optimization
A key optimization in Vorssaint-utils is conditional engine activation. The requiresEngine(volume:) method returns true only for volumes deviating from the default 1.0:
// Check if audio engine is needed for this volume
if MixerRoutingSupport.requiresEngine(volume: safeVolume) {
MixerEngine.shared.start()
}
This design ensures the mixer stays idle for apps using system defaults, conserving CPU resources. The engine spins up exclusively when an app's volume differs from unity level, applying gain transformations and routing streams to the selected output device.
Device Routing and Persistence
When users change output hardware or toggle "Universal Output", preferencesAfterUniversalOutputSwitch recalculates per-app volume mappings and device selections. The system maintains these settings through shouldRestoreOutputVolume(appliedVolume:currentVolume:), ensuring custom volumes reapply automatically after audio device switches.
All preferences are stored locally via the Defaults API, with bundle identifiers or display names serving as unique keys for each running application.
UI Integration in MixerSection.swift
The user interface layer lives in Sources/Vorssaint/UI/MenuPanel/MixerSection.swift. This component binds mixer state to the interface, exposing per-app volume sliders and device selectors. It uses MixerRoutingSupport.systemDefaultSelectionID as the "auto" option and updates dynamically when users adjust sliders.
Row interactivity is controlled by rowMayBeTapped(savedVolume:), which determines whether an app's row should be interactive based on existing custom volume settings.
Complete Workflow Example
The following Swift code demonstrates the full per-app volume mixing workflow:
// 1. Parse a user-entered percentage string
let fraction = MixerRoutingSupport.volumeFraction(fromPercentageText: "75%") // → 0.75
// 2. Clamp and store a custom per-app volume
let safeVolume = Defaults.sanitizedAppVolume(fraction) // → 0.75 (or 1.0 if out-of-range)
Defaults.setAppVolume(appID: "com.example.Player", volume: safeVolume)
// 3. Conditionally start the audio engine
if MixerRoutingSupport.requiresEngine(volume: safeVolume) {
MixerEngine.shared.start()
}
// 4. Handle output device changes
let newPrefs = MixerRoutingSupport.preferencesAfterUniversalOutputSwitch(
oldPrefs: oldPrefs,
selectedDeviceUID: "AirPods Pro"
)
Required Permissions and Privacy
Per-app volume mixing requires System Audio Recording permission on macOS, documented in the repository's PERMISSIONS.md and TROUBLESHOOTING.md files. According to PRIVACY.md, all audio processing occurs locally; no audio data is transmitted off-machine.
Summary
- MixerRoutingSupport provides the core helper methods for parsing, clamping, and managing per-app volumes in
vorssaint/vorssaint-utils - Conditional activation via
requiresEngine(volume:)ensures the mixer only runs when necessary, optimizing performance - Input validation uses
volumeFraction(fromPercentageText:)andsanitizedAppVolume(_:)to safely handle user percentages - Device routing automatically updates through
preferencesAfterUniversalOutputSwitchwhen hardware changes occur - UI binding occurs in
MixerSection.swift, displaying interactive sliders for each detected application - Privacy-first architecture keeps all audio processing local, requiring only macOS System Audio Recording permission
Frequently Asked Questions
What permission is required for per-app volume mixing in Vorssaint-utils?
Vorssaint-utils requires the System Audio Recording permission on macOS to read and control individual app audio streams. Without this permission, the mixer cannot access per-app audio data, though the application will still function for other features. This permission is requested through the standard macOS security dialog and can be managed in System Settings.
How does Vorssaint-utils handle invalid volume inputs?
Invalid entries are processed through Defaults.sanitizedAppVolume(_:), which clamps negative values to 0 and values greater than 2 to 2 (subsequently normalized to 1 after boost calculation). The volumeFraction(fromPercentageText:) method handles string parsing, converting formats like "40%" or "-10%" into normalized Float values between 0 and 1 before sanitization occurs.
Does the audio mixer run constantly in the background?
No, the mixer uses conditional activation to conserve resources. The requiresEngine(volume:) method returns false for apps using the default volume of 1.0, keeping the engine idle. The audio engine only activates when an app's stored volume differs from unity level, applying gain transformations and routing only when necessary.
Where are per-app volume settings stored?
Per-app volumes are persisted through the Defaults API using bundle identifiers or display names as keys. Settings are stored locally on the device and automatically restored when switching output devices via shouldRestoreOutputVolume(appliedVolume:currentVolume:). preferencesAfterUniversalOutputSwitch manages these transitions when users toggle "Universal Output" or change audio hardware.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →