initial commit
Pulsar .NET 9.0 Windows Release / build (push) Waiting to run
Mirror to Codeberg and Gitea / mirror (push) Waiting to run

This commit is contained in:
i2p
2026-08-27 10:57:58 -06:00
commit 773d05f8f1
1038 changed files with 109261 additions and 0 deletions
+201
View File
@@ -0,0 +1,201 @@
using System;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Windows.Forms;
namespace Pulsar.Server.Controls
{
/// <summary>
/// A custom control that visualizes audio levels with smooth animations in dark mode.
/// </summary>
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();
}
/// <summary>
/// Calculates the optimal number of bars to fill the control width.
/// </summary>
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;
}
}
/// <summary>
/// Updates the audio level for visualization.
/// </summary>
/// <param name="level">Audio level between 0.0 and 1.0</param>
public void UpdateLevel(float level)
{
_targetLevel = Math.Max(0f, Math.Min(1f, level));
}
/// <summary>
/// Resets the visualizer to zero.
/// </summary>
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);
}
}
}