12 KiB
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: HOÀN THÀNH Phase 0-4 (2026-08-18) — code + test pass.
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`) |
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ó):
{
"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:
- Đọc input WAV (16/24/32-bit, mono/stereo → convert stereo float) — thêm WAV reader (WAV writer đã có).
- Với mỗi slot trong
fx_chain, process toàn bộ buffer quaNativeFxEngine::processAudio(audio_in, audio_out)— audio-input, KHÔNG MIDI. VST3 effect: loadplugin_path,loadSerializedStatetừpreset_b64(cùng cơ chế preset đã có),processBlocktừng block;bypass=true→ copy thẳng. builtin= fallback không cần VST (normalize, gain) — dùng chung cho Docker headless.- 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ùnggetClassInfo/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)
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:
"master": { "fx_chain": [...], "volume_db": 0, "mute": false, "bypass": false }
render_project():
master_buffer = render_session_container(...)(giữ nguyên).- Nếu
master.bypassfalse →master_buffer = render_fx_chain(wav, master.fx_chain). - Á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):
"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 = classkInstrument; effect =kAudioEffect/kFx). - Không có SDK VST3 ở Python? → dùng chính bridge: thêm mode
--scan <dir>(C++ dùng VST3getClassInfođã 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 =
.vstpresetfile, 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_containerinstrument nhánh có sẵn. - Hoặc chỉnh chord offline rồi render note events — cần UI riêng (ngoài plan).
→ QUYẾT ĐỊNH (Phase 1, 2026-08-18): Scaler = VSTi, không vào FX Rack. Xác
nhận bằng bridge
--scanthật trênC:/Program Files/Common Files/VST3(86 plugins):Scaler 2→is_fx=false(instrument),ScalerAudio 2→is_fx=true(effect — dùng được trong FX Rack/masterbus). Nếu user muốn Scaler trong FX Rack → Phase sau như audio-FX bọc ngoài (unlikely).
4. Phases
| Phase | Nội dung | Deliverable | Trạng thái |
|---|---|---|---|
| 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 | ✅ test /c/tmp/test_fx_phase0.py 7/7 (gain0 bit-exact, -6dB RMS/2, normalize, Ozone 11 Maximizer khác input 88172 samples, scan 86 plugins); banner stdout→stderr 45a7bde |
| 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 |
✅ code sẵn + tests mới test_render_fx_chain_builtin_gain_e2e, test_fx_render_builtin_gain |
| 2 | Masterbus: session key master, render_project chạy chain, backward compatible |
Export session có mastering VST | ✅ render_project (render_engine.py) + test_render_project_master_fx_chain_gain_via_bridge |
| 3 | FX Rack per-track: track fx_chain, render_engine integrate; UI panel (slot/bypass/order/preset upload) |
Track VST FX + UI | ✅ backend vst_fx_chain + UI FX Rack (__openFxRack, add/change/bypass/preset upload) + test_render_track_fx_chain_gain_via_bridge |
| 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 | ✅ "Nghe thử" = __renderProjectPreview → renderProject offline (âm thật = âm export); md/mastering_expand.md + md/unified_fx_rack_panel.md TỒN TẠI (không cần gỡ) |
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);
--scantrên folder có 1 instrument + 1 effect giả → đúng nhãn. - Python: unit test
render_fx_chainvớ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. Kết luận (2026-08-18):
giữ (c) — chain hiện tại xử lý đúng audio bridge-instrument; DAW master bus
(a) là hướng dài hạn ngoài plan (cần SHM audio-in mới). Realtime Ozone trên
toàn master = future work; offline export đã chạy chain đúng (Phase 2).
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_bridgegiữ 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.