using LibAudioVisualizer; using NAudio.CoreAudioApi; using NAudio.Dsp; using NAudio.Wave; using SharpDX; using SharpDX.Direct2D1; using SharpDX.Mathematics.Interop; using System; using System.Linq; using System.Numerics; using System.Windows.Forms; namespace AudioVisualizerDx { public partial class MainWindow : Form { WasapiCapture capture; // 音频捕获 Visualizer visualizer; // 可视化 double[]? spectrumData; // 频谱数据 RawColor4[] allColors; // 渐变颜色 Factory fac; RenderTarget rt; public MainWindow() { MMDeviceEnumerator enumerator = new MMDeviceEnumerator(); capture = new WasapiLoopbackCapture(); // 捕获电脑发出的声音 visualizer = new Visualizer(256); // 新建一个可视化器, 并使用 256 个采样进行傅里叶变换 allColors = GetAllHsvColors(); // 获取所有的渐变颜色 (HSV 颜色) capture.WaveFormat = WaveFormat.CreateIeeeFloatWaveFormat(8192, 2); // 指定捕获的格式, 单声道, 32位深度, IeeeFloat 编码, 8192采样率 capture.DataAvailable += Capture_DataAvailable; // 订阅事件 InitializeComponent(); fac = new Factory(); rt = new WindowRenderTarget(fac, new RenderTargetProperties( new PixelFormat(SharpDX.DXGI.Format.R8G8B8A8_UNorm, AlphaMode.Ignore)), new HwndRenderTargetProperties() { Hwnd = drawingPanel.Handle, PixelSize = new Size2(drawingPanel.Width, drawingPanel.Height), PresentOptions = PresentOptions.None, }); } /// /// 获取 HSV 中所有的基础颜色 (饱和度和明度均为最大值) /// /// 所有的 HSV 基础颜色(共 256 * 6 个, 并且随着索引增加, 颜色也会渐变) private RawColor4[] GetAllHsvColors() { RawColor4[] result = new RawColor4[256 * 6]; for (int i = 0; i < 256; i++) { result[i] = new RawColor4(1, i / 255f, 0, 1); } for (int i = 0; i < 256; i++) { result[256 + i] = new RawColor4((255 - i) / 255f, 1, 0, 1); } for (int i = 0; i < 256; i++) { result[512 + i] = new RawColor4(0, 1, i / 255f, 1); } for (int i = 0; i < 256; i++) { result[768 + i] = new RawColor4(0, (255 - i) / 255f, 1, 1); } for (int i = 0; i < 256; i++) { result[1024 + i] = new RawColor4(i / 255f, 0, 1, 1); } for (int i = 0; i < 256; i++) { result[1280 + i] = new RawColor4(1, 0, (255 - i) / 255f, 1); } return result; } /// /// /// /// /// 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); // 将新的采样存储到 可视化器 中 } /// /// 用来刷新频谱数据以及实现频谱数据缓动 /// /// /// private void DataTimer_Tick(object? sender, EventArgs e) { double[] newSpectrumData = visualizer.GetSpectrumData(""); // 从可视化器中获取频谱数据 newSpectrumData = Visualizer.MakeSmooth(newSpectrumData, 2); // 平滑频谱数据 if (spectrumData == null) // 如果已经存储的频谱数据为空, 则把新的频谱数据直接赋值上去 { spectrumData = newSpectrumData; return; } for (int i = 0; i < newSpectrumData.Length; i++) // 计算旧频谱数据和新频谱数据之间的 "中间值" { double oldData = spectrumData[i]; double newData = newSpectrumData[i]; double lerpData = oldData + (newData - oldData) * .2f; // 每一次执行, 频谱值会向目标值移动 20% (如果太大, 缓动效果不明显, 如果太小, 频谱会有延迟的感觉) spectrumData[i] = lerpData; } } /// /// 绘制一个渐变的 波浪 /// /// 绘图目标 /// 下方颜色 /// 上方颜色 /// 频谱数据 /// 波浪中, 点的数量 /// 波浪的宽度 /// 波浪的起始X坐标 /// 波浪的其实Y坐标 /// 频谱的缩放(使用负值可以翻转波浪) private void DrawGradient(RenderTarget g, RawColor4 down, RawColor4 up, double[] spectrumData, int pointCount, int drawingWidth, float xOffset, float yOffset, double scale) { RawVector2[] points = new RawVector2[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 RawVector2((float)x, (float)y); } points[0] = new RawVector2(xOffset, yOffset); points[points.Length - 1] = new RawVector2(xOffset + drawingWidth, yOffset); using PathGeometry geo = new PathGeometry(fac); using GeometrySink sink = geo.Open(); sink.BeginFigure(points[0], FigureBegin.Filled); for (int i = 1; i < points.Length; i++) sink.AddLine(points[i]); sink.EndFigure(FigureEnd.Closed); float upP = (float)points.Min(v => v.Y); if (Math.Abs(upP - yOffset) < 1) return; LinearGradientBrushProperties linearGradientBrushProperties = new LinearGradientBrushProperties() { StartPoint = new RawVector2(0, yOffset), EndPoint = new RawVector2(0, upP) }; using GradientStopCollection gradientStopCollection = new GradientStopCollection(rt, new GradientStop[] { new GradientStop() { Position = 0, Color = down }, new GradientStop() { Position = 1, Color = up } }); using LinearGradientBrush brush = new LinearGradientBrush(rt, linearGradientBrushProperties, gradientStopCollection); g.FillGeometry(geo, brush); } /// /// 绘制渐变的条形 /// /// 绘图目标 /// 下方颜色 /// 上方颜色 /// 频谱数据 /// 条形的数量 /// 绘图的宽度 /// 绘图的起始 X 坐标 /// 绘图的起始 Y 坐标 /// 条形与条形之间的间隔(像素) /// private void DrawGradientStrips(RenderTarget g, RawColor4 down, RawColor4 up, double[] spectrumData, int stripCount, int drawingWidth, float xOffset, float yOffset, float spacing, double scale) { float stripWidth = (drawingWidth - spacing * stripCount) / stripCount; RawVector2[] points = new RawVector2[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 RawVector2((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; LinearGradientBrushProperties linearGradientBrushProperties = new LinearGradientBrushProperties() { StartPoint = new RawVector2(0, downP), EndPoint = new RawVector2(0, upP) }; using GradientStopCollection gradientStopCollection = new GradientStopCollection(rt, new GradientStop[] { new GradientStop() { Position = 0, Color = down }, new GradientStop() { Position = 1, Color = up } }); using Brush brush = new LinearGradientBrush(rt, linearGradientBrushProperties, gradientStopCollection); for (int i = 0; i < stripCount; i++) { RawVector2 p = points[i]; float y = yOffset; float height = p.Y; if (height < 0) { y += height; height = -height; } g.FillRectangle(new RawRectangleF(p.X, y, p.X + stripWidth, y + height), brush); } } /// /// 画曲线 /// /// /// /// /// /// /// /// /// private void DrawCurve(RenderTarget g, Brush brush, double[] spectrumData, int pointCount, int drawingWidth, double xOffset, double yOffset, double scale) { RawVector2[] points = new RawVector2[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 RawVector2((float)x, (float)y); } using PathGeometry geo = new PathGeometry(fac); using GeometrySink sink = geo.Open(); sink.BeginFigure(points[0], FigureBegin.Filled); for (int i = 1; i < pointCount; i++) sink.AddLine(points[i]); sink.EndFigure(FigureEnd.Open); sink.Close(); g.DrawGeometry(geo, brush); } private void DrawCircleStrips(RenderTarget 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 RawVector2[] points = new RawVector2[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 RawVector2((float)x, (float)y); } for (int i = 0; i < stripCount; i++) { RawVector2 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); RawVector2 p0 = new RawVector2((float)(cosStart * radius + xOffset), (float)(sinStart * radius + yOffset)); RawVector2 p1 = new RawVector2((float)(cosEnd * radius + xOffset), (float)(sinEnd * radius + yOffset)); RawVector2 p2 = new RawVector2((float)(cosEnd * (radius + p.Y) + xOffset), (float)(sinEnd * (radius + p.Y) + yOffset)); RawVector2 p3 = new RawVector2((float)(cosStart * (radius + p.Y) + xOffset), (float)(sinStart * (radius + p.Y) + yOffset)); using PathGeometry geo = new PathGeometry(fac); using GeometrySink sink = geo.Open(); sink.BeginFigure(p0, FigureBegin.Filled); sink.AddLine(p1); sink.AddLine(p2); sink.AddLine(p3); sink.EndFigure(FigureEnd.Closed); sink.Close(); g.FillGeometry(geo, brush); } } /// /// 画圆环条 /// /// /// /// /// /// /// /// /// /// /// private void DrawCircleGradientStrips(RenderTarget g, RawColor4 inner, RawColor4 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 RawVector2[] points = new RawVector2[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 RawVector2((float)x, (float)y); } double maxHeight = points.Max(v => v.Y); double outerRadius = radius + maxHeight; using GradientStopCollection gradientStopCollection = new GradientStopCollection(rt, new GradientStop[] { new GradientStop() { Position = 0, Color = inner }, new GradientStop() { Position = 1, Color = outer } }); RawVector2[] polygon = new RawVector2[4]; for (int i = 0; i < stripCount; i++) { RawVector2 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); RawVector2 p0 = new RawVector2((float)(cosStart * radius + xOffset), (float)(sinStart * radius + yOffset)), p1 = new RawVector2((float)(cosEnd * radius + xOffset), (float)(sinEnd * radius + yOffset)), p2 = new RawVector2((float)(cosEnd * (radius + p.Y) + xOffset), (float)(sinEnd * (radius + p.Y) + yOffset)), p3 = new RawVector2((float)(cosStart * (radius + p.Y) + xOffset), (float)(sinStart * (radius + p.Y) + yOffset)); polygon[0] = p0; polygon[1] = p1; polygon[2] = p2; polygon[3] = p3; RawVector2 innerP = new RawVector2((p0.X + p1.X) / 2, (p0.Y + p1.Y) / 2); RawVector2 outerP = new RawVector2((p2.X + p3.X) / 2, (p2.Y + p3.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) < 3) continue; using PathGeometry geo = new PathGeometry(fac); using GeometrySink sink = geo.Open(); sink.BeginFigure(p0, FigureBegin.Filled); sink.AddLine(p1); sink.AddLine(p2); sink.AddLine(p3); sink.EndFigure(FigureEnd.Closed); sink.Close(); LinearGradientBrushProperties linearGradientBrushProperties = new LinearGradientBrushProperties() { StartPoint = innerP, EndPoint = outerP }; using LinearGradientBrush brush = new LinearGradientBrush(rt, linearGradientBrushProperties, gradientStopCollection); g.FillGeometry(geo, brush); brush.Dispose(); } } private void DrawStrips(RenderTarget g, Brush brush, double[] spectrumData, int stripCount, int drawingWidth, float xOffset, float yOffset, float spacing, double scale) { float stripWidth = (drawingWidth - spacing * stripCount) / stripCount; RawVector2[] points = new RawVector2[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 RawVector2((float)x, (float)y); } for (int i = 0; i < stripCount; i++) { RawVector2 p = points[i]; float y = yOffset; float height = p.Y; if (height < 0) { y += height; height = -height; } g.FillRectangle(new RawRectangleF(p.X, y, p.X + stripWidth, y + height), brush); } } private void DrawGradientBorder(RenderTarget g, RawColor4 inner, RawColor4 outer, RawRectangleF area, double scale, float width) { int thickness = (int)(width * scale); RawRectangleF rect = new RawRectangleF(area.Left, area.Top, area.Right, area.Bottom); RawRectangleF up = new RawRectangleF(rect.Left, rect.Top, rect.Right, rect.Top + thickness); RawRectangleF down = new RawRectangleF(rect.Left, rect.Bottom - thickness, rect.Right, rect.Bottom); RawRectangleF left = new RawRectangleF(rect.Left, rect.Top, rect.Left + thickness, rect.Bottom); RawRectangleF right = new RawRectangleF(rect.Right - thickness, rect.Top, rect.Right, rect.Bottom); using GradientStopCollection gradientStopCollection = new GradientStopCollection(rt, new GradientStop[] { new GradientStop() { Position = 0, Color = inner }, new GradientStop() { Position = 1, Color = outer } }); using LinearGradientBrush upB = new LinearGradientBrush(rt, new LinearGradientBrushProperties() { StartPoint = new RawVector2(up.Left, up.Bottom), EndPoint = new RawVector2(up.Left, up.Top) }, gradientStopCollection); using LinearGradientBrush downB = new LinearGradientBrush(rt, new LinearGradientBrushProperties() { StartPoint = new RawVector2(down.Left, down.Top), EndPoint = new RawVector2(down.Left, down.Bottom) }, gradientStopCollection); using LinearGradientBrush leftB = new LinearGradientBrush(rt, new LinearGradientBrushProperties() { StartPoint = new RawVector2(left.Right, left.Top), EndPoint = new RawVector2(left.Left, left.Top) }, gradientStopCollection); using LinearGradientBrush rightB = new LinearGradientBrush(rt, new LinearGradientBrushProperties() { StartPoint = new RawVector2(right.Left, right.Top), EndPoint = new RawVector2(right.Right, right.Top) }, gradientStopCollection); g.FillRectangle(up, upB); g.FillRectangle(down, downB); g.FillRectangle(left, leftB); g.FillRectangle(right, rightB); } int colorIndex = 0; double rotation = 0; private void DrawingTimer_Tick(object? sender, EventArgs e) { if (spectrumData == null) return; rotation += .1; colorIndex++; RawColor4 color1 = allColors[colorIndex % allColors.Length]; RawColor4 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; RawRectangleF border = new RawRectangleF(0, 0, drawingPanel.Width, drawingPanel.Height); using SolidColorBrush sampleBrush = new SolidColorBrush(rt, new RawColor4(238 / 255f, 130 / 255f, 238 / 255f, 1)); rt.BeginDraw(); rt.Clear(new RawColor4(0, 0, 0, 1)); //rt.FillRectangle(border, new SolidColorBrush(rt, new RawColor4(0, 0, 0, 0.1f))); DrawGradientBorder(rt, new RawColor4(color1.R, color1.G, color1.B, 0), color2, border, bassScale, drawingPanel.Width / 10); DrawGradientStrips(rt, color1, color2, spectrumData, spectrumData.Length, drawingPanel.Width, 0, drawingPanel.Height, 3, -drawingPanel.Height * 10); DrawCircleGradientStrips(rt, 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(rt, sampleBrush, visualizer.SampleData, visualizer.SampleData.Length, drawingPanel.Width, 0, drawingPanel.Height / 2, MathF.Min(drawingPanel.Height / 10, 100)); rt.EndDraw(); } 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); } } }