From 4b643883fffd83c015c013199508b3104f6bbc0b Mon Sep 17 00:00:00 2001 From: locphamtran Date: Mon, 24 Aug 2026 20:51:13 +0700 Subject: [PATCH] fix(export): offline render dong export panel + hien progress timeline renderMasteringExport khong tu dong export panel/progress (chi bounce cu lam). triggerWavExport: nhanh offline render -> setShowExportPanel(false), setShowExportProgress(true), setExportProgress(-1) truoc khi goi, reset progress trong finally. --- app/static/js/app.jsx | 11 ++++++++++- app/static/js/app.precompiled.js | 4 +++- app/templates/index.html | 2 +- 3 files changed, 14 insertions(+), 3 deletions(-) diff --git a/app/static/js/app.jsx b/app/static/js/app.jsx index 0e0c923..b3fee43 100644 --- a/app/static/js/app.jsx +++ b/app/static/js/app.jsx @@ -28001,7 +28001,16 @@ const App = () => { // xuất file 1 note rồi câm / câm hẳn) → chỉ dùng làm fallback khi server lỗi. if (midiTracks.length > 0 && exportSettings.source === 'project' && exportSettings.format === 'wav') { if (serverStatus === 'connected') { - await renderMasteringExport(); + // Đóng export panel + hiện progress timeline (renderMasteringExport + // không tự đóng — bounce cũ có làm). + setShowExportPanel(false); + setShowExportProgress(true); + setExportProgress(-1); + try { + await renderMasteringExport(); + } finally { + setShowExportProgress(false); + } return; } if (!cacheOK) { diff --git a/app/static/js/app.precompiled.js b/app/static/js/app.precompiled.js index fc6947c..e4af72e 100644 --- a/app/static/js/app.precompiled.js +++ b/app/static/js/app.precompiled.js @@ -2123,7 +2123,9 @@ const allT=activeTracksRef.current&&activeTracksRef.current.length?activeTracksR // project có MIDI → render OFFLINE deterministic qua server (đầy đủ MIDI + // FX + mastering, đúng như DAW). Bounce REALTIME race không đáng tin (từng // xuất file 1 note rồi câm / câm hẳn) → chỉ dùng làm fallback khi server lỗi. -if(midiTracks.length>0&&exportSettings.source==='project'&&exportSettings.format==='wav'){if(serverStatus==='connected'){await renderMasteringExport();return;}if(!cacheOK){showToast("Cache MIDI chưa đủ và server chưa kết nối — tự chuyển sang bounce REALTIME...","info");await triggerBounceExport();return;}}if(midiTracks.length>0&&!cacheOK){showToast("Project có MIDI items chưa đủ cache preview — file offline có thể thiếu âm MIDI. Bật Realtime để bounce trung thực.","warning");}}let exportTracks;let clipStart=0;let clipEnd=0;const src=exportSettings.source;if(src==='active_clip'||src==='clip_selection'){const selTrack=selectedTrackId?tracks.find(t=>t.id===selectedTrackId):null;if(!selTrack||!selTrack.buffer){showToast("Không có clip nào được chọn.","warning");return;}let rangeStart=selectionStart;let rangeEnd=selectionEnd;if(rangeStart===null||rangeEnd===null||rangeEnd<=rangeStart){rangeStart=0;rangeEnd=selTrack.buffer.duration;}const ctx=getAudioContext();const numCh=selTrack.buffer.numberOfChannels||1;const sr=selTrack.buffer.sampleRate;const startSample=Math.max(0,Math.floor(rangeStart*sr));const endSample=Math.min(selTrack.buffer.length,Math.floor(rangeEnd*sr));const len=endSample-startSample;if(len<=100){showToast("Vùng chọn quá ngắn hoặc không có dữ liệu.","warning");return;}const clipBuf=ctx.createBuffer(numCh,len,sr);for(let ch=0;ch!t.muted&&(t.buffer||t.midiItems&&t.midiItems.length>0));const selTrk=selectedTrackId?sel.filter(t=>t.id===selectedTrackId):sel;if(selTrk.length===0){showToast("Track được chọn không có dữ liệu.","warning");return;}exportTracks=selTrk;}else{exportTracks=tracks.filter(t=>!t.muted&&(t.buffer||t.midiItems&&t.midiItems.length>0));}if(exportTracks.length===0){showToast("Không tìm thấy dữ liệu âm thanh hợp lệ để xuất.","warning");return;}// LOOP export (BIG_PHASE_3 Phase 2): cắt chính xác vùng loop — bắt đầu từ +if(midiTracks.length>0&&exportSettings.source==='project'&&exportSettings.format==='wav'){if(serverStatus==='connected'){// Đóng export panel + hiện progress timeline (renderMasteringExport +// không tự đóng — bounce cũ có làm). +setShowExportPanel(false);setShowExportProgress(true);setExportProgress(-1);try{await renderMasteringExport();}finally{setShowExportProgress(false);}return;}if(!cacheOK){showToast("Cache MIDI chưa đủ và server chưa kết nối — tự chuyển sang bounce REALTIME...","info");await triggerBounceExport();return;}}if(midiTracks.length>0&&!cacheOK){showToast("Project có MIDI items chưa đủ cache preview — file offline có thể thiếu âm MIDI. Bật Realtime để bounce trung thực.","warning");}}let exportTracks;let clipStart=0;let clipEnd=0;const src=exportSettings.source;if(src==='active_clip'||src==='clip_selection'){const selTrack=selectedTrackId?tracks.find(t=>t.id===selectedTrackId):null;if(!selTrack||!selTrack.buffer){showToast("Không có clip nào được chọn.","warning");return;}let rangeStart=selectionStart;let rangeEnd=selectionEnd;if(rangeStart===null||rangeEnd===null||rangeEnd<=rangeStart){rangeStart=0;rangeEnd=selTrack.buffer.duration;}const ctx=getAudioContext();const numCh=selTrack.buffer.numberOfChannels||1;const sr=selTrack.buffer.sampleRate;const startSample=Math.max(0,Math.floor(rangeStart*sr));const endSample=Math.min(selTrack.buffer.length,Math.floor(rangeEnd*sr));const len=endSample-startSample;if(len<=100){showToast("Vùng chọn quá ngắn hoặc không có dữ liệu.","warning");return;}const clipBuf=ctx.createBuffer(numCh,len,sr);for(let ch=0;ch!t.muted&&(t.buffer||t.midiItems&&t.midiItems.length>0));const selTrk=selectedTrackId?sel.filter(t=>t.id===selectedTrackId):sel;if(selTrk.length===0){showToast("Track được chọn không có dữ liệu.","warning");return;}exportTracks=selTrk;}else{exportTracks=tracks.filter(t=>!t.muted&&(t.buffer||t.midiItems&&t.midiItems.length>0));}if(exportTracks.length===0){showToast("Không tìm thấy dữ liệu âm thanh hợp lệ để xuất.","warning");return;}// LOOP export (BIG_PHASE_3 Phase 2): cắt chính xác vùng loop — bắt đầu từ // selection (nếu có) hoặc playhead, độ dài = loopBars * secondsPerBar. // Không fade in/out, không kéo đuôi (cắt cứng tại biên). let loopStart=0,loopDur=0;if(exportSettings.loop){const loopSecPerBar=60.0/(parseFloat(bpm)||120)*4;loopStart=selectionStart!==null&&selectionEnd!==null&&selectionEnd>selectionStart?selectionStart:currentTime;loopDur=Math.max(0.1,(exportSettings.loopBars||4)*loopSecPerBar);const lctx=getAudioContext();exportTracks=exportTracks.map(t=>{if(!t.buffer)return null;const ls=Math.max(0,Math.floor(loopStart*lctx.sampleRate));const le=Math.min(t.buffer.length,Math.ceil((loopStart+loopDur)*lctx.sampleRate));if(le<=ls)return null;const buf=lctx.createBuffer(t.buffer.numberOfChannels,le-ls,t.buffer.sampleRate);for(let ch=0;ch - +