diff --git a/app/static/js/app.jsx b/app/static/js/app.jsx index cc75196..ca519d2 100644 --- a/app/static/js/app.jsx +++ b/app/static/js/app.jsx @@ -25132,6 +25132,7 @@ const App = () => { trkCh, track.synth_engine ); + scheduleNoteExpressionCC11(track, note, trkCh, context.currentTime, delay, noteDurSec); } // Trigger VU meter flash when the note starts playing setTimeout(() => { @@ -25157,6 +25158,7 @@ const App = () => { trkCh, track.synth_engine ); + scheduleNoteExpressionCC11(track, note, trkCh, context.currentTime, 0, remainingDurMs / 1000); } // Trigger VU meter flash instantly if (window.triggerMidiVuActivity) { @@ -25280,6 +25282,7 @@ const App = () => { subCh, subTrack.synth_engine ); + scheduleNoteExpressionCC11(subTrack, note, subCh, context.currentTime, delay, playDurMs / 1000); } // Trigger VU meter flash đúng nhịp (cả subTrack — VU // section tab + parent track — VU MAIN): MIDI section đi @@ -25309,6 +25312,7 @@ const App = () => { subCh, subTrack.synth_engine ); + scheduleNoteExpressionCC11(subTrack, note, subCh, context.currentTime, 0, remainingDurMs / 1000); } if (window.triggerMidiVuActivity) { // VU key theo CONTEXT (tách MAIN vs SECTION — 07:20) @@ -25415,6 +25419,7 @@ const App = () => { lcCh, track.synth_engine ); + scheduleNoteExpressionCC11(track, note, lcCh, context.currentTime, delay, noteDurSec); } } else { const remainingDurMs = (noteEndSec - offsetTime) * 1000; @@ -25429,6 +25434,7 @@ const App = () => { lcCh, track.synth_engine ); + scheduleNoteExpressionCC11(track, note, lcCh, context.currentTime, 0, remainingDurMs / 1000); } } } diff --git a/app/static/js/app.precompiled.js b/app/static/js/app.precompiled.js index 16261e9..ab6024b 100644 --- a/app/static/js/app.precompiled.js +++ b/app/static/js/app.precompiled.js @@ -1953,11 +1953,11 @@ const midiItems=track.midiItems||[];const nativeSf=isStandaloneSf()&&isSfTrackEn ensureMidiCapture(track,activeTrackNodesRef.current[track.id]);var trkCh=assignTrackMidiChannel(track,allPlayTracks);// Ensure instrument is loaded in FluidSynth if(window.SonicSF&&track.synth_engine&&track.synth_engine.type==='soundfont'&&track.synth_engine.soundfont_id){window.SonicSF.selectInstrument(trkCh,track.synth_engine.soundfont_bank||0,track.synth_engine.soundfont_program||0,track.synth_engine.soundfont_id);}const bpmVal=parseInt(bpm)||120;const secondsPerBeat=60.0/bpmVal;midiItems.forEach(item=>{const itemEndSec=item.startTime+(item.duration||4);const notes=item.notes||[];notes.forEach(note=>{// note start/duration is in beats (for MIDI items) const noteStartSec=item.startTime+(note.start_beat||0)*secondsPerBeat;if(noteStartSec>=itemEndSec)return;// skip notes outside item's visual duration -const noteDurSec=(note.duration_beats||1)*secondsPerBeat;const noteEndSec=noteStartSec+noteDurSec;if(offsetTime{// ⚠️ Guard: stop → setTimeout sót không được fire (VU nhảy // sau khi âm đã dừng — user bug 05:45). if(!isPlayingRef.current)return;if(window.triggerMidiVuActivity){// VU key theo CONTEXT (tách MAIN vs SECTION — user 07:20) -window.triggerMidiVuActivity((activeTab&&activeTab.startsWith('session_')?'_sess_':'')+track.id,note.velocity||0.8);}},delay*1000);}else{const playOffset=offsetTime-noteStartSec;const remainingDurMs=(noteEndSec-offsetTime)*1000;if(window.SonicSF){scheduleMidiNoteDispatch(track,note.pitch||60,note.velocity||0.8,remainingDurMs,context.currentTime,program,gainNode,trkCh,track.synth_engine);}// Trigger VU meter flash instantly +window.triggerMidiVuActivity((activeTab&&activeTab.startsWith('session_')?'_sess_':'')+track.id,note.velocity||0.8);}},delay*1000);}else{const playOffset=offsetTime-noteStartSec;const remainingDurMs=(noteEndSec-offsetTime)*1000;if(window.SonicSF){scheduleMidiNoteDispatch(track,note.pitch||60,note.velocity||0.8,remainingDurMs,context.currentTime,program,gainNode,trkCh,track.synth_engine);scheduleNoteExpressionCC11(track,note,trkCh,context.currentTime,0,remainingDurMs/1000);}// Trigger VU meter flash instantly if(window.triggerMidiVuActivity){window.triggerMidiVuActivity((activeTab&&activeTab.startsWith('session_')?'_sess_':'')+track.id,note.velocity||0.8);}}}});});}}// If track has instrumentId set but no MIDI items, create scheduled oscillators if(track.instrumentId&&midiItems.length===0&&track.buffer){// Play audio normally (the buffer has the audio data) // This block is intentionally empty - audio clips already play above @@ -1970,7 +1970,7 @@ const subClips=subTrack.clips||[];subClips.forEach(clip=>{if(!clip.buffer)return const activeStart=Math.max(clipStartMain,secStart);const activeEnd=Math.min(clipEndMain,secEnd);if(activeStart0&&(window.SonicSF||subNativeSf||subNativeVst)){if(subNativeSf||subNativeVst){scheduleNativeTrack(subTrack,offsetTime,context,subNode,bpm,{baseOffsetSec:secStart,limitSec:secEnd,playheadNow:()=>startOffsetTimeRef.current+(getAudioContext().currentTime-startAudioTimeRef.current),sources:activeSourcesRef.current,isActive:()=>!!isPlayingRef.current});return;// native render → audio buffer (không qua WASM per-note) }subMidiItems.forEach(item=>{const notes=item.notes||[];notes.forEach(note=>{const noteStartSecLocal=item.startTime+(note.start_beat||0)*secondsPerBeat;const noteDurSec=(note.duration_beats||1)*secondsPerBeat;const noteStartMain=secStart+noteStartSecLocal;const noteEndMain=noteStartMain+noteDurSec;// Only play if the note starts inside the Section item bounds and hasn't finished yet -if(noteStartMain>=secStart&¬eStartMain=secStart&¬eStartMain=secStart&¬eStartMain{// ⚠️ Guard: stop → setTimeout sót không được fire (VU // nhảy sau khi âm đã dừng — user bug 05:45). if(!isPlayingRef.current)return;if(window.triggerMidiVuActivity){// VU key theo CONTEXT (tách MAIN vs SECTION — 07:20) -window.triggerMidiVuActivity((activeTab&&activeTab.startsWith('session_')?'_sess_':'')+subTrack.id,note.velocity||0.8);window.triggerMidiVuActivity((activeTab&&activeTab.startsWith('session_')?'_sess_':'')+track.id,note.velocity||0.8);}},delay*1000);}else{const remainingDurMs=(notePlayEndMain-offsetTime)*1000;if(window.SonicSF){scheduleMidiNoteDispatch(subTrack,note.pitch||60,note.velocity||0.8,remainingDurMs,context.currentTime,program,subNode,subCh,subTrack.synth_engine);}if(window.triggerMidiVuActivity){// VU key theo CONTEXT (tách MAIN vs SECTION — 07:20) +window.triggerMidiVuActivity((activeTab&&activeTab.startsWith('session_')?'_sess_':'')+subTrack.id,note.velocity||0.8);window.triggerMidiVuActivity((activeTab&&activeTab.startsWith('session_')?'_sess_':'')+track.id,note.velocity||0.8);}},delay*1000);}else{const remainingDurMs=(notePlayEndMain-offsetTime)*1000;if(window.SonicSF){scheduleMidiNoteDispatch(subTrack,note.pitch||60,note.velocity||0.8,remainingDurMs,context.currentTime,program,subNode,subCh,subTrack.synth_engine);scheduleNoteExpressionCC11(subTrack,note,subCh,context.currentTime,0,remainingDurMs/1000);}if(window.triggerMidiVuActivity){// VU key theo CONTEXT (tách MAIN vs SECTION — 07:20) window.triggerMidiVuActivity((activeTab&&activeTab.startsWith('session_')?'_sess_':'')+subTrack.id,note.velocity||0.8);window.triggerMidiVuActivity((activeTab&&activeTab.startsWith('session_')?'_sess_':'')+track.id,note.velocity||0.8);}}}});});}});});}});updateSfRouting();};// Solo playback for Local Selection Loop (LOOP_MAKER.md §2.2) const startLocalTrackPlayback=(trackId,offsetTime)=>{const context=getAudioContext();// V12: parity với startTrackPlayback — loop restart (solo/local) sau // stopAllPlayback(): hủy STOP#2 + báo PLAY (nếu không C++ transportStopped @@ -1991,7 +1991,7 @@ const gainNode=getOrCreateTrackNode(track,context);const pannerNode=activeTrackN // chain for audio clips (double path: with + without FX). const clipStart=clip.startTime||0;const clipDuration=clip.buffer.duration/(clip.speed||1.0);const clipEnd=clipStart+clipDuration;if(offsetTime0)&&(window.SonicSF||nativeSf||nativeVst)){if(nativeSf||nativeVst){scheduleNativeTrack(track,offsetTime,context,gainNode,bpm,{playheadNow:()=>startOffsetTimeRef.current+(getAudioContext().currentTime-startAudioTimeRef.current),sources:activeSourcesRef.current,isActive:()=>!!isPlayingRef.current});return;// native render → audio buffer (không qua WASM per-note) -}var lcCh=assignTrackMidiChannel(track,tracks);const bpmVal=parseInt(bpm)||120;const secondsPerBeat=60.0/bpmVal;midiItems.forEach(item=>{const notes=item.notes||[];notes.forEach(note=>{const noteStartSec=item.startTime+(note.start_beat||0)*secondsPerBeat;const noteDurSec=(note.duration_beats||1)*secondsPerBeat;const noteEndSec=noteStartSec+noteDurSec;if(offsetTime{if(st.type!=='PIANO_ROLL')return;const context=getAudioContext();_nativePlayEpoch++;// new play — vô hiệu doHardStop đang fade +}var lcCh=assignTrackMidiChannel(track,tracks);const bpmVal=parseInt(bpm)||120;const secondsPerBeat=60.0/bpmVal;midiItems.forEach(item=>{const notes=item.notes||[];notes.forEach(note=>{const noteStartSec=item.startTime+(note.start_beat||0)*secondsPerBeat;const noteDurSec=(note.duration_beats||1)*secondsPerBeat;const noteEndSec=noteStartSec+noteDurSec;if(offsetTime{if(st.type!=='PIANO_ROLL')return;const context=getAudioContext();_nativePlayEpoch++;// new play — vô hiệu doHardStop đang fade restorePendingFadeSession();// V17: huy fade dang treo — replay khong bi dip gain // V10: piano roll loop restart cũng gọi stopAllPlayback() trước (STOP#1 → // bridge transportStopped=true) → mọi note-on bị drop nếu không PLAY lại. diff --git a/app/templates/index.html b/app/templates/index.html index b24cabe..f3d33b2 100644 --- a/app/templates/index.html +++ b/app/templates/index.html @@ -51,7 +51,7 @@ - +