How the Volume Mixer Works in vorssaint-utils Without an Audio Driver

The volume mixer in vorssaint-utils operates entirely in user space by leveraging macOS's Core Audio HAL to create process taps and aggregate devices, eliminating the need for a custom kernel driver.

The vorssaint-utils repository implements a per-application volume mixer that manipulates audio streams without installing a traditional audio driver. By utilizing public Core Audio APIs available in macOS 14.4 and later, the mixer intercepts and modifies application audio through hardware abstraction layer primitives rather than kernel extensions.

Core Audio HAL: The Foundation of Driverless Audio Routing

Unlike traditional audio utilities that rely on kernel extensions (KEXTs) or system extensions (DriverKit), vorssaint-utils builds upon the Core Audio HAL (Hardware Abstraction Layer). The HAL provides a user-space interface to audio hardware, allowing applications to create virtual audio devices and intercept streams without touching the kernel driver stack.

The mixer attaches global listeners to system audio properties such as kAudioHardwarePropertyDevices and kAudioHardwarePropertyDefaultOutputDevice. These listeners run on a dedicated dispatch queue (halQueue) and trigger refreshes whenever the HAL reports hardware changes.

The Six-Step Audio Pipeline

Step 1: Monitoring System Audio Changes

The mixer begins by installing property listeners via AudioObjectAddPropertyListener. In Sources/Vorssaint/Services/Audio/AppVolumeMixer.swift, the installListener(selector:) method registers these listeners for system-wide audio hardware properties:

// AppVolumeMixer.swift lines 83-92
func installListener(selector: AudioObjectPropertySelector) {
    var address = AudioObjectPropertyAddress(
        selector: selector,
        scope: kAudioObjectPropertyScopeGlobal,
        element: kAudioObjectPropertyElementMain
    )
    AudioObjectAddPropertyListener(
        kAudioObjectSystemObject,
        &address,
        propertyListenerCallback,
        Unmanaged.passUnretained(self).toOpaque()
    )
}

When the HAL detects new audio devices or default output changes, the callback schedules a refresh on halQueue, ensuring the mixer state stays synchronized with hardware reality.

Step 2: Discovering Active Audio Processes

To identify which applications are producing audio, the mixer takes a snapshot of all active audio processes. The readSnapshot(_:) method in AppVolumeMixer.swift (lines 9188-9235) queries the HAL for audioObjectIDs and constructs a RefreshSnapshot containing:

  • Process ID (PID) for each audio client
  • Bundle identifiers to identify specific applications
  • Bypass status for applications like Zoom or DAWs that require special handling

This snapshot captures the complete state of audio processes without requiring low-level system hooks.

Step 3: Determining When to Create Audio Taps

Not every application requires an audio tap. The MixerRoutingSupport.requiresEngine logic (extensively tested in Tests/MetricsTests.swift lines 7369-7382) evaluates whether a given application needs its audio stream intercepted based on current volume levels, selected output devices, and existing audio objects.

For example, when the volume is set to 1.0 (100%) and no device routing is required, the mixer skips tap creation to minimize CPU usage:

// MetricsTests.swift validates this logic
expect(!MixerRoutingSupport.requiresEngine(volume: 1, isSystemDevice: true, hasTap: false))

Step 4: Creating Process Taps and Aggregate Devices

For applications requiring volume adjustment, the mixer creates a process tap and aggregate device through the TapGainEngine class. This occurs entirely in user space via two key Core Audio APIs:

  1. AudioHardwareCreateProcessTap – Creates a tap on the target application's audio stream
  2. AudioHardwareCreateAggregateDevice – Creates a virtual device that combines the tapped stream with the desired output

In AppVolumeMixer.swift (lines 54-71), the TapGainEngine initialization constructs these objects:

// Lines 54-60: Process tap creation
var tapID: AudioObjectID = kAudioObjectUnknown
let tapStatus = AudioHardwareCreateProcessTap(
    tapDescription,
    &tapID
)

// Lines 66-71: Aggregate device creation  
let aggregateDict: [String: Any] = [
    kAudioAggregateDeviceNameKey: "VorssaintTap_\(appID)",
    kAudioAggregateDeviceSubDeviceListKey: subDevices,
    kAudioAggregateDeviceMasterSubDeviceKey: masterUID
]
AudioHardwareCreateAggregateDevice(aggregateDict as CFDictionary, &aggregateID)

Because these are HAL objects rather than kernel drivers, they behave like standard virtual audio devices that macOS routes automatically.

Step 5: Real-Time Audio Processing

