# How vorssaint-utils Detects and Reconciles Frozen Audio Engines After Sleep

> Discover how vorssaint-utils detects and reconciles frozen audio engines after sleep. Learn how it clears stale state and rebuilds wedged engines for seamless audio.

- Repository: [vorssaint/vorssaint-utils](https://github.com/vorssaint/vorssaint-utils)
- Tags: internals
- Published: 2026-09-10

---

**vorssaint-utils detects frozen audio engines by listening for macOS wake notifications, clearing stale render state, and running a reconciliation pass that tears down wedged engines and rebuilds them fresh.**

When a Mac wakes from sleep, the underlying CoreAudio HAL can leave per-application audio engines in a "wedged" state—where the tap's aggregate device remains active but the render callback stops firing, causing applications to remain silent. The `vorssaint/vorssaint-utils` repository solves this through a systematic detection and recovery pipeline implemented in the audio mixing layer. This article breaks down the exact mechanisms used to identify stalled engines and restore audio functionality automatically.

## The Problem: Wedged Audio Engines on macOS Wake

The audio mixer in vorssaint-utils runs a per-app "engine" that taps each application's audio stream, applies volume and routing changes, and writes the result back to the system output. During normal operation, these engines maintain render callbacks that process audio buffers in real-time. However, when macOS wakes from sleep, the CoreAudio HAL may leave an engine's aggregate device active while silently stopping its render callbacks. This creates a **wedged engine**—the system believes audio is flowing, but no buffers are being processed, resulting in silent applications.

## Wake Detection and Immediate Response

The recovery process begins the moment the system signals a wake event. The mixer registers for system notifications during initialization and responds by purging stale state and forcing a full audit of all active engines.

### Listening for NSWorkspace.didWakeNotification

In [`AppVolumeMixer.swift`](https://github.com/vorssaint/vorssaint-utils/blob/main/AppVolumeMixer.swift), the `start()` method registers an observer for `NSWorkspace.didWakeNotification` during mixer initialization. This observer triggers a dedicated wake handler that performs four critical operations in sequence:

1. Clears cached render observations from `engineRenderProgress`
2. Clears any pending recovery state from `engineRecovery`
3. Forces a full refresh of the application list via `refreshApps()`
4. Immediately executes `reconcileEngines(with: self.apps)` to audit all engines

```swift
// AppVolumeMixer.start() – registers the wake observer at lines 188-199
// Source: Sources/Vorssaint/Services/Audio/AppVolumeMixer.swift

```

This sequence ensures that no stale data from the pre-sleep state interferes with the post-wake reconciliation.

## The Reconciliation Pipeline

The `reconcileEngines(with:)` method serves as the core detection logic, examining every live engine to determine if it has stalled and requires teardown.

### Detecting Stalled Render Cycles

At lines 1159-1205 of [`AppVolumeMixer.swift`](https://github.com/vorssaint/vorssaint-utils/blob/main/AppVolumeMixer.swift), the reconciliation logic compares each engine's render count against its previous state. If the render count has stalled while the parent application is still reporting playback activity, the engine is classified as **wedged**. The mixer immediately tears down wedged engines, allowing the system to create fresh taps with functional render callbacks.

The detection heuristic relies on two conditions:
- **Render stall**: The engine's `renderCycles` counter has not incremented since the last check
- **Active playback**: The application reports ongoing audio session activity

When both conditions are met, the engine is marked for destruction rather than reuse.

### Handling Temporary Object Loss

If an application temporarily loses its audio objects during the wake transition, the mixer applies a short teardown delay to avoid premature engine destruction. If the objects do not return within the grace period, the engine is discarded. After processing all existing engines, the mixer calls `applyRouting(for:)` to instantiate new engines for any applications that now require audio taps.

### Scheduling Delayed Rebuilds

Some engines require additional time before they can be safely rebuilt—typically when waiting for hardware devices to become available. The `scheduleEngineReconcile(after:)` method (lines 66-73) posts a one-off dispatch after the specified delay, keeping the system event-driven rather than blocking the main thread with busy-waiting.

```swift
// Scheduling the next reconcile pass
// Source: scheduleEngineReconcile() – lines 66-73

```

This mechanism ensures that engines dependent on specific hardware readiness states do not enter retry loops that consume CPU resources.

## Practical Code Examples

You can manually trigger the wake reconciliation sequence for testing or custom recovery scenarios:

```swift
// Example: Manually trigger a wake-reconciliation (used in tests)
AppVolumeMixer.shared.wakeObserver?.let {
    // Simulate the wake callback
    AppVolumeMixer.shared.engineRenderProgress.removeAll()
    AppVolumeMixer.shared.engineRecovery.clearAll()
    AppVolumeMixer.shared.refreshApps()
    AppVolumeMixer.shared.reconcileEngines(with: AppVolumeMixer.shared.apps)
}

```

To force a reconciliation after changing output devices or other asynchronous events:

```swift
// Example: Force a reconciliation after a custom delay
AppVolumeMixer.shared.scheduleEngineReconcile(after: 0.5)   // half-second later

```

For debugging specific engines, inspect the render progress dictionary:

```swift
// Example: Inspect an engine's render status
if let progress = AppVolumeMixer.shared.engineRenderProgress[app.id] {
    print("Engine \(app.id) rendered \(progress.renderCycles) cycles")
}

```

## Key Source Files

The reconciliation system spans multiple files within the vorssaint-utils codebase:

- **[`Sources/Vorssaint/Services/Audio/AppVolumeMixer.swift`](https://github.com/vorssaint/vorssaint-utils/blob/main/Sources/Vorssaint/Services/Audio/AppVolumeMixer.swift)** – Contains the core mixing logic, engine lifecycle management, wake handling, and the `reconcileEngines(with:)` implementation.
- **[`Sources/Vorssaint/Services/Audio/MixerRoutingSupport.swift`](https://github.com/vorssaint/vorssaint-utils/blob/main/Sources/Vorssaint/Services/Audio/MixerRoutingSupport.swift)** – Provides helper functions that determine when an engine is wedged, stalled, or safe to rebuild based on system state.
- **[`Sources/Vorssaint/Services/Recorder/RecorderCaptureEngine.swift`](https://github.com/vorssaint/vorssaint-utils/blob/main/Sources/Vorssaint/Services/Recorder/RecorderCaptureEngine.swift)** – Implements the low-level CoreAudio tap creation and aggregate device management that the reconciliation layer controls.
- **[`Sources/Vorssaint/Services/SmoothScrollSupport.swift`](https://github.com/vorssaint/vorssaint-utils/blob/main/Sources/Vorssaint/Services/SmoothScrollSupport.swift)** – Demonstrates the same reconciliation pattern used for audio engines in a different domain, showing the generic nature of the approach.

## Summary

- **Wake detection** relies on `NSWorkspace.didWakeNotification` observers registered in `AppVolumeMixer.start()`.
- **State invalidation** clears `engineRenderProgress` and `engineRecovery` to prevent stale data from corrupting the recovery process.
- **Stall detection** identifies wedged engines by monitoring render cycle counts while applications report active playback.
- **Automatic teardown** destroys wedged engines immediately while scheduling delayed rebuilds for engines waiting on hardware availability.
- **Event-driven architecture** uses `scheduleEngineReconcile(after:)` to avoid polling loops and maintain efficient resource usage.

## Frequently Asked Questions

### How does vorssaint-utils know when to check for frozen engines?

The mixer registers for `NSWorkspace.didWakeNotification` in the `start()` method of [`AppVolumeMixer.swift`](https://github.com/vorssaint/vorssaint-utils/blob/main/AppVolumeMixer.swift). When macOS emits this notification after waking from sleep, the mixer's observer callback immediately triggers the reconciliation pipeline without requiring periodic polling.

### What specific condition identifies a "wedged" audio engine?

An engine is considered wedged when its render cycle count has stopped incrementing while the associated application still reports active audio playback. This condition is evaluated in `reconcileEngines(with:)` at lines 1159-1205 of [`AppVolumeMixer.swift`](https://github.com/vorssaint/vorssaint-utils/blob/main/AppVolumeMixer.swift), where the mixer compares current render progress against previous observations.

### Can I manually trigger the reconciliation process without putting the Mac to sleep?

Yes. You can manually invoke the same sequence used during wake recovery by clearing the progress caches, refreshing the app list, and calling `reconcileEngines(with:)` directly on the shared mixer instance. This approach is commonly used in unit tests to verify recovery logic without system sleep cycles.

### Why does the mixer wait before rebuilding some engines?

Certain engines depend on specific hardware devices that may not be immediately available after waking from sleep. The `scheduleEngineReconcile(after:)` method queues a delayed rebuild rather than attempting immediate reconstruction, preventing resource contention and ensuring the hardware layer is ready before creating new audio taps.