diff --git a/BUILD_LAYOUT.md b/BUILD_LAYOUT.md new file mode 100644 index 0000000..38e496b --- /dev/null +++ b/BUILD_LAYOUT.md @@ -0,0 +1,43 @@ +# Build Layout — vị trí chuẩn (ổn định qua build & đổi code) + +## Canonical outputs (đều gitignored, tái tạo từ build) + +| Vị trí | Nội dung | Nguồn | +|---|---|---| +| `src-tauri/binaries/` | Sidecars + DLL runtime | `build/scripts/build_native_bridge.ps1` | +| `src-tauri/binaries/daw_vst_bridge-x86_64-pc-windows-msvc.exe` | Instrument bridge (VSTi + SF2/SF3/SFZ, SHM realtime, control 1-6) | CMake `daw_vst_bridge` | +| `src-tauri/binaries/fx_vst_bridge-x86_64-pc-windows-msvc.exe` | FX bridge (VST3 FX: `--render-fx`, `--scan`, `--open-fx-gui`, `--fx-gui`) | CMake `fx_vst_bridge` | +| `src-tauri/binaries/libfluidsynth-3.dll`, `jpeg62.dll` | Runtime DLL (fluidsynth, libjpeg-turbo) | vcpkg manifest install | +| `src-tauri/resources/daw_engine/` | Python engine đã đóng băng (PyInstaller) | `build/scripts/build_windows.ps1` → `dist/daw_engine` | + +## External (shared, không nằm trong repo) + +| Vị trí | Nội dung | +|---|---| +| `%USERPROFILE%\vcpkg` | vcpkg.exe + package cache (fluidsynth, libjpeg-turbo, pkgconf) | +| `%USERPROFILE%\vcpkg\packages\libjpeg-turbo_x64-windows\bin\jpeg62.dll` | Fallback nguồn jpeg62.dll khi manifest chưa tái tạo | + +## Dev-time resolution (Python) + +`app/core/native_render.py`: +- `find_bridge_exe()` — `SF_BRIDGE_PATH` → frozen (`_MEIPASS`/`exe_dir`) → `src-tauri/binaries/` → `install/` (legacy). Instrument bridge. +- `find_fx_bridge_exe()` — `SF_FX_BRIDGE_PATH` → cùng chuỗi. FX bridge. + +Tauri sidecar rename: `binaries/daw_vst_bridge` (externalBin) → `daw_vst_bridge-x86_64-pc-windows-msvc.exe` — cả hai dạng đều được tìm. + +## Build + +```powershell +# Native bridges (C++): vcpkg manifest + sfizz + CMake → src-tauri/binaries +build/scripts/build_native_bridge.ps1 + +# Full desktop (engine PyInstaller + bridges + Tauri NSIS/MSI) +build/scripts/build_windows.ps1 +``` + +`build/scripts/` là tracked (dù `build/` gitignored) — không xoá `build/` wholesale. + +## Split bridges + +- `daw_vst_bridge` — VSTi instruments, VST2 (.dll) + VST3 (.vst3), native GUI qua SHM control 4 (`OPEN_GUI`), SF2/SF3/SFZ (fluidsynth/sfizz), realtime FX chain in-process (control 6 `SET_FX_CHAIN`). +- `fx_vst_bridge` — VST3 FX only. One-shot: `--scan `, `--render-fx --in --out`, `--open-fx-gui ` (cửa sổ rời), `--fx-gui ` (embed + HTTP `SF_FXGUI_PORT`). Không SHM, không fluidsynth/sfizz. VST2 FX không hỗ trợ (VST2 không introspect được — xem là instrument; ponytail: thêm AEffect hosting khi có nhu cầu). diff --git a/app/api/v1/plugins.py b/app/api/v1/plugins.py index 3443d4b..6abe893 100644 --- a/app/api/v1/plugins.py +++ b/app/api/v1/plugins.py @@ -701,12 +701,12 @@ async def open_fx_gui(req: FxGuiRequest, current_user: dict = Depends(get_curren (exit 0). Không spawn 2 lần cho cùng plugin đang mở (tránh trùng GUI). """ enforce_password_changed(current_user) - from app.core.native_render import find_bridge_exe - exe = find_bridge_exe() + from app.core.native_render import find_fx_bridge_exe + exe = find_fx_bridge_exe() if not exe: raise HTTPException( status_code=409, - detail="Không tìm thấy bridge native (daw_vst_bridge.exe). " + detail="Không tìm thấy bridge native (fx_vst_bridge.exe). " "Hãy chạy bản desktop đã build đầy đủ.", ) plugin_path = (req.path or "").strip() diff --git a/app/core/native_render.py b/app/core/native_render.py index b49ea21..cfa4a3b 100644 --- a/app/core/native_render.py +++ b/app/core/native_render.py @@ -19,33 +19,47 @@ from fastapi import HTTPException from app.config import settings BRIDGE_EXE = "daw_vst_bridge.exe" if os.name == "nt" else "daw_vst_bridge" +FX_BRIDGE_EXE = "fx_vst_bridge.exe" if os.name == "nt" else "fx_vst_bridge" -def find_bridge_exe() -> str: - """Đường dẫn bridge exe: env SF_BRIDGE_PATH → bundle (frozen) → install/ (dev).""" - env = os.getenv("SF_BRIDGE_PATH", "").strip() +def find_bridge_exe(exe_name=BRIDGE_EXE, env_key="SF_BRIDGE_PATH") -> str: + """Đường dẫn bridge exe: env → bundle (frozen) → src-tauri/binaries → install/ (dev).""" + env = os.getenv(env_key, "").strip() if env and os.path.isfile(env): return env + # Tauri externalBin renames sidecars → -x86_64-pc-windows-msvc.exe. + sidecar = exe_name[:-4] + "-x86_64-pc-windows-msvc.exe" if exe_name.lower().endswith(".exe") else exe_name cands = [] if getattr(sys, "frozen", False): root = getattr(sys, "_MEIPASS", "") if root: - cands += [os.path.join(root, "binaries", BRIDGE_EXE), - os.path.join(root, BRIDGE_EXE)] + cands += [os.path.join(root, "binaries", exe_name), + os.path.join(root, "binaries", sidecar), + os.path.join(root, exe_name), + os.path.join(root, sidecar)] # PyInstaller onedir: daw_engine.exe nam /daw_engine/, bridge # sidecar nam canh app root (khong trong _MEIPASS) - tim ca exe_dir. exe_dir = os.path.dirname(os.path.abspath(sys.executable)) - cands += [os.path.join(exe_dir, "binaries", BRIDGE_EXE), - os.path.join(exe_dir, BRIDGE_EXE), - os.path.join(os.path.dirname(exe_dir), "binaries", BRIDGE_EXE), - os.path.join(os.path.dirname(exe_dir), BRIDGE_EXE)] - cands.append(os.path.join(settings.BASE_DIR, "install", BRIDGE_EXE)) + cands += [os.path.join(exe_dir, "binaries", exe_name), + os.path.join(exe_dir, "binaries", sidecar), + os.path.join(exe_dir, exe_name), + os.path.join(exe_dir, sidecar), + os.path.join(os.path.dirname(exe_dir), "binaries", exe_name), + os.path.join(os.path.dirname(exe_dir), "binaries", sidecar), + os.path.join(os.path.dirname(exe_dir), exe_name), + os.path.join(os.path.dirname(exe_dir), sidecar)] + # Canonical build output (build_native_bridge.ps1) + legacy install/ (dev). + cands.append(os.path.join(settings.BASE_DIR, "src-tauri", "binaries", sidecar)) + cands.append(os.path.join(settings.BASE_DIR, "install", exe_name)) for p in cands: if p and os.path.isfile(p): return p return "" +def find_fx_bridge_exe() -> str: + """Đường dẫn FX bridge (fx_vst_bridge.exe): SF_FX_BRIDGE_PATH → bundle → install/.""" + return find_bridge_exe(FX_BRIDGE_EXE, "SF_FX_BRIDGE_PATH") def normalize_audio_peak(audio, target_db=0.0): """Scale audio (numpy array, peak) về target_db nếu vượt; giữ nguyên nếu dưới. @@ -209,18 +223,18 @@ def render_fx_chain(input_wav, fx_chain, sample_rate, out_path=None) -> str: out_path; lỗi → HTTPException (501 thiếu bridge, 404 plugin lạ, 500 render). """ from app.core.vst_engine import resolve_plugin_path - exe = find_bridge_exe() + exe = find_fx_bridge_exe() if not exe: raise HTTPException( status_code=501, - detail="native_bridge không khả dụng — đặt SF_BRIDGE_PATH hoặc cài daw_vst_bridge", + detail="native_bridge không khả dụng — đặt SF_FX_BRIDGE_PATH hoặc cài fx_vst_bridge", ) slots = [] for fx in fx_chain or []: typ = fx.get("type") if typ == "vst3": p = (fx.get("path") or "").strip() - resolved = p if os.path.isfile(p) else resolve_plugin_path(p, ".vst3") + resolved = p if os.path.exists(p) else resolve_plugin_path(p, ".vst3") if not resolved: raise HTTPException( status_code=404, diff --git a/app/core/vst_engine.py b/app/core/vst_engine.py index cc627e2..1e5a7d3 100644 --- a/app/core/vst_engine.py +++ b/app/core/vst_engine.py @@ -481,8 +481,8 @@ class PluginManager: """ if not refresh and self._fx_scan_cache is not None: return self._fx_scan_cache - from app.core.native_render import find_bridge_exe - exe = find_bridge_exe() + from app.core.native_render import find_fx_bridge_exe + exe = find_fx_bridge_exe() results = [] if exe: dirs = [d for d in [self.vst_dir] + self.extra_vst_dirs @@ -629,7 +629,8 @@ def resolve_plugin_path(name_or_path: str, ext: str = "") -> str: dirs = list(_load_user_plugin_dirs()) or [] except Exception: pass - dirs += [settings.VST_DIR, settings.SOUNDFONT_DIR] + from app.config import settings as _st + dirs += [_st.VST_DIR, _st.SOUNDFONT_DIR] for base_dir in dirs: if not os.path.isdir(base_dir): continue diff --git a/app/static/js/app.precompiled.js b/app/static/js/app.precompiled.js index dd7d5f6..19c0928 100644 --- a/app/static/js/app.precompiled.js +++ b/app/static/js/app.precompiled.js @@ -577,7 +577,7 @@ const[loadingBackups,setLoadingBackups]=useState({});// project_id -> bool const[backupMaxCount,setBackupMaxCount]=useState(()=>parseInt(localStorage.getItem('sonic_backup_max_count')||'10'));const[showBackupConfig,setShowBackupConfig]=useState(false);const handleDragStart=e=>{const r=dragRef.current;r.active=true;r.startX=e.clientX;r.startY=e.clientY;r.ofsX=dragOfs.x;r.ofsY=dragOfs.y;const onMove=ev=>{if(!r.active)return;setDragOfs({x:r.ofsX+ev.clientX-r.startX,y:r.ofsY+ev.clientY-r.startY});};const onUp=()=>{r.active=false;document.removeEventListener('mousemove',onMove);document.removeEventListener('mouseup',onUp);};document.addEventListener('mousemove',onMove);document.addEventListener('mouseup',onUp);};useEffect(()=>{if(isOpen){fetchProfile();if(activeTab==='projects')fetchProjects();if(activeTab==='files')fetchFiles();}},[isOpen,activeTab]);const fetchProfile=async()=>{try{const data=await window.SonicAPI.getProfile();setProfile(data);}catch(e){setError(e.message||'Không thể tải thông tin profile');}};const fetchProjects=async()=>{setLoadingProjects(true);try{const data=await window.SonicAPI.listCloudProjects();setProjectsList(data||[]);}catch(e){showToast(e.message||'Không thể tải danh sách dự án','error');}finally{setLoadingProjects(false);}};const fetchFiles=async()=>{setLoadingFiles(true);try{const activeFileIds=tracks.map(t=>t.serverFileId).filter(Boolean);const data=await window.SonicAPI.listMyFiles(activeFileIds);setFilesList(data||[]);}catch(e){showToast(e.message||'Không thể tải danh sách tệp tin','error');}finally{setLoadingFiles(false);}};const fetchBackups=async projectId=>{setLoadingBackups(prev=>({...prev,[projectId]:true}));try{const data=await window.SonicAPI.listBackups(projectId);setBackupsMap(prev=>({...prev,[projectId]:data||[]}));}catch(e){showToast(e.message||'Không thể tải danh sách backup','error');}finally{setLoadingBackups(prev=>({...prev,[projectId]:false}));}};const handleDeleteBackup=async backupId=>{try{await window.SonicAPI.deleteBackup(backupId);setBackupsMap(prev=>{const next={...prev};Object.keys(next).forEach(pid=>{next[pid]=next[pid].filter(b=>b.id!==backupId);});return next;});fetchProjects();showToast('Đã xóa bản backup','info');}catch(e){showToast(e.message||'Lỗi xóa backup','error');}};const handleCleanupBackups=async()=>{try{const res=await window.SonicAPI.cleanupBackups(backupMaxCount);setBackupsMap({});fetchProjects();showToast(`Đã dọn dẹp ${res.deleted} bản backup cũ (giữ lại ${res.keep})`,'info');}catch(e){showToast(e.message||'Lỗi dọn dẹp backup','error');}};const handleOpenProject=async(projectId,projectName)=>{setAppWarningModal({title:"Mở dự án",message:"Bạn có muốn mở dự án này? Các thay đổi chưa lưu trên workspace hiện tại sẽ bị mất.",isAlert:false,onConfirm:async()=>{try{const proj=await window.SonicAPI.getCloudProject(projectId);const parsed=JSON.parse(proj.data_json);let restoredTracks=[];let restoredBpm=bpm;let restoredSessionTabs=[];let restoredSubTabs=[];if(parsed.main_session){const result=deserializeProjectFromSchema(parsed);restoredTracks=result.tracks;restoredBpm=result.bpm;restoredSessionTabs=result.sessionTabs;restoredSubTabs=result.subTabs;if(result.masteringSettings){setMasteringSettings(result.masteringSettings);}else{setMasteringSettings({masterConnected:false,activeModule:'eq',eqActive:true,imagerActive:true,maximizerActive:true,eqLowGain:1.5,eqMid1Gain:-1.0,eqMid2Gain:2.0,eqHighGain:1.8,w1:0,w2:115,w3:135,w4:150,imagerScale:'v2',maxGain:5.4,maxUpward:2.0,maxSoftClip:15,maxTransient:25,ceiling:-0.1,isBypassed:false,chain:DEFAULT_MASTER_CHAIN.map(m=>({...m})),compActive:false,compThreshold:-16,compRatio:3,compMakeup:0,limActive:false,limThreshold:-1.0,excActive:false,excDrive:40,rebalActive:false,rebalMid:0,rebalSide:0});}}else{restoredTracks=(parsed.tracks||[]).map(t=>{const{height:_h,...rest}=t;return{...rest,buffer:null,channelInfo:t.channelInfo||null,clips:t.clips||[],serverFileId:t.serverFileId||null};});}setTracks(restoredTracks);loadAudioBuffersForTracks(restoredTracks).catch(function(err){console.warn('loadAudioBuffersForTracks error:',err);});trackMidiChannelsRef.current={};preloadTrackInstruments(restoredTracks).catch(function(err){console.warn('preloadTrackInstruments error:',err);});setBpm(restoredBpm.toString());setSelectedTrackId(restoredTracks[0]?.id||'1');setProjectName(proj.name);setCurrentProjectId(proj.id);if(restoredSessionTabs.length>0){setSessionTabs(restoredSessionTabs);}if(restoredSubTabs.length>0){setSubTabs(restoredSubTabs);}localStorage.setItem('sonic_project_name',proj.name);localStorage.setItem('sonic_project_id',proj.id);showToast(`Đã nạp dự án "${proj.name}" thành công!`,"success");}catch(e){showToast(e.message||"Lỗi khi nạp dự án","error");}}});};const handleDeleteProject=async(projectId,e)=>{e.stopPropagation();setConfirmModal({title:"Xóa dự án Cloud",message:"Bạn có chắc chắn muốn xóa dự án này khỏi Cloud? Hành động này không thể hoàn tác.",onConfirm:async()=>{try{await window.SonicAPI.deleteCloudProject(projectId);showToast("Đã xóa dự án thành công!","success");fetchProjects();fetchProfile();}catch(err){showToast(err.message||"Lỗi khi xóa dự án","error");}}});};const handleDeleteFile=async fileId=>{if(!confirm(`Bạn có chắc chắn muốn xóa tệp tin ${fileId}?`))return;try{await window.SonicAPI.deleteMyFile(fileId);showToast("Đã xóa tệp tin thành công!","success");fetchFiles();fetchProfile();}catch(err){showToast(err.message||"Lỗi khi xóa tệp tin","error");}};const handleCleanUnusedFiles=async()=>{const unusedFiles=filesList.filter(f=>!f.is_in_use);if(unusedFiles.length===0){showToast("Không có tập tin rác nào để dọn dẹp.","info");return;}if(!confirm(`Bạn có chắc chắn muốn xóa tất cả ${unusedFiles.length} tập tin rác để giải phóng dung lượng?`))return;let successCount=0;for(const file of unusedFiles){try{await window.SonicAPI.deleteMyFile(file.file_id);successCount++;}catch(e){console.error("Lỗi xóa file rác: ",file.file_id,e);}}showToast(`Đã dọn dẹp thành công ${successCount}/${unusedFiles.length} tập tin rác!`,"success");fetchFiles();fetchProfile();};const handleChangePassword=async e=>{e.preventDefault();setMsg('');setError('');setLoading(true);try{const res=await window.SonicAPI.changePassword(oldPassword,newPassword);setMsg(res.message||'Đổi mật khẩu thành công!');setOldPassword('');setNewPassword('');}catch(err){setError(err.message||'Lỗi khi đổi mật khẩu');}finally{setLoading(false);}};const modalStyle={left:`calc(50% + ${dragOfs.x}px)`,top:`calc(50% + ${dragOfs.y}px)`,transform:'translate(-50%, -50%)'};return/*#__PURE__*/React.createElement(React.Fragment,null,/*#__PURE__*/React.createElement("div",{key:"backdrop",className:"fixed inset-0 z-40 bg-black/70 backdrop-blur-sm",onClick:onClose}),confirmModal&&/*#__PURE__*/React.createElement("div",{key:"confirm-overlay",className:"fixed inset-0 z-[60] flex items-center justify-center bg-black/50"},/*#__PURE__*/React.createElement("div",{className:"bg-[#262626] border border-[#383838] rounded-lg shadow-2xl p-5 max-w-sm w-full text-slate-200"},/*#__PURE__*/React.createElement("h4",{className:"text-sm font-bold text-rose-400 mb-2"},confirmModal.title),/*#__PURE__*/React.createElement("p",{className:"text-xs text-slate-300 mb-4"},confirmModal.message),/*#__PURE__*/React.createElement("div",{className:"flex justify-end gap-2"},/*#__PURE__*/React.createElement("button",{onClick:()=>setConfirmModal(null),className:"px-3 py-1.5 bg-zinc-700 hover:bg-zinc-600 text-slate-300 rounded text-xs font-semibold"},confirmModal.cancelText||"Hủy"),/*#__PURE__*/React.createElement("button",{onClick:()=>{const fn=confirmModal.onConfirm;setConfirmModal(null);fn();},className:"px-3 py-1.5 bg-rose-700 hover:bg-rose-600 text-white rounded text-xs font-semibold"},confirmModal.confirmText||"Xác nhận xóa")))),/*#__PURE__*/React.createElement("div",{key:"dialog",className:"fixed z-50 bg-[#262626] border border-[#383838] rounded-xl shadow-2xl w-full max-w-2xl p-6 text-slate-200 flex flex-col max-h-[85vh]",style:modalStyle},/*#__PURE__*/React.createElement("div",{className:"flex justify-between items-center pb-3 border-b border-[#383838] shrink-0 cursor-grab active:cursor-grabbing select-none",onMouseDown:handleDragStart},/*#__PURE__*/React.createElement("h3",{className:"text-md font-bold text-teal-400 flex items-center gap-1.5"},"👤 Hồ Sơ Cá Nhân & Quản Lý Dự Án"),/*#__PURE__*/React.createElement("button",{onClick:onClose,className:"text-slate-400 hover:text-slate-200"},"✕")),/*#__PURE__*/React.createElement("div",{className:"flex gap-2 border-b border-[#383838] py-2 shrink-0 text-xs font-semibold"},[/*#__PURE__*/React.createElement("button",{key:"tab-acc",onClick:()=>setActiveTab('account'),className:`px-3 py-1.5 rounded transition ${activeTab==='account'?'bg-teal-950/60 text-teal-300 border border-teal-800':'text-slate-400 hover:text-slate-200'}`},"Tài Khoản"),/*#__PURE__*/React.createElement("button",{key:"tab-proj",onClick:()=>setActiveTab('projects'),className:`px-3 py-1.5 rounded transition ${activeTab==='projects'?'bg-teal-950/60 text-teal-300 border border-teal-800':'text-slate-400 hover:text-slate-200'}`},"Dự Án Cloud"),/*#__PURE__*/React.createElement("button",{key:"tab-files",onClick:()=>setActiveTab('files'),className:`px-3 py-1.5 rounded transition ${activeTab==='files'?'bg-teal-950/60 text-teal-300 border border-teal-800':'text-slate-400 hover:text-slate-200'}`},"Tập Tin Của Tôi")]),/*#__PURE__*/React.createElement("div",{className:"flex-1 overflow-y-auto pt-4 space-y-4 pr-1 min-h-[300px]"},activeTab==='account'&&profile?[/*#__PURE__*/React.createElement("div",{key:"quota-info",className:"bg-[#1e1e1e] p-4 rounded-lg border border-[#333] grid grid-cols-2 gap-4 text-xs"},[/*#__PURE__*/React.createElement("div",{key:"username"},[/*#__PURE__*/React.createElement("span",{className:"text-slate-500 block"},"Tên người dùng"),/*#__PURE__*/React.createElement("span",{className:"font-bold text-teal-300 text-sm"},profile.username)]),/*#__PURE__*/React.createElement("div",{key:"role"},[/*#__PURE__*/React.createElement("span",{className:"text-slate-500 block"},"Vai trò"),/*#__PURE__*/React.createElement("span",{className:"uppercase font-semibold text-amber-400"},profile.role)]),/*#__PURE__*/React.createElement("div",{key:"email"},[/*#__PURE__*/React.createElement("span",{className:"text-slate-500 block"},"Email"),/*#__PURE__*/React.createElement("span",null,profile.email)]),/*#__PURE__*/React.createElement("div",{key:"quota"},[/*#__PURE__*/React.createElement("span",{className:"text-slate-500 block"},"Dung lượng Quota"),/*#__PURE__*/React.createElement("span",{className:"font-semibold text-slate-200"},`${profile.quota.used_mb} MB / ${profile.quota.storage_limit_mb} MB`)])]),/*#__PURE__*/React.createElement("div",{key:"progress"},[/*#__PURE__*/React.createElement("div",{className:"flex justify-between text-xs mb-1"},[/*#__PURE__*/React.createElement("span",{className:"text-slate-400"},"Tiến trình sử dụng bộ nhớ Server"),/*#__PURE__*/React.createElement("span",{className:"font-bold text-teal-400"},`${(profile.quota.used_mb/profile.quota.storage_limit_mb*100).toFixed(1)}%`)]),/*#__PURE__*/React.createElement("div",{className:"w-full h-2 bg-slate-800 rounded-full overflow-hidden"},[/*#__PURE__*/React.createElement("div",{className:"h-full bg-teal-500 rounded-full transition-all duration-300",style:{width:`${Math.min(100,profile.quota.used_mb/profile.quota.storage_limit_mb*100)}%`}})])]),/*#__PURE__*/React.createElement("form",{key:"pwd-form",onSubmit:handleChangePassword,className:"pt-4 border-t border-[#383838] space-y-3"},[/*#__PURE__*/React.createElement("h4",{className:"text-xs font-bold text-slate-300 uppercase"},"Thay Đổi Mật Khẩu"),msg&&/*#__PURE__*/React.createElement("div",{className:"p-2 bg-emerald-950/60 border border-emerald-700 text-emerald-300 rounded text-xs"},msg),error&&/*#__PURE__*/React.createElement("div",{className:"p-2 bg-red-950/60 border border-red-700 text-red-300 rounded text-xs"},error),/*#__PURE__*/React.createElement("div",{key:"old"},[/*#__PURE__*/React.createElement("label",{className:"block text-xs text-slate-400 mb-1"},"Mật khẩu cũ"),/*#__PURE__*/React.createElement("input",{type:"password",autoComplete:"current-password",required:true,value:oldPassword,onChange:e=>setOldPassword(e.target.value),className:"w-full bg-[#1e1e1e] border border-[#383838] rounded px-3 py-1.5 text-xs focus:outline-none focus:border-teal-500"})]),/*#__PURE__*/React.createElement("div",{key:"new"},[/*#__PURE__*/React.createElement("label",{className:"block text-xs text-slate-400 mb-1"},"Mật khẩu mới"),/*#__PURE__*/React.createElement("input",{type:"password",autoComplete:"new-password",required:true,value:newPassword,onChange:e=>setNewPassword(e.target.value),className:"w-full bg-[#1e1e1e] border border-[#383838] rounded px-3 py-1.5 text-xs focus:outline-none focus:border-teal-500"})]),/*#__PURE__*/React.createElement("button",{type:"submit",disabled:loading,className:"w-full py-1.5 bg-teal-600 hover:bg-teal-500 text-white font-semibold text-xs rounded transition"},loading?'Đang cập nhật...':'Cập Nhật Mật Khẩu')])]:activeTab==='projects'?[loadingProjects?/*#__PURE__*/React.createElement("div",{key:"loading",className:"text-center py-8 text-xs text-zinc-500"},"Đang tải danh sách dự án..."):projectsList.length===0?/*#__PURE__*/React.createElement("div",{key:"empty",className:"text-center py-8 text-xs text-zinc-500"},"Bạn chưa có dự án nào lưu trên Cloud."):/*#__PURE__*/React.createElement(React.Fragment,{key:"list"},[/*#__PURE__*/React.createElement("div",{key:"backup-config-bar",className:"flex items-center justify-between bg-zinc-900/60 p-2 rounded border border-zinc-800 mb-2 text-xs"},[/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-2"},[/*#__PURE__*/React.createElement("span",{className:"text-zinc-400 text-[10px]"},"⚙️ Tự động lưu 5 phút / Backup 30 phút"),/*#__PURE__*/React.createElement("button",{onClick:e=>{e.stopPropagation();setShowBackupConfig(!showBackupConfig);},className:"px-2 py-0.5 bg-zinc-800 hover:bg-zinc-700 text-zinc-300 rounded text-[10px] border border-zinc-700"},showBackupConfig?"ẨN":"CẤU HÌNH")]),/*#__PURE__*/React.createElement("button",{onClick:e=>{e.stopPropagation();handleCleanupBackups();},className:"px-2 py-0.5 bg-amber-800 hover:bg-amber-700 text-amber-200 rounded text-[10px] border border-amber-700"},"🧹 DỌN BACKUP")]),showBackupConfig&&/*#__PURE__*/React.createElement("div",{key:"backup-config-detail",className:"bg-[#18181b] border border-zinc-800 rounded p-3 mb-2 text-xs"},[/*#__PURE__*/React.createElement("div",{className:"flex items-center justify-between mb-2"},[/*#__PURE__*/React.createElement("label",{className:"text-zinc-300 font-semibold"},"Số bản backup tối đa:"),/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-2"},[/*#__PURE__*/React.createElement("input",{type:"range",min:5,max:20,value:backupMaxCount,onChange:e=>{const v=parseInt(e.target.value);setBackupMaxCount(v);localStorage.setItem('sonic_backup_max_count',v.toString());},className:"w-24 accent-amber-500"}),/*#__PURE__*/React.createElement("span",{className:"text-amber-400 font-bold w-6 text-center"},backupMaxCount)])]),/*#__PURE__*/React.createElement("p",{className:"text-[10px] text-zinc-500"},"Mỗi dự án sẽ giữ tối đa số bản backup này. Backup cũ nhất sẽ tự động bị xóa khi vượt quá giới hạn.")]),/*#__PURE__*/React.createElement("div",{className:"space-y-1.5"},projectsList.map(proj=>/*#__PURE__*/React.createElement("div",{key:proj.id},[/*#__PURE__*/React.createElement("div",{onClick:()=>handleOpenProject(proj.id),className:"flex items-center justify-between p-3 bg-[#1e1e1e] hover:bg-zinc-800 rounded border border-zinc-800 transition cursor-pointer text-xs group"},[/*#__PURE__*/React.createElement("div",{key:"meta"},[/*#__PURE__*/React.createElement("div",{className:"font-bold text-slate-200 group-hover:text-teal-400"},proj.name),/*#__PURE__*/React.createElement("div",{className:"text-[10px] text-zinc-500 mt-0.5"},[`Dung lượng: ${proj.size_mb} MB | Cập nhật: ${new Date(proj.updated_at*1000).toLocaleString()}`,proj.backup_count>0&&` | Backup: ${proj.backup_count}`])]),/*#__PURE__*/React.createElement("div",{key:"actions",className:"flex items-center gap-1.5"},[/*#__PURE__*/React.createElement("button",{onClick:e=>{e.stopPropagation();if(expandedBackupId===proj.id){setExpandedBackupId(null);}else{setExpandedBackupId(proj.id);fetchBackups(proj.id);}},className:`px-2 py-1 rounded font-semibold text-[10px] border ${expandedBackupId===proj.id?'bg-amber-800/80 text-amber-200 border-amber-700':'bg-zinc-800 hover:bg-zinc-700 text-zinc-400 border-zinc-700'}`},`Backup (${proj.backup_count})`),/*#__PURE__*/React.createElement("button",{onClick:e=>{e.stopPropagation();handleOpenProject(proj.id);},className:"px-2.5 py-1 bg-teal-800/80 hover:bg-teal-700 text-teal-200 rounded font-semibold text-[10px]"},"MỞ"),/*#__PURE__*/React.createElement("button",{onClick:e=>handleDeleteProject(proj.id,e),className:"px-2 py-1 bg-rose-950/60 hover:bg-rose-900/80 text-rose-300 rounded font-semibold text-[10px]"},"XÓA")])]),expandedBackupId===proj.id&&/*#__PURE__*/React.createElement("div",{key:"backup-list",className:"ml-4 pl-3 border-l-2 border-amber-800/50 bg-[#161618] rounded-b p-2 mb-1"},[loadingBackups[proj.id]?/*#__PURE__*/React.createElement("div",{className:"text-[10px] text-zinc-500 py-2 text-center"},"Đang tải..."):!backupsMap[proj.id]||backupsMap[proj.id].length===0?/*#__PURE__*/React.createElement("div",{className:"text-[10px] text-zinc-500 py-2 text-center"},"Chưa có bản backup nào."):/*#__PURE__*/React.createElement("div",{className:"space-y-1 max-h-48 overflow-y-auto"},backupsMap[proj.id].map(b=>/*#__PURE__*/React.createElement("div",{key:b.id,className:"flex items-center justify-between py-1.5 px-2 bg-[#1e1e22] rounded border border-zinc-800"},[/*#__PURE__*/React.createElement("div",{key:"info",className:"flex-1 min-w-0"},[/*#__PURE__*/React.createElement("div",{className:"text-[10px] text-zinc-300 truncate"},b.name),/*#__PURE__*/React.createElement("div",{className:"text-[9px] text-zinc-500 mt-0.5"},`${b.size_mb} MB | ${new Date(b.created_at*1000).toLocaleString()}`)]),/*#__PURE__*/React.createElement("button",{onClick:e=>{e.stopPropagation();handleDeleteBackup(b.id);},className:"px-1.5 py-0.5 bg-rose-950 hover:bg-rose-900 text-rose-300 rounded text-[9px] border border-rose-900 ml-2 shrink-0"},"XÓA")])))])])))])]:activeTab==='files'?[/*#__PURE__*/React.createElement("div",{key:"cleanup-header",className:"flex items-center justify-between bg-zinc-900/60 p-2.5 rounded border border-zinc-800 text-xs shrink-0 mb-3"},[/*#__PURE__*/React.createElement("span",{className:"text-zinc-400 text-[10px]"},"💡 Các tập tin không dùng trong dự án nào sẽ được đánh dấu 'Không dùng' và có thể xóa để giải phóng dung lượng."),/*#__PURE__*/React.createElement("button",{onClick:handleCleanUnusedFiles,className:"px-2.5 py-1 bg-rose-700 hover:bg-rose-600 text-white rounded font-bold text-[10px] uppercase flex items-center gap-1"},"🧹 Dọn dẹp tệp rác")]),loadingFiles?/*#__PURE__*/React.createElement("div",{key:"loading",className:"text-center py-8 text-xs text-zinc-500"},"Đang tải danh sách tập tin..."):filesList.length===0?/*#__PURE__*/React.createElement("div",{key:"empty",className:"text-center py-8 text-xs text-zinc-500"},"Chưa có tập tin nào tải lên hoặc tạo ra."):/*#__PURE__*/React.createElement("div",{key:"list",className:"space-y-1.5"},[filesList.map(file=>/*#__PURE__*/React.createElement("div",{key:file.file_id,className:"flex items-center justify-between p-3 bg-[#1e1e1e] rounded border border-zinc-800 text-xs"},[/*#__PURE__*/React.createElement("div",{key:"meta",className:"max-w-[70%]"},[/*#__PURE__*/React.createElement("div",{className:"font-semibold text-slate-300 truncate"},file.original_name||file.file_id),/*#__PURE__*/React.createElement("div",{className:"text-[10px] text-zinc-500 mt-0.5"},`Loại: ${file.type} | Dung lượng: ${file.size_mb} MB | Tạo lúc: ${new Date(file.created_at*1000).toLocaleString()}`)]),/*#__PURE__*/React.createElement("div",{key:"actions",className:"flex items-center gap-2"},[file.is_in_use?/*#__PURE__*/React.createElement("span",{className:"px-2 py-0.5 bg-emerald-950 text-emerald-400 border border-emerald-900 rounded text-[9px] uppercase font-bold font-mono"},"Đang dùng"):/*#__PURE__*/React.createElement("span",{className:"px-2 py-0.5 bg-zinc-850 text-zinc-400 border border-zinc-700 rounded text-[9px] uppercase font-bold font-mono"},"Không dùng"),!file.is_in_use&&/*#__PURE__*/React.createElement("button",{onClick:()=>handleDeleteFile(file.file_id),className:"px-2 py-0.5 bg-rose-950 hover:bg-rose-900 text-rose-300 rounded font-semibold text-[10px] border border-rose-900"},"Xóa")])]))])]:null)));};const SaveProjectModal=({isOpen,onClose,onSaveCloud,onSaveLocal,projectName})=>{if(!isOpen)return null;const[name,setName]=useState(projectName||'');const[saveType,setSaveType]=useState(localStorage.getItem('sonic_token')?'cloud':'local');const[cloudProjects,setCloudProjects]=useState([]);const[loading,setLoading]=useState(false);const[selectedExisting,setSelectedExisting]=useState(null);const[confirmOverwriteProject,setConfirmOverwriteProject]=useState(null);React.useEffect(function(){if(!isOpen)return;if(saveType==='cloud'&&window.SonicAPI&&window.SonicAPI.listCloudProjects){setLoading(true);window.SonicAPI.listCloudProjects().then(function(data){setCloudProjects(data||[]);}).catch(function(){setCloudProjects([]);}).finally(function(){setLoading(false);});}},[isOpen,saveType]);const handleSubmit=e=>{e.preventDefault();if(!name.trim())return;var matched=null;for(var i=0;i{if(!isOpen)return null;const[tab,setTab]=useState('cloud');const[projects,setProjects]=useState([]);const[loading,setLoading]=useState(false);React.useEffect(function(){if(!isOpen)return;if(tab==='cloud'){setLoading(true);var api=window.SonicAPI;if(api&&api.listCloudProjects){api.listCloudProjects().then(function(data){setProjects(data||[]);}).catch(function(){setProjects([]);}).finally(function(){setLoading(false);});}else{setLoading(false);}}},[isOpen,tab]);return React.createElement("div",{className:"fixed inset-0 z-50 flex items-center justify-center bg-black/70 backdrop-blur-sm"},React.createElement("div",{className:"bg-[#262626] border border-[#383838] rounded-xl shadow-2xl w-full max-w-lg p-6 text-slate-200"},React.createElement("h3",{className:"text-sm font-bold text-teal-400 mb-4 uppercase"},"Mở dự án"),React.createElement("div",{className:"grid grid-cols-2 gap-2 text-xs mb-4"},[React.createElement("button",{key:"cloud-tab",type:"button",onClick:function(){setTab('cloud');},className:"py-2 rounded border flex flex-col items-center gap-1 font-semibold transition "+(tab==='cloud'?'bg-teal-950/60 border-teal-500 text-teal-300':'bg-zinc-800 border-zinc-700 hover:bg-zinc-750 text-zinc-400')},[React.createElement("span",{key:"t"},"☁️ Cloud"),React.createElement("span",{key:"d",className:"text-[9px] font-normal text-zinc-500"},"Dự án trên server")]),React.createElement("button",{key:"local-tab",type:"button",onClick:function(){setTab('local');},className:"py-2 rounded border flex flex-col items-center gap-1 font-semibold transition "+(tab==='local'?'bg-teal-950/60 border-teal-500 text-teal-300':'bg-zinc-800 border-zinc-700 hover:bg-zinc-750 text-zinc-400')},[React.createElement("span",{key:"t"},"💾 Local"),React.createElement("span",{key:"d",className:"text-[9px] font-normal text-zinc-500"},"Tập tin .sfs trên máy")])]),tab==='cloud'?React.createElement("div",{className:"space-y-1.5 max-h-64 overflow-y-auto"},loading?[React.createElement("div",{key:"l",className:"text-center py-8 text-xs text-zinc-500"},"Đang tải danh sách dự án...")]:projects.length===0?[React.createElement("div",{key:"e",className:"text-center py-8 text-xs text-zinc-500"},"Bạn chưa có dự án nào trên Cloud.")]:projects.map(function(p){return React.createElement("div",{key:p.id,onClick:function(){onOpenCloud(p.id,p.name);},className:"flex items-center justify-between p-3 bg-[#1e1e1e] hover:bg-zinc-800 rounded border border-zinc-800 transition cursor-pointer text-xs group"},[React.createElement("div",{key:"meta"},[React.createElement("div",{className:"font-bold text-slate-200 group-hover:text-teal-400"},p.name),React.createElement("div",{className:"text-[10px] text-zinc-500 mt-0.5"},"Dung lượng: "+(p.size_mb||0)+" MB | Cập nhật: "+new Date((p.updated_at||0)*1000).toLocaleString())]),React.createElement("button",{onClick:function(e){e.stopPropagation();onOpenCloud(p.id,p.name);},className:"px-2.5 py-1 bg-teal-800/80 hover:bg-teal-700 text-teal-200 rounded font-semibold text-[10px]"},"MỞ")]);})):React.createElement("div",{className:"py-4 text-center text-xs text-zinc-400 space-y-3"},[React.createElement("div",{key:"d",className:"text-zinc-500"},"Chọn tệp .sfs để mở dự án từ Local."),React.createElement("button",{key:"b",onClick:function(){onOpenLocal();},className:"px-4 py-2 bg-teal-600 hover:bg-teal-500 text-white rounded font-semibold text-xs transition"},"Chọn tệp .sfs ...")]),React.createElement("div",{className:"flex justify-end gap-2 text-xs mt-4 pt-3 border-t border-zinc-800"},React.createElement("button",{type:"button",onClick:onClose,className:"px-3 py-1.5 bg-zinc-800 hover:bg-zinc-700 text-zinc-300 rounded"},"Hủy"))));};const SaveAsModal=({isOpen,onClose,projectName,onSaveCloud,onSaveLocal})=>{if(!isOpen)return null;const[name,setName]=useState(projectName||'');const[saveType,setSaveType]=useState('cloud');const handleSubmit=e=>{e.preventDefault();if(!name.trim())return;if(saveType==='cloud'){onSaveCloud(name.trim());}else{onSaveLocal(name.trim());}onClose();};return/*#__PURE__*/React.createElement("div",{className:"fixed inset-0 z-50 flex items-center justify-center bg-black/70 backdrop-blur-sm"},/*#__PURE__*/React.createElement("div",{className:"bg-[#262626] border border-[#383838] rounded-xl shadow-2xl w-full max-w-md p-6 text-slate-200"},/*#__PURE__*/React.createElement("h3",{className:"text-sm font-bold text-teal-400 mb-4 uppercase"},"Lưu dưới tên khác (Save As...)"),/*#__PURE__*/React.createElement("form",{onSubmit:handleSubmit,className:"space-y-4"},[/*#__PURE__*/React.createElement("div",{key:"name-block"},[/*#__PURE__*/React.createElement("label",{className:"block text-[10px] text-zinc-400 uppercase font-bold mb-1"},"Tên dự án mới"),/*#__PURE__*/React.createElement("input",{type:"text",placeholder:"Nhập tên mới...",required:true,value:name,onChange:e=>setName(e.target.value),className:"w-full bg-[#1e1e1e] border border-[#383838] rounded px-3 py-1.5 text-xs text-slate-200 focus:outline-none focus:border-teal-500 font-bold",autoFocus:true})]),/*#__PURE__*/React.createElement("div",{key:"type-block"},[/*#__PURE__*/React.createElement("label",{className:"block text-[10px] text-zinc-400 uppercase font-bold mb-1.5"},"Phương thức lưu trữ"),/*#__PURE__*/React.createElement("div",{className:"grid grid-cols-2 gap-2 text-xs"},[/*#__PURE__*/React.createElement("button",{key:"btn-cloud",type:"button",onClick:()=>setSaveType('cloud'),className:`py-2 rounded border flex flex-col items-center gap-1 font-semibold transition ${saveType==='cloud'?'bg-teal-950/60 border-teal-500 text-teal-300':'bg-zinc-800 border-zinc-700 hover:bg-zinc-750 text-zinc-400'}`},[/*#__PURE__*/React.createElement("span",{key:"title"},"☁️ Lưu Cloud"),/*#__PURE__*/React.createElement("span",{key:"desc",className:"text-[9px] font-normal text-zinc-500"},"Lưu lên server cá nhân")]),/*#__PURE__*/React.createElement("button",{key:"btn-local",type:"button",onClick:()=>setSaveType('local'),className:`py-2 rounded border flex flex-col items-center gap-1 font-semibold transition ${saveType==='local'?'bg-teal-950/60 border-teal-500 text-teal-300':'bg-zinc-800 border-zinc-700 hover:bg-zinc-750 text-zinc-400'}`},[/*#__PURE__*/React.createElement("span",{key:"title"},"💾 Tải về máy (.sfs)"),/*#__PURE__*/React.createElement("span",{key:"desc",className:"text-[9px] font-normal text-zinc-500"},"Tải tệp JSON dự án về máy")])])]),/*#__PURE__*/React.createElement("div",{key:"actions",className:"flex justify-end gap-2 text-xs pt-2"},[/*#__PURE__*/React.createElement("button",{key:"cancel",type:"button",onClick:onClose,className:"px-3 py-1.5 bg-zinc-800 hover:bg-zinc-700 text-zinc-300 rounded"},"Hủy"),/*#__PURE__*/React.createElement("button",{key:"save",type:"submit",className:"px-3 py-1.5 bg-teal-600 hover:bg-teal-500 text-white rounded font-semibold"},"Thực hiện lưu")])])));};const SystemManagerModal=({isOpen,onClose})=>{if(!isOpen)return null;const[users,setUsers]=useState([]);const[loading,setLoading]=useState(true);const[msg,setMsg]=useState('');const[error,setError]=useState('');const[editingQuotaUser,setEditingQuotaUser]=useState(null);const[newQuotaMb,setNewQuotaMb]=useState(500);useEffect(()=>{if(isOpen)loadUsers();},[isOpen]);const loadUsers=async()=>{setLoading(true);setError('');try{const data=await window.SonicAPI.listUsers();setUsers(data);}catch(err){setError(err.message||'Không thể tải danh sách người dùng hệ thống');}finally{setLoading(false);}};const handleSaveQuota=async userId=>{try{await window.SonicAPI.updateUserQuota(userId,parseInt(newQuotaMb));setMsg('Đã cập nhật hạn mức Quota thành công!');setEditingQuotaUser(null);loadUsers();}catch(err){setError(err.message||'Lỗi cập nhật Quota');}};const handleToggleRole=async user=>{const nextRole=user.role==='admin'?'standard':'admin';try{await window.SonicAPI.updateUserRole(user.id,nextRole,user.is_active);setMsg(`Đã đổi vai trò người dùng ${user.username} thành ${nextRole}`);loadUsers();}catch(err){setError(err.message||'Lỗi cập nhật vai trò');}};const handleDeleteUser=async userId=>{if(!confirm('Bạn có chắc chắn muốn xóa người dùng này khỏi hệ thống?'))return;try{await window.SonicAPI.deleteUser(userId);setMsg('Đã xóa người dùng thành công');loadUsers();}catch(err){setError(err.message||'Lỗi khi xóa người dùng');}};return/*#__PURE__*/React.createElement("div",{className:"fixed inset-0 z-50 flex items-center justify-center bg-black/70 backdrop-blur-sm"},/*#__PURE__*/React.createElement("div",{className:"bg-[#262626] border border-[#383838] rounded-xl shadow-2xl w-full max-w-4xl p-6 text-slate-200"},/*#__PURE__*/React.createElement("div",{className:"flex justify-between items-center pb-4 border-b border-[#383838]"},/*#__PURE__*/React.createElement("h3",{className:"text-lg font-bold text-amber-400"},"⚙️ Quản Lý Hệ Thống & Phân Quyền Admin"),/*#__PURE__*/React.createElement("button",{onClick:onClose,className:"text-slate-400 hover:text-slate-200"},"✕")),msg&&/*#__PURE__*/React.createElement("div",{className:"mt-3 p-2 bg-emerald-950/60 border border-emerald-700 text-emerald-300 rounded text-xs"},msg),error&&/*#__PURE__*/React.createElement("div",{className:"mt-3 p-2 bg-red-950/60 border border-red-700 text-red-300 rounded text-xs"},error),/*#__PURE__*/React.createElement("div",{className:"mt-4 overflow-x-auto max-h-96 no-scrollbar"},loading?/*#__PURE__*/React.createElement("div",{className:"py-8 text-center text-slate-400 text-xs"},"Đang tải thông tin hệ thống..."):/*#__PURE__*/React.createElement("table",{className:"w-full text-left text-xs border-collapse"},/*#__PURE__*/React.createElement("thead",null,/*#__PURE__*/React.createElement("tr",{className:"border-b border-[#383838] text-slate-400 bg-[#1e1e1e]"},/*#__PURE__*/React.createElement("th",{className:"p-3"},"Tên Người Dùng"),/*#__PURE__*/React.createElement("th",{className:"p-3"},"Email"),/*#__PURE__*/React.createElement("th",{className:"p-3"},"Vai Trò"),/*#__PURE__*/React.createElement("th",{className:"p-3"},"Dung Lượng Sử Dụng"),/*#__PURE__*/React.createElement("th",{className:"p-3"},"Hạn Mức Quota"),/*#__PURE__*/React.createElement("th",{className:"p-3 text-right"},"Thao Tác"))),/*#__PURE__*/React.createElement("tbody",{className:"divide-y divide-[#333]"},users.map(u=>/*#__PURE__*/React.createElement("tr",{key:u.id,className:"hover:bg-[#2e2e2e]"},/*#__PURE__*/React.createElement("td",{className:"p-3 font-semibold text-teal-300"},u.username,u.must_change_password&&/*#__PURE__*/React.createElement("span",{className:"ml-2 text-xs bg-amber-900/60 text-amber-300 px-1.5 py-0.5 rounded"},"Mật khẩu gốc")),/*#__PURE__*/React.createElement("td",{className:"p-3 text-slate-300"},u.email),/*#__PURE__*/React.createElement("td",{className:"p-3 uppercase font-bold text-amber-400"},u.role),/*#__PURE__*/React.createElement("td",{className:"p-3"},u.used_mb," MB"),/*#__PURE__*/React.createElement("td",{className:"p-3"},editingQuotaUser===u.id?/*#__PURE__*/React.createElement("div",{className:"flex items-center space-x-1"},/*#__PURE__*/React.createElement("input",{type:"number",value:newQuotaMb,onChange:e=>setNewQuotaMb(e.target.value),className:"w-16 bg-[#1e1e1e] border border-[#444] rounded px-1 py-0.5 text-xs text-slate-200"}),/*#__PURE__*/React.createElement("span",null,"MB"),/*#__PURE__*/React.createElement("button",{onClick:()=>handleSaveQuota(u.id),className:"px-2 py-0.5 bg-teal-600 rounded text-xs"},"Lưu")):/*#__PURE__*/React.createElement("span",{className:"font-semibold"},u.quota_mb," MB")),/*#__PURE__*/React.createElement("td",{className:"p-3 text-right space-x-2"},/*#__PURE__*/React.createElement("button",{onClick:()=>{setEditingQuotaUser(u.id);setNewQuotaMb(u.quota_mb);},className:"px-2 py-1 bg-slate-700 hover:bg-slate-600 rounded text-xs"},"Sửa Quota"),/*#__PURE__*/React.createElement("button",{onClick:()=>handleToggleRole(u),className:"px-2 py-1 bg-amber-700/60 hover:bg-amber-600 rounded text-xs"},"Đổi Role"),u.role!=='admin'&&/*#__PURE__*/React.createElement("button",{onClick:()=>handleDeleteUser(u.id),className:"px-2 py-1 bg-red-700/60 hover:bg-red-600 rounded text-xs"},"Xóa")))))))));};/* ═══════════════════════════════════════════════════════════════════ ABOUT / HELP / PREFERENCES MODALS (menu Help + Tools → Preferences) ═══════════════════════════════════════════════════════════════════ */const APP_VERSION='1.0.0';// khớp src-tauri/tauri.conf.json -const AboutModal=({isOpen,onClose})=>{if(!isOpen)return null;return/*#__PURE__*/React.createElement("div",{className:"fixed inset-0 z-[9999] flex items-center justify-center bg-black/70 backdrop-blur-sm",onClick:onClose},/*#__PURE__*/React.createElement("div",{className:"bg-[#1e1e24] border border-zinc-700 rounded-xl shadow-2xl w-full max-w-md p-6 text-slate-200",onClick:e=>e.stopPropagation()},/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-3 mb-4"},/*#__PURE__*/React.createElement("img",{src:"/favicon.svg",alt:"SonicForge Studio",className:"w-12 h-12 rounded-lg bg-zinc-900 border border-zinc-700 shadow-lg object-contain p-0.5"}),/*#__PURE__*/React.createElement("div",null,/*#__PURE__*/React.createElement("h2",{className:"text-lg font-bold text-white"},"SonicForge Studio"),/*#__PURE__*/React.createElement("p",{className:"text-xs text-zinc-400"},"Professional DAW — v",APP_VERSION))),/*#__PURE__*/React.createElement("div",{className:"space-y-2 text-sm"},/*#__PURE__*/React.createElement("p",{className:"text-zinc-300 leading-relaxed"},"Phần mềm sản xuất âm nhạc (DAW) — soạn nhạc, ghi âm, chỉnh sửa MIDI/Audio, SoundFont & VST, trộn và master."),/*#__PURE__*/React.createElement("div",{className:"pt-2 border-t border-zinc-800 space-y-1 text-xs"},/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-2"},/*#__PURE__*/React.createElement("span",{className:"text-zinc-500 w-20 shrink-0"},"Developer"),/*#__PURE__*/React.createElement("span",{className:"text-cyan-400 font-semibold"},"Lộc Phạm")),/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-2"},/*#__PURE__*/React.createElement("span",{className:"text-zinc-500 w-20 shrink-0"},"Email"),/*#__PURE__*/React.createElement("a",{href:"mailto:tranloclqd@gmail.com",className:"text-cyan-400 hover:underline"},"tranloclqd@gmail.com")),/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-2"},/*#__PURE__*/React.createElement("span",{className:"text-zinc-500 w-20 shrink-0"},"Version"),/*#__PURE__*/React.createElement("span",{className:"text-zinc-300"},APP_VERSION)),/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-2"},/*#__PURE__*/React.createElement("span",{className:"text-zinc-500 w-20 shrink-0"},"Build"),/*#__PURE__*/React.createElement("span",{className:"text-zinc-300"},"Standalone (Tauri v2 + PyInstaller)")))),/*#__PURE__*/React.createElement("div",{className:"mt-5 flex justify-end"},/*#__PURE__*/React.createElement("button",{onClick:onClose,className:"px-4 py-1.5 bg-cyan-700 hover:bg-cyan-600 rounded text-xs font-bold text-white transition"},"Đóng"))));};const HelpModal=({isOpen,onClose,lang})=>{if(!isOpen)return null;const vi=lang!=='en';const sections=vi?[{title:'🚀 Bắt đầu nhanh',body:'Nhấn phím Cách (Space) để Play/Pause. Nhấn phím / để dừng. Dùng nút Record (●) để ghi âm/MIDI. Tạo track mới từ menu Track hoặc nút "+ Add Track".'},{title:'🎹 MIDI & ARM',body:'Bật nút ARM đỏ trên track để nhận phím từ MIDI keyboard. Vào menu Tools → MIDI Devices để chọn thiết bị. Khi ARM + bấm phím, âm preview phát và VU meter nhảy theo trường độ.'},{title:'🎼 Piano Roll',body:'Nhấp đúp vào MIDI item để mở Piano Roll. Dùng Ctrl+scroll hoặc nút +/− để zoom. Nhấp để vẽ note, kéo để di chuyển, Ctrl+kéo để copy. Phím tắt: C (vẽ), E (tẩy), Space (nghe).'},{title:'🎛️ FX & Master',body:'Chọn track → nút FX để mở FX Rack (thêm EQ, compressor...). Bấm nút PWR ở Master để mở Mastering Panel: EQ 4 băng, compressor, limiter. Mọi thay đổi áp dụng realtime.'},{title:'🧪 SoundFont & VST',body:'Vào Tools → Plugin Manager để quét SoundFont (.sf2/.sf3) và VSTi. Track MIDI dùng SoundFont làm nhạc cụ — chọn instrument từ nút Synth trên track.'},{title:'🤖 AI',body:'Tools → Config AI Providers để cấu hình API (OpenAI, Gemini, Ollama...). Dùng AI Prompt Generator (nút ✨) để sinh MIDI/ý tưởng; AI MIDI Preset Manager để lưu preset.'},{title:'💾 Lưu & Xuất',body:'Ctrl+S lưu project (cloud/temp), Ctrl+Shift+S Save As. Export WAV qua nút Export — chọn vùng, format, bitrate rồi Render.'},{title:'⌨️ Phím tắt',body:'Space: Play/Pause · /: Stop · Ctrl+Z/Y: Undo/Redo · Ctrl+C/X/V: Copy/Cut/Paste · Ctrl+S: Save · Ctrl+N: New · S: Split · Ctrl+E: Edit in new tab · Ctrl+wheel: zoom timeline'}]:[{title:'🚀 Quick start',body:'Press Space to Play/Pause. Press / to stop. Use Record (●) to capture audio/MIDI. Add tracks from the Track menu or the "+ Add Track" button.'},{title:'🎹 MIDI & ARM',body:'Enable the red ARM button on a track to receive keys from a MIDI keyboard. Go to Tools → MIDI Devices to pick your device. When ARM + key press, preview audio plays and the VU meter animates for the note length.'},{title:'🎼 Piano Roll',body:'Double-click a MIDI item to open the Piano Roll. Use Ctrl+scroll or +/− buttons to zoom. Click to draw notes, drag to move, Ctrl+drag to copy. Shortcuts: C (draw), E (erase), Space (listen).'},{title:'🎛️ FX & Master',body:'Select a track → FX button to open the FX Rack (EQ, compressor...). Press the PWR button on the Master to open the Mastering Panel: 4-band EQ, compressor, limiter — all realtime.'},{title:'🧪 SoundFont & VST',body:'Tools → Plugin Manager scans SoundFonts (.sf2/.sf3) and VSTi. MIDI tracks use SoundFonts as instruments — pick one from the Synth button on the track.'},{title:'🤖 AI',body:'Tools → Config AI Providers to set up APIs (OpenAI, Gemini, Ollama...). Use the AI Prompt Generator (✨) to create MIDI/ideas; AI MIDI Preset Manager stores presets.'},{title:'💾 Save & Export',body:'Ctrl+S saves the project (cloud/temp), Ctrl+Shift+S Save As. Export WAV via the Export button — pick range, format, bitrate, then Render.'},{title:'⌨️ Shortcuts',body:'Space: Play/Pause · /: Stop · Ctrl+Z/Y: Undo/Redo · Ctrl+C/X/V: Copy/Cut/Paste · Ctrl+S: Save · Ctrl+N: New · S: Split · Ctrl+E: Edit in new tab · Ctrl+wheel: zoom timeline'}];return/*#__PURE__*/React.createElement("div",{className:"fixed inset-0 z-[9999] flex items-center justify-center bg-black/70 backdrop-blur-sm",onClick:onClose},/*#__PURE__*/React.createElement("div",{className:"bg-[#1e1e24] border border-zinc-700 rounded-xl shadow-2xl w-full max-w-2xl max-h-[85vh] flex flex-col text-slate-200",onClick:e=>e.stopPropagation()},/*#__PURE__*/React.createElement("div",{className:"flex items-center justify-between px-5 py-3 border-b border-zinc-800 shrink-0"},/*#__PURE__*/React.createElement("h2",{className:"text-base font-bold text-white"},vi?'Hướng dẫn sử dụng SonicForge Studio':'SonicForge Studio User Guide'),/*#__PURE__*/React.createElement("button",{onClick:onClose,className:"w-6 h-6 rounded hover:bg-zinc-700 text-zinc-400 hover:text-white text-sm"},"✕")),/*#__PURE__*/React.createElement("div",{className:"overflow-y-auto px-5 py-4 space-y-3"},sections.map((s,i)=>/*#__PURE__*/React.createElement("div",{key:i,className:"bg-zinc-900/60 border border-zinc-800 rounded-lg p-3"},/*#__PURE__*/React.createElement("h3",{className:"text-sm font-bold text-cyan-400 mb-1"},s.title),/*#__PURE__*/React.createElement("p",{className:"text-xs text-zinc-300 leading-relaxed"},s.body)))),/*#__PURE__*/React.createElement("div",{className:"px-5 py-3 border-t border-zinc-800 flex justify-end shrink-0"},/*#__PURE__*/React.createElement("button",{onClick:onClose,className:"px-4 py-1.5 bg-cyan-700 hover:bg-cyan-600 rounded text-xs font-bold text-white transition"},vi?'Đóng':'Close'))));};const PreferencesModal=({isOpen,onClose,prefs,onPrefsChange})=>{if(!isOpen)return null;const vi=prefs.language!=='en';const themes=[{id:'dark',name:vi?'Tối (mặc định)':'Dark (default)',swatch:'from-zinc-700 to-zinc-900',ring:'ring-cyan-400'},{id:'midnight',name:vi?'Đêm xanh':'Midnight',swatch:'from-sky-800 to-slate-950',ring:'ring-sky-400'},{id:'forest',name:vi?'Rừng xanh':'Forest',swatch:'from-emerald-700 to-green-950',ring:'ring-emerald-400'},{id:'violet',name:vi?'Tím':'Violet',swatch:'from-violet-700 to-purple-950',ring:'ring-violet-400'},{id:'graphite',name:vi?'Than chì':'Graphite',swatch:'from-zinc-600 to-neutral-900',ring:'ring-zinc-300'}];const set=(k,v)=>onPrefsChange({...prefs,[k]:v});return/*#__PURE__*/React.createElement("div",{className:"fixed inset-0 z-[9999] flex items-center justify-center bg-black/70 backdrop-blur-sm",onClick:onClose},/*#__PURE__*/React.createElement("div",{className:"bg-[#1e1e24] border border-zinc-700 rounded-xl shadow-2xl w-full max-w-md max-h-[85vh] overflow-y-auto p-5 text-slate-200",onClick:e=>e.stopPropagation()},/*#__PURE__*/React.createElement("div",{className:"flex items-center justify-between mb-4"},/*#__PURE__*/React.createElement("h2",{className:"text-base font-bold text-white"},vi?'Tùy chọn (Preferences)':'Preferences'),/*#__PURE__*/React.createElement("button",{onClick:onClose,className:"w-6 h-6 rounded hover:bg-zinc-700 text-zinc-400 hover:text-white text-sm"},"✕")),/*#__PURE__*/React.createElement("div",{className:"mb-5"},/*#__PURE__*/React.createElement("h3",{className:"text-xs font-bold text-zinc-400 uppercase tracking-wider mb-2"},vi?'🎨 Chủ đề màu':'🎨 Theme'),/*#__PURE__*/React.createElement("div",{className:"grid grid-cols-1 gap-1.5"},themes.map(th=>/*#__PURE__*/React.createElement("button",{key:th.id,onClick:()=>set('theme',th.id),className:`flex items-center gap-2.5 px-2.5 py-2 rounded-lg border text-xs text-left transition ${prefs.theme===th.id?'border-cyan-500 bg-zinc-800':'border-zinc-700 bg-zinc-900 hover:bg-zinc-800'}`},/*#__PURE__*/React.createElement("span",{className:`w-6 h-6 rounded-md bg-gradient-to-br ${th.swatch} ring-1 ring-black/40 shrink-0 ${prefs.theme===th.id?`ring-2 ${th.ring}`:''}`}),/*#__PURE__*/React.createElement("span",{className:"text-zinc-200"},th.name),prefs.theme===th.id&&/*#__PURE__*/React.createElement("span",{className:"ml-auto text-cyan-400 text-[10px] font-bold"},"✓"))))),/*#__PURE__*/React.createElement("div",{className:"mb-5"},/*#__PURE__*/React.createElement("h3",{className:"text-xs font-bold text-zinc-400 uppercase tracking-wider mb-2"},vi?'🌐 Ngôn ngữ':'🌐 Language'),/*#__PURE__*/React.createElement("div",{className:"flex gap-1.5"},[{id:'vi',label:'Tiếng Việt'},{id:'en',label:'English'}].map(l=>/*#__PURE__*/React.createElement("button",{key:l.id,onClick:()=>set('language',l.id),className:`flex-1 px-2 py-1.5 rounded-lg border text-xs font-semibold transition ${prefs.language===l.id?'border-cyan-500 bg-cyan-900/40 text-cyan-300':'border-zinc-700 bg-zinc-900 text-zinc-400 hover:bg-zinc-800'}`},l.label))),/*#__PURE__*/React.createElement("p",{className:"text-[10px] text-zinc-500 mt-1"},vi?'Áp dụng ngay cho menu & hướng dẫn.':'Applies immediately to menus & guide.')),/*#__PURE__*/React.createElement("div",{className:"mb-5"},/*#__PURE__*/React.createElement("h3",{className:"text-xs font-bold text-zinc-400 uppercase tracking-wider mb-2"},vi?'🔠 Cỡ chữ nút bấm':'🔠 Button font size'),/*#__PURE__*/React.createElement("div",{className:"flex gap-1.5"},[{id:'sm',label:vi?'Nhỏ':'Small'},{id:'md',label:vi?'Vừa':'Medium'},{id:'lg',label:vi?'Lớn':'Large'}].map(f=>/*#__PURE__*/React.createElement("button",{key:f.id,onClick:()=>set('buttonFontSize',f.id),className:`flex-1 px-2 py-1.5 rounded-lg border text-xs font-semibold transition ${prefs.buttonFontSize===f.id?'border-cyan-500 bg-cyan-900/40 text-cyan-300':'border-zinc-700 bg-zinc-900 text-zinc-400 hover:bg-zinc-800'}`},f.label)))),/*#__PURE__*/React.createElement("div",{className:"flex justify-end gap-2 pt-2 border-t border-zinc-800"},/*#__PURE__*/React.createElement("button",{onClick:onClose,className:"px-3 py-1.5 bg-zinc-700 hover:bg-zinc-600 rounded text-xs font-bold text-white transition"},vi?'Hủy':'Cancel'),/*#__PURE__*/React.createElement("button",{onClick:onClose,className:"px-4 py-1.5 bg-cyan-700 hover:bg-cyan-600 rounded text-xs font-bold text-white transition"},vi?'Lưu & Đóng':'Save & Close'))));};const AIPresetModal=({isOpen,onClose})=>{if(!isOpen)return null;const mgrRef=React.useRef(null);if(!mgrRef.current)mgrRef.current=new window.PromptTemplateManager();const mgr=mgrRef.current;const[presets,setPresets]=React.useState(()=>[...mgr.getPresets()]);const[search,setSearch]=React.useState('');const[filterCategory,setFilterCategory]=React.useState('ALL');const[showFavoritesOnly,setShowFavoritesOnly]=React.useState(false);const[editingPreset,setEditingPreset]=React.useState(null);const[syncing,setSyncing]=React.useState(false);const[formName,setFormName]=React.useState('');const[formKeywords,setFormKeywords]=React.useState('');const[formCategory,setFormCategory]=React.useState('Orchestral / Film Score');const[formBars,setFormBars]=React.useState(8);const[formBpm,setFormBpm]=React.useState(120);const[formScale,setFormScale]=React.useState('C Minor');const[formTemplate,setFormTemplate]=React.useState('');const[showGeneratorModal,setShowGeneratorModal]=React.useState(false);const[selectedCategory,setSelectedCategory]=React.useState('Orchestral / Film Score');const[isAddingCategory,setIsAddingCategory]=React.useState(false);const[newCategoryValue,setNewCategoryValue]=React.useState('');const[categoriesVersion,setCategoriesVersion]=React.useState(0);const presetCategories=React.useMemo(()=>{const fromPresets=[...new Set(presets.map(p=>p.category).filter(Boolean))];let saved=[];try{const raw=localStorage.getItem('midi_prompt_categories');saved=raw?JSON.parse(raw):[];}catch(e){saved=[];}return[...new Set([...fromPresets,...saved])].sort();},[presets,categoriesVersion]);const addNewCategory=cat=>{const val=(cat||'').trim();if(!val)return;setFormCategory(val);setSelectedCategory(val);try{const raw=localStorage.getItem('midi_prompt_categories');const list=raw?JSON.parse(raw):[];if(!list.includes(val)){list.push(val);localStorage.setItem('midi_prompt_categories',JSON.stringify(list));setCategoriesVersion(v=>v+1);}}catch(e){}};const refreshPresets=()=>{setPresets([...mgr.getPresets()]);};// Sync from backend on mount — merge into local presets, never overwrite +const AboutModal=({isOpen,onClose})=>{if(!isOpen)return null;return/*#__PURE__*/React.createElement("div",{className:"fixed inset-0 z-[9999] flex items-center justify-center bg-black/70 backdrop-blur-sm",onClick:onClose},/*#__PURE__*/React.createElement("div",{className:"bg-[#1e1e24] border border-zinc-700 rounded-xl shadow-2xl w-full max-w-md p-6 text-slate-200",onClick:e=>e.stopPropagation()},/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-3 mb-4"},/*#__PURE__*/React.createElement("img",{src:"/favicon.svg",alt:"SonicForge Studio",className:"w-12 h-12 rounded-lg bg-zinc-900 border border-zinc-700 shadow-lg object-contain p-0.5"}),/*#__PURE__*/React.createElement("div",null,/*#__PURE__*/React.createElement("h2",{className:"text-lg font-bold text-white"},"SonicForge Studio"),/*#__PURE__*/React.createElement("p",{className:"text-xs text-zinc-400"},"Professional DAW \u2014 v",APP_VERSION))),/*#__PURE__*/React.createElement("div",{className:"space-y-2 text-sm"},/*#__PURE__*/React.createElement("p",{className:"text-zinc-300 leading-relaxed"},"Ph\u1EA7n m\u1EC1m s\u1EA3n xu\u1EA5t \xE2m nh\u1EA1c (DAW) \u2014 so\u1EA1n nh\u1EA1c, ghi \xE2m, ch\u1EC9nh s\u1EEDa MIDI/Audio, SoundFont & VST, tr\u1ED9n v\xE0 master."),/*#__PURE__*/React.createElement("div",{className:"pt-2 border-t border-zinc-800 space-y-1 text-xs"},/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-2"},/*#__PURE__*/React.createElement("span",{className:"text-zinc-500 w-20 shrink-0"},"Developer"),/*#__PURE__*/React.createElement("span",{className:"text-cyan-400 font-semibold"},"L\u1ED9c Ph\u1EA1m")),/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-2"},/*#__PURE__*/React.createElement("span",{className:"text-zinc-500 w-20 shrink-0"},"Email"),/*#__PURE__*/React.createElement("a",{href:"mailto:tranloclqd@gmail.com",className:"text-cyan-400 hover:underline"},"tranloclqd@gmail.com")),/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-2"},/*#__PURE__*/React.createElement("span",{className:"text-zinc-500 w-20 shrink-0"},"Version"),/*#__PURE__*/React.createElement("span",{className:"text-zinc-300"},APP_VERSION)),/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-2"},/*#__PURE__*/React.createElement("span",{className:"text-zinc-500 w-20 shrink-0"},"Build"),/*#__PURE__*/React.createElement("span",{className:"text-zinc-300"},"Standalone (Tauri v2 + PyInstaller)")))),/*#__PURE__*/React.createElement("div",{className:"mt-5 flex justify-end"},/*#__PURE__*/React.createElement("button",{onClick:onClose,className:"px-4 py-1.5 bg-cyan-700 hover:bg-cyan-600 rounded text-xs font-bold text-white transition"},"\u0110\xF3ng"))));};const HelpModal=({isOpen,onClose,lang})=>{if(!isOpen)return null;const vi=lang!=='en';const sections=vi?[{title:'🚀 Bắt đầu nhanh',body:'Nhấn phím Cách (Space) để Play/Pause. Nhấn phím / để dừng. Dùng nút Record (●) để ghi âm/MIDI. Tạo track mới từ menu Track hoặc nút "+ Add Track".'},{title:'🎹 MIDI & ARM',body:'Bật nút ARM đỏ trên track để nhận phím từ MIDI keyboard. Vào menu Tools → MIDI Devices để chọn thiết bị. Khi ARM + bấm phím, âm preview phát và VU meter nhảy theo trường độ.'},{title:'🎼 Piano Roll',body:'Nhấp đúp vào MIDI item để mở Piano Roll. Dùng Ctrl+scroll hoặc nút +/− để zoom. Nhấp để vẽ note, kéo để di chuyển, Ctrl+kéo để copy. Phím tắt: C (vẽ), E (tẩy), Space (nghe).'},{title:'🎛️ FX & Master',body:'Chọn track → nút FX để mở FX Rack (thêm EQ, compressor...). Bấm nút PWR ở Master để mở Mastering Panel: EQ 4 băng, compressor, limiter. Mọi thay đổi áp dụng realtime.'},{title:'🧪 SoundFont & VST',body:'Vào Tools → Plugin Manager để quét SoundFont (.sf2/.sf3) và VSTi. Track MIDI dùng SoundFont làm nhạc cụ — chọn instrument từ nút Synth trên track.'},{title:'🤖 AI',body:'Tools → Config AI Providers để cấu hình API (OpenAI, Gemini, Ollama...). Dùng AI Prompt Generator (nút ✨) để sinh MIDI/ý tưởng; AI MIDI Preset Manager để lưu preset.'},{title:'💾 Lưu & Xuất',body:'Ctrl+S lưu project (cloud/temp), Ctrl+Shift+S Save As. Export WAV qua nút Export — chọn vùng, format, bitrate rồi Render.'},{title:'⌨️ Phím tắt',body:'Space: Play/Pause · /: Stop · Ctrl+Z/Y: Undo/Redo · Ctrl+C/X/V: Copy/Cut/Paste · Ctrl+S: Save · Ctrl+N: New · S: Split · Ctrl+E: Edit in new tab · Ctrl+wheel: zoom timeline'}]:[{title:'🚀 Quick start',body:'Press Space to Play/Pause. Press / to stop. Use Record (●) to capture audio/MIDI. Add tracks from the Track menu or the "+ Add Track" button.'},{title:'🎹 MIDI & ARM',body:'Enable the red ARM button on a track to receive keys from a MIDI keyboard. Go to Tools → MIDI Devices to pick your device. When ARM + key press, preview audio plays and the VU meter animates for the note length.'},{title:'🎼 Piano Roll',body:'Double-click a MIDI item to open the Piano Roll. Use Ctrl+scroll or +/− buttons to zoom. Click to draw notes, drag to move, Ctrl+drag to copy. Shortcuts: C (draw), E (erase), Space (listen).'},{title:'🎛️ FX & Master',body:'Select a track → FX button to open the FX Rack (EQ, compressor...). Press the PWR button on the Master to open the Mastering Panel: 4-band EQ, compressor, limiter — all realtime.'},{title:'🧪 SoundFont & VST',body:'Tools → Plugin Manager scans SoundFonts (.sf2/.sf3) and VSTi. MIDI tracks use SoundFonts as instruments — pick one from the Synth button on the track.'},{title:'🤖 AI',body:'Tools → Config AI Providers to set up APIs (OpenAI, Gemini, Ollama...). Use the AI Prompt Generator (✨) to create MIDI/ideas; AI MIDI Preset Manager stores presets.'},{title:'💾 Save & Export',body:'Ctrl+S saves the project (cloud/temp), Ctrl+Shift+S Save As. Export WAV via the Export button — pick range, format, bitrate, then Render.'},{title:'⌨️ Shortcuts',body:'Space: Play/Pause · /: Stop · Ctrl+Z/Y: Undo/Redo · Ctrl+C/X/V: Copy/Cut/Paste · Ctrl+S: Save · Ctrl+N: New · S: Split · Ctrl+E: Edit in new tab · Ctrl+wheel: zoom timeline'}];return/*#__PURE__*/React.createElement("div",{className:"fixed inset-0 z-[9999] flex items-center justify-center bg-black/70 backdrop-blur-sm",onClick:onClose},/*#__PURE__*/React.createElement("div",{className:"bg-[#1e1e24] border border-zinc-700 rounded-xl shadow-2xl w-full max-w-2xl max-h-[85vh] flex flex-col text-slate-200",onClick:e=>e.stopPropagation()},/*#__PURE__*/React.createElement("div",{className:"flex items-center justify-between px-5 py-3 border-b border-zinc-800 shrink-0"},/*#__PURE__*/React.createElement("h2",{className:"text-base font-bold text-white"},vi?'Hướng dẫn sử dụng SonicForge Studio':'SonicForge Studio User Guide'),/*#__PURE__*/React.createElement("button",{onClick:onClose,className:"w-6 h-6 rounded hover:bg-zinc-700 text-zinc-400 hover:text-white text-sm"},"\u2715")),/*#__PURE__*/React.createElement("div",{className:"overflow-y-auto px-5 py-4 space-y-3"},sections.map((s,i)=>/*#__PURE__*/React.createElement("div",{key:i,className:"bg-zinc-900/60 border border-zinc-800 rounded-lg p-3"},/*#__PURE__*/React.createElement("h3",{className:"text-sm font-bold text-cyan-400 mb-1"},s.title),/*#__PURE__*/React.createElement("p",{className:"text-xs text-zinc-300 leading-relaxed"},s.body)))),/*#__PURE__*/React.createElement("div",{className:"px-5 py-3 border-t border-zinc-800 flex justify-end shrink-0"},/*#__PURE__*/React.createElement("button",{onClick:onClose,className:"px-4 py-1.5 bg-cyan-700 hover:bg-cyan-600 rounded text-xs font-bold text-white transition"},vi?'Đóng':'Close'))));};const PreferencesModal=({isOpen,onClose,prefs,onPrefsChange})=>{if(!isOpen)return null;const vi=prefs.language!=='en';const themes=[{id:'dark',name:vi?'Tối (mặc định)':'Dark (default)',swatch:'from-zinc-700 to-zinc-900',ring:'ring-cyan-400'},{id:'midnight',name:vi?'Đêm xanh':'Midnight',swatch:'from-sky-800 to-slate-950',ring:'ring-sky-400'},{id:'forest',name:vi?'Rừng xanh':'Forest',swatch:'from-emerald-700 to-green-950',ring:'ring-emerald-400'},{id:'violet',name:vi?'Tím':'Violet',swatch:'from-violet-700 to-purple-950',ring:'ring-violet-400'},{id:'graphite',name:vi?'Than chì':'Graphite',swatch:'from-zinc-600 to-neutral-900',ring:'ring-zinc-300'}];const set=(k,v)=>onPrefsChange({...prefs,[k]:v});return/*#__PURE__*/React.createElement("div",{className:"fixed inset-0 z-[9999] flex items-center justify-center bg-black/70 backdrop-blur-sm",onClick:onClose},/*#__PURE__*/React.createElement("div",{className:"bg-[#1e1e24] border border-zinc-700 rounded-xl shadow-2xl w-full max-w-md max-h-[85vh] overflow-y-auto p-5 text-slate-200",onClick:e=>e.stopPropagation()},/*#__PURE__*/React.createElement("div",{className:"flex items-center justify-between mb-4"},/*#__PURE__*/React.createElement("h2",{className:"text-base font-bold text-white"},vi?'Tùy chọn (Preferences)':'Preferences'),/*#__PURE__*/React.createElement("button",{onClick:onClose,className:"w-6 h-6 rounded hover:bg-zinc-700 text-zinc-400 hover:text-white text-sm"},"\u2715")),/*#__PURE__*/React.createElement("div",{className:"mb-5"},/*#__PURE__*/React.createElement("h3",{className:"text-xs font-bold text-zinc-400 uppercase tracking-wider mb-2"},vi?'🎨 Chủ đề màu':'🎨 Theme'),/*#__PURE__*/React.createElement("div",{className:"grid grid-cols-1 gap-1.5"},themes.map(th=>/*#__PURE__*/React.createElement("button",{key:th.id,onClick:()=>set('theme',th.id),className:`flex items-center gap-2.5 px-2.5 py-2 rounded-lg border text-xs text-left transition ${prefs.theme===th.id?'border-cyan-500 bg-zinc-800':'border-zinc-700 bg-zinc-900 hover:bg-zinc-800'}`},/*#__PURE__*/React.createElement("span",{className:`w-6 h-6 rounded-md bg-gradient-to-br ${th.swatch} ring-1 ring-black/40 shrink-0 ${prefs.theme===th.id?`ring-2 ${th.ring}`:''}`}),/*#__PURE__*/React.createElement("span",{className:"text-zinc-200"},th.name),prefs.theme===th.id&&/*#__PURE__*/React.createElement("span",{className:"ml-auto text-cyan-400 text-[10px] font-bold"},"\u2713"))))),/*#__PURE__*/React.createElement("div",{className:"mb-5"},/*#__PURE__*/React.createElement("h3",{className:"text-xs font-bold text-zinc-400 uppercase tracking-wider mb-2"},vi?'🌐 Ngôn ngữ':'🌐 Language'),/*#__PURE__*/React.createElement("div",{className:"flex gap-1.5"},[{id:'vi',label:'Tiếng Việt'},{id:'en',label:'English'}].map(l=>/*#__PURE__*/React.createElement("button",{key:l.id,onClick:()=>set('language',l.id),className:`flex-1 px-2 py-1.5 rounded-lg border text-xs font-semibold transition ${prefs.language===l.id?'border-cyan-500 bg-cyan-900/40 text-cyan-300':'border-zinc-700 bg-zinc-900 text-zinc-400 hover:bg-zinc-800'}`},l.label))),/*#__PURE__*/React.createElement("p",{className:"text-[10px] text-zinc-500 mt-1"},vi?'Áp dụng ngay cho menu & hướng dẫn.':'Applies immediately to menus & guide.')),/*#__PURE__*/React.createElement("div",{className:"mb-5"},/*#__PURE__*/React.createElement("h3",{className:"text-xs font-bold text-zinc-400 uppercase tracking-wider mb-2"},vi?'🔠 Cỡ chữ nút bấm':'🔠 Button font size'),/*#__PURE__*/React.createElement("div",{className:"flex gap-1.5"},[{id:'sm',label:vi?'Nhỏ':'Small'},{id:'md',label:vi?'Vừa':'Medium'},{id:'lg',label:vi?'Lớn':'Large'}].map(f=>/*#__PURE__*/React.createElement("button",{key:f.id,onClick:()=>set('buttonFontSize',f.id),className:`flex-1 px-2 py-1.5 rounded-lg border text-xs font-semibold transition ${prefs.buttonFontSize===f.id?'border-cyan-500 bg-cyan-900/40 text-cyan-300':'border-zinc-700 bg-zinc-900 text-zinc-400 hover:bg-zinc-800'}`},f.label)))),/*#__PURE__*/React.createElement("div",{className:"flex justify-end gap-2 pt-2 border-t border-zinc-800"},/*#__PURE__*/React.createElement("button",{onClick:onClose,className:"px-3 py-1.5 bg-zinc-700 hover:bg-zinc-600 rounded text-xs font-bold text-white transition"},vi?'Hủy':'Cancel'),/*#__PURE__*/React.createElement("button",{onClick:onClose,className:"px-4 py-1.5 bg-cyan-700 hover:bg-cyan-600 rounded text-xs font-bold text-white transition"},vi?'Lưu & Đóng':'Save & Close'))));};const AIPresetModal=({isOpen,onClose})=>{if(!isOpen)return null;const mgrRef=React.useRef(null);if(!mgrRef.current)mgrRef.current=new window.PromptTemplateManager();const mgr=mgrRef.current;const[presets,setPresets]=React.useState(()=>[...mgr.getPresets()]);const[search,setSearch]=React.useState('');const[filterCategory,setFilterCategory]=React.useState('ALL');const[showFavoritesOnly,setShowFavoritesOnly]=React.useState(false);const[editingPreset,setEditingPreset]=React.useState(null);const[syncing,setSyncing]=React.useState(false);const[formName,setFormName]=React.useState('');const[formKeywords,setFormKeywords]=React.useState('');const[formCategory,setFormCategory]=React.useState('Orchestral / Film Score');const[formBars,setFormBars]=React.useState(8);const[formBpm,setFormBpm]=React.useState(120);const[formScale,setFormScale]=React.useState('C Minor');const[formTemplate,setFormTemplate]=React.useState('');const[showGeneratorModal,setShowGeneratorModal]=React.useState(false);const[selectedCategory,setSelectedCategory]=React.useState('Orchestral / Film Score');const[isAddingCategory,setIsAddingCategory]=React.useState(false);const[newCategoryValue,setNewCategoryValue]=React.useState('');const[categoriesVersion,setCategoriesVersion]=React.useState(0);const presetCategories=React.useMemo(()=>{const fromPresets=[...new Set(presets.map(p=>p.category).filter(Boolean))];let saved=[];try{const raw=localStorage.getItem('midi_prompt_categories');saved=raw?JSON.parse(raw):[];}catch(e){saved=[];}return[...new Set([...fromPresets,...saved])].sort();},[presets,categoriesVersion]);const addNewCategory=cat=>{const val=(cat||'').trim();if(!val)return;setFormCategory(val);setSelectedCategory(val);try{const raw=localStorage.getItem('midi_prompt_categories');const list=raw?JSON.parse(raw):[];if(!list.includes(val)){list.push(val);localStorage.setItem('midi_prompt_categories',JSON.stringify(list));setCategoriesVersion(v=>v+1);}}catch(e){}};const refreshPresets=()=>{setPresets([...mgr.getPresets()]);};// Sync from backend on mount — merge into local presets, never overwrite React.useEffect(()=>{if(!window.SonicAPI)return;setSyncing(true);window.SonicAPI.getAIPresets().then(data=>{if(!data||!data.presets||data.presets.length===0)return;var existing=mgr.presets;var existingIds=new Set(existing.map(function(p){return p.id;}));var merged=existing.slice();data.presets.forEach(function(bp){if(!existingIds.has(bp.id)){merged.push(bp);existingIds.add(bp.id);}});mgr.presets=merged;setPresets(merged);}).catch(function(){}).finally(function(){setSyncing(false);});},[]);// Listen for GENERATOR_PRESET_DATA from the iframe generator modal React.useEffect(()=>{const handleMessage=event=>{if(event.data&&event.data.type==='GENERATOR_PRESET_DATA'){const d=event.data;setFormName(d.name||'');setFormCategory(d.category||'Orchestral / Film Score');setSelectedCategory(d.category||'Orchestral / Film Score');setFormKeywords(d.keywords||'');setFormBars(parseInt(d.default_bars)||8);setFormBpm(parseInt(d.default_bpm)||120);setFormScale(d.default_scale||'C Minor');setFormTemplate(d.template||'');setEditingPreset('new');setShowGeneratorModal(false);setIsAddingCategory(false);setNewCategoryValue('');refreshPresets();}};window.addEventListener('message',handleMessage);return()=>window.removeEventListener('message',handleMessage);},[]);const savePresets=newPresets=>{setPresets(newPresets);mgr.presets=newPresets;mgr.savePresets();// Sync to backend if available const userDefined=newPresets.filter(p=>p.is_user_defined);if(window.SonicAPI&&userDefined.length>0){userDefined.forEach(p=>{window.SonicAPI.saveAIPreset(p).catch(()=>{});});}};const handleEdit=p=>{setEditingPreset(p);setFormName(p.name);setFormKeywords(p.keywords.join(', '));setFormCategory(p.category);setSelectedCategory(p.category);setFormBars(p.default_bars);setFormBpm(p.default_bpm);setFormScale(p.default_scale);setFormTemplate(p.system_instruction_template);setIsAddingCategory(false);setNewCategoryValue('');};const handleNew=()=>{setEditingPreset('new');setFormName('');setFormKeywords('');setFormCategory('Orchestral / Film Score');setSelectedCategory('Orchestral / Film Score');setFormBars(8);setFormBpm(120);setFormScale('C Minor');setFormTemplate('');setIsAddingCategory(false);setNewCategoryValue('');};const handleNewStructured=()=>{refreshPresets();setShowGeneratorModal(true);};const handleToggleFav=id=>{mgr.toggleFavorite(id);setPresets([...mgr.getPresets()]);const p=mgr.presets.find(x=>x.id===id);if(p&&p.is_user_defined&&window.SonicAPI){window.SonicAPI.saveAIPreset(p).catch(()=>{});}};const handleDelete=id=>{const p=presets.find(x=>x.id===id);if(p&&p.is_user_defined&&window.SonicAPI){window.SonicAPI.deleteAIPreset(id).catch(()=>{});}mgr.deletePreset(id);setPresets([...mgr.getPresets()]);showToast('Đã xóa preset.','info');};const handleSaveForm=e=>{e.preventDefault();if(!formName.trim()||!formTemplate.trim()){showToast('Vui lòng điền đầy đủ tên và mẫu gợi ý.','warning');return;}const keywordsArray=formKeywords.split(',').map(k=>k.trim()).filter(Boolean);const presetObj={id:editingPreset==='new'?'preset_'+Date.now():editingPreset.id,name:formName.trim(),keywords:keywordsArray,category:formCategory,default_bars:parseInt(formBars)||8,default_bpm:parseInt(formBpm)||120,default_scale:formScale,system_instruction_template:formTemplate.trim(),is_user_defined:true,is_favorite:editingPreset==='new'?false:editingPreset.is_favorite||false,created_at:editingPreset==='new'?new Date().toISOString():editingPreset.created_at};mgr.saveUserPreset(presetObj);setPresets([...mgr.getPresets()]);setEditingPreset(null);if(window.SonicAPI){window.SonicAPI.saveAIPreset(presetObj).catch(()=>{});}showToast('Đã lưu preset thành công!','success');};const categories=['ALL','★ Yêu thích','Người dùng',...new Set(presets.map(p=>p.category))];const filtered=presets.filter(p=>{const matchesSearch=p.name.toLowerCase().includes(search.toLowerCase())||p.keywords.some(k=>k.toLowerCase().includes(search.toLowerCase()));let matchesCategory;if(filterCategory==='ALL'){matchesCategory=true;}else if(filterCategory==='★ Yêu thích'){matchesCategory=p.is_favorite;}else if(filterCategory==='Người dùng'){matchesCategory=p.is_user_defined;}else{matchesCategory=p.category===filterCategory;}if(showFavoritesOnly)matchesCategory=matchesCategory&&p.is_favorite;return matchesSearch&&matchesCategory;});return/*#__PURE__*/React.createElement(React.Fragment,null,/*#__PURE__*/React.createElement("div",{className:"fixed inset-0 z-50 flex items-center justify-center bg-black/75 backdrop-blur-sm p-4 animate-fade-in"},/*#__PURE__*/React.createElement("div",{className:"bg-[#18181b] border border-zinc-800 rounded-xl shadow-2xl w-full max-w-4xl max-h-[85vh] flex flex-col overflow-hidden text-zinc-100"},/*#__PURE__*/React.createElement("div",{className:"p-4 border-b border-zinc-800 flex items-center justify-between shrink-0 bg-[#202024]"},/*#__PURE__*/React.createElement("h2",{className:"text-sm font-bold tracking-wider uppercase text-purple-400 flex items-center gap-1.5"},/*#__PURE__*/React.createElement("i",{"data-lucide":"sliders",className:"w-4 h-4"}),"AI Prompt Preset Manager",syncing&&/*#__PURE__*/React.createElement("span",{className:"text-[10px] text-zinc-500 ml-2"},"đang đồng bộ...")),/*#__PURE__*/React.createElement("button",{onClick:()=>{setEditingPreset(null);onClose();},className:"text-zinc-400 hover:text-zinc-200 transition"},/*#__PURE__*/React.createElement("i",{"data-lucide":"x",className:"w-4 h-4"}))),/*#__PURE__*/React.createElement("div",{className:"flex-1 overflow-y-auto p-4 flex gap-4 min-h-0"},!editingPreset?/*#__PURE__*/React.createElement(React.Fragment,null,/*#__PURE__*/React.createElement("div",{className:"flex-1 flex flex-col min-w-0"},/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-2 mb-3 shrink-0"},/*#__PURE__*/React.createElement("input",{type:"text",placeholder:"Tìm kiếm preset hoặc từ khóa...",value:search,onChange:e=>setSearch(e.target.value),className:"flex-1 bg-zinc-900 border border-zinc-700 rounded px-2.5 py-1 text-xs outline-none focus:border-purple-600"}),/*#__PURE__*/React.createElement("select",{value:filterCategory,onChange:e=>setFilterCategory(e.target.value),className:"bg-zinc-900 border border-zinc-700 rounded px-2.5 py-1 text-xs outline-none focus:border-purple-600"},categories.map(c=>/*#__PURE__*/React.createElement("option",{key:c,value:c},c==='ALL'?'Tất cả danh mục':c))),/*#__PURE__*/React.createElement("button",{onClick:()=>setShowFavoritesOnly(!showFavoritesOnly),className:`px-2.5 py-1 rounded text-xs font-bold transition shrink-0 ${showFavoritesOnly?'bg-yellow-700 text-yellow-300':'bg-zinc-800 text-zinc-400 hover:bg-zinc-700'}`,title:"Chỉ hiện yêu thích"},/*#__PURE__*/React.createElement("i",{"data-lucide":"star",className:"w-3.5 h-3.5 inline-block mr-1"}),"★"),/*#__PURE__*/React.createElement("button",{onClick:handleNew,className:"px-3 py-1 bg-purple-700 hover:bg-purple-600 text-white rounded text-xs font-bold flex items-center gap-1.5 shadow transition shrink-0"},/*#__PURE__*/React.createElement("i",{"data-lucide":"plus",className:"w-3.5 h-3.5"}),"Tạo mới")),/*#__PURE__*/React.createElement("div",{className:"flex-1 border border-zinc-800 rounded bg-[#0f0f12] overflow-y-auto"},filtered.length===0?/*#__PURE__*/React.createElement("div",{className:"p-8 text-center text-zinc-500 text-xs italic"},"Không tìm thấy preset nào."):/*#__PURE__*/React.createElement("table",{className:"w-full text-left text-xs border-collapse"},/*#__PURE__*/React.createElement("thead",{className:"bg-[#1f1f23] text-zinc-400 font-bold border-b border-zinc-800"},/*#__PURE__*/React.createElement("tr",null,/*#__PURE__*/React.createElement("th",{className:"p-2.5 w-8"},""),/*#__PURE__*/React.createElement("th",{className:"p-2.5 w-1/4"},"Tên Preset"),/*#__PURE__*/React.createElement("th",{className:"p-2.5 w-1/4"},"Từ khóa kích hoạt"),/*#__PURE__*/React.createElement("th",{className:"p-2.5 w-1/6"},"Số Bar"),/*#__PURE__*/React.createElement("th",{className:"p-2.5 w-1/6"},"BPM"),/*#__PURE__*/React.createElement("th",{className:"p-2.5 w-1/6 text-right"},"Hành động"))),/*#__PURE__*/React.createElement("tbody",null,filtered.map(p=>/*#__PURE__*/React.createElement("tr",{key:p.id,className:"border-b border-zinc-800/50 hover:bg-zinc-850"},/*#__PURE__*/React.createElement("td",{className:"p-2.5 text-center"},/*#__PURE__*/React.createElement("button",{onClick:()=>handleToggleFav(p.id),className:`transition ${p.is_favorite?'text-yellow-400':'text-zinc-600 hover:text-zinc-400'}`,title:p.is_favorite?'Bỏ yêu thích':'Đánh dấu yêu thích'},p.is_favorite?"★":"☆")),/*#__PURE__*/React.createElement("td",{className:"p-2.5 font-semibold text-purple-300"},p.name),/*#__PURE__*/React.createElement("td",{className:"p-2.5 text-zinc-400 font-mono text-[11px] truncate max-w-[150px]"},p.keywords.join(', ')),/*#__PURE__*/React.createElement("td",{className:"p-2.5 text-zinc-400"},p.default_bars," Bars"),/*#__PURE__*/React.createElement("td",{className:"p-2.5 text-zinc-400"},p.default_bpm," BPM"),/*#__PURE__*/React.createElement("td",{className:"p-2.5 text-right flex items-center justify-end gap-1.5 h-full"},/*#__PURE__*/React.createElement("button",{type:"button",onClick:()=>handleEdit(p),className:"px-2 py-0.5 bg-zinc-850 hover:bg-zinc-700 text-zinc-300 rounded border border-zinc-700 text-[10px]"},"Sửa"),p.is_user_defined&&/*#__PURE__*/React.createElement("button",{type:"button",onClick:()=>handleDelete(p.id),className:"px-2 py-0.5 bg-red-950/40 hover:bg-red-800 text-red-400 rounded border border-red-900 text-[10px]"},"Xóa"))))))))):/*#__PURE__*/React.createElement("form",{onSubmit:handleSaveForm,className:"flex-1 flex flex-col gap-3 min-w-0"},/*#__PURE__*/React.createElement("div",{className:"text-xs font-bold text-zinc-400 shrink-0 border-b border-zinc-800 pb-1"},editingPreset==='new'?"TẠO PRESET MỚI":`SỬA PRESET: ${editingPreset.name}`),/*#__PURE__*/React.createElement("div",{className:"grid grid-cols-2 gap-3"},/*#__PURE__*/React.createElement("div",{className:"flex flex-col gap-1"},/*#__PURE__*/React.createElement("label",{className:"text-[10px] uppercase font-bold text-zinc-500"},"Tên Preset"),/*#__PURE__*/React.createElement("input",{type:"text",value:formName,onChange:e=>setFormName(e.target.value),className:"bg-zinc-900 border border-zinc-700 rounded px-2.5 py-1 text-xs outline-none focus:border-purple-600 text-zinc-200"})),/*#__PURE__*/React.createElement("div",{className:"flex flex-col gap-1"},/*#__PURE__*/React.createElement("label",{className:"text-[10px] uppercase font-bold text-zinc-500"},"Danh mục"),isAddingCategory?/*#__PURE__*/React.createElement("div",{className:"flex gap-2"},/*#__PURE__*/React.createElement("input",{type:"text",value:newCategoryValue,onChange:e=>setNewCategoryValue(e.target.value),onBlur:()=>{if(newCategoryValue.trim()){addNewCategory(newCategoryValue.trim());}setIsAddingCategory(false);},onKeyDown:e=>{if(e.key==='Enter'){e.preventDefault();if(newCategoryValue.trim()){addNewCategory(newCategoryValue.trim());}setIsAddingCategory(false);}else if(e.key==='Escape'){setIsAddingCategory(false);}},autoFocus:true,className:"flex-1 bg-zinc-900 border border-zinc-700 rounded px-2.5 py-1 text-xs outline-none focus:border-purple-600 text-zinc-200",placeholder:"Nhập danh mục mới..."})):/*#__PURE__*/React.createElement("select",{value:selectedCategory,onChange:e=>{if(e.target.value==='__add_new__'){setIsAddingCategory(true);setNewCategoryValue('');}else{setSelectedCategory(e.target.value);setFormCategory(e.target.value);}},className:"bg-zinc-900 border border-zinc-700 rounded px-2.5 py-1 text-xs outline-none focus:border-purple-600 text-zinc-200"},presetCategories.map(c=>/*#__PURE__*/React.createElement("option",{key:c,value:c},c)),/*#__PURE__*/React.createElement("option",{value:'__add_new__'},"+ Nhập danh mục mới...")))),/*#__PURE__*/React.createElement("div",{className:"flex flex-col gap-1"},/*#__PURE__*/React.createElement("label",{className:"text-[10px] uppercase font-bold text-zinc-500"},"Từ khóa kích hoạt (ngăn cách bằng dấu phẩy)"),/*#__PURE__*/React.createElement("input",{type:"text",value:formKeywords,onChange:e=>setFormKeywords(e.target.value),placeholder:"Ví dụ: epic orchestra, hoành tráng, nhạc phim epic",className:"bg-zinc-900 border border-zinc-700 rounded px-2.5 py-1 text-xs outline-none focus:border-purple-600 text-zinc-200"})),/*#__PURE__*/React.createElement("div",{className:"grid grid-cols-3 gap-3"},/*#__PURE__*/React.createElement("div",{className:"flex flex-col gap-1"},/*#__PURE__*/React.createElement("label",{className:"text-[10px] uppercase font-bold text-zinc-500"},"Số Bars mặc định"),/*#__PURE__*/React.createElement("input",{type:"number",value:formBars,onChange:e=>setFormBars(e.target.value),className:"bg-zinc-900 border border-zinc-700 rounded px-2.5 py-1 text-xs outline-none focus:border-purple-600 text-zinc-200"})),/*#__PURE__*/React.createElement("div",{className:"flex flex-col gap-1"},/*#__PURE__*/React.createElement("label",{className:"text-[10px] uppercase font-bold text-zinc-500"},"BPM mặc định"),/*#__PURE__*/React.createElement("input",{type:"number",value:formBpm,onChange:e=>setFormBpm(e.target.value),className:"bg-zinc-900 border border-zinc-700 rounded px-2.5 py-1 text-xs outline-none focus:border-purple-600 text-zinc-200"})),/*#__PURE__*/React.createElement("div",{className:"flex flex-col gap-1"},/*#__PURE__*/React.createElement("label",{className:"text-[10px] uppercase font-bold text-zinc-500"},"Âm giai (Scale) mặc định"),/*#__PURE__*/React.createElement("input",{type:"text",value:formScale,onChange:e=>setFormScale(e.target.value),className:"bg-zinc-900 border border-zinc-700 rounded px-2.5 py-1 text-xs outline-none focus:border-purple-600 text-zinc-200"}))),/*#__PURE__*/React.createElement("div",{className:"flex flex-col gap-1 flex-1 min-h-0"},/*#__PURE__*/React.createElement("label",{className:"text-[10px] uppercase font-bold text-zinc-500"},"System Prompt Template / Luật soạn nhạc"),/*#__PURE__*/React.createElement("textarea",{value:formTemplate,onChange:e=>setFormTemplate(e.target.value),rows:6,className:"flex-1 bg-zinc-900 border border-zinc-700 rounded p-2.5 text-xs outline-none focus:border-purple-600 text-zinc-200 font-mono resize-none"})),/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-2 shrink-0 pt-2 border-t border-zinc-800"},/*#__PURE__*/React.createElement("button",{type:"button",onClick:handleNewStructured,className:"px-3 py-1.5 bg-emerald-800 hover:bg-emerald-700 text-emerald-300 border border-emerald-700 rounded text-xs transition"},"Tạo preset với cấu trúc"),/*#__PURE__*/React.createElement("div",{className:"flex-1"}),/*#__PURE__*/React.createElement("button",{type:"button",onClick:()=>setEditingPreset(null),className:"px-3 py-1.5 bg-zinc-800 hover:bg-zinc-700 text-zinc-400 border border-zinc-700 rounded text-xs transition"},"Quay lại"),/*#__PURE__*/React.createElement("button",{type:"submit",className:"px-3 py-1.5 bg-purple-700 hover:bg-purple-600 text-white rounded text-xs font-bold shadow transition"},"Lưu Preset")))),/*#__PURE__*/React.createElement("div",{className:"p-4 border-t border-zinc-800 flex justify-end shrink-0 bg-[#202024]"},/*#__PURE__*/React.createElement("button",{onClick:()=>{setEditingPreset(null);onClose();},className:"px-4 py-1.5 bg-zinc-800 hover:bg-zinc-700 text-zinc-300 border border-zinc-700 rounded text-xs font-bold shadow transition"},"Đóng")))),showGeneratorModal?/*#__PURE__*/React.createElement("div",{className:"fixed inset-0 z-[60] flex items-center justify-center bg-black/80 p-4",onClick:e=>{if(e.target===e.currentTarget){refreshPresets();setShowGeneratorModal(false);}}},/*#__PURE__*/React.createElement("div",{className:"w-full h-full max-w-6xl max-h-[90vh] bg-[#13141a] border border-zinc-800 rounded-2xl shadow-2xl overflow-hidden flex flex-col"},/*#__PURE__*/React.createElement("div",{className:"flex items-center justify-between p-3 border-b border-zinc-800 shrink-0"},/*#__PURE__*/React.createElement("h3",{className:"text-sm font-bold text-purple-400"},"AI Prompt Generator"),/*#__PURE__*/React.createElement("button",{onClick:()=>{refreshPresets();setShowGeneratorModal(false);},className:"text-zinc-400 hover:text-white"},/*#__PURE__*/React.createElement("i",{"data-lucide":"x",className:"w-4 h-4"}))),/*#__PURE__*/React.createElement("iframe",{src:"/ai-prompt-generator",className:"flex-1 w-full border-0 bg-white",title:"AI Prompt Generator"}))):null);};const PianoRollTabEditor=({st,zoom,bpm,viewportWidth,activeTracks,onClose,onUpdateNotes,onSaveNotes,setSubTabs,onPlayPause,onStop,isPlaying,playPreviewNote,showToast,midiDevices,recordingState,recTempMidiNotes,onRecord,selectedMidiInputId,onMidiInputSelect,activeMidiPitches,onInstrumentSelect,onRescheduleMidi,onSeekPlayhead,snapValue,onSnapChange,onRealtimePlay,onCopyNotes,clipboardNotes,onExportMidiAudio})=>{const[activeRollTool,setActiveRollTool]=React.useState('select');const[renderTick,setRenderTick]=React.useState(0);const[ccMode,setCcMode]=React.useState('velocity');const[rollZoom,setRollZoom]=React.useState(()=>{// Persist zoom piano roll qua phiên (localStorage) — user yêu cầu giữ kích @@ -747,7 +747,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})=>{if(!open)return null;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:"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-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"},"SR (Hz)"),/*#__PURE__*/React.createElement("select",{value:exportSettings.sampleRate,onChange:e=>setExportSettings(p=>({...p,sampleRate:e.target.value})),className:"w-full bg-[#141414] border border-zinc-800 rounded px-1 py-0.5 text-xs text-zinc-300 focus:outline-none"},/*#__PURE__*/React.createElement("option",{value:"22500"},"22500"),/*#__PURE__*/React.createElement("option",{value:"44100"},"44100"))),/*#__PURE__*/React.createElement("div",null,/*#__PURE__*/React.createElement("label",{className:"block text-[7px] text-zinc-500 font-bold uppercase mb-0.5"},"Bit"),/*#__PURE__*/React.createElement("select",{value:exportSettings.bitDepth,onChange:e=>setExportSettings(p=>({...p,bitDepth:e.target.value})),className:"w-full bg-[#141414] border border-zinc-800 rounded px-1 py-0.5 text-xs text-zinc-300 focus:outline-none"},/*#__PURE__*/React.createElement("option",{value:"8"},"8"),/*#__PURE__*/React.createElement("option",{value:"16"},"16"),/*#__PURE__*/React.createElement("option",{value:"24"},"24"),/*#__PURE__*/React.createElement("option",{value:"float"},"32 float")))):/*#__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("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"},"Kênh"),/*#__PURE__*/React.createElement("select",{value:exportSettings.channels,onChange:e=>setExportSettings(p=>({...p,channels:e.target.value})),className:"w-full bg-[#141414] border border-zinc-800 rounded px-1 py-0.5 text-xs text-zinc-300 focus:outline-none"},/*#__PURE__*/React.createElement("option",{value:"mono"},"Mono"),/*#__PURE__*/React.createElement("option",{value:"stereo"},"Stereo"))),/*#__PURE__*/React.createElement("div",null)),/*#__PURE__*/React.createElement("div",{className:"grid grid-cols-2 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')))));};// --- 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 \xB7 Wings/Wheel: Q \xB7 Dbl-click: add/delete"))));};const ExportModal=({open,onClose,exportSettings,setExportSettings,isExporting,onExport,onBounce})=>{if(!open)return null;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:"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\u1ED3n"),/*#__PURE__*/React.createElement("select",{value:exportSettings.source,onChange:e=>setExportSettings(p=>({...p,source:e.target.value})),className:"w-full bg-[#141414] border border-zinc-800 rounded px-1 py-0.5 text-xs text-zinc-300 focus:outline-none"},/*#__PURE__*/React.createElement("option",{value:"project"},"Project (Mix)"),/*#__PURE__*/React.createElement("option",{value:"track_mix"},"Track Selection"),/*#__PURE__*/React.createElement("option",{value:"active_clip"},"Active Clip"),/*#__PURE__*/React.createElement("option",{value:"clip_selection"},"Clip Selection"))),/*#__PURE__*/React.createElement("div",null,/*#__PURE__*/React.createElement("label",{className:"block text-[7px] text-zinc-500 font-bold uppercase mb-0.5"},"\u0110\u1ECBnh d\u1EA1ng"),/*#__PURE__*/React.createElement("select",{value:exportSettings.format,onChange:e=>setExportSettings(p=>({...p,format:e.target.value,sampleRate:'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-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"},"SR (Hz)"),/*#__PURE__*/React.createElement("select",{value:exportSettings.sampleRate,onChange:e=>setExportSettings(p=>({...p,sampleRate:e.target.value})),className:"w-full bg-[#141414] border border-zinc-800 rounded px-1 py-0.5 text-xs text-zinc-300 focus:outline-none"},/*#__PURE__*/React.createElement("option",{value:"22500"},"22500"),/*#__PURE__*/React.createElement("option",{value:"44100"},"44100"))),/*#__PURE__*/React.createElement("div",null,/*#__PURE__*/React.createElement("label",{className:"block text-[7px] text-zinc-500 font-bold uppercase mb-0.5"},"Bit"),/*#__PURE__*/React.createElement("select",{value:exportSettings.bitDepth,onChange:e=>setExportSettings(p=>({...p,bitDepth:e.target.value})),className:"w-full bg-[#141414] border border-zinc-800 rounded px-1 py-0.5 text-xs text-zinc-300 focus:outline-none"},/*#__PURE__*/React.createElement("option",{value:"8"},"8"),/*#__PURE__*/React.createElement("option",{value:"16"},"16"),/*#__PURE__*/React.createElement("option",{value:"24"},"24"),/*#__PURE__*/React.createElement("option",{value:"float"},"32 float")))):/*#__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\u1EA5t l\u01B0\u1EE3ng"),/*#__PURE__*/React.createElement("select",{value:exportSettings.quality,onChange:e=>setExportSettings(p=>({...p,quality:e.target.value})),className:"w-full bg-[#141414] border border-zinc-800 rounded px-1 py-0.5 text-xs text-zinc-300 focus:outline-none"},/*#__PURE__*/React.createElement("option",{value:"44khz"},"44kHz"),/*#__PURE__*/React.createElement("option",{value:"lossless"},"Lossless"))),/*#__PURE__*/React.createElement("div",null)),/*#__PURE__*/React.createElement("div",{className:"grid grid-cols-2 gap-2"},/*#__PURE__*/React.createElement("div",null,/*#__PURE__*/React.createElement("label",{className:"block text-[7px] text-zinc-500 font-bold uppercase mb-0.5"},"K\xEAnh"),/*#__PURE__*/React.createElement("select",{value:exportSettings.channels,onChange:e=>setExportSettings(p=>({...p,channels:e.target.value})),className:"w-full bg-[#141414] border border-zinc-800 rounded px-1 py-0.5 text-xs text-zinc-300 focus:outline-none"},/*#__PURE__*/React.createElement("option",{value:"mono"},"Mono"),/*#__PURE__*/React.createElement("option",{value:"stereo"},"Stereo"))),/*#__PURE__*/React.createElement("div",null)),/*#__PURE__*/React.createElement("div",{className:"grid grid-cols-2 gap-2 pt-1"},/*#__PURE__*/React.createElement("button",{onClick:onBounce,disabled:isExporting,title:"Bounce realtime \u2014 file WAV \u0111\u1EA7y \u0111\u1EE7 MIDI + FX Rack + Mastering Chain (ch\u1EA1y l\u1EA1i project th\u1EADt)",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')))));};// --- 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; @@ -765,10 +765,10 @@ React.useEffect(()=>{let alive=true;const maybeShow=()=>{const r=screenRectRef.c // re-anchors over the panel on scroll/drag/resize. Idempotent: the // bridge skips SetWindowPos/resize when nothing moved. try{fetch(url+'/show',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({rect:r})}).catch(()=>{});}catch(e){}}};const refresh=()=>{const box=boxRef.current;if(!box)return;const r=box.getBoundingClientRect();if(r.width<4||r.height<4)return;try{const tw=window.__TAURI__&&window.__TAURI__.window;if(tw&&tw.getCurrentWindow){Promise.all([tw.getCurrentWindow().innerPosition(),tw.getCurrentWindow().scaleFactor()]).then(([pos,sf])=>{if(!alive||!boxRef.current)return;const rb=boxRef.current.getBoundingClientRect();screenRectRef.current={x:Math.round(pos.x+rb.left*sf),y:Math.round(pos.y+rb.top*sf),w:Math.round(rb.width*sf),h:Math.round(rb.height*sf)};maybeShow();}).catch(()=>{if(!alive||!boxRef.current)return;const rb=boxRef.current.getBoundingClientRect();screenRectRef.current={x:Math.round((window.screenX||0)+rb.left),y:Math.round((window.screenY||0)+rb.top),w:Math.round(rb.width),h:Math.round(rb.height)};maybeShow();});}else{screenRectRef.current={x:Math.round((window.screenX||0)+r.left),y:Math.round((window.screenY||0)+r.top),w:Math.round(r.width),h:Math.round(r.height)};maybeShow();}}catch(e){}};refresh();const iv=setInterval(refresh,500);return()=>{alive=false;clearInterval(iv);};// eslint-disable-next-line react-hooks/exhaustive-deps -},[url]);const pt=e=>{const box=boxRef.current,img=imgRef.current;if(!box||!img||!img.naturalWidth||!img.naturalHeight)return null;const r=box.getBoundingClientRect();if(r.width===0||r.height===0)return null;return{x:Math.round((e.clientX-r.left)/r.width*img.naturalWidth),y:Math.round((e.clientY-r.top)/r.height*img.naturalHeight)};};const handle=e=>{const p=pt(e);if(!p)return;post({type:e.type==='mousedown'?'mousedown':e.type==='mouseup'?'mouseup':'mousemove',x:p.x,y:p.y,button:e.button,buttons:e.buttons,rect:screenRectRef.current||undefined});};const close=()=>{try{fetch(url+'/close',{method:'POST'}).catch(()=>{});}catch(e){}onClose&&onClose();};return/*#__PURE__*/React.createElement("div",{className:"relative w-full h-full flex flex-col"},/*#__PURE__*/React.createElement("div",{className:"flex items-center justify-between px-3 py-1.5 bg-slate-900/80 border border-slate-800 rounded-t-lg shrink-0"},/*#__PURE__*/React.createElement("span",{className:"text-[10px] text-violet-300 font-mono"},"VST GUI — nhúng (frame-capture · ~10fps)"),/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-2"},dead&&/*#__PURE__*/React.createElement("span",{className:"text-[9px] text-red-400 font-mono"},"bridge mất kết nối"),/*#__PURE__*/React.createElement("button",{onClick:close,className:"px-2 py-0.5 bg-red-900/60 hover:bg-red-800 text-red-100 text-[10px] font-semibold rounded transition",title:"Đóng GUI và trả về graph"},"✕ Đóng GUI"))),/*#__PURE__*/React.createElement("div",{ref:boxRef,tabIndex:0,className:"flex-1 relative bg-slate-950 border border-slate-800 rounded-b-lg overflow-hidden outline-none",onMouseDown:handle,onMouseUp:handle,onMouseMove:handle,onMouseLeave:()=>post({type:'mouseup',x:-1,y:-1,button:0}),onWheel:e=>{const p=pt(e);if(p)post({type:'wheel',x:p.x,y:p.y,deltaY:e.deltaY,rect:screenRectRef.current||undefined});},onKeyDown:e=>{e.preventDefault();post({type:'keydown',keyCode:e.keyCode||e.which});},onKeyUp:e=>{e.preventDefault();post({type:'keyup',keyCode:e.keyCode||e.which});}},/*#__PURE__*/React.createElement("img",{ref:imgRef,alt:"VST GUI",className:"w-full h-full object-contain select-none pointer-events-none",draggable:false})));};const vstRow=(s,idx,toggleV,changeV,uploadV,moveV,removeV,fxl,chainLen,openGui,showCarla=true,activeIdx=-1,onSelect=null)=>/*#__PURE__*/React.createElement("div",{key:idx,onClick:()=>onSelect&&onSelect(s,idx),className:`flex items-center gap-2 bg-slate-900/60 border rounded-lg px-2 py-1.5 flex-wrap cursor-pointer transition-all ${activeIdx===idx?'border-teal-400 bg-slate-800/80':'border-slate-800 hover:border-slate-600'}`},/*#__PURE__*/React.createElement("button",{onClick:e=>{e.stopPropagation();toggleV(idx);},className:`w-4 h-4 rounded-full flex items-center justify-center text-[9px] font-bold shrink-0 ${!s.bypass?'bg-teal-500 text-slate-950':'bg-slate-700 text-slate-300'}`,title:s.bypass?'Bypass':'Active'},/*#__PURE__*/React.createElement("i",{"data-lucide":"power",className:"w-2.5 h-2.5"})),/*#__PURE__*/React.createElement("span",{className:"text-[10px] font-bold text-slate-300 font-mono w-6 shrink-0"},idx+1),/*#__PURE__*/React.createElement("select",{value:s.path||'',onClick:e=>e.stopPropagation(),onChange:e=>changeV(idx,e.target.value),className:"flex-1 min-w-[200px] bg-slate-950 border border-slate-700 text-teal-300 rounded px-2 py-1 text-[11px] outline-none focus:border-teal-500"},/*#__PURE__*/React.createElement("option",{value:""},"— Chọn VST FX —"),(fxl||[]).map(f=>/*#__PURE__*/React.createElement("option",{key:f.path,value:f.path},f.name||f.id))),/*#__PURE__*/React.createElement("span",{className:`text-[9px] font-mono ${s.preset_b64?'text-emerald-400':'text-slate-600'}`},s.preset_b64?'✓ preset':'no preset'),/*#__PURE__*/React.createElement("button",{onClick:e=>{e.stopPropagation();uploadV(idx);},className:"px-2 py-1 bg-slate-800 hover:bg-slate-700 text-slate-300 text-[10px] font-semibold rounded transition",title:"Upload .vstpreset (Save preset)"},"Preset"),openGui&&/*#__PURE__*/React.createElement("button",{onClick:e=>{e.stopPropagation();openGui(s,idx);},disabled:!s.path,className:"px-2 py-1 bg-violet-800 hover:bg-violet-700 disabled:opacity-30 text-violet-100 text-[10px] font-semibold rounded transition",title:"Mở GUI của VST (chỉnh thông số trực tiếp)"},/*#__PURE__*/React.createElement("i",{"data-lucide":"monitor",className:"w-3 h-3"})," GUI"),showCarla&&/*#__PURE__*/React.createElement("button",{onClick:e=>{e.stopPropagation();if(!s.path){window.showToast&&window.showToast('Chọn VST FX trước','warning');return;}window.SonicAPI.openInCarla(s.name||s.path,s.path).then(r=>{if(r&&r.success)window.showToast&&window.showToast('Đã mở Carla với '+(s.name||s.path)+' — chỉnh âm trong Carla rồi Save preset','success');else window.showToast&&window.showToast('Không mở được Carla','error');}).catch(err=>window.showToast&&window.showToast('Lỗi mở Carla: '+(err.message||err),'error'));},className:"px-2 py-1 bg-teal-800 hover:bg-teal-700 text-teal-100 text-[10px] font-semibold rounded transition",title:"Mở native GUI trong Carla"},"Carla"),/*#__PURE__*/React.createElement("button",{onClick:e=>{e.stopPropagation();moveV(idx,-1);},disabled:idx===0,className:"w-6 h-6 bg-slate-800 hover:bg-slate-700 text-slate-300 rounded text-xs disabled:opacity-30",title:"Lên"},/*#__PURE__*/React.createElement("i",{"data-lucide":"chevron-up",className:"w-3 h-3"})),/*#__PURE__*/React.createElement("button",{onClick:e=>{e.stopPropagation();moveV(idx,1);},disabled:idx===chainLen-1,className:"w-6 h-6 bg-slate-800 hover:bg-slate-700 text-slate-300 rounded text-xs disabled:opacity-30",title:"Xuống"},/*#__PURE__*/React.createElement("i",{"data-lucide":"chevron-down",className:"w-3 h-3"})),/*#__PURE__*/React.createElement("button",{onClick:e=>{e.stopPropagation();removeV(idx);},className:"text-slate-600 hover:text-red-400 text-xs px-0.5",title:"Xóa slot"},/*#__PURE__*/React.createElement("i",{"data-lucide":"x",className:"w-3 h-3"})));// ── VST FX Plugin Picker (spec vst_fx_panel_integration.md §II) ────────── +},[url]);const pt=e=>{const box=boxRef.current,img=imgRef.current;if(!box||!img||!img.naturalWidth||!img.naturalHeight)return null;const r=box.getBoundingClientRect();if(r.width===0||r.height===0)return null;return{x:Math.round((e.clientX-r.left)/r.width*img.naturalWidth),y:Math.round((e.clientY-r.top)/r.height*img.naturalHeight)};};const handle=e=>{const p=pt(e);if(!p)return;post({type:e.type==='mousedown'?'mousedown':e.type==='mouseup'?'mouseup':'mousemove',x:p.x,y:p.y,button:e.button,buttons:e.buttons,rect:screenRectRef.current||undefined});};const close=()=>{try{fetch(url+'/close',{method:'POST'}).catch(()=>{});}catch(e){}onClose&&onClose();};return/*#__PURE__*/React.createElement("div",{className:"relative w-full h-full flex flex-col"},/*#__PURE__*/React.createElement("div",{className:"flex items-center justify-between px-3 py-1.5 bg-slate-900/80 border border-slate-800 rounded-t-lg shrink-0"},/*#__PURE__*/React.createElement("span",{className:"text-[10px] text-violet-300 font-mono"},"VST GUI \u2014 nh\xFAng (frame-capture \xB7 ~10fps)"),/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-2"},dead&&/*#__PURE__*/React.createElement("span",{className:"text-[9px] text-red-400 font-mono"},"bridge m\u1EA5t k\u1EBFt n\u1ED1i"),/*#__PURE__*/React.createElement("button",{onClick:close,className:"px-2 py-0.5 bg-red-900/60 hover:bg-red-800 text-red-100 text-[10px] font-semibold rounded transition",title:"\u0110\xF3ng GUI v\xE0 tr\u1EA3 v\u1EC1 graph"},"\u2715 \u0110\xF3ng GUI"))),/*#__PURE__*/React.createElement("div",{ref:boxRef,tabIndex:0,className:"flex-1 relative bg-slate-950 border border-slate-800 rounded-b-lg overflow-hidden outline-none",onMouseDown:handle,onMouseUp:handle,onMouseMove:handle,onMouseLeave:()=>post({type:'mouseup',x:-1,y:-1,button:0}),onWheel:e=>{const p=pt(e);if(p)post({type:'wheel',x:p.x,y:p.y,deltaY:e.deltaY,rect:screenRectRef.current||undefined});},onKeyDown:e=>{e.preventDefault();post({type:'keydown',keyCode:e.keyCode||e.which});},onKeyUp:e=>{e.preventDefault();post({type:'keyup',keyCode:e.keyCode||e.which});}},/*#__PURE__*/React.createElement("img",{ref:imgRef,alt:"VST GUI",className:"w-full h-full object-contain select-none pointer-events-none",draggable:false})));};const vstRow=(s,idx,toggleV,changeV,uploadV,moveV,removeV,fxl,chainLen,openGui,showCarla=true,activeIdx=-1,onSelect=null)=>/*#__PURE__*/React.createElement("div",{key:idx,onClick:()=>onSelect&&onSelect(s,idx),className:`flex items-center gap-2 bg-slate-900/60 border rounded-lg px-2 py-1.5 flex-wrap cursor-pointer transition-all ${activeIdx===idx?'border-teal-400 bg-slate-800/80':'border-slate-800 hover:border-slate-600'}`},/*#__PURE__*/React.createElement("button",{onClick:e=>{e.stopPropagation();toggleV(idx);},className:`w-4 h-4 rounded-full flex items-center justify-center text-[9px] font-bold shrink-0 ${!s.bypass?'bg-teal-500 text-slate-950':'bg-slate-700 text-slate-300'}`,title:s.bypass?'Bypass':'Active'},/*#__PURE__*/React.createElement("i",{"data-lucide":"power",className:"w-2.5 h-2.5"})),/*#__PURE__*/React.createElement("span",{className:"text-[10px] font-bold text-slate-300 font-mono w-6 shrink-0"},idx+1),/*#__PURE__*/React.createElement("select",{value:s.path||'',onClick:e=>e.stopPropagation(),onChange:e=>changeV(idx,e.target.value),className:"flex-1 min-w-[200px] bg-slate-950 border border-slate-700 text-teal-300 rounded px-2 py-1 text-[11px] outline-none focus:border-teal-500"},/*#__PURE__*/React.createElement("option",{value:""},"\u2014 Ch\u1ECDn VST FX \u2014"),(fxl||[]).map(f=>/*#__PURE__*/React.createElement("option",{key:f.path,value:f.path},f.name||f.id))),/*#__PURE__*/React.createElement("span",{className:`text-[9px] font-mono ${s.preset_b64?'text-emerald-400':'text-slate-600'}`},s.preset_b64?'✓ preset':'no preset'),/*#__PURE__*/React.createElement("button",{onClick:e=>{e.stopPropagation();uploadV(idx);},className:"px-2 py-1 bg-slate-800 hover:bg-slate-700 text-slate-300 text-[10px] font-semibold rounded transition",title:"Upload .vstpreset (Save preset)"},"Preset"),openGui&&/*#__PURE__*/React.createElement("button",{onClick:e=>{e.stopPropagation();openGui(s,idx);},disabled:!s.path,className:"px-2 py-1 bg-violet-800 hover:bg-violet-700 disabled:opacity-30 text-violet-100 text-[10px] font-semibold rounded transition",title:"M\u1EDF GUI c\u1EE7a VST (ch\u1EC9nh th\xF4ng s\u1ED1 tr\u1EF1c ti\u1EBFp)"},/*#__PURE__*/React.createElement("i",{"data-lucide":"monitor",className:"w-3 h-3"})," GUI"),showCarla&&/*#__PURE__*/React.createElement("button",{onClick:e=>{e.stopPropagation();if(!s.path){window.showToast&&window.showToast('Chọn VST FX trước','warning');return;}window.SonicAPI.openInCarla(s.name||s.path,s.path).then(r=>{if(r&&r.success)window.showToast&&window.showToast('Đã mở Carla với '+(s.name||s.path)+' — chỉnh âm trong Carla rồi Save preset','success');else window.showToast&&window.showToast('Không mở được Carla','error');}).catch(err=>window.showToast&&window.showToast('Lỗi mở Carla: '+(err.message||err),'error'));},className:"px-2 py-1 bg-teal-800 hover:bg-teal-700 text-teal-100 text-[10px] font-semibold rounded transition",title:"M\u1EDF native GUI trong Carla"},"Carla"),/*#__PURE__*/React.createElement("button",{onClick:e=>{e.stopPropagation();moveV(idx,-1);},disabled:idx===0,className:"w-6 h-6 bg-slate-800 hover:bg-slate-700 text-slate-300 rounded text-xs disabled:opacity-30",title:"L\xEAn"},/*#__PURE__*/React.createElement("i",{"data-lucide":"chevron-up",className:"w-3 h-3"})),/*#__PURE__*/React.createElement("button",{onClick:e=>{e.stopPropagation();moveV(idx,1);},disabled:idx===chainLen-1,className:"w-6 h-6 bg-slate-800 hover:bg-slate-700 text-slate-300 rounded text-xs disabled:opacity-30",title:"Xu\u1ED1ng"},/*#__PURE__*/React.createElement("i",{"data-lucide":"chevron-down",className:"w-3 h-3"})),/*#__PURE__*/React.createElement("button",{onClick:e=>{e.stopPropagation();removeV(idx);},className:"text-slate-600 hover:text-red-400 text-xs px-0.5",title:"X\xF3a slot"},/*#__PURE__*/React.createElement("i",{"data-lucide":"x",className:"w-3 h-3"})));// ── VST FX Plugin Picker (spec vst_fx_panel_integration.md §II) ────────── // Modal chọn VST FX theo category (heuristic từ backend list_fx.category). // Dùng chung FXRackModal + MasteringModal: [+] → picker → chèn VST card. -const VST_FX_CATEGORIES=[{id:'all',label:'Tất cả'},{id:'equalizer',label:'Equalizers / Dynamic EQ'},{id:'reverb_delay',label:'Reverbs & Delays'},{id:'comp_limiter',label:'Compressors & Limiters'},{id:'distortion',label:'Distortion / Saturation'},{id:'utility',label:'Utility'}];const VstPluginPicker=({open,onClose,plugins,onPick})=>{const[cat,setCat]=React.useState('all');React.useEffect(()=>{if(open)setCat('all');},[open]);if(!open)return null;const list=(plugins||[]).filter(p=>cat==='all'||(p.category||'utility')===cat);return/*#__PURE__*/React.createElement("div",{className:"fixed inset-0 z-[125] bg-black/80 backdrop-blur-sm flex items-center justify-center p-4",onClick:onClose},/*#__PURE__*/React.createElement("div",{className:"w-full max-w-2xl bg-slate-900 border border-slate-700 rounded-2xl p-5 space-y-4 shadow-2xl max-h-[80vh] flex flex-col",onClick:e=>e.stopPropagation()},/*#__PURE__*/React.createElement("div",{className:"flex items-center justify-between border-b border-slate-800 pb-3"},/*#__PURE__*/React.createElement("h3",{className:"text-xs font-bold text-white uppercase tracking-wider font-mono"},"CHỌN VST FX — PLUGIN PICKER"),/*#__PURE__*/React.createElement("button",{onClick:onClose,className:"text-slate-400 hover:text-white"},/*#__PURE__*/React.createElement("i",{"data-lucide":"x",className:"w-4 h-4"}))),/*#__PURE__*/React.createElement("div",{className:"flex flex-wrap gap-1.5 shrink-0"},VST_FX_CATEGORIES.map(c=>/*#__PURE__*/React.createElement("button",{key:c.id,onClick:()=>setCat(c.id),className:`px-2.5 py-1 rounded text-[10px] font-semibold transition ${cat===c.id?'bg-teal-700 text-white':'bg-slate-800 hover:bg-slate-700 text-slate-300'}`},c.label))),/*#__PURE__*/React.createElement("div",{className:"flex-1 overflow-y-auto oz-scrollbar grid grid-cols-2 gap-2"},list.length===0&&/*#__PURE__*/React.createElement("div",{className:"col-span-2 text-[11px] text-slate-500 font-mono py-10 text-center"},"Không có VST FX trong mục này."),list.map(f=>/*#__PURE__*/React.createElement("button",{key:f.path,onClick:()=>{onPick(f);onClose();},className:"p-2 bg-slate-950 hover:bg-slate-800 border border-slate-800 rounded-lg text-left space-y-0.5 transition-colors"},/*#__PURE__*/React.createElement("div",{className:"text-[11px] font-bold text-teal-300 flex items-center gap-1.5"},/*#__PURE__*/React.createElement("i",{"data-lucide":"plug",className:"w-3 h-3"})," ",f.name||f.id),/*#__PURE__*/React.createElement("div",{className:"text-[9px] text-slate-500 font-mono truncate"},f.path))))));};// GUI embed state survives FXRackModal unmount so the native window +const VST_FX_CATEGORIES=[{id:'all',label:'Tất cả'},{id:'equalizer',label:'Equalizers / Dynamic EQ'},{id:'reverb_delay',label:'Reverbs & Delays'},{id:'comp_limiter',label:'Compressors & Limiters'},{id:'distortion',label:'Distortion / Saturation'},{id:'utility',label:'Utility'}];const VstPluginPicker=({open,onClose,plugins,onPick})=>{const[cat,setCat]=React.useState('all');React.useEffect(()=>{if(open)setCat('all');},[open]);if(!open)return null;const list=(plugins||[]).filter(p=>cat==='all'||(p.category||'utility')===cat);return/*#__PURE__*/React.createElement("div",{className:"fixed inset-0 z-[125] bg-black/80 backdrop-blur-sm flex items-center justify-center p-4",onClick:onClose},/*#__PURE__*/React.createElement("div",{className:"w-full max-w-2xl bg-slate-900 border border-slate-700 rounded-2xl p-5 space-y-4 shadow-2xl max-h-[80vh] flex flex-col",onClick:e=>e.stopPropagation()},/*#__PURE__*/React.createElement("div",{className:"flex items-center justify-between border-b border-slate-800 pb-3"},/*#__PURE__*/React.createElement("h3",{className:"text-xs font-bold text-white uppercase tracking-wider font-mono"},"CH\u1ECCN VST FX \u2014 PLUGIN PICKER"),/*#__PURE__*/React.createElement("button",{onClick:onClose,className:"text-slate-400 hover:text-white"},/*#__PURE__*/React.createElement("i",{"data-lucide":"x",className:"w-4 h-4"}))),/*#__PURE__*/React.createElement("div",{className:"flex flex-wrap gap-1.5 shrink-0"},VST_FX_CATEGORIES.map(c=>/*#__PURE__*/React.createElement("button",{key:c.id,onClick:()=>setCat(c.id),className:`px-2.5 py-1 rounded text-[10px] font-semibold transition ${cat===c.id?'bg-teal-700 text-white':'bg-slate-800 hover:bg-slate-700 text-slate-300'}`},c.label))),/*#__PURE__*/React.createElement("div",{className:"flex-1 overflow-y-auto oz-scrollbar grid grid-cols-2 gap-2"},list.length===0&&/*#__PURE__*/React.createElement("div",{className:"col-span-2 text-[11px] text-slate-500 font-mono py-10 text-center"},"Kh\xF4ng c\xF3 VST FX trong m\u1EE5c n\xE0y."),list.map(f=>/*#__PURE__*/React.createElement("button",{key:f.path,onClick:()=>{onPick(f);onClose();},className:"p-2 bg-slate-950 hover:bg-slate-800 border border-slate-800 rounded-lg text-left space-y-0.5 transition-colors"},/*#__PURE__*/React.createElement("div",{className:"text-[11px] font-bold text-teal-300 flex items-center gap-1.5"},/*#__PURE__*/React.createElement("i",{"data-lucide":"plug",className:"w-3 h-3"})," ",f.name||f.id),/*#__PURE__*/React.createElement("div",{className:"text-[9px] text-slate-500 font-mono truncate"},f.path))))));};// GUI embed state survives FXRackModal unmount so the native window // re-shows automatically when the rack is reopened (close/reopen bug). let _persistFxGuiOpen=null;// Track id whose rack owns the currently-open embedded GUI (for save). let _persistFxGuiTrackId=null;// Latest preset_b64 captured from each plugin's embedded GUI (poll /preset) @@ -795,7 +795,7 @@ ctx.strokeStyle='rgba(51, 65, 85, 0.35)';for(let i=1;i<5;i++){const y=i/5*h;ctx. const bands=[{type:'lowshelf',f0:100,gain:ap.g1||0,q:0.7},{type:'peaking',f0:800,gain:ap.g2||0,q:0.7},{type:'peaking',f0:3200,gain:ap.g3||0,q:1.2},{type:'highshelf',f0:10000,gain:ap.g4||0,q:0.7}];const pts=[];const N=120;for(let i=0;i<=N;i++){const f=20*Math.pow(20000/20,i/N);let db=0;bands.forEach(b=>{const lf=Math.log(f/b.f0);if(b.type==='peaking'){db+=b.gain/(1+Math.pow(lf*b.q,2));}else if(b.type==='highshelf'){db+=b.gain/2*(1+2/Math.PI*Math.atan(lf/(1/b.q)));}else{db+=b.gain/2*(1-2/Math.PI*Math.atan(lf/(1/b.q)));}});const x=i/N*w;const y=midY-db/12*(h/2);pts.push([x,y]);}// fill ctx.beginPath();pts.forEach(([x,y],i)=>i===0?ctx.moveTo(x,y):ctx.lineTo(x,y));ctx.lineTo(w,h);ctx.lineTo(0,h);ctx.closePath();ctx.fillStyle='rgba(34, 211, 238, 0.10)';ctx.fill();// curve ctx.beginPath();pts.forEach(([x,y],i)=>i===0?ctx.moveTo(x,y):ctx.lineTo(x,y));ctx.strokeStyle='#22d3ee';ctx.lineWidth=2;ctx.stroke();// dB labels -ctx.fillStyle='#475569';ctx.font='9px monospace';ctx.fillText('+12 dB',4,midY-h/2+10);ctx.fillText('0 dB',4,midY+3);ctx.fillText('-12 dB',4,midY+h/2-4);ctx.fillText('20Hz',4,h-2);ctx.fillText('20kHz',w-38,h-2);},[ap.g1,ap.g2,ap.g3,ap.g4,activeMod]);return/*#__PURE__*/React.createElement("div",{className:"fixed inset-0 z-[115] bg-black/80 backdrop-blur-sm flex items-center justify-center p-4",onClick:onClose},/*#__PURE__*/React.createElement("div",{className:"w-full max-w-4xl bg-slate-900 border border-slate-700 rounded-2xl p-5 space-y-4 shadow-2xl flex flex-col max-h-[90vh] text-slate-200",onClick:e=>e.stopPropagation()},/*#__PURE__*/React.createElement("div",{className:"flex items-center justify-between border-b border-slate-800 pb-3"},/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-2"},/*#__PURE__*/React.createElement("div",{className:"w-3 h-3 rounded-full bg-cyan-400 animate-pulse"}),/*#__PURE__*/React.createElement("h3",{className:"text-xs font-bold text-white uppercase tracking-wider font-mono"},track.name," — FX RACK PANEL")),/*#__PURE__*/React.createElement("button",{onClick:onClose,className:"text-slate-400 hover:text-white text-base"},/*#__PURE__*/React.createElement("i",{"data-lucide":"x",className:"w-4 h-4"}))),/*#__PURE__*/React.createElement("div",{className:"h-16 bg-slate-950 border border-slate-800 rounded-xl px-3 flex items-center gap-2 overflow-x-auto shrink-0 select-none"},/*#__PURE__*/React.createElement("span",{className:"text-[10px] font-bold text-slate-500 uppercase tracking-widest mr-1 font-mono shrink-0"},"CHAIN:"),chain.length===0&&/*#__PURE__*/React.createElement("span",{className:"text-[10px] text-slate-500 font-mono"},"Chưa có FX — bấm [+] để thêm (signal đi thẳng: Source → Fader)"),chain.map((m,idx)=>{const meta=TRACK_FX_META[m.type]||{name:m.type,icon:'circle',color:'#94a3b8',sub:''};const on=m.active!==false;return/*#__PURE__*/React.createElement("div",{key:idx,draggable:true,onDragStart:e=>{dragChainIndexRef.current=idx;e.dataTransfer.effectAllowed='move';},onDragOver:e=>{e.preventDefault();e.dataTransfer.dropEffect='move';},onDrop:e=>{e.preventDefault();const from=dragChainIndexRef.current;if(from!==null&&from!==idx){const next=[...chain];const mv=next.splice(from,1)[0];next.splice(idx,0,mv);setChain(next);}dragChainIndexRef.current=null;},onClick:()=>{closeFxGui();setActiveType(m.type);},className:`w-36 h-12 rounded-lg p-2 flex items-center justify-between cursor-pointer transition-all shrink-0 ${activeType===m.type?'border-2 border-cyan-400 bg-slate-800':'bg-slate-900 border border-slate-800 hover:border-slate-600'}`},/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-2 min-w-0"},/*#__PURE__*/React.createElement("button",{onClick:e=>{e.stopPropagation();toggleMod(idx);},className:"w-4 h-4 rounded-full flex items-center justify-center text-[9px] font-bold shrink-0",style:{backgroundColor:on?'#38bdf8':'#334155',color:on?'#0f172a':'#94a3b8'}},/*#__PURE__*/React.createElement("i",{"data-lucide":"power",className:"w-2.5 h-2.5"})),/*#__PURE__*/React.createElement("div",{className:"min-w-0"},/*#__PURE__*/React.createElement("div",{className:"text-[11px] font-bold text-slate-200 truncate"},meta.name),/*#__PURE__*/React.createElement("div",{className:"text-[9px] font-mono truncate",style:{color:meta.color}},idx+1,". ",meta.sub))),/*#__PURE__*/React.createElement("button",{onClick:e=>{e.stopPropagation();removeMod(idx);},className:"text-slate-600 hover:text-red-400 text-xs px-0.5 shrink-0",title:"Xóa module"},/*#__PURE__*/React.createElement("i",{"data-lucide":"x",className:"w-3 h-3"})));}),/*#__PURE__*/React.createElement("button",{onClick:()=>setAddModuleOpen(true),className:"w-16 h-12 rounded-lg border border-dashed border-slate-700 hover:border-cyan-500 flex items-center justify-center text-slate-500 hover:text-cyan-400 cursor-pointer transition-all bg-slate-900/40 shrink-0",title:"Thêm module"},/*#__PURE__*/React.createElement("i",{"data-lucide":"plus",className:"w-4 h-4"}))),/*#__PURE__*/React.createElement("div",{className:"bg-slate-950 border border-teal-900/60 rounded-xl px-3 py-2 flex flex-col gap-2 shrink-0 select-none"},/*#__PURE__*/React.createElement("div",{className:"flex items-center justify-between flex-wrap gap-2"},/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-2 min-w-0"},/*#__PURE__*/React.createElement("span",{className:"text-[10px] font-bold text-teal-400 uppercase tracking-widest font-mono shrink-0"},"VST FX:"),/*#__PURE__*/React.createElement("span",{className:"text-[9px] text-slate-500 font-mono truncate"},"Plugin VST3 (Ozone...) — realtime browser vẫn WebAudio builtin; VST áp dụng lúc Export/Offline render (preview = âm export)")),/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-2 shrink-0"},/*#__PURE__*/React.createElement("button",{onClick:previewFx,className:"px-2 py-1 bg-teal-800 hover:bg-teal-700 text-teal-100 text-[10px] font-semibold rounded transition flex items-center gap-1"},/*#__PURE__*/React.createElement("i",{"data-lucide":"play",className:"w-3 h-3"})," Nghe thử (render offline)"),/*#__PURE__*/React.createElement("label",{className:"flex items-center gap-1 text-[10px] text-slate-400 font-mono cursor-pointer"},/*#__PURE__*/React.createElement("input",{type:"checkbox",checked:vstBypass,onChange:e=>{const val=e.target.checked;setVstBypass(val);if(onUpdateTrack)onUpdateTrack(track.id,{vstFxBypass:val});},className:"w-3 h-3"})," Bypass"))),vstChain.length===0&&/*#__PURE__*/React.createElement("div",{className:"text-[10px] text-slate-500 font-mono"},"Chưa có VST FX — chọn plugin bên dưới để thêm vào insert chain (WebAudio chain → VST FX → Fader)."),vstChain.map((s,idx)=>vstRow(s,idx,toggleVst,changeVstPlugin,uploadVstPreset,moveVst,removeVst,fxList,vstChain.length,openVstGui,false,activeVstIdx,(s2,i2)=>openVstGui(s2,i2))),/*#__PURE__*/React.createElement("button",{onClick:()=>setVstPickerOpen(true),className:"w-full bg-slate-950 border border-dashed border-slate-700 hover:border-teal-600 text-teal-300 rounded px-2 py-1.5 text-[11px] outline-none text-left",title:"Thêm VST FX (Plugin Picker theo category)"},/*#__PURE__*/React.createElement("i",{"data-lucide":"plus",className:"w-3 h-3 inline-block mr-1"})," Thêm VST FX... (Picker: EQ / Reverb / Comp / Utility)")),fxGuiOpen?/*#__PURE__*/React.createElement("div",{className:"flex-1 min-h-0"},/*#__PURE__*/React.createElement(VstGuiEmbed,{url:fxGuiOpen.url,onClose:closeFxGui,onDead:()=>{window.showToast&&window.showToast('Bridge VST GUI đã mất kết nối — trả về graph','error');closeFxGui();}})):/*#__PURE__*/React.createElement(React.Fragment,null,/*#__PURE__*/React.createElement("div",{className:"flex-1 bg-slate-950 border border-slate-800 rounded-xl p-4 flex flex-col gap-4 min-h-[240px] overflow-y-auto oz-scrollbar"},/*#__PURE__*/React.createElement("div",{className:"h-8 bg-slate-900/80 border-b border-slate-800/80 px-3 flex items-center justify-between text-xs font-mono rounded-t-lg shrink-0"},/*#__PURE__*/React.createElement("span",{className:"text-slate-400"},"Active Module: ",/*#__PURE__*/React.createElement("strong",{className:"text-cyan-400"},activeMod?TRACK_FX_META[activeMod.type]?.name||activeMod.type:'—')),activeMod&&activeMod.type==='eq'&&/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-2"},/*#__PURE__*/React.createElement("span",{className:"text-slate-400"},"EQ Preset:"),/*#__PURE__*/React.createElement("select",{value:"flat",onChange:e=>applyPreset(e.target.value),className:"bg-slate-950 border border-slate-700 text-cyan-300 rounded px-2 py-0.5 text-xs outline-none focus:border-cyan-500"},Object.keys(TRACK_EQ_PRESETS).map(k=>/*#__PURE__*/React.createElement("option",{key:k,value:k},TRACK_EQ_PRESETS[k].name))))),!activeMod&&/*#__PURE__*/React.createElement("div",{className:"text-xs text-slate-500 font-mono text-center py-10"},"Chưa có module. Bấm [+] để thêm EQ / Compressor / Limiter / Exciter / Rebalance."),activeMod&&activeMod.type==='eq'&&activeIdx>=0&&/*#__PURE__*/React.createElement("div",{className:"space-y-3"},/*#__PURE__*/React.createElement("div",{className:"grid grid-cols-4 gap-4"},[['g1','BAND 1 (LOW)','100 Hz','#22d3ee'],['g2','BAND 2 (MID LOW)','800 Hz','#fbbf24'],['g3','BAND 3 (MID HIGH)','3.2 kHz','#a855f7'],['g4','BAND 4 (HIGH)','10 kHz','#34d399']].map(([key,label,freq,color])=>/*#__PURE__*/React.createElement("div",{key:key,className:"bg-slate-900/80 border border-slate-800 p-3 rounded-lg space-y-2"},slider(label,ap[key]!==undefined?ap[key]:0,-12,12,0.1,color,v=>setParams(activeIdx,{[key]:v}),v=>`${v>0?'+':''}${v.toFixed(1)} dB`),/*#__PURE__*/React.createElement("div",{className:"text-[9px] text-slate-500 font-mono text-center"},freq)))),/*#__PURE__*/React.createElement("div",{className:"bg-slate-900/80 border border-slate-800 rounded-lg p-2"},/*#__PURE__*/React.createElement("div",{className:"text-[9px] text-slate-500 font-mono mb-1 flex justify-between"},/*#__PURE__*/React.createElement("span",null,"VECTOR DISPLAY — EQ RESPONSE"),/*#__PURE__*/React.createElement("span",null,"20Hz – 20kHz")),/*#__PURE__*/React.createElement("canvas",{ref:eqCurveRef,className:"w-full h-[88px] block"}))),activeMod&&activeMod.type==='eqpro'&&activeIdx>=0&&/*#__PURE__*/React.createElement("div",{className:"space-y-3"},/*#__PURE__*/React.createElement("div",{className:"flex items-center justify-between"},/*#__PURE__*/React.createElement("span",{className:"text-[9px] text-teal-400 font-mono uppercase tracking-widest"},"PARAMETRIC / GRAPHIC EQ PRO — Pro-Q style interactive"),/*#__PURE__*/React.createElement("span",{className:"text-[9px] text-slate-500 font-mono"},"20Hz – 20kHz · ±24dB · ",EQPRO_MAX_BANDS," bands max")),/*#__PURE__*/React.createElement(InteractiveEqPro,{track:track,params:ap,onChange:next=>setParams(activeIdx,next),getModule:()=>window.__getTrackFxModule?window.__getTrackFxModule(track.id,'eqpro'):null,spectrumModules:()=>{const a=window.__getTrackFxModule?window.__getTrackFxModule(track.id,'eqpro'):null;const b=window.__getTrackSfFxModule?window.__getTrackSfFxModule(track.id,'eqpro'):null;return[a,b].filter(Boolean);}})),activeMod&&activeMod.type==='compressor'&&activeIdx>=0&&/*#__PURE__*/React.createElement("div",{className:"grid grid-cols-3 gap-4"},/*#__PURE__*/React.createElement("div",{className:"bg-slate-900/80 border border-slate-800 p-3 rounded-lg"},slider('THRESHOLD',ap.threshold,-60,0,0.5,'#fbbf24',v=>setParams(activeIdx,{threshold:v}),v=>`${v.toFixed(1)} dB`)),/*#__PURE__*/React.createElement("div",{className:"bg-slate-900/80 border border-slate-800 p-3 rounded-lg"},slider('RATIO',ap.ratio,1,20,0.5,'#f59e0b',v=>setParams(activeIdx,{ratio:v}),v=>`${v.toFixed(1)} : 1`)),/*#__PURE__*/React.createElement("div",{className:"bg-slate-900/80 border border-slate-800 p-3 rounded-lg"},slider('MAKE-UP',ap.makeup,0,12,0.1,'#f59e0b',v=>setParams(activeIdx,{makeup:v}),v=>`${v.toFixed(1)} dB`))),activeMod&&activeMod.type==='limiter'&&activeIdx>=0&&/*#__PURE__*/React.createElement("div",{className:"grid grid-cols-2 gap-4"},/*#__PURE__*/React.createElement("div",{className:"bg-slate-900/80 border border-slate-800 p-3 rounded-lg"},slider('CEILING',ap.ceiling,-24,0,0.1,'#f43f5e',v=>setParams(activeIdx,{ceiling:v}),v=>`${v.toFixed(1)} dB`)),/*#__PURE__*/React.createElement("div",{className:"bg-slate-900/80 border border-slate-800 p-3 rounded-lg flex items-center justify-center text-[10px] text-slate-500 font-mono text-center leading-snug"},"True-Peak limiting",/*#__PURE__*/React.createElement("br",null),"Ratio 20:1 · Knee 0dB")),activeMod&&activeMod.type==='exciter'&&activeIdx>=0&&/*#__PURE__*/React.createElement("div",{className:"grid grid-cols-2 gap-4"},/*#__PURE__*/React.createElement("div",{className:"bg-slate-900/80 border border-slate-800 p-3 rounded-lg"},slider('DRIVE / MIX',ap.drive,0,100,1,'#c084fc',v=>setParams(activeIdx,{drive:v}),v=>`${v}%`)),/*#__PURE__*/React.createElement("div",{className:"bg-slate-900/80 border border-slate-800 p-3 rounded-lg flex items-center justify-center text-[10px] text-slate-500 font-mono text-center leading-snug"},"WaveShaper saturation",/*#__PURE__*/React.createElement("br",null),"High-pass 2kHz · 4× oversampled")),activeMod&&activeMod.type==='rebalance'&&activeIdx>=0&&/*#__PURE__*/React.createElement("div",{className:"grid grid-cols-2 gap-4"},/*#__PURE__*/React.createElement("div",{className:"bg-slate-900/80 border border-slate-800 p-3 rounded-lg"},slider('MID GAIN',ap.mid,-12,12,0.1,'#38bdf8',v=>setParams(activeIdx,{mid:v}),v=>`${v>0?'+':''}${v.toFixed(1)} dB`)),/*#__PURE__*/React.createElement("div",{className:"bg-slate-900/80 border border-slate-800 p-3 rounded-lg"},slider('SIDE GAIN',ap.side,-12,12,0.1,'#22d3ee',v=>setParams(activeIdx,{side:v}),v=>`${v>0?'+':''}${v.toFixed(1)} dB`))),activeMod&&activeMod.type==='carla'&&activeIdx>=0&&/*#__PURE__*/React.createElement("div",{className:"bg-amber-950/40 border border-amber-800/60 rounded-lg p-4 space-y-3"},/*#__PURE__*/React.createElement("div",{className:"text-[10px] text-amber-300 font-mono"},"Carla Bridge đã bị gỡ khỏi FX Rack — track cũ vẫn giữ module để không mất cấu hình."),/*#__PURE__*/React.createElement("button",{onClick:()=>removeMod(activeIdx),className:"px-3 py-1.5 bg-amber-800 hover:bg-amber-700 text-amber-50 text-xs font-semibold rounded transition"},"Gỡ module Carla khỏi track"))),/*#__PURE__*/React.createElement("div",{className:"bg-slate-950 border border-slate-800 rounded-xl p-3 flex flex-col gap-2 shrink-0"},/*#__PURE__*/React.createElement("div",{className:"flex items-center justify-between text-xs font-mono border-b border-slate-800/80 pb-1.5"},/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-2"},/*#__PURE__*/React.createElement("span",{className:"font-bold text-white tracking-wider"},"WAVE OBSERVER"),/*#__PURE__*/React.createElement("span",{className:"text-[9px] bg-cyan-950 border border-cyan-800 text-cyan-400 px-1.5 py-0.5 rounded"},"Real-time Oscilloscope")),/*#__PURE__*/React.createElement("span",{className:"text-[10px] text-slate-500 truncate max-w-[200px]"},"Context: ",track.name)),/*#__PURE__*/React.createElement("div",{className:"relative w-full h-36 bg-slate-950 border border-slate-900 rounded-lg overflow-hidden"},/*#__PURE__*/React.createElement("canvas",{ref:scopeCanvasRef,className:"w-full h-full block cursor-crosshair"})),/*#__PURE__*/React.createElement("div",{className:"flex items-center justify-between bg-slate-900/90 border border-slate-800 rounded-lg px-3 py-1.5 text-xs font-mono flex-wrap gap-2"},/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-2 pr-3 border-r border-slate-800"},/*#__PURE__*/React.createElement("span",{className:"text-[10px] text-slate-400"},"Input"),/*#__PURE__*/React.createElement("div",{className:"flex flex-col gap-1 w-12"},/*#__PURE__*/React.createElement("div",{className:"h-1.5 bg-slate-950 rounded overflow-hidden flex"},/*#__PURE__*/React.createElement("div",{ref:scopeMeterLRef,className:"h-full bg-cyan-400 w-0 transition-all"})),/*#__PURE__*/React.createElement("div",{className:"h-1.5 bg-slate-950 rounded overflow-hidden flex"},/*#__PURE__*/React.createElement("div",{ref:scopeMeterRRef,className:"h-full bg-cyan-400 w-0 transition-all"})))),/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-1.5"},/*#__PURE__*/React.createElement("span",{className:"text-slate-500 text-[10px]"},"Channel:"),/*#__PURE__*/React.createElement("select",{value:scopeChannel,onChange:e=>setScopeChannel(e.target.value),className:"bg-slate-950 border border-slate-800 text-slate-300 rounded px-1.5 py-0.5 text-[11px] outline-none"},/*#__PURE__*/React.createElement("option",{value:"stereo"},"Stereo"),/*#__PURE__*/React.createElement("option",{value:"left"},"Left"),/*#__PURE__*/React.createElement("option",{value:"right"},"Right"),/*#__PURE__*/React.createElement("option",{value:"mid"},"Mid"),/*#__PURE__*/React.createElement("option",{value:"side"},"Side"))),/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-1.5"},/*#__PURE__*/React.createElement("span",{className:"text-slate-500 text-[10px]"},"Mode:"),/*#__PURE__*/React.createElement("select",{value:scopeMode,onChange:e=>setScopeMode(e.target.value),className:"bg-slate-950 border border-slate-800 text-slate-300 rounded px-1.5 py-0.5 text-[11px] outline-none"},/*#__PURE__*/React.createElement("option",{value:"waveform"},"Waveform"),/*#__PURE__*/React.createElement("option",{value:"lissajous"},"Lissajous"),/*#__PURE__*/React.createElement("option",{value:"spectrum"},"Spectrum"))),/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-1.5"},/*#__PURE__*/React.createElement("span",{className:"text-slate-500 text-[10px]"},"Duration:"),/*#__PURE__*/React.createElement("span",{className:"text-cyan-400 font-bold text-[11px] w-12"},scopeDuration.toFixed(2),"s"),/*#__PURE__*/React.createElement("input",{type:"range",min:"0.1",max:"5.0",step:"0.1",value:scopeDuration,onChange:e=>setScopeDuration(parseFloat(e.target.value)),className:"w-16 h-1 cursor-pointer",style:{accentColor:'#38bdf8'}})),/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-1.5"},/*#__PURE__*/React.createElement("span",{className:"text-slate-500 text-[10px]"},"V.Zoom:"),/*#__PURE__*/React.createElement("span",{className:"text-cyan-400 font-bold text-[11px] w-12"},scopeZoom>0?'+':'',scopeZoom.toFixed(1)," dB"),/*#__PURE__*/React.createElement("input",{type:"range",min:"-12",max:"20",step:"0.5",value:scopeZoom,onChange:e=>setScopeZoom(parseFloat(e.target.value)),className:"w-16 h-1 cursor-pointer",style:{accentColor:'#38bdf8'}})),/*#__PURE__*/React.createElement("button",{onClick:()=>setScopePaused(p=>!p),className:`px-3 py-1 font-semibold rounded text-[11px] border transition-colors ${scopePaused?'bg-amber-600 border-amber-400 text-white':'bg-slate-800 hover:bg-slate-700 text-slate-300 border-slate-700'}`},scopePaused?'Resume':'Pause'))))),addModuleOpen&&/*#__PURE__*/React.createElement("div",{className:"fixed inset-0 z-[120] bg-black/80 backdrop-blur-sm flex items-center justify-center p-4",onClick:()=>setAddModuleOpen(false)},/*#__PURE__*/React.createElement("div",{className:"w-full max-w-lg bg-slate-900 border border-slate-700 rounded-2xl p-5 space-y-4 shadow-2xl max-h-[80vh] overflow-y-auto",onClick:e=>e.stopPropagation()},/*#__PURE__*/React.createElement("div",{className:"flex items-center justify-between border-b border-slate-800 pb-3"},/*#__PURE__*/React.createElement("h3",{className:"text-xs font-bold text-white uppercase tracking-wider font-mono"},"THÊM MODULE VÀO FX CHAIN"),/*#__PURE__*/React.createElement("button",{onClick:()=>setAddModuleOpen(false),className:"text-slate-400 hover:text-white"},/*#__PURE__*/React.createElement("i",{"data-lucide":"x",className:"w-4 h-4"}))),/*#__PURE__*/React.createElement("div",{className:"grid grid-cols-2 gap-3 text-xs"},Object.keys(TRACK_FX_META).map(t=>{const meta=TRACK_FX_META[t];return/*#__PURE__*/React.createElement("button",{key:t,onClick:()=>addMod(t),className:"p-3 bg-slate-950 hover:bg-slate-800 border border-slate-800 rounded-xl text-left space-y-1 transition-colors"},/*#__PURE__*/React.createElement("div",{className:"font-bold flex items-center gap-1.5",style:{color:meta.color}},/*#__PURE__*/React.createElement("i",{"data-lucide":meta.icon,className:"w-3.5 h-3.5"})," ",meta.name),/*#__PURE__*/React.createElement("div",{className:"text-[10px] text-slate-400"},meta.sub));})))),/*#__PURE__*/React.createElement(VstPluginPicker,{open:vstPickerOpen,onClose:()=>setVstPickerOpen(false),plugins:fxList,onPick:f=>addVst(f.path)}));};// ────────────────────────────────────────────── +ctx.fillStyle='#475569';ctx.font='9px monospace';ctx.fillText('+12 dB',4,midY-h/2+10);ctx.fillText('0 dB',4,midY+3);ctx.fillText('-12 dB',4,midY+h/2-4);ctx.fillText('20Hz',4,h-2);ctx.fillText('20kHz',w-38,h-2);},[ap.g1,ap.g2,ap.g3,ap.g4,activeMod]);return/*#__PURE__*/React.createElement("div",{className:"fixed inset-0 z-[115] bg-black/80 backdrop-blur-sm flex items-center justify-center p-4",onClick:onClose},/*#__PURE__*/React.createElement("div",{className:"w-full max-w-4xl bg-slate-900 border border-slate-700 rounded-2xl p-5 space-y-4 shadow-2xl flex flex-col max-h-[90vh] text-slate-200",onClick:e=>e.stopPropagation()},/*#__PURE__*/React.createElement("div",{className:"flex items-center justify-between border-b border-slate-800 pb-3"},/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-2"},/*#__PURE__*/React.createElement("div",{className:"w-3 h-3 rounded-full bg-cyan-400 animate-pulse"}),/*#__PURE__*/React.createElement("h3",{className:"text-xs font-bold text-white uppercase tracking-wider font-mono"},track.name," \u2014 FX RACK PANEL")),/*#__PURE__*/React.createElement("button",{onClick:onClose,className:"text-slate-400 hover:text-white text-base"},/*#__PURE__*/React.createElement("i",{"data-lucide":"x",className:"w-4 h-4"}))),/*#__PURE__*/React.createElement("div",{className:"h-16 bg-slate-950 border border-slate-800 rounded-xl px-3 flex items-center gap-2 overflow-x-auto shrink-0 select-none"},/*#__PURE__*/React.createElement("span",{className:"text-[10px] font-bold text-slate-500 uppercase tracking-widest mr-1 font-mono shrink-0"},"CHAIN:"),chain.length===0&&/*#__PURE__*/React.createElement("span",{className:"text-[10px] text-slate-500 font-mono"},"Ch\u01B0a c\xF3 FX \u2014 b\u1EA5m [+] \u0111\u1EC3 th\xEAm (signal \u0111i th\u1EB3ng: Source \u2192 Fader)"),chain.map((m,idx)=>{const meta=TRACK_FX_META[m.type]||{name:m.type,icon:'circle',color:'#94a3b8',sub:''};const on=m.active!==false;return/*#__PURE__*/React.createElement("div",{key:idx,draggable:true,onDragStart:e=>{dragChainIndexRef.current=idx;e.dataTransfer.effectAllowed='move';},onDragOver:e=>{e.preventDefault();e.dataTransfer.dropEffect='move';},onDrop:e=>{e.preventDefault();const from=dragChainIndexRef.current;if(from!==null&&from!==idx){const next=[...chain];const mv=next.splice(from,1)[0];next.splice(idx,0,mv);setChain(next);}dragChainIndexRef.current=null;},onClick:()=>{closeFxGui();setActiveType(m.type);},className:`w-36 h-12 rounded-lg p-2 flex items-center justify-between cursor-pointer transition-all shrink-0 ${activeType===m.type?'border-2 border-cyan-400 bg-slate-800':'bg-slate-900 border border-slate-800 hover:border-slate-600'}`},/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-2 min-w-0"},/*#__PURE__*/React.createElement("button",{onClick:e=>{e.stopPropagation();toggleMod(idx);},className:"w-4 h-4 rounded-full flex items-center justify-center text-[9px] font-bold shrink-0",style:{backgroundColor:on?'#38bdf8':'#334155',color:on?'#0f172a':'#94a3b8'}},/*#__PURE__*/React.createElement("i",{"data-lucide":"power",className:"w-2.5 h-2.5"})),/*#__PURE__*/React.createElement("div",{className:"min-w-0"},/*#__PURE__*/React.createElement("div",{className:"text-[11px] font-bold text-slate-200 truncate"},meta.name),/*#__PURE__*/React.createElement("div",{className:"text-[9px] font-mono truncate",style:{color:meta.color}},idx+1,". ",meta.sub))),/*#__PURE__*/React.createElement("button",{onClick:e=>{e.stopPropagation();removeMod(idx);},className:"text-slate-600 hover:text-red-400 text-xs px-0.5 shrink-0",title:"X\xF3a module"},/*#__PURE__*/React.createElement("i",{"data-lucide":"x",className:"w-3 h-3"})));}),/*#__PURE__*/React.createElement("button",{onClick:()=>setAddModuleOpen(true),className:"w-16 h-12 rounded-lg border border-dashed border-slate-700 hover:border-cyan-500 flex items-center justify-center text-slate-500 hover:text-cyan-400 cursor-pointer transition-all bg-slate-900/40 shrink-0",title:"Th\xEAm module"},/*#__PURE__*/React.createElement("i",{"data-lucide":"plus",className:"w-4 h-4"}))),/*#__PURE__*/React.createElement("div",{className:"bg-slate-950 border border-teal-900/60 rounded-xl px-3 py-2 flex flex-col gap-2 shrink-0 select-none"},/*#__PURE__*/React.createElement("div",{className:"flex items-center justify-between flex-wrap gap-2"},/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-2 min-w-0"},/*#__PURE__*/React.createElement("span",{className:"text-[10px] font-bold text-teal-400 uppercase tracking-widest font-mono shrink-0"},"VST FX:"),/*#__PURE__*/React.createElement("span",{className:"text-[9px] text-slate-500 font-mono truncate"},"Plugin VST3 (Ozone...) \u2014 realtime browser v\u1EABn WebAudio builtin; VST \xE1p d\u1EE5ng l\xFAc Export/Offline render (preview = \xE2m export)")),/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-2 shrink-0"},/*#__PURE__*/React.createElement("button",{onClick:previewFx,className:"px-2 py-1 bg-teal-800 hover:bg-teal-700 text-teal-100 text-[10px] font-semibold rounded transition flex items-center gap-1"},/*#__PURE__*/React.createElement("i",{"data-lucide":"play",className:"w-3 h-3"})," Nghe th\u1EED (render offline)"),/*#__PURE__*/React.createElement("label",{className:"flex items-center gap-1 text-[10px] text-slate-400 font-mono cursor-pointer"},/*#__PURE__*/React.createElement("input",{type:"checkbox",checked:vstBypass,onChange:e=>{const val=e.target.checked;setVstBypass(val);if(onUpdateTrack)onUpdateTrack(track.id,{vstFxBypass:val});},className:"w-3 h-3"})," Bypass"))),vstChain.length===0&&/*#__PURE__*/React.createElement("div",{className:"text-[10px] text-slate-500 font-mono"},"Ch\u01B0a c\xF3 VST FX \u2014 ch\u1ECDn plugin b\xEAn d\u01B0\u1EDBi \u0111\u1EC3 th\xEAm v\xE0o insert chain (WebAudio chain \u2192 VST FX \u2192 Fader)."),vstChain.map((s,idx)=>vstRow(s,idx,toggleVst,changeVstPlugin,uploadVstPreset,moveVst,removeVst,fxList,vstChain.length,openVstGui,false,activeVstIdx,(s2,i2)=>openVstGui(s2,i2))),/*#__PURE__*/React.createElement("button",{onClick:()=>setVstPickerOpen(true),className:"w-full bg-slate-950 border border-dashed border-slate-700 hover:border-teal-600 text-teal-300 rounded px-2 py-1.5 text-[11px] outline-none text-left",title:"Th\xEAm VST FX (Plugin Picker theo category)"},/*#__PURE__*/React.createElement("i",{"data-lucide":"plus",className:"w-3 h-3 inline-block mr-1"})," Th\xEAm VST FX... (Picker: EQ / Reverb / Comp / Utility)")),fxGuiOpen?/*#__PURE__*/React.createElement("div",{className:"flex-1 min-h-0"},/*#__PURE__*/React.createElement(VstGuiEmbed,{url:fxGuiOpen.url,onClose:closeFxGui,onDead:()=>{window.showToast&&window.showToast('Bridge VST GUI đã mất kết nối — trả về graph','error');closeFxGui();}})):/*#__PURE__*/React.createElement(React.Fragment,null,/*#__PURE__*/React.createElement("div",{className:"flex-1 bg-slate-950 border border-slate-800 rounded-xl p-4 flex flex-col gap-4 min-h-[240px] overflow-y-auto oz-scrollbar"},/*#__PURE__*/React.createElement("div",{className:"h-8 bg-slate-900/80 border-b border-slate-800/80 px-3 flex items-center justify-between text-xs font-mono rounded-t-lg shrink-0"},/*#__PURE__*/React.createElement("span",{className:"text-slate-400"},"Active Module: ",/*#__PURE__*/React.createElement("strong",{className:"text-cyan-400"},activeMod?TRACK_FX_META[activeMod.type]?.name||activeMod.type:'—')),activeMod&&activeMod.type==='eq'&&/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-2"},/*#__PURE__*/React.createElement("span",{className:"text-slate-400"},"EQ Preset:"),/*#__PURE__*/React.createElement("select",{value:"flat",onChange:e=>applyPreset(e.target.value),className:"bg-slate-950 border border-slate-700 text-cyan-300 rounded px-2 py-0.5 text-xs outline-none focus:border-cyan-500"},Object.keys(TRACK_EQ_PRESETS).map(k=>/*#__PURE__*/React.createElement("option",{key:k,value:k},TRACK_EQ_PRESETS[k].name))))),!activeMod&&/*#__PURE__*/React.createElement("div",{className:"text-xs text-slate-500 font-mono text-center py-10"},"Ch\u01B0a c\xF3 module. B\u1EA5m [+] \u0111\u1EC3 th\xEAm EQ / Compressor / Limiter / Exciter / Rebalance."),activeMod&&activeMod.type==='eq'&&activeIdx>=0&&/*#__PURE__*/React.createElement("div",{className:"space-y-3"},/*#__PURE__*/React.createElement("div",{className:"grid grid-cols-4 gap-4"},[['g1','BAND 1 (LOW)','100 Hz','#22d3ee'],['g2','BAND 2 (MID LOW)','800 Hz','#fbbf24'],['g3','BAND 3 (MID HIGH)','3.2 kHz','#a855f7'],['g4','BAND 4 (HIGH)','10 kHz','#34d399']].map(([key,label,freq,color])=>/*#__PURE__*/React.createElement("div",{key:key,className:"bg-slate-900/80 border border-slate-800 p-3 rounded-lg space-y-2"},slider(label,ap[key]!==undefined?ap[key]:0,-12,12,0.1,color,v=>setParams(activeIdx,{[key]:v}),v=>`${v>0?'+':''}${v.toFixed(1)} dB`),/*#__PURE__*/React.createElement("div",{className:"text-[9px] text-slate-500 font-mono text-center"},freq)))),/*#__PURE__*/React.createElement("div",{className:"bg-slate-900/80 border border-slate-800 rounded-lg p-2"},/*#__PURE__*/React.createElement("div",{className:"text-[9px] text-slate-500 font-mono mb-1 flex justify-between"},/*#__PURE__*/React.createElement("span",null,"VECTOR DISPLAY \u2014 EQ RESPONSE"),/*#__PURE__*/React.createElement("span",null,"20Hz \u2013 20kHz")),/*#__PURE__*/React.createElement("canvas",{ref:eqCurveRef,className:"w-full h-[88px] block"}))),activeMod&&activeMod.type==='eqpro'&&activeIdx>=0&&/*#__PURE__*/React.createElement("div",{className:"space-y-3"},/*#__PURE__*/React.createElement("div",{className:"flex items-center justify-between"},/*#__PURE__*/React.createElement("span",{className:"text-[9px] text-teal-400 font-mono uppercase tracking-widest"},"PARAMETRIC / GRAPHIC EQ PRO \u2014 Pro-Q style interactive"),/*#__PURE__*/React.createElement("span",{className:"text-[9px] text-slate-500 font-mono"},"20Hz \u2013 20kHz \xB7 \xB124dB \xB7 ",EQPRO_MAX_BANDS," bands max")),/*#__PURE__*/React.createElement(InteractiveEqPro,{track:track,params:ap,onChange:next=>setParams(activeIdx,next),getModule:()=>window.__getTrackFxModule?window.__getTrackFxModule(track.id,'eqpro'):null,spectrumModules:()=>{const a=window.__getTrackFxModule?window.__getTrackFxModule(track.id,'eqpro'):null;const b=window.__getTrackSfFxModule?window.__getTrackSfFxModule(track.id,'eqpro'):null;return[a,b].filter(Boolean);}})),activeMod&&activeMod.type==='compressor'&&activeIdx>=0&&/*#__PURE__*/React.createElement("div",{className:"grid grid-cols-3 gap-4"},/*#__PURE__*/React.createElement("div",{className:"bg-slate-900/80 border border-slate-800 p-3 rounded-lg"},slider('THRESHOLD',ap.threshold,-60,0,0.5,'#fbbf24',v=>setParams(activeIdx,{threshold:v}),v=>`${v.toFixed(1)} dB`)),/*#__PURE__*/React.createElement("div",{className:"bg-slate-900/80 border border-slate-800 p-3 rounded-lg"},slider('RATIO',ap.ratio,1,20,0.5,'#f59e0b',v=>setParams(activeIdx,{ratio:v}),v=>`${v.toFixed(1)} : 1`)),/*#__PURE__*/React.createElement("div",{className:"bg-slate-900/80 border border-slate-800 p-3 rounded-lg"},slider('MAKE-UP',ap.makeup,0,12,0.1,'#f59e0b',v=>setParams(activeIdx,{makeup:v}),v=>`${v.toFixed(1)} dB`))),activeMod&&activeMod.type==='limiter'&&activeIdx>=0&&/*#__PURE__*/React.createElement("div",{className:"grid grid-cols-2 gap-4"},/*#__PURE__*/React.createElement("div",{className:"bg-slate-900/80 border border-slate-800 p-3 rounded-lg"},slider('CEILING',ap.ceiling,-24,0,0.1,'#f43f5e',v=>setParams(activeIdx,{ceiling:v}),v=>`${v.toFixed(1)} dB`)),/*#__PURE__*/React.createElement("div",{className:"bg-slate-900/80 border border-slate-800 p-3 rounded-lg flex items-center justify-center text-[10px] text-slate-500 font-mono text-center leading-snug"},"True-Peak limiting",/*#__PURE__*/React.createElement("br",null),"Ratio 20:1 \xB7 Knee 0dB")),activeMod&&activeMod.type==='exciter'&&activeIdx>=0&&/*#__PURE__*/React.createElement("div",{className:"grid grid-cols-2 gap-4"},/*#__PURE__*/React.createElement("div",{className:"bg-slate-900/80 border border-slate-800 p-3 rounded-lg"},slider('DRIVE / MIX',ap.drive,0,100,1,'#c084fc',v=>setParams(activeIdx,{drive:v}),v=>`${v}%`)),/*#__PURE__*/React.createElement("div",{className:"bg-slate-900/80 border border-slate-800 p-3 rounded-lg flex items-center justify-center text-[10px] text-slate-500 font-mono text-center leading-snug"},"WaveShaper saturation",/*#__PURE__*/React.createElement("br",null),"High-pass 2kHz \xB7 4\xD7 oversampled")),activeMod&&activeMod.type==='rebalance'&&activeIdx>=0&&/*#__PURE__*/React.createElement("div",{className:"grid grid-cols-2 gap-4"},/*#__PURE__*/React.createElement("div",{className:"bg-slate-900/80 border border-slate-800 p-3 rounded-lg"},slider('MID GAIN',ap.mid,-12,12,0.1,'#38bdf8',v=>setParams(activeIdx,{mid:v}),v=>`${v>0?'+':''}${v.toFixed(1)} dB`)),/*#__PURE__*/React.createElement("div",{className:"bg-slate-900/80 border border-slate-800 p-3 rounded-lg"},slider('SIDE GAIN',ap.side,-12,12,0.1,'#22d3ee',v=>setParams(activeIdx,{side:v}),v=>`${v>0?'+':''}${v.toFixed(1)} dB`))),activeMod&&activeMod.type==='carla'&&activeIdx>=0&&/*#__PURE__*/React.createElement("div",{className:"bg-amber-950/40 border border-amber-800/60 rounded-lg p-4 space-y-3"},/*#__PURE__*/React.createElement("div",{className:"text-[10px] text-amber-300 font-mono"},"Carla Bridge \u0111\xE3 b\u1ECB g\u1EE1 kh\u1ECFi FX Rack \u2014 track c\u0169 v\u1EABn gi\u1EEF module \u0111\u1EC3 kh\xF4ng m\u1EA5t c\u1EA5u h\xECnh."),/*#__PURE__*/React.createElement("button",{onClick:()=>removeMod(activeIdx),className:"px-3 py-1.5 bg-amber-800 hover:bg-amber-700 text-amber-50 text-xs font-semibold rounded transition"},"G\u1EE1 module Carla kh\u1ECFi track"))),/*#__PURE__*/React.createElement("div",{className:"bg-slate-950 border border-slate-800 rounded-xl p-3 flex flex-col gap-2 shrink-0"},/*#__PURE__*/React.createElement("div",{className:"flex items-center justify-between text-xs font-mono border-b border-slate-800/80 pb-1.5"},/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-2"},/*#__PURE__*/React.createElement("span",{className:"font-bold text-white tracking-wider"},"WAVE OBSERVER"),/*#__PURE__*/React.createElement("span",{className:"text-[9px] bg-cyan-950 border border-cyan-800 text-cyan-400 px-1.5 py-0.5 rounded"},"Real-time Oscilloscope")),/*#__PURE__*/React.createElement("span",{className:"text-[10px] text-slate-500 truncate max-w-[200px]"},"Context: ",track.name)),/*#__PURE__*/React.createElement("div",{className:"relative w-full h-36 bg-slate-950 border border-slate-900 rounded-lg overflow-hidden"},/*#__PURE__*/React.createElement("canvas",{ref:scopeCanvasRef,className:"w-full h-full block cursor-crosshair"})),/*#__PURE__*/React.createElement("div",{className:"flex items-center justify-between bg-slate-900/90 border border-slate-800 rounded-lg px-3 py-1.5 text-xs font-mono flex-wrap gap-2"},/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-2 pr-3 border-r border-slate-800"},/*#__PURE__*/React.createElement("span",{className:"text-[10px] text-slate-400"},"Input"),/*#__PURE__*/React.createElement("div",{className:"flex flex-col gap-1 w-12"},/*#__PURE__*/React.createElement("div",{className:"h-1.5 bg-slate-950 rounded overflow-hidden flex"},/*#__PURE__*/React.createElement("div",{ref:scopeMeterLRef,className:"h-full bg-cyan-400 w-0 transition-all"})),/*#__PURE__*/React.createElement("div",{className:"h-1.5 bg-slate-950 rounded overflow-hidden flex"},/*#__PURE__*/React.createElement("div",{ref:scopeMeterRRef,className:"h-full bg-cyan-400 w-0 transition-all"})))),/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-1.5"},/*#__PURE__*/React.createElement("span",{className:"text-slate-500 text-[10px]"},"Channel:"),/*#__PURE__*/React.createElement("select",{value:scopeChannel,onChange:e=>setScopeChannel(e.target.value),className:"bg-slate-950 border border-slate-800 text-slate-300 rounded px-1.5 py-0.5 text-[11px] outline-none"},/*#__PURE__*/React.createElement("option",{value:"stereo"},"Stereo"),/*#__PURE__*/React.createElement("option",{value:"left"},"Left"),/*#__PURE__*/React.createElement("option",{value:"right"},"Right"),/*#__PURE__*/React.createElement("option",{value:"mid"},"Mid"),/*#__PURE__*/React.createElement("option",{value:"side"},"Side"))),/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-1.5"},/*#__PURE__*/React.createElement("span",{className:"text-slate-500 text-[10px]"},"Mode:"),/*#__PURE__*/React.createElement("select",{value:scopeMode,onChange:e=>setScopeMode(e.target.value),className:"bg-slate-950 border border-slate-800 text-slate-300 rounded px-1.5 py-0.5 text-[11px] outline-none"},/*#__PURE__*/React.createElement("option",{value:"waveform"},"Waveform"),/*#__PURE__*/React.createElement("option",{value:"lissajous"},"Lissajous"),/*#__PURE__*/React.createElement("option",{value:"spectrum"},"Spectrum"))),/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-1.5"},/*#__PURE__*/React.createElement("span",{className:"text-slate-500 text-[10px]"},"Duration:"),/*#__PURE__*/React.createElement("span",{className:"text-cyan-400 font-bold text-[11px] w-12"},scopeDuration.toFixed(2),"s"),/*#__PURE__*/React.createElement("input",{type:"range",min:"0.1",max:"5.0",step:"0.1",value:scopeDuration,onChange:e=>setScopeDuration(parseFloat(e.target.value)),className:"w-16 h-1 cursor-pointer",style:{accentColor:'#38bdf8'}})),/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-1.5"},/*#__PURE__*/React.createElement("span",{className:"text-slate-500 text-[10px]"},"V.Zoom:"),/*#__PURE__*/React.createElement("span",{className:"text-cyan-400 font-bold text-[11px] w-12"},scopeZoom>0?'+':'',scopeZoom.toFixed(1)," dB"),/*#__PURE__*/React.createElement("input",{type:"range",min:"-12",max:"20",step:"0.5",value:scopeZoom,onChange:e=>setScopeZoom(parseFloat(e.target.value)),className:"w-16 h-1 cursor-pointer",style:{accentColor:'#38bdf8'}})),/*#__PURE__*/React.createElement("button",{onClick:()=>setScopePaused(p=>!p),className:`px-3 py-1 font-semibold rounded text-[11px] border transition-colors ${scopePaused?'bg-amber-600 border-amber-400 text-white':'bg-slate-800 hover:bg-slate-700 text-slate-300 border-slate-700'}`},scopePaused?'Resume':'Pause'))))),addModuleOpen&&/*#__PURE__*/React.createElement("div",{className:"fixed inset-0 z-[120] bg-black/80 backdrop-blur-sm flex items-center justify-center p-4",onClick:()=>setAddModuleOpen(false)},/*#__PURE__*/React.createElement("div",{className:"w-full max-w-lg bg-slate-900 border border-slate-700 rounded-2xl p-5 space-y-4 shadow-2xl max-h-[80vh] overflow-y-auto",onClick:e=>e.stopPropagation()},/*#__PURE__*/React.createElement("div",{className:"flex items-center justify-between border-b border-slate-800 pb-3"},/*#__PURE__*/React.createElement("h3",{className:"text-xs font-bold text-white uppercase tracking-wider font-mono"},"TH\xCAM MODULE V\xC0O FX CHAIN"),/*#__PURE__*/React.createElement("button",{onClick:()=>setAddModuleOpen(false),className:"text-slate-400 hover:text-white"},/*#__PURE__*/React.createElement("i",{"data-lucide":"x",className:"w-4 h-4"}))),/*#__PURE__*/React.createElement("div",{className:"grid grid-cols-2 gap-3 text-xs"},Object.keys(TRACK_FX_META).map(t=>{const meta=TRACK_FX_META[t];return/*#__PURE__*/React.createElement("button",{key:t,onClick:()=>addMod(t),className:"p-3 bg-slate-950 hover:bg-slate-800 border border-slate-800 rounded-xl text-left space-y-1 transition-colors"},/*#__PURE__*/React.createElement("div",{className:"font-bold flex items-center gap-1.5",style:{color:meta.color}},/*#__PURE__*/React.createElement("i",{"data-lucide":meta.icon,className:"w-3.5 h-3.5"})," ",meta.name),/*#__PURE__*/React.createElement("div",{className:"text-[10px] text-slate-400"},meta.sub));})))),/*#__PURE__*/React.createElement(VstPluginPicker,{open:vstPickerOpen,onClose:()=>setVstPickerOpen(false),plugins:fxList,onPick:f=>addVst(f.path)}));};// ────────────────────────────────────────────── // MASTERING MODAL COMPONENT (from md/45_MASTERING_MODULE.md) // ────────────────────────────────────────────── // GUI embed state survives MasteringModal unmount so the native window @@ -848,7 +848,7 @@ const MODULE_META={eq:{name:'Dynamic EQ',sub:'4-Band Peak',icon:'activity',color const flags={};chain.forEach(m=>{flags[chainFlag(m.type)]=!!m.active;});const activeModule=prev.activeModule;let vstFxChain=prev.vstFxChain||[];if(removed&&removed.type==='vst3'){const vi=vstFxChain.findIndex(s=>s.path===removed.path);if(vi>=0)vstFxChain=[...vstFxChain.slice(0,vi),...vstFxChain.slice(vi+1)];}return{...prev,chain,...flags,vstFxChain,activeModule:chain.some(m=>m.type===activeModule)?activeModule:chain[chain.length-1].type};});};const reorderChain=(fromIdx,toIdx)=>{if(fromIdx===toIdx)return;setOzState(prev=>{const chain=[...prev.chain];const moved=chain.splice(fromIdx,1)[0];chain.splice(toIdx,0,moved);return{...prev,chain};});};const addModuleToChain=type=>{const meta=MODULE_META[type];const id='mod_'+type+'_'+Date.now();const entry={id,type,name:meta.name,active:true};if(type==='eqpro')entry.params={amount:100,bands:JSON.parse(JSON.stringify(EQPRO_DEFAULT_BANDS))};if(type==='carla')entry.params={plugin:'',plugin_path:''};setOzState(prev=>({...prev,chain:[...(prev.chain||[]),entry],[chainFlag(type)]:true,activeModule:type}));setAddModuleOpen(false);};const addVstToChain=path=>{const fx=(masterFxList||[]).find(f=>f.path===path);if(!fx)return;const id='mod_vst_'+Date.now();const entry={id,type:'vst3',path:fx.path,name:fx.name||fx.id,preset_b64:'',bypass:false,active:true};setOzState(prev=>({...prev,chain:[...(prev.chain||[]),entry],vstFxChain:[...(prev.vstFxChain||[]),{type:'vst3',path:fx.path,name:fx.name||fx.id,preset_b64:'',bypass:false}]}));setAddModuleOpen(false);};const updateChainEntryParams=(modId,patch)=>{setOzState(prev=>({...prev,chain:(prev.chain||[]).map(m=>m.id===modId?{...m,params:{...(m.params||{}),...patch}}:m)}));};// ── EQ Preset Library (mastering_expand.md §II.1) ── const EQ_PRESET_LIBRARY={flat:{name:'Flat / Reset',bands:[{id:1,type:'lowshelf',freq:100,gain:0.0,q:0.7},{id:2,type:'peaking',freq:800,gain:0.0,q:0.7},{id:3,type:'peaking',freq:3200,gain:0.0,q:1.2},{id:4,type:'highshelf',freq:10000,gain:0.0,q:0.7}]},vocal_clarity:{name:'Vocal Unmask & Clarity',bands:[{id:1,type:'lowshelf',freq:90,gain:-2.5,q:0.7},{id:2,type:'peaking',freq:500,gain:-1.8,q:1.0},{id:3,type:'peaking',freq:2800,gain:3.2,q:1.2},{id:4,type:'highshelf',freq:12000,gain:2.0,q:0.7}]},bass_punch:{name:'EDM Low-End Punch',bands:[{id:1,type:'lowshelf',freq:80,gain:4.0,q:0.8},{id:2,type:'peaking',freq:300,gain:-3.0,q:1.4},{id:3,type:'peaking',freq:4000,gain:1.5,q:1.0},{id:4,type:'highshelf',freq:10000,gain:1.0,q:0.7}]},warm_tape:{name:'Warm Vintage Analog',bands:[{id:1,type:'lowshelf',freq:120,gain:2.0,q:0.6},{id:2,type:'peaking',freq:1500,gain:1.0,q:0.5},{id:3,type:'peaking',freq:5000,gain:-2.0,q:1.0},{id:4,type:'highshelf',freq:8000,gain:-3.0,q:0.7}]}};const applyEQPreset=presetKey=>{const preset=EQ_PRESET_LIBRARY[presetKey];if(!preset)return;const bus=masterBus;if(bus&&bus.eqLowFilter){const now=audioCtx?audioCtx.currentTime:0;const filters=[bus.eqLowFilter,bus.eqMid1Filter,bus.eqMid2Filter,bus.eqHighFilter];preset.bands.forEach((bd,i)=>{const f=filters[i];if(!f)return;try{// setValueAtTime (không automation) — tránh "BiquadFilterNode: state is bad" f.frequency.setValueAtTime(bd.freq,now);f.gain.setValueAtTime(bd.gain,now);f.Q.setValueAtTime(bd.q,now);}catch(e){}});}// Sync UI knobs + canvas: [eqLowGain, eqMid1Gain, eqMid2Gain, eqHighGain] -setOzState(prev=>({...prev,eqLowGain:preset.bands[0]?preset.bands[0].gain:prev.eqLowGain,eqMid1Gain:preset.bands[1]?preset.bands[1].gain:prev.eqMid1Gain,eqMid2Gain:preset.bands[2]?preset.bands[2].gain:prev.eqMid2Gain,eqHighGain:preset.bands[3]?preset.bands[3].gain:prev.eqHighGain,eqPreset:presetKey}));};const bandKnob=(param,min,max,val,unit,label,freq,color,filterType)=>/*#__PURE__*/React.createElement("div",{className:"bg-slate-900/80 border border-slate-800 p-2 flex flex-col justify-between items-center rounded-lg"},/*#__PURE__*/React.createElement("div",{className:"flex items-center justify-between text-[10px] font-bold w-full",style:{color}},/*#__PURE__*/React.createElement("span",null,label),/*#__PURE__*/React.createElement("span",{className:"text-[9px] font-mono text-slate-400"},freq)),/*#__PURE__*/React.createElement("div",{className:"my-1"},/*#__PURE__*/React.createElement(MasteringKnob,{param:param,min:min,max:max,value:ozState[param]!==undefined?ozState[param]:val,unit:unit,color:color,onChange:(p,v)=>setOzState(prev=>({...prev,[p]:v}))})),/*#__PURE__*/React.createElement("div",{className:"text-[9px] text-slate-500 font-mono text-center mt-1"},filterType));return/*#__PURE__*/React.createElement("div",{className:"fixed inset-0 z-[100] flex items-center justify-center bg-black/70 backdrop-blur-sm",onClick:onClose},/*#__PURE__*/React.createElement("div",{id:"masteringModalBody",className:"flex flex-col bg-slate-950 text-slate-200 w-[95vw] h-[92vh] max-w-[1400px] rounded-2xl border border-slate-800/80 shadow-2xl overflow-hidden",onClick:e=>e.stopPropagation(),style:{fontFamily:"'Inter', sans-serif"}},/*#__PURE__*/React.createElement("header",{className:"h-12 bg-slate-900/90 border-b border-slate-800/80 flex items-center justify-between px-4 z-30 shrink-0"},/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-3"},/*#__PURE__*/React.createElement("div",{className:"w-7 h-7 rounded-lg bg-cyan-600 flex items-center justify-center font-bold text-white shadow-lg shadow-cyan-950"},/*#__PURE__*/React.createElement("i",{"data-lucide":"zap",className:"w-3.5 h-3.5"})),/*#__PURE__*/React.createElement("div",null,/*#__PURE__*/React.createElement("h1",{className:"text-xs font-bold tracking-wider text-white flex items-center gap-2"},"MASTERING SUITE ",/*#__PURE__*/React.createElement("span",{className:"text-[9px] bg-cyan-950 text-cyan-400 border border-cyan-800/80 px-1.5 py-0.5 rounded oz-font-mono"},"WEB MASTERING V10.5")))),/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-3 bg-slate-950 px-3 py-1 rounded-lg border border-slate-800/80"},/*#__PURE__*/React.createElement("button",{onClick:startAudioDemo,className:"px-3 py-1 bg-cyan-600 hover:bg-cyan-500 text-white rounded text-xs font-semibold flex items-center gap-1.5 transition-all shadow-md shadow-cyan-950"},/*#__PURE__*/React.createElement("i",{"data-lucide":"play",className:"w-3 h-3"})," ",/*#__PURE__*/React.createElement("span",null,"Play Reference")),/*#__PURE__*/React.createElement("button",{onClick:stopAudioDemo,className:"px-3 py-1 bg-slate-800 hover:bg-slate-700 text-slate-300 rounded text-xs font-semibold flex items-center gap-1.5 transition-all"},/*#__PURE__*/React.createElement("i",{"data-lucide":"square",className:"w-3 h-3"})," ",/*#__PURE__*/React.createElement("span",null,"Stop")),/*#__PURE__*/React.createElement("div",{className:"h-4 w-[1px] bg-slate-800 mx-1"}),/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-2 text-xs oz-font-mono"},/*#__PURE__*/React.createElement("span",{className:"text-slate-400 text-[11px]"},"Preset:"),/*#__PURE__*/React.createElement("select",{className:"bg-slate-900 border border-slate-700 rounded px-2 py-0.5 text-xs text-cyan-300 outline-none focus:border-cyan-500"},/*#__PURE__*/React.createElement("option",{value:"adaptive"},"Adaptive Dynamic Master"),/*#__PURE__*/React.createElement("option",{value:"edm_club"},"EDM / Club Punch Maximizer"),/*#__PURE__*/React.createElement("option",{value:"wide_space"},"Cinematic Stereo Expansion"),/*#__PURE__*/React.createElement("option",{value:"transparent"},"Transparent High-Clarity Limiter")))),/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-3 text-xs oz-font-mono"},/*#__PURE__*/React.createElement("span",{className:"text-slate-400 text-[11px]"},"Target LUFS:"),/*#__PURE__*/React.createElement("span",{className:"text-cyan-400 font-bold bg-slate-950 border border-cyan-900/60 px-2 py-0.5 rounded"},"-11.0 LUFS"),/*#__PURE__*/React.createElement("button",{onClick:()=>{getAudioContext();setMasterConnected(prev=>!prev);},className:`px-2 py-0.5 rounded text-[10px] font-bold border transition-colors ${masterConnected?'bg-emerald-700 border-emerald-500 text-white':'bg-slate-800 border-slate-700 text-slate-300 hover:bg-slate-700'}`},masterConnected?'Master ON':'Master OFF'),/*#__PURE__*/React.createElement("button",{onClick:onClose,className:"px-2 py-0.5 rounded text-[10px] font-bold border transition-colors bg-slate-800 border-slate-700 text-slate-300 hover:bg-[#c2410c] hover:border-red-600 hover:text-white"},"Đóng"),/*#__PURE__*/React.createElement("button",{onClick:onClose,className:"text-slate-500 hover:text-slate-300 ml-1"},/*#__PURE__*/React.createElement("i",{"data-lucide":"x",className:"w-4 h-4"})))),/*#__PURE__*/React.createElement("div",{className:"h-16 bg-slate-950 border-b border-slate-800 px-4 flex items-center gap-2 overflow-x-auto shrink-0 oz-scrollbar"},/*#__PURE__*/React.createElement("span",{className:"text-[10px] font-bold text-slate-500 uppercase tracking-widest mr-1 oz-font-mono shrink-0"},"CHAIN:"),(ozState.chain||[]).map((mod,idx)=>{const isVst=mod.type==='vst3';const meta=MODULE_META[mod.type]||{name:mod.type,sub:'',icon:'circle',color:'#94a3b8'};const isEditing=!isVst&&ozState.activeModule===mod.type||isVst&&activeMasterVstPath===mod.path;const isOn=isVst?mod.active!==false:chainActive(mod.type);return/*#__PURE__*/React.createElement("div",{key:mod.id,draggable:true,onDragStart:e=>{dragChainIndexRef.current=idx;e.dataTransfer.effectAllowed='move';},onDragOver:e=>{e.preventDefault();e.dataTransfer.dropEffect='move';},onDrop:e=>{e.preventDefault();const from=dragChainIndexRef.current;if(from!==null&&from!==idx)reorderChain(from,idx);dragChainIndexRef.current=null;},onClick:()=>{if(mod.type==='vst3'){openMasterVstGui({path:mod.path,name:mod.name||mod.path});}else{closeMasterGui();switchModule(mod.type);}},className:`w-40 h-12 rounded-lg p-2 flex items-center justify-between cursor-pointer transition-all shrink-0 ${isEditing?'oz-card-active':'oz-card'}`},/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-2 min-w-0"},/*#__PURE__*/React.createElement("button",{onClick:e=>{e.stopPropagation();toggleChainModule(mod.id);},className:"w-4 h-4 rounded-full flex items-center justify-center text-[9px] font-bold shrink-0",style:{backgroundColor:isOn?'#38bdf8':'#334155',color:isOn?'#0f172a':'#94a3b8'}},/*#__PURE__*/React.createElement("i",{"data-lucide":"power",className:"w-2.5 h-2.5"})),/*#__PURE__*/React.createElement("div",{className:"min-w-0"},/*#__PURE__*/React.createElement("div",{className:"text-[11px] font-bold text-slate-200 truncate"},isVst?mod.name||meta.name:meta.name),/*#__PURE__*/React.createElement("div",{className:"text-[9px] oz-font-mono truncate",style:{color:meta.color}},idx+1,". ",isVst?mod.path?mod.path.split(/[\\/]/).pop():'VST3':meta.sub))),/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-1 shrink-0"},/*#__PURE__*/React.createElement("i",{"data-lucide":meta.icon,className:"w-3 h-3 text-slate-500"}),/*#__PURE__*/React.createElement("button",{onClick:e=>{e.stopPropagation();removeChainModule(mod.id);},className:"text-slate-600 hover:text-red-400 text-xs w-4 h-4 flex items-center justify-center shrink-0",title:"Xóa module"},/*#__PURE__*/React.createElement("i",{"data-lucide":"x",className:"w-3 h-3"}))));}),/*#__PURE__*/React.createElement("button",{onClick:()=>setAddModuleOpen(true),className:"w-24 h-12 rounded-lg border border-dashed border-slate-800 flex items-center justify-center text-slate-600 hover:text-slate-400 hover:border-slate-600 cursor-pointer transition-all shrink-0",title:"Thêm module vào chain"},/*#__PURE__*/React.createElement("i",{"data-lucide":"plus",className:"w-4 h-4"}))),/*#__PURE__*/React.createElement("main",{className:"flex-1 flex overflow-hidden min-h-0"},masterGuiOpen?/*#__PURE__*/React.createElement("div",{className:"flex-1 flex flex-col bg-slate-950 relative min-h-0"},/*#__PURE__*/React.createElement(VstGuiEmbed,{url:masterGuiOpen.url,onClose:closeMasterGui,onDead:()=>{window.showToast&&window.showToast('Bridge VST GUI đã mất kết nối — trả về graph','error');closeMasterGui();}})):/*#__PURE__*/React.createElement("div",{className:"flex-1 flex flex-col bg-slate-950 relative overflow-y-auto oz-scrollbar"},/*#__PURE__*/React.createElement("div",{className:"h-9 bg-slate-900/60 border-b border-slate-800/80 px-4 flex items-center justify-between text-xs oz-font-mono shrink-0"},/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-3"},/*#__PURE__*/React.createElement("span",{className:"text-slate-400 flex items-center gap-1.5"},/*#__PURE__*/React.createElement("i",{"data-lucide":"headphones",className:"w-3 h-3 text-cyan-400"})," Delta Listen"),/*#__PURE__*/React.createElement("select",{className:"bg-slate-950 border border-slate-800 rounded px-2 py-0.5 text-[11px] text-slate-300 outline-none"},/*#__PURE__*/React.createElement("option",{value:"irc4"},"IRC IV - Classic"),/*#__PURE__*/React.createElement("option",{value:"irc3"},"IRC III - Balanced"),/*#__PURE__*/React.createElement("option",{value:"irc2"},"IRC II - Crisp"))),/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-2 text-[11px]"},ozState.activeModule==='eq'&&/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-1.5"},/*#__PURE__*/React.createElement("span",{className:"text-slate-400"},"EQ Presets:"),/*#__PURE__*/React.createElement("select",{value:ozState.eqPreset||'flat',onChange:e=>applyEQPreset(e.target.value),className:"bg-slate-950 border border-slate-700 text-cyan-300 rounded px-2 py-0.5 text-[11px] outline-none focus:border-cyan-500"},Object.keys(EQ_PRESET_LIBRARY).map(k=>/*#__PURE__*/React.createElement("option",{key:k,value:k},EQ_PRESET_LIBRARY[k].name)))),/*#__PURE__*/React.createElement("span",{className:"text-slate-400"},"Learn Input Gain:"),/*#__PURE__*/React.createElement("button",{className:"bg-cyan-950 text-cyan-300 border border-cyan-800/80 px-2 py-0.5 rounded font-bold hover:bg-cyan-900 transition-colors"},"-11.0 LUFS"))),/*#__PURE__*/React.createElement("div",{className:`flex-1 flex flex-col p-4 gap-4 ${ozState.activeModule==='eq'?'':'hidden'}`},/*#__PURE__*/React.createElement("div",{className:"relative w-full h-64 bg-slate-950 border border-slate-800/90 rounded-xl overflow-hidden shadow-inner cursor-crosshair"},/*#__PURE__*/React.createElement("canvas",{ref:eqCanvasRef,className:"w-full h-full block"})),/*#__PURE__*/React.createElement("div",{className:"oz-panel p-3 rounded-xl grid grid-cols-4 gap-3"},bandKnob('eqLowGain',-12,12,1.5,'dB','BAND 1 (LOW)','100 Hz','#22d3ee','Shelf Filter'),bandKnob('eqMid1Gain',-12,12,-1.0,'dB','BAND 2 (MID LOW)','822 Hz','#fbbf24','Dynamic Bell (Q: 0.7)'),bandKnob('eqMid2Gain',-12,12,2.0,'dB','BAND 3 (MID HIGH)','3.2 kHz','#a855f7','Dynamic Bell (Q: 1.2)'),bandKnob('eqHighGain',-12,12,1.8,'dB','BAND 4 (HIGH)','10 kHz','#34d399','High Shelf'))),/*#__PURE__*/React.createElement("div",{className:`flex-1 flex flex-col p-4 gap-4 ${ozState.activeModule==='eqpro'?'':'hidden'}`},(()=>{const chainMods=ozState.chain||[];const cm=chainMods.find(m=>m.type==='eqpro'&&m.active!==false)||chainMods.find(m=>m.type==='eqpro');if(!cm)return/*#__PURE__*/React.createElement("div",{className:"text-xs text-slate-500 font-mono py-10 text-center"},"Chưa có module EQ PRO trong chain — bấm [+] để thêm.");return/*#__PURE__*/React.createElement(InteractiveEqPro,{track:null,params:cm.params||{},onChange:next=>updateChainEntryParams(cm.id,next),getModule:()=>masterBus&&masterBus.eqProInstances?masterBus.eqProInstances[cm.id]:null,spectrumModules:()=>{const m=masterBus&&masterBus.eqProInstances?masterBus.eqProInstances[cm.id]:null;return m?[m]:[];},applyTo:fn=>{const m=masterBus&&masterBus.eqProInstances?masterBus.eqProInstances[cm.id]:null;if(m)fn(m);}});})()),/*#__PURE__*/React.createElement("div",{className:`flex-1 flex flex-col p-4 gap-4 ${ozState.activeModule==='imager'?'':'hidden'}`},/*#__PURE__*/React.createElement("div",{className:"grid grid-cols-12 gap-4 flex-1"},/*#__PURE__*/React.createElement("div",{className:"col-span-8 bg-slate-950 border border-slate-800/90 rounded-xl p-3 flex flex-col relative shadow-inner"},/*#__PURE__*/React.createElement("span",{className:"text-xs font-bold text-cyan-400 uppercase oz-font-mono mb-2 flex items-center justify-between w-full"},/*#__PURE__*/React.createElement("span",null,/*#__PURE__*/React.createElement("i",{"data-lucide":"radio",className:"w-3.5 h-3.5"})," Polar Vectorscope & Correlation Meter"),/*#__PURE__*/React.createElement("span",{className:"text-[11px] text-slate-400 normal-case"},"Corr: ",/*#__PURE__*/React.createElement("span",{id:"corrText",className:"font-bold text-emerald-400"},"1.00"))),/*#__PURE__*/React.createElement("div",{className:"flex-1 relative w-full h-56 flex items-center justify-center"},/*#__PURE__*/React.createElement("canvas",{ref:imagerCanvasRef,className:"w-full h-full block"}))),/*#__PURE__*/React.createElement("div",{className:"col-span-4 oz-panel p-3 rounded-xl flex flex-col justify-between"},/*#__PURE__*/React.createElement("span",{className:"text-xs font-bold text-slate-300 uppercase oz-font-mono"},"4-Band Stereo Width"),/*#__PURE__*/React.createElement("div",{className:"text-[10px] text-slate-500 oz-font-mono leading-snug"},"0% = Mono · 100% = Original · 200% = 2× Width"),/*#__PURE__*/React.createElement("div",{className:"space-y-3 my-auto"},[{id:'w1',label:'Band 1 (20-100Hz)',color:'#22d3ee',val:ozState.w1},{id:'w2',label:'Band 2 (100Hz-1kHz)',color:'#fbbf24',val:ozState.w2},{id:'w3',label:'Band 3 (1k-6kHz)',color:'#a855f7',val:ozState.w3},{id:'w4',label:'Band 4 (6k-20kHz)',color:'#34d399',val:ozState.w4}].map(b=>/*#__PURE__*/React.createElement("div",{key:b.id},/*#__PURE__*/React.createElement("div",{className:"flex justify-between text-[11px] font-mono mb-1"},/*#__PURE__*/React.createElement("span",{style:{color:b.color,fontWeight:700}},b.label),/*#__PURE__*/React.createElement("span",{id:b.id+'Val'},b.val,"%")),/*#__PURE__*/React.createElement("input",{type:"range",min:"0",max:"200",value:b.val,onChange:e=>setOzState(prev=>({...prev,[b.id]:parseInt(e.target.value)})),className:"w-full h-1 cursor-pointer",style:{accentColor:b.color}}))))))),/*#__PURE__*/React.createElement("div",{className:`flex-1 flex flex-col p-4 gap-4 ${ozState.activeModule==='maximizer'?'':'hidden'}`},/*#__PURE__*/React.createElement("div",{className:"oz-panel p-4 rounded-xl flex-1 grid grid-cols-12 gap-6 items-center"},/*#__PURE__*/React.createElement("div",{className:"col-span-4 flex flex-col items-center justify-center border-r border-slate-800 pr-4"},/*#__PURE__*/React.createElement("span",{className:"text-xs font-bold text-slate-400 uppercase oz-font-mono mb-3"},"Maximizer Gain Boost"),/*#__PURE__*/React.createElement("div",{className:"my-2"},/*#__PURE__*/React.createElement(MasteringKnob,{param:"maxGain",min:0,max:12,value:ozState.maxGain,unit:"dB",color:"#22d3ee",size:"large",onChange:(p,v)=>setOzState(prev=>({...prev,[p]:v}))})),/*#__PURE__*/React.createElement("div",{className:"w-full mt-4 bg-slate-950 p-2 rounded-lg border border-slate-800 text-xs oz-font-mono flex flex-col gap-1.5"},/*#__PURE__*/React.createElement("div",{className:"flex justify-between items-center w-full"},/*#__PURE__*/React.createElement("span",{className:"text-slate-400"},"Ceiling Level:"),/*#__PURE__*/React.createElement("span",{className:"text-cyan-400 font-bold"},ozState.ceiling.toFixed(2)," dB")),/*#__PURE__*/React.createElement("input",{type:"range",min:"-12",max:"0",step:"0.1",value:ozState.ceiling,onChange:e=>setOzState(prev=>({...prev,ceiling:parseFloat(e.target.value)})),className:"w-full h-1 cursor-pointer accent-cyan-400"}))),/*#__PURE__*/React.createElement("div",{className:"col-span-8 grid grid-cols-3 gap-4"},/*#__PURE__*/React.createElement("div",{className:"bg-slate-950 p-3 border border-slate-800 rounded-xl flex flex-col items-center justify-center h-full"},/*#__PURE__*/React.createElement(MasteringKnob,{param:"maxUpward",min:0,max:10,value:ozState.maxUpward,unit:"dB",label:"UPWARD COMPRESS",color:"#22d3ee",onChange:(p,v)=>setOzState(prev=>({...prev,[p]:v}))})),/*#__PURE__*/React.createElement("div",{className:"bg-slate-950 p-3 border border-slate-800 rounded-xl flex flex-col items-center justify-center h-full"},/*#__PURE__*/React.createElement(MasteringKnob,{param:"maxSoftClip",min:0,max:100,value:ozState.maxSoftClip,unit:"%",label:"SOFT CLIPPER",color:"#fbbf24",onChange:(p,v)=>setOzState(prev=>({...prev,[p]:v}))})),/*#__PURE__*/React.createElement("div",{className:"bg-slate-950 p-3 border border-slate-800 rounded-xl flex flex-col items-center justify-center h-full"},/*#__PURE__*/React.createElement(MasteringKnob,{param:"maxTransient",min:0,max:100,value:ozState.maxTransient,unit:"%",label:"TRANSIENT EMPHASIS",color:"#34d399",onChange:(p,v)=>setOzState(prev=>({...prev,[p]:v}))}))))),/*#__PURE__*/React.createElement("div",{className:`flex-1 flex flex-col p-4 gap-4 ${ozState.activeModule==='compressor'?'':'hidden'}`},/*#__PURE__*/React.createElement("div",{className:"oz-panel p-4 rounded-xl flex-1 grid grid-cols-12 gap-6 items-center"},/*#__PURE__*/React.createElement("div",{className:"col-span-4 flex flex-col items-center justify-center border-r border-slate-800 pr-4"},/*#__PURE__*/React.createElement("span",{className:"text-xs font-bold text-amber-400 uppercase oz-font-mono mb-3"},"Bus Compressor"),/*#__PURE__*/React.createElement("div",{className:"my-2"},/*#__PURE__*/React.createElement(MasteringKnob,{param:"compMakeup",min:0,max:12,value:ozState.compMakeup,unit:"dB",label:"MAKE-UP GAIN",color:"#fbbf24",onChange:(p,v)=>setOzState(prev=>({...prev,[p]:v}))})),/*#__PURE__*/React.createElement("button",{onClick:()=>toggleChainModule((ozState.chain||[]).find(m=>m.type==='compressor')?.id),className:`px-3 py-1 rounded text-[10px] font-bold border transition-colors mt-2 ${ozState.compActive?'bg-amber-700 border-amber-500 text-white':'bg-slate-800 border-slate-700 text-slate-300'}`},ozState.compActive?'ON':'OFF')),/*#__PURE__*/React.createElement("div",{className:"col-span-8 grid grid-cols-3 gap-4"},/*#__PURE__*/React.createElement("div",{className:"bg-slate-950 p-3 border border-slate-800 rounded-xl flex flex-col items-center justify-center h-full"},/*#__PURE__*/React.createElement(MasteringKnob,{param:"compThreshold",min:-60,max:0,value:ozState.compThreshold,unit:"dB",label:"THRESHOLD",color:"#fbbf24",onChange:(p,v)=>setOzState(prev=>({...prev,[p]:v}))})),/*#__PURE__*/React.createElement("div",{className:"bg-slate-950 p-3 border border-slate-800 rounded-xl flex flex-col items-center justify-center h-full"},/*#__PURE__*/React.createElement(MasteringKnob,{param:"compRatio",min:1,max:20,value:ozState.compRatio,unit:":1",label:"RATIO",color:"#f59e0b",onChange:(p,v)=>setOzState(prev=>({...prev,[p]:v}))})),/*#__PURE__*/React.createElement("div",{className:"bg-slate-950 p-3 border border-slate-800 rounded-xl flex flex-col items-center justify-center h-full"},/*#__PURE__*/React.createElement("span",{className:"text-[10px] text-slate-500 oz-font-mono text-center leading-snug px-2"},"Glue & Punch",/*#__PURE__*/React.createElement("br",null),"Attack 20ms · Release 250ms · Knee 8dB"))))),/*#__PURE__*/React.createElement("div",{className:`flex-1 flex flex-col p-4 gap-4 ${ozState.activeModule==='limiter'?'':'hidden'}`},/*#__PURE__*/React.createElement("div",{className:"oz-panel p-4 rounded-xl flex-1 grid grid-cols-12 gap-6 items-center"},/*#__PURE__*/React.createElement("div",{className:"col-span-4 flex flex-col items-center justify-center border-r border-slate-800 pr-4"},/*#__PURE__*/React.createElement("span",{className:"text-xs font-bold text-rose-400 uppercase oz-font-mono mb-3"},"Brickwall Limiter"),/*#__PURE__*/React.createElement("button",{onClick:()=>toggleChainModule((ozState.chain||[]).find(m=>m.type==='limiter')?.id),className:`px-3 py-1 rounded text-[10px] font-bold border transition-colors mt-2 ${ozState.limActive?'bg-rose-700 border-rose-500 text-white':'bg-slate-800 border-slate-700 text-slate-300'}`},ozState.limActive?'ON':'OFF')),/*#__PURE__*/React.createElement("div",{className:"col-span-8 grid grid-cols-3 gap-4"},/*#__PURE__*/React.createElement("div",{className:"bg-slate-950 p-3 border border-slate-800 rounded-xl flex flex-col items-center justify-center h-full"},/*#__PURE__*/React.createElement(MasteringKnob,{param:"limThreshold",min:-24,max:0,value:ozState.limThreshold,unit:"dB",label:"CEILING",color:"#f43f5e",onChange:(p,v)=>setOzState(prev=>({...prev,[p]:v}))})),/*#__PURE__*/React.createElement("div",{className:"bg-slate-950 p-3 border border-slate-800 rounded-xl flex flex-col items-center justify-center h-full"},/*#__PURE__*/React.createElement("span",{className:"text-[10px] text-slate-500 oz-font-mono text-center leading-snug px-2"},"True-Peak limiting",/*#__PURE__*/React.createElement("br",null),"Ratio 20:1 · Knee 0dB",/*#__PURE__*/React.createElement("br",null),"Attack 1ms · Release 50ms"))))),/*#__PURE__*/React.createElement("div",{className:`flex-1 flex flex-col p-4 gap-4 ${ozState.activeModule==='exciter'?'':'hidden'}`},/*#__PURE__*/React.createElement("div",{className:"oz-panel p-4 rounded-xl flex-1 grid grid-cols-12 gap-6 items-center"},/*#__PURE__*/React.createElement("div",{className:"col-span-4 flex flex-col items-center justify-center border-r border-slate-800 pr-4"},/*#__PURE__*/React.createElement("span",{className:"text-xs font-bold text-purple-400 uppercase oz-font-mono mb-3"},"Harmonic Exciter"),/*#__PURE__*/React.createElement("button",{onClick:()=>toggleChainModule((ozState.chain||[]).find(m=>m.type==='exciter')?.id),className:`px-3 py-1 rounded text-[10px] font-bold border transition-colors mt-2 ${ozState.excActive?'bg-purple-700 border-purple-500 text-white':'bg-slate-800 border-slate-700 text-slate-300'}`},ozState.excActive?'ON':'OFF')),/*#__PURE__*/React.createElement("div",{className:"col-span-8 grid grid-cols-3 gap-4"},/*#__PURE__*/React.createElement("div",{className:"bg-slate-950 p-3 border border-slate-800 rounded-xl flex flex-col items-center justify-center h-full"},/*#__PURE__*/React.createElement(MasteringKnob,{param:"excDrive",min:0,max:100,value:ozState.excDrive,unit:"%",label:"DRIVE / MIX",color:"#c084fc",onChange:(p,v)=>setOzState(prev=>({...prev,[p]:v}))})),/*#__PURE__*/React.createElement("div",{className:"bg-slate-950 p-3 border border-slate-800 rounded-xl flex flex-col items-center justify-center h-full"},/*#__PURE__*/React.createElement("span",{className:"text-[10px] text-slate-500 oz-font-mono text-center leading-snug px-2"},"WaveShaper saturation",/*#__PURE__*/React.createElement("br",null),"High-pass 2kHz",/*#__PURE__*/React.createElement("br",null),"4× oversampled · wet/dry mix"))))),/*#__PURE__*/React.createElement("div",{className:`flex-1 flex flex-col p-4 gap-4 ${ozState.activeModule==='rebalance'?'':'hidden'}`},/*#__PURE__*/React.createElement("div",{className:"oz-panel p-4 rounded-xl flex-1 grid grid-cols-12 gap-6 items-center"},/*#__PURE__*/React.createElement("div",{className:"col-span-4 flex flex-col items-center justify-center border-r border-slate-800 pr-4"},/*#__PURE__*/React.createElement("span",{className:"text-xs font-bold text-sky-400 uppercase oz-font-mono mb-3"},"Master Rebalance (M/S)"),/*#__PURE__*/React.createElement("button",{onClick:()=>toggleChainModule((ozState.chain||[]).find(m=>m.type==='rebalance')?.id),className:`px-3 py-1 rounded text-[10px] font-bold border transition-colors mt-2 ${ozState.rebalActive?'bg-sky-700 border-sky-500 text-white':'bg-slate-800 border-slate-700 text-slate-300'}`},ozState.rebalActive?'ON':'OFF')),/*#__PURE__*/React.createElement("div",{className:"col-span-8 grid grid-cols-3 gap-4"},/*#__PURE__*/React.createElement("div",{className:"bg-slate-950 p-3 border border-slate-800 rounded-xl flex flex-col items-center justify-center h-full"},/*#__PURE__*/React.createElement(MasteringKnob,{param:"rebalMid",min:-12,max:12,value:ozState.rebalMid,unit:"dB",label:"MID GAIN",color:"#38bdf8",onChange:(p,v)=>setOzState(prev=>({...prev,[p]:v}))})),/*#__PURE__*/React.createElement("div",{className:"bg-slate-950 p-3 border border-slate-800 rounded-xl flex flex-col items-center justify-center h-full"},/*#__PURE__*/React.createElement(MasteringKnob,{param:"rebalSide",min:-12,max:12,value:ozState.rebalSide,unit:"dB",label:"SIDE GAIN",color:"#22d3ee",onChange:(p,v)=>setOzState(prev=>({...prev,[p]:v}))})),/*#__PURE__*/React.createElement("div",{className:"bg-slate-950 p-3 border border-slate-800 rounded-xl flex flex-col items-center justify-center h-full"},/*#__PURE__*/React.createElement("span",{className:"text-[10px] text-slate-500 oz-font-mono text-center leading-snug px-2"},"ChannelSplitter + M/S gains",/*#__PURE__*/React.createElement("br",null),"Center (vocal/bass) vs Sides (stereo width)"))))),/*#__PURE__*/React.createElement("div",{className:`flex-1 flex flex-col p-4 gap-4 ${ozState.activeModule==='carla'?'':'hidden'}`},/*#__PURE__*/React.createElement("div",{className:"oz-panel p-4 rounded-xl flex-1 grid grid-cols-12 gap-6 items-center"},/*#__PURE__*/React.createElement("div",{className:"col-span-4 flex flex-col items-center justify-center border-r border-slate-800 pr-4"},/*#__PURE__*/React.createElement("span",{className:"text-xs font-bold text-teal-400 uppercase oz-font-mono mb-3"},"Carla Bridge (VST FX)"),/*#__PURE__*/React.createElement("button",{onClick:()=>toggleChainModule((ozState.chain||[]).find(m=>m.type==='carla')?.id),className:`px-3 py-1 rounded text-[10px] font-bold border transition-colors mt-2 ${ozState.carlaBridgeActive?'bg-teal-700 border-teal-500 text-white':'bg-slate-800 border-slate-700 text-slate-300'}`},ozState.carlaBridgeActive?'ON':'OFF')),/*#__PURE__*/React.createElement("div",{className:"col-span-8 space-y-3"},/*#__PURE__*/React.createElement("div",{className:"flex items-end gap-2 flex-wrap"},/*#__PURE__*/React.createElement("div",{className:"flex-1 min-w-[220px]"},/*#__PURE__*/React.createElement("div",{className:"text-[9px] text-slate-500 oz-font-mono mb-1"},"CHỌN VST FX (đã scan trên máy)"),/*#__PURE__*/React.createElement("select",{value:ozState.carlaBridge&&ozState.carlaBridge.plugin||'',onChange:e=>setOzState(prev=>({...prev,carlaBridge:{...(prev.carlaBridge||{}),plugin:e.target.value,plugin_path:''}})),className:"w-full bg-slate-950 border border-slate-700 text-teal-300 rounded px-2 py-1.5 text-xs outline-none focus:border-teal-500"},/*#__PURE__*/React.createElement("option",{value:""},"— Chọn VST FX —"),(masterCarlaVsts||[]).map(v=>/*#__PURE__*/React.createElement("option",{key:v.id||v.name,value:v.id||v.name},v.name||v.id)))),/*#__PURE__*/React.createElement("button",{onClick:()=>{const plugin=ozState.carlaBridge&&ozState.carlaBridge.plugin||'';if(!plugin){window.showToast&&window.showToast('Chọn VST FX trước khi Load Carla','warning');return;}window.SonicAPI.openInCarla(plugin,ozState.carlaBridge.plugin_path).then(r=>{if(r&&r.success){setOzState(prev=>({...prev,carlaBridge:{...(prev.carlaBridge||{}),connected:true}}));window.showToast&&window.showToast('Đã mở Carla với '+plugin+' (VST FX) — chỉnh âm thanh trong Carla','success');}else{window.showToast&&window.showToast('Không mở được Carla','error');}}).catch(err=>window.showToast&&window.showToast('Lỗi mở Carla: '+(err.message||err),'error'));},className:"px-4 py-1.5 bg-teal-700 hover:bg-teal-600 text-white text-xs font-semibold rounded transition flex items-center gap-1 shrink-0"},/*#__PURE__*/React.createElement("i",{"data-lucide":"play",className:"w-3 h-3"})," Load Carla Bridge"),/*#__PURE__*/React.createElement("button",{onClick:()=>{if(window.SonicCarlaMidi&&window.SonicCarlaMidi.stopBridge)window.SonicCarlaMidi.stopBridge();setOzState(prev=>({...prev,carlaBridge:{...(prev.carlaBridge||{}),connected:false}}));window.showToast&&window.showToast('Đã ngắt kết nối Carla Bridge','info');},className:"px-4 py-1.5 bg-zinc-800 hover:bg-zinc-700 text-red-300 text-xs font-semibold rounded transition flex items-center gap-1 shrink-0"},/*#__PURE__*/React.createElement("i",{"data-lucide":"square",className:"w-3 h-3"})," Stop / Unload")),/*#__PURE__*/React.createElement("div",{className:"text-[10px] text-slate-500 oz-font-mono leading-relaxed"},"Mở Carla với VST FX để chỉnh sửa âm thanh master (native GUI). Module này là pass-through trong master chain (không thêm DSP WebAudio).")))),/*#__PURE__*/React.createElement("div",{className:"border-t border-slate-800 bg-slate-900/60 p-3 flex flex-col shrink-0"},/*#__PURE__*/React.createElement("div",{className:"flex items-center justify-between pb-1.5 border-b border-slate-800/80 mb-2"},/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-2"},/*#__PURE__*/React.createElement("span",{className:"text-[11px] font-bold text-white uppercase tracking-wider"},"Wave Observer"),/*#__PURE__*/React.createElement("span",{className:"text-[9px] bg-cyan-950 text-cyan-400 border border-cyan-800/60 px-1.5 py-0.2 rounded font-mono"},"Real-time Oscilloscope")),/*#__PURE__*/React.createElement("div",{className:"flex gap-2"},['Scope','Settings','Help','About'].map(tab=>/*#__PURE__*/React.createElement("button",{key:tab,className:`px-2 py-0.5 rounded text-[10px] font-bold ${tab==='Scope'?'bg-cyan-950 text-cyan-300 border border-cyan-800/60':'text-slate-400 hover:text-slate-200'}`},tab)))),/*#__PURE__*/React.createElement("div",{className:"relative w-full h-32 bg-[#090d16] border border-slate-800/80 rounded-lg overflow-hidden mb-2"},/*#__PURE__*/React.createElement("canvas",{ref:woCanvasRef,className:"w-full h-full block"})),/*#__PURE__*/React.createElement("div",{className:"flex items-center justify-between gap-4 text-[10px] oz-font-mono text-slate-400 mt-1 select-none"},/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-2 bg-slate-950 px-2.5 py-1.5 rounded border border-slate-800/80 shrink-0"},/*#__PURE__*/React.createElement("span",{className:"text-[10px] font-bold text-slate-300"},"Input"),/*#__PURE__*/React.createElement("div",{className:"flex flex-col gap-1 w-20"},/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-1"},/*#__PURE__*/React.createElement("span",{className:"text-[8px] text-slate-500 w-2"},"L"),/*#__PURE__*/React.createElement("div",{className:"w-16 h-1.5 bg-slate-900 rounded-sm overflow-hidden flex items-center"},/*#__PURE__*/React.createElement("div",{ref:woLeftMeterRef,className:"h-full bg-cyan-400 transition-all duration-75",style:{width:'0%'}}))),/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-1"},/*#__PURE__*/React.createElement("span",{className:"text-[8px] text-slate-500 w-2"},"R"),/*#__PURE__*/React.createElement("div",{className:"w-16 h-1.5 bg-slate-900 rounded-sm overflow-hidden flex items-center"},/*#__PURE__*/React.createElement("div",{ref:woRightMeterRef,className:"h-full bg-teal-500 transition-all duration-75",style:{width:'0%'}}))))),/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-4 bg-slate-950 px-3 py-1 rounded border border-slate-800/80 flex-1 justify-around"},/*#__PURE__*/React.createElement("span",{className:"font-bold text-slate-300 uppercase tracking-widest text-[9px]"},"Scope"),/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-1.5"},/*#__PURE__*/React.createElement("span",null,"Channel"),/*#__PURE__*/React.createElement("select",{value:woChannel,onChange:e=>setWoChannel(e.target.value),className:"bg-slate-900 border border-slate-700 rounded px-1.5 py-0.5 text-[9px] text-slate-300 outline-none"},/*#__PURE__*/React.createElement("option",{value:"stereo"},"Stereo"),/*#__PURE__*/React.createElement("option",{value:"left"},"Left Only"),/*#__PURE__*/React.createElement("option",{value:"right"},"Right Only"))),/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-1.5"},/*#__PURE__*/React.createElement("span",null,"Mode"),/*#__PURE__*/React.createElement("select",{value:woMode,onChange:e=>setWoMode(e.target.value),className:"bg-slate-900 border border-slate-700 rounded px-1.5 py-0.5 text-[9px] text-slate-300 outline-none"},/*#__PURE__*/React.createElement("option",{value:"waveform"},"Waveform"),/*#__PURE__*/React.createElement("option",{value:"envelope"},"Envelope"))),/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-1.5"},/*#__PURE__*/React.createElement("span",null,"Duration:"),/*#__PURE__*/React.createElement("span",{className:"text-cyan-400 font-bold w-10 text-right"},woDuration.toFixed(3),"s"),/*#__PURE__*/React.createElement("input",{type:"range",min:"0.5",max:"5.0",step:"0.1",value:woDuration,onChange:e=>setWoDuration(parseFloat(e.target.value)),className:"w-16 h-1 cursor-pointer accent-cyan-400"})),/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-1.5"},/*#__PURE__*/React.createElement("span",null,"V.Zoom:"),/*#__PURE__*/React.createElement("span",{className:"text-cyan-400 font-bold w-12 text-right"},woZoom.toFixed(1)," dB"),/*#__PURE__*/React.createElement("input",{type:"range",min:"-12",max:"24",step:"0.5",value:woZoom,onChange:e=>setWoZoom(parseFloat(e.target.value)),className:"w-16 h-1 cursor-pointer accent-cyan-400"}))),/*#__PURE__*/React.createElement("button",{onClick:()=>setWoPaused(!woPaused),className:`px-3 py-1 rounded font-bold border transition shadow-md shrink-0 flex items-center gap-1 ${woPaused?'bg-amber-800 hover:bg-amber-700 border-amber-500 text-amber-100':'bg-slate-800 hover:bg-slate-700 border-slate-700 text-slate-200'}`},woPaused?'Resume':'Pause')))),/*#__PURE__*/React.createElement("div",{className:"w-64 bg-slate-900 border-l border-slate-800/80 p-3 flex flex-col justify-between shadow-2xl oz-font-mono text-xs z-20 shrink-0"},/*#__PURE__*/React.createElement("div",{className:"flex items-center justify-between border-b border-slate-800 pb-2 mb-2"},/*#__PURE__*/React.createElement("span",{className:"text-xs font-bold text-white tracking-wider uppercase flex items-center gap-1.5"},/*#__PURE__*/React.createElement("i",{"data-lucide":"sliders",className:"w-3 h-3 text-cyan-400"})," I/O METERS"),/*#__PURE__*/React.createElement("span",{className:"text-[9px] bg-cyan-950 border border-cyan-800 text-cyan-300 px-1 py-0.5 rounded"},"TRUE PEAK")),/*#__PURE__*/React.createElement("div",{className:"grid grid-cols-2 gap-2 text-[10px] text-center mb-2"},/*#__PURE__*/React.createElement("div",{className:"bg-slate-950 border border-slate-800/80 p-1.5 rounded"},/*#__PURE__*/React.createElement("div",{className:"text-slate-500 font-bold"},"IN PEAK"),/*#__PURE__*/React.createElement("div",{id:"inPeakText",className:"text-cyan-400 font-bold oz-font-mono"},"-inf dB")),/*#__PURE__*/React.createElement("div",{className:"bg-slate-950 border border-slate-800/80 p-1.5 rounded"},/*#__PURE__*/React.createElement("div",{className:"text-slate-500 font-bold"},"OUT PEAK"),/*#__PURE__*/React.createElement("div",{id:"outPeakText",className:"text-emerald-400 font-bold oz-font-mono"},"-inf dB"))),/*#__PURE__*/React.createElement("div",{className:"grid grid-cols-2 gap-2 text-[10px] text-center mb-2"},/*#__PURE__*/React.createElement("div",{className:"bg-slate-950 border border-slate-800/80 p-1.5 rounded"},/*#__PURE__*/React.createElement("div",{className:"text-slate-500 font-bold"},"OUT RMS"),/*#__PURE__*/React.createElement("div",{id:"outRmsText",className:"text-sky-300 font-bold oz-font-mono"},"-inf")),/*#__PURE__*/React.createElement("div",{className:"bg-slate-950 border border-slate-800/80 p-1.5 rounded"},/*#__PURE__*/React.createElement("div",{className:"text-slate-500 font-bold"},"LOUDNESS"),/*#__PURE__*/React.createElement("div",{id:"lufsText",className:"text-fuchsia-300 font-bold oz-font-mono"},"--.-"))),/*#__PURE__*/React.createElement("div",{className:"flex-1 bg-slate-950 border border-slate-800/90 rounded-xl p-2 flex justify-around relative overflow-hidden my-1"},/*#__PURE__*/React.createElement("div",{className:"flex flex-col items-center h-full"},/*#__PURE__*/React.createElement("span",{className:"text-[9px] text-slate-500 mb-1"},"IN"),/*#__PURE__*/React.createElement("div",{className:"flex-1 w-5 bg-slate-900 border border-slate-800 rounded relative overflow-hidden flex items-end"},/*#__PURE__*/React.createElement("canvas",{ref:inMeterCanvasRef,className:"w-full h-full block"}))),/*#__PURE__*/React.createElement("div",{className:"flex flex-col items-center h-full"},/*#__PURE__*/React.createElement("span",{className:"text-[9px] text-slate-500 mb-1"},"OUT"),/*#__PURE__*/React.createElement("div",{className:"flex-1 w-6 bg-slate-900 border border-slate-800 rounded relative overflow-hidden flex items-end"},/*#__PURE__*/React.createElement("canvas",{ref:outMeterCanvasRef,className:"w-full h-full block"})))),/*#__PURE__*/React.createElement("div",{className:"grid grid-cols-2 gap-1.5 mt-2"},/*#__PURE__*/React.createElement("button",{onClick:()=>setOzState(prev=>({...prev,isBypassed:!prev.isBypassed})),className:`py-1.5 rounded text-[10px] font-bold border transition-colors ${ozState.isBypassed?'bg-cyan-600 border-cyan-500 text-white':'bg-slate-800 hover:bg-slate-700 text-slate-200 border-slate-700'}`},"Bypass"),/*#__PURE__*/React.createElement("button",{className:"bg-slate-800 hover:bg-slate-700 text-slate-200 py-1.5 rounded text-[10px] font-bold border border-slate-700 transition-colors"},"Gain Match"),/*#__PURE__*/React.createElement("button",{className:"bg-slate-800 hover:bg-slate-700 text-slate-300 py-1.5 rounded text-[10px] border border-slate-700"},"Codec"),/*#__PURE__*/React.createElement("button",{className:"bg-slate-800 hover:bg-slate-700 text-slate-300 py-1.5 rounded text-[10px] border border-slate-700"},"Dither"))))),addModuleOpen&&/*#__PURE__*/React.createElement("div",{className:"fixed inset-0 z-[110] bg-black/80 backdrop-blur-sm flex items-center justify-center p-4",onClick:()=>setAddModuleOpen(false)},/*#__PURE__*/React.createElement("div",{className:"w-full max-w-lg bg-slate-900 border border-slate-700 rounded-2xl p-5 space-y-4 shadow-2xl max-h-[80vh] overflow-y-auto",onClick:e=>e.stopPropagation()},/*#__PURE__*/React.createElement("div",{className:"flex items-center justify-between border-b border-slate-800 pb-3"},/*#__PURE__*/React.createElement("h3",{className:"text-xs font-bold text-white uppercase tracking-wider oz-font-mono"},"THÊM MODULE VÀO MASTERING CHAIN"),/*#__PURE__*/React.createElement("button",{onClick:()=>setAddModuleOpen(false),className:"text-slate-400 hover:text-white"},/*#__PURE__*/React.createElement("i",{"data-lucide":"x",className:"w-4 h-4"}))),/*#__PURE__*/React.createElement("div",{className:"grid grid-cols-2 gap-3 text-xs"},[{t:'eqpro',c:'text-teal-400',i:'chart-area',d:'Parametric / Graphic EQ PRO — Pro-Q style, 8 bands, interactive canvas + spectrum.'},{t:'compressor',c:'text-amber-400',i:'compress',d:'Nén dynamic range, glue & punch cho master.'},{t:'limiter',c:'text-rose-400',i:'shield-half',d:'True-Peak ceiling (ratio 20:1, knee 0) chống clipping.'},{t:'exciter',c:'text-purple-400',i:'wand-2',d:'Saturation hài cho warmth và top-end brilliance.'},{t:'rebalance',c:'text-sky-400',i:'sliders-horizontal',d:'Cân bằng Mid/Side (Vocal/Bass vs stereo width).'},{t:'eq',c:'text-cyan-400',i:'activity',d:'EQ 4-band (lowshelf, 2× peaking, highshelf) + presets.'},{t:'imager',c:'text-fuchsia-400',i:'radio',d:'Stereo width 4-band M/S + vectorscope/correlation.'},{t:'maximizer',c:'text-emerald-400',i:'gauge',d:'Maximizer: boost, soft clip, upward comp, ceiling.'}].map(m=>/*#__PURE__*/React.createElement("button",{key:m.t,onClick:()=>addModuleToChain(m.t),className:"p-3 bg-slate-950 hover:bg-slate-800 border border-slate-800 rounded-xl text-left space-y-1 transition-colors"},/*#__PURE__*/React.createElement("div",{className:`font-bold ${m.c} flex items-center gap-1.5`},/*#__PURE__*/React.createElement("i",{"data-lucide":m.i,className:"w-3.5 h-3.5"})," ",MODULE_META[m.t].name),/*#__PURE__*/React.createElement("div",{className:"text-[10px] text-slate-400"},m.d)))),/*#__PURE__*/React.createElement("div",{className:"border-t border-slate-800 pt-3"},/*#__PURE__*/React.createElement("div",{className:"text-[10px] font-bold text-teal-400 uppercase tracking-wider oz-font-mono mb-2"},"VST FX (Ozone...)"),/*#__PURE__*/React.createElement("button",{onClick:()=>setMasterVstPickerOpen(true),className:"w-full bg-slate-950 hover:bg-slate-800 border border-dashed border-slate-700 hover:border-teal-600 text-teal-300 rounded px-2 py-2 text-[11px] text-left transition-colors",title:"Plugin Picker theo category (EQ / Reverb / Comp / Utility)"},/*#__PURE__*/React.createElement("i",{"data-lucide":"plug",className:"w-3 h-3 inline-block mr-1"})," Chọn VST FX từ thư viện — Picker: EQ / Reverb / Comp / Utility")))),/*#__PURE__*/React.createElement(VstPluginPicker,{open:masterVstPickerOpen,onClose:()=>setMasterVstPickerOpen(false),plugins:masterFxList,onPick:f=>addVstToChain(f.path)}));};// ── Media Explorer Panel (from md/51_MEDIA_EXPLORER.md) ── +setOzState(prev=>({...prev,eqLowGain:preset.bands[0]?preset.bands[0].gain:prev.eqLowGain,eqMid1Gain:preset.bands[1]?preset.bands[1].gain:prev.eqMid1Gain,eqMid2Gain:preset.bands[2]?preset.bands[2].gain:prev.eqMid2Gain,eqHighGain:preset.bands[3]?preset.bands[3].gain:prev.eqHighGain,eqPreset:presetKey}));};const bandKnob=(param,min,max,val,unit,label,freq,color,filterType)=>/*#__PURE__*/React.createElement("div",{className:"bg-slate-900/80 border border-slate-800 p-2 flex flex-col justify-between items-center rounded-lg"},/*#__PURE__*/React.createElement("div",{className:"flex items-center justify-between text-[10px] font-bold w-full",style:{color}},/*#__PURE__*/React.createElement("span",null,label),/*#__PURE__*/React.createElement("span",{className:"text-[9px] font-mono text-slate-400"},freq)),/*#__PURE__*/React.createElement("div",{className:"my-1"},/*#__PURE__*/React.createElement(MasteringKnob,{param:param,min:min,max:max,value:ozState[param]!==undefined?ozState[param]:val,unit:unit,color:color,onChange:(p,v)=>setOzState(prev=>({...prev,[p]:v}))})),/*#__PURE__*/React.createElement("div",{className:"text-[9px] text-slate-500 font-mono text-center mt-1"},filterType));return/*#__PURE__*/React.createElement("div",{className:"fixed inset-0 z-[100] flex items-center justify-center bg-black/70 backdrop-blur-sm",onClick:onClose},/*#__PURE__*/React.createElement("div",{id:"masteringModalBody",className:"flex flex-col bg-slate-950 text-slate-200 w-[95vw] h-[92vh] max-w-[1400px] rounded-2xl border border-slate-800/80 shadow-2xl overflow-hidden",onClick:e=>e.stopPropagation(),style:{fontFamily:"'Inter', sans-serif"}},/*#__PURE__*/React.createElement("header",{className:"h-12 bg-slate-900/90 border-b border-slate-800/80 flex items-center justify-between px-4 z-30 shrink-0"},/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-3"},/*#__PURE__*/React.createElement("div",{className:"w-7 h-7 rounded-lg bg-cyan-600 flex items-center justify-center font-bold text-white shadow-lg shadow-cyan-950"},/*#__PURE__*/React.createElement("i",{"data-lucide":"zap",className:"w-3.5 h-3.5"})),/*#__PURE__*/React.createElement("div",null,/*#__PURE__*/React.createElement("h1",{className:"text-xs font-bold tracking-wider text-white flex items-center gap-2"},"MASTERING SUITE ",/*#__PURE__*/React.createElement("span",{className:"text-[9px] bg-cyan-950 text-cyan-400 border border-cyan-800/80 px-1.5 py-0.5 rounded oz-font-mono"},"WEB MASTERING V10.5")))),/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-3 bg-slate-950 px-3 py-1 rounded-lg border border-slate-800/80"},/*#__PURE__*/React.createElement("button",{onClick:startAudioDemo,className:"px-3 py-1 bg-cyan-600 hover:bg-cyan-500 text-white rounded text-xs font-semibold flex items-center gap-1.5 transition-all shadow-md shadow-cyan-950"},/*#__PURE__*/React.createElement("i",{"data-lucide":"play",className:"w-3 h-3"})," ",/*#__PURE__*/React.createElement("span",null,"Play Reference")),/*#__PURE__*/React.createElement("button",{onClick:stopAudioDemo,className:"px-3 py-1 bg-slate-800 hover:bg-slate-700 text-slate-300 rounded text-xs font-semibold flex items-center gap-1.5 transition-all"},/*#__PURE__*/React.createElement("i",{"data-lucide":"square",className:"w-3 h-3"})," ",/*#__PURE__*/React.createElement("span",null,"Stop")),/*#__PURE__*/React.createElement("div",{className:"h-4 w-[1px] bg-slate-800 mx-1"}),/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-2 text-xs oz-font-mono"},/*#__PURE__*/React.createElement("span",{className:"text-slate-400 text-[11px]"},"Preset:"),/*#__PURE__*/React.createElement("select",{className:"bg-slate-900 border border-slate-700 rounded px-2 py-0.5 text-xs text-cyan-300 outline-none focus:border-cyan-500"},/*#__PURE__*/React.createElement("option",{value:"adaptive"},"Adaptive Dynamic Master"),/*#__PURE__*/React.createElement("option",{value:"edm_club"},"EDM / Club Punch Maximizer"),/*#__PURE__*/React.createElement("option",{value:"wide_space"},"Cinematic Stereo Expansion"),/*#__PURE__*/React.createElement("option",{value:"transparent"},"Transparent High-Clarity Limiter")))),/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-3 text-xs oz-font-mono"},/*#__PURE__*/React.createElement("span",{className:"text-slate-400 text-[11px]"},"Target LUFS:"),/*#__PURE__*/React.createElement("span",{className:"text-cyan-400 font-bold bg-slate-950 border border-cyan-900/60 px-2 py-0.5 rounded"},"-11.0 LUFS"),/*#__PURE__*/React.createElement("button",{onClick:()=>{getAudioContext();setMasterConnected(prev=>!prev);},className:`px-2 py-0.5 rounded text-[10px] font-bold border transition-colors ${masterConnected?'bg-emerald-700 border-emerald-500 text-white':'bg-slate-800 border-slate-700 text-slate-300 hover:bg-slate-700'}`},masterConnected?'Master ON':'Master OFF'),/*#__PURE__*/React.createElement("button",{onClick:onClose,className:"px-2 py-0.5 rounded text-[10px] font-bold border transition-colors bg-slate-800 border-slate-700 text-slate-300 hover:bg-[#c2410c] hover:border-red-600 hover:text-white"},"\u0110\xF3ng"),/*#__PURE__*/React.createElement("button",{onClick:onClose,className:"text-slate-500 hover:text-slate-300 ml-1"},/*#__PURE__*/React.createElement("i",{"data-lucide":"x",className:"w-4 h-4"})))),/*#__PURE__*/React.createElement("div",{className:"h-16 bg-slate-950 border-b border-slate-800 px-4 flex items-center gap-2 overflow-x-auto shrink-0 oz-scrollbar"},/*#__PURE__*/React.createElement("span",{className:"text-[10px] font-bold text-slate-500 uppercase tracking-widest mr-1 oz-font-mono shrink-0"},"CHAIN:"),(ozState.chain||[]).map((mod,idx)=>{const isVst=mod.type==='vst3';const meta=MODULE_META[mod.type]||{name:mod.type,sub:'',icon:'circle',color:'#94a3b8'};const isEditing=!isVst&&ozState.activeModule===mod.type||isVst&&activeMasterVstPath===mod.path;const isOn=isVst?mod.active!==false:chainActive(mod.type);return/*#__PURE__*/React.createElement("div",{key:mod.id,draggable:true,onDragStart:e=>{dragChainIndexRef.current=idx;e.dataTransfer.effectAllowed='move';},onDragOver:e=>{e.preventDefault();e.dataTransfer.dropEffect='move';},onDrop:e=>{e.preventDefault();const from=dragChainIndexRef.current;if(from!==null&&from!==idx)reorderChain(from,idx);dragChainIndexRef.current=null;},onClick:()=>{if(mod.type==='vst3'){openMasterVstGui({path:mod.path,name:mod.name||mod.path});}else{closeMasterGui();switchModule(mod.type);}},className:`w-40 h-12 rounded-lg p-2 flex items-center justify-between cursor-pointer transition-all shrink-0 ${isEditing?'oz-card-active':'oz-card'}`},/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-2 min-w-0"},/*#__PURE__*/React.createElement("button",{onClick:e=>{e.stopPropagation();toggleChainModule(mod.id);},className:"w-4 h-4 rounded-full flex items-center justify-center text-[9px] font-bold shrink-0",style:{backgroundColor:isOn?'#38bdf8':'#334155',color:isOn?'#0f172a':'#94a3b8'}},/*#__PURE__*/React.createElement("i",{"data-lucide":"power",className:"w-2.5 h-2.5"})),/*#__PURE__*/React.createElement("div",{className:"min-w-0"},/*#__PURE__*/React.createElement("div",{className:"text-[11px] font-bold text-slate-200 truncate"},isVst?mod.name||meta.name:meta.name),/*#__PURE__*/React.createElement("div",{className:"text-[9px] oz-font-mono truncate",style:{color:meta.color}},idx+1,". ",isVst?mod.path?mod.path.split(/[\\/]/).pop():'VST3':meta.sub))),/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-1 shrink-0"},/*#__PURE__*/React.createElement("i",{"data-lucide":meta.icon,className:"w-3 h-3 text-slate-500"}),/*#__PURE__*/React.createElement("button",{onClick:e=>{e.stopPropagation();removeChainModule(mod.id);},className:"text-slate-600 hover:text-red-400 text-xs w-4 h-4 flex items-center justify-center shrink-0",title:"X\xF3a module"},/*#__PURE__*/React.createElement("i",{"data-lucide":"x",className:"w-3 h-3"}))));}),/*#__PURE__*/React.createElement("button",{onClick:()=>setAddModuleOpen(true),className:"w-24 h-12 rounded-lg border border-dashed border-slate-800 flex items-center justify-center text-slate-600 hover:text-slate-400 hover:border-slate-600 cursor-pointer transition-all shrink-0",title:"Th\xEAm module v\xE0o chain"},/*#__PURE__*/React.createElement("i",{"data-lucide":"plus",className:"w-4 h-4"}))),/*#__PURE__*/React.createElement("main",{className:"flex-1 flex overflow-hidden min-h-0"},masterGuiOpen?/*#__PURE__*/React.createElement("div",{className:"flex-1 flex flex-col bg-slate-950 relative min-h-0"},/*#__PURE__*/React.createElement(VstGuiEmbed,{url:masterGuiOpen.url,onClose:closeMasterGui,onDead:()=>{window.showToast&&window.showToast('Bridge VST GUI đã mất kết nối — trả về graph','error');closeMasterGui();}})):/*#__PURE__*/React.createElement("div",{className:"flex-1 flex flex-col bg-slate-950 relative overflow-y-auto oz-scrollbar"},/*#__PURE__*/React.createElement("div",{className:"h-9 bg-slate-900/60 border-b border-slate-800/80 px-4 flex items-center justify-between text-xs oz-font-mono shrink-0"},/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-3"},/*#__PURE__*/React.createElement("span",{className:"text-slate-400 flex items-center gap-1.5"},/*#__PURE__*/React.createElement("i",{"data-lucide":"headphones",className:"w-3 h-3 text-cyan-400"})," Delta Listen"),/*#__PURE__*/React.createElement("select",{className:"bg-slate-950 border border-slate-800 rounded px-2 py-0.5 text-[11px] text-slate-300 outline-none"},/*#__PURE__*/React.createElement("option",{value:"irc4"},"IRC IV - Classic"),/*#__PURE__*/React.createElement("option",{value:"irc3"},"IRC III - Balanced"),/*#__PURE__*/React.createElement("option",{value:"irc2"},"IRC II - Crisp"))),/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-2 text-[11px]"},ozState.activeModule==='eq'&&/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-1.5"},/*#__PURE__*/React.createElement("span",{className:"text-slate-400"},"EQ Presets:"),/*#__PURE__*/React.createElement("select",{value:ozState.eqPreset||'flat',onChange:e=>applyEQPreset(e.target.value),className:"bg-slate-950 border border-slate-700 text-cyan-300 rounded px-2 py-0.5 text-[11px] outline-none focus:border-cyan-500"},Object.keys(EQ_PRESET_LIBRARY).map(k=>/*#__PURE__*/React.createElement("option",{key:k,value:k},EQ_PRESET_LIBRARY[k].name)))),/*#__PURE__*/React.createElement("span",{className:"text-slate-400"},"Learn Input Gain:"),/*#__PURE__*/React.createElement("button",{className:"bg-cyan-950 text-cyan-300 border border-cyan-800/80 px-2 py-0.5 rounded font-bold hover:bg-cyan-900 transition-colors"},"-11.0 LUFS"))),/*#__PURE__*/React.createElement("div",{className:`flex-1 flex flex-col p-4 gap-4 ${ozState.activeModule==='eq'?'':'hidden'}`},/*#__PURE__*/React.createElement("div",{className:"relative w-full h-64 bg-slate-950 border border-slate-800/90 rounded-xl overflow-hidden shadow-inner cursor-crosshair"},/*#__PURE__*/React.createElement("canvas",{ref:eqCanvasRef,className:"w-full h-full block"})),/*#__PURE__*/React.createElement("div",{className:"oz-panel p-3 rounded-xl grid grid-cols-4 gap-3"},bandKnob('eqLowGain',-12,12,1.5,'dB','BAND 1 (LOW)','100 Hz','#22d3ee','Shelf Filter'),bandKnob('eqMid1Gain',-12,12,-1.0,'dB','BAND 2 (MID LOW)','822 Hz','#fbbf24','Dynamic Bell (Q: 0.7)'),bandKnob('eqMid2Gain',-12,12,2.0,'dB','BAND 3 (MID HIGH)','3.2 kHz','#a855f7','Dynamic Bell (Q: 1.2)'),bandKnob('eqHighGain',-12,12,1.8,'dB','BAND 4 (HIGH)','10 kHz','#34d399','High Shelf'))),/*#__PURE__*/React.createElement("div",{className:`flex-1 flex flex-col p-4 gap-4 ${ozState.activeModule==='eqpro'?'':'hidden'}`},(()=>{const chainMods=ozState.chain||[];const cm=chainMods.find(m=>m.type==='eqpro'&&m.active!==false)||chainMods.find(m=>m.type==='eqpro');if(!cm)return/*#__PURE__*/React.createElement("div",{className:"text-xs text-slate-500 font-mono py-10 text-center"},"Ch\u01B0a c\xF3 module EQ PRO trong chain \u2014 b\u1EA5m [+] \u0111\u1EC3 th\xEAm.");return/*#__PURE__*/React.createElement(InteractiveEqPro,{track:null,params:cm.params||{},onChange:next=>updateChainEntryParams(cm.id,next),getModule:()=>masterBus&&masterBus.eqProInstances?masterBus.eqProInstances[cm.id]:null,spectrumModules:()=>{const m=masterBus&&masterBus.eqProInstances?masterBus.eqProInstances[cm.id]:null;return m?[m]:[];},applyTo:fn=>{const m=masterBus&&masterBus.eqProInstances?masterBus.eqProInstances[cm.id]:null;if(m)fn(m);}});})()),/*#__PURE__*/React.createElement("div",{className:`flex-1 flex flex-col p-4 gap-4 ${ozState.activeModule==='imager'?'':'hidden'}`},/*#__PURE__*/React.createElement("div",{className:"grid grid-cols-12 gap-4 flex-1"},/*#__PURE__*/React.createElement("div",{className:"col-span-8 bg-slate-950 border border-slate-800/90 rounded-xl p-3 flex flex-col relative shadow-inner"},/*#__PURE__*/React.createElement("span",{className:"text-xs font-bold text-cyan-400 uppercase oz-font-mono mb-2 flex items-center justify-between w-full"},/*#__PURE__*/React.createElement("span",null,/*#__PURE__*/React.createElement("i",{"data-lucide":"radio",className:"w-3.5 h-3.5"})," Polar Vectorscope & Correlation Meter"),/*#__PURE__*/React.createElement("span",{className:"text-[11px] text-slate-400 normal-case"},"Corr: ",/*#__PURE__*/React.createElement("span",{id:"corrText",className:"font-bold text-emerald-400"},"1.00"))),/*#__PURE__*/React.createElement("div",{className:"flex-1 relative w-full h-56 flex items-center justify-center"},/*#__PURE__*/React.createElement("canvas",{ref:imagerCanvasRef,className:"w-full h-full block"}))),/*#__PURE__*/React.createElement("div",{className:"col-span-4 oz-panel p-3 rounded-xl flex flex-col justify-between"},/*#__PURE__*/React.createElement("span",{className:"text-xs font-bold text-slate-300 uppercase oz-font-mono"},"4-Band Stereo Width"),/*#__PURE__*/React.createElement("div",{className:"text-[10px] text-slate-500 oz-font-mono leading-snug"},"0% = Mono \xB7 100% = Original \xB7 200% = 2\xD7 Width"),/*#__PURE__*/React.createElement("div",{className:"space-y-3 my-auto"},[{id:'w1',label:'Band 1 (20-100Hz)',color:'#22d3ee',val:ozState.w1},{id:'w2',label:'Band 2 (100Hz-1kHz)',color:'#fbbf24',val:ozState.w2},{id:'w3',label:'Band 3 (1k-6kHz)',color:'#a855f7',val:ozState.w3},{id:'w4',label:'Band 4 (6k-20kHz)',color:'#34d399',val:ozState.w4}].map(b=>/*#__PURE__*/React.createElement("div",{key:b.id},/*#__PURE__*/React.createElement("div",{className:"flex justify-between text-[11px] font-mono mb-1"},/*#__PURE__*/React.createElement("span",{style:{color:b.color,fontWeight:700}},b.label),/*#__PURE__*/React.createElement("span",{id:b.id+'Val'},b.val,"%")),/*#__PURE__*/React.createElement("input",{type:"range",min:"0",max:"200",value:b.val,onChange:e=>setOzState(prev=>({...prev,[b.id]:parseInt(e.target.value)})),className:"w-full h-1 cursor-pointer",style:{accentColor:b.color}}))))))),/*#__PURE__*/React.createElement("div",{className:`flex-1 flex flex-col p-4 gap-4 ${ozState.activeModule==='maximizer'?'':'hidden'}`},/*#__PURE__*/React.createElement("div",{className:"oz-panel p-4 rounded-xl flex-1 grid grid-cols-12 gap-6 items-center"},/*#__PURE__*/React.createElement("div",{className:"col-span-4 flex flex-col items-center justify-center border-r border-slate-800 pr-4"},/*#__PURE__*/React.createElement("span",{className:"text-xs font-bold text-slate-400 uppercase oz-font-mono mb-3"},"Maximizer Gain Boost"),/*#__PURE__*/React.createElement("div",{className:"my-2"},/*#__PURE__*/React.createElement(MasteringKnob,{param:"maxGain",min:0,max:12,value:ozState.maxGain,unit:"dB",color:"#22d3ee",size:"large",onChange:(p,v)=>setOzState(prev=>({...prev,[p]:v}))})),/*#__PURE__*/React.createElement("div",{className:"w-full mt-4 bg-slate-950 p-2 rounded-lg border border-slate-800 text-xs oz-font-mono flex flex-col gap-1.5"},/*#__PURE__*/React.createElement("div",{className:"flex justify-between items-center w-full"},/*#__PURE__*/React.createElement("span",{className:"text-slate-400"},"Ceiling Level:"),/*#__PURE__*/React.createElement("span",{className:"text-cyan-400 font-bold"},ozState.ceiling.toFixed(2)," dB")),/*#__PURE__*/React.createElement("input",{type:"range",min:"-12",max:"0",step:"0.1",value:ozState.ceiling,onChange:e=>setOzState(prev=>({...prev,ceiling:parseFloat(e.target.value)})),className:"w-full h-1 cursor-pointer accent-cyan-400"}))),/*#__PURE__*/React.createElement("div",{className:"col-span-8 grid grid-cols-3 gap-4"},/*#__PURE__*/React.createElement("div",{className:"bg-slate-950 p-3 border border-slate-800 rounded-xl flex flex-col items-center justify-center h-full"},/*#__PURE__*/React.createElement(MasteringKnob,{param:"maxUpward",min:0,max:10,value:ozState.maxUpward,unit:"dB",label:"UPWARD COMPRESS",color:"#22d3ee",onChange:(p,v)=>setOzState(prev=>({...prev,[p]:v}))})),/*#__PURE__*/React.createElement("div",{className:"bg-slate-950 p-3 border border-slate-800 rounded-xl flex flex-col items-center justify-center h-full"},/*#__PURE__*/React.createElement(MasteringKnob,{param:"maxSoftClip",min:0,max:100,value:ozState.maxSoftClip,unit:"%",label:"SOFT CLIPPER",color:"#fbbf24",onChange:(p,v)=>setOzState(prev=>({...prev,[p]:v}))})),/*#__PURE__*/React.createElement("div",{className:"bg-slate-950 p-3 border border-slate-800 rounded-xl flex flex-col items-center justify-center h-full"},/*#__PURE__*/React.createElement(MasteringKnob,{param:"maxTransient",min:0,max:100,value:ozState.maxTransient,unit:"%",label:"TRANSIENT EMPHASIS",color:"#34d399",onChange:(p,v)=>setOzState(prev=>({...prev,[p]:v}))}))))),/*#__PURE__*/React.createElement("div",{className:`flex-1 flex flex-col p-4 gap-4 ${ozState.activeModule==='compressor'?'':'hidden'}`},/*#__PURE__*/React.createElement("div",{className:"oz-panel p-4 rounded-xl flex-1 grid grid-cols-12 gap-6 items-center"},/*#__PURE__*/React.createElement("div",{className:"col-span-4 flex flex-col items-center justify-center border-r border-slate-800 pr-4"},/*#__PURE__*/React.createElement("span",{className:"text-xs font-bold text-amber-400 uppercase oz-font-mono mb-3"},"Bus Compressor"),/*#__PURE__*/React.createElement("div",{className:"my-2"},/*#__PURE__*/React.createElement(MasteringKnob,{param:"compMakeup",min:0,max:12,value:ozState.compMakeup,unit:"dB",label:"MAKE-UP GAIN",color:"#fbbf24",onChange:(p,v)=>setOzState(prev=>({...prev,[p]:v}))})),/*#__PURE__*/React.createElement("button",{onClick:()=>toggleChainModule((ozState.chain||[]).find(m=>m.type==='compressor')?.id),className:`px-3 py-1 rounded text-[10px] font-bold border transition-colors mt-2 ${ozState.compActive?'bg-amber-700 border-amber-500 text-white':'bg-slate-800 border-slate-700 text-slate-300'}`},ozState.compActive?'ON':'OFF')),/*#__PURE__*/React.createElement("div",{className:"col-span-8 grid grid-cols-3 gap-4"},/*#__PURE__*/React.createElement("div",{className:"bg-slate-950 p-3 border border-slate-800 rounded-xl flex flex-col items-center justify-center h-full"},/*#__PURE__*/React.createElement(MasteringKnob,{param:"compThreshold",min:-60,max:0,value:ozState.compThreshold,unit:"dB",label:"THRESHOLD",color:"#fbbf24",onChange:(p,v)=>setOzState(prev=>({...prev,[p]:v}))})),/*#__PURE__*/React.createElement("div",{className:"bg-slate-950 p-3 border border-slate-800 rounded-xl flex flex-col items-center justify-center h-full"},/*#__PURE__*/React.createElement(MasteringKnob,{param:"compRatio",min:1,max:20,value:ozState.compRatio,unit:":1",label:"RATIO",color:"#f59e0b",onChange:(p,v)=>setOzState(prev=>({...prev,[p]:v}))})),/*#__PURE__*/React.createElement("div",{className:"bg-slate-950 p-3 border border-slate-800 rounded-xl flex flex-col items-center justify-center h-full"},/*#__PURE__*/React.createElement("span",{className:"text-[10px] text-slate-500 oz-font-mono text-center leading-snug px-2"},"Glue & Punch",/*#__PURE__*/React.createElement("br",null),"Attack 20ms \xB7 Release 250ms \xB7 Knee 8dB"))))),/*#__PURE__*/React.createElement("div",{className:`flex-1 flex flex-col p-4 gap-4 ${ozState.activeModule==='limiter'?'':'hidden'}`},/*#__PURE__*/React.createElement("div",{className:"oz-panel p-4 rounded-xl flex-1 grid grid-cols-12 gap-6 items-center"},/*#__PURE__*/React.createElement("div",{className:"col-span-4 flex flex-col items-center justify-center border-r border-slate-800 pr-4"},/*#__PURE__*/React.createElement("span",{className:"text-xs font-bold text-rose-400 uppercase oz-font-mono mb-3"},"Brickwall Limiter"),/*#__PURE__*/React.createElement("button",{onClick:()=>toggleChainModule((ozState.chain||[]).find(m=>m.type==='limiter')?.id),className:`px-3 py-1 rounded text-[10px] font-bold border transition-colors mt-2 ${ozState.limActive?'bg-rose-700 border-rose-500 text-white':'bg-slate-800 border-slate-700 text-slate-300'}`},ozState.limActive?'ON':'OFF')),/*#__PURE__*/React.createElement("div",{className:"col-span-8 grid grid-cols-3 gap-4"},/*#__PURE__*/React.createElement("div",{className:"bg-slate-950 p-3 border border-slate-800 rounded-xl flex flex-col items-center justify-center h-full"},/*#__PURE__*/React.createElement(MasteringKnob,{param:"limThreshold",min:-24,max:0,value:ozState.limThreshold,unit:"dB",label:"CEILING",color:"#f43f5e",onChange:(p,v)=>setOzState(prev=>({...prev,[p]:v}))})),/*#__PURE__*/React.createElement("div",{className:"bg-slate-950 p-3 border border-slate-800 rounded-xl flex flex-col items-center justify-center h-full"},/*#__PURE__*/React.createElement("span",{className:"text-[10px] text-slate-500 oz-font-mono text-center leading-snug px-2"},"True-Peak limiting",/*#__PURE__*/React.createElement("br",null),"Ratio 20:1 \xB7 Knee 0dB",/*#__PURE__*/React.createElement("br",null),"Attack 1ms \xB7 Release 50ms"))))),/*#__PURE__*/React.createElement("div",{className:`flex-1 flex flex-col p-4 gap-4 ${ozState.activeModule==='exciter'?'':'hidden'}`},/*#__PURE__*/React.createElement("div",{className:"oz-panel p-4 rounded-xl flex-1 grid grid-cols-12 gap-6 items-center"},/*#__PURE__*/React.createElement("div",{className:"col-span-4 flex flex-col items-center justify-center border-r border-slate-800 pr-4"},/*#__PURE__*/React.createElement("span",{className:"text-xs font-bold text-purple-400 uppercase oz-font-mono mb-3"},"Harmonic Exciter"),/*#__PURE__*/React.createElement("button",{onClick:()=>toggleChainModule((ozState.chain||[]).find(m=>m.type==='exciter')?.id),className:`px-3 py-1 rounded text-[10px] font-bold border transition-colors mt-2 ${ozState.excActive?'bg-purple-700 border-purple-500 text-white':'bg-slate-800 border-slate-700 text-slate-300'}`},ozState.excActive?'ON':'OFF')),/*#__PURE__*/React.createElement("div",{className:"col-span-8 grid grid-cols-3 gap-4"},/*#__PURE__*/React.createElement("div",{className:"bg-slate-950 p-3 border border-slate-800 rounded-xl flex flex-col items-center justify-center h-full"},/*#__PURE__*/React.createElement(MasteringKnob,{param:"excDrive",min:0,max:100,value:ozState.excDrive,unit:"%",label:"DRIVE / MIX",color:"#c084fc",onChange:(p,v)=>setOzState(prev=>({...prev,[p]:v}))})),/*#__PURE__*/React.createElement("div",{className:"bg-slate-950 p-3 border border-slate-800 rounded-xl flex flex-col items-center justify-center h-full"},/*#__PURE__*/React.createElement("span",{className:"text-[10px] text-slate-500 oz-font-mono text-center leading-snug px-2"},"WaveShaper saturation",/*#__PURE__*/React.createElement("br",null),"High-pass 2kHz",/*#__PURE__*/React.createElement("br",null),"4\xD7 oversampled \xB7 wet/dry mix"))))),/*#__PURE__*/React.createElement("div",{className:`flex-1 flex flex-col p-4 gap-4 ${ozState.activeModule==='rebalance'?'':'hidden'}`},/*#__PURE__*/React.createElement("div",{className:"oz-panel p-4 rounded-xl flex-1 grid grid-cols-12 gap-6 items-center"},/*#__PURE__*/React.createElement("div",{className:"col-span-4 flex flex-col items-center justify-center border-r border-slate-800 pr-4"},/*#__PURE__*/React.createElement("span",{className:"text-xs font-bold text-sky-400 uppercase oz-font-mono mb-3"},"Master Rebalance (M/S)"),/*#__PURE__*/React.createElement("button",{onClick:()=>toggleChainModule((ozState.chain||[]).find(m=>m.type==='rebalance')?.id),className:`px-3 py-1 rounded text-[10px] font-bold border transition-colors mt-2 ${ozState.rebalActive?'bg-sky-700 border-sky-500 text-white':'bg-slate-800 border-slate-700 text-slate-300'}`},ozState.rebalActive?'ON':'OFF')),/*#__PURE__*/React.createElement("div",{className:"col-span-8 grid grid-cols-3 gap-4"},/*#__PURE__*/React.createElement("div",{className:"bg-slate-950 p-3 border border-slate-800 rounded-xl flex flex-col items-center justify-center h-full"},/*#__PURE__*/React.createElement(MasteringKnob,{param:"rebalMid",min:-12,max:12,value:ozState.rebalMid,unit:"dB",label:"MID GAIN",color:"#38bdf8",onChange:(p,v)=>setOzState(prev=>({...prev,[p]:v}))})),/*#__PURE__*/React.createElement("div",{className:"bg-slate-950 p-3 border border-slate-800 rounded-xl flex flex-col items-center justify-center h-full"},/*#__PURE__*/React.createElement(MasteringKnob,{param:"rebalSide",min:-12,max:12,value:ozState.rebalSide,unit:"dB",label:"SIDE GAIN",color:"#22d3ee",onChange:(p,v)=>setOzState(prev=>({...prev,[p]:v}))})),/*#__PURE__*/React.createElement("div",{className:"bg-slate-950 p-3 border border-slate-800 rounded-xl flex flex-col items-center justify-center h-full"},/*#__PURE__*/React.createElement("span",{className:"text-[10px] text-slate-500 oz-font-mono text-center leading-snug px-2"},"ChannelSplitter + M/S gains",/*#__PURE__*/React.createElement("br",null),"Center (vocal/bass) vs Sides (stereo width)"))))),/*#__PURE__*/React.createElement("div",{className:`flex-1 flex flex-col p-4 gap-4 ${ozState.activeModule==='carla'?'':'hidden'}`},/*#__PURE__*/React.createElement("div",{className:"oz-panel p-4 rounded-xl flex-1 grid grid-cols-12 gap-6 items-center"},/*#__PURE__*/React.createElement("div",{className:"col-span-4 flex flex-col items-center justify-center border-r border-slate-800 pr-4"},/*#__PURE__*/React.createElement("span",{className:"text-xs font-bold text-teal-400 uppercase oz-font-mono mb-3"},"Carla Bridge (VST FX)"),/*#__PURE__*/React.createElement("button",{onClick:()=>toggleChainModule((ozState.chain||[]).find(m=>m.type==='carla')?.id),className:`px-3 py-1 rounded text-[10px] font-bold border transition-colors mt-2 ${ozState.carlaBridgeActive?'bg-teal-700 border-teal-500 text-white':'bg-slate-800 border-slate-700 text-slate-300'}`},ozState.carlaBridgeActive?'ON':'OFF')),/*#__PURE__*/React.createElement("div",{className:"col-span-8 space-y-3"},/*#__PURE__*/React.createElement("div",{className:"flex items-end gap-2 flex-wrap"},/*#__PURE__*/React.createElement("div",{className:"flex-1 min-w-[220px]"},/*#__PURE__*/React.createElement("div",{className:"text-[9px] text-slate-500 oz-font-mono mb-1"},"CH\u1ECCN VST FX (\u0111\xE3 scan tr\xEAn m\xE1y)"),/*#__PURE__*/React.createElement("select",{value:ozState.carlaBridge&&ozState.carlaBridge.plugin||'',onChange:e=>setOzState(prev=>({...prev,carlaBridge:{...(prev.carlaBridge||{}),plugin:e.target.value,plugin_path:''}})),className:"w-full bg-slate-950 border border-slate-700 text-teal-300 rounded px-2 py-1.5 text-xs outline-none focus:border-teal-500"},/*#__PURE__*/React.createElement("option",{value:""},"\u2014 Ch\u1ECDn VST FX \u2014"),(masterCarlaVsts||[]).map(v=>/*#__PURE__*/React.createElement("option",{key:v.id||v.name,value:v.id||v.name},v.name||v.id)))),/*#__PURE__*/React.createElement("button",{onClick:()=>{const plugin=ozState.carlaBridge&&ozState.carlaBridge.plugin||'';if(!plugin){window.showToast&&window.showToast('Chọn VST FX trước khi Load Carla','warning');return;}window.SonicAPI.openInCarla(plugin,ozState.carlaBridge.plugin_path).then(r=>{if(r&&r.success){setOzState(prev=>({...prev,carlaBridge:{...(prev.carlaBridge||{}),connected:true}}));window.showToast&&window.showToast('Đã mở Carla với '+plugin+' (VST FX) — chỉnh âm thanh trong Carla','success');}else{window.showToast&&window.showToast('Không mở được Carla','error');}}).catch(err=>window.showToast&&window.showToast('Lỗi mở Carla: '+(err.message||err),'error'));},className:"px-4 py-1.5 bg-teal-700 hover:bg-teal-600 text-white text-xs font-semibold rounded transition flex items-center gap-1 shrink-0"},/*#__PURE__*/React.createElement("i",{"data-lucide":"play",className:"w-3 h-3"})," Load Carla Bridge"),/*#__PURE__*/React.createElement("button",{onClick:()=>{if(window.SonicCarlaMidi&&window.SonicCarlaMidi.stopBridge)window.SonicCarlaMidi.stopBridge();setOzState(prev=>({...prev,carlaBridge:{...(prev.carlaBridge||{}),connected:false}}));window.showToast&&window.showToast('Đã ngắt kết nối Carla Bridge','info');},className:"px-4 py-1.5 bg-zinc-800 hover:bg-zinc-700 text-red-300 text-xs font-semibold rounded transition flex items-center gap-1 shrink-0"},/*#__PURE__*/React.createElement("i",{"data-lucide":"square",className:"w-3 h-3"})," Stop / Unload")),/*#__PURE__*/React.createElement("div",{className:"text-[10px] text-slate-500 oz-font-mono leading-relaxed"},"M\u1EDF Carla v\u1EDBi VST FX \u0111\u1EC3 ch\u1EC9nh s\u1EEDa \xE2m thanh master (native GUI). Module n\xE0y l\xE0 pass-through trong master chain (kh\xF4ng th\xEAm DSP WebAudio).")))),/*#__PURE__*/React.createElement("div",{className:"border-t border-slate-800 bg-slate-900/60 p-3 flex flex-col shrink-0"},/*#__PURE__*/React.createElement("div",{className:"flex items-center justify-between pb-1.5 border-b border-slate-800/80 mb-2"},/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-2"},/*#__PURE__*/React.createElement("span",{className:"text-[11px] font-bold text-white uppercase tracking-wider"},"Wave Observer"),/*#__PURE__*/React.createElement("span",{className:"text-[9px] bg-cyan-950 text-cyan-400 border border-cyan-800/60 px-1.5 py-0.2 rounded font-mono"},"Real-time Oscilloscope")),/*#__PURE__*/React.createElement("div",{className:"flex gap-2"},['Scope','Settings','Help','About'].map(tab=>/*#__PURE__*/React.createElement("button",{key:tab,className:`px-2 py-0.5 rounded text-[10px] font-bold ${tab==='Scope'?'bg-cyan-950 text-cyan-300 border border-cyan-800/60':'text-slate-400 hover:text-slate-200'}`},tab)))),/*#__PURE__*/React.createElement("div",{className:"relative w-full h-32 bg-[#090d16] border border-slate-800/80 rounded-lg overflow-hidden mb-2"},/*#__PURE__*/React.createElement("canvas",{ref:woCanvasRef,className:"w-full h-full block"})),/*#__PURE__*/React.createElement("div",{className:"flex items-center justify-between gap-4 text-[10px] oz-font-mono text-slate-400 mt-1 select-none"},/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-2 bg-slate-950 px-2.5 py-1.5 rounded border border-slate-800/80 shrink-0"},/*#__PURE__*/React.createElement("span",{className:"text-[10px] font-bold text-slate-300"},"Input"),/*#__PURE__*/React.createElement("div",{className:"flex flex-col gap-1 w-20"},/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-1"},/*#__PURE__*/React.createElement("span",{className:"text-[8px] text-slate-500 w-2"},"L"),/*#__PURE__*/React.createElement("div",{className:"w-16 h-1.5 bg-slate-900 rounded-sm overflow-hidden flex items-center"},/*#__PURE__*/React.createElement("div",{ref:woLeftMeterRef,className:"h-full bg-cyan-400 transition-all duration-75",style:{width:'0%'}}))),/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-1"},/*#__PURE__*/React.createElement("span",{className:"text-[8px] text-slate-500 w-2"},"R"),/*#__PURE__*/React.createElement("div",{className:"w-16 h-1.5 bg-slate-900 rounded-sm overflow-hidden flex items-center"},/*#__PURE__*/React.createElement("div",{ref:woRightMeterRef,className:"h-full bg-teal-500 transition-all duration-75",style:{width:'0%'}}))))),/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-4 bg-slate-950 px-3 py-1 rounded border border-slate-800/80 flex-1 justify-around"},/*#__PURE__*/React.createElement("span",{className:"font-bold text-slate-300 uppercase tracking-widest text-[9px]"},"Scope"),/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-1.5"},/*#__PURE__*/React.createElement("span",null,"Channel"),/*#__PURE__*/React.createElement("select",{value:woChannel,onChange:e=>setWoChannel(e.target.value),className:"bg-slate-900 border border-slate-700 rounded px-1.5 py-0.5 text-[9px] text-slate-300 outline-none"},/*#__PURE__*/React.createElement("option",{value:"stereo"},"Stereo"),/*#__PURE__*/React.createElement("option",{value:"left"},"Left Only"),/*#__PURE__*/React.createElement("option",{value:"right"},"Right Only"))),/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-1.5"},/*#__PURE__*/React.createElement("span",null,"Mode"),/*#__PURE__*/React.createElement("select",{value:woMode,onChange:e=>setWoMode(e.target.value),className:"bg-slate-900 border border-slate-700 rounded px-1.5 py-0.5 text-[9px] text-slate-300 outline-none"},/*#__PURE__*/React.createElement("option",{value:"waveform"},"Waveform"),/*#__PURE__*/React.createElement("option",{value:"envelope"},"Envelope"))),/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-1.5"},/*#__PURE__*/React.createElement("span",null,"Duration:"),/*#__PURE__*/React.createElement("span",{className:"text-cyan-400 font-bold w-10 text-right"},woDuration.toFixed(3),"s"),/*#__PURE__*/React.createElement("input",{type:"range",min:"0.5",max:"5.0",step:"0.1",value:woDuration,onChange:e=>setWoDuration(parseFloat(e.target.value)),className:"w-16 h-1 cursor-pointer accent-cyan-400"})),/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-1.5"},/*#__PURE__*/React.createElement("span",null,"V.Zoom:"),/*#__PURE__*/React.createElement("span",{className:"text-cyan-400 font-bold w-12 text-right"},woZoom.toFixed(1)," dB"),/*#__PURE__*/React.createElement("input",{type:"range",min:"-12",max:"24",step:"0.5",value:woZoom,onChange:e=>setWoZoom(parseFloat(e.target.value)),className:"w-16 h-1 cursor-pointer accent-cyan-400"}))),/*#__PURE__*/React.createElement("button",{onClick:()=>setWoPaused(!woPaused),className:`px-3 py-1 rounded font-bold border transition shadow-md shrink-0 flex items-center gap-1 ${woPaused?'bg-amber-800 hover:bg-amber-700 border-amber-500 text-amber-100':'bg-slate-800 hover:bg-slate-700 border-slate-700 text-slate-200'}`},woPaused?'Resume':'Pause')))),/*#__PURE__*/React.createElement("div",{className:"w-64 bg-slate-900 border-l border-slate-800/80 p-3 flex flex-col justify-between shadow-2xl oz-font-mono text-xs z-20 shrink-0"},/*#__PURE__*/React.createElement("div",{className:"flex items-center justify-between border-b border-slate-800 pb-2 mb-2"},/*#__PURE__*/React.createElement("span",{className:"text-xs font-bold text-white tracking-wider uppercase flex items-center gap-1.5"},/*#__PURE__*/React.createElement("i",{"data-lucide":"sliders",className:"w-3 h-3 text-cyan-400"})," I/O METERS"),/*#__PURE__*/React.createElement("span",{className:"text-[9px] bg-cyan-950 border border-cyan-800 text-cyan-300 px-1 py-0.5 rounded"},"TRUE PEAK")),/*#__PURE__*/React.createElement("div",{className:"grid grid-cols-2 gap-2 text-[10px] text-center mb-2"},/*#__PURE__*/React.createElement("div",{className:"bg-slate-950 border border-slate-800/80 p-1.5 rounded"},/*#__PURE__*/React.createElement("div",{className:"text-slate-500 font-bold"},"IN PEAK"),/*#__PURE__*/React.createElement("div",{id:"inPeakText",className:"text-cyan-400 font-bold oz-font-mono"},"-inf dB")),/*#__PURE__*/React.createElement("div",{className:"bg-slate-950 border border-slate-800/80 p-1.5 rounded"},/*#__PURE__*/React.createElement("div",{className:"text-slate-500 font-bold"},"OUT PEAK"),/*#__PURE__*/React.createElement("div",{id:"outPeakText",className:"text-emerald-400 font-bold oz-font-mono"},"-inf dB"))),/*#__PURE__*/React.createElement("div",{className:"grid grid-cols-2 gap-2 text-[10px] text-center mb-2"},/*#__PURE__*/React.createElement("div",{className:"bg-slate-950 border border-slate-800/80 p-1.5 rounded"},/*#__PURE__*/React.createElement("div",{className:"text-slate-500 font-bold"},"OUT RMS"),/*#__PURE__*/React.createElement("div",{id:"outRmsText",className:"text-sky-300 font-bold oz-font-mono"},"-inf")),/*#__PURE__*/React.createElement("div",{className:"bg-slate-950 border border-slate-800/80 p-1.5 rounded"},/*#__PURE__*/React.createElement("div",{className:"text-slate-500 font-bold"},"LOUDNESS"),/*#__PURE__*/React.createElement("div",{id:"lufsText",className:"text-fuchsia-300 font-bold oz-font-mono"},"--.-"))),/*#__PURE__*/React.createElement("div",{className:"flex-1 bg-slate-950 border border-slate-800/90 rounded-xl p-2 flex justify-around relative overflow-hidden my-1"},/*#__PURE__*/React.createElement("div",{className:"flex flex-col items-center h-full"},/*#__PURE__*/React.createElement("span",{className:"text-[9px] text-slate-500 mb-1"},"IN"),/*#__PURE__*/React.createElement("div",{className:"flex-1 w-5 bg-slate-900 border border-slate-800 rounded relative overflow-hidden flex items-end"},/*#__PURE__*/React.createElement("canvas",{ref:inMeterCanvasRef,className:"w-full h-full block"}))),/*#__PURE__*/React.createElement("div",{className:"flex flex-col items-center h-full"},/*#__PURE__*/React.createElement("span",{className:"text-[9px] text-slate-500 mb-1"},"OUT"),/*#__PURE__*/React.createElement("div",{className:"flex-1 w-6 bg-slate-900 border border-slate-800 rounded relative overflow-hidden flex items-end"},/*#__PURE__*/React.createElement("canvas",{ref:outMeterCanvasRef,className:"w-full h-full block"})))),/*#__PURE__*/React.createElement("div",{className:"grid grid-cols-2 gap-1.5 mt-2"},/*#__PURE__*/React.createElement("button",{onClick:()=>setOzState(prev=>({...prev,isBypassed:!prev.isBypassed})),className:`py-1.5 rounded text-[10px] font-bold border transition-colors ${ozState.isBypassed?'bg-cyan-600 border-cyan-500 text-white':'bg-slate-800 hover:bg-slate-700 text-slate-200 border-slate-700'}`},"Bypass"),/*#__PURE__*/React.createElement("button",{className:"bg-slate-800 hover:bg-slate-700 text-slate-200 py-1.5 rounded text-[10px] font-bold border border-slate-700 transition-colors"},"Gain Match"),/*#__PURE__*/React.createElement("button",{className:"bg-slate-800 hover:bg-slate-700 text-slate-300 py-1.5 rounded text-[10px] border border-slate-700"},"Codec"),/*#__PURE__*/React.createElement("button",{className:"bg-slate-800 hover:bg-slate-700 text-slate-300 py-1.5 rounded text-[10px] border border-slate-700"},"Dither"))))),addModuleOpen&&/*#__PURE__*/React.createElement("div",{className:"fixed inset-0 z-[110] bg-black/80 backdrop-blur-sm flex items-center justify-center p-4",onClick:()=>setAddModuleOpen(false)},/*#__PURE__*/React.createElement("div",{className:"w-full max-w-lg bg-slate-900 border border-slate-700 rounded-2xl p-5 space-y-4 shadow-2xl max-h-[80vh] overflow-y-auto",onClick:e=>e.stopPropagation()},/*#__PURE__*/React.createElement("div",{className:"flex items-center justify-between border-b border-slate-800 pb-3"},/*#__PURE__*/React.createElement("h3",{className:"text-xs font-bold text-white uppercase tracking-wider oz-font-mono"},"TH\xCAM MODULE V\xC0O MASTERING CHAIN"),/*#__PURE__*/React.createElement("button",{onClick:()=>setAddModuleOpen(false),className:"text-slate-400 hover:text-white"},/*#__PURE__*/React.createElement("i",{"data-lucide":"x",className:"w-4 h-4"}))),/*#__PURE__*/React.createElement("div",{className:"grid grid-cols-2 gap-3 text-xs"},[{t:'eqpro',c:'text-teal-400',i:'chart-area',d:'Parametric / Graphic EQ PRO — Pro-Q style, 8 bands, interactive canvas + spectrum.'},{t:'compressor',c:'text-amber-400',i:'compress',d:'Nén dynamic range, glue & punch cho master.'},{t:'limiter',c:'text-rose-400',i:'shield-half',d:'True-Peak ceiling (ratio 20:1, knee 0) chống clipping.'},{t:'exciter',c:'text-purple-400',i:'wand-2',d:'Saturation hài cho warmth và top-end brilliance.'},{t:'rebalance',c:'text-sky-400',i:'sliders-horizontal',d:'Cân bằng Mid/Side (Vocal/Bass vs stereo width).'},{t:'eq',c:'text-cyan-400',i:'activity',d:'EQ 4-band (lowshelf, 2× peaking, highshelf) + presets.'},{t:'imager',c:'text-fuchsia-400',i:'radio',d:'Stereo width 4-band M/S + vectorscope/correlation.'},{t:'maximizer',c:'text-emerald-400',i:'gauge',d:'Maximizer: boost, soft clip, upward comp, ceiling.'}].map(m=>/*#__PURE__*/React.createElement("button",{key:m.t,onClick:()=>addModuleToChain(m.t),className:"p-3 bg-slate-950 hover:bg-slate-800 border border-slate-800 rounded-xl text-left space-y-1 transition-colors"},/*#__PURE__*/React.createElement("div",{className:`font-bold ${m.c} flex items-center gap-1.5`},/*#__PURE__*/React.createElement("i",{"data-lucide":m.i,className:"w-3.5 h-3.5"})," ",MODULE_META[m.t].name),/*#__PURE__*/React.createElement("div",{className:"text-[10px] text-slate-400"},m.d)))),/*#__PURE__*/React.createElement("div",{className:"border-t border-slate-800 pt-3"},/*#__PURE__*/React.createElement("div",{className:"text-[10px] font-bold text-teal-400 uppercase tracking-wider oz-font-mono mb-2"},"VST FX (Ozone...)"),/*#__PURE__*/React.createElement("button",{onClick:()=>setMasterVstPickerOpen(true),className:"w-full bg-slate-950 hover:bg-slate-800 border border-dashed border-slate-700 hover:border-teal-600 text-teal-300 rounded px-2 py-2 text-[11px] text-left transition-colors",title:"Plugin Picker theo category (EQ / Reverb / Comp / Utility)"},/*#__PURE__*/React.createElement("i",{"data-lucide":"plug",className:"w-3 h-3 inline-block mr-1"})," Ch\u1ECDn VST FX t\u1EEB th\u01B0 vi\u1EC7n \u2014 Picker: EQ / Reverb / Comp / Utility")))),/*#__PURE__*/React.createElement(VstPluginPicker,{open:masterVstPickerOpen,onClose:()=>setMasterVstPickerOpen(false),plugins:masterFxList,onPick:f=>addVstToChain(f.path)}));};// ── Media Explorer Panel (from md/51_MEDIA_EXPLORER.md) ── const MEDIA_LIBRARY_SAMPLES=[{name:"MIDI_Loop_01.mid",events:95,lengthQn:16,time:"0:08.000",tpqn:480,bpm:130,kind:"midi"},{name:"MIDI_Loop_02_Bass.mid",events:48,lengthQn:16,time:"0:08.000",tpqn:480,bpm:130,kind:"midi"},{name:"MIDI_Loop_03_Lead.mid",events:110,lengthQn:16,time:"0:08.000",tpqn:480,bpm:130,kind:"midi"},{name:"MIDI_Loop_04.mid",events:76,lengthQn:16,time:"0:08.000",tpqn:480,bpm:130,kind:"midi"},{name:"MIDI_Loop_05_Bass.mid",events:52,lengthQn:16,time:"0:08.000",tpqn:480,bpm:130,kind:"midi"},{name:"MIDI_Loop_06.mid",events:88,lengthQn:16,time:"0:08.000",tpqn:480,bpm:130,kind:"midi"}];const MediaExplorerPanel=({height,clipboardRef,active})=>{const[userFiles,setUserFiles]=React.useState([]);const[folder,setFolder]=React.useState('library');const[selected,setSelected]=React.useState(null);const[filterText,setFilterText]=React.useState('');const[viewMode,setViewMode]=React.useState('details');const[isPlaying,setIsPlaying]=React.useState(false);const[isPaused,setIsPaused]=React.useState(false);const[isLooping,setIsLooping]=React.useState(false);const[autoPlay,setAutoPlay]=React.useState(true);const[pitch,setPitch]=React.useState(0.0);const[rate,setRate]=React.useState(1.0);const[volumeDb,setVolumeDb]=React.useState(0.0);const[currentTime,setCurrentTime]=React.useState(0);const[peaks,setPeaks]=React.useState(null);const[audioBuffer,setAudioBuffer]=React.useState(null);const[audioDuration,setAudioDuration]=React.useState(0);const[midiNotes,setMidiNotes]=React.useState(null);const[midiTotal,setMidiTotal]=React.useState(4);const[midiBars,setMidiBars]=React.useState(1);const[midiTotalBeats,setMidiTotalBeats]=React.useState(16);const[midiFileBpm,setMidiFileBpm]=React.useState(120);const[tempo,setTempo]=React.useState(function(){var saved=localStorage.getItem('studio_media_explorer_tempo');return saved?parseInt(saved):120;});const[tempoText,setTempoText]=React.useState(String(function(){var saved=localStorage.getItem('studio_media_explorer_tempo');return saved?parseInt(saved):120;}()));const[zoom,setZoom]=React.useState(1.0);// Đồng bộ zoom ra window ref — serializeProjectToSchema lưu vào metadata.zoom // (module-level không truy cập state) → restore/open khôi phục đúng zoom. React.useEffect(()=>{window.__currentZoom=zoom;},[zoom]);const[scrollOffset,setScrollOffset]=React.useState(0);const scrollOffsetRef=React.useRef(0);scrollOffsetRef.current=scrollOffset;const[selStart,setSelStart]=React.useState(null);const[selEnd,setSelEnd]=React.useState(null);const[isDragging,setIsDragging]=React.useState(false);const[previewCtxMenu,setPreviewCtxMenu]=React.useState(null);// { x, y } @@ -938,10 +938,10 @@ isLoopingRef.current=next;const cur=selectedRef.current;const st=playStateRef.cu // the loop points to the current selection so it loops continuously // over the selected region until Stop is pressed. if(next&&st.source.buffer){if(hasSelection){st.source.loopStart=Math.min(sStart,sEnd);st.source.loopEnd=Math.max(sStart,sEnd);}else{st.source.loopStart=0;st.source.loopEnd=st.source.buffer.duration;}}}if(next&&isMidiFile(cur)){// Re-schedule loop for the currently previewing MIDI file -if(cur&&(cur.handle||cur.path||cur.file_id||cur.fileId||cur.kind==='midi'&&cur.name)){selectTokenRef.current++;const token=selectTokenRef.current;try{if(window.SonicMidiRouter)window.SonicMidiRouter.panic();else if(window.SonicSF&&typeof window.SonicSF.stopAll==='function')window.SonicSF.stopAll();}catch(e){}stopAllNativeSfNotes();stopAllNativeVstNotes();playMidiPreview(cur,token);}}return next;});};const toggleRate=dir=>setRate(prev=>Math.max(0.25,Math.min(4.0,Math.round((prev+dir*0.1)*100)/100)));const togglePitch=dir=>setPitch(prev=>Math.max(-24,Math.min(24,prev+dir*0.5)));const selIsMidi=isMidiFile(selected);const selDur=fileDuration(selected);const selBpm=selected&&selected.bpm||tempo;return/*#__PURE__*/React.createElement("div",{ref:containerRef,className:"flex flex-col w-full h-full text-slate-900 overflow-hidden select-none",style:{fontFamily:"'Inter', sans-serif"}},/*#__PURE__*/React.createElement("div",{className:"h-8 bg-[#d4d0c8] border-b border-[#808080] px-2 flex items-center justify-between text-xs shrink-0"},/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-1 flex-1 mr-2"},/*#__PURE__*/React.createElement("button",{className:"w-5 h-5 bg-[#e0e0e0] hover:bg-white border border-[#707070] rounded-sm flex items-center justify-center text-[10px]",title:"Back",onClick:()=>folder==='computer'?goComputerParent():setFolder('library')},/*#__PURE__*/React.createElement("i",{className:"fa-solid fa-arrow-left"})),/*#__PURE__*/React.createElement("button",{className:"w-5 h-5 bg-[#e0e0e0] hover:bg-white border border-[#707070] rounded-sm flex items-center justify-center text-[10px]",title:"Forward",onClick:()=>setFolder('uploads')},/*#__PURE__*/React.createElement("i",{className:"fa-solid fa-arrow-right"})),/*#__PURE__*/React.createElement("button",{className:"w-5 h-5 bg-[#e0e0e0] hover:bg-white border border-[#707070] rounded-sm flex items-center justify-center text-[10px]",title:"Up Directory",onClick:()=>folder==='computer'?goComputerParent():setFolder('library')},/*#__PURE__*/React.createElement("i",{className:"fa-solid fa-arrow-up"})),/*#__PURE__*/React.createElement("button",{className:"w-5 h-5 bg-[#e0e0e0] hover:bg-white border border-[#707070] rounded-sm flex items-center justify-center text-[10px]",title:"Refresh",onClick:()=>{if(folder==='computer'){const node=computerTree[computerPath];if(node&&node.handle)browseComputerDir({name:computerPath.split('/').pop()||computerPath,path:computerPath,is_dir:true,handle:node.handle});else if(computerPath)browseComputerDir({name:computerPath.split(/[\\/]/).pop()||computerPath,path:computerPath,is_dir:true});}else if(window.SonicAPI&&window.SonicAPI.listMyFiles)window.SonicAPI.listMyFiles([]).then(d=>setUserFiles(d||[]));}},/*#__PURE__*/React.createElement("i",{className:"fa-solid fa-rotate-right"})),/*#__PURE__*/React.createElement("div",{className:"flex-1 flex items-center bg-white border border-[#808080] h-5 px-1"},/*#__PURE__*/React.createElement("i",{className:"fa-solid fa-folder-open text-[#d9a752] text-[11px] mr-1.5"}),/*#__PURE__*/React.createElement("span",{className:"flex-1 text-xs text-slate-800 truncate"},folder==='computer'?computerPath||'My Computer':folder==='library'?'Media Library':folder==='uploads'?'Uploads':'Processed'),/*#__PURE__*/React.createElement("i",{className:"fa-solid fa-caret-down text-slate-600 text-[10px] ml-1"}))),/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-2"},/*#__PURE__*/React.createElement("div",{className:"flex items-center bg-white border border-[#808080] h-5 px-1 w-40"},/*#__PURE__*/React.createElement("input",{type:"text",placeholder:"Filter/Search...",value:filterText,onChange:e=>setFilterText(e.target.value),className:"w-full text-xs outline-none bg-transparent font-sans text-slate-800"}),/*#__PURE__*/React.createElement("i",{className:"fa-solid fa-caret-down text-slate-600 text-[10px] ml-1"})),/*#__PURE__*/React.createElement("button",{className:"px-2 py-0.5 bg-[#e0e0e0] hover:bg-white border border-[#707070] rounded-sm text-[11px] flex items-center gap-1 font-semibold",onClick:()=>setViewMode(viewMode==='details'?'list':'details')},/*#__PURE__*/React.createElement("span",null,viewMode==='details'?'Details':'List')," ",/*#__PURE__*/React.createElement("i",{className:"fa-solid fa-caret-down text-[9px]"})))),/*#__PURE__*/React.createElement("div",{className:"flex-1 flex overflow-hidden min-h-0 relative"},/*#__PURE__*/React.createElement("div",{className:"flex bg-white border border-[#808080] m-1 mr-0 text-xs select-none shrink-0 overflow-hidden"},/*#__PURE__*/React.createElement("div",{ref:treePaneRef,className:"overflow-y-auto p-1",style:{width:treeWidth+'px',minWidth:treeWidth+'px',maxWidth:treeWidth+'px'}},/*#__PURE__*/React.createElement("div",{className:"space-y-0.5 font-sans"},/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-1 px-1 py-0.5 text-slate-700"},/*#__PURE__*/React.createElement("span",{className:"w-3"})," "),/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-1 px-1 py-0.5 text-slate-700"},/*#__PURE__*/React.createElement("span",{className:"w-3"})," "),/*#__PURE__*/React.createElement("div",{className:`flex items-center gap-1 px-1 py-0.5 cursor-pointer rounded-sm font-bold ${folder==='computer'&&computerPath==='favorited'?'bg-slate-300 text-slate-900':'hover:bg-slate-200 text-slate-800'}`,onClick:()=>{openFavorited();setFavoritedExpanded(!favoritedExpanded);}},/*#__PURE__*/React.createElement("i",{className:"fa-solid fa-star text-amber-500 text-[11px] font-bold"})," Favorited"),favoritedExpanded&&/*#__PURE__*/React.createElement("div",{className:"pl-3 space-y-0.5"},favorites.map((fav,fi)=>/*#__PURE__*/React.createElement("div",{key:fav.path+fi,"data-tree-path":fav.path,className:"flex items-center gap-1 px-1 py-0.5 cursor-pointer rounded-sm hover:bg-amber-100 text-slate-800",style:{paddingLeft:20},onClick:()=>openFavorite(fav),onContextMenu:e=>{e.preventDefault();e.stopPropagation();setFavContext(Object.assign({},fav,{x:e.clientX,y:e.clientY}));}},/*#__PURE__*/React.createElement("i",{className:"fa-solid fa-folder text-[#d9a752] shrink-0"}),/*#__PURE__*/React.createElement("span",{className:"truncate flex-1"},fav.name),/*#__PURE__*/React.createElement("i",{className:"fa-solid fa-star text-amber-500 text-[9px] shrink-0"}))),favorites.length===0&&/*#__PURE__*/React.createElement("div",{className:"pl-4 py-0.5 text-slate-400 italic text-[11px]"},"No favorites")),/*#__PURE__*/React.createElement("div",{className:`flex items-center gap-1 px-1 py-0.5 cursor-pointer rounded-sm ${folder==='computer'&&computerPath!=='favorited'?'bg-slate-300 text-slate-900 font-semibold':'hover:bg-slate-200 text-slate-800'}`,onClick:openMyComputer},/*#__PURE__*/React.createElement("i",{className:"fa-solid fa-computer text-[11px] text-slate-600"})," My Computer"),folder==='computer'&&computerPath!=='favorited'&&computerRoots&&/*#__PURE__*/React.createElement("div",{className:"pl-3 space-y-0.5"},computerRoots.map(root=>renderComputerNode(root,0,true))),/*#__PURE__*/React.createElement("div",{className:`flex items-center gap-1 px-1 py-0.5 cursor-pointer font-semibold rounded-sm ${folder==='library'?'bg-slate-300 text-slate-900':'hover:bg-slate-200 text-slate-800'}`,onClick:()=>setFolder('library')},/*#__PURE__*/React.createElement("i",{className:"fa-solid fa-folder-open text-[#d9a752]"})," Media Library"),/*#__PURE__*/React.createElement("div",{className:"pl-3 space-y-0.5"},/*#__PURE__*/React.createElement("div",{className:`flex items-center gap-1 px-1 py-0.5 cursor-pointer pl-4 rounded-sm ${folder==='uploads'?'bg-slate-300 text-slate-900':'hover:bg-slate-200 text-slate-800'}`,onClick:()=>setFolder('uploads')},/*#__PURE__*/React.createElement("i",{className:"fa-solid fa-folder text-[#d9a752]"})," Uploads"),/*#__PURE__*/React.createElement("div",{className:`flex items-center gap-1 px-1 py-0.5 cursor-pointer pl-4 rounded-sm ${folder==='processed'?'bg-slate-300 text-slate-900':'hover:bg-slate-200 text-slate-800'}`,onClick:()=>setFolder('processed')},/*#__PURE__*/React.createElement("i",{className:"fa-solid fa-folder text-[#d9a752]"})," Processed")))),/*#__PURE__*/React.createElement("div",{className:"w-1.5 cursor-ew-resize hover:bg-blue-500/40 active:bg-blue-500/60 transition-colors shrink-0",onMouseDown:startTreeResize,title:"Kéo để thay đổi chiều rộng"})),favContext&&/*#__PURE__*/React.createElement("div",{className:"fixed z-[100] bg-white border border-[#808080] shadow-lg rounded-sm text-xs font-sans text-slate-800 min-w-[180px]",style:{left:favContext.x,top:favContext.y},onMouseLeave:()=>setFavContext(null)},/*#__PURE__*/React.createElement("div",{className:`px-2 py-1 cursor-pointer hover:bg-blue-100 flex items-center gap-1.5 ${isFavorite(favContext)?'text-amber-700':''}`,onClick:()=>{toggleFavorite(favContext);setFavContext(null);}},/*#__PURE__*/React.createElement("i",{className:`fa-solid ${isFavorite(favContext)?'fa-star text-amber-500':'fa-star text-slate-400'} text-[11px]`}),isFavorite(favContext)?'Gỡ khỏi Favorited':'Thêm vào Favorited'),/*#__PURE__*/React.createElement("div",{className:"px-2 py-1 cursor-pointer hover:bg-blue-100 flex items-center gap-1.5",onClick:()=>{if(favContext)browseComputerDir(favContext);setFavContext(null);}},/*#__PURE__*/React.createElement("i",{className:"fa-solid fa-folder-open text-[#d9a752] text-[11px]"})," Mở thư mục")),/*#__PURE__*/React.createElement("div",{className:"flex-1 bg-white border border-[#808080] m-1 overflow-y-auto relative"},/*#__PURE__*/React.createElement("table",{className:"w-full text-xs text-left border-collapse",style:{tableLayout:'fixed'}},/*#__PURE__*/React.createElement("thead",{className:"sticky top-0 bg-[#e0e0e0] border-b border-[#a0a0a0] text-slate-800 font-semibold select-none shadow-sm z-10"},/*#__PURE__*/React.createElement("tr",null,/*#__PURE__*/React.createElement("th",{className:"py-1 px-2 border-r border-[#b0b0b0] relative",style:viewMode==='details'?{width:colWidths.file}:undefined},/*#__PURE__*/React.createElement("div",{className:"truncate pr-2"},"File"),/*#__PURE__*/React.createElement("div",{className:"absolute right-0 top-0 bottom-0 w-1.5 cursor-col-resize hover:bg-slate-400",onMouseDown:e=>startColResize('file',e)})),viewMode==='details'&&/*#__PURE__*/React.createElement(React.Fragment,null,/*#__PURE__*/React.createElement("th",{className:"py-1 px-2 border-r border-[#b0b0b0] relative",style:{width:colWidths.size}},/*#__PURE__*/React.createElement("div",{className:"truncate pr-2"},"Size"),/*#__PURE__*/React.createElement("div",{className:"absolute right-0 top-0 bottom-0 w-1.5 cursor-col-resize hover:bg-slate-400",onMouseDown:e=>startColResize('size',e)})),/*#__PURE__*/React.createElement("th",{className:"py-1 px-2 border-r border-[#b0b0b0] relative",style:{width:colWidths.type}},/*#__PURE__*/React.createElement("div",{className:"truncate pr-2"},"Type"),/*#__PURE__*/React.createElement("div",{className:"absolute right-0 top-0 bottom-0 w-1.5 cursor-col-resize hover:bg-slate-400",onMouseDown:e=>startColResize('type',e)}))))),/*#__PURE__*/React.createElement("tbody",{className:"font-sans text-slate-800"},visibleFiles.map((f,i)=>{const isSel=selected&&(selected.name||selected.file_id)===(f.name||f.file_id);const isMidi=isMidiFile(f);const icon=f.is_dir?'fa-folder text-[#d9a752]':isMidi?'fa-music text-purple-600':f.kind==='audio'?'fa-file-audio text-emerald-600':'fa-file text-zinc-500';return/*#__PURE__*/React.createElement("tr",{key:(f.path||f.file_id||f.name)+i,draggable:!f.is_dir,onDragStart:e=>{if(f.is_dir){e.preventDefault();return;}e.dataTransfer.setData('text/plain',f.name||f.original_name||'');e.dataTransfer.effectAllowed='copy';window.__mediaExplorerDragFile=f;},onDragEnd:()=>{window.__mediaExplorerDragFile=null;},className:`cursor-pointer hover:bg-blue-100 ${isSel?'file-row-selected':''}`,onClick:()=>f.is_dir?browseComputerDir(f):handleSelect(f),onDoubleClick:()=>f.is_dir&&browseComputerDir(f),onContextMenu:e=>{if(f.is_dir){e.preventDefault();e.stopPropagation();setFavContext(Object.assign({},f,{x:e.clientX,y:e.clientY}));}}},/*#__PURE__*/React.createElement("td",{className:"py-1 px-2 truncate"},/*#__PURE__*/React.createElement("i",{className:`fa-solid ${icon} mr-2`}),f.name||f.original_name),viewMode==='details'&&/*#__PURE__*/React.createElement("td",{className:"py-1 px-2 truncate"},f.size_mb!=null?f.size_mb.toFixed(2)+' MB':isMidi?(f.tpqn||'MIDI')+' TPQN':'-'),viewMode==='details'&&/*#__PURE__*/React.createElement("td",{className:"py-1 px-2 truncate"},f.is_dir?'Folder':isMidi?'MIDI':f.kind==='audio'?'Audio':'File'));}),visibleFiles.length===0&&/*#__PURE__*/React.createElement("tr",null,/*#__PURE__*/React.createElement("td",{className:"py-3 px-2 text-slate-400 italic",colSpan:viewMode==='details'?3:1},"No files")))))),/*#__PURE__*/React.createElement("div",{className:"h-[38%] min-h-[110px] bg-[#d4d0c8] border-t border-[#808080] p-1.5 flex flex-col justify-between text-xs shrink-0 select-none"},/*#__PURE__*/React.createElement("div",{className:"flex items-center justify-between gap-2"},/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-1"},/*#__PURE__*/React.createElement("button",{id:"btnStop",className:"w-6 h-6 bg-[#e0e0e0] hover:bg-white border border-[#707070] rounded-sm flex items-center justify-center text-slate-800",title:"Stop",onClick:stopMediaPlayback},/*#__PURE__*/React.createElement("i",{className:"fa-solid fa-square text-[10px]"})),/*#__PURE__*/React.createElement("button",{id:"btnPlay",className:"w-6 h-6 bg-[#e0e0e0] hover:bg-white border border-[#707070] rounded-sm flex items-center justify-center text-emerald-700 font-bold",title:"Play",onClick:()=>isPlaying?togglePause():playSelected(selected)},/*#__PURE__*/React.createElement("i",{className:`fa-solid ${isPlaying&&!isPaused?'fa-play':'fa-play'} text-xs`})),/*#__PURE__*/React.createElement("button",{id:"btnPause",className:"w-6 h-6 bg-[#e0e0e0] hover:bg-white border border-[#707070] rounded-sm flex items-center justify-center text-amber-700",title:"Pause",onClick:togglePause},/*#__PURE__*/React.createElement("i",{className:"fa-solid fa-pause text-xs"})),/*#__PURE__*/React.createElement("button",{id:"btnLoop",className:`w-6 h-6 border rounded-sm flex items-center justify-center text-xs ${isLooping?'bg-cyan-600 text-white border-cyan-700':'bg-[#e0e0e0] hover:bg-white text-slate-700 border-[#707070]'}`,title:"Loop / Repeat",onClick:toggleLoop},/*#__PURE__*/React.createElement("i",{className:"fa-solid fa-rotate-right"})),/*#__PURE__*/React.createElement("button",{id:"btnAutoPlay",className:`h-6 px-2 border rounded-sm font-bold text-[10px] flex items-center gap-1 ${autoPlay?'bg-gradient-to-r from-cyan-600 to-emerald-600 text-white border-slate-700':'bg-[#e0e0e0] text-slate-600 border-[#707070]'}`,onClick:()=>setAutoPlay(p=>!p)},/*#__PURE__*/React.createElement("i",{className:"fa-solid fa-bolt text-[9px]"})," ",/*#__PURE__*/React.createElement("span",null,"Auto-Play")),/*#__PURE__*/React.createElement("div",{className:"relative"},/*#__PURE__*/React.createElement("button",{id:"btnSynth",className:`h-6 px-2 border rounded-sm font-bold text-[10px] flex items-center gap-1 ${synthInst?'bg-gradient-to-r from-violet-600 to-fuchsia-600 text-white border-slate-700':'bg-[#e0e0e0] text-slate-600 border-[#707070]'}`,ref:btnSynthRef,title:"Chọn instrument để preview MIDI",onClick:toggleSynthDropdown},/*#__PURE__*/React.createElement("i",{className:"fa-solid fa-music text-[9px]"})," ",/*#__PURE__*/React.createElement("span",null,"Synth",synthInst?': '+(synthInst.name||'?'):''),/*#__PURE__*/React.createElement("i",{className:"fa-solid fa-caret-down text-[8px]"})),synthOpen&&(()=>{// Portal ra document.body: dropdown absolute bottom-8 bị clip bởi +if(cur&&(cur.handle||cur.path||cur.file_id||cur.fileId||cur.kind==='midi'&&cur.name)){selectTokenRef.current++;const token=selectTokenRef.current;try{if(window.SonicMidiRouter)window.SonicMidiRouter.panic();else if(window.SonicSF&&typeof window.SonicSF.stopAll==='function')window.SonicSF.stopAll();}catch(e){}stopAllNativeSfNotes();stopAllNativeVstNotes();playMidiPreview(cur,token);}}return next;});};const toggleRate=dir=>setRate(prev=>Math.max(0.25,Math.min(4.0,Math.round((prev+dir*0.1)*100)/100)));const togglePitch=dir=>setPitch(prev=>Math.max(-24,Math.min(24,prev+dir*0.5)));const selIsMidi=isMidiFile(selected);const selDur=fileDuration(selected);const selBpm=selected&&selected.bpm||tempo;return/*#__PURE__*/React.createElement("div",{ref:containerRef,className:"flex flex-col w-full h-full text-slate-900 overflow-hidden select-none",style:{fontFamily:"'Inter', sans-serif"}},/*#__PURE__*/React.createElement("div",{className:"h-8 bg-[#d4d0c8] border-b border-[#808080] px-2 flex items-center justify-between text-xs shrink-0"},/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-1 flex-1 mr-2"},/*#__PURE__*/React.createElement("button",{className:"w-5 h-5 bg-[#e0e0e0] hover:bg-white border border-[#707070] rounded-sm flex items-center justify-center text-[10px]",title:"Back",onClick:()=>folder==='computer'?goComputerParent():setFolder('library')},/*#__PURE__*/React.createElement("i",{className:"fa-solid fa-arrow-left"})),/*#__PURE__*/React.createElement("button",{className:"w-5 h-5 bg-[#e0e0e0] hover:bg-white border border-[#707070] rounded-sm flex items-center justify-center text-[10px]",title:"Forward",onClick:()=>setFolder('uploads')},/*#__PURE__*/React.createElement("i",{className:"fa-solid fa-arrow-right"})),/*#__PURE__*/React.createElement("button",{className:"w-5 h-5 bg-[#e0e0e0] hover:bg-white border border-[#707070] rounded-sm flex items-center justify-center text-[10px]",title:"Up Directory",onClick:()=>folder==='computer'?goComputerParent():setFolder('library')},/*#__PURE__*/React.createElement("i",{className:"fa-solid fa-arrow-up"})),/*#__PURE__*/React.createElement("button",{className:"w-5 h-5 bg-[#e0e0e0] hover:bg-white border border-[#707070] rounded-sm flex items-center justify-center text-[10px]",title:"Refresh",onClick:()=>{if(folder==='computer'){const node=computerTree[computerPath];if(node&&node.handle)browseComputerDir({name:computerPath.split('/').pop()||computerPath,path:computerPath,is_dir:true,handle:node.handle});else if(computerPath)browseComputerDir({name:computerPath.split(/[\\/]/).pop()||computerPath,path:computerPath,is_dir:true});}else if(window.SonicAPI&&window.SonicAPI.listMyFiles)window.SonicAPI.listMyFiles([]).then(d=>setUserFiles(d||[]));}},/*#__PURE__*/React.createElement("i",{className:"fa-solid fa-rotate-right"})),/*#__PURE__*/React.createElement("div",{className:"flex-1 flex items-center bg-white border border-[#808080] h-5 px-1"},/*#__PURE__*/React.createElement("i",{className:"fa-solid fa-folder-open text-[#d9a752] text-[11px] mr-1.5"}),/*#__PURE__*/React.createElement("span",{className:"flex-1 text-xs text-slate-800 truncate"},folder==='computer'?computerPath||'My Computer':folder==='library'?'Media Library':folder==='uploads'?'Uploads':'Processed'),/*#__PURE__*/React.createElement("i",{className:"fa-solid fa-caret-down text-slate-600 text-[10px] ml-1"}))),/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-2"},/*#__PURE__*/React.createElement("div",{className:"flex items-center bg-white border border-[#808080] h-5 px-1 w-40"},/*#__PURE__*/React.createElement("input",{type:"text",placeholder:"Filter/Search...",value:filterText,onChange:e=>setFilterText(e.target.value),className:"w-full text-xs outline-none bg-transparent font-sans text-slate-800"}),/*#__PURE__*/React.createElement("i",{className:"fa-solid fa-caret-down text-slate-600 text-[10px] ml-1"})),/*#__PURE__*/React.createElement("button",{className:"px-2 py-0.5 bg-[#e0e0e0] hover:bg-white border border-[#707070] rounded-sm text-[11px] flex items-center gap-1 font-semibold",onClick:()=>setViewMode(viewMode==='details'?'list':'details')},/*#__PURE__*/React.createElement("span",null,viewMode==='details'?'Details':'List')," ",/*#__PURE__*/React.createElement("i",{className:"fa-solid fa-caret-down text-[9px]"})))),/*#__PURE__*/React.createElement("div",{className:"flex-1 flex overflow-hidden min-h-0 relative"},/*#__PURE__*/React.createElement("div",{className:"flex bg-white border border-[#808080] m-1 mr-0 text-xs select-none shrink-0 overflow-hidden"},/*#__PURE__*/React.createElement("div",{ref:treePaneRef,className:"overflow-y-auto p-1",style:{width:treeWidth+'px',minWidth:treeWidth+'px',maxWidth:treeWidth+'px'}},/*#__PURE__*/React.createElement("div",{className:"space-y-0.5 font-sans"},/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-1 px-1 py-0.5 text-slate-700"},/*#__PURE__*/React.createElement("span",{className:"w-3"})," "),/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-1 px-1 py-0.5 text-slate-700"},/*#__PURE__*/React.createElement("span",{className:"w-3"})," "),/*#__PURE__*/React.createElement("div",{className:`flex items-center gap-1 px-1 py-0.5 cursor-pointer rounded-sm font-bold ${folder==='computer'&&computerPath==='favorited'?'bg-slate-300 text-slate-900':'hover:bg-slate-200 text-slate-800'}`,onClick:()=>{openFavorited();setFavoritedExpanded(!favoritedExpanded);}},/*#__PURE__*/React.createElement("i",{className:"fa-solid fa-star text-amber-500 text-[11px] font-bold"})," Favorited"),favoritedExpanded&&/*#__PURE__*/React.createElement("div",{className:"pl-3 space-y-0.5"},favorites.map((fav,fi)=>/*#__PURE__*/React.createElement("div",{key:fav.path+fi,"data-tree-path":fav.path,className:"flex items-center gap-1 px-1 py-0.5 cursor-pointer rounded-sm hover:bg-amber-100 text-slate-800",style:{paddingLeft:20},onClick:()=>openFavorite(fav),onContextMenu:e=>{e.preventDefault();e.stopPropagation();setFavContext(Object.assign({},fav,{x:e.clientX,y:e.clientY}));}},/*#__PURE__*/React.createElement("i",{className:"fa-solid fa-folder text-[#d9a752] shrink-0"}),/*#__PURE__*/React.createElement("span",{className:"truncate flex-1"},fav.name),/*#__PURE__*/React.createElement("i",{className:"fa-solid fa-star text-amber-500 text-[9px] shrink-0"}))),favorites.length===0&&/*#__PURE__*/React.createElement("div",{className:"pl-4 py-0.5 text-slate-400 italic text-[11px]"},"No favorites")),/*#__PURE__*/React.createElement("div",{className:`flex items-center gap-1 px-1 py-0.5 cursor-pointer rounded-sm ${folder==='computer'&&computerPath!=='favorited'?'bg-slate-300 text-slate-900 font-semibold':'hover:bg-slate-200 text-slate-800'}`,onClick:openMyComputer},/*#__PURE__*/React.createElement("i",{className:"fa-solid fa-computer text-[11px] text-slate-600"})," My Computer"),folder==='computer'&&computerPath!=='favorited'&&computerRoots&&/*#__PURE__*/React.createElement("div",{className:"pl-3 space-y-0.5"},computerRoots.map(root=>renderComputerNode(root,0,true))),/*#__PURE__*/React.createElement("div",{className:`flex items-center gap-1 px-1 py-0.5 cursor-pointer font-semibold rounded-sm ${folder==='library'?'bg-slate-300 text-slate-900':'hover:bg-slate-200 text-slate-800'}`,onClick:()=>setFolder('library')},/*#__PURE__*/React.createElement("i",{className:"fa-solid fa-folder-open text-[#d9a752]"})," Media Library"),/*#__PURE__*/React.createElement("div",{className:"pl-3 space-y-0.5"},/*#__PURE__*/React.createElement("div",{className:`flex items-center gap-1 px-1 py-0.5 cursor-pointer pl-4 rounded-sm ${folder==='uploads'?'bg-slate-300 text-slate-900':'hover:bg-slate-200 text-slate-800'}`,onClick:()=>setFolder('uploads')},/*#__PURE__*/React.createElement("i",{className:"fa-solid fa-folder text-[#d9a752]"})," Uploads"),/*#__PURE__*/React.createElement("div",{className:`flex items-center gap-1 px-1 py-0.5 cursor-pointer pl-4 rounded-sm ${folder==='processed'?'bg-slate-300 text-slate-900':'hover:bg-slate-200 text-slate-800'}`,onClick:()=>setFolder('processed')},/*#__PURE__*/React.createElement("i",{className:"fa-solid fa-folder text-[#d9a752]"})," Processed")))),/*#__PURE__*/React.createElement("div",{className:"w-1.5 cursor-ew-resize hover:bg-blue-500/40 active:bg-blue-500/60 transition-colors shrink-0",onMouseDown:startTreeResize,title:"K\xE9o \u0111\u1EC3 thay \u0111\u1ED5i chi\u1EC1u r\u1ED9ng"})),favContext&&/*#__PURE__*/React.createElement("div",{className:"fixed z-[100] bg-white border border-[#808080] shadow-lg rounded-sm text-xs font-sans text-slate-800 min-w-[180px]",style:{left:favContext.x,top:favContext.y},onMouseLeave:()=>setFavContext(null)},/*#__PURE__*/React.createElement("div",{className:`px-2 py-1 cursor-pointer hover:bg-blue-100 flex items-center gap-1.5 ${isFavorite(favContext)?'text-amber-700':''}`,onClick:()=>{toggleFavorite(favContext);setFavContext(null);}},/*#__PURE__*/React.createElement("i",{className:`fa-solid ${isFavorite(favContext)?'fa-star text-amber-500':'fa-star text-slate-400'} text-[11px]`}),isFavorite(favContext)?'Gỡ khỏi Favorited':'Thêm vào Favorited'),/*#__PURE__*/React.createElement("div",{className:"px-2 py-1 cursor-pointer hover:bg-blue-100 flex items-center gap-1.5",onClick:()=>{if(favContext)browseComputerDir(favContext);setFavContext(null);}},/*#__PURE__*/React.createElement("i",{className:"fa-solid fa-folder-open text-[#d9a752] text-[11px]"})," M\u1EDF th\u01B0 m\u1EE5c")),/*#__PURE__*/React.createElement("div",{className:"flex-1 bg-white border border-[#808080] m-1 overflow-y-auto relative"},/*#__PURE__*/React.createElement("table",{className:"w-full text-xs text-left border-collapse",style:{tableLayout:'fixed'}},/*#__PURE__*/React.createElement("thead",{className:"sticky top-0 bg-[#e0e0e0] border-b border-[#a0a0a0] text-slate-800 font-semibold select-none shadow-sm z-10"},/*#__PURE__*/React.createElement("tr",null,/*#__PURE__*/React.createElement("th",{className:"py-1 px-2 border-r border-[#b0b0b0] relative",style:viewMode==='details'?{width:colWidths.file}:undefined},/*#__PURE__*/React.createElement("div",{className:"truncate pr-2"},"File"),/*#__PURE__*/React.createElement("div",{className:"absolute right-0 top-0 bottom-0 w-1.5 cursor-col-resize hover:bg-slate-400",onMouseDown:e=>startColResize('file',e)})),viewMode==='details'&&/*#__PURE__*/React.createElement(React.Fragment,null,/*#__PURE__*/React.createElement("th",{className:"py-1 px-2 border-r border-[#b0b0b0] relative",style:{width:colWidths.size}},/*#__PURE__*/React.createElement("div",{className:"truncate pr-2"},"Size"),/*#__PURE__*/React.createElement("div",{className:"absolute right-0 top-0 bottom-0 w-1.5 cursor-col-resize hover:bg-slate-400",onMouseDown:e=>startColResize('size',e)})),/*#__PURE__*/React.createElement("th",{className:"py-1 px-2 border-r border-[#b0b0b0] relative",style:{width:colWidths.type}},/*#__PURE__*/React.createElement("div",{className:"truncate pr-2"},"Type"),/*#__PURE__*/React.createElement("div",{className:"absolute right-0 top-0 bottom-0 w-1.5 cursor-col-resize hover:bg-slate-400",onMouseDown:e=>startColResize('type',e)}))))),/*#__PURE__*/React.createElement("tbody",{className:"font-sans text-slate-800"},visibleFiles.map((f,i)=>{const isSel=selected&&(selected.name||selected.file_id)===(f.name||f.file_id);const isMidi=isMidiFile(f);const icon=f.is_dir?'fa-folder text-[#d9a752]':isMidi?'fa-music text-purple-600':f.kind==='audio'?'fa-file-audio text-emerald-600':'fa-file text-zinc-500';return/*#__PURE__*/React.createElement("tr",{key:(f.path||f.file_id||f.name)+i,draggable:!f.is_dir,onDragStart:e=>{if(f.is_dir){e.preventDefault();return;}e.dataTransfer.setData('text/plain',f.name||f.original_name||'');e.dataTransfer.effectAllowed='copy';window.__mediaExplorerDragFile=f;},onDragEnd:()=>{window.__mediaExplorerDragFile=null;},className:`cursor-pointer hover:bg-blue-100 ${isSel?'file-row-selected':''}`,onClick:()=>f.is_dir?browseComputerDir(f):handleSelect(f),onDoubleClick:()=>f.is_dir&&browseComputerDir(f),onContextMenu:e=>{if(f.is_dir){e.preventDefault();e.stopPropagation();setFavContext(Object.assign({},f,{x:e.clientX,y:e.clientY}));}}},/*#__PURE__*/React.createElement("td",{className:"py-1 px-2 truncate"},/*#__PURE__*/React.createElement("i",{className:`fa-solid ${icon} mr-2`}),f.name||f.original_name),viewMode==='details'&&/*#__PURE__*/React.createElement("td",{className:"py-1 px-2 truncate"},f.size_mb!=null?f.size_mb.toFixed(2)+' MB':isMidi?(f.tpqn||'MIDI')+' TPQN':'-'),viewMode==='details'&&/*#__PURE__*/React.createElement("td",{className:"py-1 px-2 truncate"},f.is_dir?'Folder':isMidi?'MIDI':f.kind==='audio'?'Audio':'File'));}),visibleFiles.length===0&&/*#__PURE__*/React.createElement("tr",null,/*#__PURE__*/React.createElement("td",{className:"py-3 px-2 text-slate-400 italic",colSpan:viewMode==='details'?3:1},"No files")))))),/*#__PURE__*/React.createElement("div",{className:"h-[38%] min-h-[110px] bg-[#d4d0c8] border-t border-[#808080] p-1.5 flex flex-col justify-between text-xs shrink-0 select-none"},/*#__PURE__*/React.createElement("div",{className:"flex items-center justify-between gap-2"},/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-1"},/*#__PURE__*/React.createElement("button",{id:"btnStop",className:"w-6 h-6 bg-[#e0e0e0] hover:bg-white border border-[#707070] rounded-sm flex items-center justify-center text-slate-800",title:"Stop",onClick:stopMediaPlayback},/*#__PURE__*/React.createElement("i",{className:"fa-solid fa-square text-[10px]"})),/*#__PURE__*/React.createElement("button",{id:"btnPlay",className:"w-6 h-6 bg-[#e0e0e0] hover:bg-white border border-[#707070] rounded-sm flex items-center justify-center text-emerald-700 font-bold",title:"Play",onClick:()=>isPlaying?togglePause():playSelected(selected)},/*#__PURE__*/React.createElement("i",{className:`fa-solid ${isPlaying&&!isPaused?'fa-play':'fa-play'} text-xs`})),/*#__PURE__*/React.createElement("button",{id:"btnPause",className:"w-6 h-6 bg-[#e0e0e0] hover:bg-white border border-[#707070] rounded-sm flex items-center justify-center text-amber-700",title:"Pause",onClick:togglePause},/*#__PURE__*/React.createElement("i",{className:"fa-solid fa-pause text-xs"})),/*#__PURE__*/React.createElement("button",{id:"btnLoop",className:`w-6 h-6 border rounded-sm flex items-center justify-center text-xs ${isLooping?'bg-cyan-600 text-white border-cyan-700':'bg-[#e0e0e0] hover:bg-white text-slate-700 border-[#707070]'}`,title:"Loop / Repeat",onClick:toggleLoop},/*#__PURE__*/React.createElement("i",{className:"fa-solid fa-rotate-right"})),/*#__PURE__*/React.createElement("button",{id:"btnAutoPlay",className:`h-6 px-2 border rounded-sm font-bold text-[10px] flex items-center gap-1 ${autoPlay?'bg-gradient-to-r from-cyan-600 to-emerald-600 text-white border-slate-700':'bg-[#e0e0e0] text-slate-600 border-[#707070]'}`,onClick:()=>setAutoPlay(p=>!p)},/*#__PURE__*/React.createElement("i",{className:"fa-solid fa-bolt text-[9px]"})," ",/*#__PURE__*/React.createElement("span",null,"Auto-Play")),/*#__PURE__*/React.createElement("div",{className:"relative"},/*#__PURE__*/React.createElement("button",{id:"btnSynth",className:`h-6 px-2 border rounded-sm font-bold text-[10px] flex items-center gap-1 ${synthInst?'bg-gradient-to-r from-violet-600 to-fuchsia-600 text-white border-slate-700':'bg-[#e0e0e0] text-slate-600 border-[#707070]'}`,ref:btnSynthRef,title:"Ch\u1ECDn instrument \u0111\u1EC3 preview MIDI",onClick:toggleSynthDropdown},/*#__PURE__*/React.createElement("i",{className:"fa-solid fa-music text-[9px]"})," ",/*#__PURE__*/React.createElement("span",null,"Synth",synthInst?': '+(synthInst.name||'?'):''),/*#__PURE__*/React.createElement("i",{className:"fa-solid fa-caret-down text-[8px]"})),synthOpen&&(()=>{// Portal ra document.body: dropdown absolute bottom-8 bị clip bởi // overflow-hidden của root Media Explorer khi list dài (max-h-72 // trồi lên trên) → header + ô tìm kiếm mất khi mở lần 2+. -const btnRect=btnSynthRef.current?btnSynthRef.current.getBoundingClientRect():null;return ReactDOM.createPortal(/*#__PURE__*/React.createElement("div",{className:"fixed z-40 w-64 bg-white border border-[#808080] shadow-xl rounded-sm text-xs text-slate-800 font-sans max-h-72 overflow-y-auto",style:{left:btnRect?btnRect.left:0,bottom:btnRect?window.innerHeight-btnRect.top+8:0}},/*#__PURE__*/React.createElement("div",{className:"sticky top-0 bg-[#e0e0e0] px-2 py-1 font-bold border-b border-[#a0a0a0] flex items-center justify-between z-20"},/*#__PURE__*/React.createElement("span",null,"Select Instrument"),/*#__PURE__*/React.createElement("button",{onClick:()=>setSynthOpen(false),className:"text-slate-500 hover:text-slate-900"},/*#__PURE__*/React.createElement("i",{className:"fa-solid fa-xmark"}))),/*#__PURE__*/React.createElement("div",{className:"sticky top-[23px] bg-white p-1 border-b border-[#c0c0c0] z-20 flex items-center gap-1"},/*#__PURE__*/React.createElement("i",{className:"fa-solid fa-magnifying-glass text-slate-400 pl-1 text-[10px]"}),/*#__PURE__*/React.createElement("input",{type:"text",placeholder:"Tìm nhạc cụ...",value:synthFilter,onChange:e=>setSynthFilter(e.target.value),onClick:e=>e.stopPropagation(),className:"w-full px-1 py-0.5 border border-[#c0c0c0] rounded-sm text-xs font-sans focus:outline-none focus:border-blue-500"}),synthFilter&&/*#__PURE__*/React.createElement("button",{onClick:e=>{e.stopPropagation();setSynthFilter('');},className:"text-slate-400 hover:text-slate-700 px-1"},/*#__PURE__*/React.createElement("i",{className:"fa-solid fa-xmark text-[10px]"}))),synthLoading&&/*#__PURE__*/React.createElement("div",{className:"px-2 py-2 text-slate-400 italic"},"Loading..."),!synthLoading&&(!synthList||synthList.length===0)&&/*#__PURE__*/React.createElement("div",{className:"px-2 py-2 text-slate-400 italic"},"Không có SoundFont nào"),/*#__PURE__*/React.createElement("div",{className:`flex items-center gap-1 px-2 py-1 cursor-pointer hover:bg-blue-100 ${!synthInst?'bg-slate-200':''}`,onClick:()=>selectSynthInst(null)},/*#__PURE__*/React.createElement("i",{className:"fa-solid fa-ban text-slate-400"})," None (mặc định)"),!synthLoading&&filteredSynthList&&filteredSynthList.map(group=>/*#__PURE__*/React.createElement("div",{key:group.sf.id||group.sf.name},/*#__PURE__*/React.createElement("div",{className:"px-2 py-1 bg-[#f0f0f0] font-semibold text-slate-600 border-t border-[#e0e0e0] truncate"},group.sf.display||group.sf.name||group.sf.id),(group.presets||[]).slice(0,200).map((p,pi)=>{const progId=p.id||p.name||'preset_'+pi;return/*#__PURE__*/React.createElement("div",{key:progId,className:`flex items-center gap-1 px-2 pl-4 py-0.5 cursor-pointer hover:bg-blue-100 truncate ${synthInst&&synthInst.program===p.program&&synthInst.sfId===(group.sf.id||group.sf.name)?'bg-slate-200':''}`,onClick:()=>selectSynthInst({sfId:group.sf.id,sfName:group.sf.display||group.sf.name||group.sf.id,bank:p.bank||0,program:p.program,name:p.name||'Program '+p.program})},p.bank===128?/*#__PURE__*/React.createElement("i",{className:"fa-solid fa-drum text-slate-400"}):/*#__PURE__*/React.createElement("i",{className:"fa-solid fa-music text-slate-400"})," ",p.name||'Program '+p.program);}))),!synthLoading&&filteredSynthList&&filteredSynthList.length===0&&synthFilter&&/*#__PURE__*/React.createElement("div",{className:"px-2 py-2 text-slate-400 italic"},"Không tìm thấy nhạc cụ trùng khớp")),document.body);})()),/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-1",title:"Tempo preview MIDI"},/*#__PURE__*/React.createElement("span",{className:"font-mono text-[10px] text-slate-700"},"Tempo:"),/*#__PURE__*/React.createElement("button",{className:"px-1 bg-[#e0e0e0] hover:bg-white border border-[#707070] rounded-sm text-[10px]",onClick:()=>commitTempo((tempo||120)-1)},"-"),/*#__PURE__*/React.createElement("div",{className:"bg-white border border-[#808080] px-1 h-5 flex items-center w-14"},/*#__PURE__*/React.createElement("input",{type:"number",min:"40",max:"300",value:tempoText,onChange:e=>{const raw=e.target.value;setTempoText(raw);const n=parseInt(raw);if(n>=40&&n<=300)commitTempo(n);},onBlur:()=>{const n=parseInt(tempoText);commitTempo(n);},onKeyDown:e=>{if(e.key==='Enter'){e.currentTarget.blur();}},className:"w-full text-xs text-right outline-none bg-transparent"})),/*#__PURE__*/React.createElement("button",{className:"px-1 bg-[#e0e0e0] hover:bg-white border border-[#707070] rounded-sm text-[10px]",onClick:()=>commitTempo((tempo||120)+1)},"+"),/*#__PURE__*/React.createElement("span",{className:"text-slate-600 text-[10px]"},"BPM"))),/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-3 font-mono text-[11px]"},/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-1"},/*#__PURE__*/React.createElement("span",null,"Pitch:"),/*#__PURE__*/React.createElement("button",{className:"px-1 bg-[#e0e0e0] hover:bg-white border border-[#707070] rounded-sm text-[10px]",onClick:()=>togglePitch(-0.5)},"-"),/*#__PURE__*/React.createElement("div",{className:"bg-white border border-[#808080] px-1 h-5 flex items-center w-14"},/*#__PURE__*/React.createElement("input",{type:"number",value:pitch.toFixed(1),step:"0.5",onChange:e=>setPitch(parseFloat(e.target.value)||0),className:"w-full text-xs text-right outline-none bg-transparent"})),/*#__PURE__*/React.createElement("button",{className:"px-1 bg-[#e0e0e0] hover:bg-white border border-[#707070] rounded-sm text-[10px]",onClick:()=>togglePitch(0.5)},"+")),/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-1"},/*#__PURE__*/React.createElement("span",null,"Rate:"),/*#__PURE__*/React.createElement("div",{className:"bg-white border border-[#808080] px-1 h-5 flex items-center w-12"},/*#__PURE__*/React.createElement("input",{type:"number",value:rate.toFixed(2),step:"0.1",onChange:e=>setRate(Math.max(0.25,Math.min(4,parseFloat(e.target.value)||1))),className:"w-full text-xs text-right outline-none bg-transparent"})),/*#__PURE__*/React.createElement("button",{className:"w-5 h-5 bg-[#e0e0e0] hover:bg-white border border-[#707070] rounded-sm text-[9px]",onClick:()=>toggleRate(-1)},"-"),/*#__PURE__*/React.createElement("button",{className:"w-5 h-5 bg-[#e0e0e0] hover:bg-white border border-[#707070] rounded-sm text-[9px]",onClick:()=>toggleRate(1)},"+"))),/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-2"},/*#__PURE__*/React.createElement("span",{className:"font-sans text-slate-700"},"Volume:"),/*#__PURE__*/React.createElement("input",{type:"range",min:"-60",max:"12",step:"0.5",value:volumeDb,onChange:e=>setVolumeDb(parseFloat(e.target.value)),className:"me-fader-slider w-24"}),/*#__PURE__*/React.createElement("div",{className:"bg-white border border-[#808080] px-1 h-5 flex items-center justify-center w-14 font-mono text-[11px]"},volumeDb<=-50?'-inf':volumeDb.toFixed(1)," dB")),/*#__PURE__*/React.createElement("div",{className:`px-2 py-0.5 border font-mono font-bold text-[10px] rounded-sm ${selIsMidi?'bg-purple-950 text-purple-300 border-purple-800':'bg-emerald-950 text-emerald-300 border-emerald-800'}`},selIsMidi?'MIDI':'Audio')),/*#__PURE__*/React.createElement("div",{className:"flex items-stretch gap-2 my-1 flex-1 min-h-0"},/*#__PURE__*/React.createElement("div",{className:"flex-1 bg-[#181818] border border-[#3a3a3a] relative overflow-hidden p-0.5"},/*#__PURE__*/React.createElement("canvas",{ref:canvasRef,className:"w-full h-full block cursor-pointer",onMouseDown:handleCanvasMouseDown,onMouseMove:handleCanvasMouseMove,onMouseUp:handleCanvasMouseUp,onContextMenu:handleCanvasContextMenu}),/*#__PURE__*/React.createElement("div",{className:"absolute top-1.5 right-1.5 flex gap-1 z-10"},/*#__PURE__*/React.createElement("button",{className:"w-5 h-5 bg-[#2a2a2a] hover:bg-slate-700 border border-[#444] rounded flex items-center justify-center text-white text-xs font-bold select-none cursor-pointer",title:"Zoom In",onClick:()=>setZoom(prev=>Math.min(10.0,prev*1.25))},"+"),/*#__PURE__*/React.createElement("button",{className:"w-5 h-5 bg-[#2a2a2a] hover:bg-slate-700 border border-[#444] rounded flex items-center justify-center text-white text-xs font-bold select-none cursor-pointer",title:"Zoom Out",onClick:()=>setZoom(prev=>Math.max(0.2,prev/1.25))},"-"),/*#__PURE__*/React.createElement("button",{className:"w-5 h-5 bg-[#2a2a2a] hover:bg-slate-700 border border-[#444] rounded flex items-center justify-center text-white text-[9px] select-none cursor-pointer",title:"Reset Zoom",onClick:()=>setZoom(1.0)},"1x")),previewCtxMenu&&/*#__PURE__*/React.createElement("div",{className:"fixed bg-[#1e1e24] border border-[#3e3e4a] rounded shadow-md z-[9999] py-1 font-sans text-xs text-slate-300 w-32 cursor-pointer select-none",style:{top:previewCtxMenu.y,left:previewCtxMenu.x},onClick:e=>e.stopPropagation()},/*#__PURE__*/React.createElement("div",{className:"px-3 py-1.5 hover:bg-slate-700 hover:text-white flex items-center gap-2",onClick:()=>{handleCopySelection();setPreviewCtxMenu(null);}},/*#__PURE__*/React.createElement("i",{className:"fa-solid fa-copy"})," Copy"),/*#__PURE__*/React.createElement("div",{className:"px-3 py-1.5 hover:bg-slate-700 hover:text-white border-t border-[#3e3e4a] flex items-center gap-2",onClick:()=>setPreviewCtxMenu(null)},/*#__PURE__*/React.createElement("i",{className:"fa-solid fa-xmark"})," Cancel"))),/*#__PURE__*/React.createElement("div",{className:"w-48 bg-[#181818] border border-[#3a3a3a] p-1.5 font-mono text-[10px] text-slate-300 leading-tight overflow-y-auto shrink-0"},selected?selIsMidi&&!selected.path?/*#__PURE__*/React.createElement(React.Fragment,null,/*#__PURE__*/React.createElement("div",null,selected.events," MIDI events"),/*#__PURE__*/React.createElement("div",null,"Length: ",selected.lengthQn," quarter notes"),/*#__PURE__*/React.createElement("div",null,"Length: ",selected.time," (est)"),/*#__PURE__*/React.createElement("div",null,"Ticks per quarter note: ",selected.tpqn)):/*#__PURE__*/React.createElement(React.Fragment,null,/*#__PURE__*/React.createElement("div",null,"Size: ",selected.size_mb!=null?selected.size_mb.toFixed(2)+' MB':'-'),selIsMidi?/*#__PURE__*/React.createElement(React.Fragment,null,/*#__PURE__*/React.createElement("div",null,"Bars: ",midiBars||1),/*#__PURE__*/React.createElement("div",null,"Beats: ",Math.round(midiTotalBeats||16)),/*#__PURE__*/React.createElement("div",null,"BPM: ",midiFileBpm||120),/*#__PURE__*/React.createElement("div",null,"Duration: ",selDur.toFixed(2),"s")):/*#__PURE__*/React.createElement(React.Fragment,null,/*#__PURE__*/React.createElement("div",null,"Duration: ",selDur.toFixed(2),"s"),/*#__PURE__*/React.createElement("div",null,"Sample Rate: 44100 Hz"),/*#__PURE__*/React.createElement("div",null,"Type: ",selected.path?selected.kind==='other'?'Local File':'Local Audio':selected.type||'Audio'))):/*#__PURE__*/React.createElement("div",null,"No file selected"))),/*#__PURE__*/React.createElement("div",{className:"h-5 bg-[#c0c0c0] border-t border-white flex items-center justify-between text-[11px] font-mono px-1 shrink-0"},/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-3"},selIsMidi&&midiNotes&&midiNotes.length?/*#__PURE__*/React.createElement("div",{className:"bg-white border border-[#808080] px-1.5 text-slate-900 font-bold"},"Bar ",Math.max(1,Math.floor(currentTime/(4*60/(tempo||120)))+1)," / ",midiBars||1,/*#__PURE__*/React.createElement("span",{className:"text-slate-500 ml-1"},"| ",formatTime(currentTime)," / ",formatTime(selDur))):/*#__PURE__*/React.createElement("div",{className:"bg-white border border-[#808080] px-1.5 text-slate-900 font-bold"},formatTime(currentTime)," / ",formatTime(selDur))),/*#__PURE__*/React.createElement("div",{className:"text-slate-800 font-bold truncate max-w-[40%]"},selected?selected.name||selected.original_name:'No file selected'),/*#__PURE__*/React.createElement("div",{className:"text-slate-700"},selBpm," bpm x",rate.toFixed(2)))));};const App=()=>{// ── State Definitions ── +const btnRect=btnSynthRef.current?btnSynthRef.current.getBoundingClientRect():null;return ReactDOM.createPortal(/*#__PURE__*/React.createElement("div",{className:"fixed z-40 w-64 bg-white border border-[#808080] shadow-xl rounded-sm text-xs text-slate-800 font-sans max-h-72 overflow-y-auto",style:{left:btnRect?btnRect.left:0,bottom:btnRect?window.innerHeight-btnRect.top+8:0}},/*#__PURE__*/React.createElement("div",{className:"sticky top-0 bg-[#e0e0e0] px-2 py-1 font-bold border-b border-[#a0a0a0] flex items-center justify-between z-20"},/*#__PURE__*/React.createElement("span",null,"Select Instrument"),/*#__PURE__*/React.createElement("button",{onClick:()=>setSynthOpen(false),className:"text-slate-500 hover:text-slate-900"},/*#__PURE__*/React.createElement("i",{className:"fa-solid fa-xmark"}))),/*#__PURE__*/React.createElement("div",{className:"sticky top-[23px] bg-white p-1 border-b border-[#c0c0c0] z-20 flex items-center gap-1"},/*#__PURE__*/React.createElement("i",{className:"fa-solid fa-magnifying-glass text-slate-400 pl-1 text-[10px]"}),/*#__PURE__*/React.createElement("input",{type:"text",placeholder:"T\xECm nh\u1EA1c c\u1EE5...",value:synthFilter,onChange:e=>setSynthFilter(e.target.value),onClick:e=>e.stopPropagation(),className:"w-full px-1 py-0.5 border border-[#c0c0c0] rounded-sm text-xs font-sans focus:outline-none focus:border-blue-500"}),synthFilter&&/*#__PURE__*/React.createElement("button",{onClick:e=>{e.stopPropagation();setSynthFilter('');},className:"text-slate-400 hover:text-slate-700 px-1"},/*#__PURE__*/React.createElement("i",{className:"fa-solid fa-xmark text-[10px]"}))),synthLoading&&/*#__PURE__*/React.createElement("div",{className:"px-2 py-2 text-slate-400 italic"},"Loading..."),!synthLoading&&(!synthList||synthList.length===0)&&/*#__PURE__*/React.createElement("div",{className:"px-2 py-2 text-slate-400 italic"},"Kh\xF4ng c\xF3 SoundFont n\xE0o"),/*#__PURE__*/React.createElement("div",{className:`flex items-center gap-1 px-2 py-1 cursor-pointer hover:bg-blue-100 ${!synthInst?'bg-slate-200':''}`,onClick:()=>selectSynthInst(null)},/*#__PURE__*/React.createElement("i",{className:"fa-solid fa-ban text-slate-400"})," None (m\u1EB7c \u0111\u1ECBnh)"),!synthLoading&&filteredSynthList&&filteredSynthList.map(group=>/*#__PURE__*/React.createElement("div",{key:group.sf.id||group.sf.name},/*#__PURE__*/React.createElement("div",{className:"px-2 py-1 bg-[#f0f0f0] font-semibold text-slate-600 border-t border-[#e0e0e0] truncate"},group.sf.display||group.sf.name||group.sf.id),(group.presets||[]).slice(0,200).map((p,pi)=>{const progId=p.id||p.name||'preset_'+pi;return/*#__PURE__*/React.createElement("div",{key:progId,className:`flex items-center gap-1 px-2 pl-4 py-0.5 cursor-pointer hover:bg-blue-100 truncate ${synthInst&&synthInst.program===p.program&&synthInst.sfId===(group.sf.id||group.sf.name)?'bg-slate-200':''}`,onClick:()=>selectSynthInst({sfId:group.sf.id,sfName:group.sf.display||group.sf.name||group.sf.id,bank:p.bank||0,program:p.program,name:p.name||'Program '+p.program})},p.bank===128?/*#__PURE__*/React.createElement("i",{className:"fa-solid fa-drum text-slate-400"}):/*#__PURE__*/React.createElement("i",{className:"fa-solid fa-music text-slate-400"})," ",p.name||'Program '+p.program);}))),!synthLoading&&filteredSynthList&&filteredSynthList.length===0&&synthFilter&&/*#__PURE__*/React.createElement("div",{className:"px-2 py-2 text-slate-400 italic"},"Kh\xF4ng t\xECm th\u1EA5y nh\u1EA1c c\u1EE5 tr\xF9ng kh\u1EDBp")),document.body);})()),/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-1",title:"Tempo preview MIDI"},/*#__PURE__*/React.createElement("span",{className:"font-mono text-[10px] text-slate-700"},"Tempo:"),/*#__PURE__*/React.createElement("button",{className:"px-1 bg-[#e0e0e0] hover:bg-white border border-[#707070] rounded-sm text-[10px]",onClick:()=>commitTempo((tempo||120)-1)},"-"),/*#__PURE__*/React.createElement("div",{className:"bg-white border border-[#808080] px-1 h-5 flex items-center w-14"},/*#__PURE__*/React.createElement("input",{type:"number",min:"40",max:"300",value:tempoText,onChange:e=>{const raw=e.target.value;setTempoText(raw);const n=parseInt(raw);if(n>=40&&n<=300)commitTempo(n);},onBlur:()=>{const n=parseInt(tempoText);commitTempo(n);},onKeyDown:e=>{if(e.key==='Enter'){e.currentTarget.blur();}},className:"w-full text-xs text-right outline-none bg-transparent"})),/*#__PURE__*/React.createElement("button",{className:"px-1 bg-[#e0e0e0] hover:bg-white border border-[#707070] rounded-sm text-[10px]",onClick:()=>commitTempo((tempo||120)+1)},"+"),/*#__PURE__*/React.createElement("span",{className:"text-slate-600 text-[10px]"},"BPM"))),/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-3 font-mono text-[11px]"},/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-1"},/*#__PURE__*/React.createElement("span",null,"Pitch:"),/*#__PURE__*/React.createElement("button",{className:"px-1 bg-[#e0e0e0] hover:bg-white border border-[#707070] rounded-sm text-[10px]",onClick:()=>togglePitch(-0.5)},"-"),/*#__PURE__*/React.createElement("div",{className:"bg-white border border-[#808080] px-1 h-5 flex items-center w-14"},/*#__PURE__*/React.createElement("input",{type:"number",value:pitch.toFixed(1),step:"0.5",onChange:e=>setPitch(parseFloat(e.target.value)||0),className:"w-full text-xs text-right outline-none bg-transparent"})),/*#__PURE__*/React.createElement("button",{className:"px-1 bg-[#e0e0e0] hover:bg-white border border-[#707070] rounded-sm text-[10px]",onClick:()=>togglePitch(0.5)},"+")),/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-1"},/*#__PURE__*/React.createElement("span",null,"Rate:"),/*#__PURE__*/React.createElement("div",{className:"bg-white border border-[#808080] px-1 h-5 flex items-center w-12"},/*#__PURE__*/React.createElement("input",{type:"number",value:rate.toFixed(2),step:"0.1",onChange:e=>setRate(Math.max(0.25,Math.min(4,parseFloat(e.target.value)||1))),className:"w-full text-xs text-right outline-none bg-transparent"})),/*#__PURE__*/React.createElement("button",{className:"w-5 h-5 bg-[#e0e0e0] hover:bg-white border border-[#707070] rounded-sm text-[9px]",onClick:()=>toggleRate(-1)},"-"),/*#__PURE__*/React.createElement("button",{className:"w-5 h-5 bg-[#e0e0e0] hover:bg-white border border-[#707070] rounded-sm text-[9px]",onClick:()=>toggleRate(1)},"+"))),/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-2"},/*#__PURE__*/React.createElement("span",{className:"font-sans text-slate-700"},"Volume:"),/*#__PURE__*/React.createElement("input",{type:"range",min:"-60",max:"12",step:"0.5",value:volumeDb,onChange:e=>setVolumeDb(parseFloat(e.target.value)),className:"me-fader-slider w-24"}),/*#__PURE__*/React.createElement("div",{className:"bg-white border border-[#808080] px-1 h-5 flex items-center justify-center w-14 font-mono text-[11px]"},volumeDb<=-50?'-inf':volumeDb.toFixed(1)," dB")),/*#__PURE__*/React.createElement("div",{className:`px-2 py-0.5 border font-mono font-bold text-[10px] rounded-sm ${selIsMidi?'bg-purple-950 text-purple-300 border-purple-800':'bg-emerald-950 text-emerald-300 border-emerald-800'}`},selIsMidi?'MIDI':'Audio')),/*#__PURE__*/React.createElement("div",{className:"flex items-stretch gap-2 my-1 flex-1 min-h-0"},/*#__PURE__*/React.createElement("div",{className:"flex-1 bg-[#181818] border border-[#3a3a3a] relative overflow-hidden p-0.5"},/*#__PURE__*/React.createElement("canvas",{ref:canvasRef,className:"w-full h-full block cursor-pointer",onMouseDown:handleCanvasMouseDown,onMouseMove:handleCanvasMouseMove,onMouseUp:handleCanvasMouseUp,onContextMenu:handleCanvasContextMenu}),/*#__PURE__*/React.createElement("div",{className:"absolute top-1.5 right-1.5 flex gap-1 z-10"},/*#__PURE__*/React.createElement("button",{className:"w-5 h-5 bg-[#2a2a2a] hover:bg-slate-700 border border-[#444] rounded flex items-center justify-center text-white text-xs font-bold select-none cursor-pointer",title:"Zoom In",onClick:()=>setZoom(prev=>Math.min(10.0,prev*1.25))},"+"),/*#__PURE__*/React.createElement("button",{className:"w-5 h-5 bg-[#2a2a2a] hover:bg-slate-700 border border-[#444] rounded flex items-center justify-center text-white text-xs font-bold select-none cursor-pointer",title:"Zoom Out",onClick:()=>setZoom(prev=>Math.max(0.2,prev/1.25))},"-"),/*#__PURE__*/React.createElement("button",{className:"w-5 h-5 bg-[#2a2a2a] hover:bg-slate-700 border border-[#444] rounded flex items-center justify-center text-white text-[9px] select-none cursor-pointer",title:"Reset Zoom",onClick:()=>setZoom(1.0)},"1x")),previewCtxMenu&&/*#__PURE__*/React.createElement("div",{className:"fixed bg-[#1e1e24] border border-[#3e3e4a] rounded shadow-md z-[9999] py-1 font-sans text-xs text-slate-300 w-32 cursor-pointer select-none",style:{top:previewCtxMenu.y,left:previewCtxMenu.x},onClick:e=>e.stopPropagation()},/*#__PURE__*/React.createElement("div",{className:"px-3 py-1.5 hover:bg-slate-700 hover:text-white flex items-center gap-2",onClick:()=>{handleCopySelection();setPreviewCtxMenu(null);}},/*#__PURE__*/React.createElement("i",{className:"fa-solid fa-copy"})," Copy"),/*#__PURE__*/React.createElement("div",{className:"px-3 py-1.5 hover:bg-slate-700 hover:text-white border-t border-[#3e3e4a] flex items-center gap-2",onClick:()=>setPreviewCtxMenu(null)},/*#__PURE__*/React.createElement("i",{className:"fa-solid fa-xmark"})," Cancel"))),/*#__PURE__*/React.createElement("div",{className:"w-48 bg-[#181818] border border-[#3a3a3a] p-1.5 font-mono text-[10px] text-slate-300 leading-tight overflow-y-auto shrink-0"},selected?selIsMidi&&!selected.path?/*#__PURE__*/React.createElement(React.Fragment,null,/*#__PURE__*/React.createElement("div",null,selected.events," MIDI events"),/*#__PURE__*/React.createElement("div",null,"Length: ",selected.lengthQn," quarter notes"),/*#__PURE__*/React.createElement("div",null,"Length: ",selected.time," (est)"),/*#__PURE__*/React.createElement("div",null,"Ticks per quarter note: ",selected.tpqn)):/*#__PURE__*/React.createElement(React.Fragment,null,/*#__PURE__*/React.createElement("div",null,"Size: ",selected.size_mb!=null?selected.size_mb.toFixed(2)+' MB':'-'),selIsMidi?/*#__PURE__*/React.createElement(React.Fragment,null,/*#__PURE__*/React.createElement("div",null,"Bars: ",midiBars||1),/*#__PURE__*/React.createElement("div",null,"Beats: ",Math.round(midiTotalBeats||16)),/*#__PURE__*/React.createElement("div",null,"BPM: ",midiFileBpm||120),/*#__PURE__*/React.createElement("div",null,"Duration: ",selDur.toFixed(2),"s")):/*#__PURE__*/React.createElement(React.Fragment,null,/*#__PURE__*/React.createElement("div",null,"Duration: ",selDur.toFixed(2),"s"),/*#__PURE__*/React.createElement("div",null,"Sample Rate: 44100 Hz"),/*#__PURE__*/React.createElement("div",null,"Type: ",selected.path?selected.kind==='other'?'Local File':'Local Audio':selected.type||'Audio'))):/*#__PURE__*/React.createElement("div",null,"No file selected"))),/*#__PURE__*/React.createElement("div",{className:"h-5 bg-[#c0c0c0] border-t border-white flex items-center justify-between text-[11px] font-mono px-1 shrink-0"},/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-3"},selIsMidi&&midiNotes&&midiNotes.length?/*#__PURE__*/React.createElement("div",{className:"bg-white border border-[#808080] px-1.5 text-slate-900 font-bold"},"Bar ",Math.max(1,Math.floor(currentTime/(4*60/(tempo||120)))+1)," / ",midiBars||1,/*#__PURE__*/React.createElement("span",{className:"text-slate-500 ml-1"},"| ",formatTime(currentTime)," / ",formatTime(selDur))):/*#__PURE__*/React.createElement("div",{className:"bg-white border border-[#808080] px-1.5 text-slate-900 font-bold"},formatTime(currentTime)," / ",formatTime(selDur))),/*#__PURE__*/React.createElement("div",{className:"text-slate-800 font-bold truncate max-w-[40%]"},selected?selected.name||selected.original_name:'No file selected'),/*#__PURE__*/React.createElement("div",{className:"text-slate-700"},selBpm," bpm x",rate.toFixed(2)))));};const App=()=>{// ── State Definitions ── const[tracks,setTracks]=useState([{id:'1',name:'Track 01',buffer:null,startTime:0,volumeDb:0,pan:0,muted:false,solo:false,color:'#0f766e',markers:[],serverFileId:null,clips:[],sections:[],midiItems:[],isArmed:false,monitoringEnabled:true,inputSource:{deviceType:'NONE',deviceId:''}},{id:'2',name:'Track 02',buffer:null,startTime:0,volumeDb:0,pan:0,muted:false,solo:false,color:'#1d4ed8',markers:[],serverFileId:null,clips:[],sections:[],midiItems:[],isArmed:false,monitoringEnabled:true,inputSource:{deviceType:'NONE',deviceId:''}}]);const[appWarningModal,setAppWarningModal]=useState(null);const[bpm,setBpm]=useState(localStorage.getItem('studio_bpm')||'120');const prevBpmRef=useRef(bpm);const[draggedClip,setDraggedClip]=useState(null);const[hoveredTrackId,setHoveredTrackId]=useState(null);// Recalculate item/section/selection durations when BPM changes useEffect(()=>{const oldSpb=prevBpmRef.current?60.0/parseFloat(prevBpmRef.current)*4:null;const bpmVal=parseFloat(bpm)||120;const secondsPerBar=60.0/bpmVal*4;// Recalculate range loop selection to maintain bar count (tempo mode only) if(oldSpb&&selectionFollowsTempo&&selectionStart!==null&&selectionEnd!==null&&selectionEnd>selectionStart){const startBar=selectionStart/oldSpb;const endBar=selectionEnd/oldSpb;if(endBar-startBar>0.01){setSelectionStart(startBar*secondsPerBar);setSelectionEnd(endBar*secondsPerBar);}}prevBpmRef.current=bpm;// Force canvas redraw @@ -2127,4 +2127,4 @@ const tid=selectedTrackId||activeTracks&&activeTracks[0]&&activeTracks[0].id;if( 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ừ Carla (native GUI)"},"\u2B06 Upload preset (t\u1EEB Carla...)"),!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. -document.addEventListener('wheel',e=>{const t=e.target;if(t&&t.closest&&t.closest('[data-sf-wheel]'))e.preventDefault();},{passive:false,capture:true});const root=ReactDOM.createRoot(document.getElementById('root'));root.render(/*#__PURE__*/React.createElement(App,null));setTimeout(()=>lucide.createIcons(),300); +document.addEventListener('wheel',e=>{const t=e.target;if(t&&t.closest&&t.closest('[data-sf-wheel]'))e.preventDefault();},{passive:false,capture:true});const root=ReactDOM.createRoot(document.getElementById('root'));root.render(/*#__PURE__*/React.createElement(App,null));setTimeout(()=>lucide.createIcons(),300); \ No newline at end of file diff --git a/build/scripts/build_native_bridge.ps1 b/build/scripts/build_native_bridge.ps1 index 4a5737c..13a1cb1 100644 --- a/build/scripts/build_native_bridge.ps1 +++ b/build/scripts/build_native_bridge.ps1 @@ -79,7 +79,7 @@ cmake -S $NB_DIR -B $BUILD_DIR -G $VS_GEN -A $VS_ARCH ` -DCMAKE_BUILD_TYPE=Release ` -DVCPKG_TARGET_TRIPLET=x64-windows if ($LASTEXITCODE -ne 0) { Write-Host "ERROR: cmake configure failed" -ForegroundColor Red; exit 1 } -cmake --build $BUILD_DIR --config Release --target daw_vst_bridge -- /m +cmake --build $BUILD_DIR --config Release --target daw_vst_bridge fx_vst_bridge -- /m if ($LASTEXITCODE -ne 0) { Write-Host "ERROR: cmake build failed" -ForegroundColor Red; exit 1 } # 6. Copy sidecar to Tauri externalBin location @@ -89,9 +89,13 @@ New-Item -ItemType Directory -Force $BIN_DIR | Out-Null $EXE = Join-Path $BUILD_DIR "Release\daw_vst_bridge.exe" if (-not (Test-Path $EXE)) { Write-Host "ERROR: $EXE not found" -ForegroundColor Red; exit 1 } Copy-Item $EXE (Join-Path $BIN_DIR "daw_vst_bridge-x86_64-pc-windows-msvc.exe") -Force +# fx_vst_bridge: VST FX host sidecar (split — see CMakeLists.txt) +$FX_EXE = Join-Path $BUILD_DIR "Release\fx_vst_bridge.exe" +if (-not (Test-Path $FX_EXE)) { Write-Host "ERROR: $FX_EXE not found" -ForegroundColor Red; exit 1 } +Copy-Item $FX_EXE (Join-Path $BIN_DIR "fx_vst_bridge-x86_64-pc-windows-msvc.exe") -Force -# 7. Copy fluidsynth runtime DLL next to sidecar (Tauri externalBin bundles only the exe) -Write-Host "== [7/7] Copy libfluidsynth-3.dll ==" +# 7. Copy runtime DLLs next to sidecar (Tauri externalBin bundles only the exe) +Write-Host "== [7/7] Copy runtime DLLs (fluidsynth + jpeg) ==" $FS_DLL = Join-Path $BUILD_DIR "vcpkg_installed\x64-windows\bin\libfluidsynth-3.dll" if (-not (Test-Path $FS_DLL)) { $FS_DLL = Join-Path $VCPKG_ROOT "installed\x64-windows\bin\libfluidsynth-3.dll" } if (Test-Path $FS_DLL) { @@ -101,4 +105,15 @@ if (Test-Path $FS_DLL) { } else { Write-Host "WARN: libfluidsynth-3.dll not found - bridge needs it at runtime" -ForegroundColor Yellow } +# jpeg62.dll (VST3 hosting image decode; vcpkg libjpeg-turbo) - same candidates as fluidsynth +$JPEG_DLL = Join-Path $NB_DIR "vcpkg_installed\x64-windows\bin\jpeg62.dll" +if (-not (Test-Path $JPEG_DLL)) { $JPEG_DLL = Join-Path $BUILD_DIR "vcpkg_installed\x64-windows\bin\jpeg62.dll" } +if (-not (Test-Path $JPEG_DLL)) { $JPEG_DLL = Join-Path $VCPKG_ROOT "packages\libjpeg-turbo_x64-windows\bin\jpeg62.dll" } +if (Test-Path $JPEG_DLL) { + Copy-Item $JPEG_DLL (Join-Path $BIN_DIR "jpeg62.dll") -Force + Copy-Item $JPEG_DLL (Join-Path $BUILD_DIR "Release\jpeg62.dll") -Force + Write-Host " jpeg62.dll copied to src-tauri\binaries" +} else { + Write-Host "WARN: jpeg62.dll not found - VST3 GUIs may fail to show images" -ForegroundColor Yellow +} Write-Host "== DONE: sidecar + DLL in src-tauri\binaries ==" diff --git a/build/scripts/build_windows.ps1 b/build/scripts/build_windows.ps1 index e09deec..670254c 100644 --- a/build/scripts/build_windows.ps1 +++ b/build/scripts/build_windows.ps1 @@ -57,8 +57,8 @@ if ($LASTEXITCODE -ne 0) { Write-Host "ERROR: bridge sidecar missing" -Foregroun npm install -D @tauri-apps/cli # Frontend host: copy template -> src-tauri/ui/index.html (giu Tauri context, # document.write UI engine; neu thieu -> window mat __TAURI__ -> bridge chet). -Copy-Item buildscriptsfrontend_host_index.html src-tauriuiindex.html -Force -if (-not (Test-Path src-tauriuiindex.html)) { Write-Host "ERROR: frontend host missing" -ForegroundColor Red; exit 1 } +Copy-Item build\scripts\index.html src-tauri\ui\index.html -Force +if (-not (Test-Path src-tauri\ui\index.html)) { Write-Host "ERROR: frontend host missing" -ForegroundColor Red; exit 1 } npx tauri build Write-Host "" diff --git a/install/daw_vst_bridge.exe b/install/daw_vst_bridge.exe deleted file mode 100644 index 48f2666..0000000 Binary files a/install/daw_vst_bridge.exe and /dev/null differ diff --git a/install/jpeg62.dll b/install/jpeg62.dll deleted file mode 100644 index 599b5f3..0000000 Binary files a/install/jpeg62.dll and /dev/null differ diff --git a/install/libfluidsynth-3.dll b/install/libfluidsynth-3.dll deleted file mode 100644 index 64c6e62..0000000 Binary files a/install/libfluidsynth-3.dll and /dev/null differ diff --git a/install/plugin_host.exe b/install/plugin_host.exe deleted file mode 100644 index 223e1d0..0000000 Binary files a/install/plugin_host.exe and /dev/null differ diff --git a/install/sonicforge-daw.exe b/install/sonicforge-daw.exe deleted file mode 100644 index 6a7b68a..0000000 Binary files a/install/sonicforge-daw.exe and /dev/null differ diff --git a/native_bridge/CMakeLists.txt b/native_bridge/CMakeLists.txt index a1cf086..894d6a0 100644 --- a/native_bridge/CMakeLists.txt +++ b/native_bridge/CMakeLists.txt @@ -98,6 +98,32 @@ else() target_link_libraries(daw_vst_bridge PRIVATE ${FLUIDSYNTH_LIBRARIES} ${SFIZZ_LIBRARIES}) endif() +# ── fx_vst_bridge: VST FX host (split from daw_vst_bridge). Windows-only: +# one-shot modes --render-fx / --scan / --open-fx-gui / --fx-gui. No SHM +# realtime loop (the live master FX chain stays in daw_vst_bridge because +# it processes the instrument mix in-process). ── +if(WIN32) +add_executable(fx_vst_bridge + src/main_fx.cpp + src/RenderFxJob.cpp + src/FxGuiServer.cpp +) +if(VST3_SDK_TARGET) + target_compile_definitions(fx_vst_bridge PRIVATE HAVE_VST3SDK=1) + target_link_libraries(fx_vst_bridge PRIVATE ${VST3_SDK_TARGET} sdk_hosting sdk_common) + target_sources(fx_vst_bridge PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR}/vst3sdk/public.sdk/source/vst/hosting/module_win32.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/vst3sdk/public.sdk/source/common/memorystream.cpp + ) +endif() + find_package(JPEG REQUIRED) # libjpeg-turbo (vcpkg) — FxGuiServer.cpp JPEG encode + # FX modes are VST3 + builtin DSP only — no fluidsynth/sfizz (avoids a + # runtime libfluidsynth-3.dll dependency for the FX bridge). + target_link_libraries(fx_vst_bridge PRIVATE JPEG::JPEG) + # Required Windows libs + target_link_libraries(fx_vst_bridge PRIVATE winmm imm32 ws2_32) +endif() + # ── gui_probe: standalone VST3 GUI attach probe (debug tool, not shipped) ── if(WIN32) add_executable(gui_probe diff --git a/native_bridge/debug/build_selfcheck.bat b/native_bridge/debug/build_selfcheck.bat new file mode 100644 index 0000000..da492e5 --- /dev/null +++ b/native_bridge/debug/build_selfcheck.bat @@ -0,0 +1,6 @@ +@echo off +call "C:\Program Files (x86)\Microsoft Visual Studio\2019\BuildTools\VC\Auxiliary\Build\vcvars64.bat" >nul 2>&1 +cd /d C:\Users\locpham\SonicForgeStudio\native_bridge +cl /nologo /std:c++17 /EHsc /I include /I vestige tests\shm_selfcheck.cpp /Fe:debug\shm_selfcheck_new.exe +if errorlevel 1 exit /b 1 +debug\shm_selfcheck_new.exe diff --git a/native_bridge/src/NativeInstrumentEngine.cpp b/native_bridge/src/NativeInstrumentEngine.cpp index b39adac..2ba5aeb 100644 --- a/native_bridge/src/NativeInstrumentEngine.cpp +++ b/native_bridge/src/NativeInstrumentEngine.cpp @@ -24,7 +24,6 @@ #include #include #ifdef _WIN32 -#include // _heapchk (TEMP diag) #endif // ----------------------------------------------------------------- @@ -438,9 +437,8 @@ void InstrumentEngineManager::allNotesOff() { // audio buffers; plain std::vector is only 16B-aligned. Over-allocate and // align the pointer handed to plugins to 64B. static float* alignF(std::vector& v, size_t n) { - // 64B-aligned pointer can sit up to 63B into the allocation; TEMP diag: - // huge tail so we can measure how far DUNE writes past n. - const size_t kTail = 2097152 + 64; + // 64B-aligned pointer can sit up to 63B into the allocation. + const size_t kTail = 64; if (v.size() < n + kTail) v.assign(n + kTail, 0.0f); uintptr_t a = reinterpret_cast(v.data()); return reinterpret_cast((a + 63) & ~(uintptr_t)63); @@ -562,17 +560,6 @@ void InstrumentEngineManager::renderAll(float* outputL, float* outputR, uint32_t if (channelQuiet(ch)) continue; // editor open / crashed: do not process float* spL = alignF(scratchL_, numSamples); float* spR = alignF(scratchR_, numSamples); - std::cerr << "[NativeBridge][diag] renderAll ch=" << ch << " n=" << numSamples - << " spL=0x" << std::hex << (uintptr_t)spL - << " spR=0x" << (uintptr_t)spR - << " capL=" << std::dec << scratchL_.size() - << " capR=" << scratchR_.size() << std::endl; - // TEMP diag: fill canary with a NaN pattern so writes of 0.0 (silence) - // are still detected. - for (uint32_t i = 0; i < numSamples + 2097152; ++i) { - spL[i] = 0x7FC00000u; // quiet NaN - spR[i] = 0x7FC00000u; - } #ifdef _WIN32 DWORD sehCode = 0; #else @@ -617,28 +604,6 @@ void InstrumentEngineManager::renderAll(float* outputL, float* outputR, uint32_t } continue; } - // TEMP diag: plugin output-overrun canary + heap health. - { - static uint64_t diagBlock = 0; ++diagBlock; - int farL = -1, farR = -1; - for (uint32_t i = 0; i < 2097152; ++i) { - uint32_t bL, bR; - std::memcpy(&bL, &spL[numSamples + i], 4); - std::memcpy(&bR, &spR[numSamples + i], 4); - if (bL != 0x7FC00000u) farL = (int)i; - if (bR != 0x7FC00000u) farR = (int)i; - } - if (farL >= 0 || farR >= 0) - std::cerr << "[NativeBridge][diag] block " << diagBlock - << " OUTPUT OVERRUN past n: L=" << farL - << " R=" << farR << " (ch=" << ch << ")" << std::endl; -#ifdef _WIN32 - int h = _heapchk(); - if (h != _HEAPOK) - std::cerr << "[NativeBridge][diag] block " << diagBlock - << " heap NOT OK code=" << h << std::endl; -#endif - } // Auto-normalize: makeup calibrated (constant per-channel gain, no steps). auto mit = makeup_.find(ch); const float g = (mit != makeup_.end()) ? mit->second : 1.0f; diff --git a/native_bridge/src/RenderJob.cpp b/native_bridge/src/RenderJob.cpp index 9b8dd84..a45d0ec 100644 --- a/native_bridge/src/RenderJob.cpp +++ b/native_bridge/src/RenderJob.cpp @@ -351,11 +351,24 @@ int run_render_job(const std::string& jobPath, const std::string& outPath) { std::cerr << "[RenderJob] preset ignored for non-VST3 type " << (int)type << " (SF2/SFZ use soundfont_bank/soundfont_program)" << std::endl; #ifdef HAVE_VST3SDK - } else if (!applyVst3Preset(mgr.get(0), preset)) { - fail("preset import FAILED for " + pluginPath); - return rc; // rc stays 1: job/argument error } else { - std::cout << "[RenderJob] preset imported (VST3 .vstpreset) type=" << (int)type << std::endl; + // Accept BOTH .vstpreset ('VST3' magic) and the bridge state + // blob ([4B BE compLen][comp][4B BE ctrlLen][ctrl]) -- realtime + // LOAD restores with loadSerializedState, so offline --render + // must too (plugins whose default preset is silent -- Ample, + // Toontrack, Kong Audio -- otherwise render digital silence). + std::vector head; + bool isVstPreset = base64Decode(preset, head) && head.size() >= 4 && + std::memcmp(head.data(), "VST3", 4) == 0; + if (!isVstPreset) { + mgr.get(0)->loadSerializedState(preset); + std::cout << "[RenderJob] preset restored (bridge state blob) type=" << (int)type << std::endl; + } else if (!applyVst3Preset(mgr.get(0), preset)) { + fail("preset import FAILED for " + pluginPath); + return rc; // rc stays 1: job/argument error + } else { + std::cout << "[RenderJob] preset imported (VST3 .vstpreset) type=" << (int)type << std::endl; + } } #else } else { diff --git a/native_bridge/src/Vst2Instrument.cpp b/native_bridge/src/Vst2Instrument.cpp index 2c87fcb..e905681 100644 --- a/native_bridge/src/Vst2Instrument.cpp +++ b/native_bridge/src/Vst2Instrument.cpp @@ -25,8 +25,6 @@ std::unordered_map g_v2Registry; // the callback may be invoked with a null effect (some plugins query the // sample rate during construction). thread_local Vst2Instrument* t_loading = nullptr; -// TEMP probe: log every audioMaster call while effEditOpen is running. -thread_local bool t_editingProbe = false; // Resize hook registered by the bridge (main.cpp): plugin audioMaster // (SizeWindow) -> move the host window on the UI worker thread. static void (*g_vst2ResizeHook)(void*, int, int) = nullptr; @@ -98,9 +96,6 @@ struct Vst2HostState { intptr_t audioMaster(AEffect* effect, int32_t opcode, int32_t index, intptr_t value, void* ptr, float opt) { (void)index; (void)value; (void)ptr; (void)opt; - if (t_editingProbe || true) - std::cerr << "[Vst2Instrument][editprobe] audioMaster op=" << opcode - << " idx=" << index << " val=" << value << " ptr=" << ptr << std::endl; Vst2Instrument* self = nullptr; if (effect) { std::lock_guard lock(g_v2Mutex); @@ -155,7 +150,6 @@ intptr_t audioMaster(AEffect* effect, int32_t opcode, int32_t index, ti.ppqPos = pos * 120.0 / 60.0 / 44100.0; ti.flags = kVstTempoValid | kVstTransportPlaying; // TEMP diag: DUNE silent - try forcing transport playing pos += (self ? self->maxBlockSize() : 512); - std::cerr << "[Vst2Instrument][diag] getTime flags=" << ti.flags << " pos=" << pos << std::endl; return (intptr_t)&ti; } case kAudioMasterGetCurrentProcessLevel: return 1; // audio thread @@ -268,9 +262,7 @@ static intptr_t v2_dispatch(AEffect* fx, int32_t op, int32_t idx, intptr_t val, static void* g_sehAddr = nullptr; static CONTEXT g_ctxCopy; __try { - if (op == kEffectEditOpen) t_editingProbe = true; intptr_t r = fx->dispatcher(fx, op, idx, val, ptr, opt); - if (op == kEffectEditOpen) t_editingProbe = false; return r; } __except (g_sehCode = GetExceptionCode(), g_sehAddr = GetExceptionInformation()->ExceptionRecord->ExceptionAddress, @@ -388,73 +380,11 @@ bool Vst2Instrument::init(double sampleRate, uint32_t maxBlockSize) { maxBlockSize_ = maxBlockSize; AEffect* fx = s->effect; try { - // TEMP diag variant B: sampleRate/blockSize BEFORE effOpen, block size - // matches the actual render size (256), no StartProcess. fx->dispatcher(fx, kEffectSetSampleRate, 0, 0, nullptr, (float)sampleRate); fx->dispatcher(fx, kEffectSetBlockSize, 0, (intptr_t)maxBlockSize, nullptr, 0); fx->dispatcher(fx, kEffectOpen, 0, 0, nullptr, 0); fx->dispatcher(fx, kEffectSetProgram, 0, 0, nullptr, 0); fx->dispatcher(fx, kEffectMainsChanged, 0, 1, nullptr, 0); - // TEMP diag: dump first params to see if the default patch is audible - { - const int pis[] = {0, 1, 10, 100, 500, 1000, 1160, 1168}; - for (int pi : pis) { - if (pi >= fx->numParams) continue; - char nm[128] = {0}, disp[128] = {0}, lbl[128] = {0}; - fx->dispatcher(fx, kEffectGetParamName, 0, pi, nm, 0); - fx->dispatcher(fx, kEffectGetParamDisplay, 0, pi, disp, 0); - fx->dispatcher(fx, kEffectGetParamLabel, 0, pi, lbl, 0); - std::cerr << "[Vst2Instrument][diag] param " << pi << " name=" << nm << " disp=" << disp << " label=" << lbl << std::endl; - } - } - // TEMP diag: does the plugin itself claim to receive MIDI? - { - const char* probes[] = {"receiveVstEvents", "receiveVstMidiEvent", "midiProgramNames", "bypass"}; - for (auto pr : probes) { - intptr_t r = fx->dispatcher(fx, kEffectCanDo, 0, 0, (void*)pr, 0); - std::cerr << "[Vst2Instrument][diag] plugin canDo " << pr << " -> " << r << std::endl; - } - } - // TEMP diag: send raw MIDI directly (bypass SHM path). CC7 vol max + noteOn - // with kVstMidiEventIsRealtime + retriggers. - { - auto sendMidi = [&](int b0, int b1, int b2) { - VstMidiEvent e; - std::memset(&e, 0, sizeof(e)); - e.type = kVstMidiType; - e.byteSize = (int32_t)sizeof(e); - e.flags = kVstMidiEventIsRealtime; - e.midiData[0] = (char)b0; e.midiData[1] = (char)b1; e.midiData[2] = (char)b2; - VstEvent* evs[1] = { reinterpret_cast(&e) }; - VstEvents events; - std::memset(&events, 0, sizeof(events)); - events.numEvents = 1; - events.events[0] = evs[0]; - fx->dispatcher(fx, kEffectProcessEvents, 0, 0, &events, 0); - }; - std::cerr << "[Vst2Instrument][diag] raw CC7 127 + noteOn 60/100 x8" << std::endl; - sendMidi(0xB0, 7, 127); - for (int r = 0; r < 8; ++r) sendMidi(0x90, 60, 100); - } - // TEMP diag: does DUNE hold a preset chunk? dump its head as hex+ascii - { - void* chunkPtr = nullptr; - intptr_t sz = fx->dispatcher(fx, kEffectGetChunk, 0, 1, &chunkPtr, 0); - std::cerr << "[Vst2Instrument][diag] getChunk size=" << sz << " ptr=" << chunkPtr << std::endl; - if (sz > 0 && chunkPtr) { - const unsigned char* b = (const unsigned char*)chunkPtr; - std::string ascii; - for (intptr_t i = 0; i < sz && i < 512; ++i) { - ascii += (b[i] >= 32 && b[i] < 127) ? (char)b[i] : '.'; - } - std::cerr << "[Vst2Instrument][diag] chunkhead: " << ascii << std::endl; - std::cerr << "[Vst2Instrument][diag] chunkbytes: "; - for (intptr_t i = 0; i < sz && i < 64; ++i) { - char hx[8]; snprintf(hx, sizeof(hx), "%02x ", b[i]); std::cerr << hx; - } - std::cerr << std::endl; - } - } } catch (...) { std::cerr << "[Vst2Instrument] init dispatcher EXCEPTION ch=" << channel_ << std::endl; return false; @@ -683,29 +613,25 @@ void Vst2Instrument::loadSerializedState(const std::string& base64) { } void Vst2Instrument::processAudioBlock(float* outputL, float* outputR, uint32_t numSamples) { - std::cerr << "[Vst2Instrument] pab START n=" << numSamples << std::endl; auto* s = static_cast(state_); - std::cerr << "[Vst2Instrument] pab state ok=" << (s && s->effect) << std::endl; if (!s || !s->effect) { std::memset(outputL, 0, numSamples * sizeof(float)); std::memset(outputR, 0, numSamples * sizeof(float)); return; } - std::cerr << "[Vst2Instrument] pab before alignF" << std::endl; AEffect* fx = s->effect; // 64B-align inputs: DUNE 3 etc. use aligned SIMD on audio buffers. auto alignF = [](std::vector& v, size_t n) -> float* { - // TEMP diag (heap-corrupt hunt): huge tail + input canary to measure - // how far DUNE writes past n. - const size_t kTail = 2097152 + 64; + // 64B-aligned pointer can sit up to 63B into the allocation. + const size_t kTail = 64; if (v.size() < n + kTail) v.assign(n + kTail, 0.0f); uintptr_t a = reinterpret_cast(v.data()); return reinterpret_cast((a + 63) & ~(uintptr_t)63); }; float* inL = alignF(inL_, numSamples); float* inR = alignF(inR_, numSamples); - for (uint32_t i = 0; i < numSamples + 2097152; ++i) { - inL[i] = 0.0f; // TEMP: clean input (NaN poisons effect plugins) + for (uint32_t i = 0; i < numSamples; ++i) { + inL[i] = 0.0f; inR[i] = 0.0f; } // Build pointer arrays covering the plugin's full ins/outs: plugins with @@ -731,42 +657,7 @@ void Vst2Instrument::processAudioBlock(float* outputL, float* outputR, uint32_t } if (nIn == 0) inBufs[0] = nullptr; // ins=0: pass NULL input array entry - std::cerr << "[Vst2Instrument] processReplacing IN ch=" << channel_ << " n=" << numSamples - << " ins=" << nIn << " outs=" << nOut - << " inL=0x" << std::hex << (uintptr_t)inL << " inR=0x" << (uintptr_t)inR - << " outL=0x" << (uintptr_t)outputL << " outR=0x" << (uintptr_t)outputR - << std::dec << std::endl; fx->processReplacing(fx, inBufs.data(), outBufs.data(), (int32_t)numSamples); - std::cerr << "[Vst2Instrument] processReplacing OUT ch=" << channel_ << std::endl; - // TEMP diag: does the plugin write any audio? - { - static int diagBlk = 0; - float peakL = 0.f, peakR = 0.f; - for (uint32_t i = 0; i < numSamples; ++i) { - float a = outputL[i], b = outputR[i]; - if (a > peakL) peakL = a; else if (-a > peakL) peakL = -a; - if (b > peakR) peakR = b; else if (-b > peakR) peakR = -b; - } - if (peakL != 0.f || peakR != 0.f || diagBlk < 10) { - std::cerr << "[Vst2Instrument][diag] PEAK blk=" << diagBlk - << " n=" << numSamples << " L=" << peakL << " R=" << peakR << std::endl; - diagBlk++; - } - } - // TEMP diag: how far past n did DUNE write into the INPUT buffers? - { - int farL = -1, farR = -1; - for (uint32_t i = 0; i < 2097152; ++i) { - uint32_t bL, bR; - std::memcpy(&bL, &inL[numSamples + i], 4); - std::memcpy(&bR, &inR[numSamples + i], 4); - if (bL != 0x7FC00000u) farL = (int)i; - if (bR != 0x7FC00000u) farR = (int)i; - } - if (farL >= 0 || farR >= 0) - std::cerr << "[Vst2Instrument][diag] INPUT OVERRUN past n: L=" << farL - << " R=" << farR << std::endl; - } } const void* Vst2Instrument::effect() const { diff --git a/native_bridge/src/Vst3Instrument.cpp b/native_bridge/src/Vst3Instrument.cpp index 003f831..97415c8 100644 --- a/native_bridge/src/Vst3Instrument.cpp +++ b/native_bridge/src/Vst3Instrument.cpp @@ -321,12 +321,6 @@ bool Vst3Instrument::loadPlugin(const std::string& path, double sampleRate) { std::cerr << "[Vst3Instrument] setupProcessing failed" << std::endl; return false; } - if (component->setActive(true) != kResultOk) { - std::cerr << "[Vst3Instrument] setActive failed" << std::endl; - return false; - } - processor->setProcessing(true); - // Build per-bus buffers sized maxBlockSize_ (HostProcessData owns them; // we must NOT override channelBuffers with external pointers — unprepare // would delete[] them). @@ -335,6 +329,13 @@ bool Vst3Instrument::loadPlugin(const std::string& path, double sampleRate) { return false; } + + if (component->setActive(true) != kResultOk) { + std::cerr << "[Vst3Instrument] setActive failed" << std::endl; + return false; + } + processor->setProcessing(true); + // Count output channels of bus 0 (mono plugins duplicate ch0 → both outs). int32 outChannels = 2; if (component->getBusCount(kAudio, kOutput) > 0) { @@ -848,6 +849,14 @@ void Vst3Instrument::processAudioBlock(float* outputL, float* outputR, uint32_t std::memset(ob.channelBuffers32[c], 0, numSamples * sizeof(float)); } } + if (s->processData.numInputs > 0) { + for (int32 b = 0; b < s->processData.numInputs; ++b) { + const Steinberg::Vst::AudioBusBuffers& ib = s->processData.inputs[b]; + for (int32 c = 0; c < ib.numChannels; ++c) + if (ib.channelBuffers32[c]) + std::memset(ib.channelBuffers32[c], 0, numSamples * sizeof(float)); + } + } tresult pr = processor->process(s->processData); float mx = 0.f; if (pr >= 0 && s->processData.numOutputs > 0) { diff --git a/native_bridge/src/main_fx.cpp b/native_bridge/src/main_fx.cpp new file mode 100644 index 0000000..e7dbc5c --- /dev/null +++ b/native_bridge/src/main_fx.cpp @@ -0,0 +1,148 @@ +// native_bridge/src/main_fx.cpp +// Entry point of fx_vst_bridge.exe — VST FX host bridge (task: split FX out +// of daw_vst_bridge). Windows only. One-shot modes only: +// --render-fx --in --out +// --scan +// --open-fx-gui +// --fx-gui +// No SHM realtime loop: the realtime master FX chain (control type 6) stays +// in daw_vst_bridge because it processes the live instrument mix in-process. +#include "RenderFxJob.h" + +#ifdef _WIN32 +#include +#include +#include +#include +#include +#else +#include +#endif + +#include +#include +#include + +#ifdef _WIN32 +// Minidump on crash -> %TEMP%\sf_dumps (same convention as daw_vst_bridge). +static LONG WINAPI FxCrashDumpHandler(EXCEPTION_POINTERS* ep) { + static LONG once = 0; + if (InterlockedCompareExchange(&once, 1, 0)) return EXCEPTION_CONTINUE_SEARCH; + PEXCEPTION_RECORD er = ep ? ep->ExceptionRecord : nullptr; + std::cerr << "[CRASH] fx_vst_bridge exception code=0x" << std::hex + << (er ? er->ExceptionCode : 0) << " addr=0x" + << (er ? (void*)er->ExceptionAddress : nullptr) << std::dec << std::endl; + char dumpDir[MAX_PATH]; + if (GetTempPathA(MAX_PATH, dumpDir)) { + std::strncat(dumpDir, "sf_dumps", MAX_PATH - std::strlen(dumpDir) - 1); + CreateDirectoryA(dumpDir, nullptr); + char dumpPath[MAX_PATH]; + std::snprintf(dumpPath, sizeof(dumpPath), "%s/fx_bridge_crash_%lu.dmp", + dumpDir, GetCurrentProcessId()); + HMODULE hDbg = LoadLibraryA("dbghelp.dll"); + if (hDbg) { + typedef BOOL(WINAPI* MiniDumpWriteDumpFn)( + HANDLE, DWORD, HANDLE, MINIDUMP_TYPE, + PMINIDUMP_EXCEPTION_INFORMATION, PMINIDUMP_USER_STREAM_INFORMATION, + PMINIDUMP_CALLBACK_INFORMATION); + auto pMiniDumpWriteDump = + (MiniDumpWriteDumpFn)GetProcAddress(hDbg, "MiniDumpWriteDump"); + if (pMiniDumpWriteDump) { + HANDLE hFile = CreateFileA(dumpPath, GENERIC_WRITE, 0, nullptr, + CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, + nullptr); + if (hFile != INVALID_HANDLE_VALUE) { + MINIDUMP_EXCEPTION_INFORMATION mei = {}; + mei.ThreadId = GetCurrentThreadId(); + mei.ExceptionPointers = ep; + mei.ClientPointers = TRUE; + pMiniDumpWriteDump(GetCurrentProcess(), GetCurrentProcessId(), + hFile, MiniDumpNormal, &mei, nullptr, + nullptr); + CloseHandle(hFile); + } + } + } + } + return EXCEPTION_CONTINUE_SEARCH; +} + +static LONG WINAPI FxVectoredCrashHandler(PEXCEPTION_POINTERS ep) { + PEXCEPTION_RECORD er = ep ? ep->ExceptionRecord : nullptr; + DWORD code = er ? er->ExceptionCode : 0; + if (code != 0xC0000374 && code != 0xC0000005 && code != 0xC0000409) + return EXCEPTION_CONTINUE_SEARCH; + return FxCrashDumpHandler(ep); +} + +// Same daemon init as daw_vst_bridge main(): no console flash, no power +// throttling, per-monitor DPI (plugin renderers must not be OS-scaled), +// no default IME on the main thread (IME recursion fix). Redirected stdio +// (bridge.log pipe / SF_FXGUI_PORT reader) survives FreeConsole. +static void win_daemon_init() { + if (!std::getenv("SF_KEEP_CONSOLE")) FreeConsole(); +#ifndef PROCESS_POWER_THROTTLING_IGNORE_TIMER_RESOLUTION +#define PROCESS_POWER_THROTTLING_IGNORE_TIMER_RESOLUTION 0x00000004 +#endif +#ifndef PROCESS_POWER_THROTTLING_EXECUTION_SPEED +#define PROCESS_POWER_THROTTLING_EXECUTION_SPEED 0x00000001 +#endif + PROCESS_POWER_THROTTLING_STATE powerState = {}; + powerState.Version = PROCESS_POWER_THROTTLING_CURRENT_VERSION; + powerState.ControlMask = PROCESS_POWER_THROTTLING_IGNORE_TIMER_RESOLUTION | + PROCESS_POWER_THROTTLING_EXECUTION_SPEED; + powerState.StateMask = 0; + SetProcessInformation(GetCurrentProcess(), ProcessPowerThrottling, + &powerState, sizeof(powerState)); + SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2); + ImmAssociateContextEx(nullptr, nullptr, IACE_DEFAULT); +} +#endif // _WIN32 + +int main(int argc, char* argv[]) { +#ifdef _WIN32 + SetUnhandledExceptionFilter(FxCrashDumpHandler); + AddVectoredExceptionHandler(1, FxVectoredCrashHandler); + win_daemon_init(); +#endif + // stderr, not stdout: stdout is reserved for machine-readable output in + // one-shot modes (--scan JSON, --fx-gui "SF_FXGUI_PORT=..."). + std::cerr << "[FxBridge] Starting DAW FX Host Bridge..." << std::endl; + + std::string renderFxJob, renderFxIn, renderFxOut; + std::string scanDir; + std::string openFxGuiJob; + std::string fxGuiServerJob; + for (int i = 1; i < argc; ++i) { + if (std::strcmp(argv[i], "--render-fx") == 0 && i + 1 < argc) + renderFxJob = argv[i + 1]; + else if (std::strcmp(argv[i], "--in") == 0 && i + 1 < argc) + renderFxIn = argv[i + 1]; + else if (std::strcmp(argv[i], "--out") == 0 && i + 1 < argc) + renderFxOut = argv[i + 1]; + else if (std::strcmp(argv[i], "--scan") == 0 && i + 1 < argc) + scanDir = argv[i + 1]; + else if (std::strcmp(argv[i], "--open-fx-gui") == 0 && i + 1 < argc) + openFxGuiJob = argv[i + 1]; + else if (std::strcmp(argv[i], "--fx-gui") == 0 && i + 1 < argc) + fxGuiServerJob = argv[i + 1]; + } + if (!renderFxJob.empty()) { + if (renderFxIn.empty() || renderFxOut.empty()) { + std::cerr << "[RenderFxJob] --render-fx requires --in " + " --out " << std::endl; + return 1; + } + return run_render_fx_job(renderFxJob, renderFxIn, renderFxOut); + } + if (!scanDir.empty()) return run_scan_dir(scanDir); + if (!openFxGuiJob.empty()) return run_open_fx_gui(openFxGuiJob); + if (!fxGuiServerJob.empty()) return run_fx_gui_server(fxGuiServerJob); + + std::cerr << "fx_vst_bridge: no mode. Usage:\n" + " --render-fx --in --out \n" + " --scan \n" + " --open-fx-gui \n" + " --fx-gui " << std::endl; + return 1; +} diff --git a/package-lock.json b/package-lock.json index b8c3b4f..40d79bb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -700,9 +700,6 @@ "arm64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "Apache-2.0 OR MIT", "optional": true, "os": [ @@ -720,9 +717,6 @@ "arm64" ], "dev": true, - "libc": [ - "musl" - ], "license": "Apache-2.0 OR MIT", "optional": true, "os": [ @@ -740,9 +734,6 @@ "riscv64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "Apache-2.0 OR MIT", "optional": true, "os": [ @@ -760,9 +751,6 @@ "x64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "Apache-2.0 OR MIT", "optional": true, "os": [ @@ -780,9 +768,6 @@ "x64" ], "dev": true, - "libc": [ - "musl" - ], "license": "Apache-2.0 OR MIT", "optional": true, "os": [ diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index 3962f74..c746d3b 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -697,7 +697,8 @@ pub fn run() { .args(["/IM", "daw_engine.exe", "/T", "/F"]) .status(); let _ = std::process::Command::new("taskkill") - .args(["/IM", "daw_vst_bridge.exe", "/T", "/F"]) + .args(["/IM", "daw_vst_bridge.exe", "/T", "/F", + "/IM", "fx_vst_bridge.exe", "/T", "/F"]) .status(); window.app_handle().exit(0); } diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json index 5f8b54b..1f5083d 100644 --- a/src-tauri/tauri.conf.json +++ b/src-tauri/tauri.conf.json @@ -27,7 +27,7 @@ "binaries/libfluidsynth-3.dll": "libfluidsynth-3.dll", "binaries/jpeg62.dll": "jpeg62.dll" }, - "externalBin": ["binaries/daw_vst_bridge"], + "externalBin": ["binaries/daw_vst_bridge", "binaries/fx_vst_bridge"], "windows": { "nsis": { "installerHooks": "hooks.nsh" diff --git a/tools/bridge_state.backup.json b/tools/bridge_state.backup.json new file mode 100644 index 0000000..a528c05 --- /dev/null +++ b/tools/bridge_state.backup.json @@ -0,0 +1,71 @@ +{ + "version": 1, + "playing": true, + "playhead": 0.000000, + "instruments": [ + { + "channel": 0, + "type": 0, + "path": "C:\\Program Files\\Common Files\\VST3\\Nexus.vst3", + "presetBase64": "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", + "bank": 0, + "program": 0 + }, + { + "channel": 1, + "type": 0, + "path": "C:\\Program Files\\Common Files\\VST3\\Nexus.vst3", + "presetBase64": "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", + "bank": 0, + "program": 0 + }, + { + "channel": 2, + "type": 0, + "path": "C:\\Program Files\\Common Files\\VST3\\Nexus.vst3", + "presetBase64": "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", + "bank": 0, + "program": 0 + }, + { + "channel": 3, + "type": 0, + "path": "C:\\Program Files\\Common Files\\VST3\\Nexus.vst3", + "presetBase64": "AAAQ6E5FWFVTNC41IEJhbmsAAADtV2iCNIg0TaR+mxHgKsoOXv2jcqAQNOScFRZDxMEI8m1itAHCO+KiCCrQHZSFDWKtTo0/2J4X80y+ozq0oD/K6hWdRwl4h6/C1rO3om2BZ/c/wu4jmU3by0a4jjcGrGIc88cSP8vHrZ+5tLjZPMRt9FQtOK0mVIxtdlrX2CfWuD5e8cM6cnYCsFDhLzt3HnRADEVOQkMoAYFkDQ/qjSlVhOcsErbbzLUm+7KwgHAIqs2vxKwMsGC/9i0Xu8KkMwobyrtDeUgw+QQywrSUIvpLNrngxtJKciutF/vJfJen4ml/SAN7+91/srJ7kbczMzNcPEYVLa5KK8T+J+K/jfJ1srvvkK9fz8eOQwmllwayro27bCOO/PhbVAlS1gXW91D15LPY4q/EvM3M0rU4dSrxVCGsLJVYFxpZemjmA5GBbZ1aTyb3vTdXZ0aMV+P7H0D6PQACA64p1vTIpoQetSHodCpmuJtaaj1iMI94u2CfbL6syvsS6ArW4rq7KmXwVr0nafXHlp0KE6M38cC5DFxQ8hFA0Att/BQBlvJ17JjmusHHWsfV6WafPoowJGGk+Np1ImtT6qyjFGVgz1bBDaoXMnjr0BSj6fbUnQ82KInpWijTSIqaLElLcWvyOpis02CL5VhINZeVqu4Tt3zIl9g0fDKz7qu1GUNixE19XOTiRvPlDPslwXuEtR8jIsh0zAPnAujdoPSx/+dqLmzYFqiHXW9Gqmg8L1GdQBZ4DV/SXFg7lnydK1eLXrkfgMd4KLw8WKt3xfoPvsf27xWaKJKtrGwmMvGwekfE2S37jnhjyefsGf4iefnUKCw1e5BEOB/o/nZuYoEzNSIv2uY5tbhnW+26jQ9tn4jW0WsZVvlhNmush0YEO0WyBjjqvBDXKEF3BKs1OXHyosnFLYWAviLwLFETEx48lZTjyY71u+dV10WzI+U0RZZVmwrAaALjU72WRhHzHxWUZCrdgTBZQ9p9hntO7jkr8ibQYPzhJlDStQikEreZMEaSvOcWtadpO14rZPJTyrEBuCto50r1rH8YPJw61FVil3yo/HgPcyHlU81Xu5LMfhneWl5XMSlslPIoZJurfYziqENNkavShLkJ5vn2YP5gJxsCWwg4e/ZFMxkNPHCAtZUSx1mDfuKUjhUNHDzps15wa1rLklIvy7Kj0lf8lvGnxxcwJ4rnVGYSc1jFpJi5ruZCjnWNfNGqAa6vDybhPBt6K1Rd5kyqrOM95RgqL/YEYdsFA+FXbExmu+1mUKTyTBH+CBG99XvbvNGQR1js1S/mbHmiIWtYGqOHFc6MkPd4Y6vf+sthOL1IVulH2mbDGTu47m+YH7jFMe6ECMZOllGPN4678KoIDHD0mPFmXBL49f/QM4rQ3ZzkB+qoBNIcyHF2EA+1hzqLb4/Hy2ff/4Z+DgWbWOxJ5j2dwpmvk8jA9qZII1MdQV4Zh0LUIF25CrJdGqHhAO7m/31ndDWBYuxNF26z4fSHBj4MpvM2bIQ3Sy/CudTU9FXyBl4QNWKwB0AEPNTz54McG0KOwEUkHj3HmopQf7KzrjS0wWRNY4MEGEFChWnIkhVRTfknxxsjOBsqfw7I3k0ChUL/ehMAmPxqRxJ6AVf2+u3wXJvav5fU8sj/fGchTBkNdrGOLRYfsWuHNkTU9igcKKQfq772aK8gG4Tq7qFeA7Cc28tFJKhpIDR5cVJ8IyJ5Xv5hQNvIx20TOkrda6JnLO5lRITca27uHBa61+z/BpHiPjcwf5zJ9a+deElQBICnji13yJvR3FVX/aFy1UAXhk6t7clu9N/PSAIV7B8bE2QK1d+/mJ9xEb5zzQizAfKsMf/yNpgEEPBJL1jDyt8esD89B+uosTqZtfQF7XUD6s8GKPYbACdUnDHma56dArqeW0LTr5gVE1stE+Y4MnGXzffivj7TA1r6+xPKb30/efvl8shDQTee8vZW/OFANzqQTjy5phhcBqRtKYn7IzBMPtatLnO9YTd4cHR5AooY0EtrwrT7VKn8E7oZOcp2aucT6VmrdZuGdKHORF1VPIjBn9jLFWP4p76z4C/xBAGMnH/gYsBpOmfUidm/IkeP2zd/47be8JXM4cGX8HPejKqaERkV2kaKCXSbnoRLaV2gq1AuGLmyHNnKaVu1ftThc6t0xsbWGE4K7cP725aEzLqm4mzlSh0hytEpMXId6Ks9e9VuA0mnKCNd8Y1VU1RaMbl1HMdEuJ4YPOVfF2wVJ0xx4QvlRYQax+TRFRSd9371tR7zGlls53neh25k4v4Hu/iOwVJs8ge1wGh3SIvXpfu+NY8d4Z4Dzczgq+z5joGsArrzaAPN9WNQRfCo5NnLvbFs65BrhnC/SdOfIl06va7PdVVPhxPz9V/wCcM6oeEyzBe8u1O8mvSivlrxhcl15Sdjw9tAmku1NgVXLr1srovaB6lm25rPa2JXgNiuGQisMWX/X6EDFWOkd2wRPmDWSdMN9m4bJSKFbUscniXiyWa49QscsGm4+IRJpuyvO8MSiCXKRBEdFmOpDKe7Bb1Fct+N5Jf7udAEXYNBaG4BQm7G0OgiJLqv2YRZItQvD/hahLL+8M8Vio7xJpiTHQyj9kRPqM/LU99WBnjXyVfaqFBjN8NczP7nKd0ZcPB1CaccPP3kD6L4QJllvDuziwnjoi9BdC3yle+qjBvmgWiQAL21hAwoMMy5u4ZCK4ybdC7tmitRbZTrcEJn3Pe7goDn/kQ3eRViPazktfXOkxVvu5OnG3Hvv0kdHC3QgJe/kOv4wZNSZyMsP6v7/CC0oi2rE17Bwo4xmDw7BESO/MbqOFmN34SjU3/du534K22jXt1n67SP9UgWnRXCoXqA4IFGcRDcPldKgQkU8S3TeKyvLQ4vsEWceD75xt0Xb6eUyapj2GU99JAo8BOdBNB8Q6s+pWdyLbbboBbxpmf8i15ecGqTDGF2sTgXIzyXyRVVKJRtrpOGVldJuPzxXi7G/YKAXnJst/d932UjjYq0Q5HkntQ4PZzJ9K3HT0I122d/cJZWm7feSS57aa7prOK+am0eaQB+uwSCRrAjcNtwN6lgf3BA5YaRtJBz9ouT7HF3iVuoTt1SknnaQ8wxd2JF2wZtm4hSglKGgdV5znQSDfauKReANipp+1CvMUNJgz4HzVWePdwrolCTkNDTm8PsMQWa9dJ/rhSRyWPSfBT77XMv8z3ZQTJc1tN4O17xeNogfNR7/Nk0AjeLJ4LlZbvCgDPRH3QwjcI8tNyjjTRnpu9uz3FakdxloSwYveKq1cczsY/Y/STYRl+Q9e+m2Ci205WtE1noj6tA2BToi6ixxxJA8mzPh5tpuWIdzwPfwhRBV/bhdmgt3S5Vt99OFgPbM/suUl3lTsW/EceAEPeumBgnU6t2wTKAPY6JFX1qpFfnt8+RLOwZnRaVadb1D4YbOz+I7+qgXPUHMeizSYdRefjOzwZTV7saOW3PNwkkVCcyU9+VSIn4eTaDGiB43IiOtyprNTppbV75R4aJoLOyvmbShYqnIj9fvXGJVyySqUG3fPZuKJsiDKKGAjrjlhF5FEnZGXwIChS23aOMe/1YkeybKhKO8+2KPs/cruhCMcsDXcxaV/6YkqTtaXMDxlSSM8y7FTEmLQtaVTef++ep4XCkpvaWxtkOhE9o3BMEljPDkerxCqqfoXvxPMdnV2x5PKjbSXt+mTxcK28ojE0t4COrbssnhmMyyNnnedZiqBjfqE9CuHzsHzlQ24ErgdPmqYkrdGkjSqSj1ROXc1ct2pxpT3ZpAnNaWpLmUMXoU8dhYllnofl4TBlXZg1d2Wxl/EMKwUGDDWmqugNCPCfFNCQGg/FiuzdRwN6miAbAGcNqNzlgzvBOWHZ8dgvZ4fbwTu+YnYurokEERMCWnyqNTjIw607M1vvg2oXEq5pM5iFDKZV94OaYOjGx42fHz1jQWBNg8MowwkxJDvv7+Pj3aiq5r2tQIQ0UVcKm1hUOnAHAwMTLjNve9f5ZVG9ji7/Ngm5MbVPWs3LiguCqxChVrGbe/5KF9Q0mTvMl21L8SRnH3GxohEvpnJrkuQayNmBwXxywmoXUJqXl24CDvd8g61qBkGDYolXY6Nep3ZAVQOvBQqKmODEizGSPNwHohbEXijH7YanM5m4ayiRiRhoAqJcTluHNTAXfaDYf9HPT0wkIAHt3uiGF+lsaErD5BHClr+NhrUw9tprGLhWm4RnlM99LoRtZXWwcUy7prSkph5xyuAm+U37bBoYxqmCUFC1Gb/Bp4wTrXGbujFivQG/Xw+p1LebZeXGglRdLxTxps/YlhKVHxlZXWXm+7PS5JfiNLmfigJxGfu5Exygp96exMqSzQU/XfarC4BLecf5wM3cz4Xe1f6UsQsbje4z9Z18xh30bMNlBlSVrX9Bg37PRr2DpncvSYbrXS4pk+YITdbb6NJUrt4fdCZNxNjChjvd/c5QCVUw2UddcUpGD5+7A7RjIGiu7vX9LnlMImqIZ3DUIrYxr9sOunxn7FOM8WENR6e5ZH5A9ff5PT4eIMC0q3xZQYJmuwGE+p1uTnRC5h+9FsmjE124QL7djKCTbQOnpjX0sW3DsvM+H2i+chqmdcoGVzr7OQpUCfVugsZhumQoQC8wlgRdugD8AD6KmdBEFmLnabxBc7BqO2quLmtQxqSDdk/MbALXCUxHIOj272xZVNeIJwNbJBcwC7aigpDDNSolA1Clev82dFDEvr/yxOhk3SjEA/KlKd2Ed5z5bZ32lR3jF6SUZU2teymYLt7R6dUMIZ2L8hljG54eUd/tLCDL3IW32ja8CsYpDebgSmPsDrAESfHzCIVl25DaDzFqDXLM6PcbLkjdpoKaAEA7VPdMf9pPv4iItTnrCEnH8UMm0awOwiwci9Jpg7dfGDGs1qJkIVpaetMDRjueoCoUln9tS4pwCLyJgZIexoNldlGhNe+uicxFRMrKJ4zJ7X6ikR6Cu1bwsp++iYOvxwyYaPacieH1qnl49I2jGap+ajgBKtffNaF+Krt4LxP5+dSNBpzPcgnE3ruxG1IJpq+NhvUQSgnOVB2dDGLGpz4opYoQV12XSs7ToNbZszLhIg660XbjPZTYpM5M2bpA+cSjDxxDxquTodBic3cBthPLmLrVD7JmWjHJtbKD702iilDrZsuPdCKVwPd9sQAr0h8FiyxDpxA1Y7+XrSEFefXmVXVN2eSBR3X6Kl7VeRLEK6+LYwDllrbsXnLCO4c6kTWn0Goe3+ZemC6mI1O8R4yneyN25ERgf3jALRnT5AgNNmwp+4loC92B0IYR6cH89Xwso3ErF+BOoADfTEhJGo2xn8syYBK6kDhXqB1QgiFpySSMp63rcd3XXF4/wMIDczE1lSHk4Zihm2O8BhsZcKtNx/DjmH8s+c/aP7qkD+A2lf5k+2PxLgATM7KPy5hfvGP3CjKThzkdnqMQUIVFMPRXLmPqmyzFPAF4Nt8jkjvfPnFUIi1FwuKiUrz0ncw2/GpmOyIHc90ysymGm1/w6A9ko6R5WCXUnkwRTq1vp/W98+FLyM3zCjQ39/e7s/gQtGLM4/on90eD6e+F4Nk/nIPNv3S1nNsZ/FvtawHijIfogrDFJzWd/wbnd0TC31YUhcdNLLceij46A51RTxcETleop8XE5tytuIblz9K2rTe6tJ8+y1QqYAAAAAAAAAABKVUNFUHJpdmF0ZURhdGEAAQFCeXBhc3MAAQEDAB0AAAAAAAAASlVDRVByaXZhdGVEYXRhAAAAAA==", + "bank": 0, + "program": 0 + }, + { + "channel": 4, + "type": 0, + "path": "C:\\Program Files\\Common Files\\VST3\\Nexus.vst3", + "presetBase64": "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", + "bank": 0, + "program": 0 + }, + { + "channel": 5, + "type": 0, + "path": "C:\\Program Files\\Common Files\\VST3\\Toontrack\\EZkeys 2.vst3", + "presetBase64": "AABIiVNBTVBMRVIgewpzb3VuZGxpYiAiRVpLMi1TZXNzaW9uR3JhbmQiICJFWmtleXMgMiBTZXNzaW9uIEdyYW5kIgppbnN0cnVtZW50c2V0dXAgewoiUGlhbm8iICJGYXppIgoiS2V5Tm9pc2UiICJGYXppIgoiUGVkYWwiICJGYXppIgoiRXh0cmFQYWQiICIiCn0KcGFyYW0gcmVsZWFzZWNyb3NzIDAuMTEwMTUzCnBhcmFtIHBhdGhldGljcmVzIDAuMjUxMTg5Cm1peGVyIHsKbGF0Y29tcApIYW1tZXJzIHsKcGFuIDAKdm9sdW1lIDEKcGhhc2VyZXZlcnNlIDAKbm9ibGVlZCAwCnh0cmFmYWRlIDAKbGVha3ZvbCAxCnNvbG8gMAptdXRlIDAKcm91dGUgYnVzcyAwCn0KSGFtbWVyc1IgewpwYW4gMQp2b2x1bWUgMQpwaGFzZXJldmVyc2UgMApub2JsZWVkIDAKeHRyYWZhZGUgMApsZWFrdm9sIDEKc29sbyAwCm11dGUgMApyb3V0ZSBidXNzIDAKfQpTdHJpbmdzXzEyMSB7CnBhbiAwCnZvbHVtZSAwCnBoYXNlcmV2ZXJzZSAwCm5vYmxlZWQgMAp4dHJhZmFkZSAwCmxlYWt2b2wgMQpzb2xvIDAKbXV0ZSAwCnJvdXRlIG91dHB1dCAtMQp9ClN0cmluZ3NfMTIxUiB7CnBhbiAxCnZvbHVtZSAwCnBoYXNlcmV2ZXJzZSAwCm5vYmxlZWQgMAp4dHJhZmFkZSAwCmxlYWt2b2wgMQpzb2xvIDAKbXV0ZSAwCnJvdXRlIG91dHB1dCAtMQp9ClBpYW5pc3RPSCB7CnBhbiAwCnZvbHVtZSAwCnBoYXNlcmV2ZXJzZSAwCm5vYmxlZWQgMAp4dHJhZmFkZSAwCmxlYWt2b2wgMQpzb2xvIDAKbXV0ZSAwCnJvdXRlIG91dHB1dCAtMQp9ClBpYW5pc3RPSFIgewpwYW4gMQp2b2x1bWUgMApwaGFzZXJldmVyc2UgMApub2JsZWVkIDAKeHRyYWZhZGUgMApsZWFrdm9sIDEKc29sbyAwCm11dGUgMApyb3V0ZSBvdXRwdXQgLTEKfQpBbWJfQzI0IHsKcGFuIDAKdm9sdW1lIDAKcGhhc2VyZXZlcnNlIDAKbm9ibGVlZCAwCnh0cmFmYWRlIDAKbGVha3ZvbCAxCnNvbG8gMAptdXRlIDAKcm91dGUgb3V0cHV0IC0xCn0KQW1iX0MyNFIgewpwYW4gMQp2b2x1bWUgMApwaGFzZXJldmVyc2UgMApub2JsZWVkIDAKeHRyYWZhZGUgMApsZWFrdm9sIDEKc29sbyAwCm11dGUgMApyb3V0ZSBvdXRwdXQgLTEKfQpBbWJfS004NCB7CnBhbiAwCnZvbHVtZSAwCnBoYXNlcmV2ZXJzZSAwCm5vYmxlZWQgMAp4dHJhZmFkZSAwCmxlYWt2b2wgMQpzb2xvIDAKbXV0ZSAwCnJvdXRlIG91dHB1dCAtMQp9CkFtYl9LTTg0UiB7CnBhbiAxCnZvbHVtZSAwCnBoYXNlcmV2ZXJzZSAwCm5vYmxlZWQgMAp4dHJhZmFkZSAwCmxlYWt2b2wgMQpzb2xvIDAKbXV0ZSAwCnJvdXRlIG91dHB1dCAtMQp9ClBlZGFsX0ggewpwYW4gMAp2b2x1bWUgMC4wMzM0NTY1CnBoYXNlcmV2ZXJzZSAwCm5vYmxlZWQgMAp4dHJhZmFkZSAwCmxlYWt2b2wgMQpzb2xvIDAKbXV0ZSAwCnJvdXRlIGJ1c3MgMAp9ClBlZGFsX0hSIHsKcGFuIDEKdm9sdW1lIDAuMDMzNDU2NQpwaGFzZXJldmVyc2UgMApub2JsZWVkIDAKeHRyYWZhZGUgMApsZWFrdm9sIDEKc29sbyAwCm11dGUgMApyb3V0ZSBidXNzIDAKfQpOb2lzZV8xMjEgewpwYW4gMAp2b2x1bWUgMC40ODc3OTgKcGhhc2VyZXZlcnNlIDAKbm9ibGVlZCAwCnh0cmFmYWRlIDAKbGVha3ZvbCAxCnNvbG8gMAptdXRlIDAKcm91dGUgYnVzcyAwCn0KTm9pc2VfMTIxUiB7CnBhbiAxCnZvbHVtZSAwLjQ4Nzc5NgpwaGFzZXJldmVyc2UgMApub2JsZWVkIDAKeHRyYWZhZGUgMApsZWFrdm9sIDEKc29sbyAwCm11dGUgMApyb3V0ZSBidXNzIDAKfQpQYWRzIHsKcGFuIDAKdm9sdW1lIDAKcGhhc2VyZXZlcnNlIDAKbm9ibGVlZCAwCnh0cmFmYWRlIDAKbGVha3ZvbCAxCnNvbG8gMAptdXRlIDAKcm91dGUgb3V0cHV0IC0xCn0KUGFkc1IgewpwYW4gMQp2b2x1bWUgMApwaGFzZXJldmVyc2UgMApub2JsZWVkIDAKeHRyYWZhZGUgMApsZWFrdm9sIDEKc29sbyAwCm11dGUgMApyb3V0ZSBvdXRwdXQgLTEKfQpidXNzIDAgewpwYW4gMAp2b2x1bWUgMQpwaGFzZXJldmVyc2UgMAplZmZlY3QgMSB7CnNpZGVjaGFpbiAiIgplZmZlY3QgOAphY3RpdmUgMQpjaHVuayAxMDEwNCA3NTc4ClZuTjBWd0FBQUFnQUFBQUJBQUFBQUVOamJrc0FBQjJDUmtKRGFBQUFBQUpVVkZCSkFCUUZCQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFIT3BXUXpJaHBSd0FBRHcvZUcxc0lIWmxjbk5wYjI0OUlqRXVNQ0lnWlc1amIyUnBibWM5SWxWVVJpMDRJajgrSUR4cGJuUmxjbTVoYkY5eVpYQnlaWE5sYm5SaGRHbHZiaUJtYjNKdFlYUmZkbVZ5YzJsdmJqMGlNU0krUEdOMWNuSmxiblJmY0hKbGMyVjBJR05vWVc1blpXUTlJakVpUGp4d2NtVnpaWFFnWm05eWJXRjBYM1psY25OcGIyNDlJallpSUdsdVptOXliV0YwYVc5dVBTSWlQanhqYUdGdWJtVnNjejQ4WTJoaGJtNWxiRDQ4YzJ4dmRITStQR1ZtWm1WamRDQnBaRDBpVkVGZlJGVk5UVmtpUGp4d1lYSmhiV1YwWlhKelBqeHdZWEpoYldWMFpYSWdibUZ0WlQwaWNHOTNaWElpSUhWelpYSmZkbUZzZFdVOUlqRXVNQ0l2UGp3dmNHRnlZVzFsZEdWeWN6NDhMMlZtWm1WamRENDhaV1ptWldOMElHbGtQU0pVUVY5RlVWQXhRU0krUEhCaGNtRnRaWFJsY25NK1BIQmhjbUZ0WlhSbGNpQnVZVzFsUFNKd2IzZGxjaUlnZFhObGNsOTJZV3gxWlQwaU1TNHdJaTgrUEhCaGNtRnRaWFJsY2lCdVlXMWxQU0pwYmkxc1pYWWlJSFZ6WlhKZmRtRnNkV1U5SWpFdU9UYzJOelk0TkRrek5qVXlNelEwSWk4K1BIQmhjbUZ0WlhSbGNpQnVZVzFsUFNKc2IzY3RZbTl2YzNRaUlIVnpaWEpmZG1Gc2RXVTlJakF1TnpFek9UazVPVGcyTmpRNE5UVTVOaUl2UGp4d1lYSmhiV1YwWlhJZ2JtRnRaVDBpYkc5M0xXRjBkR1Z1SWlCMWMyVnlYM1poYkhWbFBTSXhMakE1TmpJNU1qZzFNek0xTlRRd09DSXZQanh3WVhKaGJXVjBaWElnYm1GdFpUMGliRzkzTFdOd2N5SWdkWE5sY2w5MllXeDFaVDBpTWk0d0lpOCtQSEJoY21GdFpYUmxjaUJ1WVcxbFBTSmlZVzVrZDJsMGFDSWdkWE5sY2w5MllXeDFaVDBpTlM0d0lpOCtQSEJoY21GdFpYUmxjaUJ1WVcxbFBTSm9hV2RvTFdKdmIzTjBJaUIxYzJWeVgzWmhiSFZsUFNJeExqUXlOems1T1RrM016STVOekV4T1NJdlBqeHdZWEpoYldWMFpYSWdibUZ0WlQwaWFHbG5hQzFoZEhSbGJpSWdkWE5sY2w5MllXeDFaVDBpTUM0d0lpOCtQSEJoY21GdFpYUmxjaUJ1WVcxbFBTSm9hV2RvTFd0amN5SWdkWE5sY2w5MllXeDFaVDBpTVM0d0lpOCtQSEJoY21GdFpYUmxjaUJ1WVcxbFBTSmhkSFJsYmkxelpXd2lJSFZ6WlhKZmRtRnNkV1U5SWpBdU1DSXZQanh3WVhKaGJXVjBaWElnYm1GdFpUMGliM1YwTFd4bGRpSWdkWE5sY2w5MllXeDFaVDBpTUM0MU1ESXhNRGsxTWpjMU9EYzRPVEEySWk4K1BDOXdZWEpoYldWMFpYSnpQand2WldabVpXTjBQanhsWm1abFkzUWdhV1E5SWxSQlgwUlZUVTFaSWo0OGNHRnlZVzFsZEdWeWN6NDhjR0Z5WVcxbGRHVnlJRzVoYldVOUluQnZkMlZ5SWlCMWMyVnlYM1poYkhWbFBTSXhMakFpTHo0OEwzQmhjbUZ0WlhSbGNuTStQQzlsWm1abFkzUStQR1ZtWm1WamRDQnBaRDBpVkVGZlJGVk5UVmtpUGp4d1lYSmhiV1YwWlhKelBqeHdZWEpoYldWMFpYSWdibUZ0WlQwaWNHOTNaWElpSUhWelpYSmZkbUZzZFdVOUlqRXVNQ0l2UGp3dmNHRnlZVzFsZEdWeWN6NDhMMlZtWm1WamRENDhaV1ptWldOMElHbGtQU0pVUVY5RVZVMU5XU0krUEhCaGNtRnRaWFJsY25NK1BIQmhjbUZ0WlhSbGNpQnVZVzFsUFNKd2IzZGxjaUlnZFhObGNsOTJZV3gxWlQwaU1TNHdJaTgrUEM5d1lYSmhiV1YwWlhKelBqd3ZaV1ptWldOMFBqd3ZjMnh2ZEhNK1BDOWphR0Z1Ym1Wc1BqeGphR0Z1Ym1Wc1BqeHpiRzkwY3o0OFpXWm1aV04wSUdsa1BTSlVRVjlTUlZaSVFVeE1JajQ4Y0dGeVlXMWxkR1Z5Y3o0OGNHRnlZVzFsZEdWeUlHNWhiV1U5SW5CdmQyVnlJaUIxYzJWeVgzWmhiSFZsUFNJeExqQWlMejQ4Y0dGeVlXMWxkR1Z5SUc1aGJXVTlJblJwYldVaUlIVnpaWEpmZG1Gc2RXVTlJakF1T0RjMk1qRTBPREF5TWpZMU1UWTNNaUl2UGp4d1lYSmhiV1YwWlhJZ2JtRnRaVDBpYzJsNlpTSWdkWE5sY2w5MllXeDFaVDBpTWpndU1qQTFOamsyTVRBMU9UVTNNRE1pTHo0OGNHRnlZVzFsZEdWeUlHNWhiV1U5SW1ScFptWjFjMmx2YmlJZ2RYTmxjbDkyWVd4MVpUMGlOVEF1TUNJdlBqeHdZWEpoYldWMFpYSWdibUZ0WlQwaWMyaGhjR1VpSUhWelpYSmZkbUZzZFdVOUlqWTJMamM1TVRjME1EUXhOelE0TURRM0lpOCtQSEJoY21GdFpYUmxjaUJ1WVcxbFBTSm9hV2RvSWlCMWMyVnlYM1poYkhWbFBTSXhMamcyTVRJMU9EazRNell4TWpBMk1TSXZQanh3WVhKaGJXVjBaWElnYm1GdFpUMGljSEpsWkdWc1lYa2lJSFZ6WlhKZmRtRnNkV1U5SWpBdU1DSXZQanh3WVhKaGJXVjBaWElnYm1GdFpUMGljSEpsWkdWc1lYa3RjM2x1WTJWa0lpQjFjMlZ5WDNaaGJIVmxQU0l3TGpBaUx6NDhjR0Z5WVcxbGRHVnlJRzVoYldVOUluQnlaV1JsYkdGNUxXSndiU0lnZFhObGNsOTJZV3gxWlQwaU5DNHdJaTgrUEhCaGNtRnRaWFJsY2lCdVlXMWxQU0ozWlhRaUlIVnpaWEpmZG1Gc2RXVTlJakV3TUM0d0lpOCtQSEJoY21GdFpYUmxjaUJ1WVcxbFBTSnNaWFpsYkNJZ2RYTmxjbDkyWVd4MVpUMGlNVEF3TGpBaUx6NDhMM0JoY21GdFpYUmxjbk0rUEM5bFptWmxZM1ErUEdWbVptVmpkQ0JwWkQwaVZFRmZSRlZOVFZraVBqeHdZWEpoYldWMFpYSnpQanh3WVhKaGJXVjBaWElnYm1GdFpUMGljRzkzWlhJaUlIVnpaWEpmZG1Gc2RXVTlJakV1TUNJdlBqd3ZjR0Z5WVcxbGRHVnljejQ4TDJWbVptVmpkRDQ4WldabVpXTjBJR2xrUFNKVVFWOUVWVTFOV1NJK1BIQmhjbUZ0WlhSbGNuTStQSEJoY21GdFpYUmxjaUJ1WVcxbFBTSndiM2RsY2lJZ2RYTmxjbDkyWVd4MVpUMGlNUzR3SWk4K1BDOXdZWEpoYldWMFpYSnpQand2WldabVpXTjBQanhsWm1abFkzUWdhV1E5SWxSQlgwUlZUVTFaSWo0OGNHRnlZVzFsZEdWeWN6NDhjR0Z5WVcxbGRHVnlJRzVoYldVOUluQnZkMlZ5SWlCMWMyVnlYM1poYkhWbFBTSXhMakFpTHo0OEwzQmhjbUZ0WlhSbGNuTStQQzlsWm1abFkzUStQR1ZtWm1WamRDQnBaRDBpVkVGZlJGVk5UVmtpUGp4d1lYSmhiV1YwWlhKelBqeHdZWEpoYldWMFpYSWdibUZ0WlQwaWNHOTNaWElpSUhWelpYSmZkbUZzZFdVOUlqRXVNQ0l2UGp3dmNHRnlZVzFsZEdWeWN6NDhMMlZtWm1WamRENDhMM05zYjNSelBqd3ZZMmhoYm01bGJENDhZMmhoYm01bGJENDhjMnh2ZEhNK1BHVm1abVZqZENCcFpEMGlWRUZmUkZWTlRWa2lQanh3WVhKaGJXVjBaWEp6UGp4d1lYSmhiV1YwWlhJZ2JtRnRaVDBpY0c5M1pYSWlJSFZ6WlhKZmRtRnNkV1U5SWpFdU1DSXZQand2Y0dGeVlXMWxkR1Z5Y3o0OEwyVm1abVZqZEQ0OFpXWm1aV04wSUdsa1BTSlVRVjlFVlUxTldTSStQSEJoY21GdFpYUmxjbk0rUEhCaGNtRnRaWFJsY2lCdVlXMWxQU0p3YjNkbGNpSWdkWE5sY2w5MllXeDFaVDBpTVM0d0lpOCtQQzl3WVhKaGJXVjBaWEp6UGp3dlpXWm1aV04wUGp4bFptWmxZM1FnYVdROUlsUkJYMFJWVFUxWklqNDhjR0Z5WVcxbGRHVnljejQ4Y0dGeVlXMWxkR1Z5SUc1aGJXVTlJbkJ2ZDJWeUlpQjFjMlZ5WDNaaGJIVmxQU0l4TGpBaUx6NDhMM0JoY21GdFpYUmxjbk0rUEM5bFptWmxZM1ErUEdWbVptVmpkQ0JwWkQwaVZFRmZSRlZOVFZraVBqeHdZWEpoYldWMFpYSnpQanh3WVhKaGJXVjBaWElnYm1GdFpUMGljRzkzWlhJaUlIVnpaWEpmZG1Gc2RXVTlJakV1TUNJdlBqd3ZjR0Z5WVcxbGRHVnljejQ4TDJWbVptVmpkRDQ4WldabVpXTjBJR2xrUFNKVVFWOUVWVTFOV1NJK1BIQmhjbUZ0WlhSbGNuTStQSEJoY21GdFpYUmxjaUJ1WVcxbFBTSndiM2RsY2lJZ2RYTmxjbDkyWVd4MVpUMGlNUzR3SWk4K1BDOXdZWEpoYldWMFpYSnpQand2WldabVpXTjBQand2YzJ4dmRITStQQzlqYUdGdWJtVnNQanhqYUdGdWJtVnNQanh6Ykc5MGN6NDhaV1ptWldOMElHbGtQU0pVUVY5RVZVMU5XU0krUEhCaGNtRnRaWFJsY25NK1BIQmhjbUZ0WlhSbGNpQnVZVzFsUFNKd2IzZGxjaUlnZFhObGNsOTJZV3gxWlQwaU1TNHdJaTgrUEM5d1lYSmhiV1YwWlhKelBqd3ZaV1ptWldOMFBqeGxabVpsWTNRZ2FXUTlJbFJCWDBSVlRVMVpJajQ4Y0dGeVlXMWxkR1Z5Y3o0OGNHRnlZVzFsZEdWeUlHNWhiV1U5SW5CdmQyVnlJaUIxYzJWeVgzWmhiSFZsUFNJeExqQWlMejQ4TDNCaGNtRnRaWFJsY25NK1BDOWxabVpsWTNRK1BHVm1abVZqZENCcFpEMGlWRUZmUkZWTlRWa2lQanh3WVhKaGJXVjBaWEp6UGp4d1lYSmhiV1YwWlhJZ2JtRnRaVDBpY0c5M1pYSWlJSFZ6WlhKZmRtRnNkV1U5SWpFdU1DSXZQand2Y0dGeVlXMWxkR1Z5Y3o0OEwyVm1abVZqZEQ0OFpXWm1aV04wSUdsa1BTSlVRVjlFVlUxTldTSStQSEJoY21GdFpYUmxjbk0rUEhCaGNtRnRaWFJsY2lCdVlXMWxQU0p3YjNkbGNpSWdkWE5sY2w5MllXeDFaVDBpTVM0d0lpOCtQQzl3WVhKaGJXVjBaWEp6UGp3dlpXWm1aV04wUGp4bFptWmxZM1FnYVdROUlsUkJYMFJWVFUxWklqNDhjR0Z5WVcxbGRHVnljejQ4Y0dGeVlXMWxkR1Z5SUc1aGJXVTlJbkJ2ZDJWeUlpQjFjMlZ5WDNaaGJIVmxQU0l4TGpBaUx6NDhMM0JoY21GdFpYUmxjbk0rUEM5bFptWmxZM1ErUEM5emJHOTBjejQ4TDJOb1lXNXVaV3crUEM5amFHRnVibVZzY3o0OFoyeHZZbUZzUGp4bFptWmxZM1FnYVdROUlsUkJYMGRNVDBKQlRDSStQSEJoY21GdFpYUmxjbk0rUEhCaGNtRnRaWFJsY2lCdVlXMWxQU0p3YjNkbGNpSWdkWE5sY2w5MllXeDFaVDBpTVM0d0lpOCtQSEJoY21GdFpYUmxjaUJ1WVcxbFBTSnBibDlzWlhZaUlIVnpaWEpmZG1Gc2RXVTlJakF1TUNJdlBqeHdZWEpoYldWMFpYSWdibUZ0WlQwaWMybGtaV05vWVdsdVgyOXVJaUIxYzJWeVgzWmhiSFZsUFNJd0xqQWlMejQ4Y0dGeVlXMWxkR1Z5SUc1aGJXVTlJbk5wWkdWamFHRnBibDl0YVhnaUlIVnpaWEpmZG1Gc2RXVTlJakF1TUNJdlBqeHdZWEpoYldWMFpYSWdibUZ0WlQwaVkyZ3NhVzRzYlhWMFpTSWdkWE5sY2w5MllXeDFaVDBpTUM0d0lpOCtQSEJoY21GdFpYUmxjaUJ1WVcxbFBTSmphQ3hoZFhneExHMTFkR1VpSUhWelpYSmZkbUZzZFdVOUlqQXVNQ0l2UGp4d1lYSmhiV1YwWlhJZ2JtRnRaVDBpWTJnc1lYVjRNaXh0ZFhSbElpQjFjMlZ5WDNaaGJIVmxQU0l3TGpBaUx6NDhjR0Z5WVcxbGRHVnlJRzVoYldVOUltTm9MR2x1TEhOdmJHOGlJSFZ6WlhKZmRtRnNkV1U5SWpBdU1DSXZQanh3WVhKaGJXVjBaWElnYm1GdFpUMGlZMmdzWVhWNE1TeHpiMnh2SWlCMWMyVnlYM1poYkhWbFBTSXdMakFpTHo0OGNHRnlZVzFsZEdWeUlHNWhiV1U5SW1Ob0xHRjFlRElzYzI5c2J5SWdkWE5sY2w5MllXeDFaVDBpTUM0d0lpOCtQSEJoY21GdFpYUmxjaUJ1WVcxbFBTSmphQ3hwYml4MmIyeDFiV1VpSUhWelpYSmZkbUZzZFdVOUlqQXVNQ0l2UGp4d1lYSmhiV1YwWlhJZ2JtRnRaVDBpWTJnc1lYVjRNU3gyYjJ4MWJXVWlJSFZ6WlhKZmRtRnNkV1U5SWkwek15NHhOakkwTkRFeU5UTTJOakl4TVNJdlBqeHdZWEpoYldWMFpYSWdibUZ0WlQwaVkyZ3NZWFY0TWl4MmIyeDFiV1VpSUhWelpYSmZkbUZzZFdVOUlqQXVNQ0l2UGp4d1lYSmhiV1YwWlhJZ2JtRnRaVDBpWTJnc2FXNHNjR0Z1SWlCMWMyVnlYM1poYkhWbFBTSXdMakFpTHo0OGNHRnlZVzFsZEdWeUlHNWhiV1U5SW1Ob0xHRjFlREVzY0dGdUlpQjFjMlZ5WDNaaGJIVmxQU0l3TGpBaUx6NDhjR0Z5WVcxbGRHVnlJRzVoYldVOUltTm9MR0YxZURJc2NHRnVJaUIxYzJWeVgzWmhiSFZsUFNJd0xqQWlMejQ4Y0dGeVlXMWxkR1Z5SUc1aGJXVTlJbU5vTEdsdUxIQm9ZWE5sSWlCMWMyVnlYM1poYkhWbFBTSXdMakFpTHo0OGNHRnlZVzFsZEdWeUlHNWhiV1U5SW1Ob0xHRjFlREVzY0doaGMyVWlJSFZ6WlhKZmRtRnNkV1U5SWpBdU1DSXZQanh3WVhKaGJXVjBaWElnYm1GdFpUMGlZMmdzWVhWNE1peHdhR0Z6WlNJZ2RYTmxjbDkyWVd4MVpUMGlNQzR3SWk4K1BIQmhjbUZ0WlhSbGNpQnVZVzFsUFNKaGRYaHpaVzVrTEdsdVgyRjFlREVzYkdWMlpXd2lJSFZ6WlhKZmRtRnNkV1U5SWpBdU1DSXZQanh3WVhKaGJXVjBaWElnYm1GdFpUMGlZWFY0YzJWdVpDeHBibDloZFhneUxHeGxkbVZzSWlCMWMyVnlYM1poYkhWbFBTSXRNVEF3TUM0d0lpOCtQSEJoY21GdFpYUmxjaUJ1WVcxbFBTSmhkWGh6Wlc1a0xHRjFlREZmWVhWNE1peHNaWFpsYkNJZ2RYTmxjbDkyWVd4MVpUMGlMVEV3TURBdU1DSXZQanh3WVhKaGJXVjBaWElnYm1GdFpUMGlZWFY0YzJWdVpDeHBibDloZFhneExITndiR2wwSWlCMWMyVnlYM1poYkhWbFBTSTFMakFpTHo0OGNHRnlZVzFsZEdWeUlHNWhiV1U5SW1GMWVITmxibVFzYVc1ZllYVjRNaXh6Y0d4cGRDSWdkWE5sY2w5MllXeDFaVDBpTVRBd01DNHdJaTgrUEhCaGNtRnRaWFJsY2lCdVlXMWxQU0poZFhoelpXNWtMR0YxZURGZllYVjRNaXh6Y0d4cGRDSWdkWE5sY2w5MllXeDFaVDBpTVRBd01DNHdJaTgrUEhCaGNtRnRaWFJsY2lCdVlXMWxQU0pzWm04c01DeDNZWFpsWm05eWJTSWdkWE5sY2w5MllXeDFaVDBpTUM0d0lpOCtQSEJoY21GdFpYUmxjaUJ1WVcxbFBTSnNabThzTUN4emNHVmxaQ0lnZFc1emVXNWpaV1JmZFhObGNsOTJZV3gxWlQwaU1TNHdJaUJ6ZVc1alpXUmZkWE5sY2w5MllXeDFaVDBpTUM0d0lpQnplVzVqWDNWelpYSmZkbUZzZFdVOUlqQXVNQ0l2UGp4d1lYSmhiV1YwWlhJZ2JtRnRaVDBpYkdadkxEQXNaR1Z3ZEdnaUlIVnpaWEpmZG1Gc2RXVTlJakkxTGpBaUx6NDhjR0Z5WVcxbGRHVnlJRzVoYldVOUlteG1ieXd3TEhCaGNtRnRJaUIyWVd4MVpUMGlJaTgrUEhCaGNtRnRaWFJsY2lCdVlXMWxQU0pzWm04c01TeDNZWFpsWm05eWJTSWdkWE5sY2w5MllXeDFaVDBpTUM0d0lpOCtQSEJoY21GdFpYUmxjaUJ1WVcxbFBTSnNabThzTVN4emNHVmxaQ0lnZFc1emVXNWpaV1JmZFhObGNsOTJZV3gxWlQwaU1TNHdJaUJ6ZVc1alpXUmZkWE5sY2w5MllXeDFaVDBpTUM0d0lpQnplVzVqWDNWelpYSmZkbUZzZFdVOUlqQXVNQ0l2UGp4d1lYSmhiV1YwWlhJZ2JtRnRaVDBpYkdadkxERXNaR1Z3ZEdnaUlIVnpaWEpmZG1Gc2RXVTlJakkxTGpBaUx6NDhjR0Z5WVcxbGRHVnlJRzVoYldVOUlteG1ieXd4TEhCaGNtRnRJaUIyWVd4MVpUMGlJaTgrUEhCaGNtRnRaWFJsY2lCdVlXMWxQU0pzWm04c01peDNZWFpsWm05eWJTSWdkWE5sY2w5MllXeDFaVDBpTUM0d0lpOCtQSEJoY21GdFpYUmxjaUJ1WVcxbFBTSnNabThzTWl4emNHVmxaQ0lnZFc1emVXNWpaV1JmZFhObGNsOTJZV3gxWlQwaU1TNHdJaUJ6ZVc1alpXUmZkWE5sY2w5MllXeDFaVDBpTUM0d0lpQnplVzVqWDNWelpYSmZkbUZzZFdVOUlqQXVNQ0l2UGp4d1lYSmhiV1YwWlhJZ2JtRnRaVDBpYkdadkxESXNaR1Z3ZEdnaUlIVnpaWEpmZG1Gc2RXVTlJakkxTGpBaUx6NDhjR0Z5WVcxbGRHVnlJRzVoYldVOUlteG1ieXd5TEhCaGNtRnRJaUIyWVd4MVpUMGlJaTgrUEhCaGNtRnRaWFJsY2lCdVlXMWxQU0pzWm04c015eDNZWFpsWm05eWJTSWdkWE5sY2w5MllXeDFaVDBpTUM0d0lpOCtQSEJoY21GdFpYUmxjaUJ1WVcxbFBTSnNabThzTXl4emNHVmxaQ0lnZFc1emVXNWpaV1JmZFhObGNsOTJZV3gxWlQwaU1TNHdJaUJ6ZVc1alpXUmZkWE5sY2w5MllXeDFaVDBpTUM0d0lpQnplVzVqWDNWelpYSmZkbUZzZFdVOUlqQXVNQ0l2UGp4d1lYSmhiV1YwWlhJZ2JtRnRaVDBpYkdadkxETXNaR1Z3ZEdnaUlIVnpaWEpmZG1Gc2RXVTlJakkxTGpBaUx6NDhjR0Z5WVcxbGRHVnlJRzVoYldVOUlteG1ieXd6TEhCaGNtRnRJaUIyWVd4MVpUMGlJaTgrUEM5d1lYSmhiV1YwWlhKelBqd3ZaV1ptWldOMFBqd3ZaMnh2WW1Gc1BqeHZkWFErUEdWbVptVmpkQ0JwWkQwaVZFRmZUMVZVVUZWVUlqNDhjR0Z5WVcxbGRHVnljejQ4Y0dGeVlXMWxkR1Z5SUc1aGJXVTlJbkJ2ZDJWeUlpQjFjMlZ5WDNaaGJIVmxQU0l4TGpBaUx6NDhjR0Z5WVcxbGRHVnlJRzVoYldVOUltTm9MRzkxZEN4MmIyeDFiV1VpSUhWelpYSmZkbUZzZFdVOUlqSXVNRE0yTXpNME9Ua3hORFUxTURjNElpOCtQSEJoY21GdFpYUmxjaUJ1WVcxbFBTSmphQ3h2ZFhRc2NHRnVJaUIxYzJWeVgzWmhiSFZsUFNJd0xqQWlMejQ4Y0dGeVlXMWxkR1Z5SUc1aGJXVTlJbU5vTEc5MWRDeGllWEJoYzNNaUlIVnpaWEpmZG1Gc2RXVTlJakF1TUNJdlBqd3ZjR0Z5WVcxbGRHVnljejQ4TDJWbVptVmpkRDQ4TDI5MWRENDhjbVZ6WlhKMlpXUmZkR0ZuY3o0OGRHRm5JRzVoYldVOUlrbHVjM1J5ZFcxbGJuUWlJSFpoYkhWbFBTSWlMejQ4ZEdGbklHNWhiV1U5SWxSNWNHVWlJSFpoYkhWbFBTSWlMejQ4ZEdGbklHNWhiV1U5SWxCeWIyUjFZMlZ5SWlCMllXeDFaVDBpSWk4K1BIUmhaeUJ1WVcxbFBTSkhaVzV5WlNJZ2RtRnNkV1U5SWlJdlBqd3ZjbVZ6WlhKMlpXUmZkR0ZuY3o0OGRYTmxjbDkwWVdkekx6NDhlR1poWkdWeUlIWmhiSFZsUFNJd0xqSTFORGs1T1RrNU5USXpNVFl5T0RRaUlHeGhZbVZzUFNKQ2IyUjVJaUJ2Wm1ac1lXSmxiRDBpSWlCdmJteGhZbVZzUFNJaUlIUjVjR1U5SW1aaFpHVnlJajQ4YVc1MFpYSndiMnhoZEdsdmJpQndZWEpoYlQwaU1Ec3hPMnh2ZHkxaWIyOXpkQ0lnYkdGaVpXdzlJa2xPSURFc0lFeHZkeUJDYjI5emRDSWdiV2x1UFNJd0xqQWlJRzFoZUQwaU1DNHlPREF3TURBd01ERXhPVEl3T1RJNUlpQmpkWEoyWlQwaU1DNHdJaTgrUEdsdWRHVnljRzlzWVhScGIyNGdjR0Z5WVcwOUlqQTdNVHRzYjNjdFlYUjBaVzRpSUd4aFltVnNQU0pKVGlBeExDQk1iM2NnUVhSMFpXNGlJRzFwYmowaU1DNHdJaUJ0WVhnOUlqQXVOREk1T1RFNE56azFPREkwTURVd09TSWdZM1Z5ZG1VOUlqQXVNQ0l2UGp4cGJuUmxjbkJ2YkdGMGFXOXVJSEJoY21GdFBTSXdPekU3YjNWMExXeGxkaUlnYkdGaVpXdzlJa2xPSURFc0lFOTFkQ0JNWlhabGJDSWdiV2x1UFNJd0xqVWlJRzFoZUQwaU1DNDFOalUyTXpVeU5qTTVNVGs0TXpBeklpQmpkWEoyWlQwaU1DNHdJaTgrUEM5NFptRmtaWEkrUEhobVlXUmxjaUIyWVd4MVpUMGlNQzR5TURrNU9UazVPVE0wTkRNME9Ea3hJaUJzWVdKbGJEMGlRbkpwYkd4cFlXNWpaU0lnYjJabWJHRmlaV3c5SWlJZ2IyNXNZV0psYkQwaUlpQjBlWEJsUFNKbVlXUmxjaUkrUEdsdWRHVnljRzlzWVhScGIyNGdjR0Z5WVcwOUlqQTdNVHRvYVdkb0xXSnZiM04wSWlCc1lXSmxiRDBpU1U0Z01Td2dTR2xuYUNCQ2IyOXpkQ0lnYldsdVBTSXdMakFpSUcxaGVEMGlNQzQyT0RBd01EQXdNRGN4TlRJMU5UYzBJaUJqZFhKMlpUMGlNQzR3SWk4K1BDOTRabUZrWlhJK1BIaG1ZV1JsY2lCMllXeDFaVDBpTUM0eU1UazVPVGs1T1RnNE1EYzVNRGN4SWlCc1lXSmxiRDBpU0dGc2JDSWdiMlptYkdGaVpXdzlJaUlnYjI1c1lXSmxiRDBpSWlCMGVYQmxQU0ptWVdSbGNpSStQR2x1ZEdWeWNHOXNZWFJwYjI0Z2NHRnlZVzA5SWpFN0xURTdkbTlzZFcxbElpQnNZV0psYkQwaVFWVllNU0JXYjJ4MWJXVWlJRzFwYmowaU1DNHdJaUJ0WVhnOUlqQXVOams1T1RrNU9UZzRNRGM1TURjeElpQmpkWEoyWlQwaU1DNHlNVEk0TVRJME9UZ3lNVEU0TmpBM0lpOCtQQzk0Wm1Ga1pYSStQSGhtWVdSbGNpQjJZV3gxWlQwaU1DNHlNREF3TURBd01ESTVPREF5TXpJeUlpQnNZV0psYkQwaVUybDZaU0lnYjJabWJHRmlaV3c5SWlJZ2IyNXNZV0psYkQwaUlpQjBlWEJsUFNKbVlXUmxjaUkrUEdsdWRHVnljRzlzWVhScGIyNGdjR0Z5WVcwOUlqRTdNRHQwYVcxbElpQnNZV0psYkQwaVFWVllNU0F3TENCVWFXMWxJaUJ0YVc0OUlqQXVNRFF5T0RreE5qVXhNemt4T1Rnek1ETWlJRzFoZUQwaU1DNHpOamd6T1RVek5UZ3pNalF3TlRBNUlpQmpkWEoyWlQwaU1DNHdJaTgrUEdsdWRHVnljRzlzWVhScGIyNGdjR0Z5WVcwOUlqRTdNRHR6YVhwbElpQnNZV0psYkQwaVFWVllNU0F3TENCVGFYcGxJaUJ0YVc0OUlqQXVNakkwTXpVeU16ZzVOVGMwTURVd09TSWdiV0Y0UFNJd0xqVXhNamczTlRFNU9UTXhOemt6TWpFaUlHTjFjblpsUFNJd0xqQWlMejQ4TDNobVlXUmxjajQ4Y0dGa00yUWdlRk50WVhKMFBTSXRNU0lnZVZOdFlYSjBQU0l0TVNJZ2VsTnRZWEowUFNJdE1TSXZQand2Y0hKbGMyVjBQand2WTNWeWNtVnVkRjl3Y21WelpYUStQQzlwYm5SbGNtNWhiRjl5WlhCeVpYTmxiblJoZEdsdmJqNEFBQUFBQUFBQUFBQktWVU5GVUhKcGRtRjBaVVJoZEdFQUFRRkNlWEJoYzNNQUFRRURBQjBBQUFBQUFBQUFTbFZEUlZCeWFYWmhkR1ZFWVhRQQowIDAuMjU1CjEgMC4yMQoyIDAuMjIKMyAwLjIKfQpzb2xvIDAKbXV0ZSAwCnJvdXRlIG91dHB1dCAwCn0KYnVzcyAxIHsKcGFuIDEKdm9sdW1lIDEKcGhhc2VyZXZlcnNlIDAKc29sbyAwCm11dGUgMApyb3V0ZSBvdXRwdXQgLTEKfQpidXNzIDIgewpwYW4gMAp2b2x1bWUgMQpwaGFzZXJldmVyc2UgMApzb2xvIDAKbXV0ZSAwCnJvdXRlIG91dHB1dCAtMQp9CmJ1c3MgMyB7CnBhbiAxCnZvbHVtZSAxCnBoYXNlcmV2ZXJzZSAwCnNvbG8gMAptdXRlIDAKcm91dGUgb3V0cHV0IC0xCn0KYnVzcyA0IHsKcGFuIDAKdm9sdW1lIDEKcGhhc2VyZXZlcnNlIDAKc29sbyAwCm11dGUgMApyb3V0ZSBvdXRwdXQgLTEKfQpidXNzIDUgewpwYW4gMQp2b2x1bWUgMQpwaGFzZXJldmVyc2UgMApzb2xvIDAKbXV0ZSAwCnJvdXRlIG91dHB1dCAtMQp9CmJ1c3MgNiB7CnBhbiAwCnZvbHVtZSAxCnBoYXNlcmV2ZXJzZSAwCnNvbG8gMAptdXRlIDAKcm91dGUgb3V0cHV0IC0xCn0KYnVzcyA3IHsKcGFuIDEKdm9sdW1lIDEKcGhhc2VyZXZlcnNlIDAKc29sbyAwCm11dGUgMApyb3V0ZSBvdXRwdXQgLTEKfQpidXNzIDggewpwYW4gMAp2b2x1bWUgMQpwaGFzZXJldmVyc2UgMApzb2xvIDAKbXV0ZSAwCnJvdXRlIG91dHB1dCAtMQp9CmJ1c3MgOSB7CnBhbiAxCnZvbHVtZSAxCnBoYXNlcmV2ZXJzZSAwCnNvbG8gMAptdXRlIDAKcm91dGUgb3V0cHV0IC0xCn0KYnVzcyAxMCB7CnBhbiAwCnZvbHVtZSAxCnBoYXNlcmV2ZXJzZSAwCnNvbG8gMAptdXRlIDAKcm91dGUgb3V0cHV0IC0xCn0KYnVzcyAxMSB7CnBhbiAxCnZvbHVtZSAxCnBoYXNlcmV2ZXJzZSAwCnNvbG8gMAptdXRlIDAKcm91dGUgb3V0cHV0IC0xCn0KYnVzcyAxMiB7CnBhbiAwCnZvbHVtZSAxCnBoYXNlcmV2ZXJzZSAwCnNvbG8gMAptdXRlIDAKcm91dGUgb3V0cHV0IC0xCn0KYnVzcyAxMyB7CnBhbiAxCnZvbHVtZSAxCnBoYXNlcmV2ZXJzZSAwCnNvbG8gMAptdXRlIDAKcm91dGUgb3V0cHV0IC0xCn0KYnVzcyAxNCB7CnBhbiAwCnZvbHVtZSAxCnBoYXNlcmV2ZXJzZSAwCnNvbG8gMAptdXRlIDAKcm91dGUgb3V0cHV0IC0xCn0KYnVzcyAxNSB7CnBhbiAxCnZvbHVtZSAxCnBoYXNlcmV2ZXJzZSAwCnNvbG8gMAptdXRlIDAKcm91dGUgb3V0cHV0IC0xCn0KYnVzcyAxNiB7CnBhbiAwCnZvbHVtZSAxCnBoYXNlcmV2ZXJzZSAwCnNvbG8gMAptdXRlIDAKcm91dGUgb3V0cHV0IC0xCn0KYnVzcyAxNyB7CnBhbiAxCnZvbHVtZSAxCnBoYXNlcmV2ZXJzZSAwCnNvbG8gMAptdXRlIDAKcm91dGUgb3V0cHV0IC0xCn0KYnVzcyAxOCB7CnBhbiAwCnZvbHVtZSAxCnBoYXNlcmV2ZXJzZSAwCnNvbG8gMAptdXRlIDAKcm91dGUgb3V0cHV0IC0xCn0KYnVzcyAxOSB7CnBhbiAxCnZvbHVtZSAxCnBoYXNlcmV2ZXJzZSAwCnNvbG8gMAptdXRlIDAKcm91dGUgb3V0cHV0IC0xCn0KYnVzcyAyMCB7CnBhbiAwCnZvbHVtZSAxCnBoYXNlcmV2ZXJzZSAwCnNvbG8gMAptdXRlIDAKcm91dGUgb3V0cHV0IC0xCn0KYnVzcyAyMSB7CnBhbiAxCnZvbHVtZSAxCnBoYXNlcmV2ZXJzZSAwCnNvbG8gMAptdXRlIDAKcm91dGUgb3V0cHV0IC0xCn0KYnVzcyAyMiB7CnBhbiAwCnZvbHVtZSAxCnBoYXNlcmV2ZXJzZSAwCnNvbG8gMAptdXRlIDAKcm91dGUgb3V0cHV0IC0xCn0KYnVzcyAyMyB7CnBhbiAxCnZvbHVtZSAxCnBoYXNlcmV2ZXJzZSAwCnNvbG8gMAptdXRlIDAKcm91dGUgb3V0cHV0IC0xCn0KYnVzcyAyNCB7CnBhbiAwCnZvbHVtZSAxCnBoYXNlcmV2ZXJzZSAwCnNvbG8gMAptdXRlIDAKcm91dGUgb3V0cHV0IC0xCn0KYnVzcyAyNSB7CnBhbiAxCnZvbHVtZSAxCnBoYXNlcmV2ZXJzZSAwCnNvbG8gMAptdXRlIDAKcm91dGUgb3V0cHV0IC0xCn0KYnVzcyAyNiB7CnBhbiAwCnZvbHVtZSAxCnBoYXNlcmV2ZXJzZSAwCnNvbG8gMAptdXRlIDAKcm91dGUgb3V0cHV0IC0xCn0KYnVzcyAyNyB7CnBhbiAxCnZvbHVtZSAxCnBoYXNlcmV2ZXJzZSAwCnNvbG8gMAptdXRlIDAKcm91dGUgb3V0cHV0IC0xCn0KYnVzcyAyOCB7CnBhbiAwCnZvbHVtZSAxCnBoYXNlcmV2ZXJzZSAwCnNvbG8gMAptdXRlIDAKcm91dGUgb3V0cHV0IC0xCn0KYnVzcyAyOSB7CnBhbiAxCnZvbHVtZSAxCnBoYXNlcmV2ZXJzZSAwCnNvbG8gMAptdXRlIDAKcm91dGUgb3V0cHV0IC0xCn0KYnVzcyAzMCB7CnBhbiAwCnZvbHVtZSAxCnBoYXNlcmV2ZXJzZSAwCnNvbG8gMAptdXRlIDAKcm91dGUgb3V0cHV0IC0xCn0KYnVzcyAzMSB7CnBhbiAxCnZvbHVtZSAxCnBoYXNlcmV2ZXJzZSAwCnNvbG8gMAptdXRlIDAKcm91dGUgb3V0cHV0IC0xCn0Kb3V0cHV0IDAgewpwYW4gMAp2b2x1bWUgMQpwaGFzZXJldmVyc2UgMApzb2xvIDAKbXV0ZSAwCn0Kb3V0cHV0IDEgewpwYW4gMQp2b2x1bWUgMQpwaGFzZXJldmVyc2UgMApzb2xvIDAKbXV0ZSAwCn0Kb3V0cHV0IDIgewpwYW4gMAp2b2x1bWUgMQpwaGFzZXJldmVyc2UgMApzb2xvIDAKbXV0ZSAwCn0Kb3V0cHV0IDMgewpwYW4gMQp2b2x1bWUgMQpwaGFzZXJldmVyc2UgMApzb2xvIDAKbXV0ZSAwCn0Kb3V0cHV0IDQgewpwYW4gMAp2b2x1bWUgMQpwaGFzZXJldmVyc2UgMApzb2xvIDAKbXV0ZSAwCn0Kb3V0cHV0IDUgewpwYW4gMQp2b2x1bWUgMQpwaGFzZXJldmVyc2UgMApzb2xvIDAKbXV0ZSAwCn0Kb3V0cHV0IDYgewpwYW4gMAp2b2x1bWUgMQpwaGFzZXJldmVyc2UgMApzb2xvIDAKbXV0ZSAwCn0Kb3V0cHV0IDcgewpwYW4gMQp2b2x1bWUgMQpwaGFzZXJldmVyc2UgMApzb2xvIDAKbXV0ZSAwCn0KfQptaWRpbGVhcm4gewp9Cm1hY3JvcyB7Cmtub2IgMCB7Cm5hbWUgIkJvZHkiCmRlc2MgIkxvdyBmcmVxdWVuY3kgZW5oYW5jZXIiCmRlZiAwLjI1NQp0YXJnZXQgMCBidXNlZmYgYmFzaWMgewppeDMgMQpLbm9iVmFsdWUgMC4yNTUKfQp9Cmtub2IgMSB7Cm5hbWUgIkJyaWxsaWFuY2UiCmRlc2MgIkhpZ2ggZnJlcXVlbmN5IGVuaGFuY2VyIgpkZWYgMC4yMQp0YXJnZXQgMCBidXNlZmYgYmFzaWMgewppeDIgMQppeDMgMQpLbm9iVmFsdWUgMC4yMQp9Cn0Ka25vYiAyIHsKbmFtZSAiSGFsbCIKZGVzYyAiSGFsbCByZXZlcmIgbGV2ZWwiCmRlZiAwLjIyCnRhcmdldCAwIGJ1c2VmZiBiYXNpYyB7Cml4MiAyCml4MyAxCktub2JWYWx1ZSAwLjIyCn0KfQprbm9iIDMgewpuYW1lICJTaXplIgpkZXNjICJIYWxsIHJldmVyYiBzaXplIgpkZWYgMC4yCnRhcmdldCAwIGJ1c2VmZiBiYXNpYyB7Cml4MiAzCml4MyAxCktub2JWYWx1ZSAwLjIKfQp9Cmtub2IgNCB7Cm5hbWUgIkhhbW1lcnMiCmRlc2MgIkhhbW1lciBub2lzZSBsZXZlbCIKZGVmIDAuNjE5OQp0YXJnZXQgMCB2b2wgYmFzaWMgewppeDEgMTIKcmFuZ2UgMCAwLjg1NDE2NyAtMC40NQpLbm9iVmFsdWUgMC42MTk5Cn0KfQprbm9iIDUgewpuYW1lICJQZWRhbCIKZGVzYyAiU3VzdGFpbiBwZWRhbCBub2lzZSBsZXZlbCIKZGVmIDAuMzM3CnRhcmdldCAwIHZvbCBiYXNpYyB7Cml4MSAxMApyYW5nZSAwIDAuNjMxMzc1IDAuNTUKS25vYlZhbHVlIDAuMzM3Cn0KfQpwa25vYiAxNiB7Cm5hbWUgIkRZTkFNSUNTIgpkZXNjICJTZXQgdGhlIHZvbHVtZSBvZiB0aGUgc29mdCBub3RlcyIKZGVmIDAuNQp0YXJnZXQgMCBzb2Z0dmVsIGJhc2ljIHsKcmFuZ2UgMC41OCAwLjQyIDAKS25vYlZhbHVlIC0xCn0KfQprbm9iZ3JvdXAgMCB7Cm5hbWUgIkVRIgpraW5kIDAKcmFuZ2UgMCAxCn0Ka25vYmdyb3VwIDEgewpuYW1lICJIYWxsIFJldmVyYiIKa2luZCAwCnJhbmdlIDIgMwp9Cmtub2Jncm91cCAyIHsKbmFtZSAiTm9pc2UiCmtpbmQgMApyYW5nZSA0IDUKfQp9Cn0KbWFzdGVydm9sIDEKQ2xlYXJUcmFja3NGaXJzdApTb25nVHJhY2sgMCB7Ck1pbmltaXplZCAwCkNsZWFyVHJhY2tGaXJzdApUcmFja0xlbmd0aCAyMDAwLjA2ClZpc2libGVBcmVhIDAgNjUKWm9vbSAxMC4wMApUZW1wbyAxMjAgMCAwClRpbWVTaWcgNCA0IDAKS2V5U2lnbiAwIDEgMCAwIDAKVHJhY2tOYW1lICJUcmFjayAxIgpNdXRlZENDcyAKQ2xlYXJTb25nUGFydHMKR3JpZFJlcyAzMgpMb29wQXJlYSAwIDAKTG9vcEFjdGl2ZSAwClN0YXJ0TnVtYmVyIDEKUGxheUhlYWRQb3MgMTk0Mi4xMwpDaG9yZFBhbmVsIHsKQ2xlYXJDaG9yZHMKfQp9ClNldEN1cnJlbnRTb25nVHJhY2sgMAplZGl0b3JWYXJzIHsKc2VsZWN0ZWRUYWIgMQpNaWRpRWRpdFNuYXBNb2RlIDAKTWlkaUVkaXRTbmFwT24gMQpNaWRpRWRpdFNuYXBWYWx1ZSAwIDAKTWlkaUVkaXRBdXRvU2Nyb2xsIDEKTWlkaUVkaXRWYWx1ZXNTaXplIDEgMTUwIDMwCk1pZGlFZGl0Um93SGVpZ2h0IDE5Ck1pZGlFZGl0Wm9vbSA1MApNaWRpRWRpdFZpZXdQb3MgMCA0MDAKVmFsdWVzRWRpdG9yTW9kZSAwIDAKTWlkaUVkaXRUb29sIDAKRVBTVmlzaWJsZSAwCk1pZGlFZGl0U2NhbGVTbmFwT24gMApNaWRpRWRpdEhpZ2hsaWdodEtleXNDb250cm9sc09wZW4gMApNaWRpRWRpdEhpZ2hsaWdodEtleXNPbkZyb21TbmFwIDAKTWlkaUVkaXRDdXJyZW50SGlnaGxpZ2h0TW9kZSAyCk1pZGlFZGl0RGVmYXVsdEhpZ2hsaWdodE1vZGUgMgpNaWRpRWRpdG9yVHJhbnNwb3NlQ29udHJvbHNPcGVuIDAKU3RhcnRSZWNDb3VudCAwClN0YXJ0UmVjTWV0cm9ub21lIDAKU3RhcnRSZWNQbGF5IDAKQ3VycmVudFBlZGFsRWRpdG9yIDEKTWlkaUVkaXRIaWdobGlnaHRNb2RlT24gMApLZXlTaWduIDAgMSAwIDAgMApQcmVzZXROYW1lICJCYXNpYyIKUHJlc2V0QWx0ZXJlZCAwClByZXNldElzVXNlciAwCn0Kc3FsTWlkaVByb3ZpZGVyIHsKVmlzaWJsZVJlc3VsdENvbHVtbnMgOTszMSwzMDAsMCwwOzAsMTUwLDAsMDsxLDE3MCwwLDA7MTEsMTUwLDAsMDszLDc5LDAsMDs0LDEyMCwwLDA7Nyw4MCwwLDA7OCw4OCwwLDA7MiwxMDAsMCwwClZpc2libGVGaWx0ZXJDb2x1bW5zIDUsMiw0LDUsNyw4CkJyb3dzZXJIZWlnaHQgMjA4ClNlbGVjdGVkRmlsZSAtMQpzZWFyY2hlZFdvcmRJRHMgIiIKc2VhcmNoZWRXb3JkcyAiIgpCcm93c2VySXNGYXZvcml0ZSAwClNlbGVjdGVkVGFncyAiIgpFeGNsdWRlZFRhZ3MgIiIKVmVsb2NpdHkgMApTdHJldGNoIDEKT3JpZ2luYWxUZW1wbyAwCk9jdGF2ZSAwClNvbmdLZXkgMApFeHByZXNzaW9uIDAKQWRqdXN0VG9PcmdhbiAxIDEgMQpDbGVhclRhcDJGaW5kClNvcnRCeVByaW8gMQpTb3J0QnlUZW1wbyAxClNvcnRCeVRTaWcgMQp9CkJhbmRUYWIgewpTZWxlY3RlZEdlbnJlICIiClNlbGVjdGVkR3Jvb3ZlICIiClNlbGVjdGVkR3Jvb3ZlR2VucmUgIiIKU2VsZWN0ZWRQbGF5U3R5bGVGaWx0ZXIgNjY2ClNlbGVjdGVkVHlwZUZpbHRlciA2NjYKU2VsZWN0ZWRIYXJkbmVzc0ZpbHRlciA2NjYKU2VsZWN0ZWRFeHRyYUZpbHRlciA2NjYKU2VsZWN0ZWRUZW1wb0ZpbHRlciAwCklzT3JnYW4gMApJc1BhZCAwCklucHV0VHlwZSAwClpvb20gMTAKVHJhY2tMZW5ndGggODAwCkJhbmRNYXRlUGxheXNEcnVtcyAwClVzZU91dHB1dENob3JkcyAxCkdyb292ZVN0YXJ0UHBxIDAKR3Jvb3ZlRW5kUHBxIDAKR3Jvb3ZlQ29udGFpbmVyU3RhcnRUaW1lUHBxIDAKR3Jvb3ZlQ29udGFpbmVyRW5kVGltZVBwcSAwCklucHV0SGFzTWlkSW5mb0ZpbGUgMApDaG9yZFBhbmVsIHsKQ2xlYXJDaG9yZHMKfQpPdXRwdXRDaG9yZFBhbmVsIHsKQ2xlYXJDaG9yZHMKfQpTd2luZ0Ftb3VudCAwClN3aW5nUmVzIDAgMApFeHRyYVZlbG9jaXR5IDAKRXhwcmVzc2lvbiAwCk9jdGF2ZU1pblZhbHVlIC0zCk9jdGF2ZU1heFZhbHVlIDMKRXh0cmFPY3RhdmUgMApMYXN0UHJvY2Vzc2VkQW1vdW50VmFsdWUgMApBbW91bnRQcm9jZXNzaW5nVmFsdWUgMC41CkFtb3VudE1heFZhbHVlIDAuNTQKQW1vdW50TWluVmFsdWUgMC40ODUKQW1vdW50Q2xpY2tlZFZhbHVlIDAuNQpBZGp1c3RUb09yZ2FuIDEgMSAxCkxvb3BBcmVhU3RhcnQgMCAwCkxvb3BBcmVhRW5kIDAgMApJbnB1dFN3aW5nIDAKTGFzdExvYWRlZEVudHJ5ICB7CklzQk1Hcm9vdmUgMApJc1VzZXJHcm9vdmUgMApEYXRhYmFzZVBhdGggewp9Cn0KSW5wdXRUZW1wbyAxMjAKSW5wdXRUaW1lc2lnIDQgNApUZW1wb1R5cGUgMQpLZXlTaWduIDAgMSAwIDAgMApTeW50aFByZXNldCAiS2V5cyAtIFdhcm0iClBsYXlQb3MgMApJbnB1dFZvbHVtZSAwLjgKT3VwdXRWb2x1bWUgMQpDbGVhcklucHV0IApDbGVhck91dHB1dCAKfQpTeW5jVG9Ib3N0IDEKTWlkaU91dEVuYWJsZWQgMApNaWRpSW5DaGFubmVsIC0xClNUTWV0cm9ub21lT24gMApDb3VudEluIDAKSW5wdXRUcmFmbyB7IApsbyAwIDEKbG9saW0gMAp2ZWx0cmFmb29mZgp9CkJsb2NrUHJvZ3JhbSAxCk1ldHJvbm9tZVNldHRpbmdzIHsKTWV0cm9ub21lT3V0Q2hhbm5lbCAwCk1ldHJvbm9tZU5vdGVzIDAKTWV0cm9ub21lT25CZWF0U291bmQgMApNZXRyb25vbWVPZmZCZWF0U291bmQgMQpNZXRyb25vbWVPbkJlYXRWb2x1bWUgMQpNZXRyb25vbWVPZmZCZWF0Vm9sdW1lIDEKfQpPcmdhbkRyYXdiYXJQcmVzZXRNYW5hZ2VyIHsKfQoAAAAAAAAAAABKVUNFUHJpdmF0ZURhdGEAAQFCeXBhc3MAAQEDAB0AAAAAAAAASlVDRVByaXZhdGVEYXRhAAAAAA==", + "bank": 0, + "program": 0 + }, + { + "channel": 6, + "type": 2, + "path": "C:\\Users\\locpham\\Downloads\\Collections soundfont\\squidfont_orchestral.sf2", + "presetBase64": "", + "bank": 0, + "program": 46 + }, + { + "channel": 7, + "type": 0, + "path": "C:\\Program Files\\Common Files\\VST3\\Nexus.vst3", + "presetBase64": "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", + "bank": 0, + "program": 0 + } + ] +} diff --git a/tools/check_gui_fix.js b/tools/check_gui_fix.js new file mode 100644 index 0000000..6d75460 --- /dev/null +++ b/tools/check_gui_fix.js @@ -0,0 +1,43 @@ +// tools/check_gui_fix.js — self-check for the TCP GUI button fix (V9 regression). +// Run: node tools/check_gui_fix.js (exit 0 = OK) +// Verifies: guard exists in source, compiled bundle, and all 6 shipped copies +// are byte-identical to the compiled bundle; bundle is syntactically valid. +const { readFileSync } = require('fs'); +const { execFileSync } = require('child_process'); +const path = require('path'); +const root = path.join(__dirname, '..'); + +const SRC = path.join(root, 'app/static/js/app.jsx'); +const PRECOMPILED = path.join(root, 'app/static/js/app.precompiled.js'); +const GUARD_SRC = 'if (!loadOk && !window.__bridgeLoadedChannels[bch]) {'; +const GUARD_COMPILED = 'if(!loadOk&&!window.__bridgeLoadedChannels[bch]){'; +const COPIES = [ + 'dist/daw_engine/_internal/app/static/js/app.precompiled.js', + 'install/daw_engine/_internal/app/static/js/app.precompiled.js', + 'src-tauri/resources/daw_engine/_internal/app/static/js/app.precompiled.js', + 'src-tauri/target/debug/daw_engine/_internal/app/static/js/app.precompiled.js', + 'src-tauri/target/release/daw_engine/_internal/app/static/js/app.precompiled.js' +]; + +function assert(cond, msg) { + if (!cond) { console.error('FAIL: ' + msg); process.exit(1); } +} + +const src = readFileSync(SRC, 'utf8'); +assert(src.includes(GUARD_SRC), 'guard missing in app.jsx'); + +const compiled = readFileSync(PRECOMPILED, 'utf8'); +assert(compiled.includes(GUARD_COMPILED), 'guard missing in app.precompiled.js'); +assert(compiled.includes('__bridgePluginPaths[instrumentId]=knownPath') || compiled.includes('window.__bridgePluginPaths[instrumentId]=knownPath'), 'path cache write missing in precompiled'); +assert(src.includes('__bridgeLoadedState'), 'V10 loaded-state guard missing in app.jsx'); +assert(compiled.includes('__bridgeLoadedState'), 'V10 loaded-state guard missing in precompiled'); + +// Bundle must parse (Babel output is plain script). +execFileSync(process.execPath, ['--check', PRECOMPILED], { stdio: 'pipe' }); + +for (const c of COPIES) { + const p = path.join(root, c); + assert(readFileSync(p, 'utf8') === compiled, 'copy out of sync: ' + c); +} + +console.log('OK: GUI fix guard present in source + compiled + all ' + (COPIES.length + 1) + ' bundles; bundle parses.'); diff --git a/tools/fix_vst2_ext.py b/tools/fix_vst2_ext.py new file mode 100644 index 0000000..f76eb89 --- /dev/null +++ b/tools/fix_vst2_ext.py @@ -0,0 +1,22 @@ +import io + +p = 'app/api/v1/plugins.py' +s = io.open(p, encoding='utf-8', newline='').read() + +a = '".vst2" if instrument_type.upper() == "VST2" else' +b = '(".dll", ".so") if instrument_type.upper() == "VST2" else' +assert s.count(a) == 1, s.count(a) +s = s.replace(a, b) + +a2 = 'if fn.lower().endswith(ext) and \\' +b2 = 'if (fn.lower().endswith(ext) if isinstance(ext, tuple) else fn.lower().endswith(ext)) and \\' +assert s.count(a2) == 1, s.count(a2) +s = s.replace(a2, b2) + +a3 = 'if os.path.isdir(sp) and sub.lower().endswith(ext) and \\' +b3 = 'if os.path.isdir(sp) and (sub.lower().endswith(ext) if isinstance(ext, tuple) else sub.lower().endswith(ext)) and \\' +assert s.count(a3) == 1, s.count(a3) +s = s.replace(a3, b3) + +io.open(p, 'w', encoding='utf-8', newline='').write(s) +print('ok') diff --git a/tools/fix_vst_list_type.py b/tools/fix_vst_list_type.py new file mode 100644 index 0000000..227a10e --- /dev/null +++ b/tools/fix_vst_list_type.py @@ -0,0 +1,30 @@ +import io + +p = 'app/core/vst_engine.py' +s = io.open(p, encoding='utf-8', newline='').read() + +old = ''' def list_available(self) -> dict: + return { + "vst_instruments": [ + {"id": k, "name": k, "type": "VST3", "has_native_support": HAS_PEDALBOARD} + for k in self._scan_plugins().keys() + ], + "soundfonts": self._scan_soundfonts() + }''' + +new = ''' def list_available(self) -> dict: + plugins = self._scan_plugins() + return { + "vst_instruments": [ + {"id": k, "name": k, + "type": "VST3" if p.lower().endswith(".vst3") else "VST2", + "has_native_support": HAS_PEDALBOARD} + for k, p in plugins.items() + ], + "soundfonts": self._scan_soundfonts() + }''' + +assert old in s, 'not found' +s = s.replace(old, new) +io.open(p, 'w', encoding='utf-8', newline='').write(s) +print('ok') diff --git a/tools/parse_dump.py b/tools/parse_dump.py new file mode 100644 index 0000000..3cb5e0e --- /dev/null +++ b/tools/parse_dump.py @@ -0,0 +1,105 @@ +import struct, sys + +path = r"C:/Users/locpham/AppData/Local/Temp/sf_dumps/bridge_crash_1696.dmp" +data = open(path, "rb").read() + +sig, ver, numstreams, sdir = struct.unpack_from(" %s+%x" % (ex_tid, rip, n.split("\\")[-1] if n else "?", (rip - b) if b else 0)) + print("stack descriptor start=%016x rva=%x" % (st_start, st_rva)) + if st_rva: + stack = data[st_rva:st_rva + ctx_sz * 2] + print("stack bytes available:", len(stack)) + for j in range(0, min(len(stack), 16384) - 8, 8): + val = struct.unpack_from(" %s+%x" % (j, val, n2.split("\\")[-1], val - b2)) diff --git a/tools/patch_app_jsx_phase3.py b/tools/patch_app_jsx_phase3.py new file mode 100644 index 0000000..dadb746 --- /dev/null +++ b/tools/patch_app_jsx_phase3.py @@ -0,0 +1,345 @@ +# Phase 3 UI patch: VST FX chain per-track (FX Rack) + masterbus (Mastering modal). +# CRLF-safe: normalize to LF, patch, restore CRLF. +import io, sys + +PATH = 'app/static/js/app.jsx' + +def main(): + with io.open(PATH, encoding='utf-8', newline='') as f: + s = f.read() + crlf = '\r\n' in s + s = s.replace('\r\n', '\n') + orig = s + + def sub(old, new): + nonlocal s + if old not in s: + print('NOT FOUND:', old[:90].replace('\n', '\\n')) + sys.exit(1) + if s.count(old) > 1: + print('NOT UNIQUE:', old[:90].replace('\n', '\\n'), 'count=', s.count(old)) + sys.exit(1) + s = s.replace(old, new) + + # 1. serializeTracksList — add vst_fx_chain + vst_fx_bypass + sub( + " fx_chain: (t.fxChain || []).map(m => typeof m === 'string' ? { type: m, active: true } : { type: m.type, active: m.active !== false }),\n" + " server_file_id: t.serverFileId || null,", + " fx_chain: (t.fxChain || []).map(m => typeof m === 'string' ? { type: m, active: true } : { type: m.type, active: m.active !== false }),\n" + " vst_fx_chain: (t.vstFxChain || []).map(s => ({ type: s.type || 'vst3', path: s.path || '', name: s.name || '', preset_b64: s.preset_b64 || '', bypass: !!s.bypass })),\n" + " vst_fx_bypass: !!t.vstFxBypass,\n" + " server_file_id: t.serverFileId || null," + ) + + # 2. deserializeTracksList — restore vstFxChain + vstFxBypass + sub( + " fxChain: (t.fx_chain || []).map(m => typeof m === 'string' ? { type: m, active: true } : { type: m.type || m, active: m.active !== false }),\n" + " };", + " fxChain: (t.fx_chain || []).map(m => typeof m === 'string' ? { type: m, active: true } : { type: m.type || m, active: m.active !== false }),\n" + " vstFxChain: (t.vst_fx_chain || []).map(s => ({ type: s.type || 'vst3', path: s.path || '', name: s.name || '', preset_b64: s.preset_b64 || '', bypass: !!s.bypass })),\n" + " vstFxBypass: t.vst_fx_bypass !== undefined ? !!t.vst_fx_bypass : false,\n" + " };" + ) + + # 3. serializeProjectToSchema — master key (only when VST slots exist) + sub( + " tracks: mainTracks\n" + " },", + " tracks: mainTracks,\n" + " ...(masteringSettings && Array.isArray(masteringSettings.vstFxChain) && masteringSettings.vstFxChain.length > 0 ? {\n" + " master: {\n" + " fx_chain: masteringSettings.vstFxChain.map(s => ({ type: s.type || 'vst3', path: s.path || '', name: s.name || '', preset_b64: s.preset_b64 || '', bypass: !!s.bypass })),\n" + " volume_db: 0,\n" + " mute: false,\n" + " bypass: !!masteringSettings.vstBypass\n" + " }\n" + " } : {})\n" + " }," + ) + + # 4. deserializeProjectFromSchema — restore master VST chain + sub( + " return {\n" + " bpm: bpmVal,\n" + " tracks: restoredTracks,\n" + " sessionTabs: restoredSessionTabs,\n" + " subTabs: restoredSubTabs,\n" + " masteringSettings: _migrateMasteringSettings(schemaObj.mastering_settings),\n" + " zoom: schemaObj.metadata && schemaObj.metadata.zoom ? parseFloat(schemaObj.metadata.zoom) : 1.0\n" + " };", + " const _migratedMastering = _migrateMasteringSettings(schemaObj.mastering_settings) || {};\n" + " const _masterBus = (schemaObj.main_session && schemaObj.main_session.master) || {};\n" + " const _masterFx = Array.isArray(_masterBus.fx_chain) ? _masterBus.fx_chain : [];\n" + " return {\n" + " bpm: bpmVal,\n" + " tracks: restoredTracks,\n" + " sessionTabs: restoredSessionTabs,\n" + " subTabs: restoredSubTabs,\n" + " masteringSettings: {\n" + " ..._migratedMastering,\n" + " vstFxChain: _masterFx.map(s => ({ type: s.type || 'vst3', path: s.path || '', name: s.name || '', preset_b64: s.preset_b64 || '', bypass: !!s.bypass })),\n" + " vstBypass: !!_masterBus.bypass\n" + " },\n" + " zoom: schemaObj.metadata && schemaObj.metadata.zoom ? parseFloat(schemaObj.metadata.zoom) : 1.0\n" + " };" + ) + + # 5. masteringSettings initial state + sub( + " rebalActive: false, rebalMid: 0, rebalSide: 0,\n" + " });", + " rebalActive: false, rebalMid: 0, rebalSide: 0,\n" + " vstFxChain: [],\n" + " vstBypass: false,\n" + " });" + ) + + # 6. App scope: offline render preview (preview = âm export) + sub( + " window.__openFxRack = (trackId, trackName) => setFxRackTarget({ trackId, trackName });", + " window.__openFxRack = (trackId, trackName) => setFxRackTarget({ trackId, trackName });\n" + " // Render offline toàn project qua native_bridge (VST FX masterbus + track) → WAV.\n" + " // Dùng cho nút \"Nghe thử\" trong FX Rack / Mastering — preview = âm export.\n" + " window.__renderProjectPreview = async () => {\n" + " const schema = serializeProjectToSchema(currentProjectId || 'temp_project', projectName || 'Dự án tạm chưa lưu', bpm, tracks, subTabs, sessionTabs, masteringSettings);\n" + " try {\n" + " const res = await window.SonicAPI.renderProject(schema, 'preview_' + Date.now() + '.wav');\n" + " return res;\n" + " } catch (e) {\n" + " window.showToast && window.showToast('Render preview lỗi: ' + (e.message || e), 'error');\n" + " throw e;\n" + " }\n" + " };" + ) + + # 7. FXRackModal hooks — before early return + sub( + " }, [track && track.id]);\n" + "\n" + " if (!track) return null;", + " }, [track && track.id]);\n" + "\n" + " // VST FX (native_bridge --render-fx) — list VST3 effect plugins + preview\n" + " const [fxList, setFxList] = React.useState([]);\n" + " React.useEffect(() => {\n" + " if (window.SonicAPI && window.SonicAPI.listFx) {\n" + " window.SonicAPI.listFx().then(d => setFxList((d && d.plugins) || [])).catch(() => setFxList([]));\n" + " }\n" + " }, [track && track.id]);\n" + " const previewAudioRef = React.useRef(null);\n" + " const [vstBypass, setVstBypass] = React.useState(!!(track && track.vstFxBypass));\n" + " const [addVstSel, setAddVstSel] = React.useState('');\n" + "\n" + " if (!track) return null;" + ) + + # 8. FXRackModal helpers + sub( + " const chain = track.fxChain || [];\n" + " const setChain = (next) => {", + " const chain = track.fxChain || [];\n" + "\n" + " // ── VST FX chain (native_bridge --render-fx, áp dụng khi Export/Offline render) ──\n" + " const vstChain = (track.vstFxChain || []);\n" + " const setVstChain = (next) => { if (onUpdateTrack) onUpdateTrack(track.id, { vstFxChain: next, vstFxBypass: vstBypass }); };\n" + " const toggleVst = (idx) => setVstChain(vstChain.map((s, i) => i === idx ? { ...s, bypass: !s.bypass } : s));\n" + " const removeVst = (idx) => setVstChain(vstChain.filter((_, i) => i !== idx));\n" + " const moveVst = (idx, dir) => { const j = idx + dir; if (j < 0 || j >= vstChain.length) return; const next = [...vstChain]; const mv = next.splice(idx, 1)[0]; next.splice(j, 0, mv); setVstChain(next); };\n" + " const addVst = (path) => { const fx = (fxList || []).find(f => f.path === path); if (!fx) return; setVstChain([...vstChain, { type: 'vst3', path: fx.path, name: fx.name || fx.id, preset_b64: '', bypass: false }]); };\n" + " const changeVstPlugin = (idx, path) => { const fx = (fxList || []).find(f => f.path === path); setVstChain(vstChain.map((s, i) => i === idx ? { ...s, path, name: fx ? (fx.name || fx.id) : s.name, preset_b64: '' } : s)); };\n" + " const uploadVstPreset = (idx) => {\n" + " const inp = document.createElement('input');\n" + " inp.type = 'file';\n" + " inp.accept = '.vstpreset,.fxp,.fxb,.dspreset';\n" + " inp.onchange = () => {\n" + " const f = inp.files && inp.files[0];\n" + " if (!f) return;\n" + " const reader = new FileReader();\n" + " reader.onload = () => {\n" + " const b64 = String(reader.result || '').split(',')[1] || '';\n" + " setVstChain(vstChain.map((s, i) => i === idx ? { ...s, preset_b64: b64 } : s));\n" + " window.showToast && window.showToast('Đã gắn preset .vstpreset vào slot ' + (idx + 1), 'success');\n" + " };\n" + " reader.readAsDataURL(f);\n" + " };\n" + " inp.click();\n" + " };\n" + " const previewFx = async () => {\n" + " try {\n" + " if (previewAudioRef.current) { previewAudioRef.current.pause(); previewAudioRef.current = null; }\n" + " if (!window.__renderProjectPreview) { window.showToast && window.showToast('Không có hàm render preview', 'warning'); return; }\n" + " window.showToast && window.showToast('Đang render offline (bridge)...', 'info');\n" + " const res = await window.__renderProjectPreview();\n" + " const au = new Audio((window.API_BASE_URL || '') + res.url);\n" + " previewAudioRef.current = au;\n" + " au.onended = () => { previewAudioRef.current = null; };\n" + " au.play().then(() => window.showToast && window.showToast('Preview offline — âm thật = âm export (VST FX)', 'success')).catch(() => {});\n" + " } catch (e) { /* toast ở __renderProjectPreview */ }\n" + " };\n" + "\n" + " const setChain = (next) => {" + ) + + # 9. FXRackModal JSX — VST FX section between CHAIN strip and WORKSPACE CONTROLS + vst_row = ( + " const vstRow = (s, idx, toggleV, changeV, uploadV, moveV, removeV, fxl, chainLen) => (\n" + "
\n" + " \n" + " {idx + 1}\n" + " \n" + " {s.preset_b64 ? '✓ preset' : 'no preset'}\n" + " \n" + " \n" + " \n" + " \n" + " \n" + "
\n" + " );\n" + ) + # Insert helper ABOVE the FXRackModal component so both modals can use it. + sub( + "const FXRackModal = ({ track, onUpdateTrack, onClose }) => {", + vst_row + "\n" + "const FXRackModal = ({ track, onUpdateTrack, onClose }) => {" + ) + + fxrack_vst_section = ( + " {/* VST FX CHAIN (native_bridge --render-fx — áp dụng khi Export/Offline render) */}\n" + "
\n" + "
\n" + "
\n" + " VST FX:\n" + " Plugin VST3 (Ozone...) — realtime browser vẫn WebAudio builtin; VST áp dụng lúc Export/Offline render (preview = âm export)\n" + "
\n" + "
\n" + " \n" + " \n" + "
\n" + "
\n" + " {vstChain.length === 0 &&
Chưa có VST FX — chọn plugin bên dưới để thêm vào insert chain (WebAudio chain → VST FX → Fader).
}\n" + " {vstChain.map((s, idx) => vstRow(s, idx, toggleVst, changeVstPlugin, uploadVstPreset, moveVst, removeVst, fxList, vstChain.length))}\n" + " \n" + "
\n" + "\n" + " {/* WORKSPACE CONTROLS */}" + ) + sub( + " {/* WORKSPACE CONTROLS */}", + fxrack_vst_section + ) + + # 10. MasteringModal hooks + sub( + " const knobsInitializedRef = React.useRef(false);", + " const knobsInitializedRef = React.useRef(false);\n" + "\n" + " // VST FX masterbus (native_bridge --render-fx) — list VST3 effect plugins + preview\n" + " const [masterFxList, setMasterFxList] = React.useState([]);\n" + " React.useEffect(() => {\n" + " if (window.SonicAPI && window.SonicAPI.listFx) {\n" + " window.SonicAPI.listFx().then(d => setMasterFxList((d && d.plugins) || [])).catch(() => setMasterFxList([]));\n" + " }\n" + " }, []);\n" + " const previewAudioRef = React.useRef(null);\n" + " const [addMasterVstSel, setAddMasterVstSel] = React.useState('');" + ) + + # 11. MasteringModal helpers + sub( + " const audioRef = React.useRef({ source: null });", + " // ── VST FX masterbus (native_bridge --render-fx, áp dụng khi Export/Offline render) ──\n" + " const masterVstChain = (ozState.vstFxChain || []);\n" + " const setMasterVstChain = (next) => setOzState(prev => ({ ...prev, vstFxChain: next }));\n" + " const toggleMasterVst = (idx) => setMasterVstChain(masterVstChain.map((s, i) => i === idx ? { ...s, bypass: !s.bypass } : s));\n" + " const removeMasterVst = (idx) => setMasterVstChain(masterVstChain.filter((_, i) => i !== idx));\n" + " const moveMasterVst = (idx, dir) => { const j = idx + dir; if (j < 0 || j >= masterVstChain.length) return; const next = [...masterVstChain]; const mv = next.splice(idx, 1)[0]; next.splice(j, 0, mv); setMasterVstChain(next); };\n" + " const addMasterVst = (path) => { const fx = (masterFxList || []).find(f => f.path === path); if (!fx) return; setMasterVstChain([...masterVstChain, { type: 'vst3', path: fx.path, name: fx.name || fx.id, preset_b64: '', bypass: false }]); };\n" + " const changeMasterVstPlugin = (idx, path) => { const fx = (masterFxList || []).find(f => f.path === path); setMasterVstChain(masterVstChain.map((s, i) => i === idx ? { ...s, path, name: fx ? (fx.name || fx.id) : s.name, preset_b64: '' } : s)); };\n" + " const uploadMasterVstPreset = (idx) => {\n" + " const inp = document.createElement('input');\n" + " inp.type = 'file';\n" + " inp.accept = '.vstpreset,.fxp,.fxb,.dspreset';\n" + " inp.onchange = () => {\n" + " const f = inp.files && inp.files[0];\n" + " if (!f) return;\n" + " const reader = new FileReader();\n" + " reader.onload = () => {\n" + " const b64 = String(reader.result || '').split(',')[1] || '';\n" + " setMasterVstChain(masterVstChain.map((s, i) => i === idx ? { ...s, preset_b64: b64 } : s));\n" + " window.showToast && window.showToast('Đã gắn preset .vstpreset vào slot ' + (idx + 1), 'success');\n" + " };\n" + " reader.readAsDataURL(f);\n" + " };\n" + " inp.click();\n" + " };\n" + " const previewMasterFx = async () => {\n" + " try {\n" + " if (previewAudioRef.current) { previewAudioRef.current.pause(); previewAudioRef.current = null; }\n" + " if (!window.__renderProjectPreview) { window.showToast && window.showToast('Không có hàm render preview', 'warning'); return; }\n" + " window.showToast && window.showToast('Đang render offline (bridge)...', 'info');\n" + " const res = await window.__renderProjectPreview();\n" + " const au = new Audio((window.API_BASE_URL || '') + res.url);\n" + " previewAudioRef.current = au;\n" + " au.onended = () => { previewAudioRef.current = null; };\n" + " au.play().then(() => window.showToast && window.showToast('Preview offline — âm thật = âm export (VST FX)', 'success')).catch(() => {});\n" + " } catch (e) { /* toast ở __renderProjectPreview */ }\n" + " };\n" + "\n" + " const audioRef = React.useRef({ source: null });" + ) + + # 12. MasteringModal JSX — VST section after CHAIN strip, before MAIN WORKSPACE + master_vst_section = ( + " {/* VST FX MASTERBUS (native_bridge --render-fx — áp dụng khi Export/Offline render) */}\n" + "
\n" + "
\n" + "
\n" + " VST FX MASTERBUS:\n" + " Plugin VST3 (Ozone...) — realtime browser vẫn WebAudio builtin; VST áp dụng lúc Export/Offline render (preview = âm export)\n" + "
\n" + "
\n" + " \n" + " \n" + "
\n" + "
\n" + " {masterVstChain.length === 0 &&
Chưa có VST FX masterbus — chọn plugin bên dưới (Ozone...) để chạy chain sau khi mix WebAudio.
}\n" + " {masterVstChain.map((s, idx) => vstRow(s, idx, toggleMasterVst, changeMasterVstPlugin, uploadMasterVstPreset, moveMasterVst, removeMasterVst, masterFxList, masterVstChain.length))}\n" + " \n" + "
\n" + "\n" + " {/* MAIN WORKSPACE */}" + ) + sub( + " {/* MAIN WORKSPACE */}", + master_vst_section + ) + + if s == orig: + print('NO CHANGE — nothing patched') + sys.exit(1) + + with io.open(PATH, 'w', encoding='utf-8', newline='') as f: + f.write(s if not crlf else s.replace('\n', '\r\n')) + print('OK patched app.jsx') + +if __name__ == '__main__': + main() diff --git a/tools/patch_crash_dump.py b/tools/patch_crash_dump.py new file mode 100644 index 0000000..b02c5b3 --- /dev/null +++ b/tools/patch_crash_dump.py @@ -0,0 +1,80 @@ +import io, sys + +p = r"C:/Users/locpham/SonicForgeStudio/native_bridge/src/main.cpp" +data = open(p, "rb").read() + +def rep(old, new, count=1): + global data + old = old.encode() + new = new.encode() + assert data.count(old) >= count, "pattern not found: %r" % old + data = data.replace(old, new, count) + +# 1. includes: cstdio + dbghelp.h into the _WIN32 block +rep("#include \n#include \n", + "#include \n#include \n#include \n#include \n") + +# 2. handler + registration at main entry +handler = r""" +// --- crash minidump handler (temp diagnostics) ------------------------------- +// WER LocalDumps produces NO dump for 0xC0000374 (heap corruption) — write our +// own minidump via dbghelp.dll when an unhandled exception reaches this filter. +#ifdef _WIN32 +static LONG WINAPI CrashDumpHandler(EXCEPTION_POINTERS* ep) { + static LONG once = 0; + if (InterlockedCompareExchange(&once, 1, 0)) return EXCEPTION_CONTINUE_SEARCH; + PEXCEPTION_RECORD er = ep ? ep->ExceptionRecord : nullptr; + std::cerr << "[CRASH] exception code=0x" << std::hex + << (er ? er->ExceptionCode : 0) << " addr=0x" + << (er ? (void*)er->ExceptionAddress : nullptr) << std::dec << std::endl; + if (er) { + HMODULE hMod = nullptr; + char modPath[MAX_PATH] = {0}; + if (GetModuleHandleExA(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | + GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, + (LPCSTR)er->ExceptionAddress, &hMod) && hMod) { + GetModuleFileNameA(hMod, modPath, MAX_PATH); + std::cerr << "[CRASH] faulting module: " << modPath << std::endl; + } + } + const char* dumpDir = "C:/Users/locpham/AppData/Local/Temp/sf_dumps"; + CreateDirectoryA(dumpDir, nullptr); + char dumpPath[MAX_PATH]; + snprintf(dumpPath, sizeof(dumpPath), "%s/bridge_crash_%lu.dmp", dumpDir, GetCurrentProcessId()); + HMODULE hDbg = LoadLibraryA("dbghelp.dll"); + if (hDbg) { + typedef BOOL(WINAPI* MiniDumpWriteDumpFn)(HANDLE, DWORD, HANDLE, MINIDUMP_TYPE, + PMINIDUMP_EXCEPTION_INFORMATION, PMINIDUMP_USER_STREAM_INFORMATION, + PMINIDUMP_CALLBACK_INFORMATION); + auto pMiniDumpWriteDump = (MiniDumpWriteDumpFn)GetProcAddress(hDbg, "MiniDumpWriteDump"); + if (pMiniDumpWriteDump) { + HANDLE hFile = CreateFileA(dumpPath, GENERIC_WRITE, 0, nullptr, + CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr); + if (hFile != INVALID_HANDLE_VALUE) { + MINIDUMP_EXCEPTION_INFORMATION mei = {}; + mei.ThreadId = GetCurrentThreadId(); + mei.ExceptionPointers = ep; + mei.ClientPointers = TRUE; + BOOL ok = pMiniDumpWriteDump(GetCurrentProcess(), GetCurrentProcessId(), + hFile, MiniDumpNormal, &mei, nullptr, nullptr); + CloseHandle(hFile); + std::cerr << "[CRASH] minidump " << (ok ? "written: " : "FAILED: ") << dumpPath << std::endl; + } else { + std::cerr << "[CRASH] cannot open dump file" << std::endl; + } + } else { + std::cerr << "[CRASH] MiniDumpWriteDump not found in dbghelp.dll" << std::endl; + } + } else { + std::cerr << "[CRASH] cannot load dbghelp.dll" << std::endl; + } + return EXCEPTION_CONTINUE_SEARCH; // let WER proceed, process dies normally +} +#endif +""" + +rep("int main(int argc, char* argv[]) {\n", + handler + "\nint main(int argc, char* argv[]) {\n SetUnhandledExceptionFilter(CrashDumpHandler);\n") + +open(p, "wb").write(data) +print("patched OK, size=", len(data)) diff --git a/tools/patch_native_track.py b/tools/patch_native_track.py new file mode 100644 index 0000000..dabb3ab --- /dev/null +++ b/tools/patch_native_track.py @@ -0,0 +1,327 @@ +# -*- coding: utf-8 -*- +"""Patch app/static/js/app.jsx: thay render per-item (scheduleNativeSfItem / +scheduleNativeVstItem) bằng scheduleNativeTrack (render cả track 1 request) ++ epoch guard + decode queue + prerenderNativeTracks. Debug tool — không commit.""" +import io, sys +sys.stdout.reconfigure(encoding="utf-8") + +PATH = "app/static/js/app.jsx" +with open(PATH, "rb") as f: + raw = f.read() +src = raw.decode("utf-8").replace("\r\n", "\n") +orig = src + + +def save(): + with open(PATH, "wb") as f: + f.write(src.replace("\n", "\r\n").encode("utf-8")) + + +# ── Patch 1: thay scheduleNativeSfItem (per-item) bằng họ scheduleNativeTrack ── +if "_nativePlayEpoch" not in src: + OLD_START = "// \u2500\u2500 Native soundfont item render (standalone, transport) \u2500" + OLD_END = " } catch (e) { console.warn('[NativeSF] scheduleNativeSfItem error:', e); }\n};" + i0 = src.index(OLD_START) + i1 = src.index(OLD_END) + len(OLD_END) + assert src[i1:i1+4] == "\n\n//", repr(src[i1:i1+40]) + NEW = """// \u2500\u2500 Native track render (standalone, transport) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 +// Render TO\u00c0N B\u1ed8 MIDI items c\u1ee7a track b\u1eb1ng 1 request (native FluidSynth cho +// soundfont, native_bridge --render cho VSTi) \u2192 decode \u2192 1 BufferSource duy +// nh\u1ea5t cho c\u1ea3 track (nh\u01b0 audio clip). Thay th\u1ebf render per-item: RENDER_LOCK +// server serialize t\u1eebng item \u2192 project nhi\u1ec1u item = delay v\u00e0i ch\u1ee5c gi\u00e2y. +// opts: +// baseOffsetSec: offset th\u00eam (section: secStart) | limitSec: ch\u1eb7n t\u1ea1i secEnd +// isActive(): guard stop gi\u1eefa ch\u1eebng | sources: m\u1ea3ng ngu\u1ed3n \u0111\u1ec3 stop +// playheadNow(): playhead TH\u1eacT t\u1ea1i th\u1eddi \u0111i\u1ec3m resolve \u2014 render tr\u1ec5 v\u00e0i ch\u1ee5c +// gi\u00e2y \u2192 item v\u1eabn schedule \u0111\u00fang v\u1ecb tr\u00ed playhead hi\u1ec7n t\u1ea1i (kh\u00f4ng ch\u1ed3ng +// \u00e2m/wall-of-sound nh\u01b0 d\u00f9ng offsetTime c\u0169). +// notesOverride: d\u00f9ng thay track.midiItems (piano roll tab). +// Epoch guard: stop/rebuild t\u0103ng _nativePlayEpoch \u2192 m\u1ecdi render pending c\u1ee7a +// l\u1ea7n play c\u0169 resolve nh\u01b0ng b\u1ecb skip (kh\u00f4ng duplicate schedule). +let _nativePlayEpoch = 0; +const _nativeTrackUrls = {}; +const _nativeTrackPending = {}; +const _buildNativeTrackNotes = (track, bpm, baseOffsetSec, notesOverride) => { + const secPerBeat = 60.0 / (parseInt(bpm) || 120); + const items = notesOverride ? [{ startTime: 0, notes: notesOverride }] : ((track && track.midiItems) || []); + let trackStartAbs = Infinity; + const absNotes = []; + items.forEach(function (item) { + const itemStartAbs = (baseOffsetSec || 0) + (item.startTime || 0); + if (itemStartAbs < trackStartAbs) trackStartAbs = itemStartAbs; + (item.notes || []).forEach(function (n) { + absNotes.push({ + pitch: n.pitch || 60, + start_beat: itemStartAbs / secPerBeat + (n.start_beat || 0), + duration_beats: n.duration_beats || 1, + velocity: n.velocity != null ? n.velocity : 0.8, + }); + }); + }); + return { absNotes: absNotes, trackStartAbs: trackStartAbs }; +}; +const _nativeTrackKey = (track, bpm, baseOffsetSec, notesOverride) => { + try { + const eng = track && track.synth_engine; + const instrumentId = (eng && eng.plugin_id) || (track && track.instrumentId) || ''; + const sfId = (eng && eng.soundfont_id) || ''; + const data = _buildNativeTrackNotes(track, bpm, baseOffsetSec, notesOverride); + const nk = data.absNotes.map(function (n) { + return n.pitch + ':' + n.start_beat + ':' + n.duration_beats + ':' + n.velocity; + }).join(','); + return [track && track.id ? track.id : '', instrumentId, sfId, (eng && eng.preset_id) || '', (eng && eng.preset_path) || '', parseFloat(bpm) || 120, baseOffsetSec || 0, nk].join('|'); + } catch (e) { return ''; } +}; +const _renderNativeTrack = (track, bpm, baseOffsetSec, notesOverride) => { + try { + const eng = track && track.synth_engine; + const data = _buildNativeTrackNotes(track, bpm, baseOffsetSec, notesOverride); + if (!data.absNotes.length) return Promise.resolve(null); + if (isSfTrackEngine(eng)) { + const sfId = eng && eng.soundfont_id; + if (!sfId) return Promise.resolve(null); + return window.SonicAPI.soundfontRender({ + soundfont_id: sfId, + bank: (eng && eng.soundfont_bank) || 0, + program: (eng && eng.soundfont_program) || 0, + bpm: parseFloat(bpm) || 120, + notes: data.absNotes, + }).then(function (res) { + if (!res || !res.success || !res.url) return null; + return API_BASE_URL + res.url; + }).catch(function () { return null; }); + } + const instrumentId = (eng && eng.plugin_id) || (track && track.instrumentId); + if (!instrumentId || !isVstTrackEngine(eng)) return Promise.resolve(null); + return window.SonicAPI.midiRender({ + instrument_id: instrumentId, + notes: data.absNotes, + bpm: parseFloat(bpm) || 120, + preset_id: (eng && eng.preset_id) || undefined, + preset_path: (eng && eng.preset_path) || undefined, + preset_data: (eng && eng.preset_data) || undefined, + }).then(function (res) { + if (!res || !res.success || !res.url) return null; + return API_BASE_URL + res.url; + }).catch(function () { return null; }); + } catch (e) { return Promise.resolve(null); } +}; +// Dedupe in-flight render + cache WAV: play l\u1eb7p/re-schedule trong l\u00fac render +// \u0111ang ch\u1ea1y \u2192 ch\u1edd c\u00f9ng promise, kh\u00f4ng b\u1eafn duplicate request (tr\u00e1nh queue sau +// RENDER_LOCK + render 2 l\u1ea7n c\u00f9ng n\u1ed9i dung). +const ensureRenderNativeTrack = (track, bpm, baseOffsetSec, notesOverride) => { + const key = _nativeTrackKey(track, bpm, baseOffsetSec, notesOverride); + if (!key) return _renderNativeTrack(track, bpm, baseOffsetSec, notesOverride); + if (_nativeTrackUrls[key]) return Promise.resolve(_nativeTrackUrls[key]); + if (_nativeTrackPending[key]) return _nativeTrackPending[key]; + const p = _renderNativeTrack(track, bpm, baseOffsetSec, notesOverride).then(function (url) { + if (url) _nativeTrackUrls[key] = url; + return url; + }).then(function (url) { + delete _nativeTrackPending[key]; + return url; + }); + _nativeTrackPending[key] = p; + return p; +}; +// Gi\u1edbi h\u1ea1n decodeAudioData song song (max 3) \u2014 nhi\u1ec1u WAV fetch v\u1ec1 c\u00f9ng l\u00fac \u2192 +// main thread ngh\u1ebdn \u2192 UI \u0111\u01a1, Stop kh\u00f4ng ph\u1ea3n h\u1ed3i. +let _nativeDecodeActive = 0; +const _nativeDecodeQueue = []; +const _pumpNativeDecodes = () => { + while (_nativeDecodeActive < 3 && _nativeDecodeQueue.length) { + const job = _nativeDecodeQueue.shift(); + _nativeDecodeActive++; + try { + job.ctx.decodeAudioData(job.buf, function (ab) { + _nativeDecodeActive--; + job.cb(ab); + _pumpNativeDecodes(); + }, function () { + _nativeDecodeActive--; + job.cb(null); + _pumpNativeDecodes(); + }); + } catch (e) { + _nativeDecodeActive--; + job.cb(null); + _pumpNativeDecodes(); + } + } +}; +const _decodeNativeQueue = (ctx, buf, cb) => { + _nativeDecodeQueue.push({ ctx: ctx, buf: buf, cb: cb }); + _pumpNativeDecodes(); +}; +const scheduleNativeTrack = (track, offsetTime, context, destNode, bpm, opts) => { + try { + const eng = track && track.synth_engine; + if (!isSfTrackEngine(eng) && !isVstTrackEngine(eng)) return; + const baseOffsetSec = (opts && opts.baseOffsetSec) || 0; + const notesOverride = (opts && opts.notesOverride) || null; + const data = _buildNativeTrackNotes(track, bpm, baseOffsetSec, notesOverride); + if (!data.absNotes.length) return; + const key = _nativeTrackKey(track, bpm, baseOffsetSec, notesOverride); + const epoch = _nativePlayEpoch; + const scheduleUrl = function (url) { + if (!url) return; + fetch(url).then(function (r) { return r.arrayBuffer(); }).then(function (buf) { + _decodeNativeQueue(context, buf, function (audioBuf) { + try { + if (!audioBuf) return; + if (epoch !== _nativePlayEpoch) return; // stale play (stop/rebuild) + if (opts && typeof opts.isActive === 'function' && !opts.isActive()) return; + const ph = (opts && typeof opts.playheadNow === 'function') ? opts.playheadNow() : offsetTime; + const src = context.createBufferSource(); + src.buffer = audioBuf; + src.connect(destNode); + const trackEndAbs = data.trackStartAbs + audioBuf.duration; + if (ph < data.trackStartAbs) { + src.start(context.currentTime + (data.trackStartAbs - ph), 0); + } else if (ph < trackEndAbs) { + src.start(context.currentTime, Math.min(ph - data.trackStartAbs, Math.max(0, audioBuf.duration - 0.05))); + } else { + return; + } + if (opts && opts.limitSec && opts.limitSec < trackEndAbs) { + src.stop(context.currentTime + Math.max(0.02, opts.limitSec - Math.max(ph, data.trackStartAbs))); + } + if (opts && opts.sources && opts.sources.push) opts.sources.push(src); + } catch (e) { console.warn('[NativeTrack] schedule decode error:', e); } + }); + }).catch(function () {}); + }; + if (key && _nativeTrackUrls[key]) { scheduleUrl(_nativeTrackUrls[key]); return; } + ensureRenderNativeTrack(track, bpm, baseOffsetSec, notesOverride).then(function (url) { + if (!url) return; + scheduleUrl(url); + }); + } catch (e) { console.warn('[NativeTrack] scheduleNativeTrack error:', e); } +};""" + src = src[:i0] + NEW + src[i1:] + print("OK patch 1 (scheduleNativeSfItem -> scheduleNativeTrack)") +else: + print("SKIP patch 1 (da ap)") + +# ── Patch 2: URL cache per-item + prerenderNativeVstItems → prerenderNativeTracks ── +if "prerenderNativeTracks" not in src: + P2_START = "// URL cache cho midi-render: key = fingerprint (item+instrument+preset+notes)" + P2_END = " }, 800);\n } catch (e) {}\n};" + i0 = src.index(P2_START) + i1 = src.index(P2_END, i0) + len(P2_END) + P2 = """// \u2500\u2500 Native track pre-render (standalone) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 +// Render C\u1ea2 TRACK 1 request khi project load \u2192 play l\u1ea7n \u0111\u1ea7u kh\u00f4ng ch\u1edd render. +let _prerenderNativeTracksTimer = null; +const prerenderNativeTracks = (tracks, bpm) => { + try { + if (!isStandaloneSf()) return; + if (_prerenderNativeTracksTimer) { clearTimeout(_prerenderNativeTracksTimer); _prerenderNativeTracksTimer = null; } + _prerenderNativeTracksTimer = setTimeout(function () { + _prerenderNativeTracksTimer = null; + const seen = {}; + (tracks || []).forEach(function (track) { + const eng = track && track.synth_engine; + const native = isStandaloneSf() && (isVstTrackEngine(eng) || isSfTrackEngine(eng)) && !(window.SonicMidiRouter && window.SonicMidiRouter.isBridgeActive()); + if (!native) return; + const key = _nativeTrackKey(track, bpm, 0, null); + if (!key || seen[key] || _nativeTrackUrls[key]) return; + seen[key] = true; + ensureRenderNativeTrack(track, bpm, 0, null).then(function () {}); + }); + }, 800); + } catch (e) {} +};""" + src = src[:i0] + P2 + src[i1:] + print("OK patch 2 (prerenderNativeVstItems -> prerenderNativeTracks)") +else: + print("SKIP patch 2 (da ap)") + +# ── Patch 3: xóa scheduleNativeVstItem (không còn dùng) ── +if "scheduleNativeVstItem" in src: + P3_START = "// \u2500\u2500 Native VSTi item render (standalone, transport) \u2500" + P3_END = " } catch (e) { console.warn('[NativeVST] scheduleNativeVstItem error:', e); }\n};" + i0 = src.index(P3_START) + i1 = src.index(P3_END) + len(P3_END) + assert src[i1:i1+2] == "\n\n", repr(src[i1:i1+30]) + src = src[:i0] + src[i1+2:] + print("OK patch 3 (xoa scheduleNativeVstItem)") +else: + print("SKIP patch 3 (da ap)") + +# ── Patch 4: đổi 4 call sites sang scheduleNativeTrack ── +C4 = [ + # site 1: startTrackPlayback + (""" if (nativeSf) { + midiItems.forEach(item => scheduleNativeSfItem(track, item, offsetTime, context, gainNode, bpm, { sources: activeSourcesRef.current, isActive: () => !!isPlayingRef.current })); + } else if (nativeVst) { + midiItems.forEach(item => scheduleNativeVstItem(track, item, offsetTime, context, gainNode, bpm, { sources: activeSourcesRef.current, isActive: () => !!isPlayingRef.current })); + } else {""", + """ if (nativeSf || nativeVst) { + scheduleNativeTrack(track, offsetTime, context, gainNode, bpm, { sources: activeSourcesRef.current, isActive: () => !!isPlayingRef.current }); + } else {"""), + # site 2: section playback + (""" if (subNativeSf) { + subMidiItems.forEach(item => scheduleNativeSfItem(subTrack, item, offsetTime, context, subNode, bpm, { baseOffsetSec: secStart, limitSec: secEnd, sources: activeSourcesRef.current, isActive: () => !!isPlayingRef.current })); + return; // native render \u2192 audio buffer (kh\u00f4ng qua WASM/Carla per-note) + } + if (subNativeVst) { + subMidiItems.forEach(item => scheduleNativeVstItem(subTrack, item, offsetTime, context, subNode, bpm, { baseOffsetSec: secStart, limitSec: secEnd, sources: activeSourcesRef.current, isActive: () => !!isPlayingRef.current })); + return; // native render \u2192 audio buffer (kh\u00f4ng qua WASM/Carla per-note) + }""", + """ if (subNativeSf || subNativeVst) { + scheduleNativeTrack(subTrack, offsetTime, context, subNode, bpm, { baseOffsetSec: secStart, limitSec: secEnd, sources: activeSourcesRef.current, isActive: () => !!isPlayingRef.current }); + return; // native render \u2192 audio buffer (kh\u00f4ng qua WASM/Carla per-note) + }"""), + # site 3: startLocalTrackPlayback + (""" if (nativeSf) { + midiItems.forEach(item => scheduleNativeSfItem(track, item, offsetTime, context, gainNode, bpm, { sources: activeSourcesRef.current, isActive: () => !!isPlayingRef.current })); + return; // native render \u2192 audio buffer (kh\u00f4ng qua WASM/Carla per-note) + } + if (nativeVst) { + midiItems.forEach(item => scheduleNativeVstItem(track, item, offsetTime, context, gainNode, bpm, { sources: activeSourcesRef.current, isActive: () => !!isPlayingRef.current })); + return; // native render \u2192 audio buffer (kh\u00f4ng qua WASM/Carla per-note) + }""", + """ if (nativeSf || nativeVst) { + scheduleNativeTrack(track, offsetTime, context, gainNode, bpm, { sources: activeSourcesRef.current, isActive: () => !!isPlayingRef.current }); + return; // native render \u2192 audio buffer (kh\u00f4ng qua WASM/Carla per-note) + }"""), + # site 4: piano roll (notesOverride) + (""" if (isStandaloneSf() && !routeCarla && isSfTrackEngine(synthEngine) && !(window.SonicMidiRouter && window.SonicMidiRouter.isBridgeActive())) { + scheduleNativeSfItem(track, { startTime: 0, notes: midiNotes }, offsetSeconds, context, destNode, bpm, { sources: activeSourcesRef.current, isActive: () => !!isPlayingRef.current }); + return; // native render c\u1ea3 tab \u2192 audio buffer (kh\u00f4ng per-note WASM/Carla) + } + if (isStandaloneSf() && !routeCarla && isVstTrackEngine(synthEngine) && !(window.SonicMidiRouter && window.SonicMidiRouter.isBridgeActive())) { + scheduleNativeVstItem(track, { startTime: 0, notes: midiNotes }, offsetSeconds, context, destNode, bpm, { sources: activeSourcesRef.current, isActive: () => !!isPlayingRef.current }); + return; // native render c\u1ea3 tab \u2192 audio buffer (kh\u00f4ng per-note WASM/Carla) + }""", + """ if (isStandaloneSf() && !routeCarla && (isSfTrackEngine(synthEngine) || isVstTrackEngine(synthEngine)) && !(window.SonicMidiRouter && window.SonicMidiRouter.isBridgeActive())) { + scheduleNativeTrack(track, offsetSeconds, context, destNode, bpm, { notesOverride: midiNotes, sources: activeSourcesRef.current, isActive: () => !!isPlayingRef.current }); + return; // native render c\u1ea3 tab \u2192 audio buffer (kh\u00f4ng per-note WASM/Carla) + }"""), +] +n4 = 0 +for old, new in C4: + if old in src: + src = src.replace(old, new, 1) + n4 += 1 +print("OK patch 4 (%d/4 call sites)" % n4) + +# ── Patch 5: stopAllPlayback tăng epoch ── +if "_nativePlayEpoch++" not in src: + anchor = " try {\n isPlayingRef.current = false;" + assert src.count(anchor) == 1, "anchor stopAllPlayback khong duy nhat" + src = src.replace(anchor, " try {\n isPlayingRef.current = false;\n _nativePlayEpoch++; // h\u1ee7y m\u1ecdi render/decode pending c\u1ee7a l\u1ea7n play c\u0169", 1) + print("OK patch 5 (epoch guard trong stopAllPlayback)") +else: + print("SKIP patch 5 (da ap)") + +# ── Patch 6: đổi call sites prerender ── +n6 = src.count("prerenderNativeVstItems(") +if n6: + src = src.replace("prerenderNativeVstItems(", "prerenderNativeTracks(") + print("OK patch 6 (%d prerender call sites)" % n6) +else: + print("SKIP patch 6 (da ap)") + +save() +print("DONE. changed=", src != orig, "len=", len(src)) diff --git a/tools/patch_plugin_path.py b/tools/patch_plugin_path.py new file mode 100644 index 0000000..8456a13 --- /dev/null +++ b/tools/patch_plugin_path.py @@ -0,0 +1,8 @@ +import io +p = 'tools/test_vst2_host.py' +s = io.open(p, encoding='utf-8', newline='').read() +old = "PLUGIN = r'C:\\Program Files\\Steinberg\\VSTPlugins\\DUNE 3.dll'" +assert old in s, 'not found' +s = s.replace(old, "PLUGIN = r'C:\\Program Files\\Steinberg\\VSTPlugins\\WaveObserver.dll'") +io.open(p, 'w', encoding='utf-8', newline='').write(s) +print('patched') diff --git a/tools/patch_stacktrace.py b/tools/patch_stacktrace.py new file mode 100644 index 0000000..e63bc98 --- /dev/null +++ b/tools/patch_stacktrace.py @@ -0,0 +1,54 @@ +import sys + +p = r"C:/Users/locpham/SonicForgeStudio/native_bridge/src/main.cpp" +data = open(p, "rb").read() + +def rep(old, new, count=1): + global data + old = old.encode(); new = new.encode() + assert data.count(old) >= count, "pattern not found: %r" % old + data = data.replace(old, new, count) + +# unwind stack from exception context BEFORE the dump writer clobbers it +stackdump = r""" +// Unwind the LIVE stack from the exception context (MiniDumpWriteDump captures +// the context at dump time, not at raise time — by then the handler's own +// frames replace the real ones). Print return-address candidates that land in +// loaded modules, lowest stack address first. +#ifdef _WIN32 +static void DumpStackTraceFromContext(const CONTEXT* ctx) { + if (!ctx) return; + std::cerr << "[VCRASH] stack trace (raw scan):" << std::endl; + const unsigned char* sp = (const unsigned char*)ctx->Rsp; + for (int i = 0; i < 512; i++) { + uintptr_t val = 0; + memcpy(&val, sp + i * 8, sizeof(val)); // stack is readable in a handler + HMODULE h = nullptr; + char m[MAX_PATH] = {0}; + if (GetModuleHandleExA(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | + GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, + (LPCSTR)val, &h) && h && GetModuleFileNameA(h, m, MAX_PATH)) { + const char* base = strrchr(m, '\\'); + base = base ? base + 1 : m; + std::cerr << " +" << i * 8 << " " << (void*)val << " " << base + << "+0x" << std::hex << (val - (uintptr_t)h) << std::dec << std::endl; + } + } +} +#endif +""" + +rep("static LONG WINAPI VectoredCrashHandler(PEXCEPTION_POINTERS ep) {", + stackdump + "\nstatic LONG WINAPI VectoredCrashHandler(PEXCEPTION_POINTERS ep) {") + +# call it right at handler entry +rep(""" std::cerr << "[VCRASH] first-chance code=0x" << std::hex << code + << " addr=0x" << (er ? (void*)er->ExceptionAddress : nullptr) + << std::dec << " tid=" << GetCurrentThreadId() << std::endl;""", + """ std::cerr << "[VCRASH] first-chance code=0x" << std::hex << code + << " addr=0x" << (er ? (void*)er->ExceptionAddress : nullptr) + << std::dec << " tid=" << GetCurrentThreadId() << std::endl; + DumpStackTraceFromContext(ep ? ep->ContextRecord : nullptr);""") + +open(p, "wb").write(data) +print("patched OK, size=", len(data)) diff --git a/tools/patch_vectored.py b/tools/patch_vectored.py new file mode 100644 index 0000000..f2f36b8 --- /dev/null +++ b/tools/patch_vectored.py @@ -0,0 +1,39 @@ +import sys + +p = r"C:/Users/locpham/SonicForgeStudio/native_bridge/src/main.cpp" +data = open(p, "rb").read() + +def rep(old, new, count=1): + global data + old = old.encode(); new = new.encode() + assert data.count(old) >= count, "pattern not found: %r" % old + data = data.replace(old, new, count) + +# 1. vectored handler ghi dump cho first-chance; cai gan CrashDumpHandler +vectored = r""" +// Vectored handler: catches first-chance exceptions INCLUDING heap-corruption +// raises that never reach SetUnhandledExceptionFilter (0xC0000374 observed to +// bypass it on this machine). Writes the same minidump then continues. +#ifdef _WIN32 +static LONG WINAPI VectoredCrashHandler(PEXCEPTION_POINTERS ep) { + PEXCEPTION_RECORD er = ep ? ep->ExceptionRecord : nullptr; + DWORD code = er ? er->ExceptionCode : 0; + if (code != 0xC0000374 && code != 0xC0000005 && code != 0xC0000409) { + return EXCEPTION_CONTINUE_SEARCH; // ignore benign first-chance noise + } + std::cerr << "[VCRASH] first-chance code=0x" << std::hex << code + << " addr=0x" << (er ? (void*)er->ExceptionAddress : nullptr) + << std::dec << " tid=" << GetCurrentThreadId() << std::endl; + // route into the same dump writer + return CrashDumpHandler(ep) == EXCEPTION_CONTINUE_SEARCH + ? EXCEPTION_CONTINUE_SEARCH + : EXCEPTION_CONTINUE_SEARCH; +} +#endif +""" + +rep("int main(int argc, char* argv[]) {\n SetUnhandledExceptionFilter(CrashDumpHandler);\n", + vectored + "\nint main(int argc, char* argv[]) {\n SetUnhandledExceptionFilter(CrashDumpHandler);\n AddVectoredExceptionHandler(1, VectoredCrashHandler);\n") + +open(p, "wb").write(data) +print("patched OK, size=", len(data)) diff --git a/tools/scan_stack_refs.py b/tools/scan_stack_refs.py new file mode 100644 index 0000000..398f4f8 --- /dev/null +++ b/tools/scan_stack_refs.py @@ -0,0 +1,54 @@ +import struct + +path = r"C:/Users/locpham/AppData/Local/Temp/sf_dumps/bridge_crash_1696.dmp" +data = open(path, "rb").read() + +sig, ver, numstreams, sdir = struct.unpack_from(" 1) +print('wi advanced:', len(set(s[1] for s in samples)) > 1) +print('max eL:', max(s[2] for s in samples), 'max eR:', max(s[3] for s in samples)) + +k32.UnmapViewOfFile(ptr) +k32.CloseHandle(h) diff --git a/tools/shm_test_preview_fix.py b/tools/shm_test_preview_fix.py new file mode 100644 index 0000000..0e76ead --- /dev/null +++ b/tools/shm_test_preview_fix.py @@ -0,0 +1,96 @@ +# SHM injection test for live-flag fix: verify transportStopped guard drops +# non-live note-ons but passes live ones (reserved[0]=1). +# Layout from SharedMemoryIPC.h (verified): base=2064 bytes, midiQueue 64x12 @2064, +# midiQueueCount @2832, controlQueue 8x1040 @2836, controlQueueCount @11156. +import ctypes, sys, time, os + +sys.stdout.reconfigure(encoding='utf-8', errors='replace') + +APP_PID = sys.argv[1] if len(sys.argv) > 1 else '9712' +SHM_NAME = f'SonicForge_DAW_IPC_{APP_PID}' + +OFF_MQ = 2064 +OFF_MQ_COUNT = 2832 +OFF_CQ = 2836 +OFF_CQ_COUNT = 11156 +MIDI_ENTRY = 12 +CTRL_ENTRY = 1040 + +k32 = ctypes.WinDLL('kernel32', use_last_error=True) +k32.OpenFileMappingA.restype = ctypes.c_void_p +k32.OpenFileMappingA.argtypes = [ctypes.c_uint32, ctypes.c_int, ctypes.c_char_p] +k32.MapViewOfFile.restype = ctypes.c_void_p +k32.MapViewOfFile.argtypes = [ctypes.c_void_p, ctypes.c_uint32, ctypes.c_uint32, ctypes.c_uint32, ctypes.c_size_t] +k32.UnmapViewOfFile.argtypes = [ctypes.c_void_p] +k32.CloseHandle.argtypes = [ctypes.c_void_p] + +def get_shm(): + h = k32.OpenFileMappingA(0x0002, 0, SHM_NAME.encode()) # FILE_MAP_WRITE first + if not h: + h = k32.OpenFileMappingA(0x0004, 0, SHM_NAME.encode()) # fallback FILE_MAP_READ + if not h: + err = ctypes.get_last_error() + print(f'FATAL: cannot open SHM {SHM_NAME} (err={err})') + sys.exit(1) + ptr = k32.MapViewOfFile(h, 0x0002, 0, 0, 0) # FILE_MAP_WRITE + if not ptr: + print(f'FATAL: MapViewOfFile err={ctypes.get_last_error()}') + sys.exit(1) + return h, ptr + +def w32(ptr, off, val): + ctypes.c_uint32.from_address(ptr + off).value = val + +def send_ctrl(ptr, type_, arg0, arg1=0, channel=0): + idx = ctypes.c_uint32.from_address(ptr + OFF_CQ_COUNT).value + if idx >= 8: + print('FATAL: control queue full') + sys.exit(1) + base = ptr + OFF_CQ + idx * CTRL_ENTRY + ctypes.c_uint32.from_address(base + 0).value = type_ + ctypes.c_uint32.from_address(base + 4).value = arg0 + ctypes.c_uint32.from_address(base + 8).value = arg1 + ctypes.c_uint32.from_address(base + 12).value = channel + ctypes.c_uint32.from_address(ptr + OFF_CQ_COUNT).value = idx + 1 + print(f'sent ctrl type={type_} arg0={arg0}') + +def send_midi(ptr, cmd, channel, pitch, velocity, live, data2=0, data3=0): + idx = ctypes.c_uint32.from_address(ptr + OFF_MQ_COUNT).value + if idx >= 64: + print('FATAL: midi queue full') + sys.exit(1) + base = ptr + OFF_MQ + idx * MIDI_ENTRY + ctypes.c_uint8.from_address(base + 0).value = cmd + ctypes.c_uint8.from_address(base + 1).value = channel + ctypes.c_uint8.from_address(base + 2).value = pitch + ctypes.c_uint8.from_address(base + 3).value = velocity + ctypes.c_uint8.from_address(base + 4).value = data2 + ctypes.c_uint8.from_address(base + 5).value = data3 + ctypes.c_uint8.from_address(base + 6).value = 1 if live else 0 # reserved[0] = live flag + ctypes.c_uint8.from_address(base + 7).value = 0 + ctypes.c_uint32.from_address(base + 8).value = 0 # sampleOffset + ctypes.c_uint32.from_address(ptr + OFF_MQ_COUNT).value = idx + 1 + print(f'sent midi cmd=0x{cmd:x} ch={channel} pitch={pitch} vel={velocity} live={live}') + +h, ptr = get_shm() +print(f'SHM {SHM_NAME} mapped at {ptr:#x}') + +# 1) transport STOP -> guard arms +send_ctrl(ptr, 3, 0) # TRANSPORT STOP +time.sleep(1.0) +# 2) non-live NOTE_ON -> must be dropped (no [midi] ON log) +send_midi(ptr, 0x9, 0, 60, 100, live=0) +time.sleep(1.0) +# 3) live NOTE_ON -> must pass +send_midi(ptr, 0x9, 0, 60, 100, live=1) +time.sleep(1.0) +# 4) non-live NOTE_ON again -> dropped (sanity) +send_midi(ptr, 0x9, 0, 62, 90, live=0) +time.sleep(1.0) +# 5) restore PLAY so app continues working +send_ctrl(ptr, 3, 1) +time.sleep(0.5) + +k32.UnmapViewOfFile(ptr) +k32.CloseHandle(h) +print('DONE') diff --git a/tools/test_midi_polyphony.js b/tools/test_midi_polyphony.js new file mode 100644 index 0000000..1428158 --- /dev/null +++ b/tools/test_midi_polyphony.js @@ -0,0 +1,59 @@ +// tools/test_midi_polyphony.js — runnable check cho Phase A (dedup/legato/polyphony). +// Đọc app.jsx, extract 2 hàm thuần, eval, assert spec §II.1 + §II.5. +const fs = require('fs'); +const path = require('path'); +const src = fs.readFileSync(path.join(__dirname, '..', 'app', 'static', 'js', 'app.jsx'), 'utf8'); + +const start = src.indexOf('const _cleanNativeNotes = (notes) => {'); +const end = src.indexOf('const _nativeTrackKey', start); +if (start < 0 || end < 0) { console.error('FAIL: khong tim thay 2 ham'); process.exit(1); } +const code = src.slice(start, end) + + ';globalThis._cleanNativeNotes=_cleanNativeNotes;' + + 'globalThis._scaleNativePolyphony=_scaleNativePolyphony;'; +// eslint-disable-next-line no-eval +(0, eval)(code); // indirect eval: const khai báo ở global scope + +let fails = 0; +const assert = (cond, msg) => { if (!cond) { fails++; console.error('FAIL: ' + msg); } else { console.log('ok: ' + msg); } }; +const near = (a, b, eps) => Math.abs(a - b) < (eps || 1e-9); + +// 1. 8-note chord cùng lúc → tổng peak ≈ 0.3*8/sqrt(8) ≤ 1.0 +const chord = []; +for (let i = 0; i < 8; i++) chord.push({ pitch: 60 + i, start_beat: 0, duration_beats: 2, velocity: 0.3 }); +const sc = _scaleNativePolyphony(_cleanNativeNotes(chord)); +const sum = sc.reduce((a, n) => a + n.velocity, 0); +assert(sc.length === 8, 'chord giu 8 note'); +assert(near(sum, 8 * 0.3 / Math.sqrt(8)), 'tong peak ' + sum.toFixed(4) + ' ~= ' + (8 * 0.3 / Math.sqrt(8)).toFixed(4)); +assert(sum <= 1.0, 'tong peak <= 1.0'); + +// 2. Solo → giữ nguyên velocity +const solo = _scaleNativePolyphony([{ pitch: 60, start_beat: 0, duration_beats: 1, velocity: 0.8 }]); +assert(near(solo[0].velocity, 0.8), 'solo velocity giu nguyen 0.8'); + +// 3. Dedup: cùng pitch + start_beat → gộp, velocity max +const dup = _cleanNativeNotes([ + { pitch: 60, start_beat: 1, duration_beats: 2, velocity: 0.5 }, + { pitch: 60, start_beat: 1, duration_beats: 3, velocity: 0.7 }, +]); +assert(dup.length === 1, 'dedup gop 2 note'); +assert(near(dup[0].velocity, 0.7), 'dedup giu velocity cao 0.7'); +assert(near(dup[0].duration_beats, 3), 'dedup giu duration dai nhat'); + +// 4. Legato: note trước cắt 0.01 beats trước note sau cùng pitch +const leg = _cleanNativeNotes([ + { pitch: 60, start_beat: 0, duration_beats: 4, velocity: 0.8 }, + { pitch: 60, start_beat: 2, duration_beats: 1, velocity: 0.8 }, +]); +assert(leg.length === 2, 'legato giu 2 note'); +assert(near(leg[0].duration_beats, 2 - 0.01, 1e-6), 'note dau cat con ' + leg[0].duration_beats); +assert(near(leg[1].duration_beats, 1), 'note sau giu nguyen'); + +// 5. Nối tiếp khác pitch → không cắt +const seq = _cleanNativeNotes([ + { pitch: 60, start_beat: 0, duration_beats: 4, velocity: 0.8 }, + { pitch: 64, start_beat: 2, duration_beats: 1, velocity: 0.8 }, +]); +assert(near(seq[0].duration_beats, 4), 'khac pitch khong bi cat'); + +console.log(fails ? 'FAILED: ' + fails : 'ALL PASS'); +process.exit(fails ? 1 : 0); diff --git a/tools/test_sf2_ring.py b/tools/test_sf2_ring.py new file mode 100644 index 0000000..befd7cb --- /dev/null +++ b/tools/test_sf2_ring.py @@ -0,0 +1,151 @@ +# Test VST2 host: create SHM, spawn bridge (stdout->file), LOAD_INSTRUMENT (VST2), +# noteOn, read ring audio energy. Verify [Vst2Instrument] loaded log. +import ctypes, subprocess, sys, time, os, tempfile + +sys.stdout.reconfigure(encoding='utf-8', errors='replace') +SHM_NAME = 'SonicForge_DAW_IPC_V2TEST' +SHM_SIZE = 30000 +PLUGIN = r'C:/Users/locpham/SonicForgeStudio/app/storage/soundfonts/518e850f-a5d3-4790-b1f9-0c90c203c524.sf2' +BRIDGE = r'C:\Users\locpham\SonicForgeStudio\native_bridge\build\Release\daw_vst_bridge.exe' + +k32 = ctypes.WinDLL('kernel32', use_last_error=True) +k32.CreateFileMappingA.restype = ctypes.c_void_p +k32.CreateFileMappingA.argtypes = [ctypes.c_void_p, ctypes.c_void_p, ctypes.c_uint32, ctypes.c_uint32, ctypes.c_uint32, ctypes.c_char_p] +k32.MapViewOfFile.restype = ctypes.c_void_p +k32.MapViewOfFile.argtypes = [ctypes.c_void_p, ctypes.c_uint32, ctypes.c_uint32, ctypes.c_uint32, ctypes.c_size_t] +k32.UnmapViewOfFile.argtypes = [ctypes.c_void_p] +k32.CloseHandle.argtypes = [ctypes.c_void_p] + +h = k32.CreateFileMappingA(ctypes.c_void_p(-1), None, 0x04, 0, SHM_SIZE, SHM_NAME.encode()) +if not h: + print('FATAL CreateFileMappingA', ctypes.get_last_error()); sys.exit(1) +ptr = k32.MapViewOfFile(h, 0x02, 0, 0, 0) +if not ptr: + print('FATAL MapViewOfFile', ctypes.get_last_error()); sys.exit(1) + +def w32(off, val): ctypes.c_uint32.from_address(ptr + off).value = val +def r32(off): return ctypes.c_uint32.from_address(ptr + off).value +def energy(off, n=256): + arr = (ctypes.c_float * n).from_address(ptr + off) + return sum(abs(v) for v in arr) + +OFF_MQ = 2064; OFF_MQ_COUNT = 2832 +OFF_CQ = 2836; OFF_CQ_COUNT = 11156 +OFF_HEARTBEAT = 11164 +MIDI_ENTRY = 12; CTRL_ENTRY = 1040 +OFF_RING_L = 11172; OFF_RING_R = 11172 + 8192 +OFF_RING_MASK = 11168 + +def send_ctrl(type_, arg0, arg1, channel, path=b''): + idx = r32(OFF_CQ_COUNT) + if idx >= 8: print('FATAL ctrl full'); sys.exit(1) + base = ptr + OFF_CQ + idx * CTRL_ENTRY + ctypes.c_uint32.from_address(base + 0).value = type_ + ctypes.c_uint32.from_address(base + 4).value = arg0 + ctypes.c_uint32.from_address(base + 8).value = arg1 + ctypes.c_uint32.from_address(base + 12).value = channel + if path: + ctypes.memmove(base + 16, path, len(path)) + ctypes.c_char.from_address(base + 16 + len(path)).value = b'\0' + w32(OFF_CQ_COUNT, idx + 1) + print(f'sent ctrl type={type_} arg0={arg0} arg1={arg1} ch={channel}') + +def send_midi(cmd, channel, pitch, velocity, live=1, d2=0, d3=0): + idx = r32(OFF_MQ_COUNT) + if idx >= 64: print('FATAL midi full'); sys.exit(1) + base = ptr + OFF_MQ + idx * MIDI_ENTRY + ctypes.c_uint8.from_address(base + 0).value = cmd + ctypes.c_uint8.from_address(base + 1).value = channel + ctypes.c_uint8.from_address(base + 2).value = pitch + ctypes.c_uint8.from_address(base + 3).value = velocity + ctypes.c_uint8.from_address(base + 4).value = d2 + ctypes.c_uint8.from_address(base + 5).value = d3 + ctypes.c_uint8.from_address(base + 6).value = 1 if live else 0 + ctypes.c_uint32.from_address(base + 8).value = 0 + w32(OFF_MQ_COUNT, idx + 1) + print(f'sent midi cmd=0x{cmd:x} ch={channel} p={pitch} v={velocity}') + +env = dict(os.environ) +env['SF_SHM_NAME'] = SHM_NAME +env['SF_SAMPLE_RATE'] = '44100' +env['SF_BLOCK_SIZE'] = '256' +env['SF_PARENT_PID'] = str(os.getpid()) +empty_state = os.path.join(tempfile.gettempdir(), 'sf_v2test_state.json') +open(empty_state, 'w').write('{}') +env['SF_STATE_FILE'] = empty_state + +bout = tempfile.NamedTemporaryFile(delete=False, suffix='.log', mode='w', encoding='utf-8') +bout_path = bout.name +bout.close() +with open(bout_path, 'w', encoding='utf-8') as f: + proc = subprocess.Popen([BRIDGE, '--shm', SHM_NAME], env=env, + stdout=f, stderr=subprocess.STDOUT) +print('bridge pid', proc.pid) +time.sleep(2.5) + +def tail_bridge(tag): + with open(bout_path, encoding='utf-8', errors='replace') as f: + lines = f.read().splitlines() + print(f'--- bridge {tag} ({len(lines)} lines) ---') + for l in lines[-50:]: + print(l) + +def head_bridge(tag, n=60): + with open(bout_path, encoding='utf-8', errors='replace') as f: + lines = f.read().splitlines() + print(f'--- bridge HEAD {tag} ({len(lines)} lines) ---') + for l in lines[:n]: + print(l) + +# LOAD VST2 (type enum: VST2=1) +send_ctrl(2, 2, len(PLUGIN.encode()), 0, PLUGIN.encode()) +print('proc alive after load:', proc.poll() is None) +time.sleep(2.0) +head_bridge('after LOAD') +tail_bridge('after LOAD') + +# TRANSPORT PLAY so plugins gating DSP on transport start producing sound +send_ctrl(3, 1, 0, 0) +time.sleep(0.3) +# CC7 volume max (some presets default to 0 volume). +# NOTE: bridge reads CC value from data2 (matches app.jsx pushEvent data2). +send_midi(0xB, 0, 7, 0, d2=127) +time.sleep(0.2) + +send_midi(0x9, 0, 60, 100) +print('mq count after noteOn send:', r32(OFF_MQ_COUNT)) +time.sleep(0.5) +print('mq count 0.5s later:', r32(OFF_MQ_COUNT)) +for t in (0.5, 1.0, 2.0, 4.0): + time.sleep(t) + print('energy at +', t, ':', round(max(sum(abs(v) for v in (ctypes.c_float * 256).from_address(ptr + OFF_RING_L + s * 1024)) + sum(abs(v) for v in (ctypes.c_float * 256).from_address(ptr + OFF_RING_R + s * 1024)) for s in range(8)), 3)) + +mask = r32(OFF_RING_MASK) or 7 +wi = r32(4) +slots = [(wi - i) & mask for i in range(mask + 1)] +energies = [] +for s in slots: + e = energy(OFF_RING_L + s * 1024) + energy(OFF_RING_R + s * 1024) + energies.append(e) +print('ringMask', mask, 'writeIndex', wi, 'heartbeat', r32(OFF_HEARTBEAT)) +print('slot energies (L+R):', [round(e, 3) for e in energies]) +print('max energy:', round(max(energies), 3)) + +send_midi(0x8, 0, 60, 0) +time.sleep(0.5) +tail_bridge('tail') + +proc.terminate() +time.sleep(1) +if proc.poll() is None: + proc.kill() +k32.UnmapViewOfFile(ptr); k32.CloseHandle(h) +print('--- grep Vst2Instrument in log ---') +with open(bout_path, encoding='utf-8', errors='replace') as f: + hits = [l.rstrip() for l in f if 'Vst2Instrument' in l] +for l in hits[-20:]: + print(l) +import shutil +shutil.copy(bout_path, r'C:/Users/locpham/SonicForgeStudio/tools/_bridge_keep.log') +os.remove(bout_path) +print('done') diff --git a/tools/test_vst2_gui.py b/tools/test_vst2_gui.py new file mode 100644 index 0000000..7210d08 --- /dev/null +++ b/tools/test_vst2_gui.py @@ -0,0 +1,214 @@ +# Test VST2 64-bit GUI: create SHM, spawn bridge, LOAD DUNE 3 (VST2), +# OPEN_GUI (bridge auto-creates native window), verify log "GUI attached", +# EnumWindows finds the host window + editor child, noteOn -> ring energy. +import ctypes, subprocess, sys, time, os, tempfile + +sys.stdout.reconfigure(encoding='utf-8', errors='replace') +SHM_NAME = 'SonicForge_DAW_IPC_V2GUITEST' +SHM_SIZE = 30000 +PLUGIN = sys.argv[1] if len(sys.argv) > 1 else r'C:\Program Files\Steinberg\VSTPlugins\DUNE 3.dll' +BRIDGE = r'C:\Users\locpham\SonicForgeStudio\native_bridge\build\Release\daw_vst_bridge.exe' + +k32 = ctypes.WinDLL('kernel32', use_last_error=True) +k32.CreateFileMappingA.restype = ctypes.c_void_p +k32.CreateFileMappingA.argtypes = [ctypes.c_void_p, ctypes.c_void_p, ctypes.c_uint32, ctypes.c_uint32, ctypes.c_uint32, ctypes.c_char_p] +k32.MapViewOfFile.restype = ctypes.c_void_p +k32.MapViewOfFile.argtypes = [ctypes.c_void_p, ctypes.c_uint32, ctypes.c_uint32, ctypes.c_uint32, ctypes.c_size_t] +k32.UnmapViewOfFile.argtypes = [ctypes.c_void_p] +k32.CloseHandle.argtypes = [ctypes.c_void_p] +user32 = ctypes.WinDLL('user32', use_last_error=True) +user32.EnumWindows.argtypes = [ctypes.c_void_p, ctypes.c_void_p] +user32.GetWindowThreadProcessId.argtypes = [ctypes.c_void_p, ctypes.POINTER(ctypes.c_uint32)] +user32.GetClassNameA.argtypes = [ctypes.c_void_p, ctypes.c_char_p, ctypes.c_int] +user32.GetWindowTextA.argtypes = [ctypes.c_void_p, ctypes.c_char_p, ctypes.c_int] +user32.EnumChildWindows.argtypes = [ctypes.c_void_p, ctypes.c_void_p, ctypes.c_void_p] +user32.PostMessageA.argtypes = [ctypes.c_void_p, ctypes.c_uint32, ctypes.c_void_p, ctypes.c_void_p] + +h = k32.CreateFileMappingA(ctypes.c_void_p(-1), None, 0x04, 0, SHM_SIZE, SHM_NAME.encode()) +if not h: + print('FATAL CreateFileMappingA', ctypes.get_last_error()); sys.exit(1) +ptr = k32.MapViewOfFile(h, 0x02, 0, 0, 0) +if not ptr: + print('FATAL MapViewOfFile', ctypes.get_last_error()); sys.exit(1) + +def w32(off, val): ctypes.c_uint32.from_address(ptr + off).value = val +def r32(off): return ctypes.c_uint32.from_address(ptr + off).value + +OFF_MQ = 2064; OFF_MQ_COUNT = 2832 +OFF_CQ = 2836; OFF_CQ_COUNT = 11156 +OFF_HEARTBEAT = 11164 +MIDI_ENTRY = 12; CTRL_ENTRY = 1040 +OFF_RING_L = 11172; OFF_RING_R = 11172 + 8192 +OFF_RING_MASK = 11168 + +def send_ctrl(type_, arg0, arg1, channel, path=b''): + idx = r32(OFF_CQ_COUNT) + if idx >= 8: print('FATAL ctrl full'); sys.exit(1) + base = ptr + OFF_CQ + idx * CTRL_ENTRY + ctypes.c_uint32.from_address(base + 0).value = type_ + ctypes.c_uint32.from_address(base + 4).value = arg0 + ctypes.c_uint32.from_address(base + 8).value = arg1 + ctypes.c_uint32.from_address(base + 12).value = channel + if path: + ctypes.memmove(base + 16, path, len(path)) + ctypes.c_char.from_address(base + 16 + len(path)).value = b'\0' + w32(OFF_CQ_COUNT, idx + 1) + print(f'sent ctrl type={type_} arg0={arg0} arg1={arg1} ch={channel}') + +def send_midi(cmd, channel, pitch, velocity, live=1, d2=0, d3=0): + idx = r32(OFF_MQ_COUNT) + if idx >= 64: print('FATAL midi full'); sys.exit(1) + base = ptr + OFF_MQ + idx * MIDI_ENTRY + ctypes.c_uint8.from_address(base + 0).value = cmd + ctypes.c_uint8.from_address(base + 1).value = channel + ctypes.c_uint8.from_address(base + 2).value = pitch + ctypes.c_uint8.from_address(base + 3).value = velocity + ctypes.c_uint8.from_address(base + 4).value = d2 + ctypes.c_uint8.from_address(base + 5).value = d3 + ctypes.c_uint8.from_address(base + 6).value = 1 if live else 0 + ctypes.c_uint32.from_address(base + 8).value = 0 + w32(OFF_MQ_COUNT, idx + 1) + print(f'sent midi cmd=0x{cmd:x} ch={channel} p={pitch} v={velocity}') + +env = dict(os.environ) +env['SF_SHM_NAME'] = SHM_NAME +env['SF_SAMPLE_RATE'] = '44100' +env['SF_BLOCK_SIZE'] = '256' +env['SF_PARENT_PID'] = str(os.getpid()) +empty_state = os.path.join(tempfile.gettempdir(), 'sf_v2guittest_state.json') +open(empty_state, 'w').write('{}') +env['SF_STATE_FILE'] = empty_state + +bout_path = os.path.join(tempfile.gettempdir(), 'sf_v2guittest_bridge.log') +with open(bout_path, 'w', encoding='utf-8') as f: + proc = subprocess.Popen([BRIDGE, '--shm', SHM_NAME], env=env, + stdout=f, stderr=subprocess.STDOUT) +print('bridge pid', proc.pid) +time.sleep(2.5) + +def tail_bridge(tag): + with open(bout_path, encoding='utf-8', errors='replace') as f: + lines = f.read().splitlines() + print(f'--- bridge {tag} ({len(lines)} lines) ---') + for l in lines[-40:]: + print(l) + +def dump_modules(bridge_pid): + # module base..end ranges of the bridge process (CreateToolhelp32Snapshot) + k32t = ctypes.WinDLL('kernel32', use_last_error=True) + class MODULEENTRY32W(ctypes.Structure): + _fields_ = [('dwSize', ctypes.c_uint32), ('th32ModuleID', ctypes.c_uint32), + ('th32ProcessID', ctypes.c_uint32), ('GlblcntUsage', ctypes.c_uint32), + ('ProccntUsage', ctypes.c_uint32), ('modBaseAddr', ctypes.c_void_p), + ('modBaseSize', ctypes.c_uint32), ('hModule', ctypes.c_void_p), + ('szModule', ctypes.c_wchar * 256), ('szExePath', ctypes.c_wchar * 260)] + k32t.CreateToolhelp32Snapshot.restype = ctypes.c_void_p + k32t.CreateToolhelp32Snapshot.argtypes = [ctypes.c_uint32, ctypes.c_uint32] + k32t.Module32FirstW.argtypes = [ctypes.c_void_p, ctypes.POINTER(MODULEENTRY32W)] + k32t.Module32NextW.argtypes = [ctypes.c_void_p, ctypes.POINTER(MODULEENTRY32W)] + snap = k32t.CreateToolhelp32Snapshot(0x8, bridge_pid) # TH32CS_SNAPMODULE + if snap == ctypes.c_void_p(-1).value or not snap: + print('module snapshot failed', ctypes.get_last_error()); return [] + me = MODULEENTRY32W(); me.dwSize = ctypes.sizeof(MODULEENTRY32W) + mods = [] + if k32t.Module32FirstW(snap, ctypes.byref(me)): + while True: + base = me.modBaseAddr or 0 + mods.append((base, base + me.modBaseSize, me.szModule)) + if not k32t.Module32NextW(snap, ctypes.byref(me)): + break + k32t.CloseHandle(snap) + return mods + +def dump_windows(bridge_pid): + found = [] + @ctypes.WINFUNCTYPE(ctypes.c_int, ctypes.c_void_p, ctypes.c_void_p) + def cb(hwnd, lparam): + pid = ctypes.c_uint32() + user32.GetWindowThreadProcessId(hwnd, ctypes.byref(pid)) + if pid.value == bridge_pid: + cls = ctypes.create_string_buffer(128) + user32.GetClassNameA(hwnd, cls, 128) + title = ctypes.create_string_buffer(256) + user32.GetWindowTextA(hwnd, title, 256) + found.append((hwnd, cls.value.decode('latin1'), title.value.decode('latin1', 'replace'))) + return 1 + user32.EnumWindows(cb, 0) + print(f'--- top-level windows of pid {bridge_pid}: {len(found)} ---') + for hwnd, cls, title in found: + print(f' hwnd={hwnd:#x} class={cls!r} title={title!r}') + # enumerate children of this window + kids = [] + @ctypes.WINFUNCTYPE(ctypes.c_int, ctypes.c_void_p, ctypes.c_void_p) + def cbk(hw, lp): + c2 = ctypes.create_string_buffer(128) + user32.GetClassNameA(hw, c2, 128) + t2 = ctypes.create_string_buffer(256) + user32.GetWindowTextA(hw, t2, 256) + kids.append((hw, c2.value.decode('latin1'), t2.value.decode('latin1', 'replace'))) + return 1 + user32.EnumChildWindows(hwnd, cbk, 0) + for kh, kc, kt in kids: + print(f' child hwnd={kh:#x} class={kc!r} title={kt!r}') + return found + +# LOAD VST2 (type enum: VST2=1) +send_ctrl(2, 1, len(PLUGIN.encode()), 0, PLUGIN.encode()) +print('proc alive after load:', proc.poll() is None) +time.sleep(2.0) +tail_bridge('after LOAD') + +pbase = os.path.basename(PLUGIN).lower().replace('.dll', '') +print('--- bridge modules (plugin-related) ---') +for b, e, name in dump_modules(proc.pid): + nl = name.lower() + if 'sonicforge' in nl or 'daw_vst' in nl or pbase in nl: + print(f' {b:#x}..{e:#x} {name}') + +# OPEN_GUI: type=4, arg1=0 (bridge creates native window), arg2=pluginId +send_ctrl(4, 0, 0, 0, PLUGIN.encode()) +time.sleep(3.0) +tail_bridge('after OPEN_GUI') +print('proc alive after open_gui:', proc.poll() is None) +wins = dump_windows(proc.pid) +host = [w for w in wins if 'SonicForge_Native_VST3_Class' in w[1]] +print('host window found:', len(host) > 0) +if host: + child_count = 0 + for w in wins: + pass + # count editor children under the host window + kids = [] + @ctypes.WINFUNCTYPE(ctypes.c_int, ctypes.c_void_p, ctypes.c_void_p) + def cbk2(hw, lp): + c2 = ctypes.create_string_buffer(128) + user32.GetClassNameA(hw, c2, 128) + kids.append((hw, c2.value.decode('latin1'))) + return 1 + user32.EnumChildWindows(host[0][0], cbk2, 0) + print('editor child windows under host:', len(kids), [k[1] for k in kids]) + +# TRANSPORT PLAY + note -> energy +send_ctrl(3, 1, 0, 0) +time.sleep(0.3) +send_midi(0xB, 0, 7, 127) +time.sleep(0.2) +send_midi(0x9, 0, 60, 100) +for t in (0.5, 1.0, 2.0): + time.sleep(t) + mask = r32(OFF_RING_MASK) or 7 + wi = r32(4) + slots = [(wi - i) & mask for i in range(mask + 1)] + es = [] + for s in slots: + e = sum(abs(v) for v in (ctypes.c_float * 256).from_address(ptr + OFF_RING_L + s * 1024)) + \ + sum(abs(v) for v in (ctypes.c_float * 256).from_address(ptr + OFF_RING_R + s * 1024)) + es.append(e) + print(f'energy at +{t}: max={round(max(es), 3)} heartbeat={r32(OFF_HEARTBEAT)}') + +print('proc alive at end:', proc.poll() is None) +proc.terminate() +time.sleep(0.5) +k32.UnmapViewOfFile(ptr) +k32.CloseHandle(h) +print('DONE') diff --git a/tools/test_vst2_host.py b/tools/test_vst2_host.py new file mode 100644 index 0000000..2e96dfe --- /dev/null +++ b/tools/test_vst2_host.py @@ -0,0 +1,137 @@ +# Test VST2 host: create SHM, spawn bridge (stdout->file), LOAD_INSTRUMENT (VST2), +# noteOn, read ring audio energy. Verify [Vst2Instrument] loaded log. +import ctypes, subprocess, sys, time, os, tempfile + +sys.stdout.reconfigure(encoding='utf-8', errors='replace') +SHM_NAME = 'SonicForge_DAW_IPC_V2TEST' +SHM_SIZE = 30000 +PLUGIN = r'C:\Program Files\Steinberg\VSTPlugins\DUNE 3.dll' +BRIDGE = r'C:\Users\locpham\SonicForgeStudio\native_bridge\build\Release\daw_vst_bridge.exe' + +k32 = ctypes.WinDLL('kernel32', use_last_error=True) +k32.CreateFileMappingA.restype = ctypes.c_void_p +k32.CreateFileMappingA.argtypes = [ctypes.c_void_p, ctypes.c_void_p, ctypes.c_uint32, ctypes.c_uint32, ctypes.c_uint32, ctypes.c_char_p] +k32.MapViewOfFile.restype = ctypes.c_void_p +k32.MapViewOfFile.argtypes = [ctypes.c_void_p, ctypes.c_uint32, ctypes.c_uint32, ctypes.c_uint32, ctypes.c_size_t] +k32.UnmapViewOfFile.argtypes = [ctypes.c_void_p] +k32.CloseHandle.argtypes = [ctypes.c_void_p] + +h = k32.CreateFileMappingA(ctypes.c_void_p(-1), None, 0x04, 0, SHM_SIZE, SHM_NAME.encode()) +if not h: + print('FATAL CreateFileMappingA', ctypes.get_last_error()); sys.exit(1) +ptr = k32.MapViewOfFile(h, 0x02, 0, 0, 0) +if not ptr: + print('FATAL MapViewOfFile', ctypes.get_last_error()); sys.exit(1) + +def w32(off, val): ctypes.c_uint32.from_address(ptr + off).value = val +def r32(off): return ctypes.c_uint32.from_address(ptr + off).value +def energy(off, n=256): + arr = (ctypes.c_float * n).from_address(ptr + off) + return sum(abs(v) for v in arr) + +OFF_MQ = 2064; OFF_MQ_COUNT = 2832 +OFF_CQ = 2836; OFF_CQ_COUNT = 11156 +OFF_HEARTBEAT = 11164 +MIDI_ENTRY = 12; CTRL_ENTRY = 1040 +OFF_RING_L = 11172; OFF_RING_R = 11172 + 8192 +OFF_RING_MASK = 11168 + +def send_ctrl(type_, arg0, arg1, channel, path=b''): + idx = r32(OFF_CQ_COUNT) + if idx >= 8: print('FATAL ctrl full'); sys.exit(1) + base = ptr + OFF_CQ + idx * CTRL_ENTRY + ctypes.c_uint32.from_address(base + 0).value = type_ + ctypes.c_uint32.from_address(base + 4).value = arg0 + ctypes.c_uint32.from_address(base + 8).value = arg1 + ctypes.c_uint32.from_address(base + 12).value = channel + if path: + ctypes.memmove(base + 16, path, len(path)) + ctypes.c_char.from_address(base + 16 + len(path)).value = b'\0' + w32(OFF_CQ_COUNT, idx + 1) + print(f'sent ctrl type={type_} arg0={arg0} arg1={arg1} ch={channel}') + +def send_midi(cmd, channel, pitch, velocity, live=1, d2=0, d3=0): + idx = r32(OFF_MQ_COUNT) + if idx >= 64: print('FATAL midi full'); sys.exit(1) + base = ptr + OFF_MQ + idx * MIDI_ENTRY + ctypes.c_uint8.from_address(base + 0).value = cmd + ctypes.c_uint8.from_address(base + 1).value = channel + ctypes.c_uint8.from_address(base + 2).value = pitch + ctypes.c_uint8.from_address(base + 3).value = velocity + ctypes.c_uint8.from_address(base + 4).value = d2 + ctypes.c_uint8.from_address(base + 5).value = d3 + ctypes.c_uint8.from_address(base + 6).value = 1 if live else 0 + ctypes.c_uint32.from_address(base + 8).value = 0 + w32(OFF_MQ_COUNT, idx + 1) + print(f'sent midi cmd=0x{cmd:x} ch={channel} p={pitch} v={velocity}') + +env = dict(os.environ) +env['SF_SHM_NAME'] = SHM_NAME +env['SF_SAMPLE_RATE'] = '44100' +env['SF_BLOCK_SIZE'] = '256' +env['SF_PARENT_PID'] = str(os.getpid()) +empty_state = os.path.join(tempfile.gettempdir(), 'sf_v2test_state.json') +open(empty_state, 'w').write('{}') +env['SF_STATE_FILE'] = empty_state + +bout = tempfile.NamedTemporaryFile(delete=False, suffix='.log', mode='w', encoding='utf-8') +bout_path = bout.name +bout.close() +with open(bout_path, 'w', encoding='utf-8') as f: + proc = subprocess.Popen([BRIDGE, '--shm', SHM_NAME], env=env, + stdout=f, stderr=subprocess.STDOUT) +print('bridge pid', proc.pid) +time.sleep(2.5) + +def tail_bridge(tag): + with open(bout_path, encoding='utf-8', errors='replace') as f: + lines = f.read().splitlines() + print(f'--- bridge {tag} ({len(lines)} lines) ---') + for l in lines[-50:]: + print(l) + +# LOAD VST2 (type enum: VST2=1) +send_ctrl(2, 1, len(PLUGIN.encode()), 0, PLUGIN.encode()) +print('proc alive after load:', proc.poll() is None) +time.sleep(2.0) +tail_bridge('after LOAD') + +# TRANSPORT PLAY so plugins gating DSP on transport start producing sound +send_ctrl(3, 1, 0, 0) +time.sleep(0.3) +# CC7 volume max (some presets default to 0 volume) +send_midi(0xB, 0, 7, 127) +time.sleep(0.2) + +send_midi(0x9, 0, 60, 100) +for t in (0.5, 1.0, 2.0, 4.0): + time.sleep(t) + print('energy at +', t, ':', round(max(sum(abs(v) for v in (ctypes.c_float * 256).from_address(ptr + OFF_RING_L + s * 1024)) + sum(abs(v) for v in (ctypes.c_float * 256).from_address(ptr + OFF_RING_R + s * 1024)) for s in range(8)), 3)) + +mask = r32(OFF_RING_MASK) or 7 +wi = r32(4) +slots = [(wi - i) & mask for i in range(mask + 1)] +energies = [] +for s in slots: + e = energy(OFF_RING_L + s * 1024) + energy(OFF_RING_R + s * 1024) + energies.append(e) +print('ringMask', mask, 'writeIndex', wi, 'heartbeat', r32(OFF_HEARTBEAT)) +print('slot energies (L+R):', [round(e, 3) for e in energies]) +print('max energy:', round(max(energies), 3)) + +send_midi(0x8, 0, 60, 0) +time.sleep(0.5) +tail_bridge('tail') + +proc.terminate() +time.sleep(1) +if proc.poll() is None: + proc.kill() +k32.UnmapViewOfFile(ptr); k32.CloseHandle(h) +print('--- grep Vst2Instrument in log ---') +with open(bout_path, encoding='utf-8', errors='replace') as f: + hits = [l.rstrip() for l in f if 'Vst2Instrument' in l] +for l in hits[-20:]: + print(l) +os.remove(bout_path) +print('done') diff --git a/tools/walk_stack.py b/tools/walk_stack.py new file mode 100644 index 0000000..5bce744 --- /dev/null +++ b/tools/walk_stack.py @@ -0,0 +1,69 @@ +import struct + +path = r"C:/Users/locpham/AppData/Local/Temp/sf_dumps/bridge_crash_1696.dmp" +data = open(path, "rb").read() +sig, ver, numstreams, sdir = struct.unpack_from("= start + msz: break + val = struct.unpack_from(" 0x1000: # code pointer heuristic (offset beyond header) + print(" %016x [%04x] %016x -> %s+%x" % (addr, j, val, nm, val - b))