fix(fxrt): toggle VST bypass khong reload plugin - cache instance trong bridge, chain giu slot bypassed, bypass state trong chainKey, poll 100ms + fade-in 512 samples che gap latency

This commit is contained in:
2026-08-24 16:56:10 +07:00
parent 3a3f46b7d3
commit 59e4951912
6 changed files with 76 additions and 51 deletions
+15 -2
View File
@@ -198,7 +198,7 @@ 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
// 100ms; 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);
@@ -209,6 +209,8 @@ 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
uint32_t fadeLeft = 0; // output fade-in còn lại sau chain swap (che gap latency jump)
const uint32_t fadeTotal = 512; // ~11.6ms @44.1k
// ---- FXRT_PERF instrumentation (tạm, đo crackle) ----
uint64_t perfIter = 0, perfProc = 0, perfIdle = 0, perfOutFull = 0;
double perfProcSum = 0.0, perfProcMax = 0.0, perfProcMin = 1e9;
@@ -224,7 +226,7 @@ int run_realtime_fx_loop(const std::string& jobPath, const std::string& shmName,
}
{
const auto nowT = std::chrono::steady_clock::now();
if (std::chrono::duration_cast<std::chrono::milliseconds>(nowT - lastPoll).count() >= 500) {
if (std::chrono::duration_cast<std::chrono::milliseconds>(nowT - lastPoll).count() >= 100) {
lastPoll = nowT;
const std::string j2 = readFile(jobPath);
if (!j2.empty()) {
@@ -266,6 +268,7 @@ int run_realtime_fx_loop(const std::string& jobPath, const std::string& shmName,
ipc->h.latWrite++;
}
lastGen = gen;
fadeLeft = fadeTotal;
std::cerr << "[RealtimeFxLoop] latency reported: " << lats.size()
<< " slot(s)" << std::endl;
}
@@ -314,6 +317,16 @@ int run_realtime_fx_loop(const std::string& jobPath, const std::string& shmName,
if (perfProcMs > perfProcMax) perfProcMax = perfProcMs;
if (perfProcMs < perfProcMin) perfProcMin = perfProcMs;
for (uint32_t i = 0, o = 0; i < take; ++i, o += n) {
if (fadeLeft > 0) {
const uint32_t f = std::min<uint32_t>(fadeLeft, n);
const float start = (float)(fadeTotal - fadeLeft);
for (uint32_t k = 0; k < f; ++k) {
const float g = (start + (float)k) / (float)fadeTotal;
L[o + k] *= g;
R[o + k] *= g;
}
fadeLeft -= f;
}
const uint32_t oslot = ipc->h.outWrite & outMask;
std::memcpy(ipc->outL[oslot], L + o, n * sizeof(float));
std::memcpy(ipc->outR[oslot], R + o, n * sizeof(float));
+38 -10
View File
@@ -1244,6 +1244,12 @@ struct RealtimeFxChain::Impl {
double sampleRate_ = 44100.0;
int32_t blockSize_ = 256;
// VST3 instance cache: giữ plugin đã load qua các lần buildChain để toggle
// bypass không phải load lại từ disk (tránh delay dry->wet). Key = path + #occ.
std::map<std::string, std::shared_ptr<Vst3Fx>> cache_;
std::map<std::string, std::string> lastPreset_; // preset b64 da ap (tranh ap lai)
std::map<std::string, int> occ_; // so lan xuat hien cua path trong chain
std::shared_ptr<Chain> buildChain(const std::string& json);
void workerLoop();
};
@@ -1332,12 +1338,13 @@ std::vector<uint32_t> RealtimeFxChain::entryLatencies() {
if (!c) return out;
out.reserve(c->entries.size());
for (const auto& e : c->entries)
out.push_back(e.fx ? (uint32_t)e.fx->latencySamples() : 0u);
out.push_back((!e.bypass && e.fx) ? (uint32_t)e.fx->latencySamples() : 0u);
return out;
}
std::shared_ptr<RealtimeFxChain::Impl::Chain> RealtimeFxChain::Impl::buildChain(const std::string& json) {
auto chain = std::make_shared<Chain>();
occ_.clear(); // key theo vị trí slot trong CHAIN NÀY — reset mỗi build
json_parse_result_s pres = {};
json_value_s* root = json_parse_ex(json.data(), json.size(), json_parse_flags_default,
nullptr, nullptr, &pres);
@@ -1373,21 +1380,42 @@ std::shared_ptr<RealtimeFxChain::Impl::Chain> RealtimeFxChain::Impl::buildChain(
if (path.empty()) continue;
Entry ent;
ent.bypass = memberBool(o, "bypass", false);
const std::string preset = memberString(o, "preset_b64", "");
// Key ổn định theo vị trí slot: đếm MỌI lần path xuất hiện (kể cả
// bypassed) — toggle bypass không đổi key → không reload nhầm instance.
const std::string ckey = path + "#" + std::to_string(occ_[path]++);
if (!ent.bypass) {
auto fx = std::make_shared<Vst3Fx>();
if (fx->load(path, sampleRate_, blockSize_)) {
const std::string preset = memberString(o, "preset_b64", "");
if (!preset.empty()) {
if (fx->applyPreset(preset))
std::cerr << "[RealtimeFx] preset applied: " << path << std::endl;
auto it = cache_.find(ckey);
if (it != cache_.end()) {
ent.fx = it->second; // đã load: reuse ngay, khong reload
const std::string last = lastPreset_[ckey];
if (!preset.empty() && preset != last) {
if (ent.fx->applyPreset(preset))
std::cerr << "[RealtimeFx] preset applied (cached): " << path << std::endl;
else
std::cerr << "[RealtimeFx] preset apply FAILED (default kept): " << path << std::endl;
lastPreset_[ckey] = preset;
}
ent.fx = std::move(fx);
if (preset.empty()) lastPreset_[ckey] = "";
}
else {
std::cerr << "[RealtimeFx] load failed — slot bypassed: " << path << std::endl;
ent.bypass = true;
auto fx = std::make_shared<Vst3Fx>();
if (fx->load(path, sampleRate_, blockSize_)) {
if (!preset.empty()) {
if (fx->applyPreset(preset))
std::cerr << "[RealtimeFx] preset applied: " << path << std::endl;
else
std::cerr << "[RealtimeFx] preset apply FAILED (default kept): " << path << std::endl;
}
ent.fx = fx;
cache_[ckey] = fx;
lastPreset_[ckey] = preset;
}
else {
std::cerr << "[RealtimeFx] load failed — slot bypassed: " << path << std::endl;
ent.bypass = true;
occ_[path]--; // khong dem lan that bai
}
}
}
chain->entries.push_back(std::move(ent));