M26: fix pdc=wet-lag FILL (10240) + sync display/anchor - het upbeat khi PWR ON giua play
- FX_RT_ROUNDTRIP_SAMPLES (12032) -> FX_RT_WET_LAG_SAMPLES (10240 = FILL 40 block) - fxRtApplyPdc + OFF-rebase + wake-rebase + bridge pos + playhead (main/sub/PR): tru latencyTotal (VST report 17536~365ms, content-latency that ~0) -> wet-lag - xoa diag M26b wake + M26 OFF (ph_on_*/ph_echo), giu ph_* logs guarded
This commit is contained in:
+20
-75
@@ -2149,29 +2149,6 @@ async function fxRtStart() {
|
||||
fxRt.inQueue = [];
|
||||
fxRt.outL = null;
|
||||
fxRt.outR = null;
|
||||
// M26b diag: PWR ON wake — snap pdcDelay/gains + hoi worklet state luc
|
||||
// wake, luc 'started' (wet bat dau) + 180/400/800ms sau: do wet-lag that,
|
||||
// delay node co ve 0 truoc crossfade khong. Bo diag o build fix.
|
||||
let _m26onWlArmed = false;
|
||||
const _snapOn = (tag, _sched) => {
|
||||
let _pd='na',_gd='na',_gw='na',_ps='na',_pa='na';
|
||||
const _b=masterBus, _c=getAudioContext();
|
||||
try { if(_b&&_b.pdcDelay) _pd=(_b.pdcDelay.delayTime.value*1000).toFixed(1); } catch(e){}
|
||||
try { if(_b&&_b.masterFxDryGain) _gd=_b.masterFxDryGain.gain.value.toFixed(4); } catch(e){}
|
||||
try { if(_b&&_b.masterFxGain) _gw=_b.masterFxGain.gain.value.toFixed(4); } catch(e){}
|
||||
try { _ps=typeof fxRt.processedSamples==='number'?fxRt.processedSamples:'na'; } catch(e){}
|
||||
try { _pa=typeof fxRt.processedAnchor==='number'?fxRt.processedAnchor:'na'; } catch(e){}
|
||||
_dbgLog(tag,'pdcMs='+_pd+' gDry='+_gd+' gWet='+_gw+' lat='+(fxRt.latencyTotal||0)+' ps='+_ps+' pa='+_pa+' act='+(fxRt.active?1:0)+' warm='+(fxRt.warm?1:0)+' faded='+(fxRt.fadedIn?1:0)+' ctx='+(_c?_c.currentTime.toFixed(3):'na'));
|
||||
try { if (fxRt.node && fxRt.node.port) fxRt.node.port.postMessage({ type: 'dbg_state', k: tag }); } catch(e){}
|
||||
if (_sched && _sched.length) _sched.forEach((_ms,_ix)=>setTimeout(()=>_snapOn('ph_on'+( _ix+1)),_ms));
|
||||
};
|
||||
try { if (fxRt.node && fxRt.node.port && !_m26onWlArmed) {
|
||||
_m26onWlArmed = true;
|
||||
const _wl = (ev) => { try { const d = ev.data; if (d && d.type === 'dbg_state_r') _dbgLog('ph_on_wl','k='+d.k+' t='+d.t+' q='+d.q+' play='+d.playing+' hs='+d.hasStarted+' sil='+d.silenced+' fp='+d.flushPending+' stp='+d.stopped+' wm='+d.wrapMode+' wfb='+d.wrapFadeBlocks+' lat='+d.latencySamples+' rfill='+d.resumeFill+' inC='+d.inCount+' outPos='+d.outPos+' fadeL='+d.fadeLeft+' holdL='+d.holdFadeLeft); } catch(e){} };
|
||||
fxRt.node.port.addEventListener('message', _wl);
|
||||
setTimeout(()=>{ try { if (fxRt.node && fxRt.node.port) fxRt.node.port.removeEventListener('message', _wl); } catch(e){} }, 2600);
|
||||
} } catch(e){}
|
||||
_snapOn('ph_on_a', null);
|
||||
// M19 Bug A fix: wake PWR ON sau warm-bypass. Trước đây chỉ flush engine
|
||||
// ring + fadedIn=true NGAY — nhưng worklet lúc bypass rời graph không
|
||||
// process: outQueue còn ~FILL block audio CŨ + hasStarted=true (không gửi
|
||||
@@ -2192,7 +2169,6 @@ async function fxRtStart() {
|
||||
try {
|
||||
if (ev.data && ev.data.type === 'started' && wToken === fxRt.startToken) {
|
||||
fxRt.fadedIn = true;
|
||||
try { _snapOn('ph_on_s', [180, 400, 800]); } catch (e) {}
|
||||
fxRtCrossfade(true, wToken);
|
||||
setTimeout(() => { if (wToken === fxRt.startToken) fxRtApplyPdc(); }, 250);
|
||||
}
|
||||
@@ -2658,13 +2634,15 @@ async function fxRtEnsureWarmForGui(path) {
|
||||
// Master chain (worklet → bridge VST3) trễ ~2 block round-trip + latency
|
||||
// plugin. Track bypass mastering đi dry path (dryInput→dryOutput→output) KHÔNG
|
||||
// qua worklet → lệch pha với mastered. fxRtApplyPdc trễ dry path đúng lượng
|
||||
// để align. FX_RT_ROUNDTRIP_SAMPLES là ước lượng — ponytail: đo RTT thật
|
||||
// (tag seq trên block) khi cần sample-accurate.
|
||||
// M18: FILL 40 (sf-fx-realtime.js V48) → RTT = batching 4 block (~7 block @48k
|
||||
// ≈ 1792) + FILL buffer 40 block (10240) = 47 block ≈ 12032 (~251ms @48k). Giữ
|
||||
// đồng bộ với sf-fx-realtime.js. FX_RT_START_EST_MS: ước lượng khởi đầu khi
|
||||
// chưa có phép đo (fx_play_first) — dùng cho freeze playhead thay V39 0.31s.
|
||||
const FX_RT_ROUNDTRIP_SAMPLES = 12032;
|
||||
// de align. M26: pdc = wet-lag FILL - content nghe = S(t-FILL); M26b do onset
|
||||
// +208-221ms = FILL 40 block (10240 @48k ~213ms) + RTT nho. KHONG tin VST
|
||||
// report (Ozone bao 17536 ~365ms nhung content-latency that ~0). ponytail:
|
||||
// neu plugin content-latency that L>0 -> pdc = FILL + L (display-lat giu FILL
|
||||
// vi processed da gom L).
|
||||
// Dong bo voi sf-fx-realtime.js: FILL 40 block (10240 @48k). FX_RT_START_EST_MS:
|
||||
// uoc luong khoi dau khi chua co phep do (fx_play_first) - dung cho freeze
|
||||
// playhead thay V39 0.31s.
|
||||
const FX_RT_WET_LAG_SAMPLES = 10240;
|
||||
const FX_RT_START_EST_MS = 450;
|
||||
// G3: anchor processed — playhead = startOffset + (processed - anchor - latency)/sr
|
||||
// (da tru latency -> bam vi tri mau NGHE DUOC; pre-roll tu giu tai startOffset —
|
||||
@@ -2687,11 +2665,10 @@ function fxRtApplyPdc() {
|
||||
const ctx = getAudioContext();
|
||||
const bus = masterBus;
|
||||
if (!ctx || !bus || !bus.pdcDelay) return;
|
||||
// M23 Bug C: bo +FX_RT_ROUNDTRIP_SAMPLES — latencyTotal (bridge 'latency'
|
||||
// frame) DA gom round-trip that (log M21/M22: display active = wall - lat).
|
||||
// +12032 double-count -> dry path delay > wet lag ~250ms -> crossfade lech pha
|
||||
// (scratch). ponytail: neu engine pipeline doi RTT that -> do lai o day.
|
||||
const l = (fxRt && fxRt.active) ? (fxRt.latencyTotal || 0) : 0;
|
||||
// M26: pdc = wet-lag FILL 10240 (~213ms) - content nghe = S(t-FILL); VST
|
||||
// report (17536) la plugin buffer, content-latency that ~0 (M26b do onset).
|
||||
// ponytail: neu content-latency that L>0 -> FX_RT_WET_LAG_SAMPLES + L.
|
||||
const l = (fxRt && fxRt.active) ? FX_RT_WET_LAG_SAMPLES : 0;
|
||||
try { bus.pdcDelay.delayTime.setTargetAtTime(l / (ctx.sampleRate || 44100), ctx.currentTime, 0.05); } catch (e) {}
|
||||
}
|
||||
|
||||
@@ -21022,10 +20999,10 @@ const App = () => {
|
||||
const _off2 = startOffsetTimeRef.current || 0;
|
||||
const _at2 = startAudioTimeRef.current || _c2.currentTime;
|
||||
const _eng2 = (typeof fxRt.processedSamples === 'number' && fxRt.processedAnchor != null)
|
||||
? Math.max(_off2, _off2 + (fxRt.processedSamples - fxRt.processedAnchor - (fxRt.latencyTotal || 0)) / _sr2)
|
||||
? Math.max(_off2, _off2 + (fxRt.processedSamples - fxRt.processedAnchor - FX_RT_WET_LAG_SAMPLES) / _sr2)
|
||||
: null;
|
||||
const _wall2 = _off2 + (_c2.currentTime - _at2);
|
||||
const _content2 = (_eng2 !== null) ? _eng2 : (_wall2 - (fxRt.latencyTotal || 0) / _sr2);
|
||||
const _content2 = (_eng2 !== null) ? _eng2 : (_wall2 - FX_RT_WET_LAG_SAMPLES / _sr2);
|
||||
startOffsetTimeRef.current = _content2;
|
||||
startAudioTimeRef.current = _c2.currentTime;
|
||||
_dbgLog('ph_offhold', 'cur=' + (currentTimeRef.current == null ? 'na' : currentTimeRef.current.toFixed(3)) + ' wall=' + _wall2.toFixed(3) + ' content=' + _content2.toFixed(3) + ' off=' + startOffsetTimeRef.current.toFixed(3) + ' at=' + startAudioTimeRef.current.toFixed(3) + ' ctx=' + _c2.currentTime.toFixed(3) + ' lat=' + (fxRt.latencyTotal || 0) + ' hold=1');
|
||||
@@ -21034,38 +21011,6 @@ const App = () => {
|
||||
}
|
||||
} catch (e) { try { _dbgLog('ph_offhold', 'err ' + String(e)); } catch (e2) {} }
|
||||
fxRtWarmBypass();
|
||||
// M26 diag: PWR OFF mid-play — snap pdcDelay/gains + hoi worklet
|
||||
// state tai t0/+180/+400/+800ms de do: pdc thuc te khi dry mo,
|
||||
// wet tail song bao lau, gains ramp ra sao. Bo diag o build fix.
|
||||
if (_plNow) {
|
||||
const _busB = masterBus;
|
||||
const _ctxB = getAudioContext();
|
||||
const _snapB = (k) => {
|
||||
let _pdB = 'na', _gdB = 'na', _gwB = 'na', _psB = 'na', _paB = 'na';
|
||||
try { if (_busB && _busB.pdcDelay) _pdB = (_busB.pdcDelay.delayTime.value * 1000).toFixed(1); } catch (e) {}
|
||||
try { if (_busB && _busB.masterFxDryGain) _gdB = _busB.masterFxDryGain.gain.value.toFixed(4); } catch (e) {}
|
||||
try { if (_busB && _busB.masterFxGain) _gwB = _busB.masterFxGain.gain.value.toFixed(4); } catch (e) {}
|
||||
try { _psB = typeof fxRt.processedSamples === 'number' ? fxRt.processedSamples : 'na'; } catch (e) {}
|
||||
try { _paB = typeof fxRt.processedAnchor === 'number' ? fxRt.processedAnchor : 'na'; } catch (e) {}
|
||||
const _oB = startOffsetTimeRef.current || 0;
|
||||
const _aB = startAudioTimeRef.current || 0;
|
||||
const _tB = _ctxB ? _ctxB.currentTime : 0;
|
||||
_dbgLog('ph_echo' + k, 'pdcMs=' + _pdB + ' gDry=' + _gdB + ' gWet=' + _gwB + ' lat=' + (fxRt.latencyTotal || 0) + ' ps=' + _psB + ' pa=' + _paB + ' cur=' + (currentTimeRef.current == null ? 'na' : currentTimeRef.current.toFixed(3)) + ' off=' + _oB.toFixed(3) + ' wall=' + (_oB + _tB - _aB).toFixed(3) + ' ctx=' + _tB.toFixed(3));
|
||||
try { if (fxRt.node && fxRt.node.port) fxRt.node.port.postMessage({ type: 'dbg_state', k: k }); } catch (e) {}
|
||||
};
|
||||
const _wlB = (ev) => {
|
||||
try {
|
||||
const d = ev.data;
|
||||
if (d && d.type === 'dbg_state_r') {
|
||||
_dbgLog('ph_echo_wl', 'k=' + d.k + ' t=' + d.t + ' q=' + d.q + ' play=' + d.playing + ' hs=' + d.hasStarted + ' sil=' + d.silenced + ' fp=' + d.flushPending + ' stp=' + d.stopped + ' wm=' + d.wrapMode + ' wfb=' + d.wrapFadeBlocks + ' lat=' + d.latencySamples + ' rfill=' + d.resumeFill + ' inC=' + d.inCount + ' outPos=' + d.outPos + ' fadeL=' + d.fadeLeft + ' holdL=' + d.holdFadeLeft);
|
||||
}
|
||||
} catch (e) {}
|
||||
};
|
||||
try { if (fxRt.node && fxRt.node.port) fxRt.node.port.addEventListener('message', _wlB); } catch (e) {}
|
||||
_snapB(0);
|
||||
[180, 400, 800].forEach((_msB, _iB) => setTimeout(() => _snapB(_iB + 1), _msB));
|
||||
setTimeout(() => { try { if (fxRt.node && fxRt.node.port) fxRt.node.port.removeEventListener('message', _wlB); } catch (e) {} }, 1600);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
fxRtStop();
|
||||
@@ -22090,7 +22035,7 @@ const App = () => {
|
||||
const _contentNow = (startOffsetTimeRef.current || 0) + (_c.currentTime - (startAudioTimeRef.current || _c.currentTime));
|
||||
startOffsetTimeRef.current = _contentNow;
|
||||
startAudioTimeRef.current = _c.currentTime;
|
||||
if (_pa !== null) fxRt.processedAnchor = _pa - Math.round(fxRt.latencyTotal || 0);
|
||||
if (_pa !== null) fxRt.processedAnchor = _pa - FX_RT_WET_LAG_SAMPLES;
|
||||
fxRtWarmPlayHold = false;
|
||||
} else {
|
||||
// (M22) rebase wall — khi khong OFF-giua-play (warm tu stop), off/at la
|
||||
@@ -25268,7 +25213,7 @@ const App = () => {
|
||||
if (fxRt && fxRt.active && typeof fxRt.processedSamples === 'number' && fxRt.processedAnchor != null) {
|
||||
_posSamples = Math.max(
|
||||
Math.floor(startOffsetTimeRef.current * (_pctx.sampleRate || 44100)),
|
||||
Math.floor(startOffsetTimeRef.current * (_pctx.sampleRate || 44100) + (fxRt.processedSamples - fxRt.processedAnchor - (fxRt.latencyTotal || 0)))
|
||||
Math.floor(startOffsetTimeRef.current * (_pctx.sampleRate || 44100) + (fxRt.processedSamples - fxRt.processedAnchor - FX_RT_WET_LAG_SAMPLES))
|
||||
);
|
||||
} else {
|
||||
const _elapsed = _pctx.currentTime - (startAudioTimeRef.current || _pctx.currentTime);
|
||||
@@ -25534,7 +25479,7 @@ const App = () => {
|
||||
const context = getAudioContext();
|
||||
let _ph = startOffsetTimeRef.current + (context.currentTime - startAudioTimeRef.current);
|
||||
if (fxRt && fxRt.active && typeof fxRt.processedSamples === 'number' && fxRt.processedAnchor != null) {
|
||||
_ph = startOffsetTimeRef.current + (fxRt.processedSamples - fxRt.processedAnchor - (fxRt.latencyTotal || 0)) / (context.sampleRate || 44100);
|
||||
_ph = startOffsetTimeRef.current + (fxRt.processedSamples - fxRt.processedAnchor - FX_RT_WET_LAG_SAMPLES) / (context.sampleRate || 44100);
|
||||
}
|
||||
let _local = _ph;
|
||||
const _itemInfo = getPianoRollItemInfo(st);
|
||||
@@ -25562,7 +25507,7 @@ const App = () => {
|
||||
let wallTime = startOffsetTimeRef.current + elapsed;
|
||||
const bufferPos = startBufferOffsetRef.current + elapsed * speedFactor;
|
||||
if (fxRt && fxRt.active && typeof fxRt.processedSamples === 'number' && fxRt.processedAnchor != null) {
|
||||
wallTime = startOffsetTimeRef.current + (fxRt.processedSamples - fxRt.processedAnchor - (fxRt.latencyTotal || 0)) / (context.sampleRate || 44100);
|
||||
wallTime = startOffsetTimeRef.current + (fxRt.processedSamples - fxRt.processedAnchor - FX_RT_WET_LAG_SAMPLES) / (context.sampleRate || 44100);
|
||||
}
|
||||
|
||||
// Loop sub-tab selection (bufferPos is buffer-time)
|
||||
@@ -25640,7 +25585,7 @@ const App = () => {
|
||||
const _playheadSec = (_fxRtSeekFreeze && fxRt && fxRt.active)
|
||||
? startOffsetTimeRef.current
|
||||
: ((fxRt && fxRt.active && typeof fxRt.processedSamples === 'number' && fxRt.processedAnchor != null)
|
||||
? Math.max(startOffsetTimeRef.current, startOffsetTimeRef.current + (fxRt.processedSamples - fxRt.processedAnchor - (fxRt.latencyTotal || 0)) / (context.sampleRate || 44100))
|
||||
? Math.max(startOffsetTimeRef.current, startOffsetTimeRef.current + (fxRt.processedSamples - fxRt.processedAnchor - FX_RT_WET_LAG_SAMPLES) / (context.sampleRate || 44100))
|
||||
: updatedTime);
|
||||
|
||||
// Selection Loop - LOOP_MAKER.md + LOOP_EDITOR_2.md §4.2
|
||||
|
||||
Reference in New Issue
Block a user