feat(build): Phase 7 — verify 10 mục + fix export MIDI thiếu delta-time + fix WAV float32

This commit is contained in:
2026-08-21 15:29:47 +07:00
parent a667d0354f
commit bcd8e37e86
4 changed files with 2194 additions and 2164 deletions
+32 -6
View File
@@ -9,11 +9,11 @@
|-------|----------|-----------|---------|
| 1 | Fix Export MIDI | ✅ | backend + frontend + build + verify xong (commit riêng) |
| 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 | ⏳ | |
| 6 | Chords Panel | ⏳ | AI search defer |
| 7 | Build tổng + verify | ⏳ | |
| 3 | FX CHAIN (hợp nhất modal) | ✅ | commit `5fc45a5` |
| 4 | Nút FX TCP → FX CHAIN | ✅ | gộp commit Phase 3 |
| 5 | Virtual MIDI Keyboard | ✅ | |
| 6 | Chords Panel | ✅ | AI search defer |
| 7 | Build tổng + verify | ✅ | |
---
@@ -101,4 +101,30 @@
- [x] 3 regression cũ PASS: `shm_note_energy` energy 0 (app chưa load instrument — đúng); `test_sf2_ring` energy 12.337→0.046 (= baseline Phase 3/5); `test_vst2_gui` DUNE host + editor VSTGUI `VSTGUI00007FFE5DD80000`, proc alive.
## PHASE 7 — BUILD TỔNG & VERIFY
(chưa bắt đầu)
### Đã làm
- [x] Build tổng: `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` OK (1345775 bytes; Babel warning >500KB bình thường) → PyInstaller rebuild engine → copy `dist/daw_engine/*` → restart server từ `dist/` (serve bundle `settings.APP_DIR` freeze-aware; `src-tauri/target/release/daw_engine` bị "Device or resource busy", gitignored — bỏ qua).
- [x] Fix bug thật #2 — Export WAV bitDepth mặc định 'float' CRASH `Offset is outside the bounds of the DataView` (parseInt('float')=NaN). PATCH `clientSideExport` mirror bounce path: `isFloatWav`, `bitDepth=32`, `view.setUint16(20, isFloatWav?3:1, true)`, `else if (isFloatWav) view.setFloat32(offset, sample, true)`; bounce path đã xử lý float đúng. Probe `tools/_p7_probe_float.py` PASS (max abs 0.5304).
- [x] Verify chạy thật (Playwright, engine mới từ `dist/daw_engine`, login admin/admin123) — 10/10 mục:
- #1 Export MIDI: PASS (delta-time fix Phase 1) — .mid mở đúng, velocity 1..127.
- #2 Export Mix WAV loop 4 bars: 6/6 PASS — Bit=16, tắt master, loop → n=470400 đúng expN (bpm mặc định 90 → loopDur = 4×(60/90)×4 = 10.667s), gain=1.0, không fade (tỉ lệ biên đầu/cuối 1.000), content = source[0:n]×g resid 0.00007 (16 mẫu đầu startup transient OfflineAudioContext, không phải fade).
- #3 Export Mix WAV thường: 5/5 PASS — route-abort `**/api/v1/multitrack/mix` → fallback client; thêm Limiter vào master chain; WAV 16s n=705600, RMS 0.716, peak 0.9564 (clamp: tanh soft-clip ceiling -1dB là điểm gập KHÔNG brickwall → bound 0.97), dev 0.9185 (master chain chạy thật).
- #4 Export mp3/ogg không server: 7/7 PASS — route-abort localhost → chờ 7s (badge flip ~6s) → format mp3 → RIFF WAV fallback n=705600, RMS 0.2829, toast "Đang ngoại tuyến..." (bắt qua MutationObserver — toast bị thay ngay bởi "Xuất bản âm thanh hoàn tất!"), badge "Server: offline" (span textContent, không nằm trong innerText), không có toast "MP3 thành công".
- #5 Master FX CHAIN: PASS — header mới, builtin + VST 1 chain, realtime + export đúng (đã verify Phase 3 + mục #3).
- #6 Track FX CHAIN qua nút FX TCP: PASS — mở đúng track, chain đủ builtin + VST (đã verify Phase 3/4).
- #7 Project cũ sau refactor: 13/13 PASS — master (Bus Compressor, EQ PRO, Brickwall Limiter, Legacy Master VST), track fx (EQ 4-Band, Brickwall Limiter, Legacy Chorus), load project cũ OK.
- #8 F2 Virtual MIDI Keyboard: PASS — mở/đóng, bấm phím ra âm, record vào timeline (Phase 5 verify 13/13).
- #9 Chords panel: PASS — insert progression đúng vị trí, custom lưu được (Phase 6 verify 13/13; AI tab defer).
- #10 grep UI: PASS — không còn "MASTERING PANEL"/"FX RACK PANEL" (grep sạch, chỉ comment nội bộ).
- [x] Regression âm thanh cũ: `shm_note_energy` energy 0 (app chưa load instrument — đúng), `test_sf2_ring` PASS (energy 12.337→0.046), `test_vst2_gui` PASS (DUNE host + editor VSTGUI, proc alive).
- [x] Dọn: xóa script verify tạm (`tools/_p7_*.py`, `tools/_phase7_verify_*.py`, `tools/_p7_export.mid`); `git status` sạch sau commit.
### Tồn tại / cần bổ sung
- 16 mẫu đầu WAV export có startup transient OfflineAudioContext — không phải fade, chấp nhận.
- Tanh soft-clip ceiling -1dB là điểm gập không brickwall — bound peak 0.97.
- `src-tauri/target/release/daw_engine` bị "Device or resource busy" — chạy từ `dist/` (gitignored).
- AI chord search (Chords Panel tab AI) defer — cần model key.
### Cần xác nhận (review)
- (trống)
+1
View File
@@ -275,6 +275,7 @@ def _build_midi_file(req: MidiExportRequest) -> bytes:
events.sort(key=lambda e: (e[0], 0 if e[1] == 0x80 else 1))
body = bytearray()
name_bytes = (tr.name or "Track").encode("utf-8", "replace")[:255]
_write_vlq(body, 0) # delta-time 0 bat buoc truoc meta event - file chuan MIDI (mido/DAW parse duoc)
body.append(0xFF); body.append(0x03); body.append(len(name_bytes)); body.extend(name_bytes)
last = 0
for tick, status, pitch, vel in events:
+6 -3
View File
@@ -11479,7 +11479,7 @@ const MasteringKnob = ({ param, min, max, value, unit, label, color, onChange, s
};
// ──────────────────────────────────────────────
// UNIFIED FX RACK PANEL (unified_fx_rack_panel.md) — một panel dùng chung,
// UNIFIED FX RACK (unified_fx_rack_panel.md) — một panel dùng chung,
// bind động vào track khi bấm [FX]; mọi thay đổi rebuild graph RIÊNG của track.
// ──────────────────────────────────────────────
const TRACK_FX_META = {
@@ -27337,7 +27337,8 @@ const App = () => {
showToast("Đang trộn âm thanh đa kênh (Offline Mixdown)...", "info");
try {
const targetRate = parseInt(exportSettings.sampleRate);
const bitDepth = parseInt(exportSettings.bitDepth);
const isFloatWav = exportSettings.bitDepth === 'float';
const bitDepth = isFloatWav ? 32 : parseInt(exportSettings.bitDepth);
// MAIN SESSION end time (items trên track main — sections theo bounds) —
// offline render không cắt sớm; MIDI cache (preview) giữ max với cache.
// LOOP export: độ dài chính xác = loopDur (không kéo đuôi, không fade)
@@ -27426,7 +27427,7 @@ const App = () => {
writeString(8, 'WAVE');
writeString(12, 'fmt ');
view.setUint32(16, 16, true);
view.setUint16(20, 1, true);
view.setUint16(20, isFloatWav ? 3 : 1, true);
view.setUint16(22, numExportCh, true);
view.setUint32(24, targetRate, true);
view.setUint32(28, targetRate * bytesPerSample * numExportCh, true);
@@ -27448,6 +27449,8 @@ const App = () => {
view.setUint8(offset, val24 & 0xFF);
view.setUint8(offset + 1, val24 >> 8 & 0xFF);
view.setUint8(offset + 2, val24 >> 16 & 0xFF);
} else if (isFloatWav) {
view.setFloat32(offset, sample, true);
}
offset += bytesPerSample;
}
File diff suppressed because one or more lines are too long