Fangjun Kuang
Committed by GitHub

Support not using external buffers for node-addon (#925)

@@ -18,7 +18,7 @@ fi @@ -18,7 +18,7 @@ fi
18 SHERPA_ONNX_VERSION=$(grep "SHERPA_ONNX_VERSION" ./CMakeLists.txt | cut -d " " -f 2 | cut -d '"' -f 2) 18 SHERPA_ONNX_VERSION=$(grep "SHERPA_ONNX_VERSION" ./CMakeLists.txt | cut -d " " -f 2 | cut -d '"' -f 2)
19 echo "SHERPA_ONNX_VERSION $SHERPA_ONNX_VERSION" 19 echo "SHERPA_ONNX_VERSION $SHERPA_ONNX_VERSION"
20 20
21 -# SHERPA_ONNX_VERSION=1.0.25 21 +# SHERPA_ONNX_VERSION=1.0.27
22 22
23 if [ -z $owner ]; then 23 if [ -z $owner ]; then
24 owner=k2-fsa 24 owner=k2-fsa
@@ -55,7 +55,7 @@ jobs: @@ -55,7 +55,7 @@ jobs:
55 55
56 SHERPA_ONNX_VERSION=$(grep "SHERPA_ONNX_VERSION" ./CMakeLists.txt | cut -d " " -f 2 | cut -d '"' -f 2) 56 SHERPA_ONNX_VERSION=$(grep "SHERPA_ONNX_VERSION" ./CMakeLists.txt | cut -d " " -f 2 | cut -d '"' -f 2)
57 echo "SHERPA_ONNX_VERSION $SHERPA_ONNX_VERSION" 57 echo "SHERPA_ONNX_VERSION $SHERPA_ONNX_VERSION"
58 - # SHERPA_ONNX_VERSION=1.0.25 58 + # SHERPA_ONNX_VERSION=1.0.27
59 59
60 src_dir=.github/scripts/node-addon 60 src_dir=.github/scripts/node-addon
61 sed -i.bak s/SHERPA_ONNX_VERSION/$SHERPA_ONNX_VERSION/g $src_dir/package.json 61 sed -i.bak s/SHERPA_ONNX_VERSION/$SHERPA_ONNX_VERSION/g $src_dir/package.json
1 { 1 {
2 "dependencies": { 2 "dependencies": {
3 - "sherpa-onnx-node": "^1.0.25" 3 + "sherpa-onnx-node": "^1.0.27"
4 } 4 }
5 } 5 }
@@ -24,7 +24,12 @@ const tts = createOfflineTts(); @@ -24,7 +24,12 @@ const tts = createOfflineTts();
24 const text = 'Alles hat ein Ende, nur die Wurst hat zwei.' 24 const text = 'Alles hat ein Ende, nur die Wurst hat zwei.'
25 25
26 let start = Date.now(); 26 let start = Date.now();
27 -const audio = tts.generate({text: text, sid: 0, speed: 1.0}); 27 +const audio = tts.generate({
  28 + text: text,
  29 + sid: 0,
  30 + speed: 1.0,
  31 + enableExternalBuffer: true,
  32 +});
28 let stop = Date.now(); 33 let stop = Date.now();
29 const elapsed_seconds = (stop - start) / 1000; 34 const elapsed_seconds = (stop - start) / 1000;
30 const duration = audio.samples.length / audio.sampleRate; 35 const duration = audio.samples.length / audio.sampleRate;
@@ -99,7 +99,7 @@ function do_check() { @@ -99,7 +99,7 @@ function do_check() {
99 ;; 99 ;;
100 2) 100 2)
101 echo "Check all files" 101 echo "Check all files"
102 - files=$(find $sherpa_onnx_dir/sherpa-onnx -name "*.h" -o -name "*.cc") 102 + files=$(find $sherpa_onnx_dir/sherpa-onnx/csrc $sherpa_onnx_dir/sherpa-onnx/python $sherpa_onnx_dir/scripts/node-addon-api/src $sherpa_onnx_dir/sherpa-onnx/jni $sherpa_onnx_dir/sherpa-onnx/c-api -name "*.h" -o -name "*.cc")
103 ;; 103 ;;
104 *) 104 *)
105 echo "Check last commit" 105 echo "Check last commit"
@@ -18,9 +18,9 @@ class SpeakerEmbeddingExtractor { @@ -18,9 +18,9 @@ class SpeakerEmbeddingExtractor {
18 } 18 }
19 19
20 // return a float32 array 20 // return a float32 array
21 - compute(stream) { 21 + compute(stream, enableExternalBuffer = true) {
22 return addon.speakerEmbeddingExtractorComputeEmbedding( 22 return addon.speakerEmbeddingExtractorComputeEmbedding(
23 - this.handle, stream.handle); 23 + this.handle, stream.handle, enableExternalBuffer);
24 } 24 }
25 } 25 }
26 26
@@ -11,8 +11,9 @@ class CircularBuffer { @@ -11,8 +11,9 @@ class CircularBuffer {
11 } 11 }
12 12
13 // return a float32 array 13 // return a float32 array
14 - get(startIndex, n) {  
15 - return addon.circularBufferGet(this.handle, startIndex, n); 14 + get(startIndex, n, enableExternalBuffer = true) {
  15 + return addon.circularBufferGet(
  16 + this.handle, startIndex, n, enableExternalBuffer);
16 } 17 }
17 18
18 pop(n) { 19 pop(n) {
@@ -48,23 +49,23 @@ config = { @@ -48,23 +49,23 @@ config = {
48 } 49 }
49 50
50 acceptWaveform(samples) { 51 acceptWaveform(samples) {
51 - addon.voiceActivityDetectorAcceptWaveform(this.handle, samples) 52 + addon.voiceActivityDetectorAcceptWaveform(this.handle, samples);
52 } 53 }
53 54
54 isEmpty() { 55 isEmpty() {
55 - return addon.voiceActivityDetectorIsEmpty(this.handle) 56 + return addon.voiceActivityDetectorIsEmpty(this.handle);
56 } 57 }
57 58
58 isDetected() { 59 isDetected() {
59 - return addon.voiceActivityDetectorIsDetected(this.handle) 60 + return addon.voiceActivityDetectorIsDetected(this.handle);
60 } 61 }
61 62
62 pop() { 63 pop() {
63 - addon.voiceActivityDetectorPop(this.handle) 64 + addon.voiceActivityDetectorPop(this.handle);
64 } 65 }
65 66
66 clear() { 67 clear() {
67 - addon.VoiceActivityDetectorClearWrapper(this.handle) 68 + addon.VoiceActivityDetectorClearWrapper(this.handle);
68 } 69 }
69 70
70 /* 71 /*
@@ -73,12 +74,12 @@ config = { @@ -73,12 +74,12 @@ config = {
73 start: a int32 74 start: a int32
74 } 75 }
75 */ 76 */
76 - front() {  
77 - return addon.voiceActivityDetectorFront(this.handle) 77 + front(enableExternalBuffer = true) {
  78 + return addon.voiceActivityDetectorFront(this.handle, enableExternalBuffer);
78 } 79 }
79 80
80 reset() { 81 reset() {
81 - return addon.VoiceActivityDetectorResetWrapper(this.handle) 82 + return addon.VoiceActivityDetectorResetWrapper(this.handle);
82 } 83 }
83 } 84 }
84 85
@@ -2,6 +2,7 @@ @@ -2,6 +2,7 @@
2 // 2 //
3 // Copyright (c) 2024 Xiaomi Corporation 3 // Copyright (c) 2024 Xiaomi Corporation
4 4
  5 +#include <algorithm>
