diff --git a/gearslip/src/main/java/app/seb3thehacker/gearslip/speech/SpeechEngine.kt b/gearslip/src/main/java/app/seb3thehacker/gearslip/speech/SpeechEngine.kt index 6b8de85..525c55c 100644 --- a/gearslip/src/main/java/app/seb3thehacker/gearslip/speech/SpeechEngine.kt +++ b/gearslip/src/main/java/app/seb3thehacker/gearslip/speech/SpeechEngine.kt @@ -17,6 +17,7 @@ import app.seb3thehacker.gearslip.audio.CarMic import com.k2fsa.sherpa.onnx.EndpointConfig import com.k2fsa.sherpa.onnx.EndpointRule import com.k2fsa.sherpa.onnx.FeatureConfig +import com.k2fsa.sherpa.onnx.OfflineCanaryModelConfig import com.k2fsa.sherpa.onnx.OfflineModelConfig import com.k2fsa.sherpa.onnx.OfflineRecognizer import com.k2fsa.sherpa.onnx.OfflineRecognizerConfig @@ -284,12 +285,21 @@ object SpeechEngine { OfflineRecognizer( config = OfflineRecognizerConfig( featConfig = FeatureConfig(sampleRate = RATE, featureDim = 80), - modelConfig = OfflineModelConfig( - transducer = OfflineTransducerModelConfig(path(model.encoder), path(model.decoder), path(model.joiner)), - tokens = path("tokens.txt"), - numThreads = threads(), - modelType = model.modelType, - ), + modelConfig = if (model.modelType == "canary") { + // English in, English out, and no punctuation: clean() strips it anyway. + OfflineModelConfig( + canary = OfflineCanaryModelConfig(path(model.encoder), path(model.decoder), srcLang = "en", tgtLang = "en", usePnc = false), + tokens = path("tokens.txt"), + numThreads = threads(), + ) + } else { + OfflineModelConfig( + transducer = OfflineTransducerModelConfig(path(model.encoder), path(model.decoder), path(model.joiner)), + tokens = path("tokens.txt"), + numThreads = threads(), + modelType = model.modelType, + ) + }, decodingMethod = "greedy_search", ), ) diff --git a/gearslip/src/main/java/app/seb3thehacker/gearslip/speech/SpeechModels.kt b/gearslip/src/main/java/app/seb3thehacker/gearslip/speech/SpeechModels.kt index 06484d4..74437fb 100644 --- a/gearslip/src/main/java/app/seb3thehacker/gearslip/speech/SpeechModels.kt +++ b/gearslip/src/main/java/app/seb3thehacker/gearslip/speech/SpeechModels.kt @@ -31,6 +31,7 @@ object SpeechModels { private val archive: String, val encoder: String, val decoder: String, + /** Empty for Canary, which decodes with attention and has no joiner. */ val joiner: String, val modelType: String = "", ) { @@ -44,6 +45,11 @@ object SpeechModels { "sherpa-onnx-nemo-parakeet_tdt_transducer_110m-en-36000-int8", "encoder.int8.onnx", "decoder.int8.onnx", "joiner.int8.onnx", modelType = "nemo_transducer", ), + CANARY( + Role.FINAL, "canary-180m-en", "Canary", "In between: better than Parakeet small, quicker than large. NVIDIA NeMo, CC BY 4.0", 154, + "sherpa-onnx-nemo-canary-180m-flash-en-es-de-fr-int8", + "encoder.int8.onnx", "decoder.int8.onnx", "", modelType = "canary", + ), PARAKEET_LARGE( Role.FINAL, "parakeet-0.6b-en", "Parakeet large", "Recommended. Hears names and places best. NVIDIA NeMo, CC BY 4.0", 460, "sherpa-onnx-nemo-parakeet-tdt-0.6b-v2-int8", @@ -52,7 +58,7 @@ object SpeechModels { ; val url get() = "https://github.com/k2-fsa/sherpa-onnx/releases/download/asr-models/$archive.tar.bz2" - val files get() = listOf(encoder, decoder, joiner, "tokens.txt") + val files get() = listOf(encoder, decoder, joiner, "tokens.txt").filter { it.isNotEmpty() } } sealed interface State { diff --git a/gearslip/src/main/java/app/seb3thehacker/gearslip/ui/SupportViews.kt b/gearslip/src/main/java/app/seb3thehacker/gearslip/ui/SupportViews.kt index 7599961..4d8f22b 100644 --- a/gearslip/src/main/java/app/seb3thehacker/gearslip/ui/SupportViews.kt +++ b/gearslip/src/main/java/app/seb3thehacker/gearslip/ui/SupportViews.kt @@ -155,7 +155,7 @@ internal fun AboutLinkRows() { SettingsRow( "Voice credits", subtitle = "Speech recognition by sherpa-onnx (Apache 2.0), with Kroko ASR by Banafo " + - "(CC BY-SA 4.0) and Parakeet by NVIDIA (CC BY 4.0)", + "(CC BY-SA 4.0), and Parakeet and Canary by NVIDIA (CC BY 4.0)", icon = Icons.Filled.Favorite, ) }