dspx 1.2.4 → 1.3.1

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 (74) hide show
  1. package/README.md +40 -78
  2. package/binding.gyp +10 -0
  3. package/dist/FilterBankDesign.d.ts +233 -0
  4. package/dist/FilterBankDesign.d.ts.map +1 -0
  5. package/dist/FilterBankDesign.js +247 -0
  6. package/dist/FilterBankDesign.js.map +1 -0
  7. package/dist/advanced-dsp.d.ts +6 -6
  8. package/dist/advanced-dsp.d.ts.map +1 -1
  9. package/dist/advanced-dsp.js +35 -12
  10. package/dist/advanced-dsp.js.map +1 -1
  11. package/dist/backends.d.ts +0 -103
  12. package/dist/backends.d.ts.map +1 -1
  13. package/dist/backends.js +0 -217
  14. package/dist/backends.js.map +1 -1
  15. package/dist/bindings.d.ts +270 -17
  16. package/dist/bindings.d.ts.map +1 -1
  17. package/dist/bindings.js +566 -43
  18. package/dist/bindings.js.map +1 -1
  19. package/dist/index.d.ts +4 -2
  20. package/dist/index.d.ts.map +1 -1
  21. package/dist/index.js +2 -1
  22. package/dist/index.js.map +1 -1
  23. package/dist/types.d.ts +67 -8
  24. package/dist/types.d.ts.map +1 -1
  25. package/dist/utils.d.ts +38 -8
  26. package/dist/utils.d.ts.map +1 -1
  27. package/dist/utils.js +84 -26
  28. package/dist/utils.js.map +1 -1
  29. package/package.json +1 -2
  30. package/prebuilds/win32-x64/dspx.node +0 -0
  31. package/scripts/add-dispose-to-tests.js +145 -0
  32. package/src/native/DspPipeline.cc +699 -126
  33. package/src/native/DspPipeline.h +13 -0
  34. package/src/native/FilterBankDesignBindings.cc +241 -0
  35. package/src/native/IDspStage.h +24 -0
  36. package/src/native/UtilityBindings.cc +130 -0
  37. package/src/native/adapters/AmplifyStage.h +148 -0
  38. package/src/native/adapters/ClipDetectionStage.h +15 -4
  39. package/src/native/adapters/ConvolutionStage.h +101 -0
  40. package/src/native/adapters/CumulativeMovingAverageStage.h +264 -0
  41. package/src/native/adapters/DecimatorStage.h +80 -0
  42. package/src/native/adapters/DifferentiatorStage.h +13 -0
  43. package/src/native/adapters/ExponentialMovingAverageStage.h +290 -0
  44. package/src/native/adapters/FilterBankStage.cc +336 -0
  45. package/src/native/adapters/FilterBankStage.h +170 -0
  46. package/src/native/adapters/FilterStage.cc +122 -0
  47. package/src/native/adapters/FilterStage.h +4 -0
  48. package/src/native/adapters/HilbertEnvelopeStage.h +55 -0
  49. package/src/native/adapters/IntegratorStage.h +15 -0
  50. package/src/native/adapters/InterpolatorStage.h +51 -0
  51. package/src/native/adapters/LinearRegressionStage.h +40 -0
  52. package/src/native/adapters/LmsStage.h +63 -0
  53. package/src/native/adapters/MeanAbsoluteValueStage.h +76 -0
  54. package/src/native/adapters/MovingAverageStage.h +119 -0
  55. package/src/native/adapters/PeakDetectionStage.h +53 -0
  56. package/src/native/adapters/RectifyStage.h +14 -0
  57. package/src/native/adapters/ResamplerStage.h +67 -0
  58. package/src/native/adapters/RlsStage.h +76 -0
  59. package/src/native/adapters/RmsStage.h +72 -0
  60. package/src/native/adapters/SnrStage.h +45 -0
  61. package/src/native/adapters/SquareStage.h +78 -0
  62. package/src/native/adapters/SscStage.h +65 -0
  63. package/src/native/adapters/StftStage.h +62 -0
  64. package/src/native/adapters/VarianceStage.h +59 -0
  65. package/src/native/adapters/WampStage.h +59 -0
  66. package/src/native/adapters/WaveformLengthStage.h +51 -0
  67. package/src/native/adapters/ZScoreNormalizeStage.h +64 -0
  68. package/src/native/core/CumulativeMovingAverageFilter.h +123 -0
  69. package/src/native/core/ExponentialMovingAverageFilter.h +129 -0
  70. package/src/native/core/FilterBankDesign.h +266 -0
  71. package/src/native/core/Policies.h +124 -0
  72. package/src/native/utils/CircularBufferArray.cc +2 -1
  73. package/src/native/utils/SimdOps.h +67 -0
  74. package/src/native/utils/Toon.h +195 -0
