# 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 `