pipedog is an unofficial community port of Pipecat, not affiliated with Daily.
pipedog
Text-to-speech

Kokoro

Local text-to-speech with KokoroTTSService, running the Kokoro-82M ONNX model on your machine with eSpeak NG phonemization. No API key.

Overview

  • KokoroTTSService: synthesizes each sentence locally with Kokoro-82M, an 82M-parameter model that runs on the CPU, and streams 24 kHz audio resampled to the pipeline's rate. No account, no API key.
  • Kokoro: the engine behind it, a TypeScript reimplementation of the Python kokoro-onnx package (0.6.1) on @pipedog/onnx and @pipedog/espeak-ng. Its audio is bit-identical to kokoro-onnx's for the int8 model.

Kokoro is one of upstream's keyless local defaults for evals: it speaks the simulated user's turns in audio scenarios (service: kokoro).

Installation

bun add @pipedog/kokoro onnxruntime-node @echogarden/espeak-ng-emscripten
  • onnxruntime-node runs the model. onnxruntime-web@1.30.0 (WebAssembly) works too, slower.
  • @echogarden/espeak-ng-emscripten is eSpeak NG compiled to WebAssembly, for phonemization. It is GPL-3.0 (see Licenses).

Both are optional peer dependencies, loaded on first use.

import { KokoroTTSService } from "@pipedog/kokoro/tts";

What you need

The same files upstream uses, from kokoro-onnx's model-files-v1.0 GitHub release. They are downloaded once, when the pipeline starts, if they are missing:

FileSizeDefault location
kokoro-v1.0.onnx310 MiB<cache>/kokoro-v1.0.onnx
voices-v1.0.bin (54 voices)27 MiB<cache>/voices-v1.0.bin

<cache> is $PIPEDOG_CACHE_DIR/kokoro, else $XDG_CACHE_HOME/pipedog/kokoro, else ~/.cache/pipedog/kokoro. Pass cacheDir to use another directory.

To use files you already have, pass modelPath and voicesPath. A path that exists is used as is; a missing one is downloaded there from modelUrl / voicesUrl. The same release has smaller variants of the model, such as kokoro-v1.0.int8.onnx (88 MiB):

// Files you already have, or the smaller int8 model from the same release.
const int8 = new KokoroTTSService({
  modelPath: "/models/kokoro-v1.0.int8.onnx", // used as is if present...
  modelUrl:
    "https://github.com/thewh1teagle/kokoro-onnx/releases/download/model-files-v1.0/kokoro-v1.0.int8.onnx", // ...else downloaded from here
  voicesPath: "/models/voices-v1.0.bin",
  settings: new KokoroTTSService.Settings({ voice: "af_heart" }),
});

eSpeak NG adds about 25 MB (the WebAssembly build and its voice data), loaded into memory once per process. @pipedog/espeak-ng wraps it: phonemize() reproduces the Python phonemizer package that kokoro-onnx uses, so the same text gives the same phonemes.

Runtimes

RuntimeWorksWhat it needs
Bun✅ yesonnxruntime-node (or onnxruntime-web) and @echogarden/espeak-ng-emscripten; the Kokoro model (310 MB, or 88 MB int8) and voices (27 MB), downloaded on first use.
Node.js✅ yesonnxruntime-node (or onnxruntime-web) and @echogarden/espeak-ng-emscripten; the Kokoro model (310 MB, or 88 MB int8) and voices (27 MB), downloaded on first use.
Deno✅ yesonnxruntime-node (or onnxruntime-web) and @echogarden/espeak-ng-emscripten; the Kokoro model (310 MB, or 88 MB int8) and voices (27 MB), downloaded on first use.
Cloudflare Workers❌ noonnxruntime-node (or onnxruntime-web) and @echogarden/espeak-ng-emscripten; the Kokoro model (310 MB, or 88 MB int8) and voices (27 MB), downloaded on first use. Not supported: the model and eSpeak NG data exceed Workers' memory and bundle limits.

Generated from pipecat.runtimes and pipecat.compat in packages/kokoro/package.json: ✅ means the compat smoke test passes on that runtime.

