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