@@ -3,6 +3,7 @@
3
3
  #include "../IDspStage.h"
4
4
  #include "../core/SscFilter.h"
5
5
  #include "../utils/NapiUtils.h"
6
+ #include "../utils/Toon.h"
6
7
  #include <vector>
7
8
  #include <string>
8
9
  #include <stdexcept>
@@ -112,6 +113,70 @@ namespace dsp::adapters
112
113
  }
113
114
  }
114
115
 
116
+ void serializeToon(dsp::toon::Serializer &s) const override
117
+ {
118
+ s.writeInt32(static_cast<int32_t>(m_window_size));
119
+ s.writeFloat(m_threshold);
120
+ s.writeInt32(static_cast<int32_t>(m_filters.size()));
121
+
122
+ for (const auto &filter : m_filters)
123
+ {
124
+ auto [internalState, filterState] = filter.getState();
125
+ auto [bufferData, runningCount] = internalState;
126
+
127
+ // Serialize bool vector as byte array
128
+ s.writeInt32(static_cast<int32_t>(bufferData.size()));
129
+ for (bool b : bufferData)
130
+ {
131
+ s.writeBool(b);
132
+ }
133
+ s.writeInt32(static_cast<int32_t>(runningCount));
134
+ s.writeFloat(filterState.sample_minus_1);
135
+ s.writeFloat(filterState.sample_minus_2);
136
+ s.writeInt32(filterState.init_count);
137
+ }
138
+ }
139
+
140
+ void deserializeToon(dsp::toon::Deserializer &d) override
141
+ {
142
+ size_t windowSize = static_cast<size_t>(d.readInt32());
143
+ float threshold = d.readFloat();
144
+ if (windowSize != m_window_size || threshold != m_threshold)
145
+ {
146
+ throw std::runtime_error("SSC TOON: parameter mismatch");
147
+ }
148
+
149
+ int32_t numChannels = d.readInt32();
150
+ m_filters.clear();
151
+ for (int32_t i = 0; i < numChannels; ++i)
152
+ {
153
+ m_filters.emplace_back(m_window_size, m_threshold);
154
+ }
155
+
156
+ for (auto &filter : m_filters)
157
+ {
158
+ int32_t bufSize = d.readInt32();
159
+ std::vector<bool> bufferData(bufSize);
160
+ for (int32_t j = 0; j < bufSize; ++j)
161
+ {
162
+ bufferData[j] = d.readBool();
163
+ }
164
+ size_t runningCount = static_cast<size_t>(d.readInt32());
165
+
166
+ dsp::core::SscFilter<float>::SscFilterState filterState;
167
+ filterState.sample_minus_1 = d.readFloat();
168
+ filterState.sample_minus_2 = d.readFloat();
169
+ filterState.init_count = d.readInt32();
170
+
171
+ if (!dsp::core::CounterPolicy::validateState(runningCount, bufferData))
172
+ {
173
+ throw std::runtime_error("SSC TOON: validation failed");
174
+ }
175
+
176
+ filter.setState(bufferData, runningCount, filterState);
177
+ }
178
+ }
179
+
115
180
  private:
116
181
  size_t m_window_size;
117
182
  float m_threshold;
