Speech

Give your app a voice.

Bring on-device multilingual speech to text to your app or workflows.

Primordial gives your app two speech workflows. You can transcribe a local audio file that your app already owns, or create a recording session that captures the microphone and delivers text while the person is speaking. Both use the .voiceInput AI capability and the same coordinated speech resource.

Create a Primordial Client

Import Primordial and create a PrimordialClient.

import Foundation
import Primordial

let primordial = PrimordialClient()

The client already selects Primordial’s default Parakeet 0.6B multilingual speech to text model.

Check Whether Voice Input Is Available

File transcription and microphone recording require the .voiceInput capability. Check its status before showing the feature or asking your user to download anything.

let status = await primordial.ai.status(for: .voiceInput)

print(status.isInstalled)
print(status.isAvailable)
print(status.downloadBytes)
print(status.requiredAvailableBytes)

isInstalled tells you whether the required speech model is already on the device. isAvailable tells you whether the complete capability can be used in the current environment.

When installation is needed, use downloadBytes and requiredAvailableBytes to explain the download and storage requirement in your own interface before continuing. Checking status does not load or download the model.

Activate Primordial

Activate Primordial before installing the speech model, transcribing audio, or starting a recording.

try await primordial.activate(
    .evaluationKey("pk_eval_your_key_here")
)

You can check primordial.isActivated when your UI needs to reflect activation state.

Prepare Voice Input

makeAvailable checks storage, downloads the missing speech resources, verifies them, and reports progress as an asynchronous stream.

for try await progress in primordial.ai.makeAvailable(.voiceInput) {
    if let fraction = progress.fractionCompleted {
        print("Voice setup: \(Int(fraction * 100))%")
    }

    print(progress.phase)
    print(progress.downloadedBytes as Any)
    print(progress.totalBytes as Any)
}

Calling makeAvailable again reuses an existing valid download. Transcription and recording may load an installed model when needed, but they never start a model download silently.

Primordial supports background model downloads when your app forwards background URL-session events. See Background downloads for the required app delegate code. If the user force-quits your app, the download is canceled.

Add Speech Permissions

Before recording, add privacy descriptions that tell people why your app needs the microphone and live speech recognition. Use descriptions that name the feature and explain what the captured speech does.

Xcode key Raw Info.plist key Example description
Privacy - Microphone Usage Description NSMicrophoneUsageDescription Record your voice so the app can create a transcript.
Privacy - Speech Recognition Usage Description NSSpeechRecognitionUsageDescription Show live text while you speak.

A sandboxed macOS app must also enable Audio Input under the App Sandbox capability. File transcription does not request microphone access, but the file must still be local and voice input must already be installed and available.

Choose a Speech Workflow

Both workflows return a PrimordialTranscription, but they begin with different audio sources.

Workflow Use it when Entry point
Transcribe a file Your app already has a local audio file to turn into text. transcribe(_:)
Record and transcribe You want to capture the microphone and update your UI while someone speaks. recording(localeIdentifier:)

Transcribe an Audio File

Pass a local file URL owned by your application. A remote web URL is not accepted. You can download remote audio into your app’s storage and use Primordial to transcribe it.

let recordingURL = URL.documentsDirectory
    .appendingPathComponent("interview.m4a")

let transcription = try await primordial.transcribe(recordingURL)

print(transcription.text)
print(transcription.duration)
print(transcription.languageIdentifier as Any)

for segment in transcription.segments {
    print(segment.text, segment.startTime, segment.endTime)
}

text contains the complete transcription and duration contains the audio duration in seconds. When the selected speech engine reports them, segments divide the transcription into timed pieces and languageIdentifier identifies the detected language. The language identifier may be nil, and some results may not contain timed segments.

Record and Transcribe Speech

The Parakeet 0.6B v3 multilingual ASR model does not support real-time transcription. Primordial SDK provides dual-track transcription by using SFSpeechRecognizer for fast provisional text while Parakeet produces the stable final transcript.

SFSpeechRecognizer is Apple’s built-in speech recognition framework. It is fast and responsive, but not multilingual or accurate as Parakeet. By default, Primordial uses en-US, but you can choose another locale when creating the recorder with localeIdentifier.

let recording = primordial.recording(
    localeIdentifier: "en-US"
)

try await recording.start { update in
    showLiveText(update.liveText)
    showConfirmedText(update.confirmedText)
}

start asks for microphone and speech-recognition permission at the point of use. The update closure runs on the main actor, so it can update interface state directly. Primordial manages the update stream and its task lifetime for the application-level workflow.

Understand recording updates

Value Meaning
liveText Fast, provisional text that can change while the person continues speaking.
confirmedText Text confirmed by Primordial’s local speech transcription path.
isDualTrack true when both live and confirmed transcription tracks are active.
isFinal true for the final confirmed update delivered when recording stops.

We recommend clearly indicating that the liveText is not multi-lingual, provisional and may change. Only treat it as temporary interface feedback. In the UI, show the live transcript in a different color or style to distinguish it from the final transcript.

Stop or Cancel the Recording


Call stop() when the person finishes speaking. It ends microphone capture, delivers the final update, and returns the completed multi-lingual transcription with any encoded recording data that is available.

let result = try await recording.stop()

showTranscript(result.transcription.text)

if let audioData = result.audioData {
    saveRecording(audioData)
}

audioData is optional because a session can finish without captured audio or audio conversion may fail even though transcription succeeds. The returned transcription includes the requested locale identifier and elapsed recording duration, timed segments may be empty for microphone recordings.

When the person abandons the recording, cancel it explicitly:

await recording.cancel()

Both stopping and canceling release the active speech resource. A recording session is one-shot: after stop() or cancel(), create a new session before recording again.

Handle Speech Errors

Speech operations fail explicitly when voice input is missing, activation is required, permission is denied, the audio URL is invalid, another resource operation conflicts with active speech, or transcription or recording fails. Swift task cancellation remains CancellationError.

do {
    let transcription = try await primordial.transcribe(recordingURL)
    showTranscript(transcription.text)
} catch is CancellationError {
    print("Transcription canceled")
} catch let error as PrimordialActivationError {
    print(error.localizedDescription)
} catch let error as PrimordialAIError {
    switch error {
    case .capabilityUnavailable(let requirement):
        print("Voice download: \(requirement.downloadBytes) bytes")

    case .permissionDenied(let message),
         .invalidRecord(let message),
         .resourceConflict(let message):
        print(message)

    case .transcriptionFailed:
        print("The audio could not be transcribed")

    case .recordingFailed:
        print("The recording could not be completed")

    default:
        print(error.localizedDescription)
    }
}

Do not start a model download automatically from an error handler. Explain what is missing, ask the person first, and call makeAvailable(.voiceInput) only after they agree.


For permission recovery, simulator limitations, and interrupted downloads, see Recording or transcription fails.