Summary
Acoustic echo cancellation is currently off. Without it the microphone re-hears remote participants coming out of the speakers, so remote speech can be transcribed twice — once from the system tap, once from the mic — and diarization may split one person across two clusters.
Deferred from #58, where the obvious fix (setVoiceProcessingEnabled(true)) silenced the microphone entirely and had to be reverted. This is a quality issue, not data loss, and anyone using headphones is unaffected — hence not a release blocker.
Background: why the obvious fix failed
AVAudioEngine's voice-processing I/O gives echo cancellation, noise suppression and automatic gain in one call, with no third-party dependency. Enabling it on the input node produced complete silence on the microphone while the level meter kept moving, because the meter shows max(system, mic) and system audio was still live.
Every timeline window logged:
Timeline: microphone contributed 10s at RMS 0.00000
Removing the call restored capture immediately (RMS 0.01200 on the next run). The suspected cause is that voice-processing I/O is a duplex unit and wants a running output chain, which this engine does not have — but that was never confirmed.
The revert and its reasoning are at Logue/Engine/AudioRecorder.swift:78.
What good looks like
Any approach is fine as long as capture is proven to survive it. Options worth exploring:
- Give the engine a real output chain (e.g. a connected mixer at zero volume) so the duplex unit has both halves, then re-enable voice processing.
- Configure ducking separately via
voiceProcessingOtherAudioDuckingConfiguration so re-enabling does not also quieten the system audio being recorded — this was already needed once and is easy to forget.
- Apply echo cancellation only to the transcriber's copy of the audio, leaving the saved recording and the diarization timeline on the raw signal.
- Do nothing in the audio path and suppress the echo downstream instead — the system tap knows exactly what was played, so mic content matching it within a short window could be discarded after transcription.
Verification
This must not be re-landed on inspection alone. TimelineContributionLog exists precisely to catch it — see Logue/Engine/TimelineContributionLog.swift:19. A recording where the microphone reports a non-zero RMS is the minimum bar, and it should be checked with system audio simultaneously armed, since that is the configuration that failed.
Acceptance criteria
Notes
Summary
Acoustic echo cancellation is currently off. Without it the microphone re-hears remote participants coming out of the speakers, so remote speech can be transcribed twice — once from the system tap, once from the mic — and diarization may split one person across two clusters.
Deferred from #58, where the obvious fix (
setVoiceProcessingEnabled(true)) silenced the microphone entirely and had to be reverted. This is a quality issue, not data loss, and anyone using headphones is unaffected — hence not a release blocker.Background: why the obvious fix failed
AVAudioEngine's voice-processing I/O gives echo cancellation, noise suppression and automatic gain in one call, with no third-party dependency. Enabling it on the input node produced complete silence on the microphone while the level meter kept moving, because the meter showsmax(system, mic)and system audio was still live.Every timeline window logged:
Removing the call restored capture immediately (
RMS 0.01200on the next run). The suspected cause is that voice-processing I/O is a duplex unit and wants a running output chain, which this engine does not have — but that was never confirmed.The revert and its reasoning are at Logue/Engine/AudioRecorder.swift:78.
What good looks like
Any approach is fine as long as capture is proven to survive it. Options worth exploring:
voiceProcessingOtherAudioDuckingConfigurationso re-enabling does not also quieten the system audio being recorded — this was already needed once and is easy to forget.Verification
This must not be re-landed on inspection alone.
TimelineContributionLogexists precisely to catch it — see Logue/Engine/TimelineContributionLog.swift:19. A recording where the microphone reports a non-zero RMS is the minimum bar, and it should be checked with system audio simultaneously armed, since that is the configuration that failed.Acceptance criteria
Timeline: microphone contributed …RMS in every window, with system audio armed at the same timeNotes