fix(engine): giam jitter bridge timer va fade-out khi transport stall

- RealtimeFxLoop: timeBeginPeriod(1) + sleep_until thay Sleep(1) -> loopMax 32.9->24.8ms
- plugins.py _pump_output: stall-fade 8 block gain 1->0 khi gap > 5.5*block_sec (~29ms),
  burst send, fade-in 0->1 khi resume; poll dung asyncio.to_thread(time.sleep) vi asyncio.sleep
  bi cap 15.6ms tren Win11 (IOCP) du timeBeginPeriod
- fx_realtime.py: stats dem publish/drop/stale/read cho diagnostics
- rebuild bridge binary
This commit is contained in:
2026-08-24 10:40:21 +07:00
parent 708e7de32b
commit 21130858d6
5 changed files with 135 additions and 14 deletions
+55 -1
View File
@@ -16,6 +16,7 @@
#define NOMINMAX
#endif
#include <windows.h>
#include <mmsystem.h>
#endif
#include <algorithm>
@@ -156,6 +157,12 @@ int run_realtime_fx_loop(const std::string& jobPath, const std::string& shmName,
closeShm(v);
return 2;
}
// Windows timer resolution 1ms — bỏ granularity Sleep 15.6ms cho toàn
// process (idle sleep, chain wait, heartbeat). timeEndPeriod đối ứng ở
// cuối. (winmm đã link sẵn — CMakeLists fx_vst_bridge.)
#ifdef _WIN32
timeBeginPeriod(1);
#endif
// Chờ chain ĐẦU TIÊN được worker build xong (gen>=1) trước khi READY.
// setChain() là ASYNC — worker thread load VST3 + preset mất 1-4s; nếu set
// READY ngay, engine mở WS và audio chảy qua chain RỖNG (passthrough), rồi
@@ -197,6 +204,14 @@ int run_realtime_fx_loop(const std::string& jobPath, const std::string& shmName,
uint32_t outMask = ipc->h.outSlots - 1;
uint64_t processed = 0;
uint64_t lastGen = 0; // chain generation đã báo latency
// ---- FXRT_PERF instrumentation (tạm, đo crackle) ----
uint64_t perfIter = 0, perfProc = 0, perfIdle = 0;
double perfProcSum = 0.0, perfProcMax = 0.0, perfProcMin = 1e9;
double perfLoopSum = 0.0, perfLoopMax = 0.0;
uint32_t perfTakeSum = 0;
auto perfT0 = std::chrono::steady_clock::now();
auto perfRunStart = perfT0;
uint64_t perfLastOut = 0;
while (ipc->h.running) {
if (parentPid && !parentAlive(parentPid)) {
std::cerr << "[RealtimeFxLoop] parent gone — exiting" << std::endl;
@@ -250,7 +265,16 @@ int run_realtime_fx_loop(const std::string& jobPath, const std::string& shmName,
<< " slot(s)" << std::endl;
}
uint32_t avail = ipc->h.inWrite - ipc->h.inRead;
if (avail == 0) { sleepMs(1); continue; }
if (avail == 0) {
++perfIdle;
// sleep_until: pacing chính xác theo steady_clock (Sleep(1) bị
// granularity 15.6ms khi không có timeBeginPeriod → jitter loop
// tới 32ms, phá headroom FILL của worklet). timeBeginPeriod(1)
// ở đầu loop + sleep_until giữ loopMax ~2ms.
std::this_thread::sleep_until(std::chrono::steady_clock::now() +
std::chrono::milliseconds(1));
continue;
}
// Batch: tiêu thụ tối đa FXRT_IN_SLOTS block, xử lí 1 call. Input về
// đều → take=1 (latency thấp nhất); dồn burst → take>1 gom lại.
const uint32_t take = std::min<uint32_t>(avail, FXRT_IN_SLOTS);
@@ -262,7 +286,13 @@ int run_realtime_fx_loop(const std::string& jobPath, const std::string& shmName,
ipc->h.inRead++; // consume
off += n;
}
const auto perfP0 = std::chrono::steady_clock::now();
chain.process(L, R, off); // SEH-guarded; chain rỗng = passthrough
const auto perfP1 = std::chrono::steady_clock::now();
const double perfProcMs = std::chrono::duration<double, std::milli>(perfP1 - perfP0).count();
++perfProc; perfProcSum += perfProcMs; perfTakeSum += take;
if (perfProcMs > perfProcMax) perfProcMax = perfProcMs;
if (perfProcMs < perfProcMin) perfProcMin = perfProcMs;
for (uint32_t i = 0, o = 0; i < take; ++i, o += n) {
const uint32_t oslot = ipc->h.outWrite & outMask;
std::memcpy(ipc->outL[oslot], L + o, n * sizeof(float));
@@ -271,10 +301,34 @@ int run_realtime_fx_loop(const std::string& jobPath, const std::string& shmName,
ipc->h.outWrite++; // publish
++processed;
}
const auto perfIterT1 = std::chrono::steady_clock::now();
const double perfLoopMs = std::chrono::duration<double, std::milli>(perfIterT1 - perfT0).count();
perfT0 = perfIterT1; ++perfIter;
perfLoopSum += perfLoopMs; if (perfLoopMs > perfLoopMax) perfLoopMax = perfLoopMs;
if (perfIter % 100 == 0) {
const double runSec = std::chrono::duration<double>(std::chrono::steady_clock::now() - perfRunStart).count();
std::cerr << "[FxRTPerf] iter=" << perfIter
<< " procN=" << perfProc
<< " procAvg=" << (perfProc ? perfProcSum / perfProc : 0.0) << "ms"
<< " procMax=" << perfProcMax << "ms"
<< " procMin=" << (perfProc ? perfProcMin : 0.0) << "ms"
<< " loopAvg=" << (perfIter ? perfLoopSum / perfIter : 0.0) << "ms"
<< " loopMax=" << perfLoopMax << "ms"
<< " idle=" << perfIdle
<< " takeAvg=" << (perfProc ? (double)perfTakeSum / perfProc : 0.0)
<< " procBlocks=" << processed
<< " rate=" << (runSec > 0 ? processed / runSec : 0.0) << "blk/s"
<< " outDepth=" << (ipc->h.outWrite - ipc->h.outRead)
<< std::endl;
perfProc = 0; perfProcSum = 0.0; perfTakeSum = 0; perfIdle = 0;
}
}
ipc->h.state = FXRT_STATE_STARTING; // đã dừng
hb.join();
closeShm(v);
#ifdef _WIN32
timeEndPeriod(1);
#endif
std::cerr << "[RealtimeFxLoop] exit processed=" << processed << std::endl;
return 0;
}