Usage Guide

July 20, 2026 · View on GitHub

This guide is for Python developers driving phoonnx directly. After reading it you can load a voice, synthesize to a WAV or a stream, tune synthesis, and reach the low-level phonemize / tokenize / vocode calls.

Loading a Voice

The main entry point is TTSVoice.load(). You need an ONNX model file and its accompanying JSON config:

from phoonnx.voice import TTSVoice

voice = TTSVoice.load(
    model_path="model.onnx",
    config_path="model.json",   # defaults to model_path + ".json" if omitted
)

Note: the default (<model_path>.json, e.g. model.onnx.json) does not match the voice manager's cache layout, which ships model.onnx + model.json side by side. Loading straight out of a cache directory needs config_path passed explicitly as above; manager.voices[voice_id].load() already does this for you.

Loading with Overrides

You can override the phonemizer and language at load time:

voice = TTSVoice.load(
    model_path="model.onnx",
    config_path="model.json",
    lang_code="en-US",
    phoneme_type_str="espeak",   # override phonemizer
    alphabet_str="ipa",
)

Choosing execution providers

Pass an ordered list of ONNX Runtime execution providers to run on a GPU. The list also drives the auxiliary graphs an engine loads (vocoders, speaker encoders):

voice = TTSVoice.load(
    "model.onnx", "model.json",
    providers=["CUDAExecutionProvider", "CPUExecutionProvider"],
)

When omitted, providers come from the PHOONNX_ONNX_PROVIDERS environment variable, then from auto-detection. use_cuda=True is a deprecated alias for providers=["CUDAExecutionProvider"]; prefer providers. See the Configuration reference.

Set PHOONNX_ORT_CACHE_DIR to a writable directory to cache the ONNX Runtime-optimized graph across process restarts, and pass warmup=True to TTSVoice.load to pay the first-inference kernel-selection cost during load instead of on the first synthesize call — both cut cold-start latency.

Synthesizing Speech

To a WAV File

import wave
from phoonnx.voice import TTSVoice

voice = TTSVoice.load("model.onnx", "model.json")

with wave.open("output.wav", "wb") as wav_file:
    voice.synthesize_wav("Hello, world!", wav_file)

Streaming Audio Chunks

synthesize() is a generator that yields one AudioChunk per sentence. This enables low-latency streaming:

from phoonnx.voice import TTSVoice
from phoonnx.config import SynthesisConfig

voice = TTSVoice.load("model.onnx", "model.json")
syn_config = SynthesisConfig(length_scale=1.2)  # slightly slower speech

for chunk in voice.synthesize("Hello! How are you today?", syn_config=syn_config):
    print(f"Sample rate: {chunk.sample_rate}")
    print(f"Audio shape: {chunk.audio_float_array.shape}")
    # chunk.audio_int16_bytes — raw PCM bytes for playback

From Phoneme IDs Directly

If you have pre-computed phoneme IDs, you can synthesize raw audio directly:

import numpy as np
phoneme_ids = [1, 5, 23, 7, 2]  # example IDs
audio: np.ndarray = voice.phoneme_ids_to_audio(phoneme_ids)

Phoneme Alignment

Every AudioChunk carries the phonemes and phoneme_ids that produced it. Pass include_alignments=True to also get per-phoneme timing — how many audio samples each phoneme spans — when the model exposes a duration output:

for chunk in voice.synthesize("Hello world.", include_alignments=True):
    for align in chunk.phoneme_alignments or []:
        ms = align.num_samples / chunk.sample_rate * 1000
        print(f"{align.phoneme!r:6s} {ms:6.1f} ms")

include_alignments=False (the default) is a strict no-op on the audio, and models without a duration output degrade gracefully — phoneme_alignments comes back None. If a model was exported without a duration output but has one to find, the first include_alignments=True call retrofits it on demand via load-time graph surgery (see Phoneme alignment's "Runtime alignment for models exported without the flag"), so re-exporting is optional, not required. See Phoneme alignment for the full guide, the engine support matrix, and how to export a VITS model with alignment.

SynthesisConfig

SynthesisConfig controls synthesis parameters at runtime:

from phoonnx.config import SynthesisConfig

syn_config = SynthesisConfig(
    speaker_id=0,           # for multi-speaker voices
    lang_id=0,              # for multi-language voices
    length_scale=1.0,       # 1.0 = normal speed, <1 faster, >1 slower
    noise_scale=0.667,      # generator noise (affects naturalness/variation)
    noise_w_scale=0.8,      # phoneme duration noise
    normalize_audio=True,   # normalize output to full amplitude range
    volume=1.0,             # volume multiplier
    enable_phonetic_spellings=True,  # apply user-defined word replacements
    add_diacritics=None,    # None defers to the voice config (Arabic/Hebrew)
)

add_diacritics defaults to None, meaning "use the voice's own setting"; set it to True or False only to force diacritization on or off for this call. diacritizer_model (default None) likewise inherits the voice config's choice — set it to override the Arabic text2tashkeel model for this call. Cloning and autoregressive engines add more fields — speaker_reference, speaker_reference_text, speaker_reference_lang, exaggeration, temperature, top_p, extra_params — documented in Cloning and the Configuration reference.

Inline Phoneme Input

You can bypass the phonemizer for specific words by embedding phonemes directly in double brackets:

text = "The word [[θɪs]] is phonemized manually."
for chunk in voice.synthesize(text):
    ...

Phonetic Spellings

You can define custom word-level pronunciation overrides that are applied before phonemization:

# phonetic_spellings is loaded from a JSON file at voice load time
# Format: {"word": "replacement_text_or_phonemes"}
voice.phonetic_spellings  # dict-like object, or None if not configured

Multi-Speaker Voices

For voices with multiple speakers, set speaker_id in SynthesisConfig:

syn_config = SynthesisConfig(speaker_id=2)
for chunk in voice.synthesize("Hello!", syn_config=syn_config):
    ...

Audio Output Format

Each AudioChunk has the following attributes:

AttributeDescription
audio_float_arraynp.ndarray (float32), values in [-1.0, 1.0]
audio_int16_arraynp.ndarray (int16), the float array clipped and scaled
audio_int16_bytesRaw PCM bytes (int16, little-endian)
sample_rateSample rate in Hz (e.g. 22050)
sample_widthBytes per sample (always 2)
sample_channelsNumber of channels (always 1, mono)

Low-level API

synthesize() is built from three composable steps you can call yourself:

# text -> phonemes grouped by sentence (a list of lists of phoneme strings)
chunks = voice.phonemize("Hello world", lang="en-US")

# phonemes -> integer token IDs
ids = voice.phonemes_to_ids(chunks[0])

# token IDs -> raw float32 audio (unnormalized)
audio = voice.phoneme_ids_to_audio(ids)

phonemize() accepts an optional lang to phonemize in a language other than the voice's own (used for cross-lingual cloning references). phoneme_ids_to_audio() accepts a SynthesisConfig and optional per-phoneme language_ids.