FIX: (1) MIDI item route CHỈ qua Carla bridge khi VSTi loaded (bỏ FluidSynth GM sai âm) ở mọi đường playback + ghosts; (2) updateSfRouting sync route gains nhánh gainNode + ensureMasteringRouting force-sync mọi node — preview MIDI keyboard qua mastering không cần bật/tắt power; (3) stopNote/panic dừng fallback oscillator theo channel:pitch — hết âm kêu liên tục sau khi thả phím

This commit is contained in:
2026-08-10 09:07:42 +00:00
parent 75795653c5
commit f492104869
5 changed files with 229 additions and 90 deletions
+47 -1
View File
@@ -21,6 +21,10 @@
let _pendingNoteTimers = [];
let _loadedFonts = {};
let _activeOscillators = {};
// Fallback oscillator theo 'channel:pitch' — để stopNote/stopAll dừng được
// note khi thả phím (trước đây stopNote không dừng oscillator → âm kêu
// liên tục dù đã thả phím khi FluidSynth chưa sẵn sàng / font load fail).
let _activeOscillatorsByKey = {};
let _gainNode = null;
let _pendingOutputDestination = null;
let _outputDestination = null; // cache đích route — dedupe swap dư giữa stream
@@ -479,6 +483,23 @@
// Hủy note đang CHỜ LOAD soundfont (chưa noteon) — trước đây note
// này vẫn bắn TRỄ sau khi thả phím → âm loop không dừng được.
if (_pendingNoteOns[key]) delete _pendingNoteOns[key];
// ⚠️ FIX: dừng fallback oscillator theo 'channel:pitch' — trước đây
// stopNote chỉ noteoff FluidSynth, KHÔNG dừng oscillator (khi WASM
// chưa sẵn sàng / font load fail → _playNoteFallback) → âm kêu liên
// tục mặc dù đã thả phím (không xử lí noteoff).
var _oscIds = _activeOscillatorsByKey[key];
if (_oscIds && _oscIds.length) {
var _nowT = getCtx().currentTime;
_oscIds.slice().forEach(function (oid) {
var entry = _activeOscillators[oid];
if (entry) {
try { entry.gain.gain.cancelScheduledValues(_nowT); entry.gain.gain.setValueAtTime(0, _nowT); } catch (e) {}
try { entry.osc.stop(_nowT); } catch (e) {}
delete _activeOscillators[oid];
}
});
delete _activeOscillatorsByKey[key];
}
if (_initialized && _fluidModule) {
var mappedChs = _activeNotes[key];
if (mappedChs === undefined) mappedChs = [channel];
@@ -711,6 +732,17 @@
for (var c = 0; c < 16; c++) _fluidModule._fluid_synth_all_notes_off(_synthPtr, c);
} catch (e) {}
}
// Dừng fallback oscillator (FluidSynth chưa sẵn sàng / font fail)
try {
var _pctx = getCtx();
var _pnow = _pctx.currentTime;
Object.values(_activeOscillators).forEach(function (entry) {
try { if (entry.gain) { entry.gain.gain.cancelScheduledValues(_pnow); entry.gain.gain.setValueAtTime(0, _pnow); } } catch (e) {}
try { if (entry.osc) entry.osc.stop(_pnow); } catch (e) {}
});
Object.keys(_activeOscillators).forEach(function (k) { delete _activeOscillators[k]; });
_activeOscillatorsByKey = {};
} catch (e) {}
_activeNotes = {};
},
@@ -768,7 +800,20 @@
osc.stop(stopAt);
var oscId = note + '_' + Date.now() + '_' + Math.random();
_activeOscillators[oscId] = { osc: osc, gain: noteGain };
setTimeout(function () { delete _activeOscillators[oscId]; }, (stopAt - ctx.currentTime) * 1000 + 100);
// Đăng ký theo 'channel:pitch' để stopNote dừng được khi thả phím
var oscKey = (channel !== undefined && channel >= 0 && channel < 16) ? (channel + ':' + note) : null;
if (oscKey) {
if (!_activeOscillatorsByKey[oscKey]) _activeOscillatorsByKey[oscKey] = [];
_activeOscillatorsByKey[oscKey].push(oscId);
}
setTimeout(function () {
delete _activeOscillators[oscId];
if (oscKey && _activeOscillatorsByKey[oscKey]) {
var _oi = _activeOscillatorsByKey[oscKey].indexOf(oscId);
if (_oi >= 0) _activeOscillatorsByKey[oscKey].splice(_oi, 1);
if (_activeOscillatorsByKey[oscKey].length === 0) delete _activeOscillatorsByKey[oscKey];
}
}, (stopAt - ctx.currentTime) * 1000 + 100);
return osc;
},
@@ -805,6 +850,7 @@
} catch (e) {}
});
Object.keys(_activeOscillators).forEach(function (k) { delete _activeOscillators[k]; });
_activeOscillatorsByKey = {};
_activeNotes = {};
},