59 lines
3.4 KiB
Markdown
59 lines
3.4 KiB
Markdown
# 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`.
|