Automatische vertaling · Engels origineel

API en protocollen

Inheemse Apple runtime · PocketTTS

PocketTTS voegt native CoreML spraaksynthese toe aan speech-swift. Alle fasen zijn standaard voor CPU-uitvoering. De Engelse Alba-voorinstelling streamt 1.920-sample frames op 24 kHz (80 ms per frame).

Swift en CLI-gebruik

import PocketTTS
let model = try PocketTTSModel.fromBundle(URL(fileURLWithPath: "/path/to/bundle"))
try await model.generateStreaming(text: "Hello world.") { chunk in
    await consume(chunk.samples)
}

De module AudioCommon definieert model-agnostische protocollen en gedeelde types. Elk conform model kan door deze interfaces onderling worden gebruikt.

Protocoloverzicht

┌─────────────────────────────────────────────────────────┐
│                    AudioCommon                          │
│                                                         │
│  AudioChunk          SpeechGenerationModel (TTS)        │
│  AlignedWord         SpeechRecognitionModel (STT)       │
│  SpeechSegment       ForcedAlignmentModel               │
│                      SpeechToSpeechModel                │
│                      VoiceActivityDetectionModel (VAD)   │
│                      TurnCompletionProvider (EOT)       │
│                      SpeakerEmbeddingModel              │
│                      SpeakerDiarizationModel            │
│                      SpeakerExtractionCapable           │
└─────────────────────────────────────────────────────────┘

SpeechRecognitionModel

Protocol voor spraak-tekstmodellen.

public protocol SpeechRecognitionModel: AnyObject {
    var inputSampleRate: Int { get }
    func transcribe(audio: [Float], sampleRate: Int, language: String?) -> String
    func transcribeWithLanguage(audio: [Float], sampleRate: Int, language: String?) -> TranscriptionResult
}

Configurerende types: Qwen3ASRModel, WhisperASRModel, ParakeetASRModel, ParakeetStreamingASRModel, OmnilingualASRModel (CoreML), OmnilingualASRMLXModel (MLX)

SpeechGenerationModel

Protocol voor tekst-tot-spraakmodellen.

public protocol SpeechGenerationModel: AnyObject {
    var sampleRate: Int { get }
    func generate(text: String, language: String?) async throws -> [Float]
    func generateStream(text: String, language: String?) -> AsyncThrowingStream<AudioChunk, Error>  // has default impl
}

generateStream() heeft een standaard-implementatie die inpakt generate() als een enkele brok. Modellen met echte streaming (bijv. Qwen3-TTS) overschrijven.

Configurerende types: Qwen3TTSModel, CosyVoiceTTSModel, VoxCPM2TTSModel, KokoroTTSModel, IndexTTS2TTSModel

IndexTTS2TTSModel voegt een referentie-audio generate overbelasting toe voor het klonen van spraak zonder shot en gebruikt IndexTTS2SynthesisOptions voor het spreken-snelheids- en pauzebeheer.

Qwen3-TTS lokaal laden

fromLocal valideert vereiste bestanden en checkpoint harde volledigheid, haalt vervolgens modelgrootte, quantisatie en architectuur van config.json. De optionele configuration waarde is slechts een terugval voor weggelaten metagegevens; misvormde of tegenstrijdige metadata gooit Qwen3TTSLoadingError.invalidConfiguration voor modeltoewijzing.

public static func fromLocal(
    modelDirectory: URL,
    tokenizerDirectory: URL,
    configuration: Qwen3TTSConfig? = nil,
    wiredMemoryPolicy: Qwen3TTSWiredMemoryPolicy = .none,
    progressHandler: ((Double, String) -> Void)? = nil
) throws -> Qwen3TTSModel

public static func fromPretrained(
    modelId: String = Qwen3TTSModel.defaultModelId,
    tokenizerModelId: String = "Qwen/Qwen3-TTS-Tokenizer-12Hz",
    cacheDir: URL? = nil,
    tokenizerCacheDir: URL? = nil,
    offlineMode: Bool = false,
    wiredMemoryPolicy: Qwen3TTSWiredMemoryPolicy = .pin(fraction: 0.9),
    progressHandler: ((Double, String) -> Void)? = nil
) async throws -> Qwen3TTSModel
Geheugenbeleid

