Files
SonicForgeStudio/PLAN_MASTERBUS_FX_RACK_VST.md

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:

  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)

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():

  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):

"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). → QUYẾT ĐỊNH (Phase 1, 2026-08-18): Scaler = VSTi, không vào FX Rack. Xác nhận bằng bridge --scan thật trên C:/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); --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. 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_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.