How to Select a TTS Engine in VoiceStudio: A Complete Guide

Select a TTS engine in VoiceStudio by opening Settings → Voice, choosing from the dropdown of available engines, and letting the app save your selection to user_env.tts_engine for all subsequent voice operations.

VoiceStudio simplifies text-to-speech engine management through a registry-based discovery system. The Python worker exposes available backends via a REST API, while the React frontend renders them as a user-friendly dropdown. This article explains exactly how engine selection works under the hood, with file paths and code references from the debpalash/VoiceStudio repository.

Where TTS Engines Are Discovered

VoiceStudio maintains a central registry of TTS backends in services/tts_backend.py. The _REGISTRY dictionary holds all engine classes, keyed by their unique identifiers.

Engine registration happens through a decorator pattern:


# services/tts_backend.py

_REGISTRY = {}

def register_engine(id_: str):
    def decorator(cls):
        _REGISTRY[id_] = cls
        return cls
    return decorator

@register_engine("indextts")
class IndexttsBackend(OmniVoiceBackend):
    @staticmethod
    def is_available() -> Tuple[bool, Optional[str]]:
        # Verify binary, model files, GPU availability

        return (True, None)

Each engine implements is_available() to report whether it can run on the current worker. This method returns a tuple: (available: bool, reason: Optional[str]). The reason string explains why an engine is unavailable—missing binaries, insufficient VRAM, or missing model weights.

How the Frontend Lists Available Engines

The VoicePanel component in frontend/src/components/settings/VoicePanel.jsx fetches the engine list when mounted. It calls GET /api/settings/tts-engines and transforms the response into a dropdown.

// frontend/src/components/settings/VoicePanel.jsx
const [engines, setEngines] = useState([]);
const [engineAvailable, setEngineAvailable] = useState(true);
const [engineReason, setEngineReason] = useState(null);

useEffect(() => {
  apiJson('/api/settings/tts-engines')
    .then(data => {
      setEngineAvailable(data?.engine_available !== false);
      setEngineReason(data?.engine_reason || null);
      setEngines(data?.engines ?? []);
    });
}, []);

The API returns an array of engine objects with these fields:

  • id — the registry key (e.g., "indextts", "voxcpm2")
  • label — human-readable display name
  • available — boolean from is_available()
  • reason — explanation when available is false

The UI filters to show only available engines by default, with tooltips for unavailable ones.

Selecting and Persisting Your TTS Engine

When you pick an engine from the dropdown, the frontend POSTs the choice to the backend:

// frontend/src/components/settings/VoicePanel.jsx (lines 252-260)
<Select
  value={selectedEngine?.id}
  onChange={e => apiPost('/api/settings/tts-engine', {engine: e.target.value})}
  options={engines.map(e => ({value: e.id, label: e.label}))}
/>

The FastAPI route validates the engine ID against _REGISTRY, then stores it:

@app.post("/api/settings/tts-engine")
def set_tts_engine(payload: dict, user: User = Depends(get_current_user)):
    engine_id = payload.get("engine")
    if engine_id not in services.tts_backend._REGISTRY:
        raise HTTPException(400, "Unknown TTS engine")
    user.settings["tts_engine"] = engine_id
    return {"ok": True}

This writes to the user's environment file (.voice_studio_env). The setting takes effect immediately—no restart required.

How the Worker Uses Your Selected Engine

Every TTS operation calls get_active_tts_backend() to resolve the concrete backend instance. This function:

  1. Reads user.settings["tts_engine"]
  2. Looks up the class in _REGISTRY
  3. Instantiates it with worker-appropriate configuration

The resolution logic is exercised in tests/test_worker_executor_residency.py around line 200. The function guarantees that voice-over, dubbing, and audiobook generation tasks all use the same user-selected engine.

Troubleshooting Engine Availability

If your preferred engine doesn't appear in the dropdown, check these common causes:

  • Missing binaries — The engine's executable isn't in PATH or the expected directory
  • Model weights missing — Required checkpoint files aren't downloaded
  • GPU requirements — The engine needs CUDA but the worker has no compatible GPU

The UI surfaces this information through engine_reason. In VoicePanel.jsx, unavailable engines trigger a banner:

{engineAvailable ? (
  <Select /* ... */ />
) : (
  <span>{engineReason || t('voicePanel.engine_unavailable')}</span>
)}

Adding a Custom TTS Engine

To register a new engine:

  1. Create a class extending OmniVoiceBackend (or the appropriate base)
  2. Implement is_available() with your detection logic
  3. Apply the @register_engine("your_id") decorator
  4. Ensure the worker module containing your class is imported at startup

The test file tests/test_worker_tts_parity.py demonstrates mock engine registration for validation.

Summary

  • Engine discovery happens through services.tts_backend._REGISTRY, populated by decorators on backend classes
  • UI population flows from GET /api/settings/tts-engines to the VoicePanel dropdown in frontend/src/components/settings/VoicePanel.jsx
  • Selection persistence stores tts_engine in user settings via POST /api/settings/tts-engine
  • Runtime resolution uses get_active_tts_backend() to instantiate the chosen engine for every TTS task
  • Availability checks run automatically; unavailable engines show explanatory tooltips instead of blocking the UI

Frequently Asked Questions

Why doesn't my installed engine appear in the dropdown?

The engine's is_available() method returned False. Check engine_reason in the UI or run the worker with debug logging to see which requirement failed—missing binary, model file, or GPU capability.

Do I need to restart VoiceStudio after changing engines?

No. The new tts_engine value is read at the start of each TTS task. Active jobs continue with their original engine; new jobs use your selection immediately.

Can I use different engines for different projects?

Not through the UI. VoiceStudio stores one global tts_engine per user environment. For per-project engines, you would need to modify get_active_tts_backend() to accept an override parameter or manage environment switching externally.

Where is the engine selection saved?

In the user's VoiceStudio environment file, typically .voice_studio_env in the working directory or a platform-specific config location. The exact path depends on how the User model resolves user_env in your deployment.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →