Traducere automată · Originalul în engleză

API și protocoale

Nativ Apple runtime · PocketTTS

PocketTTS adaugă sinteze native CoreML sinteze de vorbire la speech-swift. Toate etapele sunt implicite pentru executarea exclusivă a procesorului. Fluxurile de prefixe engleze Alba sunt 1,920-frame de probe la 24 kHz (80 ms per cadru).

Swift și utilizarea CLI

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

Modulul AudioCommon definește protocoalele de model-agnostic și tipurile partajate. Orice model de conformitate poate fi utilizat interschimbabil prin intermediul acestor interfețe.

Prezentare generală a protocolului

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

Mod de vorbireRecunoaștere

Protocolul pentru modelele de vorbire în text.

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
}

Tipuri de formulare: Qwen3ASRModel, WhisperASRModel, ParakeetASRModel, ParakeetStreamingASRModel, OmnilingualASRModel (CoreML), OmnilingualASRMLXModel (MLX)

Mod de vorbire

Protocolul pentru modelele de text-la-discurs.

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() are o implementare implicită care se încheie generate() ca o singură bucată. Modele cu streaming adevărat (de exemplu, Qwen3-TTS) suprascrie.

Tipuri de formulare: Qwen3TTSModel, CosyVoiceTTSModel, VoxCPM2TTSModel, KokoroTTSModel, IndexTTS2TTSModel

IndexTTS2TTSModel adaugă o referință-audio generate supraîncărcare pentru clonarea vocală cu foc zero și folosește IndexTTS2SynthesisOptions pentru controlul vitezei de vorbire și de pauză.

Qwen3-TTS încărcare locală

fromLocal validează fișierele necesare și caracterul complet al punctului de control, apoi derivă dimensiunea modelului, cuantizarea și arhitectura de la config.json. Valoarea opțională configuration este doar o rezervă pentru metadate omise; metadate malefice sau contradictorii aruncă Qwen3TTSLoadingError.invalidConfiguration înainte de alocarea modelului.

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
Politica în domeniul memoriei

fromLocal implicits to .none, astfel încât nu schimbă procesul la nivel Metal wired-memory limit. fromPretrained păstrează .pin(fraction: 0.9) implicit.Ambii încărcători precalificați acceptă un separat tokenizerCacheDir.

Mod de aliniere forţată

Protocolul pentru alinierea la nivelul de timp a marcajelor de caractere.

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

DiscursPentru DiscursModel

Protocolul pentru modelele de dialog între vorbire și vorbire.

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

Tipuri de formulare: PersonaPlexModel

HibikiTraduce Model

Translator de vorbire-la-discurs. SpeechToSpeechModel deoarece contractul este diferit: intrarea și producția sunt limbi diferite, lungimea de ieșire este variabilă (orientată EOS, plafonată la max(tSrc * 5/2, tSrc + 20) pași 1.0–1.6× durata sursei pe clipuri de tip FLEURS), și există un flux paralel text-token alături de audio. Ghid complet →

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
}

Rata maximă: 24 kHz mono, atât de intrare, cât și de ieșire. Rată de bază: 12,5 Hz (Mimi codec). Limbi sursă: FR / ES / PT / DE. Limbă de țintă: EN. Set HIBIKI_GREEDY=1 la lansarea procesului pentru decodarea argmax deterministic (matches the CI canaries).

Modul de activare vocală

Protocol pentru detectarea activității vocale.

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

Completare de rotire

Protocolul pentru clasatorii finali consultați de StreamingVADProcessor pe fiecare pauza VAD confirmată. A VAD aude doar tăcere; un model de turn-completare ascultă prosodia întregii declaraţii, astfel încât o pauză de sentinţă medie îl face pe agent să aştepte în timp ce o propoziţie terminată primeşte un răspuns imediat. Hărţile către speech-core sc_turn_completion_vtable_t Vezi... Ghid Smart Turn.

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
}

Tipuri de formulare: SmartTurnModel (Smart Turn v3.2)

VorbitorEmbeddingModel

Protocolul pentru introducerea de către vorbitor a extracției.

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

Tipuri de formulare: WeSpeakerModel

