mirror of
https://github.com/k2-fsa/sherpa-onnx.git
synced 2026-11-10 13:04:05 +08:00
Add JavaScript API (WebAssembly) for Moonshine v2 models (#3241)
This pull request expands the sherpa-onnx library's capabilities by adding comprehensive support for Moonshine v2 models within its JavaScript/WebAssembly API. This includes necessary modifications to model configurations, the introduction of a new example for practical usage, and updates to the continuous integration pipeline to ensure stability and correctness.
This commit is contained in:
1 parent
8519d61296
commit
d9e3fcf17d
10 files changed
+117
-40
No files matched your search
@@ -9,6 +9,16 @@ git status
|
||||
ls -lh
|
||||
ls -lh node_modules
|
||||
|
||||
echo "---test moonshine v2---"
|
||||
|
||||
curl -SL -O https://github.com/k2-fsa/sherpa-onnx/releases/download/asr-models/sherpa-onnx-moonshine-tiny-en-quantized-2026-02-27.tar.bz2
|
||||
tar xvf sherpa-onnx-moonshine-tiny-en-quantized-2026-02-27.tar.bz2
|
||||
rm sherpa-onnx-moonshine-tiny-en-quantized-2026-02-27.tar.bz2
|
||||
|
||||
node ./test-offline-moonshine-v2.js
|
||||
|
||||
rm -rf sherpa-onnx-moonshine-tiny-en-quantized-2026-02-27
|
||||
|
||||
echo "---test FireRedASR CTC---"
|
||||
curl -SL -O https://github.com/k2-fsa/sherpa-onnx/releases/download/asr-models/sherpa-onnx-fire-red-asr2-ctc-zh_en-int8-2026-02-25.tar.bz2
|
||||
tar xvf sherpa-onnx-fire-red-asr2-ctc-zh_en-int8-2026-02-25.tar.bz2
|
||||
|
||||
@@ -422,6 +422,22 @@ rm sherpa-onnx-fire-red-asr-large-zh_en-2025-02-16.tar.bz2
|
||||
node ./test-offline-fire-red-asr.js
|
||||
```
|
||||
|
||||
## ./test-offline-moonshine-v2.js
|
||||
|
||||
[./test-offline-moonshine-v2.js](./test-offline-moonshine-v2.js) demonstrates
|
||||
how to decode a file with a Moonshine v2 model. In the code we use
|
||||
[sherpa-onnx-moonshine-tiny-en-quantized-2026-02-27](https://github.com/k2-fsa/sherpa-onnx/releases/download/asr-models/sherpa-onnx-moonshine-tiny-en-quantized-2026-02-27.tar.bz2).
|
||||
|
||||
You can use the following command to run it:
|
||||
|
||||
```bash
|
||||
wget https://github.com/k2-fsa/sherpa-onnx/releases/download/asr-models/sherpa-onnx-moonshine-tiny-en-quantized-2026-02-27.tar.bz2
|
||||
tar xvf sherpa-onnx-moonshine-tiny-en-quantized-2026-02-27.tar.bz2
|
||||
rm sherpa-onnx-moonshine-tiny-en-quantized-2026-02-27.tar.bz2
|
||||
|
||||
node ./test-offline-moonshine-v2.js
|
||||
```
|
||||
|
||||
## ./test-offline-moonshine.js
|
||||
|
||||
[./test-offline-moonshine.js](./test-offline-moonshine.js) demonstrates
|
||||
|
||||
@@ -21,8 +21,8 @@ function createOfflineRecognizer() {
|
||||
return sherpa_onnx.createOfflineRecognizer(config);
|
||||
}
|
||||
|
||||
recognizer = createOfflineRecognizer();
|
||||
stream = recognizer.createStream();
|
||||
const recognizer = createOfflineRecognizer();
|
||||
const stream = recognizer.createStream();
|
||||
|
||||
const waveFilename =
|
||||
'./sherpa-onnx-fire-red-asr-large-zh_en-2025-02-16/test_wavs/0.wav';
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
// Copyright (c) 2023-2026 Xiaomi Corporation (authors: Fangjun Kuang)
|
||||
//
|
||||
const sherpa_onnx = require('sherpa-onnx');
|
||||
|
||||
function createOfflineRecognizer() {
|
||||
let modelConfig = {
|
||||
moonshine: {
|
||||
encoder:
|
||||
'./sherpa-onnx-moonshine-tiny-en-quantized-2026-02-27/encoder_model.ort',
|
||||
mergedDecoder:
|
||||
'./sherpa-onnx-moonshine-tiny-en-quantized-2026-02-27/decoder_model_merged.ort',
|
||||
},
|
||||
tokens: './sherpa-onnx-moonshine-tiny-en-quantized-2026-02-27/tokens.txt',
|
||||
};
|
||||
|
||||
let config = {
|
||||
modelConfig: modelConfig,
|
||||
};
|
||||
|
||||
return sherpa_onnx.createOfflineRecognizer(config);
|
||||
}
|
||||
|
||||
const recognizer = createOfflineRecognizer();
|
||||
const stream = recognizer.createStream();
|
||||
|
||||
const waveFilename =
|
||||
'./sherpa-onnx-moonshine-tiny-en-quantized-2026-02-27/test_wavs/0.wav';
|
||||
const wave = sherpa_onnx.readWave(waveFilename);
|
||||
stream.acceptWaveform(wave.sampleRate, wave.samples);
|
||||
|
||||
recognizer.decode(stream);
|
||||
const text = recognizer.getResult(stream).text;
|
||||
console.log(text);
|
||||
|
||||
stream.free();
|
||||
recognizer.free();
|
||||
@@ -22,8 +22,8 @@ function createOfflineRecognizer() {
|
||||
return sherpa_onnx.createOfflineRecognizer(config);
|
||||
}
|
||||
|
||||
recognizer = createOfflineRecognizer();
|
||||
stream = recognizer.createStream();
|
||||
const recognizer = createOfflineRecognizer();
|
||||
const stream = recognizer.createStream();
|
||||
|
||||
const waveFilename = './sherpa-onnx-moonshine-tiny-en-int8/test_wavs/0.wav';
|
||||
const wave = sherpa_onnx.readWave(waveFilename);
|
||||
|
||||
@@ -18,7 +18,7 @@ function createOfflineSpeechDenoiser() {
|
||||
return sherpa_onnx.createOfflineSpeechDenoiser(config);
|
||||
}
|
||||
|
||||
speech_denoiser = createOfflineSpeechDenoiser();
|
||||
const speech_denoiser = createOfflineSpeechDenoiser();
|
||||
|
||||
const waveFilename = './inp_16k.wav';
|
||||
const wave = sherpa_onnx.readWave(waveFilename);
|
||||
|
||||
@@ -24,8 +24,8 @@ function createOfflineRecognizer() {
|
||||
return sherpa_onnx.createOfflineRecognizer(config);
|
||||
}
|
||||
|
||||
recognizer = createOfflineRecognizer();
|
||||
stream = recognizer.createStream();
|
||||
const recognizer = createOfflineRecognizer();
|
||||
const stream = recognizer.createStream();
|
||||
|
||||
const waveFilename = './sherpa-onnx-whisper-tiny.en/test_wavs/0.wav';
|
||||
const wave = sherpa_onnx.readWave(waveFilename);
|
||||
|
||||
@@ -1,11 +1,15 @@
|
||||
// Copyright (c) 2023 Xiaomi Corporation (authors: Fangjun Kuang)
|
||||
const mic = require('mic'); // It uses `mic` for better compatibility, do check its [npm](https://www.npmjs.com/package/mic) before running it.
|
||||
const mic = require(
|
||||
'mic'); // It uses `mic` for better compatibility, do check its
|
||||
// [npm](https://www.npmjs.com/package/mic) before running it.
|
||||
const sherpa_onnx = require('sherpa-onnx');
|
||||
|
||||
function createOnlineRecognizer() {
|
||||
let onlineParaformerModelConfig = {
|
||||
encoder: './sherpa-onnx-streaming-paraformer-bilingual-zh-en/encoder.int8.onnx',
|
||||
decoder: './sherpa-onnx-streaming-paraformer-bilingual-zh-en/decoder.int8.onnx',
|
||||
encoder:
|
||||
'./sherpa-onnx-streaming-paraformer-bilingual-zh-en/encoder.int8.onnx',
|
||||
decoder:
|
||||
'./sherpa-onnx-streaming-paraformer-bilingual-zh-en/decoder.int8.onnx',
|
||||
};
|
||||
|
||||
let onlineModelConfig = {
|
||||
@@ -62,32 +66,32 @@ class SpeechSession {
|
||||
}
|
||||
|
||||
shouldStartNewSession() {
|
||||
return Date.now() - this.lastUpdateTime > 10000; // 10 seconds of silence
|
||||
return Date.now() - this.lastUpdateTime > 10000; // 10 seconds of silence
|
||||
}
|
||||
}
|
||||
|
||||
function formatOutput() {
|
||||
clearConsole();
|
||||
console.log('\n=== Automated Speech Recognition ===');
|
||||
console.log(`Current Session #${sessionCount}`);
|
||||
console.log('Time:', new Date().toLocaleTimeString());
|
||||
clearConsole();
|
||||
console.log('\n=== Automated Speech Recognition ===');
|
||||
console.log(`Current Session #${sessionCount}`);
|
||||
console.log('Time:', new Date().toLocaleTimeString());
|
||||
console.log('------------------------');
|
||||
|
||||
// 显示历史句子
|
||||
if (currentSession.sentences.length > 0) {
|
||||
console.log('Recognized Sentences:');
|
||||
currentSession.sentences.forEach((sentence, index) => {
|
||||
console.log(`[${sentence.timestamp}] ${index + 1}. ${sentence.text}`);
|
||||
});
|
||||
console.log('------------------------');
|
||||
|
||||
// 显示历史句子
|
||||
if (currentSession.sentences.length > 0) {
|
||||
console.log('Recognized Sentences:');
|
||||
currentSession.sentences.forEach((sentence, index) => {
|
||||
console.log(`[${sentence.timestamp}] ${index + 1}. ${sentence.text}`);
|
||||
});
|
||||
console.log('------------------------');
|
||||
}
|
||||
|
||||
// 显示当前正在识别的内容
|
||||
if (currentSession.currentText) {
|
||||
console.log('Recognizing:', currentSession.currentText);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 显示当前正在识别的内容
|
||||
if (currentSession.currentText) {
|
||||
console.log('Recognizing:', currentSession.currentText);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const recognizer = createOnlineRecognizer();
|
||||
const stream = recognizer.createStream();
|
||||
@@ -113,7 +117,7 @@ function exitHandler(options, exitCode) {
|
||||
const micInstance = mic({
|
||||
rate: recognizer.config.featConfig.sampleRate,
|
||||
channels: 1,
|
||||
debug: false, // 关闭调试输出
|
||||
debug: false, // 关闭调试输出
|
||||
device: 'default',
|
||||
bitwidth: 16,
|
||||
encoding: 'signed-integer',
|
||||
@@ -129,12 +133,12 @@ function startMic() {
|
||||
console.log('Mic phone started.');
|
||||
resolve();
|
||||
});
|
||||
|
||||
|
||||
micInputStream.once('error', (err) => {
|
||||
console.error('Mic phone start error:', err);
|
||||
reject(err);
|
||||
});
|
||||
|
||||
|
||||
micInstance.start();
|
||||
});
|
||||
}
|
||||
@@ -142,7 +146,7 @@ function startMic() {
|
||||
micInputStream.on('data', buffer => {
|
||||
const int16Array = new Int16Array(buffer.buffer);
|
||||
const samples = new Float32Array(int16Array.length);
|
||||
|
||||
|
||||
for (let i = 0; i < int16Array.length; i++) {
|
||||
samples[i] = int16Array[i] / 32768.0;
|
||||
}
|
||||
@@ -163,7 +167,7 @@ micInputStream.on('data', buffer => {
|
||||
sessionCount++;
|
||||
currentSession = new SpeechSession();
|
||||
}
|
||||
|
||||
|
||||
currentSession.addOrUpdateText(text);
|
||||
formatOutput();
|
||||
}
|
||||
@@ -203,4 +207,4 @@ async function main() {
|
||||
}
|
||||
}
|
||||
|
||||
main();
|
||||
main();
|
||||
@@ -1050,12 +1050,14 @@ function initSherpaOnnxOfflineMoonshineModelConfig(config, Module) {
|
||||
Module.lengthBytesUTF8(config.uncachedDecoder || '') + 1;
|
||||
const cachedDecoderLen =
|
||||
Module.lengthBytesUTF8(config.cachedDecoder || '') + 1;
|
||||
const mergedDecoderLen =
|
||||
Module.lengthBytesUTF8(config.mergedDecoder || '') + 1;
|
||||
|
||||
const n =
|
||||
preprocessorLen + encoderLen + uncachedDecoderLen + cachedDecoderLen;
|
||||
const n = preprocessorLen + encoderLen + uncachedDecoderLen +
|
||||
cachedDecoderLen + mergedDecoderLen;
|
||||
const buffer = Module._malloc(n);
|
||||
|
||||
const len = 4 * 4; // 4 pointers
|
||||
const len = 5 * 4; // 5 pointers
|
||||
const ptr = Module._malloc(len);
|
||||
|
||||
let offset = 0;
|
||||
@@ -1074,6 +1076,10 @@ function initSherpaOnnxOfflineMoonshineModelConfig(config, Module) {
|
||||
config.cachedDecoder || '', buffer + offset, cachedDecoderLen);
|
||||
offset += cachedDecoderLen;
|
||||
|
||||
Module.stringToUTF8(
|
||||
config.mergedDecoder || '', buffer + offset, mergedDecoderLen);
|
||||
offset += mergedDecoderLen;
|
||||
|
||||
offset = 0;
|
||||
Module.setValue(ptr, buffer + offset, 'i8*');
|
||||
offset += preprocessorLen;
|
||||
@@ -1087,6 +1093,9 @@ function initSherpaOnnxOfflineMoonshineModelConfig(config, Module) {
|
||||
Module.setValue(ptr + 12, buffer + offset, 'i8*');
|
||||
offset += cachedDecoderLen;
|
||||
|
||||
Module.setValue(ptr + 16, buffer + offset, 'i8*');
|
||||
offset += mergedDecoderLen;
|
||||
|
||||
return {
|
||||
buffer: buffer,
|
||||
ptr: ptr,
|
||||
@@ -1288,6 +1297,7 @@ function initSherpaOnnxOfflineModelConfig(config, Module) {
|
||||
encoder: '',
|
||||
uncachedDecoder: '',
|
||||
cachedDecoder: '',
|
||||
mergedDecoder: '',
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@ static_assert(sizeof(SherpaOnnxOfflineDolphinModelConfig) == 4, "");
|
||||
static_assert(sizeof(SherpaOnnxOfflineNemoEncDecCtcModelConfig) == 4, "");
|
||||
static_assert(sizeof(SherpaOnnxOfflineWhisperModelConfig) == 7 * 4, "");
|
||||
static_assert(sizeof(SherpaOnnxOfflineFireRedAsrModelConfig) == 2 * 4, "");
|
||||
static_assert(sizeof(SherpaOnnxOfflineMoonshineModelConfig) == 4 * 4, "");
|
||||
static_assert(sizeof(SherpaOnnxOfflineMoonshineModelConfig) == 5 * 4, "");
|
||||
static_assert(sizeof(SherpaOnnxOfflineTdnnModelConfig) == 4, "");
|
||||
static_assert(sizeof(SherpaOnnxOfflineSenseVoiceModelConfig) == 3 * 4, "");
|
||||
static_assert(sizeof(SherpaOnnxOfflineCanaryModelConfig) == 5 * 4, "");
|
||||
@@ -130,6 +130,7 @@ void PrintOfflineRecognizerConfig(SherpaOnnxOfflineRecognizerConfig *config) {
|
||||
fprintf(stdout, "encoder: %s\n", moonshine->encoder);
|
||||
fprintf(stdout, "uncached_decoder: %s\n", moonshine->uncached_decoder);
|
||||
fprintf(stdout, "cached_decoder: %s\n", moonshine->cached_decoder);
|
||||
fprintf(stdout, "merged_decoder: %s\n", moonshine->merged_decoder);
|
||||
|
||||
fprintf(stdout, "----------offline FireRedAsr model config----------\n");
|
||||
fprintf(stdout, "encoder: %s\n", fire_red_asr->encoder);
|
||||
|
||||
Reference in new issue
Block a user