Skip to content

Commit

Permalink
Nodejs wrapper
Browse files Browse the repository at this point in the history
  • Loading branch information
nekuz0r authored and evancohen committed Sep 15, 2016
1 parent d6fd13b commit 5b56d6d
Show file tree
Hide file tree
Showing 7 changed files with 385 additions and 0 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,5 @@ snowboydetect.py
/swig/Android/java/
/swig/Android/jniLibs/

/build
/node_modules
20 changes: 20 additions & 0 deletions .npmignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/lib/libsnowboy-detect.a
snowboy-detect-swig.cc
snowboydetect.py
.DS_Store

*.pyc
*.o
*.so

/examples/C++/pa_stable_v19_20140130.tgz
/examples/C++/portaudio
/examples/C++/demo

/swig/Android/OpenBLAS-0.2.18.tar.gz
/swig/Android/OpenBLAS-Android/
/swig/Android/java/
/swig/Android/jniLibs/

/build
/node_modules
53 changes: 53 additions & 0 deletions binding.gyp
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
{
'targets': [{
'target_name': 'SnowboyDetect',
'sources': [
'swig/Node/snowboy-detect.cc'
],
'conditions': [
['OS=="mac"', {
'link_settings': {
'libraries': [
'<(module_root_dir)/lib/osx/libsnowboy-detect.a',
]
}
}],
['OS=="linux" and target_arch=="x64"', {
'link_settings': {
'libraries': [
'<(module_root_dir)/lib/ubuntu64/libsnowboy-detect.a',
]
}
}],
['OS=="linux" and target_arch=="arm"', {
'link_settings': {
'libraries': [
'<(module_root_dir)/lib/rpi/libsnowboy-detect.a',
]
}
}]
],
'cflags': [
'-std=c++11',
'-stdlib=libc++'
],
'include_dirs': [
"<!(node -e \"require('nan')\")",
"<!(pwd)/include"
],
'libraries': [
'-lcblas'
],
'cflags': [
'-Wall'
],
'xcode_settings': {
'MACOSX_DEPLOYMENT_TARGET': '10.11',
"GCC_ENABLE_CPP_EXCEPTIONS": "YES",
'OTHER_CFLAGS': [
'-std=c++11',
'-stdlib=libc++'
]
}
}]
}
32 changes: 32 additions & 0 deletions examples/Node/demo.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
const record = require('node-record-lpcm16');
const SnowboyDetect = require('../../');

const d = new SnowboyDetect({
resource: "resources/common.res",
model: "resources/snowboy.umdl",
sensitivity: "0.5",
audioGain: 2.0
});

d.on('silence', function () {
console.log('silence');
});

d.on('noise', function () {
console.log('noise');
});

d.on('error', function () {
console.log('error');
});

d.on('hotword', function (index) {
console.log('hotword', index);
});

const r = record.start({
threshold: 0,
verbose: true
});

r.pipe(d);
89 changes: 89 additions & 0 deletions lib/node/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
const stream = require('stream');
const addon = require('bindings')('SnowboyDetect.node');

class SnowboyDetect extends stream.Writable {
constructor (options) {
super();

this.nativeInstance = new addon.SnowboyDetect(options.resource, options.model);

if (options.sensitivity !== null && options.sensitivity !== undefined) {
this.nativeInstance.SetSensitivity(options.sensitivity);
}

if (options.audioGain !== null && options.audioGain !== undefined) {
this.nativeInstance.SetAudioGain(options.audioGain);
}
}

reset () {
return this.nativeInstance.Reset();
}

runDetection (buffer) {
const index = this.nativeInstance.RunDetection(buffer);
this._processResult(index);
return index;
}

setSensitivity (sensitivity) {
this.nativeInstance.SetSensitivity(sensitivity);
}

getSensitivity () {
return this.nativeInstance.GetSensitivity();
}

setAudioGain (gain) {
this.nativeInstance.setAudioGain(gain);
}

updateModel () {
this.nativeInstance.UpdateModel();
}

numHotwords () {
return this.nativeInstance.NumHotwords();
}

sampleRate () {
return this.nativeInstance.SampleRate();
}

numChannels () {
return this.nativeInstance.NumChannels();
}

bitsPerSample () {
return this.nativeInstance.BitsPerSample();
}

// Stream implementation
_write (chunk, encoding, callback) {
const index = this.nativeInstance.RunDetection(chunk);
this._processResult(index);
return callback();
}

_processResult (index) {
switch (index) {
case -2:
this.emit('silence');
break;

case -1:
this.emit('error');
break;

case 0:
this.emit('noise');
break;

default:
this.emit('hotword', index)
break;
}
}
}

