Terjemahan mesin · Versi asli bahasa Inggris

API dan protokol

Asli Apple waktu jalan

PocketTTS menambahkan asli CoreML speech synthesis to speech-swift. Semua tahap baku ke CPU hanya eksekusi. Aliran Alba Inggris preset 1,920-sampel frame di 24 kHz (80 ms per frame).

Penggunaan Swift dan 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)
}

The AudioCommon modul mendefinisikan protokol model-agnostik dan tipe bersama. Model konforman apapun dapat digunakan secara bergantian melalui antarmuka ini.

Ringkasan Protokol

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

Model Speech RecognitionModel

Protokol untuk cepat-to- model teks.

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
}

Tipe konformasi: Qwen3ASRModel, WhisperASRModel, ParakeetASRModel, ParakeetStreamingASRModel, OmnilingualASRModel (CoreML), OmnilingualASRMLXModel (MLX)

Model Generasi Pidato

Protokol untuk teks-to- model bicara.

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() memiliki implementasi baku yang membungkus generate() sebagai sebuah potongan tunggal. Model dengan streaming benar (mis. Qwen3-TTS) override itu.

Tipe konformasi: Qwen3TTSModel, CosyVoiceTTSModel, VoxCPM2TTSModel, KokoroTTSModel, IndexTTS2TTSModel

IndexTTS2TTSModel menambah reference- audio generate overload untuk zero- shot suara kloning dan menggunakan IndexTTS2SynthesisOptions untuk berbicara-rate dan kontrol jeda.

Qwen3-TTS local loading

fromLocal validate berkas yang diperlukan dan checkpoint shard komplement, kemudian turunan model ukuran, kuantisasi, dan arsitektur dari config.json. The opsional configuration nilai hanya sebuah fallback untuk metadata diabaikan; salah bentuk atau metadata yang bertentangan melempar Qwen3TTSLoadingError.invalidConfiguration sebelum alokasi model.

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
Kebijakan memori

fromLocal default ke .none, sehingga tidak mengubah proses-lebar Metal batas memori. fromPretrained menyimpan nya .pin(fraction: 0.9) baku. Kedua pelanggar yang terlatih menerima terpisah tokenizerCacheDir.

ForcedAlignMentment Model

Protokol untuk keselarasan waktu tingkat kata-waktu.

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

SpeechToSpeechModel

Protokol untuk cepat - ke - dialog pidato model.

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

Tipe konformasi: PersonaPlexModel

HibikiTranslator Model

Penggerak terjemahan pidato. Tidak sesuai dengan SpeechToSpeechModel karena kontrak berbeda: masukan dan keluaran adalah bahasa yang berbeda, panjang keluaran adalah variabel (EOS- didorong, dicapped di max(tSrc * 5/2, tSrc + 20) langkah - kira-kira 1.0–1.6Set durasi sumber pada klip gaya FLEURS), dan ada teks paralel -token stream di samping audio. Pemandu penuh.

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
}

Laju sampel: 24 kHz mono, baik masukan dan keluaran. Laju bingkai: 12.5 Hz (Mimi codec). Bahasa sumber: FR / ES / PT / DE. Bahasa target: Siap. HIBIKI_GREEDY=1 pada peluncuran proses untuk deterministik argmax decoding (cocok dengan canary CI).

Model Deteksi Aktif Suara

Protokol untuk deteksi aktivitas suara.

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

Penyedia Penyelesai Pemulihan

Protokol untuk penomoran end-of-turn yang berkonsultasi dengan StreamingVADProcessor pada setiap dikonfirmasi VAD Jeda. VAD hanya mendengar keheningan; sebuah model turn-completion mendengarkan seluruh ucapan tersebut, sehingga jeda tengah kalimat membuat agen menunggu sementara sebuah kalimat selesai mendapat jawaban langsung. Peta ke speech-core's sc_turn_completion_vtable_tLihat Smart Turn panduan.

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
}

Tipe konformasi: SmartTurnModel (Smart Turn v3.2)

SpeakerEmbeddingModel

Protokol untuk speaker embedding ekstraksi.

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

Tipe konformasi: WeSpeakerModel

Model SpeakerDiarizationModel

Protokol untuk model diarisasi pembicara yang menetapkan label speaker ke segmen audio.

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

Tipe konformasi: DiarizationPipeline (Pyannote), SortformerDiarizer

