diff --git a/README.md b/README.md
index 725460ab..0b67840b 100644
--- a/README.md
+++ b/README.md
@@ -68,6 +68,7 @@ Unlike cloud services that lock your voice data behind subscriptions, Voicebox g
- **Model flexibility** — currently powered by Qwen3-TTS, with support for XTTS, Bark, and other models coming soon
- **API-first** — use the desktop app or integrate voice synthesis into your own projects
- **Native performance** — built with Tauri (Rust), not Electron
+- **Super fast on Mac** — MLX backend with native Metal acceleration for 4-5x faster inference on Apple Silicon
Download a voice model, clone any voice from a few seconds of audio, and compose multi-voice projects with studio-grade editing tools. No Python install required, no cloud dependency, no limits.
@@ -97,6 +98,7 @@ Powered by Alibaba's **Qwen3-TTS** — a breakthrough model that achieves near-p
- **Instant cloning** — Upload a sample, get a voice profile
- **High fidelity** — Natural prosody, emotion, and cadence
- **Multi-language** — English, Chinese, and more coming
+- **Lightning fast on Mac** — MLX backend leverages Apple Silicon's Neural Engine for super fast generation
### Voice Profile Management
diff --git a/app/src/components/VoiceProfiles/ProfileCard.tsx b/app/src/components/VoiceProfiles/ProfileCard.tsx
index ed46259b..e879294f 100644
--- a/app/src/components/VoiceProfiles/ProfileCard.tsx
+++ b/app/src/components/VoiceProfiles/ProfileCard.tsx
@@ -1,6 +1,5 @@
import { Download, Edit, Mic, Trash2 } from 'lucide-react';
import { useState } from 'react';
-import { useServerStore } from '@/stores/serverStore';
import { Badge } from '@/components/ui/badge';
import { Button } from '@/components/ui/button';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
@@ -16,6 +15,7 @@ import {
import type { VoiceProfileResponse } from '@/lib/api/types';
import { useDeleteProfile, useExportProfile } from '@/lib/hooks/useProfiles';
import { cn } from '@/lib/utils/cn';
+import { useServerStore } from '@/stores/serverStore';
import { useUIStore } from '@/stores/uiStore';
interface ProfileCardProps {
@@ -35,9 +35,7 @@ export function ProfileCard({ profile }: ProfileCardProps) {
const isSelected = selectedProfileId === profile.id;
- const avatarUrl = profile.avatar_path
- ? `${serverUrl}/profiles/${profile.id}/avatar`
- : null;
+ const avatarUrl = profile.avatar_path ? `${serverUrl}/profiles/${profile.id}/avatar` : null;
const handleSelect = () => {
setSelectedProfileId(isSelected ? null : profile.id);
@@ -81,7 +79,7 @@ export function ProfileCard({ profile }: ProfileCardProps) {
alt={`${profile.name} avatar`}
className={cn(
'h-full w-full object-cover transition-all duration-200',
- !isSelected && 'grayscale'
+ !isSelected && 'grayscale',
)}
onError={() => setAvatarError(true)}
/>
diff --git a/app/src/components/VoiceProfiles/ProfileForm.tsx b/app/src/components/VoiceProfiles/ProfileForm.tsx
index f7a84e15..90cc4fe0 100644
--- a/app/src/components/VoiceProfiles/ProfileForm.tsx
+++ b/app/src/components/VoiceProfiles/ProfileForm.tsx
@@ -45,8 +45,8 @@ import { useSystemAudioCapture } from '@/lib/hooks/useSystemAudioCapture';
import { useTranscription } from '@/lib/hooks/useTranscription';
import { isTauri } from '@/lib/tauri';
import { formatAudioDuration, getAudioDuration } from '@/lib/utils/audio';
-import { type ProfileFormDraft, useUIStore } from '@/stores/uiStore';
import { useServerStore } from '@/stores/serverStore';
+import { type ProfileFormDraft, useUIStore } from '@/stores/uiStore';
import { AudioSampleRecording } from './AudioSampleRecording';
import { AudioSampleSystem } from './AudioSampleSystem';
import { AudioSampleUpload } from './AudioSampleUpload';
@@ -427,7 +427,8 @@ export function ProfileForm() {
} catch (avatarError) {
toast({
title: 'Avatar upload failed',
- description: avatarError instanceof Error ? avatarError.message : 'Failed to upload avatar',
+ description:
+ avatarError instanceof Error ? avatarError.message : 'Failed to upload avatar',
variant: 'destructive',
});
}
@@ -520,7 +521,8 @@ export function ProfileForm() {
} catch (avatarError) {
toast({
title: 'Avatar upload failed',
- description: avatarError instanceof Error ? avatarError.message : 'Failed to upload avatar',
+ description:
+ avatarError instanceof Error ? avatarError.message : 'Failed to upload avatar',
variant: 'destructive',
});
}
diff --git a/backend/backends/mlx_backend.py b/backend/backends/mlx_backend.py
index 8eafc56e..7585c656 100644
--- a/backend/backends/mlx_backend.py
+++ b/backend/backends/mlx_backend.py
@@ -424,34 +424,35 @@ class MLXSTTBackend:
) -> str:
"""
Transcribe audio to text.
-
+
Args:
audio_path: Path to audio file
language: Optional language hint (en or zh)
-
+
Returns:
Transcribed text
"""
await self.load_model_async(None)
-
+
def _transcribe_sync():
"""Run synchronous transcription in thread pool."""
- # Load audio
- audio, sr = load_audio(audio_path, sample_rate=16000)
-
- # MLX Whisper transcription
- # The API may vary - check mlx-audio documentation
- # For now, assuming similar API to PyTorch Whisper
- result = self.model.transcribe(audio, language=language)
-
- # Extract text from result (format may vary)
+ # MLX Whisper transcription using generate method
+ # The generate method accepts audio path directly
+ decode_options = {}
+ if language:
+ decode_options["language"] = language
+
+ result = self.model.generate(str(audio_path), **decode_options)
+
+ # Extract text from result
if isinstance(result, str):
return result.strip()
elif isinstance(result, dict):
return result.get("text", "").strip()
+ elif hasattr(result, "text"):
+ return result.text.strip()
else:
- # Try to get text attribute
return str(result).strip()
-
+
# Run blocking transcription in thread pool
return await asyncio.to_thread(_transcribe_sync)
diff --git a/landing/src/app/page.tsx b/landing/src/app/page.tsx
index be4869f3..46cf3556 100644
--- a/landing/src/app/page.tsx
+++ b/landing/src/app/page.tsx
@@ -5,7 +5,7 @@ import Image from 'next/image';
import { useEffect, useState } from 'react';
import { AppleIcon, LinuxIcon, WindowsIcon } from '@/components/PlatformIcons';
import { Button } from '@/components/ui/button';
-import { Section, SectionTitle } from '@/components/ui/section';
+import { Section } from '@/components/ui/section';
import { DOWNLOAD_LINKS, GITHUB_REPO } from '@/lib/constants';
import type { DownloadLinks } from '@/lib/releases';
import { FeatureCard } from '../components/ui/feature-card';
@@ -39,17 +39,19 @@ export default function Home() {
"Powered by Alibaba's Qwen3-TTS model for exceptional voice quality and accuracy.",
icon: ,
+ },
{
title: 'Multi-Sample Support',
description:
'Combine multiple voice samples for higher quality and more natural-sounding results.',
icon: ,
},
- {
- title: 'Smart Caching',
- description: 'Instant re-generation with voice prompt caching. No need to reprocess samples.',
- icon:
+ Optimized for performance with Metal acceleration on Mac and{' '} + CUDA acceleration on Windows/Linux for fast, local inference. +
No Python install required.
@@ -281,10 +287,6 @@ export default function Home() { {/* Features Section */}- Everything you need for professional voice cloning in a desktop app. -