Add async TTS examples, LinearResampler API, and use node-cpal for microphone (#3623)

This commit is contained in:
Fangjun Kuang authored and GitHub committed 2026-05-19 10:07:31 +08:00
1 parent 58bb4b8001
commit 6acf367a39
57 files changed
+1982 -811

No files matched your search

+18 -5
View File
@@ -130,6 +130,7 @@ tar xf kitten-nano-en-v0_1-fp16.tar.bz2
rm kitten-nano-en-v0_1-fp16.tar.bz2
node ./test_tts_non_streaming_kitten_en.js
node ./test_tts_non_streaming_kitten_en_sync.js
rm -rf kitten-nano-en-v0_1-fp16
@@ -253,9 +254,9 @@ if [[ $arch != "ia32" && $platform != "win32" ]]; then
node ./test_asr_non_streaming_nemo_ctc.js
rm -rf sherpa-onnx-nemo-fast-conformer-ctc-be-de-en-es-fr-hr-it-pl-ru-uk-20k
curl -SL -O https://github.com/k2-fsa/sherpa-onnx/releases/download/asr-models/sherpa-onnx-sense-voice-zh-en-ja-ko-yue-2024-07-17.tar.bz2
tar xvf sherpa-onnx-sense-voice-zh-en-ja-ko-yue-2024-07-17.tar.bz2
rm sherpa-onnx-sense-voice-zh-en-ja-ko-yue-2024-07-17.tar.bz2
curl -SL -O https://github.com/k2-fsa/sherpa-onnx/releases/download/asr-models/sherpa-onnx-sense-voice-zh-en-ja-ko-yue-int8-2024-07-17.tar.bz2
tar xvf sherpa-onnx-sense-voice-zh-en-ja-ko-yue-int8-2024-07-17.tar.bz2
rm sherpa-onnx-sense-voice-zh-en-ja-ko-yue-int8-2024-07-17.tar.bz2
node ./test_asr_non_streaming_sense_voice.js
@@ -268,8 +269,12 @@ if [[ $arch != "ia32" && $platform != "win32" ]]; then
node ./test_asr_non_streaming_sense_voice_with_hr.js
rm -rf sherpa-onnx-sense-voice-zh-en-ja-ko-yue-2024-07-17
rm -rf dict replace.fst test-hr.wav lexicon.txt
curl -SL -O https://github.com/k2-fsa/sherpa-onnx/releases/download/asr-models/silero_vad.onnx
node ./test_vad_asr_non_streaming_sense_voice_microphone.js
rm -rf sherpa-onnx-sense-voice-zh-en-ja-ko-yue-int8-2024-07-17
rm -rf dict replace.fst test-hr.wav lexicon.txt silero_vad.onnx
curl -SL -O https://github.com/k2-fsa/sherpa-onnx/releases/download/asr-models/sherpa-onnx-paraformer-zh-2023-09-14.tar.bz2
tar xvf sherpa-onnx-paraformer-zh-2023-09-14.tar.bz2
@@ -294,6 +299,7 @@ tar xf kokoro-multi-lang-v1_0.tar.bz2
rm kokoro-multi-lang-v1_0.tar.bz2
node ./test_tts_non_streaming_kokoro_zh_en.js
node ./test_tts_non_streaming_kokoro_zh_en_async.js
ls -lh *.wav
rm -rf kokoro-multi-lang-v1_0
@@ -302,6 +308,7 @@ tar xf kokoro-en-v0_19.tar.bz2
rm kokoro-en-v0_19.tar.bz2
node ./test_tts_non_streaming_kokoro_en.js
node ./test_tts_non_streaming_kokoro_en_async.js
ls -lh *.wav
rm -rf kokoro-en-v0_19
@@ -311,6 +318,7 @@ rm matcha-icefall-en_US-ljspeech.tar.bz2
curl -SL -O https://github.com/k2-fsa/sherpa-onnx/releases/download/vocoder-models/vocos-22khz-univ.onnx
node ./test_tts_non_streaming_matcha_icefall_en.js
node ./test_tts_non_streaming_matcha_icefall_en_async.js
rm vocos-22khz-univ.onnx
rm -rf matcha-icefall-en_US-ljspeech
@@ -320,6 +328,7 @@ rm matcha-icefall-zh-baker.tar.bz2
curl -SL -O https://github.com/k2-fsa/sherpa-onnx/releases/download/vocoder-models/vocos-22khz-univ.onnx
node ./test_tts_non_streaming_matcha_icefall_zh.js
node ./test_tts_non_streaming_matcha_icefall_zh_async.js
rm vocos-22khz-univ.onnx
rm -rf matcha-icefall-zh-baker
ls -lh *.wav
@@ -329,6 +338,7 @@ tar xf vits-piper-en_GB-cori-medium.tar.bz2
rm vits-piper-en_GB-cori-medium.tar.bz2
node ./test_tts_non_streaming_vits_piper_en.js
node ./test_tts_non_streaming_vits_piper_en_async.js
rm -rf vits-piper-en_GB-cori-medium
curl -SL -O https://github.com/k2-fsa/sherpa-onnx/releases/download/tts-models/vits-coqui-de-css10.tar.bz2
@@ -336,6 +346,7 @@ tar xvf vits-coqui-de-css10.tar.bz2
rm vits-coqui-de-css10.tar.bz2
node ./test_tts_non_streaming_vits_coqui_de.js
node ./test_tts_non_streaming_vits_coqui_de_async.js
rm -rf vits-coqui-de-css10
curl -SL -O https://github.com/k2-fsa/sherpa-onnx/releases/download/tts-models/sherpa-onnx-vits-zh-ll.tar.bz2
@@ -343,6 +354,7 @@ tar xvf sherpa-onnx-vits-zh-ll.tar.bz2
rm sherpa-onnx-vits-zh-ll.tar.bz2
node ./test_tts_non_streaming_vits_zh_ll.js
node ./test_tts_non_streaming_vits_zh_ll_async.js
rm -rf sherpa-onnx-vits-zh-ll
curl -SL -O https://github.com/k2-fsa/sherpa-onnx/releases/download/tts-models/vits-icefall-zh-aishell3.tar.bz2
@@ -350,6 +362,7 @@ tar xvf vits-icefall-zh-aishell3.tar.bz2
rm vits-icefall-zh-aishell3.tar.bz2
node ./test_tts_non_streaming_vits_zh_aishell3.js
node ./test_tts_non_streaming_vits_zh_aishell3_async.js
rm -rf vits-icefall-zh-aishell3
echo "----------keyword spotting----------"
@@ -58,6 +58,7 @@ add_library(sherpa_onnx SHARED
version.cc
wave-reader.cc
wave-writer.cc
resampler.cc
)
add_library(sherpa_onnx_c_api SHARED IMPORTED)
@@ -0,0 +1,218 @@
// scripts/node-addon-api/src/resampler.cc
//
// Copyright (c) 2026 Xiaomi Corporation
#include <sstream>
#include "macros.h" // NOLINT
#include "napi.h" // NOLINT
#include "sherpa-onnx/c-api/c-api.h"
// createLinearResampler(inputSampleRate, outputSampleRate)
// Returns an External handle to a SherpaOnnxLinearResampler.
static Napi::External<SherpaOnnxLinearResampler> CreateLinearResamplerWrapper(
const Napi::CallbackInfo &info) {
Napi::Env env = info.Env();
if (info.Length() != 2) {
std::ostringstream os;
os << "Expect 2 arguments (inputSampleRate, outputSampleRate). Given: "
<< info.Length();
Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException();
return {};
}
if (!info[0].IsNumber()) {
Napi::TypeError::New(env, "Argument 0 should be a number (inputSampleRate)")
.ThrowAsJavaScriptException();
return {};
}
if (!info[1].IsNumber()) {
Napi::TypeError::New(env,
"Argument 1 should be a number (outputSampleRate)")
.ThrowAsJavaScriptException();
return {};
}
int32_t input_sample_rate = info[0].As<Napi::Number>().Int32Value();
int32_t output_sample_rate = info[1].As<Napi::Number>().Int32Value();
if (input_sample_rate <= 0 || output_sample_rate <= 0) {
Napi::TypeError::New(env,
"inputSampleRate and outputSampleRate must be > 0")
.ThrowAsJavaScriptException();
return {};
}
const SherpaOnnxLinearResampler *p = SherpaOnnxCreateLinearResampler(
input_sample_rate, output_sample_rate, 0, 0);
return Napi::External<SherpaOnnxLinearResampler>::New(
env, const_cast<SherpaOnnxLinearResampler *>(p),
[](Napi::Env env, SherpaOnnxLinearResampler *ptr) {
SherpaOnnxDestroyLinearResampler(ptr);
});
}
// resampleLinear(resamplerHandle, samples, flush)
// Returns a Float32Array of resampled samples.
// flush should be 1 for the final chunk; 0 otherwise.
static Napi::Float32Array ResampleLinearWrapper(
const Napi::CallbackInfo &info) {
Napi::Env env = info.Env();
if (info.Length() != 3) {
std::ostringstream os;
os << "Expect 3 arguments (resampler, samples, flush). Given: "
<< info.Length();
Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException();
return {};
}
if (!info[0].IsExternal()) {
Napi::TypeError::New(env, "Argument 0 should be a resampler handle.")
.ThrowAsJavaScriptException();
return {};
}
if (!info[1].IsTypedArray() ||
info[1].As<Napi::TypedArray>().TypedArrayType() != napi_float32_array) {
Napi::TypeError::New(env, "Argument 1 should be a Float32Array.")
.ThrowAsJavaScriptException();
return {};
}
if (!info[2].IsNumber()) {
Napi::TypeError::New(env, "Argument 2 should be a number (flush: 0 or 1).")
.ThrowAsJavaScriptException();
return {};
}
SherpaOnnxLinearResampler *p =
info[0].As<Napi::External<SherpaOnnxLinearResampler>>().Data();
Napi::Float32Array samples = info[1].As<Napi::Float32Array>();
int32_t input_dim = samples.ElementLength();
int32_t flush = info[2].As<Napi::Number>().Int32Value();
const SherpaOnnxResampleOut *out =
SherpaOnnxLinearResamplerResample(p, samples.Data(), input_dim, flush);
Napi::Float32Array result = Napi::Float32Array::New(env, out->n);
std::copy(out->samples, out->samples + out->n, result.Data());
SherpaOnnxLinearResamplerResampleFree(out);
return result;
}
// linearResamplerReset(resamplerHandle)
static void LinearResamplerResetWrapper(const Napi::CallbackInfo &info) {
Napi::Env env = info.Env();
if (info.Length() != 1) {
std::ostringstream os;
os << "Expect 1 argument (resampler). Given: " << info.Length();
Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException();
return;
}
if (!info[0].IsExternal()) {
Napi::TypeError::New(env, "Argument 0 should be a resampler handle.")
.ThrowAsJavaScriptException();
return;
}
SherpaOnnxLinearResampler *p =
info[0].As<Napi::External<SherpaOnnxLinearResampler>>().Data();
SherpaOnnxLinearResamplerReset(p);
}
// linearResamplerGetInputSampleRate(resamplerHandle)
static Napi::Number LinearResamplerGetInputSampleRateWrapper(
const Napi::CallbackInfo &info) {
Napi::Env env = info.Env();
if (info.Length() != 1) {
std::ostringstream os;
os << "Expect 1 argument (resampler). Given: " << info.Length();
Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException();
return {};
}
if (!info[0].IsExternal()) {
Napi::TypeError::New(env, "Argument 0 should be a resampler handle.")
.ThrowAsJavaScriptException();
return {};
}
SherpaOnnxLinearResampler *p =
info[0].As<Napi::External<SherpaOnnxLinearResampler>>().Data();
return Napi::Number::New(
env, SherpaOnnxLinearResamplerResampleGetInputSampleRate(p));
}
// linearResamplerGetOutputSampleRate(resamplerHandle)
static Napi::Number LinearResamplerGetOutputSampleRateWrapper(
const Napi::CallbackInfo &info) {
Napi::Env env = info.Env();
if (info.Length() != 1) {
std::ostringstream os;
os << "Expect 1 argument (resampler). Given: " << info.Length();
Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException();
return {};
}
if (!info[0].IsExternal()) {
Napi::TypeError::New(env, "Argument 0 should be a resampler handle.")
.ThrowAsJavaScriptException();
return {};
}
SherpaOnnxLinearResampler *p =
info[0].As<Napi::External<SherpaOnnxLinearResampler>>().Data();
return Napi::Number::New(
env, SherpaOnnxLinearResamplerResampleGetOutputSampleRate(p));
}
void InitResampler(Napi::Env env, Napi::Object exports) {
exports.Set(Napi::String::New(env, "createLinearResampler"),
Napi::Function::New(env, CreateLinearResamplerWrapper));
exports.Set(Napi::String::New(env, "resampleLinear"),
Napi::Function::New(env, ResampleLinearWrapper));
exports.Set(Napi::String::New(env, "linearResamplerReset"),
Napi::Function::New(env, LinearResamplerResetWrapper));
exports.Set(Napi::String::New(env, "linearResamplerGetInputSampleRate"),
Napi::Function::New(env, LinearResamplerGetInputSampleRateWrapper));
exports.Set(
Napi::String::New(env, "linearResamplerGetOutputSampleRate"),
Napi::Function::New(env, LinearResamplerGetOutputSampleRateWrapper));
}
@@ -35,6 +35,8 @@ void InitOnlineSpeechDenoiser(Napi::Env env, Napi::Object exports);
void InitVersion(Napi::Env env, Napi::Object exports);
void InitResampler(Napi::Env env, Napi::Object exports);
#if __OHOS__
void InitUtils(Napi::Env env, Napi::Object exports);
#endif
@@ -56,6 +58,7 @@ Napi::Object Init(Napi::Env env, Napi::Object exports) {
InitNonStreamingSpeechDenoiser(env, exports);
InitOnlineSpeechDenoiser(env, exports);
InitVersion(env, exports);
InitResampler(env, exports);
#if __OHOS__
InitUtils(env, exports);
+145 -44
View File
@@ -174,18 +174,27 @@ The following tables list the examples in this folder.
|[./test_tts_non_streaming_zipvoice_zh_en.js](./test_tts_non_streaming_zipvoice_zh_en.js)| Zero-shot text-to-speech with a ZipVoice Chinese/English Model|
|[./test_tts_non_streaming_zipvoice_zh_en_async.js](./test_tts_non_streaming_zipvoice_zh_en_async.js)| Zero-shot text-to-speech with a ZipVoice Chinese/English Model using async JS API|
|[./test_tts_non_streaming_zipvoice_zh_en_play_async.js](./test_tts_non_streaming_zipvoice_zh_en_play_async.js)| Zero-shot text-to-speech with a ZipVoice Chinese/English Model using async JS API and live audio playback|
|[./test_tts_non_streaming_kitten_en.js](./test_tts_non_streaming_kitten_en.js)| Text-to-speech with a KittenTTS English Model|
|[./test_tts_non_streaming_kokoro_en.js](./test_tts_non_streaming_kokoro_en.js)| Text-to-speech with a Kokoro English Model|
|[./test_tts_non_streaming_kokoro_zh_en.js](./test_tts_non_streaming_kokoro_zh_en.js)| Text-to-speech with a Kokoro Model supporting Chinese and English|
|[./test_tts_non_streaming_matcha_icefall_en.js](./test_tts_non_streaming_matcha_icefall_en.js)| Text-to-speech with a [MatchaTTS English Model](https://k2-fsa.github.io/sherpa/onnx/tts/pretrained_models/matcha.html#matcha-icefall-en-us-ljspeech-american-english-1-female-speaker)|
|[./test_tts_non_streaming_matcha_icefall_zh.js](./test_tts_non_streaming_matcha_icefall_zh.js)| Text-to-speech with a [MatchaTTS Chinese Model](https://k2-fsa.github.io/sherpa/onnx/tts/pretrained_models/matcha.html#matcha-icefall-zh-baker-chinese-1-female-speaker)|
|[./test_tts_non_streaming_supertonic_en.js](./test_tts_non_streaming_supertonic_en.js)| Text-to-speech with a Supertonic English Model|
|[./test_tts_non_streaming_supertonic_en_async.js](./test_tts_non_streaming_supertonic_en_async.js)| Text-to-speech with a Supertonic English Model using async JS API|
|[./test_tts_non_streaming_supertonic_en_play_async.js](./test_tts_non_streaming_supertonic_en_play_async.js)| Text-to-speech with a Supertonic English Model using async JS API and live audio playback|
|[./test_tts_non_streaming_vits_piper_en.js](./test_tts_non_streaming_vits_piper_en.js)| Text-to-speech with a [piper](https://github.com/rhasspy/piper) English model|
|[./test_tts_non_streaming_vits_coqui_de.js](./test_tts_non_streaming_vits_coqui_de.js)| Text-to-speech with a [coqui](https://github.com/coqui-ai/TTS) German model|
|[./test_tts_non_streaming_vits_zh_ll.js](./test_tts_non_streaming_vits_zh_ll.js)| Text-to-speech with a Chinese model using [cppjieba](https://github.com/yanyiwu/cppjieba)|
|[./test_tts_non_streaming_vits_zh_aishell3.js](./test_tts_non_streaming_vits_zh_aishell3.js)| Text-to-speech with a Chinese TTS model|
|[./test_tts_non_streaming_kitten_en.js](./test_tts_non_streaming_kitten_en.js)| Text-to-speech with a KittenTTS English Model (async API)|
|[./test_tts_non_streaming_kitten_en_sync.js](./test_tts_non_streaming_kitten_en_sync.js)| Text-to-speech with a KittenTTS English Model (sync API)|
|[./test_tts_non_streaming_kokoro_en.js](./test_tts_non_streaming_kokoro_en.js)| Text-to-speech with a Kokoro English Model (sync API)|
|[./test_tts_non_streaming_kokoro_en_async.js](./test_tts_non_streaming_kokoro_en_async.js)| Text-to-speech with a Kokoro English Model (async API)|
|[./test_tts_non_streaming_kokoro_zh_en.js](./test_tts_non_streaming_kokoro_zh_en.js)| Text-to-speech with a Kokoro Model supporting Chinese and English (sync API)|
|[./test_tts_non_streaming_kokoro_zh_en_async.js](./test_tts_non_streaming_kokoro_zh_en_async.js)| Text-to-speech with a Kokoro Model supporting Chinese and English (async API)|
|[./test_tts_non_streaming_matcha_icefall_en.js](./test_tts_non_streaming_matcha_icefall_en.js)| Text-to-speech with a [MatchaTTS English Model](https://k2-fsa.github.io/sherpa/onnx/tts/pretrained_models/matcha.html#matcha-icefall-en-us-ljspeech-american-english-1-female-speaker) (sync API)|
|[./test_tts_non_streaming_matcha_icefall_en_async.js](./test_tts_non_streaming_matcha_icefall_en_async.js)| Text-to-speech with a [MatchaTTS English Model](https://k2-fsa.github.io/sherpa/onnx/tts/pretrained_models/matcha.html#matcha-icefall-en-us-ljspeech-american-english-1-female-speaker) (async API)|
|[./test_tts_non_streaming_matcha_icefall_zh.js](./test_tts_non_streaming_matcha_icefall_zh.js)| Text-to-speech with a [MatchaTTS Chinese Model](https://k2-fsa.github.io/sherpa/onnx/tts/pretrained_models/matcha.html#matcha-icefall-zh-baker-chinese-1-female-speaker) (sync API)|
|[./test_tts_non_streaming_matcha_icefall_zh_async.js](./test_tts_non_streaming_matcha_icefall_zh_async.js)| Text-to-speech with a [MatchaTTS Chinese Model](https://k2-fsa.github.io/sherpa/onnx/tts/pretrained_models/matcha.html#matcha-icefall-zh-baker-chinese-1-female-speaker) (async API)|
|[./test_tts_non_streaming_supertonic_en.js](./test_tts_non_streaming_supertonic_en.js)| Text-to-speech with a Supertonic English Model (sync API)|
|[./test_tts_non_streaming_supertonic_en_async.js](./test_tts_non_streaming_supertonic_en_async.js)| Text-to-speech with a Supertonic English Model (async API)|
|[./test_tts_non_streaming_supertonic_en_play_async.js](./test_tts_non_streaming_supertonic_en_play_async.js)| Text-to-speech with a Supertonic English Model (async API + live playback)|
|[./test_tts_non_streaming_vits_piper_en.js](./test_tts_non_streaming_vits_piper_en.js)| Text-to-speech with a [piper](https://github.com/rhasspy/piper) English model (sync API)|
|[./test_tts_non_streaming_vits_piper_en_async.js](./test_tts_non_streaming_vits_piper_en_async.js)| Text-to-speech with a [piper](https://github.com/rhasspy/piper) English model (async API)|
|[./test_tts_non_streaming_vits_coqui_de.js](./test_tts_non_streaming_vits_coqui_de.js)| Text-to-speech with a [coqui](https://github.com/coqui-ai/TTS) German model (sync API)|
|[./test_tts_non_streaming_vits_coqui_de_async.js](./test_tts_non_streaming_vits_coqui_de_async.js)| Text-to-speech with a [coqui](https://github.com/coqui-ai/TTS) German model (async API)|
|[./test_tts_non_streaming_vits_zh_ll.js](./test_tts_non_streaming_vits_zh_ll.js)| Text-to-speech with a Chinese model using [cppjieba](https://github.com/yanyiwu/cppjieba) (sync API)|
|[./test_tts_non_streaming_vits_zh_ll_async.js](./test_tts_non_streaming_vits_zh_ll_async.js)| Text-to-speech with a Chinese model using [cppjieba](https://github.com/yanyiwu/cppjieba) (async API)|
|[./test_tts_non_streaming_vits_zh_aishell3.js](./test_tts_non_streaming_vits_zh_aishell3.js)| Text-to-speech with a Chinese TTS model (sync API)|
|[./test_tts_non_streaming_vits_zh_aishell3_async.js](./test_tts_non_streaming_vits_zh_aishell3_async.js)| Text-to-speech with a Chinese TTS model (async API)|
### Speaker diarization
@@ -218,8 +227,8 @@ node ./test_offline_speech_enhancement_gtcrn.js
wget https://github.com/k2-fsa/sherpa-onnx/releases/download/asr-models/silero_vad.onnx
# To run the test with a microphone, you need to install the package naudiodon2
npm install naudiodon2
# To run the test with a microphone, you need to install the package node-cpal
npm install node-cpal
node ./test_vad_microphone.js
```
@@ -279,8 +288,8 @@ rm sherpa-onnx-streaming-zipformer-bilingual-zh-en-2023-02-20.tar.bz2
node ./test_asr_streaming_transducer.js
# To run the test with a microphone, you need to install the package naudiodon2
npm install naudiodon2
# To run the test with a microphone, you need to install the package node-cpal
npm install node-cpal
node ./test_asr_streaming_transducer_microphone.js
```
@@ -297,8 +306,8 @@ node ./test_asr_streaming_ctc.js
# To decode with HLG.fst
node ./test_asr_streaming_ctc_hlg.js
# To run the test with a microphone, you need to install the package naudiodon2
npm install naudiodon2
# To run the test with a microphone, you need to install the package node-cpal
npm install node-cpal
node ./test_asr_streaming_ctc_microphone.js
node ./test_asr_streaming_ctc_hlg_microphone.js
@@ -313,8 +322,8 @@ rm sherpa-onnx-streaming-paraformer-bilingual-zh-en.tar.bz2
node ./test_asr_streaming_paraformer.js
# To run the test with a microphone, you need to install the package naudiodon2
npm install naudiodon2
# To run the test with a microphone, you need to install the package node-cpal
npm install node-cpal
node ./test_asr_streaming_paraformer_microphone.js
```
@@ -329,7 +338,7 @@ rm sherpa-onnx-zipformer-en-2023-04-01.tar.bz2
node ./test_asr_non_streaming_transducer.js
# To run VAD + non-streaming ASR with transudcer using a microphone
npm install naudiodon2
npm install node-cpal
node ./test_vad_asr_non_streaming_transducer_microphone.js
```
@@ -362,7 +371,7 @@ rm sherpa-onnx-whisper-tiny.en.tar.bz2
node ./test_asr_non_streaming_whisper.js
# To run VAD + non-streaming ASR with Whisper using a microphone
npm install naudiodon2
npm install node-cpal
node ./test_vad_asr_non_streaming_whisper_microphone.js
```
@@ -386,7 +395,7 @@ rm sherpa-onnx-moonshine-tiny-en-int8.tar.bz2
node ./test_asr_non_streaming_moonshine.js
# To run VAD + non-streaming ASR with Moonshine using a microphone
npm install naudiodon2
npm install node-cpal
node ./test_vad_asr_non_streaming_moonshine_microphone.js
```
@@ -447,7 +456,7 @@ rm sherpa-onnx-zipformer-ctc-zh-int8-2025-07-03.tar.bz2
node ./test_asr_non_streaming_zipformer_ctc.js
# To run VAD + non-streaming ASR with Paraformer using a microphone
npm install naudiodon2
npm install node-cpal
node ./test_vad_asr_non_streaming_zipformer_ctc_microphone.js
```
@@ -471,7 +480,7 @@ rm sherpa-onnx-nemo-fast-conformer-ctc-be-de-en-es-fr-hr-it-pl-ru-uk-20k.tar.bz2
node ./test_asr_non_streaming_nemo_ctc.js
# To run VAD + non-streaming ASR with Paraformer using a microphone
npm install naudiodon2
npm install node-cpal
node ./test_vad_asr_non_streaming_nemo_ctc_microphone.js
```
@@ -575,15 +584,15 @@ rm sherpa-onnx-paraformer-zh-2023-09-14.tar.bz2
node ./test_asr_non_streaming_paraformer.js
# To run VAD + non-streaming ASR with Paraformer using a microphone
npm install naudiodon2
npm install node-cpal
node ./test_vad_asr_non_streaming_paraformer_microphone.js
```
### Non-streaming speech recognition with SenseVoice with homophone replacer
```bash
curl -SL -O https://github.com/k2-fsa/sherpa-onnx/releases/download/asr-models/sherpa-onnx-sense-voice-zh-en-ja-ko-yue-2024-07-17.tar.bz2
tar xvf sherpa-onnx-sense-voice-zh-en-ja-ko-yue-2024-07-17.tar.bz2
rm sherpa-onnx-sense-voice-zh-en-ja-ko-yue-2024-07-17.tar.bz2
curl -SL -O https://github.com/k2-fsa/sherpa-onnx/releases/download/asr-models/sherpa-onnx-sense-voice-zh-en-ja-ko-yue-int8-2024-07-17.tar.bz2
tar xvf sherpa-onnx-sense-voice-zh-en-ja-ko-yue-int8-2024-07-17.tar.bz2
rm sherpa-onnx-sense-voice-zh-en-ja-ko-yue-int8-2024-07-17.tar.bz2
curl -SL -O https://github.com/k2-fsa/sherpa-onnx/releases/download/hr-files/dict.tar.bz2
tar xf dict.tar.bz2
@@ -598,14 +607,14 @@ node ./test_asr_non_streaming_sense_voice_with_hr.js
### Non-streaming speech recognition with SenseVoice
```bash
wget https://github.com/k2-fsa/sherpa-onnx/releases/download/asr-models/sherpa-onnx-sense-voice-zh-en-ja-ko-yue-2024-07-17.tar.bz2
tar xvf sherpa-onnx-sense-voice-zh-en-ja-ko-yue-2024-07-17.tar.bz2
rm sherpa-onnx-sense-voice-zh-en-ja-ko-yue-2024-07-17.tar.bz2
wget https://github.com/k2-fsa/sherpa-onnx/releases/download/asr-models/sherpa-onnx-sense-voice-zh-en-ja-ko-yue-int8-2024-07-17.tar.bz2
tar xvf sherpa-onnx-sense-voice-zh-en-ja-ko-yue-int8-2024-07-17.tar.bz2
rm sherpa-onnx-sense-voice-zh-en-ja-ko-yue-int8-2024-07-17.tar.bz2
node ./test_asr_non_streaming_sense_voice.js
# To run VAD + non-streaming ASR with Paraformer using a microphone
npm install naudiodon2
npm install node-cpal
node ./test_vad_asr_non_streaming_sense_voice_microphone.js
```
@@ -683,7 +692,7 @@ curl -SL -O https://github.com/k2-fsa/sherpa-onnx/releases/download/vocoder-mode
node ./test_tts_non_streaming_zipvoice_zh_en.js
```
### Text-to-speech with KittenTTS models (English TTS)
### Text-to-speech with KittenTTS models (English TTS, async API)
```bash
curl -SL -O https://github.com/k2-fsa/sherpa-onnx/releases/download/tts-models/kitten-nano-en-v0_1-fp16.tar.bz2
@@ -693,6 +702,16 @@ rm kitten-nano-en-v0_1-fp16.tar.bz2
node ./test_tts_non_streaming_kitten_en.js
```
### Text-to-speech with KittenTTS models (English TTS, sync API)
```bash
curl -SL -O https://github.com/k2-fsa/sherpa-onnx/releases/download/tts-models/kitten-nano-en-v0_1-fp16.tar.bz2
tar xf kitten-nano-en-v0_1-fp16.tar.bz2
rm kitten-nano-en-v0_1-fp16.tar.bz2
node ./test_tts_non_streaming_kitten_en_sync.js
```
### Text-to-speech with Supertonic TTS models (English TTS, sync API)
```bash
@@ -725,7 +744,7 @@ npm install speaker
node ./test_tts_non_streaming_supertonic_en_play_async.js
```
### Text-to-speech with Kokoro TTS models (Chinese + English TTS)
### Text-to-speech with Kokoro TTS models (Chinese + English TTS, sync API)
```bash
curl -SL -O https://github.com/k2-fsa/sherpa-onnx/releases/download/tts-models/kokoro-multi-lang-v1_0.tar.bz2
@@ -735,7 +754,17 @@ rm kokoro-multi-lang-v1_0.tar.bz2
node ./test_tts_non_streaming_kokoro_zh_en.js
```
### Text-to-speech with Kokoro TTS models (English TTS)
### Text-to-speech with Kokoro TTS models (Chinese + English TTS, async API)
```bash
curl -SL -O https://github.com/k2-fsa/sherpa-onnx/releases/download/tts-models/kokoro-multi-lang-v1_0.tar.bz2
tar xf kokoro-multi-lang-v1_0.tar.bz2
rm kokoro-multi-lang-v1_0.tar.bz2
node ./test_tts_non_streaming_kokoro_zh_en_async.js
```
### Text-to-speech with Kokoro TTS models (English TTS, sync API)
```bash
curl -SL -O https://github.com/k2-fsa/sherpa-onnx/releases/download/tts-models/kokoro-en-v0_19.tar.bz2
@@ -745,7 +774,17 @@ rm kokoro-en-v0_19.tar.bz2
node ./test_tts_non_streaming_kokoro_en.js
```
### Text-to-speech with MatchaTTS models (English TTS)
### Text-to-speech with Kokoro TTS models (English TTS, async API)
```bash
curl -SL -O https://github.com/k2-fsa/sherpa-onnx/releases/download/tts-models/kokoro-en-v0_19.tar.bz2
tar xf kokoro-en-v0_19.tar.bz2
rm kokoro-en-v0_19.tar.bz2
node ./test_tts_non_streaming_kokoro_en_async.js
```
### Text-to-speech with MatchaTTS models (English TTS, sync API)
```bash
wget https://github.com/k2-fsa/sherpa-onnx/releases/download/tts-models/matcha-icefall-en_US-ljspeech.tar.bz2
tar xf matcha-icefall-en_US-ljspeech.tar.bz2
@@ -756,7 +795,18 @@ wget https://github.com/k2-fsa/sherpa-onnx/releases/download/vocoder-models/voco
node ./test_tts_non_streaming_matcha_icefall_en.js
```
### Text-to-speech with MatchaTTS models (Chinese TTS)
### Text-to-speech with MatchaTTS models (English TTS, async API)
```bash
wget https://github.com/k2-fsa/sherpa-onnx/releases/download/tts-models/matcha-icefall-en_US-ljspeech.tar.bz2
tar xf matcha-icefall-en_US-ljspeech.tar.bz2
rm matcha-icefall-en_US-ljspeech.tar.bz2
wget https://github.com/k2-fsa/sherpa-onnx/releases/download/vocoder-models/vocos-22khz-univ.onnx
node ./test_tts_non_streaming_matcha_icefall_en_async.js
```
### Text-to-speech with MatchaTTS models (Chinese TTS, sync API)
```bash
wget https://github.com/k2-fsa/sherpa-onnx/releases/download/tts-models/matcha-icefall-zh-baker.tar.bz2
tar xvf matcha-icefall-zh-baker.tar.bz2
@@ -767,7 +817,18 @@ wget https://github.com/k2-fsa/sherpa-onnx/releases/download/vocoder-models/voco
node ./test_tts_non_streaming_matcha_icefall_zh.js
```
### Text-to-speech with piper VITS models (TTS)
### Text-to-speech with MatchaTTS models (Chinese TTS, async API)
```bash
wget https://github.com/k2-fsa/sherpa-onnx/releases/download/tts-models/matcha-icefall-zh-baker.tar.bz2
tar xvf matcha-icefall-zh-baker.tar.bz2
rm matcha-icefall-zh-baker.tar.bz2
wget https://github.com/k2-fsa/sherpa-onnx/releases/download/vocoder-models/vocos-22khz-univ.onnx
node ./test_tts_non_streaming_matcha_icefall_zh_async.js
```
### Text-to-speech with piper VITS models (sync API)
```bash
wget https://github.com/k2-fsa/sherpa-onnx/releases/download/tts-models/vits-piper-en_GB-cori-medium.tar.bz2
@@ -777,7 +838,17 @@ rm vits-piper-en_GB-cori-medium.tar.bz2
node ./test_tts_non_streaming_vits_piper_en.js
```
### Text-to-speech with piper Coqui-ai/TTS models (TTS)
### Text-to-speech with piper VITS models (async API)
```bash
wget https://github.com/k2-fsa/sherpa-onnx/releases/download/tts-models/vits-piper-en_GB-cori-medium.tar.bz2
tar xvf vits-piper-en_GB-cori-medium.tar.bz2
rm vits-piper-en_GB-cori-medium.tar.bz2
node ./test_tts_non_streaming_vits_piper_en_async.js
```
### Text-to-speech with Coqui-ai/TTS VITS models (German, sync API)
```bash
wget https://github.com/k2-fsa/sherpa-onnx/releases/download/tts-models/vits-coqui-de-css10.tar.bz2
@@ -787,7 +858,17 @@ rm vits-coqui-de-css10.tar.bz2
node ./test_tts_non_streaming_vits_coqui_de.js
```
### Text-to-speech with vits Chinese models (1/2)
### Text-to-speech with Coqui-ai/TTS VITS models (German, async API)
```bash
wget https://github.com/k2-fsa/sherpa-onnx/releases/download/tts-models/vits-coqui-de-css10.tar.bz2
tar xvf vits-coqui-de-css10.tar.bz2
rm vits-coqui-de-css10.tar.bz2
node ./test_tts_non_streaming_vits_coqui_de_async.js
```
### Text-to-speech with vits Chinese zh-ll models (sync API)
```bash
wget https://github.com/k2-fsa/sherpa-onnx/releases/download/tts-models/sherpa-onnx-vits-zh-ll.tar.bz2
@@ -797,7 +878,17 @@ rm sherpa-onnx-vits-zh-ll.tar.bz2
node ./test_tts_non_streaming_vits_zh_ll.js
```
### Text-to-speech with vits Chinese models (2/2)
### Text-to-speech with vits Chinese zh-ll models (async API)
```bash
wget https://github.com/k2-fsa/sherpa-onnx/releases/download/tts-models/sherpa-onnx-vits-zh-ll.tar.bz2
tar xvf sherpa-onnx-vits-zh-ll.tar.bz2
rm sherpa-onnx-vits-zh-ll.tar.bz2
node ./test_tts_non_streaming_vits_zh_ll_async.js
```
### Text-to-speech with vits Chinese AiShell3 models (sync API)
```bash
wget https://github.com/k2-fsa/sherpa-onnx/releases/download/tts-models/vits-icefall-zh-aishell3.tar.bz2
@@ -807,6 +898,16 @@ rm vits-icefall-zh-aishell3.tar.bz2
node ./test_tts_non_streaming_vits_zh_aishell3.js
```
### Text-to-speech with vits Chinese AiShell3 models (async API)
```bash
wget https://github.com/k2-fsa/sherpa-onnx/releases/download/tts-models/vits-icefall-zh-aishell3.tar.bz2
tar xvf vits-icefall-zh-aishell3.tar.bz2
rm vits-icefall-zh-aishell3.tar.bz2
node ./test_tts_non_streaming_vits_zh_aishell3_async.js
```
### Spoken language identification with Whisper multi-lingual models
```bash
@@ -821,7 +922,7 @@ rm spoken-language-identification-test-wavs.tar.bz2
node ./test_spoken_language_identification.js
# To run VAD + spoken language identification using a microphone
npm install naudiodon2
npm install node-cpal
node ./test_vad_spoken_language_identification_microphone.js
```
@@ -868,6 +969,6 @@ rm sherpa-onnx-kws-zipformer-wenetspeech-3.3M-2024-01-01.tar.bz2
node ./test_keyword_spotter_transducer.js
# To run keyword spotting using a microphone
npm install naudiodon2
npm install node-cpal
node ./test_keyword_spotter_transducer_microphone.js
```
+1
View File
@@ -1,5 +1,6 @@
{
"dependencies": {
"node-cpal": "^0.1.1",
"sherpa-onnx-node": "^1.13.2"
}
}
@@ -9,13 +9,6 @@ const sherpa_onnx = require('sherpa-onnx-node');
// the following code
//
// let encoder = new TextEncoder();
// let tokens = encoder.encode(
// './sherpa-onnx-sense-voice-zh-en-ja-ko-yue-2024-07-17/测试.txt');
// let model = encoder.encode(
// './sherpa-onnx-sense-voice-zh-en-ja-ko-yue-2024-07-17/测试.int8.onnx');
const config = {
'featConfig': {
'sampleRate': 16000,
@@ -24,11 +17,11 @@ const config = {
'modelConfig': {
'senseVoice': {
'model':
'./sherpa-onnx-sense-voice-zh-en-ja-ko-yue-2024-07-17/model.int8.onnx',
'./sherpa-onnx-sense-voice-zh-en-ja-ko-yue-int8-2024-07-17/model.int8.onnx',
// 'model': model,
'useInverseTextNormalization': 1,
},
'tokens': './sherpa-onnx-sense-voice-zh-en-ja-ko-yue-2024-07-17/tokens.txt',
'tokens': './sherpa-onnx-sense-voice-zh-en-ja-ko-yue-int8-2024-07-17/tokens.txt',
// 'tokens': tokens,
'numThreads': 2,
'provider': 'cpu',
@@ -37,7 +30,7 @@ const config = {
};
const waveFilename =
'./sherpa-onnx-sense-voice-zh-en-ja-ko-yue-2024-07-17/test_wavs/zh.wav';
'./sherpa-onnx-sense-voice-zh-en-ja-ko-yue-int8-2024-07-17/test_wavs/zh.wav';
const recognizer = new sherpa_onnx.OfflineRecognizer(config);
console.log('Started');
@@ -10,13 +10,6 @@ const sherpa_onnx = require('sherpa-onnx-node');
// the following code
//
// let encoder = new TextEncoder();
// let tokens = encoder.encode(
// './sherpa-onnx-sense-voice-zh-en-ja-ko-yue-2024-07-17/测试.txt');
// let model = encoder.encode(
// './sherpa-onnx-sense-voice-zh-en-ja-ko-yue-2024-07-17/测试.int8.onnx');
const config = {
'featConfig': {
'sampleRate': 16000,
@@ -25,11 +18,11 @@ const config = {
'modelConfig': {
'senseVoice': {
'model':
'./sherpa-onnx-sense-voice-zh-en-ja-ko-yue-2024-07-17/model.int8.onnx',
'./sherpa-onnx-sense-voice-zh-en-ja-ko-yue-int8-2024-07-17/model.int8.onnx',
// 'model': model,
'useInverseTextNormalization': 1,
},
'tokens': './sherpa-onnx-sense-voice-zh-en-ja-ko-yue-2024-07-17/tokens.txt',
'tokens': './sherpa-onnx-sense-voice-zh-en-ja-ko-yue-int8-2024-07-17/tokens.txt',
// 'tokens': tokens,
'numThreads': 2,
'provider': 'cpu',
@@ -1,7 +1,7 @@
// Copyright (c) 2023-2024 Xiaomi Corporation (authors: Fangjun Kuang)
//
const portAudio = require('naudiodon2');
// console.log(portAudio.getDevices());
const cpal = require('node-cpal');
const sherpa_onnx = require('sherpa-onnx-node');
@@ -40,44 +40,45 @@ const stream = recognizer.createStream();
let lastText = '';
let segmentIndex = 0;
const ai = new portAudio.AudioIO({
inOptions: {
channelCount: 1,
closeOnError: true, // Close the stream if an audio error is detected, if
// set false then just log the error
deviceId: -1, // Use -1 or omit the deviceId to select the default device
sampleFormat: portAudio.SampleFormatFloat32,
sampleRate: recognizer.config.featConfig.sampleRate
}
});
const inputDevice = cpal.getDefaultInputDevice();
const deviceConfig = cpal.getDefaultInputConfig(inputDevice.deviceId);
const nativeSampleRate = deviceConfig.sampleRate;
const targetSampleRate = recognizer.config.featConfig.sampleRate;
const resampler = new sherpa_onnx.LinearResampler(nativeSampleRate, targetSampleRate);
const display = new sherpa_onnx.Display(50);
ai.on('data', data => {
const samples = new Float32Array(data.buffer);
const inputStream = cpal.createStream(
inputDevice.deviceId,
true,
{
sampleRate: nativeSampleRate,
channels: 1,
format: 'f32',
},
(data) => {
const resampled = resampler.resample(data);
stream.acceptWaveform(
{sampleRate: targetSampleRate, samples: resampled});
stream.acceptWaveform(
{sampleRate: recognizer.config.featConfig.sampleRate, samples: samples});
while (recognizer.isReady(stream)) {
recognizer.decode(stream);
}
while (recognizer.isReady(stream)) {
recognizer.decode(stream);
}
const isEndpoint = recognizer.isEndpoint(stream);
const text = recognizer.getResult(stream).text.toLowerCase();
const isEndpoint = recognizer.isEndpoint(stream);
const text = recognizer.getResult(stream).text.toLowerCase();
if (text.length > 0 && lastText != text) {
lastText = text;
display.print(segmentIndex, lastText);
}
if (isEndpoint) {
if (text.length > 0) {
lastText = text;
segmentIndex += 1;
}
recognizer.reset(stream);
}
});
if (text.length > 0 && lastText != text) {
lastText = text;
display.print(segmentIndex, lastText);
}
if (isEndpoint) {
if (text.length > 0) {
lastText = text;
segmentIndex += 1;
}
recognizer.reset(stream);
}
});
ai.start();
console.log('Started! Please speak');
@@ -1,7 +1,7 @@
// Copyright (c) 2023-2024 Xiaomi Corporation (authors: Fangjun Kuang)
//
const portAudio = require('naudiodon2');
// console.log(portAudio.getDevices());
const cpal = require('node-cpal');
const sherpa_onnx = require('sherpa-onnx-node');
@@ -39,45 +39,45 @@ const stream = recognizer.createStream();
let lastText = '';
let segmentIndex = 0;
const ai = new portAudio.AudioIO({
inOptions: {
channelCount: 1,
closeOnError: true, // Close the stream if an audio error is detected, if
// set false then just log the error
deviceId: -1, // Use -1 or omit the deviceId to select the default device
sampleFormat: portAudio.SampleFormatFloat32,
sampleRate: recognizer.config.featConfig.sampleRate
}
});
const inputDevice = cpal.getDefaultInputDevice();
const deviceConfig = cpal.getDefaultInputConfig(inputDevice.deviceId);
const nativeSampleRate = deviceConfig.sampleRate;
const targetSampleRate = recognizer.config.featConfig.sampleRate;
const resampler = new sherpa_onnx.LinearResampler(nativeSampleRate, targetSampleRate);
const display = new sherpa_onnx.Display(50);
ai.on('data', data => {
const samples = new Float32Array(data.buffer);
const inputStream = cpal.createStream(
inputDevice.deviceId,
true,
{
sampleRate: nativeSampleRate,
channels: 1,
format: 'f32',
},
(data) => {
const resampled = resampler.resample(data);
stream.acceptWaveform(
{sampleRate: targetSampleRate, samples: resampled});
stream.acceptWaveform(
{sampleRate: recognizer.config.featConfig.sampleRate, samples: samples});
while (recognizer.isReady(stream)) {
recognizer.decode(stream);
}
while (recognizer.isReady(stream)) {
recognizer.decode(stream);
}
const isEndpoint = recognizer.isEndpoint(stream);
const text = recognizer.getResult(stream).text.toLowerCase();
const isEndpoint = recognizer.isEndpoint(stream);
const text = recognizer.getResult(stream).text.toLowerCase();
if (text.length > 0 && lastText != text) {
lastText = text;
display.print(segmentIndex, lastText);
}
if (isEndpoint) {
if (text.length > 0) {
lastText = text;
segmentIndex += 1;
}
recognizer.reset(stream);
}
});
if (text.length > 0 && lastText != text) {
lastText = text;
display.print(segmentIndex, lastText);
}
if (isEndpoint) {
if (text.length > 0) {
lastText = text;
segmentIndex += 1;
}
recognizer.reset(stream);
}
});
ai.start();
console.log('Started! Please speak');
@@ -1,7 +1,7 @@
// Copyright (c) 2023-2024 Xiaomi Corporation (authors: Fangjun Kuang)
//
const portAudio = require('naudiodon2');
// console.log(portAudio.getDevices());
const cpal = require('node-cpal');
const sherpa_onnx = require('sherpa-onnx-node');
@@ -40,59 +40,60 @@ const stream = recognizer.createStream();
let lastText = '';
let segmentIndex = 0;
const ai = new portAudio.AudioIO({
inOptions: {
channelCount: 1,
closeOnError: true, // Close the stream if an audio error is detected, if
// set false then just log the error
deviceId: -1, // Use -1 or omit the deviceId to select the default device
sampleFormat: portAudio.SampleFormatFloat32,
sampleRate: recognizer.config.featConfig.sampleRate
}
});
const inputDevice = cpal.getDefaultInputDevice();
const deviceConfig = cpal.getDefaultInputConfig(inputDevice.deviceId);
const nativeSampleRate = deviceConfig.sampleRate;
const targetSampleRate = recognizer.config.featConfig.sampleRate;
const resampler = new sherpa_onnx.LinearResampler(nativeSampleRate, targetSampleRate);
const display = new sherpa_onnx.Display(50);
ai.on('data', data => {
const samples = new Float32Array(data.buffer);
const inputStream = cpal.createStream(
inputDevice.deviceId,
true,
{
sampleRate: nativeSampleRate,
channels: 1,
format: 'f32',
},
(data) => {
const resampled = resampler.resample(data);
stream.acceptWaveform(
{sampleRate: targetSampleRate, samples: resampled});
stream.acceptWaveform(
{sampleRate: recognizer.config.featConfig.sampleRate, samples: samples});
while (recognizer.isReady(stream)) {
recognizer.decode(stream);
}
while (recognizer.isReady(stream)) {
recognizer.decode(stream);
}
const isEndpoint = recognizer.isEndpoint(stream);
let text = recognizer.getResult(stream).text.toLowerCase();
const isEndpoint = recognizer.isEndpoint(stream);
let text = recognizer.getResult(stream).text.toLowerCase();
if (isEndpoint) {
// for online paraformer models, we have to manually padding on endpoint
// so that the last word can be recognized
const tailPadding =
new Float32Array(targetSampleRate * 0.4);
stream.acceptWaveform({
samples: tailPadding,
sampleRate: targetSampleRate
});
while (recognizer.isReady(stream)) {
recognizer.decode(stream);
}
text = recognizer.getResult(stream).text.toLowerCase();
}
if (isEndpoint) {
// for online paraformer models, we have to manually padding on endpoint
// so that the last word can be recognized
const tailPadding =
new Float32Array(recognizer.config.featConfig.sampleRate * 0.4);
stream.acceptWaveform({
samples: tailPadding,
sampleRate: recognizer.config.featConfig.sampleRate
if (text.length > 0 && lastText != text) {
lastText = text;
display.print(segmentIndex, lastText);
}
if (isEndpoint) {
if (text.length > 0) {
lastText = text;
segmentIndex += 1;
}
recognizer.reset(stream);
}
});
while (recognizer.isReady(stream)) {
recognizer.decode(stream);
}
text = recognizer.getResult(stream).text.toLowerCase();
}
if (text.length > 0 && lastText != text) {
lastText = text;
display.print(segmentIndex, lastText);
}
if (isEndpoint) {
if (text.length > 0) {
lastText = text;
segmentIndex += 1;
}
recognizer.reset(stream);
}
});
ai.start();
console.log('Started! Please speak');
@@ -1,7 +1,7 @@
// Copyright (c) 2023-2024 Xiaomi Corporation (authors: Fangjun Kuang)
//
const portAudio = require('naudiodon2');
// console.log(portAudio.getDevices());
const cpal = require('node-cpal');
const sherpa_onnx = require('sherpa-onnx-node');
@@ -43,44 +43,45 @@ const stream = recognizer.createStream();
let lastText = '';
let segmentIndex = 0;
const ai = new portAudio.AudioIO({
inOptions: {
channelCount: 1,
closeOnError: true, // Close the stream if an audio error is detected, if
// set false then just log the error
deviceId: -1, // Use -1 or omit the deviceId to select the default device
sampleFormat: portAudio.SampleFormatFloat32,
sampleRate: recognizer.config.featConfig.sampleRate
}
});
const inputDevice = cpal.getDefaultInputDevice();
const deviceConfig = cpal.getDefaultInputConfig(inputDevice.deviceId);
const nativeSampleRate = deviceConfig.sampleRate;
const targetSampleRate = recognizer.config.featConfig.sampleRate;
const resampler = new sherpa_onnx.LinearResampler(nativeSampleRate, targetSampleRate);
const display = new sherpa_onnx.Display(50);
ai.on('data', data => {
const samples = new Float32Array(data.buffer);
const inputStream = cpal.createStream(
inputDevice.deviceId,
true,
{
sampleRate: nativeSampleRate,
channels: 1,
format: 'f32',
},
(data) => {
const resampled = resampler.resample(data);
stream.acceptWaveform(
{sampleRate: targetSampleRate, samples: resampled});
stream.acceptWaveform(
{sampleRate: recognizer.config.featConfig.sampleRate, samples: samples});
while (recognizer.isReady(stream)) {
recognizer.decode(stream);
}
while (recognizer.isReady(stream)) {
recognizer.decode(stream);
}
const isEndpoint = recognizer.isEndpoint(stream);
const text = recognizer.getResult(stream).text.toLowerCase();
const isEndpoint = recognizer.isEndpoint(stream);
const text = recognizer.getResult(stream).text.toLowerCase();
if (text.length > 0 && lastText != text) {
lastText = text;
display.print(segmentIndex, lastText);
}
if (isEndpoint) {
if (text.length > 0) {
lastText = text;
segmentIndex += 1;
}
recognizer.reset(stream);
}
});
if (text.length > 0 && lastText != text) {
lastText = text;
display.print(segmentIndex, lastText);
}
if (isEndpoint) {
if (text.length > 0) {
lastText = text;
segmentIndex += 1;
}
recognizer.reset(stream);
}
});
ai.start();
console.log('Started! Please speak');
@@ -1,7 +1,7 @@
// Copyright (c) 2023-2024 Xiaomi Corporation (authors: Fangjun Kuang)
//
const portAudio = require('naudiodon2');
// console.log(portAudio.getDevices());
const cpal = require('node-cpal');
const sherpa_onnx = require('sherpa-onnx-node');
@@ -45,44 +45,45 @@ const stream = recognizer.createStream();
let lastText = '';
let segmentIndex = 0;
const ai = new portAudio.AudioIO({
inOptions: {
channelCount: 1,
closeOnError: true, // Close the stream if an audio error is detected, if
// set false then just log the error
deviceId: -1, // Use -1 or omit the deviceId to select the default device
sampleFormat: portAudio.SampleFormatFloat32,
sampleRate: recognizer.config.featConfig.sampleRate
}
});
const inputDevice = cpal.getDefaultInputDevice();
const deviceConfig = cpal.getDefaultInputConfig(inputDevice.deviceId);
const nativeSampleRate = deviceConfig.sampleRate;
const targetSampleRate = recognizer.config.featConfig.sampleRate;
const resampler = new sherpa_onnx.LinearResampler(nativeSampleRate, targetSampleRate);
const display = new sherpa_onnx.Display(50);
ai.on('data', data => {
const samples = new Float32Array(data.buffer);
const inputStream = cpal.createStream(
inputDevice.deviceId,
true,
{
sampleRate: nativeSampleRate,
channels: 1,
format: 'f32',
},
(data) => {
const resampled = resampler.resample(data);
stream.acceptWaveform(
{sampleRate: targetSampleRate, samples: resampled});
stream.acceptWaveform(
{sampleRate: recognizer.config.featConfig.sampleRate, samples: samples});
while (recognizer.isReady(stream)) {
recognizer.decode(stream);
}
while (recognizer.isReady(stream)) {
recognizer.decode(stream);
}
const isEndpoint = recognizer.isEndpoint(stream);
const text = recognizer.getResult(stream).text.toLowerCase();
const isEndpoint = recognizer.isEndpoint(stream);
const text = recognizer.getResult(stream).text.toLowerCase();
if (text.length > 0 && lastText != text) {
lastText = text;
display.print(segmentIndex, lastText);
}
if (isEndpoint) {
if (text.length > 0) {
lastText = text;
segmentIndex += 1;
}
recognizer.reset(stream);
}
});
if (text.length > 0 && lastText != text) {
lastText = text;
display.print(segmentIndex, lastText);
}
if (isEndpoint) {
if (text.length > 0) {
lastText = text;
segmentIndex += 1;
}
recognizer.reset(stream);
}
});
ai.start();
console.log('Started! Please speak');
@@ -1,7 +1,7 @@
// Copyright (c) 2023-2024 Xiaomi Corporation (authors: Fangjun Kuang)
//
const portAudio = require('naudiodon2');
// console.log(portAudio.getDevices());
const cpal = require('node-cpal');
const sherpa_onnx = require('sherpa-onnx-node');
@@ -39,36 +39,37 @@ const stream = kws.createStream();
let lastText = '';
let segmentIndex = 0;
const ai = new portAudio.AudioIO({
inOptions: {
channelCount: 1,
closeOnError: true, // Close the stream if an audio error is detected, if
// set false then just log the error
deviceId: -1, // Use -1 or omit the deviceId to select the default device
sampleFormat: portAudio.SampleFormatFloat32,
sampleRate: kws.config.featConfig.sampleRate
}
});
const inputDevice = cpal.getDefaultInputDevice();
const deviceConfig = cpal.getDefaultInputConfig(inputDevice.deviceId);
const nativeSampleRate = deviceConfig.sampleRate;
const targetSampleRate = kws.config.featConfig.sampleRate;
const resampler = new sherpa_onnx.LinearResampler(nativeSampleRate, targetSampleRate);
const display = new sherpa_onnx.Display(50);
ai.on('data', data => {
const samples = new Float32Array(data.buffer);
const inputStream = cpal.createStream(
inputDevice.deviceId,
true,
{
sampleRate: nativeSampleRate,
channels: 1,
format: 'f32',
},
(data) => {
const resampled = resampler.resample(data);
stream.acceptWaveform(
{sampleRate: targetSampleRate, samples: resampled});
stream.acceptWaveform(
{sampleRate: kws.config.featConfig.sampleRate, samples: samples});
while (kws.isReady(stream)) {
kws.decode(stream);
}
while (kws.isReady(stream)) {
kws.decode(stream);
}
const keyword = kws.getResult(stream).keyword;
if (keyword != '') {
display.print(segmentIndex, keyword);
segmentIndex += 1;
}
});
const keyword = kws.getResult(stream).keyword;
if (keyword != '') {
display.print(segmentIndex, keyword);
segmentIndex += 1;
}
});
ai.start();
console.log('Started! Please speak.');
console.log(`Only words from ${kws.config.keywordsFile} can be recognized`);
@@ -0,0 +1,52 @@
// Copyright (c) 2026 Xiaomi Corporation
//
// Synchronous text-to-speech with the Kitten model.
//
const sherpa_onnx = require('sherpa-onnx-node');
function createOfflineTts() {
const config = {
model: {
kitten: {
model: './kitten-nano-en-v0_1-fp16/model.fp16.onnx',
voices: './kitten-nano-en-v0_1-fp16/voices.bin',
tokens: './kitten-nano-en-v0_1-fp16/tokens.txt',
dataDir: './kitten-nano-en-v0_1-fp16/espeak-ng-data',
},
debug: true,
numThreads: 1,
provider: 'cpu',
},
maxNumSentences: 1,
};
return new sherpa_onnx.OfflineTts(config);
}
const tts = createOfflineTts();
const text =
'Today as always, men fall into two groups: slaves and free men. Whoever does not have two-thirds of his day for himself, is a slave, whatever he may be: a statesman, a businessman, an official, or a scholar.';
const generationConfig = new sherpa_onnx.GenerationConfig({
sid: 6,
speed: 1.0,
silenceScale: 0.2,
});
let start = Date.now();
const audio = tts.generate({text, generationConfig});
let stop = Date.now();
const elapsed_seconds = (stop - start) / 1000;
const duration = audio.samples.length / audio.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));
const filename = 'test-kitten-en-sync.wav';
sherpa_onnx.writeWave(
filename, {samples: audio.samples, sampleRate: audio.sampleRate});
console.log(`Saved to ${filename}`);
@@ -0,0 +1,70 @@
// Copyright (c) 2026 Xiaomi Corporation
//
// Asynchronous text-to-speech with the Kokoro English model.
//
const sherpa_onnx = require('sherpa-onnx-node');
async function createOfflineTts() {
const config = {
model: {
kokoro: {
model: './kokoro-en-v0_19/model.onnx',
voices: './kokoro-en-v0_19/voices.bin',
tokens: './kokoro-en-v0_19/tokens.txt',
dataDir: './kokoro-en-v0_19/espeak-ng-data',
},
debug: false,
numThreads: 1,
provider: 'cpu',
},
maxNumSentences: 1,
};
return await sherpa_onnx.OfflineTts.createAsync(config);
}
async function main() {
const tts = await createOfflineTts();
const text =
'Today as always, men fall into two groups: slaves and free men. Whoever does not have two-thirds of his day for himself, is a slave, whatever he may be: a statesman, a businessman, an official, or a scholar.';
const generationConfig = new sherpa_onnx.GenerationConfig({
sid: 6,
speed: 1.0,
silenceScale: 0.2,
});
const start = Date.now();
const audio = await tts.generateAsync({
text,
enableExternalBuffer: true,
generationConfig,
onProgress: ({samples, progress}) => {
process.stdout.write(
`Progress: ${(progress * 100).toFixed(1)}%, ` +
`Samples: ${samples.length}\r`);
return 1;
},
});
console.log('');
const stop = Date.now();
const elapsed_seconds = (stop - start) / 1000;
const duration = audio.samples.length / audio.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));
const filename = 'test-kokoro-en-async.wav';
sherpa_onnx.writeWave(
filename, {samples: audio.samples, sampleRate: audio.sampleRate});
console.log(`Saved to ${filename}`);
}
main().catch((err) => {
console.error('Error:', err);
process.exitCode = 1;
});
@@ -0,0 +1,72 @@
// Copyright (c) 2026 Xiaomi Corporation
//
// Asynchronous text-to-speech with the Kokoro Chinese+English model.
//
const sherpa_onnx = require('sherpa-onnx-node');
async function createOfflineTts() {
const config = {
model: {
kokoro: {
model: './kokoro-multi-lang-v1_0/model.onnx',
voices: './kokoro-multi-lang-v1_0/voices.bin',
tokens: './kokoro-multi-lang-v1_0/tokens.txt',
dataDir: './kokoro-multi-lang-v1_0/espeak-ng-data',
lexicon:
'./kokoro-multi-lang-v1_0/lexicon-us-en.txt,./kokoro-multi-lang-v1_0/lexicon-zh.txt',
},
debug: false,
numThreads: 1,
provider: 'cpu',
},
maxNumSentences: 1,
};
return await sherpa_onnx.OfflineTts.createAsync(config);
}
async function main() {
const tts = await createOfflineTts();
const text =
'中英文语音合成测试。This is generated by next generation Kaldi using Kokoro without Misaki. 你觉得中英文说的如何呢?';
const generationConfig = new sherpa_onnx.GenerationConfig({
sid: 48,
speed: 1.0,
silenceScale: 0.2,
});
const start = Date.now();
const audio = await tts.generateAsync({
text,
enableExternalBuffer: true,
generationConfig,
onProgress: ({samples, progress}) => {
process.stdout.write(
`Progress: ${(progress * 100).toFixed(1)}%, ` +
`Samples: ${samples.length}\r`);
return 1;
},
});
console.log('');
const stop = Date.now();
const elapsed_seconds = (stop - start) / 1000;
const duration = audio.samples.length / audio.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));
const filename = 'test-kokoro-zh-en-async.wav';
sherpa_onnx.writeWave(
filename, {samples: audio.samples, sampleRate: audio.sampleRate});
console.log(`Saved to ${filename}`);
}
main().catch((err) => {
console.error('Error:', err);
process.exitCode = 1;
});
@@ -0,0 +1,70 @@
// Copyright (c) 2026 Xiaomi Corporation
//
// Asynchronous text-to-speech with the Matcha English model.
//
const sherpa_onnx = require('sherpa-onnx-node');
async function createOfflineTts() {
const config = {
model: {
matcha: {
acousticModel: './matcha-icefall-en_US-ljspeech/model-steps-3.onnx',
vocoder: './vocos-22khz-univ.onnx',
tokens: './matcha-icefall-en_US-ljspeech/tokens.txt',
dataDir: './matcha-icefall-en_US-ljspeech/espeak-ng-data',
},
debug: false,
numThreads: 1,
provider: 'cpu',
},
maxNumSentences: 1,
};
return await sherpa_onnx.OfflineTts.createAsync(config);
}
async function main() {
const tts = await createOfflineTts();
const text =
'Today as always, men fall into two groups: slaves and free men. Whoever does not have two-thirds of his day for himself, is a slave, whatever he may be: a statesman, a businessman, an official, or a scholar.';
const generationConfig = new sherpa_onnx.GenerationConfig({
sid: 0,
speed: 1.0,
silenceScale: 0.2,
});
const start = Date.now();
const audio = await tts.generateAsync({
text,
enableExternalBuffer: true,
generationConfig,
onProgress: ({samples, progress}) => {
process.stdout.write(
`Progress: ${(progress * 100).toFixed(1)}%, ` +
`Samples: ${samples.length}\r`);
return 1;
},
});
console.log('');
const stop = Date.now();
const elapsed_seconds = (stop - start) / 1000;
const duration = audio.samples.length / audio.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));
const filename = 'test-matcha-en-async.wav';
sherpa_onnx.writeWave(
filename, {samples: audio.samples, sampleRate: audio.sampleRate});
console.log(`Saved to ${filename}`);
}
main().catch((err) => {
console.error('Error:', err);
process.exitCode = 1;
});
@@ -0,0 +1,72 @@
// Copyright (c) 2026 Xiaomi Corporation
//
// Asynchronous text-to-speech with the Matcha Chinese model.
//
const sherpa_onnx = require('sherpa-onnx-node');
async function createOfflineTts() {
const config = {
model: {
matcha: {
acousticModel: './matcha-icefall-zh-baker/model-steps-3.onnx',
vocoder: './vocos-22khz-univ.onnx',
lexicon: './matcha-icefall-zh-baker/lexicon.txt',
tokens: './matcha-icefall-zh-baker/tokens.txt',
},
debug: false,
numThreads: 1,
provider: 'cpu',
},
maxNumSentences: 1,
ruleFsts:
'./matcha-icefall-zh-baker/phone.fst,./matcha-icefall-zh-baker/date.fst,./matcha-icefall-zh-baker/number.fst',
};
return await sherpa_onnx.OfflineTts.createAsync(config);
}
async function main() {
const tts = await createOfflineTts();
const text =
'当夜幕降临,星光点点,伴随着微风拂面,我在静谧中感受着时光的流转,思念如涟漪荡漾,梦境如画卷展开,我与自然融为一体,沉静在这片宁静的美丽之中,感受着生命的奇迹与温柔. 某某银行的副行长和一些行政领导表示,他们去过长江和长白山; 经济不断增长。2024年12月31号,拨打110或者18920240511。123456块钱。';
const generationConfig = new sherpa_onnx.GenerationConfig({
sid: 0,
speed: 1.0,
silenceScale: 0.2,
});
const start = Date.now();
const audio = await tts.generateAsync({
text,
enableExternalBuffer: true,
generationConfig,
onProgress: ({samples, progress}) => {
process.stdout.write(
`Progress: ${(progress * 100).toFixed(1)}%, ` +
`Samples: ${samples.length}\r`);
return 1;
},
});
console.log('');
const stop = Date.now();
const elapsed_seconds = (stop - start) / 1000;
const duration = audio.samples.length / audio.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));
const filename = 'test-matcha-zh-async.wav';
sherpa_onnx.writeWave(
filename, {samples: audio.samples, sampleRate: audio.sampleRate});
console.log(`Saved to ${filename}`);
}
main().catch((err) => {
console.error('Error:', err);
process.exitCode = 1;
});
@@ -102,4 +102,5 @@ async function main() {
// Run the async main
main().catch((err) => {
console.error('Error:', err);
process.exitCode = 1;
});
@@ -138,4 +138,5 @@ async function main() {
main().catch((err) => {
console.error('Error:', err);
process.exitCode = 1;
});
@@ -99,4 +99,5 @@ async function main() {
// Run the async main
main().catch((err) => {
console.error('Error:', err);
process.exitCode = 1;
});
@@ -135,4 +135,5 @@ async function main() {
main().catch((err) => {
console.error('Error:', err);
process.exitCode = 1;
});
@@ -0,0 +1,67 @@
// Copyright (c) 2026 Xiaomi Corporation
//
// Asynchronous text-to-speech with the VITS Coqui German model.
//
const sherpa_onnx = require('sherpa-onnx-node');
async function createOfflineTts() {
const config = {
model: {
vits: {
model: './vits-coqui-de-css10/model.onnx',
tokens: './vits-coqui-de-css10/tokens.txt',
},
debug: false,
numThreads: 1,
provider: 'cpu',
},
maxNumSentences: 1,
};
return await sherpa_onnx.OfflineTts.createAsync(config);
}
async function main() {
const tts = await createOfflineTts();
const text = 'Alles hat ein Ende, nur die Wurst hat zwei.';
const generationConfig = new sherpa_onnx.GenerationConfig({
sid: 0,
speed: 1.0,
silenceScale: 0.2,
});
const start = Date.now();
const audio = await tts.generateAsync({
text,
enableExternalBuffer: true,
generationConfig,
onProgress: ({samples, progress}) => {
process.stdout.write(
`Progress: ${(progress * 100).toFixed(1)}%, ` +
`Samples: ${samples.length}\r`);
return 1;
},
});
console.log('');
const stop = Date.now();
const elapsed_seconds = (stop - start) / 1000;
const duration = audio.samples.length / audio.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));
const filename = 'test-vits-coqui-de-async.wav';
sherpa_onnx.writeWave(
filename, {samples: audio.samples, sampleRate: audio.sampleRate});
console.log(`Saved to ${filename}`);
}
main().catch((err) => {
console.error('Error:', err);
process.exitCode = 1;
});
@@ -0,0 +1,69 @@
// Copyright (c) 2026 Xiaomi Corporation
//
// Asynchronous text-to-speech with the VITS Piper English model.
//
const sherpa_onnx = require('sherpa-onnx-node');
async function createOfflineTts() {
const config = {
model: {
vits: {
model: './vits-piper-en_GB-cori-medium/en_GB-cori-medium.onnx',
tokens: './vits-piper-en_GB-cori-medium/tokens.txt',
dataDir: './vits-piper-en_GB-cori-medium/espeak-ng-data',
},
debug: false,
numThreads: 1,
provider: 'cpu',
},
maxNumSentences: 1,
};
return await sherpa_onnx.OfflineTts.createAsync(config);
}
async function main() {
const tts = await createOfflineTts();
const text =
'Today as always, men fall into two groups: slaves and free men. Whoever does not have two-thirds of his day for himself, is a slave, whatever he may be: a statesman, a businessman, an official, or a scholar.';
const generationConfig = new sherpa_onnx.GenerationConfig({
sid: 0,
speed: 1.0,
silenceScale: 0.2,
});
const start = Date.now();
const audio = await tts.generateAsync({
text,
enableExternalBuffer: true,
generationConfig,
onProgress: ({samples, progress}) => {
process.stdout.write(
`Progress: ${(progress * 100).toFixed(1)}%, ` +
`Samples: ${samples.length}\r`);
return 1;
},
});
console.log('');
const stop = Date.now();
const elapsed_seconds = (stop - start) / 1000;
const duration = audio.samples.length / audio.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));
const filename = 'test-vits-piper-en-async.wav';
sherpa_onnx.writeWave(
filename, {samples: audio.samples, sampleRate: audio.sampleRate});
console.log(`Saved to ${filename}`);
}
main().catch((err) => {
console.error('Error:', err);
process.exitCode = 1;
});
@@ -0,0 +1,72 @@
// Copyright (c) 2026 Xiaomi Corporation
//
// Asynchronous text-to-speech with the VITS Chinese (AiShell3) model.
//
const sherpa_onnx = require('sherpa-onnx-node');
async function createOfflineTts() {
const config = {
model: {
vits: {
model: './vits-icefall-zh-aishell3/model.onnx',
tokens: './vits-icefall-zh-aishell3/tokens.txt',
lexicon: './vits-icefall-zh-aishell3/lexicon.txt',
},
debug: false,
numThreads: 1,
provider: 'cpu',
},
maxNumSentences: 1,
ruleFsts:
'./vits-icefall-zh-aishell3/date.fst,./vits-icefall-zh-aishell3/phone.fst,./vits-icefall-zh-aishell3/number.fst,./vits-icefall-zh-aishell3/new_heteronym.fst',
ruleFars: './vits-icefall-zh-aishell3/rule.far',
};
return await sherpa_onnx.OfflineTts.createAsync(config);
}
async function main() {
const tts = await createOfflineTts();
const text =
'他在长沙出生,长白山长大,去过长江,现在他是一个银行的行长,主管行政工作。有困难,请拨110,或者13020240513。今天是2024年5月13号, 他上个月的工资是12345块钱。';
const generationConfig = new sherpa_onnx.GenerationConfig({
sid: 88,
speed: 1.0,
silenceScale: 0.2,
});
const start = Date.now();
const audio = await tts.generateAsync({
text,
enableExternalBuffer: true,
generationConfig,
onProgress: ({samples, progress}) => {
process.stdout.write(
`Progress: ${(progress * 100).toFixed(1)}%, ` +
`Samples: ${samples.length}\r`);
return 1;
},
});
console.log('');
const stop = Date.now();
const elapsed_seconds = (stop - start) / 1000;
const duration = audio.samples.length / audio.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));
const filename = 'test-vits-zh-aishell3-async.wav';
sherpa_onnx.writeWave(
filename, {samples: audio.samples, sampleRate: audio.sampleRate});
console.log(`Saved to ${filename}`);
}
main().catch((err) => {
console.error('Error:', err);
process.exitCode = 1;
});
@@ -0,0 +1,71 @@
// Copyright (c) 2026 Xiaomi Corporation
//
// Asynchronous text-to-speech with the VITS Chinese (zh-ll) model.
//
const sherpa_onnx = require('sherpa-onnx-node');
async function createOfflineTts() {
const config = {
model: {
vits: {
model: './sherpa-onnx-vits-zh-ll/model.onnx',
tokens: './sherpa-onnx-vits-zh-ll/tokens.txt',
lexicon: './sherpa-onnx-vits-zh-ll/lexicon.txt',
},
debug: false,
numThreads: 1,
provider: 'cpu',
},
maxNumSentences: 1,
ruleFsts:
'./sherpa-onnx-vits-zh-ll/date.fst,./sherpa-onnx-vits-zh-ll/phone.fst,./sherpa-onnx-vits-zh-ll/number.fst',
};
return await sherpa_onnx.OfflineTts.createAsync(config);
}
async function main() {
const tts = await createOfflineTts();
const text =
'当夜幕降临,星光点点,伴随着微风拂面,我在静谧中感受着时光的流转,思念如涟漪荡漾,梦境如画卷展开,我与自然融为一体,沉静在这片宁静的美丽之中,感受着生命的奇迹与温柔。2024年5月13号,拨打110或者18920240513。123456块钱。';
const generationConfig = new sherpa_onnx.GenerationConfig({
sid: 2,
speed: 1.0,
silenceScale: 0.2,
});
const start = Date.now();
const audio = await tts.generateAsync({
text,
enableExternalBuffer: true,
generationConfig,
onProgress: ({samples, progress}) => {
process.stdout.write(
`Progress: ${(progress * 100).toFixed(1)}%, ` +
`Samples: ${samples.length}\r`);
return 1;
},
});
console.log('');
const stop = Date.now();
const elapsed_seconds = (stop - start) / 1000;
const duration = audio.samples.length / audio.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));
const filename = 'test-vits-zh-ll-async.wav';
sherpa_onnx.writeWave(
filename, {samples: audio.samples, sampleRate: audio.sampleRate});
console.log(`Saved to ${filename}`);
}
main().catch((err) => {
console.error('Error:', err);
process.exitCode = 1;
});
@@ -95,4 +95,5 @@ async function main() {
main().catch((err) => {
console.error('Error:', err);
process.exitCode = 1;
});
@@ -140,4 +140,5 @@ async function main() {
main().catch((err) => {
console.error('Error:', err);
process.exitCode = 1;
});
@@ -1,7 +1,7 @@
// Copyright (c) 2023-2024 Xiaomi Corporation (authors: Fangjun Kuang)
//
const portAudio = require('naudiodon2');
// console.log(portAudio.getDevices());
const cpal = require('node-cpal');
const sherpa_onnx = require('sherpa-onnx-node');
@@ -60,56 +60,61 @@ const bufferSizeInSeconds = 30;
const buffer =
new sherpa_onnx.CircularBuffer(bufferSizeInSeconds * vad.config.sampleRate);
const ai = new portAudio.AudioIO({
inOptions: {
channelCount: 1,
closeOnError: true, // Close the stream if an audio error is detected, if
// set false then just log the error
deviceId: -1, // Use -1 or omit the deviceId to select the default device
sampleFormat: portAudio.SampleFormatFloat32,
sampleRate: vad.config.sampleRate
}
});
const inputDevice = cpal.getDefaultInputDevice();
const deviceConfig = cpal.getDefaultInputConfig(inputDevice.deviceId);
const nativeSampleRate = deviceConfig.sampleRate;
const targetSampleRate = vad.config.sampleRate;
const resampler = new sherpa_onnx.LinearResampler(nativeSampleRate, targetSampleRate);
let printed = false;
let index = 0;
ai.on('data', data => {
const windowSize = vad.config.sileroVad.windowSize;
buffer.push(new Float32Array(data.buffer));
while (buffer.size() > windowSize) {
const samples = buffer.get(buffer.head(), windowSize);
buffer.pop(windowSize);
vad.acceptWaveform(samples);
}
while (!vad.isEmpty()) {
const segment = vad.front();
vad.pop();
const stream = recognizer.createStream();
stream.acceptWaveform({
samples: segment.samples,
sampleRate: recognizer.config.featConfig.sampleRate
const inputStream = cpal.createStream(
inputDevice.deviceId,
true,
{
sampleRate: nativeSampleRate,
channels: 1,
format: 'f32',
},
(data) => {
const resampled = resampler.resample(data);
const windowSize = vad.config.sileroVad.windowSize;
buffer.push(resampled);
while (buffer.size() >= windowSize) {
const samples = buffer.get(buffer.head(), windowSize);
buffer.pop(windowSize);
vad.acceptWaveform(samples);
}
while (!vad.isEmpty()) {
const segment = vad.front();
vad.pop();
const stream = recognizer.createStream();
stream.acceptWaveform({
samples: segment.samples,
sampleRate: recognizer.config.featConfig.sampleRate
});
recognizer.decode(stream);
const r = recognizer.getResult(stream);
if (r.text.length > 0) {
const text = r.text.toLowerCase().trim();
console.log(`${index}: ${text}`);
const filename = `${index}-${text}-${
new Date()
.toLocaleTimeString('en-US', {hour12: false})
.split(' ')[0]}.wav`
.replace(/:/g, '-');
sherpa_onnx.writeWave(
filename,
{samples: segment.samples, sampleRate: vad.config.sampleRate});
index += 1;
}
}
});
recognizer.decode(stream);
const r = recognizer.getResult(stream);
if (r.text.length > 0) {
const text = r.text.toLowerCase().trim();
console.log(`${index}: ${text}`);
const filename = `${index}-${text}-${
new Date()
.toLocaleTimeString('en-US', {hour12: false})
.split(' ')[0]}.wav`
.replace(/:/g, '-');
sherpa_onnx.writeWave(
filename,
{samples: segment.samples, sampleRate: vad.config.sampleRate});
index += 1;
}
}
});
ai.start();
console.log('Started! Please speak');
@@ -1,7 +1,7 @@
// Copyright (c) 2023-2024 Xiaomi Corporation (authors: Fangjun Kuang)
//
const portAudio = require('naudiodon2');
// console.log(portAudio.getDevices());
const cpal = require('node-cpal');
const sherpa_onnx = require('sherpa-onnx-node');
@@ -57,56 +57,61 @@ const bufferSizeInSeconds = 30;
const buffer =
new sherpa_onnx.CircularBuffer(bufferSizeInSeconds * vad.config.sampleRate);
const ai = new portAudio.AudioIO({
inOptions: {
channelCount: 1,
closeOnError: true, // Close the stream if an audio error is detected, if
// set false then just log the error
deviceId: -1, // Use -1 or omit the deviceId to select the default device
sampleFormat: portAudio.SampleFormatFloat32,
sampleRate: vad.config.sampleRate
}
});
const inputDevice = cpal.getDefaultInputDevice();
const deviceConfig = cpal.getDefaultInputConfig(inputDevice.deviceId);
const nativeSampleRate = deviceConfig.sampleRate;
const targetSampleRate = vad.config.sampleRate;
const resampler = new sherpa_onnx.LinearResampler(nativeSampleRate, targetSampleRate);
let printed = false;
let index = 0;
ai.on('data', data => {
const windowSize = vad.config.sileroVad.windowSize;
buffer.push(new Float32Array(data.buffer));
while (buffer.size() > windowSize) {
const samples = buffer.get(buffer.head(), windowSize);
buffer.pop(windowSize);
vad.acceptWaveform(samples);
}
while (!vad.isEmpty()) {
const segment = vad.front();
vad.pop();
const stream = recognizer.createStream();
stream.acceptWaveform({
samples: segment.samples,
sampleRate: recognizer.config.featConfig.sampleRate
const inputStream = cpal.createStream(
inputDevice.deviceId,
true,
{
sampleRate: nativeSampleRate,
channels: 1,
format: 'f32',
},
(data) => {
const resampled = resampler.resample(data);
const windowSize = vad.config.sileroVad.windowSize;
buffer.push(resampled);
while (buffer.size() >= windowSize) {
const samples = buffer.get(buffer.head(), windowSize);
buffer.pop(windowSize);
vad.acceptWaveform(samples);
}
while (!vad.isEmpty()) {
const segment = vad.front();
vad.pop();
const stream = recognizer.createStream();
stream.acceptWaveform({
samples: segment.samples,
sampleRate: recognizer.config.featConfig.sampleRate
});
recognizer.decode(stream);
const r = recognizer.getResult(stream);
if (r.text.length > 0) {
const text = r.text.toLowerCase().trim();
console.log(`${index}: ${text}`);
const filename = `${index}-${text}-${
new Date()
.toLocaleTimeString('en-US', {hour12: false})
.split(' ')[0]}.wav`
.replace(/:/g, '-');
sherpa_onnx.writeWave(
filename,
{samples: segment.samples, sampleRate: vad.config.sampleRate});
index += 1;
}
}
});
recognizer.decode(stream);
const r = recognizer.getResult(stream);
if (r.text.length > 0) {
const text = r.text.toLowerCase().trim();
console.log(`${index}: ${text}`);
const filename = `${index}-${text}-${
new Date()
.toLocaleTimeString('en-US', {hour12: false})
.split(' ')[0]}.wav`
.replace(/:/g, '-');
sherpa_onnx.writeWave(
filename,
{samples: segment.samples, sampleRate: vad.config.sampleRate});
index += 1;
}
}
});
ai.start();
console.log('Started! Please speak');
@@ -1,7 +1,7 @@
// Copyright (c) 2023-2024 Xiaomi Corporation (authors: Fangjun Kuang)
//
const portAudio = require('naudiodon2');
// console.log(portAudio.getDevices());
const cpal = require('node-cpal');
const sherpa_onnx = require('sherpa-onnx-node');
@@ -55,56 +55,61 @@ const bufferSizeInSeconds = 30;
const buffer =
new sherpa_onnx.CircularBuffer(bufferSizeInSeconds * vad.config.sampleRate);
const ai = new portAudio.AudioIO({
inOptions: {
channelCount: 1,
closeOnError: true, // Close the stream if an audio error is detected, if
// set false then just log the error
deviceId: -1, // Use -1 or omit the deviceId to select the default device
sampleFormat: portAudio.SampleFormatFloat32,
sampleRate: vad.config.sampleRate
}
});
const inputDevice = cpal.getDefaultInputDevice();
const deviceConfig = cpal.getDefaultInputConfig(inputDevice.deviceId);
const nativeSampleRate = deviceConfig.sampleRate;
const targetSampleRate = vad.config.sampleRate;
const resampler = new sherpa_onnx.LinearResampler(nativeSampleRate, targetSampleRate);
let printed = false;
let index = 0;
ai.on('data', data => {
const windowSize = vad.config.sileroVad.windowSize;
buffer.push(new Float32Array(data.buffer));
while (buffer.size() > windowSize) {
const samples = buffer.get(buffer.head(), windowSize);
buffer.pop(windowSize);
vad.acceptWaveform(samples);
}
while (!vad.isEmpty()) {
const segment = vad.front();
vad.pop();
const stream = recognizer.createStream();
stream.acceptWaveform({
samples: segment.samples,
sampleRate: recognizer.config.featConfig.sampleRate
const inputStream = cpal.createStream(
inputDevice.deviceId,
true,
{
sampleRate: nativeSampleRate,
channels: 1,
format: 'f32',
},
(data) => {
const resampled = resampler.resample(data);
const windowSize = vad.config.sileroVad.windowSize;
buffer.push(resampled);
while (buffer.size() >= windowSize) {
const samples = buffer.get(buffer.head(), windowSize);
buffer.pop(windowSize);
vad.acceptWaveform(samples);
}
while (!vad.isEmpty()) {
const segment = vad.front();
vad.pop();
const stream = recognizer.createStream();
stream.acceptWaveform({
samples: segment.samples,
sampleRate: recognizer.config.featConfig.sampleRate
});
recognizer.decode(stream);
const r = recognizer.getResult(stream);
if (r.text.length > 0) {
const text = r.text.toLowerCase().trim();
console.log(`${index}: ${text}`);
const filename = `${index}-${text}-${
new Date()
.toLocaleTimeString('en-US', {hour12: false})
.split(' ')[0]}.wav`
.replace(/:/g, '-');
sherpa_onnx.writeWave(
filename,
{samples: segment.samples, sampleRate: vad.config.sampleRate});
index += 1;
}
}
});
recognizer.decode(stream);
const r = recognizer.getResult(stream);
if (r.text.length > 0) {
const text = r.text.toLowerCase().trim();
console.log(`${index}: ${text}`);
const filename = `${index}-${text}-${
new Date()
.toLocaleTimeString('en-US', {hour12: false})
.split(' ')[0]}.wav`
.replace(/:/g, '-');
sherpa_onnx.writeWave(
filename,
{samples: segment.samples, sampleRate: vad.config.sampleRate});
index += 1;
}
}
});
ai.start();
console.log('Started! Please speak');
@@ -1,7 +1,7 @@
// Copyright (c) 2023-2024 Xiaomi Corporation (authors: Fangjun Kuang)
//
const portAudio = require('naudiodon2');
// console.log(portAudio.getDevices());
const cpal = require('node-cpal');
const sherpa_onnx = require('sherpa-onnx-node');
@@ -16,11 +16,11 @@ function createRecognizer() {
'modelConfig': {
'senseVoice': {
'model':
'./sherpa-onnx-sense-voice-zh-en-ja-ko-yue-2024-07-17/model.int8.onnx',
'./sherpa-onnx-sense-voice-zh-en-ja-ko-yue-int8-2024-07-17/model.int8.onnx',
'useInverseTextNormalization': 1,
},
'tokens':
'./sherpa-onnx-sense-voice-zh-en-ja-ko-yue-2024-07-17/tokens.txt',
'./sherpa-onnx-sense-voice-zh-en-ja-ko-yue-int8-2024-07-17/tokens.txt',
'numThreads': 2,
'provider': 'cpu',
'debug': 1,
@@ -58,56 +58,59 @@ const bufferSizeInSeconds = 30;
const buffer =
new sherpa_onnx.CircularBuffer(bufferSizeInSeconds * vad.config.sampleRate);
const ai = new portAudio.AudioIO({
inOptions: {
channelCount: 1,
closeOnError: true, // Close the stream if an audio error is detected, if
// set false then just log the error
deviceId: -1, // Use -1 or omit the deviceId to select the default device
sampleFormat: portAudio.SampleFormatFloat32,
sampleRate: vad.config.sampleRate
}
});
const inputDevice = cpal.getDefaultInputDevice();
const deviceConfig = cpal.getDefaultInputConfig(inputDevice.deviceId);
const nativeSampleRate = deviceConfig.sampleRate;
const targetSampleRate = vad.config.sampleRate;
const resampler = new sherpa_onnx.LinearResampler(nativeSampleRate, targetSampleRate);
let printed = false;
let index = 0;
ai.on('data', data => {
const windowSize = vad.config.sileroVad.windowSize;
buffer.push(new Float32Array(data.buffer));
while (buffer.size() > windowSize) {
const samples = buffer.get(buffer.head(), windowSize);
buffer.pop(windowSize);
vad.acceptWaveform(samples);
}
while (!vad.isEmpty()) {
const segment = vad.front();
vad.pop();
const stream = recognizer.createStream();
stream.acceptWaveform({
samples: segment.samples,
sampleRate: recognizer.config.featConfig.sampleRate
const inputStream = cpal.createStream(
inputDevice.deviceId, true, {
sampleRate: nativeSampleRate,
channels: 1,
format: 'f32',
},
(data) => {
const resampled = resampler.resample(data);
const windowSize = vad.config.sileroVad.windowSize;
buffer.push(resampled);
while (buffer.size() >= windowSize) {
const samples = buffer.get(buffer.head(), windowSize);
buffer.pop(windowSize);
vad.acceptWaveform(samples);
}
while (!vad.isEmpty()) {
const segment = vad.front();
vad.pop();
const stream = recognizer.createStream();
stream.acceptWaveform({
samples: segment.samples,
sampleRate: recognizer.config.featConfig.sampleRate
});
recognizer.decode(stream);
const r = recognizer.getResult(stream);
if (r.text.length > 0) {
const text = r.text.toLowerCase().trim();
console.log(`${index}: ${text}`);
const filename = `${index}-${text}-${
new Date()
.toLocaleTimeString('en-US', {hour12: false})
.split(' ')[0]}.wav`
.replace(/:/g, '-');
sherpa_onnx.writeWave(
filename,
{samples: segment.samples, sampleRate: vad.config.sampleRate});
index += 1;
}
}
});
recognizer.decode(stream);
const r = recognizer.getResult(stream);
if (r.text.length > 0) {
const text = r.text.toLowerCase().trim();
console.log(`${index}: ${text}`);
const filename = `${index}-${text}-${
new Date()
.toLocaleTimeString('en-US', {hour12: false})
.split(' ')[0]}.wav`
.replace(/:/g, '-');
sherpa_onnx.writeWave(
filename,
{samples: segment.samples, sampleRate: vad.config.sampleRate});
index += 1;
}
}
});
ai.start();
console.log('Started! Please speak');
@@ -1,7 +1,7 @@
// Copyright (c) 2023-2024 Xiaomi Corporation (authors: Fangjun Kuang)
//
const portAudio = require('naudiodon2');
// console.log(portAudio.getDevices());
const cpal = require('node-cpal');
const sherpa_onnx = require('sherpa-onnx-node');
@@ -60,56 +60,61 @@ const bufferSizeInSeconds = 30;
const buffer =
new sherpa_onnx.CircularBuffer(bufferSizeInSeconds * vad.config.sampleRate);
const ai = new portAudio.AudioIO({
inOptions: {
channelCount: 1,
closeOnError: true, // Close the stream if an audio error is detected, if
// set false then just log the error
deviceId: -1, // Use -1 or omit the deviceId to select the default device
sampleFormat: portAudio.SampleFormatFloat32,
sampleRate: vad.config.sampleRate
}
});
const inputDevice = cpal.getDefaultInputDevice();
const deviceConfig = cpal.getDefaultInputConfig(inputDevice.deviceId);
const nativeSampleRate = deviceConfig.sampleRate;
const targetSampleRate = vad.config.sampleRate;
const resampler = new sherpa_onnx.LinearResampler(nativeSampleRate, targetSampleRate);
let printed = false;
let index = 0;
ai.on('data', data => {
const windowSize = vad.config.sileroVad.windowSize;
buffer.push(new Float32Array(data.buffer));
while (buffer.size() > windowSize) {
const samples = buffer.get(buffer.head(), windowSize);
buffer.pop(windowSize);
vad.acceptWaveform(samples);
}
while (!vad.isEmpty()) {
const segment = vad.front();
vad.pop();
const stream = recognizer.createStream();
stream.acceptWaveform({
samples: segment.samples,
sampleRate: recognizer.config.featConfig.sampleRate
const inputStream = cpal.createStream(
inputDevice.deviceId,
true,
{
sampleRate: nativeSampleRate,
channels: 1,
format: 'f32',
},
(data) => {
const resampled = resampler.resample(data);
const windowSize = vad.config.sileroVad.windowSize;
buffer.push(resampled);
while (buffer.size() >= windowSize) {
const samples = buffer.get(buffer.head(), windowSize);
buffer.pop(windowSize);
vad.acceptWaveform(samples);
}
while (!vad.isEmpty()) {
const segment = vad.front();
vad.pop();
const stream = recognizer.createStream();
stream.acceptWaveform({
samples: segment.samples,
sampleRate: recognizer.config.featConfig.sampleRate
});
recognizer.decode(stream);
const r = recognizer.getResult(stream);
if (r.text.length > 0) {
const text = r.text.toLowerCase().trim();
console.log(`${index}: ${text}`);
const filename = `${index}-${text}-${
new Date()
.toLocaleTimeString('en-US', {hour12: false})
.split(' ')[0]}.wav`
.replace(/:/g, '-');
sherpa_onnx.writeWave(
filename,
{samples: segment.samples, sampleRate: vad.config.sampleRate});
index += 1;
}
}
});
recognizer.decode(stream);
const r = recognizer.getResult(stream);
if (r.text.length > 0) {
const text = r.text.toLowerCase().trim();
console.log(`${index}: ${text}`);
const filename = `${index}-${text}-${
new Date()
.toLocaleTimeString('en-US', {hour12: false})
.split(' ')[0]}.wav`
.replace(/:/g, '-');
sherpa_onnx.writeWave(
filename,
{samples: segment.samples, sampleRate: vad.config.sampleRate});
index += 1;
}
}
});
ai.start();
console.log('Started! Please speak');
@@ -1,7 +1,7 @@
// Copyright (c) 2023-2024 Xiaomi Corporation (authors: Fangjun Kuang)
//
const portAudio = require('naudiodon2');
// console.log(portAudio.getDevices());
const cpal = require('node-cpal');
const sherpa_onnx = require('sherpa-onnx-node');
@@ -56,56 +56,61 @@ const bufferSizeInSeconds = 30;
const buffer =
new sherpa_onnx.CircularBuffer(bufferSizeInSeconds * vad.config.sampleRate);
const ai = new portAudio.AudioIO({
inOptions: {
channelCount: 1,
closeOnError: true, // Close the stream if an audio error is detected, if
// set false then just log the error
deviceId: -1, // Use -1 or omit the deviceId to select the default device
sampleFormat: portAudio.SampleFormatFloat32,
sampleRate: vad.config.sampleRate
}
});
const inputDevice = cpal.getDefaultInputDevice();
const deviceConfig = cpal.getDefaultInputConfig(inputDevice.deviceId);
const nativeSampleRate = deviceConfig.sampleRate;
const targetSampleRate = vad.config.sampleRate;
const resampler = new sherpa_onnx.LinearResampler(nativeSampleRate, targetSampleRate);
let printed = false;
let index = 0;
ai.on('data', data => {
const windowSize = vad.config.sileroVad.windowSize;
buffer.push(new Float32Array(data.buffer));
while (buffer.size() > windowSize) {
const samples = buffer.get(buffer.head(), windowSize);
buffer.pop(windowSize);
vad.acceptWaveform(samples);
}
while (!vad.isEmpty()) {
const segment = vad.front();
vad.pop();
const stream = recognizer.createStream();
stream.acceptWaveform({
samples: segment.samples,
sampleRate: recognizer.config.featConfig.sampleRate
const inputStream = cpal.createStream(
inputDevice.deviceId,
true,
{
sampleRate: nativeSampleRate,
channels: 1,
format: 'f32',
},
(data) => {
const resampled = resampler.resample(data);
const windowSize = vad.config.sileroVad.windowSize;
buffer.push(resampled);
while (buffer.size() >= windowSize) {
const samples = buffer.get(buffer.head(), windowSize);
buffer.pop(windowSize);
vad.acceptWaveform(samples);
}
while (!vad.isEmpty()) {
const segment = vad.front();
vad.pop();
const stream = recognizer.createStream();
stream.acceptWaveform({
samples: segment.samples,
sampleRate: recognizer.config.featConfig.sampleRate
});
recognizer.decode(stream);
const r = recognizer.getResult(stream);
if (r.text.length > 0) {
const text = r.text.toLowerCase().trim();
console.log(`${index}: ${text}`);
const filename = `${index}-${text}-${
new Date()
.toLocaleTimeString('en-US', {hour12: false})
.split(' ')[0]}.wav`
.replace(/:/g, '-');
sherpa_onnx.writeWave(
filename,
{samples: segment.samples, sampleRate: vad.config.sampleRate});
index += 1;
}
}
});
recognizer.decode(stream);
const r = recognizer.getResult(stream);
if (r.text.length > 0) {
const text = r.text.toLowerCase().trim();
console.log(`${index}: ${text}`);
const filename = `${index}-${text}-${
new Date()
.toLocaleTimeString('en-US', {hour12: false})
.split(' ')[0]}.wav`
.replace(/:/g, '-');
sherpa_onnx.writeWave(
filename,
{samples: segment.samples, sampleRate: vad.config.sampleRate});
index += 1;
}
}
});
ai.start();
console.log('Started! Please speak');
@@ -1,7 +1,7 @@
// Copyright (c) 2025 Xiaomi Corporation (authors: Fangjun Kuang)
//
const portAudio = require('naudiodon2');
// console.log(portAudio.getDevices());
const cpal = require('node-cpal');
const sherpa_onnx = require('sherpa-onnx-node');
@@ -56,56 +56,61 @@ const bufferSizeInSeconds = 30;
const buffer =
new sherpa_onnx.CircularBuffer(bufferSizeInSeconds * vad.config.sampleRate);
const ai = new portAudio.AudioIO({
inOptions: {
channelCount: 1,
closeOnError: true, // Close the stream if an audio error is detected, if
// set false then just log the error
deviceId: -1, // Use -1 or omit the deviceId to select the default device
sampleFormat: portAudio.SampleFormatFloat32,
sampleRate: vad.config.sampleRate
}
});
const inputDevice = cpal.getDefaultInputDevice();
const deviceConfig = cpal.getDefaultInputConfig(inputDevice.deviceId);
const nativeSampleRate = deviceConfig.sampleRate;
const targetSampleRate = vad.config.sampleRate;
const resampler = new sherpa_onnx.LinearResampler(nativeSampleRate, targetSampleRate);
let printed = false;
let index = 0;
ai.on('data', data => {
const windowSize = vad.config.sileroVad.windowSize;
buffer.push(new Float32Array(data.buffer));
while (buffer.size() > windowSize) {
const samples = buffer.get(buffer.head(), windowSize);
buffer.pop(windowSize);
vad.acceptWaveform(samples);
}
while (!vad.isEmpty()) {
const segment = vad.front();
vad.pop();
const stream = recognizer.createStream();
stream.acceptWaveform({
samples: segment.samples,
sampleRate: recognizer.config.featConfig.sampleRate
const inputStream = cpal.createStream(
inputDevice.deviceId,
true,
{
sampleRate: nativeSampleRate,
channels: 1,
format: 'f32',
},
(data) => {
const resampled = resampler.resample(data);
const windowSize = vad.config.sileroVad.windowSize;
buffer.push(resampled);
while (buffer.size() >= windowSize) {
const samples = buffer.get(buffer.head(), windowSize);
buffer.pop(windowSize);
vad.acceptWaveform(samples);
}
while (!vad.isEmpty()) {
const segment = vad.front();
vad.pop();
const stream = recognizer.createStream();
stream.acceptWaveform({
samples: segment.samples,
sampleRate: recognizer.config.featConfig.sampleRate
});
recognizer.decode(stream);
const r = recognizer.getResult(stream);
if (r.text.length > 0) {
const text = r.text.toLowerCase().trim();
console.log(`${index}: ${text}`);
const filename = `${index}-${text}-${
new Date()
.toLocaleTimeString('en-US', {hour12: false})
.split(' ')[0]}.wav`
.replace(/:/g, '-');
sherpa_onnx.writeWave(
filename,
{samples: segment.samples, sampleRate: vad.config.sampleRate});
index += 1;
}
}
});
recognizer.decode(stream);
const r = recognizer.getResult(stream);
if (r.text.length > 0) {
const text = r.text.toLowerCase().trim();
console.log(`${index}: ${text}`);
const filename = `${index}-${text}-${
new Date()
.toLocaleTimeString('en-US', {hour12: false})
.split(' ')[0]}.wav`
.replace(/:/g, '-');
sherpa_onnx.writeWave(
filename,
{samples: segment.samples, sampleRate: vad.config.sampleRate});
index += 1;
}
}
});
ai.start();
console.log('Started! Please speak');
+59 -50
View File
@@ -1,7 +1,6 @@
// Copyright (c) 2023-2024 Xiaomi Corporation (authors: Fangjun Kuang)
const portAudio = require('naudiodon2');
// console.log(portAudio.getDevices());
const cpal = require('node-cpal');
const sherpa_onnx = require('sherpa-onnx-node');
@@ -46,61 +45,71 @@ const bufferSizeInSeconds = 30;
const buffer =
new sherpa_onnx.CircularBuffer(bufferSizeInSeconds * vad.config.sampleRate);
const inputDevice = cpal.getDefaultInputDevice();
const deviceConfig = cpal.getDefaultInputConfig(inputDevice.deviceId);
const nativeSampleRate = deviceConfig.sampleRate;
const targetSampleRate = vad.config.sampleRate;
const resampler =
new sherpa_onnx.LinearResampler(nativeSampleRate, targetSampleRate);
const ai = new portAudio.AudioIO({
inOptions: {
channelCount: 1,
closeOnError: true, // Close the stream if an audio error is detected, if
// set false then just log the error
deviceId: -1, // Use -1 or omit the deviceId to select the default device
sampleFormat: portAudio.SampleFormatFloat32,
sampleRate: vad.config.sampleRate,
}
});
console.log(
`Device: ${inputDevice.name}, native sample rate: ${nativeSampleRate} Hz`);
console.log(`Resampling from ${nativeSampleRate} to ${targetSampleRate} Hz`);
let printed = false;
let index = 0;
ai.on('data', data => {
const windowSize = vad.config.sileroVad.model != '' ?
vad.config.sileroVad.windowSize :
vad.config.tenVad.windowSize;
buffer.push(new Float32Array(data.buffer));
while (buffer.size() > windowSize) {
const samples = buffer.get(buffer.head(), windowSize);
buffer.pop(windowSize);
vad.acceptWaveform(samples);
if (vad.isDetected() && !printed) {
console.log(`${index}: Detected speech`);
printed = true;
}
const inputStream = cpal.createStream(
inputDevice.deviceId,
true, // true = input/recording stream
{
sampleRate: nativeSampleRate,
channels: 1,
format: 'f32',
},
(data) => {
const windowSize = vad.config.sileroVad.model != '' ?
vad.config.sileroVad.windowSize :
vad.config.tenVad.windowSize;
if (!vad.isDetected()) {
printed = false;
}
const resampled = resampler.resample(data);
buffer.push(resampled);
while (buffer.size() >= windowSize) {
const samples = buffer.get(buffer.head(), windowSize);
buffer.pop(windowSize);
vad.acceptWaveform(samples);
if (vad.isDetected() && !printed) {
console.log(`${index}: Detected speech`);
printed = true;
}
while (!vad.isEmpty()) {
const segment = vad.front();
vad.pop();
const filename = `${index}-${
new Date()
.toLocaleTimeString('en-US', {hour12: false})
.split(' ')[0]}.wav`
.replace(/:/g, '-');
sherpa_onnx.writeWave(
filename,
{samples: segment.samples, sampleRate: vad.config.sampleRate});
const duration = segment.samples.length / vad.config.sampleRate;
console.log(`${index} End of speech. Duration: ${duration} seconds`);
console.log(`Saved to ${filename}`);
index += 1;
}
}
});
if (!vad.isDetected()) {
printed = false;
}
ai.on('close', () => {
console.log('Free resources');
});
while (!vad.isEmpty()) {
const segment = vad.front();
vad.pop();
const filename = `${index}-${
new Date()
.toLocaleTimeString('en-US', {hour12: false})
.split(' ')[0]}.wav`
.replace(/:/g, '-');
sherpa_onnx.writeWave(
filename,
{samples: segment.samples, sampleRate: vad.config.sampleRate});
const duration = segment.samples.length / vad.config.sampleRate;
console.log(`${index} End of speech. Duration: ${duration} seconds`);
console.log(`Saved to ${filename}`);
index += 1;
}
}
});
ai.start();
console.log('Started! Please speak');
process.on('SIGINT', () => {
cpal.closeStream(inputStream);
console.log('Free resources');
process.exit(0);
});
@@ -1,7 +1,7 @@
// Copyright (c) 2023-2024 Xiaomi Corporation (authors: Fangjun Kuang)
const portAudio = require('naudiodon2');
// console.log(portAudio.getDevices());
const cpal = require('node-cpal');
const sherpa_onnx = require('sherpa-onnx-node');
@@ -51,66 +51,73 @@ const buffer =
new sherpa_onnx.CircularBuffer(bufferSizeInSeconds * vad.config.sampleRate);
const ai = new portAudio.AudioIO({
inOptions: {
channelCount: 1,
closeOnError: true, // Close the stream if an audio error is detected, if
// set false then just log the error
deviceId: -1, // Use -1 or omit the deviceId to select the default device
sampleFormat: portAudio.SampleFormatFloat32,
sampleRate: vad.config.sampleRate,
}
});
const inputDevice = cpal.getDefaultInputDevice();
const deviceConfig = cpal.getDefaultInputConfig(inputDevice.deviceId);
const nativeSampleRate = deviceConfig.sampleRate;
const targetSampleRate = vad.config.sampleRate;
const resampler = new sherpa_onnx.LinearResampler(nativeSampleRate, targetSampleRate);
let printed = false;
let index = 0;
ai.on('data', data => {
const windowSize = vad.config.sileroVad.windowSize;
buffer.push(new Float32Array(data.buffer));
while (buffer.size() > windowSize) {
const samples = buffer.get(buffer.head(), windowSize);
buffer.pop(windowSize);
vad.acceptWaveform(samples);
if (vad.isDetected() && !printed) {
console.log(`${index}: Detected speech`);
printed = true;
}
if (!vad.isDetected()) {
printed = false;
}
const inputStream = cpal.createStream(
inputDevice.deviceId,
true,
{
sampleRate: nativeSampleRate,
channels: 1,
format: 'f32',
},
(data) => {
const resampled = resampler.resample(data);
const windowSize = vad.config.sileroVad.windowSize;
buffer.push(resampled);
while (buffer.size() >= windowSize) {
const samples = buffer.get(buffer.head(), windowSize);
buffer.pop(windowSize);
vad.acceptWaveform(samples);
if (vad.isDetected() && !printed) {
console.log(`${index}: Detected speech`);
printed = true;
}
while (!vad.isEmpty()) {
const segment = vad.front();
vad.pop();
if (!vad.isDetected()) {
printed = false;
}
const stream = slid.createStream();
stream.acceptWaveform(
{samples: segment.samples, sampleRate: vad.config.sampleRate});
const lang = slid.compute(stream);
const fullLang = display.of(lang);
while (!vad.isEmpty()) {
const segment = vad.front();
vad.pop();
const filename = `${index}-${fullLang}-${
new Date()
.toLocaleTimeString('en-US', {hour12: false})
.split(' ')[0]}.wav`
.replace(/:/g, '-');
const stream = slid.createStream();
stream.acceptWaveform(
{samples: segment.samples, sampleRate: vad.config.sampleRate});
const lang = slid.compute(stream);
const fullLang = display.of(lang);
sherpa_onnx.writeWave(
filename,
{samples: segment.samples, sampleRate: vad.config.sampleRate});
const duration = segment.samples.length / vad.config.sampleRate;
console.log(`${index} End of speech. Duration: ${
duration} seconds.\n Detected language: ${fullLang}`);
console.log(`Saved to ${filename}`);
index += 1;
}
}
});
const filename = `${index}-${fullLang}-${
new Date()
.toLocaleTimeString('en-US', {hour12: false})
.split(' ')[0]}.wav`
.replace(/:/g, '-');
ai.on('close', () => {
sherpa_onnx.writeWave(
filename,
{samples: segment.samples, sampleRate: vad.config.sampleRate});
const duration = segment.samples.length / vad.config.sampleRate;
console.log(`${index} End of speech. Duration: ${
duration} seconds.\n Detected language: ${fullLang}`);
console.log(`Saved to ${filename}`);
index += 1;
}
}
});
process.on('SIGINT', () => {
cpal.closeStream(inputStream);
console.log('Free resources');
process.exit(0);
});
ai.start();
console.log('Started! Please speak');
+1
View File
@@ -50,6 +50,7 @@ set(srcs
src/version.cc
src/wave-reader.cc
src/wave-writer.cc
src/resampler.cc
)
if(NOT DEFINED ENV{SHERPA_ONNX_INSTALL_DIR})
+4
View File
@@ -21,3 +21,7 @@ npm i
# see test/test_asr_streaming_transducer.js
# for usages
```
Please see doc at <https://k2-fsa.github.io/sherpa/onnx/javascript-api/index.html>.
API doc can be found at <https://k2-fsa.github.io/sherpa/onnx/javascript-api/html/index.html>
@@ -1,4 +1,5 @@
/** @typedef {import('./types').KeywordSpotterConfig} KeywordSpotterConfig */
/** @typedef {import('./types').KeywordSpotterHandle} KeywordSpotterHandle */
/** @typedef {import('./types').KeywordResult} KeywordResult */
/** @typedef {import('./streaming-asr').OnlineStream} OnlineStream */
@@ -1,4 +1,6 @@
/** @typedef {import('./types').OfflineStreamObject} OfflineStreamObject */
/** @typedef {import('./types').OfflineStreamHandle} OfflineStreamHandle */
/** @typedef {import('./types').OfflineRecognizerHandle} OfflineRecognizerHandle */
/** @typedef {import('./types').Waveform} Waveform */
/**
* @typedef {import('./types').OfflineRecognizerConfig} OfflineRecognizerConfig
@@ -1,4 +1,5 @@
/** @typedef {import('./types').OfflineSpeakerDiarizationConfig} OfflineSpeakerDiarizationConfig */
/** @typedef {import('./types').OfflineSpeakerDiarizationHandle} OfflineSpeakerDiarizationHandle */
/** @typedef {import('./types').SpeakerDiarizationSegment} SpeakerDiarizationSegment */
const addon = require('./addon.js');
@@ -1,4 +1,5 @@
/** @typedef {import('./types').OfflineSpeechDenoiserConfig} OfflineSpeechDenoiserConfig */
/** @typedef {import('./types').OfflineSpeechDenoiserHandle} OfflineSpeechDenoiserHandle */
/** @typedef {import('./types').GeneratedAudio} GeneratedAudio */
/** @typedef {import('./types').AudioProcessRequest} AudioProcessRequest */
@@ -1,4 +1,5 @@
/** @typedef {import('./types').OfflineTtsConfig} OfflineTtsConfig */
/** @typedef {import('./types').OfflineTtsHandle} OfflineTtsHandle */
/** @typedef {import('./types').TtsRequest} TtsRequest */
/** @typedef {import('./types').GeneratedAudio} GeneratedAudio */
@@ -83,7 +84,7 @@ class OfflineTts {
*
* The progress callback receives streaming audio chunks.
*
* @param {TtsRequest & { generationConfig?: object, onProgress?: (info: {
* @param {TtsRequest & { generationConfig?: GenerationConfig, onProgress?: (info: {
* samples: Float32Array, progress: number }) => number | boolean | void
* }} obj
* @returns {Promise<GeneratedAudio>}
@@ -1,4 +1,5 @@
/** @typedef {import('./types').OnlineSpeechDenoiserConfig} OnlineSpeechDenoiserConfig */
/** @typedef {import('./types').OnlineSpeechDenoiserHandle} OnlineSpeechDenoiserHandle */
/** @typedef {import('./types').GeneratedAudio} GeneratedAudio */
/** @typedef {import('./types').AudioProcessRequest} AudioProcessRequest */
@@ -1,6 +1,7 @@
/** @typedef {import('./types').OfflinePunctuationHandle} OfflinePunctuationHandle */
/** @typedef {import('./types').OfflinePunctuationConfig} OfflinePunctuationConfig */
/** @typedef {import('./types').OnlinePunctuationConfig} OnlinePunctuationConfig */
/** @typedef {import('./types').OnlinePunctuationHandle} OnlinePunctuationHandle */
const addon = require('./addon.js');
+80
View File
@@ -0,0 +1,80 @@
/** @typedef {import('./types').LinearResamplerHandle} LinearResamplerHandle */
const addon = require('./addon.js');
/**
* A linear resampler that converts audio from one sample rate to another.
*/
class LinearResampler {
/**
* Create a linear resampler.
*
* @param {number} inputSampleRate - Input sample rate in Hz.
* @param {number} outputSampleRate - Output sample rate in Hz.
*/
constructor(inputSampleRate, outputSampleRate) {
/** @type {LinearResamplerHandle} */
this.handle =
addon.createLinearResampler(inputSampleRate, outputSampleRate);
this.inputSampleRate = inputSampleRate;
this.outputSampleRate = outputSampleRate;
}
/**
* Resample a chunk of audio samples.
*
* Call this for each chunk of input audio. For the final chunk, call
* {@link flush} instead so that any internally buffered samples are
* emitted.
*
* @param {Float32Array} samples - Input audio samples.
* @returns {Float32Array} Resampled audio samples.
*/
resample(samples) {
return addon.resampleLinear(this.handle, samples, 0);
}
/**
* Resample the final chunk of audio and flush internal buffers.
*
* This is the same as {@link resample} but sets flush=1 so that any
* remaining samples buffered inside the resampler are emitted. Call
* this once after the last chunk of input audio.
*
* @param {Float32Array} samples - The final chunk of input audio samples.
* @returns {Float32Array} Resampled audio samples including buffered tail.
*/
flush(samples) {
return addon.resampleLinear(this.handle, samples, 1);
}
/**
* Reset the resampler to its initial state, discarding any internal
* buffered samples.
*/
reset() {
addon.linearResamplerReset(this.handle);
}
/**
* Get the input sample rate.
*
* @returns {number} Input sample rate in Hz.
*/
getInputSampleRate() {
return addon.linearResamplerGetInputSampleRate(this.handle);
}
/**
* Get the output sample rate.
*
* @returns {number} Output sample rate in Hz.
*/
getOutputSampleRate() {
return addon.linearResamplerGetOutputSampleRate(this.handle);
}
}
module.exports = {
LinearResampler,
}
@@ -19,6 +19,7 @@ const kws = require('./keyword-spotter.js');
const sd = require('./non-streaming-speaker-diarization.js');
const speech_denoiser = require('./non-streaming-speech-denoiser.js');
const online_speech_denoiser = require('./online-speech-denoiser.js');
const resampler = require('./resampler.js');
module.exports = {
OnlineRecognizer : streaming_asr.OnlineRecognizer,
@@ -40,6 +41,7 @@ module.exports = {
OfflineSpeakerDiarization : sd.OfflineSpeakerDiarization,
OfflineSpeechDenoiser : speech_denoiser.OfflineSpeechDenoiser,
OnlineSpeechDenoiser : online_speech_denoiser.OnlineSpeechDenoiser,
LinearResampler : resampler.LinearResampler,
version : addon.version,
gitSha1 : addon.gitSha1,
gitDate : addon.gitDate,
@@ -2,6 +2,8 @@
/** @typedef {import('./types').SpeakerEmbeddingManagerSearchObj} SpeakerEmbeddingManagerSearchObj */
/** @typedef {import('./types').SpeakerEmbeddingManagerVerifyObj} SpeakerEmbeddingManagerVerifyObj */
/** @typedef {import('./types').SpeakerEmbeddingExtractorConfig} SpeakerEmbeddingExtractorConfig */
/** @typedef {import('./types').SpeakerEmbeddingExtractorHandle} SpeakerEmbeddingExtractorHandle */
/** @typedef {import('./types').SpeakerEmbeddingManagerHandle} SpeakerEmbeddingManagerHandle */
/** @typedef {import('./streaming-asr').OnlineStream} OnlineStream */
const addon = require('./addon.js');
@@ -1,4 +1,5 @@
/** @typedef {import('./types').SpokenLanguageIdentificationConfig} SpokenLanguageIdentificationConfig */
/** @typedef {import('./types').SpokenLanguageIdentificationHandle} SpokenLanguageIdentificationHandle */
/** @typedef {import('./non-streaming-asr').OfflineStream} OfflineStream */
const addon = require('./addon.js');
@@ -1,5 +1,7 @@
/** @typedef {import('./types').OnlineStreamObject} OnlineStreamObject */
/** @typedef {import('./types').OnlineRecognizerHandle} OnlineRecognizerHandle */
/** @typedef {import('./types').OnlineStreamHandle} OnlineStreamHandle */
/** @typedef {import('./types').DisplayHandle} DisplayHandle */
/** @typedef {import('./types').DisplayObject} DisplayObject */
/** @typedef {import('./types').OnlineRecognizerConfig} OnlineRecognizerConfig */
/** @typedef {import('./types').Waveform} Waveform */
+129 -79
View File
@@ -52,7 +52,57 @@
/**
* @typedef {Object} OfflinePunctuationHandle
* @see src/offline-punctuation.cc
* @see src/offline-punctuation.cc
*/
/**
* @typedef {Object} LinearResamplerHandle
* @see src/resampler.cc
*/
/**
* @typedef {Object} OfflineTtsHandle
* @see src/non-streaming-tts.cc
*/
/**
* @typedef {Object} OnlinePunctuationHandle
* @see src/punctuation.cc
*/
/**
* @typedef {Object} KeywordSpotterHandle
* @see src/keyword-spotter.cc
*/
/**
* @typedef {Object} SpeakerEmbeddingExtractorHandle
* @see src/speaker-identification.cc
*/
/**
* @typedef {Object} SpeakerEmbeddingManagerHandle
* @see src/speaker-identification.cc
*/
/**
* @typedef {Object} SpokenLanguageIdentificationHandle
* @see src/spoken-language-identification.cc
*/
/**
* @typedef {Object} OfflineSpeakerDiarizationHandle
* @see src/non-streaming-speaker-diarization.cc
*/
/**
* @typedef {Object} OfflineSpeechDenoiserHandle
* @see src/non-streaming-speech-denoiser.cc
*/
/**
* @typedef {Object} OnlineSpeechDenoiserHandle
* @see src/online-speech-denoiser.cc
*/
/**
@@ -234,19 +284,19 @@
* @property {number} [useInverseTextNormalization]
*/
/**
* Offline Cohere Transcribe model config
* @typedef {Object} OfflineCohereTranscribeModelConfig
* @property {string} [encoder]
* @property {string} [decoder]
* @property {string} [language]
* @property {number} [usePunct]
* @property {number} [useItn]
*/
/**
* Offline model config.
* @typedef {Object} OfflineModelConfig
/**
* Offline Cohere Transcribe model config
* @typedef {Object} OfflineCohereTranscribeModelConfig
* @property {string} [encoder]
* @property {string} [decoder]
* @property {string} [language]
* @property {number} [usePunct]
* @property {number} [useItn]
*/
/**
* Offline model config.
* @typedef {Object} OfflineModelConfig
* @property {OfflineTransducerModelConfig} [transducer]
* @property {OfflineParaformerModelConfig} [paraformer]
* @property {OfflineZipformerCtcModelConfig} [zipformerCtc]
@@ -255,14 +305,14 @@
* @property {OfflineMedAsrCtcModelConfig} [medasr]
* @property {OfflineDolphinModelConfig} [dolphin]
* @property {OfflineNeMoCtcModelConfig} [nemoCtc]
* @property {OfflineCanaryModelConfig} [canary]
* @property {OfflineWhisperModelConfig} [whisper]
* @property {OfflineFireRedAsrModelConfig} [fireRedAsr]
* @property {OfflineMoonshineModelConfig} [moonshine]
* @property {OfflineTdnnModelConfig} [tdnn]
* @property {OfflineSenseVoiceModelConfig} [senseVoice]
* @property {OfflineCohereTranscribeModelConfig} [cohereTranscribe]
* @property {string} [tokens]
* @property {OfflineCanaryModelConfig} [canary]
* @property {OfflineWhisperModelConfig} [whisper]
* @property {OfflineFireRedAsrModelConfig} [fireRedAsr]
* @property {OfflineMoonshineModelConfig} [moonshine]
* @property {OfflineTdnnModelConfig} [tdnn]
* @property {OfflineSenseVoiceModelConfig} [senseVoice]
* @property {OfflineCohereTranscribeModelConfig} [cohereTranscribe]
* @property {string} [tokens]
* @property {number} [numThreads]
* @property {boolean|number} [debug]
* @property {string} [provider]
@@ -512,24 +562,24 @@
* @property {number} [lengthScale]
*/
/**
* @typedef {Object} OfflineTtsZipvoiceModelConfig
* @property {string} [tokens]
* @property {string} [encoder]
* @property {string} [decoder]
* @property {string} [vocoder]
* @property {string} [dataDir]
* @property {string} [lexicon]
* @property {number} [featScale]
* @property {number} [tShift]
* @property {number} [targetRms]
* @property {number} [guidanceScale]
*/
/**
* @typedef {Object} OfflineTtsPocketModelConfig
* @property {string} [lmFlow]
* @property {string} [lmMain]
/**
* @typedef {Object} OfflineTtsZipvoiceModelConfig
* @property {string} [tokens]
* @property {string} [encoder]
* @property {string} [decoder]
* @property {string} [vocoder]
* @property {string} [dataDir]
* @property {string} [lexicon]
* @property {number} [featScale]
* @property {number} [tShift]
* @property {number} [targetRms]
* @property {number} [guidanceScale]
*/
/**
* @typedef {Object} OfflineTtsPocketModelConfig
* @property {string} [lmFlow]
* @property {string} [lmMain]
* @property {string} [encoder]
* @property {string} [decoder]
* @property {string} [textConditioner]
@@ -542,12 +592,12 @@
* Offline TTS model config
* @typedef {Object} OfflineTtsModelConfig
* @property {OfflineTtsVitsModelConfig} [vits]
* @property {OfflineTtsMatchaModelConfig} [matcha]
* @property {OfflineTtsKokoroModelConfig} [kokoro]
* @property {OfflineTtsKittenModelConfig} [kitten]
* @property {OfflineTtsZipvoiceModelConfig} [zipvoice]
* @property {OfflineTtsPocketModelConfig} [pocket]
*/
* @property {OfflineTtsMatchaModelConfig} [matcha]
* @property {OfflineTtsKokoroModelConfig} [kokoro]
* @property {OfflineTtsKittenModelConfig} [kitten]
* @property {OfflineTtsZipvoiceModelConfig} [zipvoice]
* @property {OfflineTtsPocketModelConfig} [pocket]
*/
/**
* Offline TTS configuration (partial, commonly used props).
@@ -559,39 +609,39 @@
* @property {string} [provider]
*/
/**
* Offline Speech Denoiser model config
* @typedef {Object} OfflineSpeechDenoiserGtcrnModelConfig
* @property {string} [model]
*/
/**
* Offline Speech Denoiser model config
* @typedef {Object} OfflineSpeechDenoiserDpdfNetModelConfig
* @property {string} [model]
*/
/**
* Offline Speech Denoiser model config
* @typedef {Object} OfflineSpeechDenoiserModelConfig
* @property {OfflineSpeechDenoiserGtcrnModelConfig} [gtcrn]
* @property {OfflineSpeechDenoiserDpdfNetModelConfig} [dpdfnet]
* @property {number} [numThreads]
* @property {boolean|number} [debug]
* @property {string} [provider]
*/
/**
* Offline Speech Denoiser configuration (partial).
* @typedef {Object} OfflineSpeechDenoiserConfig
* @property {OfflineSpeechDenoiserModelConfig} [model]
*/
/**
* Online Speech Denoiser configuration (partial).
* @typedef {Object} OnlineSpeechDenoiserConfig
* @property {OfflineSpeechDenoiserModelConfig} [model]
*/
/**
* Offline Speech Denoiser model config
* @typedef {Object} OfflineSpeechDenoiserGtcrnModelConfig
* @property {string} [model]
*/
/**
* Offline Speech Denoiser model config
* @typedef {Object} OfflineSpeechDenoiserDpdfNetModelConfig
* @property {string} [model]
*/
/**
* Offline Speech Denoiser model config
* @typedef {Object} OfflineSpeechDenoiserModelConfig
* @property {OfflineSpeechDenoiserGtcrnModelConfig} [gtcrn]
* @property {OfflineSpeechDenoiserDpdfNetModelConfig} [dpdfnet]
* @property {number} [numThreads]
* @property {boolean|number} [debug]
* @property {string} [provider]
*/
/**
* Offline Speech Denoiser configuration (partial).
* @typedef {Object} OfflineSpeechDenoiserConfig
* @property {OfflineSpeechDenoiserModelConfig} [model]
*/
/**
* Online Speech Denoiser configuration (partial).
* @typedef {Object} OnlineSpeechDenoiserConfig
* @property {OfflineSpeechDenoiserModelConfig} [model]
*/
/**
* Offline speaker segmentation (pyannote) model config
+1
View File
@@ -1,4 +1,5 @@
/** @typedef {import('./types').CircularBufferHandle} CircularBufferHandle */
/** @typedef {import('./types').VoiceActivityDetectorHandle} VoiceActivityDetectorHandle */
/** @typedef {import('./types').SpeechSegment} SpeechSegment */
/** @typedef {import('./types').VadConfig} VadConfig */
+1
View File
@@ -0,0 +1 @@
../../../harmony-os/SherpaOnnxHar/sherpa_onnx/src/main/cpp/resampler.cc
+18
View File
@@ -2706,6 +2706,24 @@ struct SherpaOnnxLinearResampler {
const SherpaOnnxLinearResampler *SherpaOnnxCreateLinearResampler(
int32_t samp_rate_in_hz, int32_t samp_rate_out_hz, float filter_cutoff_hz,
int32_t num_zeros) {
if (samp_rate_in_hz <= 0 || samp_rate_out_hz <= 0) {
SHERPA_ONNX_LOGE("Sample rates must be > 0. Given in=%d out=%d",
samp_rate_in_hz, samp_rate_out_hz);
return nullptr;
}
if (num_zeros < 0) {
SHERPA_ONNX_LOGE("num_zeros must be >= 0. Given %d", num_zeros);
return nullptr;
}
if (filter_cutoff_hz == 0) {
float min_freq = std::min(samp_rate_in_hz, samp_rate_out_hz);
filter_cutoff_hz = 0.99f * 0.5f * min_freq;
}
if (num_zeros == 0) {
num_zeros = 6;
}
SherpaOnnxLinearResampler *p = new SherpaOnnxLinearResampler;
p->impl = std::make_unique<sherpa_onnx::LinearResample>(
samp_rate_in_hz, samp_rate_out_hz, filter_cutoff_hz, num_zeros);
+10 -7
View File
@@ -3664,7 +3664,8 @@ typedef struct SherpaOnnxLinearResampler SherpaOnnxLinearResampler;
/**
* @brief Create a linear resampler.
*
* A common choice is:
* If @p filter_cutoff_hz or @p num_zeros is 0, the following defaults
* are used (same convention as alsa-play.cc):
*
* @code
* float min_freq = samp_rate_in_hz < samp_rate_out_hz ? samp_rate_in_hz
@@ -3673,12 +3674,14 @@ typedef struct SherpaOnnxLinearResampler SherpaOnnxLinearResampler;
* int32_t num_zeros = 6;
* @endcode
*
* @param samp_rate_in_hz Input sample rate in Hz.
* @param samp_rate_out_hz Output sample rate in Hz.
* @param filter_cutoff_hz Low-pass cutoff frequency in Hz.
* @param num_zeros Low-pass filter width control parameter.
* @return A newly allocated resampler. Free it with
* SherpaOnnxDestroyLinearResampler().
* @param samp_rate_in_hz Input sample rate in Hz. Must be > 0.
* @param samp_rate_out_hz Output sample rate in Hz. Must be > 0.
* @param filter_cutoff_hz Low-pass cutoff frequency in Hz. Pass 0 to use
* the default formula above. Must be >= 0.
* @param num_zeros Low-pass filter width control parameter. Pass 0 to use
* the default value of 6. Must be >= 0.
* @return A newly allocated resampler, or nullptr on invalid input. Free it
* with SherpaOnnxDestroyLinearResampler().
*/
SHERPA_ONNX_API const SherpaOnnxLinearResampler *
SherpaOnnxCreateLinearResampler(int32_t samp_rate_in_hz,