npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

openwakeword-web

v0.1.0

Published

Native browser (JavaScript) port of openWakeWord. Runs wake word detection fully client-side using ONNX Runtime Web.

Downloads

165

Readme

openWakeWord — native browser port

A native JavaScript / browser port of openWakeWord. Wake word detection runs fully client-side in the browser using ONNX Runtime Web — no Python server, no websocket streaming.

It loads the exact same ONNX models as the Python package (melspectrogram, speech embedding, and the pre-trained wake word models) and faithfully reimplements the streaming feature pipeline (AudioFeatures) and prediction logic (Model.predict) in JavaScript.

Looking for the server-based approach instead? See [examples/web](../examples/web) for streaming microphone audio over a websocket into openWakeWord running in a Python backend.

Pipeline

mic (16 kHz, 16-bit PCM)
  └─ melspectrogram.onnx   → mel features (32 bins), transformed x/10 + 2
       └─ embedding_model.onnx → 96-dim speech embeddings (76-frame windows, step 8)
            └─ <wakeword>.onnx  → detection score 0..1

Quick start

cd web
npm install                 # installs onnxruntime-web
npm run download-models     # fetches the ONNX models into ./models
npm run serve               # or any static file server
# open http://localhost:8080/demo/

Then open the page, click Start Listening, and say "hey jarvis", "alexa", "hey mycroft", or "hey rhasspy".

The demo loads onnxruntime-web from a CDN via an import map, so it works without a bundler. A static server is still required because ES modules and the AudioWorklet cannot be loaded from file://.

Two hard requirements (browser rules)

  • Must be served from http://localhost or https://. ES modules, AudioWorklet, and microphone access all require a real, secure origin, so opening the file directly via file:// will not work.
  • Model files must be same-origin (or served with CORS headers). The upstream GitHub release assets do not send Access-Control-Allow-Origin, so you cannot point baseUrl directly at the GitHub release — host the .onnx files yourself (which is what npm run download-models sets up).

Library usage

npm install openwakeword-web
import { OpenWakeWord } from "openwakeword-web";
import { Microphone } from "openwakeword-web/microphone";

const oww = await OpenWakeWord.create({
  baseUrl: "./models/",
  wakewordModels: ["hey_jarvis", "alexa"], // or omit for all pre-trained models
  threshold: 0.5,
  onDetection: ({ label, score }) => {
    console.log(`Wake word detected: ${label} (score ${score.toFixed(2)})`);
  },
});

const mic = new Microphone(async (frame) => {
  // frame is an Int16Array of 1280 samples (80 ms @ 16 kHz)
  await oww.predict(frame); // onDetection fires automatically when threshold is met
});

await mic.start();

predict() accepts any Int16Array of 16 kHz PCM audio (ideally multiples of 1280 samples) and returns a { label: score } map, matching the Python API. The onDetection callback fires inside predict() for every label whose score meets threshold — so you don't have to poll the returned scores yourself.

Both threshold and onDetection can be updated at runtime:

oww.threshold = 0.75;
oww.onDetection = ({ label }) => triggerAssistant(label);

If you prefer the polling style, simply omit onDetection and inspect the scores returned by predict() directly:

const scores = await oww.predict(frame);
if (scores["hey_jarvis"] >= 0.5) console.log("detected hey jarvis!");

Utterance capture (wake word + command)

Pass onUtterance to automatically record audio from the moment a wake word is detected until the user stops speaking (via Silero VAD), then receive the full utterance as a single Int16Array. Feed it to any speech-to-text API.

const oww = await OpenWakeWord.create({
  baseUrl: "./models/",          // must also contain silero_vad.onnx
  wakewordModels: ["hey_jarvis"],
  onUtterance: async ({ label, audio }) => {
    // `audio` is Int16Array: wake word + user command, 16 kHz PCM
    console.log(`Captured ${audio.length / 16000}s after "${label}"`);
    await sendToSpeechToText(audio);
  },
  // optional tuning:
  vadStopThreshold: 0.5,  // VAD score below this = silence (default 0.5)
  vadStopFrames: 6,        // consecutive silent frames before firing (~480 ms)
  maxCaptureDuration: 10,  // hard cap in seconds (default 10)
});

const mic = new Microphone(async (frame) => {
  await oww.predict(frame);
});
await mic.start();

onDetection and onUtterance can be used together — onDetection fires immediately when the threshold is crossed; onUtterance fires later once speech ends.

Model file: onUtterance requires silero_vad.onnx to be served from baseUrl (or override with vadUrl). The file is included when you run npm run download-models.

Bundlers: Microphone loads its AudioWorklet via new URL("./mic-worklet.js", import.meta.url), which Vite and webpack 5 handle automatically. If your bundler does not emit the worklet asset, copy mic-worklet.js somewhere same-origin and pass it explicitly: new Microphone(onFrame, { workletUrl: "/mic-worklet.js" }).

TypeScript type definitions (.d.ts) ship with the package, so the API is fully typed out of the box.

Usage in React (Vite)

Place the .onnx model files in public/assets/models/ so Vite serves them as static assets, then use a useEffect hook to own the lifecycle:

