Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -164,3 +164,4 @@ sherpa-onnx-qnn-10-seconds-zipformer-ctc-zh-2025-07-03-int8-android-aarch64
build-riscv64-linux-gnu-spacemit/
spacemit-toolchain*
sherpa-onnx-qnn-*
matcha-icefall-*
Original file line number Diff line number Diff line change
Expand Up @@ -294,6 +294,15 @@ class MainActivity : AppCompatActivity() {
// dataDir = "kokoro-multi-lang-v1_0/espeak-ng-data"
// isKitten = true

// Example 12
// matcha-icefall-zh-en
// https://k2-fsa.github.io/sherpa/onnx/tts/all/Chinese-English/matcha-icefall-zh-en.html
// modelDir = "matcha-icefall-zh-en"
// acousticModelName = "model-steps-3.onnx"
// vocoder = "vocos-16khz-univ.onnx" // Vocoder should be downloaded separately; place in the **root directory of your resources folder**, not under modelDir.
// dataDir = "matcha-icefall-zh-en/espeak-ng-data"
// lexicon = "lexicon.txt"

if (dataDir != null) {
val newDir = copyDataDir(dataDir!!)
dataDir = "$newDir/$dataDir"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ import androidx.compose.ui.unit.dp
import com.k2fsa.sherpa.onnx.tts.engine.ui.theme.SherpaOnnxTtsEngineTheme
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
Expand All @@ -64,7 +65,9 @@ class MainActivity : ComponentActivity() {

private var stopped: Boolean = false

private var samplesChannel = Channel<FloatArray>()
private var samplesChannel = Channel<FloatArray>(capacity = 128)
private val scope = CoroutineScope(Dispatchers.IO + SupervisorJob())


override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
Expand Down Expand Up @@ -177,8 +180,16 @@ class MainActivity : ComponentActivity() {
rtfText = ""
Log.i(TAG, "Started with text $testText")

CoroutineScope(Dispatchers.IO).launch {
scope.launch {
for (samples in samplesChannel) {
if (samples.isEmpty()) {
break
}

Log.i(
TAG,
"Received ${samples.count()} samples"
)
track.write(
samples,
0,
Expand All @@ -189,10 +200,14 @@ class MainActivity : ComponentActivity() {
break
}
}
Log.i(TAG, "Draining the channel")

for (s in samplesChannel) {
// drain the channel
// drain remaining
while (!samplesChannel.isEmpty) {
samplesChannel.tryReceive().getOrNull()
}
Log.i(TAG, "Channel drained")

}

CoroutineScope(Dispatchers.Default).launch {
Expand Down Expand Up @@ -222,6 +237,12 @@ class MainActivity : ComponentActivity() {
elapsed / audioDuration
)

scope.launch {
Log.i(TAG, "send 0 samples")
samplesChannel.send(FloatArray(0))
Log.i(TAG, "send 0 samples done")
}

val filename =
application.filesDir.absolutePath + "/generated.wav"

Expand All @@ -237,8 +258,10 @@ class MainActivity : ComponentActivity() {
playEnabled = true
rtfText = RTF
}


}
}.start()
}
}
}) {
Text("Start")
Expand Down Expand Up @@ -311,8 +334,10 @@ class MainActivity : ComponentActivity() {
private fun callback(samples: FloatArray): Int {
if (!stopped) {
val samplesCopy = samples.copyOf()
CoroutineScope(Dispatchers.IO).launch {
samplesChannel.send(samplesCopy)
scope.launch {
Log.i(TAG, "callback called with ${samplesCopy.count()} samples")
val ok = samplesChannel.trySend(samplesCopy).isSuccess
Log.i(TAG, "callback called with $ok")
Comment on lines +339 to +340

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

While logging the success status of trySend is useful for debugging, the log message could be more informative, especially for the failure case. When trySend fails, audio samples are dropped, which can lead to degraded audio quality. It would be better to log this as a warning to highlight the data loss.

Consider checking the return value and logging a warning if the send operation was not successful.

                val ok = samplesChannel.trySend(samplesCopy).isSuccess
                if (!ok) {
                    Log.w(TAG, "Channel buffer is full. Dropping audio samples.")
                }

}
return 1
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,16 @@ object TtsEngine {
// dataDir = "kitten-nano-en-v0_1-fp16/espeak-ng-data"
// lang = "eng"
// isKitten = true

// Example 12
// matcha-icefall-zh-en
// https://k2-fsa.github.io/sherpa/onnx/tts/all/Chinese-English/matcha-icefall-zh-en.html
// modelDir = "matcha-icefall-zh-en"
// acousticModelName = "model-steps-3.onnx"
// vocoder = "vocos-16khz-univ.onnx"
// dataDir = "matcha-icefall-zh-en/espeak-ng-data"
// lexicon = "lexicon.txt"
// lang = "zho"
}

fun createTts(context: Context) {
Expand Down
4 changes: 4 additions & 0 deletions scripts/apk/build-apk-tts-engine.sh.in
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,10 @@ if [[ $model_dir == vits-melo-tts-zh_en ]]; then
lang=zh_en
fi

if [[ $model_dir == matcha-icefall-zh-en ]]; then
lang=zh_en
fi
Comment on lines +116 to +118

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

This adds another if block to set lang=zh_en. To improve readability and maintainability, consider grouping all conditions that set lang to zh_en into a single control structure, like a case statement. This would make the script easier to read and modify in the future.

For example:

case $model_dir in
  vits-melo-tts-zh_en|\
  matcha-icefall-zh-en|\
  kokoro-multi-lang-v1_0|\
  kokoro-multi-lang-v1_1|\
  kokoro-int8-multi-lang-v1_1)
    lang=zh_en
    ;;
esac


if [[ $model_dir == kokoro-multi-lang-v1_0 || $model_dir == kokoro-multi-lang-v1_1 || $model_dir == kokoro-int8-multi-lang-v1_1 ]]; then
lang=zh_en
fi
Expand Down
4 changes: 4 additions & 0 deletions scripts/apk/build-apk-tts.sh.in
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,10 @@ if [[ $model_dir == vits-melo-tts-zh_en ]]; then
lang=zh_en
fi

if [[ $model_dir == matcha-icefall-zh-en ]]; then
lang=zh_en
fi
Comment on lines +110 to +112

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

This adds another if block to set lang=zh_en. To improve readability and maintainability, consider grouping all conditions that set lang to zh_en into a single control structure, like a case statement. This would make the script easier to read and modify in the future.

For example:

case $model_dir in
  vits-melo-tts-zh_en|\
  matcha-icefall-zh-en|\
  kokoro-multi-lang-v1_0|\
  kokoro-multi-lang-v1_1|\
  kokoro-int8-multi-lang-v1_1)
    lang=zh_en
    ;;
esac


if [[ $model_dir == kokoro-multi-lang-v1_0 || $model_dir == kokoro-multi-lang-v1_1 || $model_dir == kokoro-int8-multi-lang-v1_1 ]]; then
lang=zh_en
fi
Expand Down
21 changes: 17 additions & 4 deletions scripts/apk/generate-tts-apk-script.py
Original file line number Diff line number Diff line change
Expand Up @@ -411,7 +411,6 @@ def get_vits_models() -> List[TtsModel]:
or "melo-tts" in m.model_dir
):
s = s[:-1]
m.dict_dir = m.model_dir + "/dict"
else:
m.rule_fars = f"{m.model_dir}/rule.far"

Expand Down Expand Up @@ -440,15 +439,30 @@ def get_matcha_models() -> List[TtsModel]:
model_dir="matcha-icefall-zh-baker",
acoustic_model_name="model-steps-3.onnx",
lang="zh",
lexicon="lexicon.txt",
)
]
rule_fsts = ["phone.fst", "date.fst", "number.fst"]
for m in chinese_models:
s = [f"{m.model_dir}/{r}" for r in rule_fsts]
m.rule_fsts = ",".join(s)
m.dict_dir = m.model_dir + "/dict"
m.vocoder = "vocos-22khz-univ.onnx"

chinese_english_models = [
TtsModel(
model_dir="matcha-icefall-zh-en",
acoustic_model_name="model-steps-3.onnx",
lang="zh",
lexicon="lexicon.txt",
)
]
rule_fsts_zh = ["phone-zh.fst", "date-zh.fst", "number-zh.fst"]
for m in chinese_english_models:
s = [f"{m.model_dir}/{r}" for r in rule_fsts_zh]
m.rule_fsts = ",".join(s)
m.vocoder = "vocos-16khz-univ.onnx"
m.data_dir = f"{m.model_dir}/espeak-ng-data"

english_persian_models = [
TtsModel(
model_dir="matcha-icefall-en_US-ljspeech",
Expand All @@ -470,7 +484,7 @@ def get_matcha_models() -> List[TtsModel]:
m.data_dir = f"{m.model_dir}/espeak-ng-data"
m.vocoder = "vocos-22khz-univ.onnx"

return chinese_models + english_persian_models
return chinese_models + english_persian_models + chinese_english_models


def get_kokoro_models() -> List[TtsModel]:
Expand Down Expand Up @@ -507,7 +521,6 @@ def get_kokoro_models() -> List[TtsModel]:
]
for m in multi_lingual_models:
m.data_dir = f"{m.model_dir}/espeak-ng-data"
m.dict_dir = f"{m.model_dir}/dict"
m.voices = "voices.bin"
m.lexicon = f"{m.model_dir}/lexicon-us-en.txt,{m.model_dir}/lexicon-zh.txt"
m.rule_fsts = f"{m.model_dir}/phone-zh.fst,{m.model_dir}/date-zh.fst,{m.model_dir}/number-zh.fst"
Expand Down