Mod de diagramă

Protocol pentru modele de diarizare difuzor care atribuie etichete difuzor segmente audio.

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

Tipuri de formulare: DiarizationPipeline (Pyannote), SortformerDiarizer

Extragere difuzorCapabil

Protocolul extins de diarizare pentru motoare care suportă extragerea segmentelor unui vorbitor țintă folosind o înglobare de referință. Nu toate motoarele susțin acest lucru (Sortformer este de la un capăt la altul și nu produce înglobări difuzor).

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

Tipuri de formulare: DiarizationPipeline (Pyannote numai)

Tipuri partajate

AudioChunk

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

Segment de vorbire

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

Cuvântul aliniat

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
}

Segment diarizat

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

Un segment parsat de dialog multi-speaker text cu difuzor opţional şi etichete emoţionale. Utilizat cu DialogueParser şi DialogueSynthesizer pentru CosyVoice3 sinteza dialogului.

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

Parses multi-speaker dialog text cu tag-uri inline speaker ([S1]) și etichete emoționale ((happy)).

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

Emoţii construite: happy/excited, sad, angry, whispers/whispering, laughs/laughing, calm, surprised, serious Etichetele necunoscute trec prin ca instrucțiuni de formă liberă.

DialogSintezator

Orchestrates multi-segment dialog sinteză cu clonarea vocii per-speaker, goluri de tăcere, și crossfade.

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

DialogSintezăConfig

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
}

Pipellm

Protocolul pentru integrarea modelului lingvistic cu conductele de voce. Poduri o LLM la VoicePipeline's ASR → LLM → TTS Curge.

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

Adaptor de alimentare: Qwen3PipelineLLM poduri Qwen35MLXChat la acest protocol cu curăţare jeton, anulare şi acumulare de fraze în aşteptare.

AudioIO

Managerul audio reutilizabil I/O care elimină cazaniera AVAudioEngine. Manipulează captura mic, resampling, redare, și contorizarea nivelului audio.

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

AudioIO include un StreamingAudioPlayer pentru o ieșire TTS și un AudioRingBuffer pentru un transfer audio securizat între fire de captare și de interferență.

SystemAudioTap

Captureaza mixul de iesire al sistemului, care este Mac care joaca in prezent ca mono Float32 la o rata de esantionare calumer-chosen (Core Audio procesting robinets, macOS 14.4+). Doar robinetul mono global exclude procesul curent implicit, astfel încât o aplicație propria redare nu este niciodată re-capturată, și este învelit într-un dispozitiv agregat privat care conține doar robinetul.

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

Captura supravieţuieşte switch-urilor de ieşire implicită NSAudioCaptureUsageDescription. În cazul în care permisiunea de înregistrare este respinsă, un robinet poate fi creat încă livra doar tăcere: ceas framesCaptured cresc în timp ce nonSilentFrames rămâne 0 și nu reușesc închis.

Surse independente cu timbru temporizat

Ambele clase de captare expun suprasarcini cu timbru temporal. CapturedAudioChunk poartă mono samples la sampleRate plus un Mach opțional hostTime pentru primul cadru de intrare. Deoarece ambele surse folosesc același ceas gazdă, o aplicație poate păstra evenimentele de transcriere derivate din PCM separate și ordine fără a amesteca audio.

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

Pentru captarea numai de ascultare, enableAEC: true activează Apple Voice Processing înainte ca formatul microfonului să fie citit, și enablePlayback: false omite jucătorul neutilizat. Pe macOS, redarea dispozitivului este eliminată din mostrele microfonului și se raportează eșecul de pornire a AEC în loc să se retragă la intrare brută.

Intrarea și recunoașterea inițială a fișierelor

CapturedAudioChunk are, de asemenea, un monoton frameIndex și un marker isFinal. AudioFileLoader.stream produce aceste bucăți la cerere cu memorie delimitată, reeșalonări o dată cu starea persistentă a convertorului și media tuturor canalelor de intrare în mod implicit.Use .first sau .select([indices]) atunci când ruta canalului este cunoscută.

StreamingRecognitionModel și StreamingRecognitionSession provide o cache-conserving incremental API through Parakeet Streaming and 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)
}

