How FluidVoice Handles Audio Device Selection and Management in macOS

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. 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 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. 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:

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 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:

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.

FluidVoice exposes quick device switching through the macOS menu bar via Sources/Fluid/Services/MenuBarManager.swift. The manager populates a dynamic microphone list using the same static API:

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
  • 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. 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.

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 →