Files
SonicForgeStudio/FIX_VSTI_AUDIO_CRACKLE.md

5.0 KiB

Fix: VSTi play không smooth / cracking / noise trên standalone (Windows/Tauri)

Ngày: 2026-08-16 Phạm vi: Linux review code — ghi nguyên nhân + hướng fix, KHÔNG sửa code. Pull về Windows sửa.

Triệu chứng

Load VSTi instrument và play: âm không smooth, không clear; âm sắc thỉnh thoảng cracking/noise.

Đường âm hiện tại (đã trace trong code)

  1. C++ bridge (native_bridge/src/main.cpp) render VST3 vào SHM 1 slot 256 frame (main.cpp:759, main.cpp:1414 — render xong mới tăng bridgeWriteIndex).
  2. Rust pump thread poll mỗi ~1ms, chỉ đọc block mới nhất (src-tauri/src/lib.rs:454-469).
  3. Mỗi block phát 1 event bridge-audio qua Tauri IPC (lib.rs:467, app/static/js/services/nativeBridgeService.js:50).
  4. JS push vào ring 24 block (bridgeAudioNode.js:55-65), drain bằng ScriptProcessorNode 4096 (bridgeAudioNode.js:29-53).

Nguyên nhân gốc

1. SHM single-slot, không phải ring buffer

native_bridge/include/SharedMemoryIPC.h:17-21 — chính code ghi chú: "volatile is NOT a sync primitive. Real impl should use an interlocked/ index-flag pair or a Win32 event (SetEvent) signalled by the writer." C++ ghi đè cùng 1 slot mỗi vòng ~5.3ms (256/48000); Rust pump ngủ 1ms + emit async → chỉ cần trễ > 1 block period là block bị ghi đè trước khi đọc → gap.

2. Rust pump đọc-latest, không drain delta

src-tauri/src/lib.rs:454-466: if idx != last_index → nếu pump/WebView bị jank (GC, React render, IME), index nhảy 2+ → chỉ đọc block cuối, các block trước bị bỏ im lặng → click/gap.

3. 1 event/block qua Tauri IPC

lib.rs:467 + nativeBridgeService.js:50: ~187 event/s (48000/256), mỗi event serialize Vec → đè main thread WebView → càng jitter, càng drop block.

4. Consumer bằng ScriptProcessor chạy trên MAIN THREAD

bridgeAudioNode.js:28-53: callback trễ → if (!_chunks.length) return; → fill silence → gap. Overrun: while (_chunks.length > RING_DEPTH) _chunks.shift() (bridgeAudioNode.js:63-64) → drop block cũ → click. Không có pacing theo thời gian — drain "có gì chơi nấy".

5. Không có drift compensation

C++ pace bằng steady_clock (main.cpp:1424), WebAudio chạy theo device clock. Ring 24 block (~139ms) chỉ hấp thụ jitter tạm; lệch nhịp kéo dài → underrun/ overrun định kỳ → cracking "lâu lâu" (đúng triệu chứng).

Yếu tố VSTi phụ (giải thích "không clear / âm sắc lạ")

  • Không latency compensation: getLatencySamples chưa dùng (native_bridge/src/Vst3Instrument.cpp:816-870) — VSTi có delay nội bộ (chorus/reverb/lookahead) bị dịch pha → âm bẩn.
  • Tempo hardcode 120: Vst3Instrument.cpp:357 processContext.tempo = 120.0, không update khi play — LFO/arp tempo-sync chạy sai BPM.
  • Block 256 + sample-accurate split: main.cpp:1400-1413 gọi processAudioBlock theo sub-block (vd 100+156) — vài plugin nhạy block-size tạo artifact nhẹ.
  • channelQuiet mute channel khi mở editor (main.cpp:759 renderAll skip) — cố ý, không phải lỗi.

Hướng fix (Windows thực hiện)

  1. SHM multi-slot ring (4-16 slot) cho main bridge — hoặc dùng đúng pattern double-buffer đã có sẵn SandboxHostIPC (SharedMemoryIPC.h:33-39) cho daw_vst_bridge (hiện main path dùng SharedAudioBufferIPC single-slot).
  2. Rust pump drain đủ delta: vòng lặp đọc/phát tất cả block chênh lệch (không chỉ latest), hoặc batch emit mỗi 4-8 block để giảm IPC overhead.
  3. Thay ScriptProcessor bằng AudioWorklet (chạy trên audio thread, không đè main thread) + ring ≥ 32 block + timestamp.
  4. Drift: monitor buffer level (ring fill) → resample/throttle nhẹ để giữ ring ổn định, không để cạn/tràn.
  5. Latency compensation: đọc processor->getLatencySamples() sau setupProcessing, trừ vào projectTimeSamples / bù offset khi render.
  6. Tempo sync: push BPM thật qua control queue mỗi lần play (hiện chỉ có TRANSPORT/LOAD — thêm field tempo hoặc event mới), cập nhật processContext.tempo + timeSigNumerator/Denominator.

Vị trí file liên quan

  • native_bridge/include/SharedMemoryIPC.h (SHM layout, single-slot)
  • native_bridge/src/main.cpp (audio loop, renderSegment, pacing)
  • native_bridge/src/Vst3Instrument.cpp (processAudioBlock, setupProcessing, processContext)
  • src-tauri/src/lib.rs (audio pump thread, emit bridge-audio)
  • app/static/js/services/nativeBridgeService.js (listener bridge-audio)
  • app/static/js/services/bridgeAudioNode.js (ScriptProcessor sink, ring)

Ưu tiên

P1: #2 + #4 (drop block + drift) — bỏ crackle phần lớn. P2: #3 (AudioWorklet) — hết jank main-thread. P3: #1 (multi-slot ring) — chắc chắn không mất block dù pump trễ. P4: #5/#6 (latency + tempo) — hết "không clear/âm sắc lạ".