From 06bd5ca624b4bea03fb132e4e11f69c7a1b046b2 Mon Sep 17 00:00:00 2001
From: clocks <doomsdayrs@gmail.com>
Date: Wed, 19 Jun 2024 15:10:27 -0400
Subject: [PATCH 1/6] Do not tap into the executors or Handlers

Bad practice, we can use kotlin coroutines instead.
---
 .../kotlin/org/vosk/android/StorageService.kt | 27 ++++++++++---------
 1 file changed, 15 insertions(+), 12 deletions(-)

diff --git a/kotlin/src/androidMain/kotlin/org/vosk/android/StorageService.kt b/kotlin/src/androidMain/kotlin/org/vosk/android/StorageService.kt
index 75093fcc..557efe1b 100644
--- a/kotlin/src/androidMain/kotlin/org/vosk/android/StorageService.kt
+++ b/kotlin/src/androidMain/kotlin/org/vosk/android/StorageService.kt
@@ -18,13 +18,13 @@ package org.vosk.android
 import android.content.Context
 import android.content.res.AssetManager
 import android.os.Environment
-import android.os.Handler
-import android.os.Looper
 import android.util.Log
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.GlobalScope
+import kotlinx.coroutines.launch
 import org.vosk.Model
+import org.vosk.exception.IOException
 import java.io.*
-import java.util.concurrent.Executor
-import java.util.concurrent.Executors
 import java.util.function.Consumer
 
 /**
@@ -34,6 +34,7 @@ import java.util.function.Consumer
 object StorageService {
 	private val TAG = StorageService::class.simpleName
 
+	@Deprecated("Use your own multi-threading. Or maybe just use Kotlin Coroutines.")
 	@JvmStatic
 	fun unpack(
 		context: Context,
@@ -42,20 +43,22 @@ object StorageService {
 		completeCallback: Consumer<Model>,
 		errorCallback: Consumer<IOException>
 	) {
-		val executor: Executor =
-			Executors.newSingleThreadExecutor() // change according to your requirements
-		val handler = Handler(Looper.getMainLooper())
-		executor.execute {
+		GlobalScope.launch(Dispatchers.IO) {
 			try {
-				val outputPath = sync(context, sourcePath, targetPath)
-				val model = Model(outputPath)
-				handler.post { completeCallback.accept(model) }
+				val model = unpack(context, sourcePath, targetPath)
+				launch(Dispatchers.Main) { completeCallback.accept(model) }
 			} catch (e: IOException) {
-				handler.post { errorCallback.accept(e) }
+				launch(Dispatchers.Main) { errorCallback.accept(e) }
 			}
 		}
 	}
 
+	@Throws(IOException::class)
+	fun unpack(context: Context, sourcePath: String, targetPath: String): Model {
+		val outputPath = sync(context, sourcePath, targetPath)
+		return Model(outputPath)
+	}
+
 	@JvmStatic
 	@Throws(IOException::class)
 	fun sync(context: Context, sourcePath: String, targetPath: String): String {

From d034dcb7283b99e4ec23897a2392298efb751b7d Mon Sep 17 00:00:00 2001
From: clocks <doomsdayrs@gmail.com>
Date: Wed, 19 Jun 2024 15:22:06 -0400
Subject: [PATCH 2/6] SpeechService: Use Coroutines

---
 .../kotlin/org/vosk/android/SpeechService.kt  | 165 ++++++++----------
 1 file changed, 71 insertions(+), 94 deletions(-)

diff --git a/kotlin/src/androidMain/kotlin/org/vosk/android/SpeechService.kt b/kotlin/src/androidMain/kotlin/org/vosk/android/SpeechService.kt
index 300fccb3..78c22bb5 100644
--- a/kotlin/src/androidMain/kotlin/org/vosk/android/SpeechService.kt
+++ b/kotlin/src/androidMain/kotlin/org/vosk/android/SpeechService.kt
@@ -19,8 +19,12 @@ import android.annotation.SuppressLint
 import android.media.AudioFormat
 import android.media.AudioRecord
 import android.media.MediaRecorder.AudioSource
-import android.os.Handler
-import android.os.Looper
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.withContext
 import org.vosk.Recognizer
 import java.io.IOException
 import kotlin.math.roundToInt
@@ -37,8 +41,13 @@ class SpeechService @Throws(IOException::class) constructor(
 	private val sampleRate: Int
 	private val bufferSize: Int
 	private val recorder: AudioRecord
-	private var recognizerThread: RecognizerThread? = null
-	private val mainHandler = Handler(Looper.getMainLooper())
+
+	private val scope = CoroutineScope(Dispatchers.IO)
+	private var recognizerThread: Job? = null
+
+	private var paused = false
+	private var reset = false
+	private var interrupt = false
 
 	/**
 	 * Creates speech service. Service holds the AudioRecord object, so you
@@ -70,8 +79,7 @@ class SpeechService @Throws(IOException::class) constructor(
 	 */
 	fun startListening(listener: RecognitionListener): Boolean {
 		if (null != recognizerThread) return false
-		recognizerThread = RecognizerThread(listener)
-		recognizerThread!!.start()
+		recognizerThread = scope.launch { startRecognizer(listener) }
 		return true
 	}
 
