Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions .github/scripts/test-nodejs-addon-npm.sh
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,15 @@ arch=$(node -p "require('os').arch()")
platform=$(node -p "require('os').platform()")
node_version=$(node -p "process.versions.node.split('.')[0]")

echo "----------non-streaming ASR FunASR Nano----------"

curl -SL -O https://github.com/k2-fsa/sherpa-onnx/releases/download/asr-models/sherpa-onnx-funasr-nano-int8-2025-12-30.tar.bz2
tar xvf sherpa-onnx-funasr-nano-int8-2025-12-30.tar.bz2
rm sherpa-onnx-funasr-nano-int8-2025-12-30.tar.bz2

node ./test_asr_non_streaming_funasr_nano.js
rm -rf sherpa-onnx-funasr-nano-int8-2025-12-30
Comment on lines +15 to +20

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

To improve script robustness and maintainability, it's better to chain commands that depend on each other with &&. This ensures that the script will stop if a command fails. Also, using a variable for the model name avoids repetition and makes it easier to update.

Consider adding set -e at the top of your script to make it exit immediately if a command exits with a non-zero status.

Suggested change
curl -SL -O https://github.com/k2-fsa/sherpa-onnx/releases/download/asr-models/sherpa-onnx-funasr-nano-int8-2025-12-30.tar.bz2
tar xvf sherpa-onnx-funasr-nano-int8-2025-12-30.tar.bz2
rm sherpa-onnx-funasr-nano-int8-2025-12-30.tar.bz2
node ./test_asr_non_streaming_funasr_nano.js
rm -rf sherpa-onnx-funasr-nano-int8-2025-12-30
MODEL_NAME="sherpa-onnx-funasr-nano-int8-2025-12-30"
curl -SL -O "https://github.com/k2-fsa/sherpa-onnx/releases/download/asr-models/${MODEL_NAME}.tar.bz2" && \
tar xvf "${MODEL_NAME}.tar.bz2" && \
rm "${MODEL_NAME}.tar.bz2"
node ./test_asr_non_streaming_funasr_nano.js
rm -rf "${MODEL_NAME}"


echo "----------non-streaming ASR Google MedASR CTC----------"

wget https://github.com/k2-fsa/sherpa-onnx/releases/download/asr-models/sherpa-onnx-medasr-ctc-en-int8-2025-12-25.tar.bz2
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,31 @@ static SherpaOnnxOfflineMedAsrCtcModelConfig GetOfflineMedAsrCtcModelConfig(
return c;
}

static SherpaOnnxOfflineFunASRNanoModelConfig GetOfflineFunAsrNanoModelConfig(
Napi::Object obj) {
SherpaOnnxOfflineFunASRNanoModelConfig c;
memset(&c, 0, sizeof(c));

if (!obj.Has("funasrNano") || !obj.Get("funasrNano").IsObject()) {
return c;
}

Napi::Object o = obj.Get("funasrNano").As<Napi::Object>();

SHERPA_ONNX_ASSIGN_ATTR_STR(encoder_adaptor, encoderAdaptor);
SHERPA_ONNX_ASSIGN_ATTR_STR(llm, llm);
SHERPA_ONNX_ASSIGN_ATTR_STR(embedding, embedding);
SHERPA_ONNX_ASSIGN_ATTR_STR(tokenizer, tokenizer);
SHERPA_ONNX_ASSIGN_ATTR_STR(system_prompt, systemPrompt);
SHERPA_ONNX_ASSIGN_ATTR_STR(user_prompt, userPrompt);
SHERPA_ONNX_ASSIGN_ATTR_INT32(max_new_tokens, maxNewTokens);
SHERPA_ONNX_ASSIGN_ATTR_FLOAT(temperature, temperature);
SHERPA_ONNX_ASSIGN_ATTR_FLOAT(top_p, topP);
SHERPA_ONNX_ASSIGN_ATTR_INT32(seed, seed);

return c;
}

