FIX: (1) masteringConnected default true + ensureMasteringRouting/refreshCarlaStatus ở mọi đường play — MIDI item/preview qua mastering ngay khi chạy app; (2) _instrumentEpoch chống cache stale + retry bank 0 — hết chọn Synth String nghe Piano; (3) /carla-status + auto-open Carla + route exclusive chỉ khi Carla sống (fallback FluidSynth tránh câm); (4) keybed giữ note theo thời gian bấm (auto-off 5s phòng hờ, mouseup dừng ngay)

This commit is contained in:
2026-08-10 09:51:58 +00:00
parent f492104869
commit 75eba27dcf
7 changed files with 135 additions and 26 deletions
+24 -1
View File
@@ -25,6 +25,11 @@
// 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 = {};
// Epoch chống cache channel STALE: tăng mỗi khi selectInstrument/load
// soundfont — channel cache chỉ được tin khi khớp epoch hiện tại, nếu
// không → note KẾ TIẾP luôn program_select lại (hết "chọn Synth String
// nghe Piano" do cache cũ skip program_select).
let _instrumentEpoch = 0;
let _gainNode = null;
let _pendingOutputDestination = null;
let _outputDestination = null; // cache đích route — dedupe swap dư giữa stream
@@ -332,6 +337,7 @@
_sfHandleMap.set(sfId, cachedOk);
_currentSfId = sfId;
_loadedFonts[sfId] = true;
_instrumentEpoch++;
console.log("[SonicSF] SoundFont loaded from cache:", sfId, "handle:", cachedOk);
return true;
}
@@ -367,6 +373,7 @@
_sfHandleMap.set(sfId, sfHandle);
_currentSfId = sfId;
_loadedFonts[sfId] = true;
_instrumentEpoch++;
console.log("[SonicSF] SoundFont loaded:", sfId, "handle:", sfHandle);
return true;
} catch (e) {
@@ -396,9 +403,17 @@
_engineChMap[engKey] = channel;
}
var sfHandle = _sfHandleMap.get(sfId);
// ⚠️ FIX âm sai instrument: tăng epoch → channel cache cũ thành
// stale → note kế tiếp LUÔN program_select lại (hết "chọn Synth
// String nghe Piano" do cache cũ skip).
_instrumentEpoch++;
if (sfHandle !== undefined) {
try {
_fluidModule._fluid_synth_program_select(_synthPtr, channel, sfHandle, bank, program);
var _sr = _fluidModule._fluid_synth_program_select(_synthPtr, channel, sfHandle, bank, program);
if (_sr !== 0) {
// Preset không tồn tại ở bank/program này → thử bank 0
try { _fluidModule._fluid_synth_program_select(_synthPtr, channel, sfHandle, 0, program); } catch (e2) {}
}
} catch (e) {}
} else {
try { _fluidModule._fluid_synth_bank_select(_synthPtr, channel, bank); } catch (e) {}
@@ -408,6 +423,7 @@
_channels[channel].program = program;
_channels[channel].isPercussion = (bank === 128);
_channels[channel].sfId = sfId;
_channels[channel]._epoch = _instrumentEpoch;
},
controllerChange: function (channel, controller, value) {
@@ -632,6 +648,7 @@
// ⚠️ Chỉ skip khi handle SF vẫn CÒN HỢP LỆ trong map — nếu
// không → vẫn program_select lại (tránh dùng handle đã unload).
var progAlreadySet = cachedCh && cachedCh.program === finalProg && cachedCh.bank === finalBank && cachedCh.sfId === finalSfId
&& cachedCh._epoch === _instrumentEpoch
&& (finalSfId ? _sfHandleMap.has(finalSfId) : true);
if ((synthEngine || program !== undefined) && !progAlreadySet) {
var sfHandle = finalSfId ? _sfHandleMap.get(finalSfId) : undefined;
@@ -666,6 +683,11 @@
}
}
var _selRet = _fluidModule._fluid_synth_program_select(_synthPtr, ch, sfHandle, finalBank, finalProg);
if (_selRet !== 0 && finalBank !== 0) {
// Preset không tồn tại ở bank này → thử bank 0
// (hết "noteon preset rỗng = âm sai/không đúng")
try { _fluidModule._fluid_synth_program_select(_synthPtr, ch, sfHandle, 0, finalProg); } catch (e2) {}
}
} catch (e) {}
} else {
try { _fluidModule._fluid_synth_bank_select(_synthPtr, ch, finalBank); } catch (e) {}
@@ -675,6 +697,7 @@
_channels[ch].bank = finalBank;
_channels[ch].program = finalProg;
_channels[ch].sfId = finalSfId;
_channels[ch]._epoch = _instrumentEpoch;
}
console.log('[SonicSF] noteon channel:', ch, 'pitch:', midiPitch, 'vel:', midiVel, 'sfId:', finalSfId);
_fluidModule._fluid_synth_noteon(_synthPtr, ch, midiPitch, midiVel);