Files
SonicForgeStudio/FIX_VSTI_AUDIO_CRACKLE.md

93 lines
5.0 KiB
Markdown

# 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<f32> → đè 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ạ".