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

speaklet

v1.0.1

Published

Vosklet-based voice challenge toolkit for the browser: offline speech recognition (bundled monosklet/Vosklet engine) unified with on-device speaker verification. Microphone capture, enrollment, and verification in one API.

Readme

speaklet

An on-device voice-challenge toolkit for browsers and WebViews. speaklet joins offline speech recognition, microphone capture, enrollment, speaker verification, and speaker identification behind one application API.

speaklet is built on Vosklet. Speech-to-text is provided by the bundled monosklet engine, which vendors the Vosklet Vosk + Kaldi WebAssembly runtime from the Devrax/Vosklet fork. speaklet adds capture and NeXt-TDNN speaker verification through @jaehyun-ko/speaker-verification and onnxruntime-web.

Everything runs on the device. You decide whether the speech and speaker models are bundled with the application or downloaded and cached on first use.

Install

npm install speaklet
# or
pnpm add speaklet

Installing monosklet separately is not required. Its worker engine and single-thread Vosklet runtime are bundled into speaklet.

Required application setup

speaklet needs three runtime assets:

  1. A Vosk speech model repackaged as USTAR TAR.
  2. A NeXt-TDNN ONNX speaker model, either from the default remote URL or your own URL.
  3. The onnxruntime-web Wasm binaries served by your application.

1. Prepare a speech model

Choose a language from alphacephei.com/vosk/models, then convert the downloaded ZIP archive:

unzip vosk-model-small-es-0.42.zip
cd vosk-model-small-es-0.42
tar --format=ustar -cf ../es-small.tar .

Put the TAR file in your application's static assets, for example public/models/es-small.tar. Android application assets should use plain .tar, not .tar.gz.

2. Serve the ONNX Runtime Wasm files

Copy these files from node_modules/onnxruntime-web/dist/ into a static directory served as /ort/:

ort-wasm.wasm
ort-wasm-simd.wasm

For example, a Vite application can copy them into public/ort/ as part of its setup or build script. If they are served elsewhere, pass an absolute verifier.wasmPaths URL.

3. Configure the ort bundler alias

@jaehyun-ko/speaker-verification imports an external dependency named ort. Map it to onnxruntime-web in the consuming application:

// vite.config.js
import { defineConfig } from "vite";

export default defineConfig({
  resolve: {
    alias: {
      ort: "onnxruntime-web"
    }
  }
});

Other bundlers need the equivalent alias. The repository's Examples/demo/vite.config.js shows a complete Vite plugin that serves the files in development and copies them into production builds.

Quick start: enroll, then verify

This example records two separate utterances: the first enrolls a voice, and the second verifies it.

import { createVoskletSpeaker } from "speaklet";

const enrollmentText =
  "Confirmo que esta es mi voz y autorizo guardarla como referencia";

const speaker = await createVoskletSpeaker({
  model: {
    url: "/models/es-small.tar",
    id: "vosk-model-small-es-0.42"
  },
  verifier: {
    model: "standard-384",
    wasmPaths: new URL("/ort/", window.location.origin).href
  },
  capture: {
    speechThreshold: 0.015,
    stopAfterSpoken: 3_000
  }
});

async function recordOnce() {
  const capture = await speaker.record();
  const recording = await capture.result;

  if (!recording) {
    throw new Error("Recording was cancelled");
  }

  return recording;
}

try {
  // Optional: download or restore the ONNX model before the user records.
  await speaker.warmUp((source) => console.log(`Speaker model: ${source}`));

  // 1. Enrollment recording.
  const enrollmentRecording = await recordOnce();
  const { text: enrollmentTranscript } =
    await speaker.transcribe(enrollmentRecording);

  const enrollment = await speaker.enroll(
    {
      wav: enrollmentRecording.wav,
      text: enrollmentTranscript
    },
    {
      expectedText: enrollmentText,
      matchThreshold: 0.65,
      id: "current-user",
      label: "Current user"
    }
  );

  if (!enrollment.accepted) {
    throw new Error(`Enrollment reading matched only ${enrollment.overlap}`);
  }

  // 2. A different, later recording.
  const verificationRecording = await recordOnce();
  const { text } = await speaker.transcribe(verificationRecording);
  const verification = await speaker.verify(verificationRecording.wav, {
    id: "current-user"
  });

  console.log({ text, ...verification });
  // { text, id, label, score, match, threshold }
} finally {
  await speaker.dispose();
}

