├── public └── CNAME ├── .npmrc ├── lib ├── index.js ├── kissfftmodified │ ├── README.md │ ├── COPYING │ ├── webfftWrapper.js │ ├── index.html │ └── kiss_fft.h ├── wasmfft │ ├── README.md │ ├── fft.wasm │ ├── LICENSE │ ├── wasmfftWrapper.js │ └── fft.js ├── screenshot.png ├── kissfftviljaNOTFINISHED │ ├── README.md │ ├── example.js │ └── webfftWrapper.js ├── index.html ├── cross │ ├── Cross.h │ ├── webfftWrapper.js │ ├── FFT.js │ └── Cross.c ├── kissfft │ ├── README.md │ ├── COPYING │ ├── webfftWrapper.js │ ├── index.html │ └── kiss_fft.h ├── indutny │ ├── webfftWrapper.js │ └── LICENSE ├── nockert │ ├── webfftWrapper.js │ └── LICENSE ├── dntj │ ├── webfftWrapper.js │ ├── LICENSE │ ├── complex_array.js │ └── fft.js ├── vailNOTFINISHED │ ├── webfftWrapper.js │ ├── complex.cjs │ ├── fftutil.cjs │ ├── fft.js │ └── twiddle.cjs ├── mljs │ ├── webfftWrapper.js │ └── fftlib.js ├── nayukic │ ├── webfftWrapper.js │ ├── FFT.js │ ├── fft.h │ └── ffttest.c ├── nayuki │ ├── webfftWrapper.js │ └── fft.js ├── indutnymodified │ ├── LICENSE │ └── fft.js ├── utils │ ├── sortPerformance.js │ └── checkCapabilities.js ├── @types │ └── webfft │ │ └── index.d.ts ├── Makefile └── benchmark.js ├── site ├── src │ ├── vite-env.d.ts │ ├── types │ │ └── types.tsx │ ├── main.tsx │ ├── utils │ │ └── webworker.tsx │ ├── components │ │ ├── Button.tsx │ │ ├── NotFound.tsx │ │ ├── SiteHeader.tsx │ │ ├── CodeBlock.tsx │ │ ├── Home.tsx │ │ ├── Breadcrumbs.tsx │ │ ├── FFTSizeInputButton.tsx │ │ ├── MarkdownComponents.tsx │ │ ├── Docs.tsx │ │ ├── LinksSection.tsx │ │ ├── ResultsSection.tsx │ │ ├── About.tsx │ │ ├── InteractiveSignal.tsx │ │ └── BenchmarkSection.tsx │ ├── App.tsx │ ├── docs │ │ ├── GettingStarted.mdx │ │ └── ListofLibs.mdx │ └── index.css ├── public │ ├── robots.txt │ ├── assets │ │ ├── wavelet.png │ │ ├── __MACOSX │ │ │ ├── ._github-mark │ │ │ └── github-mark │ │ │ │ ├── ._github-mark.png │ │ │ │ ├── ._github-mark.svg │ │ │ │ ├── ._github-mark-white.png │ │ │ │ └── ._github-mark-white.svg │ │ ├── favicons │ │ │ ├── favicon.ico │ │ │ ├── favicon-16x16.png │ │ │ ├── favicon-32x32.png │ │ │ ├── mstile-150x150.png │ │ │ ├── apple-touch-icon.png │ │ │ ├── android-chrome-192x192.png │ │ │ ├── android-chrome-512x512.png │ │ │ ├── browserconfig.xml │ │ │ └── site.webmanifest │ │ ├── github-mark │ │ │ ├── github-mark.png │ │ │ ├── github-mark-white.png │ │ │ ├── github-mark-white.svg │ │ │ └── github-mark.svg │ │ ├── npm-logo-red.svg │ │ └── react.svg │ └── sitemap.xml ├── postcss.config.js ├── tsconfig.node.json ├── .gitignore ├── .eslintrc.cjs ├── vite.config.ts ├── tailwind.config.js ├── tsconfig.json ├── package.json ├── README.md └── index.html ├── .prettierrc ├── .eslintignore ├── SECURITY.md ├── .npmignore ├── tests ├── vitest.config.ts ├── profile.test.js ├── fft2d.test.js └── fft.test.js ├── .dockerignore ├── .eslintrc.json ├── examples ├── fft2d.js ├── basicUsage.js └── profile.js ├── .vscode └── settings.json ├── webpack.config.cjs ├── LICENSE ├── package.json ├── default.conf ├── .gitignore └── README.md /public/CNAME: -------------------------------------------------------------------------------- 1 | webfft.com -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | registry=https://registry.npmjs.org/ 2 | -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | import webfft from "./main.js"; 2 | -------------------------------------------------------------------------------- /lib/kissfftmodified/README.md: -------------------------------------------------------------------------------- 1 | See ../kissfft/README.md -------------------------------------------------------------------------------- /site/src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "doubleQuote": true, 3 | "quoteProps": "preserve" 4 | } -------------------------------------------------------------------------------- /lib/wasmfft/README.md: -------------------------------------------------------------------------------- 1 | # WasmFFT 2 | 3 | From https://github.com/Laksen/WebFFT 4 | -------------------------------------------------------------------------------- /lib/screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IQEngine/WebFFT/HEAD/lib/screenshot.png -------------------------------------------------------------------------------- /lib/wasmfft/fft.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IQEngine/WebFFT/HEAD/lib/wasmfft/fft.wasm -------------------------------------------------------------------------------- /site/public/robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | Allow: / 3 | 4 | Sitemap: https://webfft.com/sitemap.xml 5 | -------------------------------------------------------------------------------- /site/public/assets/wavelet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IQEngine/WebFFT/HEAD/site/public/assets/wavelet.png -------------------------------------------------------------------------------- /site/postcss.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | }; 7 | -------------------------------------------------------------------------------- /site/public/assets/__MACOSX/._github-mark: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IQEngine/WebFFT/HEAD/site/public/assets/__MACOSX/._github-mark -------------------------------------------------------------------------------- /site/public/assets/favicons/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IQEngine/WebFFT/HEAD/site/public/assets/favicons/favicon.ico -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | lib/cross/Cross.js 2 | lib/dist/bundle.js 3 | lib/kissfft/KissFFT.js 4 | lib/nayukic/NayukiCFFT.js 5 | lib/nockert/complex.js -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | ## Reporting a Vulnerability 4 | 5 | Please report any security vulnerabilities to IQEngine@vt.edu 6 | -------------------------------------------------------------------------------- /site/public/assets/favicons/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IQEngine/WebFFT/HEAD/site/public/assets/favicons/favicon-16x16.png -------------------------------------------------------------------------------- /site/public/assets/favicons/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IQEngine/WebFFT/HEAD/site/public/assets/favicons/favicon-32x32.png -------------------------------------------------------------------------------- /site/public/assets/favicons/mstile-150x150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IQEngine/WebFFT/HEAD/site/public/assets/favicons/mstile-150x150.png -------------------------------------------------------------------------------- /site/public/assets/github-mark/github-mark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IQEngine/WebFFT/HEAD/site/public/assets/github-mark/github-mark.png -------------------------------------------------------------------------------- /site/public/assets/favicons/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IQEngine/WebFFT/HEAD/site/public/assets/favicons/apple-touch-icon.png -------------------------------------------------------------------------------- /site/src/types/types.tsx: -------------------------------------------------------------------------------- 1 | export type BrowserInfoType = { 2 | browserName: string; 3 | version: string | null; 4 | os: string | null; 5 | }; 6 | -------------------------------------------------------------------------------- /site/public/assets/github-mark/github-mark-white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IQEngine/WebFFT/HEAD/site/public/assets/github-mark/github-mark-white.png -------------------------------------------------------------------------------- /site/public/assets/favicons/android-chrome-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IQEngine/WebFFT/HEAD/site/public/assets/favicons/android-chrome-192x192.png -------------------------------------------------------------------------------- /site/public/assets/favicons/android-chrome-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IQEngine/WebFFT/HEAD/site/public/assets/favicons/android-chrome-512x512.png -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | /site 2 | Dockerfile 3 | default.conf 4 | .prettierrc 5 | .eslintrc.json 6 | .eslintignore 7 | /.vscode 8 | .dockerignore 9 | /coverage 10 | /.github -------------------------------------------------------------------------------- /site/public/assets/__MACOSX/github-mark/._github-mark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IQEngine/WebFFT/HEAD/site/public/assets/__MACOSX/github-mark/._github-mark.png -------------------------------------------------------------------------------- /site/public/assets/__MACOSX/github-mark/._github-mark.svg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IQEngine/WebFFT/HEAD/site/public/assets/__MACOSX/github-mark/._github-mark.svg -------------------------------------------------------------------------------- /site/public/assets/__MACOSX/github-mark/._github-mark-white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IQEngine/WebFFT/HEAD/site/public/assets/__MACOSX/github-mark/._github-mark-white.png -------------------------------------------------------------------------------- /site/public/assets/__MACOSX/github-mark/._github-mark-white.svg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IQEngine/WebFFT/HEAD/site/public/assets/__MACOSX/github-mark/._github-mark-white.svg -------------------------------------------------------------------------------- /lib/kissfftviljaNOTFINISHED/README.md: -------------------------------------------------------------------------------- 1 | code lives here https://github.com/iVilja/kissfft-wasm/tree/main 2 | 3 | because it uses kissfft as a dep we went ahead and just imported it as a dep for now -------------------------------------------------------------------------------- /site/tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "composite": true, 4 | "skipLibCheck": true, 5 | "module": "ESNext", 6 | "moduleResolution": "bundler", 7 | "allowSyntheticDefaultImports": true 8 | }, 9 | "include": ["vite.config.ts"] 10 | } 11 | -------------------------------------------------------------------------------- /lib/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | -------------------------------------------------------------------------------- /site/src/main.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom/client"; 3 | import App from "./App.tsx"; 4 | import "./index.css"; 5 | 6 | ReactDOM.createRoot(document.getElementById("root")!).render( 7 | 8 | 9 | 10 | ); 11 | -------------------------------------------------------------------------------- /tests/vitest.config.ts: -------------------------------------------------------------------------------- 1 | /// 2 | import { defineConfig } from "vite"; 3 | 4 | export default defineConfig({ 5 | test: { 6 | coverage: { 7 | provider: "v8", 8 | }, 9 | environment: "jsdom", // to be able to unit test localStorage 10 | }, 11 | }); 12 | -------------------------------------------------------------------------------- /lib/cross/Cross.h: -------------------------------------------------------------------------------- 1 | 2 | #ifndef CROSS_H 3 | #define CROSS_H 4 | 5 | #ifdef __cplusplus 6 | extern "C" { 7 | #endif 8 | 9 | extern void fftCross(unsigned int n, int inverse, 10 | const double *ri, const double *ii, 11 | double *ro, double *io); 12 | 13 | #ifdef __cplusplus 14 | } 15 | #endif 16 | 17 | #endif 18 | -------------------------------------------------------------------------------- /site/public/assets/favicons/browserconfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | #da532c 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /lib/kissfftviljaNOTFINISHED/example.js: -------------------------------------------------------------------------------- 1 | import { fft } from "kissfft-wasm"; 2 | 3 | const size = 1024; 4 | 5 | // Create input array 6 | const ci = new Float32Array(2 * size); 7 | for (let j = 0; j < size; j++) { 8 | ci[2 * j] = Math.random() - 0.5; 9 | ci[2 * j + 1] = Math.random() - 0.5; 10 | } 11 | 12 | const co = fft(ci); 13 | 14 | console.log(co); 15 | -------------------------------------------------------------------------------- /lib/kissfft/README.md: -------------------------------------------------------------------------------- 1 | # Setting up emscripten toolchain 2 | 3 | ```bash 4 | git clone https://github.com/emscripten-core/emsdk.git 5 | cd emsdk 6 | ./emsdk install latest 7 | ./emsdk activate latest 8 | source ./emsdk_env.sh 9 | ``` 10 | 11 | ## Building the module 12 | 13 | ```bash 14 | cd emsdk 15 | source ./emsdk_env.sh 16 | cd to this dir 17 | make 18 | ``` 19 | -------------------------------------------------------------------------------- /site/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | -------------------------------------------------------------------------------- /lib/kissfftviljaNOTFINISHED/webfftWrapper.js: -------------------------------------------------------------------------------- 1 | import { fft } from "kissfft-wasm"; 2 | 3 | class ViljaFftWrapperWasm { 4 | constructor(size) { 5 | // vilja doesnt have a constructor 6 | this.outputArr = new Float32Array(2 * size); 7 | } 8 | 9 | fft(inputArr) { 10 | return fft(inputArr); 11 | } 12 | } 13 | 14 | export default ViljaFftWrapperWasm; 15 | -------------------------------------------------------------------------------- /site/src/utils/webworker.tsx: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | import webfft, { ProfileResult } from "webfft"; 4 | 5 | onmessage = (e: MessageEvent<[number, number]>) => { 6 | const [fftSize, duration] = e.data; 7 | const fft = new webfft(fftSize); 8 | const profileObj: ProfileResult = fft.profile(duration); 9 | fft.dispose(); 10 | 11 | self.postMessage(profileObj); 12 | }; 13 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | # Node modules 2 | node_modules 3 | npm-debug.log 4 | 5 | # Git directory and files 6 | .git 7 | .gitignore 8 | 9 | # Environment files 10 | .env 11 | 12 | # Build directory 13 | dist 14 | 15 | # OS generated files 16 | .DS_Store 17 | Thumbs.db 18 | 19 | # Editor directories and files 20 | .idea 21 | *.swp 22 | *.swo 23 | *.swl 24 | *.swm 25 | *.swn 26 | *.swk 27 | *.swp 28 | .vscode/ 29 | *.sublime* 30 | -------------------------------------------------------------------------------- /lib/indutny/webfftWrapper.js: -------------------------------------------------------------------------------- 1 | import FFT_indutny from "./fft.js"; 2 | 3 | class IndutnyFftWrapperJavascript { 4 | constructor(size) { 5 | this.size = size; 6 | this.indutnyFft = new FFT_indutny(size); 7 | } 8 | 9 | fft(inputArr) { 10 | const outputArr = new Float32Array(2 * this.size); 11 | this.indutnyFft.transform(outputArr, inputArr); 12 | return outputArr; 13 | } 14 | } 15 | 16 | export default IndutnyFftWrapperJavascript; 17 | -------------------------------------------------------------------------------- /lib/nockert/webfftWrapper.js: -------------------------------------------------------------------------------- 1 | import FFT from "./complex.js"; 2 | 3 | class NockertFftWrapperJavascript { 4 | constructor(size) { 5 | this.size = size; 6 | this.nockertfft = new FFT.complex(size, false); // 2nd arg is for inverse 7 | } 8 | 9 | fft(inputArr) { 10 | const outputArr = new Float32Array(2 * this.size); 11 | this.nockertfft.simple(outputArr, inputArr, "complex"); 12 | return outputArr; 13 | } 14 | } 15 | 16 | export default NockertFftWrapperJavascript; 17 | -------------------------------------------------------------------------------- /site/.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { browser: true, es2020: true }, 4 | extends: [ 5 | 'eslint:recommended', 6 | 'plugin:@typescript-eslint/recommended', 7 | 'plugin:react-hooks/recommended', 8 | ], 9 | ignorePatterns: ['dist', '.eslintrc.cjs'], 10 | parser: '@typescript-eslint/parser', 11 | plugins: ['react-refresh'], 12 | rules: { 13 | 'react-refresh/only-export-components': [ 14 | 'warn', 15 | { allowConstantExport: true }, 16 | ], 17 | }, 18 | } 19 | -------------------------------------------------------------------------------- /site/public/assets/npm-logo-red.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 7 | 9 | 10 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": true, 4 | "es2021": true 5 | }, 6 | "extends": [ 7 | "eslint:recommended", 8 | "plugin:@typescript-eslint/recommended", 9 | "plugin:react/recommended" 10 | ], 11 | "parser": "@typescript-eslint/parser", 12 | "parserOptions": { 13 | "ecmaVersion": "latest", 14 | "sourceType": "module" 15 | }, 16 | "plugins": [ 17 | "@typescript-eslint", 18 | "react" 19 | ], 20 | "rules": { 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /examples/fft2d.js: -------------------------------------------------------------------------------- 1 | import webfft from "../lib/main.js"; 2 | //import webfft from "webfft"; 3 | //const webfft = require("webfft"); 4 | 5 | const fftsize = 1024; 6 | const outterSize = 128; 7 | const fft = new webfft(fftsize); 8 | let inputArr = []; 9 | for (let j = 0; j < outterSize; j++) { 10 | const subArray = new Float32Array(fftsize * 2); 11 | for (let i = 0; i < fftsize * 2; i++) { 12 | subArray[i] = i * j * 1.12312312; // Arbitrary 13 | } 14 | inputArr.push(subArray); 15 | } 16 | const out = fft.fft2d(inputArr); 17 | 18 | fft.dispose(); 19 | -------------------------------------------------------------------------------- /site/public/assets/favicons/site.webmanifest: -------------------------------------------------------------------------------- 1 | { 2 | "name": "WebFFT", 3 | "short_name": "", 4 | "icons": [ 5 | { 6 | "src": "/assets/favicons/android-chrome-192x192.png", 7 | "sizes": "192x192", 8 | "type": "image/png" 9 | }, 10 | { 11 | "src": "/assets/favicons/android-chrome-512x512.png", 12 | "sizes": "512x512", 13 | "type": "image/png" 14 | } 15 | ], 16 | "theme_color": "#333333", 17 | "background_color": "#333333", 18 | "display": "standalone" 19 | } -------------------------------------------------------------------------------- /site/src/components/Button.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | interface ButtonProps extends React.ButtonHTMLAttributes { 4 | children: React.ReactNode; 5 | } 6 | 7 | const Button: React.FC = ({ children, onClick, ...props }) => { 8 | const handleClick = (e: React.MouseEvent) => { 9 | if (onClick) { 10 | onClick(e); 11 | } 12 | e.currentTarget.blur(); 13 | }; 14 | 15 | return ( 16 | 19 | ); 20 | }; 21 | 22 | export default Button; 23 | -------------------------------------------------------------------------------- /site/src/components/NotFound.tsx: -------------------------------------------------------------------------------- 1 | import SiteHeader from "./SiteHeader"; 2 | 3 | function NotFound() { 4 | return ( 5 |
6 | 7 |
8 |

12 | 404
13 | Not Found 14 |

