fix(engine): defer set_param ra worker thread, VST API khong goi tren audio thread

This commit is contained in:
2026-08-24 12:03:05 +07:00
parent b907db3251
commit 4240f15e30
6 changed files with 58 additions and 33 deletions
+1 -1
View File
@@ -51,7 +51,7 @@
<script src="/static/js/services/promptTemplateManager.js?v=202607281039"></script>
<script src="/static/js/services/undoRedoEngine.js?v=202607290941"></script>
<script src="/static/js/services/chordTheory.js?v=202608211300"></script>
<script src="/static/js/app.precompiled.js?v=202608241041" defer></script>
<script src="/static/js/app.precompiled.js?v=202608241142" defer></script>
<link rel="stylesheet" href="/static/css/styles.css?v=202607271016">
<style>
:root {
+57 -32
View File
@@ -1228,6 +1228,7 @@ struct RealtimeFxChain::Impl {
struct PendingParam { int slot; std::string key; double value; };
std::mutex pmutex_; // guards pending_
std::vector<PendingParam> pending_;
std::atomic<int> paramQueued_{0}; // wake worker: param chờ apply
std::mutex mutex_; // guards chain_ / retired_
std::shared_ptr<Chain> chain_;
@@ -1304,20 +1305,19 @@ void RealtimeFxChain::process(float* inL, float* inR, uint32_t n) {
}
void RealtimeFxChain::setParam(int slot, const std::string& key, double value) {
// SET_PARAM (SHM control ring, Phase 2.8): áp dụng live vào builtin slot.
// VST3 param automation qua VST3 API — ponytail: them khi co nhu cau.
// SET_PARAM (SHM control ring, Phase 2.8): KHÔNG gọi VST API trên audio
// thread (chain.setParam chạy từ RealtimeFxLoop drain ctrl ring) —
// controller->setParamNormalized là COM call cross-thread với GUI pump,
// block ms→chục ms → out ring cạn → worklet underrun → crackle. Mọi
// param defer vào pending_, worker thread áp (builtin nhanh, VST3 an
// toàn) — audio thread chỉ push queue + notify.
if (!impl_) return;
std::shared_ptr<Impl::Chain> c;
{ std::lock_guard<std::mutex> lk(impl_->mutex_); c = impl_->chain_; }
if (c && slot >= 0 && (size_t)slot < c->entries.size()) {
auto& e = c->entries[(size_t)slot];
if (e.bfx) e.bfx->setParam(key, value);
else if (e.fx) e.fx->setParam(key, value);
return;
{
std::lock_guard<std::mutex> lk(impl_->pmutex_);
impl_->pending_.push_back({slot, key, value});
}
// Chain chưa swap (SET_PARAM tới trước setChain) — stash, worker áp sau swap.
std::lock_guard<std::mutex> lk(impl_->pmutex_);
impl_->pending_.push_back({slot, key, value});
impl_->paramQueued_.fetch_add(1, std::memory_order_release);
impl_->qcv_.notify_all();
}
uint64_t RealtimeFxChain::chainGen() {
@@ -1405,29 +1405,54 @@ void RealtimeFxChain::Impl::workerLoop() {
std::string job;
{
std::unique_lock<std::mutex> lk(qmutex_);
qcv_.wait(lk, [&]() { return quit_ || !q_.empty(); });
qcv_.wait(lk, [&]() {
return quit_ || !q_.empty() || paramQueued_.load(std::memory_order_acquire) > 0;
});
if (quit_) break;
job = std::move(q_.front());
q_.erase(q_.begin());
}
std::shared_ptr<Chain> next = buildChain(job); // slow: plugin load
{
std::lock_guard<std::mutex> lk(mutex_);
retired_ = std::move(chain_); // old chain destroyed on THIS thread
chain_ = std::move(next);
}
{
// Áp param chờ (đăng ký trước khi chain tồn tại).
std::lock_guard<std::mutex> lk(pmutex_);
for (const auto& p : pending_) {
if (p.slot < 0 || (size_t)p.slot >= chain_->entries.size()) continue;
auto& e = chain_->entries[(size_t)p.slot];
if (e.bfx) e.bfx->setParam(p.key, p.value);
else if (e.fx) e.fx->setParam(p.key, p.value);
if (!q_.empty()) {
job = std::move(q_.front());
q_.erase(q_.begin());
}
}
if (!job.empty()) {
std::shared_ptr<Chain> next = buildChain(job); // slow: plugin load
{
std::lock_guard<std::mutex> lk(mutex_);
retired_ = std::move(chain_); // old chain destroyed on THIS thread
chain_ = std::move(next);
}
gen_.fetch_add(1, std::memory_order_release); // loop báo lại latency
}
{
// Áp param chờ trên WORKER thread (audio thread không bao giờ gọi
// VST API): queue trước swap áp vào chain mới; queue trong lúc
// chain tồn tại áp ngay, không cần đợi setChain.
std::vector<Impl::PendingParam> toApply;
{
std::lock_guard<std::mutex> lk(pmutex_);
if (!pending_.empty()) {
toApply.swap(pending_);
paramQueued_.store(0, std::memory_order_release);
}
}
if (!toApply.empty()) {
std::shared_ptr<Chain> c;
{ std::lock_guard<std::mutex> lk(mutex_); c = chain_; }
if (c) {
for (const auto& p : toApply) {
if (p.slot < 0 || (size_t)p.slot >= c->entries.size()) continue;
auto& e = c->entries[(size_t)p.slot];
if (e.bfx) e.bfx->setParam(p.key, p.value);
else if (e.fx) e.fx->setParam(p.key, p.value);
}
} else {
// Chain chưa tồn tại — stash lại, áp sau swap.
std::lock_guard<std::mutex> lk(pmutex_);
pending_.insert(pending_.begin(), toApply.begin(), toApply.end());
paramQueued_.fetch_add((int)toApply.size(), std::memory_order_release);
}
}
pending_.clear();
}
gen_.fetch_add(1, std::memory_order_release); // loop báo lại latency
}
#ifdef _WIN32
CoUninitialize();
Binary file not shown.
Binary file not shown.