fix: sửa lỗi cho AI thực hiện tạo MIDI

This commit is contained in:
2026-07-23 18:19:12 +07:00
parent b46a2c45f2
commit 055f351a17
8 changed files with 192 additions and 18 deletions
+175 -1
View File
@@ -5074,6 +5074,54 @@ const App = () => {
const openPanel = id => {
if (id === 'export') setShowExportPanel(true); else if (id === 'ai') setShowAIPanel(true); else if (id === 'python_tools') setShowPythonToolsPanel(true); else if (id === 'selection') setShowSelectionPanel(true);
};
const [instrumentSelectorTrackId, setInstrumentSelectorTrackId] = useState(null);
const [instrumentSelectorData, setInstrumentSelectorData] = useState(null);
const openInstrumentSelector = trackId => {
setInstrumentSelectorTrackId(trackId);
if (!instrumentSelectorData) {
window.SonicAPI.listPlugins().then(data => setInstrumentSelectorData(data)).catch(() => {});
}
};
const closeInstrumentSelector = () => setInstrumentSelectorTrackId(null);
const [synthCategory, setSynthCategory] = useState(null); // 'vst' | 'soundfont'
const [selectedSoundFontId, setSelectedSoundFontId] = useState(null);
const GM_INSTRUMENTS = [
"Acoustic Grand Piano","Bright Acoustic Piano","Electric Grand Piano","Honky-tonk Piano","Electric Piano 1","Electric Piano 2","Harpsichord","Clavi",
"Celesta","Glockenspiel","Music Box","Vibraphone","Marimba","Xylophone","Tubular Bells","Dulcimer",
"Drawbar Organ","Percussive Organ","Rock Organ","Church Organ","Reed Organ","Accordion","Harmonica","Tango Accordion",
"Acoustic Guitar (nylon)","Acoustic Guitar (steel)","Electric Guitar (jazz)","Electric Guitar (clean)","Electric Guitar (muted)","Overdriven Guitar","Distortion Guitar","Guitar harmonics",
"Acoustic Bass","Electric Bass (finger)","Electric Bass (pick)","Fretless Bass","Slap Bass 1","Slap Bass 2","Synth Bass 1","Synth Bass 2",
"Violin","Viola","Cello","Contrabass","Tremolo Strings","Pizzicato Strings","Orchestral Harp","Timpani",
"String Ensemble 1","String Ensemble 2","Synth Strings 1","Synth Strings 2","Choir Aahs","Voice Oohs","Synth Voice","Orchestra Hit",
"Trumpet","Trombone","Tuba","Muted Trumpet","French Horn","Brass Section","Synth Brass 1","Synth Brass 2",
"Soprano Sax","Alto Sax","Tenor Sax","Baritone Sax","Oboe","English Horn","Bassoon","Clarinet",
"Piccolo","Flute","Recorder","Pan Flute","Blown Bottle","Shakuhachi","Whistle","Ocarina",
"Lead 1 (square)","Lead 2 (sawtooth)","Lead 3 (calliope)","Lead 4 (chiff)","Lead 5 (charang)","Lead 6 (voice)","Lead 7 (fifths)","Lead 8 (bass+lead)",
"Pad 1 (new age)","Pad 2 (warm)","Pad 3 (polysynth)","Pad 4 (choir)","Pad 5 (bowed)","Pad 6 (metallic)","Pad 7 (halo)","Pad 8 (sweep)",
"FX 1 (rain)","FX 2 (soundtrack)","FX 3 (crystal)","FX 4 (atmosphere)","FX 5 (brightness)","FX 6 (goblins)","FX 7 (echoes)","FX 8 (sci-fi)",
"Sitar","Banjo","Shamisen","Koto","Kalimba","Bag pipe","Fiddle","Shanai",
"Tinkle Bell","Agogo","Steel Drums","Woodblock","Taiko Drum","Melodic Tom","Synth Drum","Reverse Cymbal",
"Guitar Fret Noise","Breath Noise","Seashore","Bird Tweet","Telephone Ring","Helicopter","Applause","Gunshot"
];
const setTrackInstrumentWithProgram = (trackId, instrumentId, programNumber) => {
updateActiveTracks(prev => prev.map(t => {
if (t.id !== trackId) return t;
return { ...t, instrumentId, instrumentProgram: programNumber !== undefined ? programNumber : undefined };
}));
setInstrumentSelectorTrackId(null);
setSynthCategory(null);
setSelectedSoundFontId(null);
setTimeout(() => lucide.createIcons(), 50);
};
const setTrackInstrument = (trackId, instrumentId) => {
if (instrumentId && instrumentId.startsWith('sf_')) {
setSelectedSoundFontId(instrumentId);
setSynthCategory('soundfont');
setInstrumentSelectorTrackId(trackId);
} else {
setTrackInstrumentWithProgram(trackId, instrumentId);
}
};
const [activeTool, setActiveTool] = useState('select'); // 'select' | 'grab' | 'razor'
const [snapValue, setSnapValue] = useState('free'); // 'free', '1', '1/2', '1/4', '1/8', '1/16', '1/32'
@@ -11410,6 +11458,68 @@ const App = () => {
showToast(`Đã nạp ${aiTracks.length} tracks MIDI thế hệ AI!`, 'success');
return { success: true };
},
createMidiItem: (args) => {
const trackId = args.track_id || selectedTrackId;
if (!trackId) return { success: false, error: 'No track_id provided' };
const secondsPerBar = (60.0 / (parseInt(bpm) || 120)) * 4;
const startBar = args.start_bar !== undefined ? parseFloat(args.start_bar) : 0;
const lengthBars = args.length_bars !== undefined ? parseFloat(args.length_bars) : 4;
const midiItem = {
id: `midi_${Date.now()}_${Math.random().toString(36).substr(2, 5)}`,
name: 'MIDI Item',
startTime: startBar * secondsPerBar,
duration: lengthBars * secondsPerBar,
notes: [],
color: '#a78bfa'
};
updateActiveTracks(prev => prev.map(t => {
if (t.id !== trackId && t.id !== 'track_' + trackId) return t;
return {
...t,
midiItems: [...(t.midiItems || []), midiItem]
};
}));
return { success: true, itemId: midiItem.id, trackId };
},
modifyMidiNotes: (args) => {
const itemId = args.item_id;
if (!itemId) return { success: false, error: 'No item_id provided' };
const noteNameToMidi = (name) => {
if (typeof name === 'number') return name;
if (!name || typeof name !== 'string') return 60;
const match = name.match(/^([A-Ga-g]#?|b?)(-?\d+)$/);
if (!match) {
const num = parseInt(name);
return isNaN(num) ? 60 : num;
}
const noteNames = { 'c': 0, 'c#': 1, 'db': 1, 'd': 2, 'd#': 3, 'eb': 3, 'e': 4, 'f': 5, 'f#': 6, 'gb': 6, 'g': 7, 'g#': 8, 'ab': 8, 'a': 9, 'a#': 10, 'bb': 10, 'b': 11 };
const key = match[1].toLowerCase();
const octave = parseInt(match[2]);
const base = noteNames[key] !== undefined ? noteNames[key] : 0;
return (octave + 1) * 12 + base;
};
const newNotes = (args.notes || []).map((n, index) => ({
id: `note_mod_${Date.now()}_${index}`,
pitch: noteNameToMidi(n.pitch),
start_beat: parseFloat(n.start_time || 0),
duration_beats: parseFloat(n.duration || 1),
velocity: n.velocity !== undefined ? n.velocity / 127.0 : 0.8,
pan: 0.0
}));
updateActiveTracks(prev => prev.map(t => {
const items = t.midiItems || [];
const exists = items.some(m => m.id === itemId);
if (!exists) return t;
return {
...t,
midiItems: items.map(m => m.id === itemId ? { ...m, notes: newNotes } : m)
};
}));
return { success: true, itemId };
}
};
window.DAWCommandDispatcher.registerDAWCommands(api);
@@ -12997,6 +13107,10 @@ const App = () => {
title: "ARM (Record)",
className: `px-1.5 py-0.5 text-[10px] rounded font-mono font-bold border transition flex items-center gap-0.5 ${track.isArmed ? 'bg-red-600 text-white border-red-500 hover:bg-red-500' : 'bg-zinc-800 text-zinc-400 border-transparent hover:text-zinc-200'}`
}, /*#__PURE__*/React.createElement("i", { "data-lucide": "circle", className: `w-2.5 h-2.5 ${track.isArmed ? 'fill-white' : ''}` })), /*#__PURE__*/React.createElement("button", {
onClick: e => { e.stopPropagation(); openInstrumentSelector(track.id); },
title: track.instrumentId ? (track.instrumentProgram !== undefined ? track.instrumentId + ' [' + track.instrumentProgram + ']' : track.instrumentId) : "Synth",
className: `px-1.5 py-0.5 text-[10px] rounded font-mono font-bold border transition flex items-center gap-0.5 ${track.instrumentId ? 'bg-violet-900 text-violet-300 border-violet-700' : 'bg-zinc-800 text-zinc-500 border-transparent hover:text-zinc-300'}`
}, /*#__PURE__*/React.createElement("i", { "data-lucide": "music", className: "w-3 h-3" })), /*#__PURE__*/React.createElement("button", {
onClick: e => {
e.stopPropagation();
toggleTrackMonitor(track.id);
@@ -14170,7 +14284,67 @@ const App = () => {
}), /*#__PURE__*/React.createElement(AIPresetModal, {
isOpen: aiPresetModalOpen,
onClose: () => setAiPresetModalOpen(false)
}));
}), instrumentSelectorTrackId && /*#__PURE__*/React.createElement("div", {
className: "fixed inset-0 z-50 flex items-center justify-center bg-black/60 backdrop-blur-sm",
onClick: closeInstrumentSelector
}, /*#__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); }, 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("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)"),
/*#__PURE__*/React.createElement("div", { className: "grid grid-cols-2 gap-0.5" },
GM_INSTRUMENTS.map((name, i) => /*#__PURE__*/React.createElement("button", {
key: i,
onClick: () => setTrackInstrumentWithProgram(instrumentSelectorTrackId, selectedSoundFontId, i),
className: "text-left px-2 py-1 text-[10px] rounded bg-zinc-800/60 hover:bg-amber-800 text-zinc-300 truncate"
}, i + ". " + name))
)
)
)
) : (
/*#__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),
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.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),
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.")
)
)
))));
};
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(/*#__PURE__*/React.createElement(App, null));
File diff suppressed because one or more lines are too long
@@ -68,9 +68,9 @@ const DAWCommandDispatcher = (function() {
register('SET_PLAYHEAD', (args) => api.setPlayhead(args));
register('SELECT_ITEM', (args) => api.selectItem(args));
register('ADD_MARKER', (args) => api.addMarker(args));
register('CREATE_MIDI_ITEM', (args) => AIGateway.createMidiItem(args));
register('MODIFY_MIDI_NOTES', (args) => AIGateway.modifyMidiNotes(args));
register('PROCESS_AI_DSP', (args) => AIGateway.processAIDSP(args));
register('CREATE_MIDI_ITEM', (args) => api.createMidiItem(args));
register('MODIFY_MIDI_NOTES', (args) => api.modifyMidiNotes(args));
register('PROCESS_AI_DSP', (args) => api.processAudioDsp(args));
register('GENERATE_MULTITRACK_MIDI', (args) => api.generateMultitrackMidi(args));
}