# How FluidVoice Handles Audio Device Selection and Management in macOS

> FluidVoice simplifies macOS audio device selection and management using its AudioDevice API. Discover how it handles input/output, defaults, and app-local modes for your ASR pipeline.

- Repository: [ALTIC/FluidVoice](https://github.com/altic-dev/FluidVoice)
- Tags: how-to-guide
- Published: 2026-07-03

---

**FluidVoice abstracts macOS CoreAudio operations through a static `AudioDevice` API that enumerates input/output devices, caches default device names to avoid SwiftUI rendering races, and supports both system-synced and app-local device selection modes for its ASR pipeline.**

FluidVoice is a macOS speech recognition application that requires robust interaction with system audio hardware. The project handles audio device selection and management by wrapping CoreAudio’s C-based APIs in a clean Swift service layer, ensuring thread-safe enumeration while preventing race conditions during UI updates.

## CoreAudio Abstraction with AudioDeviceService

The foundation of FluidVoice’s audio handling resides in [`Sources/Fluid/Services/AudioDeviceService.swift`](https://github.com/altic-dev/FluidVoice/blob/main/Sources/Fluid/Services/AudioDeviceService.swift). This service provides a static `AudioDevice` interface that shields the rest of the codebase from direct CoreAudio calls.

Key capabilities include:

- Enumerating all hardware with `listAllDevices()`, `listInputDevices()`, and `listOutputDevices()`
- Querying system defaults via `getDefaultInputDevice()` and `getDefaultOutputDevice()`
- Changing system defaults using `setDefaultInputDevice(uid:)` and `setDefaultOutputDevice(uid:)`

All interactions are funneled through this static API, keeping components like `ASRService` and `MenuBarManager` agnostic of CoreAudio implementation details.

### Avoiding CoreAudio Race Conditions

A critical implementation detail appears in the UI layer. FluidVoice maintains `cachedDefaultInputName` and `cachedDefaultOutputName` properties in [`SettingsView.swift`](https://github.com/altic-dev/FluidVoice/blob/main/SettingsView.swift) because calling `AudioDevice.getDefaultInputDevice()` during SwiftUI view rendering triggers a CoreAudio HAL initialization race condition. The source code contains a comment labeled "CRITICAL FIX" explaining this workaround, ensuring smooth UI performance by caching device names rather than querying hardware during view updates.

## UI Layer Device Selection in SettingsView

The primary interface for audio configuration lives in [`Sources/Fluid/UI/SettingsView.swift`](https://github.com/altic-dev/FluidVoice/blob/main/Sources/Fluid/UI/SettingsView.swift). This SwiftUI view displays available microphones and output devices while managing the sync state between the app and macOS system preferences.

When **Sync audio devices with system** is enabled (`SettingsStore.shared.syncAudioDevicesWithSystem`), user selections immediately update the system defaults:

```swift
if SettingsStore.shared.syncAudioDevicesWithSystem {
    let success = AudioDevice.setDefaultInputDevice(uid: newUID)
    if success {
        cachedDefaultInputName = AudioDevice.getDefaultInputDevice()?.name ?? ""
    }
}

```

When sync is disabled, the app stores the selected UID locally via `SettingsStore.shared.inputDeviceUID` without touching macOS system defaults.

## ASR Pipeline Integration

The `ASRService` in [`Sources/Fluid/Services/ASRService.swift`](https://github.com/altic-dev/FluidVoice/blob/main/Sources/Fluid/Services/ASRService.swift) manages which microphone feeds the speech recognition engine. During startup, it caches the current device list and resolves the appropriate input source based on the sync setting.

When sync is disabled, the service retrieves the locally stored UID and validates device availability:

```swift
guard let preferredUID = SettingsStore.shared.inputDeviceUID else {
    return AudioDevice.getDefaultInputDevice()
}
guard let device = AudioDevice.getInputDevice(byUID: preferredUID) else {
    return AudioDevice.getDefaultInputDevice()
}
return device

```

This ensures the ASR pipeline uses the user’s preferred device even if it differs from the macOS system default, falling back automatically when devices are unavailable.

## Menu Bar Device Management

FluidVoice exposes quick device switching through the macOS menu bar via [`Sources/Fluid/Services/MenuBarManager.swift`](https://github.com/altic-dev/FluidVoice/blob/main/Sources/Fluid/Services/MenuBarManager.swift). The manager populates a dynamic microphone list using the same static API:

```swift
let inputDevices = AudioDevice.listInputDevices()
let defaultUID = AudioDevice.getDefaultInputDevice()?.uid
populateMicrophoneMenu(inputDevices: inputDevices, defaultInputUID: defaultUID)

```

This allows users to select input devices without opening the main settings window, with all interactions funneled through `AudioDevice.setDefaultInputDevice(uid:)`.

## Summary

- FluidVoice wraps macOS CoreAudio in a static `AudioDevice` API provided by [`AudioDeviceService.swift`](https://github.com/altic-dev/FluidVoice/blob/main/AudioDeviceService.swift)
- The UI caches default device names in `cachedDefaultInputName` and `cachedDefaultOutputName` to prevent CoreAudio HAL initialization races during SwiftUI rendering
- **Sync audio devices with system** mode controls whether changes affect macOS system defaults or remain local to the app via `SettingsStore.shared.inputDeviceUID`
- `ASRService` resolves device selection during startup using `getInputDevice(byUID:)`, falling back to system defaults when preferred devices are unavailable
- `MenuBarManager` provides dynamic menu population for quick device switching without opening Settings

## Frequently Asked Questions

### How does FluidVoice prevent UI freezing when listing audio devices?

FluidVoice prevents UI freezing by caching default device names in `cachedDefaultInputName` and `cachedDefaultOutputName` within [`SettingsView.swift`](https://github.com/altic-dev/FluidVoice/blob/main/SettingsView.swift). Direct CoreAudio calls during SwiftUI view rendering can trigger HAL initialization race conditions, so the app reads device names once and updates the cache only after explicit user actions or system notifications.

### Can FluidVoice use a different microphone than the macOS system default?

Yes. When **Sync audio devices with system** is disabled in `SettingsStore.shared.syncAudioDevicesWithSystem`, FluidVoice stores the selected device UID locally. The `ASRService` retrieves this UID via `AudioDevice.getInputDevice(byUID:)` and uses it for speech recognition without changing the system default input device via `setDefaultInputDevice(uid:)`.

### What happens if the selected audio device is disconnected?

If the preferred device specified by `SettingsStore.shared.inputDeviceUID` is not present in `AudioDevice.listInputDevices()`, the `ASRService` falls back to the current system default by calling `AudioDevice.getDefaultInputDevice()`. This ensures the speech recognition pipeline continues functioning even when hardware is removed.

### Where does FluidVoice store the selected audio device preferences?

FluidVoice persists device preferences through `SettingsStore`, which uses standard macOS UserDefaults. The `syncAudioDevicesWithSystem` boolean and `selectedInputUID` string are stored locally, allowing the app to recall the user's preferred input device across launches without requiring system-level changes.