Files
SonicForgeStudio/plans/WALKTHROUGH.md
T

177 lines
14 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 (T1T3) → B (T4T7) → C (T8T15) → 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 25 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 T4T5 đã 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: <reason>".
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<trackId, TrackInstrument>` (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 128256 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 đủ.