diff --git a/Correlator/App.xaml.cs b/Correlator/App.xaml.cs index 0016e08..66b62a8 100644 --- a/Correlator/App.xaml.cs +++ b/Correlator/App.xaml.cs @@ -104,6 +104,7 @@ containerRegistry.RegisterDialog(); containerRegistry.RegisterDialog(); containerRegistry.RegisterDialog(); + containerRegistry.RegisterDialog(); containerRegistry.RegisterDialog(); containerRegistry.RegisterDialog(); containerRegistry.RegisterDialog(); diff --git a/Correlator/App.xaml.cs b/Correlator/App.xaml.cs index 0016e08..66b62a8 100644 --- a/Correlator/App.xaml.cs +++ b/Correlator/App.xaml.cs @@ -104,6 +104,7 @@ containerRegistry.RegisterDialog(); containerRegistry.RegisterDialog(); containerRegistry.RegisterDialog(); + containerRegistry.RegisterDialog(); containerRegistry.RegisterDialog(); containerRegistry.RegisterDialog(); containerRegistry.RegisterDialog(); diff --git a/Correlator/Correlator.csproj b/Correlator/Correlator.csproj index 87d02cf..49a5b0c 100644 --- a/Correlator/Correlator.csproj +++ b/Correlator/Correlator.csproj @@ -235,6 +235,9 @@ AudioFileDialog.xaml + + AudioVisualizeDialog.xaml + CheckResponseDialog.xaml @@ -265,6 +268,7 @@ + @@ -334,6 +338,7 @@ + Designer MSBuild:Compile diff --git a/Correlator/App.xaml.cs b/Correlator/App.xaml.cs index 0016e08..66b62a8 100644 --- a/Correlator/App.xaml.cs +++ b/Correlator/App.xaml.cs @@ -104,6 +104,7 @@ containerRegistry.RegisterDialog(); containerRegistry.RegisterDialog(); containerRegistry.RegisterDialog(); + containerRegistry.RegisterDialog(); containerRegistry.RegisterDialog(); containerRegistry.RegisterDialog(); containerRegistry.RegisterDialog(); diff --git a/Correlator/Correlator.csproj b/Correlator/Correlator.csproj index 87d02cf..49a5b0c 100644 --- a/Correlator/Correlator.csproj +++ b/Correlator/Correlator.csproj @@ -235,6 +235,9 @@ AudioFileDialog.xaml + + AudioVisualizeDialog.xaml + CheckResponseDialog.xaml @@ -265,6 +268,7 @@ + @@ -334,6 +338,7 @@ + Designer MSBuild:Compile diff --git a/Correlator/Dialog/AudioFileDialog.xaml b/Correlator/Dialog/AudioFileDialog.xaml index 92757c6..3707e01 100644 --- a/Correlator/Dialog/AudioFileDialog.xaml +++ b/Correlator/Dialog/AudioFileDialog.xaml @@ -12,8 +12,6 @@ Height="635" d:DataContext="{d:DesignInstance Type=vm:AudioFileDialogViewModel}" Background="White" - Loaded="AudioFileView_OnLoaded" - Unloaded="AudioFileView_OnUnloaded" mc:Ignorable="d"> @@ -110,7 +108,7 @@ hc:IconElement.Geometry="{StaticResource AudioGeometry}" BorderThickness="0" Command="{Binding DataContext.PlayAudioCommand, ElementName=TargetDataGrid}" - CommandParameter="{Binding FullPath}" + CommandParameter="{Binding}" Style="{StaticResource ButtonInfo}" /> + + + diff --git a/Correlator/App.xaml.cs b/Correlator/App.xaml.cs index 0016e08..66b62a8 100644 --- a/Correlator/App.xaml.cs +++ b/Correlator/App.xaml.cs @@ -104,6 +104,7 @@ containerRegistry.RegisterDialog(); containerRegistry.RegisterDialog(); containerRegistry.RegisterDialog(); + containerRegistry.RegisterDialog(); containerRegistry.RegisterDialog(); containerRegistry.RegisterDialog(); containerRegistry.RegisterDialog(); diff --git a/Correlator/Correlator.csproj b/Correlator/Correlator.csproj index 87d02cf..49a5b0c 100644 --- a/Correlator/Correlator.csproj +++ b/Correlator/Correlator.csproj @@ -235,6 +235,9 @@ AudioFileDialog.xaml + + AudioVisualizeDialog.xaml + CheckResponseDialog.xaml @@ -265,6 +268,7 @@ + @@ -334,6 +338,7 @@ + Designer MSBuild:Compile diff --git a/Correlator/Dialog/AudioFileDialog.xaml b/Correlator/Dialog/AudioFileDialog.xaml index 92757c6..3707e01 100644 --- a/Correlator/Dialog/AudioFileDialog.xaml +++ b/Correlator/Dialog/AudioFileDialog.xaml @@ -12,8 +12,6 @@ Height="635" d:DataContext="{d:DesignInstance Type=vm:AudioFileDialogViewModel}" Background="White" - Loaded="AudioFileView_OnLoaded" - Unloaded="AudioFileView_OnUnloaded" mc:Ignorable="d"> @@ -110,7 +108,7 @@ hc:IconElement.Geometry="{StaticResource AudioGeometry}" BorderThickness="0" Command="{Binding DataContext.PlayAudioCommand, ElementName=TargetDataGrid}" - CommandParameter="{Binding FullPath}" + CommandParameter="{Binding}" Style="{StaticResource ButtonInfo}" /> + + + diff --git a/Correlator/Dialog/AudioVisualizeDialog.xaml.cs b/Correlator/Dialog/AudioVisualizeDialog.xaml.cs new file mode 100644 index 0000000..13155b8 --- /dev/null +++ b/Correlator/Dialog/AudioVisualizeDialog.xaml.cs @@ -0,0 +1,188 @@ +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.Dialog +{ + public partial class AudioVisualizeDialog : 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 AudioVisualizeDialog(IApplicationDataService dataService) + { + InitializeComponent(); + + var sampleRate = dataService.GetSampleRateByWorkMode(RuntimeCache.WorkMode); + + _capture = new WasapiLoopbackCapture(); // 捕获电脑发出的声音 + _visualizer = dataService.GetAudioVisualizer(512); + _allColors = dataService.GetAllHsvColors(); // 获取所有的渐变颜色 (HSV 颜色) + + _capture.WaveFormat = WaveFormat.CreateIeeeFloatWaveFormat(sampleRate, 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(); + } + + /// + /// 刷新频谱数据以及实现频谱数据缓动 + /// + /// + /// + 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 * 0.95, + 2, -StripsPath.ActualHeight * 25 + ); + } + + /// + /// 绘制渐变的条形 + /// + /// 绘图目标 + /// 下方颜色 + /// 上方颜色 + /// 频谱数据 + /// 条形的数量 + /// 绘图的宽度 + /// 绘图的起始 X 坐标 + /// 绘图的起始 Y 坐标 + /// 条形与条形之间的间隔(像素) + /// 控制频谱振幅高度 + 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; + } + } +} \ No newline at end of file diff --git a/Correlator/App.xaml.cs b/Correlator/App.xaml.cs index 0016e08..66b62a8 100644 --- a/Correlator/App.xaml.cs +++ b/Correlator/App.xaml.cs @@ -104,6 +104,7 @@ containerRegistry.RegisterDialog(); containerRegistry.RegisterDialog(); containerRegistry.RegisterDialog(); + containerRegistry.RegisterDialog(); containerRegistry.RegisterDialog(); containerRegistry.RegisterDialog(); containerRegistry.RegisterDialog(); diff --git a/Correlator/Correlator.csproj b/Correlator/Correlator.csproj index 87d02cf..49a5b0c 100644 --- a/Correlator/Correlator.csproj +++ b/Correlator/Correlator.csproj @@ -235,6 +235,9 @@ AudioFileDialog.xaml + + AudioVisualizeDialog.xaml + CheckResponseDialog.xaml @@ -265,6 +268,7 @@ + @@ -334,6 +338,7 @@ + Designer MSBuild:Compile diff --git a/Correlator/Dialog/AudioFileDialog.xaml b/Correlator/Dialog/AudioFileDialog.xaml index 92757c6..3707e01 100644 --- a/Correlator/Dialog/AudioFileDialog.xaml +++ b/Correlator/Dialog/AudioFileDialog.xaml @@ -12,8 +12,6 @@ Height="635" d:DataContext="{d:DesignInstance Type=vm:AudioFileDialogViewModel}" Background="White" - Loaded="AudioFileView_OnLoaded" - Unloaded="AudioFileView_OnUnloaded" mc:Ignorable="d"> @@ -110,7 +108,7 @@ hc:IconElement.Geometry="{StaticResource AudioGeometry}" BorderThickness="0" Command="{Binding DataContext.PlayAudioCommand, ElementName=TargetDataGrid}" - CommandParameter="{Binding FullPath}" + CommandParameter="{Binding}" Style="{StaticResource ButtonInfo}" /> + + + diff --git a/Correlator/Dialog/AudioVisualizeDialog.xaml.cs b/Correlator/Dialog/AudioVisualizeDialog.xaml.cs new file mode 100644 index 0000000..13155b8 --- /dev/null +++ b/Correlator/Dialog/AudioVisualizeDialog.xaml.cs @@ -0,0 +1,188 @@ +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.Dialog +{ + public partial class AudioVisualizeDialog : 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 AudioVisualizeDialog(IApplicationDataService dataService) + { + InitializeComponent(); + + var sampleRate = dataService.GetSampleRateByWorkMode(RuntimeCache.WorkMode); + + _capture = new WasapiLoopbackCapture(); // 捕获电脑发出的声音 + _visualizer = dataService.GetAudioVisualizer(512); + _allColors = dataService.GetAllHsvColors(); // 获取所有的渐变颜色 (HSV 颜色) + + _capture.WaveFormat = WaveFormat.CreateIeeeFloatWaveFormat(sampleRate, 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(); + } + + /// + /// 刷新频谱数据以及实现频谱数据缓动 + /// + /// + /// + 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 * 0.95, + 2, -StripsPath.ActualHeight * 25 + ); + } + + /// + /// 绘制渐变的条形 + /// + /// 绘图目标 + /// 下方颜色 + /// 上方颜色 + /// 频谱数据 + /// 条形的数量 + /// 绘图的宽度 + /// 绘图的起始 X 坐标 + /// 绘图的起始 Y 坐标 + /// 条形与条形之间的间隔(像素) + /// 控制频谱振幅高度 + 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; + } + } +} \ No newline at end of file diff --git a/Correlator/Fonts/iconfont.ttf b/Correlator/Fonts/iconfont.ttf index 17852ab..9dce096 100644 --- a/Correlator/Fonts/iconfont.ttf +++ b/Correlator/Fonts/iconfont.ttf Binary files differ diff --git a/Correlator/App.xaml.cs b/Correlator/App.xaml.cs index 0016e08..66b62a8 100644 --- a/Correlator/App.xaml.cs +++ b/Correlator/App.xaml.cs @@ -104,6 +104,7 @@ containerRegistry.RegisterDialog(); containerRegistry.RegisterDialog(); containerRegistry.RegisterDialog(); + containerRegistry.RegisterDialog(); containerRegistry.RegisterDialog(); containerRegistry.RegisterDialog(); containerRegistry.RegisterDialog(); diff --git a/Correlator/Correlator.csproj b/Correlator/Correlator.csproj index 87d02cf..49a5b0c 100644 --- a/Correlator/Correlator.csproj +++ b/Correlator/Correlator.csproj @@ -235,6 +235,9 @@ AudioFileDialog.xaml + + AudioVisualizeDialog.xaml + CheckResponseDialog.xaml @@ -265,6 +268,7 @@ + @@ -334,6 +338,7 @@ + Designer MSBuild:Compile diff --git a/Correlator/Dialog/AudioFileDialog.xaml b/Correlator/Dialog/AudioFileDialog.xaml index 92757c6..3707e01 100644 --- a/Correlator/Dialog/AudioFileDialog.xaml +++ b/Correlator/Dialog/AudioFileDialog.xaml @@ -12,8 +12,6 @@ Height="635" d:DataContext="{d:DesignInstance Type=vm:AudioFileDialogViewModel}" Background="White" - Loaded="AudioFileView_OnLoaded" - Unloaded="AudioFileView_OnUnloaded" mc:Ignorable="d"> @@ -110,7 +108,7 @@ hc:IconElement.Geometry="{StaticResource AudioGeometry}" BorderThickness="0" Command="{Binding DataContext.PlayAudioCommand, ElementName=TargetDataGrid}" - CommandParameter="{Binding FullPath}" + CommandParameter="{Binding}" Style="{StaticResource ButtonInfo}" /> + + + diff --git a/Correlator/Dialog/AudioVisualizeDialog.xaml.cs b/Correlator/Dialog/AudioVisualizeDialog.xaml.cs new file mode 100644 index 0000000..13155b8 --- /dev/null +++ b/Correlator/Dialog/AudioVisualizeDialog.xaml.cs @@ -0,0 +1,188 @@ +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.Dialog +{ + public partial class AudioVisualizeDialog : 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 AudioVisualizeDialog(IApplicationDataService dataService) + { + InitializeComponent(); + + var sampleRate = dataService.GetSampleRateByWorkMode(RuntimeCache.WorkMode); + + _capture = new WasapiLoopbackCapture(); // 捕获电脑发出的声音 + _visualizer = dataService.GetAudioVisualizer(512); + _allColors = dataService.GetAllHsvColors(); // 获取所有的渐变颜色 (HSV 颜色) + + _capture.WaveFormat = WaveFormat.CreateIeeeFloatWaveFormat(sampleRate, 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(); + } + + /// + /// 刷新频谱数据以及实现频谱数据缓动 + /// + /// + /// + 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 * 0.95, + 2, -StripsPath.ActualHeight * 25 + ); + } + + /// + /// 绘制渐变的条形 + /// + /// 绘图目标 + /// 下方颜色 + /// 上方颜色 + /// 频谱数据 + /// 条形的数量 + /// 绘图的宽度 + /// 绘图的起始 X 坐标 + /// 绘图的起始 Y 坐标 + /// 条形与条形之间的间隔(像素) + /// 控制频谱振幅高度 + 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; + } + } +} \ No newline at end of file diff --git a/Correlator/Fonts/iconfont.ttf b/Correlator/Fonts/iconfont.ttf index 17852ab..9dce096 100644 --- a/Correlator/Fonts/iconfont.ttf +++ b/Correlator/Fonts/iconfont.ttf Binary files differ diff --git a/Correlator/ViewModels/AudioFileDialogViewModel.cs b/Correlator/ViewModels/AudioFileDialogViewModel.cs index 0becb74..fa07071 100644 --- a/Correlator/ViewModels/AudioFileDialogViewModel.cs +++ b/Correlator/ViewModels/AudioFileDialogViewModel.cs @@ -6,13 +6,10 @@ using System.Threading.Tasks; using System.Windows; using System.Windows.Media.Imaging; -using System.Windows.Threading; -using Correlator.DataService; using Correlator.Model; using Correlator.Util; using HandyControl.Controls; using HandyControl.Data; -using NAudio.Wave; using Prism.Commands; using Prism.Mvvm; using Prism.Services.Dialogs; @@ -56,42 +53,6 @@ } } - private string _audioState = "未播放"; - - public string AudioState - { - get => _audioState; - set - { - _audioState = value; - RaisePropertyChanged(); - } - } - - private int _max = int.MaxValue; - - public int Max - { - get => _max; - set - { - _max = value; - RaisePropertyChanged(); - } - } - - private int _currentValue; - - public int CurrentValue - { - get => _currentValue; - set - { - _currentValue = value; - RaisePropertyChanged(); - } - } - private int _maxPage; public int MaxPage @@ -120,82 +81,40 @@ #region DelegateCommand - public DelegateCommand PlayAudioCommand { get; set; } + public DelegateCommand PlayAudioCommand { get; set; } public DelegateCommand DeleteAudioCommand { get; set; } public DelegateCommand> PageUpdatedCmd { get; set; } #endregion - private readonly IAudioService _audioService; - private AudioFileReader _audioFileReader; + private readonly IDialogService _dialogService; /// /// 列表每页条目数 /// private const int PerPageItemCount = 8; - private readonly DispatcherTimer _audioTimer = new DispatcherTimer + public AudioFileDialogViewModel(IDialogService dialogService) { - Interval = new TimeSpan(0, 0, 0, 1) - }; + _dialogService = dialogService; - public AudioFileDialogViewModel(IAudioService audioService) - { - _audioService = audioService; - - _audioTimer.Tick += delegate - { - if (audioService.Wave.PlaybackState == PlaybackState.Stopped) - { - _audioTimer.Stop(); - AudioState = "未播放"; - } - - if (_audioFileReader == null) - { - return; - } - - CurrentValue = _audioFileReader.CurrentTime.Seconds; - }; - - PlayAudioCommand = new DelegateCommand(PlayAudio); + PlayAudioCommand = new DelegateCommand(PlayAudio); DeleteAudioCommand = new DelegateCommand(DeleteAudio); PageUpdatedCmd = new DelegateCommand>(PageUpdated); } - private void PlayAudio(object filePath) + private void PlayAudio(object audioFile) { - if (filePath == null) + if (audioFile == null) { MessageBox.Show("音频文件路径错误,无法播放", "温馨提示", MessageBoxButton.OK, MessageBoxImage.Error); return; } - if (_audioService.Wave.PlaybackState == PlaybackState.Playing) + _dialogService.ShowDialog("AudioVisualizeDialog", new DialogParameters { - _audioService.Wave.Stop(); - DisposeResource("已暂停"); - } - else - { - try - { - _audioTimer.Start(); - _audioFileReader = new AudioFileReader((string)filePath); - Max = (int)_audioFileReader.TotalTime.TotalSeconds; - AudioState = $"时长:{_max}s"; - - _audioService.Wave.Init(_audioFileReader); - _audioService.Wave.Play(); - } - catch (FormatException) - { - DisposeResource("未播放"); - - MessageBox.Show("音频文件已损坏,无法播放", "温馨提示", MessageBoxButton.OK, MessageBoxImage.Error); - } - } + { "AudioFile", audioFile } + }, delegate { }); } private async void DeleteAudio(object filePath) @@ -206,12 +125,6 @@ return; } - if (_audioService.Wave.PlaybackState == PlaybackState.Playing) - { - MessageBox.Show("音频播放中,请勿删除", "温馨提示", MessageBoxButton.OK, MessageBoxImage.Stop); - return; - } - try { File.Delete((string)filePath); @@ -237,13 +150,6 @@ AudioFiles = audioFiles.ToObservableCollection(); } - private void DisposeResource(string message) - { - _audioFileReader?.Dispose(); - _audioTimer?.Stop(); - AudioState = message; - } - public bool CanCloseDialog() { return true; diff --git a/Correlator/App.xaml.cs b/Correlator/App.xaml.cs index 0016e08..66b62a8 100644 --- a/Correlator/App.xaml.cs +++ b/Correlator/App.xaml.cs @@ -104,6 +104,7 @@ containerRegistry.RegisterDialog(); containerRegistry.RegisterDialog(); containerRegistry.RegisterDialog(); + containerRegistry.RegisterDialog(); containerRegistry.RegisterDialog(); containerRegistry.RegisterDialog(); containerRegistry.RegisterDialog(); diff --git a/Correlator/Correlator.csproj b/Correlator/Correlator.csproj index 87d02cf..49a5b0c 100644 --- a/Correlator/Correlator.csproj +++ b/Correlator/Correlator.csproj @@ -235,6 +235,9 @@ AudioFileDialog.xaml + + AudioVisualizeDialog.xaml + CheckResponseDialog.xaml @@ -265,6 +268,7 @@ + @@ -334,6 +338,7 @@ + Designer MSBuild:Compile diff --git a/Correlator/Dialog/AudioFileDialog.xaml b/Correlator/Dialog/AudioFileDialog.xaml index 92757c6..3707e01 100644 --- a/Correlator/Dialog/AudioFileDialog.xaml +++ b/Correlator/Dialog/AudioFileDialog.xaml @@ -12,8 +12,6 @@ Height="635" d:DataContext="{d:DesignInstance Type=vm:AudioFileDialogViewModel}" Background="White" - Loaded="AudioFileView_OnLoaded" - Unloaded="AudioFileView_OnUnloaded" mc:Ignorable="d"> @@ -110,7 +108,7 @@ hc:IconElement.Geometry="{StaticResource AudioGeometry}" BorderThickness="0" Command="{Binding DataContext.PlayAudioCommand, ElementName=TargetDataGrid}" - CommandParameter="{Binding FullPath}" + CommandParameter="{Binding}" Style="{StaticResource ButtonInfo}" /> + + + diff --git a/Correlator/Dialog/AudioVisualizeDialog.xaml.cs b/Correlator/Dialog/AudioVisualizeDialog.xaml.cs new file mode 100644 index 0000000..13155b8 --- /dev/null +++ b/Correlator/Dialog/AudioVisualizeDialog.xaml.cs @@ -0,0 +1,188 @@ +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.Dialog +{ + public partial class AudioVisualizeDialog : 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 AudioVisualizeDialog(IApplicationDataService dataService) + { + InitializeComponent(); + + var sampleRate = dataService.GetSampleRateByWorkMode(RuntimeCache.WorkMode); + + _capture = new WasapiLoopbackCapture(); // 捕获电脑发出的声音 + _visualizer = dataService.GetAudioVisualizer(512); + _allColors = dataService.GetAllHsvColors(); // 获取所有的渐变颜色 (HSV 颜色) + + _capture.WaveFormat = WaveFormat.CreateIeeeFloatWaveFormat(sampleRate, 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(); + } + + /// + /// 刷新频谱数据以及实现频谱数据缓动 + /// + /// + /// + 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 * 0.95, + 2, -StripsPath.ActualHeight * 25 + ); + } + + /// + /// 绘制渐变的条形 + /// + /// 绘图目标 + /// 下方颜色 + /// 上方颜色 + /// 频谱数据 + /// 条形的数量 + /// 绘图的宽度 + /// 绘图的起始 X 坐标 + /// 绘图的起始 Y 坐标 + /// 条形与条形之间的间隔(像素) + /// 控制频谱振幅高度 + 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; + } + } +} \ No newline at end of file diff --git a/Correlator/Fonts/iconfont.ttf b/Correlator/Fonts/iconfont.ttf index 17852ab..9dce096 100644 --- a/Correlator/Fonts/iconfont.ttf +++ b/Correlator/Fonts/iconfont.ttf Binary files differ diff --git a/Correlator/ViewModels/AudioFileDialogViewModel.cs b/Correlator/ViewModels/AudioFileDialogViewModel.cs index 0becb74..fa07071 100644 --- a/Correlator/ViewModels/AudioFileDialogViewModel.cs +++ b/Correlator/ViewModels/AudioFileDialogViewModel.cs @@ -6,13 +6,10 @@ using System.Threading.Tasks; using System.Windows; using System.Windows.Media.Imaging; -using System.Windows.Threading; -using Correlator.DataService; using Correlator.Model; using Correlator.Util; using HandyControl.Controls; using HandyControl.Data; -using NAudio.Wave; using Prism.Commands; using Prism.Mvvm; using Prism.Services.Dialogs; @@ -56,42 +53,6 @@ } } - private string _audioState = "未播放"; - - public string AudioState - { - get => _audioState; - set - { - _audioState = value; - RaisePropertyChanged(); - } - } - - private int _max = int.MaxValue; - - public int Max - { - get => _max; - set - { - _max = value; - RaisePropertyChanged(); - } - } - - private int _currentValue; - - public int CurrentValue - { - get => _currentValue; - set - { - _currentValue = value; - RaisePropertyChanged(); - } - } - private int _maxPage; public int MaxPage @@ -120,82 +81,40 @@ #region DelegateCommand - public DelegateCommand PlayAudioCommand { get; set; } + public DelegateCommand PlayAudioCommand { get; set; } public DelegateCommand DeleteAudioCommand { get; set; } public DelegateCommand> PageUpdatedCmd { get; set; } #endregion - private readonly IAudioService _audioService; - private AudioFileReader _audioFileReader; + private readonly IDialogService _dialogService; /// /// 列表每页条目数 /// private const int PerPageItemCount = 8; - private readonly DispatcherTimer _audioTimer = new DispatcherTimer + public AudioFileDialogViewModel(IDialogService dialogService) { - Interval = new TimeSpan(0, 0, 0, 1) - }; + _dialogService = dialogService; - public AudioFileDialogViewModel(IAudioService audioService) - { - _audioService = audioService; - - _audioTimer.Tick += delegate - { - if (audioService.Wave.PlaybackState == PlaybackState.Stopped) - { - _audioTimer.Stop(); - AudioState = "未播放"; - } - - if (_audioFileReader == null) - { - return; - } - - CurrentValue = _audioFileReader.CurrentTime.Seconds; - }; - - PlayAudioCommand = new DelegateCommand(PlayAudio); + PlayAudioCommand = new DelegateCommand(PlayAudio); DeleteAudioCommand = new DelegateCommand(DeleteAudio); PageUpdatedCmd = new DelegateCommand>(PageUpdated); } - private void PlayAudio(object filePath) + private void PlayAudio(object audioFile) { - if (filePath == null) + if (audioFile == null) { MessageBox.Show("音频文件路径错误,无法播放", "温馨提示", MessageBoxButton.OK, MessageBoxImage.Error); return; } - if (_audioService.Wave.PlaybackState == PlaybackState.Playing) + _dialogService.ShowDialog("AudioVisualizeDialog", new DialogParameters { - _audioService.Wave.Stop(); - DisposeResource("已暂停"); - } - else - { - try - { - _audioTimer.Start(); - _audioFileReader = new AudioFileReader((string)filePath); - Max = (int)_audioFileReader.TotalTime.TotalSeconds; - AudioState = $"时长:{_max}s"; - - _audioService.Wave.Init(_audioFileReader); - _audioService.Wave.Play(); - } - catch (FormatException) - { - DisposeResource("未播放"); - - MessageBox.Show("音频文件已损坏,无法播放", "温馨提示", MessageBoxButton.OK, MessageBoxImage.Error); - } - } + { "AudioFile", audioFile } + }, delegate { }); } private async void DeleteAudio(object filePath) @@ -206,12 +125,6 @@ return; } - if (_audioService.Wave.PlaybackState == PlaybackState.Playing) - { - MessageBox.Show("音频播放中,请勿删除", "温馨提示", MessageBoxButton.OK, MessageBoxImage.Stop); - return; - } - try { File.Delete((string)filePath); @@ -237,13 +150,6 @@ AudioFiles = audioFiles.ToObservableCollection(); } - private void DisposeResource(string message) - { - _audioFileReader?.Dispose(); - _audioTimer?.Stop(); - AudioState = message; - } - public bool CanCloseDialog() { return true; diff --git a/Correlator/ViewModels/AudioVisualizeDialogViewModel.cs b/Correlator/ViewModels/AudioVisualizeDialogViewModel.cs new file mode 100644 index 0000000..ae78963 --- /dev/null +++ b/Correlator/ViewModels/AudioVisualizeDialogViewModel.cs @@ -0,0 +1,144 @@ +using System; +using System.Windows.Threading; +using Correlator.DataService; +using Correlator.Model; +using NAudio.Wave; +using Prism.Commands; +using Prism.Mvvm; +using Prism.Services.Dialogs; + +namespace Correlator.ViewModels +{ + public class AudioVisualizeDialogViewModel : BindableBase, IDialogAware + { + public event Action RequestClose + { + add { } + remove { } + } + + #region 属性绑定 + + public string Title { get; set; } + + private float _currentValue; + + public float CurrentValue + { + get => _currentValue; + set + { + _currentValue = value; + RaisePropertyChanged(); + } + } + + private string _audioCurrentTime; + + public string AudioCurrentTime + { + get => _audioCurrentTime; + set + { + _audioCurrentTime = value; + RaisePropertyChanged(); + } + } + + private bool _canReStartAudio; + + public bool CanReStartAudio + { + get => _canReStartAudio; + set + { + _canReStartAudio = value; + RaisePropertyChanged(); + } + } + + #endregion + + #region DelegateCommand + + public DelegateCommand ReStartAudioCommand { get; set; } + + #endregion + + private readonly IAudioService _audioService; + private AudioFileReader _audioFileReader; + private AudioFile _audioFile; + + private readonly DispatcherTimer _audioTimer = new DispatcherTimer + { + Interval = new TimeSpan(0, 0, 0, 1) + }; + + public AudioVisualizeDialogViewModel(IAudioService audioService) + { + _audioService = audioService; + + _audioTimer.Tick += delegate + { + if (_audioFileReader == null) + { + return; + } + + CurrentValue = (float)_audioFileReader.Position / _audioFileReader.Length; + var timeSpan = _audioFileReader.CurrentTime; + var time = timeSpan.ToString(@"mm\:ss"); + AudioCurrentTime = $"{time}/{_audioFile?.Duration}"; + }; + + ReStartAudioCommand = new DelegateCommand(ReStartAudio); + } + + private void ReStartAudio() + { + StartAudio(); + CanReStartAudio = false; + } + + private void Audio_PlaybackStopped_Event(object sender, StoppedEventArgs e) + { + DisposeResource(); + CanReStartAudio = true; + } + + public bool CanCloseDialog() + { + return true; + } + + public void OnDialogClosed() + { + DisposeResource(); + } + + public void OnDialogOpened(IDialogParameters parameters) + { + _audioFile = parameters.GetValue("AudioFile"); + Title = _audioFile.FileName; + StartAudio(); + } + + private void StartAudio() + { + AudioCurrentTime = $"00:00/{_audioFile.Duration}"; + + _audioFileReader = new AudioFileReader(_audioFile.FullPath); + _audioService.Wave.Init(_audioFileReader); + _audioService.Wave.PlaybackStopped += Audio_PlaybackStopped_Event; + _audioService.Wave.Play(); + _audioTimer.Start(); + } + + private void DisposeResource() + { + _audioService.Wave.Stop(); + _audioFileReader?.Dispose(); + _audioTimer?.Stop(); + } + } +} \ No newline at end of file