Fix AI proxy origin in Tauri, SF2 program_select, VSTi GUI reopen + sample rate bridge

- aiGateway.js: use window.API_BASE_URL (engine port) instead of window.location.origin (tauri://localhost asset protocol returned index.html -> JSON parse error); check content-type before parsing response
- NativeInstrumentEngine.cpp: fluid_synth_program_select for SF2 instrument + program change
- Vst3Instrument.cpp: activate all audio/event buses, channel=0 forced, GUI resize/reopen, kEvent using fix
- main.cpp: VST GUI reopen via WM_DESTROY, OleInitialize, steady_clock loop
- lib.rs: BridgeSampleRate state, restart_bridge_with_sample_rate command, borrow fix
- app.jsx/nativeBridgeService.js: isMidiTrack widening, bridge masterbus connect, held MIDI notes, sample-rate restart, openNativeGUI channel
This commit is contained in:
2026-08-13 11:26:39 +07:00
parent 49549aba2e
commit d168328004
8 changed files with 218 additions and 59 deletions
+60 -23
View File
@@ -14,9 +14,9 @@
#include <unistd.h>
#include <cstdlib>
#include <thread>
#include <chrono>
#endif
#include <chrono>
#include <algorithm>
#include <condition_variable>
#include <cstring>
@@ -48,6 +48,19 @@ static void sleep_ms(uint32_t ms) {
#endif
}
#ifdef _WIN32
static LRESULT CALLBACK VstWindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
if (uMsg == WM_DESTROY) {
void* ptr = (void*)GetWindowLongPtrA(hwnd, GWLP_USERDATA);
if (ptr) {
INativeInstrument* inst = static_cast<INativeInstrument*>(ptr);
inst->closeGUI();
}
}
return DefWindowProcA(hwnd, uMsg, wParam, lParam);
}
#endif
// B9: native Win32 window for the VST editor (replaces the WebView2 surface —
// the HTML window was drawn ON TOP of the plugin GUI). MUST be created on the
// ChannelWorker thread so the worker's idle message pump services its messages.
@@ -57,7 +70,7 @@ static void* create_native_vst_window(const char* title) {
static bool registered = false;
if (!registered) {
WNDCLASSA wc = {};
wc.lpfnWndProc = DefWindowProcA;
wc.lpfnWndProc = VstWindowProc;
wc.hInstance = GetModuleHandleA(nullptr);
wc.lpszClassName = kWndClass;
RegisterClassA(&wc);
@@ -86,7 +99,7 @@ public:
ChannelWorker() {
th_ = std::thread([this] {
#ifdef _WIN32
CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED);
OleInitialize(nullptr);
#endif
std::unique_lock<std::mutex> lk(mu_);
for (;;) {
@@ -112,7 +125,7 @@ public:
lk.lock();
}
#ifdef _WIN32
CoUninitialize();
OleUninitialize();
#endif
});
}
@@ -229,6 +242,10 @@ int main(int argc, char* argv[]) {
instruments.renderAll(shmIPC->masterLeft + from, shmIPC->masterRight + from, to - from);
};
double blockDurationMs = (double)block / sampleRate * 1000.0;
auto startTime = std::chrono::steady_clock::now();
uint64_t blockCount = 0;
// 2. REAL-TIME AUDIO PROCESSING ENGINE LOOP
while (true) {
#ifdef _WIN32
@@ -289,8 +306,6 @@ int main(int argc, char* argv[]) {
playheadSamples = c.arg1;
}
} else if (c.type == 4) { // OPEN_GUI (A7): arg1 = parent HWND (0 → bridge tự tạo native window), arg2 = plugin id
// ponytail: per-plugin channel mapping chua co — gan GUI cho
// instrument dau tien duoc load (smoke test = 1 instrument).
// Chay tren CUNG ChannelWorker da load instrument: thread rieng
// cho openGUI lai tao COM apartment moi, con plugin thi song o
// apartment cu da chet (load thread exit) -> Nexus attached()
@@ -299,16 +314,14 @@ int main(int argc, char* argv[]) {
// len worker (async, VST3 init co the mat giay) truoc khi type=4
// duoc xu ly — khong doi, instruments.get() con rong -> "no
// instrument loaded". Poll toi da 10s cho LOAD hoan tat.
uint32_t guiCh = 16;
for (int tries = 0; tries < 200 && guiCh == 16; ++tries) {
for (uint32_t ch = 0; ch < 16; ++ch) {
if (instruments.get(ch)) { guiCh = ch; break; }
}
if (guiCh == 16) sleep_ms(50);
uint32_t guiCh = c.channel;
if (guiCh >= 16) guiCh = 0;
for (int tries = 0; tries < 200 && !instruments.get(guiCh); ++tries) {
sleep_ms(50);
}
if (guiCh == 16) {
if (!instruments.get(guiCh)) {
std::cerr << "[NativeBridge] GUI attach FAILED hwnd=" << c.arg1
<< " plugin=" << c.arg2 << " (no instrument loaded)" << std::endl;
<< " plugin=" << c.arg2 << " ch=" << guiCh << " (no instrument loaded)" << std::endl;
} else {
if (!workers[guiCh]) workers[guiCh] = std::make_unique<ChannelWorker>();
workers[guiCh]->post([&instruments, &guiWindows, guiCh, arg1 = c.arg1, arg2 = std::string(c.arg2)]() {
@@ -317,14 +330,27 @@ int main(int argc, char* argv[]) {
void* hwnd = (void*)(uintptr_t)arg1;
#ifdef _WIN32
if (arg1 == 0) {
// B9: bridge tự tạo native window — editor VST3 đính
// vào đây; message pump bởi ChannelWorker idle loop.
hwnd = create_native_vst_window(arg2.c_str());
if (!hwnd) {
std::cerr << "[NativeBridge] GUI create window FAILED plugin=" << arg2 << std::endl;
return;
HWND existingHwnd = nullptr;
auto it = guiWindows.find(guiCh);
if (it != guiWindows.end()) {
existingHwnd = (HWND)it->second;
}
if (existingHwnd && IsWindow(existingHwnd)) {
hwnd = existingHwnd;
SetWindowTextA((HWND)hwnd, arg2.c_str());
ShowWindow((HWND)hwnd, SW_SHOW);
SetForegroundWindow((HWND)hwnd);
} else {
hwnd = create_native_vst_window(arg2.c_str());
if (!hwnd) {
std::cerr << "[NativeBridge] GUI create window FAILED plugin=" << arg2 << std::endl;
return;
}
guiWindows[guiCh] = hwnd; // keep window alive
if (auto* inst = instruments.get(guiCh)) {
SetWindowLongPtrA((HWND)hwnd, GWLP_USERDATA, (LONG_PTR)inst);
}
}
guiWindows[guiCh] = hwnd; // keep window alive
}
#else
(void)guiWindows;
@@ -376,8 +402,19 @@ int main(int argc, char* argv[]) {
break;
}
// E. Sleep briefly for the next frame tick (block@44100 ≈ 5.8ms)
sleep_ms(1);
// E. Synchronize with real-time audio playback
blockCount++;
auto targetTime = startTime + std::chrono::microseconds(static_cast<int64_t>(blockCount * blockDurationMs * 1000.0));
auto now = std::chrono::steady_clock::now();
if (now < targetTime) {
auto diff = std::chrono::duration_cast<std::chrono::microseconds>(targetTime - now).count();
if (diff > 1000) {
sleep_ms(diff / 1000);
}
while (std::chrono::steady_clock::now() < targetTime) {
std::this_thread::yield();
}
}
}
#ifdef _WIN32