wasmPorcupine - Web API

  • Wake Word Engine
  • Offline Voice Commands
  • Local Speech Recognition
  • Always Listening
  • npm

This document outlines how to integrate the Porcupine wake word engine within an application using its Web API.

Requirements

  • yarn (or npm)
  • Secure browser context (i.e. HTTPS or localhost)

Compatibility

  • Chrome, Edge
  • Firefox
  • Safari

The Picovoice SDKs for Web are powered by WebAssembly (WASM), the Web Audio API, and Web Workers. All audio processing is performed in-browser, providing intrinsic privacy and reliability.

All modern browsers are supported, including on mobile. Internet Explorer is not supported.

Using the Web Audio API requires a secure context (HTTPS connection), with the exception of localhost, for local development.

JavaScript Frameworks

Looking to use Porcupine with React, Angular, or Vue? There are framework-specific packages available:

The framework-specific packages operate at a higher level of abstraction and are meant to integrate as quickly and easily as possible, following each framework's conventions and best practices. Furthermore, the demo applications show the complete lifecycle of using Porcupine in a component, including setup and teardown. Interacting with Web Workers is hidden behind a facade, and the Web Voice Processor is also coordinated behind the scenes to automatically setup the microphone.

Otherwise, this doc will provide instruction on how to use Porcupine with "Vanilla" JavaScript and HTML, connecting it to the Web Voice Processor for microphone use.

Introduction

Porcupine for Web is available in two flavors: Worker and Factory:

  • The Worker packages are all-in-one Web Workers which wrap Porcupine instances that will work with the web-voice-processor (and the Angular, React, and Vue packages).
  • The Factory packages give you access to instances directly. This is useful if you wish to build your own worker/worklet, or perhaps use Porcupine in some other custom scenario.

Structure

The Porcupine SDK for Web is provided in several npm packages, due to the logistics and size of shipping ~1-2MB voice models.

Workers

For typical cases, use the worker packages. Worker packages create complete self-contained Porcupine Web Worker instances that can be immediately used with @picovoice/web-voice-processor and with the Angular, React, and Vue packages.

Factories

Factory packages allow you to create instances of Porcupine directly. Useful for building your own custom Worker/Worklet, or some other bespoke purpose.

Installation & Usage

Worker: Using modern JavaScript, ES Modules, Bundlers (e.g. Webpack)

To obtain a Porcupine Worker, we can use the static create factory method from the PorcupineWorkerFactory. Here is a complete example that:

  1. Obtains a Worker from the PorcupineWorkerFactory (in this case, English) to listen for the built-in English wake word "Picovoice"
  2. Responds to the wake word detection event by setting the worker's onmessage event handler
  3. Starts up the WebVoiceProcessor to forward microphone audio to the Porcupine Worker

E.g.:

yarn add @picovoice/web-voice-processor @picovoice/porcupine-web-en-worker
import { WebVoiceProcessor } from "@picovoice/web-voice-processor"
import { PorcupineWorkerFactory } from "@picovoice/porcupine-web-en-worker";
async startPorcupine()
// Create a Porcupine Worker (English language) to listen for
// the built-in keyword "Picovoice", at a sensitivity of 0.65
// Note: you receive a Worker object, _not_ an individual Porcupine instance
const porcupineWorker = await PorcupineWorkerFactory.create(
[{builtin: "Picovoice", sensitivity: 0.65}]
);
// The worker will send a message with data.command = "ppn-keyword" upon a detection event
// Here we tell it to log it to the console
porcupineWorker.onmessage = (msg) => {
switch (msg.data.command) {
case 'ppn-keyword':
// Porcupine keyword detection
console.log("Porcupine detected " + msg.data.keywordLabel);
break;
default:
break;
}
};
// Start up the web voice processor. It will request microphone permission
// and immediately (start: true) start listening.
// It downsamples the audio to voice recognition standard format (16-bit 16kHz linear PCM, single-channel)
// The incoming microphone audio frames will then be forwarded to the Porcupine Worker
// n.b. This promise will reject if the user refuses permission! Make sure you handle that possibility.
const webVp = await WebVoiceProcessor.init({
engines: [porcupineWorker],
start: true,
});
}
}
startPorcupine()
...
// Finished with Porcupine? Release the WebVoiceProcessor and the worker.
if (done) {
webVp.release()
porcupineWorker.sendMessage({command: "release"})
}

Worker: Script Tag / IIFE / CDN

Porcupine's worker and factory packages are also available in IIFE format, intended for direct inclusion into HTML instead of a bundler. You can use local node modules, or use the CDN unpkg version for rapid prototyping:

