├── .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 | 22 | 24 | 43 | 48 | 49 | 51 | 52 | 54 | image/svg+xml 55 | 57 | 58 | 59 | 60 | 61 | 66 | 74 | FFT 85 | 91 | sharp 102 | 103 | 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 --------------------------------------------------------------------------------