From dc962c87cd79d93f07853b66a9950ca25b1cc39b Mon Sep 17 00:00:00 2001 From: locphamtran Date: Mon, 7 Sep 2026 10:38:45 +0700 Subject: [PATCH] =?UTF-8?q?M22:=20fix=20Bug=20C=20playhead=20nhay=20khi=20?= =?UTF-8?q?PWR=20toggle=20=E2=80=94=20rebase=20timeline=20(off/at=20+=20an?= =?UTF-8?q?chor=3Dps)=20khi=20wake=20warm-session=20giua=20play;=20diag=20?= =?UTF-8?q?Bug=20B=20=E2=80=94=20mdiag=20dB=20khi=20F7=20toggle=20Mixer=20?= =?UTF-8?q?Panel?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/static/js/app.jsx | 27 +++++++++++++++++++++++++++ app/static/js/app.precompiled.js | 21 +++++++++++++++------ app/templates/index.html | 2 +- 3 files changed, 43 insertions(+), 7 deletions(-) diff --git a/app/static/js/app.jsx b/app/static/js/app.jsx index 8b04168..d6f3019 100644 --- a/app/static/js/app.jsx +++ b/app/static/js/app.jsx @@ -19630,6 +19630,10 @@ const App = () => { setShowMediaExplorerRef.current = setShowMediaExplorer; // F6/F7 mutual exclusion: enabling one panel disables the other window.__toggleMixerRef = function() { + // M22 Bug B diag: F7 mở/đóng MIXER PANEL (user: mở panel → âm lượng giảm + // nhỏ; M20 chỉ instrument MasteringModal panel_open — F7 đi đường khác nên + // chưa có dB). Chụp dB master OUT + hist + post như panel_open. + try { _mdiagSample('mixer_toggle'); _mdiagDump(); _mdiagSchedulePost(); } catch (e) {} setShowMixer(function(p) { const next = !p; if (next) setShowMediaExplorerRef.current(false); @@ -20828,6 +20832,8 @@ const App = () => { useEffect(() => { window.currentMasteringSettings = masteringSettings; + const _fxPrevAct = !!(fxRt && fxRt.active); + const _fxPrevWarm = !!(fxRt && fxRt.warm); if (audioCtx && masterBus) { toggleMasteringOnMaster(masteringSettings.masterConnected, masteringSettings.isBypassed); applyMasteringSettings(masteringSettings); @@ -20914,6 +20920,7 @@ const App = () => { updateSfRouting(); } catch (e) {} } + try { if (!_fxPrevAct && _fxPrevWarm && fxRt.active && window.__fxRtWakeRebase) window.__fxRtWakeRebase(); } catch (e) {} try { if (_phArmed() && window.__phSnapAll) { window.__phSnapAll('ph_effect'); _dbgLog('ph_effect', 'mc=' + (masteringSettings.masterConnected ? 1 : 0) + ' bp=' + (masteringSettings.isBypassed ? 1 : 0) + ' chain=' + fxRtChain().length + ' gui=' + (masteringSettings.gui_open_path || 'na')); } } catch (e) {} }, [masteringSettings]); @@ -21875,6 +21882,26 @@ const App = () => { const mainResumeRef = useRef(null); currentTimeRef.current = currentTime; + // M22 Bug C fix hook: rebase timeline khi wake warm-session (PWR ON giữa lúc + // play) — engine processedSamples bị đóng băng suốt OFF-warm (worklet rời + // graph) nên ps−anchor cũ tụt cả khoảng OFF; nếu không rebase playhead + // engine-derived nhảy LÙI về vị trí trước OFF (ph_jump d âm lớn). Rebase: + // dời startOffsetTimeRef/startAudioTimeRef về wall hiện tại + anchor=ps → + // engine clock nối lại đúng chỗ, playhead liên tục (lệch ~latencyTotal). + window.__fxRtWakeRebase = function () { + try { + if (!fxRt || !fxRt.active) return; + const _c = getAudioContext(); + const _wall = (startOffsetTimeRef.current || 0) + (_c.currentTime - (startAudioTimeRef.current || _c.currentTime)); + const _pa = (typeof fxRt.processedSamples === 'number') ? fxRt.processedSamples : null; + startOffsetTimeRef.current = _wall; + startAudioTimeRef.current = _c.currentTime; + if (_pa !== null) fxRt.processedAnchor = _pa; + if (_fxRtSeekFreeze) { _fxRtSeekFreeze = false; if (_fxRtSeekFreezeTimer) { clearTimeout(_fxRtSeekFreezeTimer); _fxRtSeekFreezeTimer = null; } } + try { _dbgLog('ph_rebase', 'wall=' + _wall.toFixed(3) + ' at=' + startAudioTimeRef.current.toFixed(3) + ' off=' + startOffsetTimeRef.current.toFixed(3) + ' ps=' + (typeof fxRt.processedSamples === 'number' ? fxRt.processedSamples : 'na') + ' pa=' + (typeof fxRt.processedAnchor === 'number' ? fxRt.processedAnchor : 'na') + ' lat=' + (fxRt.latencyTotal || 0)); } catch (e) {} + } catch (e) { try { _dbgLog('ph_rebase', 'err ' + String(e)); } catch (e2) {} } + }; + // M21 ph-diag hooks (Bug C — playhead nhay khi PWR master FX toggle) // __phArmPwr: user bam PWR OFF/ON -> arm cua so log 6s + snapshot ngay lap tuc. // __phSnapAll: snapshot refs/fxRt tai diem goi (chi log khi cua so dang mo). diff --git a/app/static/js/app.precompiled.js b/app/static/js/app.precompiled.js index 1ecc7ca..61ae1fc 100644 --- a/app/static/js/app.precompiled.js +++ b/app/static/js/app.precompiled.js @@ -417,7 +417,7 @@ if(fxRt.warm)fxRtStop(true);// Token chống race bypass: user bấm PWR/Bypass // node → bypass vô hiệu. Mỗi fxRtStop() bump startToken → mọi checkpoint // dưới đây thấy mismatch → hủy session vừa tạo, không nối node. const token=++fxRt.startToken;fxRt.starting=true;const ctx=getAudioContext();if(!ctx||!ctx.audioWorklet||!masterBus||!masterBus.output||!masterBus.analyser||!masterBus.masterFxGain||!masterBus.masterFxDryGain)return;const chainPayload=chain.map(s=>s.type==='vst3'?{type:'vst3',path:s.path,name:s.name||s.path,preset_b64:s.preset_b64||'',bypass:!!s.bypass||!!(window.currentMasteringSettings||{}).vstBypass}:{type:'builtin',id:s.type,params:s.params&&Object.keys(s.params).length?s.params:{},bypass:!!s.bypass});let st;try{st=await window.SonicAPI.fxRealtimeStart({fx_chain:chainPayload,sample_rate:ctx.sampleRate||44100});}catch(e){console.warn('[FX RT] start fail:',e);showToast('Không khởi động VST FX realtime: '+(e.message||e),'warning');fxRt.starting=false;return;}if(token!==fxRt.startToken){// Bypass/stop giữa lúc engine đang load chain — hủy session vừa tạo. -console.warn('[FX RT] start cancelled mid-flight — session stopped');try{window.SonicAPI.fxRealtimeStop({session_id:st.session_id}).catch(()=>{});}catch(e2){}fxRt.starting=false;return;}fxRt.sessionId=st.session_id;fxRtGuiPushShm();try{await ctx.audioWorklet.addModule(`${API_BASE_URL}/static/js/sf-fx-realtime.js?v=202609071004`);if(token!==fxRt.startToken){fxRt.starting=false;return;}fxRt.node=new AudioWorkletNode(ctx,'sf-fx-realtime',{numberOfInputs:1,numberOfOutputs:1,outputChannelCount:[2]});const u=new URL(API_BASE_URL);const wsProto=u.protocol==='https:'?'wss://':'ws://';// WS to 'localhost' bi tre ~300ms: Chromium thu ::1 truoc (Windows khong +console.warn('[FX RT] start cancelled mid-flight — session stopped');try{window.SonicAPI.fxRealtimeStop({session_id:st.session_id}).catch(()=>{});}catch(e2){}fxRt.starting=false;return;}fxRt.sessionId=st.session_id;fxRtGuiPushShm();try{await ctx.audioWorklet.addModule(`${API_BASE_URL}/static/js/sf-fx-realtime.js?v=202609071038`);if(token!==fxRt.startToken){fxRt.starting=false;return;}fxRt.node=new AudioWorkletNode(ctx,'sf-fx-realtime',{numberOfInputs:1,numberOfOutputs:1,outputChannelCount:[2]});const u=new URL(API_BASE_URL);const wsProto=u.protocol==='https:'?'wss://':'ws://';// WS to 'localhost' bi tre ~300ms: Chromium thu ::1 truoc (Windows khong // co listener IPv6 -> SYN hang ~2s) roi moi fallback 127.0.0.1. Dung // thang 127.0.0.1 (loopback) -> WS open ~5ms. Server chap nhan loopback. const wsHost=u.hostname==='localhost'||u.hostname==='::1'?'127.0.0.1'+(u.port?':'+u.port:''):u.host;const tok=localStorage.getItem('sonic_token')||'';const wsUrl=`${wsProto}${wsHost}${st.ws_url}${tok?'?token='+encodeURIComponent(tok):''}`;const ws=new WebSocket(wsUrl);fxRt.ws=ws;ws.binaryType='arraybuffer';ws.onopen=()=>{if(token!==fxRt.startToken){try{ws.close();}catch(e){}fxRt.starting=false;return;}fxRt.active=true;fxRt.flushing=false;fxRt.starting=false;fxRt.inQueue=[];// bỏ audio stale sản xuất trước khi WS open (~4s load VST) — hết lag khởi đầu @@ -1505,7 +1505,10 @@ React.useEffect(()=>{const VKB_MAP={z:0,s:1,x:2,d:3,c:4,v:5,g:6,b:7,h:8,n:9,j:10 // element khác, không chặn listener cùng node cùng phase (bug user: // VKB mở, phím V vẫn bật prompt Gain). e.stopImmediatePropagation();const vel=Math.max(1,Math.min(127,parseInt(s.velocity)||100))/127;if(vkbNoteOnRef.current)vkbNoteOnRef.current(pitch,vel);}else{e.preventDefault();e.stopImmediatePropagation();if(vkbNoteOffRef.current)vkbNoteOffRef.current(pitch);}};window.addEventListener('keydown',handler,{capture:true});window.addEventListener('keyup',handler,{capture:true});return()=>{window.removeEventListener('keydown',handler,{capture:true});window.removeEventListener('keyup',handler,{capture:true});};},[]);const[showAIPanel,setShowAIPanel]=useState(true);const[showSelectionPanel,setShowSelectionPanel]=useState(false);const[showPythonToolsPanel,setShowPythonToolsPanel]=useState(false);const[showMediaExplorer,setShowMediaExplorer]=useState(false);const[scrollBufferExtra,setScrollBufferExtra]=useState(0);const scrollBufferExtraRef=useRef(0);scrollBufferExtraRef.current=scrollBufferExtra;const[showFxRack,setShowFxRack]=useState(false);const[showMidiEvents,setShowMidiEvents]=useState(false);const[showMixer,setShowMixer]=useState(false);const setShowMixerRef=useRef(setShowMixer);setShowMixerRef.current=setShowMixer;const setShowMediaExplorerRef=useRef(setShowMediaExplorer);setShowMediaExplorerRef.current=setShowMediaExplorer;// F6/F7 mutual exclusion: enabling one panel disables the other -window.__toggleMixerRef=function(){setShowMixer(function(p){const next=!p;if(next)setShowMediaExplorerRef.current(false);return next;});};// Live per-track bypass (split into two buttons per user request): +window.__toggleMixerRef=function(){// M22 Bug B diag: F7 mở/đóng MIXER PANEL (user: mở panel → âm lượng giảm +// nhỏ; M20 chỉ instrument MasteringModal panel_open — F7 đi đường khác nên +// chưa có dB). Chụp dB master OUT + hist + post như panel_open. +try{_mdiagSample('mixer_toggle');_mdiagDump();_mdiagSchedulePost();}catch(e){}setShowMixer(function(p){const next=!p;if(next)setShowMediaExplorerRef.current(false);return next;});};// Live per-track bypass (split into two buttons per user request): // - 'audio': bypass FX+mastering for AUDIO items (audio clips + section items) // - 'midi' : bypass FX+mastering for MIDI items (soundfont instrument) window.__setTrackBypass=function(trackId,which,on){if(which==='audio'){trackAudioBypassMap[trackId]=!!on;trackMasteringBypassMap[trackId]=!!on;// Live re-route the main track node AND every section sub-node of this @@ -1679,7 +1682,7 @@ const handlePrefsChange=next=>{setPrefs(next);try{localStorage.setItem('sf_prefs const[fxRackTarget,setFxRackTarget]=useState(null);window.__openFxRack=(trackId,trackName)=>setFxRackTarget({trackId,trackName});// Render offline toàn project qua native_bridge (VST FX masterbus + track) → WAV. // Dùng cho nút "Nghe thử" trong FX Rack / Mastering — preview = âm export. window.__renderProjectPreview=async()=>{const schema=serializeProjectToSchema(currentProjectId||'temp_project',projectName||'Dự án tạm chưa lưu',bpm,tracks,subTabs,sessionTabs,masteringSettings,masterVolume,{volL:masterVolL,volR:masterVolR,link:masterLink,muted:masterMuted});try{const res=await window.SonicAPI.renderProject(schema,'preview_'+Date.now()+'.wav');_lastMasterStats={lufs:res.lufs,true_peak_db:res.true_peak_db};return res;}catch(e){window.showToast&&window.showToast('Render preview lỗi: '+(e.message||e),'error');throw e;}};const[masteringSettings,setMasteringSettings]=useState({masterConnected:true,// ⚠️ FIX: bật ON mặc định — MIDI item/preview PHẢI được xử lí qua mastering FX ngay khi chạy app (không cần bật/tắt power) -activeModule:null,imagerActive:false,maximizerActive:false,eqLowGain:1.5,eqMid1Gain:-1.0,eqMid2Gain:2.0,eqHighGain:1.8,w1:0,w2:115,w3:135,w4:150,imagerScale:'v2',maxGain:5.4,maxUpward:2.0,maxSoftClip:15,maxTransient:25,ceiling:-0.1,isBypassed:false,chain:[],compActive:false,compThreshold:-16,compRatio:3,compMakeup:0,limActive:false,limThreshold:-1.0,limMode:0,limLookahead:2,excActive:false,excDrive:40,rebalActive:false,rebalMid:0,rebalSide:0,vstFxChain:[],vstBypass:false});useEffect(()=>{window.currentMasteringSettings=masteringSettings;if(audioCtx&&masterBus){toggleMasteringOnMaster(masteringSettings.masterConnected,masteringSettings.isBypassed);applyMasteringSettings(masteringSettings);// Đồng bộ realtime VST master với PWR/Bypass: WebAudio graph đã đổi theo +activeModule:null,imagerActive:false,maximizerActive:false,eqLowGain:1.5,eqMid1Gain:-1.0,eqMid2Gain:2.0,eqHighGain:1.8,w1:0,w2:115,w3:135,w4:150,imagerScale:'v2',maxGain:5.4,maxUpward:2.0,maxSoftClip:15,maxTransient:25,ceiling:-0.1,isBypassed:false,chain:[],compActive:false,compThreshold:-16,compRatio:3,compMakeup:0,limActive:false,limThreshold:-1.0,limMode:0,limLookahead:2,excActive:false,excDrive:40,rebalActive:false,rebalMid:0,rebalSide:0,vstFxChain:[],vstBypass:false});useEffect(()=>{window.currentMasteringSettings=masteringSettings;const _fxPrevAct=!!(fxRt&&fxRt.active);const _fxPrevWarm=!!(fxRt&&fxRt.warm);if(audioCtx&&masterBus){toggleMasteringOnMaster(masteringSettings.masterConnected,masteringSettings.isBypassed);applyMasteringSettings(masteringSettings);// Đồng bộ realtime VST master với PWR/Bypass: WebAudio graph đã đổi theo // masterConnected nhưng fxRt không tự dừng → tắt PWR mà âm không đổi. try{if(fxRtChain().length){fxRtStart();// V45.46: start từ settings-change lúc KHÔNG có playback (mount mở // project có master chain / chỉnh PWR khi đang dừng) → engine từng chạy @@ -1700,7 +1703,7 @@ const _hasVst=!!(masteringSettings.gui_open_path&&(masteringSettings.chain||[]). try{const _list=activeTracksRef.current&&activeTracksRef.current.length?activeTracksRef.current:tracks;_list.forEach(_t=>{const _n=activeTrackNodesRef.current[_t.id];if(_n){if(_n.sfRouteGain&&_n.sfDryGain){const _b=effMidiBypass(_t);_n.sfRouteGain.gain.value=_b?0:1;_n.sfDryGain.gain.value=_b?1:0;}if(_n.route&&_n.route.routeGain&&_n.route.dryGain){const _ab=routeBypassFor(_t);_n.route.routeGain.gain.value=_ab?0:1;_n.route.dryGain.gain.value=_ab?1:0;}}});// Sub-nodes (section tabs, key '_sub_'): PWR master ON → // ép qua master chain; OFF → khôi phục theo nút A/♪ riêng của section // (route._bypass lưu trạng thái gốc lúc tạo/toggle). -Object.keys(activeTrackNodesRef.current).forEach(_k=>{if(!_k.includes('_sub_'))return;const _sn=activeTrackNodesRef.current[_k];if(_sn&&_sn.route&&_sn.route.routeGain&&_sn.route.dryGain){const _sab=masteringChainOn()?false:!!_sn.route._bypass;_sn.route.routeGain.gain.value=_sab?0:1;_sn.route.dryGain.gain.value=_sab?1:0;}});updateSfRouting();}catch(e){}}try{if(_phArmed()&&window.__phSnapAll){window.__phSnapAll('ph_effect');_dbgLog('ph_effect','mc='+(masteringSettings.masterConnected?1:0)+' bp='+(masteringSettings.isBypassed?1:0)+' chain='+fxRtChain().length+' gui='+(masteringSettings.gui_open_path||'na'));}}catch(e){}},[masteringSettings]);const[pluginManagerModalOpen,setPluginManagerModalOpen]=useState(false);const[pluginsData,setPluginsData]=useState(null);// D10: bridge indicator + "Ép dùng WASM" debug toggle. +Object.keys(activeTrackNodesRef.current).forEach(_k=>{if(!_k.includes('_sub_'))return;const _sn=activeTrackNodesRef.current[_k];if(_sn&&_sn.route&&_sn.route.routeGain&&_sn.route.dryGain){const _sab=masteringChainOn()?false:!!_sn.route._bypass;_sn.route.routeGain.gain.value=_sab?0:1;_sn.route.dryGain.gain.value=_sab?1:0;}});updateSfRouting();}catch(e){}}try{if(!_fxPrevAct&&_fxPrevWarm&&fxRt.active&&window.__fxRtWakeRebase)window.__fxRtWakeRebase();}catch(e){}try{if(_phArmed()&&window.__phSnapAll){window.__phSnapAll('ph_effect');_dbgLog('ph_effect','mc='+(masteringSettings.masterConnected?1:0)+' bp='+(masteringSettings.isBypassed?1:0)+' chain='+fxRtChain().length+' gui='+(masteringSettings.gui_open_path||'na'));}}catch(e){}},[masteringSettings]);const[pluginManagerModalOpen,setPluginManagerModalOpen]=useState(false);const[pluginsData,setPluginsData]=useState(null);// D10: bridge indicator + "Ép dùng WASM" debug toggle. const[bridgeUi,setBridgeUi]=useState({connected:false,forceWasm:false});React.useEffect(()=>{if(!window.NativeBridgeService)return;setBridgeUi(s=>({...s,connected:!!window.NativeBridgeService.isBridgeConnected}));window.NativeBridgeService.onStatusChange(function(st){// V8 bug 4: reset map NGAY DAU callback (ca connected lan !connected) — // Rust auto-restart KHONG emit bridge-down khi thanh cong → map cu true → // GUI click skip load → attach null → nen trang. @@ -1804,7 +1807,13 @@ const bridgeNoteEpochRef=useRef(0);// Resume main/session play khi rời PIANO R // đang play → bấm Space (play piano roll) → stopAllPlayback dừng main → // quay lại MAIN/SECTION → CÂM. Lưu {offset, audioTime} lúc mở tab → khi // rời tab resume startTrackPlayback(offset + elapsed thật). -const mainResumeRef=useRef(null);currentTimeRef.current=currentTime;// M21 ph-diag hooks (Bug C — playhead nhay khi PWR master FX toggle) +const mainResumeRef=useRef(null);currentTimeRef.current=currentTime;// M22 Bug C fix hook: rebase timeline khi wake warm-session (PWR ON giữa lúc +// play) — engine processedSamples bị đóng băng suốt OFF-warm (worklet rời +// graph) nên ps−anchor cũ tụt cả khoảng OFF; nếu không rebase playhead +// engine-derived nhảy LÙI về vị trí trước OFF (ph_jump d âm lớn). Rebase: +// dời startOffsetTimeRef/startAudioTimeRef về wall hiện tại + anchor=ps → +// engine clock nối lại đúng chỗ, playhead liên tục (lệch ~latencyTotal). +window.__fxRtWakeRebase=function(){try{if(!fxRt||!fxRt.active)return;const _c=getAudioContext();const _wall=(startOffsetTimeRef.current||0)+(_c.currentTime-(startAudioTimeRef.current||_c.currentTime));const _pa=typeof fxRt.processedSamples==='number'?fxRt.processedSamples:null;startOffsetTimeRef.current=_wall;startAudioTimeRef.current=_c.currentTime;if(_pa!==null)fxRt.processedAnchor=_pa;if(_fxRtSeekFreeze){_fxRtSeekFreeze=false;if(_fxRtSeekFreezeTimer){clearTimeout(_fxRtSeekFreezeTimer);_fxRtSeekFreezeTimer=null;}}try{_dbgLog('ph_rebase','wall='+_wall.toFixed(3)+' at='+startAudioTimeRef.current.toFixed(3)+' off='+startOffsetTimeRef.current.toFixed(3)+' ps='+(typeof fxRt.processedSamples==='number'?fxRt.processedSamples:'na')+' pa='+(typeof fxRt.processedAnchor==='number'?fxRt.processedAnchor:'na')+' lat='+(fxRt.latencyTotal||0));}catch(e){}}catch(e){try{_dbgLog('ph_rebase','err '+String(e));}catch(e2){}}};// M21 ph-diag hooks (Bug C — playhead nhay khi PWR master FX toggle) // __phArmPwr: user bam PWR OFF/ON -> arm cua so log 6s + snapshot ngay lap tuc. // __phSnapAll: snapshot refs/fxRt tai diem goi (chi log khi cua so dang mo). window.__phArmPwr=function(dir){try{_phDiagArmedUntil=Date.now()+6000;_dbgLog('ph_pwr','dir='+dir+' cur='+(currentTimeRef.current==null?'na':currentTimeRef.current.toFixed(3))+' off='+(startOffsetTimeRef.current==null?'na':startOffsetTimeRef.current.toFixed(3))+' at='+(startAudioTimeRef.current==null?'na':startAudioTimeRef.current.toFixed(3))+' ctx='+(getAudioContext().currentTime||0).toFixed(3)+' fx='+(fxRt?'act='+(fxRt.active?1:0)+' warm='+(fxRt.warm?1:0)+' start='+(fxRt.starting?1:0)+' ps='+(typeof fxRt.processedSamples==='number'?fxRt.processedSamples:'na')+' pa='+(typeof fxRt.processedAnchor==='number'?fxRt.processedAnchor:'na')+' lat='+(fxRt.latencyTotal||0)+' pdc='+(fxRt.pdcCalibMs||0)+' sid='+(fxRt.sessionId||'na'):'na')+' freeze='+(_fxRtSeekFreeze?1:0)+' snap='+(_fxRtPlayheadSnapPending?1:0)+' wh='+(wrapHoldRef.current?wrapHoldRef.current.end.toFixed(3)+'/'+wrapHoldRef.current.v0.toFixed(3):'na'));}catch(e){try{_dbgLog('ph_pwr','err '+String(e));}catch(e2){}}};window.__phSnapAll=function(tag){try{if(!_phArmed())return;_dbgLog(tag,'cur='+(currentTimeRef.current==null?'na':currentTimeRef.current.toFixed(3))+' off='+(startOffsetTimeRef.current==null?'na':startOffsetTimeRef.current.toFixed(3))+' at='+(startAudioTimeRef.current==null?'na':startAudioTimeRef.current.toFixed(3))+' ctx='+(getAudioContext().currentTime||0).toFixed(3)+' fx='+(fxRt?'act='+(fxRt.active?1:0)+' warm='+(fxRt.warm?1:0)+' start='+(fxRt.starting?1:0)+' ps='+(typeof fxRt.processedSamples==='number'?fxRt.processedSamples:'na')+' pa='+(typeof fxRt.processedAnchor==='number'?fxRt.processedAnchor:'na')+' lat='+(fxRt.latencyTotal||0)+' pdc='+(fxRt.pdcCalibMs||0)+' sid='+(fxRt.sessionId||'na'):'na')+' freeze='+(_fxRtSeekFreeze?1:0)+' snap='+(_fxRtPlayheadSnapPending?1:0)+' wh='+(wrapHoldRef.current?wrapHoldRef.current.end.toFixed(3)+'/'+wrapHoldRef.current.v0.toFixed(3):'na'));}catch(e){try{_dbgLog(tag,'err '+String(e));}catch(e2){}}};// ── Keyboard Shortcuts ── @@ -2190,7 +2199,7 @@ fxRtTrackReconcile(trackId,node,track);}catch(e){console.warn('rebuildTrackFxGra // Cần activeTrackNodesRef/isPlayingRef. Session giống master (fxRtStart): // fetch start → AudioWorkletNode 'sf-fx-realtime' → WS; WS open → splice // worklet vào chain hiện tại (đọc mods MỚI NHẤT — an toàn với rebuild). -const fxRtTrackStart=async(trackId,track,path)=>{const key=trackId+':'+path;const chain=fxRtTrackChain(track);if(!chain.length)return;if(fxRtTracks[key]){if(!fxRtTracks[key].node||fxRtTracks[key].node.context!==getAudioContext())fxRtTrackStop(trackId,path,true);else return;}const ctx=getAudioContext();if(!ctx||!ctx.audioWorklet)return;const chainPayload=chain.map(s=>s.type==='vst3'?{type:'vst3',path:s.path,name:s.name||s.path,preset_b64:s.preset_b64||'',bypass:!!s.bypass||!!(track&&track.vstFxBypass)}:{type:'builtin',id:s.type,params:s.params&&Object.keys(s.params).length?s.params:{},bypass:!!s.bypass});let st;try{st=await window.SonicAPI.fxRealtimeStart({fx_chain:chainPayload,sample_rate:ctx.sampleRate||44100});}catch(e){console.warn('[FX RT track] start fail',trackId,path,e);return;}const entry={sessionId:st.session_id,ws:null,node:null,ready:false,path,spliced:null,chainKey:fxRtTrackChainKey(chain)+'|vb:'+(track&&track.vstFxBypass?'1':'0'),paramsBySlot:chain.map(s=>fxRtEntryParams(s,null)),inQueue:[],pumpTimer:null,lastInSent:0,flushing:false,flushTimer:null};fxRtTracks[key]=entry;fxRtGuiPushShm();try{await ctx.audioWorklet.addModule(`${API_BASE_URL}/static/js/sf-fx-realtime.js?v=202609071004`);const node=new AudioWorkletNode(ctx,'sf-fx-realtime',{numberOfInputs:1,numberOfOutputs:1,outputChannelCount:[2]});entry.node=node;const u=new URL(API_BASE_URL);const wsProto=u.protocol==='https:'?'wss://':'ws://';// WS to 'localhost' bi tre ~300ms (Chromium thu ::1 truoc roi fallback) - dung 127.0.0.1 +const fxRtTrackStart=async(trackId,track,path)=>{const key=trackId+':'+path;const chain=fxRtTrackChain(track);if(!chain.length)return;if(fxRtTracks[key]){if(!fxRtTracks[key].node||fxRtTracks[key].node.context!==getAudioContext())fxRtTrackStop(trackId,path,true);else return;}const ctx=getAudioContext();if(!ctx||!ctx.audioWorklet)return;const chainPayload=chain.map(s=>s.type==='vst3'?{type:'vst3',path:s.path,name:s.name||s.path,preset_b64:s.preset_b64||'',bypass:!!s.bypass||!!(track&&track.vstFxBypass)}:{type:'builtin',id:s.type,params:s.params&&Object.keys(s.params).length?s.params:{},bypass:!!s.bypass});let st;try{st=await window.SonicAPI.fxRealtimeStart({fx_chain:chainPayload,sample_rate:ctx.sampleRate||44100});}catch(e){console.warn('[FX RT track] start fail',trackId,path,e);return;}const entry={sessionId:st.session_id,ws:null,node:null,ready:false,path,spliced:null,chainKey:fxRtTrackChainKey(chain)+'|vb:'+(track&&track.vstFxBypass?'1':'0'),paramsBySlot:chain.map(s=>fxRtEntryParams(s,null)),inQueue:[],pumpTimer:null,lastInSent:0,flushing:false,flushTimer:null};fxRtTracks[key]=entry;fxRtGuiPushShm();try{await ctx.audioWorklet.addModule(`${API_BASE_URL}/static/js/sf-fx-realtime.js?v=202609071038`);const node=new AudioWorkletNode(ctx,'sf-fx-realtime',{numberOfInputs:1,numberOfOutputs:1,outputChannelCount:[2]});entry.node=node;const u=new URL(API_BASE_URL);const wsProto=u.protocol==='https:'?'wss://':'ws://';// WS to 'localhost' bi tre ~300ms (Chromium thu ::1 truoc roi fallback) - dung 127.0.0.1 const wsHost=u.hostname==='localhost'||u.hostname==='::1'?'127.0.0.1'+(u.port?':'+u.port:''):u.host;const tok=localStorage.getItem('sonic_token')||'';const ws=new WebSocket(`${wsProto}${wsHost}${st.ws_url}${tok?'?token='+encodeURIComponent(tok):''}`);entry.ws=ws;ws.binaryType='arraybuffer';ws.onopen=()=>{entry.ready=true;entry.inQueue=[];// bỏ audio stale trước khi WS open — hết lag khởi đầu fxRtTrackSplice(trackId,path);try{(entry.paramsBySlot||[]).forEach((pl,slot)=>pl.forEach(p=>ws.send(JSON.stringify({cmd:'set_param',slot,key:p.key,value:p.value}))));}catch(e){}};ws.onmessage=ev=>{if(typeof ev.data==='string'){try{const d=JSON.parse(ev.data);if(d&&d.cmd==='flush_done'){entry.flushing=false;if(entry.flushTimer){clearTimeout(entry.flushTimer);entry.flushTimer=null;}if(entry.node)entry.node.port.postMessage({type:'flush_done'});}}catch(e){}return;}if(!(ev.data instanceof ArrayBuffer)||!entry.node)return;if(entry.flushing)return;// V24: output stale trong luc flush — drop const f=new Float32Array(ev.data);const half=f.length/2;const l=new Float32Array(half);const r=new Float32Array(half);for(let i=0;ifxRtTrackStop(trackId,path,true);ws.onerror=()=>{try{ws.close();}catch(e){}};entry.node.port.onmessage=ev=>{const d=ev.data;if(!d||!entry.ws)return;if(d.type==='flush'){// V24: nhu master — worklet underrun-resume → flush engine ring diff --git a/app/templates/index.html b/app/templates/index.html index 4e89783..a8b844e 100644 --- a/app/templates/index.html +++ b/app/templates/index.html @@ -51,7 +51,7 @@ - +