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
+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));