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)
- 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ăngbridgeWriteIndex). - Rust pump thread poll mỗi ~1ms, chỉ đọc block mới nhất
(
src-tauri/src/lib.rs:454-469). - Mỗi block phát 1 event
bridge-audioqua Tauri IPC (lib.rs:467,app/static/js/services/nativeBridgeService.js:50). - 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:
getLatencySampleschư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:357processContext.tempo = 120.0, không update khi play — LFO/arp tempo-sync chạy sai BPM. - Block 256 + sample-accurate split:
main.cpp:1400-1413gọiprocessAudioBlocktheo sub-block (vd 100+156) — vài plugin nhạy block-size tạo artifact nhẹ. channelQuietmute channel khi mở editor (main.cpp:759renderAll skip) — cố ý, không phải lỗi.
Hướng fix (Windows thực hiện)
- 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) chodaw_vst_bridge(hiện main path dùngSharedAudioBufferIPCsingle-slot). - 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.
- Thay ScriptProcessor bằng AudioWorklet (chạy trên audio thread, không đè main thread) + ring ≥ 32 block + timestamp.
- Drift: monitor buffer level (ring fill) → resample/throttle nhẹ để giữ ring ổn định, không để cạn/tràn.
- Latency compensation: đọc
processor->getLatencySamples()sau setupProcessing, trừ vàoprojectTimeSamples/ bù offset khi render. - 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ạ".