Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

suggestion: NodeJS module #4

Closed
amir-s opened this issue May 21, 2016 · 47 comments
Closed

suggestion: NodeJS module #4

amir-s opened this issue May 21, 2016 · 47 comments

Comments

@amir-s
Copy link

amir-s commented May 21, 2016

Hello and thanks for the awesome job.

I was trying to create an NPM module for snowboy, but I kind of lost.
So, it might be easier to work together. If anyone could write simple C++ file to use snowboy, I can help with wrapping it as a javascript module and putting it in npm.

Or if anyone wants to go all the way, here are the directions to create c++ addons: https://nodejs.org/api/addons.html

Thanks

@chenguoguo
Copy link
Collaborator

I think it will be a good idea if we create examples of using Snowboy for different programing languages. I'll add some C++ examples first.

@chenguoguo
Copy link
Collaborator

chenguoguo commented May 23, 2016

@amir-s I added some C++ examples. Most part of the code relates to PortAudio which you can probably skip. See the pull request here: #7. I haven't tested this on a linux machine as I don't have one by hand right now.

Let me know if this is enough for you to create the NodeJS module.

@chenguoguo
Copy link
Collaborator

@amir-s any progress on the NodeJS module? Do you need additional explanations regarding the C++ example? Happy to explain more.

@amir-s
Copy link
Author

amir-s commented May 25, 2016

@chenguoguo Perfect. Yes, I'm working on it, but I'm quite slow ...
The thing is Node environment uses gyp as build system ... So, we need to define the build process using gyp with bindings.gyp file.

The rest is just some modification to the C++ example you provided.

Here is an article about gyp. It is much easier to deal with different operating systems and conditions ...

@evancohen
Copy link
Contributor

Hey @amir-s, any progress on this? I'd be happy to take a look, but I'd like to avoid doing duplicate work.

@chenguoguo
Copy link
Collaborator

@amir-s Hey Amir, any progress on this? Shall we get help from @evancohen? It will be great if snowboy runs on node at some point :-)

@chenguoguo
Copy link
Collaborator

Looks like @amir-s is busy with other stuff recently. @evancohen if you could help us create the wrapper and send a pull request to our repo, that will be great too! I'm quite excited about the NodeJS wrapper.

@amir-s
Copy link
Author

amir-s commented Jun 12, 2016

@chenguoguo @evancohen Sorry guys, for some reasons i didn't get notified about this thread...
I'm very busy with at my work as the release deadlines are comming ... I'd be happy to help if anyone could lead this. I personally don't have much time for couple of weeks.

@evancohen
Copy link
Contributor

No worries @amir-s, I can try to pick this up :) Were you able to make any progress with bindings.gyp? I took a look for a few min the other day and was having trouble getting it to include some of the dependencies (namely the Portaudio stuff). It's been a few years since I've dealt with any C++ makefiles ;)

@chenguoguo the trick, as I see it, is to get that bindings file to generate something that maps to what already exists in the example Makefile and demo.mk, from there everything else should be really straightforward.

I'll take a crack at this either tomorrow or Wednesday and report back 🚀

@tiny-tinker
Copy link

tiny-tinker commented Jun 14, 2016

:gets popcorn:
I'll be very interested in a Node module. I haven't touched C++ in a decade or so, so I'm not sure if I can be much help in development, but I'd be happy to test and provide feedback.

@illperipherals
Copy link

Is this the proper approach for the module?: https://nodejs.org/api/addons.html#addons_function_factory
I made it through the docs posted above the other day, but I haven't had much time to look further into it.

Thank you folks for the hard work.

@evancohen
Copy link
Contributor

I've made some progress on this here: evancohen@56f59a0
I've yet to actually plug everything together, but I have a working node module that includes all the necessary dependencies and will run. Hopefully I can finish it up this weekend.

One major drawback that I found with using gyp is relative build paths... I had to copy all the necessary resources into the node folder after I unsuccessfully tried using module_root_dir from nodejs/node-gyp#49. The real solution here may be to nest this repo inside the another that contains the module for building (that way there aren't duplicate files).

