Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ jobs:
pip install -r backend/requirements.txt
pip install --no-deps chatterbox-tts
pip install --no-deps hume-tada
pip install --no-deps "voxcpm>=2.0.3,<2.1"
pip install --no-deps omnivoice

- name: Install MLX dependencies (Apple Silicon only)
Expand Down Expand Up @@ -294,6 +295,7 @@ jobs:
pip install -r backend/requirements.txt
pip install --no-deps chatterbox-tts
pip install --no-deps hume-tada
pip install --no-deps "voxcpm>=2.0.3,<2.1"
pip install --no-deps omnivoice

- name: Install PyTorch with CUDA 12.8
Expand Down Expand Up @@ -457,6 +459,7 @@ jobs:
pip install -r backend/requirements.txt
pip install --no-deps chatterbox-tts
pip install --no-deps hume-tada
pip install --no-deps "voxcpm>=2.0.3,<2.1"
pip install --no-deps omnivoice

- name: Build ROCm server binary (onedir)
Expand Down
1 change: 1 addition & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ RUN if [ "$PYTORCH_VARIANT" = "rocm" ]; then \
RUN pip install --no-cache-dir --prefix=/install -r requirements.txt
RUN pip install --no-cache-dir --prefix=/install --no-deps chatterbox-tts
RUN pip install --no-cache-dir --prefix=/install --no-deps hume-tada
RUN pip install --no-cache-dir --prefix=/install --no-deps "voxcpm>=2.0.3,<2.1"
RUN pip install --no-cache-dir --prefix=/install --no-deps omnivoice
RUN pip install --no-cache-dir --prefix=/install \
git+https://github.com/QwenLM/Qwen3-TTS.git
Expand Down
16 changes: 9 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,16 +87,16 @@ Voicebox is built and maintained with [Capy](https://capy.ai?utm_source=voicebox

## What is Voicebox?

Voicebox is a **local-first AI voice studio** — a free and open-source alternative to **ElevenLabs** and **WisprFlow** in one app. Clone voices from a few seconds of audio, generate speech in 23 languages across 8 TTS engines, dictate into any text field with a global hotkey, and give any MCP-aware AI agent a voice of your choosing.
Voicebox is a **local-first AI voice studio** — a free and open-source alternative to **ElevenLabs** and **WisprFlow** in one app. Clone voices from a few seconds of audio, generate speech in 30 languages across 10 TTS engines, dictate into any text field with a global hotkey, and give any MCP-aware AI agent a voice of your choosing.

The two cloud incumbents sit on opposite halves of the voice I/O loop — ElevenLabs on output, WisprFlow on input. Voicebox does both, bridges them with a bundled local LLM for refinement and per-profile personas, and runs the whole thing on your machine.

- **Complete privacy** — models, voice data, and captures never leave your machine
- **8 TTS engines** — Qwen3-TTS, Qwen CustomVoice, Qwen VoiceDesign, LuxTTS, Chatterbox Multilingual, Chatterbox Turbo, HumeAI TADA, and Kokoro
- **10 TTS engines** — Qwen3-TTS, Qwen CustomVoice, Qwen VoiceDesign, LuxTTS, Chatterbox Multilingual, Chatterbox Turbo, HumeAI TADA, Kokoro, OmniVoice (non-commercial), and VoxCPM2
- **Voice cloning and preset voices** — zero-shot cloning from a reference sample, or 50+ curated preset voices via Kokoro and Qwen CustomVoice
- **23 languages** — from English to Arabic, Japanese, Hindi, Swahili, and more
- **30 languages** — from English to Arabic, Japanese, Hindi, Thai, Vietnamese, and more
- **Post-processing effects** — pitch shift, reverb, delay, chorus, compression, and filters
- **Expressive speech** — paralinguistic tags like `[laugh]`, `[sigh]`, `[gasp]` via Chatterbox Turbo; natural-language delivery control via Qwen CustomVoice; voices described in plain language via Qwen VoiceDesign
- **Expressive speech** — paralinguistic tags like `[laugh]`, `[sigh]`, `[gasp]` via Chatterbox Turbo; natural-language delivery control via Qwen CustomVoice and VoxCPM2; voices described in plain language via Qwen VoiceDesign
- **Unlimited length** — auto-chunking with crossfade for scripts, articles, and chapters
- **Stories editor** — multi-track timeline for conversations, podcasts, and narratives
- **Voice input** — global dictation hotkey with push-to-talk and toggle modes, accessibility-verified auto-paste on macOS, in-app mic on every text field, Whisper-based STT
Expand Down Expand Up @@ -129,7 +129,7 @@ The two cloud incumbents sit on opposite halves of the voice I/O loop — Eleven

### Multi-Engine Voice Cloning

Eight TTS engines with different strengths, switchable per-generation:
Ten TTS engines with different strengths, switchable per-generation:

| Engine | Languages | Strengths |
| --------------------------- | --------- | ---------------------------------------------------------------------------------------------------------------------------------------- |
Expand All @@ -141,11 +141,13 @@ Eight TTS engines with different strengths, switchable per-generation:
| **Chatterbox Turbo** | English | Fast 350M model with paralinguistic emotion/sound tags |
| **TADA** (1B / 3B) | 10 | HumeAI speech-language model — 700s+ coherent audio, text-acoustic dual alignment |
| **Kokoro** | 8 | 50 curated preset voices, tiny 82M model, fast CPU inference |
| **VoxCPM2** | 30 | 2B multilingual model, 48kHz output, high-fidelity reference-audio cloning; CUDA or CPU fallback |
| **OmniVoice** | 23 exposed | Multilingual cloning and attribute instructions; CC-BY-NC weights, non-commercial use only |

### Emotions & Paralinguistic Tags

Only **Chatterbox Turbo** interprets paralinguistic tags like `[laugh]` and
`[sigh]`. Qwen3-TTS, LuxTTS, Chatterbox Multilingual, and HumeAI TADA read them
`[sigh]`. Qwen3-TTS, LuxTTS, Chatterbox Multilingual, HumeAI TADA, and VoxCPM2 read them
literally as text.

With **Chatterbox Turbo** selected, type `/` in the text input to open the tag
Expand Down Expand Up @@ -393,7 +395,7 @@ Full API documentation available at `http://127.0.0.1:17493/docs`.
| Frontend | React, TypeScript, Tailwind CSS |
| State | Zustand, React Query |
| Backend | FastAPI (Python) |
| TTS Engines | Qwen3-TTS, Qwen CustomVoice, Qwen VoiceDesign, LuxTTS, Chatterbox, Chatterbox Turbo, TADA, Kokoro |
| TTS Engines | Qwen3-TTS, Qwen CustomVoice, Qwen VoiceDesign, LuxTTS, Chatterbox, Chatterbox Turbo, TADA, Kokoro, OmniVoice (non-commercial), VoxCPM2 |
| STT | Whisper / Whisper Turbo (PyTorch or MLX) |
| Local LLM | Qwen3 (0.6B / 1.7B / 4B), shared runtime with TTS / STT |
| MCP Server | FastMCP mounted at `/mcp` (Streamable HTTP) + bundled stdio shim binary |
Expand Down
2 changes: 1 addition & 1 deletion app/src/components/CapturesTab/CapturesTab.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -269,7 +269,7 @@ export function CapturesTab() {
// override fall through to whatever the backend picks.
const engine = voice.default_engine as
| 'qwen' | 'qwen_custom_voice' | 'qwen_voice_design' | 'luxtts' | 'chatterbox'
| 'chatterbox_turbo' | 'tada' | 'kokoro' | 'omnivoice'
| 'chatterbox_turbo' | 'tada' | 'kokoro' | 'omnivoice' | 'voxcpm2'
| undefined;
return apiClient.generateSpeech({
profile_id: voice.id,
Expand Down
3 changes: 3 additions & 0 deletions app/src/components/Generation/EngineModelSelector.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ const ENGINE_OPTIONS = [
{ value: 'tada:3B', label: 'TADA 3B Multilingual', engine: 'tada' },
{ value: 'kokoro', label: 'Kokoro 82M', engine: 'kokoro' },
{ value: 'omnivoice', label: 'OmniVoice (non-commercial)', engine: 'omnivoice' },
{ value: 'voxcpm2', label: 'VoxCPM2', engine: 'voxcpm2' },
] as const;

const ENGINE_DESCRIPTIONS: Record<string, string> = {
Expand All @@ -41,6 +42,7 @@ const ENGINE_DESCRIPTIONS: Record<string, string> = {
tada: 'HumeAI, 700s+ coherent audio',
kokoro: '82M params, CPU realtime, 8 langs',
omnivoice: '23 languages, voice design. CC-BY-NC weights: non-commercial use only',
voxcpm2: '2B params, 48kHz, multilingual cloning',
};

/** Engines that only support English and should force language to 'en' on select. */
Expand All @@ -54,6 +56,7 @@ const CLONING_ENGINES = new Set([
'chatterbox_turbo',
'tada',
'omnivoice',
'voxcpm2',
]);

/** Engines that synthesise a voice from a written description. */
Expand Down
3 changes: 2 additions & 1 deletion app/src/components/Generation/FloatingGenerateBox.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,8 @@ export function FloatingGenerateBox({
| 'kokoro'
| 'qwen_custom_voice'
| 'qwen_voice_design'
| 'omnivoice';
| 'omnivoice'
| 'voxcpm2';
useEffect(() => {
if (selectedProfile?.language) {
form.setValue('language', selectedProfile.language as LanguageCode);
Expand Down
5 changes: 4 additions & 1 deletion app/src/components/ServerSettings/ModelManagement.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,8 @@ const MODEL_DESCRIPTIONS: Record<string, string> = {
'Kokoro 82M by hexgrad. Tiny 82M-parameter TTS that runs at CPU realtime. Supports 8 languages with pre-built voice styles. Apache 2.0 licensed.',
omnivoice:
'OmniVoice by k2-fsa. Zero-shot voice cloning via a diffusion language model, plus attribute-based voice design (gender, age, pitch, accent). The model covers 600+ languages; Voicebox exposes the 23 in its own language list. 24kHz output. NON-COMMERCIAL: the weights are CC-BY-NC (code is Apache 2.0; the bundled Higgs Audio V2 codec is under the Boson community licence), so do not use output commercially.',
voxcpm2:
'VoxCPM2 by OpenBMB. A 2B-parameter multilingual TTS model with 48kHz output and high-fidelity reference-audio voice cloning.',
'qwen-custom-voice-1.7B':
'Qwen3-TTS CustomVoice 1.7B by Alibaba. 9 premium preset voices with instruct-based style control for tone, emotion, and prosody. Supports 10 languages.',
'qwen-custom-voice-0.6B':
Expand Down Expand Up @@ -420,7 +422,8 @@ export function ModelManagement() {
m.model_name.startsWith('chatterbox') ||
m.model_name.startsWith('tada') ||
m.model_name.startsWith('kokoro') ||
m.model_name.startsWith('omnivoice'),
m.model_name.startsWith('omnivoice') ||
m.model_name.startsWith('voxcpm2'),
) ?? [];
const whisperModels = modelStatus?.models.filter((m) => m.model_name.startsWith('whisper')) ?? [];
const llmModels = modelStatus?.models.filter((m) => m.model_name.startsWith('qwen3-')) ?? [];
Expand Down
1 change: 1 addition & 0 deletions app/src/components/VoiceProfiles/ProfileForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ const DEFAULT_ENGINE_OPTIONS = [
{ value: 'tada', label: 'TADA' },
{ value: 'kokoro', label: 'Kokoro 82M' },
{ value: 'omnivoice', label: 'OmniVoice (non-commercial)' },
{ value: 'voxcpm2', label: 'VoxCPM2' },
] as const;

function makeProfileSchema(t: (key: string) => string) {
Expand Down
2 changes: 1 addition & 1 deletion app/src/i18n/locales/en/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -890,7 +890,7 @@
"body": "A few seconds of reference audio is enough. Multi-sample support for higher quality when you want it."
},
"engines": {
"title": "Seven engines, 23 languages.",
"title": "Ten engines, 30 languages.",
"body": "Pick the tradeoff that fits — quality, speed, or multilingual coverage."
},
"agentReady": {
Expand Down
2 changes: 1 addition & 1 deletion app/src/i18n/locales/es/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -889,7 +889,7 @@
"body": "Bastan unos segundos de audio de referencia. Compatible con varias muestras para mayor calidad cuando lo necesites."
},
"engines": {
"title": "Siete motores, 23 idiomas.",
"title": "Diez motores, 30 idiomas.",
"body": "Elige el equilibrio que encaje: calidad, velocidad o cobertura multilingüe."
},
"agentReady": {
Expand Down
2 changes: 1 addition & 1 deletion app/src/i18n/locales/fr/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -889,7 +889,7 @@
"body": "Quelques secondes d'audio de référence suffisent. Prise en charge multi-échantillons pour une qualité supérieure quand vous le souhaitez."
},
"engines": {
"title": "Sept moteurs, 23 langues.",
"title": "Dix moteurs, 30 langues.",
"body": "Choisissez le compromis qui vous convient — qualité, vitesse ou couverture multilingue."
},
"agentReady": {
Expand Down
2 changes: 1 addition & 1 deletion app/src/i18n/locales/it/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -889,7 +889,7 @@
"body": "Bastano pochi secondi di audio di riferimento. Supporto multi-campione per una qualità superiore quando ne hai bisogno."
},
"engines": {
"title": "Sette motori, 23 lingue.",
"title": "Dieci motori, 30 lingue.",
"body": "Scegli il compromesso più adatto alle tue esigenze tra qualità, velocità o copertura multilingue."
},
"agentReady": {
Expand Down
2 changes: 1 addition & 1 deletion app/src/i18n/locales/ja/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -889,7 +889,7 @@
"body": "数秒のリファレンス音声があれば十分です。より高い品質を求めるときは複数サンプルにも対応します。"
},
"engines": {
"title": "7 つのエンジン、23 言語。",
"title": "10 のエンジン、30 言語。",
"body": "品質、速度、多言語対応 — 用途に合ったトレードオフを選べます。"
},
"agentReady": {
Expand Down
2 changes: 1 addition & 1 deletion app/src/i18n/locales/ko/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -889,7 +889,7 @@
"body": "몇 초의 참조 오디오면 충분합니다. 원할 경우 여러 샘플로 더 높은 품질 지원."
},
"engines": {
"title": "7개 엔진, 23개 언어.",
"title": "10개 엔진, 30개 언어.",
"body": "품질, 속도, 다국어 지원 중 원하는 트레이드오프를 선택하세요."
},
"agentReady": {
Expand Down
2 changes: 1 addition & 1 deletion app/src/i18n/locales/pt-BR/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -889,7 +889,7 @@
"body": "Alguns segundos de áudio de referência já bastam. Suporte a múltiplas amostras para maior qualidade quando você quiser."
},
"engines": {
"title": "Sete motores, 23 idiomas.",
"title": "Dez motores, 30 idiomas.",
"body": "Escolha o equilíbrio ideal — qualidade, velocidade ou cobertura multilíngue."
},
"agentReady": {
Expand Down
2 changes: 1 addition & 1 deletion app/src/i18n/locales/zh-CN/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -889,7 +889,7 @@
"body": "几秒钟的参考音频就足够了。需要更高质量时,支持多样本克隆。"
},
"engines": {
"title": "七种引擎,23 种语言。",
"title": "十种引擎,30 种语言。",
"body": "选择最合适的取舍——质量、速度,或多语言覆盖。"
},
"agentReady": {
Expand Down
2 changes: 1 addition & 1 deletion app/src/i18n/locales/zh-TW/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -889,7 +889,7 @@
"body": "幾秒鐘的參考音訊就夠了。需要更高品質時也支援多樣本。"
},
"engines": {
"title": "七種引擎、23 種語言。",
"title": "十種引擎、30 種語言。",
"body": "選擇最符合需求的取捨——品質、速度,或多語言覆蓋。"
},
"agentReady": {
Expand Down
3 changes: 2 additions & 1 deletion app/src/lib/api/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,8 @@ export interface GenerationRequest {
| 'chatterbox_turbo'
| 'tada'
| 'kokoro'
| 'omnivoice';
| 'omnivoice'
| 'voxcpm2';
instruct?: string;
/** When true and the profile has a personality prompt, input text is rewritten in-character before TTS. */
personality?: boolean;
Expand Down
4 changes: 3 additions & 1 deletion app/src/lib/constants/engines.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,14 @@
* VoiceDesign take free-form prose ("speak in an angry tone"; VoiceDesign
* layers it on top of the profile's stored design prompt), while OmniVoice
* takes a closed vocabulary of attributes ("female, low pitch, british
* accent") and raises on anything outside it.
* accent") and raises on anything outside it. VoxCPM2 maps free-form prose
* to a parenthesized delivery prefix in the backend.
*/
export const INSTRUCT_ENGINES: ReadonlySet<string> = new Set([
'qwen_custom_voice',
'qwen_voice_design',
'omnivoice',
'voxcpm2',
]);

/** Whether the given engine should show and forward the instruct field. */
Expand Down
43 changes: 41 additions & 2 deletions app/src/lib/constants/languages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
* Chatterbox Multilingual supports 23 languages.
* Chatterbox Turbo is English-only.
* Kokoro supports 8 languages.
* VoxCPM2 supports 30 languages.
*/

/** All languages that any engine supports. */
Expand All @@ -20,18 +21,25 @@ export const ALL_LANGUAGES = {
fr: 'French',
he: 'Hebrew',
hi: 'Hindi',
id: 'Indonesian',
it: 'Italian',
ja: 'Japanese',
km: 'Khmer',
ko: 'Korean',
lo: 'Lao',
ms: 'Malay',
my: 'Burmese',
nl: 'Dutch',
no: 'Norwegian',
pl: 'Polish',
pt: 'Portuguese',
ru: 'Russian',
sv: 'Swedish',
sw: 'Swahili',
th: 'Thai',
tl: 'Tagalog',
tr: 'Turkish',
vi: 'Vietnamese',
zh: 'Chinese',
} as const;

Expand Down Expand Up @@ -71,8 +79,7 @@ export const ENGINE_LANGUAGES: Record<string, readonly LanguageCode[]> = {
kokoro: ['en', 'es', 'fr', 'hi', 'it', 'pt', 'ja', 'zh'],
qwen_custom_voice: ['zh', 'en', 'ja', 'ko', 'de', 'fr', 'ru', 'pt', 'es', 'it'],
qwen_voice_design: ['zh', 'en', 'ja', 'ko', 'de', 'fr', 'ru', 'pt', 'es', 'it'],
// OmniVoice covers 600+ languages. Listed here are the ones ALL_LANGUAGES
// already knows -- widening that list is a separate change.
// Keep OmniVoice's validated 23-language subset; VoxCPM2 adds seven more below.
omnivoice: [
'ar',
'da',
Expand All @@ -98,6 +105,38 @@ export const ENGINE_LANGUAGES: Record<string, readonly LanguageCode[]> = {
'tr',
'zh',
],
voxcpm2: [
'ar',
'da',
'de',
'el',
'en',
'es',
'fi',
'fr',
'he',
'hi',
'id',
'it',
'ja',
'km',
'ko',
'lo',
'ms',
'my',
'nl',
'no',
'pl',
'pt',
'ru',
'sv',
'sw',
'th',
'tl',
'tr',
'vi',
'zh',
],
} as const;

/** Helper: get language options for a given engine. */
Expand Down
25 changes: 15 additions & 10 deletions app/src/lib/hooks/useGenerationForm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ const generationSchema = z.object({
'tada',
'kokoro',
'omnivoice',
'voxcpm2',
])
.optional(),
personality: z.boolean().optional(),
Expand Down Expand Up @@ -107,9 +108,11 @@ export function useGenerationForm(options: UseGenerationFormOptions = {}) {
? 'qwen-voice-design-1.7B'
: engine === 'omnivoice'
? 'omnivoice'
: engine === 'qwen_custom_voice'
? `qwen-custom-voice-${data.modelSize}`
: `qwen-tts-${data.modelSize}`;
: engine === 'voxcpm2'
? 'voxcpm2'
: engine === 'qwen_custom_voice'
? `qwen-custom-voice-${data.modelSize}`
: `qwen-tts-${data.modelSize}`;
const displayName =
engine === 'luxtts'
? 'LuxTTS'
Expand All @@ -127,13 +130,15 @@ export function useGenerationForm(options: UseGenerationFormOptions = {}) {
? 'Qwen VoiceDesign 1.7B'
: engine === 'omnivoice'
? 'OmniVoice'
: engine === 'qwen_custom_voice'
? data.modelSize === '1.7B'
? 'Qwen CustomVoice 1.7B'
: 'Qwen CustomVoice 0.6B'
: data.modelSize === '1.7B'
? 'Qwen TTS 1.7B'
: 'Qwen TTS 0.6B';
: engine === 'voxcpm2'
? 'VoxCPM2'
: engine === 'qwen_custom_voice'
? data.modelSize === '1.7B'
? 'Qwen CustomVoice 1.7B'
: 'Qwen CustomVoice 0.6B'
: data.modelSize === '1.7B'
? 'Qwen TTS 1.7B'
: 'Qwen TTS 0.6B';

// Check if model needs downloading
try {
Expand Down
1 change: 1 addition & 0 deletions app/src/lib/utils/format.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ const ENGINE_DISPLAY_NAMES: Record<string, string> = {
chatterbox_turbo: 'Chatterbox Turbo',
tada: 'TADA',
kokoro: 'Kokoro 82M',
voxcpm2: 'VoxCPM2',
};

export function formatEngineName(engine?: string, modelSize?: string): string {
Expand Down
Loading