Skip to content

Commit 693ea42

Browse files
authored
Add examples for NodeJS (#280)
1 parent 61664c7 commit 693ea42

File tree

7 files changed

+456
-13
lines changed

7 files changed

+456
-13
lines changed

.github/workflows/nodejs.yaml

+95
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
name: nodejs
2+
3+
on:
4+
push:
5+
branches:
6+
- master
7+
paths:
8+
- '.github/workflows/nodejs.yaml'
9+
- 'CMakeLists.txt'
10+
- 'cmake/**'
11+
- 'nodejs-examples/**'
12+
- 'sherpa-ncnn/csrc/*'
13+
pull_request:
14+
branches:
15+
- master
16+
paths:
17+
- '.github/workflows/nodejs.yaml'
18+
- 'CMakeLists.txt'
19+
- 'cmake/**'
20+
- 'nodejs-examples/**'
21+
- 'sherpa-ncnn/csrc/*'
22+
23+
concurrency:
24+
group: nodejs-${{ github.ref }}
25+
cancel-in-progress: true
26+
27+
permissions:
28+
contents: read
29+
30+
jobs:
31+
nodejs:
32+
runs-on: ${{ matrix.os }}
33+
strategy:
34+
fail-fast: false
35+
matrix:
36+
os: [ubuntu-latest, macos-latest]
37+
python-version: ["3.8"]
38+
39+
steps:
40+
- uses: actions/checkout@v2
41+
with:
42+
fetch-depth: 0
43+
44+
- name: Setup Python ${{ matrix.python-version }}
45+
uses: actions/setup-python@v2
46+
with:
47+
python-version: ${{ matrix.python-version }}
48+
49+
- uses: actions/setup-node@v3
50+
with:
51+
node-version: 13
52+
53+
- name: Display node version
54+
shell: bash
55+
run: |
56+
node --version
57+
npm --version
58+
cd nodejs-examples
59+
60+
npm install [email protected] -g
61+
npm install [email protected]
62+
npm --version
63+
64+
- name: Install npm packages
65+
shell: bash
66+
run: |
67+
cd nodejs-examples
68+
npm install ffi-napi ref-struct-napi wav
69+
npm list
70+
71+
- name: ccache
72+
uses: hendrikmuhs/[email protected]
73+
with:
74+
key: ${{ matrix.os }}-shared
75+
76+
- name: Download model
77+
shell: bash
78+
run: |
79+
cd nodejs-examples
80+
GIT_LFS_SKIP_SMUDGE=1 git clone https://huggingface.co/csukuangfj/sherpa-ncnn-streaming-zipformer-bilingual-zh-en-2023-02-13
81+
cd sherpa-ncnn-streaming-zipformer-bilingual-zh-en-2023-02-13
82+
git lfs pull --include "*.bin"
83+
ls -lh
84+
85+
- name: Test
86+
shell: bash
87+
run: |
88+
export CMAKE_CXX_COMPILER_LAUNCHER=ccache
89+
export PATH="/usr/lib/ccache:/usr/local/opt/ccache/libexec:$PATH"
90+
cmake --version
91+
92+
cd nodejs-examples
93+
ls -lh
94+
95+
./run.sh

CMakeLists.txt

+15-13
Original file line numberDiff line numberDiff line change
@@ -92,19 +92,21 @@ endif()
9292
set(CMAKE_CXX_STANDARD 14 CACHE STRING "The C++ version to be used.")
9393
set(CMAKE_CXX_EXTENSIONS OFF)
9494

95-
include(CheckIncludeFileCXX)
96-
check_include_file_cxx(alsa/asoundlib.h SHERPA_NCNN_HAS_ALSA)
97-
if(SHERPA_NCNN_HAS_ALSA)
98-
add_definitions(-DSHERPA_NCNN_ENABLE_ALSA=1)
99-
elseif(UNIX AND NOT APPLE)
100-
message(WARNING "\
101-
Could not find alsa/asoundlib.h !
102-
We won't build sherpa-ncnn-alsa
103-
To fix that, please do:
104-
(1) sudo apt-get install alsa-utils libasound2-dev
105-
(2) rm -rf build
106-
(3) re-try
107-
")
95+
if(SHERPA_NCNN_ENABLE_BINARY AND UNIX AND NOT APPLE)
96+
include(CheckIncludeFileCXX)
97+
check_include_file_cxx(alsa/asoundlib.h SHERPA_NCNN_HAS_ALSA)
98+
if(SHERPA_NCNN_HAS_ALSA)
99+
add_definitions(-DSHERPA_NCNN_ENABLE_ALSA=1)
100+
elseif(UNIX AND NOT APPLE)
101+
message(WARNING "\
102+
Could not find alsa/asoundlib.h !
103+
We won't build sherpa-ncnn-alsa
104+
To fix that, please do:
105+
(1) sudo apt-get install alsa-utils libasound2-dev
106+
(2) rm -rf build
107+
(3) re-try
108+
")
109+
endif()
108110
endif()
109111

110112
list(APPEND CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/cmake)

nodejs-examples/.gitignore

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
install
2+
node_modules
3+
package.json
4+
package-lock.json

nodejs-examples/index.js

+181
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
1+
// Copyright (c) 2023 Xiaomi Corporation (authors: Fangjun Kuang)
2+
//
3+
// Please use
4+
//
5+
// npm install ffi-napi ref-struct-napi
6+
//
7+
// before you use this file
8+
//
9+
//
10+
// Please use node 13. node 16, 18, 20, and 21 are known not working.
11+
// See also
12+
// https://github.com/node-ffi-napi/node-ffi-napi/issues/244
13+
// and
14+
// https://github.com/node-ffi-napi/node-ffi-napi/issues/97
15+
'use strict'
16+
17+
const debug = require('debug')('sherpa-ncnn');
18+
const os = require('os');
19+
const path = require('path');
20+
const ffi = require('ffi-napi');
21+
const ref = require('ref-napi');
22+
const fs = require('fs');
23+
24+
const StructType = require('ref-struct-napi');
25+
const cstring = ref.types.CString;
26+
const int32_t = ref.types.int32;
27+
const float = ref.types.float;
28+
const floatPtr = ref.refType(float);
29+
30+
const RecognizerPtr = ref.refType(ref.types.void);
31+
const StreamPtr = ref.refType(ref.types.void);
32+
const SherpaNcnnModelConfig = StructType({
33+
'encoderParam': cstring,
34+
'encoderBin': cstring,
35+
'decoderParam': cstring,
36+
'decoderBin': cstring,
37+
'joinerParam': cstring,
38+
'joinerBin': cstring,
39+
'tokens': cstring,
40+
'useVulkanCompute': int32_t,
41+
'numThreads': int32_t,
42+
});
43+
44+
const SherpaNcnnDecoderConfig = StructType({
45+
'decodingMethod': cstring,
46+
'numActivePaths': int32_t,
47+
});
48+
49+
const SherpaNcnnFeatureExtractorConfig = StructType({
50+
'sampleRate': float,
51+
'featureDim': int32_t,
52+
});
53+
54+
const SherpaNcnnRecognizerConfig = StructType({
55+
'featConfig': SherpaNcnnFeatureExtractorConfig,
56+
'modelConfig': SherpaNcnnModelConfig,
57+
'decoderConfig': SherpaNcnnDecoderConfig,
58+
'enableEndpoint': int32_t,
59+
'rule1MinTrailingSilence': float,
60+
'rule2MinTrailingSilence': float,
61+
'rule3MinUtteranceLength': float,
62+
'hotwordsFile': cstring,
63+
'hotwordsScore': cstring,
64+
});
65+
66+
const SherpaNcnnResult = StructType({
67+
'text': cstring,
68+
'tokens': cstring,
69+
'timestamps': floatPtr,
70+
'count': int32_t,
71+
});
72+
73+
74+
const ResultPtr = ref.refType(SherpaNcnnResult);
75+
const RecognizerConfigPtr = ref.refType(SherpaNcnnRecognizerConfig)
76+
77+
let soname;
78+
if (os.platform() == 'win32') {
79+
soname = path.join(__dirname, 'install', 'lib', 'sherpa-ncnn-c-api.dll');
80+
} else if (os.platform() == 'darwin') {
81+
soname = path.join(__dirname, 'install', 'lib', 'libsherpa-ncnn-c-api.dylib');
82+
} else if (os.platform() == 'linux') {
83+
soname = path.join(__dirname, 'install', 'lib', 'libsherpa-ncnn-c-api.so');
84+
} else {
85+
throw new Error(`Unsupported platform ${os.platform()}`);
86+
}
87+
if (!fs.existsSync(soname)) {
88+
throw new Error(`Cannot find file ${soname}. Please make sure you have run
89+
./build.sh`);
90+
}
91+
92+
debug('soname ', soname)
93+
94+
const libsherpa_ncnn = ffi.Library(soname, {
95+
'CreateRecognizer': [RecognizerPtr, [RecognizerConfigPtr]],
96+
'DestroyRecognizer': ['void', [RecognizerPtr]],
97+
'CreateStream': [StreamPtr, [RecognizerPtr]],
98+
'DestroyStream': ['void', [StreamPtr]],
99+
'AcceptWaveform': ['void', [StreamPtr, float, floatPtr, int32_t]],
100+
'IsReady': [int32_t, [RecognizerPtr, StreamPtr]],
101+
'Decode': ['void', [RecognizerPtr, StreamPtr]],
102+
'GetResult': [ResultPtr, [RecognizerPtr, StreamPtr]],
103+
'DestroyResult': ['void', [ResultPtr]],
104+
'Reset': ['void', [RecognizerPtr, StreamPtr]],
105+
'InputFinished': ['void', [StreamPtr]],
106+
'IsEndpoint': [int32_t, [RecognizerPtr, StreamPtr]],
107+
});
108+
109+
class Recognizer {
110+
/**
111+
* @param {SherpaNcnnRecognizerConfig} config Configuration for the recognizer
112+
*
113+
* The user has to invoke this.free() at the end to avoid memory leak.
114+
*/
115+
constructor(config) {
116+
this.recognizer_handle = libsherpa_ncnn.CreateRecognizer(config.ref());
117+
this.stream_handle = libsherpa_ncnn.CreateStream(this.recognizer_handle);
118+
}
119+
120+
free() {
121+
if (this.stream_handle) {
122+
libsherpa_ncnn.DestroyStream(this.stream_handle);
123+
this.stream_handle = null;
124+
}
125+
126+
libsherpa_ncnn.DestroyRecognizer(this.recognizer_handle);
127+
this.handle = null;
128+
}
129+
130+
/**
131+
* @param {bool} true to create a new stream
132+
*/
133+
reset(recreate) {
134+
if (recreate) {
135+
libsherpa_ncnn.DestroyStream(this.stream_handle);
136+
this.stream_handle = libsherpa_ncnn.CreateStream(this.recognizer_handle);
137+
return;
138+
}
139+
libsherpa_ncnn.Reset(this.recognizer_handle, this.stream_handle)
140+
}
141+
/**
142+
* @param {float} Sample rate of the input data
143+
* @param {float[]} A 1-d float array containing audio samples. It should be
144+
* in the range [-1, 1].
145+
*/
146+
acceptWaveform(sampleRate, samples) {
147+
libsherpa_ncnn.AcceptWaveform(
148+
this.stream_handle, sampleRate, samples, samples.length);
149+
}
150+
151+
isReady() {
152+
return libsherpa_ncnn.IsReady(this.recognizer_handle, this.stream_handle);
153+
}
154+
155+
decode() {
156+
libsherpa_ncnn.Decode(this.recognizer_handle, this.stream_handle);
157+
}
158+
159+
getResult() {
160+
const h =
161+
libsherpa_ncnn.GetResult(this.recognizer_handle, this.stream_handle);
162+
const text = Buffer.from(h.deref().text, 'utf-8').toString();
163+
libsherpa_ncnn.DestroyResult(h);
164+
return text;
165+
}
166+
};
167+
168+
// alias
169+
170+
const ModelConfig = SherpaNcnnModelConfig;
171+
const DecoderConfig = SherpaNcnnDecoderConfig;
172+
const FeatureConfig = SherpaNcnnFeatureExtractorConfig;
173+
const RecognizerConfig = SherpaNcnnRecognizerConfig;
174+
175+
module.exports = {
176+
FeatureConfig,
177+
ModelConfig,
178+
DecoderConfig,
179+
Recognizer,
180+
RecognizerConfig,
181+
};

nodejs-examples/package.json

+29
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
{
2+
"name": "sherpa-ncnn",
3+
"version": "2.1.4",
4+
"description": "real-time speech recognition with Next-gen Kaldi",
5+
"main": "index.js",
6+
"dependencies": {
7+
"ffi-napi": "^4.0.3",
8+
"ref-struct-napi": "^1.1.1",
9+
"wav": "^1.0.2"
10+
},
11+
"devDependencies": {},
12+
"scripts": {
13+
"test": "echo \"Error: no test specified\" && exit 1"
14+
},
15+
"repository": {
16+
"type": "git",
17+
"url": "git+ssh://[email protected]/k2-fsa/sherpa-ncnn.git"
18+
},
19+
"keywords": [
20+
"speech-to-text;",
21+
"ASR"
22+
],
23+
"author": "The Next-gen Kaldi team",
24+
"license": "Apache-2.0",
25+
"bugs": {
26+
"url": "https://github.com/k2-fsa/sherpa-ncnn/issues"
27+
},
28+
"homepage": "https://github.com/k2-fsa/sherpa-ncnn#readme"
29+
}

nodejs-examples/run.sh

+34
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
#!/usr/bin/env bash
2+
# Copyright (c) 2023 Xiaomi Corporation (authors: Fangjun Kuang)
3+
4+
npm list | grep ffi-napi >/dev/null || npm install ffi-napi
5+
npm list | grep ref-struct-napi >/dev/null || npm install ref-struct-napi
6+
npm list | grep wav >/dev/null || npm install wav
7+
8+
if [ ! -e ./install ]; then
9+
cd ..
10+
mkdir -p build
11+
cd build
12+
cmake -DBUILD_SHARED_LIBS=ON \
13+
-DCMAKE_BUILD_TYPE=Release \
14+
-DCMAKE_INSTALL_PREFIX=./install \
15+
-DSHERPA_NCNN_ENABLE_PORTAUDIO=OFF \
16+
-DSHERPA_NCNN_ENABLE_BINARY=OFF \
17+
-DSHERPA_NCNN_ENABLE_C_API=ON \
18+
-DSHERPA_NCNN_ENABLE_GENERATE_INT8_SCALE_TABLE=OFF \
19+
-DSHERPA_NCNN_ENABLE_PYTHON=OFF \
20+
..
21+
make -j3
22+
make install
23+
cd ../nodejs-examples
24+
ln -s $PWD/../build/install .
25+
fi
26+
27+
if [ ! -d ./sherpa-ncnn-streaming-zipformer-bilingual-zh-en-2023-02-13 ]; then
28+
echo "Please refer to"
29+
echo "https://k2-fsa.github.io/sherpa/ncnn/pretrained_models/zipformer-transucer-models.html#csukuangfj-sherpa-ncnn-streaming-zipformer-bilingual-zh-en-2023-02-13-bilingual-chinese-english"
30+
echo "to download the models"
31+
exit 0
32+
fi
33+
34+
node ./test.js

0 commit comments

Comments
 (0)