@@ -86,21 +94,21 @@ class SpeechService @Throws(IOException::class) constructor(
 	 */
 	fun startListening(listener: RecognitionListener, timeout: Int): Boolean {
 		if (null != recognizerThread) return false
-		recognizerThread = RecognizerThread(listener, timeout)
-		recognizerThread!!.start()
+		recognizerThread = scope.launch { startRecognizer(listener, timeout) }
 		return true
 	}
 
-	private fun stopRecognizerThread(): Boolean {
+	private suspend fun stopRecognizerThread(): Boolean {
 		if (null == recognizerThread) return false
 		try {
-			recognizerThread!!.interrupt()
+			interrupt = true
 			recognizerThread!!.join()
 		} catch (e: InterruptedException) {
 			// Restore the interrupted status.
 			Thread.currentThread().interrupt()
 		}
 		recognizerThread = null
+		interrupt = false
 		return true
 	}
 
@@ -111,7 +119,7 @@ class SpeechService @Throws(IOException::class) constructor(
 	 * @return true if recognition was actually stopped
 	 */
 	fun stop(): Boolean {
-		return stopRecognizerThread()
+		return runBlocking { stopRecognizerThread() }
 	}
 
 	/**
@@ -121,10 +129,8 @@ class SpeechService @Throws(IOException::class) constructor(
 	 * @return true if recognition was actually stopped
 	 */
 	fun cancel(): Boolean {
-		if (recognizerThread != null) {
-			recognizerThread!!.setPause(true)
-		}
-		return stopRecognizerThread()
+		paused = true
+		return runBlocking { stopRecognizerThread() }
 	}
 
 	/**
@@ -135,101 +141,72 @@ class SpeechService @Throws(IOException::class) constructor(
 	}
 
 	fun setPause(paused: Boolean) {
-		if (recognizerThread != null) {
-			recognizerThread!!.setPause(paused)
-		}
+		this.paused = paused
 	}
 
 	/**
 	 * Resets recognizer in a thread, starts recognition over again
 	 */
 	fun reset() {
-		if (recognizerThread != null) {
-			recognizerThread!!.reset()
-		}
+		reset = true
 	}
 
-	private inner class RecognizerThread @JvmOverloads constructor(
-		var listener: RecognitionListener,
-		timeout: Int = Companion.NO_TIMEOUT
-	) : Thread() {
-		private var remainingSamples: Int
-		private val timeoutSamples: Int
+	private suspend fun startRecognizer(
+		listener: RecognitionListener,
+		timeout: Int = NO_TIMEOUT
+	) {
+		var remainingSamples: Int
 
-		@Volatile
-		private var paused = false
+		val timeoutSamples: Int = if (timeout != NO_TIMEOUT) {
+			timeout * sampleRate / 1000
+		} else {
+			NO_TIMEOUT
+		}
 
-		@Volatile
-		private var reset = false
+		remainingSamples = timeoutSamples
 
-		init {
-			timeoutSamples = if (timeout != Companion.NO_TIMEOUT) {
-				timeout * sampleRate / 1000
+		recorder.startRecording()
+		if (recorder.recordingState == AudioRecord.RECORDSTATE_STOPPED) {
+			recorder.stop()
+			val ioe = IOException(
+				"Failed to start recording. Microphone might be already in use."
+			)
+			withContext(Dispatchers.Main) { listener.onError(ioe) }
+		}
+		val buffer = ShortArray(bufferSize)
+		while (!interrupt
+			&& (timeoutSamples == NO_TIMEOUT || remainingSamples > 0)
+		) {
+			val nread = recorder.read(buffer, 0, buffer.size)
+			if (paused) {
+				continue
+			}
+			if (reset) {
+				recognizer.reset()
+				reset = false
+			}
+			if (nread < 0) throw RuntimeException("error reading audio buffer")
+			if (recognizer.acceptWaveform(buffer)) {
+				val result = recognizer.result
+				withContext(Dispatchers.Main) { listener.onResult(result) }
 			} else {
-				Companion.NO_TIMEOUT
+				val partialResult = recognizer.partialResult
+				withContext(Dispatchers.Main) { listener.onPartialResult(partialResult) }
+			}
+			if (timeoutSamples != NO_TIMEOUT) {
+				remainingSamples -= nread
 			}
-			remainingSamples = timeoutSamples
-		}
-
-		/**
-		 * When we are paused, don't process audio by the recognizer and don't emit
-		 * any listener results
-		 *
-		 * @param paused the status of pause
-		 */
-		fun setPause(paused: Boolean) {
-			this.paused = paused
 		}
 
-		/**
-		 * Set reset state to signal reset of the recognizer and start over
-		 */
-		fun reset() {
-			reset = true
-		}
+		recorder.stop()
 
-		override fun run() {
-			recorder.startRecording()
-			if (recorder.recordingState == AudioRecord.RECORDSTATE_STOPPED) {
-				recorder.stop()
-				val ioe = IOException(
-					"Failed to start recording. Microphone might be already in use."
-				)
-				mainHandler.post { listener.onError(ioe) }
-			}
-			val buffer = ShortArray(bufferSize)
-			while (!interrupted()
-				&& (timeoutSamples == Companion.NO_TIMEOUT || remainingSamples > 0)
-			) {
-				val nread = recorder.read(buffer, 0, buffer.size)
-				if (paused) {
-					continue
-				}
-				if (reset) {
-					recognizer.reset()
-					reset = false
-				}
-				if (nread < 0) throw RuntimeException("error reading audio buffer")
-				if (recognizer.acceptWaveform(buffer)) {
-					val result = recognizer.result
-					mainHandler.post { listener.onResult(result) }
-				} else {
-					val partialResult = recognizer.partialResult
-					mainHandler.post { listener.onPartialResult(partialResult) }
-				}
-				if (timeoutSamples != NO_TIMEOUT) {
-					remainingSamples -= nread
-				}
-			}
-			recorder.stop()
-			if (!paused) {
-				// If we met timeout signal that speech ended
-				if (timeoutSamples != NO_TIMEOUT && remainingSamples <= 0) {
-					mainHandler.post { listener.onTimeout() }
-				} else {
-					val finalResult = recognizer.finalResult
-					mainHandler.post { listener.onFinalResult(finalResult) }
-				}
+		if (!paused) {
+			// If we met timeout signal that speech ended
+			if (timeoutSamples != NO_TIMEOUT && remainingSamples <= 0) {
+				withContext(Dispatchers.Main) { listener.onTimeout() }
+			} else {
+				val finalResult = recognizer.finalResult
+				withContext(Dispatchers.Main) { listener.onFinalResult(finalResult) }
 			}
 		}
 	}

From 6d797d8260430f628e8bd9b0d714d4cf0873a698 Mon Sep 17 00:00:00 2001
From: clocks <doomsdayrs@gmail.com>
Date: Wed, 19 Jun 2024 15:27:16 -0400
Subject: [PATCH 3/6] SpeechStreamService: Use Coroutines

---
 .../org/vosk/android/SpeechStreamService.kt   | 105 ++++++++++--------
 1 file changed, 57 insertions(+), 48 deletions(-)

diff --git a/kotlin/src/androidMain/kotlin/org/vosk/android/SpeechStreamService.kt b/kotlin/src/androidMain/kotlin/org/vosk/android/SpeechStreamService.kt
index ad3f813e..d34bf793 100644
--- a/kotlin/src/androidMain/kotlin/org/vosk/android/SpeechStreamService.kt
+++ b/kotlin/src/androidMain/kotlin/org/vosk/android/SpeechStreamService.kt
@@ -15,8 +15,12 @@
  */
 package org.vosk.android
 
-import android.os.Handler
-import android.os.Looper
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.withContext
 import org.vosk.Recognizer
 import java.io.IOException
 import java.io.InputStream
@@ -35,8 +39,11 @@ class SpeechStreamService(
 	private val inputStream: InputStream
 	private val sampleRate: Int
 	private val bufferSize: Int
-	private var recognizerThread: Thread? = null
-	private val mainHandler = Handler(Looper.getMainLooper())
+
+	private var recognizerThread: Job? = null
+	private val scope = CoroutineScope(Dispatchers.IO)
+
+	private var interrupt = false
 
 	/**
 	 * Creates speech service.
@@ -54,7 +61,7 @@ class SpeechStreamService(
 	 */
 	fun start(listener: RecognitionListener): Boolean {
 		if (null != recognizerThread) return false
-		recognizerThread = RecognizerThread(listener)
+		recognizerThread = scope.launch { recognizerThread(listener) }
 		recognizerThread!!.start()
 		return true
 	}
@@ -70,7 +77,7 @@ class SpeechStreamService(
 	 */
 	fun start(listener: RecognitionListener, timeout: Int): Boolean {
 		if (null != recognizerThread) return false
-		recognizerThread = RecognizerThread(listener, timeout)
+		recognizerThread = scope.launch { recognizerThread(listener, timeout) }
 		recognizerThread!!.start()
 		return true
 	}
@@ -84,64 +91,66 @@ class SpeechStreamService(
 	fun stop(): Boolean {
 		if (null == recognizerThread) return false
 		try {
-			recognizerThread!!.interrupt()
-			recognizerThread!!.join()
+			interrupt = true
+			runBlocking {
+				recognizerThread!!.join()
+			}
 		} catch (e: InterruptedException) {
 			// Restore the interrupted status.
 			Thread.currentThread().interrupt()
 		}
 		recognizerThread = null
+		interrupt = false
 		return true
 	}
 
-	private inner class RecognizerThread @JvmOverloads constructor(
-		var listener: RecognitionListener,
-		timeout: Int = Companion.NO_TIMEOUT
-	) : Thread() {
-		private var remainingSamples: Int
-		private val timeoutSamples: Int
+	suspend fun recognizerThread(
+		listener: RecognitionListener,
+		timeout: Int = NO_TIMEOUT
+	) {
+		var remainingSamples: Int
+		val timeoutSamples: Int
 
-		init {
-			if (timeout != Companion.NO_TIMEOUT) timeoutSamples =
-				timeout * sampleRate / 1000 else timeoutSamples = Companion.NO_TIMEOUT
-			remainingSamples = timeoutSamples
-		}
+		if (timeout != NO_TIMEOUT) timeoutSamples =
+			timeout * sampleRate / 1000 else timeoutSamples = NO_TIMEOUT
+		remainingSamples = timeoutSamples
 
-		override fun run() {
-			val buffer = ByteArray(bufferSize)
-			while (!interrupted()
-				&& (timeoutSamples == Companion.NO_TIMEOUT || remainingSamples > 0)
-			) {
-				try {
-					val nread = inputStream.read(buffer, 0, buffer.size)
-					if (nread < 0) {
-						break
+		val buffer = ByteArray(bufferSize)
+		while (!interrupt
+			&& (timeoutSamples == NO_TIMEOUT || remainingSamples > 0)
+		) {
+			try {
+				val nread = withContext(Dispatchers.IO) {
+					inputStream.read(buffer, 0, buffer.size)
+				}
+				if (nread < 0) {
+					break
+				} else {
+					val isSilence: Boolean = recognizer.acceptWaveform(buffer)
+					if (isSilence) {
+						val result = recognizer.result
+						withContext(Dispatchers.Main) { listener.onResult(result) }
 					} else {
-						val isSilence: Boolean = recognizer.acceptWaveform(buffer)
-						if (isSilence) {
-							val result = recognizer.result
-							mainHandler.post { listener.onResult(result) }
-						} else {
-							val partialResult = recognizer.partialResult
-							mainHandler.post { listener.onPartialResult(partialResult) }
-						}
+						val partialResult = recognizer.partialResult
+						withContext(Dispatchers.Main) { listener.onPartialResult(partialResult) }
 					}
-					if (timeoutSamples != NO_TIMEOUT) {
-						remainingSamples -= nread
-					}
-				} catch (e: IOException) {
-					mainHandler.post { listener.onError(e) }
 				}
+				if (timeoutSamples != NO_TIMEOUT) {
+					remainingSamples -= nread
+				}
+			} catch (e: IOException) {
+				withContext(Dispatchers.Main) { listener.onError(e) }
 			}
+		}
 
-			// If we met timeout signal that speech ended
-			if (timeoutSamples != NO_TIMEOUT && remainingSamples <= 0) {
-				mainHandler.post { listener.onTimeout() }
-			} else {
-				val finalResult = recognizer.finalResult
-				mainHandler.post { listener.onFinalResult(finalResult) }
-			}
+		// If we met timeout signal that speech ended
+		if (timeoutSamples != NO_TIMEOUT && remainingSamples <= 0) {
+			withContext(Dispatchers.Main) { listener.onTimeout() }
+		} else {
+			val finalResult = recognizer.finalResult
+			withContext(Dispatchers.Main) { listener.onFinalResult(finalResult) }
 		}
+
 	}
 
 	companion object {

From 1ac577b22578f09a1ae9f6a15fc074fc479e8508 Mon Sep 17 00:00:00 2001
From: clocks <doomsdayrs@gmail.com>
Date: Thu, 20 Jun 2024 00:03:21 -0400
Subject: [PATCH 4/6] Correct dependency conflict between jvm & android

---
 kotlin/build.gradle.kts                                | 10 +++++++++-
 .../kotlin/org/vosk/BatchModel.kt                      |  0
 .../kotlin/org/vosk/BatchRecognizer.kt                 |  0
 .../{jvmMain => commonJVM}/kotlin/org/vosk/LibVosk.kt  |  0
 .../{jvmMain => commonJVM}/kotlin/org/vosk/Model.kt    |  0
 .../kotlin/org/vosk/Recognizer.kt                      |  0
 .../kotlin/org/vosk/SpeakerModel.kt                    |  0
 .../kotlin/org/vosk/TextProcessor.kt                   |  0
 .../src/{jvmMain => commonJVM}/kotlin/org/vosk/Vosk.kt |  0
 .../kotlin/org/vosk/WaveformExt.kt                     |  0
 .../kotlin/org/vosk/exception/IOException.kt           |  0
 11 files changed, 9 insertions(+), 1 deletion(-)
 rename kotlin/src/{jvmMain => commonJVM}/kotlin/org/vosk/BatchModel.kt (100%)
 rename kotlin/src/{jvmMain => commonJVM}/kotlin/org/vosk/BatchRecognizer.kt (100%)
 rename kotlin/src/{jvmMain => commonJVM}/kotlin/org/vosk/LibVosk.kt (100%)
 rename kotlin/src/{jvmMain => commonJVM}/kotlin/org/vosk/Model.kt (100%)
 rename kotlin/src/{jvmMain => commonJVM}/kotlin/org/vosk/Recognizer.kt (100%)
 rename kotlin/src/{jvmMain => commonJVM}/kotlin/org/vosk/SpeakerModel.kt (100%)
 rename kotlin/src/{jvmMain => commonJVM}/kotlin/org/vosk/TextProcessor.kt (100%)
 rename kotlin/src/{jvmMain => commonJVM}/kotlin/org/vosk/Vosk.kt (100%)
 rename kotlin/src/{jvmMain => commonJVM}/kotlin/org/vosk/WaveformExt.kt (100%)
 rename kotlin/src/{jvmMain => commonJVM}/kotlin/org/vosk/exception/IOException.kt (100%)

diff --git a/kotlin/build.gradle.kts b/kotlin/build.gradle.kts
index 7820ef2b..25968ce8 100644
--- a/kotlin/build.gradle.kts
+++ b/kotlin/build.gradle.kts
@@ -159,7 +159,15 @@ kotlin {
 				implementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:$coroutines_version")
 			}
 		}
+		val commonJVM = create("commonJVM") {
+			dependsOn(commonMain)
+			dependencies {
+				compileOnly("net.java.dev.jna:jna:$jna_version")
+			}
+		}
+
 		val jvmMain by getting {
+			dependsOn(commonJVM)
 			dependencies {
 				api("net.java.dev.jna:jna:$jna_version")
 			}
@@ -169,7 +177,7 @@ kotlin {
 			val nativeMain by getting
 		}
 		val androidMain by getting {
-			dependsOn(jvmMain)
+			dependsOn(commonJVM)
 			dependencies {
 				api("net.java.dev.jna:jna:$jna_version@aar")
 			}
diff --git a/kotlin/src/jvmMain/kotlin/org/vosk/BatchModel.kt b/kotlin/src/commonJVM/kotlin/org/vosk/BatchModel.kt
similarity index 100%
rename from kotlin/src/jvmMain/kotlin/org/vosk/BatchModel.kt
rename to kotlin/src/commonJVM/kotlin/org/vosk/BatchModel.kt
diff --git a/kotlin/src/jvmMain/kotlin/org/vosk/BatchRecognizer.kt b/kotlin/src/commonJVM/kotlin/org/vosk/BatchRecognizer.kt
similarity index 100%
rename from kotlin/src/jvmMain/kotlin/org/vosk/BatchRecognizer.kt
rename to kotlin/src/commonJVM/kotlin/org/vosk/BatchRecognizer.kt
diff --git a/kotlin/src/jvmMain/kotlin/org/vosk/LibVosk.kt b/kotlin/src/commonJVM/kotlin/org/vosk/LibVosk.kt
similarity index 100%
rename from kotlin/src/jvmMain/kotlin/org/vosk/LibVosk.kt
rename to kotlin/src/commonJVM/kotlin/org/vosk/LibVosk.kt
diff --git a/kotlin/src/jvmMain/kotlin/org/vosk/Model.kt b/kotlin/src/commonJVM/kotlin/org/vosk/Model.kt
similarity index 100%
rename from kotlin/src/jvmMain/kotlin/org/vosk/Model.kt
rename to kotlin/src/commonJVM/kotlin/org/vosk/Model.kt
diff --git a/kotlin/src/jvmMain/kotlin/org/vosk/Recognizer.kt b/kotlin/src/commonJVM/kotlin/org/vosk/Recognizer.kt
similarity index 100%
rename from kotlin/src/jvmMain/kotlin/org/vosk/Recognizer.kt
rename to kotlin/src/commonJVM/kotlin/org/vosk/Recognizer.kt
diff --git a/kotlin/src/jvmMain/kotlin/org/vosk/SpeakerModel.kt b/kotlin/src/commonJVM/kotlin/org/vosk/SpeakerModel.kt
similarity index 100%
rename from kotlin/src/jvmMain/kotlin/org/vosk/SpeakerModel.kt
rename to kotlin/src/commonJVM/kotlin/org/vosk/SpeakerModel.kt
diff --git a/kotlin/src/jvmMain/kotlin/org/vosk/TextProcessor.kt b/kotlin/src/commonJVM/kotlin/org/vosk/TextProcessor.kt
similarity index 100%
rename from kotlin/src/jvmMain/kotlin/org/vosk/TextProcessor.kt
rename to kotlin/src/commonJVM/kotlin/org/vosk/TextProcessor.kt
diff --git a/kotlin/src/jvmMain/kotlin/org/vosk/Vosk.kt b/kotlin/src/commonJVM/kotlin/org/vosk/Vosk.kt
similarity index 100%
rename from kotlin/src/jvmMain/kotlin/org/vosk/Vosk.kt
rename to kotlin/src/commonJVM/kotlin/org/vosk/Vosk.kt
diff --git a/kotlin/src/jvmMain/kotlin/org/vosk/WaveformExt.kt b/kotlin/src/commonJVM/kotlin/org/vosk/WaveformExt.kt
similarity index 100%
rename from kotlin/src/jvmMain/kotlin/org/vosk/WaveformExt.kt
rename to kotlin/src/commonJVM/kotlin/org/vosk/WaveformExt.kt
diff --git a/kotlin/src/jvmMain/kotlin/org/vosk/exception/IOException.kt b/kotlin/src/commonJVM/kotlin/org/vosk/exception/IOException.kt
similarity index 100%
rename from kotlin/src/jvmMain/kotlin/org/vosk/exception/IOException.kt
rename to kotlin/src/commonJVM/kotlin/org/vosk/exception/IOException.kt

From 30e017a6224071d385a174c24f22b6906d66620d Mon Sep 17 00:00:00 2001
From: clocks <doomsdayrs@gmail.com>
Date: Sat, 6 Jul 2024 10:32:27 -0400
Subject: [PATCH 5/6] jdk17

---
 kotlin/.idea/compiler.xml | 2 +-
 kotlin/.idea/gradle.xml   | 8 ++++++--
 kotlin/.idea/misc.xml     | 3 +--
 3 files changed, 8 insertions(+), 5 deletions(-)

diff --git a/kotlin/.idea/compiler.xml b/kotlin/.idea/compiler.xml
index fb7f4a8a..b589d56e 100644
--- a/kotlin/.idea/compiler.xml
+++ b/kotlin/.idea/compiler.xml
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <project version="4">
   <component name="CompilerConfiguration">
-    <bytecodeTargetLevel target="11" />
+    <bytecodeTargetLevel target="17" />
   </component>
 </project>
\ No newline at end of file
diff --git a/kotlin/.idea/gradle.xml b/kotlin/.idea/gradle.xml
index 6cec5693..0f1069f4 100644
--- a/kotlin/.idea/gradle.xml
+++ b/kotlin/.idea/gradle.xml
@@ -4,14 +4,18 @@
   <component name="GradleSettings">
     <option name="linkedExternalProjectsSettings">
       <GradleProjectSettings>
-        <option name="testRunner" value="GRADLE" />
-        <option name="distributionType" value="DEFAULT_WRAPPED" />
         <option name="externalProjectPath" value="$PROJECT_DIR$" />
+        <option name="gradleJvm" value="#GRADLE_LOCAL_JAVA_HOME" />
         <option name="modules">
           <set>
             <option value="$PROJECT_DIR$" />
+            <option value="$PROJECT_DIR$/clapack" />
+            <option value="$PROJECT_DIR$/kaldi" />
+            <option value="$PROJECT_DIR$/openblas" />
+            <option value="$PROJECT_DIR$/openfst" />
           </set>
         </option>
+        <option name="resolveExternalAnnotations" value="false" />
       </GradleProjectSettings>
     </option>
   </component>
diff --git a/kotlin/.idea/misc.xml b/kotlin/.idea/misc.xml
index b1f8730f..8978d23d 100644
--- a/kotlin/.idea/misc.xml
+++ b/kotlin/.idea/misc.xml
@@ -1,7 +1,6 @@
-<?xml version="1.0" encoding="UTF-8"?>
 <project version="4">
   <component name="ExternalStorageConfigurationManager" enabled="true" />
-  <component name="ProjectRootManager" version="2" languageLevel="JDK_11" default="true" project-jdk-name="Embedded JDK" project-jdk-type="JavaSDK">
+  <component name="ProjectRootManager" version="2" languageLevel="JDK_17" default="true" project-jdk-name="jbr-17" project-jdk-type="JavaSDK">
     <output url="file://$PROJECT_DIR$/build/classes" />
   </component>
   <component name="ProjectType">

From 82fe40e64819a94a0dbd91849a8748ed472ae2ec Mon Sep 17 00:00:00 2001
From: clocks <doomsdayrs@gmail.com>
Date: Wed, 10 Jul 2024 16:13:51 -0400
Subject: [PATCH 6/6] Attempt to compile vosk in android studio

Failure, giving up and moving on.
---
 .gitmodules                                  | 14 +++++
 kotlin/.idea/vcs.xml                         |  4 ++
 kotlin/build.gradle.kts                      | 25 ++++++++-
 kotlin/clapack/.gitignore                    |  1 +
 kotlin/clapack/build.gradle.kts              | 45 +++++++++++++++
 kotlin/clapack/consumer-rules.pro            |  0
 kotlin/clapack/proguard-rules.pro            | 21 +++++++
 kotlin/clapack/src/main/AndroidManifest.xml  |  4 ++
 kotlin/clapack/src/main/cpp                  |  1 +
 kotlin/kaldi/.gitignore                      |  1 +
 kotlin/kaldi/build.gradle.kts                | 53 ++++++++++++++++++
 kotlin/kaldi/consumer-rules.pro              |  0
 kotlin/kaldi/proguard-rules.pro              | 21 +++++++
 kotlin/kaldi/src/main/AndroidManifest.xml    |  4 ++
 kotlin/kaldi/src/main/cpp                    |  1 +
 kotlin/openblas/.gitignore                   |  1 +
 kotlin/openblas/build.gradle.kts             | 45 +++++++++++++++
 kotlin/openblas/consumer-rules.pro           |  0
 kotlin/openblas/proguard-rules.pro           | 21 +++++++
 kotlin/openblas/src/main/AndroidManifest.xml |  4 ++
 kotlin/openblas/src/main/cpp                 |  1 +
 kotlin/openfst/.gitignore                    |  1 +
 kotlin/openfst/build.gradle.kts              | 58 ++++++++++++++++++++
 kotlin/openfst/consumer-rules.pro            |  0
 kotlin/openfst/proguard-rules.pro            | 21 +++++++
 kotlin/openfst/src/main/AndroidManifest.xml  |  4 ++
 kotlin/openfst/src/main/cpp                  |  1 +
 kotlin/settings.gradle.kts                   |  6 +-
 28 files changed, 354 insertions(+), 4 deletions(-)
 create mode 100644 .gitmodules
 create mode 100644 kotlin/clapack/.gitignore
 create mode 100644 kotlin/clapack/build.gradle.kts
 create mode 100644 kotlin/clapack/consumer-rules.pro
 create mode 100644 kotlin/clapack/proguard-rules.pro
 create mode 100644 kotlin/clapack/src/main/AndroidManifest.xml
 create mode 160000 kotlin/clapack/src/main/cpp
 create mode 100644 kotlin/kaldi/.gitignore
 create mode 100644 kotlin/kaldi/build.gradle.kts
 create mode 100644 kotlin/kaldi/consumer-rules.pro
 create mode 100644 kotlin/kaldi/proguard-rules.pro
 create mode 100644 kotlin/kaldi/src/main/AndroidManifest.xml
 create mode 160000 kotlin/kaldi/src/main/cpp
 create mode 100644 kotlin/openblas/.gitignore
 create mode 100644 kotlin/openblas/build.gradle.kts
 create mode 100644 kotlin/openblas/consumer-rules.pro
 create mode 100644 kotlin/openblas/proguard-rules.pro
 create mode 100644 kotlin/openblas/src/main/AndroidManifest.xml
 create mode 160000 kotlin/openblas/src/main/cpp
 create mode 100644 kotlin/openfst/.gitignore
 create mode 100644 kotlin/openfst/build.gradle.kts
 create mode 100644 kotlin/openfst/consumer-rules.pro
 create mode 100644 kotlin/openfst/proguard-rules.pro
 create mode 100644 kotlin/openfst/src/main/AndroidManifest.xml
 create mode 160000 kotlin/openfst/src/main/cpp

diff --git a/.gitmodules b/.gitmodules
new file mode 100644
index 00000000..424ca224
--- /dev/null
+++ b/.gitmodules
@@ -0,0 +1,14 @@
+[submodule "kotlin/openblas/src/main/cpp"]
+	path = kotlin/openblas/src/main/cpp
+	url = https://github.com/OpenMathLib/OpenBLAS.git
+[submodule "kotlin/clapack/src/main/cpp"]
+	path = kotlin/clapack/src/main/cpp
+	url = https://github.com/alphacep/clapack.git
+	branch = v3.2.1
+[submodule "kotlin/kaldi/src/main/cpp"]
+	path = kotlin/kaldi/src/main/cpp
+	url = https://github.com/alphacep/kaldi.git
+	branch = vosk-android
+[submodule "kotlin/openfst/src/main/cpp"]
+	path = kotlin/openfst/src/main/cpp
+	url = https://github.com/messiaen/openfst.git
\ No newline at end of file
diff --git a/kotlin/.idea/vcs.xml b/kotlin/.idea/vcs.xml
index 6c0b8635..0d494a5c 100644
--- a/kotlin/.idea/vcs.xml
+++ b/kotlin/.idea/vcs.xml
@@ -2,5 +2,9 @@
 <project version="4">
   <component name="VcsDirectoryMappings">
     <mapping directory="$PROJECT_DIR$/.." vcs="Git" />
+    <mapping directory="$PROJECT_DIR$/clapack/src/main/cpp" vcs="Git" />
+    <mapping directory="$PROJECT_DIR$/kaldi/src/main/cpp" vcs="Git" />
+    <mapping directory="$PROJECT_DIR$/openblas/src/main/cpp" vcs="Git" />
+    <mapping directory="$PROJECT_DIR$/openfst/src/main/cpp" vcs="Git" />
   </component>
 </project>
\ No newline at end of file
diff --git a/kotlin/build.gradle.kts b/kotlin/build.gradle.kts
index 25968ce8..f13892b7 100644
--- a/kotlin/build.gradle.kts
+++ b/kotlin/build.gradle.kts
@@ -24,14 +24,17 @@ plugins {
 	`maven-publish`
 	id("org.jetbrains.dokka") version "1.9.20"
 	kotlin("plugin.serialization") version "2.0.0"
+	id("org.jetbrains.kotlin.android") version "1.9.0" apply false
 }
 
 group = "com.alphacephei"
 version = "0.3.50"
 
-repositories {
-	google()
-	mavenCentral()
+allprojects {
+	repositories {
+		google()
+		mavenCentral()
+	}
 }
 
 val dokkaOutputDir = "$buildDir/dokka"
@@ -180,6 +183,9 @@ kotlin {
 			dependsOn(commonJVM)
 			dependencies {
 				api("net.java.dev.jna:jna:$jna_version@aar")
+				implementation(project(":kaldi"))
+				implementation(project(":openfst"))
+				implementation(project(":openblas"))
 			}
 		}
 		val androidUnitTest by getting {
@@ -197,6 +203,12 @@ android {
 	defaultConfig {
 		minSdk = 24
 		targetSdk = 34
+		externalNativeBuild {
+			cmake {
+				cppFlags("")
+				arguments("-Wno-dev")
+			}
+		}
 	}
 	compileOptions {
 		sourceCompatibility = JavaVersion.VERSION_17
@@ -209,4 +221,11 @@ android {
 			allVariants()
 		}
 	}
+
+	externalNativeBuild {
+		cmake {
+			path("../CMakeLists.txt")
+			version = "3.22.1"
+		}
+	}
 }
diff --git a/kotlin/clapack/.gitignore b/kotlin/clapack/.gitignore
new file mode 100644
index 00000000..42afabfd
--- /dev/null
+++ b/kotlin/clapack/.gitignore
@@ -0,0 +1 @@
+/build
\ No newline at end of file
diff --git a/kotlin/clapack/build.gradle.kts b/kotlin/clapack/build.gradle.kts
new file mode 100644
index 00000000..311ddefd
--- /dev/null
+++ b/kotlin/clapack/build.gradle.kts
@@ -0,0 +1,45 @@
+plugins {
+	id("com.android.library")
+}
+
+android {
+	namespace = "page.doomsdayrs.libs.clapack"
+	compileSdk = 34
+
+	defaultConfig {
+		minSdk = 24
+
+		testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
+		consumerProguardFiles("consumer-rules.pro")
+		externalNativeBuild {
+			cmake {
+				arguments(
+					"-DCMAKE_TRY_COMPILE_TARGET_TYPE=STATIC_LIBRARY",
+					"-DCMAKE_CROSSCOMPILING=True",
+					"-DCMAKE_SYSTEM_NAME=Generic",
+					"-Wno-dev"
+				)
+			}
+		}
+	}
+
+	buildTypes {
+		release {
+			isMinifyEnabled = false
+			proguardFiles(
+				getDefaultProguardFile("proguard-android-optimize.txt"),
+				"proguard-rules.pro"
+			)
+		}
+	}
+	externalNativeBuild {
+		cmake {
+			path("src/main/cpp/CMakeLists.txt")
+			version = "3.22.1"
+		}
+	}
+	compileOptions {
+		sourceCompatibility = JavaVersion.VERSION_1_8
+		targetCompatibility = JavaVersion.VERSION_1_8
+	}
+}
\ No newline at end of file
diff --git a/kotlin/clapack/consumer-rules.pro b/kotlin/clapack/consumer-rules.pro
new file mode 100644
index 00000000..e69de29b
diff --git a/kotlin/clapack/proguard-rules.pro b/kotlin/clapack/proguard-rules.pro
new file mode 100644
index 00000000..481bb434
--- /dev/null
+++ b/kotlin/clapack/proguard-rules.pro
@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+#   http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+#   public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
\ No newline at end of file
diff --git a/kotlin/clapack/src/main/AndroidManifest.xml b/kotlin/clapack/src/main/AndroidManifest.xml
new file mode 100644
index 00000000..a5918e68
--- /dev/null
+++ b/kotlin/clapack/src/main/AndroidManifest.xml
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android">
+
+</manifest>
\ No newline at end of file
diff --git a/kotlin/clapack/src/main/cpp b/kotlin/clapack/src/main/cpp
new file mode 160000
index 00000000..1cdd625e
--- /dev/null
+++ b/kotlin/clapack/src/main/cpp
@@ -0,0 +1 @@
+Subproject commit 1cdd625ea47651831db3497c8e065c965bdfe7a3
diff --git a/kotlin/kaldi/.gitignore b/kotlin/kaldi/.gitignore
new file mode 100644
index 00000000..42afabfd
--- /dev/null
+++ b/kotlin/kaldi/.gitignore
@@ -0,0 +1 @@
+/build
\ No newline at end of file
diff --git a/kotlin/kaldi/build.gradle.kts b/kotlin/kaldi/build.gradle.kts
new file mode 100644
index 00000000..f20610ea
--- /dev/null
+++ b/kotlin/kaldi/build.gradle.kts
@@ -0,0 +1,53 @@
+plugins {
+	id("com.android.library")
+}
+
+android {
+	namespace = "page.doomsdayrs.libs.kaldi"
+	compileSdk = 34
+
+	defaultConfig {
+		minSdk = 24
+
+		testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
+		consumerProguardFiles("consumer-rules.pro")
+		externalNativeBuild {
+			cmake {
+				arguments(
+					"-DMATHLIB=OpenBLAS",
+					"-DBUILD_SHARED_LIBS=ON",
+
+					"-Wno-dev",
+					"-DKALDI_BUILD_EXE=OFF",
+					"-DKALDI_BUILD_TEST=OFF",
+				)
+				cppFlags("-O3", "-DFST_NO_DYNAMIC_LINKING")
+			}
+		}
+	}
+
+	buildTypes {
+		release {
+			isMinifyEnabled = false
+			proguardFiles(
+				getDefaultProguardFile("proguard-android-optimize.txt"),
+				"proguard-rules.pro"
+			)
+		}
+	}
+	externalNativeBuild {
+		cmake {
+			path("src/main/cpp/CMakeLists.txt")
+			version = "3.22.1"
+		}
+	}
+	compileOptions {
+		sourceCompatibility = JavaVersion.VERSION_1_8
+		targetCompatibility = JavaVersion.VERSION_1_8
+	}
+}
+
+dependencies {
+	compileOnly(project(":openfst"))
+	compileOnly(project(":openblas"))
+}
\ No newline at end of file
diff --git a/kotlin/kaldi/consumer-rules.pro b/kotlin/kaldi/consumer-rules.pro
new file mode 100644
index 00000000..e69de29b
diff --git a/kotlin/kaldi/proguard-rules.pro b/kotlin/kaldi/proguard-rules.pro
new file mode 100644
index 00000000..481bb434
--- /dev/null
+++ b/kotlin/kaldi/proguard-rules.pro
@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+#   http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+#   public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
\ No newline at end of file
diff --git a/kotlin/kaldi/src/main/AndroidManifest.xml b/kotlin/kaldi/src/main/AndroidManifest.xml
new file mode 100644
index 00000000..a5918e68
--- /dev/null
+++ b/kotlin/kaldi/src/main/AndroidManifest.xml
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android">
+
+</manifest>
\ No newline at end of file
diff --git a/kotlin/kaldi/src/main/cpp b/kotlin/kaldi/src/main/cpp
new file mode 160000
index 00000000..b6920778
--- /dev/null
+++ b/kotlin/kaldi/src/main/cpp
@@ -0,0 +1 @@
+Subproject commit b692077807b4832cb4e1a8dd0889ed5f0a71b5b8
diff --git a/kotlin/openblas/.gitignore b/kotlin/openblas/.gitignore
new file mode 100644
index 00000000..42afabfd
--- /dev/null
+++ b/kotlin/openblas/.gitignore
@@ -0,0 +1 @@
+/build
\ No newline at end of file
diff --git a/kotlin/openblas/build.gradle.kts b/kotlin/openblas/build.gradle.kts
new file mode 100644
index 00000000..fe9c6fa0
--- /dev/null
+++ b/kotlin/openblas/build.gradle.kts
@@ -0,0 +1,45 @@
+plugins {
+	id("com.android.library")
+}
+
+android {
+	namespace = "page.doomsdayrs.libs.openblas"
+	compileSdk = 34
+
+	defaultConfig {
+		minSdk = 24
+
+		testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
+		consumerProguardFiles("consumer-rules.pro")
+		ndk {
+			abiFilters += "arm64-v8a"
+			abiFilters += "armeabi-v7a"
+			// TODO x86
+		}
+		externalNativeBuild {
+			cmake {
+				cppFlags("-marm", "-mfpu=vfp", "-mfloat-abi=softfp", "-Wno-dev")
+			}
+		}
+	}
+
+	buildTypes {
+		release {
+			isMinifyEnabled = false
+			proguardFiles(
+				getDefaultProguardFile("proguard-android-optimize.txt"),
+				"proguard-rules.pro"
+			)
+		}
+	}
+	externalNativeBuild {
+		cmake {
+			path("src/main/cpp/CMakeLists.txt")
+			version = "3.22.1"
+		}
+	}
+	compileOptions {
+		sourceCompatibility = JavaVersion.VERSION_1_8
+		targetCompatibility = JavaVersion.VERSION_1_8
+	}
+}
\ No newline at end of file
diff --git a/kotlin/openblas/consumer-rules.pro b/kotlin/openblas/consumer-rules.pro
new file mode 100644
index 00000000..e69de29b
diff --git a/kotlin/openblas/proguard-rules.pro b/kotlin/openblas/proguard-rules.pro
new file mode 100644
index 00000000..481bb434
--- /dev/null
+++ b/kotlin/openblas/proguard-rules.pro
@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+#   http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+#   public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
\ No newline at end of file
diff --git a/kotlin/openblas/src/main/AndroidManifest.xml b/kotlin/openblas/src/main/AndroidManifest.xml
new file mode 100644
index 00000000..a5918e68
--- /dev/null
+++ b/kotlin/openblas/src/main/AndroidManifest.xml
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android">
+
+</manifest>
\ No newline at end of file
diff --git a/kotlin/openblas/src/main/cpp b/kotlin/openblas/src/main/cpp
new file mode 160000
index 00000000..d2b11c47
--- /dev/null
+++ b/kotlin/openblas/src/main/cpp
@@ -0,0 +1 @@
+Subproject commit d2b11c47774b9216660e76e2fc67e87079f26fa1
diff --git a/kotlin/openfst/.gitignore b/kotlin/openfst/.gitignore
new file mode 100644
index 00000000..42afabfd
--- /dev/null
+++ b/kotlin/openfst/.gitignore
@@ -0,0 +1 @@
+/build
\ No newline at end of file
diff --git a/kotlin/openfst/build.gradle.kts b/kotlin/openfst/build.gradle.kts
new file mode 100644
index 00000000..34c40b3d
--- /dev/null
+++ b/kotlin/openfst/build.gradle.kts
@@ -0,0 +1,58 @@
+plugins {
+	id("com.android.library")
+}
+
+android {
+	namespace = "page.doomsdayrs.libs.openfst"
+	compileSdk = 34
+
+	defaultConfig {
+		minSdk = 24
+
+		testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
+		consumerProguardFiles("consumer-rules.pro")
+		externalNativeBuild {
+			cmake {
+				cppFlags("-O3", "-DFST_NO_DYNAMIC_LINKING")
+				arguments(
+					"-DBUILD_SHARED_LIBS=ON",
+					"-DHAVE_BIN=OFF",
+					"-DHAVE_LOOKAHEAD=ON",
+					"-DHAVE_NGRAM=ON",
+
+					"-DHAVE_COMPACT=OFF",
+					"-DHAVE_CONST=OFF",
+					"-DHAVE_FAR=OFF",
+					"-DHAVE_GRM=OFF",
+					"-DHAVE_PDT=OFF",
+					"-DHAVE_MPDT=OFF",
+					"-DHAVE_LINEAR=OFF",
+					"-DHAVE_LOOKAHEAD=OFF",
+					"-DHAVE_SPECIAL=OFF",
+
+					"-Wno-dev"
+				)
+			}
+		}
+	}
+
+	buildTypes {
+		release {
+			isMinifyEnabled = false
+			proguardFiles(
+				getDefaultProguardFile("proguard-android-optimize.txt"),
+				"proguard-rules.pro"
+			)
+		}
+	}
+	externalNativeBuild {
+		cmake {
+			path("src/main/cpp/CMakeLists.txt")
+			version = "3.22.1"
+		}
+	}
+	compileOptions {
+		sourceCompatibility = JavaVersion.VERSION_1_8
+		targetCompatibility = JavaVersion.VERSION_1_8
+	}
+}
\ No newline at end of file
diff --git a/kotlin/openfst/consumer-rules.pro b/kotlin/openfst/consumer-rules.pro
new file mode 100644
index 00000000..e69de29b
diff --git a/kotlin/openfst/proguard-rules.pro b/kotlin/openfst/proguard-rules.pro
new file mode 100644
index 00000000..481bb434
--- /dev/null
+++ b/kotlin/openfst/proguard-rules.pro
@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+#   http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+#   public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
\ No newline at end of file
diff --git a/kotlin/openfst/src/main/AndroidManifest.xml b/kotlin/openfst/src/main/AndroidManifest.xml
new file mode 100644
index 00000000..a5918e68
--- /dev/null
+++ b/kotlin/openfst/src/main/AndroidManifest.xml
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android">
+
+</manifest>
\ No newline at end of file
diff --git a/kotlin/openfst/src/main/cpp b/kotlin/openfst/src/main/cpp
new file mode 160000
index 00000000..7ffa870f
--- /dev/null
+++ b/kotlin/openfst/src/main/cpp
@@ -0,0 +1 @@
+Subproject commit 7ffa870fbe8a17ba5b7b63ac240f23af2f6b39fb
diff --git a/kotlin/settings.gradle.kts b/kotlin/settings.gradle.kts
index 4aba20b2..7bc9e631 100644
--- a/kotlin/settings.gradle.kts
+++ b/kotlin/settings.gradle.kts
@@ -28,4 +28,8 @@ pluginManagement {
 		}
 	}
 }
-rootProject.name = "vosk-api-kotlin"
\ No newline at end of file
+rootProject.name = "vosk-api-kotlin"
+include(":kaldi")
+include(":openblas")
+include(":clapack")
+include(":openfst")