fix(playback): stop fade-out 80ms khi nhan Stop/Pause - ramp gain track + bridge gain xuong 0 truoc hard-stop, guard play-gen tranh STOP tre giet play moi; sync 5 ban JS + index.html ?v=202608181818
This commit is contained in:
+92
-35
@@ -21486,6 +21486,7 @@ const App = () => {
|
||||
// ── Update Playhead ──
|
||||
const startSubTabPlayback = (st, offsetWallTime) => {
|
||||
const context = getAudioContext();
|
||||
_nativePlayEpoch++; // new play — vô hiệu doHardStop đang fade
|
||||
if (!st.buffer) {
|
||||
st.buffer = context.createBuffer(1, 128, context.sampleRate);
|
||||
}
|
||||
@@ -22677,6 +22678,7 @@ const App = () => {
|
||||
|
||||
const startTrackPlayback = offsetTime => {
|
||||
const context = getAudioContext();
|
||||
_nativePlayEpoch++; // new play — vô hiệu doHardStop đang fade (play trong cửa sổ 80ms)
|
||||
// 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) {
|
||||
@@ -23126,6 +23128,7 @@ const App = () => {
|
||||
const schedulePianoRollMidi = (st, offsetSeconds, notesOverride) => {
|
||||
if (st.type !== 'PIANO_ROLL') return;
|
||||
const context = getAudioContext();
|
||||
_nativePlayEpoch++; // new play — vô hiệu doHardStop đang fade
|
||||
// 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).
|
||||
@@ -23236,13 +23239,13 @@ const App = () => {
|
||||
// tab): Space phải STOP trước — không được play sub-tab (user: trong
|
||||
// PIANO ROLL không phát hiện MIDI đang play → space 2 lần mới stop).
|
||||
if (isPlaying) {
|
||||
stopAllPlayback();
|
||||
stopAllPlayback(true); // user stop — fade out
|
||||
setSubTabs(prev => prev.map(s => s.isPlaying ? { ...s, isPlaying: false } : s));
|
||||
setIsPlaying(false);
|
||||
return;
|
||||
}
|
||||
if (st.isPlaying) {
|
||||
stopAllPlayback();
|
||||
stopAllPlayback(true); // user stop — fade out
|
||||
setSubTabs(prev => prev.map(s => s.id === activeTab ? {
|
||||
...s,
|
||||
isPlaying: false
|
||||
@@ -23275,7 +23278,7 @@ const App = () => {
|
||||
}
|
||||
const context = getAudioContext();
|
||||
if (isPlaying) {
|
||||
stopAllPlayback();
|
||||
stopAllPlayback(true); // user stop — fade out
|
||||
} else {
|
||||
startOffsetTimeRef.current = currentTime;
|
||||
startAudioTimeRef.current = context.currentTime;
|
||||
@@ -23327,9 +23330,9 @@ const App = () => {
|
||||
}, 60);
|
||||
};
|
||||
const handlePause = () => {
|
||||
if (isPlaying || subTabs.some(s => s.isPlaying)) stopAllPlayback();
|
||||
if (isPlaying || subTabs.some(s => s.isPlaying)) stopAllPlayback(true);
|
||||
};
|
||||
const stopAllPlayback = () => {
|
||||
const stopAllPlayback = (fade) => {
|
||||
try {
|
||||
isPlayingRef.current = false;
|
||||
_nativePlayEpoch++; // hủy mọi render/decode pending của lần play cũ
|
||||
@@ -23338,16 +23341,91 @@ const App = () => {
|
||||
bridgeNoteEpochRef.current++;
|
||||
try { clearAllPendingNoteOffs(); } catch (e) {}
|
||||
try { if (subTabsRef.current) subTabsRef.current = subTabsRef.current.map(s => ({ ...s, isPlaying: false })); } catch (e) {}
|
||||
activeSourcesRef.current.forEach(src => {
|
||||
try {
|
||||
src.stop();
|
||||
} catch (e) { }
|
||||
});
|
||||
// Fade-out khi user nhấn Stop/Pause (fade=true): ramp gain track + bridge
|
||||
// xuống 0 trong FADE_MS rồi mới hard-stop — hết click, âm ngân mượt.
|
||||
// Loop restart/seek (fade=false) giữ stop tức thì như cũ (không fade).
|
||||
// Lưu ref trước khi clear để doHardStop trễ không đụng playback mới.
|
||||
const ctx = getAudioContext();
|
||||
const stopEpoch = _nativePlayEpoch;
|
||||
const FADE_MS = 80;
|
||||
const capturedSources = activeSourcesRef.current.slice();
|
||||
const capturedNodes = Object.values(activeTrackNodesRef.current);
|
||||
activeSourcesRef.current = [];
|
||||
Object.values(activeTrackNodesRef.current).forEach(n => {
|
||||
if (n.fxStopFn) { try { n.fxStopFn(); } catch (e) {} }
|
||||
});
|
||||
activeTrackNodesRef.current = {};
|
||||
const fadedGains = [];
|
||||
let bridgeGainNode = null;
|
||||
if (fade && ctx) {
|
||||
try {
|
||||
const now = ctx.currentTime;
|
||||
capturedNodes.forEach(n => {
|
||||
if (n && n.gainNode && n.gainNode.gain) {
|
||||
fadedGains.push({ node: n.gainNode, prev: n.gainNode.gain.value });
|
||||
n.gainNode.gain.cancelScheduledValues(now);
|
||||
n.gainNode.gain.setValueAtTime(n.gainNode.gain.value, now);
|
||||
n.gainNode.gain.linearRampToValueAtTime(0.0001, now + FADE_MS / 1000);
|
||||
}
|
||||
});
|
||||
if (window.BridgeAudioNode && window.BridgeAudioNode.getOutputNode) {
|
||||
const bg = window.BridgeAudioNode.getOutputNode();
|
||||
if (bg && bg.gain) {
|
||||
bridgeGainNode = bg;
|
||||
bg.gain.cancelScheduledValues(now);
|
||||
bg.gain.setValueAtTime(bg.gain.value, now);
|
||||
bg.gain.linearRampToValueAtTime(0.0001, now + FADE_MS / 1000);
|
||||
}
|
||||
}
|
||||
} catch (e) { console.warn('[Stop] fade setup error:', e); }
|
||||
}
|
||||
const doHardStop = () => {
|
||||
try {
|
||||
const stale = _nativePlayEpoch !== stopEpoch; // play mới bắt đầu trong lúc fade
|
||||
capturedSources.forEach(src => {
|
||||
try { src.stop(); } catch (e) { }
|
||||
});
|
||||
capturedNodes.forEach(n => {
|
||||
if (n.fxStopFn) { try { n.fxStopFn(); } catch (e) {} }
|
||||
});
|
||||
if (stale) return; // play mới đang chạy — không dừng bridge/SF của nó
|
||||
stopMidiCapture();
|
||||
stopAllNativeSfNotes(); stopAllNativeVstNotes();
|
||||
// D2/D6: bridge active -> transport STOP (bridge flush toàn pitch, hết
|
||||
// âm ngân tức thì; A13 C++ flush note-off).
|
||||
if (window.NativeBridgeService && window.NativeBridgeService.isBridgeConnected) {
|
||||
try { window.NativeBridgeService.transport('stop'); } catch (e) {}
|
||||
// V8 bug 3: transport stop thu 2 sau 150ms — note-on timer guard sync
|
||||
// qua React effect (cham) van bay toi bridge sau STOP → retrigger am
|
||||
// treo. V10: timer lưu ref — startTrackPlayback (loop restart) hủy
|
||||
// khi PLAY, tránh STOP#2 giết loop tiếp theo (VSTi chỉ play 1 vòng).
|
||||
if (bridgeStopRetryTimerRef.current) { clearTimeout(bridgeStopRetryTimerRef.current); }
|
||||
bridgeStopRetryTimerRef.current = setTimeout(() => {
|
||||
bridgeStopRetryTimerRef.current = null;
|
||||
try { window.NativeBridgeService.transport('stop'); } catch (e) {}
|
||||
}, 150);
|
||||
}
|
||||
if (window.SonicSF) {
|
||||
try { window.SonicSF.stopAll(); } catch (e) { console.warn('[Stop] stopAll error:', e); }
|
||||
// Dừng triệt để: noteoff từng note + hủy scheduled note-on (hết âm stuck)
|
||||
if (window.SonicSF.panic) { try { window.SonicSF.panic(); } catch (e) { console.warn('[Stop] panic error:', e); } }
|
||||
}
|
||||
} 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(); }
|
||||
} else {
|
||||
doHardStop();
|
||||
}
|
||||
// Clear VU activity NGAY: hết âm → VU tắt tức thì (không decay 0.35s từ
|
||||
// âm cũ — user 06:45: tắt âm MAIN items khi vào SECTION-TAB → VU main
|
||||
// items phải tắt theo, không "diễn" tiếp).
|
||||
@@ -23355,27 +23433,6 @@ const App = () => {
|
||||
// Clear luôn counter note đang giữ — nếu không, sau stop (âm đã dừng)
|
||||
// tick vẫn thấy heldCnt > 0 → giữ peak → VU dính mãi (user bug 08:08).
|
||||
try { heldMidiNotesRef.current = {}; } catch (e) { }
|
||||
stopMidiCapture();
|
||||
stopAllNativeSfNotes(); stopAllNativeVstNotes();
|
||||
// D2/D6: bridge active -> transport STOP (bridge flush toàn pitch, hết âm
|
||||
// ngân tức thì; A13 C++ flush note-off).
|
||||
if (window.NativeBridgeService && window.NativeBridgeService.isBridgeConnected) {
|
||||
try { window.NativeBridgeService.transport('stop'); } catch (e) {}
|
||||
// V8 bug 3: transport stop thu 2 sau 150ms — note-on timer guard sync qua
|
||||
// React effect (cham) van bay toi bridge sau STOP → retrigger am treo.
|
||||
// V10: timer lưu ref — startTrackPlayback (loop restart) hủy khi PLAY,
|
||||
// tránh STOP#2 giết loop tiếp theo (VSTi chỉ play 1 vòng).
|
||||
if (bridgeStopRetryTimerRef.current) { clearTimeout(bridgeStopRetryTimerRef.current); }
|
||||
bridgeStopRetryTimerRef.current = setTimeout(() => {
|
||||
bridgeStopRetryTimerRef.current = null;
|
||||
try { window.NativeBridgeService.transport('stop'); } catch (e) {}
|
||||
}, 150);
|
||||
}
|
||||
if (window.SonicSF) {
|
||||
try { window.SonicSF.stopAll(); } catch (e) { console.warn('[Stop] stopAll error:', e); }
|
||||
// Dừng triệt để: noteoff từng note + hủy scheduled note-on (hết âm stuck)
|
||||
if (window.SonicSF.panic) { try { window.SonicSF.panic(); } catch (e) { console.warn('[Stop] panic error:', e); } }
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('[Stop] stopAllPlayback error:', e);
|
||||
}
|
||||
@@ -30990,7 +31047,7 @@ STRICT CONSTRAINTS:
|
||||
onSaveNotes: handleSaveMidiNotes,
|
||||
setSubTabs: setSubTabs,
|
||||
onPlayPause: handlePlayPause,
|
||||
onStop: stopAllPlayback,
|
||||
onStop: () => stopAllPlayback(true),
|
||||
isPlaying: isPlaying,
|
||||
playPreviewNote: playMidiPreviewNote,
|
||||
showToast: showToast,
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -50,7 +50,7 @@
|
||||
<script src="/static/js/services/midiExtractor.js?v=202607281052"></script>
|
||||
<script src="/static/js/services/promptTemplateManager.js?v=202607281039"></script>
|
||||
<script src="/static/js/services/undoRedoEngine.js?v=202607290941"></script>
|
||||
<script src="/static/js/app.precompiled.js?v=202608181807" defer></script>
|
||||
<script src="/static/js/app.precompiled.js?v=202608181818" defer></script>
|
||||
<link rel="stylesheet" href="/static/css/styles.css?v=202607271016">
|
||||
<style>
|
||||
:root {
|
||||
|
||||
Reference in New Issue
Block a user