15 |
16 |
17 | ); 18 | } 19 | 20 | export default NotFound; 21 | -------------------------------------------------------------------------------- /site/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "vite"; 2 | import react from "@vitejs/plugin-react"; 3 | import mdx from "@mdx-js/rollup"; 4 | 5 | export default defineConfig({ 6 | server: { 7 | port: 3000, 8 | host: "0.0.0.0", 9 | }, 10 | optimizeDeps: { 11 | include: ["react/jsx-runtime"], 12 | exclude: ["@mdx-js/react"], 13 | }, 14 | base: "/", // for custom domain set to / as per https://vitejs.dev/guide/static-deploy.html#github-pages 15 | plugins: [ 16 | { 17 | enforce: "pre", 18 | ...mdx(), 19 | }, 20 | react({ 21 | include: /\.(mdx|js|jsx|ts|tsx)$/, 22 | }), 23 | ], 24 | }); 25 | -------------------------------------------------------------------------------- /examples/basicUsage.js: -------------------------------------------------------------------------------- 1 | import webfft from "../lib/main.js"; 2 | //import webfft from "webfft"; 3 | //const webfft = require("webfft"); 4 | 5 | // Complex-valued input 6 | const fftsize = 1024; 7 | const fft = new webfft(fftsize); 8 | const inputArr = new Float32Array(fftsize * 2); 9 | for (let i = 0; i < fftsize * 2; i++) { 10 | inputArr[i] = i * 1.12312312; // Arbitrary 11 | } 12 | const outputArr = fft.fft(inputArr); 13 | console.log(outputArr); 14 | 15 | // Real-valued input 16 | const inputArrReal = new Float32Array(fftsize); 17 | for (let i = 0; i < fftsize; i++) { 18 | inputArrReal[i] = i * 1.12312312; // Arbitrary 19 | } 20 | const outputArrReal = fft.fftr(inputArrReal); 21 | console.log(outputArrReal); 22 | 23 | fft.dispose(); 24 | -------------------------------------------------------------------------------- /examples/profile.js: -------------------------------------------------------------------------------- 1 | import webfft from "../lib/main.js"; 2 | //import webfft from "webfft"; 3 | //const webfft = require("webfft"); 4 | 5 | const fftsize = 1024; 6 | const fft = new webfft(fftsize); 7 | const profileObj = fft.profile(2); // duration to run profile, in seconds 8 | fft.dispose(); 9 | console.log(profileObj); 10 | console.log("Fastest sub-library:", profileObj.fastestSubLibrary); 11 | 12 | // get it from local storage 13 | if (typeof localStorage !== "undefined") { 14 | const profileObjLocal = JSON.parse(localStorage.getItem("webfftProfile")); 15 | console.log(profileObjLocal); 16 | console.log("Fastest sub-library:", profileObjLocal.fastestSubLibrary); 17 | } 18 | 19 | // it will automatically set future .fft() calls to use whichever was fastest 20 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "[typescript]": { 3 | "editor.defaultFormatter": "esbenp.prettier-vscode", 4 | "editor.formatOnSave": true 5 | }, 6 | "[typescriptreact]": { 7 | "editor.defaultFormatter": "esbenp.prettier-vscode", 8 | "editor.formatOnSave": true 9 | }, 10 | "[javascript]": { 11 | "editor.defaultFormatter": "esbenp.prettier-vscode", 12 | "editor.formatOnSave": true 13 | }, 14 | "[javascriptreact]": { 15 | "editor.formatOnSave": true, 16 | "editor.defaultFormatter": "esbenp.prettier-vscode" 17 | }, 18 | "[css]": { 19 | "editor.formatOnSave": true, 20 | "editor.defaultFormatter": "esbenp.prettier-vscode" 21 | }, 22 | "[markdown]": { 23 | "editor.formatOnSave": false 24 | }, 25 | "prettier.printWidth": 120 26 | } 27 | -------------------------------------------------------------------------------- /site/src/components/SiteHeader.tsx: -------------------------------------------------------------------------------- 1 | import { Link } from "react-router-dom"; 2 | 3 | function SiteHeader() { 4 | return ( 5 |
6 |
7 |

8 | 14 | WebFFT 15 | 16 |

17 |

18 | Optimized, Intelligent, and Ultra-Fast. 19 |
That's WebFFT Meta-Library for You. 20 |

21 |
22 |
23 | ); 24 | } 25 | 26 | export default SiteHeader; 27 | -------------------------------------------------------------------------------- /site/src/App.tsx: -------------------------------------------------------------------------------- 1 | import { HashRouter, Routes, Route } from "react-router-dom"; 2 | import Home from "./components/Home"; 3 | import About from "./components/About"; 4 | import Docs from "./components/Docs"; 5 | import Breadcrumbs from "./components/Breadcrumbs"; 6 | import NotFound from "./components/NotFound"; 7 | 8 | function App() { 9 | return ( 10 | 11 | 12 | 13 | } errorElement={} /> 14 | } errorElement={} /> 15 | } errorElement={} /> 16 | } /> // TODO: Make a 404 page 17 | 18 | 19 | ); 20 | } 21 | 22 | export default App; 23 | -------------------------------------------------------------------------------- /lib/cross/webfftWrapper.js: -------------------------------------------------------------------------------- 1 | import FFTCross from "./FFT.js"; 2 | 3 | class CrossFftWrapperWasm { 4 | constructor(size) { 5 | this.size = size; 6 | this.fftcross = new FFTCross(size); 7 | this.real = new Float64Array(this.size); 8 | this.imag = new Float64Array(this.size); 9 | } 10 | 11 | fft(inputArr) { 12 | for (var i = 0; i < this.size; i++) { 13 | this.real[i] = inputArr[2 * i]; 14 | this.imag[i] = inputArr[2 * i + 1]; 15 | } 16 | const out = this.fftcross.transform(this.real, this.imag, false); 17 | const outputArr = new Float32Array(2 * this.size); 18 | for (var i = 0; i < this.size; i++) { 19 | outputArr[2 * i] = out.real[i]; 20 | outputArr[2 * i + 1] = out.imag[i]; 21 | } 22 | return outputArr; 23 | } 24 | } 25 | 26 | export default CrossFftWrapperWasm; 27 | -------------------------------------------------------------------------------- /lib/dntj/webfftWrapper.js: -------------------------------------------------------------------------------- 1 | import { FFT } from "./fft.js"; 2 | import { ComplexArray } from "./complex_array.js"; 3 | 4 | class DntjWebFftWrapperJavascript { 5 | constructor(size) { 6 | this.size = size; 7 | this.cin = new ComplexArray(size); 8 | this.scale = Math.sqrt(size); 9 | } 10 | 11 | fft(inputArr) { 12 | for (var i = 0; i < this.size; ++i) { 13 | this.cin.real[i] = inputArr[i * 2]; 14 | this.cin.imag[i] = inputArr[i * 2 + 1]; 15 | } 16 | const co = this.cin.FFT(); 17 | const outputArr = new Float32Array(2 * this.size); 18 | for (var i = 0; i < this.size; ++i) { 19 | outputArr[i * 2] = co.real[i] * this.scale; 20 | outputArr[i * 2 + 1] = co.imag[i] * this.scale; 21 | } 22 | return outputArr; 23 | } 24 | } 25 | 26 | export default DntjWebFftWrapperJavascript; 27 | -------------------------------------------------------------------------------- /site/tailwind.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import("tailwindcss").Config} */ 2 | module.exports = { 3 | mode: "jit", 4 | content: ["./src/**/*.{js,ts,jsx,tsx}"], 5 | darkMode: "media", 6 | theme: { 7 | extend: { 8 | fontFamily: { 9 | cyber: ["Orbitron", "system-ui", "sans-serif"], 10 | system: ["Segoe UI", "system-ui", "sans-serif"], 11 | }, 12 | colors: { 13 | "cyber-background1": "hsl(0, 0%, 5%)", 14 | "cyber-background2": "hsl(0, 0%, 11%)", 15 | "cyber-primary": "hsla(320, 80%, 50%, 0.8)", 16 | "cyber-secondary": "hsl(200, 100%, 50%, 0.75)", 17 | "cyber-accent": "hsla(100, 60%, 40%, 0.7)", 18 | "cyber-text": "hsla(0, 0%, 80%, 0.9)", 19 | "cyber-text-secondary": "hsla(50, 100%, 60%, 0.75)", 20 | }, 21 | }, 22 | }, 23 | plugins: [require("@tailwindcss/typography")], 24 | }; 25 | -------------------------------------------------------------------------------- /site/src/components/CodeBlock.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Light as SyntaxHighlighter } from "react-syntax-highlighter"; 3 | import { docco } from "react-syntax-highlighter/dist/esm/styles/hljs"; 4 | 5 | interface CodeBlockProps { 6 | language: string; 7 | children: string | string[]; 8 | } 9 | 10 | const CodeBlock: React.FC = ({ language, children }) => { 11 | let content = ""; 12 | 13 | if (typeof children === "string") { 14 | content = children; 15 | } else if (Array.isArray(children)) { 16 | content = children.join("\n"); 17 | } else if (React.isValidElement(children)) { 18 | content = "React Element, please provide a string"; 19 | } 20 | return ( 21 | 22 | {content} 23 | 24 | ); 25 | }; 26 | 27 | export default CodeBlock; 28 | -------------------------------------------------------------------------------- /site/public/sitemap.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | https://webfft.com/ 5 | 2023-09-12 6 | weekly 7 | 1.0 8 | 9 | 10 | https://webfft.com/Docs 11 | 2023-09-12 12 | weekly 13 | 0.9 14 | 15 | 16 | https://webfft.com/Docs/listoflibs 17 | 2023-09-12 18 | monthly 19 | 0.8 20 | 21 | 22 | https://webfft.com/About 23 | 2023-09-12 24 | monthly 25 | 0.7 26 | 27 | 28 | -------------------------------------------------------------------------------- /lib/vailNOTFINISHED/webfftWrapper.js: -------------------------------------------------------------------------------- 1 | import FFT_vail from "./fft.js"; 2 | 3 | class VailFftWrapperJavascript { 4 | constructor(size) { 5 | // Vail doesnt have any constructor on its own, it figures out the size based on input 6 | } 7 | 8 | fft(inputArr) { 9 | this.outputArr = new Float32Array(inputArr.length); 10 | /* 11 | const input_real = new Float32Array(inputArr.length); 12 | const input_imag = new Float32Array(inputArr.length); 13 | for (let i = 0; i < inputArr.length; i++) { 14 | input_real[i] = inputArr[2 * i]; 15 | input_imag[i] = inputArr[2 * i + 1]; 16 | } 17 | const vail_in = [[input_real, input_imag]]; 18 | const outputArr = FFT_vail.fft(vail_in); 19 | */ 20 | const outputArr = FFT_vail.fft(inputArr); 21 | //console.log(outputArr); 22 | return outputArr; 23 | } 24 | } 25 | 26 | export default VailFftWrapperJavascript; 27 | -------------------------------------------------------------------------------- /lib/mljs/webfftWrapper.js: -------------------------------------------------------------------------------- 1 | import FFT from "./fftlib.js"; 2 | 3 | class MljsWebFftWrapperJavascript { 4 | constructor(size) { 5 | this.size = size; 6 | this.FFT_mljs = FFT; 7 | this.FFT_mljs.init(size); 8 | } 9 | 10 | fft(inputArr) { 11 | const input_real = new Float32Array(this.size); 12 | const input_imag = new Float32Array(this.size); 13 | const outputArr = new Float32Array(2 * this.size); 14 | 15 | for (var i = 0; i < this.size; ++i) { 16 | input_real[i] = inputArr[i * 2]; 17 | input_imag[i] = inputArr[i * 2 + 1]; 18 | } 19 | 20 | this.FFT_mljs.fft(input_real, input_imag); // performs fft in-place 21 | 22 | for (var i = 0; i < this.size; ++i) { 23 | outputArr[i * 2] = input_real[i]; 24 | outputArr[i * 2 + 1] = input_imag[i]; 25 | } 26 | return outputArr; 27 | } 28 | } 29 | 30 | export default MljsWebFftWrapperJavascript; 31 | -------------------------------------------------------------------------------- /lib/nayukic/webfftWrapper.js: -------------------------------------------------------------------------------- 1 | import FFTNayukiC from "./FFT.js"; 2 | 3 | class NayukiWasmFftWrapperWasm { 4 | constructor(size) { 5 | this.size = size; 6 | this.fftNayuki = new FFTNayukiC(size); 7 | } 8 | 9 | fft(inputArr) { 10 | // it uses the same buffer for input and output 11 | const real = new Float32Array(this.size); 12 | const imag = new Float32Array(this.size); 13 | const outputArr = new Float32Array(this.size * 2); 14 | 15 | for (var i = 0; i < this.size; ++i) { 16 | real[i] = inputArr[i * 2]; 17 | imag[i] = inputArr[i * 2 + 1]; 18 | } 19 | this.fftNayuki.forward(real, imag); // this does the FFT, it uses the same buffer for input and output 20 | for (var i = 0; i < this.size; ++i) { 21 | outputArr[i * 2] = real[i]; 22 | outputArr[i * 2 + 1] = imag[i]; 23 | } 24 | return outputArr; 25 | } 26 | } 27 | 28 | export default NayukiWasmFftWrapperWasm; 29 | -------------------------------------------------------------------------------- /site/public/assets/github-mark/github-mark-white.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /site/public/assets/github-mark/github-mark.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /site/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2020", 4 | "useDefineForClassFields": true, 5 | "lib": ["ES2020", "DOM", "DOM.Iterable"], 6 | "module": "ESNext", 7 | "skipLibCheck": true, 8 | "esModuleInterop": true, 9 | "allowJs": true, 10 | "typeRoots": ["./node_modules/@types", "../lib/@types"], 11 | 12 | /* Bundler mode */ 13 | "moduleResolution": "bundler", 14 | "allowImportingTsExtensions": true, 15 | "resolveJsonModule": true, 16 | "isolatedModules": true, 17 | "noEmit": true, 18 | "jsx": "react-jsx", 19 | "jsxImportSource": "react", 20 | 21 | /* Linting */ 22 | "strict": true, 23 | "noUnusedLocals": false, 24 | "noUnusedParameters": false, 25 | "noFallthroughCasesInSwitch": true, 26 | "paths": { 27 | "webfft": ["../lib/@types/webfft/index.d.ts"] 28 | } 29 | }, 30 | "include": ["src/**/*.ts", "src/**/*.tsx"], 31 | "references": [{ "path": "./tsconfig.node.json" }] 32 | } 33 | -------------------------------------------------------------------------------- /lib/nayuki/webfftWrapper.js: -------------------------------------------------------------------------------- 1 | import FFTNayuki from "./fft.js"; // this one has the sin/cos tables precomputed 2 | 3 | class NayukiFftWrapperJavascript { 4 | constructor(size) { 5 | this.size = size; 6 | this.fftNayuki = new FFTNayuki(size); 7 | } 8 | 9 | fft(inputArr) { 10 | // it uses the same buffer for input and output 11 | const real = new Float32Array(this.size); 12 | const imag = new Float32Array(this.size); 13 | const outputArr = new Float32Array(this.size * 2); 14 | 15 | for (var i = 0; i < this.size; ++i) { 16 | real[i] = inputArr[i * 2]; 17 | imag[i] = inputArr[i * 2 + 1]; 18 | } 19 | this.fftNayuki.forward(real, imag); // this does the FFT, it uses the same buffer for input and output 20 | for (var i = 0; i < this.size; ++i) { 21 | outputArr[i * 2] = real[i]; 22 | outputArr[i * 2 + 1] = imag[i]; 23 | } 24 | return outputArr; 25 | } 26 | } 27 | 28 | export default NayukiFftWrapperJavascript; 29 | -------------------------------------------------------------------------------- /webpack.config.cjs: -------------------------------------------------------------------------------- 1 | const path = require("path"); 2 | const TerserPlugin = require("terser-webpack-plugin"); 3 | const webpack = require("webpack"); 4 | 5 | module.exports = { 6 | entry: "./lib/index.js", 7 | mode: "production", 8 | output: { 9 | filename: "bundle.js", 10 | path: path.resolve(__dirname, "dist") 11 | }, 12 | resolve: { 13 | extensions: [".ts", ".tsx", ".js", ".jsx", ".cjs"], 14 | fallback: { 15 | path: require.resolve("path-browserify") 16 | } 17 | }, 18 | plugins: [ 19 | new webpack.optimize.LimitChunkCountPlugin({ 20 | maxChunks: 1 21 | }) 22 | ], 23 | module: { 24 | rules: [ 25 | { 26 | test: /\.tsx?$/, 27 | use: "ts-loader", // Use ts-loader for TypeScript files 28 | exclude: /node_modules/ 29 | } 30 | ] 31 | }, 32 | optimization: { 33 | minimizer: [ 34 | new TerserPlugin({ 35 | terserOptions: { 36 | keep_fnames: true 37 | } 38 | }) 39 | ] 40 | } 41 | }; 42 | -------------------------------------------------------------------------------- /site/src/components/Home.tsx: -------------------------------------------------------------------------------- 1 | import { useState } from "react"; 2 | import SiteHeader from "./SiteHeader"; 3 | import LinksSection from "./LinksSection"; 4 | import BenchmarkSection from "./BenchmarkSection"; 5 | import InteractiveSignal from "./InteractiveSignal"; 6 | 7 | function Home() { 8 | const [fftSize, setFftSize] = useState(1024); 9 | const [duration, setDuration] = useState(1); 10 | 11 | const handleClearState = (_: any) => { 12 | setFftSize(1024); 13 | setDuration(1); 14 | }; 15 | 16 | return ( 17 |
18 | 19 |
20 | 21 | 28 | 29 |
30 |
31 | ); 32 | } 33 | 34 | export default Home; 35 | -------------------------------------------------------------------------------- /lib/indutny/LICENSE: -------------------------------------------------------------------------------- 1 | This software is licensed under the MIT License. 2 | 3 | Copyright Fedor Indutny, 2017. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /lib/indutnymodified/LICENSE: -------------------------------------------------------------------------------- 1 | This software is licensed under the MIT License. 2 | 3 | Copyright Fedor Indutny, 2017. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 IQEngine 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 | -------------------------------------------------------------------------------- /lib/dntj/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright (c) 2012 Nick Jones 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 13 | all 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 21 | THE SOFTWARE. -------------------------------------------------------------------------------- /lib/utils/sortPerformance.js: -------------------------------------------------------------------------------- 1 | function sortDescending(data) { 2 | // Extract arrays from data object 3 | let xArray = data[0].x; 4 | let yArray = data[0].y; 5 | let barColors = data[0].marker.color; 6 | 7 | if (!yArray || yArray.length == 0) { 8 | return data; 9 | } 10 | 11 | // Create an array of objects from xArray, yArray, and barColors 12 | let combined = xArray.map((x, i) => ({ 13 | x: x, 14 | y: yArray[i], 15 | color: barColors[i], 16 | })); 17 | 18 | // Sort combined array by 'y' property in descending order 19 | combined.sort((a, b) => b.y - a.y); 20 | 21 | // Map sorted array back to original arrays 22 | const sortedXArray = combined.map((item) => item.x); 23 | const sortedYArray = combined.map((item) => item.y); 24 | const sortedBarColors = combined.map((item) => item.color); 25 | 26 | // Create a new data object with sorted arrays 27 | const sortedData = [ 28 | { 29 | x: sortedXArray, 30 | y: sortedYArray, 31 | type: "bar", 32 | marker: { color: sortedBarColors }, 33 | }, 34 | ]; 35 | 36 | return sortedData; 37 | } 38 | 39 | export default sortDescending; 40 | -------------------------------------------------------------------------------- /lib/wasmfft/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Jeppe Johansen - jeppe@j-software.dk 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 | -------------------------------------------------------------------------------- /site/src/components/Breadcrumbs.tsx: -------------------------------------------------------------------------------- 1 | import { useLocation, useNavigate } from "react-router-dom"; 2 | 3 | function Breadcrumbs() { 4 | const location = useLocation(); 5 | const navigate = useNavigate(); 6 | 7 | const pathnames = location.pathname.split("/").filter((x) => x); 8 | 9 | return ( 10 |
11 | {pathnames.length > 0 ? ( 12 | <> 13 |
navigate("/")} className="text-cyber-secondary cursor-pointer"> 14 | Home 15 |
16 | {pathnames.map((value, index) => { 17 | const to = `/${pathnames.slice(0, index + 1).join("/")}`; 18 | return ( 19 |
20 | {">"}{" "} 21 |
navigate(to)} className="inline text-cyber-secondary cursor-pointer"> 22 | {value.charAt(0).toUpperCase() + value.slice(1)} 23 |
24 |
25 | ); 26 | })} 27 | 28 | ) : null} 29 |
30 | ); 31 | } 32 | 33 | export default Breadcrumbs; 34 | -------------------------------------------------------------------------------- /lib/cross/FFT.js: -------------------------------------------------------------------------------- 1 | import CrossModule from "./Cross.mjs"; 2 | 3 | ("use strict"); 4 | 5 | var crossModule = CrossModule({}); 6 | 7 | var fftCross = crossModule.cwrap("fftCross", "void", [ 8 | "number", 9 | "number", 10 | "number", 11 | "number", 12 | "number", 13 | "number", 14 | ]); 15 | 16 | function FFTCross(size) { 17 | this.size = size; 18 | this.n = size * 8; 19 | this.ptr = crossModule._malloc(this.n * 4); 20 | this.ri = new Uint8Array(crossModule.HEAPU8.buffer, this.ptr, this.n); 21 | this.ii = new Uint8Array( 22 | crossModule.HEAPU8.buffer, 23 | this.ptr + this.n, 24 | this.n, 25 | ); 26 | 27 | this.transform = function (real, imag, inverse) { 28 | var ptr = this.ptr; 29 | var n = this.n; 30 | this.ri.set(new Uint8Array(real.buffer)); 31 | this.ii.set(new Uint8Array(imag.buffer)); 32 | fftCross(this.size, inverse, ptr, ptr + n, ptr + n * 2, ptr + n * 3); 33 | var ro = new Float64Array( 34 | crossModule.HEAPU8.buffer, 35 | ptr + n * 2, 36 | this.size, 37 | ); 38 | var io = new Float64Array( 39 | crossModule.HEAPU8.buffer, 40 | ptr + n * 3, 41 | this.size, 42 | ); 43 | return { real: ro, imag: io }; 44 | }; 45 | 46 | this.dispose = function () { 47 | crossModule._free(this.ptr); 48 | }; 49 | } 50 | 51 | export default FFTCross; 52 | -------------------------------------------------------------------------------- /lib/vailNOTFINISHED/complex.cjs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------- 2 | // Add two complex numbers 3 | //------------------------------------------------- 4 | var complexAdd = function (a, b) { 5 | return [a[0] + b[0], a[1] + b[1]]; 6 | }; 7 | 8 | //------------------------------------------------- 9 | // Subtract two complex numbers 10 | //------------------------------------------------- 11 | var complexSubtract = function (a, b) { 12 | return [a[0] - b[0], a[1] - b[1]]; 13 | }; 14 | 15 | //------------------------------------------------- 16 | // Multiply two complex numbers 17 | // 18 | // (a + bi) * (c + di) = (ac - bd) + (ad + bc)i 19 | //------------------------------------------------- 20 | var complexMultiply = function (a, b) { 21 | return [a[0] * b[0] - a[1] * b[1], a[0] * b[1] + a[1] * b[0]]; 22 | }; 23 | 24 | //------------------------------------------------- 25 | // Calculate |a + bi| 26 | // 27 | // sqrt(a*a + b*b) 28 | //------------------------------------------------- 29 | var complexMagnitude = function (c) { 30 | return Math.sqrt(c[0] * c[0] + c[1] * c[1]); 31 | }; 32 | 33 | //------------------------------------------------- 34 | // Exports 35 | //------------------------------------------------- 36 | module.exports = { 37 | add: complexAdd, 38 | subtract: complexSubtract, 39 | multiply: complexMultiply, 40 | magnitude: complexMagnitude 41 | }; 42 | -------------------------------------------------------------------------------- /lib/wasmfft/wasmfftWrapper.js: -------------------------------------------------------------------------------- 1 | import { getFFT, wasmBinary } from "./fft.js"; 2 | 3 | ("use strict"); 4 | 5 | function intArrayFromBase64(s) { 6 | try { 7 | var decoded = atob(s); 8 | var bytes = new Uint8Array(decoded.length); 9 | for (var i = 0; i < decoded.length; ++i) { 10 | bytes[i] = decoded.charCodeAt(i) 11 | } 12 | return bytes 13 | } catch (_) { 14 | throw new Error("Converting base64 string to bytes failed.") 15 | } 16 | } 17 | 18 | var module = new WebAssembly.Module(intArrayFromBase64(wasmBinary)); 19 | 20 | class WasmFftWrapperWasm { 21 | constructor(size) { 22 | this.size = size; 23 | 24 | this.instance = getFFT(module, size, "complex", "complex"); 25 | 26 | this.inptr = this.instance.getInputBuffer(); 27 | [this.outr, this.outi] = this.instance.getOutputBuffer(); 28 | } 29 | 30 | fft = function (inputArray) { 31 | this.inptr.set(inputArray); 32 | 33 | this.instance.run(); 34 | 35 | let outputArray = new Float32Array(this.size * 2); 36 | let [outr, outi] = [this.outr, this.outi]; 37 | for (let i=0; i; 8 | dispose(): void; 9 | } 10 | 11 | export interface ProfileResult { 12 | fftsPerSecond: number[]; 13 | subLibraries: string[]; 14 | totalElapsed: number; 15 | fastestSubLibrary: string; 16 | } 17 | 18 | export interface BrowserCapabilities { 19 | browserName: string; 20 | browserVersion: string; 21 | osName: string; 22 | osVersion: string; 23 | wasm: boolean; 24 | relaxedSimd: boolean; 25 | simd: boolean; 26 | } 27 | 28 | export interface WebfftWrapper { 29 | fft(inputArr: Float32Array): Float32Array; 30 | } 31 | 32 | export interface KissFftWrapperWasm extends WebfftWrapper {} 33 | export interface IndutnyFftWrapperJavascript extends WebfftWrapper {} 34 | export interface DntjWebFftWrapperJavascript extends WebfftWrapper {} 35 | export interface CrossFftWrapperWasm extends WebfftWrapper {} 36 | export interface NayukiFftWrapperJavascript extends WebfftWrapper {} 37 | export interface NayukiWasmFftWrapperWasm extends WebfftWrapper {} 38 | export interface NockertFftWrapperJavascript extends WebfftWrapper {} 39 | export interface WasmFftWrapperWasm extends WebfftWrapper {} -------------------------------------------------------------------------------- /lib/nockert/LICENSE: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2012, Jens Nockert , Jussi Kalliokoski 2 | * All rights reserved. 3 | * 4 | * Redistribution and use in source and binary forms, with or without 5 | * modification, are permitted provided that the following conditions are met: 6 | * 7 | * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 8 | * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 9 | * 10 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 11 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 12 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 13 | * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 14 | * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 15 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 16 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 17 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 18 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 19 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 20 | */ -------------------------------------------------------------------------------- /lib/kissfft/COPYING: -------------------------------------------------------------------------------- 1 | Copyright (c) 2003-2010 Mark Borgerding 2 | 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 8 | * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 9 | * Neither the author nor the names of any contributors may be used to endorse or promote products derived from this software without specific prior written permission. 10 | 11 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 12 | 13 | -------------------------------------------------------------------------------- /lib/kissfftmodified/COPYING: -------------------------------------------------------------------------------- 1 | Copyright (c) 2003-2010 Mark Borgerding 2 | 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 8 | * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 9 | * Neither the author nor the names of any contributors may be used to endorse or promote products derived from this software without specific prior written permission. 10 | 11 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 12 | 13 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "webfft", 3 | "version": "1.0.3", 4 | "description": "The Fastest Fourier Transform on the Web! 🚀🚀", 5 | "main": "lib/main.js", 6 | "types": "lib/@types/webfft/index.d.ts", 7 | "scripts": { 8 | "serve": "serve lib -l 8080", 9 | "build": "webpack --config ./webpack.config.cjs --output-path ./lib/dist", 10 | "test": "vitest run --config ./tests/vitest.config.ts --coverage" 11 | }, 12 | "repository": { 13 | "type": "git", 14 | "url": "git+https://github.com/IQEngine/WebFFT.git" 15 | }, 16 | "keywords": [ 17 | "FFT", 18 | "frequency", 19 | "IQ", 20 | "RF", 21 | "wireless", 22 | "audio", 23 | "fourier", 24 | "dsp", 25 | "signal", 26 | "processing" 27 | ], 28 | "author": "IQEngine", 29 | "license": "MIT", 30 | "bugs": { 31 | "url": "https://github.com/IQEngine/WebFFT/issues" 32 | }, 33 | "homepage": "https://github.com/IQEngine/WebFFT#readme", 34 | "devDependencies": { 35 | "@typescript-eslint/eslint-plugin": "^6.7.0", 36 | "@typescript-eslint/parser": "^6.7.0", 37 | "@vitest/coverage-v8": "^0.34.4", 38 | "auto-changelog": "^2.4.0", 39 | "eslint": "^8.49.0", 40 | "eslint-plugin-react": "^7.33.2", 41 | "fs": "^0.0.2", 42 | "gh-release": "^7.0.2", 43 | "path-browserify": "^1.0.1", 44 | "prettier": "^3.0.3", 45 | "serve": "^14.2.1", 46 | "vite": "^4.4.9", 47 | "vitest": "^0.34.4", 48 | "webpack": "^5.88.2", 49 | "webpack-cli": "^5.1.4" 50 | }, 51 | "type": "module" 52 | } 53 | -------------------------------------------------------------------------------- /lib/nayukic/FFT.js: -------------------------------------------------------------------------------- 1 | import NayukiCModule from "./NayukiCFFT.mjs"; 2 | 3 | ("use strict"); 4 | 5 | var nayukiCModule = NayukiCModule({}); 6 | 7 | var nc_precalc = nayukiCModule.cwrap("precalc", "number", ["number"]); 8 | 9 | var nc_dispose = nayukiCModule.cwrap("dispose", "void", ["number"]); 10 | 11 | var nc_transform_radix2_precalc = nayukiCModule.cwrap( 12 | "transform_radix2_precalc", 13 | "void", 14 | ["number", "number", "number", "number"], 15 | ); 16 | 17 | var nc_precalc_f = nayukiCModule.cwrap("precalc_f", "number", ["number"]); 18 | 19 | var nc_dispose_f = nayukiCModule.cwrap("dispose_f", "void", ["number"]); 20 | 21 | var nc_transform_radix2_precalc_f = nayukiCModule.cwrap( 22 | "transform_radix2_precalc_f", 23 | "void", 24 | ["number", "number", "number", "number"], 25 | ); 26 | 27 | function FFTNayukiC(n) { 28 | this.n = n; 29 | this.rptr = nayukiCModule._malloc(n * 4 + n * 4); 30 | this.iptr = this.rptr + n * 4; 31 | this.rarr = new Float32Array(nayukiCModule.HEAPU8.buffer, this.rptr, n); 32 | this.iarr = new Float32Array(nayukiCModule.HEAPU8.buffer, this.iptr, n); 33 | this.tables = nc_precalc_f(n); 34 | 35 | this.forward = function (real, imag) { 36 | this.rarr.set(real); 37 | this.iarr.set(imag); 38 | nc_transform_radix2_precalc_f(this.rptr, this.iptr, this.n, this.tables); 39 | real.set(this.rarr); 40 | imag.set(this.iarr); 41 | }; 42 | 43 | this.dispose = function () { 44 | nayukiCModule._free(this.rptr); 45 | nc_dispose_f(this.tables); 46 | }; 47 | } 48 | 49 | export default FFTNayukiC; 50 | -------------------------------------------------------------------------------- /default.conf: -------------------------------------------------------------------------------- 1 | events {} 2 | http { 3 | include mime.types; 4 | default_type application/octet-stream; 5 | 6 | server { 7 | listen 80; 8 | 9 | server_name webfft.com www.webfft.com; 10 | 11 | root /usr/share/nginx/html; 12 | index index.html; 13 | 14 | charset utf-8; 15 | 16 | access_log /var/log/nginx/access.log; 17 | error_log /var/log/nginx/error.log; 18 | 19 | add_header Content-Security-Policy "frame-ancestors 'self';"; 20 | 21 | gzip on; 22 | gzip_types text/plain text/css text/json application/json application/javascript text/xml application/xml application/xml+rss text/javascript; 23 | 24 | location / { 25 | try_files $uri $uri/ /index.html; 26 | } 27 | 28 | location ~* \.(css|js|svg|png|bmp|jpeg|jpg|ico|webmanifest)$ { 29 | add_header Cache-Control "public, max-age=31536000, immutable" always; 30 | add_header X-Content-Type-Options "nosniff"; 31 | } 32 | 33 | location ~ ^/docs { 34 | add_header Cache-Control "public, max-age=31536000, immutable" always; 35 | } 36 | 37 | location = /sitemap.xml { 38 | add_header Content-Type "application/xml"; 39 | try_files $uri /index.html; 40 | } 41 | 42 | location = /robots.txt { 43 | add_header Content-Type "text/plain"; 44 | try_files $uri /index.html; 45 | } 46 | 47 | error_page 404 /index.html; 48 | 49 | location = /40x.html { 50 | } 51 | 52 | location = /50x.html { 53 | } 54 | } 55 | } 56 | 57 | -------------------------------------------------------------------------------- /tests/profile.test.js: -------------------------------------------------------------------------------- 1 | import { expect, test } from "vitest"; 2 | import webfft from "../lib/main.js"; 3 | 4 | test("run profile", () => { 5 | const duration = 2; // dont reduce this, there was some wasm oom errors that didnt happen when it was at 0.5 6 | const fftsize = 1024; 7 | const fft = new webfft(fftsize); 8 | const start = performance.now(); 9 | const profileObj = fft.profile(duration); 10 | const elapsed = (performance.now() - start) / 1e3; 11 | expect(elapsed).toBeGreaterThan(duration); 12 | expect(elapsed).toBeLessThan(duration * 1.5); // possibility for this to error in the future if run on a super slow machine 13 | }); 14 | 15 | test("test local storage", () => { 16 | const duration = 2; 17 | const fftsize = 1024; 18 | const fft = new webfft(fftsize); 19 | const profileObj = fft.profile(duration); 20 | const profileObj2 = fft.profile(duration); 21 | }); 22 | 23 | test("refresh set to false", () => { 24 | const fft = new webfft(1024); 25 | const profileObj = fft.profile(0.5); 26 | const profileObj2 = fft.profile(0.5, false); 27 | }); 28 | 29 | // this one has to come last because it will wipe out localStorage for future tests 30 | test("test when localStorage is undefined", () => { 31 | const duration = 2; 32 | const fftsize = 1024; 33 | localStorage = undefined; 34 | const fft = new webfft(fftsize); 35 | const profileObj = fft.profile(duration); 36 | }); 37 | 38 | test("test checkBrowserCapabilities", () => { 39 | const fft = new webfft(1024); 40 | const ret = fft.checkBrowserCapabilities(); 41 | }); 42 | 43 | test("test dispose", () => { 44 | const fft = new webfft(1024, "kissWasm"); 45 | fft.dispose(); 46 | }); 47 | -------------------------------------------------------------------------------- /site/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "site", 3 | "private": true, 4 | "version": "0.0.0", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite", 8 | "build": "tsc && vite build", 9 | "lint": "eslint 'src/**/*.{ts,tsx}' --ext ts,tsx --report-unused-disable-directives --max-warnings 0", 10 | "preview": "vite preview", 11 | "predeploy": "npm run build", 12 | "deploy": "gh-pages -d dist" 13 | }, 14 | "dependencies": { 15 | "@mdx-js/react": "^2.3.0", 16 | "@mdx-js/rollup": "^2.3.0", 17 | "@tailwindcss/typography": "^0.5.10", 18 | "@types/react-syntax-highlighter": "^15.5.7", 19 | "@vitejs/plugin-react": "^4.0.4", 20 | "autoprefixer": "^10.4.15", 21 | "chart.js": "^4.4.0", 22 | "detect-browser": "^5.3.0", 23 | "fftshift": "^1.0.1", 24 | "gh-pages": "^6.1.1", 25 | "graceful-fs": "^4.2.11", 26 | "plotly.js": "^2.26.0", 27 | "postcss": "^8.4.29", 28 | "react": "^18.2.0", 29 | "react-chartjs-2": "^5.2.0", 30 | "react-dom": "^18.2.0", 31 | "react-loader-spinner": "^5.4.5", 32 | "react-plotly.js": "^2.6.0", 33 | "react-router-dom": "^6.15.0", 34 | "react-syntax-highlighter": "^15.5.0", 35 | "tailwindcss": "^3.3.3", 36 | "webfft": "^1.0.3", 37 | "@types/react-dom": "^18.2.7", 38 | "@types/web": "^0.0.114", 39 | "@typescript-eslint/eslint-plugin": "^6.0.0", 40 | "@typescript-eslint/parser": "^6.0.0", 41 | "eslint": "^8.45.0", 42 | "eslint-plugin-react-hooks": "^4.6.0", 43 | "eslint-plugin-react-refresh": "^0.4.3", 44 | "ts-node": "^10.9.1", 45 | "typescript": "^5.0.2", 46 | "vite": "^4.4.9", 47 | "vite-plugin-sitemap": "^0.4.2" 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /site/src/components/FFTSizeInputButton.tsx: -------------------------------------------------------------------------------- 1 | import React, { Dispatch, SetStateAction } from "react"; 2 | import Button from "./Button"; 3 | 4 | interface FFTSizeInputProps { 5 | fftSize: number; 6 | setFftSize: Dispatch>; 7 | } 8 | 9 | const FFTSizeInput: React.FC = ({ fftSize, setFftSize }) => { 10 | const maxFFTSize = 131072; // Setting max limit to a reasonable power of 2 11 | const minFFTSize = 4; // Setting min limit to a reasonable power of 2 12 | 13 | const incrementFFTSize = () => { 14 | if (fftSize < maxFFTSize) { 15 | setFftSize(fftSize * 2); 16 | } 17 | }; 18 | 19 | const decrementFFTSize = () => { 20 | if (fftSize > minFFTSize) { 21 | setFftSize(fftSize / 2); 22 | } 23 | }; 24 | 25 | return ( 26 |
27 | 34 | 42 | 49 |
50 | ); 51 | }; 52 | 53 | export default FFTSizeInput; 54 | -------------------------------------------------------------------------------- /lib/kissfft/webfftWrapper.js: -------------------------------------------------------------------------------- 1 | import KissFFTModule from "./KissFFT.mjs"; 2 | 3 | ("use strict"); 4 | 5 | var kissFFTModule = KissFFTModule({}); 6 | 7 | var kiss_fft_alloc = kissFFTModule.cwrap("kiss_fft_alloc", "number", [ 8 | "number", 9 | "number", 10 | "number", 11 | "number", 12 | ]); 13 | 14 | var kiss_fft = kissFFTModule.cwrap("kiss_fft", "void", [ 15 | "number", 16 | "number", 17 | "number", 18 | ]); 19 | 20 | var kiss_fft_free = kissFFTModule.cwrap("kiss_fft_free", "void", ["number"]); 21 | 22 | class KissFftWrapperWasm { 23 | constructor(size) { 24 | this.size = size; 25 | this.fcfg = kiss_fft_alloc(this.size, false); 26 | this.icfg = kiss_fft_alloc(this.size, true); 27 | 28 | this.inptr = kissFFTModule._malloc(this.size * 8); 29 | 30 | this.cin = new Float32Array( 31 | kissFFTModule.HEAPU8.buffer, 32 | this.inptr, 33 | this.size * 2, 34 | ); 35 | } 36 | 37 | fft = function (inputArray) { 38 | // TODO: figure out how to move this into the constructor without breaking things (unit tests will catch it) 39 | const outptr = kissFFTModule._malloc(this.size * 8); 40 | 41 | const cout = new Float32Array( 42 | kissFFTModule.HEAPU8.buffer, 43 | outptr, 44 | this.size * 2, 45 | ); 46 | 47 | this.cin.set(inputArray); 48 | 49 | kiss_fft(this.fcfg, this.inptr, outptr); 50 | 51 | // we need to free the memory of outptr before we return, so we need this too 52 | let outputArray = new Float32Array(this.size * 2); 53 | outputArray.set(cout); 54 | 55 | kissFFTModule._free(outptr); 56 | 57 | return outputArray; 58 | }; 59 | 60 | dispose() { 61 | kiss_fft_free(this.fcfg); 62 | kiss_fft_free(this.icfg); 63 | kissFFTModule._free(this.inptr); 64 | } 65 | } 66 | 67 | export default KissFftWrapperWasm; 68 | -------------------------------------------------------------------------------- /lib/kissfftmodified/webfftWrapper.js: -------------------------------------------------------------------------------- 1 | import KissFFTModule from "./KissFFT.mjs"; 2 | 3 | ("use strict"); 4 | 5 | var kissFFTModule = KissFFTModule({}); 6 | 7 | var kiss_fft_alloc = kissFFTModule.cwrap("kiss_fft_alloc", "number", [ 8 | "number", 9 | "number", 10 | "number", 11 | "number", 12 | ]); 13 | 14 | var kiss_fft = kissFFTModule.cwrap("kiss_fft", "void", [ 15 | "number", 16 | "number", 17 | "number", 18 | ]); 19 | 20 | var kiss_fft_free = kissFFTModule.cwrap("kiss_fft_free", "void", ["number"]); 21 | 22 | class KissFftModifiedWrapperWasm { 23 | constructor(size) { 24 | this.size = size; 25 | this.fcfg = kiss_fft_alloc(size, false); 26 | this.icfg = kiss_fft_alloc(size, true); 27 | 28 | this.inptr = kissFFTModule._malloc(size * 8 + size * 8); 29 | 30 | this.cin = new Float32Array( 31 | kissFFTModule.HEAPU8.buffer, 32 | this.inptr, 33 | size * 2, 34 | ); 35 | } 36 | 37 | fft = function (inputArray) { 38 | // TODO: figure out how to move this into the constructor without breaking things (unit tests will catch it) 39 | const outptr = kissFFTModule._malloc(this.size * 8); 40 | 41 | const cout = new Float32Array( 42 | kissFFTModule.HEAPU8.buffer, 43 | outptr, 44 | this.size * 2, 45 | ); 46 | 47 | this.cin.set(inputArray); 48 | 49 | kiss_fft(this.fcfg, this.inptr, outptr); 50 | 51 | // we need to free the memory of outptr before we return, so we need this too 52 | let outputArray = new Float32Array(this.size * 2); 53 | outputArray.set(cout); 54 | 55 | kissFFTModule._free(outptr); 56 | 57 | return outputArray; 58 | }; 59 | 60 | dispose() { 61 | kiss_fft_free(this.fcfg); 62 | kiss_fft_free(this.icfg); 63 | kissFFTModule._free(this.inptr); 64 | } 65 | } 66 | 67 | export default KissFftModifiedWrapperWasm; 68 | -------------------------------------------------------------------------------- /lib/Makefile: -------------------------------------------------------------------------------- 1 | all: KissFFT KissFFTModified NayukiCFFT Cross 2 | 3 | KissFFT: kissfft/kiss_fft.c kissfft/kiss_fft.h 4 | emcc --no-entry kissfft/kiss_fft.c -o kissfft/KissFFT.mjs \ 5 | -s ENVIRONMENT='web' \ 6 | -s EXPORT_NAME='KissFFTModule' \ 7 | -s MODULARIZE=1 \ 8 | -s BINARYEN_ASYNC_COMPILATION=0 \ 9 | -s SINGLE_FILE=1 \ 10 | -s EXPORTED_FUNCTIONS='["_malloc", "_free", "_kiss_fft_alloc", "_kiss_fft", "_kiss_fft_free"]' \ 11 | -s EXPORTED_RUNTIME_METHODS='["ccall", "cwrap"]' \ 12 | -O3 13 | 14 | KissFFTModified: kissfftmodified/kiss_fft.c kissfftmodified/kiss_fft.h 15 | emcc --no-entry kissfftmodified/kiss_fft.c -o kissfftmodified/KissFFT.mjs \ 16 | -s ENVIRONMENT='web' \ 17 | -s EXPORT_NAME='KissFFTModule' \ 18 | -s MODULARIZE=1 \ 19 | -s BINARYEN_ASYNC_COMPILATION=0 \ 20 | -s SINGLE_FILE=1 \ 21 | -s EXPORTED_FUNCTIONS='["_malloc", "_free", "_kiss_fft_alloc", "_kiss_fft", "_kiss_fft_free"]' \ 22 | -s EXPORTED_RUNTIME_METHODS='["ccall", "cwrap"]' \ 23 | -O3 24 | 25 | NayukiCFFT: nayukic/fft.c nayukic/fft.h 26 | emcc --no-entry nayukic/fft.c -o nayukic/NayukiCFFT.mjs \ 27 | -s ENVIRONMENT='web' \ 28 | -s MODULARIZE=1 \ 29 | -s BINARYEN_ASYNC_COMPILATION=0 \ 30 | -s SINGLE_FILE=1 \ 31 | -s EXPORT_NAME="'NayukiCModule'" \ 32 | -s EXPORTED_FUNCTIONS="['_malloc', '_free', '_transform_radix2_precalc','_precalc','_dispose','_transform_radix2_precalc_f','_precalc_f','_dispose_f']" \ 33 | -s EXPORTED_RUNTIME_METHODS='["ccall", "cwrap"]' \ 34 | -O3 35 | 36 | Cross: cross/Cross.c cross/Cross.h 37 | emcc --no-entry cross/Cross.c -o cross/Cross.mjs -s ENVIRONMENT='web' -s EXPORT_NAME='CrossModule' -s MODULARIZE=1 -s BINARYEN_ASYNC_COMPILATION=0 -s SINGLE_FILE=1 -s EXPORTED_FUNCTIONS='["_malloc", "_free", "_fftCross"]' -s EXPORTED_RUNTIME_METHODS='["ccall", "cwrap"]' -O3 38 | 39 | 40 | .PHONY: clean 41 | clean: 42 | rm kissfft/KissFFT.mjs 43 | rm kissfftmodified/KissFFT.mjs 44 | rm nayukic/NayukiCFFT.mjs 45 | rm cross/Cross.mjs 46 | -------------------------------------------------------------------------------- /lib/kissfft/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | asdasdasd 5 | 6 | 62 | 63 | -------------------------------------------------------------------------------- /lib/kissfftmodified/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | asdasdasd 5 | 6 | 62 | 63 | -------------------------------------------------------------------------------- /lib/vailNOTFINISHED/fftutil.cjs: -------------------------------------------------------------------------------- 1 | /*===========================================================================*\ 2 | * Fast Fourier Transform Frequency/Magnitude passes 3 | * 4 | * (c) Vail Systems. Joshua Jung and Ben Bryan. 2015 5 | * 6 | * This code is not designed to be highly optimized but as an educational 7 | * tool to understand the Fast Fourier Transform. 8 | \*===========================================================================*/ 9 | 10 | //------------------------------------------------- 11 | // The following code assumes a complex number is 12 | // an array: [real, imaginary] 13 | //------------------------------------------------- 14 | var complex = require("./complex.cjs"); 15 | 16 | //------------------------------------------------- 17 | // By Eulers Formula: 18 | // 19 | // e^(i*x) = cos(x) + i*sin(x) 20 | // 21 | // and in DFT: 22 | // 23 | // x = -2*PI*(k/N) 24 | //------------------------------------------------- 25 | var mapExponent = {}, 26 | exponent = function (k, N) { 27 | var x = -2 * Math.PI * (k / N); 28 | 29 | mapExponent[N] = mapExponent[N] || {}; 30 | mapExponent[N][k] = mapExponent[N][k] || [Math.cos(x), Math.sin(x)]; // [Real, Imaginary] 31 | 32 | return mapExponent[N][k]; 33 | }; 34 | 35 | //------------------------------------------------- 36 | // Calculate FFT Magnitude for complex numbers. 37 | //------------------------------------------------- 38 | var fftMag = function (fftBins) { 39 | var ret = fftBins.map(complex.magnitude); 40 | return ret.slice(0, ret.length / 2); 41 | }; 42 | 43 | //------------------------------------------------- 44 | // Calculate Frequency Bins 45 | // 46 | // Returns an array of the frequencies (in hertz) of 47 | // each FFT bin provided, assuming the sampleRate is 48 | // samples taken per second. 49 | //------------------------------------------------- 50 | var fftFreq = function (fftBins, sampleRate) { 51 | var stepFreq = sampleRate / fftBins.length; 52 | var ret = fftBins.slice(0, fftBins.length / 2); 53 | 54 | return ret.map(function (__, ix) { 55 | return ix * stepFreq; 56 | }); 57 | }; 58 | 59 | //------------------------------------------------- 60 | // Exports 61 | //------------------------------------------------- 62 | module.exports = { 63 | fftMag: fftMag, 64 | fftFreq: fftFreq, 65 | exponent: exponent 66 | }; 67 | -------------------------------------------------------------------------------- /site/src/docs/GettingStarted.mdx: -------------------------------------------------------------------------------- 1 | ## Overview 2 | 3 | WebFFT is a metalibrary containing many FFT libraries, both javascript and webassembly based. We'll refer to these as sub-libraries. 4 | 5 | There is a default sub-library that is used, but if you run 6 | 7 | ```javascript 8 | import webfft from "webfft"; 9 | const fft = new webfft(1024); 10 | fft.profile(); // optional arg sets number of seconds spent profiling 11 | ``` 12 | 13 | it will benchmark them all and use the best one for future calls. 14 | 15 | As part of importing the library we will run a check to see if wasm is even supported, so the profiler and default can know which pool to pull from. 16 | 17 | ### Basic Usage 18 | 19 | ```javascript 20 | const webfft = require('webfft'); 21 | 22 | // Instantiate 23 | const fftsize = 1024; // must be power of 2 24 | const fft = new webfft(fftsize); 25 | 26 | // Profile 27 | profileResults = fft.profile(); // results object can be used to make visualizations of the benchmarking results 28 | 29 | // Create Input 30 | const input = new Float32Array(2048); // interleaved complex array (IQIQIQIQ...), so it's twice the size 31 | input.fill(0); 32 | 33 | // Run FFT 34 | const out = fft.fft(input); // out will be a Float32Array of size 2048 35 | // or 36 | const out = fft.fft(input, 'kissWasm'); 37 | 38 | fft.dispose(); // release Wasm memory 39 | ``` 40 | 41 | ### 2D FFTs 42 | 43 | WebFFT also supports 2D FFTs, using an array of arrays. 44 | The inner arrays should be length 2*size and the outter array length should be a power of 2 but does not need to match the inner. 45 | 46 | ```javascript 47 | import webfft from "webfft"; 48 | 49 | const fftsize = 1024; 50 | const outterSize = 128; 51 | const fft = new webfft(fftsize); 52 | let inputArr = []; 53 | for (let j = 0; j < outterSize; j++) { 54 | const subArray = new Float32Array(fftsize * 2); 55 | for (let i = 0; i < fftsize * 2; i++) { 56 | subArray[i] = i * j * 1.12312312; // Arbitrary 57 | } 58 | inputArr.push(subArray); // add inner array 59 | } 60 | const out = fft.fft2d(inputArr); 61 | 62 | fft.dispose(); // cleanup wasm 63 | ``` 64 | 65 | ### Other Notes 66 | 67 | Use fftr() for real-valued input, the output will still be complex but only the positive frequencies will be returned. 68 | 69 | You don't have to pass fft/fftr/fft2d typed arrays, they can be regular javascript arrays. -------------------------------------------------------------------------------- /tests/fft2d.test.js: -------------------------------------------------------------------------------- 1 | import { expect, test } from "vitest"; 2 | import webfft from "../lib/main.js"; 3 | 4 | test("fft2d validation which also validates 1d fft values", () => { 5 | const fftsize = 1024; 6 | const outterSize = 128; 7 | 8 | // Create input 9 | let inputArr = []; 10 | for (let j = 0; j < outterSize; j++) { 11 | let subArray = new Float32Array(fftsize * 2); 12 | for (let i = 0; i < fftsize * 2; i++) { 13 | subArray[i] = Math.sin(i + j); // Arbitrary, but dont change or known correct sum will be wrong 14 | } 15 | inputArr.push(subArray); 16 | } 17 | 18 | const unusedft = new webfft(16); // doesnt matter 19 | const availableSubLibraries = unusedft.availableSubLibraries(); 20 | for (let i = 0; i < availableSubLibraries.length; i++) { 21 | const fft = new webfft(fftsize, availableSubLibraries[i]); 22 | const outputArr = fft.fft2d(inputArr); 23 | fft.dispose(); 24 | 25 | expect(outputArr[0][0]).toBeCloseTo(0.1705339835898485, 3); 26 | expect(outputArr[0][1]).toBeCloseTo(-0.17611848087108228, 3); 27 | 28 | expect(outputArr[100][2000]).toBeCloseTo(-0.01635975610207374, 5); 29 | expect(outputArr[100][2001]).toBeCloseTo(0.9907842644470186, 5); 30 | 31 | // sum the mags 32 | let sum = 0; 33 | for (let i = 0; i < outterSize; i++) { 34 | for (let j = 0; j < fftsize; j++) { 35 | sum += Math.sqrt( 36 | outputArr[i][j * 2] * outputArr[i][j * 2] + 37 | outputArr[i][j * 2 + 1] * outputArr[i][j * 2 + 1], 38 | ); 39 | } 40 | } 41 | expect(sum).toBeCloseTo(1039851.9120030339, 0); // see below for how this number was found 42 | } 43 | }); 44 | 45 | /* 46 | PYTHON USED TO GET CORRECT SUM 47 | 48 | import numpy as np 49 | fftsize = 1024 50 | outterSize = 128 51 | inputArr = np.zeros((outterSize, fftsize), np.complex64) 52 | for j in range(outterSize): 53 | for i in range(fftsize): 54 | real_part = np.sin((2*i) + j) 55 | imag_part = np.sin((2*i + 1) + j) 56 | inputArr[j][i] = real_part + 1j * imag_part 57 | 58 | out = np.fft.fft2(inputArr) 59 | 60 | sum = 0 61 | for i in range(outterSize): 62 | for j in range(fftsize): 63 | sum += np.abs(out[i][j]) 64 | 65 | print(sum) 66 | print(out[0][0]) 67 | print(out[100][1000]) 68 | 69 | >>> print(sum) 70 | 1039851.9120030339 71 | >>> print(out[0][0]) 72 | (0.1705339835898485-0.17611848087108228j) 73 | >>> print(out[100][1000]) 74 | (-0.01635975610207374+0.9907842644470186j) 75 | 76 | */ 77 | -------------------------------------------------------------------------------- /lib/benchmark.js: -------------------------------------------------------------------------------- 1 | // Adapted from https://github.com/j-funk/js-dsp-test 2 | // Nice comparison in https://thebreakfastpost.com/2015/10/18/ffts-in-javascript/ 3 | 4 | import webfft from "./main.js"; 5 | import sortDescending from "./utils/sortPerformance.js"; 6 | import checkBrowserCapabilities from "./utils/checkCapabilities.js"; 7 | 8 | // Check for Wasm and SIMD support 9 | checkBrowserCapabilities().then((capabilities) => { 10 | console.log(`Capabilities: ${JSON.stringify(capabilities)}`); 11 | }); 12 | 13 | window.onload = function () { 14 | const duration = 0.1; 15 | const fftsize = 1024; 16 | const fft = new webfft(fftsize); 17 | 18 | const profileObj = fft.profile(duration); 19 | fft.dispose(); 20 | console.log(profileObj); 21 | 22 | let results = profileObj.fftsPerSecond; 23 | 24 | let test_names = []; 25 | let barColors = []; 26 | for (let i = 0; i < profileObj.subLibraries.length; i++) { 27 | if (profileObj.subLibraries[i].includes("Javascript")) { 28 | test_names.push(profileObj.subLibraries[i].split("Javascript")[0]); 29 | barColors.push("rgba(255,0,0,0.7)"); 30 | } else if (profileObj.subLibraries[i].includes("Wasm")) { 31 | test_names.push(profileObj.subLibraries[i].split("Wasm")[0]); 32 | barColors.push("rgba(0,0,255,0.7)"); 33 | } else { 34 | console.error("all sublibraries should end in Javascript or Wasm"); 35 | } 36 | } 37 | 38 | // Plotly stuff 39 | const xArray = test_names; 40 | const yArray = results; 41 | 42 | const data = [ 43 | { 44 | x: xArray, 45 | y: yArray, 46 | type: "bar", 47 | marker: { color: barColors }, 48 | }, 49 | ]; 50 | 51 | const layout = { 52 | title: "Comparison of FFTs in Javascript and WebAssembly", 53 | yaxis: { 54 | title: { 55 | text: "FFTs per Second", 56 | font: { 57 | family: "sans serif", 58 | size: 18, 59 | }, 60 | }, 61 | }, 62 | annotations: [ 63 | { 64 | x: 1, 65 | y: Math.max.apply(Math, yArray) * 1.2, 66 | text: "JavaScript", 67 | showarrow: false, 68 | font: { 69 | family: "sans serif", 70 | size: 18, 71 | color: "rgba(255,0,0,1)", 72 | }, 73 | }, 74 | { 75 | x: 1, 76 | y: Math.max.apply(Math, yArray) * 1.3, 77 | text: "WebAssembly", 78 | showarrow: false, 79 | font: { 80 | family: "sans serif", 81 | size: 18, 82 | color: "rgba(0,0,255,1)", 83 | }, 84 | }, 85 | ], 86 | }; 87 | 88 | Plotly.newPlot("myPlot", sortDescending(data), layout); 89 | }; 90 | -------------------------------------------------------------------------------- /lib/cross/Cross.c: -------------------------------------------------------------------------------- 1 | 2 | /* Public domain FFT implementation from Don Cross. */ 3 | 4 | #include "Cross.h" 5 | 6 | #include 7 | #include 8 | 9 | void 10 | fftCross(unsigned int n, int inverse, 11 | const double *ri, const double *ii, 12 | double *ro, double *io) 13 | { 14 | if (!ri || !ro || !io) return; 15 | 16 | unsigned int bits; 17 | unsigned int i, j, k, m; 18 | unsigned int blockSize, blockEnd; 19 | 20 | double tr, ti; 21 | 22 | if (n < 2) return; 23 | if (n & (n-1)) return; 24 | 25 | double angle = 2.0 * M_PI; 26 | if (inverse) angle = -angle; 27 | 28 | for (i = 0; ; ++i) { 29 | if (n & (1 << i)) { 30 | bits = i; 31 | break; 32 | } 33 | } 34 | 35 | #ifdef _MSC_VER 36 | int *table = (int *)_malloca(n * sizeof(int)); 37 | #else 38 | int table[n]; 39 | #endif 40 | 41 | for (i = 0; i < n; ++i) { 42 | m = i; 43 | for (j = k = 0; j < bits; ++j) { 44 | k = (k << 1) | (m & 1); 45 | m >>= 1; 46 | } 47 | table[i] = k; 48 | } 49 | 50 | if (ii) { 51 | for (i = 0; i < n; ++i) { 52 | ro[table[i]] = ri[i]; 53 | io[table[i]] = ii[i]; 54 | } 55 | } else { 56 | for (i = 0; i < n; ++i) { 57 | ro[table[i]] = ri[i]; 58 | io[table[i]] = 0.0; 59 | } 60 | } 61 | 62 | blockEnd = 1; 63 | 64 | for (blockSize = 2; blockSize <= n; blockSize <<= 1) { 65 | 66 | double delta = angle / (double)blockSize; 67 | double sm2 = -sin(-2 * delta); 68 | double sm1 = -sin(-delta); 69 | double cm2 = cos(-2 * delta); 70 | double cm1 = cos(-delta); 71 | double w = 2 * cm1; 72 | double ar[3], ai[3]; 73 | 74 | for (i = 0; i < n; i += blockSize) { 75 | 76 | ar[2] = cm2; 77 | ar[1] = cm1; 78 | 79 | ai[2] = sm2; 80 | ai[1] = sm1; 81 | 82 | for (j = i, m = 0; m < blockEnd; j++, m++) { 83 | 84 | ar[0] = w * ar[1] - ar[2]; 85 | ar[2] = ar[1]; 86 | ar[1] = ar[0]; 87 | 88 | ai[0] = w * ai[1] - ai[2]; 89 | ai[2] = ai[1]; 90 | ai[1] = ai[0]; 91 | 92 | k = j + blockEnd; 93 | tr = ar[0] * ro[k] - ai[0] * io[k]; 94 | ti = ar[0] * io[k] + ai[0] * ro[k]; 95 | 96 | ro[k] = ro[j] - tr; 97 | io[k] = io[j] - ti; 98 | 99 | ro[j] += tr; 100 | io[j] += ti; 101 | } 102 | } 103 | 104 | blockEnd = blockSize; 105 | } 106 | 107 | if (inverse) { 108 | 109 | double denom = (double)n; 110 | 111 | for (i = 0; i < n; i++) { 112 | ro[i] /= denom; 113 | io[i] /= denom; 114 | } 115 | } 116 | 117 | #ifdef _MSC_VER 118 | _freea(table); 119 | #endif 120 | } 121 | 122 | -------------------------------------------------------------------------------- /site/README.md: -------------------------------------------------------------------------------- 1 | # Web FFT App: React + TypeScript + Vite + Tailwind CSS 2 | 3 | ## Table of Contents 4 | - [Overview](#overview) 5 | - [Quick Start](#quick-start) 6 | - [Installation](#installation) 7 | - [Running Locally](#running-locally) 8 | - [Building for Production](#building-for-production) 9 | - [Further Configuration and Expansion](#further-configuration-and-expansion) 10 | - [Testing](#testing) 11 | - [Contributing](#contributing) 12 | - [Support](#support) 13 | - [License](#license) 14 | 15 | ## Overview 16 | 17 | This is the web app for the Web FFT Meta-Library. Benchmark and test your browser(s) and different FFT algorithms against one another to see what our NPM library can do for you! 18 | 19 | ## Quick Start 20 | 21 | ### Installation 22 | 23 | First, ensure you have [Node.js](https://nodejs.org/) and npm installed. 24 | 25 | Then, clone the repository or download the code. Navigate to the root of the project in your terminal and run: 26 | 27 | ```bash 28 | npm install 29 | ``` 30 | 31 | This command will install all necessary dependencies. 32 | 33 | ### Running Locally 34 | 35 | To start the development server and view the app in your browser, simply run: 36 | 37 | ```bash 38 | npm run dev 39 | ``` 40 | 41 | This will launch the Vite development server. You can then open your browser to [http://localhost:3000/](http://localhost:3000/) to interact with the local web app. 42 | 43 | Any changes you make to the source code will be reflected in real-time in the browser, thanks to Vite's hot module reloading. 44 | 45 | ### Building for Production 46 | 47 | Deploy site (currently served through github pages) using `cd site && npm run deploy` 48 | 49 | The way it works is, `npm run build` will build the static content and put it in /dist 50 | 51 | ## Further Configuration and Expansion 52 | 53 | The project comes with a minimal ESLint configuration to ensure code quality. For production applications, consider expanding the ESLint setup. Guidelines and suggestions for this are provided in the original template documentation. 54 | 55 | ## Testing 56 | 57 | We encourage testing your configurations extensively before deploying them in a production environment. To run the existing test suite, use the following command: 58 | 59 | ```bash 60 | npm run test 61 | ``` 62 | 63 | ## Contributing 64 | 65 | We welcome contributions from the community. If you'd like to contribute, please fork the repository, create a new branch for your features or bug fixes, and submit a pull request. 66 | 67 | ## Support 68 | 69 | If you encounter any issues or have questions, feel free to open an issue in the repository. We're here to help you. 70 | 71 | ## License 72 | 73 | [MIT](LICENSE) - see the file for details 74 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | .pnpm-debug.log* 9 | 10 | # Diagnostic reports (https://nodejs.org/api/report.html) 11 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 12 | 13 | # Runtime data 14 | pids 15 | *.pid 16 | *.seed 17 | *.pid.lock 18 | 19 | # Directory for instrumented libs generated by jscoverage/JSCover 20 | lib-cov 21 | 22 | # Coverage directory used by tools like istanbul 23 | coverage 24 | *.lcov 25 | 26 | # nyc test coverage 27 | .nyc_output 28 | 29 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 30 | .grunt 31 | 32 | # Bower dependency directory (https://bower.io/) 33 | bower_components 34 | 35 | # node-waf configuration 36 | .lock-wscript 37 | 38 | # Compiled binary addons (https://nodejs.org/api/addons.html) 39 | build/Release 40 | 41 | # Dependency directories 42 | node_modules/ 43 | jspm_packages/ 44 | 45 | # Snowpack dependency directory (https://snowpack.dev/) 46 | web_modules/ 47 | 48 | # TypeScript cache 49 | *.tsbuildinfo 50 | 51 | # Optional npm cache directory 52 | .npm 53 | 54 | # Optional eslint cache 55 | .eslintcache 56 | 57 | # Optional stylelint cache 58 | .stylelintcache 59 | 60 | # Microbundle cache 61 | .rpt2_cache/ 62 | .rts2_cache_cjs/ 63 | .rts2_cache_es/ 64 | .rts2_cache_umd/ 65 | 66 | # Optional REPL history 67 | .node_repl_history 68 | 69 | # Output of 'npm pack' 70 | *.tgz 71 | 72 | # Yarn Integrity file 73 | .yarn-integrity 74 | 75 | # dotenv environment variable files 76 | .env 77 | .env.development.local 78 | .env.test.local 79 | .env.production.local 80 | .env.local 81 | 82 | # parcel-bundler cache (https://parceljs.org/) 83 | .cache 84 | .parcel-cache 85 | 86 | # Next.js build output 87 | .next 88 | out 89 | 90 | # Nuxt.js build / generate output 91 | .nuxt 92 | dist 93 | 94 | # Gatsby files 95 | .cache/ 96 | # Comment in the public line in if your project uses Gatsby and not Next.js 97 | # https://nextjs.org/blog/next-9-1#public-directory-support 98 | # public 99 | 100 | # vuepress build output 101 | .vuepress/dist 102 | 103 | # vuepress v2.x temp and cache directory 104 | .temp 105 | .cache 106 | 107 | # Docusaurus cache and generated files 108 | .docusaurus 109 | 110 | # Serverless directories 111 | .serverless/ 112 | 113 | # FuseBox cache 114 | .fusebox/ 115 | 116 | # DynamoDB Local files 117 | .dynamodb/ 118 | 119 | # TernJS port file 120 | .tern-port 121 | 122 | # Stores VSCode versions used for testing VSCode extensions 123 | .vscode-test 124 | 125 | # yarn v2 126 | .yarn/cache 127 | .yarn/unplugged 128 | .yarn/build-state.yml 129 | .yarn/install-state.gz 130 | .pnp.* 131 | lib/bundle.js 132 | -------------------------------------------------------------------------------- /site/src/components/MarkdownComponents.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import CodeBlock from "./CodeBlock"; 3 | 4 | interface CustomParagraphProps 5 | extends React.HTMLAttributes { 6 | children?: React.ReactNode; 7 | } 8 | 9 | const CustomParagraph: React.FC = (props) => { 10 | const { children } = props; 11 | 12 | const hasCodeElement = React.Children.toArray(children).some((child) => { 13 | return React.isValidElement(child) && child.type === "code"; 14 | }); 15 | 16 | return hasCodeElement ? ( 17 |
22 | {React.Children.map(children, (child) => { 23 | if (React.isValidElement(child) && child.type === "code") { 24 | return child; // Simply return the code child without wrapping it again 25 | } 26 | })} 27 |
28 | ) : ( 29 | 30 | {children} 31 | 32 | ); 33 | }; 34 | 35 | const components = { 36 | h1: (props: any) => ( 37 |

38 | ), 39 | h2: (props: any) => ( 40 |

41 | ), 42 | h3: (props: any) => ( 43 |

44 | ), 45 | h4: (props: any) => ( 46 |

47 | ), 48 | h5: (props: any) => ( 49 |

50 | ), 51 | h6: (props: any) => ( 52 |
53 | ), 54 | ul: (props: any) => ( 55 |
    56 | ), 57 | li: (props: any) => ( 58 |
  • 59 | ), 60 | a: (props: any) => ( 61 | 62 | ), 63 | blockquote: (props: any) => ( 64 |
    69 | ), 70 | code: ({ className, children }: any) => { 71 | const match = /language-(\w+)/.exec(className || ""); 72 | return ( 73 | 77 | {String(children).replace(/\n$/, "")} 78 | 79 | ); 80 | }, 81 | p: CustomParagraph, 82 | }; 83 | 84 | export default components; 85 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # WebFFT 2 | 3 | The Fastest Fourier Transform on the Web! 4 | 5 | [Try it out](https://webfft.com/) 6 | 7 | [Documentation](https://webfft.com/docs) 8 | 9 | We welcome feedback via GitHub Issues and PRs! 10 | 11 | ## Overview 12 | 13 | WebFFT is a metalibrary containing many FFT libraries, both javascript and webassembly based. We'll refer to these as sub-libraries. 14 | 15 | There is a default sub-library that is used, but if you run 16 | 17 | ```javascript 18 | import webfft from "webfft"; 19 | const fft = new webfft(1024); 20 | fft.profile(); // optional arg sets number of seconds spent profiling 21 | ``` 22 | 23 | it will benchmark them all and use the best one for future calls. 24 | 25 | As part of importing the library we will run a check to see if wasm is even supported, so the profiler and default can know which pool to pull from. 26 | 27 | ### Basic Usage 28 | 29 | ```javascript 30 | const webfft = require('webfft'); 31 | 32 | // Instantiate 33 | const fftsize = 1024; // must be power of 2 34 | const fft = new webfft(fftsize); 35 | 36 | // Profile 37 | profileResults = fft.profile(); // results object can be used to make visualizations of the benchmarking results 38 | 39 | // Create Input 40 | const input = new Float32Array(2048); // interleaved complex array (IQIQIQIQ...), so it's twice the size 41 | input.fill(0); 42 | 43 | // Run FFT 44 | const out = fft.fft(input); // out will be a Float32Array of size 2048 45 | // or 46 | const out = fft.fft(input, 'kissWasm'); 47 | 48 | fft.dispose(); // release Wasm memory 49 | ``` 50 | 51 | ### 2D FFTs 52 | 53 | WebFFT also supports 2D FFTs, using an array of arrays. 54 | The inner arrays should be length 2*size and the outter array length should be a power of 2 but does not need to match the inner. 55 | 56 | ```javascript 57 | import webfft from "webfft"; 58 | 59 | const fftsize = 1024; 60 | const outterSize = 128; 61 | const fft = new webfft(fftsize); 62 | let inputArr = []; 63 | for (let j = 0; j < outterSize; j++) { 64 | const subArray = new Float32Array(fftsize * 2); 65 | for (let i = 0; i < fftsize * 2; i++) { 66 | subArray[i] = i * j * 1.12312312; // Arbitrary 67 | } 68 | inputArr.push(subArray); // add inner array 69 | } 70 | const out = fft.fft2d(inputArr); 71 | 72 | fft.dispose(); // cleanup wasm 73 | ``` 74 | 75 | ### Other Notes 76 | 77 | Deploy site using `cd site && npm run deploy`, and make sure in github pages settings it uses "deploy from a branch" and gh-pages is selected as the branch, because npm run deploy runs the gh-pages command which publishes the site to gh-pages branch by default. 78 | 79 | Use fftr() for real-valued input, the output will still be complex but only the positive frequencies will be returned. 80 | 81 | You don't have to pass fft/fftr/fft2d typed arrays, they can be regular javascript arrays. 82 | 83 | Run unit tests with `npm run test` 84 | -------------------------------------------------------------------------------- /lib/dntj/complex_array.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var // If the typed array is unspecified, use this. 4 | DefaultArrayType = Float32Array, 5 | // Simple math functions we need. 6 | sqrt = Math.sqrt, 7 | sqr = function (number) { 8 | return Math.pow(number, 2); 9 | }; 10 | 11 | export function isComplexArray(obj) { 12 | return ( 13 | obj !== undefined && obj.hasOwnProperty !== undefined && obj.hasOwnProperty("real") && obj.hasOwnProperty("imag") 14 | ); 15 | } 16 | 17 | export function ComplexArray(other, opt_array_type) { 18 | if (isComplexArray(other)) { 19 | // Copy constuctor. 20 | this.ArrayType = other.ArrayType; 21 | this.real = new this.ArrayType(other.real); 22 | this.imag = new this.ArrayType(other.imag); 23 | } else { 24 | this.ArrayType = opt_array_type || DefaultArrayType; 25 | // other can be either an array or a number. 26 | this.real = new this.ArrayType(other); 27 | this.imag = new this.ArrayType(this.real.length); 28 | } 29 | 30 | this.length = this.real.length; 31 | } 32 | 33 | ComplexArray.prototype.toString = function () { 34 | var components = []; 35 | 36 | this.forEach(function (c_value, i) { 37 | components.push("(" + c_value.real.toFixed(2) + "," + c_value.imag.toFixed(2) + ")"); 38 | }); 39 | 40 | return "[" + components.join(",") + "]"; 41 | }; 42 | 43 | // In-place mapper. 44 | ComplexArray.prototype.map = function (mapper) { 45 | var i, 46 | n = this.length, 47 | // For GC efficiency, pass a single c_value object to the mapper. 48 | c_value = {}; 49 | 50 | for (i = 0; i < n; i++) { 51 | c_value.real = this.real[i]; 52 | c_value.imag = this.imag[i]; 53 | mapper(c_value, i, n); 54 | this.real[i] = c_value.real; 55 | this.imag[i] = c_value.imag; 56 | } 57 | 58 | return this; 59 | }; 60 | 61 | ComplexArray.prototype.forEach = function (iterator) { 62 | var i, 63 | n = this.length, 64 | // For consistency with .map. 65 | c_value = {}; 66 | 67 | for (i = 0; i < n; i++) { 68 | c_value.real = this.real[i]; 69 | c_value.imag = this.imag[i]; 70 | iterator(c_value, i, n); 71 | } 72 | }; 73 | 74 | ComplexArray.prototype.conjugate = function () { 75 | return new ComplexArray(this).map(function (value) { 76 | value.imag *= -1; 77 | }); 78 | }; 79 | 80 | // Helper so we can make ArrayType objects returned have similar interfaces 81 | // to ComplexArrays. 82 | function iterable(obj) { 83 | if (!obj.forEach) 84 | obj.forEach = function (iterator) { 85 | var i, 86 | n = this.length; 87 | 88 | for (i = 0; i < n; i++) iterator(this[i], i, n); 89 | }; 90 | 91 | return obj; 92 | } 93 | 94 | ComplexArray.prototype.magnitude = function () { 95 | var mags = new this.ArrayType(this.length); 96 | 97 | this.forEach(function (value, i) { 98 | mags[i] = sqrt(sqr(value.real) + sqr(value.imag)); 99 | }); 100 | 101 | // ArrayType will not necessarily be iterable: make it so. 102 | return iterable(mags); 103 | }; 104 | -------------------------------------------------------------------------------- /site/src/docs/ListofLibs.mdx: -------------------------------------------------------------------------------- 1 | ## Existing web FFT libs 2 | 3 | ### Javascript-Based 4 | 5 | - fft.js aka indutny 6 | - [https://github.com/indutny/fft.js/](https://github.com/indutny/fft.js/) (last changed 2021) 7 | - What IQEngine used before WebFFT 8 | - fft-js aka vail 9 | - [https://www.npmjs.com/package/fft-js](https://www.npmjs.com/package/fft-js) 10 | - [https://github.com/vail-systems/node-fft](https://github.com/vail-systems/node-fft) (last changed 2019) 11 | - jsfft aka dntj 12 | - [https://github.com/dntj/jsfft](https://github.com/dntj/jsfft) (last changed 2019) 13 | - fourier-transform 14 | - [https://github.com/scijs/fourier-transform](https://github.com/scijs/fourier-transform) (last changed 2018) 15 | - real-valued inputs only 16 | - author compared it to several others [here](https://github.com/scijs/fourier-transform/blob/master/benchmark.md) 17 | - ndarray-fft 18 | - [https://github.com/scijs/ndarray-fft](https://github.com/scijs/ndarray-fft) (last changed 2016) 19 | - DSP.js 20 | - [https://github.com/corbanbrook/dsp.js](https://github.com/corbanbrook/dsp.js) (no longer maintained but last changed 2022) 21 | - digitalsignals (fork of DSP.js) 22 | - [https://github.com/zewemli/dsp.js](https://github.com/zewemli/dsp.js) (last changed 2014) 23 | - fourier 24 | - [https://github.com/drom/fourier](https://github.com/drom/fourier) (last changed 2021) 25 | - has separate functions for each FFT size, making it difficult to use/wrap 26 | - ml-fft 27 | - [https://github.com/mljs/fft](https://github.com/mljs/fft) (last changed 2020) 28 | - Nockert 29 | - [https://github.com/auroranockert/fft.js](https://github.com/auroranockert/fft.js) 30 | - not on npm 31 | 32 | ### WebAssembly-based 33 | 34 | - PulseFFT 35 | - [https://github.com/AWSM-WASM/PulseFFT](https://github.com/AWSM-WASM/PulseFFT) (last change in 2018) 36 | - KissFFT based 37 | - kissfft-wasm 38 | - [https://github.com/iVilja/kissfft-wasm](https://github.com/iVilja/kissfft-wasm) (last change Oct 2022) 39 | - KissFFT based 40 | - Mozilla's implementation used in webkit audio 41 | - appears to be a copy of kissFFT 42 | - [https://github.com/mozilla/gecko-dev/tree/6dd16cb77552c7cec8ab7e4e3b74ca7d5e320339/media/kiss_fft](https://github.com/mozilla/gecko-dev/tree/6dd16cb77552c7cec8ab7e4e3b74ca7d5e320339/media/kiss_fft) 43 | - fftw-js 44 | - FFTW (extremely popular for desktop) ported with webasm 45 | - [https://github.com/j-funk/fftw-js](https://github.com/j-funk/fftw-js) 46 | - GPL licensed!!! 47 | - pffft.wasm 48 | - [https://github.com/JorenSix/pffft.wasm](https://github.com/JorenSix/pffft.wasm) (last change June 2022) 49 | - SIMD support 50 | 51 | ### Other people's benchmarks and comparisons 52 | 53 | - [https://github.com/j-funk/js-dsp-test](https://github.com/j-funk/js-dsp-test) 54 | - [https://github.com/scijs/fourier-transform/blob/HEAD/benchmark.md](https://github.com/scijs/fourier-transform/blob/HEAD/benchmark.md) 55 | - [https://thebreakfastpost.com/2015/10/18/ffts-in-javascript/](https://thebreakfastpost.com/2015/10/18/ffts-in-javascript/) 56 | - [https://toughengineer.github.io/demo/dsp/fft-perf/](https://toughengineer.github.io/demo/dsp/fft-perf/) 57 | -------------------------------------------------------------------------------- /lib/kissfftmodified/kiss_fft.h: -------------------------------------------------------------------------------- 1 | #ifndef KISS_FFT_H 2 | #define KISS_FFT_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #ifdef __cplusplus 10 | extern "C" { 11 | #endif 12 | 13 | /* 14 | ATTENTION! 15 | If you would like a : 16 | -- a utility that will handle the caching of fft objects 17 | -- real-only (no imaginary time component ) FFT 18 | -- a multi-dimensional FFT 19 | -- a command-line utility to perform ffts 20 | -- a command-line utility to perform fast-convolution filtering 21 | 22 | Then see kfc.h kiss_fftr.h kiss_fftnd.h fftutil.c kiss_fastfir.c 23 | in the tools/ directory. 24 | */ 25 | 26 | #ifdef USE_SIMD 27 | # include 28 | # define kiss_fft_scalar __m128 29 | #define KISS_FFT_MALLOC(nbytes) _mm_malloc(nbytes,16) 30 | #define KISS_FFT_FREE _mm_free 31 | #else 32 | #define KISS_FFT_MALLOC malloc 33 | #define KISS_FFT_FREE free 34 | #endif 35 | 36 | 37 | #ifdef FIXED_POINT 38 | #include 39 | # if (FIXED_POINT == 32) 40 | # define kiss_fft_scalar int32_t 41 | # else 42 | # define kiss_fft_scalar int16_t 43 | # endif 44 | #else 45 | # ifndef kiss_fft_scalar 46 | /* default is float */ 47 | # define kiss_fft_scalar float 48 | # endif 49 | #endif 50 | 51 | typedef struct { 52 | kiss_fft_scalar r; 53 | kiss_fft_scalar i; 54 | }kiss_fft_cpx; 55 | 56 | typedef struct kiss_fft_state* kiss_fft_cfg; 57 | 58 | /* 59 | * kiss_fft_alloc 60 | * 61 | * Initialize a FFT (or IFFT) algorithm's cfg/state buffer. 62 | * 63 | * typical usage: kiss_fft_cfg mycfg=kiss_fft_alloc(1024,0,NULL,NULL); 64 | * 65 | * The return value from fft_alloc is a cfg buffer used internally 66 | * by the fft routine or NULL. 67 | * 68 | * If lenmem is NULL, then kiss_fft_alloc will allocate a cfg buffer using malloc. 69 | * The returned value should be free()d when done to avoid memory leaks. 70 | * 71 | * The state can be placed in a user supplied buffer 'mem': 72 | * If lenmem is not NULL and mem is not NULL and *lenmem is large enough, 73 | * then the function places the cfg in mem and the size used in *lenmem 74 | * and returns mem. 75 | * 76 | * If lenmem is not NULL and ( mem is NULL or *lenmem is not large enough), 77 | * then the function returns NULL and places the minimum cfg 78 | * buffer size in *lenmem. 79 | * */ 80 | 81 | kiss_fft_cfg kiss_fft_alloc(int nfft,int inverse_fft,void * mem,size_t * lenmem); 82 | 83 | /* 84 | * kiss_fft(cfg,in_out_buf) 85 | * 86 | * Perform an FFT on a complex input buffer. 87 | * for a forward FFT, 88 | * fin should be f[0] , f[1] , ... ,f[nfft-1] 89 | * fout will be F[0] , F[1] , ... ,F[nfft-1] 90 | * Note that each element is complex and can be accessed like 91 | f[k].r and f[k].i 92 | * */ 93 | void kiss_fft(kiss_fft_cfg cfg,const kiss_fft_cpx *fin,kiss_fft_cpx *fout); 94 | 95 | /* If kiss_fft_alloc allocated a buffer, it is one contiguous 96 | buffer and can be simply free()d when no longer needed*/ 97 | //#define kiss_fft_free free 98 | void kiss_fft_free(kiss_fft_cfg); 99 | 100 | 101 | #ifdef __cplusplus 102 | } 103 | #endif 104 | 105 | #endif 106 | -------------------------------------------------------------------------------- /site/src/components/Docs.tsx: -------------------------------------------------------------------------------- 1 | import { ReactElement, lazy, Suspense, useState } from "react"; 2 | import SiteHeader from "./SiteHeader"; 3 | import components from "./MarkdownComponents"; 4 | import { useRoutes, Link } from "react-router-dom"; 5 | 6 | const GettingStarted = lazy(() => import("../docs/GettingStarted.mdx")); 7 | const ListofLibs = lazy(() => import("../docs/ListofLibs.mdx")); 8 | 9 | function Docs(): ReactElement { 10 | let routes = useRoutes([ 11 | { 12 | path: "/", 13 | element: , 14 | }, 15 | { 16 | path: "/listoflibs", 17 | element: , 18 | }, 19 | // add more routes here 20 | ]); 21 | 22 | const [isNavVisible, setNavVisible] = useState(false); 23 | 24 | return ( 25 |
    26 | 27 | 35 |
    36 | 63 | 86 |
    87 | Loading...
    }>{routes} 88 |
    89 | 90 | 91 | ); 92 | } 93 | 94 | export default Docs; 95 | -------------------------------------------------------------------------------- /site/src/index.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | :root { 6 | font-family: "Orbitron", system-ui, Avenir, Helvetica, Arial, sans-serif; 7 | line-height: 1.5; 8 | font-weight: 400; 9 | 10 | color-scheme: light dark; 11 | color: rgba(255, 255, 255, 0.87); 12 | background-color: #242424; 13 | 14 | font-synthesis: none; 15 | text-rendering: optimizeLegibility; 16 | -webkit-font-smoothing: antialiased; 17 | -moz-osx-font-smoothing: grayscale; 18 | -webkit-text-size-adjust: 100%; 19 | } 20 | 21 | a { 22 | font-weight: 500; 23 | color: theme("colors.cyber-secondary"); 24 | text-decoration: inherit; 25 | } 26 | a:hover { 27 | color: #535bf2; 28 | } 29 | 30 | body { 31 | margin: 0; 32 | font-family: "Orbitron", "system-ui", sans-serif; 33 | background-color: #242424; /* fallback color */ 34 | background-image: linear-gradient( 35 | 45deg, 36 | #0b0b0b 60%, 37 | theme("colors.cyber-primary") 38 | ), 39 | radial-gradient(circle at 75% 75%, transparent 30%, #242424 70%); 40 | background-repeat: no-repeat, no-repeat, no-repeat; 41 | background-position: 42 | 0 0, 43 | bottom left, 44 | bottom left; 45 | background-size: 46 | 100% 100%, 47 | 30% 30%, 48 | 100% 100%; 49 | background-blend-mode: normal, screen, normal; 50 | } 51 | 52 | h1 { 53 | font-size: 3.2em; 54 | line-height: 1.1; 55 | } 56 | 57 | button { 58 | border-radius: 8px; 59 | border: 1px solid transparent; 60 | padding: 0.6em 1.2em; 61 | font-size: 1em; 62 | font-weight: 500; 63 | font-family: inherit; 64 | background-color: #1a1a1a; 65 | cursor: pointer; 66 | transition: border-color theme("colors.cyber-secondary"); 67 | } 68 | button:hover { 69 | border-color: theme("colors.cyber-secondary"); 70 | } 71 | button:focus, 72 | button:focus-visible { 73 | outline: 4px auto -webkit-focus-ring-color; 74 | } 75 | 76 | @media (prefers-color-scheme: light) { 77 | :root { 78 | color: #213547; 79 | background-color: #ffffff; 80 | } 81 | a:hover { 82 | color: #747bff; 83 | } 84 | button { 85 | background-color: #f9f9f9; 86 | } 87 | } 88 | 89 | /* sm: apply styles for screens ≥640px */ 90 | @media (min-width: 640px) { 91 | span, 92 | body, 93 | p, 94 | label { 95 | @apply text-base; 96 | } 97 | h1 { 98 | @apply text-4xl; 99 | } 100 | h2 { 101 | @apply text-3xl; 102 | } 103 | h3 { 104 | @apply text-2xl; 105 | } 106 | } 107 | 108 | /* md: apply styles for screens ≥768px */ 109 | @media (min-width: 768px) { 110 | span, 111 | body, 112 | p, 113 | label { 114 | @apply text-xl; 115 | } 116 | h1 { 117 | @apply text-5xl; 118 | } 119 | h2 { 120 | @apply text-4xl; 121 | } 122 | h3 { 123 | @apply text-3xl; 124 | } 125 | } 126 | 127 | /* lg: apply styles for screens ≥1024px */ 128 | @media (min-width: 1024px) { 129 | span, 130 | body, 131 | p, 132 | label { 133 | @apply text-xl; 134 | } 135 | h1 { 136 | @apply text-6xl; 137 | } 138 | h2 { 139 | @apply text-5xl; 140 | } 141 | h3 { 142 | @apply text-4xl; 143 | } 144 | } 145 | 146 | /* Chrome */ 147 | @media screen and (-webkit-min-device-pixel-ratio: 0) { 148 | body { 149 | -webkit-text-size-adjust: 100%; 150 | } 151 | } 152 | 153 | /* Firefox */ 154 | @-moz-document url-prefix() { 155 | body { 156 | -moz-text-size-adjust: 100%; 157 | text-size-adjust: 100%; 158 | } 159 | } 160 | -------------------------------------------------------------------------------- /site/src/components/LinksSection.tsx: -------------------------------------------------------------------------------- 1 | import GitHubMark from "/assets/github-mark/github-mark-white.svg"; 2 | import NpmLogo from "/assets/npm-logo-red.svg"; 3 | import { Link } from "react-router-dom"; 4 | 5 | function LinksSection() { 6 | return ( 7 |
    8 | 34 |
    35 | 41 | Documentation 42 | 43 | 49 | About 50 | 51 |
    52 |
    53 |
    54 |
    55 | 56 | 1d, 2d, complex, real 57 |
    58 |
    59 | 60 | Auto-optimized FFT algorithms 61 |
    62 |
    63 | 64 | Browser-specific optimizations 65 |
    66 |
    67 | 68 | SIMD WASM support 69 |
    70 |
    71 | 72 | Peak performance assurance 73 |
    74 |
    75 | 76 | Easy integration 77 |
    78 |
    79 |
    80 |
    81 | ); 82 | } 83 | 84 | export default LinksSection; 85 | -------------------------------------------------------------------------------- /site/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 20 | 25 | 31 | 37 | 38 | 43 | 44 | 48 | 52 | 56 | 60 | 64 | 65 | 66 | 70 | 74 | 78 | 79 | 83 | 84 | 85 | WebFFT 86 | 87 | 88 |
    89 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /lib/kissfft/kiss_fft.h: -------------------------------------------------------------------------------- 1 | #ifndef KISS_FFT_H 2 | #define KISS_FFT_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #ifdef __cplusplus 10 | extern "C" { 11 | #endif 12 | 13 | /* 14 | ATTENTION! 15 | If you would like a : 16 | -- a utility that will handle the caching of fft objects 17 | -- real-only (no imaginary time component ) FFT 18 | -- a multi-dimensional FFT 19 | -- a command-line utility to perform ffts 20 | -- a command-line utility to perform fast-convolution filtering 21 | 22 | Then see kfc.h kiss_fftr.h kiss_fftnd.h fftutil.c kiss_fastfir.c 23 | in the tools/ directory. 24 | */ 25 | 26 | #ifdef USE_SIMD 27 | # include 28 | # define kiss_fft_scalar __m128 29 | #define KISS_FFT_MALLOC(nbytes) _mm_malloc(nbytes,16) 30 | #define KISS_FFT_FREE _mm_free 31 | #else 32 | #define KISS_FFT_MALLOC malloc 33 | #define KISS_FFT_FREE free 34 | #endif 35 | 36 | 37 | #ifdef FIXED_POINT 38 | #include 39 | # if (FIXED_POINT == 32) 40 | # define kiss_fft_scalar int32_t 41 | # else 42 | # define kiss_fft_scalar int16_t 43 | # endif 44 | #else 45 | # ifndef kiss_fft_scalar 46 | /* default is float */ 47 | # define kiss_fft_scalar float 48 | # endif 49 | #endif 50 | 51 | typedef struct { 52 | kiss_fft_scalar r; 53 | kiss_fft_scalar i; 54 | }kiss_fft_cpx; 55 | 56 | typedef struct kiss_fft_state* kiss_fft_cfg; 57 | 58 | /* 59 | * kiss_fft_alloc 60 | * 61 | * Initialize a FFT (or IFFT) algorithm's cfg/state buffer. 62 | * 63 | * typical usage: kiss_fft_cfg mycfg=kiss_fft_alloc(1024,0,NULL,NULL); 64 | * 65 | * The return value from fft_alloc is a cfg buffer used internally 66 | * by the fft routine or NULL. 67 | * 68 | * If lenmem is NULL, then kiss_fft_alloc will allocate a cfg buffer using malloc. 69 | * The returned value should be free()d when done to avoid memory leaks. 70 | * 71 | * The state can be placed in a user supplied buffer 'mem': 72 | * If lenmem is not NULL and mem is not NULL and *lenmem is large enough, 73 | * then the function places the cfg in mem and the size used in *lenmem 74 | * and returns mem. 75 | * 76 | * If lenmem is not NULL and ( mem is NULL or *lenmem is not large enough), 77 | * then the function returns NULL and places the minimum cfg 78 | * buffer size in *lenmem. 79 | * */ 80 | 81 | kiss_fft_cfg kiss_fft_alloc(int nfft,int inverse_fft,void * mem,size_t * lenmem); 82 | 83 | /* 84 | * kiss_fft(cfg,in_out_buf) 85 | * 86 | * Perform an FFT on a complex input buffer. 87 | * for a forward FFT, 88 | * fin should be f[0] , f[1] , ... ,f[nfft-1] 89 | * fout will be F[0] , F[1] , ... ,F[nfft-1] 90 | * Note that each element is complex and can be accessed like 91 | f[k].r and f[k].i 92 | * */ 93 | void kiss_fft(kiss_fft_cfg cfg,const kiss_fft_cpx *fin,kiss_fft_cpx *fout); 94 | 95 | /* 96 | A more generic version of the above function. It reads its input from every Nth sample. 97 | * */ 98 | void kiss_fft_stride(kiss_fft_cfg cfg,const kiss_fft_cpx *fin,kiss_fft_cpx *fout,int fin_stride); 99 | 100 | /* If kiss_fft_alloc allocated a buffer, it is one contiguous 101 | buffer and can be simply free()d when no longer needed*/ 102 | //#define kiss_fft_free free 103 | void kiss_fft_free(kiss_fft_cfg); 104 | 105 | /* 106 | Cleans up some memory that gets managed internally. Not necessary to call, but it might clean up 107 | your compiler output to call this before you exit. 108 | */ 109 | void kiss_fft_cleanup(void); 110 | 111 | 112 | /* 113 | * Returns the smallest integer k, such that k>=n and k has only "fast" factors (2,3,5) 114 | */ 115 | int kiss_fft_next_fast_size(int n); 116 | 117 | /* for real ffts, we need an even size */ 118 | #define kiss_fftr_next_fast_size_real(n) \ 119 | (kiss_fft_next_fast_size( ((n)+1)>>1)<<1) 120 | 121 | #ifdef __cplusplus 122 | } 123 | #endif 124 | 125 | #endif 126 | -------------------------------------------------------------------------------- /lib/vailNOTFINISHED/fft.js: -------------------------------------------------------------------------------- 1 | /*===========================================================================*\ 2 | * Fast Fourier Transform (Cooley-Tukey Method) 3 | * 4 | * (c) Vail Systems. Joshua Jung and Ben Bryan. 2015 5 | * 6 | * This code is not designed to be highly optimized but as an educational 7 | * tool to understand the Fast Fourier Transform. 8 | \*===========================================================================*/ 9 | 10 | //------------------------------------------------ 11 | // Note: Some of this code is not optimized and is 12 | // primarily designed as an educational and testing 13 | // tool. 14 | // To get high performace would require transforming 15 | // the recursive calls into a loop and then loop 16 | // unrolling. All of this is best accomplished 17 | // in C or assembly. 18 | //------------------------------------------------- 19 | 20 | //------------------------------------------------- 21 | // The following code assumes a complex number is 22 | // an array: [real, imaginary] 23 | //------------------------------------------------- 24 | import complex from "./complex.cjs"; 25 | import fftUtil from "./fftutil.cjs"; 26 | import twiddle from "./twiddle.cjs"; 27 | 28 | var fft = { 29 | //------------------------------------------------- 30 | // Calculate FFT for vector where vector.length 31 | // is assumed to be a power of 2. 32 | //------------------------------------------------- 33 | fft: function fft(vector) { 34 | var X = [], 35 | N = vector.length; 36 | 37 | // Base case is X = x + 0i since our input is assumed to be real only. 38 | if (N == 1) { 39 | if (Array.isArray(vector[0])) { 40 | //If input vector contains complex numbers 41 | return [[vector[0][0], vector[0][1]]]; 42 | } else return [[vector[0], 0]]; 43 | } 44 | 45 | // Recurse: all even samples 46 | var X_evens = fft(vector.filter(even)), 47 | // Recurse: all odd samples 48 | X_odds = fft(vector.filter(odd)); 49 | 50 | // Now, perform N/2 operations! 51 | for (var k = 0; k < N / 2; k++) { 52 | // t is a complex number! 53 | var t = X_evens[k], 54 | e = complex.multiply(fftUtil.exponent(k, N), X_odds[k]); 55 | 56 | X[k] = complex.add(t, e); 57 | X[k + N / 2] = complex.subtract(t, e); 58 | } 59 | 60 | function even(__, ix) { 61 | return ix % 2 == 0; 62 | } 63 | 64 | function odd(__, ix) { 65 | return ix % 2 == 1; 66 | } 67 | 68 | return X; 69 | }, 70 | //------------------------------------------------- 71 | // Calculate FFT for vector where vector.length 72 | // is assumed to be a power of 2. This is the in- 73 | // place implementation, to avoid the memory 74 | // footprint used by recursion. 75 | //------------------------------------------------- 76 | fftInPlace: function (vector) { 77 | var N = vector.length; 78 | 79 | var trailingZeros = twiddle.countTrailingZeros(N); //Once reversed, this will be leading zeros 80 | 81 | // Reverse bits 82 | for (var k = 0; k < N; k++) { 83 | var p = twiddle.reverse(k) >>> (twiddle.INT_BITS - trailingZeros); 84 | if (p > k) { 85 | var complexTemp = [vector[k], 0]; 86 | vector[k] = vector[p]; 87 | vector[p] = complexTemp; 88 | } else { 89 | vector[p] = [vector[p], 0]; 90 | } 91 | } 92 | 93 | //Do the DIT now in-place 94 | for (var len = 2; len <= N; len += len) { 95 | for (var i = 0; i < len / 2; i++) { 96 | var w = fftUtil.exponent(i, len); 97 | for (var j = 0; j < N / len; j++) { 98 | var t = complex.multiply(w, vector[j * len + i + len / 2]); 99 | vector[j * len + i + len / 2] = complex.subtract(vector[j * len + i], t); 100 | vector[j * len + i] = complex.add(vector[j * len + i], t); 101 | } 102 | } 103 | } 104 | } 105 | }; 106 | 107 | export default fft; 108 | -------------------------------------------------------------------------------- /site/src/components/ResultsSection.tsx: -------------------------------------------------------------------------------- 1 | import { Bar } from "react-chartjs-2"; 2 | import { 3 | Chart as ChartJS, 4 | CategoryScale, 5 | LinearScale, 6 | BarElement, 7 | LineController, 8 | PointElement, 9 | Title, 10 | Tooltip, 11 | Legend, 12 | LineElement, 13 | } from "chart.js"; 14 | import { BallTriangle } from "react-loader-spinner"; 15 | 16 | // Registering the components 17 | ChartJS.register( 18 | CategoryScale, 19 | LinearScale, 20 | BarElement, 21 | LineController, 22 | LineElement, 23 | PointElement, 24 | Title, 25 | Tooltip, 26 | Legend, 27 | ); 28 | 29 | interface Props { 30 | benchmarkData: any; 31 | loading: boolean; 32 | } 33 | 34 | function ResultsSection({ benchmarkData, loading }: Props) { 35 | // Modify and sort benchmark data before using it in the chart 36 | if (benchmarkData) { 37 | benchmarkData.labels = benchmarkData.labels.map((label: string) => 38 | label.replace(/(javascript|wasm)/gi, ""), 39 | ); 40 | } 41 | 42 | const options = { 43 | responsive: true, 44 | maintainAspectRatio: false, 45 | elements: { 46 | bar: { 47 | borderWidth: 5, 48 | }, 49 | }, 50 | plugins: { 51 | legend: { 52 | position: "top" as const, 53 | display: true, 54 | }, 55 | title: { 56 | display: false, 57 | text: "Test Results", 58 | }, 59 | }, 60 | scales: { 61 | y: { 62 | grid: { 63 | display: true, 64 | color: `hsla(0, 0%, 80%, 0.9)`, 65 | }, 66 | title: { 67 | display: true, 68 | font: { 69 | size: 16, 70 | }, 71 | color: `hsla(0, 0%, 80%, 0.9)`, 72 | text: "FFTs per Second", 73 | }, 74 | }, 75 | x: { 76 | stacked: true, 77 | grid: { 78 | display: true, 79 | offset: true, 80 | color: `hsla(0, 0%, 80%, 0.9)`, 81 | }, 82 | title: { 83 | display: true, 84 | font: { 85 | size: 16, 86 | }, 87 | color: `hsla(0, 0%, 80%, 0.9)`, 88 | text: "FFT Algorithms", 89 | }, 90 | ticks: { 91 | display: true, 92 | font: { 93 | size: 10, 94 | }, 95 | color: `hsla(0, 0%, 80%, 0.9)`, 96 | }, 97 | }, 98 | }, 99 | }; 100 | 101 | return ( 102 |
    103 |
    104 | {(loading || benchmarkData) && ( 105 |

    109 | Results 110 |

    111 | )} 112 | 113 | {loading && ( 114 | 123 | )} 124 | 125 | {benchmarkData && ( 126 |
    127 |
    128 | 135 |
    136 |
    137 | )} 138 | 139 | {/* Display table of all of the results here */} 140 | {/* Assuming you will populate the table with data fetched after the benchmarking */} 141 |
    142 |
    143 | ); 144 | } 145 | 146 | export default ResultsSection; 147 | -------------------------------------------------------------------------------- /site/public/assets/react.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lib/nayukic/fft.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Free FFT and convolution (C) 3 | * 4 | * Copyright (c) 2014 Project Nayuki 5 | * http://www.nayuki.io/page/free-small-fft-in-multiple-languages 6 | * 7 | * (MIT License) 8 | * Permission is hereby granted, free of charge, to any person obtaining a copy of 9 | * this software and associated documentation files (the "Software"), to deal in 10 | * the Software without restriction, including without limitation the rights to 11 | * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 12 | * the Software, and to permit persons to whom the Software is furnished to do so, 13 | * subject to the following conditions: 14 | * - The above copyright notice and this permission notice shall be included in 15 | * all copies or substantial portions of the Software. 16 | * - The Software is provided "as is", without warranty of any kind, express or 17 | * implied, including but not limited to the warranties of merchantability, 18 | * fitness for a particular purpose and noninfringement. In no event shall the 19 | * authors or copyright holders be liable for any claim, damages or other 20 | * liability, whether in an action of contract, tort or otherwise, arising from, 21 | * out of or in connection with the Software or the use or other dealings in the 22 | * Software. 23 | */ 24 | 25 | 26 | /* 27 | * Computes the discrete Fourier transform (DFT) of the given complex vector, storing the result back into the vector. 28 | * The vector can have any length. This is a wrapper function. Returns 1 (true) if successful, 0 (false) otherwise (out of memory). 29 | */ 30 | int transform(double real[], double imag[], size_t n); 31 | 32 | /* 33 | * Computes the inverse discrete Fourier transform (IDFT) of the given complex vector, storing the result back into the vector. 34 | * The vector can have any length. This is a wrapper function. This transform does not perform scaling, so the inverse is not a true inverse. 35 | * Returns 1 (true) if successful, 0 (false) otherwise (out of memory). 36 | */ 37 | int inverse_transform(double real[], double imag[], size_t n); 38 | 39 | /* 40 | * Computes the discrete Fourier transform (DFT) of the given complex vector, storing the result back into the vector. 41 | * The vector's length must be a power of 2. Uses the Cooley-Tukey decimation-in-time radix-2 algorithm. 42 | * Returns 1 (true) if successful, 0 (false) otherwise (n is not a power of 2, or out of memory). 43 | */ 44 | int transform_radix2(double real[], double imag[], size_t n); 45 | 46 | /* Test versions with precalculated structures -- this API is 47 | absolutely not for production use! */ 48 | typedef struct { 49 | double *cos; 50 | double *sin; 51 | int levels; 52 | } tables; 53 | 54 | tables *precalc(size_t n); 55 | void dispose(tables *); 56 | void transform_radix2_precalc(double real[], double imag[], int n, tables *tables); 57 | 58 | typedef struct { 59 | float *cos; 60 | float *sin; 61 | int levels; 62 | } tables_f; 63 | 64 | tables_f *precalc_f(size_t n); 65 | void dispose_f(tables_f *); 66 | void transform_radix2_precalc_f(float real[], float imag[], int n, tables_f *tables); 67 | 68 | /* 69 | * Computes the discrete Fourier transform (DFT) of the given complex vector, storing the result back into the vector. 70 | * The vector can have any length. This requires the convolution function, which in turn requires the radix-2 FFT function. 71 | * Uses Bluestein's chirp z-transform algorithm. Returns 1 (true) if successful, 0 (false) otherwise (out of memory). 72 | */ 73 | int transform_bluestein(double real[], double imag[], size_t n); 74 | 75 | /* 76 | * Computes the circular convolution of the given real vectors. Each vector's length must be the same. 77 | * Returns 1 (true) if successful, 0 (false) otherwise (out of memory). 78 | */ 79 | int convolve_real(const double x[], const double y[], double out[], size_t n); 80 | 81 | /* 82 | * Computes the circular convolution of the given complex vectors. Each vector's length must be the same. 83 | * Returns 1 (true) if successful, 0 (false) otherwise (out of memory). 84 | */ 85 | int convolve_complex(const double xreal[], const double ximag[], const double yreal[], const double yimag[], double outreal[], double outimag[], size_t n); 86 | 87 | -------------------------------------------------------------------------------- /lib/nayuki/fft.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Free FFT and convolution (JavaScript) 3 | * 4 | * Copyright (c) 2014 Project Nayuki 5 | * http://www.nayuki.io/page/free-small-fft-in-multiple-languages 6 | * 7 | * (MIT License) 8 | * Permission is hereby granted, free of charge, to any person obtaining a copy of 9 | * this software and associated documentation files (the "Software"), to deal in 10 | * the Software without restriction, including without limitation the rights to 11 | * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 12 | * the Software, and to permit persons to whom the Software is furnished to do so, 13 | * subject to the following conditions: 14 | * - The above copyright notice and this permission notice shall be included in 15 | * all copies or substantial portions of the Software. 16 | * - The Software is provided "as is", without warranty of any kind, express or 17 | * implied, including but not limited to the warranties of merchantability, 18 | * fitness for a particular purpose and noninfringement. In no event shall the 19 | * authors or copyright holders be liable for any claim, damages or other 20 | * liability, whether in an action of contract, tort or otherwise, arising from, 21 | * out of or in connection with the Software or the use or other dealings in the 22 | * Software. 23 | * 24 | * Slightly restructured by Chris Cannam, cannam@all-day-breakfast.com 25 | */ 26 | 27 | "use strict"; 28 | 29 | /* 30 | * Construct an object for calculating the discrete Fourier transform (DFT) of size n, where n is a power of 2. 31 | */ 32 | function FFTNayuki(n) { 33 | this.n = n; 34 | this.levels = -1; 35 | 36 | for (var i = 0; i < 32; i++) { 37 | if (1 << i == n) { 38 | this.levels = i; // Equal to log2(n) 39 | } 40 | } 41 | if (this.levels == -1) { 42 | throw "Length is not a power of 2"; 43 | } 44 | 45 | this.cosTable = new Array(n / 2); 46 | this.sinTable = new Array(n / 2); 47 | for (var i = 0; i < n / 2; i++) { 48 | this.cosTable[i] = Math.cos((2 * Math.PI * i) / n); 49 | this.sinTable[i] = Math.sin((2 * Math.PI * i) / n); 50 | } 51 | 52 | /* 53 | * Computes the discrete Fourier transform (DFT) of the given complex vector, storing the result back into the vector. 54 | * The vector's length must be equal to the size n that was passed to the object constructor, and this must be a power of 2. Uses the Cooley-Tukey decimation-in-time radix-2 algorithm. 55 | */ 56 | this.forward = function (real, imag) { 57 | var n = this.n; 58 | 59 | // Bit-reversed addressing permutation 60 | for (var i = 0; i < n; i++) { 61 | var j = reverseBits(i, this.levels); 62 | if (j > i) { 63 | var temp = real[i]; 64 | real[i] = real[j]; 65 | real[j] = temp; 66 | temp = imag[i]; 67 | imag[i] = imag[j]; 68 | imag[j] = temp; 69 | } 70 | } 71 | 72 | // Cooley-Tukey decimation-in-time radix-2 FFT 73 | for (var size = 2; size <= n; size *= 2) { 74 | var halfsize = size / 2; 75 | var tablestep = n / size; 76 | for (var i = 0; i < n; i += size) { 77 | for (var j = i, k = 0; j < i + halfsize; j++, k += tablestep) { 78 | var tpre = real[j + halfsize] * this.cosTable[k] + imag[j + halfsize] * this.sinTable[k]; 79 | var tpim = -real[j + halfsize] * this.sinTable[k] + imag[j + halfsize] * this.cosTable[k]; 80 | real[j + halfsize] = real[j] - tpre; 81 | imag[j + halfsize] = imag[j] - tpim; 82 | real[j] += tpre; 83 | imag[j] += tpim; 84 | } 85 | } 86 | } 87 | 88 | // Returns the integer whose value is the reverse of the lowest 'bits' bits of the integer 'x'. 89 | function reverseBits(x, bits) { 90 | var y = 0; 91 | for (var i = 0; i < bits; i++) { 92 | y = (y << 1) | (x & 1); 93 | x >>>= 1; 94 | } 95 | return y; 96 | } 97 | }; 98 | 99 | /* 100 | * Computes the inverse discrete Fourier transform (IDFT) of the given complex vector, storing the result back into the vector. 101 | * The vector's length must be equal to the size n that was passed to the object constructor, and this must be a power of 2. This is a wrapper function. This transform does not perform scaling, so the inverse is not a true inverse. 102 | */ 103 | this.inverse = function (real, imag) { 104 | forward(imag, real); 105 | }; 106 | } 107 | 108 | export default FFTNayuki; 109 | -------------------------------------------------------------------------------- /site/src/components/About.tsx: -------------------------------------------------------------------------------- 1 | import SiteHeader from "./SiteHeader"; 2 | 3 | function About() { 4 | return ( 5 |
    6 | 7 |
    8 |

    12 | {" "} 13 | About WebFFT 14 |

    15 | 16 |

    17 | WebFFT started as part of a Microsoft internal Hackathon in 2023, 18 | motivated by the lack of actively maintained web-based FFT libraries{" "} 19 |
    20 | and interest in applying new WebSIMD technology to the problem. 21 |

    22 | 23 |

    24 |
    25 | 35 | 45 | 55 | 65 | 75 | 85 | 95 | 105 |
    106 | 107 |

    108 |

    109 | The project is currently maintained by the{" "} 110 | IQEngine{" "} 111 | organization, as it is heavily used within IQEngine. 112 |

    113 |

    114 |

    WebFFT is licensed under the MIT License and welcomes Issues/PRs!

    115 |
    116 |
    117 | ); 118 | } 119 | 120 | export default About; 121 | -------------------------------------------------------------------------------- /lib/mljs/fftlib.js: -------------------------------------------------------------------------------- 1 | // core operations 2 | let _n = 0; // order 3 | let _bitrev = null; // bit reversal table 4 | let _cstb = null; // sin/cos table 5 | 6 | function init(n) { 7 | if (n !== 0 && (n & (n - 1)) === 0) { 8 | _n = n; 9 | _initArray(); 10 | _makeBitReversalTable(); 11 | _makeCosSinTable(); 12 | } else { 13 | throw new Error("init: radix-2 required"); 14 | } 15 | } 16 | 17 | // 1D-FFT 18 | function fft1d(re, im) { 19 | fastFourierTransform(re, im, 1); 20 | } 21 | 22 | // 1D-IFFT 23 | function ifft1d(re, im) { 24 | let n = 1 / _n; 25 | fastFourierTransform(re, im, -1); 26 | for (let i = 0; i < _n; i++) { 27 | re[i] *= n; 28 | im[i] *= n; 29 | } 30 | } 31 | 32 | // 1D-IFFT 33 | function bt1d(re, im) { 34 | fastFourierTransform(re, im, -1); 35 | } 36 | 37 | // 2D-FFT Not very useful if the number of rows have to be equal to cols 38 | function fft2d(re, im) { 39 | let tre = []; 40 | let tim = []; 41 | let i = 0; 42 | // x-axis 43 | for (let y = 0; y < _n; y++) { 44 | i = y * _n; 45 | for (let x1 = 0; x1 < _n; x1++) { 46 | tre[x1] = re[x1 + i]; 47 | tim[x1] = im[x1 + i]; 48 | } 49 | fft1d(tre, tim); 50 | for (let x2 = 0; x2 < _n; x2++) { 51 | re[x2 + i] = tre[x2]; 52 | im[x2 + i] = tim[x2]; 53 | } 54 | } 55 | // y-axis 56 | for (let x = 0; x < _n; x++) { 57 | for (let y1 = 0; y1 < _n; y1++) { 58 | i = x + y1 * _n; 59 | tre[y1] = re[i]; 60 | tim[y1] = im[i]; 61 | } 62 | fft1d(tre, tim); 63 | for (let y2 = 0; y2 < _n; y2++) { 64 | i = x + y2 * _n; 65 | re[i] = tre[y2]; 66 | im[i] = tim[y2]; 67 | } 68 | } 69 | } 70 | 71 | // 2D-IFFT 72 | function ifft2d(re, im) { 73 | let tre = []; 74 | let tim = []; 75 | let i = 0; 76 | // x-axis 77 | for (let y = 0; y < _n; y++) { 78 | i = y * _n; 79 | for (let x1 = 0; x1 < _n; x1++) { 80 | tre[x1] = re[x1 + i]; 81 | tim[x1] = im[x1 + i]; 82 | } 83 | ifft1d(tre, tim); 84 | for (let x2 = 0; x2 < _n; x2++) { 85 | re[x2 + i] = tre[x2]; 86 | im[x2 + i] = tim[x2]; 87 | } 88 | } 89 | // y-axis 90 | for (let x = 0; x < _n; x++) { 91 | for (let y1 = 0; y1 < _n; y1++) { 92 | i = x + y1 * _n; 93 | tre[y1] = re[i]; 94 | tim[y1] = im[i]; 95 | } 96 | ifft1d(tre, tim); 97 | for (let y2 = 0; y2 < _n; y2++) { 98 | i = x + y2 * _n; 99 | re[i] = tre[y2]; 100 | im[i] = tim[y2]; 101 | } 102 | } 103 | } 104 | 105 | // core operation of FFT 106 | function fastFourierTransform(re, im, inv) { 107 | let d; 108 | let h; 109 | let ik; 110 | let m; 111 | let tmp; 112 | let wr; 113 | let wi; 114 | let xr; 115 | let xi; 116 | let n4 = _n >> 2; 117 | // bit reversal 118 | for (let l = 0; l < _n; l++) { 119 | m = _bitrev[l]; 120 | if (l < m) { 121 | tmp = re[l]; 122 | re[l] = re[m]; 123 | re[m] = tmp; 124 | tmp = im[l]; 125 | im[l] = im[m]; 126 | im[m] = tmp; 127 | } 128 | } 129 | // butterfly operation 130 | for (let k = 1; k < _n; k <<= 1) { 131 | h = 0; 132 | d = _n / (k << 1); 133 | for (let j = 0; j < k; j++) { 134 | wr = _cstb[h + n4]; 135 | wi = inv * _cstb[h]; 136 | for (let i = j; i < _n; i += k << 1) { 137 | ik = i + k; 138 | xr = wr * re[ik] + wi * im[ik]; 139 | xi = wr * im[ik] - wi * re[ik]; 140 | re[ik] = re[i] - xr; 141 | re[i] += xr; 142 | im[ik] = im[i] - xi; 143 | im[i] += xi; 144 | } 145 | h += d; 146 | } 147 | } 148 | } 149 | 150 | // initialize the array (supports TypedArray) 151 | function _initArray() { 152 | if (typeof Uint32Array !== "undefined") { 153 | _bitrev = new Uint32Array(_n); 154 | } else { 155 | _bitrev = []; 156 | } 157 | if (typeof Float64Array !== "undefined") { 158 | _cstb = new Float64Array(_n * 1.25); 159 | } else { 160 | _cstb = []; 161 | } 162 | } 163 | 164 | //function _paddingZero() { 165 | // // TODO 166 | //} 167 | 168 | function _makeBitReversalTable() { 169 | let i = 0; 170 | let j = 0; 171 | let k = 0; 172 | _bitrev[0] = 0; 173 | while (++i < _n) { 174 | k = _n >> 1; 175 | while (k <= j) { 176 | j -= k; 177 | k >>= 1; 178 | } 179 | j += k; 180 | _bitrev[i] = j; 181 | } 182 | } 183 | 184 | // makes trigonometric function table 185 | function _makeCosSinTable() { 186 | let n2 = _n >> 1; 187 | let n4 = _n >> 2; 188 | let n8 = _n >> 3; 189 | let n2p4 = n2 + n4; 190 | let t = Math.sin(Math.PI / _n); 191 | let dc = 2 * t * t; 192 | let ds = Math.sqrt(dc * (2 - dc)); 193 | let c = (_cstb[n4] = 1); 194 | let s = (_cstb[0] = 0); 195 | t = 2 * dc; 196 | for (let i = 1; i < n8; i++) { 197 | c -= dc; 198 | dc += t * c; 199 | s += ds; 200 | ds -= t * s; 201 | _cstb[i] = s; 202 | _cstb[n4 - i] = c; 203 | } 204 | if (n8 !== 0) { 205 | _cstb[n8] = Math.sqrt(0.5); 206 | } 207 | for (let j = 0; j < n4; j++) { 208 | _cstb[n2 - j] = _cstb[j]; 209 | } 210 | for (let k = 0; k < n2p4; k++) { 211 | _cstb[k + n2] = -_cstb[k]; 212 | } 213 | } 214 | 215 | const FFT = { 216 | init, 217 | fft1d, 218 | ifft1d, 219 | fft2d, 220 | ifft2d, 221 | fft: fft1d, 222 | ifft: ifft1d, 223 | bt: bt1d 224 | }; 225 | 226 | export default FFT; 227 | -------------------------------------------------------------------------------- /lib/utils/checkCapabilities.js: -------------------------------------------------------------------------------- 1 | // The following SIMD checkers were taken from 'wasm-feature-detect' NPM package: 2 | async function relaxedSimd() { 3 | return await WebAssembly.validate( 4 | new Uint8Array([ 5 | 0, 97, 115, 109, 1, 0, 0, 0, 1, 5, 1, 96, 0, 1, 123, 3, 2, 1, 0, 10, 15, 6 | 1, 13, 0, 65, 1, 253, 15, 65, 2, 253, 15, 253, 128, 2, 11, 7 | ]), 8 | ); 9 | } 10 | 11 | async function simd() { 12 | return await WebAssembly.validate( 13 | new Uint8Array([ 14 | 0, 97, 115, 109, 1, 0, 0, 0, 1, 5, 1, 96, 0, 1, 123, 3, 2, 1, 0, 10, 10, 15 | 1, 8, 0, 65, 0, 253, 15, 253, 98, 11, 16 | ]), 17 | ); 18 | } 19 | 20 | // Define the async function to check for Wasm and SIMD support 21 | async function checkBrowserCapabilities() { 22 | let browserName = "Other"; 23 | let browserVersion = "Unknown"; 24 | let osName = "Other"; 25 | let osVersion = "Unknown"; 26 | let uad = navigator.userAgentData; 27 | let ua = navigator.userAgent; 28 | 29 | // Check if Edge, Chrome, or Opera (uses User Agent Data API) 30 | try { 31 | if (uad) { 32 | const values = await uad.getHighEntropyValues([ 33 | "architecture", 34 | "model", 35 | "platform", 36 | "platformVersion", 37 | "uaFullVersion", 38 | ]); 39 | 40 | const brandInfo = uad.brands.find((brand) => 41 | ["Microsoft Edge", "Google Chrome", "Opera"].includes(brand.brand), 42 | ); 43 | browserName = brandInfo ? brandInfo.brand : "Other"; 44 | browserVersion = brandInfo ? `v${brandInfo.version}` : "Unknown"; 45 | osName = values.platform ? values.platform : "Other"; 46 | osVersion = values.platformVersion 47 | ? `v${values.platformVersion}` 48 | : "Unknown"; 49 | } 50 | 51 | // user agent string parsing for mobile and firefox, safari 52 | // other browsers included in case mobile 53 | if (browserName === "Other" || osName === "Other") { 54 | const uaArr = ua.split(" "); 55 | const uaBrowser = uaArr[uaArr.length - 1]; 56 | const isFirefox = /Firefox/.test(uaBrowser); 57 | // CriOS is Chrome for iOS and Chrome is Chrome for Android 58 | const isSafari = 59 | /Safari/.test(uaBrowser) && 60 | !/CriOS/.test(uaBrowser) && 61 | !/Chrome/.test(uaBrowser); 62 | const isChrome = /CriOS/.test(uaBrowser) || /Chrome/.test(uaBrowser); 63 | const isEdge = /Edg/.test(uaBrowser); 64 | const isOpera = /OPR/.test(uaBrowser); 65 | 66 | const browsers = [ 67 | { 68 | name: "Mozilla Firefox", 69 | regex: /Firefox\/(\d+\.\d+)/, 70 | flag: isFirefox, 71 | }, 72 | { name: "Safari", regex: /Version\/(\d+\.\d+)/, flag: isSafari }, 73 | { 74 | name: "Google Chrome", 75 | regex: /CriOS|Chrome\/(\d+\.\d+)/, 76 | flag: isChrome, 77 | }, 78 | { name: "Microsoft Edge", regex: /Edg\/(\d+\.\d+)/, flag: isEdge }, 79 | { name: "Opera", regex: /OPR\/(\d+\.\d+)/, flag: isOpera }, 80 | ]; 81 | 82 | for (const browser of browsers) { 83 | if (browser.flag) { 84 | browserName = browser.name; 85 | const versionMatch = uaBrowser.match(browser.regex); 86 | browserVersion = versionMatch ? versionMatch[1] : "Unknown"; 87 | break; 88 | } 89 | } 90 | 91 | const osMatch = ua.match(/\(([^)]+)\)/); 92 | const osDetails = osMatch ? osMatch[1].split("; ") : []; 93 | console.log(osMatch); 94 | console.log(osDetails); 95 | 96 | const windowsVersionMap = { 97 | "10.0": "10", 98 | "6.3": "8.1", 99 | "6.2": "8", 100 | "6.1": "7", 101 | "6.0": "Vista", 102 | "5.2": "XP 64-bit", 103 | "5.1": "XP", 104 | "5.0": "2000", 105 | }; 106 | 107 | const osInfo = [ 108 | { 109 | name: "Windows", 110 | regex: /Windows NT/, 111 | transform: (s) => windowsVersionMap[s.split(" ")[2]], 112 | index: 0, 113 | }, 114 | { 115 | name: "Mac OS X", 116 | regex: /Mac OS X/, 117 | transform: (s) => s.replace("_", ".").split(" ")[3], 118 | index: 0, 119 | }, 120 | { 121 | name: "Linux", 122 | regex: /Linux/, 123 | transform: () => "Unknown", // Precise Linux version can be difficult to determine 124 | index: 0, 125 | }, 126 | { 127 | name: "Android", 128 | regex: /Android/, 129 | transform: (s) => s.split(" ")[1], 130 | index: 0, 131 | }, 132 | { 133 | name: "iOS", 134 | regex: /iPhone/, 135 | transform: (s) => s.split(" ")[1].replace("_", "."), 136 | index: 0, 137 | }, 138 | ]; 139 | 140 | for (const os of osInfo) { 141 | if (os.regex.test(osDetails[0])) { 142 | osName = os.name; 143 | console.log(`osDetails: ${osDetails}`); 144 | osVersion = os.transform 145 | ? os.transform(osDetails[1]) 146 | : os.versionMap[osDetails[1].split(" ")[os.index]]; 147 | break; 148 | } 149 | } 150 | } 151 | } catch (error) { 152 | console.error("Could not retrieve user agent data", error); 153 | } 154 | return { 155 | browserName: browserName, 156 | browserVersion: browserVersion, 157 | osName: osName, 158 | osVersion: osVersion, 159 | wasm: typeof WebAssembly === "object", 160 | relaxedSimd: await relaxedSimd(), 161 | simd: await simd(), 162 | }; 163 | } 164 | 165 | // Export the function 166 | export default checkBrowserCapabilities; 167 | -------------------------------------------------------------------------------- /lib/vailNOTFINISHED/twiddle.cjs: -------------------------------------------------------------------------------- 1 | /** 2 | * Bit twiddling hacks for JavaScript. 3 | * 4 | * Author: Mikola Lysenko 5 | * 6 | * Ported from Stanford bit twiddling hack library: 7 | * http://graphics.stanford.edu/~seander/bithacks.html 8 | */ 9 | 10 | "use strict"; 11 | "use restrict"; 12 | 13 | //Number of bits in an integer 14 | var INT_BITS = 32; 15 | 16 | //Constants 17 | exports.INT_BITS = INT_BITS; 18 | exports.INT_MAX = 0x7fffffff; 19 | exports.INT_MIN = -1 << (INT_BITS - 1); 20 | 21 | //Returns -1, 0, +1 depending on sign of x 22 | exports.sign = function (v) { 23 | return (v > 0) - (v < 0); 24 | }; 25 | 26 | //Computes absolute value of integer 27 | exports.abs = function (v) { 28 | var mask = v >> (INT_BITS - 1); 29 | return (v ^ mask) - mask; 30 | }; 31 | 32 | //Computes minimum of integers x and y 33 | exports.min = function (x, y) { 34 | return y ^ ((x ^ y) & -(x < y)); 35 | }; 36 | 37 | //Computes maximum of integers x and y 38 | exports.max = function (x, y) { 39 | return x ^ ((x ^ y) & -(x < y)); 40 | }; 41 | 42 | //Checks if a number is a power of two 43 | exports.isPow2 = function (v) { 44 | return !(v & (v - 1)) && !!v; 45 | }; 46 | 47 | //Computes log base 2 of v 48 | exports.log2 = function (v) { 49 | var r, shift; 50 | r = (v > 0xffff) << 4; 51 | v >>>= r; 52 | shift = (v > 0xff) << 3; 53 | v >>>= shift; 54 | r |= shift; 55 | shift = (v > 0xf) << 2; 56 | v >>>= shift; 57 | r |= shift; 58 | shift = (v > 0x3) << 1; 59 | v >>>= shift; 60 | r |= shift; 61 | return r | (v >> 1); 62 | }; 63 | 64 | //Computes log base 10 of v 65 | exports.log10 = function (v) { 66 | return v >= 1000000000 67 | ? 9 68 | : v >= 100000000 69 | ? 8 70 | : v >= 10000000 71 | ? 7 72 | : v >= 1000000 73 | ? 6 74 | : v >= 100000 75 | ? 5 76 | : v >= 10000 77 | ? 4 78 | : v >= 1000 79 | ? 3 80 | : v >= 100 81 | ? 2 82 | : v >= 10 83 | ? 1 84 | : 0; 85 | }; 86 | 87 | //Counts number of bits 88 | exports.popCount = function (v) { 89 | v = v - ((v >>> 1) & 0x55555555); 90 | v = (v & 0x33333333) + ((v >>> 2) & 0x33333333); 91 | return (((v + (v >>> 4)) & 0xf0f0f0f) * 0x1010101) >>> 24; 92 | }; 93 | 94 | //Counts number of trailing zeros 95 | function countTrailingZeros(v) { 96 | var c = 32; 97 | v &= -v; 98 | if (v) c--; 99 | if (v & 0x0000ffff) c -= 16; 100 | if (v & 0x00ff00ff) c -= 8; 101 | if (v & 0x0f0f0f0f) c -= 4; 102 | if (v & 0x33333333) c -= 2; 103 | if (v & 0x55555555) c -= 1; 104 | return c; 105 | } 106 | exports.countTrailingZeros = countTrailingZeros; 107 | 108 | //Rounds to next power of 2 109 | exports.nextPow2 = function (v) { 110 | v += v === 0; 111 | --v; 112 | v |= v >>> 1; 113 | v |= v >>> 2; 114 | v |= v >>> 4; 115 | v |= v >>> 8; 116 | v |= v >>> 16; 117 | return v + 1; 118 | }; 119 | 120 | //Rounds down to previous power of 2 121 | exports.prevPow2 = function (v) { 122 | v |= v >>> 1; 123 | v |= v >>> 2; 124 | v |= v >>> 4; 125 | v |= v >>> 8; 126 | v |= v >>> 16; 127 | return v - (v >>> 1); 128 | }; 129 | 130 | //Computes parity of word 131 | exports.parity = function (v) { 132 | v ^= v >>> 16; 133 | v ^= v >>> 8; 134 | v ^= v >>> 4; 135 | v &= 0xf; 136 | return (0x6996 >>> v) & 1; 137 | }; 138 | 139 | var REVERSE_TABLE = new Array(256); 140 | 141 | (function (tab) { 142 | for (var i = 0; i < 256; ++i) { 143 | var v = i, 144 | r = i, 145 | s = 7; 146 | for (v >>>= 1; v; v >>>= 1) { 147 | r <<= 1; 148 | r |= v & 1; 149 | --s; 150 | } 151 | tab[i] = (r << s) & 0xff; 152 | } 153 | })(REVERSE_TABLE); 154 | 155 | //Reverse bits in a 32 bit word 156 | exports.reverse = function (v) { 157 | return ( 158 | (REVERSE_TABLE[v & 0xff] << 24) | 159 | (REVERSE_TABLE[(v >>> 8) & 0xff] << 16) | 160 | (REVERSE_TABLE[(v >>> 16) & 0xff] << 8) | 161 | REVERSE_TABLE[(v >>> 24) & 0xff] 162 | ); 163 | }; 164 | 165 | //Interleave bits of 2 coordinates with 16 bits. Useful for fast quadtree codes 166 | exports.interleave2 = function (x, y) { 167 | x &= 0xffff; 168 | x = (x | (x << 8)) & 0x00ff00ff; 169 | x = (x | (x << 4)) & 0x0f0f0f0f; 170 | x = (x | (x << 2)) & 0x33333333; 171 | x = (x | (x << 1)) & 0x55555555; 172 | 173 | y &= 0xffff; 174 | y = (y | (y << 8)) & 0x00ff00ff; 175 | y = (y | (y << 4)) & 0x0f0f0f0f; 176 | y = (y | (y << 2)) & 0x33333333; 177 | y = (y | (y << 1)) & 0x55555555; 178 | 179 | return x | (y << 1); 180 | }; 181 | 182 | //Extracts the nth interleaved component 183 | exports.deinterleave2 = function (v, n) { 184 | v = (v >>> n) & 0x55555555; 185 | v = (v | (v >>> 1)) & 0x33333333; 186 | v = (v | (v >>> 2)) & 0x0f0f0f0f; 187 | v = (v | (v >>> 4)) & 0x00ff00ff; 188 | v = (v | (v >>> 16)) & 0x000ffff; 189 | return (v << 16) >> 16; 190 | }; 191 | 192 | //Interleave bits of 3 coordinates, each with 10 bits. Useful for fast octree codes 193 | exports.interleave3 = function (x, y, z) { 194 | x &= 0x3ff; 195 | x = (x | (x << 16)) & 4278190335; 196 | x = (x | (x << 8)) & 251719695; 197 | x = (x | (x << 4)) & 3272356035; 198 | x = (x | (x << 2)) & 1227133513; 199 | 200 | y &= 0x3ff; 201 | y = (y | (y << 16)) & 4278190335; 202 | y = (y | (y << 8)) & 251719695; 203 | y = (y | (y << 4)) & 3272356035; 204 | y = (y | (y << 2)) & 1227133513; 205 | x |= y << 1; 206 | 207 | z &= 0x3ff; 208 | z = (z | (z << 16)) & 4278190335; 209 | z = (z | (z << 8)) & 251719695; 210 | z = (z | (z << 4)) & 3272356035; 211 | z = (z | (z << 2)) & 1227133513; 212 | 213 | return x | (z << 2); 214 | }; 215 | 216 | //Extracts nth interleaved component of a 3-tuple 217 | exports.deinterleave3 = function (v, n) { 218 | v = (v >>> n) & 1227133513; 219 | v = (v | (v >>> 2)) & 3272356035; 220 | v = (v | (v >>> 4)) & 251719695; 221 | v = (v | (v >>> 8)) & 4278190335; 222 | v = (v | (v >>> 16)) & 0x3ff; 223 | return (v << 22) >> 22; 224 | }; 225 | 226 | //Computes next combination in colexicographic order (this is mistakenly called nextPermutation on the bit twiddling hacks page) 227 | exports.nextCombination = function (v) { 228 | var t = v | (v - 1); 229 | return (t + 1) | (((~t & -~t) - 1) >>> (countTrailingZeros(v) + 1)); 230 | }; 231 | -------------------------------------------------------------------------------- /lib/indutnymodified/fft.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | /* Modification Notes 4 | - Removing the inv part increased it by 3% or so 5 | - Simplifying transform hurt because i was repeating the same math 6 | - getting rid of the wrapper imrpoved it by like 1% 7 | */ 8 | 9 | function IndutnyModifiedFftWrapperJavascript(size) { 10 | this.size = size; 11 | 12 | this._csize = size << 1; 13 | 14 | var table = new Array(this.size * 2); 15 | for (var i = 0; i < table.length; i += 2) { 16 | const angle = (Math.PI * i) / this.size; 17 | table[i] = Math.cos(angle); 18 | table[i + 1] = -Math.sin(angle); 19 | } 20 | this.table = table; 21 | 22 | // Find size's power of two 23 | var power = 0; 24 | for (var t = 1; this.size > t; t <<= 1) power++; 25 | 26 | // Calculate initial step's width, either 4 or 8 depending on the fftsize 27 | this._width = power % 2 === 0 ? power - 1 : power; 28 | 29 | // Pre-compute bit-reversal patterns 30 | this._bitrev = new Array(1 << this._width); 31 | for (var j = 0; j < this._bitrev.length; j++) { 32 | this._bitrev[j] = 0; 33 | for (var shift = 0; shift < this._width; shift += 2) { 34 | var revShift = this._width - shift - 2; 35 | this._bitrev[j] |= ((j >>> shift) & 3) << revShift; 36 | } 37 | } 38 | 39 | this._data = null; 40 | } 41 | 42 | // FFT 43 | IndutnyModifiedFftWrapperJavascript.prototype.fft = function fft(data) { 44 | this._data = data; 45 | this._out = new Float32Array(2 * this.size); 46 | var size = this._csize; 47 | 48 | // Initial step (permute and transform) 49 | var step = 1 << this._width; 50 | var len = (size / step) << 1; 51 | 52 | var outOff; 53 | var t; 54 | var bitrev = this._bitrev; 55 | // len is either 4 or 8, and both are used for the common fft sizes 56 | if (len === 4) { 57 | for (outOff = 0, t = 0; outOff < size; outOff += len, t++) { 58 | const off = bitrev[t]; 59 | this._singleTransform2(outOff, off, step); 60 | } 61 | } else { 62 | for (outOff = 0, t = 0; outOff < size; outOff += len, t++) { 63 | const off = bitrev[t]; 64 | this._singleTransform4(outOff, off, step); 65 | } 66 | } 67 | 68 | // Loop through steps in decreasing order 69 | for (step >>= 2; step >= 2; step >>= 2) { 70 | len = (size / step) << 1; 71 | var quarterLen = len >>> 2; 72 | 73 | // Loop through offsets in the data 74 | for (outOff = 0; outOff < size; outOff += len) { 75 | // Full case 76 | var limit = outOff + quarterLen; 77 | for (var i = outOff, k = 0; i < limit; i += 2, k += step) { 78 | const A = i; 79 | const B = A + quarterLen; 80 | const C = B + quarterLen; 81 | const D = C + quarterLen; 82 | 83 | // Original values 84 | const Ar = this._out[A]; 85 | const Ai = this._out[A + 1]; 86 | const Br = this._out[B]; 87 | const Bi = this._out[B + 1]; 88 | const Cr = this._out[C]; 89 | const Ci = this._out[C + 1]; 90 | const Dr = this._out[D]; 91 | const Di = this._out[D + 1]; 92 | 93 | // Middle values 94 | const MAr = Ar; 95 | const MAi = Ai; 96 | 97 | const tableBr = this.table[k]; 98 | const tableBi = this.table[k + 1]; 99 | const MBr = Br * tableBr - Bi * tableBi; 100 | const MBi = Br * tableBi + Bi * tableBr; 101 | 102 | const tableCr = this.table[2 * k]; 103 | const tableCi = this.table[2 * k + 1]; 104 | const MCr = Cr * tableCr - Ci * tableCi; 105 | const MCi = Cr * tableCi + Ci * tableCr; 106 | 107 | const tableDr = this.table[3 * k]; 108 | const tableDi = this.table[3 * k + 1]; 109 | const MDr = Dr * tableDr - Di * tableDi; 110 | const MDi = Dr * tableDi + Di * tableDr; 111 | 112 | // Pre-Final values 113 | const T0r = MAr + MCr; 114 | const T0i = MAi + MCi; 115 | const T1r = MAr - MCr; 116 | const T1i = MAi - MCi; 117 | const T2r = MBr + MDr; 118 | const T2i = MBi + MDi; 119 | const T3r = MBr - MDr; 120 | const T3i = MBi - MDi; 121 | 122 | this._out[A] = T0r + T2r; 123 | this._out[A + 1] = T0i + T2i; 124 | this._out[B] = T1r + T3i; 125 | this._out[B + 1] = T1i - T3r; 126 | this._out[C] = T0r - T2r; 127 | this._out[C + 1] = T0i - T2i; 128 | this._out[D] = T1r - T3i; 129 | this._out[D + 1] = T1i + T3r; 130 | } 131 | } 132 | } 133 | 134 | return this._out; 135 | }; 136 | 137 | // radix-2 (called for len=4) 138 | IndutnyModifiedFftWrapperJavascript.prototype._singleTransform2 = 139 | function _singleTransform2(outOff, off, step) { 140 | const evenR = this._data[off]; 141 | const evenI = this._data[off + 1]; 142 | const oddR = this._data[off + step]; 143 | const oddI = this._data[off + step + 1]; 144 | 145 | this._out[outOff] = evenR + oddR; 146 | this._out[outOff + 1] = evenI + oddI; 147 | this._out[outOff + 2] = evenR - oddR; 148 | this._out[outOff + 3] = evenI - oddI; 149 | }; 150 | 151 | // radix-4 (called for len=8) 152 | IndutnyModifiedFftWrapperJavascript.prototype._singleTransform4 = 153 | function _singleTransform4(outOff, off, step) { 154 | const step2 = step * 2; 155 | const step3 = step * 3; 156 | 157 | const Ar = this._data[off]; 158 | const Ai = this._data[off + 1]; 159 | const Br = this._data[off + step]; 160 | const Bi = this._data[off + step + 1]; 161 | const Cr = this._data[off + step2]; 162 | const Ci = this._data[off + step2 + 1]; 163 | const Dr = this._data[off + step3]; 164 | const Di = this._data[off + step3 + 1]; 165 | 166 | const T0r = Ar + Cr; 167 | const T0i = Ai + Ci; 168 | const T1r = Ar - Cr; 169 | const T1i = Ai - Ci; 170 | const T2r = Br + Dr; 171 | const T2i = Bi + Di; 172 | const T3r = Br - Dr; 173 | const T3i = Bi - Di; 174 | 175 | this._out[outOff] = T0r + T2r; 176 | this._out[outOff + 1] = T0i + T2i; 177 | this._out[outOff + 2] = T1r + T3i; 178 | this._out[outOff + 3] = T1i - T3r; 179 | this._out[outOff + 4] = T0r - T2r; 180 | this._out[outOff + 5] = T0i - T2i; 181 | this._out[outOff + 6] = T1r - T3i; 182 | this._out[outOff + 7] = T1i + T3r; 183 | }; 184 | 185 | export default IndutnyModifiedFftWrapperJavascript; 186 | -------------------------------------------------------------------------------- /lib/dntj/fft.js: -------------------------------------------------------------------------------- 1 | import * as complex_array from "./complex_array.js"; 2 | 3 | ("use strict"); 4 | 5 | var ComplexArray = complex_array.ComplexArray, 6 | // Math constants and functions we need. 7 | PI = Math.PI, 8 | SQRT1_2 = Math.SQRT1_2, 9 | sqrt = Math.sqrt, 10 | cos = Math.cos, 11 | sin = Math.sin; 12 | 13 | ComplexArray.prototype.FFT = function () { 14 | return internalFFT(this, false); 15 | }; 16 | 17 | export function FFT(input) { 18 | return ensureComplexArray(input).FFT(); 19 | } 20 | 21 | ComplexArray.prototype.InvFFT = function () { 22 | return internalFFT(this, true); 23 | }; 24 | 25 | export function InvFFT(input) { 26 | return ensureComplexArray(input).InvFFT(); 27 | } 28 | 29 | // Applies a frequency-space filter to input, and returns the real-space 30 | // filtered input. 31 | // filterer accepts freq, i, n and modifies freq.real and freq.imag. 32 | ComplexArray.prototype.frequencyMap = function (filterer) { 33 | return this.FFT().map(filterer).InvFFT(); 34 | }; 35 | 36 | export function frequencyMap(input, filterer) { 37 | return ensureComplexArray(input).frequencyMap(filterer); 38 | } 39 | 40 | function ensureComplexArray(input) { 41 | return (complex_array.isComplexArray(input) && input) || new ComplexArray(input); 42 | } 43 | 44 | function internalFFT(input, inverse) { 45 | var n = input.length; 46 | 47 | if (n & (n - 1)) { 48 | return FFT_Recursive(input, inverse); 49 | } else { 50 | return FFT_2_Iterative(input, inverse); 51 | } 52 | } 53 | 54 | function FFT_Recursive(input, inverse) { 55 | var n = input.length, 56 | // Counters. 57 | i, 58 | j, 59 | output, 60 | // Complex multiplier and its delta. 61 | f_r, 62 | f_i, 63 | del_f_r, 64 | del_f_i, 65 | // Lowest divisor and remainder. 66 | p, 67 | m, 68 | normalisation, 69 | recursive_result, 70 | _swap, 71 | _real, 72 | _imag; 73 | 74 | if (n === 1) { 75 | return input; 76 | } 77 | 78 | output = new ComplexArray(n, input.ArrayType); 79 | 80 | // Use the lowest odd factor, so we are able to use FFT_2_Iterative in the 81 | // recursive transforms optimally. 82 | p = LowestOddFactor(n); 83 | m = n / p; 84 | normalisation = 1 / sqrt(p); 85 | recursive_result = new ComplexArray(m, input.ArrayType); 86 | 87 | // Loops go like O(n Σ p_i), where p_i are the prime factors of n. 88 | // for a power of a prime, p, this reduces to O(n p log_p n) 89 | for (j = 0; j < p; j++) { 90 | for (i = 0; i < m; i++) { 91 | recursive_result.real[i] = input.real[i * p + j]; 92 | recursive_result.imag[i] = input.imag[i * p + j]; 93 | } 94 | // Don't go deeper unless necessary to save allocs. 95 | if (m > 1) { 96 | recursive_result = internalFFT(recursive_result, inverse); 97 | } 98 | 99 | del_f_r = cos((2 * PI * j) / n); 100 | del_f_i = (inverse ? -1 : 1) * sin((2 * PI * j) / n); 101 | f_r = 1; 102 | f_i = 0; 103 | 104 | for (i = 0; i < n; i++) { 105 | _real = recursive_result.real[i % m]; 106 | _imag = recursive_result.imag[i % m]; 107 | 108 | output.real[i] += f_r * _real - f_i * _imag; 109 | output.imag[i] += f_r * _imag + f_i * _real; 110 | 111 | _swap = f_r * del_f_r - f_i * del_f_i; 112 | f_i = f_r * del_f_i + f_i * del_f_r; 113 | f_r = _swap; 114 | } 115 | } 116 | 117 | // Copy back to input to match FFT_2_Iterative in-placeness 118 | // TODO: faster way of making this in-place? 119 | for (i = 0; i < n; i++) { 120 | input.real[i] = normalisation * output.real[i]; 121 | input.imag[i] = normalisation * output.imag[i]; 122 | } 123 | 124 | return input; 125 | } 126 | 127 | function FFT_2_Iterative(input, inverse) { 128 | var n = input.length, 129 | // Counters. 130 | i, 131 | j, 132 | output, 133 | output_r, 134 | output_i, 135 | // Complex multiplier and its delta. 136 | f_r, 137 | f_i, 138 | del_f_r, 139 | del_f_i, 140 | temp, 141 | // Temporary loop variables. 142 | l_index, 143 | r_index, 144 | left_r, 145 | left_i, 146 | right_r, 147 | right_i, 148 | // width of each sub-array for which we're iteratively calculating FFT. 149 | width; 150 | 151 | output = BitReverseComplexArray(input); 152 | output_r = output.real; 153 | output_i = output.imag; 154 | // Loops go like O(n log n): 155 | // width ~ log n; i,j ~ n 156 | width = 1; 157 | while (width < n) { 158 | del_f_r = cos(PI / width); 159 | del_f_i = (inverse ? -1 : 1) * sin(PI / width); 160 | for (i = 0; i < n / (2 * width); i++) { 161 | f_r = 1; 162 | f_i = 0; 163 | for (j = 0; j < width; j++) { 164 | l_index = 2 * i * width + j; 165 | r_index = l_index + width; 166 | 167 | left_r = output_r[l_index]; 168 | left_i = output_i[l_index]; 169 | right_r = f_r * output_r[r_index] - f_i * output_i[r_index]; 170 | right_i = f_i * output_r[r_index] + f_r * output_i[r_index]; 171 | 172 | output_r[l_index] = SQRT1_2 * (left_r + right_r); 173 | output_i[l_index] = SQRT1_2 * (left_i + right_i); 174 | output_r[r_index] = SQRT1_2 * (left_r - right_r); 175 | output_i[r_index] = SQRT1_2 * (left_i - right_i); 176 | temp = f_r * del_f_r - f_i * del_f_i; 177 | f_i = f_r * del_f_i + f_i * del_f_r; 178 | f_r = temp; 179 | } 180 | } 181 | width <<= 1; 182 | } 183 | 184 | return output; 185 | } 186 | 187 | function BitReverseIndex(index, n) { 188 | var bitreversed_index = 0; 189 | 190 | while (n > 1) { 191 | bitreversed_index <<= 1; 192 | bitreversed_index += index & 1; 193 | index >>= 1; 194 | n >>= 1; 195 | } 196 | return bitreversed_index; 197 | } 198 | 199 | function BitReverseComplexArray(array) { 200 | var n = array.length, 201 | flips = {}, 202 | swap, 203 | i; 204 | 205 | for (i = 0; i < n; i++) { 206 | var r_i = BitReverseIndex(i, n); 207 | 208 | if (flips.hasOwnProperty(i) || flips.hasOwnProperty(r_i)) continue; 209 | 210 | swap = array.real[r_i]; 211 | array.real[r_i] = array.real[i]; 212 | array.real[i] = swap; 213 | 214 | swap = array.imag[r_i]; 215 | array.imag[r_i] = array.imag[i]; 216 | array.imag[i] = swap; 217 | 218 | flips[i] = flips[r_i] = true; 219 | } 220 | 221 | return array; 222 | } 223 | 224 | function LowestOddFactor(n) { 225 | var factor = 3, 226 | sqrt_n = sqrt(n); 227 | 228 | while (factor <= sqrt_n) { 229 | if (n % factor === 0) return factor; 230 | factor = factor + 2; 231 | } 232 | return n; 233 | } 234 | -------------------------------------------------------------------------------- /tests/fft.test.js: -------------------------------------------------------------------------------- 1 | import { expect, test } from "vitest"; 2 | import webfft from "../lib/main.js"; 3 | 4 | test("basic usage", () => { 5 | const fftsize = 1024; 6 | const fft = new webfft(fftsize); 7 | const inputArr = new Float32Array(fftsize * 2); 8 | for (let i = 0; i < fftsize * 2; i++) { 9 | inputArr[i] = i * 1.12312312; // Arbitrary 10 | } 11 | const outputArr = fft.fft(inputArr); 12 | expect(outputArr[0]).not.toBe(0); 13 | expect(outputArr.length).toBe(2 * fftsize); 14 | fft.dispose(); 15 | }); 16 | 17 | test("available sublibraries", () => { 18 | const fft = new webfft(1024); 19 | const availableSubLibraries = fft.availableSubLibraries(); 20 | expect(availableSubLibraries.length).toBeGreaterThan(1); 21 | fft.dispose(); 22 | }); 23 | 24 | test("outputs for all sublibs approx match using different fftsizes", () => { 25 | const fftsizes = [ 26 | 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096, 8192, 16384, 32768, 27 | 16384, 32768, 65536, 131072, 28 | ]; 29 | for (let j = 0; j < fftsizes.length; j++) { 30 | const fftsize = fftsizes[j]; 31 | const fft = new webfft(fftsize); 32 | 33 | const availableSubLibraries = fft.availableSubLibraries(); 34 | 35 | const inputArr = new Float32Array(fftsize * 2); 36 | for (let i = 0; i < fftsize * 2; i++) { 37 | inputArr[i] = i * 1.12312312; // Arbitrary 38 | } 39 | 40 | fft.setSubLibrary("indutnyJavascript"); 41 | let co = fft.fft(inputArr); 42 | let goldenTotal = 0; 43 | for (let k = 0; k < fftsize; ++k) { 44 | goldenTotal += Math.sqrt( 45 | co[k * 2] * co[k * 2] + co[k * 2 + 1] * co[k * 2 + 1], 46 | ); 47 | } 48 | goldenTotal = goldenTotal / fftsize / fftsize; 49 | 50 | // Try each sub-library 51 | for (let i = 0; i < availableSubLibraries.length; i++) { 52 | fft.setSubLibrary(availableSubLibraries[i]); 53 | co = fft.fft(inputArr); 54 | let total = 0; 55 | for (let k = 0; k < fftsize; ++k) { 56 | total += Math.sqrt( 57 | co[k * 2] * co[k * 2] + co[k * 2 + 1] * co[k * 2 + 1], 58 | ); 59 | } 60 | total = total / fftsize / fftsize; 61 | expect(total).toBeCloseTo(goldenTotal, 5); 62 | } 63 | 64 | fft.dispose(); 65 | } 66 | }); 67 | 68 | test("fftr", () => { 69 | const fftsize = 1024; 70 | const fft = new webfft(fftsize); 71 | 72 | const availableSubLibraries = fft.availableSubLibraries(); 73 | 74 | const inputArr = new Float32Array(fftsize); 75 | for (let i = 0; i < fftsize; i++) { 76 | inputArr[i] = i * 1.12312312; // Arbitrary 77 | } 78 | 79 | fft.setSubLibrary("indutnyJavascript"); 80 | let co = fft.fftr(inputArr); 81 | let goldenTotal = 0; 82 | for (let k = 0; k < fftsize / 2; ++k) { 83 | goldenTotal += Math.sqrt( 84 | co[k * 2] * co[k * 2] + co[k * 2 + 1] * co[k * 2 + 1], 85 | ); 86 | } 87 | goldenTotal = goldenTotal / fftsize; 88 | 89 | // Try each sub-library 90 | for (let i = 0; i < availableSubLibraries.length; i++) { 91 | fft.setSubLibrary(availableSubLibraries[i]); 92 | co = fft.fftr(inputArr); 93 | let total = 0; 94 | for (let k = 0; k < fftsize / 2; ++k) { 95 | total += Math.sqrt(co[k * 2] * co[k * 2] + co[k * 2 + 1] * co[k * 2 + 1]); 96 | } 97 | total = total / fftsize; 98 | expect(total).toBeCloseTo(goldenTotal, 3); 99 | } 100 | 101 | fft.dispose(); 102 | }); 103 | 104 | test("int16 inputs", () => { 105 | const fftsize = 1024; 106 | const fft = new webfft(fftsize); 107 | 108 | const availableSubLibraries = fft.availableSubLibraries(); 109 | 110 | const inputArr = new Int16Array(fftsize * 2); 111 | for (let i = 0; i < fftsize * 2; i++) { 112 | inputArr[i] = i * 30; // Arbitrary 113 | } 114 | 115 | fft.setSubLibrary("indutnyJavascript"); 116 | let co = fft.fft(inputArr); 117 | let goldenTotal = 0; 118 | for (let k = 0; k < fftsize; ++k) { 119 | goldenTotal += Math.sqrt( 120 | co[k * 2] * co[k * 2] + co[k * 2 + 1] * co[k * 2 + 1], 121 | ); 122 | } 123 | goldenTotal = goldenTotal / fftsize; 124 | 125 | // Try each sub-library 126 | for (let i = 0; i < availableSubLibraries.length; i++) { 127 | fft.setSubLibrary(availableSubLibraries[i]); 128 | co = fft.fft(inputArr); 129 | let total = 0; 130 | for (let k = 0; k < fftsize; ++k) { 131 | total += Math.sqrt(co[k * 2] * co[k * 2] + co[k * 2 + 1] * co[k * 2 + 1]); 132 | } 133 | total = total / fftsize; 134 | expect(total).toBeCloseTo(goldenTotal, 1); 135 | } 136 | 137 | fft.dispose(); 138 | }); 139 | 140 | test("1000th element matches for all", () => { 141 | const fftsize = 1024; 142 | const fft = new webfft(fftsize); 143 | 144 | const availableSubLibraries = fft.availableSubLibraries(); 145 | 146 | const inputArr = new Float32Array(fftsize * 2); 147 | for (let i = 0; i < fftsize * 2; i++) { 148 | inputArr[i] = i * 1.12312312; // Arbitrary 149 | } 150 | 151 | fft.setSubLibrary("indutnyJavascript"); 152 | let co = fft.fft(inputArr); 153 | const goldenPhase = Math.atan(co[1000] / co[1001]); 154 | const goldenMag = Math.sqrt(co[1000] * co[1000] + co[1001] * co[1001]); 155 | 156 | // Try each sub-library 157 | for (let i = 0; i < availableSubLibraries.length; i++) { 158 | console.log("Testing", availableSubLibraries[i]); 159 | fft.setSubLibrary(availableSubLibraries[i]); 160 | 161 | co = fft.fft(inputArr); 162 | const phase = Math.atan(co[1000] / co[1001]); 163 | const mag = Math.sqrt(co[1000] * co[1000] + co[1001] * co[1001]); 164 | expect(phase).toBeCloseTo(goldenPhase, 5); // 2nd arg is the num digits after decimal point that must match 165 | expect(mag).toBeCloseTo(goldenMag, 2); // 2nd arg is the num digits after decimal point that must match 166 | } 167 | 168 | fft.dispose(); 169 | }); 170 | 171 | test("non power of 2 size", () => { 172 | expect(() => { 173 | const fft = new webfft(1023); 174 | }).toThrowError(); 175 | 176 | const fft2 = new webfft(1024); 177 | 178 | // fft 179 | const inputArray = new Float32Array(1023 * 2); 180 | inputArray.fill(0); 181 | expect(() => { 182 | fft2.fft(inputArray); 183 | }).toThrowError(); 184 | 185 | // fftr 186 | const inputArray2 = new Float32Array(1023); 187 | expect(() => { 188 | fft2.fftr(inputArray2); 189 | }).toThrowError(); 190 | 191 | // fft2d inner 192 | const inputArray3 = [new Float32Array(1023 * 2), new Float32Array(1023 * 2)]; 193 | expect(() => { 194 | fft2.fft2d(inputArray3); 195 | }).toThrowError(); 196 | 197 | // fft2d outter 198 | const inputArray4 = [ 199 | new Float32Array(1024 * 2), 200 | new Float32Array(1024 * 2), 201 | new Float32Array(1024 * 2), 202 | ]; 203 | expect(() => { 204 | fft2.fft2d(inputArray4); 205 | }).toThrowError(); 206 | }); 207 | 208 | test("invalid sublibrary", () => { 209 | expect(() => { 210 | const fft = new webfft(1024, "notasublibrary"); 211 | }).toThrowError(); 212 | }); 213 | -------------------------------------------------------------------------------- /lib/nayukic/ffttest.c: -------------------------------------------------------------------------------- 1 | /* 2 | * FFT and convolution test (C) 3 | * 4 | * Copyright (c) 2014 Project Nayuki 5 | * http://www.nayuki.io/page/free-small-fft-in-multiple-languages 6 | * 7 | * (MIT License) 8 | * Permission is hereby granted, free of charge, to any person obtaining a copy of 9 | * this software and associated documentation files (the "Software"), to deal in 10 | * the Software without restriction, including without limitation the rights to 11 | * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 12 | * the Software, and to permit persons to whom the Software is furnished to do so, 13 | * subject to the following conditions: 14 | * - The above copyright notice and this permission notice shall be included in 15 | * all copies or substantial portions of the Software. 16 | * - The Software is provided "as is", without warranty of any kind, express or 17 | * implied, including but not limited to the warranties of merchantability, 18 | * fitness for a particular purpose and noninfringement. In no event shall the 19 | * authors or copyright holders be liable for any claim, damages or other 20 | * liability, whether in an action of contract, tort or otherwise, arising from, 21 | * out of or in connection with the Software or the use or other dealings in the 22 | * Software. 23 | */ 24 | 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include "fft.h" 31 | 32 | 33 | // Private function prototypes 34 | static void test_fft(int n); 35 | static void test_convolution(int n); 36 | static void naive_dft(const double *inreal, const double *inimag, double *outreal, double *outimag, int inverse, int n); 37 | static void naive_convolve(const double *xreal, const double *ximag, const double *yreal, const double *yimag, double *outreal, double *outimag, int n); 38 | static double log10_rms_err(const double *xreal, const double *ximag, const double *yreal, const double *yimag, int n); 39 | static double *random_reals(int n); 40 | static void *memdup(const void *src, size_t n); 41 | 42 | static double max_log_error = -INFINITY; 43 | 44 | 45 | /* Main and test functions */ 46 | 47 | int main(int argc, char **argv) { 48 | int i; 49 | int prev; 50 | srand(time(NULL)); 51 | 52 | // Test power-of-2 size FFTs 53 | for (i = 0; i <= 12; i++) 54 | test_fft(1 << i); 55 | 56 | // Test small size FFTs 57 | for (i = 0; i < 30; i++) 58 | test_fft(i); 59 | 60 | // Test diverse size FFTs 61 | prev = 0; 62 | for (i = 0; i <= 100; i++) { 63 | int n = (int)lround(pow(1500, i / 100.0)); 64 | if (n > prev) { 65 | test_fft(n); 66 | prev = n; 67 | } 68 | } 69 | 70 | // Test power-of-2 size convolutions 71 | for (i = 0; i <= 12; i++) 72 | test_convolution(1 << i); 73 | 74 | // Test diverse size convolutions 75 | prev = 0; 76 | for (i = 0; i <= 100; i++) { 77 | int n = (int)lround(pow(1500, i / 100.0)); 78 | if (n > prev) { 79 | test_convolution(n); 80 | prev = n; 81 | } 82 | } 83 | 84 | printf("\n"); 85 | printf("Max log err = %.1f\n", max_log_error); 86 | printf("Test %s\n", max_log_error < -10 ? "passed" : "failed"); 87 | return 0; 88 | } 89 | 90 | 91 | static void test_fft(int n) { 92 | double *inputreal, *inputimag; 93 | double *refoutreal, *refoutimag; 94 | double *actualoutreal, *actualoutimag; 95 | 96 | inputreal = random_reals(n); 97 | inputimag = random_reals(n); 98 | 99 | refoutreal = malloc(n * sizeof(double)); 100 | refoutimag = malloc(n * sizeof(double)); 101 | naive_dft(inputreal, inputimag, refoutreal, refoutimag, 0, n); 102 | 103 | actualoutreal = memdup(inputreal, n * sizeof(double)); 104 | actualoutimag = memdup(inputimag, n * sizeof(double)); 105 | transform(actualoutreal, actualoutimag, n); 106 | 107 | printf("fftsize=%4d logerr=%5.1f\n", n, log10_rms_err(refoutreal, refoutimag, actualoutreal, actualoutimag, n)); 108 | 109 | free(inputreal); 110 | free(inputimag); 111 | free(refoutreal); 112 | free(refoutimag); 113 | free(actualoutreal); 114 | free(actualoutimag); 115 | } 116 | 117 | 118 | static void test_convolution(int n) { 119 | double *input0real, *input0imag; 120 | double *input1real, *input1imag; 121 | double *refoutreal, *refoutimag; 122 | double *actualoutreal, *actualoutimag; 123 | 124 | input0real = random_reals(n); 125 | input0imag = random_reals(n); 126 | input1real = random_reals(n); 127 | input1imag = random_reals(n); 128 | 129 | refoutreal = malloc(n * sizeof(double)); 130 | refoutimag = malloc(n * sizeof(double)); 131 | naive_convolve(input0real, input0imag, input1real, input1imag, refoutreal, refoutimag, n); 132 | 133 | actualoutreal = malloc(n * sizeof(double)); 134 | actualoutimag = malloc(n * sizeof(double)); 135 | convolve_complex(input0real, input0imag, input1real, input1imag, actualoutreal, actualoutimag, n); 136 | 137 | printf("convsize=%4d logerr=%5.1f\n", n, log10_rms_err(refoutreal, refoutimag, actualoutreal, actualoutimag, n)); 138 | 139 | free(input0real); 140 | free(input0imag); 141 | free(input1real); 142 | free(input1imag); 143 | free(refoutreal); 144 | free(refoutimag); 145 | free(actualoutreal); 146 | free(actualoutimag); 147 | } 148 | 149 | 150 | /* Naive reference computation functions */ 151 | 152 | static void naive_dft(const double *inreal, const double *inimag, double *outreal, double *outimag, int inverse, int n) { 153 | double coef = (inverse ? 2 : -2) * M_PI; 154 | int k; 155 | for (k = 0; k < n; k++) { // For each output element 156 | double sumreal = 0; 157 | double sumimag = 0; 158 | int t; 159 | for (t = 0; t < n; t++) { // For each input element 160 | double angle = coef * ((long long)t * k % n) / n; 161 | sumreal += inreal[t]*cos(angle) - inimag[t]*sin(angle); 162 | sumimag += inreal[t]*sin(angle) + inimag[t]*cos(angle); 163 | } 164 | outreal[k] = sumreal; 165 | outimag[k] = sumimag; 166 | } 167 | } 168 | 169 | 170 | static void naive_convolve(const double *xreal, const double *ximag, const double *yreal, const double *yimag, double *outreal, double *outimag, int n) { 171 | int i; 172 | for (i = 0; i < n; i++) { 173 | double sumreal = 0; 174 | double sumimag = 0; 175 | int j; 176 | for (j = 0; j < n; j++) { 177 | int k = (i - j + n) % n; 178 | sumreal += xreal[k] * yreal[j] - ximag[k] * yimag[j]; 179 | sumimag += xreal[k] * yimag[j] + ximag[k] * yreal[j]; 180 | } 181 | outreal[i] = sumreal; 182 | outimag[i] = sumimag; 183 | } 184 | } 185 | 186 | 187 | /* Utility functions */ 188 | 189 | static double log10_rms_err(const double *xreal, const double *ximag, const double *yreal, const double *yimag, int n) { 190 | double err = 0; 191 | int i; 192 | for (i = 0; i < n; i++) 193 | err += (xreal[i] - yreal[i]) * (xreal[i] - yreal[i]) + (ximag[i] - yimag[i]) * (ximag[i] - yimag[i]); 194 | 195 | err /= n > 0 ? n : 1; 196 | err = sqrt(err); // Now this is a root mean square (RMS) error 197 | err = err > 0 ? log10(err) : -99.0; 198 | if (err > max_log_error) 199 | max_log_error = err; 200 | return err; 201 | } 202 | 203 | 204 | static double *random_reals(int n) { 205 | double *result = malloc(n * sizeof(double)); 206 | int i; 207 | for (i = 0; i < n; i++) 208 | result[i] = (rand() / (RAND_MAX + 1.0)) * 2 - 1; 209 | return result; 210 | } 211 | 212 | 213 | static void *memdup(const void *src, size_t n) { 214 | void *dest = malloc(n); 215 | if (dest != NULL) 216 | memcpy(dest, src, n); 217 | return dest; 218 | } 219 | -------------------------------------------------------------------------------- /site/src/components/InteractiveSignal.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect, useRef } from "react"; 2 | import webfft from "webfft"; 3 | import { Line } from "react-chartjs-2"; 4 | // @ts-ignore 5 | import { fftshift } from "fftshift"; 6 | 7 | function InteractiveSignal() { 8 | const fftSize = 1024; 9 | const [signalStr, setSignalStr] = useState(null); 10 | const [amplitude, setAmplitude] = useState(20); 11 | const [frequency, setFrequency] = useState(0.1); 12 | const [_, setLastUpdate] = useState(null); 13 | const [update, setUpdate] = useState(0); 14 | const [plotData, setPlotData] = useState(new Float32Array()); 15 | const [start, setStart] = useState(false); 16 | const sin = new Float32Array(2 * fftSize); 17 | const [webfftInstance, setWebfftInstance] = useState(null); 18 | 19 | const textColor = "hsla(0, 0%, 80%, 0.9)"; 20 | const waveColor = "rgb(34 197 94)"; 21 | 22 | useEffect(() => { 23 | setWebfftInstance(new webfft(fftSize)); 24 | }, []); 25 | 26 | useEffect(() => { 27 | if (webfftInstance) { 28 | //webfftInstance.setSubLibrary("indutnyJavascript"); 29 | webfftInstance.profile(0.5); 30 | } 31 | }, [webfftInstance]); 32 | 33 | useEffect(() => { 34 | if (start) { 35 | const y0 = 75; 36 | let sinString = "M " + 0 + "," + y0; 37 | 38 | sin.fill(0); 39 | const t = new Date().getTime(); 40 | 41 | for (let i = 0; i < fftSize; i++) { 42 | const sample = 43 | 2 * amplitude * Math.sin(frequency * i + t) + 44 | (Math.random() - 0.5) * 50; 45 | sinString += " L " + i + "," + (y0 + sample); 46 | sin[2 * i] = sample; 47 | } 48 | 49 | setSignalStr(sinString); 50 | const psd = webfftInstance.fft(sin); 51 | const mag = new Float32Array(fftSize); 52 | for (let i = 0; i < fftSize; i++) { 53 | mag[i] = Math.sqrt( 54 | psd[2 * i] * psd[2 * i] + psd[2 * i + 1] * psd[2 * i + 1], 55 | ); 56 | } 57 | setPlotData(fftshift(mag).slice(fftSize / 2, fftSize)); 58 | setLastUpdate(performance.now()); 59 | } 60 | }, [amplitude, frequency, update]); 61 | 62 | useEffect(() => { 63 | const timerId = setInterval(() => { 64 | setUpdate(Math.random()); 65 | }, 50); // in ms 66 | return function cleanup() { 67 | clearInterval(timerId); 68 | }; 69 | }, []); 70 | 71 | const slidersChanged = useRef(false); 72 | 73 | useEffect(() => { 74 | if (start) { 75 | slidersChanged.current = false; // Reset the flag when starting 76 | } 77 | }, [start]); 78 | 79 | const handleAmplitudeChange = (e: React.ChangeEvent) => { 80 | setAmplitude(Number(e.target.value)); 81 | slidersChanged.current = true; 82 | }; 83 | 84 | const handleFrequencyChange = (e: React.ChangeEvent) => { 85 | setFrequency(Number(e.target.value)); 86 | slidersChanged.current = true; 87 | }; 88 | 89 | const handleStop = () => { 90 | setStart(false); 91 | 92 | let neutralSignalStr = "M0,75"; 93 | for (let i = 1; i < fftSize; i++) { 94 | neutralSignalStr += ` L${i},75`; 95 | } 96 | setSignalStr(neutralSignalStr); 97 | 98 | setPlotData(new Float32Array(fftSize / 2).fill(0)); 99 | }; 100 | 101 | /* 102 | // will trigger the main useeffect to run again 103 | useEffect(() => { 104 | const timerId = setInterval(() => { 105 | setUpdate(Math.random()); 106 | }, 50); // in ms 107 | return function cleanup() { 108 | clearInterval(timerId); 109 | }; 110 | }, []); 111 | */ 112 | 113 | return ( 114 |
    115 |

    116 | WebFFT In Action! 117 |

    118 |
    119 | {/* Start/Stop Button */} 120 |
    121 | 133 |
    134 | {/* Amplitude Control */} 135 |
    136 | Signal Amplitude 137 | 146 |
    147 | {/* Frequency Control */} 148 |
    149 | Signal Frequency 150 | 159 |
    160 | {/* SVG Plot */} 161 |
    162 | 163 | 164 | 171 | 172 | 173 |
    174 | {/* ChartJS Plot */} 175 |
    176 | i, 181 | ), 182 | datasets: [ 183 | { 184 | label: "Dataset", 185 | data: plotData, 186 | fill: false, 187 | borderColor: waveColor, 188 | borderWidth: 1, 189 | pointRadius: 0, 190 | }, 191 | ], 192 | }} 193 | options={{ 194 | plugins: { 195 | legend: { 196 | display: false, 197 | }, 198 | }, 199 | scales: { 200 | x: { 201 | display: true, 202 | grid: { 203 | display: false, 204 | }, 205 | title: { 206 | display: true, 207 | text: "Frequency", 208 | color: textColor, 209 | }, 210 | ticks: { 211 | display: false, 212 | color: textColor, 213 | }, 214 | }, 215 | y: { 216 | display: true, 217 | grid: { 218 | display: false, 219 | }, 220 | title: { 221 | display: true, 222 | text: "PSD", 223 | color: textColor, 224 | }, 225 | ticks: { 226 | display: false, 227 | color: textColor, 228 | }, 229 | }, 230 | }, 231 | }} 232 | /> 233 |
    234 |
    235 |
    236 | ); 237 | } 238 | 239 | export default React.memo(InteractiveSignal); 240 | -------------------------------------------------------------------------------- /lib/wasmfft/fft.js: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2022 Jeppe Johansen - jeppe@j-software.dk 2 | export let wasmBinary = `AGFzbQEAAAABLQlgAX8AYAF9AX1gAX0Bf2ACf38Bf2AAAGACf38CfX1gAn9/AGAAAn9/YAABfwKJ 3 | AQkETWF0aANjb3MAAQRNYXRoA3NpbgABBE1hdGgEY2VpbAACBE1hdGgEbG9nMgABBmNvbmZpZwZw 4 | b2ludHMDfwAGY29uZmlnCWlucHV0VHlwZQN/AAZjb25maWcKb3V0cHV0VHlwZQN/AAZjb25maWcF 5 | c2hpZnQDfwAGY29uZmlnBXNjYWxlA30AAxMSAwAAAAAAAAQFAwQEBAYEBAcIBAQBcAAGBQMBAAEG 6 | PQx/AUEAC38BQQALfwFBAAt/AUEAC38BQQALfwFBAAt/AUEAC38BQQALfwFBAAt/AUEAC38BQQAL 7 | fwFBAAsHNAQGbWVtb3J5AgADcnVuAAsQZ2V0T3V0cHV0QnVmZmVycwAUDmdldElucHV0QnVmZmVy 8 | ABUIARMJDAEAQQALBgoIBQcJBgrwFxJCAQF/IwUgAUEBa2oiAiABQQFrQX9zcSICIABqJAUjBT8A 9 | QYCABGxLBEAjBT8AQYCABGxrQf//A2pBEHZAABoLIAILhAEDBX8BewJ/IwAhASMIIQIjCSEEIwch 10 | A0EAIQUgAUEEbEEBayEHIwMEQCABQQJsIQgFQQAhCAsDQCADIAhqIAL9AAQAIgYgBv3mASAE/QAE 11 | ACIGIAb95gH95AH9CwQAIAhBEGogB3EhCCACQRBqIQIgBEEQaiEEIAVBBGoiBSABSQ0ACwuvAgIG 12 | fwd7IwAhASMLIgMgAWohBCMIIQUjCSEGIwT9EyENA0AgAygCACECIAL9XAIAIQcgAiABQQJsaiAH 13 | /VYCAAEhByACIAFqIAf9VgIAAiEHIAIgAUEDbGogB/1WAgADIQcgByAH/Q0AAQIDAAECAwgJCgsI 14 | CQoLIAcgB/3hAf0NBAUGBxQVFhcMDQ4PHB0eH/3kASEKIAogCv3hAf0NCAkKCxwdHh8YGRobDA0O 15 | DyEMIAz9DAAAAAD/////AAAAAP/////9TiEJIAogCv0NAAECAwQFBgcAAQIDBAUGByAM/Qz///// 16 | AAAAAP////8AAAAA/U795AEhCCAFIAggDf3mAf0LBAAgBiAJIA395gH9CwQAIAVBEGohBSAGQRBq 17 | IQYgA0EEaiIDIARJDQALC58DAgZ/B3sjAEECbCEDIwT9EyELIwshBCMIIQUjCSEGQQAhAQNAIAQo 18 | AgAhAiAC/V0DACEHIAIgA2r9XQMAIQggAiADQQJsav1dAwAiCSACIANBA2xq/V0DACIK/Q0AAQID 19 | AAECAxAREhMUFRYX/QwAAAAAAAAAgAAAAAAAAACA/VEgByAI/Q0AAQIDAAECAxAREhMUFRYX/eQB 20 | IQwgCSAK/Q0EBQYHBAUGBxQVFhcQERIT/QwAAAAAAAAAgAAAAAAAAACA/VEgByAI/Q0EBQYHBAUG 21 | BxQVFhcQERIT/eQBIQ0gBSAMIAz9DQABAgMEBQYHAAECAwQFBgcgDCAM/Q0ICQoLDA0ODwgJCgsM 22 | DQ4P/QwAAAAAAAAAAAAAAIAAAACA/VH95AEgC/3mAf0LBAAgBiANIA39DQABAgMEBQYHAAECAwQF 23 | BgcgDSAN/Q0ICQoLDA0ODwgJCgsMDQ4P/QwAAAAAAAAAgAAAAIAAAAAA/VH95AEgC/3mAf0LBAAg 24 | BEEEaiEEIAVBEGohBSAGQRBqIQYgAUEIaiIBIANJDQALC8wEAgp/HHtBASAAdCEBIwwgAEEDdGoo 25 | AgAhAiABQQJsIQMjCCEGIwkiByMAQQRsaiEFA0AgAkHgAGshCiAGIQggByIJIANqIQQDQCAI/QAE 26 | ACELIAn9AAQAIQwgCCADav0ABAAhDSAJIANq/QAEACEOIAggA0ECbGr9AAQAIQ8gCSADQQJsav0A 27 | BAAhECAKQYABaiIK/QAEACETIAr9AAQQIRYgDyAT/eYBIBAgFv3mAf3lASEbIA8gFv3mASAQIBP9 28 | 5gH95AEhHCAK/QAEICEUIAr9AAQwIRcgDSAU/eYBIA4gF/3mAf3lASEZIA0gF/3mASAOIBT95gH9 29 | 5AEhGiALIBn95AEhHyALIBn95QEhICAMIBr95AEhJSAMIBr95QEhJiAIIANBA2xq/QAEACERIAkg 30 | A0EDbGr9AAQAIRIgCv0ABEAhFSAK/QAEUCEYIBEgFf3mASASIBj95gH95QEhHSARIBj95gEgEiAV 31 | /eYB/eQBIR4gGyAd/eQBISEgGyAd/eUBISIgHCAe/eUBISMgHCAe/eQBISQgCCADQQNsaiAgICP9 32 | 5QH9CwQAIAkgA0EDbGogJiAi/eQB/QsEACAJIANBAmxqICUgJP3lAf0LBAAgCCADQQJsaiAfICH9 33 | 5QH9CwQAIAggA2ogICAj/eQB/QsEACAJIANqICYgIv3lAf0LBAAgCCAfICH95AH9CwQAIAkgJSAk 34 | /eQB/QsEACAIQRBqIQggCUEQaiIJIARJDQALIAYgA0ECdGohBiAHIANBAnRqIgcgBUkNAAsLrQQC 35 | CH8ce0EIIQFBECECIwghBSMJIQb9DAAAgD9eg2w/8wQ1PxXvwz4hEf0MAAAAABXvw77zBDW/XoNs 36 | vyEU/QwAAIA/8wQ1PwAAAADzBDW/IRL9DAAAAADzBDW/AACAv/MENb8hFf0MAACAPxXvwz7zBDW/ 37 | XoNsvyET/QwAAAAAXoNsv/MENb8V78M+IRZBACEEA0AgBSEHIAYhCCAH/QAEACEJIAj9AAQAIQog 38 | B/0ABBAhCyAI/QAEECEMIAf9AAQgIQ0gCP0ABCAhDiANIBH95gEgDiAU/eYB/eUBIRkgDSAU/eYB 39 | IA4gEf3mAf3kASEaIAsgEv3mASAMIBX95gH95QEhFyALIBX95gEgDCAS/eYB/eQBIRggCSAX/eQB 40 | IR0gCSAX/eUBIR4gCiAY/eQBISMgCiAY/eUBISQgB/0ABDAhDyAI/QAEMCEQIA8gE/3mASAQIBb9 41 | 5gH95QEhGyAPIBb95gEgECAT/eYB/eQBIRwgGSAb/eQBIR8gGSAb/eUBISAgGiAc/eUBISEgGiAc 42 | /eQBISIgByAeICH95QH9CwQwIAggJCAg/eQB/QsEMCAIICMgIv3lAf0LBCAgByAdIB/95QH9CwQg 43 | IAcgHiAh/eQB/QsEECAIICQgIP3lAf0LBBAgByAdIB/95AH9CwQAIAggIyAi/eQB/QsEACAHQRBq 44 | IQcgCEEQaiEIIAUgAkECdGohBSAGIAJBAnRqIQYgBCACaiIEIwBJDQALC+kBAgd/CHsjDSEGIw4h 45 | B0EBIAB0IQEgAUECbCECIwghBCMJIQVBACEDA0AgBP0ABAAhCCAF/QAEACEJIAQgAmr9AAQAIQog 46 | BSACav0ABAAhCyAG/QAEACEOIAf9AAQAIQ8gDiAK/eYBIA8gC/3mAf3lASEMIA4gC/3mASAPIAr9 47 | 5gH95AEhDSAEIAggDP3kAf0LBAAgBSAJIA395AH9CwQAIAQgAmogCCAM/eUB/QsEACAFIAJqIAkg 48 | Df3lAf0LBAAgBkEQaiEGIAdBEGohByAEQRBqIQQgBUEQaiEFIANBCGoiAyABSQ0ACws0AQR/Iw8h 49 | AiMQIQFBACEAA0AgAiAAQQhsaiIDKAIEIAMoAgARAAAgAEEBaiIAIAFJDQALCxkBAX1D2w/JwCAA 50 | s5QgAbOVIgIQASACEAALMwECf0EAIQNBACECA0AgACACdkEBcSABIAJrQQFrdCADciEDIAJBAWoi 51 | AiABSQ0ACyADCzwBAn9BACEAIwFBAEYEQEEIIQEFQQQhAQsDQCMLIABqIAAjBhANIAFsIwpqNgIA 52 | IABBBGoiACMASQ0ACwtGAgN/An0jACICQQF2IQFBACEAA0AgACACEAwhAyEEIw0gAEEEbGogAzgC 53 | ACMOIABBBGxqIAQ4AgAgAEEBaiIAIAFJDQALC6gBAgh/An1BAyEAA0BBASAAdCIBQQJuIQIgAUEQ 54 | bEEQEAQhBkEAIQMDQEEAIQUDQEEAIQQDQCAFIAMgBGpsIAEQDCEIIQkgBiADIAVqQQhsIARqQQRs 55 | aiIHIAg4AgAgByAJOAIQIARBAWoiBEEESQ0ACyAFQQFqIgVBBEkNAAsgA0EEaiIDIAJJDQALIwwg 56 | AEEBa0EDdGogBjYCACAAQQFqIgAjBk0NAAsLIQEBfyMQQQhsIw9qIgIgADYCACACIAE2AgQjEEEB 57 | aiQQC4kBAQJ/IwFBAEYEQEEDQQAQEQVBBUEAEBELIwBBIE8EQEEEQQMQEUEFIQAFQQMhAAtBASAA 58 | dCEBA0AgAUECbCMATSABQQhPcQRAQQEgABARIABBAmohACABQQRsIQEFQQAgABARIABBAWohACAB 59 | QQJsIQELIAEjAE0NAAsjAkEARgRAQQJBABARCwuAAQAjALMQAxACJAYjAEEEbEEQEAQkCCMAQQRs 60 | QRAQBCQJIwMEQCMAQQRsQRAQBCQHBSMIJAcLIwBBBGxBEBAEJAsjAEEIbEEQEAQkCiMGQRBsQRAQ 61 | BCQPIwBBAmxBEBAEJA0jAEECbEEQEAQkDiMGQRBsQRAQBCQMEA4QDxAQEBILBgAjByMJCwQAIwoL`; 62 | 63 | export class FFTWasmImpl { 64 | #memory; 65 | #run; 66 | 67 | #inputBuffer; 68 | #realOut; 69 | #workingBufferR; 70 | #workingBufferI; 71 | 72 | points = 0; 73 | 74 | /** 75 | * 76 | * @param {Number} points 77 | * @param {WebAssembly.WebAssemblyInstantiatedSource} wasmModule 78 | */ 79 | constructor(points, realOut, wasmModule) { 80 | if (points < 8) { 81 | throw new Error("FFT size below 8 is not supported"); 82 | } 83 | 84 | this.points = points; 85 | this.#realOut = realOut; 86 | 87 | this.#memory = wasmModule.exports.memory; 88 | this.#run = wasmModule.exports.run; 89 | 90 | let [outpR, outpI] = wasmModule.exports.getOutputBuffers(); 91 | let inp = wasmModule.exports.getInputBuffer(); 92 | 93 | this.#inputBuffer = new Float32Array(this.#memory.buffer, inp, points * 2); 94 | this.#workingBufferR = new Float32Array(this.#memory.buffer, outpR, points); 95 | this.#workingBufferI = new Float32Array(this.#memory.buffer, outpI, points); 96 | } 97 | 98 | /** 99 | * Get the input buffer. The formatting of data depends on what settings the FFT was initialized with. Real input will be an array 100 | * of `points` Float32's. Complex input will be an `Float32Array` with `points` `Float32` I/Q pairs interleaved. 101 | * @returns {Float32Array} Input buffer 102 | */ 103 | getInputBuffer() { 104 | return this.#inputBuffer 105 | } 106 | 107 | /** 108 | * Get output buffer(s) 109 | * @returns {Array | Float32Array} Separate I/Q arrays, or single magnitude square array 110 | */ 111 | getOutputBuffer() { 112 | if (this.#realOut) 113 | return this.#workingBufferR; 114 | else 115 | return [this.#workingBufferR, this.#workingBufferI]; 116 | } 117 | 118 | /** 119 | * Run a single FFT transform. 120 | */ 121 | run() { 122 | this.#run(); 123 | } 124 | } 125 | 126 | /** 127 | * 128 | * @param {Number} points Number of points in FFT 129 | * @param {String} inputType Type of inputs. Can be "real" or "complex". 130 | * @param {String} outputType Type of output. Can be "magsqr" or "complex". "magsqr" returns an array of Float32 containing |Fi]^2 131 | * @param {String} wasmPath Path to .wasm file 132 | * @param {Boolean} shift Whether to shift so omega(0) is at index points/2 133 | * @param {Number} scale Scale input by this value 134 | * @returns {Promise} 135 | */ 136 | async function getFFTAsync(points, inputType = "complex", outputType = "complex", wasmPath = "/fft.wasm", shift = false, scale = 1.0) { 137 | const importObject = { 138 | Math: Math, 139 | config: { 140 | points: points, 141 | inputType: {"complex": 0, "real": 1}[inputType], 142 | outputType: {"magsqr": 0, "complex": 1}[outputType], 143 | shift: shift ? 1 : 0, 144 | scale: scale 145 | } 146 | }; 147 | 148 | return await fetch(wasmPath).then(async resp => { 149 | return await WebAssembly.instantiate(await resp.arrayBuffer(), importObject).then((obj) => { 150 | return new FFTWasmImpl(points, outputType != "complex", obj); 151 | }); 152 | }); 153 | } 154 | 155 | /** 156 | * 157 | * @param {WebAssembly.Module} module Compiled module 158 | * @param {Number} points Number of points in FFT 159 | * @param {String} inputType Type of inputs. Can be "real" or "complex". 160 | * @param {String} outputType Type of output. Can be "magsqr" or "complex". "magsqr" returns an array of Float32 containing |Fi]^2 161 | * @param {Boolean} shift Whether to shift so omega(0) is at index points/2 162 | * @param {Number} scale Scale input by this value 163 | * @returns {FFTWasmImpl} 164 | */ 165 | export function getFFT(module, points, inputType = "complex", outputType = "complex", shift = false, scale = 1.0) { 166 | const importObject = { 167 | Math: Math, 168 | config: { 169 | points: points, 170 | inputType: {"complex": 0, "real": 1}[inputType], 171 | outputType: {"magsqr": 0, "complex": 1}[outputType], 172 | shift: shift ? 1 : 0, 173 | scale: scale 174 | } 175 | }; 176 | 177 | var instance = new WebAssembly.Instance(module, importObject); 178 | return new FFTWasmImpl(points, outputType != "complex", instance); 179 | } 180 | -------------------------------------------------------------------------------- /site/src/components/BenchmarkSection.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | useState, 3 | useEffect, 4 | Dispatch, 5 | SetStateAction, 6 | ChangeEvent, 7 | } from "react"; 8 | import FFTSizeInput from "./FFTSizeInputButton"; 9 | import ResultsSection from "./ResultsSection"; 10 | import Button from "./Button"; 11 | import webfft, { BrowserCapabilities, ProfileResult } from "webfft"; 12 | 13 | interface Props { 14 | fftSize: number; 15 | setFftSize: Dispatch>; 16 | duration: number; 17 | setDuration: Dispatch>; 18 | handleClearAppState: Dispatch; 19 | } 20 | 21 | function BenchmarkSection({ 22 | fftSize, 23 | setFftSize, 24 | duration, 25 | setDuration, 26 | handleClearAppState, 27 | }: Props) { 28 | const [showSettings, setShowSettings] = useState(false); 29 | const [browserCapabilities, setBrowserInfo] = useState({ 30 | browserName: "Unknown", 31 | browserVersion: "Unknown", 32 | osName: "Unknown", 33 | osVersion: "Unknown", 34 | wasm: false, 35 | relaxedSimd: false, 36 | simd: false, 37 | }); 38 | const [simdSupport, setSimdSupport] = useState(false); 39 | const [relaxedSimdSupport, setRelaxedSimdSupport] = useState(false); 40 | const [wasmSupport, setWasmSupport] = useState(false); 41 | const [benchmarkData, setBenchmarkData] = useState(null); 42 | const [loading, setLoading] = useState(false); 43 | 44 | useEffect(() => { 45 | const webfftInstance = new webfft(128); 46 | webfftInstance 47 | .checkBrowserCapabilities() 48 | .then((browserResult) => { 49 | setBrowserInfo(browserResult); 50 | }) 51 | .catch((error) => { 52 | console.error("Failed to check browser capabilities:", error); 53 | }); 54 | webfftInstance.dispose(); 55 | }, []); 56 | 57 | useEffect(() => { 58 | if (browserCapabilities) { 59 | setSimdSupport(browserCapabilities.simd); 60 | setRelaxedSimdSupport(browserCapabilities.relaxedSimd); 61 | setWasmSupport(browserCapabilities.wasm); 62 | } 63 | }, [browserCapabilities]); 64 | 65 | const handleDurationChange = (event: ChangeEvent) => { 66 | var val = parseInt(event.target.value); 67 | if (val > 1) { 68 | setDuration(val); 69 | } else { 70 | setDuration(1); 71 | } 72 | }; 73 | 74 | const handleClearState = () => { 75 | setBenchmarkData(null); 76 | handleClearAppState(true); 77 | }; 78 | 79 | const renderBrowserInfo = () => { 80 | if (browserCapabilities) { 81 | return ( 82 |
    83 |
    84 | Browser: 85 | {browserCapabilities.browserName ?? "Unknown"}{" "} 86 | {{browserCapabilities.browserVersion} ?? ""} 87 |
    88 |
    89 | OS: 90 | {browserCapabilities.osName ?? "Unknown"}{" "} 91 | {{browserCapabilities.osVersion} ?? ""} 92 |
    93 |
    94 | ); 95 | } 96 | return ( 97 | 98 | Browser not recognized or detected. 99 | 100 | ); 101 | }; 102 | 103 | // This will run when you click the run benchmark button 104 | useEffect(() => { 105 | if (loading) { 106 | // allow UI to update before starting long running task 107 | const fftWorker = new Worker( 108 | new URL("../utils/webworker.tsx", import.meta.url), 109 | { type: "module" }, 110 | ); 111 | 112 | fftWorker.postMessage([fftSize, duration]); 113 | 114 | fftWorker.onmessage = (e: MessageEvent) => { 115 | const profileObj = e.data; 116 | 117 | const wasmColor = "hsl(200, 100%, 50%, 0.75)"; 118 | const jsColor = "hsla(320, 80%, 50%, 0.8)"; 119 | 120 | const dataWithLabels = profileObj.subLibraries.map((label, index) => ({ 121 | label: label, 122 | value: profileObj.fftsPerSecond[index], 123 | })); 124 | 125 | // Sort the pairs in descending order based on the data values 126 | dataWithLabels.sort((a, b) => b.value - a.value); 127 | 128 | // Create arrays of labels and data, preserving the new order 129 | const sortedLabels = dataWithLabels.map((item) => item.label); 130 | const sortedData = dataWithLabels.map((item) => item.value); 131 | 132 | // Define the label subsets for each category 133 | const wasmLabels = sortedLabels.filter((label) => 134 | label.includes("Wasm"), 135 | ); 136 | const jsLabels = sortedLabels.filter((label) => 137 | label.includes("Javascript"), 138 | ); 139 | 140 | // Create arrays of the WASM and JS data, maintaining the new order, and only including a data point if the label matches the category 141 | const wasmData = sortedLabels.map((label, index) => 142 | wasmLabels.includes(label) ? sortedData[index] : null, 143 | ); 144 | 145 | const jsData = sortedLabels.map((label, index) => 146 | jsLabels.includes(label) ? sortedData[index] : null, 147 | ); 148 | 149 | // Create datasets for the bar chart 150 | const datasets = [ 151 | { 152 | label: "WASM", 153 | data: wasmData, 154 | backgroundColor: wasmColor, 155 | borderColor: "hsla(0, 0%, 80%, 0.9)", 156 | borderWidth: 1, 157 | }, 158 | { 159 | label: "Javascript", 160 | data: jsData, 161 | backgroundColor: jsColor, 162 | borderColor: "hsla(0, 0%, 80%, 0.9)", 163 | borderWidth: 1, 164 | }, 165 | ]; 166 | 167 | setBenchmarkData({ 168 | labels: sortedLabels, 169 | datasets: datasets, 170 | }); 171 | 172 | setLoading(false); 173 | }; 174 | 175 | return () => { 176 | fftWorker.terminate(); 177 | }; 178 | } 179 | }, [loading, fftSize, duration]); 180 | 181 | return ( 182 |
    183 |
    184 |

    188 | Benchmark your browser 189 |

    190 | 191 |
    192 | 202 | 203 | 210 | 217 |
    218 | 219 | {showSettings && ( 220 |
    221 |
    222 | FFT Size 223 | 224 |
    225 | 226 |
    227 | 245 |
    246 | 247 |
    248 | 249 | Browser Information:
    250 | {renderBrowserInfo()} 251 |
    252 | 253 | SIMD Support:
    254 | 255 | {simdSupport ? "Supported" : "Not supported"} 256 | 257 |
    258 | 259 | Relaxed SIMD:
    260 | 261 | {relaxedSimdSupport ? "Enabled" : "Disabled"} 262 | 263 |
    264 | 265 | WASM Support:
    266 | 267 | {wasmSupport ? "Supported" : "Not supported"} 268 | 269 |
    270 |
    271 |
    272 | )} 273 |
    274 | 275 | 276 |
    277 | ); 278 | } 279 | 280 | export default BenchmarkSection; 281 | --------------------------------------------------------------------------------