fromLocal standaard naar .none, dus het verandert de procesbreed Metal bedrade geheugenlimiet niet. fromPretrained behoudt zijn .pin(fraction: 0.9) standaard. Beide voorgetrainde laders accepteren een aparte tokenizerCacheDir.

Gedwongen uitlijningModel

Protocol voor tijdstempel-uitlijning op woordniveau.

public protocol ForcedAlignmentModel: AnyObject {
    func align(audio: [Float], text: String, sampleRate: Int, language: String?) -> [AlignedWord]
}

SpeechToSpeechModel

Protocol voor spraak-spraak-dialoogmodellen.

public protocol SpeechToSpeechModel: AnyObject {
    var sampleRate: Int { get }
    func respond(userAudio: [Float]) -> [Float]
    func respondStream(userAudio: [Float]) -> AsyncThrowingStream<AudioChunk, Error>
}

Conformerende typen: PersonaPlexModel

HibikiModel vertalen

Speech-to-speech vertaalstuurprogramma. SpeechToSpeechModel omdat het contract anders is: input en output zijn verschillende talen, output lengte is variabel (EOS-gedreven, begrensd op max(tSrc * 5/2, tSrc + 20) Stappen ruwweg 1.0–1.6× bronduur op FLEURS-stijl clips), en er is een parallelle tekst-getoken stroom naast de audio. Volledige gids →

public final class HibikiTranslateModel {
    public let cfg: HibikiConfig
    public static var defaultModelId: String { "aufklarer/Hibiki-Zero-3B-MLX-4bit" }
    public static var modelId8bit: String  { "aufklarer/Hibiki-Zero-3B-MLX-8bit" }

    public static func fromPretrained(
        modelId: String = defaultModelId,
        cacheDir: URL? = nil,
        offlineMode: Bool = false,
        progressHandler: ((Double, String) -> Void)? = nil
    ) async throws -> HibikiTranslateModel

    public func warmUp()

    public func translate(
        sourceAudio: [Float],
        sourceLanguage: HibikiSourceLanguage = .fr,
        verbose: Bool = false
    ) -> (audio: [Float], textTokens: [Int32])

    public func translateStream(
        sourceAudio: [Float],
        sourceLanguage: HibikiSourceLanguage = .fr,
        streaming: HibikiStreamingConfig = .default,
        verbose: Bool = false
    ) -> AsyncThrowingStream<AudioChunk, Error>
}

public enum HibikiSourceLanguage: String, CaseIterable, Sendable, Codable {
    case fr, es, pt, de
}

Eenvoudige snelheid: 24 kHz mono, zowel input als output. Frame rate: 12,5 Hz (Mimi codec). Brontalen: FR / ES / PT / DE. Targettaal: EN. Instellen HIBIKI_GREEDY=1 bij het opstarten van het proces voor deterministische argmax-decodering (matcht met de CI-kanaries).

VoiceActivityDetectionModel

Protocol voor de detectie van spraakactiviteit.

public protocol VoiceActivityDetectionModel: AnyObject {
    var inputSampleRate: Int { get }
    func detectSpeech(audio: [Float], sampleRate: Int) -> [SpeechSegment]
}

TurnConvolutionProvider

Protocol voor eind-van-draai classifiers geraadpleegd door StreamingVADProcessor op elke bevestigde VAD pauze. Een VAD hoort alleen stilte; een turn-completion model luistert naar de prosodie van de hele uitdrukking, dus een mid-sencentie pauze houdt de agent wachten terwijl een voltooide zin een onmiddellijk antwoord krijgt. Kaarten naar speech-core's sc_turn_completion_vtable_t. Zie de Smart Turn guide.

public protocol TurnCompletionProvider: AnyObject {
    /// Probability in [0, 1] that the turn is complete, given the audio of the
    /// turn so far (Smart Turn looks at the last 8 s).
    func turnCompleteProbability(audio: [Float], sampleRate: Int) throws -> Float
}

Conformerende typen: SmartTurnModel (Smart Turn v3.2)

SpeakerEmbeddingModel

Protocol voor speaker inbedding extractie.

public protocol SpeakerEmbeddingModel: AnyObject {
    var inputSampleRate: Int { get }
    var embeddingDimension: Int { get }
    func embed(audio: [Float], sampleRate: Int) -> [Float]
}

Conforming types: WeSpeakerModel

SpeakerDiarizationModel

Protocol voor speaker diarization modellen die speaker labels toe te wijzen aan audio segmenten.

