From 2c93c701cebf12c0d84ad7fe539d6c276094e692 Mon Sep 17 00:00:00 2001 From: locphamtran Date: Fri, 21 Aug 2026 11:25:49 +0700 Subject: [PATCH] =?UTF-8?q?feat(export):=20Export=20Mix=20loop=20kh=C3=B4n?= =?UTF-8?q?g=20fade=20(checkbox=20+=20c=E1=BA=AFt=20buffer=20client)=20+?= =?UTF-8?q?=20server=20download=20thay=20blob=20(WebView2=20ch=E1=BA=B7n)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- IMPLEMENT_PHASE_3.md | 21 ++++- app/static/js/app.jsx | 139 +++++++++++++++++++++++++------ app/static/js/app.precompiled.js | 41 ++++++--- app/templates/index.html | 2 +- 4 files changed, 160 insertions(+), 43 deletions(-) diff --git a/IMPLEMENT_PHASE_3.md b/IMPLEMENT_PHASE_3.md index 1240e8f..f6a570d 100644 --- a/IMPLEMENT_PHASE_3.md +++ b/IMPLEMENT_PHASE_3.md @@ -8,7 +8,7 @@ | Phase | Nội dung | Trạng thái | Ghi chú | |-------|----------|-----------|---------| | 1 | Fix Export MIDI | ✅ | backend + frontend + build + verify xong (commit riêng) | -| 2 | Fix Export Mix + loop | ⏳ | | +| 2 | Fix Export Mix + loop | ✅ | frontend + build + verify xong (commit riêng) | | 3 | FX CHAIN (hợp nhất modal) | ⏳ | | | 4 | Nút FX TCP → FX CHAIN | ⏳ | wiring đã có, cần verify | | 5 | Virtual MIDI Keyboard | ⏳ | | @@ -35,7 +35,24 @@ --- ## PHASE 2 — FIX EXPORT MIX + LOOP -(chưa bắt đầu) + +### Đã làm +- [x] Khảo sát: 3 đường xuất — server `POST /multitrack/mix` (cần `allOnServer && connected`), client `clientSideExport` (OfflineAudioContext, raw buffer không fade), bounce `triggerBounceExport` (realtime capture master bus, trước đây luôn +1.2s tail + blob download). Worker `app/tasks/worker.py` tôn trọng `fade_in_ms/fade_out_ms` từ clip. +- [x] UI ExportModal: checkbox "Xuất loop (không fade in / không fade out)" + input "Số ô nhịp" (1..256, mặc định 4). +- [x] Helper `downloadBlobViaServer(blob, filename)`: POST `/api/v1/audio/upload` → download `${API_BASE_URL}/api/v1/audio/download/{file_id}`; fail → fallback blob download + throw (pattern Phase 1). +- [x] `triggerWavExport`: khi loop → cắt buffer từng track đúng `[loopStart, loopStart+loopDur)` (`loopSecPerBar=(60/bpm)*4`, `loopStart` = selection nếu hợp lệ else playhead), clip `export_loop_clip` startTime 0, luôn đi client path (buffer đã cắt phía client), gọi `clientSideExport(exportTracks, {loopStart, loopDur})`. +- [x] `clientSideExport(activeTracks, loopInfo)`: `durationLimit = loopDur` khi loop (không kéo đuôi); midi cache buffer cắt `subarray(0, loopDur*sr)`; **fix bug**: midi cache không được kéo dài `durationLimit` khi loop (file không dư đuôi); WAV thay blob download bằng `downloadBlobViaServer`. +- [x] `triggerBounceExport`: khi loop → `durationLimit = loopDur` (bỏ +1.2 tail), sau capture cắt cứng `[loopStart, loopStart+loopDur)` frames khỏi stereo buffer, download qua `downloadBlobViaServer`. +- [x] Build: `node build.mjs` OK (2 lần — lần 2 sau fix durationLimit) + PyInstaller rebuild engine + copy `dist/daw_engine/*` sang `src-tauri/target/release/daw_engine/` + bump cache-bust `app.precompiled.js?v=202608211105`. +- [x] Verify chạy thật: engine serve `app.precompiled.js` mới (md5 khớp build local, đủ loop UI/loopActive/downloadBlobViaServer/export_loop_clip), index.html `?v=202608211105`; `/upload` + `/download` roundtrip OK; `POST /multitrack/mix` (server path) SUCCESS → file 2.0s đúng, all samples nonzero (không regression). +- [x] Self-check toán loop (node `tools/_loopcheck.mjs`): độ dài WAV = `loopBars*4*60/bpm` ±0 sample với mọi tổ hợp (120bpm×4, 120×4 target 48k, 90×8 start 1.5s, 140×1), biên đầu/cuối giữ nguyên mẫu (không fade) — PASS. +- [x] Regression cũ: `tools/shm_note_energy.py` (energy 0 — app chưa load instrument, đúng), `tools/test_sf2_ring.py` PASS (energy 12.337, ring decay), `tools/test_vst2_gui.py` PASS (DUNE host + editor VSTGUI, proc alive). + +### Tồn tại / cần bổ sung +- Loop với track MIDI: midi cache cắt từ đầu cache (`subarray(0, loopDur*sr)`), chưa offset theo `loopStart` — đúng với thiết kế cache preview (cache bắt đầu từ preview), ghi nhận để Phase 7 review nếu cần. + +### Cần xác nhận (review) +- (trống) ## PHASE 3 — FX CHAIN (chưa bắt đầu) diff --git a/app/static/js/app.jsx b/app/static/js/app.jsx index b53d4b4..988558c 100644 --- a/app/static/js/app.jsx +++ b/app/static/js/app.jsx @@ -11931,6 +11931,22 @@ const ExportModal = ({ open, onClose, exportSettings, setExportSettings, isExpor
+
+ + {exportSettings.loop && ( + + )} +