diff --git a/app/build.gradle.kts b/app/build.gradle.kts index efcaf35..41a6038 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -11,8 +11,8 @@ android { applicationId = "net.mmanningau.speechtokeyboard" minSdk = 28 targetSdk = 36 - versionCode = 9 - versionName = "1.0" + versionCode = 10 + versionName = "1.1" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" } diff --git a/app/src/main/ic_launcher-playstore.png b/app/src/main/ic_launcher-playstore.png new file mode 100644 index 0000000..c63de09 Binary files /dev/null and b/app/src/main/ic_launcher-playstore.png differ diff --git a/app/src/main/java/net/mmanningau/speechtokeyboard/MainActivity.kt b/app/src/main/java/net/mmanningau/speechtokeyboard/MainActivity.kt index ed3a182..6d7b73d 100644 --- a/app/src/main/java/net/mmanningau/speechtokeyboard/MainActivity.kt +++ b/app/src/main/java/net/mmanningau/speechtokeyboard/MainActivity.kt @@ -89,7 +89,7 @@ class MainActivity : AppCompatActivity() { var entry = zipInputStream.nextEntry var foundEncoder = false var foundDecoder = false - var foundJoiner = false + // var foundJoiner = false - removed for true Whisper model use var foundTokens = false while (entry != null) { @@ -100,7 +100,7 @@ class MainActivity : AppCompatActivity() { val targetFileName = when { name.contains("encoder") && name.endsWith(".onnx") -> "encoder.onnx" name.contains("decoder") && name.endsWith(".onnx") -> "decoder.onnx" - name.contains("joiner") && name.endsWith(".onnx") -> "joiner.onnx" + // name.contains("joiner") && name.endsWith(".onnx") -> "joiner.onnx" - removed for true Whisper model use name.contains("tokens.txt") -> "tokens.txt" else -> null } @@ -115,7 +115,7 @@ class MainActivity : AppCompatActivity() { when (targetFileName) { "encoder.onnx" -> foundEncoder = true "decoder.onnx" -> foundDecoder = true - "joiner.onnx" -> foundJoiner = true + // "joiner.onnx" -> foundJoiner = true = re,moved for true Whisper model use "tokens.txt" -> foundTokens = true } } @@ -124,7 +124,8 @@ class MainActivity : AppCompatActivity() { } runOnUiThread { - if (foundEncoder && foundDecoder && foundJoiner && foundTokens) { + // if (foundEncoder && foundDecoder && foundJoiner && foundTokens) { - removed for true Whisper model use + if (foundEncoder && foundDecoder && foundTokens) { statusText.text = "Model Installed Successfully!" Toast.makeText(this, "Ready to use!", Toast.LENGTH_SHORT).show() } else { diff --git a/app/src/main/java/net/mmanningau/speechtokeyboard/TestModelActivity.kt b/app/src/main/java/net/mmanningau/speechtokeyboard/TestModelActivity.kt index a7ef7a3..1e35d4e 100644 --- a/app/src/main/java/net/mmanningau/speechtokeyboard/TestModelActivity.kt +++ b/app/src/main/java/net/mmanningau/speechtokeyboard/TestModelActivity.kt @@ -20,11 +20,23 @@ import com.hoho.android.usbserial.driver.UsbSerialProber import com.hoho.android.usbserial.util.SerialInputOutputManager import com.k2fsa.sherpa.onnx.EndpointConfig import com.k2fsa.sherpa.onnx.EndpointRule +/* import com.k2fsa.sherpa.onnx.FeatureConfig +import com.k2fsa.sherpa.onnx.OnlineModelConfig import com.k2fsa.sherpa.onnx.OnlineRecognizer import com.k2fsa.sherpa.onnx.OnlineRecognizerConfig import com.k2fsa.sherpa.onnx.OnlineTransducerModelConfig import com.k2fsa.sherpa.onnx.OnlineStream + + */ +// Below for the "offline" libraries and the true Whisper integration +import com.k2fsa.sherpa.onnx.OfflineRecognizer +import com.k2fsa.sherpa.onnx.OfflineStream +import com.k2fsa.sherpa.onnx.OfflineRecognizerConfig +import com.k2fsa.sherpa.onnx.OfflineModelConfig +import com.k2fsa.sherpa.onnx.OfflineWhisperModelConfig +import com.k2fsa.sherpa.onnx.FeatureConfig + import java.io.File class TestModelActivity : AppCompatActivity() { @@ -34,8 +46,10 @@ class TestModelActivity : AppCompatActivity() { private lateinit var micButton: ImageButton // Sherpa (Whisper) Components - private var recognizer: OnlineRecognizer? = null - private var stream: OnlineStream? = null + // private var recognizer: OnlineRecognizer? = null // - Removed for true Whisper model usa + // private var stream: OnlineStream? = null // - Removed for true Whisper model usa + private var recognizer: OfflineRecognizer? = null // Was OnlineRecognizer + private var stream: OfflineStream? = null // Was OnlineStream private var isRecording = false private var recordingThread: Thread? = null @@ -76,46 +90,37 @@ class TestModelActivity : AppCompatActivity() { return } + // 1. Point to your files + val encoderPath = File(modelDir, "encoder.onnx").absolutePath + val decoderPath = File(modelDir, "decoder.onnx").absolutePath + val tokensPath = File(modelDir, "tokens.txt").absolutePath + try { - // 1. Define Model Paths - val transducerConfig = OnlineTransducerModelConfig( - encoder = File(modelDir, "encoder.onnx").absolutePath, - decoder = File(modelDir, "decoder.onnx").absolutePath, - joiner = File(modelDir, "joiner.onnx").absolutePath - ) - - // 2. Define General Config - val onlineModelConfig = com.k2fsa.sherpa.onnx.OnlineModelConfig( - transducer = transducerConfig, - tokens = File(modelDir, "tokens.txt").absolutePath, - numThreads = 1, - debug = false, - modelType = "zipformer" - ) - - // 3. Define Endpoint Rule (The fix for your error) - // rule1 = detected silence after speech. We set this to 2.4 seconds. - val silenceRule = EndpointRule( - mustContainNonSilence = false, - minTrailingSilence = 2.4f, - minUtteranceLength = 0.0f - ) - - // 4. Create Recognizer Config - val config = OnlineRecognizerConfig( + // CONFIGURATION FOR WHISPER (OFFLINE) + val config = OfflineRecognizerConfig( featConfig = FeatureConfig(sampleRate = 16000, featureDim = 80), - modelConfig = onlineModelConfig, - endpointConfig = EndpointConfig(rule1 = silenceRule), // Pass the rule object here - enableEndpoint = true, + modelConfig = OfflineModelConfig( + // This parameter 'whisper' exists here! + whisper = OfflineWhisperModelConfig( + encoder = encoderPath, + decoder = decoderPath, + // tokenizer is not strictly needed in config here if passed in tokens param below + // but usually standard offline config uses just these two: + ), + tokens = tokensPath, + modelType = "whisper", + debug = false, + numThreads = 1 + ), decodingMethod = "greedy_search", maxActivePaths = 4 ) - // recognizer = OnlineRecognizer(assetManager = assets, config = config) - recognizer = OnlineRecognizer(config = config) + // Initialize OFFLINE Engine + recognizer = OfflineRecognizer(config = config) stream = recognizer?.createStream() - outputText.text = "Engine Loaded. Ready to Stream." + outputText.text = "Whisper Engine Ready." } catch (e: Exception) { Log.e("Sherpa", "Init Error", e) @@ -156,21 +161,46 @@ class TestModelActivity : AppCompatActivity() { private fun stopRecording() { isRecording = false - recordingThread?.join() + try { + recordingThread?.join() // Wait for loop to finish + } catch (e: InterruptedException) { + // Handle interruption if necessary + } + micButton.clearColorFilter() - // Just show what we have, don't overwrite with "[Stopped]" - // to prevent visual jarring. - outputText.append("\n[Stopped]") + // FIX: Safely unwrap 'stream' before passing it to getResult + // This reads: "If stream is NOT null, call getResult. Otherwise return empty string." + val finalCurrentText = stream?.let { activeStream -> + recognizer?.getResult(activeStream)?.text + } ?: "" + + val cleanFinal = finalCurrentText.lowercase() + + if (cleanFinal.isNotEmpty()) { + // 1. Commit to history + committedText += "$cleanFinal " + + // 2. Send to Pico + sendToPico("$cleanFinal ") + + // 3. Update UI + outputText.text = "$committedText \n[Stopped]" + + // 4. Reset for next time + // We release the old stream and create a fresh one for the next sentence + stream?.release() + stream = recognizer?.createStream() + } else { + outputText.append("\n[Stopped - No Text]") + } } private fun processAudioLoop() { val sampleRate = 16000 val bufferSize = AudioRecord.getMinBufferSize(sampleRate, AudioFormat.CHANNEL_IN_MONO, AudioFormat.ENCODING_PCM_16BIT) - // 1. GUARD CLAUSE: Unpack nullables safely - // If recognizer or stream are null, we stop immediately. - // This creates 'localRec' and 'localStream' which are GUARANTEED non-null. + // 1. GUARD CLAUSE (Safely unwrap nullables) val localRec = recognizer ?: return val localStream = stream ?: return @@ -188,28 +218,24 @@ class TestModelActivity : AppCompatActivity() { if (ret > 0) { val samples = FloatArray(ret) { buffer[it] / 32768.0f } - // 2. Use the LOCAL (non-null) variables + // 2. Feed Audio localStream.acceptWaveform(samples, sampleRate) - while (localRec.isReady(localStream)) { - localRec.decode(localStream) - } + // 3. Decode (No isReady check needed for Offline) + localRec.decode(localStream) + // 4. Get Current Text + // Whisper updates this string constantly as it hears more val text = localRec.getResult(localStream).text - val isEndpoint = localRec.isEndpoint(localStream) if (text.isNotEmpty()) { val cleanText = text.lowercase() runOnUiThread { - if (isEndpoint) { - committedText += "$cleanText " - outputText.text = committedText - sendToPico("$cleanText ") - localRec.reset(localStream) - } else { - outputText.text = "$committedText $cleanText" - } + // Update the screen so user sees what is happening + // We do NOT send to USB yet, because Whisper might change this text + // as you keep speaking. + outputText.text = "$committedText $cleanText" } } } diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml new file mode 100644 index 0000000..036d09b --- /dev/null +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml new file mode 100644 index 0000000..036d09b --- /dev/null +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/mipmap-anydpi/ic_launcher.xml b/app/src/main/res/mipmap-anydpi/ic_launcher.xml deleted file mode 100644 index 6f3b755..0000000 --- a/app/src/main/res/mipmap-anydpi/ic_launcher.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/app/src/main/res/mipmap-anydpi/ic_launcher_round.xml b/app/src/main/res/mipmap-anydpi/ic_launcher_round.xml deleted file mode 100644 index 6f3b755..0000000 --- a/app/src/main/res/mipmap-anydpi/ic_launcher_round.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher.webp b/app/src/main/res/mipmap-hdpi/ic_launcher.webp index c209e78..bc80388 100644 Binary files a/app/src/main/res/mipmap-hdpi/ic_launcher.webp and b/app/src/main/res/mipmap-hdpi/ic_launcher.webp differ diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.webp b/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.webp new file mode 100644 index 0000000..0020c64 Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.webp differ diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp index b2dfe3d..6f29998 100644 Binary files a/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp and b/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp differ diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher.webp b/app/src/main/res/mipmap-mdpi/ic_launcher.webp index 4f0f1d6..db7b700 100644 Binary files a/app/src/main/res/mipmap-mdpi/ic_launcher.webp and b/app/src/main/res/mipmap-mdpi/ic_launcher.webp differ diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.webp b/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.webp new file mode 100644 index 0000000..7e26258 Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.webp differ diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp index 62b611d..73ab348 100644 Binary files a/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp and b/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher.webp b/app/src/main/res/mipmap-xhdpi/ic_launcher.webp index 948a307..36f91c4 100644 Binary files a/app/src/main/res/mipmap-xhdpi/ic_launcher.webp and b/app/src/main/res/mipmap-xhdpi/ic_launcher.webp differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.webp b/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.webp new file mode 100644 index 0000000..2531c92 Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.webp differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp index 1b9a695..cdf0cab 100644 Binary files a/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp and b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp b/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp index 28d4b77..6b7c6e6 100644 Binary files a/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp and b/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.webp b/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.webp new file mode 100644 index 0000000..c5d2d7e Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.webp differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp index 9287f50..13024dd 100644 Binary files a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp and b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp index aa7d642..a53a10c 100644 Binary files a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.webp b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.webp new file mode 100644 index 0000000..cb1a443 Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.webp differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp index 9126ae3..98c334b 100644 Binary files a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp differ diff --git a/app/src/main/res/values/ic_launcher_background.xml b/app/src/main/res/values/ic_launcher_background.xml new file mode 100644 index 0000000..8be8333 --- /dev/null +++ b/app/src/main/res/values/ic_launcher_background.xml @@ -0,0 +1,4 @@ + + + #0878F5 + \ No newline at end of file