Files
LlamaCasty/TASKS/task-22-audio-sync-offset.md
T
gramps b22d08eca6 feat: signed A/V sync offset (−500..+500), negative advances by eating live stream head (OBS eat-head semantics)
Positive offsets still delay the whole mix via the delay line (lip-sync fix);
negative offsets now ARM once at StartLive and drop |N| ms off the pipe's write
head so audio events land earlier when audio runs BEHIND video. Slider relabeled
AUDIO SYNC, Min −500, locked while live/recording (IsEditMode). LayoutStore and
VM clamp to −500..500.

OBS reference for eat-the-head negative sync: https://obsproject.com/kb/obs-studio/buffering-time (negative sync values pull audio earlier by discarding buffered player audio).

Test: StartLive_NegativeOffset_AdvancesAudio_ByDroppingTheStreamHead (6x0.9 head
must be eaten before 0.2 bed reaches the wire).
2026-09-14 12:30:44 -07:00

44 lines
3.9 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# TASK 22 — Audio sync offset
> Catalog: [`TASKS.md`](../TASKS.md) — status and requirements live here.
**Goal:** per-source audio delay compensation to prevent lip-sync drift from USB mics and capture cards.
### Status: ✅ Done (shipped TASK 22, 2026-08-31)
1. ☑ `AudioSyncOffsetMs` on `MainViewModel` (global, default 0, range 0..500 ms) — positive-only: OBS's documented fix delays the audio so it lands on video when it runs ahead; advancing audio would need a video-side delay (out of the audio layer's scope, v1.1+).
2. ☑ Applied in `AudioMixer` — post-mix interleaved-stereo delay via the pure `AudioSyncDelay` line (`Services/Audio/AudioSyncDelay.cs`), fed through a `Func<int> syncOffsetMs` seam each mix tick.
3. ☑ UI: compact "SYNC" slider (0..500) on the mic bar with a status dot (green = no-op, amber = offset set) via `IntToSyncBrushConverter`. **Provenance (recorded 2026-09-01 after a creator "I never ordered this" scare): the creator explicitly asked for OBS's delay-filter lip-sync fix BUILT NATIVELY — the feature is his, not AI drift. Its placement (preview-rail vs mic-settings/gear) remains an open design question; do not remove the capability.**
4. ☑ Persisted in `LayoutStore.Settings` (`Audio.SyncOffsetMs`) via `LoadAudioSyncOffsetMs`/`SaveAudioSyncOffsetMs`; saved from `SaveLayoutNow`.
5. ☑ Tests: `AudioSyncDelayTests` — zero-delay identity, negative→0 clamp, >500 ms clamp to 500 ms, and 10 ms → 960 interleaved-sample shift.
6. ⚠ **Post-ship regression (found 2026-09-01, first real launch):** this task's sync-delay line `_delayedMix` was declared nullable and never initialized — the first `delayed.Length` deref NRE'd EVERY live-mix tick, silently killing all live/record audio, hanging `AudioPipelineTests`, and being mislabeled a "known failure". Fixed in the recording-verification pass (init + null-check + throttled loop errors logged with stack); `AudioPipelineTests` 25/25 green afterward. Lesson recorded in MyMistakes.
### Design decisions
- **Global offset first** — one setting for all audio sources. Per-source is v1.1+.
- **Signed offset (2026-09-14)** — WIDENED from positive-only 0..500 to **−500..+500**.
> The "positive-only / advance needs video-side delay / out of scope" line below is
> RETIRED. Negative offsets now ADVANCE the audio by eating the head of the live stream
> (OBS's negative-sync behavior): `AudioMixer.StartLive` arms `_advanceSamplesRemaining`
> = |N| ms → samples, and `LiveLoopAsync` drops that many samples off the write head.
> Positive keeps using the `AudioSyncDelay` line live-reactive. Slider relabelled
> "AUDIO SYNC", `Min="-500"`, and **locked (`IsEnabled = IsEditMode`) while live or
> recording** — a negative advance can only be armed at go-live, so it must not move
> mid-session. Clamps: `Pre-viewModel` + `LayoutStore` ±500; `AudioSyncDelay` still
> clamps negative→0 internally (pure delay line, unchanged).
> **Provenance (2026-09-14): creator-directive** — asked for both directions after the
> ring-backlog fix surfaced the residual (audio can run late too: capture cards, BT,
> webcams). Regression test: `StartLive_NegativeOffset_AdvancesAudio_ByDroppingTheStreamHead`.
- **Positive-only (delay audio)** — the physically-correct direction (audio runs ahead of the video). True "advance" needs a video-side delay and is PERMANENTLY OUT with per-source sync (TASKS.md → "Out of product" — v1.x phrasing retired 2026-09-01).
- **Simple slider** — 0 to +500 ms, default 0. No numeric input needed.
- **Visual feedback** — "sync OK" status dot shows when an offset is dialled in.
### ❗ REQUIRED before 1.0 (creator directive 2026-09-14)
A **detailed user-doc tutorial** on the audio-sync feature (what -500..+500 means, the
clap-calibration recipe both directions, and that it locks while live). `docs/` currently
holds only the README image — the tutorial is unstarted. Add it to the 1.0/gold-pass checklist.