Files
LlamaCasty/HANDOFF.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

83 lines
5.1 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.
# HANDOFF — 2026-09-14 (SIGNED AUDIO SYNC ONLINE: −500..+500, negative advances by eating the stream head; slider locked while live/recording)
## Branch / Commit State
`main` HEAD = **`11a7af2`** (pre-live ring-backlog fix, committed, pushed NOT authorized).
Working tree **dirty** with the signed-sync work below — NOT yet committed. **NOT pushing**
(no-push ruling still in effect; the last two fixes — audio silence + ring backlog — are
committed locally and the user has not greenlit a push).
DB now: **`Audio.SyncOffsetMs = 0`** (confirmed via sqlite3 this session — the creator slid
the sync to 0 and the write finally stuck; sound is correct at 0). The 0.54s residual in the
1128 take ≈ 300ms injected offset (leftover DB value) + ~240ms natural (partly webcam
clap-quantization ±40–85ms, partly measurement).
## ✅ SHIPPED (this dirty tree) — signed audio sync, −500..+500
**What:** the audio-sync control is now a SIGNED offset. Positive = delay the mix (audio runs
AHEAD of video — existing `AudioSyncDelay` behavior, unchanged and live-reactive). Negative =
**advance** the audio (audio runs BEHIND video): OBS's "eat the head of the buffer" fix —
the mixer drops the first |N| ms of the written stream at the pipe, re-anchoring the audio
stream so every event lands |N| ms EARLIER relative to video.
**Mechanism (AudioMixer):** `StartLive` arms `_advanceSamplesRemaining = |N| ms → samples` at
go-live (a negative offset can only eat the HEAD of the stream; it is armed once, not
live-reactive). `LiveLoopAsync` skips `min(budget, mixBuffer.Length)` samples off each write
head while the budget lasts — the pipe writer accepts a partial chunk via `AsMemory(writeFrom)`.
Positive path untouched (delay line still re-reads the Func every tick).
**UI/plumbing:**
- `MainViewModel.Audio.cs` — clamp `Math.Clamp(value, -500, 500)`, doc updated.
- `LayoutStore.Settings.cs` — `LoadAudioSyncOffsetMs`/`SaveAudioSyncOffsetMs` clamp −500..500.
- `PreviewPane.xaml` — label **SYNC → "AUDIO SYNC"**, `Minimum="-500"`, tooltip explains both
directions (calibrate with a clap: clap late → negative; early → positive), and
**`IsEnabled="{Binding IsEditMode}"`** — the slider locks during live AND recording (gun
safety, same property `IsRecording`/`StreamStatus` already raise PropertyChanged for).
- `AudioSyncDelay` unchanged (still clamps negative→0 internally; header doc updated to point
at the mixer for the advance side).
**Regression test (the ONE integration test for this change):**
`StartLive_NegativeOffset_AdvancesAudio_ByDroppingTheStreamHead` in `AudioPipelineTests.cs` —
−40 ms advance (8-tick budget at 5ms interval), emits 6×0.9 fresh right after StartLive (≤
budget, so the head MUST be eaten) then a long 0.2 bed; asserts the wire max ≈ 0.2 (< 0.3).
Fails loudly if −N no longer drops the head (0.9 leaks).
**User directives (this session):**
- Signed −500..+500 with positive=delay / negative=advance, relabel "AUDIO SYNC", default 0.
- **Lock the sync control when live or recording** (done — `IsEditMode`).
- **NOTE ONLY, no fix:** the live recording is completely different from the "recording
results" shown in Chat view (recorded in `bugs.md` — do not rediscover as a surprise).
- **Before 1.0:** write a detailed USER-DOC tutorial on the audio-sync feature (see TASKS.md
note; add it to the gold-pass/1.0 checklist). `docs/` currently holds only the README image.
## Take verification so far (ring-backlog fix)
Take `ty-20260914-1128-0000-2.mp4` (1920×1080@60fps, 356 frames / 5.93s): audio clap RMS peak
3.260s; video diff-frame 163 → 2.717s; **offset ≈ +0.54s** — collapsed from +2.11/2.22s; the
remaining ~300ms was the baked-in `SyncOffsetMs=300` (now 0). Both streams `start_time=0` — not
an `avoid_negative_ts` artifact.
## Open threads
- **Verify signed sync on device:** re-run the clap take with a NEGATIVE offset to confirm the
advance direction end-to-end (the regression test proves the mixer; a take proves the file).
- **Audio-silence verification** — fixed code (`724af14`) confirmed; creator heard real audio.
- **Webcam MJPG missing / ~10–14Hz**, layer SortOrder, truncation-with-dynamic-scenes — queued.
- **Sync control tutorial in user docs — REQUIRED before 1.0** (creator directive).
## Landmines
- testhost shares startup.log — filter by time.
- `cmd.exe /c "taskkill /F /IM ytLive.exe"` (WSL double-slashes mangle) before rebuilds.
- Build/tests: **Windows dotnet host** (`/mnt/c/Program Files/dotnet/dotnet.exe`). 0 warnings.
- ffmpeg/ffprobe: `/mnt/c/Program Files/Krita (x64)/bin/` with Windows paths.
- `MyMistakes.md` has the **audio/video sync measurement recipe** (claps + cross-correlation)
— grep it before re-deriving.
- sqlite3 lives at `/home/gramps/android-sdk/platform-tools/sqlite3` (WSL) for the DB at
`/mnt/c/Users/gramp/AppData/Roaming/ytLlive/ytLlive.db`.
## Next step
Run the verify.sh gate (clean build, 0 warnings, full suite, scope check) on the dirty tree,
then commit the signed-sync work unit (no push). Optionally a device clap take with a negative
offset to validate the advance on the file.