SpeakerExtractionAble

Extended diarization protokol untuk mesin yang mendukung mengekstrak segmen target speaker menggunakan referensi embedding. Tidak semua mesin mendukung ini (Sortformer end-to-end dan tidak menghasilkan penggelapan speaker).

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

Tipe konformasi: DiarizationPipeline (Pyannote saja)

Tipe Bersama

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
}

Kata AlignedWord

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

DialogueSegment

Sebuah segmen parsed dari dialog multi- speaker teks dengan speaker opsional dan tag emosi. Digunakan dengan DialogueParser dan DialogueSynthesizer untuk CosyVoice3 dialog sintesis.

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
}

DialogueParser

Bagian teks dialog multi- speaker dengan tag speaker inline ([S1]) dan tag emosi ((happy)).

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

Bangun-dalam emosi: happy/excited, sad, angry, whispers/whispering, laughs/laughing, calm, surprised, serious. Tag tidak dikenal melewati sebagai instruksi freform.

DialogueSynthesizer

Membiasakan multi- segmen dialog sintesis dengan pengkloning suara perspeaker, kesenjangan keheningan, dan crossfade.

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

DialogueSynthesisConfig

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
}

PipelineLM

Protokol untuk integrasi model bahasa dengan jaringan pipa suara. Jembatan LLM ke VoicePipeline 's ASR → LLM → TTS Mengalir.

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

Built- dalam adaptor: Qwen3PipelineLLM jembatan Qwen35MLXChat untuk protokol ini dengan pembersihan token, pembatalan, dan tambahan frase akumulasi.

AudioIO

Manajer audio I / O yang dapat menghilangkan boilerplate AVAudioEngine. Penangkapan mic genggam, menata ulang, memutar, dan tingkat audio metering.

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

AudioIO termasuk StreamingAudioPlayer untuk TTS keluaran dan sebuah AudioRingBuffer untuk thread-safe transfer audio antara menangkap dan benang inferensi.

SystemAudioTap

Menangkap campuran output sistem - apa yang Mac saat ini sedang diputar - sebagai mono Float32 pada tingkat caller- dipilih sampel (Core Audio proses tap, macOS 14.4+). Penyadapan global mono mengecualikan proses saat ini secara baku, sehingga putar sendiri aplikasi tidak pernah ditangkap kembali, dan dibungkus dalam perangkat agregasi pribadi yang hanya berisi keran.

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

Penangkapan bertahan dari pilihan perangkat default-outputt-perangkat - resampler internal dibangun kembali ketika tingkat campuran berubah. Aplikasi host harus menyatakan NSAudioCaptureUsageDescriptionJika ijin rekaman ditolak, keran masih dapat dibuat namun memberikan hanya keheningan: menonton framesCaptured tumbuh sementara nonSilentFrames tetap 0 dan gagal ditutup.

Sumber independen yang distempel waktu

Kedua kelas penangkapan mengekspos time stapted overloads. CapturedAudioChunk membawa mono samples di sampleRate plus sebuah pilihan Mach hostTime untuk frame masukan pertama. Karena kedua sumber menggunakan jam host yang sama, aplikasi dapat menjaga mereka terpisah dan urutan peristiwa transkrip turunan PCM tanpa mencampur 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)
}

Untuk mendengarkan - hanya menangkap, enableAEC: true mengaktifkan Processing Apple Voice sebelum format mikrofon dibaca, dan enablePlayback: false Menghilangkan pemutar yang tidak terpakai. Pada macOS, pemutaran perangkat dihapus dari sampel mikrofon dan gagal untuk memulai AEC dilaporkan bukan mundur ke masukan mentah.

Incremental file input dan pengakuan

CapturedAudioChunk juga membawa monotonic frameIndex dan isFinal penanda. AudioFileLoader.stream menghasilkan potongan-potongan ini sesuai permintaan dengan memori terbatas, sampel ulang sekali dengan kondisi konverter persisten, dan rata-rata semua saluran masukan secara baku. Gunakan .first atau .select([indices]) ketika saluran routing diketahui.

StreamingRecognitionModel dan StreamingRecognitionSession memberikan satu kaktus - melestarikan incremental API di seluruh Parakeet Streaming dan 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

