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,291 @@
1
+ /**
2
+ * Moving/Batched FFT Filter Implementation with SIMD Optimizations
3
+ */
4
+
5
+ #define _USE_MATH_DEFINES
6
+ #include "MovingFftFilter.h"
7
+ #include "../utils/SimdOps.h"
8
+ #include <cmath>
9
+ #include <stdexcept>
10
+
11
+ #ifndef M_PI
12
+ #define M_PI 3.14159265358979323846
13
+ #endif
14
+
15
+ namespace dsp
16
+ {
17
+ namespace core
18
+ {
19
+
20
+ template <typename T>
21
+ MovingFftFilter<T>::MovingFftFilter(
22
+ size_t fftSize,
23
+ size_t hopSize,
24
+ FftMode mode,
25
+ WindowType windowType,
26
+ bool realInput)
27
+ : m_fftSize(fftSize), m_hopSize(hopSize == 0 ? fftSize : hopSize), m_mode(mode), m_windowType(windowType), m_realInput(realInput), m_fftEngine(std::make_unique<FftEngine<T>>(fftSize)), m_buffer(fftSize * 2) // Buffer size = 2x FFT size for overlap
28
+ ,
29
+ m_sampleCounter(0)
30
+ {
31
+ if (fftSize == 0)
32
+ {
33
+ throw std::invalid_argument("FFT size must be > 0");
34
+ }
35
+
36
+ if (m_hopSize > fftSize)
37
+ {
38
+ throw std::invalid_argument("Hop size cannot exceed FFT size");
39
+ }
40
+
41
+ // Allocate working buffers
42
+ m_windowedSamples.resize(fftSize);
43
+
44
+ size_t spectrumSize = realInput ? m_fftEngine->getHalfSize() : fftSize;
45
+ m_spectrum.resize(spectrumSize);
46
+
47
+ // Initialize window
48
+ initWindow();
49
+ }
50
+
51
+ template <typename T>
52
+ bool MovingFftFilter<T>::addSample(T sample, Complex *spectrum)
53
+ {
54
+ m_buffer.push(sample);
55
+
56
+ bool computed = false;
57
+
58
+ if (m_mode == FftMode::Moving)
59
+ {
60
+ // Moving mode: compute on every sample when buffer is full
61
+ if (m_buffer.getCount() >= m_fftSize)
62
+ {
63
+ computeSpectrum(m_spectrum.data());
64
+
65
+ if (spectrum)
66
+ {
67
+ size_t specSize = getSpectrumSize();
68
+ std::copy(m_spectrum.begin(), m_spectrum.begin() + specSize, spectrum);
69
+ }
70
+
71
+ computed = true;
72
+ }
73
+ }
74
+ else
75
+ {
76
+ // Batched mode: compute every hop samples
77
+ ++m_sampleCounter;
78
+
79
+ if (m_buffer.getCount() >= m_fftSize && m_sampleCounter >= m_hopSize)
80
+ {
81
+ computeSpectrum(m_spectrum.data());
82
+
83
+ if (spectrum)
84
+ {
85
+ size_t specSize = getSpectrumSize();
86
+ std::copy(m_spectrum.begin(), m_spectrum.begin() + specSize, spectrum);
87
+ }
88
+
89
+ m_sampleCounter = 0;
90
+ computed = true;
91
+ }
92
+ }
93
+
94
+ return computed;
95
+ }
96
+
97
+ template <typename T>
98
+ size_t MovingFftFilter<T>::addSamples(
99
+ const T *samples,
100
+ size_t count,
101
+ std::function<void(const Complex *, size_t)> callback)
102
+ {
103
+ size_t numSpectra = 0;
104
+
105
+ for (size_t i = 0; i < count; ++i)
106
+ {
107
+ if (addSample(samples[i], m_spectrum.data()))
108
+ {
109
+ if (callback)
110
+ {
111
+ callback(m_spectrum.data(), getSpectrumSize());
112
+ }
113
+ ++numSpectra;
114
+ }
115
+ }
116
+
117
+ return numSpectra;
118
+ }
119
+
120
+ template <typename T>
121
+ void MovingFftFilter<T>::computeSpectrum(Complex *spectrum)
122
+ {
123
+ if (m_buffer.getCount() < m_fftSize)
124
+ {
125
+ throw std::runtime_error("Insufficient samples for FFT");
126
+ }
127
+
128
+ // Get latest samples from buffer
129
+ std::vector<T> allSamples = m_buffer.toVector();
130
+ std::vector<T> samples(m_fftSize);
131
+
132
+ // Extract the last fftSize samples
133
+ size_t startIdx = allSamples.size() - m_fftSize;
134
+ for (size_t i = 0; i < m_fftSize; ++i)
135
+ {
136
+ samples[i] = allSamples[startIdx + i];
137
+ }
138
+
139
+ // Apply window
140
+ applyWindow(samples.data(), m_windowedSamples.data());
141
+
142
+ // Compute FFT
143
+ if (m_realInput)
144
+ {
145
+ if (m_fftEngine->isPowerOfTwo())
146
+ {
147
+ m_fftEngine->rfft(m_windowedSamples.data(), spectrum);
148
+ }
149
+ else
150
+ {
151
+ m_fftEngine->rdft(m_windowedSamples.data(), spectrum);
152
+ }
153
+ }
154
+ else
155
+ {
156
+ // Pack real samples as complex
157
+ std::vector<Complex> complexInput(m_fftSize);
158
+ for (size_t i = 0; i < m_fftSize; ++i)
159
+ {
160
+ complexInput[i] = Complex(m_windowedSamples[i], 0);
161
+ }
162
+
163
+ if (m_fftEngine->isPowerOfTwo())
164
+ {
165
+ m_fftEngine->fft(complexInput.data(), spectrum);
166
+ }
167
+ else
168
+ {
169
+ m_fftEngine->dft(complexInput.data(), spectrum);
170
+ }
171
+ }
172
+ }
173
+
174
+ template <typename T>
175
+ void MovingFftFilter<T>::reset()
176
+ {
177
+ m_buffer.clear();
178
+ m_sampleCounter = 0;
179
+ }
180
+
181
+ template <typename T>
182
+ void MovingFftFilter<T>::setWindowType(WindowType type)
183
+ {
184
+ m_windowType = type;
185
+ initWindow();
186
+ }
187
+
188
+ template <typename T>
189
+ void MovingFftFilter<T>::getMagnitudeSpectrum(T *magnitudes)
190
+ {
191
+ // MEDIUM WIN: Use SIMD-optimized magnitude calculation from FftEngine
192
+ m_fftEngine->getMagnitude(m_spectrum.data(), magnitudes, getSpectrumSize());
193
+ }
194
+
195
+ template <typename T>
196
+ void MovingFftFilter<T>::getPowerSpectrum(T *power)
197
+ {
198
+ // MEDIUM WIN: Use SIMD-optimized power calculation from FftEngine
199
+ m_fftEngine->getPower(m_spectrum.data(), power, getSpectrumSize());
200
+ }
201
+
202
+ template <typename T>
203
+ void MovingFftFilter<T>::getPhaseSpectrum(T *phases)
204
+ {
205
+ m_fftEngine->getPhase(m_spectrum.data(), phases, getSpectrumSize());
206
+ }
207
+
208
+ template <typename T>
209
+ void MovingFftFilter<T>::getFrequencyBins(T sampleRate, T *frequencies)
210
+ {
211
+ size_t specSize = getSpectrumSize();
212
+ T binWidth = sampleRate / static_cast<T>(m_fftSize);
213
+
214
+ for (size_t i = 0; i < specSize; ++i)
215
+ {
216
+ frequencies[i] = static_cast<T>(i) * binWidth;
217
+ }
218
+ }
219
+
220
+ // ========== Private Methods ==========
221
+
222
+ template <typename T>
223
+ void MovingFftFilter<T>::initWindow()
224
+ {
225
+ m_window.resize(m_fftSize);
226
+
227
+ for (size_t i = 0; i < m_fftSize; ++i)
228
+ {
229
+ m_window[i] = getWindowCoefficient(i, m_windowType);
230
+ }
231
+ }
232
+
233
+ template <typename T>
234
+ void MovingFftFilter<T>::applyWindow(const T *input, T *output)
235
+ {
236
+ // QUICK WIN: SIMD-optimized window application
237
+ if constexpr (std::is_same_v<T, float>)
238
+ {
239
+ dsp::simd::apply_window(input, m_window.data(), output, m_fftSize);
240
+ }
241
+ else
242
+ {
243
+ // Fallback for double precision
244
+ for (size_t i = 0; i < m_fftSize; ++i)
245
+ {
246
+ output[i] = input[i] * m_window[i];
247
+ }
248
+ }
249
+ }
250
+
251
+ template <typename T>
252
+ T MovingFftFilter<T>::getWindowCoefficient(size_t n, WindowType type)
253
+ {
254
+ if (type == WindowType::None)
255
+ {
256
+ return T(1);
257
+ }
258
+
259
+ const T pi = static_cast<T>(M_PI);
260
+ const T N = static_cast<T>(m_fftSize);
261
+ const T nf = static_cast<T>(n);
262
+
263
+ switch (type)
264
+ {
265
+ case WindowType::Hann:
266
+ // w[n] = 0.5 * (1 - cos(2πn/(N-1)))
267
+ return T(0.5) * (T(1) - std::cos(T(2) * pi * nf / (N - T(1))));
268
+
269
+ case WindowType::Hamming:
270
+ // w[n] = 0.54 - 0.46 * cos(2πn/(N-1))
271
+ return T(0.54) - T(0.46) * std::cos(T(2) * pi * nf / (N - T(1)));
272
+
273
+ case WindowType::Blackman:
274
+ // w[n] = 0.42 - 0.5*cos(2πn/(N-1)) + 0.08*cos(4πn/(N-1))
275
+ return T(0.42) - T(0.5) * std::cos(T(2) * pi * nf / (N - T(1))) + T(0.08) * std::cos(T(4) * pi * nf / (N - T(1)));
276
+
277
+ case WindowType::Bartlett:
278
+ // w[n] = 1 - |2n/(N-1) - 1|
279
+ return T(1) - std::abs(T(2) * nf / (N - T(1)) - T(1));
280
+
281
+ default:
282
+ return T(1);
283
+ }
284
+ }
285
+
286
+ // Explicit template instantiations
287
+ template class MovingFftFilter<float>;
288
+ template class MovingFftFilter<double>;
289
+
290
+ } // namespace core
291
+ } // namespace dsp
@@ -0,0 +1,203 @@
1
+ /**
2
+ * Moving/Batched FFT Filter
3
+ *
4
+ * Provides sliding-window and batched FFT processing:
5
+ * - Moving FFT: Updates spectrum as new samples arrive
6
+ * - Batched FFT: Processes complete frames with configurable overlap
7
+ * - Zero-padding support
8
+ * - Windowing functions (Hann, Hamming, Blackman)
9
+ *
10
+ * Uses CircularBufferArray for efficient sample buffering
11
+ */
12
+
13
+ #ifndef DSP_CORE_MOVING_FFT_FILTER_H
14
+ #define DSP_CORE_MOVING_FFT_FILTER_H
15
+
16
+ #include "FftEngine.h"
17
+ #include "../utils/CircularBufferArray.h"
18
+ #include <vector>
19
+ #include <functional>
20
+
21
+ namespace dsp
22
+ {
23
+ namespace core
24
+ {
25
+
26
+ /**
27
+ * Window function types
28
+ */
29
+ enum class WindowType
30
+ {
31
+ None, // Rectangular (no windowing)
32
+ Hann, // Hann window (cosine taper)
33
+ Hamming, // Hamming window
34
+ Blackman, // Blackman window (better sidelobe rejection)
35
+ Bartlett // Triangular window
36
+ };
37
+
38
+ /**
39
+ * FFT processing mode
40
+ */
41
+ enum class FftMode
42
+ {
43
+ Moving, // Sliding window, updates on every sample
44
+ Batched // Process complete frames
45
+ };
46
+
47
+ template <typename T = float>
48
+ class MovingFftFilter
49
+ {
50
+ public:
51
+ using Complex = std::complex<T>;
52
+
53
+ /**
54
+ * Constructor
55
+ *
56
+ * @param fftSize FFT size (must be power of 2)
57
+ * @param hopSize Hop size for batched mode (samples between frames)
58
+ * @param mode Processing mode (Moving or Batched)
59
+ * @param windowType Windowing function
60
+ * @param realInput True for real-input transforms (RFFT/RDFT)
61
+ */
62
+ MovingFftFilter(
63
+ size_t fftSize,
64
+ size_t hopSize = 0,
65
+ FftMode mode = FftMode::Batched,
66
+ WindowType windowType = WindowType::Hann,
67
+ bool realInput = true);
68
+
69
+ ~MovingFftFilter() = default;
70
+
71
+ /**
72
+ * Add sample and optionally compute FFT
73
+ *
74
+ * @param sample Input sample
75
+ * @param spectrum Output spectrum (nullptr if not ready)
76
+ * @return True if spectrum was computed
77
+ */
78
+ bool addSample(T sample, Complex *spectrum);
79
+
80
+ /**
81
+ * Add batch of samples
82
+ *
83
+ * @param samples Input samples
84
+ * @param count Number of samples
85
+ * @param callback Called for each computed spectrum
86
+ * @return Number of spectra computed
87
+ */
88
+ size_t addSamples(
89
+ const T *samples,
90
+ size_t count,
91
+ std::function<void(const Complex *, size_t)> callback);
92
+
93
+ /**
94
+ * Compute FFT from current buffer (force computation)
95
+ *
96
+ * @param spectrum Output spectrum
97
+ */
98
+ void computeSpectrum(Complex *spectrum);
99
+
100
+ /**
101
+ * Reset filter state
102
+ */
103
+ void reset();
104
+
105
+ /**
106
+ * Get FFT size
107
+ */
108
+ size_t getFftSize() const { return m_fftSize; }
109
+
110
+ /**
111
+ * Get spectrum size (N for complex, N/2+1 for real)
112
+ */
113
+ size_t getSpectrumSize() const
114
+ {
115
+ return m_realInput ? m_fftEngine->getHalfSize() : m_fftSize;
116
+ }
117
+
118
+ /**
119
+ * Get hop size
120
+ */
121
+ size_t getHopSize() const { return m_hopSize; }
122
+
123
+ /**
124
+ * Get current fill level
125
+ */
126
+ size_t getFillLevel() const { return m_buffer.getCount(); }
127
+
128
+ /**
129
+ * Check if ready to compute (buffer full)
130
+ */
131
+ bool isReady() const { return m_buffer.getCount() >= m_fftSize; }
132
+
133
+ /**
134
+ * Set window function
135
+ */
136
+ void setWindowType(WindowType type);
137
+
138
+ /**
139
+ * Get magnitude spectrum
140
+ */
141
+ void getMagnitudeSpectrum(T *magnitudes);
142
+
143
+ /**
144
+ * Get power spectrum
145
+ */
146
+ void getPowerSpectrum(T *power);
147
+
148
+ /**
149
+ * Get phase spectrum
150
+ */
151
+ void getPhaseSpectrum(T *phases);
152
+
153
+ /**
154
+ * Get frequency bins (Hz)
155
+ *
156
+ * @param sampleRate Sample rate in Hz
157
+ * @param frequencies Output array (size = spectrum size)
158
+ */
159
+ void getFrequencyBins(T sampleRate, T *frequencies);
160
+
161
+ private:
162
+ size_t m_fftSize;
163
+ size_t m_hopSize;
164
+ FftMode m_mode;
165
+ WindowType m_windowType;
166
+ bool m_realInput;
167
+
168
+ // FFT engine
169
+ std::unique_ptr<FftEngine<T>> m_fftEngine;
170
+
171
+ // Circular buffer for sample accumulation
172
+ utils::CircularBufferArray<T> m_buffer;
173
+
174
+ // Window coefficients
175
+ std::vector<T> m_window;
176
+
177
+ // Working buffers
178
+ std::vector<T> m_windowedSamples;
179
+ std::vector<Complex> m_spectrum;
180
+
181
+ // Sample counter for hop detection
182
+ size_t m_sampleCounter;
183
+
184
+ /**
185
+ * Initialize window function
186
+ */
187
+ void initWindow();
188
+
189
+ /**
190
+ * Apply window to samples
191
+ */
192
+ void applyWindow(const T *input, T *output);
193
+
194
+ /**
195
+ * Compute window function coefficient
196
+ */
197
+ T getWindowCoefficient(size_t n, WindowType type);
198
+ };
199
+
200
+ } // namespace core
201
+ } // namespace dsp
202
+
203
+ #endif // DSP_CORE_MOVING_FFT_FILTER_H
@@ -0,0 +1,194 @@
1
+ #include "MovingVarianceFilter.h"
2
+
3
+ using namespace dsp::core;
4
+
5
+ // -----------------------------------------------------------------------------
6
+ // Constructor
7
+ // -----------------------------------------------------------------------------
8
+ template <typename T>
9
+ MovingVarianceFilter<T>::MovingVarianceFilter(size_t window_size)
10
+ : buffer(window_size), // Initialize the circular buffer
11
+ running_sum(0),
12
+ running_sum_of_squares(0),
13
+ window_size(window_size)
14
+ {
15
+ if (window_size == 0)
16
+ {
17
+ throw std::invalid_argument("Window size must be greater than 0");
18
+ }
19
+ }
20
+
21
+ // -----------------------------------------------------------------------------
22
+ // Time-aware Constructor
23
+ // -----------------------------------------------------------------------------
24
+ template <typename T>
25
+ MovingVarianceFilter<T>::MovingVarianceFilter(size_t window_size, double window_duration_ms)
26
+ : buffer(window_size, window_duration_ms), // Initialize time-aware circular buffer
27
+ running_sum(0),
28
+ running_sum_of_squares(0),
29
+ window_size(window_size)
30
+ {
31
+ if (window_size == 0)
32
+ {
33
+ throw std::invalid_argument("Window size must be greater than 0");
34
+ }
35
+ if (window_duration_ms <= 0.0)
36
+ {
37
+ throw std::invalid_argument("Window duration must be greater than 0");
38
+ }
39
+ }
40
+
41
+ // -----------------------------------------------------------------------------
42
+ // Method: addSample
43
+ // -----------------------------------------------------------------------------
44
+ template <typename T>
45
+ T MovingVarianceFilter<T>::addSample(T newValue)
46
+ {
47
+ T oldestValue = 0;
48
+
49
+ // If the buffer is full, get the oldest value
50
+ if (buffer.isFull())
51
+ {
52
+ oldestValue = buffer.peek();
53
+ }
54
+
55
+ // Add the new value to the buffer
56
+ buffer.pushOverwrite(newValue);
57
+
58
+ // Update running sums
59
+ T oldestValueSquared = oldestValue * oldestValue;
60
+ T newValueSquared = newValue * newValue;
61
+
62
+ running_sum = running_sum - oldestValue + newValue;
63
+ running_sum_of_squares = running_sum_of_squares - oldestValueSquared + newValueSquared;
64
+
65
+ // Return the new variance
66
+ return getVariance();
67
+ }
68
+
69
+ // -----------------------------------------------------------------------------
70
+ // Method: addSampleWithTimestamp
71
+ // -----------------------------------------------------------------------------
72
+ template <typename T>
73
+ T MovingVarianceFilter<T>::addSampleWithTimestamp(T newValue, double timestamp)
74
+ {
75
+ // Expire old samples
76
+ size_t initial_count = buffer.getCount();
77
+ size_t expired_count = buffer.expireOld(timestamp);
78
+
79
+ // Rebuild running statistics if samples were expired
80
+ if (expired_count > 0)
81
+ {
82
+ running_sum = 0;
83
+ running_sum_of_squares = 0;
84
+
85
+ auto remaining = buffer.toVector();
86
+ for (const auto &value : remaining)
87
+ {
88
+ running_sum += value;
89
+ running_sum_of_squares += value * value;
90
+ }
91
+ }
92
+
93
+ // Add new sample
94
+ T oldestValue = 0;
95
+ if (buffer.isFull())
96
+ {
97
+ oldestValue = buffer.peek();
98
+ }
99
+
100
+ buffer.pushOverwriteWithTimestamp(newValue, timestamp);
101
+
102
+ // Update running sums
103
+ T oldestValueSquared = oldestValue * oldestValue;
104
+ T newValueSquared = newValue * newValue;
105
+
106
+ running_sum = running_sum - oldestValue + newValue;
107
+ running_sum_of_squares = running_sum_of_squares - oldestValueSquared + newValueSquared;
108
+
109
+ return getVariance();
110
+ }
111
+
112
+ // -----------------------------------------------------------------------------
113
+ // Method: getVariance
114
+ // -----------------------------------------------------------------------------
115
+ template <typename T>
116
+ T MovingVarianceFilter<T>::getVariance() const
117
+ {
118
+ size_t count = buffer.getCount();
119
+ if (count == 0)
120
+ {
121
+ return 0; // Avoid division by zero
122
+ }
123
+
124
+ T countT = static_cast<T>(count);
125
+
126
+ // Calculate mean
127
+ T mean = running_sum / countT;
128
+
129
+ // Calculate mean of squares
130
+ T mean_of_squares = running_sum_of_squares / countT;
131
+
132
+ // Variance = E[X^2] - (E[X])^2
133
+ // Use std::max to prevent negative values from floating point inaccuracies
134
+ T variance = std::max(static_cast<T>(0), mean_of_squares - (mean * mean));
135
+
136
+ return variance;
137
+ }
138
+
139
+ // -----------------------------------------------------------------------------
140
+ // Method: clear
141
+ // -----------------------------------------------------------------------------
142
+ template <typename T>
143
+ void MovingVarianceFilter<T>::clear()
144
+ {
145
+ buffer.clear();
146
+ running_sum = 0;
147
+ running_sum_of_squares = 0;
148
+ }
149
+
150
+ // -----------------------------------------------------------------------------
151
+ // Method: isFull
152
+ // -----------------------------------------------------------------------------
153
+ template <typename T>
154
+ bool MovingVarianceFilter<T>::isFull() const noexcept
155
+ {
156
+ return buffer.isFull();
157
+ }
158
+
159
+ // -----------------------------------------------------------------------------
160
+ // Method: isTimeAware
161
+ // -----------------------------------------------------------------------------
162
+ template <typename T>
163
+ bool MovingVarianceFilter<T>::isTimeAware() const noexcept
164
+ {
165
+ return buffer.isTimeAware();
166
+ }
167
+
168
+ // -----------------------------------------------------------------------------
169
+ // Method: getState
170
+ // -----------------------------------------------------------------------------
171
+ template <typename T>
172
+ std::pair<std::vector<T>, std::pair<T, T>> MovingVarianceFilter<T>::getState() const
173
+ {
174
+ return {buffer.toVector(), {running_sum, running_sum_of_squares}};
175
+ }
176
+
177
+ // -----------------------------------------------------------------------------
178
+ // Method: setState
179
+ // -----------------------------------------------------------------------------
180
+ template <typename T>
181
+ void MovingVarianceFilter<T>::setState(const std::vector<T> &bufferData, T sum, T sumOfSquares)
182
+ {
183
+ buffer.fromVector(bufferData);
184
+ running_sum = sum;
185
+ running_sum_of_squares = sumOfSquares;
186
+ }
187
+
188
+ // Explicit template instantiation for common types
189
+ namespace dsp::core
190
+ {
191
+ template class MovingVarianceFilter<int>;
192
+ template class MovingVarianceFilter<float>;
193
+ template class MovingVarianceFilter<double>;
194
+ }