9.0 KiB
9.0 KiB
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_<APP_PID>; 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 +__bridgePlayingguard (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]
- Patch gui_probe.cpp: usage string, branch
variant == "shared_worker" || variant == "ui_thread", isDeferred list. - Fix build: CMakeLists gui_probe target thêm
memorystream.cpp+sdk_common(LNK2019). - 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. Commit3587a34.
G1.2 — Chuyển tạo window + attach job từ ChannelWorker → UiThread mới (1 thread chung) [HOÀN THÀNH — 9e6e72a]
- Thay per-channel workers (16) bằng 1 UiThread: COM STA + message pump + job queue.
- Audio loop bỏ PeekMessageW.
- 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).
- 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]
- channelQuiet chi con reloading (bo editorOpenFn check); xoa setEditorOpenPredicate/g_editorOpen/g_editorPathCount/clear_editor_open.
- 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]
- stress driver native_bridge/debug/test_g15_stress.py: 5 + 10 iterations LOAD/OPEN_GUI/PLAY/STOP/WM_CLOSE Nexus+EZkeys.
- 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 -nqua 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
sincewindow, 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.
sleepkhông tồn tại trong sh →python -c "import time; time.sleep(N)".timeoutWindows khác GNU → dùng python subprocess wrapper.- CRLF:
s.count(old)với\nthật trong heredoc<<'EOF'KHÔNG khớp CRLF; dùng script .py file thay vìpython -cchuỗ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).