# How FluidVoice Handles Text-to-Speech Generation: A Deep Dive into the Swift Implementation

> FluidVoice handles text-to-speech generation by integrating Apple's AVSpeechSynthesizer and offering a streaming path via FluidAudioProvider. Explore the Swift implementation.

- Repository: [ALTIC/FluidVoice](https://github.com/altic-dev/FluidVoice)
- Tags: deep-dive
- Published: 2026-08-16

---

**FluidVoice generates spoken audio from text by wrapping Apple's `AVSpeechSynthesizer` in a service called `AppleSpeechProvider`, with an alternative streaming path via `FluidAudioProvider` for the Parakeet Flash engine.**

This article examines how the altic-dev/FluidVoice repository implements text-to-speech (TTS) generation in Swift. The codebase abstracts TTS behind provider protocols, allowing the application to switch between Apple's native speech synthesis and a custom low-latency streaming engine without changing calling code.

## The Two TTS Engines in FluidVoice

FluidVoice supports two distinct text-to-speech implementations:

- **Apple Speech** — Uses the system `AVSpeechSynthesizer` for standard macOS TTS
- **Parakeet Flash** — Uses a streaming EOU (End-Of-Utterance) model via `FluidAudioProvider` for real-time, low-latency synthesis

The `VoiceEngineSettingsViewModel` class determines which engine is active based on user preferences stored in `SettingsStore`.

## Apple Speech Provider: The Native TTS Path

### File Location and Core Class

The native TTS implementation lives in [`Sources/Fluid/Services/AppleSpeechProvider.swift`](https://github.com/altic-dev/FluidVoice/blob/main/Sources/Fluid/Services/AppleSpeechProvider.swift). This class wraps `AVSpeechSynthesizer` and exposes a unified `speak` method.

### How AppleSpeechProvider Works

When your code needs to vocalize text through the Apple engine, the flow follows four steps:

1. **Request initiation** — Call `AppleSpeechProvider.speak(_:withVoice:rate:completion:)`
2. **Utterance construction** — Create an `AVSpeechUtterance` with the text, voice identifier, and rate
3. **Synthesis execution** — Pass the utterance to a shared `AVSpeechSynthesizer` instance
4. **Callback propagation** — Forward delegate callbacks to the completion handler

### Code Example: Basic TTS with AppleSpeechProvider

```swift
let provider = AppleSpeechProvider.shared
provider.speak(
    "Hello, world!",
    withVoice: AVSpeechSynthesisVoice(language: "en-US"),
    rate: 0.5
) { finished in
    print("Finished speaking: \(finished)")
}

```

The `rate` parameter accepts values between `0.0` and `1.0`, where `0.5` represents normal speaking speed. The `completion` closure receives `true` when speech finishes naturally or `false` if interrupted.

## FluidAudioProvider: The Streaming TTS Path

For users selecting **Parakeet Flash**, FluidVoice switches to [`Sources/Fluid/Services/FluidAudioProvider.swift`](https://github.com/altic-dev/FluidVoice/blob/main/Sources/Fluid/Services/FluidAudioProvider.swift). This provider implements the same `TextToSpeechProvider` protocol as `AppleSpeechProvider`, maintaining API consistency.

The Parakeet pipeline uses FluidAudio's streaming architecture to generate speech with minimal latency—critical for real-time dictation workflows. Despite the different underlying technology, callers interact with identical method signatures:

```swift
let streamingProvider = FluidAudioProvider.shared
streamingProvider.speak(
    "Streaming speech output",
    withVoice: selectedVoice,
    rate: selectedRate
) { _ in
    // Completion handling identical to AppleSpeechProvider
}

```

## Engine Selection and Settings Persistence

### VoiceEngineSettingsViewModel

The [`Sources/Fluid/UI/AISettings/VoiceEngineSettingsViewModel.swift`](https://github.com/altic-dev/FluidVoice/blob/main/Sources/Fluid/UI/AISettings/VoiceEngineSettingsViewModel.swift) file centralizes engine management. This view model:

- Exposes the current `voiceEngine` selection as a published property
- Maps enum values (`.appleSpeech` or `.parakeetFlash`) to concrete provider instances
- Provides convenience methods for immediate speech requests

### SettingsStore

User preferences persist through [`Sources/Fluid/Persistence/SettingsStore.swift`](https://github.com/altic-dev/FluidVoice/blob/main/Sources/Fluid/Persistence/SettingsStore.swift). This class stores:

- Selected voice engine (`.appleSpeech` or `.parakeetFlash`)
- Voice-specific parameters (language, rate, pitch)
- Custom voice identifiers for third-party voices

### Runtime Engine Switching

```swift
let settings = SettingsStore.shared
let engine = settings.voiceEngine

let ttsProvider: TextToSpeechProvider = 
    (engine == .appleSpeech) ? AppleSpeechProvider.shared
                              : FluidAudioProvider.shared

ttsProvider.speak(text, withVoice: selectedVoice, rate: selectedRate) { _ in
    // Engine-agnostic completion handling
}

```

This pattern keeps the rest of the codebase decoupled from TTS implementation details.

## UI Integration

The `Sources/Fluid/UI/AISettingsView+SpeechRecognition.swift` file renders the settings interface for TTS configuration. Users can:

- Toggle between Apple Speech and Parakeet Flash
- Adjust speech rate with a slider
- Preview voices with instant playback

The view binds directly to `VoiceEngineSettingsViewModel`, ensuring UI changes immediately affect subsequent synthesis requests.

## Provider Protocol Design

Both TTS implementations conform to a shared `TextToSpeechProvider` protocol (implied by the identical APIs). This protocol likely specifies:

```swift
protocol TextToSpeechProvider {
    func speak(
        _ text: String,
        withVoice voice: AVSpeechSynthesisVoice?,
        rate: Float,
        completion: @escaping (Bool) -> Void
    )
}

```

This abstraction enables the view model to return either provider type without exposing implementation specifics to callers.

## Summary

- **FluidVoice implements text-to-speech through two providers**: `AppleSpeechProvider` for native macOS synthesis and `FluidAudioProvider` for streaming Parakeet Flash generation
- **Engine selection happens at runtime** via `VoiceEngineSettingsViewModel`, with preferences stored in `SettingsStore`
- **Both providers share identical APIs**, allowing engine-agnostic calling code throughout the application
- **The native path** constructs `AVSpeechUtterance` objects and delegates to `AVSpeechSynthesizer`
- **The streaming path** uses FluidAudio's low-latency EOU model for real-time speech generation

## Frequently Asked Questions

### How does FluidVoice choose between Apple Speech and Parakeet Flash?

The `VoiceEngineSettingsViewModel` checks `SettingsStore.shared.voiceEngine` when initializing the appropriate provider. This enum value persists across app launches, so users retain their preferred engine.

### Can I use custom voices with FluidVoice's TTS?

Yes. Both providers accept optional `AVSpeechSynthesisVoice` parameters. The Apple engine supports any system-installed voice, while the Parakeet pipeline can use voice models compatible with the FluidAudio streaming framework.

### What happens if speech synthesis is interrupted?

The `completion` closure receives `false` when synthesis doesn't finish naturally—whether from user cancellation, audio session interruption, or errors. The Apple provider forwards these states directly from `AVSpeechSynthesizer` delegate callbacks.

### Where does FluidVoice store TTS configuration?

All voice engine settings live in [`Sources/Fluid/Persistence/SettingsStore.swift`](https://github.com/altic-dev/FluidVoice/blob/main/Sources/Fluid/Persistence/SettingsStore.swift). This includes the active engine selection, speech rate, and voice identifiers. The store persists data across sessions so user preferences survive app restarts.