Клонирование голоса

Клонируйте любой голос по короткому референсному аудио. IndexTTS2 добавляет нативное MLX zero-shot клонирование с управлением эмоцией, темпом и паузами наряду с клонированием через спикер-энкодеры Qwen3-TTS и CosyVoice3.

Как это работает

  1. Запишите или предоставьте референсный аудиосэмпл целевого голоса
  2. Извлечение эмбеддинга спикера — speaker-энкодер обрабатывает референсное аудио в вектор эмбеддинга фиксированной размерности
  3. Внедрение эмбеддинга — эмбеддинг спикера обуславливает TTS-модель во время синтеза
  4. Синтез речи — TTS-модель генерирует речь, соответствующую вокальным характеристикам референса

Движки

Клонирование голоса доступно с обоими TTS-движками. Каждый использует свой speaker-энкодер:

ДвижокSpeaker-энкодерЭмбеддингБэкенд
Qwen3-TTSECAPA-TDNN1024-мерный x-vectorMLX (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 или вектор, настройку скорости речи и ограничение внутренних пауз. Сейчас работает только в batch-синтезе.

CosyVoice3 + CAM++

CosyVoice3 использует speaker-энкодер CAM++ (Context-Aware Masking++) из проекта 3D-Speaker от Alibaba. 192-мерный эмбеддинг обуславливает flow-модель DiT через аффинный проекционный слой (192 → 80), который обучен совместно с CosyVoice3.

Архитектура CAM++

ЭтапОписание
FCMFront-end свёрточный модуль (Conv2d + 2 ResBlocks, 32 канала)
TDNNTime Delay Neural Network (320 в 128 каналов, kernel size 5)
D-TDNN-блоки3 плотно связанных блока (12/24/16 слоёв) с context-aware masking
Stats PoolПулинг среднего + стандартного отклонения (глобальная статистика)
DenseЛинейная проекция в 192-мерный эмбеддинг

CoreML-модель (~14 МБ, FP16) работает на Neural Engine. Она автоматически скачивается из aufklarer/CamPlusPlus-Speaker-CoreML при первом использовании.

Клонирование голоса в Qwen3-TTS

Qwen3-TTS поддерживает два режима клонирования голоса:

Режим ICL (рекомендуется)

Режим In-Context Learning кодирует референсное аудио в codec-токены через энкодер Mimi speech-токенизатора и добавляет их перед референсным транскриптом. Это даёт модели полный акустический контекст — выше качество и надёжный 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

Использует энкодер ECAPA-TDNN, выдающий 1024-мерный x-vector. Транскрипт не нужен, но качество ниже. Может не выдать EOS на коротких текстах или некоторых языках.

Архитектура ECAPA-TDNN

ЭтапОписание
TDNNTime Delay Neural Network (128 в 512 каналов, kernel size 5)
SE-Res2Net-блоки3 блока со Squeeze-and-Excitation (512 каналов, dilation 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 поддерживает многоспикерный диалог с клонированием голоса для каждого спикера. Используйте флаг --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

Референсное аудио каждого спикера обрабатывается через энкодер CAM++ для извлечения 192-мерного эмбеддинга. Модель загружается один раз и переиспользуется для всех спикеров. См. руководство CosyVoice3 для полных деталей по синтаксису диалога и тегам эмоций.

Советы по референсному аудио

Важно

Для Qwen3-TTS клонирование голоса работает только с моделью base, а не с customVoice. Клонирование CosyVoice3 работает с моделью по умолчанию. IndexTTS2 требует --voice-sample, сейчас работает только в batch-синтезе и принимает опциональные настройки эмоции/стиля.

Swift API

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

Reference Audio Caching

Both synthesizeWithVoiceClone (x-vector) and synthesizeWithVoiceCloneICL (ICL) cache their per-reference preprocessing across calls on the same model instance. The x-vector path caches the ECAPA-TDNN speaker embedding; the ICL path additionally caches the Mimi codec encoder output. The cache is content-addressed (hash of raw samples + sample rate) and bounded to a small LRU (default 4 entries), so repeated generations against the same reference waveform skip the mel + encoder passes without unbounded memory growth.

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