feat(audio): microphone-only sync delay, on by default at 175 ms

The webcam's frames reach the compositor later than the mic's samples reach the audio
pipe, so the voice runs ahead of the lips. The existing lip-sync offset could not fix
that: it delays the WHOLE mix, which drags desktop and alert audio — neither of which
has a camera to follow — out of sync in the process.

So the delay line learned channels (mono for a source, stereo for the mix, offset always
in milliseconds so the frame count is what stays constant), and FillAndMix runs a second
line over micChunk before the mix. Slider-side: presets off / 150 / 200 in the LIP-SYNC
bay under the whole-mix control, persisted as Audio.MicSyncOffsetMs.

Default 175 ms is the creator's own number for a C920 pipeline (they asked for
+150..+200). It is a setting, not a constant, because the right figure belongs to the
camera and the machine — a faster camera wants less, no camera wants none.

Both delay lines are reconfigured every tick, so the 2026-09-12 trap still applies and is
documented at both sites: a Configure that reallocates discards the buffered voice every
10 ms.

673 pass, 0 warnings. Ear check pending: this is an audio change.
This commit is contained in:
2026-11-12 11:15:50 -08:00
parent f6fdab3bf2
commit 7f89534309
9 changed files with 135 additions and 13 deletions
+4 -1
View File
@@ -368,6 +368,7 @@ public partial class MainViewModel : ViewModelBase
// (yellow) instead of silently falling back to the default.
MicSourceName = _layoutStore.LoadMicSourceName();
AudioSyncOffsetMs = _layoutStore.LoadAudioSyncOffsetMs();
MicSyncOffsetMs = _layoutStore.LoadMicSyncOffsetMs();
_zoomFollowsBox = _layoutStore.LoadZoomFollowBox();
_autoChromaScreen = _layoutStore.LoadAutoChromaScreen();
_steamLicenceDismissed = _layoutStore.LoadSteamLicenceIndicatorDismissed();
@@ -413,7 +414,8 @@ public partial class MainViewModel : ViewModelBase
message => AppLog.Write(message),
micGain: _gainProvider.MicGain,
loopbackGain: _gainProvider.LoopbackGain,
syncOffsetMs: () => AudioSyncOffsetMs);
syncOffsetMs: () => AudioSyncOffsetMs,
micSyncOffsetMs: () => MicSyncOffsetMs);
_audioMixer.MicLevelChanged += OnMicLevelChanged;
_audioMixer.LoopbackLevelChanged += OnLoopbackLevelChanged;
_audioMixer.MicConnected += OnMicConnected;
@@ -641,6 +643,7 @@ public partial class MainViewModel : ViewModelBase
_layoutStore.SaveTransitionSettings(
SelectedTransitionType.ToString(), TransitionDurationMs);
_layoutStore.SaveAudioSyncOffsetMs(AudioSyncOffsetMs);
_layoutStore.SaveMicSyncOffsetMs(MicSyncOffsetMs);
}
catch (Exception ex)
{