diff --git a/app/static/js/app.jsx b/app/static/js/app.jsx index e32e88f..c79b405 100644 --- a/app/static/js/app.jsx +++ b/app/static/js/app.jsx @@ -14568,8 +14568,22 @@ const App = () => { updateSfRouting(); }, [tracks, sessionTabs]); + const prevActiveTabRef = useRef(activeTab); useEffect(() => { + const prevTab = prevActiveTabRef.current; + prevActiveTabRef.current = activeTab; updateSfRouting(); + // Tab DEACTIVE → stop âm của tab đó (user requirement): rời khỏi sub-tab + // (PIANO_ROLL/section/audio tab) đang play → stop playback của tab đó. + // Tab mới hoạt động bình thường; MAIN giữ hành vi cũ (mở piano-roll lúc + // main play → main tiếp tục — handleEditMidiInTab đã xử lý). + if (prevTab !== activeTab) { + const prevSub = subTabsRef.current.find(s => s.id === prevTab); + if (prevSub && prevSub.isPlaying) { + try { stopAllPlayback(); } catch (e) {} + setSubTabs(prev => prev.map(s => s.id === prevTab ? { ...s, isPlaying: false } : s)); + } + } }, [activeTab]); const [selectionCleared, setSelectionCleared] = useState(false); // LOOP_EDITOR_2.md §4.2 diff --git a/app/static/js/app.precompiled.js b/app/static/js/app.precompiled.js index fe2db18..ced9bc8 100644 --- a/app/static/js/app.precompiled.js +++ b/app/static/js/app.precompiled.js @@ -631,7 +631,11 @@ useEffect(()=>{const all=[...(tracks||[]),...(sessionTabs||[]).reduce((acc,s)=>a const audioSig=list.map(t=>(t.muted?'1':'0')+(t.solo?'1':'0')+':'+(t.volumeDb??0)+':'+(t.audioBypass?'1':'0')+(t.midiBypass?'1':'0')+':'+(t.midiItems||[]).length+':'+(t.clips||[]).length+':'+(t.sections||[]).length).join('|');if(trackAudioSyncSigRef.current===audioSig)return;trackAudioSyncSigRef.current=audioSig;list.forEach(t=>{// ♪ state → SF mastering route (sfRouteGain/sfDryGain), live on existing nodes const sn=activeTrackNodesRef.current[t.id];if(sn&&sn.sfRouteGain&&sn.sfDryGain&&sn.sfRouteGain.gain.value>0!==!effMidiBypass(t)){const _b=effMidiBypass(t);sn.sfRouteGain.gain.value=_b?0:1;sn.sfDryGain.gain.value=_b?1:0;}const sig=(t.muted?'1':'0')+(t.solo?'1':'0')+':'+(t.volumeDb??0);if(trackMuteSoloSigRef.current[t.id]===sig)return;trackMuteSoloSigRef.current[t.id]=sig;const audible=computeTrackAudibleGain(list,t)>0;const node=activeTrackNodesRef.current[t.id];const audibleGain=computeTrackAudibleGain(list,t);if(node)setTrackNodeGain(node,audibleGain);if(node&&node.sfEntry){try{const g=typeof audibleGain==='number'&&isFinite(audibleGain)&&!isNaN(audibleGain)?audibleGain:1.0;node.sfEntry.gain.setTargetAtTime(g,getAudioContext().currentTime,0.02);}catch(e){}}// MIDI tracks: mirror the gain decision onto the channel CC7 volume so // FluidSynth-rendered notes respect mute/solo too. -if(t.midiItems&&t.midiItems.length>0||t.type==='MIDI'){try{if(window.SonicSF&&typeof window.SonicSF.controllerChange==='function'){const ch=ensureTrackMidiChannel(t,list);window.SonicSF.controllerChange(ch,7,audible?100:0);}}catch(e){}}trackAudibleRef.current[t.id]=audible;});updateSfRouting();},[tracks,sessionTabs]);useEffect(()=>{updateSfRouting();},[activeTab]);const[selectionCleared,setSelectionCleared]=useState(false);// LOOP_EDITOR_2.md §4.2 +if(t.midiItems&&t.midiItems.length>0||t.type==='MIDI'){try{if(window.SonicSF&&typeof window.SonicSF.controllerChange==='function'){const ch=ensureTrackMidiChannel(t,list);window.SonicSF.controllerChange(ch,7,audible?100:0);}}catch(e){}}trackAudibleRef.current[t.id]=audible;});updateSfRouting();},[tracks,sessionTabs]);const prevActiveTabRef=useRef(activeTab);useEffect(()=>{const prevTab=prevActiveTabRef.current;prevActiveTabRef.current=activeTab;updateSfRouting();// Tab DEACTIVE → stop âm của tab đó (user requirement): rời khỏi sub-tab +// (PIANO_ROLL/section/audio tab) đang play → stop playback của tab đó. +// Tab mới hoạt động bình thường; MAIN giữ hành vi cũ (mở piano-roll lúc +// main play → main tiếp tục — handleEditMidiInTab đã xử lý). +if(prevTab!==activeTab){const prevSub=subTabsRef.current.find(s=>s.id===prevTab);if(prevSub&&prevSub.isPlaying){try{stopAllPlayback();}catch(e){}setSubTabs(prev=>prev.map(s=>s.id===prevTab?{...s,isPlaying:false}:s));}}},[activeTab]);const[selectionCleared,setSelectionCleared]=useState(false);// LOOP_EDITOR_2.md §4.2 const midiVuActivityRef=useRef({});const triggerMidiVuActivity=(trackId,velocity)=>{if(!trackId)return;const velFactor=typeof velocity==='number'?velocity>1?velocity/127:velocity:0.8;const peak=Math.min(1.0,Math.max(0.15,velFactor));midiVuActivityRef.current[trackId]=peak;};window.triggerMidiVuActivity=triggerMidiVuActivity;// ── Temp Edit Tab (LOOP_EDITOR_2.md §1.2 Sub Tab) ── const[tempTabActive,setTempTabActive]=useState(false);const[tempTabBuffer,setTempTabBuffer]=useState(null);const[tempTabTrackId,setTempTabTrackId]=useState(null);const[tempTabOrigStart,setTempTabOrigStart]=useState(0);const[tempTabOrigEnd,setTempTabOrigEnd]=useState(0);const tempTabCanvasRef=useRef(null);// Effect parameters for temp tab const[tempTabEffects,setTempTabEffects]=useState({reverse:false,gainDb:0,fadeInMs:0,fadeOutMs:0});// ── Auth / User State ── diff --git a/app/templates/index.html b/app/templates/index.html index a0b1331..d461614 100644 --- a/app/templates/index.html +++ b/app/templates/index.html @@ -24,7 +24,7 @@ - +