diff --git a/app/static/js/app.jsx b/app/static/js/app.jsx index a0fe7da..fe90460 100644 --- a/app/static/js/app.jsx +++ b/app/static/js/app.jsx @@ -12380,12 +12380,20 @@ const App = () => { const updateActiveTracksRef = useCallback(updater => { const currentSessionTabs = sessionTabsRef.current; const currentActiveTab = activeTabRef.current; - const st = currentSessionTabs.find(s => s.id === currentActiveTab); + var st = currentSessionTabs.find(s => s.id === currentActiveTab); if (st) { setSessionTabs(prev => prev.map(s => s.id === currentActiveTab ? { ...s, tracks: updater(s.tracks), isDirty: true } : s)); - } else { - setTracks(updater); + return; } + var pr = subTabsRef.current ? subTabsRef.current.find(s => s.id === currentActiveTab && s.type === 'PIANO_ROLL') : null; + if (pr && pr.parent_tab_id && pr.parent_tab_id.startsWith('session_')) { + var parentSt = currentSessionTabs.find(s => s.id === pr.parent_tab_id); + if (parentSt) { + setSessionTabs(prev => prev.map(s => s.id === pr.parent_tab_id ? { ...s, tracks: updater(s.tracks), isDirty: true } : s)); + return; + } + } + setTracks(updater); }, []); const updateActiveTracks = updateActiveTracksRef; diff --git a/app/static/js/app.precompiled.js b/app/static/js/app.precompiled.js index 53b3814..a07c3ad 100644 --- a/app/static/js/app.precompiled.js +++ b/app/static/js/app.precompiled.js @@ -348,7 +348,7 @@ uploadToServer(file,trackId);// Decode locally for playback + analyze channels ( const{audioBuffer:decodedBuffer,channelInfo}=await window.SonicAudio.decodeAudioFile(file);setTracks(prev=>prev.map(t=>t.id===trackId?{...t,name:file.name,buffer:decodedBuffer,channelInfo:channelInfo}:t));showToast(`Nạp file thành công: ${file.name} (${channelInfo.label})`,'success');}catch(err){showToast("Lỗi giải mã âm thanh. Định dạng file không tương thích.",'error');}};// ── Synth Generators ── const generateSynthToTrack=(trackId,type)=>{const sampleRate=44100;const duration=12.0;const context=getAudioContext();const frameCount=sampleRate*duration;const newBuffer=context.createBuffer(1,frameCount,sampleRate);const channelData=newBuffer.getChannelData(0);if(type==='kick'){for(let i=0;iprev.map(t=>t.id===trackId?{...t,name:`Demo_${type.toUpperCase()}.wav`,buffer:newBuffer}:t));showToast(`Đã nạp sóng âm tổng hợp: ${type.toUpperCase()}`,'success');};// ── Add Track ── const addNewTrack=()=>{const curTracks=activeTracks;const newId=(curTracks.length+1).toString();const colors=['#0f766e','#1d4ed8','#701a75','#a21caf','#b45309'];const selectColor=colors[curTracks.length%colors.length];updateActiveTracks(prev=>[...prev,{id:newId,name:`Track ${newId}`,buffer:null,startTime:0,volumeDb:0,pan:0,muted:false,solo:false,color:selectColor,markers:[],serverFileId:null,clips:[],sections:[],midiItems:[],isArmed:false,monitoringEnabled:true,inputSource:{deviceType:'NONE',deviceId:''}}]);showToast(`Đã thêm Track ${newId}.`,'info');setTimeout(()=>lucide.createIcons(),200);return newId;};// ── Update tracks in active context (main session or section tab) ── -const updateActiveTracksRef=useCallback(updater=>{const currentSessionTabs=sessionTabsRef.current;const currentActiveTab=activeTabRef.current;const st=currentSessionTabs.find(s=>s.id===currentActiveTab);if(st){setSessionTabs(prev=>prev.map(s=>s.id===currentActiveTab?{...s,tracks:updater(s.tracks),isDirty:true}:s));}else{setTracks(updater);}},[]);const updateActiveTracks=updateActiveTracksRef;const toggleTrackArm=trackId=>{updateActiveTracks(prev=>prev.map(t=>{if(t.id!==trackId)return t;return{...t,isArmed:!t.isArmed};}));setTimeout(()=>lucide.createIcons(),50);};const toggleTrackMonitor=trackId=>{updateActiveTracks(prev=>prev.map(t=>{if(t.id!==trackId)return t;return{...t,monitoringEnabled:!t.monitoringEnabled};}));setTimeout(()=>lucide.createIcons(),50);};const updateTrackInputSource=(trackId,type,deviceId)=>{updateActiveTracks(prev=>prev.map(t=>{if(t.id!==trackId)return t;return{...t,inputSource:{deviceType:type,deviceId}};}));};// ── MIDI Export: tạo file MIDI từ tất cả tracks ── +const updateActiveTracksRef=useCallback(updater=>{const currentSessionTabs=sessionTabsRef.current;const currentActiveTab=activeTabRef.current;var st=currentSessionTabs.find(s=>s.id===currentActiveTab);if(st){setSessionTabs(prev=>prev.map(s=>s.id===currentActiveTab?{...s,tracks:updater(s.tracks),isDirty:true}:s));return;}var pr=subTabsRef.current?subTabsRef.current.find(s=>s.id===currentActiveTab&&s.type==='PIANO_ROLL'):null;if(pr&&pr.parent_tab_id&&pr.parent_tab_id.startsWith('session_')){var parentSt=currentSessionTabs.find(s=>s.id===pr.parent_tab_id);if(parentSt){setSessionTabs(prev=>prev.map(s=>s.id===pr.parent_tab_id?{...s,tracks:updater(s.tracks),isDirty:true}:s));return;}}setTracks(updater);},[]);const updateActiveTracks=updateActiveTracksRef;const toggleTrackArm=trackId=>{updateActiveTracks(prev=>prev.map(t=>{if(t.id!==trackId)return t;return{...t,isArmed:!t.isArmed};}));setTimeout(()=>lucide.createIcons(),50);};const toggleTrackMonitor=trackId=>{updateActiveTracks(prev=>prev.map(t=>{if(t.id!==trackId)return t;return{...t,monitoringEnabled:!t.monitoringEnabled};}));setTimeout(()=>lucide.createIcons(),50);};const updateTrackInputSource=(trackId,type,deviceId)=>{updateActiveTracks(prev=>prev.map(t=>{if(t.id!==trackId)return t;return{...t,inputSource:{deviceType:type,deviceId}};}));};// ── MIDI Export: tạo file MIDI từ tất cả tracks ── const triggerMidiExport=()=>{const bpmNum=parseFloat(bpm)||120;const ppq=480;// Pulses Per Quarter Note const ticksPerBeat=ppq;const beatDuration=60/bpmNum;// Build MIDI tracks let midiTracks=[];let currentTrackNum=0;tracks.forEach(track=>{currentTrackNum++;const events=[];let hasNotes=false;// Collect MIDI events from midiItems diff --git a/wiki.md b/wiki.md index ce4d85d..7a16a89 100644 --- a/wiki.md +++ b/wiki.md @@ -538,3 +538,9 @@ - **Các file ảnh hưởng:** `app/static/js/app.jsx`, `app/static/js/app.precompiled.js` - **Ghi chú/Test (nếu có):** `npm run build` pass. Ctrl+Click+Drag section/MIDI item → copy đến vị trí mới, item không bị selected. --- + +### [2026-07-28 08:12] Task: Sync instrument load methods từ MAIN vào SECTION-TAB PIANO_ROLL +- **Tóm tắt thay đổi:** Fix `updateActiveTracksRef` — khi `activeTab` là PIANO_ROLL sub-tab có `parent_tab_id` trỏ đến SECTION-TAB, routing instrument update vào `setSessionTabs` thay vì `setTracks` (main). Trước đây đổi instrument trong PIANO_ROLL của SECTION-TAB ghi vào main tracks (vô hiệu). Cùng logic với `activeTracks` useMemo fix trước đó. +- **Các file ảnh hưởng:** `app/static/js/app.jsx`, `app/static/js/app.precompiled.js` +- **Ghi chú/Test (nếu có):** `npm run build` pass. Mở SECTION-TAB → PIANO_ROLL → Synth → chọn instrument → verify instrument được áp dụng cho section track (không phải main track). +---