Contributing to VoiceStudio

August 11, 2026 Β· View on GitHub

Thanks for your interest in improving VoiceStudio! This guide covers everything you need to get started.

πŸ’¬ ChatDiscord
πŸ› BugsGitHub Issues
🏷️ Good First IssuesFiltered list
πŸ“‹ RoadmapREADME β†’ Roadmap

Adding a TTS or ASR engine

New engines are hired for a named job, not added to a list β€” the bar, the current job map, and the out-of-tree path are in docs/engine-acceptance.md. Read it before opening a proposal; the licence check in particular ends most of them.

Development Setup

Prerequisites

  • Git
  • curl (used by the Bun / uv / rustup install one-liners on macOS and Linux)
  • Bun (frontend package manager)
  • uv (Python environment manager)
  • ffmpeg (audio/video processing)
  • Rust / Cargo (desktop shell only)
  • Python 3.10+ (managed automatically by uv)

Linux desktop development also needs WebKitGTK/GTK development libraries. On Debian or Ubuntu, install the same packages used by CI:

sudo apt-get update
sudo apt-get install -y \
  libwebkit2gtk-4.1-dev libgtk-3-dev libpango1.0-dev libcairo2-dev \
  libsoup-3.0-dev libgdk-pixbuf-2.0-dev \
  libayatana-appindicator3-dev librsvg2-dev libssl-dev libxdo-dev \
  libasound2-dev build-essential curl wget file

See the Linux source-build guide for Fedora and Arch packages.

Clone & Run

git clone https://github.com/debpalash/VoiceStudio.git
cd VoiceStudio
bun install
bun run dev

This starts both services:

ServiceURLWhat it does
Backendlocalhost:3900FastAPI server β€” TTS, ASR, diarization, dubbing pipeline
Frontendlocalhost:3901React + Vite UI

The backend runs through scripts/dev-backend.mjs (the dev:api script): the uvicorn command is unchanged, but if the backend dies (OOM kill, hard crash), the wrapper prints a boxed exit banner with the exit code/signal and the last 20 lines of omnivoice.log before the dev stack shuts down β€” so the cause doesn't scroll away with the terminal. The same death is also reported as a crash notice in the UI the next time the backend starts (see docs/install/troubleshooting.md Β§14c).

Desktop App (Tauri)

bun run desktop          # dev: hot-reload Tauri shell + backend
bun run desktop-prod     # production: builds, bundles the backend, then launches

Both run uv sync first (so the Python backend env is set up) and start the backend automatically β€” you do not start it separately. Use the exact script names: there is no desktop=prod (note the hyphen in desktop-prod). desktop-prod is Windows-aware (auto-detects bash/git; see scripts/desktop-prod.mjs).

Requires Rust and platform-specific Tauri dependencies β€” see the Tauri prerequisites.

After installing Rust with rustup on macOS/Linux, either open a new terminal or load Cargo into the current one before starting the desktop app:

source "$HOME/.cargo/env"
bun desktop

On Linux, errors such as Package gdk-3.0 was not found, pango.pc missing, or javascriptcoregtk-4.1 missing mean the native packages above were not installed; changing PKG_CONFIG_PATH does not fix libraries that are absent.

If the app opens but stays on the setup splash with no buttons, the Python backend didn't finish starting β€” the splash surfaces the stall reason, a log panel, and a Retry button (and Settings β†’ Logs β†’ Backend has the full trace). The most common from-source cause is uv or Python not being on your PATH.


Project Structure

VoiceStudio/
β”œβ”€β”€ backend/                 # Python FastAPI server
β”‚   β”œβ”€β”€ api/                 # Route handlers
β”‚   β”œβ”€β”€ core/                # Config, prefs, constants
β”‚   └── services/            # TTS engines, ASR, dubbing, audio DSP
β”‚       └── tts_backend.py   # ← Multi-engine TTS registry
β”œβ”€β”€ frontend/                # React + Vite
β”‚   β”œβ”€β”€ src/
β”‚   β”‚   β”œβ”€β”€ components/      # UI components
β”‚   β”‚   β”œβ”€β”€ hooks/           # Custom React hooks
β”‚   β”‚   β”œβ”€β”€ stores/          # Zustand state slices
β”‚   β”‚   └── utils/           # Shared utilities
β”‚   └── src-tauri/           # Rust/Tauri desktop shell
β”œβ”€β”€ deploy/                  # Docker, CI configs
β”œβ”€β”€ docs/                    # Screenshots, MCP config
└── scripts/                 # Build & release scripts

