4 Commits

25 changed files with 79 additions and 29 deletions

View File

@@ -4,10 +4,10 @@
<selectionStates>
<SelectionState runConfigName="app">
<option name="selectionMode" value="DROPDOWN" />
<DropdownSelection timestamp="2026-01-22T04:36:45.393638454Z">
<DropdownSelection timestamp="2026-01-23T01:29:57.710335816Z">
<Target type="DEFAULT_BOOT">
<handle>
<DeviceId pluginId="LocalEmulator" identifier="path=/home/michael/.android/avd/Pixel_5_API_31_Android_12_.avd" />
<DeviceId pluginId="PhysicalDevice" identifier="serial=DKTAB13NEU0019483" />
</handle>
</Target>
</DropdownSelection>

View File

@@ -11,8 +11,8 @@ android {
applicationId = "net.mmanningau.speechtokeyboard"
minSdk = 28
targetSdk = 36
versionCode = 9
versionName = "1.0"
versionCode = 12
versionName = "1.1"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
}
@@ -25,6 +25,11 @@ android {
"proguard-rules.pro"
)
}
debug {
applicationIdSuffix = ".streaming"
// This changes the app name on your homescreen to "MyApp (Dev)"
resValue("string", "app_name", "Speech To Keyboard (Streaming)")
}
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_11

View File

@@ -37,6 +37,8 @@
<activity
android:name=".TestModelActivity"
android:parentActivityName=".MainActivity"
android:exported="false"
android:configChanges="orientation|screenSize|screenLayout|keyboardHidden"
android:label="Test Microphone" />
</application>

Binary file not shown.

After

Width:  |  Height:  |  Size: 411 KiB

View File

@@ -27,6 +27,10 @@ import com.k2fsa.sherpa.onnx.OnlineTransducerModelConfig
import com.k2fsa.sherpa.onnx.OnlineStream
import java.io.File
import com.k2fsa.sherpa.onnx.OfflinePunctuation
import com.k2fsa.sherpa.onnx.OfflinePunctuationConfig
import com.k2fsa.sherpa.onnx.OfflinePunctuationModelConfig
class TestModelActivity : AppCompatActivity() {
// UI Components
@@ -39,6 +43,9 @@ class TestModelActivity : AppCompatActivity() {
private var isRecording = false
private var recordingThread: Thread? = null
// Punctuation variables
private var punctuator: OfflinePunctuation? = null
// USB Components
private var usbPort: UsbSerialPort? = null
@@ -117,6 +124,23 @@ class TestModelActivity : AppCompatActivity() {
outputText.text = "Engine Loaded. Ready to Stream."
// ... existing recognizer init code ...
// 5. Initialize Punctuation Engine
val punctPath = File(modelDir, "punct_model.onnx").absolutePath
if (File(punctPath).exists()) {
// CORRECTED: Wrap the path inside 'OfflinePunctuationModelConfig'
val punctConfig = OfflinePunctuationConfig(
model = OfflinePunctuationModelConfig(ctTransformer = punctPath)
)
punctuator = OfflinePunctuation(config = punctConfig)
outputText.append("\n+ Punctuation Ready")
} else {
outputText.append("\n(No Punctuation model found)")
}
} catch (e: Exception) {
Log.e("Sherpa", "Init Error", e)
outputText.text = "Init Error: ${e.message}"
@@ -140,9 +164,12 @@ class TestModelActivity : AppCompatActivity() {
return
}
// Reset the stream for a new session
// Note: Sherpa streams can be persistent, but resetting ensures clean start
// If you want continuous conversation, don't reset 'committedText'
// FIX 1: CLEAR THE BUFFER
// This prevents the "ghost text" from the previous session appearing
// when you hit record again.
stream?.let { activeStream ->
recognizer?.reset(activeStream)
}
isRecording = true
micButton.setColorFilter(android.graphics.Color.RED)
@@ -168,9 +195,7 @@ class TestModelActivity : AppCompatActivity() {
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.
// Guard clauses
val localRec = recognizer ?: return
val localStream = stream ?: return
@@ -188,7 +213,6 @@ class TestModelActivity : AppCompatActivity() {
if (ret > 0) {
val samples = FloatArray(ret) { buffer[it] / 32768.0f }
// 2. Use the LOCAL (non-null) variables
localStream.acceptWaveform(samples, sampleRate)
while (localRec.isReady(localStream)) {
@@ -201,13 +225,30 @@ class TestModelActivity : AppCompatActivity() {
if (text.isNotEmpty()) {
val cleanText = text.lowercase()
runOnUiThread {
if (isEndpoint) {
committedText += "$cleanText "
if (isEndpoint) {
// FIX 2: THE ORDER OF OPERATIONS
// A. Update UI first
// 1. PUNCTUATE
// We pass the raw text to the punctuator
val punctuatedText = punctuator?.addPunctuation(cleanText) ?: cleanText
runOnUiThread {
// 2. Commit the BEAUTIFUL text
committedText += "$punctuatedText "
outputText.text = committedText
sendToPico("$cleanText ")
localRec.reset(localStream)
} else {
sendToPico("$punctuatedText ")
}
// B. RESET IMMEDIATELY ON BACKGROUND THREAD
// We do this HERE, not inside runOnUiThread.
// This guarantees the stream is clean BEFORE the loop
// reads the next chunk of audio.
localRec.reset(localStream)
} else {
// Standard partial update
runOnUiThread {
outputText.text = "$committedText $cleanText"
}
}

View File

@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/ic_launcher_background"/>
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
</adaptive-icon>

View File

@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/ic_launcher_background"/>
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
</adaptive-icon>

View File

@@ -1,6 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background" />
<foreground android:drawable="@drawable/ic_launcher_foreground" />
<monochrome android:drawable="@drawable/ic_launcher_foreground" />
</adaptive-icon>

View File

@@ -1,6 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background" />
<foreground android:drawable="@drawable/ic_launcher_foreground" />
<monochrome android:drawable="@drawable/ic_launcher_foreground" />
</adaptive-icon>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

After

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 982 B

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 6.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 KiB

After

Width:  |  Height:  |  Size: 8.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.8 KiB

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 KiB

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 79 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.6 KiB

After

Width:  |  Height:  |  Size: 25 KiB

View File

@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="ic_launcher_background">#0878F5</color>
</resources>