# G3 — 1 clock: PDC latency thật + playhead engine-derived (kết quả) Ngày: 2026-08-28. Plan: `JUCE_LATENCY_PLAN.md` G3. Option A — 1 clock. ## Nội dung - **Bridge báo processed samples** qua lat ring slot đặc biệt `0xFFFFFFFE` (`JuceFxLoop.cpp`, mỗi block sau `++processed`): `samples = processed*n` (n=curBlock). Python `_lat_drain` tách slot này riêng → `self.processed` (KHÔNG vào `latencies` dict — tổng chain không đổi). Thêm `get_processed()`. - **WS latency message thêm `processed`** (`plugins.py`): gửi khi `processed` đổi (mỗi block ~5.33ms), bỏ gate chỉ theo total — total chỉ đổi khi chain đổi. - **Playhead engine-derived** (`app.jsx`): khi FX active và có anchor, `position = startOffset + (processed - anchor - latencyTotal)/sr` (đã trừ latency → bám vị trí mẫu NGHE được; pre-roll tự giữ tại startOffset qua clamp). Anchor = raw processed tại play/wrap start (`fxRtAnchorProcessed` gọi từ `__fxRtPlayheadFreeze`, mọi play/resume path đều đi qua). Bỏ freeze/snap khi active; fallback wall-clock khi không active. Áp dụng 4 chỗ: bridge position block, sub-tab `_ph`, sub-tab `wallTime`, main branch `_playheadSec` → `setCurrentTime`. - **Worklet nhận latency thật** qua port message `{type:'latency', samples}`; `clear_queue('loop')`: `wrapFadeBlocks = outQueue.length + ceil(latencySamples/BLOCK)` — align processed-tail, crossfade đúng seam end→start kể cả khi FX latency (pureComp 2108 mẫu) còn in-flight trong bridge. ## Verify (đã chạy) 1. **Build**: `cmake --build native_bridge/build_juce --config Release --target juce_fx_bridge` OK. Copy `src-tauri/binaries/juce_fx_bridge-x86_64-pc-windows-msvc.exe`, SHA256 khớp bản build (`07621b81...`). 2. **Probe SHM thật** (`native_bridge/debug/g3_probe_check.py`, JUCE bridge qua `SF_JUCE_FX_BRIDGE_PATH`, chain pureComp, SR 48k, 400 block ~2.1s): - `latencies` dict KHÔNG đổi: `{0: 2108}` trước/sau — processed tách riêng không làm sai tổng chain. - `processed` tiến 0 → 102400 (= 400 block × 256 mẫu) — bridge báo đúng. 3. **Cú pháp**: `python -m py_compile` fx_realtime.py + plugins.py OK; `node --check` worklet OK; `babel` (node_modules) parse app.jsx OK (node thuần không hiểu JSX — dùng babel, exit 0). 4. **Fake session** (object.__new__, không shm): `get_processed()` → None, `get_latencies()` → `{0: 2108}` — path không-SHM an toàn. ## Done-when trạng thái - ✅ processed tách riêng, tổng latency chain không đổi (probe) - ✅ playhead công thức engine-derived đã cài đủ 4 chỗ; freeze/snap vô hiệu khi active (code path) - ⏳ **Chưa verify bằng tai**: 100-pass loop không click với crossfade tối thiểu; playhead bám vị trí mẫu trong app thật (cần user chạy GUI + nghe). ## Ghi chú - Probe check script giữ ở `native_bridge/debug/g3_probe_check.py` (chạy lại: `python native_bridge/debug/g3_probe_check.py`). - BufferError lúc thoát (`cannot close exported pointers`) là quirk teardown shared_memory Python 3.13, không ảnh hưởng kết quả. - Latency WS ~5.33ms/msg = 187 msg/s — nhỏ so với pump audio; nếu cần tiết kiệm sau này: batch processed 4 block/lần (giảm 4×) — `ponytail: chỉ làm khi có bằng chứng quá tải`.