From 81e0925f6f112dc6515e5b58c0f80e09e9aab295 Mon Sep 17 00:00:00 2001 From: Jeremy Rand Date: Tue, 19 Oct 2021 00:55:58 -0400 Subject: [PATCH] Move application logic out of view code. --- ListenerGS/Info.plist | 2 +- ListenerGS/SpeechForwarder.swift | 219 +++++++++++++++++++++++++++++++ 2 files changed, 220 insertions(+), 1 deletion(-) create mode 100644 ListenerGS/SpeechForwarder.swift diff --git a/ListenerGS/Info.plist b/ListenerGS/Info.plist index 2d15f66..32907fe 100644 --- a/ListenerGS/Info.plist +++ b/ListenerGS/Info.plist @@ -19,7 +19,7 @@ CFBundleShortVersionString 1.0 CFBundleVersion - 122 + 132 LSApplicationCategoryType public.app-category.utilities LSRequiresIPhoneOS diff --git a/ListenerGS/SpeechForwarder.swift b/ListenerGS/SpeechForwarder.swift new file mode 100644 index 0000000..ec69ab3 --- /dev/null +++ b/ListenerGS/SpeechForwarder.swift @@ -0,0 +1,219 @@ +// +// SpeechForwarder.swift +// ListenerGS +// +// Created by Jeremy Rand on 2021-10-18. +// + +import Foundation + +import Speech + +class SpeechForwarder : ObservableObject { + @Published var listening = false + @Published var listenEnabled = false + @Published var log = "" + @Published var ipAddress = "" + private var textHeard = "" + @Published var isEditing = false + + let LISTEN_STATE_MSG = 1 + let LISTEN_TEXT_MSG = 2 + + let port = 19026 + private var client: TCPClient? + + private let speechRecognizer = SFSpeechRecognizer(locale: Locale(identifier: "en-US"))! + + private var recognitionRequest: SFSpeechAudioBufferRecognitionRequest? + + private var recognitionTask: SFSpeechRecognitionTask? + + private let audioEngine = AVAudioEngine() + + func logError(message: String) { + log.append("ERROR: " + message + "\n") + } + + func logEvent(message: String) { + log.append("EVENT: " + message + "\n") + } + + func validate(destination : String) { + logEvent(message: "Attempting to connect to " + destination) + client = TCPClient(address: destination, port: Int32(port)) + guard let client = client else { return } + switch client.connect(timeout: 10) { + case .success: + listenEnabled = true + logEvent(message: "Connected to " + destination) + case .failure(let error): + client.close() + self.client = nil + logError(message: String(describing: error)) + break + } + } + + func listen() { + self.listening.toggle() + if (self.listening) { + SFSpeechRecognizer.requestAuthorization { authStatus in + // The authorization status results in changes to the + // app’s interface, so process the results on the app’s + // main queue. + OperationQueue.main.addOperation { + switch authStatus { + case .authorized: + break + + case .denied: + self.listening = false + break + + case .restricted: + self.listening = false + break + + case .notDetermined: + self.listening = false + break + + default: + self.listening = false + break + } + } + } + } + + guard let client = client else { return } + if (self.listening) { + switch (client.send(data: isListening())) { + case .success: + break + case .failure(let error): + self.listening = false + logError(message: String(describing: error)) + } + } + + if (self.listening) { + do { + try startRecording() + logEvent(message: "Listening...") + } + catch { + self.listening = false + } + } + + if (!self.listening) { + logEvent(message: "Listening stopped") + audioEngine.stop() + recognitionRequest?.endAudio() + switch (client.send(data: isListening())) { + case .success: + break + case .failure(let error): + self.listening = false + logError(message: String(describing: error)) + } + } + } + + private func isListening() -> Data { + return pack(" 0) { + if (latestText.prefix(commonChars) == self.textHeard.prefix(commonChars)) { + break + } + commonChars -= 1 + } + var stringToSend = "" + if (commonChars < self.textHeard.count) { + stringToSend = String(repeating: "\u{7f}", count: self.textHeard.count - commonChars) + } + stringToSend.append(contentsOf: latestText.suffix(latestText.count - commonChars).replacingOccurrences(of: "\n", with: "\r")) + + if (stringToSend.count > 0) { + // TODO - Handle strings to send that are longer than 64K (doubt that would happen though) + // TODO - Try to convert encoding from utf8 to something the GS can understand. + switch (client.send(data: pack("