diff --git a/FIX_VSTI_AUDIO_CRACKLE.md b/FIX_VSTI_AUDIO_CRACKLE.md new file mode 100644 index 0000000..1f2c4e8 --- /dev/null +++ b/FIX_VSTI_AUDIO_CRACKLE.md @@ -0,0 +1,92 @@ +# 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ạ".