diff --git a/big_tasks.md b/big_tasks.md new file mode 100644 index 0000000..1296b37 --- /dev/null +++ b/big_tasks.md @@ -0,0 +1,86 @@ +# Sonic Forge Studio — Big Tasks (ROADMAP G0–G4) + +Trạng thái: 2026-08-15. Bug gốc: PLAY + mở GUI VST → câm, GUI không load. +User duyệt roadmap G0–G4 ("Hãy bắt đầu"). Đang ở G1. + +## Ngữ cảnh kỹ thuật + +- Logs: `C:/Users/locpham/AppData/Roaming/SonicForgeDAW/logs/` — `bridge.log` (stdout bridge), `spawn.log` (watchdog restart), `engine.log`. +- VST test: `C:\Program Files\Common Files\VST3\Nexus.vst3`. +- SHM: `SonicForge_DAW_IPC_`; offsets: controlQueue @2836 (8×1040), controlQueueCount @11156, attach_in_flight @11160, writeIndex @4, size 11164. Control types: 2=LOAD, 3=TRANSPORT (0=STOP/1=PLAY), 4=OPEN_GUI. +- Branch: `standalone-shm-bridge`. App: sonicforge-daw, daw_engine, daw_vst_bridge (3 process). +- Probe: `native_bridge/build/Release/gui_probe.exe` — standalone VST3 GUI attach probe. + Variants: main_own | worker_own_nopump | worker_own_pump | worker_foreign_nopump | worker_foreign_pump | bridge_like | same_thread | two_instances | two_instances_close | close_reopen | bridge_two | shared_worker | ui_thread | two_workers_close. +- File CRLF toàn bộ native_bridge + CMakeLists.txt → patch multi-line qua python `io.open(newline='')`. + +## G0 — Hoàn thành (đã commit, branch standalone-shm-bridge) + +- G0.1 (1839480): defer OPEN_GUI khi PLAY. +- G0.2 (2f78618): openVstGuiRetry ≤2 lần + `__bridgePlaying` guard (app.precompiled.js). +- G0.3 (5313d85): watchdog giữ 15s khi attach-in-flight (SHM flag offset 11160) + RAII guard. +- G0.4 (eac7f2f): save/restore preset quanh reload — captureState/restoreState trong Vst3Instrument (getState/setState qua MemoryStream), gọi trong post_close_gui; INativeInstrument.h virtual no-op; CMakeLists link memorystream.cpp vào daw_vst_bridge + sdk_common. VERIFIED PASS (probe_g04_preset.py). + +## G1 — 1 UiThread chung (ĐANG LÀM) + +Mục tiêu: 1 thread chung host mọi VST3 instance thay per-channel workers → hết "2 thread 1 DLL" hang/crash (bridge_two HANG/EXIT3221225477 vs shared_worker OK). + +### G1.1 — Probe variant ui_thread (alias shared_worker) [HOÀN THÀNH] +- [x] Patch gui_probe.cpp: usage string, branch `variant == "shared_worker" || variant == "ui_thread"`, isDeferred list. +- [x] Fix build: CMakeLists gui_probe target thêm `memorystream.cpp` + `sdk_common` (LNK2019). +- [x] Fix crash 0xC0000409: ProbeWorker thiếu destructor join → std::terminate. Thêm destructor + join + closeGUI trước teardown + gỡ IME. VERIFIED `shared_worker 15`: RESULT done=1 aRes=1 bRes=1, EXIT=0, 6.73s. Commit 3587a34. + +### G1.2 — Chuyển tạo window + attach job từ ChannelWorker → UiThread mới (1 thread chung) [HOÀN THÀNH — 9e6e72a] +- [x] Thay per-channel workers (16) bằng 1 UiThread: COM STA + message pump + job queue. +- [x] Audio loop bỏ PeekMessageW. +- [x] Option B (đóng editor channel khác trước attach) chuyển PostMessage(WM_CLOSE)+5s → close_editor_now đồng bộ (1 thread: job close sau attach job cùng queue → không bao giờ chạy). +- [x] VERIFIED: build EXIT=0; live test PASS — LOAD 7, PLAY writeIndex advancing, defer OK, attach OK, Option B close đồng bộ OK, WM_CLOSE→reload OK, 0 crash/restart. + +### G1.3 — Close/resize/IME sang UiThread + bỏ pump gate cũ [HOÀN THÀNH — b92c85e] +### G1.4 — Bỏ mute-when-editor-open [HOÀN THÀNH — 7629783] +- [x] channelQuiet chi con reloading (bo editorOpenFn check); xoa setEditorOpenPredicate/g_editorOpen/g_editorPathCount/clear_editor_open. +- [x] VERIFIED: build PASS; live test PASS — PLAY+editor open writeIndex advancing (het cam), LOAD-while-editor OK, close/reopen x4 OK, regression G1.2 PASS, 0 crash/restart. +### G1.5 — Regression driver stress [HOÀN THÀNH] +- [x] stress driver native_bridge/debug/test_g15_stress.py: 5 + 10 iterations LOAD/OPEN_GUI/PLAY/STOP/WM_CLOSE Nexus+EZkeys. +- [x] VERIFIED: PASS ca hai run — PLAY+editor open writeIndex advancing moi iter (delta ~290-330), WM_CLOSE state restored OK, bridge alive 30512, 0 EXCEPTION/FAILED/crash. + +## G2 — Persistence restart (2-3 tuần) +Mục tiêu: restart không mất instrument — "stop/play nghe lại" ngay sau respawn. +- G2.1 save/restore đầy đủ: toàn bộ trạng thái instrument (path, preset, params, transport position) ra đĩa. +- G2.2 dump state trước kill: trước khi DAW/bridge bị kill, dump trạng thái hiện tại. +- G2.3 auto-reload sau respawn: sau khi bridge respawn, tự load lại từ dump. +- G2.4 bỏ !isPlayingRef chặn restore: gỡ guard chặn restore khi đang PLAY. + +## G3 — Health / Watchdog thông minh (1-2 tuần) +Mục tiêu: Watchdog thông minh, cô lập lỗi. +- G3.1 heartbeat riêng: heartbeat bridge/engine tách khỏi logic audio. +- G3.2 phân loại stall: phân biệt hang/crash/stall để xử lý đúng mức. +- G3.3 SEH quanh process từng channel: 1 plugin crash → chỉ mute channel đó, không sập cả bridge. + +## G4 — Sandbox plugin (tuỳ chọn, 2-3 tháng) +Mục tiêu: plugin process con — VST3 không có chuẩn OOP như AUv3 → wrapper tự làm. +Lưu ý: G1+G3 đã lo 90% kịch bản. Khuyến nghị làm sau. + +## Kiến trúc hiện tại (main.cpp 1254 dòng, sau G1.4) — tham chiếu G1.2–G1.5 + +- 1 UiThread chung (class ChannelWorker, main.cpp ~236): COM STA + job queue + message pump (cap 128 msg/iteration). MỌI window (native VST + editor children) thuộc thread này; CBT hook IME per-thread + priority ABOVE_NORMAL. loadPlugin/createView/close_editor_now/LOAD jobs đều chạy ở đây — thread sống cả đời bridge, COM STA apartment không chết → Nexus attached() không treo. (G1.1/G1.2) +- Audio/main loop (main.cpp ~972-1235): drain control queue → pending-GUI sweep → MIDI snapshot → dispatch sample-accurate (A11: sort theo sampleOffset, renderSegment từng sub-block) → renderAll → bridgeWriteIndex++ → sync real-time. KHÔNG còn PeekMessageW (G1.3); try/catch net bọc cả loop (plugin exception không escape). +- Control (SHM): controlQueue @2836 (8×1040), count @11156, attachInFlight @11160, writeIndex @4. Types: 1=PANIC, 2=LOAD, 3=TRANSPORT (0=STOP flush notes + CC64, 1=PLAY, 2=SET_POSITION), 4=OPEN_GUI. +- OPEN_GUI (handleOpenGui): defer khi chưa load hoặc đang PLAY → g_pendingGui, sweep khi transportStopped && loaded (G0.1); attach job trên UiThread (g_loadMutex serialize createView); attach-in-flight báo watchdog qua SHM attachInFlight RAII (G0.3); Option B đóng editor channel khác ĐỒNG BỘ qua close_editor_now (G1.2 — bỏ PostMessage(WM_CLOSE)+5s timeout). +- LOAD (assign trên UiThread): setReloading(ch + mọi same-path channel) TRƯỚC khi vào DLL; hide + detach editor cũ (closeGUI + destroy_window_on_owner); restore unmute sau job (g_closeInFlight + g_attachPaths ngăn unmute nhầm). +- Guards còn lại: g_loadMutex (serialize mọi plugin-DLL entry: createInstance + createView), reloadingCh_[16] (engine — channelQuiet CHỈ mute khi reloading; G1.4 bỏ mute-when-editor-open), g_attachPaths + g_attachMutex (attach-in-flight registry), g_closeInFlight[16], g_guiMutex + g_guiWindows + g_hwndToCh (window registry: giữ sống + reuse + WM_DESTROY cleanup), g_pendingGui + g_pendingGuiMutex, g_cbtHook (strip IMC trên JUCE windows — chống IME recursion 0xC00000FD), destroy_window_on_owner (destroy editor child trên UiThread, không cross-thread — hết 0xc000041d). +- VstWindowProc: WM_CLOSE → post_close_gui + hide (KHÔNG DefWindowProc destroy khi view attached — tránh cross-thread destroy); WM_DESTROY → registry cleanup + post_close_gui; WM_SIZE → MoveWindow child + post_resize_view; WM_ERASEBKGND/WM_PAINT no-op (chống trắng loá). +- G0.4: save/restore preset quanh close/reload — captureState/restoreState qua MemoryStream (post_close_gui/LOAD); bridge + gui_probe link memorystream.cpp. +- Watchdog thread riêng (main.cpp ~600): parent chết → TerminateProcess ngay + check birth-time chống PID reuse (V8 bug 2). + +## Learnings + +- read_file lỗi với main.cpp/lib.rs/gui_probe.cpp → dùng `sed -n` qua exec_command. +- MemoryStream::write cần 3 args (buffer, size, int32* numBytesWritten); seek(0, kIBSeekSet, nullptr). memorystream.cpp không thuộc target SDK → add source trực tiếp vào mỗi target dùng Vst3Instrument.cpp (bridge Đà thêm, gui_probe Đà thêm). +- tasklist CSV parse: dùng `subprocess.run(['tasklist'])` full output + `line.split()[1]`. +- Probe wait_log: log cùng job xuất hiện ~đồng thời → dùng chung `since` window, không reset. +- Probe PASS có thể dương tính giả → luôn kiểm tra bridge.log/spawn.log thực. +- Deploy: taskkill 3 process → chờ 2s → copy exe → start → chờ 5s → tasklist lấy PID mới → update APP_PID trong probe. +- `sleep` không tồn tại trong sh → `python -c "import time; time.sleep(N)"`. +- `timeout` Windows khác GNU → dùng python subprocess wrapper. +- CRLF: `s.count(old)` với `\n` thật trong heredoc `<<'EOF'` KHÔNG khớp CRLF; dùng script .py file thay vì `python -c` chuỗi dài (escaping lỗi). +- 0xC0000409 trong ucrtbase = fail-fast/terminate (không phải C++ exception thường bắt được bằng catch(...) nếu là SEH/fail-fast). diff --git a/native_bridge/debug/ARCHITECTURE_ROADMAP.md b/native_bridge/debug/ARCHITECTURE_ROADMAP.md new file mode 100644 index 0000000..ed3c2f8 --- /dev/null +++ b/native_bridge/debug/ARCHITECTURE_ROADMAP.md @@ -0,0 +1,123 @@ +# ROADMAP: Xử lý triệt để như DAW hiện đại — tách UI thread khỏi audio loop + +Ngày: 2026-08-15 · Repo: `C:/Users/locpham/SonicForgeStudio` · Dựa trên bằng chứng +`native_bridge/debug/BUG_REPORT.md` + source hiện tại (main.cpp, Vst3Instrument.cpp, lib.rs). + +## Mục tiêu kiến trúc đích + +DAW hiện đại (Cubase/Reaper/FL/Bitwig) tách: +- **Audio thread**: real-time, chỉ gọi `process()` +- **UI thread**: main UI thread, chạy `view->attached()` + message pump editor +- (tuỳ chọn) **Plugin process riêng**: sandbox crash plugin khỏi engine + +Bridge hiện tại gộp 2 thứ đầu vào 1 thread (`while(true)` vừa `renderAll()` vừa +`PeekMessageW`, main.cpp:1220-1240) + attach job trên ChannelWorker → 2 thread 1 +DLL → Nexus throw/hang. Roadmap này tách UI khỏi audio đúng VST3 contract. + +## Nguyên tắc vận hành (áp dụng cho MỌI task) + +``` +1. Viết test tái hiện TRƯỚC (gui_probe variant / probe_appN / driver kịch bản) +2. Implement tối thiểu (đúng phạm vi task) +3. Chạy bộ test: gui_probe matrix + live driver + regression cũ +4. Ghi nhận bug vào log + BUG_REPORT.md (mỗi bug: kịch bản, log, nguyên nhân) +5. Fix → chạy lại → pass hết mới deploy +6. Deploy exe vào install/ + commit (theo pattern git log hiện có) +``` + +Không task nào merge nếu chưa: probe matrix pass + driver 3 run liên tiếp pass + +screenshot GUI không trắng. + +## GIAI ĐOẠN 0 — Ổn định hiện trạng (P0, ngăn máu chảy) + +Làm ngay, độc lập kiến trúc. Không phải "xử lý triệt để" nhưng là nền để làm việc +dài hạn không bị bug quấy phá. + +| # | Task | File | Done khi | +|---|---|---|---| +| G0.1 | Chặn/defer OPEN_GUI khi transport PLAY (đợi STOP rồi attach; hoặc tạm dừng transport) | main.cpp handleOpenGui + Rust transport state | probe_app4_live: PLAY + OPEN_GUI → không EXCEPTION, attach sau STOP thành công | +| G0.2 | Bỏ spam `openVstGuiRetry` 20×500ms → tối đa 2 lần, có backoff, không retry khi đang PLAY | app/static/js/app.precompiled.js | bridge.log không còn chuỗi EXCEPTION/FAILED lặp; stall không xảy ra | +| G0.3 | Watchdog né giết khi attach-in-flight (flag SHM hoặc stall type riêng) | lib.rs:397 + shm | Stall do attach dài → không kill; stall thật → kill như cũ | +| G0.4 | Save/restore state nhanh (getState trước khi reload, setState sau) | Vst3Instrument.cpp | Mở GUI → đóng → mở lại: preset giữ nguyên | + +## GIAI ĐOẠN 1 — Tách GUI pump khỏi audio loop (P1, lõi triệt để) + +**Vấn đề hiện tại**: main thread vừa render audio vừa pump message editor. +Attach job chạy ChannelWorker (thread khác) → 2 thread 1 DLL. + +**Đích**: main thread chỉ render audio. Thread UI mới (native UI thread) tạo window ++ pump message editor + chạy toàn bộ attach/close. Đúng mô hình Steinberg +`editorhost.cpp`: 1 UI thread, 1 audio thread. + +| # | Task | File | Done khi | +|---|---|---|---| +| G1.1 | Thêm `UiThread` (thread riêng, COM STA, message pump riêng, job queue) | main.cpp mới class | gui_probe variant `ui_thread` tương đương bridge mới: Nexus attach OK không hang | +| G1.2 | Chuyển tạo window + attach job từ ChannelWorker → UiThread; audio loop bỏ `PeekMessageW` | main.cpp | Probe bridge arch mới: 2 instance Nexus attach OK, không HANG (so với bridge_two HANG hiện tại) | +| G1.3 | Chuyển close/resize/IME sang UiThread; bỏ pump gate cũ (g_juceTidsMutex...) nếu không còn cần | main.cpp | close editor đang PLAY không crash; không cần mute workaround | +| G1.4 | Bỏ mute-when-editor-open (g_editorOpen/g_editorPathCount) — test lại | main.cpp | 6 Nexus + GUI ch5 mở khi PLAY: track khác vẫn kêu (mute hiện tại làm track câm) | +| G1.5 | Regression: driver stress 6 Nexus + Ample + EZkeys, mở/đóng GUI khi PLAY | driver + probe | 3 run liên tiếp không crash/stall | + +## GIAI ĐOẠN 2 — Persistence: restart không mất instrument (P2) + +Trả lời câu hỏi "stop/play nghe lại". Hiện bridge kill = mất hết instance (state +trong memory process). + +| # | Task | File | Done khi | +|---|---|---|---| +| G2.1 | `saveState()`/`restoreState()` đầy đủ (IComponentHandler/getState, preset, MIDI routing) | Vst3Instrument.cpp + NativeInstrumentEngine | Save → reload → restore: âm thanh/preset giống hệt | +| G2.2 | Bridge dump state ra file khi shutdown/trước kill (SIGHUP/SHM flag) | main.cpp + lib.rs | Kill bridge → state file có đủ 16 channel | +| G2.3 | Frontend auto-reload sau respawn: đọc state file → bridgeLoad lại từng track | app + NativeBridgeService | Giết bridge giữa PLAY → bridge mới tự nạp lại, PLAY tiếp tục, không câm | +| G2.4 | Bỏ `!isPlayingRef.current` chặn restore khi đang play | app.precompiled.js:816 | Restore chạy được cả khi PLAY | + +## GIAI ĐOẠN 3 — Watchdog thông minh + health (P3) + +| # | Task | File | Done khi | +|---|---|---|---| +| G3.1 | Heartbeat riêng (thread bridge ghi timestamp mỗi 100ms) tách khỏi writeIndex audio | main.cpp + shm.rs + lib.rs | Bridge sống nhưng audio stall → không kill; bridge chết → phát hiện <1s | +| G3.2 | Phân loại stall: audio (render quá lâu) vs GUI job (attach/close) vs chết | lib.rs | Mỗi loại có policy riêng (GUI: đợi, không kill) | +| G3.3 | Bridge tự phục hồi lỗi plugin: catch crash riêng channel (SEH quanh process của từng channel) | main.cpp renderAll | 1 plugin crash → mute channel đó, các channel khác chạy tiếp (giống DAW sandbox-lite) | + +## GIAI ĐOẠN 4 — Sandbox plugin process riêng (P4, tuỳ chọn tham vọng) + +VST3 không có chuẩn out-of-process như AUv3 → cần wrapper tự làm. Đây là bước xa +nhất, chỉ làm khi G1-G3 chưa đủ. + +| # | Task | File | Done khi | +|---|---|---|---| +| G4.1 | Nghiên cứu: host plugin trong process con (VST3 host trong exe thứ 2), audio qua SHM | thiết kế mới | PoC 1 plugin Nexus trong process con, GUI + audio OK | +| G4.2 | Bridge → client process pool (1 process/plugin hoặc /nhóm) | thiết kế mới | Plugin crash → process con chết, bridge + GUI sống | +| G4.3 | IPC audio real-time (SHM ring buffer double-buffer) | thiết kế mới | 6 Nexus song song, latency < 20ms, không xrun | + +**Cân nhắc**: G4 là dự án riêng (ước lượng gấp 3-5× G1+G2). Khuyến nghị: làm G0-G3 +trước; G4 chỉ khi cần cô lập crash plugin thật sự (hiện G3.3 + G1 đã lo được 90% +kịch bản bug). + +## Thứ tự thực hiện đề xuất + +``` +G0.1 → G0.2 → G0.3 → G0.4 (1-2 tuần, an toàn ngay, trả lời "stop/play nghe lại" một phần qua G0.4) + ↓ +G1.1 → G1.2 → G1.3 → G1.4 → G1.5 (3-5 tuần, lõi triệt để — mở GUI khi PLAY thành công) + ↓ +G2.1 → G2.2 → G2.3 → G2.4 (2-3 tuần, restart không câm) + ↓ +G3.1 → G3.2 → G3.3 (1-2 tuần, health đầy đủ) + ↓ +G4.* (tuỳ chọn, 2-3 tháng) +``` + +## Bắt đầu ngay — task đầu tiên + +**G0.1**: sửa `handleOpenGui` (main.cpp:943) — nếu transport PLAY, đưa OPEN_GUI +vào `g_pendingGui` (đã có cơ chế defer cho "chưa load"), sweep xử lý khi transport +STOP. Kèm test: `probe_app4_live.py` biến thể "PLAY rồi OPEN_GUI rồi STOP" — kỳ +vọng không EXCEPTION, GUI attach sau STOP. + +## Files liên quan hiện tại + +- `native_bridge/src/main.cpp` — loop (1220), handleOpenGui (943), ChannelWorker, mute/close +- `native_bridge/src/Vst3Instrument.cpp` — reload (615), reloadForGUI (528), attachView (550) +- `native_bridge/src/gui_probe.cpp` — probe variants (bridge_two 243, two_workers_close 318) +- `native_bridge/src/lib.rs` → thực tế `src-tauri/src/lib.rs` — watchdog (356-430), kill/spawn (50,135) +- `app/static/js/app.precompiled.js` — openVstGuiRetry (470), restore (816) +- `native_bridge/debug/` — BUG_REPORT.md, sweep_bridge_two_8s.json, probe_attach_detail.py, probe_app4_live.py diff --git a/native_bridge/debug/BUG_REPORT.md b/native_bridge/debug/BUG_REPORT.md new file mode 100644 index 0000000..bf9297a --- /dev/null +++ b/native_bridge/debug/BUG_REPORT.md @@ -0,0 +1,122 @@ +# BÁO CÁO BUG: Mở VST GUI khi đang PLAY → dự án câm, GUI không load + +Ngày: 2026-08-15 · Repo: `C:/Users/locpham/SonicForgeStudio` · git status sạch + +## 1. Tóm tắt + +Nguyên nhân gốc: **nhiều thread cùng vào 1 plugin DLL trong lúc `view->attached()`**. +Khi đang PLAY, audio loop `process()` chạy trên các instance Nexus (ch0-4) cùng lúc +worker thread của ch5 chạy `attachView()`. Nexus throw exception trong +`createView/attached` → attach FAILED → frontend retry ~30 lần (0.3s/lần) → +`bridgeWriteIndex` stall 3s → Tauri watchdog kill+respawn bridge → **mất hết +instrument → câm toàn bộ**. GUI không bao giờ load được. + +Không phải "instrument track khác bị reset" (giả thuyết 1 sai ở bước này — reload +reset state chỉ xảy ra khi GUI Đà mở thành công rồi đóng rồi mở lại, không phải lần +attach đầu). + +## 2. Chuỗi bug đầy đủ (xác nhận live, bridge.log) + +``` +... 6x Nexus loaded ch0-5 + EZkeys ch6 ... +[NativeBridge] transport PLAY playhead=0 +[NativeBridge] GUI attach EXCEPTION ch=5 — plugin threw (createView/attached) +[NativeBridge] GUI attach FAILED hwnd=0000000002250F36 plugin=Nexus +[tauri] bridge restarted (attempt 1) ← watchdog: writeIndex stall 3s +[NativeBridge] Starting DAW Host Bridge Engine... ← restart, mất hết instrument +``` + +1. `handleOpenGui` (main.cpp ~943): OPEN_GUI ch5 → silence mọi channel cùng + plugin path → `g_loadMutex` → `try { reloadForGUI(); attachView(hwnd); } catch(...)`. +2. `attachView` (Vst3Instrument.cpp:550): `controller->createView(kEditor)` → + `view->attached(hwnd, kPlatformTypeHWND)` → **Nexus throw** khi đang PLAY + (audio loop process() cùng DLL đang chạy trên instance khác). +3. catch → `ok=false` → log EXCEPTION + FAILED → PostMessage WM_CLOSE (GUI không load). +4. Frontend `openVstGuiRetry` spam lại ~30 lần; mỗi lần chạy lại job (silence + + mutex + reload/attach) → main loop bị nghẽn → `bridgeWriteIndex` stall 3s. +5. `lib.rs`: stall >3s → `kill_bridge` + `spawn_bridge` (restart_attempts==0) → + mất toàn bộ instrument state → **CÂM**. + +## 3. Bằng chứng thực nghiệm + +### 3.1 Probe kiến trúc chính xác (bridge_two: 2 worker STA riêng + main pump) + +`gui_probe.exe bridge_two ` — kết quả 76 VST3 hệ thống (sweep): +`native_bridge/debug/sweep_bridge_two_8s.json`, `probe_attach_detail.py` (bổ sung). + +⚠️ **Hiệu chỉnh sweep cũ**: exit code 0xC0000409 của TẤT CẢ 76 plugin là +**artifact của probe** — `ProbeWorker::th` không join → `~std::thread` trên +joinable thread → `std::terminate` → fail-fast. RESULT line in ra nhưng không flush +kịp. Không phải 76 plugin cùng crash teardown. + +Phân loại đúng dựa trên output per-attach: + +| Nhóm | Số plugin | Bằng chứng | Ý nghĩa | +|---|---|---|---| +| **HANG attach inst2** | 30 | `workerA openGUI inst1=1` rồi `workerB openGUI inst2` in ra, **không có kết quả** — Nexus 25s không về | 2 instance cùng DLL, 2 apartment → attach instance 2 HANG trong `view->attached()` | +| **BOTH-OK** | 20 | `workerB openGUI inst2=1` — Kontakt, BlueARP, Ozone 11 (18) | Attach 2 instance OK; chỉ crash teardown artifact | +| **HANG/crash attach inst1** | 18 | `workerA hwnd=` in ra, không in `openGUI inst1` — RX 11 (18) + ACTP | Chết ngay instance 1 | +| **CRASH EARLY** | 8 | exit 1.2-4.6s, 0xC0000005 (AV): Melodyne, Orb Arpeggios/Melody, smartEQ3/live, smartreverb, DUNE3, ACDD | Crash thật trong attach | + +Không plugin nào HANG >18s (không WATCHDOG-KILL) — main pump cứu được các trường +hợp cần pump; vấn đề là **conflict thread/apartment khi 2 instance cùng DLL**. + +### 3.2 Nexus: hang khi 2 instance — OK khi 1 thread + +- `bridge_two` (2 worker STA, 2 instance Nexus): inst1 attach OK, **inst2 HANG 25s+** (xác nhận bằng run 25s). +- `same_thread` (load + attach cùng 1 thread): **OK** (returned=1). +- `main_own` (load+attach main thread): **OK** (RESULT done=1 attached_ok=1). +- `two_workers_close` (đóng editor inst1 trước khi attach inst2, có closeGUI + attach qua temp thread): Nexus **HANG ngay attach inst1**; Kontakt chạy hết, OK cả 2. + +→ 2 thread/apartment cùng 1 DLL = conflict thật (không riêng Nexus: 30 plugin hang +inst2, 8 crash AV). Kontakt/Ozone/BlueARP chịu được. + +### 3.3 Giả thuyết (a) reload mất state — XÁC NHẬN từ code + +`Vst3Instrument::reload()` (Vst3Instrument.cpp:615): `closeGUI()` + terminate + +`delete state_` + `loadPlugin(path_)` **MỚI** → preset/state mất khi mở lại GUI +(reopen). Kích hoạt bởi `reloadForGUI()` khi `hasAttachedOnce_ && !guiAttached_` +(đã từng mở GUI rồi đóng). Không phải nguyên nhân của bug câm lần này (attach đầu +chưa thành công nên chưa tới reload), nhưng là bug phụ: **mở GUI → đóng → mở lại = +mất preset/state của track**. + +## 4. Kết luận nguyên nhân + +1. **Root cause**: attach GUI chạy worker thread trong khi audio loop `process()` + đang chạy instance cùng DLL (2+ thread 1 DLL). Nexus throw → attach fail. + Probe không audio: biểu hiện hang (inst2); app thật có audio: biểu hiện + exception (ch5). Cùng gốc. +2. **Khuếch đại**: frontend retry spam 30× → stall 3s → watchdog tưởng bridge chết + → restart → mất hết instrument → câm. Đây là lý do dự án chết hoàn toàn, không + chỉ mất GUI. +3. `g_loadMutex` + silence hiện có chưa đủ: không đợi renderAll thoát hẳn instance + cùng DLL trước khi `createView`, và Nexus vẫn throw ngay cả khi đã silence + (silence chỉ ngăn process gọi tiếp, không chặn instance đang nằm trong process()). + +## 5. Đề xuất fix (xếp theo ưu tiên) + +1. **Chặn/defer OPEN_GUI khi transport PLAY** — đơn giản nhất, an toàn nhất: + nếu đang PLAY, đợi transport STOP (hoặc tự tạm dừng) trước khi attach, resume + sau. Loại bỏ hoàn toàn conflict audio-loop × attachView. (`ponytail:` người dùng + muốn mở GUI khi đang chơi thì nâng cấp thành pause-resume tự động, hoặc đợi + `setProcessing(false)` + đợi renderAll thoát.) +2. **Giới hạn retry**: bỏ `openVstGuiRetry` spam; thử tối đa 2-3 lần có backoff. +3. **Watchdog đừng giết khi đang attach GUI**: phân biệt stall do GUI job dài (có + attach-in-flight flag) với stall do bridge chết thật; nâng ngưỡng stall khi có + attach đang chạy. +4. **Fix state reset**: `reload()` nên save/restore state (`getState`/`setState`) + thay vì load instance mới mất preset. (Bug phụ, độc lập.) +5. (Dài hạn) 1 apartment chung cho mọi instance cùng DLL (`shared_worker` pattern + đã có trong probe) — loại conflict nhiều-thread-1-DLL triệt để. + +## 6. Files liên quan + +- `native_bridge/src/main.cpp` — handleOpenGui (943), silence/close-other (1015-1090), attach job + catch (1132-1160) +- `native_bridge/src/Vst3Instrument.cpp` — reload() (615), reloadForGUI() (528), attachView() (550), closeGUI() (640) +- `native_bridge/src/gui_probe.cpp` — probe bridge_two (243), two_workers_close (318) +- `native_bridge/debug/` — sweep_all_gui.py, sweep_bridge_two_8s.json, probe_attach_detail.py, probe_app4_live.py, probe_app4.py +- Bridge log: `%AppData%/SonicForgeDAW/logs/bridge.log` + +Trạng thái live hiện tại: app câm (bridge 21932 sống, không instrument, transport +STOP). Test thêm bằng `probe_app4_live.py` sẽ lại làm hư session — cần xác nhận +trước khi chạy tiếp. diff --git a/native_bridge/debug/build_bridge.ps1 b/native_bridge/debug/build_bridge.ps1 new file mode 100644 index 0000000..536c140 --- /dev/null +++ b/native_bridge/debug/build_bridge.ps1 @@ -0,0 +1,6 @@ +$ErrorActionPreference = 'Stop' +cd 'C:/Users/locpham/SonicForgeStudio/native_bridge/build' +cmake -DCMAKE_CXX_FLAGS_RELEASE='/O2 /Ob2 /DNDEBUG /Zi' -DCMAKE_EXE_LINKER_FLAGS_RELEASE='/DEBUG' . +if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } +cmake --build . --config Release --target daw_vst_bridge +exit $LASTEXITCODE diff --git a/native_bridge/debug/build_zi.ps1 b/native_bridge/debug/build_zi.ps1 new file mode 100644 index 0000000..2207a16 --- /dev/null +++ b/native_bridge/debug/build_zi.ps1 @@ -0,0 +1,6 @@ +$ErrorActionPreference = 'Stop' +cd 'C:/Users/locpham/SonicForgeStudio/native_bridge/build' +cmake -DCMAKE_CXX_FLAGS_RELEASE='/O2 /Ob2 /DNDEBUG /Zi' -DCMAKE_EXE_LINKER_FLAGS_RELEASE='/DEBUG' . +if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } +cmake --build . --config Release --target gui_probe +exit $LASTEXITCODE diff --git a/native_bridge/debug/cdb_zi_shared_clean.txt b/native_bridge/debug/cdb_zi_shared_clean.txt new file mode 100644 index 0000000..548dc64 --- /dev/null +++ b/native_bridge/debug/cdb_zi_shared_clean.txt @@ -0,0 +1,760 @@ +ÿþ +************* Preparing the environment for Debugger Extensions Gallery repositories ************** + ExtensionRepository : Implicit + UseExperimentalFeatureForNugetShare : true + AllowNugetExeUpdate : true + NonInteractiveNuget : true + AllowNugetMSCredentialProviderInstall : true + AllowParallelInitializationOfLocalRepositories : true + + -- Configuring repositories + ----> Repository : LocalInstalled, Enabled: true + ----> Repository : UserExtensions, Enabled: true + +>>>>>>>>>>>>> Preparing the environment for Debugger Extensions Gallery repositories completed, duration 0.000 seconds + +************* Waiting for Debugger Extensions Gallery to Initialize ************** + +>>>>>>>>>>>>> Waiting for Debugger Extensions Gallery to Initialize completed, duration 0.016 seconds + ----> Repository : UserExtensions, Enabled: true, Packages count: 0 + ----> Repository : LocalInstalled, Enabled: true, Packages count: 46 + +Microsoft (R) Windows Debugger Version 10.0.29617.1000 AMD64 +Copyright (c) Microsoft Corporation. All rights reserved. + +CommandLine: .\gui_probe.exe "C:\Program Files\Common Files\VST3\Nexus.vst3" shared_worker 12 + +************* Path validation summary ************** +Response Time (ms) Location +Deferred srv* +Symbol search path is: srv* +Executable search path is: + ++------------------------------------------------------------------------+ +| This target supports Hardware-enforced Stack Protection. A HW based | +| "Shadow Stack" may be available to assist in debugging and analysis. | +| See aka.ms/userhsp for more info. | +| | +| dps @ssp | +| | ++------------------------------------------------------------------------+ + +ModLoad: 00007ff7`ced60000 00007ff7`cf3b6000 gui_probe.exe +ModLoad: 00007fff`ee160000 00007fff`ee3c6000 ntdll.dll +ModLoad: 00007fff`eda20000 00007fff`edae9000 C:\WINDOWS\System32\KERNEL32.DLL +ModLoad: 00007fff`eb7c0000 00007fff`ebbbf000 C:\WINDOWS\System32\KERNELBASE.dll +ModLoad: 00007fff`ee0e0000 00007fff`ee112000 C:\WINDOWS\System32\IMM32.dll +ModLoad: 00007fff`eb4b0000 00007fff`eb4d7000 C:\WINDOWS\System32\win32u.dll +ModLoad: 00007fff`ec1e0000 00007fff`ec3a6000 C:\WINDOWS\System32\USER32.dll +ModLoad: 00007fff`ec640000 00007fff`ec66b000 C:\WINDOWS\System32\GDI32.dll +ModLoad: 00007fff`ebbd0000 00007fff`ebcf9000 C:\WINDOWS\System32\gdi32full.dll +ModLoad: 00007ffe`c7490000 00007ffe`c7574000 C:\Users\locpham\SonicForgeStudio\native_bridge\build\Release\libfluidsynth-3.dll +ModLoad: 00007fff`eb370000 00007fff`eb413000 C:\WINDOWS\System32\msvcp_win.dll +ModLoad: 00007fff`eb4e0000 00007fff`eb62c000 C:\WINDOWS\System32\ucrtbase.dll +ModLoad: 00007fff`ecfa0000 00007fff`ed728000 C:\WINDOWS\System32\SHELL32.dll +ModLoad: 00007fff`ed880000 00007fff`eda19000 C:\WINDOWS\System32\ole32.dll +ModLoad: 00007fff`eca30000 00007fff`ecab2000 C:\WINDOWS\System32\WS2_32.dll +ModLoad: 00007fff`ec6a0000 00007fff`eca25000 C:\WINDOWS\System32\combase.dll +ModLoad: 00007fff`1cd30000 00007fff`1cdc4000 C:\WINDOWS\SYSTEM32\DSOUND.dll +ModLoad: 00007fff`e48d0000 00007fff`e4906000 C:\WINDOWS\SYSTEM32\WINMM.dll +ModLoad: 0000023d`23430000 0000023d`23548000 C:\WINDOWS\System32\RPCRT4.dll +ModLoad: 00007fff`ecb70000 00007fff`ecc88000 C:\WINDOWS\System32\RPCRT4.dll +ModLoad: 00007fff`ebf20000 00007fff`ebfdc000 C:\WINDOWS\System32\advapi32.dll +ModLoad: 00007fff`1cc70000 00007fff`1cd27000 C:\WINDOWS\SYSTEM32\ResampleDmo.DLL +ModLoad: 00007fff`ecc90000 00007fff`ecd39000 C:\WINDOWS\System32\msvcrt.dll +ModLoad: 00007fff`c25a0000 00007fff`c263d000 C:\WINDOWS\SYSTEM32\MSVCP140.dll +ModLoad: 00007fff`c2640000 00007fff`c266c000 C:\WINDOWS\SYSTEM32\VCRUNTIME140.dll +ModLoad: 00007fff`ecec0000 00007fff`ecf99000 C:\WINDOWS\System32\OLEAUT32.dll +ModLoad: 00007fff`ecac0000 00007fff`ecb6a000 C:\WINDOWS\System32\sechost.dll +ModLoad: 00007fff`d42f0000 00007fff`d42fc000 C:\WINDOWS\SYSTEM32\VCRUNTIME140_1.dll +ModLoad: 00007fff`ed730000 00007fff`ed829000 C:\WINDOWS\System32\shcore.dll +ModLoad: 00007fff`a1860000 00007fff`a18a3000 C:\WINDOWS\SYSTEM32\MSVCP140_2.dll +ModLoad: 00007fff`73170000 00007fff`7317e000 C:\WINDOWS\SYSTEM32\msdmo.dll +ModLoad: 00007fff`eb1d0000 00007fff`eb22e000 C:\WINDOWS\SYSTEM32\powrprof.dll +ModLoad: 00007fff`c82f0000 00007fff`c8327000 C:\WINDOWS\SYSTEM32\winmmbase.dll +ModLoad: 0000023d`23430000 0000023d`2348e000 C:\WINDOWS\SYSTEM32\powrprof.dll +ModLoad: 00007fff`eaed0000 00007fff`eaf22000 C:\WINDOWS\SYSTEM32\cfgmgr32.dll +ModLoad: 00007fff`eb1b0000 00007fff`eb1c5000 C:\WINDOWS\SYSTEM32\UMPDC.dll +ModLoad: 00007fff`ea020000 00007fff`ea03b000 C:\WINDOWS\SYSTEM32\kernel.appcore.dll +ModLoad: 00007fff`eb700000 00007fff`eb7ac000 C:\WINDOWS\System32\bcryptPrimitives.dll +ModLoad: 00007fff`e84a0000 00007fff`e8548000 C:\WINDOWS\system32\uxtheme.dll +ModLoad: 00007fff`ec070000 00007fff`ec1cd000 C:\WINDOWS\System32\MSCTF.dll +ownHwnd=0000000000AC102E +loading C:\Program Files\Common Files\VST3\Nexus.vst3 +ModLoad: 00007ffe`c91e0000 00007ffe`ca5e5000 C:\Program Files\Common Files\VST3\Nexus.vst3 +ModLoad: 00007fff`ec670000 00007fff`ec690000 C:\WINDOWS\System32\imagehlp.dll +ModLoad: 00007fff`ecd40000 00007fff`ece35000 C:\WINDOWS\System32\COMDLG32.dll +ModLoad: 00007fff`e85d0000 00007fff`e870a000 C:\WINDOWS\SYSTEM32\dxgi.dll +ModLoad: 00007fff`edfb0000 00007fff`ee016000 C:\WINDOWS\System32\SHLWAPI.dll +ModLoad: 00007fff`eb420000 00007fff`eb4a3000 C:\WINDOWS\System32\WINTRUST.dll +ModLoad: 00007fff`c5300000 00007fff`c53b5000 C:\WINDOWS\WinSxS\amd64_microsoft.windows.common-controls_6595b64144ccf1df_5.82.26100.8328_none_87eede957a2ccddd\COMCTL32.dll +ModLoad: 00007fff`c3430000 00007fff`c36c5000 C:\WINDOWS\SYSTEM32\WININET.dll +ModLoad: 00007fff`db6f0000 00007fff`db6fb000 C:\WINDOWS\SYSTEM32\VERSION.dll +ModLoad: 00007fff`ebd00000 00007fff`ebe7e000 C:\WINDOWS\System32\CRYPT32.dll +ModLoad: 00007fff`ebff0000 00007fff`ec063000 C:\WINDOWS\System32\WLDAP32.dll +ModLoad: 00007fff`ec630000 00007fff`ec638000 C:\WINDOWS\System32\Normaliz.dll +ModLoad: 00007fff`eb260000 00007fff`eb28a000 C:\WINDOWS\SYSTEM32\bcrypt.dll +ModLoad: 00007fff`eaa40000 00007fff`eaa53000 C:\WINDOWS\SYSTEM32\MSASN1.dll +ModLoad: 00007fff`e8e00000 00007fff`e969b000 C:\WINDOWS\SYSTEM32\windows.storage.dll +ModLoad: 00007fff`e8370000 00007fff`e837d000 C:\WINDOWS\SYSTEM32\secur32.dll +ModLoad: 00007fff`ea2d0000 00007fff`ea319000 C:\WINDOWS\SYSTEM32\SSPICLI.DLL +ModLoad: 00007fff`e99d0000 00007fff`e9a03000 C:\WINDOWS\SYSTEM32\iphlpapi.dll +(750c.6960): C++ EH exception - code e06d7363 (first chance) +(750c.6960): C++ EH exception - code e06d7363 (first chance) +(750c.6960): C++ EH exception - code e06d7363 (first chance) +(750c.6960): C++ EH exception - code e06d7363 (first chance) +(750c.6960): C++ EH exception - code e06d7363 (first chance) +(750c.6960): C++ EH exception - code e06d7363 (first chance) +(750c.6960): C++ EH exception - code e06d7363 (first chance) +(750c.6960): C++ EH exception - code e06d7363 (first chance) +ModLoad: 00007fff`ea5b0000 00007fff`ea61c000 C:\WINDOWS\system32\mswsock.dll +ModLoad: 00007fff`e9a60000 00007fff`e9bb8000 C:\WINDOWS\SYSTEM32\DNSAPI.dll +ModLoad: 00007fff`ea3b0000 00007fff`ea3bd000 C:\WINDOWS\SYSTEM32\DSPARSE.dll +ModLoad: 00007fff`edaf0000 00007fff`edafa000 C:\WINDOWS\System32\NSI.dll +ModLoad: 00007fff`dbaf0000 00007fff`dbafb000 C:\Windows\System32\rasadhlp.dll +ModLoad: 00007fff`ddb60000 00007fff`ddbe6000 C:\WINDOWS\System32\fwpuclnt.dll +(750c.6960): C++ EH exception - code e06d7363 (first chance) +(750c.6960): C++ EH exception - code e06d7363 (first chance) +(750c.6960): C++ EH exception - code e06d7363 (first chance) +20:41:05: ERR - Failed to download: https://auth.refx.xom/login/n4/ Could not resolve host: auth.refx.xom [6] +ModLoad: 00007fff`e9e50000 00007fff`e9f15000 C:\WINDOWS\system32\schannel.DLL +[Vst3Instrument] loaded C:\Program Files\Common Files\VST3\Nexus.vst3 (Nexus) outCh=2 +[shared_worker] load inst1=1 +ModLoad: 00007fff`e7050000 00007fff`e768a000 C:\WINDOWS\SYSTEM32\d2d1.dll +ModLoad: 00007fff`e6de0000 00007fff`e7047000 C:\WINDOWS\SYSTEM32\DWrite.dll +ModLoad: 00007fff`e7690000 00007fff`e78f0000 C:\WINDOWS\SYSTEM32\d3d11.dll +ModLoad: 00007fff`8e500000 00007fff`8eaae000 C:\WINDOWS\SYSTEM32\D3D10Warp.dll +ModLoad: 00007fff`e8580000 00007fff`e85c5000 C:\WINDOWS\SYSTEM32\dxcore.dll +ModLoad: 00007fff`e8ab0000 00007fff`e8ac4000 C:\WINDOWS\SYSTEM32\resourcepolicyclient.dll +ModLoad: 00007fff`e5c40000 00007fff`e5c9f000 C:\WINDOWS\SYSTEM32\directxdatabasehelper.dll +onecore\windows\directx\database\helperlibrary\lib\perappusersettingsqueryimpl.cpp(135)\directxdatabasehelper.dll!00007FFFE5C5A476: (caller: 00007FFFE5C5A32C) ReturnHr(1) tid(6960) 80070002 The system cannot find the file specified. +onecore\windows\directx\database\helperlibrary\lib\directxdatabasehelper.cpp(2290)\directxdatabasehelper.dll!00007FFFE5C52A1A: (caller: 00007FFFE865494E) ReturnHr(2) tid(6960) 80070002 The system cannot find the file specified. +onecore\windows\directx\database\helperlibrary\lib\perappusersettingsqueryimpl.cpp(135)\directxdatabasehelper.dll!00007FFFE5C5A476: (caller: 00007FFFE5C5A32C) ReturnHr(3) tid(6960) 80070002 The system cannot find the file specified. +onecore\windows\directx\database\helperlibrary\lib\perappusersettingsqueryimpl.cpp(135)\directxdatabasehelper.dll!00007FFFE5C5A476: (caller: 00007FFFE5C5A32C) ReturnHr(4) tid(6960) 80070002 The system cannot find the file specified. +onecore\windows\directx\database\helperlibrary\lib\perappusersettingsqueryimpl.cpp(135)\directxdatabasehelper.dll!00007FFFE5C5A476: (caller: 00007FFFE5C5A32C) ReturnHr(5) tid(6960) 80070002 The system cannot find the file specified. +onecore\windows\directx\database\helperlibrary\lib\perappusersettingsqueryimpl.cpp(135)\directxdatabasehelper.dll!00007FFFE5C5A476: (caller: 00007FFFE5C5A32C) ReturnHr(6) tid(6960) 80070002 The system cannot find the file specified. +onecore\windows\directx\database\helperlibrary\lib\perappusersettingsqueryimpl.cpp(135)\directxdatabasehelper.dll!00007FFFE5C5A476: (caller: 00007FFFE5C5A32C) ReturnHr(7) tid(6960) 80070002 The system cannot find the file specified. +onecore\windows\directx\database\helperlibrary\lib\perappusersettingsqueryimpl.cpp(135)\directxdatabasehelper.dll!00007FFFE5C5A476: (caller: 00007FFFE5C5A32C) ReturnHr(8) tid(6960) 80070002 The system cannot find the file specified. +onecore\windows\directx\database\helperlibrary\lib\perappusersettingsqueryimpl.cpp(135)\directxdatabasehelper.dll!00007FFFE5C5A476: (caller: 00007FFFE5C5A32C) ReturnHr(9) tid(6960) 80070002 The system cannot find the file specified. +onecore\windows\directx\database\helperlibrary\lib\perappusersettingsqueryimpl.cpp(135)\directxdatabasehelper.dll!00007FFFE5C5A476: (caller: 00007FFFE5C5A32C) ReturnHr(10) tid(6960) 80070002 The system cannot find the file specified. +ModLoad: 00007fff`eab40000 00007fff`eab70000 C:\WINDOWS\SYSTEM32\ncrypt.dll +ModLoad: 00007fff`eaaf0000 00007fff`eab2f000 C:\WINDOWS\SYSTEM32\NTASN1.dll +ModLoad: 00007fff`9cb10000 00007fff`9cb58000 C:\WINDOWS\system32\ncryptsslp.dll +(750c.6960): C++ EH exception - code e06d7363 (first chance) +(750c.6960): C++ EH exception - code e06d7363 (first chance) +(750c.6960): C++ EH exception - code e06d7363 (first chance) +(750c.6960): C++ EH exception - code e06d7363 (first chance) +(750c.6960): C++ EH exception - code e06d7363 (first chance) +(750c.6960): C++ EH exception - code e06d7363 (first chance) +(750c.6960): C++ EH exception - code e06d7363 (first chance) +(750c.6960): C++ EH exception - code e06d7363 (first chance) +(750c.6960): C++ EH exception - code e06d7363 (first chance) +(750c.6960): C++ EH exception - code e06d7363 (first chance) +20:41:09: INFO - Nexus 4.5.17 (curl 8.4.0) +20:41:09: INFO - 15 Aug 2026 +20:41:09: INFO - Computer Name: DESKTOP-4D33G7B +20:41:09: INFO - Content path: F:\MUSIC_SOFTWARE\reFX - Nexus 4 Complete\Nexus Library +20:41:09: INFO - Host: C:\Users\locpham\SonicForgeStudio\native_bridge\build\Release\gui_probe.exe +ModLoad: 00007fff`ea150000 00007fff`ea186000 C:\WINDOWS\SYSTEM32\ntmarta.dll +ModLoad: 00007fff`ee020000 00007fff`ee0ce000 C:\WINDOWS\System32\clbcatq.dll +ModLoad: 00007fff`dc500000 00007fff`dc750000 C:\Windows\System32\twinapi.appcore.dll +ModLoad: 00007fff`c1840000 00007fff`c189c000 C:\WINDOWS\system32\dataexchange.dll +onecore\windows\directx\database\helperlibrary\lib\perappusersettingsqueryimpl.cpp(135)\directxdatabasehelper.dll!00007FFFE5C5A476: (caller: 00007FFFE5C5A32C) ReturnHr(11) tid(6960) 80070002 The system cannot find the file specified. +[shared_worker] attach inst1=1 hwnd=0000000003A00E78 +(750c.6960): C++ EH exception - code e06d7363 (first chance) +(750c.6960): C++ EH exception - code e06d7363 (first chance) +(750c.6960): C++ EH exception - code e06d7363 (first chance) +[Vst3Instrument] loaded C:\Program Files\Common Files\VST3\Nexus.vst3 (Nexus) outCh=2 +[shared_worker] load inst2=1 +(750c.6960): C++ EH exception - code e06d7363 (first chance) +(750c.6960): C++ EH exception - code e06d7363 (first chance) +(750c.6960): C++ EH exception - code e06d7363 (first chance) +(750c.6960): C++ EH exception - code e06d7363 (first chance) +(750c.6960): C++ EH exception - code e06d7363 (first chance) +(750c.6960): C++ EH exception - code e06d7363 (first chance) +(750c.6960): C++ EH exception - code e06d7363 (first chance) +(750c.6960): C++ EH exception - code e06d7363 (first chance) +(750c.6960): C++ EH exception - code e06d7363 (first chance) +(750c.6960): C++ EH exception - code e06d7363 (first chance) +[shared_worker] attach inst2=1 hwnd=0000000002750F28 +[wpump] msg=0x31F hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +ModLoad: 00007fff`aad50000 00007fff`aae96000 C:\WINDOWS\SYSTEM32\textinputframework.dll +[wpump] msg=0x31F hwnd=0000000003A00E78 +[wpump] msg=0x31F hwnd=0000000002750F28 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x113 hwnd=0000000004C40B3A +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +[wpump] msg=0x47B hwnd=0000000000FC1172 +RESULT: done=1 aRes=1 bRes=1 +[wpump] msg=0x47B hwnd=0000000000FC1172 +(750c.65c4): Security check failure or stack buffer overrun - code c0000409 (!!! second chance !!!) +Subcode: 0x7 FAST_FAIL_FATAL_APP_EXIT +ucrtbase!abort+0x4e: +00007fff`eb58527e cd29 int 29h +0:000> cdb: Reading initial command 'g' +WARNING: Continuing a non-continuable exception +(750c.65c4): Security check failure or stack buffer overrun - code c0000409 (!!! second chance !!!) +Subcode: 0x7 FAST_FAIL_FATAL_APP_EXIT +ucrtbase!abort+0x4e: +00007fff`eb58527e cd29 int 29h diff --git a/native_bridge/debug/deploy_g12.ps1 b/native_bridge/debug/deploy_g12.ps1 new file mode 100644 index 0000000..3cff71c --- /dev/null +++ b/native_bridge/debug/deploy_g12.ps1 @@ -0,0 +1,11 @@ +$ErrorActionPreference = 'Continue' +$install = 'C:\Users\locpham\SonicForgeStudio\install' +$new = 'C:\Users\locpham\SonicForgeStudio\native_bridge\build\Release\daw_vst_bridge.exe' + +Get-Process sonicforge-daw, daw_engine, daw_vst_bridge -ErrorAction SilentlyContinue | Stop-Process -Force +Start-Sleep -Seconds 2 +Copy-Item -Path $new -Destination (Join-Path $install 'daw_vst_bridge.exe') -Force +Write-Output "copied: $((Get-Item (Join-Path $install 'daw_vst_bridge.exe')).Length) bytes" +Start-Process -FilePath (Join-Path $install 'sonicforge-daw.exe') +Start-Sleep -Seconds 8 +Get-Process sonicforge-daw, daw_engine, daw_vst_bridge -ErrorAction SilentlyContinue | Select-Object Id, ProcessName, Path | Format-Table -AutoSize diff --git a/native_bridge/debug/direct_shared4_esc.txt b/native_bridge/debug/direct_shared4_esc.txt new file mode 100644 index 0000000..a7f26d5 --- /dev/null +++ b/native_bridge/debug/direct_shared4_esc.txt @@ -0,0 +1 @@ +'��o\x00w\x00n\x00H\x00w\x00n\x00d\x00=\x000\x000\x000\x000\x000\x000\x000\x000\x000\x000\x00A\x007\x000\x003\x005\x004\x00\n\x00\n\x00l\x00o\x00a\x00d\x00i\x00n\x00g\x00 \x00C\x00:\x00\\\x00P\x00r\x00o\x00g\x00r\x00a\x00m\x00 \x00F\x00i\x00l\x00e\x00s\x00\\\x00C\x00o\x00m\x00m\x00o\x00n\x00 \x00F\x00i\x00l\x00e\x00s\x00\\\x00V\x00S\x00T\x003\x00\\\x00N\x00e\x00x\x00u\x00s\x00.\x00v\x00s\x00t\x003\x00\n\x00\n\x00[\x00V\x00s\x00t\x003\x00I\x00n\x00s\x00t\x00r\x00u\x00m\x00e\x00n\x00t\x00]\x00 \x00l\x00o\x00a\x00d\x00e\x00d\x00 \x00C\x00:\x00\\\x00P\x00r\x00o\x00g\x00r\x00a\x00m\x00 \x00F\x00i\x00l\x00e\x00s\x00\\\x00C\x00o\x00m\x00m\x00o\x00n\x00 \x00F\x00i\x00l\x00e\x00s\x00\\\x00V\x00S\x00T\x003\x00\\\x00N\x00e\x00x\x00u\x00s\x00.\x00v\x00s\x00t\x003\x00 \x00(\x00N\x00e\x00x\x00u\x00s\x00)\x00 \x00o\x00u\x00t\x00C\x00h\x00=\x002\x00\n\x00\n\x00[\x00s\x00h\x00a\x00r\x00e\x00d\x00_\x00w\x00o\x00r\x00k\x00e\x00r\x00]\x00 \x00l\x00o\x00a\x00d\x00 \x00i\x00n\x00s\x00t\x001\x00=\x001\x00\n\x00\n\x00[\x00s\x00h\x00a\x00r\x00e\x00d\x00_\x00w\x00o\x00r\x00k\x00e\x00r\x00]\x00 \x00a\x00t\x00t\x00a\x00c\x00h\x00 \x00i\x00n\x00s\x00t\x001\x00=\x001\x00 \x00h\x00w\x00n\x00d\x00=\x000\x000\x000\x000\x000\x000\x000\x000\x000\x000\x001\x003\x000\x00B\x00B\x002\x00\n\x00\n\x00[\x00V\x00s\x00t\x003\x00I\x00n\x00s\x00t\x00r\x00u\x00m\x00e\x00n\x00t\x00]\x00 \x00l\x00o\x00a\x00d\x00e\x00d\x00 \x00C\x00:\x00\\\x00P\x00r\x00o\x00g\x00r\x00a\x00m\x00 \x00F\x00i\x00l\x00e\x00s\x00\\\x00C\x00o\x00m\x00m\x00o\x00n\x00 \x00F\x00i\x00l\x00e\x00s\x00\\\x00V\x00S\x00T\x003\x00\\\x00N\x00e\x00x\x00u\x00s\x00.\x00v\x00s\x00t\x003\x00 \x00(\x00N\x00e\x00x\x00u\x00s\x00)\x00 \x00o\x00u\x00t\x00C\x00h\x00=\x002\x00\n\x00\n\x00[\x00s\x00h\x00a\x00r\x00e\x00d\x00_\x00w\x00o\x00r\x00k\x00e\x00r\x00]\x00 \x00l\x00o\x00a\x00d\x00 \x00i\x00n\x00s\x00t\x002\x00=\x001\x00\n\x00\n\x00[\x00s\x00h\x00a\x00r\x00e\x00d\x00_\x00w\x00o\x00r\x00k\x00e\x00r\x00]\x00 \x00a\x00t\x00t\x00a\x00c\x00h\x00 \x00i\x00n\x00s\x00t\x002\x00=\x001\x00 \x00h\x00w\x00n\x00d\x00=\x000\x000\x000\x000\x000\x000\x000\x000\x000\x000\x004\x001\x000\x008\x00D\x002\x00\n\x00\n\x00[\x00w\x00p\x00u\x00m\x00p\x00]\x00 \x00m\x00s\x00g\x00=\x000\x00x\x003\x001\x00F\x00 \x00h\x00w\x00n\x00d\x00=\x000\x000\x000\x000\x000\x000\x000\x000\x000\x000\x00A\x000\x000\x003\x009\x00E\x00\n\x00\n\x00[\x00w\x00p\x00u\x00m\x00p\x00]\x00 \x00m\x00s\x00g\x00=\x000\x00x\x003\x001\x00F\x00 \x00h\x00w\x00n\x00d\x00=\x000\x000\x000\x000\x000\x000\x000\x000\x000\x000\x001\x003\x000\x00B\x00B\x002\x00\n\x00\n\x00[\x00w\x00p\x00u\x00m\x00p\x00]\x00 \x00m\x00s\x00g\x00=\x000\x00x\x004\x007\x00B\x00 \x00h\x00w\x00n\x00d\x00=\x000\x000\x000\x000\x000\x000\x000\x000\x000\x000\x00A\x000\x000\x003\x009\x00E\x00\n\x00\n\x00[\x00w\x00p\x00u\x00m\x00p\x00]\x00 \x00m\x00s\x00g\x00=\x000\x00x\x003\x001\x00F\x00 \x00h\x00w\x00n\x00d\x00=\x000\x000\x000\x000\x000\x000\x000\x000\x000\x000\x004\x001\x000\x008\x00D\x002\x00\n\x00\n\x00[\x00w\x00p\x00u\x00m\x00p\x00]\x00 \x00m\x00s\x00g\x00=\x000\x00x\x004\x007\x00B\x00 \x00h\x00w\x00n\x00d\x00=\x000\x000\x000\x000\x000\x000\x000\x000\x000\x000\x00A\x000\x000\x003\x009\x00E\x00\n\x00\n\x00R\x00E\x00S\x00U\x00L\x00T\x00:\x00 \x00d\x00o\x00n\x00e\x00=\x001\x00 \x00a\x00R\x00e\x00s\x00=\x001\x00 \x00b\x00R\x00e\x00s\x00=\x001\x00\n\x00\n\x00E\x00X\x00I\x00T\x00=\x000\x00 \x00E\x00L\x00A\x00P\x00S\x00E\x00D\x00=\x006\x00.\x007\x007\x004\x000\x004\x005\x001\x00\n\x00\n\x00' \ No newline at end of file diff --git a/native_bridge/debug/evt.ps1 b/native_bridge/debug/evt.ps1 new file mode 100644 index 0000000..50060f2 --- /dev/null +++ b/native_bridge/debug/evt.ps1 @@ -0,0 +1,4 @@ +Get-WinEvent -FilterHashtable @{LogName='Application'; ProviderName='Application Error'} -MaxEvents 30 -ErrorAction SilentlyContinue | + Where-Object { $_.Message -match 'gui_probe' } | + Select-Object -First 3 | + ForEach-Object { $_.TimeCreated; $_.Message; '====' } diff --git a/native_bridge/debug/evt2.ps1 b/native_bridge/debug/evt2.ps1 new file mode 100644 index 0000000..e7d4b46 --- /dev/null +++ b/native_bridge/debug/evt2.ps1 @@ -0,0 +1,4 @@ +$ev = Get-WinEvent -FilterHashtable @{LogName='Application'; ProviderName='Application Error'} -MaxEvents 30 -ErrorAction SilentlyContinue | + Where-Object { $_.Message -match 'gui_probe' } | Select-Object -First 3 +$out = foreach ($e in $ev) { "$($e.TimeCreated)" + "`n" + $e.Message + "`n====" } +$out | Out-File -FilePath 'debug/evt_out.txt' -Encoding utf8 diff --git a/native_bridge/debug/evt_out.txt b/native_bridge/debug/evt_out.txt new file mode 100644 index 0000000..378c56f --- /dev/null +++ b/native_bridge/debug/evt_out.txt @@ -0,0 +1,39 @@ +08/15/2026 18:14:32 +Faulting application name: gui_probe.exe, version: 0.0.0.0, time stamp: 0x6a80387d +Faulting module name: ucrtbase.dll, version: 10.0.26100.8875, time stamp: 0x303b538a +Exception code: 0xc0000409 +Fault offset: 0x00000000000a527e +Faulting process id: 0x391C +Faulting application start time: 0x1DD2CA73F032169 +Faulting application path: C:\Users\locpham\SonicForgeStudio\native_bridge\build\Release\gui_probe.exe +Faulting module path: C:\WINDOWS\System32\ucrtbase.dll +Report Id: a5c1658e-8152-43b4-9a45-216fa4fe295f +Faulting package full name: +Faulting package-relative application ID: +==== +08/15/2026 18:05:40 +Faulting application name: gui_probe.exe, version: 0.0.0.0, time stamp: 0x6a80387d +Faulting module name: ucrtbase.dll, version: 10.0.26100.8875, time stamp: 0x303b538a +Exception code: 0xc0000409 +Fault offset: 0x00000000000a527e +Faulting process id: 0x4F94 +Faulting application start time: 0x1DD2CA5FD1B9FEF +Faulting application path: C:\Users\locpham\SonicForgeStudio\native_bridge\build\Release\gui_probe.exe +Faulting module path: C:\WINDOWS\System32\ucrtbase.dll +Report Id: a01a443e-2a0d-4e70-ae77-4e7293d1284b +Faulting package full name: +Faulting package-relative application ID: +==== +08/15/2026 18:02:55 +Faulting application name: gui_probe.exe, version: 0.0.0.0, time stamp: 0x6a80387d +Faulting module name: ucrtbase.dll, version: 10.0.26100.8875, time stamp: 0x303b538a +Exception code: 0xc0000409 +Fault offset: 0x00000000000a527e +Faulting process id: 0x2854 +Faulting application start time: 0x1DD2CA59B0C2BF8 +Faulting application path: C:\Users\locpham\SonicForgeStudio\native_bridge\build\Release\gui_probe.exe +Faulting module path: C:\WINDOWS\System32\ucrtbase.dll +Report Id: 7f3f4817-e770-4024-8f07-7d9f9e43bc95 +Faulting package full name: +Faulting package-relative application ID: +==== diff --git a/native_bridge/debug/fix_probe.py b/native_bridge/debug/fix_probe.py new file mode 100644 index 0000000..720430d --- /dev/null +++ b/native_bridge/debug/fix_probe.py @@ -0,0 +1,31 @@ +import io, sys +p = r'C:\Users\locpham\SonicForgeStudio\native_bridge\src\gui_probe.cpp' +s = io.open(p, encoding='utf-8', newline='').read() + +old = 'EXCEPTION: %s\r\n", e.what())' +new = 'EXCEPTION: %s\\n", e.what())' +n1 = s.count(old) +s = s.replace(old, new) +print('fix1 (CRLF):', n1) + +old2 = 'EXCEPTION\r\n");' +new2 = 'EXCEPTION\\n");' +n2 = s.count(old2) +s = s.replace(old2, new2) +print('fix2 (CRLF):', n2) + +# also bare LF variants just in case +old3 = 'EXCEPTION: %s\n", e.what())' +new3 = 'EXCEPTION: %s\\n", e.what())' +n3 = s.count(old3) +s = s.replace(old3, new3) +print('fix3 (LF):', n3) + +old4 = 'EXCEPTION\n");' +new4 = 'EXCEPTION\\n");' +n4 = s.count(old4) +s = s.replace(old4, new4) +print('fix4 (LF):', n4) + +io.open(p, 'w', encoding='utf-8', newline='').write(s) +print('written') diff --git a/native_bridge/debug/ime_lines.txt b/native_bridge/debug/ime_lines.txt new file mode 100644 index 0000000..0922f80 --- /dev/null +++ b/native_bridge/debug/ime_lines.txt @@ -0,0 +1,12 @@ + 9| #include + 33| ImmAssociateContext(w, nullptr); + 35| ImmAssociateContext(c, nullptr); + 67| ImmAssociateContextEx(nullptr, nullptr, IACE_DEFAULT); + 191| if (!done.load()) { printf("RESULT: TIMEOUT (no pump)\n"); return 3; } + 196| if (!done.load()) { printf("RESULT: TIMEOUT (pump concurrent)\n"); return 3; } + 213| if (!done.load()) { printf("RESULT: TIMEOUT (apartment dead)\n"); return 3; } + 218| ImmAssociateContextEx(nullptr, nullptr, IACE_DEFAULT); + 228| if (!done.load()) { printf("RESULT: TIMEOUT (same_thread)\n"); return 3; } + 343| printf("RESULT: TIMEOUT inst1 attach (aDone=0)\n"); + 427| if (!aDone.load()) { printf("RESULT: TIMEOUT inst1 (aDone=0)\n"); return 3; } + 499| printf("[%s] TIMEOUT openGUI(%s)\n", variant.c_str(), tag); \ No newline at end of file diff --git a/native_bridge/debug/patch_com.py b/native_bridge/debug/patch_com.py new file mode 100644 index 0000000..58cc01c --- /dev/null +++ b/native_bridge/debug/patch_com.py @@ -0,0 +1,27 @@ +import io +p = r'C:\Users\locpham\SonicForgeStudio\native_bridge\src\gui_probe.cpp' +s = io.open(p, encoding='utf-8', newline='').read() +def crlf(x): return x.replace('\n', '\r\n') + +old = crlf('''#ifdef _WIN32 + OleInitialize(nullptr); +#endif''') +new = crlf('''#ifdef _WIN32 + CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED); +#endif''') +n = s.count(old) +print('OleInitialize count:', n) +s = s.replace(old, new) + +old2 = crlf('''#ifdef _WIN32 + OleUninitialize(); +#endif''') +new2 = crlf('''#ifdef _WIN32 + CoUninitialize(); +#endif''') +n2 = s.count(old2) +print('OleUninitialize count:', n2) +s = s.replace(old2, new2) + +io.open(p, 'w', encoding='utf-8', newline='').write(s) +print('written') diff --git a/native_bridge/debug/patch_g12.py b/native_bridge/debug/patch_g12.py new file mode 100644 index 0000000..30b250f --- /dev/null +++ b/native_bridge/debug/patch_g12.py @@ -0,0 +1,380 @@ +# G1.2: 16 ChannelWorkers -> 1 UiThread; window+attach job len UiThread; bo main pump. +import io, sys + +path = r'C:/Users/locpham/SonicForgeStudio/native_bridge/src/main.cpp' +s = open(path, encoding='utf-8', newline='').read() +s = s.replace('\r\n', '\n') + +def rep(old, new, tag): + global s + n = s.count(old) + if n != 1: + print('FAIL %s: count=%d' % (tag, n)) + sys.exit(1) + s = s.replace(old, new) + print('OK %s' % tag) + +# R1: g_workers -> g_uiWorker +rep( +"""static std::map>* g_workers = nullptr;""", +"""static ChannelWorker* g_uiWorker = nullptr;""", +"R1") + +# R2: post_resize_view +rep( +"""static void post_resize_view(uint32_t ch, int w, int h) { + if (g_workers) { + auto wit = g_workers->find(ch); + if (wit != g_workers->end()) { + wit->second->post([ch, w, h]() { + if (!g_engine) return; + auto* i = g_engine->get(ch); + if (i) i->resizeView(w, h); + }); + } + } +}""", +"""static void post_resize_view(uint32_t ch, int w, int h) { + if (g_uiWorker) { + g_uiWorker->post([ch, w, h]() { + if (!g_engine) return; + auto* i = g_engine->get(ch); + if (i) i->resizeView(w, h); + }); + } +}""", +"R2") + +# R3a: post_close_gui header -> close_editor_now +rep( +"""// closeGUI() MUST run on the channel worker thread (its COM STA apartment) — +// the view was attached there. Calling view->removed() from the main thread +// (WM_DESTROY handler) is a cross-apartment COM call that corrupts the plugin; +// Nexus then hangs on the NEXT view->attached(). Erase the registry inside the +// job so Option B (closing another editor before attach) waits for closeGUI to +// actually finish. +static void post_close_gui(uint32_t ch, HWND hwnd) { + ChannelWorker* w = nullptr; + if (g_workers) { + auto wit = g_workers->find(ch); + if (wit != g_workers->end()) w = wit->second.get(); + } + if (w) { + // closeGUI() (view->removed()) PHAI chay tren channel worker - COM STA + // apartment cua plugin song o do. Window KHONG bi destroy: VstWindowProc + // giu lai (an) de reuse nen plugin editor children (worker-owned) khong + // bao gio bi huy cross-thread. Registry (g_guiWindows/g_hwndToCh) chi + // xoa trong WM_DESTROY khi window thuc su bi huy. + w->post([ch, hwnd]() { + // Guard window-replaced race: neu OPEN_GUI moi da dung window khac + // (hoac da xoa), khong detach view cua window moi.""", +"""// closeGUI() MUST run on the UiThread (its COM STA apartment) — the view was +// attached there. Calling view->removed() from another thread is a +// cross-apartment COM call that corrupts the plugin; Nexus then hangs on the +// NEXT view->attached(). G1.2: 1 UiThread chung — close chay synchronous tren +// job; goi truc tiep (Option B trong attach job) hoac qua post_close_gui +// (WM_CLOSE/WM_DESTROY). +static void close_editor_now(uint32_t ch, HWND hwnd) { + // Guard window-replaced race: neu OPEN_GUI moi da dung window khac + // (hoac da xoa), khong detach view cua window moi.""", +"R3a") + +# R3c: close_editor_now closing + post_close_gui wrapper +rep( +""" std::lock_guard lk(g_attachMutex); + g_closeInFlight[ch] = false; + } + }); + } +}""", +""" std::lock_guard lk(g_attachMutex); + g_closeInFlight[ch] = false; + } +} + +static void post_close_gui(uint32_t ch, HWND hwnd) { + if (g_uiWorker) { + g_uiWorker->post([ch, hwnd]() { close_editor_now(ch, hwnd); }); + } +}""", +"R3c") + +# R4a: handleOpenGui — bo worker tao + window main thread, mo job tren UiThread +rep( +""" if (!workers[guiCh]) { + workers[guiCh] = std::make_unique(); + } + void* hwnd = (void*)arg1; +#ifdef _WIN32 + // Window PHAI thuoc MAIN thread (audio loop pump nay dispatch + // messages cua no moi vong lap). Tao/cap nhat window ngay tai day. + HWND nativeHwnd = nullptr; + { + std::lock_guard lock(g_guiMutex); + auto it = g_guiWindows.find(guiCh); + if (it != g_guiWindows.end()) nativeHwnd = (HWND)it->second; + } + if (hwnd == 0) { + if (nativeHwnd && IsWindow(nativeHwnd)) { + hwnd = (void*)nativeHwnd; + SetWindowTextA(nativeHwnd, pluginId.c_str()); + ShowWindow(nativeHwnd, SW_SHOW); + SetForegroundWindow(nativeHwnd); + // Reuse: cap nhat USERDATA (channel+1) - inst CU da bi thay + // the boi assign() -> WM_DESTROY sau nay lookup inst MOI. + SetWindowLongPtrA(nativeHwnd, GWLP_USERDATA, (LONG_PTR)(guiCh + 1)); + } else { + nativeHwnd = (HWND)create_native_vst_window(pluginId.c_str()); + if (!nativeHwnd) { + std::cerr << "[NativeBridge] GUI create window FAILED plugin=" << pluginId << std::endl; + return; + } + { + std::lock_guard lock(g_guiMutex); + g_guiWindows[guiCh] = nativeHwnd; // keep window alive + g_hwndToCh[nativeHwnd] = guiCh; // WM_DESTROY cleanup + } + SetWindowLongPtrA(nativeHwnd, GWLP_USERDATA, (LONG_PTR)(guiCh + 1)); + hwnd = (void*)nativeHwnd; + } + } +#endif""", +""" // G1.2: window + attach chay tren UiThread (1 thread chung, COM STA + + // message pump + job queue). Window messages do UiThread pump dispatch — + // audio loop khong con PeekMessageW (xoa block pump o main loop). + // G1.1 verify: gui_probe shared_worker = 1 worker load+openGUI 2 inst OK. + uiWorker.post([&instruments, shmIPC, guiCh, arg1, arg2 = pluginId]() { + AttachInFlightGuard _aifGuard{shmIPC}; + if (!instruments.get(guiCh)) { + std::cerr << "[NativeBridge] GUI attach FAILED hwnd=0" + << " plugin=" << arg2 << " ch=" << guiCh << " (no instrument loaded)" << std::endl; + return; + } + if (auto* inst0 = instruments.get(guiCh)) { + if (inst0->hasAttachedView()) { + // Dedupe race: main da silence (setReloading) truoc khi post — + // restore de channel khong cam vinh vien. + std::vector sp = same_plugin_channels(guiCh); + for (uint32_t y : sp) unmute_if_not_closing(y, "openGUI-dedupe"); + unmute_if_not_closing(guiCh, "openGUI-dedupe"); + return; + } + } + void* hwnd = (void*)arg1; +#ifdef _WIN32 + HWND nativeHwnd = nullptr; + { + std::lock_guard lock(g_guiMutex); + auto it = g_guiWindows.find(guiCh); + if (it != g_guiWindows.end()) nativeHwnd = (HWND)it->second; + } + if (hwnd == 0) { + if (nativeHwnd && IsWindow(nativeHwnd)) { + hwnd = (void*)nativeHwnd; + SetWindowTextA(nativeHwnd, arg2.c_str()); + ShowWindow(nativeHwnd, SW_SHOW); + SetForegroundWindow(nativeHwnd); + // Reuse: cap nhat USERDATA (channel+1) - inst CU da bi thay + // the boi assign() -> WM_DESTROY sau nay lookup inst MOI. + SetWindowLongPtrA(nativeHwnd, GWLP_USERDATA, (LONG_PTR)(guiCh + 1)); + } else { + nativeHwnd = (HWND)create_native_vst_window(arg2.c_str()); + if (!nativeHwnd) { + std::cerr << "[NativeBridge] GUI create window FAILED plugin=" << arg2 << std::endl; + std::vector sp = same_plugin_channels(guiCh); + for (uint32_t y : sp) unmute_if_not_closing(y, "openGUI-createfail"); + unmute_if_not_closing(guiCh, "openGUI-createfail"); + return; + } + { + std::lock_guard lock(g_guiMutex); + g_guiWindows[guiCh] = nativeHwnd; // keep window alive + g_hwndToCh[nativeHwnd] = guiCh; // WM_DESTROY cleanup + } + SetWindowLongPtrA(nativeHwnd, GWLP_USERDATA, (LONG_PTR)(guiCh + 1)); + hwnd = (void*)nativeHwnd; + } + } +#endif""", +"R4a") + +# R4b: bo header job cu (da mo job o R4a) +rep( +""" workers[guiCh]->post([&instruments, shmIPC, guiCh, hwnd, arg2 = pluginId]() { + AttachInFlightGuard _aifGuard{shmIPC}; + if (!instruments.get(guiCh)) { + std::cerr << "[NativeBridge] GUI attach FAILED hwnd=" << hwnd + << " plugin=" << arg2 << " ch=" << guiCh << " (no instrument loaded)" << std::endl; + return; + } + if (auto* inst0 = instruments.get(guiCh)) { + if (inst0->hasAttachedView()) { + return; + } + } +#ifdef _WIN32""", +"""#ifdef _WIN32""", +"R4b") + +# R4c: Option B — close dong bo qua close_editor_now +rep( +""" // Option B: chi 1 editor VST mo tai 1 thoi diem toan + // bridge. Instance thu 2 cua CUNG plugin (Nexus) attach + // view o apartment/worker khac -> treo. Dong editor cua + // channel khac TRUOC khi attach: WM_CLOSE -> main pump + // destroy window -> WM_DESTROY -> closeGUI() + xoa registry. + // Chay tren worker job de khong stall writeIndex cua real-time + // loop. CRASH FIX: silence moi channel TRUOC khi dong editor + // cua no (view->removed() tren worker cung luc process() tren + // audio loop = 2 thread trong 1 plugin -> crash USER32). + { + std::vector others; + bool abortAttach = false; + { + std::lock_guard lock(g_guiMutex); + for (const auto& kv : g_guiWindows) + if (kv.first != guiCh) others.push_back(kv.first); + } + for (uint32_t y : others) { + // Skip channels whose editor already detached (close job + // finished): do not WM_CLOSE a hidden reused window, do + // not re-silence a channel that is already unmuted. + if (auto* yi0 = instruments.get(y)) { + if (!yi0->hasAttachedView()) { + continue; + } + } + HWND yHwnd = nullptr; + { + std::lock_guard lock(g_guiMutex); + auto it = g_guiWindows.find(y); + if (it != g_guiWindows.end()) yHwnd = (HWND)it->second; + } + if (!yHwnd || !IsWindow(yHwnd)) continue; + if (instruments.get(y)) { + instruments.setReloading(y, true); + } + PostMessage(yHwnd, WM_CLOSE, 0, 0); + // WM_CLOSE -> VstWindowProc -> post_close_gui -> closeGUI() + // tren worker cua channel y. Doi cho view da detach (window + // duoc giu lai de reuse, khong doi registry erase nhu cu). + bool closed = false; + // The close job = closeGUI + destroy children + fresh + // reload (createInstance, ~2s for Nexus). hasAttachedView + // flips false after closeGUI only - ALSO wait for the + // close job to finish entirely (close_in_flight): the + // attach must not run createView while another thread is + // still inside this plugin DLL (2 threads in one DLL -> + // Nexus exits silently, observed in probes). + for (int i = 0; i < 500; ++i) { // 5s budget + auto* yi = instruments.get(y); + bool detached = !yi || !yi->hasAttachedView(); + if (detached && !close_in_flight(y)) { closed = true; break; } + Sleep(10); + } + if (!closed) { + abortAttach = true; + break; + } + } + if (abortAttach) { + // A close job is stuck; do NOT createView (would race the + // plugin DLL). Unmute what we silenced (skips channels + // whose close job is still in flight - that job unmutes + // them when it finishes). + for (uint32_t y : others) unmute_if_not_closing(y, "openGUI-abort"); + return; + } + }""", +""" // Option B: chi 1 editor VST mo tai 1 thoi diem toan bridge. Instance + // thu 2 cua CUNG plugin (Nexus) attach view o thread khac -> treo. + // G1.2: 1 UiThread -> dong editor cua channel khac DONG BO (goi + // close_editor_now truc tiep). Khong the PostMessage(WM_CLOSE) cho + // job khac nhu cu — job close nam sau job hien tai trong queue cua + // CUNG thread -> khong bao gio chay -> 5s timeout -> attach abort. + { + std::vector others; + { + std::lock_guard lock(g_guiMutex); + for (const auto& kv : g_guiWindows) + if (kv.first != guiCh) others.push_back(kv.first); + } + for (uint32_t y : others) { + // Skip channels whose editor already detached (close job + // finished): do not WM_CLOSE a hidden reused window, do + // not re-silence a channel that is already unmuted. + if (auto* yi0 = instruments.get(y)) { + if (!yi0->hasAttachedView()) { + continue; + } + } + HWND yHwnd = nullptr; + { + std::lock_guard lock(g_guiMutex); + auto it = g_guiWindows.find(y); + if (it != g_guiWindows.end()) yHwnd = (HWND)it->second; + } + if (!yHwnd || !IsWindow(yHwnd)) continue; + close_editor_now(y, yHwnd); + } + }""", +"R4c") + +# R5: main workers map -> uiWorker +rep( +""" std::map> workers; + g_workers = &workers;""", +""" // G1.2: MOT UiThread chung (COM STA + message pump + job queue) cho moi + // channel — thay 16 ChannelWorker. Verify gui_probe `shared_worker` (G1.1): + // load + openGUI 2 instance tren 1 worker OK, khong crash/hang. + ChannelWorker uiWorker; + g_uiWorker = &uiWorker;""", +"R5") + +# R6: LOAD job post +rep( +""" if (!workers[ch]) { + workers[ch] = std::make_unique(); + } + workers[ch]->post([&instruments, t, ch, path, sampleRate, block]() {""", +""" uiWorker.post([&instruments, t, ch, path, sampleRate, block]() {""", +"R6") + +# R7: bo main pump +rep( +"""#ifdef _WIN32 + // Message pump: VST editors (Nexus, JUCE-based...) block inside + // view->attached() until the host dispatches messages — openGUI runs + // on a worker thread, so THIS loop must pump concurrently (verified + // with gui_probe: worker-thread openGUI + concurrent pump → attached + // returns kResultOk; without it → hangs forever). + MSG msg; + int pumpedMain = 0; + while (PeekMessageW(&msg, nullptr, 0, 0, PM_REMOVE)) { + TranslateMessage(&msg); + // CRASH FIX (0xc000041d): same as the worker pump — a C++ exception + // raised inside a plugin window proc (Nexus) must never escape + // DispatchMessageW as an unhandled fatal callback exception. + try { + DispatchMessageW(&msg); + } catch (...) { + std::cerr << "[NativeBridge] main pump EXCEPTION — plugin window proc threw" << std::endl; + } + ++pumpedMain; + } +#endif""", +""" // G1.2: audio loop KHONG pump messages nua — moi window (native VST + + // editor children) thuoc UiThread; UiThread pump dispatch chung.""", +"R7") + +# R8: ChannelWorker comment +rep( +"""// Per-channel persistent worker: ONE thread owns the COM STA apartment for that +// channel's instrument for its whole lifetime. loadPlugin and openGUI MUST run""", +"""// G1.2: single persistent UiThread — ONE thread owns the COM STA apartment for +// ALL instruments for the whole bridge lifetime. loadPlugin and openGUI MUST run""", +"R8") + +s = s.replace('\n', '\r\n') +open(path, 'w', encoding='utf-8', newline='').write(s) +print('ALL OK') diff --git a/native_bridge/debug/patch_g13.py b/native_bridge/debug/patch_g13.py new file mode 100644 index 0000000..be8e242 --- /dev/null +++ b/native_bridge/debug/patch_g13.py @@ -0,0 +1,97 @@ +# G1.3 patch: bo pump gate cu (is_teardown_window/g_ownerTid/g_juceOwnerTids/ +# g_tidToWorker/post_and_wait) — 1 UiThread: teardown job va pump cung 1 thread, +# khong con race "2 thread 1 DLL" khi teardown. CBT hook chi con tren UiThread. +import io + +p = r'C:/Users/locpham/SonicForgeStudio/native_bridge/src/main.cpp' +t = io.open(p, newline='').read() +lines = t.split('\n') + +def rep(a, b, new, tag): + global t + old = '\n'.join(lines[a-1:b]) + assert old in t, f'{tag}: old not found at {a}-{b}' + assert t.count(old) == 1, f'{tag}: old not unique ({t.count(old)})' + t = t.replace(old, new) + print(f'OK {tag}') + +# R1: bo comment over-gate + g_juceTidsMutex/g_juceOwnerTids +rep(75, 86, ''' // G1.3: bo pump gate cu (g_juceOwnerTids / g_ownerTid / + // is_teardown_window): teardown (close/LOAD job) va message pump cung chay tren + // 1 thread (UiThread) — job chay thi pump dung, job xong thi editor window da + // destroy nen USER32 tu huy message toi window chet. Khong con "2 thread trong + // 1 DLL" khi teardown (multi-worker cu).''', 'R1') + +# R2: CBT hook bo ghi tid +rep(101, 105, ''' ImmAssociateContext(w, nullptr); +''', 'R2') + +# R3: bo fwd decl is_teardown_window/pump_window_channel +rep(120, 121, '', 'R3') + +# R4: bo g_tidMutex/g_tidToWorker +rep(127, 128, '', 'R4') + +# R5: bo g_ownerTid + comment +rep(139, 144, '', 'R5') + +# R6: UiThread ctor bo register tid +rep(274, 277, ' OleInitialize(nullptr);', 'R6') + +# R7: pump bo gate block +rep(309, 343, ''' // G1.3: bo pump gate cu — teardown (close/LOAD job) + // va pump cung 1 thread (UiThread): job chay thi pump dung; + // het job thi editor window da destroy -> message chet bi + // USER32 huy tu dong. Khong can drop theo close_in_flight. +''', 'R7') + +# R8: dtor bo erase tid +rep(372, 375, ' OleUninitialize();', 'R8') + +# R9: bo post_and_wait +rep(411, 438, '', 'R9') + +# R10: bo pump_window_channel + is_teardown_window +rep(495, 554, '', 'R10') + +# R11: don gian destroy_window_on_owner +rep(556, 591, ''' // G1.3: 1 UiThread — editor windows tao trong attach job (UiThread), destroy + // chi goi tu UiThread job (close_editor_now / LOAD) -> ownerTid luon == current + // tid -> destroy truc tiep. Neu owner khac thread (khong con xay ra) -> bo + // (leak > crash): cross-thread DestroyWindow khong an toan. + static void destroy_window_on_owner(HWND hwnd) { + if (!hwnd || !IsWindow(hwnd)) return; + DWORD ownerTid = GetWindowThreadProcessId(hwnd, nullptr); + DWORD curTid = GetCurrentThreadId(); + if (ownerTid == 0 || ownerTid == curTid) { + if (IsWindow(hwnd)) DestroyWindow(hwnd); + return; + } + std::cerr << "[NativeBridge] destroy_window_on_owner: owner tid=" << ownerTid + << " != current " << curTid << " - leaving window " << hwnd << std::endl; + } +''', 'R11') + +# R12: attach-end bo g_ownerTid recording +rep(1191, 1208, ''' disable_ime_contexts((HWND)hwnd); +''', 'R12') + +# R13: bo main-thread CBT hook (windows chi tao tren UiThread) +rep(767, 771, ''' // Default IMC = none on the main thread (IME recursion fix). + ImmAssociateContextEx(nullptr, nullptr, IACE_DEFAULT); +''', 'R13') + +# R14a: comment LOAD job — window tao tren UiThread (khong phai MAIN) +rep(1299, 1304, ''' // G1.2/G1.3: window tao tren UiThread (handleOpenGui post attach + // job; create_native_vst_window chay trong job). LOAD job cung + // chay tren UiThread -> khong cross-thread destroy. An window, + // giu registry de reuse; editor detach + reload o duoi. +''', 'R14a') + +# R14b: comment attach-fail — WM_CLOSE -> UiThread pump +rep(1222, 1223, ''' // registry; WM_CLOSE -> UiThread pump destroy (owner = UiThread). + // PostMessage an toan cross-thread (khong nhu DestroyWindow). +''', 'R14b') + +io.open(p, 'w', newline='').write(t) +print('written') diff --git a/native_bridge/debug/patch_g14.py b/native_bridge/debug/patch_g14.py new file mode 100644 index 0000000..94920be --- /dev/null +++ b/native_bridge/debug/patch_g14.py @@ -0,0 +1,97 @@ +# G1.4: bo mute-when-editor-open — xoa editorOpen machinery (channelQuiet chi con reloading) +import io, sys + +p = 'native_bridge/src/main.cpp' +raw = open(p, 'rb').read() +EM = b'\xe2\x80\x94' +NL = b'\r\n' + +def rep(old, new, tag): + global raw + o = old.replace(b'\n', NL) + if o not in raw: + print(f'MISSING [{tag}]') + sys.exit(1) + raw = raw.replace(o, new.replace(b'\n', NL)) + print(f'OK [{tag}]') + +rep(b"""// Editor-open tracking: while a channel's VST editor (native window) is +// attached, EVERY channel assigned the same plugin DLL path must stay quiet """ + EM + b""" +// the plugin's window proc runs on the editor channel's worker while the +// audio loop calls process() on other instances of the same DLL (2 threads +// in one DLL -> Nexus USER32 crash). g_editorPathCount = refcount per +// lowercase plugin path; g_editorOpenPath/g_editorOpen per channel. +// Lock order: g_editorMutex is taken while holding the engine mutex +// (channelQuiet) or standalone in non-rt jobs """ + EM + b""" never the reverse. +static std::mutex g_editorMutex; +static std::map g_editorPathCount; +static std::string g_editorOpenPath[16]; +static bool g_editorOpen[16] = { false }; + +""", b"", "decl-block") + +rep(b"""// Editor closed / instrument replaced: drop the channel's editor-open state +// (refcounted per lowercase plugin path). Called from non-rt jobs only """ + EM + b""" +// never from the audio loop. +static void clear_editor_open(uint32_t ch) { + std::lock_guard lk(g_editorMutex); + if (!g_editorOpen[ch]) return; + g_editorOpen[ch] = false; + std::string pth = g_editorOpenPath[ch]; + g_editorOpenPath[ch].clear(); + if (!pth.empty()) { + auto it = g_editorPathCount.find(pth); + if (it != g_editorPathCount.end() && --it->second <= 0) + g_editorPathCount.erase(it); + } +} + +""", b"", "clear_editor_open") + +rep(b""" // Editor-open predicate: channelQuiet() checks whether ANY editor is + // attached for the channel's plugin DLL path (refcounted in + // g_editorPathCount, updated by the open/close GUI jobs). + instruments.setEditorOpenPredicate([](const std::string& lp) { + std::lock_guard lk(g_editorMutex); + auto it = g_editorPathCount.find(lp); + return it != g_editorPathCount.end() && it->second > 0; + }); +""", b"", "set-predicate") + +rep(b""" // Editor attached: mark the plugin path editor-open so the + // audio loop stops processing EVERY channel assigned this + // DLL while the editor lives (its window proc runs on this + // worker; process() elsewhere on the same DLL -> Nexus crash). + { + std::lock_guard lk(g_editorMutex); + if (!gp.empty()) { + g_editorOpen[guiCh] = true; + g_editorOpenPath[guiCh] = gp; + ++g_editorPathCount[gp]; + } + } + disable_ime_contexts((HWND)hwnd); +""", b""" disable_ime_contexts((HWND)hwnd); +""", "attach-mark") + +rep(b""" // Editor detached + fresh instance loaded: drop editor-open + // state (channelQuiet falls back to reloading flags only). + clear_editor_open(ch); +""", b"", "close-ok-path") + +rep(b""" // Instrument already gone (unloaded) """ + EM + b""" nothing to rebuild. + g_engine->setReloading(ch, false); + clear_editor_open(ch); +""", b""" // Instrument already gone (unloaded) """ + EM + b""" nothing to rebuild. + g_engine->setReloading(ch, false); +""", "close-else-path") + +rep(b""" ShowWindow(hToHide, SW_HIDE); + // Editor detached: drop editor-open state before the + // instrument is replaced (assign). + clear_editor_open(ch); +""", b""" ShowWindow(hToHide, SW_HIDE); +""", "load-close-path") + +open(p, 'wb').write(raw) +print('DONE main.cpp') diff --git a/native_bridge/debug/patch_handler_worker.py b/native_bridge/debug/patch_handler_worker.py new file mode 100644 index 0000000..501ab9c --- /dev/null +++ b/native_bridge/debug/patch_handler_worker.py @@ -0,0 +1,19 @@ +import io +p = r'C:\Users\locpham\SonicForgeStudio\native_bridge\src\gui_probe.cpp' +s = io.open(p, encoding='utf-8', newline='').read() +def crlf(x): return x.replace('\n', '\r\n') + +old = crlf(''' th = std::thread([this] { +#ifdef _WIN32 + CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED); +#endif''') +new = crlf(''' th = std::thread([this] { +#ifdef _WIN32 + _set_invalid_parameter_handler(probe_invalid_param); + CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED); +#endif''') +n = s.count(old) +print('match:', n) +s = s.replace(old, new) +io.open(p, 'w', encoding='utf-8', newline='').write(s) +print('written') diff --git a/native_bridge/debug/patch_invalid_param.py b/native_bridge/debug/patch_invalid_param.py new file mode 100644 index 0000000..26518d2 --- /dev/null +++ b/native_bridge/debug/patch_invalid_param.py @@ -0,0 +1,53 @@ +import io +p = r'C:\Users\locpham\SonicForgeStudio\native_bridge\src\gui_probe.cpp' +s = io.open(p, encoding='utf-8', newline='').read() +def crlf(x): return x.replace('\n', '\r\n') + +# 1) add includes + handler helpers after existing includes +old_inc = crlf('#include ') +new_inc = crlf('''#include +#include + +#ifdef _WIN32 +static void probe_invalid_param(const wchar_t* expr, const wchar_t* func, const wchar_t* file, + unsigned int line, uintptr_t reserved) { + printf("[probe] INVALID_PARAMETER expr=%ls func=%ls\\n", expr ? expr : L"null", func ? func : L"null"); + fflush(stdout); +} +#endif''') +assert s.count(old_inc) == 1 +s = s.replace(old_inc, new_inc) + +# 2) pump_for: wrap body with SEH + param handler +old_pf = crlf('''static void pump_for(int secs) { + auto t0 = std::chrono::steady_clock::now(); + while (std::chrono::duration_cast( + std::chrono::steady_clock::now() - t0).count() < secs) { + MSG msg; + while (PeekMessageW(&msg, nullptr, 0, 0, PM_REMOVE)) { + TranslateMessage(&msg); + DispatchMessageW(&msg); + } + Sleep(5); + } +}''') +new_pf = crlf('''static void pump_for(int secs) { +#ifdef _WIN32 + _set_invalid_parameter_handler(probe_invalid_param); +#endif + auto t0 = std::chrono::steady_clock::now(); + while (std::chrono::duration_cast( + std::chrono::steady_clock::now() - t0).count() < secs) { + MSG msg; + while (PeekMessageW(&msg, nullptr, 0, 0, PM_REMOVE)) { + TranslateMessage(&msg); + DispatchMessageW(&msg); + } + Sleep(5); + } +}''') +assert s.count(old_pf) == 1 +s = s.replace(old_pf, new_pf) + +io.open(p, 'w', encoding='utf-8', newline='').write(s) +print('patched') diff --git a/native_bridge/debug/patch_main_pump.py b/native_bridge/debug/patch_main_pump.py new file mode 100644 index 0000000..9d51e2f --- /dev/null +++ b/native_bridge/debug/patch_main_pump.py @@ -0,0 +1,62 @@ +import io +p = r'C:\Users\locpham\SonicForgeStudio\native_bridge\src\gui_probe.cpp' +s = io.open(p, encoding='utf-8', newline='').read() + +def crlf(x): return x.replace('\n', '\r\n') + +old = crlf(''' auto t0 = std::chrono::steady_clock::now(); + while (!done.load() && std::chrono::duration_cast( + std::chrono::steady_clock::now() - t0).count() < secs) { + MSG msg; + while (PeekMessageW(&msg, nullptr, 0, 0, PM_REMOVE)) { + TranslateMessage(&msg); + DispatchMessageW(&msg); + } + Sleep(2); + }''') +new = crlf(''' auto t0 = std::chrono::steady_clock::now(); + try { + while (!done.load() && std::chrono::duration_cast( + std::chrono::steady_clock::now() - t0).count() < secs) { + MSG msg; + while (PeekMessageW(&msg, nullptr, 0, 0, PM_REMOVE)) { + TranslateMessage(&msg); + DispatchMessageW(&msg); + } + Sleep(2); + } + } catch (const std::exception& e) { printf("[main] PUMP1 EXCEPTION: %s\\n", e.what()); fflush(stdout); } + catch (...) { printf("[main] PUMP1 UNKNOWN EXCEPTION\\n"); fflush(stdout); }''') +n = s.count(old) +print('pump1 count:', n) +s = s.replace(old, new) + +old2 = crlf(''' auto t1 = std::chrono::steady_clock::now(); + while (std::chrono::duration_cast( + std::chrono::steady_clock::now() - t1).count() < 5) { + MSG msg; + while (PeekMessageW(&msg, nullptr, 0, 0, PM_REMOVE)) { + TranslateMessage(&msg); + DispatchMessageW(&msg); + } + Sleep(2); + }''') +new2 = crlf(''' auto t1 = std::chrono::steady_clock::now(); + try { + while (std::chrono::duration_cast( + std::chrono::steady_clock::now() - t1).count() < 5) { + MSG msg; + while (PeekMessageW(&msg, nullptr, 0, 0, PM_REMOVE)) { + TranslateMessage(&msg); + DispatchMessageW(&msg); + } + Sleep(2); + } + } catch (const std::exception& e) { printf("[main] PUMP2 EXCEPTION: %s\\n", e.what()); fflush(stdout); } + catch (...) { printf("[main] PUMP2 UNKNOWN EXCEPTION\\n"); fflush(stdout); }''') +n2 = s.count(old2) +print('pump2 count:', n2) +s = s.replace(old2, new2) + +io.open(p, 'w', encoding='utf-8', newline='').write(s) +print('written') diff --git a/native_bridge/debug/patch_msglog.py b/native_bridge/debug/patch_msglog.py new file mode 100644 index 0000000..a707321 --- /dev/null +++ b/native_bridge/debug/patch_msglog.py @@ -0,0 +1,41 @@ +import io +p = r'C:\Users\locpham\SonicForgeStudio\native_bridge\src\gui_probe.cpp' +s = io.open(p, encoding='utf-8', newline='').read() +def crlf(x): return x.replace('\n', '\r\n') + +old = crlf(''' MSG msg; + while (PeekMessageW(&msg, nullptr, 0, 0, PM_REMOVE)) { + TranslateMessage(&msg); + DispatchMessageW(&msg); + } + Sleep(5);''') +new = crlf(''' MSG msg; + while (PeekMessageW(&msg, nullptr, 0, 0, PM_REMOVE)) { + printf("[pump] msg=0x%X hwnd=%p\\n", (unsigned)msg.message, (void*)msg.hwnd); + fflush(stdout); + TranslateMessage(&msg); + DispatchMessageW(&msg); + } + Sleep(5);''') +n = s.count(old) +print('pump_for match:', n) +s = s.replace(old, new) + +old2 = crlf(''' MSG msg; + while (PeekMessageW(&msg, nullptr, 0, 0, PM_REMOVE)) { + TranslateMessage(&msg); + DispatchMessageW(&msg); + }''') +new2 = crlf(''' MSG msg; + while (PeekMessageW(&msg, nullptr, 0, 0, PM_REMOVE)) { + printf("[wpump] msg=0x%X hwnd=%p\\n", (unsigned)msg.message, (void*)msg.hwnd); + fflush(stdout); + TranslateMessage(&msg); + DispatchMessageW(&msg); + }''') +n2 = s.count(old2) +print('worker pump match:', n2) +s = s.replace(old2, new2) + +io.open(p, 'w', encoding='utf-8', newline='').write(s) +print('written') diff --git a/native_bridge/debug/probe_app4_live.py b/native_bridge/debug/probe_app4_live.py new file mode 100644 index 0000000..068f089 --- /dev/null +++ b/native_bridge/debug/probe_app4_live.py @@ -0,0 +1,132 @@ +# Live repro of the GUI-open bug in the RUNNING app: load 6 Nexus + EZkeys, +# transport PLAY, then OPEN_GUI ch=5 (Nexus) — exactly the frontend sequence. +# Usage: python probe_app4_live.py +import ctypes, ctypes.wintypes as w, struct, subprocess, sys, os, time, re + +sys.stdout.reconfigure(encoding='utf-8', errors='replace') + +APP_PID = 22264 # sonicforge-daw.exe (SHM name is per-PID) +SHM_NAME = f"SonicForge_DAW_IPC_{APP_PID}".encode() +SHM_NAME_LEGACY = b"SonicForge_DAW_IPC" # installed build still uses legacy name +APP_LOG = os.path.join(os.environ.get('APPDATA', '.'), 'SonicForgeDAW', 'logs', 'bridge.log') +VST = r"C:\Program Files\Common Files\VST3" +NEXUS = os.path.join(VST, "Nexus.vst3") +EZKEYS = os.path.join(VST, "Toontrack", "EZkeys 2.vst3") + +kernel32 = ctypes.WinDLL('kernel32', use_last_error=True) +kernel32.OpenFileMappingA.argtypes = [ctypes.wintypes.DWORD, w.BOOL, ctypes.c_char_p] +kernel32.OpenFileMappingA.restype = ctypes.c_void_p +kernel32.MapViewOfFile.argtypes = [ctypes.c_void_p, ctypes.wintypes.DWORD, ctypes.wintypes.DWORD, ctypes.wintypes.DWORD, ctypes.c_size_t] +kernel32.MapViewOfFile.restype = ctypes.c_void_p +FILE_MAP_ALL_ACCESS = 0xF001F + +hMap = kernel32.OpenFileMappingA(FILE_MAP_ALL_ACCESS, False, SHM_NAME) +name_used = SHM_NAME +if not hMap: + print(f"OpenFileMapping({SHM_NAME}) failed err={ctypes.get_last_error()}, trying legacy...") + hMap = kernel32.OpenFileMappingA(FILE_MAP_ALL_ACCESS, False, SHM_NAME_LEGACY) + name_used = SHM_NAME_LEGACY +if not hMap: + print(f"OpenFileMapping({SHM_NAME_LEGACY}) failed err={ctypes.get_last_error()}") + sys.exit(1) +ptr = kernel32.MapViewOfFile(hMap, FILE_MAP_ALL_ACCESS, 0, 0, 0) +if not ptr: + print("MapViewOfFile failed", ctypes.get_last_error()); sys.exit(1) +print(f"SHM {name_used} mapped") + +def w32(off, fmt, *args): + ctypes.memmove(ptr + off, struct.pack(fmt, *args), struct.calcsize(fmt)) + +def rd32(off): + return ctypes.c_uint32.from_address(ptr + off).value + +def write_control(ctype, arg0, arg1, ch, path): + for i in range(8): + off = 2836 + i * 1040 + if rd32(off) == 0: + w32(off, 'IIII', ctype, arg0, arg1, ch) + if path: + b = path.encode('utf-8')[:1023] + b'\x00' + ctypes.memmove(ptr + off + 16, b, len(b)) + return i + raise RuntimeError("control queue full") + +def push_controls(events): + for i in range(8): + w32(2836 + i * 1040, 'I', 0) + for (ctype, arg0, arg1, ch, path) in events: + write_control(ctype, arg0, arg1, ch, path) + w32(11156, 'I', len(events)) + +def log_lines(): + try: + with open(APP_LOG, 'r', encoding='utf-8', errors='replace') as f: + return f.readlines() + except FileNotFoundError: + return [] + +def wait_log(pattern, timeout, since): + end = time.time() + timeout + while time.time() < end: + lines = log_lines() + for i in range(since, len(lines)): + if pattern in lines[i]: + return lines[i].strip() + time.sleep(0.2) + return None + +def bridge_pid(): + try: + out = subprocess.run(["tasklist", "/FI", "IMAGENAME eq daw_vst_bridge.exe", "/FO", "CSV"], + capture_output=True, text=True).stdout + for line in out.splitlines()[1:]: + if "daw_vst_bridge" in line: + return line.split(',')[1].strip('"') + except Exception: + pass + return None + +# 1) LOAD 6 Nexus ch0-5 + EZkeys ch6 (restore session state after bridge restart) +since = len(log_lines()) +evs = [(2, 0, 0, ch, NEXUS) for ch in range(6)] + [(2, 0, 0, 6, EZKEYS)] +push_controls(evs) +print("pushed LOAD x7; waiting for instruments...") +loaded = [] +for ch in range(7): + r = wait_log(f"instrument loaded ch={ch}", 60, since) + loaded.append(r or "TIMEOUT") + print(f" ch={ch}: {('loaded' if r else 'TIMEOUT')}") +# 2) TRANSPORT PLAY +since = len(log_lines()) +push_controls([(3, 1, 0, 0, "")]) +r = wait_log("transport PLAY", 10, since) +print("transport:", r or "TIMEOUT") +# 3) OPEN_GUI ch=5 (Nexus) while playing — same args as frontend open_vst_gui +since = len(log_lines()) +push_controls([(4, 0, 0, 5, "Nexus")]) +print("pushed OPEN_GUI ch=5 plugin=Nexus; watching 40s...") +pid0 = bridge_pid() +t0 = time.time() +wi0 = rd32(4) +last_wi, last_t = wi0, t0 +stall_started = None +events = [] +while time.time() - t0 < 40: + wi = rd32(4) + if wi != last_wi: + last_wi, last_t = wi, time.time() + stall_started = None + elif stall_started is None and time.time() - last_t > 3.0: + stall_started = time.time() - last_t + print(f" [{time.time()-t0:.1f}s] bridge_write_index STALL {wi} since +{time.time()-last_t:.1f}s") + pid = bridge_pid() + if pid and pid != pid0: + print(f" [{time.time()-t0:.1f}s] BRIDGE RESTARTED pid {pid0} -> {pid}") + pid0 = pid + lines = log_lines() + for i in range(since, len(lines)): + l = lines[i].strip() + if any(k in l for k in ("EXCEPTION", "FAILED", "attached", "deferred", "restarted", "Starting DAW")): + print(f" [{time.time()-t0:.1f}s] LOG: {l}") + time.sleep(0.2) +print("done. write_index advanced:", last_wi, ">", wi0) diff --git a/native_bridge/debug/probe_attach_detail.py b/native_bridge/debug/probe_attach_detail.py new file mode 100644 index 0000000..68871cf --- /dev/null +++ b/native_bridge/debug/probe_attach_detail.py @@ -0,0 +1,33 @@ +# probe_attach_detail.py - targeted runs to distinguish crash-in-attach vs +# teardown for gui_probe. Prints FULL stdout + real returncode. +import subprocess, sys, time, os + +sys.stdout.reconfigure(encoding="utf-8", errors="replace") +PROBE = r"C:\Users\locpham\SonicForgeStudio\native_bridge\build\Release\gui_probe.exe" + +CASES = [ + # (label, plugin, variant, secs) + ("nexus_bridge_two", r"C:\Program Files\Common Files\VST3\Nexus.vst3", "bridge_two", 8), + ("nexus_bridge_two_25s",r"C:\Program Files\Common Files\VST3\Nexus.vst3", "bridge_two", 25), + ("nexus_same_thread", r"C:\Program Files\Common Files\VST3\Nexus.vst3", "same_thread", 8), + ("nexus_main_own", r"C:\Program Files\Common Files\VST3\Nexus.vst3", "main_own", 8), + ("scaler2_bridge_two", r"C:\Program Files\Common Files\VST3\Scaler2.vst3", "bridge_two", 8), + ("ezkeys_bridge_two", r"C:\Program Files\Common Files\VST3\Toontrack\EZkeys 2.vst3", "bridge_two", 8), + ("kontakt_bridge_two", r"C:\Program Files\Common Files\VST3\Kontakt.vst3", "bridge_two", 8), + ("kontakt_main_own", r"C:\Program Files\Common Files\VST3\Kontakt.vst3", "main_own", 8), + ("nexus_two_workers_close", r"C:\Program Files\Common Files\VST3\Nexus.vst3", "two_workers_close", 8), +] + +for label, plugin, variant, secs in CASES: + t0 = time.time() + print(f"\n===== {label} ({variant}, {secs}s) =====", flush=True) + try: + r = subprocess.run([PROBE, plugin, variant, str(secs)], + capture_output=True, text=True, timeout=secs + 25, + creationflags=subprocess.CREATE_NO_WINDOW) + out = (r.stdout or "") + (r.stderr or "") + print(f"returncode={r.returncode} ({r.returncode & 0xFFFFFFFF:08X}) secs={time.time()-t0:.1f}") + print(out) + except subprocess.TimeoutExpired: + print(f"HARD-TIMEOUT after {time.time()-t0:.1f}s") + print("-----", flush=True) diff --git a/native_bridge/debug/probe_g01_live.py b/native_bridge/debug/probe_g01_live.py new file mode 100644 index 0000000..c65d3f0 --- /dev/null +++ b/native_bridge/debug/probe_g01_live.py @@ -0,0 +1,127 @@ +# G0.1 verification: PLAY -> OPEN_GUI must be DEFERRED (not attach-while-playing); +# after STOP the pending GUI must attach OK. No EXCEPTION/FAILED, no watchdog kill. +import ctypes, ctypes.wintypes as w, struct, subprocess, sys, os, time + +sys.stdout.reconfigure(encoding='utf-8', errors='replace') + +APP_PID = 25488 # sonicforge-daw.exe (update per run) +SHM_NAME = f"SonicForge_DAW_IPC_{APP_PID}".encode() +SHM_NAME_LEGACY = b"SonicForge_DAW_IPC" +APP_LOG = os.path.join(os.environ.get('APPDATA', '.'), 'SonicForgeDAW', 'logs', 'bridge.log') +VST = r"C:\Program Files\Common Files\VST3" +NEXUS = os.path.join(VST, "Nexus.vst3") +EZKEYS = os.path.join(VST, "Toontrack", "EZkeys 2.vst3") + +kernel32 = ctypes.WinDLL('kernel32', use_last_error=True) +kernel32.OpenFileMappingA.argtypes = [ctypes.wintypes.DWORD, w.BOOL, ctypes.c_char_p] +kernel32.OpenFileMappingA.restype = ctypes.c_void_p +kernel32.MapViewOfFile.argtypes = [ctypes.c_void_p, ctypes.wintypes.DWORD, ctypes.wintypes.DWORD, ctypes.wintypes.DWORD, ctypes.c_size_t] +kernel32.MapViewOfFile.restype = ctypes.c_void_p +FILE_MAP_ALL_ACCESS = 0xF001F + +hMap = kernel32.OpenFileMappingA(FILE_MAP_ALL_ACCESS, False, SHM_NAME) +name_used = SHM_NAME +if not hMap: + print(f"OpenFileMapping({SHM_NAME}) failed err={ctypes.get_last_error()}, trying legacy...") + hMap = kernel32.OpenFileMappingA(FILE_MAP_ALL_ACCESS, False, SHM_NAME_LEGACY) + name_used = SHM_NAME_LEGACY +if not hMap: + print(f"OpenFileMapping({SHM_NAME_LEGACY}) failed err={ctypes.get_last_error()}") + sys.exit(1) +ptr = kernel32.MapViewOfFile(hMap, FILE_MAP_ALL_ACCESS, 0, 0, 0) +if not ptr: + print("MapViewOfFile failed", ctypes.get_last_error()); sys.exit(1) +print(f"SHM {name_used} mapped") + +def w32(off, fmt, *args): + ctypes.memmove(ptr + off, struct.pack(fmt, *args), struct.calcsize(fmt)) + +def rd32(off): + return ctypes.c_uint32.from_address(ptr + off).value + +def write_control(ctype, arg0, arg1, ch, path): + for i in range(8): + off = 2836 + i * 1040 + if rd32(off) == 0: + w32(off, 'IIII', ctype, arg0, arg1, ch) + if path: + b = path.encode('utf-8')[:1023] + b'\x00' + ctypes.memmove(ptr + off + 16, b, len(b)) + return i + raise RuntimeError("control queue full") + +def push_controls(events): + for i in range(8): + w32(2836 + i * 1040, 'I', 0) + for (ctype, arg0, arg1, ch, path) in events: + write_control(ctype, arg0, arg1, ch, path) + w32(11156, 'I', len(events)) + +def log_lines(): + try: + with open(APP_LOG, 'r', encoding='utf-8', errors='replace') as f: + return f.readlines() + except FileNotFoundError: + return [] + +def wait_log(pattern, timeout, since, verbose=True): + end = time.time() + timeout + while time.time() < end: + lines = log_lines() + for i in range(since, len(lines)): + if pattern in lines[i]: + if verbose: print(f" LOG: {lines[i].strip()}") + return lines[i].strip() + time.sleep(0.2) + return None + +def bridge_pid(): + try: + out = subprocess.run(["cmd", "/c", "tasklist /FI \"IMAGENAME eq daw_vst_bridge.exe\" /FO CSV"], + capture_output=True, text=True).stdout + for line in out.splitlines()[1:]: + if "daw_vst_bridge" in line: + return line.split(',')[1].strip('"') + except Exception: + pass + return None + +# 1) LOAD 6 Nexus ch0-5 + EZkeys ch6 +since = len(log_lines()) +push_controls([(2, 0, 0, ch, NEXUS) for ch in range(6)] + [(2, 0, 0, 6, EZKEYS)]) +print("pushed LOAD x7; waiting for instruments...") +for ch in range(7): + r = wait_log(f"instrument loaded ch={ch}", 60, since) + print(f" ch={ch}: {'loaded' if r else 'TIMEOUT'}") +# 2) TRANSPORT PLAY +since = len(log_lines()) +push_controls([(3, 1, 0, 0, "")]) +r = wait_log("transport PLAY", 10, since) +print("transport:", r or "TIMEOUT") +# 3) OPEN_GUI ch=5 while PLAYING -> expect DEFERRED +since = len(log_lines()) +push_controls([(4, 0, 0, 5, "Nexus")]) +r = wait_log("GUI deferred ch=5", 10, since) +print("G0.1 defer:", r or "TIMEOUT - KHONG DEFER (bug chua fix)") +pid0 = bridge_pid() +# 4) TRANSPORT STOP -> pending GUI should attach now +since = len(log_lines()) +push_controls([(3, 0, 0, 0, "")]) +r = wait_log("transport STOP", 10, since) +print("transport:", r or "TIMEOUT") +r = wait_log("GUI attached hwnd=", 30, since) +print("G0.1 attach after STOP:", r or "TIMEOUT - attach khong xay ra") +# 5) watch 10s for EXCEPTION/FAILED/restart +since = len(log_lines()) +end = time.time() + 10 +bad = [] +while time.time() < end: + for l in log_lines()[since:]: + if any(k in l for k in ("EXCEPTION", "FAILED", "restarted")): + bad.append(l.strip()) + pid = bridge_pid() + if pid != pid0: + bad.append(f"BRIDGE RESTART {pid0}->{pid}") + break + time.sleep(0.2) +print("G0.1 RESULT:", "PASS - khong loi, attach OK sau STOP" if not bad else "FAIL:\n" + "\n".join(bad[:10])) diff --git a/native_bridge/debug/probe_g01_testA_stop.py b/native_bridge/debug/probe_g01_testA_stop.py new file mode 100644 index 0000000..3b5c062 --- /dev/null +++ b/native_bridge/debug/probe_g01_testA_stop.py @@ -0,0 +1,109 @@ +# Test A: load 6 Nexus + EZkeys, KHONG PLAY, OPEN_GUI ch5 -> attach co OK khong? +import ctypes, ctypes.wintypes as w, struct, subprocess, sys, os, time + +sys.stdout.reconfigure(encoding='utf-8', errors='replace') + +APP_PID = 23760 +SHM_NAME = f"SonicForge_DAW_IPC_{APP_PID}".encode() +SHM_NAME_LEGACY = b"SonicForge_DAW_IPC" +APP_LOG = os.path.join(os.environ.get('APPDATA', '.'), 'SonicForgeDAW', 'logs', 'bridge.log') +VST = r"C:\Program Files\Common Files\VST3" +NEXUS = os.path.join(VST, "Nexus.vst3") +EZKEYS = os.path.join(VST, "Toontrack", "EZkeys 2.vst3") + +kernel32 = ctypes.WinDLL('kernel32', use_last_error=True) +kernel32.OpenFileMappingA.argtypes = [ctypes.wintypes.DWORD, w.BOOL, ctypes.c_char_p] +kernel32.OpenFileMappingA.restype = ctypes.c_void_p +kernel32.MapViewOfFile.argtypes = [ctypes.c_void_p, ctypes.wintypes.DWORD, ctypes.wintypes.DWORD, ctypes.wintypes.DWORD, ctypes.c_size_t] +kernel32.MapViewOfFile.restype = ctypes.c_void_p +FILE_MAP_ALL_ACCESS = 0xF001F + +hMap = kernel32.OpenFileMappingA(FILE_MAP_ALL_ACCESS, False, SHM_NAME) +name_used = SHM_NAME +if not hMap: + hMap = kernel32.OpenFileMappingA(FILE_MAP_ALL_ACCESS, False, SHM_NAME_LEGACY) + name_used = SHM_NAME_LEGACY +if not hMap: + print("no SHM", ctypes.get_last_error()); sys.exit(1) +ptr = kernel32.MapViewOfFile(hMap, FILE_MAP_ALL_ACCESS, 0, 0, 0) +if not ptr: + print("MapViewOfFile failed", ctypes.get_last_error()); sys.exit(1) +print(f"SHM {name_used} mapped") + +def w32(off, fmt, *args): + ctypes.memmove(ptr + off, struct.pack(fmt, *args), struct.calcsize(fmt)) + +def rd32(off): + return ctypes.c_uint32.from_address(ptr + off).value + +def write_control(ctype, arg0, arg1, ch, path): + for i in range(8): + off = 2836 + i * 1040 + if rd32(off) == 0: + w32(off, 'IIII', ctype, arg0, arg1, ch) + if path: + b = path.encode('utf-8')[:1023] + b'\x00' + ctypes.memmove(ptr + off + 16, b, len(b)) + return i + raise RuntimeError("control queue full") + +def push_controls(events): + for i in range(8): + w32(2836 + i * 1040, 'I', 0) + for (ctype, arg0, arg1, ch, path) in events: + write_control(ctype, arg0, arg1, ch, path) + w32(11156, 'I', len(events)) + +def log_lines(): + try: + with open(APP_LOG, 'r', encoding='utf-8', errors='replace') as f: + return f.readlines() + except FileNotFoundError: + return [] + +def wait_log(pattern, timeout, since, verbose=True): + end = time.time() + timeout + while time.time() < end: + lines = log_lines() + for i in range(since, len(lines)): + if pattern in lines[i]: + if verbose: print(f" LOG: {lines[i].strip()}") + return lines[i].strip() + time.sleep(0.2) + return None + +def bridge_pid(): + try: + out = subprocess.run(["cmd", "/c", "tasklist /FI \"IMAGENAME eq daw_vst_bridge.exe\" /FO CSV"], + capture_output=True, text=True).stdout + for line in out.splitlines()[1:]: + if "daw_vst_bridge" in line: + return line.split(',')[1].strip('"') + except Exception: + pass + return None + +since = len(log_lines()) +push_controls([(2, 0, 0, ch, NEXUS) for ch in range(6)] + [(2, 0, 0, 6, EZKEYS)]) +print("pushed LOAD x7; waiting...") +for ch in range(7): + r = wait_log(f"instrument loaded ch={ch}", 60, since) + print(f" ch={ch}: {'loaded' if r else 'TIMEOUT'}") +print("Khong PLAY - mo GUI ch5 ngay (transport stopped)") +since = len(log_lines()) +push_controls([(4, 0, 0, 5, "Nexus")]) +pid0 = bridge_pid() +r = wait_log("GUI attached hwnd=", 40, since) +print("ATTACH:", r or "TIMEOUT - GUI khong attach") +end = time.time() + 8 +bad = [] +while time.time() < end: + for l in log_lines()[since:]: + if any(k in l for k in ("EXCEPTION", "FAILED", "restarted")): + bad.append(l.strip()) + pid = bridge_pid() + if pid != pid0: + bad.append(f"BRIDGE RESTART {pid0}->{pid}") + break + time.sleep(0.2) +print("RESULT:", "PASS - attach OK khi STOP, khong loi" if (r and not bad) else "FAIL: " + "; ".join(bad[:5]) if bad else "FAIL - attach TIMEOUT") diff --git a/native_bridge/debug/probe_g03_diag.py b/native_bridge/debug/probe_g03_diag.py new file mode 100644 index 0000000..e891a62 --- /dev/null +++ b/native_bridge/debug/probe_g03_diag.py @@ -0,0 +1,147 @@ +# G0.3 diagnosis: track attach_in_flight flag + writeIndex over LOAD->PLAY->OPEN_GUI->STOP. +# Determines whether watchdog killed at 3s (flag not seen) or attach hung >15s (flag set). +import ctypes, ctypes.wintypes as w, struct, subprocess, sys, os, time + +sys.stdout.reconfigure(encoding='utf-8', errors='replace') + +APP_PID = 9692 +SHM_NAME = f"SonicForge_DAW_IPC_{APP_PID}".encode() +SHM_NAME_LEGACY = b"SonicForge_DAW_IPC" +APP_LOG = os.path.join(os.environ.get('APPDATA', '.'), 'SonicForgeDAW', 'logs', 'bridge.log') +VST = r"C:\Program Files\Common Files\VST3" +NEXUS = os.path.join(VST, "Nexus.vst3") +EZKEYS = os.path.join(VST, "Toontrack", "EZkeys 2.vst3") + +kernel32 = ctypes.WinDLL('kernel32', use_last_error=True) +kernel32.OpenFileMappingA.argtypes = [ctypes.wintypes.DWORD, w.BOOL, ctypes.c_char_p] +kernel32.OpenFileMappingA.restype = ctypes.c_void_p +kernel32.MapViewOfFile.argtypes = [ctypes.c_void_p, ctypes.wintypes.DWORD, ctypes.wintypes.DWORD, ctypes.wintypes.DWORD, ctypes.c_size_t] +kernel32.MapViewOfFile.restype = ctypes.c_void_p +FILE_MAP_ALL_ACCESS = 0xF001F + +hMap = kernel32.OpenFileMappingA(FILE_MAP_ALL_ACCESS, False, SHM_NAME) +name_used = SHM_NAME +if not hMap: + hMap = kernel32.OpenFileMappingA(FILE_MAP_ALL_ACCESS, False, SHM_NAME_LEGACY) + name_used = SHM_NAME_LEGACY +if not hMap: + print(f"OpenFileMapping failed err={ctypes.get_last_error()}"); sys.exit(1) +ptr = kernel32.MapViewOfFile(hMap, FILE_MAP_ALL_ACCESS, 0, 0, 0) +if not ptr: + print("MapViewOfFile failed", ctypes.get_last_error()); sys.exit(1) +print(f"SHM {name_used} mapped") + +def w32(off, fmt, *args): + ctypes.memmove(ptr + off, struct.pack(fmt, *args), struct.calcsize(fmt)) + +def rd32(off): + return ctypes.c_uint32.from_address(ptr + off).value + +def write_control(ctype, arg0, arg1, ch, path): + for i in range(8): + off = 2836 + i * 1040 + if rd32(off) == 0: + w32(off, 'IIII', ctype, arg0, arg1, ch) + if path: + b = path.encode('utf-8')[:1023] + b'\x00' + ctypes.memmove(ptr + off + 16, b, len(b)) + return i + raise RuntimeError("control queue full") + +def push_controls(events): + for i in range(8): + w32(2836 + i * 1040, 'I', 0) + for (ctype, arg0, arg1, ch, path) in events: + write_control(ctype, arg0, arg1, ch, path) + w32(11156, 'I', len(events)) + +def log_lines(): + try: + with open(APP_LOG, 'r', encoding='utf-8', errors='replace') as f: + return f.readlines() + except FileNotFoundError: + return [] + +def wait_log(pattern, timeout, since, verbose=True): + end = time.time() + timeout + while time.time() < end: + lines = log_lines() + for i in range(since, len(lines)): + if pattern in lines[i]: + if verbose: print(f" LOG: {lines[i].strip()}") + return lines[i].strip() + time.sleep(0.2) + return None + +def bridge_pid(): + try: + out = subprocess.run(["cmd", "/c", "tasklist /FI \"IMAGENAME eq daw_vst_bridge.exe\" /FO CSV"], + capture_output=True, text=True).stdout + for line in out.splitlines()[1:]: + if "daw_vst_bridge" in line: + return line.split(',')[1].strip('"') + except Exception: + pass + return None + +OFF_ATTACH = 11160 # attach_in_flight (u32, appended after control_queue_count @11156) +OFF_WRITE = 4 # bridge_write_index + +since = len(log_lines()) +push_controls([(2, 0, 0, ch, NEXUS) for ch in range(6)] + [(2, 0, 0, 6, EZKEYS)]) +print("pushed LOAD x7; waiting...") +for ch in range(7): + r = wait_log(f"instrument loaded ch={ch}", 60, since) + print(f" ch={ch}: {'loaded' if r else 'TIMEOUT'}") + +since = len(log_lines()) +push_controls([(3, 1, 0, 0, "")]) +wait_log("transport PLAY", 10, since) + +since = len(log_lines()) +push_controls([(4, 0, 0, 5, "Nexus")]) +wait_log("GUI deferred ch=5", 10, since) + +pid0 = bridge_pid() +print(f"bridge pid before STOP: {pid0}") + +# STOP then track writeIndex + attach flag every 100ms for 40s +since = len(log_lines()) +push_controls([(3, 0, 0, 0, "")]) +wait_log("transport STOP", 10, since) +print(f"{'t(s)':>5} {'wIdx':>8} {'attach':>6} events") +t0 = time.time() +last_w = rd32(OFF_WRITE) +stall_t = None +attached_seen = False +end = time.time() + 40 +while time.time() < end: + w = rd32(OFF_WRITE) + a = rd32(OFF_ATTACH) + t = time.time() - t0 + if w != last_w: + if stall_t is not None: + print(f"{t:5.1f} {w:8d} {a:6d} writeIndex resumed after {t - stall_t:.1f}s stall") + stall_t = None + last_w = w + else: + if stall_t is None: + stall_t = t + print(f"{t:5.1f} {w:8d} {a:6d} WRITEINDEX STALL START") + if a == 1 and (not hasattr(sys, '_a_was')): + print(f"{t:5.1f} {w:8d} {a:6d} attach_in_flight SET") + if a == 0 and stall_t is not None and not attached_seen: + pass + # log check + for l in log_lines()[since:]: + if "GUI attached hwnd=" in l and not attached_seen: + attached_seen = True + print(f"{t:5.1f} --- GUI ATTACHED: {l.strip()}") + if "bridge restarted" in l: + print(f"{t:5.1f} --- BRIDGE RESTARTED IN LOG") + pid = bridge_pid() + if pid != pid0: + print(f"{t:5.1f} --- BRIDGE PID CHANGED {pid0}->{pid}") + break + time.sleep(0.1) +print("done. final attach_in_flight =", rd32(OFF_ATTACH), " writeIndex =", rd32(OFF_WRITE)) diff --git a/native_bridge/debug/probe_g04_preset.py b/native_bridge/debug/probe_g04_preset.py new file mode 100644 index 0000000..df0a053 --- /dev/null +++ b/native_bridge/debug/probe_g04_preset.py @@ -0,0 +1,175 @@ +# G0.4 verification: close GUI -> preset save/restore around reload(). +# Flow: LOAD ch5 Nexus -> OPEN_GUI -> WM_CLOSE -> expect "state captured ch=5" +# -> OPEN_GUI again -> expect "state restored ch=5" + "GUI attached" (2nd time). +# No EXCEPTION/FAILED/bridge restart. +import ctypes, ctypes.wintypes as w, struct, subprocess, sys, os, time, re + +sys.stdout.reconfigure(encoding='utf-8', errors='replace') + +APP_PID = 25488 # sonicforge-daw.exe (update per run) +SHM_NAME = f"SonicForge_DAW_IPC_{APP_PID}".encode() +SHM_NAME_LEGACY = b"SonicForge_DAW_IPC" +APP_LOG = os.path.join(os.environ.get('APPDATA', '.'), 'SonicForgeDAW', 'logs', 'bridge.log') +VST = r"C:\Program Files\Common Files\VST3" +NEXUS = os.path.join(VST, "Nexus.vst3") + +kernel32 = ctypes.WinDLL('kernel32', use_last_error=True) +kernel32.OpenFileMappingA.argtypes = [ctypes.wintypes.DWORD, w.BOOL, ctypes.c_char_p] +kernel32.OpenFileMappingA.restype = ctypes.c_void_p +kernel32.MapViewOfFile.argtypes = [ctypes.c_void_p, ctypes.wintypes.DWORD, ctypes.wintypes.DWORD, ctypes.wintypes.DWORD, ctypes.c_size_t] +kernel32.MapViewOfFile.restype = ctypes.c_void_p +FILE_MAP_ALL_ACCESS = 0xF001F + +hMap = kernel32.OpenFileMappingA(FILE_MAP_ALL_ACCESS, False, SHM_NAME) +name_used = SHM_NAME +if not hMap: + print(f"OpenFileMapping({SHM_NAME}) failed err={ctypes.get_last_error()}, trying legacy...") + hMap = kernel32.OpenFileMappingA(FILE_MAP_ALL_ACCESS, False, SHM_NAME_LEGACY) + name_used = SHM_NAME_LEGACY +if not hMap: + print(f"OpenFileMapping({SHM_NAME_LEGACY}) failed err={ctypes.get_last_error()}") + sys.exit(1) +ptr = kernel32.MapViewOfFile(hMap, FILE_MAP_ALL_ACCESS, 0, 0, 0) +if not ptr: + print("MapViewOfFile failed", ctypes.get_last_error()); sys.exit(1) +print(f"SHM {name_used} mapped") + +def w32(off, fmt, *args): + ctypes.memmove(ptr + off, struct.pack(fmt, *args), struct.calcsize(fmt)) + +def rd32(off): + return ctypes.c_uint32.from_address(ptr + off).value + +def write_control(ctype, arg0, arg1, ch, path): + for i in range(8): + off = 2836 + i * 1040 + if rd32(off) == 0: + w32(off, 'IIII', ctype, arg0, arg1, ch) + if path: + b = path.encode('utf-8')[:1023] + b'\x00' + ctypes.memmove(ptr + off + 16, b, len(b)) + return i + raise RuntimeError("control queue full") + +def push_controls(events): + for i in range(8): + w32(2836 + i * 1040, 'I', 0) + for (ctype, arg0, arg1, ch, path) in events: + write_control(ctype, arg0, arg1, ch, path) + w32(11156, 'I', len(events)) + +def log_lines(): + try: + with open(APP_LOG, 'r', encoding='utf-8', errors='replace') as f: + return f.readlines() + except FileNotFoundError: + return [] + +def wait_log(pattern, timeout, since, verbose=True): + end = time.time() + timeout + while time.time() < end: + lines = log_lines() + for i in range(since, len(lines)): + if pattern in lines[i]: + if verbose: print(f" LOG: {lines[i].strip()}") + return lines[i].strip() + time.sleep(0.2) + return None + +def bridge_pid(): + try: + r = subprocess.run(['tasklist'], capture_output=True) + txt = r.stdout.decode('utf-8', 'replace') + for line in txt.splitlines(): + if 'daw_vst_bridge' in line.lower(): + return line.split()[1] + except Exception: + pass + return None + +def watch_restart(since, pid0, seconds=15): + end = time.time() + seconds + while time.time() < end: + for l in log_lines()[since:]: + if "bridge restarted" in l or "EXCEPTION" in l or "FAILED" in l: + return l.strip() + pid = bridge_pid() + if pid != pid0: + return f"BRIDGE PID CHANGE {pid0}->{pid}" + time.sleep(0.2) + return None + +for attempt in range(1, 4): + print(f"\n===== ATTEMPT {attempt} =====") + pid0 = bridge_pid() + print("bridge pid0:", pid0) + bad = [] + # 1) ensure ch=5 loaded + since = len(log_lines()) + push_controls([(2, 0, 0, 5, NEXUS)]) + r = wait_log("instrument loaded ch=5", 90, since) + print("LOAD ch5:", r or "TIMEOUT") + # 2) STOP transport (idle state) + since = len(log_lines()) + push_controls([(3, 0, 0, 0, "")]) + wait_log("transport STOP", 10, since) + # 3) OPEN_GUI ch=5 + since = len(log_lines()) + push_controls([(4, 0, 0, 5, "Nexus")]) + r = wait_log("GUI attached hwnd=", 60, since) + print("G0.4 open #1:", r or "TIMEOUT - attach khong xay ra") + m = re.search(r'hwnd=([0-9A-Fa-f]+)', r or "") + hwnd = int(m.group(1), 16) if m else None + restart = watch_restart(since, pid0) + if restart: + print(" restart during open #1:", restart); bad.append(restart); time.sleep(5); continue + if not hwnd: + bad.append("attach #1 timeout"); time.sleep(2); continue + time.sleep(3) + # 4) WM_CLOSE -> close job runs captureState -> reload -> restoreState + # (all 3 lines appear ~simultaneously; use one shared since window) + since = len(log_lines()) + ctypes.windll.user32.SendMessageW(hwnd, 0x0010, 0, 0) + print(f" sent WM_CLOSE hwnd={hwnd:#x}") + r = wait_log("state captured ch=5", 60, since) + print("G0.4 capture:", r or "TIMEOUT - khong captureState khi close GUI") + cap_line = r + if not r: bad.append("no capture") + restart = watch_restart(since, pid0) + if restart: + print(" restart during close:", restart); bad.append(restart); time.sleep(5); continue + # 5) reload + restore also inside the same window + r = wait_log("[Vst3Instrument] loaded", 60, since) + print("G0.4 reload after close:", r or "TIMEOUT - reload khong xay ra") + if not r: bad.append("no reload after close") + r = wait_log("state restored ch=5", 60, since) + print("G0.4 restore:", r or "TIMEOUT - khong restoreState khi reopen") + if not r: bad.append("no restore") + # same component size before/after = preset preserved + mcap = re.search(r'component=(\d+)B', cap_line or "") + mres = re.search(r'component=(\d+)B', r or "") + if mcap and mres and mcap.group(1) != mres.group(1): + bad.append(f"preset size diff capture={mcap.group(1)} restore={mres.group(1)}") + # 6) OPEN_GUI again -> attach succeeds after restore + since = len(log_lines()) + push_controls([(4, 0, 0, 5, "Nexus")]) + r = wait_log("GUI attached hwnd=", 60, since) + print("G0.4 open #2:", r or "TIMEOUT - attach lan 2 khong xay ra") + if not r: bad.append("attach #2 timeout") + restart = watch_restart(since, pid0) + if restart: + print(" restart during open #2:", restart); bad.append(restart); time.sleep(5); continue + # 7) watch 10s for EXCEPTION/FAILED/restart + since = len(log_lines()) + time.sleep(10) + for l in log_lines()[since:]: + if any(k in l for k in ("EXCEPTION", "FAILED", "bridge restarted")): + bad.append(l.strip()) + pid = bridge_pid() + if pid != pid0: + bad.append(f"BRIDGE RESTART {pid0}->{pid}") + print("G0.4 RESULT:", "PASS - save/restore preset OK, GUI reopen OK" if not bad else "FAIL:\n" + "\n".join(bad[:10])) + if not bad: + break +else: + print("\nALL ATTEMPTS FAILED - attach treo intermittent (bug G1), G0.4 chua verify duoc") diff --git a/native_bridge/debug/probe_g12_live.py b/native_bridge/debug/probe_g12_live.py new file mode 100644 index 0000000..78f5b82 --- /dev/null +++ b/native_bridge/debug/probe_g12_live.py @@ -0,0 +1,127 @@ +# G0.1 verification: PLAY -> OPEN_GUI must be DEFERRED (not attach-while-playing); +# after STOP the pending GUI must attach OK. No EXCEPTION/FAILED, no watchdog kill. +import ctypes, ctypes.wintypes as w, struct, subprocess, sys, os, time + +sys.stdout.reconfigure(encoding='utf-8', errors='replace') + +APP_PID = 30228 # sonicforge-daw.exe (update per run) +SHM_NAME = f"SonicForge_DAW_IPC_{APP_PID}".encode() +SHM_NAME_LEGACY = b"SonicForge_DAW_IPC" +APP_LOG = os.path.join(os.environ.get('APPDATA', '.'), 'SonicForgeDAW', 'logs', 'bridge.log') +VST = r"C:\Program Files\Common Files\VST3" +NEXUS = os.path.join(VST, "Nexus.vst3") +EZKEYS = os.path.join(VST, "Toontrack", "EZkeys 2.vst3") + +kernel32 = ctypes.WinDLL('kernel32', use_last_error=True) +kernel32.OpenFileMappingA.argtypes = [ctypes.wintypes.DWORD, w.BOOL, ctypes.c_char_p] +kernel32.OpenFileMappingA.restype = ctypes.c_void_p +kernel32.MapViewOfFile.argtypes = [ctypes.c_void_p, ctypes.wintypes.DWORD, ctypes.wintypes.DWORD, ctypes.wintypes.DWORD, ctypes.c_size_t] +kernel32.MapViewOfFile.restype = ctypes.c_void_p +FILE_MAP_ALL_ACCESS = 0xF001F + +hMap = kernel32.OpenFileMappingA(FILE_MAP_ALL_ACCESS, False, SHM_NAME) +name_used = SHM_NAME +if not hMap: + print(f"OpenFileMapping({SHM_NAME}) failed err={ctypes.get_last_error()}, trying legacy...") + hMap = kernel32.OpenFileMappingA(FILE_MAP_ALL_ACCESS, False, SHM_NAME_LEGACY) + name_used = SHM_NAME_LEGACY +if not hMap: + print(f"OpenFileMapping({SHM_NAME_LEGACY}) failed err={ctypes.get_last_error()}") + sys.exit(1) +ptr = kernel32.MapViewOfFile(hMap, FILE_MAP_ALL_ACCESS, 0, 0, 0) +if not ptr: + print("MapViewOfFile failed", ctypes.get_last_error()); sys.exit(1) +print(f"SHM {name_used} mapped") + +def w32(off, fmt, *args): + ctypes.memmove(ptr + off, struct.pack(fmt, *args), struct.calcsize(fmt)) + +def rd32(off): + return ctypes.c_uint32.from_address(ptr + off).value + +def write_control(ctype, arg0, arg1, ch, path): + for i in range(8): + off = 2836 + i * 1040 + if rd32(off) == 0: + w32(off, 'IIII', ctype, arg0, arg1, ch) + if path: + b = path.encode('utf-8')[:1023] + b'\x00' + ctypes.memmove(ptr + off + 16, b, len(b)) + return i + raise RuntimeError("control queue full") + +def push_controls(events): + for i in range(8): + w32(2836 + i * 1040, 'I', 0) + for (ctype, arg0, arg1, ch, path) in events: + write_control(ctype, arg0, arg1, ch, path) + w32(11156, 'I', len(events)) + +def log_lines(): + try: + with open(APP_LOG, 'r', encoding='utf-8', errors='replace') as f: + return f.readlines() + except FileNotFoundError: + return [] + +def wait_log(pattern, timeout, since, verbose=True): + end = time.time() + timeout + while time.time() < end: + lines = log_lines() + for i in range(since, len(lines)): + if pattern in lines[i]: + if verbose: print(f" LOG: {lines[i].strip()}") + return lines[i].strip() + time.sleep(0.2) + return None + +def bridge_pid(): + try: + out = subprocess.run(["cmd", "/c", "tasklist /FI \"IMAGENAME eq daw_vst_bridge.exe\" /FO CSV"], + capture_output=True, text=True).stdout + for line in out.splitlines()[1:]: + if "daw_vst_bridge" in line: + return line.split(',')[1].strip('"') + except Exception: + pass + return None + +# 1) LOAD 6 Nexus ch0-5 + EZkeys ch6 +since = len(log_lines()) +push_controls([(2, 0, 0, ch, NEXUS) for ch in range(6)] + [(2, 0, 0, 6, EZKEYS)]) +print("pushed LOAD x7; waiting for instruments...") +for ch in range(7): + r = wait_log(f"instrument loaded ch={ch}", 60, since) + print(f" ch={ch}: {'loaded' if r else 'TIMEOUT'}") +# 2) TRANSPORT PLAY +since = len(log_lines()) +push_controls([(3, 1, 0, 0, "")]) +r = wait_log("transport PLAY", 10, since) +print("transport:", r or "TIMEOUT") +# 3) OPEN_GUI ch=5 while PLAYING -> expect DEFERRED +since = len(log_lines()) +push_controls([(4, 0, 0, 5, "Nexus")]) +r = wait_log("GUI deferred ch=5", 10, since) +print("G0.1 defer:", r or "TIMEOUT - KHONG DEFER (bug chua fix)") +pid0 = bridge_pid() +# 4) TRANSPORT STOP -> pending GUI should attach now +since = len(log_lines()) +push_controls([(3, 0, 0, 0, "")]) +r = wait_log("transport STOP", 10, since) +print("transport:", r or "TIMEOUT") +r = wait_log("GUI attached hwnd=", 30, since) +print("G0.1 attach after STOP:", r or "TIMEOUT - attach khong xay ra") +# 5) watch 10s for EXCEPTION/FAILED/restart +since = len(log_lines()) +end = time.time() + 10 +bad = [] +while time.time() < end: + for l in log_lines()[since:]: + if any(k in l for k in ("EXCEPTION", "FAILED", "restarted")): + bad.append(l.strip()) + pid = bridge_pid() + if pid != pid0: + bad.append(f"BRIDGE RESTART {pid0}->{pid}") + break + time.sleep(0.2) +print("G0.1 RESULT:", "PASS - khong loi, attach OK sau STOP" if not bad else "FAIL:\n" + "\n".join(bad[:10])) diff --git a/native_bridge/debug/rd.py b/native_bridge/debug/rd.py new file mode 100644 index 0000000..e605ca2 --- /dev/null +++ b/native_bridge/debug/rd.py @@ -0,0 +1,9 @@ +import sys +sys.stdout.reconfigure(encoding='utf-8',errors='replace') +P = 'C:/Users/locpham/SonicForgeStudio/' +src = open(P+'native_bridge/src/gui_probe.cpp', encoding='utf-8', errors='replace').read() +lines = src.split('\n') +for a,b,title in [(34,115,'ProbeWorker'),(220,290,'shared_worker')]: + print('====',title,'====') + for i in range(a-1,b): + print('%5d| %s' % (i+1, lines[i])) diff --git a/native_bridge/debug/rd2.py b/native_bridge/debug/rd2.py new file mode 100644 index 0000000..4bc33ec --- /dev/null +++ b/native_bridge/debug/rd2.py @@ -0,0 +1,12 @@ +import sys, io +sys.stdout.reconfigure(encoding='utf-8',errors='replace') +P = 'C:/Users/locpham/SonicForgeStudio/' +src = open(P+'native_bridge/src/gui_probe.cpp', encoding='utf-8', errors='replace').read() +lines = src.split('\n') +out = [] +for a,b,title in [(34,115,'ProbeWorker'),(220,290,'shared_worker'),(540,560,'tail')]: + out.append('==== '+title+' ====') + for i in range(a-1,min(b,len(lines))): + out.append('%5d| %s' % (i+1, lines[i])) +open(P+'native_bridge/debug/rd_out2.txt','w',encoding='utf-8').write('\n'.join(out)) +print('written', len(out)) diff --git a/native_bridge/debug/rd_out.txt b/native_bridge/debug/rd_out.txt new file mode 100644 index 0000000..530a303 --- /dev/null +++ b/native_bridge/debug/rd_out.txt @@ -0,0 +1,155 @@ +==== ProbeWorker ==== + 34| for (HWND c = GetWindow(w, GW_CHILD); c; c = GetWindow(c, GW_HWNDNEXT)) + 35| ImmAssociateContext(c, nullptr); + 36| } + 37| } + 38| + 39| static void pump_for(int secs) { + 40| #ifdef _WIN32 + 41| _set_invalid_parameter_handler(probe_invalid_param); + 42| #endif + 43| auto t0 = std::chrono::steady_clock::now(); + 44| while (std::chrono::duration_cast( + 45| std::chrono::steady_clock::now() - t0).count() < secs) { + 46| MSG msg; + 47| while (PeekMessageW(&msg, nullptr, 0, 0, PM_REMOVE)) { + 48| printf("[pump] msg=0x%X hwnd=%p\n", (unsigned)msg.message, (void*)msg.hwnd); + 49| fflush(stdout); + 50| TranslateMessage(&msg); + 51| DispatchMessageW(&msg); + 52| } + 53| Sleep(5); + 54| } + 55| } + 56| + 57| static void sleep_for(int secs) { Sleep((DWORD)(secs * 1000)); } + 58| + 59| // Per-channel persistent worker replicating the bridge (round-3 probe). + 60| class ProbeWorker { + 61| public: + 62| void start() { + 63| th = std::thread([this] { + 64| #ifdef _WIN32 + 65| _set_invalid_parameter_handler(probe_invalid_param); + 66| CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED); + 67| ImmAssociateContextEx(nullptr, nullptr, IACE_DEFAULT); + 68| #endif + 69| std::unique_lock lk(mu); + 70| for (;;) { + 71| if (stop && jobs.empty()) break; + 72| if (!jobs.empty()) { + 73| auto j = std::move(jobs.front()); + 74| jobs.pop_front(); + 75| lk.unlock(); + 76| try { j(); } + 77| catch (const std::exception& e) { printf("[worker] JOB EXCEPTION: %s\n", e.what()); fflush(stdout); } + 78| catch (...) { printf("[worker] JOB UNKNOWN EXCEPTION\n"); fflush(stdout); } + 79| lk.lock(); + 80| continue; + 81| } + 82| cv.wait_for(lk, std::chrono::milliseconds(5)); + 83| lk.unlock(); + 84| try { + 85| MSG msg; + 86| while (PeekMessageW(&msg, nullptr, 0, 0, PM_REMOVE)) { + 87| printf("[wpump] msg=0x%X hwnd=%p\n", (unsigned)msg.message, (void*)msg.hwnd); + 88| fflush(stdout); + 89| TranslateMessage(&msg); + 90| DispatchMessageW(&msg); + 91| } + 92| } + 93| catch (const std::exception& e) { printf("[worker] PUMP EXCEPTION: %s\n", e.what()); fflush(stdout); } + 94| catch (...) { printf("[worker] PUMP UNKNOWN EXCEPTION\n"); fflush(stdout); } + 95| lk.lock(); + 96| } + 97| #ifdef _WIN32 + 98| CoUninitialize(); + 99| #endif + 100| }); + 101| } + 102| void post(std::function j) { + 103| { std::lock_guard lk(mu); jobs.push_back(std::move(j)); } + 104| cv.notify_all(); + 105| } + 106| std::thread th; + 107| std::mutex mu; + 108| std::condition_variable cv; + 109| std::deque> jobs; + 110| bool stop = false; + 111| }; + 112| + 113| int main(int argc, char* argv[]) { + 114| if (argc < 2) { + 115| printf("usage: gui_probe [variant] [secs] [hwnd]\n" +==== shared_worker ==== + 220| } else if (variant == "shared_worker" || variant == "ui_thread") { + 221| // Proposed round-3 fix: ALL VST3 instances live in ONE apartment — + 222| // one persistent worker thread hosts load + openGUI for every channel. + 223| ProbeWorker w; + 224| w.start(); + 225| std::atomic done{false}; + 226| int aRes = 0, bRes = 0; + 227| w.post([&]() { + 228| bool r = inst.loadPlugin(path, 44100.0); + 229| printf("[shared_worker] load inst1=%d\n", r ? 1 : 0); + 230| fflush(stdout); + 231| if (r) { + 232| HWND h = CreateWindowEx(0, "GuiProbeClass", "ProbeA", WS_OVERLAPPEDWINDOW, + 233| 0, 0, 800, 600, nullptr, nullptr, GetModuleHandle(nullptr), nullptr); + 234| disable_ime_contexts(h); + 235| r = inst.openGUI(h); + 236| disable_ime_contexts(h); + 237| printf("[shared_worker] attach inst1=%d hwnd=%p\n", r ? 1 : 0, (void*)h); + 238| fflush(stdout); + 239| aRes = r ? 1 : 0; + 240| } + 241| r = inst2.loadPlugin(path, 44100.0); + 242| printf("[shared_worker] load inst2=%d\n", r ? 1 : 0); + 243| fflush(stdout); + 244| if (r) { + 245| HWND h = CreateWindowEx(0, "GuiProbeClass", "ProbeB", WS_OVERLAPPEDWINDOW, + 246| 0, 0, 800, 600, nullptr, nullptr, GetModuleHandle(nullptr), nullptr); + 247| disable_ime_contexts(h); + 248| r = inst2.openGUI(h); + 249| disable_ime_contexts(h); + 250| printf("[shared_worker] attach inst2=%d hwnd=%p\n", r ? 1 : 0, (void*)h); + 251| fflush(stdout); + 252| bRes = r ? 1 : 0; + 253| } + 254| done = true; + 255| }); + 256| auto t0 = std::chrono::steady_clock::now(); + 257| try { + 258| while (!done.load() && std::chrono::duration_cast( + 259| std::chrono::steady_clock::now() - t0).count() < secs) { + 260| MSG msg; + 261| while (PeekMessageW(&msg, nullptr, 0, 0, PM_REMOVE)) { + 262| TranslateMessage(&msg); + 263| DispatchMessageW(&msg); + 264| } + 265| Sleep(2); + 266| } + 267| } catch (const std::exception& e) { printf("[main] PUMP1 EXCEPTION: %s\n", e.what()); fflush(stdout); } + 268| catch (...) { printf("[main] PUMP1 UNKNOWN EXCEPTION\n"); fflush(stdout); } + 269| // Stay alive 5s with BOTH editors attached and the worker pumping, + 270| // while MAIN KEEPS PUMPING like the bridge audio loop: distinguishes a + 271| // live crash (bad) from a stop-pumping probe artifact. + 272| auto t1 = std::chrono::steady_clock::now(); + 273| try { + 274| while (std::chrono::duration_cast( + 275| std::chrono::steady_clock::now() - t1).count() < 5) { + 276| MSG msg; + 277| while (PeekMessageW(&msg, nullptr, 0, 0, PM_REMOVE)) { + 278| TranslateMessage(&msg); + 279| DispatchMessageW(&msg); + 280| } + 281| Sleep(2); + 282| } + 283| } catch (const std::exception& e) { printf("[main] PUMP2 EXCEPTION: %s\n", e.what()); fflush(stdout); } + 284| catch (...) { printf("[main] PUMP2 UNKNOWN EXCEPTION\n"); fflush(stdout); } + 285| printf("RESULT: done=%d aRes=%d bRes=%d\n", done.load() ? 1 : 0, aRes, bRes); + 286| return (done.load() && aRes && bRes) ? 0 : 3; + 287| } else if (variant == "bridge_two") { + 288| // Exact bridge architecture: per-channel persistent worker threads with + 289| // own COM STA apartments and idle message pumps; inst1 loaded+attached + 290| // on worker A, then inst2 loaded+attached on worker B; main pumps. diff --git a/native_bridge/debug/rd_out2.txt b/native_bridge/debug/rd_out2.txt new file mode 100644 index 0000000..3e75876 --- /dev/null +++ b/native_bridge/debug/rd_out2.txt @@ -0,0 +1,168 @@ +==== ProbeWorker ==== + 34| for (HWND c = GetWindow(w, GW_CHILD); c; c = GetWindow(c, GW_HWNDNEXT)) + 35| ImmAssociateContext(c, nullptr); + 36| } + 37| } + 38| + 39| static void pump_for(int secs) { + 40| #ifdef _WIN32 + 41| _set_invalid_parameter_handler(probe_invalid_param); + 42| #endif + 43| auto t0 = std::chrono::steady_clock::now(); + 44| while (std::chrono::duration_cast( + 45| std::chrono::steady_clock::now() - t0).count() < secs) { + 46| MSG msg; + 47| while (PeekMessageW(&msg, nullptr, 0, 0, PM_REMOVE)) { + 48| printf("[pump] msg=0x%X hwnd=%p\n", (unsigned)msg.message, (void*)msg.hwnd); + 49| fflush(stdout); + 50| TranslateMessage(&msg); + 51| DispatchMessageW(&msg); + 52| } + 53| Sleep(5); + 54| } + 55| } + 56| + 57| static void sleep_for(int secs) { Sleep((DWORD)(secs * 1000)); } + 58| + 59| // Per-channel persistent worker replicating the bridge (round-3 probe). + 60| class ProbeWorker { + 61| public: + 62| void start() { + 63| th = std::thread([this] { + 64| #ifdef _WIN32 + 65| _set_invalid_parameter_handler(probe_invalid_param); + 66| CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED); + 67| ImmAssociateContextEx(nullptr, nullptr, IACE_DEFAULT); + 68| #endif + 69| std::unique_lock lk(mu); + 70| for (;;) { + 71| if (stop && jobs.empty()) break; + 72| if (!jobs.empty()) { + 73| auto j = std::move(jobs.front()); + 74| jobs.pop_front(); + 75| lk.unlock(); + 76| try { j(); } + 77| catch (const std::exception& e) { printf("[worker] JOB EXCEPTION: %s\n", e.what()); fflush(stdout); } + 78| catch (...) { printf("[worker] JOB UNKNOWN EXCEPTION\n"); fflush(stdout); } + 79| lk.lock(); + 80| continue; + 81| } + 82| cv.wait_for(lk, std::chrono::milliseconds(5)); + 83| lk.unlock(); + 84| try { + 85| MSG msg; + 86| while (PeekMessageW(&msg, nullptr, 0, 0, PM_REMOVE)) { + 87| printf("[wpump] msg=0x%X hwnd=%p\n", (unsigned)msg.message, (void*)msg.hwnd); + 88| fflush(stdout); + 89| TranslateMessage(&msg); + 90| DispatchMessageW(&msg); + 91| } + 92| } + 93| catch (const std::exception& e) { printf("[worker] PUMP EXCEPTION: %s\n", e.what()); fflush(stdout); } + 94| catch (...) { printf("[worker] PUMP UNKNOWN EXCEPTION\n"); fflush(stdout); } + 95| lk.lock(); + 96| } + 97| #ifdef _WIN32 + 98| CoUninitialize(); + 99| #endif + 100| }); + 101| } + 102| void post(std::function j) { + 103| { std::lock_guard lk(mu); jobs.push_back(std::move(j)); } + 104| cv.notify_all(); + 105| } + 106| std::thread th; + 107| std::mutex mu; + 108| std::condition_variable cv; + 109| std::deque> jobs; + 110| bool stop = false; + 111| }; + 112| + 113| int main(int argc, char* argv[]) { + 114| if (argc < 2) { + 115| printf("usage: gui_probe [variant] [secs] [hwnd]\n" +==== shared_worker ==== + 220| } else if (variant == "shared_worker" || variant == "ui_thread") { + 221| // Proposed round-3 fix: ALL VST3 instances live in ONE apartment — + 222| // one persistent worker thread hosts load + openGUI for every channel. + 223| ProbeWorker w; + 224| w.start(); + 225| std::atomic done{false}; + 226| int aRes = 0, bRes = 0; + 227| w.post([&]() { + 228| bool r = inst.loadPlugin(path, 44100.0); + 229| printf("[shared_worker] load inst1=%d\n", r ? 1 : 0); + 230| fflush(stdout); + 231| if (r) { + 232| HWND h = CreateWindowEx(0, "GuiProbeClass", "ProbeA", WS_OVERLAPPEDWINDOW, + 233| 0, 0, 800, 600, nullptr, nullptr, GetModuleHandle(nullptr), nullptr); + 234| disable_ime_contexts(h); + 235| r = inst.openGUI(h); + 236| disable_ime_contexts(h); + 237| printf("[shared_worker] attach inst1=%d hwnd=%p\n", r ? 1 : 0, (void*)h); + 238| fflush(stdout); + 239| aRes = r ? 1 : 0; + 240| } + 241| r = inst2.loadPlugin(path, 44100.0); + 242| printf("[shared_worker] load inst2=%d\n", r ? 1 : 0); + 243| fflush(stdout); + 244| if (r) { + 245| HWND h = CreateWindowEx(0, "GuiProbeClass", "ProbeB", WS_OVERLAPPEDWINDOW, + 246| 0, 0, 800, 600, nullptr, nullptr, GetModuleHandle(nullptr), nullptr); + 247| disable_ime_contexts(h); + 248| r = inst2.openGUI(h); + 249| disable_ime_contexts(h); + 250| printf("[shared_worker] attach inst2=%d hwnd=%p\n", r ? 1 : 0, (void*)h); + 251| fflush(stdout); + 252| bRes = r ? 1 : 0; + 253| } + 254| done = true; + 255| }); + 256| auto t0 = std::chrono::steady_clock::now(); + 257| try { + 258| while (!done.load() && std::chrono::duration_cast( + 259| std::chrono::steady_clock::now() - t0).count() < secs) { + 260| MSG msg; + 261| while (PeekMessageW(&msg, nullptr, 0, 0, PM_REMOVE)) { + 262| TranslateMessage(&msg); + 263| DispatchMessageW(&msg); + 264| } + 265| Sleep(2); + 266| } + 267| } catch (const std::exception& e) { printf("[main] PUMP1 EXCEPTION: %s\n", e.what()); fflush(stdout); } + 268| catch (...) { printf("[main] PUMP1 UNKNOWN EXCEPTION\n"); fflush(stdout); } + 269| // Stay alive 5s with BOTH editors attached and the worker pumping, + 270| // while MAIN KEEPS PUMPING like the bridge audio loop: distinguishes a + 271| // live crash (bad) from a stop-pumping probe artifact. + 272| auto t1 = std::chrono::steady_clock::now(); + 273| try { + 274| while (std::chrono::duration_cast( + 275| std::chrono::steady_clock::now() - t1).count() < 5) { + 276| MSG msg; + 277| while (PeekMessageW(&msg, nullptr, 0, 0, PM_REMOVE)) { + 278| TranslateMessage(&msg); + 279| DispatchMessageW(&msg); + 280| } + 281| Sleep(2); + 282| } + 283| } catch (const std::exception& e) { printf("[main] PUMP2 EXCEPTION: %s\n", e.what()); fflush(stdout); } + 284| catch (...) { printf("[main] PUMP2 UNKNOWN EXCEPTION\n"); fflush(stdout); } + 285| printf("RESULT: done=%d aRes=%d bRes=%d\n", done.load() ? 1 : 0, aRes, bRes); + 286| return (done.load() && aRes && bRes) ? 0 : 3; + 287| } else if (variant == "bridge_two") { + 288| // Exact bridge architecture: per-channel persistent worker threads with + 289| // own COM STA apartments and idle message pumps; inst1 loaded+attached + 290| // on worker A, then inst2 loaded+attached on worker B; main pumps. +==== tail ==== + 540| catch (...) { printf("[main] PUMP2 UNKNOWN EXCEPTION\n"); fflush(stdout); } + 541| printf("RESULT: done=%d attached_ok=%d\n", 1, 1); + 542| return 0; + 543| } else { + 544| printf("unknown variant %s\n", variant.c_str()); + 545| return 2; + 546| } + 547| + 548| printf("RESULT: done=%d attached_ok=%d\n", done.load() ? 1 : 0, result.load() ? 1 : 0); + 549| return 0; + 550| } + 551| \ No newline at end of file diff --git a/native_bridge/debug/repro_g13_close.py b/native_bridge/debug/repro_g13_close.py new file mode 100644 index 0000000..7e8db15 --- /dev/null +++ b/native_bridge/debug/repro_g13_close.py @@ -0,0 +1,110 @@ +# Reproduce: attach ch5 -> PLAY -> LOAD EZkeys ch5 (editor open) -> STOP -> WM_CLOSE -> watch +import ctypes, ctypes.wintypes as w, struct, subprocess, sys, os, time, re + +sys.stdout.reconfigure(encoding='utf-8', errors='replace') +APP_PID = int(sys.argv[1]) +SHM_NAME = f"SonicForge_DAW_IPC_{APP_PID}".encode() +APP_LOG = os.path.join(os.environ.get('APPDATA', '.'), 'SonicForgeDAW', 'logs', 'bridge.log') +NEXUS = r"C:\Program Files\Common Files\VST3\Nexus.vst3" +EZKEYS = r"C:\Program Files\Common Files\VST3\Toontrack\EZkeys 2.vst3" + +kernel32 = ctypes.WinDLL('kernel32', use_last_error=True) +kernel32.OpenFileMappingA.argtypes = [ctypes.wintypes.DWORD, w.BOOL, ctypes.c_char_p] +kernel32.OpenFileMappingA.restype = ctypes.c_void_p +kernel32.MapViewOfFile.argtypes = [ctypes.c_void_p, ctypes.wintypes.DWORD, ctypes.wintypes.DWORD, ctypes.wintypes.DWORD, ctypes.c_size_t] +kernel32.MapViewOfFile.restype = ctypes.c_void_p +hMap = kernel32.OpenFileMappingA(0xF001F, False, SHM_NAME) +ptr = kernel32.MapViewOfFile(hMap, 0xF001F, 0, 0, 0) +user32 = ctypes.windll.user32 + +def w32(off, fmt, *args): + ctypes.memmove(ptr + off, struct.pack(fmt, *args), struct.calcsize(fmt)) +def rd32(off): + return ctypes.c_uint32.from_address(ptr + off).value +def write_control(ctype, arg0, arg1, ch, path): + for i in range(8): + off = 2836 + i * 1040 + if rd32(off) == 0: + w32(off, 'IIII', ctype, arg0, arg1, ch) + if path: + b = path.encode('utf-8')[:1023] + b'\x00' + ctypes.memmove(ptr + off + 16, b, len(b)) + return i +def push_controls(events): + for i in range(8): + w32(2836 + i * 1040, 'I', 0) + for (ctype, arg0, arg1, ch, path) in events: + write_control(ctype, arg0, arg1, ch, path) + w32(11156, 'I', len(events)) +def log_lines(): + try: + with open(APP_LOG, 'r', encoding='utf-8', errors='replace') as f: + return f.readlines() + except FileNotFoundError: + return [] +def wait_log(pattern, timeout, since, verbose=True): + end = time.time() + timeout + while time.time() < end: + lines = log_lines() + for i in range(since, len(lines)): + if pattern in lines[i]: + if verbose: print(f" LOG: {lines[i].strip()}") + return lines[i].strip() + time.sleep(0.2) + return None +def find_hwnd(ch): + for l in log_lines(): + if f"ch={ch}" in l and "GUI attached" in l: + m = re.search(r'hwnd=([0-9A-Fa-f]+)', l) + if m: return int(m.group(1), 16) + return None + +since = len(log_lines()) +print("== step 1: attach ch5 ==") +push_controls([(4, 0, 0, 5, "Nexus")]) +r = wait_log("GUI attached", 30, since) +print("attach:", "OK" if r else "TIMEOUT") +hwnd5 = None +if r: + m = re.search(r'hwnd=([0-9A-Fa-f]+)', r) + if m: hwnd5 = int(m.group(1), 16) +print("hwnd5:", hex(hwnd5) if hwnd5 else None) +print("IsWindow:", user32.IsWindow(hwnd5), "tid:", user32.GetWindowThreadProcessId(hwnd5, None), "visible:", user32.IsWindowVisible(hwnd5)) + +since = len(log_lines()) +print("== step 2: PLAY ==") +push_controls([(3, 1, 0, 0, "")]) +wait_log("transport PLAY", 10, since) + +since = len(log_lines()) +print("== step 3: LOAD EZkeys ch5 while editor open ==") +push_controls([(2, 0, 0, 5, EZKEYS)]) +r = wait_log("instrument loaded ch=5", 60, since) +print("load:", "OK" if r else "TIMEOUT") +print("IsWindow:", user32.IsWindow(hwnd5), "visible:", user32.IsWindowVisible(hwnd5), "child:", user32.FindWindowExA(hwnd5, None, None, None)) + +since = len(log_lines()) +print("== step 4: STOP ==") +push_controls([(3, 0, 0, 0, "")]) +wait_log("transport STOP", 10, since) + +since = len(log_lines()) +print("== step 5: WM_CLOSE to", hex(hwnd5) if hwnd5 else "None", "watch 45s ==") +if hwnd5: + user32.PostMessageW(hwnd5, 0x0010, 0, 0) +end = time.time() + 45 +seen = [] +while time.time() < end: + ls = log_lines() + for l in ls[since:]: + if any(k in l for k in ("state captured", "state restored", "closeGUI", "EXCEPTION", "FAILED")): + if l.strip() not in seen: + print(" LOG:", l.strip()) + seen.append(l.strip()) + if any("state restored ch=5" in s for s in seen): + break + time.sleep(0.3) +if not any("state restored ch=5" in s for s in seen): + print(" NO 'state restored ch=5' in 45s; last 10 log lines:") + for l in log_lines()[-10:]: print(" ", l.strip()) +print("IsWindow after:", user32.IsWindow(hwnd5), "visible:", user32.IsWindowVisible(hwnd5)) diff --git a/native_bridge/debug/run_cdb_shared.ps1 b/native_bridge/debug/run_cdb_shared.ps1 new file mode 100644 index 0000000..956cceb --- /dev/null +++ b/native_bridge/debug/run_cdb_shared.ps1 @@ -0,0 +1,4 @@ +cd 'C:/Users/locpham/SonicForgeStudio/native_bridge/build/Release' +$cdb = 'C:/Program Files/WindowsApps/Microsoft.WinDbg_1.2606.22001.0_x64__8wekyb3d8bbwe/amd64/cdb.exe' +$plugin = 'C:\Program Files\Common Files\VST3\Nexus.vst3' +& $cdb -o -g -G -c "g" .\gui_probe.exe $plugin shared_worker 12 2>&1 | Tee-Object -FilePath 'C:/Users/locpham/SonicForgeStudio/native_bridge/debug/cdb_zi_shared.log' diff --git a/native_bridge/debug/run_cdb_shared2.ps1 b/native_bridge/debug/run_cdb_shared2.ps1 new file mode 100644 index 0000000..ac5f383 --- /dev/null +++ b/native_bridge/debug/run_cdb_shared2.ps1 @@ -0,0 +1,5 @@ +$ErrorActionPreference = 'Stop' +cd 'C:/Users/locpham/SonicForgeStudio/native_bridge/build/Release' +$cdb = 'C:/Program Files/WindowsApps/Microsoft.WinDbg_1.2606.22001.0_x64__8wekyb3d8bbwe/amd64/cdb.exe' +$plugin = 'C:\Program Files\Common Files\VST3\Nexus.vst3' +& $cdb -o -g -G -c "g" .\gui_probe.exe $plugin shared_worker 8 2>&1 | Tee-Object -FilePath 'C:/Users/locpham/SonicForgeStudio/native_bridge/debug/cdb_zi_shared2.log' diff --git a/native_bridge/debug/run_cdb_shared3.ps1 b/native_bridge/debug/run_cdb_shared3.ps1 new file mode 100644 index 0000000..d7d1f28 --- /dev/null +++ b/native_bridge/debug/run_cdb_shared3.ps1 @@ -0,0 +1,5 @@ +$ErrorActionPreference = 'Stop' +cd 'C:/Users/locpham/SonicForgeStudio/native_bridge/build/Release' +$cdb = 'C:/Program Files/WindowsApps/Microsoft.WinDbg_1.2606.22001.0_x64__8wekyb3d8bbwe/amd64/cdb.exe' +$plugin = 'C:\Program Files\Common Files\VST3\Nexus.vst3' +& $cdb -o -g -G -c "g" .\gui_probe.exe $plugin shared_worker 15 2>&1 | Tee-Object -FilePath 'C:/Users/locpham/SonicForgeStudio/native_bridge/debug/cdb_zi_shared3.log' diff --git a/native_bridge/debug/run_direct_shared4.ps1 b/native_bridge/debug/run_direct_shared4.ps1 new file mode 100644 index 0000000..456b79b --- /dev/null +++ b/native_bridge/debug/run_direct_shared4.ps1 @@ -0,0 +1,8 @@ +$ErrorActionPreference = 'Continue' +cd 'C:/Users/locpham/SonicForgeStudio/native_bridge/build/Release' +$plugin = 'C:\Program Files\Common Files\VST3\Nexus.vst3' +$sw = [System.Diagnostics.Stopwatch]::StartNew() +& .\gui_probe.exe $plugin shared_worker 15 *> 'C:/Users/locpham/SonicForgeStudio/native_bridge/debug/direct_shared4.log' +$code = $LASTEXITCODE +$sw.Stop() +"EXIT=$code ELAPSED=$($sw.Elapsed.TotalSeconds)" | Out-File -Append 'C:/Users/locpham/SonicForgeStudio/native_bridge/debug/direct_shared4.log' diff --git a/native_bridge/debug/seg1.txt b/native_bridge/debug/seg1.txt new file mode 100644 index 0000000..5c44990 --- /dev/null +++ b/native_bridge/debug/seg1.txt @@ -0,0 +1,48 @@ + 185| attach_job(); // editorhost pattern: attached on main thread, pump after + 186| pump_for(secs); + 187| } else if (variant == "worker_own_nopump" || variant == "worker_foreign_nopump") { + 188| std::thread t(attach_job); + 189| sleep_for(secs); + 190| if (!done.load()) { printf("RESULT: TIMEOUT (no pump)\n"); return 3; } + 191| } else if (variant == "worker_own_pump" || variant == "worker_foreign_pump") { + 192| std::thread t(attach_job); + 193| pump_for(secs); + 194| if (!done.load()) { printf("RESULT: TIMEOUT (pump concurrent)\n"); return 3; } + 195| } else if (variant == "bridge_like") { + 196| // Bridge pattern: plugin loaded on thread A which then EXITS (COM + 197| // apartment dies), openGUI on separate thread B, main pumps. + 198| std::thread loadA([&]() { + 199| CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED); + 200| printf("[bridge_like] loadPlugin on thread A\n"); + 201| fflush(stdout); + 202| bool r = inst.loadPlugin(path, 44100.0); + 203| printf("[bridge_like] load=%d (thread A exiting)\n", r ? 1 : 0); + 204| fflush(stdout); + 205| CoUninitialize(); + 206| }); + 207| loadA.join(); + 208| std::thread t(attach_job); // thread B, after A exited + 209| pump_for(secs); + 210| if (!done.load()) { printf("RESULT: TIMEOUT (apartment dead)\n"); return 3; } + 211| } else if (variant == "same_thread") { + 212| // Load and openGUI on the SAME worker thread, kept alive; main pumps. + 213| std::thread t([&]() { + 214| CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED); + 215| ImmAssociateContextEx(nullptr, nullptr, IACE_DEFAULT); + 216| printf("[same_thread] loadPlugin on worker\n"); + 217| fflush(stdout); + 218| bool r = inst.loadPlugin(path, 44100.0); + 219| printf("[same_thread] load=%d\n", r ? 1 : 0); + 220| fflush(stdout); + 221| if (r) { disable_ime_contexts((HWND)targetHwnd); attach_job(); disable_ime_contexts((HWND)targetHwnd); } + 222| }); + 223| pump_for(secs); + 224| if (!done.load()) { printf("RESULT: TIMEOUT (same_thread)\n"); return 3; } + 225| } else if (variant == "shared_worker" || variant == "ui_thread") { + 226| // Proposed round-3 fix: ALL VST3 instances live in ONE apartment — + 227| // one persistent worker thread hosts load + openGUI for every channel. + 228| ProbeWorker w; + 229| w.start(); + 230| std::atomic done{false}; + 231| int aRes = 0, bRes = 0; + 232| w.post([&]() { \ No newline at end of file diff --git a/native_bridge/debug/seg2.txt b/native_bridge/debug/seg2.txt new file mode 100644 index 0000000..9909d7e --- /dev/null +++ b/native_bridge/debug/seg2.txt @@ -0,0 +1,72 @@ + 115| bool stop = false; + 116| }; + 117| + 118| int main(int argc, char* argv[]) { + 119| if (argc < 2) { + 120| printf("usage: gui_probe [variant] [secs] [hwnd]\n" + 121| "variants: main_own | worker_own_nopump | worker_own_pump | worker_foreign_nopump | worker_foreign_pump | bridge_like | same_thread | two_instances | two_instances_close | close_reopen | bridge_two | shared_worker | ui_thread | two_workers_close\n"); + 122| return 2; + 123| } + 124| std::string path = argv[1]; + 125| std::string variant = argc > 2 ? argv[2] : "main_own"; + 126| int secs = argc > 3 ? atoi(argv[3]) : 20; + 127| + 128| CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED); + 129| + 130| // Watchdog: never let a stuck plugin hang this probe forever. + 131| std::thread([secs]() { + 132| Sleep((DWORD)(secs + 10) * 1000); + 133| printf("WATCHDOG-KILL\n"); + 134| fflush(stdout); + 135| TerminateProcess(GetCurrentProcess(), 0); + 136| }).detach(); + 137| + 138| HWND ownHwnd = 0; + 139| { + 140| WNDCLASSEX wc = {}; + 141| wc.cbSize = sizeof(wc); + 142| wc.lpfnWndProc = DefWindowProc; + 143| wc.hInstance = GetModuleHandle(nullptr); + 144| wc.lpszClassName = "GuiProbeClass"; + 145| RegisterClassEx(&wc); + 146| ownHwnd = CreateWindowEx(0, "GuiProbeClass", "GuiProbe", WS_OVERLAPPEDWINDOW, + 147| 0, 0, 800, 600, nullptr, nullptr, wc.hInstance, nullptr); + 148| printf("ownHwnd=%p\n", (void*)ownHwnd); + 149| } + 150| + 151| void* targetHwnd = (void*)ownHwnd; + 152| if (argc > 4) targetHwnd = (void*)(uintptr_t)strtoull(argv[4], nullptr, 0); + 153| + 154| printf("loading %s\n", path.c_str()); + 155| fflush(stdout); + 156| Vst3Instrument inst; + 157| Vst3Instrument inst2; + 158| bool isDeferred = (variant == "bridge_like" || variant == "same_thread" || + 159| variant == "two_instances" || variant == "two_instances_close" || + 160| variant == "close_reopen" || variant == "bridge_two" || + 161| variant == "shared_worker" || variant == "ui_thread"); + 162| bool ok = true; + 163| if (!isDeferred) { + 164| ok = inst.loadPlugin(path, 44100.0); + 165| printf("load=%d\n", ok ? 1 : 0); + 166| fflush(stdout); + 167| if (!ok) return 1; + 168| } + 169| + 170| std::atomic done{false}; + 171| std::atomic result{false}; + 172| + 173| auto attach_job = [&]() { + 174| CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED); + 175| printf("[%s] openGUI start hwnd=%p\n", variant.c_str(), targetHwnd); + 176| fflush(stdout); + 177| bool r = inst.openGUI(targetHwnd); + 178| printf("[%s] openGUI returned=%d\n", variant.c_str(), r ? 1 : 0); + 179| fflush(stdout); + 180| result = r; + 181| done = true; + 182| }; + 183| + 184| if (variant == "main_own") { + 185| attach_job(); // editorhost pattern: attached on main thread, pump after + 186| pump_for(secs); \ No newline at end of file diff --git a/native_bridge/debug/seg3.txt b/native_bridge/debug/seg3.txt new file mode 100644 index 0000000..96be4ba --- /dev/null +++ b/native_bridge/debug/seg3.txt @@ -0,0 +1,40 @@ + 1| // native_bridge/src/gui_probe.cpp — standalone VST3 GUI attach probe. + 2| // Debug tool (NOT shipped with the DAW): loads a VST3 with Vst3Instrument and + 3| // tests openGUI under different thread/pump/parent-window setups so we can + 4| // find why view->attached() hangs inside the bridge. + 5| #include "Vst3Instrument.h" + 6| + 7| #ifdef _WIN32 + 8| #include + 9| #include + 10| #endif + 11| #include + 12| #include + 13| #include + 14| #include + 15| #include + 16| #include + 17| #include + 18| #include + 19| #include + 20| #include + 21| #include + 22| + 23| #ifdef _WIN32 + 24| static void probe_invalid_param(const wchar_t* expr, const wchar_t* func, const wchar_t* file, + 25| unsigned int line, uintptr_t reserved) { + 26| printf("[probe] INVALID_PARAMETER expr=%ls func=%ls\n", expr ? expr : L"null", func ? func : L"null"); + 27| fflush(stdout); + 28| } + 29| #endif + 30| + 31| static void disable_ime_contexts(HWND w) { + 32| if (w && IsWindow(w)) { + 33| ImmAssociateContext(w, nullptr); + 34| for (HWND c = GetWindow(w, GW_CHILD); c; c = GetWindow(c, GW_HWNDNEXT)) + 35| ImmAssociateContext(c, nullptr); + 36| } + 37| } + 38| + 39| static void pump_for(int secs) { + 40| #ifdef _WIN32 \ No newline at end of file diff --git a/native_bridge/debug/shm_probe_names.py b/native_bridge/debug/shm_probe_names.py new file mode 100644 index 0000000..5c5ed8b --- /dev/null +++ b/native_bridge/debug/shm_probe_names.py @@ -0,0 +1,18 @@ +import ctypes, ctypes.wintypes as w +k32 = ctypes.WinDLL('kernel32', use_last_error=True) +k32.OpenFileMappingA.argtypes = [ctypes.wintypes.DWORD, w.BOOL, ctypes.c_char_p] +k32.OpenFileMappingA.restype = ctypes.c_void_p +cands = [ + b'SonicForge_DAW_IPC', + b'SonicForge_DAW_IPC_22264', + b'SonicForge_DAW_IPC_16140', + b'SonicForge_DAW_IPC_26328', + b'Global\\SonicForge_DAW_IPC_22264', + b'Local\\SonicForge_DAW_IPC_22264', +] +for n in cands: + h = k32.OpenFileMappingA(0xF001F, False, n) + e = ctypes.get_last_error() + print(n, '->', hex(h) if h else 'NULL', 'err=', e) + if h: + ctypes.windll.kernel32.CloseHandle(ctypes.c_void_p(h)) diff --git a/native_bridge/debug/sweep_all_gui.py b/native_bridge/debug/sweep_all_gui.py new file mode 100644 index 0000000..11436db --- /dev/null +++ b/native_bridge/debug/sweep_all_gui.py @@ -0,0 +1,66 @@ +# Sweep ALL system VST3: gui_probe bridge_two (exact bridge arch: +# 2 worker threads, own STA, main pump), classify load/attach/hang/timeout. +# Usage: python sweep_all_gui.py [variant] [secs] [jobs] +import subprocess, sys, os, time, concurrent.futures, re, json + +sys.stdout.reconfigure(encoding='utf-8', errors='replace') +VST = r"C:\Program Files\Common Files\VST3" +PROBE = r"C:\Users\locpham\SonicForgeStudio\native_bridge\build\Release\gui_probe.exe" +HERE = os.path.dirname(os.path.abspath(__file__)) +variant = sys.argv[1] if len(sys.argv) > 1 else "bridge_two" +secs = int(sys.argv[2]) if len(sys.argv) > 2 else 8 +jobs = int(sys.argv[3]) if len(sys.argv) > 3 else 4 + +# top-level bundles only (dedupe inner Contents/x86_64-win/*.vst3) +plugins = [] +for root, dirs, files in os.walk(VST): + if "/Contents/" in root or "\\Contents\\" in root: + continue + for f in files: + if f.lower().endswith(".vst3"): + plugins.append(os.path.join(root, f)) +plugins.sort() +print(f"sweep {len(plugins)} plugins, variant={variant} secs={secs} jobs={jobs}", flush=True) + +def run_one(p): + t0 = time.time() + try: + r = subprocess.run([PROBE, p, variant, str(secs)], + capture_output=True, text=True, timeout=secs + 20, + creationflags=subprocess.CREATE_NO_WINDOW) + out = (r.stdout or "") + (r.stderr or "") + code = r.returncode + except subprocess.TimeoutExpired: + out = "HARD-TIMEOUT" + code = -1 + dt = time.time() - t0 + m = re.search(r"RESULT: (.*)", out) + res = m.group(1).strip() if m else "" + if "WATCHDOG-KILL" in out: verdict = "HANG" + elif code == 0: verdict = "PASS" + elif code == 3: verdict = "FAIL" + elif "load=" in out and re.search(r"load=0", out): verdict = "LOAD-FAIL" + else: verdict = f"EXIT{code}" + # extract individual attach results if present + a = re.search(r"workerA openGUI inst1=(\d)", out) + b = re.search(r"workerB openGUI inst2=(\d)", out) + detail = f"a={a.group(1) if a else '?'} b={b.group(1) if b else '?'}" + return {"plugin": p, "verdict": verdict, "detail": detail, "secs": round(dt, 1), "res": res} + +results = [] +with concurrent.futures.ThreadPoolExecutor(max_workers=jobs) as ex: + futs = {ex.submit(run_one, p): p for p in plugins} + done = 0 + for f in concurrent.futures.as_completed(futs): + results.append(f.result()) + done += 1 + print(f"[{done}/{len(plugins)}] {results[-1]['verdict']:10s} {results[-1]['detail']} {results[-1]['secs']}s {os.path.basename(results[-1]['plugin'])}", flush=True) + +results.sort(key=lambda x: (x["verdict"], x["plugin"])) +print("\n==== SUMMARY ====", flush=True) +for r in results: + print(f"{r['verdict']:10s} {r['detail']:8s} {r['secs']:5.1f}s {r['plugin']}", flush=True) +from collections import Counter +print("counts:", dict(Counter(r["verdict"] for r in results)), flush=True) +with open(os.path.join(HERE, f"sweep_{variant}_{secs}s.json"), "w", encoding="utf-8") as f: + json.dump(results, f, ensure_ascii=False, indent=2) diff --git a/native_bridge/debug/sweep_bridge_two_8s.json b/native_bridge/debug/sweep_bridge_two_8s.json new file mode 100644 index 0000000..c5c3707 --- /dev/null +++ b/native_bridge/debug/sweep_bridge_two_8s.json @@ -0,0 +1,534 @@ +[ + { + "plugin": "C:\\Program Files\\Common Files\\VST3\\Celemony\\Melodyne\\Melodyne.vst3", + "verdict": "EXIT3221225477", + "detail": "a=1 b=?", + "secs": 1.2, + "res": "" + }, + { + "plugin": "C:\\Program Files\\Common Files\\VST3\\Orb Plugins\\Orb Arpeggios.vst3", + "verdict": "EXIT3221225477", + "detail": "a=1 b=?", + "secs": 1.5, + "res": "" + }, + { + "plugin": "C:\\Program Files\\Common Files\\VST3\\Orb Plugins\\Orb Melody.vst3", + "verdict": "EXIT3221225477", + "detail": "a=1 b=?", + "secs": 1.9, + "res": "" + }, + { + "plugin": "C:\\Program Files\\Common Files\\VST3\\Sonible\\smartEQ3.vst3", + "verdict": "EXIT3221225477", + "detail": "a=1 b=?", + "secs": 1.6, + "res": "" + }, + { + "plugin": "C:\\Program Files\\Common Files\\VST3\\Sonible\\smartEQlive_x64.vst3", + "verdict": "EXIT3221225477", + "detail": "a=1 b=?", + "secs": 1.3, + "res": "" + }, + { + "plugin": "C:\\Program Files\\Common Files\\VST3\\Sonible\\smartreverb_x64.vst3", + "verdict": "EXIT3221225477", + "detail": "a=1 b=?", + "secs": 1.3, + "res": "" + }, + { + "plugin": "C:\\Program Files\\Common Files\\VST3\\Synapse Audio\\DUNE3.vst3", + "verdict": "EXIT3221225477", + "detail": "a=? b=?", + "secs": 4.6, + "res": "" + }, + { + "plugin": "C:\\Program Files\\Common Files\\VST3\\ACE Bridge 2.vst3", + "verdict": "EXIT3221226505", + "detail": "a=1 b=?", + "secs": 8.2, + "res": "" + }, + { + "plugin": "C:\\Program Files\\Common Files\\VST3\\ACE Bridge ARA.vst3", + "verdict": "EXIT3221226505", + "detail": "a=1 b=?", + "secs": 8.2, + "res": "" + }, + { + "plugin": "C:\\Program Files\\Common Files\\VST3\\Ample Sound\\ACDD.vst3", + "verdict": "EXIT3221226505", + "detail": "a=1 b=?", + "secs": 3.0, + "res": "" + }, + { + "plugin": "C:\\Program Files\\Common Files\\VST3\\Ample Sound\\ACDX.vst3", + "verdict": "EXIT3221226505", + "detail": "a=1 b=?", + "secs": 8.2, + "res": "" + }, + { + "plugin": "C:\\Program Files\\Common Files\\VST3\\Ample Sound\\ACEH.vst3", + "verdict": "EXIT3221226505", + "detail": "a=1 b=?", + "secs": 8.1, + "res": "" + }, + { + "plugin": "C:\\Program Files\\Common Files\\VST3\\Ample Sound\\ACGQ.vst3", + "verdict": "EXIT3221226505", + "detail": "a=1 b=?", + "secs": 8.2, + "res": "" + }, + { + "plugin": "C:\\Program Files\\Common Files\\VST3\\Ample Sound\\ACKH.vst3", + "verdict": "EXIT3221226505", + "detail": "a=1 b=?", + "secs": 8.2, + "res": "" + }, + { + "plugin": "C:\\Program Files\\Common Files\\VST3\\Ample Sound\\ACP.vst3", + "verdict": "EXIT3221226505", + "detail": "a=1 b=?", + "secs": 8.2, + "res": "" + }, + { + "plugin": "C:\\Program Files\\Common Files\\VST3\\Ample Sound\\ACQD.vst3", + "verdict": "EXIT3221226505", + "detail": "a=1 b=?", + "secs": 8.1, + "res": "" + }, + { + "plugin": "C:\\Program Files\\Common Files\\VST3\\Ample Sound\\ACTP.vst3", + "verdict": "EXIT3221226505", + "detail": "a=? b=?", + "secs": 8.1, + "res": "" + }, + { + "plugin": "C:\\Program Files\\Common Files\\VST3\\Ample Sound\\ACZ.vst3", + "verdict": "EXIT3221226505", + "detail": "a=1 b=?", + "secs": 8.2, + "res": "" + }, + { + "plugin": "C:\\Program Files\\Common Files\\VST3\\Audiomodern\\Riffer_64.vst3", + "verdict": "EXIT3221226505", + "detail": "a=1 b=?", + "secs": 8.1, + "res": "" + }, + { + "plugin": "C:\\Program Files\\Common Files\\VST3\\BlueARP\\BlueARP.vst3", + "verdict": "EXIT3221226505", + "detail": "a=1 b=1", + "secs": 8.1, + "res": "" + }, + { + "plugin": "C:\\Program Files\\Common Files\\VST3\\Focusrite\\fast-balancer.vst3", + "verdict": "EXIT3221226505", + "detail": "a=1 b=?", + "secs": 8.2, + "res": "" + }, + { + "plugin": "C:\\Program Files\\Common Files\\VST3\\Focusrite\\fast-compressor_x64.vst3", + "verdict": "EXIT3221226505", + "detail": "a=1 b=?", + "secs": 8.1, + "res": "" + }, + { + "plugin": "C:\\Program Files\\Common Files\\VST3\\Focusrite\\fast-equaliser_x64.vst3", + "verdict": "EXIT3221226505", + "detail": "a=1 b=?", + "secs": 8.2, + "res": "" + }, + { + "plugin": "C:\\Program Files\\Common Files\\VST3\\Focusrite\\fast-limiter_x64.vst3", + "verdict": "EXIT3221226505", + "detail": "a=1 b=?", + "secs": 8.1, + "res": "" + }, + { + "plugin": "C:\\Program Files\\Common Files\\VST3\\Focusrite\\fast-reveal_x64.vst3", + "verdict": "EXIT3221226505", + "detail": "a=1 b=?", + "secs": 8.2, + "res": "" + }, + { + "plugin": "C:\\Program Files\\Common Files\\VST3\\Focusrite\\fast-verb_x64.vst3", + "verdict": "EXIT3221226505", + "detail": "a=1 b=?", + "secs": 8.2, + "res": "" + }, + { + "plugin": "C:\\Program Files\\Common Files\\VST3\\Kontakt.vst3", + "verdict": "EXIT3221226505", + "detail": "a=1 b=1", + "secs": 11.3, + "res": "" + }, + { + "plugin": "C:\\Program Files\\Common Files\\VST3\\LANDR\\LANDR Composer.vst3", + "verdict": "EXIT3221226505", + "detail": "a=1 b=?", + "secs": 8.2, + "res": "" + }, + { + "plugin": "C:\\Program Files\\Common Files\\VST3\\LANDR\\LANDR Guitar.vst3", + "verdict": "EXIT3221226505", + "detail": "a=1 b=?", + "secs": 8.2, + "res": "" + }, + { + "plugin": "C:\\Program Files\\Common Files\\VST3\\Nexus.vst3", + "verdict": "EXIT3221226505", + "detail": "a=1 b=?", + "secs": 8.2, + "res": "" + }, + { + "plugin": "C:\\Program Files\\Common Files\\VST3\\Orb Plugins\\Orb Bass.vst3", + "verdict": "EXIT3221226505", + "detail": "a=1 b=?", + "secs": 8.2, + "res": "" + }, + { + "plugin": "C:\\Program Files\\Common Files\\VST3\\Orb Plugins\\Orb Chords.vst3", + "verdict": "EXIT3221226505", + "detail": "a=1 b=?", + "secs": 8.2, + "res": "" + }, + { + "plugin": "C:\\Program Files\\Common Files\\VST3\\Scaler2.vst3", + "verdict": "EXIT3221226505", + "detail": "a=1 b=?", + "secs": 8.2, + "res": "" + }, + { + "plugin": "C:\\Program Files\\Common Files\\VST3\\ScalerAudio2.vst3", + "verdict": "EXIT3221226505", + "detail": "a=1 b=?", + "secs": 8.2, + "res": "" + }, + { + "plugin": "C:\\Program Files\\Common Files\\VST3\\Sonible\\entropyEQ_x64.vst3", + "verdict": "EXIT3221226505", + "detail": "a=1 b=?", + "secs": 8.1, + "res": "" + }, + { + "plugin": "C:\\Program Files\\Common Files\\VST3\\Sonible\\freiraum_x64.vst3", + "verdict": "EXIT3221226505", + "detail": "a=1 b=?", + "secs": 8.1, + "res": "" + }, + { + "plugin": "C:\\Program Files\\Common Files\\VST3\\Sonible\\proximityEQ_x64.vst3", + "verdict": "EXIT3221226505", + "detail": "a=1 b=?", + "secs": 8.1, + "res": "" + }, + { + "plugin": "C:\\Program Files\\Common Files\\VST3\\Sonible\\smartcomp2.vst3", + "verdict": "EXIT3221226505", + "detail": "a=1 b=?", + "secs": 8.2, + "res": "" + }, + { + "plugin": "C:\\Program Files\\Common Files\\VST3\\Sonible\\smartlimit.vst3", + "verdict": "EXIT3221226505", + "detail": "a=1 b=?", + "secs": 8.3, + "res": "" + }, + { + "plugin": "C:\\Program Files\\Common Files\\VST3\\Toontrack\\EZdrummer 3.vst3", + "verdict": "EXIT3221226505", + "detail": "a=1 b=?", + "secs": 8.3, + "res": "" + }, + { + "plugin": "C:\\Program Files\\Common Files\\VST3\\Toontrack\\EZkeys 2.vst3", + "verdict": "EXIT3221226505", + "detail": "a=1 b=?", + "secs": 8.3, + "res": "" + }, + { + "plugin": "C:\\Program Files\\Common Files\\VST3\\iZotope\\Ozone 11 Clarity.vst3", + "verdict": "EXIT3221226505", + "detail": "a=1 b=1", + "secs": 8.3, + "res": "" + }, + { + "plugin": "C:\\Program Files\\Common Files\\VST3\\iZotope\\Ozone 11 Dynamic EQ.vst3", + "verdict": "EXIT3221226505", + "detail": "a=1 b=1", + "secs": 8.2, + "res": "" + }, + { + "plugin": "C:\\Program Files\\Common Files\\VST3\\iZotope\\Ozone 11 Dynamics.vst3", + "verdict": "EXIT3221226505", + "detail": "a=1 b=1", + "secs": 8.2, + "res": "" + }, + { + "plugin": "C:\\Program Files\\Common Files\\VST3\\iZotope\\Ozone 11 Equalizer.vst3", + "verdict": "EXIT3221226505", + "detail": "a=1 b=1", + "secs": 8.2, + "res": "" + }, + { + "plugin": "C:\\Program Files\\Common Files\\VST3\\iZotope\\Ozone 11 Exciter.vst3", + "verdict": "EXIT3221226505", + "detail": "a=1 b=1", + "secs": 8.2, + "res": "" + }, + { + "plugin": "C:\\Program Files\\Common Files\\VST3\\iZotope\\Ozone 11 Imager.vst3", + "verdict": "EXIT3221226505", + "detail": "a=1 b=1", + "secs": 8.2, + "res": "" + }, + { + "plugin": "C:\\Program Files\\Common Files\\VST3\\iZotope\\Ozone 11 Impact.vst3", + "verdict": "EXIT3221226505", + "detail": "a=1 b=1", + "secs": 8.2, + "res": "" + }, + { + "plugin": "C:\\Program Files\\Common Files\\VST3\\iZotope\\Ozone 11 Low End Focus.vst3", + "verdict": "EXIT3221226505", + "detail": "a=1 b=1", + "secs": 8.2, + "res": "" + }, + { + "plugin": "C:\\Program Files\\Common Files\\VST3\\iZotope\\Ozone 11 Master Rebalance.vst3", + "verdict": "EXIT3221226505", + "detail": "a=1 b=1", + "secs": 8.2, + "res": "" + }, + { + "plugin": "C:\\Program Files\\Common Files\\VST3\\iZotope\\Ozone 11 Match EQ.vst3", + "verdict": "EXIT3221226505", + "detail": "a=1 b=1", + "secs": 8.2, + "res": "" + }, + { + "plugin": "C:\\Program Files\\Common Files\\VST3\\iZotope\\Ozone 11 Maximizer.vst3", + "verdict": "EXIT3221226505", + "detail": "a=1 b=1", + "secs": 8.2, + "res": "" + }, + { + "plugin": "C:\\Program Files\\Common Files\\VST3\\iZotope\\Ozone 11 Spectral Shaper.vst3", + "verdict": "EXIT3221226505", + "detail": "a=1 b=1", + "secs": 8.2, + "res": "" + }, + { + "plugin": "C:\\Program Files\\Common Files\\VST3\\iZotope\\Ozone 11 Stabilizer.vst3", + "verdict": "EXIT3221226505", + "detail": "a=1 b=1", + "secs": 8.2, + "res": "" + }, + { + "plugin": "C:\\Program Files\\Common Files\\VST3\\iZotope\\Ozone 11 Vintage Compressor.vst3", + "verdict": "EXIT3221226505", + "detail": "a=1 b=1", + "secs": 8.2, + "res": "" + }, + { + "plugin": "C:\\Program Files\\Common Files\\VST3\\iZotope\\Ozone 11 Vintage EQ.vst3", + "verdict": "EXIT3221226505", + "detail": "a=1 b=1", + "secs": 8.2, + "res": "" + }, + { + "plugin": "C:\\Program Files\\Common Files\\VST3\\iZotope\\Ozone 11 Vintage Limiter.vst3", + "verdict": "EXIT3221226505", + "detail": "a=1 b=1", + "secs": 8.2, + "res": "" + }, + { + "plugin": "C:\\Program Files\\Common Files\\VST3\\iZotope\\Ozone 11 Vintage Tape.vst3", + "verdict": "EXIT3221226505", + "detail": "a=1 b=1", + "secs": 8.2, + "res": "" + }, + { + "plugin": "C:\\Program Files\\Common Files\\VST3\\iZotope\\Ozone 11.vst3", + "verdict": "EXIT3221226505", + "detail": "a=1 b=1", + "secs": 8.3, + "res": "" + }, + { + "plugin": "C:\\Program Files\\Common Files\\VST3\\iZotope\\RX 11 Breath Control.vst3", + "verdict": "EXIT3221226505", + "detail": "a=? b=?", + "secs": 8.2, + "res": "" + }, + { + "plugin": "C:\\Program Files\\Common Files\\VST3\\iZotope\\RX 11 Connect.vst3", + "verdict": "EXIT3221226505", + "detail": "a=? b=?", + "secs": 8.2, + "res": "" + }, + { + "plugin": "C:\\Program Files\\Common Files\\VST3\\iZotope\\RX 11 De-click.vst3", + "verdict": "EXIT3221226505", + "detail": "a=? b=?", + "secs": 8.2, + "res": "" + }, + { + "plugin": "C:\\Program Files\\Common Files\\VST3\\iZotope\\RX 11 De-clip.vst3", + "verdict": "EXIT3221226505", + "detail": "a=? b=?", + "secs": 8.2, + "res": "" + }, + { + "plugin": "C:\\Program Files\\Common Files\\VST3\\iZotope\\RX 11 De-crackle.vst3", + "verdict": "EXIT3221226505", + "detail": "a=? b=?", + "secs": 8.2, + "res": "" + }, + { + "plugin": "C:\\Program Files\\Common Files\\VST3\\iZotope\\RX 11 De-ess.vst3", + "verdict": "EXIT3221226505", + "detail": "a=? b=?", + "secs": 8.2, + "res": "" + }, + { + "plugin": "C:\\Program Files\\Common Files\\VST3\\iZotope\\RX 11 De-hum.vst3", + "verdict": "EXIT3221226505", + "detail": "a=? b=?", + "secs": 8.2, + "res": "" + }, + { + "plugin": "C:\\Program Files\\Common Files\\VST3\\iZotope\\RX 11 De-plosive.vst3", + "verdict": "EXIT3221226505", + "detail": "a=? b=?", + "secs": 8.1, + "res": "" + }, + { + "plugin": "C:\\Program Files\\Common Files\\VST3\\iZotope\\RX 11 De-reverb.vst3", + "verdict": "EXIT3221226505", + "detail": "a=? b=?", + "secs": 8.1, + "res": "" + }, + { + "plugin": "C:\\Program Files\\Common Files\\VST3\\iZotope\\RX 11 Dialogue Isolate.vst3", + "verdict": "EXIT3221226505", + "detail": "a=? b=?", + "secs": 8.2, + "res": "" + }, + { + "plugin": "C:\\Program Files\\Common Files\\VST3\\iZotope\\RX 11 Guitar De-noise.vst3", + "verdict": "EXIT3221226505", + "detail": "a=? b=?", + "secs": 8.2, + "res": "" + }, + { + "plugin": "C:\\Program Files\\Common Files\\VST3\\iZotope\\RX 11 Monitor.vst3", + "verdict": "EXIT3221226505", + "detail": "a=? b=?", + "secs": 8.2, + "res": "" + }, + { + "plugin": "C:\\Program Files\\Common Files\\VST3\\iZotope\\RX 11 Mouth De-click.vst3", + "verdict": "EXIT3221226505", + "detail": "a=? b=?", + "secs": 8.1, + "res": "" + }, + { + "plugin": "C:\\Program Files\\Common Files\\VST3\\iZotope\\RX 11 Repair Assistant.vst3", + "verdict": "EXIT3221226505", + "detail": "a=? b=?", + "secs": 8.2, + "res": "" + }, + { + "plugin": "C:\\Program Files\\Common Files\\VST3\\iZotope\\RX 11 Spectral De-noise.vst3", + "verdict": "EXIT3221226505", + "detail": "a=? b=?", + "secs": 8.2, + "res": "" + }, + { + "plugin": "C:\\Program Files\\Common Files\\VST3\\iZotope\\RX 11 Spectral Editor.vst3", + "verdict": "EXIT3221226505", + "detail": "a=? b=?", + "secs": 8.2, + "res": "" + }, + { + "plugin": "C:\\Program Files\\Common Files\\VST3\\iZotope\\RX 11 Voice De-noise.vst3", + "verdict": "EXIT3221226505", + "detail": "a=? b=?", + "secs": 8.2, + "res": "" + } +] \ No newline at end of file diff --git a/native_bridge/debug/test_g12_live.py b/native_bridge/debug/test_g12_live.py new file mode 100644 index 0000000..b72c87e --- /dev/null +++ b/native_bridge/debug/test_g12_live.py @@ -0,0 +1,181 @@ +# G1.2 verification against live app: single UiThread. +# Flow: PLAY(audio advancing) -> OPEN_GUI ch5 during PLAY (deferred, audio still alive) +# -> STOP (attach ch5) -> PLAY again -> OPEN_GUI ch6 (Option B closes ch5 editor +# synchronously, ch6 deferred) -> STOP (attach ch6) -> close ch6 editor via WM_CLOSE +# -> no EXCEPTION/FAILED/restart anywhere, writeIndex advancing while PLAY. +import ctypes, ctypes.wintypes as w, struct, subprocess, sys, os, time + +sys.stdout.reconfigure(encoding='utf-8', errors='replace') + +APP_PID = int(sys.argv[1]) +SHM_NAME = f"SonicForge_DAW_IPC_{APP_PID}".encode() +APP_LOG = os.path.join(os.environ.get('APPDATA', '.'), 'SonicForgeDAW', 'logs', 'bridge.log') +NEXUS = r"C:\Program Files\Common Files\VST3\Nexus.vst3" + +kernel32 = ctypes.WinDLL('kernel32', use_last_error=True) +kernel32.OpenFileMappingA.argtypes = [ctypes.wintypes.DWORD, w.BOOL, ctypes.c_char_p] +kernel32.OpenFileMappingA.restype = ctypes.c_void_p +kernel32.MapViewOfFile.argtypes = [ctypes.c_void_p, ctypes.wintypes.DWORD, ctypes.wintypes.DWORD, ctypes.wintypes.DWORD, ctypes.c_size_t] +kernel32.MapViewOfFile.restype = ctypes.c_void_p +hMap = kernel32.OpenFileMappingA(0xF001F, False, SHM_NAME) +if not hMap: + print(f"OpenFileMapping failed err={ctypes.get_last_error()}"); sys.exit(1) +ptr = kernel32.MapViewOfFile(hMap, 0xF001F, 0, 0, 0) +print(f"SHM mapped ({SHM_NAME})") + +def w32(off, fmt, *args): + ctypes.memmove(ptr + off, struct.pack(fmt, *args), struct.calcsize(fmt)) +def rd32(off): + return ctypes.c_uint32.from_address(ptr + off).value + +def write_control(ctype, arg0, arg1, ch, path): + for i in range(8): + off = 2836 + i * 1040 + if rd32(off) == 0: + w32(off, 'IIII', ctype, arg0, arg1, ch) + if path: + b = path.encode('utf-8')[:1023] + b'\x00' + ctypes.memmove(ptr + off + 16, b, len(b)) + return i + raise RuntimeError("control queue full") + +def push_controls(events): + for i in range(8): + w32(2836 + i * 1040, 'I', 0) + for (ctype, arg0, arg1, ch, path) in events: + write_control(ctype, arg0, arg1, ch, path) + w32(11156, 'I', len(events)) + +def log_lines(): + try: + with open(APP_LOG, 'r', encoding='utf-8', errors='replace') as f: + return f.readlines() + except FileNotFoundError: + return [] + +def wait_log(pattern, timeout, since, verbose=True): + end = time.time() + timeout + while time.time() < end: + lines = log_lines() + for i in range(since, len(lines)): + if pattern in lines[i]: + if verbose: print(f" LOG: {lines[i].strip()}") + return lines[i].strip() + time.sleep(0.2) + return None + +def write_index(): + return rd32(4) + +def sample_wi(seconds): + a = write_index(); time.sleep(seconds); b = write_index() + return a, b + +def bridge_pid(): + try: + out = subprocess.run(["cmd", "/c", "tasklist /FI \"IMAGENAME eq daw_vst_bridge.exe\" /FO CSV"], + capture_output=True, text=True).stdout + for line in out.splitlines()[1:]: + if "daw_vst_bridge" in line: + return line.split(',')[1].strip('"') + except Exception: + pass + return None + +bad = [] +def collect_bad(since): + for l in log_lines()[since:]: + if any(k in l for k in ("EXCEPTION", "FAILED", "restarted")): + bad.append(l.strip()) + +since = len(log_lines()) +pid0 = bridge_pid() +print("bridge pid:", pid0) + +# --- Phase A: PLAY, audio advancing --- +push_controls([(3, 1, 0, 0, "")]) +r = wait_log("transport PLAY", 10, since) +print("A transport:", r or "TIMEOUT") +a, b = sample_wi(3) +print(f"A writeIndex {a}->{b} delta={b-a} ({'ADVANCING' if b > a else 'STALLED'})") +if b <= a: bad.append("A: writeIndex stalled during PLAY") + +# --- Phase B: OPEN_GUI ch5 during PLAY (expect deferred) --- +since = len(log_lines()) +push_controls([(4, 0, 0, 5, "Nexus")]) +r = wait_log("transport PLAY) - queued", 10, since) +print("B defer ch5:", r or "TIMEOUT") +a, b = sample_wi(3) +print(f"B writeIndex {a}->{b} delta={b-a} ({'ADVANCING' if b > a else 'STALLED'})") +if b <= a: bad.append("B: writeIndex stalled while GUI deferred") +collect_bad(since) + +# --- Phase C: STOP -> attach ch5 --- +since = len(log_lines()) +push_controls([(3, 0, 0, 0, "")]) +r = wait_log("transport STOP", 10, since) +print("C transport:", r or "TIMEOUT") +r = wait_log("GUI attached hwnd=", 30, since) +print("C attach ch5:", r or "TIMEOUT") + +# --- Phase D: PLAY again -> OPEN_GUI ch6 (Option B synchronous close of ch5) --- +since = len(log_lines()) +push_controls([(3, 1, 0, 0, "")]) +r = wait_log("transport PLAY", 10, since) +print("D transport:", r or "TIMEOUT") +a, b = sample_wi(2) +print(f"D writeIndex {a}->{b} delta={b-a} ({'ADVANCING' if b > a else 'STALLED'})") +if b <= a: bad.append("D: writeIndex stalled during PLAY #2") +since = len(log_lines()) +push_controls([(4, 0, 0, 6, "Nexus")]) +r = wait_log("transport PLAY) - queued", 10, since) +print("D defer ch6:", r or "TIMEOUT (maybe attach directly?)") +time.sleep(1) +collect_bad(since) +# ch5 editor must be closed (Option B) — check log for close editor of ch5 +closehit = wait_log("closeGUI", 15, since, verbose=False) or "" +print("D close ch5 editor:", closehit or "no closeGUI log (check)") + +# --- Phase E: STOP -> attach ch6 --- +since = len(log_lines()) +push_controls([(3, 0, 0, 0, "")]) +r = wait_log("transport STOP", 10, since) +print("E transport:", r or "TIMEOUT") +r = wait_log("GUI attached hwnd=", 30, since) +print("E attach ch6:", r or "TIMEOUT") +collect_bad(since) + +# --- Phase F: close ch6 editor via WM_CLOSE (post_close_gui path) --- +since = len(log_lines()) +# find ch6 hwnd from log +hwnd6 = None +for l in log_lines(): + if "ch=6" in l and "GUI attached" in l: + import re + m = re.search(r'hwnd=([0-9A-Fa-f]+)', l) + if m: hwnd6 = int(m.group(1), 16) +if hwnd6: + ctypes.windll.user32.PostMessageW(hwnd6, 0x0010, 0, 0) # WM_CLOSE + print("F posted WM_CLOSE to ch6 hwnd", hex(hwnd6)) +else: + print("F hwnd6 not found") +r = wait_log("closeGUI reload", 30, since) +print("F close ch6:", r or "TIMEOUT") +collect_bad(since) + +# --- watch 8s for crash/restart --- +since = len(log_lines()) +end = time.time() + 8 +while time.time() < end: + collect_bad(since) + pid = bridge_pid() + if pid != pid0: + bad.append(f"BRIDGE RESTART {pid0}->{pid}") + break + time.sleep(0.2) +print() +if bad: + print("G1.2 RESULT: FAIL") + for x in bad[:20]: print(" -", x) +else: + print("G1.2 RESULT: PASS - 1 UiThread, defer OK, Option B OK, close OK, audio advancing, no crash") diff --git a/native_bridge/debug/test_g13_live.py b/native_bridge/debug/test_g13_live.py new file mode 100644 index 0000000..d3f69d3 --- /dev/null +++ b/native_bridge/debug/test_g13_live.py @@ -0,0 +1,189 @@ +# G1.3 verification: pump gate removed, single UiThread. +# Stress: resize (WM_SIZE) editor, LOAD replacing instrument while editor open, +# close/reopen cycles, Option B, PLAY audio advancing throughout, no crash. +import ctypes, ctypes.wintypes as w, struct, subprocess, sys, os, time, re + +sys.stdout.reconfigure(encoding='utf-8', errors='replace') + +APP_PID = int(sys.argv[1]) +SHM_NAME = f"SonicForge_DAW_IPC_{APP_PID}".encode() +APP_LOG = os.path.join(os.environ.get('APPDATA', '.'), 'SonicForgeDAW', 'logs', 'bridge.log') +NEXUS = r"C:\Program Files\Common Files\VST3\Nexus.vst3" +EZKEYS = r"C:\Program Files\Common Files\VST3\Toontrack\EZkeys 2.vst3" + +kernel32 = ctypes.WinDLL('kernel32', use_last_error=True) +kernel32.OpenFileMappingA.argtypes = [ctypes.wintypes.DWORD, w.BOOL, ctypes.c_char_p] +kernel32.OpenFileMappingA.restype = ctypes.c_void_p +kernel32.MapViewOfFile.argtypes = [ctypes.c_void_p, ctypes.wintypes.DWORD, ctypes.wintypes.DWORD, ctypes.wintypes.DWORD, ctypes.c_size_t] +kernel32.MapViewOfFile.restype = ctypes.c_void_p +hMap = kernel32.OpenFileMappingA(0xF001F, False, SHM_NAME) +if not hMap: + print(f"OpenFileMapping failed err={ctypes.get_last_error()}"); sys.exit(1) +ptr = kernel32.MapViewOfFile(hMap, 0xF001F, 0, 0, 0) +user32 = ctypes.windll.user32 + +def w32(off, fmt, *args): + ctypes.memmove(ptr + off, struct.pack(fmt, *args), struct.calcsize(fmt)) +def rd32(off): + return ctypes.c_uint32.from_address(ptr + off).value + +def write_control(ctype, arg0, arg1, ch, path): + for i in range(8): + off = 2836 + i * 1040 + if rd32(off) == 0: + w32(off, 'IIII', ctype, arg0, arg1, ch) + if path: + b = path.encode('utf-8')[:1023] + b'\x00' + ctypes.memmove(ptr + off + 16, b, len(b)) + return i + raise RuntimeError("control queue full") + +def push_controls(events): + for i in range(8): + w32(2836 + i * 1040, 'I', 0) + for (ctype, arg0, arg1, ch, path) in events: + write_control(ctype, arg0, arg1, ch, path) + w32(11156, 'I', len(events)) + +def log_lines(): + try: + with open(APP_LOG, 'r', encoding='utf-8', errors='replace') as f: + return f.readlines() + except FileNotFoundError: + return [] + +def wait_log(pattern, timeout, since, verbose=True): + end = time.time() + timeout + while time.time() < end: + lines = log_lines() + for i in range(since, len(lines)): + if pattern in lines[i]: + if verbose: print(f" LOG: {lines[i].strip()}") + return lines[i].strip() + time.sleep(0.2) + return None + +def write_index(): + return rd32(4) + +def bridge_alive(pid0): + try: + out = subprocess.run(["tasklist"], capture_output=True, text=True).stdout + for line in out.splitlines(): + p = line.split() + if len(p) >= 2 and p[0] == "daw_vst_bridge.exe" and p[1] == pid0: + return True + except Exception: + pass + return False + +bad = [] +def collect_bad(since): + for l in log_lines()[since:]: + if any(k in l for k in ("EXCEPTION", "FAILED", "restarted")): + bad.append(l.strip()) + +def find_hwnd(ch): + last = None + for l in log_lines(): + if f"ch={ch}" in l and "GUI attached" in l: + m = re.search(r'hwnd=([0-9A-Fa-f]+)', l) + if m: last = int(m.group(1), 16) + return last + +since0 = len(log_lines()) +# LOAD 6 Nexus + EZkeys (state may differ per run; just LOAD the needed ones) +for ch in range(6): + since = len(log_lines()) + push_controls([(2, 0, 0, ch, NEXUS)]) + r = wait_log(f"instrument loaded ch={ch}", 60, since, verbose=False) + if not r: print(f" WARN load ch={ch} timeout") +print("LOAD 6 Nexus done") + +# --- T1: OPEN_GUI ch5, wait attach --- +since = len(log_lines()) +push_controls([(4, 0, 0, 5, "Nexus")]) +r = wait_log("GUI attached", 30, since) +print("T1 attach ch5:", "OK" if r else "TIMEOUT") +collect_bad(since) +hwnd5 = find_hwnd(5) +print("T1 hwnd5:", hex(hwnd5) if hwnd5 else "None") + +# --- T2: resize (WM_SIZE) while attached --- +since = len(log_lines()) +if hwnd5: + user32.SendMessageW(hwnd5, 0x0005, 0, (900 << 16) | 600) # WM_SIZE w=900 h=600 + time.sleep(1.5) +print("T2 resize sent; log tail:") +for l in log_lines()[-4:]: print(" ", l.strip()) +collect_bad(since) + +# --- T3: PLAY with editor open, audio advancing --- +since = len(log_lines()) +push_controls([(3, 1, 0, 0, "")]) +r = wait_log("transport PLAY", 10, since) +print("T3 transport:", r or "TIMEOUT") +a, b = write_index(), 0 +time.sleep(3); b = write_index() +print(f"T3 writeIndex {a}->{b} delta={b-a} ({'ADVANCING' if b > a else 'STALLED'})") +if b <= a: bad.append("T3: writeIndex stalled with editor open") +collect_bad(since) + +# --- T4: LOAD EZkeys on ch5 WHILE editor open + PLAYING (replace inst with editor) --- +since = len(log_lines()) +push_controls([(2, 0, 0, 5, EZKEYS)]) +r = wait_log("instrument loaded ch=5", 60, since) +print("T4 load ch5 (EZkeys) while editor open:", "OK" if r else "TIMEOUT") +collect_bad(since) +a, b = write_index(), 0 +time.sleep(2); b = write_index() +print(f"T4 writeIndex {a}->{b} delta={b-a} ({'ADVANCING' if b > a else 'STALLED'})") +if b <= a: bad.append("T4: writeIndex stalled after LOAD while editor open") + +# --- T5: STOP, close ch5 editor (WM_CLOSE), reopen --- +since = len(log_lines()) +push_controls([(3, 0, 0, 0, "")]) +wait_log("transport STOP", 10, since) +hwnd5b = find_hwnd(5) +if hwnd5b: + since = len(log_lines()) + user32.PostMessageW(hwnd5b, 0x0010, 0, 0) # WM_CLOSE + r = wait_log("state restored ch=5", 40, since) + print("T5 close ch5:", "OK" if r else "TIMEOUT") +collect_bad(since) +# reopen ch5 (now EZkeys) +since = len(log_lines()) +push_controls([(4, 0, 0, 5, "EZkeys 2")]) +r = wait_log("GUI attached", 30, since) +print("T5 reopen ch5:", "OK" if r else "TIMEOUT") +collect_bad(since) + +# --- T6: 3x close/reopen cycle ch5 --- +for i in range(3): + hwnd = find_hwnd(5) + if not hwnd: print(f"T6 iter{i}: no hwnd"); break + since = len(log_lines()) + user32.PostMessageW(hwnd, 0x0010, 0, 0) + r = wait_log("state restored ch=5", 40, since, verbose=False) + since = len(log_lines()) + push_controls([(4, 0, 0, 5, "EZkeys 2")]) + r2 = wait_log("GUI attached", 30, since, verbose=False) + print(f"T6 iter{i}: close={'OK' if r else 'TIMEOUT'} reopen={'OK' if r2 else 'TIMEOUT'}") + if not r or not r2: + bad.append(f"T6 iter{i}: close/reopen fail"); break + collect_bad(since) + +# --- T7: watch 8s --- +since = len(log_lines()) +end = time.time() + 8 +while time.time() < end: + collect_bad(since) + if not bridge_alive(str(APP_PID and 0)): + pass + time.sleep(0.2) +print() +if bad: + print("G1.3 RESULT: FAIL") + for x in bad[:20]: print(" -", x) +else: + print("G1.3 RESULT: PASS - resize OK, LOAD-while-editor OK, close/reopen x4 OK, no crash") diff --git a/native_bridge/debug/test_g15_stress.py b/native_bridge/debug/test_g15_stress.py new file mode 100644 index 0000000..a0a1f77 --- /dev/null +++ b/native_bridge/debug/test_g15_stress.py @@ -0,0 +1,159 @@ +# G1.5 regression stress driver: N iterations of LOAD/OPEN_GUI/PLAY/STOP/WM_CLOSE +# on Nexus + EZkeys. Watch: bridge process alive, no new EXCEPTION/FAILED, writeIndex advancing. +import ctypes, ctypes.wintypes as w, struct, subprocess, sys, os, time, re + +sys.stdout.reconfigure(encoding='utf-8', errors='replace') + +APP_PID = int(sys.argv[1]) +ITERS = int(sys.argv[2]) if len(sys.argv) > 2 else 5 +SHM_NAME = f"SonicForge_DAW_IPC_{APP_PID}".encode() +APP_LOG = os.path.join(os.environ.get('APPDATA', '.'), 'SonicForgeDAW', 'logs', 'bridge.log') +NEXUS = r"C:\Program Files\Common Files\VST3\Nexus.vst3" +EZKEYS = r"C:\Program Files\Common Files\VST3\Toontrack\EZkeys 2.vst3" + +kernel32 = ctypes.WinDLL('kernel32', use_last_error=True) +kernel32.OpenFileMappingA.argtypes = [ctypes.wintypes.DWORD, w.BOOL, ctypes.c_char_p] +kernel32.OpenFileMappingA.restype = ctypes.c_void_p +kernel32.MapViewOfFile.argtypes = [ctypes.c_void_p, ctypes.wintypes.DWORD, ctypes.wintypes.DWORD, ctypes.wintypes.DWORD, ctypes.c_size_t] +kernel32.MapViewOfFile.restype = ctypes.c_void_p +hMap = kernel32.OpenFileMappingA(0xF001F, False, SHM_NAME) +if not hMap: + print(f"OpenFileMapping failed err={ctypes.get_last_error()}"); sys.exit(1) +ptr = kernel32.MapViewOfFile(hMap, 0xF001F, 0, 0, 0) +print(f"SHM mapped ({SHM_NAME}) iters={ITERS}") + +def w32(off, fmt, *args): + ctypes.memmove(ptr + off, struct.pack(fmt, *args), struct.calcsize(fmt)) +def rd32(off): + return ctypes.c_uint32.from_address(ptr + off).value + +def write_control(ctype, arg0, arg1, ch, path): + for i in range(8): + off = 2836 + i * 1040 + if rd32(off) == 0: + w32(off, 'IIII', ctype, arg0, arg1, ch) + if path: + b = path.encode('utf-8')[:1023] + b'\x00' + ctypes.memmove(ptr + off + 16, b, len(b)) + return i + raise RuntimeError("control queue full") + +def push_controls(events): + for i in range(8): + w32(2836 + i * 1040, 'I', 0) + for (ctype, arg0, arg1, ch, path) in events: + write_control(ctype, arg0, arg1, ch, path) + w32(11156, 'I', len(events)) + +def log_lines(): + try: + with open(APP_LOG, 'r', encoding='utf-8', errors='replace') as f: + return f.readlines() + except FileNotFoundError: + return [] + +def wait_log(pattern, timeout, since): + end = time.time() + timeout + while time.time() < end: + for i in range(since, len(log_lines())): + if pattern in log_lines()[i]: + return log_lines()[i] + time.sleep(0.2) + return None + +def wait_gui(ch, timeout, since): + end = time.time() + timeout + while time.time() < end: + for i in range(since, len(log_lines())): + l = log_lines()[i] + if "GUI attached hwnd=" in l and f"ch={ch}" in l: + return l + time.sleep(0.2) + return None + +def bridge_pid(): + out = subprocess.run(['powershell', '-Command', + '(Get-Process daw_vst_bridge -ErrorAction SilentlyContinue).Id'], + capture_output=True, text=True).stdout.strip().splitlines() + return [int(x) for x in out if x.strip().isdigit()] + +def bad_since(since): + return [l for l in log_lines()[since:] if 'EXCEPTION' in l or 'FAILED' in l] + +def find_hwnd(ch): + last = None + for l in log_lines(): + if "GUI attached hwnd=" in l and f"ch={ch}" in l: + m = re.search(r'hwnd=([0-9A-Fa-f]+)', l) + if m: last = int(m.group(1), 16) + return last + +fails = 0 +for it in range(ITERS): + print(f"--- iter {it} ---") + since = len(log_lines()) + # LOAD Nexus ch5 + push_controls([(2, 0, 0, 5, NEXUS)]) + if not wait_log("instrument loaded ch=5 type=0", 30, since): + print(f"iter{it} LOAD Nexus TIMEOUT"); fails += 1; continue + # OPEN_GUI ch5 + push_controls([(4, 0, 0, 5, NEXUS)]) + if not wait_gui(5, 30, since): + print(f"iter{it} OPEN_GUI Nexus TIMEOUT"); fails += 1; continue + # PLAY -> writeIndex advancing while editor open (G1.4 core) + wi0 = rd32(4) + push_controls([(3, 1, 0, 0, "")]) + if not wait_log("transport PLAY", 10, since): + print(f"iter{it} PLAY TIMEOUT"); fails += 1; continue + time.sleep(1.5) + wi1 = rd32(4) + ok = (wi1 - wi0) > 50 + print(f"iter{it} PLAY editor-open writeIndex {wi0}->{wi1} delta={wi1-wi0} {'OK' if ok else 'STALLED'}") + if not ok: fails += 1 + # STOP + push_controls([(3, 0, 0, 0, "")]) + if not wait_log("transport STOP", 10, since): + print(f"iter{it} STOP TIMEOUT"); fails += 1; continue + # WM_CLOSE ch5 -> state restored + hwnd = find_hwnd(5) + if hwnd: + ctypes.windll.user32.PostMessageW(hwnd, 0x0010, 0, 0) + r = wait_log("state restored ch=5", 30, since) + print(f"iter{it} WM_CLOSE Nexus hwnd={hex(hwnd)}: {'OK' if r else 'TIMEOUT'}") + if not r: fails += 1 + else: + print(f"iter{it} hwnd Nexus not found"); fails += 1 + # LOAD EZkeys ch5 + OPEN_GUI + PLAY + STOP + CLOSE + since = len(log_lines()) + push_controls([(2, 0, 0, 5, EZKEYS)]) + if not wait_log("instrument loaded ch=5 type=0", 30, since): + print(f"iter{it} LOAD EZkeys TIMEOUT"); fails += 1; continue + push_controls([(4, 0, 0, 5, EZKEYS)]) + if not wait_gui(5, 30, since): + print(f"iter{it} OPEN_GUI EZkeys TIMEOUT"); fails += 1; continue + push_controls([(3, 1, 0, 0, "")]) + wait_log("transport PLAY", 10, since) + time.sleep(1.5) + push_controls([(3, 0, 0, 0, "")]) + wait_log("transport STOP", 10, since) + hwnd = find_hwnd(5) + if hwnd: + ctypes.windll.user32.PostMessageW(hwnd, 0x0010, 0, 0) + r = wait_log("state restored ch=5", 30, since) + print(f"iter{it} WM_CLOSE EZkeys hwnd={hex(hwnd)}: {'OK' if r else 'TIMEOUT'}") + if not r: fails += 1 + else: + print(f"iter{it} hwnd EZkeys not found"); fails += 1 + bad = bad_since(since) + if bad: + print(f"iter{it} EXCEPTION/FAILED: {bad[-2:]}"); fails += 1 + +# final watch: bridge alive + no new bad +since = len(log_lines()) +time.sleep(3) +bad = bad_since(since) +alive = bridge_pid() +print(f"bridge alive: {alive} (expect non-empty)") if alive else print("BRIDGE DEAD") +if not alive: fails += 1 +print(f"G1.5 RESULT: {'PASS' if fails == 0 else f'FAIL ({fails} issues)'}") +sys.exit(0 if fails == 0 else 1)