├── .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 | 
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 | 
--------------------------------------------------------------------------------
/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
--------------------------------------------------------------------------------