@picovoice/porcupine-node
Version:
Picovoice Porcupine Node.js binding
196 lines • 8.93 kB
JavaScript
//
// 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.
//
;
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