module.exports = SnowboyDetect;
19 changes: 19 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"name": "snowboy-detect",
"version": "1.0.4",
"description": "Snowboy is a customizable hotword detection engine",
"main": "lib/node/index.js",
"scripts": {
"install": "node-gyp rebuild"
},
"author": "Kitt-AI <[email protected]>",
"contributors": [
"Leandre Gohy <[email protected]>"
],
"license": "ISC",
"dependencies": {
"bindings": "^1.2.1",
"nan": "^2.4.0",
"node-gyp": "^3.4.0"
}
}
170 changes: 170 additions & 0 deletions swig/Node/snowboy-detect.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
#include <nan.h>
#include <snowboy-detect.h>
#include <iostream>

class SnowboyDetect : public Nan::ObjectWrap {
public:
static NAN_MODULE_INIT(Init);

private:
explicit SnowboyDetect(const std::string& resource_filename, const std::string& model_str);
~SnowboyDetect();

static NAN_METHOD(New);
static NAN_METHOD(Reset);
static NAN_METHOD(RunDetection);
static NAN_METHOD(SetSensitivity);
static NAN_METHOD(GetSensitivity);
static NAN_METHOD(SetAudioGain);
static NAN_METHOD(UpdateModel);
static NAN_METHOD(NumHotwords);
static NAN_METHOD(SampleRate);
static NAN_METHOD(NumChannels);
static NAN_METHOD(BitsPerSample);

static Nan::Persistent<v8::Function> constructor;

snowboy::SnowboyDetect* detector;
};

Nan::Persistent<v8::Function> SnowboyDetect::constructor;

SnowboyDetect::SnowboyDetect(const std::string& resource_filename, const std::string& model_str) {
try {
this->detector = new snowboy::SnowboyDetect(resource_filename, model_str);
} catch (std::runtime_error e) {
Nan::ThrowError(e.what());
}
}
SnowboyDetect::~SnowboyDetect() {
if (this->detector) {
delete this->detector;
}
}

NAN_MODULE_INIT(SnowboyDetect::Init) {
v8::Local<v8::FunctionTemplate> tpl = Nan::New<v8::FunctionTemplate>(New);
tpl->SetClassName(Nan::New("SnowboyDetect").ToLocalChecked());
tpl->InstanceTemplate()->SetInternalFieldCount(1);

SetPrototypeMethod(tpl, "Reset", Reset);
SetPrototypeMethod(tpl, "RunDetection", RunDetection);
SetPrototypeMethod(tpl, "SetSensitivity", SetSensitivity);
SetPrototypeMethod(tpl, "GetSensitivity", GetSensitivity);
SetPrototypeMethod(tpl, "SetAudioGain", SetAudioGain);
SetPrototypeMethod(tpl, "UpdateModel", UpdateModel);
SetPrototypeMethod(tpl, "NumHotwords", NumHotwords);
SetPrototypeMethod(tpl, "SampleRate", SampleRate);
SetPrototypeMethod(tpl, "NumChannels", NumChannels);
SetPrototypeMethod(tpl, "BitsPerSample", BitsPerSample);

constructor.Reset(Nan::GetFunction(tpl).ToLocalChecked());
Nan::Set(target, Nan::New("SnowboyDetect").ToLocalChecked(),
Nan::GetFunction(tpl).ToLocalChecked());
}

NAN_METHOD(SnowboyDetect::New) {
if (!info.IsConstructCall()) {
Nan::ThrowError("Cannot call constructor as function, you need to use 'new' keyword");
return;
} else if (!info[0]->IsString()) {
Nan::ThrowTypeError("resource must be a string");
return;
} else if (!info[1]->IsString()) {
Nan::ThrowTypeError("model must be a string");
return;
}

Nan::MaybeLocal<v8::Object> resource = Nan::To<v8::Object>(info[0]);
Nan::MaybeLocal<v8::Object> model = Nan::To<v8::Object>(info[1]);
Nan::Utf8String resourceString(resource.ToLocalChecked());
Nan::Utf8String modelString(model.ToLocalChecked());
SnowboyDetect* obj = new SnowboyDetect(*resourceString, *modelString);
obj->Wrap(info.This());
info.GetReturnValue().Set(info.This());
}

