├── .gitattributes ├── .gitignore ├── AudioVisualizer.sln ├── AudioVisualizer ├── AudioVisualizer.csproj ├── MainWindow.Designer.cs ├── MainWindow.cs ├── MainWindow.resx └── Program.cs ├── AudioVisualizerDx ├── AudioVisualizerDx.csproj ├── MainWindow.Designer.cs ├── MainWindow.cs ├── MainWindow.resx └── Program.cs ├── AudioVisualizerDxDesktop ├── AudioVisualizerDxDesktop.csproj ├── MainWindow.Designer.cs ├── MainWindow.cs ├── MainWindow.resx └── Program.cs ├── AudioVisualizerGL ├── AppWindow.cs ├── AudioVisualizerGL.csproj ├── BarsObject.cs ├── Mesh.cs ├── RenderObject.cs └── Shader.cs ├── LICENSE ├── LibAudioVisualizer ├── LibAudioVisualizer.csproj └── Visualizer.cs ├── LibDynamics ├── LibDynamics.csproj ├── SecondOrderDynamics.cs └── SecondOrderDynamicsForArray.cs ├── WpfAudioVisualizer ├── App.xaml ├── App.xaml.cs ├── AssemblyInfo.cs ├── AudioVisualizerWpf.csproj ├── ConsoleUtils.cs ├── MainWindow.xaml ├── MainWindow.xaml.cs ├── Properties │ └── launchSettings.json ├── Themes │ └── Generic.xaml ├── Utilities │ └── ColorUtils.cs └── VisualizerControl.cs ├── readme.md └── res ├── code.png ├── preview1.png └── preview2.png /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 5 | 6 | # User-specific files 7 | *.rsuser 8 | *.suo 9 | *.user 10 | *.userosscache 11 | *.sln.docstates 12 | 13 | # User-specific files (MonoDevelop/Xamarin Studio) 14 | *.userprefs 15 | 16 | # Mono auto generated files 17 | mono_crash.* 18 | 19 | # Build results 20 | [Dd]ebug/ 21 | [Dd]ebugPublic/ 22 | [Rr]elease/ 23 | [Rr]eleases/ 24 | x64/ 25 | x86/ 26 | [Ww][Ii][Nn]32/ 27 | [Aa][Rr][Mm]/ 28 | [Aa][Rr][Mm]64/ 29 | bld/ 30 | [Bb]in/ 31 | [Oo]bj/ 32 | [Oo]ut/ 33 | [Ll]og/ 34 | [Ll]ogs/ 35 | 36 | # Visual Studio 2015/2017 cache/options directory 37 | .vs/ 38 | # Uncomment if you have tasks that create the project's static files in wwwroot 39 | #wwwroot/ 40 | 41 | # Visual Studio 2017 auto generated files 42 | Generated\ Files/ 43 | 44 | # MSTest test Results 45 | [Tt]est[Rr]esult*/ 46 | [Bb]uild[Ll]og.* 47 | 48 | # NUnit 49 | *.VisualState.xml 50 | TestResult.xml 51 | nunit-*.xml 52 | 53 | # Build Results of an ATL Project 54 | [Dd]ebugPS/ 55 | [Rr]eleasePS/ 56 | dlldata.c 57 | 58 | # Benchmark Results 59 | BenchmarkDotNet.Artifacts/ 60 | 61 | # .NET Core 62 | project.lock.json 63 | project.fragment.lock.json 64 | artifacts/ 65 | 66 | # ASP.NET Scaffolding 67 | ScaffoldingReadMe.txt 68 | 69 | # StyleCop 70 | StyleCopReport.xml 71 | 72 | # Files built by Visual Studio 73 | *_i.c 74 | *_p.c 75 | *_h.h 76 | *.ilk 77 | *.meta 78 | *.obj 79 | *.iobj 80 | *.pch 81 | *.pdb 82 | *.ipdb 83 | *.pgc 84 | *.pgd 85 | *.rsp 86 | *.sbr 87 | *.tlb 88 | *.tli 89 | *.tlh 90 | *.tmp 91 | *.tmp_proj 92 | *_wpftmp.csproj 93 | *.log 94 | *.vspscc 95 | *.vssscc 96 | .builds 97 | *.pidb 98 | *.svclog 99 | *.scc 100 | 101 | # Chutzpah Test files 102 | _Chutzpah* 103 | 104 | # Visual C++ cache files 105 | ipch/ 106 | *.aps 107 | *.ncb 108 | *.opendb 109 | *.opensdf 110 | *.sdf 111 | *.cachefile 112 | *.VC.db 113 | *.VC.VC.opendb 114 | 115 | # Visual Studio profiler 116 | *.psess 117 | *.vsp 118 | *.vspx 119 | *.sap 120 | 121 | # Visual Studio Trace Files 122 | *.e2e 123 | 124 | # TFS 2012 Local Workspace 125 | $tf/ 126 | 127 | # Guidance Automation Toolkit 128 | *.gpState 129 | 130 | # ReSharper is a .NET coding add-in 131 | _ReSharper*/ 132 | *.[Rr]e[Ss]harper 133 | *.DotSettings.user 134 | 135 | # TeamCity is a build add-in 136 | _TeamCity* 137 | 138 | # DotCover is a Code Coverage Tool 139 | *.dotCover 140 | 141 | # AxoCover is a Code Coverage Tool 142 | .axoCover/* 143 | !.axoCover/settings.json 144 | 145 | # Coverlet is a free, cross platform Code Coverage Tool 146 | coverage*.json 147 | coverage*.xml 148 | coverage*.info 149 | 150 | # Visual Studio code coverage results 151 | *.coverage 152 | *.coveragexml 153 | 154 | # NCrunch 155 | _NCrunch_* 156 | .*crunch*.local.xml 157 | nCrunchTemp_* 158 | 159 | # MightyMoose 160 | *.mm.* 161 | AutoTest.Net/ 162 | 163 | # Web workbench (sass) 164 | .sass-cache/ 165 | 166 | # Installshield output folder 167 | [Ee]xpress/ 168 | 169 | # DocProject is a documentation generator add-in 170 | DocProject/buildhelp/ 171 | DocProject/Help/*.HxT 172 | DocProject/Help/*.HxC 173 | DocProject/Help/*.hhc 174 | DocProject/Help/*.hhk 175 | DocProject/Help/*.hhp 176 | DocProject/Help/Html2 177 | DocProject/Help/html 178 | 179 | # Click-Once directory 180 | publish/ 181 | 182 | # Publish Web Output 183 | *.[Pp]ublish.xml 184 | *.azurePubxml 185 | # Note: Comment the next line if you want to checkin your web deploy settings, 186 | # but database connection strings (with potential passwords) will be unencrypted 187 | *.pubxml 188 | *.publishproj 189 | 190 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 191 | # checkin your Azure Web App publish settings, but sensitive information contained 192 | # in these scripts will be unencrypted 193 | PublishScripts/ 194 | 195 | # NuGet Packages 196 | *.nupkg 197 | # NuGet Symbol Packages 198 | *.snupkg 199 | # The packages folder can be ignored because of Package Restore 200 | **/[Pp]ackages/* 201 | # except build/, which is used as an MSBuild target. 202 | !**/[Pp]ackages/build/ 203 | # Uncomment if necessary however generally it will be regenerated when needed 204 | #!**/[Pp]ackages/repositories.config 205 | # NuGet v3's project.json files produces more ignorable files 206 | *.nuget.props 207 | *.nuget.targets 208 | 209 | # Microsoft Azure Build Output 210 | csx/ 211 | *.build.csdef 212 | 213 | # Microsoft Azure Emulator 214 | ecf/ 215 | rcf/ 216 | 217 | # Windows Store app package directories and files 218 | AppPackages/ 219 | BundleArtifacts/ 220 | Package.StoreAssociation.xml 221 | _pkginfo.txt 222 | *.appx 223 | *.appxbundle 224 | *.appxupload 225 | 226 | # Visual Studio cache files 227 | # files ending in .cache can be ignored 228 | *.[Cc]ache 229 | # but keep track of directories ending in .cache 230 | !?*.[Cc]ache/ 231 | 232 | # Others 233 | ClientBin/ 234 | ~$* 235 | *~ 236 | *.dbmdl 237 | *.dbproj.schemaview 238 | *.jfm 239 | *.pfx 240 | *.publishsettings 241 | orleans.codegen.cs 242 | 243 | # Including strong name files can present a security risk 244 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 245 | #*.snk 246 | 247 | # Since there are multiple workflows, uncomment next line to ignore bower_components 248 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 249 | #bower_components/ 250 | 251 | # RIA/Silverlight projects 252 | Generated_Code/ 253 | 254 | # Backup & report files from converting an old project file 255 | # to a newer Visual Studio version. Backup files are not needed, 256 | # because we have git ;-) 257 | _UpgradeReport_Files/ 258 | Backup*/ 259 | UpgradeLog*.XML 260 | UpgradeLog*.htm 261 | ServiceFabricBackup/ 262 | *.rptproj.bak 263 | 264 | # SQL Server files 265 | *.mdf 266 | *.ldf 267 | *.ndf 268 | 269 | # Business Intelligence projects 270 | *.rdl.data 271 | *.bim.layout 272 | *.bim_*.settings 273 | *.rptproj.rsuser 274 | *- [Bb]ackup.rdl 275 | *- [Bb]ackup ([0-9]).rdl 276 | *- [Bb]ackup ([0-9][0-9]).rdl 277 | 278 | # Microsoft Fakes 279 | FakesAssemblies/ 280 | 281 | # GhostDoc plugin setting file 282 | *.GhostDoc.xml 283 | 284 | # Node.js Tools for Visual Studio 285 | .ntvs_analysis.dat 286 | node_modules/ 287 | 288 | # Visual Studio 6 build log 289 | *.plg 290 | 291 | # Visual Studio 6 workspace options file 292 | *.opt 293 | 294 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 295 | *.vbw 296 | 297 | # Visual Studio LightSwitch build output 298 | **/*.HTMLClient/GeneratedArtifacts 299 | **/*.DesktopClient/GeneratedArtifacts 300 | **/*.DesktopClient/ModelManifest.xml 301 | **/*.Server/GeneratedArtifacts 302 | **/*.Server/ModelManifest.xml 303 | _Pvt_Extensions 304 | 305 | # Paket dependency manager 306 | .paket/paket.exe 307 | paket-files/ 308 | 309 | # FAKE - F# Make 310 | .fake/ 311 | 312 | # CodeRush personal settings 313 | .cr/personal 314 | 315 | # Python Tools for Visual Studio (PTVS) 316 | __pycache__/ 317 | *.pyc 318 | 319 | # Cake - Uncomment if you are using it 320 | # tools/** 321 | # !tools/packages.config 322 | 323 | # Tabs Studio 324 | *.tss 325 | 326 | # Telerik's JustMock configuration file 327 | *.jmconfig 328 | 329 | # BizTalk build output 330 | *.btp.cs 331 | *.btm.cs 332 | *.odx.cs 333 | *.xsd.cs 334 | 335 | # OpenCover UI analysis results 336 | OpenCover/ 337 | 338 | # Azure Stream Analytics local run output 339 | ASALocalRun/ 340 | 341 | # MSBuild Binary and Structured Log 342 | *.binlog 343 | 344 | # NVidia Nsight GPU debugger configuration file 345 | *.nvuser 346 | 347 | # MFractors (Xamarin productivity tool) working folder 348 | .mfractor/ 349 | 350 | # Local History for Visual Studio 351 | .localhistory/ 352 | 353 | # BeatPulse healthcheck temp database 354 | healthchecksdb 355 | 356 | # Backup folder for Package Reference Convert tool in Visual Studio 2017 357 | MigrationBackup/ 358 | 359 | # Ionide (cross platform F# VS Code tools) working folder 360 | .ionide/ 361 | 362 | # Fody - auto-generated XML schema 363 | FodyWeavers.xsd -------------------------------------------------------------------------------- /AudioVisualizer.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.3.32929.385 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AudioVisualizer", "AudioVisualizer\AudioVisualizer.csproj", "{D413F08A-60A2-4617-880C-BF303397AF2C}" 7 | EndProject 8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AudioVisualizerWpf", "WpfAudioVisualizer\AudioVisualizerWpf.csproj", "{459FE289-A8E1-4BEC-9113-FC42B9CE9436}" 9 | EndProject 10 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LibAudioVisualizer", "LibAudioVisualizer\LibAudioVisualizer.csproj", "{15A2D4B9-BD52-411F-96E9-2BFACFEC4579}" 11 | EndProject 12 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AudioVisualizerDx", "AudioVisualizerDx\AudioVisualizerDx.csproj", "{20B17F7E-11C3-4E46-9E79-DE9A3B25B640}" 13 | EndProject 14 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{F54E0AE6-96AB-42EA-AFD1-37F1BCBAD304}" 15 | ProjectSection(SolutionItems) = preProject 16 | readme.md = readme.md 17 | EndProjectSection 18 | EndProject 19 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AudioVisualizerGL", "AudioVisualizerGL\AudioVisualizerGL.csproj", "{EC24D98A-2776-400D-8494-7B8C954EAF84}" 20 | EndProject 21 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AudioVisualizerDxDesktop", "AudioVisualizerDxDesktop\AudioVisualizerDxDesktop.csproj", "{4398D445-4BE7-4671-83C1-BA7320BEF1EE}" 22 | EndProject 23 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LibDynamics", "LibDynamics\LibDynamics.csproj", "{F31F51C2-25D8-4AF1-891C-01087BB7E5EC}" 24 | EndProject 25 | Global 26 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 27 | Debug|Any CPU = Debug|Any CPU 28 | Release|Any CPU = Release|Any CPU 29 | EndGlobalSection 30 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 31 | {D413F08A-60A2-4617-880C-BF303397AF2C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 32 | {D413F08A-60A2-4617-880C-BF303397AF2C}.Debug|Any CPU.Build.0 = Debug|Any CPU 33 | {D413F08A-60A2-4617-880C-BF303397AF2C}.Release|Any CPU.ActiveCfg = Release|Any CPU 34 | {D413F08A-60A2-4617-880C-BF303397AF2C}.Release|Any CPU.Build.0 = Release|Any CPU 35 | {459FE289-A8E1-4BEC-9113-FC42B9CE9436}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 36 | {459FE289-A8E1-4BEC-9113-FC42B9CE9436}.Debug|Any CPU.Build.0 = Debug|Any CPU 37 | {459FE289-A8E1-4BEC-9113-FC42B9CE9436}.Release|Any CPU.ActiveCfg = Release|Any CPU 38 | {459FE289-A8E1-4BEC-9113-FC42B9CE9436}.Release|Any CPU.Build.0 = Release|Any CPU 39 | {15A2D4B9-BD52-411F-96E9-2BFACFEC4579}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 40 | {15A2D4B9-BD52-411F-96E9-2BFACFEC4579}.Debug|Any CPU.Build.0 = Debug|Any CPU 41 | {15A2D4B9-BD52-411F-96E9-2BFACFEC4579}.Release|Any CPU.ActiveCfg = Release|Any CPU 42 | {15A2D4B9-BD52-411F-96E9-2BFACFEC4579}.Release|Any CPU.Build.0 = Release|Any CPU 43 | {20B17F7E-11C3-4E46-9E79-DE9A3B25B640}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 44 | {20B17F7E-11C3-4E46-9E79-DE9A3B25B640}.Debug|Any CPU.Build.0 = Debug|Any CPU 45 | {20B17F7E-11C3-4E46-9E79-DE9A3B25B640}.Release|Any CPU.ActiveCfg = Release|Any CPU 46 | {20B17F7E-11C3-4E46-9E79-DE9A3B25B640}.Release|Any CPU.Build.0 = Release|Any CPU 47 | {EC24D98A-2776-400D-8494-7B8C954EAF84}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 48 | {EC24D98A-2776-400D-8494-7B8C954EAF84}.Debug|Any CPU.Build.0 = Debug|Any CPU 49 | {EC24D98A-2776-400D-8494-7B8C954EAF84}.Release|Any CPU.ActiveCfg = Release|Any CPU 50 | {EC24D98A-2776-400D-8494-7B8C954EAF84}.Release|Any CPU.Build.0 = Release|Any CPU 51 | {4398D445-4BE7-4671-83C1-BA7320BEF1EE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 52 | {4398D445-4BE7-4671-83C1-BA7320BEF1EE}.Debug|Any CPU.Build.0 = Debug|Any CPU 53 | {4398D445-4BE7-4671-83C1-BA7320BEF1EE}.Release|Any CPU.ActiveCfg = Release|Any CPU 54 | {4398D445-4BE7-4671-83C1-BA7320BEF1EE}.Release|Any CPU.Build.0 = Release|Any CPU 55 | {F31F51C2-25D8-4AF1-891C-01087BB7E5EC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 56 | {F31F51C2-25D8-4AF1-891C-01087BB7E5EC}.Debug|Any CPU.Build.0 = Debug|Any CPU 57 | {F31F51C2-25D8-4AF1-891C-01087BB7E5EC}.Release|Any CPU.ActiveCfg = Release|Any CPU 58 | {F31F51C2-25D8-4AF1-891C-01087BB7E5EC}.Release|Any CPU.Build.0 = Release|Any CPU 59 | EndGlobalSection 60 | GlobalSection(SolutionProperties) = preSolution 61 | HideSolutionNode = FALSE 62 | EndGlobalSection 63 | GlobalSection(ExtensibilityGlobals) = postSolution 64 | SolutionGuid = {5339D678-7325-4FD8-A49D-47B54E59D8E6} 65 | EndGlobalSection 66 | EndGlobal 67 | -------------------------------------------------------------------------------- /AudioVisualizer/AudioVisualizer.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | WinExe 5 | net6.0-windows 6 | enable 7 | true 8 | enable 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /AudioVisualizer/MainWindow.Designer.cs: -------------------------------------------------------------------------------- 1 | namespace AudioVisualizer 2 | { 3 | partial class MainWindow 4 | { 5 | /// 6 | /// Required designer variable. 7 | /// 8 | private System.ComponentModel.IContainer components = null; 9 | 10 | /// 11 | /// Clean up any resources being used. 12 | /// 13 | /// true if managed resources should be disposed; otherwise, false. 14 | protected override void Dispose(bool disposing) 15 | { 16 | if (disposing && (components != null)) 17 | { 18 | components.Dispose(); 19 | } 20 | base.Dispose(disposing); 21 | } 22 | 23 | #region Windows Form Designer generated code 24 | 25 | /// 26 | /// Required method for Designer support - do not modify 27 | /// the contents of this method with the code editor. 28 | /// 29 | private void InitializeComponent() 30 | { 31 | this.components = new System.ComponentModel.Container(); 32 | this.dataTimer = new System.Windows.Forms.Timer(this.components); 33 | this.drawingPanel = new System.Windows.Forms.Panel(); 34 | this.drawingTimer = new System.Windows.Forms.Timer(this.components); 35 | this.SuspendLayout(); 36 | // 37 | // dataTimer 38 | // 39 | this.dataTimer.Interval = 30; 40 | this.dataTimer.Tick += new System.EventHandler(this.DataTimer_Tick); 41 | // 42 | // drawingPanel 43 | // 44 | this.drawingPanel.Dock = System.Windows.Forms.DockStyle.Fill; 45 | this.drawingPanel.Location = new System.Drawing.Point(0, 0); 46 | this.drawingPanel.Name = "drawingPanel"; 47 | this.drawingPanel.Size = new System.Drawing.Size(800, 450); 48 | this.drawingPanel.TabIndex = 0; 49 | this.drawingPanel.MouseDoubleClick += new System.Windows.Forms.MouseEventHandler(this.DrawingPanel_MouseDoubleClick); 50 | // 51 | // drawingTimer 52 | // 53 | this.drawingTimer.Interval = 30; 54 | this.drawingTimer.Tick += new System.EventHandler(this.DrawingTimer_Tick); 55 | // 56 | // MainWindow 57 | // 58 | this.AutoScaleDimensions = new System.Drawing.SizeF(10F, 25F); 59 | this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; 60 | this.BackColor = System.Drawing.Color.Black; 61 | this.ClientSize = new System.Drawing.Size(800, 450); 62 | this.Controls.Add(this.drawingPanel); 63 | this.Name = "MainWindow"; 64 | this.Text = "Music Visualizer"; 65 | this.FormClosed += new System.Windows.Forms.FormClosedEventHandler(this.MainWindow_FormClosed); 66 | this.Load += new System.EventHandler(this.MainWindow_Load); 67 | this.ResumeLayout(false); 68 | 69 | } 70 | 71 | #endregion 72 | 73 | private System.Windows.Forms.Timer dataTimer; 74 | private Panel drawingPanel; 75 | private System.Windows.Forms.Timer drawingTimer; 76 | } 77 | } -------------------------------------------------------------------------------- /AudioVisualizer/MainWindow.cs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SlimeNull/AudioVisualizer/538f67443e26d126d9665b9f3f0c083ff0544ebb/AudioVisualizer/MainWindow.cs -------------------------------------------------------------------------------- /AudioVisualizer/MainWindow.resx: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | text/microsoft-resx 50 | 51 | 52 | 2.0 53 | 54 | 55 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 56 | 57 | 58 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 59 | 60 | 61 | 17, 17 62 | 63 | 64 | 197, 17 65 | 66 | -------------------------------------------------------------------------------- /AudioVisualizer/Program.cs: -------------------------------------------------------------------------------- 1 | namespace AudioVisualizer 2 | { 3 | internal static class Program 4 | { 5 | /// 6 | /// The main entry point for the application. 7 | /// 8 | [STAThread] 9 | static void Main() 10 | { 11 | // To customize application configuration such as set high DPI settings or default font, 12 | // see https://aka.ms/applicationconfiguration. 13 | ApplicationConfiguration.Initialize(); 14 | Application.Run(new MainWindow()); 15 | } 16 | } 17 | } -------------------------------------------------------------------------------- /AudioVisualizerDx/AudioVisualizerDx.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | WinExe 5 | net6.0-windows 6 | enable 7 | true 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /AudioVisualizerDx/MainWindow.Designer.cs: -------------------------------------------------------------------------------- 1 | using System.Windows.Forms; 2 | 3 | namespace AudioVisualizerDx 4 | { 5 | partial class MainWindow 6 | { 7 | /// 8 | /// Required designer variable. 9 | /// 10 | private System.ComponentModel.IContainer components = null; 11 | 12 | /// 13 | /// Clean up any resources being used. 14 | /// 15 | /// true if managed resources should be disposed; otherwise, false. 16 | protected override void Dispose(bool disposing) 17 | { 18 | if (disposing && (components != null)) 19 | { 20 | components.Dispose(); 21 | } 22 | base.Dispose(disposing); 23 | } 24 | 25 | #region Windows Form Designer generated code 26 | 27 | /// 28 | /// Required method for Designer support - do not modify 29 | /// the contents of this method with the code editor. 30 | /// 31 | private void InitializeComponent() 32 | { 33 | this.components = new System.ComponentModel.Container(); 34 | this.drawingPanel = new System.Windows.Forms.Panel(); 35 | this.dataTimer = new System.Windows.Forms.Timer(this.components); 36 | this.drawingTimer = new System.Windows.Forms.Timer(this.components); 37 | this.SuspendLayout(); 38 | // 39 | // drawingPanel 40 | // 41 | this.drawingPanel.Dock = System.Windows.Forms.DockStyle.Fill; 42 | this.drawingPanel.Location = new System.Drawing.Point(0, 0); 43 | this.drawingPanel.Name = "drawingPanel"; 44 | this.drawingPanel.Size = new System.Drawing.Size(1002, 664); 45 | this.drawingPanel.TabIndex = 0; 46 | // 47 | // dataTimer 48 | // 49 | this.dataTimer.Interval = 30; 50 | this.dataTimer.Tick += new System.EventHandler(this.DataTimer_Tick); 51 | // 52 | // drawingTimer 53 | // 54 | this.drawingTimer.Interval = 30; 55 | this.drawingTimer.Tick += new System.EventHandler(this.DrawingTimer_Tick); 56 | // 57 | // MainWindow 58 | // 59 | this.AutoScaleDimensions = new System.Drawing.SizeF(10F, 25F); 60 | this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; 61 | this.BackColor = System.Drawing.Color.Black; 62 | this.ClientSize = new System.Drawing.Size(1002, 664); 63 | this.Controls.Add(this.drawingPanel); 64 | this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedSingle; 65 | this.MaximizeBox = false; 66 | this.Name = "MainWindow"; 67 | this.Text = "Music Visualizer (DX)"; 68 | this.FormClosed += new System.Windows.Forms.FormClosedEventHandler(this.MainWindow_FormClosed); 69 | this.Load += new System.EventHandler(this.MainWindow_Load); 70 | this.ResumeLayout(false); 71 | 72 | } 73 | 74 | #endregion 75 | 76 | private Panel drawingPanel; 77 | private System.Windows.Forms.Timer dataTimer; 78 | private System.Windows.Forms.Timer drawingTimer; 79 | } 80 | } -------------------------------------------------------------------------------- /AudioVisualizerDx/MainWindow.cs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SlimeNull/AudioVisualizer/538f67443e26d126d9665b9f3f0c083ff0544ebb/AudioVisualizerDx/MainWindow.cs -------------------------------------------------------------------------------- /AudioVisualizerDx/MainWindow.resx: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | text/microsoft-resx 50 | 51 | 52 | 2.0 53 | 54 | 55 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 56 | 57 | 58 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 59 | 60 | 61 | 17, 17 62 | 63 | 64 | 131, 17 65 | 66 | -------------------------------------------------------------------------------- /AudioVisualizerDx/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Windows.Forms; 3 | 4 | namespace AudioVisualizerDx 5 | { 6 | internal static class Program 7 | { 8 | /// 9 | /// The main entry point for the application. 10 | /// 11 | [STAThread] 12 | static void Main() 13 | { 14 | // To customize application configuration such as set high DPI settings or default font, 15 | // see https://aka.ms/applicationconfiguration. 16 | ApplicationConfiguration.Initialize(); 17 | Application.Run(new MainWindow()); 18 | } 19 | } 20 | } -------------------------------------------------------------------------------- /AudioVisualizerDxDesktop/AudioVisualizerDxDesktop.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | WinExe 5 | net6.0-windows 6 | enable 7 | true 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /AudioVisualizerDxDesktop/MainWindow.Designer.cs: -------------------------------------------------------------------------------- 1 | using System.Windows.Forms; 2 | 3 | namespace AudioVisualizerDx 4 | { 5 | partial class MainWindow 6 | { 7 | /// 8 | /// Required designer variable. 9 | /// 10 | private System.ComponentModel.IContainer components = null; 11 | 12 | /// 13 | /// Clean up any resources being used. 14 | /// 15 | /// true if managed resources should be disposed; otherwise, false. 16 | protected override void Dispose(bool disposing) 17 | { 18 | if (disposing && (components != null)) 19 | { 20 | components.Dispose(); 21 | } 22 | base.Dispose(disposing); 23 | } 24 | 25 | #region Windows Form Designer generated code 26 | 27 | /// 28 | /// Required method for Designer support - do not modify 29 | /// the contents of this method with the code editor. 30 | /// 31 | private void InitializeComponent() 32 | { 33 | this.components = new System.ComponentModel.Container(); 34 | this.drawingPanel = new System.Windows.Forms.Panel(); 35 | this.dataTimer = new System.Windows.Forms.Timer(this.components); 36 | this.drawingTimer = new System.Windows.Forms.Timer(this.components); 37 | this.SuspendLayout(); 38 | // 39 | // drawingPanel 40 | // 41 | this.drawingPanel.Dock = System.Windows.Forms.DockStyle.Fill; 42 | this.drawingPanel.Location = new System.Drawing.Point(0, 0); 43 | this.drawingPanel.Name = "drawingPanel"; 44 | this.drawingPanel.Size = new System.Drawing.Size(1002, 664); 45 | this.drawingPanel.TabIndex = 0; 46 | // 47 | // dataTimer 48 | // 49 | this.dataTimer.Interval = 30; 50 | this.dataTimer.Tick += new System.EventHandler(this.DataTimer_Tick); 51 | // 52 | // drawingTimer 53 | // 54 | this.drawingTimer.Interval = 30; 55 | this.drawingTimer.Tick += new System.EventHandler(this.DrawingTimer_Tick); 56 | // 57 | // MainWindow 58 | // 59 | this.AutoScaleDimensions = new System.Drawing.SizeF(10F, 25F); 60 | this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; 61 | this.BackColor = System.Drawing.Color.Black; 62 | this.ClientSize = new System.Drawing.Size(1002, 664); 63 | this.Controls.Add(this.drawingPanel); 64 | this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedSingle; 65 | this.MaximizeBox = false; 66 | this.Name = "MainWindow"; 67 | this.Text = "Music Visualizer (DX)"; 68 | this.FormClosed += new System.Windows.Forms.FormClosedEventHandler(this.MainWindow_FormClosed); 69 | this.Load += new System.EventHandler(this.MainWindow_Load); 70 | this.ResumeLayout(false); 71 | 72 | } 73 | 74 | #endregion 75 | 76 | private Panel drawingPanel; 77 | private System.Windows.Forms.Timer dataTimer; 78 | private System.Windows.Forms.Timer drawingTimer; 79 | } 80 | } -------------------------------------------------------------------------------- /AudioVisualizerDxDesktop/MainWindow.cs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SlimeNull/AudioVisualizer/538f67443e26d126d9665b9f3f0c083ff0544ebb/AudioVisualizerDxDesktop/MainWindow.cs -------------------------------------------------------------------------------- /AudioVisualizerDxDesktop/MainWindow.resx: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | text/microsoft-resx 50 | 51 | 52 | 2.0 53 | 54 | 55 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 56 | 57 | 58 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 59 | 60 | 61 | 17, 17 62 | 63 | 64 | 131, 17 65 | 66 | -------------------------------------------------------------------------------- /AudioVisualizerDxDesktop/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Windows.Forms; 3 | 4 | namespace AudioVisualizerDx 5 | { 6 | internal static class Program 7 | { 8 | /// 9 | /// The main entry point for the application. 10 | /// 11 | [STAThread] 12 | static void Main() 13 | { 14 | // To customize application configuration such as set high DPI settings or default font, 15 | // see https://aka.ms/applicationconfiguration. 16 | ApplicationConfiguration.Initialize(); 17 | Application.Run(new MainWindow()); 18 | } 19 | } 20 | } -------------------------------------------------------------------------------- /AudioVisualizerGL/AppWindow.cs: -------------------------------------------------------------------------------- 1 | using LibAudioVisualizer; 2 | using ManagedBass; 3 | using OpenTK; 4 | using OpenTK.Mathematics; 5 | using OpenTK.Windowing.Common; 6 | using OpenTK.Windowing.Desktop; 7 | using System.Diagnostics; 8 | using System.Drawing; 9 | 10 | namespace AudioVisualizerGL 11 | { 12 | 13 | public class AppWindow : GameWindow 14 | { 15 | 16 | 17 | public float BarThickness { get; set; } = 0.1f; 18 | public float CircleRadius { get; set; } = 0.16f; 19 | public Vector3 Color1 { get; set; } = new Vector3(245 / 255f, 3 / 255f, 54 / 255f); 20 | public Vector3 Color2 { get; set; } = new Vector3(23 / 255f, 0 / 255f, 244 / 255f); 21 | 22 | public AppWindow(GameWindowSettings gameWindowSettings, NativeWindowSettings nativeWindowSettings) : base(gameWindowSettings, nativeWindowSettings) 23 | { 24 | } 25 | 26 | protected override void OnRenderFrame(FrameEventArgs args) 27 | { 28 | base.OnRenderFrame(args); 29 | } 30 | } 31 | } -------------------------------------------------------------------------------- /AudioVisualizerGL/AudioVisualizerGL.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | WinExe 5 | latest 6 | net8.0 7 | enable 8 | enable 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /AudioVisualizerGL/BarsObject.cs: -------------------------------------------------------------------------------- 1 | namespace AudioVisualizerGL 2 | { 3 | public class BarsObject : RenderObject, IDisposable 4 | { 5 | private bool _disposedValue; 6 | 7 | public BarsObject() : base(new BarsMesh(), new BarsShader()) 8 | { 9 | 10 | } 11 | 12 | ~BarsObject() 13 | { 14 | // 不要更改此代码。请将清理代码放入“Dispose(bool disposing)”方法中 15 | Dispose(disposing: false); 16 | } 17 | 18 | 19 | 20 | public class BarsMesh : Mesh 21 | { 22 | public BarsMesh() : base( 23 | new float[] 24 | { 25 | // left bar 26 | -1, -1, 0, 27 | 28 | }) 29 | { 30 | 31 | } 32 | } 33 | 34 | public class BarsShader : Shader 35 | { 36 | const string vertexShader = 37 | """ 38 | #version 330 core 39 | layout (location = 0) in vec3 position0; 40 | 41 | void main() 42 | { 43 | gl_Position = vec4(position0, 0); 44 | } 45 | """; 46 | 47 | const string fragmentShader = 48 | """ 49 | #version 330 core 50 | uniform vec3 color1; 51 | uniform vec3 color2; 52 | """; 53 | 54 | public BarsShader() : base( 55 | new StringReader(vertexShader), 56 | new StringReader(fragmentShader)) 57 | { 58 | 59 | } 60 | } 61 | 62 | protected virtual void Dispose(bool disposing) 63 | { 64 | if (!_disposedValue) 65 | { 66 | Shader.Dispose(); 67 | Mesh.Dispose(); 68 | _disposedValue = true; 69 | } 70 | } 71 | 72 | public void Dispose() 73 | { 74 | // 不要更改此代码。请将清理代码放入“Dispose(bool disposing)”方法中 75 | Dispose(disposing: true); 76 | GC.SuppressFinalize(this); 77 | } 78 | } 79 | } -------------------------------------------------------------------------------- /AudioVisualizerGL/Mesh.cs: -------------------------------------------------------------------------------- 1 | using OpenTK.Graphics.OpenGL; 2 | 3 | namespace AudioVisualizerGL 4 | { 5 | public class Mesh : IDisposable 6 | { 7 | int _buffer; 8 | int _vertexArray; 9 | private bool _disposedValue; 10 | 11 | public Mesh(float[] vertices) 12 | { 13 | _buffer = GL.GenBuffer(); 14 | GL.BindBuffer(BufferTarget.ArrayBuffer, _buffer); 15 | GL.BufferData(BufferTarget.ArrayBuffer, vertices.Length * sizeof(float), vertices, BufferUsageHint.StaticDraw); 16 | 17 | _vertexArray = GL.GenVertexArray(); 18 | GL.BindVertexArray(_vertexArray); 19 | 20 | GL.VertexAttribPointer(0, sizeof(float), VertexAttribPointerType.Float, false, sizeof(float) * 3, 0); 21 | GL.EnableVertexAttribArray(0); 22 | } 23 | 24 | public Mesh(float[] vertices, float[] colors) 25 | { 26 | _buffer = GL.GenBuffer(); 27 | GL.BindBuffer(BufferTarget.ArrayBuffer, _buffer); 28 | GL.BufferData(BufferTarget.ArrayBuffer, vertices.Length * sizeof(float), vertices, BufferUsageHint.StaticDraw); 29 | 30 | _vertexArray = GL.GenVertexArray(); 31 | GL.BindVertexArray(_vertexArray); 32 | 33 | GL.VertexAttribPointer(0, sizeof(float), VertexAttribPointerType.Float, false, sizeof(float) * 6, 0); 34 | GL.EnableVertexAttribArray(0); 35 | } 36 | 37 | 38 | ~Mesh() 39 | { 40 | // 不要更改此代码。请将清理代码放入“Dispose(bool disposing)”方法中 41 | Dispose(disposing: false); 42 | } 43 | 44 | public virtual void Use() 45 | { 46 | GL.BindVertexArray(_vertexArray); 47 | } 48 | 49 | protected virtual void Dispose(bool disposing) 50 | { 51 | if (!_disposedValue) 52 | { 53 | GL.DeleteBuffer(_buffer); 54 | GL.DeleteVertexArray(_vertexArray); 55 | _disposedValue = true; 56 | } 57 | } 58 | 59 | public void Dispose() 60 | { 61 | // 不要更改此代码。请将清理代码放入“Dispose(bool disposing)”方法中 62 | Dispose(disposing: true); 63 | GC.SuppressFinalize(this); 64 | } 65 | } 66 | } -------------------------------------------------------------------------------- /AudioVisualizerGL/RenderObject.cs: -------------------------------------------------------------------------------- 1 | namespace AudioVisualizerGL 2 | { 3 | public class RenderObject 4 | { 5 | public RenderObject(Mesh mesh, Shader shader) 6 | { 7 | Mesh = mesh; 8 | Shader = shader; 9 | } 10 | 11 | public Mesh Mesh { get; } 12 | public Shader Shader { get; } 13 | 14 | public virtual void Draw() 15 | { 16 | Mesh.Use(); 17 | Shader.Use(); 18 | } 19 | } 20 | } -------------------------------------------------------------------------------- /AudioVisualizerGL/Shader.cs: -------------------------------------------------------------------------------- 1 | using OpenTK.Graphics.OpenGL; 2 | 3 | namespace AudioVisualizerGL 4 | { 5 | 6 | public class Shader : IDisposable 7 | { 8 | private bool _disposedValue; 9 | 10 | public int Handle { get; set; } 11 | 12 | public Shader( 13 | TextReader vertexShaderSource, 14 | TextReader fragmentShaderSource) 15 | { 16 | string vertexShaderString = vertexShaderSource.ReadToEnd(); 17 | string fragmentShaderString = fragmentShaderSource.ReadToEnd(); 18 | 19 | var vertexShader = GL.CreateShader(ShaderType.VertexShader); 20 | GL.ShaderSource(vertexShader, vertexShaderString); 21 | GL.CompileShader(vertexShader); 22 | GL.GetShader(vertexShader, ShaderParameter.CompileStatus, out int vertexShaderCompileSuccess); 23 | if (vertexShaderCompileSuccess != (int)All.True) 24 | { 25 | string info = GL.GetShaderInfoLog(vertexShader); 26 | throw new Exception($"Failed to compile vertex shader. {info}"); 27 | } 28 | 29 | var fragmentShader = GL.CreateShader(ShaderType.FragmentShader); 30 | GL.ShaderSource(fragmentShader, fragmentShaderString); 31 | GL.CompileShader(fragmentShader); 32 | GL.GetShader(fragmentShader, ShaderParameter.CompileStatus, out int fragmentShaderCompileSuccess); 33 | if (fragmentShaderCompileSuccess != (int)All.True) 34 | { 35 | string info = GL.GetShaderInfoLog(fragmentShader); 36 | throw new Exception($"Failed to compile fragment shader. {info}"); 37 | } 38 | 39 | Handle = GL.CreateProgram(); 40 | GL.AttachShader(Handle, vertexShader); 41 | GL.AttachShader(Handle, fragmentShader); 42 | 43 | GL.LinkProgram(Handle); 44 | GL.GetProgram(Handle, GetProgramParameterName.LinkStatus, out var programLinkSuccess); 45 | if (programLinkSuccess != (int)All.True) 46 | { 47 | string info = GL.GetProgramInfoLog(Handle); 48 | throw new Exception($"Failed to link program. {info}"); 49 | } 50 | 51 | GL.DetachShader(Handle, vertexShader); 52 | GL.DetachShader(Handle, fragmentShader); 53 | GL.DeleteShader(vertexShader); 54 | GL.DeleteShader(fragmentShader); 55 | } 56 | ~Shader() 57 | { 58 | // 不要更改此代码。请将清理代码放入“Dispose(bool disposing)”方法中 59 | Dispose(disposing: false); 60 | } 61 | 62 | public virtual void Use() 63 | { 64 | GL.UseProgram(Handle); 65 | } 66 | 67 | protected virtual void Dispose(bool disposing) 68 | { 69 | if (!_disposedValue) 70 | { 71 | if (disposing) 72 | { 73 | GL.DeleteProgram(Handle); 74 | } 75 | 76 | // TODO: 释放未托管的资源(未托管的对象)并重写终结器 77 | // TODO: 将大型字段设置为 null 78 | _disposedValue = true; 79 | } 80 | } 81 | 82 | public void Dispose() 83 | { 84 | // 不要更改此代码。请将清理代码放入“Dispose(bool disposing)”方法中 85 | Dispose(disposing: true); 86 | GC.SuppressFinalize(this); 87 | } 88 | } 89 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 SlimeNull 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /LibAudioVisualizer/LibAudioVisualizer.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net6.0 5 | enable 6 | enable 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /LibAudioVisualizer/Visualizer.cs: -------------------------------------------------------------------------------- 1 | using LibDynamics; 2 | using FftComplex = FftSharp.Complex; 3 | using FftTransform = FftSharp.Transform; 4 | 5 | namespace LibAudioVisualizer 6 | { 7 | public class Visualizer 8 | { 9 | //private int _m; 10 | private double[] _sampleData; 11 | private DateTime _lastTime; 12 | private SecondOrderDynamicsForArray _dynamics; 13 | private int _size; 14 | 15 | /// 16 | /// 采样数据 17 | /// 18 | public double[] SampleData => _sampleData; 19 | 20 | /// 21 | /// 尺寸 22 | /// 23 | public int Size 24 | { 25 | get => _size; set 26 | { 27 | if (!(Get2Flag(value))) 28 | throw new ArgumentException("长度必须是 2 的 n 次幂"); 29 | 30 | _size = value; 31 | _sampleData = new double[value]; 32 | _dynamics = new SecondOrderDynamicsForArray(3, 1, 0, 0, value / 2); 33 | } 34 | } 35 | 36 | public int OutputSize => Size / 2; 37 | 38 | public Visualizer(int size) 39 | { 40 | if (!(Get2Flag(size))) 41 | throw new ArgumentException("大小必须是 2 的 n 次幂", nameof(size)); 42 | 43 | _lastTime = DateTime.Now; 44 | _sampleData = new double[size]; 45 | _dynamics = new SecondOrderDynamicsForArray(3, 1, 0, 0, size / 2); 46 | } 47 | 48 | /// 49 | /// 判断是否是 2 的整数次幂 50 | /// 51 | /// 52 | /// 53 | private bool Get2Flag(int num) 54 | { 55 | if (num < 1) 56 | return false; 57 | return (num & num - 1) == 0; 58 | } 59 | 60 | public void PushSampleData(double[] waveData) 61 | { 62 | if (waveData.Length > _sampleData.Length) 63 | { 64 | Array.Copy(waveData, waveData.Length - _sampleData.Length, _sampleData, 0, _sampleData.Length); 65 | } 66 | else 67 | { 68 | Array.Copy(_sampleData, waveData.Length, _sampleData, 0, _sampleData.Length - waveData.Length); 69 | Array.Copy(waveData, 0, _sampleData, _sampleData.Length - waveData.Length, waveData.Length); 70 | } 71 | } 72 | 73 | public void PushSampleData(double[] waveData, int count) 74 | { 75 | if (count > _sampleData.Length) 76 | { 77 | Array.Copy(waveData, count - _sampleData.Length, _sampleData, 0, _sampleData.Length); 78 | } 79 | else 80 | { 81 | Array.Copy(_sampleData, count, _sampleData, 0, _sampleData.Length - count); 82 | Array.Copy(waveData, 0, _sampleData, _sampleData.Length - count, count); 83 | } 84 | } 85 | 86 | /// 87 | /// 获取频谱数据 (数据已经删去共轭部分) 88 | /// 89 | /// 90 | public double[] GetSpectrumData() 91 | { 92 | DateTime now = DateTime.Now; 93 | double deltaTime = (now - _lastTime).TotalSeconds; 94 | _lastTime = now; 95 | 96 | int len = _sampleData.Length; 97 | FftComplex[] data = new FftComplex[len]; 98 | 99 | for (int i = 0; i < len; i++) 100 | data[i] = new FftComplex(_sampleData[i], 0); 101 | 102 | FftTransform.FFT(data); 103 | 104 | int halfLen = len / 2; 105 | double[] spectrum = new double[halfLen]; // 傅里叶变换结果左右对称, 只需要取一半 106 | for (int i = 0; i < halfLen; i++) 107 | spectrum[i] = data[i].Magnitude / len; 108 | 109 | var window = new FftSharp.Windows.Bartlett(); 110 | window.Create(halfLen); 111 | window.ApplyInPlace(spectrum, false); 112 | 113 | //return spectrum; 114 | return _dynamics.Update(deltaTime, spectrum); 115 | } 116 | 117 | /// 118 | /// 取指定频率内的频谱数据 119 | /// 120 | /// 源频谱数据 121 | /// 采样率 122 | /// 目标频率 123 | /// 124 | public static double[] TakeSpectrumOfFrequency(double[] spectrum, double sampleRate, double frequency) 125 | { 126 | double frequencyPerSampe = sampleRate / spectrum.Length; 127 | 128 | int lengthInNeed = (int)(Math.Min(frequency / frequencyPerSampe, spectrum.Length)); 129 | double[] result = new double[lengthInNeed]; 130 | Array.Copy(spectrum, 0, result, 0, lengthInNeed); 131 | return result; 132 | } 133 | 134 | /// 135 | /// 简单的数据模糊 136 | /// 137 | /// 数据 138 | /// 模糊半径 139 | /// 结果 140 | public static double[] GetBlurry(double[] data, int radius) 141 | { 142 | double[] GetWeights(int radius) 143 | { 144 | double Gaussian(double x) => Math.Pow(Math.E, (-4 * x * x)); // 憨批高斯函数 145 | 146 | int len = 1 + radius * 2; // 长度 147 | int end = len - 1; // 最后的索引 148 | double radiusF = (double)radius; // 半径浮点数 149 | double[] weights = new double[len]; // 权重 150 | 151 | for (int i = 0; i <= radius; i++) // 先把右边的权重算出来 152 | weights[radius + i] = Gaussian(i / radiusF); 153 | for (int i = 0; i < radius; i++) // 把右边的权重拷贝到左边 154 | weights[i] = weights[end - i]; 155 | 156 | double total = weights.Sum(); 157 | for (int i = 0; i < len; i++) // 使权重合为 0 158 | weights[i] = weights[i] / total; 159 | 160 | return weights; 161 | } 162 | 163 | void ApplyWeights(double[] buffer, double[] weights) 164 | { 165 | int len = buffer.Length; 166 | for (int i = 0; i < len; i++) 167 | buffer[i] = buffer[i] * weights[i]; 168 | } 169 | 170 | 171 | double[] weights = GetWeights(radius); 172 | double[] buffer = new double[1 + radius * 2]; 173 | 174 | double[] result = new double[data.Length]; 175 | if (data.Length < radius) 176 | { 177 | Array.Fill(result, data.Average()); 178 | return result; 179 | } 180 | 181 | 182 | for (int i = 0; i < radius; i++) 183 | { 184 | Array.Fill(buffer, data[i], 0, radius + 1); // 填充缺省 185 | for (int j = 0; j < radius; j++) // 186 | { 187 | buffer[radius + 1 + j] = data[i + j]; 188 | } 189 | 190 | ApplyWeights(buffer, weights); 191 | result[i] = buffer.Sum(); 192 | } 193 | 194 | for (int i = radius; i < data.Length - radius; i++) 195 | { 196 | for (int j = 0; j < radius; j++) // 197 | { 198 | buffer[j] = data[i - j]; 199 | } 200 | 201 | buffer[radius] = data[i]; 202 | 203 | for (int j = 0; j < radius; j++) // 204 | { 205 | buffer[radius + j + 1] = data[i + j]; 206 | } 207 | 208 | ApplyWeights(buffer, weights); 209 | result[i] = buffer.Sum(); 210 | } 211 | 212 | for (int i = data.Length - radius; i < data.Length; i++) 213 | { 214 | Array.Fill(buffer, data[i], 0, radius + 1); // 填充缺省 215 | for (int j = 0; j < radius; j++) // 216 | { 217 | buffer[radius + 1 + j] = data[i - j]; 218 | } 219 | 220 | ApplyWeights(buffer, weights); 221 | result[i] = buffer.Sum(); 222 | } 223 | 224 | return result; 225 | } 226 | } 227 | } -------------------------------------------------------------------------------- /LibDynamics/LibDynamics.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net6.0 5 | enable 6 | enable 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /LibDynamics/SecondOrderDynamics.cs: -------------------------------------------------------------------------------- 1 | namespace LibDynamics 2 | { 3 | public class SecondOrderDynamics 4 | { 5 | private double xp;// previous input 6 | private double y, yd; // state variables 7 | private double _w, _z, _d, k1, k2, k3; // dynamics constants 8 | private double _r; 9 | private double _f; 10 | 11 | /// 12 | /// 频率 13 | /// - 即速度, 单位是赫兹(Hz) 14 | /// - 不会影响输出结果的形状, 会影响 '震荡频率' 15 | /// 16 | public double F 17 | { 18 | get => _f; set 19 | { 20 | _f = value; 21 | InitMotionValues(_f, _z, _r); 22 | } 23 | } 24 | 25 | /// 26 | /// 阻尼
27 | /// - 当为 0 时, 输出将永远震荡不衰减
28 | /// - 当大于 0 小于 1 时, 输出会超出结果, 并逐渐趋于目标
29 | /// - 当为 1 时, 输出的曲线是趋向结果, 并正好在指定频率对应时间内抵达结果
30 | /// - 当大于 1 时, 输出值同样时取向结果, 但速度会更慢, 无法在指定频率对应时间内抵达结果
31 | ///
32 | public double Z 33 | { 34 | get => _z; set 35 | { 36 | _z = value; 37 | InitMotionValues(_f, _z, _r); 38 | } 39 | } 40 | 41 | /// 42 | /// 初始响应 43 | /// - 当为 0 时, 数据需要进行 '加速' 来开始运动
44 | /// - 当为 1 时, 数据会立即开始响应
45 | /// - 当大于 1 时, 输出会因为 '速度过快' 而超出目标结果
46 | /// - 当小于 0 时, 输出会 '预测运动', 即 '抬手动作'. 例如目标是 '加' 时, 输出会先进行 '减', 再进行 '加', 47 | /// - 当运动目标为机械时, 通常取值为 2 48 | ///
49 | public double R 50 | { 51 | get => _r; set 52 | { 53 | _r = value; 54 | InitMotionValues(_f, _z, _r); 55 | } 56 | } 57 | 58 | public SecondOrderDynamics(double f, double z, double r, double x0) 59 | { 60 | //compute constants 61 | InitMotionValues(f, z, r); 62 | 63 | // initialize variables 64 | xp = x0; 65 | y = x0; 66 | yd = 0; 67 | } 68 | 69 | private void InitMotionValues(double f, double z, double r) 70 | { 71 | _w = 2 * Math.PI * f; 72 | _z = z; 73 | _d = _w * Math.Sqrt(Math.Abs(z * z - 1)); 74 | k1 = z / (Math.PI * f); 75 | k2 = 1 / ((2 * Math.PI * f) * (2 * Math.PI * f)); 76 | k3 = r * z / (2 * Math.PI * f); 77 | } 78 | 79 | public double Update(double deltaTime, double x) 80 | { 81 | double xd = (x - xp) / deltaTime; 82 | double k1_stable, k2_stable; 83 | 84 | if (_w * deltaTime < _z) 85 | { 86 | k1_stable = k1; 87 | k2_stable = Math.Max(Math.Max(k2, deltaTime * deltaTime / 2 + deltaTime * k1 / 2), deltaTime * k1); 88 | } 89 | else 90 | { 91 | double t1 = Math.Exp(-_z * _w * deltaTime); 92 | double alpha = 2 * t1 * (_z <= 1 ? Math.Cos(deltaTime * _d) : Math.Cosh(deltaTime * _d)); 93 | double beta = t1 * t1; 94 | double t2 = deltaTime / (1 + beta - alpha); 95 | k1_stable = (1 - beta) * t2; 96 | k2_stable = deltaTime * t2; 97 | } 98 | 99 | y = y + deltaTime * yd; 100 | yd = yd + deltaTime * (x + k3 * xd - y - k1_stable * yd) / k2_stable; 101 | 102 | xp = x; 103 | return y; 104 | } 105 | } 106 | } -------------------------------------------------------------------------------- /LibDynamics/SecondOrderDynamicsForArray.cs: -------------------------------------------------------------------------------- 1 | namespace LibDynamics 2 | { 3 | public class SecondOrderDynamicsForArray 4 | { 5 | private double[] xps, xds;// previous input 6 | private double[] ys, yds; // state variables 7 | private double _w, _z, _d, k1, k2, k3; // dynamics constants 8 | private double _r; 9 | private double _f; 10 | 11 | /// 12 | /// 频率 13 | /// - 即速度, 单位是赫兹(Hz) 14 | /// - 不会影响输出结果的形状, 会影响 '震荡频率' 15 | /// 16 | public double F 17 | { 18 | get => _f; set 19 | { 20 | _f = value; 21 | InitMotionValues(_f, _z, _r); 22 | } 23 | } 24 | 25 | /// 26 | /// 阻尼
27 | /// - 当为 0 时, 输出将永远震荡不衰减
28 | /// - 当大于 0 小于 1 时, 输出会超出结果, 并逐渐趋于目标
29 | /// - 当为 1 时, 输出的曲线是趋向结果, 并正好在指定频率对应时间内抵达结果
30 | /// - 当大于 1 时, 输出值同样时取向结果, 但速度会更慢, 无法在指定频率对应时间内抵达结果
31 | ///
32 | public double Z 33 | { 34 | get => _z; set 35 | { 36 | _z = value; 37 | InitMotionValues(_f, _z, _r); 38 | } 39 | } 40 | 41 | /// 42 | /// 初始响应 43 | /// - 当为 0 时, 数据需要进行 '加速' 来开始运动
44 | /// - 当为 1 时, 数据会立即开始响应
45 | /// - 当大于 1 时, 输出会因为 '速度过快' 而超出目标结果
46 | /// - 当小于 0 时, 输出会 '预测运动', 即 '抬手动作'. 例如目标是 '加' 时, 输出会先进行 '减', 再进行 '加', 47 | /// - 当运动目标为机械时, 通常取值为 2 48 | ///
49 | public double R 50 | { 51 | get => _r; set 52 | { 53 | _r = value; 54 | InitMotionValues(_f, _z, _r); 55 | } 56 | } 57 | 58 | /// 59 | /// 60 | /// 61 | /// 62 | /// 63 | /// 64 | /// 65 | /// Array size 66 | public SecondOrderDynamicsForArray(double f, double z, double r, double x0, int size) 67 | { 68 | //compute constants 69 | InitMotionValues(f, z, r); 70 | 71 | // initialize variables 72 | xps = new double[size]; 73 | ys = new double[size]; 74 | 75 | xds = new double[size]; 76 | yds = new double[size]; 77 | 78 | Array.Fill(xps, x0); 79 | Array.Fill(ys, x0); 80 | } 81 | 82 | private void InitMotionValues(double f, double z, double r) 83 | { 84 | _w = 2 * Math.PI * f; 85 | _z = z; 86 | _d = _w * Math.Sqrt(Math.Abs(z * z - 1)); 87 | k1 = z / (Math.PI * f); 88 | k2 = 1 / ((2 * Math.PI * f) * (2 * Math.PI * f)); 89 | k3 = r * z / (2 * Math.PI * f); 90 | } 91 | 92 | public double[] Update(double deltaTime, double[] xs) 93 | { 94 | if (xs.Length != xps.Length) 95 | throw new ArgumentException(); 96 | 97 | for (int i = 0; i < xds.Length; i++) 98 | xds[i] = (xs[i] - xps[i]) / deltaTime; 99 | double k1_stable, k2_stable; 100 | if (_w * deltaTime < _z) 101 | { 102 | k1_stable = k1; 103 | k2_stable = Math.Max(Math.Max(k2, deltaTime * deltaTime / 2 + deltaTime * k1 / 2), deltaTime * k1); 104 | } 105 | else 106 | { 107 | double t1 = Math.Exp(-_z * _w * deltaTime); 108 | double alpha = 2 * t1 * (_z <= 1 ? Math.Cos(deltaTime * _d) : Math.Cosh(deltaTime * _d)); 109 | double beta = t1 * t1; 110 | double t2 = deltaTime / (1 + beta - alpha); 111 | k1_stable = (1 - beta) * t2; 112 | k2_stable = deltaTime * t2; 113 | } 114 | 115 | for (int i = 0; i < ys.Length; i++) 116 | { 117 | ys[i] = ys[i] + deltaTime * yds[i]; 118 | yds[i] = yds[i] + deltaTime * (xs[i] + k3 * xds[i] - ys[i] - k1_stable * yds[i]) / k2_stable; 119 | } 120 | 121 | for (int i = 0; i < xps.Length; i++) 122 | xps[i] = xs[i]; 123 | return ys; 124 | } 125 | } 126 | } -------------------------------------------------------------------------------- /WpfAudioVisualizer/App.xaml: -------------------------------------------------------------------------------- 1 |  5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /WpfAudioVisualizer/App.xaml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Configuration; 4 | using System.Data; 5 | using System.Linq; 6 | using System.Threading.Tasks; 7 | using System.Windows; 8 | using CommandLine; 9 | using CommandLine.Text; 10 | 11 | namespace WpfAudioVisualizer 12 | { 13 | /// 14 | /// Interaction logic for App.xaml 15 | /// 16 | public partial class App : Application 17 | { 18 | public class AppOptions 19 | { 20 | [Option('f', "full-screen")] 21 | public bool FullScreen { get; set; } 22 | 23 | 24 | [Option(longName: "enable-curve", HelpText = "Enable curve rendering")] 25 | public bool EnableCurve { get; set; } 26 | 27 | [Option(longName: "enable-strips", HelpText = "Enable strips rendering")] 28 | public bool EnableStrips { get; set; } 29 | 30 | [Option(longName: "enable-border", HelpText = "Enable border rendering")] 31 | public bool EnableBorder { get; set; } 32 | 33 | [Option(longName: "enable-circle-strips", HelpText = "Enable circle strips rendering")] 34 | public bool EnableCircleStripsRendering { get; set; } 35 | 36 | 37 | [Option(longName: "disable-curve", HelpText = "Disable curve rendering")] 38 | public bool DisableCurve { get; set; } 39 | [Option(longName: "disable-strips", HelpText = "Disable strips rendering")] 40 | public bool DisableStrips { get; set; } 41 | [Option(longName: "disable-border", HelpText = "Disable border rendering")] 42 | public bool DisableBorder { get; set; } 43 | [Option(longName: "disable-circle-strips", HelpText = "Disable circle strips rendering")] 44 | public bool DisableCircleStripsRendering { get; set; } 45 | 46 | 47 | [Option(longName: "spectrum-size", HelpText = "Set the spectrum size")] 48 | public int? SpectrumSize { get; set; } 49 | [Option(longName: "spectrum-sample-rate", HelpText = "Set the spectrum sampling rate")] 50 | public int? SpectrumSampleRate { get; set; } 51 | [Option(longName: "spectrum-blurry", HelpText = "Set the spectrum blurry")] 52 | public int? SpectrumBlurry { get; set; } 53 | [Option(longName: "spectrum-factor", HelpText = "Set the spectrum factor (scale)")] 54 | public float? SpectrumFactor { get; set; } 55 | 56 | 57 | [Option(longName: "strip-count", HelpText = "Set the strip count")] 58 | public int? StripCount { get; set; } 59 | [Option(longName: "strip-spacing", HelpText = "Set the strip spacing")] 60 | public float? StripSpacing { get; set; } 61 | [Option(longName: "circle-strip-count", HelpText = "Set the circle strip count")] 62 | public int? CircleStripCount { get; set; } 63 | [Option(longName: "circle-strip-spacing", HelpText = "Set the circle strip spacing")] 64 | public float? CircleStripSpacing { get; set; } 65 | [Option(longName: "circle-strip-rotation-speed", HelpText = "Set the circle strip rotation speed")] 66 | public double? CircleStripRotationSpeed { get; set; } 67 | 68 | [Option('o', longName: "opacity", Default = 0.5)] 69 | public double Opacity { get; set; } = 0.5; 70 | } 71 | 72 | protected override void OnStartup(StartupEventArgs e) 73 | { 74 | var parserResult = Parser.Default.ParseArguments(e.Args); 75 | 76 | 77 | parserResult 78 | .WithParsed(o => 79 | { 80 | new MainWindow(o).Show(); 81 | }) 82 | .WithNotParsed(errs => 83 | { 84 | if (!ConsoleUtils.HasConsole) 85 | { 86 | ConsoleUtils.AllocConsole(); 87 | ConsoleUtils.InvalidateOutAndError(); 88 | } 89 | 90 | Console.WriteLine(HelpText.AutoBuild(parserResult)); 91 | }); 92 | 93 | base.OnStartup(e); 94 | } 95 | } 96 | } 97 | 98 | -------------------------------------------------------------------------------- /WpfAudioVisualizer/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Windows; 2 | 3 | [assembly: ThemeInfo( 4 | ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located 5 | //(used if a resource is not found in the page, 6 | // or application resource dictionaries) 7 | ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located 8 | //(used if a resource is not found in the page, 9 | // app, or any theme specific resource dictionaries) 10 | )] 11 | -------------------------------------------------------------------------------- /WpfAudioVisualizer/AudioVisualizerWpf.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | WinExe 5 | net6.0-windows 6 | enable 7 | true 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /WpfAudioVisualizer/ConsoleUtils.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using System.Runtime.InteropServices; 4 | using System.Security; 5 | 6 | namespace WpfAudioVisualizer 7 | { 8 | [SuppressUnmanagedCodeSecurity] 9 | public static class ConsoleUtils 10 | { 11 | public const string Kernel32_DllName = "kernel32.dll"; 12 | 13 | [DllImport(Kernel32_DllName)] 14 | public static extern bool AllocConsole(); 15 | 16 | [DllImport(Kernel32_DllName)] 17 | public static extern bool FreeConsole(); 18 | 19 | [DllImport(Kernel32_DllName)] 20 | public static extern IntPtr GetConsoleWindow(); 21 | 22 | [DllImport(Kernel32_DllName)] 23 | public static extern int GetConsoleOutputCP(); 24 | 25 | public static bool HasConsole 26 | { 27 | get { return GetConsoleWindow() != IntPtr.Zero; } 28 | } 29 | 30 | public static void InvalidateOutAndError() 31 | { 32 | Type type = typeof(System.Console); 33 | 34 | System.Reflection.FieldInfo? _out = type.GetField("_out", 35 | System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.NonPublic); 36 | 37 | System.Reflection.FieldInfo? _error = type.GetField("_error", 38 | System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.NonPublic); 39 | 40 | System.Reflection.MethodInfo? _InitializeStdOutError = type.GetMethod("InitializeStdOutError", 41 | System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.NonPublic); 42 | 43 | _out?.SetValue(null, null); 44 | _error?.SetValue(null, null); 45 | 46 | _InitializeStdOutError?.Invoke(null, new object[] { true }); 47 | } 48 | } 49 | } 50 | 51 | -------------------------------------------------------------------------------- /WpfAudioVisualizer/MainWindow.xaml: -------------------------------------------------------------------------------- 1 |  11 | 12 | 16 | 17 | -------------------------------------------------------------------------------- /WpfAudioVisualizer/MainWindow.xaml.cs: -------------------------------------------------------------------------------- 1 | using CommandLine; 2 | using CommunityToolkit.Mvvm.Input; 3 | using LibAudioVisualizer; 4 | using NAudio.CoreAudioApi; 5 | using NAudio.Wave; 6 | using System; 7 | using System.Linq; 8 | using System.Threading; 9 | using System.Windows; 10 | using System.Windows.Input; 11 | using System.Windows.Media; 12 | using System.Windows.Shapes; 13 | using System.Windows.Shell; 14 | using System.Windows.Threading; 15 | 16 | namespace WpfAudioVisualizer 17 | { 18 | /// 19 | /// Interaction logic for MainWindow.xaml 20 | /// 21 | public partial class MainWindow : Window 22 | { 23 | public MainWindow(App.AppOptions o) 24 | { 25 | DataContext = this; 26 | 27 | InitializeComponent(); 28 | ProcessCommandLine(o); 29 | } 30 | 31 | void ProcessCommandLine(App.AppOptions o) 32 | { 33 | if (o.FullScreen) 34 | { 35 | Topmost = true; 36 | WindowStyle = WindowStyle.None; 37 | ResizeMode = ResizeMode.NoResize; 38 | WindowState = WindowState.Maximized; 39 | Background = Brushes.Transparent; 40 | Opacity = o.Opacity; 41 | 42 | AllowsTransparency = true; 43 | IsHitTestVisible = false; 44 | //WindowChrome.SetWindowChrome(this, new WindowChrome() 45 | //{ 46 | // GlassFrameThickness = new Thickness(-1) 47 | //}); 48 | 49 | 50 | visualizerControl.EnableCurveRendering = false; 51 | visualizerControl.EnableCircleStripsRendering = false; 52 | } 53 | 54 | 55 | if (o.DisableCurve) 56 | visualizerControl.EnableCurveRendering = false; 57 | if (o.DisableStrips) 58 | visualizerControl.EnableStripsRendering = false; 59 | if (o.DisableBorder) 60 | visualizerControl.EnableBorderRendering = false; 61 | if (o.DisableCircleStripsRendering) 62 | visualizerControl.EnableCircleStripsRendering = false; 63 | 64 | 65 | if (o.EnableCurve) 66 | visualizerControl.EnableCurveRendering = true; 67 | if (o.EnableStrips) 68 | visualizerControl.EnableStripsRendering = true; 69 | if (o.EnableBorder) 70 | visualizerControl.EnableBorderRendering = true; 71 | if (o.EnableCircleStripsRendering) 72 | visualizerControl.EnableCircleStripsRendering = true; 73 | 74 | if (o.SpectrumSize.HasValue) 75 | visualizerControl.SpectrumSize = o.SpectrumSize.Value; 76 | if (o.SpectrumSampleRate.HasValue) 77 | visualizerControl.SpectrumSampleRate = o.SpectrumSampleRate.Value; 78 | if (o.SpectrumBlurry.HasValue) 79 | visualizerControl.SpectrumBlurry = o.SpectrumBlurry.Value; 80 | if (o.SpectrumFactor.HasValue) 81 | visualizerControl.SpectrumFactor = o.SpectrumFactor.Value; 82 | 83 | if (o.StripCount.HasValue) 84 | visualizerControl.StripCount = o.StripCount.Value; 85 | if (o.StripSpacing.HasValue) 86 | visualizerControl.StripSpacing = o.StripSpacing.Value; 87 | if (o.CircleStripCount.HasValue) 88 | visualizerControl.CircleStripCount = o.CircleStripCount.Value; 89 | if (o.CircleStripSpacing.HasValue) 90 | visualizerControl.CircleStripSpacing = o.CircleStripSpacing.Value; 91 | if (o.CircleStripRotationSpeed.HasValue) 92 | visualizerControl.CircleStripRotationSpeed = o.CircleStripRotationSpeed.Value; 93 | } 94 | 95 | [RelayCommand] 96 | public void Toggle() 97 | { 98 | visualizerControl.RenderEnabled ^= true; 99 | } 100 | 101 | private void Window_Closed(object sender, EventArgs e) 102 | { 103 | visualizerControl.RenderEnabled = false; 104 | } 105 | 106 | private void Window_Loaded(object sender, RoutedEventArgs e) 107 | { 108 | visualizerControl.RenderEnabled = true; 109 | } 110 | } 111 | } -------------------------------------------------------------------------------- /WpfAudioVisualizer/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "profiles": { 3 | "AudioVisualizerWpf": { 4 | "commandName": "Project", 5 | "commandLineArgs": "-f --opacity 0.3" 6 | } 7 | } 8 | } -------------------------------------------------------------------------------- /WpfAudioVisualizer/Themes/Generic.xaml: -------------------------------------------------------------------------------- 1 | 5 | 6 | 7 | 19 | 20 | -------------------------------------------------------------------------------- /WpfAudioVisualizer/Utilities/ColorUtils.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using System.Windows.Media; 7 | 8 | namespace LibAudioVisualizer.Utilities 9 | { 10 | public static class ColorUtils 11 | { 12 | /// 13 | /// 获取 HSV 中所有的基础颜色 (饱和度和明度均为最大值) 14 | /// 15 | /// 所有的 HSV 基础颜色(共 256 * 6 个, 并且随着索引增加, 颜色也会渐变) 16 | public static Color[] GetAllHsvColors() 17 | { 18 | Color[] result = new Color[256 * 6]; 19 | 20 | for (int i = 0; i <= 255; i++) 21 | { 22 | result[i] = Color.FromArgb(255, 255, (byte)i, 0); 23 | } 24 | 25 | for (int i = 0; i <= 255; i++) 26 | { 27 | result[256 + i] = Color.FromArgb(255, (byte)(255 - i), 255, 0); 28 | } 29 | 30 | for (int i = 0; i <= 255; i++) 31 | { 32 | result[512 + i] = Color.FromArgb(255, 0, 255, (byte)i); 33 | } 34 | 35 | for (int i = 0; i <= 255; i++) 36 | { 37 | result[768 + i] = Color.FromArgb(255, 0, (byte)(255 - i), 255); 38 | } 39 | 40 | for (int i = 0; i <= 255; i++) 41 | { 42 | result[1024 + i] = Color.FromArgb(255, (byte)i, 0, 255); 43 | } 44 | 45 | for (int i = 0; i <= 255; i++) 46 | { 47 | result[1280 + i] = Color.FromArgb(255, 255, 0, (byte)(255 - i)); 48 | } 49 | 50 | return result; 51 | } 52 | 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /WpfAudioVisualizer/VisualizerControl.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Buffers; 3 | using System.Collections.Generic; 4 | using System.ComponentModel; 5 | using System.Linq; 6 | using System.Text; 7 | using System.Threading; 8 | using System.Threading.Tasks; 9 | using System.Windows; 10 | using System.Windows.Controls; 11 | using System.Windows.Data; 12 | using System.Windows.Documents; 13 | using System.Windows.Input; 14 | using System.Windows.Media; 15 | using System.Windows.Media.Converters; 16 | using System.Windows.Media.Imaging; 17 | using System.Windows.Navigation; 18 | using System.Windows.Shapes; 19 | using LibAudioVisualizer; 20 | using LibAudioVisualizer.Utilities; 21 | using NAudio.CoreAudioApi; 22 | using NAudio.Wave; 23 | 24 | namespace WpfAudioVisualizer 25 | { 26 | public class VisualizerControl : Control 27 | { 28 | static VisualizerControl() 29 | { 30 | DefaultStyleKeyProperty.OverrideMetadata(typeof(VisualizerControl), new FrameworkPropertyMetadata(typeof(VisualizerControl))); 31 | } 32 | 33 | public VisualizerControl() 34 | { 35 | _capture = new WasapiLoopbackCapture(); 36 | _visualizer = new Visualizer(512); 37 | _startTime = DateTime.Now; 38 | _doubleArrayPool = ArrayPool.Create(); 39 | _pointArrayPool = ArrayPool.Create(); 40 | 41 | _capture.WaveFormat = WaveFormat.CreateIeeeFloatWaveFormat(8192, 1); 42 | _capture.DataAvailable += CaptureDataAvailable; 43 | } 44 | 45 | ~VisualizerControl() 46 | { 47 | if (_capture.CaptureState == CaptureState.Capturing) 48 | _capture.StopRecording(); 49 | } 50 | 51 | WasapiCapture _capture; 52 | Visualizer _visualizer; 53 | DateTime _startTime; 54 | double[]? _spectrumData; 55 | ArrayPool _doubleArrayPool; 56 | ArrayPool _pointArrayPool; 57 | 58 | static readonly Color[] allColors = 59 | ColorUtils.GetAllHsvColors(); 60 | 61 | 62 | 63 | public int SpectrumSize 64 | { 65 | get { return (int)GetValue(SpectrumSizeProperty); } 66 | set { SetValue(SpectrumSizeProperty, value); } 67 | } 68 | 69 | public int SpectrumSampleRate 70 | { 71 | get { return (int)GetValue(SpectrumSampleRateProperty); } 72 | set { SetValue(SpectrumSampleRateProperty, value); } 73 | } 74 | 75 | public int SpectrumBlurry 76 | { 77 | get { return (int)GetValue(SpectrumBlurryProperty); } 78 | set { SetValue(SpectrumBlurryProperty, value); } 79 | } 80 | 81 | public double SpectrumFactor 82 | { 83 | get { return (double)GetValue(SpectrumFactorProperty); } 84 | set { SetValue(SpectrumFactorProperty, value); } 85 | } 86 | 87 | 88 | 89 | 90 | 91 | public bool IsRendering 92 | { 93 | get { return (bool)GetValue(IsRenderingProperty.DependencyProperty); } 94 | private set { SetValue(IsRenderingProperty, value); } 95 | } 96 | public bool RenderEnabled 97 | { 98 | get { return (bool)GetValue(EnableRenderingProperty); } 99 | set { SetValue(EnableRenderingProperty, value); } 100 | } 101 | 102 | public int RenderInterval 103 | { 104 | get { return (int)GetValue(RenderIntervalProperty); } 105 | set { SetValue(RenderIntervalProperty, value); } 106 | } 107 | 108 | public float ColorTransitionTime 109 | { 110 | get { return (float)GetValue(ColorTransitionTimeProperty); } 111 | set { SetValue(ColorTransitionTimeProperty, value); } 112 | } 113 | 114 | public float ColorGradientOffset 115 | { 116 | get { return (float)GetValue(ColorGradientOffsetProperty); } 117 | set { SetValue(ColorGradientOffsetProperty, value); } 118 | } 119 | 120 | public int StripCount 121 | { 122 | get { return (int)GetValue(StripCountProperty); } 123 | set { SetValue(StripCountProperty, value); } 124 | } 125 | 126 | public float StripSpacing 127 | { 128 | get { return (float)GetValue(StripSpacingProperty); } 129 | set { SetValue(StripSpacingProperty, value); } 130 | } 131 | 132 | public int CircleStripCount 133 | { 134 | get { return (int)GetValue(CircleStripCountProperty); } 135 | set { SetValue(CircleStripCountProperty, value); } 136 | } 137 | 138 | public float CircleStripSpacing 139 | { 140 | get { return (float)GetValue(CircleStripSpacingProperty); } 141 | set { SetValue(CircleStripSpacingProperty, value); } 142 | } 143 | 144 | public double CircleStripRotationSpeed 145 | { 146 | get { return (double)GetValue(CircleStripRotationSpeedProperty); } 147 | set { SetValue(CircleStripRotationSpeedProperty, value); } 148 | } 149 | 150 | 151 | public bool EnableCurveRendering 152 | { 153 | get { return (bool)GetValue(EnableCurveProperty); } 154 | set { SetValue(EnableCurveProperty, value); } 155 | } 156 | 157 | public bool EnableStripsRendering 158 | { 159 | get { return (bool)GetValue(EnableStripsProperty); } 160 | set { SetValue(EnableStripsProperty, value); } 161 | } 162 | 163 | public bool EnableBorderRendering 164 | { 165 | get { return (bool)GetValue(EnableBorderDrawingProperty); } 166 | set { SetValue(EnableBorderDrawingProperty, value); } 167 | } 168 | 169 | public bool EnableCircleStripsRendering 170 | { 171 | get { return (bool)GetValue(EnableCircleStripsRenderingProperty); } 172 | set { SetValue(EnableCircleStripsRenderingProperty, value); } 173 | } 174 | 175 | public static readonly DependencyProperty EnableCurveProperty = 176 | DependencyProperty.Register(nameof(EnableCurveRendering), typeof(bool), typeof(VisualizerControl), new PropertyMetadata(true)); 177 | public static readonly DependencyProperty EnableStripsProperty = 178 | DependencyProperty.Register(nameof(EnableStripsRendering), typeof(bool), typeof(VisualizerControl), new PropertyMetadata(true)); 179 | public static readonly DependencyProperty EnableBorderDrawingProperty = 180 | DependencyProperty.Register(nameof(EnableBorderRendering), typeof(bool), typeof(VisualizerControl), new PropertyMetadata(true)); 181 | public static readonly DependencyProperty EnableCircleStripsRenderingProperty = 182 | DependencyProperty.Register(nameof(EnableCircleStripsRendering), typeof(bool), typeof(VisualizerControl), new PropertyMetadata(true)); 183 | 184 | 185 | 186 | 187 | 188 | public static readonly DependencyProperty SpectrumSizeProperty = 189 | DependencyProperty.Register(nameof(SpectrumSize), typeof(int), typeof(VisualizerControl), new PropertyMetadata(512, SpectrumSizeChanged)); 190 | public static readonly DependencyProperty SpectrumSampleRateProperty = 191 | DependencyProperty.Register(nameof(SpectrumSampleRate), typeof(int), typeof(VisualizerControl), new PropertyMetadata(8192, SpectrumSampleRateChanged)); 192 | public static readonly DependencyProperty SpectrumBlurryProperty = 193 | DependencyProperty.Register(nameof(SpectrumBlurry), typeof(int), typeof(VisualizerControl), new PropertyMetadata(0)); 194 | public static readonly DependencyProperty SpectrumFactorProperty = 195 | DependencyProperty.Register(nameof(SpectrumFactor), typeof(double), typeof(VisualizerControl), new PropertyMetadata(1.0)); 196 | public static readonly DependencyPropertyKey IsRenderingProperty = 197 | DependencyProperty.RegisterReadOnly(nameof(IsRendering), typeof(bool), typeof(VisualizerControl), new PropertyMetadata(false)); 198 | public static readonly DependencyProperty EnableRenderingProperty = 199 | DependencyProperty.Register(nameof(RenderEnabled), typeof(bool), typeof(VisualizerControl), new PropertyMetadata(false, RenderEnableChanged)); 200 | public static readonly DependencyProperty RenderIntervalProperty = 201 | DependencyProperty.Register(nameof(RenderInterval), typeof(int), typeof(VisualizerControl), new PropertyMetadata(10)); 202 | public static readonly DependencyProperty ColorTransitionTimeProperty = 203 | DependencyProperty.Register(nameof(ColorTransitionTime), typeof(float), typeof(VisualizerControl), new PropertyMetadata(30f)); 204 | public static readonly DependencyProperty ColorGradientOffsetProperty = 205 | DependencyProperty.Register(nameof(ColorGradientOffset), typeof(float), typeof(VisualizerControl), new PropertyMetadata(.1f)); 206 | 207 | 208 | 209 | public static readonly DependencyProperty StripCountProperty = 210 | DependencyProperty.Register(nameof(StripCount), typeof(int), typeof(VisualizerControl), new PropertyMetadata(128)); 211 | public static readonly DependencyProperty StripSpacingProperty = 212 | DependencyProperty.Register(nameof(StripSpacing), typeof(float), typeof(VisualizerControl), new PropertyMetadata(.2f)); 213 | public static readonly DependencyProperty CircleStripCountProperty = 214 | DependencyProperty.Register(nameof(CircleStripCount), typeof(int), typeof(VisualizerControl), new PropertyMetadata(128)); 215 | public static readonly DependencyProperty CircleStripSpacingProperty = 216 | DependencyProperty.Register(nameof(CircleStripSpacing), typeof(float), typeof(VisualizerControl), new PropertyMetadata(.2f)); 217 | public static readonly DependencyProperty CircleStripRotationSpeedProperty = 218 | DependencyProperty.Register(nameof(CircleStripRotationSpeed), typeof (double), typeof(VisualizerControl), new PropertyMetadata(.5)); 219 | 220 | 221 | 222 | private static void SpectrumSizeChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) 223 | { 224 | if (d is not VisualizerControl visualizerControl || 225 | e.NewValue is not int spectrumSize) 226 | return; 227 | 228 | if (visualizerControl.IsRendering) 229 | throw new InvalidOperationException($"{nameof(SpectrumSize)} on only be set while not rendering"); 230 | 231 | visualizerControl._visualizer.Size = spectrumSize * 2; 232 | } 233 | 234 | private static void SpectrumSampleRateChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) 235 | { 236 | if (d is not VisualizerControl visualizerControl || 237 | e.NewValue is not int spectrumSampleRate) 238 | return; 239 | 240 | if (visualizerControl.IsRendering) 241 | throw new InvalidOperationException($"{nameof(SpectrumSampleRate)} on only be set while not rendering"); 242 | 243 | visualizerControl._capture.WaveFormat = WaveFormat.CreateIeeeFloatWaveFormat(spectrumSampleRate, 1); 244 | } 245 | 246 | private static void RenderEnableChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) 247 | { 248 | if (d is not VisualizerControl visualizerControl || 249 | e.NewValue is not bool value) 250 | return; 251 | #if DEBUG 252 | if (DesignerProperties.GetIsInDesignMode(d)) 253 | return; 254 | #endif 255 | 256 | if (value) 257 | visualizerControl.StartRenderAsync(); 258 | else 259 | visualizerControl.StopRendering(); 260 | } 261 | 262 | private void CaptureDataAvailable(object? sender, WaveInEventArgs e) 263 | { 264 | int len = e.BytesRecorded / 4; 265 | double[] result = _doubleArrayPool.Rent(len); 266 | 267 | for (int i = 0; i < len; i++) 268 | result[i] = BitConverter.ToSingle(e.Buffer, i * 4); 269 | 270 | _visualizer.PushSampleData(result, len); 271 | 272 | _doubleArrayPool.Return(result); 273 | } 274 | 275 | protected override void OnRender(DrawingContext drawingContext) 276 | { 277 | base.OnRender(drawingContext); 278 | 279 | if (_spectrumData == null) 280 | return; 281 | 282 | TimeSpan elapsedTime = DateTime.Now - _startTime; 283 | 284 | if (EnableCurveRendering) 285 | DrawCurve(drawingContext, _visualizer.SampleData); 286 | 287 | if (EnableStripsRendering) 288 | DrawStrips(drawingContext, _spectrumData); 289 | 290 | if (EnableBorderRendering) 291 | DrawBorder(drawingContext, _spectrumData); 292 | 293 | if (EnableCircleStripsRendering) 294 | DrawCircleStrips(drawingContext, _spectrumData, elapsedTime.TotalSeconds); 295 | } 296 | 297 | private void DrawStrips(DrawingContext drawingContext, double[] spectrumData) 298 | { 299 | int stripCount = StripCount; 300 | double thickness = ActualWidth / StripCount * (1 - StripSpacing); 301 | 302 | if (thickness < 0) 303 | thickness = 1; 304 | 305 | double minY = ActualHeight; 306 | 307 | PathGeometry pathGeometry = new PathGeometry(); 308 | 309 | int end = stripCount - 1; 310 | for (int i = 0; i < stripCount; i++) 311 | { 312 | double value = spectrumData[i * spectrumData.Length / stripCount]; 313 | double y = ActualHeight * (1 - value * 10); 314 | double x = ((double)i / end) * ActualWidth; 315 | 316 | if (y < minY) 317 | minY = y; 318 | 319 | pathGeometry.Figures.Add(new PathFigure() 320 | { 321 | StartPoint = new Point(x, ActualHeight), 322 | Segments = 323 | { 324 | new LineSegment() 325 | { 326 | Point = new Point(x, y) 327 | } 328 | } 329 | }); 330 | } 331 | 332 | GetCurrentColor(out var color1, out var color2); 333 | 334 | GradientBrush brush = new LinearGradientBrush(color1, color2, new Point(0, 1), new Point(0, 0)); 335 | Pen pen = new Pen(brush, thickness); 336 | 337 | drawingContext.DrawGeometry(null, pen, pathGeometry); 338 | } 339 | 340 | private void DrawBorder(DrawingContext drawingContext, double[] spectrumData) 341 | { 342 | double[] bassArea = Visualizer.TakeSpectrumOfFrequency(spectrumData, _capture.WaveFormat.SampleRate, 250); 343 | double bass = bassArea.Average() * 100; 344 | double thickness = ActualWidth / 10 * bass; 345 | 346 | if (thickness <= 0) 347 | return; 348 | 349 | GetCurrentColor(out var color1, out var color2); 350 | color2.A = 0; 351 | 352 | LinearGradientBrush brush1 = new LinearGradientBrush(color1, color2, new Point(0, 0), new Point(0, 1)); 353 | drawingContext.DrawRectangle(brush1, null, new Rect(new Point(0, 0), new Size(ActualWidth, thickness))); 354 | 355 | LinearGradientBrush brush2 = new LinearGradientBrush(color1, color2, new Point(0, 0), new Point(1, 0)); 356 | drawingContext.DrawRectangle(brush2, null, new Rect(new Point(0, 0), new Size(thickness, ActualHeight))); 357 | 358 | LinearGradientBrush brush3 = new LinearGradientBrush(color1, color2, new Point(1, 0), new Point(0, 0)); 359 | drawingContext.DrawRectangle(brush3, null, new Rect(new Point(ActualWidth - thickness, 0), new Size(thickness, ActualHeight))); 360 | 361 | LinearGradientBrush brush4 = new LinearGradientBrush(color1, color2, new Point(0, 1), new Point(0, 0)); 362 | drawingContext.DrawRectangle(brush4, null, new Rect(new Point(0, ActualHeight - thickness), new Size(ActualWidth, thickness))); 363 | } 364 | 365 | private void DrawCircleStrips(DrawingContext drawingContext, double[] spectrumData, double time) 366 | { 367 | double[] bassArea = Visualizer.TakeSpectrumOfFrequency(spectrumData, _capture.WaveFormat.SampleRate, 250); 368 | double bassScale = bassArea.Average() * 100; 369 | double extraScale = Math.Min(ActualWidth, ActualHeight) / 6; 370 | 371 | int stripCount = CircleStripCount; 372 | double xOffset = ActualWidth / 2; 373 | double yOffset = ActualHeight / 2; 374 | double radius = Math.Min(ActualWidth, ActualHeight) / 4 + extraScale * bassScale; 375 | double spacing = CircleStripSpacing; 376 | double rotation = CircleStripRotationSpeed * time % (Math.PI * 2); 377 | double scale = ActualWidth / 6 * 10; 378 | 379 | double rotationAngle = Math.PI / 180 * rotation; 380 | double blockWidth = Math.PI * 2 / stripCount; 381 | double stripWidth = blockWidth * (1 - spacing); 382 | Point[] points = _pointArrayPool.Rent(stripCount); 383 | 384 | for (int i = 0; i < stripCount; i++) 385 | { 386 | double x = blockWidth * i + rotationAngle; 387 | double y = spectrumData[i * spectrumData.Length / stripCount] * scale; 388 | points[i] = new Point(x, y); 389 | } 390 | 391 | double maxHeight = points.Max(v => v.Y); 392 | double outerRadius = radius + maxHeight; 393 | 394 | double gradientStart = radius / outerRadius; 395 | 396 | GetCurrentColor(out var color1, out var color2); 397 | RadialGradientBrush brush = new RadialGradientBrush( 398 | new GradientStopCollection() 399 | { 400 | new GradientStop(color1, 0), 401 | new GradientStop(color1, gradientStart), 402 | new GradientStop(color2, 1), 403 | }); 404 | 405 | PathGeometry pathGeometry = new PathGeometry(); 406 | 407 | for (int i = 0; i < stripCount; i++) 408 | { 409 | Point p = points[i]; 410 | double cosStart = Math.Cos(p.X); 411 | double sinStart = Math.Sin(p.X); 412 | double cosEnd = Math.Cos(p.X + stripWidth); 413 | double sinEnd = Math.Sin(p.X + stripWidth); 414 | 415 | Point 416 | p0 = new Point(cosStart * radius + xOffset, sinStart * radius + yOffset), 417 | p1 = new Point(cosEnd * radius + xOffset, sinEnd * radius + yOffset), 418 | p2 = new Point(cosEnd * (radius + p.Y) + xOffset, sinEnd * (radius + p.Y) + yOffset), 419 | p3 = new Point(cosStart * (radius + p.Y) + xOffset, sinStart * (radius + p.Y) + yOffset); 420 | 421 | pathGeometry.Figures.Add( 422 | new PathFigure() 423 | { 424 | StartPoint = p0, 425 | Segments = 426 | { 427 | new LineSegment() { Point = p1 }, 428 | new LineSegment() { Point = p2 }, 429 | new LineSegment() { Point = p3 }, 430 | }, 431 | }); 432 | } 433 | 434 | _pointArrayPool.Return(points); 435 | drawingContext.DrawGeometry(brush, null, pathGeometry); 436 | } 437 | 438 | private void DrawCurve(DrawingContext drawingContext, double[] spectrumData) 439 | { 440 | double yBase = ActualHeight / 2; 441 | double scale = Math.Min(ActualHeight / 10, 100); 442 | double drawingWidth = ActualWidth; 443 | 444 | Point[] points = new Point[spectrumData.Length]; 445 | for (int i = 0; i < points.Length; i++) 446 | { 447 | double x = i * drawingWidth / points.Length; 448 | double y = spectrumData[i] * scale + yBase; 449 | points[i] = new Point(x, y); 450 | } 451 | 452 | PathFigure figure = new PathFigure(); 453 | figure.StartPoint = points[0]; 454 | for (int i = 0; i < points.Length; i++) 455 | figure.Segments.Add(new LineSegment() { Point = points[i] }); 456 | 457 | PathGeometry pathGeometry = new PathGeometry() 458 | { 459 | Figures = 460 | { 461 | figure 462 | } 463 | }; 464 | 465 | drawingContext.DrawGeometry(null, new Pen(Brushes.Cyan, 1), pathGeometry); 466 | } 467 | 468 | 469 | private async Task RenderLoopAsync(CancellationToken token) 470 | { 471 | IsRendering = true; 472 | _capture.StartRecording(); 473 | 474 | while (true) 475 | { 476 | if (token.IsCancellationRequested) 477 | break; 478 | 479 | _spectrumData = _visualizer.GetSpectrumData(); 480 | 481 | if (SpectrumBlurry is int blurry and not 0) 482 | _spectrumData = Visualizer.GetBlurry(_spectrumData, blurry); 483 | 484 | if (SpectrumFactor is double factor and not 1.0) 485 | for (int i = 0; i < _spectrumData.Length; i++) 486 | _spectrumData[i] *= factor; 487 | 488 | InvalidateVisual(); 489 | 490 | await Task.Delay(RenderInterval); 491 | } 492 | 493 | _capture.StopRecording(); 494 | IsRendering = false; 495 | } 496 | 497 | Task? renderTask; 498 | CancellationTokenSource? cancellation; 499 | 500 | private void StartRenderAsync() 501 | { 502 | if (renderTask != null) 503 | return; 504 | 505 | cancellation = new CancellationTokenSource(); 506 | renderTask = RenderLoopAsync(cancellation.Token); 507 | } 508 | 509 | private void StopRendering() 510 | { 511 | cancellation?.Cancel(); 512 | renderTask = null; 513 | } 514 | 515 | private Color GetColorFromRate(double rate) 516 | { 517 | if (rate < 0) 518 | rate = rate % 1 + 1; 519 | else 520 | rate = rate % 1; 521 | 522 | int maxIndex = allColors.Length - 1; 523 | return allColors[(int)(maxIndex * rate)]; 524 | } 525 | 526 | private void GetCurrentColor(out Color color1, out Color color2) 527 | { 528 | double time = (DateTime.Now - _startTime).TotalSeconds; 529 | double rate = time / ColorTransitionTime; 530 | 531 | color1 = GetColorFromRate(rate); 532 | color2 = GetColorFromRate(rate + ColorGradientOffset); 533 | } 534 | } 535 | } 536 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # 音频可视化器 / AudioVisualizer 2 | 3 | 分别在 GDI+ 和 Direct2D(SharpDX) 上实现了音频可视化 \ 4 | Audio visualization on GDI+ and Direct2D (SharpDX) respectively 5 | 6 | 你可以学到: 如何实现音频可视化, 如何使用 SharpDX 进行 2D 绘图 \ 7 | You can learn: how to implement audio visualization, how to use SharpDX for 2D drawing 8 | 9 | ![preview](res/preview1.png) 10 | 11 | ## 主要逻辑 / Main logic 12 | 13 | ### 音频可视化 / Audio visualization 14 | 15 | - 每一帧对旧数据和新数据进行 "渐变", 值不会立即更新为新值, 而是更新为两值的中间值, 也就是说, 每一次刷新, 数据只会 "移动" 一定比例 \ 16 | Each frame do lerp operation between the old data and the new data, the value is not updated to the new value immediately, but is updated to the intermediate value of the two values, that is, each refresh, the data will only "move" a certain percentage 17 | - 加个窗函数, 遮盖住低频与高频, 主要展示中间频率 \ 18 | Add a window function to cover the low frequency and high frequency, mainly showing the middle frequency 19 | - 将低频区域的值取出来, 单独用来做 "低音" 的视觉效果 \ 20 | Take out the value of the low-frequency area and use it separately for the visual effect of "bass" 21 | 22 | ### WinForm 23 | 24 | - 使用 WinForm 的 Timer 实现在单线程的定时频谱数据获取以及界面刷新 \ 25 | Using WinForm's Timer to achieve timed spectrum data acquisition and interface refresh in a single thread 26 | 27 | ### GDI+ 28 | 29 | - 使用 BufferedGraphics 使绘制时不会闪屏 \ 30 | Use BufferedGraphics to draw without flickering 31 | 32 | ## Enjoy 33 | 34 | 代码注释很完善, 所以, 学起来呀~ \ 35 | The code comments are very perfect, so let's just learn it~ 36 | 37 | ![code](res/code.png) -------------------------------------------------------------------------------- /res/code.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SlimeNull/AudioVisualizer/538f67443e26d126d9665b9f3f0c083ff0544ebb/res/code.png -------------------------------------------------------------------------------- /res/preview1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SlimeNull/AudioVisualizer/538f67443e26d126d9665b9f3f0c083ff0544ebb/res/preview1.png -------------------------------------------------------------------------------- /res/preview2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SlimeNull/AudioVisualizer/538f67443e26d126d9665b9f3f0c083ff0544ebb/res/preview2.png --------------------------------------------------------------------------------