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