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

Moonshine

Local speech-to-text with MoonshineSTTService, running Moonshine's small, fast ASR models on the CPU. No API key.

Overview

  • MoonshineSTTService: a segmented STT service that transcribes each speech segment, cut by the pipeline's VAD, with a Moonshine model on your machine. Moonshine is a small, fast ASR family that runs on the CPU: no GPU, no account, no API key.
  • Transcriber (@pipedog/moonshine/moonshine_voice): the engine. Upstream wraps the moonshine_voice Python package (a C++ core on ONNX Runtime). This one runs the same quantized .ort model files through @pipedog/onnx, with the part of Moonshine's C++ core that transcribe_without_streaming runs ported to TypeScript.

Moonshine is the default transcriber of upstream's keyless local defaults for evals: it transcribes the bot's speech in audio scenarios.

Installation

bun add @pipedog/moonshine onnxruntime-node

onnxruntime-node is an optional peer of @pipedog/onnx. onnxruntime-web@1.30.0 (WebAssembly) loads the same .ort files and gives the same transcripts, about 3x slower.

import { Model, MoonshineSTTService } from "@pipedog/moonshine/stt";

What you need

ModelsMoonshine's quantized ORT-format models from https://download.moonshine.ai/model/..., the files and versions moonshine_voice 0.1.5 uses. Each download is checked against Moonshine's published size and CRC32C
SizesEnglish: tiny 44 MB, tiny-streaming 45 MB, base 141 MB, small-streaming 142 MB (the default), medium-streaming 269 MB. Other languages: tiny-streaming 32 MB, small-streaming 122 MB, base 65-141 MB, tiny (ja, ko) 72 MB
DownloadAutomatic, once, when the pipeline starts (or on the first transcription)
Cache$PIPEDOG_CACHE_DIR/moonshine, else $XDG_CACHE_HOME/pipedog/moonshine, else ~/.cache/pipedog/moonshine, or the cacheRoot option
VADSilero VAD, the silero_vad.onnx shipped with @pipedog/audio-silero (a dependency), for the segmentation Moonshine applies inside each batch
Input16 kHz, 16-bit mono PCM, and a VAD in the pipeline

Files go in <cacheRoot>/download.moonshine.ai/model/<model>/..., the layout moonshine_voice uses, so you can copy a moonshine_voice cache over. To use files you already have, pass cacheRoot:

// Models already on disk, in <cacheRoot>/download.moonshine.ai/model/<model>/...
// (the layout moonshine_voice uses, so a moonshine_voice cache can be copied over).
const fromDisk = new MoonshineSTTService({
  cacheRoot: "/models/moonshine",
  settings: new MoonshineSTTService.Settings({ model: Model.TINY }),
});

Or give the engine any model directory directly (see Usage), or the model files in memory with transcriberOptions.files. downloadModel(language, arch?) downloads a model ahead of time.

Runtimes

RuntimeWorksWhat it needs
Bun✅ yesonnxruntime-node (or onnxruntime-web) and a filesystem for the model cache: models download on first use from download.moonshine.ai (tiny 44 MB; the default, small-streaming, 142 MB).
Node.js✅ yesonnxruntime-node (or onnxruntime-web) and a filesystem for the model cache: models download on first use from download.moonshine.ai (tiny 44 MB; the default, small-streaming, 142 MB).
Deno✅ yesonnxruntime-node (or onnxruntime-web) and a filesystem for the model cache: models download on first use from download.moonshine.ai (tiny 44 MB; the default, small-streaming, 142 MB).
Cloudflare Workers❌ noonnxruntime-node (or onnxruntime-web) and a filesystem for the model cache: models download on first use from download.moonshine.ai (tiny 44 MB; the default, small-streaming, 142 MB). Not supported: the models (32-269 MB) and their inference sessions exceed Workers' memory and bundle limits.

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

Cloudflare Workers is not supported: the models (32-269 MB) and their inference sessions don't fit a Worker's 128 MB of memory or its 10 MB bundle limit.

