Maskinoversettelse · Engelsk original

API og protokoller

Native Apple Runtime · PocketTTS

PocketTTS legger til opprinnelig CoreML talesyntese til speech-swift. Alle stadier er standard til CPU-beskyttelse. Den engelske Alba-forhåndsinnstilte strømmer 1.920-prøverammer ved 24 kHz (80 ms per ramme).

Swift og CLI-bruk

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)
}

Modulen AudioCommon definerer modell-agnostiske protokoller og delte typer. Enhver samsvarende modell kan brukes utskiftelig gjennom disse grensesnittene.

Protokol Oversikt

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

SpeechRecensionModel

Protokoll for tale-til-tekst-modeller.

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
}

Forenlige typer: Qwen3ASRModel, WhisperASRModel, ParakeetASRModel, ParakeetStreamingASRModel, OmnilingualASRModel (CoreML), OmnilingualASRMLXModel (MLX)

TaleGenerationModel

Protokoll for tekst-til-speech modeller.

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() har en standard implementasjon som wraps generate() som en enkelt del. Modeller med sann streaming (f.eks. Qwen3-TTS) overstyrer den.

Forenlige typer: Qwen3TTSModel, CosyVoiceTTSModel, VoxCPM2TTSModel, KokoroTTSModel, IndexTTS2TTSModel

IndexTTS2TTSModel legger til en referanse-audio generate overbelastning for null-shot stemmekloning og bruker IndexTTS2SynthesisOptions for talerate- og pausekontroller.

Qwen3-TTS lokal lasting

fromLocal validerer nødvendige filer og kontrollpunktshard-fullføring, deretter stammer modellstørrelse, kvantisering og arkitektur fra config.json. Den valgfrie configuration verdien er bare en reserveverdi for utelatt metadata; feilaktige eller motstridende metadatakast Qwen3TTSLoadingError.invalidConfiguration før modelltildeling.

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
Minnepolitikk

fromLocal standard til .none, så det endrer ikke prosessen-vidt Metal wired-hukommelsesgrense. fromPretrained holder sin .pin(fraction: 0.9) standard. Begge forhåndsutdannede lasere aksepterer en egen tokenizerCacheDir.

TvungetAjustmentModel

Protokol for ord-nivå tidsstempeljustering.

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

TaleTo speelingModel

Protokoll for tale-til-speech dialogmodeller.

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

Samarbeidstyper: PersonaPlexModel

HibikiTranslateModel

Speech-to-speech oversettelsesdriver. Oversettelsesdriver er ikke i samsvar med SpeechToSpeechModel fordi kontrakten er forskjellig: inngang og utgang er forskjellige språk, er utgangslengden variabel (EOS-drevet, dekket på max(tSrc * 5/2, tSrc + 20) trinn - ca. 1.0-1.6× kilde varighet på FLEURS-stil klipp), og det er en parallell tekst-tokenstrøm ved siden av lyden. Full guide →

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
}

Prøvehastighet: 24 kHz-mono, både inngang og utgang. Rammerate: 12.5 Hz (Mimi codec). Kildespråk: FR / ES / PT / DE. Målspråk: EN. Sett HIBIKI_GREEDY=1 ved prosess lansering for deterministisk argmax dekoding (samler CI-kanariene).

VoiceActivity DefinitionModel

Protokol for deteksjon av stemmeaktivitet.

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

TurnClubProvider

Protokol for sluttklassifiseringsklassifisering som konsulteres av StreamingVADProcessor på hver bekreftet VAD pause. En VAD hører bare stillhet; en tur-fullføringsmodell lytter til prosodien til hele uttalelsen, så en midt-sentens pause holder agenten venter mens en ferdig setning får et øyeblikkelig svar. Kart til speech-cores sc_turn_completion_vtable_t. Se den 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
}

Samarbeidstyper: SmartTurnModel (Smart Turn v3.2)

SpeakerEinbyggingModel

Protokoll for utvinning av høyttalere.

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

Samarbeidstyper: WeSpeakerModel

SpeakerDiarizationModel

Protokol for høyttaler diariseringsmodeller som tildeler høyttalermerker til lydsegmenter.

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