static SherpaOnnxOfflineDolphinModelConfig GetOfflineDolphinModelConfig(
Napi::Object obj) {
SherpaOnnxOfflineDolphinModelConfig c;
Expand Down Expand Up @@ -277,6 +302,7 @@ static SherpaOnnxOfflineModelConfig GetOfflineModelConfig(Napi::Object obj) {
c.wenet_ctc = GetOfflineWenetCtcModelConfig(o);
c.omnilingual = GetOfflineOmnilingualAsrCtcModelConfig(o);
c.medasr = GetOfflineMedAsrCtcModelConfig(o);
c.funasr_nano = GetOfflineFunAsrNanoModelConfig(o);

SHERPA_ONNX_ASSIGN_ATTR_STR(tokens, tokens);
SHERPA_ONNX_ASSIGN_ATTR_INT32(num_threads, numThreads);
Expand Down Expand Up @@ -373,6 +399,13 @@ static void FreeConfig(const SherpaOnnxOfflineRecognizerConfig &c) {
SHERPA_ONNX_DELETE_C_STR(c.model_config.omnilingual.model);
SHERPA_ONNX_DELETE_C_STR(c.model_config.medasr.model);

SHERPA_ONNX_DELETE_C_STR(c.model_config.funasr_nano.user_prompt);
SHERPA_ONNX_DELETE_C_STR(c.model_config.funasr_nano.system_prompt);
SHERPA_ONNX_DELETE_C_STR(c.model_config.funasr_nano.tokenizer);
SHERPA_ONNX_DELETE_C_STR(c.model_config.funasr_nano.embedding);
SHERPA_ONNX_DELETE_C_STR(c.model_config.funasr_nano.llm);
SHERPA_ONNX_DELETE_C_STR(c.model_config.funasr_nano.encoder_adaptor);

SHERPA_ONNX_DELETE_C_STR(c.model_config.tokens);
SHERPA_ONNX_DELETE_C_STR(c.model_config.provider);
SHERPA_ONNX_DELETE_C_STR(c.model_config.model_type);
Expand Down
11 changes: 11 additions & 0 deletions nodejs-addon-examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,7 @@ The following tables list the examples in this folder.
|[./test_asr_non_streaming_wenet_ctc.js](./test_asr_non_streaming_wenet_ctc.js)|Non-streaming speech recognition from a file using a [u2pp_conformer_yue](https://huggingface.co/ASLP-lab/WSYue-ASR/tree/main/u2pp_conformer_yue) CTC model with greedy search|
|[./test_asr_non_streaming_omnilingual_asr_ctc.js](./test_asr_non_streaming_omnilingual_asr_ctc.js)|Non-streaming speech recognition from a file using a [Omnilingual-ASR](https://github.com/facebookresearch/omnilingual-asr) CTC model with greedy search|
|[./test_asr_non_streaming_medasr_ctc.js](./test_asr_non_streaming_medasr_ctc.js)|Non-streaming speech recognition from a file using a [Google MedASR](https://github.com/google-health/medasr) CTC model with greedy search|
|[./test_asr_non_streaming_funasr_nano.js](./test_asr_non_streaming_funasr_nano.js)|Non-streaming speech recognition from a file using a [FunASR Nano](https://modelscope.cn/models/FunAudioLLM/Fun-ASR-Nano-2512) model|
|[./test_asr_non_streaming_nemo_canary.js](./test_asr_non_streaming_nemo_canary.js)|Non-streaming speech recognition from a file using a [NeMo](https://github.com/NVIDIA/NeMo) [Canary](https://k2-fsa.github.io/sherpa/onnx/nemo/canary.html#sherpa-onnx-nemo-canary-180m-flash-en-es-de-fr-int8-english-spanish-german-french) model|
|[./test_asr_non_streaming_zipformer_ctc.js](./test_asr_non_streaming_zipformer_ctc.js)|Non-streaming speech recognition from a file using a Zipformer CTC model with greedy search|
|[./test_asr_non_streaming_nemo_parakeet_tdt_v2.js](./test_asr_non_streaming_nemo_parakeet_tdt_v2.js)|Non-streaming speech recognition from a file using a [NeMo](https://github.com/NVIDIA/NeMo) [parakeet-tdt-0.6b-v2](https://k2-fsa.github.io/sherpa/onnx/pretrained_models/offline-transducer/nemo-transducer-models.html#sherpa-onnx-nemo-parakeet-tdt-0-6b-v2-int8-english) model with greedy search|
Expand Down Expand Up @@ -429,6 +430,16 @@ npm install naudiodon2
node ./test_vad_asr_non_streaming_nemo_ctc_microphone.js
```

### Non-streaming speech recognition with FunASR Nano models

```bash
wget https://github.com/k2-fsa/sherpa-onnx/releases/download/asr-models/sherpa-onnx-funasr-nano-int8-2025-12-30.tar.bz2
tar xvf sherpa-onnx-funasr-nano-int8-2025-12-30.tar.bz2
rm sherpa-onnx-funasr-nano-int8-2025-12-30.tar.bz2

node ./test_asr_non_streaming_funasr_nano.js
Comment on lines +436 to +440

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

For better maintainability and to avoid repeating the long model filename, consider introducing a variable in this example script. This makes it easier to update the model version in the future.

For example:

MODEL_NAME="sherpa-onnx-funasr-nano-int8-2025-12-30"
wget https://github.com/k2-fsa/sherpa-onnx/releases/download/asr-models/${MODEL_NAME}.tar.bz2
tar xvf "${MODEL_NAME}.tar.bz2"
rm "${MODEL_NAME}.tar.bz2"

node ./test_asr_non_streaming_funasr_nano.js

```

### Non-streaming speech recognition with Google MedASR CTC models

```bash
Expand Down
51 changes: 51 additions & 0 deletions nodejs-addon-examples/test_asr_non_streaming_funasr_nano.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
// Copyright (c) 2026 Xiaomi Corporation
const sherpa_onnx = require('sherpa-onnx-node');

// Please download test files from
// https://github.com/k2-fsa/sherpa-onnx/releases/tag/asr-models
const config = {
'featConfig': {
'sampleRate': 16000,
'featureDim': 80,
},
'modelConfig': {
'funasrNano': {
'encoderAdaptor':
'./sherpa-onnx-funasr-nano-int8-2025-12-30/encoder_adaptor.int8.onnx',
'llm': './sherpa-onnx-funasr-nano-int8-2025-12-30/llm.int8.onnx',
'embedding':
'./sherpa-onnx-funasr-nano-int8-2025-12-30/embedding.int8.onnx',
'tokenizer': './sherpa-onnx-funasr-nano-int8-2025-12-30/Qwen3-0.6B',
},
'tokens': '',
'numThreads': 2,
'provider': 'cpu',
'debug': 1,
}
};

const waveFilename =
'./sherpa-onnx-funasr-nano-int8-2025-12-30/test_wavs/lyrics.wav';
Comment on lines +6 to +28

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The model directory path is hardcoded in multiple places. To improve maintainability, it's a good practice to define it as a constant and reuse it. This makes it much easier to update the model version in the future.

For example:

const modelDir = './sherpa-onnx-funasr-nano-int8-2025-12-30';
const config = {
  'featConfig': {
    'sampleRate': 16000,
    'featureDim': 80,
  },
  'modelConfig': {
    'funasrNano': {
      'encoderAdaptor': `${modelDir}/encoder_adaptor.int8.onnx`,
      'llm': `${modelDir}/llm.int8.onnx`,
      'embedding': `${modelDir}/embedding.int8.onnx`,
      'tokenizer': `${modelDir}/Qwen3-0.6B`,
    },
    'tokens': '',
    'numThreads': 2,
    'provider': 'cpu',
    'debug': 1,
  }
};

const waveFilename = `${modelDir}/test_wavs/lyrics.wav`;


const recognizer = new sherpa_onnx.OfflineRecognizer(config);
console.log('Started')
let start = Date.now();
const stream = recognizer.createStream();
const wave = sherpa_onnx.readWave(waveFilename);
stream.acceptWaveform({sampleRate: wave.sampleRate, samples: wave.samples});

recognizer.decode(stream);
const result = recognizer.getResult(stream);
let stop = Date.now();
console.log('Done')

const elapsed_seconds = (stop - start) / 1000;
const duration = wave.samples.length / wave.sampleRate;
const real_time_factor = elapsed_seconds / duration;
console.log('Wave duration', duration.toFixed(3), 'seconds')
console.log('Elapsed', elapsed_seconds.toFixed(3), 'seconds')
console.log(
`RTF = ${elapsed_seconds.toFixed(3)}/${duration.toFixed(3)} =`,
real_time_factor.toFixed(3))
console.log(waveFilename)
console.log('result\n', result)