Compare commits

...

4 Commits

Author SHA1 Message Date
Joshua Perry 83e3c53ebc mia class skeleton 2024-10-19 19:50:56 +01:00
Joshua Perry b8f64ad4ba os window control 2024-10-19 19:50:40 +01:00
Joshua Perry a9ba42d24c Speech to text 2024-10-19 19:50:30 +01:00
Joshua Perry 4836e4c4c0 Microphone access and stream 2024-10-19 19:50:18 +01:00
5 changed files with 221 additions and 0 deletions

2
src/main/kotlin/MIA.kt Normal file
View File

@ -0,0 +1,2 @@
class MIA { //TODO: Semantic Kernel Class
}

View File

@ -0,0 +1,61 @@
import javax.sound.sampled.AudioFormat
import javax.sound.sampled.AudioSystem
import javax.sound.sampled.DataLine
import javax.sound.sampled.TargetDataLine
/**
* TODO: Documentation
*/
class Microphone {
/**
*
*/
private val source = getSource()
/**
*
*/
private val audioFormat = getFormat()
/**
*
*/
fun startCapture() {
source.start()
//TODO: Start processing loop in new coroutine and check if it could be moved into own file
}
/**
*
*/
fun stopCapture() {
source.stop()
}
/**
*
*/
fun closeSource() {
source.close()
}
/**
*
*/
private fun getFormat(): AudioFormat {
return AudioFormat( //TODO: Get format settings from user settings
16000.0f, // 16000 Required
16,
2,
true,
true
)
}
/**
*
*/
private fun getSource(): TargetDataLine {
val info = DataLine.Info(
TargetDataLine::class.java,
audioFormat
)
val source = AudioSystem.getLine(info) as TargetDataLine
source.open(audioFormat)
return source
}
}

39
src/main/kotlin/STT.kt Normal file
View File

@ -0,0 +1,39 @@
import org.vosk.Model
import org.vosk.Recognizer
import java.nio.file.Paths
/**
* TODO: Documentation
*/
class STT {
/**
*
*/
private val model = Model(getModelPath())
/**
*
*/
private val recognizer = Recognizer(model, 16000.0f)
/**
*
*/
fun parseBuffer(buffer: ByteArray, audioBytes: Int): String? {
return if (recognizer.acceptWaveForm(buffer, audioBytes)) {
recognizer.result
} else {
recognizer.partialResult
}
}
fun closeModel() {
recognizer.close()
}
/**
*
*/
private fun getModelPath(): String {
return Paths.get(
javaClass.getResource("STT-model")!!.toURI()
).toFile().absolutePath
}
//TODO: Install model into dir from resource link
}

View File

@ -0,0 +1,7 @@
package window_control
enum class OS {
Windows,
Mac,
Linux
}

View File

@ -0,0 +1,112 @@
package window_control
import com.sun.jna.Library
import com.sun.jna.Native
import com.sun.jna.platform.win32.WinDef
import com.sun.jna.win32.W32APIOptions
import java.awt.Point
import java.awt.Robot
import java.awt.event.KeyEvent
class WindowController { //TODO: Class that controls program window
private val os = OS.valueOf(System.getProperty("os.name"))
private val inputController = Robot()
fun setActiveWindow(windowName: String) {
when (os) {
OS.Windows -> activateWindowsWindow(windowName)
OS.Mac -> activateMacWindow(windowName)
OS.Linux -> activateLinuxWindow(windowName)
}
}
fun sendKeyPresses(keyCodes: Array<Int>) {
keyCodes.iterator().forEach { keyCode ->
inputController.keyPress(keyCode)
}
}
fun sendKeyReleases(keyCodes: Array<Int>) {
keyCodes.iterator().forEach { keyCode ->
inputController.keyRelease(keyCode)
}
}
fun sendKeyStrokes(keyCodes: Array<Int>, delay: Int?) { //TODO: Create KeyList Class
keyCodes.iterator().forEach { keyCode ->
sendKeyPresses(arrayOf(keyCode))
inputController.delay(delay!!)
sendKeyReleases(arrayOf(keyCode))
}
}
fun sendMultiKeyStrokes(commands: Array<Array<Int>>, delay: Int?) { //TODO: Create MultiKeyList Class
commands.iterator().forEach { command ->
sendMousePresses(command)
inputController.delay(delay!!)
sendMouseReleases(command)
}
}
fun sendMousePresses(buttonCodes: Array<Int>) { //TODO: Create ButtonList Class
buttonCodes.iterator().forEach { buttonCode ->
inputController.mousePress(buttonCode)
}
}
fun sendMouseReleases(buttonCodes: Array<Int>) {
buttonCodes.iterator().forEach { buttonCode ->
inputController.mouseRelease(buttonCode)
}
}
fun sendMouseLocation(location: Point) {
inputController.mouseMove(location.x, location.y)
}
fun sendMouseStrokes(buttonCodes: Array<Int>, delay: Int?) {
buttonCodes.iterator().forEach { buttonCode ->
sendMousePresses(arrayOf(buttonCode))
inputController.delay(delay!!)
sendMouseReleases(arrayOf(buttonCode))
}
}
fun sendMouseStrokesAtLocation(buttonCodes: Array<Int>, delay: Int?, location: Point) {
sendMouseLocation(location)
buttonCodes.iterator().forEach { buttonCode ->
sendMouseStrokes(arrayOf(buttonCode), delay)
}
}
fun sendMouseStrokesAtLocations(commands: Array<Pair<Point,Array<Int>>>, delay: Int?) {
commands.iterator().forEach { command ->
sendMouseLocation(command.first)
sendMouseStrokes(command.second, delay)
}
}
private fun activateWindowsWindow(windowName: String) {
val window = User32.INSTANCE.findWindow(null, windowName)
User32.INSTANCE.setActiveWindow(window)
}
private fun activateMacWindow(windowName: String) {
val command = arrayOf("osascript", "-e", "tell application \"$windowName\" to activate") //TODO: double check how this command works to ensure it works
Runtime.getRuntime().exec(command).waitFor()
}
private fun activateLinuxWindow(windowName: String) {
val xorgFlag = System.getenv("DISPLAY")
val hyprlandFlag = System.getenv("HYPRLAND_CMD") //TODO: User override of default checks, and add sway compatibility
if (hyprlandFlag != null) {
val command = arrayOf("hyprctl", "dispatch", "focuswindow", windowName) //TODO: need to check both class regex and title regex (look at hyprland wiki)
Runtime.getRuntime().exec(command).waitFor()
}
else if (xorgFlag != null) {
val command = arrayOf("xdotool", "search", "--name", windowName, "windowactivate")
Runtime.getRuntime().exec(command).waitFor()
}
}
interface User32: Library {
companion object {
val INSTANCE: User32 = Native.load("user32", User32::class.java, W32APIOptions.DEFAULT_OPTIONS)
}
fun findWindow(className: String?, windowName: String?): WinDef.HWND
fun setActiveWindow(window: WinDef.HWND): Boolean
}
}