Samarbeidstyper: DiarizationPipeline (Pyannote), SortformerDiarizer

SpeakerExtractionCapable

Utvidet diariseringsprotokoll for motorer som støtter å trekke ut målhøyttalerens segmenter ved hjelp av en referanseinnbygging. Ikke alle motorer støtter dette (Sortformer er slutt-til-ende og produserer ikke høyttalerinnbygginger).

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

Conforming typer: DiarizationPipeline (Pyannote bare)

Delte typer

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
}

Justert ord

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
}

DiarizedSegment

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)
}

DialogSegment

Et tolket segment av multi-høyttaler dialogtekst med valgfrie høyttaler- og følelserstagger. Brukt med DialogueParser og DialogueSynthesizer for CosyVoice3 dialogsyntese.

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
}

DialogParser

Tolker flerspråklig dialogtekst med innlinjehøyttalertagger ([S1]) og følelsestagger ( (happy)).

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

Innbyggede følelser: happy/excited, sad, angry, whispers/whispering, laughs/laughing, calm, surprised, serious. Ukjente tagger passerer gjennom som freeform instruksjoner.

DialogSynthesizer

Orkesterer fler-segment dialog syntese med per-speaker stemme kloning, stillhet hull og crossfade.

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

Dialog SynteseConfig

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
}

PipelineLLM

Protokoll for språkmodellintegrasjon med stemmerørledninger. Broer en LLM til VoicePipélines ASR → LLM → TTS flyt.

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

Built-in adapter: Qwen3PipelineLLM broer Qwen35MLXChat til denne protokollen med token opprydding, kansellering og avventende frase akkumulering.

AudioIO

Reusable lyd I/O manager som eliminerer AVAudioEngine kjeleplate. Hanterer mikrofon fangst, resampling, avspilling og lydnivåmåling.

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

AudioIO inkluderer en StreamingAudioPlayer for TTS utgang og en AudioRingBuffer for trådsikre lydoverføring mellom fangst- og inferenstråder.

SystemAudioTap

Fanger systemets utgangsblanding - hva Mac spiller for tiden - som mono Float32 ved en caller-chosen prøvehastighet (Kore lydprosesstrykk, macOS 14.4+). Det mono globale trykket utelukker den gjeldende prosessen som standard, så en apps egen avspilling er aldri gjeninnpakket, og det er pakket i en privat aggregert enhet som inneholder bare trykk.

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

Fangst overlever standardutgangsavviksbrytere - den interne resuppleren gjenoppbygges når blandingshastighetsendringene endres. Vertsappen må erklære NSAudioCaptureUsageDescription. Hvis opptaksgodkjennelsen blir nektet, kan det fortsatt opprettes et trykk ennå bare levere stillhet: se framesCaptured vokse mens nonSilentFrames blir 0 og mislykkes stengt.

Tidsstemplet uavhengige kilder

Begge fange klasser eksponerer tidsstemplede overbelastninger. CapturedAudioChunk bærer mono samples på sampleRate pluss en valgfri Mach hostTime for den første inngangsrammen. Fordi begge kildene bruker samme vertsklokke, kan en app holde PCM-separerte og bestille avledede transkripsjonshendelser uten å blande lyden.

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)
}

For å lytte-bare fange, enableAEC: true aktiverer Apple Voice Processing før mikrofonformatet leses, og enablePlayback: false om den ubrukte spilleren. På macOS fjernes enhetens avspilling fra mikrofonprøver og manglende oppstart av AEC rapporteres i stedet for å falle tilbake til rå inndata.

Økt filinnmating og gjenkjennelse

CapturedAudioChunk bærer også en monoton frameIndex og en isFinal marker. AudioFileLoader.stream produserer disse bitene etterspørselen med avgrenset minne, omdemper en gang med vedvarende konverteringstilstand, og i gjennomsnitt alle inngangskanaler som standard. Bruk .first eller .select([indices]) når kanalruten er kjent.

StreamingRecognitionModel og StreamingRecognitionSession gi én cache-bevarende inkremental API på tvers av Parakeet Strømming og 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

