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.
Quick Links
| π¬ Chat | Discord |
| π Bugs | GitHub Issues |
| π·οΈ Good First Issues | Filtered list |
| π Roadmap | README β 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:
| Service | URL | What it does |
|---|---|---|
| Backend | localhost:3900 | FastAPI server β TTS, ASR, diarization, dubbing pipeline |
| Frontend | localhost:3901 | React + 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:
- What happened vs what you expected
- Steps to reproduce
- OS, GPU, and Python version (find in Settings β Logs)
- Error logs (Settings β Logs β copy relevant lines)
Pull Requests
- Fork the repo and create a branch from
main - Keep PRs focused β one feature or fix per PR
- Run tests before pushing:
# Backend tests uv run pytest backend/ -x -q # Frontend build check cd frontend && npx vite build --mode development - Write a clear PR title β it becomes the squash-merge commit message
- 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:
- Open
backend/services/tts_backend.py - 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
- Register it in
_REGISTRYat the bottom of the file - 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 bareprint() - 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 thesrc/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.cssfiles 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 tosrc/index.csswith a provenance comment. (The only other.cssis the test-only visual harness. Seedocs/shadcn-migration.md.) - Naming:
PascalCasefor components,camelCasefor hooks and utils
Rust (Tauri)
- Format:
cargo fmtbefore 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
.jsxfile. Anything over 500 lines must be split. (The cap does not apply tosrc/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.jsxtogether in a per-page feature folder underfrontend/src/components/(e.g.components/settings/,components/dub/). Styling is not co-located β it's utilities + shadcn, with any irreducible rules insrc/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 toerroronce 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 underfrontend/src/i18n/locales/. Translate; don't copy English into non-English locales. CI fails on hardcoded CJK outside the allowlist intests/test_no_hardcoded_cjk.py(extend_ALLOWED_FILESwith 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:
- you have the right to submit it (your own work, or compatibly licensed);
- it is licensed to the project under AGPL-3.0; and
- 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?
- Stuck on setup? Ask in Discord #help
- Not sure where to start? Check good first issues
- Want to discuss a big change? Open a discussion or Discord thread before coding
Thank you for contributing! ποΈ