The aggregate device's IO procedure (AudioDeviceCreateIOProcIDWithBlock) runs on a high-priority audio thread. In AppVolumeMixer.swift (lines 88-95), this block implements a pull-mix-push cycle:

  1. Pulls audio buffers from the process tap
  2. Applies gain using gainBox.value to scale sample levels
  3. Pushes modified buffers to the output device
  4. Limits peaks via BoostLookaheadBufferListLimiter to prevent digital clipping when gain exceeds 1.0

This processing occurs in real-time with latency comparable to native Core Audio operations, all without kernel-level code.

Step 6: Synchronizing with the SwiftUI Interface

After each HAL refresh, the main thread updates @Published properties including apps, outputDevices, and systemOutputVolume. The UI layer in Sources/Vorssaint/UI/MenuPanel/MixerSection.swift binds directly to these properties.

For example, the system volume slider connects to mixer.systemOutputVolume and writes changes through setCurrentOutputVolume(_:) (lines 140-150):

// MixerSection.swift
Slider(value: $mixer.systemOutputVolume, in: 0...1) { editing in
    if !editing {
        mixer.setCurrentOutputVolume(mixer.systemOutputVolume)
    }
}

This reactive architecture ensures the UI reflects the current mixer state immediately after HAL updates propagate.

Why No Kernel Driver Is Required

The vorssaint-utils volume mixer avoids kernel extensions by design. All operations use public Core Audio APIs that macOS ships with in the HAL framework. The process taps and aggregate devices are pure user-space constructs—virtual audio devices that the system routes just like physical hardware.

Because the mixer never touches the low-level kernel driver stack, it works on any Mac supporting the required HAL calls (macOS 14.4 or later) without triggering system security warnings or requiring reduced security settings.

Implementation Example

Here's how to interact with the mixer in your own Swift code:

import Vorssaint

// 1️⃣ Get the shared mixer instance
let mixer = AppVolumeMixer.shared

// 2️⃣ List all apps currently shown in the mixer
for app in mixer.apps {
    print("\(app.name) – volume: \(app.volume)")
}

// 3️⃣ Set the volume of a specific app (e.g. Safari)
if let safari = mixer.apps.first(where: { $0.name.contains("Safari") }) {
    mixer.setVolume(0.3, for: safari)   // 30 % of the original level
}

// 4️⃣ Route an app to a different output device (e.g. headphones)
if let headphonesUID = mixer.outputDevices.first(where: { $0.isHeadphones })?.uid {
    mixer.setOutputDeviceUID(headphonesUID, for: safari)
}

// 5️⃣ Adjust the system‑wide output volume (affects the default device)
mixer.setCurrentOutputVolume(0.6)       // 60 % of the current device’s max

Summary

  • The vorssaint-utils volume mixer leverages Core Audio HAL instead of custom drivers to manipulate audio streams.
  • AppVolumeMixer.swift contains the core logic for HAL interaction, process discovery, and tap management.
  • Process taps (AudioHardwareCreateProcessTap) and aggregate devices (AudioHardwareCreateAggregateDevice) handle audio interception in user space.
  • Real-time processing occurs in IO procedure blocks with anti-clipping protection via BoostLookaheadBufferListLimiter.
  • The SwiftUI interface in MixerSection.swift binds to @Published properties for reactive updates.
  • The implementation requires macOS 14.4 or later and requires no kernel extensions or security compromises.

Frequently Asked Questions

Does vorssaint-utils require a kernel extension to mix audio?

No. The mixer uses only public Core Audio HAL APIs available in macOS. It creates process taps and aggregate devices entirely in user space, behaving as virtual audio devices that the system routes automatically without kernel-level code.

What macOS version is required for the volume mixer?

The volume mixer requires macOS 14.4 or later. This version provides the necessary HAL APIs for AudioHardwareCreateProcessTap and aggregate device management used extensively in AppVolumeMixer.swift.

How does the mixer handle applications like Zoom or professional DAWs?

The readSnapshot(_:) method in AppVolumeMixer.swift identifies "bypassed" applications through bundle identifier checks and audio object inspection. Applications like Zoom or DAWs that require direct hardware access are flagged to avoid creating taps that would interfere with their low-latency requirements.

Can the mixer route audio to specific output devices per application?

Yes. Through setOutputDeviceUID(_:for:) in AppVolumeMixer.swift, the mixer can direct individual application streams to specific output devices. This works by configuring the aggregate device's sub-device list to include the target hardware, allowing per-application routing without changing the system default device.

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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →