From f2c82164e845ba329114c08a6e092cc15b0f5db9 Mon Sep 17 00:00:00 2001 From: "alexei.dolgolyov" Date: Sat, 25 Apr 2026 11:41:32 +0300 Subject: [PATCH] ui(vu): narrower 44deg swing, peak-based level, faster response; mini progress bar fix - VU needle swings -22..+22deg instead of -45..+45 for a more realistic VU look - Switch from RMS to peak frequency reading so the needle catches musical hits - Faster attack (0.7) and release (0.25) so it swings rather than pinning - Replace explicit grid lines with subtle repeating-conic-gradient ticks - Scope mini progress bar styles to .mini-player; taller (3px), clickable --- media_server/static/css/styles.css | 45 +++++++++--------------------- media_server/static/js/player.js | 32 +++++++++------------ 2 files changed, 26 insertions(+), 51 deletions(-) diff --git a/media_server/static/css/styles.css b/media_server/static/css/styles.css index 1a8b1f7..c7ac6d8 100644 --- a/media_server/static/css/styles.css +++ b/media_server/static/css/styles.css @@ -5377,12 +5377,17 @@ body.visualizer-active .vinyl-stage .spectrogram-canvas { } #mini-current-time { color: var(--copper); font-weight: 500; } -.mini-progress-bar { - height: 2px; +.mini-player .mini-progress-bar { + flex: 0 0 auto; + height: 3px; background: var(--rule-strong); border-radius: 0; + cursor: pointer; + position: relative; + min-width: 0; } -.mini-progress-fill { +.mini-player .mini-progress-fill { + height: 100%; background: var(--copper); box-shadow: 0 0 8px var(--copper-glow); border-radius: 0; @@ -7062,33 +7067,10 @@ body.audio-spectrum-live .now-playing .spectrum span { content: ""; position: absolute; inset: 0; - /* 11 grid lines (every 9°) drawn ONLY in the needle's -45°..+45° - swing range. No mask — the conic-gradient is explicitly transparent - outside the 90° active wedge so the leftmost line aligns with the - needle's rest position (proper zero). */ - background: conic-gradient(from 315deg at 50% 100%, - rgba(242, 235, 220, 0.18) 0deg 0.5deg, - transparent 0.5deg 9deg, - rgba(242, 235, 220, 0.18) 9deg 9.5deg, - transparent 9.5deg 18deg, - rgba(242, 235, 220, 0.18) 18deg 18.5deg, - transparent 18.5deg 27deg, - rgba(242, 235, 220, 0.18) 27deg 27.5deg, - transparent 27.5deg 36deg, - rgba(242, 235, 220, 0.18) 36deg 36.5deg, - transparent 36.5deg 45deg, - rgba(242, 235, 220, 0.25) 45deg 45.5deg, /* slightly brighter centre line at 0 */ - transparent 45.5deg 54deg, - rgba(242, 235, 220, 0.18) 54deg 54.5deg, - transparent 54.5deg 63deg, - rgba(242, 235, 220, 0.18) 63deg 63.5deg, - transparent 63.5deg 72deg, - rgba(242, 235, 220, 0.18) 72deg 72.5deg, - transparent 72.5deg 81deg, - rgba(242, 235, 220, 0.18) 81deg 81.5deg, - transparent 81.5deg 90deg, - rgba(242, 235, 220, 0.18) 90deg 90.5deg, - transparent 90.5deg 360deg); + background: repeating-conic-gradient(from 195deg at 50% 100%, + transparent 0deg 4deg, + rgba(242, 235, 220, 0.08) 4deg 5deg, + transparent 5deg 9deg); } .now-playing .vu-meter::after { content: "VU"; @@ -7109,8 +7091,7 @@ body.audio-spectrum-live .now-playing .spectrum span { height: 88%; background: linear-gradient(to top, var(--copper) 0%, var(--copper-hi) 70%, var(--ink) 100%); transform-origin: bottom center; - /* Rest at full-left like a real VU meter at silence (-∞ dB) */ - transform: rotate(-45deg); + transform: rotate(-22deg); transition: transform 350ms var(--ease); box-shadow: 0 0 8px var(--copper-glow); } diff --git a/media_server/static/js/player.js b/media_server/static/js/player.js index d791ae3..8ab91d2 100644 --- a/media_server/static/js/player.js +++ b/media_server/static/js/player.js @@ -763,10 +763,10 @@ export function updateUI(status) { dom.volumeDisplay.textContent = `${status.volume}%`; dom.miniVolumeSlider.value = status.volume; dom.miniVolumeDisplay.textContent = `${status.volume}%`; - // VU needle: map 0-100 volume to -45deg..+45deg rotation. + // VU needle: map 0-100 volume to -22deg..+22deg rotation. const needle = document.getElementById('vuNeedle'); if (needle) { - const deg = -45 + (status.volume / 100) * 90; + const deg = -22 + (status.volume / 100) * 44; needle.style.transform = `rotate(${deg}deg)`; } // Editorial VU readout: VOL XX% / OUT (SYS or MUTED) @@ -802,22 +802,19 @@ export function updateUI(status) { // slider position so the needle still looks alive. let vuWobbleHandle = null; let vuWobbleStart = 0; -let vuLevelSmoothed = 0; // Smoothed RMS of recent frequency frames -const VU_LEVEL_ATTACK = 0.55; // How fast needle climbs to a peak -const VU_LEVEL_RELEASE = 0.12; // How fast it falls back +let vuLevelSmoothed = 0; +const VU_LEVEL_ATTACK = 0.7; // Fast climb so the needle catches musical hits +const VU_LEVEL_RELEASE = 0.25; // Faster fall so it swings between hits, not pins function readAudioLevel() { - // frequencyData is the WS-driven FFT payload from player.js scope. if (!frequencyData || !frequencyData.frequencies) return null; const bins = frequencyData.frequencies; if (!bins.length) return null; - let sumSq = 0; - // Skip the very lowest bin (DC + sub-rumble) for cleaner level. - for (let i = 1; i < bins.length; i++) sumSq += bins[i] * bins[i]; - const rms = Math.sqrt(sumSq / (bins.length - 1)); - // The values are in 0..1 from the backend; gain a touch so quieter - // tracks still swing the needle. - return Math.min(1, rms * 1.6); + let peak = 0; + for (let i = 1; i < bins.length; i++) { + if (bins[i] > peak) peak = bins[i]; + } + return Math.min(1, peak * 1.4); } function startVuWobble() { @@ -839,11 +836,9 @@ function startVuWobble() { const wanted = audioLevel * (vol / 100); const k = wanted > vuLevelSmoothed ? VU_LEVEL_ATTACK : VU_LEVEL_RELEASE; vuLevelSmoothed = vuLevelSmoothed * (1 - k) + wanted * k; - // Map 0..1 to -45deg..+45deg. - target = -45 + vuLevelSmoothed * 90; + target = -22 + vuLevelSmoothed * 44; } else { - // Synthetic fallback: volume-mapped + sine wobble. - const base = -45 + (vol / 100) * 90; + const base = -22 + (vol / 100) * 44; const mag = Math.max(2, Math.min(14, vol * 0.16)); const t = (performance.now() - vuWobbleStart) / 1000; target = base @@ -864,9 +859,8 @@ function stopVuWobble() { vuWobbleHandle = null; } vuLevelSmoothed = 0; - // Settle needle back to the bottom of the swing. const needle = document.getElementById('vuNeedle'); - if (needle) needle.style.transform = 'rotate(-45deg)'; + if (needle) needle.style.transform = 'rotate(-22deg)'; } export function updatePlaybackState(state) {