v20: fix CC11 piano roll (epoch check _cc11Epoch - v19 con so _nativePlayEpoch) + SF program change khi play piano roll (bridge)

This commit is contained in:
2026-08-26 18:18:20 +07:00
parent 1f78a834a8
commit 3e6080603c
3 changed files with 13 additions and 4 deletions
+6 -1
View File
@@ -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
+6 -2
View File
@@ -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<pts.length;i++){if(ratio<=pts[i].position_ratio){const a=pts[i-1],b=pts[i];const t=(ratio-a.position_ratio)/(b.position_ratio-a.position_ratio||1);return Math.round(a.value+(b.value-a.value)*t);}}return pts[pts.length-1].value;};// ponytail: duplicate interp cc11ValueAt (component) — consolidate khi curve
// thành field track-level.
const EXPRESSION_CC=11;// CC11 expression — TEST 7 da xong, revert 11
const scheduleNoteExpressionCC11=(track,note,ch,startWallTime,startSec,durSec)=>{try{const curve=Array.isArray(note&&note.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&&note.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.
+1 -1
View File
@@ -51,7 +51,7 @@
<script src="/static/js/services/promptTemplateManager.js?v=202607281039"></script>
<script src="/static/js/services/undoRedoEngine.js?v=202607290941"></script>
<script src="/static/js/services/chordTheory.js?v=202608211300"></script>
<script src="/static/js/app.precompiled.js?v=202608310900" defer></script>
<script src="/static/js/app.precompiled.js?v=202608311000" defer></script>
<link rel="stylesheet" href="/static/css/styles.css?v=202607271016">
<style>
:root {