diff --git a/app/static/js/app.jsx b/app/static/js/app.jsx
index b7b5c67..a4fc652 100644
--- a/app/static/js/app.jsx
+++ b/app/static/js/app.jsx
@@ -11822,6 +11822,12 @@ const serializeProjectToSchema = (projectId, name, bpmVal, tracksList, subTabsLi
section_store: sectionStore,
mastering_settings: masteringSettings ? {
...masteringSettings,
+ // FIX preset VST master mất sau save/load: GUI capture (_capturedPresetByPath)
+ // trước đây chỉ merge vào main_session.master.fx_chain, còn mastering_settings
+ // (nguồn ưu tiên lúc deserialize) giữ preset cũ/rỗng → load lại mất settings.
+ // Merge cả vstFxChain + chain (entry vst3) để 2 nơi lưu đồng nhất.
+ vstFxChain: (masteringSettings.vstFxChain || []).map(s => ({ ...s, preset_b64: _capturedPresetByPath[s.path] || s.preset_b64 || '' })),
+ chain: (masteringSettings.chain || []).map(m => m && m.type === 'vst3' ? { ...m, preset_b64: _capturedPresetByPath[m.path] || m.preset_b64 || '' } : m),
gui_open_path: (_persistMasterGuiOpen && _persistMasterGuiOpen.path) ? _persistMasterGuiOpen.path : (masteringSettings && masteringSettings.gui_open_path) || null,
gui_open_name: (_persistMasterGuiOpen && _persistMasterGuiOpen.name) ? _persistMasterGuiOpen.name : (masteringSettings && masteringSettings.gui_open_name) || null
} : null
@@ -11919,8 +11925,22 @@ const deserializeProjectFromSchema = (schemaObj) => {
masteringSettings: (() => {
const migrated = { ..._migratedMastering };
const _masterVstSources = [];
- (migrated.vstFxChain || []).forEach(s => { if (s && s.path && !_masterVstSources.some(x => x.path === s.path)) _masterVstSources.push(s); });
- _masterFx.forEach(s => { if (s && s.path && !_masterVstSources.some(x => x.path === s.path)) _masterVstSources.push(s); });
+ // Merge preset: ưu tiên vstFxChain (thứ tự), nhưng nếu preset rỗng mà
+ // main_session.master.fx_chain có preset (project cũ lưu trước fix) → lấy
+ // preset không rỗng đó (fix: settings VST master mất sau save/load).
+ const _mergeVstSource = (s) => {
+ if (!s || !s.path) return;
+ const ex = _masterVstSources.find(x => x.path === s.path);
+ if (ex) {
+ if (!ex.preset_b64 && s.preset_b64) ex.preset_b64 = s.preset_b64;
+ if (!ex.name && s.name) ex.name = s.name;
+ if (ex.bypass === undefined && s.bypass !== undefined) ex.bypass = s.bypass;
+ return;
+ }
+ _masterVstSources.push({ ...s });
+ };
+ (migrated.vstFxChain || []).forEach(_mergeVstSource);
+ _masterFx.forEach(_mergeVstSource);
const _finalChain = [...(migrated.chain || [])];
_masterVstSources.forEach(s => {
if (!_finalChain.some(m => m.type === 'vst3' && m.path === s.path)) {
@@ -13542,6 +13562,9 @@ const addMasterVst = (path) => { const fx = (masterFxList || []).find(f => f.pat
const [woMode, setWoMode] = React.useState('waveform');
const [woDuration, setWoDuration] = React.useState(2.0);
const [woZoom, setWoZoom] = React.useState(0.0);
+ // Scope KHÔNG tự chạy khi mở modal (fix: Wave Observer "built-in" auto-load
+ // khi nhấn VST FX) — chỉ render khi user bật toggle, mặc định TẮT.
+ const [woEnabled, setWoEnabled] = React.useState(false);
const woPausedRef = React.useRef(woPaused);
woPausedRef.current = woPaused;
@@ -13553,6 +13576,8 @@ const addMasterVst = (path) => { const fx = (masterFxList || []).find(f => f.pat
woDurationRef.current = woDuration;
const woZoomRef = React.useRef(woZoom);
woZoomRef.current = woZoom;
+ const woEnabledRef = React.useRef(woEnabled);
+ woEnabledRef.current = woEnabled;
const ozStateRef = React.useRef(ozState);
ozStateRef.current = ozState;
@@ -13817,7 +13842,14 @@ const addMasterVst = (path) => { const fx = (masterFxList || []).find(f => f.pat
// Wave Observer Oscilloscope Rendering
const woCanvas = woCanvasRef.current;
- if (woCanvas) {
+ if (woCanvas && !woEnabledRef.current) {
+ // Scope tắt (mặc định) — không chạy tự động: xóa canvas + zero meters.
+ const woCtx = woCanvas.getContext('2d');
+ woCtx.clearRect(0, 0, woCanvas.width, woCanvas.height);
+ if (woLeftMeterRef.current) woLeftMeterRef.current.style.width = '0%';
+ if (woRightMeterRef.current) woRightMeterRef.current.style.width = '0%';
+ }
+ if (woCanvas && woEnabledRef.current) {
const w = woCanvas.width, h = woCanvas.height;
const woCtx = woCanvas.getContext('2d');
woCtx.clearRect(0, 0, w, h);
@@ -14643,6 +14675,10 @@ const addMasterVst = (path) => { const fx = (masterFxList || []).find(f => f.pat
Real-time Oscilloscope
+
{['Scope', 'Settings', 'Help', 'About'].map(tab => (