From 3055a432d5f57a3a702c48489912094754eb817c Mon Sep 17 00:00:00 2001 From: Fangjun Kuang Date: Thu, 19 Mar 2026 18:38:49 +0800 Subject: [PATCH 1/2] Refactor Kokoro TTS to use the new Generate API --- c-api-examples/kokoro-tts-en-c-api.c | 12 ++-- c-api-examples/kokoro-tts-zh-en-c-api.c | 12 ++-- cxx-api-examples/kokoro-tts-en-cxx-api.cc | 8 ++- cxx-api-examples/kokoro-tts-zh-en-cxx-api.cc | 8 ++- dart-api-examples/tts/bin/kokoro-en.dart | 8 ++- dart-api-examples/tts/bin/kokoro-zh-en.dart | 8 ++- dotnet-examples/kokoro-tts-play/Program.cs | 17 +++--- dotnet-examples/kokoro-tts/Program.cs | 24 +++++--- .../NonStreamingTtsKokoroEn.java | 6 +- .../NonStreamingTtsKokoroZhEn.java | 6 +- .../test_tts_non_streaming_kokoro_en.js | 8 ++- .../test_tts_non_streaming_kokoro_zh_en.js | 8 ++- nodejs-examples/test-offline-tts-kokoro-en.js | 7 ++- .../test-offline-tts-kokoro-zh-en.js | 7 ++- .../tts/kokoro-en-playback.pas | 13 ++-- pascal-api-examples/tts/kokoro-en.pas | 9 ++- .../tts/kokoro-zh-en-playback.pas | 13 ++-- pascal-api-examples/tts/kokoro-zh-en.pas | 9 ++- sherpa-onnx/csrc/offline-tts-kokoro-impl.h | 61 ++++++++++++++++--- swift-api-examples/tts-kokoro-en.swift | 12 ++-- swift-api-examples/tts-kokoro-zh-en.swift | 12 ++-- 21 files changed, 202 insertions(+), 66 deletions(-) diff --git a/c-api-examples/kokoro-tts-en-c-api.c b/c-api-examples/kokoro-tts-en-c-api.c index 581b125fcf..a626c082f8 100644 --- a/c-api-examples/kokoro-tts-en-c-api.c +++ b/c-api-examples/kokoro-tts-en-c-api.c @@ -26,7 +26,7 @@ rm kokoro-en-v0_19.tar.bz2 #include "sherpa-onnx/c-api/c-api.h" static int32_t ProgressCallback(const float *samples, int32_t num_samples, - float progress) { + float progress, void *arg) { fprintf(stderr, "Progress: %.3f%%\n", progress * 100); // return 1 to continue generating // return 0 to stop generating @@ -60,15 +60,19 @@ int32_t main(int32_t argc, char *argv[]) { // 6->am_michael, 7->bf_emma, 8->bf_isabella, 9->bm_george, 10->bm_lewis int32_t sid = 0; float speed = 1.0; // larger -> faster in speech speed + SherpaOnnxGenerationConfig cfg = {0}; + cfg.silence_scale = 0.2f; + cfg.sid = sid; + cfg.speed = speed; #if 0 // If you don't want to use a callback, then please enable this branch const SherpaOnnxGeneratedAudio *audio = - SherpaOnnxOfflineTtsGenerate(tts, text, sid, speed); + SherpaOnnxOfflineTtsGenerateWithConfig(tts, text, &cfg, NULL, NULL); #else const SherpaOnnxGeneratedAudio *audio = - SherpaOnnxOfflineTtsGenerateWithProgressCallback(tts, text, sid, speed, - ProgressCallback); + SherpaOnnxOfflineTtsGenerateWithConfig(tts, text, &cfg, ProgressCallback, + NULL); #endif SherpaOnnxWriteWave(audio->samples, audio->n, audio->sample_rate, filename); diff --git a/c-api-examples/kokoro-tts-zh-en-c-api.c b/c-api-examples/kokoro-tts-zh-en-c-api.c index 4657b49b8c..c2b1a22607 100644 --- a/c-api-examples/kokoro-tts-zh-en-c-api.c +++ b/c-api-examples/kokoro-tts-zh-en-c-api.c @@ -26,7 +26,7 @@ rm kokoro-multi-lang-v1_0.tar.bz2 #include "sherpa-onnx/c-api/c-api.h" static int32_t ProgressCallback(const float *samples, int32_t num_samples, - float progress) { + float progress, void *arg) { fprintf(stderr, "Progress: %.3f%%\n", progress * 100); // return 1 to continue generating // return 0 to stop generating @@ -58,15 +58,19 @@ int32_t main(int32_t argc, char *argv[]) { const SherpaOnnxOfflineTts *tts = SherpaOnnxCreateOfflineTts(&config); int32_t sid = 0; // there are 53 speakers float speed = 1.0; // larger -> faster in speech speed + SherpaOnnxGenerationConfig cfg = {0}; + cfg.silence_scale = 0.2f; + cfg.sid = sid; + cfg.speed = speed; #if 0 // If you don't want to use a callback, then please enable this branch const SherpaOnnxGeneratedAudio *audio = - SherpaOnnxOfflineTtsGenerate(tts, text, sid, speed); + SherpaOnnxOfflineTtsGenerateWithConfig(tts, text, &cfg, NULL, NULL); #else const SherpaOnnxGeneratedAudio *audio = - SherpaOnnxOfflineTtsGenerateWithProgressCallback(tts, text, sid, speed, - ProgressCallback); + SherpaOnnxOfflineTtsGenerateWithConfig(tts, text, &cfg, ProgressCallback, + NULL); #endif SherpaOnnxWriteWave(audio->samples, audio->n, audio->sample_rate, filename); diff --git a/cxx-api-examples/kokoro-tts-en-cxx-api.cc b/cxx-api-examples/kokoro-tts-en-cxx-api.cc index 80dd0e9c42..fcdaac6fcf 100644 --- a/cxx-api-examples/kokoro-tts-en-cxx-api.cc +++ b/cxx-api-examples/kokoro-tts-en-cxx-api.cc @@ -57,12 +57,16 @@ int32_t main(int32_t argc, char *argv[]) { auto tts = OfflineTts::Create(config); int32_t sid = 0; float speed = 1.0; // larger -> faster in speech speed + GenerationConfig gen_config; + gen_config.sid = sid; + gen_config.speed = speed; + gen_config.silence_scale = 0.2f; #if 0 // If you don't want to use a callback, then please enable this branch - GeneratedAudio audio = tts.Generate(text, sid, speed); + GeneratedAudio audio = tts.Generate(text, gen_config); #else - GeneratedAudio audio = tts.Generate(text, sid, speed, ProgressCallback); + GeneratedAudio audio = tts.Generate(text, gen_config, ProgressCallback); #endif WriteWave(filename, {audio.samples, audio.sample_rate}); diff --git a/cxx-api-examples/kokoro-tts-zh-en-cxx-api.cc b/cxx-api-examples/kokoro-tts-zh-en-cxx-api.cc index b45adc11c6..9a5b38f0a4 100644 --- a/cxx-api-examples/kokoro-tts-zh-en-cxx-api.cc +++ b/cxx-api-examples/kokoro-tts-zh-en-cxx-api.cc @@ -58,12 +58,16 @@ int32_t main(int32_t argc, char *argv[]) { auto tts = OfflineTts::Create(config); int32_t sid = 50; float speed = 1.0; // larger -> faster in speech speed + GenerationConfig gen_config; + gen_config.sid = sid; + gen_config.speed = speed; + gen_config.silence_scale = 0.2f; #if 0 // If you don't want to use a callback, then please enable this branch - GeneratedAudio audio = tts.Generate(text, sid, speed); + GeneratedAudio audio = tts.Generate(text, gen_config); #else - GeneratedAudio audio = tts.Generate(text, sid, speed, ProgressCallback); + GeneratedAudio audio = tts.Generate(text, gen_config, ProgressCallback); #endif WriteWave(filename, {audio.samples, audio.sample_rate}); diff --git a/dart-api-examples/tts/bin/kokoro-en.dart b/dart-api-examples/tts/bin/kokoro-en.dart index b92d92883f..71c3dd716f 100644 --- a/dart-api-examples/tts/bin/kokoro-en.dart +++ b/dart-api-examples/tts/bin/kokoro-en.dart @@ -58,7 +58,6 @@ void main(List arguments) async { voices: voices, tokens: tokens, dataDir: dataDir, - lengthScale: 1 / speed, ); final modelConfig = sherpa_onnx.OfflineTtsModelConfig( @@ -74,7 +73,12 @@ void main(List arguments) async { ); final tts = sherpa_onnx.OfflineTts(config); - final audio = tts.generate(text: text, sid: sid, speed: speed); + final genConfig = sherpa_onnx.OfflineTtsGenerationConfig( + sid: sid, + speed: speed, + silenceScale: config.silenceScale, + ); + final audio = tts.generateWithConfig(text: text, config: genConfig); tts.free(); sherpa_onnx.writeWave( diff --git a/dart-api-examples/tts/bin/kokoro-zh-en.dart b/dart-api-examples/tts/bin/kokoro-zh-en.dart index d263de55ff..2a7cfb4287 100644 --- a/dart-api-examples/tts/bin/kokoro-zh-en.dart +++ b/dart-api-examples/tts/bin/kokoro-zh-en.dart @@ -65,7 +65,6 @@ void main(List arguments) async { voices: voices, tokens: tokens, dataDir: dataDir, - lengthScale: 1 / speed, lexicon: lexicon, ); @@ -82,7 +81,12 @@ void main(List arguments) async { ); final tts = sherpa_onnx.OfflineTts(config); - final audio = tts.generate(text: text, sid: sid, speed: speed); + final genConfig = sherpa_onnx.OfflineTtsGenerationConfig( + sid: sid, + speed: speed, + silenceScale: config.silenceScale, + ); + final audio = tts.generateWithConfig(text: text, config: genConfig); tts.free(); sherpa_onnx.writeWave( diff --git a/dotnet-examples/kokoro-tts-play/Program.cs b/dotnet-examples/kokoro-tts-play/Program.cs index 76e537bb4d..95752e078f 100644 --- a/dotnet-examples/kokoro-tts-play/Program.cs +++ b/dotnet-examples/kokoro-tts-play/Program.cs @@ -35,9 +35,12 @@ static void Main(string[] args) "thing in the world was to lose touch with someone."; // mapping of sid to voice name - // 0->af, 1->af_bella, 2->af_nicole, 3->af_sarah, 4->af_sky, 5->am_adam - // 6->am_michael, 7->bf_emma, 8->bf_isabella, 9->bm_george, 10->bm_lewis - var sid = 0; + // 0->af, 1->af_bella, 2->af_nicole, 3->af_sarah, 4->af_sky, 5->am_adam + // 6->am_michael, 7->bf_emma, 8->bf_isabella, 9->bm_george, 10->bm_lewis + var sid = 0; + OfflineTtsGenerationConfig genConfig = new OfflineTtsGenerationConfig(); + genConfig.Sid = sid; + genConfig.Speed = speed; Console.WriteLine(PortAudio.VersionInfo.versionText); @@ -73,7 +76,7 @@ static void Main(string[] args) // https://learn.microsoft.com/en-us/dotnet/standard/collections/thread-safe/blockingcollection-overview var dataItems = new BlockingCollection(); - var MyCallback = (IntPtr samples, int n, float progress) => + var MyCallback = (IntPtr samples, int n, float progress, IntPtr arg) => { Console.WriteLine($"Progress {progress*100}%"); @@ -165,9 +168,9 @@ IntPtr userData stream.Start(); - var callback = new OfflineTtsCallbackProgress(MyCallback); - - var audio = tts.GenerateWithCallbackProgress(text, speed, sid, callback); + var callback = new OfflineTtsCallbackProgressWithArg(MyCallback); + + var audio = tts.GenerateWithConfig(text, genConfig, callback); var outputFilename = "./generated-kokoro-0.wav"; var ok = audio.SaveToWaveFile(outputFilename); diff --git a/dotnet-examples/kokoro-tts/Program.cs b/dotnet-examples/kokoro-tts/Program.cs index fe66b1b028..c19b0ca964 100644 --- a/dotnet-examples/kokoro-tts/Program.cs +++ b/dotnet-examples/kokoro-tts/Program.cs @@ -38,7 +38,11 @@ static void TestZhEn() var sid = 50; - var MyCallback = (IntPtr samples, int n, float progress) => + OfflineTtsGenerationConfig genConfig = new OfflineTtsGenerationConfig(); + genConfig.Sid = sid; + genConfig.Speed = speed; + + var MyCallback = (IntPtr samples, int n, float progress, IntPtr arg) => { float[] data = new float[n]; Marshal.Copy(samples, data, 0, n); @@ -51,9 +55,9 @@ static void TestZhEn() return 1; }; - var callback = new OfflineTtsCallbackProgress(MyCallback); - - var audio = tts.GenerateWithCallbackProgress(text, speed, sid, callback); + var callback = new OfflineTtsCallbackProgressWithArg(MyCallback); + + var audio = tts.GenerateWithConfig(text, genConfig, callback); var outputFilename = "./generated-kokoro-zh-en.wav"; var ok = audio.SaveToWaveFile(outputFilename); @@ -93,7 +97,11 @@ static void TestEn() // 6->am_michael, 7->bf_emma, 8->bf_isabella, 9->bm_george, 10->bm_lewis var sid = 0; - var MyCallback = (IntPtr samples, int n, float progress) => + OfflineTtsGenerationConfig genConfig = new OfflineTtsGenerationConfig(); + genConfig.Sid = sid; + genConfig.Speed = speed; + + var MyCallback = (IntPtr samples, int n, float progress, IntPtr arg) => { float[] data = new float[n]; Marshal.Copy(samples, data, 0, n); @@ -106,9 +114,9 @@ static void TestEn() return 1; }; - var callback = new OfflineTtsCallbackProgress(MyCallback); - - var audio = tts.GenerateWithCallbackProgress(text, speed, sid, callback); + var callback = new OfflineTtsCallbackProgressWithArg(MyCallback); + + var audio = tts.GenerateWithConfig(text, genConfig, callback); var outputFilename = "./generated-kokoro-en.wav"; var ok = audio.SaveToWaveFile(outputFilename); diff --git a/java-api-examples/NonStreamingTtsKokoroEn.java b/java-api-examples/NonStreamingTtsKokoroEn.java index 7f03f0aae8..5270477246 100644 --- a/java-api-examples/NonStreamingTtsKokoroEn.java +++ b/java-api-examples/NonStreamingTtsKokoroEn.java @@ -38,8 +38,12 @@ public static void main(String[] args) { int sid = 0; float speed = 1.0f; + GenerationConfig genConfig = new GenerationConfig(); + genConfig.setSid(sid); + genConfig.setSpeed(speed); + genConfig.setSilenceScale(0.2f); long start = System.currentTimeMillis(); - GeneratedAudio audio = tts.generate(text, sid, speed); + GeneratedAudio audio = tts.generateWithConfigAndCallback(text, genConfig, samples -> {}); long stop = System.currentTimeMillis(); float timeElapsedSeconds = (stop - start) / 1000.0f; diff --git a/java-api-examples/NonStreamingTtsKokoroZhEn.java b/java-api-examples/NonStreamingTtsKokoroZhEn.java index fb8234a5af..24781b7432 100644 --- a/java-api-examples/NonStreamingTtsKokoroZhEn.java +++ b/java-api-examples/NonStreamingTtsKokoroZhEn.java @@ -40,8 +40,12 @@ public static void main(String[] args) { int sid = 0; // this model has 53 speakers. You can use sid in the range 0-52 float speed = 1.0f; + GenerationConfig genConfig = new GenerationConfig(); + genConfig.setSid(sid); + genConfig.setSpeed(speed); + genConfig.setSilenceScale(0.2f); long start = System.currentTimeMillis(); - GeneratedAudio audio = tts.generate(text, sid, speed); + GeneratedAudio audio = tts.generateWithConfigAndCallback(text, genConfig, samples -> {}); long stop = System.currentTimeMillis(); float timeElapsedSeconds = (stop - start) / 1000.0f; diff --git a/nodejs-addon-examples/test_tts_non_streaming_kokoro_en.js b/nodejs-addon-examples/test_tts_non_streaming_kokoro_en.js index 76e730afa6..76a3d39fed 100644 --- a/nodejs-addon-examples/test_tts_non_streaming_kokoro_en.js +++ b/nodejs-addon-examples/test_tts_non_streaming_kokoro_en.js @@ -27,9 +27,15 @@ 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: text, sid: 6, speed: 1.0}); +const audio = tts.generate({text, generationConfig}); let stop = Date.now(); const elapsed_seconds = (stop - start) / 1000; const duration = audio.samples.length / audio.sampleRate; diff --git a/nodejs-addon-examples/test_tts_non_streaming_kokoro_zh_en.js b/nodejs-addon-examples/test_tts_non_streaming_kokoro_zh_en.js index 973e7c6379..761cc67d97 100644 --- a/nodejs-addon-examples/test_tts_non_streaming_kokoro_zh_en.js +++ b/nodejs-addon-examples/test_tts_non_streaming_kokoro_zh_en.js @@ -29,8 +29,14 @@ const tts = 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, +}); + let start = Date.now(); -const audio = tts.generate({text: text, sid: 48, speed: 1.0}); +const audio = tts.generate({text, generationConfig}); let stop = Date.now(); const elapsed_seconds = (stop - start) / 1000; const duration = audio.samples.length / audio.sampleRate; diff --git a/nodejs-examples/test-offline-tts-kokoro-en.js b/nodejs-examples/test-offline-tts-kokoro-en.js index cccf8fcae9..76614249f2 100644 --- a/nodejs-examples/test-offline-tts-kokoro-en.js +++ b/nodejs-examples/test-offline-tts-kokoro-en.js @@ -28,10 +28,15 @@ function createOfflineTts() { const tts = createOfflineTts(); const speakerId = 0; const speed = 1.0; +const generationConfig = { + sid: speakerId, + speed: speed, + silenceScale: 0.2, +}; 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.generate({text: text, sid: speakerId, speed: speed}); +const audio = tts.generateWithConfig(text, generationConfig); tts.save('./test-kokoro-en.wav', audio); console.log('Saved to test-kokoro-en.wav successfully.'); tts.free(); diff --git a/nodejs-examples/test-offline-tts-kokoro-zh-en.js b/nodejs-examples/test-offline-tts-kokoro-zh-en.js index 66b4157dc4..63bf4f9439 100644 --- a/nodejs-examples/test-offline-tts-kokoro-zh-en.js +++ b/nodejs-examples/test-offline-tts-kokoro-zh-en.js @@ -30,10 +30,15 @@ function createOfflineTts() { const tts = createOfflineTts(); const speakerId = 49; const speed = 1.0; +const generationConfig = { + sid: speakerId, + speed: speed, + silenceScale: 0.2, +}; const text = '中英文语音合成测试。This is generated by next generation Kaldi using Kokoro without Misaki. 你觉得中英文说的如何呢?'; -const audio = tts.generate({text: text, sid: speakerId, speed: speed}); +const audio = tts.generateWithConfig(text, generationConfig); tts.save('./test-kokoro-zh-en-49.wav', audio); console.log('Saved to test-kokoro-zh-en-49.wav successfully.'); tts.free(); diff --git a/pascal-api-examples/tts/kokoro-en-playback.pas b/pascal-api-examples/tts/kokoro-en-playback.pas index 7796a6fee5..b9395c2d64 100644 --- a/pascal-api-examples/tts/kokoro-en-playback.pas +++ b/pascal-api-examples/tts/kokoro-en-playback.pas @@ -50,10 +50,11 @@ Param: TPaStreamParameters; Stream: PPaStream; Wave: TSherpaOnnxWave; + GenerationConfig: TSherpaOnnxGenerationConfig; function GenerateCallback( Samples: pcfloat; N: cint32; - Arg: Pointer): cint; cdecl; + Progress: cfloat; Arg: Pointer): cint; cdecl; begin EnterCriticalSection(CriticalSection); try @@ -207,8 +208,13 @@ function GetOfflineTts: TSherpaOnnxOfflineTts; Text := 'Friends fell out often because life was changing so fast. The easiest thing in the world was to lose touch with someone.'; - Audio := Tts.Generate(Text, SpeakerId, Speed, - PSherpaOnnxGeneratedAudioCallbackWithArg(@GenerateCallback), nil); + GenerationConfig := Default(TSherpaOnnxGenerationConfig); + GenerationConfig.SilenceScale := 0.2; + GenerationConfig.Speed := Speed; + GenerationConfig.Sid := SpeakerId; + + Audio := Tts.Generate(Text, GenerationConfig, + @GenerateCallback, nil); FinishedGeneration := True; SherpaOnnxWriteWave('./kokoro-en-playback-7.wav', Audio.Samples, Audio.SampleRate); WriteLn('Saved to ./kokoro-en-playback-7.wav'); @@ -236,4 +242,3 @@ function GetOfflineTts: TSherpaOnnxOfflineTts; Exit; end; end. - diff --git a/pascal-api-examples/tts/kokoro-en.pas b/pascal-api-examples/tts/kokoro-en.pas index 5e186b24d8..ee623d0f74 100644 --- a/pascal-api-examples/tts/kokoro-en.pas +++ b/pascal-api-examples/tts/kokoro-en.pas @@ -34,6 +34,7 @@ function GetOfflineTts: TSherpaOnnxOfflineTts; var Tts: TSherpaOnnxOfflineTts; Audio: TSherpaOnnxGeneratedAudio; + GenerationConfig: TSherpaOnnxGenerationConfig; Text: AnsiString; Speed: Single = 1.0; {Use a larger value to speak faster} @@ -46,10 +47,14 @@ function GetOfflineTts: TSherpaOnnxOfflineTts; Text := 'Friends fell out often because life was changing so fast. The easiest thing in the world was to lose touch with someone.'; - Audio := Tts.Generate(Text, SpeakerId, Speed); + GenerationConfig := Default(TSherpaOnnxGenerationConfig); + GenerationConfig.SilenceScale := 0.2; + GenerationConfig.Speed := Speed; + GenerationConfig.Sid := SpeakerId; + + Audio := Tts.Generate(Text, GenerationConfig, NIL, NIL); SherpaOnnxWriteWave('./kokoro-en-8.wav', Audio.Samples, Audio.SampleRate); WriteLn('Saved to ./kokoro-en-8.wav'); FreeAndNil(Tts); end. - diff --git a/pascal-api-examples/tts/kokoro-zh-en-playback.pas b/pascal-api-examples/tts/kokoro-zh-en-playback.pas index 22d36dea4a..8435c466d5 100644 --- a/pascal-api-examples/tts/kokoro-zh-en-playback.pas +++ b/pascal-api-examples/tts/kokoro-zh-en-playback.pas @@ -50,10 +50,11 @@ Param: TPaStreamParameters; Stream: PPaStream; Wave: TSherpaOnnxWave; + GenerationConfig: TSherpaOnnxGenerationConfig; function GenerateCallback( Samples: pcfloat; N: cint32; - Arg: Pointer): cint; cdecl; + Progress: cfloat; Arg: Pointer): cint; cdecl; begin EnterCriticalSection(CriticalSection); try @@ -209,8 +210,13 @@ function GetOfflineTts: TSherpaOnnxOfflineTts; Text := '中英文语音合成测试。This is generated by next generation Kaldi using Kokoro without Misaki. 你觉得中英文说的如何呢?'; - Audio := Tts.Generate(Text, SpeakerId, Speed, - PSherpaOnnxGeneratedAudioCallbackWithArg(@GenerateCallback), nil); + GenerationConfig := Default(TSherpaOnnxGenerationConfig); + GenerationConfig.SilenceScale := 0.2; + GenerationConfig.Speed := Speed; + GenerationConfig.Sid := SpeakerId; + + Audio := Tts.Generate(Text, GenerationConfig, + @GenerateCallback, nil); FinishedGeneration := True; SherpaOnnxWriteWave('./kokoro-zh-en-playback-47.wav', Audio.Samples, Audio.SampleRate); WriteLn('Saved to ./kokoro-zh-en-playback-47.wav'); @@ -238,4 +244,3 @@ function GetOfflineTts: TSherpaOnnxOfflineTts; Exit; end; end. - diff --git a/pascal-api-examples/tts/kokoro-zh-en.pas b/pascal-api-examples/tts/kokoro-zh-en.pas index d34e864854..48e472128d 100644 --- a/pascal-api-examples/tts/kokoro-zh-en.pas +++ b/pascal-api-examples/tts/kokoro-zh-en.pas @@ -36,6 +36,7 @@ function GetOfflineTts: TSherpaOnnxOfflineTts; var Tts: TSherpaOnnxOfflineTts; Audio: TSherpaOnnxGeneratedAudio; + GenerationConfig: TSherpaOnnxGenerationConfig; Text: AnsiString; Speed: Single = 1.0; {Use a larger value to speak faster} @@ -48,10 +49,14 @@ function GetOfflineTts: TSherpaOnnxOfflineTts; Text := '中英文语音合成测试。This is generated by next generation Kaldi using Kokoro without Misaki. 你觉得中英文说的如何呢?'; - Audio := Tts.Generate(Text, SpeakerId, Speed); + GenerationConfig := Default(TSherpaOnnxGenerationConfig); + GenerationConfig.SilenceScale := 0.2; + GenerationConfig.Speed := Speed; + GenerationConfig.Sid := SpeakerId; + + Audio := Tts.Generate(Text, GenerationConfig, NIL, NIL); SherpaOnnxWriteWave('./kokoro-zh-en-46.wav', Audio.Samples, Audio.SampleRate); WriteLn('Saved to ./kokoro-zh-en-46.wav'); FreeAndNil(Tts); end. - diff --git a/sherpa-onnx/csrc/offline-tts-kokoro-impl.h b/sherpa-onnx/csrc/offline-tts-kokoro-impl.h index 0ca2921114..0b0f3b3ed6 100644 --- a/sherpa-onnx/csrc/offline-tts-kokoro-impl.h +++ b/sherpa-onnx/csrc/offline-tts-kokoro-impl.h @@ -149,9 +149,30 @@ class OfflineTtsKokoroImpl : public OfflineTtsImpl { return model_->GetMetaData().num_speakers; } + // Supported options in GenerationConfig: + // - sid: Speaker ID for multi-speaker models + // - speed: Speech speed factor. If left at 1.0, it falls back to the + // default implied by kokoro.length_scale. + // - silence_scale: Scale applied to pauses in the generated audio. If left + // at 0.2, it falls back to OfflineTtsConfig.silence_scale. + // + // Supported extra options in config.extra: + // - lang: Language override for Kokoro >= 1.0. Defaults to + // kokoro.lang if provided, otherwise meta_data.voice. GeneratedAudio Generate( - const std::string &_text, int64_t sid = 0, float speed = 1.0, + const std::string &_text, const GenerationConfig &gen_config, GeneratedAudioCallback callback = nullptr) const override { + if (config_.model.debug) { + SHERPA_ONNX_LOGE("%s", gen_config.ToString().c_str()); + } + + int64_t sid = gen_config.sid; + float speed = gen_config.speed; + if (speed <= 0) { + SHERPA_ONNX_LOGE("Speed must be > 0. Given: %f", speed); + return {}; + } + const auto &meta_data = model_->GetMetaData(); int32_t num_speakers = meta_data.num_speakers; @@ -220,9 +241,14 @@ class OfflineTtsKokoroImpl : public OfflineTtsImpl { } } + std::string lang = gen_config.GetExtraString("lang"); + if (lang.empty()) { + lang = config_.model.kokoro.lang.empty() ? meta_data.voice + : config_.model.kokoro.lang; + } + std::vector token_ids = frontend_->ConvertTextToTokenIds( - text, config_.model.kokoro.lang.empty() ? meta_data.voice - : config_.model.kokoro.lang); + text, lang); if (token_ids.empty() || (token_ids.size() == 1 && token_ids[0].tokens.empty())) { @@ -292,7 +318,8 @@ class OfflineTtsKokoroImpl : public OfflineTtsImpl { batch_x.push_back(std::move(x[k])); } - auto audio = Process(batch_x, sid, speed); + auto audio = + Process(batch_x, sid, speed, gen_config.silence_scale); ans.sample_rate = audio.sample_rate; ans.samples.insert(ans.samples.end(), audio.samples.begin(), audio.samples.end()); @@ -313,7 +340,8 @@ class OfflineTtsKokoroImpl : public OfflineTtsImpl { } if (!batch_x.empty()) { - auto audio = Process(batch_x, sid, speed); + auto audio = + Process(batch_x, sid, speed, gen_config.silence_scale); ans.sample_rate = audio.sample_rate; ans.samples.insert(ans.samples.end(), audio.samples.begin(), audio.samples.end()); @@ -328,6 +356,21 @@ class OfflineTtsKokoroImpl : public OfflineTtsImpl { return ans; } + [[deprecated("Use Generate(text, GenerationConfig, callback) instead")]] + GeneratedAudio Generate( + const std::string &text, int64_t sid = 0, float speed = 1.0, + GeneratedAudioCallback callback = nullptr) const override { + GenerationConfig gen_config; + gen_config.sid = sid; + gen_config.speed = speed; + gen_config.silence_scale = config_.silence_scale; + if (!config_.model.kokoro.lang.empty()) { + gen_config.extra["lang"] = config_.model.kokoro.lang; + } + + return Generate(text, gen_config, std::move(callback)); + } + private: template void InitFrontend(Manager *mgr) { @@ -382,7 +425,8 @@ class OfflineTtsKokoroImpl : public OfflineTtsImpl { } GeneratedAudio Process(const std::vector> &tokens, - int32_t sid, float speed) const { + int32_t sid, float speed, + float silence_scale) const { int32_t num_tokens = 0; for (const auto &k : tokens) { num_tokens += k.size(); @@ -418,7 +462,10 @@ class OfflineTtsKokoroImpl : public OfflineTtsImpl { ans.sample_rate = model_->GetMetaData().sample_rate; ans.samples = std::vector(p, p + total); - float silence_scale = config_.silence_scale; + if (silence_scale == 0.2f) { + silence_scale = config_.silence_scale; + } + if (silence_scale != 1) { ans = ans.ScaleSilence(silence_scale); } diff --git a/swift-api-examples/tts-kokoro-en.swift b/swift-api-examples/tts-kokoro-en.swift index a0459cf8d4..3f08b0fce7 100644 --- a/swift-api-examples/tts-kokoro-en.swift +++ b/swift-api-examples/tts-kokoro-en.swift @@ -26,7 +26,7 @@ func run() { // https://medium.com/codex/swift-c-callback-interoperability-6d57da6c8ee6 let arg = Unmanaged.passUnretained(myClass).toOpaque() - let callback: TtsCallbackWithArg = { samples, n, arg in + let callback: TtsProgressCallbackWithArg = { samples, n, progress, arg in let o = Unmanaged.fromOpaque(arg!).takeUnretainedValue() var savedSamples: [Float] = [] for index in 0...passUnretained(myClass).toOpaque() - let callback: TtsCallbackWithArg = { samples, n, arg in + let callback: TtsProgressCallbackWithArg = { samples, n, progress, arg in let o = Unmanaged.fromOpaque(arg!).takeUnretainedValue() var savedSamples: [Float] = [] for index in 0.. Date: Thu, 19 Mar 2026 18:56:14 +0800 Subject: [PATCH 2/2] Small fixes --- dotnet-examples/kokoro-tts-play/Program.cs | 1 + dotnet-examples/kokoro-tts/Program.cs | 2 ++ 2 files changed, 3 insertions(+) diff --git a/dotnet-examples/kokoro-tts-play/Program.cs b/dotnet-examples/kokoro-tts-play/Program.cs index 95752e078f..d968de1938 100644 --- a/dotnet-examples/kokoro-tts-play/Program.cs +++ b/dotnet-examples/kokoro-tts-play/Program.cs @@ -41,6 +41,7 @@ static void Main(string[] args) OfflineTtsGenerationConfig genConfig = new OfflineTtsGenerationConfig(); genConfig.Sid = sid; genConfig.Speed = speed; + genConfig.SilenceScale = 0.2f; Console.WriteLine(PortAudio.VersionInfo.versionText); diff --git a/dotnet-examples/kokoro-tts/Program.cs b/dotnet-examples/kokoro-tts/Program.cs index c19b0ca964..ee56762c49 100644 --- a/dotnet-examples/kokoro-tts/Program.cs +++ b/dotnet-examples/kokoro-tts/Program.cs @@ -41,6 +41,7 @@ static void TestZhEn() OfflineTtsGenerationConfig genConfig = new OfflineTtsGenerationConfig(); genConfig.Sid = sid; genConfig.Speed = speed; + genConfig.SilenceScale = 0.2f; var MyCallback = (IntPtr samples, int n, float progress, IntPtr arg) => { @@ -100,6 +101,7 @@ static void TestEn() OfflineTtsGenerationConfig genConfig = new OfflineTtsGenerationConfig(); genConfig.Sid = sid; genConfig.Speed = speed; + genConfig.SilenceScale = 0.2f; var MyCallback = (IntPtr samples, int n, float progress, IntPtr arg) => {