UNPKG

@picovoice/porcupine-node

Version:

Picovoice Porcupine Node.js binding

196 lines 8.93 kB
// // Copyright 2020-2023 Picovoice Inc. // // You may not use this file except in compliance with the license. A copy of the license is located in the "LICENSE" // file accompanying this source. // // Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on // an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the // specific language governing permissions and limitations under the License. // "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const fs = require("fs"); const path = require("path"); const pv_status_t_1 = require("./pv_status_t"); const errors_1 = require("./errors"); const platforms_1 = require("./platforms"); const builtin_keywords_1 = require("./builtin_keywords"); const MODEL_PATH_DEFAULT = "../lib/common/porcupine_params.pv"; class Porcupine { _pvPorcupine; _handle; _version; _sampleRate; _frameLength; /** * Creates an instance of Porcupine. * @param {string} accessKey AccessKey obtained from Picovoice Console (https://console.picovoice.ai/). * @param {Array} keywords Absolute paths to keyword model files (`.ppn`). * @param {Array} sensitivities Sensitivity values for detecting keywords. * Each value should be a number within [0, 1]. A higher sensitivity results in fewer misses at the cost of increasing the false alarm rate. * @param {string} manualModelPath Absolute path to the file containing model parameters (`.pv`). * @param {string} manualLibraryPath Absolute path to Porcupine's dynamic library (platform-dependent extension). */ constructor(accessKey, keywords, sensitivities, manualModelPath, manualLibraryPath) { if (accessKey === null || accessKey === undefined || accessKey.length === 0) { throw new errors_1.PorcupineInvalidArgumentError(`No AccessKey provided to Porcupine`); } let modelPath = manualModelPath; if (modelPath === undefined || modelPath === null) { modelPath = path.resolve(__dirname, MODEL_PATH_DEFAULT); } let libraryPath = manualLibraryPath; if (libraryPath === undefined || modelPath === null) { libraryPath = (0, platforms_1.getSystemLibraryPath)(); } if (keywords === null || keywords === undefined || keywords.length === 0) { throw new errors_1.PorcupineInvalidArgumentError(`keywordPaths are null/undefined/empty (${keywords})`); } if (sensitivities === null || sensitivities === undefined || sensitivities.length === 0) { throw new errors_1.PorcupineInvalidArgumentError(`sensitivities are null/undefined/empty (${sensitivities})`); } for (const sensitivity of sensitivities) { if (sensitivity < 0 || sensitivity > 1 || isNaN(sensitivity)) { throw new RangeError(`Sensitivity value in 'sensitivities' not in range [0,1]: ${sensitivity}`); } } if (!Array.isArray(keywords)) { throw new errors_1.PorcupineInvalidArgumentError(`Keywords is not an array: ${keywords}`); } if (keywords.length !== sensitivities.length) { throw new errors_1.PorcupineInvalidArgumentError(`Number of keywords (${keywords.length}) does not match number of sensitivities (${sensitivities.length})`); } if (!fs.existsSync(libraryPath)) { throw new errors_1.PorcupineInvalidArgumentError(`File not found at 'libraryPath': ${libraryPath}`); } if (!fs.existsSync(modelPath)) { throw new errors_1.PorcupineInvalidArgumentError(`File not found at 'modelPath': ${modelPath}`); } const keywordPaths = []; for (let i = 0; i < keywords.length; i++) { const keyword = keywords[i]; if (Object.values(builtin_keywords_1.BuiltinKeyword).includes(keyword)) { keywordPaths[i] = (0, builtin_keywords_1.getBuiltinKeywordPath)(keyword); } else { if (!fs.existsSync(keyword)) { throw new errors_1.PorcupineInvalidArgumentError(`File not found in 'keywords': ${keyword}`); } else { keywordPaths[i] = keyword; } } } const pvPorcupine = require(libraryPath); // eslint-disable-line this._pvPorcupine = pvPorcupine; let porcupineHandleAndStatus = null; try { pvPorcupine.set_sdk("nodejs"); porcupineHandleAndStatus = pvPorcupine.init(accessKey, modelPath, keywordPaths.length, keywordPaths, sensitivities); } catch (err) { (0, errors_1.pvStatusToException)(pv_status_t_1.default[err.code], err); } const status = porcupineHandleAndStatus.status; if (status !== pv_status_t_1.default.SUCCESS) { this.handlePvStatus(status, "Porcupine failed to initialize"); } this._handle = porcupineHandleAndStatus.handle; this._frameLength = pvPorcupine.frame_length(); this._sampleRate = pvPorcupine.sample_rate(); this._version = pvPorcupine.version(); } /** * @returns number of audio samples per frame (i.e. the length of the array provided to the process function) * @see {@link process} */ get frameLength() { return this._frameLength; } /** * @returns the audio sampling rate accepted by Porcupine */ get sampleRate() { return this._sampleRate; } /** * @returns the version of the Porcupine engine */ get version() { return this._version; } /** * Process a frame of pcm audio. * * @param {Array} frame of mono, 16-bit, linear-encoded PCM audio. * The specific array length can be attained by calling `.frameLength`. * The incoming audio needs to have a sample rate equal to `.sampleRate` and be 16-bit linearly-encoded. * Porcupine operates on single-channel audio. * @returns {number} Index of observed keyword at the end of the current frame. * Indexing is 0-based and matches the ordering of keyword models provided to the constructor. * If no keyword is detected then it returns -1. */ process(frame) { if (this._handle === 0 || this._handle === null || this._handle === undefined) { throw new errors_1.PorcupineInvalidStateError("Porcupine is not initialized"); } if (frame === undefined || frame === null) { throw new errors_1.PorcupineInvalidArgumentError(`Frame array provided to process() is undefined or null`); } else if (frame.length !== this.frameLength) { throw new errors_1.PorcupineInvalidArgumentError(`Size of frame array provided to 'process' (${frame.length}) does not match the engine 'frameLength' (${this.frameLength})`); } // sample the first frame to check for non-integer values if (!Number.isInteger(frame[0])) { throw new errors_1.PorcupineInvalidArgumentError(`Non-integer frame values provided to process(): ${frame[0]}. Porcupine requires 16-bit integers`); } const frameBuffer = new Int16Array(frame); let keywordAndStatus = null; try { keywordAndStatus = this._pvPorcupine.process(this._handle, frameBuffer); } catch (err) { (0, errors_1.pvStatusToException)(pv_status_t_1.default[err.code], err); } const status = keywordAndStatus.status; if (status !== pv_status_t_1.default.SUCCESS) { this.handlePvStatus(status, "Porcupine failed to process"); } const keywordIndex = keywordAndStatus.keyword_index; return keywordIndex; } /** * Releases the resources acquired by Porcupine. * * Be sure to call this when finished with the instance * to reclaim the memory that was allocated by the C library. */ release() { if (this._handle > 0) { this._pvPorcupine.delete(this._handle); this._handle = 0; } else { // eslint-disable-next-line no-console console.warn("Porcupine is not initialized; nothing to destroy"); } } handlePvStatus(status, message) { const errorObject = this._pvPorcupine.get_error_stack(); if (errorObject.status === pv_status_t_1.default.SUCCESS) { (0, errors_1.pvStatusToException)(status, message, errorObject.message_stack); } else { (0, errors_1.pvStatusToException)(status, "Unable to get Porcupine error state"); } } } exports.default = Porcupine; //# sourceMappingURL=porcupine.js.map