SentincePieceModel

Cititor de protobuf partajat pentru SentincePiece .model fișiere, trăiește în AudioCommon. Fiecare modul care trebuie să decodeze piese de SentencePiece (PersonaPlex, OmnilingualASR, viitor ASR / TTS porturi) își construiește propriul decodor pe partea de sus a acestui singur cititor în loc să reimplementeze formatul de sârmă protobuf.

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
}

Utilizat de: OmnilingualASR.OmnilingualVocabulary, PersonaPlex.SentencePieceDecoder. Acoperit de 7 teste unitare în Tests/AudioCommonTests/SentencePieceModelTests.

MLXCommon.SDPA

Atentie punct-produs escaladat ajutor partajat pe fiecare modul de atenţie MLX (Qwen3-ASR / Qwen3-TTS/Qwen3-Chat // CosyVoice &amp; PersonaPlex - OmnilingualASR). Fiecare modul păstrează propriile proiecţii

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
}

Toate apelurile de remodelare utilizează -1 pentru dimensiunea lotului, astfel încât ajutoarele să compună cu MLX.compile(shapeless:) grafice care să varieze lotul la rulare (de exemplu Qwen3-TTS Talker decodare autoregresivă).

HTTP API Server

ă speech-server binara expune fiecare model din speech-swift sub forma unor criterii finale HTTP REST plus un criteriu final WebSocket care pune în aplicare OpenAI Realtime API. Modelele sunt încărcate lene la prima cerere; trece --preload să le încălzească pe toate la startup.

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

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

REST Puncte finale

Punct final Metoda Cerere Răspuns
/transcribePOSTaudio/wav organismJSON { text } (Qwen3-ASR)
/v1/audio/transcriptionsPOST Multipart { file, model, response_format?, language?, prompt?, temperature? }JSON, text, verbose JSON , SRT sau VTT
/v1/audio/speechPOSTJSON { model, input, voice, response_format?, speed? }audio/wav sau audio/pcm organism
/speakPOSTJSON { text, engine?, language?, voice? }audio/wav corp (Qwen3-TTS, CosyVoice și Kokoro)
/respondPOSTaudio/wav organismaudio/wav organism (PersonaPlex)
/enhancePOSTaudio/wav organismaudio/wav corp (DeepFilterNet3)
/vadPOSTaudio/wav organism Lista segmentului JSON
/diarizePOSTaudio/wav organismJSON DiarizedSegment list
/embed-speakerPOSTaudio/wav organismJSON [Float] (256-dim)

/v1/audio/speech hărţi standard TTS modele şi denumiri vocale pentru motoarele locale. Implică la WAV; response_format: "pcm" întoarce fără antet 24 kHz mono PCM16 audio mic-endian. Formatele comprimate nesusţinute sunt respinse în mod explicit.

/v1/audio/transcriptions acceptă un fișier WAV ca date multipart sub formă și implicit la {"text":"..."}; response_format poate solicita, de asemenea, text, verbose JSON, SRT, sau VTT. Pe Apple, modele pseudonime selectați un local ASR Motorul. speech-core server pe server Linux şi Windows foloseste lenes încărcat Parakeet, acceptă PCM16/24/32 sau Float32 WAV, și capace de încărcare la 25 MiB și 10 minute.

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

Obiectivul WebSocket la ws://host:port/v1/realtime pune în aplicare protocolul OpenAI Realtime. Toate mesajele sunt JSON cu un discriminatoriu type; încărcăturile utile audio sunt base64-encoded PCM16 la 24 kHz mono.

In timpul incarcarilor de modele reci sau generatii lungi, serverul emite usor realtime.keepalive Evenimente JSON si rame de control pong websocket aproximativ la fiecare 15 secunde pana cand modelul de iesire este gata. Clientii pot ignora aceste evenimente sau le trateaza ca indicatori de activitate.

Client → Evenimente server

EvenimentScop
session.updateConfigurează motorul, limba, vocea, formatul audio și serverul opțional VAD
input_audio_buffer.appendAdăugați o bucată base64 PCM16 la tamponul de intrare
input_audio_buffer.commitComite audio tamponat pentru transcriere
input_audio_buffer.clear Aruncați tamponul de intrare curent
response.create Cerere TTS sinteză pentru textul/instrucțiunile furnizate

