fix(export): MIDI offline khong co am thanh - cache preview cam/khong du -> tu chuyen sang bounce REALTIME (capture masterBus.output) thay vi xuat file cam
This commit is contained in:
+12
-2
@@ -27970,7 +27970,7 @@ const App = () => {
|
||||
const hasMidiItems = allT.some(t => t.midiItems && t.midiItems.length > 0)
|
||||
|| (sessionTabs || []).some(s => (s.tracks || []).some(t => t.midiItems && t.midiItems.length > 0));
|
||||
if (hasMidiItems) {
|
||||
const midiTracks = allT.filter(t => t.midiItems && t.midiItems.length > 0);
|
||||
const midiTracks = allT.filter(t => t.midiItems && t.midiItems.length > 0 && !t.muted);
|
||||
let needDur = 1.0;
|
||||
midiTracks.forEach(t => {
|
||||
(t.midiItems || []).forEach(m => {
|
||||
@@ -27993,7 +27993,17 @@ const App = () => {
|
||||
const c = midiCacheRef.current[t.id];
|
||||
return c && c.chunks.length > 0 && c.duration >= needDur - 0.3 && cacheHasEnergy(c);
|
||||
});
|
||||
if (!cacheOK) {
|
||||
// MIDI chỉ render offline qua preview cache (tap sfEntry lúc play). Cache
|
||||
// câm/thiếu khi routing bridge vào masterBus.input hoặc sub-node thay vì
|
||||
// sfEntry → file offline đủ duration nhưng KHÔNG CÓ ÂM THANH. Khi cache
|
||||
// không đủ → TỰ chuyển sang bounce REALTIME (capture masterBus.output =
|
||||
// đúng âm đang nghe) thay vì xuất file câm. Áp dụng Export Project (mix).
|
||||
if (midiTracks.length > 0 && !cacheOK && exportSettings.source === 'project' && exportSettings.format === 'wav') {
|
||||
showToast("Cache MIDI chưa đủ — tự chuyển sang bounce REALTIME để giữ nguyên âm thanh...", "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");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2116,7 +2116,12 @@ const triggerWavExport=async()=>{// Stop transport ngay khi bắt đầu export
|
||||
try{if(typeof stopAllPlayback==='function')stopAllPlayback();}catch(e){}try{if(window.SonicSF&&window.SonicSF.stopAllNotes)window.SonicSF.stopAllNotes();}catch(e){}// Realtime UNCHECKED → export OFFLINE thuần (server-side nếu connected, fallback client).
|
||||
// MIDI không render được trong OfflineAudioContext: nếu project có MIDI items mà
|
||||
// cache preview chưa đủ → file xuất có thể thiếu âm MIDI. Cảnh báo, vẫn xuất offline.
|
||||
const allT=activeTracksRef.current&&activeTracksRef.current.length?activeTracksRef.current:tracks;const hasMidiItems=allT.some(t=>t.midiItems&&t.midiItems.length>0)||(sessionTabs||[]).some(s=>(s.tracks||[]).some(t=>t.midiItems&&t.midiItems.length>0));if(hasMidiItems){const midiTracks=allT.filter(t=>t.midiItems&&t.midiItems.length>0);let needDur=1.0;midiTracks.forEach(t=>{(t.midiItems||[]).forEach(m=>{if(m.endTime)needDur=Math.max(needDur,m.endTime);else if(m.startTime)needDur=Math.max(needDur,m.startTime+(m.duration||8));});});const cacheHasEnergy=c=>{if(!c)return false;const chunks=c.chunks||[];for(let i=0;i<chunks.length;i++){const ch=chunks[i];for(let j=0;j<ch.length;j++){if(Math.abs(ch[j])>0.0005)return true;}}return false;};const cacheOK=midiTracks.length>0&&midiTracks.every(t=>{const c=midiCacheRef.current[t.id];return c&&c.chunks.length>0&&c.duration>=needDur-0.3&&cacheHasEnergy(c);});if(!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<numCh;ch++){clipBuf.copyToChannel(selTrack.buffer.getChannelData(ch).subarray(startSample,endSample),ch);}exportTracks=[{...selTrack,buffer:clipBuf,startTime:0,clips:[{id:'export_clip',buffer:clipBuf,startTime:0,name:selTrack.name}]}];}else if(src==='track_mix'){const sel=tracks.filter(t=>!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ừ
|
||||
const allT=activeTracksRef.current&&activeTracksRef.current.length?activeTracksRef.current:tracks;const hasMidiItems=allT.some(t=>t.midiItems&&t.midiItems.length>0)||(sessionTabs||[]).some(s=>(s.tracks||[]).some(t=>t.midiItems&&t.midiItems.length>0));if(hasMidiItems){const midiTracks=allT.filter(t=>t.midiItems&&t.midiItems.length>0&&!t.muted);let needDur=1.0;midiTracks.forEach(t=>{(t.midiItems||[]).forEach(m=>{if(m.endTime)needDur=Math.max(needDur,m.endTime);else if(m.startTime)needDur=Math.max(needDur,m.startTime+(m.duration||8));});});const cacheHasEnergy=c=>{if(!c)return false;const chunks=c.chunks||[];for(let i=0;i<chunks.length;i++){const ch=chunks[i];for(let j=0;j<ch.length;j++){if(Math.abs(ch[j])>0.0005)return true;}}return false;};const cacheOK=midiTracks.length>0&&midiTracks.every(t=>{const c=midiCacheRef.current[t.id];return c&&c.chunks.length>0&&c.duration>=needDur-0.3&&cacheHasEnergy(c);});// MIDI chỉ render offline qua preview cache (tap sfEntry lúc play). Cache
|
||||
// câm/thiếu khi routing bridge vào masterBus.input hoặc sub-node thay vì
|
||||
// sfEntry → file offline đủ duration nhưng KHÔNG CÓ ÂM THANH. Khi cache
|
||||
// không đủ → TỰ chuyển sang bounce REALTIME (capture masterBus.output =
|
||||
// đúng âm đang nghe) thay vì xuất file câm. Áp dụng Export Project (mix).
|
||||
if(midiTracks.length>0&&!cacheOK&&exportSettings.source==='project'&&exportSettings.format==='wav'){showToast("Cache MIDI chưa đủ — tự chuyển sang bounce REALTIME để giữ nguyên âm thanh...","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<numCh;ch++){clipBuf.copyToChannel(selTrack.buffer.getChannelData(ch).subarray(startSample,endSample),ch);}exportTracks=[{...selTrack,buffer:clipBuf,startTime:0,clips:[{id:'export_clip',buffer:clipBuf,startTime:0,name:selTrack.name}]}];}else if(src==='track_mix'){const sel=tracks.filter(t=>!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<t.buffer.numberOfChannels;ch++){buf.copyToChannel(t.buffer.getChannelData(ch).subarray(ls,le),ch);}return{...t,buffer:buf,startTime:0,clips:[{id:'export_loop_clip',buffer:buf,startTime:0,name:t.name,speed:1}]};}).filter(Boolean);if(exportTracks.length===0){showToast("Vùng loop không có dữ liệu âm thanh.","warning");return;}}// Check if all active tracks have server file IDs
|
||||
|
||||
@@ -51,7 +51,7 @@
|
||||
<script src="/static/js/services/promptTemplateManager.js?v=202607281039"></script>
|
||||
<script src="/static/js/services/undoRedoEngine.js?v=202607290941"></script>
|
||||
<script src="/static/js/services/chordTheory.js?v=202608211300"></script>
|
||||
<script src="/static/js/app.precompiled.js?v=202608232000" defer></script>
|
||||
<script src="/static/js/app.precompiled.js?v=202608232200" defer></script>
|
||||
<link rel="stylesheet" href="/static/css/styles.css?v=202607271016">
|
||||
<style>
|
||||
:root {
|
||||
|
||||
Reference in New Issue
Block a user