How to Contribute

Bug Reports

Open an issue with:

  1. What happened vs what you expected
  2. Steps to reproduce
  3. OS, GPU, and Python version (find in Settings β†’ Logs)
  4. Error logs (Settings β†’ Logs β†’ copy relevant lines)

Pull Requests

  1. Fork the repo and create a branch from main
  2. Keep PRs focused β€” one feature or fix per PR
  3. Run tests before pushing:
    # Backend tests
    uv run pytest backend/ -x -q
    
    # Frontend build check
    cd frontend && npx vite build --mode development
    
  4. Write a clear PR title β€” it becomes the squash-merge commit message
  5. Don't include local machine stats, file paths, or private system info in PR descriptions

Adding a New TTS Engine

VoiceStudio's TTS backend is a plugin registry. Adding a new engine takes ~50 lines:

  1. Open backend/services/tts_backend.py
  2. Create a class extending TTSBackend:
class MyEngineBackend(TTSBackend):
    id = "my-engine"
    display_name = "My Engine (description)"

    @classmethod
    def is_available(cls) -> tuple[bool, str]:
        try:
            import my_engine  # noqa: F401
            return True, "ready"
        except ImportError:
            return False, "my_engine not installed. pip install my-engine"

    @property
    def sample_rate(self) -> int:
        return 24000

    @property
    def supported_languages(self) -> list[str]:
        return ["en", "zh"]

    def generate(self, text: str, **kw) -> torch.Tensor:
        # ... call your engine, return [1, num_samples] tensor
  1. Register it in _REGISTRY at the bottom of the file
  2. That's it β€” it auto-appears in Settings β†’ TTS Engine

Code Style

Python (Backend)

  • Formatter: We don't enforce one globally β€” match the style of the file you're editing
  • Logging: Use logger.warning() / logger.error(), never bare print()
  • Exceptions: Avoid bare except: pass β€” catch specific exceptions
  • Type hints: Use them for public API functions and class methods

JavaScript/React (Frontend)

  • Components: Functional components with hooks
  • State: Zustand stores in src/stores/, organized by slice
  • Brand assets: Reuse the canonical mark, palette, naming, and compatibility rules in docs/branding.md; do not redraw or rename runtime identifiers ad hoc
  • CSS: Utilities-first + shadcn/ui, one stylesheet. UI is built on the shadcn/ui primitives in src/components/ui/ (wrapped by the src/ui/ barrel, themed to the VoiceStudio palette), composed with Tailwind v4 utility classes. All styling now lives in a single file β€” src/index.css: the @theme / [data-theme] token foundation plus the irreducible set utilities can't express (@keyframes, glassmorphism/backdrop-filter, pseudo-elements, :has(), unlayered cascade overrides, and styling hooks on library-generated DOM like virtualized rows / WaveSurfer). The per-component .css files were eliminated in the CSSβ†’Tailwind/shadcn migration β€” do not create new ones. Reach for shadcn primitives + utilities; if a rule is genuinely irreducible, add it to src/index.css with a provenance comment. (The only other .css is the test-only visual harness. See docs/shadcn-migration.md.)
  • Naming: PascalCase for components, camelCase for hooks and utils

Rust (Tauri)

  • Format: cargo fmt before committing
  • Modules: One concern per file (bootstrap.rs, tools.rs, config.rs, commands.rs)

Frontend file structure & size limits

Frontend code stays modular so an edit loads one small file, not a 1900-line one. The rules:

  • Size caps: soft 300 lines, hard 500 lines per .jsx file. Anything over 500 lines must be split. (The cap does not apply to src/index.css β€” it is the single, intentional styling foundation and the only app stylesheet; see the CSS rule above.)
  • Pages are thin orchestrators. A file in frontend/src/pages/ is just layout + routing + state wiring that composes feature components β€” no inline sub-component over ~50 lines.
  • One component per file. Co-locate Foo.jsx + Foo.test.jsx together in a per-page feature folder under frontend/src/components/ (e.g. components/settings/, components/dub/). Styling is not co-located β€” it's utilities + shadcn, with any irreducible rules in src/index.css.
  • Shared bits go in a primitives/ folder inside the feature folder (components/settings/primitives/ is the existing example).
  • Enforced by ESLint max-lines (max: 500) β€” warn-only for now so it never breaks CI, with the goal of upgrading to error once the backlog of oversized files clears.

