fix(latency): threadpool renders + RENDER_LOCK + client pre-render cache
This commit is contained in:
@@ -57,10 +57,14 @@ const scheduleNativeSfItem=(track,item,offsetTime,context,destNode,bpm,opts)=>{t
|
||||
// Render 1 note bằng backend pedalboard (VST3 đúng instrument_id/preset) →
|
||||
// play WAV — giống hệt nativeSf nhưng qua midiRender; map token riêng.
|
||||
const _nativeVstPreviews={};const playNativeVstNote=(track,pitch,velocity,durationMs,startTime,key)=>{try{const eng=track&&track.synth_engine;const instrumentId=eng&&eng.plugin_id||track&&track.instrumentId;if(!instrumentId||!isVstTrackEngine(eng))return;const k=key||(track?track.id:'global');const prev=_nativeVstPreviews[k];const token=(prev?prev.token:0)+1;const durationSec=Math.max(0.2,(durationMs||500)/1000);window.SonicAPI.midiRender({instrument_id:instrumentId,notes:[{pitch:pitch,start_beat:0,duration_beats:durationSec*2,velocity:velocity!=null?velocity:0.8}],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;if(_nativeVstPreviews[k]&&_nativeVstPreviews[k].token!==token)return;// stale
|
||||
const audio=new Audio(API_BASE_URL+res.url);_nativeVstPreviews[k]={audio:audio,token:token};const ctx=getAudioContext();const delay=startTime?Math.max(0,(startTime-ctx.currentTime)*1000):0;setTimeout(function(){if(!_nativeVstPreviews[k]||_nativeVstPreviews[k].audio!==audio)return;audio.play().catch(function(){});audio._vstStopTimer&&clearTimeout(audio._vstStopTimer);audio._vstStopTimer=setTimeout(function(){try{audio.pause();}catch(e){}},durationSec*1000+400);},delay);}).catch(function(){});}catch(e){console.warn('[NativeVST] playNativeVstNote error:',e);}};const stopNativeVstNote=key=>{try{const k=key||'global';const prev=_nativeVstPreviews[k];if(!prev)return;prev.token++;try{if(prev.audio){prev.audio.pause();prev.audio.currentTime=0;}}catch(e){}}catch(e){}};const stopAllNativeVstNotes=()=>{try{Object.keys(_nativeVstPreviews).forEach(function(k){stopNativeVstNote(k);});}catch(e){}};// ── Native VSTi item render (standalone, transport) ────────────────────────
|
||||
const audio=new Audio(API_BASE_URL+res.url);_nativeVstPreviews[k]={audio:audio,token:token};const ctx=getAudioContext();const delay=startTime?Math.max(0,(startTime-ctx.currentTime)*1000):0;setTimeout(function(){if(!_nativeVstPreviews[k]||_nativeVstPreviews[k].audio!==audio)return;audio.play().catch(function(){});audio._vstStopTimer&&clearTimeout(audio._vstStopTimer);audio._vstStopTimer=setTimeout(function(){try{audio.pause();}catch(e){}},durationSec*1000+400);},delay);}).catch(function(){});}catch(e){console.warn('[NativeVST] playNativeVstNote error:',e);}};const stopNativeVstNote=key=>{try{const k=key||'global';const prev=_nativeVstPreviews[k];if(!prev)return;prev.token++;try{if(prev.audio){prev.audio.pause();prev.audio.currentTime=0;}}catch(e){}}catch(e){}};const stopAllNativeVstNotes=()=>{try{Object.keys(_nativeVstPreviews).forEach(function(k){stopNativeVstNote(k);});}catch(e){}};// URL cache cho midi-render: key = fingerprint (item+instrument+preset+notes)
|
||||
// → re-play cùng item dùng lại WAV đã render, không chờ round-trip.
|
||||
const _nativeVstItemUrls={};const _nativeVstItemKey=(track,item,bpm)=>{try{const eng=track&&track.synth_engine;const instrumentId=eng&&eng.plugin_id||track&&track.instrumentId||'';const notes=item&&item.notes||[];const nk=notes.map(function(n){return(n.pitch||60)+':'+(n.start_beat||0)+':'+(n.duration_beats||1)+':'+(n.velocity!=null?n.velocity:0.8);}).join(',');return[item&&item.id?item.id:instrumentId+':'+nk,instrumentId,eng&&eng.preset_id||'',eng&&eng.preset_path||'',bpm].join('|')+'|'+nk;}catch(e){return'';}};const _renderNativeVstItem=(track,item,bpm)=>{try{const eng=track&&track.synth_engine;const instrumentId=eng&&eng.plugin_id||track&&track.instrumentId;if(!instrumentId||!isVstTrackEngine(eng))return Promise.resolve(null);const notes=item&&item.notes||[];if(!notes.length)return Promise.resolve(null);return window.SonicAPI.midiRender({instrument_id:instrumentId,notes:notes.map(function(n){return{pitch:n.pitch||60,start_beat:n.start_beat||0,duration_beats:n.duration_beats||1,velocity:n.velocity!=null?n.velocity:0.8};}),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: play trong lúc pre-render đang chạy → chờ cùng promise,
|
||||
// không bắn duplicate request (tránh queue sau RENDER_LOCK + render 2 lần).
|
||||
const _nativeVstItemPending={};const ensureRenderNativeVstItem=(track,item,bpm)=>{const key=_nativeVstItemKey(track,item,bpm);if(!key)return _renderNativeVstItem(track,item,bpm);if(_nativeVstItemUrls[key])return Promise.resolve(_nativeVstItemUrls[key]);if(_nativeVstItemPending[key])return _nativeVstItemPending[key];const p=_renderNativeVstItem(track,item,bpm).then(function(url){if(url)_nativeVstItemUrls[key]=url;return url;}).then(function(url){delete _nativeVstItemPending[key];return url;});_nativeVstItemPending[key]=p;return p;};let _prerenderNativeVstTimer=null;const prerenderNativeVstItems=(tracks,bpm)=>{try{if(!isStandaloneSf())return;if(_prerenderNativeVstTimer){clearTimeout(_prerenderNativeVstTimer);_prerenderNativeVstTimer=null;}_prerenderNativeVstTimer=setTimeout(function(){_prerenderNativeVstTimer=null;const seen={};(tracks||[]).forEach(function(track){const nativeVst=isStandaloneSf()&&isVstTrackEngine(track&&track.synth_engine)&&!(window.SonicMidiRouter&&window.SonicMidiRouter.isBridgeActive());if(!nativeVst)return;(track.midiItems||[]).forEach(function(item){const key=_nativeVstItemKey(track,item,bpm);if(!key||seen[key]||_nativeVstItemUrls[key])return;seen[key]=true;ensureRenderNativeVstItem(track,item,bpm).then(function(){});});});},800);}catch(e){}};// ── Native VSTi item render (standalone, transport) ────────────────────────
|
||||
// Render TOÀN BỘ MIDI item bằng pedalboard (VST3) → decode AudioBuffer →
|
||||
// schedule giống scheduleNativeSfItem. opts giống hệt nativeSf.
|
||||
const scheduleNativeVstItem=(track,item,offsetTime,context,destNode,bpm,opts)=>{try{const eng=track&&track.synth_engine;const instrumentId=eng&&eng.plugin_id||track&&track.instrumentId;if(!instrumentId||!isVstTrackEngine(eng))return;const notes=item&&item.notes||[];if(!notes.length)return;const secPerBeat=60.0/(parseInt(bpm)||120);const baseOffsetSec=opts&&opts.baseOffsetSec||0;const itemStartAbs=baseOffsetSec+(item.startTime||0);let itemEndAbs=itemStartAbs+0.05;notes.forEach(function(n){const end=itemStartAbs+((n.start_beat||0)+(n.duration_beats||1))*secPerBeat;if(end>itemEndAbs)itemEndAbs=end;});window.SonicAPI.midiRender({instrument_id:instrumentId,notes:notes.map(function(n){return{pitch:n.pitch||60,start_beat:n.start_beat||0,duration_beats:n.duration_beats||1,velocity:n.velocity!=null?n.velocity:0.8};}),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;fetch(API_BASE_URL+res.url).then(function(r){return r.arrayBuffer();}).then(function(buf){context.decodeAudioData(buf,function(audioBuf){try{if(opts&&typeof opts.isActive==='function'&&!opts.isActive())return;const src=context.createBufferSource();src.buffer=audioBuf;src.connect(destNode);if(offsetTime<itemStartAbs){src.start(context.currentTime+(itemStartAbs-offsetTime),0);}else if(offsetTime<itemEndAbs){src.start(context.currentTime,Math.min(offsetTime-itemStartAbs,Math.max(0,audioBuf.duration-0.05)));}else{return;}if(opts&&opts.limitSec&&opts.limitSec<itemStartAbs+audioBuf.duration){src.stop(context.currentTime+Math.max(0.02,opts.limitSec-Math.max(offsetTime,itemStartAbs)));}if(opts&&opts.sources&&opts.sources.push)opts.sources.push(src);}catch(e){console.warn('[NativeVST] schedule decode error:',e);}},function(){});}).catch(function(){});}).catch(function(){});}catch(e){console.warn('[NativeVST] scheduleNativeVstItem error:',e);}};// Handle ?sfs=<encoded> from double-clicking a .sfs file (opens domain -> loads project)
|
||||
const scheduleNativeVstItem=(track,item,offsetTime,context,destNode,bpm,opts)=>{try{const eng=track&&track.synth_engine;const instrumentId=eng&&eng.plugin_id||track&&track.instrumentId;if(!instrumentId||!isVstTrackEngine(eng))return;const notes=item&&item.notes||[];if(!notes.length)return;const secPerBeat=60.0/(parseInt(bpm)||120);const baseOffsetSec=opts&&opts.baseOffsetSec||0;const itemStartAbs=baseOffsetSec+(item.startTime||0);let itemEndAbs=itemStartAbs+0.05;notes.forEach(function(n){const end=itemStartAbs+((n.start_beat||0)+(n.duration_beats||1))*secPerBeat;if(end>itemEndAbs)itemEndAbs=end;});const key=_nativeVstItemKey(track,item,bpm);const scheduleUrl=function(url){if(!url)return;fetch(url).then(function(r){return r.arrayBuffer();}).then(function(buf){context.decodeAudioData(buf,function(audioBuf){try{if(opts&&typeof opts.isActive==='function'&&!opts.isActive())return;const src=context.createBufferSource();src.buffer=audioBuf;src.connect(destNode);if(offsetTime<itemStartAbs){src.start(context.currentTime+(itemStartAbs-offsetTime),0);}else if(offsetTime<itemEndAbs){src.start(context.currentTime,Math.min(offsetTime-itemStartAbs,Math.max(0,audioBuf.duration-0.05)));}else{return;}if(opts&&opts.limitSec&&opts.limitSec<itemStartAbs+audioBuf.duration){src.stop(context.currentTime+Math.max(0.02,opts.limitSec-Math.max(offsetTime,itemStartAbs)));}if(opts&&opts.sources&&opts.sources.push)opts.sources.push(src);}catch(e){console.warn('[NativeVST] schedule decode error:',e);}},function(){});}).catch(function(){});};if(key&&_nativeVstItemUrls[key]){scheduleUrl(_nativeVstItemUrls[key]);return;}ensureRenderNativeVstItem(track,item,bpm).then(function(url){if(!url)return;scheduleUrl(url);});}catch(e){console.warn('[NativeVST] scheduleNativeVstItem error:',e);}};// Handle ?sfs=<encoded> from double-clicking a .sfs file (opens domain -> loads project)
|
||||
(function handleSfsDeepLink(){try{const params=new URLSearchParams(window.location.search);const sfsParam=params.get('sfs');if(!sfsParam)return;const decoded=JSON.parse(decodeURIComponent(sfsParam));window.__pendingSfsProject=decoded;// consumed after auth in App
|
||||
if(window.history.replaceState){window.history.replaceState({},document.title,window.location.pathname);}}catch(e){window.__pendingSfsProject=null;}})();// Storage for server-side file IDs mapped to track IDs
|
||||
let serverFileIdMap={};let audioCtx;let masterBus=null;// { input, compressor, analyser, output, masteringActive, dryInput, dryOutput }
|
||||
@@ -1051,10 +1055,10 @@ const sectionInnerTracks=[];updatedTracks.forEach(function(ut){(ut.sections||[])
|
||||
const prof=currentUser||function(){try{return JSON.parse(localStorage.getItem('sonic_user')||'null');}catch(e){return null;}}();if(prof&&window.SonicAPI&&window.SonicAPI.listCloudProjects){try{const cloudList=await window.SonicAPI.listCloudProjects();if(cloudList&&cloudList.length>0){const latest=cloudList[0];// backend ORDER BY updated_at DESC
|
||||
lastId=latest.id;lastName=latest.name||lastName;const proj=await window.SonicAPI.getCloudProject(lastId);if(proj&&proj.data_json)parsed=JSON.parse(proj.data_json);}}catch(e){console.warn('restore cloud fallback error:',e);}}}else{try{if(lastId.startsWith('local_')){var localData=localStorage.getItem('sonic_local_project_data');if(localData)parsed=JSON.parse(localData);}else{var proj=await window.SonicAPI.getCloudProject(lastId);if(proj)parsed=JSON.parse(proj.data_json);}}catch(e){console.warn('restore load error:',e);}}try{if(!parsed)return;var restoredBpm=bpm;var restoredTracks=[];var restoredSessionTabs=[];var restoredSubTabs=[];if(parsed.main_session){var result=deserializeProjectFromSchema(parsed);restoredTracks=result.tracks;restoredBpm=result.bpm;restoredSessionTabs=result.sessionTabs;restoredSubTabs=result.subTabs;// Bỏ qua zoom ≤ 1 (data cũ lưu 1.0 — lỗi __currentZoom của Media
|
||||
// Explorer) — áp dụng mới giữ zoom hiện tại/localStorage — hết thu nhỏ.
|
||||
if(result.zoom>1&&setZoom)setZoom(result.zoom);if(result.masteringSettings)setMasteringSettings(result.masteringSettings);}else{restoredTracks=(parsed.tracks||[]).map(function(t){var rest=Object.assign({},t);delete rest.height;return Object.assign({},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(lastName);setCurrentProjectId(lastId);localStorage.setItem('sonic_project_id',lastId);setSessionTabs(restoredSessionTabs);setSubTabs(restoredSubTabs);// DIAG section-tab block: log tracks + sections của section tab sau restore
|
||||
if(result.zoom>1&&setZoom)setZoom(result.zoom);if(result.masteringSettings)setMasteringSettings(result.masteringSettings);}else{restoredTracks=(parsed.tracks||[]).map(function(t){var rest=Object.assign({},t);delete rest.height;return Object.assign({},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);});prerenderNativeVstItems(restoredTracks,restoredBpm);setBpm(restoredBpm.toString());setSelectedTrackId(restoredTracks[0]?.id||'1');setProjectName(lastName);setCurrentProjectId(lastId);localStorage.setItem('sonic_project_id',lastId);setSessionTabs(restoredSessionTabs);setSubTabs(restoredSubTabs);// DIAG section-tab block: log tracks + sections của section tab sau restore
|
||||
try{(restoredSessionTabs||[]).forEach(function(st){var secCount=0,midiCount=0,clipCount=0;(st.tracks||[]).forEach(function(tr){secCount+=(tr.sections||[]).length;midiCount+=(tr.midiItems||[]).length;clipCount+=(tr.clips||[]).length;});console.log('[Restore] sessionTab',st.id,'sectionId',st.sectionId,'tracks',(st.tracks||[]).length,'sections',secCount,'midi',midiCount,'clips',clipCount);});}catch(e){}var restoredItemCount=0;restoredTracks.forEach(function(rt){if(rt.clips)restoredItemCount+=rt.clips.length;if(rt.midiItems)restoredItemCount+=rt.midiItems.length;if(rt.sections)restoredItemCount+=rt.sections.length;});showToast('Đã khôi phục dự án "'+lastName+'" ('+restoredItemCount+' items).','info');}catch(e){console.warn('restoreLastSessionProject failed:',e);// Stale session id (project deleted / DB reset): clear it so the error
|
||||
// does not repeat on every page load.
|
||||
localStorage.removeItem('sonic_project_id');localStorage.removeItem('sonic_project_name');}};useEffect(()=>{const checkAuthStatus=async()=>{const savedToken=localStorage.getItem('sonic_token');if(!savedToken){setIsMandatoryLogin(true);setAuthMode('login');setAuthModalOpen(true);return;}try{const profile=await window.SonicAPI.getProfile();setCurrentUser(profile);localStorage.setItem('sonic_user',JSON.stringify(profile));if(profile.must_change_password){setIsMandatoryLogin(true);setAuthMode('force_change');setAuthModalOpen(true);}else{setIsMandatoryLogin(false);setAuthModalOpen(false);restoreLastSessionProject();}}catch(err){const cached=localStorage.getItem('sonic_user');if(cached){try{setCurrentUser(JSON.parse(cached));}catch(_){}setIsMandatoryLogin(false);setAuthModalOpen(false);restoreLastSessionProject();}else{localStorage.removeItem('sonic_token');localStorage.removeItem('sonic_user');setCurrentUser(null);setIsMandatoryLogin(true);setAuthMode('login');setAuthModalOpen(true);}}};checkAuthStatus();},[]);const loadPendingSfsProject=()=>{const proj=window.__pendingSfsProject;if(!proj)return;try{let restoredTracks=[];let restoredBpm=bpm;let restoredSessionTabs=[];let restoredSubTabs=[];if(proj.main_session){const result=deserializeProjectFromSchema(proj);restoredTracks=result.tracks;restoredBpm=result.bpm;restoredSessionTabs=result.sessionTabs;restoredSubTabs=result.subTabs;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,carlaBridgeActive:false,carlaBridge:{plugin:'',plugin_path:'',connected:false}});}}else{restoredTracks=(proj.tracks||[]).map(t=>{const{height:_h,...rest}=t;return{...rest,buffer:null,channelInfo:t.channelInfo||null,clips:t.clips||[],serverFileId:t.serverFileId||null};});}if(restoredTracks.length>0){setTracks(restoredTracks);setBpm(restoredBpm.toString());if(restoredSessionTabs.length>0){setSessionTabs(restoredSessionTabs);}if(restoredSubTabs.length>0){setSubTabs(restoredSubTabs);}showToast(`Đã tải dự án "${proj.metadata?.title||proj.name||'Dự án mới'}" từ liên kết .sfs thành công!`,"success");}}catch(e){showToast("Lỗi tải dự án từ .sfs","error");}finally{window.__pendingSfsProject=null;}};const handleAuthSuccess=user=>{setCurrentUser(user);if(user.must_change_password){setIsMandatoryLogin(true);setAuthMode('force_change');setAuthModalOpen(true);}else{setIsMandatoryLogin(false);setAuthModalOpen(false);restoreLastSessionProject();const loadPrefs=p=>{if(!p)return;if(p.showAIPanel!==undefined)setShowAIPanel(p.showAIPanel);if(p.showExportPanel!==undefined)setShowExportPanel(p.showExportPanel);if(p.showSelectionPanel!==undefined)setShowSelectionPanel(p.showSelectionPanel);if(p.showPythonToolsPanel!==undefined)setShowPythonToolsPanel(p.showPythonToolsPanel);if(p.showMediaExplorer!==undefined)setShowMediaExplorer(p.showMediaExplorer);if(p.showFxRack!==undefined)setShowFxRack(p.showFxRack);if(p.showMidiEvents!==undefined)setShowMidiEvents(p.showMidiEvents);if(p.panelPositions)setPanelPositions(p.panelPositions);if(p.rightSidebarWidth)setRightSidebarWidth(p.rightSidebarWidth);if(p.mediaExplorerHeight)setMediaExplorerHeight(p.mediaExplorerHeight);if(p.selectedProviderId)setSelectedProviderId(p.selectedProviderId);};(async()=>{try{const data=await window.SonicAPI.getPreferences();if(data&&data.preferences)loadPrefs(data.preferences);else{const cached=localStorage.getItem('sonic_preferences');if(cached)loadPrefs(JSON.parse(cached));}}catch(e){const cached=localStorage.getItem('sonic_preferences');if(cached)loadPrefs(JSON.parse(cached));}try{const data=await window.SonicAPI.getAIConfigs();if(data&&data.providers){setAiProviders(data.providers);const active=data.providers.find(p=>p.is_active)||data.providers[0];if(active)setSelectedProviderId(active.id);}}catch(e){}try{window.SonicAPI.getSoundfontCatalog().then(cat=>{window.__soundfontCatalog=cat;}).catch(()=>{});}catch(e){}// Re-fetch instrument data after auth (useEffect on mount runs before token is set)
|
||||
localStorage.removeItem('sonic_project_id');localStorage.removeItem('sonic_project_name');}};useEffect(()=>{const checkAuthStatus=async()=>{const savedToken=localStorage.getItem('sonic_token');if(!savedToken){setIsMandatoryLogin(true);setAuthMode('login');setAuthModalOpen(true);return;}try{const profile=await window.SonicAPI.getProfile();setCurrentUser(profile);localStorage.setItem('sonic_user',JSON.stringify(profile));if(profile.must_change_password){setIsMandatoryLogin(true);setAuthMode('force_change');setAuthModalOpen(true);}else{setIsMandatoryLogin(false);setAuthModalOpen(false);restoreLastSessionProject();}}catch(err){const cached=localStorage.getItem('sonic_user');if(cached){try{setCurrentUser(JSON.parse(cached));}catch(_){}setIsMandatoryLogin(false);setAuthModalOpen(false);restoreLastSessionProject();}else{localStorage.removeItem('sonic_token');localStorage.removeItem('sonic_user');setCurrentUser(null);setIsMandatoryLogin(true);setAuthMode('login');setAuthModalOpen(true);}}};checkAuthStatus();},[]);const loadPendingSfsProject=()=>{const proj=window.__pendingSfsProject;if(!proj)return;try{let restoredTracks=[];let restoredBpm=bpm;let restoredSessionTabs=[];let restoredSubTabs=[];if(proj.main_session){const result=deserializeProjectFromSchema(proj);restoredTracks=result.tracks;restoredBpm=result.bpm;restoredSessionTabs=result.sessionTabs;restoredSubTabs=result.subTabs;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,carlaBridgeActive:false,carlaBridge:{plugin:'',plugin_path:'',connected:false}});}}else{restoredTracks=(proj.tracks||[]).map(t=>{const{height:_h,...rest}=t;return{...rest,buffer:null,channelInfo:t.channelInfo||null,clips:t.clips||[],serverFileId:t.serverFileId||null};});}if(restoredTracks.length>0){setTracks(restoredTracks);setBpm(restoredBpm.toString());prerenderNativeVstItems(restoredTracks,restoredBpm);if(restoredSessionTabs.length>0){setSessionTabs(restoredSessionTabs);}if(restoredSubTabs.length>0){setSubTabs(restoredSubTabs);}showToast(`Đã tải dự án "${proj.metadata?.title||proj.name||'Dự án mới'}" từ liên kết .sfs thành công!`,"success");}}catch(e){showToast("Lỗi tải dự án từ .sfs","error");}finally{window.__pendingSfsProject=null;}};const handleAuthSuccess=user=>{setCurrentUser(user);if(user.must_change_password){setIsMandatoryLogin(true);setAuthMode('force_change');setAuthModalOpen(true);}else{setIsMandatoryLogin(false);setAuthModalOpen(false);restoreLastSessionProject();const loadPrefs=p=>{if(!p)return;if(p.showAIPanel!==undefined)setShowAIPanel(p.showAIPanel);if(p.showExportPanel!==undefined)setShowExportPanel(p.showExportPanel);if(p.showSelectionPanel!==undefined)setShowSelectionPanel(p.showSelectionPanel);if(p.showPythonToolsPanel!==undefined)setShowPythonToolsPanel(p.showPythonToolsPanel);if(p.showMediaExplorer!==undefined)setShowMediaExplorer(p.showMediaExplorer);if(p.showFxRack!==undefined)setShowFxRack(p.showFxRack);if(p.showMidiEvents!==undefined)setShowMidiEvents(p.showMidiEvents);if(p.panelPositions)setPanelPositions(p.panelPositions);if(p.rightSidebarWidth)setRightSidebarWidth(p.rightSidebarWidth);if(p.mediaExplorerHeight)setMediaExplorerHeight(p.mediaExplorerHeight);if(p.selectedProviderId)setSelectedProviderId(p.selectedProviderId);};(async()=>{try{const data=await window.SonicAPI.getPreferences();if(data&&data.preferences)loadPrefs(data.preferences);else{const cached=localStorage.getItem('sonic_preferences');if(cached)loadPrefs(JSON.parse(cached));}}catch(e){const cached=localStorage.getItem('sonic_preferences');if(cached)loadPrefs(JSON.parse(cached));}try{const data=await window.SonicAPI.getAIConfigs();if(data&&data.providers){setAiProviders(data.providers);const active=data.providers.find(p=>p.is_active)||data.providers[0];if(active)setSelectedProviderId(active.id);}}catch(e){}try{window.SonicAPI.getSoundfontCatalog().then(cat=>{window.__soundfontCatalog=cat;}).catch(()=>{});}catch(e){}// Re-fetch instrument data after auth (useEffect on mount runs before token is set)
|
||||
try{window.SonicAPI.listPlugins().then(async data=>{try{const catResp=(await window.SonicAPI.getSoundfontCatalog?.())??(await fetch('/api/v1/plugins/soundfonts/catalog').then(r=>r.json()));const catalog=catResp.full_catalog||{};data.soundfonts=(data.soundfonts||[]).map(sf=>{const sfId=sf.id.replace('sf_','');const catEntry=catalog[sfId.toLowerCase()]||catalog[sfId];if(catEntry&&catEntry.instruments)return{...sf,presets:catEntry.instruments};return sf;});}catch(e){console.warn('Catalog fetch error:',e);}setInstrumentSelectorData(data);}).catch(()=>{});}catch(e){}// ── Khôi phục dự án đang làm dở (temp autosave) khi load lại app ──
|
||||
// Yêu cầu: "Khi tắt ứng dụng → tự động lưu temp; khi load lại → tải lại
|
||||
// dự án đang làm dở." Chỉ restore khi app khởi động với project TRỐNG
|
||||
|
||||
Reference in New Issue
Block a user