fix(engine): defer set_param ra worker thread, VST API khong goi tren audio thread
This commit is contained in:
@@ -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 {
|
||||
|
||||
@@ -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.
Binary file not shown.
Binary file not shown.
Reference in New Issue
Block a user