fix(fx-rt): het crackle master FX - limiter attack smooth + crossfade khi bat/tat chain
- NativeInstrumentEngine: kAttack 0.9->0.1 (het zipper khi sum nhieu track) - app.jsx: crossfade masterFxDryGain/masterFxGain thay hard-rewire graph luc activation/deactivation master chain FX; worklet bao 'started' -> ramp dung luc; PDC tre sau khi dry silent (tranh gap 80ms) - sf-fx-realtime.js: fade-in 512 mau luc FILL xong / resume sau underrun - index.html + addModule: cache-buster v=202608241200 - rebuild app.precompiled.js, copy bundle vao dist/ + src-tauri/resources/ - keo dai fix cac round truoc: FILL 16, FXRT_BURST_MS, REBUFFER, pacing, VST GUI click/focus, solo crackle - them harness + plans/patch scripts
This commit is contained in:
@@ -156,6 +156,23 @@ int run_realtime_fx_loop(const std::string& jobPath, const std::string& shmName,
|
||||
closeShm(v);
|
||||
return 2;
|
||||
}
|
||||
// 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
|
||||
// worker swap chain GIỮA STREAM khi load xong → jump dry→VST + latency đổi
|
||||
// đột ngột → crackle. Chờ swap xong: engine không gửi audio tới khi chain
|
||||
// thật sự sẵn sàng. (Engine wait_ready timeout = 30s > deadline 28s.)
|
||||
const auto chainDeadline = std::chrono::steady_clock::now() +
|
||||
std::chrono::seconds(28);
|
||||
while (chain.chainGen() == 0 &&
|
||||
std::chrono::steady_clock::now() < chainDeadline)
|
||||
sleepMs(10);
|
||||
if (chain.chainGen() == 0)
|
||||
std::cerr << "[RealtimeFxLoop] chain load TIMEOUT 28s — READY anyway"
|
||||
<< std::endl;
|
||||
else
|
||||
std::cerr << "[RealtimeFxLoop] chain ready (gen=" << chain.chainGen()
|
||||
<< ")" << std::endl;
|
||||
ipc->h.state = FXRT_STATE_READY;
|
||||
std::cerr << "[RealtimeFxLoop] ready sr=" << sampleRate << " block=" << block
|
||||
<< " chain=" << (chainJson.empty() ? "(empty)" : "loaded") << std::endl;
|
||||
@@ -168,6 +185,12 @@ int run_realtime_fx_loop(const std::string& jobPath, const std::string& shmName,
|
||||
}
|
||||
});
|
||||
|
||||
// Chain update (set_chain): engine ghi lại job file seq++ — poll mỗi
|
||||
// 500ms; seq đổi → setChain ASYNC (worker load chain mới, swap atomic
|
||||
// dưới mutex — audio thread copy shared_ptr, không dừng) → load/đổi VST
|
||||
// FX + đổi preset GUI không ngắt âm/crackle.
|
||||
uint64_t lastSeq = (uint64_t)jsonNumber(job, "seq", 0);
|
||||
auto lastPoll = std::chrono::steady_clock::now();
|
||||
const uint32_t n = block;
|
||||
float L[FXRT_BLOCK * FXRT_IN_SLOTS], R[FXRT_BLOCK * FXRT_IN_SLOTS];
|
||||
uint32_t inMask = ipc->h.inSlots - 1;
|
||||
@@ -179,6 +202,24 @@ int run_realtime_fx_loop(const std::string& jobPath, const std::string& shmName,
|
||||
std::cerr << "[RealtimeFxLoop] parent gone — exiting" << std::endl;
|
||||
break;
|
||||
}
|
||||
{
|
||||
const auto nowT = std::chrono::steady_clock::now();
|
||||
if (std::chrono::duration_cast<std::chrono::milliseconds>(nowT - lastPoll).count() >= 500) {
|
||||
lastPoll = nowT;
|
||||
const std::string j2 = readFile(jobPath);
|
||||
if (!j2.empty()) {
|
||||
const uint64_t s2 = (uint64_t)jsonNumber(j2, "seq", 0);
|
||||
if (s2 != lastSeq) {
|
||||
const std::string c2 = extractChainJson(j2);
|
||||
if (!c2.empty()) {
|
||||
lastSeq = s2;
|
||||
std::cerr << "[RealtimeFxLoop] chain update seq=" << s2 << std::endl;
|
||||
chain.setChain(c2, sampleRate, (int32_t)batchN);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// SET_PARAM ring (engine -> bridge, Phase 2.8): drain mỗi iteration —
|
||||
// áp dụng vào chain TRƯỚC block kế tiếp. Ring đầy → drop lệnh cũ.
|
||||
while (ipc->h.ctrlRead < ipc->h.ctrlWrite) {
|
||||
|
||||
Reference in New Issue
Block a user