From b2c2cbd587eeed3ec621cd3b9ec8bf1c2e564c91 Mon Sep 17 00:00:00 2001 From: Jeremy Rand Date: Tue, 22 Feb 2022 23:52:00 -0500 Subject: [PATCH] Add code to pace the text to the GS to reduce the amount of re-typing required. --- ListenerGS/GSView.swift | 4 +- ListenerGS/Info.plist | 2 +- ListenerGS/SpeechForwarder.swift | 242 +++++++++++++++++++++---------- 3 files changed, 171 insertions(+), 77 deletions(-) diff --git a/ListenerGS/GSView.swift b/ListenerGS/GSView.swift index 063ace5..55b523f 100644 --- a/ListenerGS/GSView.swift +++ b/ListenerGS/GSView.swift @@ -52,7 +52,7 @@ struct GSView: View { speechForwarder.connect(destination: ipAddress) } } - .disabled(false) + .disabled(speechForwarder.connecting) .buttonStyle(GSButtonStyle()) Button(speechForwarder.listening ? @@ -60,7 +60,7 @@ struct GSView: View { "\(Image(systemName: "ear.and.waveform")) Listen and Send Text") { speechForwarder.listen() } - .disabled(!speechForwarder.connected) + .disabled((!speechForwarder.connected) || (!speechForwarder.listening && speechForwarder.sending)) .buttonStyle(GSButtonStyle()) } .fixedSize(horizontal: true, vertical: false) diff --git a/ListenerGS/Info.plist b/ListenerGS/Info.plist index 0ac7358..c252e06 100644 --- a/ListenerGS/Info.plist +++ b/ListenerGS/Info.plist @@ -19,7 +19,7 @@ CFBundleShortVersionString 1.0 CFBundleVersion - 390 + 434 LSApplicationCategoryType public.app-category.utilities LSRequiresIPhoneOS diff --git a/ListenerGS/SpeechForwarder.swift b/ListenerGS/SpeechForwarder.swift index d021b5f..86bf0c2 100644 --- a/ListenerGS/SpeechForwarder.swift +++ b/ListenerGS/SpeechForwarder.swift @@ -12,10 +12,13 @@ import Speech class SpeechForwarder : ObservableObject { @Published var listening = false @Published var connected = false + @Published var connecting = false @Published var textHeard = "" + @Published var sending = false let LISTEN_STATE_MSG = 1 let LISTEN_TEXT_MSG = 2 + let LISTEN_SEND_MORE = 3 let port = 19026 private var client: TCPClient? @@ -30,19 +33,31 @@ class SpeechForwarder : ObservableObject { private let logger = Logger() + private let queue = OperationQueue() + + private var condition = NSCondition() + private var latestText = "" + func connect(destination : String) { - logger.debug("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: - connected = true - logger.debug("Connected to \(destination)") - case .failure(let error): - client.close() - self.client = nil - logger.error("Failed to connect to \(destination): \(String(describing: error))") - break + connecting = true + queue.addOperation { + self.logger.debug("Attempting to connect to \(destination)") + self.client = TCPClient(address: destination, port: Int32(self.port)) + guard let client = self.client else { + OperationQueue.main.addOperation { self.connecting = false } + return + } + switch client.connect(timeout: 10) { + case .success: + OperationQueue.main.addOperation { self.connected = true } + self.logger.debug("Connected to \(destination)") + case .failure(let error): + client.close() + self.client = nil + self.logger.error("Failed to connect to \(destination): \(String(describing: error))") + break + } + OperationQueue.main.addOperation { self.connecting = false } } } @@ -52,7 +67,13 @@ class SpeechForwarder : ObservableObject { } guard let client = client else { return } + + condition.lock() client.close() + self.client = nil + condition.broadcast() + condition.unlock() + connected = false } @@ -64,25 +85,25 @@ class SpeechForwarder : ObservableObject { // 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 + switch authStatus { + case .authorized: + break + + case .denied: + self.listening = false + break - case .restricted: - self.listening = false - break + case .restricted: + self.listening = false + break - case .notDetermined: - self.listening = false - break - - default: - self.listening = false - break + case .notDetermined: + self.listening = false + break + + default: + self.listening = false + break } } } @@ -111,16 +132,21 @@ class SpeechForwarder : ObservableObject { if (!self.listening) { logger.debug("Stopped listening") - audioEngine.stop() recognitionRequest?.endAudio() + audioEngine.stop() + audioEngine.inputNode.removeTap(onBus: 0) recognitionTask?.cancel() - audioEngine.inputNode.removeTap(onBus: 0); - audioEngine.inputNode.reset() + + self.recognitionRequest = nil + self.recognitionTask = nil + condition.lock() + self.listening = false + condition.broadcast() + condition.unlock() switch (client.send(data: isListening())) { case .success: break case .failure(let error): - self.listening = false logger.error("Failed to send header: \(String(describing: error))") } } @@ -130,43 +156,109 @@ class SpeechForwarder : ObservableObject { return pack(" Bool { + guard let client = client else { return false } + var commonChars = lastSent.count while (commonChars > 0) { - if (latestText.prefix(commonChars) == self.textHeard.prefix(commonChars)) { + if (latestText.prefix(commonChars) == lastSent.prefix(commonChars)) { break } commonChars -= 1 } var stringToSend = "" - if (commonChars < self.textHeard.count) { - stringToSend = String(repeating: "\u{7f}", count: self.textHeard.count - commonChars) + if (commonChars < lastSent.count) { + stringToSend = String(repeating: "\u{7f}", count: lastSent.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) - let nsEnc = CFStringConvertEncodingToNSStringEncoding(CFStringEncoding(CFStringBuiltInEncodings.macRoman.rawValue)) - let encoding = String.Encoding(rawValue: nsEnc) // String.Encoding - if let bytes = stringToSend.data(using: encoding) { - switch (client.send(data: pack("