mirror of
https://github.com/k2-fsa/sherpa-onnx.git
synced 2026-11-10 21:11:03 +08:00
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:
1 parent
5c4348e049
commit
3a6758261c
15 files changed
+374
-18
No files matched your search
@@ -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();
|
||||
@@ -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}")
|
||||
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -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}")
|
||||
|
||||
|
||||
@@ -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}")
|
||||
|
||||
@@ -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, "");
|
||||
|
||||
@@ -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}")
|
||||
|
||||
|
||||
@@ -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}")
|
||||
|
||||
|
||||
@@ -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
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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}")
|
||||
|
||||
|
||||
@@ -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}")
|
||||
|
||||
|
||||
Reference in new issue
Block a user