又写了一堆屎山
This commit is contained in:
@@ -0,0 +1,77 @@
|
||||
namespace AudioVisualizer
|
||||
{
|
||||
partial class MainWindow
|
||||
{
|
||||
/// <summary>
|
||||
/// Required designer variable.
|
||||
/// </summary>
|
||||
private System.ComponentModel.IContainer components = null;
|
||||
|
||||
/// <summary>
|
||||
/// Clean up any resources being used.
|
||||
/// </summary>
|
||||
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing && (components != null))
|
||||
{
|
||||
components.Dispose();
|
||||
}
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
|
||||
#region Windows Form Designer generated code
|
||||
|
||||
/// <summary>
|
||||
/// Required method for Designer support - do not modify
|
||||
/// the contents of this method with the code editor.
|
||||
/// </summary>
|
||||
private void InitializeComponent()
|
||||
{
|
||||
components = new System.ComponentModel.Container();
|
||||
dataTimer = new System.Windows.Forms.Timer(components);
|
||||
drawingPanel = new Panel();
|
||||
drawingTimer = new System.Windows.Forms.Timer(components);
|
||||
SuspendLayout();
|
||||
//
|
||||
// dataTimer
|
||||
//
|
||||
dataTimer.Interval = 30;
|
||||
dataTimer.Tick += DataTimer_Tick;
|
||||
//
|
||||
// drawingPanel
|
||||
//
|
||||
drawingPanel.Dock = DockStyle.Fill;
|
||||
drawingPanel.Location = new Point(0, 0);
|
||||
drawingPanel.Name = "drawingPanel";
|
||||
drawingPanel.Size = new Size(880, 432);
|
||||
drawingPanel.TabIndex = 0;
|
||||
drawingPanel.MouseDoubleClick += DrawingPanel_MouseDoubleClick;
|
||||
//
|
||||
// drawingTimer
|
||||
//
|
||||
drawingTimer.Interval = 30;
|
||||
drawingTimer.Tick += DrawingTimer_Tick;
|
||||
//
|
||||
// MainWindow
|
||||
//
|
||||
AutoScaleDimensions = new SizeF(11F, 24F);
|
||||
AutoScaleMode = AutoScaleMode.Font;
|
||||
BackColor = Color.Black;
|
||||
ClientSize = new Size(880, 432);
|
||||
Controls.Add(drawingPanel);
|
||||
FormBorderStyle = FormBorderStyle.None;
|
||||
Name = "MainWindow";
|
||||
Text = "Music Visualizer";
|
||||
FormClosed += MainWindow_FormClosed;
|
||||
Load += MainWindow_Load;
|
||||
ResumeLayout(false);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private System.Windows.Forms.Timer dataTimer;
|
||||
private Panel drawingPanel;
|
||||
private System.Windows.Forms.Timer drawingTimer;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,466 @@
|
||||
using LibAudioVisualizer;
|
||||
using NAudio.CoreAudioApi;
|
||||
using NAudio.Wave;
|
||||
using System.Drawing.Drawing2D;
|
||||
using System.Numerics;
|
||||
|
||||
namespace AudioVisualizer
|
||||
{
|
||||
public partial class MainWindow : Form
|
||||
{
|
||||
WasapiCapture capture; // 音频捕获
|
||||
Visualizer visualizer; // 可视化
|
||||
double[]? spectrumData; // 频谱数据
|
||||
|
||||
Color[] allColors; // 渐变颜色
|
||||
|
||||
public MainWindow()
|
||||
{
|
||||
capture = new WasapiLoopbackCapture(); // 捕获电脑发出的声音
|
||||
visualizer = new Visualizer(256); // 新建一个可视化器, 并使用 256 个采样进行傅里叶变换
|
||||
|
||||
allColors = GetAllHsvColors(); // 获取所有的渐变颜色 (HSV 颜色)
|
||||
|
||||
capture.WaveFormat = WaveFormat.CreateIeeeFloatWaveFormat(8192, 1); // 指定捕获的格式, 单声道, 32位深度, IeeeFloat 编码, 8192采样率
|
||||
capture.DataAvailable += Capture_DataAvailable; // 订阅事件
|
||||
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取 HSV 中所有的基础颜色 (饱和度和明度均为最大值)
|
||||
/// </summary>
|
||||
/// <returns>所有的 HSV 基础颜色(共 256 * 6 个, 并且随着索引增加, 颜色也会渐变)</returns>
|
||||
private Color[] GetAllHsvColors()
|
||||
{
|
||||
Color[] result = new Color[256 * 6];
|
||||
|
||||
for (int i = 0; i < 256; i++)
|
||||
{
|
||||
result[i] = Color.FromArgb(255, i, 0);
|
||||
}
|
||||
|
||||
for (int i = 0; i < 256; i++)
|
||||
{
|
||||
result[256 + i] = Color.FromArgb(255 - i, 255, 0);
|
||||
}
|
||||
|
||||
for (int i = 0; i < 256; i++)
|
||||
{
|
||||
result[512 + i] = Color.FromArgb(0, 255, i);
|
||||
}
|
||||
|
||||
for (int i = 0; i < 256; i++)
|
||||
{
|
||||
result[768 + i] = Color.FromArgb(0, 255 - i, 255);
|
||||
}
|
||||
|
||||
for (int i = 0; i < 256; i++)
|
||||
{
|
||||
result[1024 + i] = Color.FromArgb(i, 0, 255);
|
||||
}
|
||||
|
||||
for (int i = 0; i < 256; i++)
|
||||
{
|
||||
result[1280 + i] = Color.FromArgb(255, 0, 255 - i);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 当捕获有数据的时候, 就怼到可视化器里面
|
||||
/// </summary>
|
||||
/// <param name="sender"></param>
|
||||
/// <param name="e"></param>
|
||||
private void Capture_DataAvailable(object? sender, WaveInEventArgs e)
|
||||
{
|
||||
int length = e.BytesRecorded / 4; // 采样的数量 (每一个采样是 4 字节)
|
||||
double[] result = new double[length]; // 声明结果
|
||||
|
||||
for (int i = 0; i < length; i++)
|
||||
result[i] = BitConverter.ToSingle(e.Buffer, i * 4); // 取出采样值
|
||||
|
||||
visualizer.PushSampleData(result); // 将新的采样存储到 可视化器 中
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 用来刷新频谱数据以及实现频谱数据缓动
|
||||
/// </summary>
|
||||
/// <param name="sender"></param>
|
||||
/// <param name="e"></param>
|
||||
private void DataTimer_Tick(object? sender, EventArgs e)
|
||||
{
|
||||
double[] newSpectrumData = visualizer.GetSpectrumData(); // 从可视化器中获取频谱数据
|
||||
newSpectrumData = Visualizer.GetBlurry(newSpectrumData, 2); // 平滑频谱数据
|
||||
|
||||
spectrumData = newSpectrumData;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 绘制一个渐变的 波浪
|
||||
/// </summary>
|
||||
/// <param name="g">绘图目标</param>
|
||||
/// <param name="down">下方颜色</param>
|
||||
/// <param name="up">上方颜色</param>
|
||||
/// <param name="spectrumData">频谱数据</param>
|
||||
/// <param name="pointCount">波浪中, 点的数量</param>
|
||||
/// <param name="drawingWidth">波浪的宽度</param>
|
||||
/// <param name="xOffset">波浪的起始X坐标</param>
|
||||
/// <param name="yOffset">波浪的其实Y坐标</param>
|
||||
/// <param name="scale">频谱的缩放(使用负值可以翻转波浪)</param>
|
||||
private void DrawGradient(Graphics g, Color down, Color up, double[] spectrumData, int pointCount, int drawingWidth, float xOffset, float yOffset, double scale)
|
||||
{
|
||||
GraphicsPath path = new GraphicsPath();
|
||||
|
||||
PointF[] points = new PointF[pointCount + 2];
|
||||
for (int i = 0; i < pointCount; i++)
|
||||
{
|
||||
double x = i * drawingWidth / pointCount + xOffset;
|
||||
double y = spectrumData[i * spectrumData.Length / pointCount] * scale + yOffset;
|
||||
points[i + 1] = new PointF((float)x, (float)y);
|
||||
}
|
||||
|
||||
points[0] = new PointF(xOffset, yOffset);
|
||||
points[points.Length - 1] = new PointF(xOffset + drawingWidth, yOffset);
|
||||
|
||||
path.AddCurve(points);
|
||||
|
||||
float upP = (float)points.Min(v => v.Y);
|
||||
|
||||
if (Math.Abs(upP - yOffset) < 1)
|
||||
return;
|
||||
|
||||
using Brush brush = new LinearGradientBrush(new PointF(0, yOffset), new PointF(0, upP), down, up);
|
||||
g.FillPath(brush, path);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 绘制渐变的条形
|
||||
/// </summary>
|
||||
/// <param name="g">绘图目标</param>
|
||||
/// <param name="down">下方颜色</param>
|
||||
/// <param name="up">上方颜色</param>
|
||||
/// <param name="spectrumData">频谱数据</param>
|
||||
/// <param name="stripCount">条形的数量</param>
|
||||
/// <param name="drawingWidth">绘图的宽度</param>
|
||||
/// <param name="xOffset">绘图的起始 X 坐标</param>
|
||||
/// <param name="yOffset">绘图的起始 Y 坐标</param>
|
||||
/// <param name="spacing">条形与条形之间的间隔(像素)</param>
|
||||
/// <param name="scale"></param>
|
||||
private void DrawGradientStrips(Graphics g, Color down, Color up, double[] spectrumData, int stripCount, int drawingWidth, float xOffset, float yOffset, float spacing, double scale)
|
||||
{
|
||||
float stripWidth = (drawingWidth - spacing * stripCount) / stripCount;
|
||||
PointF[] points = new PointF[stripCount];
|
||||
|
||||
for (int i = 0; i < stripCount; i++)
|
||||
{
|
||||
double x = stripWidth * i + spacing * i + xOffset;
|
||||
double y = spectrumData[i * spectrumData.Length / stripCount] * scale; // height
|
||||
points[i] = new PointF((float)x, (float)y);
|
||||
}
|
||||
|
||||
float upP = (float)points.Min(v => v.Y < 0 ? yOffset + v.Y : yOffset);
|
||||
float downP = (float)points.Max(v => v.Y < 0 ? yOffset : yOffset + v.Y);
|
||||
|
||||
if (downP < yOffset)
|
||||
downP = yOffset;
|
||||
|
||||
if (Math.Abs(upP - downP) < 1)
|
||||
return;
|
||||
|
||||
using Brush brush = new LinearGradientBrush(new PointF(0, downP), new PointF(0, upP), down, up);
|
||||
|
||||
for (int i = 0; i < stripCount; i++)
|
||||
{
|
||||
PointF p = points[i];
|
||||
float y = yOffset;
|
||||
float height = p.Y;
|
||||
|
||||
if (height < 0)
|
||||
{
|
||||
y += height;
|
||||
height = -height;
|
||||
}
|
||||
|
||||
g.FillRectangle(brush, new RectangleF(p.X, y, stripWidth, height));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 画曲线
|
||||
/// </summary>
|
||||
/// <param name="g"></param>
|
||||
/// <param name="pen"></param>
|
||||
/// <param name="spectrumData"></param>
|
||||
/// <param name="pointCount"></param>
|
||||
/// <param name="drawingWidth"></param>
|
||||
/// <param name="xOffset"></param>
|
||||
/// <param name="yOffset"></param>
|
||||
/// <param name="scale"></param>
|
||||
private void DrawCurve(Graphics g, Pen pen, double[] spectrumData, int pointCount, int drawingWidth, double xOffset, double yOffset, double scale)
|
||||
{
|
||||
PointF[] points = new PointF[pointCount];
|
||||
for (int i = 0; i < pointCount; i++)
|
||||
{
|
||||
double x = i * drawingWidth / pointCount + xOffset;
|
||||
double y = spectrumData[i * spectrumData.Length / pointCount] * scale + yOffset;
|
||||
points[i] = new PointF((float)x, (float)y);
|
||||
}
|
||||
|
||||
g.DrawCurve(pen, points);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 画简单的圆环线条
|
||||
/// </summary>
|
||||
/// <param name="g"></param>
|
||||
/// <param name="brush"></param>
|
||||
/// <param name="spectrumData"></param>
|
||||
/// <param name="stripCount"></param>
|
||||
/// <param name="xOffset"></param>
|
||||
/// <param name="yOffset"></param>
|
||||
/// <param name="radius"></param>
|
||||
/// <param name="spacing"></param>
|
||||
/// <param name="rotation"></param>
|
||||
/// <param name="scale"></param>
|
||||
private void DrawCircleStrips(Graphics g, Brush brush, double[] spectrumData, int stripCount, double xOffset, double yOffset, double radius, double spacing, double rotation, double scale)
|
||||
{
|
||||
double rotationAngle = Math.PI / 180 * rotation;
|
||||
double blockWidth = MathF.PI * 2 / stripCount; // angle
|
||||
double stripWidth = blockWidth - MathF.PI / 180 * spacing; // angle
|
||||
PointF[] points = new PointF[stripCount];
|
||||
|
||||
for (int i = 0; i < stripCount; i++)
|
||||
{
|
||||
double x = blockWidth * i + rotationAngle; // angle
|
||||
double y = spectrumData[i * spectrumData.Length / stripCount] * scale; // height
|
||||
points[i] = new PointF((float)x, (float)y);
|
||||
}
|
||||
|
||||
for (int i = 0; i < stripCount; i++)
|
||||
{
|
||||
PointF p = points[i];
|
||||
double sinStart = Math.Sin(p.X);
|
||||
double sinEnd = Math.Sin(p.X + stripWidth);
|
||||
double cosStart = Math.Cos(p.X);
|
||||
double cosEnd = Math.Cos(p.X + stripWidth);
|
||||
|
||||
PointF[] polygon = new PointF[]
|
||||
{
|
||||
new PointF((float)(cosStart * radius + xOffset), (float)(sinStart * radius + yOffset)),
|
||||
new PointF((float)(cosEnd * radius + xOffset), (float)(sinEnd * radius + yOffset)),
|
||||
new PointF((float)(cosEnd * (radius + p.Y) + xOffset), (float)(sinEnd * (radius + p.Y) + yOffset)),
|
||||
new PointF((float)(cosStart * (radius + p.Y) + xOffset), (float)(sinStart * (radius + p.Y) + yOffset)),
|
||||
};
|
||||
|
||||
g.FillPolygon(brush, polygon);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 画圆环渐变条
|
||||
/// </summary>
|
||||
/// <param name="g"></param>
|
||||
/// <param name="inner"></param>
|
||||
/// <param name="outer"></param>
|
||||
/// <param name="spectrumData"></param>
|
||||
/// <param name="stripCount"></param>
|
||||
/// <param name="xOffset"></param>
|
||||
/// <param name="yOffset"></param>
|
||||
/// <param name="radius"></param>
|
||||
/// <param name="spacing"></param>
|
||||
/// <param name="scale"></param>
|
||||
private void DrawCircleGradientStrips(Graphics g, Color inner, Color outer, double[] spectrumData, int stripCount, double xOffset, double yOffset, double radius, double spacing, double rotation, double scale)
|
||||
{
|
||||
double rotationAngle = Math.PI / 180 * rotation;
|
||||
double blockWidth = Math.PI * 2 / stripCount; // angle
|
||||
double stripWidth = blockWidth - MathF.PI / 180 * spacing; // angle
|
||||
PointF[] points = new PointF[stripCount];
|
||||
|
||||
for (int i = 0; i < stripCount; i++)
|
||||
{
|
||||
double x = blockWidth * i + rotationAngle; // angle
|
||||
double y = spectrumData[i * spectrumData.Length / stripCount] * scale; // height
|
||||
points[i] = new PointF((float)x, (float)y);
|
||||
}
|
||||
|
||||
double maxHeight = points.Max(v => v.Y);
|
||||
double outerRadius = radius + maxHeight;
|
||||
|
||||
PointF[] polygon = new PointF[4];
|
||||
for (int i = 0; i < stripCount; i++)
|
||||
{
|
||||
PointF p = points[i];
|
||||
double sinStart = Math.Sin(p.X);
|
||||
double sinEnd = Math.Sin(p.X + stripWidth);
|
||||
double cosStart = Math.Cos(p.X);
|
||||
double cosEnd = Math.Cos(p.X + stripWidth);
|
||||
|
||||
PointF
|
||||
p1 = new PointF((float)(cosStart * radius + xOffset),(float)(sinStart * radius + yOffset)),
|
||||
p2 = new PointF((float)(cosEnd * radius + xOffset),(float)(sinEnd * radius + yOffset)),
|
||||
p3 = new PointF((float)(cosEnd * (radius + p.Y) + xOffset), (float)(sinEnd * (radius + p.Y) + yOffset)),
|
||||
p4 = new PointF((float)(cosStart * (radius + p.Y) + xOffset), (float)(sinStart * (radius + p.Y) + yOffset));
|
||||
|
||||
polygon[0] = p1;
|
||||
polygon[1] = p2;
|
||||
polygon[2] = p3;
|
||||
polygon[3] = p4;
|
||||
|
||||
|
||||
PointF innerP = new PointF((p1.X + p2.X) / 2, (p1.Y + p2.Y) / 2);
|
||||
PointF outerP = new PointF((p3.X + p4.X) / 2, (p3.Y + p4.Y) / 2);
|
||||
|
||||
Vector2 offset = new Vector2(outerP.X - innerP.X, outerP.Y - innerP.Y);
|
||||
if (MathF.Sqrt(offset.X * offset.X + offset.Y * offset.Y) < 1) // 渐变笔刷两点之间距离不能太小
|
||||
continue;
|
||||
|
||||
try
|
||||
{
|
||||
using LinearGradientBrush brush = new LinearGradientBrush(innerP, outerP, inner, outer); // 这里有玄学 bug, 这个 线性笔刷会 OutMemoryException
|
||||
g.FillPolygon(brush, polygon); // 但是实际上不应该有这个异常...
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 画简单的线条
|
||||
/// </summary>
|
||||
/// <param name="g"></param>
|
||||
/// <param name="brush"></param>
|
||||
/// <param name="spectrumData"></param>
|
||||
/// <param name="stripCount"></param>
|
||||
/// <param name="drawingWidth"></param>
|
||||
/// <param name="xOffset"></param>
|
||||
/// <param name="yOffset"></param>
|
||||
/// <param name="spacing"></param>
|
||||
/// <param name="scale"></param>
|
||||
private void DrawStrips(Graphics g, Brush brush, double[] spectrumData, int stripCount, int drawingWidth, float xOffset, float yOffset, float spacing, double scale)
|
||||
{
|
||||
float stripWidth = (drawingWidth - spacing * stripCount) / stripCount;
|
||||
PointF[] points = new PointF[stripCount];
|
||||
|
||||
for (int i = 0; i < stripCount; i++)
|
||||
{
|
||||
double x = stripWidth * i + spacing * i + xOffset;
|
||||
double y = spectrumData[i * spectrumData.Length / stripCount] * scale; // height
|
||||
points[i] = new PointF((float)x, (float)y);
|
||||
}
|
||||
|
||||
for (int i = 0; i < stripCount; i++)
|
||||
{
|
||||
PointF p = points[i];
|
||||
float y = yOffset;
|
||||
float height = p.Y;
|
||||
|
||||
if (height < 0)
|
||||
{
|
||||
y += height;
|
||||
height = -height;
|
||||
}
|
||||
|
||||
g.FillRectangle(brush, new RectangleF(p.X, y, stripWidth, height));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 画渐变的边框
|
||||
/// </summary>
|
||||
/// <param name="g"></param>
|
||||
/// <param name="inner"></param>
|
||||
/// <param name="outer"></param>
|
||||
/// <param name="area"></param>
|
||||
/// <param name="scale"></param>
|
||||
/// <param name="width"></param>
|
||||
private void DrawGradientBorder(Graphics g, Color inner, Color outer, Rectangle area, double scale, float width)
|
||||
{
|
||||
int thickness = (int)(width * scale);
|
||||
if (thickness < 1)
|
||||
return;
|
||||
|
||||
Rectangle rect = new Rectangle(area.X, area.Y, area.Width, area.Height);
|
||||
|
||||
Rectangle up = new Rectangle(rect.Location, new Size(rect.Width, thickness));
|
||||
Rectangle down = new Rectangle(new Point(rect.X, (int)(rect.X + rect.Height - scale * width)), new Size(rect.Width, thickness));
|
||||
Rectangle left = new Rectangle(rect.Location, new Size(thickness, rect.Height));
|
||||
Rectangle right = new Rectangle(new Point((int)(rect.X + rect.Width - scale * width), rect.Y), new Size(thickness, rect.Height));
|
||||
|
||||
LinearGradientBrush upB = new LinearGradientBrush(up, outer, inner, LinearGradientMode.Vertical);
|
||||
LinearGradientBrush downB = new LinearGradientBrush(down, inner, outer, LinearGradientMode.Vertical);
|
||||
LinearGradientBrush leftB = new LinearGradientBrush(left, outer, inner, LinearGradientMode.Horizontal);
|
||||
LinearGradientBrush rightB = new LinearGradientBrush(right, inner, outer, LinearGradientMode.Horizontal);
|
||||
|
||||
upB.WrapMode = downB.WrapMode = leftB.WrapMode = rightB.WrapMode = WrapMode.TileFlipXY;
|
||||
|
||||
g.FillRectangle(upB, up);
|
||||
g.FillRectangle(downB, down);
|
||||
g.FillRectangle(leftB, left);
|
||||
g.FillRectangle(rightB, right);
|
||||
}
|
||||
|
||||
int colorIndex = 0;
|
||||
double rotation = 0;
|
||||
BufferedGraphics? oldBuffer;
|
||||
private void DrawingTimer_Tick(object? sender, EventArgs e)
|
||||
{
|
||||
if (spectrumData == null)
|
||||
return;
|
||||
|
||||
rotation += 0.1;
|
||||
colorIndex++;
|
||||
|
||||
Color color1 = allColors[colorIndex % allColors.Length];
|
||||
Color color2 = allColors[(colorIndex + 200) % allColors.Length];
|
||||
|
||||
double[] bassArea = Visualizer.TakeSpectrumOfFrequency(spectrumData, capture.WaveFormat.SampleRate, 250); // 低频区域
|
||||
double bassScale = bassArea.Average() * 100; // 低音导致的缩放 (比例数)
|
||||
double extraScale = Math.Min(drawingPanel.Width, drawingPanel.Height) / 6; // 低音导致的缩放 (乘上窗口大小)
|
||||
|
||||
Rectangle border = new Rectangle(Point.Empty, drawingPanel.Size);
|
||||
|
||||
BufferedGraphics buffer = BufferedGraphicsManager.Current.Allocate(drawingPanel.CreateGraphics(), drawingPanel.ClientRectangle);
|
||||
Graphics g = buffer.Graphics;
|
||||
|
||||
if (oldBuffer != null)
|
||||
{
|
||||
//oldBuffer.Render(buffer.Graphics); // 如果你想要实现 "留影" 效果, 就取消注释这段代码, 并且将 g.Clear 改为 g.FillRectange(xxx, 半透明的黑色)
|
||||
oldBuffer.Dispose();
|
||||
}
|
||||
|
||||
using Pen pen = new Pen(Color.Pink); // 画音频采样波形用的笔
|
||||
|
||||
g.SmoothingMode = SmoothingMode.HighQuality; // 嗨嗨害, 那必须得是高质量绘图
|
||||
g.Clear(drawingPanel.BackColor);
|
||||
|
||||
//DrawGradientBorder(g, Color.FromArgb(0, color1), color2, border, bassScale, drawingPanel.Width / 10);
|
||||
DrawGradientStrips(g, color1, color2, spectrumData, spectrumData.Length, drawingPanel.Width, 0, drawingPanel.Height, 3, -drawingPanel.Height * 50);
|
||||
//DrawCircleGradientStrips(g, color1, color2, spectrumData, spectrumData.Length, drawingPanel.Width / 2, drawingPanel.Height / 2, MathF.Min(drawingPanel.Width, drawingPanel.Height) / 4 + extraScale * bassScale, 1, rotation, drawingPanel.Width / 6 * 10);
|
||||
|
||||
//DrawCurve(g, pen, visualizer.SampleData, visualizer.SampleData.Length, drawingPanel.Width, 0, drawingPanel.Height / 2, MathF.Min(drawingPanel.Height / 10, 100));
|
||||
|
||||
buffer.Render();
|
||||
|
||||
oldBuffer = buffer; // 保存一下 buffer (之所以不全局只使用一个 Buffer 是因为,,, 用户可能调整窗口大小, 所以每一帧都必须适应)
|
||||
}
|
||||
|
||||
private void MainWindow_Load(object sender, EventArgs e)
|
||||
{
|
||||
capture.StartRecording();
|
||||
dataTimer.Start();
|
||||
drawingTimer.Start();
|
||||
}
|
||||
|
||||
private void MainWindow_FormClosed(object sender, FormClosedEventArgs e)
|
||||
{
|
||||
Environment.Exit(0);
|
||||
}
|
||||
|
||||
private void DrawingPanel_MouseDoubleClick(object sender, MouseEventArgs e)
|
||||
{
|
||||
WindowState = WindowState != FormWindowState.Maximized ? FormWindowState.Maximized : FormWindowState.Normal;
|
||||
FormBorderStyle = WindowState == FormWindowState.Maximized ? FormBorderStyle.None : FormBorderStyle.Sizable;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
Version 2.0
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
associated with the data types.
|
||||
|
||||
Example:
|
||||
|
||||
... ado.net/XML headers & schema ...
|
||||
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||
<resheader name="version">2.0</resheader>
|
||||
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
||||
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
|
||||
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
|
||||
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
|
||||
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
||||
<value>[base64 mime encoded serialized .NET Framework object]</value>
|
||||
</data>
|
||||
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||
<comment>This is a comment</comment>
|
||||
</data>
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
name/value pairs.
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
mimetype set.
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
extensible. For a given mimetype the value must be set accordingly:
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
read any of the formats listed below.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.binary.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.soap.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||
value : The object must be serialized into a byte array
|
||||
: using a System.ComponentModel.TypeConverter
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="metadata">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||
<xsd:attribute name="type" type="xsd:string" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="assembly">
|
||||
<xsd:complexType>
|
||||
<xsd:attribute name="alias" type="xsd:string" />
|
||||
<xsd:attribute name="name" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:schema>
|
||||
<resheader name="resmimetype">
|
||||
<value>text/microsoft-resx</value>
|
||||
</resheader>
|
||||
<resheader name="version">
|
||||
<value>2.0</value>
|
||||
</resheader>
|
||||
<resheader name="reader">
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<metadata name="dataTimer.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
|
||||
<value>17, 17</value>
|
||||
</metadata>
|
||||
<metadata name="drawingTimer.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
|
||||
<value>197, 17</value>
|
||||
</metadata>
|
||||
</root>
|
||||
@@ -0,0 +1,106 @@
|
||||
namespace LibDynamics
|
||||
{
|
||||
public class SecondOrderDynamics
|
||||
{
|
||||
private double xp;// previous input
|
||||
private double y, yd; // state variables
|
||||
private double _w, _z, _d, k1, k2, k3; // dynamics constants
|
||||
private double _r;
|
||||
private double _f;
|
||||
|
||||
/// <summary>
|
||||
/// 频率
|
||||
/// - 即速度, 单位是赫兹(Hz)
|
||||
/// - 不会影响输出结果的形状, 会影响 '震荡频率'
|
||||
/// </summary>
|
||||
public double F
|
||||
{
|
||||
get => _f; set
|
||||
{
|
||||
_f = value;
|
||||
InitMotionValues(_f, _z, _r);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 阻尼 <br />
|
||||
/// - 当为 0 时, 输出将永远震荡不衰减 <br />
|
||||
/// - 当大于 0 小于 1 时, 输出会超出结果, 并逐渐趋于目标 <br />
|
||||
/// - 当为 1 时, 输出的曲线是趋向结果, 并正好在指定频率对应时间内抵达结果 <br />
|
||||
/// - 当大于 1 时, 输出值同样时取向结果, 但速度会更慢, 无法在指定频率对应时间内抵达结果 <br />
|
||||
/// </summary>
|
||||
public double Z
|
||||
{
|
||||
get => _z; set
|
||||
{
|
||||
_z = value;
|
||||
InitMotionValues(_f, _z, _r);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 初始响应
|
||||
/// - 当为 0 时, 数据需要进行 '加速' 来开始运动 <br />
|
||||
/// - 当为 1 时, 数据会立即开始响应 <br />
|
||||
/// - 当大于 1 时, 输出会因为 '速度过快' 而超出目标结果 <br />
|
||||
/// - 当小于 0 时, 输出会 '预测运动', 即 '抬手动作'. 例如目标是 '加' 时, 输出会先进行 '减', 再进行 '加',
|
||||
/// - 当运动目标为机械时, 通常取值为 2
|
||||
/// </summary>
|
||||
public double R
|
||||
{
|
||||
get => _r; set
|
||||
{
|
||||
_r = value;
|
||||
InitMotionValues(_f, _z, _r);
|
||||
}
|
||||
}
|
||||
|
||||
public SecondOrderDynamics(double f, double z, double r, double x0)
|
||||
{
|
||||
//compute constants
|
||||
InitMotionValues(f, z, r);
|
||||
|
||||
// initialize variables
|
||||
xp = x0;
|
||||
y = x0;
|
||||
yd = 0;
|
||||
}
|
||||
|
||||
private void InitMotionValues(double f, double z, double r)
|
||||
{
|
||||
_w = 2 * Math.PI * f;
|
||||
_z = z;
|
||||
_d = _w * Math.Sqrt(Math.Abs(z * z - 1));
|
||||
k1 = z / (Math.PI * f);
|
||||
k2 = 1 / ((2 * Math.PI * f) * (2 * Math.PI * f));
|
||||
k3 = r * z / (2 * Math.PI * f);
|
||||
}
|
||||
|
||||
public double Update(double deltaTime, double x)
|
||||
{
|
||||
double xd = (x - xp) / deltaTime;
|
||||
double k1_stable, k2_stable;
|
||||
|
||||
if (_w * deltaTime < _z)
|
||||
{
|
||||
k1_stable = k1;
|
||||
k2_stable = Math.Max(Math.Max(k2, deltaTime * deltaTime / 2 + deltaTime * k1 / 2), deltaTime * k1);
|
||||
}
|
||||
else
|
||||
{
|
||||
double t1 = Math.Exp(-_z * _w * deltaTime);
|
||||
double alpha = 2 * t1 * (_z <= 1 ? Math.Cos(deltaTime * _d) : Math.Cosh(deltaTime * _d));
|
||||
double beta = t1 * t1;
|
||||
double t2 = deltaTime / (1 + beta - alpha);
|
||||
k1_stable = (1 - beta) * t2;
|
||||
k2_stable = deltaTime * t2;
|
||||
}
|
||||
|
||||
y = y + deltaTime * yd;
|
||||
yd = yd + deltaTime * (x + k3 * xd - y - k1_stable * yd) / k2_stable;
|
||||
|
||||
xp = x;
|
||||
return y;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
namespace LibDynamics
|
||||
{
|
||||
public class SecondOrderDynamicsForArray
|
||||
{
|
||||
private double[] xps, xds;// previous input
|
||||
private double[] ys, yds; // state variables
|
||||
private double _w, _z, _d, k1, k2, k3; // dynamics constants
|
||||
private double _r;
|
||||
private double _f;
|
||||
|
||||
/// <summary>
|
||||
/// 频率
|
||||
/// - 即速度, 单位是赫兹(Hz)
|
||||
/// - 不会影响输出结果的形状, 会影响 '震荡频率'
|
||||
/// </summary>
|
||||
public double F
|
||||
{
|
||||
get => _f; set
|
||||
{
|
||||
_f = value;
|
||||
InitMotionValues(_f, _z, _r);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 阻尼 <br />
|
||||
/// - 当为 0 时, 输出将永远震荡不衰减 <br />
|
||||
/// - 当大于 0 小于 1 时, 输出会超出结果, 并逐渐趋于目标 <br />
|
||||
/// - 当为 1 时, 输出的曲线是趋向结果, 并正好在指定频率对应时间内抵达结果 <br />
|
||||
/// - 当大于 1 时, 输出值同样时取向结果, 但速度会更慢, 无法在指定频率对应时间内抵达结果 <br />
|
||||
/// </summary>
|
||||
public double Z
|
||||
{
|
||||
get => _z; set
|
||||
{
|
||||
_z = value;
|
||||
InitMotionValues(_f, _z, _r);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 初始响应
|
||||
/// - 当为 0 时, 数据需要进行 '加速' 来开始运动 <br />
|
||||
/// - 当为 1 时, 数据会立即开始响应 <br />
|
||||
/// - 当大于 1 时, 输出会因为 '速度过快' 而超出目标结果 <br />
|
||||
/// - 当小于 0 时, 输出会 '预测运动', 即 '抬手动作'. 例如目标是 '加' 时, 输出会先进行 '减', 再进行 '加',
|
||||
/// - 当运动目标为机械时, 通常取值为 2
|
||||
/// </summary>
|
||||
public double R
|
||||
{
|
||||
get => _r; set
|
||||
{
|
||||
_r = value;
|
||||
InitMotionValues(_f, _z, _r);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="f"></param>
|
||||
/// <param name="z"></param>
|
||||
/// <param name="r"></param>
|
||||
/// <param name="x0"></param>
|
||||
/// <param name="size">Array size</param>
|
||||
public SecondOrderDynamicsForArray(double f, double z, double r, double x0, int size)
|
||||
{
|
||||
//compute constants
|
||||
InitMotionValues(f, z, r);
|
||||
|
||||
// initialize variables
|
||||
xps = new double[size];
|
||||
ys = new double[size];
|
||||
|
||||
xds = new double[size];
|
||||
yds = new double[size];
|
||||
|
||||
Array.Fill(xps, x0);
|
||||
Array.Fill(ys, x0);
|
||||
}
|
||||
|
||||
private void InitMotionValues(double f, double z, double r)
|
||||
{
|
||||
_w = 2 * Math.PI * f;
|
||||
_z = z;
|
||||
_d = _w * Math.Sqrt(Math.Abs(z * z - 1));
|
||||
k1 = z / (Math.PI * f);
|
||||
k2 = 1 / ((2 * Math.PI * f) * (2 * Math.PI * f));
|
||||
k3 = r * z / (2 * Math.PI * f);
|
||||
}
|
||||
|
||||
public double[] Update(double deltaTime, double[] xs)//xps p for past,xds d for delta,xs current val
|
||||
{
|
||||
if (xs.Length != xps.Length)
|
||||
throw new ArgumentException();
|
||||
|
||||
for (int i = 0; i < xds.Length; i++)
|
||||
xds[i] = (xs[i] - xps[i]) / deltaTime;
|
||||
double k1_stable, k2_stable;
|
||||
if (_w * deltaTime < _z)
|
||||
{
|
||||
k1_stable = k1;
|
||||
k2_stable = Math.Max(Math.Max(k2, deltaTime * deltaTime / 2 + deltaTime * k1 / 2), deltaTime * k1);
|
||||
}
|
||||
else
|
||||
{
|
||||
double t1 = Math.Exp(-_z * _w * deltaTime);
|
||||
double alpha = 2 * t1 * (_z <= 1 ? Math.Cos(deltaTime * _d) : Math.Cosh(deltaTime * _d));
|
||||
double beta = t1 * t1;
|
||||
double t2 = deltaTime / (1 + beta - alpha);
|
||||
k1_stable = (1 - beta) * t2;
|
||||
k2_stable = deltaTime * t2;
|
||||
}
|
||||
|
||||
for (int i = 0; i < ys.Length; i++)
|
||||
{
|
||||
ys[i] = ys[i] + deltaTime * yds[i];
|
||||
yds[i] = yds[i] + deltaTime * (xs[i] + k3 * xds[i] - ys[i] - k1_stable * yds[i]) / k2_stable;
|
||||
}
|
||||
|
||||
for (int i = 0; i < xps.Length; i++)
|
||||
xps[i] = xs[i];
|
||||
return ys;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,227 @@
|
||||
using LibDynamics;
|
||||
using FftComplex = FftSharp.Complex;
|
||||
using FftTransform = FftSharp.Transform;
|
||||
|
||||
namespace LibAudioVisualizer
|
||||
{
|
||||
public class Visualizer
|
||||
{
|
||||
//private int _m;
|
||||
private double[] _sampleData;
|
||||
private DateTime _lastTime;
|
||||
private SecondOrderDynamicsForArray _dynamics;
|
||||
private int _size;
|
||||
|
||||
/// <summary>
|
||||
/// 采样数据
|
||||
/// </summary>
|
||||
public double[] SampleData => _sampleData;
|
||||
|
||||
/// <summary>
|
||||
/// 尺寸
|
||||
/// </summary>
|
||||
public int Size
|
||||
{
|
||||
get => _size; set
|
||||
{
|
||||
if (!(Get2Flag(value)))
|
||||
throw new ArgumentException("长度必须是 2 的 n 次幂");
|
||||
|
||||
_size = value;
|
||||
_sampleData = new double[value];
|
||||
_dynamics = new SecondOrderDynamicsForArray(1, 1, 1, 0, value / 2);
|
||||
}
|
||||
}
|
||||
|
||||
public int OutputSize => Size / 2;
|
||||
|
||||
public Visualizer(int size)
|
||||
{
|
||||
if (!(Get2Flag(size)))
|
||||
throw new ArgumentException("大小必须是 2 的 n 次幂", nameof(size));
|
||||
|
||||
_lastTime = DateTime.Now;
|
||||
_sampleData = new double[size];
|
||||
_dynamics = new SecondOrderDynamicsForArray(1, 1, 1, 0, size / 2);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 判断是否是 2 的整数次幂
|
||||
/// </summary>
|
||||
/// <param name="num"></param>
|
||||
/// <returns></returns>
|
||||
private bool Get2Flag(int num)
|
||||
{
|
||||
if (num < 1)
|
||||
return false;
|
||||
return (num & num - 1) == 0;
|
||||
}
|
||||
|
||||
public void PushSampleData(double[] waveData)
|
||||
{
|
||||
if (waveData.Length > _sampleData.Length)
|
||||
{
|
||||
Array.Copy(waveData, waveData.Length - _sampleData.Length, _sampleData, 0, _sampleData.Length);
|
||||
}
|
||||
else
|
||||
{
|
||||
Array.Copy(_sampleData, waveData.Length, _sampleData, 0, _sampleData.Length - waveData.Length);
|
||||
Array.Copy(waveData, 0, _sampleData, _sampleData.Length - waveData.Length, waveData.Length);
|
||||
}
|
||||
}
|
||||
|
||||
public void PushSampleData(double[] waveData, int count)
|
||||
{
|
||||
if (count > _sampleData.Length)
|
||||
{
|
||||
Array.Copy(waveData, count - _sampleData.Length, _sampleData, 0, _sampleData.Length);
|
||||
}
|
||||
else
|
||||
{
|
||||
Array.Copy(_sampleData, count, _sampleData, 0, _sampleData.Length - count);
|
||||
Array.Copy(waveData, 0, _sampleData, _sampleData.Length - count, count);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取频谱数据 (数据已经删去共轭部分)
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public double[] GetSpectrumData()
|
||||
{
|
||||
DateTime now = DateTime.Now;
|
||||
double deltaTime = (now - _lastTime).TotalSeconds;
|
||||
_lastTime = now;
|
||||
|
||||
int len = _sampleData.Length;
|
||||
FftComplex[] data = new FftComplex[len];
|
||||
|
||||
for (int i = 0; i < len; i++)
|
||||
data[i] = new FftComplex(_sampleData[i], 0);
|
||||
|
||||
FftTransform.FFT(data);
|
||||
|
||||
int halfLen = len / 2;
|
||||
double[] spectrum = new double[halfLen]; // 傅里叶变换结果左右对称, 只需要取一半
|
||||
for (int i = 0; i < halfLen; i++)
|
||||
spectrum[i] = data[i].Magnitude / len;
|
||||
|
||||
var window = new FftSharp.Windows.Bartlett();
|
||||
window.Create(halfLen);
|
||||
window.ApplyInPlace(spectrum, false);
|
||||
|
||||
//return spectrum;
|
||||
return _dynamics.Update(deltaTime, spectrum);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 取指定频率内的频谱数据
|
||||
/// </summary>
|
||||
/// <param name="spectrum">源频谱数据</param>
|
||||
/// <param name="sampleRate">采样率</param>
|
||||
/// <param name="frequency">目标频率</param>
|
||||
/// <returns></returns>
|
||||
public static double[] TakeSpectrumOfFrequency(double[] spectrum, double sampleRate, double frequency)
|
||||
{
|
||||
double frequencyPerSampe = sampleRate / spectrum.Length;
|
||||
|
||||
int lengthInNeed = (int)(Math.Min(frequency / frequencyPerSampe, spectrum.Length));
|
||||
double[] result = new double[lengthInNeed];
|
||||
Array.Copy(spectrum, 0, result, 0, lengthInNeed);
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 简单的数据模糊
|
||||
/// </summary>
|
||||
/// <param name="data">数据</param>
|
||||
/// <param name="radius">模糊半径</param>
|
||||
/// <returns>结果</returns>
|
||||
public static double[] GetBlurry(double[] data, int radius)
|
||||
{
|
||||
double[] GetWeights(int radius)
|
||||
{
|
||||
double Gaussian(double x) => Math.Pow(Math.E, (-4 * x * x)); // 憨批高斯函数
|
||||
|
||||
int len = 1 + radius * 2; // 长度
|
||||
int end = len - 1; // 最后的索引
|
||||
double radiusF = (double)radius; // 半径浮点数
|
||||
double[] weights = new double[len]; // 权重
|
||||
|
||||
for (int i = 0; i <= radius; i++) // 先把右边的权重算出来
|
||||
weights[radius + i] = Gaussian(i / radiusF);
|
||||
for (int i = 0; i < radius; i++) // 把右边的权重拷贝到左边
|
||||
weights[i] = weights[end - i];
|
||||
|
||||
double total = weights.Sum();
|
||||
for (int i = 0; i < len; i++) // 使权重合为 0
|
||||
weights[i] = weights[i] / total;
|
||||
|
||||
return weights;
|
||||
}
|
||||
|
||||
void ApplyWeights(double[] buffer, double[] weights)
|
||||
{
|
||||
int len = buffer.Length;
|
||||
for (int i = 0; i < len; i++)
|
||||
buffer[i] = buffer[i] * weights[i];
|
||||
}
|
||||
|
||||
|
||||
double[] weights = GetWeights(radius);
|
||||
double[] buffer = new double[1 + radius * 2];
|
||||
|
||||
double[] result = new double[data.Length];
|
||||
if (data.Length < radius)
|
||||
{
|
||||
Array.Fill(result, data.Average());
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
for (int i = 0; i < radius; i++)
|
||||
{
|
||||
Array.Fill(buffer, data[i], 0, radius + 1); // 填充缺省
|
||||
for (int j = 0; j < radius; j++) //
|
||||
{
|
||||
buffer[radius + 1 + j] = data[i + j];
|
||||
}
|
||||
|
||||
ApplyWeights(buffer, weights);
|
||||
result[i] = buffer.Sum();
|
||||
}
|
||||
|
||||
for (int i = radius; i < data.Length - radius; i++)
|
||||
{
|
||||
for (int j = 0; j < radius; j++) //
|
||||
{
|
||||
buffer[j] = data[i - j];
|
||||
}
|
||||
|
||||
buffer[radius] = data[i];
|
||||
|
||||
for (int j = 0; j < radius; j++) //
|
||||
{
|
||||
buffer[radius + j + 1] = data[i + j];
|
||||
}
|
||||
|
||||
ApplyWeights(buffer, weights);
|
||||
result[i] = buffer.Sum();
|
||||
}
|
||||
|
||||
for (int i = data.Length - radius; i < data.Length; i++)
|
||||
{
|
||||
Array.Fill(buffer, data[i], 0, radius + 1); // 填充缺省
|
||||
for (int j = 0; j < radius; j++) //
|
||||
{
|
||||
buffer[radius + 1 + j] = data[i - j];
|
||||
}
|
||||
|
||||
ApplyWeights(buffer, weights);
|
||||
result[i] = buffer.Sum();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user