استنساخ الصوت

استنسخ أي صوت من عيّنة صوتية مرجعية قصيرة. يضيف IndexTTS2 استنساخًا صفريًا أصليًا على MLX مع التحكم في الانفعال والسرعة والتوقفات، إلى جانب استنساخ Qwen3-TTS وCosyVoice3 عبر encoders المتحدث.

كيف يعمل

  1. سجّل أو وفّر عيّنة صوتية مرجعية للصوت المستهدف
  2. استخراج embedding المتحدّث — يقوم encoder المتحدّث بمعالجة الصوت المرجعي للحصول على متجه embedding ذي بُعد ثابت
  3. حقن الـembedding — يُكيّف embedding المتحدّث نموذج TTS أثناء التوليد
  4. توليد الكلام — يولّد نموذج TTS كلامًا يطابق الخصائص الصوتية للعيّنة المرجعية

المحرّكات

يتوفر استنساخ الصوت مع عدة محركات TTS. يستخدم IndexTTS2 صوت prompt وتكييف الأسلوب؛ أما Qwen3-TTS وCosyVoice3 فيستخدمان encoders المتحدث:

المحرّكencoder المتحدّثEmbeddingBackend
Qwen3-TTSECAPA-TDNNx-vector بـ1024 بُعدًاMLX (GPU)
CosyVoice3CAM++192 بُعدًاCoreML (Neural Engine)
IndexTTS2w2v-BERT + MaskGCT + CAMPPlussemantic codes + prompt mel + 192-dim styleMLX (GPU)

استنساخ الصوت باستخدام IndexTTS2

IndexTTS2 محرك أصلي على MLX لاستنساخ الصوت zero-shot. يتطلب --voice-sample، وينزّل aufklarer/IndexTTS2-MLX-fp16 افتراضيًا، ويدعم مراجع اختيارية للانفعال/الأسلوب، والتحكم في الانفعال عبر preset أو vector، وتعديل سرعة الكلام، وتحديد التوقفات الداخلية. حاليًا يعمل كتوليف دفعي فقط.

CosyVoice3 + CAM++

يستخدم CosyVoice3 encoder المتحدّث CAM++ (Context-Aware Masking++) من مشروع 3D-Speaker التابع لـAlibaba. يُكيّف embedding ذو الـ192 بُعدًا نموذج DiT flow عبر طبقة إسقاط أفينية (192 → 80) دُرِّبت بالتزامن مع CosyVoice3.

بنية CAM++

المرحلةالوصف
FCMوحدة التفاف أمامية (Conv2d + 2 ResBlocks، 32 قناة)
TDNNTime Delay Neural Network (من 320 إلى 128 قناة، حجم النواة 5)
كتل D-TDNN3 كتل مترابطة بكثافة (12/24/16 طبقة) مع context-aware masking
Stats Poolتجميع المتوسّط + الانحراف المعياري (إحصاءات شاملة)
Denseإسقاط خطّي إلى embedding بـ192 بُعدًا

يعمل نموذج CoreML (نحو 14 MB، FP16) على Neural Engine. يُنزَّل تلقائيًا من aufklarer/CamPlusPlus-Speaker-CoreML عند أوّل استخدام.

استنساخ الصوت في Qwen3-TTS

يدعم Qwen3-TTS وضعَين لاستنساخ الصوت:

وضع ICL (موصى به)

يُرمِّز وضع In-Context Learning الصوتَ المرجعي إلى رموز codec عبر encoder مُجزّئ الكلام Mimi ويُسبِقه بالنص المرجعي. وهذا يمنح النموذج سياقًا صوتيًا كاملًا — جودة أعلى وEOS موثوق (يُصلح المشاكل مع النصوص القصيرة واللغات غير الإنجليزية).

let (model, encoder) = try await Qwen3TTSModel.fromPretrainedWithEncoder()
let audio = model.synthesizeWithVoiceCloneICL(
    text: "Target text to synthesize.",
    referenceAudio: refSamples,
    referenceSampleRate: 24000,
    referenceText: "Exact transcript of reference audio.",
    language: "english",
    codecEncoder: encoder
)

