// SonicForge TrackInstrument Service // 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 || {}; (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; } // Loại engine native cho track: 'sf' (soundfont), 'vst3'/'vst2' (VSTi có // plugin_id) hay null (không native được → fallback WASM/autosample). TrackInstrument.prototype._nativeKind = function () { try { if (this.sfId) return 'sf'; if (window.SonicNativeAudio && this.synthEngine && this.synthEngine.plugin_id) { var t = String(this.synthEngine.type || ''); if (t.indexOf('vst2') !== -1) return 'vst2'; if (t.indexOf('vst3') !== -1 || t.indexOf('vst') !== -1) return 'vst3'; } } catch (e) { } return null; }; // Native engine sẵn sàng cho track? (sfId cho SF, plugin_id cho VSTi) TrackInstrument.prototype._nativeReady = function () { return this._nativeKind() !== 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); } }; // Phát qua SonicSF WASM (fallback khi native fail). SonicSF tự autosample // VSTi (soundfontPlayer._playNoteFluid) nên không cần ensure riêng ở đây. TrackInstrument.prototype._fallbackPlayNote = function (pitch, velocity, durationMs, startTime) { try { if (!window.SonicSF || !window.SonicSF.playNote) return; this._ensure(); 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] fallback playNote error:', e); } }; // velocity: router đã normalize int 1-127 (unifiedMidiRouter.normalizeVelocity). TrackInstrument.prototype.playNote = function (pitch, velocity, durationMs, startTime) { var vel = velocity != null ? velocity : 100; var kind = this._nativeKind(); if (kind) { try { var self = this; var dur = (durationMs != null ? durationMs : 500) || 500; if (kind === 'sf') { // ensure native SF engine cho track (server dedup theo track_id) window.SonicNativeAudio.ensureSf(this.trackId, this.sfId, this.bank, this.prog) .catch(function () {}); } else { // VSTi live native (Phase 2): ensure + note-on qua bridge DLL. // Server resolve plugin_id -> path; lỗi -> fallback WASM autosample. if (kind === 'vst3') { window.SonicNativeAudio.ensureVst3(this.trackId, this.synthEngine.plugin_id, this.synthEngine.plugin_path) .catch(function () {}); } else { window.SonicNativeAudio.ensureVst2(this.trackId, this.synthEngine.plugin_id, this.synthEngine.plugin_path) .catch(function () {}); } } window.SonicNativeAudio.noteOn(kind, this.trackId, this.ch, pitch, vel).catch(function (err) { // Native khong phat duoc (DLL/plugin loi) -> fallback WASM: // SF track di SonicSF, VSTi di autosample (SonicSF tu ensure). console.warn('[TrackInstrument] native noteOn fail -> WASM fallback:', err && err.message); self._fallbackPlayNote(pitch, velocity, dur, startTime); }); // 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) this._fallbackPlayNote(pitch, velocity, durationMs, startTime); }; TrackInstrument.prototype.noteOff = function (pitch) { var kind = this._nativeKind(); if (kind) { try { window.SonicNativeAudio.noteOff(kind, this.trackId, this.ch, pitch).catch(function () { // Native note-off fail -> stop qua WASM (neu note da fallback) try { if (window.SonicSF && window.SonicSF.stopNote) window.SonicSF.stopNote(this.ch, pitch); } catch (e) {} }); } catch (e) {} } // Belt-and-suspenders: stopNote WASM vo hai neu khong co note dang phat. try { if (window.SonicSF && window.SonicSF.stopNote) window.SonicSF.stopNote(this.ch, pitch); } catch (e) {} }; window.TrackInstrument = TrackInstrument; })();