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.SentencePieceDecoderTests/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 एंडपॉइंट्स

एंडपॉइंटमेथडअनुरोधप्रतिक्रिया
/transcribePOSTaudio/wav बॉडीJSON { text } (Qwen3-ASR)
/speakPOSTJSON { text, engine?, language?, voice? }audio/wav बॉडी (Qwen3-TTS, CosyVoice, Kokoro)
/respondPOSTaudio/wav बॉडीaudio/wav बॉडी (PersonaPlex)
/enhancePOSTaudio/wav बॉडीaudio/wav बॉडी (DeepFilterNet3)
/vadPOSTaudio/wav बॉडीJSON सेगमेंट सूची
/diarizePOSTaudio/wav बॉडीJSON DiarizedSegment सूची
/embed-speakerPOSTaudio/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प्रतिक्रिया अंतिम (मेटाडेटा + लेटेंसी आँकड़े)
errortype और 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 प्रदान करता है जो डाउनलोड, कैशिंग, और अखंडता सत्यापन को संभालता है।