T5: unified MIDI routing - thay native preview bang UnifiedMidiRouter + TrackInstrument (per-track FluidSynth channel); keybed/click/draw/hardware MIDI note qua router, fallback SonicSF; _panicMidi stop moi voice; bo _nativeSfPreviews/playNativeSfNote/stopNativeSfNote/stopAllNativeSfNotes/_sfWasmFallbackNote

This commit is contained in:
2026-08-11 15:45:53 +07:00
parent ee30ca097e
commit 2001d1c601
4 changed files with 215 additions and 151 deletions
+54
View File
@@ -0,0 +1,54 @@
// SonicForge TrackInstrument Service
// Bọc FluidSynth channel per-track: playNote/noteOff theo TrackInstrumentCtx
// (ch/program/synthEngine/sfId/bank/prog/dest) — dùng cho preview mọi nguồn
// (keybed, piano roll, draw, hardware MIDI, click) qua UnifiedMidiRouter.
// Tạo mới mỗi note-on (overwrite registry) — voice cũ giữ engine cũ trong
// tracker nên note-off luôn stop đúng channel.
window.TrackInstrument = window.TrackInstrument || {};
(function () {
function TrackInstrument(trackId, ctx) {
this.trackId = trackId;
this.ch = ctx ? (ctx.ch != null ? ctx.ch : 0) : 0;
this.program = ctx ? ctx.program : undefined;
this.synthEngine = ctx ? ctx.synthEngine : undefined;
this.sfId = ctx ? ctx.sfId : undefined;
this.bank = ctx ? (ctx.bank || 0) : 0;
this.prog = ctx ? (ctx.prog || 0) : 0;
this.dest = ctx ? (ctx.dest || null) : null;
}
// Đảm bảo channel đã select đúng instrument trước khi play (fire-and-forget:
// playNote tự load + retry nếu SF chưa load xong — dedup trong loadSoundFont).
TrackInstrument.prototype._ensure = function () {
try {
if (!window.SonicSF || !window.SonicSF.selectInstrument) return;
if (this.sfId) {
window.SonicSF.selectInstrument(this.ch, this.bank, this.prog, this.sfId);
} else if (this.program !== undefined) {
window.SonicSF.selectInstrument(this.ch, 0, this.program, null);
}
} catch (e) {
console.warn('[TrackInstrument] selectInstrument error:', e);
}
};
TrackInstrument.prototype.playNote = function (pitch, velocity, durationMs) {
try {
if (!window.SonicSF || !window.SonicSF.playNote) return;
this._ensure();
var dur = (durationMs != null ? durationMs : 500) || 500;
window.SonicSF.playNote(pitch, velocity != null ? velocity : 0.8, dur, undefined, this.program, this.dest, this.ch, this.synthEngine);
} catch (e) {
console.warn('[TrackInstrument] playNote error:', e);
}
};
TrackInstrument.prototype.noteOff = function (pitch) {
try {
if (window.SonicSF && window.SonicSF.stopNote) window.SonicSF.stopNote(this.ch, pitch);
} catch (e) {}
};
window.TrackInstrument = TrackInstrument;
})();