Fangjun Kuang
Committed by GitHub

Add more streaming ASR methods for node-addon-api (#860)

@@ -28,9 +28,13 @@ export LD_LIBRARY_PATH=$PWD/node_modules/sherpa-onnx-linux-arm64:$LD_LIBRARY_PAT @@ -28,9 +28,13 @@ export LD_LIBRARY_PATH=$PWD/node_modules/sherpa-onnx-linux-arm64:$LD_LIBRARY_PAT
28 ``` 28 ```
29 29
30 ## Streaming speech recognition with zipformer transducer 30 ## Streaming speech recognition with zipformer transducer
  31 +
31 ```bash 32 ```bash
32 wget https://github.com/k2-fsa/sherpa-onnx/releases/download/asr-models/sherpa-onnx-streaming-zipformer-bilingual-zh-en-2023-02-20.tar.bz2 33 wget https://github.com/k2-fsa/sherpa-onnx/releases/download/asr-models/sherpa-onnx-streaming-zipformer-bilingual-zh-en-2023-02-20.tar.bz2
33 tar xvf sherpa-onnx-streaming-zipformer-bilingual-zh-en-2023-02-20.tar.bz2 34 tar xvf sherpa-onnx-streaming-zipformer-bilingual-zh-en-2023-02-20.tar.bz2
34 rm sherpa-onnx-streaming-zipformer-bilingual-zh-en-2023-02-20.tar.bz2 35 rm sherpa-onnx-streaming-zipformer-bilingual-zh-en-2023-02-20.tar.bz2
35 36
  37 +node ./test_asr_streaming_transducer.js
  38 +
  39 +node ./test_asr_streaming_transducer_microphone.js
36 ``` 40 ```
1 { 1 {
2 "dependencies": { 2 "dependencies": {
3 - "sherpa-onnx-node": "*",  
4 - "perf_hooks": "*" 3 + "naudiodon2": "^2.4.0",
  4 + "perf_hooks": "*",
  5 + "sherpa-onnx-node": "*"
5 } 6 }
6 } 7 }
  1 +// Copyright (c) 2023-2024 Xiaomi Corporation (authors: Fangjun Kuang)
  2 +//
  3 +const portAudio = require('naudiodon2');
  4 +// console.log(portAudio.getDevices());
  5 +
  6 +const sherpa_onnx = require('sherpa-onnx-node');
  7 +
  8 +function createOnlineRecognizer() {
  9 + const config = {
  10 + 'featConfig': {
  11 + 'sampleRate': 16000,
  12 + 'featureDim': 80,
  13 + },
  14 + 'modelConfig': {
  15 + 'transducer': {
  16 + 'encoder':
  17 + './sherpa-onnx-streaming-zipformer-bilingual-zh-en-2023-02-20/encoder-epoch-99-avg-1.onnx',
  18 + 'decoder':
  19 + './sherpa-onnx-streaming-zipformer-bilingual-zh-en-2023-02-20/decoder-epoch-99-avg-1.onnx',
  20 + 'joiner':
  21 + './sherpa-onnx-streaming-zipformer-bilingual-zh-en-2023-02-20/joiner-epoch-99-avg-1.onnx',
  22 + },
  23 + 'tokens':
  24 + './sherpa-onnx-streaming-zipformer-bilingual-zh-en-2023-02-20/tokens.txt',
  25 + 'numThreads': 2,
  26 + 'provider': 'cpu',
  27 + 'debug': 1,
  28 + 'modelType': 'zipformer',
  29 + },
  30 + 'decodingMethod': 'greedy_search',
  31 + 'maxActivePaths': 4,
  32 + 'enableEndpoint': true,
  33 + 'rule1MinTrailingSilence': 2.4,
  34 + 'rule2MinTrailingSilence': 1.2,
  35 + 'rule3MinUtteranceLength': 20
  36 + };
  37 +
  38 + return new sherpa_onnx.OnlineRecognizer(config);
  39 +}
  40 +
  41 +const recognizer = createOnlineRecognizer();
  42 +const stream = recognizer.createStream();
  43 +
  44 +let lastText = '';
  45 +let segmentIndex = 0;
  46 +
  47 +const ai = new portAudio.AudioIO({
  48 + inOptions: {
  49 + channelCount: 1,
  50 + closeOnError: true, // Close the stream if an audio error is detected, if
  51 + // set false then just log the error
  52 + deviceId: -1, // Use -1 or omit the deviceId to select the default device
  53 + sampleFormat: portAudio.SampleFormatFloat32,
  54 + sampleRate: recognizer.config.featConfig.sampleRate
  55 + }
  56 +});
  57 +
  58 +const display = new sherpa_onnx.Display(50);
  59 +
  60 +ai.on('data', data => {
  61 + const samples = new Float32Array(data.buffer);
  62 +
  63 + stream.acceptWaveform(samples, recognizer.config.featConfig.sampleRate);
  64 +
  65 + while (recognizer.isReady(stream)) {
  66 + recognizer.decode(stream);
  67 + }
  68 +
  69 + const isEndpoint = recognizer.isEndpoint(stream);
  70 + const text = recognizer.getResult(stream).text;
  71 +
  72 + if (text.length > 0 && lastText != text) {
  73 + lastText = text;
  74 + display.print(segmentIndex, lastText);
  75 + }
  76 + if (isEndpoint) {
  77 + if (text.length > 0) {
  78 + lastText = text;
  79 + segmentIndex += 1;
  80 + }
  81 + recognizer.reset(stream)
  82 + }
  83 +});
  84 +
  85 +ai.on('close', () => {
  86 + console.log('Free resources');
  87 + stream.free();
  88 + recognizer.free();
  89 +});
  90 +
  91 +ai.start();
  92 +console.log('Started! Please speak')
@@ -4,4 +4,5 @@ const streaming_asr = require('./streaming-asr.js'); @@ -4,4 +4,5 @@ const streaming_asr = require('./streaming-asr.js');
4 module.exports = { 4 module.exports = {
5 OnlineRecognizer: streaming_asr.OnlineRecognizer, 5 OnlineRecognizer: streaming_asr.OnlineRecognizer,
6 readWave: addon.readWave, 6 readWave: addon.readWave,
  7 + Display: streaming_asr.Display,
7 } 8 }
1 const addon = require('./addon.js'); 1 const addon = require('./addon.js');
2 2
  3 +class Display {
  4 + constructor(maxWordPerline) {
  5 + this.handle = addon.createDisplay(maxWordPerline);
  6 + }
  7 +
  8 + print(idx, text) {
  9 + addon.print(this.handle, idx, text)
  10 + }
  11 +}
  12 +
3 class OnlineStream { 13 class OnlineStream {
4 constructor(handle) { 14 constructor(handle) {
5 this.handle = handle; 15 this.handle = handle;
@@ -10,11 +20,16 @@ class OnlineStream { @@ -10,11 +20,16 @@ class OnlineStream {
10 addon.acceptWaveformOnline( 20 addon.acceptWaveformOnline(
11 this.handle, {samples: samples, sampleRate: sampleRate}) 21 this.handle, {samples: samples, sampleRate: sampleRate})
12 } 22 }
  23 +
  24 + inputFinished() {
  25 + addon.inputFinished(this.handle)
  26 + }
13 } 27 }
14 28
15 class OnlineRecognizer { 29 class OnlineRecognizer {
16 constructor(config) { 30 constructor(config) {
17 this.handle = addon.createOnlineRecognizer(config); 31 this.handle = addon.createOnlineRecognizer(config);
  32 + this.config = config
18 } 33 }
19 34
20 createStream() { 35 createStream() {
@@ -30,6 +45,14 @@ class OnlineRecognizer { @@ -30,6 +45,14 @@ class OnlineRecognizer {
30 addon.decodeOnlineStream(this.handle, stream.handle); 45 addon.decodeOnlineStream(this.handle, stream.handle);
31 } 46 }
32 47
  48 + isEndpoint(stream) {
  49 + return addon.isEndpoint(this.handle, stream.handle);
  50 + }
  51 +
  52 + reset(stream) {
  53 + addon.reset(this.handle, stream.handle);
  54 + }
  55 +
33 getResult(stream) { 56 getResult(stream) {
34 const jsonStr = 57 const jsonStr =
35 addon.getOnlineStreamResultAsJson(this.handle, stream.handle); 58 addon.getOnlineStreamResultAsJson(this.handle, stream.handle);
@@ -38,4 +61,7 @@ class OnlineRecognizer { @@ -38,4 +61,7 @@ class OnlineRecognizer {
38 } 61 }
39 } 62 }
40 63
41 -module.exports = {OnlineRecognizer} 64 +module.exports = {
  65 + OnlineRecognizer,
  66 + Display
  67 +}
@@ -166,6 +166,69 @@ static Napi::External<SherpaOnnxOnlineRecognizer> CreateOnlineRecognizerWrapper( @@ -166,6 +166,69 @@ static Napi::External<SherpaOnnxOnlineRecognizer> CreateOnlineRecognizerWrapper(
166 memset(&c, 0, sizeof(c)); 166 memset(&c, 0, sizeof(c));
167 c.feat_config = GetFeatureConfig(config); 167 c.feat_config = GetFeatureConfig(config);
168 c.model_config = GetOnlineModelConfig(config); 168 c.model_config = GetOnlineModelConfig(config);
  169 +
  170 + if (config.Has("decodingMethod") && config.Get("decodingMethod").IsString()) {
  171 + Napi::String decoding_method =
  172 + config.Get("decodingMethod").As<Napi::String>();
  173 + std::string s = decoding_method.Utf8Value();
  174 + char *p = new char[s.size() + 1];
  175 + std::copy(s.begin(), s.end(), p);
  176 + p[s.size()] = 0;
  177 +
  178 + c.decoding_method = p;
  179 + }
  180 +
  181 + if (config.Has("maxActivePaths") && config.Get("maxActivePaths").IsNumber()) {
  182 + c.max_active_paths =
  183 + config.Get("maxActivePaths").As<Napi::Number>().Int32Value();
  184 + }
  185 +
  186 + // enableEndpoint can be either a boolean or an integer
  187 + if (config.Has("enableEndpoint") &&
  188 + (config.Get("enableEndpoint").IsNumber() ||
  189 + config.Get("enableEndpoint").IsBoolean())) {
  190 + if (config.Get("enableEndpoint").IsNumber()) {
  191 + c.enable_endpoint =
  192 + config.Get("enableEndpoint").As<Napi::Number>().Int32Value();
  193 + } else {
  194 + c.enable_endpoint =
  195 + config.Get("enableEndpoint").As<Napi::Boolean>().Value();
  196 + }
  197 + }
  198 +
  199 + if (config.Has("rule1MinTrailingSilence") &&
  200 + config.Get("rule1MinTrailingSilence").IsNumber()) {
  201 + c.rule1_min_trailing_silence =
  202 + config.Get("rule1MinTrailingSilence").As<Napi::Number>().FloatValue();
  203 + }
  204 +
  205 + if (config.Has("rule2MinTrailingSilence") &&
  206 + config.Get("rule2MinTrailingSilence").IsNumber()) {
  207 + c.rule2_min_trailing_silence =
  208 + config.Get("rule2MinTrailingSilence").As<Napi::Number>().FloatValue();
  209 + }
  210 +
  211 + if (config.Has("rule3MinUtteranceLength") &&
  212 + config.Get("rule3MinUtteranceLength").IsNumber()) {
  213 + c.rule3_min_utterance_length =
  214 + config.Get("rule3MinUtteranceLength").As<Napi::Number>().FloatValue();
  215 + }
  216 +
  217 + if (config.Has("hotwordsFile") && config.Get("hotwordsFile").IsString()) {
  218 + Napi::String hotwords_file = config.Get("hotwordsFile").As<Napi::String>();
  219 + std::string s = hotwords_file.Utf8Value();
  220 + char *p = new char[s.size() + 1];
  221 + std::copy(s.begin(), s.end(), p);
  222 + p[s.size()] = 0;
  223 +
  224 + c.hotwords_file = p;
  225 + }
  226 +
  227 + if (config.Has("hotwordsScore") && config.Get("hotwordsScore").IsNumber()) {
  228 + c.hotwords_score =
  229 + config.Get("hotwordsScore").As<Napi::Number>().FloatValue();
  230 + }
  231 +
169 #if 0 232 #if 0
170 printf("encoder: %s\n", c.model_config.transducer.encoder 233 printf("encoder: %s\n", c.model_config.transducer.encoder
171 ? c.model_config.transducer.encoder 234 ? c.model_config.transducer.encoder
@@ -184,6 +247,15 @@ static Napi::External<SherpaOnnxOnlineRecognizer> CreateOnlineRecognizerWrapper( @@ -184,6 +247,15 @@ static Napi::External<SherpaOnnxOnlineRecognizer> CreateOnlineRecognizerWrapper(
184 printf("debug: %d\n", c.model_config.debug); 247 printf("debug: %d\n", c.model_config.debug);
185 printf("model_type: %s\n", 248 printf("model_type: %s\n",
186 c.model_config.model_type ? c.model_config.model_type : "no"); 249 c.model_config.model_type ? c.model_config.model_type : "no");
  250 +
  251 + printf("decoding_method: %s\n", c.decoding_method ? c.decoding_method : "no");
  252 + printf("max_active_paths: %d\n", c.max_active_paths);
  253 + printf("enable_endpoint: %d\n", c.enable_endpoint);
  254 + printf("rule1_min_trailing_silence: %.3f\n", c.rule1_min_trailing_silence);
  255 + printf("rule2_min_trailing_silence: %.3f\n", c.rule2_min_trailing_silence);
  256 + printf("rule3_min_utterance_length: %.3f\n", c.rule3_min_utterance_length);
  257 + printf("hotwords_file: %s\n", c.hotwords_file ? c.hotwords_file : "no");
  258 + printf("hotwords_score: %.3f\n", c.hotwords_score);
187 #endif 259 #endif
188 260
189 SherpaOnnxOnlineRecognizer *recognizer = CreateOnlineRecognizer(&c); 261 SherpaOnnxOnlineRecognizer *recognizer = CreateOnlineRecognizer(&c);
@@ -212,6 +284,14 @@ static Napi::External<SherpaOnnxOnlineRecognizer> CreateOnlineRecognizerWrapper( @@ -212,6 +284,14 @@ static Napi::External<SherpaOnnxOnlineRecognizer> CreateOnlineRecognizerWrapper(
212 delete[] c.model_config.model_type; 284 delete[] c.model_config.model_type;
213 } 285 }
214 286
  287 + if (c.decoding_method) {
  288 + delete[] c.decoding_method;
  289 + }
  290 +
  291 + if (c.hotwords_file) {
  292 + delete[] c.hotwords_file;
  293 + }
  294 +
215 if (!recognizer) { 295 if (!recognizer) {
216 Napi::TypeError::New(env, "Please check your config!") 296 Napi::TypeError::New(env, "Please check your config!")
217 .ThrowAsJavaScriptException(); 297 .ThrowAsJavaScriptException();
@@ -270,7 +350,7 @@ static void AcceptWaveformWrapper(const Napi::CallbackInfo &info) { @@ -270,7 +350,7 @@ static void AcceptWaveformWrapper(const Napi::CallbackInfo &info) {
270 } 350 }
271 351
272 if (!info[0].IsExternal()) { 352 if (!info[0].IsExternal()) {
273 - Napi::TypeError::New(env, "Argument 0 should be a online stream pointer.") 353 + Napi::TypeError::New(env, "Argument 0 should be an online stream pointer.")
274 .ThrowAsJavaScriptException(); 354 .ThrowAsJavaScriptException();
275 355
276 return; 356 return;
@@ -337,15 +417,14 @@ static Napi::Boolean IsOnlineStreamReadyWrapper( @@ -337,15 +417,14 @@ static Napi::Boolean IsOnlineStreamReadyWrapper(
337 417
338 if (!info[0].IsExternal()) { 418 if (!info[0].IsExternal()) {
339 Napi::TypeError::New(env, 419 Napi::TypeError::New(env,
340 - "Argument 0 should be a online recognizer pointer.") 420 + "Argument 0 should be an online recognizer pointer.")
341 .ThrowAsJavaScriptException(); 421 .ThrowAsJavaScriptException();
342 422
343 return {}; 423 return {};
344 } 424 }
345 425
346 if (!info[1].IsExternal()) { 426 if (!info[1].IsExternal()) {
347 - Napi::TypeError::New(env,  
348 - "Argument 1 should be a online recognizer pointer.") 427 + Napi::TypeError::New(env, "Argument 1 should be an online stream pointer.")
349 .ThrowAsJavaScriptException(); 428 .ThrowAsJavaScriptException();
350 429
351 return {}; 430 return {};
@@ -375,15 +454,14 @@ static void DecodeOnlineStreamWrapper(const Napi::CallbackInfo &info) { @@ -375,15 +454,14 @@ static void DecodeOnlineStreamWrapper(const Napi::CallbackInfo &info) {
375 454
376 if (!info[0].IsExternal()) { 455 if (!info[0].IsExternal()) {
377 Napi::TypeError::New(env, 456 Napi::TypeError::New(env,
378 - "Argument 0 should be a online recognizer pointer.") 457 + "Argument 0 should be an online recognizer pointer.")
379 .ThrowAsJavaScriptException(); 458 .ThrowAsJavaScriptException();
380 459
381 return; 460 return;
382 } 461 }
383 462
384 if (!info[1].IsExternal()) { 463 if (!info[1].IsExternal()) {
385 - Napi::TypeError::New(env,  
386 - "Argument 1 should be a online recognizer pointer.") 464 + Napi::TypeError::New(env, "Argument 1 should be an online stream pointer.")
387 .ThrowAsJavaScriptException(); 465 .ThrowAsJavaScriptException();
388 466
389 return; 467 return;
@@ -412,15 +490,14 @@ static Napi::String GetOnlineStreamResultAsJsonWrapper( @@ -412,15 +490,14 @@ static Napi::String GetOnlineStreamResultAsJsonWrapper(
412 490
413 if (!info[0].IsExternal()) { 491 if (!info[0].IsExternal()) {
414 Napi::TypeError::New(env, 492 Napi::TypeError::New(env,
415 - "Argument 0 should be a online recognizer pointer.") 493 + "Argument 0 should be an online recognizer pointer.")
416 .ThrowAsJavaScriptException(); 494 .ThrowAsJavaScriptException();
417 495
418 return {}; 496 return {};
419 } 497 }
420 498
421 if (!info[1].IsExternal()) { 499 if (!info[1].IsExternal()) {
422 - Napi::TypeError::New(env,  
423 - "Argument 1 should be a online recognizer pointer.") 500 + Napi::TypeError::New(env, "Argument 1 should be an online stream pointer.")
424 .ThrowAsJavaScriptException(); 501 .ThrowAsJavaScriptException();
425 502
426 return {}; 503 return {};
@@ -440,6 +517,175 @@ static Napi::String GetOnlineStreamResultAsJsonWrapper( @@ -440,6 +517,175 @@ static Napi::String GetOnlineStreamResultAsJsonWrapper(
440 return s; 517 return s;
441 } 518 }
442 519
  520 +static void InputFinishedWrapper(const Napi::CallbackInfo &info) {
  521 + Napi::Env env = info.Env();
  522 +
  523 + if (info.Length() != 1) {
  524 + std::ostringstream os;
  525 + os << "Expect only 1 arguments. Given: " << info.Length();
  526 +
  527 + Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException();
  528 +
  529 + return;
  530 + }
  531 +
  532 + if (!info[0].IsExternal()) {
  533 + Napi::TypeError::New(env, "Argument 0 should be an online stream pointer.")
  534 + .ThrowAsJavaScriptException();
  535 +
  536 + return;
  537 + }
  538 +
  539 + SherpaOnnxOnlineStream *stream =
  540 + info[0].As<Napi::External<SherpaOnnxOnlineStream>>().Data();
  541 +
  542 + InputFinished(stream);
  543 +}
  544 +
  545 +static void ResetOnlineStreamWrapper(const Napi::CallbackInfo &info) {
  546 + Napi::Env env = info.Env();
  547 + if (info.Length() != 2) {
  548 + std::ostringstream os;
  549 + os << "Expect only 2 arguments. Given: " << info.Length();
  550 +
  551 + Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException();
  552 +
  553 + return;
  554 + }
  555 +
  556 + if (!info[0].IsExternal()) {
  557 + Napi::TypeError::New(env,
  558 + "Argument 0 should be an online recognizer pointer.")
  559 + .ThrowAsJavaScriptException();
  560 +
  561 + return;
  562 + }
  563 +
  564 + if (!info[1].IsExternal()) {
  565 + Napi::TypeError::New(env, "Argument 1 should be an online stream pointer.")
  566 + .ThrowAsJavaScriptException();
  567 +
  568 + return;
  569 + }
  570 +
  571 + SherpaOnnxOnlineRecognizer *recognizer =
  572 + info[0].As<Napi::External<SherpaOnnxOnlineRecognizer>>().Data();
  573 +
  574 + SherpaOnnxOnlineStream *stream =
  575 + info[1].As<Napi::External<SherpaOnnxOnlineStream>>().Data();
  576 +
  577 + Reset(recognizer, stream);
  578 +}
  579 +
  580 +static Napi::Boolean IsEndpointWrapper(const Napi::CallbackInfo &info) {
  581 + Napi::Env env = info.Env();
  582 + if (info.Length() != 2) {
  583 + std::ostringstream os;
  584 + os << "Expect only 2 arguments. Given: " << info.Length();
  585 +
  586 + Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException();
  587 +
  588 + return {};
  589 + }
  590 +
  591 + if (!info[0].IsExternal()) {
  592 + Napi::TypeError::New(env,
  593 + "Argument 0 should be an online recognizer pointer.")
  594 + .ThrowAsJavaScriptException();
  595 +
  596 + return {};
  597 + }
  598 +
  599 + if (!info[1].IsExternal()) {
  600 + Napi::TypeError::New(env, "Argument 1 should be an online stream pointer.")
  601 + .ThrowAsJavaScriptException();
  602 +
  603 + return {};
  604 + }
  605 +
  606 + SherpaOnnxOnlineRecognizer *recognizer =
  607 + info[0].As<Napi::External<SherpaOnnxOnlineRecognizer>>().Data();
  608 +
  609 + SherpaOnnxOnlineStream *stream =
  610 + info[1].As<Napi::External<SherpaOnnxOnlineStream>>().Data();
  611 +
  612 + int32_t is_endpoint = IsEndpoint(recognizer, stream);
  613 +
  614 + return Napi::Boolean::New(env, is_endpoint);
  615 +}
  616 +
  617 +static Napi::External<SherpaOnnxDisplay> CreateDisplayWrapper(
  618 + const Napi::CallbackInfo &info) {
  619 + Napi::Env env = info.Env();
  620 + if (info.Length() != 1) {
  621 + std::ostringstream os;
  622 + os << "Expect only 1 argument. Given: " << info.Length();
  623 +
  624 + Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException();
  625 +
  626 + return {};
  627 + }
  628 +
  629 + if (!info[0].IsNumber()) {
  630 + Napi::TypeError::New(env, "Expect a number as the argument")
  631 + .ThrowAsJavaScriptException();
  632 +
  633 + return {};
  634 + }
  635 + int32_t max_word_per_line = info[0].As<Napi::Number>().Int32Value();
  636 +
  637 + const SherpaOnnxDisplay *display = CreateDisplay(max_word_per_line);
  638 +
  639 + return Napi::External<SherpaOnnxDisplay>::New(
  640 + env, const_cast<SherpaOnnxDisplay *>(display),
  641 + [](Napi::Env env, SherpaOnnxDisplay *display) {
  642 + DestroyDisplay(display);
  643 + });
  644 +}
  645 +
  646 +static void PrintWrapper(const Napi::CallbackInfo &info) {
  647 + Napi::Env env = info.Env();
  648 +
  649 + if (info.Length() != 3) {
  650 + std::ostringstream os;
  651 + os << "Expect only 3 arguments. Given: " << info.Length();
  652 +
  653 + Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException();
  654 +
  655 + return;
  656 + }
  657 +
  658 + if (!info[0].IsExternal()) {
  659 + Napi::TypeError::New(env, "Argument 0 should be an online stream pointer.")
  660 + .ThrowAsJavaScriptException();
  661 +
  662 + return;
  663 + }
  664 +
  665 + if (!info[1].IsNumber()) {
  666 + Napi::TypeError::New(env, "Argument 1 should be a number.")
  667 + .ThrowAsJavaScriptException();
  668 +
  669 + return;
  670 + }
  671 +
  672 + if (!info[2].IsString()) {
  673 + Napi::TypeError::New(env, "Argument 2 should be a string.")
  674 + .ThrowAsJavaScriptException();
  675 +
  676 + return;
  677 + }
  678 +
  679 + SherpaOnnxDisplay *display =
  680 + info[0].As<Napi::External<SherpaOnnxDisplay>>().Data();
  681 +
  682 + int32_t idx = info[1].As<Napi::Number>().Int32Value();
  683 +
  684 + Napi::String text = info[2].As<Napi::String>();
  685 + std::string s = text.Utf8Value();
  686 + SherpaOnnxPrint(display, idx, s.c_str());
  687 +}
  688 +
443 void InitStreamingAsr(Napi::Env env, Napi::Object exports) { 689 void InitStreamingAsr(Napi::Env env, Napi::Object exports) {
444 exports.Set(Napi::String::New(env, "createOnlineRecognizer"), 690 exports.Set(Napi::String::New(env, "createOnlineRecognizer"),
445 Napi::Function::New(env, CreateOnlineRecognizerWrapper)); 691 Napi::Function::New(env, CreateOnlineRecognizerWrapper));
@@ -458,4 +704,19 @@ void InitStreamingAsr(Napi::Env env, Napi::Object exports) { @@ -458,4 +704,19 @@ void InitStreamingAsr(Napi::Env env, Napi::Object exports) {
458 704
459 exports.Set(Napi::String::New(env, "getOnlineStreamResultAsJson"), 705 exports.Set(Napi::String::New(env, "getOnlineStreamResultAsJson"),
460 Napi::Function::New(env, GetOnlineStreamResultAsJsonWrapper)); 706 Napi::Function::New(env, GetOnlineStreamResultAsJsonWrapper));
  707 +
  708 + exports.Set(Napi::String::New(env, "inputFinished"),
  709 + Napi::Function::New(env, InputFinishedWrapper));
  710 +
  711 + exports.Set(Napi::String::New(env, "reset"),
  712 + Napi::Function::New(env, ResetOnlineStreamWrapper));
  713 +
  714 + exports.Set(Napi::String::New(env, "isEndpoint"),
  715 + Napi::Function::New(env, IsEndpointWrapper));
  716 +
  717 + exports.Set(Napi::String::New(env, "createDisplay"),
  718 + Napi::Function::New(env, CreateDisplayWrapper));
  719 +
  720 + exports.Set(Napi::String::New(env, "print"),
  721 + Napi::Function::New(env, PrintWrapper));
461 } 722 }