fix: sửa lỗi cho AI thực hiện tạo MIDI
This commit is contained in:
@@ -15,7 +15,7 @@ import json
|
|||||||
@router.post("/proxy")
|
@router.post("/proxy")
|
||||||
async def proxy_llm(req: ProxyRequest):
|
async def proxy_llm(req: ProxyRequest):
|
||||||
try:
|
try:
|
||||||
async with httpx.AsyncClient(timeout=60.0) as client:
|
async with httpx.AsyncClient(timeout=180.0) as client:
|
||||||
resp = await client.post(
|
resp = await client.post(
|
||||||
req.url,
|
req.url,
|
||||||
headers={k: v for k, v in req.headers.items() if k.lower() not in ('host', 'origin', 'referer')},
|
headers={k: v for k, v in req.headers.items() if k.lower() not in ('host', 'origin', 'referer')},
|
||||||
|
|||||||
+175
-1
@@ -5074,6 +5074,54 @@ const App = () => {
|
|||||||
const openPanel = id => {
|
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);
|
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 [activeTool, setActiveTool] = useState('select'); // 'select' | 'grab' | 'razor'
|
||||||
const [snapValue, setSnapValue] = useState('free'); // 'free', '1', '1/2', '1/4', '1/8', '1/16', '1/32'
|
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');
|
showToast(`Đã nạp ${aiTracks.length} tracks MIDI thế hệ AI!`, 'success');
|
||||||
return { success: true };
|
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);
|
window.DAWCommandDispatcher.registerDAWCommands(api);
|
||||||
@@ -12997,6 +13107,10 @@ const App = () => {
|
|||||||
title: "ARM (Record)",
|
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'}`
|
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", {
|
}, /*#__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 => {
|
onClick: e => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
toggleTrackMonitor(track.id);
|
toggleTrackMonitor(track.id);
|
||||||
@@ -14170,7 +14284,67 @@ const App = () => {
|
|||||||
}), /*#__PURE__*/React.createElement(AIPresetModal, {
|
}), /*#__PURE__*/React.createElement(AIPresetModal, {
|
||||||
isOpen: aiPresetModalOpen,
|
isOpen: aiPresetModalOpen,
|
||||||
onClose: () => setAiPresetModalOpen(false)
|
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'));
|
const root = ReactDOM.createRoot(document.getElementById('root'));
|
||||||
root.render(/*#__PURE__*/React.createElement(App, null));
|
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('SET_PLAYHEAD', (args) => api.setPlayhead(args));
|
||||||
register('SELECT_ITEM', (args) => api.selectItem(args));
|
register('SELECT_ITEM', (args) => api.selectItem(args));
|
||||||
register('ADD_MARKER', (args) => api.addMarker(args));
|
register('ADD_MARKER', (args) => api.addMarker(args));
|
||||||
register('CREATE_MIDI_ITEM', (args) => AIGateway.createMidiItem(args));
|
register('CREATE_MIDI_ITEM', (args) => api.createMidiItem(args));
|
||||||
register('MODIFY_MIDI_NOTES', (args) => AIGateway.modifyMidiNotes(args));
|
register('MODIFY_MIDI_NOTES', (args) => api.modifyMidiNotes(args));
|
||||||
register('PROCESS_AI_DSP', (args) => AIGateway.processAIDSP(args));
|
register('PROCESS_AI_DSP', (args) => api.processAudioDsp(args));
|
||||||
register('GENERATE_MULTITRACK_MIDI', (args) => api.generateMultitrackMidi(args));
|
register('GENERATE_MULTITRACK_MIDI', (args) => api.generateMultitrackMidi(args));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
@@ -10,13 +10,13 @@
|
|||||||
<script src="https://unpkg.com/lucide@latest"></script>
|
<script src="https://unpkg.com/lucide@latest"></script>
|
||||||
<script src="https://unpkg.com/react@18.3.1/umd/react.production.min.js"></script>
|
<script src="https://unpkg.com/react@18.3.1/umd/react.production.min.js"></script>
|
||||||
<script src="https://unpkg.com/react-dom@18.3.1/umd/react-dom.production.min.js"></script>
|
<script src="https://unpkg.com/react-dom@18.3.1/umd/react-dom.production.min.js"></script>
|
||||||
<script src="/static/js/services/api.js"></script>
|
<script src="/static/js/services/api.js?v=202607231808"></script>
|
||||||
<script src="/static/js/services/audioEngine.js"></script>
|
<script src="/static/js/services/audioEngine.js?v=202607231808"></script>
|
||||||
<script src="/static/js/services/storage.js"></script>
|
<script src="/static/js/services/storage.js?v=202607231808"></script>
|
||||||
<script src="/static/js/services/soundfontPlayer.js"></script>
|
<script src="/static/js/services/soundfontPlayer.js?v=202607231808"></script>
|
||||||
<script src="/static/js/services/aiGateway.js"></script>
|
<script src="/static/js/services/aiGateway.js?v=202607231808"></script>
|
||||||
<script src="/static/js/services/dawCommandDispatcher.js"></script>
|
<script src="/static/js/services/dawCommandDispatcher.js?v=202607231808"></script>
|
||||||
<script src="/static/js/app.precompiled.js?v=202607231122" defer></script>
|
<script src="/static/js/app.precompiled.js?v=202607231808" defer></script>
|
||||||
<style>
|
<style>
|
||||||
:root {
|
:root {
|
||||||
--right-sidebar-width: 320px;
|
--right-sidebar-width: 320px;
|
||||||
|
|||||||
+1
-1
@@ -2,7 +2,7 @@
|
|||||||
"name": "sonicforge-studio",
|
"name": "sonicforge-studio",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "babel app/static/js/app.jsx -o app/static/js/app.precompiled.js"
|
"build": "babel app/static/js/app.jsx --config-file ./babel.config.json -o app/static/js/app.precompiled.js"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/cli": "^8.0.4",
|
"@babel/cli": "^8.0.4",
|
||||||
|
|||||||
Reference in New Issue
Block a user