M1: engine editor host API (Reaper-style, editor cua CHINH instance DSP trong graph) + selftest

JuceFxEngine: requestEditor/pumpEditorQueue/isEditorOpen/takeCapturedState, queue thread-safe,
thuc thi tren GUI thread; shutdown() dong editor truoc graph.clear(); prepare() map slot->processor.
juce_fx_bridge link juce_gui_basics; target juce_fx_selftest (khong ship) verify open/close/capture
voi VST3 that — PASS tren Focusrite fast-compressor_x64. Tien de M2 (GUI thread + host window).
This commit is contained in:
2026-09-04 09:59:29 +07:00
parent 6a74631cba
commit 0d22e8b4d3
4 changed files with 276 additions and 0 deletions
+73
View File
@@ -0,0 +1,73 @@
// native_bridge/juce_fx/editor_selftest.cpp
// M1 selftest (KHÔNG ship): verify editor host API trên instance DSP thật.
// juce_fx_selftest.exe "<path.vst3>"
// Main thread = JUCE message thread (initialiseJuce_GUI). Plugin không có
// editor (createEditorIfNeeded null) → [FAIL] editor open — thử plugin khác.
#include "JuceFxEngine.h"
#include <juce_audio_processors/juce_audio_processors.h>
#include <juce_gui_basics/juce_gui_basics.h>
#include <cstdio>
#include <string>
static int failures = 0;
static void check(bool ok, const char* what) {
std::printf("%s %s\n", ok ? "[PASS]" : "[FAIL]", what);
if (!ok) ++failures;
}
int main(int argc, char** argv) {
if (argc < 2) {
std::fprintf(stderr, "usage: juce_fx_selftest <path.vst3>\n");
return 2;
}
// Escape backslash + quote cho JSON path.
std::string path(argv[1]), jpath;
jpath.reserve(path.size() + 8);
for (char c : path) {
if (c == '\\' || c == '"') jpath += '\\';
jpath += c;
}
juce::initialiseJuce_GUI(); // main thread = message thread
int rc = 1;
{
JuceFxEngine engine;
const std::string chain =
"[{\"type\":\"vst3\",\"path\":\"" + jpath + "\",\"bypass\":false}]";
engine.setChain(chain);
engine.prepare(48000, 256);
// Negative: slot ngoài chain — pump xử lý nhưng không có editor nào.
engine.requestEditor(9, JuceFxEngine::EditorAction::Open);
check(!engine.pumpEditorQueue(), "negative: slot 9 ngoài chain -> pump false");
// Open editor 400x300.
engine.requestEditor(0, JuceFxEngine::EditorAction::Open, 400, 300);
bool pumped = engine.pumpEditorQueue();
// Pump message queue ngắn: editor constructor có thể tạo timer/callback.
juce::MessageManager::getInstance()->runDispatchLoopUntil(50);
check(pumped, "open: pumpEditorQueue true");
check(engine.isEditorOpen(0), "open: isEditorOpen(0) true");
// Capture state — phải có dữ liệu nếu plugin đã nạp preset mặc định.
engine.requestEditor(0, JuceFxEngine::EditorAction::Capture);
check(engine.pumpEditorQueue(), "capture: pumpEditorQueue true");
std::string cap = engine.takeCapturedState(0);
check(!cap.empty(), "capture: takeCapturedState non-empty");
std::string cap2 = engine.takeCapturedState(0);
check(cap2.empty(), "capture: lần 2 rỗng (đã xoá)");
// Close — editor đóng, processor + state giữ nguyên.
engine.requestEditor(0, JuceFxEngine::EditorAction::Close);
check(engine.pumpEditorQueue(), "close: pumpEditorQueue true");
check(!engine.isEditorOpen(0), "close: isEditorOpen(0) false");
} // ~engine -> shutdown() (đóng editor trước graph) — trước shutdownJuce_GUI
juce::shutdownJuce_GUI();
rc = (failures == 0) ? 0 : 1;
std::printf(failures == 0 ? "SELFTEST OK\n" : "SELFTEST FAILED\n");
return rc;
}