public protocol SpeakerDiarizationModel: AnyObject {
    var inputSampleRate: Int { get }
    func diarize(audio: [Float], sampleRate: Int) -> [DiarizedSegment]
}

Conforming types: DiarizationPipeline (Pyannote), SortformerDiarizer

SpeakerExtractionCapable

Uitgebreid diarization protocol voor motoren die het extraheren van segmenten van een speaker ondersteunen met behulp van een referentie inbedding. Niet alle motoren ondersteunen dit (Sorformer is end-to-end en produceert geen speaker inbeddingen).

public protocol SpeakerExtractionCapable: SpeakerDiarizationModel {
    func extractSpeaker(audio: [Float], sampleRate: Int, targetEmbedding: [Float]) -> [SpeechSegment]
}

Conforming types: DiarizationPipeline (Pyannote only)

Gedeelde typen

AudioChunk

public struct AudioChunk {
    public let samples: [Float]   // PCM samples
    public let sampleRate: Int    // Sample rate (e.g. 24000)
}

Speechsegment

public struct SpeechSegment {
    public let startTime: Float   // Start time in seconds
    public let endTime: Float     // End time in seconds
}

UitgelijndWord

public struct AlignedWord {
    public let text: String       // The word
    public let startTime: Float   // Start time in seconds
    public let endTime: Float     // End time in seconds
}

Gediariseerd segment

public struct DiarizedSegment {
    public let startTime: Float   // Start time in seconds
    public let endTime: Float     // End time in seconds
    public let speakerId: Int     // Speaker identifier (0-based)
}

Dialoogsegment

Een geparseerd segment van dialoogtekst met meerdere luidsprekers met optionele luidspreker- en emotietags. Gebruikt met DialogueParser en DialogueSynthesizer voor CosyVoice3 dialoogsynthese.

public struct DialogueSegment: Sendable, Equatable {
    public let speaker: String?   // Speaker identifier ("S1", "S2"), nil for untagged
    public let emotion: String?   // Emotion tag ("happy", "whispers"), nil if none
    public let text: String       // Cleaned text to synthesize
}

DialoguePasser

Ontleedt tekst voor de dialoog met meerdere luidsprekers met inline speakers ([S1]) en emoties ((happy)).

public enum DialogueParser {
    static func parse(_ text: String) -> [DialogueSegment]
    static func emotionToInstruction(_ emotion: String) -> String
}

Ingebouwde emoties: happy/excited, sad, angry, whispers/whispering, laughs/laughing, calm, surprised, serious. Onbekende tags passeren als vrije vorm instructies.

Dialoogsynthesizer

Orchestras multi-segment dialoog synthese met per-luidspreker stem klonen, stilte gaten, en crossfade.

public enum DialogueSynthesizer {
    static func synthesize(
        segments: [DialogueSegment],
        speakerEmbeddings: [String: [Float]],
        model: CosyVoiceTTSModel,
        language: String,
        config: DialogueSynthesisConfig,
        verbose: Bool
    ) -> [Float]
}

DialoogSynthesisConfig

public struct DialogueSynthesisConfig: Sendable {
    public var turnGapSeconds: Float      // Default: 0.2
    public var crossfadeSeconds: Float    // Default: 0.0
    public var defaultInstruction: String // Default: "You are a helpful assistant."
    public var maxTokensPerSegment: Int   // Default: 500
}

PIERLLM

Protocol voor taalmodelintegratie met spraakpijpleidingen. Bruggen een LLM naar de VoicePipeline ASR → LLM →TTS stroom.

public protocol PipelineLLM: AnyObject {
    func chat(messages: [(role: MessageRole, content: String)],
              onToken: @escaping (String, Bool) -> Void)
    func cancel()
}

Ingebouwde adapter: Qwen3PipelineLLM bruggen Qwen35MLXChat naar dit protocol met token opruimen, annuleren en in afwachting van zin accumulatie.

AudioIO

Herbruikbare audio I/O manager die AVAudioEngine ketelplaat elimineert. Handvat microfoon capture, resampling, playback en audio level meting.

let audio = AudioIO()
try audio.startMicrophone(targetSampleRate: 16000) { samples in
    pipeline.pushAudio(samples)
}
audio.player.scheduleChunk(ttsOutput)
audio.stopMicrophone()

AudioIO bevat a StreamingAudioPlayer voor TTS uitvoer en een AudioRingBuffer voor draad-veilige audiooverdracht tussen het vastleggen en het gevolg van draden.

