From 9ed876cb9b425d4c4df7ba65e59dcca21e72d56a Mon Sep 17 00:00:00 2001 From: Owen Gretzinger Date: Fri, 11 Jul 2025 14:43:43 -0400 Subject: [PATCH] fix: mic retry logic --- notetaker/Managers/AudioManager.swift | 54 ++++++++++++++++++++++++++- 1 file changed, 52 insertions(+), 2 deletions(-) diff --git a/notetaker/Managers/AudioManager.swift b/notetaker/Managers/AudioManager.swift index 390f753..d4fe17d 100644 --- a/notetaker/Managers/AudioManager.swift +++ b/notetaker/Managers/AudioManager.swift @@ -19,6 +19,22 @@ class AudioManager: NSObject, ObservableObject { // ScreenCaptureKit properties private var stream: SCStream? + // Add properties near the top, after existing private vars + private var micRetryCount = 0 + private let maxMicRetries = 3 + + override init() { + super.init() + NotificationCenter.default.addObserver(self, + selector: #selector(handleAudioEngineConfigurationChange), + name: .AVAudioEngineConfigurationChange, + object: audioEngine) + } + + deinit { + NotificationCenter.default.removeObserver(self) + } + func startRecording() { print("Starting recording...") @@ -63,8 +79,19 @@ class AudioManager: NSObject, ObservableObject { print("Internal cleanup completed") } + private func restartMicrophone() { + guard isRecording, micRetryCount < maxMicRetries else { return } + + print("🔄 Restarting microphone capture (attempt \(micRetryCount + 1))") + micRetryCount += 1 + + cleanupAudioEngine() + + DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) { + self.startMicrophoneTap() + } + } - /// Starts a microphone tap without creating a new Deepgram connection (used when also capturing system audio) private func startMicrophoneTap() { print("🎤 Starting microphone tap...") @@ -78,32 +105,49 @@ class AudioManager: NSObject, ObservableObject { channels: 1, interleaved: false) else { print("❌ Failed to create target audio format for mic tap") + self.restartMicrophone() return } guard let converter = AVAudioConverter(from: recordingFormat, to: targetFormat) else { print("❌ Failed to create audio converter for mic tap") + self.restartMicrophone() return } inputNode.installTap(onBus: 0, bufferSize: 1024, format: recordingFormat) { [weak self] buffer, _ in + guard let self = self else { return } + + // Check for invalid buffer + guard buffer.frameLength > 0, buffer.floatChannelData != nil else { + print("❌ Invalid mic buffer detected - restarting") + self.restartMicrophone() + return + } + // Debug mic RMS if let ch = buffer.floatChannelData?[0] { let frameCount = Int(buffer.frameLength) let samples = UnsafeBufferPointer(start: ch, count: frameCount) let rms = sqrt(samples.map { $0 * $0 }.reduce(0, +) / Float(frameCount)) print("🎤 Mic RMS: \(rms)") + + // Optional: Check for prolonged silence (e.g., RMS < threshold for multiple buffers) + // But for now, just process } - self?.processAudioBuffer(buffer, converter: converter, targetFormat: targetFormat, source: .mic) + + self.processAudioBuffer(buffer, converter: converter, targetFormat: targetFormat, source: .mic) } audioEngine.prepare() try audioEngine.start() connectToDeepgram(source: .mic) print("✅ Microphone tap started successfully") + micRetryCount = 0 // Reset on success } catch { print("❌ Failed to start microphone tap: \(error)") + self.restartMicrophone() } } @@ -200,6 +244,7 @@ class AudioManager: NSObject, ObservableObject { // Stop microphone capture cleanupAudioEngine() + micRetryCount = 0 // Close WebSocket micSocketTask?.cancel(with: .normalClosure, reason: nil) @@ -380,4 +425,9 @@ extension CMSampleBuffer { return AVAudioPCMBuffer(pcmFormat: format, bufferListNoCopy: audioBufferList.unsafePointer) } } +} + +@objc private func handleAudioEngineConfigurationChange(_ notification: Notification) { + print("🔔 Audio engine configuration changed - restarting mic") + restartMicrophone() } \ No newline at end of file