diff --git a/app/static/js/app.jsx b/app/static/js/app.jsx index 1fcab37..53a98dc 100644 --- a/app/static/js/app.jsx +++ b/app/static/js/app.jsx @@ -12430,7 +12430,7 @@ const InteractiveEqPro = ({ track, params, onChange, getModule, applyTo, spectru ); }; -const ExportModal = ({ open, onClose, exportSettings, setExportSettings, isExporting, onExport, onBounce, onRenderMastering }) => { +const ExportModal = ({ open, onClose, exportSettings, setExportSettings, isExporting, onExport }) => { if (!open) return null; const pickDir = async () => { try { @@ -12618,20 +12618,22 @@ const ExportModal = ({ open, onClose, exportSettings, setExportSettings, isExpor placeholder="US-ABC-26-00001" className="w-full bg-[#141414] border border-zinc-800 rounded px-1 py-0.5 text-xs text-zinc-300 focus:outline-none" /> -
- +
+ +
+
-
@@ -18404,6 +18406,7 @@ const App = () => { normalize: true, normalizeTarget: '-14lufs', monoCheck: true, + realtime: false, // Gap 13: BWF/INFO metadata cho WAV mastering export metaTitle: '', metaArtist: '', @@ -27921,16 +27924,13 @@ const App = () => { // ── Server-side Export ── const triggerWavExport = async () => { - // TỰ ĐỘNG bounce realtime nếu project có MIDI items: FluidSynth (ScriptProcessor) - // không render được offline, nên bounce là cách duy nhất giữ trung thực - // MIDI + FX Rack + Mastering Chain trong file xuất. + // Realtime UNCHECKED → export OFFLINE thuần (server-side nếu connected, fallback client). + // MIDI không render được trong OfflineAudioContext: nếu project có MIDI items mà + // cache preview chưa đủ → file xuất có thể thiếu âm MIDI. Cảnh báo, vẫn xuất offline. const allT = (activeTracksRef.current && activeTracksRef.current.length) ? activeTracksRef.current : tracks; const hasMidiItems = allT.some(t => t.midiItems && t.midiItems.length > 0) || (sessionTabs || []).some(s => (s.tracks || []).some(t => t.midiItems && t.midiItems.length > 0)); if (hasMidiItems) { - // Nếu preview đã cache đủ độ dài cho MỌI track MIDI → export OFFLINE NHANH - // bằng cache (vẫn qua FX Rack + Mastering như playback). Ngược lại → - // bounce realtime để đảm bảo trung thực. const midiTracks = allT.filter(t => t.midiItems && t.midiItems.length > 0); let needDur = 1.0; midiTracks.forEach(t => { @@ -27939,10 +27939,6 @@ const App = () => { else if (m.startTime) needDur = Math.max(needDur, m.startTime + (m.duration || 8)); }); }); - // Cache có thể "đủ" (chunks + duration) nhưng RỖNG — khi bridge VSTi active, - // âm đi qua BridgeAudioNode, KHÔNG qua track.sfEntry nơi ensureMidiCapture - // tap → chunks toàn zero → export offline ra file câm tuyệt đối. Kiểm tra - // năng lượng cache: nếu không có tín hiệu → bounce realtime (bắt đúng âm bridge). const cacheHasEnergy = (c) => { if (!c) return false; const chunks = c.chunks || []; @@ -27958,14 +27954,9 @@ const App = () => { const c = midiCacheRef.current[t.id]; return c && c.chunks.length > 0 && c.duration >= needDur - 0.3 && cacheHasEnergy(c); }); - if (cacheOK) { - showToast("Dùng MIDI cache từ preview — export OFFLINE NHANH (vẫn đủ FX Rack + Mastering Chain).", "info"); - clientSideExport(allT); - return; + if (!cacheOK) { + showToast("Project có MIDI items chưa đủ cache preview — file offline có thể thiếu âm MIDI. Bật Realtime để bounce trung thực.", "warning"); } - showToast("Chưa có MIDI cache đầy đủ — bounce realtime để đảm bảo trung thực.", "info"); - triggerBounceExport(); - return; } let exportTracks; let clipStart = 0; @@ -31214,6 +31205,10 @@ STRICT CONSTRAINTS: label: 'Export Mix...', icon: 'file-output', action: () => setShowExportPanel(true) + }, { + label: 'Bounce MIDI...', + icon: 'radio', + action: () => triggerBounceExport() }, { label: 'Export MIDI...', icon: 'music', @@ -34568,9 +34563,7 @@ STRICT CONSTRAINTS: exportSettings: exportSettings, setExportSettings: setExportSettings, isExporting: isExporting, - onExport: triggerWavExport, - onBounce: triggerBounceExport, - onRenderMastering: renderMasteringExport + onExport: () => exportSettings.realtime ? triggerBounceExport() : triggerWavExport() }), instrumentSelectorTrackId && /*#__PURE__*/React.createElement("div", { className: "fixed inset-0 z-50 flex items-center justify-center bg-black/60 backdrop-blur-sm", onClick: closeInstrumentSelector diff --git a/app/static/js/app.precompiled.js b/app/static/js/app.precompiled.js index 38c3ed5..af41609 100644 --- a/app/static/js/app.precompiled.js +++ b/app/static/js/app.precompiled.js @@ -857,7 +857,7 @@ const specMods=(spectrumModules?spectrumModules():null)||(getModule?[getModule() const applyAll=applyTo||(fn=>{if(typeof window!=='undefined'&&window.__getTrackFxModule){const a=window.__getTrackFxModule(track.id,'eqpro');if(a)fn(a);}if(typeof window!=='undefined'&&window.__getTrackSfFxModule){const b=window.__getTrackSfFxModule(track.id,'eqpro');if(b)fn(b);}});const onDown=e=>{const cv=canvasRef.current,w=cv.clientWidth,h=cv.clientHeight;const{x,y}=toLocal(e);const s=st.current;let hit=false;for(let i=0;it+1);};const onMove=e=>{const s=st.current;if(s.dragId===null)return;const cv=canvasRef.current,w=cv.clientWidth,h=cv.clientHeight;const{x,y}=toLocal(e);const mx=Math.max(0,Math.min(w,x)),my=Math.max(0,Math.min(h,y));const b=s.bands[s.dragId];if(s.mode==='center'){const f=eqproClamp(parseFloat(eqproXToFreq(mx,w).toFixed(1)),EQPRO_F_MIN,EQPRO_F_MAX);// Filters without a Gain control (highpass/lowpass/notch/bandpass) stay on // the 0 dB axis — only frequency is draggable for them. const g=eqproBandHasGain(b.type)?eqproClamp(parseFloat(eqproYToGain(my,h).toFixed(1)),-EQPRO_MAX_DB,EQPRO_MAX_DB):0;b.freq=f;b.gain=g;applyAll(mm=>mm.setBand(s.dragId,{freq:f,gain:g}));}else if(s.mode==='wing'){const q=eqproWingToQ(Math.abs(mx-eqproFreqToX(b.freq,w)));b.q=q;applyAll(mm=>mm.setBand(s.dragId,{q}));}setTick(t=>t+1);};const onUp=e=>{const s=st.current;if(s.dragId!==null)commit();s.dragId=null;s.mode=null;try{canvasRef.current.releasePointerCapture(e.pointerId);}catch(err){}};const onWheel=e=>{e.preventDefault();const s=st.current;if(s.selected===null)return;const b=s.bands[s.selected];b.q=eqproClamp(parseFloat((b.q+(e.deltaY<0?0.2:-0.2)).toFixed(1)),0.1,18);applyAll(mm=>mm.setBand(s.selected,{q:b.q}));setTick(t=>t+1);};const onDblClick=e=>{const cv=canvasRef.current,w=cv.clientWidth,h=cv.clientHeight;const{x,y}=toLocal(e);const s=st.current;const hitIdx=s.bands.findIndex(b=>Math.hypot(x-eqproFreqToX(b.freq,w),y-eqproGainToY(b.gain,h))<=10);if(hitIdx!==-1){s.bands.splice(hitIdx,1);if(s.selected===hitIdx)s.selected=null;else if(s.selected!==null&&s.selected>hitIdx)s.selected--;}else{if(s.bands.length>=EQPRO_MAX_BANDS)return;s.bands.push({type:'peaking',freq:eqproClamp(parseFloat(eqproXToFreq(x,w).toFixed(1)),EQPRO_F_MIN,EQPRO_F_MAX),gain:eqproClamp(parseFloat(eqproYToGain(y,h).toFixed(1)),-EQPRO_MAX_DB,EQPRO_MAX_DB),q:1.2,active:true});s.selected=s.bands.length-1;}applyAll(mm=>mm.syncBands(s.bands));commit();setTick(t=>t+1);};const sel=st.current.selected!==null?st.current.bands[st.current.selected]:null;const selIdx=st.current.selected;const cvW=canvasRef.current?canvasRef.current.clientWidth:460;const cvH=240;const hudColor=selIdx!==null?EQPRO_BAND_COLORS[selIdx%EQPRO_BAND_COLORS.length]:EQPRO_BAND_COLORS[0];const hudXY=(()=>{if(st.current.hudPos){// Clamp so the HUD always stays inside the canvas area (buttons clickable). -return{hx:Math.max(0,Math.min((canvasRef.current?canvasRef.current.clientWidth:460)-268,st.current.hudPos.hx)),hy:Math.max(0,Math.min((canvasRef.current?canvasRef.current.clientHeight:240)-225,st.current.hudPos.hy))};}if(!sel)return null;const nx=eqproFreqToX(sel.freq,cvW),ny=eqproGainToY(sel.gain,cvH);let hx=nx-130,hy=ny-195;if(hx<8)hx=8;if(hx>cvW-268)hx=cvW-268;if(hy<8)hy=ny+24;return{hx,hy};})();return/*#__PURE__*/React.createElement("div",{className:"space-y-2"},/*#__PURE__*/React.createElement("div",{className:"flex items-center justify-between text-[11px] font-mono"},/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-3"},/*#__PURE__*/React.createElement("span",{className:"text-slate-400"},"Bands: ",/*#__PURE__*/React.createElement("span",{className:"text-teal-300 font-bold"},st.current.bands.length),"/8"),/*#__PURE__*/React.createElement("label",{className:"flex items-center gap-1.5"},/*#__PURE__*/React.createElement("span",{className:"text-slate-400"},"Amount:"),/*#__PURE__*/React.createElement("input",{type:"range",min:0,max:200,value:st.current.amount,onChange:e=>{st.current.amount=parseInt(e.target.value);applyAll(mm=>mm.setAmount(st.current.amount));commit();setTick(t=>t+1);},className:"w-20 h-1 cursor-pointer",style:{accentColor:'#2dd4bf'}}),/*#__PURE__*/React.createElement("span",{className:"text-teal-300 font-bold w-10"},st.current.amount,"%"))),/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-1"},/*#__PURE__*/React.createElement("button",{onClick:()=>{const s=st.current;s.bands=JSON.parse(JSON.stringify(EQPRO_DEFAULT_BANDS));s.selected=null;applyAll(mm=>mm.syncBands(s.bands));commit();setTick(t=>t+1);},className:"px-2 py-1 bg-slate-800 hover:bg-red-900/60 text-slate-300 border border-slate-700 rounded text-[10px]"},"Reset"),/*#__PURE__*/React.createElement("button",{onClick:()=>{const s=st.current;if(s.bands.length>=EQPRO_MAX_BANDS)return;s.bands.push({type:'peaking',freq:1000,gain:0,q:1.0,active:true});s.selected=s.bands.length-1;applyAll(mm=>mm.syncBands(s.bands));commit();setTick(t=>t+1);},className:"px-2 py-1 bg-teal-900/50 hover:bg-teal-800 text-teal-300 border border-teal-700 rounded text-[10px]"},"+ Band"))),/*#__PURE__*/React.createElement("div",{className:"relative"},/*#__PURE__*/React.createElement("canvas",{ref:canvasRef,className:"w-full h-60 block cursor-crosshair rounded-lg border border-slate-800 bg-slate-950",onPointerDown:onDown,onPointerMove:onMove,onPointerUp:onUp,onWheel:onWheel,onDoubleClick:onDblClick}),sel&&hudXY&&/*#__PURE__*/React.createElement("div",{className:"absolute z-10 w-64 rounded-xl p-3 text-[11px] font-mono space-y-2 pointer-events-auto cursor-move",style:{left:hudXY.hx,top:hudXY.hy,background:'rgba(15,23,42,0.94)',border:'1px solid rgba(45,212,191,0.3)',boxShadow:'0 10px 30px rgba(0,0,0,0.8)',backdropFilter:'blur(12px)'},onPointerDown:e=>{const s=st.current;s.dragHud=true;const r=e.currentTarget.getBoundingClientRect();s.hudOffsetX=e.clientX-r.left;s.hudOffsetY=e.clientY-r.top;e.currentTarget.setPointerCapture(e.pointerId);},onPointerMove:e=>{const s=st.current;if(!s.dragHud)return;const cv=canvasRef.current;const w=cv?cv.clientWidth:460;s.hudPos={hx:Math.max(0,Math.min(w-268,e.clientX-s.hudOffsetX-(cv?cv.getBoundingClientRect().left:0))),hy:Math.max(0,e.clientY-s.hudOffsetY-(cv?cv.getBoundingClientRect().top:0))};setTick(t=>t+1);},onPointerUp:e=>{st.current.dragHud=false;try{e.currentTarget.releasePointerCapture(e.pointerId);}catch(err){}}},/*#__PURE__*/React.createElement("div",{className:"flex items-center justify-between border-b border-slate-700/80 pb-1.5"},/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-2"},/*#__PURE__*/React.createElement("span",{className:"w-4 h-4 rounded-full text-slate-950 font-bold flex items-center justify-center text-[10px]",style:{background:hudColor.badge}},selIdx+1),/*#__PURE__*/React.createElement("span",{className:"font-bold text-white uppercase tracking-wider"},"Band ",selIdx+1)),/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-1"},/*#__PURE__*/React.createElement("button",{title:"Bypass band",onClick:()=>{const s=st.current;s.bands[s.selected].active=!s.bands[s.selected].active;applyAll(mm=>mm.setBand(s.selected,{active:s.bands[s.selected].active}));commit();setTick(t=>t+1);},className:`w-5 h-5 rounded flex items-center justify-center text-[10px] ${sel.active?'bg-slate-800 text-slate-400':'bg-amber-600 text-white'}`},/*#__PURE__*/React.createElement("i",{className:"fa-solid fa-power-off"})),/*#__PURE__*/React.createElement("button",{title:"Delete band",onClick:()=>{const s=st.current;s.bands.splice(s.selected,1);s.selected=null;applyAll(mm=>mm.syncBands(s.bands));commit();setTick(t=>t+1);},className:"w-5 h-5 rounded bg-slate-800 hover:bg-red-600 text-slate-400 hover:text-white flex items-center justify-center text-[10px]"},/*#__PURE__*/React.createElement("i",{className:"fa-solid fa-xmark"})))),/*#__PURE__*/React.createElement("div",null,/*#__PURE__*/React.createElement("label",{className:"block text-[9px] text-slate-400 uppercase tracking-widest mb-1"},"Filter Shape"),/*#__PURE__*/React.createElement("select",{value:sel.type,onChange:ev=>{const s=st.current;s.bands[s.selected].type=ev.target.value;applyAll(mm=>mm.setBand(s.selected,{type:ev.target.value}));commit();setTick(t=>t+1);},className:"w-full bg-slate-950 border border-slate-700 text-teal-300 rounded px-2 py-1 text-[11px] outline-none"},['peaking','highpass','lowpass','lowshelf','highshelf','notch','bandpass'].map(t=>/*#__PURE__*/React.createElement("option",{key:t,value:t},t==='peaking'?'Bell / Peaking':t==='highpass'?'Low Cut / High Pass':t==='lowpass'?'High Cut / Low Pass':t==='lowshelf'?'Low Shelf':t==='highshelf'?'High Shelf':t==='notch'?'Notch / Band Stop':'Band Pass')))),/*#__PURE__*/React.createElement("div",{className:"grid grid-cols-3 gap-2 text-center bg-slate-950/80 p-2 rounded-lg border border-slate-800"},/*#__PURE__*/React.createElement("div",null,/*#__PURE__*/React.createElement("div",{className:"text-[9px] text-slate-500"},"FREQ"),/*#__PURE__*/React.createElement("div",{className:"text-teal-300 font-bold text-[11px]"},sel.freq>=1000?(sel.freq/1000).toFixed(2)+' kHz':Math.round(sel.freq)+' Hz')),/*#__PURE__*/React.createElement("div",null,/*#__PURE__*/React.createElement("div",{className:"text-[9px] text-slate-500"},"GAIN"),/*#__PURE__*/React.createElement("div",{className:"text-amber-400 font-bold text-[11px]"},eqproBandHasGain(sel.type)?(sel.gain>0?'+':'')+sel.gain.toFixed(1)+' dB':'0.0 dB')),/*#__PURE__*/React.createElement("div",null,/*#__PURE__*/React.createElement("div",{className:"text-[9px] text-slate-500"},"Q"),/*#__PURE__*/React.createElement("div",{className:"text-purple-400 font-bold text-[11px]"},sel.q.toFixed(1)))),/*#__PURE__*/React.createElement("div",{className:"text-[9px] text-slate-500 text-center"},"Drag center: Freq & Gain · Wings/Wheel: Q · Dbl-click: add/delete"))));};const ExportModal=({open,onClose,exportSettings,setExportSettings,isExporting,onExport,onBounce,onRenderMastering})=>{if(!open)return null;const pickDir=async()=>{try{const r=await fetch(`${API_BASE_URL}/api/v1/plugins/pick-dir`,{method:'POST'});if(r.ok){const d=await r.json();if(d&&d.path)setExportSettings(p=>({...p,exportDir:d.path}));}}catch(e){}};return/*#__PURE__*/React.createElement("div",{className:"fixed inset-0 z-[300] bg-black/70 backdrop-blur-sm flex items-center justify-center p-6",onClick:onClose},/*#__PURE__*/React.createElement("div",{className:"w-full max-w-2xl bg-slate-900 border border-slate-700 rounded-2xl shadow-2xl overflow-hidden",onClick:e=>e.stopPropagation()},/*#__PURE__*/React.createElement("div",{className:"h-9 bg-slate-800/80 border-b border-slate-700 flex items-center justify-between px-3 select-none"},/*#__PURE__*/React.createElement("span",{className:"text-xs font-bold text-cyan-300 font-mono flex items-center gap-1.5"},/*#__PURE__*/React.createElement("i",{"data-lucide":"download-cloud",className:"w-3.5 h-3.5"})," EXPORT"),/*#__PURE__*/React.createElement("button",{onClick:onClose,className:"text-zinc-400 hover:text-white"},/*#__PURE__*/React.createElement("i",{"data-lucide":"x",className:"w-4 h-4"}))),/*#__PURE__*/React.createElement("div",{className:"p-4 max-h-[72vh] overflow-y-auto space-y-3"},/*#__PURE__*/React.createElement("div",{className:"bg-[#141414] border border-zinc-800 rounded px-2 py-1.5 space-y-1.5"},/*#__PURE__*/React.createElement("div",null,/*#__PURE__*/React.createElement("label",{className:"block text-[7px] text-zinc-500 font-bold uppercase mb-0.5"},"Tên file export"),/*#__PURE__*/React.createElement("input",{type:"text",value:exportSettings.fileName,onChange:e=>setExportSettings(p=>({...p,fileName:e.target.value})),placeholder:"Để trống = dùng tên mặc định",className:"w-full bg-[#0d0d0d] border border-zinc-800 rounded px-1 py-0.5 text-xs text-zinc-300 focus:outline-none"})),/*#__PURE__*/React.createElement("div",{className:"flex items-end gap-2"},/*#__PURE__*/React.createElement("div",{className:"flex-1 min-w-0"},/*#__PURE__*/React.createElement("label",{className:"block text-[7px] text-zinc-500 font-bold uppercase mb-0.5"},"Thư mục export"),/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-1.5 bg-[#0d0d0d] border border-zinc-800 rounded px-1.5 py-0.5 min-h-[22px]"},/*#__PURE__*/React.createElement("i",{className:"fa-solid fa-folder text-cyan-400 text-[10px] shrink-0"}),exportSettings.exportDir?/*#__PURE__*/React.createElement("a",{className:"text-cyan-400 underline truncate cursor-pointer",title:exportSettings.exportDir,onClick:pickDir},exportSettings.exportDir):/*#__PURE__*/React.createElement("span",{className:"text-zinc-600 truncate"},"Chưa chọn — xuất sẽ mở Save-As"))),/*#__PURE__*/React.createElement("button",{onClick:pickDir,className:"shrink-0 px-2 py-1 bg-cyan-700 hover:bg-cyan-600 text-white rounded text-[10px] font-bold flex items-center gap-1"},/*#__PURE__*/React.createElement("i",{className:"fa-solid fa-folder-open text-[10px]"})," Chọn thư mục")),/*#__PURE__*/React.createElement("label",{className:"flex items-center gap-1.5 cursor-pointer"},/*#__PURE__*/React.createElement("input",{type:"checkbox",checked:!!exportSettings.rememberExportDir,onChange:e=>setExportSettings(p=>({...p,rememberExportDir:e.target.checked})),className:"accent-cyan-500 w-3.5 h-3.5"}),/*#__PURE__*/React.createElement("span",{className:"text-xs text-zinc-300"},"Lưu thư mục export làm mặc định"))),/*#__PURE__*/React.createElement("div",{className:"grid grid-cols-2 gap-2"},/*#__PURE__*/React.createElement("div",null,/*#__PURE__*/React.createElement("label",{className:"block text-[7px] text-zinc-500 font-bold uppercase mb-0.5"},"Nguồn"),/*#__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"},"Định dạng"),/*#__PURE__*/React.createElement("select",{value:exportSettings.format,onChange:e=>setExportSettings(p=>({...p,format:e.target.value,sampleRate:'44100',bitDepth:'float',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-3 gap-2"},/*#__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:"44100"},"44100"),/*#__PURE__*/React.createElement("option",{value:"48000"},"48000"),/*#__PURE__*/React.createElement("option",{value:"96000"},"96000"))),/*#__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:"16"},"16"),/*#__PURE__*/React.createElement("option",{value:"24"},"24"),/*#__PURE__*/React.createElement("option",{value:"float"},"32 float"))),/*#__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",{className:"grid grid-cols-2 gap-2"},/*#__PURE__*/React.createElement("div",null,/*#__PURE__*/React.createElement("label",{className:"block text-[7px] text-zinc-500 font-bold uppercase mb-0.5"},"Chất lượng"),/*#__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("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",{className:"grid grid-cols-2 gap-2 bg-[#141414] border border-zinc-800 rounded px-2 py-1.5"},/*#__PURE__*/React.createElement("label",{className:"flex items-center gap-1.5 cursor-pointer"},/*#__PURE__*/React.createElement("input",{type:"checkbox",checked:!!exportSettings.dither,onChange:e=>setExportSettings(p=>({...p,dither:e.target.checked})),className:"accent-cyan-500 w-3.5 h-3.5"}),/*#__PURE__*/React.createElement("span",{className:"text-xs text-zinc-300"},"Dither TPDF (PCM 16/24)")),/*#__PURE__*/React.createElement("label",{className:"flex items-center gap-1.5 cursor-pointer"},/*#__PURE__*/React.createElement("input",{type:"checkbox",checked:!!exportSettings.normalize,onChange:e=>setExportSettings(p=>({...p,normalize:e.target.checked})),className:"accent-cyan-500 w-3.5 h-3.5"}),/*#__PURE__*/React.createElement("span",{className:"text-xs text-zinc-300"},"Normalize")),/*#__PURE__*/React.createElement("label",{className:"flex items-center gap-1.5 cursor-pointer"},/*#__PURE__*/React.createElement("input",{type:"checkbox",checked:!!exportSettings.monoCheck,onChange:e=>setExportSettings(p=>({...p,monoCheck:e.target.checked})),className:"accent-cyan-500 w-3.5 h-3.5"}),/*#__PURE__*/React.createElement("span",{className:"text-xs text-zinc-300"},"Cảnh báo mono (side/mid)")),/*#__PURE__*/React.createElement("div",null,/*#__PURE__*/React.createElement("label",{className:"block text-[7px] text-zinc-500 font-bold uppercase mb-0.5"},"Normalize target"),/*#__PURE__*/React.createElement("select",{value:exportSettings.normalizeTarget,onChange:e=>setExportSettings(p=>({...p,normalizeTarget: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:"-1dbtp"},"-1 dBTP"),/*#__PURE__*/React.createElement("option",{value:"-14lufs"},"-14 LUFS")))),/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-2 pt-1 bg-[#141414] border border-zinc-800 rounded px-2 py-1.5"},/*#__PURE__*/React.createElement("label",{className:"flex items-center gap-1.5 cursor-pointer"},/*#__PURE__*/React.createElement("input",{type:"checkbox",checked:!!exportSettings.loop,onChange:e=>setExportSettings(p=>({...p,loop:e.target.checked})),className:"accent-cyan-500 w-3.5 h-3.5"}),/*#__PURE__*/React.createElement("span",{className:"text-xs text-zinc-300"},"Xuất loop (không fade in / không fade out)")),exportSettings.loop&&/*#__PURE__*/React.createElement("label",{className:"flex items-center gap-1 ml-auto"},/*#__PURE__*/React.createElement("span",{className:"text-[7px] text-zinc-500 font-bold uppercase"},"Số ô nhịp"),/*#__PURE__*/React.createElement("input",{type:"number",min:"1",max:"256",value:exportSettings.loopBars,onChange:e=>setExportSettings(p=>({...p,loopBars:Math.max(1,parseInt(e.target.value)||1)})),className:"w-16 bg-[#141414] border border-zinc-800 rounded px-1 py-0.5 text-xs text-zinc-300 focus:outline-none"}))),/*#__PURE__*/React.createElement("div",{className:"grid grid-cols-3 gap-2 pt-1 bg-[#141414] border border-zinc-800 rounded px-2 py-1.5"},/*#__PURE__*/React.createElement("div",null,/*#__PURE__*/React.createElement("label",{className:"block text-[7px] text-zinc-500 font-bold uppercase mb-0.5"},"Title (BWF/INFO)"),/*#__PURE__*/React.createElement("input",{type:"text",value:exportSettings.metaTitle,maxLength:"254",onChange:e=>setExportSettings(p=>({...p,metaTitle:e.target.value})),placeholder:"Tên bài",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("div",null,/*#__PURE__*/React.createElement("label",{className:"block text-[7px] text-zinc-500 font-bold uppercase mb-0.5"},"Artist"),/*#__PURE__*/React.createElement("input",{type:"text",value:exportSettings.metaArtist,maxLength:"254",onChange:e=>setExportSettings(p=>({...p,metaArtist:e.target.value})),placeholder:"Nghệ sĩ",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("div",null,/*#__PURE__*/React.createElement("label",{className:"block text-[7px] text-zinc-500 font-bold uppercase mb-0.5"},"ISRC"),/*#__PURE__*/React.createElement("input",{type:"text",value:exportSettings.metaIsrc,maxLength:"12",onChange:e=>setExportSettings(p=>({...p,metaIsrc:e.target.value.toUpperCase()})),placeholder:"US-ABC-26-00001",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("div",{className:"grid grid-cols-3 gap-2 pt-1"},/*#__PURE__*/React.createElement("button",{onClick:onBounce,disabled:isExporting,title:"Bounce realtime — file WAV đầy đủ MIDI + FX Rack + Mastering Chain (chạy lại project thật)",className:"w-full py-1 bg-fuchsia-800 hover:bg-fuchsia-700 text-zinc-100 font-bold rounded text-xs flex items-center justify-center gap-1"},/*#__PURE__*/React.createElement("i",{"data-lucide":"radio",className:"w-3 h-3"})," ",isExporting?'...':'Bounce MIDI'),/*#__PURE__*/React.createElement("button",{onClick:onExport,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("i",{"data-lucide":"download-cloud",className:"w-3 h-3"})," ",isExporting?'...':'Export'),/*#__PURE__*/React.createElement("button",{onClick:onRenderMastering,disabled:isExporting,title:"Render qua engine Python + mastering chain (VST3 bridge) — bit-depth/SR/dither/normalize (-1dBTP|-14LUFS) + mono side/mid check, trả LUFS + dBTP",className:"w-full py-1 bg-amber-800 hover:bg-amber-700 text-zinc-100 font-bold rounded text-xs flex items-center justify-center gap-1"},/*#__PURE__*/React.createElement("i",{"data-lucide":"gauge",className:"w-3 h-3"})," ",isExporting?'...':'Mastering')))));};// --- Embedded VST GUI (PLAN_MASTERBUS_VST_GUI_EMBED.md) -------------------- +return{hx:Math.max(0,Math.min((canvasRef.current?canvasRef.current.clientWidth:460)-268,st.current.hudPos.hx)),hy:Math.max(0,Math.min((canvasRef.current?canvasRef.current.clientHeight:240)-225,st.current.hudPos.hy))};}if(!sel)return null;const nx=eqproFreqToX(sel.freq,cvW),ny=eqproGainToY(sel.gain,cvH);let hx=nx-130,hy=ny-195;if(hx<8)hx=8;if(hx>cvW-268)hx=cvW-268;if(hy<8)hy=ny+24;return{hx,hy};})();return/*#__PURE__*/React.createElement("div",{className:"space-y-2"},/*#__PURE__*/React.createElement("div",{className:"flex items-center justify-between text-[11px] font-mono"},/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-3"},/*#__PURE__*/React.createElement("span",{className:"text-slate-400"},"Bands: ",/*#__PURE__*/React.createElement("span",{className:"text-teal-300 font-bold"},st.current.bands.length),"/8"),/*#__PURE__*/React.createElement("label",{className:"flex items-center gap-1.5"},/*#__PURE__*/React.createElement("span",{className:"text-slate-400"},"Amount:"),/*#__PURE__*/React.createElement("input",{type:"range",min:0,max:200,value:st.current.amount,onChange:e=>{st.current.amount=parseInt(e.target.value);applyAll(mm=>mm.setAmount(st.current.amount));commit();setTick(t=>t+1);},className:"w-20 h-1 cursor-pointer",style:{accentColor:'#2dd4bf'}}),/*#__PURE__*/React.createElement("span",{className:"text-teal-300 font-bold w-10"},st.current.amount,"%"))),/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-1"},/*#__PURE__*/React.createElement("button",{onClick:()=>{const s=st.current;s.bands=JSON.parse(JSON.stringify(EQPRO_DEFAULT_BANDS));s.selected=null;applyAll(mm=>mm.syncBands(s.bands));commit();setTick(t=>t+1);},className:"px-2 py-1 bg-slate-800 hover:bg-red-900/60 text-slate-300 border border-slate-700 rounded text-[10px]"},"Reset"),/*#__PURE__*/React.createElement("button",{onClick:()=>{const s=st.current;if(s.bands.length>=EQPRO_MAX_BANDS)return;s.bands.push({type:'peaking',freq:1000,gain:0,q:1.0,active:true});s.selected=s.bands.length-1;applyAll(mm=>mm.syncBands(s.bands));commit();setTick(t=>t+1);},className:"px-2 py-1 bg-teal-900/50 hover:bg-teal-800 text-teal-300 border border-teal-700 rounded text-[10px]"},"+ Band"))),/*#__PURE__*/React.createElement("div",{className:"relative"},/*#__PURE__*/React.createElement("canvas",{ref:canvasRef,className:"w-full h-60 block cursor-crosshair rounded-lg border border-slate-800 bg-slate-950",onPointerDown:onDown,onPointerMove:onMove,onPointerUp:onUp,onWheel:onWheel,onDoubleClick:onDblClick}),sel&&hudXY&&/*#__PURE__*/React.createElement("div",{className:"absolute z-10 w-64 rounded-xl p-3 text-[11px] font-mono space-y-2 pointer-events-auto cursor-move",style:{left:hudXY.hx,top:hudXY.hy,background:'rgba(15,23,42,0.94)',border:'1px solid rgba(45,212,191,0.3)',boxShadow:'0 10px 30px rgba(0,0,0,0.8)',backdropFilter:'blur(12px)'},onPointerDown:e=>{const s=st.current;s.dragHud=true;const r=e.currentTarget.getBoundingClientRect();s.hudOffsetX=e.clientX-r.left;s.hudOffsetY=e.clientY-r.top;e.currentTarget.setPointerCapture(e.pointerId);},onPointerMove:e=>{const s=st.current;if(!s.dragHud)return;const cv=canvasRef.current;const w=cv?cv.clientWidth:460;s.hudPos={hx:Math.max(0,Math.min(w-268,e.clientX-s.hudOffsetX-(cv?cv.getBoundingClientRect().left:0))),hy:Math.max(0,e.clientY-s.hudOffsetY-(cv?cv.getBoundingClientRect().top:0))};setTick(t=>t+1);},onPointerUp:e=>{st.current.dragHud=false;try{e.currentTarget.releasePointerCapture(e.pointerId);}catch(err){}}},/*#__PURE__*/React.createElement("div",{className:"flex items-center justify-between border-b border-slate-700/80 pb-1.5"},/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-2"},/*#__PURE__*/React.createElement("span",{className:"w-4 h-4 rounded-full text-slate-950 font-bold flex items-center justify-center text-[10px]",style:{background:hudColor.badge}},selIdx+1),/*#__PURE__*/React.createElement("span",{className:"font-bold text-white uppercase tracking-wider"},"Band ",selIdx+1)),/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-1"},/*#__PURE__*/React.createElement("button",{title:"Bypass band",onClick:()=>{const s=st.current;s.bands[s.selected].active=!s.bands[s.selected].active;applyAll(mm=>mm.setBand(s.selected,{active:s.bands[s.selected].active}));commit();setTick(t=>t+1);},className:`w-5 h-5 rounded flex items-center justify-center text-[10px] ${sel.active?'bg-slate-800 text-slate-400':'bg-amber-600 text-white'}`},/*#__PURE__*/React.createElement("i",{className:"fa-solid fa-power-off"})),/*#__PURE__*/React.createElement("button",{title:"Delete band",onClick:()=>{const s=st.current;s.bands.splice(s.selected,1);s.selected=null;applyAll(mm=>mm.syncBands(s.bands));commit();setTick(t=>t+1);},className:"w-5 h-5 rounded bg-slate-800 hover:bg-red-600 text-slate-400 hover:text-white flex items-center justify-center text-[10px]"},/*#__PURE__*/React.createElement("i",{className:"fa-solid fa-xmark"})))),/*#__PURE__*/React.createElement("div",null,/*#__PURE__*/React.createElement("label",{className:"block text-[9px] text-slate-400 uppercase tracking-widest mb-1"},"Filter Shape"),/*#__PURE__*/React.createElement("select",{value:sel.type,onChange:ev=>{const s=st.current;s.bands[s.selected].type=ev.target.value;applyAll(mm=>mm.setBand(s.selected,{type:ev.target.value}));commit();setTick(t=>t+1);},className:"w-full bg-slate-950 border border-slate-700 text-teal-300 rounded px-2 py-1 text-[11px] outline-none"},['peaking','highpass','lowpass','lowshelf','highshelf','notch','bandpass'].map(t=>/*#__PURE__*/React.createElement("option",{key:t,value:t},t==='peaking'?'Bell / Peaking':t==='highpass'?'Low Cut / High Pass':t==='lowpass'?'High Cut / Low Pass':t==='lowshelf'?'Low Shelf':t==='highshelf'?'High Shelf':t==='notch'?'Notch / Band Stop':'Band Pass')))),/*#__PURE__*/React.createElement("div",{className:"grid grid-cols-3 gap-2 text-center bg-slate-950/80 p-2 rounded-lg border border-slate-800"},/*#__PURE__*/React.createElement("div",null,/*#__PURE__*/React.createElement("div",{className:"text-[9px] text-slate-500"},"FREQ"),/*#__PURE__*/React.createElement("div",{className:"text-teal-300 font-bold text-[11px]"},sel.freq>=1000?(sel.freq/1000).toFixed(2)+' kHz':Math.round(sel.freq)+' Hz')),/*#__PURE__*/React.createElement("div",null,/*#__PURE__*/React.createElement("div",{className:"text-[9px] text-slate-500"},"GAIN"),/*#__PURE__*/React.createElement("div",{className:"text-amber-400 font-bold text-[11px]"},eqproBandHasGain(sel.type)?(sel.gain>0?'+':'')+sel.gain.toFixed(1)+' dB':'0.0 dB')),/*#__PURE__*/React.createElement("div",null,/*#__PURE__*/React.createElement("div",{className:"text-[9px] text-slate-500"},"Q"),/*#__PURE__*/React.createElement("div",{className:"text-purple-400 font-bold text-[11px]"},sel.q.toFixed(1)))),/*#__PURE__*/React.createElement("div",{className:"text-[9px] text-slate-500 text-center"},"Drag center: Freq & Gain · Wings/Wheel: Q · Dbl-click: add/delete"))));};const ExportModal=({open,onClose,exportSettings,setExportSettings,isExporting,onExport})=>{if(!open)return null;const pickDir=async()=>{try{const r=await fetch(`${API_BASE_URL}/api/v1/plugins/pick-dir`,{method:'POST'});if(r.ok){const d=await r.json();if(d&&d.path)setExportSettings(p=>({...p,exportDir:d.path}));}}catch(e){}};return/*#__PURE__*/React.createElement("div",{className:"fixed inset-0 z-[300] bg-black/70 backdrop-blur-sm flex items-center justify-center p-6",onClick:onClose},/*#__PURE__*/React.createElement("div",{className:"w-full max-w-2xl bg-slate-900 border border-slate-700 rounded-2xl shadow-2xl overflow-hidden",onClick:e=>e.stopPropagation()},/*#__PURE__*/React.createElement("div",{className:"h-9 bg-slate-800/80 border-b border-slate-700 flex items-center justify-between px-3 select-none"},/*#__PURE__*/React.createElement("span",{className:"text-xs font-bold text-cyan-300 font-mono flex items-center gap-1.5"},/*#__PURE__*/React.createElement("i",{"data-lucide":"download-cloud",className:"w-3.5 h-3.5"})," EXPORT"),/*#__PURE__*/React.createElement("button",{onClick:onClose,className:"text-zinc-400 hover:text-white"},/*#__PURE__*/React.createElement("i",{"data-lucide":"x",className:"w-4 h-4"}))),/*#__PURE__*/React.createElement("div",{className:"p-4 max-h-[72vh] overflow-y-auto space-y-3"},/*#__PURE__*/React.createElement("div",{className:"bg-[#141414] border border-zinc-800 rounded px-2 py-1.5 space-y-1.5"},/*#__PURE__*/React.createElement("div",null,/*#__PURE__*/React.createElement("label",{className:"block text-[7px] text-zinc-500 font-bold uppercase mb-0.5"},"Tên file export"),/*#__PURE__*/React.createElement("input",{type:"text",value:exportSettings.fileName,onChange:e=>setExportSettings(p=>({...p,fileName:e.target.value})),placeholder:"Để trống = dùng tên mặc định",className:"w-full bg-[#0d0d0d] border border-zinc-800 rounded px-1 py-0.5 text-xs text-zinc-300 focus:outline-none"})),/*#__PURE__*/React.createElement("div",{className:"flex items-end gap-2"},/*#__PURE__*/React.createElement("div",{className:"flex-1 min-w-0"},/*#__PURE__*/React.createElement("label",{className:"block text-[7px] text-zinc-500 font-bold uppercase mb-0.5"},"Thư mục export"),/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-1.5 bg-[#0d0d0d] border border-zinc-800 rounded px-1.5 py-0.5 min-h-[22px]"},/*#__PURE__*/React.createElement("i",{className:"fa-solid fa-folder text-cyan-400 text-[10px] shrink-0"}),exportSettings.exportDir?/*#__PURE__*/React.createElement("a",{className:"text-cyan-400 underline truncate cursor-pointer",title:exportSettings.exportDir,onClick:pickDir},exportSettings.exportDir):/*#__PURE__*/React.createElement("span",{className:"text-zinc-600 truncate"},"Chưa chọn — xuất sẽ mở Save-As"))),/*#__PURE__*/React.createElement("button",{onClick:pickDir,className:"shrink-0 px-2 py-1 bg-cyan-700 hover:bg-cyan-600 text-white rounded text-[10px] font-bold flex items-center gap-1"},/*#__PURE__*/React.createElement("i",{className:"fa-solid fa-folder-open text-[10px]"})," Chọn thư mục")),/*#__PURE__*/React.createElement("label",{className:"flex items-center gap-1.5 cursor-pointer"},/*#__PURE__*/React.createElement("input",{type:"checkbox",checked:!!exportSettings.rememberExportDir,onChange:e=>setExportSettings(p=>({...p,rememberExportDir:e.target.checked})),className:"accent-cyan-500 w-3.5 h-3.5"}),/*#__PURE__*/React.createElement("span",{className:"text-xs text-zinc-300"},"Lưu thư mục export làm mặc định"))),/*#__PURE__*/React.createElement("div",{className:"grid grid-cols-2 gap-2"},/*#__PURE__*/React.createElement("div",null,/*#__PURE__*/React.createElement("label",{className:"block text-[7px] text-zinc-500 font-bold uppercase mb-0.5"},"Nguồn"),/*#__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"},"Định dạng"),/*#__PURE__*/React.createElement("select",{value:exportSettings.format,onChange:e=>setExportSettings(p=>({...p,format:e.target.value,sampleRate:'44100',bitDepth:'float',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-3 gap-2"},/*#__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:"44100"},"44100"),/*#__PURE__*/React.createElement("option",{value:"48000"},"48000"),/*#__PURE__*/React.createElement("option",{value:"96000"},"96000"))),/*#__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:"16"},"16"),/*#__PURE__*/React.createElement("option",{value:"24"},"24"),/*#__PURE__*/React.createElement("option",{value:"float"},"32 float"))),/*#__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",{className:"grid grid-cols-2 gap-2"},/*#__PURE__*/React.createElement("div",null,/*#__PURE__*/React.createElement("label",{className:"block text-[7px] text-zinc-500 font-bold uppercase mb-0.5"},"Chất lượng"),/*#__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("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",{className:"grid grid-cols-2 gap-2 bg-[#141414] border border-zinc-800 rounded px-2 py-1.5"},/*#__PURE__*/React.createElement("label",{className:"flex items-center gap-1.5 cursor-pointer"},/*#__PURE__*/React.createElement("input",{type:"checkbox",checked:!!exportSettings.dither,onChange:e=>setExportSettings(p=>({...p,dither:e.target.checked})),className:"accent-cyan-500 w-3.5 h-3.5"}),/*#__PURE__*/React.createElement("span",{className:"text-xs text-zinc-300"},"Dither TPDF (PCM 16/24)")),/*#__PURE__*/React.createElement("label",{className:"flex items-center gap-1.5 cursor-pointer"},/*#__PURE__*/React.createElement("input",{type:"checkbox",checked:!!exportSettings.normalize,onChange:e=>setExportSettings(p=>({...p,normalize:e.target.checked})),className:"accent-cyan-500 w-3.5 h-3.5"}),/*#__PURE__*/React.createElement("span",{className:"text-xs text-zinc-300"},"Normalize")),/*#__PURE__*/React.createElement("label",{className:"flex items-center gap-1.5 cursor-pointer"},/*#__PURE__*/React.createElement("input",{type:"checkbox",checked:!!exportSettings.monoCheck,onChange:e=>setExportSettings(p=>({...p,monoCheck:e.target.checked})),className:"accent-cyan-500 w-3.5 h-3.5"}),/*#__PURE__*/React.createElement("span",{className:"text-xs text-zinc-300"},"Cảnh báo mono (side/mid)")),/*#__PURE__*/React.createElement("div",null,/*#__PURE__*/React.createElement("label",{className:"block text-[7px] text-zinc-500 font-bold uppercase mb-0.5"},"Normalize target"),/*#__PURE__*/React.createElement("select",{value:exportSettings.normalizeTarget,onChange:e=>setExportSettings(p=>({...p,normalizeTarget: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:"-1dbtp"},"-1 dBTP"),/*#__PURE__*/React.createElement("option",{value:"-14lufs"},"-14 LUFS")))),/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-2 pt-1 bg-[#141414] border border-zinc-800 rounded px-2 py-1.5"},/*#__PURE__*/React.createElement("label",{className:"flex items-center gap-1.5 cursor-pointer"},/*#__PURE__*/React.createElement("input",{type:"checkbox",checked:!!exportSettings.loop,onChange:e=>setExportSettings(p=>({...p,loop:e.target.checked})),className:"accent-cyan-500 w-3.5 h-3.5"}),/*#__PURE__*/React.createElement("span",{className:"text-xs text-zinc-300"},"Xuất loop (không fade in / không fade out)")),exportSettings.loop&&/*#__PURE__*/React.createElement("label",{className:"flex items-center gap-1 ml-auto"},/*#__PURE__*/React.createElement("span",{className:"text-[7px] text-zinc-500 font-bold uppercase"},"Số ô nhịp"),/*#__PURE__*/React.createElement("input",{type:"number",min:"1",max:"256",value:exportSettings.loopBars,onChange:e=>setExportSettings(p=>({...p,loopBars:Math.max(1,parseInt(e.target.value)||1)})),className:"w-16 bg-[#141414] border border-zinc-800 rounded px-1 py-0.5 text-xs text-zinc-300 focus:outline-none"}))),/*#__PURE__*/React.createElement("div",{className:"grid grid-cols-3 gap-2 pt-1 bg-[#141414] border border-zinc-800 rounded px-2 py-1.5"},/*#__PURE__*/React.createElement("div",null,/*#__PURE__*/React.createElement("label",{className:"block text-[7px] text-zinc-500 font-bold uppercase mb-0.5"},"Title (BWF/INFO)"),/*#__PURE__*/React.createElement("input",{type:"text",value:exportSettings.metaTitle,maxLength:"254",onChange:e=>setExportSettings(p=>({...p,metaTitle:e.target.value})),placeholder:"Tên bài",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("div",null,/*#__PURE__*/React.createElement("label",{className:"block text-[7px] text-zinc-500 font-bold uppercase mb-0.5"},"Artist"),/*#__PURE__*/React.createElement("input",{type:"text",value:exportSettings.metaArtist,maxLength:"254",onChange:e=>setExportSettings(p=>({...p,metaArtist:e.target.value})),placeholder:"Nghệ sĩ",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("div",null,/*#__PURE__*/React.createElement("label",{className:"block text-[7px] text-zinc-500 font-bold uppercase mb-0.5"},"ISRC"),/*#__PURE__*/React.createElement("input",{type:"text",value:exportSettings.metaIsrc,maxLength:"12",onChange:e=>setExportSettings(p=>({...p,metaIsrc:e.target.value.toUpperCase()})),placeholder:"US-ABC-26-00001",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("div",{className:"flex items-center gap-2 bg-[#141414] border border-zinc-800 rounded px-2 py-1.5"},/*#__PURE__*/React.createElement("label",{className:"flex items-center gap-1.5 cursor-pointer"},/*#__PURE__*/React.createElement("input",{type:"checkbox",checked:!!exportSettings.realtime,onChange:e=>setExportSettings(p=>({...p,realtime:e.target.checked})),className:"accent-cyan-500 w-3.5 h-3.5"}),/*#__PURE__*/React.createElement("span",{className:"text-xs text-zinc-300"},"Realtime (bounce — chạy lại project thật, giữ nguyên MIDI + FX Rack + Mastering Chain)"))),/*#__PURE__*/React.createElement("div",{className:"grid grid-cols-2 gap-2 pt-1"},/*#__PURE__*/React.createElement("button",{onClick:onExport,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("i",{"data-lucide":"download-cloud",className:"w-3 h-3"})," ",isExporting?'...':'Export'),/*#__PURE__*/React.createElement("button",{onClick:onClose,disabled:isExporting,className:"w-full py-1 bg-zinc-700 hover:bg-zinc-600 text-zinc-100 font-bold rounded text-xs"},"Hủy")))));};// --- Embedded VST GUI (PLAN_MASTERBUS_VST_GUI_EMBED.md) -------------------- // Frame-capture + input relay: hiển thị native GUI qua HTTP /frame (~10fps), // gửi pointer/key về bridge qua /input. Dùng chung MasteringModal + FXRackModal. // url = embed_url từ API fx-gui open (embed:true); onClose khi user đóng; @@ -1214,7 +1214,7 @@ const[masterMuted,setMasterMuted]=useState(false);// MUTE master const[masterAutoVol,setMasterAutoVol]=useState([]);// Gap 7: master volume automation [{time s, db}] const[masterAutoOn,setMasterAutoOn]=useState(false);// Gap 7: automation active React.useEffect(()=>{window.__masterAutoVol=masterAutoVol;window.__masterAutoOn=masterAutoOn;},[masterAutoVol,masterAutoOn]);React.useEffect(()=>{if(isPlaying&&typeof applyMasterAutomationSchedule==='function'){try{applyMasterAutomationSchedule(currentTimeRef.current||0);}catch(e){console.warn('[Auto] reschedule error:',e);}}},[masterAutoVol,masterAutoOn,isPlaying]);const[masterVU,setMasterVU]=useState(0);// 0-1 -const[masterMeterPeak,setMasterMeterPeak]=useState(0);const[rightSidebarWidth,setRightSidebarWidth]=useState(320);const[tcpWidth,setTcpWidth]=useState(320);const[mediaExplorerHeight,setMediaExplorerHeight]=useState(50);const[panelPositions,setPanelPositions]=useState({export:'bottom',ai:'right',python_tools:'bottom',selection:'bottom',media_explorer:'bottom',fx_rack:'bottom',midi_events:'bottom'});const[panelDropZone,setPanelDropZone]=useState(null);const[dragGhostPos,setDragGhostPos]=useState(null);const[dragGhostPanel,setDragGhostPanel]=useState(null);const panelDragRef=useRef(null);const trackVuRefs=useRef({});const workspaceRef=useRef(null);const colResizerRef=useRef(null);const rowResizerRef=useRef(null);const[aiConfig,setAiConfig]=useState({baseUrl:localStorage.getItem('ai_base_url')||`${API_BASE_URL}`,apiKey:localStorage.getItem('ai_api_key')||'',model:localStorage.getItem('ai_model')||'deepseek-chat'});const[aiProviders,setAiProviders]=useState([]);useEffect(()=>{(async()=>{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){/* server may not have config endpoint */}})();},[]);const[analysisState,setAnalysisState]=useState({status:'Sẵn sàng. Chạy AI để phân tích nhịp.',data:null,isRunning:false});const[aiPrompt,setAiPrompt]=useState('');const[aiComposeMode,setAiComposeMode]=useState('SIMILAR_VARIATION');const[promptHistory,setPromptHistory]=useState([]);const[promptHistIdx,setPromptHistIdx]=useState(-1);const promptHistRef=useRef([]);const aiPromptUndoRef=useRef({stack:[],idx:-1,max:30});const aiPromptUndoPush=text=>{const u=aiPromptUndoRef.current;u.stack.push(text);if(u.stack.length>u.max)u.stack.shift();u.idx=u.stack.length-1;};const[aiProvider,setAiProvider]=useState('OpenAI');const[aiModel,setAiModel]=useState('GPT-4o');const[aiActionLog,setAiActionLog]=useState([]);const actionLogContainerRef=useRef(null);useEffect(()=>{if(actionLogContainerRef.current){actionLogContainerRef.current.scrollTop=actionLogContainerRef.current.scrollHeight;}},[aiActionLog]);const[aiProcessing,setAiProcessing]=useState(false);const[aiSuggestions,setAiSuggestions]=useState([]);const[showAiTypeahead,setShowAiTypeahead]=useState(false);const[showAISuggestions,setShowAISuggestions]=useState(true);const[showAIActionLog,setShowAIActionLog]=useState(false);const aiPromptMgrRef=useRef(null);const aiTypeaheadRef=useRef(null);const[selectedProviderId,setSelectedProviderId]=useState('');const[exportSettings,setExportSettings]=useState(()=>{let expDir='',remExpDir=false;try{expDir=localStorage.getItem('sonic_export_dir')||'';}catch(e){}try{remExpDir=localStorage.getItem('sonic_remember_export_dir')==='1';}catch(e){}return{sampleRate:'44100',bitDepth:'float',format:'wav',source:'project',quality:'44khz',channels:'stereo',loop:false,loopBars:4,dither:false,normalize:true,normalizeTarget:'-14lufs',monoCheck:true,// Gap 13: BWF/INFO metadata cho WAV mastering export +const[masterMeterPeak,setMasterMeterPeak]=useState(0);const[rightSidebarWidth,setRightSidebarWidth]=useState(320);const[tcpWidth,setTcpWidth]=useState(320);const[mediaExplorerHeight,setMediaExplorerHeight]=useState(50);const[panelPositions,setPanelPositions]=useState({export:'bottom',ai:'right',python_tools:'bottom',selection:'bottom',media_explorer:'bottom',fx_rack:'bottom',midi_events:'bottom'});const[panelDropZone,setPanelDropZone]=useState(null);const[dragGhostPos,setDragGhostPos]=useState(null);const[dragGhostPanel,setDragGhostPanel]=useState(null);const panelDragRef=useRef(null);const trackVuRefs=useRef({});const workspaceRef=useRef(null);const colResizerRef=useRef(null);const rowResizerRef=useRef(null);const[aiConfig,setAiConfig]=useState({baseUrl:localStorage.getItem('ai_base_url')||`${API_BASE_URL}`,apiKey:localStorage.getItem('ai_api_key')||'',model:localStorage.getItem('ai_model')||'deepseek-chat'});const[aiProviders,setAiProviders]=useState([]);useEffect(()=>{(async()=>{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){/* server may not have config endpoint */}})();},[]);const[analysisState,setAnalysisState]=useState({status:'Sẵn sàng. Chạy AI để phân tích nhịp.',data:null,isRunning:false});const[aiPrompt,setAiPrompt]=useState('');const[aiComposeMode,setAiComposeMode]=useState('SIMILAR_VARIATION');const[promptHistory,setPromptHistory]=useState([]);const[promptHistIdx,setPromptHistIdx]=useState(-1);const promptHistRef=useRef([]);const aiPromptUndoRef=useRef({stack:[],idx:-1,max:30});const aiPromptUndoPush=text=>{const u=aiPromptUndoRef.current;u.stack.push(text);if(u.stack.length>u.max)u.stack.shift();u.idx=u.stack.length-1;};const[aiProvider,setAiProvider]=useState('OpenAI');const[aiModel,setAiModel]=useState('GPT-4o');const[aiActionLog,setAiActionLog]=useState([]);const actionLogContainerRef=useRef(null);useEffect(()=>{if(actionLogContainerRef.current){actionLogContainerRef.current.scrollTop=actionLogContainerRef.current.scrollHeight;}},[aiActionLog]);const[aiProcessing,setAiProcessing]=useState(false);const[aiSuggestions,setAiSuggestions]=useState([]);const[showAiTypeahead,setShowAiTypeahead]=useState(false);const[showAISuggestions,setShowAISuggestions]=useState(true);const[showAIActionLog,setShowAIActionLog]=useState(false);const aiPromptMgrRef=useRef(null);const aiTypeaheadRef=useRef(null);const[selectedProviderId,setSelectedProviderId]=useState('');const[exportSettings,setExportSettings]=useState(()=>{let expDir='',remExpDir=false;try{expDir=localStorage.getItem('sonic_export_dir')||'';}catch(e){}try{remExpDir=localStorage.getItem('sonic_remember_export_dir')==='1';}catch(e){}return{sampleRate:'44100',bitDepth:'float',format:'wav',source:'project',quality:'44khz',channels:'stereo',loop:false,loopBars:4,dither:false,normalize:true,normalizeTarget:'-14lufs',monoCheck:true,realtime:false,// Gap 13: BWF/INFO metadata cho WAV mastering export metaTitle:'',metaArtist:'',metaIsrc:'',// Export file + thư mục (ExportModal) — dir lưu localStorage nếu remember fileName:'',exportDir:expDir,rememberExportDir:remExpDir};});// Persist export dir mặc định (chỉ khi user bật remember) — ExportModal useEffect(()=>{try{if(exportSettings.rememberExportDir&&exportSettings.exportDir){localStorage.setItem('sonic_export_dir',exportSettings.exportDir);}else{localStorage.removeItem('sonic_export_dir');}localStorage.setItem('sonic_remember_export_dir',exportSettings.rememberExportDir?'1':'0');}catch(e){}},[exportSettings.exportDir,exportSettings.rememberExportDir]);const[serverStatus,setServerStatus]=useState('checking...');const _lan=window.SonicRuntime&&window.SonicRuntime.capabilities&&window.SonicRuntime.capabilities.lan;const serverAddr=_lan&&_lan.urls&&_lan.urls[0]?function(){try{return new URL(_lan.urls[0]).host;}catch(e){return null;}}():window.location.host;const[menuOpen,setMenuOpen]=useState(null);const[selectedClipId,setSelectedClipId]=useState(null);// { trackId, clipId } @@ -2108,17 +2108,10 @@ const insertSectionAtPlayhead=()=>{const curTracks=activeTracks;const track=curT const insertMidiItemAtPlayhead=()=>{const curTracks=activeTracks;const track=curTracks.find(t=>t.id===selectedTrackId);if(!track){showToast('Chọn track trước','warning');return;}const secondsPerBar=60.0/(parseInt(bpm)||120)*4;const midiItem={id:`midi_${Date.now()}`,name:'MIDI Item',startTime:currentTime,duration:4*secondsPerBar,length_bars:4,notes:[],color:'#a78bfa'};createMidiWithUndo(selectedTrackId,midiItem);updateActiveTracks(prev=>prev.map(t=>t.id===selectedTrackId?{...t,midiItems:[...(t.midiItems||[]),midiItem]}:t));showToast(`Đã thêm MIDI item tại ${currentTime.toFixed(2)}s`,'success');};// ── Insert Sound Clip at Cursor ── const insertSoundClipAtCursor=()=>{const curTracks=activeTracks;const track=curTracks.find(t=>t.id===selectedTrackId);if(!track){showToast('Chọn track trước','warning');return;}const input=document.createElement('input');input.type='file';input.accept='audio/*';input.multiple=true;input.onchange=async e=>{const files=Array.from(e.target.files||[]);if(!files.length)return;let offset=currentTime;let count=0;for(const file of files){try{const uploadResult=await uploadToServer(file,track.id);const fileId=uploadResult?.file_id||null;const{audioBuffer:decodedBuffer,channelInfo}=await window.SonicAudio.decodeAudioFile(file);const clipId=`clip_${Date.now()}_${Math.random().toString(36).slice(2,6)}`;const newClip={id:clipId,buffer:decodedBuffer,startTime:offset,name:file.name,speed:1.0,serverFileId:fileId};createClipWithUndo(selectedTrackId,newClip);updateActiveTracks(prev=>prev.map(t=>t.id===selectedTrackId?{...t,serverFileId:t.serverFileId||fileId,clips:[...(t.clips||[]),newClip]}:t));offset+=decodedBuffer.duration;count++;}catch(err){showToast(`Lỗi nạp ${file.name}`,'error');}}if(count>0)showToast(`Đã chèn ${count} file âm thanh`,'success');};input.click();};// ── Chords Panel insert (Phase 6) ─────────────────────────────────────── const insertChordProgression=prog=>{const curTracks=activeTracks;const pr=prog||{};const chordList=pr.chords||[];if(!chordList.length){showToast('Không có chord để chèn','warning');return;}const beatSec=60.0/(parseInt(bpm)||120);const secondsPerBar=beatSec*4;const activeSub=activeTab&&subTabs.find(s=>s.id===activeTab&&s.type==='PIANO_ROLL');let trackId=null;let item=null;let insertBeat=0;if(activeSub){trackId=activeSub.trackId;const trk=curTracks.find(t=>t.id===trackId);if(trk)item=(trk.midiItems||[]).find(m=>m.id===activeSub.target_id)||null;insertBeat=Math.max(0,(activeSub.currentTime||0)/beatSec);}else{trackId=selectedTrackId;const trk=curTracks.find(t=>t.id===trackId);if(!trk){showToast('Chọn track trước','warning');return;}const items=trk.midiItems||[];if(!items.length){const newItem={id:'midi_'+Date.now(),name:'Chords',startTime:currentTime,duration:secondsPerBar*4,length_bars:4,notes:[],color:'#a78bfa'};createMidiWithUndo(trackId,newItem);updateActiveTracks(prev=>prev.map(t=>t.id===trackId?{...t,midiItems:[...(t.midiItems||[]),newItem]}:t));item=newItem;insertBeat=0;}else{item=items[items.length-1];insertBeat=Math.max(0,(currentTime-(item.startTime||0))/beatSec);}}if(!item){showToast('Không tìm thấy MIDI item','warning');return;}const merged=(item.notes||[]).slice();let beat=insertBeat;chordList.forEach(function(ch,ci){const dur=ch.duration_beats!=null?ch.duration_beats:4;(ch.pitches||[]).forEach(function(p){merged.push({id:'note_chord_'+Date.now()+'_'+ci+'_'+p,pitch:p,start_beat:beat,duration_beats:dur,velocity:0.8,pan:0.0});});beat+=dur;});const finalItem={...item,notes:merged};updateActiveTracks(prev=>prev.map(t=>{if(t.id!==trackId)return t;const has=(t.midiItems||[]).some(m=>m.id===item.id);if(!has)return{...t,midiItems:[...(t.midiItems||[]),finalItem]};return{...t,midiItems:(t.midiItems||[]).map(m=>m.id===item.id?finalItem:m)};}));if(activeSub){setSubTabs(prev=>prev.map(s=>s.id===activeSub.id?{...s,notes:merged,isDirty:true}:s));}setCanvasRedrawCount(n=>n+1);showToast('Đã chèn '+chordList.length+' chord ('+merged.length+' notes)','success');window.__lastChordInsert={trackId:trackId,itemId:item.id,notes:merged};};// ── Server-side Export ── -const triggerWavExport=async()=>{// TỰ ĐỘNG bounce realtime nếu project có MIDI items: FluidSynth (ScriptProcessor) -// không render được offline, nên bounce là cách duy nhất giữ trung thực -// MIDI + FX Rack + Mastering Chain trong file xuất. -const allT=activeTracksRef.current&&activeTracksRef.current.length?activeTracksRef.current:tracks;const hasMidiItems=allT.some(t=>t.midiItems&&t.midiItems.length>0)||(sessionTabs||[]).some(s=>(s.tracks||[]).some(t=>t.midiItems&&t.midiItems.length>0));if(hasMidiItems){// Nếu preview đã cache đủ độ dài cho MỌI track MIDI → export OFFLINE NHANH -// bằng cache (vẫn qua FX Rack + Mastering như playback). Ngược lại → -// bounce realtime để đảm bảo trung thực. -const midiTracks=allT.filter(t=>t.midiItems&&t.midiItems.length>0);let needDur=1.0;midiTracks.forEach(t=>{(t.midiItems||[]).forEach(m=>{if(m.endTime)needDur=Math.max(needDur,m.endTime);else if(m.startTime)needDur=Math.max(needDur,m.startTime+(m.duration||8));});});// Cache có thể "đủ" (chunks + duration) nhưng RỖNG — khi bridge VSTi active, -// âm đi qua BridgeAudioNode, KHÔNG qua track.sfEntry nơi ensureMidiCapture -// tap → chunks toàn zero → export offline ra file câm tuyệt đối. Kiểm tra -// năng lượng cache: nếu không có tín hiệu → bounce realtime (bắt đúng âm bridge). -const cacheHasEnergy=c=>{if(!c)return false;const chunks=c.chunks||[];for(let i=0;i0.0005)return true;}}return false;};const cacheOK=midiTracks.length>0&&midiTracks.every(t=>{const c=midiCacheRef.current[t.id];return c&&c.chunks.length>0&&c.duration>=needDur-0.3&&cacheHasEnergy(c);});if(cacheOK){showToast("Dùng MIDI cache từ preview — export OFFLINE NHANH (vẫn đủ FX Rack + Mastering Chain).","info");clientSideExport(allT);return;}showToast("Chưa có MIDI cache đầy đủ — bounce realtime để đảm bảo trung thực.","info");triggerBounceExport();return;}let exportTracks;let clipStart=0;let clipEnd=0;const src=exportSettings.source;if(src==='active_clip'||src==='clip_selection'){const selTrack=selectedTrackId?tracks.find(t=>t.id===selectedTrackId):null;if(!selTrack||!selTrack.buffer){showToast("Không có clip nào được chọn.","warning");return;}let rangeStart=selectionStart;let rangeEnd=selectionEnd;if(rangeStart===null||rangeEnd===null||rangeEnd<=rangeStart){rangeStart=0;rangeEnd=selTrack.buffer.duration;}const ctx=getAudioContext();const numCh=selTrack.buffer.numberOfChannels||1;const sr=selTrack.buffer.sampleRate;const startSample=Math.max(0,Math.floor(rangeStart*sr));const endSample=Math.min(selTrack.buffer.length,Math.floor(rangeEnd*sr));const len=endSample-startSample;if(len<=100){showToast("Vùng chọn quá ngắn hoặc không có dữ liệu.","warning");return;}const clipBuf=ctx.createBuffer(numCh,len,sr);for(let ch=0;cht.buffer&&!t.muted);const selTrk=selectedTrackId?sel.filter(t=>t.id===selectedTrackId):sel;if(selTrk.length===0){showToast("Track được chọn không có dữ liệu.","warning");return;}exportTracks=selTrk;}else{exportTracks=tracks.filter(t=>t.buffer&&!t.muted);}if(exportTracks.length===0){showToast("Không tìm thấy dữ liệu âm thanh hợp lệ để xuất.","warning");return;}// LOOP export (BIG_PHASE_3 Phase 2): cắt chính xác vùng loop — bắt đầu từ +const triggerWavExport=async()=>{// Realtime UNCHECKED → export OFFLINE thuần (server-side nếu connected, fallback client). +// MIDI không render được trong OfflineAudioContext: nếu project có MIDI items mà +// cache preview chưa đủ → file xuất có thể thiếu âm MIDI. Cảnh báo, vẫn xuất offline. +const allT=activeTracksRef.current&&activeTracksRef.current.length?activeTracksRef.current:tracks;const hasMidiItems=allT.some(t=>t.midiItems&&t.midiItems.length>0)||(sessionTabs||[]).some(s=>(s.tracks||[]).some(t=>t.midiItems&&t.midiItems.length>0));if(hasMidiItems){const midiTracks=allT.filter(t=>t.midiItems&&t.midiItems.length>0);let needDur=1.0;midiTracks.forEach(t=>{(t.midiItems||[]).forEach(m=>{if(m.endTime)needDur=Math.max(needDur,m.endTime);else if(m.startTime)needDur=Math.max(needDur,m.startTime+(m.duration||8));});});const cacheHasEnergy=c=>{if(!c)return false;const chunks=c.chunks||[];for(let i=0;i0.0005)return true;}}return false;};const cacheOK=midiTracks.length>0&&midiTracks.every(t=>{const c=midiCacheRef.current[t.id];return c&&c.chunks.length>0&&c.duration>=needDur-0.3&&cacheHasEnergy(c);});if(!cacheOK){showToast("Project có MIDI items chưa đủ cache preview — file offline có thể thiếu âm MIDI. Bật Realtime để bounce trung thực.","warning");}}let exportTracks;let clipStart=0;let clipEnd=0;const src=exportSettings.source;if(src==='active_clip'||src==='clip_selection'){const selTrack=selectedTrackId?tracks.find(t=>t.id===selectedTrackId):null;if(!selTrack||!selTrack.buffer){showToast("Không có clip nào được chọn.","warning");return;}let rangeStart=selectionStart;let rangeEnd=selectionEnd;if(rangeStart===null||rangeEnd===null||rangeEnd<=rangeStart){rangeStart=0;rangeEnd=selTrack.buffer.duration;}const ctx=getAudioContext();const numCh=selTrack.buffer.numberOfChannels||1;const sr=selTrack.buffer.sampleRate;const startSample=Math.max(0,Math.floor(rangeStart*sr));const endSample=Math.min(selTrack.buffer.length,Math.floor(rangeEnd*sr));const len=endSample-startSample;if(len<=100){showToast("Vùng chọn quá ngắn hoặc không có dữ liệu.","warning");return;}const clipBuf=ctx.createBuffer(numCh,len,sr);for(let ch=0;cht.buffer&&!t.muted);const selTrk=selectedTrackId?sel.filter(t=>t.id===selectedTrackId):sel;if(selTrk.length===0){showToast("Track được chọn không có dữ liệu.","warning");return;}exportTracks=selTrk;}else{exportTracks=tracks.filter(t=>t.buffer&&!t.muted);}if(exportTracks.length===0){showToast("Không tìm thấy dữ liệu âm thanh hợp lệ để xuất.","warning");return;}// LOOP export (BIG_PHASE_3 Phase 2): cắt chính xác vùng loop — bắt đầu từ // selection (nếu có) hoặc playhead, độ dài = loopBars * secondsPerBar. // Không fade in/out, không kéo đuôi (cắt cứng tại biên). let loopStart=0,loopDur=0;if(exportSettings.loop){const loopSecPerBar=60.0/(parseFloat(bpm)||120)*4;loopStart=selectionStart!==null&&selectionEnd!==null&&selectionEnd>selectionStart?selectionStart:currentTime;loopDur=Math.max(0.1,(exportSettings.loopBars||4)*loopSecPerBar);const lctx=getAudioContext();exportTracks=exportTracks.map(t=>{if(!t.buffer)return null;const ls=Math.max(0,Math.floor(loopStart*lctx.sampleRate));const le=Math.min(t.buffer.length,Math.ceil((loopStart+loopDur)*lctx.sampleRate));if(le<=ls)return null;const buf=lctx.createBuffer(t.buffer.numberOfChannels,le-ls,t.buffer.sampleRate);for(let ch=0;chsetOpenProjectModalOpen(true)},{label:'Save Project',icon:'upload-cloud',shortcut:'Ctrl+S',action:()=>{// Save project + save TẤT CẢ dirty sub-tabs (user 07:35 — nhấn lưu ở // MAIN phải lưu cả piano roll/audio tab đã sửa) -handleSaveProject();subTabsRef.current.filter(s=>s.isDirty).forEach(st=>{if(st.type==='PIANO_ROLL'){handleSaveMidiNotes(st.id,st.trackId,st.target_id,st.notes||[]);}else if(st.type==='SECTION'){handleSaveSectionTab(st.id);}else if(st.buffer){const subTrack=activeTracksRef.current.find(t=>t.id===st.trackId);if(subTrack){updateActiveTracks(prev=>prev.map(t=>t.id===st.trackId?{...t,buffer:st.buffer}:t));}}});showToast('Đã lưu dự án + các tab đã sửa','success');}},{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 rearrangeNewId=(tracks.length+1).toString();setTimeout(()=>loadFileOnTrack(rearrangeNewId,e.target.files[0]),100);}};input.click();showToast('Import audio','info');}},{label:'Export Mix...',icon:'file-output',action:()=>setShowExportPanel(true)},{label:'Export MIDI...',icon:'music',action:()=>triggerMidiExport()},{label:'Master FX Chain',icon:'wand-2',shortcut:'Ctrl+Shift+M',action:()=>setShowMasteringModal(true)},{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:'Chords panel',icon:'guitar',shortcut:'Shift+K',action:()=>window.__toggleChordsPanel()},{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:()=>window.__toggleMixerRef()},{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:()=>window.__toggleMediaExplorerRef()}]},{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:()=>{// DSP Tool đã được move vào SUB-TAB editor (audioclip). Mở sub-tab +handleSaveProject();subTabsRef.current.filter(s=>s.isDirty).forEach(st=>{if(st.type==='PIANO_ROLL'){handleSaveMidiNotes(st.id,st.trackId,st.target_id,st.notes||[]);}else if(st.type==='SECTION'){handleSaveSectionTab(st.id);}else if(st.buffer){const subTrack=activeTracksRef.current.find(t=>t.id===st.trackId);if(subTrack){updateActiveTracks(prev=>prev.map(t=>t.id===st.trackId?{...t,buffer:st.buffer}:t));}}});showToast('Đã lưu dự án + các tab đã sửa','success');}},{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 rearrangeNewId=(tracks.length+1).toString();setTimeout(()=>loadFileOnTrack(rearrangeNewId,e.target.files[0]),100);}};input.click();showToast('Import audio','info');}},{label:'Export Mix...',icon:'file-output',action:()=>setShowExportPanel(true)},{label:'Bounce MIDI...',icon:'radio',action:()=>triggerBounceExport()},{label:'Export MIDI...',icon:'music',action:()=>triggerMidiExport()},{label:'Master FX Chain',icon:'wand-2',shortcut:'Ctrl+Shift+M',action:()=>setShowMasteringModal(true)},{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:'Chords panel',icon:'guitar',shortcut:'Shift+K',action:()=>window.__toggleChordsPanel()},{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:()=>window.__toggleMixerRef()},{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:()=>window.__toggleMediaExplorerRef()}]},{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:()=>{// DSP Tool đã được move vào SUB-TAB editor (audioclip). Mở sub-tab // edit cho track/clip đang chọn — nếu chưa có clip → mở panel cũ. const t=activeTracks.find(x=>x.id===selectedTrackId);if(t&&(t.buffer||t.clips&&t.clips.length)){const clipId=t.clips&&t.clips[0]?t.clips[0].id:'default';handleEditClipInSubTab(t.id,clipId);}else{openPanel('python_tools');}}},{label:'Virtual MIDI Keyboard',icon:'piano',shortcut:'F2',action:()=>window.__toggleVkb()},{sep:true},{label:'Plugin Manager (SoundFont/VSTi)',icon:'zap',action:()=>{setPluginManagerModalOpen(true);window.SonicAPI.listPlugins().then(data=>setPluginsData(data)).catch(()=>{});}},{sep:true},{label:'Preferences...',icon:'settings-2',action:()=>setPreferencesModalOpen(true)}]},{label:'Help',items:[{label:'Hướng dẫn sử dụng...',icon:'book-open',action:()=>setHelpModalOpen(true)},{sep:true},{label:'About SonicForge Studio...',icon:'info',action:()=>setAboutModalOpen(true)}]}].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,serverAddr?" · "+serverAddr:""),/*#__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&&(!window.UndoRedoEngine||!window.UndoRedoEngine.canUndo()),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&&(!window.UndoRedoEngine||!window.UndoRedoEngine.canRedo()),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:()=>seekPlaybackTo(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){const st=subTabs.find(s=>s.id===activeTab);if(!st)return;const left=st.selectionStart!==null&&st.selectionEnd!==null?Math.min(st.selectionStart,st.selectionEnd):null;if(left!==null)seekPlaybackTo(left);}else{if(selLeft!==null)seekPlaybackTo(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);// Sync loop state with active sub-tab (piano roll / audio editor) const activeSub=activeTab&&subTabs.find(s=>s.id===activeTab&&['PIANO_ROLL','AUDIO_CLIP_EDITOR','SECTION_EDITOR'].includes(s.type));if(activeSub){const newLoop=!activeSub.isLooping;setSubTabs(prev=>prev.map(s=>s.id===activeTab?{...s,isLooping:newLoop}:s));if(newLoop){const bpmVal=parseInt(bpm)||120;const beatSec=60.0/bpmVal;let maxEnd=0;if(activeSub.type==='PIANO_ROLL'){(activeSub.notes||[]).forEach(n=>{const end=(n.start_beat||0)+(n.duration_beats||1);if(end>maxEnd)maxEnd=end;});}else if(activeSub.type==='SECTION_EDITOR'){(activeSub.clips||[]).forEach(c=>{const end=(c.startTime||0)+(c.duration||0);if(end>maxEnd)maxEnd=end;});}else if(activeSub.type==='AUDIO_CLIP_EDITOR'){const dur=activeSub.buffer?.duration||0;if(dur>maxEnd)maxEnd=dur;}const loopEndTime=activeSub.type==='PIANO_ROLL'?Math.max(maxEnd,16)*beatSec+1.0:Math.max(maxEnd,1);setSubTabs(prev=>prev.map(s=>s.id===activeTab?{...s,selectionStart:0,selectionEnd:loopEndTime}:s));}}else{// Main timeline / section tab: nếu user đã chọn vùng loop riêng → @@ -2358,7 +2351,7 @@ stopAllPlayback();window.SonicSF.stopAll();stopAllNativeSfNotes();stopAllNativeV 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'),showMixer&&/*#__PURE__*/React.createElement("div",{className:"flex flex-col border-t border-zinc-700 bg-slate-900 shrink-0 select-none",style:{height:mixerHeight+'px'}},/*#__PURE__*/React.createElement("div",{className:"flex items-center justify-between px-2 py-0.5 bg-zinc-800/80 shrink-0 cursor-ns-resize",onMouseDown:e=>{e.preventDefault();var startY=e.clientY;var startH=mixerHeight;var onMove=function(ev){var newH=Math.max(80,Math.min(400,startH-(ev.clientY-startY)));setMixerHeight(newH);localStorage.setItem('studio_mixer_height',newH.toString());};var onUp=function(){document.removeEventListener('mousemove',onMove);document.removeEventListener('mouseup',onUp);};document.addEventListener('mousemove',onMove);document.addEventListener('mouseup',onUp);}},/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-2"},/*#__PURE__*/React.createElement("span",{className:"inline-flex items-center shrink-0"},/*#__PURE__*/React.createElement("i",{"data-lucide":"grip-horizontal",className:"w-3 h-3 text-zinc-600"})),/*#__PURE__*/React.createElement("span",{className:"text-[10px] font-bold text-zinc-400 uppercase tracking-wider"},"MIXER")),/*#__PURE__*/React.createElement("button",{onClick:()=>setShowMixer(false),className:"p-0.5 rounded text-zinc-500 hover:text-zinc-200 hover:bg-zinc-700 transition"},/*#__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 flex overflow-x-auto p-1.5 gap-1.5 items-stretch"},/*#__PURE__*/React.createElement(MasterStripConsole,{masterVolume:masterVolume,setMasterVolume:setMasterVolume,masterVolL:masterVolL,setMasterVolL:setMasterVolL,masterVolR:masterVolR,setMasterVolR:setMasterVolR,masterLink:masterLink,setMasterLink:setMasterLink,masterMuted:masterMuted,setMasterMuted:setMasterMuted,masterAutoVol:masterAutoVol,setMasterAutoVol:setMasterAutoVol,masterAutoOn:masterAutoOn,setMasterAutoOn:setMasterAutoOn,showMasteringModal:showMasteringModal,setShowMasteringModal:setShowMasteringModal,masteringSettings:masteringSettings,setMasteringSettings:setMasteringSettings,isPlaying:isPlaying}),activeTracks.length>0&&/*#__PURE__*/React.createElement("div",{className:"w-px bg-zinc-700 shrink-0 self-stretch mx-0.5"}),activeTracks.map(function(track,idx){return/*#__PURE__*/React.createElement(TrackStripConsole,{key:track.id,track:track,index:idx,onUpdateTrack:updateTrackProp,trackVuRefs:trackVuRefs});}))),/*#__PURE__*/React.createElement("div",{className:"flex flex-col border-t border-zinc-700 bg-slate-900 shrink-0 select-none",style:{height:mediaExplorerPanelHeight+'px',display:showMediaExplorer?'flex':'none'}},/*#__PURE__*/React.createElement("div",{className:"flex items-center justify-between px-2 py-0.5 bg-zinc-800/80 shrink-0 cursor-ns-resize",onMouseDown:e=>{e.preventDefault();var startY=e.clientY;var startH=mediaExplorerPanelHeight;var onMove=function(ev){var newH=Math.max(120,Math.min(520,startH-(ev.clientY-startY)));setMediaExplorerPanelHeight(newH);localStorage.setItem('studio_media_explorer_height',newH.toString());};var onUp=function(){document.removeEventListener('mousemove',onMove);document.removeEventListener('mouseup',onUp);};document.addEventListener('mousemove',onMove);document.addEventListener('mouseup',onUp);}},/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-2"},/*#__PURE__*/React.createElement("span",{className:"inline-flex items-center shrink-0"},/*#__PURE__*/React.createElement("i",{"data-lucide":"grip-horizontal",className:"w-3 h-3 text-zinc-600"})),/*#__PURE__*/React.createElement("span",{className:"text-[10px] font-bold text-emerald-400 uppercase tracking-wider"},"Media Explorer (F6)")),/*#__PURE__*/React.createElement("button",{onClick:()=>setShowMediaExplorer(false),className:"p-0.5 rounded text-zinc-500 hover:text-zinc-200 hover:bg-zinc-700 transition"},/*#__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 min-h-0 overflow-hidden bg-[#262626]"},/*#__PURE__*/React.createElement(MediaExplorerPanel,{height:mediaExplorerPanelHeight,clipboardRef:clipboardRef,active:showMediaExplorer}))));})(),/*#__PURE__*/React.createElement("div",{className:"h-9 bg-[#141414] border-t border-zinc-900 px-4 flex items-center justify-between text-[18px] 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-[18px]"},"Local Sel"),selectionMode==='global'&&/*#__PURE__*/React.createElement("span",{className:"text-purple-400 font-semibold uppercase text-[18px]"},"Global Sel"),hasAnySolo&&/*#__PURE__*/React.createElement("span",{className:"text-amber-500 font-semibold"},"Solo: ",tracks.filter(t=>t.solo).length," track(s)"),isLoopingSelection&&selectionMode==='local'&&/*#__PURE__*/React.createElement("span",{className:"text-emerald-400 font-semibold uppercase text-[18px]"},"Solo Loop"),isLoopingSelection&&selectionMode!=='local'&&/*#__PURE__*/React.createElement("span",{className:"text-cyan-400 font-semibold uppercase text-[18px]"},"Master Loop")),/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-1.5 ml-3"},/*#__PURE__*/React.createElement("span",{className:"text-[18px] font-bold text-zinc-400 uppercase"},"Bars:"),/*#__PURE__*/React.createElement("input",{type:"number",min:"0",value:beginBar,onChange:e=>{const b=parseInt(e.target.value)||0;setBeginBar(b);const beatDuration=60/parseInt(bpm||120);const t=b*beatDuration*4;clearLocalSelection();setSelectionMode('global');setSelectionStart(t);setSelectionEnd(t+beatDuration*4);},className:"w-[60px] bg-black text-white text-[18px] px-1 py-0 rounded border border-zinc-700 text-center font-mono"}),/*#__PURE__*/React.createElement("span",{className:"text-[18px] text-zinc-500"},"-"),/*#__PURE__*/React.createElement("input",{type:"number",min:"0",value:endBar,onChange:e=>{const b=parseInt(e.target.value)||0;setEndBar(b);const beatDuration=60/parseInt(bpm||120);const t=b*beatDuration*4;// selectionEnd = vạch bar b (không +1 bar) — khớp hiển thị endBar // (quét 8 bars hiển thị "0 đến 8"): nhập 8 → selection tới vạch 8. setSelectionEnd(t);setNumberBar(Math.max(0,b-beginBar));},className:"w-[60px] bg-black text-white text-[18px] px-1 py-0 rounded border border-zinc-700 text-center font-mono"}),/*#__PURE__*/React.createElement("span",{className:"text-[18px] text-zinc-500"},"#"),/*#__PURE__*/React.createElement("input",{type:"number",min:"1",value:numberBar,readOnly:true,className:"w-[60px] bg-black text-zinc-400 text-[18px] px-1 py-0 rounded border border-zinc-800 text-center font-mono"}),/*#__PURE__*/React.createElement("button",{onClick:()=>setSelectionFollowsTempo(prev=>!prev),className:`px-1.5 py-0 text-[18px] rounded border font-bold ${selectionFollowsTempo?'bg-amber-700 text-white border-amber-500':'bg-zinc-800 text-zinc-400 border-zinc-700'}`,title:selectionFollowsTempo?"Selection theo tempo (đổi BPM → selection thay đổi)":"Selection theo thời gian (cố định)"},selectionFollowsTempo?"♪T":"⏱T"),/*#__PURE__*/React.createElement("div",{className:"w-[1px] h-6 bg-zinc-800 mx-1"}),/*#__PURE__*/React.createElement("span",{className:"text-[18px] 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-24 bg-black text-zinc-200 text-[18px] px-1 py-0 rounded border border-zinc-700 text-center font-mono"}),/*#__PURE__*/React.createElement("span",{className:"text-[18px] 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-24 bg-black text-zinc-200 text-[18px] px-1 py-0 rounded border border-zinc-700 text-center font-mono"}),/*#__PURE__*/React.createElement("span",{className:"text-[18px] 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-24 bg-black text-amber-300 text-[18px] px-1 py-0 rounded border border-zinc-700 text-center font-mono"})),/*#__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 (floating modal)`},/*#__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:()=>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:()=>window.__toggleMixerRef(),className:`px-1.5 py-0.5 rounded text-xs font-semibold transition flex items-center gap-1 ${showMixer?'bg-indigo-900 text-indigo-300':'text-zinc-500 hover:text-zinc-300'}`,title:"Mixer Panel (F7)"},/*#__PURE__*/React.createElement("span",{className:"inline-flex items-center shrink-0"},/*#__PURE__*/React.createElement("i",{"data-lucide":"sliders-horizontal",className:"w-3 h-3"}))),/*#__PURE__*/React.createElement("button",{onClick:()=>window.__toggleMediaExplorerRef(),className:`px-1.5 py-0.5 rounded text-xs font-semibold transition flex items-center gap-1 ${showMediaExplorer?'bg-emerald-900 text-emerald-300':'text-zinc-500 hover:text-zinc-300'}`,title:"Media Explorer Panel (F6)"},/*#__PURE__*/React.createElement("span",{className:"inline-flex items-center shrink-0"},/*#__PURE__*/React.createElement("i",{"data-lucide":"folder-open",className:"w-3 h-3"}))),/*#__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"})),prHint?/*#__PURE__*/React.createElement("span",{className:"text-cyan-400"},prHint):/*#__PURE__*/React.createElement("span",{className:"text-zinc-600 italic"},"Adaptive tips: hover vào vùng làm việc để xem hướng dẫn")))),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 overflow-y-auto",style:{left:Math.min(contextMenu.x,window.innerWidth-260),top:contextMenu.y+200>window.innerHeight?undefined:contextMenu.y,bottom:contextMenu.y+200>window.innerHeight?window.innerHeight-contextMenu.y:undefined,maxHeight:'60vh'},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 overflow-y-auto",style:{left:Math.min(contextMenu.x,window.innerWidth-260),top:contextMenu.y+260>window.innerHeight?undefined:contextMenu.y,bottom:contextMenu.y+260>window.innerHeight?window.innerHeight-contextMenu.y:undefined,maxHeight:'60vh'},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("div",{className:"px-3 py-0.5 text-[10px] text-zinc-500 font-bold uppercase tracking-wider"},"Insert"),!sessionTabs.some(s=>s.id===activeTab)&&/*#__PURE__*/React.createElement("button",{onClick:()=>{insertSectionAtPlayhead();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":"folder-plus",className:"w-3.5 h-3.5 text-amber-400 shrink-0"})),/*#__PURE__*/React.createElement("span",{className:"flex-1"},"Insert Section")),/*#__PURE__*/React.createElement("button",{onClick:()=>{insertMidiItemAtPlayhead();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":"music",className:"w-3.5 h-3.5 text-purple-400 shrink-0"})),/*#__PURE__*/React.createElement("span",{className:"flex-1"},"Insert MIDI Item")),/*#__PURE__*/React.createElement("button",{onClick:()=>{insertSoundClipAtCursor();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":"file-audio",className:"w-3.5 h-3.5 text-emerald-400 shrink-0"})),/*#__PURE__*/React.createElement("span",{className:"flex-1"},"Insert Sound Clip")),/*#__PURE__*/React.createElement("button",{onClick:()=>{insertTrackBelow();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":"plus-square",className:"w-3.5 h-3.5 text-cyan-400 shrink-0"})),/*#__PURE__*/React.createElement("span",{className:"flex-1"},"Insert Track")),/*#__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,loadAudioBuffersForTracks:loadAudioBuffersForTracks,setAppWarningModal:setAppWarningModal,bpm:bpm,setBpm:setBpm,setMasteringSettings:setMasteringSettings,setSessionTabs:setSessionTabs,setSubTabs:setSubTabs,trackMidiChannelsRef:trackMidiChannelsRef}),/*#__PURE__*/React.createElement(SaveProjectModal,{isOpen:saveProjectModalOpen,onClose:()=>setSaveProjectModalOpen(false),projectName:projectName,onSaveCloud:(newName,existingId)=>{handleSaveCloudProject(newName,existingId);},onSaveLocal:newName=>{handleSaveLocalProject(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(OpenProjectModal,{isOpen:openProjectModalOpen,onClose:()=>setOpenProjectModalOpen(false),onOpenCloud:(projId,projName)=>{setOpenProjectModalOpen(false);handleOpenProject(projId,projName);},onOpenLocal:()=>{setOpenProjectModalOpen(false);handleImportSFS();}}),/*#__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(AboutModal,{isOpen:aboutModalOpen,onClose:()=>setAboutModalOpen(false)}),/*#__PURE__*/React.createElement(HelpModal,{isOpen:helpModalOpen,onClose:()=>setHelpModalOpen(false),lang:prefs.language}),/*#__PURE__*/React.createElement(PreferencesModal,{isOpen:preferencesModalOpen,onClose:()=>setPreferencesModalOpen(false),prefs:prefs,onPrefsChange:handlePrefsChange}),/*#__PURE__*/React.createElement(SystemManagerModal,{isOpen:systemManagerModalOpen,onClose:()=>setSystemManagerModalOpen(false)}),/*#__PURE__*/React.createElement(PluginManagerModal,{isOpen:pluginManagerModalOpen,onClose:()=>setPluginManagerModalOpen(false),pluginsData:pluginsData,onInsertInstrument:inst=>{// Chèn instrument (soundfont bank/program) vào track đang chọn — nút Synth -const tid=selectedTrackId||activeTracks&&activeTracks[0]&&activeTracks[0].id;if(tid&&inst&&inst.instrumentId){setTrackInstrumentWithProgram(tid,inst.instrumentId,inst.program,inst.displayName||inst.name,inst.bank);showToast('Đã chèn nhạc cụ: '+(inst.displayName||inst.name),'success');}setPluginManagerModalOpen(false);}}),/*#__PURE__*/React.createElement(AIPresetModal,{isOpen:aiPresetModalOpen,onClose:()=>{setAiPresetModalOpen(false);setAiPresetVersion(v=>v+1);}}),/*#__PURE__*/React.createElement(MasteringModal,{isOpen:showMasteringModal,onClose:()=>setShowMasteringModal(false),masteringSettings:masteringSettings,setMasteringSettings:setMasteringSettings}),fxRackTarget&&/*#__PURE__*/React.createElement(FXRackModal,{track:(activeTracksRef.current||[]).find(t=>t.id===fxRackTarget.trackId)||tracks.find(t=>t.id===fxRackTarget.trackId)||null,onUpdateTrack:updateTrackProp,onClose:()=>setFxRackTarget(null)}),vkbOpen&&/*#__PURE__*/React.createElement(VirtualMidiKeyboard,{settings:vkbSettings,onChange:vkbUpdateSettings,tracks:activeTracks,activePitches:activeMidiPitches,onNoteOn:vkbNoteOn,onNoteOff:vkbNoteOff,onClose:()=>window.__toggleVkb()}),chordsPanelOpen&&/*#__PURE__*/React.createElement(ChordsPanel,{onClose:()=>window.__toggleChordsPanel(),onInsert:insertChordProgression,onNoteOn:vkbNoteOn,onNoteOff:vkbNoteOff,bpm:bpm}),/*#__PURE__*/React.createElement(ExportModal,{open:showExportPanel,onClose:()=>setShowExportPanel(false),exportSettings:exportSettings,setExportSettings:setExportSettings,isExporting:isExporting,onExport:triggerWavExport,onBounce:triggerBounceExport,onRenderMastering:renderMasteringExport}),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-4xl p-5 text-slate-200",onClick:e=>e.stopPropagation()},/*#__PURE__*/React.createElement("div",{className:"flex justify-between items-center pb-3 border-b border-[#383838]"},/*#__PURE__*/React.createElement("h3",{className:"text-sm font-bold text-amber-400"},"Select Instrument"),/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-2"},/*#__PURE__*/React.createElement("select",{value:instrumentSelectorTrackId||'',onChange:e=>{var tid=e.target.value;if(tid){openInstrumentSelector(tid);}},className:"bg-[#1e1e1e] text-zinc-300 border border-zinc-700 rounded px-2 py-1 text-xs font-mono cursor-pointer"},activeTracks.map(function(at){return/*#__PURE__*/React.createElement("option",{key:at.id,value:at.id},at.name+' ('+(at.instrumentName||'Synth')+')');})),/*#__PURE__*/React.createElement("button",{onClick:closeInstrumentSelector,className:"text-slate-400 hover:text-slate-200"},"\u2715"))),/*#__PURE__*/React.createElement("input",{type:"text",placeholder:"T\u00ecm nh\u1ea1c c\u1ee5...",value:sfPresetSearchQuery,onChange:e=>setSfPresetSearchQuery(e.target.value),autoFocus:true,className:"w-full bg-black border border-zinc-700 rounded px-2 py-1.5 outline-none my-3 text-sm"}),/*#__PURE__*/React.createElement("div",{className:"flex gap-4",style:{height:"420px"}},/*#__PURE__*/React.createElement("div",{className:"w-1/3 border-r border-[#383838] overflow-y-auto space-y-0.5 pr-2"},/*#__PURE__*/React.createElement("button",{onClick:()=>{setSelectedSoundFontId(null);setSynthCategory(null);},className:"w-full text-left px-3 py-2 text-sm rounded "+(!selectedSoundFontId?"bg-amber-700 text-white":"bg-zinc-800 hover:bg-zinc-700 text-zinc-300")},"All Instruments"),(instrumentSelectorData?.soundfonts||[]).map(sf=>{const sfId=sf.id.startsWith('sf_')?sf.id:'sf_'+sf.id;const sfName=sf.display||sf.name||sf.id;return/*#__PURE__*/React.createElement("button",{key:sfId,onClick:()=>{setSelectedSoundFontId(sfId);setSfPresets(null);const baseId=sfId.replace('sf_','');window.SonicAPI.listSoundfontInstruments(baseId).then(data=>{if(data&&data.presets){const mapping=data.presets.map(p=>({...p,_sfId:sfId,_sfName:sfName,_sfDisplay:sfName.substring(0,30)}));setSfPresets(mapping);setInstrumentSelectorData(prev=>prev?{...prev,soundfonts:(prev.soundfonts||[]).map(s=>s.id===sf.id?{...s,presets:data.presets}:s)}:prev);}else{setSfPresets([]);}}).catch(()=>{setSfPresets([]);});},className:"w-full text-left px-3 py-2 text-sm rounded "+(selectedSoundFontId===sfId?"bg-amber-700 text-white":"bg-zinc-800 hover:bg-zinc-700 text-zinc-300")},sfName);}),// ── VST Instruments (scanned bởi Plugin Manager) ── +const tid=selectedTrackId||activeTracks&&activeTracks[0]&&activeTracks[0].id;if(tid&&inst&&inst.instrumentId){setTrackInstrumentWithProgram(tid,inst.instrumentId,inst.program,inst.displayName||inst.name,inst.bank);showToast('Đã chèn nhạc cụ: '+(inst.displayName||inst.name),'success');}setPluginManagerModalOpen(false);}}),/*#__PURE__*/React.createElement(AIPresetModal,{isOpen:aiPresetModalOpen,onClose:()=>{setAiPresetModalOpen(false);setAiPresetVersion(v=>v+1);}}),/*#__PURE__*/React.createElement(MasteringModal,{isOpen:showMasteringModal,onClose:()=>setShowMasteringModal(false),masteringSettings:masteringSettings,setMasteringSettings:setMasteringSettings}),fxRackTarget&&/*#__PURE__*/React.createElement(FXRackModal,{track:(activeTracksRef.current||[]).find(t=>t.id===fxRackTarget.trackId)||tracks.find(t=>t.id===fxRackTarget.trackId)||null,onUpdateTrack:updateTrackProp,onClose:()=>setFxRackTarget(null)}),vkbOpen&&/*#__PURE__*/React.createElement(VirtualMidiKeyboard,{settings:vkbSettings,onChange:vkbUpdateSettings,tracks:activeTracks,activePitches:activeMidiPitches,onNoteOn:vkbNoteOn,onNoteOff:vkbNoteOff,onClose:()=>window.__toggleVkb()}),chordsPanelOpen&&/*#__PURE__*/React.createElement(ChordsPanel,{onClose:()=>window.__toggleChordsPanel(),onInsert:insertChordProgression,onNoteOn:vkbNoteOn,onNoteOff:vkbNoteOff,bpm:bpm}),/*#__PURE__*/React.createElement(ExportModal,{open:showExportPanel,onClose:()=>setShowExportPanel(false),exportSettings:exportSettings,setExportSettings:setExportSettings,isExporting:isExporting,onExport:()=>exportSettings.realtime?triggerBounceExport():triggerWavExport()}),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-4xl p-5 text-slate-200",onClick:e=>e.stopPropagation()},/*#__PURE__*/React.createElement("div",{className:"flex justify-between items-center pb-3 border-b border-[#383838]"},/*#__PURE__*/React.createElement("h3",{className:"text-sm font-bold text-amber-400"},"Select Instrument"),/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-2"},/*#__PURE__*/React.createElement("select",{value:instrumentSelectorTrackId||'',onChange:e=>{var tid=e.target.value;if(tid){openInstrumentSelector(tid);}},className:"bg-[#1e1e1e] text-zinc-300 border border-zinc-700 rounded px-2 py-1 text-xs font-mono cursor-pointer"},activeTracks.map(function(at){return/*#__PURE__*/React.createElement("option",{key:at.id,value:at.id},at.name+' ('+(at.instrumentName||'Synth')+')');})),/*#__PURE__*/React.createElement("button",{onClick:closeInstrumentSelector,className:"text-slate-400 hover:text-slate-200"},"\u2715"))),/*#__PURE__*/React.createElement("input",{type:"text",placeholder:"T\u00ecm nh\u1ea1c c\u1ee5...",value:sfPresetSearchQuery,onChange:e=>setSfPresetSearchQuery(e.target.value),autoFocus:true,className:"w-full bg-black border border-zinc-700 rounded px-2 py-1.5 outline-none my-3 text-sm"}),/*#__PURE__*/React.createElement("div",{className:"flex gap-4",style:{height:"420px"}},/*#__PURE__*/React.createElement("div",{className:"w-1/3 border-r border-[#383838] overflow-y-auto space-y-0.5 pr-2"},/*#__PURE__*/React.createElement("button",{onClick:()=>{setSelectedSoundFontId(null);setSynthCategory(null);},className:"w-full text-left px-3 py-2 text-sm rounded "+(!selectedSoundFontId?"bg-amber-700 text-white":"bg-zinc-800 hover:bg-zinc-700 text-zinc-300")},"All Instruments"),(instrumentSelectorData?.soundfonts||[]).map(sf=>{const sfId=sf.id.startsWith('sf_')?sf.id:'sf_'+sf.id;const sfName=sf.display||sf.name||sf.id;return/*#__PURE__*/React.createElement("button",{key:sfId,onClick:()=>{setSelectedSoundFontId(sfId);setSfPresets(null);const baseId=sfId.replace('sf_','');window.SonicAPI.listSoundfontInstruments(baseId).then(data=>{if(data&&data.presets){const mapping=data.presets.map(p=>({...p,_sfId:sfId,_sfName:sfName,_sfDisplay:sfName.substring(0,30)}));setSfPresets(mapping);setInstrumentSelectorData(prev=>prev?{...prev,soundfonts:(prev.soundfonts||[]).map(s=>s.id===sf.id?{...s,presets:data.presets}:s)}:prev);}else{setSfPresets([]);}}).catch(()=>{setSfPresets([]);});},className:"w-full text-left px-3 py-2 text-sm rounded "+(selectedSoundFontId===sfId?"bg-amber-700 text-white":"bg-zinc-800 hover:bg-zinc-700 text-zinc-300")},sfName);}),// ── VST Instruments (scanned bởi Plugin Manager) ── instrumentSelectorData?.vst_instruments?.length>0&&/*#__PURE__*/React.createElement("div",{className:"mt-3 text-[10px] text-zinc-500 uppercase font-bold px-1 flex items-center gap-1"},/*#__PURE__*/React.createElement("i",{"data-lucide":"cpu",className:"w-3 h-3 text-violet-400"}),"VST Instruments"),(instrumentSelectorData?.vst_instruments||[]).map(v=>/*#__PURE__*/React.createElement("button",{key:'vst_modal_'+(v.id||v.name),onClick:()=>{setTrackInstrumentWithUndo(instrumentSelectorTrackId,v.id,v.name||v.id);closeInstrumentSelector();},className:"w-full text-left px-3 py-2 text-sm rounded bg-zinc-800 hover:bg-violet-800 text-zinc-300 flex items-center justify-between gap-2"},/*#__PURE__*/React.createElement("span",{className:"truncate"},v.name||v.id),/*#__PURE__*/React.createElement("span",{className:"text-[9px] text-cyan-400 shrink-0"},v.type||"VST")))),/*#__PURE__*/React.createElement("div",{className:"flex-1 overflow-y-auto space-y-0.5"},/*#__PURE__*/React.createElement("button",{onClick:()=>setTrackInstrumentWithUndo(instrumentSelectorTrackId,null),className:"w-full text-left px-3 py-2 text-sm rounded bg-zinc-800 hover:bg-zinc-700 text-zinc-400"},"None (Default Synth)"),sfPresets===null?(/*#__PURE__*/React.createElement("p",{className:"text-sm text-zinc-500 py-2"},"Loading instruments...")):sfPresets.length>0?sfPresets.filter(p=>!selectedSoundFontId||p._sfId===selectedSoundFontId).filter(p=>!sfPresetSearchQuery||(p.name||'').toLowerCase().includes(sfPresetSearchQuery.toLowerCase())||(p._sfName||'').toLowerCase().includes(sfPresetSearchQuery.toLowerCase())).map((p,i)=>/*#__PURE__*/React.createElement("button",{key:i,onClick:()=>setTrackInstrumentWithUndo(instrumentSelectorTrackId,p._sfId,p.name||'Preset '+p.program,p.bank,p.program),onDoubleClick:()=>{setTrackInstrumentWithUndo(instrumentSelectorTrackId,p._sfId,p.name||'Preset '+p.program,p.bank,p.program);closeInstrumentSelector();},className:"w-full text-left px-3 py-2 text-sm rounded bg-zinc-800/60 hover:bg-amber-800 text-zinc-300 truncate flex items-center gap-2"},/*#__PURE__*/React.createElement("span",{className:"text-xs text-zinc-500 shrink-0"},p._sfDisplay),p.bank===128?/*#__PURE__*/React.createElement("span",{className:"mr-1"},"🥁"):null,p.name||'Preset '+p.program)):(/*#__PURE__*/React.createElement("p",{className:"text-sm text-zinc-500 py-2"},sfPresetSearchQuery?"Kh\u00f4ng t\u00ecm th\u1ea5y nh\u1ea1c c\u1ee5 ph\u00f9 h\u1ee3p.":"No presets found.")))))),fxSelectorTrackId&&/*#__PURE__*/React.createElement("div",{className:"fixed inset-0 z-50 flex items-center justify-center bg-black/60 backdrop-blur-sm",onClick:()=>setFxSelectorTrackId(null)},/*#__PURE__*/React.createElement("div",{className:"bg-[#262626] border border-[#383838] rounded-xl shadow-2xl w-64 p-4 text-slate-200",onClick:e=>e.stopPropagation()},/*#__PURE__*/React.createElement("h3",{className:"text-sm font-bold text-purple-400 mb-3"},"Track FX"),/*#__PURE__*/React.createElement("div",{className:"space-y-1"},/*#__PURE__*/React.createElement("button",{onClick:()=>handleSetTrackFx(fxSelectorTrackId,null),className:"w-full text-left px-3 py-2 text-xs rounded bg-zinc-800 hover:bg-zinc-700 text-zinc-400"},"None"),/*#__PURE__*/React.createElement("button",{onClick:()=>handleSetTrackFx(fxSelectorTrackId,'chorus'),className:"w-full text-left px-3 py-2 text-xs rounded bg-zinc-800 hover:bg-cyan-800 text-cyan-300"},"Chorus"),/*#__PURE__*/React.createElement("button",{onClick:()=>handleSetTrackFx(fxSelectorTrackId,'reverb'),className:"w-full text-left px-3 py-2 text-xs rounded bg-zinc-800 hover:bg-purple-800 text-purple-300"},"Reverb")))),instrumentDropdownTrackId&&instrumentDropdownBtnRect&&/*#__PURE__*/React.createElement("div",{"data-instr-dropdown":"",className:"fixed z-[100] bg-[#1e1e1e] border border-zinc-700 rounded shadow-xl w-[220px] max-h-[300px] flex flex-col",style:{top:instrumentDropdownBtnRect.bottom+4,left:Math.max(4,Math.min(instrumentDropdownBtnRect.left,window.innerWidth-224))}},/*#__PURE__*/React.createElement("input",{type:"text",placeholder:"T\u00ecm nh\u1ea1c c\u1ee5...",value:instrumentSearchQuery,onChange:e=>setInstrumentSearchQuery(e.target.value),autoFocus:true,className:"w-full bg-black border-b border-zinc-700 px-2 py-1.5 text-xs outline-none"}),/*#__PURE__*/React.createElement("div",{className:"flex-1 overflow-y-auto"},/*#__PURE__*/React.createElement("button",{onClick:()=>setTrackInstrumentWithUndo(instrumentDropdownTrackId,null),className:"w-full text-left px-3 py-1.5 text-xs bg-zinc-800 hover:bg-zinc-700 text-zinc-400"},"None (Default Synth)"),filteredInstruments.soundfonts.length>0&&/*#__PURE__*/React.createElement("div",{className:"text-[10px] text-zinc-500 px-3 py-1 mt-1 uppercase font-bold"},"SoundFonts"),filteredInstruments.soundfonts.map((sf,i)=>/*#__PURE__*/React.createElement("button",{key:"sfd_"+i,onClick:()=>{setInstrumentDropdownTrackId(null);setInstrumentDropdownBtnRect(null);setTrackInstrumentWithUndo(instrumentDropdownTrackId,"sf_"+sf.id,sf.display||sf.name||sf.id,undefined,undefined);},className:"w-full text-left px-3 py-1.5 text-xs bg-zinc-800 hover:bg-amber-800 text-zinc-200 flex items-center justify-between"},/*#__PURE__*/React.createElement("span",{className:"truncate"},sf.display||sf.name||sf.id),/*#__PURE__*/React.createElement("span",{className:"text-[10px] text-amber-400 shrink-0 ml-1"},"SF"))),filteredInstruments.vst.length>0&&/*#__PURE__*/React.createElement("div",{className:"text-[10px] text-zinc-500 px-3 py-1 mt-1 uppercase font-bold"},"VST Instruments"),filteredInstruments.vst.map((v,i)=>/*#__PURE__*/React.createElement("div",{key:"vstd_"+i,className:"flex items-stretch"},/*#__PURE__*/React.createElement("button",{onClick:()=>{setInstrumentDropdownTrackId(null);setInstrumentDropdownBtnRect(null);setTrackInstrumentWithUndo(instrumentDropdownTrackId,v.id,v.name||v.id);},className:"flex-1 min-w-0 text-left px-3 py-1.5 text-xs bg-zinc-800 hover:bg-violet-800 text-zinc-200 flex items-center justify-between"},/*#__PURE__*/React.createElement("span",{className:"truncate"},v.name||v.id),/*#__PURE__*/React.createElement("span",{className:"text-[10px] text-cyan-400 shrink-0 ml-1"},v.type||"VST")))),filteredInstruments.vst.length>0&&/*#__PURE__*/React.createElement("div",{className:"text-[10px] text-zinc-500 px-3 py-1 mt-1 uppercase font-bold"},"VST Presets (.vstpreset)"),(window.SonicRuntime&&window.SonicRuntime.presets||[]).map((p,i)=>/*#__PURE__*/React.createElement("button",{key:"psd_"+i,onClick:()=>setTrackPreset(instrumentDropdownTrackId,p.id),className:"w-full text-left px-3 py-1 text-xs bg-zinc-800 hover:bg-emerald-800 text-zinc-300 flex items-center justify-between"},/*#__PURE__*/React.createElement("span",{className:"truncate"},p.name||p.id),/*#__PURE__*/React.createElement("span",{className:"text-[10px] text-emerald-400 shrink-0 ml-1"},"PRESET"))),/*#__PURE__*/React.createElement("button",{onClick:()=>{const inp=document.createElement('input');inp.type='file';inp.accept='.vstpreset,.fxp,.fxb,.dspreset';inp.onchange=()=>{const f=inp.files&&inp.files[0];if(!f)return;window.SonicAPI.uploadPreset(f).then(function(r){if(r&&r.success){if(window.SonicRuntime)window.SonicRuntime.refreshPresets();showToast('Đã upload preset: '+(r.original_name||r.name||''),'success');}else{showToast('Upload preset thất bại','error');}}).catch(function(err){showToast('Upload lỗi: '+(err.message||err),'error');});};inp.click();},className:"w-full text-left px-3 py-1 text-xs bg-zinc-800 hover:bg-teal-800 text-teal-300 border-t border-zinc-700",title:"Upload preset .vstpreset xuất từ native GUI"},"\u2B06 Upload preset (t\u1EEB native GUI...)"),!filteredInstruments.soundfonts.length&&!filteredInstruments.vst.length&&/*#__PURE__*/React.createElement("p",{className:"text-xs text-zinc-500 py-4 text-center"},"Kh\u00f4ng t\u00ecm th\u1ea5y nh\u1ea1c c\u1ee5 n\u00e0o"))));};// React onWheel is passive -> e.preventDefault() can't stop panel scroll, so the // ruler slips away from the cursor mid-scroll. Native capture listener (non-passive) // blocks scrolling whenever the wheel starts over a [data-sf-wheel] container. diff --git a/app/templates/index.html b/app/templates/index.html index 5de9480..f8bc1f7 100644 --- a/app/templates/index.html +++ b/app/templates/index.html @@ -51,7 +51,7 @@ - +