ASRService in FluidVoice Architecture: The Core Speech Recognition Engine

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, 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) 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

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 lines 23-33 and 57-63.

Switching Speech Models at Runtime

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

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.

Key Files in the ASR Architecture

File Role
Sources/Fluid/Services/ASRService.swift Core service containing all transcription orchestration logic
Sources/Fluid/Services/AppServices.swift Holds the singleton ASRService instance accessed via appServices.asr
Sources/Fluid/Views/CommandModeView.swift Example UI view consuming asr for live dictation
Sources/Fluid/Services/MeetingTranscriptionService.swift Uses asr.fileTranscriptionProvider for pre-recorded audio
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.

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 →