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,299 @@
1
+ #pragma once
2
+
3
+ #include "../IDspStage.h"
4
+ #include "../core/RmsFilter.h"
5
+ #include "../utils/SimdOps.h"
6
+ #include <vector>
7
+ #include <stdexcept>
8
+ #include <cmath>
9
+ #include <string>
10
+ #include <algorithm>
11
+ #include <numeric> // For std::accumulate
12
+
13
+ namespace dsp::adapters
14
+ {
15
+ enum class RmsMode
16
+ {
17
+ Batch,
18
+ Moving
19
+ };
20
+
21
+ class RmsStage : public IDspStage
22
+ {
23
+ public:
24
+ /**
25
+ * @brief Constructs a new RMS Stage.
26
+ * @param mode The RMS mode (Batch or Moving).
27
+ * @param window_size The window size in samples (0 if using duration-based).
28
+ * @param window_duration_ms The window duration in milliseconds (0 if using size-based).
29
+ */
30
+ explicit RmsStage(RmsMode mode, size_t window_size = 0, double window_duration_ms = 0.0)
31
+ : m_mode(mode),
32
+ m_window_size(window_size),
33
+ m_window_duration_ms(window_duration_ms),
34
+ m_is_initialized(window_size > 0)
35
+ {
36
+ if (m_mode == RmsMode::Moving && window_size == 0 && window_duration_ms == 0.0)
37
+ {
38
+ throw std::invalid_argument("RMS: either window size or window duration must be greater than 0 for 'moving' mode");
39
+ }
40
+ }
41
+
42
+ // Return the type identifier for this stage
43
+ const char *getType() const override
44
+ {
45
+ return "rms";
46
+ }
47
+
48
+ // Implementation of the interface method
49
+ void process(float *buffer, size_t numSamples, int numChannels, const float *timestamps = nullptr) override
50
+ {
51
+ if (m_mode == RmsMode::Batch)
52
+ {
53
+ processBatch(buffer, numSamples, numChannels);
54
+ }
55
+ else // RmsMode::Moving
56
+ {
57
+ processMoving(buffer, numSamples, numChannels, timestamps);
58
+ }
59
+ }
60
+
61
+ // Serialize the stage's state
62
+ Napi::Object serializeState(Napi::Env env) const override
63
+ {
64
+ Napi::Object state = Napi::Object::New(env);
65
+ std::string modeStr = (m_mode == RmsMode::Moving) ? "moving" : "batch";
66
+ state.Set("mode", modeStr);
67
+
68
+ if (m_mode == RmsMode::Moving)
69
+ {
70
+ state.Set("windowSize", static_cast<uint32_t>(m_window_size));
71
+ state.Set("numChannels", static_cast<uint32_t>(m_filters.size()));
72
+
73
+ // Serialize each channel's filter state
74
+ Napi::Array channelsArray = Napi::Array::New(env, m_filters.size());
75
+ for (size_t i = 0; i < m_filters.size(); ++i)
76
+ {
77
+ Napi::Object channelState = Napi::Object::New(env);
78
+
79
+ // Get the filter's internal state
80
+ auto [bufferData, runningSumOfSquares] = m_filters[i].getState();
81
+
82
+ // Convert buffer data to JavaScript array
83
+ Napi::Array bufferArray = Napi::Array::New(env, bufferData.size());
84
+ for (size_t j = 0; j < bufferData.size(); ++j)
85
+ {
86
+ bufferArray.Set(j, Napi::Number::New(env, bufferData[j]));
87
+ }
88
+
89
+ channelState.Set("buffer", bufferArray);
90
+ // Store the running sum of squares
91
+ channelState.Set("runningSumOfSquares", Napi::Number::New(env, runningSumOfSquares));
92
+
93
+ channelsArray.Set(static_cast<uint32_t>(i), channelState);
94
+ }
95
+ state.Set("channels", channelsArray);
96
+ }
97
+
98
+ return state;
99
+ }
100
+
101
+ // Deserialize and restore the stage's state
102
+ void deserializeState(const Napi::Object &state) override
103
+ {
104
+ std::string modeStr = state.Get("mode").As<Napi::String>().Utf8Value();
105
+ RmsMode newMode = (modeStr == "moving") ? RmsMode::Moving : RmsMode::Batch;
106
+
107
+ if (newMode != m_mode)
108
+ {
109
+ throw std::runtime_error("RMS mode mismatch during deserialization");
110
+ }
111
+
112
+ if (m_mode == RmsMode::Moving)
113
+ {
114
+ // Get window size and validate
115
+ size_t windowSize = state.Get("windowSize").As<Napi::Number>().Uint32Value();
116
+ if (windowSize != m_window_size)
117
+ {
118
+ throw std::runtime_error("Window size mismatch during deserialization");
119
+ }
120
+
121
+ // Get number of channels
122
+ uint32_t numChannels = state.Get("channels").As<Napi::Array>().Length();
123
+
124
+ // Recreate filters
125
+ m_filters.clear();
126
+ for (uint32_t i = 0; i < numChannels; ++i)
127
+ {
128
+ m_filters.emplace_back(m_window_size);
129
+ }
130
+
131
+ // Restore each channel's state
132
+ Napi::Array channelsArray = state.Get("channels").As<Napi::Array>();
133
+ for (uint32_t i = 0; i < numChannels; ++i)
134
+ {
135
+ Napi::Object channelState = channelsArray.Get(i).As<Napi::Object>();
136
+
137
+ // Get buffer data
138
+ Napi::Array bufferArray = channelState.Get("buffer").As<Napi::Array>();
139
+ std::vector<float> bufferData;
140
+ bufferData.reserve(bufferArray.Length());
141
+ for (uint32_t j = 0; j < bufferArray.Length(); ++j)
142
+ {
143
+ bufferData.push_back(bufferArray.Get(j).As<Napi::Number>().FloatValue());
144
+ }
145
+
146
+ // Get running sum of squares
147
+ float runningSumOfSquares = channelState.Get("runningSumOfSquares").As<Napi::Number>().FloatValue();
148
+
149
+ // Validate runningSumOfSquares matches buffer contents
150
+ float actualSumOfSquares = 0.0f;
151
+ for (const auto &val : bufferData)
152
+ {
153
+ actualSumOfSquares += val * val;
154
+ }
155
+ const float tolerance = 0.0001f * std::max(1.0f, std::abs(actualSumOfSquares));
156
+ if (std::abs(runningSumOfSquares - actualSumOfSquares) > tolerance)
157
+ {
158
+ throw std::runtime_error(
159
+ "Running sum of squares validation failed: expected " +
160
+ std::to_string(actualSumOfSquares) + " but got " +
161
+ std::to_string(runningSumOfSquares));
162
+ }
163
+
164
+ // Restore the filter's state
165
+ m_filters[i].setState(bufferData, runningSumOfSquares);
166
+ }
167
+ }
168
+ }
169
+
170
+ // Reset all filters to initial state
171
+ void reset() override
172
+ {
173
+ for (auto &filter : m_filters)
174
+ {
175
+ filter.clear();
176
+ }
177
+ }
178
+
179
+ private:
180
+ /**
181
+ * @brief Statelessly calculates the RMS for each channel
182
+ * and overwrites all samples in that channel with the result.
183
+ * Uses SIMD-optimized sum of squares for better performance.
184
+ */
185
+ void processBatch(float *buffer, size_t numSamples, int numChannels)
186
+ {
187
+ for (int c = 0; c < numChannels; ++c)
188
+ {
189
+ size_t numSamplesPerChannel = numSamples / numChannels;
190
+ if (numSamplesPerChannel == 0)
191
+ continue;
192
+
193
+ double sum_sq = 0.0;
194
+
195
+ // For single-channel, use SIMD-optimized sum of squares
196
+ if (numChannels == 1)
197
+ {
198
+ sum_sq = dsp::simd::sum_of_squares(buffer, numSamples);
199
+ }
200
+ else
201
+ {
202
+ // Multi-channel: strided access
203
+ for (size_t i = c; i < numSamples; i += numChannels)
204
+ {
205
+ double val = static_cast<double>(buffer[i]);
206
+ sum_sq += val * val;
207
+ }
208
+ }
209
+
210
+ // Calculate mean of squares and RMS
211
+ double mean_sq = sum_sq / numSamplesPerChannel;
212
+ float rms = static_cast<float>(std::sqrt(std::max(0.0, mean_sq)));
213
+
214
+ // Fill this channel's buffer with the RMS value
215
+ for (size_t i = c; i < numSamples; i += numChannels)
216
+ {
217
+ buffer[i] = rms;
218
+ }
219
+ }
220
+ }
221
+
222
+ /**
223
+ * @brief Statefully processes samples using the moving RMS filters.
224
+ */
225
+ void processMoving(float *buffer, size_t numSamples, int numChannels, const float *timestamps)
226
+ {
227
+ // Determine if we're in time-aware mode
228
+ bool useTimeAware = (m_window_duration_ms > 0.0) && timestamps != nullptr;
229
+
230
+ // Lazy initialization: convert windowDuration to windowSize if needed
231
+ if (!m_is_initialized && m_window_duration_ms > 0.0)
232
+ {
233
+ if (timestamps != nullptr && numSamples > 1)
234
+ {
235
+ // Estimate sample rate from timestamps
236
+ size_t samples_to_check = std::min(numSamples, size_t(10));
237
+ double total_time_ms = timestamps[samples_to_check - 1] - timestamps[0];
238
+ double avg_sample_period_ms = total_time_ms / (samples_to_check - 1);
239
+ double estimated_sample_rate = 1000.0 / avg_sample_period_ms;
240
+
241
+ // Use 3x the estimated size for time-aware mode
242
+ size_t estimated_size = static_cast<size_t>((m_window_duration_ms / 1000.0) * estimated_sample_rate);
243
+ m_window_size = std::max(size_t(1), estimated_size * 3);
244
+
245
+ m_is_initialized = true;
246
+ }
247
+ else
248
+ {
249
+ throw std::runtime_error("RMS: windowDuration was set, but timestamps are not available to derive sample rate");
250
+ }
251
+ }
252
+
253
+ // Lazily initialize filters, one for each channel
254
+ if (m_filters.size() != static_cast<size_t>(numChannels))
255
+ {
256
+ m_filters.clear();
257
+ for (int i = 0; i < numChannels; ++i)
258
+ {
259
+ if (useTimeAware)
260
+ {
261
+ // Create time-aware filter
262
+ m_filters.emplace_back(m_window_size, m_window_duration_ms);
263
+ }
264
+ else
265
+ {
266
+ // Create regular filter
267
+ m_filters.emplace_back(m_window_size);
268
+ }
269
+ }
270
+ }
271
+
272
+ // Process the buffer sample by sample, de-interleaving
273
+ for (size_t i = 0; i < numSamples; ++i)
274
+ {
275
+ int channel = i % numChannels;
276
+ size_t sample_index = i / numChannels;
277
+
278
+ if (useTimeAware)
279
+ {
280
+ // Use time-aware processing
281
+ buffer[i] = m_filters[channel].addSampleWithTimestamp(buffer[i], timestamps[sample_index]);
282
+ }
283
+ else
284
+ {
285
+ // Use sample-count processing
286
+ buffer[i] = m_filters[channel].addSample(buffer[i]);
287
+ }
288
+ }
289
+ }
290
+
291
+ RmsMode m_mode;
292
+ size_t m_window_size;
293
+ double m_window_duration_ms;
294
+ bool m_is_initialized;
295
+ // A separate RMS filter instance for each channel
296
+ std::vector<dsp::core::RmsFilter<float>> m_filters;
297
+ };
298
+
299
+ } // namespace dsp::adapters
@@ -0,0 +1,121 @@
1
+ #pragma once
2
+
3
+ #include "../IDspStage.h"
4
+ #include "../core/SscFilter.h"
5
+ #include "../utils/NapiUtils.h"
6
+ #include <vector>
7
+ #include <string>
8
+ #include <stdexcept>
9
+
10
+ namespace dsp::adapters
11
+ {
12
+ class SscStage : public IDspStage
13
+ {
14
+ public:
15
+ explicit SscStage(size_t window_size, float threshold)
16
+ : m_window_size(window_size), m_threshold(threshold)
17
+ {
18
+ if (window_size == 0)
19
+ {
20
+ throw std::invalid_argument("SSC: window size must be greater than 0");
21
+ }
22
+ }
23
+
24
+ const char *getType() const override { return "slopeSignChange"; }
25
+
26
+ void process(float *buffer, size_t numSamples, int numChannels, const float *timestamps = nullptr) override
27
+ {
28
+ if (m_filters.size() != numChannels)
29
+ {
30
+ m_filters.clear();
31
+ for (int i = 0; i < numChannels; ++i)
32
+ {
33
+ m_filters.emplace_back(m_window_size, m_threshold);
34
+ }
35
+ }
36
+
37
+ for (size_t i = 0; i < numSamples; ++i)
38
+ {
39
+ int channel = i % numChannels;
40
+ buffer[i] = m_filters[channel].addSample(buffer[i]);
41
+ }
42
+ }
43
+
44
+ void reset() override
45
+ {
46
+ for (auto &filter : m_filters)
47
+ {
48
+ filter.clear();
49
+ }
50
+ }
51
+
52
+ Napi::Object serializeState(Napi::Env env) const override
53
+ {
54
+ Napi::Object state = Napi::Object::New(env);
55
+ state.Set("windowSize", static_cast<uint32_t>(m_window_size));
56
+ state.Set("threshold", Napi::Number::New(env, m_threshold));
57
+ state.Set("numChannels", static_cast<uint32_t>(m_filters.size()));
58
+
59
+ Napi::Array channelsArray = Napi::Array::New(env, m_filters.size());
60
+ for (size_t i = 0; i < m_filters.size(); ++i)
61
+ {
62
+ Napi::Object channelState = Napi::Object::New(env);
63
+ auto [internalState, filterState] = m_filters[i].getState();
64
+ auto [bufferData, runningCount] = internalState;
65
+
66
+ channelState.Set("buffer", dsp::utils::VectorToNapiArray(env, bufferData));
67
+ channelState.Set("runningCount", static_cast<uint32_t>(runningCount));
68
+ channelState.Set("sample_minus_1", Napi::Number::New(env, filterState.sample_minus_1));
69
+ channelState.Set("sample_minus_2", Napi::Number::New(env, filterState.sample_minus_2));
70
+ channelState.Set("init_count", Napi::Number::New(env, filterState.init_count));
71
+
72
+ channelsArray.Set(static_cast<uint32_t>(i), channelState);
73
+ }
74
+ state.Set("channels", channelsArray);
75
+ return state;
76
+ }
77
+
78
+ void deserializeState(const Napi::Object &state) override
79
+ {
80
+ size_t windowSize = state.Get("windowSize").As<Napi::Number>().Uint32Value();
81
+ float threshold = state.Get("threshold").As<Napi::Number>().FloatValue();
82
+ if (windowSize != m_window_size || threshold != m_threshold)
83
+ {
84
+ throw std::runtime_error("SSC parameter mismatch during deserialization");
85
+ }
86
+
87
+ uint32_t numChannels = state.Get("channels").As<Napi::Array>().Length();
88
+ m_filters.clear();
89
+ for (uint32_t i = 0; i < numChannels; ++i)
90
+ {
91
+ m_filters.emplace_back(m_window_size, m_threshold);
92
+ }
93
+
94
+ Napi::Array channelsArray = state.Get("channels").As<Napi::Array>();
95
+ for (uint32_t i = 0; i < numChannels; ++i)
96
+ {
97
+ Napi::Object channelState = channelsArray.Get(i).As<Napi::Object>();
98
+ std::vector<bool> bufferData = dsp::utils::NapiArrayToVector<bool>(channelState.Get("buffer").As<Napi::Array>());
99
+ size_t runningCount = channelState.Get("runningCount").As<Napi::Number>().Uint32Value();
100
+
101
+ dsp::core::SscFilter<float>::SscFilterState filterState;
102
+ filterState.sample_minus_1 = channelState.Get("sample_minus_1").As<Napi::Number>().FloatValue();
103
+ filterState.sample_minus_2 = channelState.Get("sample_minus_2").As<Napi::Number>().FloatValue();
104
+ filterState.init_count = channelState.Get("init_count").As<Napi::Number>().Int32Value();
105
+
106
+ if (!dsp::core::CounterPolicy::validateState(runningCount, bufferData))
107
+ {
108
+ throw std::runtime_error("SSC running count validation failed");
109
+ }
110
+
111
+ m_filters[i].setState(bufferData, runningCount, filterState);
112
+ }
113
+ }
114
+
115
+ private:
116
+ size_t m_window_size;
117
+ float m_threshold;
118
+ std::vector<dsp::core::SscFilter<float>> m_filters;
119
+ };
120
+
121
+ } // namespace dsp::adapters