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