@illperipherals it really depends on how you want to use the it and where you want the heavy lifting to be. In this case I'm using as much C++ code as possible, and writing a dev friendly layer in JavaScript.

@JpEncausse
Copy link

Hi, I'm maker of SARAH (http://sarah.encausse.net) using NodeJS Server and Microsoft Speech Engine client. SARAH is powerd by 4500 geek looking for a RPi client for SARAH. So I made A NodeJS experimental version on top of SoX: https://github.com/JpEncausse/SARAH-Client-NodeJS

  1. Start my NodeJS code
  2. Start SoX
  3. Process audio buffer locally with SnowBoy
  4. If OK, forward audio buffer to MS Oxford or Google Speech

I sucks in C :-) is there a way to send the audio buffer to SnowBoy throught precompiled CLI ?
Or is there a working implementation in NodeJS (gyp has lots of drawback) ?

Thanks !

@chenguoguo
Copy link
Collaborator

Regarding "send the audio buffer to SnowBoy through precompiled CLI ", this might be possible to modify the demo.cc file. Instead of capturing the audio with PortAudio, you modify the demo.cc file to load the data from stdin, and you can use some bash command like

rec -t wav -r 16000 -b 16 -e signed-integer -c 1 - | demo

Regarding NodeJS, @evancohen might have something that you can play with?

@tiny-tinker
Copy link

@evancohen, not sure how far you got, but I got curious.
I did a clone of your repo into a clean directory. I then did

$ cd snowboy
$ cd node
$ npm install

And it complained about nan missing. So I installed it with npm install nan. Then I re-ran the npm install command and it complained about a missing file:

pi@alphared-agent-virtual:~/Tinkering/snowboy/node$ npm install
npm WARN package.json snowboy-node@1.0.0 No README data

> snowboy-node@1.0.0 install /home/pi/Tinkering/snowboy/node
> node-gyp rebuild

make: Entering directory '/home/pi/Tinkering/snowboy/node/build'
  CXX(target) Release/obj.target/snowboy/snowboy.o
../snowboy.cc:8:27: fatal error: pa_ringbuffer.h: No such file or directory
 #include <pa_ringbuffer.h>
                           ^
compilation terminated.
snowboy.target.mk:88: recipe for target 'Release/obj.target/snowboy/snowboy.o' failed
make: *** [Release/obj.target/snowboy/snowboy.o] Error 1
make: Leaving directory '/home/pi/Tinkering/snowboy/node/build'

Version info:

pi@alphared-agent-virtual:~/Tinkering/snowboy/node$ node -v
v0.10.29
pi@alphared-agent-virtual:~/Tinkering/snowboy/node$ uname -a
Linux alphared-agent-virtual 3.16.0-4-amd64 #1 SMP Debian 3.16.7-ckt25-2 (2016-04-08) x86_64 GNU/Linux

I didn't see an issues item on your repo, so I'm posting this here, let me know if I should put this somewhere else.

@evancohen
Copy link
Contributor

evancohen commented Jul 1, 2016

Hey @nomadic-squirrel you have to run the install_portaudio.sh script to install necessary dependencies. You'll probably also want to update node to 5.x or 6.x. Note that I have only built this on OSX so far, not sure how it will act on any other platforms.

Sorry for taking so long on this, I've been extra slammed with work. Hopefully I can finish it this weekend 😄

@tiny-tinker
Copy link

Ok, closer. I think. I am running on a Debian vm in the hopes that I can do some development while I travel.. yea, not so sure that is a great idea, but I'm making some progress here and there.
Anyway, I played with the install_portaudio.sh script, but it seemed to have trouble finding the right libraries. So I did make uninstall and then just did sudo apt-get install portaudio19-dev. Which got me further. But then I ran into this one:

pi@alphared-agent-virtual:~/Tinkering/snowboy/node$ npm install
npm WARN package.json snowboy-node@1.0.0 No README data

> snowboy-node@1.0.0 install /home/pi/Tinkering/snowboy/node
> node-gyp rebuild

make: Entering directory '/home/pi/Tinkering/snowboy/node/build'
  CXX(target) Release/obj.target/snowboy/snowboy.o