SystemAudioTap

Neemt de systeemuitvoermix op, wat de Mac momenteel speelt als mono Float32 bij een beller-gekozen sample rate (aftap van het audioproces, macOS 14.4+). De mono-global tap sluit standaard het huidige proces uit, zodat een app zijn eigen afspelen nooit opnieuw kan opnemen, en het wordt verpakt in een privé-geaggregeerd apparaat dat alleen de tik bevat.

let tap = SystemAudioTap()
try tap.start(targetSampleRate: 16000) { samples in
    pipeline.pushAudio(samples)
}
tap.stop()

Capture overleeft de standaard-output-apparaat switches . De interne resampler wordt herbouwd wanneer de mix rate verandert. De host app moet NSAudioCaptureUsageDescription. Als de opname toestemming wordt geweigerd, kan er nog steeds een tik worden aangemaakt die alleen stilte geeft: horloge framesCaptured groeien terwijl nonSilentFrames blijft 0 en niet gesloten.

Onafhankelijke bronnen met tijdstempel

Beide opnameklassen stellen tijdsoverbelasting bloot. CapturedAudioChunk draagt mono samples bij sampleRate plus een optionele Mach hostTime voor het eerste invoerframe. Omdat beide bronnen dezelfde hostklok gebruiken, kan een app hun PCM gescheiden houden en afgeleide transcriptevenementen ordenen zonder de audio te mengen.

try tap.startTimestamped(targetSampleRate: 16000) { systemChunk in
    systemPipeline.pushAudio(systemChunk.samples)
    recordSystemTime(systemChunk.hostTime)
}

let microphone = AudioIO(enableAEC: true, enablePlayback: false)
try microphone.startMicrophoneTimestamped(targetSampleRate: 16000) { micChunk in
    microphonePipeline.pushAudio(micChunk.samples)
    recordMicrophoneTime(micChunk.hostTime)
}

Voor alleen-luisteropname, enableAEC: true activeert Apple Voice Processing voordat het microfoonformaat wordt gelezen, en enablePlayback: false laat de ongebruikte speler achterwege. Op macOS wordt apparaatafspelen verwijderd van microfoonsamples en wordt foutmeldingen over het starten van AEC gemeld in plaats van terug te vallen naar ruwe invoer.

Incrementele bestandinvoer en -erkenning

CapturedAudioChunk draagt ook een monotone frameIndex en een isFinal marker. AudioFileLoader.stream produceert deze brokken op verzoek met begrensd geheugen, hersamples eenmaal met persistente converterstatus en gemiddelden alle invoerkanalen standaard. Gebruik .first of .select([indices]) wanneer kanaalrouting bekend is.

StreamingRecognitionModel en StreamingRecognitionSession bieden één cache-bewaarzamende incrementele API over Parakeet Streaming en Nemotron Core ML/MLX:

let source = AudioFileLoader.stream(
    url: inputURL,
    options: AudioFileStreamOptions(targetSampleRate: 16_000,
                                    chunkDuration: 0.32,
                                    channelSelection: .mixAll))
let session = try model.makeStreamingSession(language: "en-US")

for try await chunk in source {
    for update in try session.push(chunk) {
        render(update.text, final: update.isFinal)
    }
}
for update in try session.finish() {
    render(update.text, final: true)
}

SentencePieceModel

Gedeelde protobuf-lezer voor SentencePiece .model bestanden, leeft in AudioCommon. Elke module die SentenceStukken moet decoderen (PersonaPlex, OmnilingualASR, toekomst ASR / TTS poorten) bouwt zijn eigen decoder bovenop deze enkele lezer in plaats van het protobux-wireformaat opnieuw in te voeren.

public struct SentencePieceModel: Sendable {
    public struct Piece: Sendable, Equatable {
        public let text: String
        public let score: Float
        public let type: Int32
        public var pieceType: PieceType? { get }
        public var isControlOrUnknown: Bool { get }
    }
    public enum PieceType: Int32 {
        case normal = 1, unknown = 2, control = 3,
             userDefined = 4, unused = 5, byte = 6
    }
    public let pieces: [Piece]
    public var count: Int { get }
    public subscript(_ id: Int) -> Piece? { get }
    public init(contentsOf url: URL) throws
    public init(modelPath: String) throws
    public init(data: Data) throws
}

