T15: drop WebAudio master for tracks - native master gain + track gain/pan IPC (SF/VST2/VST3 bridges), NativeAudioService + /api/v1/native router, native-first TrackInstrument, test matrix 8 passed

This commit is contained in:
2026-08-11 17:33:12 +07:00
parent 500384a226
commit 0af834a8fa
14 changed files with 1131 additions and 8 deletions
+32 -3
View File
@@ -1,7 +1,9 @@
// SonicForge TrackInstrument Service
// Bọc FluidSynth channel per-track: playNote/noteOff theo TrackInstrumentCtx
// Bọc engine 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.
// T15: native-first — SF track đi thẳng vào engine native (NativeAudioService
// + SF host bridge), SonicSF WASM chỉ là fallback khi native không sẵn sàng.
// 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 || {};
@@ -18,6 +20,11 @@ window.TrackInstrument = window.TrackInstrument || {};
this.dest = ctx ? (ctx.dest || null) : null;
}
// Native engine sẵn sàng cho track SF? (chỉ khi có sfId — đường native)
TrackInstrument.prototype._nativeReady = function () {
try { return !!(window.SonicNativeAudio && this.sfId); } catch (e) { return false; }
};
// Đả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 () {
@@ -33,18 +40,40 @@ window.TrackInstrument = window.TrackInstrument || {};
}
};
// velocity: router đã normalize int 1-127 (unifiedMidiRouter.normalizeVelocity).
TrackInstrument.prototype.playNote = function (pitch, velocity, durationMs, startTime) {
var vel = velocity != null ? velocity : 100;
if (this._nativeReady()) {
try {
var self = this;
var dur = (durationMs != null ? durationMs : 500) || 500;
// ensure native SF engine cho track (server dedup theo track_id)
window.SonicNativeAudio.ensureSf(this.trackId, this.sfId, this.bank, this.prog)
.catch(function () {});
window.SonicNativeAudio.noteOn('sf', this.trackId, this.ch, pitch, vel);
// ponytail: TrackInstrument không biết audioCtx → bỏ startTime
// offset (delay = duration); thêm scheduling chính xác khi cần
setTimeout(function () { self.noteOff(pitch); }, dur + 40);
return;
} catch (e) {
console.warn('[TrackInstrument] native playNote error:', e);
}
}
// Fallback SonicSF (WASM)
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, startTime, this.program, this.dest, this.ch, this.synthEngine);
var durF = (durationMs != null ? durationMs : 500) || 500;
window.SonicSF.playNote(pitch, velocity != null ? velocity : 0.8, durF, startTime, this.program, this.dest, this.ch, this.synthEngine);
} catch (e) {
console.warn('[TrackInstrument] playNote error:', e);
}
};
TrackInstrument.prototype.noteOff = function (pitch) {
if (this._nativeReady()) {
try { window.SonicNativeAudio.noteOff('sf', this.trackId, this.ch, pitch); return; } catch (e) {}
}
try {
if (window.SonicSF && window.SonicSF.stopNote) window.SonicSF.stopNote(this.ch, pitch);
} catch (e) {}