M17: realtime FX mượt — MMCSS Pro Audio + HIGH_PRIORITY_CLASS (JuceFxLoop), GUI tick cadence động 30/200ms (guiBusy), worklet CAP 32->48, track FX gửi input NGAY như master

This commit is contained in:
2026-09-06 20:11:26 +07:00
parent ef521d9981
commit f56d7eb9cf
11 changed files with 63 additions and 38 deletions
+2 -2
View File
@@ -39,8 +39,8 @@ target_compile_definitions(juce_fx_bridge PRIVATE JUCE_PLUGINHOST_VST3=1)
# M2 GUI thread: runDispatchLoopUntil cần modal loops được phép
target_compile_definitions(juce_fx_bridge PRIVATE JUCE_MODAL_LOOPS_PERMITTED=1)
if(WIN32)
# timeBeginPeriod (winmm) — như fx_vst_bridge
target_link_libraries(juce_fx_bridge PRIVATE winmm)
# timeBeginPeriod (winmm) + MMCSS AvSetMmThreadCharacteristics (avrt)
target_link_libraries(juce_fx_bridge PRIVATE winmm avrt)
endif()
# ── M1 selftest (editor API — KHÔNG ship): verify editor host trên instance
+13
View File
@@ -378,6 +378,19 @@ bool JuceFxEngine::isEditorOpen(uint32_t slot) const {
return impl_->editors[slot].open;
}
bool JuceFxEngine::guiBusy() const {
{
std::lock_guard<std::mutex> lk(impl_->queueMutex);
if (!impl_->editorQueue.empty()) return true;
}
{
std::lock_guard<std::mutex> lk(impl_->editorMutex);
for (const auto& se : impl_->editors)
if (se.open) return true;
}
return !impl_->retryOpens.empty(); // GUI-thread-only — chỉ GUI thread gọi
}
std::string JuceFxEngine::takeCapturedState(uint32_t slot) {
std::lock_guard<std::mutex> lk(impl_->editorMutex);
if (slot >= impl_->editors.size()) return {};
+4
View File
@@ -78,6 +78,10 @@ public:
// Thread-safe đọc: editor slot đang hiển thị (Show/Hide) không.
bool isEditorVisible(uint32_t slot) const;
// GUI host: có việc editor cần tick nhanh không (queue lệnh chờ / editor
// đang mở / retry Open chờ). Gọi từ GUI thread (retryOpens GUI-thread-only).
bool guiBusy() const;
void shutdown();
private:
+8 -3
View File
@@ -568,13 +568,14 @@ void JuceFxGuiHost::guiMain() {
// Thread này trở thành JUCE message thread.
std::cerr << "[JuceFxGuiHost] guiMain start" << std::endl;
juce::initialiseJuce_GUI();
std::cerr << "[JuceFxGuiHost] initialiseJuce_GUI ok" << std::endl;
std::cerr << "[JuceFxGuiHost] initialiseJuce_GUI ok" << std::endl;
// Cadence inline (30ms busy / 200ms idle) — KHÔNG dùng juce::Timer: trên GUI thread riêng,
// timer callback không bao giờ fire dù runDispatchLoopUntil pump liên tục
// (test cho thấy 0 tick). tick() ở đây chạy trên chính message thread nên
// pump + tạo/xoá Component an toàn.
{
using clock = std::chrono::steady_clock;
using clock = std::chrono::steady_clock;
const auto fastMs = std::chrono::milliseconds(30);
const auto slowMs = std::chrono::milliseconds(200);
auto lastTick = clock::now();
while (!quit_.load()) {
@@ -596,7 +597,11 @@ void JuceFxGuiHost::guiMain() {
impl_->guiCv.notify_all();
}
juce::MessageManager::getInstance()->runDispatchLoopUntil(10);
const auto now = clock::now();
const auto now = clock::now();
// Cadence động: có việc editor (queue/open/retry) → 30ms giữ
// anchor/reconcile phản hồi nhanh; idle (không editor) → 200ms —
// giảm wakeup + churn GUI thread BELOW_NORMAL khi chỉ chạy audio.
const auto curMs = engine_.guiBusy() ? fastMs : slowMs;
if (now - lastTick >= curMs) {
tick();
lastTick = now;
+14
View File
@@ -16,6 +16,7 @@
#endif
#include <windows.h>
#include <mmsystem.h>
#include <avrt.h>
#endif
#include <algorithm>
@@ -229,6 +230,18 @@ int run_juce_fx_loop(const std::string& jobPath, const std::string& shmName,
#ifdef _WIN32
timeBeginPeriod(1);
SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_HIGHEST);
// MMCSS "Pro Audio": Windows đảm bảo quantum + ưu tiên cho thread này khi
// CPU cạnh tranh — chống spike deschedule (loopMax 430ms / 5756ms ở log).
// AvSetMmThreadCharacteristicsW (Avrt.lib). Process HIGH_PRIORITY_CLASS —
// không cần admin (REALTIME_PRIORITY_CLASS cần SeIncreaseBasePriorityPrivilege).
DWORD mmTaskIndex = 0;
HANDLE hMm = AvSetMmThreadCharacteristicsW(L"Pro Audio", &mmTaskIndex);
if (hMm) {
SetPriorityClass(GetCurrentProcess(), HIGH_PRIORITY_CLASS);
std::cerr << "[JuceFxLoop] MMCSS Pro Audio ok" << std::endl;
} else {
std::cerr << "[JuceFxLoop] MMCSS unavailable err=" << GetLastError() << std::endl;
}
#endif
// Engine: prepare graph (chain theo fx_chain). sr/block từ job; header là
@@ -501,6 +514,7 @@ int run_juce_fx_loop(const std::string& jobPath, const std::string& shmName,
guiHost.stop(); // M2: đóng editor (GUI thread) + dừng GUI thread + join
fxshm::closeShm(v);
#ifdef _WIN32
if (hMm) AvRevertMmThreadCharacteristics(hMm);
timeEndPeriod(1);
#endif
std::cerr << "[JuceFxLoop] exit processed=" << processed << std::endl;