In file included from ../snowboy.cc:4:0:
../node_modules/nan/nan.h:324:47: error: ‘REPLACE_INVALID_UTF8’ is not a member of ‘v8::String’
   static const unsigned kReplaceInvalidUtf8 = v8::String::REPLACE_INVALID_UTF8;
                                               ^
In file included from ../snowboy.cc:14:0:
../include/snowboy-detect.h:121:8: error: ‘unique_ptr’ in namespace ‘std’ does not name a template type
   std::unique_ptr<WaveHeader> wave_header_;
        ^
../include/snowboy-detect.h:122:8: error: ‘unique_ptr’ in namespace ‘std’ does not name a template type
   std::unique_ptr<PipelineDetect> detect_pipeline_;
        ^
../snowboy.cc:167:11: error: ‘v8::FunctionCallbackInfo’ has not been declared
 using v8::FunctionCallbackInfo;
           ^
snowboy.target.mk:88: recipe for target 'Release/obj.target/snowboy/snowboy.o' failed
make: *** [Release/obj.target/snowboy/snowboy.o] Error 1
make: Leaving directory '/home/pi/Tinkering/snowboy/node/build'

which I"m pretty sure is this one: https://github.com/fivdi/onoff/wiki/Node.js-v0.10.29-and-native-addons-on-the-Raspberry-Pi

So I might need to install node from source. I'll give that a shot if I have some time next week.

@evancohen
Copy link
Contributor

Depending on the arch of your Pi you should be able to follow the instructions here to upgrade node:
http://docs.smart-mirror.io/docs/installation.html

If you are using a first gen Pi (revision number starts with 0) then you will need to change all of the the 7's to 6's when downloading the library (ARMv6 vs ARMv7)

@tiny-tinker
Copy link

Ok, progress. Again, this is a Debian VM:

$ uname -a
Linux alphared-agent-virtual 3.16.0-4-amd64 #1 SMP Debian 3.16.7-ckt25-2 (2016-04-08) x86_64 GNU/Linux

That I'm playing with before I get to the pi, so the pi will likely be different. But after running the install instructions for nodesource I was able to successfully run npm install in the snow/node directory.
I don't yet have a mic hooked up here, so not much I can do for now, but at least I'll chalk it up to progress.

Just poking around, I fired up node to see what it would do. What's this call to snowboy.pow?

$ ~/Tinkering/snowboy/node$ node
> 
> var snowboy = require( './snowboy' );
TypeError: snowboy.pow is not a function
    at Object.<anonymous> (/home/pi/Tinkering/snowboy/node/snowboy.js:79:21)
    at Module._compile (module.js:541:32)
    at Object.Module._extensions..js (module.js:550:10)
    at Module.load (module.js:458:32)
    at tryModuleLoad (module.js:417:12)
    at Function.Module._load (module.js:409:3)
    at Module.require (module.js:468:17)
    at require (internal/module.js:20:19)
    at repl:1:15
    at REPLServer.defaultEval (repl.js:272:27)
> 

The last line is this:

console.log(snowboy.pow(4, 2));        // Prints '16' 

Otherwise, it looks pretty straightforward. I'll need to get a mic, but I'll post back if/when I make further progress.

Great work!!

@evancohen
Copy link
Contributor

Like I said in my previous post, not everything is wired up yet. snowboy.pow was a test I wrote to test the nan pipeline (just computes 4^2 in C++ and returns the results).

@evancohen
Copy link
Contributor

Hey everyone, it's been a while! I finally got my act together and got the Nan module working. You can find the code and instructions here: https://github.com/evancohen/snowboy/tree/devel/swig/Node

A few caveats:

  • This only works on OSX right now - I plan on fixing this shortly
  • It's a bit hacky - I wrote the js wrapper in a hurry so the interface isn't that great
  • It doesn't actually use swig - I tried, but swig doesn't support callbacks for javascript

I'll be sending a PR once I iron out a few more kinks and finalize the wrapper interface.

🚀

@chenguoguo
Copy link
Collaborator

Awesome! @evancohen

@Flowr-es
Copy link

Flowr-es commented Sep 2, 2016