Server → Evenimente client

Eveniment Semnificație
session.created Strângere de mână completă, configurare implicită emisă
session.updated Cele mai recente session.update recunoscute
input_audio_buffer.speech_startedServer VAD detectat începutul unui turn de vorbire
input_audio_buffer.speech_stoppedServer VAD a încheiat rotirea după tăcere sau limita de durată
input_audio_buffer.committedAudio acceptat și la coadă pentru transcriere
conversation.item.input_audio_transcription.completedASR rezultatul cu textul transcrierii finale
response.audio.deltaBase64 PCM16 bucată de audio sintetizat
response.audio.doneNu mai multe bucăți audio pentru acest răspuns
response.done Răspuns finalizat (metadate + statistici privind latența)
errorAnvelopă de eroare cu type și message

Rânduri automate cu server VAD

Explicit input_audio_buffer.commit rămâne implicit. Pentru a lăsa serverul să se închidă și transcrie automat cu Silero VAD, configura turn_detection. Serverul păstrează doar pre-roll-ul solicitat în timp ce inactiv, emite start/stop și comit evenimente în ordine, și se închide continuu activ la 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

Serverul locuiește în produsul AudioServer SPM. Un client de tip browser de exemplu este expediat la Examples/websocket-client.html deschideți-l alături de un server care rulează pentru a conduce întreaga călătorie ASR + TTS rotundă.

Modele de descărcări

Toate modelele sunt descărcate de la HuggingFace la prima utilizare și cache în ~/Library/Caches/qwen3-speech/. Modulul AudioCommon oferă un partaj HuggingFaceDownloader care se ocupă de descărcare, caching și verificarea integrității.

Descărcați retries

Modele de incarcatoare si descarca API accepta downloadOptions: DownloadOptions. onRetry a primit DownloadRetryEvent înainte de fiecare retragere, cu modelId, attempt, maxAttempts, originalul error, și retryInSeconds. nil înseamnă că nu mai rămân retensiuni; retragerea completă a cache-ului poate reuși.

