API ve Protokoller
AudioCommon modülü, modelden bağımsız protokolleri ve ortak tipleri tanımlar. Bu arayüzleri uygulayan herhangi bir model, birbirinin yerine kullanılabilir.
Protokol Genel Bakışı
┌─────────────────────────────────────────────────────────┐
│ AudioCommon │
│ │
│ AudioChunk SpeechGenerationModel (TTS) │
│ AlignedWord SpeechRecognitionModel (STT) │
│ SpeechSegment ForcedAlignmentModel │
│ SpeechToSpeechModel │
│ VoiceActivityDetectionModel (VAD) │
│ SpeakerEmbeddingModel │
│ SpeakerDiarizationModel │
│ SpeakerExtractionCapable │
└─────────────────────────────────────────────────────────┘SpeechRecognitionModel
Konuşmadan metne modelleri için protokol.
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
}
Uygulayan tipler: Qwen3ASRModel, ParakeetASRModel, ParakeetStreamingASRModel, OmnilingualASRModel (CoreML), OmnilingualASRMLXModel (MLX)
SpeechGenerationModel
Metinden konuşmaya modelleri için protokol.
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(), generate()'i tek bir parça olarak saran varsayılan bir uygulamaya sahiptir. Gerçek streaming'e sahip modeller (örn. Qwen3-TTS) bunu geçersiz kılar.
Uygulayan tipler: Qwen3TTSModel, CosyVoiceTTSModel, KokoroTTSModel, Qwen35MLXChat
ForcedAlignmentModel
Sözcük düzeyinde zaman damgası hizalaması için protokol.
public protocol ForcedAlignmentModel: AnyObject {
func align(audio: [Float], text: String, sampleRate: Int, language: String?) -> [AlignedWord]
}
SpeechToSpeechModel
Konuşmadan konuşmaya diyalog modelleri için protokol.
public protocol SpeechToSpeechModel: AnyObject {
var sampleRate: Int { get }
func respond(userAudio: [Float]) -> [Float]
func respondStream(userAudio: [Float]) -> AsyncThrowingStream<AudioChunk, Error>
}
Uygulayan tipler: PersonaPlexModel
VoiceActivityDetectionModel
Ses etkinliği algılama için protokol.
public protocol VoiceActivityDetectionModel: AnyObject {
var inputSampleRate: Int { get }
func detectSpeech(audio: [Float], sampleRate: Int) -> [SpeechSegment]
}
SpeakerEmbeddingModel
Konuşmacı embedding çıkarımı için protokol.
public protocol SpeakerEmbeddingModel: AnyObject {
var inputSampleRate: Int { get }
var embeddingDimension: Int { get }
func embed(audio: [Float], sampleRate: Int) -> [Float]
}
Uygulayan tipler: WeSpeakerModel
SpeakerDiarizationModel
Ses segmentlerine konuşmacı etiketleri atayan konuşmacı ayrıştırma modelleri için protokol.
public protocol SpeakerDiarizationModel: AnyObject {
var inputSampleRate: Int { get }
func diarize(audio: [Float], sampleRate: Int) -> [DiarizedSegment]
}
Uygulayan tipler: DiarizationPipeline (Pyannote), SortformerDiarizer
SpeakerExtractionCapable
Bir referans embedding kullanarak hedef konuşmacının segmentlerini çıkarmayı destekleyen motorlar için genişletilmiş ayrıştırma protokolü. Tüm motorlar bunu desteklemez (Sortformer uçtan uca çalışır ve konuşmacı embedding'leri üretmez).
public protocol SpeakerExtractionCapable: SpeakerDiarizationModel {
func extractSpeaker(audio: [Float], sampleRate: Int, targetEmbedding: [Float]) -> [SpeechSegment]
}
Uygulayan tipler: DiarizationPipeline (yalnızca Pyannote)
Ortak Tipler
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
}
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
İsteğe bağlı konuşmacı ve duygu etiketleriyle çok konuşmacılı diyalog metninin ayrıştırılmış bir segmenti. CosyVoice3 diyalog sentezi için DialogueParser ve DialogueSynthesizer ile birlikte kullanılır.
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
Satır içi konuşmacı etiketleri ([S1]) ve duygu etiketleri ((happy)) ile çok konuşmacılı diyalog metnini ayrıştırır.
public enum DialogueParser {
static func parse(_ text: String) -> [DialogueSegment]
static func emotionToInstruction(_ emotion: String) -> String
}
Yerleşik duygular: happy/excited, sad, angry, whispers/whispering, laughs/laughing, calm, surprised, serious. Bilinmeyen etiketler serbest biçimli yönergeler olarak doğrudan geçer.
DialogueSynthesizer
Konuşmacı başına ses klonlama, sessizlik boşlukları ve crossfade ile çok segmentli diyalog sentezini düzenler.
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
}
PipelineLLM
Ses pipeline'larıyla dil modeli entegrasyonu için protokol. Bir LLM'i VoicePipeline'ın ASR → LLM → TTS akışına köprüler.
public protocol PipelineLLM: AnyObject {
func chat(messages: [(role: MessageRole, content: String)],
onToken: @escaping (String, Bool) -> Void)
func cancel()
}
Yerleşik adaptör: Qwen3PipelineLLM, token temizleme, iptal ve bekleyen ifade biriktirme ile Qwen35MLXChat'i bu protokole köprüler.
AudioIO
AVAudioEngine boilerplate'ini ortadan kaldıran yeniden kullanılabilir ses I/O yöneticisi. Mikrofon yakalama, yeniden örnekleme, oynatma ve ses seviyesi ölçümünü yönetir.
let audio = AudioIO()
try audio.startMicrophone(targetSampleRate: 16000) { samples in
pipeline.pushAudio(samples)
}
audio.player.scheduleChunk(ttsOutput)
audio.stopMicrophone()
AudioIO, TTS çıktısı için bir StreamingAudioPlayer ve yakalama ile çıkarım iş parçacıkları arasında iş parçacığı güvenli ses aktarımı için bir AudioRingBuffer içerir.
SentencePieceModel
SentencePiece .model dosyaları için AudioCommon içinde bulunan ortak protobuf okuyucusu. SentencePiece parçalarını çözmesi gereken her modül (PersonaPlex, OmnilingualASR, gelecekteki ASR / TTS portları) protobuf wire formatını yeniden uygulamak yerine kendi kod çözücüsünü bu tek okuyucunun üzerine kurar.
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
}
Kullananlar: OmnilingualASR.OmnilingualVocabulary, PersonaPlex.SentencePieceDecoder. Tests/AudioCommonTests/SentencePieceModelTests içinde 7 birim testiyle kapsanır.
MLXCommon.SDPA
Her MLX dikkat modülünde (Qwen3-ASR / Qwen3-TTS / Qwen3-Chat / CosyVoice / PersonaPlex / OmnilingualASR) paylaşılan scaled dot-product attention yardımcıları. Her modül kendi projeksiyonlarını korur — SDPA yalnızca reshape → attention → merge boilerplate'ini yönetir.
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
}
Tüm reshape çağrıları batch boyutu için -1 kullanır; böylece yardımcılar, çalışma zamanında batch'i değişen MLX.compile(shapeless:) grafikleriyle (örn. Qwen3-TTS Talker otoregresif kod çözme) birleşebilir.
HTTP API Sunucusu
speech-server ikili dosyası, speech-swift içindeki her modeli HTTP REST uç noktaları olarak ve OpenAI Realtime API'sini uygulayan bir WebSocket uç noktası olarak sunar. Modeller ilk istekte tembel olarak yüklenir; başlangıçta hepsini ısıtmak için --preload geçirin.
swift build -c release
.build/release/speech-server --port 8080
# Başlangıçta her modeli önceden yükle
.build/release/speech-server --port 8080 --preload
REST Uç Noktaları
| Uç Nokta | Yöntem | İstek | Yanıt |
|---|---|---|---|
/transcribe | POST | audio/wav gövdesi | JSON { text } (Qwen3-ASR) |
/speak | POST | JSON { text, engine?, language?, voice? } | audio/wav gövdesi (Qwen3-TTS, CosyVoice, Kokoro) |
/respond | POST | audio/wav gövdesi | audio/wav gövdesi (PersonaPlex) |
/enhance | POST | audio/wav gövdesi | audio/wav gövdesi (DeepFilterNet3) |
/vad | POST | audio/wav gövdesi | JSON segment listesi |
/diarize | POST | audio/wav gövdesi | JSON DiarizedSegment listesi |
/embed-speaker | POST | audio/wav gövdesi | JSON [Float] (256 boyut) |
# Bir dosyayı transkribe et
curl -X POST http://localhost:8080/transcribe \
--data-binary @recording.wav \
-H "Content-Type: audio/wav"
# Konuşma sentezle
curl -X POST http://localhost:8080/speak \
-H "Content-Type: application/json" \
-d '{"text": "Hello world", "engine": "cosyvoice"}' \
-o output.wav
# Tam konuşmadan konuşmaya gidiş-dönüş
curl -X POST http://localhost:8080/respond \
--data-binary @question.wav \
-o response.wav
OpenAI Realtime API (/v1/realtime)
ws://host:port/v1/realtime'deki WebSocket uç noktası, OpenAI Realtime protokolünü uygular. Tüm mesajlar type ayırt edicisiyle JSON'dur; ses yükleri 24 kHz mono'da base64 ile kodlanmış PCM16'dır.
İstemci → Sunucu olayları
| Olay | Amaç |
|---|---|
session.update | Motor, dil, ses ve ses biçimini yapılandır |
input_audio_buffer.append | Giriş tamponuna base64 PCM16 parçası ekle |
input_audio_buffer.commit | Tamponlanmış sesi transkripsiyon için commit et |
input_audio_buffer.clear | Mevcut giriş tamponunu iptal et |
response.create | Verilen metin/yönergeler için TTS sentezi iste |
Sunucu → İstemci olayları
| Olay | Anlamı |
|---|---|
session.created | El sıkışma tamamlandı, varsayılan yapılandırma yayımlandı |
session.updated | En son session.update onaylandı |
input_audio_buffer.committed | Ses kabul edildi ve transkripsiyon için kuyruğa alındı |
conversation.item.input_audio_transcription.completed | Nihai transkript metniyle ASR sonucu |
response.audio.delta | Sentezlenen sesin base64 PCM16 parçası |
response.audio.done | Bu yanıt için artık ses parçası yok |
response.done | Yanıt tamamlandı (metadata + gecikme istatistikleri) |
error | type ve message ile hata zarfı |
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
Sunucu, AudioServer SPM ürününde bulunur. Examples/websocket-client.html'da örnek bir tarayıcı istemcisi sağlanır — tam ASR + TTS gidiş-dönüşünü çalıştırmak için onu çalışan bir sunucuyla birlikte açın.
Model İndirmeleri
Tüm modeller ilk kullanımda HuggingFace'ten indirilir ve ~/Library/Caches/qwen3-speech/ içinde önbelleğe alınır. AudioCommon modülü, indirme, önbellekleme ve bütünlük doğrulamayı yöneten ortak bir HuggingFaceDownloader sağlar.