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 @@
-
+