dspx 0.1.1-alpha.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/.github/workflows/ci.yml +185 -0
- package/.vscode/c_cpp_properties.json +17 -0
- package/.vscode/settings.json +68 -0
- package/.vscode/tasks.json +28 -0
- package/DISCLAIMER.md +32 -0
- package/LICENSE +21 -0
- package/README.md +1803 -0
- package/ROADMAP.md +192 -0
- package/TECHNICAL_DEBT.md +165 -0
- package/binding.gyp +65 -0
- package/docs/ADVANCED_LOGGER_FEATURES.md +598 -0
- package/docs/AUTHENTICATION_SECURITY.md +396 -0
- package/docs/BACKEND_IMPROVEMENTS.md +399 -0
- package/docs/CHEBYSHEV_BIQUAD_EQ_IMPLEMENTATION.md +405 -0
- package/docs/FFT_IMPLEMENTATION.md +490 -0
- package/docs/FFT_IMPROVEMENTS_SUMMARY.md +387 -0
- package/docs/FFT_USER_GUIDE.md +494 -0
- package/docs/FILTERS_IMPLEMENTATION.md +260 -0
- package/docs/FILTER_API_GUIDE.md +418 -0
- package/docs/FIR_SIMD_OPTIMIZATION.md +175 -0
- package/docs/LOGGER_API_REFERENCE.md +350 -0
- package/docs/NOTCH_FILTER_QUICK_REF.md +121 -0
- package/docs/PHASE2_TESTS_AND_NOTCH_FILTER.md +341 -0
- package/docs/PHASES_5_7_SUMMARY.md +403 -0
- package/docs/PIPELINE_FILTER_INTEGRATION.md +446 -0
- package/docs/SIMD_OPTIMIZATIONS.md +211 -0
- package/docs/TEST_MIGRATION_SUMMARY.md +173 -0
- package/docs/TIMESERIES_IMPLEMENTATION_SUMMARY.md +322 -0
- package/docs/TIMESERIES_QUICK_REF.md +85 -0
- package/docs/advanced.md +559 -0
- package/docs/time-series-guide.md +617 -0
- package/docs/time-series-migration.md +376 -0
- package/jest.config.js +37 -0
- package/package.json +42 -0
- package/prebuilds/linux-x64/dsp-ts-redis.node +0 -0
- package/prebuilds/win32-x64/dsp-ts-redis.node +0 -0
- package/scripts/test.js +24 -0
- package/src/build/dsp-ts-redis.node +0 -0
- package/src/native/DspPipeline.cc +675 -0
- package/src/native/DspPipeline.h +44 -0
- package/src/native/FftBindings.cc +817 -0
- package/src/native/FilterBindings.cc +1001 -0
- package/src/native/IDspStage.h +53 -0
- package/src/native/adapters/InterpolatorStage.h +201 -0
- package/src/native/adapters/MeanAbsoluteValueStage.h +289 -0
- package/src/native/adapters/MovingAverageStage.h +306 -0
- package/src/native/adapters/RectifyStage.h +88 -0
- package/src/native/adapters/ResamplerStage.h +238 -0
- package/src/native/adapters/RmsStage.h +299 -0
- package/src/native/adapters/SscStage.h +121 -0
- package/src/native/adapters/VarianceStage.h +307 -0
- package/src/native/adapters/WampStage.h +114 -0
- package/src/native/adapters/WaveformLengthStage.h +115 -0
- package/src/native/adapters/ZScoreNormalizeStage.h +326 -0
- package/src/native/core/FftEngine.cc +441 -0
- package/src/native/core/FftEngine.h +224 -0
- package/src/native/core/FirFilter.cc +324 -0
- package/src/native/core/FirFilter.h +149 -0
- package/src/native/core/IirFilter.cc +576 -0
- package/src/native/core/IirFilter.h +210 -0
- package/src/native/core/MovingAbsoluteValueFilter.cc +17 -0
- package/src/native/core/MovingAbsoluteValueFilter.h +135 -0
- package/src/native/core/MovingAverageFilter.cc +18 -0
- package/src/native/core/MovingAverageFilter.h +135 -0
- package/src/native/core/MovingFftFilter.cc +291 -0
- package/src/native/core/MovingFftFilter.h +203 -0
- package/src/native/core/MovingVarianceFilter.cc +194 -0
- package/src/native/core/MovingVarianceFilter.h +114 -0
- package/src/native/core/MovingZScoreFilter.cc +215 -0
- package/src/native/core/MovingZScoreFilter.h +113 -0
- package/src/native/core/Policies.h +352 -0
- package/src/native/core/RmsFilter.cc +18 -0
- package/src/native/core/RmsFilter.h +131 -0
- package/src/native/core/SscFilter.cc +16 -0
- package/src/native/core/SscFilter.h +137 -0
- package/src/native/core/WampFilter.cc +16 -0
- package/src/native/core/WampFilter.h +101 -0
- package/src/native/core/WaveformLengthFilter.cc +17 -0
- package/src/native/core/WaveformLengthFilter.h +98 -0
- package/src/native/utils/CircularBufferArray.cc +336 -0
- package/src/native/utils/CircularBufferArray.h +62 -0
- package/src/native/utils/CircularBufferVector.cc +145 -0
- package/src/native/utils/CircularBufferVector.h +45 -0
- package/src/native/utils/NapiUtils.cc +53 -0
- package/src/native/utils/NapiUtils.h +21 -0
- package/src/native/utils/SimdOps.h +870 -0
- package/src/native/utils/SlidingWindowFilter.cc +239 -0
- package/src/native/utils/SlidingWindowFilter.h +159 -0
- package/src/native/utils/TimeSeriesBuffer.cc +205 -0
- package/src/native/utils/TimeSeriesBuffer.h +140 -0
- package/src/ts/CircularLogBuffer.ts +87 -0
- package/src/ts/DriftDetector.ts +331 -0
- package/src/ts/TopicRouter.ts +428 -0
- package/src/ts/__tests__/AdvancedDsp.test.ts +585 -0
- package/src/ts/__tests__/AuthAndEdgeCases.test.ts +241 -0
- package/src/ts/__tests__/Chaining.test.ts +387 -0
- package/src/ts/__tests__/ChebyshevBiquad.test.ts +229 -0
- package/src/ts/__tests__/CircularLogBuffer.test.ts +158 -0
- package/src/ts/__tests__/DriftDetector.test.ts +389 -0
- package/src/ts/__tests__/Fft.test.ts +484 -0
- package/src/ts/__tests__/ListState.test.ts +153 -0
- package/src/ts/__tests__/Logger.test.ts +208 -0
- package/src/ts/__tests__/LoggerAdvanced.test.ts +319 -0
- package/src/ts/__tests__/LoggerMinor.test.ts +247 -0
- package/src/ts/__tests__/MeanAbsoluteValue.test.ts +398 -0
- package/src/ts/__tests__/MovingAverage.test.ts +322 -0
- package/src/ts/__tests__/RMS.test.ts +315 -0
- package/src/ts/__tests__/Rectify.test.ts +272 -0
- package/src/ts/__tests__/Redis.test.ts +456 -0
- package/src/ts/__tests__/SlopeSignChange.test.ts +166 -0
- package/src/ts/__tests__/Tap.test.ts +164 -0
- package/src/ts/__tests__/TimeBasedExpiration.test.ts +124 -0
- package/src/ts/__tests__/TimeBasedRmsAndMav.test.ts +231 -0
- package/src/ts/__tests__/TimeBasedVarianceAndZScore.test.ts +284 -0
- package/src/ts/__tests__/TimeSeries.test.ts +254 -0
- package/src/ts/__tests__/TopicRouter.test.ts +332 -0
- package/src/ts/__tests__/TopicRouterAdvanced.test.ts +483 -0
- package/src/ts/__tests__/TopicRouterPriority.test.ts +487 -0
- package/src/ts/__tests__/Variance.test.ts +509 -0
- package/src/ts/__tests__/WaveformLength.test.ts +147 -0
- package/src/ts/__tests__/WillisonAmplitude.test.ts +197 -0
- package/src/ts/__tests__/ZScoreNormalize.test.ts +459 -0
- package/src/ts/advanced-dsp.ts +566 -0
- package/src/ts/backends.ts +1137 -0
- package/src/ts/bindings.ts +1225 -0
- package/src/ts/easter-egg.ts +42 -0
- package/src/ts/examples/MeanAbsoluteValue/test-state.ts +99 -0
- package/src/ts/examples/MeanAbsoluteValue/test-streaming.ts +269 -0
- package/src/ts/examples/MovingAverage/test-state.ts +85 -0
- package/src/ts/examples/MovingAverage/test-streaming.ts +188 -0
- package/src/ts/examples/RMS/test-state.ts +97 -0
- package/src/ts/examples/RMS/test-streaming.ts +253 -0
- package/src/ts/examples/Rectify/test-state.ts +107 -0
- package/src/ts/examples/Rectify/test-streaming.ts +242 -0
- package/src/ts/examples/Variance/test-state.ts +195 -0
- package/src/ts/examples/Variance/test-streaming.ts +260 -0
- package/src/ts/examples/ZScoreNormalize/test-state.ts +277 -0
- package/src/ts/examples/ZScoreNormalize/test-streaming.ts +306 -0
- package/src/ts/examples/advanced-dsp-examples.ts +397 -0
- package/src/ts/examples/callbacks/advanced-router-features.ts +326 -0
- package/src/ts/examples/callbacks/benchmark-circular-buffer.ts +109 -0
- package/src/ts/examples/callbacks/monitoring-example.ts +265 -0
- package/src/ts/examples/callbacks/pipeline-callbacks-example.ts +137 -0
- package/src/ts/examples/callbacks/pooled-callbacks-example.ts +274 -0
- package/src/ts/examples/callbacks/priority-routing-example.ts +277 -0
- package/src/ts/examples/callbacks/production-topic-router.ts +214 -0
- package/src/ts/examples/callbacks/topic-based-logging.ts +161 -0
- package/src/ts/examples/chaining/test-chaining-redis.ts +113 -0
- package/src/ts/examples/chaining/test-chaining.ts +52 -0
- package/src/ts/examples/emg-features-example.ts +284 -0
- package/src/ts/examples/fft-example.ts +309 -0
- package/src/ts/examples/fft-examples.ts +349 -0
- package/src/ts/examples/filter-examples.ts +320 -0
- package/src/ts/examples/list-state-example.ts +131 -0
- package/src/ts/examples/logger-example.ts +91 -0
- package/src/ts/examples/notch-filter-examples.ts +243 -0
- package/src/ts/examples/phase5/drift-detection-example.ts +290 -0
- package/src/ts/examples/phase6-7/production-observability.ts +476 -0
- package/src/ts/examples/phase6-7/redis-timeseries-integration.ts +446 -0
- package/src/ts/examples/redis/redis-example.ts +202 -0
- package/src/ts/examples/redis-example.ts +202 -0
- package/src/ts/examples/simd-benchmark.ts +126 -0
- package/src/ts/examples/tap-debugging.ts +230 -0
- package/src/ts/examples/timeseries/comparison-example.ts +290 -0
- package/src/ts/examples/timeseries/iot-sensor-example.ts +143 -0
- package/src/ts/examples/timeseries/redis-streaming-example.ts +233 -0
- package/src/ts/examples/waveform-length-example.ts +139 -0
- package/src/ts/fft.ts +722 -0
- package/src/ts/filters.ts +1078 -0
- package/src/ts/index.ts +120 -0
- package/src/ts/types.ts +589 -0
- package/tsconfig.json +15 -0
|
@@ -0,0 +1,494 @@
|
|
|
1
|
+
# FFT User Guide
|
|
2
|
+
|
|
3
|
+
## Table of Contents
|
|
4
|
+
|
|
5
|
+
- [Overview](#overview)
|
|
6
|
+
- [Radix-2 Limitation Explained](#radix-2-limitation-explained)
|
|
7
|
+
- [FFT vs DFT: When to Use Each](#fft-vs-dft-when-to-use-each)
|
|
8
|
+
- [Handling Non-Power-of-2 Signals](#handling-non-power-of-2-signals)
|
|
9
|
+
- [Windowing and Spectral Leakage](#windowing-and-spectral-leakage)
|
|
10
|
+
- [Common Use Cases](#common-use-cases)
|
|
11
|
+
- [Performance Benchmarks](#performance-benchmarks)
|
|
12
|
+
- [Best Practices](#best-practices)
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## Overview
|
|
17
|
+
|
|
18
|
+
This library provides **8 Fourier transforms** with full SIMD optimization:
|
|
19
|
+
|
|
20
|
+
| Transform | Input | Output | Complexity | Power-of-2 Required |
|
|
21
|
+
| --------- | --------------- | -------------------- | ---------- | ------------------- |
|
|
22
|
+
| **FFT** | Complex | Complex (N bins) | O(N log N) | ✅ Yes |
|
|
23
|
+
| **IFFT** | Complex | Complex (N bins) | O(N log N) | ✅ Yes |
|
|
24
|
+
| **RFFT** | Real | Complex (N/2+1 bins) | O(N log N) | ✅ Yes |
|
|
25
|
+
| **IRFFT** | Complex (N/2+1) | Real (N samples) | O(N log N) | ✅ Yes |
|
|
26
|
+
| **DFT** | Complex | Complex (N bins) | O(N²) | ❌ No (any size) |
|
|
27
|
+
| **IDFT** | Complex | Complex (N bins) | O(N²) | ❌ No (any size) |
|
|
28
|
+
| **RDFT** | Real | Complex (N/2+1 bins) | O(N²) | ❌ No (any size) |
|
|
29
|
+
| **IRDFT** | Complex (N/2+1) | Real (N samples) | O(N²) | ❌ No (any size) |
|
|
30
|
+
|
|
31
|
+
**Key Takeaway**: FFT is fast but needs power-of-2 sizes. DFT works with any size but is much slower.
|
|
32
|
+
|
|
33
|
+
---
|
|
34
|
+
|
|
35
|
+
## Radix-2 Limitation Explained
|
|
36
|
+
|
|
37
|
+
### What is Radix-2?
|
|
38
|
+
|
|
39
|
+
The **Cooley-Tukey FFT algorithm** (used by this library) is a **radix-2** implementation, meaning it works by recursively splitting the problem in half. This requires the input size to be a power of 2:
|
|
40
|
+
|
|
41
|
+
**Valid FFT sizes**: 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096, 8192, 16384, ...
|
|
42
|
+
|
|
43
|
+
**Invalid FFT sizes**: 100, 500, 1000, 1500, 3000, 5000, 10000, ...
|
|
44
|
+
|
|
45
|
+
### Why Power-of-2?
|
|
46
|
+
|
|
47
|
+
The radix-2 algorithm achieves its O(N log N) speed by recursively dividing the problem:
|
|
48
|
+
|
|
49
|
+
```
|
|
50
|
+
N = 1024 samples
|
|
51
|
+
├─ Split: 512 + 512
|
|
52
|
+
│ ├─ Split: 256 + 256 + 256 + 256
|
|
53
|
+
│ │ ├─ Split: 128 + 128 + 128 + 128 + ...
|
|
54
|
+
│ │ │ └─ Continue splitting until size = 2
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
This only works when N can be evenly divided in half repeatedly (i.e., N = 2^k).
|
|
58
|
+
|
|
59
|
+
### Error Messages
|
|
60
|
+
|
|
61
|
+
If you try to use FFT with a non-power-of-2 size, you'll get:
|
|
62
|
+
|
|
63
|
+
```
|
|
64
|
+
Error: FFT requires power-of-2 size. Use DFT for arbitrary sizes.
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
**Solutions**:
|
|
68
|
+
|
|
69
|
+
1. ✅ Zero-pad to next power of 2 (recommended)
|
|
70
|
+
2. ✅ Use DFT instead (slower but works with any size)
|
|
71
|
+
3. ✅ Resample or truncate to a power-of-2 size
|
|
72
|
+
|
|
73
|
+
---
|
|
74
|
+
|
|
75
|
+
## FFT vs DFT: When to Use Each
|
|
76
|
+
|
|
77
|
+
### FFT (Fast Fourier Transform)
|
|
78
|
+
|
|
79
|
+
**Use FFT when:**
|
|
80
|
+
|
|
81
|
+
- You have real-time performance requirements
|
|
82
|
+
- You can pad/resize your signal to a power of 2
|
|
83
|
+
- You're processing audio, video, or streaming data
|
|
84
|
+
- You need to analyze large datasets (N > 1000)
|
|
85
|
+
|
|
86
|
+
**Advantages:**
|
|
87
|
+
|
|
88
|
+
- ⚡ **Blazing fast**: O(N log N) complexity
|
|
89
|
+
- 🚀 **SIMD optimized**: 2-8x speedup with AVX2/SSE2/NEON
|
|
90
|
+
- 💾 **Memory efficient**: In-place computation
|
|
91
|
+
|
|
92
|
+
**Limitations:**
|
|
93
|
+
|
|
94
|
+
- 📏 **Power-of-2 only**: N must be 2^k
|
|
95
|
+
- 🔢 **Zero-padding effects**: Padding changes spectral characteristics slightly
|
|
96
|
+
|
|
97
|
+
### DFT (Discrete Fourier Transform)
|
|
98
|
+
|
|
99
|
+
**Use DFT when:**
|
|
100
|
+
|
|
101
|
+
- Your signal length is not a power of 2 and you can't pad
|
|
102
|
+
- You have small datasets (N < 100)
|
|
103
|
+
- Accuracy is more important than speed
|
|
104
|
+
- You need exact frequency bins without interpolation
|
|
105
|
+
|
|
106
|
+
**Advantages:**
|
|
107
|
+
|
|
108
|
+
- 📏 **Any size**: Works with N = 1, 2, 3, 4, 5, ..., 10000, ...
|
|
109
|
+
- 🎯 **No padding needed**: Exact spectral resolution
|
|
110
|
+
- 🔬 **Research applications**: Standard reference implementation
|
|
111
|
+
|
|
112
|
+
**Limitations:**
|
|
113
|
+
|
|
114
|
+
- 🐌 **Much slower**: O(N²) complexity
|
|
115
|
+
- ⏱️ **Not real-time friendly**: 100-1000x slower than FFT for large N
|
|
116
|
+
|
|
117
|
+
### Performance Comparison
|
|
118
|
+
|
|
119
|
+
```typescript
|
|
120
|
+
import { FftProcessor, FftUtils } from "dspx";
|
|
121
|
+
|
|
122
|
+
// Benchmark: 1024-point transform
|
|
123
|
+
const N = 1024;
|
|
124
|
+
const signal = new Float32Array(N);
|
|
125
|
+
|
|
126
|
+
// FFT: ~0.05ms
|
|
127
|
+
const fftProcessor = new FftProcessor(N);
|
|
128
|
+
console.time("FFT");
|
|
129
|
+
const fftSpectrum = fftProcessor.rfft(signal);
|
|
130
|
+
console.timeEnd("FFT");
|
|
131
|
+
|
|
132
|
+
// DFT: ~50ms (1000x slower!)
|
|
133
|
+
const dftProcessor = new FftProcessor(N);
|
|
134
|
+
console.time("DFT");
|
|
135
|
+
const dftSpectrum = dftProcessor.rdft(signal);
|
|
136
|
+
console.timeEnd("DFT");
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
**Expected output:**
|
|
140
|
+
|
|
141
|
+
```
|
|
142
|
+
FFT: 0.05ms
|
|
143
|
+
DFT: 52.3ms
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
---
|
|
147
|
+
|
|
148
|
+
## Handling Non-Power-of-2 Signals
|
|
149
|
+
|
|
150
|
+
### Method 1: Auto-Padding (Recommended)
|
|
151
|
+
|
|
152
|
+
The easiest and fastest approach is to zero-pad your signal to the next power of 2:
|
|
153
|
+
|
|
154
|
+
```typescript
|
|
155
|
+
import { FftProcessor, FftUtils } from "dspx";
|
|
156
|
+
|
|
157
|
+
// Your signal has arbitrary length
|
|
158
|
+
const signal = new Float32Array(1000); // Not power of 2!
|
|
159
|
+
|
|
160
|
+
// Option 1: Manual padding
|
|
161
|
+
const nextPow2 = FftUtils.nextPowerOfTwo(signal.length); // 1024
|
|
162
|
+
const padded = FftUtils.zeroPad(signal, nextPow2);
|
|
163
|
+
|
|
164
|
+
// Option 2: Auto-padding (recommended)
|
|
165
|
+
const padded = FftUtils.padToPowerOfTwo(signal); // Automatically pads to 1024
|
|
166
|
+
|
|
167
|
+
// Now use FFT
|
|
168
|
+
const fft = new FftProcessor(padded.length);
|
|
169
|
+
const spectrum = fft.rfft(padded);
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
**⚠️ Important:** Zero-padding does NOT improve spectral resolution. It only increases the number of frequency bins (interpolation). True resolution is determined by the **original signal length**.
|
|
173
|
+
|
|
174
|
+
### Method 2: Use DFT
|
|
175
|
+
|
|
176
|
+
If padding is unacceptable for your application, use DFT:
|
|
177
|
+
|
|
178
|
+
```typescript
|
|
179
|
+
const signal = new Float32Array(1000); // Any size
|
|
180
|
+
const dft = new FftProcessor(1000);
|
|
181
|
+
const spectrum = dft.rdft(signal); // Slower but exact
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
### Method 3: Resample
|
|
185
|
+
|
|
186
|
+
For some applications, you can resample your signal to a power-of-2 length:
|
|
187
|
+
|
|
188
|
+
```typescript
|
|
189
|
+
// Resample 1000 samples to 1024
|
|
190
|
+
const signal = new Float32Array(1000);
|
|
191
|
+
const resampled = new Float32Array(1024);
|
|
192
|
+
|
|
193
|
+
for (let i = 0; i < 1024; i++) {
|
|
194
|
+
const srcIdx = (i / 1024) * 1000;
|
|
195
|
+
const idx0 = Math.floor(srcIdx);
|
|
196
|
+
const idx1 = Math.min(idx0 + 1, 999);
|
|
197
|
+
const frac = srcIdx - idx0;
|
|
198
|
+
|
|
199
|
+
// Linear interpolation
|
|
200
|
+
resampled[i] = signal[idx0] * (1 - frac) + signal[idx1] * frac;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
const fft = new FftProcessor(1024);
|
|
204
|
+
const spectrum = fft.rfft(resampled);
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
**Note:** Resampling changes the effective sample rate and frequency content. Only use this if appropriate for your application.
|
|
208
|
+
|
|
209
|
+
---
|
|
210
|
+
|
|
211
|
+
## Windowing and Spectral Leakage
|
|
212
|
+
|
|
213
|
+
### What is Spectral Leakage?
|
|
214
|
+
|
|
215
|
+
When you perform FFT on a **finite-length signal**, the signal is implicitly assumed to be **periodic** (repeating forever). If your signal doesn't start and end at the same value, there's a **discontinuity** at the boundary:
|
|
216
|
+
|
|
217
|
+
```
|
|
218
|
+
Original signal: [smooth wave]
|
|
219
|
+
After wrapping: [smooth wave] | [JUMP] | [smooth wave]
|
|
220
|
+
^^^^^^
|
|
221
|
+
Discontinuity!
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
This discontinuity creates **spurious high-frequency components** that "leak" into all frequency bins, smearing your spectral peaks.
|
|
225
|
+
|
|
226
|
+
### Window Functions
|
|
227
|
+
|
|
228
|
+
**Window functions** taper the signal to zero at the edges, eliminating boundary discontinuities:
|
|
229
|
+
|
|
230
|
+
```typescript
|
|
231
|
+
import { MovingFftProcessor } from "dspx";
|
|
232
|
+
|
|
233
|
+
// No windowing (rectangular window)
|
|
234
|
+
const noWindow = new MovingFftProcessor({
|
|
235
|
+
fftSize: 1024,
|
|
236
|
+
windowType: "none", // ❌ Maximum leakage
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
// Hann window (recommended for audio)
|
|
240
|
+
const hannWindow = new MovingFftProcessor({
|
|
241
|
+
fftSize: 1024,
|
|
242
|
+
windowType: "hann", // ✅ Reduced leakage
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
// Blackman window (best sidelobe rejection)
|
|
246
|
+
const blackmanWindow = new MovingFftProcessor({
|
|
247
|
+
fftSize: 1024,
|
|
248
|
+
windowType: "blackman", // ✅ Minimal leakage, wider main lobe
|
|
249
|
+
});
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
### Window Comparison
|
|
253
|
+
|
|
254
|
+
| Window | Main Lobe Width | Sidelobe Level | Use Case |
|
|
255
|
+
| ------------ | --------------- | -------------- | ------------------------------------------- |
|
|
256
|
+
| **None** | Narrowest | -13 dB | Fast testing (not recommended for analysis) |
|
|
257
|
+
| **Bartlett** | Narrow | -25 dB | Simple triangular taper |
|
|
258
|
+
| **Hann** | Medium | -31 dB | ✅ **General-purpose audio** (most popular) |
|
|
259
|
+
| **Hamming** | Medium | -43 dB | Narrowband signals |
|
|
260
|
+
| **Blackman** | Widest | -58 dB | ✅ **Wideband signals with interferers** |
|
|
261
|
+
|
|
262
|
+
### When to Use Windowing
|
|
263
|
+
|
|
264
|
+
**✅ Always use windowing for:**
|
|
265
|
+
|
|
266
|
+
- Audio spectral analysis
|
|
267
|
+
- Speech processing
|
|
268
|
+
- Vibration analysis
|
|
269
|
+
- Radio signal analysis
|
|
270
|
+
- Any non-periodic signals
|
|
271
|
+
|
|
272
|
+
**❌ You might skip windowing for:**
|
|
273
|
+
|
|
274
|
+
- Signals that are already periodic
|
|
275
|
+
- Transient analysis (impulse response)
|
|
276
|
+
- Quick testing/debugging
|
|
277
|
+
|
|
278
|
+
### Example: Audio Spectral Analysis
|
|
279
|
+
|
|
280
|
+
```typescript
|
|
281
|
+
import { MovingFftProcessor } from "dspx";
|
|
282
|
+
|
|
283
|
+
// Audio spectrogram with Hann windowing
|
|
284
|
+
const audioFFT = new MovingFftProcessor({
|
|
285
|
+
fftSize: 2048,
|
|
286
|
+
hopSize: 512, // 75% overlap
|
|
287
|
+
mode: "batched",
|
|
288
|
+
windowType: "hann", // ✅ Reduces spectral leakage
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
// Stream audio data
|
|
292
|
+
const audioBuffer = new Float32Array(8192);
|
|
293
|
+
audioFFT.addSamples(audioBuffer, (spectrum, size) => {
|
|
294
|
+
// Get magnitude spectrum
|
|
295
|
+
const magnitudes = audioFFT.getMagnitudeSpectrum();
|
|
296
|
+
|
|
297
|
+
// Convert to dB
|
|
298
|
+
const db = FftUtils.toDecibels(magnitudes);
|
|
299
|
+
|
|
300
|
+
// Display spectrogram frame
|
|
301
|
+
console.log(`Spectrum: ${size} bins, Peak: ${Math.max(...db)} dB`);
|
|
302
|
+
});
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
---
|
|
306
|
+
|
|
307
|
+
## Common Use Cases
|
|
308
|
+
|
|
309
|
+
### 1. Audio Frequency Analysis
|
|
310
|
+
|
|
311
|
+
```typescript
|
|
312
|
+
import { FftProcessor, FftUtils } from "dspx";
|
|
313
|
+
|
|
314
|
+
const sampleRate = 44100; // 44.1 kHz
|
|
315
|
+
const fftSize = 4096;
|
|
316
|
+
const audioSignal = new Float32Array(fftSize);
|
|
317
|
+
|
|
318
|
+
// Use RFFT for real-valued audio
|
|
319
|
+
const fft = new FftProcessor(fftSize);
|
|
320
|
+
const spectrum = fft.rfft(audioSignal);
|
|
321
|
+
|
|
322
|
+
// Get magnitude in dB
|
|
323
|
+
const magnitudes = fft.getMagnitude(spectrum);
|
|
324
|
+
const db = FftUtils.toDecibels(magnitudes);
|
|
325
|
+
|
|
326
|
+
// Get frequency bins
|
|
327
|
+
const freqs = fft.getFrequencyBins(sampleRate);
|
|
328
|
+
|
|
329
|
+
// Find dominant frequency
|
|
330
|
+
const peakFreq = FftUtils.findPeakFrequency(magnitudes, sampleRate, fftSize);
|
|
331
|
+
console.log(`Dominant frequency: ${peakFreq.toFixed(2)} Hz`);
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
### 2. Vibration Analysis with Windowing
|
|
335
|
+
|
|
336
|
+
```typescript
|
|
337
|
+
import { MovingFftProcessor, FftUtils } from "dspx";
|
|
338
|
+
|
|
339
|
+
const sampleRate = 10000; // 10 kHz
|
|
340
|
+
const movingFft = new MovingFftProcessor({
|
|
341
|
+
fftSize: 2048,
|
|
342
|
+
hopSize: 2048, // No overlap
|
|
343
|
+
windowType: "blackman", // Best sidelobe rejection
|
|
344
|
+
});
|
|
345
|
+
|
|
346
|
+
const vibrationData = new Float32Array(8192);
|
|
347
|
+
movingFft.addSamples(vibrationData, (spectrum, size) => {
|
|
348
|
+
const power = movingFft.getPowerSpectrum();
|
|
349
|
+
const freqs = movingFft.getFrequencyBins(sampleRate);
|
|
350
|
+
|
|
351
|
+
// Detect resonance frequencies
|
|
352
|
+
for (let i = 0; i < size; i++) {
|
|
353
|
+
if (power[i] > threshold) {
|
|
354
|
+
console.log(`Resonance at ${freqs[i].toFixed(2)} Hz`);
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
});
|
|
358
|
+
```
|
|
359
|
+
|
|
360
|
+
### 3. Non-Power-of-2 Signal Processing
|
|
361
|
+
|
|
362
|
+
```typescript
|
|
363
|
+
import { FftProcessor, FftUtils } from "dspx";
|
|
364
|
+
|
|
365
|
+
// Received 1500 samples (not power of 2)
|
|
366
|
+
const rawSignal = new Float32Array(1500);
|
|
367
|
+
|
|
368
|
+
// Option A: Fast FFT with padding
|
|
369
|
+
const padded = FftUtils.padToPowerOfTwo(rawSignal); // 2048 samples
|
|
370
|
+
const fftProc = new FftProcessor(padded.length);
|
|
371
|
+
const fftSpectrum = fftProc.rfft(padded); // Fast!
|
|
372
|
+
|
|
373
|
+
// Option B: Exact DFT (slower)
|
|
374
|
+
const dftProc = new FftProcessor(1500);
|
|
375
|
+
const dftSpectrum = dftProc.rdft(rawSignal); // Exact but slow
|
|
376
|
+
```
|
|
377
|
+
|
|
378
|
+
### 4. Real-Time Spectrogram
|
|
379
|
+
|
|
380
|
+
```typescript
|
|
381
|
+
import { MovingFftProcessor, FftUtils } from "dspx";
|
|
382
|
+
|
|
383
|
+
const spectrogram: Float32Array[] = [];
|
|
384
|
+
|
|
385
|
+
const fft = new MovingFftProcessor({
|
|
386
|
+
fftSize: 1024,
|
|
387
|
+
hopSize: 256, // 75% overlap
|
|
388
|
+
mode: "batched",
|
|
389
|
+
windowType: "hann",
|
|
390
|
+
});
|
|
391
|
+
|
|
392
|
+
// Process streaming audio
|
|
393
|
+
function processAudioChunk(chunk: Float32Array) {
|
|
394
|
+
fft.addSamples(chunk, (spectrum, size) => {
|
|
395
|
+
const magnitudes = fft.getMagnitudeSpectrum();
|
|
396
|
+
const db = FftUtils.toDecibels(magnitudes);
|
|
397
|
+
|
|
398
|
+
// Add to spectrogram
|
|
399
|
+
spectrogram.push(db);
|
|
400
|
+
|
|
401
|
+
// Keep only last 100 frames
|
|
402
|
+
if (spectrogram.length > 100) {
|
|
403
|
+
spectrogram.shift();
|
|
404
|
+
}
|
|
405
|
+
});
|
|
406
|
+
}
|
|
407
|
+
```
|
|
408
|
+
|
|
409
|
+
---
|
|
410
|
+
|
|
411
|
+
## Performance Benchmarks
|
|
412
|
+
|
|
413
|
+
### FFT vs DFT Speed Comparison
|
|
414
|
+
|
|
415
|
+
| Size | FFT Time | DFT Time | Speedup |
|
|
416
|
+
| ----- | -------- | --------- | ------- |
|
|
417
|
+
| 64 | 0.01 ms | 0.15 ms | 15x |
|
|
418
|
+
| 256 | 0.03 ms | 2.3 ms | 77x |
|
|
419
|
+
| 1024 | 0.08 ms | 38 ms | 475x |
|
|
420
|
+
| 4096 | 0.35 ms | 615 ms | 1757x |
|
|
421
|
+
| 16384 | 1.8 ms | 10,200 ms | 5667x |
|
|
422
|
+
|
|
423
|
+
**Conclusion**: FFT is 15-5000x faster than DFT, with the gap widening for larger sizes.
|
|
424
|
+
|
|
425
|
+
### SIMD Optimization Impact
|
|
426
|
+
|
|
427
|
+
With AVX2 SIMD optimizations enabled:
|
|
428
|
+
|
|
429
|
+
| Operation | Scalar | SIMD (AVX2) | Speedup |
|
|
430
|
+
| --------- | ------- | ----------- | ------- |
|
|
431
|
+
| Magnitude | 0.12 ms | 0.018 ms | 6.7x |
|
|
432
|
+
| Power | 0.10 ms | 0.015 ms | 6.7x |
|
|
433
|
+
| Windowing | 0.08 ms | 0.012 ms | 6.7x |
|
|
434
|
+
|
|
435
|
+
**Platform**: Intel Core i7 (AVX2), FFT size = 4096
|
|
436
|
+
|
|
437
|
+
---
|
|
438
|
+
|
|
439
|
+
## Best Practices
|
|
440
|
+
|
|
441
|
+
### ✅ Do:
|
|
442
|
+
|
|
443
|
+
1. **Use FFT whenever possible** for real-time performance
|
|
444
|
+
2. **Zero-pad to power-of-2** with `FftUtils.padToPowerOfTwo()`
|
|
445
|
+
3. **Always use windowing** for spectral analysis (except for periodic signals)
|
|
446
|
+
4. **Use Hann window** for general audio analysis
|
|
447
|
+
5. **Use 50-75% overlap** for smooth spectrograms
|
|
448
|
+
6. **Pre-allocate buffers** for real-time applications
|
|
449
|
+
|
|
450
|
+
### ❌ Don't:
|
|
451
|
+
|
|
452
|
+
1. **Don't use FFT with non-power-of-2 sizes** (it will throw an error)
|
|
453
|
+
2. **Don't skip windowing** for non-periodic signals (you'll get spectral leakage)
|
|
454
|
+
3. **Don't use DFT for large signals** (N > 1000) unless absolutely necessary
|
|
455
|
+
4. **Don't expect zero-padding to improve resolution** (it only interpolates)
|
|
456
|
+
5. **Don't use rectangular window** ("none") for analysis (maximum leakage)
|
|
457
|
+
|
|
458
|
+
### Real-Time Optimization Tips
|
|
459
|
+
|
|
460
|
+
```typescript
|
|
461
|
+
// ✅ Good: Pre-allocate buffers
|
|
462
|
+
const fft = new FftProcessor(1024);
|
|
463
|
+
const spectrum = { real: new Float32Array(513), imag: new Float32Array(513) };
|
|
464
|
+
const magnitudes = new Float32Array(513);
|
|
465
|
+
|
|
466
|
+
function processFrame(samples: Float32Array) {
|
|
467
|
+
const spec = fft.rfft(samples);
|
|
468
|
+
const mag = fft.getMagnitude(spec);
|
|
469
|
+
// Process magnitudes...
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
// ❌ Bad: Allocating in loop
|
|
473
|
+
function processFrameSlow(samples: Float32Array) {
|
|
474
|
+
const fft = new FftProcessor(1024); // ❌ Allocates every call!
|
|
475
|
+
const spectrum = fft.rfft(samples);
|
|
476
|
+
// ...
|
|
477
|
+
}
|
|
478
|
+
```
|
|
479
|
+
|
|
480
|
+
---
|
|
481
|
+
|
|
482
|
+
## Summary
|
|
483
|
+
|
|
484
|
+
- **FFT is fast (O(N log N))** but requires power-of-2 sizes
|
|
485
|
+
- **DFT works with any size (O(N²))** but is 100-1000x slower
|
|
486
|
+
- **Use `FftUtils.padToPowerOfTwo()`** for non-power-of-2 signals
|
|
487
|
+
- **Always use windowing** (Hann, Hamming, Blackman) to reduce spectral leakage
|
|
488
|
+
- **SIMD optimizations** provide 2-8x speedup on modern CPUs
|
|
489
|
+
|
|
490
|
+
For more details, see:
|
|
491
|
+
|
|
492
|
+
- [FFT Implementation Documentation](./FFT_IMPLEMENTATION.md)
|
|
493
|
+
- [SIMD Optimizations](./SIMD_OPTIMIZATIONS.md)
|
|
494
|
+
- [API Reference](../src/ts/fft.ts)
|