|
|
|
@@ -1,6 +1,9 @@
|
|
|
|
|
const{useState,useRef,useEffect,useMemo,useCallback}=React;// ── FastAPI Backend Configuration ──
|
|
|
|
|
const API_BASE_URL=(window.API_BASE_URL||window.location.origin).replace(/\/+$/,'');const API_AUDIO=`${API_BASE_URL}/api/v1/audio`;const API_MULTITRACK=`${API_BASE_URL}/api/v1/multitrack`;const API_TASKS=`${API_BASE_URL}/api/v1/audio/tasks`;// ── DEBUG diag (tạm thời) ──
|
|
|
|
|
window.__sfDiag=function(){var arr=[];try{var s=localStorage.getItem('__sf_diag');if(s)arr=JSON.parse(s)||[];}catch(e){}return function(msg){try{arr.push(new Date().toISOString().slice(11,23)+' '+msg);if(arr.length>2000)arr=arr.slice(-2000);localStorage.setItem('__sf_diag',JSON.stringify(arr));}catch(e){}};}();window.__sfDiagDump=function(){try{var s=localStorage.getItem('__sf_diag');var arr=s?JSON.parse(s)||[]:[];var idx=-1;for(var i=arr.length-1;i>=0;i--){if(arr[i].indexOf('BOUNCE_START')!==-1){idx=i;break;}}return(idx>=0?arr.slice(idx):arr.slice(-50)).join('\n');}catch(e){return String(e);}};window.__sfDiagShow=function(){try{if(window.__sfDiagDump){var ov=document.createElement('div');ov.style.cssText='position:fixed;inset:0;z-index:99999;background:rgba(0,0,0,.65);display:flex;align-items:center;justify-content:center;font-family:monospace';var box=document.createElement('div');box.style.cssText='background:#fff;color:#111;border-radius:8px;padding:12px;width:min(92vw,900px);max-height:85vh;display:flex;flex-direction:column;box-shadow:0 4px 24px rgba(0,0,0,.5)';var ta=document.createElement('textarea');ta.readOnly=true;ta.value=window.__sfDiagDump();ta.style.cssText='flex:1;min-height:55vh;font-family:monospace;font-size:11px;white-space:pre;resize:none';var bar=document.createElement('div');bar.style.cssText='display:flex;gap:8px;justify-content:flex-end;margin-top:8px';var bCopy=document.createElement('button');bCopy.textContent='Copy';bCopy.onclick=function(){try{ta.focus();ta.select();document.execCommand('copy');bCopy.textContent='Đã copy!';}catch(e){bCopy.textContent='Copy thủ công (Ctrl+C)';}};var bClose=document.createElement('button');bClose.textContent='Đóng';bClose.onclick=function(){document.body.removeChild(ov);};bar.appendChild(bCopy);bar.appendChild(bClose);box.appendChild(ta);box.appendChild(bar);ov.appendChild(box);document.body.appendChild(ov);try{ta.focus();ta.select();}catch(e){}return;}}catch(e){window.alert('DIAG:\n'+(window.__sfDiagDump?window.__sfDiagDump():''));}};// ── Dedicated per-track MIDI channel allocation ──
|
|
|
|
|
window.__sfDiag=function(){var arr=[];try{var s=localStorage.getItem('__sf_diag');if(s)arr=JSON.parse(s)||[];}catch(e){}return function(msg){try{arr.push(new Date().toISOString().slice(11,23)+' '+msg);if(arr.length>2000)arr=arr.slice(-2000);localStorage.setItem('__sf_diag',JSON.stringify(arr));}catch(e){}};}();window.__sfDiagDump=function(){try{var s=localStorage.getItem('__sf_diag');var arr=s?JSON.parse(s)||[]:[];var idx=-1;for(var i=arr.length-1;i>=0;i--){if(arr[i].indexOf('BOUNCE_START')!==-1){idx=i;break;}}return(idx>=0?arr.slice(idx):arr.slice(-50)).join('\n');}catch(e){return String(e);}};window.__sfDiagShow=function(){try{if(window.__sfDiagDump){var ov=document.createElement('div');ov.style.cssText='position:fixed;inset:0;z-index:99999;background:rgba(0,0,0,.65);display:flex;align-items:center;justify-content:center;font-family:monospace';var box=document.createElement('div');box.style.cssText='background:#fff;color:#111;border-radius:8px;padding:12px;width:min(92vw,900px);max-height:85vh;display:flex;flex-direction:column;box-shadow:0 4px 24px rgba(0,0,0,.5)';var ta=document.createElement('textarea');ta.readOnly=true;ta.value=window.__sfDiagDump();ta.style.cssText='flex:1;min-height:55vh;font-family:monospace;font-size:11px;white-space:pre;resize:none';var bar=document.createElement('div');bar.style.cssText='display:flex;gap:8px;justify-content:flex-end;margin-top:8px';var bCopy=document.createElement('button');bCopy.textContent='Copy';bCopy.onclick=function(){try{ta.focus();ta.select();document.execCommand('copy');bCopy.textContent='Đã copy!';}catch(e){bCopy.textContent='Copy thủ công (Ctrl+C)';}};var bClose=document.createElement('button');bClose.textContent='Đóng';bClose.onclick=function(){document.body.removeChild(ov);};bar.appendChild(bCopy);bar.appendChild(bClose);box.appendChild(ta);box.appendChild(bar);ov.appendChild(box);document.body.appendChild(ov);try{ta.focus();ta.select();}catch(e){}return;}}catch(e){window.alert('DIAG:\n'+(window.__sfDiagDump?window.__sfDiagDump():''));}};// HB: main-thread heartbeat — khoảng cách giữa các HB chứng minh main thread
|
|
|
|
|
// bị block (timer không fire trong lúc block). Chạy vĩnh viễn; dump chỉ lấy
|
|
|
|
|
// từ BOUNCE_START.
|
|
|
|
|
window.__sfHBCount=0;setInterval(function(){window.__sfHBCount++;if(window.__sfDiag)window.__sfDiag('HB:'+window.__sfHBCount);},250);// ── Dedicated per-track MIDI channel allocation ──
|
|
|
|
|
// FluidSynth has 16 channels; if two tracks share a channel, arming one track
|
|
|
|
|
// re-selects the other track's program and its instrument changes. Every track
|
|
|
|
|
// gets its own stable, unique channel (0-15, skipping 9 which is the classic
|
|
|
|
@@ -1882,7 +1885,7 @@ const guardPlay=startTime!=null?function(){return isPlayingRef.current||subTabsR
|
|
|
|
|
const isPerc=!!(synthEngine&&synthEngine.soundfont_bank===128);// D8: track đổi instrument → gửi CC0/CC32 (bank THẬT từ synthEngine) +
|
|
|
|
|
// program change qua bridge (A12) — helper dedupe theo track+bank+program.
|
|
|
|
|
if(program!==undefined&&program!==null){try{if(window.__ensureBridgeProgram)window.__ensureBridgeProgram(trkId,program,synthEngine);}catch(e){}}// V11: epoch guard — hủy timer cũ khi stop/loop restart (xem ref).
|
|
|
|
|
const epoch=bridgeNoteEpochRef.current;setTimeout(function(){if(epoch!==bridgeNoteEpochRef.current)return;if(!guardPlay())return;try{window.SonicMidiRouter.pushEvent({cmd:'NOTE_ON',channel:trkId,pitch:pitch||60,velocity:velocity||0.8,percussion:isPerc});}catch(e){}},delayMs);scheduleNoteOff('B:'+trkId+':'+(pitch||60),function(){if(epoch!==bridgeNoteEpochRef.current)return;if(!guardPlay())return;try{window.SonicMidiRouter.pushEvent({cmd:'NOTE_OFF',channel:trkId,pitch:pitch||60,velocity:0,percussion:isPerc});}catch(e){}},delayMs,durMs||1000);return;}// Native VSTi (standalone browser, bridge inactive) → midi-render thay vì
|
|
|
|
|
const epoch=bridgeNoteEpochRef.current;setTimeout(function(){if(epoch!==bridgeNoteEpochRef.current)return;if(!guardPlay())return;if(window.__sfDiag)window.__sfDiag('DISPATCH_ON track='+trkId+' p='+(pitch||60));try{window.SonicMidiRouter.pushEvent({cmd:'NOTE_ON',channel:trkId,pitch:pitch||60,velocity:velocity||0.8,percussion:isPerc});}catch(e){}},delayMs);scheduleNoteOff('B:'+trkId+':'+(pitch||60),function(){if(epoch!==bridgeNoteEpochRef.current)return;if(!guardPlay())return;try{window.SonicMidiRouter.pushEvent({cmd:'NOTE_OFF',channel:trkId,pitch:pitch||60,velocity:0,percussion:isPerc});}catch(e){}},delayMs,durMs||1000);return;}// Native VSTi (standalone browser, bridge inactive) → midi-render thay vì
|
|
|
|
|
// FluidSynth GM sai âm — safety net cho mọi caller (ghost notes, per-note).
|
|
|
|
|
if(isStandaloneSf()&&isVstTrackEngine(synthEngine)&&!(window.SonicMidiRouter&&window.SonicMidiRouter.isBridgeActive())){playNativeVstNote(track,pitch,velocity,durMs,startTime,track?track.id:'global');return;}window.SonicSF.playNote(pitch,velocity,durMs,startTime,program,destNode,ch,synthEngine);};const startTrackPlayback=offsetTime=>{const context=getAudioContext();if(window.__sfDiag)window.__sfDiag('startPlay offset='+offsetTime+' isBridgeConn='+!!(window.NativeBridgeService&&window.NativeBridgeService.isBridgeConnected)+' routerActive='+!!(window.SonicMidiRouter&&window.SonicMidiRouter.isBridgeActive())+' sonicSF='+!!window.SonicSF+' standalone='+isStandaloneSf());_nativePlayEpoch++;// new play — vô hiệu doHardStop đang fade (play trong cửa sổ 80ms)
|
|
|
|
|
restorePendingFadeSession();// V17: huy fade dang treo — replay khong bi dip gain
|
|
|
|
|