feat(standalone): native VSTi midi-render cho browser client khi bridge inactive
- playNativeVstNote/scheduleNativeVstItem: render qua /api/v1/plugins/midi-render (pedalboard VST3 dung instrument_id/preset) -> play WAV, map token rieng - Them nhanh nativeVst tai moi diem route nhanh cua: startTrackPlayback, section/subtrack, playMidiPreviewNote, piano roll preview (alt-scroll/click/ brush/draw), keybed, MIDI hardware input - Safety net trong scheduleMidiNoteDispatch fallthrough (ghost notes va caller khac) - stopAllNativeVstNotes/stopNativeVstNote di kem cac diem stop hien co
This commit is contained in:
+165
-18
@@ -296,6 +296,107 @@ const scheduleNativeSfItem = (track, item, offsetTime, context, destNode, bpm, o
|
||||
} catch (e) { console.warn('[NativeSF] scheduleNativeSfItem error:', e); }
|
||||
};
|
||||
|
||||
// ── Native VSTi preview (standalone, bridge inactive) ─────────────────────
|
||||
// Render 1 note bằng backend pedalboard (VST3 đúng instrument_id/preset) →
|
||||
// play WAV — giống hệt nativeSf nhưng qua midiRender; map token riêng.
|
||||
const _nativeVstPreviews = {};
|
||||
const playNativeVstNote = (track, pitch, velocity, durationMs, startTime, key) => {
|
||||
try {
|
||||
const eng = track && track.synth_engine;
|
||||
const instrumentId = (eng && eng.plugin_id) || (track && track.instrumentId);
|
||||
if (!instrumentId || !isVstTrackEngine(eng)) return;
|
||||
const k = key || (track ? track.id : 'global');
|
||||
const prev = _nativeVstPreviews[k];
|
||||
const token = (prev ? prev.token : 0) + 1;
|
||||
const durationSec = Math.max(0.2, (durationMs || 500) / 1000);
|
||||
window.SonicAPI.midiRender({
|
||||
instrument_id: instrumentId,
|
||||
notes: [{ pitch: pitch, start_beat: 0, duration_beats: durationSec * 2, velocity: velocity != null ? velocity : 0.8 }],
|
||||
bpm: 120,
|
||||
preset_id: (eng && eng.preset_id) || undefined,
|
||||
preset_path: (eng && eng.preset_path) || undefined,
|
||||
preset_data: (eng && eng.preset_data) || undefined,
|
||||
}).then(function (res) {
|
||||
if (!res || !res.success || !res.url) return;
|
||||
if (_nativeVstPreviews[k] && _nativeVstPreviews[k].token !== token) return; // stale
|
||||
const audio = new Audio(API_BASE_URL + res.url);
|
||||
_nativeVstPreviews[k] = { audio: audio, token: token };
|
||||
const ctx = getAudioContext();
|
||||
const delay = startTime ? Math.max(0, (startTime - ctx.currentTime) * 1000) : 0;
|
||||
setTimeout(function () {
|
||||
if (!_nativeVstPreviews[k] || _nativeVstPreviews[k].audio !== audio) return;
|
||||
audio.play().catch(function () {});
|
||||
audio._vstStopTimer && clearTimeout(audio._vstStopTimer);
|
||||
audio._vstStopTimer = setTimeout(function () { try { audio.pause(); } catch (e) {} }, durationSec * 1000 + 400);
|
||||
}, delay);
|
||||
}).catch(function () {});
|
||||
} catch (e) { console.warn('[NativeVST] playNativeVstNote error:', e); }
|
||||
};
|
||||
const stopNativeVstNote = (key) => {
|
||||
try {
|
||||
const k = key || 'global';
|
||||
const prev = _nativeVstPreviews[k];
|
||||
if (!prev) return;
|
||||
prev.token++;
|
||||
try { if (prev.audio) { prev.audio.pause(); prev.audio.currentTime = 0; } } catch (e) {}
|
||||
} catch (e) {}
|
||||
};
|
||||
const stopAllNativeVstNotes = () => {
|
||||
try { Object.keys(_nativeVstPreviews).forEach(function (k) { stopNativeVstNote(k); }); } catch (e) {}
|
||||
};
|
||||
|
||||
// ── Native VSTi item render (standalone, transport) ────────────────────────
|
||||
// Render TOÀN BỘ MIDI item bằng pedalboard (VST3) → decode AudioBuffer →
|
||||
// schedule giống scheduleNativeSfItem. opts giống hệt nativeSf.
|
||||
const scheduleNativeVstItem = (track, item, offsetTime, context, destNode, bpm, opts) => {
|
||||
try {
|
||||
const eng = track && track.synth_engine;
|
||||
const instrumentId = (eng && eng.plugin_id) || (track && track.instrumentId);
|
||||
if (!instrumentId || !isVstTrackEngine(eng)) return;
|
||||
const notes = (item && item.notes) || [];
|
||||
if (!notes.length) return;
|
||||
const secPerBeat = 60.0 / (parseInt(bpm) || 120);
|
||||
const baseOffsetSec = (opts && opts.baseOffsetSec) || 0;
|
||||
const itemStartAbs = baseOffsetSec + (item.startTime || 0);
|
||||
let itemEndAbs = itemStartAbs + 0.05;
|
||||
notes.forEach(function (n) {
|
||||
const end = itemStartAbs + ((n.start_beat || 0) + (n.duration_beats || 1)) * secPerBeat;
|
||||
if (end > itemEndAbs) itemEndAbs = end;
|
||||
});
|
||||
window.SonicAPI.midiRender({
|
||||
instrument_id: instrumentId,
|
||||
notes: notes.map(function (n) { return { pitch: n.pitch || 60, start_beat: n.start_beat || 0, duration_beats: n.duration_beats || 1, velocity: n.velocity != null ? n.velocity : 0.8 }; }),
|
||||
bpm: parseFloat(bpm) || 120,
|
||||
preset_id: (eng && eng.preset_id) || undefined,
|
||||
preset_path: (eng && eng.preset_path) || undefined,
|
||||
preset_data: (eng && eng.preset_data) || undefined,
|
||||
}).then(function (res) {
|
||||
if (!res || !res.success || !res.url) return;
|
||||
fetch(API_BASE_URL + res.url).then(function (r) { return r.arrayBuffer(); }).then(function (buf) {
|
||||
context.decodeAudioData(buf, function (audioBuf) {
|
||||
try {
|
||||
if (opts && typeof opts.isActive === 'function' && !opts.isActive()) return;
|
||||
const src = context.createBufferSource();
|
||||
src.buffer = audioBuf;
|
||||
src.connect(destNode);
|
||||
if (offsetTime < itemStartAbs) {
|
||||
src.start(context.currentTime + (itemStartAbs - offsetTime), 0);
|
||||
} else if (offsetTime < itemEndAbs) {
|
||||
src.start(context.currentTime, Math.min(offsetTime - itemStartAbs, Math.max(0, audioBuf.duration - 0.05)));
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
if (opts && opts.limitSec && opts.limitSec < itemStartAbs + audioBuf.duration) {
|
||||
src.stop(context.currentTime + Math.max(0.02, opts.limitSec - Math.max(offsetTime, itemStartAbs)));
|
||||
}
|
||||
if (opts && opts.sources && opts.sources.push) opts.sources.push(src);
|
||||
} catch (e) { console.warn('[NativeVST] schedule decode error:', e); }
|
||||
}, function () {});
|
||||
}).catch(function () {});
|
||||
}).catch(function () {});
|
||||
} catch (e) { console.warn('[NativeVST] scheduleNativeVstItem error:', e); }
|
||||
};
|
||||
|
||||
// Handle ?sfs=<encoded> from double-clicking a .sfs file (opens domain -> loads project)
|
||||
(function handleSfsDeepLink() {
|
||||
try {
|
||||
@@ -8328,6 +8429,10 @@ const PianoRollTabEditor = ({ st, zoom, bpm, viewportWidth, activeTracks, onClos
|
||||
playNativeSfNote(pvTrk, n.pitch, n.velocity || 0.8, 200, undefined, 'pv_' + st.trackId);
|
||||
return;
|
||||
}
|
||||
if (isStandaloneSf() && isVstTrackEngine(pvCtx.synthEngine) && !shouldRouteCarla(pvCtx.synthEngine) && !(window.SonicMidiRouter && window.SonicMidiRouter.isBridgeActive())) {
|
||||
playNativeVstNote(pvTrk, n.pitch, n.velocity || 0.8, 200, undefined, 'pv_' + st.trackId);
|
||||
return;
|
||||
}
|
||||
scheduleMidiNoteDispatch(pvTrk, n.pitch, n.velocity || 0.8, 200, ctx.currentTime, pvCtx.program, null, pvCtx.ch, pvCtx.synthEngine);
|
||||
});
|
||||
}
|
||||
@@ -8696,6 +8801,8 @@ const PianoRollTabEditor = ({ st, zoom, bpm, viewportWidth, activeTracks, onClos
|
||||
ensureSonicInstrument(clCtx);
|
||||
if (isStandaloneSf() && isSfTrackEngine(clCtx.synthEngine) && !shouldRouteCarla(clCtx.synthEngine) && !(window.SonicMidiRouter && window.SonicMidiRouter.isBridgeActive())) {
|
||||
playNativeSfNote(clTrk, notes[clickedNoteIdx].pitch, notes[clickedNoteIdx].velocity || 0.8, 300, undefined, 'pv_' + st.trackId);
|
||||
} else if (isStandaloneSf() && isVstTrackEngine(clCtx.synthEngine) && !shouldRouteCarla(clCtx.synthEngine) && !(window.SonicMidiRouter && window.SonicMidiRouter.isBridgeActive())) {
|
||||
playNativeVstNote(clTrk, notes[clickedNoteIdx].pitch, notes[clickedNoteIdx].velocity || 0.8, 300, undefined, 'pv_' + st.trackId);
|
||||
} else {
|
||||
scheduleMidiNoteDispatch(clTrk, notes[clickedNoteIdx].pitch, notes[clickedNoteIdx].velocity || 0.8, 300, ctx.currentTime, clCtx.program, null, clCtx.ch, clCtx.synthEngine);
|
||||
}
|
||||
@@ -8960,6 +9067,8 @@ const PianoRollTabEditor = ({ st, zoom, bpm, viewportWidth, activeTracks, onClos
|
||||
var dwDurMs = Math.max(100, Math.round(initialDur * (60 / bpm) * 1000));
|
||||
if (isStandaloneSf() && isSfTrackEngine(dwTrk && dwTrk.synth_engine) && !shouldRouteCarla(dwTrk && dwTrk.synth_engine) && !(window.SonicMidiRouter && window.SonicMidiRouter.isBridgeActive())) {
|
||||
playNativeSfNote(dwTrk, dwPitch, brushVelocityRef.current || 0.8, dwDurMs, undefined, 'pvdraw_' + st.trackId);
|
||||
} else if (isStandaloneSf() && isVstTrackEngine(dwTrk && dwTrk.synth_engine) && !shouldRouteCarla(dwTrk && dwTrk.synth_engine) && !(window.SonicMidiRouter && window.SonicMidiRouter.isBridgeActive())) {
|
||||
playNativeVstNote(dwTrk, dwPitch, brushVelocityRef.current || 0.8, dwDurMs, undefined, 'pvdraw_' + st.trackId);
|
||||
} else if (window.SonicSF && window.SonicSF.playNote) {
|
||||
const ctx = getAudioContext();
|
||||
// playNote (FluidSynth — nhạc cụ THẬT của track). _playNoteFallback chỉ
|
||||
@@ -9061,6 +9170,8 @@ const PianoRollTabEditor = ({ st, zoom, bpm, viewportWidth, activeTracks, onClos
|
||||
var pvCtxInst = resolveTrackInstrumentCtx(pvTrk, activeTracks);
|
||||
if (isStandaloneSf() && isSfTrackEngine(pvCtxInst.synthEngine) && !shouldRouteCarla(pvCtxInst.synthEngine) && !(window.SonicMidiRouter && window.SonicMidiRouter.isBridgeActive())) {
|
||||
playNativeSfNote(pvTrk, p, brushVelocityRef.current || 0.8, durMs, undefined, 'pvdraw_' + st.trackId);
|
||||
} else if (isStandaloneSf() && isVstTrackEngine(pvCtxInst.synthEngine) && !shouldRouteCarla(pvCtxInst.synthEngine) && !(window.SonicMidiRouter && window.SonicMidiRouter.isBridgeActive())) {
|
||||
playNativeVstNote(pvTrk, p, brushVelocityRef.current || 0.8, durMs, undefined, 'pvdraw_' + st.trackId);
|
||||
} else if (window.SonicSF && window.SonicSF.playNote) {
|
||||
var pvCtx = getAudioContext();
|
||||
// playNote (FluidSynth — nhạc cụ THẬT). _playNoteFallback = oscillator
|
||||
@@ -9452,13 +9563,16 @@ const PianoRollTabEditor = ({ st, zoom, bpm, viewportWidth, activeTracks, onClos
|
||||
window.triggerMidiVuActivity((st.parent_tab_id && st.parent_tab_id.startsWith('session_') ? '_sess_' : '') + st.trackId, 100);
|
||||
}
|
||||
const kbNative = isStandaloneSf() && isSfTrackEngine(kbCtx.synthEngine) && !shouldRouteCarla(kbCtx.synthEngine) && !(window.SonicMidiRouter && window.SonicMidiRouter.isBridgeActive());
|
||||
const kbNativeVst = isStandaloneSf() && isVstTrackEngine(kbCtx.synthEngine) && !shouldRouteCarla(kbCtx.synthEngine) && !(window.SonicMidiRouter && window.SonicMidiRouter.isBridgeActive());
|
||||
if (kbNative) {
|
||||
playNativeSfNote(kbTrk, pitch, 100 / 127, 5000, undefined, 'kb_' + st.trackId + '_' + pitch);
|
||||
} else if (kbNativeVst) {
|
||||
playNativeVstNote(kbTrk, pitch, 100 / 127, 5000, undefined, 'kb_' + st.trackId + '_' + pitch);
|
||||
}
|
||||
if (window.SonicMidiRouter && window.SonicMidiRouter.isBridgeActive()) {
|
||||
try { if (window.__ensureBridgeProgram) window.__ensureBridgeProgram(kbTrk ? kbTrk.id : 0, kbCtx.program, kbCtx.synthEngine); } catch (er) {}
|
||||
window.SonicMidiRouter.pushEvent({ cmd: 'NOTE_ON', channel: kbTrk ? kbTrk.id : 0, pitch: pitch, velocity: 100 / 127, percussion: !!(kbCtx && kbCtx.synthEngine && kbCtx.synthEngine.soundfont_bank === 128), live: true });
|
||||
} else if (window.SonicSF && !kbNative) {
|
||||
} else if (window.SonicSF && !kbNative && !kbNativeVst) {
|
||||
// ⚠️ FIX: giữ note theo thời gian bấm phím — durationMs lớn (5s)
|
||||
// chỉ là auto-off phòng hờ; mouseup/mouseleave gọi stopNote dừng
|
||||
// NGAY (trước đây 500ms → note tự tắt giữa chừng khi giữ phím).
|
||||
@@ -9482,11 +9596,13 @@ const PianoRollTabEditor = ({ st, zoom, bpm, viewportWidth, activeTracks, onClos
|
||||
}
|
||||
if (isStandaloneSf() && isSfTrackEngine(kbCtx.synthEngine) && !shouldRouteCarla(kbCtx.synthEngine) && !(window.SonicMidiRouter && window.SonicMidiRouter.isBridgeActive())) {
|
||||
playNativeSfNote(kbTrk, pitch, 100 / 127, 5000, undefined, 'kb_' + st.trackId + '_' + pitch);
|
||||
} else if (isStandaloneSf() && isVstTrackEngine(kbCtx.synthEngine) && !shouldRouteCarla(kbCtx.synthEngine) && !(window.SonicMidiRouter && window.SonicMidiRouter.isBridgeActive())) {
|
||||
playNativeVstNote(kbTrk, pitch, 100 / 127, 5000, undefined, 'kb_' + st.trackId + '_' + pitch);
|
||||
}
|
||||
if (window.SonicMidiRouter && window.SonicMidiRouter.isBridgeActive()) {
|
||||
try { if (window.__ensureBridgeProgram) window.__ensureBridgeProgram(kbTrk ? kbTrk.id : 0, kbCtx.program, kbCtx.synthEngine); } catch (er) {}
|
||||
window.SonicMidiRouter.pushEvent({ cmd: 'NOTE_ON', channel: kbTrk ? kbTrk.id : 0, pitch: pitch, velocity: 100 / 127, percussion: !!(kbCtx && kbCtx.synthEngine && kbCtx.synthEngine.soundfont_bank === 128), live: true });
|
||||
} else if (window.SonicSF && !(isStandaloneSf() && isSfTrackEngine(kbCtx.synthEngine) && !shouldRouteCarla(kbCtx.synthEngine))) {
|
||||
} else if (window.SonicSF && !(isStandaloneSf() && (isSfTrackEngine(kbCtx.synthEngine) || isVstTrackEngine(kbCtx.synthEngine)) && !shouldRouteCarla(kbCtx.synthEngine))) {
|
||||
// giữ note khi kéo qua phím (mouse enter) — dừng bằng mouseup/leave
|
||||
window.SonicSF.playNote(pitch, 100, 5000, undefined, kbCtx.program, null, kbCtx.ch, kbCtx.synthEngine);
|
||||
}
|
||||
@@ -9502,7 +9618,7 @@ const PianoRollTabEditor = ({ st, zoom, bpm, viewportWidth, activeTracks, onClos
|
||||
keybedMouseDownRef.current = false;
|
||||
// Dừng note khi thả phím — tránh kẹt âm (loop liên tục) với soundfont
|
||||
try {
|
||||
if (isStandaloneSf()) stopNativeSfNote('kb_' + st.trackId + '_' + pitch);
|
||||
if (isStandaloneSf()) { stopNativeSfNote('kb_' + st.trackId + '_' + pitch); stopNativeVstNote('kb_' + st.trackId + '_' + pitch); }
|
||||
if (window.SonicMidiRouter && window.SonicMidiRouter.isBridgeActive()) {
|
||||
window.SonicMidiRouter.pushEvent({ cmd: 'NOTE_OFF', channel: kbTrk ? kbTrk.id : 0, pitch: pitch, velocity: 0, percussion: !!(kbCtx && kbCtx.synthEngine && kbCtx.synthEngine.soundfont_bank === 128), live: true });
|
||||
} else if (window.SonicSF && window.SonicSF.stopNote) window.SonicSF.stopNote(kbCtx.ch, pitch);
|
||||
@@ -9514,7 +9630,7 @@ const PianoRollTabEditor = ({ st, zoom, bpm, viewportWidth, activeTracks, onClos
|
||||
if (!keybedMouseDownRef.current) return;
|
||||
// Kéo chuột ra khỏi phím → dừng note của phím đó
|
||||
try {
|
||||
if (isStandaloneSf()) stopNativeSfNote('kb_' + st.trackId + '_' + pitch);
|
||||
if (isStandaloneSf()) { stopNativeSfNote('kb_' + st.trackId + '_' + pitch); stopNativeVstNote('kb_' + st.trackId + '_' + pitch); }
|
||||
if (window.SonicMidiRouter && window.SonicMidiRouter.isBridgeActive()) {
|
||||
window.SonicMidiRouter.pushEvent({ cmd: 'NOTE_OFF', channel: kbTrk ? kbTrk.id : 0, pitch: pitch, velocity: 0, percussion: !!(kbCtx && kbCtx.synthEngine && kbCtx.synthEngine.soundfont_bank === 128), live: true });
|
||||
} else if (window.SonicSF && window.SonicSF.stopNote) window.SonicSF.stopNote(kbCtx.ch, pitch);
|
||||
@@ -14216,7 +14332,7 @@ const MediaExplorerPanel = ({ height, clipboardRef, active }) => {
|
||||
selectTokenRef.current++;
|
||||
const token = selectTokenRef.current;
|
||||
try { if (window.SonicMidiRouter) window.SonicMidiRouter.panic(); else if (window.SonicSF && typeof window.SonicSF.stopAll === 'function') window.SonicSF.stopAll(); } catch (e) {}
|
||||
stopAllNativeSfNotes();
|
||||
stopAllNativeSfNotes(); stopAllNativeVstNotes();
|
||||
playMidiPreview(cur, token);
|
||||
}
|
||||
};
|
||||
@@ -14445,7 +14561,7 @@ const MediaExplorerPanel = ({ height, clipboardRef, active }) => {
|
||||
if (rafRef.current) cancelAnimationFrame(rafRef.current);
|
||||
rafRef.current = null;
|
||||
if (loopTimerRef.current) { clearInterval(loopTimerRef.current); loopTimerRef.current = null; }
|
||||
stopNativeSfNote('midifile');
|
||||
stopNativeSfNote('midifile'); stopNativeVstNote('midifile');
|
||||
if (window.SonicSF && typeof window.SonicSF.stopAll === 'function') {
|
||||
try { window.SonicSF.stopAll(); } catch (e) {}
|
||||
}
|
||||
@@ -14769,7 +14885,7 @@ const MediaExplorerPanel = ({ height, clipboardRef, active }) => {
|
||||
selectTokenRef.current++;
|
||||
const token = selectTokenRef.current;
|
||||
try { if (window.SonicMidiRouter) window.SonicMidiRouter.panic(); else if (window.SonicSF && typeof window.SonicSF.stopAll === 'function') window.SonicSF.stopAll(); } catch (e) {}
|
||||
stopAllNativeSfNotes();
|
||||
stopAllNativeSfNotes(); stopAllNativeVstNotes();
|
||||
playMidiPreview(cur, token);
|
||||
}
|
||||
};
|
||||
@@ -14940,7 +15056,7 @@ const MediaExplorerPanel = ({ height, clipboardRef, active }) => {
|
||||
selectTokenRef.current++;
|
||||
const token = selectTokenRef.current;
|
||||
try { if (window.SonicMidiRouter) window.SonicMidiRouter.panic(); else if (window.SonicSF && typeof window.SonicSF.stopAll === 'function') window.SonicSF.stopAll(); } catch (e) {}
|
||||
stopAllNativeSfNotes();
|
||||
stopAllNativeSfNotes(); stopAllNativeVstNotes();
|
||||
playMidiPreview(cur, token);
|
||||
}
|
||||
}
|
||||
@@ -15630,7 +15746,7 @@ const App = () => {
|
||||
if (_wasVst && !_nowVst && window.SonicCarlaMidi && window.SonicCarlaMidi.stopBridge) {
|
||||
window.SonicCarlaMidi.stopBridge();
|
||||
try { if (window.SonicMidiRouter) window.SonicMidiRouter.panic(); else if (window.SonicSF && window.SonicSF.stopAll) window.SonicSF.stopAll(); } catch (e) {}
|
||||
stopAllNativeSfNotes();
|
||||
stopAllNativeSfNotes(); stopAllNativeVstNotes();
|
||||
console.log('[Instrument] Carla bridge unloaded — track', trackId, 'switched from VSTi to non-VST');
|
||||
}
|
||||
} catch (e) { console.warn('[Instrument] carla-stop on instrument switch error:', e); }
|
||||
@@ -15906,6 +16022,8 @@ const App = () => {
|
||||
} catch (err) { }
|
||||
if (isStandaloneSf() && isSfTrackEngine(asSe) && !shouldRouteCarla(asSe)) {
|
||||
playNativeSfNote(asTrk, pitch, scaledVel / 127, 60000, undefined, 'midi_' + as.trackId + '_' + pitch);
|
||||
} else if (isStandaloneSf() && isVstTrackEngine(asSe) && !shouldRouteCarla(asSe)) {
|
||||
playNativeVstNote(asTrk, pitch, scaledVel / 127, 60000, undefined, 'midi_' + as.trackId + '_' + pitch);
|
||||
} else {
|
||||
window.SonicSF.playNote(pitch, scaledVel, 60000, undefined, asProg, null, asCh, asSe);
|
||||
}
|
||||
@@ -15931,6 +16049,8 @@ const App = () => {
|
||||
} catch (err) { }
|
||||
if (isStandaloneSf() && isSfTrackEngine(atSe) && !shouldRouteCarla(atSe)) {
|
||||
playNativeSfNote(at, pitch, scaledVel / 127, 60000, undefined, 'midi_' + at.id + '_' + pitch);
|
||||
} else if (isStandaloneSf() && isVstTrackEngine(atSe) && !shouldRouteCarla(atSe)) {
|
||||
playNativeVstNote(at, pitch, scaledVel / 127, 60000, undefined, 'midi_' + at.id + '_' + pitch);
|
||||
} else {
|
||||
window.SonicSF.playNote(pitch, scaledVel, 60000, undefined, atProg, atDest, atCh, atSe);
|
||||
}
|
||||
@@ -15976,7 +16096,7 @@ const App = () => {
|
||||
// channel and kill its sound.
|
||||
if (!st.synth_engine && st.midiChannel === undefined) return;
|
||||
var stCh = assignTrackMidiChannel(st, stopTracks);
|
||||
if (isStandaloneSf()) stopNativeSfNote('midi_' + st.id + '_' + pitch);
|
||||
if (isStandaloneSf()) stopNativeSfNote('midi_' + st.id + '_' + pitch); stopNativeVstNote('midi_' + st.id + '_' + pitch);
|
||||
window.SonicSF.stopNote(stCh, pitch);
|
||||
// MIDI Keyboard → Carla bridge: note-off khi thả phím — tránh
|
||||
// kẹt âm VSTi (giống keybed ảo).
|
||||
@@ -21943,6 +22063,10 @@ const App = () => {
|
||||
playNativeSfNote(track, pitch, velocity, durationMs, null, track ? track.id : 'global');
|
||||
return;
|
||||
}
|
||||
if (isStandaloneSf() && !routeCarla && isVstTrackEngine(track && track.synth_engine)) {
|
||||
playNativeVstNote(track, pitch, velocity, durationMs, null, track ? track.id : 'global');
|
||||
return;
|
||||
}
|
||||
window.SonicSF.playNote(
|
||||
pitch,
|
||||
velocity,
|
||||
@@ -21988,6 +22112,12 @@ const App = () => {
|
||||
}, delayMs + (durMs || 1000) + 30);
|
||||
return;
|
||||
}
|
||||
// Native VSTi (standalone browser, bridge inactive) → midi-render thay vì
|
||||
// FluidSynth GM sai âm — safety net cho mọi caller (ghost notes, per-note).
|
||||
if (isStandaloneSf() && isVstTrackEngine(synthEngine) && !(window.SonicMidiRouter && window.SonicMidiRouter.isBridgeActive())) {
|
||||
playNativeVstNote(track, pitch, velocity, durMs, startTime, track ? track.id : 'global');
|
||||
return;
|
||||
}
|
||||
window.SonicSF.playNote(pitch, velocity, durMs, startTime, program, destNode, ch, synthEngine);
|
||||
};
|
||||
|
||||
@@ -22064,9 +22194,12 @@ const App = () => {
|
||||
const midiItems = track.midiItems || [];
|
||||
const routeCarla = shouldRouteCarla(track.synth_engine);
|
||||
const nativeSf = isStandaloneSf() && !routeCarla && isSfTrackEngine(track.synth_engine) && !(window.SonicMidiRouter && window.SonicMidiRouter.isBridgeActive());
|
||||
if ((track.type === 'MIDI' || midiItems.length > 0) && (window.SonicSF || routeCarla || nativeSf)) {
|
||||
const nativeVst = isStandaloneSf() && !routeCarla && isVstTrackEngine(track.synth_engine) && !(window.SonicMidiRouter && window.SonicMidiRouter.isBridgeActive());
|
||||
if ((track.type === 'MIDI' || midiItems.length > 0) && (window.SonicSF || routeCarla || nativeSf || nativeVst)) {
|
||||
if (nativeSf) {
|
||||
midiItems.forEach(item => scheduleNativeSfItem(track, item, offsetTime, context, gainNode, bpm, { sources: activeSourcesRef.current, isActive: () => !!isPlayingRef.current }));
|
||||
} else if (nativeVst) {
|
||||
midiItems.forEach(item => scheduleNativeVstItem(track, item, offsetTime, context, gainNode, bpm, { sources: activeSourcesRef.current, isActive: () => !!isPlayingRef.current }));
|
||||
} else {
|
||||
// Preview cache: capture the soundfont (pre track-FX) for fast offline export
|
||||
ensureMidiCapture(track, activeTrackNodesRef.current[track.id]);
|
||||
@@ -22227,11 +22360,16 @@ const App = () => {
|
||||
const subMidiItems = subTrack.midiItems || [];
|
||||
const subRouteCarla = shouldRouteCarla(subTrack.synth_engine);
|
||||
const subNativeSf = isStandaloneSf() && !subRouteCarla && isSfTrackEngine(subTrack.synth_engine) && !(window.SonicMidiRouter && window.SonicMidiRouter.isBridgeActive());
|
||||
if (subMidiItems.length > 0 && (window.SonicSF || subRouteCarla || subNativeSf)) {
|
||||
const subNativeVst = isStandaloneSf() && !subRouteCarla && isVstTrackEngine(subTrack.synth_engine) && !(window.SonicMidiRouter && window.SonicMidiRouter.isBridgeActive());
|
||||
if (subMidiItems.length > 0 && (window.SonicSF || subRouteCarla || subNativeSf || subNativeVst)) {
|
||||
if (subNativeSf) {
|
||||
subMidiItems.forEach(item => scheduleNativeSfItem(subTrack, item, offsetTime, context, subNode, bpm, { baseOffsetSec: secStart, limitSec: secEnd, sources: activeSourcesRef.current, isActive: () => !!isPlayingRef.current }));
|
||||
return; // native render → audio buffer (không qua WASM/Carla per-note)
|
||||
}
|
||||
if (subNativeVst) {
|
||||
subMidiItems.forEach(item => scheduleNativeVstItem(subTrack, item, offsetTime, context, subNode, bpm, { baseOffsetSec: secStart, limitSec: secEnd, sources: activeSourcesRef.current, isActive: () => !!isPlayingRef.current }));
|
||||
return; // native render → audio buffer (không qua WASM/Carla per-note)
|
||||
}
|
||||
subMidiItems.forEach(item => {
|
||||
const notes = item.notes || [];
|
||||
notes.forEach(note => {
|
||||
@@ -22360,11 +22498,16 @@ const App = () => {
|
||||
const midiItems = track.midiItems || [];
|
||||
const routeCarla = shouldRouteCarla(track.synth_engine);
|
||||
const nativeSf = isStandaloneSf() && !routeCarla && isSfTrackEngine(track.synth_engine) && !(window.SonicMidiRouter && window.SonicMidiRouter.isBridgeActive());
|
||||
if ((track.type === 'MIDI' || midiItems.length > 0) && (window.SonicSF || routeCarla || nativeSf)) {
|
||||
const nativeVst = isStandaloneSf() && !routeCarla && isVstTrackEngine(track.synth_engine) && !(window.SonicMidiRouter && window.SonicMidiRouter.isBridgeActive());
|
||||
if ((track.type === 'MIDI' || midiItems.length > 0) && (window.SonicSF || routeCarla || nativeSf || nativeVst)) {
|
||||
if (nativeSf) {
|
||||
midiItems.forEach(item => scheduleNativeSfItem(track, item, offsetTime, context, gainNode, bpm, { sources: activeSourcesRef.current, isActive: () => !!isPlayingRef.current }));
|
||||
return; // native render → audio buffer (không qua WASM/Carla per-note)
|
||||
}
|
||||
if (nativeVst) {
|
||||
midiItems.forEach(item => scheduleNativeVstItem(track, item, offsetTime, context, gainNode, bpm, { sources: activeSourcesRef.current, isActive: () => !!isPlayingRef.current }));
|
||||
return; // native render → audio buffer (không qua WASM/Carla per-note)
|
||||
}
|
||||
var lcCh = assignTrackMidiChannel(track, tracks);
|
||||
const bpmVal = parseInt(bpm) || 120;
|
||||
const secondsPerBeat = 60.0 / bpmVal;
|
||||
@@ -22462,6 +22605,10 @@ const App = () => {
|
||||
scheduleNativeSfItem(track, { startTime: 0, notes: midiNotes }, offsetSeconds, context, destNode, bpm, { sources: activeSourcesRef.current, isActive: () => !!isPlayingRef.current });
|
||||
return; // native render cả tab → audio buffer (không per-note WASM/Carla)
|
||||
}
|
||||
if (isStandaloneSf() && !routeCarla && isVstTrackEngine(synthEngine) && !(window.SonicMidiRouter && window.SonicMidiRouter.isBridgeActive())) {
|
||||
scheduleNativeVstItem(track, { startTime: 0, notes: midiNotes }, offsetSeconds, context, destNode, bpm, { sources: activeSourcesRef.current, isActive: () => !!isPlayingRef.current });
|
||||
return; // native render cả tab → audio buffer (không per-note WASM/Carla)
|
||||
}
|
||||
midiNotes.forEach(note => {
|
||||
const noteOnBeat = note.start_beat || 0;
|
||||
const noteDurBeat = note.duration_beats || 1;
|
||||
@@ -22602,7 +22749,7 @@ const App = () => {
|
||||
}
|
||||
setTimeout(() => {
|
||||
window.SonicSF.stopAll();
|
||||
stopAllNativeSfNotes();
|
||||
stopAllNativeSfNotes(); stopAllNativeVstNotes();
|
||||
if (prTNode && prTNode.gainNode) {
|
||||
const prTrackData = activeTracksRef.current ? activeTracksRef.current.find(t => t.id === prSt.trackId) : null;
|
||||
const prVolDb = prTrackData ? (prTrackData.volumeDb ?? 0) : 0;
|
||||
@@ -22647,7 +22794,7 @@ const App = () => {
|
||||
// tick vẫn thấy heldCnt > 0 → giữ peak → VU dính mãi (user bug 08:08).
|
||||
try { heldMidiNotesRef.current = {}; } catch (e) { }
|
||||
stopMidiCapture();
|
||||
stopAllNativeSfNotes();
|
||||
stopAllNativeSfNotes(); stopAllNativeVstNotes();
|
||||
// D2/D6: bridge active -> transport STOP (bridge flush toàn pitch, hết âm
|
||||
// ngân tức thì; A13 C++ flush note-off).
|
||||
if (window.NativeBridgeService && window.NativeBridgeService.isBridgeConnected) {
|
||||
@@ -22685,7 +22832,7 @@ const App = () => {
|
||||
if (st.isPlaying) {
|
||||
stopAllPlayback();
|
||||
window.SonicSF.stopAll();
|
||||
stopAllNativeSfNotes();
|
||||
stopAllNativeSfNotes(); stopAllNativeVstNotes();
|
||||
setSubTabs(prev => prev.map(s => s.id === activeTab ? {
|
||||
...s,
|
||||
currentTime: time,
|
||||
@@ -30282,7 +30429,7 @@ STRICT CONSTRAINTS:
|
||||
}
|
||||
setTimeout(() => {
|
||||
window.SonicSF.stopAll();
|
||||
stopAllNativeSfNotes();
|
||||
stopAllNativeSfNotes(); stopAllNativeVstNotes();
|
||||
if (tNode && tNode.gainNode) {
|
||||
const trackData = activeTracksRef.current ? activeTracksRef.current.find(t => t.id === playingSub.trackId) : null;
|
||||
const volDb = trackData ? (trackData.volumeDb ?? 0) : 0;
|
||||
@@ -30303,7 +30450,7 @@ STRICT CONSTRAINTS:
|
||||
if (seekSt.isPlaying) {
|
||||
stopAllPlayback();
|
||||
window.SonicSF.stopAll();
|
||||
stopAllNativeSfNotes();
|
||||
stopAllNativeSfNotes(); stopAllNativeVstNotes();
|
||||
setSubTabs(prev => prev.map(s => s.id === seekSt.id ? { ...s, currentTime: clickTime, isPlaying: true } : s));
|
||||
const ctx = getAudioContext();
|
||||
startOffsetTimeRef.current = clickTime;
|
||||
|
||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user