From 3e6080603ce57731cd501c7d6568f0406b6c5db5 Mon Sep 17 00:00:00 2001 From: locphamtran Date: Wed, 26 Aug 2026 18:18:20 +0700 Subject: [PATCH] v20: fix CC11 piano roll (epoch check _cc11Epoch - v19 con so _nativePlayEpoch) + SF program change khi play piano roll (bridge) --- app/static/js/app.jsx | 7 ++++++- app/static/js/app.precompiled.js | 8 ++++++-- app/templates/index.html | 2 +- 3 files changed, 13 insertions(+), 4 deletions(-) diff --git a/app/static/js/app.jsx b/app/static/js/app.jsx index 56e43b2..8066ed3 100644 --- a/app/static/js/app.jsx +++ b/app/static/js/app.jsx @@ -768,7 +768,7 @@ const scheduleNoteExpressionCC11 = (track, note, ch, startWallTime, startSec, du const delayMs = Math.max(0, wallMs - ctx.currentTime * 1000); const val = _cc11ValueAt(curve, ratio); const timer = setTimeout(() => { - if (epoch !== _nativePlayEpoch) return; + if (epoch !== _cc11Epoch) return; if (window.SonicMidiRouter && window.SonicMidiRouter.isBridgeActive && window.SonicMidiRouter.isBridgeActive()) { window.SonicMidiRouter.pushEvent({ cmd: 'CC', channel: track ? track.id : 0, pitch: EXPRESSION_CC, velocity: 0, data2: val, percussion: isPerc }); return; @@ -25510,6 +25510,11 @@ const App = () => { const mainCh = instCtx.ch; var allTracks = activeTracksRef.current || []; ensureSonicInstrument(instCtx); + // Bridge active: gui program change (CC0/CC32/PROGRAM) de SF preset dung + // (instCtx.program undefined cho SF -> __ensureBridgeProgram fallback + // synthEngine.soundfont_program da set khi load qua modal). Thieu -> + // bridge choi preset mac dinh, khong dung preset da chon. + try { if (window.__ensureBridgeProgram) window.__ensureBridgeProgram(st.trackId, instCtx.program, instCtx.synthEngine); } catch (e) {} console.log('[Play] PianoRoll schedule:', st.id, '| notes=', midiNotes.length, '| track=', st.trackId, '| program=', instrumentProgram, '| sf=', instCtx.sfId ? instCtx.sfId : '-', '| dest=', destNode ? 'ok' : 'null', '| ch=', mainCh, '| sfEngine=', synthEngine ? 'yes' : 'no', '| bypassA=', !!trackAudioBypassMap[st.trackId], '| bypassMidi=', !!trackMidiBypassMap[st.trackId]); // 0 = item start. Notes must be scheduled relative to the item too, otherwise // items placed later in the project play `item.startTime` seconds in the diff --git a/app/static/js/app.precompiled.js b/app/static/js/app.precompiled.js index dfe3516..bf366e3 100644 --- a/app/static/js/app.precompiled.js +++ b/app/static/js/app.precompiled.js @@ -148,7 +148,7 @@ function sendMidiVolumeCC7(t,list,audible){const volDb=typeof t.volumeDb==='numb let _cc11Timers=new Set();const clearAllCC11Timers=()=>{_cc11Timers.forEach(t=>{try{clearTimeout(t);}catch(e){}});_cc11Timers.clear();};const _cc11ValueAt=(curve,ratio)=>{if(!curve||!curve.length)return 127;if(curve.length===1)return curve[0].value;const pts=curve.slice().sort((a,b)=>a.position_ratio-b.position_ratio);if(ratio<=pts[0].position_ratio)return pts[0].value;if(ratio>=pts[pts.length-1].position_ratio)return pts[pts.length-1].value;for(let i=1;i{try{const curve=Array.isArray(note&¬e.expression_curve)?note.expression_curve:[];if(!curve||!curve.length)return;const epoch=_cc11Epoch;const isPerc=!!(track&&track.synth_engine&&track.synth_engine.soundfont_bank===128)||!!(track&&track.is_percussion);const ctx=getAudioContext();const GRID_MS=16;const n=Math.max(1,Math.ceil(durSec*1000/GRID_MS));const stepMs=durSec*1000/n;for(let i=0;i<=n;i++){const ratio=Math.min(1,i/n);const wallMs=(startWallTime+startSec)*1000+i*stepMs;const delayMs=Math.max(0,wallMs-ctx.currentTime*1000);const val=_cc11ValueAt(curve,ratio);const timer=setTimeout(()=>{if(epoch!==_nativePlayEpoch)return;if(window.SonicMidiRouter&&window.SonicMidiRouter.isBridgeActive&&window.SonicMidiRouter.isBridgeActive()){window.SonicMidiRouter.pushEvent({cmd:'CC',channel:track?track.id:0,pitch:EXPRESSION_CC,velocity:0,data2:val,percussion:isPerc});return;}if(window.SonicSF&&typeof window.SonicSF.controllerChange==='function'){window.SonicSF.controllerChange(ch,EXPRESSION_CC,val);}},delayMs);_cc11Timers.add(timer);}}catch(e){console.warn('[CC11] schedule error:',e);}};// MAIN SESSION end-time (seconds): endtime of the items ON the session's own +const scheduleNoteExpressionCC11=(track,note,ch,startWallTime,startSec,durSec)=>{try{const curve=Array.isArray(note&¬e.expression_curve)?note.expression_curve:[];if(!curve||!curve.length)return;const epoch=_cc11Epoch;const isPerc=!!(track&&track.synth_engine&&track.synth_engine.soundfont_bank===128)||!!(track&&track.is_percussion);const ctx=getAudioContext();const GRID_MS=16;const n=Math.max(1,Math.ceil(durSec*1000/GRID_MS));const stepMs=durSec*1000/n;for(let i=0;i<=n;i++){const ratio=Math.min(1,i/n);const wallMs=(startWallTime+startSec)*1000+i*stepMs;const delayMs=Math.max(0,wallMs-ctx.currentTime*1000);const val=_cc11ValueAt(curve,ratio);const timer=setTimeout(()=>{if(epoch!==_cc11Epoch)return;if(window.SonicMidiRouter&&window.SonicMidiRouter.isBridgeActive&&window.SonicMidiRouter.isBridgeActive()){window.SonicMidiRouter.pushEvent({cmd:'CC',channel:track?track.id:0,pitch:EXPRESSION_CC,velocity:0,data2:val,percussion:isPerc});return;}if(window.SonicSF&&typeof window.SonicSF.controllerChange==='function'){window.SonicSF.controllerChange(ch,EXPRESSION_CC,val);}},delayMs);_cc11Timers.add(timer);}}catch(e){console.warn('[CC11] schedule error:',e);}};// MAIN SESSION end-time (seconds): endtime of the items ON the session's own // tracks only — audio clips, MIDI items, and section-item bounds. Section-TAB // content is deliberately IGNORED here: when played inside the main session the // sub-track items are clamped to their section bounds, so a long SECTION-TAB @@ -2013,7 +2013,11 @@ isPlayingRef.current=true;try{if(window.__ensureMasteringRouting)window.__ensure // phải chơi ĐÚNG instrument đó. ensureSonicInstrument select channel đúng // trước khi notes bắn (fire-and-forget — playNote tự load+retry nếu SF // chưa xong). -const instCtx=resolveTrackInstrumentCtx(track,activeTracksRef.current||[]);const instrumentProgram=instCtx.program;const synthEngine=instCtx.synthEngine;const mainCh=instCtx.ch;var allTracks=activeTracksRef.current||[];ensureSonicInstrument(instCtx);console.log('[Play] PianoRoll schedule:',st.id,'| notes=',midiNotes.length,'| track=',st.trackId,'| program=',instrumentProgram,'| sf=',instCtx.sfId?instCtx.sfId:'-','| dest=',destNode?'ok':'null','| ch=',mainCh,'| sfEngine=',synthEngine?'yes':'no','| bypassA=',!!trackAudioBypassMap[st.trackId],'| bypassMidi=',!!trackMidiBypassMap[st.trackId]);// 0 = item start. Notes must be scheduled relative to the item too, otherwise +const instCtx=resolveTrackInstrumentCtx(track,activeTracksRef.current||[]);const instrumentProgram=instCtx.program;const synthEngine=instCtx.synthEngine;const mainCh=instCtx.ch;var allTracks=activeTracksRef.current||[];ensureSonicInstrument(instCtx);// Bridge active: gui program change (CC0/CC32/PROGRAM) de SF preset dung +// (instCtx.program undefined cho SF -> __ensureBridgeProgram fallback +// synthEngine.soundfont_program da set khi load qua modal). Thieu -> +// bridge choi preset mac dinh, khong dung preset da chon. +try{if(window.__ensureBridgeProgram)window.__ensureBridgeProgram(st.trackId,instCtx.program,instCtx.synthEngine);}catch(e){}console.log('[Play] PianoRoll schedule:',st.id,'| notes=',midiNotes.length,'| track=',st.trackId,'| program=',instrumentProgram,'| sf=',instCtx.sfId?instCtx.sfId:'-','| dest=',destNode?'ok':'null','| ch=',mainCh,'| sfEngine=',synthEngine?'yes':'no','| bypassA=',!!trackAudioBypassMap[st.trackId],'| bypassMidi=',!!trackMidiBypassMap[st.trackId]);// 0 = item start. Notes must be scheduled relative to the item too, otherwise // items placed later in the project play `item.startTime` seconds in the // future (silence when pressing play). Ghost notes are already relative to // the item window, so no absolute-session offset is applied anywhere here. diff --git a/app/templates/index.html b/app/templates/index.html index 6aec56b..a02e51d 100644 --- a/app/templates/index.html +++ b/app/templates/index.html @@ -51,7 +51,7 @@ - +