# G0 — Baseline đo latency (kết quả) Ngày: 2026-08-27. Probe: `native_bridge/debug/g0_latency_probe.py` (chạy với `timeBeginPeriod(1)`, 10k block/config, pipeline thật Python→SHM→fx_vst_bridge→SHM). Log: `native_bridge/debug/g0_probe_run_full.log` (run 1), `native_bridge/debug/g0_probe_run2.log` (run 2 — có `pump_rate` + drain đầy đủ). ## 1. Bảng latency plugin (getLatencySamples, @48kHz) | Plugin | getLatencySamples | ms @48k | Ghi chú | |---|---|---|---| | Sonible pureComp | 2108 | 43.9 | báo latency thật (lookahead) | | iZotope Ozone 11 | 0 | 0 | không báo latency (realtime mode) | | empty (passthrough) | — | — | chain rỗng | Nguồn: probe `plugin_latency` + log app thật `fxrt_bridge.log` (`[RenderFx] STEP getLatencySamples = 2108/0`). ## 2. Jitter round-trip (10k block, 53.3s/config) Round-trip = thời gian từ lúc probe gọi `write_input(block j)` đến lúc nhận `read_output()` block j (FIFO theo index). Đã sửa pacing probe bằng `timeBeginPeriod(1)` (bridge C++ đã gọi — `RealtimeFxLoop.cpp:160`); lần chạy trước thiếu nó → Sleep 15.6ms granularity → pump ~22ms/batch ≠ 21.33ms realtime → pureComp bị "stall 1.9s" giả. Với pacing chuẩn, stall đó biến mất — là artifact probe, KHÔNG phải VST stall. | Config | mean ms | σ | p50 | p99 | max | recv/sent | pump_rate | drained | |---|---|---|---|---|---|---|---|---| | empty | 21.67 | 0.88 | 21.70 | 22.03 | 26.51 | 10000/10000 | 184.8 | ✅ | | pureComp | 21.69 | 0.15 | 21.67 | 22.03 | 24.07 | 10000/10000 | 184.9 | ✅ | | Ozone 11 | 21.70 | 0.14 | 21.68 | 22.02 | 22.20 | 10000/10000 | 184.8 | ✅ | - Round-trip ≈ 21.5–21.7ms = đúng 1 batch 4 block (21.33ms) + overhead nhỏ (SHM copy + bridge proc 0.001–0.45ms). Latency plugin KHÔNG nằm trong round-trip — plugin latency bù bằng PDC (không chặn pipeline). - **`drained=True` mọi config (run 2): bridge xử lí HẾT 10000 block, recv = sent — KHÔNG mất block, KHÔNG backlog tích lũy.** Đây là bằng chứng quyết định: không có drift thật (xem §4). - σ round-trip: 0.14–0.15ms (pureComp/Ozone) — RẤT nhỏ → **Gate G2 MỞ**: PDC mẫu có ý nghĩa, không cần giảm jitter trước (không cần relay WS→SHM sớm). - empty σ=0.88: 1 outlier khởi động (min=0.05ms), p50/p99 vẫn 21.7/22.0. ## 3. Inter-arrival deviation | Config | σ ms | min | max | |---|---|---|---| | empty | 9.44 | −5.33 | 38.4 | | pureComp | 9.36 | −5.33 | 23.5 | | Ozone 11 | 9.36 | −5.33 | 17.8 | Block về theo batch 4 (bridge xử lí take=4/lần) → inter-arrival lệch so với 5.33ms lý tưởng là đặc tính batching, không phải jitter ngẫu nhiên. Round-trip σ (§2) là thước đo đúng cho PDC; inter-arrival dùng cho sizing FILL — xem §5. ## 4. Bridge FxRTPerf | Nguồn | procAvg | procMax | loopAvg | loopMax | rate blk/s | vs realtime 187.5 | |---|---|---|---|---|---|---| | probe empty | 0.001ms | 0.06ms | 22.0ms | 1017ms | 181.5 | −3.2% | | probe pureComp | 0.46ms | 1.37ms | 22.0ms | 1011ms | 181.6 | −3.1% | | probe Ozone | 0.14ms | 0.71ms | 22.0ms | 1015ms | 181.5 | −3.2% | | app thật (master chain đầy) | 3.4ms | 10.0ms | 21.4ms | 156ms | 186.5 | −0.5% | - **Rate "chậm 3% (probe) / 0.5% (app)" KHÔNG phải drift thật — artifact đo.** Bằng chứng: run 2 `drained=True`, recv=sent=10000 mọi config — nếu bridge thật sự chậm 3%, backlog 300+ block phải tích lại và drain phải mất >1.5s sau pump (thực tế drain xong ngay trong vòng lặp pump, `pump_rate` chỉ thấp vì probe tự sleep lệch pha với nhịp bridge poll 1ms + granularity sleep). Bridge xử lí ASAP khi có input (`avail>0` → process ngay) — không có pace riêng để "chậm"; output rate = input rate. App thật rate 186.5 thấp hơn vì runSec gồm idle trước khi audio chảy (chain load, chờ play). - **loopMax ≈ 1s ở MỌI config probe kể cả empty** — spike 1s/lần chạy ~53s, không phải VST (empty cũng có). Không xuất hiện trong log app thật gần nhất (loopMax 156ms) — nghi Windows scheduler/antivirus/power, cần theo dõi khi chạy thật. Probe round-trip max vẫn ≤27ms → spike xảy ra lúc bridge idle, không chặn block đang xử lí (không gây underrun). - **Kết luận: KHÔNG cần bù drift.** FILL 8 block + underrun-fade/resume hiện có đủ — u=3/session (fx_stats) hiếm, do main-thread stall (GUI), không phải drift đều. Đề xuất resync/bù rate ban đầu rút lại (xem §6). ## 5. ~0.22s gồm gì (xác nhận) Latency path thực đo + hằng số app: | Thành phần | ms | Nguồn | |---|---|---| | Batching 4 block (pipeline) | ~21.6 | probe round-trip (§2) | | FILL buffer worklet 8 block | 42.7 | `sf-fx-realtime.js` FILL=8 | | Plugin latency (pureComp) | 43.9 | `getLatencySamples=2108` @48k | | WS/main-thread jitter (warm, p50) | ~101 | dbg.log `fx_play_first` sincePlay 38–160ms | | **Tổng nghe thấy (warm)** | **~210ms ≈ 0.21s** | khớp ~0.22s quan sát | - PDC hiện tại `FX_RT_ROUNDTRIP_SAMPLES=3840` (80ms) = batching ~7 block + FILL 8 block — khớp thực đo (21.6 + 42.7 ≈ 64ms + WS jitter margin). Giữ nguyên; `latencyTotal` (plugin thật qua `{cmd:'latency'}`) cộng thêm đúng (pureComp +43.9ms). - Cold start VST load: 8–17s (dbg.log sincePlay 8111–17211ms), warm sau đó 38–160ms — là plugin init, không phải latency pipeline; không nằm trong PDC. - fx_stats app thật (session master): u=3 h=3 f=13 s=13 cap=1 fi=13 q=5–11 — underrun hiếm, queue duy trì 5–11 block → FILL đủ hấp thụ jitter thường. ## 6. Kết luận cho G2+ 1. **Gate G2: MỞ.** σ round-trip 0.14–0.15ms → PDC mẫu có nghĩa; không cần relay WS→SHM trước G2. 2. **Không có drift bridge** (đã kiểm chứng run 2: drained=True, recv=sent). Không cần bù rate/resync — FILL 8 + underrun-fade/resume đủ. Tránh thêm code không cần thiết. Nếu sau này thấy underrun đều trên session dài → đo lại `pump_rate`/`drained` trước khi kết luận drift. 3. **loopMax ~1s spike** (probe, cả empty): theo dõi, chưa thấy ở app thật. 4. Plugin latency bảng §1 đủ cho G2: `graph.getLatencySamples()` sẽ thay hằng số; dữ liệu hiện tại xác nhận pureComp là plugin duy nhất có latency thật.