API और प्रोटोकॉल
AudioCommon मॉड्यूल मॉडल-अज्ञेयवादी प्रोटोकॉल और साझा प्रकार परिभाषित करता है। इन इंटरफ़ेस के माध्यम से कोई भी अनुरूप मॉडल परस्पर उपयोग किया जा सकता है।
प्रोटोकॉल अवलोकन
┌─────────────────────────────────────────────────────────┐
│ AudioCommon │
│ │
│ AudioChunk SpeechGenerationModel (TTS) │
│ AlignedWord SpeechRecognitionModel (STT) │
│ SpeechSegment ForcedAlignmentModel │
│ SpeechToSpeechModel │
│ VoiceActivityDetectionModel (VAD) │
│ SpeakerEmbeddingModel │
│ SpeakerDiarizationModel │
│ SpeakerExtractionCapable │
└─────────────────────────────────────────────────────────┘SpeechRecognitionModel
स्पीच-टू-टेक्स्ट मॉडलों के लिए प्रोटोकॉल।
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
}
अनुरूप प्रकार: Qwen3ASRModel, ParakeetASRModel, ParakeetStreamingASRModel, OmnilingualASRModel (CoreML), OmnilingualASRMLXModel (MLX)
SpeechGenerationModel
टेक्स्ट-टू-स्पीच मॉडलों के लिए प्रोटोकॉल।
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() को एकल चंक के रूप में लपेटता है। वास्तविक स्ट्रीमिंग वाले मॉडल (जैसे Qwen3-TTS) इसे ओवरराइड करते हैं।
अनुरूप प्रकार: Qwen3TTSModel, CosyVoiceTTSModel, KokoroTTSModel, Qwen35MLXChat
ForcedAlignmentModel
शब्द-स्तरीय टाइमस्टैम्प अलाइनमेंट के लिए प्रोटोकॉल।
public protocol ForcedAlignmentModel: AnyObject {
func align(audio: [Float], text: String, sampleRate: Int, language: String?) -> [AlignedWord]
}
SpeechToSpeechModel
स्पीच-टू-स्पीच डायलॉग मॉडलों के लिए प्रोटोकॉल।
public protocol SpeechToSpeechModel: AnyObject {
var sampleRate: Int { get }
func respond(userAudio: [Float]) -> [Float]
func respondStream(userAudio: [Float]) -> AsyncThrowingStream<AudioChunk, Error>
}
अनुरूप प्रकार: PersonaPlexModel
VoiceActivityDetectionModel
वॉयस एक्टिविटी डिटेक्शन के लिए प्रोटोकॉल।
public protocol VoiceActivityDetectionModel: AnyObject {
var inputSampleRate: Int { get }
func detectSpeech(audio: [Float], sampleRate: Int) -> [SpeechSegment]
}
SpeakerEmbeddingModel
स्पीकर एम्बेडिंग निष्कर्षण के लिए प्रोटोकॉल।
public protocol SpeakerEmbeddingModel: AnyObject {
var inputSampleRate: Int { get }
var embeddingDimension: Int { get }
func embed(audio: [Float], sampleRate: Int) -> [Float]
}
अनुरूप प्रकार: WeSpeakerModel
SpeakerDiarizationModel
स्पीकर डायराइज़ेशन मॉडलों के लिए प्रोटोकॉल जो ऑडियो सेगमेंट को स्पीकर लेबल असाइन करते हैं।
public protocol SpeakerDiarizationModel: AnyObject {
var inputSampleRate: Int { get }
func diarize(audio: [Float], sampleRate: Int) -> [DiarizedSegment]
}
अनुरूप प्रकार: DiarizationPipeline (Pyannote), SortformerDiarizer
SpeakerExtractionCapable
उन इंजनों के लिए विस्तारित डायराइज़ेशन प्रोटोकॉल जो संदर्भ एम्बेडिंग का उपयोग करके लक्ष्य स्पीकर के सेगमेंट निकालने का समर्थन करते हैं। सभी इंजन इसका समर्थन नहीं करते (Sortformer एंड-टू-एंड है और स्पीकर एम्बेडिंग नहीं बनाता)।
public protocol SpeakerExtractionCapable: SpeakerDiarizationModel {
func extractSpeaker(audio: [Float], sampleRate: Int, targetEmbedding: [Float]) -> [SpeechSegment]
}
अनुरूप प्रकार: DiarizationPipeline (केवल Pyannote)
साझा प्रकार
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
वैकल्पिक स्पीकर और इमोशन टैग के साथ मल्टी-स्पीकर डायलॉग टेक्स्ट का पार्स किया गया सेगमेंट। CosyVoice3 डायलॉग संश्लेषण के लिए DialogueParser और DialogueSynthesizer के साथ उपयोग किया जाता है।
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
इनलाइन स्पीकर टैग ([S1]) और इमोशन टैग ((happy)) के साथ मल्टी-स्पीकर डायलॉग टेक्स्ट को पार्स करता है।
public enum DialogueParser {
static func parse(_ text: String) -> [DialogueSegment]
static func emotionToInstruction(_ emotion: String) -> String
}
अंतर्निहित इमोशन: happy/excited, sad, angry, whispers/whispering, laughs/laughing, calm, surprised, serious। अज्ञात टैग फ्रीफ़ॉर्म निर्देशों के रूप में पास होते हैं।
DialogueSynthesizer
प्रति-स्पीकर वॉयस क्लोनिंग, मौन अंतराल, और क्रॉसफ़ेड के साथ मल्टी-सेगमेंट डायलॉग संश्लेषण का आयोजन करता है।
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
वॉयस पाइपलाइन के साथ भाषा मॉडल एकीकरण के लिए प्रोटोकॉल। VoicePipeline के ASR → LLM → TTS प्रवाह में LLM को ब्रिज करता है।
public protocol PipelineLLM: AnyObject {
func chat(messages: [(role: MessageRole, content: String)],
onToken: @escaping (String, Bool) -> Void)
func cancel()
}
अंतर्निहित एडाप्टर: Qwen3PipelineLLM टोकन क्लीनअप, कैंसिलेशन, और पेंडिंग फ़्रेज़ संचय के साथ Qwen35MLXChat को इस प्रोटोकॉल से ब्रिज करता है।
AudioIO
पुन: उपयोग योग्य ऑडियो I/O मैनेजर जो AVAudioEngine बॉयलरप्लेट को समाप्त करता है। माइक कैप्चर, रीसैंपलिंग, प्लेबैक, और ऑडियो लेवल मीटरिंग को संभालता है।
let audio = AudioIO()
try audio.startMicrophone(targetSampleRate: 16000) { samples in
pipeline.pushAudio(samples)
}
audio.player.scheduleChunk(ttsOutput)
audio.stopMicrophone()
AudioIO में TTS आउटपुट के लिए एक StreamingAudioPlayer और कैप्चर और इन्फ़रेंस थ्रेड्स के बीच थ्रेड-सुरक्षित ऑडियो ट्रांसफ़र के लिए एक AudioRingBuffer शामिल है।
SentencePieceModel
SentencePiece .model फ़ाइलों के लिए साझा protobuf रीडर, AudioCommon में रहता है। प्रत्येक मॉड्यूल जिसे SentencePiece पीसेस को डिकोड करना होता है (PersonaPlex, OmnilingualASR, भविष्य के ASR / TTS पोर्ट) protobuf wire format को फिर से लागू करने के बजाय इस एकल रीडर के ऊपर अपना डिकोडर बनाता है।
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
}
उपयोगकर्ता: OmnilingualASR.OmnilingualVocabulary, PersonaPlex.SentencePieceDecoder। Tests/AudioCommonTests/SentencePieceModelTests में 7 यूनिट टेस्ट द्वारा कवर।
MLXCommon.SDPA
हर MLX attention मॉड्यूल (Qwen3-ASR / Qwen3-TTS / Qwen3-Chat / CosyVoice / PersonaPlex / OmnilingualASR) में साझा किए गए scaled dot-product attention हेल्पर्स। प्रत्येक मॉड्यूल अपने प्रोजेक्शन रखता है — SDPA केवल reshape → attention → merge बॉयलरप्लेट को संभालता है।
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
}
सभी reshape कॉल बैच डाइमेंशन के लिए -1 का उपयोग करते हैं ताकि हेल्पर्स MLX.compile(shapeless:) ग्राफ़ के साथ कम्पोज़ हो सकें जो रनटाइम पर बैच बदलते हैं (जैसे Qwen3-TTS Talker ऑटोरिग्रेसिव डिकोड)।
HTTP API सर्वर
audio-server बाइनरी speech-swift में प्रत्येक मॉडल को HTTP REST एंडपॉइंट्स और एक WebSocket एंडपॉइंट के रूप में एक्सपोज़ करती है जो OpenAI Realtime API को लागू करता है। मॉडल पहले अनुरोध पर आलसी रूप से लोड होते हैं; स्टार्टअप पर सभी को गर्म करने के लिए --preload पास करें।
swift build -c release
.build/release/audio-server --port 8080
# स्टार्टअप पर हर मॉडल को प्रीलोड करें
.build/release/audio-server --port 8080 --preload
REST एंडपॉइंट्स
| एंडपॉइंट | मेथड | अनुरोध | प्रतिक्रिया |
|---|---|---|---|
/transcribe | POST | audio/wav बॉडी | JSON { text } (Qwen3-ASR) |
/speak | POST | JSON { text, engine?, language?, voice? } | audio/wav बॉडी (Qwen3-TTS, CosyVoice, Kokoro) |
/respond | POST | audio/wav बॉडी | audio/wav बॉडी (PersonaPlex) |
/enhance | POST | audio/wav बॉडी | audio/wav बॉडी (DeepFilterNet3) |
/vad | POST | audio/wav बॉडी | JSON सेगमेंट सूची |
/diarize | POST | audio/wav बॉडी | JSON DiarizedSegment सूची |
/embed-speaker | POST | audio/wav बॉडी | JSON [Float] (256-dim) |
# एक फ़ाइल को ट्रांसक्राइब करें
curl -X POST http://localhost:8080/transcribe \
--data-binary @recording.wav \
-H "Content-Type: audio/wav"
# स्पीच संश्लेषित करें
curl -X POST http://localhost:8080/speak \
-H "Content-Type: application/json" \
-d '{"text": "Hello world", "engine": "cosyvoice"}' \
-o output.wav
# पूर्ण स्पीच-टू-स्पीच राउंड ट्रिप
curl -X POST http://localhost:8080/respond \
--data-binary @question.wav \
-o response.wav
OpenAI Realtime API (/v1/realtime)
ws://host:port/v1/realtime पर WebSocket एंडपॉइंट OpenAI Realtime प्रोटोकॉल लागू करता है। सभी संदेश JSON हैं जिसमें एक type विभेदक होता है; ऑडियो पेलोड 24 kHz मोनो पर base64-एन्कोडेड PCM16 हैं।
क्लाइंट → सर्वर इवेंट्स
| इवेंट | उद्देश्य |
|---|---|
session.update | इंजन, भाषा, वॉयस, और ऑडियो फ़ॉर्मेट कॉन्फ़िगर करें |
input_audio_buffer.append | इनपुट बफ़र में एक base64 PCM16 चंक जोड़ें |
input_audio_buffer.commit | ट्रांसक्रिप्शन के लिए बफ़र किए गए ऑडियो को कमिट करें |
input_audio_buffer.clear | वर्तमान इनपुट बफ़र को छोड़ें |
response.create | दिए गए टेक्स्ट/निर्देशों के लिए TTS संश्लेषण का अनुरोध करें |
सर्वर → क्लाइंट इवेंट्स
| इवेंट | अर्थ |
|---|---|
session.created | हैंडशेक पूर्ण, डिफ़ॉल्ट कॉन्फ़िग जारी |
session.updated | सबसे हाल का session.update स्वीकृत |
input_audio_buffer.committed | ऑडियो स्वीकार और ट्रांसक्रिप्शन के लिए क्यू में |
conversation.item.input_audio_transcription.completed | अंतिम ट्रांसक्रिप्ट टेक्स्ट के साथ ASR परिणाम |
response.audio.delta | संश्लेषित ऑडियो का Base64 PCM16 चंक |
response.audio.done | इस प्रतिक्रिया के लिए और ऑडियो चंक्स नहीं |
response.done | प्रतिक्रिया अंतिम (मेटाडेटा + लेटेंसी आँकड़े) |
error | type और message के साथ त्रुटि लिफ़ाफ़ा |
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
सर्वर AudioServer SPM उत्पाद में रहता है। एक उदाहरण ब्राउज़र क्लाइंट Examples/websocket-client.html पर शिप किया जाता है — पूर्ण ASR + TTS राउंड ट्रिप चलाने के लिए इसे एक चल रहे सर्वर के साथ खोलें।
मॉडल डाउनलोड
सभी मॉडल पहली बार उपयोग पर HuggingFace से डाउनलोड किए जाते हैं और ~/Library/Caches/qwen3-speech/ में कैश किए जाते हैं। AudioCommon मॉड्यूल एक साझा HuggingFaceDownloader प्रदान करता है जो डाउनलोड, कैशिंग, और अखंडता सत्यापन को संभालता है।