Files
SonicForgeStudio/PLAN_MASTERBUS_FX_RACK_VST.md
T
admin 6efef85e47 fix(bridge): VST2 host dung ABI SDK — crash outs>2, NaN input, editOpen(NULL), opcodes; docs A14 MIDI-out + phan tich Ozone -Inf
- aeffect.h: bo sung opcodes 4-22, 53, 55 + audioMaster 15/41-44 + ERect (dung chuan VST2.4 SDK, DUNE load OK khong crash)
- Vst2Instrument: cap du pointer arrays theo numInputs/numOutputs (fix AV 0xC0000005 Qin outs=32), input fill 0.0f (het NaN poison effect), bo probe effEditOpen(NULL) (crash WaveObserver), GetDirectory + SizeWindow hook, canDo sendVstEvents/sendVstTimeInfo
- TASKS.md: A14 MIDI-out tu plugin (Scaler2/Instacomposer/MelodicFlow xuat MIDI khong phai audio)
- PLAN_MASTERBUS_FX_RACK_VST.md: phan tich Ozone -Inf — chain day du, nguyen nhan = audio DAW khong chay qua bridge master ring
2026-08-19 20:57:16 +07:00

211 lines
11 KiB
Markdown

# PLAN: Masterbus + FX Rack hỗ trợ chèn VST FX (Ozone, Scaler) — xử lý âm thanh
Ngày: 2026-08-15
Trạng thái: ĐỀ XUẤT — chưa code.
Kế thừa nền: `PLAN_REPLACE_PEDALBOARD_NATIVE_BRIDGE.md` HOÀN THÀNH (Phase 0-5,
commit `dc331f3`) — pedalboard GPL-3.0 đã gỡ, `RENDER_ENGINE` mặc định
`"bridge"`, `native_bridge` (MIT) render VSTi offline qua
`--render <job.json> --out <wav>`; tests 114 pass / 0 skip.
## 1. Mục tiêu
Chèn **VST FX thật** (Ozone mastering chain, Scaler, …) vào 2 tầng xử lý âm
thanh:
- **Masterbus**: chain FX áp lên toàn bộ mix (mastering).
- **FX Rack per-track**: chain FX áp lên từng track (insert).
Offline export chạy qua `native_bridge` (giữ nguyên luồng RENDER_ENGINE hiện
tại). Realtime trong browser **KHÔNG chạy được VST** → giữ WebAudio builtin,
VST FX dùng quick_render preview (âm thật = âm export, như cơ chế preset đang có).
## 2. Hiện trạng (cần mở rộng)
| Tầng | Hiện tại | Hạn chế |
|---|---|---|
| Realtime browser (`app/static/js/app.jsx`) | FX chain WebAudio per-track (chorus/reverb), mastering chain Ozone-style (route dry/mastering, `createMasteringRoute`) | WebAudio node — KHÔNG chạy được binary `.vst3`; data model chỉ có `fxChain` + mastering toggle, chưa có slot plugin |
| Offline export (`app/core/render_engine.py`) | Track FX: `fx_type=="chorus"|"reverb"` fallback scipy/numpy; master bus = sum track buffers + normalize peak (`max_peak`) | Không VST; master chỉ normalize, không chain; `fxChain` WebAudio không tái tạo offline |
| Bridge (`native_bridge/src/*`) | Chỉ instrument host: MIDI → audio (`NativeInstrumentEngine`, VST3/SF2/SFZ) | Chưa có audio-effect processing path; `RenderJob.cpp` chỉ nhận notes, không nhận input WAV |
| Scan plugin (`app/core/vst_engine.py`) | Quét `.vst3`/`.so`/`.dll`, list vst_instruments | Không phân loại instrument vs effect |
Tham chiếu doc cũ: `md/45_MASTERING_MODULE.md` (spec WebAudio Ozone) — giữ
nguyên, chỉ bổ sung VST path. `app.jsx` tham chiếu `mastering_expand.md` +
`unified_fx_rack_panel.md` — 2 file này không tồn tại → gỡ tham chiếu hoặc viết
mới trong Phase 3.
## 3. Kiến trúc đích
### 3.1. Bridge: FX render mode (C++, MIT)
Thêm entrypoint CLI song song mode instrument:
```
daw_vst_bridge(.exe) --render-fx <job.json> --in <input.wav> --out <output.wav>
```
job.json (mở rộng format hiện có):
```json
{
"sample_rate": 44100,
"block_size": 512,
"fx_chain": [
{ "type": "vst3", "path": "C:/.../Ozone 11.vst3", "preset_b64": "...", "bypass": false },
{ "type": "builtin", "id": "normalize", "params": { "peak": 0.95 } }
]
}
```
Luồng:
1. Đọc input WAV (16/24/32-bit, mono/stereo → convert stereo float) — thêm WAV
reader (WAV writer đã có).
2. Với mỗi slot trong `fx_chain`, process toàn bộ buffer qua
`NativeFxEngine::processAudio(audio_in, audio_out)` — audio-input,
KHÔNG MIDI. VST3 effect: load `plugin_path`, `loadSerializedState` từ
`preset_b64` (cùng cơ chế preset đã có), `processBlock` từng block;
`bypass=true` → copy thẳng.
3. `builtin` = fallback không cần VST (normalize, gain) — dùng chung cho
Docker headless.
4. Ghi WAV stereo 16-bit. Exit code như mode instrument (0 ok, 1 job error,
2 load fail, 3 crash — SEH guard như `SandboxVst3Host`).
Lưu ý kỹ thuật:
- Phân biệt class category VST3: instrument (`kInstrument`) vs effect
(`kAudioEffect`) — dùng `getClassInfo`/category string để host đúng
interface (audio-in/audio-out thay vì MIDI-in).
- VST2 FX (`type:"vst2"`) thêm sau nếu cần; V1 chỉ VST3 + builtin.
- Linux bridge chỉ SF2/SFZ (VST3 hosting Windows-only) → FX VST chỉ
Windows/desktop; Docker fallback builtin. Capabilities API báo đúng
(`features.vst_fx_render`).
### 3.2. Python client (`app/core/native_render.py` — mở rộng)
```python
render_fx_chain(input_wav: str, fx_chain: list[dict], sample_rate: int) -> str
```
Spawn bridge `--render-fx`, trả path WAV. Chịu trách nhiệm: resolve plugin
path, base64 encode preset file, timeout, rc → exception. Không import
pedalboard.
### 3.3. Masterbus (offline)
Session thêm key `master`:
```json
"master": { "fx_chain": [...], "volume_db": 0, "mute": false, "bypass": false }
```
`render_project()`:
1. `master_buffer = render_session_container(...)` (giữ nguyên).
2. Nếu `master.bypass` false → `master_buffer = render_fx_chain(wav, master.fx_chain)`.
3. Áp `volume_db`; giữ normalize-peak làm builtin slot cuối (hoặc bỏ khi chain
có master limiter — config theo chain, mặc định giữ).
Session cũ không có `master` → skip, không vỡ (backward compatible).
### 3.4. FX Rack per-track (offline)
Track thêm key `fx_chain` (mảng insert slots, thay thế `fx_type` string):
```json
"fx_chain": [
{ "plugin_id": "ozone", "path": "...", "preset_b64": "...", "bypass": false }
]
```
Trong `render_session_container`, sau khi mix audio track (trước volume/pan):
nếu `fx_chain` có VST slot → render track buffer qua bridge trước khi cộng vào
session. `fx_type` legacy ("chorus"/"reverb") giữ nguyên fallback scipy — cùng
lúc chỉ dùng một; ưu tiên `fx_chain` nếu có.
### 3.5. Scan + phân loại FX (`app/core/vst_engine.py`)
- Thêm `list_fx()`: quét cùng thư mục, đọc category VST3 để tách effect khỏi
instrument (instrument = class `kInstrument`; effect = `kAudioEffect`/`kFx`).
- Không có SDK VST3 ở Python? → dùng chính bridge: thêm mode `--scan <dir>`
(C++ dùng VST3 `getClassInfo` đã có) trả JSON `[{path, is_fx, name}]`. Bridge
scan là nguồn chân lý, Python chỉ cache.
### 3.6. API + UI (V1 tối thiểu)
- `POST /api/v1/plugins/fx-render` `{ input_wav_url, fx_chain }` → WAV
(dùng cho preview + export thử nghiệm; pattern giống `/api/v1/plugins/midi-render`).
- `GET /api/v1/plugins/fx` → danh sách FX (từ scan bridge).
- UI: FX Rack panel per-track + masterbus — danh sách slot, chọn plugin,
bypass toggle, order (up/down), nút "Mở trong Carla / Chỉnh preset" → save
`.vstpreset` (cơ chế CARLA_BRIDGE có sẵn, `md/52_CARLA_BRIDGE.md`).
- Preset-based V1: state plugin = `.vstpreset` file, **chưa có param
automation** (khớp giới hạn preset bridge hiện tại). Dry/wet: bỏ qua V1
(Ozone/Scaler xử lý trong chain), thêm sau nếu cần.
### 3.7. Realtime
- Browser không chạy VST: giữ nguyên WebAudio builtin FX + mastering chain.
- Track/masterbus có VST slot → khi play, nếu slot không bypass: dùng
quick_render preview (đã có `preview_mode: "quick_render"` — render clip ngắn
qua bridge → WAV → phát), ghi chú "preview = âm export". Nếu chỉ builtin →
WebAudio trực tiếp.
- Chưa realtime VST streaming (Tauri SHM) trong phạm vi plan này — ghi là
future work.
### 3.8. Scaler — OPEN QUESTION (quyết định trong Phase 1)
Scaler 2 là **MIDI/chord tool**, không phải audio-effect processor:
- Chạy như VSTi insert (đường VSTi hiện có): nhận MIDI → output audio cho
chord — không cần FX path mới, dùng `render_session_container` instrument
nhánh có sẵn.
- Hoặc chỉnh chord offline rồi render note events — cần UI riêng (ngoài plan).
→ Đề xuất V1: **Scaler = VSTi**, không vào FX Rack; FX Rack/masterbus dành cho
audio-effect VST3 (Ozone). Nếu user muốn Scaler trong FX Rack → đưa vào Phase 4
như audio-FX bọc ngoài (unlikely, cần confirm).
## 4. Phases
| Phase | Nội dung | Deliverable |
|---|---|---|
| 0 | Bridge `--render-fx`: WAV reader, NativeFxEngine VST3 effect (audio-in/out), builtin normalize/gain, SEH guard | CLI render FX demo: WAV in → WAV out, test với Ozone |
| 1 | Bridge `--scan`: phân loại instrument/effect; Python `native_render.render_fx_chain` + `vst_engine.list_fx` | `/api/v1/plugins/fx` + `/api/v1/plugins/fx-render` |
| 2 | Masterbus: session key `master`, render_project chạy chain, backward compatible | Export session có mastering VST |
| 3 | FX Rack per-track: track `fx_chain`, render_engine integrate; UI panel (slot/bypass/order/preset upload) | Track VST FX + UI |
| 4 | Realtime preview quick_render cho slot VST; gỡ/làm rõ tham chiếu doc thiếu (`mastering_expand.md`) | Preview thật; doc sạch |
Mỗi phase kết thúc = commit + test pass. Phase 0-1 có thể merge (cùng chạm
bridge).
## 5. Test
- Bridge: self-check WAV in→out (identity khi bypass; gain builtin đúng dB);
`--scan` trên folder có 1 instrument + 1 effect giả → đúng nhãn.
- Python: unit test `render_fx_chain` với builtin chain (không cần VST) —
input wav → output wav tồn tại, đúng sr; master key vắng → output không đổi
so với trước (regression).
- E2E (thủ công, máy có Ozone): export session có master Ozone chain → nghe
khác rõ so với không chain, không clip.
- Chạy full test suite hiện có — không phá 114 tests.
## 6. Kết quả phân tích Ozone -Inf input (2026-08-18)
Verify code toàn chuỗi realtime master VST3 FX chain — **đã đủ, không thiếu setActive/setProcessing**:
| Chặng | File | Trạng thái |
|---|---|---|
| Frontend gửi chain | `app/static/js/app.jsx:12732-12734` + `app.precompiled.js:803` — `useEffect` trên `ozState.vstFxChain` → `NativeBridgeService.setFxChain(JSON)` | ✅ Có |
| Rust | `src-tauri/src/lib.rs:862-872` `set_fx_chain` → SHM control `type=6` | ✅ Có |
| C++ nhận | `main.cpp:1567-1568` `type==6` → `g_fxChain->setChain(json, sr, block)` | ✅ Có |
| Load plugin | `RenderFxJob.cpp` `vst3FxLoadInner` (397-470): activateBus toàn bộ, `setupProcessing(kRealtime,kSample32)`, `setActive(true)` (454), `setProcessing(true)` (455), `processData.prepare` | ✅ Đủ |
| Process | `RealtimeFxChain::process` (audio thread, in-place trên ring master, SEH quanh từng plugin) + `Vst3Fx::processAudio` (copy input → zero output → process → copy ra) | ✅ Đúng |
| processContext | tempo 120, sampleRate, kPlaying, projectTimeSamples tăng mỗi block | ✅ Có |
→ 4 giả thuyết user: #3 (thiếu setActive/setProcessing) **SAI** — đã có đủ.
**Nguyên nhân -Inf thực tế: audio DAW không chảy qua bridge.** Chain chạy trên
`shmIPC->ringLeft/ringRight[slot]` — master mix CHỈ chứa audio từ instrument
trong bridge. DAW master bus (WebAudio graph: audio track, SonicSF fallback,
non-bridge source) KHÔNG vào SHM → Ozone nhận silence → input -Inf.
→ Hướng sửa: (a) pipe DAW master bus audio vào bridge (SHM audio-in mới) để
chain xử lý đúng master thật, hoặc (b) chạy chain trên WebAudio graph; hoặc
(c) chỉ dùng bridge chain cho phần bridge-instrument. Chưa quyết — ghi nhận
để bước tiếp.
## 7. License
- Ozone (iZotope/Native Instruments) + Scaler (PluginBoutique/Scale Software):
plugin **thương mại** — KHÔNG nhúng, chỉ host bằng trình load của
`native_bridge` (MIT). Không bundle binary plugin vào repo.
- `native_bridge` giữ MIT; code mới (WAV reader, NativeFxEngine) cùng license.
- Không quay lại pedalboard (GPL-3.0) dưới mọi hình thức.