// src/hooks/useWakeWord.ts
import { useEffect, useRef } from "react";
import { OpenWakeWord, configureOrt } from "openwakeword-web";
import type { DetectionEvent, UtteranceEvent } from "openwakeword-web";
import { Microphone } from "openwakeword-web/microphone";

interface UseWakeWordOptions {
  wakewordModels?: string[];
  threshold?: number;
  onDetection?: (e: DetectionEvent) => void;
  /** Receives the full utterance (wake word + command) once speech ends. */
  onUtterance?: (e: UtteranceEvent) => void;
  vadStopThreshold?: number;
  vadStopFrames?: number;
}

export function useWakeWord({
  wakewordModels = ["hey_jarvis", "alexa"],
  threshold = 0.5,
  onDetection,
  onUtterance,
  vadStopThreshold = 0.5,
  vadStopFrames = 6,
}: UseWakeWordOptions = {}) {
  // Stable refs so callbacks never cause re-initialisation.
  const onDetectionRef = useRef(onDetection);
  onDetectionRef.current = onDetection;
  const onUtteranceRef = useRef(onUtterance);
  onUtteranceRef.current = onUtterance;

  useEffect(() => {
    let oww: OpenWakeWord | null = null;
    let mic: Microphone | null = null;
    let cancelled = false;

    configureOrt({ numThreads: 1 }); // avoid COOP/COEP requirement

    OpenWakeWord.create({
      baseUrl: "/assets/models/",
      wakewordModels,
      threshold,
      onDetection: onDetectionRef.current
        ? (e) => onDetectionRef.current!(e)
        : undefined,
      onUtterance: onUtteranceRef.current
        ? (e) => onUtteranceRef.current!(e)
        : undefined,
      vadStopThreshold,
      vadStopFrames,
    }).then((instance) => {
      if (cancelled) return;
      oww = instance;
      mic = new Microphone(async (frame) => {
        await oww!.predict(frame);
      });
      mic.start();
    });

    return () => {
      cancelled = true;
      mic?.stop();
      oww?.reset();
    };
  }, [threshold, vadStopThreshold, vadStopFrames, ...wakewordModels]);
}
// src/App.tsx
import { useWakeWord } from "./hooks/useWakeWord";

export default function App() {
  useWakeWord({
    wakewordModels: ["hey_jarvis", "alexa"],
    onDetection: ({ label }) => console.log(`Detected: ${label}`),
    onUtterance: async ({ label, audio }) => {
      // audio is Int16Array (16 kHz PCM): wake word + user command
      console.log(`Utterance after "${label}": ${audio.length / 16000}s`);
      // e.g. send to Whisper or any STT API
    },
  });

  return <div>Listening for wake words…</div>;
}

Model files — copy the .onnx files into public/assets/models/:

public/
  assets/
    models/
      melspectrogram.onnx
      embedding_model.onnx
      silero_vad.onnx        ← required for onUtterance
      hey_jarvis_v0.1.onnx
      alexa_v0.1.onnx

You can copy them from the web/models/ directory after running npm run download-models in this package, or point baseUrl at any same-origin path that serves the files with correct CORS headers.

Custom models

const oww = await OpenWakeWord.create({
  wakewordModels: [
    "alexa",                                   // pre-trained, by name
    { name: "my_word", url: "/models/my_word.onnx" }, // your own trained model
  ],
});

Using your own ONNX runtime / wasm hosting

import { configureOrt } from "./src/openwakeword.js";
configureOrt({ wasmPaths: "/ort/", numThreads: 1 });

Multi-threaded wasm requires the page to be cross-origin isolated (COOP/COEP headers). The demo uses numThreads: 1 to avoid that requirement.

Layout

| Path | Purpose | | ----------------------------- | ------------------------------------------------------- | | src/openwakeword.js | Main OpenWakeWord class — model loading + predict() | | src/audio-features.js | Streaming melspectrogram + embedding pipeline | | src/models.js | Pre-trained model registry + class mappings | | src/microphone.js | Mic capture helper (16 kHz AudioContext + worklet) | | src/mic-worklet.js | AudioWorklet: float → 16-bit PCM framing | | demo/index.html | Live detection demo UI (uses the src/ modules) | | scripts/download-models.mjs | Downloads the ONNX models from the GitHub release | | test/verify.mjs | Node script that runs the port on the repo's test clips |

Verification

test/verify.mjs streams the repository's test clips (tests/data/alexa_test.wav, tests/data/hey_mycroft_test.wav) through the port under Node and checks the correct wake word fires:

cd web
npm test
# alexa_test.wav       → alexa = 1.000        PASS
# hey_mycroft_test.wav → hey_mycroft = 1.000  PASS

Browser support

Requires AudioWorklet, ES modules, and AudioContext({ sampleRate: 16000 }) — supported by current Chrome, Edge, Firefox, and Safari. A secure context (https:// or localhost) is required for microphone access.

License

Apache-2.0, same as openWakeWord. The bundled model files are downloaded from the upstream openWakeWord releases and retain their original licenses.