Retragerile sunt sincrone și @Sendable, fără o garanție principală. Anularea nu produce un eveniment de retrie. Set retryDelaysSeconds pentru a personaliza politica ([] înseamnă o singură încercare; descărcarea explicită API argument are prioritate. Se păstrează numărul de încercări implicite și progresele octet. Încercările eșuate sunt înregistrate și la nivel de notificare prin AudioLog.download.

Anulare cooperativă

Pentru MLX transcription, Qwen3ASRModel.transcribeCheckingCancellation(audio:sampleRate:options:) aruncări CancellationError atunci când anularea sarcinii este observată înainte de extragerea caracteristicilor, codificarea, prefillarea decodorului sau o etapă de decodare. GPU munca depusă deja poate fi terminată. Cronhous transcribe și transcribeBatch API își păstrează comportamentul de non-ascensiune și ignoră anularea sarcinilor. speech-server folosește punctul de intrare pentru anulare-aware pentru Qwen3-ASR.

MOSS-TTS-Nano

MossTTSNanoModel serializeaza cererile si creeaza stare proaspata CoreML pentru fiecare declaratie. sintetizeaza si sintetizeazaStream pastrare stereo; SpeechGenerationModel returneaza un monodownmix. Pass language: nil. Streams se termina cu un marker final gol.

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

Inference locală nativă

Pentru 12B, markerii de închidere a apelului de instrument adiacenti repetati imediat dupa un apel complet, validat sunt consumati cu acel apel. Ei nu creeaza un alt apel sau isi schimba argumentele. Markerii si argumentele malformate raman erori; E4B retine manipularea stricta.

Pentru a păstra cereri de chat native eșuate, set failureCaptureDirectory în manifestul de inferență. Capture este oprit în mod implicit. Directorul trebuie să refuze grupul și alte acces; noi directoare folosesc 0700 și utilizarea de fișiere de eșec 0600. Fișierele includ organismul de cerere exactă, ID-ul cererii și eroarea. Generațiile de 12B finalizate păstrează, de asemenea, prompte, ID-uri token, producția brută și motiv de oprire înainte de parsare. E4B eșecurile nu au înregistrări de generație brută. Aceste fișiere locale conțin conținut conversație și instrumente; erorile HTTP și cererile de succes nu le includ.

Gemma4UnifiedChat.generateResponse acceptă un apel opțional onGeneration cu un Gemma4GenerationTrace. Apelul se execută înainte de parcurgerea uneltelor, inclusiv atunci când această finalizare este respinsă. Înregistrarea nu modifică prelevarea de probe sau sintaxa de instrumente acceptată.

Agent opţionalDirector încarcă o etapă Gemma 4 12B Unificat INT4 model text alături E4B. Utilizați modelul: "gemma4-12b-it" pentru chat cu instrumente native, istorie rezultat, și anularea. E4B rămâne disponibilă pentru alte cereri. Gemma4UnifiedChat Foloseşte dispozitivul fixat MLX Implementarea textului și lansează fiecare cerere . Răspunsul depărtează delimitatorii canalului de text returnat. Terminatorii de instrumente orfane sunt respinse ca răspunsuri malformate. Gemma 4 12B, conținutul de mesaje de instrumente păstrează JSON ca text, potrivind șablonul de chat punct de control.

Sursa construiește acceptă --inference-manifest modele.json pentru a încărca înscenat Gemma 4 E4B INT4, VoxCPM2, plin Whisper Large-v3, și Silero VAD. Acest serviciu începe offline și respinge identitățile de model incompatibile.

VoxCPM2 suportă greutateaPrecizie: .float16 pentru un punct de control nequantizat. Pentru acest mod, parametrii principali utilizați FP16; AudioVAE și unele calcule rămân FP32. INT8 punctele de verificare sunt respinse.

Gemma4ConversationMessage, Gemma4Tool şi genereazăResponsa păstrează declaraţii de instrumente native, argumente şi istoric de rezultat.InferenceCancellationToken anulează generaţia cooperativ. Silero makeIndependentStramul izolatelor de stare, şi procesChunkTrowing propagează erori de inferenţă.

Whisper ASRModel.de la Decodorul 32-straturi poate construi prefixul său complet Large-v3 CoreML fără textDecodorContextPrefill.mlmodelc și eliberează temporarii pe trepte.Modelul implicit Whisper Modelul CLI rămâne Turbo.

Serviciul nativ serializează chat-ul, sinteza și transcrierea; ușor VAD are o stare de flux separată. Gemma prefill folosește 256-token loturi, cerere KV stat este eliberat, iar cache-ul MLX este limitat la 128 MiB cu o limită de 20 GiB allocator.

Discursul cu flux: adevărat întoarce NDJSON evenimente audio la 48 kHz, urmat de făcut sau eroare. Anulați prin cerereID la /v1/inferință/cancel. GET /v1/inference/capabilități și /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

Proprietate opțională a modelului

SpeechRuntime oferă încărcare comună, admitere FIFO canelabilă și execuție serializată. Qwen3ASRRuntime selectează MLX sau complet CoreML prin descriptori expliciti; API-urile native și steagurile CLI rămân disponibile.

Serverul audio deţine un permis de execuţie prin fiecare cerere completă, inclusiv fluxul de ieşire, cu până la 32 de cereri de aşteptare. ASR eşecuri produc erori HTTP sau WebSocket evenimente de eroare; transcrieri goale valabile rămân de succes. Playback păstrează mostrele de supraîncărcare. Modele cache şi audio în curs de redare ocupa încă memorie.

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 ruleaza local pe Apple Silicon prin modulul VotralTTSS: 20 voci prestabilite, nouă limbi, și 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)
}

Doar vocile presetate; clonarea de referință-audio nu este disponibilă. Fluxuri de ieșire audio prin SDK, CLI, WebSocket și HTTP PCM. Furnizarea textului complet de intrare în față. Greutate model de utilizare CC BY-NC 4.0.

import AudioCommon

let player = StreamingAudioPlayer()
player.skipInitialSilence = false

Citeşte ghidul Voxtral TTS