Files
SonicForgeStudio/TASKS.md
T
locpham c3368d0a91 feat: native host bridge integration — C++ bridge, Rust SHM, JS routing, build scripts, docs
- native_bridge/: InstrumentEngineManager multi-channel, sample-accurate, CC/program/pitchbend, transport, Vst3Instrument stub (HAVE_VST3SDK)
- src-tauri: shm.rs, bridge spawn + audio pump + health monitor, open_vst_gui, externalBin, commands
- app: UnifiedMidiRouter, NativeBridgeService, bridgeAudioNode, audioRoutingEngine, Plugin Manager UI, Bridge/WASM indicator, set_position sync
- build: 3 ps1 (force-added, build/ ignored), verify_bundle --check-bridge, CI workflow
- docs: TASKS.md, TEST_NOTES.md (Windows verify checklist), install/report updates
2026-08-11 23:37:29 +07:00

22 KiB

TASKS — TRIỂN KHAI NATIVE HOST BRIDGE THEO PLAN

Nguồn: PLAN.md + spec. Mỗi task: file đích, hành động cụ thể, điều kiện hoàn thành (DoD) để verify. Ký hiệu: [MỚI] tạo mới, [THAY] thay thế/đổi luồng hiện có, [BỔ SUNG] thêm vào code hiện có.

TRẠNG THÁI 2026-08-11: = đã làm xong. Task liên quan Rust/Windows (B1-B4, C2, C5, C7) mới verify ở mức code (node --check JS / review struct) — cần cargo check + chạy thật trên máy Windows trước khi coi là hoàn tất; máy này (Linux) không có rustc/CMake-MSVC. D2/D9: đã đổi timeline → scheduleMidiNoteDispatch (bridge active) + transport play/stop + bridgePlayheadSampleRef; sampleOffset/set_position chỉ gửi khi C++ A11/A13 sẵn sàng (nay gửi sampleOffset=0 qua setTimeout). E1-E3: syntax OK (ast.parse); máy này SIGILL khi import app.core.vst_engine (numpy/pedalboard pre-existing) → chưa chạy thử API tại đây. BATCH A10-A13 + D5 (2026-08-11): C++ multi-instance (InstrumentEngineManager), CC/program/pitch bend (MidiEventIPC + data2/data3), TRANSPORT (type=3 STOP flush/PLAY/SET_POSITION + playheadSamples), sample-accurate segment render, JS dispatch MỞ RỘNG (CC/PROGRAM/PITCH_BEND), load theo channel, transport(playhead). Struct SHM ĐỔI: 10872 → 11160 bytes — đã sync C (selfcheck pass Linux) ↔ Rust shm.rs ↔ selfcheck; bắt buộc cargo check + chạy shm_selfcheck trên Windows. Sửa bug pre-existing: Rust push_control không set pathlen → C++ load giờ dùng strnlen(arg2) thay vì tin arg1. C++ chưa compile ở máy này (thiếu fluidsynth/sfizz headers) — verify bằng shm_selfcheck (không cần engine) + review. BATCH B8-B10 (cùng ngày): spawn bridge kèm SF_SAMPLE_RATE/SF_BLOCK_SIZE (C++ main đọc env, block cố định 256), open_vst_gui tạo child WebviewWindow + HWND raw_window_handle → control type=4, health monitor stall 3s → respawn 1 lần → bridge-down (đếm vào bridge.log). Rust chưa compile được (rustup không có toolchain) — review tay; cargo check trên Windows là điều kiện hoàn tất. BATCH D7/D10/E4/E5/F1-F7/B6/B7/A7 (2026-08-11, cuối): D7 UI Plugin Manager (badge Bridge ON/OFF + nút "Load Bridge" trên VST/SoundFont → /api/v1/bridge/load → loadInstrument); D10 header indicator Bridge/WASM + nút "Ép WASM" (setForceWasm) + service onStatusChange; E4 watchdog verify (không đụng bridge); E5 GET /api/v1/bridge/log; F1-F3 3 ps1 copy vào repo build/scripts/; F4 verify_bundle --check-bridge; F5 hooks.nsh OK; F6 docs 2 sidecar; F7 CI yml; B6 externalBin; B7 capabilities OK; A7 tách Vst3Instrument.h/.cpp (guard HAVE_VST3SDK, stub no-op khi thiếu submodule — A6 host thật vẫn TODO chờ vst3sdk trên Windows). Bundle rebuild OK; selfcheck 11160 pass Linux. BATCH CUỐI (2026-08-11): B5 (pump thread tồn tại trong lib.rs ~263 — review tay, cần cargo check Windows); F5 (hooks.nsh customInstall ExecWait vc_redist toàn máy — đủ cho cả 2 exe); F2 🟡 (ps1 đã copy, chạy thật cần Windows); D9 bổ sung transport('set_position', playhead) ~mỗi giây trong updatePlayhead (JS là nguồn playhead duy nhất — C++ A13 không tự biết). Bundle rebuilt OK.


