fix(fxrt): mo GUI VST khong gay crackle - GUI spawn BELOW_NORMAL, mien watchdog khi mo native GUI, realtime loop ABOVE_NORMAL
This commit is contained in:
+10
-2
@@ -698,12 +698,20 @@ async def open_fx_gui(req: FxGuiRequest, current_user: dict = Depends(get_curren
|
||||
raise HTTPException(status_code=500, detail="Không ghi được job file cho bridge.")
|
||||
cmd = [exe, "--fx-gui" if req.embed else "--open-fx-gui", job_path]
|
||||
try:
|
||||
# fx-gui spawn o BELOW_NORMAL: process nay load instance VST THU HAI
|
||||
# (CPU spike vai giay) - neu o NORMAL se preempt realtime audio loop
|
||||
# (fx_vst_bridge --realtime-fx, thread NORMAL) -> underrun -> am "bop
|
||||
# nghet" ngay luc mo GUI roi moi hoi phuc. BELOW_NORMAL cho load GUI
|
||||
# khong canh tranh thread audio.
|
||||
_gui_creationflags = subprocess.CREATE_NO_WINDOW if os.name == "nt" else 0
|
||||
if os.name == "nt":
|
||||
_gui_creationflags |= getattr(subprocess, "BELOW_NORMAL_PRIORITY_CLASS", 0)
|
||||
if req.embed:
|
||||
proc = subprocess.Popen(cmd, stdout=subprocess.PIPE,
|
||||
stderr=subprocess.STDOUT, text=True,
|
||||
close_fds=os.name != "nt",
|
||||
env=dict(os.environ, SF_PARENT_PID=str(os.getpid())),
|
||||
creationflags=subprocess.CREATE_NO_WINDOW if os.name == "nt" else 0)
|
||||
creationflags=_gui_creationflags)
|
||||
# Bridge prints SF_FXGUI_PORT=<port> after binding its HTTP server.
|
||||
embed_url = None
|
||||
try:
|
||||
@@ -741,7 +749,7 @@ async def open_fx_gui(req: FxGuiRequest, current_user: dict = Depends(get_curren
|
||||
"embed_url": embed_url, "cmd": cmd}
|
||||
proc = subprocess.Popen(cmd, close_fds=os.name != "nt",
|
||||
env=dict(os.environ, SF_PARENT_PID=str(os.getpid())),
|
||||
creationflags=subprocess.CREATE_NO_WINDOW if os.name == "nt" else 0)
|
||||
creationflags=_gui_creationflags)
|
||||
proc._sf_plugin_path = plugin_path
|
||||
_register_fx_gui_process(proc)
|
||||
return {"success": True, "started": True, "already_running": False, "cmd": cmd}
|
||||
|
||||
+22
-6
@@ -1654,6 +1654,10 @@ const fxRt = { active: false, starting: false, startToken: 0, sessionId: null, w
|
||||
// stop thật (fade=true) / bridge-down / new-project / export-end KHÔNG set →
|
||||
// teardown bình thường.
|
||||
let fxRtKeepForRestart = false;
|
||||
// Native VSTi GUI open/reload đang chạy (C++ reloadForGUI mute 1-3s khi mở
|
||||
// lại GUI sau khi đóng) → master-silence watchdog (750ms) sẽ false-positive →
|
||||
// rebuild + fxRtStop → dry→wet vài giây. Exempt watchdog trong cửa sổ này.
|
||||
let nativeGuiOpenUntilTs = 0;
|
||||
// Pace input realtime: worklet sản xuất đúng realtime, nhưng sau khi main
|
||||
// thread bị block (solo restart, GUI decode, rAF) các frame 'in' dồn lại —
|
||||
// gửi 1 loạt làm SHM ring 4-slot tràn → mất block → ngắt quãng. Gửi tối đa 1
|
||||
@@ -7223,6 +7227,10 @@ const ensureAndOpenVstGui = async (track) => {
|
||||
// can spam. 20 lan x 500ms tao deferred storm (50+ event/moi restart) va
|
||||
// re-attach view dang attached (crash USER32 0xc000041d). Cap 5.
|
||||
const openVstGuiRetry = async (instrumentId, bch, attempts) => {
|
||||
// C++ reloadForGUI (mở lại GUI sau khi đóng) mute audio 1-3s → watchdog
|
||||
// master-silence false-positive → rebuild + fxRtStop → dry→wet vài giây.
|
||||
// Exempt watchdog 12s kể từ lần mở này (đủ cho retry + load/reload).
|
||||
nativeGuiOpenUntilTs = performance.now() + 12000;
|
||||
const n = Math.min((attempts && attempts > 0) ? attempts : 5, 5);
|
||||
for (let i = 0; i < n; i++) {
|
||||
try { await window.NativeBridgeService.openNativeGUI(instrumentId, bch); }
|
||||
@@ -23362,12 +23370,19 @@ const App = () => {
|
||||
// giữa các note > 750ms là BÌNH THƯỜNG → false-positive = recovery
|
||||
// hủy play + restart notes (glitch) — đúng chuỗi log recovery trước.
|
||||
if ((isPianoRoll || isSectionTab ? nanOut : (pk < 0.001 || nanOut))) {
|
||||
masterSilenceFramesRef.current++;
|
||||
const sinceRebuild = performance.now() - (lastMasterRebuildTimeRef.current || 0);
|
||||
// NaN = chain CHẾT chắc chắn → rebuild NGAY (3 frame ≈ 50ms).
|
||||
// pk<0.001 (im lặng nghi ngờ — main) giữ 45 frame (750ms) để loại
|
||||
// transient gap false-positive. Cooldown 3000 chống rebuild-loop.
|
||||
if (masterSilenceFramesRef.current > (nanOut ? 3 : 45) && sinceRebuild > 3000) {
|
||||
// Native VSTi GUI đang mở/reload (reloadForGUI mute 1-3s) — im
|
||||
// lặng là TỰ NHIÊN (C++ đang rebuild instance), không phải graph
|
||||
// hỏng. Không rebuild → không stopAllPlayback → không fxRtStop →
|
||||
// hết chuỗi dry→wet vài giây sau mỗi lần mở GUI track.
|
||||
if (performance.now() < nativeGuiOpenUntilTs) {
|
||||
masterSilenceFramesRef.current = 0;
|
||||
} else {
|
||||
masterSilenceFramesRef.current++;
|
||||
const sinceRebuild = performance.now() - (lastMasterRebuildTimeRef.current || 0);
|
||||
// NaN = chain CHẾT chắc chắn → rebuild NGAY (3 frame ≈ 50ms).
|
||||
// pk<0.001 (im lặng nghi ngờ — main) giữ 45 frame (750ms) để loại
|
||||
// transient gap false-positive. Cooldown 3000 chống rebuild-loop.
|
||||
if (masterSilenceFramesRef.current > (nanOut ? 3 : 45) && sinceRebuild > 3000) {
|
||||
masterSilenceFramesRef.current = 0;
|
||||
lastMasterRebuildTimeRef.current = performance.now();
|
||||
console.warn('[Recovery] Master silent while sources active — rebuilding audio graph');
|
||||
@@ -23408,6 +23423,7 @@ const App = () => {
|
||||
}
|
||||
} catch (e) { console.warn('[Recovery] rebuild error:', e); }
|
||||
}
|
||||
}
|
||||
} else {
|
||||
masterSilenceFramesRef.current = 0;
|
||||
}
|
||||
|
||||
@@ -285,7 +285,10 @@ const fxRt={active:false,starting:false,startToken:0,sessionId:null,ws:null,node
|
||||
// (hết bypass rồi mới load lại). Chỉ set TRƯỚC nhánh restart/resume; user
|
||||
// stop thật (fade=true) / bridge-down / new-project / export-end KHÔNG set →
|
||||
// teardown bình thường.
|
||||
let fxRtKeepForRestart=false;// Pace input realtime: worklet sản xuất đúng realtime, nhưng sau khi main
|
||||
let fxRtKeepForRestart=false;// Native VSTi GUI open/reload đang chạy (C++ reloadForGUI mute 1-3s khi mở
|
||||
// lại GUI sau khi đóng) → master-silence watchdog (750ms) sẽ false-positive →
|
||||
// rebuild + fxRtStop → dry→wet vài giây. Exempt watchdog trong cửa sổ này.
|
||||
let nativeGuiOpenUntilTs=0;// Pace input realtime: worklet sản xuất đúng realtime, nhưng sau khi main
|
||||
// thread bị block (solo restart, GUI decode, rAF) các frame 'in' dồn lại —
|
||||
// gửi 1 loạt làm SHM ring 4-slot tràn → mất block → ngắt quãng. Gửi tối đa 1
|
||||
// burst (4 block ~21ms) mỗi 21ms → ring luôn kịp tiêu thụ, audio hồi phục
|
||||
@@ -651,7 +654,10 @@ try{const ok=await window.NativeBridgeService.loadInstrument(lc.path,/\.dll$/i.t
|
||||
// som → C++ defer va tu fulfil khi assign() xong (pending-GUI sweep) → khong
|
||||
// can spam. 20 lan x 500ms tao deferred storm (50+ event/moi restart) va
|
||||
// re-attach view dang attached (crash USER32 0xc000041d). Cap 5.
|
||||
const openVstGuiRetry=async(instrumentId,bch,attempts)=>{const n=Math.min(attempts&&attempts>0?attempts:5,5);for(let i=0;i<n;i++){try{await window.NativeBridgeService.openNativeGUI(instrumentId,bch);}catch(e){console.warn('[Bridge] openNativeGUI retry',i+1,e);}await new Promise(r=>setTimeout(r,500));}return true;};// Wheel trên range input (Vol/Pan TCP): scale step theo deltaY — trackpad
|
||||
const openVstGuiRetry=async(instrumentId,bch,attempts)=>{// C++ reloadForGUI (mở lại GUI sau khi đóng) mute audio 1-3s → watchdog
|
||||
// master-silence false-positive → rebuild + fxRtStop → dry→wet vài giây.
|
||||
// Exempt watchdog 12s kể từ lần mở này (đủ cho retry + load/reload).
|
||||
nativeGuiOpenUntilTs=performance.now()+12000;const n=Math.min(attempts&&attempts>0?attempts:5,5);for(let i=0;i<n;i++){try{await window.NativeBridgeService.openNativeGUI(instrumentId,bch);}catch(e){console.warn('[Bridge] openNativeGUI retry',i+1,e);}await new Promise(r=>setTimeout(r,500));}return true;};// Wheel trên range input (Vol/Pan TCP): scale step theo deltaY — trackpad
|
||||
// smooth gửi deltaY nhỏ (~1-10px/sự kiện) nhưng rất nhiều sự kiện; wheel
|
||||
// notch gửi ~100-120px. Tích lũy theo key → trackpad lăn 100px = 1 nấc
|
||||
// (0.5dB vol / 5 pan), wheel notch giữ nguyên tốc độ cũ. deltaMode:
|
||||
@@ -1640,14 +1646,18 @@ let nanOut=false;if(isPianoRoll){try{const f=new Float32Array(128);masterBus.ana
|
||||
// pk<0.001 (im lặng) KHÔNG trigger cho piano roll — rests tự nhiên
|
||||
// giữa các note > 750ms là BÌNH THƯỜNG → false-positive = recovery
|
||||
// hủy play + restart notes (glitch) — đúng chuỗi log recovery trước.
|
||||
if(isPianoRoll||isSectionTab?nanOut:pk<0.001||nanOut){masterSilenceFramesRef.current++;const sinceRebuild=performance.now()-(lastMasterRebuildTimeRef.current||0);// NaN = chain CHẾT chắc chắn → rebuild NGAY (3 frame ≈ 50ms).
|
||||
if(isPianoRoll||isSectionTab?nanOut:pk<0.001||nanOut){// Native VSTi GUI đang mở/reload (reloadForGUI mute 1-3s) — im
|
||||
// lặng là TỰ NHIÊN (C++ đang rebuild instance), không phải graph
|
||||
// hỏng. Không rebuild → không stopAllPlayback → không fxRtStop →
|
||||
// hết chuỗi dry→wet vài giây sau mỗi lần mở GUI track.
|
||||
if(performance.now()<nativeGuiOpenUntilTs){masterSilenceFramesRef.current=0;}else{masterSilenceFramesRef.current++;const sinceRebuild=performance.now()-(lastMasterRebuildTimeRef.current||0);// NaN = chain CHẾT chắc chắn → rebuild NGAY (3 frame ≈ 50ms).
|
||||
// pk<0.001 (im lặng nghi ngờ — main) giữ 45 frame (750ms) để loại
|
||||
// transient gap false-positive. Cooldown 3000 chống rebuild-loop.
|
||||
if(masterSilenceFramesRef.current>(nanOut?3:45)&&sinceRebuild>3000){masterSilenceFramesRef.current=0;lastMasterRebuildTimeRef.current=performance.now();console.warn('[Recovery] Master silent while sources active — rebuilding audio graph');try{const pt=currentTimeRef.current;stopAllPlayback();Object.keys(activeTrackNodesRef.current).forEach(k=>{const n=activeTrackNodesRef.current[k];try{if(n&&n.gainNode&&n.gainNode.disconnect)n.gainNode.disconnect();}catch(e){}});activeTrackNodesRef.current={};// Tháo chain cũ khỏi destination rồi rebuild THẬT — initMasterBus
|
||||
// early-return khi masterBus còn tồn tại → recovery trước đây là
|
||||
// no-op → chain "state is bad" bị STUCK vĩnh viễn.
|
||||
try{if(masterBus){try{if(masterBus.analyser)masterBus.analyser.disconnect();}catch(e){}try{if(masterBus.output)masterBus.output.disconnect();}catch(e){}try{if(masterBus.dryOutput)masterBus.dryOutput.disconnect();}catch(e){}}masterBus=null;initMasterBus(getAudioContext());}catch(e){console.warn('[Recovery] initMasterBus error:',e);}// Resume ĐÚNG chế độ play hiện tại (main hoặc sub-tab piano roll)
|
||||
const curTab=activeTabRef.current;const subSt=subTabsRef.current.find(s=>s.id===curTab);if(subSt&&(subSt.type==='PIANO_ROLL'||subSt.buffer)){const resumeAt=subSt.currentTime||0;if(subSt.type==='PIANO_ROLL')schedulePianoRollMidi(subSt,resumeAt);startSubTabPlayback(subSt,resumeAt);setSubTabs(prev=>prev.map(s=>s.id===curTab?{...s,buffer:s.buffer||getAudioContext().createBuffer(1,128,getAudioContext().sampleRate),isPlaying:true,currentTime:resumeAt}:s));animationFrameIdRef.current=requestAnimationFrame(updatePlayhead);}else{setIsPlaying(true);startTrackPlayback(pt);}}catch(e){console.warn('[Recovery] rebuild error:',e);}}}else{masterSilenceFramesRef.current=0;}}else{masterSilenceFramesRef.current=0;}}catch(e){masterSilenceFramesRef.current=0;}}else{masterSilenceFramesRef.current=0;}if(recordingStateRef.current==='RECORDING'){const audioCtx=getAudioContext();const lookahead=0.1;// 100ms
|
||||
const curTab=activeTabRef.current;const subSt=subTabsRef.current.find(s=>s.id===curTab);if(subSt&&(subSt.type==='PIANO_ROLL'||subSt.buffer)){const resumeAt=subSt.currentTime||0;if(subSt.type==='PIANO_ROLL')schedulePianoRollMidi(subSt,resumeAt);startSubTabPlayback(subSt,resumeAt);setSubTabs(prev=>prev.map(s=>s.id===curTab?{...s,buffer:s.buffer||getAudioContext().createBuffer(1,128,getAudioContext().sampleRate),isPlaying:true,currentTime:resumeAt}:s));animationFrameIdRef.current=requestAnimationFrame(updatePlayhead);}else{setIsPlaying(true);startTrackPlayback(pt);}}catch(e){console.warn('[Recovery] rebuild error:',e);}}}}else{masterSilenceFramesRef.current=0;}}else{masterSilenceFramesRef.current=0;}}catch(e){masterSilenceFramesRef.current=0;}}else{masterSilenceFramesRef.current=0;}if(recordingStateRef.current==='RECORDING'){const audioCtx=getAudioContext();const lookahead=0.1;// 100ms
|
||||
const secondsPerBeat=60.0/(parseInt(bpmRef.current)||120);// Metronome Click Scheduler
|
||||
while(true){const beatNum=nextMetronomeBeatRef.current;const elapsedBeats=beatNum-recordingStartTimeRef.current/secondsPerBeat;const beatTime=startAudioTimeRef.current+elapsedBeats*secondsPerBeat;if(beatTime<audioCtx.currentTime+lookahead){const isDownbeat=beatNum%4===0;playMetronomeClick(beatTime,isDownbeat);nextMetronomeBeatRef.current++;}else{break;}}// Throttled Live Preview compilation (every 200ms)
|
||||
const now=Date.now();if(now-lastTempCompileTimeRef.current>200){lastTempCompileTimeRef.current=now;// Audio preview
|
||||
|
||||
@@ -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=202608242300" defer></script>
|
||||
<script src="/static/js/app.precompiled.js?v=202608241630" defer></script>
|
||||
<link rel="stylesheet" href="/static/css/styles.css?v=202607271016">
|
||||
<style>
|
||||
:root {
|
||||
|
||||
@@ -162,6 +162,11 @@ int run_realtime_fx_loop(const std::string& jobPath, const std::string& shmName,
|
||||
// cuối. (winmm đã link sẵn — CMakeLists fx_vst_bridge.)
|
||||
#ifdef _WIN32
|
||||
timeBeginPeriod(1);
|
||||
// Thread audio realtime ABOVE_NORMAL: khong bi tien trinh NORMAL khac
|
||||
// (fx-gui spawn + VST load instance thu 2, native VSTi GUI open...) preempt
|
||||
// -> underrun -> am bi "bop nghet"/ngat quan. ChannelWorker (main.cpp)
|
||||
// da dung THREAD_PRIORITY_ABOVE_NORMAL.
|
||||
SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_ABOVE_NORMAL);
|
||||
#endif
|
||||
// Chờ chain ĐẦU TIÊN được worker build xong (gen>=1) trước khi READY.
|
||||
// setChain() là ASYNC — worker thread load VST3 + preset mất 1-4s; nếu set
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Reference in New Issue
Block a user