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.
Files changed (172) hide show
  1. package/.github/workflows/ci.yml +185 -0
  2. package/.vscode/c_cpp_properties.json +17 -0
  3. package/.vscode/settings.json +68 -0
  4. package/.vscode/tasks.json +28 -0
  5. package/DISCLAIMER.md +32 -0
  6. package/LICENSE +21 -0
  7. package/README.md +1803 -0
  8. package/ROADMAP.md +192 -0
  9. package/TECHNICAL_DEBT.md +165 -0
  10. package/binding.gyp +65 -0
  11. package/docs/ADVANCED_LOGGER_FEATURES.md +598 -0
  12. package/docs/AUTHENTICATION_SECURITY.md +396 -0
  13. package/docs/BACKEND_IMPROVEMENTS.md +399 -0
  14. package/docs/CHEBYSHEV_BIQUAD_EQ_IMPLEMENTATION.md +405 -0
  15. package/docs/FFT_IMPLEMENTATION.md +490 -0
  16. package/docs/FFT_IMPROVEMENTS_SUMMARY.md +387 -0
  17. package/docs/FFT_USER_GUIDE.md +494 -0
  18. package/docs/FILTERS_IMPLEMENTATION.md +260 -0
  19. package/docs/FILTER_API_GUIDE.md +418 -0
  20. package/docs/FIR_SIMD_OPTIMIZATION.md +175 -0
  21. package/docs/LOGGER_API_REFERENCE.md +350 -0
  22. package/docs/NOTCH_FILTER_QUICK_REF.md +121 -0
  23. package/docs/PHASE2_TESTS_AND_NOTCH_FILTER.md +341 -0
  24. package/docs/PHASES_5_7_SUMMARY.md +403 -0
  25. package/docs/PIPELINE_FILTER_INTEGRATION.md +446 -0
  26. package/docs/SIMD_OPTIMIZATIONS.md +211 -0
  27. package/docs/TEST_MIGRATION_SUMMARY.md +173 -0
  28. package/docs/TIMESERIES_IMPLEMENTATION_SUMMARY.md +322 -0
  29. package/docs/TIMESERIES_QUICK_REF.md +85 -0
  30. package/docs/advanced.md +559 -0
  31. package/docs/time-series-guide.md +617 -0
  32. package/docs/time-series-migration.md +376 -0
  33. package/jest.config.js +37 -0
  34. package/package.json +42 -0
  35. package/prebuilds/linux-x64/dsp-ts-redis.node +0 -0
  36. package/prebuilds/win32-x64/dsp-ts-redis.node +0 -0
  37. package/scripts/test.js +24 -0
  38. package/src/build/dsp-ts-redis.node +0 -0
  39. package/src/native/DspPipeline.cc +675 -0
  40. package/src/native/DspPipeline.h +44 -0
  41. package/src/native/FftBindings.cc +817 -0
  42. package/src/native/FilterBindings.cc +1001 -0
  43. package/src/native/IDspStage.h +53 -0
  44. package/src/native/adapters/InterpolatorStage.h +201 -0
  45. package/src/native/adapters/MeanAbsoluteValueStage.h +289 -0
  46. package/src/native/adapters/MovingAverageStage.h +306 -0
  47. package/src/native/adapters/RectifyStage.h +88 -0
  48. package/src/native/adapters/ResamplerStage.h +238 -0
  49. package/src/native/adapters/RmsStage.h +299 -0
  50. package/src/native/adapters/SscStage.h +121 -0
  51. package/src/native/adapters/VarianceStage.h +307 -0
  52. package/src/native/adapters/WampStage.h +114 -0
  53. package/src/native/adapters/WaveformLengthStage.h +115 -0
  54. package/src/native/adapters/ZScoreNormalizeStage.h +326 -0
  55. package/src/native/core/FftEngine.cc +441 -0
  56. package/src/native/core/FftEngine.h +224 -0
  57. package/src/native/core/FirFilter.cc +324 -0
  58. package/src/native/core/FirFilter.h +149 -0
  59. package/src/native/core/IirFilter.cc +576 -0
  60. package/src/native/core/IirFilter.h +210 -0
  61. package/src/native/core/MovingAbsoluteValueFilter.cc +17 -0
  62. package/src/native/core/MovingAbsoluteValueFilter.h +135 -0
  63. package/src/native/core/MovingAverageFilter.cc +18 -0
  64. package/src/native/core/MovingAverageFilter.h +135 -0
  65. package/src/native/core/MovingFftFilter.cc +291 -0
  66. package/src/native/core/MovingFftFilter.h +203 -0
  67. package/src/native/core/MovingVarianceFilter.cc +194 -0
  68. package/src/native/core/MovingVarianceFilter.h +114 -0
  69. package/src/native/core/MovingZScoreFilter.cc +215 -0
  70. package/src/native/core/MovingZScoreFilter.h +113 -0
  71. package/src/native/core/Policies.h +352 -0
  72. package/src/native/core/RmsFilter.cc +18 -0
  73. package/src/native/core/RmsFilter.h +131 -0
  74. package/src/native/core/SscFilter.cc +16 -0
  75. package/src/native/core/SscFilter.h +137 -0
  76. package/src/native/core/WampFilter.cc +16 -0
  77. package/src/native/core/WampFilter.h +101 -0
  78. package/src/native/core/WaveformLengthFilter.cc +17 -0
  79. package/src/native/core/WaveformLengthFilter.h +98 -0
  80. package/src/native/utils/CircularBufferArray.cc +336 -0
  81. package/src/native/utils/CircularBufferArray.h +62 -0
  82. package/src/native/utils/CircularBufferVector.cc +145 -0
  83. package/src/native/utils/CircularBufferVector.h +45 -0
  84. package/src/native/utils/NapiUtils.cc +53 -0
  85. package/src/native/utils/NapiUtils.h +21 -0
  86. package/src/native/utils/SimdOps.h +870 -0
  87. package/src/native/utils/SlidingWindowFilter.cc +239 -0
  88. package/src/native/utils/SlidingWindowFilter.h +159 -0
  89. package/src/native/utils/TimeSeriesBuffer.cc +205 -0
  90. package/src/native/utils/TimeSeriesBuffer.h +140 -0
  91. package/src/ts/CircularLogBuffer.ts +87 -0
  92. package/src/ts/DriftDetector.ts +331 -0
  93. package/src/ts/TopicRouter.ts +428 -0
  94. package/src/ts/__tests__/AdvancedDsp.test.ts +585 -0
  95. package/src/ts/__tests__/AuthAndEdgeCases.test.ts +241 -0
  96. package/src/ts/__tests__/Chaining.test.ts +387 -0
  97. package/src/ts/__tests__/ChebyshevBiquad.test.ts +229 -0
  98. package/src/ts/__tests__/CircularLogBuffer.test.ts +158 -0
  99. package/src/ts/__tests__/DriftDetector.test.ts +389 -0
  100. package/src/ts/__tests__/Fft.test.ts +484 -0
  101. package/src/ts/__tests__/ListState.test.ts +153 -0
  102. package/src/ts/__tests__/Logger.test.ts +208 -0
  103. package/src/ts/__tests__/LoggerAdvanced.test.ts +319 -0
  104. package/src/ts/__tests__/LoggerMinor.test.ts +247 -0
  105. package/src/ts/__tests__/MeanAbsoluteValue.test.ts +398 -0
  106. package/src/ts/__tests__/MovingAverage.test.ts +322 -0
  107. package/src/ts/__tests__/RMS.test.ts +315 -0
  108. package/src/ts/__tests__/Rectify.test.ts +272 -0
  109. package/src/ts/__tests__/Redis.test.ts +456 -0
  110. package/src/ts/__tests__/SlopeSignChange.test.ts +166 -0
  111. package/src/ts/__tests__/Tap.test.ts +164 -0
  112. package/src/ts/__tests__/TimeBasedExpiration.test.ts +124 -0
  113. package/src/ts/__tests__/TimeBasedRmsAndMav.test.ts +231 -0
  114. package/src/ts/__tests__/TimeBasedVarianceAndZScore.test.ts +284 -0
  115. package/src/ts/__tests__/TimeSeries.test.ts +254 -0
  116. package/src/ts/__tests__/TopicRouter.test.ts +332 -0
  117. package/src/ts/__tests__/TopicRouterAdvanced.test.ts +483 -0
  118. package/src/ts/__tests__/TopicRouterPriority.test.ts +487 -0
  119. package/src/ts/__tests__/Variance.test.ts +509 -0
  120. package/src/ts/__tests__/WaveformLength.test.ts +147 -0
  121. package/src/ts/__tests__/WillisonAmplitude.test.ts +197 -0
  122. package/src/ts/__tests__/ZScoreNormalize.test.ts +459 -0
  123. package/src/ts/advanced-dsp.ts +566 -0
  124. package/src/ts/backends.ts +1137 -0
  125. package/src/ts/bindings.ts +1225 -0
  126. package/src/ts/easter-egg.ts +42 -0
  127. package/src/ts/examples/MeanAbsoluteValue/test-state.ts +99 -0
  128. package/src/ts/examples/MeanAbsoluteValue/test-streaming.ts +269 -0
  129. package/src/ts/examples/MovingAverage/test-state.ts +85 -0
  130. package/src/ts/examples/MovingAverage/test-streaming.ts +188 -0
  131. package/src/ts/examples/RMS/test-state.ts +97 -0
  132. package/src/ts/examples/RMS/test-streaming.ts +253 -0
  133. package/src/ts/examples/Rectify/test-state.ts +107 -0
  134. package/src/ts/examples/Rectify/test-streaming.ts +242 -0
  135. package/src/ts/examples/Variance/test-state.ts +195 -0
  136. package/src/ts/examples/Variance/test-streaming.ts +260 -0
  137. package/src/ts/examples/ZScoreNormalize/test-state.ts +277 -0
  138. package/src/ts/examples/ZScoreNormalize/test-streaming.ts +306 -0
  139. package/src/ts/examples/advanced-dsp-examples.ts +397 -0
  140. package/src/ts/examples/callbacks/advanced-router-features.ts +326 -0
  141. package/src/ts/examples/callbacks/benchmark-circular-buffer.ts +109 -0
  142. package/src/ts/examples/callbacks/monitoring-example.ts +265 -0
  143. package/src/ts/examples/callbacks/pipeline-callbacks-example.ts +137 -0
  144. package/src/ts/examples/callbacks/pooled-callbacks-example.ts +274 -0
  145. package/src/ts/examples/callbacks/priority-routing-example.ts +277 -0
  146. package/src/ts/examples/callbacks/production-topic-router.ts +214 -0
  147. package/src/ts/examples/callbacks/topic-based-logging.ts +161 -0
  148. package/src/ts/examples/chaining/test-chaining-redis.ts +113 -0
  149. package/src/ts/examples/chaining/test-chaining.ts +52 -0
  150. package/src/ts/examples/emg-features-example.ts +284 -0
  151. package/src/ts/examples/fft-example.ts +309 -0
  152. package/src/ts/examples/fft-examples.ts +349 -0
  153. package/src/ts/examples/filter-examples.ts +320 -0
  154. package/src/ts/examples/list-state-example.ts +131 -0
  155. package/src/ts/examples/logger-example.ts +91 -0
  156. package/src/ts/examples/notch-filter-examples.ts +243 -0
  157. package/src/ts/examples/phase5/drift-detection-example.ts +290 -0
  158. package/src/ts/examples/phase6-7/production-observability.ts +476 -0
  159. package/src/ts/examples/phase6-7/redis-timeseries-integration.ts +446 -0
  160. package/src/ts/examples/redis/redis-example.ts +202 -0
  161. package/src/ts/examples/redis-example.ts +202 -0
  162. package/src/ts/examples/simd-benchmark.ts +126 -0
  163. package/src/ts/examples/tap-debugging.ts +230 -0
  164. package/src/ts/examples/timeseries/comparison-example.ts +290 -0
  165. package/src/ts/examples/timeseries/iot-sensor-example.ts +143 -0
  166. package/src/ts/examples/timeseries/redis-streaming-example.ts +233 -0
  167. package/src/ts/examples/waveform-length-example.ts +139 -0
  168. package/src/ts/fft.ts +722 -0
  169. package/src/ts/filters.ts +1078 -0
  170. package/src/ts/index.ts +120 -0
  171. package/src/ts/types.ts +589 -0
  172. package/tsconfig.json +15 -0
