Vocalizer is a native PHP extension by Akram Zerarka that runs text-to-speech synthesis locally, with no API calls and no runtime dependencies. It embeds two inference backends — sherpa-onnx (ONNX Runtime) and audio.cpp (ggml) — and ships as a prebuilt .so binary, so there's nothing to compile.

Here's what the extension gives you:

  • Eight model families, one API — Chatterbox, Supertonic, Piper/VITS, Pocket, Kokoro, Kitten, Matcha, and ZipVoice, auto-detected from the model directory
  • Voice cloning — Chatterbox clones any voice from a 3–10 second reference WAV across 23 languages
  • Anti-hallucination guard — Chatterbox output is checked for skipped text, loops, and silence, and re-synthesized with a fresh seed when it looks suspicious
  • Crash isolation — models run in fork mode by default, so an engine crash is retried and reloaded instead of taking down the PHP worker
  • Model cache — models load once per PHP worker (LRU eviction, vocalizer.max_models) and stay warm for subsequent requests
  • Async synthesisspeakAsync() returns a job you can wait() on, with per-call timeouts
  • WAV or raw PCM output — save to a file, get the WAV as a string, or grab float32 PCM

One API Across Eight Model Families

Engine::load() points at a model directory and figures out the backend from its contents. Synthesis is a single speak() call:

use Vocalizer\Engine;

$engine = Engine::load('/opt/voices/sherpa-onnx-supertonic-3-tts-int8-2026-05-11');

$res = $engine->speak('Votre commande est prête.', [

'lang' => 'fr', // required for Supertonic

'voice' => 0, // 0–9 preset voices

'speed' => 1.0,

'timeout_ms' => 30_000,

]);

$res->save('/var/www/audio/notice.wav');

echo $res->seconds, " s in ", $res->generationMs, " ms\n";

Which model to load depends on what you're after:

Goal Model Latency (CPU)
Best realism, voice cloning Chatterbox Slow (~20× real time)
Fast multi-language (31 languages) Supertonic 3 Real-time
Lightweight FR/EN cloning Pocket TTS Fast
Fastest, one model per locale Piper/VITS Very fast

Voice Cloning with Chatterbox

Chatterbox covers 23 languages with a single ~7.5 GB model and clones a voice from a short reference WAV in the target language:

$engine = Engine::load('/opt/voices/chatterbox', [

'threads' => 4,

'opts' => ['weight_type' => 'f16'], // default: q8_0

]);

$res = $engine->speak('Bonjour, votre commande est prête.', [

'lang' => 'fr',

'reference' => '/opt/voices/refs/fr.wav',

'opts' => [

'temperature' => 0.6,

'repetition_penalty' => 1.2,

'seed' => 42,

],

'timeout_ms' => 600_000,

]);

echo $res->qualityRetries; // guard re-syntheses (0 = first output accepted)

$res->save('/tmp/out.wav');

Autoregressive TTS models can skip text, loop, or produce silence, which is where the anti-hallucination guard comes in. Vocalizer checks every Chatterbox output against the text length and signal energy, and re-synthesizes suspicious audio with a new seed — two extra attempts by default, configurable via verify_retries. If every attempt fails, it throws a Vocalizer\Exception rather than returning corrupt audio, which makes a fallback to a faster model straightforward:

try {

$res = $engine->speak($text, ['lang' => 'fr', 'reference' => $ref]);

} catch (\Vocalizer\Exception $e) {

$res = Engine::load('/opt/voices/sherpa-onnx-supertonic-3-tts-int8-2026-05-11')

->speak($text, ['lang' => 'fr']);

}

Crash Isolation and Async

Native inference engines can crash, and a segfault inside a PHP extension normally kills the FPM worker with it. Vocalizer's default fork isolation mode runs synthesis in a forked child process, so a crash is caught, retried (up to vocalizer.max_retries), and the model reloaded — surfacing as a Vocalizer\CrashException only when recovery fails. Chatterbox is the exception: its ggml thread pool is not fork-safe, so it always runs in direct mode.

For longer texts, speakAsync() moves synthesis off the request path:

$job = $engine->speakAsync($paragraph);

$res = $job->wait(30_000) ?? throw new RuntimeException('still running');

Behavior is tuned through php.ini directives: vocalizer.isolation (fork vs. direct), vocalizer.timeout_ms, vocalizer.max_models for the per-worker model cache, and vocalizer.max_concurrency for the async pool. One production note from the README worth repeating: model RAM is per FPM worker, and Chatterbox alone needs several GB.

Installation

Vocalizer requires Linux x86-64 (glibc ≥ 2.28) and PHP 8.4 or 8.5 NTS — Alpine/musl, ARM, and ZTS builds are not supported. The install script downloads the prebuilt extension (~44 MB) and verifies it via SHA256:

curl -fsSL https://raw.githubusercontent.com/akramzerarka/vocalizer/main/install.sh | bash

Models are downloaded separately with the bundled script:

./scripts/download-model.sh chatterbox # ~7.5 GB

./scripts/download-model.sh sherpa-onnx-supertonic-3-tts-int8-2026-05-11 # ~120 MB

./scripts/download-model.sh vits-piper-en_US-amy-low # ~65 MB

The extension is MIT-licensed and statically links its dependencies, including sherpa-onnx (Apache-2.0), audio.cpp/ggml (MIT), ONNX Runtime (MIT), and espeak-ng (GPL-3.0 phonemization data).

You can find the full API reference, configuration details, and model catalog on GitHub.