Pembaca protobuf bersama untuk SentencePiece .model berkas, tinggal di AudioCommon. Setiap modul yang perlu decode potongan SentencePiece (PersonaPlex, OmnilingualASR, masa depan ASR / TTS port) membangun decoder sendiri di atas pembaca tunggal ini bukan mengimplementasikan format kawat 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
}

Digunakan oleh: OmnilingualASR.OmnilingualVocabulary, PersonaPlex.SentencePieceDecoderDicakup oleh tes unit 7 di Tests/AudioCommonTests/SentencePieceModelTests.

MLXCommons.DPA

Skala dot- pembantu perhatian produk dibagi di setiap MLX perhatian modul (Qwen3-ASR / Qwen3-TTS / Qwen3-Chat / CosyVoice / PersonaPlex / OmnilingualASR). Setiap modul menyimpan proyeksinya sendiri - SDPA hanya menangani reshade sneattion Groumerge 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
}

Semua bentuk ulang penggunaan panggilan -1 (Untuk tujuan yang benar) lafal ini dinashabkan oleh Fi 'il yang diperkirakan keberadaannya pada sebelumnya (dan demi yang datang dengan membawa amal saleh) yakni malaikat-malaikat yang mencabut MLX.compile(shapeless:) grafik yang bervariasi batch at runtime (mis. Qwen3-TTS Talker autoregresif decode).

Server HTTP API

The speech-server biner mengekspos setiap model dalam speech-swift sebagai HTTP REST titik akhir ditambah sebuah WebSocket titik akhir yang mengimplementasi OpenAI Realtime APIModel dimuat malas pada permintaan pertama; lulus --preload untuk menghangatkan mereka semua saat startup.

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

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

REST Endpoints

EndpointMetodePermintaanRespon
/transcribePOSTaudio/wav tubuhJSON { text } (Qwen3-ASR)
/v1/audio/transcriptionsPOSTMultipart { file, model, response_format?, language?, prompt?, temperature? }JSON, text, verbose JSON, SRT, atau VTT
/v1/audio/speechPOSTJSON { model, input, voice, response_format?, speed? }audio/wav atau audio/pcm tubuh
/speakPOSTJSON { text, engine?, language?, voice? }audio/wav tubuh (Qwen3-TTS, CosyVoice, Kokoro)
/respondPOSTaudio/wav tubuhaudio/wav body (PersonaPlex)
/enhancePOSTaudio/wav tubuhaudio/wav body (DeepFilterNet3)
/vadPOSTaudio/wav tubuhDaftar segmen JSON
/diarizePOSTaudio/wav tubuhJSON DiarizedSegment daftar
/embed-speakerPOSTaudio/wav tubuhJSON [Float] (256-dim)

/v1/audio/speech peta standar TTS model dan nama suara ke mesin lokal. Ini default untuk WAV; response_format: "pcm" mengembalikan tanpa kepala 24 kHz mono PCM16 audio little-endian. Format terkompresi tidak didukung ditolak secara eksplisit.

/v1/audio/transcriptions menerima satu berkas WAV sebagai multipart format data dan baku ke {"text":"..."}; response_format dapat juga meminta teks, verbose JSON, SRT, atau VTT. Pada Apple, model alias memilih lokal ASR Mesin. paket speech-core server hidup Linux dan Windows gunakan lazily loaded Parakeet, menerima PCM16/24/32 atau Float32 WAV, dan caps uploads di 25 MiB dan 10 menit.

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

Titik akhir WebSocket di ws://host:port/v1/realtime menerapkan protokol OpenAI Realtime. Semua pesan adalah JSON dengan type diskriminator; audio payloads base64-encoded PCM16 di 24 kHz mono.

Selama beban model dingin atau generasi panjang, server memancarkan ringan realtime.keepalive Peristiwa JSON dan frame kontrol websocket pong kira-kira setiap 15 detik sampai keluaran model siap. Klien dapat mengabaikan peristiwa ini atau memperlakukan mereka sebagai indikator aktivitas.

Peristiwa Server Klien

PeristiwaTujuan
session.updateKonfigurasikan mesin, bahasa, suara, format audio, dan server opsional VAD
input_audio_buffer.appendTambahkan base64 PCM16 chunk ke buffer masukan
input_audio_buffer.commitKomit audio yang dibelokkan untuk transkripsi
input_audio_buffer.clearBuang buffer masukan saat ini
response.createPermintaan sintesis TTS untuk teks / instruksi yang diberikan

