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, WhisperASRModel, 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, VoxCPM2TTSModel, KokoroTTSModel, IndexTTS2TTSModel

IndexTTS2TTSModel zero-shot voice cloning के लिए reference-audio वाला generate overload जोड़ता है और speaking-rate तथा pause controls के लिए IndexTTS2SynthesisOptions का उपयोग करता है।

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 शामिल है।

SystemAudioTap

सिस्टम आउटपुट मिक्स — जो Mac अभी चला रहा है — को कॉलर द्वारा चुनी गई सैंपल दर पर मोनो Float32 के रूप में कैप्चर करता है (Core Audio process taps, macOS 14.4+)। ग्लोबल मोनो टैप डिफ़ॉल्ट रूप से वर्तमान प्रोसेस को बाहर रखता है, इसलिए ऐप का अपना प्लेबैक कभी दोबारा कैप्चर नहीं होता, और टैप केवल उसी टैप वाले निजी एग्रीगेट डिवाइस में लपेटा जाता है।

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

डिफ़ॉल्ट आउटपुट डिवाइस बदलने पर भी कैप्चर जारी रहता है — मिक्स दर बदलने पर आंतरिक रीसैंपलर फिर से बनता है। होस्ट ऐप को NSAudioCaptureUsageDescription घोषित करना होगा। रिकॉर्डिंग अनुमति अस्वीकृत होने पर टैप बन तो सकता है पर केवल मौन देता है: framesCaptured बढ़े और nonSilentFrames 0 पर रहे तो स्पष्ट रूप से विफल हों।

टाइमस्टैम्प वाले स्वतंत्र स्रोत

दोनों कैप्चर क्लास टाइमस्टैम्प वाले ओवरलोड देती हैं। CapturedAudioChunk में sampleRate पर मोनो samples और पहले इनपुट फ्रेम के लिए वैकल्पिक Mach hostTime होता है। दोनों स्रोत एक ही होस्ट घड़ी का उपयोग करते हैं, इसलिए ऐप उनके PCM को अलग रखकर ऑडियो मिलाए बिना प्राप्त ट्रांसक्रिप्ट इवेंट्स को समय के अनुसार क्रमबद्ध कर सकता है।

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

केवल सुनने वाले कैप्चर के लिए enableAEC: true माइक्रोफ़ोन फ़ॉर्मैट पढ़ने से पहले Apple Voice Processing चालू करता है, और enablePlayback: false अनुपयोगी प्लेयर को नहीं जोड़ता। macOS पर डिवाइस का प्लेबैक माइक्रोफ़ोन सैंपल से हटा दिया जाता है; AEC शुरू न हो सके तो कच्चे इनपुट पर लौटने के बजाय त्रुटि दी जाती है।

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 सर्वर

speech-server बाइनरी speech-swift में प्रत्येक मॉडल को HTTP REST एंडपॉइंट्स और एक WebSocket एंडपॉइंट के रूप में एक्सपोज़ करती है जो OpenAI Realtime API को लागू करता है। मॉडल पहले अनुरोध पर आलसी रूप से लोड होते हैं; स्टार्टअप पर सभी को गर्म करने के लिए --preload पास करें।

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

# स्टार्टअप पर हर मॉडल को प्रीलोड करें
.build/release/speech-server --port 8080 --preload

REST एंडपॉइंट्स

एंडपॉइंटमेथडअनुरोधप्रतिक्रिया
/transcribePOSTaudio/wav बॉडीJSON { text } (Qwen3-ASR)
/v1/audio/transcriptionsPOSTmultipart form { file, model, response_format?, language?, prompt?, temperature? }JSON, text, verbose JSON, SRT या VTT
/v1/audio/speechPOSTJSON { model, input, voice, response_format?, speed? }audio/wav या audio/pcm ऑडियो बॉडी
/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)

/v1/audio/speech मानक TTS मॉडल और वॉइस नामों को स्थानीय इंजनों से मैप करता है। डिफ़ॉल्ट रूप से WAV मिलता है; response_format: "pcm" बिना हेडर वाला 24 kHz मोनो PCM16 लिटिल-एंडियन ऑडियो देता है। असमर्थित संपीड़ित प्रारूप स्पष्ट रूप से अस्वीकार किए जाते हैं।

/v1/audio/transcriptions multipart form data के रूप में एक WAV file लेता है और default में {"text":"..."} लौटाता है; response_format से text, verbose JSON, SRT या VTT भी माँगा जा सकता है। Apple पर model aliases local ASR engine चुनते हैं। Linux और Windows के packaged speech-core server में Parakeet lazy load होता है, PCM16/24/32 या Float32 WAV स्वीकार होता है और upload सीमा 25 MiB तथा 10 मिनट है।

# एक फ़ाइल को ट्रांसक्राइब करें
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

# 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

# पूर्ण स्पीच-टू-स्पीच राउंड ट्रिप
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 हैं।

कोल्ड मॉडल लोड या लंबे जनरेशन के दौरान, मॉडल आउटपुट तैयार होने तक सर्वर लगभग हर 15 सेकंड में हल्के realtime.keepalive JSON इवेंट और websocket pong कंट्रोल फ्रेम भेजता है। क्लाइंट इन्हें अनदेखा कर सकते हैं या गतिविधि संकेतक की तरह उपयोग कर सकते हैं।

क्लाइंट → सर्वर इवेंट्स

इवेंटउद्देश्य
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 प्रदान करता है जो डाउनलोड, कैशिंग, और अखंडता सत्यापन को संभालता है।