record() auto-stops after speech followed by silence. Its returned handle also provides stop() for a stop button and cancel() to discard the recording. The WAV is created before transcribe() transfers the PCM block buffers to the worker, so it remains available for enroll(), verify(), or identify().

For a complete interface with UI state, progress, errors, manual stop, and model status, see Examples/demo/src/speaker.js.

Which speaker engine is being used?

speaklet contains two distinct speaker-processing paths:

| Path | Speech-to-text | Speaker embeddings | What you implement | | --- | --- | --- | --- | | speaker.enroll(), verify(), identify() | Vosk + Kaldi through monosklet | External NeXt-TDNN ONNX models | High-level API handles enrollment, persistence, comparison, and thresholds | | createVoskletMonoWorker() / engine.loadSpkModel() | Vosk + Kaldi through monosklet | Native Vosk x-vectors | Your application stores, averages, and compares raw embeddings |

Therefore, speaklet's high-level speaker methods do not use monosklet's native Vosk speaker-recognition model. Vosk + Kaldi recognizes the words; @jaehyun-ko/speaker-verification and onnxruntime-web produce and compare the voice embeddings.

The bundled monosklet functionality is not restricted. speaklet re-exports createVoskletMonoWorker() and supportsWorkerHost() for applications that want the native Vosk x-vector API. The default suite's worker is also available at speaker.engine at run time, but no high-level speaklet method wraps loadSpkModel().

Configure the models

Vosk speech model

Use a local model for a fully offline installation:

const speaker = await createVoskletSpeaker({
  model: {
    url: "/models/es-small.tar",
    id: "vosk-model-small-es-0.42",
    storagePath: "Spanish"
  }
});

Or download one from your own server on first use:

const speaker = await createVoskletSpeaker({
  model: {
    url: "https://cdn.example.com/vosk-model-small-es-0.42.tar.gz",
    id: "vosk-model-small-es-0.42"
  }
});

The archive is cached under id; change the ID when replacing model contents at the same URL.

NeXt-TDNN speaker model

The following aliases are provided by @jaehyun-ko/speaker-verification:

| Alias | Approximate download | Notes | | --- | ---: | --- | | standard-384 | 27 MB | Default; highest-capacity standard option | | standard-256 | 28 MB | Standard model | | standard-192 | 16 MB | Smaller standard model | | standard-128 | 7.5 MB | Compact standard model | | mobile-256 | 20 MB | Mobile architecture | | mobile-128 | 5 MB | Smallest; useful for WebView applications |

With only an alias, the model is downloaded from the project's default Hugging Face repository and cached on first use:

verifier: { model: "mobile-128", wasmPaths: "/ort/" }

To avoid a runtime dependency on Hugging Face, host the matching ONNX file:

verifier: {
  model: "standard-384",
  modelUrl: "/models/NeXt_TDNN_C384_B1_K65_7.onnx",
  wasmPaths: new URL("/ort/", window.location.origin).href
}

Keep the alias consistent with the selected model file. Embeddings from different models are not comparable, and changing models requires enrollment again. wasmPaths should be absolute when the application has pages below a subpath such as /speaker/.

Multiple speakers

Enroll each person under a stable ID, optionally with a display label:

await speaker.enroll(
  { wav: rafaRecording.wav, text: rafaTranscript },
  { id: "rafa", label: "Rafael", expectedText: enrollmentText }
);

await speaker.enroll(
  { wav: anaRecording.wav, text: anaTranscript },
  { id: "ana", label: "Ana", expectedText: enrollmentText }
);

const identified = await speaker.identify(probeRecording.wav);
// {
//   id: "ana", label: "Ana", score: 0.71, match: true, threshold: 0.5,
//   scores: [{ id: "ana", ... }, { id: "rafa", score: 0.22, ... }]
// }

speaker.listSpeakers();
await speaker.verify(probeRecording.wav, { id: "rafa" }); // one-to-one
speaker.clearReference("ana");
speaker.clearAllReferences();

