From 17a284ad1700eaa3280820226a058043ccd9536d Mon Sep 17 00:00:00 2001 From: 3dtours Date: Fri, 24 Jul 2026 17:00:22 +0700 Subject: [PATCH] =?UTF-8?q?feat:=20b=E1=BB=95=20sung=20ph=E1=BA=A7n=20sect?= =?UTF-8?q?ion-item=20v=C3=A0=20section=20tab?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/static/css/styles.css | 6 + app/static/js/app.jsx | 666 +++++++++++++++++++++++-------- app/static/js/app.precompiled.js | 46 ++- app/storage/sonicforge.db | Bin 49152 -> 53248 bytes app/templates/index.html | 6 + 5 files changed, 536 insertions(+), 188 deletions(-) diff --git a/app/static/css/styles.css b/app/static/css/styles.css index 8258a92..2e3342e 100644 --- a/app/static/css/styles.css +++ b/app/static/css/styles.css @@ -47,3 +47,9 @@ body { font-size: 11px; backdrop-filter: blur(4px); } + +/* Disable mouse pointer events on SVG icons to prevent SVGAnimatedString className type errors in event listeners */ +svg, i[data-lucide] { + pointer-events: none; +} + diff --git a/app/static/js/app.jsx b/app/static/js/app.jsx index 33a5402..e9e1959 100644 --- a/app/static/js/app.jsx +++ b/app/static/js/app.jsx @@ -3466,11 +3466,13 @@ const ProfileModal = ({ let restoredTracks = []; let restoredBpm = bpm; let restoredSessionTabs = []; + let restoredSubTabs = []; if (parsed.main_session) { const result = deserializeProjectFromSchema(parsed); restoredTracks = result.tracks; restoredBpm = result.bpm; restoredSessionTabs = result.sessionTabs; + restoredSubTabs = result.subTabs; } else { restoredTracks = (parsed.tracks || []).map(t => { const { height: _h, ...rest } = t; @@ -3491,6 +3493,9 @@ const ProfileModal = ({ if (restoredSessionTabs.length > 0) { setSessionTabs(restoredSessionTabs); } + if (restoredSubTabs.length > 0) { + setSubTabs(restoredSubTabs); + } localStorage.setItem('sonic_project_name', proj.name); localStorage.setItem('sonic_project_id', proj.id); showToast(`Đã nạp dự án "${proj.name}" thành công!`, "success"); @@ -4340,7 +4345,7 @@ const AIPresetModal = ({ isOpen, onClose }) => { }, "Đóng")))); }; -const PianoRollTabEditor = ({ st, zoom, bpm, viewportWidth, onClose, onUpdateNotes, onSaveNotes, setSubTabs, onPlayPause, onStop, isPlaying, playPreviewNote }) => { +const PianoRollTabEditor = ({ st, zoom, bpm, viewportWidth, onClose, onUpdateNotes, onSaveNotes, setSubTabs, onPlayPause, onStop, isPlaying, playPreviewNote, showToast }) => { const [activeRollTool, setActiveRollTool] = React.useState('select'); const [snapVal, setSnapVal] = React.useState('1/16'); const [ccMode, setCcMode] = React.useState('velocity'); @@ -5079,9 +5084,8 @@ const PianoRollTabEditor = ({ st, zoom, bpm, viewportWidth, onClose, onUpdateNot }))))); }; -const serializeProjectToSchema = (projectId, name, bpmVal, tracksList, subTabsList, sessionTabsList) => { - const secondsPerBar = (60.0 / parseFloat(bpmVal || 120)) * 4; - const mainTracks = (tracksList || []).map(t => { +const serializeTracksList = (tracksList, secondsPerBar) => { + return (tracksList || []).map(t => { let trackType = "AUDIO"; if (t.sections && t.sections.length > 0) trackType = "SECTION"; else if (t.midiItems && t.midiItems.length > 0) trackType = "MIDI"; @@ -5151,34 +5155,139 @@ const serializeProjectToSchema = (projectId, name, bpmVal, tracksList, subTabsLi pan: t.pan || 0.0, mute: t.muted || false, solo: t.solo || false, + instrument_id: t.instrumentId || null, + instrument_program: t.instrumentProgram !== undefined ? t.instrumentProgram : null, + instrument_name: t.instrumentName || null, items: items }; }); +}; + +const deserializeTracksList = (schemaTracks, secondsPerBar, sectionStore) => { + return (schemaTracks || []).map(t => { + const clips = []; + const sections = []; + const midiItems = []; + + (t.items || []).forEach(item => { + if (item.type === "AUDIO_ITEM") { + const src = item.source_data || {}; + clips.push({ + id: item.id, + name: item.name, + startTime: item.start_bar * secondsPerBar, + speed: 1.0 + }); + } else if (item.type === "MIDI_ITEM") { + const src = item.source_data || {}; + midiItems.push({ + id: item.id, + name: item.name, + startTime: item.start_bar * secondsPerBar, + duration: item.duration_bars * secondsPerBar, + notes: (src.notes || []).map(n => ({ + id: n.id, + pitch: n.pitch || 60, + start_beat: n.start_beat || 0.0, + duration_beats: n.duration_beats || 1.0, + velocity: n.velocity || 0.8, + pan: n.pan || 0.0 + })) + }); + } else if (item.type === "SECTION_ITEM") { + const src = item.source_data || {}; + const secId = src.referenced_section_id; + const secContainer = sectionStore ? sectionStore[secId] : null; + sections.push({ + id: item.id, + name: item.name, + start: item.start_bar * secondsPerBar, + duration: item.duration_bars * secondsPerBar, + sectionId: secId, + tracks: secContainer ? deserializeTracksList(secContainer.tracks, secondsPerBar, sectionStore) : null + }); + } + }); + + return { + id: t.id, + name: t.name, + volumeDb: t.volume_db || 0.0, + pan: t.pan || 0.0, + muted: t.mute || false, + solo: t.solo || false, + color: t.id === '1' ? '#0f766e' : '#1d4ed8', + markers: [], + serverFileId: t.items && t.items.find(i => i.type === "AUDIO_ITEM")?.source_data?.audio_file_url?.split("/").pop() || null, + clips: clips, + sections: sections, + midiItems: midiItems, + instrumentId: t.instrument_id || null, + instrumentProgram: t.instrument_program !== null ? t.instrument_program : undefined, + instrumentName: t.instrument_name || null + }; + }); +}; + +const serializeProjectToSchema = (projectId, name, bpmVal, tracksList, subTabsList, sessionTabsList) => { + const secondsPerBar = (60.0 / parseFloat(bpmVal || 120)) * 4; + const mainTracks = serializeTracksList(tracksList, secondsPerBar); const sectionStore = {}; + + // 1. Populate from sessionTabsList (open tabs) (sessionTabsList || []).forEach(st => { - const secTracks = (st.tracks || []).map(t => { - let trackType = "AUDIO"; - if (t.sections && t.sections.length > 0) trackType = "SECTION"; - else if (t.midiItems && t.midiItems.length > 0) trackType = "MIDI"; - return { - id: t.id, - name: t.name, - type: trackType, - volume_db: t.volumeDb || 0.0, - pan: t.pan || 0.0, - mute: t.muted || false, - solo: t.solo || false, - items: [] - }; - }); sectionStore[st.sectionId] = { id: st.sectionId, name: st.name, is_root: false, length_bars: st.length_bars || 16.0, auto_compute_length: st.auto_compute_length !== undefined ? st.auto_compute_length : true, - tracks: secTracks + tracks: serializeTracksList(st.tracks, secondsPerBar), + color: st.color || null + }; + }); + + // 2. Also populate from tracksList (closed tabs saved inside Section items) + const scanForSections = (tracks) => { + (tracks || []).forEach(t => { + if (t.sections) { + t.sections.forEach(s => { + const secId = s.sectionId || s.id; + if (s.tracks && !sectionStore[secId]) { + sectionStore[secId] = { + id: secId, + name: s.name, + is_root: false, + length_bars: s.tracks[0]?.length_bars || 16.0, + auto_compute_length: true, + tracks: serializeTracksList(s.tracks, secondsPerBar), + color: s.color || null + }; + } + if (s.tracks) { + scanForSections(s.tracks); + } + }); + } + }); + }; + scanForSections(tracksList); + + const subTabs = (subTabsList || []).map(st => { + return { + id: st.id, + label: st.label, + type: st.type, + track_id: st.trackId, + target_id: st.target_id, + parent_tab_id: st.parent_tab_id, + notes: st.notes || [], + duration: st.duration || 4, + instrument_program: st.instrumentProgram, + instrument_name: st.instrumentName, + current_time: st.currentTime || 0, + color: st.color || null }; }); @@ -5199,6 +5308,7 @@ const serializeProjectToSchema = (projectId, name, bpmVal, tracksList, subTabsLi auto_compute_length: true, tracks: mainTracks }, + sub_tabs: subTabs, section_store: sectionStore }; }; @@ -5207,90 +5317,46 @@ const deserializeProjectFromSchema = (schemaObj) => { const bpmVal = schemaObj.metadata ? schemaObj.metadata.bpm : 120; const secondsPerBar = (60.0 / bpmVal) * 4; - const restoredTracks = (schemaObj.main_session.tracks || []).map(t => { - const clips = []; - const sections = []; - const midiItems = []; - - (t.items || []).forEach(item => { - if (item.type === "AUDIO_ITEM") { - const src = item.source_data || {}; - const audioUrl = src.audio_file_url || ""; - const serverFileId = audioUrl.split("/").pop() || null; - clips.push({ - id: item.id, - name: item.name, - startTime: item.start_bar * secondsPerBar, - speed: 1.0 - }); - } else if (item.type === "MIDI_ITEM") { - const src = item.source_data || {}; - midiItems.push({ - id: item.id, - name: item.name, - startTime: item.start_bar * secondsPerBar, - duration: item.duration_bars * secondsPerBar, - notes: src.notes || [] - }); - } else if (item.type === "SECTION_ITEM") { - const src = item.source_data || {}; - sections.push({ - id: item.id, - name: item.name, - start: item.start_bar * secondsPerBar, - duration: item.duration_bars * secondsPerBar, - sectionId: src.referenced_section_id - }); - } - }); - - return { - id: t.id, - name: t.name, - volumeDb: t.volume_db || 0.0, - pan: t.pan || 0.0, - muted: t.mute || false, - solo: t.solo || false, - color: t.id === '1' ? '#0f766e' : '#1d4ed8', - markers: [], - serverFileId: t.items && t.items.find(i => i.type === "AUDIO_ITEM")?.source_data?.audio_file_url?.split("/").pop() || null, - clips: clips, - sections: sections, - midiItems: midiItems - }; - }); + const sectionStore = schemaObj.section_store || {}; + const restoredTracks = deserializeTracksList(schemaObj.main_session.tracks, secondsPerBar, sectionStore); const restoredSessionTabs = []; - const sectionStore = schemaObj.section_store || {}; Object.keys(sectionStore).forEach(secId => { const secContainer = sectionStore[secId]; - const secTracks = (secContainer.tracks || []).map(t => { - return { - id: t.id, - name: t.name, - volumeDb: t.volume_db || 0.0, - pan: t.pan || 0.0, - muted: t.mute || false, - solo: t.solo || false, - clips: [], - sections: [], - midiItems: [] - }; - }); + const secTracks = deserializeTracksList(secContainer.tracks, secondsPerBar, sectionStore); restoredSessionTabs.push({ id: 'session_' + secId, name: secContainer.name, sectionId: secId, tracks: secTracks, length_bars: secContainer.length_bars || 16.0, - auto_compute_length: secContainer.auto_compute_length + auto_compute_length: secContainer.auto_compute_length !== undefined ? secContainer.auto_compute_length : true, + color: secContainer.color || null }); }); + const restoredSubTabs = (schemaObj.sub_tabs || []).map(st => { + return { + id: st.id, + label: st.label, + type: st.type, + trackId: st.track_id, + target_id: st.target_id, + parent_tab_id: st.parent_tab_id, + notes: st.notes || [], + duration: st.duration || 4, + instrumentProgram: st.instrument_program, + instrumentName: st.instrument_name, + currentTime: st.current_time || 0, + color: st.color || null + }; + }); + return { bpm: bpmVal, tracks: restoredTracks, - sessionTabs: restoredSessionTabs + sessionTabs: restoredSessionTabs, + subTabs: restoredSubTabs }; }; @@ -5690,6 +5756,29 @@ const App = () => { const [subTabPitchVal, setSubTabPitchVal] = useState(0); const [subTabs, setSubTabs] = useState([]); // [{id, label, trackId, startTime, endTime, buffer, effects, currentTime, selectionStart, selectionEnd, isPlaying}, ...] const [sessionTabs, setSessionTabs] = useState([]); // [{id, name, tracks}, ...] + const [tabContextMenu, setTabContextMenu] = useState(null); // { x, y, tabId, tabType } + const handleSetTabColor = (tabId, tabType, color) => { + if (tabType === 'session') { + setSessionTabs(prev => prev.map(s => s.id === tabId ? { ...s, color: color } : s)); + const tab = sessionTabs.find(s => s.id === tabId); + if (tab) { + setTracks(prev => prev.map(t => { + if (!t.sections || t.sections.length === 0) return t; + return { + ...t, + sections: t.sections.map(s => { + if (s.sectionId === tab.sectionId) { + return { ...s, color: color }; + } + return s; + }) + }; + })); + } + } else { + setSubTabs(prev => prev.map(s => s.id === tabId ? { ...s, color: color } : s)); + } + }; const activeTracks = useMemo(() => { const st = sessionTabs.find(s => s.id === activeTab); return st ? st.tracks : tracks; @@ -5774,11 +5863,13 @@ const App = () => { let restoredTracks = []; let restoredBpm = bpm; let restoredSessionTabs = []; + let restoredSubTabs = []; if (proj.main_session) { const result = deserializeProjectFromSchema(proj); restoredTracks = result.tracks; restoredBpm = result.bpm; restoredSessionTabs = result.sessionTabs; + restoredSubTabs = result.subTabs; } else { restoredTracks = (proj.tracks || []).map(t => { const { height: _h, ...rest } = t; @@ -5797,6 +5888,9 @@ const App = () => { if (restoredSessionTabs.length > 0) { setSessionTabs(restoredSessionTabs); } + if (restoredSubTabs.length > 0) { + setSubTabs(restoredSubTabs); + } showToast(`Đã tải dự án "${proj.metadata?.title || proj.name || 'Dự án mới'}" từ liên kết .sfs thành công!`, "success"); } } catch (e) { @@ -6671,7 +6765,9 @@ const App = () => { if (!track) return; const midiItem = (track.midiItems || []).find(m => m.id === midiItemId); if (!midiItem) return; + const existing = subTabs.find(s => s.type === 'PIANO_ROLL' && s.target_id === midiItemId); + if (existing) { setActiveTab(existing.id); showToast('Piano Roll cho nốt MIDI đã được mở.', 'info'); @@ -6708,14 +6804,15 @@ const App = () => { }; const handleUpdateMidiNotes = (tabId, notes) => { - setSubTabs(prev => prev.map(s => s.id === tabId ? { ...s, notes: notes } : s)); + setSubTabs(prev => prev.map(s => s.id === tabId ? { ...s, notes: notes, isDirty: true } : s)); }; const handleSaveMidiNotes = (tabId, trackId, midiItemId, updatedNotes) => { - const isSectionTrack = sessionTabs.some(s => s.tracks.some(t => t.id === trackId)); - if (isSectionTrack) { + const pianoRollTab = subTabs.find(s => s.id === tabId); + const parentTabId = pianoRollTab ? pianoRollTab.parent_tab_id : null; + if (parentTabId && parentTabId.startsWith('session_')) { setSessionTabs(prev => prev.map(s => { - if (!s.tracks.some(t => t.id === trackId)) return s; + if (s.id !== parentTabId) return s; return { ...s, tracks: s.tracks.map(t => { @@ -6742,6 +6839,7 @@ const App = () => { }; })); } + setSubTabs(prev => prev.map(s => s.id === tabId ? { ...s, isDirty: false } : s)); showToast('Đã lưu các chỉnh sửa nốt MIDI vào item cha!', 'success'); }; @@ -6762,11 +6860,16 @@ const App = () => { return { ...s, name: tab.name, - duration: durationSec + duration: durationSec, + tracks: tab.tracks }; }) }; })); + + // Clear isDirty flag + setSessionTabs(prev => prev.map(s => s.id === tabId ? { ...s, isDirty: false } : s)); + showToast(`Đã lưu nội dung Section "${tab.name}" vào Main Session!`, 'success'); }; @@ -6780,16 +6883,19 @@ const App = () => { if (existing) { setActiveTab(existing.id); showToast(`Tab "${section.name}" already open.`, 'info'); return; } const tabId = 'session_' + Date.now(); const tabName = section.name || 'Section'; - const clonedTracks = tracks.map(t => ({ + const clonedTracks = section.tracks ? section.tracks : tracks.map(t => ({ ...t, clips: [], sections: [], midiItems: [], markers: [], isArmed: false, - monitoringEnabled: true + monitoringEnabled: true, + instrumentId: null, + instrumentProgram: undefined, + instrumentName: null })); - setSessionTabs(prev => [...prev, { id: tabId, name: tabName, sectionId: sectionId, tracks: clonedTracks }]); + setSessionTabs(prev => [...prev, { id: tabId, name: tabName, sectionId: sectionId, tracks: clonedTracks, color: section.color || null }]); setActiveTab(tabId); }; @@ -7198,8 +7304,22 @@ const App = () => { } }; const closeSubTab = tabId => { - setSubTabs(prev => prev.filter(s => s.id !== tabId)); - if (activeTab === tabId) setActiveTab('main'); + const tab = subTabs.find(s => s.id === tabId); + if (!tab) return; + const performClose = () => { + setSubTabs(prev => prev.filter(s => s.id !== tabId)); + if (activeTab === tabId) setActiveTab('main'); + }; + if (tab.isDirty) { + setAppWarningModal({ + title: "Đóng Tab?", + message: `Bạn chưa lưu các thay đổi của tab "${tab.label || 'MIDI'}". Bạn có chắc chắn muốn đóng tab này và mất hết thay đổi?`, + isAlert: false, + onConfirm: performClose + }); + } else { + performClose(); + } }; const closeSessionTab = tabId => { const targetTab = sessionTabs.find(s => s.id === tabId); @@ -7214,8 +7334,20 @@ const App = () => { }); return; } - setSessionTabs(prev => prev.filter(s => s.id !== tabId)); - if (activeTab === tabId) setActiveTab('main'); + const performClose = () => { + setSessionTabs(prev => prev.filter(s => s.id !== tabId)); + if (activeTab === tabId) setActiveTab('main'); + }; + if (targetTab.isDirty) { + setAppWarningModal({ + title: "Đóng Tab Section?", + message: `Bạn chưa lưu các thay đổi của Section "${targetTab.name}". Bạn có chắc chắn muốn đóng tab này và mất hết thay đổi?`, + isAlert: false, + onConfirm: performClose + }); + } else { + performClose(); + } }; const updateSubTabEffects = (tabId, effects) => { setSubTabs(prev => prev.map(s => s.id === tabId ? { @@ -7961,6 +8093,7 @@ const App = () => { }); toastTimeoutRef.current = setTimeout(() => setToastMessage(null), actionText ? 8000 : 3500); }; + window.showToast = showToast; // ── Server-side upload ── const uploadToServer = async (file, trackId) => { @@ -8043,6 +8176,9 @@ const App = () => { // ── Update Playhead ── const startSubTabPlayback = (st, offsetWallTime) => { const context = getAudioContext(); + if (!st.buffer) { + st.buffer = context.createBuffer(1, 128, context.sampleRate); + } const speed = st.speed || 1.0; const offsetBuffer = offsetWallTime * speed; const eff = st.buffer.getChannelData(0); @@ -8375,8 +8511,8 @@ const App = () => { const startTrackPlayback = offsetTime => { const context = getAudioContext(); - const hasSolo = tracks.some(t => t.solo) || soloedTrackId !== null; - tracks.forEach(track => { + const hasSolo = activeTracks.some(t => t.solo) || soloedTrackId !== null; + activeTracks.forEach(track => { const isPlayable = hasSolo ? track.id === soloedTrackId || track.solo : !track.muted; if (!isPlayable) return; @@ -8456,6 +8592,108 @@ const App = () => { // Play audio normally (the buffer has the audio data) // This block is intentionally empty - audio clips already play above } + + // SECTION items playback + const sections = track.sections || []; + if (activeTab === 'main' && sections.length > 0) { + const bpmVal = parseInt(bpm) || 120; + const secondsPerBeat = 60.0 / bpmVal; + const secondsPerBar = secondsPerBeat * 4; + + sections.forEach(sec => { + const secStart = sec.start || 0; + const secEnd = secStart + (sec.duration || 0); + + const secTracks = sec.tracks || []; + const hasSubSolo = secTracks.some(st => st.solo); + + secTracks.forEach(subTrack => { + const isSubPlayable = hasSubSolo ? subTrack.solo : !subTrack.muted; + if (!isSubPlayable) return; + + // 1. Play clips in subTrack + const subClips = subTrack.clips || []; + subClips.forEach(clip => { + if (!clip.buffer) return; + + const clipStartLocal = clip.startTime || 0; + const clipDurLocal = clip.buffer.duration / (clip.speed || 1.0); + + const clipStartMain = secStart + clipStartLocal; + const clipEndMain = clipStartMain + clipDurLocal; + + // Only play within the bounds of [secStart, secEnd] + const activeStart = Math.max(clipStartMain, secStart); + const activeEnd = Math.min(clipEndMain, secEnd); + + if (activeStart < activeEnd && offsetTime < activeEnd) { + const source = context.createBufferSource(); + source.buffer = clip.buffer; + source.playbackRate.value = clip.speed || 1.0; + source.connect(gainNode); + + if (offsetTime < activeStart) { + const delay = activeStart - offsetTime; + const playOffset = activeStart - clipStartMain; + const playDuration = activeEnd - activeStart; + source.start(context.currentTime + delay, playOffset * (clip.speed || 1.0), playDuration); + activeSourcesRef.current.push(source); + } else { + const playOffset = offsetTime - clipStartMain; + const playDuration = activeEnd - offsetTime; + source.start(context.currentTime, playOffset * (clip.speed || 1.0), playDuration); + activeSourcesRef.current.push(source); + } + } + }); + + // 2. Play MIDI items in subTrack + const subMidiItems = subTrack.midiItems || []; + if (window.SonicSF && subMidiItems.length > 0) { + subMidiItems.forEach(item => { + const notes = item.notes || []; + notes.forEach(note => { + const noteStartSecLocal = item.startTime + (note.start_beat || 0) * secondsPerBeat; + const noteDurSec = (note.duration_beats || 1) * secondsPerBeat; + + const noteStartMain = secStart + noteStartSecLocal; + const noteEndMain = noteStartMain + noteDurSec; + + // Only play if the note starts inside the Section item bounds and hasn't finished yet + if (noteStartMain >= secStart && noteStartMain < secEnd && offsetTime < noteEndMain) { + const notePlayEndMain = Math.min(noteEndMain, secEnd); + const program = subTrack.instrumentProgram !== undefined ? subTrack.instrumentProgram : 0; + + if (offsetTime < noteStartMain) { + const delay = noteStartMain - offsetTime; + const startTime = context.currentTime + delay; + const playDurMs = (notePlayEndMain - noteStartMain) * 1000; + window.SonicSF.playNote( + note.pitch || 60, + note.velocity || 0.8, + playDurMs, + startTime, + program, + gainNode + ); + } else { + const remainingDurMs = (notePlayEndMain - offsetTime) * 1000; + window.SonicSF.playNote( + note.pitch || 60, + note.velocity || 0.8, + remainingDurMs, + context.currentTime, + program, + gainNode + ); + } + } + }); + }); + } + }); + }); + } }); }; @@ -8538,7 +8776,7 @@ const App = () => { } }; const handlePlayPause = () => { - if (activeTab !== 'main') { + if (activeTab !== 'main' && !activeTab.startsWith('session_')) { // Sub-tab playback transport const st = subTabs.find(s => s.id === activeTab); if (!st || (!st.buffer && st.type !== 'PIANO_ROLL')) return; @@ -8590,6 +8828,9 @@ const App = () => { } } }); + + // Call startSubTabPlayback to spin up the silent buffer playhead timing/references + startSubTabPlayback(st, startOffset); } else { startSubTabPlayback(st, startOffset); } @@ -8639,7 +8880,7 @@ const App = () => { return; } stopAllPlayback(); - if (activeTab !== 'main') { + if (activeTab !== 'main' && !activeTab.startsWith('session_')) { setSubTabs(prev => prev.map(s => s.id === activeTab ? { ...s, currentTime: 0 @@ -9481,27 +9722,37 @@ const App = () => { const newStart = Math.max(0, time - drag.clickOffset); const targetTrackId = hoveredTrackIdRef.current || drag.trackId; const secondsPerBar = (60.0 / (parseInt(bpm) || 120)) * 4; - updateActiveTracks(prev => prev.map(t => { - const items = drag.itemType === 'section' ? (t.sections || []) : (t.midiItems || []); - const updatedItems = items.filter(it => it.id !== drag.itemId); - if (t.id === targetTrackId) { - const movedItem = items.find(it => it.id === drag.itemId); - if (movedItem) { - updatedItems.push(drag.itemType === 'section' - ? { ...movedItem, start: newStart } - : { ...movedItem, startTime: newStart }); - } else { - if (drag.itemType === 'section') { - updatedItems.push({ id: drag.itemId, name: 'Section', start: newStart, duration: 4 * secondsPerBar, color: '#06b6d4' }); - } else { - updatedItems.push({ id: drag.itemId, name: 'MIDI Item', startTime: newStart, duration: 4 * secondsPerBar, notes: [], color: '#a78bfa' }); - } + updateActiveTracks(prev => { + let movedItem = null; + for (let track of prev) { + const items = drag.itemType === 'section' ? (track.sections || []) : (track.midiItems || []); + const found = items.find(it => it.id === drag.itemId); + if (found) { + movedItem = found; + break; } } - return drag.itemType === 'section' - ? { ...t, sections: updatedItems } - : { ...t, midiItems: updatedItems }; - })); + return prev.map(t => { + const items = drag.itemType === 'section' ? (t.sections || []) : (t.midiItems || []); + const updatedItems = items.filter(it => it.id !== drag.itemId); + if (t.id === targetTrackId) { + if (movedItem) { + updatedItems.push(drag.itemType === 'section' + ? { ...movedItem, start: newStart } + : { ...movedItem, startTime: newStart }); + } else { + if (drag.itemType === 'section') { + updatedItems.push({ id: drag.itemId, name: 'Section', start: newStart, duration: 4 * secondsPerBar, color: '#06b6d4' }); + } else { + updatedItems.push({ id: drag.itemId, name: 'MIDI Item', startTime: newStart, duration: 4 * secondsPerBar, notes: [], color: '#a78bfa' }); + } + } + } + return drag.itemType === 'section' + ? { ...t, sections: updatedItems } + : { ...t, midiItems: updatedItems }; + }); + }); if (drag.trackId !== targetTrackId) { setDraggedSectionItem(prev => ({ ...prev, trackId: targetTrackId })); } @@ -9911,7 +10162,7 @@ const App = () => { const currentActiveTab = activeTabRef.current; const st = currentSessionTabs.find(s => s.id === currentActiveTab); if (st) { - setSessionTabs(prev => prev.map(s => s.id === currentActiveTab ? { ...s, tracks: updater(s.tracks) } : s)); + setSessionTabs(prev => prev.map(s => s.id === currentActiveTab ? { ...s, tracks: updater(s.tracks), isDirty: true } : s)); } else { setTracks(updater); } @@ -12450,51 +12701,102 @@ const App = () => { }, /*#__PURE__*/React.createElement("i", { "data-lucide": "layout-dashboard", className: "w-3 h-3" - })), " Main Session"), sessionTabs.map(st => /*#__PURE__*/React.createElement("div", { - key: st.id, - className: "flex items-stretch" - }, /*#__PURE__*/React.createElement("button", { - onClick: () => setActiveTab(st.id), - className: `px-2 text-xs font-medium border-b-2 transition flex items-center gap-1 ${activeTab === st.id ? 'text-cyan-400 border-cyan-500 bg-zinc-800/50' : 'text-zinc-500 border-transparent hover:text-zinc-300 hover:bg-zinc-800/30'}` - }, /*#__PURE__*/React.createElement("span", { - className: "inline-flex items-center shrink-0" - }, /*#__PURE__*/React.createElement("i", { - "data-lucide": "layout-dashboard", - className: "w-3 h-3" - })), /*#__PURE__*/React.createElement("span", { - className: "max-w-[120px] truncate" - }, st.name)), /*#__PURE__*/React.createElement("button", { - onClick: () => closeSessionTab(st.id), - className: "px-1 text-zinc-600 hover:text-red-400 transition text-xs", - title: "Close tab" - }, /*#__PURE__*/React.createElement("span", { - className: "inline-flex items-center shrink-0" - }, /*#__PURE__*/React.createElement("i", { - "data-lucide": "x", - className: "w-3 h-3" - }))))), subTabs.map(st => /*#__PURE__*/React.createElement("div", { - key: st.id, - className: "flex items-stretch" - }, /*#__PURE__*/React.createElement("button", { - onClick: () => setActiveTab(st.id), - className: `px-2 text-xs font-medium border-b-2 transition flex items-center gap-1 ${activeTab === st.id ? 'text-amber-400 border-amber-500 bg-zinc-800/50' : 'text-zinc-500 border-transparent hover:text-zinc-300 hover:bg-zinc-800/30'}` - }, /*#__PURE__*/React.createElement("span", { - className: "inline-flex items-center shrink-0" - }, /*#__PURE__*/React.createElement("i", { - "data-lucide": "file-edit", - className: "w-3 h-3" - })), /*#__PURE__*/React.createElement("span", { - className: "max-w-[100px] truncate" - }, st.label)), /*#__PURE__*/React.createElement("button", { - onClick: () => closeSubTab(st.id), - className: "px-1 text-zinc-600 hover:text-red-400 transition text-xs", - title: "Close tab" - }, /*#__PURE__*/React.createElement("span", { - className: "inline-flex items-center shrink-0" - }, /*#__PURE__*/React.createElement("i", { - "data-lucide": "x", - className: "w-3 h-3" - })))))), showAIConfig && /*#__PURE__*/React.createElement("div", { + })), " Main Session"), sessionTabs.map(st => { + const isTabActive = activeTab === st.id; + const tabColor = st.color || (isTabActive ? '#06b6d4' : null); + const borderStyle = isTabActive ? { borderBottomColor: tabColor, color: tabColor } : (st.color ? { color: st.color, borderBottomColor: 'transparent' } : {}); + + return /*#__PURE__*/React.createElement("div", { + key: st.id, + className: "flex items-stretch" + }, /*#__PURE__*/React.createElement("button", { + onClick: () => setActiveTab(st.id), + onAuxClick: e => { + if (e.button === 1) { + e.preventDefault(); + closeSessionTab(st.id); + } + }, + onContextMenu: e => { + e.preventDefault(); + setTabContextMenu({ + x: e.clientX, + y: e.clientY, + tabId: st.id, + tabType: 'session' + }); + }, + style: borderStyle, + className: `px-2 text-xs font-medium border-b-2 transition flex items-center gap-1 ${isTabActive ? 'bg-zinc-800/50' : 'text-zinc-500 border-transparent hover:text-zinc-300 hover:bg-zinc-800/30'}` + }, /*#__PURE__*/React.createElement("span", { + className: "inline-flex items-center shrink-0" + }, /*#__PURE__*/React.createElement("i", { + "data-lucide": "layers", + className: "w-3 h-3" + })), /*#__PURE__*/React.createElement("span", { + className: "max-w-[120px] truncate" + }, st.name), st.isDirty && /*#__PURE__*/React.createElement("span", { + className: "w-1.5 h-1.5 rounded-full bg-amber-500 ml-1", + title: "Chưa lưu thay đổi" + })), /*#__PURE__*/React.createElement("button", { + onClick: () => closeSessionTab(st.id), + className: "px-1 text-zinc-600 hover:text-red-400 transition text-xs", + title: "Close tab" + }, /*#__PURE__*/React.createElement("span", { + className: "inline-flex items-center shrink-0" + }, /*#__PURE__*/React.createElement("i", { + "data-lucide": "x", + className: "w-3 h-3" + })))); + }), subTabs.map(st => { + const isTabActive = activeTab === st.id; + const tabColor = st.color || (isTabActive ? '#f59e0b' : null); + const borderStyle = isTabActive ? { borderBottomColor: tabColor, color: tabColor } : (st.color ? { color: st.color, borderBottomColor: 'transparent' } : {}); + const iconName = st.type === 'PIANO_ROLL' ? 'music' : 'file-edit'; + + return /*#__PURE__*/React.createElement("div", { + key: st.id, + className: "flex items-stretch" + }, /*#__PURE__*/React.createElement("button", { + onClick: () => setActiveTab(st.id), + onAuxClick: e => { + if (e.button === 1) { + e.preventDefault(); + closeSubTab(st.id); + } + }, + onContextMenu: e => { + e.preventDefault(); + setTabContextMenu({ + x: e.clientX, + y: e.clientY, + tabId: st.id, + tabType: 'sub' + }); + }, + style: borderStyle, + className: `px-2 text-xs font-medium border-b-2 transition flex items-center gap-1 ${isTabActive ? 'bg-zinc-800/50' : 'text-zinc-500 border-transparent hover:text-zinc-300 hover:bg-zinc-800/30'}` + }, /*#__PURE__*/React.createElement("span", { + className: "inline-flex items-center shrink-0" + }, /*#__PURE__*/React.createElement("i", { + "data-lucide": iconName, + className: "w-3 h-3" + })), /*#__PURE__*/React.createElement("span", { + className: "max-w-[100px] truncate" + }, st.label), st.isDirty && /*#__PURE__*/React.createElement("span", { + className: "w-1.5 h-1.5 rounded-full bg-amber-500 ml-1", + title: "Chưa lưu thay đổi" + })), /*#__PURE__*/React.createElement("button", { + onClick: () => closeSubTab(st.id), + className: "px-1 text-zinc-600 hover:text-red-400 transition text-xs", + title: "Close tab" + }, /*#__PURE__*/React.createElement("span", { + className: "inline-flex items-center shrink-0" + }, /*#__PURE__*/React.createElement("i", { + "data-lucide": "x", + className: "w-3 h-3" + })))); + })), showAIConfig && /*#__PURE__*/React.createElement("div", { className: "bg-zinc-900 border-b border-purple-900 p-3 flex flex-col gap-2 transition-all" }, /*#__PURE__*/React.createElement("div", { className: "text-xs font-bold text-purple-400 uppercase tracking-wider flex items-center gap-1" @@ -14827,7 +15129,31 @@ const App = () => { if (fn) fn(); }, className: "px-4 py-2 bg-cyan-600 hover:bg-cyan-500 active:bg-cyan-700 text-white rounded text-xs font-semibold transition" - }, "Xác nhận"))))), /*#__PURE__*/React.createElement(AuthModal, { + }, "Xác nhận"))))), tabContextMenu && /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement("div", { + className: "fixed inset-0 z-[190]", + onClick: () => setTabContextMenu(null), + onContextMenu: e => { + e.preventDefault(); + setTabContextMenu(null); + } + }), /*#__PURE__*/React.createElement("div", { + className: "fixed bg-zinc-900 border border-zinc-800 rounded-lg shadow-2xl p-2 z-[200] flex gap-1.5", + style: { + top: tabContextMenu.y, + left: tabContextMenu.x + }, + onClick: e => e.stopPropagation() + }, ['#f43f5e', '#f59e0b', '#10b981', '#06b6d4', '#8b5cf6', '#64748b'].map(color => /*#__PURE__*/React.createElement("button", { + key: color, + className: "w-5 h-5 rounded-full border border-zinc-700 hover:scale-110 active:scale-95 transition", + style: { + backgroundColor: color + }, + onClick: () => { + handleSetTabColor(tabContextMenu.tabId, tabContextMenu.tabType, color); + setTabContextMenu(null); + } + })))), /*#__PURE__*/React.createElement(AuthModal, { isOpen: authModalOpen, mode: authMode, forceMandatory: isMandatoryLogin, diff --git a/app/static/js/app.precompiled.js b/app/static/js/app.precompiled.js index 567cc11..0f8903a 100644 --- a/app/static/js/app.precompiled.js +++ b/app/static/js/app.precompiled.js @@ -118,9 +118,9 @@ React.createElement('div',{className:'px-5 py-2 bg-[#1a1a1a] border-t border-[#3 sfToDelete&&React.createElement('div',{className:'fixed inset-0 z-[60] flex items-center justify-center bg-black/70',onClick:()=>setSfToDelete(null)},React.createElement('div',{className:'bg-[#262626] border border-[#383838] rounded-xl shadow-2xl w-full max-w-sm p-5 text-slate-200',onClick:e=>e.stopPropagation()},React.createElement('h3',{className:'text-sm font-bold text-red-400 mb-3'},'Delete SoundFont?'),React.createElement('p',{className:'text-xs text-zinc-400 mb-1'},'Are you sure you want to delete:'),React.createElement('p',{className:'text-sm font-semibold text-slate-200 mb-4'},sfToDelete.display||sfToDelete.name||sfToDelete.id),React.createElement('div',{className:'flex justify-end gap-2'},React.createElement('button',{onClick:()=>setSfToDelete(null),className:'px-4 py-2 text-xs rounded bg-zinc-700 hover:bg-zinc-600 text-zinc-300 transition'},'Cancel'),React.createElement('button',{onClick:async()=>{try{if(window.SonicAPI.deleteSoundFont){await window.SonicAPI.deleteSoundFont(sfToDelete.id);}const data=await window.SonicAPI.listPlugins();setLocalData(data);setSfToDelete(null);window.showToast&&window.showToast('SoundFont deleted.','info');}catch(err){window.showToast&&window.showToast('Delete failed: '+err.message,'error');setSfToDelete(null);}},className:'px-4 py-2 text-xs rounded bg-red-700 hover:bg-red-600 text-white font-semibold transition'},'Delete'))))));};const ProfileModal=({isOpen,onClose,tracks,setTracks,setSelectedTrackId,projectName,setProjectName,currentProjectId,setCurrentProjectId,showToast})=>{if(!isOpen)return null;const[activeTab,setActiveTab]=useState('account');const[profile,setProfile]=useState(null);const[oldPassword,setOldPassword]=useState('');const[newPassword,setNewPassword]=useState('');const[msg,setMsg]=useState('');const[error,setError]=useState('');const[loading,setLoading]=useState(false);const[dragOfs,setDragOfs]=useState({x:0,y:0});const dragRef=useRef({active:false,startX:0,startY:0,ofsX:0,ofsY:0});// Projects list state const[projectsList,setProjectsList]=useState([]);const[loadingProjects,setLoadingProjects]=useState(false);// Files list state const[filesList,setFilesList]=useState([]);const[loadingFiles,setLoadingFiles]=useState(false);// Confirmation modal state -const[confirmModal,setConfirmModal]=useState(null);const handleDragStart=e=>{const r=dragRef.current;r.active=true;r.startX=e.clientX;r.startY=e.clientY;r.ofsX=dragOfs.x;r.ofsY=dragOfs.y;const onMove=ev=>{if(!r.active)return;setDragOfs({x:r.ofsX+ev.clientX-r.startX,y:r.ofsY+ev.clientY-r.startY});};const onUp=()=>{r.active=false;document.removeEventListener('mousemove',onMove);document.removeEventListener('mouseup',onUp);};document.addEventListener('mousemove',onMove);document.addEventListener('mouseup',onUp);};useEffect(()=>{if(isOpen){fetchProfile();if(activeTab==='projects')fetchProjects();if(activeTab==='files')fetchFiles();}},[isOpen,activeTab]);const fetchProfile=async()=>{try{const data=await window.SonicAPI.getProfile();setProfile(data);}catch(e){setError(e.message||'Không thể tải thông tin profile');}};const fetchProjects=async()=>{setLoadingProjects(true);try{const data=await window.SonicAPI.listCloudProjects();setProjectsList(data||[]);}catch(e){showToast(e.message||'Không thể tải danh sách dự án','error');}finally{setLoadingProjects(false);}};const fetchFiles=async()=>{setLoadingFiles(true);try{const activeFileIds=tracks.map(t=>t.serverFileId).filter(Boolean);const data=await window.SonicAPI.listMyFiles(activeFileIds);setFilesList(data||[]);}catch(e){showToast(e.message||'Không thể tải danh sách tệp tin','error');}finally{setLoadingFiles(false);}};const handleOpenProject=async projectId=>{setAppWarningModal({title:"Mở dự án",message:"Bạn có muốn mở dự án này? Các thay đổi chưa lưu trên workspace hiện tại sẽ bị mất.",isAlert:false,onConfirm:async()=>{try{const proj=await window.SonicAPI.getCloudProject(projectId);const parsed=JSON.parse(proj.data_json);let restoredTracks=[];let restoredBpm=bpm;let restoredSessionTabs=[];if(parsed.main_session){const result=deserializeProjectFromSchema(parsed);restoredTracks=result.tracks;restoredBpm=result.bpm;restoredSessionTabs=result.sessionTabs;}else{restoredTracks=(parsed.tracks||[]).map(t=>{const{height:_h,...rest}=t;return{...rest,buffer:null,channelInfo:t.channelInfo||null,clips:t.clips||[],serverFileId:t.serverFileId||null};});}setTracks(restoredTracks);setBpm(restoredBpm.toString());setSelectedTrackId(restoredTracks[0]?.id||'1');setProjectName(proj.name);setCurrentProjectId(proj.id);if(restoredSessionTabs.length>0){setSessionTabs(restoredSessionTabs);}localStorage.setItem('sonic_project_name',proj.name);localStorage.setItem('sonic_project_id',proj.id);showToast(`Đã nạp dự án "${proj.name}" thành công!`,"success");}catch(e){showToast(e.message||"Lỗi khi nạp dự án","error");}}});};const handleDeleteProject=async(projectId,e)=>{e.stopPropagation();setConfirmModal({title:"Xóa dự án Cloud",message:"Bạn có chắc chắn muốn xóa dự án này khỏi Cloud? Hành động này không thể hoàn tác.",onConfirm:async()=>{try{await window.SonicAPI.deleteCloudProject(projectId);showToast("Đã xóa dự án thành công!","success");fetchProjects();fetchProfile();}catch(err){showToast(err.message||"Lỗi khi xóa dự án","error");}}});};const handleDeleteFile=async fileId=>{if(!confirm(`Bạn có chắc chắn muốn xóa tệp tin ${fileId}?`))return;try{await window.SonicAPI.deleteMyFile(fileId);showToast("Đã xóa tệp tin thành công!","success");fetchFiles();fetchProfile();}catch(err){showToast(err.message||"Lỗi khi xóa tệp tin","error");}};const handleCleanUnusedFiles=async()=>{const unusedFiles=filesList.filter(f=>!f.is_in_use);if(unusedFiles.length===0){showToast("Không có tập tin rác nào để dọn dẹp.","info");return;}if(!confirm(`Bạn có chắc chắn muốn xóa tất cả ${unusedFiles.length} tập tin rác để giải phóng dung lượng?`))return;let successCount=0;for(const file of unusedFiles){try{await window.SonicAPI.deleteMyFile(file.file_id);successCount++;}catch(e){console.error("Lỗi xóa file rác: ",file.file_id,e);}}showToast(`Đã dọn dẹp thành công ${successCount}/${unusedFiles.length} tập tin rác!`,"success");fetchFiles();fetchProfile();};const handleChangePassword=async e=>{e.preventDefault();setMsg('');setError('');setLoading(true);try{const res=await window.SonicAPI.changePassword(oldPassword,newPassword);setMsg(res.message||'Đổi mật khẩu thành công!');setOldPassword('');setNewPassword('');}catch(err){setError(err.message||'Lỗi khi đổi mật khẩu');}finally{setLoading(false);}};const modalStyle={left:`calc(50% + ${dragOfs.x}px)`,top:`calc(50% + ${dragOfs.y}px)`,transform:'translate(-50%, -50%)'};return/*#__PURE__*/React.createElement(React.Fragment,null,/*#__PURE__*/React.createElement("div",{key:"backdrop",className:"fixed inset-0 z-40 bg-black/70 backdrop-blur-sm",onClick:onClose}),confirmModal&&/*#__PURE__*/React.createElement("div",{key:"confirm-overlay",className:"fixed inset-0 z-[60] flex items-center justify-center bg-black/50"},/*#__PURE__*/React.createElement("div",{className:"bg-[#262626] border border-[#383838] rounded-lg shadow-2xl p-5 max-w-sm w-full text-slate-200"},/*#__PURE__*/React.createElement("h4",{className:"text-sm font-bold text-rose-400 mb-2"},confirmModal.title),/*#__PURE__*/React.createElement("p",{className:"text-xs text-slate-300 mb-4"},confirmModal.message),/*#__PURE__*/React.createElement("div",{className:"flex justify-end gap-2"},/*#__PURE__*/React.createElement("button",{onClick:()=>setConfirmModal(null),className:"px-3 py-1.5 bg-zinc-700 hover:bg-zinc-600 text-slate-300 rounded text-xs font-semibold"},"Hủy"),/*#__PURE__*/React.createElement("button",{onClick:()=>{const fn=confirmModal.onConfirm;setConfirmModal(null);fn();},className:"px-3 py-1.5 bg-rose-700 hover:bg-rose-600 text-white rounded text-xs font-semibold"},"Xác nhận xóa")))),/*#__PURE__*/React.createElement("div",{key:"dialog",className:"fixed z-50 bg-[#262626] border border-[#383838] rounded-xl shadow-2xl w-full max-w-2xl p-6 text-slate-200 flex flex-col max-h-[85vh]",style:modalStyle},/*#__PURE__*/React.createElement("div",{className:"flex justify-between items-center pb-3 border-b border-[#383838] shrink-0 cursor-grab active:cursor-grabbing select-none",onMouseDown:handleDragStart},/*#__PURE__*/React.createElement("h3",{className:"text-md font-bold text-teal-400 flex items-center gap-1.5"},"👤 Hồ Sơ Cá Nhân & Quản Lý Dự Án"),/*#__PURE__*/React.createElement("button",{onClick:onClose,className:"text-slate-400 hover:text-slate-200"},"✕")),/*#__PURE__*/React.createElement("div",{className:"flex gap-2 border-b border-[#383838] py-2 shrink-0 text-xs font-semibold"},[/*#__PURE__*/React.createElement("button",{key:"tab-acc",onClick:()=>setActiveTab('account'),className:`px-3 py-1.5 rounded transition ${activeTab==='account'?'bg-teal-950/60 text-teal-300 border border-teal-800':'text-slate-400 hover:text-slate-200'}`},"Tài Khoản"),/*#__PURE__*/React.createElement("button",{key:"tab-proj",onClick:()=>setActiveTab('projects'),className:`px-3 py-1.5 rounded transition ${activeTab==='projects'?'bg-teal-950/60 text-teal-300 border border-teal-800':'text-slate-400 hover:text-slate-200'}`},"Dự Án Cloud"),/*#__PURE__*/React.createElement("button",{key:"tab-files",onClick:()=>setActiveTab('files'),className:`px-3 py-1.5 rounded transition ${activeTab==='files'?'bg-teal-950/60 text-teal-300 border border-teal-800':'text-slate-400 hover:text-slate-200'}`},"Tập Tin Của Tôi")]),/*#__PURE__*/React.createElement("div",{className:"flex-1 overflow-y-auto pt-4 space-y-4 pr-1 min-h-[300px]"},activeTab==='account'&&profile?[/*#__PURE__*/React.createElement("div",{key:"quota-info",className:"bg-[#1e1e1e] p-4 rounded-lg border border-[#333] grid grid-cols-2 gap-4 text-xs"},[/*#__PURE__*/React.createElement("div",{key:"username"},[/*#__PURE__*/React.createElement("span",{className:"text-slate-500 block"},"Tên người dùng"),/*#__PURE__*/React.createElement("span",{className:"font-bold text-teal-300 text-sm"},profile.username)]),/*#__PURE__*/React.createElement("div",{key:"role"},[/*#__PURE__*/React.createElement("span",{className:"text-slate-500 block"},"Vai trò"),/*#__PURE__*/React.createElement("span",{className:"uppercase font-semibold text-amber-400"},profile.role)]),/*#__PURE__*/React.createElement("div",{key:"email"},[/*#__PURE__*/React.createElement("span",{className:"text-slate-500 block"},"Email"),/*#__PURE__*/React.createElement("span",null,profile.email)]),/*#__PURE__*/React.createElement("div",{key:"quota"},[/*#__PURE__*/React.createElement("span",{className:"text-slate-500 block"},"Dung lượng Quota"),/*#__PURE__*/React.createElement("span",{className:"font-semibold text-slate-200"},`${profile.quota.used_mb} MB / ${profile.quota.storage_limit_mb} MB`)])]),/*#__PURE__*/React.createElement("div",{key:"progress"},[/*#__PURE__*/React.createElement("div",{className:"flex justify-between text-xs mb-1"},[/*#__PURE__*/React.createElement("span",{className:"text-slate-400"},"Tiến trình sử dụng bộ nhớ Server"),/*#__PURE__*/React.createElement("span",{className:"font-bold text-teal-400"},`${(profile.quota.used_mb/profile.quota.storage_limit_mb*100).toFixed(1)}%`)]),/*#__PURE__*/React.createElement("div",{className:"w-full h-2 bg-slate-800 rounded-full overflow-hidden"},[/*#__PURE__*/React.createElement("div",{className:"h-full bg-teal-500 rounded-full transition-all duration-300",style:{width:`${Math.min(100,profile.quota.used_mb/profile.quota.storage_limit_mb*100)}%`}})])]),/*#__PURE__*/React.createElement("form",{key:"pwd-form",onSubmit:handleChangePassword,className:"pt-4 border-t border-[#383838] space-y-3"},[/*#__PURE__*/React.createElement("h4",{className:"text-xs font-bold text-slate-300 uppercase"},"Thay Đổi Mật Khẩu"),msg&&/*#__PURE__*/React.createElement("div",{className:"p-2 bg-emerald-950/60 border border-emerald-700 text-emerald-300 rounded text-xs"},msg),error&&/*#__PURE__*/React.createElement("div",{className:"p-2 bg-red-950/60 border border-red-700 text-red-300 rounded text-xs"},error),/*#__PURE__*/React.createElement("div",{key:"old"},[/*#__PURE__*/React.createElement("label",{className:"block text-xs text-slate-400 mb-1"},"Mật khẩu cũ"),/*#__PURE__*/React.createElement("input",{type:"password",autoComplete:"current-password",required:true,value:oldPassword,onChange:e=>setOldPassword(e.target.value),className:"w-full bg-[#1e1e1e] border border-[#383838] rounded px-3 py-1.5 text-xs focus:outline-none focus:border-teal-500"})]),/*#__PURE__*/React.createElement("div",{key:"new"},[/*#__PURE__*/React.createElement("label",{className:"block text-xs text-slate-400 mb-1"},"Mật khẩu mới"),/*#__PURE__*/React.createElement("input",{type:"password",autoComplete:"new-password",required:true,value:newPassword,onChange:e=>setNewPassword(e.target.value),className:"w-full bg-[#1e1e1e] border border-[#383838] rounded px-3 py-1.5 text-xs focus:outline-none focus:border-teal-500"})]),/*#__PURE__*/React.createElement("button",{type:"submit",disabled:loading,className:"w-full py-1.5 bg-teal-600 hover:bg-teal-500 text-white font-semibold text-xs rounded transition"},loading?'Đang cập nhật...':'Cập Nhật Mật Khẩu')])]:activeTab==='projects'?[loadingProjects?/*#__PURE__*/React.createElement("div",{key:"loading",className:"text-center py-8 text-xs text-zinc-500"},"Đang tải danh sách dự án..."):projectsList.length===0?/*#__PURE__*/React.createElement("div",{key:"empty",className:"text-center py-8 text-xs text-zinc-500"},"Bạn chưa có dự án nào lưu trên Cloud."):/*#__PURE__*/React.createElement("div",{key:"list",className:"space-y-1.5"},[projectsList.map(proj=>/*#__PURE__*/React.createElement("div",{key:proj.id,onClick:()=>handleOpenProject(proj.id),className:"flex items-center justify-between p-3 bg-[#1e1e1e] hover:bg-zinc-800 rounded border border-zinc-800 transition cursor-pointer text-xs group"},[/*#__PURE__*/React.createElement("div",{key:"meta"},[/*#__PURE__*/React.createElement("div",{className:"font-bold text-slate-200 group-hover:text-teal-400"},proj.name),/*#__PURE__*/React.createElement("div",{className:"text-[10px] text-zinc-500 mt-0.5"},`Dung lượng: ${proj.size_mb} MB | Cập nhật: ${new Date(proj.updated_at*1000).toLocaleString()}`)]),/*#__PURE__*/React.createElement("div",{key:"actions",className:"flex items-center gap-1.5"},[/*#__PURE__*/React.createElement("button",{onClick:e=>{e.stopPropagation();handleOpenProject(proj.id);},className:"px-2.5 py-1 bg-teal-800/80 hover:bg-teal-700 text-teal-200 rounded font-semibold text-[10px]"},"MỞ"),/*#__PURE__*/React.createElement("button",{onClick:e=>handleDeleteProject(proj.id,e),className:"px-2 py-1 bg-rose-950/60 hover:bg-rose-900/80 text-rose-300 rounded font-semibold text-[10px]"},"XÓA")])]))])]:activeTab==='files'?[/*#__PURE__*/React.createElement("div",{key:"cleanup-header",className:"flex items-center justify-between bg-zinc-900/60 p-2.5 rounded border border-zinc-800 text-xs shrink-0 mb-3"},[/*#__PURE__*/React.createElement("span",{className:"text-zinc-400 text-[10px]"},"💡 Các tập tin không dùng trong dự án nào sẽ được đánh dấu 'Không dùng' và có thể xóa để giải phóng dung lượng."),/*#__PURE__*/React.createElement("button",{onClick:handleCleanUnusedFiles,className:"px-2.5 py-1 bg-rose-700 hover:bg-rose-600 text-white rounded font-bold text-[10px] uppercase flex items-center gap-1"},"🧹 Dọn dẹp tệp rác")]),loadingFiles?/*#__PURE__*/React.createElement("div",{key:"loading",className:"text-center py-8 text-xs text-zinc-500"},"Đang tải danh sách tập tin..."):filesList.length===0?/*#__PURE__*/React.createElement("div",{key:"empty",className:"text-center py-8 text-xs text-zinc-500"},"Chưa có tập tin nào tải lên hoặc tạo ra."):/*#__PURE__*/React.createElement("div",{key:"list",className:"space-y-1.5"},[filesList.map(file=>/*#__PURE__*/React.createElement("div",{key:file.file_id,className:"flex items-center justify-between p-3 bg-[#1e1e1e] rounded border border-zinc-800 text-xs"},[/*#__PURE__*/React.createElement("div",{key:"meta",className:"max-w-[70%]"},[/*#__PURE__*/React.createElement("div",{className:"font-semibold text-slate-300 truncate"},file.original_name||file.file_id),/*#__PURE__*/React.createElement("div",{className:"text-[10px] text-zinc-500 mt-0.5"},`Loại: ${file.type} | Dung lượng: ${file.size_mb} MB | Tạo lúc: ${new Date(file.created_at*1000).toLocaleString()}`)]),/*#__PURE__*/React.createElement("div",{key:"actions",className:"flex items-center gap-2"},[file.is_in_use?/*#__PURE__*/React.createElement("span",{className:"px-2 py-0.5 bg-emerald-950 text-emerald-400 border border-emerald-900 rounded text-[9px] uppercase font-bold font-mono"},"Đang dùng"):/*#__PURE__*/React.createElement("span",{className:"px-2 py-0.5 bg-zinc-850 text-zinc-400 border border-zinc-700 rounded text-[9px] uppercase font-bold font-mono"},"Không dùng"),!file.is_in_use&&/*#__PURE__*/React.createElement("button",{onClick:()=>handleDeleteFile(file.file_id),className:"px-2 py-0.5 bg-rose-950 hover:bg-rose-900 text-rose-300 rounded font-semibold text-[10px] border border-rose-900"},"Xóa")])]))])]:null)));};const SaveProjectModal=({isOpen,onClose,onSave})=>{if(!isOpen)return null;const[name,setName]=useState('');const handleSubmit=e=>{e.preventDefault();if(!name.trim())return;onSave(name.trim());onClose();};return/*#__PURE__*/React.createElement("div",{className:"fixed inset-0 z-50 flex items-center justify-center bg-black/70 backdrop-blur-sm"},/*#__PURE__*/React.createElement("div",{className:"bg-[#262626] border border-[#383838] rounded-xl shadow-2xl w-full max-w-md p-6 text-slate-200"},/*#__PURE__*/React.createElement("h3",{className:"text-sm font-bold text-teal-400 mb-4 uppercase"},"Đặt tên dự án"),/*#__PURE__*/React.createElement("form",{onSubmit:handleSubmit,className:"space-y-4"},[/*#__PURE__*/React.createElement("input",{key:"name-input",type:"text",placeholder:"Nhập tên dự án...",required:true,value:name,onChange:e=>setName(e.target.value),className:"w-full bg-[#1e1e1e] border border-[#383838] rounded px-3 py-1.5 text-xs text-slate-200 focus:outline-none focus:border-teal-500 font-bold",autoFocus:true}),/*#__PURE__*/React.createElement("div",{key:"actions",className:"flex justify-end gap-2 text-xs"},[/*#__PURE__*/React.createElement("button",{key:"cancel",type:"button",onClick:onClose,className:"px-3 py-1.5 bg-zinc-800 hover:bg-zinc-700 text-zinc-300 rounded"},"Hủy"),/*#__PURE__*/React.createElement("button",{key:"save",type:"submit",className:"px-3 py-1.5 bg-teal-600 hover:bg-teal-500 text-white rounded font-semibold"},"Lưu")])])));};const SaveAsModal=({isOpen,onClose,projectName,onSaveCloud,onSaveLocal})=>{if(!isOpen)return null;const[name,setName]=useState(projectName||'');const[saveType,setSaveType]=useState('cloud');const handleSubmit=e=>{e.preventDefault();if(!name.trim())return;if(saveType==='cloud'){onSaveCloud(name.trim());}else{onSaveLocal(name.trim());}onClose();};return/*#__PURE__*/React.createElement("div",{className:"fixed inset-0 z-50 flex items-center justify-center bg-black/70 backdrop-blur-sm"},/*#__PURE__*/React.createElement("div",{className:"bg-[#262626] border border-[#383838] rounded-xl shadow-2xl w-full max-w-md p-6 text-slate-200"},/*#__PURE__*/React.createElement("h3",{className:"text-sm font-bold text-teal-400 mb-4 uppercase"},"Lưu dưới tên khác (Save As...)"),/*#__PURE__*/React.createElement("form",{onSubmit:handleSubmit,className:"space-y-4"},[/*#__PURE__*/React.createElement("div",{key:"name-block"},[/*#__PURE__*/React.createElement("label",{className:"block text-[10px] text-zinc-400 uppercase font-bold mb-1"},"Tên dự án mới"),/*#__PURE__*/React.createElement("input",{type:"text",placeholder:"Nhập tên mới...",required:true,value:name,onChange:e=>setName(e.target.value),className:"w-full bg-[#1e1e1e] border border-[#383838] rounded px-3 py-1.5 text-xs text-slate-200 focus:outline-none focus:border-teal-500 font-bold",autoFocus:true})]),/*#__PURE__*/React.createElement("div",{key:"type-block"},[/*#__PURE__*/React.createElement("label",{className:"block text-[10px] text-zinc-400 uppercase font-bold mb-1.5"},"Phương thức lưu trữ"),/*#__PURE__*/React.createElement("div",{className:"grid grid-cols-2 gap-2 text-xs"},[/*#__PURE__*/React.createElement("button",{key:"btn-cloud",type:"button",onClick:()=>setSaveType('cloud'),className:`py-2 rounded border flex flex-col items-center gap-1 font-semibold transition ${saveType==='cloud'?'bg-teal-950/60 border-teal-500 text-teal-300':'bg-zinc-800 border-zinc-700 hover:bg-zinc-750 text-zinc-400'}`},[/*#__PURE__*/React.createElement("span",{key:"title"},"☁️ Lưu Cloud"),/*#__PURE__*/React.createElement("span",{key:"desc",className:"text-[9px] font-normal text-zinc-500"},"Lưu lên server cá nhân")]),/*#__PURE__*/React.createElement("button",{key:"btn-local",type:"button",onClick:()=>setSaveType('local'),className:`py-2 rounded border flex flex-col items-center gap-1 font-semibold transition ${saveType==='local'?'bg-teal-950/60 border-teal-500 text-teal-300':'bg-zinc-800 border-zinc-700 hover:bg-zinc-750 text-zinc-400'}`},[/*#__PURE__*/React.createElement("span",{key:"title"},"💾 Tải về máy (.sfs)"),/*#__PURE__*/React.createElement("span",{key:"desc",className:"text-[9px] font-normal text-zinc-500"},"Tải tệp JSON dự án về máy")])])]),/*#__PURE__*/React.createElement("div",{key:"actions",className:"flex justify-end gap-2 text-xs pt-2"},[/*#__PURE__*/React.createElement("button",{key:"cancel",type:"button",onClick:onClose,className:"px-3 py-1.5 bg-zinc-800 hover:bg-zinc-700 text-zinc-300 rounded"},"Hủy"),/*#__PURE__*/React.createElement("button",{key:"save",type:"submit",className:"px-3 py-1.5 bg-teal-600 hover:bg-teal-500 text-white rounded font-semibold"},"Thực hiện lưu")])])));};const SystemManagerModal=({isOpen,onClose})=>{if(!isOpen)return null;const[users,setUsers]=useState([]);const[loading,setLoading]=useState(true);const[msg,setMsg]=useState('');const[error,setError]=useState('');const[editingQuotaUser,setEditingQuotaUser]=useState(null);const[newQuotaMb,setNewQuotaMb]=useState(500);useEffect(()=>{if(isOpen)loadUsers();},[isOpen]);const loadUsers=async()=>{setLoading(true);setError('');try{const data=await window.SonicAPI.listUsers();setUsers(data);}catch(err){setError(err.message||'Không thể tải danh sách người dùng hệ thống');}finally{setLoading(false);}};const handleSaveQuota=async userId=>{try{await window.SonicAPI.updateUserQuota(userId,parseInt(newQuotaMb));setMsg('Đã cập nhật hạn mức Quota thành công!');setEditingQuotaUser(null);loadUsers();}catch(err){setError(err.message||'Lỗi cập nhật Quota');}};const handleToggleRole=async user=>{const nextRole=user.role==='admin'?'standard':'admin';try{await window.SonicAPI.updateUserRole(user.id,nextRole,user.is_active);setMsg(`Đã đổi vai trò người dùng ${user.username} thành ${nextRole}`);loadUsers();}catch(err){setError(err.message||'Lỗi cập nhật vai trò');}};const handleDeleteUser=async userId=>{if(!confirm('Bạn có chắc chắn muốn xóa người dùng này khỏi hệ thống?'))return;try{await window.SonicAPI.deleteUser(userId);setMsg('Đã xóa người dùng thành công');loadUsers();}catch(err){setError(err.message||'Lỗi khi xóa người dùng');}};return/*#__PURE__*/React.createElement("div",{className:"fixed inset-0 z-50 flex items-center justify-center bg-black/70 backdrop-blur-sm"},/*#__PURE__*/React.createElement("div",{className:"bg-[#262626] border border-[#383838] rounded-xl shadow-2xl w-full max-w-4xl p-6 text-slate-200"},/*#__PURE__*/React.createElement("div",{className:"flex justify-between items-center pb-4 border-b border-[#383838]"},/*#__PURE__*/React.createElement("h3",{className:"text-lg font-bold text-amber-400"},"⚙️ Quản Lý Hệ Thống & Phân Quyền Admin"),/*#__PURE__*/React.createElement("button",{onClick:onClose,className:"text-slate-400 hover:text-slate-200"},"✕")),msg&&/*#__PURE__*/React.createElement("div",{className:"mt-3 p-2 bg-emerald-950/60 border border-emerald-700 text-emerald-300 rounded text-xs"},msg),error&&/*#__PURE__*/React.createElement("div",{className:"mt-3 p-2 bg-red-950/60 border border-red-700 text-red-300 rounded text-xs"},error),/*#__PURE__*/React.createElement("div",{className:"mt-4 overflow-x-auto max-h-96 no-scrollbar"},loading?/*#__PURE__*/React.createElement("div",{className:"py-8 text-center text-slate-400 text-xs"},"Đang tải thông tin hệ thống..."):/*#__PURE__*/React.createElement("table",{className:"w-full text-left text-xs border-collapse"},/*#__PURE__*/React.createElement("thead",null,/*#__PURE__*/React.createElement("tr",{className:"border-b border-[#383838] text-slate-400 bg-[#1e1e1e]"},/*#__PURE__*/React.createElement("th",{className:"p-3"},"Tên Người Dùng"),/*#__PURE__*/React.createElement("th",{className:"p-3"},"Email"),/*#__PURE__*/React.createElement("th",{className:"p-3"},"Vai Trò"),/*#__PURE__*/React.createElement("th",{className:"p-3"},"Dung Lượng Sử Dụng"),/*#__PURE__*/React.createElement("th",{className:"p-3"},"Hạn Mức Quota"),/*#__PURE__*/React.createElement("th",{className:"p-3 text-right"},"Thao Tác"))),/*#__PURE__*/React.createElement("tbody",{className:"divide-y divide-[#333]"},users.map(u=>/*#__PURE__*/React.createElement("tr",{key:u.id,className:"hover:bg-[#2e2e2e]"},/*#__PURE__*/React.createElement("td",{className:"p-3 font-semibold text-teal-300"},u.username,u.must_change_password&&/*#__PURE__*/React.createElement("span",{className:"ml-2 text-xs bg-amber-900/60 text-amber-300 px-1.5 py-0.5 rounded"},"Mật khẩu gốc")),/*#__PURE__*/React.createElement("td",{className:"p-3 text-slate-300"},u.email),/*#__PURE__*/React.createElement("td",{className:"p-3 uppercase font-bold text-amber-400"},u.role),/*#__PURE__*/React.createElement("td",{className:"p-3"},u.used_mb," MB"),/*#__PURE__*/React.createElement("td",{className:"p-3"},editingQuotaUser===u.id?/*#__PURE__*/React.createElement("div",{className:"flex items-center space-x-1"},/*#__PURE__*/React.createElement("input",{type:"number",value:newQuotaMb,onChange:e=>setNewQuotaMb(e.target.value),className:"w-16 bg-[#1e1e1e] border border-[#444] rounded px-1 py-0.5 text-xs text-slate-200"}),/*#__PURE__*/React.createElement("span",null,"MB"),/*#__PURE__*/React.createElement("button",{onClick:()=>handleSaveQuota(u.id),className:"px-2 py-0.5 bg-teal-600 rounded text-xs"},"Lưu")):/*#__PURE__*/React.createElement("span",{className:"font-semibold"},u.quota_mb," MB")),/*#__PURE__*/React.createElement("td",{className:"p-3 text-right space-x-2"},/*#__PURE__*/React.createElement("button",{onClick:()=>{setEditingQuotaUser(u.id);setNewQuotaMb(u.quota_mb);},className:"px-2 py-1 bg-slate-700 hover:bg-slate-600 rounded text-xs"},"Sửa Quota"),/*#__PURE__*/React.createElement("button",{onClick:()=>handleToggleRole(u),className:"px-2 py-1 bg-amber-700/60 hover:bg-amber-600 rounded text-xs"},"Đổi Role"),u.role!=='admin'&&/*#__PURE__*/React.createElement("button",{onClick:()=>handleDeleteUser(u.id),className:"px-2 py-1 bg-red-700/60 hover:bg-red-600 rounded text-xs"},"Xóa")))))))));};const DEFAULT_PRESETS=[{id:"preset_epic_orchestra_intro",name:"Epic Orchestra Intro (8 Bars)",keywords:["epic orchestra","epic orchestral","hoành tráng","nhạc phim epic"],category:"Orchestral / Film Score",default_bars:8,default_bpm:130,default_scale:"C Minor",system_instruction_template:"You are a professional film composer. Create a powerful, dramatic 8-bar orchestral intro. Keep the note density low (e.g. use mostly whole notes, half notes, or quarter notes) and do NOT generate dense 16th notes or complex drum rolls. This is critical to avoid output token limit timeouts. The required structure to return via the `generate_multitrack_midi` tool consists of 3 tracks: 1. Strings: plays smooth legato chord changes (one chord per 1 or 2 bars). 2. Brass Theme: plays a swelling simple melodic line in the C3-C5 range. 3. Epic Percussion: hits heavily on beats 1 and 3. Ensure the duration is precisely 8 bars (32 beats).",is_user_defined:false,created_at:"2026-07-23T16:00:00Z"},{id:"preset_pop_piano_chords",name:"Pop Piano Chords (4 Bars)",keywords:["pop piano","piano chords","ballad piano","hợp âm piano"],category:"Pop / Ballad",default_bars:4,default_bpm:90,default_scale:"C Major",system_instruction_template:"You are a professional Pop Piano player. Generate a beautiful 4-bar piano chord progression (e.g. C - G - Am - F) with pleasant chord voicing and simple accompaniment. Return the MIDI notes via `generate_multitrack_midi` function on a track named 'Pop Piano'. Keep notes simple, using mostly whole/half/quarter notes. Ensure the duration of the track is precisely 4 bars (16 beats).",is_user_defined:false,created_at:"2026-07-23T16:00:00Z"},{id:"preset_cyberpunk_synth",name:"Cyberpunk Synthwave (8 Bars)",keywords:["cyberpunk synth","synthwave","cyberpunk","futuristic synth"],category:"Electronic / Synthwave",default_bars:8,default_bpm:120,default_scale:"A Minor",system_instruction_template:"You are a Synthwave producer. Generate a driving 8-bar cyberpunk synth theme. Return MIDI notes via `generate_multitrack_midi` containing: 1. Synth Bass: eighth notes on pitch A1, C2, G1. 2. Synth Lead: simple melodic line in high register C4-E5. Keep notes clean and concise to ensure fast generation.",is_user_defined:false,created_at:"2026-07-23T16:00:00Z"}];const AIPresetModal=({isOpen,onClose})=>{if(!isOpen)return null;const[presets,setPresets]=React.useState(()=>{const local=localStorage.getItem('daw_ai_prompt_presets');if(!local)return DEFAULT_PRESETS;try{const parsed=JSON.parse(local);const userPresets=parsed.filter(p=>p.is_user_defined);return[...DEFAULT_PRESETS,...userPresets];}catch(_){return DEFAULT_PRESETS;}});const[search,setSearch]=React.useState('');const[filterCategory,setFilterCategory]=React.useState('ALL');const[editingPreset,setEditingPreset]=React.useState(null);// preset object or 'new' +const[confirmModal,setConfirmModal]=useState(null);const handleDragStart=e=>{const r=dragRef.current;r.active=true;r.startX=e.clientX;r.startY=e.clientY;r.ofsX=dragOfs.x;r.ofsY=dragOfs.y;const onMove=ev=>{if(!r.active)return;setDragOfs({x:r.ofsX+ev.clientX-r.startX,y:r.ofsY+ev.clientY-r.startY});};const onUp=()=>{r.active=false;document.removeEventListener('mousemove',onMove);document.removeEventListener('mouseup',onUp);};document.addEventListener('mousemove',onMove);document.addEventListener('mouseup',onUp);};useEffect(()=>{if(isOpen){fetchProfile();if(activeTab==='projects')fetchProjects();if(activeTab==='files')fetchFiles();}},[isOpen,activeTab]);const fetchProfile=async()=>{try{const data=await window.SonicAPI.getProfile();setProfile(data);}catch(e){setError(e.message||'Không thể tải thông tin profile');}};const fetchProjects=async()=>{setLoadingProjects(true);try{const data=await window.SonicAPI.listCloudProjects();setProjectsList(data||[]);}catch(e){showToast(e.message||'Không thể tải danh sách dự án','error');}finally{setLoadingProjects(false);}};const fetchFiles=async()=>{setLoadingFiles(true);try{const activeFileIds=tracks.map(t=>t.serverFileId).filter(Boolean);const data=await window.SonicAPI.listMyFiles(activeFileIds);setFilesList(data||[]);}catch(e){showToast(e.message||'Không thể tải danh sách tệp tin','error');}finally{setLoadingFiles(false);}};const handleOpenProject=async projectId=>{setAppWarningModal({title:"Mở dự án",message:"Bạn có muốn mở dự án này? Các thay đổi chưa lưu trên workspace hiện tại sẽ bị mất.",isAlert:false,onConfirm:async()=>{try{const proj=await window.SonicAPI.getCloudProject(projectId);const parsed=JSON.parse(proj.data_json);let restoredTracks=[];let restoredBpm=bpm;let restoredSessionTabs=[];let restoredSubTabs=[];if(parsed.main_session){const result=deserializeProjectFromSchema(parsed);restoredTracks=result.tracks;restoredBpm=result.bpm;restoredSessionTabs=result.sessionTabs;restoredSubTabs=result.subTabs;}else{restoredTracks=(parsed.tracks||[]).map(t=>{const{height:_h,...rest}=t;return{...rest,buffer:null,channelInfo:t.channelInfo||null,clips:t.clips||[],serverFileId:t.serverFileId||null};});}setTracks(restoredTracks);setBpm(restoredBpm.toString());setSelectedTrackId(restoredTracks[0]?.id||'1');setProjectName(proj.name);setCurrentProjectId(proj.id);if(restoredSessionTabs.length>0){setSessionTabs(restoredSessionTabs);}if(restoredSubTabs.length>0){setSubTabs(restoredSubTabs);}localStorage.setItem('sonic_project_name',proj.name);localStorage.setItem('sonic_project_id',proj.id);showToast(`Đã nạp dự án "${proj.name}" thành công!`,"success");}catch(e){showToast(e.message||"Lỗi khi nạp dự án","error");}}});};const handleDeleteProject=async(projectId,e)=>{e.stopPropagation();setConfirmModal({title:"Xóa dự án Cloud",message:"Bạn có chắc chắn muốn xóa dự án này khỏi Cloud? Hành động này không thể hoàn tác.",onConfirm:async()=>{try{await window.SonicAPI.deleteCloudProject(projectId);showToast("Đã xóa dự án thành công!","success");fetchProjects();fetchProfile();}catch(err){showToast(err.message||"Lỗi khi xóa dự án","error");}}});};const handleDeleteFile=async fileId=>{if(!confirm(`Bạn có chắc chắn muốn xóa tệp tin ${fileId}?`))return;try{await window.SonicAPI.deleteMyFile(fileId);showToast("Đã xóa tệp tin thành công!","success");fetchFiles();fetchProfile();}catch(err){showToast(err.message||"Lỗi khi xóa tệp tin","error");}};const handleCleanUnusedFiles=async()=>{const unusedFiles=filesList.filter(f=>!f.is_in_use);if(unusedFiles.length===0){showToast("Không có tập tin rác nào để dọn dẹp.","info");return;}if(!confirm(`Bạn có chắc chắn muốn xóa tất cả ${unusedFiles.length} tập tin rác để giải phóng dung lượng?`))return;let successCount=0;for(const file of unusedFiles){try{await window.SonicAPI.deleteMyFile(file.file_id);successCount++;}catch(e){console.error("Lỗi xóa file rác: ",file.file_id,e);}}showToast(`Đã dọn dẹp thành công ${successCount}/${unusedFiles.length} tập tin rác!`,"success");fetchFiles();fetchProfile();};const handleChangePassword=async e=>{e.preventDefault();setMsg('');setError('');setLoading(true);try{const res=await window.SonicAPI.changePassword(oldPassword,newPassword);setMsg(res.message||'Đổi mật khẩu thành công!');setOldPassword('');setNewPassword('');}catch(err){setError(err.message||'Lỗi khi đổi mật khẩu');}finally{setLoading(false);}};const modalStyle={left:`calc(50% + ${dragOfs.x}px)`,top:`calc(50% + ${dragOfs.y}px)`,transform:'translate(-50%, -50%)'};return/*#__PURE__*/React.createElement(React.Fragment,null,/*#__PURE__*/React.createElement("div",{key:"backdrop",className:"fixed inset-0 z-40 bg-black/70 backdrop-blur-sm",onClick:onClose}),confirmModal&&/*#__PURE__*/React.createElement("div",{key:"confirm-overlay",className:"fixed inset-0 z-[60] flex items-center justify-center bg-black/50"},/*#__PURE__*/React.createElement("div",{className:"bg-[#262626] border border-[#383838] rounded-lg shadow-2xl p-5 max-w-sm w-full text-slate-200"},/*#__PURE__*/React.createElement("h4",{className:"text-sm font-bold text-rose-400 mb-2"},confirmModal.title),/*#__PURE__*/React.createElement("p",{className:"text-xs text-slate-300 mb-4"},confirmModal.message),/*#__PURE__*/React.createElement("div",{className:"flex justify-end gap-2"},/*#__PURE__*/React.createElement("button",{onClick:()=>setConfirmModal(null),className:"px-3 py-1.5 bg-zinc-700 hover:bg-zinc-600 text-slate-300 rounded text-xs font-semibold"},"Hủy"),/*#__PURE__*/React.createElement("button",{onClick:()=>{const fn=confirmModal.onConfirm;setConfirmModal(null);fn();},className:"px-3 py-1.5 bg-rose-700 hover:bg-rose-600 text-white rounded text-xs font-semibold"},"Xác nhận xóa")))),/*#__PURE__*/React.createElement("div",{key:"dialog",className:"fixed z-50 bg-[#262626] border border-[#383838] rounded-xl shadow-2xl w-full max-w-2xl p-6 text-slate-200 flex flex-col max-h-[85vh]",style:modalStyle},/*#__PURE__*/React.createElement("div",{className:"flex justify-between items-center pb-3 border-b border-[#383838] shrink-0 cursor-grab active:cursor-grabbing select-none",onMouseDown:handleDragStart},/*#__PURE__*/React.createElement("h3",{className:"text-md font-bold text-teal-400 flex items-center gap-1.5"},"👤 Hồ Sơ Cá Nhân & Quản Lý Dự Án"),/*#__PURE__*/React.createElement("button",{onClick:onClose,className:"text-slate-400 hover:text-slate-200"},"✕")),/*#__PURE__*/React.createElement("div",{className:"flex gap-2 border-b border-[#383838] py-2 shrink-0 text-xs font-semibold"},[/*#__PURE__*/React.createElement("button",{key:"tab-acc",onClick:()=>setActiveTab('account'),className:`px-3 py-1.5 rounded transition ${activeTab==='account'?'bg-teal-950/60 text-teal-300 border border-teal-800':'text-slate-400 hover:text-slate-200'}`},"Tài Khoản"),/*#__PURE__*/React.createElement("button",{key:"tab-proj",onClick:()=>setActiveTab('projects'),className:`px-3 py-1.5 rounded transition ${activeTab==='projects'?'bg-teal-950/60 text-teal-300 border border-teal-800':'text-slate-400 hover:text-slate-200'}`},"Dự Án Cloud"),/*#__PURE__*/React.createElement("button",{key:"tab-files",onClick:()=>setActiveTab('files'),className:`px-3 py-1.5 rounded transition ${activeTab==='files'?'bg-teal-950/60 text-teal-300 border border-teal-800':'text-slate-400 hover:text-slate-200'}`},"Tập Tin Của Tôi")]),/*#__PURE__*/React.createElement("div",{className:"flex-1 overflow-y-auto pt-4 space-y-4 pr-1 min-h-[300px]"},activeTab==='account'&&profile?[/*#__PURE__*/React.createElement("div",{key:"quota-info",className:"bg-[#1e1e1e] p-4 rounded-lg border border-[#333] grid grid-cols-2 gap-4 text-xs"},[/*#__PURE__*/React.createElement("div",{key:"username"},[/*#__PURE__*/React.createElement("span",{className:"text-slate-500 block"},"Tên người dùng"),/*#__PURE__*/React.createElement("span",{className:"font-bold text-teal-300 text-sm"},profile.username)]),/*#__PURE__*/React.createElement("div",{key:"role"},[/*#__PURE__*/React.createElement("span",{className:"text-slate-500 block"},"Vai trò"),/*#__PURE__*/React.createElement("span",{className:"uppercase font-semibold text-amber-400"},profile.role)]),/*#__PURE__*/React.createElement("div",{key:"email"},[/*#__PURE__*/React.createElement("span",{className:"text-slate-500 block"},"Email"),/*#__PURE__*/React.createElement("span",null,profile.email)]),/*#__PURE__*/React.createElement("div",{key:"quota"},[/*#__PURE__*/React.createElement("span",{className:"text-slate-500 block"},"Dung lượng Quota"),/*#__PURE__*/React.createElement("span",{className:"font-semibold text-slate-200"},`${profile.quota.used_mb} MB / ${profile.quota.storage_limit_mb} MB`)])]),/*#__PURE__*/React.createElement("div",{key:"progress"},[/*#__PURE__*/React.createElement("div",{className:"flex justify-between text-xs mb-1"},[/*#__PURE__*/React.createElement("span",{className:"text-slate-400"},"Tiến trình sử dụng bộ nhớ Server"),/*#__PURE__*/React.createElement("span",{className:"font-bold text-teal-400"},`${(profile.quota.used_mb/profile.quota.storage_limit_mb*100).toFixed(1)}%`)]),/*#__PURE__*/React.createElement("div",{className:"w-full h-2 bg-slate-800 rounded-full overflow-hidden"},[/*#__PURE__*/React.createElement("div",{className:"h-full bg-teal-500 rounded-full transition-all duration-300",style:{width:`${Math.min(100,profile.quota.used_mb/profile.quota.storage_limit_mb*100)}%`}})])]),/*#__PURE__*/React.createElement("form",{key:"pwd-form",onSubmit:handleChangePassword,className:"pt-4 border-t border-[#383838] space-y-3"},[/*#__PURE__*/React.createElement("h4",{className:"text-xs font-bold text-slate-300 uppercase"},"Thay Đổi Mật Khẩu"),msg&&/*#__PURE__*/React.createElement("div",{className:"p-2 bg-emerald-950/60 border border-emerald-700 text-emerald-300 rounded text-xs"},msg),error&&/*#__PURE__*/React.createElement("div",{className:"p-2 bg-red-950/60 border border-red-700 text-red-300 rounded text-xs"},error),/*#__PURE__*/React.createElement("div",{key:"old"},[/*#__PURE__*/React.createElement("label",{className:"block text-xs text-slate-400 mb-1"},"Mật khẩu cũ"),/*#__PURE__*/React.createElement("input",{type:"password",autoComplete:"current-password",required:true,value:oldPassword,onChange:e=>setOldPassword(e.target.value),className:"w-full bg-[#1e1e1e] border border-[#383838] rounded px-3 py-1.5 text-xs focus:outline-none focus:border-teal-500"})]),/*#__PURE__*/React.createElement("div",{key:"new"},[/*#__PURE__*/React.createElement("label",{className:"block text-xs text-slate-400 mb-1"},"Mật khẩu mới"),/*#__PURE__*/React.createElement("input",{type:"password",autoComplete:"new-password",required:true,value:newPassword,onChange:e=>setNewPassword(e.target.value),className:"w-full bg-[#1e1e1e] border border-[#383838] rounded px-3 py-1.5 text-xs focus:outline-none focus:border-teal-500"})]),/*#__PURE__*/React.createElement("button",{type:"submit",disabled:loading,className:"w-full py-1.5 bg-teal-600 hover:bg-teal-500 text-white font-semibold text-xs rounded transition"},loading?'Đang cập nhật...':'Cập Nhật Mật Khẩu')])]:activeTab==='projects'?[loadingProjects?/*#__PURE__*/React.createElement("div",{key:"loading",className:"text-center py-8 text-xs text-zinc-500"},"Đang tải danh sách dự án..."):projectsList.length===0?/*#__PURE__*/React.createElement("div",{key:"empty",className:"text-center py-8 text-xs text-zinc-500"},"Bạn chưa có dự án nào lưu trên Cloud."):/*#__PURE__*/React.createElement("div",{key:"list",className:"space-y-1.5"},[projectsList.map(proj=>/*#__PURE__*/React.createElement("div",{key:proj.id,onClick:()=>handleOpenProject(proj.id),className:"flex items-center justify-between p-3 bg-[#1e1e1e] hover:bg-zinc-800 rounded border border-zinc-800 transition cursor-pointer text-xs group"},[/*#__PURE__*/React.createElement("div",{key:"meta"},[/*#__PURE__*/React.createElement("div",{className:"font-bold text-slate-200 group-hover:text-teal-400"},proj.name),/*#__PURE__*/React.createElement("div",{className:"text-[10px] text-zinc-500 mt-0.5"},`Dung lượng: ${proj.size_mb} MB | Cập nhật: ${new Date(proj.updated_at*1000).toLocaleString()}`)]),/*#__PURE__*/React.createElement("div",{key:"actions",className:"flex items-center gap-1.5"},[/*#__PURE__*/React.createElement("button",{onClick:e=>{e.stopPropagation();handleOpenProject(proj.id);},className:"px-2.5 py-1 bg-teal-800/80 hover:bg-teal-700 text-teal-200 rounded font-semibold text-[10px]"},"MỞ"),/*#__PURE__*/React.createElement("button",{onClick:e=>handleDeleteProject(proj.id,e),className:"px-2 py-1 bg-rose-950/60 hover:bg-rose-900/80 text-rose-300 rounded font-semibold text-[10px]"},"XÓA")])]))])]:activeTab==='files'?[/*#__PURE__*/React.createElement("div",{key:"cleanup-header",className:"flex items-center justify-between bg-zinc-900/60 p-2.5 rounded border border-zinc-800 text-xs shrink-0 mb-3"},[/*#__PURE__*/React.createElement("span",{className:"text-zinc-400 text-[10px]"},"💡 Các tập tin không dùng trong dự án nào sẽ được đánh dấu 'Không dùng' và có thể xóa để giải phóng dung lượng."),/*#__PURE__*/React.createElement("button",{onClick:handleCleanUnusedFiles,className:"px-2.5 py-1 bg-rose-700 hover:bg-rose-600 text-white rounded font-bold text-[10px] uppercase flex items-center gap-1"},"🧹 Dọn dẹp tệp rác")]),loadingFiles?/*#__PURE__*/React.createElement("div",{key:"loading",className:"text-center py-8 text-xs text-zinc-500"},"Đang tải danh sách tập tin..."):filesList.length===0?/*#__PURE__*/React.createElement("div",{key:"empty",className:"text-center py-8 text-xs text-zinc-500"},"Chưa có tập tin nào tải lên hoặc tạo ra."):/*#__PURE__*/React.createElement("div",{key:"list",className:"space-y-1.5"},[filesList.map(file=>/*#__PURE__*/React.createElement("div",{key:file.file_id,className:"flex items-center justify-between p-3 bg-[#1e1e1e] rounded border border-zinc-800 text-xs"},[/*#__PURE__*/React.createElement("div",{key:"meta",className:"max-w-[70%]"},[/*#__PURE__*/React.createElement("div",{className:"font-semibold text-slate-300 truncate"},file.original_name||file.file_id),/*#__PURE__*/React.createElement("div",{className:"text-[10px] text-zinc-500 mt-0.5"},`Loại: ${file.type} | Dung lượng: ${file.size_mb} MB | Tạo lúc: ${new Date(file.created_at*1000).toLocaleString()}`)]),/*#__PURE__*/React.createElement("div",{key:"actions",className:"flex items-center gap-2"},[file.is_in_use?/*#__PURE__*/React.createElement("span",{className:"px-2 py-0.5 bg-emerald-950 text-emerald-400 border border-emerald-900 rounded text-[9px] uppercase font-bold font-mono"},"Đang dùng"):/*#__PURE__*/React.createElement("span",{className:"px-2 py-0.5 bg-zinc-850 text-zinc-400 border border-zinc-700 rounded text-[9px] uppercase font-bold font-mono"},"Không dùng"),!file.is_in_use&&/*#__PURE__*/React.createElement("button",{onClick:()=>handleDeleteFile(file.file_id),className:"px-2 py-0.5 bg-rose-950 hover:bg-rose-900 text-rose-300 rounded font-semibold text-[10px] border border-rose-900"},"Xóa")])]))])]:null)));};const SaveProjectModal=({isOpen,onClose,onSave})=>{if(!isOpen)return null;const[name,setName]=useState('');const handleSubmit=e=>{e.preventDefault();if(!name.trim())return;onSave(name.trim());onClose();};return/*#__PURE__*/React.createElement("div",{className:"fixed inset-0 z-50 flex items-center justify-center bg-black/70 backdrop-blur-sm"},/*#__PURE__*/React.createElement("div",{className:"bg-[#262626] border border-[#383838] rounded-xl shadow-2xl w-full max-w-md p-6 text-slate-200"},/*#__PURE__*/React.createElement("h3",{className:"text-sm font-bold text-teal-400 mb-4 uppercase"},"Đặt tên dự án"),/*#__PURE__*/React.createElement("form",{onSubmit:handleSubmit,className:"space-y-4"},[/*#__PURE__*/React.createElement("input",{key:"name-input",type:"text",placeholder:"Nhập tên dự án...",required:true,value:name,onChange:e=>setName(e.target.value),className:"w-full bg-[#1e1e1e] border border-[#383838] rounded px-3 py-1.5 text-xs text-slate-200 focus:outline-none focus:border-teal-500 font-bold",autoFocus:true}),/*#__PURE__*/React.createElement("div",{key:"actions",className:"flex justify-end gap-2 text-xs"},[/*#__PURE__*/React.createElement("button",{key:"cancel",type:"button",onClick:onClose,className:"px-3 py-1.5 bg-zinc-800 hover:bg-zinc-700 text-zinc-300 rounded"},"Hủy"),/*#__PURE__*/React.createElement("button",{key:"save",type:"submit",className:"px-3 py-1.5 bg-teal-600 hover:bg-teal-500 text-white rounded font-semibold"},"Lưu")])])));};const SaveAsModal=({isOpen,onClose,projectName,onSaveCloud,onSaveLocal})=>{if(!isOpen)return null;const[name,setName]=useState(projectName||'');const[saveType,setSaveType]=useState('cloud');const handleSubmit=e=>{e.preventDefault();if(!name.trim())return;if(saveType==='cloud'){onSaveCloud(name.trim());}else{onSaveLocal(name.trim());}onClose();};return/*#__PURE__*/React.createElement("div",{className:"fixed inset-0 z-50 flex items-center justify-center bg-black/70 backdrop-blur-sm"},/*#__PURE__*/React.createElement("div",{className:"bg-[#262626] border border-[#383838] rounded-xl shadow-2xl w-full max-w-md p-6 text-slate-200"},/*#__PURE__*/React.createElement("h3",{className:"text-sm font-bold text-teal-400 mb-4 uppercase"},"Lưu dưới tên khác (Save As...)"),/*#__PURE__*/React.createElement("form",{onSubmit:handleSubmit,className:"space-y-4"},[/*#__PURE__*/React.createElement("div",{key:"name-block"},[/*#__PURE__*/React.createElement("label",{className:"block text-[10px] text-zinc-400 uppercase font-bold mb-1"},"Tên dự án mới"),/*#__PURE__*/React.createElement("input",{type:"text",placeholder:"Nhập tên mới...",required:true,value:name,onChange:e=>setName(e.target.value),className:"w-full bg-[#1e1e1e] border border-[#383838] rounded px-3 py-1.5 text-xs text-slate-200 focus:outline-none focus:border-teal-500 font-bold",autoFocus:true})]),/*#__PURE__*/React.createElement("div",{key:"type-block"},[/*#__PURE__*/React.createElement("label",{className:"block text-[10px] text-zinc-400 uppercase font-bold mb-1.5"},"Phương thức lưu trữ"),/*#__PURE__*/React.createElement("div",{className:"grid grid-cols-2 gap-2 text-xs"},[/*#__PURE__*/React.createElement("button",{key:"btn-cloud",type:"button",onClick:()=>setSaveType('cloud'),className:`py-2 rounded border flex flex-col items-center gap-1 font-semibold transition ${saveType==='cloud'?'bg-teal-950/60 border-teal-500 text-teal-300':'bg-zinc-800 border-zinc-700 hover:bg-zinc-750 text-zinc-400'}`},[/*#__PURE__*/React.createElement("span",{key:"title"},"☁️ Lưu Cloud"),/*#__PURE__*/React.createElement("span",{key:"desc",className:"text-[9px] font-normal text-zinc-500"},"Lưu lên server cá nhân")]),/*#__PURE__*/React.createElement("button",{key:"btn-local",type:"button",onClick:()=>setSaveType('local'),className:`py-2 rounded border flex flex-col items-center gap-1 font-semibold transition ${saveType==='local'?'bg-teal-950/60 border-teal-500 text-teal-300':'bg-zinc-800 border-zinc-700 hover:bg-zinc-750 text-zinc-400'}`},[/*#__PURE__*/React.createElement("span",{key:"title"},"💾 Tải về máy (.sfs)"),/*#__PURE__*/React.createElement("span",{key:"desc",className:"text-[9px] font-normal text-zinc-500"},"Tải tệp JSON dự án về máy")])])]),/*#__PURE__*/React.createElement("div",{key:"actions",className:"flex justify-end gap-2 text-xs pt-2"},[/*#__PURE__*/React.createElement("button",{key:"cancel",type:"button",onClick:onClose,className:"px-3 py-1.5 bg-zinc-800 hover:bg-zinc-700 text-zinc-300 rounded"},"Hủy"),/*#__PURE__*/React.createElement("button",{key:"save",type:"submit",className:"px-3 py-1.5 bg-teal-600 hover:bg-teal-500 text-white rounded font-semibold"},"Thực hiện lưu")])])));};const SystemManagerModal=({isOpen,onClose})=>{if(!isOpen)return null;const[users,setUsers]=useState([]);const[loading,setLoading]=useState(true);const[msg,setMsg]=useState('');const[error,setError]=useState('');const[editingQuotaUser,setEditingQuotaUser]=useState(null);const[newQuotaMb,setNewQuotaMb]=useState(500);useEffect(()=>{if(isOpen)loadUsers();},[isOpen]);const loadUsers=async()=>{setLoading(true);setError('');try{const data=await window.SonicAPI.listUsers();setUsers(data);}catch(err){setError(err.message||'Không thể tải danh sách người dùng hệ thống');}finally{setLoading(false);}};const handleSaveQuota=async userId=>{try{await window.SonicAPI.updateUserQuota(userId,parseInt(newQuotaMb));setMsg('Đã cập nhật hạn mức Quota thành công!');setEditingQuotaUser(null);loadUsers();}catch(err){setError(err.message||'Lỗi cập nhật Quota');}};const handleToggleRole=async user=>{const nextRole=user.role==='admin'?'standard':'admin';try{await window.SonicAPI.updateUserRole(user.id,nextRole,user.is_active);setMsg(`Đã đổi vai trò người dùng ${user.username} thành ${nextRole}`);loadUsers();}catch(err){setError(err.message||'Lỗi cập nhật vai trò');}};const handleDeleteUser=async userId=>{if(!confirm('Bạn có chắc chắn muốn xóa người dùng này khỏi hệ thống?'))return;try{await window.SonicAPI.deleteUser(userId);setMsg('Đã xóa người dùng thành công');loadUsers();}catch(err){setError(err.message||'Lỗi khi xóa người dùng');}};return/*#__PURE__*/React.createElement("div",{className:"fixed inset-0 z-50 flex items-center justify-center bg-black/70 backdrop-blur-sm"},/*#__PURE__*/React.createElement("div",{className:"bg-[#262626] border border-[#383838] rounded-xl shadow-2xl w-full max-w-4xl p-6 text-slate-200"},/*#__PURE__*/React.createElement("div",{className:"flex justify-between items-center pb-4 border-b border-[#383838]"},/*#__PURE__*/React.createElement("h3",{className:"text-lg font-bold text-amber-400"},"⚙️ Quản Lý Hệ Thống & Phân Quyền Admin"),/*#__PURE__*/React.createElement("button",{onClick:onClose,className:"text-slate-400 hover:text-slate-200"},"✕")),msg&&/*#__PURE__*/React.createElement("div",{className:"mt-3 p-2 bg-emerald-950/60 border border-emerald-700 text-emerald-300 rounded text-xs"},msg),error&&/*#__PURE__*/React.createElement("div",{className:"mt-3 p-2 bg-red-950/60 border border-red-700 text-red-300 rounded text-xs"},error),/*#__PURE__*/React.createElement("div",{className:"mt-4 overflow-x-auto max-h-96 no-scrollbar"},loading?/*#__PURE__*/React.createElement("div",{className:"py-8 text-center text-slate-400 text-xs"},"Đang tải thông tin hệ thống..."):/*#__PURE__*/React.createElement("table",{className:"w-full text-left text-xs border-collapse"},/*#__PURE__*/React.createElement("thead",null,/*#__PURE__*/React.createElement("tr",{className:"border-b border-[#383838] text-slate-400 bg-[#1e1e1e]"},/*#__PURE__*/React.createElement("th",{className:"p-3"},"Tên Người Dùng"),/*#__PURE__*/React.createElement("th",{className:"p-3"},"Email"),/*#__PURE__*/React.createElement("th",{className:"p-3"},"Vai Trò"),/*#__PURE__*/React.createElement("th",{className:"p-3"},"Dung Lượng Sử Dụng"),/*#__PURE__*/React.createElement("th",{className:"p-3"},"Hạn Mức Quota"),/*#__PURE__*/React.createElement("th",{className:"p-3 text-right"},"Thao Tác"))),/*#__PURE__*/React.createElement("tbody",{className:"divide-y divide-[#333]"},users.map(u=>/*#__PURE__*/React.createElement("tr",{key:u.id,className:"hover:bg-[#2e2e2e]"},/*#__PURE__*/React.createElement("td",{className:"p-3 font-semibold text-teal-300"},u.username,u.must_change_password&&/*#__PURE__*/React.createElement("span",{className:"ml-2 text-xs bg-amber-900/60 text-amber-300 px-1.5 py-0.5 rounded"},"Mật khẩu gốc")),/*#__PURE__*/React.createElement("td",{className:"p-3 text-slate-300"},u.email),/*#__PURE__*/React.createElement("td",{className:"p-3 uppercase font-bold text-amber-400"},u.role),/*#__PURE__*/React.createElement("td",{className:"p-3"},u.used_mb," MB"),/*#__PURE__*/React.createElement("td",{className:"p-3"},editingQuotaUser===u.id?/*#__PURE__*/React.createElement("div",{className:"flex items-center space-x-1"},/*#__PURE__*/React.createElement("input",{type:"number",value:newQuotaMb,onChange:e=>setNewQuotaMb(e.target.value),className:"w-16 bg-[#1e1e1e] border border-[#444] rounded px-1 py-0.5 text-xs text-slate-200"}),/*#__PURE__*/React.createElement("span",null,"MB"),/*#__PURE__*/React.createElement("button",{onClick:()=>handleSaveQuota(u.id),className:"px-2 py-0.5 bg-teal-600 rounded text-xs"},"Lưu")):/*#__PURE__*/React.createElement("span",{className:"font-semibold"},u.quota_mb," MB")),/*#__PURE__*/React.createElement("td",{className:"p-3 text-right space-x-2"},/*#__PURE__*/React.createElement("button",{onClick:()=>{setEditingQuotaUser(u.id);setNewQuotaMb(u.quota_mb);},className:"px-2 py-1 bg-slate-700 hover:bg-slate-600 rounded text-xs"},"Sửa Quota"),/*#__PURE__*/React.createElement("button",{onClick:()=>handleToggleRole(u),className:"px-2 py-1 bg-amber-700/60 hover:bg-amber-600 rounded text-xs"},"Đổi Role"),u.role!=='admin'&&/*#__PURE__*/React.createElement("button",{onClick:()=>handleDeleteUser(u.id),className:"px-2 py-1 bg-red-700/60 hover:bg-red-600 rounded text-xs"},"Xóa")))))))));};const DEFAULT_PRESETS=[{id:"preset_epic_orchestra_intro",name:"Epic Orchestra Intro (8 Bars)",keywords:["epic orchestra","epic orchestral","hoành tráng","nhạc phim epic"],category:"Orchestral / Film Score",default_bars:8,default_bpm:130,default_scale:"C Minor",system_instruction_template:"You are a professional film composer. Create a powerful, dramatic 8-bar orchestral intro. Keep the note density low (e.g. use mostly whole notes, half notes, or quarter notes) and do NOT generate dense 16th notes or complex drum rolls. This is critical to avoid output token limit timeouts. The required structure to return via the `generate_multitrack_midi` tool consists of 3 tracks: 1. Strings: plays smooth legato chord changes (one chord per 1 or 2 bars). 2. Brass Theme: plays a swelling simple melodic line in the C3-C5 range. 3. Epic Percussion: hits heavily on beats 1 and 3. Ensure the duration is precisely 8 bars (32 beats).",is_user_defined:false,created_at:"2026-07-23T16:00:00Z"},{id:"preset_pop_piano_chords",name:"Pop Piano Chords (4 Bars)",keywords:["pop piano","piano chords","ballad piano","hợp âm piano"],category:"Pop / Ballad",default_bars:4,default_bpm:90,default_scale:"C Major",system_instruction_template:"You are a professional Pop Piano player. Generate a beautiful 4-bar piano chord progression (e.g. C - G - Am - F) with pleasant chord voicing and simple accompaniment. Return the MIDI notes via `generate_multitrack_midi` function on a track named 'Pop Piano'. Keep notes simple, using mostly whole/half/quarter notes. Ensure the duration of the track is precisely 4 bars (16 beats).",is_user_defined:false,created_at:"2026-07-23T16:00:00Z"},{id:"preset_cyberpunk_synth",name:"Cyberpunk Synthwave (8 Bars)",keywords:["cyberpunk synth","synthwave","cyberpunk","futuristic synth"],category:"Electronic / Synthwave",default_bars:8,default_bpm:120,default_scale:"A Minor",system_instruction_template:"You are a Synthwave producer. Generate a driving 8-bar cyberpunk synth theme. Return MIDI notes via `generate_multitrack_midi` containing: 1. Synth Bass: eighth notes on pitch A1, C2, G1. 2. Synth Lead: simple melodic line in high register C4-E5. Keep notes clean and concise to ensure fast generation.",is_user_defined:false,created_at:"2026-07-23T16:00:00Z"}];const AIPresetModal=({isOpen,onClose})=>{if(!isOpen)return null;const[presets,setPresets]=React.useState(()=>{const local=localStorage.getItem('daw_ai_prompt_presets');if(!local)return DEFAULT_PRESETS;try{const parsed=JSON.parse(local);const userPresets=parsed.filter(p=>p.is_user_defined);return[...DEFAULT_PRESETS,...userPresets];}catch(_){return DEFAULT_PRESETS;}});const[search,setSearch]=React.useState('');const[filterCategory,setFilterCategory]=React.useState('ALL');const[editingPreset,setEditingPreset]=React.useState(null);// preset object or 'new' // Form states -const[formName,setFormName]=React.useState('');const[formKeywords,setFormKeywords]=React.useState('');const[formCategory,setFormCategory]=React.useState('Orchestral / Film Score');const[formBars,setFormBars]=React.useState(8);const[formBpm,setFormBpm]=React.useState(120);const[formScale,setFormScale]=React.useState('C Minor');const[formTemplate,setFormTemplate]=React.useState('');const savePresets=newPresets=>{setPresets(newPresets);localStorage.setItem('daw_ai_prompt_presets',JSON.stringify(newPresets));};const handleEdit=p=>{setEditingPreset(p);setFormName(p.name);setFormKeywords(p.keywords.join(', '));setFormCategory(p.category);setFormBars(p.default_bars);setFormBpm(p.default_bpm);setFormScale(p.default_scale);setFormTemplate(p.system_instruction_template);};const handleNew=()=>{setEditingPreset('new');setFormName('');setFormKeywords('');setFormCategory('Orchestral / Film Score');setFormBars(8);setFormBpm(120);setFormScale('C Minor');setFormTemplate('');};const handleDelete=id=>{const updated=presets.filter(p=>p.id!==id);savePresets(updated);showToast('Đã xóa preset.','info');};const handleSaveForm=e=>{e.preventDefault();if(!formName.trim()||!formTemplate.trim()){showToast('Vui lòng điền đầy đủ tên và mẫu gợi ý.','warning');return;}const keywordsArray=formKeywords.split(',').map(k=>k.trim()).filter(Boolean);const presetObj={id:editingPreset==='new'?'preset_'+Date.now():editingPreset.id,name:formName.trim(),keywords:keywordsArray,category:formCategory,default_bars:parseInt(formBars)||8,default_bpm:parseInt(formBpm)||120,default_scale:formScale,system_instruction_template:formTemplate.trim(),is_user_defined:true,created_at:editingPreset==='new'?new Date().toISOString():editingPreset.created_at};let updated;if(editingPreset==='new'){updated=[...presets,presetObj];}else{updated=presets.map(p=>p.id===presetObj.id?presetObj:p);}savePresets(updated);setEditingPreset(null);showToast('Đã lưu preset thành công!','success');};const categories=['ALL',...new Set(presets.map(p=>p.category))];const filtered=presets.filter(p=>{const matchesSearch=p.name.toLowerCase().includes(search.toLowerCase())||p.keywords.some(k=>k.toLowerCase().includes(search.toLowerCase()));const matchesCategory=filterCategory==='ALL'||p.category===filterCategory;return matchesSearch&&matchesCategory;});return/*#__PURE__*/React.createElement("div",{className:"fixed inset-0 z-50 flex items-center justify-center bg-black/75 backdrop-blur-sm p-4 animate-fade-in"},/*#__PURE__*/React.createElement("div",{className:"bg-[#18181b] border border-zinc-800 rounded-xl shadow-2xl w-full max-w-4xl max-h-[85vh] flex flex-col overflow-hidden text-zinc-100"},/*#__PURE__*/React.createElement("div",{className:"p-4 border-b border-zinc-800 flex items-center justify-between shrink-0 bg-[#202024]"},/*#__PURE__*/React.createElement("h2",{className:"text-sm font-bold tracking-wider uppercase text-purple-400 flex items-center gap-1.5"},/*#__PURE__*/React.createElement("i",{"data-lucide":"sliders",className:"w-4 h-4"}),"AI Prompt Preset Manager"),/*#__PURE__*/React.createElement("button",{onClick:()=>{setEditingPreset(null);onClose();},className:"text-zinc-400 hover:text-zinc-200 transition"},/*#__PURE__*/React.createElement("i",{"data-lucide":"x",className:"w-4 h-4"}))),/*#__PURE__*/React.createElement("div",{className:"flex-1 overflow-y-auto p-4 flex gap-4 min-h-0"},!editingPreset?/*#__PURE__*/React.createElement(React.Fragment,null,/*#__PURE__*/React.createElement("div",{className:"flex-1 flex flex-col min-w-0"},/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-2 mb-3 shrink-0"},/*#__PURE__*/React.createElement("input",{type:"text",placeholder:"Tìm kiếm preset hoặc từ khóa...",value:search,onChange:e=>setSearch(e.target.value),className:"flex-1 bg-zinc-900 border border-zinc-700 rounded px-2.5 py-1 text-xs outline-none focus:border-purple-600"}),/*#__PURE__*/React.createElement("select",{value:filterCategory,onChange:e=>setFilterCategory(e.target.value),className:"bg-zinc-900 border border-zinc-700 rounded px-2.5 py-1 text-xs outline-none focus:border-purple-600"},categories.map(c=>/*#__PURE__*/React.createElement("option",{key:c,value:c},c==='ALL'?'Tất cả danh mục':c))),/*#__PURE__*/React.createElement("button",{onClick:handleNew,className:"px-3 py-1 bg-purple-700 hover:bg-purple-600 text-white rounded text-xs font-bold flex items-center gap-1.5 shadow transition shrink-0"},/*#__PURE__*/React.createElement("i",{"data-lucide":"plus",className:"w-3.5 h-3.5"}),"Tạo mới")),/*#__PURE__*/React.createElement("div",{className:"flex-1 border border-zinc-800 rounded bg-[#0f0f12] overflow-y-auto"},filtered.length===0?/*#__PURE__*/React.createElement("div",{className:"p-8 text-center text-zinc-500 text-xs italic"},"Không tìm thấy preset nào."):/*#__PURE__*/React.createElement("table",{className:"w-full text-left text-xs border-collapse"},/*#__PURE__*/React.createElement("thead",{className:"bg-[#1f1f23] text-zinc-400 font-bold border-b border-zinc-800"},/*#__PURE__*/React.createElement("tr",null,/*#__PURE__*/React.createElement("th",{className:"p-2.5 w-1/4"},"Tên Preset"),/*#__PURE__*/React.createElement("th",{className:"p-2.5 w-1/4"},"Từ khóa kích hoạt"),/*#__PURE__*/React.createElement("th",{className:"p-2.5 w-1/6"},"Số Bar"),/*#__PURE__*/React.createElement("th",{className:"p-2.5 w-1/6"},"BPM"),/*#__PURE__*/React.createElement("th",{className:"p-2.5 w-1/6 text-right"},"Hành động"))),/*#__PURE__*/React.createElement("tbody",null,filtered.map(p=>/*#__PURE__*/React.createElement("tr",{key:p.id,className:"border-b border-zinc-800/50 hover:bg-zinc-850"},/*#__PURE__*/React.createElement("td",{className:"p-2.5 font-semibold text-purple-300"},p.name),/*#__PURE__*/React.createElement("td",{className:"p-2.5 text-zinc-400 font-mono text-[11px] truncate max-w-[150px]"},p.keywords.join(', ')),/*#__PURE__*/React.createElement("td",{className:"p-2.5 text-zinc-400"},p.default_bars," Bars"),/*#__PURE__*/React.createElement("td",{className:"p-2.5 text-zinc-400"},p.default_bpm," BPM"),/*#__PURE__*/React.createElement("td",{className:"p-2.5 text-right flex items-center justify-end gap-1.5 h-full"},/*#__PURE__*/React.createElement("button",{type:"button",onClick:()=>handleEdit(p),className:"px-2 py-0.5 bg-zinc-850 hover:bg-zinc-700 text-zinc-300 rounded border border-zinc-700 text-[10px]"},"Sửa"),/*#__PURE__*/React.createElement("button",{type:"button",onClick:()=>handleDelete(p.id),className:"px-2 py-0.5 bg-red-950/40 hover:bg-red-800 text-red-400 rounded border border-red-900 text-[10px]"},"Xóa"))))))))):/*#__PURE__*/React.createElement("form",{onSubmit:handleSaveForm,className:"flex-1 flex flex-col gap-3 min-w-0"},/*#__PURE__*/React.createElement("div",{className:"text-xs font-bold text-zinc-400 shrink-0 border-b border-zinc-800 pb-1"},editingPreset==='new'?"TẠO PRESET MỚI":`SỬA PRESET: ${editingPreset.name}`),/*#__PURE__*/React.createElement("div",{className:"grid grid-cols-2 gap-3"},/*#__PURE__*/React.createElement("div",{className:"flex flex-col gap-1"},/*#__PURE__*/React.createElement("label",{className:"text-[10px] uppercase font-bold text-zinc-500"},"Tên Preset"),/*#__PURE__*/React.createElement("input",{type:"text",value:formName,onChange:e=>setFormName(e.target.value),className:"bg-zinc-900 border border-zinc-700 rounded px-2.5 py-1 text-xs outline-none focus:border-purple-600 text-zinc-200"})),/*#__PURE__*/React.createElement("div",{className:"flex flex-col gap-1"},/*#__PURE__*/React.createElement("label",{className:"text-[10px] uppercase font-bold text-zinc-500"},"Danh mục"),/*#__PURE__*/React.createElement("input",{type:"text",value:formCategory,onChange:e=>setFormCategory(e.target.value),className:"bg-zinc-900 border border-zinc-700 rounded px-2.5 py-1 text-xs outline-none focus:border-purple-600 text-zinc-200"}))),/*#__PURE__*/React.createElement("div",{className:"flex flex-col gap-1"},/*#__PURE__*/React.createElement("label",{className:"text-[10px] uppercase font-bold text-zinc-500"},"Từ khóa kích hoạt (ngăn cách bằng dấu phẩy)"),/*#__PURE__*/React.createElement("input",{type:"text",value:formKeywords,onChange:e=>setFormKeywords(e.target.value),placeholder:"Ví dụ: epic orchestra, hoành tráng, nhạc phim epic",className:"bg-zinc-900 border border-zinc-700 rounded px-2.5 py-1 text-xs outline-none focus:border-purple-600 text-zinc-200"})),/*#__PURE__*/React.createElement("div",{className:"grid grid-cols-3 gap-3"},/*#__PURE__*/React.createElement("div",{className:"flex flex-col gap-1"},/*#__PURE__*/React.createElement("label",{className:"text-[10px] uppercase font-bold text-zinc-500"},"Số Bars mặc định"),/*#__PURE__*/React.createElement("input",{type:"number",value:formBars,onChange:e=>setFormBars(e.target.value),className:"bg-zinc-900 border border-zinc-700 rounded px-2.5 py-1 text-xs outline-none focus:border-purple-600 text-zinc-200"})),/*#__PURE__*/React.createElement("div",{className:"flex flex-col gap-1"},/*#__PURE__*/React.createElement("label",{className:"text-[10px] uppercase font-bold text-zinc-500"},"BPM mặc định"),/*#__PURE__*/React.createElement("input",{type:"number",value:formBpm,onChange:e=>setFormBpm(e.target.value),className:"bg-zinc-900 border border-zinc-700 rounded px-2.5 py-1 text-xs outline-none focus:border-purple-600 text-zinc-200"})),/*#__PURE__*/React.createElement("div",{className:"flex flex-col gap-1"},/*#__PURE__*/React.createElement("label",{className:"text-[10px] uppercase font-bold text-zinc-500"},"Âm giai (Scale) mặc định"),/*#__PURE__*/React.createElement("input",{type:"text",value:formScale,onChange:e=>setFormScale(e.target.value),className:"bg-zinc-900 border border-zinc-700 rounded px-2.5 py-1 text-xs outline-none focus:border-purple-600 text-zinc-200"}))),/*#__PURE__*/React.createElement("div",{className:"flex flex-col gap-1 flex-1 min-h-0"},/*#__PURE__*/React.createElement("label",{className:"text-[10px] uppercase font-bold text-zinc-500"},"System Prompt Template / Luật soạn nhạc"),/*#__PURE__*/React.createElement("textarea",{value:formTemplate,onChange:e=>setFormTemplate(e.target.value),rows:6,className:"flex-1 bg-zinc-900 border border-zinc-700 rounded p-2.5 text-xs outline-none focus:border-purple-600 text-zinc-200 font-mono resize-none"})),/*#__PURE__*/React.createElement("div",{className:"flex items-center justify-end gap-2 shrink-0 pt-2 border-t border-zinc-800"},/*#__PURE__*/React.createElement("button",{type:"button",onClick:()=>setEditingPreset(null),className:"px-3 py-1.5 bg-zinc-800 hover:bg-zinc-700 text-zinc-400 border border-zinc-700 rounded text-xs transition"},"Quay lại"),/*#__PURE__*/React.createElement("button",{type:"submit",className:"px-3 py-1.5 bg-purple-700 hover:bg-purple-600 text-white rounded text-xs font-bold shadow transition"},"Lưu Preset")))),/*#__PURE__*/React.createElement("div",{className:"p-4 border-t border-zinc-800 flex justify-end shrink-0 bg-[#202024]"},/*#__PURE__*/React.createElement("button",{onClick:()=>{setEditingPreset(null);onClose();},className:"px-4 py-1.5 bg-zinc-800 hover:bg-zinc-700 text-zinc-300 border border-zinc-700 rounded text-xs font-bold shadow transition"},"Đóng"))));};const PianoRollTabEditor=({st,zoom,bpm,viewportWidth,onClose,onUpdateNotes,onSaveNotes,setSubTabs,onPlayPause,onStop,isPlaying,playPreviewNote})=>{const[activeRollTool,setActiveRollTool]=React.useState('select');const[snapVal,setSnapVal]=React.useState('1/16');const[ccMode,setCcMode]=React.useState('velocity');const[rollZoom,setRollZoom]=React.useState(60);// local horizontal zoom factor +const[formName,setFormName]=React.useState('');const[formKeywords,setFormKeywords]=React.useState('');const[formCategory,setFormCategory]=React.useState('Orchestral / Film Score');const[formBars,setFormBars]=React.useState(8);const[formBpm,setFormBpm]=React.useState(120);const[formScale,setFormScale]=React.useState('C Minor');const[formTemplate,setFormTemplate]=React.useState('');const savePresets=newPresets=>{setPresets(newPresets);localStorage.setItem('daw_ai_prompt_presets',JSON.stringify(newPresets));};const handleEdit=p=>{setEditingPreset(p);setFormName(p.name);setFormKeywords(p.keywords.join(', '));setFormCategory(p.category);setFormBars(p.default_bars);setFormBpm(p.default_bpm);setFormScale(p.default_scale);setFormTemplate(p.system_instruction_template);};const handleNew=()=>{setEditingPreset('new');setFormName('');setFormKeywords('');setFormCategory('Orchestral / Film Score');setFormBars(8);setFormBpm(120);setFormScale('C Minor');setFormTemplate('');};const handleDelete=id=>{const updated=presets.filter(p=>p.id!==id);savePresets(updated);showToast('Đã xóa preset.','info');};const handleSaveForm=e=>{e.preventDefault();if(!formName.trim()||!formTemplate.trim()){showToast('Vui lòng điền đầy đủ tên và mẫu gợi ý.','warning');return;}const keywordsArray=formKeywords.split(',').map(k=>k.trim()).filter(Boolean);const presetObj={id:editingPreset==='new'?'preset_'+Date.now():editingPreset.id,name:formName.trim(),keywords:keywordsArray,category:formCategory,default_bars:parseInt(formBars)||8,default_bpm:parseInt(formBpm)||120,default_scale:formScale,system_instruction_template:formTemplate.trim(),is_user_defined:true,created_at:editingPreset==='new'?new Date().toISOString():editingPreset.created_at};let updated;if(editingPreset==='new'){updated=[...presets,presetObj];}else{updated=presets.map(p=>p.id===presetObj.id?presetObj:p);}savePresets(updated);setEditingPreset(null);showToast('Đã lưu preset thành công!','success');};const categories=['ALL',...new Set(presets.map(p=>p.category))];const filtered=presets.filter(p=>{const matchesSearch=p.name.toLowerCase().includes(search.toLowerCase())||p.keywords.some(k=>k.toLowerCase().includes(search.toLowerCase()));const matchesCategory=filterCategory==='ALL'||p.category===filterCategory;return matchesSearch&&matchesCategory;});return/*#__PURE__*/React.createElement("div",{className:"fixed inset-0 z-50 flex items-center justify-center bg-black/75 backdrop-blur-sm p-4 animate-fade-in"},/*#__PURE__*/React.createElement("div",{className:"bg-[#18181b] border border-zinc-800 rounded-xl shadow-2xl w-full max-w-4xl max-h-[85vh] flex flex-col overflow-hidden text-zinc-100"},/*#__PURE__*/React.createElement("div",{className:"p-4 border-b border-zinc-800 flex items-center justify-between shrink-0 bg-[#202024]"},/*#__PURE__*/React.createElement("h2",{className:"text-sm font-bold tracking-wider uppercase text-purple-400 flex items-center gap-1.5"},/*#__PURE__*/React.createElement("i",{"data-lucide":"sliders",className:"w-4 h-4"}),"AI Prompt Preset Manager"),/*#__PURE__*/React.createElement("button",{onClick:()=>{setEditingPreset(null);onClose();},className:"text-zinc-400 hover:text-zinc-200 transition"},/*#__PURE__*/React.createElement("i",{"data-lucide":"x",className:"w-4 h-4"}))),/*#__PURE__*/React.createElement("div",{className:"flex-1 overflow-y-auto p-4 flex gap-4 min-h-0"},!editingPreset?/*#__PURE__*/React.createElement(React.Fragment,null,/*#__PURE__*/React.createElement("div",{className:"flex-1 flex flex-col min-w-0"},/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-2 mb-3 shrink-0"},/*#__PURE__*/React.createElement("input",{type:"text",placeholder:"Tìm kiếm preset hoặc từ khóa...",value:search,onChange:e=>setSearch(e.target.value),className:"flex-1 bg-zinc-900 border border-zinc-700 rounded px-2.5 py-1 text-xs outline-none focus:border-purple-600"}),/*#__PURE__*/React.createElement("select",{value:filterCategory,onChange:e=>setFilterCategory(e.target.value),className:"bg-zinc-900 border border-zinc-700 rounded px-2.5 py-1 text-xs outline-none focus:border-purple-600"},categories.map(c=>/*#__PURE__*/React.createElement("option",{key:c,value:c},c==='ALL'?'Tất cả danh mục':c))),/*#__PURE__*/React.createElement("button",{onClick:handleNew,className:"px-3 py-1 bg-purple-700 hover:bg-purple-600 text-white rounded text-xs font-bold flex items-center gap-1.5 shadow transition shrink-0"},/*#__PURE__*/React.createElement("i",{"data-lucide":"plus",className:"w-3.5 h-3.5"}),"Tạo mới")),/*#__PURE__*/React.createElement("div",{className:"flex-1 border border-zinc-800 rounded bg-[#0f0f12] overflow-y-auto"},filtered.length===0?/*#__PURE__*/React.createElement("div",{className:"p-8 text-center text-zinc-500 text-xs italic"},"Không tìm thấy preset nào."):/*#__PURE__*/React.createElement("table",{className:"w-full text-left text-xs border-collapse"},/*#__PURE__*/React.createElement("thead",{className:"bg-[#1f1f23] text-zinc-400 font-bold border-b border-zinc-800"},/*#__PURE__*/React.createElement("tr",null,/*#__PURE__*/React.createElement("th",{className:"p-2.5 w-1/4"},"Tên Preset"),/*#__PURE__*/React.createElement("th",{className:"p-2.5 w-1/4"},"Từ khóa kích hoạt"),/*#__PURE__*/React.createElement("th",{className:"p-2.5 w-1/6"},"Số Bar"),/*#__PURE__*/React.createElement("th",{className:"p-2.5 w-1/6"},"BPM"),/*#__PURE__*/React.createElement("th",{className:"p-2.5 w-1/6 text-right"},"Hành động"))),/*#__PURE__*/React.createElement("tbody",null,filtered.map(p=>/*#__PURE__*/React.createElement("tr",{key:p.id,className:"border-b border-zinc-800/50 hover:bg-zinc-850"},/*#__PURE__*/React.createElement("td",{className:"p-2.5 font-semibold text-purple-300"},p.name),/*#__PURE__*/React.createElement("td",{className:"p-2.5 text-zinc-400 font-mono text-[11px] truncate max-w-[150px]"},p.keywords.join(', ')),/*#__PURE__*/React.createElement("td",{className:"p-2.5 text-zinc-400"},p.default_bars," Bars"),/*#__PURE__*/React.createElement("td",{className:"p-2.5 text-zinc-400"},p.default_bpm," BPM"),/*#__PURE__*/React.createElement("td",{className:"p-2.5 text-right flex items-center justify-end gap-1.5 h-full"},/*#__PURE__*/React.createElement("button",{type:"button",onClick:()=>handleEdit(p),className:"px-2 py-0.5 bg-zinc-850 hover:bg-zinc-700 text-zinc-300 rounded border border-zinc-700 text-[10px]"},"Sửa"),/*#__PURE__*/React.createElement("button",{type:"button",onClick:()=>handleDelete(p.id),className:"px-2 py-0.5 bg-red-950/40 hover:bg-red-800 text-red-400 rounded border border-red-900 text-[10px]"},"Xóa"))))))))):/*#__PURE__*/React.createElement("form",{onSubmit:handleSaveForm,className:"flex-1 flex flex-col gap-3 min-w-0"},/*#__PURE__*/React.createElement("div",{className:"text-xs font-bold text-zinc-400 shrink-0 border-b border-zinc-800 pb-1"},editingPreset==='new'?"TẠO PRESET MỚI":`SỬA PRESET: ${editingPreset.name}`),/*#__PURE__*/React.createElement("div",{className:"grid grid-cols-2 gap-3"},/*#__PURE__*/React.createElement("div",{className:"flex flex-col gap-1"},/*#__PURE__*/React.createElement("label",{className:"text-[10px] uppercase font-bold text-zinc-500"},"Tên Preset"),/*#__PURE__*/React.createElement("input",{type:"text",value:formName,onChange:e=>setFormName(e.target.value),className:"bg-zinc-900 border border-zinc-700 rounded px-2.5 py-1 text-xs outline-none focus:border-purple-600 text-zinc-200"})),/*#__PURE__*/React.createElement("div",{className:"flex flex-col gap-1"},/*#__PURE__*/React.createElement("label",{className:"text-[10px] uppercase font-bold text-zinc-500"},"Danh mục"),/*#__PURE__*/React.createElement("input",{type:"text",value:formCategory,onChange:e=>setFormCategory(e.target.value),className:"bg-zinc-900 border border-zinc-700 rounded px-2.5 py-1 text-xs outline-none focus:border-purple-600 text-zinc-200"}))),/*#__PURE__*/React.createElement("div",{className:"flex flex-col gap-1"},/*#__PURE__*/React.createElement("label",{className:"text-[10px] uppercase font-bold text-zinc-500"},"Từ khóa kích hoạt (ngăn cách bằng dấu phẩy)"),/*#__PURE__*/React.createElement("input",{type:"text",value:formKeywords,onChange:e=>setFormKeywords(e.target.value),placeholder:"Ví dụ: epic orchestra, hoành tráng, nhạc phim epic",className:"bg-zinc-900 border border-zinc-700 rounded px-2.5 py-1 text-xs outline-none focus:border-purple-600 text-zinc-200"})),/*#__PURE__*/React.createElement("div",{className:"grid grid-cols-3 gap-3"},/*#__PURE__*/React.createElement("div",{className:"flex flex-col gap-1"},/*#__PURE__*/React.createElement("label",{className:"text-[10px] uppercase font-bold text-zinc-500"},"Số Bars mặc định"),/*#__PURE__*/React.createElement("input",{type:"number",value:formBars,onChange:e=>setFormBars(e.target.value),className:"bg-zinc-900 border border-zinc-700 rounded px-2.5 py-1 text-xs outline-none focus:border-purple-600 text-zinc-200"})),/*#__PURE__*/React.createElement("div",{className:"flex flex-col gap-1"},/*#__PURE__*/React.createElement("label",{className:"text-[10px] uppercase font-bold text-zinc-500"},"BPM mặc định"),/*#__PURE__*/React.createElement("input",{type:"number",value:formBpm,onChange:e=>setFormBpm(e.target.value),className:"bg-zinc-900 border border-zinc-700 rounded px-2.5 py-1 text-xs outline-none focus:border-purple-600 text-zinc-200"})),/*#__PURE__*/React.createElement("div",{className:"flex flex-col gap-1"},/*#__PURE__*/React.createElement("label",{className:"text-[10px] uppercase font-bold text-zinc-500"},"Âm giai (Scale) mặc định"),/*#__PURE__*/React.createElement("input",{type:"text",value:formScale,onChange:e=>setFormScale(e.target.value),className:"bg-zinc-900 border border-zinc-700 rounded px-2.5 py-1 text-xs outline-none focus:border-purple-600 text-zinc-200"}))),/*#__PURE__*/React.createElement("div",{className:"flex flex-col gap-1 flex-1 min-h-0"},/*#__PURE__*/React.createElement("label",{className:"text-[10px] uppercase font-bold text-zinc-500"},"System Prompt Template / Luật soạn nhạc"),/*#__PURE__*/React.createElement("textarea",{value:formTemplate,onChange:e=>setFormTemplate(e.target.value),rows:6,className:"flex-1 bg-zinc-900 border border-zinc-700 rounded p-2.5 text-xs outline-none focus:border-purple-600 text-zinc-200 font-mono resize-none"})),/*#__PURE__*/React.createElement("div",{className:"flex items-center justify-end gap-2 shrink-0 pt-2 border-t border-zinc-800"},/*#__PURE__*/React.createElement("button",{type:"button",onClick:()=>setEditingPreset(null),className:"px-3 py-1.5 bg-zinc-800 hover:bg-zinc-700 text-zinc-400 border border-zinc-700 rounded text-xs transition"},"Quay lại"),/*#__PURE__*/React.createElement("button",{type:"submit",className:"px-3 py-1.5 bg-purple-700 hover:bg-purple-600 text-white rounded text-xs font-bold shadow transition"},"Lưu Preset")))),/*#__PURE__*/React.createElement("div",{className:"p-4 border-t border-zinc-800 flex justify-end shrink-0 bg-[#202024]"},/*#__PURE__*/React.createElement("button",{onClick:()=>{setEditingPreset(null);onClose();},className:"px-4 py-1.5 bg-zinc-800 hover:bg-zinc-700 text-zinc-300 border border-zinc-700 rounded text-xs font-bold shadow transition"},"Đóng"))));};const PianoRollTabEditor=({st,zoom,bpm,viewportWidth,onClose,onUpdateNotes,onSaveNotes,setSubTabs,onPlayPause,onStop,isPlaying,playPreviewNote,showToast})=>{const[activeRollTool,setActiveRollTool]=React.useState('select');const[snapVal,setSnapVal]=React.useState('1/16');const[ccMode,setCcMode]=React.useState('velocity');const[rollZoom,setRollZoom]=React.useState(60);// local horizontal zoom factor const canvasRef=React.useRef(null);const ccCanvasRef=React.useRef(null);const gridScrollRef=React.useRef(null);const keybedRef=React.useRef(null);const rulerScrollRef=React.useRef(null);const NoteHeight=18;const PITCH_START=0;// C0 (render all 128 keys) const KeybedPixelHeight=(128-PITCH_START)*NoteHeight;const pixelsPerBeat=rollZoom;const timeSigNum=4;const totalBeats=Math.max((st.duration||4)*timeSigNum,64);// at least 64 beats (16 bars) for scrolling const drawWidth=totalBeats*pixelsPerBeat;const[notes,setNotes]=React.useState(st.notes||[]);const[selectedNoteIds,setSelectedNoteIds]=React.useState([]);const[selectionMarquee,setSelectionMarquee]=React.useState(null);// { startBeat, startPitch, currentBeat, currentPitch } @@ -147,7 +147,9 @@ setSelectedNoteIds([]);setSelectionMarquee({startBeat:beat,startPitch:pitch,curr if(hoveredResizeIdx!==-1){setDraggedNote({mode:'resize',idx:hoveredResizeIdx,originalStart:notes[hoveredResizeIdx].start_beat});return;}if(clickedNoteIdx!==-1){// Click on existing note: drag-move const clickedNote=notes[clickedNoteIdx];if(!selectedNoteIds.includes(clickedNote.id)){if(e.shiftKey){setSelectedNoteIds(prev=>[...prev,clickedNote.id]);}else{setSelectedNoteIds([clickedNote.id]);}}const selectedNotesOffset=notes.filter(n=>selectedNoteIds.includes(n.id)||n.id===clickedNote.id).map(n=>({id:n.id,originalStartBeat:n.start_beat,originalPitch:n.pitch}));setDraggedNote({mode:'move',idx:clickedNoteIdx,startOffsetBeat:beat-clickedNote.start_beat,startOffsetPitch:pitch-clickedNote.pitch,selectedNotesOffset:selectedNotesOffset});}else{// Click on empty space: DRAW a new note starting from click position const start=getSnapBeat(beat,snapVal);const initialDur=getSnapDuration(snapVal);const noteId='note_'+Date.now()+Math.random().toString(36).substr(2,5);const newNote={id:noteId,pitch:pitch,start_beat:start,duration_beats:initialDur,velocity:0.8,pan:0.0};setNotes(prev=>[...prev,newNote]);setSelectedNoteIds([noteId]);setDraggedNote({mode:'draw',idx:-1,startOffsetBeat:start,startOffsetPitch:pitch,drawNoteId:noteId,drawDuration:initialDur});// Play the note with SoundFont -if(window.SonicSF){const ctx=getAudioContext();window.SonicSF.playNote(pitch,0.8,300,ctx.currentTime,undefined,null);}}};const handleGridMouseMove=e=>{const canvas=canvasRef.current;if(!canvas)return;const rect=canvas.getBoundingClientRect();const x=e.clientX-rect.left;const y=e.clientY-rect.top;const beat=x/pixelsPerBeat;const pitch=127-Math.floor(y/NoteHeight);if(selectionMarquee){const marquee={...selectionMarquee,currentBeat:beat,currentPitch:pitch};setSelectionMarquee(marquee);const minBeat=Math.min(marquee.startBeat,marquee.currentBeat);const maxBeat=Math.max(marquee.startBeat,marquee.currentBeat);const minPitch=Math.min(marquee.startPitch,marquee.currentPitch);const maxPitch=Math.max(marquee.startPitch,marquee.currentPitch);const insideIds=notes.filter(n=>n.start_beat>=minBeat&&n.start_beat<=maxBeat&&n.pitch>=minPitch&&n.pitch<=maxPitch).map(n=>n.id);setSelectedNoteIds(insideIds);return;}if(!draggedNote){let foundIdx=-1;for(let i=0;i=n.start_beat){foundIdx=i;break;}}}if(foundIdx!==-1){canvas.style.cursor='ew-resize';setHoveredResizeIdx(foundIdx);}else{canvas.style.cursor=activeRollTool==='eraser'?'pointer':'crosshair';setHoveredResizeIdx(-1);}return;}if(draggedNote.mode==='draw'){const rawDur=beat-draggedNote.startOffsetBeat;const newDur=getSnapBeat(Math.max(0.125,rawDur),snapVal);setNotes(prev=>prev.map(n=>{if(n.id!==draggedNote.drawNoteId)return n;return{...n,duration_beats:newDur};}));return;}if(draggedNote.mode==='resize'){const newDuration=getSnapBeat(Math.max(0.125,beat-draggedNote.originalStart),snapVal);setNotes(prev=>prev.map((n,idx)=>{if(idx!==draggedNote.idx)return n;return{...n,duration_beats:newDuration};}));}else if(draggedNote.mode==='move'){const baseOriginal=draggedNote.selectedNotesOffset.find(o=>o.id===notes[draggedNote.idx].id);const deltaBeat=getSnapBeat(beat-draggedNote.startOffsetBeat,snapVal)-baseOriginal.originalStartBeat;const deltaPitch=Math.round(pitch-draggedNote.startOffsetPitch)-baseOriginal.originalPitch;setNotes(prev=>prev.map(n=>{const offset=draggedNote.selectedNotesOffset.find(o=>o.id===n.id);if(!offset)return n;return{...n,start_beat:getSnapBeat(Math.max(0,offset.originalStartBeat+deltaBeat),snapVal),pitch:Math.max(0,Math.min(127,offset.originalPitch+deltaPitch))};}));}};const handleGridMouseUp=()=>{setDraggedNote(null);setSelectionMarquee(null);};const handleContextMenu=e=>{e.preventDefault();};const handleCCMouseDown=e=>{const canvas=ccCanvasRef.current;if(!canvas)return;const rect=canvas.getBoundingClientRect();const x=e.clientX-rect.left;const y=e.clientY-rect.top;const h=rect.height;const beat=x/pixelsPerBeat;let noteIdx=notes.findIndex(n=>beat>=n.start_beat&&beat<=n.start_beat+n.duration_beats);if(noteIdx===-1){let minDistance=Infinity;notes.forEach((n,idx)=>{const center=n.start_beat+n.duration_beats/2;const dist=Math.abs(center-beat);if(distprev.map((n,idx)=>{if(idx!==noteIdx)return n;if(ccMode==='pan'){return{...n,pan:(val-0.5)*2.0};}else{return{...n,velocity:val};}}));}};const renderKeybed=()=>{const keys=[];for(let pitch=127;pitch>=PITCH_START;pitch--){const isBlack=[1,3,6,8,10].includes(pitch%12);const notesArray=['C','C#','D','D#','E','F','F#','G','G#','A','A#','B'];const octave=Math.floor(pitch/12)-1;const label=`${notesArray[pitch%12]}${octave}`;const showLabel=pitch%12===0;keys.push(/*#__PURE__*/React.createElement("div",{key:pitch,style:{height:`${NoteHeight}px`},className:`w-[60px] shrink-0 border-b border-zinc-800 text-[9px] font-bold flex items-center justify-end pr-2 transition cursor-pointer ${isBlack?'bg-zinc-950 text-slate-500 border-zinc-900 hover:bg-zinc-800':'bg-white text-zinc-800 border-r border-zinc-400 hover:bg-amber-100'}`,onMouseDown:e=>{e.stopPropagation();try{if(window.SonicSF){window.SonicSF.playNote(pitch,100,500,undefined,st.instrumentProgram,null);}}catch(err){console.error('playNote error:',err);}}},showLabel&&label));}return keys;};const handleScroll=e=>{if(keybedRef.current){keybedRef.current.scrollTop=e.currentTarget.scrollTop;}if(rulerScrollRef.current){rulerScrollRef.current.scrollLeft=e.currentTarget.scrollLeft;}};const handleKeybedScroll=e=>{if(gridScrollRef.current){gridScrollRef.current.scrollTop=e.currentTarget.scrollTop;}};const renderBarLabels=()=>{const labels=[];const barsCount=Math.ceil(totalBeats/4);for(let bar=0;bar{e.stopPropagation();const barTime=bar*4*beatSec;setSubTabs(prev=>prev.map(s=>s.id===st.id?{...s,currentTime:barTime}:s));}},`Bar ${bar}`));}return labels;};const beatSec=60.0/(parseInt(bpm)||120);const playHeadX=(st.currentTime||0)/beatSec*pixelsPerBeat;return React.createElement("div",{className:"flex-1 flex overflow-hidden min-h-0 bg-[#1e1e1e] flex-col"},React.createElement("div",{className:"h-10 bg-[#282828] border-b border-zinc-900 flex items-center justify-between px-4 shrink-0 text-slate-200"},/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-4"},/*#__PURE__*/React.createElement("span",{className:"text-xs font-bold text-yellow-500 uppercase flex items-center gap-1.5"},/*#__PURE__*/React.createElement("i",{"data-lucide":"music",className:"w-3.5 h-3.5"}),st.label),/*#__PURE__*/React.createElement("div",{className:"flex bg-zinc-800 p-0.5 rounded border border-zinc-700 text-xs"},['select','pen','eraser'].map(tool=>/*#__PURE__*/React.createElement("button",{key:tool,onClick:()=>setActiveRollTool(tool),className:`px-2.5 py-1 rounded capitalize ${activeRollTool===tool?'bg-yellow-600 text-white font-bold':'text-zinc-400 hover:text-zinc-200'}`},tool))),/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-1 text-xs"},/*#__PURE__*/React.createElement("span",{className:"text-zinc-500 font-semibold"},"Snap:"),/*#__PURE__*/React.createElement("select",{value:snapVal,onChange:e=>setSnapVal(e.target.value),className:"bg-zinc-800 border border-zinc-700 text-zinc-300 rounded px-1.5 py-0.5 outline-none focus:border-yellow-500"},['free','1','1/2','1/4','1/8','1/16','1/32'].map(v=>/*#__PURE__*/React.createElement("option",{key:v,value:v},v)))),/*#__PURE__*/React.createElement("div",{className:"flex bg-zinc-800 p-0.5 rounded border border-zinc-700 text-xs"},['velocity','pan'].map(mode=>/*#__PURE__*/React.createElement("button",{key:mode,onClick:()=>setCcMode(mode),className:`px-2.5 py-1 rounded capitalize ${ccMode===mode?'bg-purple-900/60 text-purple-300 font-bold border border-purple-700':'text-zinc-400 hover:text-zinc-200'}`},mode)))),/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-1"},/*#__PURE__*/React.createElement("button",{onClick:()=>onSaveNotes(st.id,st.trackId,st.target_id,notes),className:"px-2.5 py-1 bg-emerald-600 hover:bg-emerald-500 text-white rounded text-xs flex items-center gap-1 transition font-semibold"},/*#__PURE__*/React.createElement("i",{"data-lucide":"save",className:"w-3 h-3"}),"Lưu"),/*#__PURE__*/React.createElement("button",{onClick:onClose,className:"px-2.5 py-1 bg-zinc-800 hover:bg-zinc-700 text-zinc-300 border border-zinc-700 rounded text-xs flex items-center gap-1 transition"},/*#__PURE__*/React.createElement("i",{"data-lucide":"x",className:"w-3 h-3"}),"Đóng"))),/*#__PURE__*/React.createElement("div",{className:"h-6 bg-[#1a1a1f] border-b border-zinc-900 flex shrink-0"},/*#__PURE__*/React.createElement("div",{className:"w-[60px] bg-[#222222] border-r border-zinc-900 shrink-0"}),/*#__PURE__*/React.createElement("div",{ref:rulerScrollRef,className:"flex-1 overflow-hidden",onMouseDown:e=>{const rect=e.currentTarget.getBoundingClientRect();const x=e.clientX-rect.left+e.currentTarget.scrollLeft;const clickBeat=x/pixelsPerBeat;const clickTime=clickBeat*beatSec;if(clickTime>=0){setSubTabs(prev=>prev.map(s=>s.id===st.id?{...s,currentTime:clickTime}:s));}}},/*#__PURE__*/React.createElement("div",{style:{width:`${drawWidth}px`,height:'100%'},className:"relative h-full font-mono text-[9px] text-zinc-500 font-bold"},renderBarLabels()))),/*#__PURE__*/React.createElement("div",{className:"flex-1 flex overflow-hidden min-h-0 relative"},/*#__PURE__*/React.createElement("div",{ref:keybedRef,onScroll:handleKeybedScroll,className:"w-[60px] overflow-y-auto flex flex-col border-r border-zinc-900 shrink-0",style:{scrollbarWidth:'none',msOverflowStyle:'none'}},renderKeybed()),/*#__PURE__*/React.createElement("div",{ref:gridScrollRef,onScroll:handleScroll,className:"flex-1 overflow-auto bg-[#141414] min-w-0"},/*#__PURE__*/React.createElement("div",{style:{width:`${drawWidth}px`,height:`${(128-PITCH_START)*NoteHeight}px`},className:"relative"},/*#__PURE__*/React.createElement("canvas",{ref:canvasRef,onMouseDown:handleGridMouseDown,onMouseMove:handleGridMouseMove,onMouseUp:handleGridMouseUp,onMouseLeave:handleGridMouseUp,onContextMenu:handleContextMenu,className:"absolute inset-0 cursor-crosshair"})))),/*#__PURE__*/React.createElement("div",{className:"h-20 bg-[#161616] border-t border-zinc-900 flex shrink-0"},/*#__PURE__*/React.createElement("div",{className:"w-[60px] bg-[#222222] border-r border-zinc-900 flex items-center justify-center text-[10px] text-zinc-500 font-mono shrink-0 font-bold"},ccMode.toUpperCase()),/*#__PURE__*/React.createElement("div",{className:"flex-1 overflow-x-hidden min-w-0"},/*#__PURE__*/React.createElement("div",{style:{width:`${drawWidth}px`,height:'100%'},className:"relative"},/*#__PURE__*/React.createElement("canvas",{ref:ccCanvasRef,onMouseDown:handleCCMouseDown,className:"absolute inset-0 cursor-ns-resize"})))));};const serializeProjectToSchema=(projectId,name,bpmVal,tracksList,subTabsList,sessionTabsList)=>{const secondsPerBar=60.0/parseFloat(bpmVal||120)*4;const mainTracks=(tracksList||[]).map(t=>{let trackType="AUDIO";if(t.sections&&t.sections.length>0)trackType="SECTION";else if(t.midiItems&&t.midiItems.length>0)trackType="MIDI";const items=[];if(trackType==="AUDIO"&&t.clips){t.clips.forEach(c=>{const durationSec=c.buffer?c.buffer.duration:4.0;items.push({id:c.id,name:c.name||"Audio Clip",type:"AUDIO_ITEM",start_bar:c.startTime/secondsPerBar,duration_bars:durationSec/(c.speed||1.0)/secondsPerBar,clip_start_offset_bars:0.0,source_data:{audio_file_url:t.serverFileId?`/static/audio/uploads/${t.serverFileId}`:"",sample_rate:c.buffer?c.buffer.sampleRate:44100,channels:c.buffer?c.buffer.numberOfChannels:2,gain:1.0}});});}else if(trackType==="MIDI"&&t.midiItems){t.midiItems.forEach(m=>{items.push({id:m.id,name:m.name||"MIDI Item",type:"MIDI_ITEM",start_bar:m.startTime/secondsPerBar,duration_bars:m.duration?m.duration/secondsPerBar:4.0,clip_start_offset_bars:0.0,source_data:{total_buffer_bars:m.duration?m.duration/secondsPerBar:8.0,notes:(m.notes||[]).map(n=>({id:n.id||'note_'+Math.random().toString(36).substr(2,9),pitch:n.pitch||60,start_beat:n.start_beat||0.0,duration_beats:n.duration_beats||1.0,velocity:n.velocity||0.8,pan:n.pan||0.0}))}});});}else if(trackType==="SECTION"&&t.sections){t.sections.forEach(s=>{items.push({id:s.id,name:s.name||"Section Item",type:"SECTION_ITEM",start_bar:s.start/secondsPerBar,duration_bars:s.duration/secondsPerBar,clip_start_offset_bars:0.0,source_data:{referenced_section_id:s.sectionId||s.id}});});}return{id:t.id,name:t.name,type:trackType,volume_db:t.volumeDb||0.0,pan:t.pan||0.0,mute:t.muted||false,solo:t.solo||false,items:items};});const sectionStore={};(sessionTabsList||[]).forEach(st=>{const secTracks=(st.tracks||[]).map(t=>{let trackType="AUDIO";if(t.sections&&t.sections.length>0)trackType="SECTION";else if(t.midiItems&&t.midiItems.length>0)trackType="MIDI";return{id:t.id,name:t.name,type:trackType,volume_db:t.volumeDb||0.0,pan:t.pan||0.0,mute:t.muted||false,solo:t.solo||false,items:[]};});sectionStore[st.sectionId]={id:st.sectionId,name:st.name,is_root:false,length_bars:st.length_bars||16.0,auto_compute_length:st.auto_compute_length!==undefined?st.auto_compute_length:true,tracks:secTracks};});return{project_id:projectId||'proj_'+Date.now(),metadata:{title:name||"Dự án mới",bpm:parseFloat(bpmVal||120),time_signature_numerator:4,time_signature_denominator:4,sample_rate:44100},main_session:{id:"main",name:"MAIN SESSION",is_root:true,length_bars:16.0,auto_compute_length:true,tracks:mainTracks},section_store:sectionStore};};const deserializeProjectFromSchema=schemaObj=>{const bpmVal=schemaObj.metadata?schemaObj.metadata.bpm:120;const secondsPerBar=60.0/bpmVal*4;const restoredTracks=(schemaObj.main_session.tracks||[]).map(t=>{const clips=[];const sections=[];const midiItems=[];(t.items||[]).forEach(item=>{if(item.type==="AUDIO_ITEM"){const src=item.source_data||{};const audioUrl=src.audio_file_url||"";const serverFileId=audioUrl.split("/").pop()||null;clips.push({id:item.id,name:item.name,startTime:item.start_bar*secondsPerBar,speed:1.0});}else if(item.type==="MIDI_ITEM"){const src=item.source_data||{};midiItems.push({id:item.id,name:item.name,startTime:item.start_bar*secondsPerBar,duration:item.duration_bars*secondsPerBar,notes:src.notes||[]});}else if(item.type==="SECTION_ITEM"){const src=item.source_data||{};sections.push({id:item.id,name:item.name,start:item.start_bar*secondsPerBar,duration:item.duration_bars*secondsPerBar,sectionId:src.referenced_section_id});}});return{id:t.id,name:t.name,volumeDb:t.volume_db||0.0,pan:t.pan||0.0,muted:t.mute||false,solo:t.solo||false,color:t.id==='1'?'#0f766e':'#1d4ed8',markers:[],serverFileId:t.items&&t.items.find(i=>i.type==="AUDIO_ITEM")?.source_data?.audio_file_url?.split("/").pop()||null,clips:clips,sections:sections,midiItems:midiItems};});const restoredSessionTabs=[];const sectionStore=schemaObj.section_store||{};Object.keys(sectionStore).forEach(secId=>{const secContainer=sectionStore[secId];const secTracks=(secContainer.tracks||[]).map(t=>{return{id:t.id,name:t.name,volumeDb:t.volume_db||0.0,pan:t.pan||0.0,muted:t.mute||false,solo:t.solo||false,clips:[],sections:[],midiItems:[]};});restoredSessionTabs.push({id:'session_'+secId,name:secContainer.name,sectionId:secId,tracks:secTracks,length_bars:secContainer.length_bars||16.0,auto_compute_length:secContainer.auto_compute_length});});return{bpm:bpmVal,tracks:restoredTracks,sessionTabs:restoredSessionTabs};};const App=()=>{// ── State Definitions ── +if(window.SonicSF){const ctx=getAudioContext();window.SonicSF.playNote(pitch,0.8,300,ctx.currentTime,undefined,null);}}};const handleGridMouseMove=e=>{const canvas=canvasRef.current;if(!canvas)return;const rect=canvas.getBoundingClientRect();const x=e.clientX-rect.left;const y=e.clientY-rect.top;const beat=x/pixelsPerBeat;const pitch=127-Math.floor(y/NoteHeight);if(selectionMarquee){const marquee={...selectionMarquee,currentBeat:beat,currentPitch:pitch};setSelectionMarquee(marquee);const minBeat=Math.min(marquee.startBeat,marquee.currentBeat);const maxBeat=Math.max(marquee.startBeat,marquee.currentBeat);const minPitch=Math.min(marquee.startPitch,marquee.currentPitch);const maxPitch=Math.max(marquee.startPitch,marquee.currentPitch);const insideIds=notes.filter(n=>n.start_beat>=minBeat&&n.start_beat<=maxBeat&&n.pitch>=minPitch&&n.pitch<=maxPitch).map(n=>n.id);setSelectedNoteIds(insideIds);return;}if(!draggedNote){let foundIdx=-1;for(let i=0;i=n.start_beat){foundIdx=i;break;}}}if(foundIdx!==-1){canvas.style.cursor='ew-resize';setHoveredResizeIdx(foundIdx);}else{canvas.style.cursor=activeRollTool==='eraser'?'pointer':'crosshair';setHoveredResizeIdx(-1);}return;}if(draggedNote.mode==='draw'){const rawDur=beat-draggedNote.startOffsetBeat;const newDur=getSnapBeat(Math.max(0.125,rawDur),snapVal);setNotes(prev=>prev.map(n=>{if(n.id!==draggedNote.drawNoteId)return n;return{...n,duration_beats:newDur};}));return;}if(draggedNote.mode==='resize'){const newDuration=getSnapBeat(Math.max(0.125,beat-draggedNote.originalStart),snapVal);setNotes(prev=>prev.map((n,idx)=>{if(idx!==draggedNote.idx)return n;return{...n,duration_beats:newDuration};}));}else if(draggedNote.mode==='move'){const baseOriginal=draggedNote.selectedNotesOffset.find(o=>o.id===notes[draggedNote.idx].id);const deltaBeat=getSnapBeat(beat-draggedNote.startOffsetBeat,snapVal)-baseOriginal.originalStartBeat;const deltaPitch=Math.round(pitch-draggedNote.startOffsetPitch)-baseOriginal.originalPitch;setNotes(prev=>prev.map(n=>{const offset=draggedNote.selectedNotesOffset.find(o=>o.id===n.id);if(!offset)return n;return{...n,start_beat:getSnapBeat(Math.max(0,offset.originalStartBeat+deltaBeat),snapVal),pitch:Math.max(0,Math.min(127,offset.originalPitch+deltaPitch))};}));}};const handleGridMouseUp=()=>{setDraggedNote(null);setSelectionMarquee(null);};const handleContextMenu=e=>{e.preventDefault();};const handleCCMouseDown=e=>{const canvas=ccCanvasRef.current;if(!canvas)return;const rect=canvas.getBoundingClientRect();const x=e.clientX-rect.left;const y=e.clientY-rect.top;const h=rect.height;const beat=x/pixelsPerBeat;let noteIdx=notes.findIndex(n=>beat>=n.start_beat&&beat<=n.start_beat+n.duration_beats);if(noteIdx===-1){let minDistance=Infinity;notes.forEach((n,idx)=>{const center=n.start_beat+n.duration_beats/2;const dist=Math.abs(center-beat);if(distprev.map((n,idx)=>{if(idx!==noteIdx)return n;if(ccMode==='pan'){return{...n,pan:(val-0.5)*2.0};}else{return{...n,velocity:val};}}));}};const renderKeybed=()=>{const keys=[];for(let pitch=127;pitch>=PITCH_START;pitch--){const isBlack=[1,3,6,8,10].includes(pitch%12);const notesArray=['C','C#','D','D#','E','F','F#','G','G#','A','A#','B'];const octave=Math.floor(pitch/12)-1;const label=`${notesArray[pitch%12]}${octave}`;const showLabel=pitch%12===0;keys.push(/*#__PURE__*/React.createElement("div",{key:pitch,style:{height:`${NoteHeight}px`},className:`w-[60px] shrink-0 border-b border-zinc-800 text-[9px] font-bold flex items-center justify-end pr-2 transition cursor-pointer ${isBlack?'bg-zinc-950 text-slate-500 border-zinc-900 hover:bg-zinc-800':'bg-white text-zinc-800 border-r border-zinc-400 hover:bg-amber-100'}`,onMouseDown:e=>{e.stopPropagation();try{if(window.SonicSF){window.SonicSF.playNote(pitch,100,500,undefined,st.instrumentProgram,null);}}catch(err){console.error('playNote error:',err);}}},showLabel&&label));}return keys;};const handleScroll=e=>{if(keybedRef.current){keybedRef.current.scrollTop=e.currentTarget.scrollTop;}if(rulerScrollRef.current){rulerScrollRef.current.scrollLeft=e.currentTarget.scrollLeft;}};const handleKeybedScroll=e=>{if(gridScrollRef.current){gridScrollRef.current.scrollTop=e.currentTarget.scrollTop;}};const renderBarLabels=()=>{const labels=[];const barsCount=Math.ceil(totalBeats/4);for(let bar=0;bar{e.stopPropagation();const barTime=bar*4*beatSec;setSubTabs(prev=>prev.map(s=>s.id===st.id?{...s,currentTime:barTime}:s));}},`Bar ${bar}`));}return labels;};const beatSec=60.0/(parseInt(bpm)||120);const playHeadX=(st.currentTime||0)/beatSec*pixelsPerBeat;return React.createElement("div",{className:"flex-1 flex overflow-hidden min-h-0 bg-[#1e1e1e] flex-col"},React.createElement("div",{className:"h-10 bg-[#282828] border-b border-zinc-900 flex items-center justify-between px-4 shrink-0 text-slate-200"},/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-4"},/*#__PURE__*/React.createElement("span",{className:"text-xs font-bold text-yellow-500 uppercase flex items-center gap-1.5"},/*#__PURE__*/React.createElement("i",{"data-lucide":"music",className:"w-3.5 h-3.5"}),st.label),/*#__PURE__*/React.createElement("div",{className:"flex bg-zinc-800 p-0.5 rounded border border-zinc-700 text-xs"},['select','pen','eraser'].map(tool=>/*#__PURE__*/React.createElement("button",{key:tool,onClick:()=>setActiveRollTool(tool),className:`px-2.5 py-1 rounded capitalize ${activeRollTool===tool?'bg-yellow-600 text-white font-bold':'text-zinc-400 hover:text-zinc-200'}`},tool))),/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-1 text-xs"},/*#__PURE__*/React.createElement("span",{className:"text-zinc-500 font-semibold"},"Snap:"),/*#__PURE__*/React.createElement("select",{value:snapVal,onChange:e=>setSnapVal(e.target.value),className:"bg-zinc-800 border border-zinc-700 text-zinc-300 rounded px-1.5 py-0.5 outline-none focus:border-yellow-500"},['free','1','1/2','1/4','1/8','1/16','1/32'].map(v=>/*#__PURE__*/React.createElement("option",{key:v,value:v},v)))),/*#__PURE__*/React.createElement("div",{className:"flex bg-zinc-800 p-0.5 rounded border border-zinc-700 text-xs"},['velocity','pan'].map(mode=>/*#__PURE__*/React.createElement("button",{key:mode,onClick:()=>setCcMode(mode),className:`px-2.5 py-1 rounded capitalize ${ccMode===mode?'bg-purple-900/60 text-purple-300 font-bold border border-purple-700':'text-zinc-400 hover:text-zinc-200'}`},mode)))),/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-1"},/*#__PURE__*/React.createElement("button",{onClick:()=>onSaveNotes(st.id,st.trackId,st.target_id,notes),className:"px-2.5 py-1 bg-emerald-600 hover:bg-emerald-500 text-white rounded text-xs flex items-center gap-1 transition font-semibold"},/*#__PURE__*/React.createElement("i",{"data-lucide":"save",className:"w-3 h-3"}),"Lưu"),/*#__PURE__*/React.createElement("button",{onClick:onClose,className:"px-2.5 py-1 bg-zinc-800 hover:bg-zinc-700 text-zinc-300 border border-zinc-700 rounded text-xs flex items-center gap-1 transition"},/*#__PURE__*/React.createElement("i",{"data-lucide":"x",className:"w-3 h-3"}),"Đóng"))),/*#__PURE__*/React.createElement("div",{className:"h-6 bg-[#1a1a1f] border-b border-zinc-900 flex shrink-0"},/*#__PURE__*/React.createElement("div",{className:"w-[60px] bg-[#222222] border-r border-zinc-900 shrink-0"}),/*#__PURE__*/React.createElement("div",{ref:rulerScrollRef,className:"flex-1 overflow-hidden",onMouseDown:e=>{const rect=e.currentTarget.getBoundingClientRect();const x=e.clientX-rect.left+e.currentTarget.scrollLeft;const clickBeat=x/pixelsPerBeat;const clickTime=clickBeat*beatSec;if(clickTime>=0){setSubTabs(prev=>prev.map(s=>s.id===st.id?{...s,currentTime:clickTime}:s));}}},/*#__PURE__*/React.createElement("div",{style:{width:`${drawWidth}px`,height:'100%'},className:"relative h-full font-mono text-[9px] text-zinc-500 font-bold"},renderBarLabels()))),/*#__PURE__*/React.createElement("div",{className:"flex-1 flex overflow-hidden min-h-0 relative"},/*#__PURE__*/React.createElement("div",{ref:keybedRef,onScroll:handleKeybedScroll,className:"w-[60px] overflow-y-auto flex flex-col border-r border-zinc-900 shrink-0",style:{scrollbarWidth:'none',msOverflowStyle:'none'}},renderKeybed()),/*#__PURE__*/React.createElement("div",{ref:gridScrollRef,onScroll:handleScroll,className:"flex-1 overflow-auto bg-[#141414] min-w-0"},/*#__PURE__*/React.createElement("div",{style:{width:`${drawWidth}px`,height:`${(128-PITCH_START)*NoteHeight}px`},className:"relative"},/*#__PURE__*/React.createElement("canvas",{ref:canvasRef,onMouseDown:handleGridMouseDown,onMouseMove:handleGridMouseMove,onMouseUp:handleGridMouseUp,onMouseLeave:handleGridMouseUp,onContextMenu:handleContextMenu,className:"absolute inset-0 cursor-crosshair"})))),/*#__PURE__*/React.createElement("div",{className:"h-20 bg-[#161616] border-t border-zinc-900 flex shrink-0"},/*#__PURE__*/React.createElement("div",{className:"w-[60px] bg-[#222222] border-r border-zinc-900 flex items-center justify-center text-[10px] text-zinc-500 font-mono shrink-0 font-bold"},ccMode.toUpperCase()),/*#__PURE__*/React.createElement("div",{className:"flex-1 overflow-x-hidden min-w-0"},/*#__PURE__*/React.createElement("div",{style:{width:`${drawWidth}px`,height:'100%'},className:"relative"},/*#__PURE__*/React.createElement("canvas",{ref:ccCanvasRef,onMouseDown:handleCCMouseDown,className:"absolute inset-0 cursor-ns-resize"})))));};const serializeTracksList=(tracksList,secondsPerBar)=>{return(tracksList||[]).map(t=>{let trackType="AUDIO";if(t.sections&&t.sections.length>0)trackType="SECTION";else if(t.midiItems&&t.midiItems.length>0)trackType="MIDI";const items=[];if(trackType==="AUDIO"&&t.clips){t.clips.forEach(c=>{const durationSec=c.buffer?c.buffer.duration:4.0;items.push({id:c.id,name:c.name||"Audio Clip",type:"AUDIO_ITEM",start_bar:c.startTime/secondsPerBar,duration_bars:durationSec/(c.speed||1.0)/secondsPerBar,clip_start_offset_bars:0.0,source_data:{audio_file_url:t.serverFileId?`/static/audio/uploads/${t.serverFileId}`:"",sample_rate:c.buffer?c.buffer.sampleRate:44100,channels:c.buffer?c.buffer.numberOfChannels:2,gain:1.0}});});}else if(trackType==="MIDI"&&t.midiItems){t.midiItems.forEach(m=>{items.push({id:m.id,name:m.name||"MIDI Item",type:"MIDI_ITEM",start_bar:m.startTime/secondsPerBar,duration_bars:m.duration?m.duration/secondsPerBar:4.0,clip_start_offset_bars:0.0,source_data:{total_buffer_bars:m.duration?m.duration/secondsPerBar:8.0,notes:(m.notes||[]).map(n=>({id:n.id||'note_'+Math.random().toString(36).substr(2,9),pitch:n.pitch||60,start_beat:n.start_beat||0.0,duration_beats:n.duration_beats||1.0,velocity:n.velocity||0.8,pan:n.pan||0.0}))}});});}else if(trackType==="SECTION"&&t.sections){t.sections.forEach(s=>{items.push({id:s.id,name:s.name||"Section Item",type:"SECTION_ITEM",start_bar:s.start/secondsPerBar,duration_bars:s.duration/secondsPerBar,clip_start_offset_bars:0.0,source_data:{referenced_section_id:s.sectionId||s.id}});});}return{id:t.id,name:t.name,type:trackType,volume_db:t.volumeDb||0.0,pan:t.pan||0.0,mute:t.muted||false,solo:t.solo||false,instrument_id:t.instrumentId||null,instrument_program:t.instrumentProgram!==undefined?t.instrumentProgram:null,instrument_name:t.instrumentName||null,items:items};});};const deserializeTracksList=(schemaTracks,secondsPerBar,sectionStore)=>{return(schemaTracks||[]).map(t=>{const clips=[];const sections=[];const midiItems=[];(t.items||[]).forEach(item=>{if(item.type==="AUDIO_ITEM"){const src=item.source_data||{};clips.push({id:item.id,name:item.name,startTime:item.start_bar*secondsPerBar,speed:1.0});}else if(item.type==="MIDI_ITEM"){const src=item.source_data||{};midiItems.push({id:item.id,name:item.name,startTime:item.start_bar*secondsPerBar,duration:item.duration_bars*secondsPerBar,notes:(src.notes||[]).map(n=>({id:n.id,pitch:n.pitch||60,start_beat:n.start_beat||0.0,duration_beats:n.duration_beats||1.0,velocity:n.velocity||0.8,pan:n.pan||0.0}))});}else if(item.type==="SECTION_ITEM"){const src=item.source_data||{};const secId=src.referenced_section_id;const secContainer=sectionStore?sectionStore[secId]:null;sections.push({id:item.id,name:item.name,start:item.start_bar*secondsPerBar,duration:item.duration_bars*secondsPerBar,sectionId:secId,tracks:secContainer?deserializeTracksList(secContainer.tracks,secondsPerBar,sectionStore):null});}});return{id:t.id,name:t.name,volumeDb:t.volume_db||0.0,pan:t.pan||0.0,muted:t.mute||false,solo:t.solo||false,color:t.id==='1'?'#0f766e':'#1d4ed8',markers:[],serverFileId:t.items&&t.items.find(i=>i.type==="AUDIO_ITEM")?.source_data?.audio_file_url?.split("/").pop()||null,clips:clips,sections:sections,midiItems:midiItems,instrumentId:t.instrument_id||null,instrumentProgram:t.instrument_program!==null?t.instrument_program:undefined,instrumentName:t.instrument_name||null};});};const serializeProjectToSchema=(projectId,name,bpmVal,tracksList,subTabsList,sessionTabsList)=>{const secondsPerBar=60.0/parseFloat(bpmVal||120)*4;const mainTracks=serializeTracksList(tracksList,secondsPerBar);const sectionStore={};// 1. Populate from sessionTabsList (open tabs) +(sessionTabsList||[]).forEach(st=>{sectionStore[st.sectionId]={id:st.sectionId,name:st.name,is_root:false,length_bars:st.length_bars||16.0,auto_compute_length:st.auto_compute_length!==undefined?st.auto_compute_length:true,tracks:serializeTracksList(st.tracks,secondsPerBar),color:st.color||null};});// 2. Also populate from tracksList (closed tabs saved inside Section items) +const scanForSections=tracks=>{(tracks||[]).forEach(t=>{if(t.sections){t.sections.forEach(s=>{const secId=s.sectionId||s.id;if(s.tracks&&!sectionStore[secId]){sectionStore[secId]={id:secId,name:s.name,is_root:false,length_bars:s.tracks[0]?.length_bars||16.0,auto_compute_length:true,tracks:serializeTracksList(s.tracks,secondsPerBar),color:s.color||null};}if(s.tracks){scanForSections(s.tracks);}});}});};scanForSections(tracksList);const subTabs=(subTabsList||[]).map(st=>{return{id:st.id,label:st.label,type:st.type,track_id:st.trackId,target_id:st.target_id,parent_tab_id:st.parent_tab_id,notes:st.notes||[],duration:st.duration||4,instrument_program:st.instrumentProgram,instrument_name:st.instrumentName,current_time:st.currentTime||0,color:st.color||null};});return{project_id:projectId||'proj_'+Date.now(),metadata:{title:name||"Dự án mới",bpm:parseFloat(bpmVal||120),time_signature_numerator:4,time_signature_denominator:4,sample_rate:44100},main_session:{id:"main",name:"MAIN SESSION",is_root:true,length_bars:16.0,auto_compute_length:true,tracks:mainTracks},sub_tabs:subTabs,section_store:sectionStore};};const deserializeProjectFromSchema=schemaObj=>{const bpmVal=schemaObj.metadata?schemaObj.metadata.bpm:120;const secondsPerBar=60.0/bpmVal*4;const sectionStore=schemaObj.section_store||{};const restoredTracks=deserializeTracksList(schemaObj.main_session.tracks,secondsPerBar,sectionStore);const restoredSessionTabs=[];Object.keys(sectionStore).forEach(secId=>{const secContainer=sectionStore[secId];const secTracks=deserializeTracksList(secContainer.tracks,secondsPerBar,sectionStore);restoredSessionTabs.push({id:'session_'+secId,name:secContainer.name,sectionId:secId,tracks:secTracks,length_bars:secContainer.length_bars||16.0,auto_compute_length:secContainer.auto_compute_length!==undefined?secContainer.auto_compute_length:true,color:secContainer.color||null});});const restoredSubTabs=(schemaObj.sub_tabs||[]).map(st=>{return{id:st.id,label:st.label,type:st.type,trackId:st.track_id,target_id:st.target_id,parent_tab_id:st.parent_tab_id,notes:st.notes||[],duration:st.duration||4,instrumentProgram:st.instrument_program,instrumentName:st.instrument_name,currentTime:st.current_time||0,color:st.color||null};});return{bpm:bpmVal,tracks:restoredTracks,sessionTabs:restoredSessionTabs,subTabs:restoredSubTabs};};const App=()=>{// ── State Definitions ── const[tracks,setTracks]=useState([{id:'1',name:'Track 01',buffer:null,startTime:0,volumeDb:0,pan:0,muted:false,solo:false,color:'#0f766e',markers:[],serverFileId:null,clips:[],sections:[],midiItems:[],isArmed:false,monitoringEnabled:true,inputSource:{deviceType:'NONE',deviceId:''}},{id:'2',name:'Track 02',buffer:null,startTime:0,volumeDb:0,pan:0,muted:false,solo:false,color:'#1d4ed8',markers:[],serverFileId:null,clips:[],sections:[],midiItems:[],isArmed:false,monitoringEnabled:true,inputSource:{deviceType:'NONE',deviceId:''}}]);const[appWarningModal,setAppWarningModal]=useState(null);const[bpm,setBpm]=useState(localStorage.getItem('studio_bpm')||'120');const[draggedClip,setDraggedClip]=useState(null);// { trackId, clickOffset, buffer, name, volume, color } const[hoveredTrackId,setHoveredTrackId]=useState(null);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);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);const sfIdParam=track.instrumentId.replace('sf_','');window.SonicAPI.listSoundfontInstruments(sfIdParam).then(data=>setSfPresets(data.presets||[])).catch(()=>setSfPresets([]));}else{// Reset synth state and always reload plugin data @@ -173,12 +175,13 @@ const[undoStack,setUndoStack]=useState([]);const[redoStack,setRedoStack]=useStat buffer:track.buffer,startTime:track.startTime||0,clips:track.clips?track.clips.map(c=>({id:c.id,buffer:c.buffer,startTime:c.startTime,name:c.name})):null};};// ── Tab System (LOOP_EDITOR_2.md §1) ── const[activeTab,setActiveTab]=useState('main');const[subTabSelectedNodeTime,setSubTabSelectedNodeTime]=useState(null);const[subTabNormVal,setSubTabNormVal]=useState(0);const[subTabGainVal,setSubTabGainVal]=useState(100);const[subTabPitchVal,setSubTabPitchVal]=useState(0);const[subTabs,setSubTabs]=useState([]);// [{id, label, trackId, startTime, endTime, buffer, effects, currentTime, selectionStart, selectionEnd, isPlaying}, ...] const[sessionTabs,setSessionTabs]=useState([]);// [{id, name, tracks}, ...] -const activeTracks=useMemo(()=>{const st=sessionTabs.find(s=>s.id===activeTab);return st?st.tracks:tracks;},[activeTab,sessionTabs,tracks]);const activeTracksRef=useRef([]);activeTracksRef.current=activeTracks;const sessionTabsRef=useRef(sessionTabs);sessionTabsRef.current=sessionTabs;const[selectionCleared,setSelectionCleared]=useState(false);// LOOP_EDITOR_2.md §4.2 +const[tabContextMenu,setTabContextMenu]=useState(null);// { x, y, tabId, tabType } +const handleSetTabColor=(tabId,tabType,color)=>{if(tabType==='session'){setSessionTabs(prev=>prev.map(s=>s.id===tabId?{...s,color:color}:s));const tab=sessionTabs.find(s=>s.id===tabId);if(tab){setTracks(prev=>prev.map(t=>{if(!t.sections||t.sections.length===0)return t;return{...t,sections:t.sections.map(s=>{if(s.sectionId===tab.sectionId){return{...s,color:color};}return s;})};}));}}else{setSubTabs(prev=>prev.map(s=>s.id===tabId?{...s,color:color}:s));}};const activeTracks=useMemo(()=>{const st=sessionTabs.find(s=>s.id===activeTab);return st?st.tracks:tracks;},[activeTab,sessionTabs,tracks]);const activeTracksRef=useRef([]);activeTracksRef.current=activeTracks;const sessionTabsRef=useRef(sessionTabs);sessionTabsRef.current=sessionTabs;const[selectionCleared,setSelectionCleared]=useState(false);// LOOP_EDITOR_2.md §4.2 // ── Temp Edit Tab (LOOP_EDITOR_2.md §1.2 Sub Tab) ── const[tempTabActive,setTempTabActive]=useState(false);const[tempTabBuffer,setTempTabBuffer]=useState(null);const[tempTabTrackId,setTempTabTrackId]=useState(null);const[tempTabOrigStart,setTempTabOrigStart]=useState(0);const[tempTabOrigEnd,setTempTabOrigEnd]=useState(0);const tempTabCanvasRef=useRef(null);// Effect parameters for temp tab const[tempTabEffects,setTempTabEffects]=useState({reverse:false,gainDb:0,fadeInMs:0,fadeOutMs:0});// ── Auth / User State ── const[currentUser,setCurrentUser]=useState(null);const[authModalOpen,setAuthModalOpen]=useState(false);const[authMode,setAuthMode]=useState('login');// 'login' | 'register' | 'force_change' -const[isMandatoryLogin,setIsMandatoryLogin]=useState(false);const[profileModalOpen,setProfileModalOpen]=useState(false);const[systemManagerModalOpen,setSystemManagerModalOpen]=useState(false);const[aiConfigModalOpen,setAiConfigModalOpen]=useState(false);const[aiPresetModalOpen,setAiPresetModalOpen]=useState(false);const[pluginManagerModalOpen,setPluginManagerModalOpen]=useState(false);const[pluginsData,setPluginsData]=useState(null);useEffect(()=>{const checkAuthStatus=async()=>{const savedToken=localStorage.getItem('sonic_token');if(!savedToken){setIsMandatoryLogin(true);setAuthMode('login');setAuthModalOpen(true);return;}try{const profile=await window.SonicAPI.getProfile();setCurrentUser(profile);localStorage.setItem('sonic_user',JSON.stringify(profile));if(profile.must_change_password){setIsMandatoryLogin(true);setAuthMode('force_change');setAuthModalOpen(true);}else{setIsMandatoryLogin(false);setAuthModalOpen(false);}}catch(err){const cached=localStorage.getItem('sonic_user');if(cached){try{setCurrentUser(JSON.parse(cached));}catch(_){}setIsMandatoryLogin(false);setAuthModalOpen(false);}else{localStorage.removeItem('sonic_token');localStorage.removeItem('sonic_user');setCurrentUser(null);setIsMandatoryLogin(true);setAuthMode('login');setAuthModalOpen(true);}}};checkAuthStatus();},[]);const loadPendingSfsProject=()=>{const proj=window.__pendingSfsProject;if(!proj)return;try{let restoredTracks=[];let restoredBpm=bpm;let restoredSessionTabs=[];if(proj.main_session){const result=deserializeProjectFromSchema(proj);restoredTracks=result.tracks;restoredBpm=result.bpm;restoredSessionTabs=result.sessionTabs;}else{restoredTracks=(proj.tracks||[]).map(t=>{const{height:_h,...rest}=t;return{...rest,buffer:null,channelInfo:t.channelInfo||null,clips:t.clips||[],serverFileId:t.serverFileId||null};});}if(restoredTracks.length>0){setTracks(restoredTracks);setBpm(restoredBpm.toString());if(restoredSessionTabs.length>0){setSessionTabs(restoredSessionTabs);}showToast(`Đã tải dự án "${proj.metadata?.title||proj.name||'Dự án mới'}" từ liên kết .sfs thành công!`,"success");}}catch(e){showToast("Lỗi tải dự án từ .sfs","error");}finally{window.__pendingSfsProject=null;}};const handleAuthSuccess=user=>{setCurrentUser(user);if(user.must_change_password){setIsMandatoryLogin(true);setAuthMode('force_change');setAuthModalOpen(true);}else{setIsMandatoryLogin(false);setAuthModalOpen(false);loadPendingSfsProject();const loadPrefs=p=>{if(!p)return;if(p.showAIPanel!==undefined)setShowAIPanel(p.showAIPanel);if(p.showExportPanel!==undefined)setShowExportPanel(p.showExportPanel);if(p.showSelectionPanel!==undefined)setShowSelectionPanel(p.showSelectionPanel);if(p.showPythonToolsPanel!==undefined)setShowPythonToolsPanel(p.showPythonToolsPanel);if(p.showMediaExplorer!==undefined)setShowMediaExplorer(p.showMediaExplorer);if(p.showFxRack!==undefined)setShowFxRack(p.showFxRack);if(p.showMidiEvents!==undefined)setShowMidiEvents(p.showMidiEvents);if(p.panelPositions)setPanelPositions(p.panelPositions);if(p.rightSidebarWidth)setRightSidebarWidth(p.rightSidebarWidth);if(p.mediaExplorerHeight)setMediaExplorerHeight(p.mediaExplorerHeight);if(p.selectedProviderId)setSelectedProviderId(p.selectedProviderId);};(async()=>{try{const data=await window.SonicAPI.getPreferences();if(data&&data.preferences)loadPrefs(data.preferences);else{const cached=localStorage.getItem('sonic_preferences');if(cached)loadPrefs(JSON.parse(cached));}}catch(e){const cached=localStorage.getItem('sonic_preferences');if(cached)loadPrefs(JSON.parse(cached));}try{const data=await window.SonicAPI.getAIConfigs();if(data&&data.providers){setAiProviders(data.providers);const active=data.providers.find(p=>p.is_active)||data.providers[0];if(active)setSelectedProviderId(active.id);}}catch(e){}})();}};const handleLogout=()=>{localStorage.removeItem('sonic_token');localStorage.removeItem('sonic_user');setCurrentUser(null);setIsMandatoryLogin(true);setAuthMode('login');setAuthModalOpen(true);};// ── Temp project auto-save (local + server) ── +const[isMandatoryLogin,setIsMandatoryLogin]=useState(false);const[profileModalOpen,setProfileModalOpen]=useState(false);const[systemManagerModalOpen,setSystemManagerModalOpen]=useState(false);const[aiConfigModalOpen,setAiConfigModalOpen]=useState(false);const[aiPresetModalOpen,setAiPresetModalOpen]=useState(false);const[pluginManagerModalOpen,setPluginManagerModalOpen]=useState(false);const[pluginsData,setPluginsData]=useState(null);useEffect(()=>{const checkAuthStatus=async()=>{const savedToken=localStorage.getItem('sonic_token');if(!savedToken){setIsMandatoryLogin(true);setAuthMode('login');setAuthModalOpen(true);return;}try{const profile=await window.SonicAPI.getProfile();setCurrentUser(profile);localStorage.setItem('sonic_user',JSON.stringify(profile));if(profile.must_change_password){setIsMandatoryLogin(true);setAuthMode('force_change');setAuthModalOpen(true);}else{setIsMandatoryLogin(false);setAuthModalOpen(false);}}catch(err){const cached=localStorage.getItem('sonic_user');if(cached){try{setCurrentUser(JSON.parse(cached));}catch(_){}setIsMandatoryLogin(false);setAuthModalOpen(false);}else{localStorage.removeItem('sonic_token');localStorage.removeItem('sonic_user');setCurrentUser(null);setIsMandatoryLogin(true);setAuthMode('login');setAuthModalOpen(true);}}};checkAuthStatus();},[]);const loadPendingSfsProject=()=>{const proj=window.__pendingSfsProject;if(!proj)return;try{let restoredTracks=[];let restoredBpm=bpm;let restoredSessionTabs=[];let restoredSubTabs=[];if(proj.main_session){const result=deserializeProjectFromSchema(proj);restoredTracks=result.tracks;restoredBpm=result.bpm;restoredSessionTabs=result.sessionTabs;restoredSubTabs=result.subTabs;}else{restoredTracks=(proj.tracks||[]).map(t=>{const{height:_h,...rest}=t;return{...rest,buffer:null,channelInfo:t.channelInfo||null,clips:t.clips||[],serverFileId:t.serverFileId||null};});}if(restoredTracks.length>0){setTracks(restoredTracks);setBpm(restoredBpm.toString());if(restoredSessionTabs.length>0){setSessionTabs(restoredSessionTabs);}if(restoredSubTabs.length>0){setSubTabs(restoredSubTabs);}showToast(`Đã tải dự án "${proj.metadata?.title||proj.name||'Dự án mới'}" từ liên kết .sfs thành công!`,"success");}}catch(e){showToast("Lỗi tải dự án từ .sfs","error");}finally{window.__pendingSfsProject=null;}};const handleAuthSuccess=user=>{setCurrentUser(user);if(user.must_change_password){setIsMandatoryLogin(true);setAuthMode('force_change');setAuthModalOpen(true);}else{setIsMandatoryLogin(false);setAuthModalOpen(false);loadPendingSfsProject();const loadPrefs=p=>{if(!p)return;if(p.showAIPanel!==undefined)setShowAIPanel(p.showAIPanel);if(p.showExportPanel!==undefined)setShowExportPanel(p.showExportPanel);if(p.showSelectionPanel!==undefined)setShowSelectionPanel(p.showSelectionPanel);if(p.showPythonToolsPanel!==undefined)setShowPythonToolsPanel(p.showPythonToolsPanel);if(p.showMediaExplorer!==undefined)setShowMediaExplorer(p.showMediaExplorer);if(p.showFxRack!==undefined)setShowFxRack(p.showFxRack);if(p.showMidiEvents!==undefined)setShowMidiEvents(p.showMidiEvents);if(p.panelPositions)setPanelPositions(p.panelPositions);if(p.rightSidebarWidth)setRightSidebarWidth(p.rightSidebarWidth);if(p.mediaExplorerHeight)setMediaExplorerHeight(p.mediaExplorerHeight);if(p.selectedProviderId)setSelectedProviderId(p.selectedProviderId);};(async()=>{try{const data=await window.SonicAPI.getPreferences();if(data&&data.preferences)loadPrefs(data.preferences);else{const cached=localStorage.getItem('sonic_preferences');if(cached)loadPrefs(JSON.parse(cached));}}catch(e){const cached=localStorage.getItem('sonic_preferences');if(cached)loadPrefs(JSON.parse(cached));}try{const data=await window.SonicAPI.getAIConfigs();if(data&&data.providers){setAiProviders(data.providers);const active=data.providers.find(p=>p.is_active)||data.providers[0];if(active)setSelectedProviderId(active.id);}}catch(e){}})();}};const handleLogout=()=>{localStorage.removeItem('sonic_token');localStorage.removeItem('sonic_user');setCurrentUser(null);setIsMandatoryLogin(true);setAuthMode('login');setAuthModalOpen(true);};// ── Temp project auto-save (local + server) ── useEffect(()=>{const serializeSafe=arr=>(arr||[]).map(t=>({id:t.id,name:t.name,startTime:t.startTime,height:t.height,volumeDb:t.volumeDb,pan:t.pan,muted:t.muted,solo:t.solo,color:t.color,markers:t.markers||[],serverFileId:t.serverFileId||null,channelInfo:t.channelInfo?{channels:t.channelInfo.channels,isStereo:t.channelInfo.isStereo,label:t.channelInfo.label}:null}));window.SonicStorage.scheduleTempAutoSave(()=>{return serializeProjectToSchema(currentProjectId||'temp_project',projectName||'Dự án tạm chưa lưu',bpm,tracks,subTabs,sessionTabs);});},[tracks,subTabs,sessionTabs]);// Lucide icons initialization useEffect(()=>{setTimeout(()=>{if(window.lucide){window.lucide.createIcons();}},50);},[activeTool,activeTab]);const timelineWrapperRef=useRef(null);const[timelineWrapperNode,setTimelineWrapperNode]=useState(null);const handleTimelineWrapperRef=useCallback(node=>{timelineWrapperRef.current=node;setTimelineWrapperNode(node);},[]);const tcpContainerRef=useRef(null);const[scrollLeft,setScrollLeft]=useState(0);const handleTimelineScroll=e=>{if(tcpContainerRef.current){tcpContainerRef.current.scrollTop=e.currentTarget.scrollTop;}setScrollLeft(e.currentTarget.scrollLeft);};const handleTCPScroll=e=>{if(timelineWrapperRef.current){timelineWrapperRef.current.scrollTop=e.currentTarget.scrollTop;}};const panelDropZoneRef=useRef(null);const startPanelDrag=(panelId,e)=>{panelDragRef.current={panelId,startX:e.clientX,startY:e.clientY};setPanelDropZone(null);setDragGhostPanel(panelId);setDragGhostPos({x:e.clientX-120,y:e.clientY-20});const onMove=ev=>{if(!panelDragRef.current||!workspaceRef.current)return;const rect=workspaceRef.current.getBoundingClientRect();const x=ev.clientX-rect.left;const y=ev.clientY-rect.top;const w=rect.width;const h=rect.height;const margin=60;let zone=null;if(x10)zone='left';else if(x>w-margin&&x10)zone='top';else if(y>h-margin&&y{if(panelDragRef.current){const pid=panelDragRef.current.panelId;const targetZone=panelDropZoneRef.current;if(targetZone&&targetZone!==panelPositions[pid]){setPanelPositions(prev=>({...prev,[pid]:targetZone}));}panelDragRef.current=null;panelDropZoneRef.current=null;setPanelDropZone(null);setDragGhostPos(null);setDragGhostPanel(null);}document.removeEventListener('mousemove',onMove);document.removeEventListener('mouseup',onUp);};document.addEventListener('mousemove',onMove);document.addEventListener('mouseup',onUp);};// ── Right Sidebar Column Resizer ── const startColResize=e=>{e.preventDefault();const startX=e.clientX;const startWidth=rightSidebarWidth;const onMove=ev=>{const deltaX=startX-ev.clientX;const newWidth=Math.max(200,Math.min(600,startWidth+deltaX));setRightSidebarWidth(newWidth);};const onUp=()=>{document.removeEventListener('mousemove',onMove);document.removeEventListener('mouseup',onUp);};document.addEventListener('mousemove',onMove);document.addEventListener('mouseup',onUp);};// ── Right Sidebar Row Resizer (Media Explorer / AI Panel) ── @@ -191,8 +194,9 @@ if(e.key===' '||e.code==='Space'){e.preventDefault();if(handlePlayPauseRef.curre const curTabId=activeTabRef.current;if(ctrl&&alt&&e.key==='n'){e.preventDefault();handleSubTabNormalize(curTabId);return;}if(e.key==='f'||e.key==='F'){e.preventDefault();handleSubTabFade(curTabId,'in');return;}if(e.key==='g'||e.key==='G'){e.preventDefault();handleSubTabFade(curTabId,'out');return;}if(ctrl&&e.key==='l'){e.preventDefault();handleSubTabLoop(curTabId,4);return;}if(e.key==='v'||e.key==='V'){e.preventDefault();const val=prompt("Nhập Gain điều chỉnh (dB):","0");if(val)handleSubTabGain(curTabId,parseFloat(val)||0);return;}if(ctrl&&e.key==='x'){e.preventDefault();handleSubTabCut(curTabId);return;}if(ctrl&&e.key==='c'){e.preventDefault();handleSubTabCopy(curTabId);return;}if(ctrl&&e.key==='v'){e.preventDefault();handleSubTabPaste(curTabId);return;}if(e.key==='Delete'||e.key==='Backspace'||e.key==='Del'){e.preventDefault();if(subTabSelectedNodeTimeRef.current!==null){const selTime=subTabSelectedNodeTimeRef.current;setSubTabs(prev=>prev.map(s=>{if(s.id!==curTabId)return s;const curNodes=s.graphMode==='pan'?s.panningNodes||[]:s.volumeNodes||[];const updated=curNodes.filter(n=>n.time!==selTime);return{...s,[s.graphMode==='pan'?'panningNodes':'volumeNodes']:updated};}));setSubTabSelectedNodeTime(null);}else{handleSubTabDelete(curTabId);}return;}return;}if(ctrl&&e.key==='z'&&!e.shiftKey){e.preventDefault();handleUndoRef.current();return;}if(ctrl&&(e.key==='y'||e.key==='z'&&e.shiftKey)){e.preventDefault();handleRedoRef.current();return;}if(ctrl&&!alt&&e.key==='o'){e.preventDefault();handleImportSFS();return;}if(ctrl&&!alt&&e.key==='n'){e.preventDefault();setTracks([{id:'1',name:'Track 01',buffer:null,startTime:0,volumeDb:0,pan:0,muted:false,solo:false,color:'#0f766e',markers:[],serverFileId:null},{id:'2',name:'Track 02',buffer:null,startTime:0,volumeDb:0,pan:0,muted:false,solo:false,color:'#1d4ed8',markers:[],serverFileId:null}]);setSelectedTrackId('1');showToast('New project created','info');return;}if(ctrl&&!alt&&e.key==='s'){e.preventDefault();handleExportSFS();return;}if(ctrl&&alt&&e.key==='s'||ctrl&&e.shiftKey&&e.key==='s'){e.preventDefault();handleExportSFS();return;}if(ctrl&&!alt&&e.key==='i'){e.preventDefault();addNewTrack();return;}if(ctrl&&alt&&e.key==='i'){e.preventDefault();showToast('Import audio','info');return;}if(ctrl&&!alt&&e.key==='e'){e.preventDefault();openTempTab();return;}if(ctrl&&!alt&&e.key==='m'){e.preventDefault();handleMergeTracks();return;}if(ctrl&&!alt&&e.key==='c'){e.preventDefault();handleCopyTrack();return;}if(ctrl&&!alt&&e.key==='x'){e.preventDefault();handleCutTrack();return;}if(ctrl&&!alt&&e.key==='v'){e.preventDefault();handlePasteTrack();return;}if(e.key==='Delete'||e.key==='Backspace'||e.key==='Del'){const selClip=selectedClipIdRef.current;if(selClip){e.preventDefault();const{trackId,clipId}=selClip;setTracks(prev=>{const track=prev.find(t=>t.id===trackId);if(!track)return prev;const beforeSnap=captureTrackSnapshotRef.current?captureTrackSnapshotRef.current(trackId):null;const updatedClips=(track.clips||[]).filter(c=>c.id!==clipId);const updatedTracks=prev.map(t=>{if(t.id===trackId){return{...t,clips:updatedClips,buffer:updatedClips.length>0?updatedClips[0].buffer:null,startTime:updatedClips.length>0?updatedClips[0].startTime:0,name:updatedClips.length>0?updatedClips[0].name:`Track ${t.id}`};}return t;});setTimeout(()=>{const afterSnap=captureTrackSnapshotRef.current?captureTrackSnapshotRef.current(trackId):null;pushAction('DELETE_CLIP',trackId,beforeSnap,afterSnap);},50);return updatedTracks;});setSelectedClipId(null);showToast('Đã xóa clip.','info');return;}else{e.preventDefault();handleDeleteTrack();return;}}if(ctrl&&!alt&&e.key==='s'){e.preventDefault();handleSaveProject();return;}if(!ctrl&&!alt&&e.key==='s'){e.preventDefault();handleSplitTrack(selectedTrackId);return;}};window.addEventListener('keydown',handler);return()=>window.removeEventListener('keydown',handler);},[]);// ── Temp Tab: draw isolated waveform ── useEffect(()=>{if(!tempTabActive||!tempTabBuffer||!tempTabCanvasRef.current)return;const canvas=tempTabCanvasRef.current;const ctx=canvas.getContext('2d');const dpr=window.devicePixelRatio||1;const rect=canvas.getBoundingClientRect();canvas.width=rect.width*dpr;canvas.height=rect.height*dpr;ctx.scale(dpr,dpr);const w=rect.width;const h=rect.height;ctx.fillStyle='#181818';ctx.fillRect(0,0,w,h);const data=tempTabBuffer.getChannelData(0);const sr=tempTabBuffer.sampleRate;const totalSamples=data.length;if(totalSamples===0)return;ctx.strokeStyle='#6ee7b7';ctx.lineWidth=1;for(let px=0;pxmaxVal)maxVal=abs;}const mid=h/2;const peakHeight=maxVal*(h*0.4);ctx.beginPath();ctx.moveTo(px,mid-peakHeight);ctx.lineTo(px,mid+peakHeight);ctx.stroke();}},[tempTabActive,tempTabBuffer]);// ── Sub Tab: open as new tab instead of modal (LOOP_EDITOR_2.md §1) ── const openTempTab=()=>{const useLocal=selectionMode==='local'&&localSelectionTrackId;const trackId=useLocal?localSelectionTrackId:selectedTrackId;const t=tracks.find(x=>x.id===trackId);if(!t||!t.buffer){showToast('Vui lòng chọn track có dữ liệu âm thanh.','warning');return;}if(selLeft===null||selRight===null||selRight<=selLeft){showToast('Vui lòng chọn một khoảng thời gian trên sóng âm.','warning');return;}// Check if a subtab for this track+range already exists -const existing=subTabs.find(s=>s.trackId===trackId&&s.startTime===selLeft&&s.endTime===selRight);if(existing){setActiveTab(existing.id);showToast(`Sub Tab already open.`,'info');return;}const sr=t.buffer.sampleRate;const trackStart=t.startTime||0;const relSelLeft=Math.max(0,selLeft-trackStart);const relSelRight=Math.max(0,selRight-trackStart);const startSample=Math.max(0,Math.floor(relSelLeft*sr));const endSample=Math.min(t.buffer.length,Math.floor(relSelRight*sr));const len=endSample-startSample;if(len<100){showToast('Khoảng chọn quá ngắn.','warning');return;}const ctx=getAudioContext();const numChannels=t.buffer.numberOfChannels||1;const subBuffer=ctx.createBuffer(numChannels,len,sr);for(let c=0;c[...prev,{id:tabId,label:tabLabel,trackId:trackId,startTime:selLeft,endTime:selRight,buffer:subBuffer,channelInfo:subChannelInfo,effects:{reverse:false,gainDb:0,fadeInMs:0,fadeOutMs:0,normalizeDb:0,pitch:0,speedStretch:100},currentTime:0,selectionStart:null,selectionEnd:null,isPlaying:false,speed:1.0,volumeNodes:[],panningNodes:[],fadeInLen:0,fadeOutLen:0,graphMode:null,isLooping:false,loopCount:0}]);setActiveTab(tabId);};const handleEditClipInSubTab=(trackId,clipId)=>{const track=activeTracks.find(t=>t.id===trackId);if(!track)return;const clips=track.clips&&track.clips.length>0?track.clips:track.buffer?[{id:'default_'+track.id,buffer:track.buffer,startTime:track.startTime||0,name:track.name}]:[];const clip=clips.find(c=>c.id===clipId||clipId==='default'&&c.id==='default_'+trackId);if(!clip||!clip.buffer)return;const resolvedClipId=clip.id==='default'?'default_'+trackId:clip.id;const existing=subTabs.find(s=>s.clipId===resolvedClipId&&s.trackId===trackId);if(existing){setActiveTab(existing.id);showToast(`Sub Tab for "${clip.name}" already open.`,'info');return;}const sr=clip.buffer.sampleRate;const len=clip.buffer.length;const numChannels=clip.buffer.numberOfChannels||1;const ctx=getAudioContext();const subBuffer=ctx.createBuffer(numChannels,len,sr);for(let c=0;c[...prev,{id:tabId,label:tabLabel,trackId:trackId,clipId:resolvedClipId,startTime:clip.startTime,endTime:clip.startTime+clip.buffer.duration,buffer:subBuffer,channelInfo:subChannelInfo,effects:{reverse:false,gainDb:0,fadeInMs:0,fadeOutMs:0,normalizeDb:0,pitch:0,speedStretch:100},currentTime:0,selectionStart:null,selectionEnd:null,isPlaying:false,speed:1.0,volumeNodes:[],panningNodes:[],fadeInLen:0,fadeOutLen:0,graphMode:null,isLooping:false,loopCount:0}]);setActiveTab(tabId);};const handleEditMidiInTab=(trackId,midiItemId)=>{const track=activeTracks.find(t=>t.id===trackId);if(!track)return;const midiItem=(track.midiItems||[]).find(m=>m.id===midiItemId);if(!midiItem)return;const existing=subTabs.find(s=>s.type==='PIANO_ROLL'&&s.target_id===midiItemId);if(existing){setActiveTab(existing.id);showToast('Piano Roll cho nốt MIDI đã được mở.','info');return;}const tabId='midi_'+Date.now();const tabLabel=`Piano Roll: ${midiItem.name||'MIDI'}`;const ctx=getAudioContext();const silentBuffer=ctx.createBuffer(1,128,ctx.sampleRate);const newTab={id:tabId,label:tabLabel,type:'PIANO_ROLL',trackId:trackId,target_id:midiItemId,parent_tab_id:activeTab==='main'?null:activeTab,notes:midiItem.notes||[],duration:midiItem.duration||4,buffer:silentBuffer,instrumentProgram:track.instrumentProgram,instrumentName:track.instrumentName,currentTime:0,isPlaying:false,selectionStart:null,selectionEnd:null,viewport_start_bar:0.0,viewport_bar_width:8.0,scroll_y_pitch:60,snap_resolution:snapValue||"1/16",note_selection:[]};setSubTabs(prev=>[...prev,newTab]);setActiveTab(tabId);};const handleUpdateMidiNotes=(tabId,notes)=>{setSubTabs(prev=>prev.map(s=>s.id===tabId?{...s,notes:notes}:s));};const handleSaveMidiNotes=(tabId,trackId,midiItemId,updatedNotes)=>{const isSectionTrack=sessionTabs.some(s=>s.tracks.some(t=>t.id===trackId));if(isSectionTrack){setSessionTabs(prev=>prev.map(s=>{if(!s.tracks.some(t=>t.id===trackId))return s;return{...s,tracks:s.tracks.map(t=>{if(t.id!==trackId)return t;return{...t,midiItems:(t.midiItems||[]).map(m=>{if(m.id!==midiItemId)return m;return{...m,notes:updatedNotes};})};})};}));}else{setTracks(prev=>prev.map(t=>{if(t.id!==trackId)return t;return{...t,midiItems:(t.midiItems||[]).map(m=>{if(m.id!==midiItemId)return m;return{...m,notes:updatedNotes};})};}));}showToast('Đã lưu các chỉnh sửa nốt MIDI vào item cha!','success');};const handleSaveSectionTab=tabId=>{const tab=sessionTabs.find(s=>s.id===tabId);if(!tab)return;const bpmVal=parseInt(bpm)||120;const secondsPerBeat=60.0/bpmVal;const secondsPerBar=secondsPerBeat*4;const durationSec=(tab.length_bars||16.0)*secondsPerBar;setTracks(prev=>prev.map(t=>{if(!t.sections||t.sections.length===0)return t;return{...t,sections:t.sections.map(s=>{if(s.sectionId!==tab.sectionId)return s;return{...s,name:tab.name,duration:durationSec};})};}));showToast(`Đã lưu nội dung Section "${tab.name}" vào Main Session!`,'success');};// ── Double-click/Edit Section: open Main Session in new tab ── -const handleEditSectionInTab=(trackId,sectionId)=>{const track=tracks.find(t=>t.id===trackId);if(!track)return;const section=(track.sections||[]).find(s=>s.id===sectionId);if(!section)return;const existing=sessionTabs.find(s=>s.sectionId===sectionId);if(existing){setActiveTab(existing.id);showToast(`Tab "${section.name}" already open.`,'info');return;}const tabId='session_'+Date.now();const tabName=section.name||'Section';const clonedTracks=tracks.map(t=>({...t,clips:[],sections:[],midiItems:[],markers:[],isArmed:false,monitoringEnabled:true}));setSessionTabs(prev=>[...prev,{id:tabId,name:tabName,sectionId:sectionId,tracks:clonedTracks}]);setActiveTab(tabId);};// ── Sub Tab: apply effects & merge back (LOOP_EDITOR_2.md §1.2) ── +const existing=subTabs.find(s=>s.trackId===trackId&&s.startTime===selLeft&&s.endTime===selRight);if(existing){setActiveTab(existing.id);showToast(`Sub Tab already open.`,'info');return;}const sr=t.buffer.sampleRate;const trackStart=t.startTime||0;const relSelLeft=Math.max(0,selLeft-trackStart);const relSelRight=Math.max(0,selRight-trackStart);const startSample=Math.max(0,Math.floor(relSelLeft*sr));const endSample=Math.min(t.buffer.length,Math.floor(relSelRight*sr));const len=endSample-startSample;if(len<100){showToast('Khoảng chọn quá ngắn.','warning');return;}const ctx=getAudioContext();const numChannels=t.buffer.numberOfChannels||1;const subBuffer=ctx.createBuffer(numChannels,len,sr);for(let c=0;c[...prev,{id:tabId,label:tabLabel,trackId:trackId,startTime:selLeft,endTime:selRight,buffer:subBuffer,channelInfo:subChannelInfo,effects:{reverse:false,gainDb:0,fadeInMs:0,fadeOutMs:0,normalizeDb:0,pitch:0,speedStretch:100},currentTime:0,selectionStart:null,selectionEnd:null,isPlaying:false,speed:1.0,volumeNodes:[],panningNodes:[],fadeInLen:0,fadeOutLen:0,graphMode:null,isLooping:false,loopCount:0}]);setActiveTab(tabId);};const handleEditClipInSubTab=(trackId,clipId)=>{const track=activeTracks.find(t=>t.id===trackId);if(!track)return;const clips=track.clips&&track.clips.length>0?track.clips:track.buffer?[{id:'default_'+track.id,buffer:track.buffer,startTime:track.startTime||0,name:track.name}]:[];const clip=clips.find(c=>c.id===clipId||clipId==='default'&&c.id==='default_'+trackId);if(!clip||!clip.buffer)return;const resolvedClipId=clip.id==='default'?'default_'+trackId:clip.id;const existing=subTabs.find(s=>s.clipId===resolvedClipId&&s.trackId===trackId);if(existing){setActiveTab(existing.id);showToast(`Sub Tab for "${clip.name}" already open.`,'info');return;}const sr=clip.buffer.sampleRate;const len=clip.buffer.length;const numChannels=clip.buffer.numberOfChannels||1;const ctx=getAudioContext();const subBuffer=ctx.createBuffer(numChannels,len,sr);for(let c=0;c[...prev,{id:tabId,label:tabLabel,trackId:trackId,clipId:resolvedClipId,startTime:clip.startTime,endTime:clip.startTime+clip.buffer.duration,buffer:subBuffer,channelInfo:subChannelInfo,effects:{reverse:false,gainDb:0,fadeInMs:0,fadeOutMs:0,normalizeDb:0,pitch:0,speedStretch:100},currentTime:0,selectionStart:null,selectionEnd:null,isPlaying:false,speed:1.0,volumeNodes:[],panningNodes:[],fadeInLen:0,fadeOutLen:0,graphMode:null,isLooping:false,loopCount:0}]);setActiveTab(tabId);};const handleEditMidiInTab=(trackId,midiItemId)=>{const track=activeTracks.find(t=>t.id===trackId);if(!track)return;const midiItem=(track.midiItems||[]).find(m=>m.id===midiItemId);if(!midiItem)return;const existing=subTabs.find(s=>s.type==='PIANO_ROLL'&&s.target_id===midiItemId);if(existing){setActiveTab(existing.id);showToast('Piano Roll cho nốt MIDI đã được mở.','info');return;}const tabId='midi_'+Date.now();const tabLabel=`Piano Roll: ${midiItem.name||'MIDI'}`;const ctx=getAudioContext();const silentBuffer=ctx.createBuffer(1,128,ctx.sampleRate);const newTab={id:tabId,label:tabLabel,type:'PIANO_ROLL',trackId:trackId,target_id:midiItemId,parent_tab_id:activeTab==='main'?null:activeTab,notes:midiItem.notes||[],duration:midiItem.duration||4,buffer:silentBuffer,instrumentProgram:track.instrumentProgram,instrumentName:track.instrumentName,currentTime:0,isPlaying:false,selectionStart:null,selectionEnd:null,viewport_start_bar:0.0,viewport_bar_width:8.0,scroll_y_pitch:60,snap_resolution:snapValue||"1/16",note_selection:[]};setSubTabs(prev=>[...prev,newTab]);setActiveTab(tabId);};const handleUpdateMidiNotes=(tabId,notes)=>{setSubTabs(prev=>prev.map(s=>s.id===tabId?{...s,notes:notes,isDirty:true}:s));};const handleSaveMidiNotes=(tabId,trackId,midiItemId,updatedNotes)=>{const pianoRollTab=subTabs.find(s=>s.id===tabId);const parentTabId=pianoRollTab?pianoRollTab.parent_tab_id:null;if(parentTabId&&parentTabId.startsWith('session_')){setSessionTabs(prev=>prev.map(s=>{if(s.id!==parentTabId)return s;return{...s,tracks:s.tracks.map(t=>{if(t.id!==trackId)return t;return{...t,midiItems:(t.midiItems||[]).map(m=>{if(m.id!==midiItemId)return m;return{...m,notes:updatedNotes};})};})};}));}else{setTracks(prev=>prev.map(t=>{if(t.id!==trackId)return t;return{...t,midiItems:(t.midiItems||[]).map(m=>{if(m.id!==midiItemId)return m;return{...m,notes:updatedNotes};})};}));}setSubTabs(prev=>prev.map(s=>s.id===tabId?{...s,isDirty:false}:s));showToast('Đã lưu các chỉnh sửa nốt MIDI vào item cha!','success');};const handleSaveSectionTab=tabId=>{const tab=sessionTabs.find(s=>s.id===tabId);if(!tab)return;const bpmVal=parseInt(bpm)||120;const secondsPerBeat=60.0/bpmVal;const secondsPerBar=secondsPerBeat*4;const durationSec=(tab.length_bars||16.0)*secondsPerBar;setTracks(prev=>prev.map(t=>{if(!t.sections||t.sections.length===0)return t;return{...t,sections:t.sections.map(s=>{if(s.sectionId!==tab.sectionId)return s;return{...s,name:tab.name,duration:durationSec,tracks:tab.tracks};})};}));// Clear isDirty flag +setSessionTabs(prev=>prev.map(s=>s.id===tabId?{...s,isDirty:false}:s));showToast(`Đã lưu nội dung Section "${tab.name}" vào Main Session!`,'success');};// ── Double-click/Edit Section: open Main Session in new tab ── +const handleEditSectionInTab=(trackId,sectionId)=>{const track=tracks.find(t=>t.id===trackId);if(!track)return;const section=(track.sections||[]).find(s=>s.id===sectionId);if(!section)return;const existing=sessionTabs.find(s=>s.sectionId===sectionId);if(existing){setActiveTab(existing.id);showToast(`Tab "${section.name}" already open.`,'info');return;}const tabId='session_'+Date.now();const tabName=section.name||'Section';const clonedTracks=section.tracks?section.tracks:tracks.map(t=>({...t,clips:[],sections:[],midiItems:[],markers:[],isArmed:false,monitoringEnabled:true,instrumentId:null,instrumentProgram:undefined,instrumentName:null}));setSessionTabs(prev=>[...prev,{id:tabId,name:tabName,sectionId:sectionId,tracks:clonedTracks,color:section.color||null}]);setActiveTab(tabId);};// ── Sub Tab: apply effects & merge back (LOOP_EDITOR_2.md §1.2) ── const applySubTab=tabId=>{const subTab=subTabs.find(s=>s.id===tabId);if(!subTab||!subTab.buffer)return;const track=activeTracks.find(t=>t.id===subTab.trackId);if(!track||!track.buffer)return;const beforeSnap=captureTrackSnapshot(subTab.trackId);// Clone buffer and apply effects const ctx=getAudioContext();const sr=subTab.buffer.sampleRate;const eff=subTab.buffer.getChannelData(0);let resultBuffer=ctx.createBuffer(1,eff.length,sr);let resultData=resultBuffer.getChannelData(0);resultData.set(eff);// Apply effects inline const fx=subTab.effects||{};const applyResample=(data,ratio)=>{const newLen=Math.round(data.length*ratio);const out=new Float32Array(newLen);for(let i=0;i0){const fadeSamples=Math.min(resultData.length,Math.floor(fx.fadeInMs/1000*sr));for(let i=0;i{const subTab=subTabs.find(s=>s.id===tabId);if(!subTab||!subTab.buffer)return;const ctx=getAudioContext();const sr=subTab.buffer.sampleRate;const eff=subTab.buffer.getChannelData(0);// Clone buffer let resultBuffer=ctx.createBuffer(1,eff.length,sr);let resultData=resultBuffer.getChannelData(0);resultData.set(eff);// Calculate selection start/end in unstretched sample indices const speed=subTab.speed||1.0;const hasSelection=subTab.selectionStart!==null&&subTab.selectionEnd!==null&&subTab.selectionStart!==subTab.selectionEnd;const tStart=hasSelection?Math.min(subTab.selectionStart,subTab.selectionEnd)*speed:0;const tEnd=hasSelection?Math.max(subTab.selectionStart,subTab.selectionEnd)*speed:subTab.buffer.duration;const startSample=Math.max(0,Math.min(eff.length,Math.floor(tStart*sr)));const endSample=Math.max(0,Math.min(eff.length,Math.floor(tEnd*sr)));if(effectType==='normalize'){let maxVal=0;for(let i=startSample;imaxVal)maxVal=abs;}if(maxVal>0){const targetAmp=Math.pow(10,value/20);const scale=targetAmp/maxVal;for(let i=startSample;i{const newLen=Math.round(data.length*r);const out=new Float32Array(newLen);for(let i=0;iprev.map(s=>{if(s.id!==tabId)return s;return{...s,buffer:resultBuffer,selectionStart:null,selectionEnd:null};}));showToast(`Đã áp dụng ${effectType==='normalize'?'Normalize':effectType==='gain'?'Gain':'Pitch'} cho ${hasSelection?'vùng chọn':'toàn bộ clip'}.`,'success');};const exportSubTabBuffer=async tabId=>{const subTab=subTabs.find(s=>s.id===tabId);if(!subTab||!subTab.buffer)return;try{const buffer=subTab.buffer;const sr=buffer.sampleRate;const monoData=buffer.getChannelData(0);const bufferLength=monoData.length;const bitDepth=16;const bytesPerSample=2;const headerSize=44;const fileSizeBytes=headerSize+bufferLength*bytesPerSample;const fileBuffer=new ArrayBuffer(fileSizeBytes);const view=new DataView(fileBuffer);const writeString=(offset,string)=>{for(let i=0;i{setSubTabs(prev=>prev.filter(s=>s.id!==tabId));if(activeTab===tabId)setActiveTab('main');};const closeSessionTab=tabId=>{const targetTab=sessionTabs.find(s=>s.id===tabId);if(!targetTab)return;const activeChildTabs=subTabs.filter(s=>s.parent_tab_id===tabId);if(activeChildTabs.length>0){setAppWarningModal({title:"Không thể đóng Tab Section",message:`Vui lòng đóng các Tab con biên tập trước khi đóng Tab Section này:\n\n`+activeChildTabs.map(t=>`• ${t.label||t.name}`).join("\n"),isAlert:true});return;}setSessionTabs(prev=>prev.filter(s=>s.id!==tabId));if(activeTab===tabId)setActiveTab('main');};const updateSubTabEffects=(tabId,effects)=>{setSubTabs(prev=>prev.map(s=>s.id===tabId?{...s,effects:{...s.effects,...effects}}:s));};const handleSubTabNormalizeWithValue=(tabId,normalizeDb)=>{updateSubTabEffects(tabId,{normalizeDb});};const handleSubTabGainWithValue=(tabId,gainDb)=>{updateSubTabEffects(tabId,{gainDb});};const handleSubTabPitch=(tabId,pitch)=>{updateSubTabEffects(tabId,{pitch});};const handleSubTabStretch=(tabId,speedStretch)=>{updateSubTabEffects(tabId,{speedStretch});};const handleSubTabFadeState=(tabId,type)=>{const st=subTabs.find(s=>s.id===tabId);if(!st)return;const fx=st.effects||{};if(type==='in'){updateSubTabEffects(tabId,{fadeInMs:(fx.fadeInMs||0)+10});}else if(type==='out'){updateSubTabEffects(tabId,{fadeOutMs:(fx.fadeOutMs||0)+10});}};// ── Context Menu Handlers ── +setSubTabs(prev=>prev.map(s=>{if(s.id!==tabId)return s;return{...s,buffer:resultBuffer,selectionStart:null,selectionEnd:null};}));showToast(`Đã áp dụng ${effectType==='normalize'?'Normalize':effectType==='gain'?'Gain':'Pitch'} cho ${hasSelection?'vùng chọn':'toàn bộ clip'}.`,'success');};const exportSubTabBuffer=async tabId=>{const subTab=subTabs.find(s=>s.id===tabId);if(!subTab||!subTab.buffer)return;try{const buffer=subTab.buffer;const sr=buffer.sampleRate;const monoData=buffer.getChannelData(0);const bufferLength=monoData.length;const bitDepth=16;const bytesPerSample=2;const headerSize=44;const fileSizeBytes=headerSize+bufferLength*bytesPerSample;const fileBuffer=new ArrayBuffer(fileSizeBytes);const view=new DataView(fileBuffer);const writeString=(offset,string)=>{for(let i=0;i{const tab=subTabs.find(s=>s.id===tabId);if(!tab)return;const performClose=()=>{setSubTabs(prev=>prev.filter(s=>s.id!==tabId));if(activeTab===tabId)setActiveTab('main');};if(tab.isDirty){setAppWarningModal({title:"Đóng Tab?",message:`Bạn chưa lưu các thay đổi của tab "${tab.label||'MIDI'}". Bạn có chắc chắn muốn đóng tab này và mất hết thay đổi?`,isAlert:false,onConfirm:performClose});}else{performClose();}};const closeSessionTab=tabId=>{const targetTab=sessionTabs.find(s=>s.id===tabId);if(!targetTab)return;const activeChildTabs=subTabs.filter(s=>s.parent_tab_id===tabId);if(activeChildTabs.length>0){setAppWarningModal({title:"Không thể đóng Tab Section",message:`Vui lòng đóng các Tab con biên tập trước khi đóng Tab Section này:\n\n`+activeChildTabs.map(t=>`• ${t.label||t.name}`).join("\n"),isAlert:true});return;}const performClose=()=>{setSessionTabs(prev=>prev.filter(s=>s.id!==tabId));if(activeTab===tabId)setActiveTab('main');};if(targetTab.isDirty){setAppWarningModal({title:"Đóng Tab Section?",message:`Bạn chưa lưu các thay đổi của Section "${targetTab.name}". Bạn có chắc chắn muốn đóng tab này và mất hết thay đổi?`,isAlert:false,onConfirm:performClose});}else{performClose();}};const updateSubTabEffects=(tabId,effects)=>{setSubTabs(prev=>prev.map(s=>s.id===tabId?{...s,effects:{...s.effects,...effects}}:s));};const handleSubTabNormalizeWithValue=(tabId,normalizeDb)=>{updateSubTabEffects(tabId,{normalizeDb});};const handleSubTabGainWithValue=(tabId,gainDb)=>{updateSubTabEffects(tabId,{gainDb});};const handleSubTabPitch=(tabId,pitch)=>{updateSubTabEffects(tabId,{pitch});};const handleSubTabStretch=(tabId,speedStretch)=>{updateSubTabEffects(tabId,{speedStretch});};const handleSubTabFadeState=(tabId,type)=>{const st=subTabs.find(s=>s.id===tabId);if(!st)return;const fx=st.effects||{};if(type==='in'){updateSubTabEffects(tabId,{fadeInMs:(fx.fadeInMs||0)+10});}else if(type==='out'){updateSubTabEffects(tabId,{fadeOutMs:(fx.fadeOutMs||0)+10});}};// ── Context Menu Handlers ── const handleContextMenu=(e,trackId,clickTime,sectionId)=>{e.preventDefault();e.stopPropagation();setContextMenu({x:e.clientX,y:e.clientY,trackId,time:clickTime||currentTime,sectionId:sectionId||null});};const closeContextMenu=()=>setContextMenu(null);const contextMenuEditSection=()=>{if(contextMenu.sectionId){handleEditSectionInTab(contextMenu.trackId,contextMenu.sectionId);}closeContextMenu();};// Close context menu on any click outside useEffect(()=>{const handler=()=>{if(contextMenu)closeContextMenu();};if(contextMenu){window.addEventListener('click',handler);return()=>window.removeEventListener('click',handler);}},[contextMenu]);const contextMenuEdit=()=>{const track=activeTracks.find(t=>t.id===contextMenu.trackId);if(track)setSelectedTrackId(contextMenu.trackId);closeContextMenu();if(!track)return;const secondsPerBar=60.0/(parseInt(bpm)||120)*4;// Check MIDI items first const midiItems=track.midiItems||[];const clickedMidi=midiItems.find(m=>contextMenu.time>=m.startTime&&contextMenu.time{const tid=selectedTrackId;const sessionTab=sessionTabs.find(s=>s.id===activeTab);if(sessionTab){updateActiveTracks(prev=>prev.filter(t=>t.id!==tid));setSelectedTrackId(activeTracks.filter(t=>t.id!==tid)[0]?.id||'1');}else{const curTracks=tracks;const track=curTracks.find(t=>t.id===tid);if(track&&track.sections&&track.sections.length>0){showToast('Không thể xoá track chứa Section item.','warning');return;}setTracks(p=>p.filter(t=>t.id!==tid));setSelectedTrackId(curTracks.filter(t=>t.id!==tid)[0]?.id||'1');}showToast('Deleted track.','info');};// ── Server Health Check ── const[viewportWidth,setViewportWidth]=useState(1200);useEffect(()=>{if(!timelineWrapperNode)return;const observer=new ResizeObserver(entries=>{for(let entry of entries){setViewportWidth(entry.contentRect.width);}});observer.observe(timelineWrapperNode);return()=>observer.disconnect();},[timelineWrapperNode]);useEffect(()=>{fetch(API_BASE_URL).then(r=>{if(r.ok)setServerStatus('connected');else setServerStatus('error');}).catch(()=>setServerStatus('offline'));},[]);// ── Computed Values ── const maxDuration=useMemo(()=>{let max=10;tracks.forEach(t=>{const clips=t.clips&&t.clips.length>0?t.clips:t.buffer?[{id:'default',buffer:t.buffer,startTime:t.startTime||0,name:t.name,speed:t.speed||1.0}]:[];clips.forEach(c=>{if(c.buffer){const cStart=c.startTime||0;const cDur=c.buffer.duration/(c.speed||1.0);max=Math.max(max,cStart+cDur);}});(t.midiItems||[]).forEach(m=>{max=Math.max(max,m.startTime+(m.duration||4));});});if(recordingState==='RECORDING'||recordingState==='COUNT_IN'){max=Math.max(max,currentTime+60);}return max;},[tracks,recordingState,currentTime]);const maxDurationRef=useRef(maxDuration);maxDurationRef.current=maxDuration;const minZoom=useMemo(()=>{return viewportWidth/maxDuration;},[viewportWidth,maxDuration]);const timelineWidth=useMemo(()=>{return Math.max(zoom*maxDuration,viewportWidth);},[zoom,maxDuration,viewportWidth]);useEffect(()=>{if(zoomcurrentTime*zoom,[currentTime,zoom]);const selLeft=useMemo(()=>{if(selectionMode==='local'&&localSelectionStart!==null&&localSelectionEnd!==null){return Math.min(localSelectionStart,localSelectionEnd);}if(selectionStart===null||selectionEnd===null)return null;return Math.min(selectionStart,selectionEnd);},[selectionStart,selectionEnd,selectionMode,localSelectionStart,localSelectionEnd]);const selRight=useMemo(()=>{if(selectionMode==='local'&&localSelectionStart!==null&&localSelectionEnd!==null){return Math.max(localSelectionStart,localSelectionEnd);}if(selectionStart===null||selectionEnd===null)return null;return Math.max(selectionStart,selectionEnd);},[selectionStart,selectionEnd,selectionMode,localSelectionStart,localSelectionEnd]);const dspSelectionStats=useMemo(()=>{const track=tracks.find(t=>t.id===selectedTrackId);if(!track)return null;const numChannels=track.buffer?track.buffer.numberOfChannels||1:0;if(selLeft===null||selRight===null||selRight<=selLeft||!track.buffer){return{trackName:track.name,channels:numChannels,timeRange:'Chưa chọn vùng',peakVolume:'N/A'};}const buffer=track.buffer;const sampleRate=buffer.sampleRate;const startSample=Math.max(0,Math.min(buffer.length-1,Math.floor(selLeft*sampleRate)));const endSample=Math.max(0,Math.min(buffer.length,Math.floor(selRight*sampleRate)));let maxVal=0;for(let c=0;cmaxVal)maxVal=val;}}let peakDb='N/A';if(maxVal>0){const db=20*Math.log10(maxVal);peakDb=db.toFixed(2)+' dB';}else{peakDb='-∞ dB';}return{trackName:track.name,channels:numChannels,timeRange:`${selLeft.toFixed(2)}s - ${selRight.toFixed(2)}s (${(selRight-selLeft).toFixed(2)}s)`,peakVolume:peakDb};},[tracks,selectedTrackId,selLeft,selRight]);// ── Toast helper ── -const showToast=(text,type='info',actionText=null,onActionClick=null)=>{if(toastTimeoutRef.current)clearTimeout(toastTimeoutRef.current);setToastMessage({text,type,actionText,onActionClick});toastTimeoutRef.current=setTimeout(()=>setToastMessage(null),actionText?8000:3500);};// ── Server-side upload ── +const showToast=(text,type='info',actionText=null,onActionClick=null)=>{if(toastTimeoutRef.current)clearTimeout(toastTimeoutRef.current);setToastMessage({text,type,actionText,onActionClick});toastTimeoutRef.current=setTimeout(()=>setToastMessage(null),actionText?8000:3500);};window.showToast=showToast;// ── Server-side upload ── const uploadToServer=async(file,trackId)=>{const formData=new FormData();formData.append('file',file);try{const resp=await fetch(`${API_AUDIO}/upload`,{method:'POST',body:formData});if(!resp.ok)throw new Error(`Upload failed: ${resp.status}`);const data=await resp.json();serverFileIdMap[trackId]=data.file_id;return data;}catch(err){console.warn('Server upload failed, using client-side only:',err.message);return null;}};// ── Server-side waveform loading ── const loadServerWaveform=async fileId=>{try{const resp=await fetch(`${API_AUDIO}/waveform/${fileId}?num_peaks=800`);if(!resp.ok)return null;return await resp.json();}catch{return null;}};// ── Check Celery task result ── const pollTaskResult=async(taskId,maxPoll=10)=>{for(let i=0;isetTimeout(r,1500));try{const resp=await fetch(`${API_TASKS}/${taskId}`);if(!resp.ok)continue;const data=await resp.json();if(data.status==='SUCCESS')return data.result;if(data.status==='FAILURE')throw new Error(data.error||'Task failed');}catch(err){throw err;}}throw new Error('Task polling timeout');};// ── Wheel Zoom ── useEffect(()=>{const timeline=timelineWrapperRef.current;if(!timeline)return;const handleWheel=e=>{if(e.ctrlKey||e.metaKey){e.preventDefault();const zoomFactor=e.deltaY>0?0.9:1.1;setZoom(prevZoom=>{let newZoom=prevZoom*zoomFactor;if(newZoom50000)newZoom=50000;requestAnimationFrame(()=>{const centerTime=currentTimeRef.current;const newCenterX=centerTime*newZoom;const viewportWidth=timeline.clientWidth;timeline.scrollLeft=newCenterX-viewportWidth/2;});return newZoom;});}else if(e.shiftKey){e.preventDefault();timeline.scrollLeft+=e.deltaY;}};timeline.addEventListener('wheel',handleWheel,{passive:false});return()=>timeline.removeEventListener('wheel',handleWheel);},[minZoom]);// ── Update Playhead ── -const startSubTabPlayback=(st,offsetWallTime)=>{const context=getAudioContext();const speed=st.speed||1.0;const offsetBuffer=offsetWallTime*speed;const eff=st.buffer.getChannelData(0);const edBuffer=context.createBuffer(1,eff.length,st.buffer.sampleRate);const edData=edBuffer.getChannelData(0);edData.set(eff);const fx=st.effects||{};if(fx.reverse){const reversed=new Float32Array(edData);for(let i=0;i0){const sr=edBuffer.sampleRate;const fadeSamples=Math.min(edData.length,Math.floor(fx.fadeInMs/1000*sr));for(let i=0;i0){const sr=edBuffer.sampleRate;const fadeSamples=Math.min(edData.length,Math.floor(fx.fadeOutMs/1000*sr));for(let i=edData.length-fadeSamples;i{const context=getAudioContext();if(!st.buffer){st.buffer=context.createBuffer(1,128,context.sampleRate);}const speed=st.speed||1.0;const offsetBuffer=offsetWallTime*speed;const eff=st.buffer.getChannelData(0);const edBuffer=context.createBuffer(1,eff.length,st.buffer.sampleRate);const edData=edBuffer.getChannelData(0);edData.set(eff);const fx=st.effects||{};if(fx.reverse){const reversed=new Float32Array(edData);for(let i=0;i0){const sr=edBuffer.sampleRate;const fadeSamples=Math.min(edData.length,Math.floor(fx.fadeInMs/1000*sr));for(let i=0;i0){const sr=edBuffer.sampleRate;const fadeSamples=Math.min(edData.length,Math.floor(fx.fadeOutMs/1000*sr));for(let i=edData.length-fadeSamples;i0){volumeGainNode.gain.cancelScheduledValues(context.currentTime);volNodes.forEach((n,i)=>{const t=context.currentTime+n.time/speed;const linearGain=Math.pow(10,n.db/20);if(i===0)volumeGainNode.gain.setValueAtTime(linearGain,t);else volumeGainNode.gain.linearRampToValueAtTime(linearGain,t);});}// Schedule panning automation nodes @@ -247,18 +251,24 @@ let combinedNotes=[];const armedTracks=activeTracksRef.current.filter(t=>t.isArm if(st.selectionStart!==null&&st.selectionEnd!==null&&st.selectionStart!==st.selectionEnd){const start=Math.min(st.selectionStart,st.selectionEnd);const end=Math.max(st.selectionStart,st.selectionEnd);if(bufferPos>=end){stopAllPlayback();setSubTabs(prev=>prev.map(s=>s.id===activeTabRef.current?{...s,currentTime:wallTime,isPlaying:true}:s));startSubTabPlayback(st,wallTime);animationFrameIdRef.current=requestAnimationFrame(updatePlayhead);return;}}const effectiveDuration=st.type==='PIANO_ROLL'?(()=>{const notes=st.notes||[];const bpmVal=parseInt(bpmRef?.current||bpm)||120;const beatSec=60.0/bpmVal;let maxEnd=0;notes.forEach(n=>{const end=(n.start_beat||0)+(n.duration_beats||1);if(end>maxEnd)maxEnd=end;});return maxEnd*beatSec+1.0;})():st.buffer.duration;if(bufferPos>=effectiveDuration){stopAllPlayback();if(isLoopingSelection){setSubTabs(prev=>prev.map(s=>s.id===activeTabRef.current?{...s,currentTime:0,isPlaying:true}:s));startSubTabPlayback(st,0);animationFrameIdRef.current=requestAnimationFrame(updatePlayhead);}else{setSubTabs(prev=>prev.map(s=>s.id===activeTabRef.current?{...s,currentTime:0,isPlaying:false}:s));}return;}setSubTabs(prev=>prev.map(s=>s.id===activeTabRef.current?{...s,currentTime:wallTime}:s));animationFrameIdRef.current=requestAnimationFrame(updatePlayhead);return;}if(!isPlaying)return;const context=getAudioContext();const elapsed=context.currentTime-startAudioTimeRef.current;const updatedTime=startOffsetTimeRef.current+elapsed;// Selection Loop - LOOP_MAKER.md + LOOP_EDITOR_2.md §4.2 // If selection cleared by user, play linearly (don't loop) if(!selectionCleared&&isLoopingSelection&&selLeft!==null&&selRight!==null){if(selRight>selLeft&&updatedTime>=selRight){if(soloedTrackId!==null||selectionMode==='local'){stopAllPlayback();startOffsetTimeRef.current=selLeft;startAudioTimeRef.current=context.currentTime;const soloTid=soloedTrackId!==null?soloedTrackId:localSelectionTrackId;startLocalTrackPlayback(soloTid,selLeft);setCurrentTime(selLeft);setIsPlaying(true);}else{stopAllPlayback();startOffsetTimeRef.current=selLeft;startAudioTimeRef.current=context.currentTime;startTrackPlayback(selLeft);setCurrentTime(selLeft);setIsPlaying(true);}animationFrameIdRef.current=requestAnimationFrame(updatePlayhead);return;}}if(updatedTime>=maxDurationRef.current){if(recordingStateRef.current==='RECORDING'){setCurrentTime(updatedTime);animationFrameIdRef.current=requestAnimationFrame(updatePlayhead);return;}if(isLoopingSelection){stopAllPlayback();startOffsetTimeRef.current=0;startAudioTimeRef.current=context.currentTime;startTrackPlayback(0);setCurrentTime(0);setIsPlaying(true);animationFrameIdRef.current=requestAnimationFrame(updatePlayhead);return;}stopAllPlayback();setCurrentTime(0);return;}setCurrentTime(updatedTime);animationFrameIdRef.current=requestAnimationFrame(updatePlayhead);};useEffect(()=>{if(isPlaying||subTabs.some(s=>s.isPlaying)){animationFrameIdRef.current=requestAnimationFrame(updatePlayhead);}else{cancelAnimationFrame(animationFrameIdRef.current);}return()=>cancelAnimationFrame(animationFrameIdRef.current);},[isPlaying,subTabs,isLoopingSelection,selLeft,selRight,selectionMode,localSelectionTrackId,selectionCleared,activeTab]);// ── Playback ── -const getOrCreateTrackNode=(track,context)=>{if(!track)return null;let node=activeTrackNodesRef.current[track.id];if(!node){const gainNode=context.createGain();const volDb=track.volumeDb??0;const volLinear=volDb<=-50?0:Math.pow(10,volDb/20);gainNode.gain.setValueAtTime(volLinear,context.currentTime);const pannerNode=context.createStereoPanner();pannerNode.pan.setValueAtTime((track.pan??0)/100,context.currentTime);pannerNode.connect(context.destination);gainNode.connect(pannerNode);node={gainNode,pannerNode};activeTrackNodesRef.current[track.id]=node;}return node.gainNode;};const playMidiPreviewNote=(pitch,velocity=0.8,durationMs=500)=>{if(!window.SonicSF)return;const context=getAudioContext();const tab=subTabs.find(s=>s.id===activeTabRef.current);const trackId=tab?tab.trackId:selectedTrackId;const track=activeTracks.find(t=>t.id===trackId);const destNode=getOrCreateTrackNode(track,context);const program=track?track.instrumentProgram:undefined;window.SonicSF.playNote(pitch,velocity,durationMs,context.currentTime,program,destNode);};const startTrackPlayback=offsetTime=>{const context=getAudioContext();const hasSolo=tracks.some(t=>t.solo)||soloedTrackId!==null;tracks.forEach(track=>{const isPlayable=hasSolo?track.id===soloedTrackId||track.solo:!track.muted;if(!isPlayable)return;const gainNode=getOrCreateTrackNode(track,context);const pannerNode=activeTrackNodesRef.current[track.id].pannerNode;const clips=track.clips&&track.clips.length>0?track.clips:track.buffer?[{id:'default',buffer:track.buffer,startTime:track.startTime||0,name:track.name,speed:track.speed||1.0}]:[];clips.forEach(clip=>{if(!clip.buffer)return;const source=context.createBufferSource();source.buffer=clip.buffer;source.playbackRate.value=clip.speed||1.0;source.connect(gainNode);const clipStart=clip.startTime||0;const clipDuration=clip.buffer.duration/(clip.speed||1.0);const clipEnd=clipStart+clipDuration;if(offsetTime{if(!track)return null;let node=activeTrackNodesRef.current[track.id];if(!node){const gainNode=context.createGain();const volDb=track.volumeDb??0;const volLinear=volDb<=-50?0:Math.pow(10,volDb/20);gainNode.gain.setValueAtTime(volLinear,context.currentTime);const pannerNode=context.createStereoPanner();pannerNode.pan.setValueAtTime((track.pan??0)/100,context.currentTime);pannerNode.connect(context.destination);gainNode.connect(pannerNode);node={gainNode,pannerNode};activeTrackNodesRef.current[track.id]=node;}return node.gainNode;};const playMidiPreviewNote=(pitch,velocity=0.8,durationMs=500)=>{if(!window.SonicSF)return;const context=getAudioContext();const tab=subTabs.find(s=>s.id===activeTabRef.current);const trackId=tab?tab.trackId:selectedTrackId;const track=activeTracks.find(t=>t.id===trackId);const destNode=getOrCreateTrackNode(track,context);const program=track?track.instrumentProgram:undefined;window.SonicSF.playNote(pitch,velocity,durationMs,context.currentTime,program,destNode);};const startTrackPlayback=offsetTime=>{const context=getAudioContext();const hasSolo=activeTracks.some(t=>t.solo)||soloedTrackId!==null;activeTracks.forEach(track=>{const isPlayable=hasSolo?track.id===soloedTrackId||track.solo:!track.muted;if(!isPlayable)return;const gainNode=getOrCreateTrackNode(track,context);const pannerNode=activeTrackNodesRef.current[track.id].pannerNode;const clips=track.clips&&track.clips.length>0?track.clips:track.buffer?[{id:'default',buffer:track.buffer,startTime:track.startTime||0,name:track.name,speed:track.speed||1.0}]:[];clips.forEach(clip=>{if(!clip.buffer)return;const source=context.createBufferSource();source.buffer=clip.buffer;source.playbackRate.value=clip.speed||1.0;source.connect(gainNode);const clipStart=clip.startTime||0;const clipDuration=clip.buffer.duration/(clip.speed||1.0);const clipEnd=clipStart+clipDuration;if(offsetTime0)&&window.SonicSF){const bpmVal=parseInt(bpm)||120;const secondsPerBeat=60.0/bpmVal;midiItems.forEach(item=>{const notes=item.notes||[];notes.forEach(note=>{// note start/duration is in beats (for MIDI items) const noteStartSec=item.startTime+(note.start_beat||0)*secondsPerBeat;const noteDurSec=(note.duration_beats||1)*secondsPerBeat;const noteEndSec=noteStartSec+noteDurSec;if(offsetTime0){const bpmVal=parseInt(bpm)||120;const secondsPerBeat=60.0/bpmVal;const secondsPerBar=secondsPerBeat*4;sections.forEach(sec=>{const secStart=sec.start||0;const secEnd=secStart+(sec.duration||0);const secTracks=sec.tracks||[];const hasSubSolo=secTracks.some(st=>st.solo);secTracks.forEach(subTrack=>{const isSubPlayable=hasSubSolo?subTrack.solo:!subTrack.muted;if(!isSubPlayable)return;// 1. Play clips in subTrack +const subClips=subTrack.clips||[];subClips.forEach(clip=>{if(!clip.buffer)return;const clipStartLocal=clip.startTime||0;const clipDurLocal=clip.buffer.duration/(clip.speed||1.0);const clipStartMain=secStart+clipStartLocal;const clipEndMain=clipStartMain+clipDurLocal;// Only play within the bounds of [secStart, secEnd] +const activeStart=Math.max(clipStartMain,secStart);const activeEnd=Math.min(clipEndMain,secEnd);if(activeStart0){subMidiItems.forEach(item=>{const notes=item.notes||[];notes.forEach(note=>{const noteStartSecLocal=item.startTime+(note.start_beat||0)*secondsPerBeat;const noteDurSec=(note.duration_beats||1)*secondsPerBeat;const noteStartMain=secStart+noteStartSecLocal;const noteEndMain=noteStartMain+noteDurSec;// Only play if the note starts inside the Section item bounds and hasn't finished yet +if(noteStartMain>=secStart&¬eStartMain{const context=getAudioContext();const track=tracks.find(t=>t.id===trackId);if(!track)return;const clips=track.clips&&track.clips.length>0?track.clips:track.buffer?[{id:'default',buffer:track.buffer,startTime:track.startTime||0,name:track.name,speed:track.speed||1.0}]:[];// Get or create persistent gain & panner for real-time control const gainNode=getOrCreateTrackNode(track,context);const pannerNode=activeTrackNodesRef.current[track.id].pannerNode;clips.forEach(clip=>{if(!clip.buffer)return;const source=context.createBufferSource();source.buffer=clip.buffer;source.playbackRate.value=clip.speed||1.0;source.connect(gainNode);gainNode.connect(pannerNode);const clipStart=clip.startTime||0;const clipDuration=clip.buffer.duration/(clip.speed||1.0);const clipEnd=clipStart+clipDuration;if(offsetTime0)&&window.SonicSF){const bpmVal=parseInt(bpm)||120;const secondsPerBeat=60.0/bpmVal;midiItems.forEach(item=>{const notes=item.notes||[];notes.forEach(note=>{const noteStartSec=item.startTime+(note.start_beat||0)*secondsPerBeat;const noteDurSec=(note.duration_beats||1)*secondsPerBeat;const noteEndSec=noteStartSec+noteDurSec;if(offsetTime{if(activeTab!=='main'){// Sub-tab playback transport +const midiItems=track.midiItems||[];if((track.type==='MIDI'||midiItems.length>0)&&window.SonicSF){const bpmVal=parseInt(bpm)||120;const secondsPerBeat=60.0/bpmVal;midiItems.forEach(item=>{const notes=item.notes||[];notes.forEach(note=>{const noteStartSec=item.startTime+(note.start_beat||0)*secondsPerBeat;const noteDurSec=(note.duration_beats||1)*secondsPerBeat;const noteEndSec=noteStartSec+noteDurSec;if(offsetTime{if(activeTab!=='main'&&!activeTab.startsWith('session_')){// Sub-tab playback transport const st=subTabs.find(s=>s.id===activeTab);if(!st||!st.buffer&&st.type!=='PIANO_ROLL')return;if(st.isPlaying){stopAllPlayback();setSubTabs(prev=>prev.map(s=>s.id===activeTab?{...s,isPlaying:false}:s));}else{stopAllPlayback();const startOffset=st.currentTime||0;if(st.type==='PIANO_ROLL'){// Piano Roll: play MIDI notes via SonicSF const context=getAudioContext();const notes=st.notes||[];const bpmVal=parseInt(bpm)||120;const secondsPerBeat=60.0/bpmVal;const startWallTime=context.currentTime;// Find the track to get instrument settings -const track=activeTracks.find(t=>t.id===st.trackId);const destNode=getOrCreateTrackNode(track,context);const instrumentProgram=track?track.instrumentProgram:undefined;const instrumentName=track?track.instrumentName:undefined;notes.forEach(note=>{const noteOnBeat=note.start_beat||0;const noteDurBeat=note.duration_beats||1;const noteStartSec=noteOnBeat*secondsPerBeat;const noteDurSec=noteDurBeat*secondsPerBeat;if(noteStartSec+noteDurSec>startOffset){const effectiveStart=Math.max(0,noteStartSec-startOffset);const effectiveDur=noteDurSec-Math.max(0,startOffset-noteStartSec);const scheduledTime=startWallTime+effectiveStart;const durMs=effectiveDur*1000;if(window.SonicSF){window.SonicSF.playNote(note.pitch||60,note.velocity||0.8,durMs,scheduledTime,instrumentProgram,destNode);}}});}else{startSubTabPlayback(st,startOffset);}setSubTabs(prev=>prev.map(s=>s.id===activeTab?{...s,isPlaying:true,currentTime:startOffset}:s));}return;}const context=getAudioContext();if(isPlaying){stopAllPlayback();}else{startOffsetTimeRef.current=currentTime;startAudioTimeRef.current=context.currentTime;startTrackPlayback(currentTime);setIsPlaying(true);}};handlePlayPauseRef.current=handlePlayPause;const handlePause=()=>{if(isPlaying||subTabs.some(s=>s.isPlaying))stopAllPlayback();};const stopAllPlayback=()=>{activeSourcesRef.current.forEach(src=>{try{src.stop();}catch(e){}});activeSourcesRef.current=[];activeTrackNodesRef.current={};if(window.SonicSF){window.SonicSF.stopAll();}setIsPlaying(false);setSubTabs(prev=>prev.map(s=>({...s,isPlaying:false})));};const handleStop=()=>{if(recordingStateRef.current==='RECORDING'||recordingStateRef.current==='COUNT_IN'){stopRecordingTake();return;}stopAllPlayback();if(activeTab!=='main'){setSubTabs(prev=>prev.map(s=>s.id===activeTab?{...s,currentTime:0}:s));}else{setCurrentTime(0);}};const drawVuMeter=(canvas,db)=>{if(!canvas)return;const ctx=canvas.getContext('2d');if(!ctx)return;const w=canvas.width;const h=canvas.height;ctx.clearRect(0,0,w,h);const minDb=-60;const maxDb=0;const frac=Math.max(0,Math.min(1,(db-minDb)/(maxDb-minDb)));ctx.fillStyle='#18181b';ctx.fillRect(0,0,w,h);const grad=ctx.createLinearGradient(0,0,w,0);grad.addColorStop(0,'#10b981');grad.addColorStop(0.7,'#eab308');grad.addColorStop(0.95,'#ef4444');ctx.fillStyle=grad;ctx.fillRect(0,0,w*frac,h);if(db>=-0.5){ctx.fillStyle='#ff0000';ctx.fillRect(w-6,0,6,h);}};const handleRecordClick=async()=>{if(recordingState==='RECORDING'||recordingState==='COUNT_IN'){await stopRecordingTake();return;}const armed=activeTracks.filter(t=>t.isArmed&&t.inputSource?.deviceType&&t.inputSource.deviceType!=='NONE');if(armed.length===0){showToast('Vui lòng Arm (R) ít nhất một track và chọn cổng Input để ghi âm.','warning');return;}setRecordingState('COUNT_IN');const secondsPerBeat=60.0/(parseInt(bpm)||120);const countInDuration=secondsPerBeat*4;const audioCtx=getAudioContext();const now=audioCtx.currentTime;showToast('Metronome Count-in: 4... 3... 2... 1...','info');for(let i=0;i<4;i++){playMetronomeClick(now+i*secondsPerBeat,i===0);}setTimeout(()=>{startRecordingTake(armed);},countInDuration*1000);};const startRecordingTake=async armedTracks=>{const context=getAudioContext();if(context.state==='suspended'){await context.resume();}setRecordingState('RECORDING');setRecTempMidiNotes([]);setRecTempAudioBuffer(null);const startTimelineTime=currentTime;setRecStartTimelineTime(startTimelineTime);recordingStartTimeRef.current=startTimelineTime;const secondsPerBeat=60.0/(parseInt(bpm)||120);const startBeat=startTimelineTime/secondsPerBeat;nextMetronomeBeatRef.current=Math.ceil(startBeat);activeMIDIRecordersRef.current={};activeAudioRecordersRef.current={};recordingPCMDataRef.current={};startOffsetTimeRef.current=startTimelineTime;startAudioTimeRef.current=context.currentTime;startTrackPlayback(startTimelineTime);setIsPlaying(true);let midiRecList=[];for(let track of armedTracks){if(track.inputSource.deviceType==='MIDI_KEYBOARD'){const midiRec=new ClientMIDIRecorder(context,parseInt(bpm)||120,4);const tempMidiItemId='midi_rec_'+Date.now()+'_'+track.id;midiRec.tempMidiItemId=tempMidiItemId;updateActiveTracks(prev=>prev.map(t=>{if(t.id!==track.id)return t;return{...t,midiItems:[...(t.midiItems||[]),{id:tempMidiItemId,name:'Recording...',startTime:startTimelineTime,duration:4*(secondsPerBeat*4),notes:[]}]};}));midiRec.onNoteOn=(pitch,currentBeat)=>{const canvas=trackVuRefs.current[track.id];if(canvas){drawVuMeter(canvas,0);setTimeout(()=>drawVuMeter(canvas,-60),100);}const elapsedBeats=Math.max(0,currentBeat);const elapsedSec=elapsedBeats*secondsPerBeat;const durationSec=Math.max(4*(secondsPerBeat*4),elapsedSec);const allNotes=[...midiRec.recordedNotes,...Array.from(midiRec.activeNotes.values()).map(n=>({...n,duration_beats:currentBeat-n.start_beat}))];setRecTempMidiNotes(allNotes);setCanvasRedrawCount(n=>n+1);if(midiRec.tempMidiItemId){updateActiveTracks(prev=>prev.map(t=>{if(t.id!==track.id)return t;return{...t,midiItems:t.midiItems.map(m=>m.id===midiRec.tempMidiItemId?{...m,notes:allNotes,duration:durationSec}:m)};}));}};midiRec.onNoteOff=()=>{const currentBeat=(context.currentTime-midiRec.recStartAudioTime-midiRec.latencyCompSec)/(60.0/midiRec.bpm);const elapsedBeats=Math.max(0,currentBeat);const elapsedSec=elapsedBeats*secondsPerBeat;const durationSec=Math.max(4*(secondsPerBeat*4),elapsedSec);const activeNotesArray=Array.from(midiRec.activeNotes.values()).map(n=>({...n,duration_beats:currentBeat-n.start_beat}));const allNotes=[...midiRec.recordedNotes,...activeNotesArray];setRecTempMidiNotes(allNotes);setCanvasRedrawCount(n=>n+1);if(midiRec.tempMidiItemId){updateActiveTracks(prev=>prev.map(t=>{if(t.id!==track.id)return t;return{...t,midiItems:t.midiItems.map(m=>m.id===midiRec.tempMidiItemId?{...m,notes:allNotes,duration:durationSec}:m)};}));}};midiRec.start(startTimelineTime/(secondsPerBeat*4),track.inputSource.deviceId);activeMIDIRecordersRef.current[track.id]=midiRec;midiRecList.push({trackId:track.id,midiRec});}else if(track.inputSource.deviceType==='MICROPHONE'){const audioRec=new ClientAudioRecorder(context);try{await audioRec.initializeInput(track.inputSource.deviceId);recordingPCMDataRef.current[track.id]=[];audioRec.onLevelUpdate=db=>{const canvas=trackVuRefs.current[track.id];if(canvas){drawVuMeter(canvas,db);}};audioRec.onPCMChunk=chunk=>{if(recordingPCMDataRef.current[track.id]){const currentData=recordingPCMDataRef.current[track.id];const newData=new Float32Array(currentData.length+chunk.length);newData.set(currentData);newData.set(chunk,currentData.length);recordingPCMDataRef.current[track.id]=newData;}};const monitorGain=track.monitoringEnabled?context.destination:null;await audioRec.start(monitorGain,track.monitoringEnabled);activeAudioRecordersRef.current[track.id]=audioRec;}catch(err){console.error('Failed to initialize microphone:',err);showToast('Không khởi động được micro: '+err.message,'warning');}}}recordingSyncRef.current=setInterval(()=>{const secondsPerBeatInt=60.0/(parseInt(bpm)||120);for(let{trackId,midiRec}of midiRecList){if(!midiRec.isRecording||!midiRec.tempMidiItemId)continue;const currentTimeSec=Math.max(0,getAudioContext().currentTime-midiRec.recStartAudioTime-midiRec.latencyCompSec);const currentBeat=currentTimeSec/secondsPerBeatInt;const allNotes=[...midiRec.recordedNotes,...Array.from(midiRec.activeNotes.values()).map(n=>({...n,duration_beats:currentBeat-n.start_beat}))];const durationSec=Math.max(4*(secondsPerBeatInt*4),currentTimeSec);if(Math.random()<0.2){// Throttle log to prevent flooding (approx 2 logs/sec) +const track=activeTracks.find(t=>t.id===st.trackId);const destNode=getOrCreateTrackNode(track,context);const instrumentProgram=track?track.instrumentProgram:undefined;const instrumentName=track?track.instrumentName:undefined;notes.forEach(note=>{const noteOnBeat=note.start_beat||0;const noteDurBeat=note.duration_beats||1;const noteStartSec=noteOnBeat*secondsPerBeat;const noteDurSec=noteDurBeat*secondsPerBeat;if(noteStartSec+noteDurSec>startOffset){const effectiveStart=Math.max(0,noteStartSec-startOffset);const effectiveDur=noteDurSec-Math.max(0,startOffset-noteStartSec);const scheduledTime=startWallTime+effectiveStart;const durMs=effectiveDur*1000;if(window.SonicSF){window.SonicSF.playNote(note.pitch||60,note.velocity||0.8,durMs,scheduledTime,instrumentProgram,destNode);}}});// Call startSubTabPlayback to spin up the silent buffer playhead timing/references +startSubTabPlayback(st,startOffset);}else{startSubTabPlayback(st,startOffset);}setSubTabs(prev=>prev.map(s=>s.id===activeTab?{...s,isPlaying:true,currentTime:startOffset}:s));}return;}const context=getAudioContext();if(isPlaying){stopAllPlayback();}else{startOffsetTimeRef.current=currentTime;startAudioTimeRef.current=context.currentTime;startTrackPlayback(currentTime);setIsPlaying(true);}};handlePlayPauseRef.current=handlePlayPause;const handlePause=()=>{if(isPlaying||subTabs.some(s=>s.isPlaying))stopAllPlayback();};const stopAllPlayback=()=>{activeSourcesRef.current.forEach(src=>{try{src.stop();}catch(e){}});activeSourcesRef.current=[];activeTrackNodesRef.current={};if(window.SonicSF){window.SonicSF.stopAll();}setIsPlaying(false);setSubTabs(prev=>prev.map(s=>({...s,isPlaying:false})));};const handleStop=()=>{if(recordingStateRef.current==='RECORDING'||recordingStateRef.current==='COUNT_IN'){stopRecordingTake();return;}stopAllPlayback();if(activeTab!=='main'&&!activeTab.startsWith('session_')){setSubTabs(prev=>prev.map(s=>s.id===activeTab?{...s,currentTime:0}:s));}else{setCurrentTime(0);}};const drawVuMeter=(canvas,db)=>{if(!canvas)return;const ctx=canvas.getContext('2d');if(!ctx)return;const w=canvas.width;const h=canvas.height;ctx.clearRect(0,0,w,h);const minDb=-60;const maxDb=0;const frac=Math.max(0,Math.min(1,(db-minDb)/(maxDb-minDb)));ctx.fillStyle='#18181b';ctx.fillRect(0,0,w,h);const grad=ctx.createLinearGradient(0,0,w,0);grad.addColorStop(0,'#10b981');grad.addColorStop(0.7,'#eab308');grad.addColorStop(0.95,'#ef4444');ctx.fillStyle=grad;ctx.fillRect(0,0,w*frac,h);if(db>=-0.5){ctx.fillStyle='#ff0000';ctx.fillRect(w-6,0,6,h);}};const handleRecordClick=async()=>{if(recordingState==='RECORDING'||recordingState==='COUNT_IN'){await stopRecordingTake();return;}const armed=activeTracks.filter(t=>t.isArmed&&t.inputSource?.deviceType&&t.inputSource.deviceType!=='NONE');if(armed.length===0){showToast('Vui lòng Arm (R) ít nhất một track và chọn cổng Input để ghi âm.','warning');return;}setRecordingState('COUNT_IN');const secondsPerBeat=60.0/(parseInt(bpm)||120);const countInDuration=secondsPerBeat*4;const audioCtx=getAudioContext();const now=audioCtx.currentTime;showToast('Metronome Count-in: 4... 3... 2... 1...','info');for(let i=0;i<4;i++){playMetronomeClick(now+i*secondsPerBeat,i===0);}setTimeout(()=>{startRecordingTake(armed);},countInDuration*1000);};const startRecordingTake=async armedTracks=>{const context=getAudioContext();if(context.state==='suspended'){await context.resume();}setRecordingState('RECORDING');setRecTempMidiNotes([]);setRecTempAudioBuffer(null);const startTimelineTime=currentTime;setRecStartTimelineTime(startTimelineTime);recordingStartTimeRef.current=startTimelineTime;const secondsPerBeat=60.0/(parseInt(bpm)||120);const startBeat=startTimelineTime/secondsPerBeat;nextMetronomeBeatRef.current=Math.ceil(startBeat);activeMIDIRecordersRef.current={};activeAudioRecordersRef.current={};recordingPCMDataRef.current={};startOffsetTimeRef.current=startTimelineTime;startAudioTimeRef.current=context.currentTime;startTrackPlayback(startTimelineTime);setIsPlaying(true);let midiRecList=[];for(let track of armedTracks){if(track.inputSource.deviceType==='MIDI_KEYBOARD'){const midiRec=new ClientMIDIRecorder(context,parseInt(bpm)||120,4);const tempMidiItemId='midi_rec_'+Date.now()+'_'+track.id;midiRec.tempMidiItemId=tempMidiItemId;updateActiveTracks(prev=>prev.map(t=>{if(t.id!==track.id)return t;return{...t,midiItems:[...(t.midiItems||[]),{id:tempMidiItemId,name:'Recording...',startTime:startTimelineTime,duration:4*(secondsPerBeat*4),notes:[]}]};}));midiRec.onNoteOn=(pitch,currentBeat)=>{const canvas=trackVuRefs.current[track.id];if(canvas){drawVuMeter(canvas,0);setTimeout(()=>drawVuMeter(canvas,-60),100);}const elapsedBeats=Math.max(0,currentBeat);const elapsedSec=elapsedBeats*secondsPerBeat;const durationSec=Math.max(4*(secondsPerBeat*4),elapsedSec);const allNotes=[...midiRec.recordedNotes,...Array.from(midiRec.activeNotes.values()).map(n=>({...n,duration_beats:currentBeat-n.start_beat}))];setRecTempMidiNotes(allNotes);setCanvasRedrawCount(n=>n+1);if(midiRec.tempMidiItemId){updateActiveTracks(prev=>prev.map(t=>{if(t.id!==track.id)return t;return{...t,midiItems:t.midiItems.map(m=>m.id===midiRec.tempMidiItemId?{...m,notes:allNotes,duration:durationSec}:m)};}));}};midiRec.onNoteOff=()=>{const currentBeat=(context.currentTime-midiRec.recStartAudioTime-midiRec.latencyCompSec)/(60.0/midiRec.bpm);const elapsedBeats=Math.max(0,currentBeat);const elapsedSec=elapsedBeats*secondsPerBeat;const durationSec=Math.max(4*(secondsPerBeat*4),elapsedSec);const activeNotesArray=Array.from(midiRec.activeNotes.values()).map(n=>({...n,duration_beats:currentBeat-n.start_beat}));const allNotes=[...midiRec.recordedNotes,...activeNotesArray];setRecTempMidiNotes(allNotes);setCanvasRedrawCount(n=>n+1);if(midiRec.tempMidiItemId){updateActiveTracks(prev=>prev.map(t=>{if(t.id!==track.id)return t;return{...t,midiItems:t.midiItems.map(m=>m.id===midiRec.tempMidiItemId?{...m,notes:allNotes,duration:durationSec}:m)};}));}};midiRec.start(startTimelineTime/(secondsPerBeat*4),track.inputSource.deviceId);activeMIDIRecordersRef.current[track.id]=midiRec;midiRecList.push({trackId:track.id,midiRec});}else if(track.inputSource.deviceType==='MICROPHONE'){const audioRec=new ClientAudioRecorder(context);try{await audioRec.initializeInput(track.inputSource.deviceId);recordingPCMDataRef.current[track.id]=[];audioRec.onLevelUpdate=db=>{const canvas=trackVuRefs.current[track.id];if(canvas){drawVuMeter(canvas,db);}};audioRec.onPCMChunk=chunk=>{if(recordingPCMDataRef.current[track.id]){const currentData=recordingPCMDataRef.current[track.id];const newData=new Float32Array(currentData.length+chunk.length);newData.set(currentData);newData.set(chunk,currentData.length);recordingPCMDataRef.current[track.id]=newData;}};const monitorGain=track.monitoringEnabled?context.destination:null;await audioRec.start(monitorGain,track.monitoringEnabled);activeAudioRecordersRef.current[track.id]=audioRec;}catch(err){console.error('Failed to initialize microphone:',err);showToast('Không khởi động được micro: '+err.message,'warning');}}}recordingSyncRef.current=setInterval(()=>{const secondsPerBeatInt=60.0/(parseInt(bpm)||120);for(let{trackId,midiRec}of midiRecList){if(!midiRec.isRecording||!midiRec.tempMidiItemId)continue;const currentTimeSec=Math.max(0,getAudioContext().currentTime-midiRec.recStartAudioTime-midiRec.latencyCompSec);const currentBeat=currentTimeSec/secondsPerBeatInt;const allNotes=[...midiRec.recordedNotes,...Array.from(midiRec.activeNotes.values()).map(n=>({...n,duration_beats:currentBeat-n.start_beat}))];const durationSec=Math.max(4*(secondsPerBeatInt*4),currentTimeSec);if(Math.random()<0.2){// Throttle log to prevent flooding (approx 2 logs/sec) console.log(`[DevLog] [MIDI Rec Sync] Temp Item ID: ${midiRec.tempMidiItemId}, Duration: ${durationSec.toFixed(2)}s, ActiveNotes: ${midiRec.activeNotes.size}, RecordedNotes: ${midiRec.recordedNotes.length}`);}setRecTempMidiNotes(allNotes);setCanvasRedrawCount(n=>n+1);updateActiveTracks(prev=>prev.map(t=>{if(t.id!==trackId)return t;return{...t,midiItems:t.midiItems.map(m=>m.id===midiRec.tempMidiItemId?{...m,notes:allNotes,duration:durationSec}:m)};}));}},100);showToast('Đang ghi âm...','info');};const stopRecordingTake=async()=>{setRecordingState('IDLE');stopAllPlayback();const context=getAudioContext();const secondsPerBeat=60.0/(parseInt(bpm)||120);const secondsPerBar=secondsPerBeat*4;const midiRecorders=activeMIDIRecordersRef.current;const audioRecorders=activeAudioRecordersRef.current;Object.keys(trackVuRefs.current).forEach(tid=>{const canvas=trackVuRefs.current[tid];if(canvas)drawVuMeter(canvas,-60);});let hasRecordedAnything=false;for(let trackId in midiRecorders){const midiRec=midiRecorders[trackId];const recordedNotes=midiRec.stop();if(midiRec.tempMidiItemId){const recCurrentTimeSec=Math.max(0,context.currentTime-midiRec.recStartAudioTime-midiRec.latencyCompSec);const recElapsedBeats=recCurrentTimeSec/(60.0/midiRec.bpm);const totalDurationBeats=Math.max(4.0,recordedNotes.length>0?Math.max(recElapsedBeats,...recordedNotes.map(n=>n.start_beat+n.duration_beats)):recElapsedBeats);updateActiveTracks(prev=>prev.map(t=>{if(t.id!==trackId)return t;const itemIndex=(t.midiItems||[]).findIndex(m=>m.id===midiRec.tempMidiItemId);if(itemIndex>=0){const updatedItems=[...t.midiItems];updatedItems[itemIndex]={...updatedItems[itemIndex],name:recordedNotes.length>0?'Recorded MIDI':'Empty MIDI',notes:recordedNotes,duration:Math.ceil(totalDurationBeats/4)*secondsPerBar};return{...t,midiItems:updatedItems};}const newMidiItem={id:'midi_rec_'+Date.now(),name:'Recorded MIDI',startTime:recordingStartTimeRef.current,duration:Math.ceil(totalDurationBeats/4)*secondsPerBar,notes:recordedNotes};return{...t,midiItems:[...(t.midiItems||[]),newMidiItem]};}));if(recordedNotes.length>0){hasRecordedAnything=true;}}else if(recordedNotes.length>0){hasRecordedAnything=true;const totalDurationBeats=Math.max(4.0,...recordedNotes.map(n=>n.start_beat+n.duration_beats));const newMidiItem={id:'midi_rec_'+Date.now(),name:'Recorded MIDI',startTime:recordingStartTimeRef.current,duration:Math.ceil(totalDurationBeats/4)*secondsPerBar,notes:recordedNotes};updateActiveTracks(prev=>prev.map(t=>{if(t.id!==trackId)return t;return{...t,midiItems:[...(t.midiItems||[]),newMidiItem]};}));}}for(let trackId in audioRecorders){const audioRec=audioRecorders[trackId];const audioBuffer=await audioRec.stop();if(audioBuffer&&audioBuffer.duration>0.05){hasRecordedAnything=true;const newClip={id:'clip_rec_'+Date.now(),name:'Recorded Audio.wav',buffer:audioBuffer,startTime:recordingStartTimeRef.current,speed:1.0};updateActiveTracks(prev=>prev.map(t=>{if(t.id!==trackId)return t;const updatedClips=[...(t.clips||[]),newClip];return{...t,clips:updatedClips,buffer:updatedClips[0]?.buffer||null,startTime:updatedClips[0]?.startTime||0,name:updatedClips[0]?.name||t.name};}));}}if(recordingSyncRef.current){clearInterval(recordingSyncRef.current);recordingSyncRef.current=null;}activeMIDIRecordersRef.current={};activeAudioRecordersRef.current={};recordingPCMDataRef.current={};setRecTempMidiNotes([]);setRecTempAudioBuffer(null);if(hasRecordedAnything){showToast('Đã thu và lưu bản ghi vào timeline.','success');}else{showToast('Đã dừng ghi âm (không phát hiện tín hiệu đầu vào).','info');}};const handleSubTabResizeMouseDown=e=>{e.preventDefault();e.stopPropagation();const startY=e.clientY;const startHeight=subTabHeight;const handleMouseMove=moveEvent=>{const deltaY=moveEvent.clientY-startY;const newHeight=Math.max(48,Math.min(400,startHeight+deltaY));setSubTabHeight(newHeight);};const handleMouseUp=()=>{document.removeEventListener('mousemove',handleMouseMove);document.removeEventListener('mouseup',handleMouseUp);};document.addEventListener('mousemove',handleMouseMove);document.addEventListener('mouseup',handleMouseUp);};// ── Playhead set with seek+play ── const handlePlayheadSet=(time,shiftKey)=>{localSelectionAnchorRef.current=time;if(isPlaying){// Click during playback: seek to position and continue playing setCurrentTime(time);stopAllPlayback();setTimeout(()=>{startOffsetTimeRef.current=time;startAudioTimeRef.current=getAudioContext().currentTime;startTrackPlayback(time);setIsPlaying(true);},50);}else{// Normal click: just set playhead @@ -272,7 +282,7 @@ if(t.id===targetTrackId){const existingClips=t.clips&&t.clips.length>0?t.clips:t useEffect(()=>{const handleMouseMove=e=>{const stretch=stretchedClipRef.current;if(!stretch)return;const wrapper=timelineWrapperRef.current;if(!wrapper)return;const rect=wrapper.getBoundingClientRect();const scrollLeft=wrapper.scrollLeft;const mouseX=e.clientX-rect.left+scrollLeft;const time=mouseX/zoom;const newDuration=Math.max(0.1,time-stretch.startTime);const speedRatio=stretch.originalDuration/newDuration;setTracks(prev=>prev.map(t=>{if(t.id===stretch.trackId){const updatedClips=(t.clips||[]).map(c=>{if(c.id===stretch.clipId){return{...c,speed:speedRatio};}return c;});return{...t,clips:updatedClips,buffer:updatedClips[0]?.buffer,startTime:updatedClips[0]?.startTime||0,speed:updatedClips[0]?.speed||1.0};}return t;}));};const handleMouseUp=()=>{const stretch=stretchedClipRef.current;if(!stretch)return;const afterSnap=captureTrackSnapshotRef.current(stretch.trackId);pushAction('STRETCH_CLIP',stretch.trackId,stretch.beforeSnap,afterSnap);setStretchedClip(null);showToast('Đã giãn thời gian clip.','success');};document.addEventListener('mousemove',handleMouseMove);document.addEventListener('mouseup',handleMouseUp);return()=>{document.removeEventListener('mousemove',handleMouseMove);document.removeEventListener('mouseup',handleMouseUp);};},[zoom]);// ── Section / MIDI Item Drag Start ── const handleSectionItemDragStart=(trackId,itemType,itemId,clickOffset)=>{setDraggedSectionItem({trackId,itemType,itemId,clickOffset});};// ── Section / MIDI Item Resize Start ── const handleSectionItemResizeStart=(trackId,itemType,itemId,side,clickTime)=>{const curTracks=activeTracks;const track=curTracks.find(t=>t.id===trackId);if(!track)return;const items=itemType==='section'?track.sections:track.midiItems;const item=(items||[]).find(it=>it.id===itemId);if(!item)return;const start=itemType==='section'?item.start:item.startTime;setResizedSectionItem({trackId,itemType,itemId,side,originalStart:start,originalDuration:item.duration});};// ── Document-level mousemove/mouseup for Section/MIDI item drag ── -useEffect(()=>{const handleMouseMove=e=>{const drag=draggedSectionItemRef.current;if(!drag)return;const wrapper=timelineWrapperRef.current;if(!wrapper)return;const rect=wrapper.getBoundingClientRect();const scrollLeft=wrapper.scrollLeft;const mouseX=e.clientX-rect.left+scrollLeft;const time=mouseX/zoom;const newStart=Math.max(0,time-drag.clickOffset);const targetTrackId=hoveredTrackIdRef.current||drag.trackId;const secondsPerBar=60.0/(parseInt(bpm)||120)*4;updateActiveTracks(prev=>prev.map(t=>{const items=drag.itemType==='section'?t.sections||[]:t.midiItems||[];const updatedItems=items.filter(it=>it.id!==drag.itemId);if(t.id===targetTrackId){const movedItem=items.find(it=>it.id===drag.itemId);if(movedItem){updatedItems.push(drag.itemType==='section'?{...movedItem,start:newStart}:{...movedItem,startTime:newStart});}else{if(drag.itemType==='section'){updatedItems.push({id:drag.itemId,name:'Section',start:newStart,duration:4*secondsPerBar,color:'#06b6d4'});}else{updatedItems.push({id:drag.itemId,name:'MIDI Item',startTime:newStart,duration:4*secondsPerBar,notes:[],color:'#a78bfa'});}}}return drag.itemType==='section'?{...t,sections:updatedItems}:{...t,midiItems:updatedItems};}));if(drag.trackId!==targetTrackId){setDraggedSectionItem(prev=>({...prev,trackId:targetTrackId}));}};const handleMouseUp=()=>{const drag=draggedSectionItemRef.current;if(!drag)return;setDraggedSectionItem(null);showToast(`Đã di chuyển ${drag.itemType==='section'?'section':'MIDI item'}.`,'success');};document.addEventListener('mousemove',handleMouseMove);document.addEventListener('mouseup',handleMouseUp);return()=>{document.removeEventListener('mousemove',handleMouseMove);document.removeEventListener('mouseup',handleMouseUp);};},[zoom,activeTab,sessionTabs]);// ── Document-level mousemove/mouseup for Section/MIDI item resize ── +useEffect(()=>{const handleMouseMove=e=>{const drag=draggedSectionItemRef.current;if(!drag)return;const wrapper=timelineWrapperRef.current;if(!wrapper)return;const rect=wrapper.getBoundingClientRect();const scrollLeft=wrapper.scrollLeft;const mouseX=e.clientX-rect.left+scrollLeft;const time=mouseX/zoom;const newStart=Math.max(0,time-drag.clickOffset);const targetTrackId=hoveredTrackIdRef.current||drag.trackId;const secondsPerBar=60.0/(parseInt(bpm)||120)*4;updateActiveTracks(prev=>{let movedItem=null;for(let track of prev){const items=drag.itemType==='section'?track.sections||[]:track.midiItems||[];const found=items.find(it=>it.id===drag.itemId);if(found){movedItem=found;break;}}return prev.map(t=>{const items=drag.itemType==='section'?t.sections||[]:t.midiItems||[];const updatedItems=items.filter(it=>it.id!==drag.itemId);if(t.id===targetTrackId){if(movedItem){updatedItems.push(drag.itemType==='section'?{...movedItem,start:newStart}:{...movedItem,startTime:newStart});}else{if(drag.itemType==='section'){updatedItems.push({id:drag.itemId,name:'Section',start:newStart,duration:4*secondsPerBar,color:'#06b6d4'});}else{updatedItems.push({id:drag.itemId,name:'MIDI Item',startTime:newStart,duration:4*secondsPerBar,notes:[],color:'#a78bfa'});}}}return drag.itemType==='section'?{...t,sections:updatedItems}:{...t,midiItems:updatedItems};});});if(drag.trackId!==targetTrackId){setDraggedSectionItem(prev=>({...prev,trackId:targetTrackId}));}};const handleMouseUp=()=>{const drag=draggedSectionItemRef.current;if(!drag)return;setDraggedSectionItem(null);showToast(`Đã di chuyển ${drag.itemType==='section'?'section':'MIDI item'}.`,'success');};document.addEventListener('mousemove',handleMouseMove);document.addEventListener('mouseup',handleMouseUp);return()=>{document.removeEventListener('mousemove',handleMouseMove);document.removeEventListener('mouseup',handleMouseUp);};},[zoom,activeTab,sessionTabs]);// ── Document-level mousemove/mouseup for Section/MIDI item resize ── useEffect(()=>{const handleMouseMove=e=>{const resize=resizedSectionItemRef.current;if(!resize)return;const wrapper=timelineWrapperRef.current;if(!wrapper)return;const rect=wrapper.getBoundingClientRect();const scrollLeft=wrapper.scrollLeft;const mouseX=e.clientX-rect.left+scrollLeft;const time=mouseX/zoom;updateActiveTracks(prev=>prev.map(t=>{if(t.id!==resize.trackId)return t;const items=resize.itemType==='section'?[...(t.sections||[])]:[...(t.midiItems||[])];const idx=items.findIndex(it=>it.id===resize.itemId);if(idx===-1)return t;const item=items[idx];if(resize.side==='left'){const newStart=Math.min(time,resize.originalStart+resize.originalDuration-0.1);const end=resize.originalStart+resize.originalDuration;const newDuration=end-newStart;if(newDuration<0.1)return t;items[idx]=resize.itemType==='section'?{...item,start:newStart,duration:newDuration}:{...item,startTime:newStart,duration:newDuration};}else{const newDuration=Math.max(0.1,time-resize.originalStart);items[idx]={...item,duration:newDuration};}return resize.itemType==='section'?{...t,sections:items}:{...t,midiItems:items};}));};const handleMouseUp=()=>{const resize=resizedSectionItemRef.current;if(!resize)return;setResizedSectionItem(null);};document.addEventListener('mousemove',handleMouseMove);document.addEventListener('mouseup',handleMouseUp);return()=>{document.removeEventListener('mousemove',handleMouseMove);document.removeEventListener('mouseup',handleMouseUp);};},[zoom,activeTab,sessionTabs]);const handleSelectRange=(start,end,reset)=>{const maxLen=maxDuration;const cleanStart=Math.max(0,Math.min(maxLen,start));const cleanEnd=Math.max(0,Math.min(maxLen,end));if(reset){setSelectionStart(cleanStart);setSelectionEnd(cleanEnd);}else{setSelectionEnd(cleanEnd);}// LOOP_EDITOR_2.md §4.2: new selection = enable looping setSelectionCleared(false);};const handleSelectionInputChange=(field,val)=>{const numericVal=Math.max(0,parseFloat(val)||0);if(selectionMode==='local'){// Editing local selection directly if(field==='start'){setLocalSelectionStart(numericVal);}else{setLocalSelectionEnd(numericVal);}}else{if(field==='start'){setSelectionStart(numericVal);}else{setSelectionEnd(numericVal);}}};const selectionStats=useMemo(()=>{if(selLeft===null||selRight===null){return{start:0,end:0,length:0};}const s=Math.min(selLeft,selRight);const e=Math.max(selLeft,selRight);return{start:parseFloat(s.toFixed(3)),end:parseFloat(e.toFixed(3)),length:parseFloat((e-s).toFixed(3))};},[selLeft,selRight]);// ── Handle Drag (selection resize) ── @@ -285,7 +295,7 @@ uploadToServer(file,trackId);// Decode locally for playback + analyze channels ( const{audioBuffer:decodedBuffer,channelInfo}=await window.SonicAudio.decodeAudioFile(file);setTracks(prev=>prev.map(t=>t.id===trackId?{...t,name:file.name,buffer:decodedBuffer,channelInfo:channelInfo}:t));showToast(`Nạp file thành công: ${file.name} (${channelInfo.label})`,'success');}catch(err){showToast("Lỗi giải mã âm thanh. Định dạng file không tương thích.",'error');}};// ── Synth Generators ── const generateSynthToTrack=(trackId,type)=>{const sampleRate=44100;const duration=12.0;const context=getAudioContext();const frameCount=sampleRate*duration;const newBuffer=context.createBuffer(1,frameCount,sampleRate);const channelData=newBuffer.getChannelData(0);if(type==='kick'){for(let i=0;iprev.map(t=>t.id===trackId?{...t,name:`Demo_${type.toUpperCase()}.wav`,buffer:newBuffer}:t));showToast(`Đã nạp sóng âm tổng hợp: ${type.toUpperCase()}`,'success');};// ── Add Track ── const addNewTrack=()=>{const curTracks=activeTracks;const newId=(curTracks.length+1).toString();const colors=['#0f766e','#1d4ed8','#701a75','#a21caf','#b45309'];const selectColor=colors[curTracks.length%colors.length];updateActiveTracks(prev=>[...prev,{id:newId,name:`Track ${newId}`,buffer:null,startTime:0,volumeDb:0,pan:0,muted:false,solo:false,color:selectColor,markers:[],serverFileId:null,clips:[],sections:[],midiItems:[],isArmed:false,monitoringEnabled:true,inputSource:{deviceType:'NONE',deviceId:''}}]);showToast(`Đã thêm Track ${newId}.`,'info');setTimeout(()=>lucide.createIcons(),200);return newId;};// ── Update tracks in active context (main session or section tab) ── -const updateActiveTracksRef=useCallback(updater=>{const currentSessionTabs=sessionTabsRef.current;const currentActiveTab=activeTabRef.current;const st=currentSessionTabs.find(s=>s.id===currentActiveTab);if(st){setSessionTabs(prev=>prev.map(s=>s.id===currentActiveTab?{...s,tracks:updater(s.tracks)}:s));}else{setTracks(updater);}},[]);const updateActiveTracks=updateActiveTracksRef;const toggleTrackArm=trackId=>{updateActiveTracks(prev=>prev.map(t=>{if(t.id!==trackId)return t;return{...t,isArmed:!t.isArmed};}));setTimeout(()=>lucide.createIcons(),50);};const toggleTrackMonitor=trackId=>{updateActiveTracks(prev=>prev.map(t=>{if(t.id!==trackId)return t;return{...t,monitoringEnabled:!t.monitoringEnabled};}));setTimeout(()=>lucide.createIcons(),50);};const updateTrackInputSource=(trackId,type,deviceId)=>{updateActiveTracks(prev=>prev.map(t=>{if(t.id!==trackId)return t;return{...t,inputSource:{deviceType:type,deviceId}};}));};// ── MIDI Export: tạo file MIDI từ tất cả tracks ── +const updateActiveTracksRef=useCallback(updater=>{const currentSessionTabs=sessionTabsRef.current;const currentActiveTab=activeTabRef.current;const st=currentSessionTabs.find(s=>s.id===currentActiveTab);if(st){setSessionTabs(prev=>prev.map(s=>s.id===currentActiveTab?{...s,tracks:updater(s.tracks),isDirty:true}:s));}else{setTracks(updater);}},[]);const updateActiveTracks=updateActiveTracksRef;const toggleTrackArm=trackId=>{updateActiveTracks(prev=>prev.map(t=>{if(t.id!==trackId)return t;return{...t,isArmed:!t.isArmed};}));setTimeout(()=>lucide.createIcons(),50);};const toggleTrackMonitor=trackId=>{updateActiveTracks(prev=>prev.map(t=>{if(t.id!==trackId)return t;return{...t,monitoringEnabled:!t.monitoringEnabled};}));setTimeout(()=>lucide.createIcons(),50);};const updateTrackInputSource=(trackId,type,deviceId)=>{updateActiveTracks(prev=>prev.map(t=>{if(t.id!==trackId)return t;return{...t,inputSource:{deviceType:type,deviceId}};}));};// ── MIDI Export: tạo file MIDI từ tất cả tracks ── const triggerMidiExport=()=>{const bpmNum=parseFloat(bpm)||120;const ppq=480;// Pulses Per Quarter Note const ticksPerBeat=ppq;const beatDuration=60/bpmNum;// Build MIDI tracks let midiTracks=[];let currentTrackNum=0;tracks.forEach(track=>{currentTrackNum++;const events=[];let hasNotes=false;// Collect MIDI events from midiItems @@ -347,5 +357,5 @@ const handleSplitTrackAtTime=(trackId,clipId,time)=>{const track=tracks.find(t=> const handleGlueTracks=()=>{const track=tracks.find(t=>t.id===selectedTrackId);if(!track){showToast('Vui lòng chọn một track để thực hiện gộp (glue).','warning');return;}const clips=track.clips&&track.clips.length>0?track.clips:track.buffer?[{id:'default',buffer:track.buffer,startTime:track.startTime||0,name:track.name}]:[];if(clips.length<2){showToast('Cần ít nhất 2 clip trên track này để gộp (glue).','warning');return;}const beforeSnap=captureTrackSnapshot(track.id);const ctx=getAudioContext();const sr=clips[0].buffer.sampleRate;let minStart=Infinity;let maxEnd=-Infinity;clips.forEach(c=>{const start=c.startTime||0;const end=start+c.buffer.duration;minStart=Math.min(minStart,start);maxEnd=Math.max(maxEnd,end);});const newDur=maxEnd-minStart;const newBuffer=ctx.createBuffer(1,Math.ceil(newDur*sr),sr);const newData=newBuffer.getChannelData(0);clips.forEach(c=>{const data=c.buffer.getChannelData(0);const offset=Math.floor(((c.startTime||0)-minStart)*sr);for(let i=0;imaxPeak)maxPeak=abs;}if(maxPeak>1.0){for(let i=0;iprev.map(t=>{if(t.id===track.id){return{...t,clips:[mergedClip],buffer:newBuffer,startTime:minStart,name:mergedClip.name};}return t;}));setTimeout(()=>{const afterSnap=captureTrackSnapshot(track.id);pushAction('GLUE',track.id,beforeSnap,afterSnap);},50);showToast(`Đã gộp ${clips.length} clips thành công.`,'success');};// ── DAW Command Registration for AI (28_AI_PANEL.md §1 & §2) ── useEffect(()=>{if(typeof window.DAWCommandDispatcher==='undefined')return;const api={createTrack:args=>{const name=args.name||`AI_Track_${Date.now()}`;const type=args.type||'audio';const newId=addNewTrack();if(name&&name!==`AI_Track_${Date.now()}`){updateTrackName(newId,name);}return{success:true,trackId:newId,name};},deleteTrack:args=>{const tid=args.track_id||selectedTrackId;if(!tid)return{success:false,error:'No track_id provided'};deleteTrack(tid);return{success:true,trackId:tid};},addClip:args=>{const trackId=args.track_id||selectedTrackId;const barDur=60/parseInt(bpm||120)*4;let startTime;if(args.start_time!==undefined&&args.start_time!==null)startTime=args.start_time;else if(args.start_bar!==undefined&&args.start_bar!==null)startTime=args.start_bar*barDur;else startTime=currentTime;const track=tracks.find(t=>t.id===trackId);if(!track)return{success:false,error:'Track not found'};const ctx=getAudioContext();const sr=44100;let duration;if(args.duration_seconds!==undefined&&args.duration_seconds!==null)duration=args.duration_seconds;else if(args.length_bars!==undefined&&args.length_bars!==null)duration=args.length_bars*barDur;else duration=2;const buffer=ctx.createBuffer(1,Math.floor(sr*duration),sr);const data=buffer.getChannelData(0);for(let i=0;iprev.map(t=>{if(t.id!==trackId)return t;const clips=t.clips&&t.clips.length>0?t.clips:t.buffer?[{id:'default_'+t.id,buffer:t.buffer,startTime:t.startTime||0,name:t.name}]:[];return{...t,clips:[...clips,{id:clipId,buffer,startTime,name:args.name||'AI Clip'}],buffer:clips.length>0?clips[0].buffer:buffer,startTime:clips.length>0?clips[0].startTime:startTime,name:clips.length>0?clips[0].name:args.name||t.name};}));return{success:true,clipId,trackId};},removeClip:args=>{const trackId=args.track_id||selectedTrackId;const clipId=args.clip_id;setTracks(prev=>prev.map(t=>{if(t.id!==trackId)return t;const updatedClips=(t.clips||[]).filter(c=>c.id!==clipId);return{...t,clips:updatedClips,buffer:updatedClips[0]?.buffer||null,startTime:updatedClips[0]?.startTime||0,name:updatedClips[0]?.name||t.name};}));return{success:true};},setTrackVolume:args=>{const trackId=args.track_id||selectedTrackId;const vol=args.volume_db??args.volume??0;updateTrackVolumeDb(trackId,parseFloat(vol));return{success:true,trackId,volumeDb:vol};},setTrackPan:args=>{const trackId=args.track_id||selectedTrackId;const pan=args.pan??0;updateTrackPan(trackId,parseInt(pan));return{success:true,trackId,pan};},toggleMute:args=>{const trackId=args.track_id||selectedTrackId;toggleTrackMute(trackId);const track=tracks.find(t=>t.id===trackId);return{success:true,trackId,muted:track?track.muted:null};},toggleSolo:args=>{const trackId=args.track_id||selectedTrackId;toggleTrackSoloEvaluate(trackId);const track=tracks.find(t=>t.id===trackId);return{success:true,trackId,solo:track?track.solo:null};},processAudioDsp:args=>{const trackId=args.track_id||selectedTrackId;const action=args.action;const params=args.params||{};const track=tracks.find(t=>t.id===trackId);if(!track||!track.buffer)return{success:false,error:'Track has no audio buffer'};if(action==='normalize'){const channelData=track.buffer.getChannelData(0);let maxVal=0;for(let i=0;i0){const gain=1.0/maxVal;for(let i=0;i{const newLen=Math.round(data.length*r);const out=new Float32Array(newLen);for(let i=0;iprev.map(t=>t.id===trackId?{...t,buffer:newBuffer}:t));return{success:true,action:'pitch_shift',semitones};}return{success:false,error:`Unknown action: ${action}`};},renameTrack:args=>{const tid=args.track_id||selectedTrackId;const name=args.name;if(!tid)return{success:false,error:'No track_id provided'};if(!name)return{success:false,error:'No name provided'};updateTrackName(tid,name);return{success:true,trackId:tid,name};},setSelection:args=>{const barDur=60/parseInt(bpm||120)*4;let start,end;if(args.start_time!==undefined&&args.start_time!==null)start=args.start_time;else if(args.start_bar!==undefined&&args.start_bar!==null)start=args.start_bar*barDur;else start=currentTime;if(args.end_time!==undefined&&args.end_time!==null)end=args.end_time;else if(args.length_bars!==undefined&&args.length_bars!==null)end=start+args.length_bars*barDur;else if(args.end_bar!==undefined&&args.end_bar!==null)end=args.end_bar*barDur;else end=start+barDur;clearLocalSelection();setSelectionMode('global');setSelectionStart(start);setSelectionEnd(end);selectionRef.current={start,end};return{success:true,start:parseFloat(start.toFixed(3)),end:parseFloat(end.toFixed(3)),length:parseFloat((end-start).toFixed(3))};},cutAudio:args=>{const currentTracks=window.DAWCommandDispatcher?.currentTracks||tracks;const currentSelTrackId=window.DAWCommandDispatcher?.currentSelectedTrackId||selectedTrackId;const tid=args.track_id||currentSelTrackId;const track=currentTracks.find(t=>t.id===String(tid));if(!track)return{success:false,error:'Track not found'};if(!track.buffer)return{success:false,error:'Track has no audio buffer'};const barDur=60/parseInt(bpm||120)*4;const sel=selectionRef.current;let rawStart,rawEnd;if(args.start_time!==undefined&&args.start_time!==null)rawStart=args.start_time;else if(args.start_bar!==undefined&&args.start_bar!==null)rawStart=args.start_bar*barDur;else if(sel.start!==null)rawStart=sel.start;else rawStart=currentTime;if(args.end_time!==undefined&&args.end_time!==null)rawEnd=args.end_time;else if(args.end_bar!==undefined&&args.end_bar!==null)rawEnd=args.end_bar*barDur;else if(args.length_bars!==undefined&&args.length_bars!==null)rawEnd=rawStart+args.length_bars*barDur;else if(sel.end!==null&&sel.end>rawStart)rawEnd=sel.end;else return{success:false,error:'No end position provided. Provide end_time, end_bar, or length_bars.'};if(rawEnd<=rawStart)return{success:false,error:'End position must be after start position.'};const buffer=track.buffer;const ctx=getAudioContext();const snap=args.snap_silence!==false;const loopStart=snap?findZeroCrossing(buffer,rawStart):rawStart;const loopEnd=snap?findZeroCrossing(buffer,rawEnd):rawEnd;const sampleRate=buffer.sampleRate;const startSample=Math.max(0,Math.min(buffer.length-1,Math.floor(loopStart*sampleRate)));const endSample=Math.max(0,Math.min(buffer.length,Math.floor(loopEnd*sampleRate)));const sliceLength=endSample-startSample;if(sliceLength<=100)return{success:false,error:'Selection too short or invalid'};const numChannels=buffer.numberOfChannels||1;const slicedBuffer=ctx.createBuffer(numChannels,sliceLength,sampleRate);for(let c=0;ct.id===tid);const nextTracks=[...currentTracks];if(idx!==-1){nextTracks.splice(idx+1,0,newTrack);}else{nextTracks.push(newTrack);}if(window.DAWCommandDispatcher){window.DAWCommandDispatcher.currentTracks=nextTracks;window.DAWCommandDispatcher.currentSelectedTrackId=newId;window.DAWCommandDispatcher.lastCutSourceTrackId=tid;window.DAWCommandDispatcher.lastCutNewTrackId=newId;}setTracks(nextTracks);setSelectedTrackId(newId);selectionRef.current={start:0,end:slicedBuffer.duration};clearLocalSelection();setSelectionMode('global');setSelectionStart(0);setSelectionEnd(slicedBuffer.duration);setTimeout(()=>lucide.createIcons(),200);return{success:true,trackId:newId,trackName:cutName,cutStart:parseFloat(loopStart.toFixed(3)),cutEnd:parseFloat(loopEnd.toFixed(3)),duration:parseFloat(slicedBuffer.duration.toFixed(3))};},scanTrack:args=>{const tid=args.track_id||selectedTrackId;const track=tracks.find(t=>t.id===tid);if(!track)return{success:false,error:'Track not found'};if(!track.buffer)return{success:false,error:'Track has no audio buffer. Load audio first.'};const buffer=track.buffer;const data=buffer.getChannelData(0);const sr=buffer.sampleRate;const channels=buffer.numberOfChannels;const duration=buffer.duration;const totalSamples=buffer.length;const windowSize=Math.min(sr*3,data.length);let detectedBPM=0;if(windowSize>sr){let maxCorr=0;for(let lag=Math.floor(sr*0.3);lag<=Math.floor(sr*2.0);lag++){let corr=0;const step=4;for(let i=0;imaxCorr){maxCorr=corr;detectedBPM=60/(lag/sr);}}}detectedBPM=Math.round(Math.min(300,Math.max(30,detectedBPM)));if(args.set_tempo!==false&&detectedBPM>0){setBpm(String(detectedBPM));}const bitDepth=16;const bitrate=Math.round(sr*channels*bitDepth/1000);return{success:true,trackId:tid,trackName:track.name,bpm:detectedBPM,sampleRate:sr,channels,duration:parseFloat(duration.toFixed(3)),totalSamples,bitDepth,bitrateKbps:bitrate,hasAudio:true};},setBpm:args=>{const bpmVal=args.bpm||args.tempo||120;setBpm(String(bpmVal));return{success:true,bpm:bpmVal};},setPlayhead:args=>{const barDur=60/parseInt(bpm||120)*4;let time;if(args.time!==undefined&&args.time!==null)time=args.time;else if(args.bar!==undefined&&args.bar!==null)time=args.bar*barDur;else time=0;handlePlayheadSet(time);return{success:true,time:parseFloat(time.toFixed(3))};},exportAudio:async args=>{const currentTracks=window.DAWCommandDispatcher?.currentTracks||tracks;const currentSelTrackId=window.DAWCommandDispatcher?.currentSelectedTrackId||selectedTrackId;let tid=args.track_id;if(tid&&window.DAWCommandDispatcher?.lastCutSourceTrackId&&(String(tid)===String(window.DAWCommandDispatcher.lastCutSourceTrackId)||'track_'+tid===String(window.DAWCommandDispatcher.lastCutSourceTrackId))){tid=window.DAWCommandDispatcher.lastCutNewTrackId;}if(!tid)tid=currentSelTrackId;const track=tid&¤tTracks.find(t=>t.id===String(tid)||t.id==='track_'+tid);if(!track)return{success:false,error:'No track found'};const clips=track.clips&&track.clips.length>0?track.clips:track.buffer?[{id:'default_'+track.id,buffer:track.buffer,startTime:track.startTime||0,name:track.name}]:[];if(clips.length===0)return{success:false,error:'Track has no audio clips'};const totalDuration=Math.max(...clips.map(c=>(c.startTime||0)+(c.buffer?c.buffer.duration/(c.speed||1.0):0)));const barDur=60/parseInt(bpm||120)*4;const sel=selectionRef.current;let rawStart,rawEnd;if(args.start_time!==undefined)rawStart=args.start_time;else if(args.start_bar!==undefined)rawStart=args.start_bar*barDur;else if(sel.start!==null)rawStart=sel.start;else rawStart=0;if(args.end_time!==undefined)rawEnd=args.end_time;else if(args.length_bars!==undefined)rawEnd=(rawStart||0)+args.length_bars*barDur;else if(args.end_bar!==undefined)rawEnd=args.end_bar*barDur;else if(sel.end!==null&&sel.end>rawStart)rawEnd=sel.end;else rawEnd=totalDuration;if(rawEnd<=rawStart)return{success:false,error:'Export range is empty or invalid.'};const ctx=getAudioContext();const sr=parseInt(args.sample_rate||'44100');const firstBuffer=clips.find(c=>c.buffer)?.buffer;const numCh=args.channels==='mono'?1:firstBuffer?firstBuffer.numberOfChannels:2;const bd=parseInt(args.bit_depth||'16');const fmt=args.format||'wav';const renderLength=rawEnd-rawStart;const offlineCtx=new OfflineAudioContext(numCh,Math.ceil(sr*renderLength),sr);clips.forEach(clip=>{if(!clip.buffer)return;const clipStart=clip.startTime||0;const clipDuration=clip.buffer.duration/(clip.speed||1.0);const clipEnd=clipStart+clipDuration;if(clipEnd<=rawStart||clipStart>=rawEnd)return;const source=offlineCtx.createBufferSource();source.buffer=clip.buffer;source.playbackRate.value=clip.speed||1.0;source.connect(offlineCtx.destination);if(rawStart{for(let i=0;i>8&0xFF);vw.setUint8(ofs+2,v24>>16&0xFF);}ofs+=bps;}}const blob=new Blob([fileBuf],{type:'audio/wav'});const localUrl=URL.createObjectURL(blob);const targetFilename=`export_${Date.now()}.${fmt}`;const triggerDownload=(downloadUrl,finalFilename)=>{if(window.DAWCommandDispatcher?.isExecutingAI){showToast(`Xuất nhạc thành công (${fmt.toUpperCase()})!`,"success","Tải về",()=>{const a=document.createElement('a');a.href=downloadUrl;a.download=finalFilename;a.click();if(downloadUrl.startsWith('blob:')){URL.revokeObjectURL(downloadUrl);}});}else{const a=document.createElement('a');a.href=downloadUrl;a.download=finalFilename;a.click();showToast("Xuất bản âm thanh hoàn tất!","success");if(downloadUrl.startsWith('blob:')){URL.revokeObjectURL(downloadUrl);}}};if((fmt==='mp3'||fmt==='ogg')&&serverStatus==='connected'){try{const file=new File([blob],`export_ai.wav`,{type:'audio/wav'});const formData=new FormData();formData.append('file',file);const uploadResp=await fetch(`${API_AUDIO}/upload`,{method:'POST',body:formData});if(!uploadResp.ok)throw new Error("Upload failed");const uploadData=await uploadResp.json();const uploadId=uploadData.file_id;const exportResp=await fetch(`${API_AUDIO}/export`,{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({file_id:uploadId,format:fmt,sample_rate:sr,bit_depth:bd})});if(!exportResp.ok)throw new Error("Export failed");const exportData=await exportResp.json();const result=await pollTaskResult(exportData.task_id,20);if(result.success){const downloadUrl=`${API_AUDIO}/download/${result.output_file_id}`;triggerDownload(downloadUrl,targetFilename);}else{throw new Error(result.error||'Server encoding failed');}}catch(transcodeErr){showToast(`Lỗi chuyển đổi: ${transcodeErr.message}. Tải về dạng WAV thay thế.`,"warning");triggerDownload(localUrl,`export_${Date.now()}.wav`);}}else{const finalFilename=fmt==='wav'?`export_${Date.now()}.wav`:`export_${Date.now()}.wav`;if(fmt!=='wav'){showToast("Đang ngoại tuyến. Tải về định dạng WAV thay thế.","warning");}triggerDownload(localUrl,finalFilename);}return{success:true,trackId:tid,range:parseFloat((rawEnd-rawStart).toFixed(3))+'s',format:fmt,channels:numCh===1?'mono':'stereo'};},selectItem:args=>{if(args.select_all){setSelectedTrackId(null);clearLocalSelection();setSelectionMode('global');setSelectionStart(0);const maxDur=tracks.reduce((max,t)=>{const dur=t.buffer?t.buffer.duration:0;const clips=t.clips||[];const clipMax=clips.reduce((m,c)=>Math.max(m,(c.startTime||0)+(c.buffer?c.buffer.duration:0)),0);return Math.max(max,dur,clipMax);},0);setSelectionEnd(Math.max(maxDur,currentTime+10));return{success:true,selection:'all',duration:parseFloat(Math.max(maxDur,currentTime+10).toFixed(3))};}const tid=args.track_id||selectedTrackId;const track=tracks.find(t=>t.id===tid);if(!track)return{success:false,error:`Track ${tid} not found`};const clips=track.clips&&track.clips.length>0?track.clips:track.buffer?[{id:'default_'+track.id,name:track.name,buffer:track.buffer,startTime:track.startTime||0}]:[];if(args.item_name){const match=clips.find(c=>c.name&&c.name.toLowerCase().includes(args.item_name.toLowerCase()));if(!match)return{success:false,error:`No clip matching "${args.item_name}" on track ${track.name}`};setSelectedTrackId(tid);clearLocalSelection();setSelectionMode('global');const start=match.startTime||0;const end=start+(match.buffer?match.buffer.duration:2);setSelectionStart(start);setSelectionEnd(end);return{success:true,trackId:tid,trackName:track.name,clipId:match.id,clipName:match.name,start:parseFloat(start.toFixed(3)),end:parseFloat(end.toFixed(3))};}setSelectedTrackId(tid);clearLocalSelection();setSelectionMode('global');const trackEnd=track.buffer?track.buffer.duration:clips.length>0?Math.max(...clips.map(c=>(c.startTime||0)+(c.buffer?c.buffer.duration:0))):4;setSelectionStart(0);setSelectionEnd(trackEnd);return{success:true,trackId:tid,trackName:track.name,duration:parseFloat(trackEnd.toFixed(3))};},addMarker:args=>{const trackId=args.track_id||selectedTrackId;const time=args.time??currentTime;const track=tracks.find(t=>t.id===trackId);if(!track)return{success:false,error:'Track not found'};setTracks(prev=>prev.map(t=>{if(t.id!==trackId)return t;return{...t,markers:[...(t.markers||[]),{id:'ai_marker_'+Date.now(),time,label:args.label||'AI Marker'}]};}));return{success:true,trackId,time};},fadeIn:args=>{const currentTracks=window.DAWCommandDispatcher?.currentTracks||tracks;const currentSelTrackId=window.DAWCommandDispatcher?.currentSelectedTrackId||selectedTrackId;let trackIdRaw=args.track_id;if(trackIdRaw&&window.DAWCommandDispatcher?.lastCutSourceTrackId&&(String(trackIdRaw)===String(window.DAWCommandDispatcher.lastCutSourceTrackId)||'track_'+trackIdRaw===String(window.DAWCommandDispatcher.lastCutSourceTrackId))){trackIdRaw=window.DAWCommandDispatcher.lastCutNewTrackId;}if(!trackIdRaw)trackIdRaw=currentSelTrackId;const track=currentTracks.find(t=>t.id===String(trackIdRaw)||t.id==='track_'+trackIdRaw);if(!track)return{success:false,error:`Track ${trackIdRaw} not found`};const clips=track.clips&&track.clips.length>0?track.clips:track.buffer?[{id:'default_'+track.id,buffer:track.buffer,startTime:track.startTime||0,name:track.name}]:[];if(clips.length===0)return{success:false,error:'No clips on track'};let clip=null;if(args.clip_id){clip=clips.find(c=>c.id===args.clip_id);}else if(args.clip_index!==undefined){const idx=parseInt(args.clip_index);const realIdx=idx>0?idx-1:0;clip=clips[realIdx]||clips[0];}else{clip=clips[0];}if(!clip||!clip.buffer)return{success:false,error:'Clip has no audio buffer'};const duration=parseFloat(args.duration_seconds||3);const buffer=clip.buffer;const sr=buffer.sampleRate;const numChannels=buffer.numberOfChannels;const length=buffer.length;const ctx=getAudioContext();const newBuffer=ctx.createBuffer(numChannels,length,sr);for(let c=0;c{if(t.id!==track.id)return t;const existingClips=t.clips&&t.clips.length>0?t.clips:t.buffer?[{id:'default_'+t.id,buffer:t.buffer,startTime:t.startTime||0,name:t.name}]:[];const updatedClips=existingClips.map(c=>{if(c.id===clip.id||clip.id.startsWith('default_')&&c.id==='default'){return{...c,buffer:newBuffer};}return c;});const mainBuffer=updatedClips[0]?.buffer||t.buffer;return{...t,clips:updatedClips,buffer:mainBuffer};});if(window.DAWCommandDispatcher){window.DAWCommandDispatcher.currentTracks=nextTracks;}setTracks(nextTracks);setTimeout(()=>{const afterSnap=captureTrackSnapshot(track.id);pushAction('AI_FADE_IN',track.id,beforeSnap,afterSnap);},50);return{success:true,trackId:track.id,clipId:clip.id,duration_seconds:duration};},fadeOut:args=>{const currentTracks=window.DAWCommandDispatcher?.currentTracks||tracks;const currentSelTrackId=window.DAWCommandDispatcher?.currentSelectedTrackId||selectedTrackId;let trackIdRaw=args.track_id;if(trackIdRaw&&window.DAWCommandDispatcher?.lastCutSourceTrackId&&(String(trackIdRaw)===String(window.DAWCommandDispatcher.lastCutSourceTrackId)||'track_'+trackIdRaw===String(window.DAWCommandDispatcher.lastCutSourceTrackId))){trackIdRaw=window.DAWCommandDispatcher.lastCutNewTrackId;}if(!trackIdRaw)trackIdRaw=currentSelTrackId;const track=currentTracks.find(t=>t.id===String(trackIdRaw)||t.id==='track_'+trackIdRaw);if(!track)return{success:false,error:`Track ${trackIdRaw} not found`};const clips=track.clips&&track.clips.length>0?track.clips:track.buffer?[{id:'default_'+track.id,buffer:track.buffer,startTime:track.startTime||0,name:track.name}]:[];if(clips.length===0)return{success:false,error:'No clips on track'};let clip=null;if(args.clip_id){clip=clips.find(c=>c.id===args.clip_id);}else if(args.clip_index!==undefined){const idx=parseInt(args.clip_index);const realIdx=idx>0?idx-1:0;clip=clips[realIdx]||clips[0];}else{clip=clips[0];}if(!clip||!clip.buffer)return{success:false,error:'Clip has no audio buffer'};const duration=parseFloat(args.duration_seconds||3);const buffer=clip.buffer;const sr=buffer.sampleRate;const numChannels=buffer.numberOfChannels;const length=buffer.length;const ctx=getAudioContext();const newBuffer=ctx.createBuffer(numChannels,length,sr);for(let c=0;c{if(t.id!==track.id)return t;const existingClips=t.clips&&t.clips.length>0?t.clips:t.buffer?[{id:'default_'+t.id,buffer:t.buffer,startTime:t.startTime||0,name:t.name}]:[];const updatedClips=existingClips.map(c=>{if(c.id===clip.id||clip.id.startsWith('default_')&&c.id==='default'){return{...c,buffer:newBuffer};}return c;});const mainBuffer=updatedClips[0]?.buffer||t.buffer;return{...t,clips:updatedClips,buffer:mainBuffer};});if(window.DAWCommandDispatcher){window.DAWCommandDispatcher.currentTracks=nextTracks;}setTracks(nextTracks);setTimeout(()=>{const afterSnap=captureTrackSnapshot(track.id);pushAction('AI_FADE_OUT',track.id,beforeSnap,afterSnap);},50);return{success:true,trackId:track.id,clipId:clip.id,duration_seconds:duration};},generateMultitrackMidi:args=>{const{composition_title,bpm:aiBpm,total_bars,tracks:aiTracks}=args;if(aiBpm){setBpm(aiBpm.toString());}const bpmVal=aiBpm||parseInt(bpm)||120;const secondsPerBeat=60.0/bpmVal;const secondsPerBar=secondsPerBeat*4;const durationSec=total_bars*secondsPerBar;updateActiveTracks(prev=>{let updatedTracks=[...prev];aiTracks.forEach(aiTrack=>{let targetTrack=updatedTracks.find(t=>t.name.toLowerCase()===aiTrack.track_name.toLowerCase());if(!targetTrack){const newId=(updatedTracks.length+1).toString();const colors=['#0f766e','#1d4ed8','#701a75','#a21caf','#b45309'];const selectColor=colors[updatedTracks.length%colors.length];targetTrack={id:newId,name:aiTrack.track_name,type:'MIDI',volumeDb:0,pan:0,muted:false,solo:false,color:selectColor,markers:[],serverFileId:null,clips:[],sections:[],midiItems:[],isArmed:false,monitoringEnabled:true,inputSource:{deviceType:'NONE',deviceId:''}};updatedTracks.push(targetTrack);}const itemStartTimeSec=currentTime;const newMidiItem={id:'item_ai_'+Date.now()+'_'+Math.random().toString(36).substr(2,5),name:`${composition_title||'AI Theme'} - ${aiTrack.track_name}`,startTime:itemStartTimeSec,duration:durationSec,notes:aiTrack.notes.map((note,index)=>({id:`note_ai_${Date.now()}_${index}`,pitch:note.pitch,start_beat:note.start_beat,duration_beats:note.duration_beats,velocity:note.velocity||0.8,pan:0.0}))};targetTrack.midiItems=[...(targetTrack.midiItems||[]),newMidiItem];});return updatedTracks;});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);},[tracks,selectedTrackId,currentTime,bpm]);// ── Save AI config to localStorage ── useEffect(()=>{localStorage.setItem('ai_base_url',aiConfig.baseUrl);localStorage.setItem('ai_api_key',aiConfig.apiKey);localStorage.setItem('ai_model',aiConfig.model);},[aiConfig]);// ── Auto-save user preferences (panel state, provider) ── -const prefsRef=useRef({});prefsRef.current={showAIPanel,showExportPanel,showSelectionPanel,showPythonToolsPanel,showMediaExplorer,showFxRack,showMidiEvents,panelPositions,rightSidebarWidth,mediaExplorerHeight,selectedProviderId};useEffect(()=>{const prefs=prefsRef.current;localStorage.setItem('sonic_preferences',JSON.stringify(prefs));if(!currentUser||currentUser==='cached')return;const timer=setTimeout(async()=>{try{await window.SonicAPI.savePreferences(prefs);}catch(e){}},2000);return()=>clearTimeout(timer);},[showAIPanel,showExportPanel,showSelectionPanel,showPythonToolsPanel,showMediaExplorer,showFxRack,showMidiEvents,panelPositions,rightSidebarWidth,mediaExplorerHeight,selectedProviderId,currentUser]);return/*#__PURE__*/React.createElement(React.Fragment,null,/*#__PURE__*/React.createElement("div",{className:"h-full w-full flex flex-col bg-[#1e1e1e]"},/*#__PURE__*/React.createElement("header",{className:"h-9 bg-[#2e2e2e] border-b border-[#181818] flex items-center px-1 shrink-0 select-none"},[{label:'File',items:[{label:'New Project',icon:'file-plus',shortcut:'Ctrl+N',action:()=>{setTracks([{id:'1',name:'Track 01',buffer:null,startTime:0,volumeDb:0,pan:0,muted:false,solo:false,color:'#0f766e',markers:[],serverFileId:null,clips:[],sections:[],midiItems:[]},{id:'2',name:'Track 02',buffer:null,startTime:0,volumeDb:0,pan:0,muted:false,solo:false,color:'#1d4ed8',markers:[],serverFileId:null,clips:[],sections:[],midiItems:[]}]);setSelectedTrackId('1');setProjectName('');setCurrentProjectId(null);localStorage.removeItem('sonic_project_name');localStorage.removeItem('sonic_project_id');showToast('New project created','info');}},{label:'Open Project...',icon:'folder-open',shortcut:'Ctrl+O',action:()=>handleImportSFS()},{label:'Save Project',icon:'upload-cloud',shortcut:'Ctrl+S',action:()=>handleSaveProject()},{label:'Save As...',icon:'download',shortcut:'Ctrl+Alt+S',action:()=>setSaveAsModalOpen(true)},{sep:true},{label:'Config AI Providers...',icon:'settings',action:()=>setAiConfigModalOpen(true)},{label:'Import Audio...',icon:'file-input',shortcut:'Ctrl+Alt+I',action:()=>{const input=document.createElement('input');input.type='file';input.accept='audio/*';input.onchange=async e=>{if(e.target.files[0]){addNewTrack();const newId=(tracks.length+1).toString();setTimeout(()=>loadFileOnTrack(newId,e.target.files[0]),100);}};input.click();showToast('Import audio','info');}},{label:'Export Mix...',icon:'file-output',action:()=>triggerWavExport()},{label:'Export MIDI...',icon:'music',action:()=>triggerMidiExport()},{sep:true},...(currentUser?[{label:'Profile',icon:'user',action:()=>setProfileModalOpen(true)}]:[]),...(currentUser&¤tUser.role==='admin'?[{label:'System Manager',icon:'settings',action:()=>setSystemManagerModalOpen(true)}]:[]),{label:'Logout',icon:'log-out',action:()=>handleLogout()}]},{label:'Edit',items:[{label:'Insert New Track',icon:'plus',shortcut:'Ctrl+I',action:addNewTrack},{label:'Insert Music to Track',icon:'music',shortcut:'Ctrl+Alt+I',action:()=>showToast('Select music file to insert','info')},{sep:true},{label:'Edit in New Tab',icon:'file-edit',shortcut:'Ctrl+E',action:()=>openTempTab()},{label:'Split at Playhead',icon:'scissors',shortcut:'S',action:()=>handleSplitTrack(selectedTrackId)},{label:'Merge Tracks',icon:'combine',shortcut:'Ctrl+M',action:()=>{handleMergeTracks();}},{sep:true},{label:'Undo',icon:'undo',shortcut:'Ctrl+Z',action:()=>{handleUndo();}},{label:'Redo',icon:'redo',shortcut:'Ctrl+Y',action:()=>{handleRedo();}},{sep:true},{label:'Copy',icon:'copy',shortcut:'Ctrl+C',action:()=>{handleCopyTrack();}},{label:'Cut',icon:'scissors',shortcut:'Ctrl+X',action:()=>{handleCutTrack();}},{label:'Paste',icon:'clipboard',shortcut:'Ctrl+V',action:handlePasteTrack},{sep:true},{label:'Delete Track',icon:'trash-2',shortcut:'Del',action:()=>{handleDeleteTrack();}}]},{label:'Insert',items:[...(!sessionTabs.some(s=>s.id===activeTab)?[{label:'Insert Section',icon:'folder-plus',action:insertSectionAtPlayhead}]:[]),{label:'Insert MIDI item',icon:'music',action:insertMidiItemAtPlayhead},{label:'Insert sound clip',icon:'file-input',action:insertSoundClipAtCursor},{label:'Insert track',icon:'plus',action:insertTrackBelow}]},{label:'View',items:[{label:'Master Track',icon:'disc',action:()=>showToast('Master track view','info')},{label:'Maker View',icon:'layout',action:()=>showToast('Maker view','info')},{label:'Mixer',icon:'sliders',action:()=>showToast('Mixer panel','info')},{label:'Tempo Track',icon:'timer',action:()=>showToast('Tempo track','info')},{label:'Video',icon:'film',action:()=>showToast('Video panel','info')},{label:'Media Explorer',icon:'folder-search',action:()=>showToast('Media explorer','info')}]},{label:'Tools',items:[{label:'Config AI Providers...',icon:'settings',action:()=>setAiConfigModalOpen(true)},{label:'AI MIDI Preset Manager...',icon:'sliders',action:()=>setAiPresetModalOpen(true)},{label:'DSP Tools Panel',icon:'wrench',action:()=>openPanel('python_tools')},{sep:true},{label:'Plugin Manager (SoundFont/VSTi)',icon:'zap',action:()=>{setPluginManagerModalOpen(true);window.SonicAPI.listPlugins().then(data=>setPluginsData(data)).catch(()=>{});}}]},{label:'Help',items:[{label:'About SonicForge',icon:'info',action:()=>showToast('SonicForge Studio v1.0 - Professional DAW','info')}]}].map(menu=>/*#__PURE__*/React.createElement("div",{key:menu.label,className:"relative"},/*#__PURE__*/React.createElement("button",{onClick:()=>setMenuOpen(menuOpen===menu.label?null:menu.label),className:`px-3 py-1 text-xs font-medium transition rounded ${menuOpen===menu.label?'bg-zinc-700 text-zinc-100':'text-zinc-400 hover:text-zinc-200 hover:bg-zinc-800'}`},menu.label),menuOpen===menu.label&&/*#__PURE__*/React.createElement("div",{className:`absolute top-full left-0 mt-0.5 bg-[#2a2a2a] border border-zinc-700 rounded-lg shadow-2xl py-1 z-50 ${menu.label==='Edit'?'w-72':'w-64'}`,onClick:()=>setMenuOpen(null)},menu.items.map((item,i)=>item.sep?/*#__PURE__*/React.createElement("div",{key:i,className:"h-px bg-zinc-700 my-1"}):/*#__PURE__*/React.createElement("button",{key:item.label,onClick:e=>{e.stopPropagation();item.action();setMenuOpen(null);},className:"w-full px-3 py-1.5 text-xs text-zinc-200 hover:bg-zinc-700 text-left flex items-center gap-2"},/*#__PURE__*/React.createElement("span",{className:"inline-flex items-center shrink-0"},/*#__PURE__*/React.createElement("i",{"data-lucide":item.icon,className:"w-3.5 h-3.5 text-zinc-500 shrink-0"})),/*#__PURE__*/React.createElement("span",{className:"flex-1"},item.label),item.shortcut&&/*#__PURE__*/React.createElement("span",{className:"text-purple-400 text-xs font-semibold font-mono ml-auto pl-8"},item.shortcut)))))),/*#__PURE__*/React.createElement("div",{className:"flex-1"}),/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-2 px-2"},/*#__PURE__*/React.createElement("span",{className:`text-xs font-bold uppercase px-1.5 py-0.5 rounded ${serverStatus==='connected'?'bg-emerald-950 text-emerald-400':serverStatus==='checking'?'bg-amber-950 text-amber-400':'bg-red-950 text-red-400'}`},"Server: ",serverStatus),/*#__PURE__*/React.createElement("button",{onClick:()=>setShowAIConfig(!showAIConfig),className:`px-1.5 py-0.5 rounded text-xs border transition ${showAIConfig?'bg-purple-900 text-purple-200 border-purple-700':'bg-zinc-800 text-zinc-400 border-zinc-700 hover:text-zinc-200'}`},/*#__PURE__*/React.createElement("span",{className:"inline-flex items-center shrink-0"},/*#__PURE__*/React.createElement("i",{"data-lucide":"cpu",className:"w-3 h-3"}))))),menuOpen&&/*#__PURE__*/React.createElement("div",{className:"fixed inset-0 z-40",onClick:()=>setMenuOpen(null)}),/*#__PURE__*/React.createElement("div",{className:"h-7 bg-[#222] border-b border-zinc-800 flex items-stretch px-2 gap-0.5 shrink-0 overflow-x-auto"},/*#__PURE__*/React.createElement("button",{onClick:()=>setActiveTab('main'),className:`px-3 text-xs font-bold uppercase tracking-wider border-b-2 transition flex items-center gap-1 ${activeTab==='main'?'text-cyan-400 border-cyan-500 bg-zinc-800/50':'text-zinc-500 border-transparent hover:text-zinc-300 hover:bg-zinc-800/30'}`},/*#__PURE__*/React.createElement("span",{className:"inline-flex items-center shrink-0"},/*#__PURE__*/React.createElement("i",{"data-lucide":"layout-dashboard",className:"w-3 h-3"}))," Main Session"),sessionTabs.map(st=>/*#__PURE__*/React.createElement("div",{key:st.id,className:"flex items-stretch"},/*#__PURE__*/React.createElement("button",{onClick:()=>setActiveTab(st.id),className:`px-2 text-xs font-medium border-b-2 transition flex items-center gap-1 ${activeTab===st.id?'text-cyan-400 border-cyan-500 bg-zinc-800/50':'text-zinc-500 border-transparent hover:text-zinc-300 hover:bg-zinc-800/30'}`},/*#__PURE__*/React.createElement("span",{className:"inline-flex items-center shrink-0"},/*#__PURE__*/React.createElement("i",{"data-lucide":"layout-dashboard",className:"w-3 h-3"})),/*#__PURE__*/React.createElement("span",{className:"max-w-[120px] truncate"},st.name)),/*#__PURE__*/React.createElement("button",{onClick:()=>closeSessionTab(st.id),className:"px-1 text-zinc-600 hover:text-red-400 transition text-xs",title:"Close tab"},/*#__PURE__*/React.createElement("span",{className:"inline-flex items-center shrink-0"},/*#__PURE__*/React.createElement("i",{"data-lucide":"x",className:"w-3 h-3"}))))),subTabs.map(st=>/*#__PURE__*/React.createElement("div",{key:st.id,className:"flex items-stretch"},/*#__PURE__*/React.createElement("button",{onClick:()=>setActiveTab(st.id),className:`px-2 text-xs font-medium border-b-2 transition flex items-center gap-1 ${activeTab===st.id?'text-amber-400 border-amber-500 bg-zinc-800/50':'text-zinc-500 border-transparent hover:text-zinc-300 hover:bg-zinc-800/30'}`},/*#__PURE__*/React.createElement("span",{className:"inline-flex items-center shrink-0"},/*#__PURE__*/React.createElement("i",{"data-lucide":"file-edit",className:"w-3 h-3"})),/*#__PURE__*/React.createElement("span",{className:"max-w-[100px] truncate"},st.label)),/*#__PURE__*/React.createElement("button",{onClick:()=>closeSubTab(st.id),className:"px-1 text-zinc-600 hover:text-red-400 transition text-xs",title:"Close tab"},/*#__PURE__*/React.createElement("span",{className:"inline-flex items-center shrink-0"},/*#__PURE__*/React.createElement("i",{"data-lucide":"x",className:"w-3 h-3"})))))),showAIConfig&&/*#__PURE__*/React.createElement("div",{className:"bg-zinc-900 border-b border-purple-900 p-3 flex flex-col gap-2 transition-all"},/*#__PURE__*/React.createElement("div",{className:"text-xs font-bold text-purple-400 uppercase tracking-wider flex items-center gap-1"},/*#__PURE__*/React.createElement("span",{className:"inline-flex items-center shrink-0"},/*#__PURE__*/React.createElement("i",{"data-lucide":"cpu",className:"w-4 h-4"}))," Cấu hình cổng kết nối API"),/*#__PURE__*/React.createElement("div",{className:"grid grid-cols-1 md:grid-cols-3 gap-2 text-xs"},/*#__PURE__*/React.createElement("div",{className:"flex flex-col gap-1"},/*#__PURE__*/React.createElement("span",{className:"text-xs text-zinc-500 font-bold uppercase"},"Endpoint Base URL"),/*#__PURE__*/React.createElement("input",{type:"text",value:aiConfig.baseUrl,onChange:e=>setAiConfig(prev=>({...prev,baseUrl:e.target.value})),className:"bg-zinc-850 text-zinc-200 p-1.5 rounded border border-zinc-800 focus:outline-none focus:border-purple-600 font-mono text-xs",placeholder:"https://api.openai.com/v1"})),/*#__PURE__*/React.createElement("div",{className:"flex flex-col gap-1"},/*#__PURE__*/React.createElement("span",{className:"text-xs text-zinc-500 font-bold uppercase"},"API Token Key"),/*#__PURE__*/React.createElement("input",{type:"password",value:aiConfig.apiKey,onChange:e=>setAiConfig(prev=>({...prev,apiKey:e.target.value})),className:"bg-zinc-850 text-zinc-200 p-1.5 rounded border border-zinc-800 focus:outline-none focus:border-purple-600 font-mono text-xs",placeholder:"sk-..."})),/*#__PURE__*/React.createElement("div",{className:"flex flex-col gap-1"},/*#__PURE__*/React.createElement("span",{className:"text-xs text-zinc-500 font-bold uppercase"},"Model Name"),/*#__PURE__*/React.createElement("input",{type:"text",value:aiConfig.model,onChange:e=>setAiConfig(prev=>({...prev,model:e.target.value})),className:"bg-zinc-850 text-zinc-200 p-1.5 rounded border border-zinc-800 focus:outline-none focus:border-purple-600 font-mono text-xs",placeholder:"gpt-4o-mini"})))),/*#__PURE__*/React.createElement("div",{className:"h-9 bg-[#222] border-b border-zinc-800 flex items-center px-3 gap-2 shrink-0 select-none"},/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-1 bg-zinc-900 border border-zinc-700 rounded px-1.5 py-0.5 shadow-lg",title:"Kéo để di chuyển toolbar",style:{cursor:'grab'}},/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-0.5 mr-1 text-zinc-600"},/*#__PURE__*/React.createElement("span",{className:"inline-flex items-center shrink-0"},/*#__PURE__*/React.createElement("i",{"data-lucide":"grip-vertical",className:"w-3 h-3"}))),/*#__PURE__*/React.createElement("button",{onClick:()=>{setActiveTool('select');showToast('Select Tool','info');},className:`w-7 h-7 flex items-center justify-center rounded ${activeTool==='select'?'bg-cyan-700 text-white':'text-zinc-400 hover:text-zinc-200 hover:bg-zinc-800'}`,title:"Select Tool (V)"},/*#__PURE__*/React.createElement("span",{className:"inline-flex items-center shrink-0"},/*#__PURE__*/React.createElement("i",{"data-lucide":"mouse-pointer",className:"w-3.5 h-3.5"}))),/*#__PURE__*/React.createElement("button",{onClick:()=>{setActiveTool('grab');showToast('Grab Tool','info');},className:`w-7 h-7 flex items-center justify-center rounded ${activeTool==='grab'?'bg-cyan-700 text-white':'text-zinc-400 hover:text-zinc-200 hover:bg-zinc-800'}`,title:"Grab Tool (H)"},/*#__PURE__*/React.createElement("span",{className:"inline-flex items-center shrink-0"},/*#__PURE__*/React.createElement("i",{"data-lucide":"hand",className:"w-3.5 h-3.5"}))),/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-0.5 border border-zinc-700 rounded bg-zinc-850 px-0.5"},/*#__PURE__*/React.createElement("button",{onClick:()=>{setActiveTool('razor');showToast('Razor Tool','info');},className:`w-7 h-7 flex items-center justify-center rounded ${activeTool==='razor'?'bg-cyan-700 text-white':'text-zinc-400 hover:text-zinc-200 hover:bg-zinc-800'}`,title:"Razor Tool (C)"},/*#__PURE__*/React.createElement("svg",{className:"w-3.5 h-3.5 text-orange-400",viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2.5",strokeLinecap:"round",strokeLinejoin:"round"},/*#__PURE__*/React.createElement("path",{d:"M6 3h12a2 2 0 0 1 2 2v2a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2z"}),/*#__PURE__*/React.createElement("path",{d:"M4 9h16l-3 9H7z"}),/*#__PURE__*/React.createElement("circle",{cx:"12",cy:"6",r:"1"}))),/*#__PURE__*/React.createElement("button",{onClick:handleGlueTracks,className:"w-7 h-7 flex items-center justify-center rounded text-zinc-400 hover:text-purple-400 hover:bg-zinc-800",title:"Glue Clips"},/*#__PURE__*/React.createElement("span",{className:"inline-flex items-center shrink-0"},/*#__PURE__*/React.createElement("i",{"data-lucide":"link",className:"w-3.5 h-3.5"})))),/*#__PURE__*/React.createElement("button",{onClick:()=>{setActiveTool('pen');showToast('Pen Tool','info');},className:`w-7 h-7 flex items-center justify-center rounded ${activeTool==='pen'?'bg-cyan-700 text-white':'text-zinc-400 hover:text-zinc-200 hover:bg-zinc-800'}`,title:"Pen Tool (P)"},/*#__PURE__*/React.createElement("span",{className:"inline-flex items-center shrink-0"},/*#__PURE__*/React.createElement("i",{"data-lucide":"pen-tool",className:"w-3.5 h-3.5"}))),/*#__PURE__*/React.createElement("div",{className:"w-[1px] h-5 bg-zinc-700 mx-0.5"}),/*#__PURE__*/React.createElement("button",{onClick:handleCutTrack,className:"w-7 h-7 flex items-center justify-center rounded text-zinc-400 hover:text-red-400 hover:bg-zinc-800",title:"Cut (Ctrl+X)"},/*#__PURE__*/React.createElement("span",{className:"inline-flex items-center shrink-0"},/*#__PURE__*/React.createElement("i",{"data-lucide":"scissors",className:"w-3.5 h-3.5"}))),/*#__PURE__*/React.createElement("button",{onClick:handleCopyTrack,className:"w-7 h-7 flex items-center justify-center rounded text-zinc-400 hover:text-blue-400 hover:bg-zinc-800",title:"Copy (Ctrl+C)"},/*#__PURE__*/React.createElement("span",{className:"inline-flex items-center shrink-0"},/*#__PURE__*/React.createElement("i",{"data-lucide":"copy",className:"w-3.5 h-3.5"}))),/*#__PURE__*/React.createElement("button",{onClick:handlePasteTrack,className:"w-7 h-7 flex items-center justify-center rounded text-zinc-400 hover:text-emerald-400 hover:bg-zinc-800",title:"Paste (Ctrl+V)"},/*#__PURE__*/React.createElement("span",{className:"inline-flex items-center shrink-0"},/*#__PURE__*/React.createElement("i",{"data-lucide":"clipboard",className:"w-3.5 h-3.5"}))),/*#__PURE__*/React.createElement("div",{className:"w-[1px] h-5 bg-zinc-700 mx-0.5"}),/*#__PURE__*/React.createElement("button",{onClick:addNewTrack,className:"px-2 py-1 bg-cyan-600 hover:bg-cyan-500 text-white font-semibold rounded text-xs flex items-center gap-1 shadow transition",title:"Thêm Track Mới (Ctrl+I)"},/*#__PURE__*/React.createElement("span",{className:"inline-flex items-center shrink-0"},/*#__PURE__*/React.createElement("i",{"data-lucide":"plus",className:"w-3.5 h-3.5"})),/*#__PURE__*/React.createElement("span",null,"Track")),sessionTabs.some(s=>s.id===activeTab)&&/*#__PURE__*/React.createElement(React.Fragment,null,/*#__PURE__*/React.createElement("div",{className:"w-[1px] h-5 bg-zinc-700 mx-0.5"}),/*#__PURE__*/React.createElement("button",{onClick:()=>handleSaveSectionTab(activeTab),className:"px-2 py-1 bg-emerald-600 hover:bg-emerald-500 text-white font-semibold rounded text-xs flex items-center gap-1 shadow transition",title:"Lưu Section vào Main Session"},/*#__PURE__*/React.createElement("span",{className:"inline-flex items-center shrink-0"},/*#__PURE__*/React.createElement("i",{"data-lucide":"save",className:"w-3.5 h-3.5"})),/*#__PURE__*/React.createElement("span",null,"Lưu Section"))),/*#__PURE__*/React.createElement("div",{className:"w-[1px] h-5 bg-zinc-700 mx-0.5"}),/*#__PURE__*/React.createElement("button",{onClick:handleUndo,disabled:undoStack.length===0,className:"w-7 h-7 flex items-center justify-center rounded text-zinc-400 hover:text-zinc-200 hover:bg-zinc-800 disabled:opacity-30",title:"Undo (Ctrl+Z)"},/*#__PURE__*/React.createElement("span",{className:"inline-flex items-center shrink-0"},/*#__PURE__*/React.createElement("i",{"data-lucide":"undo",className:"w-3.5 h-3.5"}))),/*#__PURE__*/React.createElement("button",{onClick:handleRedo,disabled:redoStack.length===0,className:"w-7 h-7 flex items-center justify-center rounded text-zinc-400 hover:text-zinc-200 hover:bg-zinc-800 disabled:opacity-30",title:"Redo (Ctrl+Y)"},/*#__PURE__*/React.createElement("span",{className:"inline-flex items-center shrink-0"},/*#__PURE__*/React.createElement("i",{"data-lucide":"redo",className:"w-3.5 h-3.5"})))),/*#__PURE__*/React.createElement("div",{className:"w-[1px] h-5 bg-zinc-800 mx-0.5"}),/*#__PURE__*/React.createElement("button",{onClick:()=>{const isSubTab=subTabs.some(sub=>sub.id===activeTab);if(isSubTab){setSubTabs(prev=>prev.map(s=>s.id===activeTab?{...s,currentTime:0}:s));}else{setCurrentTime(0);}},className:"w-7 h-7 flex items-center justify-center bg-zinc-800 hover:bg-zinc-700 text-zinc-200 rounded border border-zinc-700 transition",title:"Quay lại đầu"},/*#__PURE__*/React.createElement("span",{className:"inline-flex items-center shrink-0"},/*#__PURE__*/React.createElement("i",{"data-lucide":"skip-back",className:"w-3.5 h-3.5"}))),/*#__PURE__*/React.createElement("button",{onClick:()=>{const isSubTab=subTabs.some(sub=>sub.id===activeTab);if(isSubTab){setSubTabs(prev=>prev.map(s=>{if(s.id!==activeTab)return s;const left=s.selectionStart!==null&&s.selectionEnd!==null?Math.min(s.selectionStart,s.selectionEnd):null;return left!==null?{...s,currentTime:left}:s;}));}else{if(selLeft!==null)setCurrentTime(selLeft);}},className:"w-7 h-7 flex items-center justify-center bg-zinc-800 hover:bg-zinc-700 text-zinc-200 rounded border border-zinc-700 transition",title:"Đầu vùng chọn"},/*#__PURE__*/React.createElement("span",{className:"inline-flex items-center shrink-0"},/*#__PURE__*/React.createElement("i",{"data-lucide":"step-back",className:"w-3.5 h-3.5"}))),/*#__PURE__*/React.createElement("button",{onClick:handlePlayPause,className:`w-7 h-7 flex items-center justify-center rounded border transition ${isPlaying?'bg-emerald-600 text-black border-emerald-500 hover:bg-emerald-500':'bg-cyan-600 text-white border-cyan-500 hover:bg-cyan-500'}`,title:isPlaying?"Tạm dừng":"Play"},/*#__PURE__*/React.createElement("span",{className:"inline-flex items-center shrink-0"},/*#__PURE__*/React.createElement("i",{"data-lucide":isPlaying?"pause":"play",className:"w-3.5 h-3.5 fill-current"}))),/*#__PURE__*/React.createElement("button",{onClick:handleStop,className:"w-7 h-7 flex items-center justify-center bg-zinc-800 hover:bg-zinc-700 text-zinc-200 rounded border border-zinc-700 transition",title:"Stop"},/*#__PURE__*/React.createElement("span",{className:"inline-flex items-center shrink-0"},/*#__PURE__*/React.createElement("i",{"data-lucide":"square",className:"w-3.5 h-3.5 fill-current"}))),/*#__PURE__*/React.createElement("button",{onClick:handleRecordClick,className:`w-7 h-7 flex items-center justify-center rounded border transition ${recordingState==='RECORDING'?'bg-red-600 text-white border-red-500 hover:bg-red-500 animate-pulse':recordingState==='COUNT_IN'?'bg-amber-500 text-black border-amber-400 hover:bg-amber-400 animate-pulse':'bg-zinc-800 text-red-500 border-zinc-700 hover:bg-zinc-700 hover:text-red-400'}`,title:recordingState==='RECORDING'?"Đang ghi âm...":recordingState==='COUNT_IN'?"Chuẩn bị ghi âm...":"Ghi âm (Record)"},/*#__PURE__*/React.createElement("span",{className:"inline-flex items-center shrink-0"},/*#__PURE__*/React.createElement("i",{"data-lucide":"circle",className:"w-3.5 h-3.5 fill-current"}))),/*#__PURE__*/React.createElement("button",{onClick:()=>{const isSubTab=subTabs.some(sub=>sub.id===activeTab);if(isSubTab){setSubTabs(prev=>prev.map(s=>{if(s.id!==activeTab)return s;const right=s.selectionStart!==null&&s.selectionEnd!==null?Math.max(s.selectionStart,s.selectionEnd):null;return right!==null?{...s,currentTime:right}:s;}));}else{if(selRight!==null)setCurrentTime(selRight);}},className:"w-7 h-7 flex items-center justify-center bg-zinc-800 hover:bg-zinc-700 text-zinc-200 rounded border border-zinc-700 transition",title:"Cuối vùng chọn"},/*#__PURE__*/React.createElement("span",{className:"inline-flex items-center shrink-0"},/*#__PURE__*/React.createElement("i",{"data-lucide":"step-forward",className:"w-3.5 h-3.5"}))),/*#__PURE__*/React.createElement("button",{onClick:()=>{const isSubTab=subTabs.some(sub=>sub.id===activeTab);if(isSubTab){setSubTabs(prev=>prev.map(s=>{if(s.id!==activeTab)return s;const duration=s.buffer?s.buffer.duration/(s.speed||1.0):0;return{...s,currentTime:duration};}));}else{setCurrentTime(maxDuration);}},className:"w-7 h-7 flex items-center justify-center bg-zinc-800 hover:bg-zinc-700 text-zinc-200 rounded border border-zinc-700 transition",title:"Đến cuối"},/*#__PURE__*/React.createElement("span",{className:"inline-flex items-center shrink-0"},/*#__PURE__*/React.createElement("i",{"data-lucide":"skip-forward",className:"w-3.5 h-3.5"}))),/*#__PURE__*/React.createElement("div",{className:"w-[1px] h-5 bg-zinc-800 mx-0.5"}),/*#__PURE__*/React.createElement("button",{onClick:()=>setIsLoopingSelection(prev=>!prev),className:`w-7 h-7 flex items-center justify-center rounded border transition ${isLoopingSelection?'bg-amber-600 text-black border-amber-500 hover:bg-amber-500':'bg-zinc-800 text-zinc-200 border-zinc-700 hover:bg-zinc-700'}`,title:isLoopingSelection?selLeft!==null&&selRight!==null?"Loop vùng chọn":"Loop timeline":"Bật loop"},/*#__PURE__*/React.createElement("span",{className:"inline-flex items-center shrink-0"},/*#__PURE__*/React.createElement("i",{"data-lucide":"repeat",className:"w-3.5 h-3.5"}))),/*#__PURE__*/React.createElement("span",{className:"text-[14px] text-zinc-500 font-bold uppercase ml-2"},"Snap"),/*#__PURE__*/React.createElement("select",{value:snapValue,onChange:e=>setSnapValue(e.target.value),className:"bg-black text-white text-[14px] px-1.5 py-0.5 rounded border border-zinc-700 focus:outline-none focus:border-cyan-500 font-mono cursor-pointer"},/*#__PURE__*/React.createElement("option",{value:"free"},"Free"),/*#__PURE__*/React.createElement("option",{value:"1"},"1"),/*#__PURE__*/React.createElement("option",{value:"1/2"},"1/2"),/*#__PURE__*/React.createElement("option",{value:"1/4"},"1/4"),/*#__PURE__*/React.createElement("option",{value:"1/8"},"1/8"),/*#__PURE__*/React.createElement("option",{value:"1/16"},"1/16"),/*#__PURE__*/React.createElement("option",{value:"1/32"},"1/32")),/*#__PURE__*/React.createElement("div",{className:"w-[1px] h-6 bg-zinc-800 mx-1.5"}),/*#__PURE__*/React.createElement("span",{className:"text-[14px] text-zinc-500 font-bold"},"Bars:"),/*#__PURE__*/React.createElement("input",{type:"number",min:"1",value:beginBar,onChange:e=>{const b=parseInt(e.target.value)||1;setBeginBar(b);const beatDuration=60/parseInt(bpm||120);const t=(b-1)*beatDuration*4;clearLocalSelection();setSelectionMode('global');setSelectionStart(t);setSelectionEnd(t+beatDuration*4);},className:"w-14 bg-black text-white text-[14px] px-1 py-0.5 rounded border border-zinc-700 text-center font-mono"}),/*#__PURE__*/React.createElement("span",{className:"text-[14px] text-zinc-500"},"-"),/*#__PURE__*/React.createElement("input",{type:"number",min:"1",value:endBar,onChange:e=>{const b=parseInt(e.target.value)||1;setEndBar(b);const beatDuration=60/parseInt(bpm||120);const t=(b-1)*beatDuration*4;setSelectionEnd(t+beatDuration*4);setNumberBar(b-beginBar+1);},className:"w-14 bg-black text-white text-[14px] px-1 py-0.5 rounded border border-zinc-700 text-center font-mono"}),/*#__PURE__*/React.createElement("span",{className:"text-[14px] text-zinc-500"},"#"),/*#__PURE__*/React.createElement("input",{type:"number",min:"1",value:numberBar,readOnly:true,className:"w-14 bg-black text-zinc-400 text-[14px] px-1 py-0.5 rounded border border-zinc-800 text-center font-mono"}),/*#__PURE__*/React.createElement("div",{className:"w-[1px] h-6 bg-zinc-800 mx-1.5"}),/*#__PURE__*/React.createElement("span",{className:"text-[14px] text-zinc-500"},"Start:"),/*#__PURE__*/React.createElement("input",{type:"text",value:selLeft!==null?formatTime(selLeft):'',onChange:e=>{const parts=e.target.value.split(/[:.]/);if(parts.length===3){const secs=parseInt(parts[0])*60+parseInt(parts[1])+parseFloat('0.'+(parts[2]||'0'));clearLocalSelection();setSelectionMode('global');setSelectionStart(secs);}},className:"w-20 bg-black text-zinc-200 text-[14px] px-1 py-0.5 rounded border border-zinc-700 text-center font-mono"}),/*#__PURE__*/React.createElement("span",{className:"text-[14px] text-zinc-500"},"End:"),/*#__PURE__*/React.createElement("input",{type:"text",value:selRight!==null?formatTime(selRight):'',onChange:e=>{const parts=e.target.value.split(/[:.]/);if(parts.length===3){const secs=parseInt(parts[0])*60+parseInt(parts[1])+parseFloat('0.'+(parts[2]||'0'));setSelectionEnd(secs);}},className:"w-20 bg-black text-zinc-200 text-[14px] px-1 py-0.5 rounded border border-zinc-700 text-center font-mono"}),/*#__PURE__*/React.createElement("span",{className:"text-[14px] text-zinc-500"},"Len:"),/*#__PURE__*/React.createElement("input",{type:"text",value:selLeft!==null&&selRight!==null?formatTime(Math.abs(selRight-selLeft)):'',onChange:e=>{const parts=e.target.value.split(/[:.]/);if(parts.length===3&&selLeft!==null){const secs=parseInt(parts[0])*60+parseInt(parts[1])+parseFloat('0.'+(parts[2]||'0'));setSelectionEnd(selLeft+secs);}},className:"w-20 bg-black text-amber-300 text-[14px] px-1 py-0.5 rounded border border-zinc-700 text-center font-mono"}),/*#__PURE__*/React.createElement("div",{className:"flex-1"}),/*#__PURE__*/React.createElement("div",{className:"text-xs font-bold font-mono text-zinc-100"},formatTime(currentTime))),(()=>{const dockPanels={top:[],right:[],bottom:[],left:[]};const addPanel=(id,pos,visible)=>{if(visible)dockPanels[pos].push(id);};addPanel('export',panelPositions.export,showExportPanel);addPanel('ai',panelPositions.ai,showAIPanel);addPanel('python_tools',panelPositions.python_tools||'bottom',showPythonToolsPanel);addPanel('selection',panelPositions.selection,showSelectionPanel);addPanel('media_explorer','bottom',showMediaExplorer);addPanel('fx_rack',panelPositions.fx_rack||'bottom',showFxRack);addPanel('midi_events',panelPositions.midi_events||'bottom',showMidiEvents);const closePanel=id=>{if(id==='export')setShowExportPanel(false);else if(id==='ai')setShowAIPanel(false);else if(id==='python_tools')setShowPythonToolsPanel(false);else if(id==='selection')setShowSelectionPanel(false);else if(id==='media_explorer')setShowMediaExplorer(false);else if(id==='fx_rack')setShowFxRack(false);else if(id==='midi_events')setShowMidiEvents(false);};const renderPanelContent=panelId=>{const h=id=>e=>{startPanelDrag(id,e);};if(panelId==='export')return/*#__PURE__*/React.createElement("div",{className:"flex flex-col h-full gap-1.5"},/*#__PURE__*/React.createElement("div",{className:"flex items-center justify-between shrink-0 cursor-grab active:cursor-grabbing select-none",onMouseDown:e=>startPanelDrag('export',e)},/*#__PURE__*/React.createElement("h3",{className:"font-bold text-xs text-zinc-200 flex items-center gap-1"},/*#__PURE__*/React.createElement("span",{className:"inline-flex items-center shrink-0"},/*#__PURE__*/React.createElement("i",{"data-lucide":"grip-vertical",className:"w-3 h-3 text-zinc-500"})),/*#__PURE__*/React.createElement("span",{className:"inline-flex items-center shrink-0"},/*#__PURE__*/React.createElement("i",{"data-lucide":"save",className:"w-3.5 h-3.5 text-cyan-400"}))," Export"),/*#__PURE__*/React.createElement("button",{onClick:()=>closePanel('export'),className:"text-zinc-600 hover:text-zinc-300"},/*#__PURE__*/React.createElement("span",{className:"inline-flex items-center shrink-0"},/*#__PURE__*/React.createElement("i",{"data-lucide":"x",className:"w-3 h-3"})))),/*#__PURE__*/React.createElement("div",{className:"grid grid-cols-2 gap-1"},/*#__PURE__*/React.createElement("div",null,/*#__PURE__*/React.createElement("label",{className:"block text-[7px] text-zinc-500 font-bold uppercase mb-0.5"},"Ngu\u1ed3n"),/*#__PURE__*/React.createElement("select",{value:exportSettings.source,onChange:e=>setExportSettings(p=>({...p,source:e.target.value})),className:"w-full bg-[#141414] border border-zinc-800 rounded px-1 py-0.5 text-xs text-zinc-300 focus:outline-none"},/*#__PURE__*/React.createElement("option",{value:"project"},"Project (Mix)"),/*#__PURE__*/React.createElement("option",{value:"track_mix"},"Track Selection"),/*#__PURE__*/React.createElement("option",{value:"active_clip"},"Active Clip"),/*#__PURE__*/React.createElement("option",{value:"clip_selection"},"Clip Selection"))),/*#__PURE__*/React.createElement("div",null,/*#__PURE__*/React.createElement("label",{className:"block text-[7px] text-zinc-500 font-bold uppercase mb-0.5"},"\u0110\u1ecbnh d\u1ea1ng"),/*#__PURE__*/React.createElement("select",{value:exportSettings.format,onChange:e=>setExportSettings(p=>({...p,format:e.target.value,sampleRate:e.target.value==='wav'?'44100':e.target.value==='mp3'?'44100':'44100',bitDepth:e.target.value==='wav'?'16':'16',quality:'44khz'})),className:"w-full bg-[#141414] border border-zinc-800 rounded px-1 py-0.5 text-xs text-zinc-300 focus:outline-none"},/*#__PURE__*/React.createElement("option",{value:"wav"},"WAV"),/*#__PURE__*/React.createElement("option",{value:"mp3"},"MP3"),/*#__PURE__*/React.createElement("option",{value:"ogg"},"OGG")))),exportSettings.format==='wav'?/*#__PURE__*/React.createElement("div",{className:"grid grid-cols-2 gap-1"},/*#__PURE__*/React.createElement("div",null,/*#__PURE__*/React.createElement("label",{className:"block text-[7px] text-zinc-500 font-bold uppercase mb-0.5"},"SR (Hz)"),/*#__PURE__*/React.createElement("select",{value:exportSettings.sampleRate,onChange:e=>setExportSettings(p=>({...p,sampleRate:e.target.value})),className:"w-full bg-[#141414] border border-zinc-800 rounded px-1 py-0.5 text-xs text-zinc-300 focus:outline-none"},/*#__PURE__*/React.createElement("option",{value:"22500"},"22500"),/*#__PURE__*/React.createElement("option",{value:"44100"},"44100"))),/*#__PURE__*/React.createElement("div",null,/*#__PURE__*/React.createElement("label",{className:"block text-[7px] text-zinc-500 font-bold uppercase mb-0.5"},"Bit"),/*#__PURE__*/React.createElement("select",{value:exportSettings.bitDepth,onChange:e=>setExportSettings(p=>({...p,bitDepth:e.target.value})),className:"w-full bg-[#141414] border border-zinc-800 rounded px-1 py-0.5 text-xs text-zinc-300 focus:outline-none"},/*#__PURE__*/React.createElement("option",{value:"8"},"8"),/*#__PURE__*/React.createElement("option",{value:"16"},"16"),/*#__PURE__*/React.createElement("option",{value:"24"},"24")))):/*#__PURE__*/React.createElement("div",{className:"grid grid-cols-2 gap-1"},/*#__PURE__*/React.createElement("div",null,/*#__PURE__*/React.createElement("label",{className:"block text-[7px] text-zinc-500 font-bold uppercase mb-0.5"},"Ch\u1ea5t l\u01b0\u1ee3ng"),/*#__PURE__*/React.createElement("select",{value:exportSettings.quality,onChange:e=>setExportSettings(p=>({...p,quality:e.target.value})),className:"w-full bg-[#141414] border border-zinc-800 rounded px-1 py-0.5 text-xs text-zinc-300 focus:outline-none"},/*#__PURE__*/React.createElement("option",{value:"44khz"},"44kHz"),/*#__PURE__*/React.createElement("option",{value:"lossless"},"Lossless"))),/*#__PURE__*/React.createElement("div",null)),/*#__PURE__*/React.createElement("div",{className:"grid grid-cols-2 gap-1"},/*#__PURE__*/React.createElement("div",null,/*#__PURE__*/React.createElement("label",{className:"block text-[7px] text-zinc-500 font-bold uppercase mb-0.5"},"Kênh"),/*#__PURE__*/React.createElement("select",{value:exportSettings.channels,onChange:e=>setExportSettings(p=>({...p,channels:e.target.value})),className:"w-full bg-[#141414] border border-zinc-800 rounded px-1 py-0.5 text-xs text-zinc-300 focus:outline-none"},/*#__PURE__*/React.createElement("option",{value:"mono"},"Mono"),/*#__PURE__*/React.createElement("option",{value:"stereo"},"Stereo"))),/*#__PURE__*/React.createElement("div",null)),/*#__PURE__*/React.createElement("button",{onClick:triggerWavExport,disabled:isExporting,className:"w-full py-1 bg-cyan-700 hover:bg-cyan-600 text-zinc-100 font-bold rounded text-xs flex items-center justify-center gap-1"},/*#__PURE__*/React.createElement("span",{className:"inline-flex items-center shrink-0"},/*#__PURE__*/React.createElement("i",{"data-lucide":"download-cloud",className:"w-3 h-3"})),isExporting?'...':'Export'));if(panelId==='ai')return/*#__PURE__*/React.createElement("div",{className:"flex flex-col gap-1.5 flex-1 min-h-0"},/*#__PURE__*/React.createElement("div",{className:"flex items-center justify-between shrink-0 cursor-grab active:cursor-grabbing select-none",onMouseDown:e=>startPanelDrag('ai',e)},/*#__PURE__*/React.createElement("h3",{className:"font-bold text-xs text-zinc-200 flex items-center gap-1"},/*#__PURE__*/React.createElement("span",{className:"inline-flex items-center shrink-0"},/*#__PURE__*/React.createElement("i",{"data-lucide":"grip-vertical",className:"w-3 h-3 text-zinc-500"})),/*#__PURE__*/React.createElement("span",{className:"inline-flex items-center shrink-0"},/*#__PURE__*/React.createElement("i",{"data-lucide":"cpu",className:"w-3.5 h-3.5 text-purple-400"}))," AI Copilot"),/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-1"},/*#__PURE__*/React.createElement("button",{onClick:()=>setAiPresetModalOpen(true),className:"text-zinc-600 hover:text-zinc-300 mr-0.5",title:"Preset Manager"},/*#__PURE__*/React.createElement("span",{className:"inline-flex items-center shrink-0"},/*#__PURE__*/React.createElement("i",{"data-lucide":"sliders",className:"w-3 h-3"}))),/*#__PURE__*/React.createElement("button",{onClick:()=>{setAiActionLog([]);showToast('Đã xoá nhật ký AI.','info');},className:"text-zinc-600 hover:text-zinc-300",title:"Clear log"},/*#__PURE__*/React.createElement("span",{className:"inline-flex items-center shrink-0"},/*#__PURE__*/React.createElement("i",{"data-lucide":"trash-2",className:"w-3 h-3"}))),/*#__PURE__*/React.createElement("button",{onClick:()=>closePanel('ai'),className:"text-zinc-600 hover:text-zinc-300"},/*#__PURE__*/React.createElement("span",{className:"inline-flex items-center shrink-0"},/*#__PURE__*/React.createElement("i",{"data-lucide":"x",className:"w-3 h-3"}))))),/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-1.5 w-full min-w-0 pb-1"},/*#__PURE__*/React.createElement("span",{className:"inline-flex items-center shrink-0"},/*#__PURE__*/React.createElement("i",{"data-lucide":"cpu",className:"w-3 h-3 text-purple-400"})),/*#__PURE__*/React.createElement("select",{value:selectedProviderId,onChange:e=>setSelectedProviderId(e.target.value),className:"flex-1 min-w-0 max-w-full bg-[#141414] text-zinc-200 border border-zinc-700 rounded px-1 py-0.5 text-xs font-mono focus:outline-none focus:border-purple-600 truncate"},aiProviders.length===0?/*#__PURE__*/React.createElement("option",{value:""},"Chưa có provider"):aiProviders.map(p=>/*#__PURE__*/React.createElement("option",{key:p.id,value:p.id},p.name,p.is_active?'':' (inactive)')))),/*#__PURE__*/React.createElement("div",{className:"border-t border-zinc-800 pt-1 mt-1 shrink-0"},/*#__PURE__*/React.createElement("button",{onClick:()=>{if(window.DAWCommandDispatcher&&window.DAWCommandDispatcher.undo){const entry=window.DAWCommandDispatcher.undo();if(entry){setAiActionLog(prev=>[...prev,{type:'undo',text:`Undo: ${entry.name}`,time:Date.now()}]);showToast(`Undo AI: ${entry.name}`,'info');}}else{handleUndo();setAiActionLog(prev=>[...prev,{type:'undo',text:'Undo (Ctrl+Z)',time:Date.now()}]);}},className:"w-full text-[10px] text-amber-400 hover:text-amber-300 border border-amber-800 rounded py-0.5"},/*#__PURE__*/React.createElement("span",{className:"inline-flex items-center justify-center gap-1"},/*#__PURE__*/React.createElement("i",{"data-lucide":"rotate-ccw",className:"w-3 h-3"}),"Undo"))),/*#__PURE__*/React.createElement("div",{className:"flex-1 min-h-0 flex flex-col overflow-hidden mt-1"},/*#__PURE__*/React.createElement("div",{className:"text-[10px] font-bold text-zinc-400 uppercase shrink-0 pb-0.5"},/*#__PURE__*/React.createElement("span",{className:"inline-flex items-center gap-1"},/*#__PURE__*/React.createElement("i",{"data-lucide":"list",className:"w-3 h-3"})," Action Log")),/*#__PURE__*/React.createElement("div",{ref:actionLogContainerRef,className:"flex-1 overflow-y-auto no-scrollbar bg-[#0f0f0f] rounded border border-zinc-800 select-text"},aiActionLog.length===0?/*#__PURE__*/React.createElement("div",{className:"text-xs text-zinc-600 italic select-text"},"Chưa có hành động nào."):aiActionLog.map((entry,i)=>/*#__PURE__*/React.createElement("div",{key:i,className:`text-xs font-mono py-0.5 border-b border-zinc-900 last:border-0 ${entry.type==='error'?'text-red-400':entry.type==='status'?'text-zinc-400 italic':entry.type==='undo'?'text-amber-400':'text-zinc-300'}`},new Date(entry.time).toLocaleTimeString(),entry.text)))),/*#__PURE__*/React.createElement("div",{className:"border-t border-zinc-800 pt-1.5 mt-1 shrink-0"},/*#__PURE__*/React.createElement("div",{className:"text-xs font-bold text-zinc-400 uppercase mb-1 flex items-center gap-1"},/*#__PURE__*/React.createElement("span",{className:"inline-flex items-center shrink-0"},/*#__PURE__*/React.createElement("i",{"data-lucide":"message-square",className:"w-3 h-3"}))," Copilot Prompt"),/*#__PURE__*/React.createElement("textarea",{value:aiPrompt,onChange:e=>setAiPrompt(e.target.value),placeholder:"Nhập lệnh điều khiển DAW... (VD: Tạo track mới tên Beat, đặt BPM 128)",className:"w-full bg-[#1e1e1e] text-zinc-200 p-1.5 rounded border border-zinc-700 focus:outline-none focus:border-purple-600 font-mono text-xs resize-none",rows:2,onKeyDown:e=>{if(e.key==='Enter'&&!e.shiftKey){e.preventDefault();handleAISend();}else if(e.key==='ArrowUp'&&promptHistRef.current.length>0){e.preventDefault();const idx=promptHistIdx===-1?promptHistRef.current.length-1:Math.max(0,promptHistIdx-1);setPromptHistIdx(idx);setAiPrompt(promptHistRef.current[idx]);}else if(e.key==='ArrowDown'){e.preventDefault();if(promptHistIdx===-1)return;const idx=promptHistIdx+1;if(idx>=promptHistRef.current.length){setPromptHistIdx(-1);setAiPrompt('');}else{setPromptHistIdx(idx);setAiPrompt(promptHistRef.current[idx]);}}}})),/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-1 mt-1 shrink-0"},/*#__PURE__*/React.createElement("button",{onClick:handleAISend,disabled:aiProcessing,className:"flex-1 py-1 bg-purple-700 hover:bg-purple-600 text-white font-bold rounded text-xs border border-purple-500 flex items-center justify-center gap-1"},aiProcessing?'Đang suy luận...':/*#__PURE__*/React.createElement(React.Fragment,null,/*#__PURE__*/React.createElement("span",{className:"inline-flex items-center shrink-0"},/*#__PURE__*/React.createElement("i",{"data-lucide":"send",className:"w-3 h-3"}))," Gửi")),/*#__PURE__*/React.createElement("button",{onClick:()=>{setAiPrompt('');setAiActionLog([]);},className:"px-2 py-1 bg-zinc-800 hover:bg-zinc-700 text-zinc-400 rounded text-xs border border-zinc-700"},"Clear")),/*#__PURE__*/React.createElement("div",{className:"text-xs text-zinc-600 shrink-0"},"Enter để gửi nhanh"));if(panelId==='python_tools')return/*#__PURE__*/React.createElement("div",{className:"flex flex-col h-full gap-1.5"},/*#__PURE__*/React.createElement("div",{className:"flex items-center justify-between shrink-0 cursor-grab active:cursor-grabbing select-none",onMouseDown:e=>startPanelDrag('python_tools',e)},/*#__PURE__*/React.createElement("h3",{className:"font-bold text-xs text-amber-300 flex items-center gap-1"},/*#__PURE__*/React.createElement("span",{className:"inline-flex items-center shrink-0"},/*#__PURE__*/React.createElement("i",{"data-lucide":"grip-vertical",className:"w-3 h-3 text-zinc-500"})),/*#__PURE__*/React.createElement("span",{className:"inline-flex items-center shrink-0"},/*#__PURE__*/React.createElement("i",{"data-lucide":"wrench",className:"w-3.5 h-3.5 text-amber-400"}))," DSP Tools"),/*#__PURE__*/React.createElement("button",{onClick:()=>closePanel('python_tools'),className:"text-zinc-600 hover:text-zinc-300"},/*#__PURE__*/React.createElement("span",{className:"inline-flex items-center shrink-0"},/*#__PURE__*/React.createElement("i",{"data-lucide":"x",className:"w-3 h-3"})))),/*#__PURE__*/React.createElement("div",{className:"p-1.5 bg-[#141414] rounded border border-zinc-800 text-[10px] font-mono text-zinc-400 space-y-0.5 leading-relaxed"},dspSelectionStats?[/*#__PURE__*/React.createElement("div",{key:"track"},`Track: ${dspSelectionStats.trackName}`),/*#__PURE__*/React.createElement("div",{key:"range"},`Range: ${dspSelectionStats.timeRange}`),/*#__PURE__*/React.createElement("div",{key:"ch"},`Channels: ${dspSelectionStats.channels}`),/*#__PURE__*/React.createElement("div",{key:"peak"},`Peak Vol: ${dspSelectionStats.peakVolume}`)]:"Chưa chọn track"),/*#__PURE__*/React.createElement("div",{className:"grid grid-cols-2 gap-1 text-xs"},/*#__PURE__*/React.createElement("button",{onClick:()=>runPythonTool('normalize'),className:"py-1 bg-amber-950/80 hover:bg-amber-900 text-amber-300 border border-amber-800/80 rounded font-bold flex items-center justify-center gap-1"},"⚡ Peak Norm (0dB)"),/*#__PURE__*/React.createElement("button",{onClick:()=>runPythonTool('invert_phase'),className:"py-1 bg-zinc-800 hover:bg-zinc-700 text-zinc-200 border border-zinc-700 rounded font-bold flex items-center justify-center gap-1"},"🔄 Phase Invert"),/*#__PURE__*/React.createElement("button",{onClick:()=>runPythonTool('swap_channels'),className:"py-1 bg-zinc-800 hover:bg-zinc-700 text-zinc-200 border border-zinc-700 rounded font-bold flex items-center justify-center gap-1"},"🔀 Swap L/R"),/*#__PURE__*/React.createElement("button",{onClick:()=>runPythonTool('synth_wave'),className:"py-1 bg-teal-950/80 hover:bg-teal-900 text-teal-300 border border-teal-800/80 rounded font-bold flex items-center justify-center gap-1"},"🎹 Gen Synth Tone")));if(panelId==='selection')return/*#__PURE__*/React.createElement("div",{className:"flex flex-col h-full gap-1.5"},/*#__PURE__*/React.createElement("div",{className:"flex items-center justify-between shrink-0 cursor-grab active:cursor-grabbing select-none",onMouseDown:e=>startPanelDrag('selection',e)},/*#__PURE__*/React.createElement("span",{className:"text-xs text-zinc-500 font-bold uppercase flex items-center gap-1"},/*#__PURE__*/React.createElement("span",{className:"inline-flex items-center shrink-0"},/*#__PURE__*/React.createElement("i",{"data-lucide":"grip-vertical",className:"w-3 h-3 text-zinc-500"}))," Selection"),/*#__PURE__*/React.createElement("button",{onClick:()=>closePanel('selection'),className:"text-zinc-600 hover:text-zinc-300"},/*#__PURE__*/React.createElement("span",{className:"inline-flex items-center shrink-0"},/*#__PURE__*/React.createElement("i",{"data-lucide":"x",className:"w-3 h-3"})))),/*#__PURE__*/React.createElement("div",{className:"grid grid-cols-3 gap-1 bg-[#141414] p-1.5 rounded border border-zinc-800"},/*#__PURE__*/React.createElement("div",null,/*#__PURE__*/React.createElement("span",{className:"block text-[7px] text-zinc-500 font-bold uppercase mb-0.5"},"Start"),/*#__PURE__*/React.createElement("input",{type:"number",step:"0.01",value:selectionStats.start,onChange:e=>handleSelectionInputChange('start',e.target.value),className:"w-full bg-[#242424] text-amber-400 text-center font-mono text-xs rounded py-0.5 border border-zinc-700 focus:outline-none"})),/*#__PURE__*/React.createElement("div",null,/*#__PURE__*/React.createElement("span",{className:"block text-[7px] text-zinc-500 font-bold uppercase mb-0.5"},"End"),/*#__PURE__*/React.createElement("input",{type:"number",step:"0.01",value:selectionStats.end,onChange:e=>handleSelectionInputChange('end',e.target.value),className:"w-full bg-[#242424] text-amber-400 text-center font-mono text-xs rounded py-0.5 border border-zinc-700 focus:outline-none"})),/*#__PURE__*/React.createElement("div",{className:"flex flex-col justify-center"},/*#__PURE__*/React.createElement("span",{className:"text-[7px] text-zinc-500 font-bold uppercase"},"Len"),/*#__PURE__*/React.createElement("span",{className:"text-zinc-200 font-mono text-xs font-semibold mt-0.5"},selectionStats.length,"s"))),/*#__PURE__*/React.createElement("div",{className:"grid grid-cols-3 gap-1 bg-[#141414] p-1.5 rounded border border-zinc-800 mt-1"},/*#__PURE__*/React.createElement("div",null,/*#__PURE__*/React.createElement("span",{className:"block text-[7px] text-zinc-500 font-bold uppercase mb-0.5"},"Begin Bar"),/*#__PURE__*/React.createElement("input",{type:"number",min:"1",value:beginBar,onChange:e=>{const b=parseInt(e.target.value)||1;setBeginBar(b);const beatDuration=60/parseInt(bpm||120);const t=(b-1)*beatDuration*4;clearLocalSelection();setSelectionMode('global');setSelectionStart(t);setSelectionEnd(t+beatDuration*4);},className:"w-full bg-[#242424] text-white text-center font-mono text-xs rounded py-0.5 border border-zinc-700 focus:outline-none"})),/*#__PURE__*/React.createElement("div",null,/*#__PURE__*/React.createElement("span",{className:"block text-[7px] text-zinc-500 font-bold uppercase mb-0.5"},"End Bar"),/*#__PURE__*/React.createElement("input",{type:"number",min:"1",value:endBar,onChange:e=>{const b=parseInt(e.target.value)||1;setEndBar(b);const beatDuration=60/parseInt(bpm||120);const t=(b-1)*beatDuration*4;setSelectionEnd(t+beatDuration*4);setNumberBar(b-beginBar+1);},className:"w-full bg-[#242424] text-white text-center font-mono text-xs rounded py-0.5 border border-zinc-700 focus:outline-none"})),/*#__PURE__*/React.createElement("div",{className:"flex flex-col justify-center"},/*#__PURE__*/React.createElement("span",{className:"text-[7px] text-zinc-500 font-bold uppercase"},"# Bars"),/*#__PURE__*/React.createElement("span",{className:"text-zinc-200 font-mono text-xs font-semibold mt-0.5"},numberBar))));if(panelId==='media_explorer')return/*#__PURE__*/React.createElement("div",{className:"flex flex-col h-full gap-1.5"},/*#__PURE__*/React.createElement("div",{className:"flex items-center justify-between shrink-0 cursor-grab active:cursor-grabbing select-none",onMouseDown:e=>startPanelDrag('media_explorer',e)},/*#__PURE__*/React.createElement("h3",{className:"font-bold text-xs text-emerald-300 flex items-center gap-1"},/*#__PURE__*/React.createElement("span",{className:"inline-flex items-center shrink-0"},/*#__PURE__*/React.createElement("i",{"data-lucide":"grip-vertical",className:"w-3 h-3 text-zinc-500"})),/*#__PURE__*/React.createElement("span",{className:"inline-flex items-center shrink-0"},/*#__PURE__*/React.createElement("i",{"data-lucide":"folder-open",className:"w-3.5 h-3.5 text-emerald-400"}))," Media Explorer"),/*#__PURE__*/React.createElement("button",{onClick:()=>closePanel('media_explorer'),className:"text-zinc-600 hover:text-zinc-300"},/*#__PURE__*/React.createElement("span",{className:"inline-flex items-center shrink-0"},/*#__PURE__*/React.createElement("i",{"data-lucide":"x",className:"w-3 h-3"})))),/*#__PURE__*/React.createElement("div",{className:"flex-1 overflow-y-auto text-xs text-zinc-400 font-mono p-2"},"// Placeholder: Media files browser"));if(panelId==='fx_rack')return/*#__PURE__*/React.createElement("div",{className:"flex flex-col h-full gap-1.5"},/*#__PURE__*/React.createElement("div",{className:"flex items-center justify-between shrink-0 cursor-grab active:cursor-grabbing select-none",onMouseDown:e=>startPanelDrag('fx_rack',e)},/*#__PURE__*/React.createElement("h3",{className:"font-bold text-xs text-rose-300 flex items-center gap-1"},/*#__PURE__*/React.createElement("span",{className:"inline-flex items-center shrink-0"},/*#__PURE__*/React.createElement("i",{"data-lucide":"grip-vertical",className:"w-3 h-3 text-zinc-500"})),/*#__PURE__*/React.createElement("span",{className:"inline-flex items-center shrink-0"},/*#__PURE__*/React.createElement("i",{"data-lucide":"sliders",className:"w-3.5 h-3.5 text-rose-400"}))," Plugin FX Rack"),/*#__PURE__*/React.createElement("button",{onClick:()=>closePanel('fx_rack'),className:"text-zinc-600 hover:text-zinc-300"},/*#__PURE__*/React.createElement("span",{className:"inline-flex items-center shrink-0"},/*#__PURE__*/React.createElement("i",{"data-lucide":"x",className:"w-3 h-3"})))),/*#__PURE__*/React.createElement("div",{className:"flex-1 text-xs text-zinc-500 italic flex items-center justify-center"},"No FX plugins loaded"));if(panelId==='midi_events')return/*#__PURE__*/React.createElement("div",{className:"flex flex-col h-full gap-1.5"},/*#__PURE__*/React.createElement("div",{className:"flex items-center justify-between shrink-0 cursor-grab active:cursor-grabbing select-none",onMouseDown:e=>startPanelDrag('midi_events',e)},/*#__PURE__*/React.createElement("h3",{className:"font-bold text-xs text-sky-300 flex items-center gap-1"},/*#__PURE__*/React.createElement("span",{className:"inline-flex items-center shrink-0"},/*#__PURE__*/React.createElement("i",{"data-lucide":"grip-vertical",className:"w-3 h-3 text-zinc-500"})),/*#__PURE__*/React.createElement("span",{className:"inline-flex items-center shrink-0"},/*#__PURE__*/React.createElement("i",{"data-lucide":"music",className:"w-3.5 h-3.5 text-sky-400"}))," MIDI Event List"),/*#__PURE__*/React.createElement("button",{onClick:()=>closePanel('midi_events'),className:"text-zinc-600 hover:text-zinc-300"},/*#__PURE__*/React.createElement("span",{className:"inline-flex items-center shrink-0"},/*#__PURE__*/React.createElement("i",{"data-lucide":"x",className:"w-3 h-3"})))),/*#__PURE__*/React.createElement("div",{className:"flex-1 text-xs text-zinc-500 italic flex items-center justify-center"},"No MIDI events selected"));return null;};const renderDock=(pos,title)=>{const panels=dockPanels[pos];if(panels.length===0)return null;const isSide=pos==='left'||pos==='right';const borderClass=pos==='left'?'border-r':pos==='right'?'border-l':pos==='top'?'border-b':'border-t';const bgClass='bg-[#1e1e1e]';const highlight=panelDragRef.current&&panelDropZone===pos;if(pos==='right')return/*#__PURE__*/React.createElement("div",{id:"right-sidebar",className:`${borderClass} ${bgClass} flex flex-col overflow-hidden select-none h-full`,style:{width:`${rightSidebarWidth}px`,minWidth:'200px',maxWidth:'600px',flexShrink:0}},/*#__PURE__*/React.createElement("div",{className:"flex-1 flex flex-col gap-2 p-2 overflow-hidden h-full"},panels.map((p,idx)=>/*#__PURE__*/React.createElement(React.Fragment,{key:p},/*#__PURE__*/React.createElement("div",{className:'flex flex-col flex-1 min-h-0 border border-zinc-700 rounded-lg bg-[#262626] shadow-sm p-3'},renderPanelContent(p)),idx/*#__PURE__*/React.createElement("div",{key:p,className:`${isSide?'w-full':'flex-none w-[320px]'} border border-zinc-700 rounded-lg bg-[#262626] shadow-sm p-2`},renderPanelContent(p))));};return/*#__PURE__*/React.createElement("div",{ref:workspaceRef,className:"flex-1 flex flex-col overflow-hidden select-none daw-bg relative"},panelDragRef.current&&panelDropZone&&/*#__PURE__*/React.createElement("div",{className:"absolute inset-0 z-50 pointer-events-none"},panelDropZone==='top'&&/*#__PURE__*/React.createElement("div",{className:"absolute top-0 left-0 right-0 h-1 bg-cyan-400 shadow-[0_0_8px_#06b6d4]"}),panelDropZone==='bottom'&&/*#__PURE__*/React.createElement("div",{className:"absolute bottom-0 left-0 right-0 h-1 bg-cyan-400 shadow-[0_0_8px_#06b6d4]"}),panelDropZone==='left'&&/*#__PURE__*/React.createElement("div",{className:"absolute top-0 bottom-0 left-0 w-1 bg-cyan-400 shadow-[0_0_8px_#06b6d4]"}),panelDropZone==='right'&&/*#__PURE__*/React.createElement("div",{className:"absolute top-0 bottom-0 right-0 w-1 bg-cyan-400 shadow-[0_0_8px_#06b6d4]"})),dragGhostPanel&&dragGhostPos&&/*#__PURE__*/React.createElement("div",{className:"fixed z-[100] pointer-events-none opacity-80 border border-cyan-500 rounded-lg bg-[#262626] shadow-2xl p-3 w-56",style:{left:dragGhostPos.x,top:dragGhostPos.y}},/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-2 text-xs text-zinc-200 font-bold"},/*#__PURE__*/React.createElement("span",{className:"inline-flex items-center shrink-0"},/*#__PURE__*/React.createElement("i",{"data-lucide":"move",className:"w-3.5 h-3.5 text-cyan-400"})),dragGhostPanel==='export'?'Export Panel':dragGhostPanel==='ai'?'AI Panel':dragGhostPanel==='python_tools'?'Audio Processing Panel':'Selection Panel'),/*#__PURE__*/React.createElement("div",{className:"text-xs text-zinc-500 mt-1"},"Drop at edge to dock")),renderDock('top','Top'),/*#__PURE__*/React.createElement("div",{className:"flex-1 flex overflow-hidden"},renderDock('left','Left'),/*#__PURE__*/React.createElement("div",{className:"flex-1 flex flex-col overflow-hidden min-w-0"},/*#__PURE__*/React.createElement("div",{className:"flex-1 flex overflow-hidden"},activeTab==='main'||sessionTabs.some(s=>s.id===activeTab)?/*#__PURE__*/React.createElement(React.Fragment,null,/*#__PURE__*/React.createElement("div",{ref:tcpContainerRef,onScroll:handleTCPScroll,className:"w-[320px] shrink-0 z-20 bg-[#262626] overflow-y-auto flex flex-col border-r border-zinc-900 no-scrollbar",style:{scrollbarWidth:'none',msOverflowStyle:'none'}},/*#__PURE__*/React.createElement("div",{className:"sticky top-0 z-50 flex items-center justify-between h-10 border-b border-zinc-900 bg-[#242424] px-2 shrink-0"},/*#__PURE__*/React.createElement("span",{className:"text-xs font-bold text-zinc-300 flex items-center gap-1.5"},/*#__PURE__*/React.createElement("span",{className:"inline-flex items-center shrink-0"},/*#__PURE__*/React.createElement("i",{"data-lucide":"sliders",className:"w-3.5 h-3.5 text-cyan-400"})),"TRACKS (",activeTracks.length,")"),/*#__PURE__*/React.createElement("button",{onClick:addNewTrack,className:"px-2.5 py-1 bg-cyan-600 hover:bg-cyan-500 text-white rounded text-xs font-semibold flex items-center gap-1 shadow transition"},/*#__PURE__*/React.createElement("span",{className:"inline-flex items-center shrink-0"},/*#__PURE__*/React.createElement("i",{"data-lucide":"plus",className:"w-3 h-3"}))," Add Track")),/*#__PURE__*/React.createElement("div",{className:"sticky top-10 z-40 flex h-[40px] border-b border-purple-500/30 bg-[#1a1a2e] p-2 border-l-4 border-purple-500 shrink-0"},/*#__PURE__*/React.createElement("div",{className:"flex items-center justify-between w-full"},/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-2"},/*#__PURE__*/React.createElement("span",{className:"text-xs font-bold text-purple-400 font-mono"},"TM"),/*#__PURE__*/React.createElement("span",{className:"text-xs font-semibold text-zinc-300"},"Tempo")),/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-1"},/*#__PURE__*/React.createElement("input",{type:"number",value:bpm,onChange:e=>setBpm(e.target.value),onBlur:()=>localStorage.setItem('studio_bpm',bpm),className:"w-12 bg-zinc-800 border border-zinc-700 rounded text-xs text-zinc-300 text-center font-mono focus:outline-none focus:border-purple-500",min:"40",max:"300"}),/*#__PURE__*/React.createElement("span",{className:"text-xs text-zinc-500"},"BPM")))),/*#__PURE__*/React.createElement("div",{className:"flex-1 flex flex-col divide-y divide-[#141414] bg-[#262626]"},activeTracks.length===0?/*#__PURE__*/React.createElement("div",{className:"p-6 text-center text-zinc-400 flex flex-col items-center justify-center space-y-3"},/*#__PURE__*/React.createElement("span",{className:"inline-flex items-center shrink-0"},/*#__PURE__*/React.createElement("i",{"data-lucide":"plus-circle",className:"w-8 h-8 text-cyan-400 opacity-80"})),/*#__PURE__*/React.createElement("p",{className:"text-xs font-medium"},"Chưa có Track nào trong dự án."),/*#__PURE__*/React.createElement("button",{onClick:addNewTrack,className:"px-3 py-1.5 bg-cyan-600 hover:bg-cyan-500 text-white rounded text-xs font-semibold flex items-center gap-1.5 shadow"},/*#__PURE__*/React.createElement("span",{className:"inline-flex items-center shrink-0"},/*#__PURE__*/React.createElement("i",{"data-lucide":"plus-circle",className:"w-3.5 h-3.5"}))," Thêm Track Mới")):activeTracks.map((track,idx)=>{const isSelected=selectedTrackId===track.id;const autoHeight=track.height||(track.isArmed?164:140);return/*#__PURE__*/React.createElement("div",{key:track.id,style:{height:`${autoHeight}px`},className:`shrink-0 relative flex flex-col justify-between p-2.5 bg-[#1e1e1e] border-r border-zinc-900 cursor-pointer border-l-4 border-b border-[#141414] overflow-hidden ${isSelected?'border-cyan-500 bg-[#252525]':'border-transparent hover:bg-zinc-800/20'}`,onClick:()=>setSelectedTrackId(track.id)},/*#__PURE__*/React.createElement("div",{className:"flex items-start justify-between"},/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-2"},/*#__PURE__*/React.createElement("span",{className:"text-xs font-bold text-zinc-500 font-mono"},(idx+1).toString().padStart(2,'0')),/*#__PURE__*/React.createElement("label",{onClick:e=>{e.stopPropagation();const el=e.currentTarget.querySelector('input');if(el)el.click();},className:"cursor-pointer"},/*#__PURE__*/React.createElement("input",{type:"color",value:track.color||'#0f766e',onChange:e=>{e.stopPropagation();updateTrackColor(track.id,e.target.value);},className:"w-0 h-0 opacity-0 absolute pointer-events-none"}),/*#__PURE__*/React.createElement("div",{className:"w-3 h-3 rounded-full border border-zinc-600 hover:scale-110 transition-transform",style:{backgroundColor:track.color}})),editingTrackName===track.id?/*#__PURE__*/React.createElement("input",{type:"text",value:editNameInput,autoFocus:true,onChange:e=>setEditNameInput(e.target.value),onBlur:()=>{updateTrackName(track.id,editNameInput||track.name);setEditingTrackName(null);},onKeyDown:e=>{if(e.key==='Enter'){updateTrackName(track.id,editNameInput||track.name);setEditingTrackName(null);}if(e.key==='Escape')setEditingTrackName(null);},onClick:e=>e.stopPropagation(),className:"text-xs font-semibold bg-black text-zinc-200 border border-cyan-500 rounded px-1 py-0 w-24 outline-none"}):/*#__PURE__*/React.createElement("span",{className:"text-xs font-semibold text-zinc-300 truncate max-w-[80px] cursor-text hover:text-cyan-300 transition-colors",title:"Click to rename",onClick:e=>{e.stopPropagation();setEditingTrackName(track.id);setEditNameInput(track.name);}},track.name)),/*#__PURE__*/React.createElement("div",{className:"flex flex-wrap gap-0.5 max-w-[100px] mb-0.5"},(track.clips&&track.clips.length>0?track.clips:track.buffer?[{id:'default',name:track.name,startTime:track.startTime}]:[]).slice(0,3).map(c=>/*#__PURE__*/React.createElement("span",{key:c.id,className:"text-[9px] font-mono text-zinc-500 bg-zinc-800/60 rounded px-0.5 truncate max-w-[90px] cursor-pointer hover:text-cyan-400 hover:bg-zinc-700",title:c.name||track.name,onClick:e=>{e.stopPropagation();setSelectedTrackId(track.id);clearLocalSelection();setSelectionMode('global');const start=c.startTime||0;const end=start+(c.buffer?c.buffer.duration:2);setSelectionStart(start);setSelectionEnd(end);showToast(`Selected: ${c.name||track.name}`,'info');}},c.name||track.name),editingClipName&&editingClipName.trackId===track.id&&editingClipName.clipId===c.id?/*#__PURE__*/React.createElement("input",{type:"text",value:editNameInput,autoFocus:true,onChange:e=>setEditNameInput(e.target.value),onBlur:()=>{if(editNameInput.trim())updateClipName(track.id,c.id,editNameInput.trim());setEditingClipName(null);},onKeyDown:e=>{if(e.key==='Enter'){if(editNameInput.trim())updateClipName(track.id,c.id,editNameInput.trim());setEditingClipName(null);}if(e.key==='Escape')setEditingClipName(null);},onClick:e=>e.stopPropagation(),className:"w-20 text-[9px] font-mono bg-black text-cyan-300 border border-cyan-500 rounded px-0.5 py-0 outline-none"}):/*#__PURE__*/React.createElement("button",{className:"text-[9px] text-zinc-600 hover:text-cyan-400 ml-0.5 shrink-0",title:"Sửa tên clip",onClick:e=>{e.stopPropagation();setEditingClipName({trackId:track.id,clipId:c.id});setEditNameInput(c.name||track.name);}},/*#__PURE__*/React.createElement("i",{"data-lucide":"pencil",className:"w-2.5 h-2.5"})))),/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-1"},/*#__PURE__*/React.createElement("button",{onClick:e=>{e.stopPropagation();toggleTrackMute(track.id);},title:"Mute",className:`px-1.5 py-0.5 text-[10px] rounded font-mono font-bold border transition flex items-center gap-0.5 ${track.muted?'bg-red-950 text-red-400 border-red-700':'bg-zinc-800 text-zinc-400 border-transparent hover:text-zinc-200'}`},/*#__PURE__*/React.createElement("i",{"data-lucide":track.muted?"volume-x":"volume-2",className:"w-3 h-3"})),/*#__PURE__*/React.createElement("button",{onClick:e=>{e.stopPropagation();toggleTrackSoloEvaluate(track.id);},title:"Solo",className:`px-1.5 py-0.5 text-[10px] rounded font-mono font-bold border transition flex items-center gap-0.5 ${soloedTrackId===track.id||track.solo?'bg-amber-950 text-amber-400 border-amber-600':'bg-zinc-800 text-zinc-400 border-transparent hover:text-zinc-200'}`},/*#__PURE__*/React.createElement("i",{"data-lucide":soloedTrackId===track.id||track.solo?"headphones":"headphone-off",className:"w-3 h-3"})),/*#__PURE__*/React.createElement("button",{onClick:e=>{e.stopPropagation();toggleTrackArm(track.id);},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.instrumentName||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);},title:"Input Monitor",className:`px-1.5 py-0.5 text-[10px] rounded font-mono font-bold border transition flex items-center gap-0.5 ${track.monitoringEnabled?'bg-amber-600 text-white border-amber-500 hover:bg-amber-500':'bg-zinc-800 text-zinc-500 border-transparent hover:text-zinc-305'}`},/*#__PURE__*/React.createElement("i",{"data-lucide":track.monitoringEnabled?"mic":"mic-off",className:"w-3 h-3"})),/*#__PURE__*/React.createElement("button",{onClick:e=>{e.stopPropagation();deleteTrack(track.id);},className:"p-0.5 rounded text-red-500 hover:bg-red-950/40 hover:text-red-400 transition"},/*#__PURE__*/React.createElement("span",{className:"inline-flex items-center shrink-0"},/*#__PURE__*/React.createElement("i",{"data-lucide":"trash-2",className:"w-3 h-3"}))))),/*#__PURE__*/React.createElement("div",{className:"flex flex-col gap-0.5 text-xs",onClick:e=>e.stopPropagation()},/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-1"},/*#__PURE__*/React.createElement("span",{className:"w-8 text-right text-zinc-500 text-xs"},"Vol:"),/*#__PURE__*/React.createElement("input",{type:"range",min:"-50",max:"7",step:"0.5",value:track.volumeDb??0,onChange:e=>updateTrackVolumeDb(track.id,parseFloat(e.target.value)),className:"flex-1 h-1 bg-zinc-700 rounded-lg appearance-none cursor-pointer accent-cyan-500",style:{height:'4px'}}),/*#__PURE__*/React.createElement("span",{className:"w-12 text-right font-mono text-zinc-300 text-xs"},track.volumeDb??0,"dB")),/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-1"},/*#__PURE__*/React.createElement("span",{className:"w-8 text-right text-zinc-500 text-xs"},"Pan:"),/*#__PURE__*/React.createElement("input",{type:"range",min:"-100",max:"100",step:"1",value:track.pan??0,onChange:e=>updateTrackPan(track.id,parseInt(e.target.value)),className:"flex-1 h-1 bg-zinc-700 rounded-lg appearance-none cursor-pointer accent-purple-500",style:{height:'4px'}}),/*#__PURE__*/React.createElement("span",{className:"w-12 text-right font-mono text-zinc-300 text-xs"},track.pan>0?'R'+track.pan:track.pan<0?'L'+Math.abs(track.pan):'C')),/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-1 mt-0.5"},/*#__PURE__*/React.createElement("span",{className:"w-8 text-right text-zinc-500 text-[10px]"},"In:"),/*#__PURE__*/React.createElement("select",{value:`${track.inputSource?.deviceType||'NONE'}:${track.inputSource?.deviceId||''}`,onChange:e=>{const val=e.target.value;const parts=val.split(':');const type=parts[0];const id=parts.slice(1).join(':');updateTrackInputSource(track.id,type,id);},className:"flex-1 bg-[#18181b] text-zinc-300 text-[10px] rounded border border-zinc-700 focus:outline-none py-0.5 px-1 truncate max-w-[120px]"},/*#__PURE__*/React.createElement("option",{value:"NONE:"},"No Input"),/*#__PURE__*/React.createElement("optgroup",{label:"Microphones"},audioDevices.map(d=>/*#__PURE__*/React.createElement("option",{key:d.deviceId,value:`MICROPHONE:${d.deviceId}`},d.label||`Microphone ${d.deviceId.slice(0,5)}`))),/*#__PURE__*/React.createElement("optgroup",{label:"MIDI Keyboards"},/*#__PURE__*/React.createElement("option",{value:"MIDI_KEYBOARD:ALL"},"Any MIDI Keyboard"),midiDevices.map(d=>/*#__PURE__*/React.createElement("option",{key:d.id,value:`MIDI_KEYBOARD:${d.id}`},d.name||`MIDI Input ${d.id.slice(0,5)}`)))),track.isArmed&&lastMidiNote&&(lastMidiNote.length===0||Date.now()-lastMidiNote.time<3000)&&/*#__PURE__*/React.createElement("span",{className:"text-[9px] font-mono text-emerald-400 ml-0.5 truncate max-w-[60px] shrink-0",title:"MIDI Note:velocity:length"},`${midiPitchToName(lastMidiNote.pitch)}:${lastMidiNote.velocity}:${lastMidiNote.length>0?lastMidiNote.length.toFixed(2)+'s':'...'}`)),track.isArmed&&/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-1 mt-0.5"},/*#__PURE__*/React.createElement("span",{className:"w-8 text-right text-zinc-500 text-[9px]"},"VU:"),/*#__PURE__*/React.createElement("canvas",{ref:el=>{if(el)trackVuRefs.current[track.id]=el;else delete trackVuRefs.current[track.id];},width:100,height:4,className:"flex-1 bg-[#18181b] rounded h-1"}))),/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-1.5 mt-1",onClick:e=>e.stopPropagation()},/*#__PURE__*/React.createElement("input",{type:"file",id:`upload-${track.id}`,accept:"audio/*",className:"hidden",onChange:e=>loadFileOnTrack(track.id,e.target.files[0])}),/*#__PURE__*/React.createElement("label",{htmlFor:`upload-${track.id}`,className:"px-2.5 py-1 bg-zinc-800 hover:bg-zinc-700 text-zinc-300 rounded text-xs flex items-center gap-1 cursor-pointer border border-zinc-700 font-medium"},/*#__PURE__*/React.createElement("span",{className:"inline-flex items-center shrink-0"},/*#__PURE__*/React.createElement("i",{"data-lucide":"upload",className:"w-3 h-3"}))," File"),/*#__PURE__*/React.createElement("button",{onClick:e=>{e.stopPropagation();showToast('FX panel for track '+track.id,'info');},className:"px-2.5 py-1 bg-zinc-800 hover:bg-zinc-700 text-purple-400 border border-purple-900 rounded text-xs font-bold flex items-center gap-1"},/*#__PURE__*/React.createElement("span",{className:"inline-flex items-center shrink-0"},/*#__PURE__*/React.createElement("i",{"data-lucide":"wand-2",className:"w-3 h-3"}))," FX: ",/*#__PURE__*/React.createElement("span",{className:"text-zinc-500 font-normal"}," None")),/*#__PURE__*/React.createElement("button",{onClick:e=>{e.stopPropagation();openInstrumentSelector(track.id);},className:"px-2.5 py-1 bg-zinc-800 hover:bg-zinc-700 text-amber-400 border border-amber-800 rounded text-xs font-bold flex items-center gap-1"},/*#__PURE__*/React.createElement("span",{className:"inline-flex items-center shrink-0"},/*#__PURE__*/React.createElement("i",{"data-lucide":"music",className:"w-3 h-3"}))," Synth: ",/*#__PURE__*/React.createElement("span",{className:"text-zinc-500 font-normal"},track.instrumentName||track.instrumentId||"None"))),/*#__PURE__*/React.createElement("div",{onMouseDown:e=>handleTrackResizeMouseDown(e,track.id),className:"absolute bottom-0 left-0 right-0 h-1 cursor-ns-resize z-30 hover:bg-cyan-500/50 transition-colors",onClick:e=>e.stopPropagation()}));})),/*#__PURE__*/React.createElement("div",{className:"h-[48px] border-r border-zinc-900 bg-[#1e1e1e]/50 shrink-0"})),/*#__PURE__*/React.createElement("div",{ref:handleTimelineWrapperRef,onScroll:handleTimelineScroll,className:"flex-1 relative z-10 overflow-x-auto overflow-y-auto min-w-0"},/*#__PURE__*/React.createElement("div",{style:{width:`${timelineWidth}px`},className:"relative flex flex-col min-h-full"},/*#__PURE__*/React.createElement("div",{className:"sticky top-0 z-45 flex h-10 border-b border-zinc-900 bg-[#242424] shrink-0"},/*#__PURE__*/React.createElement("div",{ref:rulerRef,className:"flex-1 relative h-full flex items-center select-none cursor-ew-resize overflow-hidden",onMouseDown:handleRulerMouseDown},Array.from({length:Math.ceil(maxDuration)}).map((_,i)=>{const sec=i;const x=sec*zoom;return/*#__PURE__*/React.createElement("div",{key:i,className:"absolute h-full border-l border-zinc-700 pl-1 pt-1 text-[14px] font-mono text-zinc-300 pointer-events-none",style:{left:`${x}px`}},formatTimeSimple(sec));}))),selectionMode==='global'&&selLeft!==null&&selRight!==null&&selRight>selLeft&&/*#__PURE__*/React.createElement("div",{className:"absolute inset-0 pointer-events-none z-20",style:{left:`${selLeft*zoom}px`,width:`${(selRight-selLeft)*zoom}px`,top:'40px'}},/*#__PURE__*/React.createElement("div",{className:"w-full h-full bg-amber-500/10",style:{borderLeft:'1px solid #f59e0b',borderRight:'1px solid #f59e0b'}})),/*#__PURE__*/React.createElement("div",{className:"sticky top-10 z-35 flex h-[40px] border-b border-purple-500/30 bg-[#1a1a2e] shrink-0"},/*#__PURE__*/React.createElement("div",{className:"flex-1 relative h-full overflow-hidden bg-[#1a1a2e]"},/*#__PURE__*/React.createElement(TempoTrackLane,{bpm:parseInt(bpm)||120,zoom:zoom,timelineWidth:timelineWidth,viewportWidth:viewportWidth,onPlayheadSet:handlePlayheadSet,snapValue:snapValue,onRulerMouseDown:handleRulerMouseDown,scrollLeft:scrollLeft}))),/*#__PURE__*/React.createElement("div",{className:"flex-1 flex flex-col divide-y divide-[#141414] relative bg-[#111111] min-h-full"},activeTracks.map((track,idx)=>{const isSelected=selectedTrackId===track.id;const autoHeight=track.height||(track.isArmed?164:140);return/*#__PURE__*/React.createElement("div",{key:track.id,style:{height:`${autoHeight}px`},className:`shrink-0 relative border-b border-[#141414] hover:bg-zinc-850/5 transition-colors ${isSelected?'bg-zinc-800/10':''}`,onDragOver:e=>e.preventDefault(),onDrop:e=>{e.preventDefault();if(e.dataTransfer.files[0])loadFileOnTrack(track.id,e.dataTransfer.files[0]);},onMouseEnter:()=>setHoveredTrackId(track.id)},/*#__PURE__*/React.createElement(WaveformLane,{track:track,zoom:zoom,timelineWidth:timelineWidth,viewportWidth:viewportWidth,onSelectRange:handleSelectRange,onPlayheadSet:handlePlayheadSet,isSelected:isSelected,onSelectTrack:setSelectedTrackId,markers:track.markers,onTrackLaneMouseDown:handleTrackLaneMouseDown,onContextMenu:handleContextMenu,onClipDragStart:handleClipDragStart,onClipStretchStart:handleClipStretchStart,onSectionItemDragStart:handleSectionItemDragStart,onSectionItemResizeStart:handleSectionItemResizeStart,onSelectionEdgeDragStart:handleSelectionEdgeDragStart,setSelectedClipId:setSelectedClipId,selectedClipId:selectedClipId,activeTool:activeTool,onSplitTrackAtTime:handleSplitTrackAtTime,onEditClipInSubTab:handleEditClipInSubTab,onEditSectionInTab:handleEditSectionInTab,onEditMidiInTab:handleEditMidiInTab,snapValue:snapValue,bpm:bpm,selectionMode:selectionMode,localSelectionTrackId:localSelectionTrackId,localSelectionStart:localSelectionStart,currentTime:currentTime,getLocalAnchor:()=>localSelectionAnchorRef.current,onClearLocalSelection:clearLocalSelection,onSetSelectionMode:setSelectionMode,onSetSelectionStart:setSelectionStart,onSetSelectionEnd:setSelectionEnd,onSetCurrentTime:setCurrentTime,onSetLocalSelectionTrackId:setLocalSelectionTrackId,onSetLocalSelectionStart:setLocalSelectionStart,onSetLocalSelectionEnd:setLocalSelectionEnd,localSelLeft:localSelectionStart!==null&&localSelectionEnd!==null?Math.min(localSelectionStart,localSelectionEnd):null,localSelRight:localSelectionStart!==null&&localSelectionEnd!==null?Math.max(localSelectionStart,localSelectionEnd):null,scrollLeft:scrollLeft,recordingState:recordingState,recTempMidiNotes:recTempMidiNotes,recTempAudioBuffer:recTempAudioBuffer,recStartTimelineTime:recStartTimelineTime,canvasRedrawCount:canvasRedrawCount}),selectionMode==='local'&&localSelectionTrackId===track.id&&localSelectionStart!==null&&localSelectionEnd!==null&&Math.abs(localSelectionEnd-localSelectionStart)>0&&/*#__PURE__*/React.createElement("div",{className:"absolute top-0 bottom-0 border-l border-r border-amber-400 bg-amber-500/20 z-20 pointer-events-none",style:{left:`${Math.min(localSelectionStart,localSelectionEnd)*zoom}px`,width:`${Math.abs(localSelectionEnd-localSelectionStart)*zoom}px`}},/*#__PURE__*/React.createElement("div",{className:"absolute -left-[1px] top-0 bottom-0 w-[2px] bg-amber-400 z-30 pointer-events-auto cursor-ew-resize",onMouseDown:e=>handleHandleDragStart(e,'left')}),/*#__PURE__*/React.createElement("div",{className:"absolute -right-[1px] top-0 bottom-0 w-[2px] bg-amber-400 z-30 pointer-events-auto cursor-ew-resize",onMouseDown:e=>handleHandleDragStart(e,'right')})),track.buffer&&/*#__PURE__*/React.createElement("div",{className:"absolute right-2 top-2 flex items-center gap-1 z-10 opacity-70 hover:opacity-100"},/*#__PURE__*/React.createElement("button",{onClick:()=>handleSplitTrack(track.id),className:"px-1.5 py-0.5 bg-zinc-900/95 text-zinc-300 rounded text-xs flex items-center gap-1 border border-zinc-700/50"},/*#__PURE__*/React.createElement("span",{className:"inline-flex items-center shrink-0"},/*#__PURE__*/React.createElement("i",{"data-lucide":"scissors",className:"w-2.5 h-2.5 text-cyan-400"}))," Cắt")),/*#__PURE__*/React.createElement("div",{onMouseDown:e=>handleTrackResizeMouseDown(e,track.id),className:"absolute bottom-0 left-0 right-0 h-1 cursor-ns-resize z-30 hover:bg-cyan-500/50 transition-colors",onClick:e=>e.stopPropagation()}));}),/*#__PURE__*/React.createElement("div",{className:"h-[48px] flex items-center justify-center text-xs text-zinc-500 hover:bg-zinc-900/20 cursor-pointer select-none border-t border-dashed border-zinc-800",onMouseEnter:()=>{if(draggedClipRef.current||draggedSectionItemRef.current){setHoveredTrackId(addNewTrack());}},onClick:addNewTrack},/*#__PURE__*/React.createElement("span",{className:"flex items-center gap-1 text-zinc-400"},/*#__PURE__*/React.createElement("span",{className:"inline-flex items-center shrink-0"},/*#__PURE__*/React.createElement("i",{"data-lucide":"plus",className:"w-3.5 h-3.5"}))," Kéo clip xuống hoặc Click tạo Track")),selectionMode==='global'&&selLeft!==null&&selRight!==null&&selRight>selLeft&&/*#__PURE__*/React.createElement("div",{className:"absolute top-0 bottom-0 border-l border-r border-amber-500 bg-amber-500/10 z-20 selection-interactive-box cursor-grab active:cursor-grabbing",style:{left:`${selLeft*zoom}px`,width:`${(selRight-selLeft)*zoom}px`},onMouseDown:handleSelectionBodyDragStart},/*#__PURE__*/React.createElement("div",{className:"absolute -left-[1px] top-0 bottom-0 w-[1px] bg-amber-500 hover:bg-amber-400 cursor-ew-resize z-30",onMouseDown:e=>handleHandleDragStart(e,'left')}),/*#__PURE__*/React.createElement("div",{className:"absolute -right-[1px] top-0 bottom-0 w-[1px] bg-amber-500 hover:bg-amber-400 cursor-ew-resize z-30",onMouseDown:e=>handleHandleDragStart(e,'right')})),/*#__PURE__*/React.createElement("div",{className:"absolute top-0 bottom-0 w-[2px] bg-[#ef4444] z-20 pointer-events-none",style:{left:`${playheadLeftPos}px`}},/*#__PURE__*/React.createElement("div",{className:"w-3 h-3 bg-[#ef4444] rotate-45 transform -translate-x-1/2 -translate-y-1/2 absolute top-0"})))))):(()=>{const st=subTabs.find(s=>s.id===activeTab);if(!st)return null;if(st.type==='PIANO_ROLL'){return/*#__PURE__*/React.createElement(PianoRollTabEditor,{st:st,zoom:zoom,bpm:bpm,viewportWidth:viewportWidth,onClose:()=>closeSubTab(st.id),onUpdateNotes:handleUpdateMidiNotes,onSaveNotes:handleSaveMidiNotes,setSubTabs:setSubTabs,onPlayPause:handlePlayPause,onStop:stopAllPlayback,isPlaying:isPlaying,playPreviewNote:playMidiPreviewNote});}const subTrack=tracks.find(t=>t.id===st.trackId);const vTrack=subTrack?{...subTrack,buffer:st.buffer,isSubTab:true}:null;const subTabDuration=st.type==='PIANO_ROLL'?(()=>{const notes=st.notes||[];const beatSec=60.0/(parseFloat(bpm)||120);let maxEnd=0;notes.forEach(n=>{const end=(n.start_beat||0)+(n.duration_beats||1);if(end>maxEnd)maxEnd=end;});return maxEnd*beatSec+1.0;})():st.buffer&&'duration'in st.buffer?st.buffer.duration:4.0;const subTabTimelineWidth=Math.max(zoom*subTabDuration,viewportWidth);return/*#__PURE__*/React.createElement(React.Fragment,null,/*#__PURE__*/React.createElement("div",{className:"w-[320px] shrink-0 z-30 bg-[#262626] overflow-y-scroll no-scrollbar flex flex-col border-r border-zinc-900",style:{scrollbarWidth:'none',msOverflowStyle:'none'}},/*#__PURE__*/React.createElement("div",{className:"sticky top-0 z-50 flex items-center h-10 border-b border-zinc-900 bg-[#242424] px-2 justify-between shrink-0"},/*#__PURE__*/React.createElement("span",{className:"text-xs font-bold text-zinc-500 uppercase"},"Sub-Tab"),/*#__PURE__*/React.createElement("button",{onClick:()=>closeSubTab(st.id),className:"px-1.5 py-0.5 text-xs bg-zinc-800 hover:bg-zinc-700 text-zinc-400 rounded border border-zinc-700 flex items-center gap-1"},/*#__PURE__*/React.createElement("span",{className:"inline-flex items-center shrink-0"},/*#__PURE__*/React.createElement("i",{"data-lucide":"x",className:"w-3 h-3"}))," Close")),vTrack?/*#__PURE__*/React.createElement("div",{key:vTrack.id,className:"flex-1 flex flex-col p-2.5 bg-[#1e1e1e] border-r border-zinc-900 border-l-4 border-l-cyan-500"},/*#__PURE__*/React.createElement("div",{className:"flex items-center justify-between mb-2"},/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-2"},/*#__PURE__*/React.createElement("label",{onClick:e=>{e.stopPropagation();const el=e.currentTarget.querySelector('input');if(el)el.click();},className:"cursor-pointer"},/*#__PURE__*/React.createElement("input",{type:"color",value:vTrack.color||'#0f766e',onChange:e=>{e.stopPropagation();updateTrackColor(vTrack.id,e.target.value);},className:"w-0 h-0 opacity-0 absolute pointer-events-none"}),/*#__PURE__*/React.createElement("div",{className:"w-3 h-3 rounded-full border border-zinc-600 hover:scale-110 transition-transform",style:{backgroundColor:vTrack.color}})),editingTrackName===vTrack.id?/*#__PURE__*/React.createElement("input",{type:"text",value:editNameInput,autoFocus:true,onChange:e=>setEditNameInput(e.target.value),onBlur:()=>{updateTrackName(vTrack.id,editNameInput||vTrack.name);setEditingTrackName(null);},onKeyDown:e=>{if(e.key==='Enter'){updateTrackName(vTrack.id,editNameInput||vTrack.name);setEditingTrackName(null);}if(e.key==='Escape')setEditingTrackName(null);},onClick:e=>e.stopPropagation(),className:"text-xs font-semibold bg-black text-zinc-200 border border-cyan-500 rounded px-1 py-0 w-24 outline-none"}):/*#__PURE__*/React.createElement("span",{className:"text-xs font-semibold text-zinc-300 truncate max-w-[80px] cursor-text hover:text-cyan-300 transition-colors",title:"Click to rename",onClick:e=>{e.stopPropagation();setEditingTrackName(vTrack.id);setEditNameInput(vTrack.name);}},vTrack.name)),/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-1"},/*#__PURE__*/React.createElement("button",{onClick:e=>{e.stopPropagation();toggleTrackMute(vTrack.id);},className:`px-1.5 py-0.5 text-xs rounded font-mono font-bold border ${vTrack.muted?'bg-red-950 text-red-400 border-red-700':'bg-zinc-800 text-zinc-400 border-transparent hover:text-zinc-200'}`},"M"),/*#__PURE__*/React.createElement("button",{onClick:e=>{e.stopPropagation();toggleTrackSoloEvaluate(vTrack.id);},className:`px-1.5 py-0.5 text-xs rounded font-mono font-bold border ${soloedTrackId===vTrack.id||vTrack.solo?'bg-amber-950 text-amber-400 border-amber-600':'bg-zinc-800 text-zinc-400 border-transparent hover:text-zinc-200'}`},"S"))),/*#__PURE__*/React.createElement("div",{className:"flex flex-col gap-2.5 text-[14px] mb-3"},/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-1"},/*#__PURE__*/React.createElement("span",{className:"w-10 text-right text-zinc-500 text-[14px]"},"Vol:"),/*#__PURE__*/React.createElement("input",{type:"range",min:"-50",max:"7",step:"0.5",id:`tcp-vol-${st.id}`,value:vTrack.volumeDb??0,onChange:e=>updateTrackVolumeDb(vTrack.id,parseFloat(e.target.value)),className:"flex-1 h-1 bg-zinc-700 rounded-lg appearance-none cursor-pointer accent-cyan-500",style:{height:'4px'}}),/*#__PURE__*/React.createElement("span",{className:"w-16 text-right font-mono text-zinc-300 text-[14px]",id:`tcp-vol-label-${st.id}`},vTrack.volumeDb??0,"dB")),/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-1"},/*#__PURE__*/React.createElement("span",{className:"w-10 text-right text-zinc-500 text-[14px]"},"Pan:"),/*#__PURE__*/React.createElement("input",{type:"range",min:"-100",max:"100",step:"1",id:`tcp-pan-${st.id}`,value:vTrack.pan??0,onChange:e=>updateTrackPan(vTrack.id,parseInt(e.target.value)),className:"flex-1 h-1 bg-zinc-700 rounded-lg appearance-none cursor-pointer accent-purple-500",style:{height:'4px'}}),/*#__PURE__*/React.createElement("span",{className:"w-16 text-right font-mono text-zinc-300 text-[14px]",id:`tcp-pan-label-detailed-${st.id}`},vTrack.pan>0?'R'+vTrack.pan:vTrack.pan<0?'L'+Math.abs(vTrack.pan):'C')),/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-1.5 text-[14px]"},/*#__PURE__*/React.createElement("button",{onClick:()=>setSubTabs(prev=>prev.map(s=>s.id===st.id?{...s,isLooping:!s.isLooping}:s)),className:`px-2 py-0.5 text-[14px] rounded border font-bold ${st.isLooping?'bg-amber-700 text-white border-amber-500':'bg-zinc-800 text-zinc-400 border-zinc-700'}`},/*#__PURE__*/React.createElement("span",{className:"inline-flex items-center shrink-0"},/*#__PURE__*/React.createElement("i",{"data-lucide":"repeat",className:"w-3.5 h-3.5"}))),/*#__PURE__*/React.createElement("button",{onClick:()=>updateSubTabEffects(st.id,{reverse:!(st.effects||{}).reverse}),className:`px-2 py-0.5 rounded border text-[14px] font-bold ${(st.effects||{}).reverse?'bg-zinc-600 text-white border-zinc-500':'bg-zinc-800 text-zinc-400 border-zinc-700'} flex items-center justify-center`,title:"Reverse"},/*#__PURE__*/React.createElement("span",{className:"inline-flex items-center shrink-0"},/*#__PURE__*/React.createElement("i",{"data-lucide":"arrow-left-right",className:"w-3.5 h-3.5"}))),/*#__PURE__*/React.createElement("span",{className:"w-10 text-right text-zinc-500 text-[14px]"},"Loop:"),/*#__PURE__*/React.createElement("input",{type:"number",min:"0",max:"999",value:st.loopCount||0,onChange:e=>setSubTabs(prev=>prev.map(s=>s.id===st.id?{...s,loopCount:Math.max(0,parseInt(e.target.value)||0)}:s)),className:"w-12 bg-black text-amber-300 text-[14px] px-1 py-0.5 rounded border border-zinc-700 text-center font-mono",title:"Loop count"}))),/*#__PURE__*/React.createElement("div",{className:"flex gap-1 justify-between my-2.5"},/*#__PURE__*/React.createElement("div",{className:"flex flex-col items-center bg-zinc-900/60 p-1.5 rounded border border-zinc-800 flex-1"},/*#__PURE__*/React.createElement("span",{className:"text-[14px] font-bold text-zinc-400 uppercase tracking-tighter",title:"Normalize"},"Norm"),/*#__PURE__*/React.createElement("input",{type:"range",min:"-12",max:"0",step:"0.1",value:subTabNormVal,onChange:e=>setSubTabNormVal(parseFloat(e.target.value)),style:{writingMode:'vertical-lr',direction:'rtl',height:'120px',width:'20px',accentColor:'#a1a1aa'},className:"my-2 cursor-pointer"}),/*#__PURE__*/React.createElement("span",{className:"text-[14px] font-mono text-zinc-300 font-bold"},subTabNormVal,"dB"),/*#__PURE__*/React.createElement("button",{onClick:()=>applySubTabEffect(st.id,'normalize',subTabNormVal),className:"mt-1.5 px-1 py-1 bg-zinc-700 hover:bg-zinc-600 text-zinc-100 rounded text-[14px] font-bold w-full uppercase border border-zinc-650"},"Apply")),/*#__PURE__*/React.createElement("div",{className:"flex flex-col items-center bg-zinc-900/60 p-1.5 rounded border border-zinc-800 flex-1"},/*#__PURE__*/React.createElement("span",{className:"text-[14px] font-bold text-zinc-400 uppercase tracking-tighter",title:"Pitch Shift"},"Pitch"),/*#__PURE__*/React.createElement("input",{type:"range",min:"-12",max:"12",step:"0.5",value:subTabPitchVal,onChange:e=>setSubTabPitchVal(parseFloat(e.target.value)),style:{writingMode:'vertical-lr',direction:'rtl',height:'120px',width:'20px',accentColor:'#a1a1aa'},className:"my-2 cursor-pointer"}),/*#__PURE__*/React.createElement("span",{className:"text-[14px] font-mono text-zinc-300 font-bold"},subTabPitchVal>0?'+':'',subTabPitchVal,"st"),/*#__PURE__*/React.createElement("button",{onClick:()=>applySubTabEffect(st.id,'pitch',subTabPitchVal),className:"mt-1.5 px-1 py-1 bg-zinc-700 hover:bg-zinc-600 text-zinc-100 rounded text-[14px] font-bold w-full uppercase border border-zinc-650"},"Apply")),/*#__PURE__*/React.createElement("div",{className:"flex flex-col items-center bg-zinc-900/60 p-1.5 rounded border border-zinc-800 flex-1"},/*#__PURE__*/React.createElement("span",{className:"text-[14px] font-bold text-zinc-400 uppercase tracking-tighter",title:"Gain Multiplier"},"Gain"),/*#__PURE__*/React.createElement("input",{type:"range",min:"0",max:"150",step:"1",value:subTabGainVal,onChange:e=>setSubTabGainVal(parseInt(e.target.value)),style:{writingMode:'vertical-lr',direction:'rtl',height:'120px',width:'20px',accentColor:'#a1a1aa'},className:"my-2 cursor-pointer"}),/*#__PURE__*/React.createElement("span",{className:"text-[14px] font-mono text-zinc-300 font-bold"},subTabGainVal,"%"),/*#__PURE__*/React.createElement("button",{onClick:()=>applySubTabEffect(st.id,'gain',subTabGainVal),className:"mt-1.5 px-1 py-1 bg-zinc-700 hover:bg-zinc-600 text-zinc-100 rounded text-[14px] font-bold w-full uppercase border border-zinc-650"},"Apply"))),/*#__PURE__*/React.createElement("div",{className:"flex-1"}),/*#__PURE__*/React.createElement("div",{className:"mt-auto pt-2.5 border-t border-zinc-800"},/*#__PURE__*/React.createElement("div",{className:"flex items-center justify-between text-[14px] text-zinc-500 mb-1 font-semibold uppercase"},/*#__PURE__*/React.createElement("span",null,"Duration:"),/*#__PURE__*/React.createElement("span",{className:"font-mono text-zinc-300"},st.buffer?formatTime(subTabDuration):'0s')),/*#__PURE__*/React.createElement("div",{className:"flex items-center justify-between text-[14px] text-zinc-500 mb-2.5 font-semibold uppercase"},/*#__PURE__*/React.createElement("span",null,"SR:"),/*#__PURE__*/React.createElement("span",{className:"font-mono text-zinc-300"},st.buffer?st.buffer.sampleRate:0," Hz")),/*#__PURE__*/React.createElement("div",{className:"grid grid-cols-2 gap-1 mb-2"},/*#__PURE__*/React.createElement("button",{onClick:async e=>{subTabAiTrackIdRef.current=st.trackId;handleAIScan();},disabled:analysisState.isRunning,className:"py-1 bg-purple-900 hover:bg-purple-800 text-purple-100 font-bold rounded text-xs border border-purple-700 flex items-center justify-center gap-1"},/*#__PURE__*/React.createElement("span",{className:"inline-flex items-center shrink-0"},/*#__PURE__*/React.createElement("i",{"data-lucide":"map-pin",className:"w-3 h-3"}))," Scan"),/*#__PURE__*/React.createElement("button",{onClick:async e=>{subTabAiTrackIdRef.current=st.trackId;handleAICutToNewTrack();},disabled:analysisState.isRunning,className:"py-1 bg-fuchsia-800 hover:bg-fuchsia-700 text-white font-bold rounded text-xs flex items-center justify-center gap-1"},/*#__PURE__*/React.createElement("span",{className:"inline-flex items-center shrink-0"},/*#__PURE__*/React.createElement("i",{"data-lucide":"scissors",className:"w-3 h-3"}))," Cut"),/*#__PURE__*/React.createElement("button",{onClick:async e=>{subTabAiTrackIdRef.current=st.trackId;if(st.buffer){setSelectionRangeOnBuffer(st.buffer,st.selectionStart||0,st.selectionEnd||st.buffer.duration);}handleAIAnalysicLoop();},disabled:analysisState.isRunning,className:"py-1 bg-violet-800 hover:bg-violet-700 text-violet-100 font-bold rounded text-xs border border-violet-600 flex items-center justify-center gap-1 col-span-2"},/*#__PURE__*/React.createElement("span",{className:"inline-flex items-center shrink-0"},/*#__PURE__*/React.createElement("i",{"data-lucide":"sparkles",className:"w-3 h-3"}))," AI Analysic Loop")),/*#__PURE__*/React.createElement("button",{onClick:()=>exportSubTabBuffer(st.id),className:"w-full py-1.5 mb-1.5 bg-blue-700 hover:bg-blue-600 text-white font-bold rounded text-[14px] flex items-center justify-center gap-1 transition"},/*#__PURE__*/React.createElement("span",{className:"inline-flex items-center shrink-0"},/*#__PURE__*/React.createElement("i",{"data-lucide":"download",className:"w-4 h-4"}))," Export"),/*#__PURE__*/React.createElement("button",{onClick:()=>applySubTab(st.id),className:"w-full py-1.5 bg-emerald-700 hover:bg-emerald-600 text-white font-bold rounded text-[14px] flex items-center justify-center gap-1 transition"},/*#__PURE__*/React.createElement("span",{className:"inline-flex items-center shrink-0"},/*#__PURE__*/React.createElement("i",{"data-lucide":"save",className:"w-4 h-4"}))," Save"))):/*#__PURE__*/React.createElement("div",{className:"flex-1 flex items-center justify-center text-xs text-zinc-500"},"Track not found")),/*#__PURE__*/React.createElement("div",{ref:handleTimelineWrapperRef,className:"flex-1 relative z-10 overflow-x-auto overflow-y-auto min-w-0"},/*#__PURE__*/React.createElement("div",{style:{width:`${subTabTimelineWidth}px`},className:"relative flex flex-col min-h-full"},/*#__PURE__*/React.createElement("div",{className:"sticky top-0 z-45 flex h-10 border-b border-zinc-900 bg-[#242424] shrink-0"},/*#__PURE__*/React.createElement("div",{className:"flex-1 relative h-full flex items-center select-none cursor-ew-resize overflow-hidden",onMouseDown:handleRulerMouseDown},Array.from({length:Math.ceil(st.buffer?st.buffer.duration:0)}).map((_,i)=>{const sec=i;const x=sec*zoom;return/*#__PURE__*/React.createElement("div",{key:i,className:"absolute h-full border-l border-zinc-700 pl-1 pt-1 text-[14px] font-mono text-zinc-300 pointer-events-none",style:{left:`${x}px`}},formatTimeSimple(sec));}))),/*#__PURE__*/React.createElement("div",{className:"sticky top-10 z-35 flex h-[40px] border-b border-purple-500/30 bg-[#1a1a2e] shrink-0"},/*#__PURE__*/React.createElement("div",{className:"flex-1 relative h-full overflow-hidden bg-[#1a1a2e]"},/*#__PURE__*/React.createElement(TempoTrackLane,{bpm:parseInt(bpm)||120,zoom:zoom,timelineWidth:subTabTimelineWidth,onPlayheadSet:setCurrentTime,snapValue:snapValue}))),/*#__PURE__*/React.createElement("div",{className:"flex-1 flex flex-col relative bg-[#111111] min-h-full"},vTrack&&/*#__PURE__*/React.createElement("div",{style:{height:`${subTabHeight}px`},className:"relative overflow-hidden border-2 border-purple-500/40 rounded bg-[#151515] mx-1 mt-1 shrink-0"},/*#__PURE__*/React.createElement(SubTabWaveform,{buffer:st.buffer,subTabId:st.id,activeTab:activeTab,activeTool:activeTool,currentTime:st.currentTime,selectionStart:st.selectionStart,selectionEnd:st.selectionEnd,onSelectRange:(start,end)=>setSubTabs(prev=>prev.map(s=>s.id===st.id?{...s,selectionStart:start,selectionEnd:end}:s)),onPlayheadSet:time=>setSubTabs(prev=>prev.map(s=>s.id===st.id?{...s,currentTime:time}:s)),onContextMenu:(e,clickTime)=>setContextMenu({x:e.clientX,y:e.clientY,isSubTab:true,subTabId:st.id,time:clickTime}),zoom:zoom,timelineWidth:subTabTimelineWidth,color:vTrack.color,name:vTrack.name,speed:st.speed||1.0,volumeNodes:st.volumeNodes||[],panningNodes:st.panningNodes||[],fadeInLen:st.fadeInLen||0,fadeOutLen:st.fadeOutLen||0,graphMode:st.graphMode,channelInfo:st.channelInfo,selectedNodeTime:subTabSelectedNodeTime,setSelectedNodeTime:setSubTabSelectedNodeTime,onUpdateNodes:nodes=>setSubTabs(prev=>prev.map(s=>s.id===st.id?{...s,[s.graphMode==='pan'?'panningNodes':'volumeNodes']:nodes}:s)),onUpdateFade:fade=>setSubTabs(prev=>prev.map(s=>s.id===st.id?{...s,fadeInLen:fade.fadeInLen??s.fadeInLen,fadeOutLen:fade.fadeOutLen??s.fadeOutLen}:s)),onModeToggle:()=>setSubTabs(prev=>prev.map(s=>s.id===st.id?{...s,graphMode:s.graphMode==='pan'?null:'pan'}:s)),onSpeedChange:newSpeed=>{setSubTabs(prev=>prev.map(s=>{if(s.id!==st.id)return s;const oldSpeed=s.speed||1.0;const ratio=oldSpeed/newSpeed;const newVolumeNodes=(s.volumeNodes||[]).map(n=>({...n,time:n.time*ratio}));const newPanningNodes=(s.panningNodes||[]).map(n=>({...n,time:n.time*ratio}));return{...s,speed:newSpeed,volumeNodes:newVolumeNodes,panningNodes:newPanningNodes,fadeInLen:(s.fadeInLen||0)*ratio,fadeOutLen:(s.fadeOutLen||0)*ratio,currentTime:(s.currentTime||0)*ratio,label:s.label.replace(/\s\(\d+%\)$/,'')+` (${Math.round(newSpeed*100)}%)`};}));const n=activeTrackNodesRef.current[st.trackId];if(n&&n.source)n.source.playbackRate.value=newSpeed;// Reset time refs to prevent playhead jump when speed changes mid-playback -const ctx=getAudioContext();const elapsed=ctx.currentTime-startAudioTimeRef.current;const oldSpeed=activePlaybackSpeedRef.current;const ratio=oldSpeed/newSpeed;startBufferOffsetRef.current=startBufferOffsetRef.current+elapsed*oldSpeed;startOffsetTimeRef.current=(startOffsetTimeRef.current+elapsed)*ratio;startAudioTimeRef.current=ctx.currentTime;activePlaybackSpeedRef.current=newSpeed;}}),/*#__PURE__*/React.createElement("div",{onMouseDown:handleSubTabResizeMouseDown,className:"absolute bottom-0 left-0 right-0 h-1.5 cursor-ns-resize z-30 hover:bg-purple-500/50 transition-colors"})),st.selectionStart!==null&&st.selectionEnd!==null&&st.selectionEnd>st.selectionStart&&/*#__PURE__*/React.createElement("div",{className:"absolute top-0 bottom-0 border-l border-r border-amber-500 bg-amber-500/10 z-20 pointer-events-none",style:{left:`${Math.min(st.selectionStart,st.selectionEnd)*zoom}px`,width:`${Math.abs(st.selectionEnd-st.selectionStart)*zoom}px`}})))));})())),/*#__PURE__*/React.createElement("div",{className:"w-1 cursor-ew-resize hover:bg-cyan-500/50 active:bg-cyan-400 transition-colors shrink-0 z-10",onMouseDown:startColResize}),renderDock('right','Right')),renderDock('bottom','Bottom'));})(),/*#__PURE__*/React.createElement("div",{className:"h-6 bg-[#141414] border-t border-zinc-900 px-4 flex items-center justify-between text-xs text-zinc-500 select-none shrink-0"},/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-4"},/*#__PURE__*/React.createElement("span",null,"Status: ",isPlaying?'Playing':'Stopped'),activeTab!=='main'&&/*#__PURE__*/React.createElement("span",{className:"text-amber-400 font-semibold uppercase"},"Sub-Tab"),activeTab==='main'&&/*#__PURE__*/React.createElement("span",{className:"text-cyan-400 font-semibold uppercase"},"Track: ID ",selectedTrackId),selectionMode==='local'&&/*#__PURE__*/React.createElement("span",{className:"text-amber-400 font-semibold uppercase text-xs"},"Local Sel"),selectionMode==='global'&&/*#__PURE__*/React.createElement("span",{className:"text-purple-400 font-semibold uppercase text-xs"},"Global Sel"),soloedTrackId&&/*#__PURE__*/React.createElement("span",{className:"text-amber-500 font-semibold"},"Solo: ID ",soloedTrackId),isLoopingSelection&&selectionMode==='local'&&/*#__PURE__*/React.createElement("span",{className:"text-emerald-400 font-semibold uppercase text-xs"},"Solo Loop"),isLoopingSelection&&selectionMode!=='local'&&/*#__PURE__*/React.createElement("span",{className:"text-cyan-400 font-semibold uppercase text-xs"},"Master Loop")),/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-2"},/*#__PURE__*/React.createElement("button",{onClick:()=>setShowExportPanel(p=>!p),className:`px-1.5 py-0.5 rounded text-xs font-semibold transition flex items-center gap-1 ${showExportPanel?'bg-cyan-900 text-cyan-300':'text-zinc-500 hover:text-zinc-300'}`,title:`Export Panel (${panelPositions.export})`},/*#__PURE__*/React.createElement("span",{className:"inline-flex items-center shrink-0"},/*#__PURE__*/React.createElement("i",{"data-lucide":"save",className:"w-3 h-3"})),/*#__PURE__*/React.createElement("span",{className:"text-[7px] opacity-60"},showExportPanel?panelPositions.export[0].toUpperCase():'')),/*#__PURE__*/React.createElement("button",{onClick:()=>setShowSelectionPanel(p=>!p),className:`px-1.5 py-0.5 rounded text-xs font-semibold transition flex items-center gap-1 ${showSelectionPanel?'bg-amber-900 text-amber-300':'text-zinc-500 hover:text-zinc-300'}`,title:`Selection Panel (${panelPositions.selection})`},/*#__PURE__*/React.createElement("span",{className:"inline-flex items-center shrink-0"},/*#__PURE__*/React.createElement("i",{"data-lucide":"sliders",className:"w-3 h-3"})),/*#__PURE__*/React.createElement("span",{className:"text-[7px] opacity-60"},showSelectionPanel?panelPositions.selection[0].toUpperCase():'')),/*#__PURE__*/React.createElement("button",{onClick:()=>setShowAIPanel(p=>!p),className:`px-1.5 py-0.5 rounded text-xs font-semibold transition flex items-center gap-1 ${showAIPanel?'bg-purple-900 text-purple-300':'text-zinc-500 hover:text-zinc-300'}`,title:`AI Panel (${panelPositions.ai})`},/*#__PURE__*/React.createElement("span",{className:"inline-flex items-center shrink-0"},/*#__PURE__*/React.createElement("i",{"data-lucide":"cpu",className:"w-3 h-3"})),/*#__PURE__*/React.createElement("span",{className:"text-[7px] opacity-60"},showAIPanel?panelPositions.ai[0].toUpperCase():'')),/*#__PURE__*/React.createElement("button",{onClick:()=>setShowFxRack(p=>!p),className:`px-1.5 py-0.5 rounded text-xs font-semibold transition flex items-center gap-1 ${showFxRack?'bg-rose-900 text-rose-300':'text-zinc-500 hover:text-zinc-300'}`,title:`FX Rack Panel (${panelPositions.fx_rack||'bottom'})`},/*#__PURE__*/React.createElement("span",{className:"inline-flex items-center shrink-0"},/*#__PURE__*/React.createElement("i",{"data-lucide":"sliders",className:"w-3 h-3"})),/*#__PURE__*/React.createElement("span",{className:"text-[7px] opacity-60"},showFxRack?(panelPositions.fx_rack||'bottom')[0].toUpperCase():'')),/*#__PURE__*/React.createElement("button",{onClick:()=>setShowMidiEvents(p=>!p),className:`px-1.5 py-0.5 rounded text-xs font-semibold transition flex items-center gap-1 ${showMidiEvents?'bg-sky-900 text-sky-300':'text-zinc-500 hover:text-zinc-300'}`,title:`MIDI Events Panel (${panelPositions.midi_events||'bottom'})`},/*#__PURE__*/React.createElement("span",{className:"inline-flex items-center shrink-0"},/*#__PURE__*/React.createElement("i",{"data-lucide":"music",className:"w-3 h-3"})),/*#__PURE__*/React.createElement("span",{className:"text-[7px] opacity-60"},showMidiEvents?(panelPositions.midi_events||'bottom')[0].toUpperCase():'')),/*#__PURE__*/React.createElement("span",{className:"w-[1px] h-3 bg-zinc-800 mx-1"}),/*#__PURE__*/React.createElement("span",{className:"flex items-center gap-1"},/*#__PURE__*/React.createElement("span",{className:"inline-flex items-center shrink-0"},/*#__PURE__*/React.createElement("i",{"data-lucide":"info",className:"w-3 h-3 text-zinc-600"}))," Scroll: Zoom"),/*#__PURE__*/React.createElement("span",null,"|"),/*#__PURE__*/React.createElement("span",{className:"flex items-center gap-1"},/*#__PURE__*/React.createElement("span",{className:"inline-flex items-center shrink-0"},/*#__PURE__*/React.createElement("i",{"data-lucide":"keyboard",className:"w-3 h-3 text-zinc-600"}))," Ctrl+Scroll: Playhead"))),contextMenu&&(contextMenu.isSubTab?/*#__PURE__*/React.createElement("div",{className:"fixed z-[60] bg-[#2a2a2a] border border-zinc-700 rounded-lg shadow-2xl py-1 w-64",style:{left:contextMenu.x,top:contextMenu.y},onClick:e=>e.stopPropagation()},/*#__PURE__*/React.createElement("div",{className:"px-3 py-1 text-xs text-zinc-500 font-bold uppercase border-b border-zinc-800 pb-1 mb-1"},"Selection: ",formatTime(subTabs.find(s=>s.id===contextMenu.subTabId)?.selectionStart||0)," - ",formatTime(subTabs.find(s=>s.id===contextMenu.subTabId)?.selectionEnd||0)),/*#__PURE__*/React.createElement("div",{className:"h-px bg-zinc-700 my-1"}),/*#__PURE__*/React.createElement("button",{onClick:()=>{handleSubTabCut(contextMenu.subTabId);closeContextMenu();},className:"w-full px-3 py-1.5 text-xs text-zinc-200 hover:bg-zinc-700 text-left flex items-center gap-2"},/*#__PURE__*/React.createElement("span",{className:"inline-flex items-center shrink-0"},/*#__PURE__*/React.createElement("i",{"data-lucide":"scissors",className:"w-3.5 h-3.5 text-rose-400 shrink-0"})),/*#__PURE__*/React.createElement("span",{className:"flex-1"},"Cut"),/*#__PURE__*/React.createElement("span",{className:"text-purple-400 text-xs font-semibold font-mono ml-auto"},"Ctrl+X")),/*#__PURE__*/React.createElement("button",{onClick:()=>{handleSubTabCopy(contextMenu.subTabId);closeContextMenu();},className:"w-full px-3 py-1.5 text-xs text-zinc-200 hover:bg-zinc-700 text-left flex items-center gap-2"},/*#__PURE__*/React.createElement("span",{className:"inline-flex items-center shrink-0"},/*#__PURE__*/React.createElement("i",{"data-lucide":"copy",className:"w-3.5 h-3.5 text-zinc-400 shrink-0"})),/*#__PURE__*/React.createElement("span",{className:"flex-1"},"Copy"),/*#__PURE__*/React.createElement("span",{className:"text-purple-400 text-xs font-semibold font-mono ml-auto"},"Ctrl+C")),/*#__PURE__*/React.createElement("button",{onClick:()=>{handleSubTabPaste(contextMenu.subTabId);closeContextMenu();},className:"w-full px-3 py-1.5 text-xs text-zinc-200 hover:bg-zinc-700 text-left flex items-center gap-2"},/*#__PURE__*/React.createElement("span",{className:"inline-flex items-center shrink-0"},/*#__PURE__*/React.createElement("i",{"data-lucide":"clipboard",className:"w-3.5 h-3.5 text-emerald-400 shrink-0"})),/*#__PURE__*/React.createElement("span",{className:"flex-1"},"Paste"),/*#__PURE__*/React.createElement("span",{className:"text-purple-400 text-xs font-semibold font-mono ml-auto"},"Ctrl+V")),/*#__PURE__*/React.createElement("button",{onClick:()=>{handleSubTabDelete(contextMenu.subTabId);closeContextMenu();},className:"w-full px-3 py-1.5 text-xs text-zinc-200 hover:bg-zinc-700 text-left flex items-center gap-2"},/*#__PURE__*/React.createElement("span",{className:"inline-flex items-center shrink-0"},/*#__PURE__*/React.createElement("i",{"data-lucide":"trash-2",className:"w-3.5 h-3.5 text-red-400 shrink-0"})),/*#__PURE__*/React.createElement("span",{className:"flex-1"},"Delete Selected Segment"),/*#__PURE__*/React.createElement("span",{className:"text-amber-400 text-xs font-semibold font-mono ml-auto"},"Del")),/*#__PURE__*/React.createElement("div",{className:"h-px bg-zinc-700 my-1"}),/*#__PURE__*/React.createElement("button",{onClick:()=>{handleSubTabLoop(contextMenu.subTabId,4);closeContextMenu();},className:"w-full px-3 py-1.5 text-xs text-zinc-200 hover:bg-zinc-700 text-left flex items-center gap-2"},/*#__PURE__*/React.createElement("span",{className:"inline-flex items-center shrink-0"},/*#__PURE__*/React.createElement("i",{"data-lucide":"repeat",className:"w-3.5 h-3.5 text-cyan-400 shrink-0"})),/*#__PURE__*/React.createElement("span",{className:"flex-1"},"Loop Selection 4 times"),/*#__PURE__*/React.createElement("span",{className:"text-purple-400 text-xs font-semibold font-mono ml-auto"},"Ctrl+L"))):/*#__PURE__*/React.createElement("div",{className:"fixed z-[60] bg-[#2a2a2a] border border-zinc-700 rounded-lg shadow-2xl py-1 w-64",style:{left:contextMenu.x,top:contextMenu.y},onClick:e=>e.stopPropagation()},contextMenu.sectionId?/*#__PURE__*/React.createElement("button",{onClick:contextMenuEditSection,className:"w-full px-3 py-1.5 text-xs text-zinc-200 hover:bg-zinc-700 text-left flex items-center gap-2"},/*#__PURE__*/React.createElement("span",{className:"inline-flex items-center shrink-0"},/*#__PURE__*/React.createElement("i",{"data-lucide":"file-edit",className:"w-3.5 h-3.5 text-amber-400 shrink-0"})),/*#__PURE__*/React.createElement("span",{className:"flex-1"},"Edit Section"),/*#__PURE__*/React.createElement("span",{className:"text-purple-400 text-xs font-semibold font-mono ml-auto pl-8"},"Ctrl+E")):/*#__PURE__*/React.createElement("button",{onClick:contextMenuEdit,className:"w-full px-3 py-1.5 text-xs text-zinc-200 hover:bg-zinc-700 text-left flex items-center gap-2"},/*#__PURE__*/React.createElement("span",{className:"inline-flex items-center shrink-0"},/*#__PURE__*/React.createElement("i",{"data-lucide":"file-edit",className:"w-3.5 h-3.5 text-amber-400 shrink-0"})),/*#__PURE__*/React.createElement("span",{className:"flex-1"},"Edit"),/*#__PURE__*/React.createElement("span",{className:"text-purple-400 text-xs font-semibold font-mono ml-auto pl-8"},"Ctrl+E")),/*#__PURE__*/React.createElement("button",{onClick:contextMenuSplit,className:"w-full px-3 py-1.5 text-xs text-zinc-200 hover:bg-zinc-700 text-left flex items-center gap-2"},/*#__PURE__*/React.createElement("span",{className:"inline-flex items-center shrink-0"},/*#__PURE__*/React.createElement("i",{"data-lucide":"scissors",className:"w-3.5 h-3.5 text-cyan-400 shrink-0"})),/*#__PURE__*/React.createElement("span",{className:"flex-1"},"Split"),/*#__PURE__*/React.createElement("span",{className:"text-purple-400 text-xs font-semibold font-mono ml-auto pl-8"},"S")),/*#__PURE__*/React.createElement("button",{onClick:contextMenuMerge,className:"w-full px-3 py-1.5 text-xs text-zinc-200 hover:bg-zinc-700 text-left flex items-center gap-2"},/*#__PURE__*/React.createElement("span",{className:"inline-flex items-center shrink-0"},/*#__PURE__*/React.createElement("i",{"data-lucide":"combine",className:"w-3.5 h-3.5 text-purple-400 shrink-0"})),/*#__PURE__*/React.createElement("span",{className:"flex-1"},"Merge"),/*#__PURE__*/React.createElement("span",{className:"text-purple-400 text-xs font-semibold font-mono ml-auto pl-8"},"Ctrl+M")),/*#__PURE__*/React.createElement("div",{className:"h-px bg-zinc-700 my-1"}),/*#__PURE__*/React.createElement("button",{onClick:contextMenuCopy,className:"w-full px-3 py-1.5 text-xs text-zinc-200 hover:bg-zinc-700 text-left flex items-center gap-2"},/*#__PURE__*/React.createElement("span",{className:"inline-flex items-center shrink-0"},/*#__PURE__*/React.createElement("i",{"data-lucide":"copy",className:"w-3.5 h-3.5 text-zinc-400 shrink-0"})),/*#__PURE__*/React.createElement("span",{className:"flex-1"},"Copy"),/*#__PURE__*/React.createElement("span",{className:"text-purple-400 text-xs font-semibold font-mono ml-auto pl-8"},"Ctrl+C")),/*#__PURE__*/React.createElement("button",{onClick:contextMenuCut,className:"w-full px-3 py-1.5 text-xs text-zinc-200 hover:bg-zinc-700 text-left flex items-center gap-2"},/*#__PURE__*/React.createElement("span",{className:"inline-flex items-center shrink-0"},/*#__PURE__*/React.createElement("i",{"data-lucide":"scissors",className:"w-3.5 h-3.5 text-rose-400 shrink-0"})),/*#__PURE__*/React.createElement("span",{className:"flex-1"},"Cut"),/*#__PURE__*/React.createElement("span",{className:"text-purple-400 text-xs font-semibold font-mono ml-auto pl-8"},"Ctrl+X")),/*#__PURE__*/React.createElement("button",{onClick:contextMenuPaste,className:"w-full px-3 py-1.5 text-xs text-zinc-200 hover:bg-zinc-700 text-left flex items-center gap-2"},/*#__PURE__*/React.createElement("span",{className:"inline-flex items-center shrink-0"},/*#__PURE__*/React.createElement("i",{"data-lucide":"clipboard",className:"w-3.5 h-3.5 text-emerald-400 shrink-0"})),/*#__PURE__*/React.createElement("span",{className:"flex-1"},"Paste"),/*#__PURE__*/React.createElement("span",{className:"text-purple-400 text-xs font-semibold font-mono ml-auto pl-8"},"Ctrl+V")),/*#__PURE__*/React.createElement("div",{className:"h-px bg-zinc-700 my-1"}),/*#__PURE__*/React.createElement("button",{onClick:contextMenuDelete,className:"w-full px-3 py-1.5 text-xs text-red-300 hover:bg-red-950 text-left flex items-center gap-2"},/*#__PURE__*/React.createElement("span",{className:"inline-flex items-center shrink-0"},/*#__PURE__*/React.createElement("i",{"data-lucide":"trash-2",className:"w-3.5 h-3.5 text-red-400 shrink-0"})),/*#__PURE__*/React.createElement("span",{className:"flex-1"},"Delete"),/*#__PURE__*/React.createElement("span",{className:"text-amber-400 text-xs font-semibold font-mono ml-auto pl-8"},"Del")))),toastMessage&&/*#__PURE__*/React.createElement("div",{className:"absolute top-14 right-4 z-50 px-4 py-2.5 rounded shadow-lg text-xs font-semibold flex items-center gap-2 border bg-zinc-900 text-zinc-100 border-zinc-800"},/*#__PURE__*/React.createElement("span",{className:"inline-flex items-center shrink-0"},/*#__PURE__*/React.createElement("i",{"data-lucide":"bell",className:`w-4 h-4 ${toastMessage.type==='success'?'text-emerald-400':toastMessage.type==='error'?'text-rose-400':toastMessage.type==='warning'?'text-amber-400':'text-cyan-400'}`})),toastMessage.text,toastMessage.onActionClick&&/*#__PURE__*/React.createElement("button",{onClick:()=>{toastMessage.onActionClick();setToastMessage(null);},className:"ml-2 px-2 py-0.5 bg-purple-700 hover:bg-purple-600 text-white rounded text-[10px] uppercase font-bold"},toastMessage.actionText||'Tải về'))),appWarningModal&&/*#__PURE__*/React.createElement("div",{className:"fixed inset-0 z-[100] flex items-center justify-center bg-black/60 backdrop-blur-sm"},/*#__PURE__*/React.createElement("div",{className:"bg-[#222222] border border-zinc-800 rounded-lg shadow-2xl p-6 max-w-md w-full text-slate-200"},/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-2 mb-3 text-cyan-400 font-bold"},/*#__PURE__*/React.createElement("h3",{className:"text-base font-bold"},appWarningModal.title)),/*#__PURE__*/React.createElement("div",{className:"text-xs text-slate-300 mb-5 leading-relaxed whitespace-pre-line"},appWarningModal.message),/*#__PURE__*/React.createElement("div",{className:"flex justify-end gap-2"},appWarningModal.isAlert?/*#__PURE__*/React.createElement("button",{onClick:()=>setAppWarningModal(null),className:"px-4 py-2 bg-cyan-600 hover:bg-cyan-500 active:bg-cyan-700 text-white rounded text-xs font-semibold transition"},"Đóng"):/*#__PURE__*/React.createElement(React.Fragment,null,/*#__PURE__*/React.createElement("button",{onClick:()=>setAppWarningModal(null),className:"px-4 py-2 bg-zinc-700 hover:bg-zinc-600 active:bg-zinc-800 text-slate-300 rounded text-xs font-semibold transition"},"Hủy"),/*#__PURE__*/React.createElement("button",{onClick:()=>{const fn=appWarningModal.onConfirm;setAppWarningModal(null);if(fn)fn();},className:"px-4 py-2 bg-cyan-600 hover:bg-cyan-500 active:bg-cyan-700 text-white rounded text-xs font-semibold transition"},"Xác nhận"))))),/*#__PURE__*/React.createElement(AuthModal,{isOpen:authModalOpen,mode:authMode,forceMandatory:isMandatoryLogin,onClose:()=>setAuthModalOpen(false),onSuccess:handleAuthSuccess}),/*#__PURE__*/React.createElement(ProfileModal,{isOpen:profileModalOpen,onClose:()=>setProfileModalOpen(false),tracks:tracks,setTracks:setTracks,setSelectedTrackId:setSelectedTrackId,projectName:projectName,setProjectName:setProjectName,currentProjectId:currentProjectId,setCurrentProjectId:setCurrentProjectId,showToast:showToast}),/*#__PURE__*/React.createElement(SaveProjectModal,{isOpen:saveProjectModalOpen,onClose:()=>setSaveProjectModalOpen(false),onSave:newName=>{handleSaveProjectWithName(newName);}}),/*#__PURE__*/React.createElement(SaveAsModal,{isOpen:saveAsModalOpen,onClose:()=>setSaveAsModalOpen(false),projectName:projectName,onSaveCloud:newName=>{handleSaveAsCloud(newName);},onSaveLocal:newName=>{handleExportSFS(newName);setProjectName(newName);localStorage.setItem('sonic_project_name',newName);}}),/*#__PURE__*/React.createElement(AIConfigModal,{isOpen:aiConfigModalOpen,onClose:()=>setAiConfigModalOpen(false),onConfigSaved:providers=>{setAiProviders(providers);const active=providers.find(p=>p.is_active)||providers[0];if(active)setSelectedProviderId(active.id);}}),/*#__PURE__*/React.createElement(SystemManagerModal,{isOpen:systemManagerModalOpen,onClose:()=>setSystemManagerModalOpen(false)}),/*#__PURE__*/React.createElement(PluginManagerModal,{isOpen:pluginManagerModalOpen,onClose:()=>setPluginManagerModalOpen(false),pluginsData:pluginsData}),/*#__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)"),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.map((p,i)=>React.createElement("button",{key:i,onClick:()=>setTrackInstrumentWithProgram(instrumentSelectorTrackId,selectedSoundFontId,p.program,p.name||'Preset '+p.program),className:"text-left px-2 py-1 text-[10px] rounded bg-zinc-800/60 hover:bg-amber-800 text-zinc-300 truncate"},p.name||'Preset '+p.program))):React.createElement("p",{className:"text-[10px] text-zinc-500 py-2"},"No presets found.")))):(/*#__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.")))))));};const root=ReactDOM.createRoot(document.getElementById('root'));root.render(/*#__PURE__*/React.createElement(App,null));setTimeout(()=>lucide.createIcons(),300); +const prefsRef=useRef({});prefsRef.current={showAIPanel,showExportPanel,showSelectionPanel,showPythonToolsPanel,showMediaExplorer,showFxRack,showMidiEvents,panelPositions,rightSidebarWidth,mediaExplorerHeight,selectedProviderId};useEffect(()=>{const prefs=prefsRef.current;localStorage.setItem('sonic_preferences',JSON.stringify(prefs));if(!currentUser||currentUser==='cached')return;const timer=setTimeout(async()=>{try{await window.SonicAPI.savePreferences(prefs);}catch(e){}},2000);return()=>clearTimeout(timer);},[showAIPanel,showExportPanel,showSelectionPanel,showPythonToolsPanel,showMediaExplorer,showFxRack,showMidiEvents,panelPositions,rightSidebarWidth,mediaExplorerHeight,selectedProviderId,currentUser]);return/*#__PURE__*/React.createElement(React.Fragment,null,/*#__PURE__*/React.createElement("div",{className:"h-full w-full flex flex-col bg-[#1e1e1e]"},/*#__PURE__*/React.createElement("header",{className:"h-9 bg-[#2e2e2e] border-b border-[#181818] flex items-center px-1 shrink-0 select-none"},[{label:'File',items:[{label:'New Project',icon:'file-plus',shortcut:'Ctrl+N',action:()=>{setTracks([{id:'1',name:'Track 01',buffer:null,startTime:0,volumeDb:0,pan:0,muted:false,solo:false,color:'#0f766e',markers:[],serverFileId:null,clips:[],sections:[],midiItems:[]},{id:'2',name:'Track 02',buffer:null,startTime:0,volumeDb:0,pan:0,muted:false,solo:false,color:'#1d4ed8',markers:[],serverFileId:null,clips:[],sections:[],midiItems:[]}]);setSelectedTrackId('1');setProjectName('');setCurrentProjectId(null);localStorage.removeItem('sonic_project_name');localStorage.removeItem('sonic_project_id');showToast('New project created','info');}},{label:'Open Project...',icon:'folder-open',shortcut:'Ctrl+O',action:()=>handleImportSFS()},{label:'Save Project',icon:'upload-cloud',shortcut:'Ctrl+S',action:()=>handleSaveProject()},{label:'Save As...',icon:'download',shortcut:'Ctrl+Alt+S',action:()=>setSaveAsModalOpen(true)},{sep:true},{label:'Config AI Providers...',icon:'settings',action:()=>setAiConfigModalOpen(true)},{label:'Import Audio...',icon:'file-input',shortcut:'Ctrl+Alt+I',action:()=>{const input=document.createElement('input');input.type='file';input.accept='audio/*';input.onchange=async e=>{if(e.target.files[0]){addNewTrack();const newId=(tracks.length+1).toString();setTimeout(()=>loadFileOnTrack(newId,e.target.files[0]),100);}};input.click();showToast('Import audio','info');}},{label:'Export Mix...',icon:'file-output',action:()=>triggerWavExport()},{label:'Export MIDI...',icon:'music',action:()=>triggerMidiExport()},{sep:true},...(currentUser?[{label:'Profile',icon:'user',action:()=>setProfileModalOpen(true)}]:[]),...(currentUser&¤tUser.role==='admin'?[{label:'System Manager',icon:'settings',action:()=>setSystemManagerModalOpen(true)}]:[]),{label:'Logout',icon:'log-out',action:()=>handleLogout()}]},{label:'Edit',items:[{label:'Insert New Track',icon:'plus',shortcut:'Ctrl+I',action:addNewTrack},{label:'Insert Music to Track',icon:'music',shortcut:'Ctrl+Alt+I',action:()=>showToast('Select music file to insert','info')},{sep:true},{label:'Edit in New Tab',icon:'file-edit',shortcut:'Ctrl+E',action:()=>openTempTab()},{label:'Split at Playhead',icon:'scissors',shortcut:'S',action:()=>handleSplitTrack(selectedTrackId)},{label:'Merge Tracks',icon:'combine',shortcut:'Ctrl+M',action:()=>{handleMergeTracks();}},{sep:true},{label:'Undo',icon:'undo',shortcut:'Ctrl+Z',action:()=>{handleUndo();}},{label:'Redo',icon:'redo',shortcut:'Ctrl+Y',action:()=>{handleRedo();}},{sep:true},{label:'Copy',icon:'copy',shortcut:'Ctrl+C',action:()=>{handleCopyTrack();}},{label:'Cut',icon:'scissors',shortcut:'Ctrl+X',action:()=>{handleCutTrack();}},{label:'Paste',icon:'clipboard',shortcut:'Ctrl+V',action:handlePasteTrack},{sep:true},{label:'Delete Track',icon:'trash-2',shortcut:'Del',action:()=>{handleDeleteTrack();}}]},{label:'Insert',items:[...(!sessionTabs.some(s=>s.id===activeTab)?[{label:'Insert Section',icon:'folder-plus',action:insertSectionAtPlayhead}]:[]),{label:'Insert MIDI item',icon:'music',action:insertMidiItemAtPlayhead},{label:'Insert sound clip',icon:'file-input',action:insertSoundClipAtCursor},{label:'Insert track',icon:'plus',action:insertTrackBelow}]},{label:'View',items:[{label:'Master Track',icon:'disc',action:()=>showToast('Master track view','info')},{label:'Maker View',icon:'layout',action:()=>showToast('Maker view','info')},{label:'Mixer',icon:'sliders',action:()=>showToast('Mixer panel','info')},{label:'Tempo Track',icon:'timer',action:()=>showToast('Tempo track','info')},{label:'Video',icon:'film',action:()=>showToast('Video panel','info')},{label:'Media Explorer',icon:'folder-search',action:()=>showToast('Media explorer','info')}]},{label:'Tools',items:[{label:'Config AI Providers...',icon:'settings',action:()=>setAiConfigModalOpen(true)},{label:'AI MIDI Preset Manager...',icon:'sliders',action:()=>setAiPresetModalOpen(true)},{label:'DSP Tools Panel',icon:'wrench',action:()=>openPanel('python_tools')},{sep:true},{label:'Plugin Manager (SoundFont/VSTi)',icon:'zap',action:()=>{setPluginManagerModalOpen(true);window.SonicAPI.listPlugins().then(data=>setPluginsData(data)).catch(()=>{});}}]},{label:'Help',items:[{label:'About SonicForge',icon:'info',action:()=>showToast('SonicForge Studio v1.0 - Professional DAW','info')}]}].map(menu=>/*#__PURE__*/React.createElement("div",{key:menu.label,className:"relative"},/*#__PURE__*/React.createElement("button",{onClick:()=>setMenuOpen(menuOpen===menu.label?null:menu.label),className:`px-3 py-1 text-xs font-medium transition rounded ${menuOpen===menu.label?'bg-zinc-700 text-zinc-100':'text-zinc-400 hover:text-zinc-200 hover:bg-zinc-800'}`},menu.label),menuOpen===menu.label&&/*#__PURE__*/React.createElement("div",{className:`absolute top-full left-0 mt-0.5 bg-[#2a2a2a] border border-zinc-700 rounded-lg shadow-2xl py-1 z-50 ${menu.label==='Edit'?'w-72':'w-64'}`,onClick:()=>setMenuOpen(null)},menu.items.map((item,i)=>item.sep?/*#__PURE__*/React.createElement("div",{key:i,className:"h-px bg-zinc-700 my-1"}):/*#__PURE__*/React.createElement("button",{key:item.label,onClick:e=>{e.stopPropagation();item.action();setMenuOpen(null);},className:"w-full px-3 py-1.5 text-xs text-zinc-200 hover:bg-zinc-700 text-left flex items-center gap-2"},/*#__PURE__*/React.createElement("span",{className:"inline-flex items-center shrink-0"},/*#__PURE__*/React.createElement("i",{"data-lucide":item.icon,className:"w-3.5 h-3.5 text-zinc-500 shrink-0"})),/*#__PURE__*/React.createElement("span",{className:"flex-1"},item.label),item.shortcut&&/*#__PURE__*/React.createElement("span",{className:"text-purple-400 text-xs font-semibold font-mono ml-auto pl-8"},item.shortcut)))))),/*#__PURE__*/React.createElement("div",{className:"flex-1"}),/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-2 px-2"},/*#__PURE__*/React.createElement("span",{className:`text-xs font-bold uppercase px-1.5 py-0.5 rounded ${serverStatus==='connected'?'bg-emerald-950 text-emerald-400':serverStatus==='checking'?'bg-amber-950 text-amber-400':'bg-red-950 text-red-400'}`},"Server: ",serverStatus),/*#__PURE__*/React.createElement("button",{onClick:()=>setShowAIConfig(!showAIConfig),className:`px-1.5 py-0.5 rounded text-xs border transition ${showAIConfig?'bg-purple-900 text-purple-200 border-purple-700':'bg-zinc-800 text-zinc-400 border-zinc-700 hover:text-zinc-200'}`},/*#__PURE__*/React.createElement("span",{className:"inline-flex items-center shrink-0"},/*#__PURE__*/React.createElement("i",{"data-lucide":"cpu",className:"w-3 h-3"}))))),menuOpen&&/*#__PURE__*/React.createElement("div",{className:"fixed inset-0 z-40",onClick:()=>setMenuOpen(null)}),/*#__PURE__*/React.createElement("div",{className:"h-7 bg-[#222] border-b border-zinc-800 flex items-stretch px-2 gap-0.5 shrink-0 overflow-x-auto"},/*#__PURE__*/React.createElement("button",{onClick:()=>setActiveTab('main'),className:`px-3 text-xs font-bold uppercase tracking-wider border-b-2 transition flex items-center gap-1 ${activeTab==='main'?'text-cyan-400 border-cyan-500 bg-zinc-800/50':'text-zinc-500 border-transparent hover:text-zinc-300 hover:bg-zinc-800/30'}`},/*#__PURE__*/React.createElement("span",{className:"inline-flex items-center shrink-0"},/*#__PURE__*/React.createElement("i",{"data-lucide":"layout-dashboard",className:"w-3 h-3"}))," Main Session"),sessionTabs.map(st=>{const isTabActive=activeTab===st.id;const tabColor=st.color||(isTabActive?'#06b6d4':null);const borderStyle=isTabActive?{borderBottomColor:tabColor,color:tabColor}:st.color?{color:st.color,borderBottomColor:'transparent'}:{};return/*#__PURE__*/React.createElement("div",{key:st.id,className:"flex items-stretch"},/*#__PURE__*/React.createElement("button",{onClick:()=>setActiveTab(st.id),onAuxClick:e=>{if(e.button===1){e.preventDefault();closeSessionTab(st.id);}},onContextMenu:e=>{e.preventDefault();setTabContextMenu({x:e.clientX,y:e.clientY,tabId:st.id,tabType:'session'});},style:borderStyle,className:`px-2 text-xs font-medium border-b-2 transition flex items-center gap-1 ${isTabActive?'bg-zinc-800/50':'text-zinc-500 border-transparent hover:text-zinc-300 hover:bg-zinc-800/30'}`},/*#__PURE__*/React.createElement("span",{className:"inline-flex items-center shrink-0"},/*#__PURE__*/React.createElement("i",{"data-lucide":"layers",className:"w-3 h-3"})),/*#__PURE__*/React.createElement("span",{className:"max-w-[120px] truncate"},st.name),st.isDirty&&/*#__PURE__*/React.createElement("span",{className:"w-1.5 h-1.5 rounded-full bg-amber-500 ml-1",title:"Chưa lưu thay đổi"})),/*#__PURE__*/React.createElement("button",{onClick:()=>closeSessionTab(st.id),className:"px-1 text-zinc-600 hover:text-red-400 transition text-xs",title:"Close tab"},/*#__PURE__*/React.createElement("span",{className:"inline-flex items-center shrink-0"},/*#__PURE__*/React.createElement("i",{"data-lucide":"x",className:"w-3 h-3"}))));}),subTabs.map(st=>{const isTabActive=activeTab===st.id;const tabColor=st.color||(isTabActive?'#f59e0b':null);const borderStyle=isTabActive?{borderBottomColor:tabColor,color:tabColor}:st.color?{color:st.color,borderBottomColor:'transparent'}:{};const iconName=st.type==='PIANO_ROLL'?'music':'file-edit';return/*#__PURE__*/React.createElement("div",{key:st.id,className:"flex items-stretch"},/*#__PURE__*/React.createElement("button",{onClick:()=>setActiveTab(st.id),onAuxClick:e=>{if(e.button===1){e.preventDefault();closeSubTab(st.id);}},onContextMenu:e=>{e.preventDefault();setTabContextMenu({x:e.clientX,y:e.clientY,tabId:st.id,tabType:'sub'});},style:borderStyle,className:`px-2 text-xs font-medium border-b-2 transition flex items-center gap-1 ${isTabActive?'bg-zinc-800/50':'text-zinc-500 border-transparent hover:text-zinc-300 hover:bg-zinc-800/30'}`},/*#__PURE__*/React.createElement("span",{className:"inline-flex items-center shrink-0"},/*#__PURE__*/React.createElement("i",{"data-lucide":iconName,className:"w-3 h-3"})),/*#__PURE__*/React.createElement("span",{className:"max-w-[100px] truncate"},st.label),st.isDirty&&/*#__PURE__*/React.createElement("span",{className:"w-1.5 h-1.5 rounded-full bg-amber-500 ml-1",title:"Chưa lưu thay đổi"})),/*#__PURE__*/React.createElement("button",{onClick:()=>closeSubTab(st.id),className:"px-1 text-zinc-600 hover:text-red-400 transition text-xs",title:"Close tab"},/*#__PURE__*/React.createElement("span",{className:"inline-flex items-center shrink-0"},/*#__PURE__*/React.createElement("i",{"data-lucide":"x",className:"w-3 h-3"}))));})),showAIConfig&&/*#__PURE__*/React.createElement("div",{className:"bg-zinc-900 border-b border-purple-900 p-3 flex flex-col gap-2 transition-all"},/*#__PURE__*/React.createElement("div",{className:"text-xs font-bold text-purple-400 uppercase tracking-wider flex items-center gap-1"},/*#__PURE__*/React.createElement("span",{className:"inline-flex items-center shrink-0"},/*#__PURE__*/React.createElement("i",{"data-lucide":"cpu",className:"w-4 h-4"}))," Cấu hình cổng kết nối API"),/*#__PURE__*/React.createElement("div",{className:"grid grid-cols-1 md:grid-cols-3 gap-2 text-xs"},/*#__PURE__*/React.createElement("div",{className:"flex flex-col gap-1"},/*#__PURE__*/React.createElement("span",{className:"text-xs text-zinc-500 font-bold uppercase"},"Endpoint Base URL"),/*#__PURE__*/React.createElement("input",{type:"text",value:aiConfig.baseUrl,onChange:e=>setAiConfig(prev=>({...prev,baseUrl:e.target.value})),className:"bg-zinc-850 text-zinc-200 p-1.5 rounded border border-zinc-800 focus:outline-none focus:border-purple-600 font-mono text-xs",placeholder:"https://api.openai.com/v1"})),/*#__PURE__*/React.createElement("div",{className:"flex flex-col gap-1"},/*#__PURE__*/React.createElement("span",{className:"text-xs text-zinc-500 font-bold uppercase"},"API Token Key"),/*#__PURE__*/React.createElement("input",{type:"password",value:aiConfig.apiKey,onChange:e=>setAiConfig(prev=>({...prev,apiKey:e.target.value})),className:"bg-zinc-850 text-zinc-200 p-1.5 rounded border border-zinc-800 focus:outline-none focus:border-purple-600 font-mono text-xs",placeholder:"sk-..."})),/*#__PURE__*/React.createElement("div",{className:"flex flex-col gap-1"},/*#__PURE__*/React.createElement("span",{className:"text-xs text-zinc-500 font-bold uppercase"},"Model Name"),/*#__PURE__*/React.createElement("input",{type:"text",value:aiConfig.model,onChange:e=>setAiConfig(prev=>({...prev,model:e.target.value})),className:"bg-zinc-850 text-zinc-200 p-1.5 rounded border border-zinc-800 focus:outline-none focus:border-purple-600 font-mono text-xs",placeholder:"gpt-4o-mini"})))),/*#__PURE__*/React.createElement("div",{className:"h-9 bg-[#222] border-b border-zinc-800 flex items-center px-3 gap-2 shrink-0 select-none"},/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-1 bg-zinc-900 border border-zinc-700 rounded px-1.5 py-0.5 shadow-lg",title:"Kéo để di chuyển toolbar",style:{cursor:'grab'}},/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-0.5 mr-1 text-zinc-600"},/*#__PURE__*/React.createElement("span",{className:"inline-flex items-center shrink-0"},/*#__PURE__*/React.createElement("i",{"data-lucide":"grip-vertical",className:"w-3 h-3"}))),/*#__PURE__*/React.createElement("button",{onClick:()=>{setActiveTool('select');showToast('Select Tool','info');},className:`w-7 h-7 flex items-center justify-center rounded ${activeTool==='select'?'bg-cyan-700 text-white':'text-zinc-400 hover:text-zinc-200 hover:bg-zinc-800'}`,title:"Select Tool (V)"},/*#__PURE__*/React.createElement("span",{className:"inline-flex items-center shrink-0"},/*#__PURE__*/React.createElement("i",{"data-lucide":"mouse-pointer",className:"w-3.5 h-3.5"}))),/*#__PURE__*/React.createElement("button",{onClick:()=>{setActiveTool('grab');showToast('Grab Tool','info');},className:`w-7 h-7 flex items-center justify-center rounded ${activeTool==='grab'?'bg-cyan-700 text-white':'text-zinc-400 hover:text-zinc-200 hover:bg-zinc-800'}`,title:"Grab Tool (H)"},/*#__PURE__*/React.createElement("span",{className:"inline-flex items-center shrink-0"},/*#__PURE__*/React.createElement("i",{"data-lucide":"hand",className:"w-3.5 h-3.5"}))),/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-0.5 border border-zinc-700 rounded bg-zinc-850 px-0.5"},/*#__PURE__*/React.createElement("button",{onClick:()=>{setActiveTool('razor');showToast('Razor Tool','info');},className:`w-7 h-7 flex items-center justify-center rounded ${activeTool==='razor'?'bg-cyan-700 text-white':'text-zinc-400 hover:text-zinc-200 hover:bg-zinc-800'}`,title:"Razor Tool (C)"},/*#__PURE__*/React.createElement("svg",{className:"w-3.5 h-3.5 text-orange-400",viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2.5",strokeLinecap:"round",strokeLinejoin:"round"},/*#__PURE__*/React.createElement("path",{d:"M6 3h12a2 2 0 0 1 2 2v2a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2z"}),/*#__PURE__*/React.createElement("path",{d:"M4 9h16l-3 9H7z"}),/*#__PURE__*/React.createElement("circle",{cx:"12",cy:"6",r:"1"}))),/*#__PURE__*/React.createElement("button",{onClick:handleGlueTracks,className:"w-7 h-7 flex items-center justify-center rounded text-zinc-400 hover:text-purple-400 hover:bg-zinc-800",title:"Glue Clips"},/*#__PURE__*/React.createElement("span",{className:"inline-flex items-center shrink-0"},/*#__PURE__*/React.createElement("i",{"data-lucide":"link",className:"w-3.5 h-3.5"})))),/*#__PURE__*/React.createElement("button",{onClick:()=>{setActiveTool('pen');showToast('Pen Tool','info');},className:`w-7 h-7 flex items-center justify-center rounded ${activeTool==='pen'?'bg-cyan-700 text-white':'text-zinc-400 hover:text-zinc-200 hover:bg-zinc-800'}`,title:"Pen Tool (P)"},/*#__PURE__*/React.createElement("span",{className:"inline-flex items-center shrink-0"},/*#__PURE__*/React.createElement("i",{"data-lucide":"pen-tool",className:"w-3.5 h-3.5"}))),/*#__PURE__*/React.createElement("div",{className:"w-[1px] h-5 bg-zinc-700 mx-0.5"}),/*#__PURE__*/React.createElement("button",{onClick:handleCutTrack,className:"w-7 h-7 flex items-center justify-center rounded text-zinc-400 hover:text-red-400 hover:bg-zinc-800",title:"Cut (Ctrl+X)"},/*#__PURE__*/React.createElement("span",{className:"inline-flex items-center shrink-0"},/*#__PURE__*/React.createElement("i",{"data-lucide":"scissors",className:"w-3.5 h-3.5"}))),/*#__PURE__*/React.createElement("button",{onClick:handleCopyTrack,className:"w-7 h-7 flex items-center justify-center rounded text-zinc-400 hover:text-blue-400 hover:bg-zinc-800",title:"Copy (Ctrl+C)"},/*#__PURE__*/React.createElement("span",{className:"inline-flex items-center shrink-0"},/*#__PURE__*/React.createElement("i",{"data-lucide":"copy",className:"w-3.5 h-3.5"}))),/*#__PURE__*/React.createElement("button",{onClick:handlePasteTrack,className:"w-7 h-7 flex items-center justify-center rounded text-zinc-400 hover:text-emerald-400 hover:bg-zinc-800",title:"Paste (Ctrl+V)"},/*#__PURE__*/React.createElement("span",{className:"inline-flex items-center shrink-0"},/*#__PURE__*/React.createElement("i",{"data-lucide":"clipboard",className:"w-3.5 h-3.5"}))),/*#__PURE__*/React.createElement("div",{className:"w-[1px] h-5 bg-zinc-700 mx-0.5"}),/*#__PURE__*/React.createElement("button",{onClick:addNewTrack,className:"px-2 py-1 bg-cyan-600 hover:bg-cyan-500 text-white font-semibold rounded text-xs flex items-center gap-1 shadow transition",title:"Thêm Track Mới (Ctrl+I)"},/*#__PURE__*/React.createElement("span",{className:"inline-flex items-center shrink-0"},/*#__PURE__*/React.createElement("i",{"data-lucide":"plus",className:"w-3.5 h-3.5"})),/*#__PURE__*/React.createElement("span",null,"Track")),sessionTabs.some(s=>s.id===activeTab)&&/*#__PURE__*/React.createElement(React.Fragment,null,/*#__PURE__*/React.createElement("div",{className:"w-[1px] h-5 bg-zinc-700 mx-0.5"}),/*#__PURE__*/React.createElement("button",{onClick:()=>handleSaveSectionTab(activeTab),className:"px-2 py-1 bg-emerald-600 hover:bg-emerald-500 text-white font-semibold rounded text-xs flex items-center gap-1 shadow transition",title:"Lưu Section vào Main Session"},/*#__PURE__*/React.createElement("span",{className:"inline-flex items-center shrink-0"},/*#__PURE__*/React.createElement("i",{"data-lucide":"save",className:"w-3.5 h-3.5"})),/*#__PURE__*/React.createElement("span",null,"Lưu Section"))),/*#__PURE__*/React.createElement("div",{className:"w-[1px] h-5 bg-zinc-700 mx-0.5"}),/*#__PURE__*/React.createElement("button",{onClick:handleUndo,disabled:undoStack.length===0,className:"w-7 h-7 flex items-center justify-center rounded text-zinc-400 hover:text-zinc-200 hover:bg-zinc-800 disabled:opacity-30",title:"Undo (Ctrl+Z)"},/*#__PURE__*/React.createElement("span",{className:"inline-flex items-center shrink-0"},/*#__PURE__*/React.createElement("i",{"data-lucide":"undo",className:"w-3.5 h-3.5"}))),/*#__PURE__*/React.createElement("button",{onClick:handleRedo,disabled:redoStack.length===0,className:"w-7 h-7 flex items-center justify-center rounded text-zinc-400 hover:text-zinc-200 hover:bg-zinc-800 disabled:opacity-30",title:"Redo (Ctrl+Y)"},/*#__PURE__*/React.createElement("span",{className:"inline-flex items-center shrink-0"},/*#__PURE__*/React.createElement("i",{"data-lucide":"redo",className:"w-3.5 h-3.5"})))),/*#__PURE__*/React.createElement("div",{className:"w-[1px] h-5 bg-zinc-800 mx-0.5"}),/*#__PURE__*/React.createElement("button",{onClick:()=>{const isSubTab=subTabs.some(sub=>sub.id===activeTab);if(isSubTab){setSubTabs(prev=>prev.map(s=>s.id===activeTab?{...s,currentTime:0}:s));}else{setCurrentTime(0);}},className:"w-7 h-7 flex items-center justify-center bg-zinc-800 hover:bg-zinc-700 text-zinc-200 rounded border border-zinc-700 transition",title:"Quay lại đầu"},/*#__PURE__*/React.createElement("span",{className:"inline-flex items-center shrink-0"},/*#__PURE__*/React.createElement("i",{"data-lucide":"skip-back",className:"w-3.5 h-3.5"}))),/*#__PURE__*/React.createElement("button",{onClick:()=>{const isSubTab=subTabs.some(sub=>sub.id===activeTab);if(isSubTab){setSubTabs(prev=>prev.map(s=>{if(s.id!==activeTab)return s;const left=s.selectionStart!==null&&s.selectionEnd!==null?Math.min(s.selectionStart,s.selectionEnd):null;return left!==null?{...s,currentTime:left}:s;}));}else{if(selLeft!==null)setCurrentTime(selLeft);}},className:"w-7 h-7 flex items-center justify-center bg-zinc-800 hover:bg-zinc-700 text-zinc-200 rounded border border-zinc-700 transition",title:"Đầu vùng chọn"},/*#__PURE__*/React.createElement("span",{className:"inline-flex items-center shrink-0"},/*#__PURE__*/React.createElement("i",{"data-lucide":"step-back",className:"w-3.5 h-3.5"}))),/*#__PURE__*/React.createElement("button",{onClick:handlePlayPause,className:`w-7 h-7 flex items-center justify-center rounded border transition ${isPlaying?'bg-emerald-600 text-black border-emerald-500 hover:bg-emerald-500':'bg-cyan-600 text-white border-cyan-500 hover:bg-cyan-500'}`,title:isPlaying?"Tạm dừng":"Play"},/*#__PURE__*/React.createElement("span",{className:"inline-flex items-center shrink-0"},/*#__PURE__*/React.createElement("i",{"data-lucide":isPlaying?"pause":"play",className:"w-3.5 h-3.5 fill-current"}))),/*#__PURE__*/React.createElement("button",{onClick:handleStop,className:"w-7 h-7 flex items-center justify-center bg-zinc-800 hover:bg-zinc-700 text-zinc-200 rounded border border-zinc-700 transition",title:"Stop"},/*#__PURE__*/React.createElement("span",{className:"inline-flex items-center shrink-0"},/*#__PURE__*/React.createElement("i",{"data-lucide":"square",className:"w-3.5 h-3.5 fill-current"}))),/*#__PURE__*/React.createElement("button",{onClick:handleRecordClick,className:`w-7 h-7 flex items-center justify-center rounded border transition ${recordingState==='RECORDING'?'bg-red-600 text-white border-red-500 hover:bg-red-500 animate-pulse':recordingState==='COUNT_IN'?'bg-amber-500 text-black border-amber-400 hover:bg-amber-400 animate-pulse':'bg-zinc-800 text-red-500 border-zinc-700 hover:bg-zinc-700 hover:text-red-400'}`,title:recordingState==='RECORDING'?"Đang ghi âm...":recordingState==='COUNT_IN'?"Chuẩn bị ghi âm...":"Ghi âm (Record)"},/*#__PURE__*/React.createElement("span",{className:"inline-flex items-center shrink-0"},/*#__PURE__*/React.createElement("i",{"data-lucide":"circle",className:"w-3.5 h-3.5 fill-current"}))),/*#__PURE__*/React.createElement("button",{onClick:()=>{const isSubTab=subTabs.some(sub=>sub.id===activeTab);if(isSubTab){setSubTabs(prev=>prev.map(s=>{if(s.id!==activeTab)return s;const right=s.selectionStart!==null&&s.selectionEnd!==null?Math.max(s.selectionStart,s.selectionEnd):null;return right!==null?{...s,currentTime:right}:s;}));}else{if(selRight!==null)setCurrentTime(selRight);}},className:"w-7 h-7 flex items-center justify-center bg-zinc-800 hover:bg-zinc-700 text-zinc-200 rounded border border-zinc-700 transition",title:"Cuối vùng chọn"},/*#__PURE__*/React.createElement("span",{className:"inline-flex items-center shrink-0"},/*#__PURE__*/React.createElement("i",{"data-lucide":"step-forward",className:"w-3.5 h-3.5"}))),/*#__PURE__*/React.createElement("button",{onClick:()=>{const isSubTab=subTabs.some(sub=>sub.id===activeTab);if(isSubTab){setSubTabs(prev=>prev.map(s=>{if(s.id!==activeTab)return s;const duration=s.buffer?s.buffer.duration/(s.speed||1.0):0;return{...s,currentTime:duration};}));}else{setCurrentTime(maxDuration);}},className:"w-7 h-7 flex items-center justify-center bg-zinc-800 hover:bg-zinc-700 text-zinc-200 rounded border border-zinc-700 transition",title:"Đến cuối"},/*#__PURE__*/React.createElement("span",{className:"inline-flex items-center shrink-0"},/*#__PURE__*/React.createElement("i",{"data-lucide":"skip-forward",className:"w-3.5 h-3.5"}))),/*#__PURE__*/React.createElement("div",{className:"w-[1px] h-5 bg-zinc-800 mx-0.5"}),/*#__PURE__*/React.createElement("button",{onClick:()=>setIsLoopingSelection(prev=>!prev),className:`w-7 h-7 flex items-center justify-center rounded border transition ${isLoopingSelection?'bg-amber-600 text-black border-amber-500 hover:bg-amber-500':'bg-zinc-800 text-zinc-200 border-zinc-700 hover:bg-zinc-700'}`,title:isLoopingSelection?selLeft!==null&&selRight!==null?"Loop vùng chọn":"Loop timeline":"Bật loop"},/*#__PURE__*/React.createElement("span",{className:"inline-flex items-center shrink-0"},/*#__PURE__*/React.createElement("i",{"data-lucide":"repeat",className:"w-3.5 h-3.5"}))),/*#__PURE__*/React.createElement("span",{className:"text-[14px] text-zinc-500 font-bold uppercase ml-2"},"Snap"),/*#__PURE__*/React.createElement("select",{value:snapValue,onChange:e=>setSnapValue(e.target.value),className:"bg-black text-white text-[14px] px-1.5 py-0.5 rounded border border-zinc-700 focus:outline-none focus:border-cyan-500 font-mono cursor-pointer"},/*#__PURE__*/React.createElement("option",{value:"free"},"Free"),/*#__PURE__*/React.createElement("option",{value:"1"},"1"),/*#__PURE__*/React.createElement("option",{value:"1/2"},"1/2"),/*#__PURE__*/React.createElement("option",{value:"1/4"},"1/4"),/*#__PURE__*/React.createElement("option",{value:"1/8"},"1/8"),/*#__PURE__*/React.createElement("option",{value:"1/16"},"1/16"),/*#__PURE__*/React.createElement("option",{value:"1/32"},"1/32")),/*#__PURE__*/React.createElement("div",{className:"w-[1px] h-6 bg-zinc-800 mx-1.5"}),/*#__PURE__*/React.createElement("span",{className:"text-[14px] text-zinc-500 font-bold"},"Bars:"),/*#__PURE__*/React.createElement("input",{type:"number",min:"1",value:beginBar,onChange:e=>{const b=parseInt(e.target.value)||1;setBeginBar(b);const beatDuration=60/parseInt(bpm||120);const t=(b-1)*beatDuration*4;clearLocalSelection();setSelectionMode('global');setSelectionStart(t);setSelectionEnd(t+beatDuration*4);},className:"w-14 bg-black text-white text-[14px] px-1 py-0.5 rounded border border-zinc-700 text-center font-mono"}),/*#__PURE__*/React.createElement("span",{className:"text-[14px] text-zinc-500"},"-"),/*#__PURE__*/React.createElement("input",{type:"number",min:"1",value:endBar,onChange:e=>{const b=parseInt(e.target.value)||1;setEndBar(b);const beatDuration=60/parseInt(bpm||120);const t=(b-1)*beatDuration*4;setSelectionEnd(t+beatDuration*4);setNumberBar(b-beginBar+1);},className:"w-14 bg-black text-white text-[14px] px-1 py-0.5 rounded border border-zinc-700 text-center font-mono"}),/*#__PURE__*/React.createElement("span",{className:"text-[14px] text-zinc-500"},"#"),/*#__PURE__*/React.createElement("input",{type:"number",min:"1",value:numberBar,readOnly:true,className:"w-14 bg-black text-zinc-400 text-[14px] px-1 py-0.5 rounded border border-zinc-800 text-center font-mono"}),/*#__PURE__*/React.createElement("div",{className:"w-[1px] h-6 bg-zinc-800 mx-1.5"}),/*#__PURE__*/React.createElement("span",{className:"text-[14px] text-zinc-500"},"Start:"),/*#__PURE__*/React.createElement("input",{type:"text",value:selLeft!==null?formatTime(selLeft):'',onChange:e=>{const parts=e.target.value.split(/[:.]/);if(parts.length===3){const secs=parseInt(parts[0])*60+parseInt(parts[1])+parseFloat('0.'+(parts[2]||'0'));clearLocalSelection();setSelectionMode('global');setSelectionStart(secs);}},className:"w-20 bg-black text-zinc-200 text-[14px] px-1 py-0.5 rounded border border-zinc-700 text-center font-mono"}),/*#__PURE__*/React.createElement("span",{className:"text-[14px] text-zinc-500"},"End:"),/*#__PURE__*/React.createElement("input",{type:"text",value:selRight!==null?formatTime(selRight):'',onChange:e=>{const parts=e.target.value.split(/[:.]/);if(parts.length===3){const secs=parseInt(parts[0])*60+parseInt(parts[1])+parseFloat('0.'+(parts[2]||'0'));setSelectionEnd(secs);}},className:"w-20 bg-black text-zinc-200 text-[14px] px-1 py-0.5 rounded border border-zinc-700 text-center font-mono"}),/*#__PURE__*/React.createElement("span",{className:"text-[14px] text-zinc-500"},"Len:"),/*#__PURE__*/React.createElement("input",{type:"text",value:selLeft!==null&&selRight!==null?formatTime(Math.abs(selRight-selLeft)):'',onChange:e=>{const parts=e.target.value.split(/[:.]/);if(parts.length===3&&selLeft!==null){const secs=parseInt(parts[0])*60+parseInt(parts[1])+parseFloat('0.'+(parts[2]||'0'));setSelectionEnd(selLeft+secs);}},className:"w-20 bg-black text-amber-300 text-[14px] px-1 py-0.5 rounded border border-zinc-700 text-center font-mono"}),/*#__PURE__*/React.createElement("div",{className:"flex-1"}),/*#__PURE__*/React.createElement("div",{className:"text-xs font-bold font-mono text-zinc-100"},formatTime(currentTime))),(()=>{const dockPanels={top:[],right:[],bottom:[],left:[]};const addPanel=(id,pos,visible)=>{if(visible)dockPanels[pos].push(id);};addPanel('export',panelPositions.export,showExportPanel);addPanel('ai',panelPositions.ai,showAIPanel);addPanel('python_tools',panelPositions.python_tools||'bottom',showPythonToolsPanel);addPanel('selection',panelPositions.selection,showSelectionPanel);addPanel('media_explorer','bottom',showMediaExplorer);addPanel('fx_rack',panelPositions.fx_rack||'bottom',showFxRack);addPanel('midi_events',panelPositions.midi_events||'bottom',showMidiEvents);const closePanel=id=>{if(id==='export')setShowExportPanel(false);else if(id==='ai')setShowAIPanel(false);else if(id==='python_tools')setShowPythonToolsPanel(false);else if(id==='selection')setShowSelectionPanel(false);else if(id==='media_explorer')setShowMediaExplorer(false);else if(id==='fx_rack')setShowFxRack(false);else if(id==='midi_events')setShowMidiEvents(false);};const renderPanelContent=panelId=>{const h=id=>e=>{startPanelDrag(id,e);};if(panelId==='export')return/*#__PURE__*/React.createElement("div",{className:"flex flex-col h-full gap-1.5"},/*#__PURE__*/React.createElement("div",{className:"flex items-center justify-between shrink-0 cursor-grab active:cursor-grabbing select-none",onMouseDown:e=>startPanelDrag('export',e)},/*#__PURE__*/React.createElement("h3",{className:"font-bold text-xs text-zinc-200 flex items-center gap-1"},/*#__PURE__*/React.createElement("span",{className:"inline-flex items-center shrink-0"},/*#__PURE__*/React.createElement("i",{"data-lucide":"grip-vertical",className:"w-3 h-3 text-zinc-500"})),/*#__PURE__*/React.createElement("span",{className:"inline-flex items-center shrink-0"},/*#__PURE__*/React.createElement("i",{"data-lucide":"save",className:"w-3.5 h-3.5 text-cyan-400"}))," Export"),/*#__PURE__*/React.createElement("button",{onClick:()=>closePanel('export'),className:"text-zinc-600 hover:text-zinc-300"},/*#__PURE__*/React.createElement("span",{className:"inline-flex items-center shrink-0"},/*#__PURE__*/React.createElement("i",{"data-lucide":"x",className:"w-3 h-3"})))),/*#__PURE__*/React.createElement("div",{className:"grid grid-cols-2 gap-1"},/*#__PURE__*/React.createElement("div",null,/*#__PURE__*/React.createElement("label",{className:"block text-[7px] text-zinc-500 font-bold uppercase mb-0.5"},"Ngu\u1ed3n"),/*#__PURE__*/React.createElement("select",{value:exportSettings.source,onChange:e=>setExportSettings(p=>({...p,source:e.target.value})),className:"w-full bg-[#141414] border border-zinc-800 rounded px-1 py-0.5 text-xs text-zinc-300 focus:outline-none"},/*#__PURE__*/React.createElement("option",{value:"project"},"Project (Mix)"),/*#__PURE__*/React.createElement("option",{value:"track_mix"},"Track Selection"),/*#__PURE__*/React.createElement("option",{value:"active_clip"},"Active Clip"),/*#__PURE__*/React.createElement("option",{value:"clip_selection"},"Clip Selection"))),/*#__PURE__*/React.createElement("div",null,/*#__PURE__*/React.createElement("label",{className:"block text-[7px] text-zinc-500 font-bold uppercase mb-0.5"},"\u0110\u1ecbnh d\u1ea1ng"),/*#__PURE__*/React.createElement("select",{value:exportSettings.format,onChange:e=>setExportSettings(p=>({...p,format:e.target.value,sampleRate:e.target.value==='wav'?'44100':e.target.value==='mp3'?'44100':'44100',bitDepth:e.target.value==='wav'?'16':'16',quality:'44khz'})),className:"w-full bg-[#141414] border border-zinc-800 rounded px-1 py-0.5 text-xs text-zinc-300 focus:outline-none"},/*#__PURE__*/React.createElement("option",{value:"wav"},"WAV"),/*#__PURE__*/React.createElement("option",{value:"mp3"},"MP3"),/*#__PURE__*/React.createElement("option",{value:"ogg"},"OGG")))),exportSettings.format==='wav'?/*#__PURE__*/React.createElement("div",{className:"grid grid-cols-2 gap-1"},/*#__PURE__*/React.createElement("div",null,/*#__PURE__*/React.createElement("label",{className:"block text-[7px] text-zinc-500 font-bold uppercase mb-0.5"},"SR (Hz)"),/*#__PURE__*/React.createElement("select",{value:exportSettings.sampleRate,onChange:e=>setExportSettings(p=>({...p,sampleRate:e.target.value})),className:"w-full bg-[#141414] border border-zinc-800 rounded px-1 py-0.5 text-xs text-zinc-300 focus:outline-none"},/*#__PURE__*/React.createElement("option",{value:"22500"},"22500"),/*#__PURE__*/React.createElement("option",{value:"44100"},"44100"))),/*#__PURE__*/React.createElement("div",null,/*#__PURE__*/React.createElement("label",{className:"block text-[7px] text-zinc-500 font-bold uppercase mb-0.5"},"Bit"),/*#__PURE__*/React.createElement("select",{value:exportSettings.bitDepth,onChange:e=>setExportSettings(p=>({...p,bitDepth:e.target.value})),className:"w-full bg-[#141414] border border-zinc-800 rounded px-1 py-0.5 text-xs text-zinc-300 focus:outline-none"},/*#__PURE__*/React.createElement("option",{value:"8"},"8"),/*#__PURE__*/React.createElement("option",{value:"16"},"16"),/*#__PURE__*/React.createElement("option",{value:"24"},"24")))):/*#__PURE__*/React.createElement("div",{className:"grid grid-cols-2 gap-1"},/*#__PURE__*/React.createElement("div",null,/*#__PURE__*/React.createElement("label",{className:"block text-[7px] text-zinc-500 font-bold uppercase mb-0.5"},"Ch\u1ea5t l\u01b0\u1ee3ng"),/*#__PURE__*/React.createElement("select",{value:exportSettings.quality,onChange:e=>setExportSettings(p=>({...p,quality:e.target.value})),className:"w-full bg-[#141414] border border-zinc-800 rounded px-1 py-0.5 text-xs text-zinc-300 focus:outline-none"},/*#__PURE__*/React.createElement("option",{value:"44khz"},"44kHz"),/*#__PURE__*/React.createElement("option",{value:"lossless"},"Lossless"))),/*#__PURE__*/React.createElement("div",null)),/*#__PURE__*/React.createElement("div",{className:"grid grid-cols-2 gap-1"},/*#__PURE__*/React.createElement("div",null,/*#__PURE__*/React.createElement("label",{className:"block text-[7px] text-zinc-500 font-bold uppercase mb-0.5"},"Kênh"),/*#__PURE__*/React.createElement("select",{value:exportSettings.channels,onChange:e=>setExportSettings(p=>({...p,channels:e.target.value})),className:"w-full bg-[#141414] border border-zinc-800 rounded px-1 py-0.5 text-xs text-zinc-300 focus:outline-none"},/*#__PURE__*/React.createElement("option",{value:"mono"},"Mono"),/*#__PURE__*/React.createElement("option",{value:"stereo"},"Stereo"))),/*#__PURE__*/React.createElement("div",null)),/*#__PURE__*/React.createElement("button",{onClick:triggerWavExport,disabled:isExporting,className:"w-full py-1 bg-cyan-700 hover:bg-cyan-600 text-zinc-100 font-bold rounded text-xs flex items-center justify-center gap-1"},/*#__PURE__*/React.createElement("span",{className:"inline-flex items-center shrink-0"},/*#__PURE__*/React.createElement("i",{"data-lucide":"download-cloud",className:"w-3 h-3"})),isExporting?'...':'Export'));if(panelId==='ai')return/*#__PURE__*/React.createElement("div",{className:"flex flex-col gap-1.5 flex-1 min-h-0"},/*#__PURE__*/React.createElement("div",{className:"flex items-center justify-between shrink-0 cursor-grab active:cursor-grabbing select-none",onMouseDown:e=>startPanelDrag('ai',e)},/*#__PURE__*/React.createElement("h3",{className:"font-bold text-xs text-zinc-200 flex items-center gap-1"},/*#__PURE__*/React.createElement("span",{className:"inline-flex items-center shrink-0"},/*#__PURE__*/React.createElement("i",{"data-lucide":"grip-vertical",className:"w-3 h-3 text-zinc-500"})),/*#__PURE__*/React.createElement("span",{className:"inline-flex items-center shrink-0"},/*#__PURE__*/React.createElement("i",{"data-lucide":"cpu",className:"w-3.5 h-3.5 text-purple-400"}))," AI Copilot"),/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-1"},/*#__PURE__*/React.createElement("button",{onClick:()=>setAiPresetModalOpen(true),className:"text-zinc-600 hover:text-zinc-300 mr-0.5",title:"Preset Manager"},/*#__PURE__*/React.createElement("span",{className:"inline-flex items-center shrink-0"},/*#__PURE__*/React.createElement("i",{"data-lucide":"sliders",className:"w-3 h-3"}))),/*#__PURE__*/React.createElement("button",{onClick:()=>{setAiActionLog([]);showToast('Đã xoá nhật ký AI.','info');},className:"text-zinc-600 hover:text-zinc-300",title:"Clear log"},/*#__PURE__*/React.createElement("span",{className:"inline-flex items-center shrink-0"},/*#__PURE__*/React.createElement("i",{"data-lucide":"trash-2",className:"w-3 h-3"}))),/*#__PURE__*/React.createElement("button",{onClick:()=>closePanel('ai'),className:"text-zinc-600 hover:text-zinc-300"},/*#__PURE__*/React.createElement("span",{className:"inline-flex items-center shrink-0"},/*#__PURE__*/React.createElement("i",{"data-lucide":"x",className:"w-3 h-3"}))))),/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-1.5 w-full min-w-0 pb-1"},/*#__PURE__*/React.createElement("span",{className:"inline-flex items-center shrink-0"},/*#__PURE__*/React.createElement("i",{"data-lucide":"cpu",className:"w-3 h-3 text-purple-400"})),/*#__PURE__*/React.createElement("select",{value:selectedProviderId,onChange:e=>setSelectedProviderId(e.target.value),className:"flex-1 min-w-0 max-w-full bg-[#141414] text-zinc-200 border border-zinc-700 rounded px-1 py-0.5 text-xs font-mono focus:outline-none focus:border-purple-600 truncate"},aiProviders.length===0?/*#__PURE__*/React.createElement("option",{value:""},"Chưa có provider"):aiProviders.map(p=>/*#__PURE__*/React.createElement("option",{key:p.id,value:p.id},p.name,p.is_active?'':' (inactive)')))),/*#__PURE__*/React.createElement("div",{className:"border-t border-zinc-800 pt-1 mt-1 shrink-0"},/*#__PURE__*/React.createElement("button",{onClick:()=>{if(window.DAWCommandDispatcher&&window.DAWCommandDispatcher.undo){const entry=window.DAWCommandDispatcher.undo();if(entry){setAiActionLog(prev=>[...prev,{type:'undo',text:`Undo: ${entry.name}`,time:Date.now()}]);showToast(`Undo AI: ${entry.name}`,'info');}}else{handleUndo();setAiActionLog(prev=>[...prev,{type:'undo',text:'Undo (Ctrl+Z)',time:Date.now()}]);}},className:"w-full text-[10px] text-amber-400 hover:text-amber-300 border border-amber-800 rounded py-0.5"},/*#__PURE__*/React.createElement("span",{className:"inline-flex items-center justify-center gap-1"},/*#__PURE__*/React.createElement("i",{"data-lucide":"rotate-ccw",className:"w-3 h-3"}),"Undo"))),/*#__PURE__*/React.createElement("div",{className:"flex-1 min-h-0 flex flex-col overflow-hidden mt-1"},/*#__PURE__*/React.createElement("div",{className:"text-[10px] font-bold text-zinc-400 uppercase shrink-0 pb-0.5"},/*#__PURE__*/React.createElement("span",{className:"inline-flex items-center gap-1"},/*#__PURE__*/React.createElement("i",{"data-lucide":"list",className:"w-3 h-3"})," Action Log")),/*#__PURE__*/React.createElement("div",{ref:actionLogContainerRef,className:"flex-1 overflow-y-auto no-scrollbar bg-[#0f0f0f] rounded border border-zinc-800 select-text"},aiActionLog.length===0?/*#__PURE__*/React.createElement("div",{className:"text-xs text-zinc-600 italic select-text"},"Chưa có hành động nào."):aiActionLog.map((entry,i)=>/*#__PURE__*/React.createElement("div",{key:i,className:`text-xs font-mono py-0.5 border-b border-zinc-900 last:border-0 ${entry.type==='error'?'text-red-400':entry.type==='status'?'text-zinc-400 italic':entry.type==='undo'?'text-amber-400':'text-zinc-300'}`},new Date(entry.time).toLocaleTimeString(),entry.text)))),/*#__PURE__*/React.createElement("div",{className:"border-t border-zinc-800 pt-1.5 mt-1 shrink-0"},/*#__PURE__*/React.createElement("div",{className:"text-xs font-bold text-zinc-400 uppercase mb-1 flex items-center gap-1"},/*#__PURE__*/React.createElement("span",{className:"inline-flex items-center shrink-0"},/*#__PURE__*/React.createElement("i",{"data-lucide":"message-square",className:"w-3 h-3"}))," Copilot Prompt"),/*#__PURE__*/React.createElement("textarea",{value:aiPrompt,onChange:e=>setAiPrompt(e.target.value),placeholder:"Nhập lệnh điều khiển DAW... (VD: Tạo track mới tên Beat, đặt BPM 128)",className:"w-full bg-[#1e1e1e] text-zinc-200 p-1.5 rounded border border-zinc-700 focus:outline-none focus:border-purple-600 font-mono text-xs resize-none",rows:2,onKeyDown:e=>{if(e.key==='Enter'&&!e.shiftKey){e.preventDefault();handleAISend();}else if(e.key==='ArrowUp'&&promptHistRef.current.length>0){e.preventDefault();const idx=promptHistIdx===-1?promptHistRef.current.length-1:Math.max(0,promptHistIdx-1);setPromptHistIdx(idx);setAiPrompt(promptHistRef.current[idx]);}else if(e.key==='ArrowDown'){e.preventDefault();if(promptHistIdx===-1)return;const idx=promptHistIdx+1;if(idx>=promptHistRef.current.length){setPromptHistIdx(-1);setAiPrompt('');}else{setPromptHistIdx(idx);setAiPrompt(promptHistRef.current[idx]);}}}})),/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-1 mt-1 shrink-0"},/*#__PURE__*/React.createElement("button",{onClick:handleAISend,disabled:aiProcessing,className:"flex-1 py-1 bg-purple-700 hover:bg-purple-600 text-white font-bold rounded text-xs border border-purple-500 flex items-center justify-center gap-1"},aiProcessing?'Đang suy luận...':/*#__PURE__*/React.createElement(React.Fragment,null,/*#__PURE__*/React.createElement("span",{className:"inline-flex items-center shrink-0"},/*#__PURE__*/React.createElement("i",{"data-lucide":"send",className:"w-3 h-3"}))," Gửi")),/*#__PURE__*/React.createElement("button",{onClick:()=>{setAiPrompt('');setAiActionLog([]);},className:"px-2 py-1 bg-zinc-800 hover:bg-zinc-700 text-zinc-400 rounded text-xs border border-zinc-700"},"Clear")),/*#__PURE__*/React.createElement("div",{className:"text-xs text-zinc-600 shrink-0"},"Enter để gửi nhanh"));if(panelId==='python_tools')return/*#__PURE__*/React.createElement("div",{className:"flex flex-col h-full gap-1.5"},/*#__PURE__*/React.createElement("div",{className:"flex items-center justify-between shrink-0 cursor-grab active:cursor-grabbing select-none",onMouseDown:e=>startPanelDrag('python_tools',e)},/*#__PURE__*/React.createElement("h3",{className:"font-bold text-xs text-amber-300 flex items-center gap-1"},/*#__PURE__*/React.createElement("span",{className:"inline-flex items-center shrink-0"},/*#__PURE__*/React.createElement("i",{"data-lucide":"grip-vertical",className:"w-3 h-3 text-zinc-500"})),/*#__PURE__*/React.createElement("span",{className:"inline-flex items-center shrink-0"},/*#__PURE__*/React.createElement("i",{"data-lucide":"wrench",className:"w-3.5 h-3.5 text-amber-400"}))," DSP Tools"),/*#__PURE__*/React.createElement("button",{onClick:()=>closePanel('python_tools'),className:"text-zinc-600 hover:text-zinc-300"},/*#__PURE__*/React.createElement("span",{className:"inline-flex items-center shrink-0"},/*#__PURE__*/React.createElement("i",{"data-lucide":"x",className:"w-3 h-3"})))),/*#__PURE__*/React.createElement("div",{className:"p-1.5 bg-[#141414] rounded border border-zinc-800 text-[10px] font-mono text-zinc-400 space-y-0.5 leading-relaxed"},dspSelectionStats?[/*#__PURE__*/React.createElement("div",{key:"track"},`Track: ${dspSelectionStats.trackName}`),/*#__PURE__*/React.createElement("div",{key:"range"},`Range: ${dspSelectionStats.timeRange}`),/*#__PURE__*/React.createElement("div",{key:"ch"},`Channels: ${dspSelectionStats.channels}`),/*#__PURE__*/React.createElement("div",{key:"peak"},`Peak Vol: ${dspSelectionStats.peakVolume}`)]:"Chưa chọn track"),/*#__PURE__*/React.createElement("div",{className:"grid grid-cols-2 gap-1 text-xs"},/*#__PURE__*/React.createElement("button",{onClick:()=>runPythonTool('normalize'),className:"py-1 bg-amber-950/80 hover:bg-amber-900 text-amber-300 border border-amber-800/80 rounded font-bold flex items-center justify-center gap-1"},"⚡ Peak Norm (0dB)"),/*#__PURE__*/React.createElement("button",{onClick:()=>runPythonTool('invert_phase'),className:"py-1 bg-zinc-800 hover:bg-zinc-700 text-zinc-200 border border-zinc-700 rounded font-bold flex items-center justify-center gap-1"},"🔄 Phase Invert"),/*#__PURE__*/React.createElement("button",{onClick:()=>runPythonTool('swap_channels'),className:"py-1 bg-zinc-800 hover:bg-zinc-700 text-zinc-200 border border-zinc-700 rounded font-bold flex items-center justify-center gap-1"},"🔀 Swap L/R"),/*#__PURE__*/React.createElement("button",{onClick:()=>runPythonTool('synth_wave'),className:"py-1 bg-teal-950/80 hover:bg-teal-900 text-teal-300 border border-teal-800/80 rounded font-bold flex items-center justify-center gap-1"},"🎹 Gen Synth Tone")));if(panelId==='selection')return/*#__PURE__*/React.createElement("div",{className:"flex flex-col h-full gap-1.5"},/*#__PURE__*/React.createElement("div",{className:"flex items-center justify-between shrink-0 cursor-grab active:cursor-grabbing select-none",onMouseDown:e=>startPanelDrag('selection',e)},/*#__PURE__*/React.createElement("span",{className:"text-xs text-zinc-500 font-bold uppercase flex items-center gap-1"},/*#__PURE__*/React.createElement("span",{className:"inline-flex items-center shrink-0"},/*#__PURE__*/React.createElement("i",{"data-lucide":"grip-vertical",className:"w-3 h-3 text-zinc-500"}))," Selection"),/*#__PURE__*/React.createElement("button",{onClick:()=>closePanel('selection'),className:"text-zinc-600 hover:text-zinc-300"},/*#__PURE__*/React.createElement("span",{className:"inline-flex items-center shrink-0"},/*#__PURE__*/React.createElement("i",{"data-lucide":"x",className:"w-3 h-3"})))),/*#__PURE__*/React.createElement("div",{className:"grid grid-cols-3 gap-1 bg-[#141414] p-1.5 rounded border border-zinc-800"},/*#__PURE__*/React.createElement("div",null,/*#__PURE__*/React.createElement("span",{className:"block text-[7px] text-zinc-500 font-bold uppercase mb-0.5"},"Start"),/*#__PURE__*/React.createElement("input",{type:"number",step:"0.01",value:selectionStats.start,onChange:e=>handleSelectionInputChange('start',e.target.value),className:"w-full bg-[#242424] text-amber-400 text-center font-mono text-xs rounded py-0.5 border border-zinc-700 focus:outline-none"})),/*#__PURE__*/React.createElement("div",null,/*#__PURE__*/React.createElement("span",{className:"block text-[7px] text-zinc-500 font-bold uppercase mb-0.5"},"End"),/*#__PURE__*/React.createElement("input",{type:"number",step:"0.01",value:selectionStats.end,onChange:e=>handleSelectionInputChange('end',e.target.value),className:"w-full bg-[#242424] text-amber-400 text-center font-mono text-xs rounded py-0.5 border border-zinc-700 focus:outline-none"})),/*#__PURE__*/React.createElement("div",{className:"flex flex-col justify-center"},/*#__PURE__*/React.createElement("span",{className:"text-[7px] text-zinc-500 font-bold uppercase"},"Len"),/*#__PURE__*/React.createElement("span",{className:"text-zinc-200 font-mono text-xs font-semibold mt-0.5"},selectionStats.length,"s"))),/*#__PURE__*/React.createElement("div",{className:"grid grid-cols-3 gap-1 bg-[#141414] p-1.5 rounded border border-zinc-800 mt-1"},/*#__PURE__*/React.createElement("div",null,/*#__PURE__*/React.createElement("span",{className:"block text-[7px] text-zinc-500 font-bold uppercase mb-0.5"},"Begin Bar"),/*#__PURE__*/React.createElement("input",{type:"number",min:"1",value:beginBar,onChange:e=>{const b=parseInt(e.target.value)||1;setBeginBar(b);const beatDuration=60/parseInt(bpm||120);const t=(b-1)*beatDuration*4;clearLocalSelection();setSelectionMode('global');setSelectionStart(t);setSelectionEnd(t+beatDuration*4);},className:"w-full bg-[#242424] text-white text-center font-mono text-xs rounded py-0.5 border border-zinc-700 focus:outline-none"})),/*#__PURE__*/React.createElement("div",null,/*#__PURE__*/React.createElement("span",{className:"block text-[7px] text-zinc-500 font-bold uppercase mb-0.5"},"End Bar"),/*#__PURE__*/React.createElement("input",{type:"number",min:"1",value:endBar,onChange:e=>{const b=parseInt(e.target.value)||1;setEndBar(b);const beatDuration=60/parseInt(bpm||120);const t=(b-1)*beatDuration*4;setSelectionEnd(t+beatDuration*4);setNumberBar(b-beginBar+1);},className:"w-full bg-[#242424] text-white text-center font-mono text-xs rounded py-0.5 border border-zinc-700 focus:outline-none"})),/*#__PURE__*/React.createElement("div",{className:"flex flex-col justify-center"},/*#__PURE__*/React.createElement("span",{className:"text-[7px] text-zinc-500 font-bold uppercase"},"# Bars"),/*#__PURE__*/React.createElement("span",{className:"text-zinc-200 font-mono text-xs font-semibold mt-0.5"},numberBar))));if(panelId==='media_explorer')return/*#__PURE__*/React.createElement("div",{className:"flex flex-col h-full gap-1.5"},/*#__PURE__*/React.createElement("div",{className:"flex items-center justify-between shrink-0 cursor-grab active:cursor-grabbing select-none",onMouseDown:e=>startPanelDrag('media_explorer',e)},/*#__PURE__*/React.createElement("h3",{className:"font-bold text-xs text-emerald-300 flex items-center gap-1"},/*#__PURE__*/React.createElement("span",{className:"inline-flex items-center shrink-0"},/*#__PURE__*/React.createElement("i",{"data-lucide":"grip-vertical",className:"w-3 h-3 text-zinc-500"})),/*#__PURE__*/React.createElement("span",{className:"inline-flex items-center shrink-0"},/*#__PURE__*/React.createElement("i",{"data-lucide":"folder-open",className:"w-3.5 h-3.5 text-emerald-400"}))," Media Explorer"),/*#__PURE__*/React.createElement("button",{onClick:()=>closePanel('media_explorer'),className:"text-zinc-600 hover:text-zinc-300"},/*#__PURE__*/React.createElement("span",{className:"inline-flex items-center shrink-0"},/*#__PURE__*/React.createElement("i",{"data-lucide":"x",className:"w-3 h-3"})))),/*#__PURE__*/React.createElement("div",{className:"flex-1 overflow-y-auto text-xs text-zinc-400 font-mono p-2"},"// Placeholder: Media files browser"));if(panelId==='fx_rack')return/*#__PURE__*/React.createElement("div",{className:"flex flex-col h-full gap-1.5"},/*#__PURE__*/React.createElement("div",{className:"flex items-center justify-between shrink-0 cursor-grab active:cursor-grabbing select-none",onMouseDown:e=>startPanelDrag('fx_rack',e)},/*#__PURE__*/React.createElement("h3",{className:"font-bold text-xs text-rose-300 flex items-center gap-1"},/*#__PURE__*/React.createElement("span",{className:"inline-flex items-center shrink-0"},/*#__PURE__*/React.createElement("i",{"data-lucide":"grip-vertical",className:"w-3 h-3 text-zinc-500"})),/*#__PURE__*/React.createElement("span",{className:"inline-flex items-center shrink-0"},/*#__PURE__*/React.createElement("i",{"data-lucide":"sliders",className:"w-3.5 h-3.5 text-rose-400"}))," Plugin FX Rack"),/*#__PURE__*/React.createElement("button",{onClick:()=>closePanel('fx_rack'),className:"text-zinc-600 hover:text-zinc-300"},/*#__PURE__*/React.createElement("span",{className:"inline-flex items-center shrink-0"},/*#__PURE__*/React.createElement("i",{"data-lucide":"x",className:"w-3 h-3"})))),/*#__PURE__*/React.createElement("div",{className:"flex-1 text-xs text-zinc-500 italic flex items-center justify-center"},"No FX plugins loaded"));if(panelId==='midi_events')return/*#__PURE__*/React.createElement("div",{className:"flex flex-col h-full gap-1.5"},/*#__PURE__*/React.createElement("div",{className:"flex items-center justify-between shrink-0 cursor-grab active:cursor-grabbing select-none",onMouseDown:e=>startPanelDrag('midi_events',e)},/*#__PURE__*/React.createElement("h3",{className:"font-bold text-xs text-sky-300 flex items-center gap-1"},/*#__PURE__*/React.createElement("span",{className:"inline-flex items-center shrink-0"},/*#__PURE__*/React.createElement("i",{"data-lucide":"grip-vertical",className:"w-3 h-3 text-zinc-500"})),/*#__PURE__*/React.createElement("span",{className:"inline-flex items-center shrink-0"},/*#__PURE__*/React.createElement("i",{"data-lucide":"music",className:"w-3.5 h-3.5 text-sky-400"}))," MIDI Event List"),/*#__PURE__*/React.createElement("button",{onClick:()=>closePanel('midi_events'),className:"text-zinc-600 hover:text-zinc-300"},/*#__PURE__*/React.createElement("span",{className:"inline-flex items-center shrink-0"},/*#__PURE__*/React.createElement("i",{"data-lucide":"x",className:"w-3 h-3"})))),/*#__PURE__*/React.createElement("div",{className:"flex-1 text-xs text-zinc-500 italic flex items-center justify-center"},"No MIDI events selected"));return null;};const renderDock=(pos,title)=>{const panels=dockPanels[pos];if(panels.length===0)return null;const isSide=pos==='left'||pos==='right';const borderClass=pos==='left'?'border-r':pos==='right'?'border-l':pos==='top'?'border-b':'border-t';const bgClass='bg-[#1e1e1e]';const highlight=panelDragRef.current&&panelDropZone===pos;if(pos==='right')return/*#__PURE__*/React.createElement("div",{id:"right-sidebar",className:`${borderClass} ${bgClass} flex flex-col overflow-hidden select-none h-full`,style:{width:`${rightSidebarWidth}px`,minWidth:'200px',maxWidth:'600px',flexShrink:0}},/*#__PURE__*/React.createElement("div",{className:"flex-1 flex flex-col gap-2 p-2 overflow-hidden h-full"},panels.map((p,idx)=>/*#__PURE__*/React.createElement(React.Fragment,{key:p},/*#__PURE__*/React.createElement("div",{className:'flex flex-col flex-1 min-h-0 border border-zinc-700 rounded-lg bg-[#262626] shadow-sm p-3'},renderPanelContent(p)),idx/*#__PURE__*/React.createElement("div",{key:p,className:`${isSide?'w-full':'flex-none w-[320px]'} border border-zinc-700 rounded-lg bg-[#262626] shadow-sm p-2`},renderPanelContent(p))));};return/*#__PURE__*/React.createElement("div",{ref:workspaceRef,className:"flex-1 flex flex-col overflow-hidden select-none daw-bg relative"},panelDragRef.current&&panelDropZone&&/*#__PURE__*/React.createElement("div",{className:"absolute inset-0 z-50 pointer-events-none"},panelDropZone==='top'&&/*#__PURE__*/React.createElement("div",{className:"absolute top-0 left-0 right-0 h-1 bg-cyan-400 shadow-[0_0_8px_#06b6d4]"}),panelDropZone==='bottom'&&/*#__PURE__*/React.createElement("div",{className:"absolute bottom-0 left-0 right-0 h-1 bg-cyan-400 shadow-[0_0_8px_#06b6d4]"}),panelDropZone==='left'&&/*#__PURE__*/React.createElement("div",{className:"absolute top-0 bottom-0 left-0 w-1 bg-cyan-400 shadow-[0_0_8px_#06b6d4]"}),panelDropZone==='right'&&/*#__PURE__*/React.createElement("div",{className:"absolute top-0 bottom-0 right-0 w-1 bg-cyan-400 shadow-[0_0_8px_#06b6d4]"})),dragGhostPanel&&dragGhostPos&&/*#__PURE__*/React.createElement("div",{className:"fixed z-[100] pointer-events-none opacity-80 border border-cyan-500 rounded-lg bg-[#262626] shadow-2xl p-3 w-56",style:{left:dragGhostPos.x,top:dragGhostPos.y}},/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-2 text-xs text-zinc-200 font-bold"},/*#__PURE__*/React.createElement("span",{className:"inline-flex items-center shrink-0"},/*#__PURE__*/React.createElement("i",{"data-lucide":"move",className:"w-3.5 h-3.5 text-cyan-400"})),dragGhostPanel==='export'?'Export Panel':dragGhostPanel==='ai'?'AI Panel':dragGhostPanel==='python_tools'?'Audio Processing Panel':'Selection Panel'),/*#__PURE__*/React.createElement("div",{className:"text-xs text-zinc-500 mt-1"},"Drop at edge to dock")),renderDock('top','Top'),/*#__PURE__*/React.createElement("div",{className:"flex-1 flex overflow-hidden"},renderDock('left','Left'),/*#__PURE__*/React.createElement("div",{className:"flex-1 flex flex-col overflow-hidden min-w-0"},/*#__PURE__*/React.createElement("div",{className:"flex-1 flex overflow-hidden"},activeTab==='main'||sessionTabs.some(s=>s.id===activeTab)?/*#__PURE__*/React.createElement(React.Fragment,null,/*#__PURE__*/React.createElement("div",{ref:tcpContainerRef,onScroll:handleTCPScroll,className:"w-[320px] shrink-0 z-20 bg-[#262626] overflow-y-auto flex flex-col border-r border-zinc-900 no-scrollbar",style:{scrollbarWidth:'none',msOverflowStyle:'none'}},/*#__PURE__*/React.createElement("div",{className:"sticky top-0 z-50 flex items-center justify-between h-10 border-b border-zinc-900 bg-[#242424] px-2 shrink-0"},/*#__PURE__*/React.createElement("span",{className:"text-xs font-bold text-zinc-300 flex items-center gap-1.5"},/*#__PURE__*/React.createElement("span",{className:"inline-flex items-center shrink-0"},/*#__PURE__*/React.createElement("i",{"data-lucide":"sliders",className:"w-3.5 h-3.5 text-cyan-400"})),"TRACKS (",activeTracks.length,")"),/*#__PURE__*/React.createElement("button",{onClick:addNewTrack,className:"px-2.5 py-1 bg-cyan-600 hover:bg-cyan-500 text-white rounded text-xs font-semibold flex items-center gap-1 shadow transition"},/*#__PURE__*/React.createElement("span",{className:"inline-flex items-center shrink-0"},/*#__PURE__*/React.createElement("i",{"data-lucide":"plus",className:"w-3 h-3"}))," Add Track")),/*#__PURE__*/React.createElement("div",{className:"sticky top-10 z-40 flex h-[40px] border-b border-purple-500/30 bg-[#1a1a2e] p-2 border-l-4 border-purple-500 shrink-0"},/*#__PURE__*/React.createElement("div",{className:"flex items-center justify-between w-full"},/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-2"},/*#__PURE__*/React.createElement("span",{className:"text-xs font-bold text-purple-400 font-mono"},"TM"),/*#__PURE__*/React.createElement("span",{className:"text-xs font-semibold text-zinc-300"},"Tempo")),/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-1"},/*#__PURE__*/React.createElement("input",{type:"number",value:bpm,onChange:e=>setBpm(e.target.value),onBlur:()=>localStorage.setItem('studio_bpm',bpm),className:"w-12 bg-zinc-800 border border-zinc-700 rounded text-xs text-zinc-300 text-center font-mono focus:outline-none focus:border-purple-500",min:"40",max:"300"}),/*#__PURE__*/React.createElement("span",{className:"text-xs text-zinc-500"},"BPM")))),/*#__PURE__*/React.createElement("div",{className:"flex-1 flex flex-col divide-y divide-[#141414] bg-[#262626]"},activeTracks.length===0?/*#__PURE__*/React.createElement("div",{className:"p-6 text-center text-zinc-400 flex flex-col items-center justify-center space-y-3"},/*#__PURE__*/React.createElement("span",{className:"inline-flex items-center shrink-0"},/*#__PURE__*/React.createElement("i",{"data-lucide":"plus-circle",className:"w-8 h-8 text-cyan-400 opacity-80"})),/*#__PURE__*/React.createElement("p",{className:"text-xs font-medium"},"Chưa có Track nào trong dự án."),/*#__PURE__*/React.createElement("button",{onClick:addNewTrack,className:"px-3 py-1.5 bg-cyan-600 hover:bg-cyan-500 text-white rounded text-xs font-semibold flex items-center gap-1.5 shadow"},/*#__PURE__*/React.createElement("span",{className:"inline-flex items-center shrink-0"},/*#__PURE__*/React.createElement("i",{"data-lucide":"plus-circle",className:"w-3.5 h-3.5"}))," Thêm Track Mới")):activeTracks.map((track,idx)=>{const isSelected=selectedTrackId===track.id;const autoHeight=track.height||(track.isArmed?164:140);return/*#__PURE__*/React.createElement("div",{key:track.id,style:{height:`${autoHeight}px`},className:`shrink-0 relative flex flex-col justify-between p-2.5 bg-[#1e1e1e] border-r border-zinc-900 cursor-pointer border-l-4 border-b border-[#141414] overflow-hidden ${isSelected?'border-cyan-500 bg-[#252525]':'border-transparent hover:bg-zinc-800/20'}`,onClick:()=>setSelectedTrackId(track.id)},/*#__PURE__*/React.createElement("div",{className:"flex items-start justify-between"},/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-2"},/*#__PURE__*/React.createElement("span",{className:"text-xs font-bold text-zinc-500 font-mono"},(idx+1).toString().padStart(2,'0')),/*#__PURE__*/React.createElement("label",{onClick:e=>{e.stopPropagation();const el=e.currentTarget.querySelector('input');if(el)el.click();},className:"cursor-pointer"},/*#__PURE__*/React.createElement("input",{type:"color",value:track.color||'#0f766e',onChange:e=>{e.stopPropagation();updateTrackColor(track.id,e.target.value);},className:"w-0 h-0 opacity-0 absolute pointer-events-none"}),/*#__PURE__*/React.createElement("div",{className:"w-3 h-3 rounded-full border border-zinc-600 hover:scale-110 transition-transform",style:{backgroundColor:track.color}})),editingTrackName===track.id?/*#__PURE__*/React.createElement("input",{type:"text",value:editNameInput,autoFocus:true,onChange:e=>setEditNameInput(e.target.value),onBlur:()=>{updateTrackName(track.id,editNameInput||track.name);setEditingTrackName(null);},onKeyDown:e=>{if(e.key==='Enter'){updateTrackName(track.id,editNameInput||track.name);setEditingTrackName(null);}if(e.key==='Escape')setEditingTrackName(null);},onClick:e=>e.stopPropagation(),className:"text-xs font-semibold bg-black text-zinc-200 border border-cyan-500 rounded px-1 py-0 w-24 outline-none"}):/*#__PURE__*/React.createElement("span",{className:"text-xs font-semibold text-zinc-300 truncate max-w-[80px] cursor-text hover:text-cyan-300 transition-colors",title:"Click to rename",onClick:e=>{e.stopPropagation();setEditingTrackName(track.id);setEditNameInput(track.name);}},track.name)),/*#__PURE__*/React.createElement("div",{className:"flex flex-wrap gap-0.5 max-w-[100px] mb-0.5"},(track.clips&&track.clips.length>0?track.clips:track.buffer?[{id:'default',name:track.name,startTime:track.startTime}]:[]).slice(0,3).map(c=>/*#__PURE__*/React.createElement("span",{key:c.id,className:"text-[9px] font-mono text-zinc-500 bg-zinc-800/60 rounded px-0.5 truncate max-w-[90px] cursor-pointer hover:text-cyan-400 hover:bg-zinc-700",title:c.name||track.name,onClick:e=>{e.stopPropagation();setSelectedTrackId(track.id);clearLocalSelection();setSelectionMode('global');const start=c.startTime||0;const end=start+(c.buffer?c.buffer.duration:2);setSelectionStart(start);setSelectionEnd(end);showToast(`Selected: ${c.name||track.name}`,'info');}},c.name||track.name),editingClipName&&editingClipName.trackId===track.id&&editingClipName.clipId===c.id?/*#__PURE__*/React.createElement("input",{type:"text",value:editNameInput,autoFocus:true,onChange:e=>setEditNameInput(e.target.value),onBlur:()=>{if(editNameInput.trim())updateClipName(track.id,c.id,editNameInput.trim());setEditingClipName(null);},onKeyDown:e=>{if(e.key==='Enter'){if(editNameInput.trim())updateClipName(track.id,c.id,editNameInput.trim());setEditingClipName(null);}if(e.key==='Escape')setEditingClipName(null);},onClick:e=>e.stopPropagation(),className:"w-20 text-[9px] font-mono bg-black text-cyan-300 border border-cyan-500 rounded px-0.5 py-0 outline-none"}):/*#__PURE__*/React.createElement("button",{className:"text-[9px] text-zinc-600 hover:text-cyan-400 ml-0.5 shrink-0",title:"Sửa tên clip",onClick:e=>{e.stopPropagation();setEditingClipName({trackId:track.id,clipId:c.id});setEditNameInput(c.name||track.name);}},/*#__PURE__*/React.createElement("i",{"data-lucide":"pencil",className:"w-2.5 h-2.5"})))),/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-1"},/*#__PURE__*/React.createElement("button",{onClick:e=>{e.stopPropagation();toggleTrackMute(track.id);},title:"Mute",className:`px-1.5 py-0.5 text-[10px] rounded font-mono font-bold border transition flex items-center gap-0.5 ${track.muted?'bg-red-950 text-red-400 border-red-700':'bg-zinc-800 text-zinc-400 border-transparent hover:text-zinc-200'}`},/*#__PURE__*/React.createElement("i",{"data-lucide":track.muted?"volume-x":"volume-2",className:"w-3 h-3"})),/*#__PURE__*/React.createElement("button",{onClick:e=>{e.stopPropagation();toggleTrackSoloEvaluate(track.id);},title:"Solo",className:`px-1.5 py-0.5 text-[10px] rounded font-mono font-bold border transition flex items-center gap-0.5 ${soloedTrackId===track.id||track.solo?'bg-amber-950 text-amber-400 border-amber-600':'bg-zinc-800 text-zinc-400 border-transparent hover:text-zinc-200'}`},/*#__PURE__*/React.createElement("i",{"data-lucide":soloedTrackId===track.id||track.solo?"headphones":"headphone-off",className:"w-3 h-3"})),/*#__PURE__*/React.createElement("button",{onClick:e=>{e.stopPropagation();toggleTrackArm(track.id);},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.instrumentName||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);},title:"Input Monitor",className:`px-1.5 py-0.5 text-[10px] rounded font-mono font-bold border transition flex items-center gap-0.5 ${track.monitoringEnabled?'bg-amber-600 text-white border-amber-500 hover:bg-amber-500':'bg-zinc-800 text-zinc-500 border-transparent hover:text-zinc-305'}`},/*#__PURE__*/React.createElement("i",{"data-lucide":track.monitoringEnabled?"mic":"mic-off",className:"w-3 h-3"})),/*#__PURE__*/React.createElement("button",{onClick:e=>{e.stopPropagation();deleteTrack(track.id);},className:"p-0.5 rounded text-red-500 hover:bg-red-950/40 hover:text-red-400 transition"},/*#__PURE__*/React.createElement("span",{className:"inline-flex items-center shrink-0"},/*#__PURE__*/React.createElement("i",{"data-lucide":"trash-2",className:"w-3 h-3"}))))),/*#__PURE__*/React.createElement("div",{className:"flex flex-col gap-0.5 text-xs",onClick:e=>e.stopPropagation()},/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-1"},/*#__PURE__*/React.createElement("span",{className:"w-8 text-right text-zinc-500 text-xs"},"Vol:"),/*#__PURE__*/React.createElement("input",{type:"range",min:"-50",max:"7",step:"0.5",value:track.volumeDb??0,onChange:e=>updateTrackVolumeDb(track.id,parseFloat(e.target.value)),className:"flex-1 h-1 bg-zinc-700 rounded-lg appearance-none cursor-pointer accent-cyan-500",style:{height:'4px'}}),/*#__PURE__*/React.createElement("span",{className:"w-12 text-right font-mono text-zinc-300 text-xs"},track.volumeDb??0,"dB")),/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-1"},/*#__PURE__*/React.createElement("span",{className:"w-8 text-right text-zinc-500 text-xs"},"Pan:"),/*#__PURE__*/React.createElement("input",{type:"range",min:"-100",max:"100",step:"1",value:track.pan??0,onChange:e=>updateTrackPan(track.id,parseInt(e.target.value)),className:"flex-1 h-1 bg-zinc-700 rounded-lg appearance-none cursor-pointer accent-purple-500",style:{height:'4px'}}),/*#__PURE__*/React.createElement("span",{className:"w-12 text-right font-mono text-zinc-300 text-xs"},track.pan>0?'R'+track.pan:track.pan<0?'L'+Math.abs(track.pan):'C')),/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-1 mt-0.5"},/*#__PURE__*/React.createElement("span",{className:"w-8 text-right text-zinc-500 text-[10px]"},"In:"),/*#__PURE__*/React.createElement("select",{value:`${track.inputSource?.deviceType||'NONE'}:${track.inputSource?.deviceId||''}`,onChange:e=>{const val=e.target.value;const parts=val.split(':');const type=parts[0];const id=parts.slice(1).join(':');updateTrackInputSource(track.id,type,id);},className:"flex-1 bg-[#18181b] text-zinc-300 text-[10px] rounded border border-zinc-700 focus:outline-none py-0.5 px-1 truncate max-w-[120px]"},/*#__PURE__*/React.createElement("option",{value:"NONE:"},"No Input"),/*#__PURE__*/React.createElement("optgroup",{label:"Microphones"},audioDevices.map(d=>/*#__PURE__*/React.createElement("option",{key:d.deviceId,value:`MICROPHONE:${d.deviceId}`},d.label||`Microphone ${d.deviceId.slice(0,5)}`))),/*#__PURE__*/React.createElement("optgroup",{label:"MIDI Keyboards"},/*#__PURE__*/React.createElement("option",{value:"MIDI_KEYBOARD:ALL"},"Any MIDI Keyboard"),midiDevices.map(d=>/*#__PURE__*/React.createElement("option",{key:d.id,value:`MIDI_KEYBOARD:${d.id}`},d.name||`MIDI Input ${d.id.slice(0,5)}`)))),track.isArmed&&lastMidiNote&&(lastMidiNote.length===0||Date.now()-lastMidiNote.time<3000)&&/*#__PURE__*/React.createElement("span",{className:"text-[9px] font-mono text-emerald-400 ml-0.5 truncate max-w-[60px] shrink-0",title:"MIDI Note:velocity:length"},`${midiPitchToName(lastMidiNote.pitch)}:${lastMidiNote.velocity}:${lastMidiNote.length>0?lastMidiNote.length.toFixed(2)+'s':'...'}`)),track.isArmed&&/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-1 mt-0.5"},/*#__PURE__*/React.createElement("span",{className:"w-8 text-right text-zinc-500 text-[9px]"},"VU:"),/*#__PURE__*/React.createElement("canvas",{ref:el=>{if(el)trackVuRefs.current[track.id]=el;else delete trackVuRefs.current[track.id];},width:100,height:4,className:"flex-1 bg-[#18181b] rounded h-1"}))),/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-1.5 mt-1",onClick:e=>e.stopPropagation()},/*#__PURE__*/React.createElement("input",{type:"file",id:`upload-${track.id}`,accept:"audio/*",className:"hidden",onChange:e=>loadFileOnTrack(track.id,e.target.files[0])}),/*#__PURE__*/React.createElement("label",{htmlFor:`upload-${track.id}`,className:"px-2.5 py-1 bg-zinc-800 hover:bg-zinc-700 text-zinc-300 rounded text-xs flex items-center gap-1 cursor-pointer border border-zinc-700 font-medium"},/*#__PURE__*/React.createElement("span",{className:"inline-flex items-center shrink-0"},/*#__PURE__*/React.createElement("i",{"data-lucide":"upload",className:"w-3 h-3"}))," File"),/*#__PURE__*/React.createElement("button",{onClick:e=>{e.stopPropagation();showToast('FX panel for track '+track.id,'info');},className:"px-2.5 py-1 bg-zinc-800 hover:bg-zinc-700 text-purple-400 border border-purple-900 rounded text-xs font-bold flex items-center gap-1"},/*#__PURE__*/React.createElement("span",{className:"inline-flex items-center shrink-0"},/*#__PURE__*/React.createElement("i",{"data-lucide":"wand-2",className:"w-3 h-3"}))," FX: ",/*#__PURE__*/React.createElement("span",{className:"text-zinc-500 font-normal"}," None")),/*#__PURE__*/React.createElement("button",{onClick:e=>{e.stopPropagation();openInstrumentSelector(track.id);},className:"px-2.5 py-1 bg-zinc-800 hover:bg-zinc-700 text-amber-400 border border-amber-800 rounded text-xs font-bold flex items-center gap-1"},/*#__PURE__*/React.createElement("span",{className:"inline-flex items-center shrink-0"},/*#__PURE__*/React.createElement("i",{"data-lucide":"music",className:"w-3 h-3"}))," Synth: ",/*#__PURE__*/React.createElement("span",{className:"text-zinc-500 font-normal"},track.instrumentName||track.instrumentId||"None"))),/*#__PURE__*/React.createElement("div",{onMouseDown:e=>handleTrackResizeMouseDown(e,track.id),className:"absolute bottom-0 left-0 right-0 h-1 cursor-ns-resize z-30 hover:bg-cyan-500/50 transition-colors",onClick:e=>e.stopPropagation()}));})),/*#__PURE__*/React.createElement("div",{className:"h-[48px] border-r border-zinc-900 bg-[#1e1e1e]/50 shrink-0"})),/*#__PURE__*/React.createElement("div",{ref:handleTimelineWrapperRef,onScroll:handleTimelineScroll,className:"flex-1 relative z-10 overflow-x-auto overflow-y-auto min-w-0"},/*#__PURE__*/React.createElement("div",{style:{width:`${timelineWidth}px`},className:"relative flex flex-col min-h-full"},/*#__PURE__*/React.createElement("div",{className:"sticky top-0 z-45 flex h-10 border-b border-zinc-900 bg-[#242424] shrink-0"},/*#__PURE__*/React.createElement("div",{ref:rulerRef,className:"flex-1 relative h-full flex items-center select-none cursor-ew-resize overflow-hidden",onMouseDown:handleRulerMouseDown},Array.from({length:Math.ceil(maxDuration)}).map((_,i)=>{const sec=i;const x=sec*zoom;return/*#__PURE__*/React.createElement("div",{key:i,className:"absolute h-full border-l border-zinc-700 pl-1 pt-1 text-[14px] font-mono text-zinc-300 pointer-events-none",style:{left:`${x}px`}},formatTimeSimple(sec));}))),selectionMode==='global'&&selLeft!==null&&selRight!==null&&selRight>selLeft&&/*#__PURE__*/React.createElement("div",{className:"absolute inset-0 pointer-events-none z-20",style:{left:`${selLeft*zoom}px`,width:`${(selRight-selLeft)*zoom}px`,top:'40px'}},/*#__PURE__*/React.createElement("div",{className:"w-full h-full bg-amber-500/10",style:{borderLeft:'1px solid #f59e0b',borderRight:'1px solid #f59e0b'}})),/*#__PURE__*/React.createElement("div",{className:"sticky top-10 z-35 flex h-[40px] border-b border-purple-500/30 bg-[#1a1a2e] shrink-0"},/*#__PURE__*/React.createElement("div",{className:"flex-1 relative h-full overflow-hidden bg-[#1a1a2e]"},/*#__PURE__*/React.createElement(TempoTrackLane,{bpm:parseInt(bpm)||120,zoom:zoom,timelineWidth:timelineWidth,viewportWidth:viewportWidth,onPlayheadSet:handlePlayheadSet,snapValue:snapValue,onRulerMouseDown:handleRulerMouseDown,scrollLeft:scrollLeft}))),/*#__PURE__*/React.createElement("div",{className:"flex-1 flex flex-col divide-y divide-[#141414] relative bg-[#111111] min-h-full"},activeTracks.map((track,idx)=>{const isSelected=selectedTrackId===track.id;const autoHeight=track.height||(track.isArmed?164:140);return/*#__PURE__*/React.createElement("div",{key:track.id,style:{height:`${autoHeight}px`},className:`shrink-0 relative border-b border-[#141414] hover:bg-zinc-850/5 transition-colors ${isSelected?'bg-zinc-800/10':''}`,onDragOver:e=>e.preventDefault(),onDrop:e=>{e.preventDefault();if(e.dataTransfer.files[0])loadFileOnTrack(track.id,e.dataTransfer.files[0]);},onMouseEnter:()=>setHoveredTrackId(track.id)},/*#__PURE__*/React.createElement(WaveformLane,{track:track,zoom:zoom,timelineWidth:timelineWidth,viewportWidth:viewportWidth,onSelectRange:handleSelectRange,onPlayheadSet:handlePlayheadSet,isSelected:isSelected,onSelectTrack:setSelectedTrackId,markers:track.markers,onTrackLaneMouseDown:handleTrackLaneMouseDown,onContextMenu:handleContextMenu,onClipDragStart:handleClipDragStart,onClipStretchStart:handleClipStretchStart,onSectionItemDragStart:handleSectionItemDragStart,onSectionItemResizeStart:handleSectionItemResizeStart,onSelectionEdgeDragStart:handleSelectionEdgeDragStart,setSelectedClipId:setSelectedClipId,selectedClipId:selectedClipId,activeTool:activeTool,onSplitTrackAtTime:handleSplitTrackAtTime,onEditClipInSubTab:handleEditClipInSubTab,onEditSectionInTab:handleEditSectionInTab,onEditMidiInTab:handleEditMidiInTab,snapValue:snapValue,bpm:bpm,selectionMode:selectionMode,localSelectionTrackId:localSelectionTrackId,localSelectionStart:localSelectionStart,currentTime:currentTime,getLocalAnchor:()=>localSelectionAnchorRef.current,onClearLocalSelection:clearLocalSelection,onSetSelectionMode:setSelectionMode,onSetSelectionStart:setSelectionStart,onSetSelectionEnd:setSelectionEnd,onSetCurrentTime:setCurrentTime,onSetLocalSelectionTrackId:setLocalSelectionTrackId,onSetLocalSelectionStart:setLocalSelectionStart,onSetLocalSelectionEnd:setLocalSelectionEnd,localSelLeft:localSelectionStart!==null&&localSelectionEnd!==null?Math.min(localSelectionStart,localSelectionEnd):null,localSelRight:localSelectionStart!==null&&localSelectionEnd!==null?Math.max(localSelectionStart,localSelectionEnd):null,scrollLeft:scrollLeft,recordingState:recordingState,recTempMidiNotes:recTempMidiNotes,recTempAudioBuffer:recTempAudioBuffer,recStartTimelineTime:recStartTimelineTime,canvasRedrawCount:canvasRedrawCount}),selectionMode==='local'&&localSelectionTrackId===track.id&&localSelectionStart!==null&&localSelectionEnd!==null&&Math.abs(localSelectionEnd-localSelectionStart)>0&&/*#__PURE__*/React.createElement("div",{className:"absolute top-0 bottom-0 border-l border-r border-amber-400 bg-amber-500/20 z-20 pointer-events-none",style:{left:`${Math.min(localSelectionStart,localSelectionEnd)*zoom}px`,width:`${Math.abs(localSelectionEnd-localSelectionStart)*zoom}px`}},/*#__PURE__*/React.createElement("div",{className:"absolute -left-[1px] top-0 bottom-0 w-[2px] bg-amber-400 z-30 pointer-events-auto cursor-ew-resize",onMouseDown:e=>handleHandleDragStart(e,'left')}),/*#__PURE__*/React.createElement("div",{className:"absolute -right-[1px] top-0 bottom-0 w-[2px] bg-amber-400 z-30 pointer-events-auto cursor-ew-resize",onMouseDown:e=>handleHandleDragStart(e,'right')})),track.buffer&&/*#__PURE__*/React.createElement("div",{className:"absolute right-2 top-2 flex items-center gap-1 z-10 opacity-70 hover:opacity-100"},/*#__PURE__*/React.createElement("button",{onClick:()=>handleSplitTrack(track.id),className:"px-1.5 py-0.5 bg-zinc-900/95 text-zinc-300 rounded text-xs flex items-center gap-1 border border-zinc-700/50"},/*#__PURE__*/React.createElement("span",{className:"inline-flex items-center shrink-0"},/*#__PURE__*/React.createElement("i",{"data-lucide":"scissors",className:"w-2.5 h-2.5 text-cyan-400"}))," Cắt")),/*#__PURE__*/React.createElement("div",{onMouseDown:e=>handleTrackResizeMouseDown(e,track.id),className:"absolute bottom-0 left-0 right-0 h-1 cursor-ns-resize z-30 hover:bg-cyan-500/50 transition-colors",onClick:e=>e.stopPropagation()}));}),/*#__PURE__*/React.createElement("div",{className:"h-[48px] flex items-center justify-center text-xs text-zinc-500 hover:bg-zinc-900/20 cursor-pointer select-none border-t border-dashed border-zinc-800",onMouseEnter:()=>{if(draggedClipRef.current||draggedSectionItemRef.current){setHoveredTrackId(addNewTrack());}},onClick:addNewTrack},/*#__PURE__*/React.createElement("span",{className:"flex items-center gap-1 text-zinc-400"},/*#__PURE__*/React.createElement("span",{className:"inline-flex items-center shrink-0"},/*#__PURE__*/React.createElement("i",{"data-lucide":"plus",className:"w-3.5 h-3.5"}))," Kéo clip xuống hoặc Click tạo Track")),selectionMode==='global'&&selLeft!==null&&selRight!==null&&selRight>selLeft&&/*#__PURE__*/React.createElement("div",{className:"absolute top-0 bottom-0 border-l border-r border-amber-500 bg-amber-500/10 z-20 selection-interactive-box cursor-grab active:cursor-grabbing",style:{left:`${selLeft*zoom}px`,width:`${(selRight-selLeft)*zoom}px`},onMouseDown:handleSelectionBodyDragStart},/*#__PURE__*/React.createElement("div",{className:"absolute -left-[1px] top-0 bottom-0 w-[1px] bg-amber-500 hover:bg-amber-400 cursor-ew-resize z-30",onMouseDown:e=>handleHandleDragStart(e,'left')}),/*#__PURE__*/React.createElement("div",{className:"absolute -right-[1px] top-0 bottom-0 w-[1px] bg-amber-500 hover:bg-amber-400 cursor-ew-resize z-30",onMouseDown:e=>handleHandleDragStart(e,'right')})),/*#__PURE__*/React.createElement("div",{className:"absolute top-0 bottom-0 w-[2px] bg-[#ef4444] z-20 pointer-events-none",style:{left:`${playheadLeftPos}px`}},/*#__PURE__*/React.createElement("div",{className:"w-3 h-3 bg-[#ef4444] rotate-45 transform -translate-x-1/2 -translate-y-1/2 absolute top-0"})))))):(()=>{const st=subTabs.find(s=>s.id===activeTab);if(!st)return null;if(st.type==='PIANO_ROLL'){return/*#__PURE__*/React.createElement(PianoRollTabEditor,{st:st,zoom:zoom,bpm:bpm,viewportWidth:viewportWidth,onClose:()=>closeSubTab(st.id),onUpdateNotes:handleUpdateMidiNotes,onSaveNotes:handleSaveMidiNotes,setSubTabs:setSubTabs,onPlayPause:handlePlayPause,onStop:stopAllPlayback,isPlaying:isPlaying,playPreviewNote:playMidiPreviewNote});}const subTrack=tracks.find(t=>t.id===st.trackId);const vTrack=subTrack?{...subTrack,buffer:st.buffer,isSubTab:true}:null;const subTabDuration=st.type==='PIANO_ROLL'?(()=>{const notes=st.notes||[];const beatSec=60.0/(parseFloat(bpm)||120);let maxEnd=0;notes.forEach(n=>{const end=(n.start_beat||0)+(n.duration_beats||1);if(end>maxEnd)maxEnd=end;});return maxEnd*beatSec+1.0;})():st.buffer&&'duration'in st.buffer?st.buffer.duration:4.0;const subTabTimelineWidth=Math.max(zoom*subTabDuration,viewportWidth);return/*#__PURE__*/React.createElement(React.Fragment,null,/*#__PURE__*/React.createElement("div",{className:"w-[320px] shrink-0 z-30 bg-[#262626] overflow-y-scroll no-scrollbar flex flex-col border-r border-zinc-900",style:{scrollbarWidth:'none',msOverflowStyle:'none'}},/*#__PURE__*/React.createElement("div",{className:"sticky top-0 z-50 flex items-center h-10 border-b border-zinc-900 bg-[#242424] px-2 justify-between shrink-0"},/*#__PURE__*/React.createElement("span",{className:"text-xs font-bold text-zinc-500 uppercase"},"Sub-Tab"),/*#__PURE__*/React.createElement("button",{onClick:()=>closeSubTab(st.id),className:"px-1.5 py-0.5 text-xs bg-zinc-800 hover:bg-zinc-700 text-zinc-400 rounded border border-zinc-700 flex items-center gap-1"},/*#__PURE__*/React.createElement("span",{className:"inline-flex items-center shrink-0"},/*#__PURE__*/React.createElement("i",{"data-lucide":"x",className:"w-3 h-3"}))," Close")),vTrack?/*#__PURE__*/React.createElement("div",{key:vTrack.id,className:"flex-1 flex flex-col p-2.5 bg-[#1e1e1e] border-r border-zinc-900 border-l-4 border-l-cyan-500"},/*#__PURE__*/React.createElement("div",{className:"flex items-center justify-between mb-2"},/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-2"},/*#__PURE__*/React.createElement("label",{onClick:e=>{e.stopPropagation();const el=e.currentTarget.querySelector('input');if(el)el.click();},className:"cursor-pointer"},/*#__PURE__*/React.createElement("input",{type:"color",value:vTrack.color||'#0f766e',onChange:e=>{e.stopPropagation();updateTrackColor(vTrack.id,e.target.value);},className:"w-0 h-0 opacity-0 absolute pointer-events-none"}),/*#__PURE__*/React.createElement("div",{className:"w-3 h-3 rounded-full border border-zinc-600 hover:scale-110 transition-transform",style:{backgroundColor:vTrack.color}})),editingTrackName===vTrack.id?/*#__PURE__*/React.createElement("input",{type:"text",value:editNameInput,autoFocus:true,onChange:e=>setEditNameInput(e.target.value),onBlur:()=>{updateTrackName(vTrack.id,editNameInput||vTrack.name);setEditingTrackName(null);},onKeyDown:e=>{if(e.key==='Enter'){updateTrackName(vTrack.id,editNameInput||vTrack.name);setEditingTrackName(null);}if(e.key==='Escape')setEditingTrackName(null);},onClick:e=>e.stopPropagation(),className:"text-xs font-semibold bg-black text-zinc-200 border border-cyan-500 rounded px-1 py-0 w-24 outline-none"}):/*#__PURE__*/React.createElement("span",{className:"text-xs font-semibold text-zinc-300 truncate max-w-[80px] cursor-text hover:text-cyan-300 transition-colors",title:"Click to rename",onClick:e=>{e.stopPropagation();setEditingTrackName(vTrack.id);setEditNameInput(vTrack.name);}},vTrack.name)),/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-1"},/*#__PURE__*/React.createElement("button",{onClick:e=>{e.stopPropagation();toggleTrackMute(vTrack.id);},className:`px-1.5 py-0.5 text-xs rounded font-mono font-bold border ${vTrack.muted?'bg-red-950 text-red-400 border-red-700':'bg-zinc-800 text-zinc-400 border-transparent hover:text-zinc-200'}`},"M"),/*#__PURE__*/React.createElement("button",{onClick:e=>{e.stopPropagation();toggleTrackSoloEvaluate(vTrack.id);},className:`px-1.5 py-0.5 text-xs rounded font-mono font-bold border ${soloedTrackId===vTrack.id||vTrack.solo?'bg-amber-950 text-amber-400 border-amber-600':'bg-zinc-800 text-zinc-400 border-transparent hover:text-zinc-200'}`},"S"))),/*#__PURE__*/React.createElement("div",{className:"flex flex-col gap-2.5 text-[14px] mb-3"},/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-1"},/*#__PURE__*/React.createElement("span",{className:"w-10 text-right text-zinc-500 text-[14px]"},"Vol:"),/*#__PURE__*/React.createElement("input",{type:"range",min:"-50",max:"7",step:"0.5",id:`tcp-vol-${st.id}`,value:vTrack.volumeDb??0,onChange:e=>updateTrackVolumeDb(vTrack.id,parseFloat(e.target.value)),className:"flex-1 h-1 bg-zinc-700 rounded-lg appearance-none cursor-pointer accent-cyan-500",style:{height:'4px'}}),/*#__PURE__*/React.createElement("span",{className:"w-16 text-right font-mono text-zinc-300 text-[14px]",id:`tcp-vol-label-${st.id}`},vTrack.volumeDb??0,"dB")),/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-1"},/*#__PURE__*/React.createElement("span",{className:"w-10 text-right text-zinc-500 text-[14px]"},"Pan:"),/*#__PURE__*/React.createElement("input",{type:"range",min:"-100",max:"100",step:"1",id:`tcp-pan-${st.id}`,value:vTrack.pan??0,onChange:e=>updateTrackPan(vTrack.id,parseInt(e.target.value)),className:"flex-1 h-1 bg-zinc-700 rounded-lg appearance-none cursor-pointer accent-purple-500",style:{height:'4px'}}),/*#__PURE__*/React.createElement("span",{className:"w-16 text-right font-mono text-zinc-300 text-[14px]",id:`tcp-pan-label-detailed-${st.id}`},vTrack.pan>0?'R'+vTrack.pan:vTrack.pan<0?'L'+Math.abs(vTrack.pan):'C')),/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-1.5 text-[14px]"},/*#__PURE__*/React.createElement("button",{onClick:()=>setSubTabs(prev=>prev.map(s=>s.id===st.id?{...s,isLooping:!s.isLooping}:s)),className:`px-2 py-0.5 text-[14px] rounded border font-bold ${st.isLooping?'bg-amber-700 text-white border-amber-500':'bg-zinc-800 text-zinc-400 border-zinc-700'}`},/*#__PURE__*/React.createElement("span",{className:"inline-flex items-center shrink-0"},/*#__PURE__*/React.createElement("i",{"data-lucide":"repeat",className:"w-3.5 h-3.5"}))),/*#__PURE__*/React.createElement("button",{onClick:()=>updateSubTabEffects(st.id,{reverse:!(st.effects||{}).reverse}),className:`px-2 py-0.5 rounded border text-[14px] font-bold ${(st.effects||{}).reverse?'bg-zinc-600 text-white border-zinc-500':'bg-zinc-800 text-zinc-400 border-zinc-700'} flex items-center justify-center`,title:"Reverse"},/*#__PURE__*/React.createElement("span",{className:"inline-flex items-center shrink-0"},/*#__PURE__*/React.createElement("i",{"data-lucide":"arrow-left-right",className:"w-3.5 h-3.5"}))),/*#__PURE__*/React.createElement("span",{className:"w-10 text-right text-zinc-500 text-[14px]"},"Loop:"),/*#__PURE__*/React.createElement("input",{type:"number",min:"0",max:"999",value:st.loopCount||0,onChange:e=>setSubTabs(prev=>prev.map(s=>s.id===st.id?{...s,loopCount:Math.max(0,parseInt(e.target.value)||0)}:s)),className:"w-12 bg-black text-amber-300 text-[14px] px-1 py-0.5 rounded border border-zinc-700 text-center font-mono",title:"Loop count"}))),/*#__PURE__*/React.createElement("div",{className:"flex gap-1 justify-between my-2.5"},/*#__PURE__*/React.createElement("div",{className:"flex flex-col items-center bg-zinc-900/60 p-1.5 rounded border border-zinc-800 flex-1"},/*#__PURE__*/React.createElement("span",{className:"text-[14px] font-bold text-zinc-400 uppercase tracking-tighter",title:"Normalize"},"Norm"),/*#__PURE__*/React.createElement("input",{type:"range",min:"-12",max:"0",step:"0.1",value:subTabNormVal,onChange:e=>setSubTabNormVal(parseFloat(e.target.value)),style:{writingMode:'vertical-lr',direction:'rtl',height:'120px',width:'20px',accentColor:'#a1a1aa'},className:"my-2 cursor-pointer"}),/*#__PURE__*/React.createElement("span",{className:"text-[14px] font-mono text-zinc-300 font-bold"},subTabNormVal,"dB"),/*#__PURE__*/React.createElement("button",{onClick:()=>applySubTabEffect(st.id,'normalize',subTabNormVal),className:"mt-1.5 px-1 py-1 bg-zinc-700 hover:bg-zinc-600 text-zinc-100 rounded text-[14px] font-bold w-full uppercase border border-zinc-650"},"Apply")),/*#__PURE__*/React.createElement("div",{className:"flex flex-col items-center bg-zinc-900/60 p-1.5 rounded border border-zinc-800 flex-1"},/*#__PURE__*/React.createElement("span",{className:"text-[14px] font-bold text-zinc-400 uppercase tracking-tighter",title:"Pitch Shift"},"Pitch"),/*#__PURE__*/React.createElement("input",{type:"range",min:"-12",max:"12",step:"0.5",value:subTabPitchVal,onChange:e=>setSubTabPitchVal(parseFloat(e.target.value)),style:{writingMode:'vertical-lr',direction:'rtl',height:'120px',width:'20px',accentColor:'#a1a1aa'},className:"my-2 cursor-pointer"}),/*#__PURE__*/React.createElement("span",{className:"text-[14px] font-mono text-zinc-300 font-bold"},subTabPitchVal>0?'+':'',subTabPitchVal,"st"),/*#__PURE__*/React.createElement("button",{onClick:()=>applySubTabEffect(st.id,'pitch',subTabPitchVal),className:"mt-1.5 px-1 py-1 bg-zinc-700 hover:bg-zinc-600 text-zinc-100 rounded text-[14px] font-bold w-full uppercase border border-zinc-650"},"Apply")),/*#__PURE__*/React.createElement("div",{className:"flex flex-col items-center bg-zinc-900/60 p-1.5 rounded border border-zinc-800 flex-1"},/*#__PURE__*/React.createElement("span",{className:"text-[14px] font-bold text-zinc-400 uppercase tracking-tighter",title:"Gain Multiplier"},"Gain"),/*#__PURE__*/React.createElement("input",{type:"range",min:"0",max:"150",step:"1",value:subTabGainVal,onChange:e=>setSubTabGainVal(parseInt(e.target.value)),style:{writingMode:'vertical-lr',direction:'rtl',height:'120px',width:'20px',accentColor:'#a1a1aa'},className:"my-2 cursor-pointer"}),/*#__PURE__*/React.createElement("span",{className:"text-[14px] font-mono text-zinc-300 font-bold"},subTabGainVal,"%"),/*#__PURE__*/React.createElement("button",{onClick:()=>applySubTabEffect(st.id,'gain',subTabGainVal),className:"mt-1.5 px-1 py-1 bg-zinc-700 hover:bg-zinc-600 text-zinc-100 rounded text-[14px] font-bold w-full uppercase border border-zinc-650"},"Apply"))),/*#__PURE__*/React.createElement("div",{className:"flex-1"}),/*#__PURE__*/React.createElement("div",{className:"mt-auto pt-2.5 border-t border-zinc-800"},/*#__PURE__*/React.createElement("div",{className:"flex items-center justify-between text-[14px] text-zinc-500 mb-1 font-semibold uppercase"},/*#__PURE__*/React.createElement("span",null,"Duration:"),/*#__PURE__*/React.createElement("span",{className:"font-mono text-zinc-300"},st.buffer?formatTime(subTabDuration):'0s')),/*#__PURE__*/React.createElement("div",{className:"flex items-center justify-between text-[14px] text-zinc-500 mb-2.5 font-semibold uppercase"},/*#__PURE__*/React.createElement("span",null,"SR:"),/*#__PURE__*/React.createElement("span",{className:"font-mono text-zinc-300"},st.buffer?st.buffer.sampleRate:0," Hz")),/*#__PURE__*/React.createElement("div",{className:"grid grid-cols-2 gap-1 mb-2"},/*#__PURE__*/React.createElement("button",{onClick:async e=>{subTabAiTrackIdRef.current=st.trackId;handleAIScan();},disabled:analysisState.isRunning,className:"py-1 bg-purple-900 hover:bg-purple-800 text-purple-100 font-bold rounded text-xs border border-purple-700 flex items-center justify-center gap-1"},/*#__PURE__*/React.createElement("span",{className:"inline-flex items-center shrink-0"},/*#__PURE__*/React.createElement("i",{"data-lucide":"map-pin",className:"w-3 h-3"}))," Scan"),/*#__PURE__*/React.createElement("button",{onClick:async e=>{subTabAiTrackIdRef.current=st.trackId;handleAICutToNewTrack();},disabled:analysisState.isRunning,className:"py-1 bg-fuchsia-800 hover:bg-fuchsia-700 text-white font-bold rounded text-xs flex items-center justify-center gap-1"},/*#__PURE__*/React.createElement("span",{className:"inline-flex items-center shrink-0"},/*#__PURE__*/React.createElement("i",{"data-lucide":"scissors",className:"w-3 h-3"}))," Cut"),/*#__PURE__*/React.createElement("button",{onClick:async e=>{subTabAiTrackIdRef.current=st.trackId;if(st.buffer){setSelectionRangeOnBuffer(st.buffer,st.selectionStart||0,st.selectionEnd||st.buffer.duration);}handleAIAnalysicLoop();},disabled:analysisState.isRunning,className:"py-1 bg-violet-800 hover:bg-violet-700 text-violet-100 font-bold rounded text-xs border border-violet-600 flex items-center justify-center gap-1 col-span-2"},/*#__PURE__*/React.createElement("span",{className:"inline-flex items-center shrink-0"},/*#__PURE__*/React.createElement("i",{"data-lucide":"sparkles",className:"w-3 h-3"}))," AI Analysic Loop")),/*#__PURE__*/React.createElement("button",{onClick:()=>exportSubTabBuffer(st.id),className:"w-full py-1.5 mb-1.5 bg-blue-700 hover:bg-blue-600 text-white font-bold rounded text-[14px] flex items-center justify-center gap-1 transition"},/*#__PURE__*/React.createElement("span",{className:"inline-flex items-center shrink-0"},/*#__PURE__*/React.createElement("i",{"data-lucide":"download",className:"w-4 h-4"}))," Export"),/*#__PURE__*/React.createElement("button",{onClick:()=>applySubTab(st.id),className:"w-full py-1.5 bg-emerald-700 hover:bg-emerald-600 text-white font-bold rounded text-[14px] flex items-center justify-center gap-1 transition"},/*#__PURE__*/React.createElement("span",{className:"inline-flex items-center shrink-0"},/*#__PURE__*/React.createElement("i",{"data-lucide":"save",className:"w-4 h-4"}))," Save"))):/*#__PURE__*/React.createElement("div",{className:"flex-1 flex items-center justify-center text-xs text-zinc-500"},"Track not found")),/*#__PURE__*/React.createElement("div",{ref:handleTimelineWrapperRef,className:"flex-1 relative z-10 overflow-x-auto overflow-y-auto min-w-0"},/*#__PURE__*/React.createElement("div",{style:{width:`${subTabTimelineWidth}px`},className:"relative flex flex-col min-h-full"},/*#__PURE__*/React.createElement("div",{className:"sticky top-0 z-45 flex h-10 border-b border-zinc-900 bg-[#242424] shrink-0"},/*#__PURE__*/React.createElement("div",{className:"flex-1 relative h-full flex items-center select-none cursor-ew-resize overflow-hidden",onMouseDown:handleRulerMouseDown},Array.from({length:Math.ceil(st.buffer?st.buffer.duration:0)}).map((_,i)=>{const sec=i;const x=sec*zoom;return/*#__PURE__*/React.createElement("div",{key:i,className:"absolute h-full border-l border-zinc-700 pl-1 pt-1 text-[14px] font-mono text-zinc-300 pointer-events-none",style:{left:`${x}px`}},formatTimeSimple(sec));}))),/*#__PURE__*/React.createElement("div",{className:"sticky top-10 z-35 flex h-[40px] border-b border-purple-500/30 bg-[#1a1a2e] shrink-0"},/*#__PURE__*/React.createElement("div",{className:"flex-1 relative h-full overflow-hidden bg-[#1a1a2e]"},/*#__PURE__*/React.createElement(TempoTrackLane,{bpm:parseInt(bpm)||120,zoom:zoom,timelineWidth:subTabTimelineWidth,onPlayheadSet:setCurrentTime,snapValue:snapValue}))),/*#__PURE__*/React.createElement("div",{className:"flex-1 flex flex-col relative bg-[#111111] min-h-full"},vTrack&&/*#__PURE__*/React.createElement("div",{style:{height:`${subTabHeight}px`},className:"relative overflow-hidden border-2 border-purple-500/40 rounded bg-[#151515] mx-1 mt-1 shrink-0"},/*#__PURE__*/React.createElement(SubTabWaveform,{buffer:st.buffer,subTabId:st.id,activeTab:activeTab,activeTool:activeTool,currentTime:st.currentTime,selectionStart:st.selectionStart,selectionEnd:st.selectionEnd,onSelectRange:(start,end)=>setSubTabs(prev=>prev.map(s=>s.id===st.id?{...s,selectionStart:start,selectionEnd:end}:s)),onPlayheadSet:time=>setSubTabs(prev=>prev.map(s=>s.id===st.id?{...s,currentTime:time}:s)),onContextMenu:(e,clickTime)=>setContextMenu({x:e.clientX,y:e.clientY,isSubTab:true,subTabId:st.id,time:clickTime}),zoom:zoom,timelineWidth:subTabTimelineWidth,color:vTrack.color,name:vTrack.name,speed:st.speed||1.0,volumeNodes:st.volumeNodes||[],panningNodes:st.panningNodes||[],fadeInLen:st.fadeInLen||0,fadeOutLen:st.fadeOutLen||0,graphMode:st.graphMode,channelInfo:st.channelInfo,selectedNodeTime:subTabSelectedNodeTime,setSelectedNodeTime:setSubTabSelectedNodeTime,onUpdateNodes:nodes=>setSubTabs(prev=>prev.map(s=>s.id===st.id?{...s,[s.graphMode==='pan'?'panningNodes':'volumeNodes']:nodes}:s)),onUpdateFade:fade=>setSubTabs(prev=>prev.map(s=>s.id===st.id?{...s,fadeInLen:fade.fadeInLen??s.fadeInLen,fadeOutLen:fade.fadeOutLen??s.fadeOutLen}:s)),onModeToggle:()=>setSubTabs(prev=>prev.map(s=>s.id===st.id?{...s,graphMode:s.graphMode==='pan'?null:'pan'}:s)),onSpeedChange:newSpeed=>{setSubTabs(prev=>prev.map(s=>{if(s.id!==st.id)return s;const oldSpeed=s.speed||1.0;const ratio=oldSpeed/newSpeed;const newVolumeNodes=(s.volumeNodes||[]).map(n=>({...n,time:n.time*ratio}));const newPanningNodes=(s.panningNodes||[]).map(n=>({...n,time:n.time*ratio}));return{...s,speed:newSpeed,volumeNodes:newVolumeNodes,panningNodes:newPanningNodes,fadeInLen:(s.fadeInLen||0)*ratio,fadeOutLen:(s.fadeOutLen||0)*ratio,currentTime:(s.currentTime||0)*ratio,label:s.label.replace(/\s\(\d+%\)$/,'')+` (${Math.round(newSpeed*100)}%)`};}));const n=activeTrackNodesRef.current[st.trackId];if(n&&n.source)n.source.playbackRate.value=newSpeed;// Reset time refs to prevent playhead jump when speed changes mid-playback +const ctx=getAudioContext();const elapsed=ctx.currentTime-startAudioTimeRef.current;const oldSpeed=activePlaybackSpeedRef.current;const ratio=oldSpeed/newSpeed;startBufferOffsetRef.current=startBufferOffsetRef.current+elapsed*oldSpeed;startOffsetTimeRef.current=(startOffsetTimeRef.current+elapsed)*ratio;startAudioTimeRef.current=ctx.currentTime;activePlaybackSpeedRef.current=newSpeed;}}),/*#__PURE__*/React.createElement("div",{onMouseDown:handleSubTabResizeMouseDown,className:"absolute bottom-0 left-0 right-0 h-1.5 cursor-ns-resize z-30 hover:bg-purple-500/50 transition-colors"})),st.selectionStart!==null&&st.selectionEnd!==null&&st.selectionEnd>st.selectionStart&&/*#__PURE__*/React.createElement("div",{className:"absolute top-0 bottom-0 border-l border-r border-amber-500 bg-amber-500/10 z-20 pointer-events-none",style:{left:`${Math.min(st.selectionStart,st.selectionEnd)*zoom}px`,width:`${Math.abs(st.selectionEnd-st.selectionStart)*zoom}px`}})))));})())),/*#__PURE__*/React.createElement("div",{className:"w-1 cursor-ew-resize hover:bg-cyan-500/50 active:bg-cyan-400 transition-colors shrink-0 z-10",onMouseDown:startColResize}),renderDock('right','Right')),renderDock('bottom','Bottom'));})(),/*#__PURE__*/React.createElement("div",{className:"h-6 bg-[#141414] border-t border-zinc-900 px-4 flex items-center justify-between text-xs text-zinc-500 select-none shrink-0"},/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-4"},/*#__PURE__*/React.createElement("span",null,"Status: ",isPlaying?'Playing':'Stopped'),activeTab!=='main'&&/*#__PURE__*/React.createElement("span",{className:"text-amber-400 font-semibold uppercase"},"Sub-Tab"),activeTab==='main'&&/*#__PURE__*/React.createElement("span",{className:"text-cyan-400 font-semibold uppercase"},"Track: ID ",selectedTrackId),selectionMode==='local'&&/*#__PURE__*/React.createElement("span",{className:"text-amber-400 font-semibold uppercase text-xs"},"Local Sel"),selectionMode==='global'&&/*#__PURE__*/React.createElement("span",{className:"text-purple-400 font-semibold uppercase text-xs"},"Global Sel"),soloedTrackId&&/*#__PURE__*/React.createElement("span",{className:"text-amber-500 font-semibold"},"Solo: ID ",soloedTrackId),isLoopingSelection&&selectionMode==='local'&&/*#__PURE__*/React.createElement("span",{className:"text-emerald-400 font-semibold uppercase text-xs"},"Solo Loop"),isLoopingSelection&&selectionMode!=='local'&&/*#__PURE__*/React.createElement("span",{className:"text-cyan-400 font-semibold uppercase text-xs"},"Master Loop")),/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-2"},/*#__PURE__*/React.createElement("button",{onClick:()=>setShowExportPanel(p=>!p),className:`px-1.5 py-0.5 rounded text-xs font-semibold transition flex items-center gap-1 ${showExportPanel?'bg-cyan-900 text-cyan-300':'text-zinc-500 hover:text-zinc-300'}`,title:`Export Panel (${panelPositions.export})`},/*#__PURE__*/React.createElement("span",{className:"inline-flex items-center shrink-0"},/*#__PURE__*/React.createElement("i",{"data-lucide":"save",className:"w-3 h-3"})),/*#__PURE__*/React.createElement("span",{className:"text-[7px] opacity-60"},showExportPanel?panelPositions.export[0].toUpperCase():'')),/*#__PURE__*/React.createElement("button",{onClick:()=>setShowSelectionPanel(p=>!p),className:`px-1.5 py-0.5 rounded text-xs font-semibold transition flex items-center gap-1 ${showSelectionPanel?'bg-amber-900 text-amber-300':'text-zinc-500 hover:text-zinc-300'}`,title:`Selection Panel (${panelPositions.selection})`},/*#__PURE__*/React.createElement("span",{className:"inline-flex items-center shrink-0"},/*#__PURE__*/React.createElement("i",{"data-lucide":"sliders",className:"w-3 h-3"})),/*#__PURE__*/React.createElement("span",{className:"text-[7px] opacity-60"},showSelectionPanel?panelPositions.selection[0].toUpperCase():'')),/*#__PURE__*/React.createElement("button",{onClick:()=>setShowAIPanel(p=>!p),className:`px-1.5 py-0.5 rounded text-xs font-semibold transition flex items-center gap-1 ${showAIPanel?'bg-purple-900 text-purple-300':'text-zinc-500 hover:text-zinc-300'}`,title:`AI Panel (${panelPositions.ai})`},/*#__PURE__*/React.createElement("span",{className:"inline-flex items-center shrink-0"},/*#__PURE__*/React.createElement("i",{"data-lucide":"cpu",className:"w-3 h-3"})),/*#__PURE__*/React.createElement("span",{className:"text-[7px] opacity-60"},showAIPanel?panelPositions.ai[0].toUpperCase():'')),/*#__PURE__*/React.createElement("button",{onClick:()=>setShowFxRack(p=>!p),className:`px-1.5 py-0.5 rounded text-xs font-semibold transition flex items-center gap-1 ${showFxRack?'bg-rose-900 text-rose-300':'text-zinc-500 hover:text-zinc-300'}`,title:`FX Rack Panel (${panelPositions.fx_rack||'bottom'})`},/*#__PURE__*/React.createElement("span",{className:"inline-flex items-center shrink-0"},/*#__PURE__*/React.createElement("i",{"data-lucide":"sliders",className:"w-3 h-3"})),/*#__PURE__*/React.createElement("span",{className:"text-[7px] opacity-60"},showFxRack?(panelPositions.fx_rack||'bottom')[0].toUpperCase():'')),/*#__PURE__*/React.createElement("button",{onClick:()=>setShowMidiEvents(p=>!p),className:`px-1.5 py-0.5 rounded text-xs font-semibold transition flex items-center gap-1 ${showMidiEvents?'bg-sky-900 text-sky-300':'text-zinc-500 hover:text-zinc-300'}`,title:`MIDI Events Panel (${panelPositions.midi_events||'bottom'})`},/*#__PURE__*/React.createElement("span",{className:"inline-flex items-center shrink-0"},/*#__PURE__*/React.createElement("i",{"data-lucide":"music",className:"w-3 h-3"})),/*#__PURE__*/React.createElement("span",{className:"text-[7px] opacity-60"},showMidiEvents?(panelPositions.midi_events||'bottom')[0].toUpperCase():'')),/*#__PURE__*/React.createElement("span",{className:"w-[1px] h-3 bg-zinc-800 mx-1"}),/*#__PURE__*/React.createElement("span",{className:"flex items-center gap-1"},/*#__PURE__*/React.createElement("span",{className:"inline-flex items-center shrink-0"},/*#__PURE__*/React.createElement("i",{"data-lucide":"info",className:"w-3 h-3 text-zinc-600"}))," Scroll: Zoom"),/*#__PURE__*/React.createElement("span",null,"|"),/*#__PURE__*/React.createElement("span",{className:"flex items-center gap-1"},/*#__PURE__*/React.createElement("span",{className:"inline-flex items-center shrink-0"},/*#__PURE__*/React.createElement("i",{"data-lucide":"keyboard",className:"w-3 h-3 text-zinc-600"}))," Ctrl+Scroll: Playhead"))),contextMenu&&(contextMenu.isSubTab?/*#__PURE__*/React.createElement("div",{className:"fixed z-[60] bg-[#2a2a2a] border border-zinc-700 rounded-lg shadow-2xl py-1 w-64",style:{left:contextMenu.x,top:contextMenu.y},onClick:e=>e.stopPropagation()},/*#__PURE__*/React.createElement("div",{className:"px-3 py-1 text-xs text-zinc-500 font-bold uppercase border-b border-zinc-800 pb-1 mb-1"},"Selection: ",formatTime(subTabs.find(s=>s.id===contextMenu.subTabId)?.selectionStart||0)," - ",formatTime(subTabs.find(s=>s.id===contextMenu.subTabId)?.selectionEnd||0)),/*#__PURE__*/React.createElement("div",{className:"h-px bg-zinc-700 my-1"}),/*#__PURE__*/React.createElement("button",{onClick:()=>{handleSubTabCut(contextMenu.subTabId);closeContextMenu();},className:"w-full px-3 py-1.5 text-xs text-zinc-200 hover:bg-zinc-700 text-left flex items-center gap-2"},/*#__PURE__*/React.createElement("span",{className:"inline-flex items-center shrink-0"},/*#__PURE__*/React.createElement("i",{"data-lucide":"scissors",className:"w-3.5 h-3.5 text-rose-400 shrink-0"})),/*#__PURE__*/React.createElement("span",{className:"flex-1"},"Cut"),/*#__PURE__*/React.createElement("span",{className:"text-purple-400 text-xs font-semibold font-mono ml-auto"},"Ctrl+X")),/*#__PURE__*/React.createElement("button",{onClick:()=>{handleSubTabCopy(contextMenu.subTabId);closeContextMenu();},className:"w-full px-3 py-1.5 text-xs text-zinc-200 hover:bg-zinc-700 text-left flex items-center gap-2"},/*#__PURE__*/React.createElement("span",{className:"inline-flex items-center shrink-0"},/*#__PURE__*/React.createElement("i",{"data-lucide":"copy",className:"w-3.5 h-3.5 text-zinc-400 shrink-0"})),/*#__PURE__*/React.createElement("span",{className:"flex-1"},"Copy"),/*#__PURE__*/React.createElement("span",{className:"text-purple-400 text-xs font-semibold font-mono ml-auto"},"Ctrl+C")),/*#__PURE__*/React.createElement("button",{onClick:()=>{handleSubTabPaste(contextMenu.subTabId);closeContextMenu();},className:"w-full px-3 py-1.5 text-xs text-zinc-200 hover:bg-zinc-700 text-left flex items-center gap-2"},/*#__PURE__*/React.createElement("span",{className:"inline-flex items-center shrink-0"},/*#__PURE__*/React.createElement("i",{"data-lucide":"clipboard",className:"w-3.5 h-3.5 text-emerald-400 shrink-0"})),/*#__PURE__*/React.createElement("span",{className:"flex-1"},"Paste"),/*#__PURE__*/React.createElement("span",{className:"text-purple-400 text-xs font-semibold font-mono ml-auto"},"Ctrl+V")),/*#__PURE__*/React.createElement("button",{onClick:()=>{handleSubTabDelete(contextMenu.subTabId);closeContextMenu();},className:"w-full px-3 py-1.5 text-xs text-zinc-200 hover:bg-zinc-700 text-left flex items-center gap-2"},/*#__PURE__*/React.createElement("span",{className:"inline-flex items-center shrink-0"},/*#__PURE__*/React.createElement("i",{"data-lucide":"trash-2",className:"w-3.5 h-3.5 text-red-400 shrink-0"})),/*#__PURE__*/React.createElement("span",{className:"flex-1"},"Delete Selected Segment"),/*#__PURE__*/React.createElement("span",{className:"text-amber-400 text-xs font-semibold font-mono ml-auto"},"Del")),/*#__PURE__*/React.createElement("div",{className:"h-px bg-zinc-700 my-1"}),/*#__PURE__*/React.createElement("button",{onClick:()=>{handleSubTabLoop(contextMenu.subTabId,4);closeContextMenu();},className:"w-full px-3 py-1.5 text-xs text-zinc-200 hover:bg-zinc-700 text-left flex items-center gap-2"},/*#__PURE__*/React.createElement("span",{className:"inline-flex items-center shrink-0"},/*#__PURE__*/React.createElement("i",{"data-lucide":"repeat",className:"w-3.5 h-3.5 text-cyan-400 shrink-0"})),/*#__PURE__*/React.createElement("span",{className:"flex-1"},"Loop Selection 4 times"),/*#__PURE__*/React.createElement("span",{className:"text-purple-400 text-xs font-semibold font-mono ml-auto"},"Ctrl+L"))):/*#__PURE__*/React.createElement("div",{className:"fixed z-[60] bg-[#2a2a2a] border border-zinc-700 rounded-lg shadow-2xl py-1 w-64",style:{left:contextMenu.x,top:contextMenu.y},onClick:e=>e.stopPropagation()},contextMenu.sectionId?/*#__PURE__*/React.createElement("button",{onClick:contextMenuEditSection,className:"w-full px-3 py-1.5 text-xs text-zinc-200 hover:bg-zinc-700 text-left flex items-center gap-2"},/*#__PURE__*/React.createElement("span",{className:"inline-flex items-center shrink-0"},/*#__PURE__*/React.createElement("i",{"data-lucide":"file-edit",className:"w-3.5 h-3.5 text-amber-400 shrink-0"})),/*#__PURE__*/React.createElement("span",{className:"flex-1"},"Edit Section"),/*#__PURE__*/React.createElement("span",{className:"text-purple-400 text-xs font-semibold font-mono ml-auto pl-8"},"Ctrl+E")):/*#__PURE__*/React.createElement("button",{onClick:contextMenuEdit,className:"w-full px-3 py-1.5 text-xs text-zinc-200 hover:bg-zinc-700 text-left flex items-center gap-2"},/*#__PURE__*/React.createElement("span",{className:"inline-flex items-center shrink-0"},/*#__PURE__*/React.createElement("i",{"data-lucide":"file-edit",className:"w-3.5 h-3.5 text-amber-400 shrink-0"})),/*#__PURE__*/React.createElement("span",{className:"flex-1"},"Edit"),/*#__PURE__*/React.createElement("span",{className:"text-purple-400 text-xs font-semibold font-mono ml-auto pl-8"},"Ctrl+E")),/*#__PURE__*/React.createElement("button",{onClick:contextMenuSplit,className:"w-full px-3 py-1.5 text-xs text-zinc-200 hover:bg-zinc-700 text-left flex items-center gap-2"},/*#__PURE__*/React.createElement("span",{className:"inline-flex items-center shrink-0"},/*#__PURE__*/React.createElement("i",{"data-lucide":"scissors",className:"w-3.5 h-3.5 text-cyan-400 shrink-0"})),/*#__PURE__*/React.createElement("span",{className:"flex-1"},"Split"),/*#__PURE__*/React.createElement("span",{className:"text-purple-400 text-xs font-semibold font-mono ml-auto pl-8"},"S")),/*#__PURE__*/React.createElement("button",{onClick:contextMenuMerge,className:"w-full px-3 py-1.5 text-xs text-zinc-200 hover:bg-zinc-700 text-left flex items-center gap-2"},/*#__PURE__*/React.createElement("span",{className:"inline-flex items-center shrink-0"},/*#__PURE__*/React.createElement("i",{"data-lucide":"combine",className:"w-3.5 h-3.5 text-purple-400 shrink-0"})),/*#__PURE__*/React.createElement("span",{className:"flex-1"},"Merge"),/*#__PURE__*/React.createElement("span",{className:"text-purple-400 text-xs font-semibold font-mono ml-auto pl-8"},"Ctrl+M")),/*#__PURE__*/React.createElement("div",{className:"h-px bg-zinc-700 my-1"}),/*#__PURE__*/React.createElement("button",{onClick:contextMenuCopy,className:"w-full px-3 py-1.5 text-xs text-zinc-200 hover:bg-zinc-700 text-left flex items-center gap-2"},/*#__PURE__*/React.createElement("span",{className:"inline-flex items-center shrink-0"},/*#__PURE__*/React.createElement("i",{"data-lucide":"copy",className:"w-3.5 h-3.5 text-zinc-400 shrink-0"})),/*#__PURE__*/React.createElement("span",{className:"flex-1"},"Copy"),/*#__PURE__*/React.createElement("span",{className:"text-purple-400 text-xs font-semibold font-mono ml-auto pl-8"},"Ctrl+C")),/*#__PURE__*/React.createElement("button",{onClick:contextMenuCut,className:"w-full px-3 py-1.5 text-xs text-zinc-200 hover:bg-zinc-700 text-left flex items-center gap-2"},/*#__PURE__*/React.createElement("span",{className:"inline-flex items-center shrink-0"},/*#__PURE__*/React.createElement("i",{"data-lucide":"scissors",className:"w-3.5 h-3.5 text-rose-400 shrink-0"})),/*#__PURE__*/React.createElement("span",{className:"flex-1"},"Cut"),/*#__PURE__*/React.createElement("span",{className:"text-purple-400 text-xs font-semibold font-mono ml-auto pl-8"},"Ctrl+X")),/*#__PURE__*/React.createElement("button",{onClick:contextMenuPaste,className:"w-full px-3 py-1.5 text-xs text-zinc-200 hover:bg-zinc-700 text-left flex items-center gap-2"},/*#__PURE__*/React.createElement("span",{className:"inline-flex items-center shrink-0"},/*#__PURE__*/React.createElement("i",{"data-lucide":"clipboard",className:"w-3.5 h-3.5 text-emerald-400 shrink-0"})),/*#__PURE__*/React.createElement("span",{className:"flex-1"},"Paste"),/*#__PURE__*/React.createElement("span",{className:"text-purple-400 text-xs font-semibold font-mono ml-auto pl-8"},"Ctrl+V")),/*#__PURE__*/React.createElement("div",{className:"h-px bg-zinc-700 my-1"}),/*#__PURE__*/React.createElement("button",{onClick:contextMenuDelete,className:"w-full px-3 py-1.5 text-xs text-red-300 hover:bg-red-950 text-left flex items-center gap-2"},/*#__PURE__*/React.createElement("span",{className:"inline-flex items-center shrink-0"},/*#__PURE__*/React.createElement("i",{"data-lucide":"trash-2",className:"w-3.5 h-3.5 text-red-400 shrink-0"})),/*#__PURE__*/React.createElement("span",{className:"flex-1"},"Delete"),/*#__PURE__*/React.createElement("span",{className:"text-amber-400 text-xs font-semibold font-mono ml-auto pl-8"},"Del")))),toastMessage&&/*#__PURE__*/React.createElement("div",{className:"absolute top-14 right-4 z-50 px-4 py-2.5 rounded shadow-lg text-xs font-semibold flex items-center gap-2 border bg-zinc-900 text-zinc-100 border-zinc-800"},/*#__PURE__*/React.createElement("span",{className:"inline-flex items-center shrink-0"},/*#__PURE__*/React.createElement("i",{"data-lucide":"bell",className:`w-4 h-4 ${toastMessage.type==='success'?'text-emerald-400':toastMessage.type==='error'?'text-rose-400':toastMessage.type==='warning'?'text-amber-400':'text-cyan-400'}`})),toastMessage.text,toastMessage.onActionClick&&/*#__PURE__*/React.createElement("button",{onClick:()=>{toastMessage.onActionClick();setToastMessage(null);},className:"ml-2 px-2 py-0.5 bg-purple-700 hover:bg-purple-600 text-white rounded text-[10px] uppercase font-bold"},toastMessage.actionText||'Tải về'))),appWarningModal&&/*#__PURE__*/React.createElement("div",{className:"fixed inset-0 z-[100] flex items-center justify-center bg-black/60 backdrop-blur-sm"},/*#__PURE__*/React.createElement("div",{className:"bg-[#222222] border border-zinc-800 rounded-lg shadow-2xl p-6 max-w-md w-full text-slate-200"},/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-2 mb-3 text-cyan-400 font-bold"},/*#__PURE__*/React.createElement("h3",{className:"text-base font-bold"},appWarningModal.title)),/*#__PURE__*/React.createElement("div",{className:"text-xs text-slate-300 mb-5 leading-relaxed whitespace-pre-line"},appWarningModal.message),/*#__PURE__*/React.createElement("div",{className:"flex justify-end gap-2"},appWarningModal.isAlert?/*#__PURE__*/React.createElement("button",{onClick:()=>setAppWarningModal(null),className:"px-4 py-2 bg-cyan-600 hover:bg-cyan-500 active:bg-cyan-700 text-white rounded text-xs font-semibold transition"},"Đóng"):/*#__PURE__*/React.createElement(React.Fragment,null,/*#__PURE__*/React.createElement("button",{onClick:()=>setAppWarningModal(null),className:"px-4 py-2 bg-zinc-700 hover:bg-zinc-600 active:bg-zinc-800 text-slate-300 rounded text-xs font-semibold transition"},"Hủy"),/*#__PURE__*/React.createElement("button",{onClick:()=>{const fn=appWarningModal.onConfirm;setAppWarningModal(null);if(fn)fn();},className:"px-4 py-2 bg-cyan-600 hover:bg-cyan-500 active:bg-cyan-700 text-white rounded text-xs font-semibold transition"},"Xác nhận"))))),tabContextMenu&&/*#__PURE__*/React.createElement(React.Fragment,null,/*#__PURE__*/React.createElement("div",{className:"fixed inset-0 z-[190]",onClick:()=>setTabContextMenu(null),onContextMenu:e=>{e.preventDefault();setTabContextMenu(null);}}),/*#__PURE__*/React.createElement("div",{className:"fixed bg-zinc-900 border border-zinc-800 rounded-lg shadow-2xl p-2 z-[200] flex gap-1.5",style:{top:tabContextMenu.y,left:tabContextMenu.x},onClick:e=>e.stopPropagation()},['#f43f5e','#f59e0b','#10b981','#06b6d4','#8b5cf6','#64748b'].map(color=>/*#__PURE__*/React.createElement("button",{key:color,className:"w-5 h-5 rounded-full border border-zinc-700 hover:scale-110 active:scale-95 transition",style:{backgroundColor:color},onClick:()=>{handleSetTabColor(tabContextMenu.tabId,tabContextMenu.tabType,color);setTabContextMenu(null);}})))),/*#__PURE__*/React.createElement(AuthModal,{isOpen:authModalOpen,mode:authMode,forceMandatory:isMandatoryLogin,onClose:()=>setAuthModalOpen(false),onSuccess:handleAuthSuccess}),/*#__PURE__*/React.createElement(ProfileModal,{isOpen:profileModalOpen,onClose:()=>setProfileModalOpen(false),tracks:tracks,setTracks:setTracks,setSelectedTrackId:setSelectedTrackId,projectName:projectName,setProjectName:setProjectName,currentProjectId:currentProjectId,setCurrentProjectId:setCurrentProjectId,showToast:showToast}),/*#__PURE__*/React.createElement(SaveProjectModal,{isOpen:saveProjectModalOpen,onClose:()=>setSaveProjectModalOpen(false),onSave:newName=>{handleSaveProjectWithName(newName);}}),/*#__PURE__*/React.createElement(SaveAsModal,{isOpen:saveAsModalOpen,onClose:()=>setSaveAsModalOpen(false),projectName:projectName,onSaveCloud:newName=>{handleSaveAsCloud(newName);},onSaveLocal:newName=>{handleExportSFS(newName);setProjectName(newName);localStorage.setItem('sonic_project_name',newName);}}),/*#__PURE__*/React.createElement(AIConfigModal,{isOpen:aiConfigModalOpen,onClose:()=>setAiConfigModalOpen(false),onConfigSaved:providers=>{setAiProviders(providers);const active=providers.find(p=>p.is_active)||providers[0];if(active)setSelectedProviderId(active.id);}}),/*#__PURE__*/React.createElement(SystemManagerModal,{isOpen:systemManagerModalOpen,onClose:()=>setSystemManagerModalOpen(false)}),/*#__PURE__*/React.createElement(PluginManagerModal,{isOpen:pluginManagerModalOpen,onClose:()=>setPluginManagerModalOpen(false),pluginsData:pluginsData}),/*#__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)"),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.map((p,i)=>React.createElement("button",{key:i,onClick:()=>setTrackInstrumentWithProgram(instrumentSelectorTrackId,selectedSoundFontId,p.program,p.name||'Preset '+p.program),className:"text-left px-2 py-1 text-[10px] rounded bg-zinc-800/60 hover:bg-amber-800 text-zinc-300 truncate"},p.name||'Preset '+p.program))):React.createElement("p",{className:"text-[10px] text-zinc-500 py-2"},"No presets found.")))):(/*#__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.")))))));};const root=ReactDOM.createRoot(document.getElementById('root'));root.render(/*#__PURE__*/React.createElement(App,null));setTimeout(()=>lucide.createIcons(),300); diff --git a/app/storage/sonicforge.db b/app/storage/sonicforge.db index d2251867ed7d4e841e925816782cd498046895f9..e19efcf40afa7bf3897db67adf95c89330bd289c 100644 GIT binary patch delta 1937 zcmeHI&u<$=6pmY|B?^V6ltUdPtN|oYooZ)x);nvYqEd3OlqOYwoT_LuyPn-_y+54U zT~j;WCfqqt4Tnew6bbbVD1QK4IdI^DM4^8HQV*OsGPADjI7)B5AaU5s_vXFt&G+Ve zySI0G@8I+oOY{44b93j^_vqaZEBileK2FXq4Ch|+7lwz6=erB@k2g=v+jl?u`QG7Q z-R~-AZ?9ax(^_8>-*CJery0*97H4+Q2F*AR!#W72V3#L8r_l+Km_}>@XDmuV^WF|n zML^128=3?Y(y%Zz4Fc+5bR67fZWbi51hRft=3fENo3UGWR&Gc*%_z@ohjP%=>Y$x- zs?IAw5+Jdf*5BsLW1Phw1 zjp&3?cFEhv#cu4$c(3h`unOHHX-bI zp6{wog3R3l%?6sxlhF(`aY<1fUMWkNx}iOA{4FGY%&V1#sP!+ZVwY4gVpycF8KHNy znE36}HAU6XbPP#1N_x|%Va!qy@qKYw9OM_u1hdgFtX_zmX~OZ|tZ{xSe*d%nL2+8qH(bgWHACU)~!@G{1>xB zeVn0)2sTZU5AZgXDlb*=OsqkS#vqHf?#sz6iS7Ii*~F#Yjj2~algwE7s8QXVkKRb2 z%qtD%CSfA2rhH$>8e>1DS;FOkofE7q!Li#WFAKl-B9{lL&67mV-lJhEuJSLBnGmF& zZFSCMAouY+hWKwp+nnJx|6mNwr240lF4`w`?ZxgWswuI8dBNM zSCsgjFTNr*#>zC}Z*GWY8kO(ye>;p;Z)w&4wjIPDg_}%&VlUy(Cb#{>EZ`l@lZI!Y VD2faIwI}<({E9aJ`F8Q({J$^fc|iaG delta 1094 zcmZ8gOKTHR7|jc6Ak~)Gg{dM8U8rrteLo@+`~$WdSEa-xPSPYzrb(u4(h_NLD`L4D zK?Gg6Hzt*qwm$xVx>9hXcI`rN(7p4*8Vj5T&K3iwd%K?}pM*lu%Tb{Uy4&}I>dJu#J6G!C z%c(O6P{IVJ>dygCn8>|RI3#qMvP+gO6cqufgIe;8(gyjrZnbE+c1>=ZnBn-};wA zdLRBzPYVhJ0NX6p{FG2FofzuNq5eQjaEAu8al`#U5eNr9bwCnhYUad-bOM@=AWwc) zCEw<+IzPE-q?VRD%N@tKnYT+;Ez`8EhH0d>JDu_9&99Fi{QeMI>=o}lg}sS)zs~I~ G#{L2yVOOyL diff --git a/app/templates/index.html b/app/templates/index.html index e121417..57661a6 100644 --- a/app/templates/index.html +++ b/app/templates/index.html @@ -17,6 +17,7 @@ +