Commit Messages

Write clear, concise messages. The PR title becomes the squash-merge commit.

good: fix: prevent CUDA OOM during concurrent transcription + TTS
good: feat: add CosyVoice 3 TTS backend adapter
good: docs: add platform compatibility matrix to README

bad:  fixed stuff
bad:  update
bad:  WIP

Testing

# Run all backend tests
uv run pytest backend/ -x -q

# Run a specific test file
uv run pytest backend/tests/test_api.py -x -q

# Frontend build validation (no test suite yet)
cd frontend && npx vite build --mode development

# Tauri shell check (requires Rust)
cd frontend/src-tauri && cargo check


What code review looks like

Every PR is reviewed by two AI reviewers before a human looks at it:

  • CodeRabbit posts a walkthrough (with a sequence diagram, and an ASCII before/after sketch for UI changes), inline findings, and warning-mode pre-merge checks against the project's hard rules.
  • Greptile reviews with the same project rubrics and learns from πŸ‘/πŸ‘Ž reactions on its comments β€” react to train it.

Both are advisory, not gating: CI and the maintainer's approval decide. Don't be surprised by detailed bot comments minutes after you open a PR β€” address what's right, push back (in a reply) on what's wrong.

Commit & PR conventions: conventional-commit style with a scope (fix(dub): …, feat(setup): …) and link the issue (Closes #N / Refs #N) in the title or body.

Contributing with AI agents

Plenty of contributions here are built with Claude Code, Cursor, and similar agents β€” welcome, with the same quality bar as hand-written PRs (real bug, correct fix, regression test; see the quality gates below).

One practical tip: this codebase is large, and re-explaining it to your agent every session burns context and tokens fast. A persistent memory layer fixes that β€” the agent recalls the architecture, conventions, and your past findings instead of re-reading the tree each time. memxt (100% local, MCP-based, built by this project's maintainer) exists for exactly this; any MCP memory server works. Pair it with the repo's agent skill β€” npx skills add debpalash/omnivoice-studio β€” so your agent knows the project's hard rules from the first prompt.

Quality gates your PR must pass

  • Cross-platform parity (hard rule): anything that ships in default mode must behave identically on macOS, Windows, and Linux. Platform-specific implementation is fine; platform-divergent default behavior is a P0. Platform-only features go behind an explicit opt-in (Settings toggle, env var, or CLI flag).
  • i18n β€” all 21 locales (hard rule): every user-facing string goes through t('...') and the key must exist in all 21 files under frontend/src/i18n/locales/. Translate; don't copy English into non-English locales. CI fails on hardcoded CJK outside the allowlist in tests/test_no_hardcoded_cjk.py (extend _ALLOWED_FILES with a justification for legitimate functional CJK).
  • DB schema changes go through an alembic migration with a tested upgrade path β€” existing omnivoice_data/ must keep working with no manual steps.
  • Engine back-compat: already-installed engines (model weights on disk) must not require reinstall or re-download.
  • Local-first: no new outbound calls except GitHub Issues (opt-in reporting) and HuggingFace model downloads. Never log or persist secrets or absolute home paths.
  • Security posture: the backend serves loopback HTTP β€” treat every query/path/form parameter as hostile. User-chosen filesystem destinations are authorized in the Tauri process (save dialog), never via HTTP params.

Contribution licensing

VoiceStudio is AGPL-3.0-only, and the maintainer also offers a commercial license (see LICENSE). By submitting a contribution you agree that:

  1. you have the right to submit it (your own work, or compatibly licensed);
  2. it is licensed to the project under AGPL-3.0; and
  3. you grant the project maintainer a perpetual, worldwide, non-exclusive right to also distribute your contribution under the project's commercial license terms.

This inbound grant is what keeps the dual-license model viable. If you can't agree to (3) for a particular contribution, say so in the PR and we'll discuss before merging. Adding a Signed-off-by: line (DCO) to your commits is appreciated but not required.


Need Help?

Thank you for contributing! πŸŽ™οΈ