Gebruikt door: OmnilingualASR.OmnilingualVocabulary, PersonaPlex.SentencePieceDecoder. Gedekt door 7 unit tests in Tests/AudioCommonTests/SentencePieceModelTests.

MLXCommon.SDPA

Geschaalde punt-product aandacht helpers gedeeld over elke MLX aandachtsmodule (Qwen3-ASR / Qwen3-TTS / Qwen3-Chat / CosyVoice / PersonaPlex / OmnilingualASR).Elke module houdt zijn eigen projecties .. SDPA alleen behandelt de reshape → aandacht → merge boilerplate.

public enum SDPA {
    // Flat [B, T, H*D] input: project/reshape happens inside
    public static func multiHead(
        q: MLXArray, k: MLXArray, v: MLXArray,
        numHeads: Int, headDim: Int, scale: Float,
        mask: MLXArray? = nil
    ) -> MLXArray

    // GQA / MQA variant with separate query and KV head counts
    public static func multiHead(
        q: MLXArray, k: MLXArray, v: MLXArray,
        numQueryHeads: Int, numKVHeads: Int, headDim: Int, scale: Float,
        mask: MLXArray? = nil
    ) -> MLXArray

    // Already-shaped [B, H, T, D] (RoPE / KV cache paths)
    public static func attendAndMerge(
        qHeads: MLXArray, kHeads: MLXArray, vHeads: MLXArray,
        scale: Float,
        mask: MLXArray? = nil
    ) -> MLXArray

    // Same, with ScaledDotProductAttentionMaskMode enum (newer API)
    public static func attendAndMerge(
        qHeads: MLXArray, kHeads: MLXArray, vHeads: MLXArray,
        scale: Float,
        mask: MLXFast.ScaledDotProductAttentionMaskMode
    ) -> MLXArray

    // Low-level head merge: [B, H, T, D] → [B, T, H*D]
    public static func mergeHeads(_ attn: MLXArray) -> MLXArray
}

Alle reshape-oproepen gebruiken -1 voor de batch-dimensie zodat de helpers samenkomen met MLX.compile(shapeless:) grafieken die batch variëren tijdens de runtime (bijv. Qwen3-TTS Speaker autoregressief decoderen).

HTTP API Server

De speech-server binaire stelt elk model bloot in speech-swift als HTTP REST eindpunten plus een WebSocket eindpunt dat het OpenAI Realtime API implementeert. Modellen worden lui geladen op eerste verzoek; geef --preload door om ze allemaal te verwarmen bij het opstarten.

swift build -c release
.build/release/speech-server --port 8080

# Preload every model at startup
.build/release/speech-server --port 8080 --preload

REST Eindpunten

Eindpunt Methode Verzoek Respons
/transcribePOSTaudio/wav bodyJSON { text } (Qwen3-ASR)
/v1/audio/transcriptionsPOSTMultipart { file, model, response_format?, language?, prompt?, temperature? }JSON, sms, werkbose JSON, SRT, of VTT
/v1/audio/speechPOSTJSON { model, input, voice, response_format?, speed? }audio/wav of audio/pcm body
/speakPOSTJSON { text, engine?, language?, voice? }audio/wav body (Qwen3-TTS, CosyVoice en Kokoro)
/respondPOSTaudio/wav bodyaudio/wav body (PersonaPlex)
/enhancePOSTaudio/wav bodyaudio/wav body (DeepFilterNet3)
/vadPOSTaudio/wav bodyJSON-segmentlijst
/diarizePOSTaudio/wav bodyJSON DiarizedSegment list
/embed-speakerPOSTaudio/wav bodyJSON [Float] (256-dim)

/v1/audio/speech maps standard TTS model en stemnamen naar lokale motoren. Het standaard naar WAV; response_format: "pcm" geeft koploze 24 kHz mono PCM16 klein-endiaanse audio terug. Niet-ondersteunde gecomprimeerde formaten worden expliciet afgewezen.

/v1/audio/transcriptions accepteert een WAV-bestand als multipart-formuliergegevens en standaardinstellingen om {"text":"..."}; response_format kan ook tekst, werkbose JSON, SRT, of VTT. Op Apple, model aliassen selecteren een lokale ASR motor. De verpakte speech-core server aan Linux en Windows gebruikt lui geladen Parakeet, accepteert PCM16/24/32 of Float32 WAV, en caps uploads op 25 MiB en 10 minuten.

