fix(rt): restart fxRt khi chain VST doi trong luc realtime dang chay

- masteringSettings effect goi fxRtStart() khong dieu kien: early-return
  khi active + cung chainKey, stop+start lai khi chain doi (insert/remove/
  bypass/reorder VST trong MASTER FX CHAIN) — truoc day nhanh chainKey
  chet vi branch chi chay khi !fxRt.active
- test list_scan_all: path plugin phai ton tai tren dia (loc entry chet
  truoc cache); dung _scan_json helper voi file that trong tmp_path
- bump cache-bust app.precompiled.js -> 202608212300
This commit is contained in:
2026-08-22 16:13:10 +07:00
parent eef8603f98
commit edc647ded6
4 changed files with 18 additions and 6 deletions
+1 -1
View File
@@ -18793,7 +18793,7 @@ const App = () => {
// Đồ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) { if (!fxRt.active) fxRtStart(); }
if (fxRtChain().length) { fxRtStart(); }
else if (fxRt.active) { fxRtStop(); }
} catch (e) { console.warn('[FX RT] resync fail:', e); }
// Re-sync live track routes: mastering ON → mọi track qua chain (♪ bị
+1 -1
View File
@@ -1239,7 +1239,7 @@ const[fxRackTarget,setFxRackTarget]=useState(null);window.__openFxRack=(trackId,
window.__renderProjectPreview=async()=>{const schema=serializeProjectToSchema(currentProjectId||'temp_project',projectName||'Dự án tạm chưa lưu',bpm,tracks,subTabs,sessionTabs,masteringSettings,masterVolume);try{const res=await window.SonicAPI.renderProject(schema,'preview_'+Date.now()+'.wav');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:true,maximizerActive:true,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,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
// masterConnected nhưng fxRt không tự dừng → tắt PWR mà âm không đổi.
try{if(fxRtChain().length){if(!fxRt.active)fxRtStart();}else if(fxRt.active){fxRtStop();}}catch(e){console.warn('[FX RT] resync fail:',e);}// Re-sync live track routes: mastering ON → mọi track qua chain (♪ bị
try{if(fxRtChain().length){fxRtStart();}else if(fxRt.active){fxRtStop();}}catch(e){console.warn('[FX RT] resync fail:',e);}// Re-sync live track routes: mastering ON → mọi track qua chain (♪ bị
// override bởi effMidiBypass/effAudioBypass) — nút PWR bật/tắt phải áp
// ngay lên node đang phát (không chờ tracks effect).
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 '<parent>_sub_<id>'): PWR master ON →
+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=202608181030" defer></script>
<script src="/static/js/app.precompiled.js?v=202608212300" defer></script>
<link rel="stylesheet" href="/static/css/styles.css?v=202607271016">
<style>
:root {
+15 -3
View File
@@ -63,15 +63,26 @@ class TestFxScanCache:
"type": "VST3", "is_fx": True, "is_instrument": False},
]})
def _scan_json(self, d):
"""JSON scan với path plugin THẬT tồn tại trên đĩa — list_scan_all lọc
entry plugin đã xóa khỏi đĩa (os.path.exists) trước khi cache; path ảo
(C:\\VST3\\Ozone Pro.vst3) bị lọc hết → test không thể assert entry."""
return json.dumps({"plugins": [
{"path": os.path.join(d, "Ozone Pro.vst3"), "name": "Ozone Pro",
"type": "VST3", "is_fx": True, "is_instrument": False},
]})
def _pm(self, tmp_path):
d = str(tmp_path)
return PluginManager(vst_dir=d, sf_dir=d, extra_vst_dirs=[])
def test_scan_caches_with_bridge(self, tmp_path):
pm = self._pm(tmp_path)
# Plugin phải tồn tại trên đĩa — list_scan_all lọc entry chết trước cache.
open(os.path.join(str(tmp_path), "Ozone Pro.vst3"), "w").close()
with patch("app.core.native_render.find_fx_bridge_exe", return_value="fx_bridge.exe"), \
patch("subprocess.run", return_value=MagicMock(
returncode=0, stdout=self.SCAN_JSON, stderr="")) as mrun:
returncode=0, stdout=self._scan_json(str(tmp_path)), stderr="")) as mrun:
r1 = pm.list_scan_all()
assert len(r1) == 1 and r1[0]["name"] == "Ozone Pro"
assert pm._fx_scan_all is not None and len(pm._fx_scan_all) == 1
@@ -88,10 +99,11 @@ class TestFxScanCache:
with patch("app.core.native_render.find_fx_bridge_exe", return_value=""):
assert pm.list_scan_all() == []
assert pm._fx_scan_all is None # KHÔNG cache rỗng
# Có bridge → scan thật chạy
# Có bridge → scan thật chạy (plugin tồn tại trên đĩa)
open(os.path.join(str(tmp_path), "Ozone Pro.vst3"), "w").close()
with patch("app.core.native_render.find_fx_bridge_exe", return_value="fx_bridge.exe"), \
patch("subprocess.run", return_value=MagicMock(
returncode=0, stdout=self.SCAN_JSON, stderr="")):
returncode=0, stdout=self._scan_json(str(tmp_path)), stderr="")):
r = pm.list_scan_all()
assert len(r) == 1