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 locallydownloadModel(_:progressHandler:)– Manages on-demand downloads with progress reporting to the UIensureAsrReady()– 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 latencystartPreferredAudioCapture– Activates the appropriate audio capture path based on settingsstopActiveAudioCapture– Safely terminates audio collectionAudioCapturePipeline– 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 sessionTranscriptionExecutor– An actor that serializes access to CoreML models, preventing concurrent access crashespartialTranscriptionandfinalText–@Publishedproperties 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 statepartialTranscription– Provides live transcription updates as the user speaksaudioLevelPublisher– Streams audio levels for visual feedback componentsdownloadProgress– 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 loadingcancelModelDownload– Stops in-progress downloadsTranscriptionExecutor.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 byinitialize()) to maintain app responsiveness while preparing expensive ML resources. - Model management is fully automated through
checkIfModelsExistAsync(),downloadModel(), andensureAsrReady(), ensuring models are available without manual intervention. - Thread-safety is guaranteed through the
TranscriptionExecutoractor, which serializes access to CoreML models and prevents concurrent access crashes. - Real-time UI updates are delivered via
@Publishedproperties likepartialTranscriptionandaudioLevelPublisher, 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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →