fix(playback): het note chong am echo o bass - KHONG huy note-off cu cung pitch (Bug 2 huy mui lam note cu khong bao gio tat); giu neu note-off cu bam truoc note-on moi, doi ve ngay truoc note-on neu overlap; sync 6 ban JS + index.html ?v=202608181900

This commit is contained in:
2026-08-18 20:13:53 +07:00
parent be8af7a6ee
commit 7bc96eebbc
3 changed files with 26 additions and 13 deletions
+22 -9
View File
@@ -118,25 +118,38 @@ const ensureSonicInstrument = (ctx) => {
// câm. Map pending theo key channel:pitch — schedule NOTE_OFF mới cùng key →
// hủy NOTE_OFF cũ (retrigger đúng, note mới ngân đủ duration).
const pendingNoteOffMap = {};
const scheduleNoteOff = (key, fn, delayMs) => {
// V18: note-off cũ cùng key KHÔNG bị hủy mù — hủy mù làm note cùng pitch lặp
// (bass: root note chạy liên tục) không bao giờ nhận note-off -> kêu dài chồng
// âm echo, không decay. So sánh thời điểm: note-off cũ bắn TRƯỚC note-on mới →
// giữ nguyên (decay đúng hạn). Note-off cũ bắn SAU note-on mới (overlap — Bug 2
// cũ) → dời về NGAY TRƯỚC note-on mới: note cũ decay sạch, note mới ngân đủ.
const scheduleNoteOff = (key, fn, noteOnDelayMs, durMs) => {
const offDelay = noteOnDelayMs + (durMs || 1000) + 30;
const prev = pendingNoteOffMap[key];
if (prev) { try { clearTimeout(prev); } catch (e) {} }
if (prev && prev.offDelay > noteOnDelayMs) {
try { clearTimeout(prev.timer); } catch (e) {}
const t = setTimeout(function () {
if (pendingNoteOffMap[key] && pendingNoteOffMap[key].timer === t) delete pendingNoteOffMap[key];
prev.fn();
}, Math.max(0, noteOnDelayMs - 5));
}
const timer = setTimeout(function () {
if (pendingNoteOffMap[key] === timer) delete pendingNoteOffMap[key];
if (pendingNoteOffMap[key] && pendingNoteOffMap[key].timer === timer) delete pendingNoteOffMap[key];
fn();
}, delayMs);
pendingNoteOffMap[key] = timer;
}, offDelay);
pendingNoteOffMap[key] = { timer: timer, fn: fn, offDelay: offDelay };
return timer;
};
const clearAllPendingNoteOffs = () => {
Object.keys(pendingNoteOffMap).forEach(function (k) {
try { clearTimeout(pendingNoteOffMap[k]); } catch (e) {}
const entry = pendingNoteOffMap[k];
if (entry) { try { clearTimeout(entry.timer); } catch (e) {} }
delete pendingNoteOffMap[k];
});
};
const fireCarlaNote = (ch, pitch, vel, delay, durMs) => {
setTimeout(function () { window.SonicCarlaMidi.noteOn(ch, pitch, vel); }, delay);
scheduleNoteOff('C:' + ch + ':' + pitch, function () { window.SonicCarlaMidi.noteOff(ch, pitch); }, delay + durMs + 30);
scheduleNoteOff('C:' + ch + ':' + pitch, function () { window.SonicCarlaMidi.noteOff(ch, pitch); }, delay, durMs);
};
// ⚠️ FIX (Bug 2): Carla mở ASYNC lúc play (ensureCarlaForPlayback) — nốt gửi
// trước khi OSC engine bind cổng bị mất → item câm dù keybed kêu. Nốt khi
@@ -22681,7 +22694,7 @@ const App = () => {
if (epoch !== bridgeNoteEpochRef.current) return;
if (!guardPlay()) return;
try { window.SonicMidiRouter.pushEvent({ cmd: 'NOTE_OFF', channel: trkId, pitch: pitch || 60, velocity: 0, percussion: isPerc }); } catch (e) {}
}, delayMs + (durMs || 1000) + 30);
}, delayMs, durMs || 1000);
return;
}
// Native VSTi (standalone browser, bridge inactive) → midi-render thay vì
@@ -23215,7 +23228,7 @@ const App = () => {
const carlaCh = (synthEngine && synthEngine.midi_channel !== undefined) ? synthEngine.midi_channel : mainCh;
const carlaPitch = note.pitch || 60;
setTimeout(function () { window.SonicCarlaMidi.noteOn(carlaCh, carlaPitch, carlaVel); }, carlaDelay);
scheduleNoteOff('C:' + carlaCh + ':' + carlaPitch, function () { window.SonicCarlaMidi.noteOff(carlaCh, carlaPitch); }, carlaDelay + durMs + 30);
scheduleNoteOff('C:' + carlaCh + ':' + carlaPitch, function () { window.SonicCarlaMidi.noteOff(carlaCh, carlaPitch); }, carlaDelay, durMs);
}
}
});