15 KiB
15 KiB
20260815 — KẾ HOẠCH THỰC HIỆN G4 (SANDBOX PLUGIN / PROCESS CON)
Nguồn: big_tasks.md — header G4 — Sandbox plugin (3-5x G1+G2, 2-3 tháng).
Trạng thái roadmap: G0 ✅, G1 ✅, G2 ✅, G3 ✅ (G3.1–G3.3). Branch: standalone-shm-bridge.
User chủ động yêu cầu: làm dirty trước, sau đó làm luôn G4.
1. PHÂN TÍCH G4
1.1 Mục tiêu
VST3 chạy trong process con (plugin_host.exe) — crash plugin chỉ chết process con,
bridge + GUI + các plugin khác sống. VST3 không có chuẩn out-of-process như AUv3 →
wrapper tự làm.
1.2 Hiện trạng (đã verify trong code)
| Thành phần | File | Ghi chú |
|---|---|---|
| VST3 host | native_bridge/src/Vst3Instrument.cpp + include/Vst3Instrument.h |
pimpl (Vst3HostState), độc lập, reuse được trong process con: loadPlugin(path, sr) (tự activate + setupProcessing với maxBlockSize_), openGUI(HWND), processAudioBlock(L,R,n) (zero output buffer trước process, copy ra ngoài), MIDI dispatch noteOn/noteOff/controlChange/programChange/pitchBend, closeGUI(), reload(). |
| SHM layout | include/SharedMemoryIPC.h |
SharedAudioBufferIPC 11168B: bridgeWriteIndex@4, masterLeft@8 (float[256]), masterRight@1032, midiQueue[64]@2064 (12B/evt), midiQueueCount@2832, controlQueue[8]@2836, heartbeat@11164 (G3.1). Helpers shm_open/shm_create/shm_ptr/shm_close/shm_write_midi/shm_write_control trong SharedMemoryIPC.cpp (trước G4.1 không có prototype trong header — đã bổ sung). |
| CMake target pattern | native_bridge/CMakeLists.txt |
gui_probe/seh_channel_test: HAVE_VST3SDK=1 + ${VST3_SDK_TARGET} sdk_hosting sdk_common + module_win32.cpp + memorystream.cpp + ${FLUIDSYNTH_LIBRARY} ${SFIZZ_LIBRARY} winmm. |
1.3 Khoảng trống (gap)
- VST3 chạy cùng process bridge → 1 plugin crash có thể hạ bridge (đã giảm thiểu phần nào bằng SEH G3.3 nhưng vẫn chung process).
- Chưa có cơ chế spawn con + SHM con (MIDI in / audio out / heartbeat) + giám sát respawn.
1.4 Nguyên tắc thiết kế (đề xuất)
- Mỗi plugin 1 process con
plugin_host.exe, argv:--shm <name> --path <vst3> [--sr] [--block] [--parent <pid>]. - Con tự tạo mapping SHM riêng (tái dùng layout
SharedAudioBufferIPC— không phát sinh struct mới, bridge đọc được nguyên layout). - GUI attach trên main thread con (editorhost pattern: attach rồi pump); heartbeat ~10Hz thread riêng (pattern G3.1).
- Con exit khi parent chết (kiểm tra
parent_alivemỗi block) — chống orphan. - G4.2/G4.3: bridge mở mapping con thay vì con tự tạo; pool + watchdog respawn; ring buffer realtime.
2. PHÂN RÃ TASKS + ESTIMATE
| ID | Task | File đích | Nội dung | Estimate |
|---|---|---|---|---|
| G4.1 | PoC plugin_host độc lập | src/plugin_host_main.cpp, CMakeLists.txt, include/SharedMemoryIPC.h |
argv(shm,path,sr,block,parent) → COM STA → load VST3 → GUI attach → audio loop qua SHM (drain MIDI → dispatch → processAudioBlock → ghi masterLeft/Right → bridgeWriteIndex++) → heartbeat thread → exit khi parent chết. Done khi: Nexus chạy trong con, GUI + audio OK, kill con → chỉ con chết. | 1-2 ngày |
| G4.2 | Bridge → client pool | main.cpp (bridge) |
bridge spawn 1 con/plugin, forward MIDI, đọc audio mix, watchdog (crash → respawn, mute/restore như G3.3). | 2-3 tuần |
| G4.3 | IPC audio realtime | SHM ring buffer | double-buffer SHM con, latency <20ms, 6 Nexus song song không xrun. | 2-3 tuần |
Phụ thuộc
G4.2 → G4.1 (cần plugin_host chạy được độc lập) · G4.3 → G4.2.
Thứ tự triển khai khuyến nghị
G4.1 → G4.2 → G4.3.
3. KẾ HOẠCH THỰC HIỆN (G4.1 PoC — hoàn tất hôm nay)
G4.1a — Đọc code, chốt API host
Vst3Instrument:init(sr, block)setsampleRate_/maxBlockSize_(gọi trướcloadPlugin— loadPlugin dùngmaxBlockSize_trongsetupProcessing; default 256 = AUDIO_BLOCK_SIZE).loadPlugintự activate + setProcessing + prepare.processAudioBlockzero output rồi process, copy stereo ra ngoài, clear eventList.- Drain MIDI theo convention bridge (main.cpp): snapshot
midiQueue[0..count-1](bounded 64), resetmidiQueueCount=0, dispatch theo command 0x9/0x8/0xB/0xC/0xE. - Heartbeat: thread riêng
Sleep(100)→heartbeat++(pattern G3.1).
G4.1b — Viết plugin_host_main.cpp
- Parse argv
--shm/--path/--sr/--block/--parent. CoInitializeEx(STA)trên main thread (GUI thuộc main thread con).shm_create(name)— PoC: con sở hữu mapping (G4.2 đổi thành mở).init(sr,block)→loadPlugin(path,sr)→ tạo own top-level window →openGUI(hwnd)(editorhost pattern) → heartbeat thread → main loop: pump messages + drain MIDI +processAudioBlock+bridgeWriteIndex+++parent_alivecheck →closeGUI()+CoUninitialize()+shm_close.- ASCII-only, LF → normalize CRLF (repo convention).
G4.1c — CMake target plugin_host + build
- Copy pattern
gui_probe:src/plugin_host_main.cpp + src/SharedMemoryIPC.cpp + src/Vst3Instrument.cpp+ SDK hosting libs (không cần NativeInstrumentEngine.cpp). - Bổ sung prototype
shm_open/shm_create/shm_ptr/shm_closevàoSharedMemoryIPC.h(trước đây chỉ có trong .cpp, plugin_host cần khai báo). - Build:
cmake --build build --config Release --target plugin_host -- /mPASS →build/Release/plugin_host.exe.
G4.1d — Probe với Nexus (probe_g41.py)
- Spawn con với
C:\Program Files\Common Files\VST3\Nexus.vst3, SHMSonicForge_PluginHost_Probe. - Verify: heartbeat tăng (hb=1 trong ~1s); GUI window xuất hiện (4 hwnd thuộc pid con, gồm PluginHostClass + editor Nexus); ghi note-on C4 → audio peak 0.38274 (nonzero); terminate con → app 15928 + bridge 20996 vẫn sống (isolation).
G4.1e — Commit + journal + push
4. RỦI RO
| Rủi ro | Ảnh hưởng | Giảm thiểu |
|---|---|---|
| Con chết khi đang render → dữ liệu audio mất 1 block | Click/pop | G4.3 ring buffer + G4.2 mute/restore; PoC chấp nhận. |
| Parent chết → con orphan | Process treo | --parent + check mỗi block (đã làm). |
| Plugin GUI yêu cầu thread/COM riêng | Hang/blank view | Attach trên main thread con (editorhost pattern đã verify qua gui_probe). |
| SHM con đổi layout sau này | Mất tương thích | Tái dùng SharedAudioBufferIPC — layout ổn định append-only. |
5. BÁO CÁO KẾT QUẢ (đã thực hiện)
| ID | Cài đặt | Kiểm tra | Bugs bắt được | Fix | Trạng thái |
|---|---|---|---|---|---|
| G4.1a | Chốt API host từ Vst3Instrument + convention drain MIDI/heartbeat (main.cpp). | — | — | — | ✅ |
| G4.1b | plugin_host_main.cpp: argv parse, COM STA, shm_create, loadPlugin, openGUI own window, audio loop + heartbeat + parent check. |
Build PASS (sau fix prototype SHM). | ShmHandle/shm_create/shm_ptr/shm_close không có prototype trong header → C2065. |
Thêm prototype vào SharedMemoryIPC.h. |
✅ |
| G4.1c | CMake target plugin_host (pattern gui_probe, không engine). |
Build PASS → build/Release/plugin_host.exe. |
Patch block bị chèn vào giữa if(WIN32) của gui_probe + endif() thừa. |
Script sửa vị trí + xóa endif. | ✅ |
| G4.1d | Probe: spawn + Nexus + heartbeat + GUI + audio + isolation. | PASS: hb=1 (1s); 4 hwnd; peak 0.38274; kill con → app 15928 + bridge 20996 sống. | Probe python đọc SHM AV — MapViewOfFile restype mặc định c_int cắt con trỏ 64-bit. |
Set restype=c_void_p. |
✅ |
| G4.1e | Commit + journal + push. | — | — | — | ✅ |
Nhật ký thực hiện
G4.1 PoC — Hoàn tất (2026-08-15)
native_bridge/src/plugin_host_main.cpp(mới): process con sandbox — argv--shm/--path/--sr/--block/--parent; COM STA;shm_create(PoC con sở hữu mapping, G4.2 đổi thànhshm_open);init→loadPlugin→ own window +openGUI(editorhost pattern) → heartbeat threadSleep(100)(G3.1 pattern) → main loop pump + drain MIDI (snapshot 64, reset count, dispatch 0x9/0x8/0xB/0xC/0xE) +processAudioBlock(masterLeft, masterRight, block)+bridgeWriteIndex+++parent_alivecheck mỗi block (chống orphan) →closeGUI+CoUninitialize+shm_close.include/SharedMemoryIPC.h: bổ sung prototype helpersshm_open/shm_create/shm_ptr/shm_close(chỉ khai báo, không đổi layout — struct giữ nguyên 11168B).CMakeLists.txt: targetplugin_host(src/plugin_host_main.cpp + SharedMemoryIPC.cpp + Vst3Instrument.cpp + SDK hosting libs). Build Release PASS.- Verify (probe_g41.py): Nexus (
C:\Program Files\Common Files\VST3\Nexus.vst3) load trong con OK; heartbeat tăng hb=1 sau ~1s; GUI window 4 hwnd thuộc pid con; note-on C4 (vel 100) → peak audio 0.38274 (nonzero); terminate con →sonicforge-daw.exe15928 +daw_vst_bridge.exe20996 vẫn sống — isolation đúng yêu cầu G4.1. - Lưu ý test-env: probe python phải set
OpenFileMappingA.restype = MapViewOfFile.restype = c_void_p(Win64 con trỏ 64-bit, mặc định c_int cắt → AV khi đọc view). - G4.2 (bridge → pool) chưa bắt đầu — bước tiếp theo sau PoC PASS.
G4.2 — Bridge → client process pool (sandbox per channel) ✅
include/SandboxVst3Host.h+src/SandboxVst3Host.cpp(mới):INativeInstrumentwrapper quanh plugin_host.exe.loadPlugin(path, sr, channel)→shm_create("SonicForge_PluginHost_<bridgePid>_<channel>")+CreateProcessAplugin_host.exe (cạnh daw_vst_bridge.exe, cùng install/) với--open --shm ... --path ... --sr ... --block ... --parent <bridgePid>; chờ child heartbeat ≤60s. MIDI noteOn/noteOff/controlChange/programChange/pitchBend →shm_write_midi(velocity float→0..127).processAudioBlockcopy snapshot masterLeft/Right (G4.3 thay bằng ring buffer).watchdogLoop(thread riêng): Sleep 500ms, child chết →alive_=false+ log + respawn sau 1s; 3 fail liên tiếp → mute vĩnh viễn đến khi reload. Destructor: stop_ + join + TerminateProcess + shm_close.src/plugin_host_main.cpp: thêm flag--open→shm_openthay vìshm_create(bridge tạo mapping, con mở).include/SharedMemoryIPC.h: thêm prototypeshm_write_midi(default args data2/data3 chỉ ở header); bỏ default args trùng trongSharedMemoryIPC.cpp(C2572).src/NativeInstrumentEngine.cpp:create_instrumentVST3 case —SF_SANDBOX_VST3env set →SandboxVst3Host, ngược lạiVst3Instrument(regression an toàn);assigndùng dynamic_cast: Vst3 → loadPlugin(path,sr)+setChannel, Sandbox → loadPlugin(path,sr,channel). assign() chạy trên uiWorker (ChannelWorker) — spawn blocking OK.CMakeLists.txt: thêmsrc/SandboxVst3Host.cppvào targetdaw_vst_bridge.- Build Release PASS (
daw_vst_bridge.exe+plugin_host.exe). - Deploy: kill app+bridge → copy exe mới vào install/ (dùng shutil,
copyshell fail im lặng — exe cũ 3900416B không có sandbox, phải verify chuỗiSF_SANDBOX_VST3trong binary) → start lại app. - Verify (probe_g42.py, bridge standalone env
SF_SANDBOX_VST3=1+ state file rỗng): LOAD Nexus ch0 → plugin_host pid spawn; child heartbeat OK; GUI 4 hwnd thuộc pid con; note-on C4 → peak 0.38276; kill child → bridge sống, heartbeat tiếp tục, respawn pid mới sau ~1.5s, GUI mới 4 hwnd, audio lại 0.38275 — PASS. - Lưu ý probe: bridge chỉ OPEN mapping
SonicForge_DAW_IPC(Rust shell tạo) → probe phảiCreateFileMappingAtrước; state file cũ (7 instruments) làm guard G2.4 skip LOAD → dùngSF_STATE_FILEtrỏ file rỗng. - Regression: G1.5 stress 3 iter PASS (bridge alive, writeIndex delta 286); G2.5 stress 2 iter PASS (kill bridge → watchdog respawn → restore preset exact + LOAD-skip OK) — không env SF_SANDBOX_VST3 → in-process như cũ, không đụng.
- Commit:
G4.2: bridge-side plugin process pool — SandboxVst3Host spawns/respawns plugin_host.exe per channel, MIDI forward + audio snapshot + watchdog mute/respawn, crash isolation verified. - Commit:
G4.1: sandbox plugin host (plugin_host.exe) PoC — Nexus runs in child process, GUI + audio via SHM, crash isolation verified.
G4.3 - Double-buffer sandbox SHM, latency <20ms, multi-instance no-xrun
include/SharedMemoryIPC.h: structSandboxHostIPC= baseSharedAudioBufferIPC(11168B giu nguyen) +writeSlot@11168 +audioLeft[2][256]@11172 +audioRight[2][256]@13220;shm_open/shm_createnhan size param (mac dinhsizeof(SharedAudioBufferIPC)), themshm_sandbox_ptr. Header rewrite toan bo CRLF (patch anchor sai vi tri lan dau lam hong header - da fix bang rewrite_g43_header.py, build PASS sau do).src/plugin_host_main.cpp:shm_sandbox_ptr; render vao slot1 - (writeSlot&1)roi publishwriteSlot = slot+bridgeWriteIndex++(double buffer: child cham khong tear, bridge doc block cu nhat da publish).timeBeginPeriod(1)deSleep(1)that su ~1ms (khong phai 15.6ms tick mac dinh).include/SandboxVst3Host.h+src/SandboxVst3Host.cpp:ipc_kieuSandboxHostIPC*;shm_createsizesizeof(SandboxHostIPC);processAudioBlockdocaudioLeft[writeSlot&1]. Watchdog log kem exit code (GetExitCodeProcess) de debug.- Fix quan trong: plugin_host phai
shm_open(shmName, sizeof(SandboxHostIPC))- mac dinh 11168B chi map duoc base, doc audioLeft vuot view -> AV crash sau LOAD (children chet lien tuc). Bug thu 2: probekids = [x for x in pl if x not in kids]thay the kids bang phan tu moi - mat phan tu cu, bao FAIL "expected 2 children" du ca 2 con song. Fix uniondict.fromkeys(kids+pl). - Build Release PASS (daw_vst_bridge.exe + plugin_host.exe). Deploy shutil.copy2 (kill proc giu file truoc).
- Verify (probe_g43.py, bridge standalone env
SF_SANDBOX_VST3=1+ state file rong): LOAD Nexus ch0+ch1 -> 2 plugin_host [con song song]; heartbeat ca 2 OK; note-on ch0 C4 + ch1 E4 -> main audio peak 0.71835 (tong 2 channel); writeSlot flip 64 blocks/s = latency 15.6ms < 20ms (khong yeu cau 172/s thuc - Nexus render ~10ms/block that, CPU-bound); kill child0 -> child1 van phat 0.36972, bridge song + heartbeat + child0 respawn - PASS. - Regression: G1.5 stress 3 iter PASS (writeIndex delta 286); G2.5 stress 2 iter PASS (kill bridge -> respawn -> preset exact + LOAD-skip OK). App deploy (install/sonicforge-daw.exe) dang chay, bridge pid moi.
- FIX idle-skip (G4.3.1): plugin_host_main.cpp them
activeNotesdem (note-on ++, note-off --, clamp >= 0)idleflag. Khi khong con note dang giu va peak block < 0.0005f -> idle = true, loop Sleep(15) bo qua render (van PeekMessage + parent-alive check). BoSleep(1)hot loop ->Sleep(0)yield. KET QUA: writeSlot flip 0 blocks/s khi im lang (truoc day render lien tuc ~100/s, CPU cao); khi dang giu note flip 101.6 blocks/s = latency 9.8ms (truoc 64/s = 15.6ms) - ca 2 loi G4.3 xac nhan da het.
- Verify lai: probe_g43.py PASS (2 children song song, peak 0.67273, kill child0 -> child1 0.36972, isolation OK); dbg_rate.py idle 0 flips/3s; G1.5 3 iter PASS (delta 287); G2.5 2 iter PASS. Deploy install/plugin_host.exe moi. App dang chay pid 21396.