From 788a371f72160c044aa1d963b53c42fac4dbdecf Mon Sep 17 00:00:00 2001 From: locphamtran Date: Sat, 29 Aug 2026 17:50:12 +0700 Subject: [PATCH] V45.3 fix Qin tremolo khi solo track khac: activeTracksRef luon ghi state da patch (bo restore STALE) - D18b guard/scheduler thay solo-out ngay, v=202608291749 --- app/static/js/app.jsx | 19 +- app/static/js/app.precompiled.js | 14 +- app/templates/index.html | 2 +- patch_q3_ref.py | 33 + .../app/static/js/app.precompiled.js | 2624 +++++++++++++++++ .../_internal/app/static/js/sf-fx-realtime.js | 360 +++ .../_internal/app/templates/index.html | 479 +++ 7 files changed, 3517 insertions(+), 14 deletions(-) create mode 100644 patch_q3_ref.py create mode 100644 target/release/daw_engine/_internal/app/static/js/app.precompiled.js create mode 100644 target/release/daw_engine/_internal/app/static/js/sf-fx-realtime.js create mode 100644 target/release/daw_engine/_internal/app/templates/index.html diff --git a/app/static/js/app.jsx b/app/static/js/app.jsx index 9c38268..1a14383 100644 --- a/app/static/js/app.jsx +++ b/app/static/js/app.jsx @@ -1907,7 +1907,7 @@ async function fxRtStart() { fxRt.sessionId = st.session_id; fxRtGuiPushShm(); try { - await ctx.audioWorklet.addModule(`${API_BASE_URL}/static/js/sf-fx-realtime.js?v=202608291721`); + await ctx.audioWorklet.addModule(`${API_BASE_URL}/static/js/sf-fx-realtime.js?v=202608291749`); 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); @@ -18937,6 +18937,12 @@ const App = () => { const applyAllTrackMuteSolo = (trackId, patch) => { const list = (activeTracksRef.current && activeTracksRef.current.length) ? activeTracksRef.current : tracks; const effective = (trackId && patch) ? list.map(t => t.id === trackId ? { ...t, ...patch } : t) : list; + // V45.3: LUON ghi ref voi state da patch (mute/solo). Truoc day chi ghi trong + // nhanh becameAudible roi restore STALE -> solo-out khong co track became + // audible -> D18b guard (25423) + scheduler (25498) + toggleTrackSoloEvaluate + // (setTimeout 60ms restart) thay trang thai PRE-solo -> Qin note van fire + // (tremolo). Render ke tiep tu dong dong bo lai ref. + activeTracksRef.current = effective; let becameAudible = false; effective.forEach(t => { const audible = computeTrackAudibleGain(effective, t) > 0; @@ -19011,13 +19017,8 @@ const App = () => { // startTrackPlayback (isPlayable) → câm + CC11 không chạy. Dùng // `effective` (đã patch mute/solo) làm nguồn restart; ref tự phục hồi // ở render kế tiếp (activeTracksRef.current = activeTracks). - const _prevActiveTracks = activeTracksRef.current; - activeTracksRef.current = effective; - try { - startTrackPlayback(resumeAt); - } finally { - activeTracksRef.current = _prevActiveTracks; - } + // V45.3: ref da = effective (dau ham) -> restart thay trang thai moi ngay. + startTrackPlayback(resumeAt); } catch (e) { console.warn('mute/solo resume error:', e); } @@ -24820,7 +24821,7 @@ const App = () => { fxRtTracks[key] = entry; fxRtGuiPushShm(); try { - await ctx.audioWorklet.addModule(`${API_BASE_URL}/static/js/sf-fx-realtime.js?v=202608291721`); + await ctx.audioWorklet.addModule(`${API_BASE_URL}/static/js/sf-fx-realtime.js?v=202608291749`); const node = new AudioWorkletNode(ctx, 'sf-fx-realtime', { numberOfInputs: 1, numberOfOutputs: 1, outputChannelCount: [2] }); entry.node = node; const u = new URL(API_BASE_URL); diff --git a/app/static/js/app.precompiled.js b/app/static/js/app.precompiled.js index 0ab9261..43b97b3 100644 --- a/app/static/js/app.precompiled.js +++ b/app/static/js/app.precompiled.js @@ -339,7 +339,7 @@ else if(fxRt.chainKey!==ck){fxRtSetChain(chain);fxRt.chainKey=ck;return;}else re // 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=202608291721`);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=45.3`);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 @@ -1321,7 +1321,12 @@ window.__setTrackMasteringBypass=function(trackId,bypass){trackAudioBypassMap[tr // track node in the current context. Called from the M/S buttons of the // mixer strips + track strips so toggling muting/soloing affects the items // already playing on that track immediately (20ms crossfade, no click). -const applyAllTrackMuteSolo=(trackId,patch)=>{const list=activeTracksRef.current&&activeTracksRef.current.length?activeTracksRef.current:tracks;const effective=trackId&&patch?list.map(t=>t.id===trackId?{...t,...patch}:t):list;let becameAudible=false;effective.forEach(t=>{const audible=computeTrackAudibleGain(effective,t)>0;const wasAudible=trackAudibleRef.current[t.id]!==false;const node=activeTrackNodesRef.current[t.id];const audibleGain=computeTrackAudibleGain(effective,t);if(node)setTrackNodeGain(node,audibleGain);// Section sub-nodes (key: _sub_) — mute/solo của +const applyAllTrackMuteSolo=(trackId,patch)=>{const list=activeTracksRef.current&&activeTracksRef.current.length?activeTracksRef.current:tracks;const effective=trackId&&patch?list.map(t=>t.id===trackId?{...t,...patch}:t):list;// V45.3: LUON ghi ref voi state da patch (mute/solo). Truoc day chi ghi trong +// nhanh becameAudible roi restore STALE -> solo-out khong co track became +// audible -> D18b guard (25423) + scheduler (25498) + toggleTrackSoloEvaluate +// (setTimeout 60ms restart) thay trang thai PRE-solo -> Qin note van fire +// (tremolo). Render ke tiep tu dong dong bo lai ref. +activeTracksRef.current=effective;let becameAudible=false;effective.forEach(t=>{const audible=computeTrackAudibleGain(effective,t)>0;const wasAudible=trackAudibleRef.current[t.id]!==false;const node=activeTrackNodesRef.current[t.id];const audibleGain=computeTrackAudibleGain(effective,t);if(node)setTrackNodeGain(node,audibleGain);// Section sub-nodes (key: _sub_) — mute/solo của // SECTION-TAB phải áp gain cho node play section — CHỈ trong section // (activeTracksRef = tracks section), KHÔNG đụng track MAIN. Object.keys(activeTrackNodesRef.current).forEach(function(k){if(k.endsWith('_sub_'+t.id)){const sn=activeTrackNodesRef.current[k];if(sn&&sn.gainNode)setTrackNodeGain(sn,audibleGain);}});if(node&&node.sfEntry){try{node.sfEntry.gain.setTargetAtTime(audibleGain,getAudioContext().currentTime,0.02);}catch(e){}}// MIDI/bridge tracks: CC7 (channel volume) realtime — even for notes @@ -1345,7 +1350,8 @@ stopAllPlayback();const ctx=getAudioContext();const resumeAt=currentTimeRef.curr // startTrackPlayback (isPlayable) → câm + CC11 không chạy. Dùng // `effective` (đã patch mute/solo) làm nguồn restart; ref tự phục hồi // ở render kế tiếp (activeTracksRef.current = activeTracks). -const _prevActiveTracks=activeTracksRef.current;activeTracksRef.current=effective;try{startTrackPlayback(resumeAt);}finally{activeTracksRef.current=_prevActiveTracks;}}catch(e){console.warn('mute/solo resume error:',e);}}updateSfRouting();};window.__applyTrackMuteSolo=applyAllTrackMuteSolo;window.__toggleMediaExplorerRef=function(){setShowMediaExplorer(function(p){const next=!p;if(next)setShowMixerRef.current(false);return next;});};const[mixerHeight,setMixerHeight]=useState(function(){var saved=localStorage.getItem('studio_mixer_height');return saved?parseInt(saved):200;}());const[mediaExplorerPanelHeight,setMediaExplorerPanelHeight]=useState(function(){var saved=localStorage.getItem('studio_media_explorer_height');return saved?parseInt(saved):240;}());const[masterVolume,setMasterVolume]=useState(0);// dB (legacy single fader) +// V45.3: ref da = effective (dau ham) -> restart thay trang thai moi ngay. +startTrackPlayback(resumeAt);}catch(e){console.warn('mute/solo resume error:',e);}}updateSfRouting();};window.__applyTrackMuteSolo=applyAllTrackMuteSolo;window.__toggleMediaExplorerRef=function(){setShowMediaExplorer(function(p){const next=!p;if(next)setShowMixerRef.current(false);return next;});};const[mixerHeight,setMixerHeight]=useState(function(){var saved=localStorage.getItem('studio_mixer_height');return saved?parseInt(saved):200;}());const[mediaExplorerPanelHeight,setMediaExplorerPanelHeight]=useState(function(){var saved=localStorage.getItem('studio_media_explorer_height');return saved?parseInt(saved):240;}());const[masterVolume,setMasterVolume]=useState(0);// dB (legacy single fader) const[masterVolL,setMasterVolL]=useState(0);// dB - master L fader const[masterVolR,setMasterVolR]=useState(0);// dB - master R fader const[masterLink,setMasterLink]=useState(true);// LINK L/R @@ -1930,7 +1936,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=202608291721`);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=45.3`);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 5c3e5ec..0f66d5a 100644 --- a/app/templates/index.html +++ b/app/templates/index.html @@ -51,7 +51,7 @@ - + + + + +
+ + + \ No newline at end of file