Skip to content

Commit d67d384

Browse files
committed
Create porcupine-wake-word-worker.js
1 parent 36f9f50 commit d67d384

File tree

1 file changed

+205
-0
lines changed

1 file changed

+205
-0
lines changed
Lines changed: 205 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,205 @@
1+
//imports
2+
importScripts('./shared/common.js');
3+
importScripts('./shared/ring-buffer.min.js');
4+
5+
importScripts('./picovoice/porcupine-wasm-interface.js');
6+
var PorcupineKeywords = {
7+
v14: {},
8+
v19: {}
9+
}
10+
11+
onmessage = function(e){
12+
//Audio worker interface
13+
//console.log("PorcupineWorker onmessage", e.data); //DEBUG
14+
if (e.data.ctrl){
15+
switch (e.data.ctrl.action){
16+
case "construct":
17+
constructWorker(e.data.ctrl.options);
18+
break;
19+
case "process":
20+
process(e.data.ctrl.data);
21+
break;
22+
case "handle":
23+
handleEvent(e.data.ctrl.data);
24+
break;
25+
case "start":
26+
start(e.data.ctrl.options);
27+
break;
28+
case "stop":
29+
stop(e.data.ctrl.options);
30+
break;
31+
case "reset":
32+
reset(e.data.ctrl.options);
33+
break;
34+
case "release":
35+
case "close":
36+
release(e.data.ctrl.options);
37+
break;
38+
default:
39+
console.log("Unknown control message:", e.data);
40+
break;
41+
}
42+
}
43+
//Custom interface
44+
if (e.data.gate != undefined){
45+
console.error("Message", e.data); //DEBUG
46+
gateControl(e.data.gate && e.data.gate == "open", e.data.gateOptions);
47+
}
48+
};
49+
50+
let porcupine;
51+
let porcupineVersion;
52+
let _Porcupine;
53+
54+
let inputSampleRate;
55+
let channelCount;
56+
let inputSampleSize;
57+
let processBufferSize; //defines '_processRingBuffer' size together with 'inputSampleSize'
58+
59+
let keywords;
60+
let sensitivities;
61+
62+
let gateIsOpen = false;
63+
let _gateOpenTS = 0;
64+
let _gateCloseTS = 0;
65+
66+
let _isFirstValidProcess = true;
67+
68+
function init(){
69+
if (inputSampleSize > processBufferSize){
70+
throw JSON.stringify(new BufferSizeException("Processor 'bufferSize' has to be bigger than 'inputSampleSize'! Currently: " + inputSampleSize + " > " + processBufferSize));
71+
//NOTE: this needs to be a string to show up in worker.onerror properly :-/
72+
}
73+
if (inputSampleRate != 16000){
74+
throw JSON.stringify(new SampleRateException("Input sample-rate needs to be 16000! Currently: " + inputSampleRate));
75+
}
76+
if (channelCount != 1){
77+
throw JSON.stringify(new ChannelCountException("Input needs to be MONO! Current channel-count: " + channelCount));
78+
}
79+
80+
_isFirstValidProcess = true;
81+
gateIsOpen = false;
82+
_gateOpenTS = 0;
83+
_gateCloseTS = 0;
84+
}
85+
function ready(){
86+
postMessage({
87+
moduleState: 1,
88+
moduleInfo: {
89+
inputSampleRate: inputSampleRate,
90+
channelCount: channelCount,
91+
inputSampleSize: inputSampleSize,
92+
processBufferSize: processBufferSize,
93+
porcupineVersion: porcupineVersion,
94+
keywords: keywords,
95+
sensitivities: sensitivities
96+
}
97+
});
98+
}
99+
100+
function gateControl(open, gateOptions){
101+
if (!gateOptions) gateOptions = {}; //TODO: use e.g. for (lookbackBufferNeedsReset = false)
102+
var msg = {
103+
moduleEvent: true, //use 'moduleEvent' to distinguish from normal processing result
104+
gate: {}
105+
};
106+
if (open){
107+
_gateOpenTS = Date.now();
108+
gateIsOpen = true;
109+
msg.gate.openedAt = _gateOpenTS;
110+
}else{
111+
_gateCloseTS = Date.now();
112+
gateIsOpen = false;
113+
msg.gate.openedAt = _gateOpenTS;
114+
msg.gate.closedAt = _gateCloseTS;
115+
}
116+
msg.gate.isOpen = gateIsOpen;
117+
postMessage(msg);
118+
}
119+
120+
//Interface
121+
122+
function constructWorker(options) {
123+
inputSampleRate = options.setup.inputSampleRate || options.setup.ctxInfo.targetSampleRate || options.setup.ctxInfo.sampleRate;
124+
channelCount = options.setup.channelCount || 1;
125+
126+
inputSampleSize = options.setup.inputSampleSize || 512;
127+
processBufferSize = options.setup.bufferSize || inputSampleSize;
128+
129+
porcupineVersion = options.setup.version || options.setup.porcupineVersion || 19;
130+
importScripts('./picovoice/porcupine-wasm-module-' + porcupineVersion + '.js');
131+
132+
keywords = options.setup.keywords || ["Computer"];
133+
keywords.forEach(function(kw){
134+
importScripts('./picovoice/porcupine-keywords/' + kw.toLowerCase() + "_wasm_" + porcupineVersion + '.js');
135+
});
136+
sensitivities = options.setup.sensitivities || [0.5];
137+
138+
var wasmFileArrayBuffer = options.preLoadResults.wasmFile;
139+
var defaultWasmBinaryFile = ""; //don't change
140+
PorcupineBuilder(wasmFileArrayBuffer, defaultWasmBinaryFile, function(buildResult){
141+
_Porcupine = buildResult;
142+
143+
var keywordsWithData = PorcupineKeywords["v" + porcupineVersion];
144+
keywords = Object.keys(keywordsWithData);
145+
porcupine = _Porcupine.create(Object.values(keywordsWithData), new Float32Array(sensitivities));
146+
147+
init();
148+
149+
ready();
150+
});
151+
}
152+
153+
function process(data) {
154+
//data, e.g.: samples, sampleRate, parameters
155+
if (data && data.samples){
156+
if (_isFirstValidProcess){
157+
//console.error("data info", data); //DEBUG
158+
_isFirstValidProcess = false;
159+
//check: inputSampleRate, inputSampleSize, channelCount, float32
160+
if (data.sampleRate != inputSampleRate){
161+
var msg = "Sample-rate mismatch! Should be '" + inputSampleRate + "' is '" + data.sampleRate + "'";
162+
console.error("Audio Worker sample-rate exception - Msg.: " + msg);
163+
throw JSON.stringify(new SampleRateException(msg)); //NOTE: this needs to be a string to show up in worker.onerror properly :-/
164+
return;
165+
}
166+
var inputArrayType = data.type || data.samples[0].constructor.name;
167+
if (inputArrayType.indexOf("Int16") != 0){
168+
var msg = "Array type mismatch! Input samples are of type '" + inputArrayType + "' but expected Int16";
169+
console.error("Audio Worker type exception - Msg.: " + msg);
170+
throw JSON.stringify(new ArrayTypeException(msg));
171+
return;
172+
}
173+
}
174+
if (gateIsOpen){
175+
let keywordIndex = porcupine.process(data.samples[0]);
176+
if (keywordIndex !== -1) {
177+
postMessage({
178+
keyword: keywords[keywordIndex]
179+
});
180+
}
181+
}
182+
}
183+
}
184+
function handleEvent(data){
185+
//data that should not be processed but might trigger an event
186+
}
187+
188+
function start(options) {
189+
//TODO: anything to do?
190+
//NOTE: timing of this signal is not very well defined, use only for gating or similar stuff!
191+
}
192+
function stop(options) {
193+
//TODO: anything to do?
194+
//NOTE: timing of this signal is not very well defined, use only for gating or similar stuff!
195+
}
196+
function reset(options) {
197+
//TODO: clean up worker and prep. for restart
198+
}
199+
function release(options){
200+
//TODO: clean up worker and close
201+
keywords = null;
202+
sensitivities = null;
203+
porcupine = null;
204+
_Porcupine = null;
205+
}

0 commit comments

Comments
 (0)