Newer
Older
Correlator / Correlator / Dialog / SimplyAuditionDialog.xaml.cs
using System;
using System.Linq;
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.Dialog
{
    /// <summary>
    /// 简化版听音界面(不带实时波形图)
    /// </summary>
    public partial class SimplyAuditionDialog : UserControl
    {
        private readonly AudioVisualizer _visualizer; // 可视化
        private double[] _spectrumData; // 频谱数据
        private readonly WasapiCapture _capture; // 音频捕获
        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 SimplyAuditionDialog(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 SimplyAuditionDialog_OnLoaded(object sender, RoutedEventArgs e)
        {
            _capture.StartRecording();
            _dataTimer.Start();
            _drawingTimer.Start();
        }

        private void SimplyAuditionDialog_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];

            var bassArea = AudioDataHub.Get.TakeSpectrumOfFrequency(
                _spectrumData, RuntimeCache.AudioSampleRate, 250
            );
            var bassScale = bassArea.Average() * 100; //低音区

            //波形曲线
            var curveBrush = new SolidColorBrush(color1);
            DrawCurve(
                SampleWavePath, curveBrush,
                _visualizer.SampleData, _visualizer.SampleData.Length,
                SampleWavePanel.ActualWidth, 0, SampleWavePanel.ActualHeight / 2,
                Math.Min(SampleWavePanel.ActualHeight / 2, 50)
            );

            //四周边框
            DrawGradientBorder(
                TopBorder, BottomBorder, LeftBorder, RightBorder,
                Color.FromArgb(0, color1.R, color1.G, color1.B), color2,
                SampleWavePanel.ActualWidth / 3, bassScale
            );
        }

        /// <summary>
        /// 画曲线
        /// </summary>
        /// <param name="wavePath"></param>
        /// <param name="brush"></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(
            Path wavePath, Brush brush, double[] spectrumData, int pointCount, double drawingWidth,
            double xOffset, double yOffset, double scale
        )
        {
            var pointArray = new Point[pointCount];
            for (var i = 0; i < pointCount; i++)
            {
                var x = i * drawingWidth / pointCount + xOffset;
                var y = spectrumData[i * spectrumData.Length / pointCount] * scale + yOffset;
                pointArray[i] = new Point(x, y);
            }

            var figure = new PathFigure
            {
                StartPoint = pointArray[0]
            };
            figure.Segments.Add(new PolyLineSegment(pointArray, true));

            wavePath.Data = new PathGeometry { Figures = { figure } };
            wavePath.Stroke = brush;
        }

        /// <summary>
        /// 画四周渐变边框
        /// </summary>
        /// <param name="topBorder"></param>
        /// <param name="bottomBorder"></param>
        /// <param name="leftBorder"></param>
        /// <param name="rightBorder"></param>
        /// <param name="inner"></param>
        /// <param name="outer"></param>
        /// <param name="width">画图宽度</param>
        /// <param name="bassScale">高低音转化比例</param>
        private void DrawGradientBorder(
            Rectangle topBorder, Rectangle bottomBorder, Rectangle leftBorder,
            Rectangle rightBorder, Color inner, Color outer, double width, double bassScale
        )
        {
            //边框粗细根据音频高低音变化
            var thickness = (int)(width * bassScale);

            topBorder.Height = thickness;
            bottomBorder.Height = thickness;
            leftBorder.Width = thickness;
            rightBorder.Width = thickness;

            topBorder.Fill = new LinearGradientBrush(outer, inner, 90);
            bottomBorder.Fill = new LinearGradientBrush(inner, outer, 90);
            leftBorder.Fill = new LinearGradientBrush(outer, inner, 0);
            rightBorder.Fill = new LinearGradientBrush(inner, outer, 0);
        }
    }
}