Add JavaScript (WebAssembly) API for PocketTTS (#3163)

This pull request significantly expands the project's capabilities by integrating PocketTTS, a zero-shot Text-to-Speech model, into its JavaScript (WebAssembly) API. The changes facilitate advanced TTS generation with configurable options and improve the underlying WebAssembly infrastructure by exposing more memory manipulation methods. This allows for greater control and flexibility when working with speech synthesis and recognition models in JavaScript environments.
This commit is contained in:
Fangjun Kuang authored and GitHub committed 2026-02-10 10:20:46 +08:00
1 parent 5c4348e049
commit 3a6758261c
15 files changed
+374 -18

No files matched your search

+36
View File
@@ -54,6 +54,42 @@ node ./test-offline-speaker-diarization.js
In the following, we demonstrate how to run text-to-speech.
## ./test-offline-tts-pocket-en.js
[./test-offline-tts-pocket-en.js](./test-offline-tts-pocket-en.js)
shows how to use PocketTTS for Zero-shot TTS.
You can use the following command to run it:
```
curl -SL -O https://github.com/k2-fsa/sherpa-onnx/releases/download/tts-models/sherpa-onnx-pocket-tts-int8-2026-01-26.tar.bz2
tar xf sherpa-onnx-pocket-tts-int8-2026-01-26.tar.bz2
rm sherpa-onnx-pocket-tts-int8-2026-01-26.tar.bz2
node ./test-offline-tts-pocket-en.js
```
Note: Since our current version of onnxruntime is 1.17.1, you need to run
```python3
# Switch to ./sherpa-onnx-pocket-tts-int8-2026-01-26 and run
import onnx
for filename in [
"./lm_flow.int8.onnx",
"./lm_main.int8.onnx",
"./encoder.onnx",
"./decoder.int8.onnx",
"./text_conditioner.onnx",
]:
model = onnx.load(filename)
print("old", model.ir_version)
model.ir_version = 9
print("new", model.ir_version)
onnx.save(model, filename)
```
to modify the models.
## ./test-offline-tts-kitten-en.js
[./test-offline-tts-kitten-en.js](./test-offline-tts-kitten-en.js) shows how to use
@@ -2,7 +2,6 @@
//
const fs = require('fs');
const {Readable} = require('stream');
const wav = require('wav');
const sherpa_onnx = require('sherpa-onnx');
@@ -0,0 +1,51 @@
// Copyright (c) 2026 Xiaomi Corporation (authors: Fangjun Kuang)
const sherpa_onnx = require('sherpa-onnx');
function createOfflineTts() {
let pocket = {
lmFlow: './sherpa-onnx-pocket-tts-int8-2026-01-26/lm_flow.int8.onnx',
lmMain: './sherpa-onnx-pocket-tts-int8-2026-01-26/lm_main.int8.onnx',
encoder: './sherpa-onnx-pocket-tts-int8-2026-01-26/encoder.onnx',
decoder: './sherpa-onnx-pocket-tts-int8-2026-01-26/decoder.int8.onnx',
textConditioner:
'./sherpa-onnx-pocket-tts-int8-2026-01-26/text_conditioner.onnx',
vocabJson: './sherpa-onnx-pocket-tts-int8-2026-01-26/vocab.json',
tokenScoresJson:
'./sherpa-onnx-pocket-tts-int8-2026-01-26/token_scores.json',
};
let offlineTtsModelConfig = {
offlineTtsPocketModelConfig: pocket,
numThreads: 1,
debug: 1, // set it to 1 to see verbose logs; 0 to disable logs
provider: 'cpu',
};
let offlineTtsConfig = {
offlineTtsModelConfig: offlineTtsModelConfig,
maxNumSentences: 1,
};
return sherpa_onnx.createOfflineTts(offlineTtsConfig);
}
const referenceWaveFilename =
'./sherpa-onnx-pocket-tts-int8-2026-01-26/test_wavs/bria.wav';
const wave = sherpa_onnx.readWave(referenceWaveFilename);
const generationConfig = {
silenceScale: 0.2,
referenceAudio: wave.samples,
referenceSampleRate: wave.sampleRate,
numSteps: 5,
extra: {max_reference_audio_len: 12}
};
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 audio = tts.generateWithConfig(text, generationConfig);
tts.save('./test-pocket-en.wav', audio);
console.log('Saved to test-pocket-en.wav successfully.');
tts.free();
+1 -1
View File
@@ -37,7 +37,7 @@ set(MY_FLAGS " -s FORCE_FILESYSTEM=1 -s INITIAL_MEMORY=512MB -s ALLOW_MEMORY_GRO
string(APPEND MY_FLAGS " -sSTACK_SIZE=10485760 ") # 10MB
string(APPEND MY_FLAGS " -sEXPORTED_FUNCTIONS=[_CopyHeap,_malloc,_free,${all_exported_functions}] ")
string(APPEND MY_FLAGS "--preload-file ${CMAKE_CURRENT_SOURCE_DIR}/assets@. ")
string(APPEND MY_FLAGS " -sEXPORTED_RUNTIME_METHODS=['ccall','stringToUTF8','setValue','getValue','lengthBytesUTF8','UTF8ToString'] ")
string(APPEND MY_FLAGS " -sEXPORTED_RUNTIME_METHODS=['ccall','stringToUTF8','setValue','getValue','lengthBytesUTF8','UTF8ToString','HEAPU8','HEAP16','HEAP32','HEAPF32','HEAPF64'] ")
message(STATUS "MY_FLAGS: ${MY_FLAGS}")
+23 -3
View File
@@ -849,13 +849,15 @@ function initSherpaOnnxOfflineFunAsrNanoModelConfig(config, Module) {
1;
const userPromptLen =
Module.lengthBytesUTF8(config.userPrompt || '语音转写:') + 1;
const languageLen = Module.lengthBytesUTF8(config.language || '') + 1;
const hotwordsLen = Module.lengthBytesUTF8(config.hotwords || '') + 1;
const n = encoderAdaptorLen + llmLen + embeddingLen + tokenizerLen +
systemPromptLen + userPromptLen;
systemPromptLen + userPromptLen + languageLen + hotwordsLen;
const buffer = Module._malloc(n);
const len = 10 * 4; // 6 pointers + 2 int + 2 float
const len = 13 * 4; // 8 pointers + 3 int + 2 float
const ptr = Module._malloc(len);
let offset = 0;
@@ -881,6 +883,12 @@ function initSherpaOnnxOfflineFunAsrNanoModelConfig(config, Module) {
config.userPrompt || '语音转写:', buffer + offset, userPromptLen);
offset += userPromptLen;
Module.stringToUTF8(config.language || '', buffer + offset, languageLen);
offset += languageLen;
Module.stringToUTF8(config.hotwords || '', buffer + offset, hotwordsLen);
offset += hotwordsLen;
offset = 0;
Module.setValue(ptr + 0 * 4, buffer + offset, 'i8*');
offset += encoderAdaptorLen;
@@ -904,6 +912,11 @@ function initSherpaOnnxOfflineFunAsrNanoModelConfig(config, Module) {
Module.setValue(ptr + 7 * 4, config.temperature || 1e-6, 'float');
Module.setValue(ptr + 8 * 4, config.topP || 0.8, 'float');
Module.setValue(ptr + 9 * 4, config.seed || 42, 'i32');
Module.setValue(ptr + 10 * 4, buffer + offset, 'i8*');
offset += languageLen;
Module.setValue(ptr + 11 * 4, config.itn || 0, 'i32');
Module.setValue(ptr + 12 * 4, buffer + offset, 'i8*');
offset += hotwordsLen;
return {
buffer: buffer,
@@ -921,7 +934,7 @@ function initSherpaOnnxOfflineWhisperModelConfig(config, Module) {
const n = encoderLen + decoderLen + languageLen + taskLen;
const buffer = Module._malloc(n);
const len = 5 * 4; // 4 pointers + 1 int32
const len = 7 * 4; // 4 pointers + 3 int32
const ptr = Module._malloc(len);
let offset = 0;
@@ -950,6 +963,8 @@ function initSherpaOnnxOfflineWhisperModelConfig(config, Module) {
offset += taskLen;
Module.setValue(ptr + 16, config.tailPaddings || 2000, 'i32');
Module.setValue(ptr + 20, config.enableTokenTimestamps || 0, 'i32');
Module.setValue(ptr + 24, config.enableSegmentTimestamps || 0, 'i32');
return {
buffer: buffer,
@@ -1220,6 +1235,9 @@ function initSherpaOnnxOfflineModelConfig(config, Module) {
temperature: 1e-6,
topP: 0.8,
seed: 42,
language: '',
itn: 0,
hotwords: '',
};
}
@@ -1230,6 +1248,8 @@ function initSherpaOnnxOfflineModelConfig(config, Module) {
language: '',
task: '',
tailPaddings: -1,
enableTokenTimestamps: 0,
enableSegmentTimestamps: 0,
};
}
+1 -1
View File
@@ -31,7 +31,7 @@ set(MY_FLAGS "-s FORCE_FILESYSTEM=1 -s INITIAL_MEMORY=512MB -s ALLOW_MEMORY_GROW
string(APPEND MY_FLAGS " -sSTACK_SIZE=10485760 ")
string(APPEND MY_FLAGS " -sEXPORTED_FUNCTIONS=[_CopyHeap,_malloc,_free,${all_exported_functions}] ")
string(APPEND MY_FLAGS "--preload-file ${CMAKE_CURRENT_SOURCE_DIR}/assets@. ")
string(APPEND MY_FLAGS " -sEXPORTED_RUNTIME_METHODS=['ccall','stringToUTF8','setValue','getValue','lengthBytesUTF8','UTF8ToString'] ")
string(APPEND MY_FLAGS " -sEXPORTED_RUNTIME_METHODS=['ccall','stringToUTF8','setValue','getValue','lengthBytesUTF8','UTF8ToString','HEAPU8','HEAP16','HEAP32','HEAPF32','HEAPF64'] ")
message(STATUS "MY_FLAGS: ${MY_FLAGS}")
+3 -2
View File
@@ -10,6 +10,7 @@ set(exported_functions
SherpaOnnxDestroyOfflineTtsGeneratedAudio
SherpaOnnxOfflineTtsGenerate
SherpaOnnxOfflineTtsGenerateWithCallback
SherpaOnnxOfflineTtsGenerateWithConfig
SherpaOnnxOfflineTtsNumSpeakers
SherpaOnnxOfflineTtsSampleRate
SherpaOnnxWriteWave
@@ -112,9 +113,9 @@ include_directories(${CMAKE_SOURCE_DIR})
set(MY_FLAGS " -s FORCE_FILESYSTEM=1 -s INITIAL_MEMORY=512MB -s ALLOW_MEMORY_GROWTH=1")
string(APPEND MY_FLAGS " -sSTACK_SIZE=10485760 ") # 10MB
string(APPEND MY_FLAGS " -sEXPORTED_FUNCTIONS=[_CopyHeap,_malloc,_free,${all_exported_functions}] ")
string(APPEND MY_FLAGS " -sEXPORTED_RUNTIME_METHODS=['ccall','stringToUTF8','setValue','getValue'] ")
string(APPEND MY_FLAGS " -sNODERAWFS=1 ")
string(APPEND MY_FLAGS " -sEXPORTED_RUNTIME_METHODS=['ccall','stringToUTF8','setValue','getValue','lengthBytesUTF8','UTF8ToString'] ")
string(APPEND MY_FLAGS " -sEXPORTED_RUNTIME_METHODS=['ccall','stringToUTF8','setValue','getValue','lengthBytesUTF8','UTF8ToString','HEAPU8','HEAP16','HEAP32','HEAPF32','HEAPF64'] ")
string(APPEND MY_FLAGS " -sMODULARIZE=1 -sWASM_ASYNC_COMPILATION=0 ")
message(STATUS "MY_FLAGS: ${MY_FLAGS}")
+2 -2
View File
@@ -17,10 +17,10 @@ static_assert(sizeof(SherpaOnnxOfflineZipformerCtcModelConfig) == 4, "");
static_assert(sizeof(SherpaOnnxOfflineWenetCtcModelConfig) == 4, "");
static_assert(sizeof(SherpaOnnxOfflineOmnilingualAsrCtcModelConfig) == 4, "");
static_assert(sizeof(SherpaOnnxOfflineMedAsrCtcModelConfig) == 4, "");
static_assert(sizeof(SherpaOnnxOfflineFunASRNanoModelConfig) == 10 * 4, "");
static_assert(sizeof(SherpaOnnxOfflineFunASRNanoModelConfig) == 13 * 4, "");
static_assert(sizeof(SherpaOnnxOfflineDolphinModelConfig) == 4, "");
static_assert(sizeof(SherpaOnnxOfflineNemoEncDecCtcModelConfig) == 4, "");
static_assert(sizeof(SherpaOnnxOfflineWhisperModelConfig) == 5 * 4, "");
static_assert(sizeof(SherpaOnnxOfflineWhisperModelConfig) == 7 * 4, "");
static_assert(sizeof(SherpaOnnxOfflineFireRedAsrModelConfig) == 2 * 4, "");
static_assert(sizeof(SherpaOnnxOfflineMoonshineModelConfig) == 4 * 4, "");
static_assert(sizeof(SherpaOnnxOfflineTdnnModelConfig) == 4, "");
+1 -1
View File
@@ -31,7 +31,7 @@ set(MY_FLAGS " -s FORCE_FILESYSTEM=1 -s INITIAL_MEMORY=512MB -s ALLOW_MEMORY_GRO
string(APPEND MY_FLAGS " -sSTACK_SIZE=10485760 ") # 10MB
string(APPEND MY_FLAGS " -sEXPORTED_FUNCTIONS=[_CopyHeap,_malloc,_free,${all_exported_functions}] ")
string(APPEND MY_FLAGS "--preload-file ${CMAKE_CURRENT_SOURCE_DIR}/assets@. ")
string(APPEND MY_FLAGS " -sEXPORTED_RUNTIME_METHODS=['ccall','stringToUTF8','setValue','getValue','lengthBytesUTF8','UTF8ToString'] ")
string(APPEND MY_FLAGS " -sEXPORTED_RUNTIME_METHODS=['ccall','stringToUTF8','setValue','getValue','lengthBytesUTF8','UTF8ToString','HEAPU8','HEAP16','HEAP32','HEAPF32','HEAPF64'] ")
message(STATUS "MY_FLAGS: ${MY_FLAGS}")
+1 -1
View File
@@ -30,7 +30,7 @@ set(MY_FLAGS " -s FORCE_FILESYSTEM=1 -s INITIAL_MEMORY=128MB -s ALLOW_MEMORY_GRO
string(APPEND MY_FLAGS " -sSTACK_SIZE=10485760 ") # 10MB
string(APPEND MY_FLAGS " -sEXPORTED_FUNCTIONS=[_CopyHeap,_malloc,_free,${all_exported_functions}] ")
string(APPEND MY_FLAGS "--preload-file ${CMAKE_CURRENT_SOURCE_DIR}/assets@. ")
string(APPEND MY_FLAGS " -sEXPORTED_RUNTIME_METHODS=['ccall','stringToUTF8','setValue','getValue','lengthBytesUTF8','UTF8ToString'] ")
string(APPEND MY_FLAGS " -sEXPORTED_RUNTIME_METHODS=['ccall','stringToUTF8','setValue','getValue','lengthBytesUTF8','UTF8ToString','HEAPU8','HEAP16','HEAP32','HEAPF32','HEAPF64'] ")
message(STATUS "MY_FLAGS: ${MY_FLAGS}")
+2 -1
View File
@@ -13,6 +13,7 @@ set(exported_functions
SherpaOnnxDestroyOfflineTtsGeneratedAudio
SherpaOnnxOfflineTtsGenerate
SherpaOnnxOfflineTtsGenerateWithCallback
SherpaOnnxOfflineTtsGenerateWithConfig
SherpaOnnxOfflineTtsNumSpeakers
SherpaOnnxOfflineTtsSampleRate
SherpaOnnxWriteWave
@@ -29,7 +30,7 @@ set(MY_FLAGS " -s FORCE_FILESYSTEM=1 -s INITIAL_MEMORY=512MB -s ALLOW_MEMORY_GRO
string(APPEND MY_FLAGS " -sSTACK_SIZE=10485760 ") # 10MB
string(APPEND MY_FLAGS " -sEXPORTED_FUNCTIONS=[_CopyHeap,_malloc,_free,${all_exported_functions}] ")
string(APPEND MY_FLAGS "--preload-file ${CMAKE_CURRENT_SOURCE_DIR}/assets@. ")
string(APPEND MY_FLAGS " -sEXPORTED_RUNTIME_METHODS=['ccall','stringToUTF8','setValue','getValue','lengthBytesUTF8','UTF8ToString'] ")
string(APPEND MY_FLAGS " -sEXPORTED_RUNTIME_METHODS=['ccall','stringToUTF8','setValue','getValue','lengthBytesUTF8','UTF8ToString','HEAPU8','HEAP16','HEAP32','HEAPF32','HEAPF64'] ")
message(STATUS "MY_FLAGS: ${MY_FLAGS}")
+235 -2
View File
@@ -24,6 +24,10 @@ function freeConfig(config, Module) {
freeConfig(config.zipvoice, Module)
}
if ('pocket' in config) {
freeConfig(config.pocket, Module)
}
Module._free(config.ptr);
}
@@ -336,6 +340,79 @@ function initSherpaOnnxOfflineTtsZipVoiceModelConfig(config, Module) {
};
}
function initSherpaOnnxOfflineTtsPocketModelConfig(config, Module) {
const lmFlowLen = Module.lengthBytesUTF8(config.lmFlow || '') + 1;
const lmMainLen = Module.lengthBytesUTF8(config.lmMain || '') + 1;
const encoderLen = Module.lengthBytesUTF8(config.encoder || '') + 1;
const decoderLen = Module.lengthBytesUTF8(config.decoder || '') + 1;
const textConditionerLen =
Module.lengthBytesUTF8(config.textConditioner || '') + 1;
const vocabJsonLen = Module.lengthBytesUTF8(config.vocabJson || '') + 1;
const tokenScoresJsonLen =
Module.lengthBytesUTF8(config.tokenScoresJson || '') + 1;
const n = lmFlowLen + lmMainLen + encoderLen + decoderLen +
textConditionerLen + vocabJsonLen + tokenScoresJsonLen;
const buffer = Module._malloc(n);
const len = 7 * 4;
const ptr = Module._malloc(len);
let offset = 0;
Module.stringToUTF8(config.lmFlow || '', buffer + offset, lmFlowLen);
offset += lmFlowLen;
Module.stringToUTF8(config.lmMain || '', buffer + offset, lmMainLen);
offset += lmMainLen;
Module.stringToUTF8(config.encoder || '', buffer + offset, encoderLen);
offset += encoderLen;
Module.stringToUTF8(config.decoder || '', buffer + offset, decoderLen);
offset += decoderLen;
Module.stringToUTF8(
config.textConditioner || '', buffer + offset, textConditionerLen);
offset += textConditionerLen;
Module.stringToUTF8(config.vocabJson || '', buffer + offset, vocabJsonLen);
offset += vocabJsonLen;
Module.stringToUTF8(
config.tokenScoresJson || '', buffer + offset, tokenScoresJsonLen);
offset += tokenScoresJsonLen;
offset = 0;
Module.setValue(ptr + 0 * 4, buffer + offset, 'i8*');
offset += lmFlowLen;
Module.setValue(ptr + 1 * 4, buffer + offset, 'i8*');
offset += lmMainLen;
Module.setValue(ptr + 2 * 4, buffer + offset, 'i8*');
offset += encoderLen;
Module.setValue(ptr + 3 * 4, buffer + offset, 'i8*');
offset += decoderLen;
Module.setValue(ptr + 4 * 4, buffer + offset, 'i8*');
offset += textConditionerLen;
Module.setValue(ptr + 5 * 4, buffer + offset, 'i8*');
offset += vocabJsonLen;
Module.setValue(ptr + 6 * 4, buffer + offset, 'i8*');
offset += tokenScoresJsonLen;
return {
buffer: buffer,
ptr: ptr,
len: len,
};
}
function initSherpaOnnxOfflineTtsModelConfig(config, Module) {
if (!('offlineTtsVitsModelConfig' in config)) {
config.offlineTtsVitsModelConfig = {
@@ -397,6 +474,18 @@ function initSherpaOnnxOfflineTtsModelConfig(config, Module) {
};
}
if (!('offlineTtsPocketModelConfig' in config)) {
config.offlineTtsPocketModelConfig = {
lmFlow: '',
lmMain: '',
encoder: '',
decoder: '',
textConditioner: '',
vocabJson: '',
tokenScoresJson: '',
};
}
const vitsModelConfig = initSherpaOnnxOfflineTtsVitsModelConfig(
config.offlineTtsVitsModelConfig, Module);
@@ -413,9 +502,12 @@ function initSherpaOnnxOfflineTtsModelConfig(config, Module) {
const zipVoiceModelConfig = initSherpaOnnxOfflineTtsZipVoiceModelConfig(
config.offlineTtsZipVoiceModelConfig, Module);
const pocketModelConfig = initSherpaOnnxOfflineTtsPocketModelConfig(
config.offlineTtsPocketModelConfig, Module);
const len = vitsModelConfig.len + matchaModelConfig.len +
kokoroModelConfig.len + kittenModelConfig.len + zipVoiceModelConfig.len +
3 * 4;
pocketModelConfig.len + 3 * 4;
const ptr = Module._malloc(len);
@@ -448,6 +540,9 @@ function initSherpaOnnxOfflineTtsModelConfig(config, Module) {
zipVoiceModelConfig.ptr, zipVoiceModelConfig.len, ptr + offset);
offset += zipVoiceModelConfig.len;
Module._CopyHeap(pocketModelConfig.ptr, pocketModelConfig.len, ptr + offset);
offset += pocketModelConfig.len;
return {
buffer: buffer,
ptr: ptr,
@@ -457,6 +552,7 @@ function initSherpaOnnxOfflineTtsModelConfig(config, Module) {
kokoro: kokoroModelConfig,
kitten: kittenModelConfig,
zipvoice: zipVoiceModelConfig,
pocket: pocketModelConfig,
};
}
@@ -497,9 +593,107 @@ function initSherpaOnnxOfflineTtsConfig(config, Module) {
};
}
/*
const genConfig = {
silenceScale: 0.2,
speed: 1.0,
sid: 1,
referenceAudio: myFloat32Array, // optional
referenceSampleRate: 16000, // used if referenceAudio is required
referenceText: "Hello world", // optional
numSteps: 5, // optional
extra: { bar: "ok", foo: 0.8, foobar: 10}
};
*/
// Allocate a SherpaOnnxGenerationConfig in WASM
function initSherpaOnnxGenerationConfig(config, Module) {
const len = 9 * 4;
const ptr = Module._malloc(len);
// Zero-init for safety
Module.HEAPU8.fill(0, ptr, ptr + len);
// float silence_scale
Module.setValue(ptr + 0 * 4, config.silenceScale || 0.2, 'float');
// float speed
Module.setValue(ptr + 1 * 4, config.speed || 1.0, 'float');
// int32_t sid
Module.setValue(ptr + 2 * 4, config.sid || 0, 'i32');
// const float* reference_audio
let referenceAudioPtr = 0;
if (config.referenceAudio && config.referenceAudio.length > 0) {
referenceAudioPtr = Module._malloc(config.referenceAudio.length * 4);
Module.HEAPF32.set(config.referenceAudio, referenceAudioPtr / 4);
}
Module.setValue(ptr + 3 * 4, referenceAudioPtr, 'i8*');
// int32_t reference_audio_len
Module.setValue(
ptr + 4 * 4, config.referenceAudio ? config.referenceAudio.length : 0,
'i32');
// int32_t reference_sample_rate
Module.setValue(ptr + 5 * 4, config.referenceSampleRate || 0, 'i32');
// const char* reference_text
let referenceTextPtr = 0;
if (config.referenceText) {
const textLen = Module.lengthBytesUTF8(config.referenceText) + 1;
referenceTextPtr = Module._malloc(textLen);
Module.stringToUTF8(config.referenceText, referenceTextPtr, textLen);
}
Module.setValue(ptr + 6 * 4, referenceTextPtr, 'i8*');
// int32_t num_steps
Module.setValue(ptr + 7 * 4, config.numSteps || 5, 'i32');
// const char* extra (JSON string)
let extraPtr = 0;
let extraStr = null;
if (config.extra) {
if (typeof config.extra === 'object') {
extraStr = JSON.stringify(config.extra);
} else if (typeof config.extra === 'string') {
extraStr = config.extra;
}
}
if (extraStr !== null) {
const extraLen = Module.lengthBytesUTF8(extraStr) + 1;
extraPtr = Module._malloc(extraLen);
Module.stringToUTF8(extraStr, extraPtr, extraLen);
}
Module.setValue(ptr + 8 * 4, extraPtr, 'i8*');
return {
ptr,
referenceAudioPtr,
referenceTextPtr,
extraPtr,
};
}
// Free the memory allocated for a SherpaOnnxGenerationConfig
function freeSherpaOnnxGenerationConfig(cfg, Module) {
if (!cfg) return;
if (cfg.referenceAudioPtr) Module._free(cfg.referenceAudioPtr);
if (cfg.referenceTextPtr) Module._free(cfg.referenceTextPtr);
if (cfg.extraPtr) Module._free(cfg.extraPtr);
if (cfg.ptr) Module._free(cfg.ptr);
}
class OfflineTts {
constructor(configObj, Module) {
console.log(configObj)
const config = initSherpaOnnxOfflineTtsConfig(configObj, Module)
const handle = Module._SherpaOnnxCreateOfflineTts(config.ptr);
@@ -541,6 +735,45 @@ class OfflineTts {
this.Module._SherpaOnnxDestroyOfflineTtsGeneratedAudio(h);
return {samples: samples, sampleRate: sampleRate};
}
generateWithConfig(text, genConfig) {
const cfgWasm = initSherpaOnnxGenerationConfig(genConfig, this.Module);
const textLen = this.Module.lengthBytesUTF8(text) + 1;
const textPtr = this.Module._malloc(textLen);
this.Module.stringToUTF8(text, textPtr, textLen);
const audioPtr = this.Module._SherpaOnnxOfflineTtsGenerateWithConfig(
this.handle, textPtr, cfgWasm.ptr,
0, // callback
0 // callback arg
);
if (!audioPtr) {
this.Module._free(textPtr);
freeSherpaOnnxGenerationConfig(cfgWasm, this.Module);
throw new Error('Failed to generate audio');
}
const samplesPtr = this.Module.HEAP32[audioPtr / 4]; // float* samples
const numSamples =
this.Module.HEAP32[audioPtr / 4 + 1]; // int32 num_samples
const sampleRate =
this.Module.HEAP32[audioPtr / 4 + 2]; // int32 sample_rate
// 5️⃣ Copy samples to Float32Array
const samples = new Float32Array(numSamples);
for (let i = 0; i < numSamples; i++) {
samples[i] = this.Module.HEAPF32[samplesPtr / 4 + i];
}
this.Module._SherpaOnnxDestroyOfflineTtsGeneratedAudio(audioPtr);
this.Module._free(textPtr);
freeSherpaOnnxGenerationConfig(cfgWasm, this.Module);
return {samples, sampleRate};
}
save(filename, audio) {
const samples = audio.samples;
const sampleRate = audio.sampleRate;
+16 -1
View File
@@ -18,17 +18,22 @@ static_assert(sizeof(SherpaOnnxOfflineTtsMatchaModelConfig) == 8 * 4, "");
static_assert(sizeof(SherpaOnnxOfflineTtsKokoroModelConfig) == 8 * 4, "");
static_assert(sizeof(SherpaOnnxOfflineTtsKittenModelConfig) == 5 * 4, "");
static_assert(sizeof(SherpaOnnxOfflineTtsZipvoiceModelConfig) == 10 * 4, "");
static_assert(sizeof(SherpaOnnxOfflineTtsPocketModelConfig) == 7 * 4, "");
static_assert(sizeof(SherpaOnnxOfflineTtsModelConfig) ==
sizeof(SherpaOnnxOfflineTtsVitsModelConfig) +
sizeof(SherpaOnnxOfflineTtsMatchaModelConfig) +
sizeof(SherpaOnnxOfflineTtsKokoroModelConfig) + 3 * 4 +
sizeof(SherpaOnnxOfflineTtsKittenModelConfig) +
sizeof(SherpaOnnxOfflineTtsZipvoiceModelConfig),
sizeof(SherpaOnnxOfflineTtsZipvoiceModelConfig) +
sizeof(SherpaOnnxOfflineTtsPocketModelConfig),
"");
static_assert(sizeof(SherpaOnnxOfflineTtsConfig) ==
sizeof(SherpaOnnxOfflineTtsModelConfig) + 4 * 4,
"");
static_assert(sizeof(SherpaOnnxGenerationConfig) == 9 * 4, "");
void MyPrint(SherpaOnnxOfflineTtsConfig *tts_config) {
auto tts_model_config = &tts_config->model;
auto vits_model_config = &tts_model_config->vits;
@@ -36,6 +41,7 @@ void MyPrint(SherpaOnnxOfflineTtsConfig *tts_config) {
auto kokoro = &tts_model_config->kokoro;
auto kitten = &tts_model_config->kitten;
auto zipvoice = &tts_model_config->zipvoice;
auto pocket = &tts_model_config->pocket;
fprintf(stdout, "----------vits model config----------\n");
fprintf(stdout, "model: %s\n", vits_model_config->model);
fprintf(stdout, "lexicon: %s\n", vits_model_config->lexicon);
@@ -85,6 +91,15 @@ void MyPrint(SherpaOnnxOfflineTtsConfig *tts_config) {
fprintf(stdout, "target_rms: %.3f\n", zipvoice->target_rms);
fprintf(stdout, "guidance_scale: %.3f\n", zipvoice->guidance_scale);
fprintf(stdout, "----------pocketTTS model config----------\n");
fprintf(stdout, "lm_flow: %s\n", pocket->lm_flow);
fprintf(stdout, "lm_main: %s\n", pocket->lm_main);
fprintf(stdout, "encoder: %s\n", pocket->encoder);
fprintf(stdout, "decoder: %s\n", pocket->decoder);
fprintf(stdout, "text_conditioner: %s\n", pocket->text_conditioner);
fprintf(stdout, "vocab_json: %s\n", pocket->vocab_json);
fprintf(stdout, "token_scores_json: %s\n", pocket->token_scores_json);
fprintf(stdout, "----------tts model config----------\n");
fprintf(stdout, "num threads: %d\n", tts_model_config->num_threads);
fprintf(stdout, "debug: %d\n", tts_model_config->debug);
+1 -1
View File
@@ -54,7 +54,7 @@ set(MY_FLAGS " -s FORCE_FILESYSTEM=1 -s INITIAL_MEMORY=512MB -s ALLOW_MEMORY_GRO
string(APPEND MY_FLAGS " -sSTACK_SIZE=10485760 ") # 10MB
string(APPEND MY_FLAGS " -sEXPORTED_FUNCTIONS=[_CopyHeap,_malloc,_free,${all_exported_functions}] ")
string(APPEND MY_FLAGS "--preload-file ${CMAKE_CURRENT_SOURCE_DIR}/assets@. ")
string(APPEND MY_FLAGS " -sEXPORTED_RUNTIME_METHODS=['ccall','stringToUTF8','setValue','getValue','lengthBytesUTF8','UTF8ToString'] ")
string(APPEND MY_FLAGS " -sEXPORTED_RUNTIME_METHODS=['ccall','stringToUTF8','setValue','getValue','lengthBytesUTF8','UTF8ToString','HEAPU8','HEAP16','HEAP32','HEAPF32','HEAPF64'] ")
message(STATUS "MY_FLAGS: ${MY_FLAGS}")
+1 -1
View File
@@ -43,7 +43,7 @@ set(MY_FLAGS " -s FORCE_FILESYSTEM=1 -s INITIAL_MEMORY=64MB -s ALLOW_MEMORY_GROW
string(APPEND MY_FLAGS " -sSTACK_SIZE=10485760 ") # 10MB
string(APPEND MY_FLAGS " -sEXPORTED_FUNCTIONS=[_CopyHeap,_malloc,_free,${all_exported_functions}] ")
string(APPEND MY_FLAGS "--preload-file ${CMAKE_CURRENT_SOURCE_DIR}/assets@. ")
string(APPEND MY_FLAGS " -sEXPORTED_RUNTIME_METHODS=['ccall','stringToUTF8','setValue','getValue','lengthBytesUTF8','UTF8ToString'] ")
string(APPEND MY_FLAGS " -sEXPORTED_RUNTIME_METHODS=['ccall','stringToUTF8','setValue','getValue','lengthBytesUTF8','UTF8ToString','HEAPU8','HEAP16','HEAP32','HEAPF32','HEAPF64'] ")
message(STATUS "MY_FLAGS: ${MY_FLAGS}")