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,260 @@
|
|
|
1
|
+
# FIR and IIR Filter Implementation Summary
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
Successfully implemented comprehensive FIR (Finite Impulse Response) and IIR (Infinite Impulse Response) digital filters with N-API bindings for Node.js.
|
|
6
|
+
|
|
7
|
+
## Implementation Details
|
|
8
|
+
|
|
9
|
+
### FIR Filter (`FirFilter.h`, `FirFilter.cc`)
|
|
10
|
+
|
|
11
|
+
**Type**: Non-recursive, always stable
|
|
12
|
+
**Features**:
|
|
13
|
+
|
|
14
|
+
- Windowed sinc filter design method
|
|
15
|
+
- Four filter types: Low-Pass, High-Pass, Band-Pass, Band-Stop
|
|
16
|
+
- Window functions: Hamming, Hann, Blackman, Bartlett
|
|
17
|
+
- Circular buffer state management (O(1) operations)
|
|
18
|
+
- Template support for `float` and `double` precision
|
|
19
|
+
- Stateful (streaming) and stateless (batch) processing modes
|
|
20
|
+
|
|
21
|
+
**Key Methods**:
|
|
22
|
+
|
|
23
|
+
- `processSample(T sample)` - Process single sample with state
|
|
24
|
+
- `process(const T* input, T* output, size_t length, bool stateless)` - Batch processing
|
|
25
|
+
- `reset()` - Clear internal state
|
|
26
|
+
- `getOrder()` - Get filter order (number of taps - 1)
|
|
27
|
+
- `getCoefficients()` - Get impulse response coefficients
|
|
28
|
+
- `setCoefficients(coeffs)` - Update filter coefficients
|
|
29
|
+
|
|
30
|
+
**Static Factory Methods**:
|
|
31
|
+
|
|
32
|
+
```cpp
|
|
33
|
+
FirFilter<T> createLowPass(T cutoffFreq, size_t numTaps, std::string windowType = "hamming");
|
|
34
|
+
FirFilter<T> createHighPass(T cutoffFreq, size_t numTaps, std::string windowType = "hamming");
|
|
35
|
+
FirFilter<T> createBandPass(T lowCutoff, T highCutoff, size_t numTaps, std::string windowType = "hamming");
|
|
36
|
+
FirFilter<T> createBandStop(T lowCutoff, T highCutoff, size_t numTaps, std::string windowType = "hamming");
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
### IIR Filter (`IirFilter.h`, `IirFilter.cc`)
|
|
40
|
+
|
|
41
|
+
**Type**: Recursive, more efficient but requires stability checking
|
|
42
|
+
**Features**:
|
|
43
|
+
|
|
44
|
+
- Direct Form II implementation (numerically stable)
|
|
45
|
+
- Bilinear transform for analog-to-digital conversion
|
|
46
|
+
- Butterworth filter designs (maximally flat passband)
|
|
47
|
+
- First-order and second-order (biquad) sections
|
|
48
|
+
- Template support for `float` and `double` precision
|
|
49
|
+
- Stateful (streaming) and stateless (batch) processing modes
|
|
50
|
+
- Stability checking method
|
|
51
|
+
|
|
52
|
+
**Key Methods**:
|
|
53
|
+
|
|
54
|
+
- `processSample(T sample)` - Process single sample with feedback
|
|
55
|
+
- `process(const T* input, T* output, size_t length, bool stateless)` - Batch processing
|
|
56
|
+
- `reset()` - Clear input/output history
|
|
57
|
+
- `getFeedforwardOrder()` - Get order of feedforward coefficients (b)
|
|
58
|
+
- `getFeedbackOrder()` - Get order of feedback coefficients (a)
|
|
59
|
+
- `getBCoefficients()` - Get feedforward (numerator) coefficients
|
|
60
|
+
- `getACoefficients()` - Get feedback (denominator) coefficients
|
|
61
|
+
- `isStable()` - Basic stability check
|
|
62
|
+
|
|
63
|
+
**Static Factory Methods**:
|
|
64
|
+
|
|
65
|
+
```cpp
|
|
66
|
+
IirFilter<T> createFirstOrderLowPass(T cutoffFreq);
|
|
67
|
+
IirFilter<T> createFirstOrderHighPass(T cutoffFreq);
|
|
68
|
+
IirFilter<T> createButterworthLowPass(T cutoffFreq, int order);
|
|
69
|
+
IirFilter<T> createButterworthHighPass(T cutoffFreq, int order);
|
|
70
|
+
IirFilter<T> createButterworthBandPass(T lowCutoff, T highCutoff, int order);
|
|
71
|
+
IirFilter<T> createBiquad(T b0, T b1, T b2, T a1, T a2);
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## N-API Bindings (`FilterBindings.cc`)
|
|
75
|
+
|
|
76
|
+
### FirFilterWrapper
|
|
77
|
+
|
|
78
|
+
Exposes FIR filter to JavaScript with:
|
|
79
|
+
|
|
80
|
+
- Constructor: `new FirFilter(coefficients, stateful?)`
|
|
81
|
+
- All instance methods wrapped
|
|
82
|
+
- Static factory methods: `FirFilter.createLowPass()`, etc.
|
|
83
|
+
- Automatic memory management via `std::unique_ptr`
|
|
84
|
+
|
|
85
|
+
### IirFilterWrapper
|
|
86
|
+
|
|
87
|
+
Exposes IIR filter to JavaScript with:
|
|
88
|
+
|
|
89
|
+
- Constructor: `new IirFilter(b_coeffs, a_coeffs, stateful?)`
|
|
90
|
+
- All instance methods wrapped
|
|
91
|
+
- Static factory methods: `IirFilter.createButterworthLowPass()`, etc.
|
|
92
|
+
- Automatic memory management via `std::unique_ptr`
|
|
93
|
+
|
|
94
|
+
## Build Configuration
|
|
95
|
+
|
|
96
|
+
Updated `binding.gyp` to include:
|
|
97
|
+
|
|
98
|
+
- `src/native/core/FirFilter.cc`
|
|
99
|
+
- `src/native/core/IirFilter.cc`
|
|
100
|
+
- `src/native/FilterBindings.cc`
|
|
101
|
+
|
|
102
|
+
Compiled successfully with:
|
|
103
|
+
|
|
104
|
+
- **3012 functions** (up from 2720)
|
|
105
|
+
- AVX2 SIMD optimizations enabled
|
|
106
|
+
- O2/O3 optimization level
|
|
107
|
+
- Fast math enabled (`/fp:fast`)
|
|
108
|
+
|
|
109
|
+
## Test Results
|
|
110
|
+
|
|
111
|
+
Created `test-filters.cjs` to verify functionality:
|
|
112
|
+
|
|
113
|
+
### FIR Filter Tests
|
|
114
|
+
|
|
115
|
+
✅ Create low-pass filter with 51 taps, Hamming window
|
|
116
|
+
✅ Process single sample (stateful)
|
|
117
|
+
✅ Process batch of 8 samples
|
|
118
|
+
✅ Reset filter state
|
|
119
|
+
✅ Get filter order (50)
|
|
120
|
+
✅ Is stateful: true
|
|
121
|
+
|
|
122
|
+
**Sample Output**:
|
|
123
|
+
|
|
124
|
+
```
|
|
125
|
+
Order: 50
|
|
126
|
+
First 5 outputs: [-0.0011, -0.0018, -0.0003, 0.0025, 0.0030]
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
### IIR Filter Tests
|
|
130
|
+
|
|
131
|
+
✅ Create first-order low-pass filter (0.1 normalized frequency)
|
|
132
|
+
✅ Process single sample (stateful with feedback)
|
|
133
|
+
✅ Process batch of 8 samples
|
|
134
|
+
✅ Reset filter state
|
|
135
|
+
✅ Get feedforward/feedback orders
|
|
136
|
+
✅ Is stable: true
|
|
137
|
+
✅ Get B and A coefficients
|
|
138
|
+
|
|
139
|
+
**First-Order Filter Coefficients**:
|
|
140
|
+
|
|
141
|
+
```
|
|
142
|
+
B coefficients: [0.2452, 0.2452]
|
|
143
|
+
A coefficients: [-0.5095]
|
|
144
|
+
First 5 outputs: [0.6154, 0.6814, 0.4698, 0.1168, -0.3084]
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
### Butterworth IIR Filter Tests
|
|
148
|
+
|
|
149
|
+
✅ Create 2nd-order Butterworth low-pass filter
|
|
150
|
+
✅ Process batch of 8 samples
|
|
151
|
+
✅ Validate coefficient structure
|
|
152
|
+
|
|
153
|
+
**Second-Order Butterworth Coefficients**:
|
|
154
|
+
|
|
155
|
+
```
|
|
156
|
+
B coefficients: [0.2066, 0.4131, 0.2066]
|
|
157
|
+
A coefficients: [-0.3695, 0.1958]
|
|
158
|
+
Outputs: [0.2066, 0.6961, 1.0430, 1.0754, 0.8129, 0.2964, -0.0497, -0.0764]
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
## Integration Status
|
|
162
|
+
|
|
163
|
+
✅ **All 395 existing tests pass** - No regressions
|
|
164
|
+
✅ **C++ implementation complete**
|
|
165
|
+
✅ **N-API bindings working**
|
|
166
|
+
✅ **Factory methods functional**
|
|
167
|
+
✅ **Stateful and stateless modes operational**
|
|
168
|
+
✅ **Memory management verified**
|
|
169
|
+
|
|
170
|
+
## Design Theory
|
|
171
|
+
|
|
172
|
+
### FIR Filters
|
|
173
|
+
|
|
174
|
+
- **Windowed Sinc Method**: Generates ideal frequency response in time domain, then applies window function to create finite-length filter
|
|
175
|
+
- **Spectral Inversion**: Converts low-pass to high-pass by subtracting from unit impulse
|
|
176
|
+
- **Filter Cascading**: Band-pass/band-stop created by combining low-pass and high-pass designs
|
|
177
|
+
- **Always Stable**: No feedback, output is weighted sum of past inputs only
|
|
178
|
+
|
|
179
|
+
### IIR Filters
|
|
180
|
+
|
|
181
|
+
- **Bilinear Transform**: Maps analog (s-domain) to digital (z-domain) via `s = 2(1-z⁻¹)/(1+z⁻¹)`
|
|
182
|
+
- **Butterworth Polynomials**: Maximally flat magnitude response in passband
|
|
183
|
+
- **Direct Form II**: Uses single delay line for both feedforward and feedback paths (numerically stable)
|
|
184
|
+
- **Biquad Sections**: Second-order sections can be cascaded for higher-order filters
|
|
185
|
+
|
|
186
|
+
### Performance Characteristics
|
|
187
|
+
|
|
188
|
+
- **FIR**: Linear phase (symmetric coefficients), higher computational cost (more taps needed)
|
|
189
|
+
- **IIR**: Non-linear phase, lower computational cost (fewer coefficients), requires stability checking
|
|
190
|
+
- **Trade-offs**: FIR for phase-critical applications, IIR for efficiency and sharp roll-off
|
|
191
|
+
|
|
192
|
+
## Frequency Specifications
|
|
193
|
+
|
|
194
|
+
All cutoff frequencies are **normalized** (0 to 0.5):
|
|
195
|
+
|
|
196
|
+
- `cutoffFreq = desiredFreq / sampleRate`
|
|
197
|
+
- Example: 100 Hz cutoff at 1000 Hz sample rate = 0.1
|
|
198
|
+
- Nyquist frequency (sampleRate/2) = 0.5
|
|
199
|
+
|
|
200
|
+
## Next Steps (Not Yet Implemented)
|
|
201
|
+
|
|
202
|
+
1. ⏳ TypeScript wrappers in `src/ts/filters.ts`
|
|
203
|
+
2. ⏳ Full test suite in `src/ts/__tests__/Filters.test.ts`
|
|
204
|
+
3. ⏳ Documentation in `docs/FILTERS_IMPLEMENTATION.md`
|
|
205
|
+
4. ⏳ Export from main `index.ts`
|
|
206
|
+
5. ⏳ Usage examples in `src/ts/examples/`
|
|
207
|
+
|
|
208
|
+
## Usage Examples
|
|
209
|
+
|
|
210
|
+
### JavaScript/Node.js (Direct Native Bindings)
|
|
211
|
+
|
|
212
|
+
```javascript
|
|
213
|
+
const dsp = require("./build/Release/dspx.node");
|
|
214
|
+
|
|
215
|
+
// FIR low-pass filter: 200 Hz cutoff at 1000 Hz sample rate
|
|
216
|
+
const firLP = dsp.FirFilter.createLowPass(0.2, 51, "hamming");
|
|
217
|
+
const filtered = firLP.process(new Float32Array([1, 0, -1, 0, 1]));
|
|
218
|
+
|
|
219
|
+
// IIR Butterworth low-pass: 150 Hz cutoff at 1000 Hz sample rate, order 2
|
|
220
|
+
const iirLP = dsp.IirFilter.createButterworthLowPass(0.15, 2);
|
|
221
|
+
const result = iirLP.process(new Float32Array([1, 2, 3, 2, 1]));
|
|
222
|
+
|
|
223
|
+
// Streaming mode (stateful)
|
|
224
|
+
for (let sample of signal) {
|
|
225
|
+
const out = firLP.processSample(sample);
|
|
226
|
+
console.log(out);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// Batch mode (stateless)
|
|
230
|
+
const batchOut = firLP.process(signalArray, true);
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
## File Manifest
|
|
234
|
+
|
|
235
|
+
```
|
|
236
|
+
src/native/
|
|
237
|
+
├── core/
|
|
238
|
+
│ ├── FirFilter.h (~150 lines)
|
|
239
|
+
│ ├── FirFilter.cc (~280 lines)
|
|
240
|
+
│ ├── IirFilter.h (~140 lines)
|
|
241
|
+
│ └── IirFilter.cc (~280 lines)
|
|
242
|
+
├── FilterBindings.cc (~720 lines)
|
|
243
|
+
└── DspPipeline.cc (updated with init calls)
|
|
244
|
+
|
|
245
|
+
binding.gyp (updated with new sources)
|
|
246
|
+
test-filters.cjs (verification script)
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
## Compilation Warnings (Harmless)
|
|
250
|
+
|
|
251
|
+
- **C4661**: Explicit template instantiation warnings for `convolve()` and `bilinearTransform()` - These are declared in headers but only defined for explicitly instantiated types (`float` and `double`). No runtime impact.
|
|
252
|
+
- **D9025**: Compiler flags override warnings (`/std:c++17` overriding `/std:c++20`) - Expected behavior from node-gyp configuration.
|
|
253
|
+
|
|
254
|
+
## Conclusion
|
|
255
|
+
|
|
256
|
+
FIR and IIR digital filters are **fully operational** at the C++ and N-API binding level. All factory methods work correctly, both stateful (streaming) and stateless (batch) modes are functional, and coefficient management is working. The implementation follows industry-standard filter design techniques and maintains numerical stability.
|
|
257
|
+
|
|
258
|
+
**Build Status**: ✅ **3012 functions compiled successfully**
|
|
259
|
+
**Test Status**: ✅ **All 395 existing tests passing**
|
|
260
|
+
**Integration**: ✅ **Ready for TypeScript wrapper layer**
|
|
@@ -0,0 +1,418 @@
|
|
|
1
|
+
# Filter Design API - User Guide
|
|
2
|
+
|
|
3
|
+
## Quick Start
|
|
4
|
+
|
|
5
|
+
```typescript
|
|
6
|
+
import { createFilter, FirFilter, IirFilter } from "dspx";
|
|
7
|
+
|
|
8
|
+
// Method 1: Unified API
|
|
9
|
+
const filter = createFilter({
|
|
10
|
+
type: "fir",
|
|
11
|
+
mode: "lowpass",
|
|
12
|
+
cutoffFrequency: 1000, // Hz
|
|
13
|
+
sampleRate: 8000, // Hz
|
|
14
|
+
order: 51,
|
|
15
|
+
windowType: "hamming",
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
// Method 2: Direct class methods
|
|
19
|
+
const fir = FirFilter.createLowPass({
|
|
20
|
+
cutoffFrequency: 1000,
|
|
21
|
+
sampleRate: 8000,
|
|
22
|
+
order: 51,
|
|
23
|
+
windowType: "hamming",
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
const iir = IirFilter.createButterworthLowPass({
|
|
27
|
+
cutoffFrequency: 1000,
|
|
28
|
+
sampleRate: 8000,
|
|
29
|
+
order: 4,
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
// Process signal
|
|
33
|
+
const filtered = await filter.process(signal);
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Filter Types
|
|
37
|
+
|
|
38
|
+
### FIR Filters
|
|
39
|
+
|
|
40
|
+
**Pros**: Always stable, linear phase possible, precise frequency response
|
|
41
|
+
**Cons**: More taps = more computation
|
|
42
|
+
|
|
43
|
+
**Available Modes**:
|
|
44
|
+
|
|
45
|
+
- `lowpass` - Pass low frequencies, attenuate high
|
|
46
|
+
- `highpass` - Pass high frequencies, attenuate low
|
|
47
|
+
- `bandpass` - Pass only frequencies in specified band
|
|
48
|
+
- `bandstop` - Reject frequencies in specified band (notch filter)
|
|
49
|
+
|
|
50
|
+
**Window Types**:
|
|
51
|
+
|
|
52
|
+
- `hamming` - Good for general use (default)
|
|
53
|
+
- `hann` - Smooth frequency response
|
|
54
|
+
- `blackman` - Best sidelobe rejection
|
|
55
|
+
- `bartlett` - Simple triangular window
|
|
56
|
+
|
|
57
|
+
### IIR Butterworth Filters
|
|
58
|
+
|
|
59
|
+
**Pros**: Efficient (fewer coefficients), sharp roll-off
|
|
60
|
+
**Cons**: Non-linear phase, can be unstable if poorly designed
|
|
61
|
+
|
|
62
|
+
**Available Modes**:
|
|
63
|
+
|
|
64
|
+
- `lowpass` - Maximally flat passband
|
|
65
|
+
- `highpass` - Maximally flat passband
|
|
66
|
+
- `bandpass` - Two Butterworth filters cascaded
|
|
67
|
+
|
|
68
|
+
**Orders**: 1-8 (higher order = sharper transition)
|
|
69
|
+
|
|
70
|
+
### First-Order IIR Filters
|
|
71
|
+
|
|
72
|
+
**Pros**: Very fast, low latency, minimal state
|
|
73
|
+
**Cons**: Gentle rolloff (-20 dB/decade)
|
|
74
|
+
|
|
75
|
+
**Available Modes**:
|
|
76
|
+
|
|
77
|
+
- `lowpass` - Simple RC low-pass
|
|
78
|
+
- `highpass` - Simple RC high-pass
|
|
79
|
+
|
|
80
|
+
## API Reference
|
|
81
|
+
|
|
82
|
+
### `createFilter(options)`
|
|
83
|
+
|
|
84
|
+
Unified function to create any filter type.
|
|
85
|
+
|
|
86
|
+
```typescript
|
|
87
|
+
function createFilter(options: FilterOptions): FirFilter | IirFilter;
|
|
88
|
+
|
|
89
|
+
type FilterOptions = {
|
|
90
|
+
type: "fir" | "butterworth";
|
|
91
|
+
mode: "lowpass" | "highpass" | "bandpass" | "bandstop" | "notch";
|
|
92
|
+
|
|
93
|
+
// For low-pass and high-pass
|
|
94
|
+
cutoffFrequency: number; // Hz
|
|
95
|
+
|
|
96
|
+
// For band-pass and band-stop
|
|
97
|
+
lowCutoffFrequency?: number; // Hz
|
|
98
|
+
highCutoffFrequency?: number; // Hz
|
|
99
|
+
|
|
100
|
+
sampleRate: number; // Hz
|
|
101
|
+
order: number; // Number of taps (FIR) or filter order (IIR)
|
|
102
|
+
|
|
103
|
+
// FIR only
|
|
104
|
+
windowType?: "hamming" | "hann" | "blackman" | "bartlett";
|
|
105
|
+
};
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
### `FirFilter` Class
|
|
109
|
+
|
|
110
|
+
```typescript
|
|
111
|
+
class FirFilter {
|
|
112
|
+
// Factory methods
|
|
113
|
+
static createLowPass(options: {
|
|
114
|
+
cutoffFrequency: number;
|
|
115
|
+
sampleRate: number;
|
|
116
|
+
order: number;
|
|
117
|
+
windowType?: string;
|
|
118
|
+
}): FirFilter;
|
|
119
|
+
|
|
120
|
+
static createHighPass(options: {...}): FirFilter;
|
|
121
|
+
static createBandPass(options: {...}): FirFilter;
|
|
122
|
+
static createBandStop(options: {...}): FirFilter;
|
|
123
|
+
|
|
124
|
+
// Instance methods
|
|
125
|
+
process(input: Float32Array): Promise<Float32Array>;
|
|
126
|
+
processSample(sample: number): Promise<number>;
|
|
127
|
+
reset(): void;
|
|
128
|
+
getOrder(): number;
|
|
129
|
+
getCoefficients(): Float32Array;
|
|
130
|
+
}
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
### `IirFilter` Class
|
|
134
|
+
|
|
135
|
+
```typescript
|
|
136
|
+
class IirFilter {
|
|
137
|
+
// Butterworth factory methods
|
|
138
|
+
static createButterworthLowPass(options: {
|
|
139
|
+
cutoffFrequency: number;
|
|
140
|
+
sampleRate: number;
|
|
141
|
+
order: number;
|
|
142
|
+
}): IirFilter;
|
|
143
|
+
|
|
144
|
+
static createButterworthHighPass(options: {...}): IirFilter;
|
|
145
|
+
static createButterworthBandPass(options: {...}): IirFilter;
|
|
146
|
+
|
|
147
|
+
// First-order factory methods
|
|
148
|
+
static createFirstOrderLowPass(options: {
|
|
149
|
+
cutoffFrequency: number;
|
|
150
|
+
sampleRate: number;
|
|
151
|
+
}): IirFilter;
|
|
152
|
+
|
|
153
|
+
static createFirstOrderHighPass(options: {...}): IirFilter;
|
|
154
|
+
|
|
155
|
+
// Instance methods
|
|
156
|
+
process(input: Float32Array): Promise<Float32Array>;
|
|
157
|
+
processSample(sample: number): Promise<number>;
|
|
158
|
+
reset(): void;
|
|
159
|
+
getOrder(): number;
|
|
160
|
+
getBCoefficients(): Float32Array;
|
|
161
|
+
getACoefficients(): Float32Array;
|
|
162
|
+
isStable(): boolean;
|
|
163
|
+
}
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
## Usage Examples
|
|
167
|
+
|
|
168
|
+
### Example 1: Anti-Aliasing Filter
|
|
169
|
+
|
|
170
|
+
```typescript
|
|
171
|
+
const antiAlias = createFilter({
|
|
172
|
+
type: "butterworth",
|
|
173
|
+
mode: "lowpass",
|
|
174
|
+
cutoffFrequency: 4000, // Nyquist - 1000 Hz
|
|
175
|
+
sampleRate: 10000,
|
|
176
|
+
order: 4,
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
const filtered = await antiAlias.process(rawSignal);
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
### Example 2: DC Offset Removal
|
|
183
|
+
|
|
184
|
+
```typescript
|
|
185
|
+
const dcBlocker = IirFilter.createFirstOrderHighPass({
|
|
186
|
+
cutoffFrequency: 20, // Remove below 20 Hz
|
|
187
|
+
sampleRate: 8000,
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
const noDC = await dcBlocker.process(signal);
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
### Example 3: Voice Band Extraction
|
|
194
|
+
|
|
195
|
+
```typescript
|
|
196
|
+
const voiceFilter = FirFilter.createBandPass({
|
|
197
|
+
lowCutoffFrequency: 300, // Telephone voice band
|
|
198
|
+
highCutoffFrequency: 3400,
|
|
199
|
+
sampleRate: 8000,
|
|
200
|
+
order: 101,
|
|
201
|
+
windowType: "blackman", // Best frequency selectivity
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
const voiceOnly = await voiceFilter.process(audio);
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
### Example 4: 50/60 Hz Notch Filter
|
|
208
|
+
|
|
209
|
+
```typescript
|
|
210
|
+
// Remove powerline hum
|
|
211
|
+
const notch50Hz = createFilter({
|
|
212
|
+
type: "fir",
|
|
213
|
+
mode: "notch",
|
|
214
|
+
lowCutoffFrequency: 48,
|
|
215
|
+
highCutoffFrequency: 52,
|
|
216
|
+
sampleRate: 8000,
|
|
217
|
+
order: 201, // Higher order = narrower notch
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
const clean = await notch50Hz.process(noisySignal);
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
### Example 5: Real-Time Sample-by-Sample
|
|
224
|
+
|
|
225
|
+
```typescript
|
|
226
|
+
const realtimeFilter = createFilter({
|
|
227
|
+
type: "butterworth",
|
|
228
|
+
mode: "lowpass",
|
|
229
|
+
cutoffFrequency: 1000,
|
|
230
|
+
sampleRate: 8000,
|
|
231
|
+
order: 4,
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
// Stream processing
|
|
235
|
+
for (const sample of incomingData) {
|
|
236
|
+
const filtered = await realtimeFilter.processSample(sample);
|
|
237
|
+
sendToOutput(filtered);
|
|
238
|
+
}
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
### Example 6: Batch Processing with State
|
|
242
|
+
|
|
243
|
+
```typescript
|
|
244
|
+
const filter = FirFilter.createLowPass({
|
|
245
|
+
cutoffFrequency: 1000,
|
|
246
|
+
sampleRate: 8000,
|
|
247
|
+
order: 51,
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
// Process first batch
|
|
251
|
+
const batch1 = await filter.process(chunk1);
|
|
252
|
+
|
|
253
|
+
// Process second batch (state maintained)
|
|
254
|
+
const batch2 = await filter.process(chunk2);
|
|
255
|
+
|
|
256
|
+
// Reset state if needed
|
|
257
|
+
filter.reset();
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
## Performance Guide
|
|
261
|
+
|
|
262
|
+
### Choosing Filter Type
|
|
263
|
+
|
|
264
|
+
| Use Case | Recommended Filter |
|
|
265
|
+
| -------------------- | ----------------------------- |
|
|
266
|
+
| Audio processing | FIR (linear phase) |
|
|
267
|
+
| Real-time control | IIR Butterworth (low latency) |
|
|
268
|
+
| Voice communications | FIR band-pass |
|
|
269
|
+
| Powerline removal | FIR notch (narrow band) |
|
|
270
|
+
| DC removal | First-order IIR high-pass |
|
|
271
|
+
| Anti-aliasing | IIR Butterworth (efficiency) |
|
|
272
|
+
|
|
273
|
+
### FIR Order Guidelines
|
|
274
|
+
|
|
275
|
+
- **Anti-aliasing**: 51-101 taps
|
|
276
|
+
- **Voice band-pass**: 101-201 taps
|
|
277
|
+
- **Notch filter**: 151-301 taps
|
|
278
|
+
- **General smoothing**: 31-51 taps
|
|
279
|
+
|
|
280
|
+
**Rule of thumb**: Transition width ≈ (4-8) / order
|
|
281
|
+
|
|
282
|
+
### IIR Order Guidelines
|
|
283
|
+
|
|
284
|
+
- **Order 2**: Basic smoothing, gentle rolloff
|
|
285
|
+
- **Order 4**: Good selectivity, common choice
|
|
286
|
+
- **Order 6-8**: Sharp transition, more latency
|
|
287
|
+
|
|
288
|
+
**Rule of thumb**: Each order adds ~6 dB/octave rolloff
|
|
289
|
+
|
|
290
|
+
## Validation and Error Handling
|
|
291
|
+
|
|
292
|
+
The API automatically validates:
|
|
293
|
+
|
|
294
|
+
✅ Cutoff frequency < Nyquist frequency (sampleRate / 2)
|
|
295
|
+
✅ Order is in valid range (FIR: > 0, IIR: 1-8)
|
|
296
|
+
✅ Band-pass/stop: low cutoff < high cutoff
|
|
297
|
+
✅ All parameters are positive numbers
|
|
298
|
+
|
|
299
|
+
Error messages:
|
|
300
|
+
|
|
301
|
+
```typescript
|
|
302
|
+
// Cutoff > Nyquist
|
|
303
|
+
Error: "Cutoff frequency must be between 0 and 4000 Hz (Nyquist frequency)";
|
|
304
|
+
|
|
305
|
+
// Invalid order
|
|
306
|
+
Error: "Order must be between 1 and 8 for IIR Butterworth filters";
|
|
307
|
+
|
|
308
|
+
// Invalid band
|
|
309
|
+
Error: "Low cutoff must be less than high cutoff";
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
## Technical Details
|
|
313
|
+
|
|
314
|
+
### Frequency Normalization
|
|
315
|
+
|
|
316
|
+
You provide frequencies in **Hz**, the API normalizes internally:
|
|
317
|
+
|
|
318
|
+
```typescript
|
|
319
|
+
normalizedFreq = cutoffFrequency / (sampleRate / 2); // Range: 0 to 1
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
### FIR Implementation
|
|
323
|
+
|
|
324
|
+
- **Method**: Windowed sinc design
|
|
325
|
+
- **Optimization**: SIMD-accelerated convolution (6.7x speedup)
|
|
326
|
+
- **Phase**: Linear (symmetric coefficients)
|
|
327
|
+
- **Stability**: Always stable
|
|
328
|
+
|
|
329
|
+
### IIR Implementation
|
|
330
|
+
|
|
331
|
+
- **Method**: Bilinear transform (analog → digital)
|
|
332
|
+
- **Structure**: Direct Form II (biquad sections)
|
|
333
|
+
- **Phase**: Non-linear
|
|
334
|
+
- **Stability**: Checked on construction
|
|
335
|
+
|
|
336
|
+
### State Management
|
|
337
|
+
|
|
338
|
+
Both FIR and IIR filters maintain state:
|
|
339
|
+
|
|
340
|
+
- **FIR**: Circular buffer of past inputs
|
|
341
|
+
- **IIR**: Past inputs and outputs (feedback)
|
|
342
|
+
|
|
343
|
+
Call `reset()` to clear state between independent signals.
|
|
344
|
+
|
|
345
|
+
## Common Patterns
|
|
346
|
+
|
|
347
|
+
### Pattern 1: Multi-Stage Filtering
|
|
348
|
+
|
|
349
|
+
```typescript
|
|
350
|
+
// Anti-alias then downsample
|
|
351
|
+
const antiAlias = createFilter({
|
|
352
|
+
type: "butterworth",
|
|
353
|
+
mode: "lowpass",
|
|
354
|
+
cutoffFrequency: sampleRate / 4,
|
|
355
|
+
sampleRate,
|
|
356
|
+
order: 4,
|
|
357
|
+
});
|
|
358
|
+
|
|
359
|
+
const step1 = await antiAlias.process(signal);
|
|
360
|
+
const downsampled = downsample(step1, 2);
|
|
361
|
+
```
|
|
362
|
+
|
|
363
|
+
### Pattern 2: Cascade for Sharp Rolloff
|
|
364
|
+
|
|
365
|
+
```typescript
|
|
366
|
+
// Two 2nd-order filters = 4th-order response
|
|
367
|
+
const stage1 = IirFilter.createButterworthLowPass({
|
|
368
|
+
cutoffFrequency: 1000,
|
|
369
|
+
sampleRate: 8000,
|
|
370
|
+
order: 2,
|
|
371
|
+
});
|
|
372
|
+
|
|
373
|
+
const stage2 = IirFilter.createButterworthLowPass({
|
|
374
|
+
cutoffFrequency: 1000,
|
|
375
|
+
sampleRate: 8000,
|
|
376
|
+
order: 2,
|
|
377
|
+
});
|
|
378
|
+
|
|
379
|
+
const filtered1 = await stage1.process(signal);
|
|
380
|
+
const filtered2 = await stage2.process(filtered1);
|
|
381
|
+
```
|
|
382
|
+
|
|
383
|
+
### Pattern 3: Adaptive Filtering
|
|
384
|
+
|
|
385
|
+
```typescript
|
|
386
|
+
let cutoff = 1000;
|
|
387
|
+
|
|
388
|
+
function updateFilter() {
|
|
389
|
+
filter = createFilter({
|
|
390
|
+
type: "butterworth",
|
|
391
|
+
mode: "lowpass",
|
|
392
|
+
cutoffFrequency: cutoff,
|
|
393
|
+
sampleRate: 8000,
|
|
394
|
+
order: 4,
|
|
395
|
+
});
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
// Adjust cutoff based on signal characteristics
|
|
399
|
+
if (noiseLevel > threshold) {
|
|
400
|
+
cutoff = 500; // More aggressive filtering
|
|
401
|
+
updateFilter();
|
|
402
|
+
}
|
|
403
|
+
```
|
|
404
|
+
|
|
405
|
+
## See Also
|
|
406
|
+
|
|
407
|
+
- **Examples**: `src/ts/examples/filter-examples.ts`
|
|
408
|
+
- **Tests**: Run `npm test`
|
|
409
|
+
- **C++ Implementation**: `docs/FILTERS_IMPLEMENTATION.md`
|
|
410
|
+
- **FFT Guide**: `docs/FFT_USER_GUIDE.md`
|
|
411
|
+
|
|
412
|
+
## Support
|
|
413
|
+
|
|
414
|
+
For more information:
|
|
415
|
+
|
|
416
|
+
- TypeScript types provide inline documentation
|
|
417
|
+
- Examples demonstrate all filter types
|
|
418
|
+
- Test suite shows validation patterns
|