30 KiB
30 KiB
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+ commitstandalone. Trước mỗi task:git statusphả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 | [x] |
| T2 | A | Bug 2: chain re-apply + sig order + neutral add | [x] |
| T3 | A | Bug 3: autosample pre-warm + toast + re-sample | [x] |
| T4 | B | unifiedMidiRouter.js |
[x] |
| T5 | B | TrackInstrument + keybed → router; xóa native preview/token |
[x] |
| T6 | B | Scheduler items → router | [x] |
| T7 | B | Hardware MIDI → router | [x] |
| T8 | C | Rust vst_gui.rs + raw-window-handle |
[x] |
| T9 | C | VST3 GUI bridge (vst3_host_bridge.cpp + DLL) |
[x] |
| T10 | C | VST2 engine (VST2AudioEngine.cpp, vestige.h) |
[x] |
| T11 | C | IPC param sync 2 chiều | [x] |
| T12 | C | Native audio loop: SPSC + driver WASAPI/ASIO | [x] |
| T13 | C | NativeMixer mirror mastering_engine.py |
[x] |
| T14 | C | Port FluidSynth native (hoặc loopback) | [x] |
| T15 | C | Bỏ WebAudio master; IPC gain/pan; test matrix | [x] |
| T16 | D | Autosample hoàn thiện (VST2 .sf3, preset, re-sample) |
[x] |
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
- Đọc file này. Tìm task đầu tiên có
[ ]hoặc[▶]. - 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). - Làm xong → Verify → build → pytest → cập nhật bảng
[x]+ Ghi chú 1 dòng → commit. - 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→_sfWasmFallbackNoteL213-236,playNativeSfNoteL239-283. - Bước:
- Trong
_sfWasmFallbackNote: stale check_nativeSfPreviews[k].token !== token→ đổi thành> token(claim-then-compare) và LUÔN ghi đè_nativeSfPreviews[k] = { wasm, token }. - Cùng pattern
!== tokenở path API-success (~L257): sửa giống hệt.
- Trong
- 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:
chainSignature(L1097): order-sensitive — signature phải gồm thứ tự module (không phải(type+active).join(',')set-insensitive).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).- 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:
ensure(): bắt lỗi (404/501/import fail) → trả về object{ ok:false, reason }, không throw âm thầm.- 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.
- Khi fail → toast "không autosample được VSTi: ".
- 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) +UnifiedMidiRoutervớiengineRegistry: Map<trackId, TrackInstrument>(thay "1 engine toàn cục" của spec — route theo trackId);dispatchMidiEvent;activeVoiceTrackerkey${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êmapp/static/js/services/trackInstrument.js - Bước:
TrackInstrument: bọc FluidSynth channel per-track —playNote(pitch, vel, dur); dùng cho cả preview lẫn items.- Keybed
onMouseDown/onMouseUp→ router.dispatchMidiEvent (sourceType VIRTUAL_KEYBOARD). - 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
_nativeSfPreviewstrong 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 →
panicAllNotesOffkhô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(L15689navigator.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):
WebviewWindowBuilderlabelvst_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ừ JSinvoke('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: VST3 SDK thuần (KHÔNG JUCE) — nhẹ, license BSD-3; T10 VST2 dùng vestige.h riêng; T12 cũng SDK thuần.
- Verify: DLL build OK + 4 export
SF_VST3_Attach/Close/GetSize/Resizeload qua ctypes. Chưa có plugin VST3 thật để test GUI — giới hạn ghi nhận. - Done khi: DLL build + export OK.
T10 — VST2 engine (Phase C)
- File mới:
native_host/VST2AudioEngine.cpp(dùngvestige.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.HostAudioMasterphả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).
- Đã làm (commit
aaf21dd):native_host/vestige.h(clean-room AEffect: struct đúng layout công khai; numInputs/numOutputs/flags là function pointer; opcode effOpen..effSetProcessPrecision; audioMaster opcodes; VstEvents/VstMidiEvent; ERectshort),native_host/VST2AudioEngine.cpp(exportSF_VST2_Load/Close/GetSize/Resize/SendNoteOn/SendNoteOff/Process; LoadLibraryW →VSTPluginMain/main→ magic check → effOpen→SetSampleRate→SetBlockSize→MainsChanged(1)→StartProcess; editor effEditGetRect→effEditOpen khi có parent; Close teardown ngược; MIDI qua effProcessEvents; processReplacing với interleaved buffersptrs[i]=buffers+i*sampleFrames;tls_loadingthread_local cho hostAudioMasterImpl trả version 2400/sampleRate/blockSize),native_host/tests/fake_vst2.cpp(plugin giả: sine 440Hz stereo, editor rect 400x300), CMakeLists thêmvst2_host_bridgetarget. - Verify đã chạy:
fake_vst2.dllbuild OK (MSVC);vst2_host_bridge.dllbuild OK; ctypes: 7 export SF_VST2_* OK; Load(parent=NULL) → handle, GetSize → -2 (không editor — đúng), Process 512×2 stereo → sine peak 0.25; Load(parent=giả) → GetSize 400x300 → Close OK.
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).
- Đã làm (commit
9eec33c, chưa push):native_host/vst3_host_bridge.cpp+native_host/VST2AudioEngine.cppthêm param API đồng bộ: exportSF_*_SetParamCallback(handle, cb, userdata),SF_*_SetParam(handle, id, valueNormalized)→controller->setParamNormalized/effect->setParameter,SF_*_GetParamCount,SF_VST2_GetParam(handle, id, &value),SF_VST3_GetParamInfo(handle, index, &id, title, cap, &value)(title String128 UTF-16 →StringConvert::convert); VST2 callback chạy trongaudioMasterAutomate(instance tra quag_effect_map+ mutex riêng để tránh deadlock với audio thread), VST3 quaComponentHandler::performEdit;native_host/tests/fake_vst2.cppthêm param 0 (gain, mặc định 0.5) +setParameter/getParameter+ gọiaudioMasterAutomatekhi đổi từ editor. - Rust (
src-tauri/src/vst_gui.rs):open_vst_gui(plugin_id, track_id, plugin_path, plugin_kind)mở WebviewWindowvst_gui.html?track=..&plugin=.., tìm bridge DLL (envSF_NATIVE_HOST_DIR→ resource_dir → exe_dir → cwd →native_host/build/Release/), Load/Attach → handle, đăng ký callback;set_vst_param,get_vst_params,close_vst_editor(kind lưu trong HANDLE_MAP để close/set đúng DLL); callback native →emit_to("vst_gui_{track}_{plugin}", "vst_param_changed", {track_id, plugin_id, param_id, value});lib.rsđăng ký 4 commands +vst_gui::inittrong setup. - JS (
src-tauri/ui/vst_gui.html): panel param trái —get_vst_params→ slider 0..1 per param; slider change →set_vst_param; listenvst_param_changedcập nhật UI, guard chống loop:pendingSetchứa param_id JS vừa set, event echo của chính mình bị bỏ qua. - Verify đã chạy:
cargo checkOK (rustc 1.97, raw-window-handle 0.6:hwnd.get()); build lại 2 bridge DLL + fake (MSVC) OK; ctypes VST2: Load→1, SetParamCallback→0, GetParamCount→1, GetParam(0)=0.5, SetParam(0, 0.75)→0, callback nhận đúng 1 event(handle, 0, 0.75)— không loop, GetParam(0)=0.75 sau đó, Close→0; VST3 bridge: 8 export OK (Attach/Close/GetSize/Resize + 4 param; SendNote/Process thuộc T12+). Chưa có plugin VST3 thật để test GUI — giới hạn T9 giữ nguyên.
T12 — Native audio loop (Phase C)
- File mới:
native_host/AudioEngine.h+AudioEngine.cpp— QUYẾT ĐỊNH: gộp "VST3AudioEngine" thànhAudioEnginedùng chung cho cả 2 plugin kind (spec cho phép); WASAPI đủ cho milestone, ASIO bỏ qua (ghi chú — thêm khi có yêu cầu rõ ràng). - AudioEngine (zero-lock audio thread): SPSC ring MIDI (capacity 4096, tối đa 256 event/block; producer có mutex riêng — audio thread KHÔNG lock, KHÔNG cấp phát); WASAPI render thread event-driven, exclusive trước → fallback shared (shared bắt buộc
GetMixFormatcủa device — format tự build IEEE_FLOAT 2ch bịAUDCLNT_E_UNSUPPORTED_FORMAT0x88890008); device default trước → fallback enumerate endpoint ACTIVE; MMCSS "Audio"; API:start/stop/pushMidi/underruns/latencySamples/blocksRendered/sampleRate,ProcessFn=bool(*)(const MidiEvent*, int32, float* outL, float* outR, int32 frames, void*); audio thread: drain SPSC → buildVstEvents(VST2, buffer 256 cố định flexible-array) /FixedEventList(VST3, Event[512] + IEventList no-alloc) →processReplacing/process→ interleave ra WASAPI. - VST2 bridge: Instance thêm
audio+audioRunning;SF_VST2_AudioStart(handle, sr, block, err, cap)(setaudioRunning=trueTRƯỚCaudio.startđể SendNote route vào SPSC),SF_VST2_AudioStop,SF_VST2_AudioUnderruns,SF_VST2_AudioLatency,SF_VST2_AudioBlocks;SendNoteOn/Off— nếuaudioRunning→ push SPSC, else giữ đường directeffProcessEventscũ (offline/không audio);Close→audioRunning=false; audio.stop()trước teardown. - VST3 bridge: Instance thêm
component(từplugProvider->getComponent()),processor(queryInterfaceIAudioProcessor::iid),audio,audioRunning; exportSF_VST3_AudioStart/Stop/Underruns/Latency/Blocks+SF_VST3_SendNoteOn/Off(LUÔN push SPSC — không có đường direct); AudioStart:setupProcessing(ProcessSetup:symbolicSampleSize=kSample32,maxSamplesPerBlock,sampleRate),setProcessing(true), rồiaudio.start; Close/AudioStop → stop audio +setProcessing(false); ProcessData:symbolicSampleSize=kSample32, outBus 2chchannelBuffers32={outL,outR},inputEvents=&FixedEventList; cần includeivstevents.h(IEventList/Event) +ivstaudioprocessor.hđã có. - Verify đã chạy (ctypes VST2 + fake_vst2): Load→1, AudioStart(44100,128)→0, blocks render sau 300ms = 30 → callback chạy; SendNoteOn(60)/Off qua SPSC → 0, blocks tăng (30→110);
SF_VST2_AudioUnderruns= 0;SF_VST2_AudioLatency= 1056 mẫu (shared-mode buffer thực tế của device — KHÔNG phải 128; exclusive không được device chấp nhận → fallback shared, ghi nhận trung thực); AudioStop→0, Close→0. VST3: 15 export OK (8 cũ + AudioStart/Stop/Underruns/Latency/Blocks + SendNoteOn/Off). Chưa có plugin VST3 thật để test audio — giới hạn giữ nguyên (như T9/T11). - Cảnh báo: KHÔNG chạy
SF_VST2_Process(offline render) song song với live audio trên cùng instance — render thread và caller cùng gọi dispatcher; T16 offline autosample sẽ dùng instance riêng. - Done khi: VSTi ra loa native, không qua WebAudio — đạt cho đường live preview; offline qua native vẫn ở T13–T16.
T13 — NativeMixer (Phase C)
- File mới:
native_host/NativeMixer.h+NativeMixer.cpp(static libnative_mixer),native_host/tests/native_mixer_test.cpp+test_native_mixer_golden.py - Nội dung (spec audio tier 3): track gain
10^(dB/20), pan constant-power, master sum, brickwall limiter — MIRRORapp/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.
- Đã làm (commit
8f1e89ctiếp theo — T13):NativeMixer—TrackGainPan::fromDbPan(gainDb, pan): gainLin=10^(dB/20); pan==0 → unity cả 2 kênh (đúng render_engine.py — KHÔNG nhân 0.7071); pan≠0 → constant-power theta=((pan+1)/2)π/2, L=cos(theta), R*=sin(theta).setLimiter(active, thresholdDb): clamp threshold −24..0, NaN→−1 (mirror_clamp); k=1/max(0.02,t_lin), tk=tanh(k).processInPlace(outL,outR,frames): gainpan rồi nếu limiter active → tanh(xk)/tk + hard clip [−1,1] (clip CHỈ khi limiter active — mirror render_project: clip sau apply_mastering; limiter off không clip). Audio-thread an toàn: setTrack/setLimiter từ UI thread trước AudioStart, processInPlace không lock/cấp phát. Wired vào cả 2 bridge: Instance thêmmixer;vst2AudioProcess/vst3AudioProcessgọimixer.processInPlacesau process; export mớiSF_VST2_/SF_VST3_SetTrackGainPan(handle, gainDb, pan)+SF_VST2_/SF_VST3_SetMasterLimiter(handle, active, thresholdDb)→ VST2 18 export, VST3 17. - Verify đã chạy: golden test
python native_host/tests/test_native_mixer_golden.py— 5 case (identity; gain/pan 0dB·pan0; +6dB pan−0.5 limiter −3dB; −9dB pan0.8 limiter −6dB; +12dB limiter 0dB) so với reference dùng CHÍNH code server: gain/pan theo render_engine.py +apply_mastering(mastering_engine) module limiter + clip — worst diff RMS/peak = 0.0001 dB (< 0.1 → PASS). Ctypes VST2: Load→1, SetTrackGainPan/SetMasterLimiter→0, AudioStart→0, 30 blocks/300ms, SendNoteOn/Off→0, underruns 0, Stop/Close→0. Build: cmake Release 2 bridge OK; native_mixer_test.exe build OK. - Ghi chú: golden test so với reference = đúng code path render_project (gain/pan render_engine + apply_mastering + clip) vì chưa có FluidSynth native (T14) để render SF trực tiếp — end-to-end qua render_project sẽ verify tiếp ở T14.
- 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.
- Đã làm (commit
500384a— T14): QUYẾT ĐỊNH — port được native, KHÔNG loopback.native_host/SFHost.cpp(DLLsf_host_bridge, link audio_engine + native_mixer): runtime-load libfluidsynth-3.dll từnative_host/fluidsynth_runtime/(gitignore; 22 DLL 12.3MB closure bằng dumpbin BFS; tái tạo bằngscripts/dl_fluidsynth_runtime.py— tải 22 MSYS2 mingw64 packages, giải nén zstandard, copy NEEDED list; ponytail: chạy lại BFS khi upgrade). Load bằngGetModuleHandleW(L"sf_host_bridge")+GetModuleFileNameW(KHÔNG nullptr — host có thể là python.exe) +LoadLibraryExW(LOAD_WITH_ALTERED_SEARCH_PATH). Export prefix FluidSynth 2.5.x:new_fluid_settings/new_fluid_synth/delete_fluid_settings/delete_fluid_synth(KHÔNG phảifluid_settings_new— verify dumpbin). Bỏ settingsynth.lock-memory(không tồn tại 2.5.6); giữaudio.driver=file,synth_set_gain(1.0)(mirror WASM client; default 0.2 = −14 dB). 15 export:SF_FS_Create/LoadSF2/SelectInstrument(handle, channel, sfontId, bank, program)/NoteOn/NoteOff/AllNotesOff/RenderBlock/AudioStart/Stop/Underruns/Latency/Blocks/SetTrackGainPan/SetMasterLimiter/Close. BUG ĐÃ SỬA:fluid_synth_program_selecttham số 3 là sfont_id (không phải bank);sfontId<0→ dùnginst->sfidlưu từ sfload (sfid thật của SF2 test là 1, không phải 0). RenderBlock: offlinewrite_float+ mixer, không qua WASAPI. - Verify đã chạy:
test_sf_host_bridge.pyPASS (Create→LoadSF2→SelectInstrument→AudioStart→NoteOn→blocks 0→40, underruns 0→Stop→Close; preset SF2 test là bank128/prog0 'Standard Kit' — không có 0/0).test_sf_host_golden.pyPASS — 3 case so reference = libfluidsynth-3.dll ctypes (write_float) + mixer math (render_engine gain/pan + apply_mastering + clip), ngưỡng 0.1 dB, sai lệch thực tế 0.0001 dB (bit-exact): (0dB/pan0/lim-off), (−6dB/lim−1), (−3dB/pan0.8/lim−6). Export count SFHost 15 (dumpbin). - Ghi chú: SF2 test sfload trả sfid=1 — test truyền sfontId=-1 (dùng inst->sfid). T13 ghi chú "chưa có FluidSynth native" giờ đã hết — golden T14 dùng reference đúng code path server.
T15 — Bỏ WebAudio master (Phase C)
- Bước: track instrument không còn đi
masterBusWebAudio; IPCset_master_gain,set_track_gain_pantừ 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.
- Đã làm (commit — T15):
- C++:
NativeMixerthêm master fader —setMasterGain(float linear)(m_masterGain=1.0), áp SAU limiter+clip trongprocessInPlace(mirrormasterBus.output.gainsau mastering chain; server render_project không có master fader — WebAudio áp sau mastering). Export mớiSF_FS_/SF_VST2_/SF_VST3_SetMasterGain(SFHost.cpp, VST2AudioEngine.cpp, vst3_host_bridge.cpp). Export count: SF 16, VST2 19, VST3 18. app/core/native_audio_service.py(mới): singletonNativeAudioServicectypes wrapper — SF (ensure_sf/note_on/note_off/set_track_gain_pan/stats/audio_stop/render_sf_offline — instance TẠM, RenderBlock loop 256, note events theo start_beat/duration_beats), VST2 (live + offline quaSF_VST2_Processraw +apply_track_mixerPython mirror NativeMixer/mastering_engine; ponytail: giả định synth 0-input → output ở buf[0:2] vì bridge xếp outputs tại offset numInputs), VST3 (live; offline limitation — bridge chưa có Process export);set_master_gain(db)(db<=-50 → 0) áp linear mọi instance;close_track/close_all/status; DLL dir =settings.BASE_DIR/native_host/build/Release(envSONICFORGE_NATIVE_DIRoverride). BUG ĐÃ SỬA: argtypes SetMasterGain phải 2 tham số (i32, float) — không dùng chung loop 3 tham số với SetTrackGainPan.app/api/v1/native.py(mới, đăng ký main.py prefix/api/v1/native): status, set_master_gain, track_gain_pan, sf/ensure (nhận sf_id → resolve_find_sf2_pathserver-side), sf/note_on, sf/note_off, sf/audio_stop, vst2/ensure, vst2/note_on, vst2/note_off, render (trả peak/rms cho test matrix).- JS:
app/static/js/services/nativeAudioClient.js(mới,window.SonicNativeAudio— setMasterGain debounce 40ms, setTrackGainPan, ensureSf, noteOn/noteOff; include vào index.html); app.jsx —handleFaderChange→ native set_master_gain,updateTrackVolumeDb/updateTrackPan→ native track_gain_pan (pan/100; dùng tracks state lấy pan/volume hiện tại);trackInstrument.js— playNote/noteOff native-first (SF track có sfId → ensureSf + note_on + setTimeout note_off theo durationMs; ponytail: bỏ startTime offset — TrackInstrument không biết audioCtx), SonicSF WASM chỉ fallback.
- C++:
- Verify đã chạy: smoke python service (SF live blocks 50→70 underruns 0; master -6dB ratio 0.5012 đúng 10^(-6/20); VST2 live blocks 40→60 underruns 0; VST2 offline peak 0.25 sine, ratio -6/-6 = 0.2512 đúng 10^(-12/20)); API TestClient 10 endpoints OK;
python -m pytest tests/test_native_matrix.py— 8 passed (SF live blocks/underruns 0, SF offline master gain + track gain pan, VST2 live, VST2 offline sine+mixer+limiter, VST3 bridge export SetMasterGain — limitation, API render); pytest toàn bộ 118 passed 1 skipped 1 env-fail (test_vst_engine Linux path — baseline). Build babel OK; node --check 2 service JS OK. - Ghi chú: VST3 live test không có plugin thật — bridge export verify (như T9/T11/T12). AudioEngine mỗi instance WASAPI riêng — chưa có master summing chung giữa track (giới hạn T15). CRLF rule: main.py 2 dòng LF do edit_file → convert lại CRLF toàn file.
T16 — Autosample hoàn thiện (Phase D)
- File:
tools/autosample_vsti.py,app/static/js/services/vstiAutosample.js - Nội dung: autosample VST2 →
.sf3cho 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.
- Đã làm:
tools/autosample_vsti.py— VST2 branch:_is_vst2_path(.dll/.so không .vst3),_render_note_nativequaget_service().render_vst2_offline(tail_sec=release),autosample_sf2nhậnextra_vst_dirs+ bỏ qua preset VST2 (bridge chưa hỗ trợ chunk);write_sf2sửa gen IDs chuẩn SF2: keyRange=43, overridingRootKey=58, sampleID=53 (gen cuối zone) — trước đó gen 60/69/74 bị FluidSynth "Discarding invalid global zone" (rms 1.5e-5 → 0.041).app/core/soundfont_converter.py—_ensure_fluidsynth_runtime()prepend PATH DLL closure (native_host/build/Release/fluidsynth_runtime) để pyfluidsynth import được; FLUID_SAMPLETYPE_OGG_VORBIS = 0x10 (không phải 0x20 — 0x20 làm sample bị ignore);_sf3_plays_audiodùngSynth().sfload(str)/get_samples (không có fluid_synth_write_float trong binding); reverse_sf3_to_sf2clear flag& ~0x10.plugins.pyPOST /autosample— truyềnextra_vst_dirs=_dirs(VST2 scan riêng), không còn 501 cứng VST2.vstiAutosample.js— payload preset_path/preset_data, keyFor = plugin_id|preset_id (re-sample khi đổi preset), notifysf:autosample-update;app.jsx2 chip badge size/note_count (PianoRollTabEditor + track strip). Babel rebuildapp.precompiled.js. - Verify đã chạy: pipeline thật fake_vst2 → SF2 3 notes, FluidSynth SF2 rms 0.041; convert → SF3 rms 0.044 (cả 2 > 1e-4, warning "invalid sample loops sanitized" vô hại — loopstart/end=0);
python -m pytest tests/— 119 passed 1 skipped 1 env-fail (test_vst_engine Linux path — baseline); test mớitest_autosample_vst2_native_bridge(fake_vst2 → 3 notes). - Ghi chú: pyfluidsynth cần DLL trên PATH —
_ensure_fluidsynth_runtime()ở module-level helper; SF3 verify dùng high-level Synth (sfload nhận str, không encode bytes);write_sf2inst terminator bagNdx = n (record terminal zone, không phải n+1). - 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 đủ.