pipedog is an unofficial community port of Pipecat, not affiliated with Daily.
pipedog

TEN VAD

Detect the user's voice with TEN VAD's WebAssembly build, which you obtain from TEN and pass in, as an opt-in alternative to Silero.

Overview

TenVADAnalyzer is a VAD analyzer backed by TEN VAD, a small voice activity detector from the TEN framework. It goes wherever a VAD analyzer goes (the user aggregator's vadAnalyzer, VADProcessor), in place of Silero. This is pipedog only: upstream Pipecat has no TEN VAD.

TEN VAD has its own license

TEN VAD is licensed under Apache-2.0 with additional conditions (among them a non-compete clause regarding Agora, and that derivative works of TEN VAD stay under TEN's license). pipedog is BSD-2-Clause, so @pipedog/audio-ten-vad contains none of TEN's code, JavaScript glue, WebAssembly or model: it only calls TEN's documented WebAssembly interface. You download TEN's prebuilt files yourself and pass them in, and TEN's conditions apply to your use of them. Read TEN VAD's license before you ship, and consider crediting it in your app ("Powered by ten-vad").

Installation

bun add @pipedog/audio-ten-vad
import { loadTenVadModule, TenVADAnalyzer } from "@pipedog/audio-ten-vad";

Then get TEN's WebAssembly build from TEN's repository: lib/Web/ten_vad.js (the Emscripten loader, whose default export is createVADModule) and lib/Web/ten_vad.wasm. Copy both into your app, for example as ten-vad/.

Runtimes

RuntimeWorksWhat it needs
Bun✅ yesTEN VAD's WebAssembly build (ten_vad.js and ten_vad.wasm from TEN's lib/Web), obtained from TEN under TEN's license and passed in at runtime. pipedog ships none of it.
Node.js✅ yesTEN VAD's WebAssembly build (ten_vad.js and ten_vad.wasm from TEN's lib/Web), obtained from TEN under TEN's license and passed in at runtime. pipedog ships none of it.
Deno— not verifiedTEN VAD's WebAssembly build (ten_vad.js and ten_vad.wasm from TEN's lib/Web), obtained from TEN under TEN's license and passed in at runtime. pipedog ships none of it.
Cloudflare Workers✅ yesTEN VAD's WebAssembly build (ten_vad.js and ten_vad.wasm from TEN's lib/Web), obtained from TEN under TEN's license and passed in at runtime. pipedog ships none of it. Import ten_vad.wasm as a module and pass it as wasmModule (Workers can't compile WebAssembly from bytes).

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

The table covers pipedog's analyzer, which the compat test runs against a stand-in module. Whether TEN's ten_vad.js loads on a runtime is up to TEN's build (it targets browsers and web workers).

Usage

loadTenVadModule() calls TEN's factory with the Emscripten options for where the .wasm comes from. Load it once per process and pass the promise to every analyzer: each one creates its own TEN VAD instance in the shared module.

On Bun, Node and Deno, read the .wasm bytes:

ten_vad.ts
// import createVADModule from "./ten-vad/ten_vad.js"; // TEN's lib/Web/ten_vad.js

// Load TEN's module once and share it: each analyzer creates its own TEN VAD instance in it.
const tenVad = loadTenVadModule({
  factory: createVADModule,
  wasmBinary: await readFile("./ten-vad/ten_vad.wasm"),
});

const vad = new TenVADAnalyzer({ module: tenVad, sampleRate: 16000 });

In browsers, TEN's loader can fetch ten_vad.wasm from next to ten_vad.js:

// import createVADModule from "/ten-vad/ten_vad.js";

// TEN's glue fetches ten_vad.wasm from next to ten_vad.js (or pass `wasmBinary`).
const vad = new TenVADAnalyzer({ module: loadTenVadModule({ factory: createVADModule }) });

On Cloudflare Workers, which can't compile WebAssembly from bytes, import the .wasm file as a module (Wrangler's default rule for .wasm gives a WebAssembly.Module) and pass it as wasmModule. It goes through Emscripten's instantiateWasm hook:

// import createVADModule from "./ten-vad/ten_vad.js";
// import tenVadWasm from "./ten-vad/ten_vad.wasm"; // Wrangler gives a WebAssembly.Module

const tenVad = loadTenVadModule({ factory: createVADModule, wasmModule: tenVadWasm });
const vad = new TenVADAnalyzer({ module: tenVad });

Then use the analyzer like Silero:

const context = new LLMContext();
const vad = new TenVADAnalyzer({
  module: tenVad,
  hopSize: 160, // 10 ms frames (default: 256, 16 ms)
  // TEN's speech probability is the confidence: VADParams decides, as for Silero.
  params: new VADParams({ confidence: 0.7, startSecs: 0.2, stopSecs: 0.2 }),
});
const aggregators = new LLMContextAggregatorPair(context, {
  userParams: new LLMUserAggregatorParams({ vadAnalyzer: vad }),
});

TEN VAD is created once the module has loaded: await vad.ready() waits for it, otherwise the first analyzeAudio does. After that every frame is scored synchronously. cleanup() destroys the instance and frees its memory.

16 kHz only

TEN VAD takes 16 kHz mono 16-bit audio, so the analyzer throws for any other sample rate. Run the transport's input at 16 kHz (audioInSampleRate: 16000), or resample before the VAD.

Configuration

new TenVADAnalyzer(options):

OptionTypeDefaultDescription
moduleTenVADModule | Promise<TenVADModule>requiredTEN VAD's WebAssembly module, from loadTenVadModule().
hopSize160 | 256256Samples per frame: 256 (16 ms) or 160 (10 ms), the sizes TEN VAD is optimized for.
thresholdnumber0.5TEN's own speech-flag threshold. The analyzer uses TEN's speech probability as the confidence, so params.confidence decides what counts as speech; this only sets the flag TEN computes alongside.
sampleRatenumberset laterMust be 16000.
paramsVADParamsnew VADParams()Confidence, start and stop times, and the volume gate, as for every VAD analyzer.

loadTenVadModule(options):

OptionTypeDescription
factoryTenVADModuleFactoryThe default export of TEN's ten_vad.js.
wasmBinaryArrayBuffer | Uint8ArrayThe .wasm bytes.
wasmModuleWebAssembly.ModuleThe .wasm compiled ahead of time (Cloudflare Workers).
moduleOverridesRecord<string, unknown>Further Emscripten options, such as locateFile.

Pass wasmBinary or wasmModule, or neither to let TEN's loader find the file.

TEN VAD or Silero

Both are small neural VADs; which one works better depends on your audio. In short:

TEN VADSilero
Package@pipedog/audio-ten-vad@pipedog/audio-silero
License of the model and runtimeApache-2.0 with TEN's additional conditionsMIT
Filesyou obtain and pass TEN's .js and .wasm (about 300 KB)the model ships with the package (2.3 MB)
RuntimeWebAssemblyONNX Runtime (onnxruntime-node or onnxruntime-web)
Sample rates16 kHz8 and 16 kHz
Frame10 or 16 ms32 ms

TEN reports lower per-frame cost and faster speech-end detection than Silero in its own benchmarks. Evaluate both on recordings of your own users (line quality, noise, languages) before switching, and tune VADParams for the one you pick.

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