diff --git a/src/AppDelegate.swift b/src/AppDelegate.swift index b3640fa..8afd321 100644 --- a/src/AppDelegate.swift +++ b/src/AppDelegate.swift @@ -204,7 +204,7 @@ public final class AppDelegate: NSObject, NSApplicationDelegate { do { try audioRecorder.startRecording() - playSystemSound(name: "Hero") + playSystemSound(name: "Tink") statusBarController.updateIcon(state: .recording) statusBarController.buildMenu(isRecording: true) HUDOverlayController.shared.show(state: .recording(level: 0.1, isLagging: false, pingQuality: PingMonitor.shared.currentQuality)) @@ -217,7 +217,8 @@ public final class AppDelegate: NSObject, NSApplicationDelegate { } private func hasAudibleSpeech(pcmData: Data) -> Bool { - guard pcmData.count >= 3200 else { return false } + // Less than 0.25s of audio is just a momentary key tap/click + guard pcmData.count >= 8000 else { return false } var sumSquares: Double = 0 var peak: Int16 = 0 let count = pcmData.count / 2 @@ -232,7 +233,8 @@ public final class AppDelegate: NSObject, NSApplicationDelegate { } } let rms = sqrt(sumSquares / Double(count)) - return peak > 1500 && rms > 0.010 + // Raised thresholds: filter out mechanical keyclicks and keyboard taps + return peak > 2500 && rms > 0.015 } public func stopRecording() { @@ -243,10 +245,10 @@ public final class AppDelegate: NSObject, NSApplicationDelegate { let pcmData = audioRecorder.stopRecording() self.lastRecordedAudio = pcmData - // Fast-path: If user did NOT speak (silence / short ambient hold), dismiss immediately with ZERO delay! + // Fast-path: If user tapped key or did NOT speak (silence / keyclick), dismiss immediately with ZERO delay! let hasSpeech = hasAudibleSpeech(pcmData: pcmData) - if !hasSpeech && !InputBoxManager.shared.hasStreamedText { - print("AppDelegate: No audible speech detected. Immediate dismissal without freeze.") + if (!hasSpeech && !InputBoxManager.shared.hasStreamedText) || pcmData.count < 8000 { + print("AppDelegate: Short tap or silence detected. Immediate 0ms dismissal.") activeSession?.cancel() activeSession = nil isBusyFinalizing = false @@ -308,12 +310,21 @@ public final class AppDelegate: NSObject, NSApplicationDelegate { print("Soniox primary finalize error:", error) } + // Strict Silence Gating: If server cleanly finished with empty text and no network drop, + // it was just silence/key tap. Dismiss immediately with zero freeze! + if rawText.isEmpty && !isNetworkError && !self.isNetworkDegraded { + print("AppDelegate: Server cleanly returned empty text for silence. Immediate dismissal.") + self.isBusyFinalizing = false + HUDOverlayController.shared.hide() + return + } + let hasSpeech = hasAudibleSpeech(pcmData: self.lastRecordedAudio) let isTruncated = hasSpeech && self.lastRecordedAudio.count > 64000 && rawText.count < 10 - let needsRetry = (isNetworkError || self.isNetworkDegraded || isTruncated || rawText.isEmpty) && hasSpeech && self.lastRecordedAudio.count > 16000 + let needsRetry = (isNetworkError || self.isNetworkDegraded || isTruncated) && hasSpeech && self.lastRecordedAudio.count > 24000 if needsRetry { - let reason = isNetworkError ? "WebSocket network error" : (isTruncated ? "Truncated transcript (\(rawText.count) chars for \(self.lastRecordedAudio.count) bytes)" : "Empty transcript for audible speech") + let reason = isNetworkError ? "WebSocket network error" : "Truncated transcript (\(rawText.count) chars for \(self.lastRecordedAudio.count) bytes)" startSmartAudioRecovery(pcmData: self.lastRecordedAudio, reason: reason) return }