# WALKTHROUGH — Thực thi tuần tự (SonicForgeStudio standalone) > Nạp file này mỗi session mới. Làm ĐÚNG 1 task, cập nhật bảng trạng thái, commit, dừng. > Kiến trúc/lý do: `GIAI_PHAP.md`. File này chỉ chứa thao tác + trạng thái → context nhỏ. ## 1. Thông tin cố định (mọi session) - Repo: `C:/Users/locpham/SonicForgeStudio` — branch: `standalone` — HEAD: `cadb540` (đã commit; **KHÔNG push**) - Build JS: `node node_modules/@babel/cli/bin/babel.js app/static/js/app.jsx --config-file ./babel.config.json -o app/static/js/app.precompiled.js` - Test: `python -m pytest tests/` → 110 passed, 1 skipped, 1 failed (`test_vst_engine.py::TestPluginManager::test_init` — env-fail Linux path, **bỏ qua, không sửa**) - Rust check: `cd src-tauri && cargo check` (nếu task C) - **CRLF rule**: file repo dùng CRLF. Đọc = `sed -n`/`grep` (không dùng read_file). Sửa = python: `s = open(p, encoding='utf-8', newline='').read()` → thay chuỗi `\n` → `s.replace('\n','\r\n')` → `open(p,'w',encoding='utf-8',newline='').write(s)`. Verify lại bằng grep/repr. - Commit rule: sau mỗi task xanh (build+test OK) → `git add` + commit `standalone`. Trước mỗi task: `git status` phải sạch. - Trình tự: A (T1–T3) → B (T4–T7) → C (T8–T15) → D (T16). ## 2. Trạng thái | ID | Phase | Task | Trạng thái | | --- | --- | --- | --- | | T1 | A | Bug 1: token fix | [ ] | | T2 | A | Bug 2: chain re-apply + sig order + neutral add | [ ] | | T3 | A | Bug 3: autosample pre-warm + toast + re-sample | [ ] | | T4 | B | `unifiedMidiRouter.js` | [ ] | | T5 | B | `TrackInstrument` + keybed → router; xóa native preview/token | [ ] | | T6 | B | Scheduler items → router | [ ] | | T7 | B | Hardware MIDI → router | [ ] | | T8 | C | Rust `vst_gui.rs` + `raw-window-handle` | [ ] | | T9 | C | VST3 GUI bridge (`vst3_host_bridge.cpp` + DLL) | [ ] | | T10 | C | VST2 engine (`VST2AudioEngine.cpp`, vestige.h) | [ ] | | T11 | C | IPC param sync 2 chiều | [ ] | | T12 | C | Native audio loop: SPSC + driver WASAPI/ASIO | [ ] | | T13 | C | `NativeMixer` mirror `mastering_engine.py` | [ ] | | T14 | C | Port FluidSynth native (hoặc loopback) | [ ] | | T15 | C | Bỏ WebAudio master; IPC gain/pan; test matrix | [ ] | | T16 | D | Autosample hoàn thiện (VST2 `.sf3`, preset, re-sample) | [ ] | Trạng thái dùng: `[ ]` chưa làm, `[▶]` đang làm/dang dở, `[x]` xong, `[!]` fail — ghi lỗi vào Ghi chú. ## 3. Tiếp tục sau fail 1. Đọc file này. Tìm task đầu tiên có `[ ]` hoặc `[▶]`. 2. Nếu `[▶]`: đọc Ghi chú (lỗi đã ghi), sửa tiếp. Nếu `[ ]`: nạp đúng mục "Context tối thiểu" của task (chỉ đọc các file/line đó — không đọc cả app.jsx). 3. Làm xong → Verify → build → pytest → cập nhật bảng `[x]` + Ghi chú 1 dòng → commit. 4. Fail không gỡ được: cập nhật `[!]` + Ghi chú lỗi đầy đủ (command, output, đoạn code), dừng, báo user. ## 4. Các task ### T1 — Bug 1: token fix (Phase A) - **Mục tiêu**: bấm phím keyboard preview lần 2+ không câm. - **File**: `app/static/js/app.jsx` - **Context tối thiểu**: `grep -n '_sfWasmFallbackNote\|playNativeSfNote\|_nativeSfPreviews' app/static/js/app.jsx` → `_sfWasmFallbackNote` L213-236, `playNativeSfNote` L239-283. - **Bước**: 1. Trong `_sfWasmFallbackNote`: stale check `_nativeSfPreviews[k].token !== token` → đổi thành `> token` (claim-then-compare) và LUÔN ghi đè `_nativeSfPreviews[k] = { wasm, token }`. 2. Cùng pattern `!== token` ở path API-success (~L257): sửa giống hệt. - **Verify**: build babel; mở app; bấm cùng phím 2–5 lần liên tiếp → mọi lần đều có tiếng; bấm nhanh nhiều phím → không câm. - **Done khi**: không còn câm; không đổi hành vi khác. - **Rollback**: `git checkout -- app/static/js/app.jsx` - **Ghi chú**: Nếu T4–T5 đã xong (router xóa cơ chế này) → bỏ qua task này, đánh `[x]` kèm "đã xóa tận gốc ở T5". ### T2 — Bug 2: chain mastering (Phase A) - **Mục tiêu**: thêm/sắp xếp module mastering không làm volume nhỏ đi; reorder đúng thứ tự. - **File**: `app/static/js/app.jsx` - **Context tối thiểu**: `grep -n 'applyMasteringSettings\|_lastMasteringSig\|chainSignature\|addModuleToChain\|_buildLimCurve\|_buildMaxCurve\|toggleMasteringOnMaster' app/static/js/app.jsx` (L635+, L1097, L1486, L913-935, L12446). - **Bước**: 1. `chainSignature` (L1097): order-sensitive — signature phải gồm thứ tự module (không phải `(type+active).join(',')` set-insensitive). 2. `applyMasteringSettings` (L635+, early-return L642): bỏ phụ thuộc sig; mọi thay đổi chain → rebuild + re-apply TOÀN BỘ tham số (debounce ~50ms thay vì skip). 3. Thêm module (limiter/compressor) qua `addModuleToChain`: neutral-then-apply — limiter threshold mặc định 0dB (không clamp −1dB), compressor makeup bù loss (hoặc threshold cao hơn), để "thêm module" không tự cắt volume. - **Verify**: build; thêm limiter → volume không giảm rõ rệt; thêm compressor → không giảm; reorder limiter↔compressor → nghe đổi thứ tự; master fader vẫn hoạt động đúng. - **Done khi**: 3 bước trên đúng; `python -m pytest tests/` xanh. - **Rollback**: `git checkout -- app/static/js/app.jsx` ### T3 — Bug 3: autosample UX (Phase A) - **Mục tiêu**: VSTi không autosample được phải hiện rõ, không âm thầm ra oscillator default. - **File**: `app/static/js/services/vstiAutosample.js`, `app/static/js/app.jsx` (nơi chọn instrument), util toast hiện có. - **Context tối thiểu**: `grep -rn 'SonicVstiAutosample\|ensure' app/static/js/` + `grep -n '_playNoteFallback' app/static/js/soundfontPlayer.js` (L787+). - **Bước**: 1. `ensure()`: bắt lỗi (404/501/import fail) → trả về object `{ ok:false, reason }`, không throw âm thầm. 2. UI: khi chọn instrument VSTi → gọi ensure NGAY (pre-warm) + progress indicator; note đầu tiên chờ ready (hoặc disable tới ready) — không rơi vào oscillator. 3. Khi fail → toast "không autosample được VSTi: ". 4. Nút re-sample trong panel instrument. - **Verify**: chọn VSTi path sai → toast hiện reason, không ra âm oscillator; chọn VSTi hợp lệ → pre-warm xong, note đầu tiên ra đúng âm; bấm re-sample → chạy lại. - **Done khi**: không còn fail âm thầm; pre-warm hoạt động. - **Rollback**: `git checkout -- app/static/js/services/vstiAutosample.js app/static/js/app.jsx` ### T4 — `unifiedMidiRouter.js` (Phase B) - **Mục tiêu**: một điểm dispatch MIDI cho mọi nguồn (spec Part II). - **File mới**: `app/static/js/services/unifiedMidiRouter.js` - **Nội dung**: `UnifiedMidiEvent` (trackId, channel, command, pitch int 0-127, velocity int 1-127, sourceType, timestampNs) + `UnifiedMidiRouter` với `engineRegistry: Map` (thay "1 engine toàn cục" của spec — route theo trackId); `dispatchMidiEvent`; `activeVoiceTracker` key `${channel}_${pitch}` chống stuck notes; `panicAllNotesOff()`. - **Verify**: `node --check app/static/js/services/unifiedMidiRouter.js`; viết 1 test assert nhỏ (pitch clamp, velocity normalize float→int, tracker đếm đúng, panic clear) — chạy bằng node. - **Done khi**: file + test chạy xanh; chưa nối vào app. - **Rollback**: xóa file. ### T5 — `TrackInstrument` + keybed → router (Phase B) - **Mục tiêu**: keyboard preview đi router; xóa cơ chế token/native. - **File**: `app/static/js/app.jsx`, có thể thêm `app/static/js/services/trackInstrument.js` - **Bước**: 1. `TrackInstrument`: bọc FluidSynth channel per-track — `playNote(pitch, vel, dur)`; dùng cho cả preview lẫn items. 2. Keybed `onMouseDown`/`onMouseUp` → router.dispatchMidiEvent (sourceType VIRTUAL_KEYBOARD). 3. XÓA: `playNativeSfNote`, `_sfWasmFallbackNote`, `_nativeSfPreviews`, mọi `__nativeSfOk`/token logic. - **Verify**: bấm phím liên tiếp 10 lần → không câm; không còn `_nativeSfPreviews` trong code (`grep`); build OK. - **Done khi**: Bug 1 hết tận gốc; preview vẫn ra tiếng đúng track/channel. - **Rollback**: `git checkout -- app/static/js/app.jsx` (xóa file mới nếu thêm). ### T6 — Scheduler items → router (Phase B) - **Mục tiêu**: MIDI items khi chạy timeline đi cùng router. - **File**: `app/static/js/app.jsx` (scheduler/playhead) - **Bước**: note-on/note-off của scheduler → `router.dispatchMidiEvent` (TIMELINE_SCHEDULER); note-off dùng tracker đối xứng. - **Verify**: chạy timeline → items phát đúng pitch/vel; dừng giữa chừng → `panicAllNotesOff` không stuck note. - **Done khi**: preview + timeline cùng engine, không nuốt voice. ### T7 — Hardware MIDI → router (Phase B) - **File**: `app/static/js/app.jsx` (L15689 `navigator.requestMIDIAccess`/`onmidimessage`) - **Bước**: `onmidimessage` → `router.handleHardwareKeyboardMessage(...)` → dispatch (HARDWARE_KEYBOARD); xóa nhánh xử lý cũ. - **Verify**: bấm phím MIDI hardware → ra tiếng qua router đúng track active; log sourceType. - **Done khi**: 3 nguồn (keybed/scheduler/hardware) cùng một router. ### T8 — Rust `vst_gui.rs` (Phase C) - **Mục tiêu**: command `open_vst_gui(plugin_id, track_id)` tạo floating child window. - **File**: `src-tauri/Cargo.toml` (+ `raw-window-handle`), `src-tauri/src/vst_gui.rs`, `src-tauri/src/lib.rs` (register command). - **Nội dung** (spec vsti_gui): `WebviewWindowBuilder` label `vst_gui_{track}_{plugin}`, 800×600, always_on_top, mở lại thì focus; `raw_window_handle()` lấy HWND. - **Verify**: `cargo check`; gọi từ JS `invoke('open_vst_gui', {...})` → cửa sổ nổi mở/đóng không crash. - **Done khi**: window nổi mở được; chưa gắn plugin GUI (T9). ### T9 — VST3 GUI bridge (Phase C) - **File mới**: `native_host/vst3_host_bridge.cpp` + build DLL (MSVC/CMake) - **Nội dung** (spec): `attach_vst3_editor_to_handle(plugin_id, parent_handle)` → `createView(kEditor)` → `attached(hwnd, kPlatformTypeHWND)` → `getSize()` → resize. Đóng: `removed()` + `release()`. - **Quyết định trước khi làm**: VST3 SDK thuần (nhẹ) HAY JUCE (gộp luôn VST2 — xem T10). Khuyến nghị: JUCE nếu muốn 1 engine cho cả 2; SDK thuần nếu chỉ VST3. - **Verify**: mở GUI VST3 trong cửa sổ nổi, chỉnh param, đóng → không leak/crash. - **Done khi**: GUI plugin hiển thị + đóng sạch. ### T10 — VST2 engine (Phase C) - **File mới**: `native_host/VST2AudioEngine.cpp` (dùng `vestige.h` — clean-room, KHÔNG dùng aeffect.h chính thức; legal note spec III) - **Nội dung** (spec VST2): `loadPlugin` (LoadLibraryW + `VSTPluginMain`/`main` + dispatcher init), `sendMidiNoteOn` (`effProcessEvents`, `deltaFrames`), `attachGUI` (`effEditOpen` + `effEditGetRect`), `processAudioBlock` (`processReplacing`), destructor teardown. `HostAudioMaster` phải trả lời opcode version/sampleRate/blockSize. - **Verify**: load .dll VST2 → mở GUI → processReplacing ra tín hiệu. - **Done khi**: VST2 GUI + audio thô chạy (chưa vào mixer). ### T11 — IPC param sync (Phase C) - **File**: `src-tauri/src/vst_gui.rs` + JS side - **Nội dung**: native param đổi → `vst_param_changed` → JS cập nhật state DAW; JS automation → `set_param`/`setParamNormalized` → native. - **Verify**: xoay knob plugin → JS state đổi; đổi tham số từ DAW → plugin đổi. - **Done khi**: sync 2 chiều không lag, không vòng lặp vô hạn (guard flag). ### T12 — Native audio loop (Phase C) - **File mới**: `native_host/VST3AudioEngine.cpp` (hoặc gộp với T9/T10 theo quyết định JUCE), SPSC ring buffer - **Nội dung** (spec audio): UI thread → SPSC queue → audio thread build EventList/VstEvents → `process()`/`processReplacing` → pre-alloc buffers; zero-allocation trong audio thread; driver WASAPI Exclusive/ASIO 128–256 samples; `setProcessing(true)` 1 lần lúc init; sampleRate từ negotiate. - **Verify**: phát preview qua native driver, 0 underrun; đo latency ≤ ~5ms. - **Done khi**: VSTi ra loa native, không qua WebAudio. ### T13 — `NativeMixer` (Phase C) - **File mới**: `native_host/NativeMixer.cpp` - **Nội dung** (spec audio tier 3): track gain `10^(dB/20)`, pan constant-power, master sum, brickwall limiter — MIRROR `app/core/mastering_engine.py` (đọc file đó trước). - **Verify**: golden test — render cùng 1 đoạn bằng native mixer và server `render_project` (cùng mastering settings) → so RMS/peak sai lệch < 0.1dB. - **Done khi**: native mixer khớp server; track gain/pan áp cho cả VST2/VST3. ### T14 — Port FluidSynth native (Phase C) - **Mục tiêu**: SF track vào CÙNG native mixer (một master duy nhất). - **Bước**: tích hợp libfluidsynth C++ vào native host; track SF → FluidSynth native → NativeMixer. (Nếu không port được: loopback WASM→native — phức tạp, ghi quyết định vào Ghi chú.) - **Verify**: SF track + VSTi track cùng master; gain/pan/mastering áp đúng cả 2; preview + items + offline nhất quán. - **Done khi**: một mixer duy nhất, hết WebAudio cho track instrument. ### T15 — Bỏ WebAudio master (Phase C) - **Bước**: track instrument không còn đi `masterBus` WebAudio; IPC `set_master_gain`, `set_track_gain_pan` từ JS → native; giữ WebAudio chỉ cho UI/aux nếu còn. - **Verify**: test matrix spec V: VST3/VST2/SF × preview/live/offline đều đúng âm lượng và mastering. - **Done khi**: Bug 2 không còn drift (một engine duy nhất); checklist spec 2 (negotiate SR/buffer, jitter <0.1ms, 0% underrun) đạt. ### T16 — Autosample hoàn thiện (Phase D) - **File**: `tools/autosample_vsti.py`, `app/static/js/services/vstiAutosample.js` - **Nội dung**: autosample VST2 → `.sf3` cho offline (Pedalboard không hỗ trợ VST2 → giữ con đường này cho export); tôn trọng preset_path/preset_data; hiển thị note_count/size; re-sample khi đổi preset. - **Verify**: export track VST2 ra file đúng âm; đổi preset → re-sample tự động; UI hiện size. - **Done khi**: matrix hoàn chỉnh; toàn bộ test xanh; commit cuối. ## 5. Checkpoint tổng - Sau T3: 3 bug đã vá (tạm) — bản phát hành an toàn. - Sau T7: unified MIDI pipeline xong phía client — Bug 1 hết tận gốc. - Sau T15: native audio hoàn chỉnh — Bug 2 hết drift, Bug 3 hết fallback mặc định. - Sau T16: matrix spec V đầy đủ.