NAN_METHOD(SnowboyDetect::Reset) {
SnowboyDetect* ptr = Nan::ObjectWrap::Unwrap<SnowboyDetect>(info.Holder());
bool ret = ptr->detector->Reset();
info.GetReturnValue().Set(Nan::New(ret));
}

NAN_METHOD(SnowboyDetect::RunDetection) {
if (!info[0]->IsObject()) {
Nan::ThrowTypeError("data must be a buffer");
return;
}

Nan::MaybeLocal<v8::Object> buffer = Nan::To<v8::Object>(info[0]);
char* bufferData = node::Buffer::Data(buffer.ToLocalChecked());
size_t bufferLength = node::Buffer::Length(buffer.ToLocalChecked());

std::string data(bufferData, bufferLength);

SnowboyDetect* ptr = Nan::ObjectWrap::Unwrap<SnowboyDetect>(info.Holder());
int ret = ptr->detector->RunDetection(data);
info.GetReturnValue().Set(Nan::New(ret));
}

NAN_METHOD(SnowboyDetect::SetSensitivity) {
if (!info[0]->IsString()) {
Nan::ThrowTypeError("sensitivity must be a string");
return;
}

Nan::MaybeLocal<v8::Object> sensitivity = Nan::To<v8::Object>(info[0]);
Nan::Utf8String sensitivityString(sensitivity.ToLocalChecked());

SnowboyDetect* ptr = Nan::ObjectWrap::Unwrap<SnowboyDetect>(info.Holder());
ptr->detector->SetSensitivity(*sensitivityString);
}

NAN_METHOD(SnowboyDetect::GetSensitivity) {
SnowboyDetect* ptr = Nan::ObjectWrap::Unwrap<SnowboyDetect>(info.Holder());
std::string sensitivity = ptr->detector->GetSensitivity();
info.GetReturnValue().Set(Nan::New(sensitivity).ToLocalChecked());
}

NAN_METHOD(SnowboyDetect::SetAudioGain) {
if (!info[0]->IsNumber()) {
Nan::ThrowTypeError("gain must be a number");
return;
}

Nan::MaybeLocal<v8::Number> gain = Nan::To<v8::Number>(info[0]);
SnowboyDetect* ptr = Nan::ObjectWrap::Unwrap<SnowboyDetect>(info.Holder());
ptr->detector->SetAudioGain(gain.ToLocalChecked()->Value());
}

NAN_METHOD(SnowboyDetect::UpdateModel) {
SnowboyDetect* ptr = Nan::ObjectWrap::Unwrap<SnowboyDetect>(info.Holder());
ptr->detector->UpdateModel();
}

NAN_METHOD(SnowboyDetect::NumHotwords) {
SnowboyDetect* ptr = Nan::ObjectWrap::Unwrap<SnowboyDetect>(info.Holder());
int numHotwords = ptr->detector->NumHotwords();
info.GetReturnValue().Set(Nan::New(numHotwords));
}

NAN_METHOD(SnowboyDetect::SampleRate) {
SnowboyDetect* ptr = Nan::ObjectWrap::Unwrap<SnowboyDetect>(info.Holder());
int sampleRate = ptr->detector->SampleRate();
info.GetReturnValue().Set(Nan::New(sampleRate));
}

NAN_METHOD(SnowboyDetect::NumChannels) {
SnowboyDetect* ptr = Nan::ObjectWrap::Unwrap<SnowboyDetect>(info.Holder());
int numChannels = ptr->detector->NumChannels();
info.GetReturnValue().Set(Nan::New(numChannels));
}

NAN_METHOD(SnowboyDetect::BitsPerSample) {
SnowboyDetect* ptr = Nan::ObjectWrap::Unwrap<SnowboyDetect>(info.Holder());
int bitsPerSample = ptr->detector->BitsPerSample();
info.GetReturnValue().Set(Nan::New(bitsPerSample));
}


NODE_MODULE(SnowboyDetect, SnowboyDetect::Init)

0 comments on commit 5b56d6d

Please sign in to comment.