Peristiwa Server £Quent

PeristiwaArtinya
session.createdHandshake lengkap, konfigurasi bawaan dikeluarkan
session.updatedTerbaru-baru ini session.update Diakui
input_audio_buffer.speech_startedServer VAD mendeteksi awal dari pergantian pidato
input_audio_buffer.speech_stoppedServer VAD mengakhiri pergantian setelah keheningan atau batas durasi
input_audio_buffer.committedAudio diterima dan mengantri untuk transkripsi
conversation.item.input_audio_transcription.completedHasil ASR dengan teks transkrip akhir
response.audio.deltaBase64 PCM16 potongan audio disintesis
response.audio.doneTidak ada lagi potongan audio untuk tanggapan ini
response.doneRespon terfinalisasi (statistik metadata + latensi)
errorAmplop galat dengan type dan message

Otomatis berputar dengan server VAD

Eksplisit input_audio_buffer.commit tetap baku. Untuk membiarkan server menutup dan transkripsi berubah secara otomatis dengan Silero VAD, konfigurasi turn_detection. Server hanya menyimpan pra-roll yang diminta ketika menganggur, memancarkan pidato mulai / berhenti dan melakukan acara secara berurutan, dan paksa-menutup giliran aktif secara terus menerus pada 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

Server tinggal di AudioServer Produk SPM. Klien peramban contoh dikirim ke Examples/websocket-client.html - membukanya bersama server yang sedang berjalan untuk drive penuh ASR + TTS round trip.

Unduhan Model

Semua model diunduh dari HuggingFace pada penggunaan pertama dan cache di ~/Library/Caches/qwen3-speech/. AudioCommon modul menyediakan berbagi HuggingFaceDownloader yang menangani download, caching, dan verifikasi integritas.

Unduh ulang

Loaders model dan download APIs terima downloadOptions: DownloadOptions. onRetry menerima DownloadRetryEvent sebelum setiap mundur, dengan modelId, attempt, maxAttempts, yang asli error, dan retryInSeconds. nil berarti tidak ada retries tetap; complete- cache fallback masih dapat berhasil.

Callbacks sinkron dan @Sendable, dengan tidak ada jaminan main- actor. Kantaran menghasilkan tidak ada acara coba ulang. Set retryDelaysSeconds untuk menyesuaikan kebijakan ([] berarti satu percobaan); unduhan eksplisit API argumen mengambil presedence. Jumlah percobaan baku dan kemajuan byte diawetkan. Percobaan gagal juga masuk pada tingkat pemberitahuan melalui AudioLog.download.

Kooperatif pembatalan

Untuk transkripsi MLX, Qwen3ASRModel.transcribeCheckingCancellation(audio:sampleRate:options:) throw CancellationError ketika pembatalan tugas diamati sebelum fitur extract, encoding, decoder prefill, atau sebuah langkah decoding. GPU bekerja telah dimasukkan dapat selesai. Sinkronisasi transcribe dan transcribeBatch APIS mempertahankan perilaku non-melempar mereka dan mengabaikan pembatalan tugas. speech-server menggunakan titik masukan yang dapat di batalkan untuk Qwen3-ASR.

MOSS- TTS- Nano

MossTTSNanoModel serializes permintaan dan menciptakan segar CoreML keadaan untuk setiap tingkat. mensintesis dan synthesizeStream melestarikan stereo; SpeechGenerationModel mengembalikan sebuah downmix mono. Pass bahasa: nil. Streams berakhir dengan penanda akhir kosong.

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

Inferensi lokal lokal

Untuk 12B, diulang di samping tool- call penutup penanda segera setelah panggilan lengkap, tervalidasi dikonsumsi dengan panggilan tersebut. Mereka tidak membuat panggilan lain atau mengubah argumen. Standally penanda dan argumen tetap salah tetap error; E4B mempertahankan penanganan ketat.

Untuk mempertahankan permintaan obrolan yang gagal, ditata failureCaptureDirectory dalam daftar muatan. Penangkapan tidak aktif secara baku. Direktori harus menolak grup dan akses lain; direktori baru pakai 0700 dan berkas kegagalan digunakan 0600Berkas termasuk tubuh permintaan yang tepat, permintaan ID dan error. Selesai 12B generasi juga mempertahankan prompt yang dirender, ID token, keluaran mentah dan stop alasan sebelum parsing. Kegagalan E4B tidak memiliki catatan pembuatan mentah. Berkas lokal ini berisi percakapan dan konten alat; Galat HTTP dan permintaan sukses tidak termasuk mereka.

