|
| 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