feat: Synth button opens modal with flat instrument list from all SFs
- openInstrumentSelector builds flat list of all instrument presets - Modal shows all instruments with search bar - Each preset shows sf name + program name - Remove old two-step (SF list -> presets) navigation - Fix pre-existing closing paren mismatch in PluginManagerModal
This commit is contained in:
+69
-91
@@ -3576,9 +3576,8 @@ const PluginManagerModal = ({ isOpen, onClose, pluginsData }) => {
|
||||
)
|
||||
)
|
||||
)
|
||||
));
|
||||
);
|
||||
};
|
||||
|
||||
const ProfileModal = ({
|
||||
isOpen,
|
||||
onClose,
|
||||
@@ -6486,29 +6485,40 @@ const App = () => {
|
||||
const openInstrumentSelector = trackId => {
|
||||
setInstrumentSelectorTrackId(trackId);
|
||||
setSfPresetSearchQuery('');
|
||||
const track = activeTracks.find(t => t.id === trackId);
|
||||
if (track && track.instrumentId && track.instrumentId.startsWith('sf_')) {
|
||||
// Track already has a SoundFont assigned → open instrument selection directly
|
||||
setSynthCategory('soundfont');
|
||||
setSelectedSoundFontId(track.instrumentId);
|
||||
setSfPresets(null);
|
||||
setSfPresetSearchQuery('');
|
||||
// Use cached presets from instrumentSelectorData
|
||||
const sfIdParam = track.instrumentId.replace('sf_', '');
|
||||
const cachedSf = (instrumentSelectorData?.soundfonts || []).find(s => s.id === track.instrumentId || s.id === sfIdParam);
|
||||
if (cachedSf && cachedSf.presets) {
|
||||
setSfPresets(cachedSf.presets);
|
||||
} else {
|
||||
window.SonicAPI.listSoundfontInstruments(sfIdParam)
|
||||
.then(data => setSfPresets(data.presets || []))
|
||||
.catch(() => setSfPresets([]));
|
||||
}
|
||||
} else {
|
||||
// Reset synth state and always reload plugin data
|
||||
setSynthCategory(null);
|
||||
setSelectedSoundFontId(null);
|
||||
setSfPresetSearchQuery('');
|
||||
window.SonicAPI.listPlugins().then(data => setInstrumentSelectorData(data)).catch(e => console.error('listPlugins failed:', e));
|
||||
// Build flat instrument list from all soundfonts
|
||||
const sfonts = instrumentSelectorData?.soundfonts || [];
|
||||
const allInstruments = [];
|
||||
(sfonts || []).forEach(sf => {
|
||||
const sfId = sf.id.startsWith('sf_') ? sf.id : 'sf_' + sf.id;
|
||||
const sfName = sf.display || sf.name || sf.id;
|
||||
(sf.presets || []).forEach(p => {
|
||||
allInstruments.push({
|
||||
...p,
|
||||
_sfId: sfId,
|
||||
_sfName: sfName,
|
||||
_sfDisplay: sfName.substring(0, 30)
|
||||
});
|
||||
});
|
||||
});
|
||||
setSfPresets(allInstruments.length > 0 ? allInstruments : null);
|
||||
// Fallback: if no cached presets, fetch on demand per soundfont
|
||||
if (allInstruments.length === 0) {
|
||||
Promise.all((sfonts || []).map(sf => {
|
||||
const sfId = sf.id.replace('sf_', '');
|
||||
return window.SonicAPI.listSoundfontInstruments(sfId)
|
||||
.then(data => ({ sf, presets: data.presets || [] }))
|
||||
.catch(() => ({ sf, presets: [] }));
|
||||
})).then(results => {
|
||||
const all = [];
|
||||
results.forEach(({ sf, presets }) => {
|
||||
const sfId = sf.id.startsWith('sf_') ? sf.id : 'sf_' + sf.id;
|
||||
const sfName = sf.display || sf.name || sf.id;
|
||||
presets.forEach(p => {
|
||||
all.push({ ...p, _sfId: sfId, _sfName: sfName, _sfDisplay: sfName.substring(0, 30) });
|
||||
});
|
||||
});
|
||||
setSfPresets(all);
|
||||
});
|
||||
}
|
||||
};
|
||||
const closeInstrumentSelector = () => {
|
||||
@@ -17073,74 +17083,42 @@ const App = () => {
|
||||
}, /*#__PURE__*/React.createElement("div", {
|
||||
className: "bg-[#262626] border border-[#383838] rounded-xl shadow-2xl w-full max-w-md p-5 text-slate-200",
|
||||
onClick: e => e.stopPropagation()
|
||||
}, synthCategory === 'soundfont' ? (
|
||||
/*#__PURE__*/React.createElement(React.Fragment, null,
|
||||
/*#__PURE__*/React.createElement("div", { className: "flex justify-between items-center pb-3 border-b border-[#383838]" },
|
||||
/*#__PURE__*/React.createElement("div", null,
|
||||
/*#__PURE__*/React.createElement("button", { onClick: () => { setSynthCategory(null); setSelectedSoundFontId(null); setSfPresetSearchQuery(''); }, className: "text-[10px] text-cyan-400 hover:text-cyan-300 mr-2" }, "\u2190 Back"),
|
||||
/*#__PURE__*/React.createElement("h3", { className: "text-sm font-bold inline text-amber-400" }, "Select Instrument")
|
||||
),
|
||||
/*#__PURE__*/React.createElement("button", { onClick: closeInstrumentSelector, className: "text-slate-400 hover:text-slate-200" }, "\u2715")
|
||||
),
|
||||
/*#__PURE__*/React.createElement("p", { className: "text-[10px] text-zinc-500 mt-2 mb-2" }, "SoundFont: ", selectedSoundFontId),
|
||||
/*#__PURE__*/React.createElement("input", {
|
||||
type: "text",
|
||||
placeholder: "T\u00ecm nh\u1ea1c c\u1ee5 trong SoundFont...",
|
||||
value: sfPresetSearchQuery,
|
||||
onChange: e => setSfPresetSearchQuery(e.target.value),
|
||||
autoFocus: true,
|
||||
className: "w-full bg-black border border-zinc-700 rounded px-2 py-1.5 text-xs outline-none mb-2"
|
||||
}),
|
||||
/*#__PURE__*/React.createElement("div", { className: "mt-2 max-h-72 overflow-y-auto space-y-0.5" },
|
||||
/*#__PURE__*/React.createElement("button", {
|
||||
onClick: () => setTrackInstrumentWithProgram(instrumentSelectorTrackId, selectedSoundFontId),
|
||||
className: "w-full text-left px-3 py-1.5 text-xs rounded bg-zinc-800 hover:bg-zinc-700 text-zinc-400"
|
||||
}, "None (Default Program)"),
|
||||
sfPresets === null ? (
|
||||
React.createElement("p", { className: "text-[10px] text-zinc-500 py-2" }, "Loading instruments...")
|
||||
) : sfPresets.length > 0 ? (
|
||||
React.createElement("div", { className: "grid grid-cols-2 gap-0.5" },
|
||||
sfPresets
|
||||
.filter(p => !sfPresetSearchQuery || (p.name || '').toLowerCase().includes(sfPresetSearchQuery.toLowerCase()))
|
||||
.map((p, i) => React.createElement("button", {
|
||||
},
|
||||
/*#__PURE__*/React.createElement("div", { className: "flex justify-between items-center pb-3 border-b border-[#383838]" },
|
||||
/*#__PURE__*/React.createElement("h3", { className: "text-sm font-bold text-amber-400" }, "Select Instrument"),
|
||||
/*#__PURE__*/React.createElement("button", { onClick: closeInstrumentSelector, className: "text-slate-400 hover:text-slate-200" }, "\u2715")
|
||||
),
|
||||
/*#__PURE__*/React.createElement("input", {
|
||||
type: "text",
|
||||
placeholder: "T\u00ecm nh\u1ea1c c\u1ee5...",
|
||||
value: sfPresetSearchQuery,
|
||||
onChange: e => setSfPresetSearchQuery(e.target.value),
|
||||
autoFocus: true,
|
||||
className: "w-full bg-black border border-zinc-700 rounded px-2 py-1.5 text-xs outline-none my-3"
|
||||
}),
|
||||
/*#__PURE__*/React.createElement("div", { className: "max-h-72 overflow-y-auto space-y-0.5" },
|
||||
/*#__PURE__*/React.createElement("button", {
|
||||
onClick: () => setTrackInstrumentWithProgram(instrumentSelectorTrackId, null),
|
||||
className: "w-full text-left px-3 py-1.5 text-xs rounded bg-zinc-800 hover:bg-zinc-700 text-zinc-400 mb-1"
|
||||
}, "None (Default Synth)"),(
|
||||
sfPresets === null ? (
|
||||
/*#__PURE__*/React.createElement("p", { className: "text-[10px] text-zinc-500 py-2" }, "Loading instruments...")
|
||||
) : sfPresets.length > 0 ? (
|
||||
/*#__PURE__*/React.createElement("div", { className: "space-y-0.5" },
|
||||
sfPresets
|
||||
.filter(p => !sfPresetSearchQuery || (p.name || '').toLowerCase().includes(sfPresetSearchQuery.toLowerCase()) || (p._sfName || '').toLowerCase().includes(sfPresetSearchQuery.toLowerCase()))
|
||||
.map((p, i) => /*#__PURE__*/React.createElement("button", {
|
||||
key: i,
|
||||
onClick: () => setTrackInstrumentWithProgram(instrumentSelectorTrackId, selectedSoundFontId, p.program, p.name || 'Preset ' + p.program, p.bank),
|
||||
className: "text-left px-2 py-1 text-[10px] rounded bg-zinc-800/60 hover:bg-amber-800 text-zinc-300 truncate"
|
||||
}, p.bank === 128 ? /*#__PURE__*/React.createElement("span", { className: "mr-1" }, "🥁") : null, p.name || 'Preset ' + p.program)
|
||||
))
|
||||
) : (
|
||||
React.createElement("p", { className: "text-[10px] text-zinc-500 py-2" }, sfPresetSearchQuery ? "Kh\u00f4ng t\u00ecm th\u1ea5y nh\u1ea1c c\u1ee5 ph\u00f9 h\u1ee3p." : "No presets found.")
|
||||
onClick: () => setTrackInstrumentWithProgram(instrumentSelectorTrackId, p._sfId, p.program, p.name || 'Preset ' + p.program, p.bank),
|
||||
className: "w-full text-left px-2 py-1 text-[10px] rounded bg-zinc-800/60 hover:bg-amber-800 text-zinc-300 truncate flex items-center gap-2"
|
||||
},
|
||||
/*#__PURE__*/React.createElement("span", { className: "text-[9px] text-zinc-500 shrink-0" }, p._sfDisplay),
|
||||
p.bank === 128 ? /*#__PURE__*/React.createElement("span", { className: "mr-1" }, "🥁") : null,
|
||||
p.name || 'Preset ' + p.program
|
||||
))
|
||||
)
|
||||
)
|
||||
)
|
||||
) : (
|
||||
/*#__PURE__*/React.createElement(React.Fragment, null,
|
||||
/*#__PURE__*/React.createElement("div", { className: "flex justify-between items-center pb-3 border-b border-[#383838]" },
|
||||
/*#__PURE__*/React.createElement("h3", { className: "text-sm font-bold text-violet-400" }, "Synth Selector")
|
||||
),
|
||||
/*#__PURE__*/React.createElement("div", { className: "mt-3 max-h-80 overflow-y-auto space-y-1" },
|
||||
/*#__PURE__*/React.createElement("button", {
|
||||
onClick: () => setTrackInstrumentWithProgram(instrumentSelectorTrackId, null),
|
||||
className: "w-full text-left px-3 py-2 text-xs rounded bg-zinc-800 hover:bg-zinc-700 text-zinc-400"
|
||||
}, "None (Default Synth)"),
|
||||
/*#__PURE__*/React.createElement("div", { className: "text-[10px] text-zinc-500 mt-2 mb-1 uppercase font-bold" }, "SoundFonts"),
|
||||
instrumentSelectorData?.soundfonts?.map((sf, i) =>
|
||||
/*#__PURE__*/React.createElement("button", {
|
||||
key: "sf_" + i,
|
||||
onClick: () => setTrackInstrument(instrumentSelectorTrackId, "sf_" + sf.id, sf.display || sf.name || sf.id),
|
||||
className: "w-full text-left px-3 py-2 text-xs rounded bg-zinc-800 hover:bg-amber-800 text-zinc-200 flex items-center justify-between"
|
||||
}, /*#__PURE__*/React.createElement("span", null, sf.display || sf.name || sf.id), /*#__PURE__*/React.createElement("span", { className: "text-[10px] text-amber-400" }, "SoundFont"))
|
||||
),
|
||||
/*#__PURE__*/React.createElement("div", { className: "text-[10px] text-zinc-500 mt-2 mb-1 uppercase font-bold" }, "VST Instruments"),
|
||||
instrumentSelectorData?.vst_instruments?.map((v, i) =>
|
||||
/*#__PURE__*/React.createElement("button", {
|
||||
key: "vst_" + i,
|
||||
onClick: () => setTrackInstrumentWithProgram(instrumentSelectorTrackId, v.id, undefined, v.name || v.id),
|
||||
className: "w-full text-left px-3 py-2 text-xs rounded bg-zinc-800 hover:bg-violet-900 text-zinc-200 flex items-center justify-between"
|
||||
}, /*#__PURE__*/React.createElement("span", null, v.name || v.id), /*#__PURE__*/React.createElement("span", { className: "text-[10px] text-cyan-400" }, v.type))
|
||||
),
|
||||
(!instrumentSelectorData || (!instrumentSelectorData.vst_instruments?.length && !instrumentSelectorData.soundfonts?.length)) &&
|
||||
/*#__PURE__*/React.createElement("p", { className: "text-xs text-zinc-500 py-4 text-center" }, "No plugins available. Upload SoundFont via Tools \u2192 Plugin Manager.")
|
||||
) : (
|
||||
/*#__PURE__*/React.createElement("p", { className: "text-[10px] text-zinc-500 py-2" }, sfPresetSearchQuery ? "Kh\u00f4ng t\u00ecm th\u1ea5y nh\u1ea1c c\u1ee5 ph\u00f9 h\u1ee3p." : "No presets found.")
|
||||
)
|
||||
)
|
||||
))), fxSelectorTrackId && /*#__PURE__*/React.createElement("div", {
|
||||
|
||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user