@emnudge/wat-fft 0.1.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +60 -51
- package/dist/fft_combined.wasm +0 -0
- package/dist/fft_real_combined.wasm +0 -0
- package/dist/fft_real_f32_dual.wasm +0 -0
- package/dist/fft_split_native_f32.wasm +0 -0
- package/dist/fft_stockham_f32_dual.wasm +0 -0
- package/package.json +44 -27
- package/dist/combined_fast.wasm +0 -0
- package/dist/combined_real.wasm +0 -0
- package/dist/combined_real_f32.wasm +0 -0
- package/dist/combined_real_opt.wasm +0 -0
- package/dist/combined_stockham.wasm +0 -0
- package/dist/combined_stockham_f32.wasm +0 -0
- package/dist/fft_fast_composed.wasm +0 -0
- package/dist/fft_radix4.wasm +0 -0
- package/dist/fft_real_combined_fma.wasm +0 -0
- package/dist/fft_real_radix4.wasm +0 -0
- package/dist/fft_recursive.wasm +0 -0
- package/dist/fft_stockham_composed.wasm +0 -0
- package/dist/reverse_bits.wasm +0 -0
- package/dist/swap.wasm +0 -0
package/README.md
CHANGED
|
@@ -6,17 +6,18 @@ A high-performance FFT implementation in WebAssembly Text format that **signific
|
|
|
6
6
|
|
|
7
7
|
### Complex FFT
|
|
8
8
|
|
|
9
|
-
Benchmarked against [
|
|
10
|
-
|
|
11
|
-
| Size | wat-fft (
|
|
12
|
-
| ------ |
|
|
13
|
-
| N=
|
|
14
|
-
| N=
|
|
15
|
-
| N=
|
|
16
|
-
| N=
|
|
17
|
-
| N=
|
|
18
|
-
| N=
|
|
19
|
-
| N=
|
|
9
|
+
Benchmarked against [pffft-wasm](https://www.npmjs.com/package/@echogarden/pffft-wasm) (PFFFT with SIMD):
|
|
10
|
+
|
|
11
|
+
| Size | wat-fft (f32) | pffft-wasm (f32) | Speedup |
|
|
12
|
+
| ------ | -------------------- | ---------------- | -------- |
|
|
13
|
+
| N=16 | **16,700,000 ops/s** | 13,900,000 ops/s | **+20%** |
|
|
14
|
+
| N=64 | **6,040,000 ops/s** | 4,440,000 ops/s | **+36%** |
|
|
15
|
+
| N=128 | **3,040,000 ops/s** | 1,950,000 ops/s | **+56%** |
|
|
16
|
+
| N=256 | **1,640,000 ops/s** | 980,000 ops/s | **+67%** |
|
|
17
|
+
| N=512 | **736,000 ops/s** | 404,000 ops/s | **+82%** |
|
|
18
|
+
| N=1024 | **365,000 ops/s** | 201,000 ops/s | **+81%** |
|
|
19
|
+
| N=2048 | **163,000 ops/s** | 84,000 ops/s | **+94%** |
|
|
20
|
+
| N=4096 | **81,000 ops/s** | 41,000 ops/s | **+95%** |
|
|
20
21
|
|
|
21
22
|
```mermaid
|
|
22
23
|
---
|
|
@@ -26,35 +27,36 @@ config:
|
|
|
26
27
|
height: 400
|
|
27
28
|
themeVariables:
|
|
28
29
|
xyChart:
|
|
29
|
-
plotColorPalette: "#4ade80, #60a5fa, #a855f7, #f87171"
|
|
30
|
+
plotColorPalette: "#4ade80, #60a5fa, #f59e0b, #a855f7, #f87171"
|
|
30
31
|
---
|
|
31
32
|
xychart-beta
|
|
32
33
|
title "Complex FFT Performance (Million ops/s)"
|
|
33
|
-
x-axis [N=64, N=128, N=256, N=512, N=1024, N=2048, N=4096]
|
|
34
|
-
y-axis "Million ops/s" 0 -->
|
|
35
|
-
line [3.83, 1.
|
|
36
|
-
line [
|
|
37
|
-
line [
|
|
38
|
-
line [
|
|
34
|
+
x-axis [N=16, N=64, N=128, N=256, N=512, N=1024, N=2048, N=4096]
|
|
35
|
+
y-axis "Million ops/s" 0 --> 18
|
|
36
|
+
line [17.57, 3.83, 1.74, 0.96, 0.37, 0.19, 0.080, 0.044]
|
|
37
|
+
line [16.68, 6.04, 3.04, 1.64, 0.74, 0.36, 0.163, 0.081]
|
|
38
|
+
line [13.88, 4.44, 1.95, 0.98, 0.40, 0.20, 0.084, 0.041]
|
|
39
|
+
line [11.50, 2.80, 1.07, 0.56, 0.22, 0.11, 0.047, 0.023]
|
|
40
|
+
line [6.05, 1.86, 0.80, 0.44, 0.18, 0.10, 0.041, 0.022]
|
|
39
41
|
```
|
|
40
42
|
|
|
41
|
-
> 🟢 **wat-fft f64** · 🔵 **wat-fft f32** · 🟣 **fft.js** · 🔴 **kissfft-js**
|
|
43
|
+
> 🟢 **wat-fft f64** · 🔵 **wat-fft f32** · 🟠 **pffft-wasm** · 🟣 **fft.js** · 🔴 **kissfft-js**
|
|
42
44
|
|
|
43
|
-
**
|
|
45
|
+
**wat-fft f32 beats pffft-wasm by 20-95%** across all sizes. It's also **2-3x faster** than fft.js (the fastest pure JS). **Choose f64** (`fft_combined.wasm`) for double precision. **Choose f32** (`fft_stockham_f32_dual.wasm`) for maximum single-precision speed.
|
|
44
46
|
|
|
45
47
|
### Real FFT
|
|
46
48
|
|
|
47
|
-
Benchmarked against [fftw-js](https://www.npmjs.com/package/fftw-js)
|
|
49
|
+
Benchmarked against [pffft-wasm](https://www.npmjs.com/package/@echogarden/pffft-wasm) and [fftw-js](https://www.npmjs.com/package/fftw-js):
|
|
48
50
|
|
|
49
|
-
| Size | wat-fft (f32) | fftw-js (f32) |
|
|
50
|
-
| ------ | ------------------- | --------------- |
|
|
51
|
-
| N=64 | **6,
|
|
52
|
-
| N=128 | **4,
|
|
53
|
-
| N=256 | **2,
|
|
54
|
-
| N=512 | **1,
|
|
55
|
-
| N=1024 | **
|
|
56
|
-
| N=2048 | **
|
|
57
|
-
| N=4096 | **
|
|
51
|
+
| Size | wat-fft (f32) | pffft-wasm (f32) | fftw-js (f32) | vs best |
|
|
52
|
+
| ------ | ------------------- | ------------------- | --------------- | ----------- |
|
|
53
|
+
| N=64 | 6,640,000 ops/s | **6,970,000 ops/s** | 6,660,000 ops/s | -5% (pffft) |
|
|
54
|
+
| N=128 | **4,510,000 ops/s** | 3,490,000 ops/s | 4,290,000 ops/s | **+5%** |
|
|
55
|
+
| N=256 | **2,280,000 ops/s** | 1,920,000 ops/s | 1,440,000 ops/s | **+19%** |
|
|
56
|
+
| N=512 | **1,110,000 ops/s** | 830,000 ops/s | 850,000 ops/s | **+31%** |
|
|
57
|
+
| N=1024 | **531,000 ops/s** | 419,000 ops/s | 458,000 ops/s | **+16%** |
|
|
58
|
+
| N=2048 | **274,000 ops/s** | 179,000 ops/s | 222,000 ops/s | **+23%** |
|
|
59
|
+
| N=4096 | **126,000 ops/s** | 89,000 ops/s | 106,000 ops/s | **+19%** |
|
|
58
60
|
|
|
59
61
|
```mermaid
|
|
60
62
|
---
|
|
@@ -64,21 +66,22 @@ config:
|
|
|
64
66
|
height: 400
|
|
65
67
|
themeVariables:
|
|
66
68
|
xyChart:
|
|
67
|
-
plotColorPalette: "#4ade80, #60a5fa, #f87171, #a855f7"
|
|
69
|
+
plotColorPalette: "#4ade80, #60a5fa, #f87171, #f59e0b, #a855f7"
|
|
68
70
|
---
|
|
69
71
|
xychart-beta
|
|
70
72
|
title "Real FFT Performance (Million ops/s)"
|
|
71
73
|
x-axis [N=64, N=128, N=256, N=512, N=1024, N=2048, N=4096]
|
|
72
74
|
y-axis "Million ops/s" 0 --> 8
|
|
73
|
-
line [4.
|
|
74
|
-
line [6.
|
|
75
|
-
line [6.
|
|
76
|
-
line [
|
|
75
|
+
line [4.70, 2.95, 1.28, 0.76, 0.29, 0.16, 0.063]
|
|
76
|
+
line [6.64, 4.51, 2.28, 1.11, 0.53, 0.27, 0.126]
|
|
77
|
+
line [6.66, 4.29, 1.44, 0.85, 0.46, 0.22, 0.106]
|
|
78
|
+
line [6.97, 3.49, 1.92, 0.83, 0.42, 0.18, 0.089]
|
|
79
|
+
line [2.93, 1.79, 0.76, 0.42, 0.17, 0.094, 0.039]
|
|
77
80
|
```
|
|
78
81
|
|
|
79
|
-
> 🟢 **wat-fft f64** · 🔵 **wat-fft f32** · 🔴 **fftw-js** · 🟣 **kissfft-js**
|
|
82
|
+
> 🟢 **wat-fft f64** · 🔵 **wat-fft f32** · 🔴 **fftw-js** · 🟠 **pffft-wasm** · 🟣 **kissfft-js**
|
|
80
83
|
|
|
81
|
-
**wat-fft f32 beats
|
|
84
|
+
**wat-fft f32 beats all competitors at N≥128** (+5% to +31%). At N=64, pffft-wasm has a slight edge. **Choose f64** (`fft_real_combined.wasm`) for double precision. **Choose f32** (`fft_real_f32_dual.wasm`) for maximum single-precision speed.
|
|
82
85
|
|
|
83
86
|
## Quick Start
|
|
84
87
|
|
|
@@ -110,9 +113,9 @@ cargo install wasm-tools
|
|
|
110
113
|
```javascript
|
|
111
114
|
import fs from "fs";
|
|
112
115
|
|
|
113
|
-
// Load the WASM module
|
|
116
|
+
// Load the WASM module
|
|
114
117
|
// No JavaScript imports needed - trig functions are computed inline
|
|
115
|
-
const wasmBuffer = fs.readFileSync("dist/
|
|
118
|
+
const wasmBuffer = fs.readFileSync("dist/fft_combined.wasm");
|
|
116
119
|
const wasmModule = await WebAssembly.compile(wasmBuffer);
|
|
117
120
|
const instance = await WebAssembly.instantiate(wasmModule);
|
|
118
121
|
const fft = instance.exports;
|
|
@@ -127,22 +130,29 @@ for (let i = 0; i < N; i++) {
|
|
|
127
130
|
|
|
128
131
|
// Compute FFT
|
|
129
132
|
fft.precompute_twiddles(N);
|
|
130
|
-
fft.
|
|
133
|
+
fft.fft(N);
|
|
131
134
|
|
|
132
135
|
// Results are in-place in data[]
|
|
133
136
|
console.log("DC component:", data[0], data[1]);
|
|
137
|
+
|
|
138
|
+
// Compute inverse FFT (roundtrip back to original)
|
|
139
|
+
fft.ifft(N);
|
|
140
|
+
console.log("Recovered signal:", data[0], data[1]);
|
|
134
141
|
```
|
|
135
142
|
|
|
136
143
|
## Implementations
|
|
137
144
|
|
|
138
145
|
**Recommended modules:**
|
|
139
146
|
|
|
140
|
-
| Module | Use Case
|
|
141
|
-
| ---------------------------- |
|
|
142
|
-
| `fft_combined.wasm` | Complex FFT (any size)
|
|
143
|
-
| `fft_real_combined.wasm` | Real FFT (any size)
|
|
144
|
-
| `fft_stockham_f32_dual.wasm` | Complex FFT (
|
|
145
|
-
| `
|
|
147
|
+
| Module | Use Case | Precision | Inverse |
|
|
148
|
+
| ---------------------------- | -------------------------- | --------- | ------------ |
|
|
149
|
+
| `fft_combined.wasm` | Complex FFT (any size) | f64 | `ifft` |
|
|
150
|
+
| `fft_real_combined.wasm` | Real FFT (any size) | f64 | - |
|
|
151
|
+
| `fft_stockham_f32_dual.wasm` | Complex FFT (interleaved) | f32 | `ifft` |
|
|
152
|
+
| `fft_split_native_f32.wasm` | Complex FFT (split format) | f32 | `ifft_split` |
|
|
153
|
+
| `fft_real_f32_dual.wasm` | Real FFT (fastest) | f32 | `irfft` |
|
|
154
|
+
|
|
155
|
+
**Split-format** (`fft_split_native_f32.wasm`) stores real and imaginary parts in separate arrays, enabling 4 complex numbers per SIMD operation. Performance is similar to interleaved format - use when your data is already in split format to avoid conversion overhead.
|
|
146
156
|
|
|
147
157
|
See [docs/IMPLEMENTATIONS.md](docs/IMPLEMENTATIONS.md) for detailed documentation of all modules, usage examples, and numerical accuracy information.
|
|
148
158
|
|
|
@@ -166,7 +176,6 @@ npm run bench:rfft # Run real FFT benchmarks
|
|
|
166
176
|
npm run bench:rfft32 # Run f32 real FFT benchmarks
|
|
167
177
|
npm run test:fft # Run comprehensive FFT tests
|
|
168
178
|
npm run test:rfft # Run real FFT tests
|
|
169
|
-
npm run test:permutation # Test permutation algorithms
|
|
170
179
|
```
|
|
171
180
|
|
|
172
181
|
## Development Tools
|
|
@@ -207,11 +216,11 @@ The comprehensive FFT test suite (`tests/fft.test.js`) tests all implementations
|
|
|
207
216
|
npm run test:fft
|
|
208
217
|
```
|
|
209
218
|
|
|
210
|
-
### Test a
|
|
219
|
+
### Test a specific size and pattern
|
|
211
220
|
|
|
212
221
|
```bash
|
|
213
|
-
node tests/fft.test.js
|
|
214
|
-
node tests/fft.test.js
|
|
222
|
+
node tests/fft.test.js 64 random
|
|
223
|
+
node tests/fft.test.js 256 impulse
|
|
215
224
|
```
|
|
216
225
|
|
|
217
226
|
### Input patterns
|
|
@@ -223,7 +232,7 @@ node tests/fft.test.js --impl fast 256 impulse
|
|
|
223
232
|
|
|
224
233
|
### Test sizes
|
|
225
234
|
|
|
226
|
-
Powers of 2: 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096
|
|
235
|
+
Powers of 2: 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096, 8192
|
|
227
236
|
|
|
228
237
|
## License
|
|
229
238
|
|
package/dist/fft_combined.wasm
CHANGED
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,48 +1,50 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@emnudge/wat-fft",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "High-performance FFT in WebAssembly",
|
|
5
|
-
"license": "ISC",
|
|
6
|
-
"author": "EmNudge",
|
|
7
|
-
"repository": {
|
|
8
|
-
"type": "git",
|
|
9
|
-
"url": "https://github.com/emnudge/wat-fft"
|
|
10
|
-
},
|
|
11
5
|
"keywords": [
|
|
6
|
+
"audio",
|
|
7
|
+
"dsp",
|
|
12
8
|
"fft",
|
|
13
|
-
"rfft",
|
|
14
9
|
"fourier",
|
|
10
|
+
"rfft",
|
|
11
|
+
"signal-processing",
|
|
15
12
|
"transform",
|
|
16
13
|
"wasm",
|
|
17
|
-
"webassembly"
|
|
18
|
-
"dsp",
|
|
19
|
-
"signal-processing",
|
|
20
|
-
"audio"
|
|
14
|
+
"webassembly"
|
|
21
15
|
],
|
|
22
|
-
"
|
|
23
|
-
"
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
16
|
+
"license": "ISC",
|
|
17
|
+
"author": "EmNudge",
|
|
18
|
+
"repository": {
|
|
19
|
+
"type": "git",
|
|
20
|
+
"url": "git+https://github.com/EmNudge/wat-fft.git"
|
|
27
21
|
},
|
|
28
22
|
"files": [
|
|
29
23
|
"index.js",
|
|
30
24
|
"dist/*.wasm"
|
|
31
25
|
],
|
|
32
|
-
"
|
|
33
|
-
|
|
26
|
+
"type": "module",
|
|
27
|
+
"exports": {
|
|
28
|
+
".": {
|
|
29
|
+
"import": "./index.js"
|
|
30
|
+
}
|
|
34
31
|
},
|
|
35
32
|
"scripts": {
|
|
36
|
-
"test": "npm run test:
|
|
37
|
-
"test:all": "npm run test && npm run test:rfft && npm run test:
|
|
33
|
+
"test": "npm run test:fft && npm run test:combined && npm run test:ifft",
|
|
34
|
+
"test:all": "npm run test && npm run test:rfft && npm run test:boundary && npm run test:correctness && npm run test:output-order && npm run test:f32 && npm run test:twiddles && npm run test:golden && npm run test:perbin && npm run test:split && npm run test:thirdparty && npm run test:bench-correctness",
|
|
35
|
+
"test:split": "node --test tests/fft_split_native.test.js",
|
|
36
|
+
"test:thirdparty": "node --test tests/third-party-correctness.test.js",
|
|
37
|
+
"test:bench-correctness": "node --test tests/benchmark-correctness.test.js",
|
|
38
|
+
"test:ifft": "node tests/ifft.test.js",
|
|
39
|
+
"test:twiddles": "node tests/twiddle_validation.test.js",
|
|
40
|
+
"test:property": "node --test tests/property_based.test.js",
|
|
41
|
+
"test:golden": "node --test tests/golden_reference.test.js",
|
|
42
|
+
"test:perbin": "node --test tests/per_bin_validation.test.js",
|
|
38
43
|
"test:output-order": "node --test tests/output-order.test.js",
|
|
39
|
-
"test:swap": "node tests/swap.test.js",
|
|
40
|
-
"test:reverse_bits": "node tests/reverse_bits.test.js",
|
|
41
|
-
"test:permutation": "node tests/permutation.test.js",
|
|
42
44
|
"test:fft": "node tests/fft.test.js",
|
|
45
|
+
"test:combined": "node tests/combined.test.js",
|
|
43
46
|
"test:rfft": "node --test tests/rfft.test.js",
|
|
44
47
|
"test:f32": "node tests/fft_f32_dual.test.js",
|
|
45
|
-
"test:property": "node tests/fft.property.test.js",
|
|
46
48
|
"test:boundary": "node --test tests/boundary.test.js",
|
|
47
49
|
"test:butterfly": "node tools/butterfly_tester.js",
|
|
48
50
|
"test:correctness": "node --test tests/correctness/*.test.js",
|
|
@@ -52,20 +54,28 @@
|
|
|
52
54
|
"test:correctness:shift": "node --test tests/correctness/fft.shift.test.js",
|
|
53
55
|
"test:correctness:reference": "node --test tests/correctness/fft.reference.test.js",
|
|
54
56
|
"test:correctness:known": "node --test tests/correctness/fft.known-values.test.js",
|
|
55
|
-
"debug:
|
|
57
|
+
"debug:compare": "node tools/wasm_compare.js",
|
|
56
58
|
"debug:index": "node tools/index_visualizer.js",
|
|
57
59
|
"debug:perm": "node tools/permutation_validator.js",
|
|
58
60
|
"debug:ref": "node tools/stockham_reference.js",
|
|
61
|
+
"debug:split": "node tests/debug_split.js",
|
|
59
62
|
"build": "node build.js",
|
|
60
63
|
"bench": "node benchmarks/fft.bench.js",
|
|
61
64
|
"bench:rfft": "node benchmarks/rfft.bench.js",
|
|
62
65
|
"bench:f32": "node benchmarks/fft_f32_dual.bench.js",
|
|
63
66
|
"bench:rfft32": "node benchmarks/rfft_f32_dual.bench.js",
|
|
67
|
+
"bench:browser": "vitest bench --run",
|
|
68
|
+
"bench:browser:ci": "vitest bench --run --outputJson=benchmark-results.json",
|
|
69
|
+
"bench:check": "node scripts/check-benchmarks.js",
|
|
64
70
|
"lint": "oxlint .",
|
|
71
|
+
"lint:all": "npm run lint && npm run lint:wasm",
|
|
72
|
+
"lint:wasm": "node tools/lint-wasm-dead-code.js",
|
|
65
73
|
"format": "oxfmt --write .",
|
|
66
74
|
"format:check": "oxfmt --check ."
|
|
67
75
|
},
|
|
68
76
|
"devDependencies": {
|
|
77
|
+
"@echogarden/pffft-wasm": "^0.4.2",
|
|
78
|
+
"@vitest/browser": "^3.0.0",
|
|
69
79
|
"fast-check": "^4.5.3",
|
|
70
80
|
"fft-js": "^0.0.12",
|
|
71
81
|
"fft.js": "^4.0.4",
|
|
@@ -73,6 +83,13 @@
|
|
|
73
83
|
"kissfft-js": "^0.1.8",
|
|
74
84
|
"kissfft-wasm": "^2.0.1",
|
|
75
85
|
"oxfmt": "^0.26.0",
|
|
76
|
-
"oxlint": "^1.41.0"
|
|
86
|
+
"oxlint": "^1.41.0",
|
|
87
|
+
"playwright": "^1.50.0",
|
|
88
|
+
"typescript": "^5.7.0",
|
|
89
|
+
"vitest": "^3.0.0",
|
|
90
|
+
"webfft": "^1.0.3"
|
|
91
|
+
},
|
|
92
|
+
"engines": {
|
|
93
|
+
"node": ">=18"
|
|
77
94
|
}
|
|
78
95
|
}
|
package/dist/combined_fast.wasm
DELETED
|
Binary file
|
package/dist/combined_real.wasm
DELETED
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/dist/fft_radix4.wasm
DELETED
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/dist/fft_recursive.wasm
DELETED
|
Binary file
|
|
Binary file
|
package/dist/reverse_bits.wasm
DELETED
|
Binary file
|
package/dist/swap.wasm
DELETED
|
Binary file
|