Delt protobuf-leser for SentencePiece .model-filer, bor i AudioCommon. Hver modul som trenger å dekode Sentence Piece-stykker (PersonaPlex, OmnilingualASR, fremtids ASR / TTS-porter) bygger sin egen dekoder på toppen av denne enkeltleseren i stedet for å implementere protobuft-trådformatet.

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
}

Brukt av: OmnilingualASR.OmnilingualVocabulary, PersonaPlex.SentencePieceDecoder. Dekkert av 7 enhetsprøver i Tests/AudioCommonTests/SentencePieceModelTests.

MLXCommon.SDPA

Skalert dot-produkt oppmerksomhetshjelpere delt på alle MLX oppmerksomhetsmodul (Qwen3-ASR / Qwen3-TTS / Qwen3-Chat / CosyVoice / PersonaPlex / OmnilingualASR. Hver modul holder sine egne projeksjoner - SDPA håndterer bare reshape → oppmerksomhet → flett kjeleplate.

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-samtaler bruker -1 for batchdimensjonen slik at hjelperne komponerer med MLX.compile(shapeless:) grafer som varierer batch ved kjøringstid (f.eks. Qwen3-TTS Talker autoregressiv dekode).

HTTP API Server

speech-server binær eksponerer hver modell i speech-swift som HTTP REST endepunkter pluss en WebSocket endepunkt som implementerer OpenAI Realtime API. Modeller lastes lazily på første forespørsel; pass --preload for å varme dem alle ved oppstart.

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

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

REST Endepunkter

Endepunkt MetodeForespørsel Svar
/transcribePOSTaudio/wav kroppJSON { text } (Qwen3-ASR)
/v1/audio/transcriptionsPOSTMultipart { file, model, response_format?, language?, prompt?, temperature? }JSON, tekst, ekstra JSON eller VTT
/v1/audio/speechPOSTJSON { model, input, voice, response_format?, speed? }audio/wav eller audio/pcm kropp
/speakPOSTJSON { text, engine?, language?, voice? }audio/wav kropp (Qwen3-TTS, CosyVoice og Kokoro)
/respondPOSTaudio/wav kroppaudio/wav kropp (PersonaPlex)
/enhancePOSTaudio/wav kroppaudio/wav kropp (DeepFilterNet3)
/vadPOSTaudio/wav kroppJSON segmentliste
/diarizePOSTaudio/wav kroppJSON DiarizedSegment-liste
/embed-speakerPOSTaudio/wav kroppJSON [Float] (256-dim)

/v1/audio/speech kart standard TTS modell og talenavn til lokale motorer. Det standard til WAV; response_format: "pcm" returnerer headerless 24 kHz mono PCM16 lite-endisk lyd. Ustøttede komprimerte formater blir eksplisitt avvist.

/v1/audio/transcriptions aksepterer en WAV-fil som multiparts skjemadata og standard til {"text":"..."}; response_format kan også be om tekst, ekstra JSON, SRT eller VTT. På Apple velger modell alias en lokal ASR motor. Den pakkede speech-core server på Linux og Windows bruker lazily lastet Parakeet, godtar PCM16/24/32 eller Float32 WAV, og caps uploads på 25 MiB og 10 Minutt.

# 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)

WebSocket endepunkt ved ws://host:port/v1/realtime implementerer OpenAI Realtime protokoll. Alle meldinger er JSON med en type diskriminator; lyd nyttelast er base64-encoded PCM16 ved 24 kHz mono.

Under kalde modeller belastninger eller lange generasjoner, sender serveren ut lette realtime.keepalive JSON hendelser og websocket pong kontrollrammer omtrent hvert 15. sekund til modellen utdata er klar. Kunder kan ignorere disse hendelsene eller behandle dem som aktivitetsindikatorer.

Kunde → Servere hendelser

Begivenhet Formål
session.updateKonfigurer motor, språk, stemme, lydformat og valgfri server VAD
input_audio_buffer.append Legg til en base64 PCM16 bit til inngangsbufferen
input_audio_buffer.commit Legg til den bufferte lyden for transkripsjon
input_audio_buffer.clearForkast den gjeldende inndatabufferen
response.createForespørsel TTS syntese for den medfølgende teksten/instrumentene

Server → Kundehendelser

BegivenhetBetydning
session.createdHandshake komplett, standardoppsett sendt ut
session.updated Siste session.update anerkjent
input_audio_buffer.speech_startedServer VAD oppdaget begynnelsen på en taletur
input_audio_buffer.speech_stoppedServer VAD avsluttet turen etter stillhet eller varighetsgrensen
input_audio_buffer.committed Lyd akseptert og køet for transkripsjon
conversation.item.input_audio_transcription.completedASR resultat med endelig transkripsjonstekst
response.audio.deltaBase64 PCM16 bit av syntetisert lyd
response.audio.done Ingen flere lydbiter for dette svaret
response.done Response finalized (metadata + latensstatistikk)
error Feil konvolutt med type og message

Automatiske svinger med serveren VAD

Eksplisitt input_audio_buffer.commit forblir standard. For å la serveren lukke og transcribe svinger automatisk med Silero VAD, konfigurer turn_detection. Serveren holder bare den etterspurte forhåndsrullen mens den er inaktiv, sender talestart/stop og forplikte hendelser i rekkefølge, og tvinge-lukker en kontinuerlig aktiv sving på 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

Serveren lever i AudioServer SPM-produktet. En eksempelleserklient sendes på Examples/websocket-client.html - åpner den sammen med en kjørende server for å kjøre hele ASR + TTS rundtur.

Modellnedlastninger

Alle modeller lastes ned fra HuggingFace ved første bruk og cached i ~/Library/Caches/qwen3-speech/. AudioCommon-modulen tilbyr en delt HuggingFaceDownloader som håndterer nedlasting, cacheing og verifisering av integritet.

Last ned retries

Modelllastere og laste ned APIs aksepterer downloadOptions: DownloadOptions. onRetry mottar en DownloadRetryEvent Før hvert backoff, med modelId, attempt, maxAttempts, den opprinnelige error, og retryInSeconds. nil betyr at ingen retries forblir; full-cache tilbakefall kan fortsatt lykkes.

Tilbaketrekk er synkrone og @Sendable, uten hovedaktørgaranti. Avbestilling produserer ingen gjenprøve hendelse. Sett retryDelaysSeconds for å tilpasse policyen ([] betyr ett forsøk); eksplisitt nedlasting API argumentet har forrang. Standard forsøkstal og byteframgang er bevart. Feil forsøk er også logget på varslingsnivå gjennom AudioLog.download.

Samarbeidsavbestilling

For MLX transkripsjon, Qwen3ASRModel.transcribeCheckingCancellation(audio:sampleRate:options:) kaster CancellationError når oppgaveavbestilling observeres før utvinning av funksjon, koding, dekoder forhåndsfylling eller et dekodingstrinn. GPU arbeider allerede sendt kan avsluttes. Synkronen transcribe og transcribeBatch APIs beholder sin ikke-strekkende oppførsel og ignorerer oppgaveavbelysning. speech-server bruker avbestillingsvare-oppføringspunktet for Qwen3-ASR.

MOSS-TTS-Nano

MossTTSNanoModel serierer forespørsler og skaper friske CoreML tilstand for hver uttalelse. syntetisere og syntetisereStream konservering stereo; SpeechGenerationModel gir en mono downmix. Pass språk: nil. Strømmer slutter med en tom sluttmarkering.

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

Lokalt innfødt inferens

For 12B, gjentatte tilstøtende verktøy-kall stenge markører umiddelbart etter en fullstendig, validert samtale er forbrukt med det anropet. De oppretter ikke et annet anrop eller endrer argumentene. Standalone markører og feilaktige argument forblir feil; E4B beholder streng håndtering.

For å beholde feil innfødte chatforespørsler, sett failureCaptureDirectory i inferien manifest. Fange er av som standard. Katalogen må nekte gruppe og annen tilgang; nye kataloger bruker 0700 og feilfiler bruker 0600. Filene inkluderer nøyaktige forespørselsorgan, be om ID og feil. Fullførte 12B generasjoner beholder også den gjengitte spørrings-, token-ID-er, rå utgangs- og stoppgrunn før tolking. E4B feil har ingen rågenerasjonspost. Disse lokale filene inneholder konversasjon og verktøyinnhold; HTTP-feil og vellykket forespørsler inkluderer dem ikke.

Gemma4UnifiedChat.generateResponse aksepterer en valgfri onGeneration tilbakekalling med Gemma4GenerationTrace. Tilbakekallingen kjører før verktøytolking, inkludert når den ferdigstillelsen blir avvist. Opptak endrer ikke prøvetaking eller akseptert verktøysyntaks.

Valgfritt agentDirectory laster en scened Gemma 4 12B-samlede INT4 tekstmodell ved siden av E4B. Bruk modell: .gemma4-12b-itFor chat med innfødte verktøy, resultathistorie og kansellering. E4B Forblir tilgjengelig for andre forespørsler. Gemma4UnifiedChat bruk av festet MLX tekst implementasjon og utgivelser hver forespørsels KV cache. Response parsing fjerner kanalsifferanser fra returnert tekst. Orphanede verktøy-kall terminatorer avvises som feilaktige svar. For Gemma 4 12B, verktøy-melding innhold bevarer JSON som tekst, samsvarer med checkpoint chat malen.

Kildebygg aksepterer --inference-manifest modeller.json å laste iscenelagt Gemma 4 E4B INT4, VoxCPM2, full Whisper Large-v3, og Silero VAD. Denne tjenesten starter offline og avviser ikke-kompatibel modellidentiteter.

VoxCPM2 støtter vektpresisjon: .float16 for et ukvantisert kontrollpunkt. Hovedparametrene bruker FP16; AudioVAE og noen beregninger forblir FP32. INT8 kontrollpunkter blir avvist for denne modusen.

Gemma4ConversationMessage, Gemma4Tool, og generer Response bevarer innfødte verktøyerklæringer, argumenter og resultathistorie. InferanseAnnulleringToken avbryter generasjonen samarbeidsmessig. Silero gjørUavhengigStream isolasjoner tilstand, og prosessChunkThrowing utbreder inferensfeil.

Whisper ASRModel.fraDirectory lasts iscenesatt full Large-v3 CoreML pakker. 32-lags dekoderen kan bygge sitt prefiks uten TextDecoderContextPrefill.mlmodelc og frigjør per trinn temporære. Standard Whisper CLI-modellen forblir Turbo.

Den opprinnelige tjenesten serialiserer chat, syntese og transkripsjon; lett VAD har separat stream-tilstand. Gemma forhåndsfyll bruker 256-token-partier, ber KV-tilstand frigis, og MLX cache er cache cache caped på 128 MiB med en 20 GiB allocator grense.

Tal med stream: ekte returnerer NDJSON-lydhendelser ved 48 kHz, etterfulgt av gjort eller feil. Avbryt ved forespørselID på /v1/inferens/cancel. GET /v1/inference/capabilities og /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

Valgfritt modelleierskap

SpeechRuntime gir delt lasting, kancellerbar FIFO-opptak og seriealisert utførelse. Qwen3ASRRuntime velger MLX eller full CoreML gjennom eksplisitte deskriptorer; innfødte API-er og CLI-flagg forblir tilgjengelige.

Lydserveren har én utføringstillatelse gjennom hver fullstendige forespørsel, inkludert streamingutgang, med opptil 32 forespørsler som venter. ASR feil produserer HTTP-feil eller WebSocket feilhenvisninger; gyldige tomme transkripsjoner forblir vellykket. Avspilling bevarer overflytprøver. Cachede modeller og avventende lyd fortsatt okkupasjonsminne.

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 kjører lokalt på Apple Silicon gjennom Voxtral TTS-modulen: 20 forhåndsinnstilte stemmer, ni språk og 24 kHz mono tale.

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)
}

Bare forhåndsinnstillinger; referanse-audio kloning er utilgjengelig. Lydutgangsstrømmer gjennom SDK, CLI, WebSocket og HTTP PCM. Gi den komplette inngangsteksten opp foran. Modellvekter bruker CC BY-NC 4.0.

import AudioCommon

let player = StreamingAudioPlayer()
player.skipInitialSilence = false

Les Voxtral TTS guide