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:
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user