TASK 22: global audio sync offset (positive delay at the mixer out)
Adds AudioSyncOffsetMs (0..500ms, default 0) that delays the whole interleaved-stereo mix so audio lands on the video when it runs ahead — OBS's documented lip-sync fix. Positive-only: advancing audio needs a video-side delay and is out of the audio layer's scope. - Services/Audio/AudioSyncDelay.cs: pure delay line, flushed on Configure - AudioMixer: Func<int> syncOffsetMs seam + delay applied post-limiter - LayoutStore.Settings + MainViewModel.Audio/VM: load/save + binding - PreviewPane mic bar: SYNC slider + status dot (IntToSyncBrushConverter) - AudioSyncDelayTests: identity, negative/beyond-500 clamps, 10ms→960 samples Reference (external scan): https://obs-versions.com/blog/how-to-fix-audio-delay-on-obs (audio ahead => positive delay). Verified: build 0 warnings; 3/3 delay tests pass.
This commit is contained in:
+40
-118
@@ -2,132 +2,54 @@
|
||||
|
||||
## Branch / Commit State
|
||||
|
||||
**`main`**. TASK 31 (SceneGraph + baked-crust compositor) implemented, tests green,
|
||||
docs updated — **uncommitted**, ready to commit as one work unit (pending push at
|
||||
the user's checkpoint).
|
||||
|
||||
**Pushed** (reached `origin/main` == `85893ea`, incl. tags): Phase 1 commit `0` +
|
||||
refactor commits `1..11` + **Phase 2 commits `12..18`** + the `Controls/index.md`
|
||||
docs commit `cb54637` + **Phase 3 splits A–E** (tags `refactor-commit-A..E`) +
|
||||
**Commit F (Recording, `refactor-commit-F`)** + **Commit G (ChatOverlayLayer,
|
||||
`refactor-commit-G`)**. Everything before TASK 31 is pushed; nothing local-only.
|
||||
|
||||
**Commit log (all pushed):**
|
||||
|
||||
| Commit | Hash | Tag | Payload |
|
||||
|--------|------|-----|---------|
|
||||
| docs | `cb54637` | — | `Controls/index.md` |
|
||||
| A | `6944db8` | `refactor-commit-A` | split `MainViewModel.cs` core → **495** |
|
||||
| B | `31362d1` | `refactor-commit-B` | split `MainViewModel.Streaming.cs` → **330** |
|
||||
| C | `fab2e09` | `refactor-commit-C` | split `MainViewModel.Background.cs` → **418** |
|
||||
| D | `d3271a0` | `refactor-commit-D` | trim `SocialsDialogViewModel.cs` → **334** |
|
||||
| E | `dcb3637` | `refactor-commit-E` | split `LayoutStore.cs` → 6 partials |
|
||||
| F | `3107f92` | `refactor-commit-F` | extract recording concern → `MainViewModel.Recording.cs` |
|
||||
| G | `85893ea` | `refactor-commit-G` | **first true decomposition** → `Services/ChatOverlayLayer.cs` |
|
||||
**`main`**, working toward TASK 22 (audio sync offset). Prior milestone **TASK 31**
|
||||
(SceneGraph + baked-crust compositor) is fully done, committed (`670fe3a`), pushed
|
||||
(`origin/main` `3bf053a..670fe3a`), and verified by the user on native Windows
|
||||
PowerShell (only the 2 known pre-existing failures remained).
|
||||
|
||||
## What's In Flight
|
||||
|
||||
**TASK 31 (SceneGraph + baked-crust compositor) implemented — uncommitted, tests green.**
|
||||
Files touched (see `git status`): `Models/SceneElement.cs`, `Models/Source.cs`,
|
||||
`Models/WebcamSceneConfig.cs`, `Services/SceneGraph.cs` (new), `Services/Compositor/SceneCompositor.cs`,
|
||||
`Services/Encoder/FramePump.cs`, `ViewModels/MainViewModel.cs`, `MainViewModel.Scenes.cs`,
|
||||
`MainViewModel.Sources.cs`, `MainViewModel.Webcam.cs`, `MainViewModel.Background.cs`,
|
||||
`ytLive.Tests/SceneGraphTests.cs` (new), `ai.md`, `TASKS.md`, `HANDOFF.md`.
|
||||
Verification: SceneCompositorTests 4, StretchMathTests 4, BackgroundTests 16,
|
||||
SceneCatalogTests 18, LayoutStorePersistenceTests 12, FramePumpTests 9,
|
||||
SceneGraphTests 1 — all green. RealAppHost GUI tests hang headless (pre-existing).
|
||||
Next: run `scripts/verify.sh`/`scope-check.sh`, commit, await user's push go-ahead.
|
||||
**TASK 22 (audio sync offset) — implemented, uncommitted, awaiting commit + user
|
||||
verification on Windows.**
|
||||
|
||||
**The directive (2026-08-31, user):** rewrite the project, breaking files into
|
||||
**functional components to compliment AI retrieval/processing** — NOT line-count
|
||||
chasing. Line count is a guideline for context management, not a design goal.
|
||||
Scope (Good-Dog single use-case; user chose positive-only slider at the mixer out):
|
||||
a global `AudioSyncOffsetMs` (0..500 ms, default 0) that delays the whole
|
||||
interleaved-stereo mix so audio lands on the video when it runs ahead — OBS's
|
||||
documented lip-sync fix. Positive-only: advancing audio needs a video-side delay
|
||||
(out of the audio layer's scope; tracked for v1.1).
|
||||
|
||||
**What was done:**
|
||||
- **Phase 3 (A–E):** every production `.cs` ≤ 500, grouped by concern. These are
|
||||
*partial shuffles* — same class, same shared state. Marginal for retrieval.
|
||||
- **Commit F:** recording-output concern → `MainViewModel.Recording.cs` (partial).
|
||||
- **Commit G — the real win:** `Services/ChatOverlayLayer.cs` is a genuine
|
||||
owner-state component (buffer + renderer + timers + preview + live `RenderFrame`);
|
||||
`MainViewModel.Chat.cs` 194 → 44 (thin binding facade). First true decomposition.
|
||||
Files touched (this work unit):
|
||||
- `Services/Audio/AudioSyncDelay.cs` (new) — pure delay line, flushed on `Configure`.
|
||||
- `Services/Audio/AudioMixer.cs` — `Func<int> syncOffsetMs` seam + `_syncDelay`
|
||||
applied post-limiter in `FillAndMix`.
|
||||
- `Services/LayoutStore.Settings.cs` — `LoadAudioSyncOffsetMs`/`SaveAudioSyncOffsetMs`
|
||||
(`Audio.SyncOffsetMs`).
|
||||
- `ViewModels/MainViewModel.cs` — load offset at startup + save in `SaveLayoutNow`.
|
||||
- `ViewModels/MainViewModel.Audio.cs` — `AudioSyncOffsetMs` property (clamped, `ScheduleSave`).
|
||||
- `Controls/PreviewPane.xaml` — SYNC slider (0..500) + status dot on the mic bar.
|
||||
- `Helpers/IntToSyncBrushConverter.cs` (new) + registered in `Themes/Controls.xaml`.
|
||||
- `ytLive.Tests/AudioSyncDelayTests.cs` (new) — 3 tests.
|
||||
- `ai.md`, `TASKS.md`, `HANDOFF.md`.
|
||||
|
||||
**Expert diagnostic (recorded in `ai.md` → Key patterns):** the remaining partials
|
||||
(Audio, Webcam, Background, Scenes, Socials…) are **binding glue over already-
|
||||
extracted services** (`AudioMixer`, `CameraManager`, `ScreenCaptureManager`,
|
||||
`ChatBoxRenderer`, `SocialValidator`). They have no cohesive owner-state blob to
|
||||
peel — forcing extraction adds coupling. **Chat was the one clean peel.**
|
||||
**Verified:** `ytLive.csproj` and `ytLive.Tests.csproj` build with 0 warnings;
|
||||
`AudioSyncDelayTests` (3/3) pass via vstest filter. The full audio pipeline /
|
||||
RealAppHost suites hang headless (pre-existing) — must be verified on native
|
||||
Windows PowerShell by the user.
|
||||
|
||||
**Next (recommended):** TASK 31 landed the *core* of the scene-graph architecture
|
||||
(the `SceneGraph` owner-object owning the collection + mutation surface, and the
|
||||
baked-crust compositor optimization) without the full binding-surface peel. The
|
||||
remaining step is the ViewModel-as-thin-facade migration (moving the many
|
||||
`Scenes`/`StagedScene`/`LiveScene` binding call sites onto the graph) — deferred
|
||||
as post-1.0 in `TASKS.md` because it is exactly the "unsupervised blind peel"
|
||||
regression risk. Do it at a real Windows session where the GUI tests can run.
|
||||
**Landmine:** a stale `testhost` can lock `ytLive.Tests`'s `ytLive.dll` and break
|
||||
`dotnet build` of the test project — kill it first (`cmd.exe /c "taskkill /PID N /F"`)
|
||||
if the build hits MSB3027.
|
||||
|
||||
**Pending bus:** `ViewModels/index.md` Phase 3 tracker has rows A–E (closed);
|
||||
Phase 3 marked COMPLETE. Watch-list (under limit, no action): `Controls/PreviewPane.xaml.cs`
|
||||
(464), `ViewModels/MainViewModel.Audio.cs` (408).
|
||||
## Next
|
||||
|
||||
## Landmines
|
||||
1. User runs `scripts/verify.sh` (or native Windows vstest) to confirm the full
|
||||
suite — expect only the 2 known pre-existing failures.
|
||||
2. On confirmation, run `scripts/scope-check.sh` with the declared file list,
|
||||
commit as one work unit, await the user's push go-ahead (sub-milestone).
|
||||
|
||||
- **Pre-existing test failures (do not chase):**
|
||||
`AudioPipelineTests.Mix_HonorsProviderGains_AndGameMute_KillsTheLoopback` +
|
||||
`RoundClipInteractionTests.Round_Clip_Corner_Is_Grabbable_And_Shape_Is_Circle`.
|
||||
- **Tests occasionally abort early (236/237)** — known flake, accepted by `verify.sh`.
|
||||
- **App running locks `ytLive.exe`** → build fails MSB3027. Kill PID before building.
|
||||
- **WinForm crash from `Window_PreviewMouseLeftButtonDown`** — handled in-side the
|
||||
preview/left panel; don't add handlers that deref `OriginalSource` unguarded.
|
||||
- **Controls cannot see `Window.Resources`** (converter rule, Commit 13): any
|
||||
StaticResource the window once supplied must live at app level in
|
||||
`Themes/Controls.xaml`. The 7 converters were promoted there.
|
||||
- **Some control XAML needs its own `xmlns:models` / `xmlns:Helpers`** — a moved
|
||||
block lost its namespace when it left MainWindow's root (LeftPanel hit this,
|
||||
MC2000 "key null"). Always add the model/helper xmlns on each control root.
|
||||
- **Name-looked-up host stubs must stay in the window:** `WebViewHostPanel`
|
||||
(ctor `InitWebView2(WebViewHostPanel)`) and `ToastArea`
|
||||
(`NotificationService.InArea("ToastArea")`) are resolved by name from the
|
||||
window — keep them as window-root children, don't bury them in a control.
|
||||
OverlayHost deliberately left them in place (Commit 18) for this reason.
|
||||
- **Pre-commit gate:** `scripts/verify.sh` (0-warning build + full tests) and
|
||||
`scripts/scope-check.sh "file1" …` — scope-check REQUIRES explicit file args.
|
||||
- Build from WSL with the Windows dotnet host only (see `AGENTS.md`).
|
||||
## The directive (2026-08-31, user)
|
||||
|
||||
## Todo state
|
||||
|
||||
- MainViewModel refactor: **Phase 1 complete (11/11 partials)**, pushed.
|
||||
- MainWindow.xaml refactor: **Phase 2 complete (6/6 controls)**, pushed at `1f4624c`.
|
||||
- 500-line compliance: **Phase 3 complete (A–E, all ≤500)**, pushed.
|
||||
- **True decomposition: Commit G — `Services/ChatOverlayLayer.cs` shipped + pushed** (first owner-state component; pattern recorded in `ai.md`).
|
||||
- **TASK 31 — SceneGraph + baked-crust compositor: implemented, tests green, uncommitted** (core optimization + collection/mutation ownership + integration test; see `TASKS.md` for the 3 documented defensive deviations). The full ViewModel-as-thin-facade peel (moving the entire binding surface) is intentionally deferred post-1.0 — flagged in `TASKS.md`.
|
||||
- TASK 3: 27/30 (preview compositor 16, text source 17, alerts 20 still open).
|
||||
- TASK 4: ✅ shipped. TASK 9: items 1–3 shipped; 4–7 open. TASK 10: steps 1–7; Velopack pending.
|
||||
- TASK 18: shipped, creator verification pending. TASK 19/23, 20, 21-A: shipped.
|
||||
- TASK 22 (audio sync offset): not started — required for v1.
|
||||
|
||||
## Session summary (2026-08-31)
|
||||
|
||||
- Ran the **500-line Phase 3 split to completion** (A–E), then the continuation.
|
||||
- **Re-structuring principle (user correction, 2026-08-31):** split by
|
||||
**functionality**, so an AI can process one concern per file; the 500-line
|
||||
count is a **ceiling**, NOT the target. Do not pad or reshape code to a number.
|
||||
- **Correction of a false premise:** my plan to "extract FFmpegEncoder /
|
||||
StreamHealthMonitor / FramePump" was a no-op — those already exist as
|
||||
`Services/Encoder/*`. The honest seams were (F) the recording concern and
|
||||
(G) the chat overlay.
|
||||
- **Commit G = first TRUE decomposition:** `ChatOverlayLayer` owns chat state +
|
||||
behavior; `MainViewModel.Chat.cs` 194 → 44 thin facade. This is the pattern
|
||||
that actually compliments AI retrieval (one self-contained unit per feature),
|
||||
unlike the partial shuffles of A–E, which only marginalize context because
|
||||
every partial still shares the god-object's state.
|
||||
- **Accident caught & recovered (Commit D):** a `> ViewModels/SocialsDialogViewModel.cs`
|
||||
write that truncated the file it was reading left it at 1 line; restored via
|
||||
`git checkout --`, re-ran from temp files. **Lesson** in `MyMistakes.md`: never
|
||||
`awk … > SRC` while awking SRC; stage cuts into temp files, then assemble.
|
||||
- Everything through G is **pushed to `origin/main` (`85893ea`)** with tags A–G.
|
||||
- **TASK 31 implemented (uncommitted, tests green):** added `ElementKind` (Static/Dynamic);
|
||||
created `Services/SceneGraph.cs` owning the collection + mutation surface + bake cache;
|
||||
made `SceneCompositor` split-aware (`BakeStaticBase`/`CompositeLayers`/`Render(staticBase,split)`);
|
||||
wired `FramePump` to the optimized path; invalidated the bake on static mutations; added
|
||||
`SceneGraphTests.BakedStaticBase_WithDynamicLayer_CompositesCorrectly`. 3 documented defensive
|
||||
deviations from the spec (ChatOverlayLayer stays decoupled; background helpers stay VM-static for
|
||||
direct testability; full facade peel deferred post-1.0). See `TASKS.md` → TASK 31.
|
||||
Rewrite the project into functional components to aid AI retrieval — the
|
||||
`Services/ChatOverlayLayer.cs` style (owner-state extraction), not line-count
|
||||
chasing. TASK 31's `SceneGraph.cs` continued that pattern (an owner object owning
|
||||
the scene collection + mutation surface).
|
||||
|
||||
Reference in New Issue
Block a user