Gemma4UnifiedChat.generateResponse menerima sebuah opsional onGeneration callback dengan Gemma4GenerationTraceCallback dijalankan sebelum parsing alat, termasuk ketika pelengkapan ditolak. Perekam tidak mengubah sampling atau sintaks alat yang diterima.

Direktori agentDirektori opsional memuat suatu ter-stage Gemma 4 12B Disatukan INT4 model teks di samping E4B Gunakan model: "gemma4-12b-it"untuk chatting dengan alat-alat asli, hasil sejarah, dan pembatalan. E4B tetap tersedia untuk permintaan lain. Gemma4UnifiedChat gunakan jepped MLX implementasi teks dan rilis setiap cache KV permintaan. Menanggapi parsing penghapusan saluran dari teks kembali. Orfaned tool- call terminator ditolak sebagai salah bentuk respon. Untuk Gemma 4 12B, isi pesan tool-message mempertahankan JSON sebagai teks, cocok dengan titik pemeriksaan template obrolan.

Sumber membangun menerima --inference-manifest model. json untuk dipentaskan Gemma 4 E4B INT4, VoxCPM2, penuh Whisper Large-v3, dan Silero VADLayanan ini mulai offline dan menolak identitas model yang tidak kompatibel.

VoxCPM2 mendukung penimbangan:.float16 untuk sebuah pos pemeriksaan unquantized. Parameter utama yang digunakan FP16; AudioVAE dan beberapa komputasi tetap FP32. INT8 titik pemeriksaan ditolak untuk mode ini.

Gemma4ConversationMessage, Gemma4Tool dan generateResponse melestarikan deklarasi alat asli, argumen, dan hasil sejarah. InferenceCancellation Token membatalkan generasi secara kooperatif. Silero makemorts dentStream isolates state, dan procechChunkthrowing menyebarkan kesalahan inferensi.

Whisper ASRModel.from Direktori loads dipentaskan penuh Large-v3 CoreML Bundles. 32-layer decoder dapat membangun prefiks tanpa TextDecoderContextComple.mmodel dan rilis per- langkah temporaries. Baku Whisper CLI model tetap Turbo.

Layanan asli serialisasi percakapan, sintesis, dan transkripsi; ringan VAD memiliki keadaan aliran terpisah. Gemma prefill menggunakan 256-token batch, permintaan keadaan KV dirilis, dan MLX cache dicapped di 128 MiB dengan 20 Batas allocator GiB.

Pidato dengan stream: kejadian audio NDJSON kembali benar di 48 kHz, diikuti oleh dilakukan atau error. Batal oleh requestID di /v1/ menyimpulkan / batal. GET /v1/ kesimpulan / kemampuan dan /v1/ inferensi / provenance laporan identitas model, parameter dtypes, pin aset, dan penghitung memori.

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

Pemilik model opsional

SpeechRuntime menyediakan muatan bersama, penerimaan FIFO yang dapat dibatalkan dan eksekusi serialisasi. Qwen3ASRRuntime memilih MLX atau penuh CoreML melalui deskriptor eksplisit; bendera APIs dan CLI asli masih tersedia.

Server audio memiliki satu ijin eksekusi melalui setiap permintaan lengkap, termasuk keluaran streaming, dengan hingga 32 permintaan menunggu. ASR gagal menghasilkan kesalahan HTTP atau WebSocket kesalahan terjadi; transkrip kosong tetap sukses. Pemutaran mempertahankan sampel overflow. Model cache dan audio putar menunggu masih menempati memori.

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 berjalan lokal pada Apple Silicon melalui modul Voxtral TTS: 20 preset suara, sembilan bahasa, dan 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)
}

Hanya preset suara; reference-audio cloning tidak tersedia. Keluaran stream audio melalui SDK, CLI, WebSocket dan HTTP PCM. Pasokan teks masukan lengkap di depan. Model bobot menggunakan CC BY- NC 4.0.

import AudioCommon

let player = StreamingAudioPlayer()
player.skipInitialSilence = false

Baca Voxtral TTS Panduan