M7-M16: VST FX GUI native embed hoàn thiện — fix click-2-lần chuyển slot, anchor lần mở đầu, taskbar icon

- app.jsx: masterGuiOpenRef sync truoc setState — guard chuyen slot doc ref, het race closure (click 1 lan chuyen slot)
- JuceFxGuiHost.cpp: M14 applyAnchor truoc setVisible (het flash giua man hinh); M15 clear WS_EX_APPWINDOW + set TOOLWINDOW (het taskbar icon — JUCE set APPWINDOW 0x40000 thang TOOLWINDOW); M9 owner DAW + M9d skip applyOwner khi an
- JuceFxLoop.cpp: M16 bo clearAnchor khi ed.open (retry ed.open ~700ms xoa rect fresh -> window moi khong anchor duoc lan dau mo panel); chi clear khi Close
- JuceFxEngine/app.jsx/plugins.py/fx_realtime.py: editor session realtime, anchor rect routing, retry ed.open
- Rebuild engine PyInstaller + juce_fx_bridge cmake; binaries dong bo 5 noi (bridge 7464a7ef)
This commit is contained in:
2026-09-06 19:45:14 +07:00
parent ec2c925d09
commit ef521d9981
20 changed files with 2040 additions and 461 deletions
+658 -97
View File
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
+2
View File
@@ -96,6 +96,8 @@ window.API_BASE_URL = (window.API_BASE_URL || window.location.origin).replace(/\
closeFxGui: (payload) => apiRequest('/api/v1/plugins/fx-gui/close', { method: 'POST', body: JSON.stringify(payload) }),
hideFxGui: (payload) => apiRequest('/api/v1/plugins/fx-gui/hide', { method: 'POST', body: JSON.stringify(payload) }),
showFxGui: (payload) => apiRequest('/api/v1/plugins/fx-gui/show', { method: 'POST', body: JSON.stringify(payload) }),
// M7: anchor GUI native vào rect panel (px màn hình vật lý) — C++ SetWindowPos khi rect đổi
rectFxGui: (payload) => apiRequest('/api/v1/plugins/fx-gui/rect', { method: 'POST', body: JSON.stringify(payload) }),
soundfontRender: (payload) => apiRequest('/api/v1/plugins/soundfont-render', { method: 'POST', body: JSON.stringify(payload) }),
// Thư viện preset VST3 (.vstpreset) — cầu nối preset → pedalboard
listPresets: () => apiRequest('/api/v1/presets', { method: 'GET' }),
+25 -6
View File
@@ -45,6 +45,7 @@ class SfFxRealtimeProcessor extends AudioWorkletProcessor {
this.playing = false; // false → im lặng (đang chờ đủ FILL)
this.hasStarted = false; // đã phát block đầu chưa — FILL chỉ chờ ở lần khởi đầu
this.flushPending = false; // V24: đang chờ engine flush (resume sau underrun) — drop block stale
this.stopped = false; // Bug1 echo: sau flush_for_stop dung het output — chi resume khi main gui clear_queue moi
this.resumeFadeIn = false; // V24: interruption đã xử lí (clear+flush) — resume sẽ fade-in từ 0
this.FADE = 512; // fade-in 512 mẫu (~11ms @48k) khi bắt đầu phát/resume — hết click
this.outGain = 0; // gain phát hiện tại (0→1 trong fade)
@@ -58,7 +59,7 @@ class SfFxRealtimeProcessor extends AudioWorkletProcessor {
this.wrapFadeBlocks = -1; // V42: số block tail pass cũ còn lại sau clear_queue('loop') — đếm ngược để crossfade đúng seam tail→head
this.latencySamples = 0; // G3: latency chain thật (engine qua WS) — align wrap fade theo processed tail
this.wrapMode = false; // V45.3: dang trong wrap (clear_queue loop) — resume underrun bo flush + re-buffer nho
this.WRAP_FADE = 256; // V42: crossfade 256 mẫu (~5.3ms @48k) tại seam wrap — che click jump nội dung end→start (FX path)
this.WRAP_FADE = 2048; // V46.2: 256->2048 (~43ms @48k) — che hole engine restart (~10-16ms) + jump noi dung end→start tai seam (256 qua ngan = giät nhẹ)
this.fadeFromL = 0.0; // mẫu cố định khi fade-out underrun (fade tuyến tính tới 0)
this.fadeFromR = 0.0;
this.port.onmessage = (e) => {
@@ -86,11 +87,13 @@ class SfFxRealtimeProcessor extends AudioWorkletProcessor {
this.fadeFromL = this.lastOutL;
this.fadeFromR = this.lastOutR;
this.holdFadeLeft = this.HOLD_FADE * this.BLOCK;
this.stopped = true; // Bug1: drop moi 'out' sau stop — engine van render input dang fade + plugin tail; neu khong drop, queue day RESUME_FILL -> worklet tu resume phat tail = echo sau stop.
this.port.postMessage({ type: 'flush' });
return;
}
if (d && d.type === 'clear_queue') {
this.clearMode = d.mode; // V45.19: nho mode de tag fx_seek_first tai dbgPlayFirst
this.stopped = false; // Bug1: play/seek/wrap moi — cho phep nhan out tro lai
// V34: backlog guard o DAU handler (V33 dat sau mutation -> outQueue.length=0
// chay truoc -> guard luon return, clear van drop queue + fade + re-buffer moi
// lan = V31). Queue nho (< FILL 8 block) -> bo qua hoan toan (khong mutation,
@@ -125,8 +128,9 @@ class SfFxRealtimeProcessor extends AudioWorkletProcessor {
// dai hon roi vao nhanh resume wrap (bo flush + resumeFill 8).
const KEEP = 16;
this.wrapMode = true;
if (this.outQueue.length > KEEP) {
const head = this.outQueue.length - KEEP;
const _preQ = this.outQueue.length; // V46.1 instrument seam
if (_preQ > KEEP) {
const head = _preQ - KEEP;
this.outQueue.splice(0, head);
this.outPos = 0;
if (this.playing) {
@@ -136,7 +140,13 @@ class SfFxRealtimeProcessor extends AudioWorkletProcessor {
this.skipFromR = this.lastOutR;
}
}
this.wrapFadeBlocks = this.outQueue.length + Math.ceil((this.latencySamples || 0) / this.BLOCK);
// V46.2: countdown = DUNG so block tail (sau splice) — truoc day cong
// ceil(latency/BLOCK)~137 (estimate 365ms) => crossfade ban tre ~380ms
// (giua bar1) = vo hieu, seam that di qua khong crossfade. Pipeline lag
// THUC ~25ms (fx_out_first +8ms sau clear) chu khong phai VST latency.
this.wrapFadeBlocks = this.outQueue.length;
// V46.1 instrument seam: pre=queue truoc splice, keep=sau splice, lat=latency est, wfb=countdown toi seam
this.port.postMessage({ type: 'dbg', tag: 'fx_loop_clear', t: Math.round(currentTime * 1000), pre: _preQ, keep: this.outQueue.length, lat: this.latencySamples || 0, wfb: this.wrapFadeBlocks, playing: this.playing ? 1 : 0 });
return;
}
if (d.mode === 'seek') {
@@ -184,7 +194,7 @@ class SfFxRealtimeProcessor extends AudioWorkletProcessor {
// V24: đang chờ flush (resume sau underrun/stall) → drop block stale.
// Backlog engine gửi khi worklet im là audio CŨ (chơi lại → lag cộng dồn
// ~1s) — chỉ nhận block MỚI từ sau flush_done.
if (this.flushPending) return;
if (this.stopped || this.flushPending) return;
this.outQueue.push({ l: d.l, r: d.r });
if (this.dbgOutFirst) {
this.dbgOutFirst = false;
@@ -339,11 +349,20 @@ class SfFxRealtimeProcessor extends AudioWorkletProcessor {
// skip-flush o stopAllPlayback -> gap giua seam). Bo flush (round-trip)
// + re-buffer nho hon FILL 24 (~128ms) -> resume sau stall som hon
// (~43ms) -> gap wrap giam tu ~110-235ms xuong con vai chuc ms.
// V46: non-wrap underrun-stall (producer stall giua play, transport
// khong doi) — sau flush da bo backlog stale, cushion 8 block (~43ms,
// proven V45.4 seek/wrap) la du; FILL 24 (~128ms) lam moi underrun
// them ~60-130ms silence (recovery ~150-400ms -> ~90-130ms).
const _wrap = this.wrapMode;
this.outQueue.length = 0;
this.outPos = 0;
this.resumeFadeIn = true;
this.resumeFill = _wrap ? 8 : this.FILL;
// V46.3: wrap-context underrun — resume chi can 4 block (~10.7ms) khong
// phai 8 (~21ms): queue pass moi la audio LIEN TUC (bar1 da bat dau),
// khong con backlog stale de phong — cho 8 block them ~10ms gap seam
// (log 08:49 wrap2: q=4 tai wrap -> tail 10.7ms can, bar1 toi +26ms,
// resume 8-block -> gap ~35ms = giat lag). Non-wrap giu 8 (V46).
this.resumeFill = _wrap ? 4 : 8;
this.flushPending = _wrap ? false : true;
if (!_wrap) this.port.postMessage({ type: 'flush' });
i--;