mirror of
https://github.com/jamiepine/voicebox.git
synced 2026-09-16 13:20:39 -07:00
1216 lines
32 KiB
Markdown
1216 lines
32 KiB
Markdown
# Qwen3-TTS Implementation Analysis
|
|
|
|
Comprehensive analysis of five existing Qwen3-TTS implementations to inform the architecture of voicebox.
|
|
|
|
---
|
|
|
|
## Projects Analyzed
|
|
|
|
1. **voice** - Rust CLI with Python backend
|
|
2. **Voice-Clone-Studio** - Feature-rich Gradio app
|
|
3. **Qwen3-TTS_server** - FastAPI REST API wrapper
|
|
4. **mimic** - Web app with audio studio (conceptually the best)
|
|
5. **qwen3-tts-enhanced** - Production-quality single-file Gradio app
|
|
|
|
---
|
|
|
|
## 1. voice (Rust CLI)
|
|
|
|
**Repository:** `/Users/jamespine/Projects/voice`
|
|
|
|
### Architecture
|
|
|
|
**Dual-mode design:**
|
|
- One-shot mode: Single generation via CLI args
|
|
- Server mode: Long-running HTTP server (port 3000)
|
|
|
|
**Language split:**
|
|
- Rust: CLI, HTTP server, IPC orchestration
|
|
- Python: TTS inference, model management
|
|
|
|
**IPC Pattern:**
|
|
```
|
|
Rust → spawn Python subprocess → JSON over stdin/stdout → Rust
|
|
```
|
|
|
|
### Python Backend (`tts.py`)
|
|
|
|
**Model Management:**
|
|
- Lazy loading via `get_tts_model()`
|
|
- Qwen3-TTS CustomVoice models (0.6B/1.7B)
|
|
- `torch_dtype=torch.bfloat16` for VRAM efficiency
|
|
- Flash Attention 2 with SDPA fallback
|
|
|
|
**Voice Profile System:**
|
|
- Profiles stored in `~/.config/voice/profiles/`
|
|
- Each profile: `audio.wav` + `reference.txt`
|
|
- Automatic voice prompt creation and caching
|
|
- Hash-based cache invalidation (MD5 of audio + text)
|
|
|
|
**Generation Pipeline:**
|
|
```python
|
|
1. Load model (lazy, cached)
|
|
2. Get voice prompt (cached if available)
|
|
3. Set seed for reproducibility
|
|
4. model.generate_voice_clone(text, language, voice_prompt)
|
|
5. Save to temp file
|
|
6. Return path as JSON
|
|
```
|
|
|
|
**Special Features:**
|
|
- M3GAN voice effect (pitch shift + formant preservation)
|
|
- Uses `librosa` + `soundfile` for audio processing
|
|
- Voice profile listing with metadata
|
|
|
|
### Rust Frontend
|
|
|
|
**HTTP Server (`main.rs`):**
|
|
- Axum web framework
|
|
- Single endpoint: `POST /generate`
|
|
- JSON request/response
|
|
- Spawns Python subprocess per request
|
|
|
|
**CLI (`cli.rs`):**
|
|
- Clap for argument parsing
|
|
- Commands: `generate`, `server`, `list-voices`
|
|
- Profile management integrated
|
|
|
|
**Process Management:**
|
|
```rust
|
|
let mut child = Command::new("python3")
|
|
.arg("tts.py")
|
|
.stdin(Stdio::piped())
|
|
.stdout(Stdio::piped())
|
|
.spawn()?;
|
|
```
|
|
|
|
### Strengths
|
|
|
|
1. **Clean separation of concerns** - Rust for I/O, Python for ML
|
|
2. **Dual-mode flexibility** - CLI and server in one binary
|
|
3. **Voice profile abstraction** - Easy to add/manage voices
|
|
4. **M3GAN effect** - Unique feature, well-implemented
|
|
5. **Good error handling** - Rust's `Result` type enforced
|
|
|
|
### Weaknesses
|
|
|
|
1. **Synchronous inference** - Blocks during generation
|
|
2. **No concurrent requests** - Server spawns subprocess per request
|
|
3. **Limited caching** - Only voice prompts, not models
|
|
4. **No generation history** - Fire and forget
|
|
5. **Basic HTTP API** - No auth, rate limiting, or WebSocket streaming
|
|
|
|
### Key Learnings
|
|
|
|
- IPC via JSON stdin/stdout is simple and works
|
|
- Voice profile pattern is user-friendly
|
|
- For Tauri, we can skip the IPC layer and use direct HTTP/IPC channels
|
|
- M3GAN effect is a differentiator worth preserving
|
|
|
|
---
|
|
|
|
## 2. Voice-Clone-Studio
|
|
|
|
**Repository:** `/Users/jamespine/Projects/Voice-Clone-Studio`
|
|
|
|
### Architecture
|
|
|
|
**Monolithic Gradio App:**
|
|
- Single file: `voice_clone_studio.py` (2,815 lines)
|
|
- Gradio for web UI
|
|
- Global state for model management
|
|
- Tab-based organization (6 tabs)
|
|
|
|
### Model Support
|
|
|
|
**Dual Engine:**
|
|
1. **Qwen3-TTS** - Base, CustomVoice, VoiceDesign
|
|
2. **VibeVoice TTS** - 1.5B/Large, multi-speaker
|
|
|
|
**Model sizes:**
|
|
- Qwen Small: 0.6B (~1GB VRAM)
|
|
- Qwen Large: 1.7B (~3GB VRAM)
|
|
- VibeVoice Small: 1.5B (~3GB VRAM)
|
|
- VibeVoice Large: ~6GB VRAM
|
|
- VibeVoice ASR: 7B (~14GB VRAM)
|
|
|
|
### Voice Prompt Caching
|
|
|
|
**Smart caching system:**
|
|
```python
|
|
def get_or_create_voice_prompt(audio_path, reference_text):
|
|
cache_key = hashlib.md5(audio_bytes + text.encode()).hexdigest()
|
|
|
|
# Check in-memory cache
|
|
if cache_key in _voice_prompt_cache:
|
|
return _voice_prompt_cache[cache_key]
|
|
|
|
# Check disk cache
|
|
prompt_file = f"{audio_path}.{cache_key}.prompt"
|
|
if os.path.exists(prompt_file):
|
|
prompt = torch.load(prompt_file)
|
|
_voice_prompt_cache[cache_key] = prompt
|
|
return prompt
|
|
|
|
# Create new
|
|
prompt = model.create_voice_clone_prompt(audio, text)
|
|
torch.save(prompt, prompt_file)
|
|
_voice_prompt_cache[cache_key] = prompt
|
|
return prompt
|
|
```
|
|
|
|
**Cache invalidation:**
|
|
- Hash changes if audio or text changes
|
|
- Orphaned `.prompt` files cleaned up on demand
|
|
|
|
### Features
|
|
|
|
**Tab 1: Voice Clone**
|
|
- Clone from samples with Qwen or VibeVoice
|
|
- Sample selection dropdown
|
|
- Language selection (en/zh)
|
|
- Seed control for reproducibility
|
|
- Model size selection
|
|
|
|
**Tab 2: Conversation**
|
|
- Multi-speaker dialogue generation
|
|
- Script format: `[1]: Text`, `[2]: Text`
|
|
- Automatic speaker assignment
|
|
- Pause duration control between speakers
|
|
|
|
**Tab 3: Voice Presets**
|
|
- 9 pre-built Qwen speakers
|
|
- Style control (narrative, conversational, etc.)
|
|
- No sample needed
|
|
|
|
**Tab 4: Voice Design**
|
|
- Generate voices from text descriptions
|
|
- "Young female, energetic, bright tone"
|
|
- Qwen VoiceDesign model (1.7B only)
|
|
|
|
**Tab 5: Prep Samples**
|
|
- Audio/video file upload
|
|
- Auto-transcription (Whisper or VibeVoice ASR)
|
|
- Audio editing (trim, normalize, mono conversion)
|
|
- Save as voice sample
|
|
|
|
**Tab 6: Output History**
|
|
- Browse generated files
|
|
- Metadata display (timestamp, seed, engine, text)
|
|
- Re-generate from metadata
|
|
|
|
### VRAM Management
|
|
|
|
**Lazy loading + mutual exclusion:**
|
|
```python
|
|
def get_tts_model():
|
|
global _tts_model, _whisper_model, _vibe_voice_model
|
|
|
|
# Unload ASR to free VRAM
|
|
if _whisper_model:
|
|
del _whisper_model
|
|
_whisper_model = None
|
|
if _vibe_voice_model:
|
|
del _vibe_voice_model
|
|
_vibe_voice_model = None
|
|
|
|
torch.cuda.empty_cache()
|
|
|
|
if not _tts_model:
|
|
_tts_model = load_model(...)
|
|
|
|
return _tts_model
|
|
```
|
|
|
|
### Audio Processing
|
|
|
|
**Pipeline:**
|
|
1. **Input normalization:**
|
|
- Peak normalization to [-1, 1]
|
|
- 0.85 scaling (15% headroom)
|
|
- Stereo → mono (average channels)
|
|
|
|
2. **Video extraction:**
|
|
- ffmpeg subprocess for audio extraction
|
|
- 24kHz mono output
|
|
|
|
3. **Transcription:**
|
|
- Whisper medium model
|
|
- VibeVoice ASR with speaker diarization
|
|
- Auto-cleans `[Speaker X]:` labels
|
|
|
|
4. **Generation:**
|
|
- Seed control via `torch.manual_seed()`
|
|
- bfloat16 inference
|
|
- Direct .wav output (soundfile)
|
|
|
|
### Strengths
|
|
|
|
1. **Voice prompt caching** - Brilliant UX (⚡ cached indicator)
|
|
2. **Dual engine support** - Qwen + VibeVoice flexibility
|
|
3. **Feature-rich** - Voice design, presets, conversations, long-form
|
|
4. **VRAM efficiency** - Smart loading/unloading
|
|
5. **Video support** - Extract audio from video files
|
|
6. **Metadata tracking** - Every output has reproducibility data
|
|
7. **Sample management** - Integrated prep workspace
|
|
|
|
### Weaknesses
|
|
|
|
1. **2,815-line single file** - Impossible to maintain
|
|
2. **Global state everywhere** - Testing nightmare
|
|
3. **Duplicated code** - 5 model loaders with identical fallback logic
|
|
4. **No separation of concerns** - UI + logic + audio all mixed
|
|
5. **No concurrency** - All operations block UI
|
|
6. **No error recovery** - Generic error messages
|
|
7. **Hardcoded parameters** - CFG scale, inference steps in function bodies
|
|
8. **No tests** - Zero test coverage
|
|
9. **No logging** - Only print statements
|
|
|
|
### Key Learnings
|
|
|
|
**Adopt:**
|
|
- Voice prompt caching with hash validation
|
|
- Lazy model loading with automatic unloading
|
|
- Metadata alongside outputs
|
|
- Sample management patterns
|
|
- Status indicators (cached vs not)
|
|
|
|
**Avoid:**
|
|
- Monolithic files over 2,000 lines
|
|
- Global mutable state
|
|
- Duplicated logic without abstraction
|
|
- Hardcoded parameters
|
|
|
|
---
|
|
|
|
## 3. Qwen3-TTS_server
|
|
|
|
**Repository:** `/Users/jamespine/Projects/Qwen3-TTS_server`
|
|
|
|
### Architecture
|
|
|
|
**FastAPI REST API:**
|
|
- Single server process
|
|
- Three endpoints: `/generate`, `/clone`, `/health`
|
|
- Deployed on RunPod with Docker
|
|
- Port 8000 (configurable)
|
|
|
|
### Project Structure
|
|
|
|
```
|
|
Qwen3-TTS_server/
|
|
├── main.py # FastAPI app
|
|
├── models/
|
|
│ └── tts.py # Model management
|
|
├── api/
|
|
│ └── routes.py # Endpoint definitions
|
|
├── utils/
|
|
│ └── audio.py # Audio processing
|
|
├── config.py # Settings
|
|
├── Dockerfile # Multi-stage build
|
|
└── requirements.txt # Dependencies
|
|
```
|
|
|
|
### API Design
|
|
|
|
**POST /generate**
|
|
```json
|
|
{
|
|
"text": "Hello world",
|
|
"language": "en",
|
|
"speaker": "default",
|
|
"seed": 42
|
|
}
|
|
|
|
Response:
|
|
{
|
|
"audio_url": "https://...",
|
|
"duration": 2.5,
|
|
"sample_rate": 24000
|
|
}
|
|
```
|
|
|
|
**POST /clone**
|
|
```json
|
|
{
|
|
"text": "Hello world",
|
|
"language": "en",
|
|
"reference_audio": "base64_encoded_audio",
|
|
"reference_text": "This is my voice",
|
|
"seed": 42
|
|
}
|
|
|
|
Response:
|
|
{
|
|
"audio_url": "https://...",
|
|
"duration": 2.5,
|
|
"sample_rate": 24000
|
|
}
|
|
```
|
|
|
|
**GET /health**
|
|
```json
|
|
{
|
|
"status": "healthy",
|
|
"model_loaded": true,
|
|
"gpu_available": true,
|
|
"vram_used_mb": 1024
|
|
}
|
|
```
|
|
|
|
### Model Management
|
|
|
|
**Singleton pattern:**
|
|
```python
|
|
class ModelManager:
|
|
_instance = None
|
|
_model = None
|
|
|
|
def __new__(cls):
|
|
if not cls._instance:
|
|
cls._instance = super().__new__(cls)
|
|
return cls._instance
|
|
|
|
def get_model(self):
|
|
if not self._model:
|
|
self._model = self._load_model()
|
|
return self._model
|
|
```
|
|
|
|
**Lazy loading:**
|
|
- Model loaded on first request
|
|
- Kept in memory for subsequent requests
|
|
- No unloading (server dedicated to TTS)
|
|
|
|
### Deployment
|
|
|
|
**Docker multi-stage build:**
|
|
```dockerfile
|
|
FROM nvidia/cuda:12.1.0-runtime-ubuntu22.04 AS base
|
|
# Install dependencies
|
|
|
|
FROM base AS builder
|
|
# Install Python packages
|
|
|
|
FROM base AS runtime
|
|
COPY --from=builder /usr/local/lib/python3.10 /usr/local/lib/python3.10
|
|
# Runtime only
|
|
```
|
|
|
|
**RunPod integration:**
|
|
- Configured for GPU instances
|
|
- Automatic model download on startup
|
|
- Health checks for orchestration
|
|
|
|
### Strengths
|
|
|
|
1. **Clean API design** - RESTful, well-documented
|
|
2. **Proper separation** - Routes, models, utils in separate modules
|
|
3. **Singleton model manager** - Better than global variables
|
|
4. **Health endpoint** - Essential for deployment
|
|
5. **Docker deployment** - Production-ready containerization
|
|
6. **Base64 audio input** - No file upload needed for cloning
|
|
|
|
### Weaknesses
|
|
|
|
1. **No authentication** - Wide open API
|
|
2. **No rate limiting** - DoS vulnerable
|
|
3. **Sequential inference** - No request queuing
|
|
4. **No caching** - Voice prompts recreated every time
|
|
5. **No streaming** - Returns only after full generation
|
|
6. **No WebSocket** - Can't send progress updates
|
|
7. **Synchronous endpoints** - Blocks during generation
|
|
8. **No storage** - Audio URLs expire quickly
|
|
9. **Limited error handling** - Generic 500 errors
|
|
|
|
### Key Learnings
|
|
|
|
**Adopt:**
|
|
- FastAPI for REST API
|
|
- Modular structure (routes, models, utils)
|
|
- Health endpoint for monitoring
|
|
- Base64 audio input option
|
|
- Docker deployment pattern
|
|
|
|
**Improve:**
|
|
- Add async/await throughout
|
|
- Implement request queue
|
|
- Add WebSocket for streaming
|
|
- Cache voice prompts
|
|
- Authentication and rate limiting
|
|
|
|
---
|
|
|
|
## 4. mimic
|
|
|
|
**Repository:** `/Users/jamespine/Projects/mimic`
|
|
|
|
### Architecture
|
|
|
|
**Three-tier web app:**
|
|
- Frontend: Vanilla JS (no framework)
|
|
- Backend: Python FastAPI
|
|
- Database: SQLite
|
|
|
|
**Best-structured backend** of all projects analyzed.
|
|
|
|
### Project Structure
|
|
|
|
```
|
|
mimic/
|
|
├── backend/
|
|
│ ├── main.py # FastAPI app
|
|
│ ├── models.py # Pydantic models
|
|
│ ├── tts.py # TTS inference
|
|
│ ├── transcribe.py # Whisper ASR
|
|
│ ├── profiles.py # Voice profile management
|
|
│ ├── history.py # Generation history
|
|
│ ├── studio.py # Audio studio features
|
|
│ └── database.py # SQLite ORM
|
|
├── frontend/
|
|
│ ├── index.html
|
|
│ ├── app.js # Main app (2,794 lines)
|
|
│ ├── studio.js # Audio studio (2,363 lines)
|
|
│ ├── profiles.js # Profile management
|
|
│ └── history.js # History UI
|
|
└── data/
|
|
├── profiles/ # Voice profiles
|
|
├── generations/ # Generated audio
|
|
└── mimic.db # SQLite database
|
|
```
|
|
|
|
### Backend Design
|
|
|
|
**Async/await throughout:**
|
|
```python
|
|
@router.post("/generate")
|
|
async def generate(request: GenerateRequest):
|
|
audio = await tts.generate_async(
|
|
text=request.text,
|
|
profile_id=request.profile_id,
|
|
language=request.language
|
|
)
|
|
|
|
history_entry = await db.create_generation(
|
|
profile_id=request.profile_id,
|
|
text=request.text,
|
|
audio_path=audio.path
|
|
)
|
|
|
|
return history_entry
|
|
```
|
|
|
|
**Modular separation:**
|
|
- `models.py` - Pydantic request/response models
|
|
- `tts.py` - TTS inference logic only
|
|
- `transcribe.py` - ASR logic only
|
|
- `profiles.py` - CRUD for voice profiles
|
|
- `history.py` - CRUD for generation history
|
|
- `studio.py` - Audio editing features
|
|
- `database.py` - SQLAlchemy ORM
|
|
|
|
### Features
|
|
|
|
**Voice Profiles:**
|
|
- Create from audio file + reference text
|
|
- Multiple samples per profile (combined)
|
|
- Metadata: name, description, tags, language
|
|
- Thumbnail generation from waveform
|
|
|
|
**Generation History:**
|
|
- SQLite database with full-text search
|
|
- Filters: profile, date range, language
|
|
- Regeneration from history
|
|
- Export to various formats
|
|
|
|
**Audio Studio:**
|
|
- Timeline-based editing
|
|
- Multiple audio tracks
|
|
- Word-level timestamps (Whisper alignment)
|
|
- Trim, fade, volume control
|
|
- Export with normalization
|
|
|
|
**Projects:**
|
|
- Save/load studio sessions
|
|
- Project metadata and versioning
|
|
- Export project as single file
|
|
|
|
### Database Schema
|
|
|
|
```sql
|
|
CREATE TABLE profiles (
|
|
id INTEGER PRIMARY KEY,
|
|
name TEXT UNIQUE,
|
|
description TEXT,
|
|
language TEXT,
|
|
created_at TIMESTAMP,
|
|
updated_at TIMESTAMP
|
|
);
|
|
|
|
CREATE TABLE profile_samples (
|
|
id INTEGER PRIMARY KEY,
|
|
profile_id INTEGER,
|
|
audio_path TEXT,
|
|
reference_text TEXT,
|
|
FOREIGN KEY (profile_id) REFERENCES profiles(id)
|
|
);
|
|
|
|
CREATE TABLE generations (
|
|
id INTEGER PRIMARY KEY,
|
|
profile_id INTEGER,
|
|
text TEXT,
|
|
language TEXT,
|
|
audio_path TEXT,
|
|
duration REAL,
|
|
seed INTEGER,
|
|
created_at TIMESTAMP,
|
|
FOREIGN KEY (profile_id) REFERENCES profiles(id)
|
|
);
|
|
|
|
CREATE TABLE projects (
|
|
id INTEGER PRIMARY KEY,
|
|
name TEXT,
|
|
data JSON,
|
|
created_at TIMESTAMP,
|
|
updated_at TIMESTAMP
|
|
);
|
|
```
|
|
|
|
### Frontend Design
|
|
|
|
**Major issue: Monolithic classes**
|
|
|
|
**app.js (2,794 lines):**
|
|
```javascript
|
|
class MimicApp {
|
|
constructor() {
|
|
this.profiles = [];
|
|
this.generations = [];
|
|
this.currentProfile = null;
|
|
this.currentGeneration = null;
|
|
// ... 50+ properties
|
|
}
|
|
|
|
// 80+ methods, no organization
|
|
async loadProfiles() { ... }
|
|
async createProfile() { ... }
|
|
async deleteProfile() { ... }
|
|
async generate() { ... }
|
|
async loadHistory() { ... }
|
|
// ... hundreds more lines
|
|
}
|
|
```
|
|
|
|
**studio.js (2,363 lines):**
|
|
```javascript
|
|
class AudioStudio {
|
|
constructor() {
|
|
this.wavesurfer = null;
|
|
this.timeline = null;
|
|
this.tracks = [];
|
|
this.regions = [];
|
|
this.words = [];
|
|
// ... 40+ properties
|
|
}
|
|
|
|
// 60+ methods, all mixed
|
|
initWavesurfer() { ... }
|
|
addTrack() { ... }
|
|
removeTrack() { ... }
|
|
playPause() { ... }
|
|
exportAudio() { ... }
|
|
// ... hundreds more lines
|
|
}
|
|
```
|
|
|
|
**Global state everywhere:**
|
|
```javascript
|
|
let app = null;
|
|
let studio = null;
|
|
let currentProfile = null;
|
|
let isGenerating = false;
|
|
```
|
|
|
|
**No module system:**
|
|
- All files loaded via `<script>` tags
|
|
- No bundler, no tree-shaking
|
|
- jQuery for DOM manipulation
|
|
- No type safety
|
|
|
|
### Audio Studio Implementation
|
|
|
|
**WaveSurfer.js integration:**
|
|
```javascript
|
|
this.wavesurfer = WaveSurfer.create({
|
|
container: '#waveform',
|
|
waveColor: '#4a9eff',
|
|
progressColor: '#1e3a8a',
|
|
cursorColor: '#ef4444',
|
|
height: 128,
|
|
normalize: true,
|
|
plugins: [
|
|
TimelinePlugin.create(),
|
|
RegionsPlugin.create()
|
|
]
|
|
});
|
|
```
|
|
|
|
**Word-level timestamps:**
|
|
- Whisper alignment API
|
|
- Each word: `{ word, start, end }`
|
|
- Clickable timeline
|
|
- Visual highlighting during playback
|
|
|
|
**Track mixing:**
|
|
- Multiple audio files on timeline
|
|
- Independent volume control
|
|
- Fade in/out per track
|
|
- Export as single mixed audio
|
|
|
|
### Strengths
|
|
|
|
1. **Best backend structure** - Modular, async, clean separation
|
|
2. **Database-backed** - Proper persistence layer
|
|
3. **Audio studio** - Timeline editing is unique
|
|
4. **Word-level timestamps** - Great UX for editing
|
|
5. **Project system** - Save/load sessions
|
|
6. **Search and filters** - Full-text search in history
|
|
7. **Multi-sample profiles** - Combine multiple references
|
|
8. **Proper API design** - RESTful with Pydantic validation
|
|
|
|
### Weaknesses
|
|
|
|
1. **Frontend is amateur:**
|
|
- 2,794-line app.js
|
|
- 2,363-line studio.js
|
|
- Global state everywhere
|
|
- No module system
|
|
- No TypeScript
|
|
- jQuery instead of modern framework
|
|
|
|
2. **No real-time updates** - Polling instead of WebSocket
|
|
3. **No concurrency** - Single generation at a time
|
|
4. **No batch processing** - One at a time only
|
|
5. **Limited audio formats** - WAV only
|
|
6. **No export options** - Can't export profiles or projects easily
|
|
|
|
### Key Learnings
|
|
|
|
**Backend patterns to adopt:**
|
|
- Async/await throughout
|
|
- Modular file structure (tts.py, transcribe.py, profiles.py, history.py)
|
|
- SQLite for persistence
|
|
- Pydantic for validation
|
|
- Proper CRUD separation
|
|
|
|
**Frontend patterns to avoid:**
|
|
- Monolithic classes over 2,000 lines
|
|
- Global state
|
|
- jQuery-based architecture
|
|
- No type safety
|
|
|
|
**Features to adopt:**
|
|
- Audio studio with timeline
|
|
- Word-level timestamps
|
|
- Project system
|
|
- Multi-sample voice profiles
|
|
- Generation history with search
|
|
|
|
---
|
|
|
|
## 5. qwen3-tts-enhanced
|
|
|
|
**Repository:** `/Users/jamespine/Projects/qwen3-tts-enhanced`
|
|
|
|
### Architecture
|
|
|
|
**Production-quality Gradio app:**
|
|
- Single file: `app.py` (1,892 lines)
|
|
- Much cleaner than Voice-Clone-Studio despite being monolithic
|
|
- Clear section comments and function organization
|
|
- Cross-platform support (Windows, Mac, Linux)
|
|
|
|
### Unique Features
|
|
|
|
**Multi-Reference Voice Cloning:**
|
|
```python
|
|
def combine_references(audio_paths, reference_texts):
|
|
"""Combine multiple voice samples for better quality"""
|
|
combined_audio = []
|
|
combined_text = []
|
|
|
|
for audio_path, text in zip(audio_paths, reference_texts):
|
|
audio, sr = sf.read(audio_path)
|
|
audio = normalize_audio(audio)
|
|
combined_audio.append(audio)
|
|
combined_text.append(text)
|
|
|
|
# Mix with equal weighting and normalization
|
|
mixed = np.concatenate(combined_audio)
|
|
mixed = normalize_audio(mixed)
|
|
|
|
combined_text_str = " ".join(combined_text)
|
|
|
|
return mixed, combined_text_str
|
|
```
|
|
|
|
**Batch Variation Generation:**
|
|
- Generate N variations of same text
|
|
- Different seeds for each
|
|
- Compare and pick best output
|
|
- Parallel generation support
|
|
|
|
**Smart Audio Mixing:**
|
|
```python
|
|
def normalize_audio(audio, target_db=-20):
|
|
"""Normalize to target loudness with peak limiting"""
|
|
# Convert to float32
|
|
audio = audio.astype(np.float32)
|
|
|
|
# Calculate current RMS
|
|
rms = np.sqrt(np.mean(audio**2))
|
|
|
|
# Calculate target RMS
|
|
target_rms = 10**(target_db / 20)
|
|
|
|
# Apply gain
|
|
if rms > 0:
|
|
gain = target_rms / rms
|
|
audio = audio * gain
|
|
|
|
# Peak limiting to prevent clipping
|
|
audio = np.clip(audio, -1.0, 1.0)
|
|
|
|
return audio
|
|
```
|
|
|
|
### Code Organization
|
|
|
|
**Clear sections:**
|
|
```python
|
|
# ============================================
|
|
# CONFIGURATION AND CONSTANTS
|
|
# ============================================
|
|
|
|
# ============================================
|
|
# MODEL MANAGEMENT
|
|
# ============================================
|
|
|
|
# ============================================
|
|
# AUDIO PROCESSING
|
|
# ============================================
|
|
|
|
# ============================================
|
|
# GENERATION FUNCTIONS
|
|
# ============================================
|
|
|
|
# ============================================
|
|
# UI COMPONENTS
|
|
# ============================================
|
|
|
|
# ============================================
|
|
# APPLICATION LAUNCH
|
|
# ============================================
|
|
```
|
|
|
|
**Consistent patterns:**
|
|
- All functions have docstrings
|
|
- Type hints on critical functions
|
|
- Error handling with context
|
|
- Progress callbacks throughout
|
|
|
|
### Cross-Platform Support
|
|
|
|
**Graceful degradation:**
|
|
```python
|
|
def get_audio_device():
|
|
"""Get audio playback device, cross-platform"""
|
|
try:
|
|
import sounddevice as sd
|
|
devices = sd.query_devices()
|
|
return sd.default.device
|
|
except ImportError:
|
|
logger.warning("sounddevice not available, using fallback")
|
|
return None
|
|
except Exception as e:
|
|
logger.warning(f"Audio device detection failed: {e}")
|
|
return None
|
|
|
|
def play_audio(audio_path):
|
|
"""Play audio with platform-specific fallback"""
|
|
device = get_audio_device()
|
|
|
|
if device is not None:
|
|
# Use sounddevice
|
|
audio, sr = sf.read(audio_path)
|
|
sd.play(audio, sr)
|
|
elif sys.platform == 'darwin':
|
|
# macOS fallback
|
|
subprocess.run(['afplay', audio_path])
|
|
elif sys.platform == 'win32':
|
|
# Windows fallback
|
|
import winsound
|
|
winsound.PlaySound(audio_path, winsound.SND_FILENAME)
|
|
else:
|
|
# Linux fallback
|
|
subprocess.run(['aplay', audio_path])
|
|
```
|
|
|
|
**Path handling:**
|
|
```python
|
|
def ensure_cross_platform_path(path):
|
|
"""Convert paths to platform-specific format"""
|
|
return os.path.normpath(path)
|
|
|
|
def get_config_dir():
|
|
"""Get config directory, respecting platform conventions"""
|
|
if sys.platform == 'win32':
|
|
return os.path.join(os.getenv('APPDATA'), 'qwen-tts')
|
|
elif sys.platform == 'darwin':
|
|
return os.path.expanduser('~/Library/Application Support/qwen-tts')
|
|
else:
|
|
return os.path.expanduser('~/.config/qwen-tts')
|
|
```
|
|
|
|
### Quality Improvements
|
|
|
|
**Reference audio validation:**
|
|
```python
|
|
def validate_reference_audio(audio_path):
|
|
"""Check if reference audio is suitable for cloning"""
|
|
audio, sr = sf.read(audio_path)
|
|
|
|
# Check duration (3-10 seconds ideal)
|
|
duration = len(audio) / sr
|
|
if duration < 2:
|
|
return False, "Audio too short (minimum 2 seconds)"
|
|
if duration > 30:
|
|
return False, "Audio too long (maximum 30 seconds)"
|
|
|
|
# Check for silence
|
|
rms = np.sqrt(np.mean(audio**2))
|
|
if rms < 0.01:
|
|
return False, "Audio is too quiet or silent"
|
|
|
|
# Check for clipping
|
|
if np.abs(audio).max() > 0.99:
|
|
return False, "Audio is clipping (reduce input gain)"
|
|
|
|
# Check sample rate
|
|
if sr < 16000:
|
|
return False, f"Sample rate too low ({sr}Hz, minimum 16kHz)"
|
|
|
|
return True, "Audio is suitable"
|
|
```
|
|
|
|
**Generation quality settings:**
|
|
```python
|
|
# Conservative defaults for quality
|
|
DEFAULT_CFG_SCALE = 4.5 # Higher = more faithful to prompt
|
|
DEFAULT_STEPS = 20 # More steps = better quality
|
|
DEFAULT_TEMPERATURE = 1.0
|
|
|
|
# Fast mode (lower quality)
|
|
FAST_CFG_SCALE = 3.0
|
|
FAST_STEPS = 10
|
|
```
|
|
|
|
### Backward Compatibility
|
|
|
|
**Config migration:**
|
|
```python
|
|
def migrate_config(config):
|
|
"""Migrate old config to new format"""
|
|
version = config.get('version', 1)
|
|
|
|
if version == 1:
|
|
# Add new fields from version 2
|
|
config['batch_size'] = 1
|
|
config['version'] = 2
|
|
|
|
if version == 2:
|
|
# Add new fields from version 3
|
|
config['multi_reference'] = False
|
|
config['version'] = 3
|
|
|
|
return config
|
|
```
|
|
|
|
**Legacy sample format:**
|
|
```python
|
|
def load_sample(sample_path):
|
|
"""Load voice sample, supporting both old and new formats"""
|
|
# Try new format (JSON metadata)
|
|
json_path = sample_path.replace('.wav', '.json')
|
|
if os.path.exists(json_path):
|
|
with open(json_path) as f:
|
|
metadata = json.load(f)
|
|
return {
|
|
'audio': sample_path,
|
|
'text': metadata['reference_text'],
|
|
'language': metadata.get('language', 'en')
|
|
}
|
|
|
|
# Try old format (text file)
|
|
txt_path = sample_path.replace('.wav', '.txt')
|
|
if os.path.exists(txt_path):
|
|
with open(txt_path) as f:
|
|
text = f.read().strip()
|
|
return {
|
|
'audio': sample_path,
|
|
'text': text,
|
|
'language': 'en' # Assume English
|
|
}
|
|
|
|
# No metadata found
|
|
return None
|
|
```
|
|
|
|
### Strengths
|
|
|
|
1. **Multi-reference cloning** - Combine samples for better quality
|
|
2. **Batch variations** - Generate multiple outputs, pick best
|
|
3. **Smart audio normalization** - RMS targeting + peak limiting
|
|
4. **Cross-platform support** - Graceful degradation everywhere
|
|
5. **Backward compatibility** - Config migration, legacy formats
|
|
6. **Reference validation** - Check audio before generation
|
|
7. **Quality presets** - Fast vs. high-quality modes
|
|
8. **Clean code** - Well-organized despite being single file
|
|
9. **Good error messages** - Contextual, actionable
|
|
10. **Progress callbacks** - User sees what's happening
|
|
|
|
### Weaknesses
|
|
|
|
1. **Still monolithic** - 1,892 lines in one file
|
|
2. **No database** - File-based storage
|
|
3. **No history** - Can't search past generations
|
|
4. **No API** - Gradio UI only
|
|
5. **No concurrent generation** - One at a time
|
|
6. **No streaming** - Wait for full generation
|
|
|
|
### Key Learnings
|
|
|
|
**Production patterns to adopt:**
|
|
- Multi-reference combination for quality
|
|
- Audio validation before generation
|
|
- Cross-platform graceful degradation
|
|
- Config migration for backward compatibility
|
|
- RMS normalization with peak limiting
|
|
- Quality presets (fast vs. high-quality)
|
|
- Clear section organization in code
|
|
- Progress callbacks everywhere
|
|
|
|
**Clean code practices:**
|
|
- Docstrings on all functions
|
|
- Type hints where useful
|
|
- Error messages with context
|
|
- Consistent naming conventions
|
|
|
|
---
|
|
|
|
## Comparative Analysis
|
|
|
|
### Model Management
|
|
|
|
| Project | Pattern | VRAM Strategy | Caching |
|
|
|---------|---------|--------------|---------|
|
|
| voice | Lazy load | Single model | Voice prompts only |
|
|
| Voice-Clone-Studio | Lazy + mutex | Unload on switch | Voice prompts + disk |
|
|
| Qwen3-TTS_server | Singleton | Keep in memory | None |
|
|
| mimic | Lazy async | Single model | None |
|
|
| qwen3-tts-enhanced | Lazy load | Single model | Voice prompts only |
|
|
|
|
**Winner:** Voice-Clone-Studio (most sophisticated caching)
|
|
|
|
### Architecture
|
|
|
|
| Project | Backend | Frontend | Separation | Modularity |
|
|
|---------|---------|----------|------------|------------|
|
|
| voice | Python subprocess | Rust CLI | Excellent | Good |
|
|
| Voice-Clone-Studio | Gradio monolith | Gradio | None | Poor |
|
|
| Qwen3-TTS_server | FastAPI | None (API only) | Excellent | Excellent |
|
|
| mimic | FastAPI | Vanilla JS | Good | Backend: Excellent, Frontend: Poor |
|
|
| qwen3-tts-enhanced | Gradio monolith | Gradio | None | Good (sections) |
|
|
|
|
**Winner:** mimic (backend), Qwen3-TTS_server (overall separation)
|
|
|
|
### Features
|
|
|
|
| Feature | voice | Voice-Clone-Studio | Qwen3-TTS_server | mimic | qwen3-tts-enhanced |
|
|
|---------|-------|-------------------|-----------------|-------|-------------------|
|
|
| Voice cloning | ✓ | ✓ | ✓ | ✓ | ✓ |
|
|
| Multi-reference | ✗ | ✗ | ✗ | ✓ | ✓ |
|
|
| Voice design | ✗ | ✓ | ✗ | ✗ | ✗ |
|
|
| Presets | ✗ | ✓ (9) | ✗ | ✗ | ✗ |
|
|
| Conversations | ✗ | ✓ | ✗ | ✗ | ✗ |
|
|
| History | ✗ | ✓ (file) | ✗ | ✓ (database) | ✗ |
|
|
| Audio studio | ✗ | ✗ | ✗ | ✓ | ✗ |
|
|
| Batch generation | ✗ | ✗ | ✗ | ✗ | ✓ |
|
|
| M3GAN effect | ✓ | ✗ | ✗ | ✗ | ✗ |
|
|
|
|
**Winner:** mimic (most comprehensive feature set)
|
|
|
|
### Code Quality
|
|
|
|
| Project | Lines | Organization | Type Safety | Tests | Documentation |
|
|
|---------|-------|--------------|-------------|-------|---------------|
|
|
| voice | ~500 | Good | Rust: Yes, Python: No | None | README only |
|
|
| Voice-Clone-Studio | 2,815 | Poor | No | None | Good README |
|
|
| Qwen3-TTS_server | ~800 | Excellent | Partial (Pydantic) | None | API docs |
|
|
| mimic | ~4,000 | Backend: Good, Frontend: Poor | Backend: Partial | None | Basic |
|
|
| qwen3-tts-enhanced | 1,892 | Good | Partial | None | Good |
|
|
|
|
**Winner:** Qwen3-TTS_server (best organized)
|
|
|
|
### Production Readiness
|
|
|
|
| Project | Deployment | Error Handling | Cross-Platform | Graceful Degradation | Monitoring |
|
|
|---------|-----------|----------------|----------------|---------------------|------------|
|
|
| voice | Binary | Good (Rust) | Yes | Limited | None |
|
|
| Voice-Clone-Studio | Python script | Basic | Yes | Good (flash attn) | None |
|
|
| Qwen3-TTS_server | Docker | Basic | Linux (container) | None | Health endpoint |
|
|
| mimic | Python script | Good | Yes | Limited | None |
|
|
| qwen3-tts-enhanced | Python script | Excellent | Excellent | Excellent | None |
|
|
|
|
**Winner:** qwen3-tts-enhanced (most robust), Qwen3-TTS_server (deployment)
|
|
|
|
---
|
|
|
|
## Recommended Architecture for voicebox
|
|
|
|
### Backend (Python + FastAPI)
|
|
|
|
**Structure (from mimic):**
|
|
```
|
|
backend/
|
|
├── main.py # FastAPI app
|
|
├── models.py # Pydantic models
|
|
├── tts.py # TTS inference
|
|
├── transcribe.py # Whisper ASR
|
|
├── profiles.py # Voice profile management
|
|
├── history.py # Generation history
|
|
├── studio.py # Audio editing
|
|
├── effects.py # M3GAN, etc.
|
|
├── database.py # SQLite ORM
|
|
└── utils/
|
|
├── audio.py # Audio processing
|
|
├── cache.py # Voice prompt caching
|
|
└── validation.py # Input validation
|
|
```
|
|
|
|
**Patterns to adopt:**
|
|
- Async/await throughout (mimic)
|
|
- Voice prompt caching (Voice-Clone-Studio)
|
|
- Multi-reference combination (qwen3-tts-enhanced)
|
|
- Audio validation (qwen3-tts-enhanced)
|
|
- Cross-platform audio (qwen3-tts-enhanced)
|
|
- Health endpoint (Qwen3-TTS_server)
|
|
- Graceful degradation (qwen3-tts-enhanced)
|
|
|
|
### Frontend (Tauri + TypeScript + React)
|
|
|
|
**Structure:**
|
|
```
|
|
frontend/
|
|
├── src/
|
|
│ ├── components/
|
|
│ │ ├── VoiceProfiles/
|
|
│ │ ├── Generation/
|
|
│ │ ├── AudioStudio/
|
|
│ │ └── History/
|
|
│ ├── lib/
|
|
│ │ ├── api.ts # Backend API client
|
|
│ │ ├── audio.ts # Audio utilities
|
|
│ │ └── store.ts # State management
|
|
│ ├── types/
|
|
│ │ └── index.ts # TypeScript types
|
|
│ └── App.tsx
|
|
└── src-tauri/
|
|
├── src/
|
|
│ └── main.rs # Tauri backend
|
|
└── tauri.conf.json
|
|
```
|
|
|
|
**Avoid monolithic components:**
|
|
- Keep components under 300 lines
|
|
- Proper state management (Zustand or Jotai)
|
|
- TypeScript everywhere
|
|
- Component-based architecture
|
|
|
|
### Features Priority
|
|
|
|
**Phase 1 (MVP):**
|
|
1. Voice profile management
|
|
2. Single-reference voice cloning
|
|
3. Generation history (database)
|
|
4. Basic audio playback
|
|
|
|
**Phase 2:**
|
|
1. Multi-reference combination
|
|
2. Batch variation generation
|
|
3. M3GAN effect
|
|
4. Audio normalization
|
|
|
|
**Phase 3:**
|
|
1. Audio studio with timeline
|
|
2. Word-level timestamps
|
|
3. Project system
|
|
4. Export options
|
|
|
|
**Phase 4:**
|
|
1. Voice design
|
|
2. Preset voices
|
|
3. Conversation mode
|
|
4. Advanced effects
|
|
|
|
### Technology Stack
|
|
|
|
**Backend:**
|
|
- FastAPI (async REST API)
|
|
- SQLAlchemy (database ORM)
|
|
- Pydantic (validation)
|
|
- Qwen3-TTS (model)
|
|
- Whisper (transcription)
|
|
- librosa + soundfile (audio)
|
|
|
|
**Frontend:**
|
|
- Tauri (desktop framework)
|
|
- React (UI framework)
|
|
- TypeScript (type safety)
|
|
- Tailwind CSS (styling)
|
|
- Zustand (state management)
|
|
- WaveSurfer.js (audio visualization)
|
|
|
|
**Database:**
|
|
- SQLite (local storage)
|
|
- Alembic (migrations)
|
|
|
|
### Key Differentiators
|
|
|
|
What will make voicebox better:
|
|
|
|
1. **Clean architecture** - Avoid monolithic files from all projects
|
|
2. **TypeScript frontend** - Type safety unlike mimic
|
|
3. **Desktop-first** - Native feel via Tauri
|
|
4. **Voice prompt caching** - Fast generations like Voice-Clone-Studio
|
|
5. **Multi-reference** - Quality like qwen3-tts-enhanced
|
|
6. **Audio studio** - Timeline editing like mimic
|
|
7. **M3GAN effect** - Unique feature from voice
|
|
8. **Production code** - No amateur patterns
|
|
9. **Proper state management** - No global variables
|
|
10. **Modular from day one** - Easy to extend
|
|
|
|
---
|
|
|
|
## Conclusion
|
|
|
|
All five projects have valuable lessons:
|
|
|
|
- **voice**: Clean Rust/Python split, M3GAN effect
|
|
- **Voice-Clone-Studio**: Brilliant caching, feature-rich but poorly organized
|
|
- **Qwen3-TTS_server**: Best modular structure, API design
|
|
- **mimic**: Best backend architecture, great features, terrible frontend
|
|
- **qwen3-tts-enhanced**: Production patterns, quality focus, cross-platform
|
|
|
|
voicebox will cherry-pick the best patterns from each while avoiding their architectural mistakes.
|