وضع X-Vector

يستخدم encoder من نوع ECAPA-TDNN يُنتج x-vector بـ1024 بُعدًا. لا يتطلّب نصًا مرجعيًا لكن جودته أقل. قد يفشل في إصدار EOS مع النصوص القصيرة أو بعض اللغات.

بنية ECAPA-TDNN

المرحلةالوصف
TDNNTime Delay Neural Network (من 128 إلى 512 قناة، حجم النواة 5)
كتل SE-Res2Net3 كتل مع Squeeze-and-Excitation (512 قناة، توسّع 2/3/4)
MFAMulti-layer Feature Aggregation (1536 قناة + ReLU)
ASPAttentive Statistics Pooling (1536 قناة، softmax عبر الزمن)
FCطبقة متّصلة بالكامل (من 3072 إلى 1024 بُعدًا)

الأوزان (76 معاملًا) مضمّنة في ملفات safetensors الخاصّة بـQwen3-TTS — لا حاجة إلى تنزيل منفصل.

استخدام CLI

# CosyVoice3 voice cloning (CAM++, CoreML Neural Engine)
.build/release/speech speak "Text in the cloned voice" \
    --engine cosyvoice --voice-sample reference.wav -o output.wav

# IndexTTS2 voice cloning (expanded MLX bundle)
.build/release/speech speak "Text in the cloned voice" \
    --engine indextts2 --voice-sample reference.wav \
    --indextts2-speaking-rate 1.35 \
    --indextts2-max-pause 0.05 \
    -o output.wav

# Qwen3-TTS voice cloning (ECAPA-TDNN, MLX GPU)
.build/release/speech speak "Text in the cloned voice" \
    --voice-sample reference.wav -o output.wav

أمثلة

# CosyVoice3: multilingual voice cloning (9 languages)
.build/release/speech speak "Hello, this is my cloned voice." \
    --engine cosyvoice --voice-sample my_voice.wav -o cloned_hello.wav

# CosyVoice3: clone voice in a different language
.build/release/speech speak "Guten Tag, das ist meine geklonte Stimme." \
    --engine cosyvoice --voice-sample my_voice.wav --language german -o german.wav

# Qwen3-TTS: English voice cloning
.build/release/speech speak "The quick brown fox jumps over the lazy dog." \
    --voice-sample recording_15s.wav -o cloned_fox.wav

# IndexTTS2: local exported bundle
.build/release/speech speak "This voice was cloned locally." \
    --engine indextts2 --voice-sample my_voice.wav \
    --indextts2-speaking-rate 1.35 \
    --indextts2-max-pause 0.05 \
    --indextts2-bundle-dir /path/to/IndexTTS2-MLX-fp16 \
    -o indextts2.wav

حوار متعدّد المتحدّثين

يدعم CosyVoice3 الحوارات متعدّدة المتحدّثين مع استنساخ صوت لكلّ متحدّث. استخدم الـflag --speakers لربط وسوم المتحدّثين بملفّات الصوت المرجعي:

# Two-speaker dialogue with voice cloning
.build/release/speech speak "[S1] Hello there! [S2] Hey, how are you?" \
    --engine cosyvoice --speakers s1=alice.wav,s2=bob.wav -o dialogue.wav

# Dialogue with emotion tags + voice cloning
.build/release/speech speak "[S1] (happy) Great news! [S2] (surprised) Really? Tell me more." \
    --engine cosyvoice --speakers s1=alice.wav,s2=bob.wav -o emotional_dialogue.wav

# Adjust silence between turns
.build/release/speech speak "[S1] First line. [S2] Second line." \
    --engine cosyvoice --speakers s1=a.wav,s2=b.wav --turn-gap 0.5 -o gapped.wav

