diff --git a/.github/scripts/test-swift.sh b/.github/scripts/test-swift.sh index f333bc0a..9ab682d2 100755 --- a/.github/scripts/test-swift.sh +++ b/.github/scripts/test-swift.sh @@ -11,6 +11,10 @@ ls -lh ls -lh rm -rf vits-piper-* +./run-tts-kokoro-en.sh +ls -lh +rm -rf kokoro-en-* + ./run-tts-matcha-zh.sh ls -lh rm -rf matcha-icefall-* diff --git a/swift-api-examples/.gitignore b/swift-api-examples/.gitignore index 1a90488a..5bcf3148 100644 --- a/swift-api-examples/.gitignore +++ b/swift-api-examples/.gitignore @@ -12,3 +12,4 @@ keyword-spotting-from-file add-punctuations tts-matcha-zh tts-matcha-en +tts-kokoro-en diff --git a/swift-api-examples/SherpaOnnx.swift b/swift-api-examples/SherpaOnnx.swift index 6d11a001..d4c39645 100644 --- a/swift-api-examples/SherpaOnnx.swift +++ b/swift-api-examples/SherpaOnnx.swift @@ -736,7 +736,8 @@ func sherpaOnnxOfflineTtsVitsModelConfig( noise_scale: noiseScale, noise_scale_w: noiseScaleW, length_scale: lengthScale, - dict_dir: toCPointer(dictDir)) + dict_dir: toCPointer(dictDir) + ) } func sherpaOnnxOfflineTtsMatchaModelConfig( @@ -757,12 +758,30 @@ func sherpaOnnxOfflineTtsMatchaModelConfig( data_dir: toCPointer(dataDir), noise_scale: noiseScale, length_scale: lengthScale, - dict_dir: toCPointer(dictDir)) + dict_dir: toCPointer(dictDir) + ) +} + +func sherpaOnnxOfflineTtsKokoroModelConfig( + model: String = "", + voices: String = "", + tokens: String = "", + dataDir: String = "", + lengthScale: Float = 1.0 +) -> SherpaOnnxOfflineTtsKokoroModelConfig { + return SherpaOnnxOfflineTtsKokoroModelConfig( + model: toCPointer(model), + voices: toCPointer(voices), + tokens: toCPointer(tokens), + data_dir: toCPointer(dataDir), + length_scale: lengthScale + ) } func sherpaOnnxOfflineTtsModelConfig( vits: SherpaOnnxOfflineTtsVitsModelConfig = sherpaOnnxOfflineTtsVitsModelConfig(), matcha: SherpaOnnxOfflineTtsMatchaModelConfig = sherpaOnnxOfflineTtsMatchaModelConfig(), + kokoro: SherpaOnnxOfflineTtsKokoroModelConfig = sherpaOnnxOfflineTtsKokoroModelConfig(), numThreads: Int = 1, debug: Int = 0, provider: String = "cpu" @@ -772,7 +791,8 @@ func sherpaOnnxOfflineTtsModelConfig( num_threads: Int32(numThreads), debug: Int32(debug), provider: toCPointer(provider), - matcha: matcha + matcha: matcha, + kokoro: kokoro ) } @@ -780,7 +800,7 @@ func sherpaOnnxOfflineTtsConfig( model: SherpaOnnxOfflineTtsModelConfig, ruleFsts: String = "", ruleFars: String = "", - maxNumSentences: Int = 2 + maxNumSentences: Int = 1 ) -> SherpaOnnxOfflineTtsConfig { return SherpaOnnxOfflineTtsConfig( model: model, diff --git a/swift-api-examples/run-tts-kokoro-en.sh b/swift-api-examples/run-tts-kokoro-en.sh new file mode 100755 index 00000000..2c7408d8 --- /dev/null +++ b/swift-api-examples/run-tts-kokoro-en.sh @@ -0,0 +1,37 @@ +#!/usr/bin/env bash + +set -ex + +if [ ! -d ../build-swift-macos ]; then + echo "Please run ../build-swift-macos.sh first!" + exit 1 +fi + +# please visit +# https://k2-fsa.github.io/sherpa/onnx/tts/pretrained_models/kokoro.html +# to download more models +if [ ! -f ./kokoro-en-v0_19/model.onnx ]; then + curl -SL -O https://github.com/k2-fsa/sherpa-onnx/releases/download/tts-models/kokoro-en-v0_19.tar.bz2 + tar xf kokoro-en-v0_19.tar.bz2 + rm kokoro-en-v0_19.tar.bz2 +fi + +if [ ! -e ./tts-kokoro-en ]; then + # Note: We use -lc++ to link against libc++ instead of libstdc++ + swiftc \ + -lc++ \ + -I ../build-swift-macos/install/include \ + -import-objc-header ./SherpaOnnx-Bridging-Header.h \ + ./tts-kokoro-en.swift ./SherpaOnnx.swift \ + -L ../build-swift-macos/install/lib/ \ + -l sherpa-onnx \ + -l onnxruntime \ + -o tts-kokoro-en + + strip tts-kokoro-en +else + echo "./tts-kokoro-en exists - skip building" +fi + +export DYLD_LIBRARY_PATH=$PWD/../build-swift-macos/install/lib:$DYLD_LIBRARY_PATH +./tts-kokoro-en diff --git a/swift-api-examples/run-tts-matcha-en.sh b/swift-api-examples/run-tts-matcha-en.sh index 1f23f56e..f472b090 100755 --- a/swift-api-examples/run-tts-matcha-en.sh +++ b/swift-api-examples/run-tts-matcha-en.sh @@ -21,7 +21,7 @@ if [ ! -f ./hifigan_v2.onnx ]; then curl -SL -O https://github.com/k2-fsa/sherpa-onnx/releases/download/vocoder-models/hifigan_v2.onnx fi -if [ ! -e ./tts ]; then +if [ ! -e ./tts-matcha-en ]; then # Note: We use -lc++ to link against libc++ instead of libstdc++ swiftc \ -lc++ \ diff --git a/swift-api-examples/run-tts-matcha-zh.sh b/swift-api-examples/run-tts-matcha-zh.sh index decbbde4..5d4f75c1 100755 --- a/swift-api-examples/run-tts-matcha-zh.sh +++ b/swift-api-examples/run-tts-matcha-zh.sh @@ -20,7 +20,7 @@ if [ ! -f ./hifigan_v2.onnx ]; then curl -SL -O https://github.com/k2-fsa/sherpa-onnx/releases/download/vocoder-models/hifigan_v2.onnx fi -if [ ! -e ./tts ]; then +if [ ! -e ./tts-matcha-zh ]; then # Note: We use -lc++ to link against libc++ instead of libstdc++ swiftc \ -lc++ \ diff --git a/swift-api-examples/run-tts-vits.sh b/swift-api-examples/run-tts-vits.sh index 4f385bd7..b4722c0a 100755 --- a/swift-api-examples/run-tts-vits.sh +++ b/swift-api-examples/run-tts-vits.sh @@ -15,7 +15,7 @@ if [ ! -d ./vits-piper-en_US-amy-low ]; then rm vits-piper-en_US-amy-low.tar.bz2 fi -if [ ! -e ./tts ]; then +if [ ! -e ./tts-vits ]; then # Note: We use -lc++ to link against libc++ instead of libstdc++ swiftc \ -lc++ \ diff --git a/swift-api-examples/tts-kokoro-en.swift b/swift-api-examples/tts-kokoro-en.swift new file mode 100644 index 00000000..a0459cf8 --- /dev/null +++ b/swift-api-examples/tts-kokoro-en.swift @@ -0,0 +1,65 @@ +class MyClass { + func playSamples(samples: [Float]) { + print("Play \(samples.count) samples") + } +} + +func run() { + let model = "./kokoro-en-v0_19/model.onnx" + let voices = "./kokoro-en-v0_19/voices.bin" + let tokens = "./kokoro-en-v0_19/tokens.txt" + let dataDir = "./kokoro-en-v0_19/espeak-ng-data" + let kokoro = sherpaOnnxOfflineTtsKokoroModelConfig( + model: model, + voices: voices, + tokens: tokens, + dataDir: dataDir + ) + let modelConfig = sherpaOnnxOfflineTtsModelConfig(kokoro: kokoro, debug: 0) + var ttsConfig = sherpaOnnxOfflineTtsConfig(model: modelConfig) + + let myClass = MyClass() + + // We use Unretained here so myClass must be kept alive as the callback is invoked + // + // See also + // https://medium.com/codex/swift-c-callback-interoperability-6d57da6c8ee6 + let arg = Unmanaged.passUnretained(myClass).toOpaque() + + let callback: TtsCallbackWithArg = { samples, n, arg in + let o = Unmanaged.fromOpaque(arg!).takeUnretainedValue() + var savedSamples: [Float] = [] + for index in 0..