Phase 3: gộp builtin DSP + VST FX thành 1 FX CHAIN (track + master), đổi tên panel/nút/menu, fix lookup track cho nút FX TCP
This commit is contained in:
+15
-2
@@ -54,8 +54,21 @@
|
||||
### Cần xác nhận (review)
|
||||
- (trống)
|
||||
|
||||
## PHASE 3 — FX CHAIN
|
||||
(chưa bắt đầu)
|
||||
## PHASE 3 — FX CHAIN (BUILTIN DSP + VST FX 1 CHAIN)
|
||||
- [x] `FXRackModal`: header `{track.name} — FX RACK PANEL` → `{track.name} — FX CHAIN` (grep sạch "FX RACK PANEL" còn lại chỉ comment nội bộ dòng 11478).
|
||||
- [x] Gộp CHAIN RACK STRIP + block VST FX CHAIN thành MỘT strip `unifiedChain = [...chain.map(m=>({kind:'builtin',m,i})), ...vstChain.map(s=>({kind:'vst',s,i}))]`:
|
||||
- Slot builtin = card cũ nguyên vẹn (power/toggleMod, name, remove, click → `setActiveType(m.type)`).
|
||||
- Slot vst = card compact (power/toggleVst, name, removeVst, click → `setActiveVstIdx(u.i)`, active = viền teal).
|
||||
- Drag reorder trên unifiedChain → split lại `setChain(nb)` + `setVstChain(nv)`; khi kéo slot vst → `setActiveVstIdx(nv.indexOf(mv.s))` giữ đúng slot đang chọn.
|
||||
- 2 nút thêm cuối strip: `[+]` (addModuleOpen, DSP) + `[VST]` (vstPickerOpen).
|
||||
- [x] Giữ toolbar VST mỏng dưới strip: "Nghe thử (render offline)" (`previewFx`) + checkbox Bypass + hint VST3 áp dụng lúc Export/Offline.
|
||||
- [x] Workspace: khi `activeVstIdx>=0 && < vstChain.length && !fxGuiOpen` → hiện detail VST đầy đủ (vstRow: select đổi plugin, Preset upload, GUI, Carla, lên/xuống, xóa); ngược lại → module editor builtin như cũ. `addMod` giờ reset `setActiveVstIdx(-1)` để quay về editor builtin khi thêm DSP.
|
||||
- [x] `MasteringModal`: header `MASTERING SUITE` → `MASTER FX CHAIN` (giữ badge "WEB MASTERING V10.5" làm subtitle); popup thêm module → `THÊM MODULE VÀO MASTER FX CHAIN`. Chain strip master đã unified sẵn từ trước (builtin + vst3 1 strip).
|
||||
- [x] Nút "MASTERING PANEL" (MasterStripConsole) → "FX CHAIN"; tooltip "Mở/Bật-Tắt MASTERING PANEL ..." → "MASTER FX CHAIN ..."; menu File "Mastering Suite" → "Master FX Chain" (giữ Ctrl+Shift+M).
|
||||
- [x] Phase 4 verify sẵn: `__openFxRack(track.id, track.name)` → `fxRackTarget` → `FXRackModal` lookup track giờ dùng `(activeTracksRef.current || []).find(...) || tracks.find(...) || null` — an toàn khi activeTracks là session-tab clones ≠ root tracks (trước: `tracks.find` có thể null → modal không mở).
|
||||
- [x] Build: `node build.mjs` OK (2 lần — lần 2 sau fix lookup); copy `app/static/js/app.precompiled.js` + `app/templates/index.html` sang bundle `src-tauri/target/release/daw_engine/_internal/app/` (frontend-only, không cần PyInstaller); bump cache-bust `?v=202608211133`.
|
||||
- [x] Verify chạy thật: engine serve precompiled md5 `9d49f209...` khớp build local, index `?v=202608211133`, bundle chứa "FX CHAIN"/"MASTER FX CHAIN"/`unifiedChain`/lookup activeTracksRef; 3 regression cũ PASS (`shm_note_energy` energy 0 = app chưa load instrument đúng; `test_sf2_ring` energy 12.337→0.046; `test_vst2_gui` DUNE host + editor VSTGUI, proc alive).
|
||||
|
||||
|
||||
## PHASE 4 — NÚT FX TCP
|
||||
(chưa bắt đầu)
|
||||
|
||||
+78
-43
@@ -2390,7 +2390,7 @@ const MasterStripConsole = ({ masterVolume, setMasterVolume, showMasteringModal,
|
||||
React.createElement("button", {
|
||||
onClick: () => setShowMasteringModal(true),
|
||||
className: "w-full bg-slate-800 hover:bg-slate-700 text-[10px] font-semibold py-1 rounded border border-slate-700 text-slate-300 tracking-tight"
|
||||
}, "MASTERING PANEL"),
|
||||
}, "FX CHAIN"),
|
||||
React.createElement("div", {
|
||||
className: "flex items-center justify-between bg-slate-950 border border-slate-800 rounded px-1.5 py-0.5 text-[10px] font-mono"
|
||||
},
|
||||
@@ -2524,7 +2524,7 @@ const MasterStripConsole = ({ masterVolume, setMasterVolume, showMasteringModal,
|
||||
id: "fxBtn",
|
||||
className: "btn-daw h-[18px] rounded font-extrabold text-[10px] transition-all",
|
||||
onClick: function() { setShowMasteringModal(true); },
|
||||
title: "Mở MASTERING PANEL để chỉnh sửa"
|
||||
title: "Mở MASTER FX CHAIN để chỉnh sửa"
|
||||
}, "FX"),
|
||||
React.createElement("button", {
|
||||
id: "powerBtn",
|
||||
@@ -2534,7 +2534,7 @@ const MasterStripConsole = ({ masterVolume, setMasterVolume, showMasteringModal,
|
||||
setMasteringSettings(function(prev) { return Object.assign({}, prev, { masterConnected: !prev.masterConnected, isBypassed: false }); });
|
||||
}
|
||||
},
|
||||
title: "Bật/Tắt MASTERING PANEL Bypass"
|
||||
title: "Bật/Tắt MASTER FX CHAIN Bypass"
|
||||
}, [React.createElement("i", { className: "fa-solid fa-power-off" + (isFxActive ? " text-emerald-400" : " text-slate-500"), key: "ico" }), React.createElement("span", { key: "lbl", className: "text-[7px] font-bold" + (isFxActive ? " text-emerald-300" : " text-slate-400") }, "PWR")]),
|
||||
React.createElement("button", { className: "btn-daw h-[18px] rounded text-slate-400 text-[8px]", title: "Trim Envelope" }, "TRIM"),
|
||||
React.createElement("button", { className: "btn-daw h-[18px] rounded text-slate-400 text-[9px]", title: "Session Info" },
|
||||
@@ -12180,6 +12180,11 @@ const FXRackModal = ({ track, onUpdateTrack, onClose }) => {
|
||||
|
||||
// ── VST FX chain (native_bridge --render-fx, áp dụng khi Export/Offline render) ──
|
||||
const vstChain = (track.vstFxChain || []);
|
||||
// Unified chain strip: builtin DSP + VST FX chung 1 chain (kéo đổi thứ tự giữa cả 2 loại).
|
||||
const unifiedChain = [
|
||||
...chain.map((m, i) => ({ kind: 'builtin', m, i })),
|
||||
...vstChain.map((s, i) => ({ kind: 'vst', s, i })),
|
||||
];
|
||||
const setVstChain = (next) => { if (onUpdateTrack) onUpdateTrack(track.id, { vstFxChain: next, vstFxBypass: vstBypass }); };
|
||||
// Embedded VST GUI state (PLAN_MASTERBUS_VST_GUI_EMBED.md Phase 3)
|
||||
const [fxGuiOpen, setFxGuiOpenState] = React.useState(_persistFxGuiOpen); // { path, url } | null
|
||||
@@ -12269,6 +12274,7 @@ const addVst = (path) => { const fx = (fxList || []).find(f => f.path === path);
|
||||
const params = Array.isArray(def.bands) ? { ...def, bands: JSON.parse(JSON.stringify(def.bands)) } : { ...def };
|
||||
setChain([...chain, { type, active: true, params }]);
|
||||
setActiveType(type);
|
||||
setActiveVstIdx(-1);
|
||||
setAddModuleOpen(false);
|
||||
};
|
||||
const applyPreset = (key) => {
|
||||
@@ -12479,68 +12485,87 @@ const addVst = (path) => { const fx = (fxList || []).find(f => f.path === path);
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="w-3 h-3 rounded-full bg-cyan-400 animate-pulse"></div>
|
||||
<h3 className="text-xs font-bold text-white uppercase tracking-wider font-mono">
|
||||
{track.name} — FX RACK PANEL
|
||||
{track.name} — FX CHAIN
|
||||
</h3>
|
||||
</div>
|
||||
<button onClick={onClose} className="text-slate-400 hover:text-white text-base"><i data-lucide="x" className="w-4 h-4"></i></button>
|
||||
</div>
|
||||
|
||||
{/* CHAIN RACK STRIP */}
|
||||
{/* FX CHAIN STRIP (builtin DSP + VST FX chung 1 chain — kéo đổi thứ tự giữa cả 2 loại) */}
|
||||
<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">
|
||||
<span className="text-[10px] font-bold text-slate-500 uppercase tracking-widest mr-1 font-mono shrink-0">CHAIN:</span>
|
||||
{chain.length === 0 && <span className="text-[10px] text-slate-500 font-mono">Chưa có FX — bấm [+] để thêm (signal đi thẳng: Source → Fader)</span>}
|
||||
{chain.map((m, idx) => {
|
||||
const meta = TRACK_FX_META[m.type] || { name: m.type, icon: 'circle', color: '#94a3b8', sub: '' };
|
||||
const on = m.active !== false;
|
||||
{unifiedChain.length === 0 && <span className="text-[10px] text-slate-500 font-mono">Chưa có FX — bấm [+] (DSP) hoặc [+VST] để thêm (signal đi thẳng: Source → Fader)</span>}
|
||||
{unifiedChain.map((u, ui) => {
|
||||
if (u.kind === 'builtin') {
|
||||
const m = u.m;
|
||||
const meta = TRACK_FX_META[m.type] || { name: m.type, icon: 'circle', color: '#94a3b8', sub: '' };
|
||||
const on = m.active !== false;
|
||||
return (
|
||||
<div key={`b${u.i}`}
|
||||
draggable
|
||||
onDragStart={e => { dragChainIndexRef.current = ui; e.dataTransfer.effectAllowed = 'move'; }}
|
||||
onDragOver={e => { e.preventDefault(); e.dataTransfer.dropEffect = 'move'; }}
|
||||
onDrop={e => { e.preventDefault(); const from = dragChainIndexRef.current; if (from !== null && from !== ui) { const next = [...unifiedChain]; const mv = next.splice(from, 1)[0]; next.splice(ui, 0, mv); const nb = next.filter(x => x.kind === 'builtin').map(x => x.m); const nv = next.filter(x => x.kind === 'vst').map(x => x.s); setChain(nb); setVstChain(nv); if (mv.kind === 'vst') setActiveVstIdx(nv.indexOf(mv.s)); } 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'}`}>
|
||||
<div className="flex items-center gap-2 min-w-0">
|
||||
<button onClick={e => { e.stopPropagation(); toggleMod(u.i); }} 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' }}>
|
||||
<i data-lucide="power" className="w-2.5 h-2.5"></i>
|
||||
</button>
|
||||
<div className="min-w-0">
|
||||
<div className="text-[11px] font-bold text-slate-200 truncate">{meta.name}</div>
|
||||
<div className="text-[9px] font-mono truncate" style={{ color: meta.color }}>{u.i + 1}. {meta.sub}</div>
|
||||
</div>
|
||||
</div>
|
||||
<button onClick={e => { e.stopPropagation(); removeMod(u.i); }} className="text-slate-600 hover:text-red-400 text-xs px-0.5 shrink-0" title="Xóa module">
|
||||
<i data-lucide="x" className="w-3 h-3"></i>
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
const s = u.s;
|
||||
return (
|
||||
<div key={idx}
|
||||
<div key={`v${u.i}`}
|
||||
draggable
|
||||
onDragStart={e => { dragChainIndexRef.current = idx; e.dataTransfer.effectAllowed = 'move'; }}
|
||||
onDragStart={e => { dragChainIndexRef.current = ui; 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'}`}>
|
||||
onDrop={e => { e.preventDefault(); const from = dragChainIndexRef.current; if (from !== null && from !== ui) { const next = [...unifiedChain]; const mv = next.splice(from, 1)[0]; next.splice(ui, 0, mv); const nb = next.filter(x => x.kind === 'builtin').map(x => x.m); const nv = next.filter(x => x.kind === 'vst').map(x => x.s); setChain(nb); setVstChain(nv); if (mv.kind === 'vst') setActiveVstIdx(nv.indexOf(mv.s)); } dragChainIndexRef.current = null; }}
|
||||
onClick={() => { closeFxGui(); setActiveVstIdx(u.i); }}
|
||||
className={`w-36 h-12 rounded-lg p-2 flex items-center justify-between cursor-pointer transition-all shrink-0 ${activeVstIdx === u.i ? 'border-2 border-teal-400 bg-slate-800' : 'bg-slate-900 border border-teal-900/70 hover:border-teal-600'}`}>
|
||||
<div className="flex items-center gap-2 min-w-0">
|
||||
<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' }}>
|
||||
<button onClick={e => { e.stopPropagation(); toggleVst(u.i); }} className="w-4 h-4 rounded-full flex items-center justify-center text-[9px] font-bold shrink-0" style={{ backgroundColor: !s.bypass ? '#2dd4bf' : '#334155', color: !s.bypass ? '#0f172a' : '#94a3b8' }}>
|
||||
<i data-lucide="power" className="w-2.5 h-2.5"></i>
|
||||
</button>
|
||||
<div className="min-w-0">
|
||||
<div className="text-[11px] font-bold text-slate-200 truncate">{meta.name}</div>
|
||||
<div className="text-[9px] font-mono truncate" style={{ color: meta.color }}>{idx + 1}. {meta.sub}</div>
|
||||
<div className="text-[11px] font-bold text-teal-200 truncate">{s.name || s.path}</div>
|
||||
<div className="text-[9px] font-mono truncate text-teal-500/80">{u.i + 1}. {s.preset_b64 ? '✓ preset' : 'no preset'}</div>
|
||||
</div>
|
||||
</div>
|
||||
<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">
|
||||
<button onClick={e => { e.stopPropagation(); removeVst(u.i); }} className="text-slate-600 hover:text-red-400 text-xs px-0.5 shrink-0" title="Xóa slot">
|
||||
<i data-lucide="x" className="w-3 h-3"></i>
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
<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">
|
||||
<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 DSP builtin">
|
||||
<i data-lucide="plus" className="w-4 h-4"></i>
|
||||
</button>
|
||||
<button onClick={() => setVstPickerOpen(true)} className="w-16 h-12 rounded-lg border border-dashed border-teal-800 hover:border-teal-500 flex items-center justify-center text-teal-500 hover:text-teal-300 cursor-pointer transition-all bg-slate-900/40 shrink-0" title="Thêm VST FX (Plugin Picker theo category)">
|
||||
<i data-lucide="plug" className="w-3.5 h-3.5"></i><span className="text-[9px] font-bold font-mono ml-0.5">VST</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* VST FX CHAIN (native_bridge --render-fx — áp dụng khi Export/Offline render) */}
|
||||
<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">
|
||||
<div className="flex items-center justify-between flex-wrap gap-2">
|
||||
<div className="flex items-center gap-2 min-w-0">
|
||||
<span className="text-[10px] font-bold text-teal-400 uppercase tracking-widest font-mono shrink-0">VST FX:</span>
|
||||
<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)</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2 shrink-0">
|
||||
<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">
|
||||
<i data-lucide="play" className="w-3 h-3"></i> Nghe thử (render offline)
|
||||
</button>
|
||||
<label className="flex items-center gap-1 text-[10px] text-slate-400 font-mono cursor-pointer">
|
||||
<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
|
||||
</label>
|
||||
</div>
|
||||
{/* VST FX toolbar — VST3 (Ozone...) áp dụng lúc Export/Offline render (preview = âm export) */}
|
||||
<div className="flex items-center justify-between flex-wrap gap-2 shrink-0">
|
||||
<span className="text-[9px] text-slate-500 font-mono truncate">VST3 (Ozone...) — realtime browser vẫn WebAudio builtin; VST áp dụng lúc Export/Offline render</span>
|
||||
<div className="flex items-center gap-2 shrink-0">
|
||||
<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">
|
||||
<i data-lucide="play" className="w-3 h-3"></i> Nghe thử (render offline)
|
||||
</button>
|
||||
<label className="flex items-center gap-1 text-[10px] text-slate-400 font-mono cursor-pointer">
|
||||
<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
|
||||
</label>
|
||||
</div>
|
||||
{vstChain.length === 0 && <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).</div>}
|
||||
{vstChain.map((s, idx) => vstRow(s, idx, toggleVst, changeVstPlugin, uploadVstPreset, moveVst, removeVst, fxList, vstChain.length, openVstGui, false, activeVstIdx, (s2, i2) => openVstGui(s2, i2)))}
|
||||
<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)">
|
||||
<i data-lucide="plus" className="w-3 h-3 inline-block mr-1"></i> Thêm VST FX... (Picker: EQ / Reverb / Comp / Utility)
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* WORKSPACE CONTROLS */}
|
||||
@@ -12551,6 +12576,15 @@ const addVst = (path) => { const fx = (fxList || []).find(f => f.path === path);
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
{activeVstIdx >= 0 && activeVstIdx < vstChain.length ? (
|
||||
<div className="flex-1 bg-slate-950 border border-teal-900/60 rounded-xl p-4 flex flex-col gap-3 min-h-[240px] overflow-y-auto oz-scrollbar">
|
||||
<div className="h-8 bg-slate-900/80 border-b border-teal-900/60 px-3 flex items-center justify-between text-xs font-mono rounded-t-lg shrink-0">
|
||||
<span className="text-slate-400">Active VST: <strong className="text-teal-400">{vstChain[activeVstIdx].name || vstChain[activeVstIdx].path}</strong></span>
|
||||
<span className="text-[9px] text-slate-500">Slot {activeVstIdx + 1}/{vstChain.length} — bấm card DSP trong CHAIN để chỉnh module builtin</span>
|
||||
</div>
|
||||
{vstRow(vstChain[activeVstIdx], activeVstIdx, toggleVst, changeVstPlugin, uploadVstPreset, moveVst, removeVst, fxList, vstChain.length, openVstGui, true, activeVstIdx, null)}
|
||||
</div>
|
||||
) : (
|
||||
<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">
|
||||
<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">
|
||||
<span className="text-slate-400">Active Module: <strong className="text-cyan-400">{activeMod ? (TRACK_FX_META[activeMod.type]?.name || activeMod.type) : '—'}</strong></span>
|
||||
@@ -12645,6 +12679,7 @@ const addVst = (path) => { const fx = (fxList || []).find(f => f.path === path);
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* WAVE OBSERVER — REAL-TIME OSCILLOSCOPE (unified_fx_rack_panel_update.md §III.3) */}
|
||||
<div className="bg-slate-950 border border-slate-800 rounded-xl p-3 flex flex-col gap-2 shrink-0">
|
||||
@@ -13490,7 +13525,7 @@ const addMasterVst = (path) => { const fx = (masterFxList || []).find(f => f.pat
|
||||
</div>
|
||||
<div>
|
||||
<h1 className="text-xs font-bold tracking-wider text-white flex items-center gap-2">
|
||||
MASTERING SUITE <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</span>
|
||||
MASTER FX CHAIN <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</span>
|
||||
</h1>
|
||||
</div>
|
||||
</div>
|
||||
@@ -14057,7 +14092,7 @@ const addMasterVst = (path) => { const fx = (masterFxList || []).find(f => f.pat
|
||||
<div className="fixed inset-0 z-[110] bg-black/80 backdrop-blur-sm flex items-center justify-center p-4" onClick={() => setAddModuleOpen(false)}>
|
||||
<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()}>
|
||||
<div className="flex items-center justify-between border-b border-slate-800 pb-3">
|
||||
<h3 className="text-xs font-bold text-white uppercase tracking-wider oz-font-mono">THÊM MODULE VÀO MASTERING CHAIN</h3>
|
||||
<h3 className="text-xs font-bold text-white uppercase tracking-wider oz-font-mono">THÊM MODULE VÀO MASTER FX CHAIN</h3>
|
||||
<button onClick={() => setAddModuleOpen(false)} className="text-slate-400 hover:text-white"><i data-lucide="x" className="w-4 h-4"></i></button>
|
||||
</div>
|
||||
<div className="grid grid-cols-2 gap-3 text-xs">
|
||||
@@ -29349,7 +29384,7 @@ STRICT CONSTRAINTS:
|
||||
icon: 'music',
|
||||
action: () => triggerMidiExport()
|
||||
}, {
|
||||
label: 'Mastering Suite',
|
||||
label: 'Master FX Chain',
|
||||
icon: 'wand-2',
|
||||
shortcut: 'Ctrl+Shift+M',
|
||||
action: () => setShowMasteringModal(true)
|
||||
@@ -32647,7 +32682,7 @@ STRICT CONSTRAINTS:
|
||||
masteringSettings: masteringSettings,
|
||||
setMasteringSettings: setMasteringSettings
|
||||
}), fxRackTarget && /*#__PURE__*/React.createElement(FXRackModal, {
|
||||
track: tracks.find(t => t.id === fxRackTarget.trackId) || null,
|
||||
track: (activeTracksRef.current || []).find(t => t.id === fxRackTarget.trackId) || tracks.find(t => t.id === fxRackTarget.trackId) || null,
|
||||
onUpdateTrack: updateTrackProp,
|
||||
onClose: () => setFxRackTarget(null)
|
||||
}), /*#__PURE__*/React.createElement(ExportModal, {
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -50,7 +50,7 @@
|
||||
<script src="/static/js/services/midiExtractor.js?v=202607281052"></script>
|
||||
<script src="/static/js/services/promptTemplateManager.js?v=202607281039"></script>
|
||||
<script src="/static/js/services/undoRedoEngine.js?v=202607290941"></script>
|
||||
<script src="/static/js/app.precompiled.js?v=202608211105" defer></script>
|
||||
<script src="/static/js/app.precompiled.js?v=202608211133" defer></script>
|
||||
<link rel="stylesheet" href="/static/css/styles.css?v=202607271016">
|
||||
<style>
|
||||
:root {
|
||||
|
||||
Reference in New Issue
Block a user