5 #include <sstream> 6 #include <sstream>
6 7
7 #include "macros.h" // NOLINT 8 #include "macros.h" // NOLINT
@@ -265,6 +266,13 @@ static Napi::Object OfflineTtsGenerateWrapper(const Napi::CallbackInfo &info) { @@ -265,6 +266,13 @@ static Napi::Object OfflineTtsGenerateWrapper(const Napi::CallbackInfo &info) {
265 return {}; 266 return {};
266 } 267 }
267 268
  269 + bool enable_external_buffer = true;
  270 + if (obj.Has("enableExternalBuffer") &&
  271 + obj.Get("enableExternalBuffer").IsBoolean()) {
  272 + enable_external_buffer =
  273 + obj.Get("enableExternalBuffer").As<Napi::Boolean>().Value();
  274 + }
  275 +
268 Napi::String _text = obj.Get("text").As<Napi::String>(); 276 Napi::String _text = obj.Get("text").As<Napi::String>();
269 std::string text = _text.Utf8Value(); 277 std::string text = _text.Utf8Value();
270 int32_t sid = obj.Get("sid").As<Napi::Number>().Int32Value(); 278 int32_t sid = obj.Get("sid").As<Napi::Number>().Int32Value();
@@ -273,20 +281,37 @@ static Napi::Object OfflineTtsGenerateWrapper(const Napi::CallbackInfo &info) { @@ -273,20 +281,37 @@ static Napi::Object OfflineTtsGenerateWrapper(const Napi::CallbackInfo &info) {
273 const SherpaOnnxGeneratedAudio *audio = 281 const SherpaOnnxGeneratedAudio *audio =
274 SherpaOnnxOfflineTtsGenerate(tts, text.c_str(), sid, speed); 282 SherpaOnnxOfflineTtsGenerate(tts, text.c_str(), sid, speed);
275 283
276 - Napi::ArrayBuffer arrayBuffer = Napi::ArrayBuffer::New(  
277 - env, const_cast<float *>(audio->samples), sizeof(float) * audio->n,  
278 - [](Napi::Env /*env*/, void * /*data*/,  
279 - const SherpaOnnxGeneratedAudio *hint) {  
280 - SherpaOnnxDestroyOfflineTtsGeneratedAudio(hint);  
281 - },  
282 - audio);  
283 - Napi::Float32Array float32Array =  
284 - Napi::Float32Array::New(env, audio->n, arrayBuffer, 0);  
285 -  
286 - Napi::Object ans = Napi::Object::New(env);  
287 - ans.Set(Napi::String::New(env, "samples"), float32Array);  
288 - ans.Set(Napi::String::New(env, "sampleRate"), audio->sample_rate);  
289 - return ans; 284 + if (enable_external_buffer) {
  285 + Napi::ArrayBuffer arrayBuffer = Napi::ArrayBuffer::New(
  286 + env, const_cast<float *>(audio->samples), sizeof(float) * audio->n,
  287 + [](Napi::Env /*env*/, void * /*data*/,
  288 + const SherpaOnnxGeneratedAudio *hint) {
  289 + SherpaOnnxDestroyOfflineTtsGeneratedAudio(hint);
  290 + },
  291 + audio);
  292 + Napi::Float32Array float32Array =
  293 + Napi::Float32Array::New(env, audio->n, arrayBuffer, 0);
  294 +
  295 + Napi::Object ans = Napi::Object::New(env);
  296 + ans.Set(Napi::String::New(env, "samples"), float32Array);
  297 + ans.Set(Napi::String::New(env, "sampleRate"), audio->sample_rate);
  298 + return ans;
  299 + } else {
  300 + // don't use external buffer
  301 + Napi::ArrayBuffer arrayBuffer =
  302 + Napi::ArrayBuffer::New(env, sizeof(float) * audio->n);
  303 +
  304 + Napi::Float32Array float32Array =
  305 + Napi::Float32Array::New(env, audio->n, arrayBuffer, 0);
  306 +
  307 + std::copy(audio->samples, audio->samples + audio->n, float32Array.Data());
  308 +
  309 + Napi::Object ans = Napi::Object::New(env);
  310 + ans.Set(Napi::String::New(env, "samples"), float32Array);
  311 + ans.Set(Napi::String::New(env, "sampleRate"), audio->sample_rate);
  312 + SherpaOnnxDestroyOfflineTtsGeneratedAudio(audio);
  313 + return ans;
  314 + }
290 } 315 }
291 316
292 void InitNonStreamingTts(Napi::Env env, Napi::Object exports) { 317 void InitNonStreamingTts(Napi::Env env, Napi::Object exports) {
1 // scripts/node-addon-api/src/speaker-identification.cc 1 // scripts/node-addon-api/src/speaker-identification.cc
2 // 2 //
3 // Copyright (c) 2024 Xiaomi Corporation 3 // Copyright (c) 2024 Xiaomi Corporation
  4 +#include <algorithm>
4 #include <sstream> 5 #include <sstream>
5 6
6 #include "macros.h" // NOLINT 7 #include "macros.h" // NOLINT
@@ -175,9 +176,9 @@ static Napi::Boolean SpeakerEmbeddingExtractorIsReadyWrapper( @@ -175,9 +176,9 @@ static Napi::Boolean SpeakerEmbeddingExtractorIsReadyWrapper(
175 static Napi::Float32Array SpeakerEmbeddingExtractorComputeEmbeddingWrapper( 176 static Napi::Float32Array SpeakerEmbeddingExtractorComputeEmbeddingWrapper(
176 const Napi::CallbackInfo &info) { 177 const Napi::CallbackInfo &info) {
177 Napi::Env env = info.Env(); 178 Napi::Env env = info.Env();
178 - if (info.Length() != 2) { 179 + if (info.Length() != 2 && info.Length() != 3) {
179 std::ostringstream os; 180 std::ostringstream os;
180 - os << "Expect only 2 arguments. Given: " << info.Length(); 181 + os << "Expect only 2 or 3 arguments. Given: " << info.Length();
181 182
182 Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException(); 183 Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException();
183 184
@@ -199,6 +200,16 @@ static Napi::Float32Array SpeakerEmbeddingExtractorComputeEmbeddingWrapper( @@ -199,6 +200,16 @@ static Napi::Float32Array SpeakerEmbeddingExtractorComputeEmbeddingWrapper(
199 return {}; 200 return {};
200 } 201 }
201 202
  203 + bool enable_external_buffer = true;
  204 + if (info.Length() == 3) {
  205 + if (info[2].IsBoolean()) {
  206 + enable_external_buffer = info[2].As<Napi::Boolean>().Value();
  207 + } else {
  208 + Napi::TypeError::New(env, "Argument 2 should be a boolean.")
  209 + .ThrowAsJavaScriptException();
  210 + }
  211 + }
  212 +
202 SherpaOnnxSpeakerEmbeddingExtractor *extractor = 213 SherpaOnnxSpeakerEmbeddingExtractor *extractor =
203 info[0].As<Napi::External<SherpaOnnxSpeakerEmbeddingExtractor>>().Data(); 214 info[0].As<Napi::External<SherpaOnnxSpeakerEmbeddingExtractor>>().Data();
204 215
@@ -210,14 +221,29 @@ static Napi::Float32Array SpeakerEmbeddingExtractorComputeEmbeddingWrapper( @@ -210,14 +221,29 @@ static Napi::Float32Array SpeakerEmbeddingExtractorComputeEmbeddingWrapper(
210 221
211 int32_t dim = SherpaOnnxSpeakerEmbeddingExtractorDim(extractor); 222 int32_t dim = SherpaOnnxSpeakerEmbeddingExtractorDim(extractor);
212 223
213 - Napi::ArrayBuffer arrayBuffer = Napi::ArrayBuffer::New(  
214 - env, const_cast<float *>(v), sizeof(float) * dim,  
215 - [](Napi::Env /*env*/, void *data) {  
216 - SherpaOnnxSpeakerEmbeddingExtractorDestroyEmbedding(  
217 - reinterpret_cast<float *>(data));  
218 - }); 224 + if (enable_external_buffer) {
  225 + Napi::ArrayBuffer arrayBuffer = Napi::ArrayBuffer::New(
  226 + env, const_cast<float *>(v), sizeof(float) * dim,
  227 + [](Napi::Env /*env*/, void *data) {
  228 + SherpaOnnxSpeakerEmbeddingExtractorDestroyEmbedding(
  229 + reinterpret_cast<float *>(data));
  230 + });
  231 +
  232 + return Napi::Float32Array::New(env, dim, arrayBuffer, 0);
  233 + } else {
  234 + // don't use external buffer
  235 + Napi::ArrayBuffer arrayBuffer =
  236 + Napi::ArrayBuffer::New(env, sizeof(float) * dim);
  237 +
  238 + Napi::Float32Array float32Array =
  239 + Napi::Float32Array::New(env, dim, arrayBuffer, 0);
219 240
220 - return Napi::Float32Array::New(env, dim, arrayBuffer, 0); 241 + std::copy(v, v + dim, float32Array.Data());
  242 +
  243 + SherpaOnnxSpeakerEmbeddingExtractorDestroyEmbedding(v);
  244 +
  245 + return float32Array;
  246 + }
221 } 247 }
222 248
223 static Napi::External<SherpaOnnxSpeakerEmbeddingManager> 249 static Napi::External<SherpaOnnxSpeakerEmbeddingManager>
@@ -2,6 +2,7 @@ @@ -2,6 +2,7 @@
2 // 2 //
3 // Copyright (c) 2024 Xiaomi Corporation 3 // Copyright (c) 2024 Xiaomi Corporation
4 4
  5 +#include <algorithm>
5 #include <sstream> 6 #include <sstream>
6 7
7 #include "macros.h" // NOLINT 8 #include "macros.h" // NOLINT
@@ -75,9 +76,9 @@ static Napi::Float32Array CircularBufferGetWrapper( @@ -75,9 +76,9 @@ static Napi::Float32Array CircularBufferGetWrapper(
75 const Napi::CallbackInfo &info) { 76 const Napi::CallbackInfo &info) {
76 Napi::Env env = info.Env(); 77 Napi::Env env = info.Env();
77 78
78 - if (info.Length() != 3) { 79 + if (info.Length() != 3 && info.Length() != 4) {
79 std::ostringstream os; 80 std::ostringstream os;
80 - os << "Expect only 3 arguments. Given: " << info.Length(); 81 + os << "Expect only 3 or 4 arguments. Given: " << info.Length();
81 82
82 Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException(); 83 Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException();
83 84
@@ -108,21 +109,46 @@ static Napi::Float32Array CircularBufferGetWrapper( @@ -108,21 +109,46 @@ static Napi::Float32Array CircularBufferGetWrapper(
108 return {}; 109 return {};
109 } 110 }
110 111
  112 + bool enable_external_buffer = true;
  113 + if (info.Length() == 4) {
  114 + if (info[3].IsBoolean()) {
  115 + enable_external_buffer = info[3].As<Napi::Boolean>().Value();
  116 + } else {
  117 + Napi::TypeError::New(env, "Argument 3 should be a boolean.")
  118 + .ThrowAsJavaScriptException();
  119 + }
  120 + }
  121 +
111 int32_t start_index = info[1].As<Napi::Number>().Int32Value(); 122 int32_t start_index = info[1].As<Napi::Number>().Int32Value();
112 int32_t n = info[2].As<Napi::Number>().Int32Value(); 123 int32_t n = info[2].As<Napi::Number>().Int32Value();
113 124
114 const float *data = SherpaOnnxCircularBufferGet(buf, start_index, n); 125 const float *data = SherpaOnnxCircularBufferGet(buf, start_index, n);
115 126
116 - Napi::ArrayBuffer arrayBuffer = Napi::ArrayBuffer::New(  
117 - env, const_cast<float *>(data), sizeof(float) * n,  
118 - [](Napi::Env /*env*/, void *p) {  
119 - SherpaOnnxCircularBufferFree(reinterpret_cast<const float *>(p));  
120 - }); 127 + if (enable_external_buffer) {
  128 + Napi::ArrayBuffer arrayBuffer = Napi::ArrayBuffer::New(
  129 + env, const_cast<float *>(data), sizeof(float) * n,
  130 + [](Napi::Env /*env*/, void *p) {
  131 + SherpaOnnxCircularBufferFree(reinterpret_cast<const float *>(p));
  132 + });
  133 +
  134 + Napi::Float32Array float32Array =
  135 + Napi::Float32Array::New(env, n, arrayBuffer, 0);
121 136
122 - Napi::Float32Array float32Array =  
123 - Napi::Float32Array::New(env, n, arrayBuffer, 0); 137 + return float32Array;
  138 + } else {
  139 + // don't use external buffer
  140 + Napi::ArrayBuffer arrayBuffer = Napi::ArrayBuffer::New(
  141 + env, const_cast<float *>(data), sizeof(float) * n);
124 142
125 - return float32Array; 143 + Napi::Float32Array float32Array =
  144 + Napi::Float32Array::New(env, n, arrayBuffer, 0);
  145 +
  146 + std::copy(data, data + n, float32Array.Data());
  147 +
  148 + SherpaOnnxCircularBufferFree(data);
  149 +
  150 + return float32Array;
  151 + }
126 } 152 }
127 153
128 static void CircularBufferPopWrapper(const Napi::CallbackInfo &info) { 154 static void CircularBufferPopWrapper(const Napi::CallbackInfo &info) {
@@ -470,9 +496,9 @@ static Napi::Object VoiceActivityDetectorFrontWrapper( @@ -470,9 +496,9 @@ static Napi::Object VoiceActivityDetectorFrontWrapper(
470 const Napi::CallbackInfo &info) { 496 const Napi::CallbackInfo &info) {
471 Napi::Env env = info.Env(); 497 Napi::Env env = info.Env();
472 498
473 - if (info.Length() != 1) { 499 + if (info.Length() != 1 && info.Length() != 2) {
474 std::ostringstream os; 500 std::ostringstream os;
475 - os << "Expect only 1 argument. Given: " << info.Length(); 501 + os << "Expect only 1 or 2 arguments. Given: " << info.Length();
476 502
477 Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException(); 503 Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException();
478 504
@@ -486,28 +512,57 @@ static Napi::Object VoiceActivityDetectorFrontWrapper( @@ -486,28 +512,57 @@ static Napi::Object VoiceActivityDetectorFrontWrapper(
486 return {}; 512 return {};
487 } 513 }
488 514
  515 + bool enable_external_buffer = true;
  516 + if (info.Length() == 2) {
  517 + if (info[1].IsBoolean()) {
  518 + enable_external_buffer = info[1].As<Napi::Boolean>().Value();
  519 + } else {
  520 + Napi::TypeError::New(env, "Argument 1 should be a boolean.")
  521 + .ThrowAsJavaScriptException();
  522 + }
  523 + }
  524 +
489 SherpaOnnxVoiceActivityDetector *vad = 525 SherpaOnnxVoiceActivityDetector *vad =
490 info[0].As<Napi::External<SherpaOnnxVoiceActivityDetector>>().Data(); 526 info[0].As<Napi::External<SherpaOnnxVoiceActivityDetector>>().Data();
491 527
492 const SherpaOnnxSpeechSegment *segment = 528 const SherpaOnnxSpeechSegment *segment =
493 SherpaOnnxVoiceActivityDetectorFront(vad); 529 SherpaOnnxVoiceActivityDetectorFront(vad);
494 530
495 - Napi::ArrayBuffer arrayBuffer = Napi::ArrayBuffer::New(  
496 - env, const_cast<float *>(segment->samples), sizeof(float) * segment->n,  
497 - [](Napi::Env /*env*/, void * /*data*/,  
498 - const SherpaOnnxSpeechSegment *hint) {  
499 - SherpaOnnxDestroySpeechSegment(hint);  
500 - },  
501 - segment); 531 + if (enable_external_buffer) {
  532 + Napi::ArrayBuffer arrayBuffer = Napi::ArrayBuffer::New(
  533 + env, const_cast<float *>(segment->samples), sizeof(float) * segment->n,
  534 + [](Napi::Env /*env*/, void * /*data*/,
  535 + const SherpaOnnxSpeechSegment *hint) {
  536 + SherpaOnnxDestroySpeechSegment(hint);
  537 + },
  538 + segment);
  539 +
  540 + Napi::Float32Array float32Array =
  541 + Napi::Float32Array::New(env, segment->n, arrayBuffer, 0);
502 542
503 - Napi::Float32Array float32Array =  
504 - Napi::Float32Array::New(env, segment->n, arrayBuffer, 0); 543 + Napi::Object obj = Napi::Object::New(env);
  544 + obj.Set(Napi::String::New(env, "start"), segment->start);
  545 + obj.Set(Napi::String::New(env, "samples"), float32Array);
505 546
506 - Napi::Object obj = Napi::Object::New(env);  
507 - obj.Set(Napi::String::New(env, "start"), segment->start);  
508 - obj.Set(Napi::String::New(env, "samples"), float32Array); 547 + return obj;
  548 + } else {
  549 + Napi::ArrayBuffer arrayBuffer =
  550 + Napi::ArrayBuffer::New(env, sizeof(float) * segment->n);
509 551
510 - return obj; 552 + Napi::Float32Array float32Array =
  553 + Napi::Float32Array::New(env, segment->n, arrayBuffer, 0);
  554 +
  555 + std::copy(segment->samples, segment->samples + segment->n,
  556 + float32Array.Data());
  557 +
  558 + Napi::Object obj = Napi::Object::New(env);
  559 + obj.Set(Napi::String::New(env, "start"), segment->start);
  560 + obj.Set(Napi::String::New(env, "samples"), float32Array);
  561 +
  562 + SherpaOnnxDestroySpeechSegment(segment);
  563 +
  564 + return obj;
  565 + }
511 } 566 }
512 567
513 static void VoiceActivityDetectorResetWrapper(const Napi::CallbackInfo &info) { 568 static void VoiceActivityDetectorResetWrapper(const Napi::CallbackInfo &info) {
@@ -2,6 +2,7 @@ @@ -2,6 +2,7 @@
2 // 2 //
3 // Copyright (c) 2024 Xiaomi Corporation 3 // Copyright (c) 2024 Xiaomi Corporation
4 4
  5 +#include <algorithm>
5 #include <sstream> 6 #include <sstream>
6 7
7 #include "napi.h" // NOLINT 8 #include "napi.h" // NOLINT
@@ -9,16 +10,17 @@ @@ -9,16 +10,17 @@
9 10
10 static Napi::Object ReadWaveWrapper(const Napi::CallbackInfo &info) { 11 static Napi::Object ReadWaveWrapper(const Napi::CallbackInfo &info) {
11 Napi::Env env = info.Env(); 12 Napi::Env env = info.Env();
12 - if (info.Length() != 1) { 13 + if (info.Length() > 2) {
13 std::ostringstream os; 14 std::ostringstream os;
14 - os << "Expect only 1 argument. Given: " << info.Length(); 15 + os << "Expect only 2 arguments. Given: " << info.Length();
15 16
16 Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException(); 17 Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException();
17 18
18 return {}; 19 return {};
19 } 20 }
  21 +
20 if (!info[0].IsString()) { 22 if (!info[0].IsString()) {
21 - Napi::TypeError::New(env, "Argument should be a string") 23 + Napi::TypeError::New(env, "Argument 0 should be a string")
22 .ThrowAsJavaScriptException(); 24 .ThrowAsJavaScriptException();
23 25
24 return {}; 26 return {};
@@ -26,6 +28,18 @@ static Napi::Object ReadWaveWrapper(const Napi::CallbackInfo &info) { @@ -26,6 +28,18 @@ static Napi::Object ReadWaveWrapper(const Napi::CallbackInfo &info) {
26 28
27 std::string filename = info[0].As<Napi::String>().Utf8Value(); 29 std::string filename = info[0].As<Napi::String>().Utf8Value();
28 30
  31 + bool enable_external_buffer = true;
  32 + if (info.Length() == 2) {
  33 + if (info[1].IsBoolean()) {
  34 + enable_external_buffer = info[1].As<Napi::Boolean>().Value();
  35 + } else {
  36 + Napi::TypeError::New(env, "Argument 1 should be a boolean")
  37 + .ThrowAsJavaScriptException();
  38 +
  39 + return {};
  40 + }
  41 + }
  42 +
29 const SherpaOnnxWave *wave = SherpaOnnxReadWave(filename.c_str()); 43 const SherpaOnnxWave *wave = SherpaOnnxReadWave(filename.c_str());
30 if (!wave) { 44 if (!wave) {
31 std::ostringstream os; 45 std::ostringstream os;
@@ -35,20 +49,40 @@ static Napi::Object ReadWaveWrapper(const Napi::CallbackInfo &info) { @@ -35,20 +49,40 @@ static Napi::Object ReadWaveWrapper(const Napi::CallbackInfo &info) {
35 return {}; 49 return {};
36 } 50 }
37 51
38 - Napi::ArrayBuffer arrayBuffer = Napi::ArrayBuffer::New(  
39 - env, const_cast<float *>(wave->samples),  
40 - sizeof(float) * wave->num_samples,  
41 - [](Napi::Env /*env*/, void * /*data*/, const SherpaOnnxWave *hint) {  
42 - SherpaOnnxFreeWave(hint);  
43 - },  
44 - wave);  
45 - Napi::Float32Array float32Array =  
46 - Napi::Float32Array::New(env, wave->num_samples, arrayBuffer, 0);  
47 -  
48 - Napi::Object obj = Napi::Object::New(env);  
49 - obj.Set(Napi::String::New(env, "samples"), float32Array);  
50 - obj.Set(Napi::String::New(env, "sampleRate"), wave->sample_rate);  
51 - return obj; 52 + if (enable_external_buffer) {
  53 + Napi::ArrayBuffer arrayBuffer = Napi::ArrayBuffer::New(
  54 + env, const_cast<float *>(wave->samples),
  55 + sizeof(float) * wave->num_samples,
  56 + [](Napi::Env /*env*/, void * /*data*/, const SherpaOnnxWave *hint) {
  57 + SherpaOnnxFreeWave(hint);
  58 + },
  59 + wave);
  60 + Napi::Float32Array float32Array =
  61 + Napi::Float32Array::New(env, wave->num_samples, arrayBuffer, 0);
  62 +
  63 + Napi::Object obj = Napi::Object::New(env);
  64 + obj.Set(Napi::String::New(env, "samples"), float32Array);
  65 + obj.Set(Napi::String::New(env, "sampleRate"), wave->sample_rate);
  66 + return obj;
  67 + } else {
  68 + // don't use external buffer
  69 + Napi::ArrayBuffer arrayBuffer =
  70 + Napi::ArrayBuffer::New(env, sizeof(float) * wave->num_samples);
  71 +
  72 + Napi::Float32Array float32Array =
  73 + Napi::Float32Array::New(env, wave->num_samples, arrayBuffer, 0);
  74 +
  75 + std::copy(wave->samples, wave->samples + wave->num_samples,
  76 + float32Array.Data());
  77 +
  78 + Napi::Object obj = Napi::Object::New(env);
  79 + obj.Set(Napi::String::New(env, "samples"), float32Array);
  80 + obj.Set(Napi::String::New(env, "sampleRate"), wave->sample_rate);
  81 +
  82 + SherpaOnnxFreeWave(wave);
  83 +
  84 + return obj;
  85 + }
52 } 86 }
53 87
54 void InitWaveReader(Napi::Env env, Napi::Object exports) { 88 void InitWaveReader(Napi::Env env, Napi::Object exports) {