Files
SonicForgeStudio/app/static/js/services/trackInstrument.js
T
admin 59cf77bcbc fix(midi): route MIDI live via masterBus, fix media explorer drag, add action logging
- TrackInstrument: gate native live path (SF/VSTi WASAPI bridge bypassed
  masterBus -> mastering FX + main out VU dead). Fallback to SonicSF WASM
  through masterBus.input; native kept for offline render only.
- MEDIA_LIBRARY_SAMPLES: rename MIDI_Loop_02_Bass/03_Lead/05_Bass to match
  actual /static/midi files (drag was 404 -> parse error).
- Add SonicAppLogger (console + POST /api/v1/logs) wired to: instrument load
  error, VSTi autosample fail, MIDI item -> mastering fx chain, play -> main
  out, drag MIDI errors (media explorer + window explorer).
- Add backend /api/v1/logs endpoint (server-side log, no DB).
2026-08-11 21:48:06 +07:00

137 lines
7.0 KiB
JavaScript

// 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).
// T15-regression fix: native live (SF/VSTi qua WASAPI bridge) phát thẳng ra
// OS device — BỎ QUA WebAudio masterBus → mastering FX + Main out VU không
// nhận tín hiệu. Gate native live path: luôn fallback WASM (SonicSF/
// autosample) qua masterBus.input. Native vẫn dùng cho OFFLINE render
// (scheduleNativeSfItem / /api/v1/native/render) — không đi qua hàm này.
// ponytail: re-enable live native khi C++ bridge có capture/readback →
// set window.__SONICFORGE_NATIVE_LIVE = true.
TrackInstrument.prototype._nativeKind = function () {
if (!window.__SONICFORGE_NATIVE_LIVE) return null;
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;
})();