G3: 1 clock - PDC latency that + playhead engine-derived
This commit is contained in:
+10
-1
@@ -1134,18 +1134,27 @@ async def ws_fx_realtime(websocket: WebSocket, session_id: str):
|
||||
if _pf is not None:
|
||||
_pf.write("%d pump_start\n" % int(_time.time() * 1000))
|
||||
_last_lat = None
|
||||
_last_proc = None
|
||||
try:
|
||||
while True:
|
||||
try:
|
||||
sess.flush_input_stale() # input lẻ kẹt trong pending → publish
|
||||
_lats = sess.get_latencies()
|
||||
_total = sum(_lats.values()) if _lats else 0
|
||||
if _total != _last_lat:
|
||||
# G3: processed samples (playhead engine-derived) — bridge
|
||||
# bao moi block; gui kem latency message khi doi (khong gate
|
||||
# theo total nua — total chi doi khi chain doi, processed
|
||||
# doi lien tuc). App: position = (processed - anchor - lat)/sr.
|
||||
_proc = sess.get_processed()
|
||||
_proc_changed = (_proc is not None and _proc != _last_proc)
|
||||
if _total != _last_lat or _proc_changed:
|
||||
_last_lat = _total
|
||||
_last_proc = _proc
|
||||
await websocket.send_text(json.dumps({
|
||||
"cmd": "latency",
|
||||
"total": _total,
|
||||
"latencies": _lats,
|
||||
"processed": _proc,
|
||||
"stats": sess.stats,
|
||||
}))
|
||||
except Exception:
|
||||
|
||||
+14
-2
@@ -128,6 +128,7 @@ class FxRealtimeSession:
|
||||
# Phase 2.8: C++ consume qua SHM control ring.
|
||||
self.pending_params = {} # {slot_idx: {key: value}}
|
||||
self.latencies = {} # {slot_idx: samples}
|
||||
self.processed = None # G3: processed samples tu bridge (lat slot 0xFFFFFFFE)
|
||||
# Diagnostics (FXRT crackle hunt): drop/publish counters.
|
||||
self.stats = {'publish': 0, 'drop_batch': 0, 'drop_blocks': 0,
|
||||
'stale_flush': 0, 'read_blocks': 0, 'read_empty': 0,
|
||||
@@ -396,6 +397,12 @@ class FxRealtimeSession:
|
||||
self._lat_drain()
|
||||
return dict(self.latencies)
|
||||
|
||||
def get_processed(self):
|
||||
"""G3: processed samples (bridge da xu li) - playhead engine-derived."""
|
||||
with self._lock:
|
||||
self._lat_drain()
|
||||
return self.processed
|
||||
|
||||
def _lat_drain(self):
|
||||
shm = getattr(self, "shm", None)
|
||||
if shm is None:
|
||||
@@ -405,12 +412,17 @@ class FxRealtimeSession:
|
||||
lats = (FxLatReport * FXRT_LAT_SLOTS).from_buffer(shm.buf, LAT_OFF)
|
||||
while h.lat_read < h.lat_write:
|
||||
ls = h.lat_read & (h.lat_slots - 1)
|
||||
self.latencies[int(lats[ls].slot)] = int(lats[ls].samples)
|
||||
_slot = int(lats[ls].slot)
|
||||
_samples = int(lats[ls].samples)
|
||||
if _slot == 0xFFFFFFFE:
|
||||
# G3: processed - khong vao latencies dict (sum chain sai).
|
||||
self.processed = _samples
|
||||
else:
|
||||
self.latencies[_slot] = _samples
|
||||
h.lat_read += 1
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
# Registry toàn tiến trình (desktop app 1 user).
|
||||
_SESSIONS = {}
|
||||
_SESSIONS_LOCK = threading.Lock()
|
||||
|
||||
+59
-6
@@ -1768,7 +1768,7 @@ function encodeFloatWavBlob(pcm, sampleRate, channels) {
|
||||
// worklet → analyser (sau master fader, trước metering/destination — khớp thứ
|
||||
// tự offline export). Track: mỗi track 1 session/worklet splice vào CẢ 2 đường
|
||||
// chain (audio-clips + MIDI/SF) — mọi luồng track qua bridge trước main out.
|
||||
const fxRt = { active: false, starting: false, startToken: 0, sessionId: null, ws: null, node: null, inQueue: [], pumpTimer: null, lastInSent: 0, chainKey: null, latencyTotal: 0, fadedIn: false, pdcCalibMs: 0 };
|
||||
const fxRt = { active: false, starting: false, startToken: 0, sessionId: null, ws: null, node: null, inQueue: [], pumpTimer: null, lastInSent: 0, chainKey: null, latencyTotal: 0, fadedIn: false, pdcCalibMs: 0, processedSamples: null, processedAnchor: null };
|
||||
// Loop restart/seek/reschedule: stopAllPlayback giữ session FX realtime (không
|
||||
// teardown) → fxRtStart early-return → KHÔNG reload VST ~4s mỗi pass loop
|
||||
// (hết bypass rồi mới load lại). Chỉ set TRƯỚC nhánh restart/resume; user
|
||||
@@ -1939,6 +1939,17 @@ async function fxRtStart() {
|
||||
const d = JSON.parse(ev.data);
|
||||
if (d && d.cmd === 'latency' && typeof d.total === 'number') {
|
||||
fxRt.latencyTotal = d.total;
|
||||
// G3: processed samples (playhead engine-derived) — anchor lan dau
|
||||
// khi active (cold start: VST load xong -> message latency dau tien).
|
||||
if (typeof d.processed === 'number') {
|
||||
fxRt.processedSamples = d.processed;
|
||||
if (fxRt.active && fxRt.processedAnchor == null) {
|
||||
fxRt.processedAnchor = d.processed;
|
||||
}
|
||||
}
|
||||
// G3: bao latency that cho worklet — align wrap crossfade theo
|
||||
// processed tail (wrapFadeBlocks = queue + ceil(latency/BLOCK)).
|
||||
try { if (fxRt.node && fxRt.node.port) fxRt.node.port.postMessage({ type: 'latency', samples: fxRt.latencyTotal || 0 }); } catch (e) {}
|
||||
// Chi delay dry path khi crossfade da xong (dry silent) — ap som
|
||||
// trong luc dry con la path nghe duoc se tao gap ~80ms.
|
||||
if (fxRt.fadedIn) fxRtApplyPdc();
|
||||
@@ -2156,6 +2167,19 @@ function fxRtStop(silent) {
|
||||
// chưa có phép đo (fx_play_first) — dùng cho freeze playhead thay V39 0.31s.
|
||||
const FX_RT_ROUNDTRIP_SAMPLES = 3840;
|
||||
const FX_RT_START_EST_MS = 450;
|
||||
// G3: anchor processed — playhead = startOffset + (processed - anchor - latency)/sr
|
||||
// (da tru latency -> bam vi tri mau NGHE DUOC; pre-roll tu giu tai startOffset —
|
||||
// thay freeze/snap V40). Anchor moi play/wrap restart qua __fxRtPlayheadFreeze
|
||||
// (moi play/resume path deu goi); cold start (chua active) -> null -> WS message
|
||||
// dau tien anchor.
|
||||
function fxRtAnchorProcessed() {
|
||||
if (fxRt && fxRt.active && typeof fxRt.processedSamples === 'number') {
|
||||
fxRt.processedAnchor = fxRt.processedSamples;
|
||||
} else {
|
||||
fxRt.processedAnchor = null;
|
||||
}
|
||||
}
|
||||
|
||||
function fxRtApplyPdc() {
|
||||
const ctx = getAudioContext();
|
||||
const bus = masterBus;
|
||||
@@ -20843,6 +20867,13 @@ const App = () => {
|
||||
// session không active trong 1500ms → hủy freeze (đừng đóng băng playhead vô hạn).
|
||||
window.__fxRtPlayheadFreeze = function (allowCold) {
|
||||
try { if (_fxRtFreezeTimer) { clearTimeout(_fxRtFreezeTimer); _fxRtFreezeTimer = null; } } catch (e3) {}
|
||||
// G3: FX active -> playhead engine-derived (processed) — KHONG freeze/snap.
|
||||
// Anchor processed tai day (moi play/resume path deu goi ham nay).
|
||||
if (fxRt && fxRt.active) {
|
||||
_fxRtPlayheadSnapPending = false;
|
||||
fxRtAnchorProcessed();
|
||||
return;
|
||||
}
|
||||
// V41: gate theo CHAIN (không chỉ active) — session FX warm (stop giữ warm,
|
||||
// bypass toggle gọi rebuildMasteringGraph KHÔNG gọi fxRtStop) → fxRt.active vẫn
|
||||
// true khi bypass → trước đây freeze nhầm → playhead chạy trước → wrap sớm → vấp.
|
||||
@@ -24009,8 +24040,19 @@ const App = () => {
|
||||
if (window.NativeBridgeService && window.NativeBridgeService.isBridgeConnected && (isPlaying || isPlayingRef.current)) {
|
||||
try {
|
||||
const _pctx = getAudioContext();
|
||||
const _elapsed = _pctx.currentTime - (startAudioTimeRef.current || _pctx.currentTime);
|
||||
bridgePlayheadSampleRef.current = Math.max(0, Math.floor((startOffsetTimeRef.current + _elapsed) * (_pctx.sampleRate || 44100)));
|
||||
// G3: FX active -> position tu processed engine (da tru latency) — bam
|
||||
// vi tri mau NGHE duoc, khong freeze/snap; fallback wall-clock.
|
||||
let _posSamples;
|
||||
if (fxRt && fxRt.active && typeof fxRt.processedSamples === 'number' && fxRt.processedAnchor != null) {
|
||||
_posSamples = Math.max(
|
||||
Math.floor(startOffsetTimeRef.current * (_pctx.sampleRate || 44100)),
|
||||
Math.floor(startOffsetTimeRef.current * (_pctx.sampleRate || 44100) + (fxRt.processedSamples - fxRt.processedAnchor - (fxRt.latencyTotal || 0)))
|
||||
);
|
||||
} else {
|
||||
const _elapsed = _pctx.currentTime - (startAudioTimeRef.current || _pctx.currentTime);
|
||||
_posSamples = Math.max(0, Math.floor((startOffsetTimeRef.current + _elapsed) * (_pctx.sampleRate || 44100)));
|
||||
}
|
||||
bridgePlayheadSampleRef.current = _posSamples;
|
||||
// set_position ~mỗi giây: C++ A13 cập nhật timeline anchor, tối ưu cho
|
||||
// seek/loop (Rust pump + C++ không tự biết vị trí; JS là nguồn duy nhất).
|
||||
try {
|
||||
@@ -24268,7 +24310,10 @@ const App = () => {
|
||||
// yên (chế độ edit). Loop luôn tiếp tục (rAF).
|
||||
if (isPlaying) {
|
||||
const context = getAudioContext();
|
||||
const _ph = startOffsetTimeRef.current + (context.currentTime - startAudioTimeRef.current);
|
||||
let _ph = startOffsetTimeRef.current + (context.currentTime - startAudioTimeRef.current);
|
||||
if (fxRt && fxRt.active && typeof fxRt.processedSamples === 'number' && fxRt.processedAnchor != null) {
|
||||
_ph = startOffsetTimeRef.current + (fxRt.processedSamples - fxRt.processedAnchor - (fxRt.latencyTotal || 0)) / (context.sampleRate || 44100);
|
||||
}
|
||||
let _local = _ph;
|
||||
const _itemInfo = getPianoRollItemInfo(st);
|
||||
if (_itemInfo) {
|
||||
@@ -24292,8 +24337,11 @@ const App = () => {
|
||||
const context = getAudioContext();
|
||||
const speedFactor = st.speed || 1.0;
|
||||
const elapsed = context.currentTime - startAudioTimeRef.current;
|
||||
const wallTime = startOffsetTimeRef.current + elapsed;
|
||||
let wallTime = startOffsetTimeRef.current + elapsed;
|
||||
const bufferPos = startBufferOffsetRef.current + elapsed * speedFactor;
|
||||
if (fxRt && fxRt.active && typeof fxRt.processedSamples === 'number' && fxRt.processedAnchor != null) {
|
||||
wallTime = startOffsetTimeRef.current + (fxRt.processedSamples - fxRt.processedAnchor - (fxRt.latencyTotal || 0)) / (context.sampleRate || 44100);
|
||||
}
|
||||
|
||||
// Loop sub-tab selection (bufferPos is buffer-time)
|
||||
if (st.selectionStart !== null && st.selectionEnd !== null && st.selectionStart !== st.selectionEnd && st.isLooping) {
|
||||
@@ -24363,6 +24411,11 @@ const App = () => {
|
||||
const context = getAudioContext();
|
||||
const elapsed = context.currentTime - startAudioTimeRef.current;
|
||||
const updatedTime = startOffsetTimeRef.current + elapsed;
|
||||
// G3: playhead display engine-derived khi FX active (wrap/loop trigger giu
|
||||
// wall-clock — transport truth).
|
||||
const _playheadSec = (fxRt && fxRt.active && typeof fxRt.processedSamples === 'number' && fxRt.processedAnchor != null)
|
||||
? Math.max(startOffsetTimeRef.current, startOffsetTimeRef.current + (fxRt.processedSamples - fxRt.processedAnchor - (fxRt.latencyTotal || 0)) / (context.sampleRate || 44100))
|
||||
: updatedTime;
|
||||
|
||||
// Selection Loop - LOOP_MAKER.md + LOOP_EDITOR_2.md §4.2
|
||||
// If selection cleared by user, play linearly (don't loop)
|
||||
@@ -24421,7 +24474,7 @@ const App = () => {
|
||||
setCurrentTime(0);
|
||||
return;
|
||||
}
|
||||
setCurrentTime(updatedTime);
|
||||
setCurrentTime(_playheadSec);
|
||||
animationFrameIdRef.current = requestAnimationFrame(updatePlayhead);
|
||||
};
|
||||
useEffect(() => {
|
||||
|
||||
@@ -56,6 +56,7 @@ class SfFxRealtimeProcessor extends AudioWorkletProcessor {
|
||||
this.lastOutL = 0.0; // mẫu output cuối — nguồn crossfade + điểm bắt đầu fade-out
|
||||
this.lastOutR = 0.0;
|
||||
this.wrapFadeBlocks = -1; // V42: số block tail pass cũ còn lại sau clear_queue('loop') — đếm ngược để crossfade đúng seam tail→head
|
||||
this.latencySamples = 0; // G3: latency chain thật (engine qua WS) — align wrap fade theo processed tail
|
||||
this.WRAP_FADE = 256; // V42: crossfade 256 mẫu (~5.3ms @48k) tại seam wrap — che click jump nội dung end→start (FX path)
|
||||
this.fadeFromL = 0.0; // mẫu cố định khi fade-out underrun (fade tuyến tính tới 0)
|
||||
this.fadeFromR = 0.0;
|
||||
@@ -63,6 +64,7 @@ class SfFxRealtimeProcessor extends AudioWorkletProcessor {
|
||||
const d = e.data;
|
||||
if (d && d.type === 'set_param') { this.port.postMessage(d); return; }
|
||||
if (d && d.type === 'flush_done') { this.flushPending = false; return; }
|
||||
if (d && d.type === 'latency') { this.latencySamples = (d.samples || 0); return; }
|
||||
if (d && d.type === 'flush_for_stop') {
|
||||
// V26: user stop — main thread báo xóa audio tail trong queue (audio đã
|
||||
// xử lí còn phát ~0.3-0.5s sau stop = tiếng "ding"). Fade-out 21ms từ
|
||||
@@ -105,7 +107,10 @@ class SfFxRealtimeProcessor extends AudioWorkletProcessor {
|
||||
// V42: dem so block tail pass cu con lai trong queue -> wrap crossfade
|
||||
// trigger dung luc block dau pass moi len front (seam tail->head).
|
||||
if (d.mode === 'loop') {
|
||||
this.wrapFadeBlocks = this.outQueue.length;
|
||||
// G3: align processed-tail — tail pass cũ còn in-flight trong bridge
|
||||
// (FX latency thật từ engine) chưa về queue; đếm đủ block tới seam
|
||||
// (block đầu pass mới lên front) → crossfade đúng chỗ jump end→start.
|
||||
this.wrapFadeBlocks = this.outQueue.length + Math.ceil((this.latencySamples || 0) / this.BLOCK);
|
||||
return;
|
||||
}
|
||||
if (d.mode !== 'play' && this.outQueue.length < this.FILL) return;
|
||||
|
||||
@@ -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`.
|
||||
@@ -0,0 +1,62 @@
|
||||
"""G3 check — processed slot 0xFFFFFFFE tach rieng, khong lam sai latencies dict.
|
||||
|
||||
Chay: python native_bridge/debug/g3_probe_check.py
|
||||
"""
|
||||
import ctypes
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
|
||||
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..")))
|
||||
|
||||
try:
|
||||
_winmm = ctypes.windll.winmm
|
||||
_winmm.timeBeginPeriod(1)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
import numpy as np # noqa: E402
|
||||
|
||||
from app.core.fx_realtime import FxRealtimeSession, FXRT_BLOCK # noqa: E402
|
||||
|
||||
# Chon JUCE bridge (processed slot chi co o JuceFxLoop.cpp).
|
||||
_REPO = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", ".."))
|
||||
os.environ["SF_JUCE_FX_BRIDGE_PATH"] = os.path.join(
|
||||
_REPO, "native_bridge", "build_juce", "Release", "juce_fx_bridge.exe")
|
||||
|
||||
SR = 48000
|
||||
PURECOMP = r"C:\Program Files\Common Files\VST3\Sonible\purecomp.vst3"
|
||||
|
||||
sess = FxRealtimeSession([{"type": "vst3", "path": PURECOMP, "bypass": False, "preset_b64": ""}], SR)
|
||||
try:
|
||||
sess.wait_ready(30)
|
||||
time.sleep(1.0)
|
||||
lat0 = dict(sess.get_latencies())
|
||||
proc0 = sess.get_processed()
|
||||
print("lat0:", lat0)
|
||||
print("proc0:", proc0)
|
||||
|
||||
t = np.arange(FXRT_BLOCK, dtype=np.float32) / SR
|
||||
s = (np.sin(2 * np.pi * 440 * t) * 0.1).astype(np.float32)
|
||||
inter = np.empty(FXRT_BLOCK * 2, dtype=np.float32)
|
||||
inter[0::2] = s
|
||||
inter[1::2] = s
|
||||
blk = inter.tobytes()
|
||||
|
||||
n_ok = 0
|
||||
last = proc0
|
||||
for _ in range(400): # ~2.1s
|
||||
sess.write_input(blk)
|
||||
sess.flush_input_stale(0.001)
|
||||
sess.read_output()
|
||||
time.sleep(0.002)
|
||||
time.sleep(0.5)
|
||||
lat1 = dict(sess.get_latencies())
|
||||
proc1 = sess.get_processed()
|
||||
print("lat1:", lat1)
|
||||
print("proc1:", proc1)
|
||||
assert lat0 == lat1, "latencies dict changed!"
|
||||
assert isinstance(proc1, int) and proc1 >= (proc0 or 0), "processed not advancing"
|
||||
print("OK: latencies dict unchanged, processed=%d->%d" % (proc0 or 0, proc1))
|
||||
finally:
|
||||
sess.close()
|
||||
@@ -236,6 +236,15 @@ int run_juce_fx_loop(const std::string& jobPath, const std::string& shmName,
|
||||
MemoryBarrier();
|
||||
ipc->h.outWrite++;
|
||||
++processed;
|
||||
// G3: báo processed samples (playhead engine-derived — app tính
|
||||
// position = (processed - anchor - latency)/sr) qua lat ring slot
|
||||
// đặc biệt 0xFFFFFFFE. Python _lat_drain tách riêng (không vào
|
||||
// latencies dict). Ring 8 slot, Python drain mỗi ~2ms — an toàn.
|
||||
const uint32_t pls = ipc->h.latWrite & (FXRT_LAT_SLOTS - 1);
|
||||
ipc->lat[pls].slot = 0xFFFFFFFEu;
|
||||
ipc->lat[pls].samples = (uint32_t)(processed * n);
|
||||
MemoryBarrier();
|
||||
ipc->h.latWrite++;
|
||||
}
|
||||
const auto perfIterT1 = std::chrono::steady_clock::now();
|
||||
const double perfLoopMs = std::chrono::duration<double, std::milli>(perfIterT1 - perfT0).count();
|
||||
|
||||
Binary file not shown.
Reference in New Issue
Block a user