Files
SonicForgeStudio/plans/WALKTHROUGH.md
T

14 KiB
Raw Blame History

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 \ns.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: ".
    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: onmidimessagerouter.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 đủ.