Newer
Older
Correlator / Correlator / Views / AudioFileView.xaml.cs
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Shapes;
using System.Windows.Threading;
using Correlator.DataService;
using Correlator.Util;
using NAudio.CoreAudioApi;
using NAudio.Wave;

namespace Correlator.Views
{
    public partial class AudioFileView : UserControl
    {
        private readonly AudioVisualizer _visualizer; // 可视化
        private readonly WasapiCapture _capture; // 音频捕获
        private double[] _spectrumData; // 频谱数据
        private int _colorIndex;
        private readonly Color[] _allColors;

        private readonly DispatcherTimer _dataTimer = new DispatcherTimer
        {
            Interval = new TimeSpan(0, 0, 0, 0, 30)
        };

        private readonly DispatcherTimer _drawingTimer = new DispatcherTimer
        {
            Interval = new TimeSpan(0, 0, 0, 0, 30)
        };

        public AudioFileView(IApplicationDataService dataService)
        {
            InitializeComponent();

            _capture = new WasapiLoopbackCapture(); // 捕获电脑发出的声音
            _visualizer = dataService.GetAudioVisualizer(512);
            _allColors = dataService.GetAllHsvColors(); // 获取所有的渐变颜色 (HSV 颜色)

            _capture.WaveFormat = WaveFormat.CreateIeeeFloatWaveFormat(7500, 1);
            _capture.DataAvailable += delegate(object o, WaveInEventArgs args)
            {
                var length = args.BytesRecorded / 4; // 采样的数量 (每一个采样是 4 字节)
                var result = new double[length];

                for (var i = 0; i < length; i++)
                {
                    result[i] = BitConverter.ToSingle(args.Buffer, i * 4); // 取出采样值
                }

                _visualizer.PushSampleData(result); // 将新的采样存储到 可视化器 中
            };

            _dataTimer.Tick += DataTimer_Tick;
            _drawingTimer.Tick += DrawingTimer_Tick;
        }

        private void AudioFileView_OnLoaded(object sender, RoutedEventArgs e)
        {
            _capture.StartRecording();
            _dataTimer.Start();
            _drawingTimer.Start();
        }

        private void AudioFileView_OnUnloaded(object sender, RoutedEventArgs e)
        {
            _drawingTimer.Stop();
            _dataTimer.Stop();
            _capture.StopRecording();
        }

        /// <summary>
        /// 刷新频谱数据以及实现频谱数据缓动
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void DataTimer_Tick(object sender, EventArgs e)
        {
            var newSpectrumData = _visualizer.GetSpectrumData(); // 从可视化器中获取频谱数据
            newSpectrumData = AudioDataHub.Get.MakeSmooth(newSpectrumData, 2); // 平滑频谱数据

            if (_spectrumData == null) // 如果已经存储的频谱数据为空, 则把新的频谱数据直接赋值上去
            {
                _spectrumData = newSpectrumData;
                return;
            }

            for (var i = 0; i < newSpectrumData.Length; i++) // 计算旧频谱数据和新频谱数据之间的 "中间值"
            {
                var oldData = _spectrumData[i];
                var newData = newSpectrumData[i];
                // 每一次执行, 频谱值会向目标值移动 20% (如果太大, 缓动效果不明显, 如果太小, 频谱会有延迟的感觉)
                var lerpData = oldData + (newData - oldData) * .2f;
                _spectrumData[i] = lerpData;
            }
        }

        private void DrawingTimer_Tick(object sender, EventArgs e)
        {
            if (_spectrumData == null)
            {
                return;
            }

            _colorIndex++;

            var color1 = _allColors[_colorIndex % _allColors.Length];
            var color2 = _allColors[(_colorIndex + 200) % _allColors.Length];

            DrawGradientStrips(
                StripsPath, color1, color2,
                _spectrumData, _spectrumData.Length,
                StripsPath.ActualWidth, 0, StripsPath.ActualHeight,
                2, -StripsPath.ActualHeight * 200
            );
        }

        /// <summary>
        /// 绘制渐变的条形
        /// </summary>
        /// <param name="stripsPath">绘图目标</param>
        /// <param name="bottomColor">下方颜色</param>
        /// <param name="topColor">上方颜色</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(
            Path stripsPath, Color bottomColor, Color topColor, double[] spectrumData,
            int stripCount, double drawingWidth, double xOffset, double yOffset, double spacing, double scale
        )
        {
            //竖条宽度
            var stripWidth = (drawingWidth - spacing * stripCount) / stripCount;
            var pointArray = new Point[stripCount];

            for (var i = 0; i < stripCount; i++)
            {
                var x = stripWidth * i + spacing * i + xOffset;
                var y = spectrumData[i * spectrumData.Length / stripCount] * scale; // height
                //给所有频谱位置赋值
                pointArray[i] = new Point(x, y);
            }

            //生成一系列频谱竖条
            var geometry = new GeometryGroup();
            foreach (var point in pointArray)
            {
                var height = point.Y;

                if (height < 0)
                {
                    height = -height;
                }

                //每根竖条的四个角坐标
                var endPoints = new[]
                {
                    new Point(point.X, point.Y + yOffset), //左下角
                    new Point(point.X, point.Y + height + yOffset), //左上角
                    new Point(point.X + stripWidth, point.Y + height + yOffset), //右上角
                    new Point(point.X + stripWidth, point.Y + yOffset) //右下角
                };

                var figure = new PathFigure
                {
                    StartPoint = endPoints[0]
                };

                figure.Segments.Add(new PolyLineSegment(endPoints, false));
                geometry.Children.Add(new PathGeometry { Figures = { figure } });
            }

            stripsPath.Data = geometry;

            //设置频谱竖条的渐变色
            var linearGradientBrush = new LinearGradientBrush(
                bottomColor, topColor, new Point(0, 0), new Point(0, 1)
            );
            stripsPath.Fill = linearGradientBrush;
        }
    }
}