يُعالَج الصوت المرجعي لكلّ متحدّث عبر encoder CAM++ لاستخراج embedding بـ192 بُعدًا. يُحمَّل النموذج مرّة واحدة ويُعاد استخدامه لجميع المتحدّثين. راجع دليل CosyVoice3 لمزيد من التفاصيل حول صياغة الحوار ووسوم الانفعال.

نصائح للصوت المرجعي

مهمّ

بالنسبة إلى Qwen3-TTS، يعمل استنساخ الصوت مع نموذج base فقط — وليس مع customVoice. يعمل استنساخ CosyVoice3 مع النموذج الافتراضي. يتطلب IndexTTS2 --voice-sample، ويعمل حاليًا كتوليف دفعي فقط، ويقبل عناصر تحكم اختيارية للانفعال/الأسلوب.

واجهة Swift

import CosyVoiceTTS

// CosyVoice3 voice cloning
let model = try await CosyVoiceTTSModel.fromPretrained()
let speaker = try await CamPlusPlusSpeaker.fromPretrained()

// Extract 192-dim speaker embedding from reference audio
let embedding = try speaker.embed(audio: refSamples, sampleRate: 16000)

// Synthesize with cloned voice
let audio = model.synthesize(
    text: "Hello in a cloned voice!",
    speakerEmbedding: embedding
)

// With custom instruction + speaker embedding
let styledAudio = model.synthesize(
    text: "Hello!",
    instruction: "Speak happily and with excitement.",
    speakerEmbedding: embedding
)

// Multi-speaker dialogue
let segments = DialogueParser.parse("[S1] (happy) Hi! [S2] Hey there.")
let embeddings = ["S1": aliceEmbedding, "S2": bobEmbedding]
let dialogueAudio = DialogueSynthesizer.synthesize(
    segments: segments,
    speakerEmbeddings: embeddings,
    model: model,
    language: "english"
)
import Qwen3TTS

// Qwen3-TTS voice cloning
let model = try await Qwen3TTSModel.fromPretrained()
let audio = model.synthesizeWithVoiceClone(
    text: "Hello in a cloned voice!",
    referenceAudio: refSamples,
    referenceSampleRate: 24000
)
import IndexTTS2TTS

// IndexTTS2 zero-shot voice cloning
let model = try await IndexTTS2TTSModel.fromPretrained()
let audio = try await model.generate(
    text: "Hello in a cloned voice!",
    referenceAudio: URL(fileURLWithPath: "reference.wav"),
    synthesisOptions: try IndexTTS2SynthesisOptions(
        speakingRate: 1.35,
        maxInternalPauseDuration: 0.05)
)

تخزين الصوت المرجعي مؤقتًا

تُخزِّن كلّ من synthesizeWithVoiceClone (x-vector) وsynthesizeWithVoiceCloneICL (ICL) المعالجة المسبقة لكلّ مرجع عبر الاستدعاءات على نفس نسخة النموذج. يُخزِّن مسار x-vector embedding المتحدّث الناتج عن ECAPA-TDNN؛ ويُخزِّن مسار ICL إضافةً إلى ذلك مخرجات encoder codec Mimi. الذاكرة المؤقّتة مفهرسة بالمحتوى (تجزئة العيّنات الخام + معدّل العيّنات) ومحدودة بسعة LRU صغيرة (4 مدخلات افتراضيًا)، بحيث تتخطّى التوليدات المتكرّرة على نفس الموجة المرجعية تمريرات mel وencoder دون نموّ غير محدود للذاكرة.

let tts = try await Qwen3TTSModel.fromPretrained()

// First call: runs ECAPA-TDNN, caches the embedding
_ = tts.synthesizeWithVoiceClone(text: "Hello", referenceAudio: ref, ...)

// Subsequent calls with the same reference: cache hit
_ = tts.synthesizeWithVoiceClone(text: "How are you?", referenceAudio: ref, ...)

// Explicit eviction (rarely needed — LRU handles capacity)
tts.clearReferenceAudioCache()