connect solution into app: VSTi live native (vst3 attach/vst2 bridge) with WASM autosample fallback, VST GUI window (Bug 1), native-first track instrument
This commit is contained in:
+121
-41
@@ -68,6 +68,25 @@ const resolveTrackInstrumentCtx = (track, tracks) => {
|
||||
return { ch, program: undefined, synthEngine: undefined, sfId: undefined, bank: 0, prog: 0 };
|
||||
};
|
||||
|
||||
// Sub-tab (piano roll / section) instrument override: uu tien instrument cua
|
||||
// CHINH TAB (st.instrumentProgram / st.instrumentId sf_*) — section scope va
|
||||
// tab co the load instrument rieng khac main track. Khong co -> fallback track.
|
||||
const resolveSubTabInstrumentCtx = (st, fallbackTrack, tracks) => {
|
||||
const all = tracks || [];
|
||||
const ch = fallbackTrack ? assignTrackMidiChannel(fallbackTrack, all) : 0;
|
||||
const stInstId = st && st.instrumentId;
|
||||
if (st && stInstId && typeof stInstId === 'string' && stInstId.startsWith('sf_')) {
|
||||
const sfId = stInstId.replace('sf_', '');
|
||||
const sfProg = (st.instrumentProgram !== undefined && st.instrumentProgram !== null) ? st.instrumentProgram : 0;
|
||||
const se = { type: 'soundfont', plugin_id: stInstId, soundfont_bank: 0, soundfont_program: sfProg, soundfont_id: sfId };
|
||||
return { ch, program: undefined, synthEngine: se, sfId, bank: 0, prog: sfProg };
|
||||
}
|
||||
if (st && st.instrumentProgram !== undefined && st.instrumentProgram !== null) {
|
||||
return { ch, program: st.instrumentProgram, synthEngine: undefined, sfId: undefined, bank: 0, prog: st.instrumentProgram };
|
||||
}
|
||||
return resolveTrackInstrumentCtx(fallbackTrack, all);
|
||||
};
|
||||
|
||||
// Đảm bảo FluidSynth channel của track đã select ĐÚNG instrument trước khi
|
||||
// notes bắn. Fire-and-forget: playNote tự load + retry nếu SF chưa load xong
|
||||
// (dedup sẵn trong loadSoundFont) — không chặn, không gây stall khi mở tab.
|
||||
@@ -126,6 +145,25 @@ const scheduleCarlaNote = (synthEngine, channel, pitch, velocity, startWallTime,
|
||||
} catch (e) { console.warn('[Carla] scheduleCarlaNote error:', e); }
|
||||
};
|
||||
|
||||
// ── Native VST GUI (Bug 1) ──────────────────────────────────────────────
|
||||
// Standalone (Tauri): mo cua so native editor qua command Rust open_vst_gui
|
||||
// (VST3 .vst3 / VST2 .dll attach HWND, khong spawn Carla ngoai app).
|
||||
// Browser: fallback ve Carla bridge cu.
|
||||
const kindFromPath = (p) => (p || '').toLowerCase().endsWith('.vst3') ? 'vst3' : 'vst2';
|
||||
const canOpenNativeVstGui = () => !!(window.__TAURI__ && window.__TAURI__.core) || !!(window.SonicRuntime && window.SonicRuntime.capabilities && window.SonicRuntime.capabilities.features && window.SonicRuntime.capabilities.features.carla_local);
|
||||
const openNativeVstGui = (pluginId, trackId, pluginPath, pluginKind) => {
|
||||
try {
|
||||
if (window.__TAURI__ && window.__TAURI__.core) {
|
||||
return window.__TAURI__.core.invoke('open_vst_gui', {
|
||||
pluginId: pluginId || '',
|
||||
trackId: trackId || '',
|
||||
pluginPath: pluginPath || '',
|
||||
pluginKind: pluginKind || kindFromPath(pluginPath),
|
||||
});
|
||||
}
|
||||
} catch (e) { console.warn('[VstGui] invoke error, fallback Carla:', e); }
|
||||
return window.SonicAPI && window.SonicAPI.openInCarla ? window.SonicAPI.openInCarla(pluginId, pluginPath) : Promise.resolve({ success: false });
|
||||
};
|
||||
// ── Carla bridge alive tracking + auto-open ────────────────────────────────
|
||||
// window.__carlaRunning: undefined = chưa biết | true = đang chạy | false = đã chết.
|
||||
// window.__carlaNoteQueue: nốt chờ flush khi Carla chưa ready (cold start).
|
||||
@@ -277,7 +315,11 @@ const _routePreviewNote = (trackId, ctx, pitch, velocity, durationMs, sourceType
|
||||
};
|
||||
const _stopMidiFilePreview = () => {
|
||||
if (_midiFilePreviewAudio) {
|
||||
try { _midiFilePreviewAudio.pause(); _midiFilePreviewAudio.currentTime = 0; } catch (e) {}
|
||||
try {
|
||||
// AudioBufferSourceNode chi co stop(); HTMLAudioElement chi co pause().
|
||||
if (_midiFilePreviewAudio.stop) { _midiFilePreviewAudio.stop(); }
|
||||
else { _midiFilePreviewAudio.pause(); _midiFilePreviewAudio.currentTime = 0; }
|
||||
} catch (e) {}
|
||||
_midiFilePreviewAudio = null;
|
||||
}
|
||||
};
|
||||
@@ -5945,7 +5987,7 @@ const PluginManagerModal = ({ isOpen, onClose, pluginsData, onInsertInstrument }
|
||||
),
|
||||
React.createElement('div', { className: 'flex items-center gap-2' },
|
||||
(window.SonicRuntime && window.SonicRuntime.capabilities && window.SonicRuntime.capabilities.features && window.SonicRuntime.capabilities.features.carla_local) && React.createElement('button', {
|
||||
onClick: (e) => { e.stopPropagation(); window.SonicAPI.openInCarla(v.id).then(function (r) { if (r && r.success) showToast('Đã mở Carla với ' + (v.name || v.id), 'success'); }).catch(function (err) { showToast('Lỗi mở Carla: ' + (err.message || err), 'error'); }); },
|
||||
onClick: (e) => { e.stopPropagation(); openNativeVstGui(v.id, selectedTrackId, v.path, v.type).then(function (r) { if (r && (r.success || typeof r === 'string')) showToast('Đã mở GUI với ' + (v.name || v.id), 'success'); }).catch(function (err) { showToast('Lỗi mở VST GUI: ' + (err.message || err), 'error'); }); },
|
||||
className: 'text-[10px] bg-teal-800 hover:bg-teal-700 text-white px-2 py-1 rounded transition shrink-0',
|
||||
title: 'Mở trong Carla (native GUI)'
|
||||
}, '🎛 Carla'),
|
||||
@@ -6102,7 +6144,7 @@ const PluginManagerModal = ({ isOpen, onClose, pluginsData, onInsertInstrument }
|
||||
React.createElement('span', { className: 'truncate' }, v.name),
|
||||
React.createElement('span', { className: 'text-[9px] text-zinc-600 font-mono truncate ml-auto' }, v.dir),
|
||||
(window.SonicRuntime && window.SonicRuntime.capabilities && window.SonicRuntime.capabilities.features && window.SonicRuntime.capabilities.features.carla_local) && React.createElement('button', {
|
||||
onClick: () => { window.SonicAPI.openInCarla(null, v.path).then(function (r) { if (r && r.success) showToast('Đã mở Carla với ' + v.name, 'success'); }).catch(function (err) { showToast('Lỗi mở Carla: ' + (err.message || err), 'error'); }); },
|
||||
onClick: () => { openNativeVstGui(v.name || v.id, selectedTrackId, v.path, v.type).then(function (r) { if (r && (r.success || typeof r === 'string')) showToast('Đã mở GUI với ' + v.name, 'success'); }).catch(function (err) { showToast('Lỗi mở VST GUI: ' + (err.message || err), 'error'); }); },
|
||||
className: 'text-[9px] bg-teal-800 hover:bg-teal-700 text-white px-1.5 py-0.5 rounded transition shrink-0',
|
||||
title: 'Mở trong Carla (native GUI)'
|
||||
}, '🎛')
|
||||
@@ -8287,7 +8329,7 @@ const PianoRollTabEditor = ({ st, zoom, bpm, viewportWidth, activeTracks, onClos
|
||||
currentBeat < n.start_beat && newBeat >= n.start_beat
|
||||
);
|
||||
var pvTrk = activeTracks.find(function(t) { return t.id === st.trackId; });
|
||||
var pvCtx = resolveTrackInstrumentCtx(pvTrk, activeTracks);
|
||||
var pvCtx = resolveSubTabInstrumentCtx(st, pvTrk, activeTracks);
|
||||
ensureSonicInstrument(pvCtx);
|
||||
playing.forEach(n => {
|
||||
if (isStandaloneSf() && isSfTrackEngine(pvCtx.synthEngine) && !shouldRouteCarla(pvCtx.synthEngine)) {
|
||||
@@ -8660,7 +8702,7 @@ const PianoRollTabEditor = ({ st, zoom, bpm, viewportWidth, activeTracks, onClos
|
||||
if (window.SonicSF) {
|
||||
const ctx = getAudioContext();
|
||||
var clTrk = activeTracks.find(function(t) { return t.id === st.trackId; });
|
||||
var clCtx = resolveTrackInstrumentCtx(clTrk, activeTracks);
|
||||
var clCtx = resolveSubTabInstrumentCtx(st, clTrk, activeTracks);
|
||||
ensureSonicInstrument(clCtx);
|
||||
if (isStandaloneSf() && isSfTrackEngine(clCtx.synthEngine) && !shouldRouteCarla(clCtx.synthEngine)) {
|
||||
if (!_routePreviewNote(clTrk && clTrk.id, clCtx, notes[clickedNoteIdx].pitch, notes[clickedNoteIdx].velocity || 0.8, 300, 'CLICK')) {
|
||||
@@ -8925,15 +8967,15 @@ const PianoRollTabEditor = ({ st, zoom, bpm, viewportWidth, activeTracks, onClos
|
||||
previewNodesRef.current = null;
|
||||
}
|
||||
var dwTrk = activeTracks.find(function(t) { return t.id === st.trackId; });
|
||||
var dwCtx = resolveTrackInstrumentCtx(dwTrk, activeTracks);
|
||||
var dwCtx = resolveSubTabInstrumentCtx(st, dwTrk, activeTracks);
|
||||
var dwCh = dwTrk ? assignTrackMidiChannel(dwTrk, activeTracks) : 0;
|
||||
var dwPitch = snapToScaleRef.current ? snapPitchToScale(pitch, selectedScaleRef.current) : pitch;
|
||||
var dwDurMs = Math.max(100, Math.round(initialDur * (60 / bpm) * 1000));
|
||||
if (isStandaloneSf() && isSfTrackEngine(dwTrk && dwTrk.synth_engine) && !shouldRouteCarla(dwTrk && dwTrk.synth_engine)) {
|
||||
if (isStandaloneSf() && isSfTrackEngine(dwCtx.synthEngine) && !shouldRouteCarla(dwCtx.synthEngine)) {
|
||||
if (!_routePreviewNote(dwTrk && dwTrk.id, dwCtx, dwPitch, brushVelocityRef.current || 0.8, dwDurMs, 'DRAW')) {
|
||||
if (window.SonicSF && window.SonicSF.playNote) {
|
||||
const ctx = getAudioContext();
|
||||
window.SonicSF.playNote(dwPitch, Math.round(brushVelocityRef.current * 127), dwDurMs, ctx.currentTime, dwTrk ? dwTrk.instrumentProgram : undefined, null, dwCh, dwTrk ? dwTrk.synth_engine : undefined);
|
||||
window.SonicSF.playNote(dwPitch, Math.round(brushVelocityRef.current * 127), dwDurMs, ctx.currentTime, dwCtx.program, null, dwCh, dwCtx.synthEngine);
|
||||
}
|
||||
}
|
||||
} else if (window.SonicSF && window.SonicSF.playNote) {
|
||||
@@ -8941,7 +8983,7 @@ const PianoRollTabEditor = ({ st, zoom, bpm, viewportWidth, activeTracks, onClos
|
||||
// playNote (FluidSynth — nhạc cụ THẬT của track). _playNoteFallback chỉ
|
||||
// là oscillator beep (sai âm với percussion/soundfont — user: note vẽ
|
||||
// mới nghe nhạc cụ track trước).
|
||||
window.SonicSF.playNote(dwPitch, Math.round(brushVelocityRef.current * 127), dwDurMs, ctx.currentTime, dwTrk ? dwTrk.instrumentProgram : undefined, null, dwCh, dwTrk ? dwTrk.synth_engine : undefined);
|
||||
window.SonicSF.playNote(dwPitch, Math.round(brushVelocityRef.current * 127), dwDurMs, ctx.currentTime, dwCtx.program, null, dwCh, dwCtx.synthEngine);
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -9034,7 +9076,7 @@ const PianoRollTabEditor = ({ st, zoom, bpm, viewportWidth, activeTracks, onClos
|
||||
// mastering FX của main out khi chain bật)
|
||||
try { if (window.__ensureMasteringRouting) window.__ensureMasteringRouting(); } catch (er) {}
|
||||
var pvTrk = activeTracks.find(function(t) { return t.id === st.trackId; });
|
||||
var pvCtxInst = resolveTrackInstrumentCtx(pvTrk, activeTracks);
|
||||
var pvCtxInst = resolveSubTabInstrumentCtx(st, pvTrk, activeTracks);
|
||||
if (isStandaloneSf() && isSfTrackEngine(pvCtxInst.synthEngine) && !shouldRouteCarla(pvCtxInst.synthEngine)) {
|
||||
if (!_routePreviewNote(pvTrk && pvTrk.id, pvCtxInst, p, brushVelocityRef.current || 0.8, durMs, 'DRAW')) {
|
||||
if (window.SonicSF && window.SonicSF.playNote) {
|
||||
@@ -9409,7 +9451,7 @@ const PianoRollTabEditor = ({ st, zoom, bpm, viewportWidth, activeTracks, onClos
|
||||
|
||||
const renderKeybed = () => {
|
||||
var kbTrk = activeTracks.find(function(t) { return t.id === st.trackId; });
|
||||
var kbCtx = resolveTrackInstrumentCtx(kbTrk, activeTracks);
|
||||
var kbCtx = resolveSubTabInstrumentCtx(st, kbTrk, activeTracks);
|
||||
ensureSonicInstrument(kbCtx);
|
||||
const keys = [];
|
||||
for (let pitch = 127; pitch >= PITCH_START; pitch--) {
|
||||
@@ -11823,10 +11865,10 @@ const FXRackModal = ({ track, onUpdateTrack, onClose }) => {
|
||||
<button
|
||||
onClick={() => {
|
||||
if (!ap.plugin) { window.showToast && window.showToast('Chọn VST FX trước khi Load Carla', 'warning'); return; }
|
||||
window.SonicAPI.openInCarla(ap.plugin, ap.plugin_path).then(r => {
|
||||
if (r && r.success) { window.showToast && window.showToast('Đã mở Carla với ' + ap.plugin + ' (VST FX) — chỉnh âm thanh trong Carla', 'success'); }
|
||||
else { window.showToast && window.showToast('Không mở được Carla', 'error'); }
|
||||
}).catch(err => window.showToast && window.showToast('Lỗi mở Carla: ' + (err.message || err), 'error'));
|
||||
openNativeVstGui(ap.plugin, track.id, ap.plugin_path, kindFromPath(ap.plugin_path)).then(r => {
|
||||
if (r && (r.success || typeof r === 'string')) { window.showToast && window.showToast('Đã mở GUI với ' + ap.plugin + ' (VST FX) — chỉnh âm thanh trong cửa sổ', 'success'); }
|
||||
else { window.showToast && window.showToast('Không mở được VST GUI', 'error'); }
|
||||
}).catch(err => window.showToast && window.showToast('Lỗi mở VST GUI: ' + (err.message || err), 'error'));
|
||||
}}
|
||||
className="px-4 py-1.5 bg-teal-700 hover:bg-teal-600 text-white text-xs font-semibold rounded transition flex items-center gap-1 shrink-0"
|
||||
>
|
||||
@@ -12952,12 +12994,12 @@ const MasteringModal = ({ isOpen, onClose, masteringSettings, setMasteringSettin
|
||||
onClick={() => {
|
||||
const plugin = (ozState.carlaBridge && ozState.carlaBridge.plugin) || '';
|
||||
if (!plugin) { window.showToast && window.showToast('Chọn VST FX trước khi Load Carla', 'warning'); return; }
|
||||
window.SonicAPI.openInCarla(plugin, ozState.carlaBridge.plugin_path).then(r => {
|
||||
if (r && r.success) {
|
||||
openNativeVstGui(plugin, '', ozState.carlaBridge.plugin_path, kindFromPath(ozState.carlaBridge.plugin_path)).then(r => {
|
||||
if (r && (r.success || typeof r === 'string')) {
|
||||
setOzState(prev => ({ ...prev, carlaBridge: { ...(prev.carlaBridge || {}), connected: true } }));
|
||||
window.showToast && window.showToast('Đã mở Carla với ' + plugin + ' (VST FX) — chỉnh âm thanh trong Carla', 'success');
|
||||
} else { window.showToast && window.showToast('Không mở được Carla', 'error'); }
|
||||
}).catch(err => window.showToast && window.showToast('Lỗi mở Carla: ' + (err.message || err), 'error'));
|
||||
window.showToast && window.showToast('Đã mở GUI với ' + plugin + ' (VST FX) — chỉnh âm thanh trong cửa sổ', 'success');
|
||||
} else { window.showToast && window.showToast('Không mở được VST GUI', 'error'); }
|
||||
}).catch(err => window.showToast && window.showToast('Lỗi mở VST GUI: ' + (err.message || err), 'error'));
|
||||
}}
|
||||
className="px-4 py-1.5 bg-teal-700 hover:bg-teal-600 text-white text-xs font-semibold rounded transition flex items-center gap-1 shrink-0"
|
||||
>
|
||||
@@ -14281,14 +14323,34 @@ const MediaExplorerPanel = ({ height, clipboardRef, active }) => {
|
||||
};
|
||||
});
|
||||
if (nativeNotes.length) {
|
||||
window.SonicAPI.soundfontRender({ soundfont_id: sfId, bank: bank, program: prog !== undefined ? prog : 0, bpm: bpmVal, notes: nativeNotes }).then(function (res) {
|
||||
window.SonicAPI.soundfontRender({ soundfont_id: sfId, bank: bank, program: prog !== undefined ? prog : 0, bpm: bpmVal, notes: nativeNotes }).then(async function (res) {
|
||||
if (!res || !res.success || !res.url) return;
|
||||
if (selectTokenRef.current !== (token || selectTokenRef.current)) return;
|
||||
const audio = new Audio(API_BASE_URL + res.url);
|
||||
audio.loop = !!isLoopingRef.current;
|
||||
if (_midiFilePreviewAudio) { try { _midiFilePreviewAudio.pause(); } catch (e) {} }
|
||||
_midiFilePreviewAudio = audio;
|
||||
audio.play().catch(function () {});
|
||||
// Preview qua masterBus (mastering FX main out khi chain bat; nguoc
|
||||
// lai qua dryInput) — truoc day new Audio() ra thang loa.
|
||||
try {
|
||||
const resp = await fetch(API_BASE_URL + res.url);
|
||||
const arrBuf = await resp.arrayBuffer();
|
||||
const ctx2 = getAudioContext();
|
||||
const decoded = await ctx2.decodeAudioData(arrBuf);
|
||||
if (selectTokenRef.current !== (token || selectTokenRef.current)) return;
|
||||
const src = ctx2.createBufferSource();
|
||||
src.buffer = decoded;
|
||||
src.loop = !!isLoopingRef.current;
|
||||
const g = ctx2.createGain();
|
||||
src.connect(g);
|
||||
const mb = window.masterBus;
|
||||
if (mb && mb.input && mb.dryInput) {
|
||||
g.connect(masteringChainOn() ? mb.input : mb.dryInput);
|
||||
} else {
|
||||
g.connect(ctx2.destination);
|
||||
}
|
||||
if (_midiFilePreviewAudio) {
|
||||
try { if (_midiFilePreviewAudio.stop) _midiFilePreviewAudio.stop(); else _midiFilePreviewAudio.pause(); } catch (e) {}
|
||||
}
|
||||
_midiFilePreviewAudio = src;
|
||||
src.start();
|
||||
} catch (e) { console.error('MIDI preview audio failed', e); }
|
||||
});
|
||||
}
|
||||
const startedAtTime = startWallTime - loopStartSec;
|
||||
@@ -22202,7 +22264,7 @@ const App = () => {
|
||||
// phải chơi ĐÚNG instrument đó. ensureSonicInstrument select channel đúng
|
||||
// trước khi notes bắn (fire-and-forget — playNote tự load+retry nếu SF
|
||||
// chưa xong).
|
||||
const instCtx = resolveTrackInstrumentCtx(track, activeTracksRef.current || []);
|
||||
const instCtx = resolveSubTabInstrumentCtx(st, track, activeTracksRef.current || []);
|
||||
const instrumentProgram = instCtx.program;
|
||||
const synthEngine = instCtx.synthEngine;
|
||||
const mainCh = instCtx.ch;
|
||||
@@ -22215,7 +22277,10 @@ const App = () => {
|
||||
// the item window, so no absolute-session offset is applied anywhere here.
|
||||
const routeCarla = shouldRouteCarla(synthEngine);
|
||||
if (isStandaloneSf() && !routeCarla && isSfTrackEngine(synthEngine)) {
|
||||
scheduleNativeSfItem(track, { startTime: 0, notes: midiNotes }, offsetSeconds, context, destNode, bpm, { sources: activeSourcesRef.current, isActive: () => !!isPlayingRef.current });
|
||||
// synthEngine co the la instrument cua CHINH TAB (resolveSubTabInstrumentCtx)
|
||||
// — pseudo-track de scheduleNativeSfItem render dung SF cua tab.
|
||||
const nativeTrack = synthEngine ? Object.assign({}, track, { synth_engine: synthEngine }) : track;
|
||||
scheduleNativeSfItem(nativeTrack, { 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 => {
|
||||
@@ -24476,7 +24541,9 @@ const App = () => {
|
||||
}
|
||||
} catch (e) {
|
||||
showToast('Không thể nạp file từ Media Explorer: ' + e.message, 'error');
|
||||
return null;
|
||||
}
|
||||
showToast('Không thể nạp file từ Media Explorer: thiếu dữ liệu file.', 'error');
|
||||
return null;
|
||||
};
|
||||
|
||||
@@ -29798,7 +29865,14 @@ STRICT CONSTRAINTS:
|
||||
}
|
||||
return;
|
||||
}
|
||||
const f = e.dataTransfer.files && e.dataTransfer.files[0];
|
||||
const dt = e.dataTransfer;
|
||||
let f = dt && dt.files && dt.files[0];
|
||||
if (!f && dt && dt.items) {
|
||||
for (let i = 0; i < dt.items.length; i++) {
|
||||
const it = dt.items[i];
|
||||
if (it.kind === 'file' && typeof it.getAsFile === 'function') { f = it.getAsFile(); if (f) break; }
|
||||
}
|
||||
}
|
||||
if (!f) return;
|
||||
if (/\.mid$|\.midi$/i.test(f.name || '')) {
|
||||
handleDropMidiToNewTracks(f);
|
||||
@@ -29838,7 +29912,14 @@ STRICT CONSTRAINTS:
|
||||
if (f) loadFileOnTrack(track.id, f);
|
||||
return;
|
||||
}
|
||||
const f = e.dataTransfer.files && e.dataTransfer.files[0];
|
||||
const dt = e.dataTransfer;
|
||||
let f = dt && dt.files && dt.files[0];
|
||||
if (!f && dt && dt.items) {
|
||||
for (let i = 0; i < dt.items.length; i++) {
|
||||
const it = dt.items[i];
|
||||
if (it.kind === 'file' && typeof it.getAsFile === 'function') { f = it.getAsFile(); if (f) break; }
|
||||
}
|
||||
}
|
||||
if (!f) return;
|
||||
loadFileOnTrack(track.id, f);
|
||||
},
|
||||
@@ -31295,10 +31376,10 @@ STRICT CONSTRAINTS:
|
||||
closeInstrumentSelector();
|
||||
// TỰ ĐỘNG mở Carla với VSTi vừa chọn (desktop + Carla local) —
|
||||
// Carla load sẵn plugin + keyboard ảo để preview realtime.
|
||||
if (window.SonicRuntime && window.SonicRuntime.capabilities && window.SonicRuntime.capabilities.features && window.SonicRuntime.capabilities.features.carla_local) {
|
||||
window.SonicAPI.openInCarla(v.id).then(function (r) {
|
||||
if (r && r.success) showToast('Đã mở Carla với ' + (v.name || v.id) + ' — chọn preset, bấm keyboard để preview', 'success');
|
||||
}).catch(function (err) { showToast('Lỗi mở Carla: ' + (err.message || err), 'error'); });
|
||||
if (canOpenNativeVstGui()) {
|
||||
openNativeVstGui(v.id, instrumentSelectorTrackId, v.path, v.type).then(function (r) {
|
||||
if (r && (r.success || typeof r === 'string')) showToast('Đã mở GUI với ' + (v.name || v.id) + ' — chỉnh tham số, bấm keyboard để preview', 'success');
|
||||
}).catch(function (err) { showToast('Lỗi mở VST GUI: ' + (err.message || err), 'error'); });
|
||||
}
|
||||
},
|
||||
className: "w-full text-left px-3 py-2 text-sm rounded bg-zinc-800 hover:bg-violet-800 text-zinc-300 flex items-center justify-between gap-2"
|
||||
@@ -31386,18 +31467,17 @@ STRICT CONSTRAINTS:
|
||||
setInstrumentDropdownTrackId(null);
|
||||
setInstrumentDropdownBtnRect(null);
|
||||
setTrackInstrumentWithUndo(instrumentDropdownTrackId, v.id, v.name || v.id);
|
||||
// TỰ ĐỘNG mở Carla với VSTi vừa chọn (desktop + Carla local) —
|
||||
// Carla load sẵn plugin, native GUI + keyboard ảo để preview realtime.
|
||||
if (window.SonicRuntime && window.SonicRuntime.capabilities && window.SonicRuntime.capabilities.features && window.SonicRuntime.capabilities.features.carla_local) {
|
||||
window.SonicAPI.openInCarla(v.id).then(function (r) {
|
||||
if (r && r.success) showToast('Đã mở Carla với ' + (v.name || v.id) + ' — chọn preset, bấm keyboard để preview', 'success');
|
||||
}).catch(function (err) { showToast('Lỗi mở Carla: ' + (err.message || err), 'error'); });
|
||||
// TỰ ĐỘNG mở native GUI với VSTi vừa chọn (standalone) / Carla (browser).
|
||||
if (canOpenNativeVstGui()) {
|
||||
openNativeVstGui(v.id, instrumentDropdownTrackId, v.path, v.type).then(function (r) {
|
||||
if (r && (r.success || typeof r === 'string')) showToast('Đã mở GUI với ' + (v.name || v.id) + ' — chọn preset, bấm keyboard để preview', 'success');
|
||||
}).catch(function (err) { showToast('Lỗi mở VST GUI: ' + (err.message || err), 'error'); });
|
||||
}
|
||||
},
|
||||
className: "flex-1 min-w-0 text-left px-3 py-1.5 text-xs bg-zinc-800 hover:bg-violet-800 text-zinc-200 flex items-center justify-between"
|
||||
}, /*#__PURE__*/React.createElement("span", { className: "truncate" }, v.name || v.id), /*#__PURE__*/React.createElement("span", { className: "text-[10px] text-cyan-400 shrink-0 ml-1" }, v.type || "VST")),
|
||||
window.SonicRuntime && window.SonicRuntime.capabilities && window.SonicRuntime.capabilities.features && window.SonicRuntime.capabilities.features.carla_local ? /*#__PURE__*/React.createElement("button", {
|
||||
onClick: (e) => { e.stopPropagation(); window.SonicAPI.openInCarla(v.id).then(function (r) { if (r && r.success) { showToast('Đã mở Carla: ' + (v.name || v.id), 'success'); } }).catch(function (err) { showToast('Lỗi mở Carla: ' + (err.message || err), 'error'); }); },
|
||||
onClick: (e) => { e.stopPropagation(); openNativeVstGui(v.id, instrumentDropdownTrackId, v.path, v.type).then(function (r) { if (r && (r.success || typeof r === 'string')) { showToast('Đã mở GUI: ' + (v.name || v.id), 'success'); } }).catch(function (err) { showToast('Lỗi mở VST GUI: ' + (err.message || err), 'error'); }); },
|
||||
className: "shrink-0 px-2 text-xs bg-zinc-800 hover:bg-teal-700 text-teal-300 border-l border-zinc-700",
|
||||
title: "Mở trong Carla (native GUI)"
|
||||
}, "\uD83C\uDF9B") : null
|
||||
|
||||
Reference in New Issue
Block a user