Cloudflare Workers is not supported: the model (88-310 MiB), the voices and eSpeak NG's data don't fit a Worker's 128 MB of memory or its 10 MB bundle limit.

KokoroTTSService

Configuration

Prop

Type

Settings

Runtime-updatable settings, passed as settings: new KokoroTTSService.Settings({...}) (KokoroTTSSettings). Any setting you leave out keeps the default below.

SettingTypeDefaultDescription
modelnullnullNot used: the model is modelPath.
voicestringnone, requiredVoice from the voices file, for example "af_heart".
languageLanguageLanguage.ENLanguage, mapped to an espeak-ng code.
speednumber1.0Speech rate multiplier, 0.5 to 2.0.

Languages: English (US and GB), Spanish, French, Hindi, Italian, Japanese, Portuguese (PT and BR) and Mandarin. Pick a voice for the language: af_* / am_* American English, bf_* / bm_* British, ef_* Spanish, ff_* French, hf_* Hindi, if_* Italian, jf_* Japanese, pf_* Portuguese, zf_* Mandarin.

Usage

tts.ts
import { TTSUpdateSettingsFrame } from "@pipedog/core/frames/frames";
import { Language } from "@pipedog/core/transcriptions/language";
import { KokoroTTSService } from "@pipedog/kokoro/tts";

// Downloads kokoro-v1.0.onnx (310 MiB) and voices-v1.0.bin (27 MiB) on first start.
const tts = new KokoroTTSService({
  settings: new KokoroTTSService.Settings({ voice: "af_heart", language: Language.EN, speed: 1.0 }),
});

Change the voice, language or speed mid-call with a TTSUpdateSettingsFrame:

// Queue it on the worker, or push it from a processor upstream of the service.
const update = new TTSUpdateSettingsFrame({
  delta: new KokoroTTSService.Settings({ voice: "bf_emma", language: Language.EN_GB, speed: 1.1 }),
});

The engine works on its own too:

import { Kokoro } from "@pipedog/kokoro/kokoro_onnx";

const kokoro = await Kokoro.load("/models/kokoro-v1.0.onnx", "/models/voices-v1.0.bin");
for await (const [samples, sampleRate] of kokoro.createStream("Hello there!", {
  voice: "af_heart",
  lang: "en-us",
})) {
  // samples: a Float32Array at sampleRate (24000 Hz), one chunk per phoneme batch
}

Notes

  • The model and voices load in start() (or on the first sentence). Services in one process share one model session and one copy of the voices; set shareSession: false to opt out.
  • A missing or blank voice is rejected by the constructor. A voice that isn't in the voices file is reported when the model loads.
  • Text is phonemized with espeak-ng, split into batches of at most 510 phonemes at sentence, clause and word boundaries, and the next batch is synthesized while the current one plays.
  • backend takes any OnnxBackend, for example one built on onnxruntime-web.

Differences from upstream

  • The model is downloaded and loaded in start(), not in the constructor, since loading is asynchronous.
  • Default cache directory: ~/.cache/pipedog/kokoro, where upstream uses ~/.cache/pipecat/kokoro-onnx.
  • cacheDir, modelUrl, voicesUrl, fetch, backend and shareSession are pipedog options.
  • The deprecated voice_id and params (InputParams) arguments are not ported. Use settings.
  • Not ported from kokoro-onnx: timed synthesis (create_timed), which needs a re-exported model.

Licenses

  • @pipedog/kokoro: BSD 2-Clause.
  • Kokoro-82M model and voices: Apache-2.0 (hexgrad). The ONNX export and voice pack come from kokoro-onnx (MIT).
  • eSpeak NG and @echogarden/espeak-ng-emscripten: GPL-3.0. Distributing an application that includes them may subject it to the GPL's terms. Upstream is in the same position: kokoro-onnx ships espeak-ng through espeakng-loader.

pipedog is an unofficial community TypeScript port of Pipecat. It is not affiliated with or endorsed by Daily or the Pipecat project.

Portions adapted from the Pipecat documentation, Copyright (c) 2024–2025 Daily, BSD-2-Clause (see apps/docs/LICENSE-pipecat-docs).

On this page