# Transcribe a file
curl -X POST http://localhost:8080/transcribe \
  --data-binary @recording.wav \
  -H "Content-Type: audio/wav"

# Synthesize speech
curl -X POST http://localhost:8080/speak \
  -H "Content-Type: application/json" \
  -d '{"text": "Hello world", "engine": "cosyvoice"}' \
  -o output.wav

# OpenAI-compatible transcription
curl http://localhost:8080/v1/audio/transcriptions \
  -F "[email protected];type=audio/wav" \
  -F "model=whisper-1"

# OpenAI-compatible speech synthesis
curl http://localhost:8080/v1/audio/speech \
  -H "Content-Type: application/json" \
  -d '{"model":"tts-1","voice":"alloy","input":"Hello world","response_format":"wav"}' \
  -o openai-output.wav

# Full speech-to-speech round trip
curl -X POST http://localhost:8080/respond \
  --data-binary @question.wav \
  -o response.wav

OpenAI Realtime API (/v1/realtime)

Het WebSocket eindpunt op ws://host:port/v1/realtime implementeert het OpenAI Realtime protocol. Alle berichten zijn JSON met een type discriminator; audiopayloads zijn base64-encoded PCM16 bij 24 kHz mono.

Tijdens koude modelladingen of lange generaties zendt de server lichtgewicht realtime.keepalive JSON-evenementen en websocket pong-besturingsframes ruwweg elke 15 seconden uit totdat de modeluitvoer klaar is. Klanten kunnen deze gebeurtenissen negeren of behandelen als activiteitsindicatoren.

Client → Server-gebeurtenissen

Gebeurtenis Betreft
session.updateEngine, language, voice, audio format, en optionele server VAD configureren
input_audio_buffer.append Voeg een base64 PCM16 brok toe aan de invoerbuffer
input_audio_buffer.commit Het gebufferde audio committen voor transcriptie
input_audio_buffer.clearDe huidige invoerbuffer weggooien
response.create Vraag de TTS synthese aan voor de meegeleverde tekst/instructies

Server → Client events

Gebeurtenis Betekenis
session.createdHandshake voltooid, standaard config uitgezonden
session.updatedMeest recente session.update erkend
input_audio_buffer.speech_startedServer VAD detecteerde het begin van een spraakommekeer
input_audio_buffer.speech_stoppedServer VAD beëindigde de draai na stilte of de duurlimiet
input_audio_buffer.committedAudio geaccepteerd en in de wachtrij geplaatst voor transcriptie
conversation.item.input_audio_transcription.completedASR resultaat met uiteindelijke transcripttekst
response.audio.deltaBase64 PCM16 brok gesynthetiseerd geluid
response.audio.done Geen audio-brokken meer voor dit antwoord
response.doneGefinaliseerd respons (metadata + latentiestatistieken)
errorFout bij envelop met type en message

Automatische draaien met server VAD

Expliciet input_audio_buffer.commit blijft de standaard. Om de server te laten sluiten en transcriberen draait automatisch met Silero VAD, configureert turn_detection. De server houdt alleen de gevraagde pre-roll inactief, zendt spraakstart/stop en commit gebeurtenissen in volgorde uit, en force-closeert een continue actieve turn bij max_turn_duration_ms.

ws.send(JSON.stringify({
  type: 'session.update',
  session: {
    turn_detection: {
      type: 'server_vad',
      threshold: 0.5,
      prefix_padding_ms: 300,
      silence_duration_ms: 500,
      max_turn_duration_ms: 120000
    }
  }
}));

// Disable automatic turns and return to explicit commits:
// { type: 'session.update', session: { turn_detection: null } }
const ws = new WebSocket('ws://localhost:8080/v1/realtime');

// ASR: push audio, request transcription
ws.send(JSON.stringify({ type: 'input_audio_buffer.append', audio: base64PCM16 }));
ws.send(JSON.stringify({ type: 'input_audio_buffer.commit' }));
// → conversation.item.input_audio_transcription.completed

// TTS: request synthesis and stream audio deltas
ws.send(JSON.stringify({
  type: 'response.create',
  response: { modalities: ['audio', 'text'], instructions: 'Hello world' }
}));
// → response.audio.delta (repeated), response.audio.done, response.done

De server leeft in het AudioServer SPM-product. Een voorbeeldbrowserclient wordt verzonden op Examples/websocket-client.html Open het naast een draaiende server om de volledige ASR + TTS heen en weer te rijden.

