From eb61015cffca95d344b4542cf3a453954b65bcde Mon Sep 17 00:00:00 2001 From: 3dtours Date: Thu, 30 Jul 2026 11:18:35 +0700 Subject: [PATCH] =?UTF-8?q?FIX:=20s=E1=BB=ADa=20MASTERING=20PANEL=20thay?= =?UTF-8?q?=20=C4=91=E1=BB=95i=20knob=20th=C3=AC=20thay=20=C4=91=E1=BB=95i?= =?UTF-8?q?=20c=C3=A1c=20th=C3=B4ng=20s=E1=BB=91=20hi=E1=BB=83n=20th?= =?UTF-8?q?=E1=BB=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/static/js/app.jsx | 569 +++++++++++++++++++++++++------ app/static/js/app.precompiled.js | 22 +- tests/user_configs_test.json | 47 +++ 3 files changed, 532 insertions(+), 106 deletions(-) create mode 100644 tests/user_configs_test.json diff --git a/app/static/js/app.jsx b/app/static/js/app.jsx index 843516e..7f78441 100644 --- a/app/static/js/app.jsx +++ b/app/static/js/app.jsx @@ -226,6 +226,16 @@ function initMasterBus(ctx) { 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(), @@ -236,6 +246,8 @@ function initMasterBus(ctx) { // Analysers for metering inputAnalyser, outputAnalyser, + leftAnalyser, + rightAnalyser, // Mastering nodes eqLowFilter, eqMid1Filter, eqMid2Filter, eqHighFilter, @@ -6114,6 +6126,9 @@ const PianoRollTabEditor = ({ st, zoom, bpm, viewportWidth, activeTracks, onClos e.stopPropagation(); keybedMouseDownRef.current = true; try { + if (window.triggerMidiVuActivity) { + window.triggerMidiVuActivity(st.trackId, 100); + } if (window.SonicSF) { window.SonicSF.playNote(pitch, 100, 500, undefined, st.instrumentProgram, null, kbCh, kbSynth); } @@ -6124,6 +6139,9 @@ const PianoRollTabEditor = ({ st, zoom, bpm, viewportWidth, activeTracks, onClos onMouseEnter: (e) => { if (keybedMouseDownRef.current) { try { + if (window.triggerMidiVuActivity) { + window.triggerMidiVuActivity(st.trackId, 100); + } if (window.SonicSF) { window.SonicSF.playNote(pitch, 100, 200, undefined, st.instrumentProgram, null, kbCh, kbSynth); } @@ -7016,6 +7034,79 @@ const deserializeProjectFromSchema = (schemaObj) => { }; }; +// ────────────────────────────────────────────── +// 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 ( +
+ {label && {label}} +
+
+ {isLarge && ( + + {value > 0 && unit === 'dB' ? '+' : ''}{value.toFixed(1)} {unit} + + )} +
+ {!isLarge && ( + + {value > 0 && unit === 'dB' ? '+' : ''}{value.toFixed(1)}{unit} + + )} +
+ ); +}; + // ────────────────────────────────────────────── // MASTERING MODAL COMPONENT (from md/45_MASTERING_MODULE.md) // ────────────────────────────────────────────── @@ -7039,6 +7130,30 @@ const MasteringModal = ({ isOpen, onClose, masteringSettings, setMasteringSettin 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; @@ -7097,6 +7212,7 @@ const MasteringModal = ({ isOpen, onClose, masteringSettings, setMasteringSettin resizeCanvas(imagerCanvasRef); resizeCanvas(inMeterCanvasRef); resizeCanvas(outMeterCanvasRef); + resizeCanvas(woCanvasRef); } resizeAll(); window.addEventListener('resize', resizeAll); @@ -7223,61 +7339,163 @@ const MasteringModal = ({ isOpen, onClose, masteringSettings, setMasteringSettin }; 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 < lHistory.length - 1; i++) { + lHistory[i] = lHistory[i + 1]; + rHistory[i] = rHistory[i + 1]; + } + + if (woModeRef.current === 'envelope') { + lHistory[lHistory.length - 1] = leftPeak; + rHistory[rHistory.length - 1] = rightPeak; + } else { + lHistory[lHistory.length - 1] = leftData[0]; + rHistory[rHistory.length - 1] = rightData[0]; + } + } + + const lHistory = woLeftHistoryRef.current; + const rHistory = woRightHistoryRef.current; + const zoomGain = Math.pow(10, woZoomRef.current / 20); + + // Update input meter bars + if (woLeftMeterRef.current && woRightMeterRef.current) { + const lPct = Math.min(100, leftPeak * 100); + const rPct = Math.min(100, rightPeak * 100); + woLeftMeterRef.current.style.width = `${lPct}%`; + woRightMeterRef.current.style.width = `${rPct}%`; + } + + // Left channel line (cyan) + if (woChannelRef.current === 'stereo' || woChannelRef.current === 'left') { + woCtx.strokeStyle = '#22d3ee'; + woCtx.lineWidth = 1.2; + woCtx.beginPath(); + for (let i = 0; i < lHistory.length; i++) { + const x = (i / (lHistory.length - 1)) * w; + const val = lHistory[i] * zoomGain; + const y = centerY - val * centerY; + if (i === 0) woCtx.moveTo(x, y); + else woCtx.lineTo(x, y); + } + woCtx.stroke(); + + if (woModeRef.current === 'envelope') { + woCtx.beginPath(); + for (let i = 0; i < lHistory.length; i++) { + const x = (i / (lHistory.length - 1)) * w; + const val = lHistory[i] * zoomGain; + const y = centerY + val * centerY; + if (i === 0) woCtx.moveTo(x, y); + else woCtx.lineTo(x, y); + } + woCtx.stroke(); + } + } + + // Right channel line (teal) + if (woChannelRef.current === 'stereo' || woChannelRef.current === 'right') { + woCtx.strokeStyle = '#0d9488'; + woCtx.lineWidth = 1.2; + woCtx.beginPath(); + for (let i = 0; i < rHistory.length; i++) { + const x = (i / (rHistory.length - 1)) * w; + const val = rHistory[i] * zoomGain; + const y = centerY - val * centerY; + if (i === 0) woCtx.moveTo(x, y); + else woCtx.lineTo(x, y); + } + woCtx.stroke(); + + if (woModeRef.current === 'envelope') { + woCtx.beginPath(); + for (let i = 0; i < rHistory.length; i++) { + const x = (i / (rHistory.length - 1)) * w; + const val = rHistory[i] * zoomGain; + const y = centerY + val * centerY; + if (i === 0) woCtx.moveTo(x, y); + else woCtx.lineTo(x, y); + } + woCtx.stroke(); + } + } + } } 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); @@ -7298,22 +7516,23 @@ const MasteringModal = ({ isOpen, onClose, masteringSettings, setMasteringSettin const switchModule = (name) => setOzState(prev => ({ ...prev, activeModule: name })); const bandKnob = (param, min, max, val, unit, label, freq, color, filterType) => ( -
-
+
+
{label} - {freq} + {freq}
-
-
-
-
-
-
-
- {val > 0 && unit === 'dB' ? '+' : ''}{val.toFixed(1)} {unit} -
+
+ setOzState(prev => ({ ...prev, [p]: v }))} + />
-
{filterType}
+
{filterType}
); @@ -7489,49 +7708,156 @@ const MasteringModal = ({ isOpen, onClose, masteringSettings, setMasteringSettin
Maximizer Gain Boost -
-
-
- +5.4 dB -
+
+ 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 */} +
+ Input +
+
+ L +
+
+
+
+
+ R +
+
+
+
+
+
+ + {/* 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