Orqanta-Voice
Streaming multilingual text-to-speech for 11 Indic languages and English, with 20 built-in voices and zero-shot voice cloning. This repository is the inference engine: a continuous-batching CUDA-graph engine, a Python API, an OpenAI-compatible HTTP server and a WebSocket streaming server. The weights are on Hugging Face at buggandhi/Orqanta-Voice.
Samples · Model card · Engine docs · Text normaliser
The Python package and model are called Orqanta-Voice (module name:
orqanta_voice).
- Languages: as bn en gu hi kn ml mr or pa ta te.
- Streaming: about 14 ms to first audio at B=1 on an H100. One H100 NVL serves 224 concurrent streams at 323 s of audio per second, with steady-state TTFA p99 242 ms and 0 playback stalls.
- Voices: 20 named voices (
voice="Ahana"), or clone a voice from 3-10 s of audio. - Text normaliser (bundled): numbers, currency, dates, URLs and ALL-CAPS names are spoken correctly in every language.
Install
You need Python 3.10-3.12, Linux and an NVIDIA GPU with a CUDA 12.8 driver (8 GB or more of GPU memory).
git clone https://github.com/GANDHIKALASI/Orqanta-Voice orqanta_voice && cd orqanta_voice
python -m venv .venv && . .venv/bin/activate
pip install -e ".[server]" --extra-index-url https://download.pytorch.org/whl/cu128
The pinned runtime is torch 2.8.0 (cu128, triton 3.4.0), transformers 5.6.2 and speechbrain 1.1.2. Weights, the
Mimi codec and the ECAPA speaker encoder are downloaded from Hugging Face on first use. Set HF_HUB_OFFLINE=1 to
use only the local cache.
Quickstart
Python
from orqanta_voice import OrqantaVoiceTTS
import soundfile as sf
tts = OrqantaVoiceTTS.from_pretrained("buggandhi/Orqanta-Voice", max_seats=16)
wav = tts.generate("नमस्ते, आपका ऑर्डर कल पहुँच जाएगा।", lang="hi", voice="Ahana")
sf.write("out.wav", wav, 24000) # float32, 24 kHz mono
for pcm in tts.stream("Your OTP is 482913.", lang="en", voice="Siddhant", normalize=True):
... # int16 PCM chunks, as they are generated
generate, stream and astream are thread-safe and asyncio-safe. Concurrent calls are batched on the GPU.
Command line
python examples/synthesize.py "నమస్కారం, మీరు ఎలా ఉన్నారు?" --lang te --voice Sreekar -o out.wav
python examples/synthesize.py --list-voices
HTTP server (OpenAI-compatible) and WebSocket streaming
python -m orqanta_voice.server --ckpt buggandhi/Orqanta-Voice --host 0.0.0.0 --port 8080 --max-seats 64
curl -sN localhost:8080/v1/audio/speech -H 'content-type: application/json' \
-d '{"input":"Your order has shipped.","language":"en","voice":"Ahana","response_format":"wav"}' > out.wav
from openai import OpenAI # any OpenAI-compatible client
client = OpenAI(base_url="http://localhost:8080/v1", api_key="unused")
with client.audio.speech.with_streaming_response.create(
model="orqanta-voice", voice="Ahana", input="Hello from Orqanta-Voice.", response_format="pcm") as r: # s16le, 24 kHz
for chunk in r.iter_bytes():
...
POST /v1/audio/speechstreamswav,pcm,mp3oropus;flacis sent once the audio is complete.WS /v1/tts/streamcarries several concurrent contexts per socket, with cancel and binary frames. Seeexamples/ws_stream.pyand the protocol in the docstring oforqanta_voice/server.py.GET/POST/DELETE /v1/voicesmanages cloned voices, andGET /healthreports status.- Text is normalised by default. Turn this off with
"normalize": falseper request, or--no-normalisefor the whole server. orqanta_voice.clienthas a small Python client for HTTP and WebSocket.
Voices
| voice | gender | age | languages (>= 1 h trained) | description |
|---|---|---|---|---|
| Ahana | female | young | bn en gu hi kn ml mr or pa ta te | Young female voice, bright, youthful |
| Vaidehi | female | adult | bn en gu hi kn ml mr or pa ta te | Adult female voice, calm, gentle |
| Malini | female | mature | bn en gu hi kn ml mr or pa ta te | Mature female voice, steady narrator |
| Charvi | female | young | bn en gu hi kn ml mr or pa ta te | Young female voice, energetic, upbeat |
| Jahnavi | female | adult | bn en gu hi kn ml mr or ta te | Adult female voice, warm, friendly |
| Anirban | male | adult | bn en gu hi kn ml mr or pa ta te | Adult male voice, steady narrator |
| Nakul | male | young | bn en gu hi kn ml mr or pa ta te | Young male voice, bright, youthful |
| Vasant | male | mature | bn en gu hi kn ml mr or ta te | Mature male voice, steady narrator |
| Siddhant | male | adult | bn en gu hi kn ml mr or pa ta te | Adult male voice, clear, professional |
| Satvik | male | young | bn en gu hi kn ml mr or pa ta te | Young male voice, warm, friendly |
| Abhay | male | adult | en hi | Adult male voice, natural conversational style; trained on English, Hindi |
| Nivedita | female | adult | en hi | Adult female voice, natural conversational style; trained on English, Hindi |
| Ishwari | female | adult | hi | Adult female voice, natural conversational style; trained on Hindi |
| Ritvik | male | adult | hi | Adult male voice, natural conversational style; trained on Hindi |
| Vrinda | female | young | hi | Young female voice, natural conversational style; trained on Hindi |
| Sreekar | male | adult | en hi te | Adult male voice, natural conversational style; trained on English, Hindi, Telugu |
| Kayal | female | adult | en ta | Adult female voice, natural conversational style; trained on English, Tamil |
| Shaunak | male | young | en hi | Young male voice, natural conversational style; trained on English, Hindi |
| Akshat | male | adult | en hi | Adult male voice, natural conversational style; trained on English, Hindi |
| Tanushree | female | adult | en hi | Adult female voice, natural conversational style; trained on English, Hindi |
- Every voice can speak all 12 languages. Languages outside its trained list are spoken cross-lingually.
- Names are case-insensitive.
tts.voices(a property) andGET /v1/voiceslist them. - The voices are synthetic personas and must not be presented as real people.
Named-voice prompt format. A named voice is selected by its name in the prompt: the engine renders
<|lang:xx|>{Name}: <text><|audio|>. Send only your text with voice="Name" (Python) or "voice": "Name" (HTTP/WS).
Do not type {Name}: yourself, and do not combine a named voice with a reference clip. Each voice's conditioning
mode is in voices/voices.json:
nameduses the trained name prompt;spkuses a stored speaker embedding.
Voice cloning
# 3-10 s of clean speech + its exact transcript and language. Only clone voices you have consent to use.
tts.register_voice("my_voice", ref_audio="ref.wav", ref_text="<exact transcript of ref.wav>", lang="hi")
wav = tts.generate("வணக்கம், எப்படி இருக்கிறீர்கள்?", lang="ta", voice="my_voice") # cross-lingual
# one-off (cached for the session):
wav = tts.generate("Hello!", lang="en", ref_audio="ref.wav", ref_text="...", ref_lang="hi")
There are three conditioning modes for a reference:
icl_spk(default): the reference audio as an in-context prompt, plus its ECAPA embedding;icl: the reference audio only;spk: the ECAPA embedding only, so no transcript is needed.
The reference language can differ from the target language. Over HTTP, POST /v1/voices registers a clone (see
examples/clone_voice.py). Zero-shot clones are recognisably close to the reference but not identical.
Languages
| code | language | CER % (Qwen3-ASR, release bench) |
|---|---|---|
as |
Assamese | 6.59 |
bn |
Bengali | 2.83 |
en |
English | 1.74 |
gu |
Gujarati | 3.76 |
hi |
Hindi | 2.05 |
kn |
Kannada | 2.72 |
ml |
Malayalam | 4.20 |
mr |
Marathi | 2.09 |
or |
Odia | 4.33 |
pa |
Punjabi | 3.32 |
ta |
Tamil | 4.55 |
te |
Telugu | 2.64 |
Overall CER on the 6,000-clip release bench is 3.40 % (Qwen3-ASR). Real failures, where two ASRs agree, are 0.15 %. The model card describes the method in full.
Streaming performance
The whole release bench (6000 texts, 20.3 h of audio) was served by orqanta_voice.server with its
defaults, as 224 concurrent WebSocket streams on one H100 NVL:
| metric | value |
|---|---|
| throughput | 323 audio-s/s |
| TTFA, steady state, p50 / p90 / p99 | 111 / 134 / 242 ms |
| TTFA, whole run incl. ramp and drain, p50 / p99 | 115 / 774 ms |
| playback stalls / errors | 0 / 0 |
| B=1 (single stream, H100) | TTFA about 14 ms, RTF about 0.13 |
GPU memory, with a bf16 backbone, an fp16 depth decoder and 2048 KV tokens per seat (add about 0.5 GB of CUDA context):
max_seats |
steady | peak |
|---|---|---|
| 1 | 7.7 GB | 9.2 GB |
| 8 | 9.7 GB | 10.9 GB |
| 16 | 11.7 GB | 12.8 GB |
| 32 | 15.8 GB | 16.5 GB |
An RTX 4090 (24 GB) serves 48 streams at capacity=2048 (TTFA p99 66 ms), or 96 at capacity=1024. Engine knobs,
design notes and the conversion tools are in docs/ENGINE.md. The text normaliser is described in
orqanta_voice/text/README.md.
Limitations
- Assamese is the weakest language, with the highest CER and the least stable voice identity. No voice has much Assamese training audio.
- Very short prompts (1-2 words) can drift in voice identity. Prefer a slightly longer phrase for short agent turns.
- Code-mixed and mixed-script text has the highest error rates, and English words inside Indic sentences vary by voice.
- URLs, symbols and long digit strings need the normaliser (on by default in the server,
normalize=Truein Python). - Clone similarity is recognisably close but not identical. Noisy or very short references reduce it.
- Inline emotion tags (
[laughs],[whispers], ...) are best effort. - Sampling. Output is sampled. The same seed is reproducible only when the request runs alone.
Responsible use
Do not use Orqanta-Voice to impersonate real people, to clone a voice without the speaker's consent, for fraud, or to defeat
voice authentication. Tell listeners when speech is synthetic. To report misuse or a security problem, see
SECURITY.md (contact: 117009786+GANDHIKALASI@users.noreply.github.com).
Repository layout
| path | content |
|---|---|
orqanta_voice/ |
public API (api.py), streaming engine (engine.py), HTTP/WS server (server.py), client, guards, text normaliser, tools, tests |
serve/ |
CUDA-graph components: fused backbone step, fp16 depth decoder, one-frame Mimi decoder, KV cache |
tts/ |
model definition and the training-time prompt packer the engine reproduces |
scripts/ |
prompt and stop-control helpers shared with the model code |
configs/ |
model config and the duration prior read by the engine |
examples/ |
command-line synthesis, OpenAI client, WebSocket streaming, voice cloning |
docs/ENGINE.md |
engine internals, knobs, memory and latency measurements, checkpoint conversion |
Tests
The CPU tests need no GPU and no weights:
pip install -e ".[dev]" --extra-index-url https://download.pytorch.org/whl/cu128
./run_tests.sh # = CUDA_VISIBLE_DEVICES= python -m pytest -q orqanta_voice/tests/test_text.py orqanta_voice/tests/test_guards.py orqanta_voice/tests/test_server.py
The GPU checks (orqanta_voice/tests/test_backbone_*.py, test_depth_fused.py, test_rope_kernel.py, test_sampling.py)
are scripts that take a checkpoint path. See their docstrings.
Author & Maintainer
- Maintainer: Gandhi Kalasi (@gandhikalasi) -
117009786+GANDHIKALASI@users.noreply.github.com - Repository: https://github.com/GANDHIKALASI/Orqanta-Voice
- Hugging Face: https://huggingface.co/buggandhi/Orqanta-Voice
License
Apache-2.0. See LICENSE. The model weights have their own license on the Hugging Face model card. Orqanta-Voice builds on
Qwen3, Mimi (Kyutai) and SpeechBrain ECAPA-TDNN; see their licences.
Citation
@misc{orqantavoice,
title = {Orqanta-Voice: Streaming Multilingual Text-to-Speech for Indic Languages},
author = {Kalasi, Gandhi},
year = {2026},
howpublished = {\url{https://huggingface.co/buggandhi/Orqanta-Voice}}
}
GitHub's "Cite this repository" button reads CITATION.cff. Changes are listed in CHANGELOG.md.
- Downloads last month
- -