<!DOCTYPE html>
<html lang="en">
<head>
<script src="https://unpkg.com/@picovoice/porcupine-web-en-worker/dist/iife/index.js"></script>
<script src="https://unpkg.com/@picovoice/porcupine-web-de-worker/dist/iife/index.js"></script>
<script src="https://unpkg.com/@picovoice/porcupine-web-fr-worker/dist/iife/index.js"></script>
<script src="https://unpkg.com/@picovoice/porcupine-web-es-worker/dist/iife/index.js"></script>
<script src="https://unpkg.com/@picovoice/web-voice-processor/dist/iife/index.js"></script>
<script type="application/javascript">
function writeMessage(message) {
console.log(message)
let p = document.createElement("p")
let text = document.createTextNode(message)
p.appendChild(text)
document.body.appendChild(p)
}
async function startPorcupine() {
writeMessage("Porcupine is loading. Please wait...")
let ppnEn = await PorcupineWebEnWorker.PorcupineWorkerFactory.create([
{
builtin: PorcupineWebEnWorker.BuiltInKeyword.Picovoice,
sensitivity: 0.65,
},
{
custom: "papaya whip",
sensitivity: 0.7,
base64:
"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",
},
])
let ppnDe = await PorcupineWebDeWorker.PorcupineWorkerFactory.create({
builtin: "Ananas",
sensitivity: 0.7,
})
let ppnEs = await PorcupineWebEsWorker.PorcupineWorkerFactory.create({
builtin: "Manzana",
sensitivity: 0.6,
})
let ppnFr = await PorcupineWebFrWorker.PorcupineWorkerFactory.create([
"Framboise",
])
writeMessage("Porcupine worker ready!")
const keywordDetection = msg => {
if (msg.data.command === "ppn-keyword") {
writeMessage("Keyword detected: " + msg.data.keywordLabel)
}
}
ppnEn.onmessage = keywordDetection
ppnEs.onmessage = keywordDetection
ppnFr.onmessage = keywordDetection
ppnDe.onmessage = keywordDetection
writeMessage(
"WebVoiceProcessor initializing. Microphone permissions requested ..."
)
try {
let webVp = await window.WebVoiceProcessor.WebVoiceProcessor.init({
engines: [ppnEn, ppnEs, ppnFr, ppnDe],
})
writeMessage("WebVoiceProcessor ready and listening!")
} catch (e) {
writeMessage("WebVoiceProcessor failed to initialize: " + e)
}
}
document.addEventListener("DOMContentLoaded", function () {
startPorcupine()
})
</script>
</head>
<body></body>
</html>

Factory

If you wish to build your own worker, or perhaps not use workers at all, use the factory packages. This will let you instantiate Porcupine engine instances directly.

The audio passed to the worker must be of the correct format. The WebVoiceProcessor handles downsampling in the examples above. If you are not using that, you must ensure you do it yourself.

E.g.:

import { Porcupine } from "@picovoice/porcupine-web-en-worker"
async function startPorcupine() {
const handle = await Porcupine.create([
{ builtin: "Bumblebee", sensitivity: 0.7 },
])
// Send Porcupine frames of audio (check handle.frameLength for size of array)
const audioFrames = new Int16Array(/* Provide data with correct format and size */)
const porcupineResult = handle.process(audioFrames)
// porcupineResult: -1 if no detection occurred, otherwise returns the index of the item in the array
// (0 in this case for "Bumblebee")
}
startPorcupine()

Porcupine Factory Parameters

Porcupine (via the factory or worker factory) accepts an array of (or a single) PorcupineKeyword object argument(s). These arguments are passed to the static create method, which returns a promise that will resolve to a Porcupine instance or Porcupine Web Worker instance, respectively.

PorcupineKeyword can be either PorcupineKeywordBuiltin or PorcupineKeywordCustom:

PorcupineKeywordBuiltin

export type PorcupineKeywordBuiltin = {
builtin: string
sensitivity?: number
}

Where builtin is one of the built-in keywords for the language. These are the built-in keywords for each language:

EnglishFrenchGermanSpanish
  • "Americano"
  • "Blueberry"
  • "Bumblebee"
  • "Grapefruit"
  • "Grasshopper"
  • "Hey Google"
  • "Hey Siri"
  • "Jarvis"
  • "Okay Google"
  • "Picovoice"
  • "Porcupine"
  • "Terminator"
  • "Framboise"
  • "Mon Chouchou"
  • "Parapluie"
  • "Perroquet"
  • "Tournesol"
  • "Ananas"
  • "Heuschrecke"
  • "Himbeere"
  • "Leguan"
  • "Stachelschwein"
  • "Emparedado"
  • "Leopardo"
  • "Manzana"
  • "Murcielago"

If you simply pass a string of a builtin keyword instead of an object, that will also work.

PorcupineKeywordCustom

export type PorcupineKeywordCustom = {
base64: string
custom: string
sensitivity?: number
}

Where base64 is the base64 representation of a trained Porcupine keyword file (i.e. a .ppn file) and custom is an arbitrary label that you want Porcupine to report when the detection occurs.

The optional sensitivity parameter is a value in [0,1] that trades miss rate for false alarm rate. The default is 0.5 if unspecified.

Custom Wake Words

You can create custom wake word models using Picovoice Console. Making Porcupine wake word models for the Web platform requires an Enterprise account.


Issue with this doc? Please let us know.