@@ -0,0 +1,324 @@
1
+ /**
2
+ * FIR Filter Implementation with SIMD-Optimized Convolution
3
+ * Now uses SlidingWindowFilter infrastructure for consistency
4
+ */
5
+
6
+ #define _USE_MATH_DEFINES
7
+ #include "FirFilter.h"
8
+ #include "../utils/SimdOps.h"
9
+ #include <cmath>
10
+ #include <stdexcept>
11
+ #include <algorithm>
12
+
13
+ #ifndef M_PI
14
+ #define M_PI 3.14159265358979323846
15
+ #endif
16
+
17
+ namespace dsp
18
+ {
19
+ namespace core
20
+ {
21
+
22
+ template <typename T>
23
+ FirFilter<T>::FirFilter(const std::vector<T> &coefficients, bool stateful)
24
+ : m_coefficients(coefficients), m_stateIndex(0), m_stateful(stateful)
25
+ {
26
+ if (coefficients.empty())
27
+ {
28
+ throw std::invalid_argument("FIR filter requires at least one coefficient");
29
+ }
30
+
31
+ if (stateful)
32
+ {
33
+ // Allocate state buffer (need M previous samples)
34
+ m_state.resize(coefficients.size(), T(0));
35
+ }
36
+ }
37
+
38
+ template <typename T>
39
+ T FirFilter<T>::processSample(T input)
40
+ {
41
+ if (!m_stateful)
42
+ {
43
+ throw std::runtime_error("processSample() requires stateful mode");
44
+ }
45
+
46
+ // Store input in circular buffer
47
+ m_state[m_stateIndex] = input;
48
+
49
+ // Compute output via SIMD-optimized convolution
50
+ T output = T(0);
51
+
52
+ if constexpr (std::is_same_v<T, float>)
53
+ {
54
+ // Build aligned buffer for SIMD (coefficients are in reverse order for convolution)
55
+ std::vector<float> aligned_samples(m_coefficients.size());
56
+
57
+ // Copy samples in correct order for dot product
58
+ for (size_t i = 0; i < m_coefficients.size(); ++i)
59
+ {
60
+ size_t stateIdx = (m_stateIndex + m_state.size() - i) % m_state.size();
61
+ aligned_samples[i] = m_state[stateIdx];
62
+ }
63
+
64
+ // SIMD dot product
65
+ output = simd::dot_product(aligned_samples.data(), m_coefficients.data(),
66
+ m_coefficients.size());
67
+ }
68
+ else
69
+ {
70
+ // Scalar convolution for double
71
+ for (size_t i = 0; i < m_coefficients.size(); ++i)
72
+ {
73
+ size_t stateIdx = (m_stateIndex + m_state.size() - i) % m_state.size();
74
+ output += m_coefficients[i] * m_state[stateIdx];
75
+ }
76
+ }
77
+
78
+ // Advance circular buffer index
79
+ m_stateIndex = (m_stateIndex + 1) % m_state.size();
80
+
81
+ return output;
82
+ }
83
+
84
+ template <typename T>
85
+ void FirFilter<T>::process(const T *input, T *output, size_t length, bool stateless)
86
+ {
87
+ if (stateless || !m_stateful)
88
+ {
89
+ // Stateless mode: each output depends only on current window
90
+ for (size_t n = 0; n < length; ++n)
91
+ {
92
+ T sum = T(0);
93
+
94
+ if constexpr (std::is_same_v<T, float>)
95
+ {
96
+ // Use SIMD for stateless convolution too
97
+ size_t available = std::min(m_coefficients.size(), n + 1);
98
+
99
+ if (available == m_coefficients.size())
100
+ {
101
+ // Full window available - direct SIMD dot product
102
+ sum = simd::dot_product(&input[n - available + 1],
103
+ m_coefficients.data(),
104
+ available);
105
+ }
106
+ else
107
+ {
108
+ // Partial window - scalar for simplicity
109
+ for (size_t i = 0; i < available; ++i)
110
+ {
111
+ sum += m_coefficients[i] * input[n - i];
112
+ }
113
+ }
114
+ }
115
+ else
116
+ {
117
+ // Scalar for double
118
+ for (size_t i = 0; i < m_coefficients.size() && i <= n; ++i)
119
+ {
120
+ sum += m_coefficients[i] * input[n - i];
121
+ }
122
+ }
123
+
124
+ output[n] = sum;
125
+ }
126
+ }
127
+ else
128
+ {
129
+ // Stateful mode: use processSample for each input
130
+ for (size_t i = 0; i < length; ++i)
131
+ {
132
+ output[i] = processSample(input[i]);
133
+ }
134
+ }
135
+ }
136
+
137
+ template <typename T>
138
+ void FirFilter<T>::reset()
139
+ {
140
+ if (m_stateful)
141
+ {
142
+ std::fill(m_state.begin(), m_state.end(), T(0));
143
+ m_stateIndex = 0;
144
+ }
145
+ }
146
+
147
+ template <typename T>
148
+ void FirFilter<T>::setCoefficients(const std::vector<T> &coefficients)
149
+ {
150
+ if (coefficients.empty())
151
+ {
152
+ throw std::invalid_argument("Coefficients cannot be empty");
153
+ }
154
+
155
+ m_coefficients = coefficients;
156
+
157
+ if (m_stateful)
158
+ {
159
+ m_state.resize(coefficients.size(), T(0));
160
+ m_stateIndex = 0;
161
+ }
162
+ }
163
+
164
+ // ========== Filter Design Methods ==========
165
+
166
+ template <typename T>
167
+ std::vector<T> FirFilter<T>::generateSincLowPass(T cutoffFreq, size_t numTaps)
168
+ {
169
+ if (numTaps % 2 == 0)
170
+ {
171
+ ++numTaps; // Ensure odd for symmetric impulse response
172
+ }
173
+
174
+ std::vector<T> impulse(numTaps);
175
+ int M = static_cast<int>(numTaps - 1);
176
+ int M_half = M / 2;
177
+
178
+ for (int n = 0; n < static_cast<int>(numTaps); ++n)
179
+ {
180
+ int n_shifted = n - M_half;
181
+
182
+ if (n_shifted == 0)
183
+ {
184
+ // sinc(0) = 1
185
+ impulse[n] = T(2) * cutoffFreq;
186
+ }
187
+ else
188
+ {
189
+ // sinc(x) = sin(πx) / (πx)
190
+ T x = T(2) * static_cast<T>(M_PI) * cutoffFreq * static_cast<T>(n_shifted);
191
+ impulse[n] = std::sin(x) / (static_cast<T>(M_PI) * static_cast<T>(n_shifted));
192
+ }
193
+ }
194
+
195
+ return impulse;
196
+ }
197
+
198
+ template <typename T>
199
+ void FirFilter<T>::applyWindow(std::vector<T> &impulse, const std::string &windowType)
200
+ {
201
+ const size_t N = impulse.size();
202
+ const T pi = static_cast<T>(M_PI);
203
+
204
+ for (size_t n = 0; n < N; ++n)
205
+ {
206
+ T window = T(1);
207
+ const T nf = static_cast<T>(n);
208
+ const T Nf = static_cast<T>(N);
209
+
210
+ if (windowType == "hamming")
211
+ {
212
+ window = T(0.54) - T(0.46) * std::cos(T(2) * pi * nf / (Nf - T(1)));
213
+ }
214
+ else if (windowType == "hann")
215
+ {
216
+ window = T(0.5) * (T(1) - std::cos(T(2) * pi * nf / (Nf - T(1))));
217
+ }
218
+ else if (windowType == "blackman")
219
+ {
220
+ window = T(0.42) - T(0.5) * std::cos(T(2) * pi * nf / (Nf - T(1))) + T(0.08) * std::cos(T(4) * pi * nf / (Nf - T(1)));
221
+ }
222
+ else if (windowType == "bartlett")
223
+ {
224
+ window = T(1) - std::abs(T(2) * nf / (Nf - T(1)) - T(1));
225
+ }
226
+
227
+ impulse[n] *= window;
228
+ }
229
+ }
230
+
231
+ template <typename T>
232
+ FirFilter<T> FirFilter<T>::createLowPass(T cutoffFreq, size_t numTaps, const std::string &windowType)
233
+ {
234
+ if (cutoffFreq <= 0 || cutoffFreq >= T(0.5))
235
+ {
236
+ throw std::invalid_argument("Cutoff frequency must be between 0 and 0.5 (normalized)");
237
+ }
238
+
239
+ auto impulse = generateSincLowPass(cutoffFreq, numTaps);
240
+ applyWindow(impulse, windowType);
241
+
242
+ // Normalize to unit gain at DC
243
+ T sum = T(0);
244
+ for (const auto &val : impulse)
245
+ {
246
+ sum += val;
247
+ }
248
+
249
+ for (auto &val : impulse)
250
+ {
251
+ val /= sum;
252
+ }
253
+
254
+ return FirFilter<T>(impulse, true);
255
+ }
256
+
257
+ template <typename T>
258
+ FirFilter<T> FirFilter<T>::createHighPass(T cutoffFreq, size_t numTaps, const std::string &windowType)
259
+ {
260
+ // High-pass = delta - low-pass (spectral inversion)
261
+ auto lowPass = createLowPass(cutoffFreq, numTaps, windowType);
262
+ auto coeffs = lowPass.getCoefficients();
263
+
264
+ // Spectral inversion
265
+ for (size_t i = 0; i < coeffs.size(); ++i)
266
+ {
267
+ coeffs[i] = -coeffs[i];
268
+ }
269
+
270
+ // Add impulse at center
271
+ coeffs[coeffs.size() / 2] += T(1);
272
+
273
+ return FirFilter<T>(coeffs, true);
274
+ }
275
+
276
+ template <typename T>
277
+ FirFilter<T> FirFilter<T>::createBandPass(T lowCutoff, T highCutoff, size_t numTaps, const std::string &windowType)
278
+ {
279
+ if (lowCutoff >= highCutoff)
280
+ {
281
+ throw std::invalid_argument("Low cutoff must be less than high cutoff");
282
+ }
283
+
284
+ // Band-pass = low-pass(high) - low-pass(low)
285
+ auto lpHigh = createLowPass(highCutoff, numTaps, windowType);
286
+ auto lpLow = createLowPass(lowCutoff, numTaps, windowType);
287
+
288
+ auto coeffsHigh = lpHigh.getCoefficients();
289
+ auto coeffsLow = lpLow.getCoefficients();
290
+
291
+ std::vector<T> bandPass(coeffsHigh.size());
292
+ for (size_t i = 0; i < coeffsHigh.size(); ++i)
293
+ {
294
+ bandPass[i] = coeffsHigh[i] - coeffsLow[i];
295
+ }
296
+
297
+ return FirFilter<T>(bandPass, true);
298
+ }
299
+
300
+ template <typename T>
301
+ FirFilter<T> FirFilter<T>::createBandStop(T lowCutoff, T highCutoff, size_t numTaps, const std::string &windowType)
302
+ {
303
+ // Band-stop = low-pass(low) + high-pass(high)
304
+ auto lpLow = createLowPass(lowCutoff, numTaps, windowType);
305
+ auto hpHigh = createHighPass(highCutoff, numTaps, windowType);
306
+
307
+ auto coeffsLow = lpLow.getCoefficients();
308
+ auto coeffsHigh = hpHigh.getCoefficients();
309
+
310
+ std::vector<T> bandStop(coeffsLow.size());
311
+ for (size_t i = 0; i < coeffsLow.size(); ++i)
312
+ {
313
+ bandStop[i] = coeffsLow[i] + coeffsHigh[i];
314
+ }
315
+
316
+ return FirFilter<T>(bandStop, true);
317
+ }
318
+
319
+ // Explicit template instantiations
320
+ template class FirFilter<float>;
321
+ template class FirFilter<double>;
322
+
323
+ } // namespace core
324
+ } // namespace dsp
@@ -0,0 +1,149 @@
1
+ /**
2
+ * FIR (Finite Impulse Response) Filter
3
+ *
4
+ * A non-recursive filter defined by:
5
+ * y[n] = b[0]*x[n] + b[1]*x[n-1] + ... + b[M]*x[n-M]
6
+ *
7
+ * Features:
8
+ * - Always stable (no feedback)
9
+ * - Linear phase possible
10
+ * - Stateful (maintains sample history) and stateless modes
11
+ * - SIMD-optimized convolution
12
+ * - Efficient circular buffer for state management
13
+ */
14
+
15
+ #ifndef DSP_CORE_FIR_FILTER_H
16
+ #define DSP_CORE_FIR_FILTER_H
17
+
18
+ #include <vector>
19
+ #include <cstddef>
20
+ #include <memory>
21
+ #include "../utils/CircularBufferArray.h"
22
+
23
+ namespace dsp
24
+ {
25
+ namespace core
26
+ {
27
+
28
+ template <typename T = float>
29
+ class FirFilter
30
+ {
31
+ public:
32
+ /**
33
+ * Constructor
34
+ * @param coefficients Filter coefficients (b[0], b[1], ..., b[M])
35
+ * @param stateful If true, maintains state between process calls
36
+ */
37
+ explicit FirFilter(const std::vector<T> &coefficients, bool stateful = true);
38
+
39
+ /**
40
+ * Process single sample (stateful mode only)
41
+ * @param input Input sample
42
+ * @return Filtered output sample
43
+ */
44
+ T processSample(T input);
45
+
46
+ /**
47
+ * Process batch of samples
48
+ * @param input Input samples
49
+ * @param output Output buffer (must be same size as input)
50
+ * @param length Number of samples
51
+ * @param stateless If true, ignores internal state (batch processing)
52
+ */
53
+ void process(const T *input, T *output, size_t length, bool stateless = false);
54
+
55
+ /**
56
+ * Reset filter state (clear history)
57
+ */
58
+ void reset();
59
+
60
+ /**
61
+ * Get filter order (M = number of coefficients - 1)
62
+ */
63
+ size_t getOrder() const { return m_coefficients.size() - 1; }
64
+
65
+ /**
66
+ * Get number of coefficients
67
+ */
68
+ size_t getNumCoefficients() const { return m_coefficients.size(); }
69
+
70
+ /**
71
+ * Get coefficients
72
+ */
73
+ const std::vector<T> &getCoefficients() const { return m_coefficients; }
74
+
75
+ /**
76
+ * Update coefficients (resets state)
77
+ */
78
+ void setCoefficients(const std::vector<T> &coefficients);
79
+
80
+ /**
81
+ * Check if filter is stateful
82
+ */
83
+ bool isStateful() const { return m_stateful; }
84
+
85
+ // ========== Common FIR Filter Designs ==========
86
+
87
+ /**
88
+ * Create low-pass filter using windowed sinc method
89
+ * @param cutoffFreq Cutoff frequency (normalized: 0 to 0.5)
90
+ * @param numTaps Number of filter taps (higher = sharper transition)
91
+ * @param windowType Window function ("hamming", "hann", "blackman")
92
+ */
93
+ static FirFilter<T> createLowPass(T cutoffFreq, size_t numTaps, const std::string &windowType = "hamming");
94
+
95
+ /**
96
+ * Create high-pass filter
97
+ * @param cutoffFreq Cutoff frequency (normalized: 0 to 0.5)
98
+ * @param numTaps Number of filter taps
99
+ * @param windowType Window function
100
+ */
101
+ static FirFilter<T> createHighPass(T cutoffFreq, size_t numTaps, const std::string &windowType = "hamming");
102
+
103
+ /**
104
+ * Create band-pass filter
105
+ * @param lowCutoff Low cutoff frequency (normalized)
106
+ * @param highCutoff High cutoff frequency (normalized)
107
+ * @param numTaps Number of filter taps
108
+ * @param windowType Window function
109
+ */
110
+ static FirFilter<T> createBandPass(T lowCutoff, T highCutoff, size_t numTaps, const std::string &windowType = "hamming");
111
+
112
+ /**
113
+ * Create band-stop (notch) filter
114
+ * @param lowCutoff Low cutoff frequency (normalized)
115
+ * @param highCutoff High cutoff frequency (normalized)
116
+ * @param numTaps Number of filter taps
117
+ * @param windowType Window function
118
+ */
119
+ static FirFilter<T> createBandStop(T lowCutoff, T highCutoff, size_t numTaps, const std::string &windowType = "hamming");
120
+
121
+ private:
122
+ std::vector<T> m_coefficients; // Filter coefficients (b[0], b[1], ..., b[M])
123
+ std::vector<T> m_state; // Sample history (x[n-1], x[n-2], ..., x[n-M])
124
+ size_t m_stateIndex; // Current position in circular state buffer
125
+ bool m_stateful; // Whether to maintain state between calls
126
+
127
+ /**
128
+ * Compute single output sample via convolution
129
+ * @param input Current input sample
130
+ * @param history Previous samples
131
+ * @param historySize Number of valid history samples
132
+ */
133
+ T convolve(T input, const T *history, size_t historySize);
134
+
135
+ /**
136
+ * Apply window function for filter design
137
+ */
138
+ static void applyWindow(std::vector<T> &impulse, const std::string &windowType);
139
+
140
+ /**
141
+ * Generate ideal sinc low-pass impulse response
142
+ */
143
+ static std::vector<T> generateSincLowPass(T cutoffFreq, size_t numTaps);
144
+ };
145
+
146
+ } // namespace core
147
+ } // namespace dsp
148
+
149
+ #endif // DSP_CORE_FIR_FILTER_H