using System; using System.Drawing; using System.Drawing.Drawing2D; using System.Windows.Forms; namespace Pulsar.Server.Controls { /// /// A custom control that visualizes audio levels with smooth animations in dark mode. /// public class AudioVisualizer : Control { private const int BAR_SPACING = 2; private int _barCount = 40; private const float DECAY_RATE = 0.92f; private const float SMOOTHING = 0.3f; private float[] _barHeights; private float[] _targetHeights; private float _currentLevel = 0f; private float _targetLevel = 0f; private Timer _animationTimer; private Random _random = new Random(); // Dark mode colors private readonly Color _backgroundColor = Color.FromArgb(28, 28, 28); private readonly Color _barColorLow = Color.FromArgb(0, 150, 255); private readonly Color _barColorMid = Color.FromArgb(0, 200, 100); private readonly Color _barColorHigh = Color.FromArgb(255, 100, 0); public AudioVisualizer() { SetStyle(ControlStyles.AllPaintingInWmPaint | ControlStyles.UserPaint | ControlStyles.OptimizedDoubleBuffer | ControlStyles.ResizeRedraw, true); BackColor = _backgroundColor; _animationTimer = new Timer(); _animationTimer.Interval = 33; // ~30 FPS _animationTimer.Tick += AnimationTimer_Tick; _animationTimer.Start(); CalculateBarCount(); InitializeBars(); } /// /// Calculates the optimal number of bars to fill the control width. /// private void CalculateBarCount() { if (Width > 0) { // Calculate how many bars can fit (minimum 3 pixels per bar + spacing) _barCount = Math.Max(20, Width / 5); } else { _barCount = 40; // Default } } protected override void OnResize(EventArgs e) { base.OnResize(e); CalculateBarCount(); InitializeBars(); } private void InitializeBars() { _barHeights = new float[_barCount]; _targetHeights = new float[_barCount]; for (int i = 0; i < _barCount; i++) { _barHeights[i] = 0f; _targetHeights[i] = 0f; } } /// /// Updates the audio level for visualization. /// /// Audio level between 0.0 and 1.0 public void UpdateLevel(float level) { _targetLevel = Math.Max(0f, Math.Min(1f, level)); } /// /// Resets the visualizer to zero. /// public void Reset() { _targetLevel = 0f; _currentLevel = 0f; if (_barHeights != null) { for (int i = 0; i < _barCount; i++) { _barHeights[i] = 0f; _targetHeights[i] = 0f; } } Invalidate(); } private void AnimationTimer_Tick(object sender, EventArgs e) { if (_barHeights == null || _targetHeights == null) return; _currentLevel += (_targetLevel - _currentLevel) * SMOOTHING; for (int i = 0; i < _barCount; i++) { float baseHeight = _currentLevel; if (_currentLevel > 0.01f) { float variation = (float)_random.NextDouble() * 0.3f - 0.15f; _targetHeights[i] = Math.Max(0f, Math.Min(1f, baseHeight + variation)); } else { _targetHeights[i] = 0f; } if (_barHeights[i] < _targetHeights[i]) { _barHeights[i] += (_targetHeights[i] - _barHeights[i]) * 0.4f; } else { _barHeights[i] *= DECAY_RATE; } } _targetLevel *= 0.85f; Invalidate(); } protected override void OnPaint(PaintEventArgs e) { base.OnPaint(e); if (_barHeights == null || _targetHeights == null) return; Graphics g = e.Graphics; g.SmoothingMode = SmoothingMode.AntiAlias; int barWidth = (Width - (_barCount - 1) * BAR_SPACING) / _barCount; if (barWidth < 1) barWidth = 1; for (int i = 0; i < _barCount; i++) { int x = i * (barWidth + BAR_SPACING); int barHeight = (int)(_barHeights[i] * Height); int y = Height - barHeight; if (barHeight > 0) { Color barColor; if (_barHeights[i] < 0.5f) { barColor = _barColorLow; } else if (_barHeights[i] < 0.8f) { barColor = _barColorMid; } else { barColor = _barColorHigh; } using (SolidBrush brush = new SolidBrush(barColor)) { g.FillRectangle(brush, x, y, barWidth, barHeight); } } } } protected override void Dispose(bool disposing) { if (disposing) { _animationTimer?.Stop(); _animationTimer?.Dispose(); } base.Dispose(disposing); } } }