Modeldownloads

Alle modellen worden gedownload van HuggingFace bij het eerste gebruik en gecached in ~/Library/Caches/qwen3-speech/. De AudioCommon module biedt een gedeelde HuggingFaceDownloader die download, caching en integriteitscontrole behandelt.

Herhalingen downloaden

Modelladers en download API's accepteren downloadOptions: DownloadOptions. onRetry ontvangt een DownloadRetryEvent voor elke terugslag, met modelId, attempt, maxAttempts, het origineel erroren retryInSeconds. nil betekent dat er geen herhalingen overblijven; volledige cache terugval kan nog steeds slagen.

Terugroepen zijn synchroon en @Sendable, zonder garantie voor de hoofdacteur. Annulering produceert geen herhalingsactie. Stel retryDelaysSeconds in om het beleid aan te passen ([] betekent één poging); het expliciete download API argument heeft voorrang. Standaard aantal pogingen en bytevooruitgang worden behouden. Foute pogingen worden ook geregistreerd op kennisgevingsniveau door AudioLog.download.

Coöperatieve annulering

Voor MLX transcriptie, Qwen3ASRModel.transcribeCheckingCancellation(audio:sampleRate:options:) gooit CancellationError wanneer taakannulering wordt waargenomen voordat functie extractie, codering, decodering voorvulling of een decoderingstap. GPU werk reeds ingediend kan worden voltooid. De synchrone transcribe en transcribeBatch API's behouden hun niet-throwing-behavior en negeren taakannulatie. speech-server maakt gebruik van het annulerings-aware ingangspunt voor Qwen3-ASR.

MOSS-TTS-Nano

MossTTSNanoModel serialiseert verzoeken en creëert nieuwe CoreML status voor elke uitdrukking. synthesizer en synthesizeStream behoudt stereo; SpeechGenerationModel geeft een mono downmix terug. Pass language: nul. Streams eindigen met een lege laatste marker.

import AudioCommon
import MossTTSNano

let model = try await MossTTSNanoModel.fromPretrained()
let reference = try await model.encodeReference(url: referenceURL)
let result = try await model.synthesize(
    text: "The weather is nice today.", reference: reference,
    options: MossTTSNanoOptions(maxFrames: 300, seed: 1))
try WAVWriter.writeStereo(left: result.left, right: result.right,
    sampleRate: 48000, to: outputURL)

MOSS-TTS-Nano

Lokale lokale gevolgtrekking

Voor 12B worden herhaalde naast elkaar liggende tool-call sluitmarkers direct na een volledige, gevalideerde oproep gebruikt. Ze maken geen andere oproep of wijzigen de argumenten ervan. Standalone markers en onjuist gevormde argumenten blijven fouten; E4B behoudt strikte behandeling.

Om mislukte native chatverzoeken te behouden, moet failureCaptureDirectory worden ingesteld in het gevolg manifest. Capture is standaard uitgeschakeld. De directory moet groep en andere toegang weigeren; nieuwe mappen gebruiken 0700 en foutbestanden gebruiken 0600. Bestanden omvatten de exacte aanvraagformulier, ID-aanvraag en foutmelding. Gereed 12B-generaties behouden ook de weergegeven prompt, token-ID's, ruwe uitvoer en stoppen reden voor het verwerken. E4B fouten hebben geen ruwe generatierecord. Deze lokale bestanden bevatten conversatie- en gereedschapsinhoud; HTTP-fouten en succesvolle verzoeken bevatten ze niet.

Gemma4UnifiedChat.generateResponse accepteert een optionele onGeneration callback met een Gemma4GenerationTrace. De callback draait voordat de tool wordt ontleden, ook wanneer die voltooiing wordt afgewezen. Opname verandert de sampling of geaccepteerde tool syntaxis niet.

Facultatief agentDirectory laadt een geënsceneerd Gemma 4 12B Unified INT4 tekstmodel naast E4B. Gebruik model: "gemma4-12b-it" voor chat met native tools, resultaatgeschiedenis, en annulering. E4B blijft beschikbaar voor andere verzoeken. Gemma4UnifiedChat gebruikt de gepinde MLX tekstimplementatie en releases van elk verzoek. KV cache. Response parsing verwijdert kanaalgrenzen uit teruggestuurde tekst. Ostered tool-call terminators worden afgewezen als misvormde reacties. Voor Gemma 4 12B, inhoud van gereedschapsberichten bewaart JSON als tekst, die overeenkomt met het checkpoint chat-sjabloon.

