-
+
+ setOzState(prev => ({ ...prev, [p]: v }))}
+ />
-
-
Ceiling Level:
-
{ozState.ceiling.toFixed(2)} dB
+
+
+ Ceiling Level:
+ {ozState.ceiling.toFixed(2)} dB
+
+
setOzState(prev => ({ ...prev, ceiling: parseFloat(e.target.value) }))}
+ className="w-full h-1 cursor-pointer accent-cyan-400"
+ />
-
-
UPWARD COMPRESS
-
-
+2.0 dB
+
+ setOzState(prev => ({ ...prev, [p]: v }))}
+ />
-
-
SOFT CLIPPER
-
-
15%
+
+ setOzState(prev => ({ ...prev, [p]: v }))}
+ />
-
-
TRANSIENT EMPHASIS
-
-
25%
+
+ setOzState(prev => ({ ...prev, [p]: v }))}
+ />
+ {/* WAVE OBSERVER INTEGRATION */}
+
+ {/* Header */}
+
+
+ Wave Observer
+ Real-time Oscilloscope
+
+
+ {['Scope', 'Settings', 'Help', 'About'].map(tab => (
+
+ ))}
+
+
+ {/* Scope Canvas */}
+
+
+
+
+ {/* Controls bar */}
+
+ {/* Input level meters */}
+
+
+ {/* Scope Controls */}
+
+
Scope
+
+
+ Channel
+
+
+
+
+ Mode
+
+
+
+
+ Duration:
+ {woDuration.toFixed(3)}s
+ setWoDuration(parseFloat(e.target.value))} className="w-16 h-1 cursor-pointer accent-cyan-400" />
+
+
+
+ V.Zoom:
+ {woZoom.toFixed(1)} dB
+ setWoZoom(parseFloat(e.target.value))} className="w-16 h-1 cursor-pointer accent-cyan-400" />
+
+
+
+
+
+
{/* RIGHT SIDEBAR: I/O METERS */}
@@ -7989,7 +8315,10 @@ const App = () => {
var asCh = window.SonicPianoRoll ? window.SonicPianoRoll.getTrackMidiChannel(asTrk, allTracks) : (asTrk ? asTrk.midiChannel : 0);
var asProg = as.instrumentProgram;
var asSe = as.synth_engine;
- window.SonicSF.playNote(pitch, scaledVel, 60000, undefined, asProg, null, asCh, asSe);
+ if (window.triggerMidiVuActivity) {
+ window.triggerMidiVuActivity(as.trackId, scaledVel);
+ }
+ window.SonicSF.playNote(pitch, scaledVel, 60000, undefined, asProg, null, asCh, asSe);
});
}
// Route to ALL armed tracks (not just the first one)
@@ -7998,7 +8327,10 @@ const App = () => {
var atProg = at.instrumentProgram;
var atSe = at.synth_engine;
var atDest = activeTrackNodesRef.current[at.id]?.gainNode || null;
- window.SonicSF.playNote(pitch, scaledVel, 60000, undefined, atProg, atDest, atCh, atSe);
+ if (window.triggerMidiVuActivity) {
+ window.triggerMidiVuActivity(at.id, scaledVel);
+ }
+ window.SonicSF.playNote(pitch, scaledVel, 60000, undefined, atProg, atDest, atCh, atSe);
});
}
} else if (cmd === 0x8 || (cmd === 0x9 && rawVel === 0)) {
@@ -8581,6 +8913,15 @@ const App = () => {
sessionTabsRef.current = sessionTabs;
const [selectionCleared, setSelectionCleared] = useState(false); // LOOP_EDITOR_2.md §4.2
+ const midiVuActivityRef = useRef({});
+ const triggerMidiVuActivity = (trackId, velocity) => {
+ if (!trackId) return;
+ const velFactor = typeof velocity === 'number' ? (velocity > 1 ? velocity / 127 : velocity) : 0.8;
+ const peak = Math.min(1.0, Math.max(0.15, velFactor));
+ midiVuActivityRef.current[trackId] = peak;
+ };
+ window.triggerMidiVuActivity = triggerMidiVuActivity;
+
// ── Temp Edit Tab (LOOP_EDITOR_2.md §1.2 Sub Tab) ──
const [tempTabActive, setTempTabActive] = useState(false);
@@ -11715,6 +12056,10 @@ const App = () => {
const midiItems = track.midiItems || [];
if ((track.type === 'MIDI' || midiItems.length > 0) && window.SonicSF) {
var trkCh = window.SonicPianoRoll ? window.SonicPianoRoll.getTrackMidiChannel(track, activeTracks) : (track.midiChannel !== undefined ? track.midiChannel : 0);
+ // Ensure instrument is loaded in FluidSynth
+ if (track.synth_engine && track.synth_engine.type === 'soundfont' && track.synth_engine.soundfont_id) {
+ window.SonicSF.selectInstrument(trkCh, track.synth_engine.soundfont_bank || 0, track.synth_engine.soundfont_program || 0, track.synth_engine.soundfont_id);
+ }
const bpmVal = parseInt(bpm) || 120;
const secondsPerBeat = 60.0 / bpmVal;
midiItems.forEach(item => {
@@ -11742,6 +12087,12 @@ const App = () => {
trkCh,
track.synth_engine
);
+ // Trigger VU meter flash when the note starts playing
+ setTimeout(() => {
+ if (window.triggerMidiVuActivity) {
+ window.triggerMidiVuActivity(track.id, note.velocity || 0.8);
+ }
+ }, delay * 1000);
} else {
const playOffset = offsetTime - noteStartSec;
const remainingDurMs = (noteEndSec - offsetTime) * 1000;
@@ -11755,6 +12106,10 @@ const App = () => {
trkCh,
track.synth_engine
);
+ // Trigger VU meter flash instantly
+ if (window.triggerMidiVuActivity) {
+ window.triggerMidiVuActivity(track.id, note.velocity || 0.8);
+ }
}
}
});
@@ -16525,17 +16880,29 @@ const App = () => {
const canvas = trackVuRefs.current[key];
if (!canvas) return;
+ let audioPeak = 0;
if (node && node.analyserNode && isPlaying) {
const analyser = node.analyserNode;
const data = new Uint8Array(128);
analyser.getByteTimeDomainData(data);
- let peak = 0;
for (let i = 0; i < data.length; i++) {
const v = Math.abs(data[i] - 128) / 128;
- if (v > peak) peak = v;
+ if (v > audioPeak) audioPeak = v;
}
- const db = peak > 0 ? 20 * Math.log10(peak) : -60;
+ }
+ let midiPeak = midiVuActivityRef.current[trackId] || 0;
+ if (midiPeak > 0) {
+ midiVuActivityRef.current[trackId] = midiPeak * 0.90;
+ if (midiVuActivityRef.current[trackId] < 0.01) {
+ midiVuActivityRef.current[trackId] = 0;
+ }
+ }
+
+ const peak = Math.max(audioPeak, midiPeak);
+ const db = peak > 0 ? 20 * Math.log10(peak) : -60;
+
+ if (peak > 0.001) {
if (key.endsWith('_mixer')) {
drawMixerVuMeter(canvas, peak);
} else {
diff --git a/app/static/js/app.precompiled.js b/app/static/js/app.precompiled.js
index 8c57444..7b07285 100644
--- a/app/static/js/app.precompiled.js
+++ b/app/static/js/app.precompiled.js
@@ -20,8 +20,8 @@ f3_lp.connect(split3);split3.connect(gainLL3,0);split3.connect(gainLR3,0);split3
f4_hp.connect(split4);split4.connect(gainLL4,0);split4.connect(gainLR4,0);split4.connect(gainRL4,1);split4.connect(gainRR4,1);gainLL4.connect(merge4,0,0);gainRL4.connect(merge4,0,0);gainLR4.connect(merge4,0,1);gainRR4.connect(merge4,0,1);merge4.connect(imagerOutput);// Maximizer nodes
const maximizerBoostGain=ctx.createGain();const maximizerSoftClipper=ctx.createWaveShaper();maximizerSoftClipper.curve=null;maximizerSoftClipper.oversample='4x';const upwardCompressor=ctx.createDynamicsCompressor();upwardCompressor.threshold.value=-30;upwardCompressor.knee.value=10;upwardCompressor.ratio.value=4;upwardCompressor.attack.value=0.01;upwardCompressor.release.value=0.1;const upwardGain=ctx.createGain();upwardGain.gain.value=0.0;const upwardSummingGain=ctx.createGain();maximizerBoostGain.connect(maximizerSoftClipper);maximizerSoftClipper.connect(upwardSummingGain);maximizerBoostGain.connect(upwardCompressor);upwardCompressor.connect(upwardGain);upwardGain.connect(upwardSummingGain);const maximizerCompressor=ctx.createDynamicsCompressor();maximizerCompressor.threshold.value=-0.1;maximizerCompressor.knee.value=0.0;maximizerCompressor.ratio.value=20.0;maximizerCompressor.attack.value=0.001;maximizerCompressor.release.value=0.05;upwardSummingGain.connect(maximizerCompressor);// Setup Analysers
const inputAnalyser=ctx.createAnalyser();inputAnalyser.fftSize=2048;const outputAnalyser=ctx.createAnalyser();outputAnalyser.fftSize=2048;// Global fader / output
-const output=ctx.createGain();output.gain.value=1.0;const analyser=ctx.createAnalyser();analyser.fftSize=256;masterBus={input:ctx.createGain(),compressor:ctx.createDynamicsCompressor(),analyser,output,masteringActive:false,// Analysers for metering
-inputAnalyser,outputAnalyser,// Mastering nodes
+const output=ctx.createGain();output.gain.value=1.0;const analyser=ctx.createAnalyser();analyser.fftSize=256;const leftAnalyser=ctx.createAnalyser();leftAnalyser.fftSize=2048;const rightAnalyser=ctx.createAnalyser();rightAnalyser.fftSize=2048;const splitter=ctx.createChannelSplitter(2);output.connect(splitter);splitter.connect(leftAnalyser,0);splitter.connect(rightAnalyser,1);masterBus={input:ctx.createGain(),compressor:ctx.createDynamicsCompressor(),analyser,output,masteringActive:false,// Analysers for metering
+inputAnalyser,outputAnalyser,leftAnalyser,rightAnalyser,// Mastering nodes
eqLowFilter,eqMid1Filter,eqMid2Filter,eqHighFilter,imagerInput,imagerOutput,gainLL1,gainRL1,gainLR1,gainRR1,gainLL2,gainRL2,gainLR2,gainRR2,gainLL3,gainRL3,gainLR3,gainRR3,gainLL4,gainRL4,gainLR4,gainRR4,maximizerBoostGain,maximizerSoftClipper,upwardCompressor,upwardGain,upwardSummingGain,maximizerCompressor};// Connect EQ chain
eqLowFilter.connect(eqMid1Filter);eqMid1Filter.connect(eqMid2Filter);eqMid2Filter.connect(eqHighFilter);// Connect EQ to Imager
eqHighFilter.connect(imagerInput);// Connect Imager to Maximizer
@@ -201,13 +201,25 @@ const minOrigStart=Math.min(...draggedNote.selectedNotesOffset.map(o=>o.original
const computeLengthBars=(tracksArr,spb)=>{let maxSec=0;(tracksArr||[]).forEach(tr=>{(tr.clips||[]).forEach(c=>{const end=(c.startTime||0)+(c.buffer?c.buffer.duration/(c.speed||1.0):4);if(end>maxSec)maxSec=end;});(tr.midiItems||[]).forEach(m=>{const end=(m.startTime||0)+(m.duration||4);if(end>maxSec)maxSec=end;});});return Math.ceil((maxSec||4)/spb);};// 1. Populate from sessionTabsList (open tabs)
(sessionTabsList||[]).forEach(st=>{const serializedTracks=serializeTracksList(st.tracks,secondsPerBar);sectionStore[st.sectionId]={id:st.sectionId,name:st.name,is_root:false,length_bars:computeLengthBars(st.tracks,secondsPerBar),auto_compute_length:true,tracks:serializedTracks,color:st.color||null};});// 2. Also populate from tracksList (closed tabs saved inside Section items)
const scanForSections=tracks=>{(tracks||[]).forEach(t=>{if(t.sections){t.sections.forEach(s=>{const secId=s.sectionId||s.id;if(s.tracks&&!sectionStore[secId]){sectionStore[secId]={id:secId,name:s.name,is_root:false,length_bars:computeLengthBars(s.tracks,secondsPerBar),auto_compute_length:true,tracks:serializeTracksList(s.tracks,secondsPerBar),color:s.color||null};}if(s.tracks){scanForSections(s.tracks);}});}});};scanForSections(tracksList);const subTabs=(subTabsList||[]).map(st=>{return{id:st.id,label:st.label,type:st.type,track_id:st.trackId,target_id:st.target_id,parent_tab_id:st.parent_tab_id,notes:st.notes||[],duration:st.duration||4,instrument_program:st.instrumentProgram,instrument_name:st.instrumentName,current_time:st.currentTime||0,color:st.color||null};});return{project_id:projectId||'proj_'+Date.now(),metadata:{title:name||"Dự án mới",bpm:parseFloat(bpmVal||120),time_signature_numerator:4,time_signature_denominator:4,sample_rate:44100},main_session:{id:"main",name:"MAIN SESSION",is_root:true,length_bars:(()=>{let maxBar=16.0;(mainTracks||[]).forEach(t=>{(t.items||[]).forEach(item=>{const end=(item.start_bar||0)+(item.duration_bars||4);if(end>maxBar)maxBar=end;});});return maxBar;})(),auto_compute_length:true,tracks:mainTracks},sub_tabs:subTabs,section_store:sectionStore,mastering_settings:masteringSettings||null};};const deserializeProjectFromSchema=schemaObj=>{const bpmVal=schemaObj.metadata?schemaObj.metadata.bpm:120;const secondsPerBar=60.0/bpmVal*4;const sectionStore=schemaObj.section_store||{};const restoredTracks=deserializeTracksList(schemaObj.main_session.tracks,secondsPerBar,sectionStore);const restoredSessionTabs=[];Object.keys(sectionStore).forEach(secId=>{const secContainer=sectionStore[secId];const secTracks=deserializeTracksList(secContainer.tracks,secondsPerBar,sectionStore);restoredSessionTabs.push({id:'session_'+secId,name:secContainer.name,sectionId:secId,tracks:secTracks,length_bars:secContainer.length_bars||16.0,auto_compute_length:secContainer.auto_compute_length!==undefined?secContainer.auto_compute_length:true,color:secContainer.color||null});});const restoredSubTabs=(schemaObj.sub_tabs||[]).map(st=>{return{id:st.id,label:st.label,type:st.type,trackId:st.track_id,target_id:st.target_id,parent_tab_id:st.parent_tab_id,notes:st.notes||[],duration:st.duration||4,instrumentProgram:st.instrument_program,instrumentName:st.instrument_name,currentTime:st.current_time||0,color:st.color||null};});return{bpm:bpmVal,tracks:restoredTracks,sessionTabs:restoredSessionTabs,subTabs:restoredSubTabs,masteringSettings:schemaObj.mastering_settings||null};};// ──────────────────────────────────────────────
+// MASTERING KNOB COMPONENT (Dynamic pointer events version)
+// ──────────────────────────────────────────────
+const MasteringKnob=({param,min,max,value,unit,label,color,onChange,size='small'})=>{const[isDragging,setIsDragging]=React.useState(false);const startYRef=React.useRef(0);const startValRef=React.useRef(0);const handlePointerDown=e=>{e.preventDefault();setIsDragging(true);startYRef.current=e.clientY;startValRef.current=value;e.currentTarget.setPointerCapture(e.pointerId);};const handlePointerMove=e=>{if(!isDragging)return;const deltaY=startYRef.current-e.clientY;let newVal=startValRef.current+deltaY/150*(max-min);newVal=Math.min(max,Math.max(min,newVal));onChange(param,newVal);};const handlePointerUp=e=>{setIsDragging(false);try{e.currentTarget.releasePointerCapture(e.pointerId);}catch(err){}};const pct=(value-min)/(max-min);const angle=-135+pct*270;const isLarge=size==='large';const dialClass=isLarge?'w-20 h-20 border-4 bg-slate-900':'w-10 h-10 border-2 bg-slate-800';const pointerHeight=isLarge?'h-6':'h-3';const valClass=isLarge?'text-xs text-cyan-300 font-bold mt-2 z-10':'text-[9px] text-slate-300 font-mono mt-1 font-bold';return/*#__PURE__*/React.createElement("div",{className:"flex flex-col items-center select-none"},label&&/*#__PURE__*/React.createElement("span",{className:"text-[10px] font-bold text-slate-400 mb-1.5 uppercase tracking-wide"},label),/*#__PURE__*/React.createElement("div",{className:`${dialClass} rounded-full relative flex items-center justify-center cursor-ns-resize shadow-lg`,style:{borderColor:color},onPointerDown:handlePointerDown,onPointerMove:handlePointerMove,onPointerUp:handlePointerUp,onPointerCancel:handlePointerUp},/*#__PURE__*/React.createElement("div",{className:"w-0.5 absolute rounded origin-bottom",style:{backgroundColor:color,height:isLarge?'22px':'12px',top:isLarge?'6px':'4px',transform:`rotate(${angle}deg)`,transformOrigin:'50% 100%'}}),isLarge&&/*#__PURE__*/React.createElement("span",{className:"text-[10px] font-bold text-cyan-300 z-10 bg-slate-950/80 px-1 py-0.5 rounded border border-slate-800"},value>0&&unit==='dB'?'+':'',value.toFixed(1)," ",unit)),!isLarge&&/*#__PURE__*/React.createElement("span",{className:valClass},value>0&&unit==='dB'?'+':'',value.toFixed(1),unit));};// ──────────────────────────────────────────────
// MASTERING MODAL COMPONENT (from md/45_MASTERING_MODULE.md)
// ──────────────────────────────────────────────
-const MasteringModal=({isOpen,onClose,masteringSettings,setMasteringSettings})=>{const ozState=masteringSettings;const setOzState=setMasteringSettings;const[isPlaying,setIsPlaying]=React.useState(false);const masterConnected=ozState.masterConnected;const setMasterConnected=val=>{setOzState(prev=>({...prev,masterConnected:typeof val==='function'?val(prev.masterConnected):val}));};const audioRef=React.useRef({source:null});const eqCanvasRef=React.useRef(null);const imagerCanvasRef=React.useRef(null);const inMeterCanvasRef=React.useRef(null);const outMeterCanvasRef=React.useRef(null);const animFrameRef=React.useRef(null);const knobsInitializedRef=React.useRef(false);const ozStateRef=React.useRef(ozState);ozStateRef.current=ozState;function startAudioDemo(){getAudioContext();const ctx=audioCtx;if(ctx.state==='suspended')ctx.resume();stopAudioDemo();const sampleRate=ctx.sampleRate;const bufferSize=sampleRate*4;const buffer=ctx.createBuffer(2,bufferSize,sampleRate);const left=buffer.getChannelData(0);const right=buffer.getChannelData(1);for(let i=0;i
{if(!isOpen)return;getAudioContext();function resizeAll(){const resizeCanvas=ref=>{const el=ref.current;if(el){el.width=el.clientWidth;el.height=el.clientHeight;}};resizeCanvas(eqCanvasRef);resizeCanvas(imagerCanvasRef);resizeCanvas(inMeterCanvasRef);resizeCanvas(outMeterCanvasRef);}resizeAll();window.addEventListener('resize',resizeAll);const fftData=new Uint8Array(1024);function getPeakLevel(analyser){if(!analyser)return 0;const bufferLength=analyser.fftSize;const dataArray=new Float32Array(bufferLength);analyser.getFloatTimeDomainData(dataArray);let maxVal=0;for(let i=0;imaxVal){maxVal=val;}}return maxVal;}function renderFrame(){animFrameRef.current=requestAnimationFrame(renderFrame);const s=ozStateRef.current;// EQ Spectrum
+const MasteringModal=({isOpen,onClose,masteringSettings,setMasteringSettings})=>{const ozState=masteringSettings;const setOzState=setMasteringSettings;const[isPlaying,setIsPlaying]=React.useState(false);const masterConnected=ozState.masterConnected;const setMasterConnected=val=>{setOzState(prev=>({...prev,masterConnected:typeof val==='function'?val(prev.masterConnected):val}));};const audioRef=React.useRef({source:null});const eqCanvasRef=React.useRef(null);const imagerCanvasRef=React.useRef(null);const inMeterCanvasRef=React.useRef(null);const outMeterCanvasRef=React.useRef(null);const animFrameRef=React.useRef(null);const knobsInitializedRef=React.useRef(false);// Wave Observer Refs & States
+const woCanvasRef=React.useRef(null);const woLeftHistoryRef=React.useRef(new Float32Array(400).fill(0));const woRightHistoryRef=React.useRef(new Float32Array(400).fill(0));const woLeftMeterRef=React.useRef(null);const woRightMeterRef=React.useRef(null);const[woPaused,setWoPaused]=React.useState(false);const[woChannel,setWoChannel]=React.useState('stereo');const[woMode,setWoMode]=React.useState('waveform');const[woDuration,setWoDuration]=React.useState(2.0);const[woZoom,setWoZoom]=React.useState(0.0);const woPausedRef=React.useRef(woPaused);woPausedRef.current=woPaused;const woChannelRef=React.useRef(woChannel);woChannelRef.current=woChannel;const woModeRef=React.useRef(woMode);woModeRef.current=woMode;const woDurationRef=React.useRef(woDuration);woDurationRef.current=woDuration;const woZoomRef=React.useRef(woZoom);woZoomRef.current=woZoom;const ozStateRef=React.useRef(ozState);ozStateRef.current=ozState;function startAudioDemo(){getAudioContext();const ctx=audioCtx;if(ctx.state==='suspended')ctx.resume();stopAudioDemo();const sampleRate=ctx.sampleRate;const bufferSize=sampleRate*4;const buffer=ctx.createBuffer(2,bufferSize,sampleRate);const left=buffer.getChannelData(0);const right=buffer.getChannelData(1);for(let i=0;i{if(!isOpen)return;getAudioContext();function resizeAll(){const resizeCanvas=ref=>{const el=ref.current;if(el){el.width=el.clientWidth;el.height=el.clientHeight;}};resizeCanvas(eqCanvasRef);resizeCanvas(imagerCanvasRef);resizeCanvas(inMeterCanvasRef);resizeCanvas(outMeterCanvasRef);resizeCanvas(woCanvasRef);}resizeAll();window.addEventListener('resize',resizeAll);const fftData=new Uint8Array(1024);function getPeakLevel(analyser){if(!analyser)return 0;const bufferLength=analyser.fftSize;const dataArray=new Float32Array(bufferLength);analyser.getFloatTimeDomainData(dataArray);let maxVal=0;for(let i=0;imaxVal){maxVal=val;}}return maxVal;}function renderFrame(){animFrameRef.current=requestAnimationFrame(renderFrame);const s=ozStateRef.current;// EQ Spectrum
const eqCanvas=eqCanvasRef.current;if(eqCanvas){const w=eqCanvas.width,h=eqCanvas.height;const eqCtx=eqCanvas.getContext('2d');eqCtx.clearRect(0,0,w,h);eqCtx.strokeStyle='rgba(51, 65, 85, 0.3)';eqCtx.lineWidth=1;eqCtx.font='9px JetBrains Mono';eqCtx.fillStyle='#475569';const freqs=[20,50,100,200,500,1000,2000,5000,10000,20000];freqs.forEach(f=>{const x=Math.log10(f/20)/Math.log10(20000/20)*w;eqCtx.beginPath();eqCtx.moveTo(x,0);eqCtx.lineTo(x,h);eqCtx.stroke();if(f>=1000)eqCtx.fillText(`${f/1000}k`,x+3,h-6);else eqCtx.fillText(`${f}`,x+3,h-6);});if(masterBus&&masterBus.outputAnalyser){masterBus.outputAnalyser.getByteFrequencyData(fftData);eqCtx.fillStyle='rgba(56, 189, 248, 0.15)';const barWidth=w/128;for(let i=0;i<128;i++){const val=fftData[i*4]/255;eqCtx.fillRect(i*barWidth,h-val*h,barWidth-1,val*h);}}eqCtx.strokeStyle='#38bdf8';eqCtx.lineWidth=2.5;eqCtx.beginPath();for(let x=0;x0.001){ic.fillStyle='#38bdf8';const maxRadius=ih/3.2*Math.min(1.0,outPeak*1.5);for(let i=0;i<40;i++){const angle=(Math.random()-0.5)*(Math.PI/2)+-Math.PI/2;const radius=Math.random()*maxRadius;const x=iw/2+Math.cos(angle)*radius*(1+s.w3/100);const y=ih/2+Math.sin(angle)*radius;ic.fillRect(x,y,2,2);}}}// I/O Meters
-const renderMeter=(analyser,ctxRef,textId)=>{const canvas=ctxRef.current;if(!canvas)return;const w=canvas.width,h=canvas.height;const ctx=canvas.getContext('2d');ctx.clearRect(0,0,w,h);const peak=getPeakLevel(analyser);const barH=Math.min(1.0,peak)*h;const grad=ctx.createLinearGradient(0,h,0,0);grad.addColorStop(0,'#38bdf8');grad.addColorStop(0.7,'#f59e0b');grad.addColorStop(1,'#ef4444');ctx.fillStyle=grad;ctx.fillRect(2,h-barH,w-4,barH);const el=document.getElementById(textId);if(el){if(peak>0){const dbVal=20*Math.log10(peak);el.innerText=dbVal<-90?'-inf dB':`${dbVal.toFixed(1)} dB`;}else{el.innerText='-inf dB';}}};renderMeter(masterBus&&masterBus.inputAnalyser,inMeterCanvasRef,'inPeakText');renderMeter(masterBus&&masterBus.outputAnalyser,outMeterCanvasRef,'outPeakText');}renderFrame();// Knob setup
-if(!knobsInitializedRef.current){knobsInitializedRef.current=true;const container=document.getElementById('masteringModalBody');if(container){container.querySelectorAll('.knob-container').forEach(knob=>{let isDragging=false,startY=0,startVal=0;const param=knob.dataset.param;if(!param)return;const min=parseFloat(knob.dataset.min);const max=parseFloat(knob.dataset.max);const unit=knob.dataset.unit||'';const dial=knob.querySelector('.knob-dial');const valText=knob.querySelector('.knob-val');function updateUI(val){const pct=(val-min)/(max-min);const angle=-135+pct*270;if(dial)dial.style.transform=`rotate(${angle}deg)`;if(valText)valText.innerText=`${val>0&&unit==='dB'?'+':''}${val.toFixed(1)} ${unit}`;setOzState(prev=>({...prev,[param]:val}));}const startValParsed=parseFloat(knob.dataset.value);updateUI(startValParsed);knob.addEventListener('pointerdown',e=>{isDragging=true;startY=e.clientY;startVal=ozStateRef.current[param]!==undefined?ozStateRef.current[param]:startValParsed;knob.setPointerCapture(e.pointerId);});knob.addEventListener('pointermove',e=>{if(!isDragging)return;const deltaY=startY-e.clientY;let newVal=startVal+deltaY/100*(max-min);newVal=Math.min(max,Math.max(min,newVal));updateUI(newVal);});knob.addEventListener('pointerup',e=>{isDragging=false;knob.releasePointerCapture(e.pointerId);});knob.addEventListener('pointercancel',e=>{isDragging=false;knob.releasePointerCapture(e.pointerId);});});}}return()=>{window.removeEventListener('resize',resizeAll);if(animFrameRef.current)cancelAnimationFrame(animFrameRef.current);};},[isOpen]);React.useEffect(()=>{if(!isOpen){stopAudioDemo();knobsInitializedRef.current=false;}else{setTimeout(()=>{try{window.lucide.createIcons();}catch(e){}},100);}},[isOpen]);if(!isOpen)return null;const switchModule=name=>setOzState(prev=>({...prev,activeModule:name}));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.5 rounded-lg flex flex-col justify-between"},/*#__PURE__*/React.createElement("div",{className:"flex items-center justify-between text-xs font-bold",style:{color}},/*#__PURE__*/React.createElement("span",null,label),/*#__PURE__*/React.createElement("span",{className:"text-[10px] font-mono text-slate-400"},freq)),/*#__PURE__*/React.createElement("div",{className:"flex items-center justify-around my-2"},/*#__PURE__*/React.createElement("div",{className:"knob-container","data-param":param,"data-min":min,"data-max":max,"data-value":val,"data-unit":unit},/*#__PURE__*/React.createElement("div",{className:"knob-dial w-9 h-9 rounded-full bg-slate-800 border-2 relative flex items-center justify-center shadow-md",style:{borderColor:color}},/*#__PURE__*/React.createElement("div",{className:"knob-pointer w-0.5 h-3 rounded absolute top-1",style:{backgroundColor:color}}))),/*#__PURE__*/React.createElement("div",{className:"text-center font-mono"},/*#__PURE__*/React.createElement("span",{className:"knob-val text-xs text-slate-200 font-bold"},val>0&&unit==='dB'?'+':'',val.toFixed(1)," ",unit))),/*#__PURE__*/React.createElement("div",{className:"text-[9px] text-slate-500 font-mono text-center"},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:"),/*#__PURE__*/React.createElement("div",{onClick:()=>switchModule('eq'),className:`w-36 h-12 rounded-lg p-2 flex items-center justify-between cursor-pointer transition-all ${ozState.activeModule==='eq'?'oz-card-active':'oz-card'}`},/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-2"},/*#__PURE__*/React.createElement("button",{onClick:e=>{e.stopPropagation();setOzState(prev=>({...prev,eqActive:!prev.eqActive}));},className:"w-4 h-4 rounded-full flex items-center justify-center text-[9px] font-bold",style:{backgroundColor:ozState.eqActive?'#38bdf8':'#334155',color:ozState.eqActive?'#0f172a':'#94a3b8'}},/*#__PURE__*/React.createElement("i",{"data-lucide":"power",className:"w-2.5 h-2.5"})),/*#__PURE__*/React.createElement("div",null,/*#__PURE__*/React.createElement("div",{className:"text-[11px] font-bold text-slate-200"},"Dynamic EQ"),/*#__PURE__*/React.createElement("div",{className:"text-[9px] text-cyan-400 oz-font-mono"},"4-Band Peak"))),/*#__PURE__*/React.createElement("i",{"data-lucide":"activity",className:"w-3.5 h-3.5 text-slate-500"})),/*#__PURE__*/React.createElement("div",{onClick:()=>switchModule('imager'),className:`w-36 h-12 rounded-lg p-2 flex items-center justify-between cursor-pointer transition-all ${ozState.activeModule==='imager'?'oz-card-active':'oz-card'}`},/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-2"},/*#__PURE__*/React.createElement("button",{onClick:e=>{e.stopPropagation();setOzState(prev=>({...prev,imagerActive:!prev.imagerActive}));},className:"w-4 h-4 rounded-full flex items-center justify-center text-[9px] font-bold",style:{backgroundColor:ozState.imagerActive?'#38bdf8':'#334155',color:ozState.imagerActive?'#0f172a':'#94a3b8'}},/*#__PURE__*/React.createElement("i",{"data-lucide":"power",className:"w-2.5 h-2.5"})),/*#__PURE__*/React.createElement("div",null,/*#__PURE__*/React.createElement("div",{className:"text-[11px] font-bold text-slate-200"},"Imager"),/*#__PURE__*/React.createElement("div",{className:"text-[9px] text-slate-400 oz-font-mono"},"4-Band Width"))),/*#__PURE__*/React.createElement("i",{"data-lucide":"radio",className:"w-3.5 h-3.5 text-slate-500"})),/*#__PURE__*/React.createElement("div",{onClick:()=>switchModule('maximizer'),className:`w-36 h-12 rounded-lg p-2 flex items-center justify-between cursor-pointer transition-all ${ozState.activeModule==='maximizer'?'oz-card-active':'oz-card'}`},/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-2"},/*#__PURE__*/React.createElement("button",{onClick:e=>{e.stopPropagation();setOzState(prev=>({...prev,maximizerActive:!prev.maximizerActive}));},className:"w-4 h-4 rounded-full flex items-center justify-center text-[9px] font-bold",style:{backgroundColor:ozState.maximizerActive?'#38bdf8':'#334155',color:ozState.maximizerActive?'#0f172a':'#94a3b8'}},/*#__PURE__*/React.createElement("i",{"data-lucide":"power",className:"w-2.5 h-2.5"})),/*#__PURE__*/React.createElement("div",null,/*#__PURE__*/React.createElement("div",{className:"text-[11px] font-bold text-slate-200"},"Maximizer"),/*#__PURE__*/React.createElement("div",{className:"text-[9px] text-slate-400 oz-font-mono"},"IRC IV True Peak"))),/*#__PURE__*/React.createElement("i",{"data-lucide":"gauge",className:"w-3.5 h-3.5 text-slate-500"})),/*#__PURE__*/React.createElement("div",{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"},/*#__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"},/*#__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]"},/*#__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==='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"},/*#__PURE__*/React.createElement("i",{"data-lucide":"radio",className:"w-3.5 h-3.5"})," Polar Vectorscope & Correlation Meter"),/*#__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:"space-y-3 my-auto"},[{id:'w1',label:'Band 1 (0-100Hz)',color:'#22d3ee',val:ozState.w1},{id:'w2',label:'Band 2 (100-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:"-100",max:"100",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:"knob-container my-2","data-param":"maxGain","data-min":"0","data-max":"12","data-value":"5.4","data-unit":"dB"},/*#__PURE__*/React.createElement("div",{className:"knob-dial w-24 h-24 rounded-full bg-slate-900 border-4 border-cyan-500 relative flex items-center justify-center shadow-2xl"},/*#__PURE__*/React.createElement("div",{className:"knob-pointer w-1 h-8 bg-cyan-400 rounded absolute top-2"}),/*#__PURE__*/React.createElement("span",{className:"knob-val text-sm font-bold oz-font-mono text-cyan-300 z-10"},"+5.4 dB"))),/*#__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 justify-between items-center"},/*#__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("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"},/*#__PURE__*/React.createElement("span",{className:"text-[10px] font-bold text-slate-400 oz-font-mono mb-2"},"UPWARD COMPRESS"),/*#__PURE__*/React.createElement("div",{className:"knob-container","data-param":"maxUpward","data-min":"0","data-max":"10","data-value":"2.0","data-unit":"dB"},/*#__PURE__*/React.createElement("div",{className:"knob-dial w-12 h-12 rounded-full bg-slate-800 border-2 border-cyan-500 relative flex items-center justify-center"},/*#__PURE__*/React.createElement("div",{className:"knob-pointer w-0.5 h-4 bg-cyan-400 rounded absolute top-1"}))),/*#__PURE__*/React.createElement("span",{className:"knob-val text-xs font-mono text-slate-200 mt-2"},"+2.0 dB")),/*#__PURE__*/React.createElement("div",{className:"bg-slate-950 p-3 border border-slate-800 rounded-xl flex flex-col items-center"},/*#__PURE__*/React.createElement("span",{className:"text-[10px] font-bold text-slate-400 oz-font-mono mb-2"},"SOFT CLIPPER"),/*#__PURE__*/React.createElement("div",{className:"knob-container","data-param":"maxSoftClip","data-min":"0","data-max":"100","data-value":"15","data-unit":"%"},/*#__PURE__*/React.createElement("div",{className:"knob-dial w-12 h-12 rounded-full bg-slate-800 border-2 border-amber-500 relative flex items-center justify-center"},/*#__PURE__*/React.createElement("div",{className:"knob-pointer w-0.5 h-4 bg-amber-400 rounded absolute top-1"}))),/*#__PURE__*/React.createElement("span",{className:"knob-val text-xs font-mono text-slate-200 mt-2"},"15%")),/*#__PURE__*/React.createElement("div",{className:"bg-slate-950 p-3 border border-slate-800 rounded-xl flex flex-col items-center"},/*#__PURE__*/React.createElement("span",{className:"text-[10px] font-bold text-slate-400 oz-font-mono mb-2"},"TRANSIENT EMPHASIS"),/*#__PURE__*/React.createElement("div",{className:"knob-container","data-param":"maxTransient","data-min":"0","data-max":"100","data-value":"25","data-unit":"%"},/*#__PURE__*/React.createElement("div",{className:"knob-dial w-12 h-12 rounded-full bg-slate-800 border-2 border-emerald-500 relative flex items-center justify-center"},/*#__PURE__*/React.createElement("div",{className:"knob-pointer w-0.5 h-4 bg-emerald-400 rounded absolute top-1"}))),/*#__PURE__*/React.createElement("span",{className:"knob-val text-xs font-mono text-slate-200 mt-2"},"25%")))))),/*#__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:"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"))))));};const App=()=>{// ── State Definitions ──
+const renderMeter=(analyser,ctxRef,textId)=>{const canvas=ctxRef.current;if(!canvas)return;const w=canvas.width,h=canvas.height;const ctx=canvas.getContext('2d');ctx.clearRect(0,0,w,h);const peak=getPeakLevel(analyser);const barH=Math.min(1.0,peak)*h;const grad=ctx.createLinearGradient(0,h,0,0);grad.addColorStop(0,'#38bdf8');grad.addColorStop(0.7,'#f59e0b');grad.addColorStop(1,'#ef4444');ctx.fillStyle=grad;ctx.fillRect(2,h-barH,w-4,barH);const el=document.getElementById(textId);if(el){if(peak>0){const dbVal=20*Math.log10(peak);el.innerText=dbVal<-90?'-inf dB':`${dbVal.toFixed(1)} dB`;}else{el.innerText='-inf dB';}}};renderMeter(masterBus&&masterBus.inputAnalyser,inMeterCanvasRef,'inPeakText');renderMeter(masterBus&&masterBus.outputAnalyser,outMeterCanvasRef,'outPeakText');// Wave Observer Oscilloscope Rendering
+const woCanvas=woCanvasRef.current;if(woCanvas){const w=woCanvas.width,h=woCanvas.height;const woCtx=woCanvas.getContext('2d');woCtx.clearRect(0,0,w,h);// Draw grid
+woCtx.strokeStyle='rgba(51, 65, 85, 0.2)';woCtx.lineWidth=1;woCtx.font='8px JetBrains Mono, monospace';woCtx.fillStyle='#475569';const centerY=h/2;const gridLines=[-0.75,-0.5,-0.25,0,0.25,0.5,0.75];gridLines.forEach(g=>{const y=centerY+g*centerY;woCtx.beginPath();woCtx.moveTo(0,y);woCtx.lineTo(w,y);woCtx.stroke();});// Vertical lines
+const ticksCount=10;for(let i=1;i<=ticksCount;i++){const x=i/(ticksCount+1)*w;woCtx.beginPath();woCtx.moveTo(x,0);woCtx.lineTo(x,h);woCtx.stroke();}// dB labels on left side
+woCtx.fillText('-6.0 dB',5,centerY-0.5*centerY+3);woCtx.fillText('-9.0 dB',5,centerY-0.35*centerY+3);woCtx.fillText('-15.0 dB',5,centerY-0.18*centerY+3);woCtx.fillText('-27.0 dB',5,centerY-0.05*centerY+3);woCtx.fillText('-27.0 dB',5,centerY+0.05*centerY+3);woCtx.fillText('-15.0 dB',5,centerY+0.18*centerY+3);woCtx.fillText('-9.0 dB',5,centerY+0.35*centerY+3);woCtx.fillText('-6.0 dB',5,centerY+0.5*centerY+3);// Time indicators at the bottom
+const durationSec=woDurationRef.current;for(let i=1;i<=5;i++){const timeVal=i/6*durationSec;const x=i/6*w;woCtx.fillText(timeVal.toFixed(2)+'s',x-10,h-4);}let leftPeak=0;let rightPeak=0;if(!woPausedRef.current&&masterBus&&masterBus.leftAnalyser&&masterBus.rightAnalyser){const leftData=new Float32Array(512);const rightData=new Float32Array(512);masterBus.leftAnalyser.getFloatTimeDomainData(leftData);masterBus.rightAnalyser.getFloatTimeDomainData(rightData);for(let i=0;i<512;i++){const l=Math.abs(leftData[i]);const r=Math.abs(rightData[i]);if(l>leftPeak)leftPeak=l;if(r>rightPeak)rightPeak=r;}const lHistory=woLeftHistoryRef.current;const rHistory=woRightHistoryRef.current;// Shift history buffer Left
+for(let i=0;i{window.removeEventListener('resize',resizeAll);if(animFrameRef.current)cancelAnimationFrame(animFrameRef.current);};},[isOpen]);React.useEffect(()=>{if(!isOpen){stopAudioDemo();knobsInitializedRef.current=false;}else{setTimeout(()=>{try{window.lucide.createIcons();}catch(e){}},100);}},[isOpen]);if(!isOpen)return null;const switchModule=name=>setOzState(prev=>({...prev,activeModule:name}));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:"),/*#__PURE__*/React.createElement("div",{onClick:()=>switchModule('eq'),className:`w-36 h-12 rounded-lg p-2 flex items-center justify-between cursor-pointer transition-all ${ozState.activeModule==='eq'?'oz-card-active':'oz-card'}`},/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-2"},/*#__PURE__*/React.createElement("button",{onClick:e=>{e.stopPropagation();setOzState(prev=>({...prev,eqActive:!prev.eqActive}));},className:"w-4 h-4 rounded-full flex items-center justify-center text-[9px] font-bold",style:{backgroundColor:ozState.eqActive?'#38bdf8':'#334155',color:ozState.eqActive?'#0f172a':'#94a3b8'}},/*#__PURE__*/React.createElement("i",{"data-lucide":"power",className:"w-2.5 h-2.5"})),/*#__PURE__*/React.createElement("div",null,/*#__PURE__*/React.createElement("div",{className:"text-[11px] font-bold text-slate-200"},"Dynamic EQ"),/*#__PURE__*/React.createElement("div",{className:"text-[9px] text-cyan-400 oz-font-mono"},"4-Band Peak"))),/*#__PURE__*/React.createElement("i",{"data-lucide":"activity",className:"w-3.5 h-3.5 text-slate-500"})),/*#__PURE__*/React.createElement("div",{onClick:()=>switchModule('imager'),className:`w-36 h-12 rounded-lg p-2 flex items-center justify-between cursor-pointer transition-all ${ozState.activeModule==='imager'?'oz-card-active':'oz-card'}`},/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-2"},/*#__PURE__*/React.createElement("button",{onClick:e=>{e.stopPropagation();setOzState(prev=>({...prev,imagerActive:!prev.imagerActive}));},className:"w-4 h-4 rounded-full flex items-center justify-center text-[9px] font-bold",style:{backgroundColor:ozState.imagerActive?'#38bdf8':'#334155',color:ozState.imagerActive?'#0f172a':'#94a3b8'}},/*#__PURE__*/React.createElement("i",{"data-lucide":"power",className:"w-2.5 h-2.5"})),/*#__PURE__*/React.createElement("div",null,/*#__PURE__*/React.createElement("div",{className:"text-[11px] font-bold text-slate-200"},"Imager"),/*#__PURE__*/React.createElement("div",{className:"text-[9px] text-slate-400 oz-font-mono"},"4-Band Width"))),/*#__PURE__*/React.createElement("i",{"data-lucide":"radio",className:"w-3.5 h-3.5 text-slate-500"})),/*#__PURE__*/React.createElement("div",{onClick:()=>switchModule('maximizer'),className:`w-36 h-12 rounded-lg p-2 flex items-center justify-between cursor-pointer transition-all ${ozState.activeModule==='maximizer'?'oz-card-active':'oz-card'}`},/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-2"},/*#__PURE__*/React.createElement("button",{onClick:e=>{e.stopPropagation();setOzState(prev=>({...prev,maximizerActive:!prev.maximizerActive}));},className:"w-4 h-4 rounded-full flex items-center justify-center text-[9px] font-bold",style:{backgroundColor:ozState.maximizerActive?'#38bdf8':'#334155',color:ozState.maximizerActive?'#0f172a':'#94a3b8'}},/*#__PURE__*/React.createElement("i",{"data-lucide":"power",className:"w-2.5 h-2.5"})),/*#__PURE__*/React.createElement("div",null,/*#__PURE__*/React.createElement("div",{className:"text-[11px] font-bold text-slate-200"},"Maximizer"),/*#__PURE__*/React.createElement("div",{className:"text-[9px] text-slate-400 oz-font-mono"},"IRC IV True Peak"))),/*#__PURE__*/React.createElement("i",{"data-lucide":"gauge",className:"w-3.5 h-3.5 text-slate-500"})),/*#__PURE__*/React.createElement("div",{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"},/*#__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"},/*#__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]"},/*#__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==='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"},/*#__PURE__*/React.createElement("i",{"data-lucide":"radio",className:"w-3.5 h-3.5"})," Polar Vectorscope & Correlation Meter"),/*#__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:"space-y-3 my-auto"},[{id:'w1',label:'Band 1 (0-100Hz)',color:'#22d3ee',val:ozState.w1},{id:'w2',label:'Band 2 (100-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:"-100",max:"100",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:"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:"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"))))));};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
diff --git a/tests/user_configs_test.json b/tests/user_configs_test.json
new file mode 100644
index 0000000..5f39245
--- /dev/null
+++ b/tests/user_configs_test.json
@@ -0,0 +1,47 @@
+{
+ "ai_configs": {
+ "anonymous": [
+ {
+ "id": "openai_default",
+ "name": "OpenAI Official",
+ "provider_type": "openai",
+ "api_base_url": "https://api.openai.com/v1",
+ "api_key": "test-sk-key-123",
+ "model_name": "gpt-4o",
+ "temperature": 0.7,
+ "is_active": true
+ },
+ {
+ "id": "openai_compat_default",
+ "name": "OpenAI Compatible (Ollama/LocalAI/DeepSeek)",
+ "provider_type": "openai_compatible",
+ "api_base_url": "http://localhost:11434/v1",
+ "api_key": "ollama",
+ "model_name": "deepseek-r1",
+ "temperature": 0.7,
+ "is_active": false
+ },
+ {
+ "id": "anthropic_default",
+ "name": "Anthropic Claude",
+ "provider_type": "anthropic",
+ "api_base_url": "https://api.anthropic.com/v1",
+ "api_key": "",
+ "model_name": "claude-3-5-sonnet",
+ "temperature": 0.7,
+ "is_active": false
+ },
+ {
+ "id": "gemini_default",
+ "name": "Google Gemini",
+ "provider_type": "gemini",
+ "api_base_url": "https://generativelanguage.googleapis.com",
+ "api_key": "",
+ "model_name": "gemini-1.5-pro",
+ "temperature": 0.7,
+ "is_active": false
+ }
+ ]
+ },
+ "preferences": {}
+}
\ No newline at end of file