# ASRService in FluidVoice Architecture: The Core Speech Recognition Engine

> Discover ASRService, the core speech recognition engine in FluidVoice. Understand its role in audio capture, model management, and real-time transcription for your app.

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

---

**ASRService is the central orchestrator of FluidVoice's speech-to-text pipeline, managing audio capture, model lifecycle, provider abstraction, and real-time transcription while exposing a thread-safe SwiftUI-compatible API.**

ASRService acts as the single point of contact for every component in the FluidVoice app that requires transcription capabilities. Located in [`Sources/Fluid/Services/ASRService.swift`](https://github.com/altic-dev/FluidVoice/blob/main/Sources/Fluid/Services/ASRService.swift), this service class coordinates the entire Automatic Speech Recognition (ASR) workflow—from low-level audio capture to high-level model management—while abstracting the complexity of multiple transcription backends behind a unified interface.

## Core Responsibilities of ASRService

### Lifecycle Management

ASRService implements a deferred initialization pattern to ensure UI responsiveness. The `init()` method performs lightweight setup only, registering observers without heavy resource allocation. All substantial initialization is deferred to `initialize()`, which is called after the UI appears to prevent blocking the app launch sequence.

This separation allows the service to exist as a singleton in `AppServices` while postponing microphone permission requests and model loading until the user actually enters a dictation view.

### Model Management

The service handles the complete lifecycle of speech recognition models through three key methods:

- **`checkIfModelsExistAsync()`** – Verifies whether required models are already downloaded and available locally
- **`downloadModel(_:progressHandler:)`** – Manages on-demand downloads with progress reporting to the UI
- **`ensureAsrReady()`** – Coordinates model existence checks, downloads, and loading into memory without duplication

These methods ensure that transcription can begin immediately when requested, with `ASRService` handling any necessary background preparation.

### Provider Abstraction

ASRService abstracts multiple transcription backends behind a unified interface. The computed property `transcriptionProvider` (lines 90-115 in [`ASRService.swift`](https://github.com/altic-dev/FluidVoice/blob/main/ASRService.swift)) switches on `SettingsStore.shared.selectedSpeechModel` to instantiate the appropriate backend—whether **Parakeet**, **Whisper**, **Apple Speech**, or other supported engines.

This architecture allows users to switch between cloud-based and on-device recognition without changing the API contract for the rest of the application.

### Audio Capture Management

The service manages the complete audio pipeline through `AVAudioEngine` and CoreAudio integrations:

- **`prewarmAudioEngineIfPossible`** – Prepares the audio engine before recording to minimize latency
- **`startPreferredAudioCapture`** – Activates the appropriate audio capture path based on settings
- **`stopActiveAudioCapture`** – Safely terminates audio collection
- **`AudioCapturePipeline`** – Routes audio to a thread-safe buffer for processing

### Real-Time Transcription

ASRService coordinates the streaming transcription workflow using Swift concurrency primitives:

- **`streamingTask`** – Maintains the long-running transcription session
- **`TranscriptionExecutor`** – An actor that serializes access to CoreML models, preventing concurrent access crashes
- **`partialTranscription`** and **`finalText`** – `@Published` properties that propagate incremental results to SwiftUI views

This architecture ensures thread-safe access to expensive ML resources while delivering real-time feedback to users.

### UI Integration

The service exposes observable state through multiple `@Published` properties that SwiftUI views bind to:

- **`isRunning`** – Indicates active recording state
- **`partialTranscription`** – Provides live transcription updates as the user speaks
- **`audioLevelPublisher`** – Streams audio levels for visual feedback components
- **`downloadProgress`** – Reports model download completion percentages

Views such as `CommandModeView`, `RewriteModeView`, and `MeetingTranscriptionView` access the service through `AppServices.shared.asr`, ensuring a consistent source of truth across the UI.

### Error Handling and Cancellation

ASRService implements robust cancellation semantics to prevent resource leaks:

- **`cancelModelPreparation`** – Aborts ongoing model loading
- **`cancelModelDownload`** – Stops in-progress downloads
- **`TranscriptionExecutor.cancelAndAwaitPending()`** – Gracefully shuts down transcription tasks

These methods ensure that switching between transcription modes or closing the app never leaves background tasks hanging.

## Practical Implementation Examples

### Initializing and Starting a Session

```swift
import Fluid

// Create the service (typically via AppServices)
let asr = ASRService()

// Initialize after UI appearance
asr.initialize()

// Start a dictation session
Task {
    await asr.start()
    // ... user speaks ...
    let transcript = await asr.stop()
    print("Final transcript:", transcript)
}

```

This pattern corresponds to the implementation in [`ASRService.swift`](https://github.com/altic-dev/FluidVoice/blob/main/ASRService.swift) lines 23-33 and 57-63.

### Switching Speech Models at Runtime

```swift
// Change the backend in SettingsStore
SettingsStore.shared.selectedSpeechModel = .whisper

// Force ASRService to reload the provider
asr.resetTranscriptionProvider()

```

The `resetTranscriptionProvider()` method (lines 498-525) clears cached provider instances and triggers a fresh model verification cycle.

### Binding to Live Results in SwiftUI

```swift
struct DictationView: View {
    @ObservedObject private var asr = ASRService()

    var body: some View {
        VStack {
            Text(asr.partialTranscription)
                .font(.title2)
            
            ProgressView(value: asr.downloadProgress ?? 0)
        }
        .onAppear { asr.initialize() }
    }
}

```

SwiftUI views rely on the `@Published var partialTranscription` property defined at line 87 of [`ASRService.swift`](https://github.com/altic-dev/FluidVoice/blob/main/ASRService.swift).

## Key Files in the ASR Architecture

| File | Role |
|------|------|
| [`Sources/Fluid/Services/ASRService.swift`](https://github.com/altic-dev/FluidVoice/blob/main/Sources/Fluid/Services/ASRService.swift) | Core service containing all transcription orchestration logic |
| [`Sources/Fluid/Services/AppServices.swift`](https://github.com/altic-dev/FluidVoice/blob/main/Sources/Fluid/Services/AppServices.swift) | Holds the singleton `ASRService` instance accessed via `appServices.asr` |
| [`Sources/Fluid/Views/CommandModeView.swift`](https://github.com/altic-dev/FluidVoice/blob/main/Sources/Fluid/Views/CommandModeView.swift) | Example UI view consuming `asr` for live dictation |
| [`Sources/Fluid/Services/MeetingTranscriptionService.swift`](https://github.com/altic-dev/FluidVoice/blob/main/Sources/Fluid/Services/MeetingTranscriptionService.swift) | Uses `asr.fileTranscriptionProvider` for pre-recorded audio |
| [`Sources/Fluid/Settings/SettingsStore.swift`](https://github.com/altic-dev/FluidVoice/blob/main/Sources/Fluid/Settings/SettingsStore.swift) | Persists `selectedSpeechModel`; changes trigger provider resets |
| `Sources/Fluid/Networking/*Provider.swift` | Concrete implementations (Parakeet, Whisper, AppleSpeech) that `ASRService` delegates to |

## Summary

- **ASRService** serves as the central coordinator for all speech recognition operations in FluidVoice, abstracting away the complexity of multiple transcription backends.
- The service implements a two-phase initialization (`init()` followed by `initialize()`) to maintain app responsiveness while preparing expensive ML resources.
- Model management is fully automated through `checkIfModelsExistAsync()`, `downloadModel()`, and `ensureAsrReady()`, ensuring models are available without manual intervention.
- Thread-safety is guaranteed through the `TranscriptionExecutor` actor, which serializes access to CoreML models and prevents concurrent access crashes.
- Real-time UI updates are delivered via `@Published` properties like `partialTranscription` and `audioLevelPublisher`, enabling seamless SwiftUI integration.
- The provider abstraction layer allows runtime switching between Whisper, Parakeet, and Apple Speech without changing the consumer API.

## Frequently Asked Questions

### What is the difference between `init()` and `initialize()` in ASRService?

The `init()` method performs lightweight setup only, registering observers to avoid blocking the app launch. The `initialize()` method handles the heavy work—checking microphone permissions, prewarming the audio engine, and verifying model availability—and should be called after the UI appears, typically in a SwiftUI `.onAppear` modifier.

### How does ASRService handle multiple transcription backends?

ASRService uses a computed property `transcriptionProvider` that switches on `SettingsStore.shared.selectedSpeechModel`. When the user selects a different model (e.g., switching from Whisper to Parakeet), the service instantiates the appropriate provider class from `Sources/Fluid/Networking/` and manages the transition without requiring changes to the UI code.

### Can ASRService transcribe pre-recorded audio files or only live audio?

ASRService supports both live streaming and file transcription. While the primary `start()` method captures live audio through `AudioCapturePipeline`, the service also exposes a `fileTranscriptionProvider` used by `MeetingTranscriptionService` to process pre-recorded audio files using the same backend models and thread-safe execution context.

### How does ASRService prevent memory leaks when cancelling operations?

The service implements structured cancellation through `cancelModelPreparation()`, `cancelModelDownload()`, and the `TranscriptionExecutor.cancelAndAwaitPending()` method. These ensure that background tasks, model downloads, and transcription sessions are properly terminated and awaited before deallocation, preventing resource leaks during rapid mode switches or app termination.