Awesome, I will test this once it works also on Linux / RPi3

@evancohen
Copy link
Contributor

evancohen commented Sep 7, 2016

Still plugging along! I hit some unexpected hurdles when trying to publish to npm, mainly using node-pre-gyp to build precompiled native node binaries for people who don't want to build the plugin from scratch. This should make things significantly easier for everyone using the node library.

While I sort out the rest of the build issues, if you are on an Mac, you can test out a prebuilt binary by running the following in your existing node project:

npm install --save snowboy

Note: this still requires sox to be installed on your machine.

A prebuilt node binary will be installed, if for some reason the binary can't be found then it will fallback to building the project (which will fail because it's not quite configured correctly). If it does download the binary, you can do something like:

var snowboy = require('snowboy');

snowboy.addModel("path/to/resources/snowboy.umdl");

// Start detection and pass in a function as our callback for when we detect the keyword
snowboy.detect(function(data){
    console.log("Example Detected: ", data);
});

// After 2 seconds check if snowboy is listening
setTimeout(function() {
    console.log("Listening: ", snowboy.isListening()); 
}, 2000);


// After 3 seconds of running, terminate snowboy
setTimeout(function() {
    snowboy.stop()
    console.log("Listening: ", snowboy.isListening()); 
}, 3000);

It's a bit chatty right now and logs a bit too much (something I plan on parameterizing). But it does work:

image

tl;dr I've made a lot of progress

Todo:

  • Get build working for the Pi 🎉
  • Get build working for Ubuntu (x64)
  • Clean up JavaScript plugin code
    • Add event handlers for "silence" and "sound"
  • Convert to event handlers instead of callbacks
  • Set up Travis CI to automatically build binaries when snowboy is updated
  • Pipe logging information from C++ to JavaScript and make it optional
  • More things that I have yet to discover?

If there there are any specific features that people would like, or if you have a preference about how the library is called, I'm totally open to feedback 😃

@nekuz0r
Copy link
Contributor

nekuz0r commented Sep 9, 2016

Hi there,

I just made a 1:1 nodejs wrapper and it also provide a WritableStream interface.
Note: Only tested on OSX

You can find the code here : https://github.com/heXeo/snowboy/tree/node
JS Interface : https://github.com/heXeo/snowboy/blob/node/lib/node/index.js

Also an npm module is publish : npm install snowboy-detect

Example

const record = require('node-record-lpcm16');
const SnowboyDetect = require('snowboy-detect');