Calls without an ID use the "default" speaker.

Persistence and privacy

Reference embeddings are stored in localStorage by default. Enrollment audio is not persisted unless explicitly requested:

await speaker.enroll(
  { wav: recording.wav, text },
  {
    id: "rafa",
    label: "Rafael",
    expectedText: enrollmentText,
    persist: true
  }
);

const wav = await speaker.loadEnrollmentAudio("rafa");
await speaker.clearEnrollmentAudio("rafa"); // keeps the embedding
speaker.clearReference("rafa");             // removes both

Audio persistence uses Cache Storage and is best-effort. On origins where that API cannot be used, such as some capacitor:// environments, enrollment still succeeds and loadEnrollmentAudio() returns null.

Voice recordings and embeddings are biometric data. Tell users what is stored, protect application access, and provide a way to clear it.

Quick API guide

High-level suite

const speaker = await createVoskletSpeaker({
  model: { url, id, storagePath },
  engine,         // optional existing monosklet-compatible engine
  engineOptions,  // options used when speaklet creates its worker
  verifier,       // NeXt-TDNN/ONNX options
  capture         // defaults merged into every record() call
});

| API | Purpose | | --- | --- | | speaker.warmUp(onStatus?) | Load the ONNX speaker model before first use | | speaker.record(options?) | Start microphone capture; returns a capture handle | | speaker.transcribe(recording, options?) | Recognize the captured PCM through monosklet | | speaker.enroll(input, options?) | Gate an enrollment by expected text and save its embedding | | speaker.verify(audio, options?) | Compare audio with one enrolled speaker | | speaker.identify(audio, options?) | Rank audio against every enrolled speaker | | speaker.listSpeakers() | Return { id, label? } entries | | speaker.hasReference(id?) | Check whether a speaker is enrolled | | speaker.loadEnrollmentAudio(id?) | Load audio saved with persist: true | | speaker.clearEnrollmentAudio(id?) | Remove saved audio but keep its embedding | | speaker.clearReference(id?) | Remove one embedding and its saved audio | | speaker.clearAllReferences() | Remove every enrollment | | speaker.dispose() | Close the shared AudioContext and owned worker engine |

enroll() resolves { accepted, overlap, embedding? }. verify() resolves { id, label?, score, match, threshold }. identify() adds a best-first scores array.

Capture handle

| API | Purpose | | --- | --- | | capture.result | Resolves to { blocks, wav, sampleRate, reason }, or null after cancellation | | capture.stop() | Finish immediately and return the audio captured so far | | capture.cancel() | Discard the recording and resolve result with null |

record() accepts an existing stream or audioContext. Values provided by the application are left open during capture teardown.

Lower-level exports

| Export | Purpose | | --- | --- | | startCapture(engine, options) | Use speaklet capture with your own compatible engine | | createSpeakerVerifier(options) | Use NeXt-TDNN enrollment and comparison without speech recognition | | createLocalStorageReferenceStore(key) | Create the default-style multi-speaker store | | encodeWav(blocks, sampleRate) | Encode mono Float32 PCM as 16-bit WAV | | normalizeText, textsMatch, wordOverlap | Compare challenge transcripts despite case, accents, and punctuation | | createVoskletMonoWorker, supportsWorkerHost | Access the bundled monosklet worker directly | | DEFAULT_SAME_SPEAKER_THRESHOLD, DEFAULT_SPEAKER_ID | Default verification constants |

createSpeakerVerifier() additionally exposes init, embed, compare, enroll, verify, identify, reference management, and persisted-enrollment audio management. The published TypeScript declarations document the complete option and result shapes.

Run the repository demo

From a fresh clone of the monorepo:

pnpm run setup
pnpm run demo

setup installs dependencies, builds the packages, packs the tarballs consumed by the demo, and downloads the uncommitted model assets. Open the speaker/ page for the high-level speaklet flow or spk/ for native Vosk x-vectors.

License and third-party software

MIT—see LICENSE.

The Vosklet lineage and the Vosk, Kaldi, OpenFST, OpenBLAS, NeXt-TDNN, and ONNX Runtime components are documented in THIRD_PARTY_NOTICES.md, which ships in the npm package. Models have their own licenses; verify them before redistribution.