@@ -38,6 +38,7 @@
38
38
  #include "../core/FftEngine.h"
39
39
  #include "../utils/CircularBufferArray.h"
40
40
  #include "../utils/SimdOps.h"
41
+ #include "../utils/Toon.h"
41
42
  #include <vector>
42
43
  #include <complex>
43
44
  #include <memory>
@@ -276,6 +277,67 @@ namespace dsp::adapters
276
277
  std::fill(m_samples_since_output.begin(), m_samples_since_output.end(), 0);
277
278
  }
278
279
 
280
+ void serializeToon(dsp::toon::Serializer &s) const override
281
+ {
282
+ s.writeInt32(static_cast<int32_t>(m_window_size));
283
+ s.writeInt32(static_cast<int32_t>(m_hop_size));
284
+ s.writeString(m_method);
285
+ s.writeString(m_type);
286
+ s.writeBool(m_forward);
287
+ s.writeString(m_output);
288
+ s.writeString(m_window_type);
289
+ s.writeInt32(static_cast<int32_t>(m_channel_buffers.size()));
290
+
291
+ // Serialize each channel's buffer and counter
292
+ for (size_t i = 0; i < m_channel_buffers.size(); ++i)
293
+ {
294
+ std::vector<float> buffer_data = m_channel_buffers[i].toVector();
295
+ s.writeFloatArray(buffer_data);
296
+ s.writeInt32(static_cast<int32_t>(m_samples_since_output[i]));
297
+ }
298
+ }
299
+
300
+ void deserializeToon(dsp::toon::Deserializer &d) override
301
+ {
302
+ size_t windowSize = static_cast<size_t>(d.readInt32());
303
+ size_t hopSize = static_cast<size_t>(d.readInt32());
304
+ std::string method = d.readString();
305
+ std::string type = d.readString();
306
+ bool forward = d.readBool();
307
+ std::string output = d.readString();
308
+ std::string windowType = d.readString();
309
+
310
+ if (windowSize != m_window_size || hopSize != m_hop_size)
311
+ {
312
+ throw std::runtime_error("STFT TOON: window/hop size mismatch");
313
+ }
314
+
315
+ uint32_t numChannels = d.readInt32();
316
+
317
+ // Recreate channel buffers
318
+ m_channel_buffers.clear();
319
+ m_samples_since_output.clear();
320
+
321
+ for (uint32_t i = 0; i < numChannels; ++i)
322
+ {
323
+ m_channel_buffers.emplace_back(m_window_size);
324
+ m_samples_since_output.push_back(0);
325
+ }
326
+
327
+ // Restore each channel's state
328
+ for (uint32_t i = 0; i < numChannels; ++i)
329
+ {
330
+ std::vector<float> buffer_data = d.readFloatArray();
331
+
332
+ for (float value : buffer_data)
333
+ {
334
+ m_channel_buffers[i].push(value);
335
+ }
336
+
337
+ m_samples_since_output[i] = static_cast<size_t>(d.readInt32());
338
+ }
339
+ }
340
+
279
341
  private:
