feat(piano-roll): vào tab lúc main play -> chỉ play MIDI notes track đang edit, các track khác mute
useEffect [activeTab] mới: activeTab là PIANO_ROLL và main/section đang play -> stopAllPlayback + restart play CHỈ track piano roll tại vị trí tương ứng trong item (mainPos - item.startTime). mainResumeRef giữ để quay lại main/section resume đúng vị trí (nhánh resume cũ không đổi).
This commit is contained in:
+27
-4
@@ -16943,6 +16943,29 @@ const App = () => {
|
||||
console.log('[SectionTab] play CHỈ section items tại local', _pt.toFixed(2), '(secStart', _secStart.toFixed(2) + ')');
|
||||
} catch (e) { console.warn('[SectionTab] restart error:', e); }
|
||||
}
|
||||
// Vào PIANO ROLL lúc MAIN/SECTION đang play → CHỈ play MIDI notes CỦA
|
||||
// TRACK đang edit, TẮT âm MỌI track khác (user: vào piano roll phải play
|
||||
// only midi notes đang active). Dừng main → play lại CHỈ track piano roll
|
||||
// tại vị trí TƯƠNG ỨNG trong item (mainPos − item.startTime). Rời tab →
|
||||
// nhánh resume (dưới) về main/section đúng vị trí (mainResumeRef vẫn giữ).
|
||||
const _prTabNew = activeTab && activeTab.startsWith('midi_') ? subTabsRef.current.find(s => s.id === activeTab && s.type === 'PIANO_ROLL') : null;
|
||||
if (_prTabNew && !_prTabNew.isPlaying && isPlaying) {
|
||||
try {
|
||||
const _ctx = getAudioContext();
|
||||
const _mainPos = startOffsetTimeRef.current + (_ctx.currentTime - startAudioTimeRef.current);
|
||||
const _itemInfo = getPianoRollItemInfo(_prTabNew);
|
||||
const _prPos = _itemInfo ? Math.max(0, _mainPos - (_itemInfo.startTime || 0)) : (_prTabNew.currentTime || 0);
|
||||
stopAllPlayback();
|
||||
startOffsetTimeRef.current = _prPos;
|
||||
startAudioTimeRef.current = _ctx.currentTime;
|
||||
startBufferOffsetRef.current = _prPos * (_prTabNew.speed || 1.0);
|
||||
schedulePianoRollMidi(_prTabNew, _prPos);
|
||||
startSubTabPlayback(_prTabNew, _prPos);
|
||||
setSubTabs(prev => prev.map(s => s.id === _prTabNew.id ? { ...s, isPlaying: true, currentTime: _prPos } : s));
|
||||
animationFrameIdRef.current = requestAnimationFrame(updatePlayhead);
|
||||
console.log('[PianoRollTab] play CHỈ track', _prTabNew.trackId, 'tại local', _prPos.toFixed(2), '(mainPos', _mainPos.toFixed(2), 'itemStart', _itemInfo ? _itemInfo.startTime.toFixed(2) : '?', ')');
|
||||
} catch (e) { console.warn('[PianoRollTab] restart error:', e); }
|
||||
}
|
||||
const prevSub = subTabsRef.current.find(s => s.id === prevTab);
|
||||
if (prevSub && prevSub.isPlaying) {
|
||||
// Chuyển sang SUB-TAB KHÁC (piano roll khác/audio tab) → STOP âm tab
|
||||
@@ -18950,10 +18973,10 @@ const App = () => {
|
||||
const midiItem = (track.midiItems || []).find(m => m.id === midiItemId);
|
||||
if (!midiItem) return;
|
||||
|
||||
// Clean stop playback ONLY khi KHÔNG đang play main — nếu main đang play,
|
||||
// mở PIANO ROLL tab KHÔNG được dừng âm (user: dblclick mở tab lúc main
|
||||
// play → âm main phải tiếp tục). Nhánh play sub-tab tự stopAllPlayback
|
||||
// trước khi play nên không xung đột route.
|
||||
// Main đang play → CHỈ lưu điểm resume; useEffect [activeTab] tự
|
||||
// stopAllPlayback + play CHỈ track piano roll khi vào tab (user: piano roll
|
||||
// play only midi notes đang active — mọi track khác mute). mainResumeRef
|
||||
// giữ để quay lại MAIN/SECTION resume đúng vị trí.
|
||||
if (isPlaying) {
|
||||
// Lưu điểm resume: nếu user bấm Space trong piano roll (play piano roll
|
||||
// → stopAllPlayback dừng main) → khi quay lại MAIN/SECTION resume main
|
||||
|
||||
@@ -975,7 +975,12 @@ if(prevTab!==activeTab){// Vào SECTION-TAB lúc MAIN đang play → CHỈ play
|
||||
// activeTracks = section clones (activeTab mới → activeTracksRef mới).
|
||||
if(prevTab==='main'&&activeTab&&activeTab.startsWith('session_')&&isPlaying){try{const _ctx=getAudioContext();// Vị trí play → LOCAL của section tab (trừ secStart — vị trí section
|
||||
// trên main): section tab items dùng time local (0-based)
|
||||
let _secStart=0;const _tab=sessionTabsRef.current.find(st=>st.id===activeTab);if(_tab&&_tab.sectionId){(tracks||[]).forEach(_t=>(_t.sections||[]).forEach(_s=>{if(_s.sectionId===_tab.sectionId||_s.id===_tab.sectionId)_secStart=_s.start||0;}));}const _pt=Math.max(0,startOffsetTimeRef.current+(_ctx.currentTime-startAudioTimeRef.current)-_secStart);stopAllPlayback();startOffsetTimeRef.current=_pt;startAudioTimeRef.current=_ctx.currentTime;startTrackPlayback(_pt);setIsPlaying(true);console.log('[SectionTab] play CHỈ section items tại local',_pt.toFixed(2),'(secStart',_secStart.toFixed(2)+')');}catch(e){console.warn('[SectionTab] restart error:',e);}}const prevSub=subTabsRef.current.find(s=>s.id===prevTab);if(prevSub&&prevSub.isPlaying){// Chuyển sang SUB-TAB KHÁC (piano roll khác/audio tab) → STOP âm tab
|
||||
let _secStart=0;const _tab=sessionTabsRef.current.find(st=>st.id===activeTab);if(_tab&&_tab.sectionId){(tracks||[]).forEach(_t=>(_t.sections||[]).forEach(_s=>{if(_s.sectionId===_tab.sectionId||_s.id===_tab.sectionId)_secStart=_s.start||0;}));}const _pt=Math.max(0,startOffsetTimeRef.current+(_ctx.currentTime-startAudioTimeRef.current)-_secStart);stopAllPlayback();startOffsetTimeRef.current=_pt;startAudioTimeRef.current=_ctx.currentTime;startTrackPlayback(_pt);setIsPlaying(true);console.log('[SectionTab] play CHỈ section items tại local',_pt.toFixed(2),'(secStart',_secStart.toFixed(2)+')');}catch(e){console.warn('[SectionTab] restart error:',e);}}// Vào PIANO ROLL lúc MAIN/SECTION đang play → CHỈ play MIDI notes CỦA
|
||||
// TRACK đang edit, TẮT âm MỌI track khác (user: vào piano roll phải play
|
||||
// only midi notes đang active). Dừng main → play lại CHỈ track piano roll
|
||||
// tại vị trí TƯƠNG ỨNG trong item (mainPos − item.startTime). Rời tab →
|
||||
// nhánh resume (dưới) về main/section đúng vị trí (mainResumeRef vẫn giữ).
|
||||
const _prTabNew=activeTab&&activeTab.startsWith('midi_')?subTabsRef.current.find(s=>s.id===activeTab&&s.type==='PIANO_ROLL'):null;if(_prTabNew&&!_prTabNew.isPlaying&&isPlaying){try{const _ctx=getAudioContext();const _mainPos=startOffsetTimeRef.current+(_ctx.currentTime-startAudioTimeRef.current);const _itemInfo=getPianoRollItemInfo(_prTabNew);const _prPos=_itemInfo?Math.max(0,_mainPos-(_itemInfo.startTime||0)):_prTabNew.currentTime||0;stopAllPlayback();startOffsetTimeRef.current=_prPos;startAudioTimeRef.current=_ctx.currentTime;startBufferOffsetRef.current=_prPos*(_prTabNew.speed||1.0);schedulePianoRollMidi(_prTabNew,_prPos);startSubTabPlayback(_prTabNew,_prPos);setSubTabs(prev=>prev.map(s=>s.id===_prTabNew.id?{...s,isPlaying:true,currentTime:_prPos}:s));animationFrameIdRef.current=requestAnimationFrame(updatePlayhead);console.log('[PianoRollTab] play CHỈ track',_prTabNew.trackId,'tại local',_prPos.toFixed(2),'(mainPos',_mainPos.toFixed(2),'itemStart',_itemInfo?_itemInfo.startTime.toFixed(2):'?',')');}catch(e){console.warn('[PianoRollTab] restart error:',e);}}const prevSub=subTabsRef.current.find(s=>s.id===prevTab);if(prevSub&&prevSub.isPlaying){// Chuyển sang SUB-TAB KHÁC (piano roll khác/audio tab) → STOP âm tab
|
||||
// cũ — play ĐÚNG nội dung TỪNG TAB (user 07:30: piano roll tab 1 →
|
||||
// tab 2 — không được nghe âm tab 1). Về MAIN/SECTION → giữ luật cũ
|
||||
// (04:15+ — âm tiếp/resume).
|
||||
@@ -1136,10 +1141,10 @@ handleSaveSectionTabRef.current(curTab);}showToast('Đã lưu dự án + các ta
|
||||
const st=subTabsRef.current.find(s=>s.id===curTab);if(st){if(st.type==='PIANO_ROLL'){handleSaveMidiNotes(st.id,st.trackId,st.target_id,st.notes||[]);showToast('Đã lưu Piano Roll','success');}else if(st.type==='SECTION'){handleSaveSectionTab(st.id);showToast('Đã lưu Section Tab','success');}else if(st.buffer){const subTrack=activeTracksRef.current.find(t=>t.id===st.trackId);if(subTrack){updateActiveTracks(prev=>prev.map(t=>t.id===st.trackId?{...t,buffer:st.buffer}:t));}showToast('Đã lưu Audio Tab','success');}}}return;}if(!ctrl&&!alt&&e.key==='s'){e.preventDefault();handleSplitTrackRef.current(selectedTrackIdRef.current);return;}if(e.key==='F7'){e.preventDefault();e.stopPropagation();window.__toggleMixerRef();return;}if(e.key==='F6'){e.preventDefault();e.stopPropagation();window.__toggleMediaExplorerRef();return;}};window.addEventListener('keydown',handler,{capture:true});return()=>window.removeEventListener('keydown',handler,{capture:true});},[]);// ── Temp Tab: draw isolated waveform ──
|
||||
useEffect(()=>{if(!tempTabActive||!tempTabBuffer||!tempTabCanvasRef.current)return;const canvas=tempTabCanvasRef.current;const ctx=canvas.getContext('2d');const dpr=window.devicePixelRatio||1;const rect=canvas.getBoundingClientRect();canvas.width=rect.width*dpr;canvas.height=rect.height*dpr;ctx.scale(dpr,dpr);const w=rect.width;const h=rect.height;ctx.fillStyle='#181818';ctx.fillRect(0,0,w,h);const data=tempTabBuffer.getChannelData(0);const sr=tempTabBuffer.sampleRate;const totalSamples=data.length;if(totalSamples===0)return;ctx.strokeStyle='#6ee7b7';ctx.lineWidth=1;for(let px=0;px<w;px++){const startSample=Math.floor(px/w*totalSamples);const endSample=Math.floor((px+1)/w*totalSamples);let maxVal=0;for(let i=startSample;i<endSample&&i<totalSamples;i++){const abs=Math.abs(data[i]);if(abs>maxVal)maxVal=abs;}const mid=h/2;const peakHeight=maxVal*(h*0.4);ctx.beginPath();ctx.moveTo(px,mid-peakHeight);ctx.lineTo(px,mid+peakHeight);ctx.stroke();}},[tempTabActive,tempTabBuffer]);// ── Sub Tab: open as new tab instead of modal (LOOP_EDITOR_2.md §1) ──
|
||||
const openTempTab=()=>{const useLocal=selectionMode==='local'&&localSelectionTrackId;const trackId=useLocal?localSelectionTrackId:selectedTrackId;const t=tracks.find(x=>x.id===trackId);if(!t||!t.buffer){showToast('Vui lòng chọn track có dữ liệu âm thanh.','warning');return;}if(selLeft===null||selRight===null||selRight<=selLeft){showToast('Vui lòng chọn một khoảng thời gian trên sóng âm.','warning');return;}// Check if a subtab for this track+range already exists
|
||||
const existing=subTabs.find(s=>s.trackId===trackId&&s.startTime===selLeft&&s.endTime===selRight);if(existing){setActiveTab(existing.id);showToast(`Sub Tab already open.`,'info');return;}const sr=t.buffer.sampleRate;const trackStart=t.startTime||0;const relSelLeft=Math.max(0,selLeft-trackStart);const relSelRight=Math.max(0,selRight-trackStart);const startSample=Math.max(0,Math.floor(relSelLeft*sr));const endSample=Math.min(t.buffer.length,Math.floor(relSelRight*sr));const len=endSample-startSample;if(len<100){showToast('Khoảng chọn quá ngắn.','warning');return;}const ctx=getAudioContext();const numChannels=t.buffer.numberOfChannels||1;const subBuffer=ctx.createBuffer(numChannels,len,sr);for(let c=0;c<numChannels;c++){subBuffer.copyToChannel(t.buffer.getChannelData(c).subarray(startSample,endSample),c);}const subChannelInfo=window.SonicAudio.analyzeAudioBufferChannels(subBuffer);const tabId='subtab_'+Date.now();const tabLabel=`Edit_${t.name.replace('.wav','').slice(0,10)}_${selLeft.toFixed(1)}s`;setSubTabs(prev=>[...prev,{id:tabId,label:tabLabel,trackId:trackId,startTime:selLeft,endTime:selRight,buffer:subBuffer,channelInfo:subChannelInfo,effects:{reverse:false,gainDb:0,fadeInMs:0,fadeOutMs:0,normalizeDb:0,pitch:0,speedStretch:100},currentTime:0,selectionStart:null,selectionEnd:null,isPlaying:false,speed:1.0,volumeNodes:[],panningNodes:[],fadeInLen:0,fadeOutLen:0,graphMode:null,isLooping:false,loopCount:0}]);setActiveTab(tabId);};const handleEditClipInSubTab=(trackId,clipId)=>{const track=activeTracks.find(t=>t.id===trackId);if(!track)return;const clips=track.clips&&track.clips.length>0?track.clips:track.buffer?[{id:'default_'+track.id,buffer:track.buffer,startTime:track.startTime||0,name:track.name}]:[];const clip=clips.find(c=>c.id===clipId||clipId==='default'&&c.id==='default_'+trackId);if(!clip||!clip.buffer)return;const resolvedClipId=clip.id==='default'?'default_'+trackId:clip.id;const existing=subTabs.find(s=>s.clipId===resolvedClipId&&s.trackId===trackId);if(existing){setActiveTab(existing.id);showToast(`Sub Tab for "${clip.name}" already open.`,'info');return;}const sr=clip.buffer.sampleRate;const len=clip.buffer.length;const numChannels=clip.buffer.numberOfChannels||1;const ctx=getAudioContext();const subBuffer=ctx.createBuffer(numChannels,len,sr);for(let c=0;c<numChannels;c++){subBuffer.copyToChannel(clip.buffer.getChannelData(c),c);}const subChannelInfo=window.SonicAudio.analyzeAudioBufferChannels(subBuffer);const tabId='subtab_'+Date.now();const tabLabel=`Edit_${clip.name.replace('.wav','').slice(0,10)}`;setSubTabs(prev=>[...prev,{id:tabId,label:tabLabel,trackId:trackId,clipId:resolvedClipId,startTime:clip.startTime,endTime:clip.startTime+clip.buffer.duration,buffer:subBuffer,channelInfo:subChannelInfo,effects:{reverse:false,gainDb:0,fadeInMs:0,fadeOutMs:0,normalizeDb:0,pitch:0,speedStretch:100},currentTime:0,selectionStart:null,selectionEnd:null,isPlaying:false,speed:1.0,volumeNodes:[],panningNodes:[],fadeInLen:0,fadeOutLen:0,graphMode:null,isLooping:false,loopCount:0}]);setActiveTab(tabId);};const handleEditMidiInTab=(trackId,midiItemId)=>{const track=activeTracks.find(t=>t.id===trackId);if(!track)return;const midiItem=(track.midiItems||[]).find(m=>m.id===midiItemId);if(!midiItem)return;// Clean stop playback ONLY khi KHÔNG đang play main — nếu main đang play,
|
||||
// mở PIANO ROLL tab KHÔNG được dừng âm (user: dblclick mở tab lúc main
|
||||
// play → âm main phải tiếp tục). Nhánh play sub-tab tự stopAllPlayback
|
||||
// trước khi play nên không xung đột route.
|
||||
const existing=subTabs.find(s=>s.trackId===trackId&&s.startTime===selLeft&&s.endTime===selRight);if(existing){setActiveTab(existing.id);showToast(`Sub Tab already open.`,'info');return;}const sr=t.buffer.sampleRate;const trackStart=t.startTime||0;const relSelLeft=Math.max(0,selLeft-trackStart);const relSelRight=Math.max(0,selRight-trackStart);const startSample=Math.max(0,Math.floor(relSelLeft*sr));const endSample=Math.min(t.buffer.length,Math.floor(relSelRight*sr));const len=endSample-startSample;if(len<100){showToast('Khoảng chọn quá ngắn.','warning');return;}const ctx=getAudioContext();const numChannels=t.buffer.numberOfChannels||1;const subBuffer=ctx.createBuffer(numChannels,len,sr);for(let c=0;c<numChannels;c++){subBuffer.copyToChannel(t.buffer.getChannelData(c).subarray(startSample,endSample),c);}const subChannelInfo=window.SonicAudio.analyzeAudioBufferChannels(subBuffer);const tabId='subtab_'+Date.now();const tabLabel=`Edit_${t.name.replace('.wav','').slice(0,10)}_${selLeft.toFixed(1)}s`;setSubTabs(prev=>[...prev,{id:tabId,label:tabLabel,trackId:trackId,startTime:selLeft,endTime:selRight,buffer:subBuffer,channelInfo:subChannelInfo,effects:{reverse:false,gainDb:0,fadeInMs:0,fadeOutMs:0,normalizeDb:0,pitch:0,speedStretch:100},currentTime:0,selectionStart:null,selectionEnd:null,isPlaying:false,speed:1.0,volumeNodes:[],panningNodes:[],fadeInLen:0,fadeOutLen:0,graphMode:null,isLooping:false,loopCount:0}]);setActiveTab(tabId);};const handleEditClipInSubTab=(trackId,clipId)=>{const track=activeTracks.find(t=>t.id===trackId);if(!track)return;const clips=track.clips&&track.clips.length>0?track.clips:track.buffer?[{id:'default_'+track.id,buffer:track.buffer,startTime:track.startTime||0,name:track.name}]:[];const clip=clips.find(c=>c.id===clipId||clipId==='default'&&c.id==='default_'+trackId);if(!clip||!clip.buffer)return;const resolvedClipId=clip.id==='default'?'default_'+trackId:clip.id;const existing=subTabs.find(s=>s.clipId===resolvedClipId&&s.trackId===trackId);if(existing){setActiveTab(existing.id);showToast(`Sub Tab for "${clip.name}" already open.`,'info');return;}const sr=clip.buffer.sampleRate;const len=clip.buffer.length;const numChannels=clip.buffer.numberOfChannels||1;const ctx=getAudioContext();const subBuffer=ctx.createBuffer(numChannels,len,sr);for(let c=0;c<numChannels;c++){subBuffer.copyToChannel(clip.buffer.getChannelData(c),c);}const subChannelInfo=window.SonicAudio.analyzeAudioBufferChannels(subBuffer);const tabId='subtab_'+Date.now();const tabLabel=`Edit_${clip.name.replace('.wav','').slice(0,10)}`;setSubTabs(prev=>[...prev,{id:tabId,label:tabLabel,trackId:trackId,clipId:resolvedClipId,startTime:clip.startTime,endTime:clip.startTime+clip.buffer.duration,buffer:subBuffer,channelInfo:subChannelInfo,effects:{reverse:false,gainDb:0,fadeInMs:0,fadeOutMs:0,normalizeDb:0,pitch:0,speedStretch:100},currentTime:0,selectionStart:null,selectionEnd:null,isPlaying:false,speed:1.0,volumeNodes:[],panningNodes:[],fadeInLen:0,fadeOutLen:0,graphMode:null,isLooping:false,loopCount:0}]);setActiveTab(tabId);};const handleEditMidiInTab=(trackId,midiItemId)=>{const track=activeTracks.find(t=>t.id===trackId);if(!track)return;const midiItem=(track.midiItems||[]).find(m=>m.id===midiItemId);if(!midiItem)return;// Main đang play → CHỈ lưu điểm resume; useEffect [activeTab] tự
|
||||
// stopAllPlayback + play CHỈ track piano roll khi vào tab (user: piano roll
|
||||
// play only midi notes đang active — mọi track khác mute). mainResumeRef
|
||||
// giữ để quay lại MAIN/SECTION resume đúng vị trí.
|
||||
if(isPlaying){// Lưu điểm resume: nếu user bấm Space trong piano roll (play piano roll
|
||||
// → stopAllPlayback dừng main) → khi quay lại MAIN/SECTION resume main
|
||||
// từ đúng vị trí âm (offset + elapsed thật) — hết câm.
|
||||
|
||||
Reference in New Issue
Block a user