A. NATIVE BRIDGE C++ (native_bridge/) — [MỚI]

Code skeleton đã chuẩn bị sẵn trong workspace native_bridge/; copy vào repo /home/locpham/SonicForgeStudio/native_bridge/ rồi hoàn thiện từng task.

ID File Hành động DoD
A1 native_bridge/ Copy toàn bộ skeleton từ workspace vào repo native_bridge/CMakeLists.txt + include/*.h + src/*.cpp tồn tại
A2 native_bridge/vst3sdk/ git submodule add https://github.com/steinbergmedia/vst3sdk.git thư mục vst3sdk/CMakeLists.txt tồn tại
A3 native_bridge/vcpkg.json Tạo manifest: {"dependencies":["fluidsynth","sfizz","pkgconf"]} vcpkg install chạy từ manifest không lỗi
A4 native_bridge/include/SharedMemoryIPC.h Giữ struct spec + controlQueue (đã có); bổ sung field uint64_t blockTimestamp (đo latency IPC) struct khớp layout Rust ở B3
A5 native_bridge/src/main.cpp Hoàn thiện: đọc control LOAD (đã có), thêm xử lý type=3 TRANSPORT_PLAY/STOP (flush note-off khi Stop), log mỗi lần load instrument ra stdout Stop timeline → bridge gửi note-off toàn pitch
A6 native_bridge/src/NativeInstrumentEngine.cpp Giữ FluidSynth + sfizz (đã có); hoàn thiện Vst3Instrument: host qua vst3sdk (IAudioProcessor::process, IPlugView::attached với HWND con), factory make_instrument trả về instance thật load .vst3 → noteon → PCM khác 0
🟡 A7 native_bridge/include/Vst3Instrument.h + src/Vst3Instrument.cpp Tách class VST3 riêng (hiện đang stub trong main.cpp); xử lý openGUI(void* hwnd) compile OK khi có vst3sdk
A8 native_bridge/src/Vst2Instrument.cpp (Tùy chọn, GĐ sau) Host VST2 qua vestige skip nếu không đủ thời gian; ghi TODO
A9 native_bridge/src/SharedMemoryIPC.cpp Bỏ no-op; thêm helper open_shm(name), write_midi, write_control (tái dùng bởi test) unit test nhỏ gọi helper pass

B. RUST / TAURI SHELL (src-tauri/) — [MỚI] + [BỔ SUNG]

ID File Hành động DoD
B1 src-tauri/Cargo.toml BỔ SUNG dependency windows-sys (features Win32_System_MemoryManagement, Win32_Foundation) để CreateFileMapping/MapViewOfFile cargo check pass
B2 src-tauri/src/shm.rs [MỚI] Module SHM: create_shm(name) -> ShmHandle tạo SonicForge_DAW_IPC (CreateFileMapping + MapViewOfFile, size = sizeof(SharedAudioBufferIPC)), expose write_midi_event(evt), read_audio_block(), reset_queue() cargo check pass; struct layout khớp A4
B3 src-tauri/src/lib.rs BỔ SUNG EngineProcess thứ hai cho bridge: bridge_candidates()binaries/daw_vst_bridge-x86_64-pc-windows-msvc.exe (resource dir + exe dir), spawn với env SF_SHM_NAME=SonicForge_DAW_IPC + SF_PARENT_PID, redirect stdout→%APPDATA%/SonicForgeDAW/logs/bridge.log, kill khi window destroyed spawn.log có dòng [daw_vst_bridge] exists=True
B4 src-tauri/src/lib.rs BỔ SUNG invoke_handler với commands: push_midi_event(cmd,ch,pitch,vel,sampleOffset), load_native_instrument(path,type), open_vst_gui(pluginId), bridge_status(), transport_control(kind) window.__TAURI__.core.invoke(...) trả về OK
B5 src-tauri/src/lib.rs [MỚI] Audio pump thread: mỗi ~5.8ms đọc SHM (bridgeWriteIndex tăng) → app.emit("bridge-audio", {L: Float32Array, R: Float32Array}); nếu index không tăng 1s → emit bridge-down UI nhận event bridge-audio
B6 src-tauri/tauri.conf.json BỔ SUNG bundle.externalBin: ["binaries/daw_vst_bridge"] (giữ resources engine) npx tauri build bundle chứa daw_vst_bridge-x86_64-pc-windows-msvc.exe
B7 src-tauri/capabilities/default.json Xác nhận không cần permission mới (commands app-level) — nếu dùng plugin window cho GUI VST thì thêm core:window:allow-create tauri dev không cảnh báo permission

C. FRONTEND SERVICES MỚI (app/static/js/services/) — [MỚI]

ID File Hành động DoD
C1 unifiedMidiRouter.js [MỚI] Class UnifiedMidiRouter: pushEvent({cmd,ch,pitch,vel,sampleOffset}) normalize UnifiedMidiEvent; subscribe(source) cho Web MIDI + Timeline; khi bridgeConnected → gọi nativeBridgeService.dispatchMidiEvent, ngược lại → gọi SonicSF.playNote/stopNote (fallback); panic()transport_control('panic') 1 router dùng chung 2 nguồn
C2 nativeBridgeService.js [MỚI] Như spec NHƯNG dispatchMidiEvent = window.__TAURI__.core.invoke('push_midi_event', evt) (không có __TAURI_IPC_SHM__); loadInstrument(filePath,type) invoke load_native_instrument; openNativeGUI(pluginId) invoke open_vst_gui; lắng nghe bridge-audio/bridge-down, expose onAudio(cb) MIDI keyboard → note chạy qua bridge
C3 audioRoutingEngine.js [MỚI] connectNativeBridgeToTrackFX(bridgeNode, trackId) theo spec §VI: nối node vào trackContext.sfEntry → FX chain → masterBus.input; disconnect() khi bridge down âm bridge qua EQ track + master maximizer
C4 app/templates/index.html BỔ SUNG 3 script tag sau soundfontPlayer.js (dòng 41): nativeBridgeService.js, unifiedMidiRouter.js, audioRoutingEngine.js (kèm ?v= mới) reload page → window.SonicMidiRouter tồn tại

D. WIRING app/static/js/app.jsx — [THAY]

ID Vị trí (dòng hiện tại) Hành động DoD
D1 onmidimessage ~15587 [THAY] Nhánh NOTE_ON/OFF gọi SonicSF.playNote/SonicCarlaMidi.noteOn → gọi UnifiedMidiRouter.pushEvent trước; giữ fallback khi !bridgeConnected bấm keyboard → router log + âm qua bridge
D2 updatePlayhead ~20771+ (nhánh dispatch midiItems) [THAY] Dispatch note timeline → pushEvent kèm sampleOffset tính từ playhead (bỏ setTimeout drift); Stop/Play → transport_control play piano roll đúng nhịp, Stop hết âm ngân
D3 playNativeSfNote / SonicSF.playNote call sites (track armed, keybed, sub-tab) [THAY] Đổi đích tới router khi bridge active (cờ SF_BRIDGE_AVAILABLE) mọi đường note đi qua 1 cổng
D4 SonicCarlaMidi (runtime.js ~74) [THAY] shouldRouteCarla trả false khi bridge connected (VSTi do bridge host); giữ Carla khi không có bridge không kép âm VST (bridge + Carla)
D5 updateSfRouting ~21255 + setOutputDestination [BỔ SUNG] Nhánh mới: khi bridge active, audioRoutingEngine.connectNativeBridgeToTrackFX(node, trackId) thay vì SonicSF.setOutputDestination âm bridge vào đúng track FX chain
D6 panic/stopAll (soundfontPlayer.js) + Stop handler app.jsx [BỔ SUNG] Stop/panic → transport_control('panic') tới bridge + vẫn gọi fallback cục bộ không note kẹt sau Stop
D7 UI Plugin Manager / Synth dropdown [BỔ SUNG] Hiển thị trạng thái bridge (bridge_status); nút "Load vào Native Bridge" gọi C2.loadInstrument với path resolve từ API plugins (Bổ sung E1) chọn VSTi/SF2 → bridge load, GUI VST mở được

E. BACKEND PYTHON (app/) — [BỔ SUNG]

ID File Hành động DoD
E1 app/api/v1/plugins.py BỔ SUNG endpoint GET /api/v1/bridge/status (đọc file %APPDATA%/SonicForgeDAW/ipc/bridge_status do Rust ghi, hoặc probe bridge.log) curl /api/v1/bridge/status trả JSON có connected
E2 app/api/v1/plugins.py BỔ SUNG endpoint POST /api/v1/bridge/load — resolve path thật (sf2 trong storage/soundfonts, .vst3 trong plugin_dirs) rồi ghi file IPC ipc/bridge_load.request cho Rust đọc (giống cơ chế pick_dir hiện có) Rust nhận request → bridge load → UI nhận bridge-audio
E3 app/core/vst_engine.py Giữ nguyên cho offline render; thêm comment + hàm resolve_plugin_path(name) tái dùng bởi E2 không phá luồng offline hiện có
E4 desktop_engine.py Không đổi logic; kiểm tra watchdog không giết bridge (bridge do Rust spawn, engine chỉ quản lý chính nó) chạy standalone vẫn OK

F. BUILD & ĐÓNG GÓI WINDOWS — [BỔ SUNG]

ID File Hành động DoD
F1 build/scripts/build_native_bridge.ps1 Copy từ workspace (đã sẵn): vcpkg bootstrap + install fluidsynth/sfizz + submodule vst3sdk + cmake MSVC + copy exe chạy trên máy Windows → exe trong src-tauri/binaries/
🟡 F2 build/scripts/build_windows.ps1 Copy từ workspace: pipeline 7 bước (0. kill engine+bridge, 1. pip, 2. frontend, 3. pyinstaller, 4. copy engine, 5. build bridge, 6. vc_redist, 7. tauri build) build 1 lệnh ra NSIS+MSI
F3 build/scripts/test_standalone.ps1 Copy từ workspace: 8 bước test (process, spawn.log, SHM, health, bridge/status, load SF2, checklist tay) pass #1-#5
F4 tools/verify_bundle.py BỔ SUNG kiểm tra thêm: src-tauri/binaries/daw_vst_bridge-x86_64-pc-windows-msvc.exe tồn tại trước tauri build build fail sớm nếu thiếu bridge
F5 src-tauri/hooks.nsh Xác nhận vc_redist.x64.exe được cài cho cả daw_engine.exe + daw_vst_bridge.exe (bridge cần VC++ runtime) máy sạch cài app → bridge chạy

G. TEST — [BỔ SUNG]

ID Nội dung DoD
G1 Unit test C++ (A9 helper) assert-based check trong native_bridge/tests/ chạy pass
G2 Test SHM: Rust tạo → bridge mở → ghi MIDI → đọc audio bridgeWriteIndex tăng; PCM khác 0 khi noteon
G3 Test load SF2/SF3 (SGM-V2.01) + SFZ (SalamanderPiano) + VST3 (Vital) spec VII mục 2 pass
G4 Test live MIDI + timeline cùng lúc (Nektar SE49) spec VII mục 3 pass, latency <10ms
G5 Test Track FX + Master FX tác động âm bridge spec VII mục 4 pass
G6 Test fallback: kill bridge → SonicSF WASM tiếp tục, không crash spec VII mục 6 pass
G7 Chạy test_standalone.ps1 trên máy cài standalone 5/5 bước tự động pass

H. BỔ SUNG LIỀN MẠCH (MẠCH DỮ LIỆU HOÀN CHỈNH)

Các task nhóm H lấp những khâu còn hở giữa A-G để pipeline chạy end-to-end không đứt đoạn. Đánh số tiếp theo nhóm tương ứng.

H-A. Bridge C++ — bổ sung

ID File Hành động DoD
A10 native_bridge/src/main.cpp + NativeInstrumentEngine.h [MỚI] Thay currentInstrument (1 instance) bằng InstrumentEngineManager: std::map<uint32_t, std::unique_ptr<INativeInstrument>> keyed theo MIDI channel; assign_instrument(channel, path, type); MIDI event → instance theo evt.channel; channel chưa gán → silent (không crash) 2 track (ch 0 + ch 2) 2 instrument phát đồng thời
A11 native_bridge/src/main.cpp [BỔ SUNG] Sample-accurate: không xử lý MIDI ngay; buffer event theo sampleOffset, rẽ block 256 thành các đoạn theo offset, gọi noteOn/Off đúng vị trí trước khi render đoạn đó note timeline không lệch nhịp (>±1 sample test)
A12 native_bridge/include/SharedMemoryIPC.h + engine [BỔ SUNG] Mở rộng MidiEventIPC: field data2 (+data3 PB MSB); hỗ trợ 0xB CC (FluidSynth fluid_synth_cc), 0xC program change, 0xE pitch bend (fluid_synth_pitch_bend); VST3 → tham số tương ứng sustain/CC/pitchbend qua bridge hoạt động
A13 native_bridge/src/main.cpp [BỔ SUNG] Control type=3 TRANSPORT: arg0=0 STOP → flush note-off toàn channel + fluid_synth_all_notes_off; arg0=1 PLAY kèm arg1=playheadSamples → đồng bộ timeline Stop → hết âm ngân tức thì

H-B. Rust — bổ sung

ID File Hành động DoD
B8 src-tauri/src/lib.rs [BỔ SUNG] Spawn bridge kèm env SF_SAMPLE_RATE + SF_BLOCK_SIZE (đọc từ bridge_status/cấu hình, mặc định 44100/256); nếu AudioContext.sampleRate khác → JS resampler (C5) — ghi chú trong code SR khác 44100 vẫn nghe đúng cao độ
B9 src-tauri/src/lib.rs [BỔ SUNG] open_vst_gui(pluginId): tạo Tauri child window (WebviewWindowBuilder, title = plugin, childOf main) → lấy HWND (windows-sys) → ghi control type=4 OPEN_GUI + hwnd vào SHM → bridge openGUI(hwnd); window đóng → close_gui VST GUI hiện trong cửa sổ con, đóng sạch
B10 src-tauri/src/lib.rs [BỔ SUNG] Health monitor: nếu bridgeWriteIndex đứng >3s và process chết → spawn lại 1 lần, vẫn fail → emit("bridge-down"); đếm số lần restart vào bridge.log app tự phục hồi bridge 1 lần; UI fallback sau đó

H-C. Frontend — bổ sung

ID File Hành động DoD
C5 app/static/js/services/bridgeAudioNode.js [MỚI] Sink phát âm: nhận bridge-audio (Float32Array L/R) → ring buffer depth 8 block (~46ms) chống underrun → ScriptProcessor/Worklet đẩy ra AudioNode (output chuẩn stereo); nút này là "bridgeNode" mà C3 nối; VU analyser tap trên node (__vuAnalyser như _gainNode cũ) để track VU nhảy; resampler tuyến tính nếu SR khác bridge âm nghe được từ SHM; VU track hoạt động
C6 app/static/js/app.jsx (useEffect khởi tạo ~gần initMasterBus) [BỔ SUNG] Bootstrap bridge: gọi bridge_status() → set SF_BRIDGE_AVAILABLE; nativeBridgeService.onAudio nối vào bridgeAudioNode; subscribe bridge-down → ngắt node + quay về SonicSF cờ đúng trước note đầu tiên; bridge chết → fallback tự động
C7 app/static/js/services/nativeBridgeService.js [BỔ SUNG] Dev-mode mock: không có window.__TAURI__ (Linux dev / browser) → mọi invoke thành log + onAudio không emit → app chạy bằng SonicSF như cũ dev trên Linux không crash

H-D. app.jsx — bổ sung

ID Vị trí Hành động DoD
D8 router (C1) [BỔ SUNG] Channel allocation: bảng trackId → channel 0-15 thay _engineChMap/allocateChannel; percussion (bank 128) → ch 9; gửi CC0/CC32/program change qua bridge khi đổi instrument (A12) 2 track 2 instrument không trùng channel
D9 updatePlayhead [BỔ SUNG] Playhead sample counter: playheadSample += block*(dt*sr/block); sampleOffset = playheadSample - lastBlockStartSamples; gửi transport_control('set_position', playheadSample) mỗi bar timeline chính xác sample
D10 UI header/status [BỔ SUNG] Indicator bridge (connected/down) + nút "Ép dùng WASM" (debug): gọi SonicMidiRouter.setForceWasm(true) user biết engine nào đang phát

H-E. Backend — bổ sung

ID File Hành động DoD
E5 app/api/v1/plugins.py [BỔ SUNG] GET /api/v1/bridge/log?lines=100 — trả tail bridge.log cho UI debug UI xem được log bridge

H-F. Build — bổ sung

ID File Hành động DoD
F6 DESKTOP_INSTALL_PLAN.md / DEPLOYMENT_REPORT.md [BỔ SUNG] Cập nhật tài liệu: kiến trúc 2 sidecar (daw_engine + daw_vst_bridge), yêu cầu VC++ runtime, port/API mới tài liệu khớp thực tế
F7 ci/build-windows.yml [MỚI] (Tùy chọn) GitHub Actions: runner windows-latest → build bridge + engine + tauri → artifact installer pipeline CI ra NSIS setup

H-G. Test — bổ sung

ID Nội dung DoD
G8 Đo latency end-to-end: đọc blockTimestamp (A4) qua bridge_status + round-trip MIDI→loa (loopback/mic); so ngưỡng IPC ≤0.5ms; end-to-end <10ms
G9 Test multi-instance: track 1 SF2 (piano) + track 2 SFZ (string) chạy cùng lúc, đổi instrument live không choke, đúng âm từng track
G10 Test CC/sustain/pitchbend qua bridge (A12) sustain pedal + pitch wheel + CC volume hoạt động

SƠ ĐỒ MẠCH DỮ LIỆU HOÀN CHỈNH (SAU KHI ĐỦ TASKS)

Web MIDI keyboard ──┐
Timeline playhead ──┴─► UnifiedMidiRouter (C1) ─ invoke push_midi_event (B4/C2)
   (D8: track→channel)      │  bridge down → SonicSF fallback (C1/C6/C7)
                            ▼
Rust shm.rs write_midi_event (B2) ──► SHM "SonicForge_DAW_IPC"
                            ▼
bridge main.cpp (A11 sampleOffset, A12 CC/PC/PB, A13 transport)
  → InstrumentEngineManager (A10) → FluidSynth / sfizz / VST3 (A6-A8)
                            ▼
SHM masterLeft/Right + bridgeWriteIndex + blockTimestamp (A4)
                            ▼
Rust audio pump (B5/B10 health) ─ emit 'bridge-audio' ─► bridgeAudioNode (C5)
                            ▼
audioRoutingEngine (C3) → track sfEntry → track FX → masterBus → speakers
                            ▼
VU analyser tap (C5) → track VU nhảy

PHỤ THUỘC (thứ tự thực hiện — đã cập nhật)

  1. A1-A4 → B1-B4 → C1-C2 + C5-C7 (skeleton + IPC + router + audio sink) — nền tảng.
  2. A10-A13 → B5, B8 → C3, C6 → D1-D2, D8-D9 (multi-instance + sample-accurate + audio pump + routing + wiring).
  3. A5-A7, A12 → B9-B10 → D3-D7, D10 → E1-E5 (engine thật + VST GUI + gỡ Carla + UI + backend).
  4. F1-F7 → G1-G10 (build pipeline + test) — cần máy Windows.

CÒN THIẾU SO VỚI SPEC (đã bổ sung vào tasks — bản đầy đủ)

  • SHM tạo bởi Rust (B2) — spec chỉ có bridge mở.
  • Đường MIDI JS→bridge (B4/C2) — spec dùng __TAURI_IPC_SHM__ không tồn tại.
  • Audio pump SHM→WebView (B5/C5) — spec nói zero-copy nhưng WebView không zero-copy được.
  • Transport/panic + playhead position (A5/A13/B4/C1/D6/D9) — spec thiếu, cần cho timeline Stop/đồng bộ.
  • Nhiều instrument/track (A10/D8) — spec chỉ 1 currentInstrument.
  • VST3 GUI child window (B9/A7) — spec có openGUI nhưng thiếu cơ chế HWND con từ Tauri.
  • CC/program/pitchbend (A12) — spec chỉ note on/off; app hiện có sustain/pitch wheel.
  • Sample-accurate scheduling (A11/D9) — spec xử lý MIDI ngay lập tức, bỏ qua sampleOffset.
  • Resampler khi SR AudioContext ≠ bridge (B8/C5).
  • Ring buffer chống underrun + VU tap (C5) — kế thừa hành vi _gainNode.__vuAnalyser cũ.
  • Fallback SonicSF (C1/C6/C7/G6) — spec không đề cập; giữ để app không chết khi bridge lỗi.
  • Dev-mode mock (C7) — chạy được trên Linux/browser không có __TAURI__.