For the 11 s JFK clip (4 segments), on an Apple M4 Pro with Bun and onnxruntime-node: tiny 0.1 s, base 0.12 s, tiny-streaming 0.2 s, small-streaming 0.35 s.

MoonshineSTTService

Configuration

Prop

Type

transcriberOptions (pipedog only) takes the Transcriber's options: backend (any OnnxBackend), fetch for downloads, files (model files in memory), vadModel, shareSessions, and Moonshine's maxTokensPerSecond (6.5), vadThreshold (0.5), vadWindowDuration (0.5 s), vadHopSize (512), vadLookBehindSampleCount (8192) and vadMaxSegmentDuration (15 s).

Settings

Runtime-updatable settings, passed as settings: new MoonshineSTTService.Settings({...}) (MoonshineSTTSettings). Any setting you leave out keeps the default below. A change reloads the model, and keeps the old one if the new one fails to load.

SettingTypeDefaultDescription
modelModel | stringModel.SMALL_STREAMINGArchitecture: TINY, BASE, TINY_STREAMING, BASE_STREAMING, SMALL_STREAMING or MEDIUM_STREAMING (or the string, such as "small-streaming").
languageLanguageLanguage.ENArabic, Chinese, English, German, Japanese, Korean, Spanish, Tagalog, Ukrainian or Vietnamese. Regional variants resolve to the base code. An architecture a language doesn't publish falls back to that language's default, as upstream.

Usage

stt.ts
import { STTUpdateSettingsFrame } from "@pipedog/core/frames/frames";
import { Language } from "@pipedog/core/transcriptions/language";
import { Model, MoonshineSTTService } from "@pipedog/moonshine/stt";

// Downloads small-streaming (142 MB) from download.moonshine.ai on first start.
const stt = new MoonshineSTTService({
  settings: new MoonshineSTTService.Settings({
    model: Model.SMALL_STREAMING,
    language: Language.EN,
  }),
});

Change the language or model mid-call with an STTUpdateSettingsFrame:

// Queue it on the worker, or push it from a processor upstream of the service.
// A model or language change reloads the model.
const update = new STTUpdateSettingsFrame({
  delta: new MoonshineSTTService.Settings({ language: Language.ES }),
});

The engine works on its own, with any model directory:

import { ModelArch, Transcriber } from "@pipedog/moonshine/moonshine_voice";

const transcriber = new Transcriber("/models/tiny-en", ModelArch.TINY);
const samples = new Float32Array(16000); // mono audio in [-1, 1]
const transcript = await transcriber.transcribeWithoutStreaming(samples, 16000);
console.log(transcript.lines.map((line) => line.text).join(" "));

Notes

  • Emits one TranscriptionFrame per segment. There are no interim results.
  • Every model, streaming ones included, is run in one batch per segment (transcribeWithoutStreaming).
  • The constructor rejects a missing model or language, a model that names no architecture, and a language Moonshine publishes no model for.

Differences from upstream

  • The model is resolved in the constructor but downloaded and loaded in start() (or on the first transcription). Upstream downloads and loads it in the constructor.
  • Upstream also prefetches the optional English spelling model, which transcribe_without_streaming never uses. It is not downloaded.
  • Only what transcribe_without_streaming needs is ported: no streaming API (create_stream), word timestamps, speaker diarization, key-term biasing or spelling mode.
  • Inference runs on onnxruntime 1.30, where Moonshine bundles 1.23.2: the same graphs, so the same transcripts up to floating-point rounding.
  • cacheRoot and transcriberOptions are pipedog options.

Licenses

  • @pipedog/moonshine: BSD 2-Clause. The engine ports parts of Moonshine (MIT).
  • Models: MIT in every language and size, except the non-streaming models (tiny, base) for languages other than English, which are under the non-commercial Moonshine Community License. They are downloaded at runtime, not shipped.
  • Silero VAD (through @pipedog/audio-silero): MIT.

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