diff --git a/.github/workflows/pascal.yaml b/.github/workflows/pascal.yaml index bb355f75ae..cc227d8e8c 100644 --- a/.github/workflows/pascal.yaml +++ b/.github/workflows/pascal.yaml @@ -167,6 +167,12 @@ jobs: ls -lh echo "---" + ./run-supertonic-en.sh + rm -rf sherpa-onnx-supertonic-* + rm supertonic-en + ls -lh + echo "---" + popd - uses: actions/upload-artifact@v4 diff --git a/pascal-api-examples/tts/.gitignore b/pascal-api-examples/tts/.gitignore index 1e94de2e24..ab3427c1b8 100644 --- a/pascal-api-examples/tts/.gitignore +++ b/pascal-api-examples/tts/.gitignore @@ -13,3 +13,4 @@ kitten-en-playback kokoro-zh-en kokoro-zh-en-playback pocket-en +supertonic-en diff --git a/pascal-api-examples/tts/run-supertonic-en.sh b/pascal-api-examples/tts/run-supertonic-en.sh new file mode 100755 index 0000000000..a89cff835c --- /dev/null +++ b/pascal-api-examples/tts/run-supertonic-en.sh @@ -0,0 +1,43 @@ +#!/usr/bin/env bash + +set -ex + +SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) +SHERPA_ONNX_DIR=$(cd $SCRIPT_DIR/../.. && pwd) + +echo "SHERPA_ONNX_DIR: $SHERPA_ONNX_DIR" + +if [[ ! -f ../../build/install/lib/libsherpa-onnx-c-api.dylib && ! -f ../../build/install/lib/libsherpa-onnx-c-api.so && ! -f ../../build/install/lib/sherpa-onnx-c-api.dll ]]; then + mkdir -p ../../build + pushd ../../build + cmake \ + -DCMAKE_INSTALL_PREFIX=./install \ + -DSHERPA_ONNX_ENABLE_PYTHON=OFF \ + -DSHERPA_ONNX_ENABLE_TESTS=OFF \ + -DSHERPA_ONNX_ENABLE_CHECK=OFF \ + -DBUILD_SHARED_LIBS=ON \ + -DSHERPA_ONNX_ENABLE_PORTAUDIO=OFF \ + .. + + cmake --build . --target install --config Release + popd +fi + +# please visit +# https://k2-fsa.github.io/sherpa/onnx/tts/supertonic.html +if [ ! -f ./sherpa-onnx-supertonic-tts-int8-2026-03-06/duration_predictor.int8.onnx ]; then + curl -SL -O https://github.com/k2-fsa/sherpa-onnx/releases/download/tts-models/sherpa-onnx-supertonic-tts-int8-2026-03-06.tar.bz2 + tar xvf sherpa-onnx-supertonic-tts-int8-2026-03-06.tar.bz2 + rm sherpa-onnx-supertonic-tts-int8-2026-03-06.tar.bz2 +fi + +fpc \ + -dSHERPA_ONNX_USE_SHARED_LIBS \ + -Fu$SHERPA_ONNX_DIR/sherpa-onnx/pascal-api \ + -Fl$SHERPA_ONNX_DIR/build/install/lib \ + ./supertonic-en.pas + +export LD_LIBRARY_PATH=$SHERPA_ONNX_DIR/build/install/lib:$LD_LIBRARY_PATH +export DYLD_LIBRARY_PATH=$SHERPA_ONNX_DIR/build/install/lib:$DYLD_LIBRARY_PATH + +./supertonic-en diff --git a/pascal-api-examples/tts/supertonic-en.pas b/pascal-api-examples/tts/supertonic-en.pas new file mode 100644 index 0000000000..6b86084f2a --- /dev/null +++ b/pascal-api-examples/tts/supertonic-en.pas @@ -0,0 +1,63 @@ +{ Copyright (c) 2026 Xiaomi Corporation } +program supertonic_en; +{ +This file shows how to use the text to speech API of sherpa-onnx +with Supertonic TTS models. + +It generates speech from text and saves it to a wave file. + +Please visit +https://k2-fsa.github.io/sherpa/onnx/tts/supertonic.html +to download the model. +} + +{$mode objfpc} + +uses + SysUtils, + sherpa_onnx; + +function GetOfflineTts: TSherpaOnnxOfflineTts; +var + Config: TSherpaOnnxOfflineTtsConfig; +begin + Config.Model.Supertonic.DurationPredictor := './sherpa-onnx-supertonic-tts-int8-2026-03-06/duration_predictor.int8.onnx'; + Config.Model.Supertonic.TextEncoder := './sherpa-onnx-supertonic-tts-int8-2026-03-06/text_encoder.int8.onnx'; + Config.Model.Supertonic.VectorEstimator := './sherpa-onnx-supertonic-tts-int8-2026-03-06/vector_estimator.int8.onnx'; + Config.Model.Supertonic.Vocoder := './sherpa-onnx-supertonic-tts-int8-2026-03-06/vocoder.int8.onnx'; + Config.Model.Supertonic.TtsJson := './sherpa-onnx-supertonic-tts-int8-2026-03-06/tts.json'; + Config.Model.Supertonic.UnicodeIndexer := './sherpa-onnx-supertonic-tts-int8-2026-03-06/unicode_indexer.bin'; + Config.Model.Supertonic.VoiceStyle := './sherpa-onnx-supertonic-tts-int8-2026-03-06/voice.bin'; + Config.Model.NumThreads := 2; + Config.Model.Debug := True; + Config.MaxNumSentences := 1; + + Result := TSherpaOnnxOfflineTts.Create(Config); +end; + +var + Tts: TSherpaOnnxOfflineTts; + GenerationConfig: TSherpaOnnxGenerationConfig; + Audio: TSherpaOnnxGeneratedAudio; + Text: AnsiString; + +begin + Tts := GetOfflineTts; + + WriteLn('There are ', Tts.GetNumSpeakers, ' speakers'); + + 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.'; + + GenerationConfig.Sid := 6; + GenerationConfig.NumSteps := 5; + GenerationConfig.Speed := 1.25; + GenerationConfig.Extra := '{"lang": "en"}'; + + Audio := Tts.Generate(Text, GenerationConfig, NIL, NIL); + SherpaOnnxWriteWave('./supertonic-tts-en.wav', Audio.Samples, Audio.SampleRate); + WriteLn('Saved to ./supertonic-tts-en.wav'); + + FreeAndNil(Tts); +end. diff --git a/sherpa-onnx/pascal-api/sherpa_onnx.pas b/sherpa-onnx/pascal-api/sherpa_onnx.pas index 822a05896e..88b47678d5 100644 --- a/sherpa-onnx/pascal-api/sherpa_onnx.pas +++ b/sherpa-onnx/pascal-api/sherpa_onnx.pas @@ -150,6 +150,18 @@ TSherpaOnnxOfflineTtsPocketModelConfig = record class operator Initialize({$IFDEF FPC}var{$ELSE}out{$ENDIF} Dest: TSherpaOnnxOfflineTtsPocketModelConfig); end; + TSherpaOnnxOfflineTtsSupertonicModelConfig = record + DurationPredictor: AnsiString; + TextEncoder: AnsiString; + VectorEstimator: AnsiString; + Vocoder: AnsiString; + TtsJson: AnsiString; + UnicodeIndexer: AnsiString; + VoiceStyle: AnsiString; + + function ToString: AnsiString; + end; + TSherpaOnnxOfflineTtsModelConfig = record Vits: TSherpaOnnxOfflineTtsVitsModelConfig; NumThreads: Integer; @@ -160,6 +172,7 @@ TSherpaOnnxOfflineTtsModelConfig = record Kitten: TSherpaOnnxOfflineTtsKittenModelConfig; ZipVoice: TSherpaOnnxOfflineTtsZipVoiceModelConfig; Pocket: TSherpaOnnxOfflineTtsPocketModelConfig; + Supertonic: TSherpaOnnxOfflineTtsSupertonicModelConfig; function ToString: AnsiString; class operator Initialize({$IFDEF FPC}var{$ELSE}out{$ENDIF} Dest: TSherpaOnnxOfflineTtsModelConfig); @@ -1120,6 +1133,16 @@ SherpaOnnxOfflineTtsPocketModelConfig = record VoiceEmbeddingCacheCapacity: cint32; end; + SherpaOnnxOfflineTtsSupertonicModelConfig = record + DurationPredictor: PAnsiChar; + TextEncoder: PAnsiChar; + VectorEstimator: PAnsiChar; + Vocoder: PAnsiChar; + TtsJson: PAnsiChar; + UnicodeIndexer: PAnsiChar; + VoiceStyle: PAnsiChar; + end; + SherpaOnnxOfflineTtsModelConfig = record Vits: SherpaOnnxOfflineTtsVitsModelConfig; NumThreads: cint32; @@ -1130,6 +1153,7 @@ SherpaOnnxOfflineTtsModelConfig = record Kitten: SherpaOnnxOfflineTtsKittenModelConfig; ZipVoice: SherpaOnnxOfflineTtsZipVoiceModelConfig; Pocket: SherpaOnnxOfflineTtsPocketModelConfig; + Supertonic: SherpaOnnxOfflineTtsSupertonicModelConfig; end; SherpaOnnxOfflineTtsConfig = record @@ -2686,6 +2710,21 @@ function TSherpaOnnxOfflineTtsPocketModelConfig.ToString: AnsiString; Self.VocabJson, Self.TokenScoresJson, Self.VoiceEmbeddingCacheCapacity]); end; +function TSherpaOnnxOfflineTtsSupertonicModelConfig.ToString: AnsiString; +begin + Result := Format('TSherpaOnnxOfflineTtsSupertonicModelConfig(' + + 'DurationPredictor := %s, ' + + 'TextEncoder := %s, ' + + 'VectorEstimator := %s, ' + + 'Vocoder := %s, ' + + 'TtsJson := %s, ' + + 'UnicodeIndexer := %s, ' + + 'VoiceStyle := %s' + + ')', + [Self.DurationPredictor, Self.TextEncoder, Self.VectorEstimator, Self.Vocoder, + Self.TtsJson, Self.UnicodeIndexer, Self.VoiceStyle]); +end; + function TSherpaOnnxOfflineTtsModelConfig.ToString: AnsiString; begin Result := Format('TSherpaOnnxOfflineTtsModelConfig(' + @@ -2697,11 +2736,12 @@ function TSherpaOnnxOfflineTtsModelConfig.ToString: AnsiString; 'Kokoro := %s, ' + 'Kitten := %s, ' + 'ZipVoice := %s, ' + - 'Pocket := %s' + + 'Pocket := %s, ' + + 'Supertonic := %s' + ')', [Self.Vits.ToString, Self.NumThreads, Self.Debug.ToString, Self.Provider, Self.Matcha.ToString, Self.Kokoro.ToString, Self.Kitten.ToString, - Self.ZipVoice.ToString, Self.Pocket.ToString + Self.ZipVoice.ToString, Self.Pocket.ToString, Self.Supertonic.ToString ]); end; @@ -2788,6 +2828,14 @@ constructor TSherpaOnnxOfflineTts.Create(Config: TSherpaOnnxOfflineTtsConfig); C.Model.Pocket.TokenScoresJson := PAnsiChar(Config.Model.Pocket.TokenScoresJson); C.Model.Pocket.VoiceEmbeddingCacheCapacity := Config.Model.Pocket.VoiceEmbeddingCacheCapacity; + C.Model.Supertonic.DurationPredictor := PAnsiChar(Config.Model.Supertonic.DurationPredictor); + C.Model.Supertonic.TextEncoder := PAnsiChar(Config.Model.Supertonic.TextEncoder); + C.Model.Supertonic.VectorEstimator := PAnsiChar(Config.Model.Supertonic.VectorEstimator); + C.Model.Supertonic.Vocoder := PAnsiChar(Config.Model.Supertonic.Vocoder); + C.Model.Supertonic.TtsJson := PAnsiChar(Config.Model.Supertonic.TtsJson); + C.Model.Supertonic.UnicodeIndexer := PAnsiChar(Config.Model.Supertonic.UnicodeIndexer); + C.Model.Supertonic.VoiceStyle := PAnsiChar(Config.Model.Supertonic.VoiceStyle); + C.Model.NumThreads := Config.Model.NumThreads; C.Model.Provider := PAnsiChar(Config.Model.Provider); C.Model.Debug := Ord(Config.Model.Debug);