Bronbouwen accepteren --inference-manifest modellen.json om gefaseerd Gemma 4 E4B INT4, VoxCPM2, volledig Whisper Large-v3 en Silero VAD. Deze dienst start offline en wijst incompatibele modelidentiteit af.

VoxCPM2 ondersteunt gewichtPrecisie: .float16 voor een niet-gequantiseerd controlepunt. Zijn belangrijkste parameters gebruiken FP16; AudioVAE en sommige berekeningen blijven FP32. INT8 controlepunten worden voor deze modus afgewezen.

Gemma4ConversationMessage, Gemma4Tool, en genereerRespons conserveren native gereedschapsverklaringen, argumenten en resultaatgeschiedenis. InvloedCancellatieToken annuleert de generatie coöperatief. Silero makeIndependentStream isolaten status, en procesChunkThrowing propageert inferentiefouten.

Whisper ASRModel.fromDirectory belastingen opgevoerd vol Large-v3 CoreML bundels.De 32-layer decoder kan zijn prefix bouwen zonder TextDecoderContextPrefill.mlmodelc en brengt per-stap tempo's uit. De standaard Whisper CLI-model blijft Turbo.

De native service serialiseert chat, synthese en transcriptie; lichtgewicht VAD heeft een aparte streamstatus. Gemma prefill gebruikt 256-getoken batches, de KV-status wordt vrijgegeven en de MLX cache is beperkt tot 128 MiB met een 20 GiB-allocatielimiet.

Speech with stream: true returns NDJSON audio events at 48 kHz, followed by done or error. Annuleren op verzoekID at /v1/inferentie/ancel. GET /v1/invloed/capaciteiten en /v1

speech-server --inference-manifest models.json --port 8080
POST /v1/inference
{"operation":"chat","model":"gemma4-e4b-it","requestID":"chat-1","messages":[{"role":"user","content":"Hello"}],"maxTokens":128,"temperature":0}

chat: gemma4-e4b-it
speech: voxcpm2
transcribe: whisper-large-v3
vad: silero-vad

Audio: audioBase64 (mono PCM16), sampleRate
VAD: streamID, reset, endStream; 512 samples/frame at 16000 Hz
Tools: name, description, parameters
Tool call: id, name, arguments
Tool result: role="tool", toolCallID, content

Facultatief modelbezit

SpeechRuntime biedt gedeelde laden, opzegbare FIFO-toegang en seriematige uitvoering. Qwen3ASRRuntime selecteert MLX of volledige CoreML via expliciete descriptoren; de oorspronkelijke API's en CLI-vlaggen blijven beschikbaar.

De audioserver heeft één uitvoervergunning door elke volledige aanvraag, inclusief gestreamde uitvoer, met maximaal 32 verzoeken die wachten. ASR storingen veroorzaken HTTP-fouten of WebSocket error events; geldige lege transcripten blijven succesvol. Afspelen behoudt overflowsamples. Gecachede modellen en in afwachting van afspeelaudio blijft het geheugen bezet.

import Qwen3ASRRuntime

let runtime = Qwen3ASRRuntime(descriptor: Qwen3ASRDescriptors.small)
let text = try await runtime.transcribe(audio: samples, sampleRate: 16000)
try await runtime.unload()

Voxtral TTS

Voxtral 4B TTS 2603 draait lokaal op Apple Silicon via de Voxtral tTS module: 20 vooraf ingestelde stemmen, negen talen en 24 kHz mono speech.

import VoxtralTTS

let model = try await VoxtralTTSModel.fromPretrained()
for try await chunk in model.generateStream(text: "Hello, welcome to Voxtral.", voice: "casual_male") {
    print(chunk.frameIndex, chunk.samples.count, chunk.isFinal)
}

Uitsluitend stemmen vooraf instellen; referentie-audio klonen is niet beschikbaar. Audiouitvoer stroomt door de SDK, CLI, WebSocket en HTTP PCM. Geef de volledige invoertekst vooraf. Modelgewichten gebruiken CC BY-NC 4.0.

import AudioCommon

let player = StreamingAudioPlayer()
player.skipInitialSilence = false

Lees de Voxtral TTS gids