# How to Select an Audio Input Device in Fluid Voice: A Complete Technical Guide

> Learn how to select an audio input device in Fluid Voice. This guide covers device enumeration, user selection binding, and applying your choice for seamless audio capture.

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

---

**Fluid Voice selects an audio input device by enumerating Core Audio devices at startup, binding the user's choice to a persisted UID in the UI, and applying that selection through `AudioCaptureCoordinator` when capture begins.**

To select an audio input device in Fluid Voice, you interact with the `AudioDeviceManager` which wraps macOS Core Audio functions, choose from the picker in the audio preferences or command mode UI, and have the `AudioCaptureCoordinator` reconcile your selection against available hardware before starting capture. This article walks through the complete implementation in the `altic-dev/FluidVoice` repository.

## How Device Enumeration Works in Fluid Voice

Fluid Voice discovers microphones through a thin C wrapper around macOS Core Audio. The `AudioDeviceManager` class exposes this functionality to Swift via the `AudioDeviceManaging` protocol.

In [`Sources/Fluid/AudioDeviceManaging.swift`](https://github.com/altic-dev/FluidVoice/blob/main/Sources/Fluid/AudioDeviceManaging.swift), the manager calls into [`Sources/CoreAudioCaptureSupport/CoreAudioCaptureSupport.c`](https://github.com/altic-dev/FluidVoice/blob/main/Sources/CoreAudioCaptureSupport/CoreAudioCaptureSupport.c) to enumerate hardware:

```c
// CoreAudioCaptureSupport.c – low-level device enumeration
FVStatus fv_core_audio_capture_list_input_devices(
    AudioDeviceList **outDevices,
    size_t *outCount
);

```

This C function queries `kAudioHardwarePropertyDevices`, filters for `kAudioDevicePropertyStreamConfiguration` inputs, and populates a list of `AudioDevice` structs. The Swift wrapper transforms this into a typed array:

```swift
// Sources/Fluid/AudioDeviceManaging.swift
let devices = AudioDeviceManager.shared.listInputDevices()
devices.forEach { device in
    print("\(device.uid) – \(device.name) – transport: \(device.transportType)")
}

```

Each `AudioDevice` carries a **UID** (persistent identifier), **name** (human-readable), and **transport type** (USB, Bluetooth, built-in, etc.).

## Binding the UI to Device Selection

The selection interface appears in [`Sources/Fluid/Views/CommandModeView.swift`](https://github.com/altic-dev/FluidVoice/blob/main/Sources/Fluid/Views/CommandModeView.swift) and related settings views. The picker binds directly to `SettingsStore.shared.selectedInputDeviceUID`:

```swift
// CommandModeView.swift – microphone selector UI
Menu {
    ForEach(devices) { device in
        Button(device.name) {
            SettingsStore.shared.selectedInputDeviceUID = device.uid
        }
    }
} label: {
    Text("Microphone")
}

```

When the user taps a device name, the UID is immediately written to `UserDefaults` via [`Sources/Fluid/SettingsStore.swift`](https://github.com/altic-dev/FluidVoice/blob/main/Sources/Fluid/SettingsStore.swift). This persistence ensures the selection survives app restarts.

## Applying the Selected Device at Capture Time

The `AudioCaptureCoordinator` does not use the stored UID directly. Instead, it calls `reconcileMicrophoneSelection(preferredUID:)` to resolve the UID to a valid `AudioObjectID`. This pattern handles cases where the preferred device was unplugged:

```swift
// AudioCaptureCoordinator.swift – device resolution before capture
func reconcileMicrophoneSelection(preferredUID: String?) -> AudioDevice? {
    guard let uid = preferredUID,
          let device = AudioDeviceManager.shared.deviceMatching(uid: uid) else {
        return AudioDeviceManager.shared.defaultInputDevice()  // Fallback
    }
    return device
}

```

Once reconciled, the coordinator creates a capture session by calling the C layer:

```c
// CoreAudioCaptureSupport.h – capture creation with explicit device
FVStatus fv_core_audio_capture_create(
    AudioObjectID deviceID,
    FVCoreAudioCapture **outCapture
);

```

The capture starts with `fv_core_audio_capture_start(_:)` and runs until explicitly stopped or the device disappears.

## Handling Disconnected Devices with AudioHardwareObserver

Fluid Voice monitors hardware changes through `AudioHardwareObserver`. If the selected device is unplugged during a session, the observer notifies the coordinator, which automatically falls back to the system default input device.

This resilience is tested in [`Tests/FluidDictationIntegrationTests/HotkeyShortcutTests.swift`](https://github.com/altic-dev/FluidVoice/blob/main/Tests/FluidDictationIntegrationTests/HotkeyShortcutTests.swift) around line 1224, where `reconcileMicrophoneSelection` is exercised with missing and present UIDs.

## Complete Flow Summary

1. **Enumeration** – `AudioDeviceManager.listInputDevices()` queries Core Audio via `fv_core_audio_capture_list_input_devices()`.
2. **Selection** – User picks from the `Menu` in `CommandModeView`, setting `SettingsStore.shared.selectedInputDeviceUID`.
3. **Persistence** – The UID is saved to `UserDefaults` immediately.
4. **Reconciliation** – `AudioCaptureCoordinator` calls `reconcileMicrophoneSelection(preferredUID:)` to validate the UID.
5. **Capture** – Valid device ID passed to `fv_core_audio_capture_create(_:outCapture:)` to start microphone input.

## Summary

- Fluid Voice wraps macOS Core Audio in a C layer ([`CoreAudioCaptureSupport.c`](https://github.com/altic-dev/FluidVoice/blob/main/CoreAudioCaptureSupport.c)) with Swift bindings ([`AudioDeviceManaging.swift`](https://github.com/altic-dev/FluidVoice/blob/main/AudioDeviceManaging.swift)).
- Device UIDs are the stable identifiers used for selection and persistence.
- The `AudioCaptureCoordinator` reconciles stored preferences against current hardware before each capture session.
- Automatic fallback to the default device occurs if the preferred microphone becomes unavailable.

## Frequently Asked Questions

### Where does Fluid Voice store the selected microphone setting?

Fluid Voice stores the selected device UID in `UserDefaults` through `SettingsStore.shared.selectedInputDeviceUID` defined in [`Sources/Fluid/SettingsStore.swift`](https://github.com/altic-dev/FluidVoice/blob/main/Sources/Fluid/SettingsStore.swift). This persists the choice across app launches without requiring manual configuration each time.

### What happens if I unplug my selected microphone while Fluid Voice is running?

The `AudioHardwareObserver` detects the device removal and notifies the `AudioCaptureCoordinator`, which falls back to the system default input device. The UI does not automatically update to show this change, but capture continues uninterrupted.

### Can I programmatically list available input devices in Fluid Voice?

Yes. Import the `Fluid` module and use `AudioDeviceManager.shared.listInputDevices()` to retrieve an array of `AudioDevice` structs containing UID, name, and transport type. For matching a specific UID, use `deviceMatching(uid:)`.

### How does Fluid Voice handle multiple microphones with identical names?

Fluid Voice distinguishes devices by **UID**, not display name. The UID is a unique hardware identifier provided by Core Audio, ensuring that two microphones of the same model do not collide in the selection logic.