Skip to content

Commit a19f4b0

Browse files
authored
Add JavaScript (node-addon) example for ZipVoice TTS (#3342)
1 parent dbaf894 commit a19f4b0

13 files changed

Lines changed: 431 additions & 14 deletions

.github/scripts/test-nodejs-addon-npm.sh

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,20 @@ node ./test_tts_non_streaming_pocket_en_async.js
4040

4141
rm -rf sherpa-onnx-pocket-tts-int8-2026-01-26
4242

43+
echo "----------ZipVoice----------"
44+
45+
curl -SL -O https://github.com/k2-fsa/sherpa-onnx/releases/download/tts-models/sherpa-onnx-zipvoice-distill-int8-zh-en-emilia.tar.bz2
46+
tar xf sherpa-onnx-zipvoice-distill-int8-zh-en-emilia.tar.bz2
47+
rm sherpa-onnx-zipvoice-distill-int8-zh-en-emilia.tar.bz2
48+
49+
curl -SL -O https://github.com/k2-fsa/sherpa-onnx/releases/download/vocoder-models/vocos_24khz.onnx
50+
51+
node ./test_tts_non_streaming_zipvoice_zh_en.js
52+
node ./test_tts_non_streaming_zipvoice_zh_en_async.js
53+
54+
rm -rf sherpa-onnx-zipvoice-distill-int8-zh-en-emilia
55+
rm -f vocos_24khz.onnx
56+
4357
echo "----------non-streaming ASR FunASR Nano----------"
4458

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

.github/workflows/test-nodejs-addon-api.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,12 @@ on:
66
- master
77
paths:
88
- '.github/workflows/test-nodejs-addon-api.yaml'
9+
- '.github/scripts/test-nodejs-addon-npm.sh'
910
- 'cmake/**'
1011
- 'sherpa-onnx/csrc/*'
1112
- 'sherpa-onnx/c-api/*'
1213
- 'scripts/node-addon-api/**'
14+
- 'nodejs-addon-examples/**'
1315

1416
workflow_dispatch:
1517

.github/workflows/test-nodejs-addon-npm-aarch64.yaml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,13 @@ on:
66
- master
77
paths:
88
- '.github/workflows/test-nodejs-addon-npm-aarch64.yaml'
9+
- '.github/scripts/test-nodejs-addon-npm.sh'
910
- 'cmake/**'
1011
- 'sherpa-onnx/csrc/*'
1112
- 'sherpa-onnx/c-api/*'
1213
- 'scripts/node-addon-api/**'
1314
- 'scripts/node-addon-api/*.js'
14-
- 'nodejs-addon-examples/package.json'
15+
- 'nodejs-addon-examples/**'
1516

1617
workflow_dispatch:
1718

.github/workflows/test-nodejs-addon-npm-win-x86.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ on:
1111
- 'sherpa-onnx/c-api/*'
1212
- 'scripts/node-addon-api/**'
1313
- 'scripts/node-addon-api/*.js'
14-
- 'nodejs-addon-examples/package.json'
14+
- 'nodejs-addon-examples/**'
1515
- '.github/scripts/test-nodejs-addon-npm.sh'
1616

1717
workflow_dispatch:

.github/workflows/test-nodejs-addon-npm.yaml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,13 @@ on:
66
- master
77
paths:
88
- '.github/workflows/test-nodejs-addon-npm.yaml'
9+
- '.github/scripts/test-nodejs-addon-npm.sh'
910
- 'cmake/**'
1011
- 'sherpa-onnx/csrc/*'
1112
- 'sherpa-onnx/c-api/*'
1213
- 'scripts/node-addon-api/**'
1314
- 'scripts/node-addon-api/*.js'
14-
- 'nodejs-addon-examples/package.json'
15+
- 'nodejs-addon-examples/**'
1516

1617
workflow_dispatch:
1718

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,3 +178,4 @@ non-streaming-fire-red-asr-ctc-decode-files
178178
sherpa-onnx-moonshine-*-quantized-2026-02-27
179179
sherpa-onnx-supertonic-tts-int8-2026-03-06
180180
token_scores.json
181+
sherpa-onnx-zipvoice-distill-int8-zh-en-emilia

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,7 @@ We also have spaces built using WebAssembly. They are listed below:
145145
|Speech synthesis (Matcha, Chinese+English) |[Click me][wasm-hf-tts-matcha-zh-en]| [地址][wasm-ms-tts-matcha-zh-en]|
146146
|Speaker diarization |[Click me][wasm-hf-speaker-diarization]|[地址][wasm-ms-speaker-diarization]|
147147
|Voice cloning with ZipVoice (Chinese+English) |[Click me][wasm-hf-voice-cloning-zipvoice]|[地址][wasm-ms-voice-cloning-zipvoice]|
148-
|Voice cloning with Pocket TTS (English) |[Click me][wasm-hf-voice-cloning-pocket]|[地址][wasm-ms-voice-cloning-pocketzipvoice]|
148+
|Voice cloning with Pocket TTS (English) |[Click me][wasm-hf-voice-cloning-pocket]|[地址][wasm-ms-voice-cloning-pocket]|
149149

150150
</details>
151151

harmony-os/SherpaOnnxHar/sherpa_onnx/src/main/cpp/non-streaming-tts.cc

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,14 @@
3838
SHERPA_ONNX_DELETE_C_STR(c.model.kitten.voices); \
3939
SHERPA_ONNX_DELETE_C_STR(c.model.kitten.tokens); \
4040
SHERPA_ONNX_DELETE_C_STR(c.model.kitten.data_dir); \
41-
\
41+
\
42+
SHERPA_ONNX_DELETE_C_STR(c.model.zipvoice.tokens); \
43+
SHERPA_ONNX_DELETE_C_STR(c.model.zipvoice.encoder); \
44+
SHERPA_ONNX_DELETE_C_STR(c.model.zipvoice.decoder); \
45+
SHERPA_ONNX_DELETE_C_STR(c.model.zipvoice.vocoder); \
46+
SHERPA_ONNX_DELETE_C_STR(c.model.zipvoice.data_dir); \
47+
SHERPA_ONNX_DELETE_C_STR(c.model.zipvoice.lexicon); \
48+
\
4249
SHERPA_ONNX_DELETE_C_STR(c.model.kokoro.model); \
4350
SHERPA_ONNX_DELETE_C_STR(c.model.kokoro.voices); \
4451
SHERPA_ONNX_DELETE_C_STR(c.model.kokoro.tokens); \
@@ -202,6 +209,30 @@ static SherpaOnnxOfflineTtsKittenModelConfig GetOfflineTtsKittenModelConfig(
202209
return c;
203210
}
204211

212+
static SherpaOnnxOfflineTtsZipvoiceModelConfig
213+
GetOfflineTtsZipvoiceModelConfig(Napi::Object obj) {
214+
SherpaOnnxOfflineTtsZipvoiceModelConfig c;
215+
memset(&c, 0, sizeof(c));
216+
217+
if (!obj.Has("zipvoice") || !obj.Get("zipvoice").IsObject()) {
218+
return c;
219+
}
220+
221+
Napi::Object o = obj.Get("zipvoice").As<Napi::Object>();
222+
SHERPA_ONNX_ASSIGN_ATTR_STR(tokens, tokens);
223+
SHERPA_ONNX_ASSIGN_ATTR_STR(encoder, encoder);
224+
SHERPA_ONNX_ASSIGN_ATTR_STR(decoder, decoder);
225+
SHERPA_ONNX_ASSIGN_ATTR_STR(vocoder, vocoder);
226+
SHERPA_ONNX_ASSIGN_ATTR_STR(data_dir, dataDir);
227+
SHERPA_ONNX_ASSIGN_ATTR_STR(lexicon, lexicon);
228+
SHERPA_ONNX_ASSIGN_ATTR_FLOAT(feat_scale, featScale);
229+
SHERPA_ONNX_ASSIGN_ATTR_FLOAT(t_shift, tShift);
230+
SHERPA_ONNX_ASSIGN_ATTR_FLOAT(target_rms, targetRms);
231+
SHERPA_ONNX_ASSIGN_ATTR_FLOAT(guidance_scale, guidanceScale);
232+
233+
return c;
234+
}
235+
205236
static SherpaOnnxOfflineTtsPocketModelConfig GetOfflineTtsPocketModelConfig(
206237
Napi::Object obj) {
207238
SherpaOnnxOfflineTtsPocketModelConfig c;
@@ -268,6 +299,7 @@ static SherpaOnnxOfflineTtsModelConfig GetOfflineTtsModelConfig(
268299
c.matcha = GetOfflineTtsMatchaModelConfig(o);
269300
c.kokoro = GetOfflineTtsKokoroModelConfig(o);
270301
c.kitten = GetOfflineTtsKittenModelConfig(o);
302+
c.zipvoice = GetOfflineTtsZipvoiceModelConfig(o);
271303
c.pocket = GetOfflineTtsPocketModelConfig(o);
272304
c.supertonic = GetOfflineTtsSupertonicModelConfig(o);
273305

nodejs-addon-examples/README.md

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,9 @@ The following tables list the examples in this folder.
166166
|---|---|
167167
|[./test_tts_non_streaming_pocket_en.js](./test_tts_non_streaming_pocket_en.js)| Zero-shot text-to-speech with a PocketTTS English Model|
168168
|[./test_tts_non_streaming_pocket_en_async.js](./test_tts_non_streaming_pocket_en_async.js)| Zero-shot text-to-speech with a PocketTTS English Model using async JS API|
169+
|[./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|
170+
|[./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|
171+
|[./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|
169172
|[./test_tts_non_streaming_kitten_en.js](./test_tts_non_streaming_kitten_en.js)| Text-to-speech with a KittenTTS English Model|
170173
|[./test_tts_non_streaming_kokoro_en.js](./test_tts_non_streaming_kokoro_en.js)| Text-to-speech with a Kokoro English Model|
171174
|[./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|
@@ -580,6 +583,48 @@ rm sherpa-onnx-pocket-tts-int8-2026-01-26.tar.bz2
580583
node ./test_tts_non_streaming_pocket_en.js
581584
```
582585

586+
### Zero-shot text-to-speech with ZipVoice models (Chinese/English TTS, async API)
587+
588+
```bash
589+
curl -SL -O https://github.com/k2-fsa/sherpa-onnx/releases/download/tts-models/sherpa-onnx-zipvoice-distill-int8-zh-en-emilia.tar.bz2
590+
tar xf sherpa-onnx-zipvoice-distill-int8-zh-en-emilia.tar.bz2
591+
rm sherpa-onnx-zipvoice-distill-int8-zh-en-emilia.tar.bz2
592+
593+
curl -SL -O https://github.com/k2-fsa/sherpa-onnx/releases/download/vocoder-models/vocos_24khz.onnx
594+
595+
# The reference text must match the reference audio transcript.
596+
node ./test_tts_non_streaming_zipvoice_zh_en_async.js
597+
```
598+
599+
### Zero-shot text-to-speech with ZipVoice models (Chinese/English TTS, async API + playback)
600+
601+
```bash
602+
curl -SL -O https://github.com/k2-fsa/sherpa-onnx/releases/download/tts-models/sherpa-onnx-zipvoice-distill-int8-zh-en-emilia.tar.bz2
603+
tar xf sherpa-onnx-zipvoice-distill-int8-zh-en-emilia.tar.bz2
604+
rm sherpa-onnx-zipvoice-distill-int8-zh-en-emilia.tar.bz2
605+
606+
curl -SL -O https://github.com/k2-fsa/sherpa-onnx/releases/download/vocoder-models/vocos_24khz.onnx
607+
608+
# Install the playback package once.
609+
npm install speaker
610+
611+
# The reference text must match the reference audio transcript.
612+
node ./test_tts_non_streaming_zipvoice_zh_en_play_async.js
613+
```
614+
615+
### Zero-shot text-to-speech with ZipVoice models (Chinese/English TTS, sync API)
616+
617+
```bash
618+
curl -SL -O https://github.com/k2-fsa/sherpa-onnx/releases/download/tts-models/sherpa-onnx-zipvoice-distill-int8-zh-en-emilia.tar.bz2
619+
tar xf sherpa-onnx-zipvoice-distill-int8-zh-en-emilia.tar.bz2
620+
rm sherpa-onnx-zipvoice-distill-int8-zh-en-emilia.tar.bz2
621+
622+
curl -SL -O https://github.com/k2-fsa/sherpa-onnx/releases/download/vocoder-models/vocos_24khz.onnx
623+
624+
# The reference text must match the reference audio transcript.
625+
node ./test_tts_non_streaming_zipvoice_zh_en.js
626+
```
627+
583628
### Text-to-speech with KittenTTS models (English TTS)
584629

585630
```bash
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
// Copyright (c) 2026 Xiaomi Corporation
2+
const sherpa_onnx = require('sherpa-onnx-node');
3+
4+
// please refer to
5+
// https://k2-fsa.github.io/sherpa/onnx/tts/zipvoice.html
6+
// to download model files
7+
function createOfflineTts() {
8+
const config = {
9+
model: {
10+
zipvoice: {
11+
tokens: './sherpa-onnx-zipvoice-distill-int8-zh-en-emilia/tokens.txt',
12+
encoder:
13+
'./sherpa-onnx-zipvoice-distill-int8-zh-en-emilia/encoder.int8.onnx',
14+
decoder:
15+
'./sherpa-onnx-zipvoice-distill-int8-zh-en-emilia/decoder.int8.onnx',
16+
vocoder: './vocos_24khz.onnx',
17+
dataDir:
18+
'./sherpa-onnx-zipvoice-distill-int8-zh-en-emilia/espeak-ng-data',
19+
lexicon: './sherpa-onnx-zipvoice-distill-int8-zh-en-emilia/lexicon.txt',
20+
},
21+
debug: true,
22+
numThreads: 2,
23+
provider: 'cpu',
24+
},
25+
maxNumSentences: 1,
26+
};
27+
return new sherpa_onnx.OfflineTts(config);
28+
}
29+
30+
const tts = createOfflineTts();
31+
const text =
32+
'小米的价值观是真诚, 热爱. 真诚,就是不欺人也不自欺. 热爱, 就是全心投入并享受其中.';
33+
const referenceText =
34+
'那还是三十六年前, 一九八七年. 我呢考上了武汉大学的计算机系.';
35+
const referenceAudioFilename =
36+
'./sherpa-onnx-zipvoice-distill-int8-zh-en-emilia/test_wavs/leijun-1.wav';
37+
const referenceWave = sherpa_onnx.readWave(referenceAudioFilename);
38+
39+
const generationConfig = new sherpa_onnx.GenerationConfig({
40+
speed: 1.0,
41+
referenceAudio: referenceWave.samples,
42+
referenceSampleRate: referenceWave.sampleRate,
43+
referenceText,
44+
numSteps: 4,
45+
extra: {min_char_in_sentence: 10},
46+
});
47+
48+
let start = Date.now();
49+
const audio = tts.generate({text, generationConfig});
50+
51+
let stop = Date.now();
52+
const elapsed_seconds = (stop - start) / 1000;
53+
const duration = audio.samples.length / audio.sampleRate;
54+
const real_time_factor = elapsed_seconds / duration;
55+
console.log('Wave duration', duration.toFixed(3), 'seconds');
56+
console.log('Elapsed', elapsed_seconds.toFixed(3), 'seconds');
57+
console.log(
58+
`RTF = ${elapsed_seconds.toFixed(3)}/${duration.toFixed(3)} =`,
59+
real_time_factor.toFixed(3));
60+
61+
const filename = 'test-zipvoice-zh-en.wav';
62+
sherpa_onnx.writeWave(
63+
filename, {samples: audio.samples, sampleRate: audio.sampleRate});
64+
65+
console.log(`Saved to ${filename}`);

0 commit comments

Comments
 (0)