fix(playback): het fade-out dai dang khi stop - restore gain LUON (ke ca stale/play moi trong cua so fade) qua pending fade session, huy fade ngay khi play moi (het dip 200ms); sync 6 ban JS + index.html ?v=202608181840

This commit is contained in:
2026-08-18 19:53:59 +07:00
parent 4ddaf1f26a
commit be8af7a6ee
3 changed files with 38 additions and 19 deletions
+30 -10
View File
@@ -279,6 +279,22 @@ const stopAllNativeSfNotes = () => {
// Epoch guard: stop/rebuild tăng _nativePlayEpoch → mọi render pending của
// lần play cũ resolve nhưng bị skip (không duplicate schedule).
let _nativePlayEpoch = 0;
// V17: fade session dang treo (gain ramp FADE_MS) — restore khi play moi /
// stop moi / doHardStop de gain KHONG bi ket ~0.0001 (root cause "am bi fade out").
let _pendingFadeSession = null;
const restorePendingFadeSession = () => {
if (!_pendingFadeSession) return;
try {
const t = getAudioContext().currentTime;
_pendingFadeSession.fadedGains.forEach(g => {
try { g.node.gain.cancelScheduledValues(t); g.node.gain.setValueAtTime(g.prev, t); } catch (e) {}
});
if (_pendingFadeSession.bridgeGainNode) {
try { _pendingFadeSession.bridgeGainNode.gain.cancelScheduledValues(t); _pendingFadeSession.bridgeGainNode.gain.setValueAtTime(1.0, t); } catch (e) {}
}
} catch (e) {}
_pendingFadeSession = null;
};
const _nativeTrackUrls = {};
const _nativeTrackPending = {};
// ── MIDI overlap cleanup + polyphony gain (spec midi_polyphony_loudness) ─
@@ -21487,6 +21503,7 @@ const App = () => {
const startSubTabPlayback = (st, offsetWallTime) => {
const context = getAudioContext();
_nativePlayEpoch++; // new play — vô hiệu doHardStop đang fade
restorePendingFadeSession(); // V17: huy fade dang treo — replay khong bi dip gain
if (!st.buffer) {
st.buffer = context.createBuffer(1, 128, context.sampleRate);
}
@@ -22679,6 +22696,7 @@ const App = () => {
const startTrackPlayback = offsetTime => {
const context = getAudioContext();
_nativePlayEpoch++; // new play — vô hiệu doHardStop đang fade (play trong cửa sổ 80ms)
restorePendingFadeSession(); // V17: huy fade dang treo — replay khong bi dip gain
// D2: bridge active -> báo transport PLAY (bridge flush note-off cũ + đồng
// bộ timeline; A13 C++ xử lý arg1=playheadSamples sau này).
if (window.NativeBridgeService && window.NativeBridgeService.isBridgeConnected) {
@@ -23129,6 +23147,7 @@ const App = () => {
if (st.type !== 'PIANO_ROLL') return;
const context = getAudioContext();
_nativePlayEpoch++; // new play — vô hiệu doHardStop đang fade
restorePendingFadeSession(); // V17: huy fade dang treo — replay khong bi dip gain
// V10: piano roll loop restart cũng gọi stopAllPlayback() trước (STOP#1 →
// bridge transportStopped=true) → mọi note-on bị drop nếu không PLAY lại.
// Hủy STOP#2 (150ms) + báo PLAY để bridge nhận note mới (loop liên tục).
@@ -23363,6 +23382,10 @@ const App = () => {
} catch (e) {}
let bridgeGainNode = null;
if (fade && ctx) {
// V17: restore fade session cu TRUOC khi capture prev — double-stop trong
// cua so fade: neu khong, session cu stale -> bo restore, session moi capture
// prev giua ramp (vd 0.5) -> restore sai -> gain ket.
restorePendingFadeSession();
try {
const now = ctx.currentTime;
capturedNodes.forEach(n => {
@@ -23383,6 +23406,7 @@ const App = () => {
}
}
} catch (e) { console.warn('[Stop] fade setup error:', e); }
_pendingFadeSession = { fadedGains, bridgeGainNode, stopEpoch };
}
const doHardStop = () => {
try {
@@ -23393,6 +23417,12 @@ const App = () => {
capturedNodes.forEach(n => {
if (n.fxStopFn) { try { n.fxStopFn(); } catch (e) {} }
});
// V17: restore gain LUON — play moi trong cua so fade (stale) truoc day bo qua
// restore -> track/bridge gain ket 0.0001 -> moi am sau bi fade-out dai dang.
// Chi skip khi co fade session MOI hon (session nay het pending — session moi tu restore).
if (_pendingFadeSession && _pendingFadeSession.stopEpoch === stopEpoch) {
restorePendingFadeSession();
}
if (stale) return; // play mới đang chạy — không dừng bridge/SF của nó
stopMidiCapture();
stopAllNativeSfNotes(); stopAllNativeVstNotes();
@@ -23418,16 +23448,6 @@ const App = () => {
} catch (e) {
console.warn('[Stop] stopAllPlayback error:', e);
}
// Restore gain về giá trị cũ — play lần sau không bị câm/đổi volume.
try {
const t = getAudioContext().currentTime;
fadedGains.forEach(g => {
try { g.node.gain.cancelScheduledValues(t); g.node.gain.setValueAtTime(g.prev, t); } catch (e) {}
});
if (bridgeGainNode) {
try { bridgeGainNode.gain.cancelScheduledValues(t); bridgeGainNode.gain.setValueAtTime(1.0, t); } catch (e) {}
}
} catch (e) {}
};
if (fade && ctx) {
try { setTimeout(doHardStop, FADE_MS); } catch (e) { doHardStop(); }