Files
SonicForgeStudio/20260815-tasks-g4.md

140 lines
15 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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)
1. 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).
2. 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_alive` mỗ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)` set `sampleRate_/maxBlockSize_` (gọi trước `loadPlugin` — loadPlugin dùng `maxBlockSize_` trong `setupProcessing`; default 256 = AUDIO_BLOCK_SIZE). `loadPlugin` tự activate + setProcessing + prepare. `processAudioBlock` zero 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), reset `midiQueueCount=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_alive` check → `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_close` vào `SharedMemoryIPC.h` (trước đây chỉ có trong .cpp, plugin_host cần khai báo).
- Build: `cmake --build build --config Release --target plugin_host -- /m` PASS → `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`, SHM `SonicForge_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ành `shm_open`); `init` → `loadPlugin` → own window + `openGUI` (editorhost pattern) → heartbeat thread `Sleep(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_alive` check mỗi block (chống orphan) → `closeGUI` + `CoUninitialize` + `shm_close`.
- `include/SharedMemoryIPC.h`: bổ sung prototype helpers `shm_open/shm_create/shm_ptr/shm_close` (chỉ khai báo, không đổi layout — struct giữ nguyên 11168B).
- `CMakeLists.txt`: target `plugin_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.exe` 15928 + `daw_vst_bridge.exe` 20996 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): `INativeInstrument` wrapper quanh plugin_host.exe. `loadPlugin(path, sr, channel)` → `shm_create("SonicForge_PluginHost_<bridgePid>_<channel>")` + `CreateProcessA` plugin_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). `processAudioBlock` copy 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_open` thay vì `shm_create` (bridge tạo mapping, con mở).
- `include/SharedMemoryIPC.h`: thêm prototype `shm_write_midi` (default args data2/data3 chỉ ở header); bỏ default args trùng trong `SharedMemoryIPC.cpp` (C2572).
- `src/NativeInstrumentEngine.cpp`: `create_instrument` VST3 case — `SF_SANDBOX_VST3` env set → `SandboxVst3Host`, ngược lại `Vst3Instrument` (regression an toàn); `assign` dù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êm `src/SandboxVst3Host.cpp` vào target `daw_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, `copy` shell fail im lặng — exe cũ 3900416B không có sandbox, phải verify chuỗi `SF_SANDBOX_VST3` trong 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ải `CreateFileMappingA` trước; state file cũ (7 instruments) làm guard G2.4 skip LOAD → dùng `SF_STATE_FILE` trỏ 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`: struct `SandboxHostIPC` = base `SharedAudioBufferIPC` (11168B giu nguyen) + `writeSlot` @11168 + `audioLeft[2][256]` @11172 + `audioRight[2][256]` @13220; `shm_open/shm_create` nhan size param (mac dinh `sizeof(SharedAudioBufferIPC)`), them `shm_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 slot `1 - (writeSlot&1)` roi publish `writeSlot = slot` + `bridgeWriteIndex++` (double buffer: child cham khong tear, bridge doc block cu nhat da publish). `timeBeginPeriod(1)` de `Sleep(1)` that su ~1ms (khong phai 15.6ms tick mac dinh).
- `include/SandboxVst3Host.h` + `src/SandboxVst3Host.cpp`: `ipc_` kieu `SandboxHostIPC*`; `shm_create` size `sizeof(SandboxHostIPC)`; `processAudioBlock` doc `audioLeft[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: probe `kids = [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 union `dict.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 `activeNotes` dem (note-on ++, note-off --, clamp >= 0)
+ `idle` flag. Khi khong con note dang giu va peak block < 0.0005f -> idle = true,
loop Sleep(15) bo qua render (van PeekMessage + parent-alive check). Bo `Sleep(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.