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).
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
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
| Endpoint | Metode | Permintaan | Respon |
|---|---|---|---|
/transcribe | POST | audio/wav tubuh | JSON { text } (Qwen3-ASR) |
/v1/audio/transcriptions | POST | Multipart { file, model, response_format?, language?, prompt?, temperature? } | JSON, text, verbose JSON, SRT, atau VTT |
/v1/audio/speech | POST | JSON { model, input, voice, response_format?, speed? } | audio/wav atau audio/pcm tubuh |
/speak | POST | JSON { text, engine?, language?, voice? } | audio/wav tubuh (Qwen3-TTS, CosyVoice, Kokoro) |
/respond | POST | audio/wav tubuh | audio/wav body (PersonaPlex) |
/enhance | POST | audio/wav tubuh | audio/wav body (DeepFilterNet3) |
/vad | POST | audio/wav tubuh | Daftar segmen JSON |
/diarize | POST | audio/wav tubuh | JSON DiarizedSegment daftar |
/embed-speaker | POST | audio/wav tubuh | JSON [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
| Peristiwa | Tujuan |
|---|---|
session.update | Konfigurasikan mesin, bahasa, suara, format audio, dan server opsional VAD |
input_audio_buffer.append | Tambahkan base64 PCM16 chunk ke buffer masukan |
input_audio_buffer.commit | Komit audio yang dibelokkan untuk transkripsi |
input_audio_buffer.clear | Buang buffer masukan saat ini |
response.create | Permintaan sintesis TTS untuk teks / instruksi yang diberikan |
Peristiwa Server £Quent
| Peristiwa | Artinya |
|---|---|
session.created | Handshake lengkap, konfigurasi bawaan dikeluarkan |
session.updated | Terbaru-baru ini session.update Diakui |
input_audio_buffer.speech_started | Server VAD mendeteksi awal dari pergantian pidato |
input_audio_buffer.speech_stopped | Server VAD mengakhiri pergantian setelah keheningan atau batas durasi |
input_audio_buffer.committed | Audio diterima dan mengantri untuk transkripsi |
conversation.item.input_audio_transcription.completed | Hasil ASR dengan teks transkrip akhir |
response.audio.delta | Base64 PCM16 potongan audio disintesis |
response.audio.done | Tidak ada lagi potongan audio untuk tanggapan ini |
response.done | Respon terfinalisasi (statistik metadata + latensi) |
error | Amplop 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)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 8080POST /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, contentPemilik 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