G3: 1 clock - PDC latency that + playhead engine-derived

This commit is contained in:
2026-08-28 15:51:19 +07:00
parent c72362e088
commit 6cbdbd8e9c
8 changed files with 218 additions and 10 deletions
+58
View File
@@ -0,0 +1,58 @@
# 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`.