280
342
  /**
281
343
  * Generate window function coefficients
@@ -2,6 +2,7 @@
2
2
 
3
3
  #include "../IDspStage.h"
4
4
  #include "../core/MovingVarianceFilter.h"
5
+ #include "../utils/Toon.h"
5
6
  #include <vector>
6
7
  #include <string>
7
8
  #include <stdexcept>
@@ -190,6 +191,64 @@ namespace dsp::adapters
190
191
  }
191
192
  }
192
193
 
194
+ void serializeToon(dsp::toon::Serializer &s) const override
195
+ {
196
+ s.writeString(m_mode == VarianceMode::Moving ? "moving" : "batch");
197
+
198
+ if (m_mode == VarianceMode::Moving)
199
+ {
200
+ s.writeInt32(static_cast<int32_t>(m_window_size));
201
+ s.writeDouble(m_window_duration_ms);
202
+ s.writeBool(m_is_initialized);
203
+
204
+ s.writeInt32(static_cast<int32_t>(m_filters.size()));
205
+ for (const auto &filter : m_filters)
206
+ {
207
+ auto [bufferData, sums] = filter.getState();
208
+ auto [runningSum, runningSumOfSquares] = sums;
209
+ s.writeFloatArray(bufferData);
210
+ s.writeFloat(runningSum);
211
+ s.writeFloat(runningSumOfSquares);
212
+ }
213
+ }
214
+ }
215
+
216
+ void deserializeToon(dsp::toon::Deserializer &d) override
217
+ {
218
+ std::string modeStr = d.readString();
219
+ VarianceMode newMode = (modeStr == "moving") ? VarianceMode::Moving : VarianceMode::Batch;
220
+
221
+ if (newMode != m_mode)
222
+ {
223
+ throw std::runtime_error("Variance TOON load: mode mismatch");
224
+ }
225
+
226
+ if (m_mode == VarianceMode::Moving)
227
+ {
228
+ int32_t windowSize = d.readInt32();
229
+ if (windowSize != static_cast<int32_t>(m_window_size))
230
+ {
231
+ throw std::runtime_error("Variance TOON load: window size mismatch");
232
+ }
233
+
234
+ d.readDouble(); // window_duration_ms
235
+ d.readBool(); // is_initialized
236
+
237
+ int32_t numChannels = d.readInt32();
238
+ m_filters.clear();
239
+ for (int32_t i = 0; i < numChannels; ++i)
240
+ {
241
+ m_filters.emplace_back(m_window_size);
242
+
243
+ std::vector<float> bufferData = d.readFloatArray();
244
+ float runningSum = d.readFloat();
245
+ float runningSumOfSquares = d.readFloat();
246
+
247
+ m_filters[i].setState(bufferData, runningSum, runningSumOfSquares);
248
+ }
249
+ }
250
+ }
251
+
193
252
  private:
194
253
  /**
195
254
  * @brief Statelessly calculates the variance for each channel
@@ -3,6 +3,7 @@
3
3
  #include "../IDspStage.h"
4
4
  #include "../core/WampFilter.h"
5
5
  #include "../utils/NapiUtils.h"
6
+ #include "../utils/Toon.h"
6
7
  #include <vector>
7
8
  #include <string>
8
9
  #include <stdexcept>
@@ -105,6 +106,64 @@ namespace dsp::adapters
105
106
  }
106
107
  }
107
108
 
109
+ void serializeToon(dsp::toon::Serializer &s) const override
110
+ {
111
+ s.writeInt32(static_cast<int32_t>(m_window_size));
112
+ s.writeFloat(m_threshold);
113
+ s.writeInt32(static_cast<int32_t>(m_filters.size()));
114
+
115
+ for (const auto &filter : m_filters)
116
+ {
117
+ auto [internalState, prevSample] = filter.getState();
118
+ auto [bufferData, runningCount] = internalState;
119
+
120
+ // Serialize bool vector as byte array
121
+ s.writeInt32(static_cast<int32_t>(bufferData.size()));
122
+ for (bool b : bufferData)
123
+ {
124
+ s.writeBool(b);
125
+ }
126
+ s.writeInt32(static_cast<int32_t>(runningCount));
127
+ s.writeFloat(prevSample);
128
+ }
129
+ }
130
+
131
+ void deserializeToon(dsp::toon::Deserializer &d) override
132
+ {
133
+ size_t windowSize = static_cast<size_t>(d.readInt32());
134
+ float threshold = d.readFloat();
135
+ if (windowSize != m_window_size || threshold != m_threshold)
136
+ {
137
+ throw std::runtime_error("WAMP TOON: parameter mismatch");
138
+ }
139
+
140
+ int32_t numChannels = d.readInt32();
141
+ m_filters.clear();
142
+ for (int32_t i = 0; i < numChannels; ++i)
143
+ {
144
+ m_filters.emplace_back(m_window_size, m_threshold);
145
+ }
146
+
147
+ for (auto &filter : m_filters)
148
+ {
149
+ int32_t bufSize = d.readInt32();
150
+ std::vector<bool> bufferData(bufSize);
151
+ for (int32_t j = 0; j < bufSize; ++j)
152
+ {
153
+ bufferData[j] = d.readBool();
154
+ }
155
+ size_t runningCount = static_cast<size_t>(d.readInt32());
156
+ float prevSample = d.readFloat();
157
+
158
+ if (!dsp::core::CounterPolicy::validateState(runningCount, bufferData))
159
+ {
160
+ throw std::runtime_error("WAMP TOON: validation failed");
161
+ }
162
+
163
+ filter.setState(bufferData, runningCount, prevSample);
164
+ }
165
+ }
166
+
108
167
  private:
109
168
  size_t m_window_size;
110
169
  float m_threshold;
@@ -3,6 +3,7 @@
3
3
  #include "../IDspStage.h"
4
4
  #include "../core/WaveformLengthFilter.h"
5
5
  #include "../utils/NapiUtils.h"
6
+ #include "../utils/Toon.h"
6
7
  #include <vector>
7
8
  #include <string>
8
9
  #include <stdexcept>
@@ -107,6 +108,56 @@ namespace dsp::adapters
107
108
  }
108
109
  }
109
110
 
111
+ void serializeToon(dsp::toon::Serializer &s) const override
112
+ {
113
+ s.writeInt32(static_cast<int32_t>(m_window_size));
114
+ s.writeInt32(static_cast<int32_t>(m_filters.size()));
115
+
116
+ for (const auto &filter : m_filters)
117
+ {
118
+ auto state = filter.getState();
119
+ const auto &internalState = state.first;
120
+ float prevSample = state.second;
121
+
122
+ const std::vector<float> &bufferData = internalState.first;
123
+ double runningSum = internalState.second;
124
+
125
+ s.writeFloatArray(bufferData);
126
+ s.writeDouble(runningSum);
127
+ s.writeFloat(prevSample);
128
+ }
129
+ }
130
+
131
+ void deserializeToon(dsp::toon::Deserializer &d) override
132
+ {
133
+ size_t windowSize = static_cast<size_t>(d.readInt32());
134
+ if (windowSize != m_window_size)
135
+ {
136
+ throw std::runtime_error("WaveformLength TOON: window size mismatch");
137
+ }
138
+
139
+ int32_t numChannels = d.readInt32();
140
+ m_filters.clear();
141
+ for (int32_t i = 0; i < numChannels; ++i)
142
+ {
143
+ m_filters.emplace_back(m_window_size);
144
+ }
145
+
146
+ for (auto &filter : m_filters)
147
+ {
148
+ std::vector<float> bufferData = d.readFloatArray();
149
+ double runningSum = d.readDouble();
150
+ float prevSample = d.readFloat();
151
+
152
+ if (!dsp::core::SumPolicy<float>::validateState(runningSum, bufferData))
153
+ {
154
+ throw std::runtime_error("WaveformLength TOON: validation failed");
155
+ }
156
+
157
+ filter.setState(bufferData, runningSum, prevSample);
158
+ }
159
+ }
160
+
110
161
  private:
111
162
  size_t m_window_size;
112
163
  std::vector<dsp::core::WaveformLengthFilter<float>> m_filters;
@@ -2,6 +2,7 @@
2
2
 
3
3
  #include "../IDspStage.h"
4
4
  #include "../core/MovingZScoreFilter.h"
5
+ #include "../utils/Toon.h"
5
6
  #include <vector>
6
7
  #include <string>
7
8
  #include <stdexcept>
@@ -195,6 +196,69 @@ namespace dsp::adapters
195
196
  }
196
197
  }
197
198
 
199
+ void serializeToon(dsp::toon::Serializer &s) const override
200
+ {
201
+ s.writeString(m_mode == ZScoreNormalizeMode::Moving ? "moving" : "batch");
202
+
203
+ if (m_mode == ZScoreNormalizeMode::Moving)
204
+ {
205
+ s.writeInt32(static_cast<int32_t>(m_window_size));
206
+ s.writeDouble(m_window_duration_ms);
207
+ s.writeBool(m_is_initialized);
208
+ s.writeFloat(m_epsilon);
209
+
210
+ s.writeInt32(static_cast<int32_t>(m_filters.size()));
211
+ for (const auto &filter : m_filters)
212
+ {
213
+ auto [bufferData, sums] = filter.getState();
214
+ auto [runningSum, runningSumOfSquares] = sums;
215
+ s.writeFloatArray(bufferData);
216
+ s.writeFloat(runningSum);
217
+ s.writeFloat(runningSumOfSquares);
218
+ s.writeFloat(m_epsilon); // Store epsilon explicitly
219
+ }
220
+ }
221
+ }
222
+
223
+ void deserializeToon(dsp::toon::Deserializer &d) override
224
+ {
225
+ std::string modeStr = d.readString();
226
+ ZScoreNormalizeMode newMode = (modeStr == "moving") ? ZScoreNormalizeMode::Moving : ZScoreNormalizeMode::Batch;
227
+
228
+ if (newMode != m_mode)
229
+ {
230
+ throw std::runtime_error("ZScore TOON load: mode mismatch");
231
+ }
232
+
233
+ if (m_mode == ZScoreNormalizeMode::Moving)
234
+ {
235
+ int32_t windowSize = d.readInt32();
236
+ if (windowSize != static_cast<int32_t>(m_window_size))
237
+ {
238
+ throw std::runtime_error("ZScore TOON load: window size mismatch");
239
+ }
240
+
241
+ m_window_duration_ms = d.readDouble(); // restore duration
242
+ m_is_initialized = d.readBool(); // restore init flag
243
+ m_epsilon = d.readFloat(); // restore epsilon
244
+
245
+ int32_t numChannels = d.readInt32();
246
+ m_filters.clear();
247
+ for (int32_t i = 0; i < numChannels; ++i)
248
+ {
249
+ m_filters.emplace_back(m_window_size, m_epsilon);
250
+
251
+ std::vector<float> bufferData = d.readFloatArray();
252
+ float runningSum = d.readFloat();
253
+ float runningSumOfSquares = d.readFloat();
254
+ d.readFloat(); // epsilon (already restored)
255
+
256
+ // setState takes (buffer, sum, sumOfSquares) - 3 separate params
257
+ m_filters[i].setState(bufferData, runningSum, runningSumOfSquares);
258
+ }
259
+ }
260
+ }
261
+
198
262
  private:
199
263
  /**
200
264
  * @brief Statelessly calculates the Z-Score for each sample
@@ -0,0 +1,123 @@
1
+ #pragma once
2
+ #include "Policies.h"
3
+ #include <vector>
4
+ #include <stdexcept>
5
+
6
+ namespace dsp::core
7
+ {
8
+ /**
9
+ * @brief Implements a Cumulative Moving Average (CMA) filter.
10
+ *
11
+ * CMA is the average of all samples seen since initialization.
12
+ * Formula: CMA(n) = (CMA(n-1) * (n-1) + value(n)) / n
13
+ * or: CMA(n) = sum(values[1..n]) / n
14
+ *
15
+ * Unlike simple moving average (SMA), CMA considers ALL historical data:
16
+ * - SMA: Uses fixed window of recent N samples
17
+ * - CMA: Uses all samples from start to current
18
+ *
19
+ * Properties:
20
+ * - Memory-efficient: Only stores running sum and count
21
+ * - Stable convergence: Influence of new samples decreases over time
22
+ * - No window size needed: Adapts to any data length
23
+ *
24
+ * Use cases:
25
+ * - Long-term averages where all history matters
26
+ * - Baseline estimation from calibration data
27
+ * - Online mean estimation for statistics
28
+ *
29
+ * @tparam T The numeric type of the samples (e.g., float, double).
30
+ */
31
+ template <typename T>
32
+ class CumulativeMovingAverageFilter
33
+ {
34
+ public:
35
+ /**
36
+ * @brief Constructs a new CMA Filter.
37
+ */
38
+ CumulativeMovingAverageFilter()
39
+ : m_policy()
40
+ {
41
+ }
42
+
43
+ // Delete copy constructor and copy assignment
44
+ CumulativeMovingAverageFilter(const CumulativeMovingAverageFilter &) = delete;
45
+ CumulativeMovingAverageFilter &operator=(const CumulativeMovingAverageFilter &) = delete;
46
+
47
+ // Enable move semantics
48
+ CumulativeMovingAverageFilter(CumulativeMovingAverageFilter &&) noexcept = default;
49
+ CumulativeMovingAverageFilter &operator=(CumulativeMovingAverageFilter &&) noexcept = default;
50
+
51
+ /**
52
+ * @brief Adds a new sample and updates the CMA.
53
+ * @param newValue The new sample value.
54
+ * @return T The updated CMA value.
55
+ */
56
+ T addSample(T newValue)
57
+ {
58
+ m_policy.onAdd(newValue);
59
+ return m_policy.getResult(0); // Window count parameter unused for CMA
60
+ }
61
+
62
+ /**
63
+ * @brief Process array of samples in batch (optimized for throughput).
64
+ *
65
+ * @param input Input array of samples
66
+ * @param output Output array (same size as input)
67
+ * @param length Number of samples to process
68
+ */
69
+ void processArray(const T *input, T *output, size_t length)
70
+ {
71
+ for (size_t i = 0; i < length; ++i)
72
+ {
73
+ output[i] = addSample(input[i]);
74
+ }
75
+ }
76
+
77
+ /**
78
+ * @brief Gets the current CMA value.
79
+ * @return T The cumulative moving average.
80
+ */
81
+ T getCma() const { return m_policy.getResult(0); }
82
+
83
+ /**
84
+ * @brief Gets the number of samples processed.
85
+ * @return size_t The total count of samples.
86
+ */
87
+ size_t getCount() const { return m_policy.getCount(); }
88
+
89
+ /**
90
+ * @brief Clears the CMA state (resets to zero).
91
+ */
92
+ void clear() { m_policy.clear(); }
93
+
94
+ /**
95
+ * @brief Checks if any samples have been processed.
96
+ * @return true if count > 0, false otherwise.
97
+ */
98
+ bool hasData() const { return m_policy.getCount() > 0; }
99
+
100
+ /**
101
+ * @brief Exports the filter's internal state.
102
+ * @return A pair containing the running sum and sample count.
103
+ */
104
+ std::pair<T, size_t> getState() const
105
+ {
106
+ return m_policy.getState();
107
+ }
108
+
109
+ /**
110
+ * @brief Restores the filter's internal state.
111
+ * @param sum The running sum to restore.
112
+ * @param count The sample count to restore.
113
+ */
114
+ void setState(T sum, size_t count)
115
+ {
116
+ m_policy.setState(sum, count);
117
+ }
118
+
119
+ private:
120
+ CmaPolicy<T> m_policy;
121
+ };
122
+
123
+ } // namespace dsp::core
@@ -0,0 +1,129 @@
1
+ #pragma once
2
+ #include "Policies.h"
3
+ #include <vector>
4
+ #include <stdexcept>
5
+
6
+ namespace dsp::core
7
+ {
8
+ /**
9
+ * @brief Implements an Exponential Moving Average (EMA) filter.
10
+ *
11
+ * EMA gives more weight to recent samples and exponentially decaying weight to older samples.
12
+ * Formula: EMA(t) = α * value(t) + (1 - α) * EMA(t-1)
13
+ *
14
+ * where α (alpha) is the smoothing factor:
15
+ * - α close to 1: Fast response to changes (less smoothing)
16
+ * - α close to 0: Slow response to changes (more smoothing)
17
+ *
18
+ * Common conversions:
19
+ * - From N-period SMA: α = 2 / (N + 1)
20
+ * - From time constant: α = 1 - exp(-Δt / τ)
21
+ *
22
+ * Unlike simple moving average, EMA does not require a fixed window size,
23
+ * making it memory-efficient for very long averaging periods.
24
+ *
25
+ * @tparam T The numeric type of the samples (e.g., float, double).
26
+ */
27
+ template <typename T>
28
+ class ExponentialMovingAverageFilter
29
+ {
30
+ public:
31
+ /**
32
+ * @brief Constructs a new EMA Filter with specified alpha.
33
+ * @param alpha The smoothing factor (0 < α ≤ 1).
34
+ * @throws std::invalid_argument if alpha is outside valid range.
35
+ */
36
+ explicit ExponentialMovingAverageFilter(T alpha)
37
+ : m_policy(alpha)
38
+ {
39
+ if (alpha <= 0 || alpha > 1)
40
+ {
41
+ throw std::invalid_argument("EMA alpha must be in range (0, 1]");
42
+ }
43
+ }
44
+
45
+ // Delete copy constructor and copy assignment
46
+ ExponentialMovingAverageFilter(const ExponentialMovingAverageFilter &) = delete;
47
+ ExponentialMovingAverageFilter &operator=(const ExponentialMovingAverageFilter &) = delete;
48
+
49
+ // Enable move semantics
50
+ ExponentialMovingAverageFilter(ExponentialMovingAverageFilter &&) noexcept = default;
51
+ ExponentialMovingAverageFilter &operator=(ExponentialMovingAverageFilter &&) noexcept = default;
52
+
53
+ /**
54
+ * @brief Adds a new sample and updates the EMA.
55
+ * @param newValue The new sample value.
56
+ * @return T The updated EMA value.
57
+ */
58
+ T addSample(T newValue)
59
+ {
60
+ m_policy.onAdd(newValue);
61
+ return m_policy.getResult(0); // Count parameter unused for EMA
62
+ }
63
+
64
+ /**
65
+ * @brief Process array of samples in batch (optimized for throughput).
66
+ *
67
+ * @param input Input array of samples
68
+ * @param output Output array (same size as input)
69
+ * @param length Number of samples to process
70
+ */
71
+ void processArray(const T *input, T *output, size_t length)
72
+ {
73
+ for (size_t i = 0; i < length; ++i)
74
+ {
75
+ output[i] = addSample(input[i]);
76
+ }
77
+ }
78
+
79
+ /**
80
+ * @brief Gets the current EMA value.
81
+ * @return T The current exponential moving average.
82
+ */
83
+ T getEma() const { return m_policy.getResult(0); }
84
+
85
+ /**
86
+ * @brief Gets the alpha (smoothing factor).
87
+ * @return T The alpha value.
88
+ */
89
+ T getAlpha() const { return m_policy.getAlpha(); }
90
+
91
+ /**
92
+ * @brief Clears the EMA state.
93
+ */
94
+ void clear() { m_policy.clear(); }
95
+
96
+ /**
97
+ * @brief Checks if the filter has been initialized with at least one sample.
98
+ * @return true if initialized, false otherwise.
99
+ */
100
+ bool isInitialized() const
101
+ {
102
+ auto [ema, initialized] = m_policy.getState();
103
+ return initialized;
104
+ }
105
+
106
+ /**
107
+ * @brief Exports the filter's internal state.
108
+ * @return A pair containing the current EMA value and initialization flag.
109
+ */
110
+ std::pair<T, bool> getState() const
111
+ {
112
+ return m_policy.getState();
113
+ }
114
+
115
+ /**
116
+ * @brief Restores the filter's internal state.
117
+ * @param ema The EMA value to restore.
118
+ * @param initialized The initialization flag to restore.
119
+ */
120
+ void setState(T ema, bool initialized)
121
+ {
122
+ m_policy.setState(ema, initialized);
123
+ }
124
+
125
+ private:
126
+ EmaPolicy<T> m_policy;
127
+ };
128
+
129
+ } // namespace dsp::core