const d = new SnowboyDetect({
  resource: "node_modules/snowboy-detect/resources/common.res",
  model: "node_modules/snowboy-detect/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);

@evancohen
Copy link
Contributor

@nekuz0r just looking over the code now, awesome work! I'd be interested in combining what we've done and sending a PR, if that's alright with you. I'll take a stab at it tonight/over the weekend and get it building for other platforms.

@Flowr-es
Copy link

Flowr-es commented Sep 9, 2016

nice work! I will also take a look into it.

@Flowr-es
Copy link

Hi @evancohen and @nekuz0r,
I have changed a bit the interface and created a pull request.
Please take a look, only minor changes but I think it makes more sense to emit silence and noise as voiceactivity.

Also I have some questions, so the difference between your both modules seems to be that @nekuz0r module needs an audio stream and @evancohen uses portaudio to get the audio. I personally prefer to have the audio stream in my hands as it is not possible to get it from snowboy ( using portaudio ), or am I wrong?

@nekuz0r your Node Interface does not support multiple Hotwords for one detection, it would be great to have this opportunity.

@nekuz0r
Copy link
Contributor

nekuz0r commented Sep 12, 2016

@Mexxxo What do you mean by "multiple hotwords for one detection".

It maps exactly the C++ API, you can specify multiples models and it will returns the index of the detected keywords.

Or am i missing something ?

@nekuz0r
Copy link
Contributor

nekuz0r commented Sep 12, 2016

@Mexxxo Indeed since the implementation is just a wrapper around the C++ API, you need to feed it with audio data.

I only added writable stream to it for convenience.

I greatly prefer to control the audio source, so you are able the feed it with whatever audio source you want.

Can be a file, audio data from an external software, from a web stream, from WebAudio API, ... it will simply works.

@evancohen
Copy link
Contributor

@Mexxxo, As @nekuz0r mentioned it's a direct mapping for the wrapper, so you can add multiple models by doing something like:

const d = new SnowboyDetect({
  resource: "somecommon.res",
  model: "firstmodel.pmdl,secondmodel.pmdl",
  sensitivity: "0.5,0.4",
  audioGain: 2.0
});

Totally agreed that using a stream is superior to portaudio, it makes the interface much more flexible (although it does add a small layer of complexity for the end user, maybe @nekuz0r can provide some sample code for the examples mentioned above?).

One of the modifications to the interface that I'm thinking about are some functions for adding and removing models. I'm also debating about weather or not it would be beneficial to optionally pass in a callback along with the model for when that specific keyword is spotted (in addition to the event emitter) to make it simpler to specify actions when a given keyword is spotted.

For example:

function optionalCallback(){
// Call me when firstmodel.pmdl this is spotted.
}

d.addModel("firstmodel.pmdl", "0.5", optionalCallback);

@nekuz0r
Copy link
Contributor

nekuz0r commented Sep 12, 2016

@evancohen your addModel proposal sounds good, it definitely adds some readability, especially if you need to specify lots of models. But i would avoid callback, in benefits of the event emitter.

Maybe it would be nice to have a HotwordModels class or something you pass to the SnowboyDetect constructor.

const models = new HotwordModels();

models.add({ file: 'firstmodel.umdl', sensitivity: '0.5', count: 2);
models.add({ file: 'secondmodel.pmdl', sensitivity: '0.5');

const detector = new SnowboyDetect({
  resource: "common.res",
  models: models,
  gain: 1.0
});

The count argument is used to determine which model triggered an hotword, because universal model files can contains multiple hotwords. It should throw an error when not specified when adding an universal model, and throw an error when specified for a personal model.
This can be easily detected based on file extension.

We could go event further and give the ability to specify hotwords mapping for each model, so we could provide in the hotword event the name of the model that triggered the hotword detection and the hotword alias.

// For universal model
models.add({
  file: 'firstmodel.umdl',
  sensitivity: '0.5',
  hotwords: [
    'hello', 'world'
  ]
});

// For personal model
models.add({
  file: 'secondmodel.pmdl',
  sensitivity: '0.5',
  hotwords: 'foo'
});

hotwords fields should be then an array for UMDL and a string for PMDL.
Internally it would be turn into array using something like [].concat(model.hotwords)
the count value would be then gathered from the hotwords array length.

Let me know what are your thoughts on it :)

@evancohen
Copy link
Contributor

Yeah, that sounds awesome! I took a crack at that in my branch and updated the example to reflect the updated structure. I haven't updated the npm package yet. Take a look and let me know what you think: evancohen@9409cd7

The count argument is used to determine which model triggered an hotword, because universal model files can contains multiple hotwords.

I'm a bit confused by this, do you mean that there can be multiple words in a "single" hotword?

@chenguoguo can you confirm that universal models can have multiple hotwords? I checked snowboy.umdl and it seems to only have 1 hotword and I don't have any other universal models to compare against.

I still haven't looked at removing models (it might be nice to do this dynamically - automatically detecting the removal/addition of a model on the fly and restarting the Detector).

@chenguoguo
Copy link
Collaborator

On Mon, Sep 12, 2016 at 10:37 PM, Evan Cohen notifications@github.com
wrote:

Yeah, that sounds awesome! I took a crack at that in my branch and updated
the example to reflect the updated structure. I haven't updated the npm
package yet. Take a look and let me know what you think: evancohen/snowboy@9409cd7
evancohen@9409cd7

The count argument is used to determine which model triggered an hotword,
because universal model files can contains multiple hotwords.

I'm a bit confused by this, do you mean that there can be multiple words
in a "single" hotword?

@chenguoguo https://github.com/chenguoguo can you confirm that
universal models can have multiple hotwords? I checked snowboy.umdl and
it seems to only have 1 hotword and I don't have any other universal models
to compare against.

Yes universal model can have multiple hotwords, although the provided
snowboy.umdl only contains one hotword. Personal model will only contain
one hotword. E.g., if you have two personal models, 1.pmdl and 3.pmdl, and
you have one universal model 2.umdl, which contains 2 hotwords, now if you
provide "1.pmdl,2.umdl,3.pmdl" to the decoder, 3.pmdl will have hotword id
4.

I still haven't looked at removing models (it might be nice to do this
dynamically - automatically detecting the removal/addition of a model on
the fly and restarting the Detector).


You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
#4 (comment), or mute
the thread
https://github.com/notifications/unsubscribe-auth/ALPfk5JG6IdVvegCB-lbRmrA3Ar0Vc0Yks5qpjZ-gaJpZM4Ij2xM
.

@nekuz0r
Copy link
Contributor

nekuz0r commented Sep 13, 2016

@evancohen Hi, this is what i had in mind https://github.com/heXeo/snowboy/blob/typescript/lib/node/index.ts
(i rebased on your branch)

The implementation handles UMDL with multiple hotwords.

I rewrote it in typescript, i still have to set up npm prepublish so it will be transpiled before publishing.

@evancohen
Copy link
Contributor

@nekuz0r Awesome job! 😄

I was thinking of rewriting in TypeScript this evening, you beat me to it! Let me know once you get prepublish working so I can test the end-to-end build process (and update the snowboy npm package + binaries).

After that I'll send a PR to devel.

@chenguoguo there are a few build configuration things you'll have to deal with on your end. I'll include that info in the PR. In the meantime, there's an issue with versioning that we can address:

The snowboy npm package is now at v1.0.9 (higher than the git repo version v1.0.4) - They recently changed the way versioning works, so I contacted them to see if we could reuse that version number... Long story short:

Unfortunately, there is no longer any way to re-use a version of a package in the npm registry.

Consistent version numbering might not even be something that you care about, but if you do we could bump up both the npm package and the git repo to 1.1.0 (I think adding a new supported language justifies a minor version bump, yes?).

Also, I'm assuming that you'll want at least some documentation to go along with this?

@chenguoguo
Copy link
Collaborator

Awesome!

Yes sure we can bump the version up to 1.1.0, we can certainly consider it as "substantial new functionality" :-)

And yes we definitely would like to see some documentation for it, perhaps in README?

@nekuz0r
Copy link
Contributor

nekuz0r commented Sep 14, 2016

@evancohen

The prepublish process is done.

Also:

  • Added an example with hotword detection from a wave file.
  • Renamed demo.js to microphone.js
  • Updated .npmignore to remove all unneeded files
  • Bumped version to 1.1.0 in package.json
  • Changed repository url in package.json to git+https://github.com/Kitt-AI/snowboy.git

I have merged typescript branch into node branch and rebased it onto master.

It needs some documentation, and then i think it will be ready for PR.

Also, i will unpublish my snowboy-detect from the npm registry. (tagged it as deprecated for now)

@evancohen
Copy link
Contributor

Great! I'll finish up the documentation I was writing and send the PR. I'm going to hold off on publishing v1.1.0 until the PR is actually accepted in case we need to make any changes.

@Flowr-es
Copy link

@evancohen @nekuz0r great work!

@WarlaxZ
Copy link

WarlaxZ commented Sep 17, 2016

Hi guys, really great work on this, but still can't get it working, as I'm getting this error (and yes im using strict in my example.js :)

class SnowboyDetect extends stream.Writable {
^^^^^

SyntaxError: Block-scoped declarations (let, const, function, class) not yet supported outside strict mode

@WarlaxZ
Copy link

WarlaxZ commented Sep 17, 2016

for reference, adding "use strict" to the top of index.js made everything work like a charm though :)

@nekuz0r
Copy link
Contributor

nekuz0r commented Sep 17, 2016

@WarlaxZ Which version did you use ?

@nekuz0r
Copy link
Contributor

nekuz0r commented Sep 17, 2016

I edited my previous answer, because npm install using git in this case will not work because of the .npmignore

@chenguoguo
Copy link
Collaborator

Closing this after #52

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

9 participants