> ## Documentation Index
> Fetch the complete documentation index at: https://typecast.ai/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Swift

> Access the Typecast API with our official Swift SDK.

The official Swift library for the [Typecast API](https://typecast.ai). Convert text to lifelike speech using AI-powered voices.

Compatible with Swift 5.9+ and supports all Apple platforms: iOS, macOS, tvOS, watchOS, and visionOS.

<CardGroup cols={2}>
  <Card title="Swift Package" icon="cube" href="https://github.com/neosapience/typecast-sdk/tree/main/typecast-swift">
    Typecast Swift SDK
  </Card>

  <Card title="Source Code" icon="github" href="https://github.com/neosapience/typecast-sdk/tree/main/typecast-swift">
    Typecast Swift SDK Source Code
  </Card>
</CardGroup>

## Requirements

| Platform | Minimum Version |
| -------- | --------------- |
| iOS      | 13.0+           |
| macOS    | 10.15+          |
| tvOS     | 13.0+           |
| watchOS  | 6.0+            |
| visionOS | 1.0+            |
| Swift    | 5.9+            |

## Installation

<Tabs>
  <Tab title="Swift Package Manager">
    Add the following to your `Package.swift`:

    ```swift theme={null}
    dependencies: [
        .package(url: "https://github.com/neosapience/typecast-sdk.git", from: "0.3.8")
    ]
    ```

    Then add `Typecast` to your target dependencies:

    ```swift theme={null}
    targets: [
        .target(
            name: "YourTarget",
            dependencies: ["Typecast"]
        )
    ]
    ```
  </Tab>

  <Tab title="Xcode">
    1. Open your project in Xcode
    2. Go to **File** → **Add Package Dependencies...**
    3. Enter the repository URL: `https://github.com/neosapience/typecast-sdk.git`
    4. Select version rules and click **Add Package**
    5. Select the `Typecast` library and add it to your target
  </Tab>
</Tabs>

<Warning>
  Latest registered version: **typecast-swift/v0.3.8** in the SDK Git tags. Make sure you have **Swift 5.9 or higher** installed. The SDK uses Swift Concurrency (async/await) which requires this minimum version.
</Warning>

## Quick Start

```swift theme={null}
import AVFoundation
import Typecast

let client = TypecastClient(apiKey: "YOUR_API_KEY")
var audioPlayer: AVAudioPlayer?

// Simple usage with convenience method
let audio = try await client.speak(
    "Hello! I'm your friendly text-to-speech assistant.",
    voiceId: "tc_672c5f5ce59fac2a48faeaee"
)

// Play audio directly from data
audioPlayer = try AVAudioPlayer(data: audio.audioData)
audioPlayer?.play()
print("Duration: \(audio.duration)s, Format: \(audio.format.rawValue)")
```

## Features

The Typecast Swift SDK provides powerful features for text-to-speech conversion:

* **Multiple Voice Models**: Support for `ssfm-v30` (latest) and `ssfm-v21` AI voice models
* **Multi-language Support**: 37 languages including English, Korean, Spanish, Japanese, Chinese, and more
* **Emotion Control**: Preset emotions (normal, happy, sad, angry, whisper, toneup, tonedown) or smart context-aware inference
* **Audio Customization**: Control loudness (LUFS -70 to 0), pitch (-12 to +12 semitones), tempo (0.5x to 2.0x), and format (WAV/MP3)
* **Voice Discovery**: V2 Voices API with filtering by model, gender, age, and use cases
* **Instant Voice Cloning**: Upload a WAV/MP3 sample and create a custom voice ID
* **Swift Concurrency**: Full async/await support for modern Swift development
* **Thread-Safe**: All types conform to `Sendable` for safe concurrent usage
* **Timestamp TTS**: Word- and character-level alignment data for subtitles, karaoke, and lip-sync
* **Cross-Platform**: Works on iOS, macOS, tvOS, watchOS, and visionOS
* **Streaming**: Real-time chunked audio delivery for low-latency playback

## Voice Recommendations

Use `recommendVoices` when you know the desired style but not the exact `voice_id`.

```swift theme={null}
let voices = try await client.recommendVoices(
    query: "warm female voice for a product tutorial",
    count: 3
)

for voice in voices {
    print("\(voice.voiceId) \(voice.voiceName) \(voice.score)")
}
```

Recommendation results contain only `voiceId`, `voiceName`, and `score`. Use `getVoice(voiceId:)` or `getVoices(filter:)` when you need detailed metadata such as supported models, emotions, gender, age, or use cases.

## Configuration

Initialize the client with your API key:

```swift theme={null}
import Typecast

// Direct initialization
let client = TypecastClient(apiKey: "your-api-key")

// With custom base URL
let client = TypecastClient(
    apiKey: "your-api-key",
    baseURL: "https://api.typecast.ai"
)

// Using configuration struct
let config = TypecastConfiguration(apiKey: "your-api-key")
let client = TypecastClient(configuration: config)
```

<Info>
  When requests go through your own proxy, set `baseURL` to the proxy endpoint and omit `apiKey`. The SDK will not send the `X-API-KEY` header for nil or empty keys. Requests to the default Typecast host still require an API key.
</Info>

```swift Proxy without API key theme={null}
let client = TypecastClient(
    baseURL: "https://your-proxy.example.com"
)
```

## Advanced Usage

### Emotion Control (ssfm-v30)

ssfm-v30 offers two emotion control modes: **Preset** and **Smart**.

<Tabs>
  <Tab title="Smart Mode">
    Let the AI infer emotion from context:

    ```swift theme={null}
    let request = TTSRequest(
        voiceId: "tc_672c5f5ce59fac2a48faeaee", // Find voice IDs at https://typecast.ai/developers/api/voices
        text: "Everything is going to be okay.",
        model: .ssfmV30,
        prompt: .smart(SmartPrompt(
            previousText: "I just got the best news!",  // Optional context
            nextText: "I can't wait to celebrate!"      // Optional context
        ))
    )

    let response = try await client.textToSpeech(request)
    audioPlayer = try AVAudioPlayer(data: response.audioData)
    audioPlayer?.play()
    ```
  </Tab>

  <Tab title="Preset Mode">
    Explicitly set emotion with preset values:

    ```swift theme={null}
    let request = TTSRequest(
        voiceId: "tc_672c5f5ce59fac2a48faeaee",
        text: "I am so excited to show you these features!",
        model: .ssfmV30,
        prompt: .preset(PresetPrompt(
            emotionPreset: .happy,  // normal, happy, sad, angry, whisper, toneup, tonedown
            emotionIntensity: 1.5   // Range: 0.0 to 2.0
        ))
    )

    let response = try await client.textToSpeech(request)
    audioPlayer = try AVAudioPlayer(data: response.audioData)
    audioPlayer?.play()
    ```
  </Tab>

  <Tab title="Convenience Method">
    Use the convenience method for quick emotion control:

    ```swift theme={null}
    let audio = try await client.speak(
        "I'm so excited!",
        voiceId: "tc_672c5f5ce59fac2a48faeaee",
        emotion: .happy,
        intensity: 1.5
    )

    audioPlayer = try AVAudioPlayer(data: audio.audioData)
    audioPlayer?.play()
    ```
  </Tab>
</Tabs>

### Audio Customization

Control loudness, pitch, tempo, and output format:

```swift theme={null}
let request = TTSRequest(
    voiceId: "tc_672c5f5ce59fac2a48faeaee",
    text: "Customized audio output!",
    model: .ssfmV30,
    output: OutputSettings(
        targetLufs: -14.0,     // Range: -70 to 0 (LUFS)
        audioPitch: 2,        // Range: -12 to +12 semitones
        audioTempo: 1.2,      // Range: 0.5x to 2.0x
        audioFormat: .mp3     // Options: .wav, .mp3
    ),
    seed: 42  // Unsigned seed for reproducible results
)

let response = try await client.textToSpeech(request)

audioPlayer = try AVAudioPlayer(data: response.audioData)
audioPlayer?.play()
print("Duration: \(response.duration)s, Format: \(response.format.rawValue)")
```

### Generate audio to a file

Use `generateToFile` when you want the SDK to synthesize speech and write the audio bytes directly to a local file. The model defaults to `ssfm-v30`, and `.mp3` / `.wav` extensions infer the output format when no output format is set. Browse available voice IDs on the [Voices](https://typecast.ai/developers/api/voices) page.

```swift theme={null}
try await client.generateToFile(
    "output.mp3",
    request: GenerateToFileRequest(
        voiceId: "tc_672c5f5ce59fac2a48faeaee", // Find voice IDs at https://typecast.ai/developers/api/voices
        text: "Hello from Typecast."
    )
)
```

### Text pauses

Use text pause markup when you only need silent gaps inside one composed text segment. Put `<|5s|>`, `<|1s|>`, `<|0.3s|>`, or `<|0.34413s|>` directly in the text. The value is interpreted as seconds and must end with `s`. This keeps the pause expression visible in plain text without adding separate pause calls.

```swift theme={null}
let audio = try await client.composeSpeech()
    .defaults(ComposerSettings(voiceId: "tc_672c5f5ce59fac2a48faeaee", model: .ssfmV30))
    .say("Hello<|5s|>Nice to meet you<|1s|>Today<|2s|>how does the weather feel?")
    .generate()
```

### Multi-speaker composition

Use the composer chaining API when one output file needs different voices or per-segment options such as pitch, tempo, prompt, or seed. The composer generates each segment as WAV, trims leading/trailing silent PCM samples, and concatenates the result. If you need MP3, generate WAV first and convert it in your app or server pipeline.

```swift theme={null}
let audio = try await client.composeSpeech()
    .defaults(ComposerSettings(voiceId: "tc_672c5f5ce59fac2a48faeaee", model: .ssfmV30))
    .say("Hello there")
    .pause(5)
    .say("Nice to meet you", overrides: ComposerSettings(
        voiceId: "tc_60e5426de8b95f1d3000d7b5",
        output: OutputSettings(audioPitch: 2)
    ))
    .say("Today")
    .pause(2)
    .say("How does the weather feel?")
    .generate()

try audio.audioData.write(to: URL(fileURLWithPath: "conversation.wav"))
```

### Voice Discovery (V2 API)

List and filter available voices with enhanced metadata:

```swift theme={null}
// Get all voices
let voices = try await client.getVoices()

// Filter by criteria
let filteredVoices = try await client.getVoices(filter: VoicesV2Filter(
    model: .ssfmV30,
    gender: .female,
    age: .youngAdult
))

// Get a specific voice
let voice = try await client.getVoice(voiceId: "tc_672c5f5ce59fac2a48faeaee")

// Display voice info
print("ID: \(voice.voiceId), Name: \(voice.voiceName)")
print("Gender: \(voice.gender?.rawValue ?? "N/A"), Age: \(voice.age?.rawValue ?? "N/A")")

for model in voice.models {
    print("Model: \(model.version.rawValue), Emotions: \(model.emotions.joined(separator: ", "))")
}

if let useCases = voice.useCases {
    print("Use cases: \(useCases.joined(separator: ", "))")
}
```

### Multilingual Content

The SDK supports 37 languages with automatic language detection:

```swift theme={null}
// Auto-detect language (recommended)
let request = TTSRequest(
    voiceId: "tc_672c5f5ce59fac2a48faeaee",
    text: "こんにちは。お元気ですか。",
    model: .ssfmV30
)

let response = try await client.textToSpeech(request)

// Or specify language explicitly
let koreanRequest = TTSRequest(
    voiceId: "tc_672c5f5ce59fac2a48faeaee",
    text: "안녕하세요. 반갑습니다.",
    model: .ssfmV30,
    language: .korean  // Explicit language code
)

let koreanResponse = try await client.textToSpeech(koreanRequest)

audioPlayer = try AVAudioPlayer(data: koreanResponse.audioData)
audioPlayer?.play()
```

### Streaming

Stream audio chunks in real-time for low-latency playback:

```swift theme={null}
import AVFoundation
import Typecast

let engine = AVAudioEngine()
let playerNode = AVAudioPlayerNode()
let format = AVAudioFormat(commonFormat: .pcmFormatInt16, sampleRate: 32000, channels: 1, interleaved: true)!

engine.attach(playerNode)
engine.connect(playerNode, to: engine.mainMixerNode, format: format)
try engine.start()
playerNode.play()

let stream = try await client.textToSpeechStream(request)
var first = true

for try await chunk in stream {
    var pcmData = chunk
    if first {
        pcmData = chunk.dropFirst(44)  // Skip 44-byte WAV header
        first = false
    }
    let buffer = AVAudioPCMBuffer(pcmFormat: format, frameCapacity: AVAudioFrameCount(pcmData.count / 2))!
    buffer.frameLength = buffer.frameCapacity
    pcmData.withUnsafeBytes { ptr in
        buffer.int16ChannelData!.pointee.update(from: ptr.bindMemory(to: Int16.self).baseAddress!, count: Int(buffer.frameLength))
    }
    playerNode.scheduleBuffer(buffer)
}
```

<Note>
  **WAV streaming format:** 32000 Hz, 16-bit, mono PCM. The first chunk includes a 44-byte WAV header (size = `0xFFFFFFFF`); subsequent chunks are raw PCM only. For MP3: 320 kbps, 44100 Hz, each chunk is independently decodable. Use `Typecast.OutputStream` to avoid collision with `Foundation.OutputStream`.
</Note>

## Timestamp TTS

`textToSpeechWithTimestamps()` wraps `POST /v1/text-to-speech/with-timestamps` and returns the audio together with per-word and per-character alignment data — useful for karaoke highlights, subtitle generation, and lip-sync applications.

### Basic Usage

```swift theme={null}
import Typecast

let client = TypecastClient(apiKey: "YOUR_API_KEY")

let request = TTSRequestWithTimestamps(
    voiceId: "tc_60e5426de8b95f1d3000d7b5",
    text: "Hello. How are you?",
    model: .ssfmV30
)

let result = try await client.textToSpeechWithTimestamps(request)

audioPlayer = try AVAudioPlayer(data: result.audioData)
audioPlayer?.play()
print("Duration: \(result.audioDuration)s")

for word in result.words {
    print("  [\(word.startTime)s – \(word.endTime)s] \(word.text)")
}
```

### Granularity

Pass `granularity: .word` (default) or `granularity: .char` to control the alignment unit.

```swift theme={null}
let request = TTSRequestWithTimestamps(
    voiceId: "tc_60e5426de8b95f1d3000d7b5",
    text: "Hello. How are you?",
    model: .ssfmV30,
    granularity: .char  // required for Japanese / Chinese
)
```

### Subtitle Export

```swift theme={null}
let srt = result.toSrt()
print(srt)

let vtt = result.toVtt()
print(vtt)
```

<Note>
  **Japanese / Chinese:** Word-level segmentation is not meaningful for languages without whitespace delimiters (jpn, zho). Use `.char` granularity for these languages to get character-level alignment.
</Note>

## Instant Voice Cloning

Clone a custom voice from a short audio sample, then pass the returned `uc_` voice ID directly to TTS.

```swift theme={null}
import AVFoundation
import Foundation
import Typecast

let client = TypecastClient(apiKey: "YOUR_API_KEY")
var audioPlayer: AVAudioPlayer?
let audioData = try Data(contentsOf: URL(fileURLWithPath: "sample.wav"))

let voice = try await client.cloneVoice(
    audio: audioData,
    filename: "sample.wav",
    name: "My Voice",
    model: "ssfm-v30"
)

let response = try await client.speak(
    "Hello from my cloned voice!",
    voiceId: voice.voiceId
)

audioPlayer = try AVAudioPlayer(data: response.audioData)
audioPlayer?.play()
try await client.deleteVoice(voice.voiceId)
```

<Warning>
  Voice cloning audio must be **25 MB or smaller**, the audio duration must be **5-150 seconds**, and the custom voice name must be **1-30 characters**.
</Warning>

## Supported Languages

The SDK supports 37 languages with automatic language detection:

| Code         | Language  | Code         | Language  | Code          | Language   |
| ------------ | --------- | ------------ | --------- | ------------- | ---------- |
| `.english`   | English   | `.japanese`  | Japanese  | `.ukrainian`  | Ukrainian  |
| `.korean`    | Korean    | `.greek`     | Greek     | `.indonesian` | Indonesian |
| `.spanish`   | Spanish   | `.tamil`     | Tamil     | `.danish`     | Danish     |
| `.german`    | German    | `.tagalog`   | Tagalog   | `.swedish`    | Swedish    |
| `.french`    | French    | `.finnish`   | Finnish   | `.malay`      | Malay      |
| `.italian`   | Italian   | `.chinese`   | Chinese   | `.czech`      | Czech      |
| `.polish`    | Polish    | `.slovak`    | Slovak    | `.portuguese` | Portuguese |
| `.dutch`     | Dutch     | `.arabic`    | Arabic    | `.bulgarian`  | Bulgarian  |
| `.russian`   | Russian   | `.croatian`  | Croatian  | `.romanian`   | Romanian   |
| `.bengali`   | Bengali   | `.hindi`     | Hindi     | `.hungarian`  | Hungarian  |
| `.minNan`    | Hokkien   | `.norwegian` | Norwegian | `.punjabi`    | Punjabi    |
| `.thai`      | Thai      | `.turkish`   | Turkish   | `.vietnamese` | Vietnamese |
| `.cantonese` | Cantonese |              |           |               |            |

<Info>
  If not specified, the language will be automatically detected from the input text.
</Info>

## Error Handling

The SDK provides a comprehensive `TypecastError` enum for handling API errors:

```swift theme={null}
import Typecast

do {
    let response = try await client.textToSpeech(request)
} catch let error as TypecastError {
    switch error {
    case .unauthorized(let message):
        // 401: Invalid API key
        print("Invalid API key: \(message)")
    case .paymentRequired(let message):
        // 402: Insufficient credits
        print("Insufficient credits: \(message)")
    case .notFound(let message):
        // 404: Resource not found
        print("Voice not found: \(message)")
    case .validationError(let message):
        // 422: Validation error
        print("Validation error: \(message)")
    case .rateLimitExceeded(let message):
        // 429: Rate limit exceeded
        print("Rate limit exceeded: \(message)")
    case .serverError(let message):
        // 500: Server error
        print("Server error: \(message)")
    case .networkError(let underlyingError):
        // Network connectivity issues
        print("Network error: \(underlyingError.localizedDescription)")
    case .invalidResponse(let message):
        // Invalid response from server
        print("Invalid response: \(message)")
    default:
        print("Error: \(error.localizedDescription)")
    }
    
    // Access status code if available
    if let statusCode = error.statusCode {
        print("HTTP Status: \(statusCode)")
    }
}
```

### Error Types

| Error                | Status Code | Description                  |
| -------------------- | ----------- | ---------------------------- |
| `.badRequest`        | 400         | Invalid request parameters   |
| `.unauthorized`      | 401         | Invalid or missing API key   |
| `.paymentRequired`   | 402         | Insufficient credits         |
| `.notFound`          | 404         | Resource not found           |
| `.validationError`   | 422         | Validation error             |
| `.rateLimitExceeded` | 429         | Rate limit exceeded          |
| `.serverError`       | 500         | Server error                 |
| `.networkError`      | -           | Network connectivity issues  |
| `.invalidResponse`   | -           | Invalid response from server |

## Platform-Specific Usage

### iOS

```swift theme={null}
import Typecast
import AVFoundation

class TTSManager {
    private let client = TypecastClient(apiKey: "YOUR_API_KEY")
    private var audioPlayer: AVAudioPlayer?
    
    func speak(_ text: String) async throws {
        let audio = try await client.speak(text, voiceId: "tc_672c5f5ce59fac2a48faeaee")
        
        // Play audio directly from data
        audioPlayer = try AVAudioPlayer(data: audio.audioData)
        audioPlayer?.play()
    }
}
```

### macOS

```swift theme={null}
import Typecast
import AppKit
import AVFoundation

class MacTTSManager {
    private let client = TypecastClient(apiKey: "YOUR_API_KEY")
    private var audioPlayer: AVAudioPlayer?
    
    func speak(_ text: String) async throws {
        let audio = try await client.speak(text, voiceId: "tc_672c5f5ce59fac2a48faeaee")
        
        audioPlayer = try AVAudioPlayer(data: audio.audioData)
        audioPlayer?.play()
    }
    
}
```

### watchOS

```swift theme={null}
import Typecast
import AVFoundation

class WatchTTSManager {
    private let client = TypecastClient(apiKey: "YOUR_API_KEY")
    private var audioPlayer: AVAudioPlayer?
    
    func speak(_ text: String) async throws {
        let audio = try await client.speak(text, voiceId: "tc_672c5f5ce59fac2a48faeaee")
        
        audioPlayer = try AVAudioPlayer(data: audio.audioData)
        audioPlayer?.play()
    }
}
```

## API Reference

### TypecastClient Methods

| Method                                      | Description                                          |
| ------------------------------------------- | ---------------------------------------------------- |
| `textToSpeech(_:)`                          | Convert text to speech audio                         |
| `generateToFile(_:request:)`                | Generate speech and save it directly to a local file |
| `speak(_:voiceId:model:)`                   | Simple TTS with minimal parameters                   |
| `speak(_:voiceId:model:emotion:intensity:)` | TTS with emotion preset                              |
| `cloneVoice(audio:filename:name:model:)`    | Create a custom voice via instant cloning            |
| `cloneVoice(audioFileURL:name:model:)`      | Create a custom voice from a local audio file        |
| `deleteVoice(_:)`                           | Delete a custom cloned voice                         |
| `getVoices(filter:)`                        | Get available voices with optional filter            |
| `getVoice(voiceId:)`                        | Get a specific voice by ID                           |

### TTSRequest Fields

| Field      | Type             | Required | Description                                         |
| ---------- | ---------------- | -------- | --------------------------------------------------- |
| `voiceId`  | `String`         | ✓        | Voice ID (format: `tc_*` or `uc_*`)                 |
| `text`     | `String`         | ✓        | Text to synthesize (max 2000 chars)                 |
| `model`    | `TTSModel`       | ✓        | TTS model (`.ssfmV21` or `.ssfmV30`)                |
| `language` | `LanguageCode`   |          | Language code (auto-detected if omitted)            |
| `prompt`   | `TTSPrompt`      |          | Emotion settings (`.basic`, `.preset`, or `.smart`) |
| `output`   | `OutputSettings` |          | Audio output settings                               |
| `seed`     | `UInt32`         |          | Unsigned integer seed for reproducibility (≥ 0)     |

### TTSResponse Fields

| Field       | Type           | Description                     |
| ----------- | -------------- | ------------------------------- |
| `audioData` | `Data`         | Generated audio data            |
| `duration`  | `TimeInterval` | Audio duration in seconds       |
| `format`    | `AudioFormat`  | Audio format (`.wav` or `.mp3`) |
