├── .github
└── workflows
│ ├── benchmark.yaml
│ ├── ci.yaml
│ └── package.yaml
├── .gitignore
├── LICENSE
├── README.md
├── dev
├── FftSharp-demo.zip
├── build
│ └── build-and-publish.bat
├── data
│ ├── fft.txt
│ ├── fftDB.txt
│ ├── fftFreq.txt
│ ├── fftMag.txt
│ ├── fftPhase.txt
│ ├── fftReal.txt
│ └── sample.txt
├── demo.png
├── icon
│ ├── v1
│ │ └── icon-v1.svg
│ ├── v2
│ │ ├── icon-24.png
│ │ ├── icon-v2.svg
│ │ ├── icon.ico
│ │ └── icon.png
│ └── v3
│ │ ├── fftsharp-icon-128.png
│ │ ├── fftsharp-icon-24.png
│ │ ├── fftsharp-icon-256.png
│ │ └── fftsharp-icon.svg
├── imp
│ ├── oakley
│ │ ├── complex.cs
│ │ ├── dft.cs
│ │ ├── fft.cs
│ │ └── main.cs
│ ├── ooura
│ │ ├── fft4g.c
│ │ ├── fft4g.f
│ │ ├── fft4g_h.c
│ │ ├── fft8g.c
│ │ ├── fft8g.f
│ │ ├── fft8g_h.c
│ │ ├── fftsg.c
│ │ ├── fftsg.f
│ │ ├── fftsg_h.c
│ │ ├── readme.md
│ │ ├── sample1
│ │ │ ├── Makefile
│ │ │ ├── Makefile.f77
│ │ │ ├── testxg.c
│ │ │ ├── testxg.f
│ │ │ └── testxg_h.c
│ │ └── sample2
│ │ │ ├── Makefile
│ │ │ ├── Makefile.pth
│ │ │ └── pi_fft.c
│ ├── readme.md
│ ├── readme.txt
│ └── sparky
│ │ ├── complex.cs
│ │ └── fftifft.cs
├── lowpass.png
├── mel-scale.png
├── microphone-fft.gif
├── notes
│ └── readme.md
├── python
│ ├── bessel.py
│ ├── fft.py
│ ├── fftshift.py
│ └── window-functions
│ │ └── make-window-tests.py
├── quickstart
│ ├── audio-windowed.png
│ ├── audio.png
│ ├── fft-windowed.png
│ ├── fft.png
│ ├── periodogram.png
│ ├── time-series.png
│ └── windows.png
├── screenshot.png
├── spectrogram.png
└── windows.png
└── src
├── FftSharp.Benchmark
├── BenchmarkLoadData.cs
├── Benchmarking.md
├── BluesteinSizeBenchmark.cs
├── FftBenchmark.cs
├── FftSharp.Benchmark.csproj
├── Program.cs
└── sample.txt
├── FftSharp.Demo
├── FftSharp.Demo.csproj
├── FormAudio.Designer.cs
├── FormAudio.cs
├── FormAudio.resx
├── FormMenu.Designer.cs
├── FormMenu.cs
├── FormMenu.resx
├── FormMicrophone.Designer.cs
├── FormMicrophone.cs
├── FormMicrophone.resx
├── FormQuickstart.Designer.cs
├── FormQuickstart.cs
├── FormQuickstart.resx
├── FormWindowInspector.Designer.cs
├── FormWindowInspector.cs
├── FormWindowInspector.resx
├── Program.cs
├── Properties
│ ├── AssemblyInfo.cs
│ ├── Resources.Designer.cs
│ ├── Resources.resx
│ ├── Settings.Designer.cs
│ └── Settings.settings
├── icon.ico
└── packages.config
├── FftSharp.Tests
├── .editorconfig
├── ExperimentalTests.cs
├── FftFreqTests.cs
├── FftSharp.Tests.csproj
├── FftTests.cs
├── Filter.cs
├── Inverse.cs
├── LoadData.cs
├── MelTests.cs
├── Padding.cs
├── PeakFrequencyDetection.cs
├── Quickstart.cs
├── Readme.cs
├── TestTools.cs
├── Transform.cs
├── Value.cs
├── VsNumpy.cs
├── Window.cs
└── WindowValueTests.cs
├── FftSharp.sln
├── FftSharp
├── .editorconfig
├── Bluestein.cs
├── DFT.cs
├── Extensions.cs
├── FFT.cs
├── FftOperations.cs
├── FftSharp.csproj
├── Filter.cs
├── IWindow.cs
├── Mel.cs
├── Pad.cs
├── README.md
├── SampleData.cs
├── Window.cs
├── Windows
│ ├── Bartlett.cs
│ ├── Blackman.cs
│ ├── Cosine.cs
│ ├── FlatTop.cs
│ ├── Hamming.cs
│ ├── HammingPeriodic.cs
│ ├── Hanning.cs
│ ├── HanningPeriodic.cs
│ ├── Kaiser.cs
│ ├── Rectangular.cs
│ ├── Tukey.cs
│ └── Welch.cs
└── icon.png
└── autoformat.bat
/.github/workflows/benchmark.yaml:
--------------------------------------------------------------------------------
1 | name: Benchmark
2 |
3 | on:
4 | workflow_dispatch:
5 |
6 | jobs:
7 | benchmark:
8 | name: Run Benchmarks
9 | runs-on: windows-latest
10 | strategy:
11 | matrix:
12 | dotnet-version: [8.x, 9.x]
13 | steps:
14 | - uses: actions/checkout@v4
15 | - uses: actions/setup-dotnet@v4
16 | with:
17 | dotnet-version: ${{matrix.dotnet-version}}
18 | dotnet-quality: "preview"
19 | - name: Run benchmark
20 | working-directory: src/FftSharp.Benchmark
21 | run: dotnet run --exporters json --filter '*'
--------------------------------------------------------------------------------
/.github/workflows/ci.yaml:
--------------------------------------------------------------------------------
1 | name: CI/CD
2 |
3 | on:
4 | workflow_dispatch:
5 | push:
6 | branches:
7 | - main
8 | pull_request:
9 | types: [opened, synchronize, reopened]
10 |
11 | jobs:
12 | build:
13 | name: Build and Test
14 | runs-on: windows-latest
15 | steps:
16 | - name: 🛒 Checkout
17 | uses: actions/checkout@v4
18 | - name: ✨ Setup .NET
19 | uses: actions/setup-dotnet@v4
20 | - name: 🚚 Restore
21 | run: dotnet restore src
22 | - name: 🛠️ Build
23 | run: dotnet build src --configuration Release --no-restore
24 | - name: 🧪 Test
25 | run: dotnet test src --configuration Release --no-build
--------------------------------------------------------------------------------
/.github/workflows/package.yaml:
--------------------------------------------------------------------------------
1 | name: Package
2 |
3 | on:
4 | workflow_dispatch:
5 |
6 | jobs:
7 | build:
8 | name: Package and Deploy
9 | runs-on: windows-latest
10 | steps:
11 | - name: 🛒 Checkout
12 | uses: actions/checkout@v4
13 | - name: ✨ Setup .NET
14 | uses: actions/setup-dotnet@v4
15 | - name: 🚚 Restore
16 | run: dotnet restore src
17 | - name: 🛠️ Build
18 | run: dotnet build src --configuration Release --no-restore
19 | - name: 🧪 Test
20 | run: dotnet test src --configuration Release --no-build
21 | - name: 📦 Pack
22 | run: dotnet pack src --configuration Release
23 | - name: 🔑 Secret
24 | uses: nuget/setup-nuget@v1
25 | with:
26 | nuget-api-key: ${{ secrets.NUGET_API_KEY }}
27 | - name: 🚀 Deploy
28 | run: nuget push "src\FftSharp\bin\Release\*.nupkg" -SkipDuplicate -Source https://api.nuget.org/v3/index.json
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 Scott W Harden
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 |
--------------------------------------------------------------------------------
/dev/FftSharp-demo.zip:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/swharden/FftSharp/3f5158f7ab146c8fb651028e8dce67407b3ded81/dev/FftSharp-demo.zip
--------------------------------------------------------------------------------
/dev/build/build-and-publish.bat:
--------------------------------------------------------------------------------
1 | @echo off
2 |
3 | :: this script requires nuget.exe to be in this folder
4 | :: https://www.nuget.org/downloads
5 |
6 | echo.
7 | echo ### DELETING OLD PACKAGES ###
8 | DEL *.nupkg
9 | DEL *.snupkg
10 |
11 | echo.
12 | echo ### DELETING RELEASE FOLDERS ###
13 | RMDIR ..\..\src\FftSharp\bin\Release /S /Q
14 |
15 | echo.
16 | echo ### CLEANING SOLUTION ###
17 | dotnet clean ..\..\src\FftSharp.sln --verbosity quiet --configuration Release
18 |
19 | echo.
20 | echo ### REBUILDING SOLUTION ###
21 | dotnet build ..\..\src\FftSharp\FftSharp.csproj --verbosity quiet --configuration Release
22 |
23 | echo.
24 | echo ### COPYING PACKAGE HERE ###
25 | copy ..\..\src\FftSharp\bin\Release\*.nupkg .\
26 | copy ..\..\src\FftSharp\bin\Release\*.snupkg .\
27 |
28 | echo ### RUNNING TESTS ###
29 | dotnet test ..\..\src\FftSharp.sln --configuration Release
30 |
31 | echo.
32 | echo WARNING! This script will UPLOAD packages to nuget.org
33 | echo.
34 | echo press ENTER 3 times to proceed...
35 | pause
36 | pause
37 | pause
38 |
39 | echo.
40 | echo ### UPDATING NUGET ###
41 | nuget update -self
42 |
43 | echo.
44 | echo ### UPLOADING TO NUGET ###
45 | nuget push *.nupkg -Source https://api.nuget.org/v3/index.json
46 |
47 | echo.
48 | pause
--------------------------------------------------------------------------------
/dev/data/fftDB.txt:
--------------------------------------------------------------------------------
1 | -17.09684910375939
2 | -23.880662276219727
3 | -23.466115068157364
4 | -26.660408770164143
5 | -31.918814324409787
6 | -31.74702354970204
7 | -21.839435640647128
8 | -23.617452176495682
9 | -22.334411900357367
10 | -27.937685557428743
11 | -25.116322848833686
12 | -22.507280181701617
13 | -25.327227264437177
14 | -21.399595653339908
15 | -21.64312684906786
16 | -21.508367603089212
17 | -18.23427936085187
18 | -17.238083181695192
19 | -14.257708328083767
20 | -11.77471118704246
21 | -7.522090155992556
22 | 4.307096195563382
23 | -1.8630262654256722
24 | -9.309836148399139
25 | -13.760946171026413
26 | -19.73608050948537
27 | -19.11137013306676
28 | -20.11531239651578
29 | -26.351201615616823
30 | -25.32143041738796
31 | -28.679411582653422
32 | -26.988701320999333
33 | -25.490584539349847
34 | -28.408666366532508
35 | -31.14168562061154
36 | -28.304588223644032
37 | -35.46792703480869
38 | -34.70241296498797
39 | -34.21956090185087
40 | -26.94007868738541
41 | -31.23597895625039
42 | -31.15627810140849
43 | -31.430021762465813
44 | -25.32924306001636
45 | -29.079320003683588
46 | -30.70194309838805
47 | -32.19759191425953
48 | -33.60594819202337
49 | -34.900968065377754
50 | -31.516517276666555
51 | -31.806472625711145
52 | -35.417280696685026
53 | -26.203466697417348
54 | -33.58723026891158
55 | -32.77093149034283
56 | -33.69813141705309
57 | -25.18305415664703
58 | -34.92804119492154
59 | -30.59999597814012
60 | -31.586618760427896
61 | -38.477752491809476
62 | -29.904008079508074
63 | -29.325930733170797
64 | -43.13453743890022
65 | -37.4751303419107
66 | -26.021716556384273
67 | -31.92091488881435
68 | -29.043408785195318
69 | -38.26377876203905
70 | -33.88376438133806
71 | -35.725065981325244
72 | -26.07415578508828
73 | -33.760017535716315
74 | -27.216043067852794
75 | -39.96688059386949
76 | -34.42069937954157
77 | -29.688700721988663
78 | -32.39617777828593
79 | -41.77096486138779
80 | -31.26225921139721
81 | -26.746656168596132
82 | -29.900308135743792
83 | -29.40678710144639
84 | -29.07823187650827
85 | -33.44019967780564
86 | -28.42913520460352
87 | -27.936641464352835
88 | -40.80609953358206
89 | -27.35295351495666
90 | -26.29582883689302
91 | -27.442589053543976
92 | -29.16167812708794
93 | -27.380481353197645
94 | -29.58665143647273
95 | -32.07157583399032
96 | -36.93480419661896
97 | -30.02778461684339
98 | -30.146564755332
99 | -28.2966525213981
100 | -39.4994900281671
101 | -30.359559197764618
102 | -21.8251884645571
103 | -41.427515061219474
104 | -23.97920153379478
105 | -21.15847156875189
106 | -15.429943209696136
107 | -7.813971047261851
108 | -1.1430862641264692
109 | -13.155098894567436
110 | -19.645571862144305
111 | -19.026761856815355
112 | -20.27821775771798
113 | -29.661678936729786
114 | -32.80831520292662
115 | -28.70325541403814
116 | -28.85540615702721
117 | -28.933468272859702
118 | -38.593427888650766
119 | -41.648508890079356
120 | -39.32959172643521
121 | -28.831491595033107
122 | -40.998958456987644
123 | -42.38320917544269
124 | -25.900371170581824
125 | -34.67650152775696
126 | -29.02295591354664
127 | -31.120715949325675
128 | -46.73872410441626
129 | -41.019322399197044
130 | -34.440617291732345
131 | -28.194275295926396
132 | -39.274703214769076
133 | -32.97967804335675
134 | -29.737316461482948
135 | -32.148502037179995
136 | -33.909412446265826
137 | -28.951608478288726
138 | -32.100428190090255
139 | -39.20418003991726
140 | -43.737575396650755
141 | -32.775039094973984
142 | -27.466462699632622
143 | -43.95992336692249
144 | -33.64465553284762
145 | -31.347640798216826
146 | -38.636052078691854
147 | -30.568505120248304
148 | -32.47610695917393
149 | -45.01373241846697
150 | -36.93965624732777
151 | -33.313596844891606
152 | -32.964531581212654
153 | -27.604232978633938
154 | -32.62068007129169
155 | -35.176435579701305
156 | -26.557373715622635
157 | -32.51561415173599
158 | -62.59865165168335
159 | -33.8085710767147
160 | -37.14726658371939
161 | -37.51100705126332
162 | -46.38070884522882
163 | -28.535418761593412
164 | -31.714333575030484
165 | -38.03595855644387
166 | -38.96351780768445
167 | -34.41403912795777
168 | -33.026636474600366
169 | -50.79584740608853
170 | -30.280633265056487
171 | -41.19947090173163
172 | -28.617823889275364
173 | -28.708229976653172
174 | -36.33495909437586
175 | -36.45683980057684
176 | -35.26348191403021
177 | -32.80661752627964
178 | -36.71657399025063
179 | -40.67014178846407
180 | -27.281227614935393
181 | -27.043551840335148
182 | -29.426220383209802
183 | -34.368068191774015
184 | -32.81580805936352
185 | -37.036250989104246
186 | -51.22182328364816
187 | -37.600541736899565
188 | -33.788604865476124
189 | -33.779834063711405
190 | -35.89691004791399
191 | -26.719269449473888
192 | -27.839520956325295
193 | -35.99287198138866
194 | -33.92199550366528
195 | -39.36040514394806
196 | -31.503883992707223
197 | -34.78599160179727
198 | -33.54315094908662
199 | -55.64377483036145
200 | -30.373719535773912
201 | -30.84967643325212
202 | -41.806351587957266
203 | -27.194171114536605
204 | -27.32281900621036
205 | -30.630795492600754
206 | -26.764234825882184
207 | -29.164184921966037
208 | -37.120904077839334
209 | -43.51869973750483
210 | -27.714408613001503
211 | -26.274010671033437
212 | -22.49385981260009
213 | -20.054727561885706
214 | -8.193087058513335
215 | -14.342026872019451
216 | -22.49728703770216
217 | -24.183579849851526
218 | -19.07508126803949
219 | -28.10882287929087
220 | -29.140132143906076
221 | -33.02129369307511
222 | -32.756143810763724
223 | -29.798939796074105
224 | -26.513348119660414
225 | -24.25299697396417
226 | -32.043430935771944
227 | -32.88225227843233
228 | -31.61452643622252
229 | -29.91514500519056
230 | -34.43813761399797
231 | -41.26585494480278
232 | -28.99469027356968
233 | -30.79356950619639
234 | -32.11696643195189
235 | -34.514335554369225
236 | -36.09566685088327
237 | -35.23128762438231
238 | -28.089851323946462
239 | -30.227894190701143
240 | -33.294601069048674
241 | -34.4293719941263
242 | -23.777500820613735
243 | -35.44618469159061
244 | -40.901490921670096
245 | -35.48633750840964
246 | -27.05490901094555
247 | -29.78088141421274
248 | -33.258850244596104
249 | -43.60032749007652
250 | -39.99156378048569
251 | -27.218820336904038
252 | -31.08377226090235
253 | -27.867517615475236
254 | -35.63307486332119
255 | -34.750616018434314
256 | -30.658348423211365
257 | -27.321167415921668
--------------------------------------------------------------------------------
/dev/data/fftMag.txt:
--------------------------------------------------------------------------------
1 | 0.1396875
2 | 0.06396860592450664
3 | 0.06709563177674814
4 | 0.04644934149959594
5 | 0.025354747142529818
6 | 0.02586120890605589
7 | 0.08091484713030261
8 | 0.06593672784387676
9 | 0.07643273583022335
10 | 0.04009735468244112
11 | 0.05548605621708106
12 | 0.07492659399149185
13 | 0.05415500955273522
14 | 0.08511776614129007
15 | 0.08276441649621553
16 | 0.08405849689704564
17 | 0.12254230129772534
18 | 0.13743452346157486
19 | 0.1936932933977287
20 | 0.25778903491575156
21 | 0.4206253977077962
22 | 1.6419306494099588
23 | 0.8069538291232333
24 | 0.3423798462575333
25 | 0.20509387538680868
26 | 0.10308511850617576
27 | 0.11077238165800275
28 | 0.09868119048903662
29 | 0.048132666212917896
30 | 0.05419116394791007
31 | 0.03681539130350632
32 | 0.04472650200419962
33 | 0.05314602323158176
34 | 0.037981025064675905
35 | 0.02772781954600149
36 | 0.038438867940417336
37 | 0.016850145225657073
38 | 0.018402607007649955
39 | 0.01945458427979627
40 | 0.04497757802570729
41 | 0.027428436494670902
42 | 0.027681275323338514
43 | 0.026822478872374652
44 | 0.05414244287655348
45 | 0.035158796438278565
46 | 0.029167744367834217
47 | 0.02455389556271749
48 | 0.020878658478533107
49 | 0.017986704373786723
50 | 0.02655670174913232
51 | 0.025684810667023994
52 | 0.01694868332136091
53 | 0.04895833786882711
54 | 0.02092370009565478
55 | 0.022985471957328703
56 | 0.02065824525869917
57 | 0.0550614053919403
58 | 0.01793072872980739
59 | 0.029512105931761186
60 | 0.026343232323729617
61 | 0.011915502864761854
62 | 0.031974193290032774
63 | 0.03417460183463599
64 | 0.006970647607273703
65 | 0.013373450756956399
66 | 0.04999357249874574
67 | 0.0253486161827714
68 | 0.03530445899342054
69 | 0.012212682366454416
70 | 0.020221426115130982
71 | 0.016358621385611193
72 | 0.04969265611132521
73 | 0.020511580377918563
74 | 0.04357103200848389
75 | 0.010038202913229903
76 | 0.01900925213077569
77 | 0.032776680210563326
78 | 0.02399888756628344
79 | 0.008155521874872165
80 | 0.027345573718049344
81 | 0.04599040049958679
82 | 0.03198781629497514
83 | 0.03385794887903203
84 | 0.03516320124191912
85 | 0.021280901232738014
86 | 0.03789162580043367
87 | 0.04010217490103228
88 | 0.009113706184665534
89 | 0.04288963242605879
90 | 0.048440493407974156
91 | 0.04244930139897473
92 | 0.034827002223772566
93 | 0.04275391921987057
94 | 0.03316403983882666
95 | 0.024912723521795084
96 | 0.014231798630425301
97 | 0.03152178257806413
98 | 0.031093654047724566
99 | 0.03847400294818445
100 | 0.010593159186248846
101 | 0.030340451557512263
102 | 0.08104767797400092
103 | 0.008484460789515843
104 | 0.06324699896582513
105 | 0.0875137757176475
106 | 0.16923993096883222
107 | 0.4067255430031667
108 | 0.8766892611907319
109 | 0.2199100388503396
110 | 0.10416490135016206
111 | 0.11185667541540928
112 | 0.09684765558611116
113 | 0.032878807173038926
114 | 0.02288675601627932
115 | 0.03671446713118034
116 | 0.0360769398355388
117 | 0.03575416058244718
118 | 0.011757868685092683
119 | 0.008271314903526521
120 | 0.010802403955578814
121 | 0.036176406102160906
122 | 0.00891357816287111
123 | 0.007600454110972566
124 | 0.05069690437102949
125 | 0.018457586977132085
126 | 0.03538768919929061
127 | 0.027794841545763335
128 | 0.004603241869735187
129 | 0.008892704886327454
130 | 0.018965711302652018
131 | 0.038930164174044435
132 | 0.010870883442367765
133 | 0.022439650984616276
134 | 0.03259373850380302
135 | 0.02469305923064052
136 | 0.02016180349812288
137 | 0.035679567070603005
138 | 0.02483010696788727
139 | 0.010959506485751362
140 | 0.006503111946815196
141 | 0.022974604571974526
142 | 0.04233278735196284
143 | 0.006338753037296639
144 | 0.02078582293257708
145 | 0.027078085804466835
146 | 0.011700310741519608
147 | 0.02961929682636819
148 | 0.023779058320795816
149 | 0.005614529642462283
150 | 0.014223850783050466
151 | 0.021593356642356367
152 | 0.022478815405688683
153 | 0.041666627597563705
154 | 0.02338654123991556
155 | 0.017425218037100513
156 | 0.04700362082859112
157 | 0.02367114645319533
158 | 0.0007414253270369163
159 | 0.020397241888153065
160 | 0.013887902881649131
161 | 0.013318326192739434
162 | 0.004796942997521303
163 | 0.037430795917228
164 | 0.02595872282384552
165 | 0.012537243826434
166 | 0.011267410307342756
167 | 0.019023833821258333
168 | 0.022318663084575897
169 | 0.002885410645095895
170 | 0.030617402022377806
171 | 0.008710166460484628
172 | 0.03707736015429467
173 | 0.03669344612664456
174 | 0.015249375032209887
175 | 0.015036889558421479
176 | 0.017251461937160015
177 | 0.022891229721352935
178 | 0.014593897796394192
179 | 0.009257482730824988
180 | 0.043245270627506494
181 | 0.04444494856961484
182 | 0.033782281839238516
183 | 0.019124786271677935
184 | 0.022867021338647455
185 | 0.014066545345191823
186 | 0.0027473173941440173
187 | 0.01318174521756751
188 | 0.020444182857280113
189 | 0.020464837326245405
190 | 0.016038158368871805
191 | 0.04613563765696218
192 | 0.04055309006480287
193 | 0.015861943585336186
194 | 0.020132616682812726
195 | 0.01076415004242694
196 | 0.026595355527974218
197 | 0.01822637997126994
198 | 0.02103015398005199
199 | 0.001651244022506231
200 | 0.03029102875220333
201 | 0.028675841968338875
202 | 0.0081223634820344
203 | 0.04368088655192353
204 | 0.043038690571599994
205 | 0.029407643530685132
206 | 0.04589741843362837
207 | 0.03481695241224865
208 | 0.01393011802640462
209 | 0.006669065964931385
210 | 0.04114144766329414
211 | 0.04856232447795324
212 | 0.07504245083626343
213 | 0.09937190646513855
214 | 0.38935490219357793
215 | 0.1918221067208959
216 | 0.07501284688676531
217 | 0.06177617399761802
218 | 0.11123614680707695
219 | 0.03931505214609033
220 | 0.03491350038276181
221 | 0.02233239575069572
222 | 0.023024637928023423
223 | 0.032363315733990136
224 | 0.047242469789162765
225 | 0.061284430024880446
226 | 0.024993579176163287
227 | 0.02269276345110882
228 | 0.026258727613080096
229 | 0.031933222700110946
230 | 0.01897112647226757
231 | 0.008643850616198195
232 | 0.035503035468721196
233 | 0.028861674516866203
234 | 0.02478287454415026
235 | 0.01880542800583553
236 | 0.015675328736992
237 | 0.017315523208045205
238 | 0.039401017184464815
239 | 0.030803870608058833
240 | 0.021640632331447134
241 | 0.01899028140382091
242 | 0.06473288440165464
243 | 0.016892377003105016
244 | 0.00901416397372531
245 | 0.0168144677497589
246 | 0.04438687288306036
247 | 0.03243067061501569
248 | 0.021729887994033255
249 | 0.006606685378972986
250 | 0.010009717274862676
251 | 0.04355710261956598
252 | 0.027913313095070606
253 | 0.040422588377554296
254 | 0.016532794091392813
255 | 0.01830076309506356
256 | 0.02931450592360604
257 | 0.043046874999999984
--------------------------------------------------------------------------------
/dev/data/sample.txt:
--------------------------------------------------------------------------------
1 | # These data simulate a sample of audio with the following parameters:
2 | # sample rate: 48 kHz
3 | # points: 512 (2^9)
4 | # offset: 0.1 (above zero)
5 | # tone: 2 kHz (amplitude 2)
6 | # tone: 10 kHz (amplitude 10)
7 | # tone: 20 kHz (amplitude .5)
8 | #
9 | # Quick and dirty checksum
10 | # the sum of these values is 71.52
11 | # the sum of the sins of these values is 10.417026634786811
12 | +0.33
13 | +2.15
14 | +1.44
15 | +1.37
16 | +0.24
17 | +2.60
18 | +3.51
19 | +1.98
20 | +1.88
21 | +0.08
22 | +1.82
23 | +1.30
24 | +0.23
25 | -1.16
26 | -1.35
27 | -0.58
28 | -0.84
29 | -1.35
30 | -2.72
31 | -2.53
32 | -0.02
33 | -0.76
34 | -0.48
35 | -2.10
36 | +0.30
37 | +1.86
38 | +1.60
39 | +1.49
40 | +0.58
41 | +2.12
42 | +2.79
43 | +1.99
44 | +1.20
45 | +0.80
46 | +2.18
47 | +1.60
48 | -0.37
49 | -1.25
50 | -1.99
51 | +0.35
52 | -1.19
53 | -1.62
54 | -3.28
55 | -2.57
56 | +0.07
57 | -0.81
58 | -1.13
59 | -1.68
60 | -0.25
61 | +1.55
62 | +1.08
63 | +1.53
64 | +0.65
65 | +2.53
66 | +2.79
67 | +2.42
68 | +1.72
69 | +0.54
70 | +2.39
71 | +1.51
72 | +0.22
73 | -1.42
74 | -1.44
75 | +0.29
76 | -1.61
77 | -1.50
78 | -3.23
79 | -2.20
80 | -0.01
81 | -1.39
82 | -0.47
83 | -1.65
84 | +0.25
85 | +2.05
86 | +1.48
87 | +0.91
88 | +0.76
89 | +2.76
90 | +2.73
91 | +2.45
92 | +1.09
93 | +0.28
94 | +2.07
95 | +1.16
96 | +0.27
97 | -1.17
98 | -1.50
99 | +0.20
100 | -0.91
101 | -1.58
102 | -2.46
103 | -2.55
104 | -0.31
105 | -0.94
106 | -1.13
107 | -1.85
108 | +0.42
109 | +1.56
110 | +0.85
111 | +0.88
112 | +0.66
113 | +2.73
114 | +3.23
115 | +2.47
116 | +1.12
117 | +0.74
118 | +1.60
119 | +1.73
120 | +0.28
121 | -1.54
122 | -2.18
123 | -0.50
124 | -1.09
125 | -1.39
126 | -2.91
127 | -2.69
128 | -0.16
129 | -1.04
130 | -1.24
131 | -1.52
132 | -0.39
133 | +1.69
134 | +1.52
135 | +0.87
136 | +0.31
137 | +2.75
138 | +3.56
139 | +2.53
140 | +1.29
141 | +0.33
142 | +1.81
143 | +1.34
144 | +0.13
145 | -1.58
146 | -2.05
147 | -0.11
148 | -0.85
149 | -1.73
150 | -3.30
151 | -2.10
152 | -0.43
153 | -0.67
154 | -1.34
155 | -1.43
156 | +0.22
157 | +2.16
158 | +1.35
159 | +1.38
160 | +0.21
161 | +2.23
162 | +3.21
163 | +1.79
164 | +1.90
165 | +0.38
166 | +1.60
167 | +1.10
168 | +0.44
169 | -1.07
170 | -1.69
171 | -0.09
172 | -0.73
173 | -2.26
174 | -2.89
175 | -2.68
176 | -0.02
177 | -0.96
178 | -0.89
179 | -1.58
180 | +0.27
181 | +2.33
182 | +0.97
183 | +0.87
184 | +0.50
185 | +2.52
186 | +2.82
187 | +1.61
188 | +1.13
189 | -0.04
190 | +1.98
191 | +1.28
192 | -0.38
193 | -1.24
194 | -1.52
195 | -0.40
196 | -0.79
197 | -2.31
198 | -2.89
199 | -1.88
200 | +0.16
201 | -1.59
202 | -0.81
203 | -1.86
204 | +0.57
205 | +1.92
206 | +1.44
207 | +1.13
208 | +0.45
209 | +3.02
210 | +3.49
211 | +2.51
212 | +1.15
213 | -0.06
214 | +2.43
215 | +1.01
216 | +0.48
217 | -1.09
218 | -1.55
219 | -0.09
220 | -1.35
221 | -1.35
222 | -3.37
223 | -2.15
224 | -0.71
225 | -1.41
226 | -0.97
227 | -1.55
228 | -0.14
229 | +1.64
230 | +0.91
231 | +1.59
232 | +0.17
233 | +2.65
234 | +3.16
235 | +2.20
236 | +1.24
237 | -0.17
238 | +1.63
239 | +1.71
240 | +0.31
241 | -0.74
242 | -1.68
243 | -0.35
244 | -1.43
245 | -1.87
246 | -3.20
247 | -1.95
248 | -0.34
249 | -0.97
250 | -1.15
251 | -1.76
252 | -0.16
253 | +2.33
254 | +1.28
255 | +0.81
256 | +1.02
257 | +3.00
258 | +2.76
259 | +2.31
260 | +0.99
261 | -0.00
262 | +1.60
263 | +0.94
264 | +0.33
265 | -1.53
266 | -1.49
267 | +0.04
268 | -1.13
269 | -2.10
270 | -2.56
271 | -1.98
272 | -0.39
273 | -0.70
274 | -0.66
275 | -1.67
276 | -0.06
277 | +2.11
278 | +1.09
279 | +1.45
280 | +1.03
281 | +2.65
282 | +2.69
283 | +2.16
284 | +1.89
285 | +0.68
286 | +2.07
287 | +0.97
288 | -0.40
289 | -1.08
290 | -1.66
291 | -0.23
292 | -0.83
293 | -2.02
294 | -2.61
295 | -2.32
296 | -0.00
297 | -1.07
298 | -0.94
299 | -1.97
300 | +0.23
301 | +1.89
302 | +0.98
303 | +1.06
304 | +0.83
305 | +2.50
306 | +3.52
307 | +1.88
308 | +1.09
309 | -0.04
310 | +2.19
311 | +1.04
312 | +0.13
313 | -1.12
314 | -1.56
315 | -0.12
316 | -1.60
317 | -1.90
318 | -3.28
319 | -1.98
320 | -0.27
321 | -0.90
322 | -0.83
323 | -2.12
324 | +0.17
325 | +1.79
326 | +1.66
327 | +0.93
328 | +0.15
329 | +2.32
330 | +3.23
331 | +2.34
332 | +1.15
333 | +0.07
334 | +1.55
335 | +1.28
336 | -0.11
337 | -0.79
338 | -1.51
339 | -0.08
340 | -0.75
341 | -2.14
342 | -2.45
343 | -1.99
344 | +0.06
345 | -1.14
346 | -0.62
347 | -1.78
348 | +0.15
349 | +1.64
350 | +1.09
351 | +1.20
352 | +0.45
353 | +2.70
354 | +3.20
355 | +2.47
356 | +1.81
357 | +0.11
358 | +2.21
359 | +1.18
360 | +0.07
361 | -0.83
362 | -2.12
363 | +0.30
364 | -1.18
365 | -1.48
366 | -2.45
367 | -2.57
368 | -0.34
369 | -1.28
370 | -1.28
371 | -1.87
372 | +0.22
373 | +1.92
374 | +1.58
375 | +1.17
376 | +0.79
377 | +2.83
378 | +2.72
379 | +1.64
380 | +1.51
381 | +0.44
382 | +2.10
383 | +1.65
384 | +0.46
385 | -1.39
386 | -1.96
387 | -0.01
388 | -1.04
389 | -2.26
390 | -2.87
391 | -1.85
392 | -0.67
393 | -1.13
394 | -1.40
395 | -1.98
396 | +0.59
397 | +1.37
398 | +1.00
399 | +0.84
400 | +0.55
401 | +2.61
402 | +3.46
403 | +1.76
404 | +1.02
405 | -0.04
406 | +2.31
407 | +1.67
408 | +0.35
409 | -1.39
410 | -2.16
411 | -0.48
412 | -1.52
413 | -1.76
414 | -2.67
415 | -2.01
416 | -0.60
417 | -1.21
418 | -1.42
419 | -1.85
420 | +0.08
421 | +1.69
422 | +1.27
423 | +1.22
424 | +0.83
425 | +2.23
426 | +2.70
427 | +1.68
428 | +1.42
429 | +0.56
430 | +1.91
431 | +1.55
432 | +0.06
433 | -1.55
434 | -1.75
435 | -0.57
436 | -0.92
437 | -1.99
438 | -2.70
439 | -2.13
440 | -0.37
441 | -1.06
442 | -0.63
443 | -1.71
444 | +0.51
445 | +1.74
446 | +1.48
447 | +1.39
448 | +0.78
449 | +2.27
450 | +3.52
451 | +2.13
452 | +1.89
453 | -0.14
454 | +2.08
455 | +0.99
456 | +0.57
457 | -1.19
458 | -1.90
459 | +0.32
460 | -1.64
461 | -1.70
462 | -3.09
463 | -1.84
464 | +0.03
465 | -1.15
466 | -0.80
467 | -2.04
468 | +0.59
469 | +2.02
470 | +0.72
471 | +1.69
472 | +0.73
473 | +2.38
474 | +3.42
475 | +2.48
476 | +1.42
477 | -0.01
478 | +2.04
479 | +1.22
480 | -0.02
481 | -1.11
482 | -1.95
483 | -0.32
484 | -0.87
485 | -1.55
486 | -2.67
487 | -2.44
488 | -0.30
489 | -1.18
490 | -1.39
491 | -1.80
492 | +0.52
493 | +2.11
494 | +1.32
495 | +1.63
496 | +0.27
497 | +2.88
498 | +3.16
499 | +1.99
500 | +1.64
501 | +0.53
502 | +2.12
503 | +0.90
504 | -0.22
505 | -1.59
506 | -1.45
507 | +0.05
508 | -1.46
509 | -1.73
510 | -2.76
511 | -2.06
512 | +0.10
513 | -1.56
514 | -0.92
515 | -1.60
516 | -0.14
517 | +1.35
518 | +0.83
519 | +0.88
520 | +0.76
521 | +2.30
522 | +3.16
523 | +2.11
--------------------------------------------------------------------------------
/dev/demo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/swharden/FftSharp/3f5158f7ab146c8fb651028e8dce67407b3ded81/dev/demo.png
--------------------------------------------------------------------------------
/dev/icon/v1/icon-v1.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
104 |
--------------------------------------------------------------------------------
/dev/icon/v2/icon-24.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/swharden/FftSharp/3f5158f7ab146c8fb651028e8dce67407b3ded81/dev/icon/v2/icon-24.png
--------------------------------------------------------------------------------
/dev/icon/v2/icon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/swharden/FftSharp/3f5158f7ab146c8fb651028e8dce67407b3ded81/dev/icon/v2/icon.ico
--------------------------------------------------------------------------------
/dev/icon/v2/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/swharden/FftSharp/3f5158f7ab146c8fb651028e8dce67407b3ded81/dev/icon/v2/icon.png
--------------------------------------------------------------------------------
/dev/icon/v3/fftsharp-icon-128.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/swharden/FftSharp/3f5158f7ab146c8fb651028e8dce67407b3ded81/dev/icon/v3/fftsharp-icon-128.png
--------------------------------------------------------------------------------
/dev/icon/v3/fftsharp-icon-24.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/swharden/FftSharp/3f5158f7ab146c8fb651028e8dce67407b3ded81/dev/icon/v3/fftsharp-icon-24.png
--------------------------------------------------------------------------------
/dev/icon/v3/fftsharp-icon-256.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/swharden/FftSharp/3f5158f7ab146c8fb651028e8dce67407b3ded81/dev/icon/v3/fftsharp-icon-256.png
--------------------------------------------------------------------------------
/dev/imp/oakley/complex.cs:
--------------------------------------------------------------------------------
1 | // The author (Christopher Oakley) agreed to release this code under MIT license (May 25, 2020)
2 | // https://www.egr.msu.edu/classes/ece480/capstone/fall11/group06/style/Application_Note_ChrisOakley.pdf
3 | // http://csclab.murraystate.edu/~bob.pilgrim/565/lectures/lecture_08.pdf
4 | class complex
5 | {
6 | public double real = 0.0;
7 | public double imag = 0.0;
8 | //Empty constructor
9 | public complex()
10 | {
11 | }
12 | public complex(double real, double im)
13 | {
14 | this.real = real;
15 | this.imag = imag;
16 | }
17 | public string ToString()
18 | {
19 | string data = real.ToString() + " " + imag.ToString() + "i";
20 | return data;
21 | }
22 | public static complex from_polar(double r, double radians)
23 | {
24 | complex data = new complex(r * Math.Cos(radians),
25 | r * Math.Sin(radians));
26 | return data;
27 | }
28 | public static complex operator +(complex a, complex b)
29 | {
30 | complex data = new complex(a.real + b.real, a.imag + b.imag);
31 | return data;
32 | }
33 | public static complex operator -(complex a, complex b)
34 | {
35 | complex data = new complex(a.real - b.real, a.imag - b.imag);
36 | return data;
37 | }
38 | public static complex operator *(complex a, complex b)
39 | {
40 | complex data = new complex((a.real * b.real) - (a.imag * b.imag),
41 | (a.real * b.imag) + (a.imag * b.real));
42 | return data;
43 | }
44 | public double magnitude
45 | {
46 | get
47 | {
48 | return Math.Sqrt(Math.Pow(real, 2) + Math.Pow(imag, 2));
49 | }
50 | }
51 | public double phase
52 | {
53 | get
54 | {
55 | return Math.Atan(imag / real);
56 | }
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/dev/imp/oakley/dft.cs:
--------------------------------------------------------------------------------
1 | // The author (Christopher Oakley) agreed to release this code under MIT license (May 25, 2020)
2 | // https://www.egr.msu.edu/classes/ece480/capstone/fall11/group06/style/Application_Note_ChrisOakley.pdf
3 | // http://csclab.murraystate.edu/~bob.pilgrim/565/lectures/lecture_08.pdf
4 |
5 | Complex[] DFT(Complex[] x)
6 | {
7 | int N = x.Length;
8 | Complex[] X = new Complex[N];
9 | for (int k = 0; k < N; k++)
10 | {
11 | X[k] = new Complex(0, 0);
12 | for (int n = 0; n < N; n++)
13 | {
14 | Complex temp = Complex.from_polar(1, -2 * Math.PI * n * k / N);
15 | temp *= x[n];
16 | X[k] += temp;
17 | }
18 | }
19 | return X;
20 | }
--------------------------------------------------------------------------------
/dev/imp/oakley/fft.cs:
--------------------------------------------------------------------------------
1 | // The author (Christopher Oakley) agreed to release this code under MIT license (May 25, 2020)
2 | // https://www.egr.msu.edu/classes/ece480/capstone/fall11/group06/style/Application_Note_ChrisOakley.pdf
3 | // http://csclab.murraystate.edu/~bob.pilgrim/565/lectures/lecture_08.pdf
4 |
5 | Complex[] FFT(Complex[] x)
6 | {
7 | int N = x.Length;
8 | Complex[] X = new Complex[N];
9 | Complex[] d, D, e, E;
10 | if (N == 1)
11 | {
12 | X[0] = x[0];
13 | return X;
14 | }
15 | int k;
16 | e = new Complex[N / 2];
17 | d = new Complex[N / 2];
18 | for (k = 0; k < N / 2; k++)
19 | {
20 | e[k] = x[2 * k];
21 | d[k] = x[2 * k + 1];
22 | }
23 | D = FFT(d);
24 | E = FFT(e);
25 | for (k = 0; k < N / 2; k++)
26 | {
27 | Complex temp = Complex.from_polar(1, -2 * Math.PI * k / N);
28 | D[k] *= temp;
29 | }
30 | for (k = 0; k < N / 2; k++)
31 | {
32 | X[k] = E[k] + D[k];
33 | X[k + N / 2] = E[k] - D[k];
34 | }
35 | return X;
36 | }
--------------------------------------------------------------------------------
/dev/imp/oakley/main.cs:
--------------------------------------------------------------------------------
1 | // The author (Christopher Oakley) agreed to release this code under MIT license (May 25, 2020)
2 | static void Main(string[] args)
3 | {
4 | int N = 128;
5 | complex[] TD = new complex[N];
6 | complex[] FD = new complex[N];
7 |
8 | for (int i = 0; i < N; i++)
9 | TD[i] = new complex(Math.Sin(i / Math.PI), 0.0);
10 |
11 | FD = fourier.DFT(TD);
12 |
13 | show_real(TD);
14 | show_magnitude(FD);
15 | Console.ReadKey();
16 | }
--------------------------------------------------------------------------------
/dev/imp/ooura/sample1/Makefile:
--------------------------------------------------------------------------------
1 | NMAX_FLAGS = -DNMAX=65536 -DNMAXSQRT=256
2 |
3 | # ---- for GNU gcc ----
4 |
5 | CC = gcc
6 |
7 | CFLAGS = -Wall
8 |
9 | OFLAGS = -O2
10 |
11 | # ---- for SUN WS cc ----
12 | #
13 | #CC = cc
14 | #
15 | #CFLAGS =
16 | #
17 | #OFLAGS = -xO2
18 |
19 |
20 |
21 |
22 | all: test4g test8g testsg test4g_h test8g_h testsg_h
23 |
24 |
25 | test4g : testxg.o fft4g.o
26 | $(CC) testxg.o fft4g.o -lm -o test4g
27 |
28 | test8g : testxg.o fft8g.o
29 | $(CC) testxg.o fft8g.o -lm -o test8g
30 |
31 | testsg : testxg.o fftsg.o
32 | $(CC) testxg.o fftsg.o -lm -o testsg
33 |
34 | test4g_h : testxg_h.o fft4g_h.o
35 | $(CC) testxg_h.o fft4g_h.o -lm -o test4g_h
36 |
37 | test8g_h : testxg_h.o fft8g_h.o
38 | $(CC) testxg_h.o fft8g_h.o -lm -o test8g_h
39 |
40 | testsg_h : testxg_h.o fftsg_h.o
41 | $(CC) testxg_h.o fftsg_h.o -lm -o testsg_h
42 |
43 |
44 | testxg.o : testxg.c
45 | $(CC) $(CFLAGS) $(OFLAGS) $(NMAX_FLAGS) -c testxg.c -o testxg.o
46 |
47 | testxg_h.o : testxg_h.c
48 | $(CC) $(CFLAGS) $(OFLAGS) $(NMAX_FLAGS) -c testxg_h.c -o testxg_h.o
49 |
50 |
51 | fft4g.o : ../fft4g.c
52 | $(CC) $(CFLAGS) $(OFLAGS) -c ../fft4g.c -o fft4g.o
53 |
54 | fft8g.o : ../fft8g.c
55 | $(CC) $(CFLAGS) $(OFLAGS) -c ../fft8g.c -o fft8g.o
56 |
57 | fftsg.o : ../fftsg.c
58 | $(CC) $(CFLAGS) $(OFLAGS) -c ../fftsg.c -o fftsg.o
59 |
60 | fft4g_h.o : ../fft4g_h.c
61 | $(CC) $(CFLAGS) $(OFLAGS) -c ../fft4g_h.c -o fft4g_h.o
62 |
63 | fft8g_h.o : ../fft8g_h.c
64 | $(CC) $(CFLAGS) $(OFLAGS) -c ../fft8g_h.c -o fft8g_h.o
65 |
66 | fftsg_h.o : ../fftsg_h.c
67 | $(CC) $(CFLAGS) $(OFLAGS) -c ../fftsg_h.c -o fftsg_h.o
68 |
69 |
70 |
71 |
72 | clean:
73 | rm -f *.o
74 |
75 |
--------------------------------------------------------------------------------
/dev/imp/ooura/sample1/Makefile.f77:
--------------------------------------------------------------------------------
1 | # ---- for GNU g77 ----
2 |
3 | F77 = g77
4 |
5 | FFLAGS = -Wall
6 |
7 | OFLAGS = -O2
8 |
9 | # ---- for SUN WS f77 ----
10 | #
11 | #F77 = f77
12 | #
13 | #FFLAGS =
14 | #
15 | #OFLAGS = -xO2
16 |
17 |
18 |
19 |
20 | all: test4g_f test8g_f testsg_f
21 |
22 |
23 | test4g_f : testxg_f.o fft4g_f.o
24 | $(F77) testxg_f.o fft4g_f.o -o test4g_f
25 |
26 | test8g_f : testxg_f.o fft8g_f.o
27 | $(F77) testxg_f.o fft8g_f.o -o test8g_f
28 |
29 | testsg_f : testxg_f.o fftsg_f.o
30 | $(F77) testxg_f.o fftsg_f.o -o testsg_f
31 |
32 |
33 | testxg_f.o : testxg.f
34 | $(F77) $(FFLAGS) $(OFLAGS) -c testxg.f -o testxg_f.o
35 |
36 |
37 | fft4g_f.o : ../fft4g.f
38 | $(F77) $(FFLAGS) $(OFLAGS) -c ../fft4g.f -o fft4g_f.o
39 |
40 | fft8g_f.o : ../fft8g.f
41 | $(F77) $(FFLAGS) $(OFLAGS) -c ../fft8g.f -o fft8g_f.o
42 |
43 | fftsg_f.o : ../fftsg.f
44 | $(F77) $(FFLAGS) $(OFLAGS) -c ../fftsg.f -o fftsg_f.o
45 |
46 |
47 |
48 |
49 | clean:
50 | rm -f *.o
51 |
52 |
--------------------------------------------------------------------------------
/dev/imp/ooura/sample1/testxg.c:
--------------------------------------------------------------------------------
1 | /* test of fft*g.c */
2 |
3 | #include
4 | #include
5 | #define MAX(x,y) ((x) > (y) ? (x) : (y))
6 |
7 | /* random number generator, 0 <= RND < 1 */
8 | #define RND(p) ((*(p) = (*(p) * 7141 + 54773) % 259200) * (1.0 / 259200.0))
9 |
10 | #ifndef NMAX
11 | #define NMAX 8192
12 | #define NMAXSQRT 64
13 | #endif
14 |
15 | void cdft(int, int, double *, int *, double *);
16 | void rdft(int, int, double *, int *, double *);
17 | void ddct(int, int, double *, int *, double *);
18 | void ddst(int, int, double *, int *, double *);
19 | void dfct(int, double *, double *, int *, double *);
20 | void dfst(int, double *, double *, int *, double *);
21 | void putdata(int nini, int nend, double *a);
22 | double errorcheck(int nini, int nend, double scale, double *a);
23 |
24 |
25 | int main()
26 | {
27 | int n, ip[NMAXSQRT + 2];
28 | double a[NMAX + 1], w[NMAX * 5 / 4], t[NMAX / 2 + 1], err;
29 |
30 | printf("data length n=? (must be 2^m)\n");
31 | scanf("%d", &n);
32 | ip[0] = 0;
33 |
34 | /* check of CDFT */
35 | putdata(0, n - 1, a);
36 | cdft(n, 1, a, ip, w);
37 | cdft(n, -1, a, ip, w);
38 | err = errorcheck(0, n - 1, 2.0 / n, a);
39 | printf("cdft err= %g \n", err);
40 |
41 | /* check of RDFT */
42 | putdata(0, n - 1, a);
43 | rdft(n, 1, a, ip, w);
44 | rdft(n, -1, a, ip, w);
45 | err = errorcheck(0, n - 1, 2.0 / n, a);
46 | printf("rdft err= %g \n", err);
47 |
48 | /* check of DDCT */
49 | putdata(0, n - 1, a);
50 | ddct(n, 1, a, ip, w);
51 | ddct(n, -1, a, ip, w);
52 | a[0] *= 0.5;
53 | err = errorcheck(0, n - 1, 2.0 / n, a);
54 | printf("ddct err= %g \n", err);
55 |
56 | /* check of DDST */
57 | putdata(0, n - 1, a);
58 | ddst(n, 1, a, ip, w);
59 | ddst(n, -1, a, ip, w);
60 | a[0] *= 0.5;
61 | err = errorcheck(0, n - 1, 2.0 / n, a);
62 | printf("ddst err= %g \n", err);
63 |
64 | /* check of DFCT */
65 | putdata(0, n, a);
66 | a[0] *= 0.5;
67 | a[n] *= 0.5;
68 | dfct(n, a, t, ip, w);
69 | a[0] *= 0.5;
70 | a[n] *= 0.5;
71 | dfct(n, a, t, ip, w);
72 | err = errorcheck(0, n, 2.0 / n, a);
73 | printf("dfct err= %g \n", err);
74 |
75 | /* check of DFST */
76 | putdata(1, n - 1, a);
77 | dfst(n, a, t, ip, w);
78 | dfst(n, a, t, ip, w);
79 | err = errorcheck(1, n - 1, 2.0 / n, a);
80 | printf("dfst err= %g \n", err);
81 |
82 | return 0;
83 | }
84 |
85 |
86 | void putdata(int nini, int nend, double *a)
87 | {
88 | int j, seed = 0;
89 |
90 | for (j = nini; j <= nend; j++) {
91 | a[j] = RND(&seed);
92 | }
93 | }
94 |
95 |
96 | double errorcheck(int nini, int nend, double scale, double *a)
97 | {
98 | int j, seed = 0;
99 | double err = 0, e;
100 |
101 | for (j = nini; j <= nend; j++) {
102 | e = RND(&seed) - a[j] * scale;
103 | err = MAX(err, fabs(e));
104 | }
105 | return err;
106 | }
107 |
108 |
--------------------------------------------------------------------------------
/dev/imp/ooura/sample1/testxg.f:
--------------------------------------------------------------------------------
1 | ! test of fft*g.f
2 | !
3 | !
4 | program main
5 | integer nmax, nmaxsqrt
6 | parameter (nmax = 32768)
7 | parameter (nmaxsqrt = 128)
8 | integer n, ip(0 : nmaxsqrt + 1)
9 | real*8 a(0 : nmax), w(0 : nmax * 5 / 4 - 1), t(0 : nmax / 2),
10 | & err, errorcheck
11 | !
12 | write (*, *) 'data length n=? (must be 2**m)'
13 | read (*, *) n
14 | ip(0) = 0
15 | !
16 | ! check of CDFT
17 | call putdata(0, n - 1, a)
18 | call cdft(n, 1, a, ip, w)
19 | call cdft(n, -1, a, ip, w)
20 | err = errorcheck(0, n - 1, 2.0d0 / n, a)
21 | write (*, *) 'cdft err= ', err
22 | !
23 | ! check of RDFT
24 | call putdata(0, n - 1, a)
25 | call rdft(n, 1, a, ip, w)
26 | call rdft(n, -1, a, ip, w)
27 | err = errorcheck(0, n - 1, 2.0d0 / n, a)
28 | write (*, *) 'rdft err= ', err
29 | !
30 | ! check of DDCT
31 | call putdata(0, n - 1, a)
32 | call ddct(n, 1, a, ip, w)
33 | call ddct(n, -1, a, ip, w)
34 | a(0) = a(0) * 0.5d0
35 | err = errorcheck(0, n - 1, 2.0d0 / n, a)
36 | write (*, *) 'ddct err= ', err
37 | !
38 | ! check of DDST
39 | call putdata(0, n - 1, a)
40 | call ddst(n, 1, a, ip, w)
41 | call ddst(n, -1, a, ip, w)
42 | a(0) = a(0) * 0.5d0
43 | err = errorcheck(0, n - 1, 2.0d0 / n, a)
44 | write (*, *) 'ddst err= ', err
45 | !
46 | ! check of DFCT
47 | call putdata(0, n, a)
48 | a(0) = a(0) * 0.5d0
49 | a(n) = a(n) * 0.5d0
50 | call dfct(n, a, t, ip, w)
51 | a(0) = a(0) * 0.5d0
52 | a(n) = a(n) * 0.5d0
53 | call dfct(n, a, t, ip, w)
54 | err = errorcheck(0, n, 2.0d0 / n, a)
55 | write (*, *) 'dfct err= ', err
56 | !
57 | ! check of DFST
58 | call putdata(1, n - 1, a)
59 | call dfst(n, a, t, ip, w)
60 | call dfst(n, a, t, ip, w)
61 | err = errorcheck(1, n - 1, 2.0d0 / n, a)
62 | write (*, *) 'dfst err= ', err
63 | !
64 | end
65 | !
66 | !
67 | subroutine putdata(nini, nend, a)
68 | integer nini, nend, j, seed
69 | real*8 a(0 : *), drnd
70 | seed = 0
71 | do j = nini, nend
72 | a(j) = drnd(seed)
73 | end do
74 | end
75 | !
76 | !
77 | function errorcheck(nini, nend, scale, a)
78 | integer nini, nend, j, seed
79 | real*8 scale, a(0 : *), drnd, err, e, errorcheck
80 | err = 0
81 | seed = 0
82 | do j = nini, nend
83 | e = drnd(seed) - a(j) * scale
84 | err = max(err, abs(e))
85 | end do
86 | errorcheck = err
87 | end
88 | !
89 | !
90 | ! random number generator, 0 <= drnd < 1
91 | real*8 function drnd(seed)
92 | integer seed
93 | seed = mod(seed * 7141 + 54773, 259200)
94 | drnd = seed * (1.0d0 / 259200.0d0)
95 | end
96 | !
97 |
--------------------------------------------------------------------------------
/dev/imp/ooura/sample1/testxg_h.c:
--------------------------------------------------------------------------------
1 | /* test of fft*g_h.c */
2 |
3 | #include
4 | #include
5 | #define MAX(x,y) ((x) > (y) ? (x) : (y))
6 |
7 | /* random number generator, 0 <= RND < 1 */
8 | #define RND(p) ((*(p) = (*(p) * 7141 + 54773) % 259200) * (1.0 / 259200.0))
9 |
10 | #ifndef NMAX
11 | #define NMAX 8192
12 | #endif
13 |
14 | void cdft(int, int, double *);
15 | void rdft(int, int, double *);
16 | void ddct(int, int, double *);
17 | void ddst(int, int, double *);
18 | void dfct(int, double *);
19 | void dfst(int, double *);
20 | void putdata(int nini, int nend, double *a);
21 | double errorcheck(int nini, int nend, double scale, double *a);
22 |
23 |
24 | int main()
25 | {
26 | int n;
27 | double a[NMAX + 1], err;
28 |
29 | printf("data length n=? (must be 2^m)\n");
30 | scanf("%d", &n);
31 |
32 | /* check of CDFT */
33 | putdata(0, n - 1, a);
34 | cdft(n, 1, a);
35 | cdft(n, -1, a);
36 | err = errorcheck(0, n - 1, 2.0 / n, a);
37 | printf("cdft err= %g \n", err);
38 |
39 | /* check of RDFT */
40 | putdata(0, n - 1, a);
41 | rdft(n, 1, a);
42 | rdft(n, -1, a);
43 | err = errorcheck(0, n - 1, 2.0 / n, a);
44 | printf("rdft err= %g \n", err);
45 |
46 | /* check of DDCT */
47 | putdata(0, n - 1, a);
48 | ddct(n, 1, a);
49 | ddct(n, -1, a);
50 | a[0] *= 0.5;
51 | err = errorcheck(0, n - 1, 2.0 / n, a);
52 | printf("ddct err= %g \n", err);
53 |
54 | /* check of DDST */
55 | putdata(0, n - 1, a);
56 | ddst(n, 1, a);
57 | ddst(n, -1, a);
58 | a[0] *= 0.5;
59 | err = errorcheck(0, n - 1, 2.0 / n, a);
60 | printf("ddst err= %g \n", err);
61 |
62 | /* check of DFCT */
63 | putdata(0, n, a);
64 | a[0] *= 0.5;
65 | a[n] *= 0.5;
66 | dfct(n, a);
67 | a[0] *= 0.5;
68 | a[n] *= 0.5;
69 | dfct(n, a);
70 | err = errorcheck(0, n, 2.0 / n, a);
71 | printf("dfct err= %g \n", err);
72 |
73 | /* check of DFST */
74 | putdata(1, n - 1, a);
75 | dfst(n, a);
76 | dfst(n, a);
77 | err = errorcheck(1, n - 1, 2.0 / n, a);
78 | printf("dfst err= %g \n", err);
79 |
80 | return 0;
81 | }
82 |
83 |
84 | void putdata(int nini, int nend, double *a)
85 | {
86 | int j, seed = 0;
87 |
88 | for (j = nini; j <= nend; j++) {
89 | a[j] = RND(&seed);
90 | }
91 | }
92 |
93 |
94 | double errorcheck(int nini, int nend, double scale, double *a)
95 | {
96 | int j, seed = 0;
97 | double err = 0, e;
98 |
99 | for (j = nini; j <= nend; j++) {
100 | e = RND(&seed) - a[j] * scale;
101 | err = MAX(err, fabs(e));
102 | }
103 | return err;
104 | }
105 |
106 |
--------------------------------------------------------------------------------
/dev/imp/ooura/sample2/Makefile:
--------------------------------------------------------------------------------
1 | # ---- for GNU gcc ----
2 |
3 | CC = gcc
4 |
5 | CFLAGS = -Wall
6 |
7 | OFLAGS_FFT = -O6 -ffast-math
8 | OFLAGS_PI = -O6 -ffast-math
9 |
10 | # ---- for SUN WS cc ----
11 | #
12 | #CC = cc
13 | #
14 | #CFLAGS =
15 | #
16 | #OFLAGS_FFT = -fast -xO5
17 | #OFLAGS_PI = -fast -xO5
18 |
19 |
20 |
21 |
22 | all: pi_fft4g pi_fft8g pi_fftsg
23 |
24 |
25 | pi_fft4g : pi_fft.o fft4g.o
26 | $(CC) pi_fft.o fft4g.o -lm -o pi_fft4g
27 |
28 | pi_fft8g : pi_fft.o fft8g.o
29 | $(CC) pi_fft.o fft8g.o -lm -o pi_fft8g
30 |
31 | pi_fftsg : pi_fft.o fftsg.o
32 | $(CC) pi_fft.o fftsg.o -lm -o pi_fftsg
33 |
34 |
35 | pi_fft.o : pi_fft.c
36 | $(CC) $(CFLAGS) $(OFLAGS_PI) -c pi_fft.c -o pi_fft.o
37 |
38 |
39 | fft4g.o : ../fft4g.c
40 | $(CC) $(CFLAGS) $(OFLAGS_FFT) -c ../fft4g.c -o fft4g.o
41 |
42 | fft8g.o : ../fft8g.c
43 | $(CC) $(CFLAGS) $(OFLAGS_FFT) -c ../fft8g.c -o fft8g.o
44 |
45 | fftsg.o : ../fftsg.c
46 | $(CC) $(CFLAGS) $(OFLAGS_FFT) -c ../fftsg.c -o fftsg.o
47 |
48 |
49 |
50 |
51 | clean:
52 | rm -f *.o
53 |
54 |
--------------------------------------------------------------------------------
/dev/imp/ooura/sample2/Makefile.pth:
--------------------------------------------------------------------------------
1 | # ---- for GNU gcc ----
2 |
3 | CC = gcc
4 |
5 | CFLAGS = -Wall -DUSE_CDFT_PTHREADS
6 |
7 | OFLAGS_FFT = -O6 -ffast-math
8 | OFLAGS_PI = -O6 -ffast-math
9 |
10 | # ---- for SUN WS cc ----
11 | #
12 | #CC = cc
13 | #
14 | #CFLAGS = -DUSE_CDFT_PTHREADS
15 | #
16 | #OFLAGS_FFT = -fast -xO5
17 | #OFLAGS_PI = -fast -xO5
18 |
19 |
20 |
21 |
22 | all: pi_fftsgpt
23 |
24 |
25 | pi_fftsgpt : pi_fft.o fftsgpt.o
26 | $(CC) pi_fft.o fftsgpt.o -lm -lpthread -o pi_fftsgpt
27 |
28 |
29 | pi_fft.o : pi_fft.c
30 | $(CC) $(CFLAGS) $(OFLAGS_PI) -c pi_fft.c -o pi_fft.o
31 |
32 |
33 | fftsgpt.o : ../fftsg.c
34 | $(CC) $(CFLAGS) $(OFLAGS_FFT) -c ../fftsg.c -o fftsgpt.o
35 |
36 |
37 |
38 |
39 | clean:
40 | rm -f *.o
41 |
42 |
--------------------------------------------------------------------------------
/dev/imp/readme.md:
--------------------------------------------------------------------------------
1 | # FFT Implementations on the Internet
2 |
3 | This page aggregates FFT/IFFT implementation resources which can be found on the internet.
4 |
5 | If you are aware of a useful open-source implementation I'd love to hear about it! Post it in an issue or add it directly to this page and make a pull request.
6 |
7 | ## KISS FFT
8 |
9 | The [KISS FFT project](https://github.com/mborgerding/kissfft) aims to provide a C FFT library. It is distributed as a combination of the [Unlicense](https://spdx.org/licenses/Unlicense.html) and [BSD 3-clause](https://spdx.org/licenses/BSD-3-Clause.html) and licenses.
10 |
11 | This library implements some very creative optimizations for maximizing performance while maintaining thread-safety.
12 |
13 | ## Takuya (C/Fortran)
14 |
15 | This is a package to calculate Discrete Fourier/Cosine/Sine Transforms of 1-dimensional sequences of length 2^N. This package contains C and Fortran FFT codes. The original webpage is [here](http://www.kurims.kyoto-u.ac.jp/~ooura/fft.html) and a copy of the source code has been added to this repository. The author has a webpage of [performance assessments](http://www.kurims.kyoto-u.ac.jp/~ooura/fftbmk.html).
16 |
17 | * Source code: [ooura](ooura)
18 |
19 | ## FFTW (C)
20 |
21 | [FFTW](http://www.fftw.org/) is an open-source C library for computing the discrete Fourier transform (DFT) in one or more dimensions, of arbitrary input size, and of both real and complex data. They have [extensive documentation](http://www.fftw.org/fftw3.pdf) which is useful to review.
22 |
23 | FFTW is GPL-licensed, so it may not be suitable for many commercial applications. Their website indicates an alternative licenses may be purchased from the author.
24 |
25 | ## Trasformata di Fourier veloce (C++)
26 | A C++ implementation of the [Cooley–Tukey FFT algorithm](https://en.wikipedia.org/wiki/Cooley%E2%80%93Tukey_FFT_algorithm) can be found on this page (code comments are in Italian). I'm unsure of the copyright or licensing which may apply.
27 |
28 | * Source code: [wiki/Trasformata_di_Fourier_veloce](https://it.wikipedia.org/wiki/Trasformata_di_Fourier_veloce)
29 |
30 | ## Oakley (C#)
31 |
32 | This C# FFT implementation came from a [2010 lecture](http://csclab.murraystate.edu/~bob.pilgrim/565/lectures/lecture_08.pdf) and a 2011 [write-up of a capstone project](https://www.egr.msu.edu/classes/ece480/capstone/fall11/group06/style/Application_Note_ChrisOakley.pdf) by Christopher Oakley. The author was contacted on May 25, 2020 and agreed to its redistribution under a MIT license. These documents are useful because they provide graphs of input waveforms alongside the FFT output.
33 |
34 | * [complex.cs](oakley/complex.cs)
35 | * [fft.cs](oakley/fft.cs)
36 | * [dft.cs](oakley/dft.cs)
37 | * [main.cs](oakley/main.cs)
38 |
39 | ## Sparky (C#)
40 |
41 | This C# FFT/IFFT implementation came from a 2007 blogspot article [Fast Fourier Transform (FFT/IFFT) in C#](http://sdasrath.blogspot.com/2007/11/20071101-fast-fourier-transform-fftifft.html) by Surujlal 'Sparky' Dasrath. Source code indicates a copyright but no license. The original author was contacted on May 24, 2020 and agreed to its redistribution under a MIT license.
42 |
43 | * [fftifft.cs](sparky/fftifft.cs)
44 | * [complex.cs](sparky/complex.cs)
45 |
46 | ## FFTPACK (Fortran)
47 |
48 | Many modern FFT implementations are translated versions of [FFTPACK](https://www.netlib.org/fftpack/) for Fortran. On example of this is [scipy.fftpack.fft](https://docs.scipy.org/doc/scipy/reference/generated/scipy.fftpack.fft.html) for Python.
49 |
50 | I found a C# version of FFTPACK on [dcprojects/SharpFFTPACK](https://github.com/dcprojects/SharpFFTPACK). This looks like it should a useful reference. It is released under the permissive "unlicense" and seems to be a C# translation of [jfftpack](https://github.com/fjfdeztoro/fftpack).
--------------------------------------------------------------------------------
/dev/lowpass.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/swharden/FftSharp/3f5158f7ab146c8fb651028e8dce67407b3ded81/dev/lowpass.png
--------------------------------------------------------------------------------
/dev/mel-scale.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/swharden/FftSharp/3f5158f7ab146c8fb651028e8dce67407b3ded81/dev/mel-scale.png
--------------------------------------------------------------------------------
/dev/microphone-fft.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/swharden/FftSharp/3f5158f7ab146c8fb651028e8dce67407b3ded81/dev/microphone-fft.gif
--------------------------------------------------------------------------------
/dev/notes/readme.md:
--------------------------------------------------------------------------------
1 | # FFT Notes
2 |
3 | ### FFT Resolution
4 | ```cs
5 | var Resolution = 1.0 / (sampleLength * samplePeriod)
6 | ```
7 |
8 | ### Two-Sided FFT to Single-Sided Power Spectrum
9 |
10 | * The two-sided spectrum shows positive and negative power.
11 | * If power of AC signals is centered at 0, positive and negative are identical.
12 | * The two-sided spectrum is redundant for real-world signals centered at zero.
13 | * The single-sided power spectrum of a waveform is the original units * rms^2
14 | * Power Spectral Density (PSD) is amplitude^2 / Hz
15 |
16 | **To combine energy** from the positive and negative spectra, discard the second half of the array and multiply every point (except the DC / the first FFT bin) by 2. Points are then `amplitude / sqrt(2)`. Amplitude divided by `sqrt(2)` of a sine wave is called [root mean square](https://en.wikipedia.org/wiki/Root_mean_square).
17 |
18 | **To get the amplitude spectrum** (in original units rms) apply this formula to every point to N/2 (where N = length of FFT output)
19 |
20 | ```cs
21 | int N = FFT.Length;
22 |
23 | // convert FFT to amplitude spectrum (single-sided)
24 | for (int i=1; i< N/2; i++)
25 | Amplitude[i] = sqrt(2) * FFT[i].Magnitude / N
26 |
27 | // special case for first (DC) bin
28 | Amplitude[0] = Magnitude(FFT[i]) / N
29 | ```
30 |
31 | **Convert to log units** by applying a dB conversion:
32 | ```
33 | Power (dB) = 2 * 10 * Log10(amplitude / reference)
34 | ```
35 |
36 | ```
37 | magnitude = FFTpos * 2 / N
38 | dB = 2 * 10 * np.log10(magnitude / reference)
39 | ```
40 |
41 | ## Resources
42 |
43 | * NI Application Note 41: [The Fundamentals of FFT-Based Signal Analysis
44 | and Measurement ](https://www.sjsu.edu/people/burford.furman/docs/me120/FFT_tutorial_NI.pdf)
45 |
46 | * [FFT Spectrum and Spectral Densities](https://www.ap.com/blog/fft-spectrum-and-spectral-densities-same-data-different-scaling/)
47 |
48 | * [Frequency and the Fast Fourier Transform](https://www.oreilly.com/library/view/elegant-scipy/9781491922927/ch04.html)
49 |
50 | * [FTT and power spectra](http://faculty.jsd.claremont.edu/jmilton/Math_Lab_tool/Labs/Lab9.pdf) with notes about application in biological contexts
51 |
52 | * [Cooley–Tukey FFT algorithm](https://en.wikipedia.org/wiki/Cooley%E2%80%93Tukey_FFT_algorithm) on Wikipedia
53 |
54 | * [Rosetta Code: Fast Fourier transform](https://rosettacode.org/wiki/Fast_Fourier_transform) in many languages
--------------------------------------------------------------------------------
/dev/python/bessel.py:
--------------------------------------------------------------------------------
1 | import numpy as np
2 | xs = np.arange(20)
3 | print(xs)
4 | print(np.i0(xs))
5 |
6 | import matplotlib.pyplot as plt
7 | window = np.kaiser(50, 14)
8 | print(window)
9 | plt.plot(window)
10 | plt.show()
--------------------------------------------------------------------------------
/dev/python/fft.py:
--------------------------------------------------------------------------------
1 | """
2 | This script analyzes standard test audio using Numpy's FFT
3 | methods to compare the output to FftSharp's output.
4 | """
5 |
6 | from os.path import dirname, basename, join, abspath
7 | import matplotlib.pyplot as plt
8 | import numpy as np
9 | np.set_printoptions(suppress=True)
10 |
11 |
12 | def readData(filename: str) -> np.array:
13 | filePath = join(dirname(__file__), f"../data/{filename}")
14 | with open(filePath) as f:
15 | lines = f.readlines()
16 | values = [float(x) for x in lines if x[0] in "+-"]
17 | values = np.array(values)
18 | assert len(values) == 512
19 | assert values[2] == 1.44
20 | return values
21 |
22 |
23 | def saveData(filename: str, values: np.ndarray):
24 | filePath = join(dirname(__file__), f"../data/{filename}")
25 | filePath = abspath(filePath)
26 | if (basename(filePath).lower() == "sample.txt"):
27 | raise Exception("dont overwrite sample values")
28 | lines = [str(x) for x in values]
29 | assert len(lines) == len(values)
30 | with open(filePath, 'w') as f:
31 | f.write("\n".join(lines))
32 | print("saved", filePath)
33 |
34 |
35 | if __name__ == "__main__":
36 |
37 | values = readData("sample.txt")
38 | assert np.sum(values) == 71.52
39 | assert np.sum(np.sin(values)) == 10.417026634786811
40 |
41 | sampleRate = 48_000
42 | samplePeriod = 1.0 / sampleRate
43 |
44 | N = len(values)
45 | fft = np.fft.fft(values)
46 | fftFreq = np.fft.fftfreq(N, samplePeriod)
47 | fftReal = np.fft.rfft(values)
48 | fftMag = np.abs(fftReal) * 2 / N
49 | fftMag[0] = np.abs(fftReal[0]) / N # special case for DC
50 | fftDB = 20 * np.log10(fftMag)
51 | fftPhase = np.angle(fft)
52 |
53 | saveData("fft.txt", fft)
54 | saveData("fftFreq.txt", fftFreq)
55 | saveData("fftReal.txt", fftReal)
56 | saveData("fftMag.txt", fftMag)
57 | saveData("fftDB.txt", fftDB)
58 | saveData("fftPhase.txt", fftPhase)
59 |
--------------------------------------------------------------------------------
/dev/python/fftshift.py:
--------------------------------------------------------------------------------
1 | import scipy.fft
2 | import numpy as np
3 |
4 | if __name__ == "__main__":
5 |
6 | values = np.arange(7)
7 | print(values)
8 | print(scipy.fft.fftshift(values))
9 |
10 | values = np.arange(8)
11 | print(values)
12 | print(scipy.fft.fftshift(values))
13 |
14 | values = [ 1, 2, 3, 4, 5, 6, 7]
15 | print(values)
16 | print(", ".join([f"{x.real:.05f}" for x in scipy.fft.fft(values)]))
17 | print(", ".join([f"{x.imag:.05f}" for x in scipy.fft.fft(values)]))
--------------------------------------------------------------------------------
/dev/python/window-functions/make-window-tests.py:
--------------------------------------------------------------------------------
1 | import numpy as np
2 | import scipy.signal
3 | import scipy.signal.windows
4 |
5 |
6 | def printCsArray(name: str, values: np.ndarray, precision: int = 8):
7 | arrayContents = ", ".join([str(np.round(x, precision)) for x in values])
8 | assignmentString = f"public double[] Known_{name}_{len(values)} = {{ {arrayContents} }};"
9 | print(assignmentString)
10 |
11 |
12 | def printCsArrays(count: int):
13 | printCsArray("bartlett", scipy.signal.windows.bartlett(count))
14 | printCsArray("blackman", scipy.signal.windows.blackman(count))
15 | printCsArray("cosine", scipy.signal.windows.cosine(count))
16 | printCsArray("flattop", scipy.signal.windows.flattop(count))
17 | printCsArray("hamming", scipy.signal.windows.hamming(count))
18 | printCsArray("hanning", scipy.signal.windows.hann(count))
19 | printCsArray("kaiser14", scipy.signal.windows.kaiser(count, beta=14))
20 | printCsArray("tukey", scipy.signal.windows.tukey(count))
21 |
22 |
23 | if __name__ == "__main__":
24 | printCsArrays(13)
25 | printCsArrays(14)
26 |
--------------------------------------------------------------------------------
/dev/quickstart/audio-windowed.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/swharden/FftSharp/3f5158f7ab146c8fb651028e8dce67407b3ded81/dev/quickstart/audio-windowed.png
--------------------------------------------------------------------------------
/dev/quickstart/audio.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/swharden/FftSharp/3f5158f7ab146c8fb651028e8dce67407b3ded81/dev/quickstart/audio.png
--------------------------------------------------------------------------------
/dev/quickstart/fft-windowed.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/swharden/FftSharp/3f5158f7ab146c8fb651028e8dce67407b3ded81/dev/quickstart/fft-windowed.png
--------------------------------------------------------------------------------
/dev/quickstart/fft.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/swharden/FftSharp/3f5158f7ab146c8fb651028e8dce67407b3ded81/dev/quickstart/fft.png
--------------------------------------------------------------------------------
/dev/quickstart/periodogram.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/swharden/FftSharp/3f5158f7ab146c8fb651028e8dce67407b3ded81/dev/quickstart/periodogram.png
--------------------------------------------------------------------------------
/dev/quickstart/time-series.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/swharden/FftSharp/3f5158f7ab146c8fb651028e8dce67407b3ded81/dev/quickstart/time-series.png
--------------------------------------------------------------------------------
/dev/quickstart/windows.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/swharden/FftSharp/3f5158f7ab146c8fb651028e8dce67407b3ded81/dev/quickstart/windows.png
--------------------------------------------------------------------------------
/dev/screenshot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/swharden/FftSharp/3f5158f7ab146c8fb651028e8dce67407b3ded81/dev/screenshot.png
--------------------------------------------------------------------------------
/dev/spectrogram.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/swharden/FftSharp/3f5158f7ab146c8fb651028e8dce67407b3ded81/dev/spectrogram.png
--------------------------------------------------------------------------------
/dev/windows.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/swharden/FftSharp/3f5158f7ab146c8fb651028e8dce67407b3ded81/dev/windows.png
--------------------------------------------------------------------------------
/src/FftSharp.Benchmark/BenchmarkLoadData.cs:
--------------------------------------------------------------------------------
1 | namespace FftSharp.Benchmark;
2 |
3 | public static class BenchmarkLoadData
4 | {
5 | public static double[] Double(string fileName) =>
6 | File.ReadLines(fileName)
7 | .Where(x => !x.StartsWith('#') && x.Length > 1)
8 | .Select(x => double.Parse(x))
9 | .ToArray();
10 |
11 | public static System.Numerics.Complex[] Complex(string fileName) =>
12 | File.ReadLines(fileName)
13 | .Select(x => x.Trim('(').Trim(')').Trim('j'))
14 | .Select(x => x.Replace("-", " -").Replace("+", " +").Trim())
15 | .Select(x => new System.Numerics.Complex(double.Parse(x.Split(' ')[0]), double.Parse(x.Split(' ')[1])))
16 | .ToArray();
17 | }
18 |
--------------------------------------------------------------------------------
/src/FftSharp.Benchmark/Benchmarking.md:
--------------------------------------------------------------------------------
1 | # Benchmarking
2 |
3 | ## Benchmarking FFTSharp
4 |
5 | To run the benchmarks navigate to the `src/FftSharp.Benchmark` directory and run the following command:
6 |
7 | ```bash
8 | dotnet run -c Release
9 | ```
10 |
11 | ## Results 12/20/2023
12 |
13 | ### BluesteinSizeBenchmark
14 | ```
15 | BenchmarkDotNet v0.13.11, Windows 11 (10.0.22631.2861/23H2/2023Update/SunValley3)
16 | Intel Core i7-1065G7 CPU 1.30GHz, 1 CPU, 8 logical and 4 physical cores
17 | .NET SDK 8.0.100
18 | [Host] : .NET 6.0.25 (6.0.2523.51912), X64 RyuJIT AVX2
19 | .NET 6.0 : .NET 6.0.25 (6.0.2523.51912), X64 RyuJIT AVX2
20 | .NET 8.0 : .NET 8.0.0 (8.0.23.53103), X64 RyuJIT AVX-512F+CD+BW+DQ+VL+VBMI
21 | ```
22 | | Method | Job | Runtime | DataLength | Mean | Error | StdDev | Gen0 | Gen1 | Gen2 | Allocated |
23 | |---------- |--------- |--------- |----------- |-------------:|-------------:|-------------:|---------:|---------:|---------:|------------:|
24 | | **Bluestein** | **.NET 6.0** | **.NET 6.0** | **100** | **26.40 μs** | **0.401 μs** | **0.375 μs** | **7.1716** | **0.0305** | **-** | **29.36 KB** |
25 | | Bluestein | .NET 8.0 | .NET 8.0 | 100 | 24.97 μs | 0.321 μs | 0.284 μs | 7.1716 | - | - | 29.36 KB |
26 | | **Bluestein** | **.NET 6.0** | **.NET 6.0** | **1000** | **247.14 μs** | **3.809 μs** | **3.181 μs** | **58.1055** | **16.1133** | **-** | **239.49 KB** |
27 | | Bluestein | .NET 8.0 | .NET 8.0 | 1000 | 239.35 μs | 4.046 μs | 3.379 μs | 58.1055 | 16.3574 | - | 239.49 KB |
28 | | **Bluestein** | **.NET 6.0** | **.NET 6.0** | **10000** | **5,426.03 μs** | **88.896 μs** | **102.372 μs** | **984.3750** | **984.3750** | **984.3750** | **3641.08 KB** |
29 | | Bluestein | .NET 8.0 | .NET 8.0 | 10000 | 5,348.21 μs | 103.018 μs | 86.025 μs | 984.3750 | 984.3750 | 984.3750 | 3641.04 KB |
30 | | **Bluestein** | **.NET 6.0** | **.NET 6.0** | **100000** | **84,122.15 μs** | **1,655.385 μs** | **2,374.104 μs** | **833.3333** | **833.3333** | **833.3333** | **29749.57 KB** |
31 | | Bluestein | .NET 8.0 | .NET 8.0 | 100000 | 83,047.50 μs | 1,654.760 μs | 2,718.818 μs | 666.6667 | 666.6667 | 666.6667 | 29751.45 KB |
32 |
33 | ### FftBenchmark
34 | ```
35 | BenchmarkDotNet v0.13.11, Windows 11 (10.0.22631.2861/23H2/2023Update/SunValley3)
36 | Intel Core i7-1065G7 CPU 1.30GHz, 1 CPU, 8 logical and 4 physical cores
37 | .NET SDK 8.0.100
38 | [Host] : .NET 6.0.25 (6.0.2523.51912), X64 RyuJIT AVX2
39 | .NET 6.0 : .NET 6.0.25 (6.0.2523.51912), X64 RyuJIT AVX2
40 | .NET 8.0 : .NET 8.0.0 (8.0.23.53103), X64 RyuJIT AVX-512F+CD+BW+DQ+VL+VBMI
41 | ```
42 | | Method | Job | Runtime | Mean | Error | StdDev | Gen0 | Gen1 | Allocated |
43 | |------------------------ |--------- |--------- |----------:|---------:|---------:|--------:|--------:|----------:|
44 | | FFT_Forward | .NET 6.0 | .NET 6.0 | 41.47 μs | 0.722 μs | 0.676 μs | 1.9531 | - | 8.02 KB |
45 | | FFT_ForwardReal | .NET 6.0 | .NET 6.0 | 42.72 μs | 0.715 μs | 0.669 μs | 2.9297 | - | 12.06 KB |
46 | | FFT_BluesteinComparason | .NET 6.0 | .NET 6.0 | 233.21 μs | 3.444 μs | 3.053 μs | 54.4434 | 15.3809 | 224.24 KB |
47 | | FFT_Forward | .NET 8.0 | .NET 8.0 | 41.08 μs | 0.749 μs | 0.585 μs | 1.9531 | - | 8.02 KB |
48 | | FFT_ForwardReal | .NET 8.0 | .NET 8.0 | 41.79 μs | 0.788 μs | 0.698 μs | 2.9297 | - | 12.06 KB |
49 | | FFT_BluesteinComparason | .NET 8.0 | .NET 8.0 | 223.61 μs | 4.330 μs | 4.633 μs | 54.4434 | 15.3809 | 224.23 KB |
--------------------------------------------------------------------------------
/src/FftSharp.Benchmark/BluesteinSizeBenchmark.cs:
--------------------------------------------------------------------------------
1 | using BenchmarkDotNet.Attributes;
2 | using BenchmarkDotNet.Jobs;
3 | using MathNet.Numerics;
4 | namespace FftSharp.Benchmark;
5 |
6 | [MemoryDiagnoser]
7 | [SimpleJob(RuntimeMoniker.Net60)]
8 | [SimpleJob(RuntimeMoniker.Net80)]
9 | public class BluesteinSizeBenchmark
10 | {
11 | private double[] Sample;
12 | [Params(100, 1000, 10_000, 100_000)]
13 | public int DataLength { get; set; }
14 | public double Frequency = 60;
15 | public double SampleRate = 1000;
16 |
17 | [GlobalSetup]
18 | public void BluesteinSizeBenchmarkSetup()
19 | {
20 | this.Sample = Generate.Sinusoidal(this.DataLength, this.SampleRate, this.Frequency, 1);
21 | if (this.Sample.Length != this.DataLength)
22 | {
23 | throw new Exception("Sample length does not match DataLength");
24 | }
25 | }
26 |
27 | [Benchmark]
28 | public void Bluestein()
29 | {
30 | var something = FftSharp.Bluestein.Forward(Sample);
31 | }
32 | }
33 |
34 |
--------------------------------------------------------------------------------
/src/FftSharp.Benchmark/FftBenchmark.cs:
--------------------------------------------------------------------------------
1 | using BenchmarkDotNet.Attributes;
2 | using BenchmarkDotNet.Jobs;
3 |
4 | namespace FftSharp.Benchmark;
5 | [SimpleJob(RuntimeMoniker.Net60)]
6 | [SimpleJob(RuntimeMoniker.Net80)]
7 | [MemoryDiagnoser]
8 | public class FftBenchmark
9 | {
10 | private double[] Sample;
11 |
12 | [GlobalSetup]
13 | public void FftBenchmarkSetup()
14 | {
15 | this.Sample = BenchmarkLoadData.Double("sample.txt");
16 | }
17 |
18 | [Benchmark]
19 | public void FFT_Forward()
20 | {
21 | var something = FFT.Forward(this.Sample);
22 | }
23 |
24 | [Benchmark]
25 | public void FFT_ForwardReal()
26 | {
27 | var something = FFT.ForwardReal(this.Sample);
28 | }
29 |
30 | [Benchmark]
31 | public void FFT_BluesteinComparason()
32 | {
33 | var something = FftSharp.Bluestein.Forward(Sample);
34 | }
35 | }
36 |
37 |
--------------------------------------------------------------------------------
/src/FftSharp.Benchmark/FftSharp.Benchmark.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Exe
5 | net8.0
6 | enable
7 | $(NoWarn);CA1018;CA5351;CA1825;CA8618
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 | Always
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/src/FftSharp.Benchmark/Program.cs:
--------------------------------------------------------------------------------
1 | using BenchmarkDotNet.Configs;
2 | using BenchmarkDotNet.Environments;
3 | using BenchmarkDotNet.Jobs;
4 | using BenchmarkDotNet.Running;
5 |
6 | namespace FftSharp.Benchmark;
7 |
8 | public class Program
9 | {
10 | static void Main(string[] args)
11 | {
12 | Console.WriteLine("FftSharp Benchmarks!");
13 | BenchmarkRunner.Run();
14 | BenchmarkRunner.Run();
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/src/FftSharp.Benchmark/sample.txt:
--------------------------------------------------------------------------------
1 | # These data simulate a sample of audio with the following parameters:
2 | # sample rate: 48 kHz
3 | # points: 512 (2^9)
4 | # offset: 0.1 (above zero)
5 | # tone: 2 kHz (amplitude 2)
6 | # tone: 10 kHz (amplitude 10)
7 | # tone: 20 kHz (amplitude .5)
8 | #
9 | # Quick and dirty checksum
10 | # the sum of these values is 71.52
11 | # the sum of the sins of these values is 10.417026634786811
12 | +0.33
13 | +2.15
14 | +1.44
15 | +1.37
16 | +0.24
17 | +2.60
18 | +3.51
19 | +1.98
20 | +1.88
21 | +0.08
22 | +1.82
23 | +1.30
24 | +0.23
25 | -1.16
26 | -1.35
27 | -0.58
28 | -0.84
29 | -1.35
30 | -2.72
31 | -2.53
32 | -0.02
33 | -0.76
34 | -0.48
35 | -2.10
36 | +0.30
37 | +1.86
38 | +1.60
39 | +1.49
40 | +0.58
41 | +2.12
42 | +2.79
43 | +1.99
44 | +1.20
45 | +0.80
46 | +2.18
47 | +1.60
48 | -0.37
49 | -1.25
50 | -1.99
51 | +0.35
52 | -1.19
53 | -1.62
54 | -3.28
55 | -2.57
56 | +0.07
57 | -0.81
58 | -1.13
59 | -1.68
60 | -0.25
61 | +1.55
62 | +1.08
63 | +1.53
64 | +0.65
65 | +2.53
66 | +2.79
67 | +2.42
68 | +1.72
69 | +0.54
70 | +2.39
71 | +1.51
72 | +0.22
73 | -1.42
74 | -1.44
75 | +0.29
76 | -1.61
77 | -1.50
78 | -3.23
79 | -2.20
80 | -0.01
81 | -1.39
82 | -0.47
83 | -1.65
84 | +0.25
85 | +2.05
86 | +1.48
87 | +0.91
88 | +0.76
89 | +2.76
90 | +2.73
91 | +2.45
92 | +1.09
93 | +0.28
94 | +2.07
95 | +1.16
96 | +0.27
97 | -1.17
98 | -1.50
99 | +0.20
100 | -0.91
101 | -1.58
102 | -2.46
103 | -2.55
104 | -0.31
105 | -0.94
106 | -1.13
107 | -1.85
108 | +0.42
109 | +1.56
110 | +0.85
111 | +0.88
112 | +0.66
113 | +2.73
114 | +3.23
115 | +2.47
116 | +1.12
117 | +0.74
118 | +1.60
119 | +1.73
120 | +0.28
121 | -1.54
122 | -2.18
123 | -0.50
124 | -1.09
125 | -1.39
126 | -2.91
127 | -2.69
128 | -0.16
129 | -1.04
130 | -1.24
131 | -1.52
132 | -0.39
133 | +1.69
134 | +1.52
135 | +0.87
136 | +0.31
137 | +2.75
138 | +3.56
139 | +2.53
140 | +1.29
141 | +0.33
142 | +1.81
143 | +1.34
144 | +0.13
145 | -1.58
146 | -2.05
147 | -0.11
148 | -0.85
149 | -1.73
150 | -3.30
151 | -2.10
152 | -0.43
153 | -0.67
154 | -1.34
155 | -1.43
156 | +0.22
157 | +2.16
158 | +1.35
159 | +1.38
160 | +0.21
161 | +2.23
162 | +3.21
163 | +1.79
164 | +1.90
165 | +0.38
166 | +1.60
167 | +1.10
168 | +0.44
169 | -1.07
170 | -1.69
171 | -0.09
172 | -0.73
173 | -2.26
174 | -2.89
175 | -2.68
176 | -0.02
177 | -0.96
178 | -0.89
179 | -1.58
180 | +0.27
181 | +2.33
182 | +0.97
183 | +0.87
184 | +0.50
185 | +2.52
186 | +2.82
187 | +1.61
188 | +1.13
189 | -0.04
190 | +1.98
191 | +1.28
192 | -0.38
193 | -1.24
194 | -1.52
195 | -0.40
196 | -0.79
197 | -2.31
198 | -2.89
199 | -1.88
200 | +0.16
201 | -1.59
202 | -0.81
203 | -1.86
204 | +0.57
205 | +1.92
206 | +1.44
207 | +1.13
208 | +0.45
209 | +3.02
210 | +3.49
211 | +2.51
212 | +1.15
213 | -0.06
214 | +2.43
215 | +1.01
216 | +0.48
217 | -1.09
218 | -1.55
219 | -0.09
220 | -1.35
221 | -1.35
222 | -3.37
223 | -2.15
224 | -0.71
225 | -1.41
226 | -0.97
227 | -1.55
228 | -0.14
229 | +1.64
230 | +0.91
231 | +1.59
232 | +0.17
233 | +2.65
234 | +3.16
235 | +2.20
236 | +1.24
237 | -0.17
238 | +1.63
239 | +1.71
240 | +0.31
241 | -0.74
242 | -1.68
243 | -0.35
244 | -1.43
245 | -1.87
246 | -3.20
247 | -1.95
248 | -0.34
249 | -0.97
250 | -1.15
251 | -1.76
252 | -0.16
253 | +2.33
254 | +1.28
255 | +0.81
256 | +1.02
257 | +3.00
258 | +2.76
259 | +2.31
260 | +0.99
261 | -0.00
262 | +1.60
263 | +0.94
264 | +0.33
265 | -1.53
266 | -1.49
267 | +0.04
268 | -1.13
269 | -2.10
270 | -2.56
271 | -1.98
272 | -0.39
273 | -0.70
274 | -0.66
275 | -1.67
276 | -0.06
277 | +2.11
278 | +1.09
279 | +1.45
280 | +1.03
281 | +2.65
282 | +2.69
283 | +2.16
284 | +1.89
285 | +0.68
286 | +2.07
287 | +0.97
288 | -0.40
289 | -1.08
290 | -1.66
291 | -0.23
292 | -0.83
293 | -2.02
294 | -2.61
295 | -2.32
296 | -0.00
297 | -1.07
298 | -0.94
299 | -1.97
300 | +0.23
301 | +1.89
302 | +0.98
303 | +1.06
304 | +0.83
305 | +2.50
306 | +3.52
307 | +1.88
308 | +1.09
309 | -0.04
310 | +2.19
311 | +1.04
312 | +0.13
313 | -1.12
314 | -1.56
315 | -0.12
316 | -1.60
317 | -1.90
318 | -3.28
319 | -1.98
320 | -0.27
321 | -0.90
322 | -0.83
323 | -2.12
324 | +0.17
325 | +1.79
326 | +1.66
327 | +0.93
328 | +0.15
329 | +2.32
330 | +3.23
331 | +2.34
332 | +1.15
333 | +0.07
334 | +1.55
335 | +1.28
336 | -0.11
337 | -0.79
338 | -1.51
339 | -0.08
340 | -0.75
341 | -2.14
342 | -2.45
343 | -1.99
344 | +0.06
345 | -1.14
346 | -0.62
347 | -1.78
348 | +0.15
349 | +1.64
350 | +1.09
351 | +1.20
352 | +0.45
353 | +2.70
354 | +3.20
355 | +2.47
356 | +1.81
357 | +0.11
358 | +2.21
359 | +1.18
360 | +0.07
361 | -0.83
362 | -2.12
363 | +0.30
364 | -1.18
365 | -1.48
366 | -2.45
367 | -2.57
368 | -0.34
369 | -1.28
370 | -1.28
371 | -1.87
372 | +0.22
373 | +1.92
374 | +1.58
375 | +1.17
376 | +0.79
377 | +2.83
378 | +2.72
379 | +1.64
380 | +1.51
381 | +0.44
382 | +2.10
383 | +1.65
384 | +0.46
385 | -1.39
386 | -1.96
387 | -0.01
388 | -1.04
389 | -2.26
390 | -2.87
391 | -1.85
392 | -0.67
393 | -1.13
394 | -1.40
395 | -1.98
396 | +0.59
397 | +1.37
398 | +1.00
399 | +0.84
400 | +0.55
401 | +2.61
402 | +3.46
403 | +1.76
404 | +1.02
405 | -0.04
406 | +2.31
407 | +1.67
408 | +0.35
409 | -1.39
410 | -2.16
411 | -0.48
412 | -1.52
413 | -1.76
414 | -2.67
415 | -2.01
416 | -0.60
417 | -1.21
418 | -1.42
419 | -1.85
420 | +0.08
421 | +1.69
422 | +1.27
423 | +1.22
424 | +0.83
425 | +2.23
426 | +2.70
427 | +1.68
428 | +1.42
429 | +0.56
430 | +1.91
431 | +1.55
432 | +0.06
433 | -1.55
434 | -1.75
435 | -0.57
436 | -0.92
437 | -1.99
438 | -2.70
439 | -2.13
440 | -0.37
441 | -1.06
442 | -0.63
443 | -1.71
444 | +0.51
445 | +1.74
446 | +1.48
447 | +1.39
448 | +0.78
449 | +2.27
450 | +3.52
451 | +2.13
452 | +1.89
453 | -0.14
454 | +2.08
455 | +0.99
456 | +0.57
457 | -1.19
458 | -1.90
459 | +0.32
460 | -1.64
461 | -1.70
462 | -3.09
463 | -1.84
464 | +0.03
465 | -1.15
466 | -0.80
467 | -2.04
468 | +0.59
469 | +2.02
470 | +0.72
471 | +1.69
472 | +0.73
473 | +2.38
474 | +3.42
475 | +2.48
476 | +1.42
477 | -0.01
478 | +2.04
479 | +1.22
480 | -0.02
481 | -1.11
482 | -1.95
483 | -0.32
484 | -0.87
485 | -1.55
486 | -2.67
487 | -2.44
488 | -0.30
489 | -1.18
490 | -1.39
491 | -1.80
492 | +0.52
493 | +2.11
494 | +1.32
495 | +1.63
496 | +0.27
497 | +2.88
498 | +3.16
499 | +1.99
500 | +1.64
501 | +0.53
502 | +2.12
503 | +0.90
504 | -0.22
505 | -1.59
506 | -1.45
507 | +0.05
508 | -1.46
509 | -1.73
510 | -2.76
511 | -2.06
512 | +0.10
513 | -1.56
514 | -0.92
515 | -1.60
516 | -0.14
517 | +1.35
518 | +0.83
519 | +0.88
520 | +0.76
521 | +2.30
522 | +3.16
523 | +2.11
--------------------------------------------------------------------------------
/src/FftSharp.Demo/FftSharp.Demo.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 | net8.0-windows
4 | WinExe
5 | false
6 | true
7 | true
8 | NU1701;CA1416
9 |
10 |
11 | icon.ico
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/src/FftSharp.Demo/FormAudio.cs:
--------------------------------------------------------------------------------
1 | using ScottPlot;
2 | using System;
3 | using System.Linq;
4 | using System.Windows.Forms;
5 |
6 | #pragma warning disable CA1416 // Validate platform compatibility
7 |
8 | namespace FftSharp.Demo
9 | {
10 | public partial class FormAudio : Form
11 | {
12 | readonly int sampleRate = 48000;
13 | readonly int fftSize = 4096; // 2^12
14 | readonly double[] OriginalAudio;
15 |
16 | public FormAudio()
17 | {
18 | InitializeComponent();
19 | plotAudio.Plot.Axes.TightMargins();
20 | plotFFT.Plot.Axes.TightMargins();
21 |
22 | OriginalAudio = new double[fftSize];
23 | GenerateSignal();
24 |
25 | IWindow[] windows = Window.GetWindows();
26 | comboBox1.Items.AddRange(windows);
27 | comboBox1.SelectedIndex = windows.ToList().FindIndex(x => x.Name == "Hanning");
28 | }
29 |
30 | private void OnSelectedWindow(object sender, EventArgs e)
31 | {
32 | IWindow window = (IWindow)comboBox1.SelectedItem;
33 | if (window is null)
34 | {
35 | richTextBox1.Clear();
36 | return;
37 | }
38 | else
39 | {
40 | richTextBox1.Text = window.Description;
41 | }
42 |
43 | // apply window
44 | double[] audio = new double[OriginalAudio.Length];
45 | Array.Copy(OriginalAudio, audio, OriginalAudio.Length);
46 | window.ApplyInPlace(audio);
47 |
48 | UpdateKernel(window);
49 | UpdateWindowed(audio);
50 | UpdateFFT(audio);
51 | }
52 |
53 | private void tbNoise_KeyUp(object sender, KeyEventArgs e) => tbNoise_MouseUp(null, null);
54 |
55 | private void tbNoise_MouseUp(object sender, MouseEventArgs e)
56 | {
57 | GenerateSignal();
58 | OnSelectedWindow(null, null);
59 | }
60 |
61 | private void cbLog_CheckedChanged(object sender, EventArgs e) => OnSelectedWindow(null, null);
62 |
63 | private void GenerateSignal()
64 | {
65 | for (int i = 0; i < OriginalAudio.Length; i++)
66 | OriginalAudio[i] = 0;
67 |
68 | SampleData.AddWhiteNoise(OriginalAudio, tbNoise.Value / 10.0);
69 | SampleData.AddSin(OriginalAudio, sampleRate, 2_000, 1);
70 | SampleData.AddSin(OriginalAudio, sampleRate, 10_000, 2);
71 | SampleData.AddSin(OriginalAudio, sampleRate, 20_000, .5);
72 | PlotOriginalSignal();
73 | }
74 |
75 | private void PlotOriginalSignal()
76 | {
77 | plotAudio.Plot.Clear();
78 | plotAudio.Plot.Add.Signal(OriginalAudio, 1.0 / (sampleRate / 1e3));
79 | plotAudio.Plot.Title("Input Signal");
80 | plotAudio.Plot.YLabel("Amplitude");
81 | plotAudio.Plot.XLabel("Time (milliseconds)");
82 | plotAudio.Plot.Axes.AutoScale();
83 | plotAudio.Refresh();
84 | }
85 |
86 | private void UpdateKernel(IWindow window)
87 | {
88 | double[] kernel = window.Create(fftSize);
89 | double[] pad = ScottPlot.Generate.Zeros(kernel.Length / 4);
90 | double[] ys = pad.Concat(kernel).Concat(pad).ToArray();
91 |
92 | plotKernel.Plot.Clear();
93 | var sig = plotKernel.Plot.Add.Signal(ys, 1.0 / (sampleRate / 1e3));
94 | sig.Color = Colors.Red;
95 | plotKernel.Plot.Axes.AutoScale();
96 | plotKernel.Plot.Title($"{window} Window");
97 | plotKernel.Plot.YLabel("Amplitude");
98 | plotKernel.Plot.XLabel("Time (milliseconds)");
99 | plotKernel.Refresh();
100 | }
101 |
102 | private void UpdateWindowed(double[] audio)
103 | {
104 | plotWindowed.Plot.Clear();
105 | plotWindowed.Plot.Add.Signal(audio, 1.0 / (sampleRate / 1e3));
106 | plotWindowed.Plot.Title("Windowed Signal");
107 | plotWindowed.Plot.YLabel("Amplitude");
108 | plotWindowed.Plot.XLabel("Time (milliseconds)");
109 | plotWindowed.Plot.Axes.AutoScale();
110 | plotWindowed.Refresh();
111 | }
112 |
113 | private void UpdateFFT(double[] audio)
114 | {
115 | System.Numerics.Complex[] spectrum = FftSharp.FFT.Forward(audio);
116 | double[] ys = cbLog.Checked ? FftSharp.FFT.Power(spectrum) : FftSharp.FFT.Magnitude(spectrum);
117 | string yLabel = cbLog.Checked ? "Power (dB)" : "Magnitude (RMS²)";
118 |
119 | plotFFT.Plot.Clear();
120 | plotFFT.Plot.Add.Signal(ys, 1.0 / ((double)fftSize / sampleRate));
121 | plotFFT.Plot.Title("Fast Fourier Transform");
122 | plotFFT.Plot.YLabel(yLabel);
123 | plotFFT.Plot.XLabel("Frequency (Hz)");
124 | plotFFT.Plot.Axes.AutoScale();
125 | plotFFT.Refresh();
126 | }
127 | }
128 | }
129 |
--------------------------------------------------------------------------------
/src/FftSharp.Demo/FormAudio.resx:
--------------------------------------------------------------------------------
1 |
2 |
3 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 | text/microsoft-resx
110 |
111 |
112 | 2.0
113 |
114 |
115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
116 |
117 |
118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
119 |
120 |
--------------------------------------------------------------------------------
/src/FftSharp.Demo/FormMenu.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.ComponentModel;
4 | using System.Data;
5 | using System.Drawing;
6 | using System.Linq;
7 | using System.Text;
8 | using System.Threading.Tasks;
9 | using System.Windows.Forms;
10 |
11 | namespace FftSharp.Demo
12 | {
13 | public partial class FormMenu : Form
14 | {
15 | public FormMenu()
16 | {
17 | InitializeComponent();
18 | }
19 |
20 | private void btnQuickstart_Click(object sender, EventArgs e) => new FormQuickstart().ShowDialog();
21 |
22 | private void btnWindowInspector_Click(object sender, EventArgs e) => new FormWindowInspector().ShowDialog();
23 |
24 | private void btnSimAudio_Click(object sender, EventArgs e) => new FormAudio().ShowDialog();
25 |
26 | private void btnMicrophone_Click(object sender, EventArgs e) => new FormMicrophone().ShowDialog();
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/FftSharp.Demo/FormMenu.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 |
--------------------------------------------------------------------------------
/src/FftSharp.Demo/FormMicrophone.cs:
--------------------------------------------------------------------------------
1 | using ScottPlot;
2 | using System;
3 | using System.Linq;
4 | using System.Windows.Forms;
5 |
6 | #pragma warning disable CA1416 // Validate platform compatibility
7 |
8 | namespace FftSharp.Demo
9 | {
10 | public partial class FormMicrophone : Form
11 | {
12 | const int SAMPLE_RATE = 48000;
13 | NAudio.Wave.WaveInEvent wvin;
14 | double MaxLevel = 0;
15 |
16 | public FormMicrophone()
17 | {
18 | InitializeComponent();
19 |
20 | formsPlot1.Plot.Axes.TightMargins();
21 |
22 | cbDevices.Items.Clear();
23 | for (int i = 0; i < NAudio.Wave.WaveIn.DeviceCount; i++)
24 | cbDevices.Items.Add(NAudio.Wave.WaveIn.GetCapabilities(i).ProductName);
25 | if (cbDevices.Items.Count > 0)
26 | cbDevices.SelectedIndex = 0;
27 | else
28 | MessageBox.Show("ERROR: no recording devices available");
29 | }
30 |
31 | private void cbDevices_SelectedIndexChanged(object sender, EventArgs e)
32 | {
33 | wvin?.Dispose();
34 | wvin = new NAudio.Wave.WaveInEvent();
35 | wvin.DeviceNumber = cbDevices.SelectedIndex;
36 | wvin.WaveFormat = new NAudio.Wave.WaveFormat(rate: SAMPLE_RATE, bits: 16, channels: 1);
37 | wvin.DataAvailable += OnDataAvailable;
38 | wvin.BufferMilliseconds = 20;
39 | wvin.StartRecording();
40 | }
41 |
42 | double[] lastBuffer;
43 | private void OnDataAvailable(object sender, NAudio.Wave.WaveInEventArgs args)
44 | {
45 | int bytesPerSample = wvin.WaveFormat.BitsPerSample / 8;
46 | int samplesRecorded = args.BytesRecorded / bytesPerSample;
47 | if (lastBuffer is null || lastBuffer.Length != samplesRecorded)
48 | lastBuffer = new double[samplesRecorded];
49 | for (int i = 0; i < samplesRecorded; i++)
50 | lastBuffer[i] = BitConverter.ToInt16(args.Buffer, i * bytesPerSample);
51 | }
52 |
53 | ScottPlot.Plottables.Signal signalPlot;
54 | double[] SignalData = null!;
55 | ScottPlot.Plottables.VerticalLine peakLine;
56 | private void timer1_Tick(object sender, EventArgs e)
57 | {
58 | if (lastBuffer is null)
59 | return;
60 |
61 | var window = new Windows.Hanning();
62 | double[] windowed = window.Apply(lastBuffer);
63 | double[] zeroPadded = FftSharp.Pad.ZeroPad(windowed);
64 | System.Numerics.Complex[] spectrum = FftSharp.FFT.Forward(zeroPadded);
65 | double[] fftPower = cbDecibel.Checked ?
66 | FftSharp.FFT.Power(spectrum) :
67 | FftSharp.FFT.Magnitude(spectrum);
68 | double[] fftFreq = FftSharp.FFT.FrequencyScale(fftPower.Length, SAMPLE_RATE);
69 |
70 | // determine peak frequency
71 | double peakFreq = 0;
72 | double peakPower = 0;
73 | for (int i = 0; i < fftPower.Length; i++)
74 | {
75 | if (fftPower[i] > peakPower)
76 | {
77 | peakPower = fftPower[i];
78 | peakFreq = fftFreq[i];
79 | }
80 | }
81 | lblPeak.Text = $"Peak Frequency: {peakFreq:N0} Hz";
82 |
83 | formsPlot1.Plot.XLabel("Frequency Hz");
84 |
85 | // make the plot for the first time, otherwise update the existing plot
86 | if (!formsPlot1.Plot.GetPlottables().Any())
87 | {
88 | double samplePeriod = 1.0 / (2.0 * fftPower.Length / SAMPLE_RATE);
89 | SignalData = fftPower;
90 | signalPlot = formsPlot1.Plot.Add.Signal(fftPower, samplePeriod);
91 | peakLine = formsPlot1.Plot.Add.VerticalLine(peakFreq, 2, Colors.Red.WithAlpha(.5));
92 | }
93 | else
94 | {
95 | Array.Copy(fftPower, SignalData, fftPower.Length);
96 | peakLine.X = peakFreq;
97 | peakLine.IsVisible = cbPeak.Checked;
98 | }
99 |
100 | if (cbAutoAxis.Checked)
101 | {
102 | try
103 | {
104 | formsPlot1.Plot.Axes.AutoScale();
105 | MaxLevel = Math.Max(MaxLevel, formsPlot1.Plot.Axes.GetLimits().Top);
106 | double minLevel = cbDecibel.Checked ? -80 : -MaxLevel / 100;
107 | formsPlot1.Plot.Axes.SetLimitsY(minLevel, MaxLevel);
108 | }
109 | catch (Exception ex)
110 | {
111 | System.Diagnostics.Debug.WriteLine(ex);
112 | }
113 | }
114 |
115 | try
116 | {
117 | formsPlot1.Refresh();
118 | }
119 | catch (Exception ex)
120 | {
121 | System.Diagnostics.Debug.WriteLine(ex);
122 | }
123 | }
124 |
125 | private void cbAutoAxis_CheckedChanged(object sender, EventArgs e)
126 | {
127 | MaxLevel = 0;
128 | }
129 |
130 | private void cbDecibel_CheckedChanged(object sender, EventArgs e)
131 | {
132 | MaxLevel = 0;
133 | }
134 | }
135 | }
136 |
--------------------------------------------------------------------------------
/src/FftSharp.Demo/FormMicrophone.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 |
--------------------------------------------------------------------------------
/src/FftSharp.Demo/FormQuickstart.Designer.cs:
--------------------------------------------------------------------------------
1 | namespace FftSharp.Demo
2 | {
3 | partial class FormQuickstart
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 | tableLayoutPanel1 = new System.Windows.Forms.TableLayoutPanel();
32 | formsPlot1 = new ScottPlot.WinForms.FormsPlot();
33 | formsPlot2 = new ScottPlot.WinForms.FormsPlot();
34 | trackBar1 = new System.Windows.Forms.TrackBar();
35 | label1 = new System.Windows.Forms.Label();
36 | tableLayoutPanel1.SuspendLayout();
37 | ((System.ComponentModel.ISupportInitialize)trackBar1).BeginInit();
38 | SuspendLayout();
39 | //
40 | // tableLayoutPanel1
41 | //
42 | tableLayoutPanel1.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right;
43 | tableLayoutPanel1.ColumnCount = 1;
44 | tableLayoutPanel1.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 50F));
45 | tableLayoutPanel1.Controls.Add(formsPlot1, 0, 0);
46 | tableLayoutPanel1.Controls.Add(formsPlot2, 0, 1);
47 | tableLayoutPanel1.Location = new System.Drawing.Point(14, 54);
48 | tableLayoutPanel1.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
49 | tableLayoutPanel1.Name = "tableLayoutPanel1";
50 | tableLayoutPanel1.RowCount = 2;
51 | tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 50F));
52 | tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 50F));
53 | tableLayoutPanel1.Size = new System.Drawing.Size(845, 532);
54 | tableLayoutPanel1.TabIndex = 1;
55 | //
56 | // formsPlot1
57 | //
58 | formsPlot1.DisplayScale = 1F;
59 | formsPlot1.Dock = System.Windows.Forms.DockStyle.Fill;
60 | formsPlot1.Location = new System.Drawing.Point(4, 3);
61 | formsPlot1.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
62 | formsPlot1.Name = "formsPlot1";
63 | formsPlot1.Size = new System.Drawing.Size(837, 260);
64 | formsPlot1.TabIndex = 0;
65 | //
66 | // formsPlot2
67 | //
68 | formsPlot2.DisplayScale = 1F;
69 | formsPlot2.Dock = System.Windows.Forms.DockStyle.Fill;
70 | formsPlot2.Location = new System.Drawing.Point(4, 269);
71 | formsPlot2.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
72 | formsPlot2.Name = "formsPlot2";
73 | formsPlot2.Size = new System.Drawing.Size(837, 260);
74 | formsPlot2.TabIndex = 1;
75 | //
76 | // trackBar1
77 | //
78 | trackBar1.LargeChange = 1;
79 | trackBar1.Location = new System.Drawing.Point(158, 14);
80 | trackBar1.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
81 | trackBar1.Maximum = 5;
82 | trackBar1.Name = "trackBar1";
83 | trackBar1.Size = new System.Drawing.Size(701, 45);
84 | trackBar1.TabIndex = 2;
85 | trackBar1.Value = 2;
86 | trackBar1.Scroll += trackBar1_Scroll;
87 | //
88 | // label1
89 | //
90 | label1.Font = new System.Drawing.Font("Segoe UI", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, 0);
91 | label1.Location = new System.Drawing.Point(13, 14);
92 | label1.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0);
93 | label1.Name = "label1";
94 | label1.Size = new System.Drawing.Size(138, 40);
95 | label1.TabIndex = 3;
96 | label1.Text = "Sine waves: 5";
97 | //
98 | // FormQuickstart
99 | //
100 | AutoScaleDimensions = new System.Drawing.SizeF(7F, 15F);
101 | AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
102 | ClientSize = new System.Drawing.Size(873, 600);
103 | Controls.Add(tableLayoutPanel1);
104 | Controls.Add(label1);
105 | Controls.Add(trackBar1);
106 | Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
107 | Name = "FormQuickstart";
108 | StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen;
109 | Text = "FftSharp Demo";
110 | Load += FormQuickstart_Load;
111 | tableLayoutPanel1.ResumeLayout(false);
112 | ((System.ComponentModel.ISupportInitialize)trackBar1).EndInit();
113 | ResumeLayout(false);
114 | PerformLayout();
115 | }
116 |
117 | #endregion
118 | private System.Windows.Forms.TableLayoutPanel tableLayoutPanel1;
119 | private ScottPlot.WinForms.FormsPlot formsPlot1;
120 | private ScottPlot.WinForms.FormsPlot formsPlot2;
121 | private System.Windows.Forms.TrackBar trackBar1;
122 | private System.Windows.Forms.Label label1;
123 | }
124 | }
125 |
126 |
--------------------------------------------------------------------------------
/src/FftSharp.Demo/FormQuickstart.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Windows.Forms;
3 |
4 | namespace FftSharp.Demo
5 | {
6 | public partial class FormQuickstart : Form
7 | {
8 | public FormQuickstart()
9 | {
10 | InitializeComponent();
11 | trackBar1_Scroll(null, null);
12 | }
13 |
14 | private void FormQuickstart_Load(object sender, EventArgs e)
15 | {
16 |
17 | }
18 |
19 | private void trackBar1_Scroll(object sender, EventArgs e)
20 | {
21 | label1.Text = $"Sine waves: {trackBar1.Value}";
22 | double[] values = FftSharp.SampleData.OddSines(128, trackBar1.Value);
23 | UpdateFFT(values);
24 | }
25 |
26 | private void UpdateFFT(double[] input)
27 | {
28 | // calculate FFT
29 | System.Numerics.Complex[] spectrum = FftSharp.FFT.Forward(input);
30 | double[] fft = FftSharp.FFT.Magnitude(spectrum);
31 |
32 | // plot the input signal
33 | formsPlot1.Plot.Clear();
34 | formsPlot1.Plot.Add.Scatter(
35 | xs: ScottPlot.Generate.Consecutive(input.Length),
36 | ys: input);
37 | formsPlot1.Plot.Title("Original Signal");
38 | formsPlot1.Refresh();
39 |
40 | // plot the FFT
41 | formsPlot2.Plot.Clear();
42 | formsPlot2.Plot.Add.Scatter(
43 | xs: ScottPlot.Generate.Consecutive(fft.Length),
44 | ys: fft);
45 | formsPlot2.Plot.Title("Fast Fourier Transform (FFT)");
46 | formsPlot2.Refresh();
47 | }
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/src/FftSharp.Demo/FormQuickstart.resx:
--------------------------------------------------------------------------------
1 |
2 |
3 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 | text/microsoft-resx
110 |
111 |
112 | 2.0
113 |
114 |
115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
116 |
117 |
118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
119 |
120 |
--------------------------------------------------------------------------------
/src/FftSharp.Demo/FormWindowInspector.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.ComponentModel;
4 | using System.Data;
5 | using System.Drawing;
6 | using System.Linq;
7 | using System.Text;
8 | using System.Threading.Tasks;
9 | using System.Windows.Forms;
10 |
11 | #pragma warning disable CA1416 // Validate platform compatibility
12 |
13 | namespace FftSharp.Demo
14 | {
15 | public partial class FormWindowInspector : Form
16 | {
17 | public FormWindowInspector()
18 | {
19 | InitializeComponent();
20 | lbWindows.Items.AddRange(Window.GetWindows());
21 | lbWindows.SelectedIndex = Window.GetWindows().ToList().FindIndex(x => x.Name == "Hanning");
22 | }
23 |
24 | private void lbWindows_SelectedIndexChanged(object sender, EventArgs e)
25 | {
26 | IWindow window = (IWindow)lbWindows.SelectedItem;
27 | if (window is null)
28 | return;
29 |
30 | rtbDescription.Text = window.Description;
31 | UpdateTimePlot(window);
32 | UpdateFrequencyPlot(window);
33 | }
34 |
35 | private void UpdateTimePlot(IWindow window)
36 | {
37 | double[] xs = ScottPlot.Generate.Consecutive(101);
38 | double[] ys = window.Create(xs.Length);
39 |
40 | plotWindow.Plot.Clear();
41 | var sp = plotWindow.Plot.Add.ScatterLine(xs, ys);
42 | sp.LineWidth = 2;
43 | plotWindow.Plot.YLabel("Amplitude");
44 | plotWindow.Plot.XLabel("Samples");
45 | plotWindow.Refresh();
46 | }
47 |
48 | private void UpdateFrequencyPlot(IWindow window)
49 | {
50 | int fftSize = (int)Math.Pow(2, 14);
51 | double[] xs = ScottPlot.Generate.Consecutive(fftSize);
52 | double[] ys = xs.Select(x => Math.Sin(x / fftSize * Math.PI * fftSize / 2)).ToArray();
53 | double[] windowed = window.Apply(ys);
54 | System.Numerics.Complex[] spectrum = FftSharp.FFT.Forward(windowed);
55 | double[] power = FFT.Power(spectrum);
56 |
57 | // hide DC component
58 | power[0] = power[1];
59 |
60 | plotFreq.Plot.Clear();
61 | var sig = plotFreq.Plot.Add.Signal(power, 1.0 / (fftSize / 2));
62 | sig.Data.XOffset = -.5;
63 | plotFreq.Plot.YLabel("Power (dB)");
64 | plotFreq.Plot.XLabel("Frequency (cycles/sample)");
65 | plotFreq.Refresh();
66 | }
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/src/FftSharp.Demo/FormWindowInspector.resx:
--------------------------------------------------------------------------------
1 |
2 |
3 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 | text/microsoft-resx
110 |
111 |
112 | 2.0
113 |
114 |
115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
116 |
117 |
118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
119 |
120 |
--------------------------------------------------------------------------------
/src/FftSharp.Demo/Program.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Threading.Tasks;
5 | using System.Windows.Forms;
6 |
7 | namespace FftSharp.Demo
8 | {
9 | static class Program
10 | {
11 | ///
12 | /// The main entry point for the application.
13 | ///
14 | [STAThread]
15 | static void Main()
16 | {
17 | Application.EnableVisualStyles();
18 | Application.SetCompatibleTextRenderingDefault(false);
19 | Application.Run(new FormMenu());
20 | }
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/FftSharp.Demo/Properties/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Reflection;
2 | using System.Runtime.CompilerServices;
3 | using System.Runtime.InteropServices;
4 |
5 | // General Information about an assembly is controlled through the following
6 | // set of attributes. Change these attribute values to modify the information
7 | // associated with an assembly.
8 | [assembly: AssemblyTitle("FftSharp.Demo")]
9 | [assembly: AssemblyDescription("")]
10 | [assembly: AssemblyConfiguration("")]
11 | [assembly: AssemblyCompany("")]
12 | [assembly: AssemblyProduct("FftSharp.Demo")]
13 | [assembly: AssemblyCopyright("Copyright © 2020")]
14 | [assembly: AssemblyTrademark("")]
15 | [assembly: AssemblyCulture("")]
16 |
17 | // Setting ComVisible to false makes the types in this assembly not visible
18 | // to COM components. If you need to access a type in this assembly from
19 | // COM, set the ComVisible attribute to true on that type.
20 | [assembly: ComVisible(false)]
21 |
22 | // The following GUID is for the ID of the typelib if this project is exposed to COM
23 | [assembly: Guid("9ea24240-f1bf-4c5f-b08c-6d5c8afbfde7")]
24 |
25 | // Version information for an assembly consists of the following four values:
26 | //
27 | // Major Version
28 | // Minor Version
29 | // Build Number
30 | // Revision
31 | //
32 | // You can specify all the values or you can default the Build and Revision Numbers
33 | // by using the '*' as shown below:
34 | // [assembly: AssemblyVersion("1.0.*")]
35 | [assembly: AssemblyVersion("1.0.0.0")]
36 | [assembly: AssemblyFileVersion("1.0.0.0")]
37 |
--------------------------------------------------------------------------------
/src/FftSharp.Demo/Properties/Resources.Designer.cs:
--------------------------------------------------------------------------------
1 | //------------------------------------------------------------------------------
2 | //
3 | // This code was generated by a tool.
4 | // Runtime Version:4.0.30319.42000
5 | //
6 | // Changes to this file may cause incorrect behavior and will be lost if
7 | // the code is regenerated.
8 | //
9 | //------------------------------------------------------------------------------
10 |
11 | namespace FftSharp.Demo.Properties
12 | {
13 |
14 |
15 | ///
16 | /// A strongly-typed resource class, for looking up localized strings, etc.
17 | ///
18 | // This class was auto-generated by the StronglyTypedResourceBuilder
19 | // class via a tool like ResGen or Visual Studio.
20 | // To add or remove a member, edit your .ResX file then rerun ResGen
21 | // with the /str option, or rebuild your VS project.
22 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
23 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
24 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
25 | internal class Resources
26 | {
27 |
28 | private static global::System.Resources.ResourceManager resourceMan;
29 |
30 | private static global::System.Globalization.CultureInfo resourceCulture;
31 |
32 | [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
33 | internal Resources()
34 | {
35 | }
36 |
37 | ///
38 | /// Returns the cached ResourceManager instance used by this class.
39 | ///
40 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
41 | internal static global::System.Resources.ResourceManager ResourceManager
42 | {
43 | get
44 | {
45 | if ((resourceMan == null))
46 | {
47 | global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("FftSharp.Demo.Properties.Resources", typeof(Resources).Assembly);
48 | resourceMan = temp;
49 | }
50 | return resourceMan;
51 | }
52 | }
53 |
54 | ///
55 | /// Overrides the current thread's CurrentUICulture property for all
56 | /// resource lookups using this strongly typed resource class.
57 | ///
58 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
59 | internal static global::System.Globalization.CultureInfo Culture
60 | {
61 | get
62 | {
63 | return resourceCulture;
64 | }
65 | set
66 | {
67 | resourceCulture = value;
68 | }
69 | }
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/src/FftSharp.Demo/Properties/Resources.resx:
--------------------------------------------------------------------------------
1 |
2 |
3 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 | text/microsoft-resx
107 |
108 |
109 | 2.0
110 |
111 |
112 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
113 |
114 |
115 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
116 |
117 |
--------------------------------------------------------------------------------
/src/FftSharp.Demo/Properties/Settings.Designer.cs:
--------------------------------------------------------------------------------
1 | //------------------------------------------------------------------------------
2 | //
3 | // This code was generated by a tool.
4 | // Runtime Version:4.0.30319.42000
5 | //
6 | // Changes to this file may cause incorrect behavior and will be lost if
7 | // the code is regenerated.
8 | //
9 | //------------------------------------------------------------------------------
10 |
11 | namespace FftSharp.Demo.Properties
12 | {
13 |
14 |
15 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
16 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "11.0.0.0")]
17 | internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase
18 | {
19 |
20 | private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));
21 |
22 | public static Settings Default
23 | {
24 | get
25 | {
26 | return defaultInstance;
27 | }
28 | }
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/src/FftSharp.Demo/Properties/Settings.settings:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/src/FftSharp.Demo/icon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/swharden/FftSharp/3f5158f7ab146c8fb651028e8dce67407b3ded81/src/FftSharp.Demo/icon.ico
--------------------------------------------------------------------------------
/src/FftSharp.Demo/packages.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/src/FftSharp.Tests/.editorconfig:
--------------------------------------------------------------------------------
1 | [*.cs]
2 | csharp_style_namespace_declarations = file_scoped:warning
--------------------------------------------------------------------------------
/src/FftSharp.Tests/ExperimentalTests.cs:
--------------------------------------------------------------------------------
1 | using NUnit.Framework;
2 | using System.Linq;
3 |
4 | namespace FftSharp.Tests;
5 |
6 | internal class ExperimentalTests
7 | {
8 | [Test]
9 | [System.Obsolete]
10 | public void Test_Experimental_DFT()
11 | {
12 | double[] prime = { 1, 2, 3, 4, 5, 6, 7 };
13 | System.Numerics.Complex[] primeComplex = prime.Select(x => new System.Numerics.Complex(x, 0)).ToArray();
14 | System.Numerics.Complex[] result = FftSharp.DFT.Forward(primeComplex);
15 |
16 | // tested with python
17 | double[] expectedReal = { 28.00000, -3.50000, -3.50000, -3.50000, -3.50000, -3.50000, -3.50000 };
18 | double[] expectedImag = { -0.00000, 7.26782, 2.79116, 0.79885, -0.79885, -2.79116, -7.26782 };
19 |
20 | Assert.AreEqual(expectedReal.Length, result.Length);
21 |
22 | for (int i = 0; i < result.Length; i++)
23 | {
24 | Assert.AreEqual(expectedReal[i], result[i].Real, 1e-5);
25 | Assert.AreEqual(expectedImag[i], result[i].Imaginary, 1e-5);
26 | }
27 | }
28 |
29 | [Test]
30 | [System.Obsolete]
31 | public void Test_Experimental_Bluestein()
32 | {
33 | double[] prime = { 1, 2, 3, 4, 5, 6, 7 };
34 | System.Numerics.Complex[] result = FftSharp.Bluestein.Forward(prime);
35 |
36 | // tested with python
37 | double[] expectedReal = { 28.00000, -3.50000, -3.50000, -3.50000, -3.50000, -3.50000, -3.50000 };
38 | double[] expectedImag = { -0.00000, 7.26782, 2.79116, 0.79885, -0.79885, -2.79116, -7.26782 };
39 |
40 | Assert.AreEqual(expectedReal.Length, result.Length);
41 |
42 | for (int i = 0; i < result.Length; i++)
43 | {
44 | Assert.AreEqual(expectedReal[i], result[i].Real, 1e-5);
45 | Assert.AreEqual(expectedImag[i], result[i].Imaginary, 1e-5);
46 | }
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/src/FftSharp.Tests/FftFreqTests.cs:
--------------------------------------------------------------------------------
1 | using NUnit.Framework;
2 | using System;
3 | using System.Linq;
4 |
5 | namespace FftSharp.Tests;
6 |
7 | internal class FftFreqTests
8 | {
9 | [Test]
10 | public void Test_FftFreq_KnownValues()
11 | {
12 | // https://github.com/swharden/FftSharp/issues/49
13 |
14 | // generate signal with 2 Hz sine wave
15 | int sampleCount = 16;
16 | double sampleRateHz = 16;
17 | double sinFrequencyHz = 2;
18 | double[] samples = Enumerable.Range(0, sampleCount)
19 | .Select(i => Math.Sin(2 * Math.PI * sinFrequencyHz * i / sampleRateHz))
20 | .ToArray();
21 |
22 | // get FFT
23 | System.Numerics.Complex[] spectrum = FftSharp.FFT.Forward(samples);
24 | double[] fft = FftSharp.FFT.Magnitude(spectrum);
25 | double[] fftKnown = { 0, 0, 1, 0, 0, 0, 0, 0, 0 };
26 | Assert.That(fft, Is.EqualTo(fftKnown).Within(1e-10));
27 |
28 | // calculate FFT frequencies both ways
29 | double[] fftFreqKnown = { 0, 1, 2, 3, 4, 5, 6, 7, 8 };
30 | double[] fftFreq = FftSharp.FFT.FrequencyScale(fft.Length, sampleRateHz);
31 | Assert.That(fftFreq, Is.EqualTo(fftFreqKnown));
32 |
33 | ScottPlot.Plot plt1 = new();
34 | plt1.Add.Signal(samples, 1.0 / sampleRateHz);
35 | TestTools.SaveFig(plt1, "signal");
36 |
37 | ScottPlot.Plot plt2 = new();
38 | plt2.Add.Scatter(fftFreq, fft);
39 | plt2.Add.VerticalLine(2, 2, ScottPlot.Colors.Red, ScottPlot.LinePattern.DenselyDashed);
40 | TestTools.SaveFig(plt2, "fft");
41 | }
42 |
43 | [Test]
44 | public void Test_Freq_Lookup()
45 | {
46 | /* Compare against values generated using Python:
47 | *
48 | * >>> numpy.fft.fftfreq(16, 1/16000)
49 | * array([ 0., 1000., 2000., 3000., 4000., 5000., 6000., 7000.,
50 | * -8000., -7000., -6000., -5000., -4000., -3000., -2000., -1000.])
51 | *
52 | */
53 |
54 | int sampleRate = 16000;
55 | int pointCount = 16;
56 | double[] signal = new double[pointCount];
57 |
58 | System.Numerics.Complex[] spectrum = FftSharp.FFT.Forward(signal);
59 | double[] fft = FftSharp.FFT.Magnitude(spectrum);
60 |
61 | double[] freqsFullKnown = {
62 | 0, 1000, 2000, 3000, 4000, 5000, 6000, 7000,
63 | -8000, -7000, -6000, -5000, -4000, -3000, -2000, -1000
64 | };
65 | double[] freqsFull = FftSharp.FFT.FrequencyScale(signal.Length, sampleRate, false);
66 | Assert.That(freqsFull, Is.EqualTo(freqsFullKnown));
67 |
68 | double[] freqsHalfKnown = {
69 | 0, 1000, 2000, 3000, 4000, 5000, 6000, 7000, 8000
70 | };
71 | double[] freqsHalf = FftSharp.FFT.FrequencyScale(fft.Length, sampleRate, true);
72 | Assert.That(freqsHalf, Is.EqualTo(freqsHalfKnown));
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/src/FftSharp.Tests/FftSharp.Tests.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net8.0
5 | false
6 |
7 |
8 |
9 |
10 |
11 | all
12 | runtime; build; native; contentfiles; analyzers; buildtransitive
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/src/FftSharp.Tests/FftTests.cs:
--------------------------------------------------------------------------------
1 | using NUnit.Framework;
2 |
3 | namespace FftSharp.Tests;
4 |
5 | internal class FftTests
6 | {
7 | [Test]
8 | public void Test_FFT_Forward()
9 | {
10 | double[] sample = LoadData.Double("sample.txt");
11 | System.Numerics.Complex[] fft = FftSharp.FFT.Forward(sample);
12 |
13 | System.Numerics.Complex[] numpyFft = LoadData.Complex("fft.txt");
14 | Assert.AreEqual(numpyFft.Length, fft.Length);
15 |
16 | for (int i = 0; i < fft.Length; i++)
17 | {
18 | Assert.AreEqual(numpyFft[i].Real, fft[i].Real, 1e-10);
19 | Assert.AreEqual(numpyFft[i].Imaginary, fft[i].Imaginary, 1e-10);
20 | }
21 | }
22 |
23 | [Test]
24 | public void Test_FFT_Inverse()
25 | {
26 | double[] sample = LoadData.Double("sample.txt");
27 | System.Numerics.Complex[] fft = FftSharp.FFT.Forward(sample);
28 | FftSharp.FFT.Inverse(fft);
29 |
30 | for (int i = 0; i < fft.Length; i++)
31 | {
32 | Assert.AreEqual(sample[i], fft[i].Real, 1e-10);
33 | }
34 | }
35 |
36 | [Test]
37 | public void Test_FFT_FrequencyScale()
38 | {
39 | double[] numpyFftFreq = LoadData.Double("fftFreq.txt");
40 | double[] fftFreq = FftSharp.FFT.FrequencyScale(length: numpyFftFreq.Length, sampleRate: 48_000);
41 |
42 | Assert.AreEqual(numpyFftFreq.Length, fftFreq.Length);
43 |
44 | for (int i = 0; i < fftFreq.Length; i++)
45 | {
46 | Assert.AreEqual(fftFreq[i], fftFreq[i], 1e-10);
47 | }
48 | }
49 |
50 | [Test]
51 | public void Test_FftShift_Odd()
52 | {
53 | double[] values = { 0, 1, 2, 3, 4, 5, 6 };
54 | double[] shifted = FFT.FftShift(values);
55 | double[] expectedResult = { 4, 5, 6, 0, 1, 2, 3 };
56 | Assert.AreEqual(expectedResult, shifted);
57 | }
58 |
59 | [Test]
60 | public void Test_FftShift_Even()
61 | {
62 | double[] values = { 0, 1, 2, 3, 4, 5, 6, 7 };
63 | double[] shifted = FFT.FftShift(values);
64 | double[] expectedResult = { 4, 5, 6, 7, 0, 1, 2, 3 };
65 | Assert.AreEqual(expectedResult, shifted);
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/src/FftSharp.Tests/Filter.cs:
--------------------------------------------------------------------------------
1 | using NUnit.Framework;
2 |
3 | namespace FftSharp.Tests;
4 |
5 | class Filter
6 | {
7 | double[] Values;
8 | double[] TimesMsec;
9 | double[] Freqs;
10 |
11 | private const double SampleRate = 48_000;
12 | private double SamplePeriod => 1.0 / SampleRate;
13 |
14 | [OneTimeSetUp]
15 | public void LoadAndVerifyData()
16 | {
17 | Values = LoadData.Double("sample.txt");
18 | TimesMsec = ScottPlot.Generate.Consecutive(Values.Length, SamplePeriod * 1000.0);
19 | Freqs = LoadData.Double("fftFreq.txt");
20 | }
21 |
22 | [Test]
23 | public void Test_Filter_Lowpass()
24 | {
25 | double[] Filtered = FftSharp.Filter.LowPass(Values, SampleRate, 2000);
26 |
27 | var plt = new ScottPlot.Plot();
28 |
29 | var sp1 = plt.Add.Scatter(TimesMsec, Values);
30 | sp1.LegendText = "Original";
31 |
32 | var sp2 = plt.Add.Scatter(TimesMsec, Filtered);
33 | sp2.LegendText = "Low-Pass";
34 | sp2.LineWidth = 2;
35 |
36 | plt.YLabel("Signal Value");
37 | plt.XLabel("Time (milliseconds)");
38 | plt.Axes.SetLimitsX(4, 6);
39 |
40 | TestTools.SaveFig(plt);
41 | }
42 |
43 | [Test]
44 | public void Test_Filter_HighPass()
45 | {
46 | double[] Filtered = FftSharp.Filter.HighPass(Values, SampleRate, 3000);
47 |
48 | var plt = new ScottPlot.Plot();
49 |
50 | var sp1 = plt.Add.Scatter(TimesMsec, Values);
51 | sp1.LegendText = "Original";
52 |
53 | var sp2 = plt.Add.Scatter(TimesMsec, Filtered);
54 | sp2.LegendText = "High-Pass";
55 | sp2.LineWidth = 2;
56 |
57 | plt.YLabel("Signal Value");
58 | plt.XLabel("Time (milliseconds)");
59 | plt.Axes.SetLimitsX(4, 6);
60 |
61 | TestTools.SaveFig(plt);
62 | }
63 |
64 | [Test]
65 | public void Test_Filter_BandPass()
66 | {
67 | double[] Filtered = FftSharp.Filter.BandPass(Values, SampleRate, 1900, 2100);
68 |
69 | var plt = new ScottPlot.Plot();
70 |
71 | var sp1 = plt.Add.Scatter(TimesMsec, Values);
72 | sp1.LegendText = "Original";
73 |
74 | var sp2 = plt.Add.Scatter(TimesMsec, Filtered);
75 | sp2.LegendText = "Band-Pass";
76 | sp2.LineWidth = 2;
77 | plt.YLabel("Signal Value");
78 | plt.XLabel("Time (milliseconds)");
79 | plt.Axes.SetLimitsX(4, 6);
80 |
81 | TestTools.SaveFig(plt);
82 | }
83 |
84 | [Test]
85 | public void Test_Filter_BandStop()
86 | {
87 | double[] Filtered = FftSharp.Filter.BandStop(Values, SampleRate, 1900, 2100);
88 |
89 | var plt = new ScottPlot.Plot();
90 |
91 | var sp1 = plt.Add.Scatter(TimesMsec, Values);
92 | sp1.LegendText = "Original";
93 |
94 | var sp2 = plt.Add.Scatter(TimesMsec, Filtered);
95 | sp2.LegendText = "Band-Pass";
96 | sp2.LineWidth = 2;
97 |
98 | plt.YLabel("Signal Value");
99 | plt.XLabel("Time (milliseconds)");
100 | plt.Axes.SetLimits(4, 6);
101 |
102 | TestTools.SaveFig(plt);
103 | }
104 | }
105 |
--------------------------------------------------------------------------------
/src/FftSharp.Tests/Inverse.cs:
--------------------------------------------------------------------------------
1 | using NUnit.Framework;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Diagnostics.Tracing;
5 | using System.Numerics;
6 | using System.Text;
7 |
8 | #pragma warning disable CS0618 // Type or member is obsolete
9 |
10 | namespace FftSharp.Tests;
11 |
12 | class Inverse
13 | {
14 | [Test]
15 | public void Test_IDFT_MatchesOriginal()
16 | {
17 | Random rand = new Random(0);
18 | System.Numerics.Complex[] original = new System.Numerics.Complex[1024];
19 | for (int i = 0; i < original.Length; i++)
20 | original[i] = new System.Numerics.Complex(rand.NextDouble() - .5, rand.NextDouble() - .5);
21 |
22 | System.Numerics.Complex[] fft = FftSharp.DFT.Forward(original);
23 | System.Numerics.Complex[] ifft = FftSharp.DFT.Inverse(fft);
24 |
25 | for (int i = 0; i < ifft.Length; i++)
26 | {
27 | Assert.AreEqual(original[i].Real, ifft[i].Real, 1e-6);
28 | Assert.AreEqual(original[i].Imaginary, ifft[i].Imaginary, 1e-6);
29 | }
30 | }
31 |
32 | [Test]
33 | public void Test_IFFT_MatchesOriginal()
34 | {
35 | Random rand = new Random(0);
36 | Complex[] original = new Complex[1024];
37 | for (int i = 0; i < original.Length; i++)
38 | original[i] = new Complex(rand.NextDouble() - .5, rand.NextDouble() - .5);
39 |
40 | Complex[] fft = new Complex[original.Length];
41 | Array.Copy(original, 0, fft, 0, original.Length);
42 | FftSharp.FFT.Forward(fft);
43 |
44 | Complex[] ifft = new Complex[fft.Length];
45 | Array.Copy(fft, 0, ifft, 0, fft.Length);
46 | FftSharp.FFT.Inverse(ifft);
47 |
48 | for (int i = 0; i < ifft.Length; i++)
49 | {
50 | Assert.AreEqual(original[i].Real, ifft[i].Real, 1e-6);
51 | Assert.AreEqual(original[i].Imaginary, ifft[i].Imaginary, 1e-6);
52 | }
53 | }
54 |
55 | [Test]
56 | public void Test_InverseReal_MatchesOriginal()
57 | {
58 | Random rand = new Random(0);
59 | double[] original = new double[1024];
60 | for (int i = 0; i < original.Length; i++)
61 | original[i] = rand.NextDouble() - .5;
62 |
63 | System.Numerics.Complex[] rfft = FftSharp.FFT.ForwardReal(original);
64 | double[] irfft = FftSharp.FFT.InverseReal(rfft);
65 |
66 | Assert.AreEqual(original.Length, irfft.Length);
67 |
68 | for (int i = 0; i < irfft.Length; i++)
69 | {
70 | Assert.AreEqual(original[i], irfft[i], 1e-10);
71 | }
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/src/FftSharp.Tests/LoadData.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.IO;
3 | using System.Linq;
4 |
5 | namespace FftSharp.Tests;
6 |
7 | public static class LoadData
8 | {
9 | public static double[] Double(string fileName) =>
10 | File.ReadLines($"../../../../../dev/data/{fileName}")
11 | .Where(x => !x.StartsWith('#') && x.Length > 1)
12 | .Select(x => double.Parse(x))
13 | .ToArray();
14 |
15 | public static System.Numerics.Complex[] Complex(string fileName) =>
16 | File.ReadLines($"../../../../../dev/data/{fileName}")
17 | .Select(x => x.Trim('(').Trim(')').Trim('j'))
18 | .Select(x => x.Replace("-", " -").Replace("+", " +").Trim())
19 | .Select(x => new System.Numerics.Complex(double.Parse(x.Split(' ')[0]), double.Parse(x.Split(' ')[1])))
20 | .ToArray();
21 | }
22 |
--------------------------------------------------------------------------------
/src/FftSharp.Tests/MelTests.cs:
--------------------------------------------------------------------------------
1 | using NUnit.Framework;
2 | using System.Linq;
3 |
4 | namespace FftSharp.Tests;
5 |
6 | class MelTests
7 | {
8 | [Test]
9 | public void Test_Mel_VsFFT()
10 | {
11 | double[] audio = SampleData.SampleAudio1();
12 | int sampleRate = 48_000;
13 | int melBinCount = 20;
14 |
15 | System.Numerics.Complex[] spectrum = FFT.Forward(audio);
16 | double[] fftMag = FftSharp.FFT.Magnitude(spectrum);
17 | double[] fftMel = FftSharp.Mel.Scale(fftMag, sampleRate, melBinCount);
18 | double[] freqMel = FFT.FrequencyScale(fftMag.Length, sampleRate)
19 | .Select(x => Mel.FromFreq(x))
20 | .ToArray();
21 |
22 | //ScottPlot.MultiPlot mp = new();
23 | //plt.SaveFig("audio-mel.png");
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/FftSharp.Tests/Padding.cs:
--------------------------------------------------------------------------------
1 | using NUnit.Framework;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Numerics;
5 | using System.Text;
6 |
7 | namespace FftSharp.Tests;
8 |
9 | class Padding
10 | {
11 | [Test]
12 | public void Test_FFT_AllLengthInput()
13 | {
14 | for (int i = 2; i < 200; i++)
15 | {
16 | System.Numerics.Complex[] input = new System.Numerics.Complex[i];
17 | System.Numerics.Complex[] padded = FftSharp.Pad.ZeroPad(input);
18 | Console.WriteLine($"Length {input.Length} -> {padded.Length}");
19 | FftSharp.FFT.Forward(padded);
20 | }
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/FftSharp.Tests/PeakFrequencyDetection.cs:
--------------------------------------------------------------------------------
1 | using NUnit.Framework;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Linq;
5 | using System.Text;
6 | using System.Threading.Tasks;
7 |
8 | namespace FftSharp.Tests;
9 |
10 | internal class PeakFrequencyDetection
11 | {
12 | [Test]
13 | public void Test_PeakFrequency_MatchesExpectation()
14 | {
15 | double sampleRate = 48_000;
16 |
17 | double[] signal = FftSharp.SampleData.SampleAudio1(); // 2 kHz peak frequency
18 | System.Numerics.Complex[] spectrum = FftSharp.FFT.Forward(signal);
19 | double[] fftMag = FftSharp.FFT.Magnitude(spectrum);
20 | double[] fftFreq = FftSharp.FFT.FrequencyScale(fftMag.Length, sampleRate);
21 |
22 | int peakIndex = 0;
23 | double peakValue = fftMag[0];
24 | for (int i = 1; i < fftFreq.Length; i++)
25 | {
26 | if (fftMag[i] > peakValue)
27 | {
28 | peakValue = fftMag[i];
29 | peakIndex = i;
30 | }
31 | }
32 |
33 | double peakFrequency = fftFreq[peakIndex];
34 | Console.WriteLine($"Peak frequency: {peakFrequency} Hz");
35 |
36 | Assert.AreEqual(1968.75, peakFrequency);
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/src/FftSharp.Tests/Quickstart.cs:
--------------------------------------------------------------------------------
1 | using NUnit.Framework;
2 | using ScottPlot;
3 | using System;
4 | using System.IO;
5 |
6 | namespace FftSharp.Tests;
7 |
8 | class Quickstart
9 | {
10 | public static string OUTPUT_FOLDER = Path.GetFullPath(Path.Combine(TestContext.CurrentContext.TestDirectory, "../../../../../dev/quickstart/"));
11 |
12 | [Test]
13 | public static void Test_OutputFolder_Exists()
14 | {
15 | Console.WriteLine(OUTPUT_FOLDER);
16 | Assert.That(Directory.Exists(OUTPUT_FOLDER), $"output folder does not exist: {OUTPUT_FOLDER}");
17 | }
18 |
19 | [TestCase(true)]
20 | [TestCase(false)]
21 | public static void Test_SimpleFftWithGraphs(bool useWindow)
22 | {
23 | // load sample audio with noise and sine waves at 500, 1200, and 1500 Hz
24 | double[] audio = FftSharp.SampleData.SampleAudio1();
25 | int sampleRate = 48_000;
26 |
27 | // optionally apply a window to the data before calculating the FFT
28 | if (useWindow)
29 | {
30 | var window = new FftSharp.Windows.Hanning();
31 | window.ApplyInPlace(audio);
32 | }
33 |
34 | // You could get the FFT as a complex result
35 | System.Numerics.Complex[] spectrum = FftSharp.FFT.Forward(audio);
36 |
37 | // For audio we typically want the FFT amplitude (in dB)
38 | double[] fftPower = FftSharp.FFT.Power(spectrum);
39 |
40 | // Create an array of frequencies for each point of the FFT
41 | double[] freqs = FftSharp.FFT.FrequencyScale(fftPower.Length, sampleRate);
42 |
43 | // create an array of audio sample times to aid plotting
44 | double[] times = ScottPlot.Generate.Consecutive(audio.Length, 1000d / sampleRate);
45 |
46 | // plot the sample audio
47 | var plt1 = new ScottPlot.Plot();
48 | var sp1 = plt1.Add.Scatter(times, audio);
49 | sp1.MarkerSize = 3;
50 | plt1.YLabel("Amplitude");
51 | plt1.XLabel("Time (ms)");
52 | plt1.Axes.TightMargins();
53 |
54 | // plot the FFT amplitude
55 | var plt2 = new ScottPlot.Plot();
56 | var sp2 = plt2.Add.Scatter(freqs, fftPower);
57 | sp2.MarkerSize = 3;
58 | plt2.YLabel("Power (dB)");
59 | plt2.XLabel("Frequency (Hz)");
60 | plt2.Axes.TightMargins();
61 |
62 | // save output
63 | if (useWindow)
64 | {
65 | plt1.SavePng(Path.Combine(OUTPUT_FOLDER, "audio-windowed.png"), 400, 300);
66 | plt2.SavePng(Path.Combine(OUTPUT_FOLDER, "fft-windowed.png"), 400, 300);
67 | }
68 | else
69 | {
70 | plt1.SavePng(Path.Combine(OUTPUT_FOLDER, "audio.png"), 400, 300);
71 | plt2.SavePng(Path.Combine(OUTPUT_FOLDER, "fft.png"), 400, 300);
72 | }
73 | }
74 |
75 | [Test]
76 | public void Test_ExampleCode_Quickstart()
77 | {
78 | // Begin with an array containing sample data
79 | double[] signal = FftSharp.SampleData.SampleAudio1();
80 |
81 | // Shape the signal using a Hanning window
82 | var window = new FftSharp.Windows.Hanning();
83 | window.ApplyInPlace(signal);
84 |
85 | // Calculate the FFT as an array of complex numbers
86 | System.Numerics.Complex[] spectrum = FftSharp.FFT.Forward(signal);
87 |
88 | // or get the magnitude (units²) or power (dB) as real numbers
89 | double[] magnitude = FftSharp.FFT.Magnitude(spectrum);
90 | double[] power = FftSharp.FFT.Power(spectrum);
91 |
92 | // plot the result
93 | ScottPlot.Plot plot = new();
94 | plot.Add.Signal(power);
95 | plot.SavePng("quickstart.png", 600, 400).ConsoleWritePath();
96 | }
97 |
98 | [Test]
99 | public void Test_ExampleCode_SampleData()
100 | {
101 | // sample audio with tones at 2, 10, and 20 kHz plus white noise
102 | double[] signal = FftSharp.SampleData.SampleAudio1();
103 | int sampleRate = 48_000;
104 | double samplePeriod = sampleRate / 1000.0;
105 |
106 | // plot the sample audio
107 | ScottPlot.Plot plt = new();
108 | plt.Add.Signal(signal, samplePeriod);
109 | plt.YLabel("Amplitude");
110 | plt.SavePng("time-series.png", 500, 200).ConsoleWritePath();
111 | }
112 |
113 | [Test]
114 | public void Test_ExampleCode_SpectralDensity()
115 | {
116 | // sample audio with tones at 2, 10, and 20 kHz plus white noise
117 | double[] signal = FftSharp.SampleData.SampleAudio1();
118 | int sampleRate = 48_000;
119 |
120 | // calculate the power spectral density using FFT
121 | System.Numerics.Complex[] spectrum = FftSharp.FFT.Forward(signal);
122 | double[] psd = FftSharp.FFT.Power(spectrum);
123 | double[] freq = FftSharp.FFT.FrequencyScale(psd.Length, sampleRate);
124 |
125 | // plot the sample audio
126 | ScottPlot.Plot plt = new();
127 | plt.Add.ScatterLine(freq, psd);
128 | plt.YLabel("Power (dB)");
129 | plt.XLabel("Frequency (Hz)");
130 | plt.SavePng("periodogram.png", 500, 200).ConsoleWritePath();
131 | }
132 |
133 | [Test]
134 | public void Test_ExampleCode_Filtering()
135 | {
136 | System.Numerics.Complex[] buffer =
137 | {
138 | new(real: 42, imaginary: 12),
139 | new(real: 96, imaginary: 34),
140 | new(real: 13, imaginary: 56),
141 | new(real: 99, imaginary: 78),
142 | };
143 |
144 | FftSharp.FFT.Forward(buffer);
145 |
146 | double[] audio = FftSharp.SampleData.SampleAudio1();
147 | double[] filtered = FftSharp.Filter.LowPass(audio, sampleRate: 48000, maxFrequency: 2000);
148 | }
149 | }
150 |
--------------------------------------------------------------------------------
/src/FftSharp.Tests/Readme.cs:
--------------------------------------------------------------------------------
1 | using NUnit.Framework;
2 | using System.IO;
3 |
4 | namespace FftSharp.Tests;
5 |
6 | internal class Readme
7 | {
8 | public static string OUTPUT_FOLDER = Path.GetFullPath(Path.Combine(TestContext.CurrentContext.TestDirectory, "../../../../../dev/quickstart/"));
9 |
10 | [Test]
11 | public void Test_Readme_Quickstart()
12 | {
13 | // Begin with an array containing sample data
14 | double[] signal = FftSharp.SampleData.SampleAudio1();
15 |
16 | // Shape the signal using a Hanning window
17 | var window = new FftSharp.Windows.Hanning();
18 | window.ApplyInPlace(signal);
19 |
20 | // Calculate the FFT as an array of complex numbers
21 | System.Numerics.Complex[] spectrum = FftSharp.FFT.Forward(signal);
22 |
23 | // Or get the spectral power (dB) or magnitude (RMS²) as real numbers
24 | double[] fftPower = FftSharp.FFT.Power(spectrum);
25 | double[] fftMagnitude = FftSharp.FFT.Magnitude(spectrum);
26 | }
27 |
28 | [Test]
29 | public void Test_Plot_TimeSeries()
30 | {
31 | // sample audio with tones at 2, 10, and 20 kHz plus white noise
32 | double[] signal = FftSharp.SampleData.SampleAudio1();
33 | int sampleRate = 48_000;
34 |
35 | // plot the sample audio
36 | var plt = new ScottPlot.Plot();
37 | plt.Add.Signal(signal, 1.0 / (sampleRate / 1000.0));
38 | plt.YLabel("Amplitude");
39 | plt.Axes.TightMargins();
40 |
41 | plt.SavePng(Path.Combine(OUTPUT_FOLDER, "time-series.png"), 400, 200);
42 | }
43 |
44 | [Test]
45 | public void Test_Plot_MagnitudePowerFreq()
46 | {
47 | // sample audio with tones at 2, 10, and 20 kHz plus white noise
48 | double[] signal = FftSharp.SampleData.SampleAudio1();
49 | int sampleRate = 48_000;
50 |
51 | // calculate the power spectral density using FFT
52 | System.Numerics.Complex[] spectrum = FftSharp.FFT.Forward(signal);
53 | double[] psd = FftSharp.FFT.Power(spectrum);
54 | double[] freq = FftSharp.FFT.FrequencyScale(psd.Length, sampleRate);
55 |
56 | // plot the sample audio
57 | var plt = new ScottPlot.Plot();
58 | plt.Add.ScatterLine(freq, psd);
59 | plt.YLabel("Power (dB)");
60 | plt.XLabel("Frequency (Hz)");
61 | plt.Axes.TightMargins();
62 |
63 | plt.SavePng(Path.Combine(OUTPUT_FOLDER, "periodogram.png"), 400, 200);
64 | }
65 |
66 | [Test]
67 | public void Test_Window()
68 | {
69 | double[] signal = FftSharp.SampleData.SampleAudio1();
70 | var window = new FftSharp.Windows.Hanning();
71 | double[] windowed = window.Apply(signal);
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/src/FftSharp.Tests/TestTools.cs:
--------------------------------------------------------------------------------
1 | using NUnit.Framework;
2 | using System;
3 |
4 | namespace FftSharp.Tests;
5 |
6 | public static class TestTools
7 | {
8 | public static string SaveFig(ScottPlot.Plot plt, string subName = "", bool artifact = false)
9 | {
10 | var stackTrace = new System.Diagnostics.StackTrace();
11 | string callingMethod = stackTrace.GetFrame(1).GetMethod().Name;
12 |
13 | if (subName != "")
14 | subName = "_" + subName;
15 |
16 | string fileName = callingMethod + subName + ".png";
17 | string filePath = System.IO.Path.GetFullPath(fileName);
18 | plt.SavePng(filePath, 600, 400);
19 |
20 | Console.WriteLine($"Saved: {filePath}");
21 | Console.WriteLine();
22 |
23 | return filePath;
24 | }
25 |
26 | ///
27 | /// assert values have mirror symmetry (except the first and last N points)
28 | ///
29 | public static void AssertMirror(double[] values, int ignoreFirst = 1)
30 | {
31 | for (int i = ignoreFirst; i < values.Length / 2; i++)
32 | {
33 | int i2 = values.Length - i;
34 | Assert.AreEqual(values[i], values[i2], delta: 1e-10, $"Not mirror at index {i} and {i2}");
35 | }
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/src/FftSharp.Tests/Transform.cs:
--------------------------------------------------------------------------------
1 | using NUnit.Framework;
2 | using System;
3 | using System.Drawing;
4 | using System.Linq;
5 | using System.Numerics;
6 | using System.Text;
7 |
8 | #pragma warning disable CS0618 // Type or member is obsolete
9 |
10 | namespace FftSharp.Tests;
11 |
12 | public class Transform
13 | {
14 | [Test]
15 | public void Test_Display_SampleData()
16 | {
17 | double[] audio = SampleData.SampleAudio1();
18 |
19 | // use this to test FFT algos in other programming langauges
20 | StringBuilder sb = new StringBuilder();
21 | for (int i = 0; i < audio.Length; i++)
22 | {
23 | if (i % 16 == 0)
24 | sb.Append("\n");
25 | double value = audio[i];
26 | string s = (value < 0) ? $"{value:N2}, " : $"+{value:N2}, ";
27 | sb.Append(s);
28 | }
29 |
30 | Console.WriteLine(sb);
31 | }
32 |
33 | [Test]
34 | public void Test_DFTvsFFT_ProduceIdenticalOutput()
35 | {
36 | double[] audio = SampleData.SampleAudio1();
37 |
38 | // FFT and DST output should be identical (aside from floating-point errors)
39 | System.Numerics.Complex[] fft = FftSharp.FFT.Forward(audio);
40 | System.Numerics.Complex[] dft = FftSharp.DFT.Forward(audio);
41 |
42 | for (int i = 0; i < fft.Length; i++)
43 | {
44 | Assert.AreEqual(fft[i].Real, dft[i].Real, 1e-6);
45 | Assert.AreEqual(fft[i].Imaginary, dft[i].Imaginary, 1e-6);
46 | Assert.AreEqual(fft[i].Magnitude, dft[i].Magnitude, 1e-6);
47 | }
48 | }
49 |
50 | [Test]
51 | public void Test_PosNeg_AreMirrored()
52 | {
53 | double[] audio = SampleData.SampleAudio1();
54 | int sampleRate = 48_000;
55 |
56 | Complex[] fft = FftSharp.FFT.Forward(audio);
57 | double[] fftAmp = fft.Select(x => x.Magnitude).ToArray();
58 | double[] fftFreq = FftSharp.FFT.FrequencyScale(fftAmp.Length, sampleRate, positiveOnly: false);
59 |
60 | TestTools.AssertMirror(fftAmp);
61 |
62 | var plt = new ScottPlot.Plot();
63 | plt.Add.Lollipop(fftAmp, fftFreq);
64 | plt.Add.HorizontalLine(0, 2, ScottPlot.Colors.Black);
65 | plt.YLabel("Magnitude (rms^2?)");
66 | plt.XLabel("Frequency (Hz)");
67 | TestTools.SaveFig(plt);
68 | }
69 |
70 | [Test]
71 | public void Test_FftHelperMethods_ThrowIfNotPowerOfTwo()
72 | {
73 | Assert.Throws(() => { FftSharp.FFT.Forward(new double[0]); });
74 | Assert.Throws(() => { FftSharp.FFT.Forward(new double[123]); });
75 | Assert.Throws(() => { FftSharp.FFT.Forward(new double[1234]); });
76 | }
77 |
78 | [Test]
79 | public void Test_FftInput_ContainsAllZeros()
80 | {
81 | System.Numerics.Complex[] complex = new System.Numerics.Complex[128];
82 | for (int i = 0; i < complex.Length; i++)
83 | complex[i] = new System.Numerics.Complex(0, 0);
84 | FftSharp.DFT.Forward(complex);
85 | }
86 |
87 | [Test]
88 | public void Test_FftInput_Uninitialized()
89 | {
90 | System.Numerics.Complex[] complex = new System.Numerics.Complex[128];
91 | FftSharp.DFT.Forward(complex);
92 | }
93 |
94 | [Test]
95 | public void Test_FftInput_ThrowsIfEmpty()
96 | {
97 | Complex[] complexValues = new Complex[0];
98 | double[] realValues = new double[0];
99 | Assert.Throws(() => { FftSharp.FFT.Forward(complexValues); });
100 | Assert.Throws(() => { FftSharp.FFT.Inverse(complexValues); });
101 | Assert.Throws(() => { FftSharp.FFT.Forward(realValues); });
102 | Assert.Throws(() => { FftSharp.FFT.InverseReal(complexValues); });
103 | }
104 |
105 | [TestCase(123, true)]
106 | [TestCase(13, true)]
107 | [TestCase(128, false)]
108 | [TestCase(16, false)]
109 | public void Test_FftInput_ThrowsIfLengthIsNotPowerOfTwo(int length, bool shouldThrow)
110 | {
111 | Complex[] complexValues = new Complex[length];
112 | double[] realValues = new double[length];
113 | Complex[] destination = new Complex[length / 2 + 1];
114 |
115 | var complexFFT = new TestDelegate(() => FftSharp.FFT.Forward(complexValues));
116 | var complexSpanFFT = new TestDelegate(() => FftSharp.FFT.Forward(complexValues.AsSpan()));
117 | var complexIFFT = new TestDelegate(() => FftSharp.FFT.Inverse(complexValues));
118 | var realFFT = new TestDelegate(() => FftSharp.FFT.Forward(realValues));
119 | var realRFFT = new TestDelegate(() => FftSharp.FFT.ForwardReal(realValues));
120 |
121 | if (shouldThrow)
122 | {
123 | Assert.Throws(complexFFT);
124 | Assert.Throws(complexSpanFFT);
125 | Assert.Throws(complexIFFT);
126 | Assert.Throws(realFFT);
127 | Assert.Throws(realRFFT);
128 | }
129 | else
130 | {
131 | Assert.DoesNotThrow(complexFFT);
132 | Assert.DoesNotThrow(complexSpanFFT);
133 | Assert.DoesNotThrow(complexIFFT);
134 | Assert.DoesNotThrow(realFFT);
135 | Assert.DoesNotThrow(realRFFT);
136 | }
137 | }
138 |
139 | [TestCase(0, true)]
140 | [TestCase(1, true)]
141 | [TestCase(2, false)]
142 | [TestCase(4, false)]
143 | [TestCase(8, false)]
144 | [TestCase(16, false)]
145 | [TestCase(32, false)]
146 | [TestCase(64, false)]
147 | public void Test_Fft_Magnitude_DifferentLengths(int pointCount, bool shouldFail)
148 | {
149 | double[] signal = new double[pointCount];
150 | if (shouldFail)
151 | {
152 | Assert.Throws(() => FftSharp.FFT.ForwardReal(signal));
153 | }
154 | else
155 | {
156 | Assert.DoesNotThrow(() => FftSharp.FFT.ForwardReal(signal));
157 | }
158 | }
159 | }
--------------------------------------------------------------------------------
/src/FftSharp.Tests/Window.cs:
--------------------------------------------------------------------------------
1 | using NUnit.Framework;
2 | using ScottPlot;
3 | using System;
4 | using System.Collections.Generic;
5 | using System.IO;
6 | using System.Linq;
7 | using System.Reflection.Emit;
8 |
9 | namespace FftSharp.Tests;
10 |
11 | class Window
12 | {
13 | public static string OUTPUT_FOLDER = Path.GetFullPath(Path.Combine(TestContext.CurrentContext.TestDirectory, "../../../../../dev/quickstart/"));
14 |
15 | [Test]
16 | public void Test_GetWindow_Works()
17 | {
18 | IWindow[] windows = FftSharp.Window.GetWindows();
19 |
20 | foreach (IWindow window in windows)
21 | {
22 | Console.WriteLine(window);
23 | }
24 |
25 | Assert.IsNotNull(windows);
26 | Assert.IsNotEmpty(windows);
27 | }
28 |
29 | [Test]
30 | public void Test_WindowNames_AreNotEmpty()
31 | {
32 | foreach (var window in FftSharp.Window.GetWindows())
33 | {
34 | Assert.That(string.IsNullOrWhiteSpace(window.Name) == false);
35 | }
36 | }
37 |
38 | [Test]
39 | public void Test_WindowDescriptions_AreNotEmpty()
40 | {
41 | foreach (var window in FftSharp.Window.GetWindows())
42 | {
43 | Assert.That(string.IsNullOrWhiteSpace(window.Description) == false);
44 | }
45 | }
46 |
47 | [Test]
48 | public void Test_WindowNames_AreUnique()
49 | {
50 | var names = FftSharp.Window.GetWindows().Select(x => x.Name);
51 |
52 | Assert.IsNotEmpty(names);
53 | Assert.AreEqual(names.Count(), names.Distinct().Count());
54 | }
55 |
56 | [Test]
57 | public void Test_WindowDescriptions_AreUnique()
58 | {
59 | var descriptions = FftSharp.Window.GetWindows().Select(x => x.Description);
60 |
61 | Assert.IsNotEmpty(descriptions);
62 | Assert.AreEqual(descriptions.Count(), descriptions.Distinct().Count());
63 | }
64 |
65 | [Test]
66 | public void Test_NormalizedWindows_SumIsOne()
67 | {
68 | foreach (IWindow window in FftSharp.Window.GetWindows())
69 | {
70 | double[] kernel = window.Create(123, true);
71 | double sum = kernel.Sum();
72 | Assert.AreEqual(1, sum, delta: 1e-10, $"{window} sum is {sum}");
73 | }
74 | }
75 |
76 | [Test]
77 | public void Test_OddLength_CenterIndexIsBiggest()
78 | {
79 | foreach (IWindow window in FftSharp.Window.GetWindows())
80 | {
81 | double[] values = window.Create(13);
82 | Assert.GreaterOrEqual(values[6], values[5], window.Name);
83 | Assert.GreaterOrEqual(values[6], values[7], window.Name);
84 | }
85 | }
86 |
87 | [Test]
88 | public void Test_EvenLength_CenterTwoAreSame()
89 | {
90 | foreach (IWindow window in FftSharp.Window.GetWindows())
91 | {
92 | double[] values = window.Create(12);
93 | if (window.IsSymmetric)
94 | {
95 | Assert.AreEqual(values[5], values[6], 1e-5, window.Name);
96 | Assert.AreEqual(values.First(), values.Last(), 1e-5, window.Name);
97 | }
98 | else
99 | {
100 | Assert.AreNotEqual(values[5], values[6], window.Name);
101 | Assert.AreNotEqual(values.First(), values.Last(), window.Name);
102 | }
103 | }
104 | }
105 |
106 | [Test]
107 | public void Test_Plot_AllWindowKernels()
108 | {
109 | ScottPlot.Plot plt = new();
110 | plt.Add.Palette = new ScottPlot.Palettes.ColorblindFriendly();
111 |
112 | foreach (IWindow window in FftSharp.Window.GetWindows())
113 | {
114 | if (window.Name == "Rectangular")
115 | continue;
116 | double[] xs = ScottPlot.Generate.Range(-1, 1, .01);
117 | double[] ys = window.Create(xs.Length);
118 | var sp = plt.Add.Scatter(xs, ys);
119 | sp.LegendText = window.Name;
120 | sp.MarkerSize = 0;
121 | sp.LineWidth = 3;
122 | sp.Color = sp.Color.WithAlpha(.8);
123 | }
124 |
125 | plt.Legend.Alignment = Alignment.UpperRight;
126 | plt.SavePng(Path.Combine(OUTPUT_FOLDER, "windows.png"), 500, 400);
127 | }
128 |
129 | [Test]
130 | public void Test_Window_Reflection()
131 | {
132 | IWindow[] window = FftSharp.Window.GetWindows();
133 | Assert.IsNotEmpty(window);
134 | }
135 |
136 | [Test]
137 | public void Test_PlotAllWindows()
138 | {
139 | foreach (IWindow window in FftSharp.Window.GetWindows())
140 | {
141 | double[] values = window.Create(32);
142 | ScottPlot.Plot plt = new();
143 | var sig = plt.Add.Signal(values);
144 | sig.Data.XOffset = -values.Length / 2 + .5;
145 | plt.Title(window.Name);
146 | plt.Add.VerticalLine(0);
147 |
148 | string filename = Path.GetFullPath($"test_window_{window.Name}.png");
149 | Console.WriteLine(filename);
150 | plt.SavePng(filename, 400, 300);
151 | }
152 | }
153 | }
154 |
--------------------------------------------------------------------------------
/src/FftSharp.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio Version 17
4 | VisualStudioVersion = 17.0.31815.197
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FftSharp", "FftSharp\FftSharp.csproj", "{43680BFA-9D82-41F2-A56A-6B93433AE4E1}"
7 | EndProject
8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FftSharp.Demo", "FftSharp.Demo\FftSharp.Demo.csproj", "{9EA24240-F1BF-4C5F-B08C-6D5C8AFBFDE7}"
9 | EndProject
10 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FftSharp.Tests", "FftSharp.Tests\FftSharp.Tests.csproj", "{A82CD7FC-ECA6-4BF7-BC73-22675DEC1CD0}"
11 | EndProject
12 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FftSharp.Benchmark", "FftSharp.Benchmark\FftSharp.Benchmark.csproj", "{A3DE12AF-60E1-4CF2-86BF-73C2B345091B}"
13 | EndProject
14 | Global
15 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
16 | Debug|Any CPU = Debug|Any CPU
17 | Release|Any CPU = Release|Any CPU
18 | EndGlobalSection
19 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
20 | {43680BFA-9D82-41F2-A56A-6B93433AE4E1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
21 | {43680BFA-9D82-41F2-A56A-6B93433AE4E1}.Debug|Any CPU.Build.0 = Debug|Any CPU
22 | {43680BFA-9D82-41F2-A56A-6B93433AE4E1}.Release|Any CPU.ActiveCfg = Release|Any CPU
23 | {43680BFA-9D82-41F2-A56A-6B93433AE4E1}.Release|Any CPU.Build.0 = Release|Any CPU
24 | {9EA24240-F1BF-4C5F-B08C-6D5C8AFBFDE7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
25 | {9EA24240-F1BF-4C5F-B08C-6D5C8AFBFDE7}.Debug|Any CPU.Build.0 = Debug|Any CPU
26 | {9EA24240-F1BF-4C5F-B08C-6D5C8AFBFDE7}.Release|Any CPU.ActiveCfg = Release|Any CPU
27 | {9EA24240-F1BF-4C5F-B08C-6D5C8AFBFDE7}.Release|Any CPU.Build.0 = Release|Any CPU
28 | {A82CD7FC-ECA6-4BF7-BC73-22675DEC1CD0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
29 | {A82CD7FC-ECA6-4BF7-BC73-22675DEC1CD0}.Debug|Any CPU.Build.0 = Debug|Any CPU
30 | {A82CD7FC-ECA6-4BF7-BC73-22675DEC1CD0}.Release|Any CPU.ActiveCfg = Release|Any CPU
31 | {A82CD7FC-ECA6-4BF7-BC73-22675DEC1CD0}.Release|Any CPU.Build.0 = Release|Any CPU
32 | {A3DE12AF-60E1-4CF2-86BF-73C2B345091B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
33 | {A3DE12AF-60E1-4CF2-86BF-73C2B345091B}.Debug|Any CPU.Build.0 = Debug|Any CPU
34 | {A3DE12AF-60E1-4CF2-86BF-73C2B345091B}.Release|Any CPU.ActiveCfg = Release|Any CPU
35 | {A3DE12AF-60E1-4CF2-86BF-73C2B345091B}.Release|Any CPU.Build.0 = Release|Any CPU
36 | EndGlobalSection
37 | GlobalSection(SolutionProperties) = preSolution
38 | HideSolutionNode = FALSE
39 | EndGlobalSection
40 | GlobalSection(ExtensibilityGlobals) = postSolution
41 | SolutionGuid = {1721ADCD-AF40-45E0-A3AA-DE007761C72A}
42 | EndGlobalSection
43 | EndGlobal
44 |
--------------------------------------------------------------------------------
/src/FftSharp/.editorconfig:
--------------------------------------------------------------------------------
1 | [*.cs]
2 | csharp_style_namespace_declarations = file_scoped:warning
--------------------------------------------------------------------------------
/src/FftSharp/DFT.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace FftSharp;
4 |
5 | ///
6 | /// Methods for calculating the discrete Fourier Transform (slower than the FFT algorithm)
7 | ///
8 | public static class DFT
9 | {
10 | ///
11 | /// Compute the forward discrete Fourier Transform
12 | ///
13 | public static System.Numerics.Complex[] Forward(System.Numerics.Complex[] input)
14 | {
15 | return GetDFT(input, false);
16 | }
17 |
18 | ///
19 | /// Compute the forward discrete Fourier Transform
20 | ///
21 | public static System.Numerics.Complex[] Forward(double[] real)
22 | {
23 | return Forward(real.ToComplexArray());
24 | }
25 |
26 | ///
27 | /// Compute the inverse discrete Fourier Transform
28 | ///
29 | public static System.Numerics.Complex[] Inverse(System.Numerics.Complex[] input)
30 | {
31 | return GetDFT(input, true);
32 | }
33 |
34 | ///
35 | /// Compute the inverse discrete Fourier Transform
36 | ///
37 | public static System.Numerics.Complex[] Inverse(double[] real)
38 | {
39 | return Inverse(real.ToComplexArray());
40 | }
41 |
42 | private static System.Numerics.Complex[] GetDFT(System.Numerics.Complex[] input, bool inverse)
43 | {
44 | int N = input.Length;
45 | double mult1 = (inverse) ? 2 * Math.PI / N : -2 * Math.PI / N;
46 | double mult2 = (inverse) ? 1.0 / N : 1.0;
47 |
48 | System.Numerics.Complex[] output = new System.Numerics.Complex[N];
49 | for (int k = 0; k < N; k++)
50 | {
51 | output[k] = new System.Numerics.Complex(0, 0);
52 | for (int n = 0; n < N; n++)
53 | {
54 | double radians = n * k * mult1;
55 | System.Numerics.Complex temp = new System.Numerics.Complex(Math.Cos(radians), Math.Sin(radians));
56 | temp *= input[n];
57 | output[k] += temp * mult2;
58 | }
59 | }
60 |
61 | return output;
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/src/FftSharp/Extensions.cs:
--------------------------------------------------------------------------------
1 | namespace FftSharp;
2 |
3 | internal static class Extensions
4 | {
5 | ///
6 | /// Return a Complex array with the given values as the real component
7 | ///
8 | ///
9 | ///
10 | public static System.Numerics.Complex[] ToComplexArray(this double[] real)
11 | {
12 | System.Numerics.Complex[] buffer = new System.Numerics.Complex[real.Length];
13 |
14 | for (int i = 0; i < real.Length; i++)
15 | buffer[i] = new(real[i], 0);
16 |
17 | return buffer;
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/FftSharp/FftOperations.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Buffers;
3 |
4 | namespace FftSharp;
5 |
6 | ///
7 | /// Primary methods for calculating FFT and performing related operations.
8 | ///
9 | internal class FftOperations
10 | {
11 | ///
12 | /// Returns true if the given number is evenly divisible by a power of 2
13 | ///
14 | public static bool IsPowerOfTwo(int x)
15 | {
16 | return ((x & (x - 1)) == 0) && (x > 0);
17 | }
18 |
19 | ///
20 | /// High performance FFT function.
21 | /// Complex input will be transformed in place.
22 | /// Input array length must be a power of two. This length is NOT validated.
23 | /// Running on an array with an invalid length may throw an invalid index exception.
24 | ///
25 | public static void FFT_WithoutChecks(Span buffer)
26 | {
27 | for (int i = 1; i < buffer.Length; i++)
28 | {
29 | int j = BitReverse(i, buffer.Length);
30 | if (j > i)
31 | (buffer[j], buffer[i]) = (buffer[i], buffer[j]);
32 | }
33 |
34 | for (int i = 1; i <= buffer.Length / 2; i *= 2)
35 | {
36 | double mult1 = -Math.PI / i;
37 | for (int j = 0; j < buffer.Length; j += (i * 2))
38 | {
39 | for (int k = 0; k < i; k++)
40 | {
41 | int evenI = j + k;
42 | int oddI = j + k + i;
43 | System.Numerics.Complex temp = new(Math.Cos(mult1 * k), Math.Sin(mult1 * k));
44 | temp *= buffer[oddI];
45 | buffer[oddI] = buffer[evenI] - temp;
46 | buffer[evenI] += temp;
47 | }
48 | }
49 | }
50 | }
51 |
52 | ///
53 | /// Calculate FFT of the input and return just the real component
54 | /// Input array length must be a power of two. This length is NOT validated.
55 | /// Running on an array with an invalid length may throw an invalid index exception.
56 | ///
57 | public static void RFFT_WithoutChecks(Span destination, Span input)
58 | {
59 | System.Numerics.Complex[] temp = ArrayPool.Shared.Rent(input.Length);
60 |
61 | try
62 | {
63 | Span buffer = temp.AsSpan(0, input.Length);
64 | input.CopyTo(buffer);
65 | FFT_WithoutChecks(buffer);
66 | buffer.Slice(0, destination.Length).CopyTo(destination);
67 | }
68 | catch (Exception ex)
69 | {
70 | throw new Exception("Could not calculate RFFT", ex);
71 | }
72 | finally
73 | {
74 | ArrayPool.Shared.Return(temp);
75 | }
76 | }
77 |
78 | ///
79 | /// High performance inverse FFT function.
80 | /// Complex input will be transformed in place.
81 | /// Input array length must be a power of two. This length is NOT validated.
82 | /// Running on an array with an invalid length may throw an invalid index exception.
83 | ///
84 | public static void IFFT_WithoutChecks(Span buffer)
85 | {
86 | // invert the imaginary component
87 | for (int i = 0; i < buffer.Length; i++)
88 | buffer[i] = new(buffer[i].Real, -buffer[i].Imaginary);
89 |
90 | // perform a forward Fourier transform
91 | FFT_WithoutChecks(buffer);
92 |
93 | // invert the imaginary component again and scale the output
94 | for (int i = 0; i < buffer.Length; i++)
95 | buffer[i] = new(
96 | real: buffer[i].Real / buffer.Length,
97 | imaginary: -buffer[i].Imaginary / buffer.Length);
98 | }
99 |
100 | ///
101 | /// High performance inverse FFT function.
102 | /// Complex input will be transformed in place.
103 | /// Input array length must be a power of two. This length is NOT validated.
104 | /// Running on an array with an invalid length may throw an invalid index exception.
105 | ///
106 | public static void IFFT_WithoutChecks(System.Numerics.Complex[] buffer)
107 | {
108 | // invert the imaginary component
109 | for (int i = 0; i < buffer.Length; i++)
110 | buffer[i] = new(buffer[i].Real, -buffer[i].Imaginary);
111 |
112 | // perform a forward Fourier transform
113 | FFT_WithoutChecks(buffer);
114 |
115 | // invert the imaginary component again and scale the output
116 | for (int i = 0; i < buffer.Length; i++)
117 | buffer[i] = new(
118 | real: buffer[i].Real / buffer.Length,
119 | imaginary: -buffer[i].Imaginary / buffer.Length);
120 | }
121 |
122 | ///
123 | /// Reverse the order of bits in an integer
124 | ///
125 | private static int BitReverse(int value, int maxValue)
126 | {
127 | int maxBitCount = (int)Math.Log(maxValue, 2);
128 | int output = value;
129 | int bitCount = maxBitCount - 1;
130 |
131 | value >>= 1;
132 | while (value > 0)
133 | {
134 | output = (output << 1) | (value & 1);
135 | bitCount -= 1;
136 | value >>= 1;
137 | }
138 |
139 | return (output << bitCount) & ((1 << maxBitCount) - 1);
140 | }
141 | }
142 |
--------------------------------------------------------------------------------
/src/FftSharp/FftSharp.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | netstandard2.0;net8.0
5 | false
6 | Library
7 | https://github.com/swharden/FftSharp
8 | MIT
9 | A .NET Standard library for computing the Fast Fourier Transform (FFT) of real or complex data
10 | Scott Harden
11 | Harden Technologies, LLC
12 | icon.png
13 | README.md
14 | https://github.com/swharden/FftSharp
15 | fft ifft fourier transform audio signal sound filter window hanning hamming
16 | https://github.com/swharden/FftSharp/releases
17 | true
18 | 2.2.0
19 | 2.2.0.0
20 | 2.2.0.0
21 | 1591
22 | portable
23 | true
24 | snupkg
25 | true
26 | true
27 | true
28 | true
29 | 10
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 | runtime; build; native; contentfiles; analyzers; buildtransitive
40 | all
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
--------------------------------------------------------------------------------
/src/FftSharp/Filter.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace FftSharp;
4 |
5 | ///
6 | /// A collection of helper methods for filtering signals using FFT/IFFT
7 | ///
8 | public static class Filter
9 | {
10 | ///
11 | /// Silence frequencies above the given frequency
12 | ///
13 | public static double[] LowPass(double[] values, double sampleRate, double maxFrequency) =>
14 | BandPass(values, sampleRate, double.NegativeInfinity, maxFrequency);
15 |
16 | ///
17 | /// Silence frequencies below the given frequency
18 | ///
19 | public static double[] HighPass(double[] values, double sampleRate, double minFrequency) =>
20 | BandPass(values, sampleRate, minFrequency, double.PositiveInfinity);
21 |
22 | ///
23 | /// Silence frequencies outside the given frequency range
24 | ///
25 | public static double[] BandPass(double[] values, double sampleRate, double minFrequency, double maxFrequency)
26 | {
27 | System.Numerics.Complex[] fft = FFT.Forward(values);
28 | double[] fftFreqs = FFT.FrequencyScale(fft.Length, sampleRate, false);
29 | for (int i = 0; i < fft.Length; i++)
30 | {
31 | double freq = Math.Abs(fftFreqs[i]);
32 | if ((freq > maxFrequency) || (freq < minFrequency))
33 | {
34 | fft[i] = new(0, 0);
35 | }
36 | }
37 | return InverseReal(fft);
38 | }
39 |
40 | ///
41 | /// Silence frequencies inside the given frequency range
42 | ///
43 | public static double[] BandStop(double[] values, double sampleRate, double minFrequency, double maxFrequency)
44 | {
45 | System.Numerics.Complex[] fft = FFT.Forward(values);
46 | double[] fftFreqs = FFT.FrequencyScale(fft.Length, sampleRate, false);
47 | for (int i = 0; i < fft.Length; i++)
48 | {
49 | double freq = Math.Abs(fftFreqs[i]);
50 | if ((freq <= maxFrequency) && (freq >= minFrequency))
51 | {
52 | fft[i] = new(0, 0);
53 | }
54 | }
55 | return InverseReal(fft);
56 | }
57 |
58 | private static double[] InverseReal(System.Numerics.Complex[] fft)
59 | {
60 | FFT.Inverse(fft);
61 | double[] Filtered = new double[fft.Length];
62 | for (int i = 0; i < fft.Length; i++)
63 | Filtered[i] = fft[i].Real;
64 | return Filtered;
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/src/FftSharp/IWindow.cs:
--------------------------------------------------------------------------------
1 | namespace FftSharp;
2 |
3 | ///
4 | /// Describes a window function that may be used to shape a segment of signal data before spectral transformation
5 | ///
6 | public interface IWindow
7 | {
8 | ///
9 | /// Generate this window as a new array with the given length.
10 | /// Normalizing will scale the window so the sum of all points is 1.
11 | ///
12 | double[] Create(int size, bool normalize = false);
13 |
14 | ///
15 | /// Return a new array where this window was multiplied by the given signal.
16 | /// Normalizing will scale the window so the sum of all points is 1 prior to multiplication.
17 | ///
18 | double[] Apply(double[] input, bool normalize = false);
19 |
20 | ///
21 | /// Modify the given signal by multiplying it by this window IN PLACE.
22 | /// Normalizing will scale the window so the sum of all points is 1 prior to multiplication.
23 | ///
24 | void ApplyInPlace(double[] input, bool normalize = false);
25 |
26 | ///
27 | /// Single word name for this window
28 | ///
29 | string Name { get; }
30 |
31 | ///
32 | /// A brief description of what makes this window unique and what it is typically used for.
33 | ///
34 | string Description { get; }
35 |
36 | ///
37 | /// Indicates whether the window is symmetric around its midpoint
38 | ///
39 | bool IsSymmetric { get; }
40 | }
41 |
--------------------------------------------------------------------------------
/src/FftSharp/Mel.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace FftSharp;
4 |
5 | ///
6 | /// Methods for conversion to/from Mel scaling
7 | ///
8 | public static class Mel
9 | {
10 | public static double ToFreq(double mel) => 700 * (Math.Pow(10, mel / 2595d) - 1);
11 |
12 | public static double FromFreq(double frequencyHz) => 2595 * Math.Log10(1 + frequencyHz / 700);
13 |
14 | public static double[] Scale(double[] fft, int sampleRate, int melBinCount)
15 | {
16 | double freqMax = sampleRate / 2;
17 | double maxMel = FromFreq(freqMax);
18 |
19 | double[] fftMel = new double[melBinCount];
20 | double melPerBin = maxMel / (melBinCount + 1);
21 | for (int binIndex = 0; binIndex < melBinCount; binIndex++)
22 | {
23 | double melLow = melPerBin * binIndex;
24 | double melHigh = melPerBin * (binIndex + 2);
25 |
26 | double freqLow = ToFreq(melLow);
27 | double freqHigh = ToFreq(melHigh);
28 |
29 | int indexLow = (int)(fft.Length * freqLow / freqMax);
30 | int indexHigh = (int)(fft.Length * freqHigh / freqMax);
31 | int indexSpan = indexHigh - indexLow;
32 |
33 | double binScaleSum = 0;
34 | for (int i = 0; i < indexSpan; i++)
35 | {
36 | double binFrac = (double)i / indexSpan;
37 | double indexScale = (binFrac < .5) ? binFrac * 2 : 1 - binFrac;
38 | binScaleSum += indexScale;
39 | fftMel[binIndex] += fft[indexLow + i] * indexScale;
40 | }
41 | fftMel[binIndex] /= binScaleSum;
42 | }
43 |
44 | return fftMel;
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/src/FftSharp/Pad.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace FftSharp;
4 |
5 | ///
6 | /// A collection of helper methods for padding data
7 | ///
8 | public static class Pad
9 | {
10 | ///
11 | /// Return the input array (or a new zero-padded new one) ensuring length is a power of 2
12 | ///
13 | /// array of any length
14 | /// the input array or a zero-padded copy
15 | public static System.Numerics.Complex[] ZeroPad(System.Numerics.Complex[] input)
16 | {
17 | if (FftOperations.IsPowerOfTwo(input.Length))
18 | return input;
19 |
20 | int targetLength = 1;
21 | while (targetLength < input.Length)
22 | targetLength *= 2;
23 |
24 | int difference = targetLength - input.Length;
25 | System.Numerics.Complex[] padded = new System.Numerics.Complex[targetLength];
26 | Array.Copy(input, 0, padded, difference / 2, input.Length);
27 |
28 | return padded;
29 | }
30 |
31 | ///
32 | /// Return the input array (or a new zero-padded new one) ensuring length is a power of 2
33 | ///
34 | /// array of any length
35 | /// the input array or a zero-padded copy
36 | public static double[] ZeroPad(double[] input)
37 | {
38 | if (FftOperations.IsPowerOfTwo(input.Length))
39 | return input;
40 |
41 | int targetLength = 1;
42 | while (targetLength < input.Length)
43 | targetLength *= 2;
44 |
45 | int difference = targetLength - input.Length;
46 | double[] padded = new double[targetLength];
47 | Array.Copy(input, 0, padded, difference / 2, input.Length);
48 |
49 | return padded;
50 | }
51 |
52 | ///
53 | /// Return the input array zero-padded to reach a final length
54 | ///
55 | /// array of any length
56 | /// pad the array with zeros a the end to achieve this final length
57 | /// a zero-padded copy of the input array
58 | public static double[] ZeroPad(double[] input, int finalLength)
59 | {
60 | int difference = finalLength - input.Length;
61 | double[] padded = new double[finalLength];
62 | Array.Copy(input, 0, padded, difference / 2, input.Length);
63 | return padded;
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/src/FftSharp/README.md:
--------------------------------------------------------------------------------
1 | **FftSharp is a collection of Fast Fourier Transform (FFT) tools for .NET**
2 |
3 | ### Quickstart
4 |
5 | ```cs
6 | // Begin with an array containing sample data
7 | double[] signal = FftSharp.SampleData.SampleAudio1();
8 |
9 | // Shape the signal using a Hanning window
10 | var window = new FftSharp.Windows.Hanning();
11 | window.ApplyInPlace(signal);
12 |
13 | // Calculate the FFT as an array of complex numbers
14 | System.Numerics.Complex[] spectrum = FftSharp.FFT.Forward(signal);
15 |
16 | // or get the magnitude (units²) or power (dB) as real numbers
17 | double[] magnitude = FftSharp.FFT.Magnitude(spectrum);
18 | double[] power = FftSharp.FFT.Power(spectrum);
19 | ```
--------------------------------------------------------------------------------
/src/FftSharp/Window.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Linq;
3 | using System.Reflection;
4 |
5 | namespace FftSharp;
6 |
7 | ///
8 | /// Describes a window function that may be used to shape a segment of signal data before spectral transformation
9 | ///
10 | public abstract class Window : IWindow
11 | {
12 | public abstract string Name { get; }
13 |
14 | public abstract string Description { get; }
15 |
16 | public abstract bool IsSymmetric { get; }
17 |
18 | public override string ToString() => Name;
19 |
20 | ///
21 | /// Generate an array of values shaped like this window
22 | ///
23 | /// number of points to generate
24 | /// if true, sum of all values returned will equal 1
25 | ///
26 | public abstract double[] Create(int size, bool normalize = false);
27 |
28 | ///
29 | /// Multiply the array by this window and return the result as a new array
30 | ///
31 | public double[] Apply(double[] input, bool normalize = false)
32 | {
33 | // TODO: save this window so it can be re-used if the next request is the same size
34 | double[] window = Create(input.Length, normalize);
35 | double[] output = new double[input.Length];
36 | for (int i = 0; i < input.Length; i++)
37 | output[i] = input[i] * window[i];
38 | return output;
39 | }
40 |
41 | ///
42 | /// Multiply the array by this window, modifying it in place
43 | ///
44 | public void ApplyInPlace(double[] input, bool normalize = false)
45 | {
46 | double[] window = Create(input.Length, normalize);
47 | for (int i = 0; i < input.Length; i++)
48 | input[i] = input[i] * window[i];
49 | }
50 |
51 | ///
52 | /// Scale all values in the window equally (in-place) so their total is 1
53 | ///
54 | internal static void NormalizeInPlace(double[] values)
55 | {
56 | double sum = 0;
57 | for (int i = 0; i < values.Length; i++)
58 | sum += values[i];
59 |
60 | for (int i = 0; i < values.Length; i++)
61 | values[i] /= sum;
62 | }
63 |
64 | ///
65 | /// Return an array containing all available windows.
66 | /// Note that all windows returned will use the default constructor, but some
67 | /// windows have customization options in their constructors if you create them individually.
68 | ///
69 | public static IWindow[] GetWindows()
70 | {
71 | return Assembly.GetExecutingAssembly()
72 | .GetTypes()
73 | .Where(x => x.IsClass)
74 | .Where(x => !x.IsAbstract)
75 | .Where(x => x.GetInterfaces().Contains(typeof(IWindow)))
76 | .Select(x => (IWindow)Activator.CreateInstance(x))
77 | .ToArray();
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/src/FftSharp/Windows/Bartlett.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace FftSharp.Windows;
4 |
5 | public class Bartlett : Window, IWindow
6 | {
7 | public override string Name => "Bartlett–Hann";
8 | public override string Description =>
9 | "The Bartlett–Hann window is triangular in shape (a 2nd order B-spline) which is effectively the " +
10 | "convolution of two half-sized rectangular windows.";
11 |
12 | public override bool IsSymmetric => true;
13 |
14 | public override double[] Create(int size, bool normalize = false)
15 | {
16 | double[] window = new double[size];
17 |
18 | bool isOddSize = size % 2 == 1;
19 |
20 | double halfSize = isOddSize ? size / 2 : (size - 1) / 2.0;
21 |
22 | for (int i = 0; i < size; i++)
23 | window[i] = 1 - Math.Abs((double)(i - halfSize) / halfSize);
24 |
25 | if (normalize)
26 | NormalizeInPlace(window);
27 |
28 | return window;
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/src/FftSharp/Windows/Blackman.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace FftSharp.Windows;
4 |
5 | public class Blackman : Window, IWindow
6 | {
7 | private readonly double A = 0.42659071;
8 | private readonly double B = 0.49656062;
9 | private readonly double C = 0.07684867;
10 |
11 | public override string Name => "Blackman-Harris";
12 | public override string Description =>
13 | "The Blackman-Harris window is similar to Hamming and Hanning windows. " +
14 | "The resulting spectrum has a wide peak, but good side lobe compression.";
15 |
16 | public override bool IsSymmetric => true;
17 |
18 | public Blackman()
19 | {
20 | }
21 |
22 | //TODO: 5-term constructor to allow testing Python's flattop
23 | public Blackman(double a, double b, double c)
24 | {
25 | (A, B, C) = (a, b, c);
26 | }
27 |
28 | public override double[] Create(int size, bool normalize = false)
29 | {
30 | double[] window = new double[size];
31 |
32 | for (int i = 0; i < size; i++)
33 | {
34 | double frac = (double)i / (size - 1);
35 | window[i] = A - B * Math.Cos(2 * Math.PI * frac) + C * Math.Cos(4 * Math.PI * frac);
36 | }
37 |
38 | if (normalize)
39 | NormalizeInPlace(window);
40 |
41 | return window;
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/src/FftSharp/Windows/Cosine.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Linq;
3 |
4 | namespace FftSharp.Windows;
5 |
6 | public class Cosine : Window, IWindow
7 | {
8 | public override string Name => "Cosine";
9 | public override string Description =>
10 | "This window is simply a cosine function. It reaches zero on both sides and is similar to " +
11 | "Blackman, Hamming, Hanning, and flat top windows, but probably should not be used in practice.";
12 |
13 | public override bool IsSymmetric => true;
14 |
15 | public override double[] Create(int size, bool normalize = false)
16 | {
17 | double[] window = Enumerable.Range(0, size).Select(x => Math.Sin(Math.PI / (size) * (x + .5))).ToArray();
18 |
19 | if (normalize)
20 | NormalizeInPlace(window);
21 |
22 | return window;
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/src/FftSharp/Windows/FlatTop.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Text;
4 |
5 | namespace FftSharp.Windows;
6 |
7 | public class FlatTop : Window, IWindow
8 | {
9 | public override string Name => "Flat Top";
10 | public override string Description =>
11 | "A flat top window is a partially negative-valued window that has minimal scalloping loss in the frequency domain. " +
12 | "These properties are desirable for the measurement of amplitudes of sinusoidal frequency components. " +
13 | "Drawbacks of the broad bandwidth are poor frequency resolution and high noise bandwidth. " +
14 | "The flat top window crosses the zero line causing a broader peak in the frequency domain, " +
15 | "which is closer to the true amplitude of the signal than with other windows";
16 |
17 | public override bool IsSymmetric => true;
18 |
19 | public readonly double A0 = 0.21557895;
20 | public readonly double A1 = 0.41663158;
21 | public readonly double A2 = 0.277263158;
22 | public readonly double A3 = 0.083578947;
23 | public readonly double A4 = 0.006947368;
24 |
25 | public FlatTop()
26 | {
27 | }
28 |
29 | public FlatTop(double a0, double a1, double a2, double a3, double a4)
30 | {
31 | A0 = a0;
32 | A1 = a1;
33 | A2 = a2;
34 | A3 = a3;
35 | A4 = a4;
36 | }
37 |
38 | public override double[] Create(int size, bool normalize = false)
39 | {
40 | double[] window = new double[size];
41 |
42 | for (int i = 0; i < size; i++)
43 | {
44 | window[i] = A0
45 | - A1 * Math.Cos(2 * Math.PI * i / (size - 1))
46 | + A2 * Math.Cos(4 * Math.PI * i / (size - 1))
47 | - A3 * Math.Cos(6 * Math.PI * i / (size - 1))
48 | + A4 * Math.Cos(8 * Math.PI * i / (size - 1));
49 | }
50 |
51 | if (normalize)
52 | NormalizeInPlace(window);
53 |
54 | return window;
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/src/FftSharp/Windows/Hamming.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace FftSharp.Windows;
4 |
5 | public class Hamming : Window, IWindow
6 | {
7 | public override string Name => "Hamming";
8 | public override string Description =>
9 | "The Hamming window has a sinusoidal shape does NOT touch zero at the edges (unlike the similar Hanning window). " +
10 | "It is similar to the Hanning window but its abrupt edges are designed to cancel the largest side lobe. " +
11 | "It may be a good choice for low-quality (8-bit) auto where side lobes lie beyond the quantization noise floor." +
12 | "A symmetric window, for use in filter design.";
13 |
14 | public override bool IsSymmetric => true;
15 |
16 | public override double[] Create(int size, bool normalize = false)
17 | {
18 | double[] window = new double[size];
19 |
20 | double phaseStep = (2.0 * Math.PI) / (size - 1.0);
21 |
22 | for (int i = 0; i < size; i++)
23 | window[i] = 0.54 - 0.46 * Math.Cos(i * phaseStep);
24 |
25 | if (normalize)
26 | NormalizeInPlace(window);
27 |
28 | return window;
29 | }
30 | }
--------------------------------------------------------------------------------
/src/FftSharp/Windows/HammingPeriodic.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace FftSharp.Windows;
4 |
5 | public class HammingPeriodic : Window, IWindow
6 | {
7 | public override string Name => "HammingPeriodic";
8 | public override string Description =>
9 | "The Hamming window has a sinusoidal shape does NOT touch zero at the edges (unlike the similar Hanning window). " +
10 | "It is similar to the Hanning window but its abrupt edges are designed to cancel the largest side lobe. " +
11 | "It may be a good choice for low-quality (8-bit) auto where side lobes lie beyond the quantization noise floor." +
12 | "A periodic window, for use in spectral analysis.";
13 |
14 | public override bool IsSymmetric => false;
15 |
16 | public override double[] Create(int size, bool normalize = false)
17 | {
18 | double[] window = new double[size];
19 |
20 | double phaseStep = (2.0 * Math.PI) / size;
21 |
22 | for (int i = 0; i < size; i++)
23 | window[i] = 0.54 - 0.46 * Math.Cos(i * phaseStep);
24 |
25 | if (normalize)
26 | NormalizeInPlace(window);
27 |
28 | return window;
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/src/FftSharp/Windows/Hanning.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace FftSharp.Windows;
4 |
5 | public class Hanning : Window, IWindow
6 | {
7 | public override string Name => "Hanning";
8 | public override string Description =>
9 | "The Hanning window has a sinusoidal shape which touches zero at the edges (unlike the similar Hamming window). " +
10 | "It has good frequency resolution, low spectral leakage, and is satisfactory for 95 percent of use cases. " +
11 | "If you do not know the nature of the signal but you want to apply a smoothing window, start with the Hann window." +
12 | "A symmetric window, for use in filter design.";
13 |
14 | public override bool IsSymmetric => true;
15 |
16 | public override double[] Create(int size, bool normalize = false)
17 | {
18 | double[] window = new double[size];
19 |
20 | double phaseStep = (2.0 * Math.PI) / (size - 1.0);
21 |
22 | for (int i = 0; i < size; i++)
23 | window[i] = 0.5 - 0.5 * Math.Cos(i * phaseStep);
24 |
25 | if (normalize)
26 | NormalizeInPlace(window);
27 |
28 | return window;
29 | }
30 | }
--------------------------------------------------------------------------------
/src/FftSharp/Windows/HanningPeriodic.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace FftSharp.Windows;
4 |
5 | public class HanningPeriodic : Window, IWindow
6 | {
7 | public override string Name => "HanningPeriodic";
8 | public override string Description =>
9 | "The Hanning window has a sinusoidal shape which touches zero at the edges (unlike the similar Hamming window). " +
10 | "It has good frequency resolution, low spectral leakage, and is satisfactory for 95 percent of use cases. " +
11 | "If you do not know the nature of the signal but you want to apply a smoothing window, start with the Hann window." +
12 | "A periodic window, for use in spectral analysis.";
13 |
14 | public override bool IsSymmetric => false;
15 |
16 | public override double[] Create(int size, bool normalize = false)
17 | {
18 | double[] window = new double[size];
19 |
20 | double phaseStep = (2.0 * Math.PI) / size;
21 |
22 | for (int i = 0; i < size; i++)
23 | window[i] = 0.5 - 0.5 * Math.Cos(i * phaseStep);
24 |
25 | if (normalize)
26 | NormalizeInPlace(window);
27 |
28 | return window;
29 | }
30 | }
--------------------------------------------------------------------------------
/src/FftSharp/Windows/Kaiser.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace FftSharp.Windows;
4 |
5 | public class Kaiser : Window, IWindow
6 | {
7 | public readonly double Beta;
8 | public override string Name => $"Kaiser-Bessel";
9 | public override string Description =>
10 | "A Kaiser-Bessel window strikes a balance among the various conflicting goals of amplitude " +
11 | "accuracy, side lobe distance, and side lobe height. It compares roughly to the BlackmanHarris window functions, " +
12 | "but for the same main lobe width, the near side lobes tend to be higher, but the further out side lobes are lower. " +
13 | "Choosing this window often reveals signals close to the noise floor";
14 |
15 | public override bool IsSymmetric => true;
16 |
17 | public Kaiser()
18 | {
19 | Beta = 15;
20 | }
21 |
22 | public Kaiser(double beta)
23 | {
24 | Beta = beta;
25 | }
26 |
27 | public override double[] Create(int size, bool normalize = false)
28 | {
29 | // derived from python/numpy:
30 | // https://github.com/numpy/numpy/blob/v1.21.0/numpy/lib/function_base.py#L3267-L3392
31 |
32 | int M = size;
33 | double alpha = (M - 1) / 2.0;
34 |
35 | double[] window = new double[size];
36 |
37 | for (int n = 0; n < size; n++)
38 | window[n] = I0(Beta * Math.Sqrt(1 - Math.Pow((n - alpha) / alpha, 2))) / I0(Beta);
39 |
40 | if (normalize)
41 | NormalizeInPlace(window);
42 |
43 | return window;
44 | }
45 |
46 | public static double I0(double x)
47 | {
48 | // Derived from code workby oygx210/navguide:
49 | // https://github.com/oygx210/navguide/blob/master/src/common/bessel.c
50 |
51 | double ax = Math.Abs(x);
52 | if (ax < 3.75)
53 | {
54 | double y = Math.Pow(x / 3.75, 2);
55 | double[] m = { 3.5156229, 3.0899424, 1.2067492, 0.2659732, 0.360768e-1, 0.45813e-2 };
56 | return 1.0 + y * (m[0] + y * (m[1] + y * (m[2] + y * (m[3] + y * (m[4] + y * m[5])))));
57 | }
58 | else
59 | {
60 | double y = 3.75 / ax;
61 | double[] m = { 0.39894228, 0.1328592e-1, 0.225319e-2, -0.157565e-2, 0.916281e-2, -0.2057706e-1, 0.2635537e-1, -0.1647633e-1, 0.392377e-2 };
62 | return (Math.Exp(ax) / Math.Sqrt(ax)) * (m[0] + y * (m[1] + y * (m[2] + y * (m[3] + y * (m[4] + y * (m[5] + y * (m[6] + y * (m[7] + y * m[8]))))))));
63 | }
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/src/FftSharp/Windows/Rectangular.cs:
--------------------------------------------------------------------------------
1 | namespace FftSharp.Windows;
2 |
3 | public class Rectangular : Window, IWindow
4 | {
5 | public override string Name => "Rectangular";
6 | public override string Description =>
7 | "The rectangular window (sometimes known as the boxcar or Dirichlet window) is the simplest window, " +
8 | "equivalent to replacing all but N values of a data sequence by zeros, making it appear as though " +
9 | "the waveform suddenly turns on and off. This window preserves transients at the start and end of the signal.";
10 |
11 | public override bool IsSymmetric => true;
12 |
13 | public override double[] Create(int size, bool normalize = false)
14 | {
15 | double[] window = new double[size];
16 |
17 | for (int i = 0; i < size; i++)
18 | window[i] = 1;
19 |
20 | if (normalize)
21 | NormalizeInPlace(window);
22 |
23 | return window;
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/FftSharp/Windows/Tukey.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Text;
4 |
5 | namespace FftSharp.Windows;
6 |
7 | public class Tukey : Window, IWindow
8 | {
9 | private readonly double Alpha;
10 |
11 | public override string Name => "Tukey";
12 | public override string Description =>
13 | "A Tukey window has a flat center and tapers at the edges according to a cosine function. " +
14 | "The amount of taper is defined by alpha (with low values being less taper). " +
15 | "Tukey windows are ideal for analyzing transient data since the amplitude of transient signal " +
16 | "in the time domain is less likely to be altered compared to using Hanning or flat top.";
17 |
18 | public override bool IsSymmetric => true;
19 |
20 | public Tukey()
21 | {
22 | Alpha = .5;
23 | }
24 |
25 | public Tukey(double alpha = .5)
26 | {
27 | Alpha = alpha;
28 | }
29 |
30 | public override double[] Create(int size, bool normalize = false)
31 | {
32 | double[] window = new double[size];
33 |
34 | double m = 2 * Math.PI / (Alpha * (size - 1));
35 |
36 | int edgeSizePoints = (int)(size * Alpha / 2);
37 |
38 | if (size % 2 == 0)
39 | edgeSizePoints += 1;
40 |
41 | for (int i = 0; i < size; i++)
42 | {
43 | if (i < edgeSizePoints)
44 | {
45 | // left edge
46 | window[i] = (1 - Math.Cos(i * m)) / 2;
47 | }
48 | else if (i >= size - edgeSizePoints)
49 | {
50 | // right edge
51 | window[i] = (1 - Math.Cos(i * m)) / 2;
52 | }
53 | else
54 | {
55 | window[i] = 1;
56 | }
57 | }
58 |
59 | if (normalize)
60 | NormalizeInPlace(window);
61 |
62 | return window;
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/src/FftSharp/Windows/Welch.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Text;
4 |
5 | namespace FftSharp.Windows;
6 |
7 | public class Welch : Window, IWindow
8 | {
9 | public override string Name => "Welch";
10 | public override string Description =>
11 | "The Welch window is typically used for antialiasing and resampling. " +
12 | "Its frequency response is better than that of the Bartlett windowed cosc function below pi, " +
13 | "but it shows again a rather distinctive bump above.";
14 |
15 | public override bool IsSymmetric => true;
16 |
17 | public override double[] Create(int size, bool normalize = false)
18 | {
19 | double[] window = new double[size];
20 |
21 | double halfN = (size - 1) / 2.0;
22 |
23 | for (int i = 0; i < size; i++)
24 | {
25 | double b = (i - halfN) / halfN;
26 | window[i] = 1 - b * b;
27 | }
28 |
29 | if (normalize)
30 | NormalizeInPlace(window);
31 |
32 | return window;
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/src/FftSharp/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/swharden/FftSharp/3f5158f7ab146c8fb651028e8dce67407b3ded81/src/FftSharp/icon.png
--------------------------------------------------------------------------------
/src/autoformat.bat:
--------------------------------------------------------------------------------
1 | dotnet format
--------------------------------------------------------------------------------