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,675 @@
1
+ #include "DspPipeline.h"
2
+ #include "adapters/MovingAverageStage.h" // Moving Average method
3
+ #include "adapters/RmsStage.h" // RMS method
4
+ #include "adapters/RectifyStage.h" // Rectify method
5
+ #include "adapters/VarianceStage.h" // Variance method
6
+ #include "adapters/ZScoreNormalizeStage.h" // Z-Score Normalize method
7
+ #include "adapters/MeanAbsoluteValueStage.h" // Mean Absolute Value method
8
+ #include "adapters/WaveformLengthStage.h" // Waveform Length method
9
+ #include "adapters/SscStage.h" // Slope Sign Change method
10
+ #include "adapters/WampStage.h" // Willison Amplitude method
11
+
12
+ namespace dsp
13
+ {
14
+ // Forward declarations for bindings
15
+ extern void InitFftBindings(Napi::Env env, Napi::Object exports);
16
+ extern void InitFilterBindings(Napi::Env env, Napi::Object exports);
17
+ }
18
+
19
+ #include <iostream>
20
+ #include <ctime>
21
+
22
+ namespace dsp
23
+ {
24
+
25
+ // N-API Boilerplate: Init function
26
+ Napi::Object DspPipeline::Init(Napi::Env env, Napi::Object exports)
27
+ {
28
+ Napi::Function func = DefineClass(env, "DspPipeline", {
29
+ // Pipeline building
30
+ InstanceMethod("addStage", &DspPipeline::AddStage),
31
+
32
+ // Processing
33
+ InstanceMethod("process", &DspPipeline::ProcessAsync),
34
+
35
+ // State management (for Redis persistence from TypeScript)
36
+ InstanceMethod("saveState", &DspPipeline::SaveState),
37
+ InstanceMethod("loadState", &DspPipeline::LoadState),
38
+ InstanceMethod("clearState", &DspPipeline::ClearState),
39
+ InstanceMethod("listState", &DspPipeline::ListState),
40
+ });
41
+
42
+ exports.Set("DspPipeline", func);
43
+ return exports;
44
+ }
45
+
46
+ // N-API Boilerplate: Constructor
47
+ DspPipeline::DspPipeline(const Napi::CallbackInfo &info)
48
+ : Napi::ObjectWrap<DspPipeline>(info)
49
+ {
50
+ // Config logic from TS (redis, stateKey) would go here
51
+ InitializeStageFactories();
52
+ }
53
+
54
+ /**
55
+ * Initialize the stage factory map with all available stages
56
+ * This is where the methods get exposed to TypeScript
57
+ */
58
+ void DspPipeline::InitializeStageFactories()
59
+ {
60
+ // Factory for Moving Average stage
61
+ m_stageFactories["movingAverage"] = [](const Napi::Object &params)
62
+ {
63
+ std::string modeStr = params.Get("mode").As<Napi::String>().Utf8Value();
64
+ dsp::adapters::AverageMode mode = (modeStr == "moving") ? dsp::adapters::AverageMode::Moving : dsp::adapters::AverageMode::Batch;
65
+
66
+ size_t windowSize = 0;
67
+ double windowDurationMs = 0.0;
68
+
69
+ if (mode == dsp::adapters::AverageMode::Moving)
70
+ {
71
+ // Accept either windowSize or windowDuration
72
+ if (params.Has("windowSize"))
73
+ {
74
+ windowSize = params.Get("windowSize").As<Napi::Number>().Uint32Value();
75
+ }
76
+ else if (params.Has("windowDuration"))
77
+ {
78
+ // Store the duration - will be converted to windowSize on first process() call
79
+ // using the actual sample rate derived from timestamps
80
+ windowDurationMs = params.Get("windowDuration").As<Napi::Number>().DoubleValue();
81
+ }
82
+ else
83
+ {
84
+ throw std::invalid_argument("MovingAverage: either 'windowSize' or 'windowDuration' is required for 'moving' mode");
85
+ }
86
+ }
87
+
88
+ return std::make_unique<dsp::adapters::MovingAverageStage>(mode, windowSize, windowDurationMs);
89
+ };
90
+
91
+ // Factory for RMS stage
92
+ m_stageFactories["rms"] = [](const Napi::Object &params)
93
+ {
94
+ std::string modeStr = params.Get("mode").As<Napi::String>().Utf8Value();
95
+ dsp::adapters::RmsMode mode = (modeStr == "moving") ? dsp::adapters::RmsMode::Moving : dsp::adapters::RmsMode::Batch;
96
+
97
+ size_t windowSize = 0;
98
+ double windowDurationMs = 0.0;
99
+
100
+ if (mode == dsp::adapters::RmsMode::Moving)
101
+ {
102
+ if (params.Has("windowSize"))
103
+ {
104
+ windowSize = params.Get("windowSize").As<Napi::Number>().Uint32Value();
105
+ }
106
+ else if (params.Has("windowDuration"))
107
+ {
108
+ windowDurationMs = params.Get("windowDuration").As<Napi::Number>().DoubleValue();
109
+ }
110
+ else
111
+ {
112
+ throw std::invalid_argument("RMS: either 'windowSize' or 'windowDuration' is required for 'moving' mode");
113
+ }
114
+ }
115
+
116
+ return std::make_unique<dsp::adapters::RmsStage>(mode, windowSize, windowDurationMs);
117
+ };
118
+
119
+ // Factory for Rectify stage
120
+ m_stageFactories["rectify"] = [](const Napi::Object &params)
121
+ {
122
+ std::string modeStr = params.Get("mode").As<Napi::String>().Utf8Value();
123
+ dsp::adapters::RectifyMode mode = (modeStr == "half") ? dsp::adapters::RectifyMode::HalfWave : dsp::adapters::RectifyMode::FullWave;
124
+ return std::make_unique<dsp::adapters::RectifyStage>(mode);
125
+ };
126
+
127
+ // Factory for Variance stage
128
+ m_stageFactories["variance"] = [](const Napi::Object &params)
129
+ {
130
+ std::string modeStr = params.Get("mode").As<Napi::String>().Utf8Value();
131
+ dsp::adapters::VarianceMode mode = (modeStr == "moving") ? dsp::adapters::VarianceMode::Moving : dsp::adapters::VarianceMode::Batch;
132
+
133
+ size_t windowSize = 0;
134
+ double windowDurationMs = 0.0;
135
+
136
+ if (mode == dsp::adapters::VarianceMode::Moving)
137
+ {
138
+ if (params.Has("windowSize"))
139
+ {
140
+ windowSize = params.Get("windowSize").As<Napi::Number>().Uint32Value();
141
+ }
142
+ else if (params.Has("windowDuration"))
143
+ {
144
+ windowDurationMs = params.Get("windowDuration").As<Napi::Number>().DoubleValue();
145
+ }
146
+ else
147
+ {
148
+ throw std::invalid_argument("Variance: either 'windowSize' or 'windowDuration' is required for 'moving' mode");
149
+ }
150
+ }
151
+
152
+ return std::make_unique<dsp::adapters::VarianceStage>(mode, windowSize, windowDurationMs);
153
+ };
154
+
155
+ // Factory for zScoreNormalize stage
156
+ m_stageFactories["zScoreNormalize"] = [](const Napi::Object &params)
157
+ {
158
+ std::string modeStr = params.Get("mode").As<Napi::String>().Utf8Value();
159
+ dsp::adapters::ZScoreNormalizeMode mode = (modeStr == "moving") ? dsp::adapters::ZScoreNormalizeMode::Moving : dsp::adapters::ZScoreNormalizeMode::Batch;
160
+
161
+ size_t windowSize = 0;
162
+ double windowDurationMs = 0.0;
163
+
164
+ if (mode == dsp::adapters::ZScoreNormalizeMode::Moving)
165
+ {
166
+ if (params.Has("windowSize"))
167
+ {
168
+ windowSize = params.Get("windowSize").As<Napi::Number>().Uint32Value();
169
+ }
170
+ else if (params.Has("windowDuration"))
171
+ {
172
+ windowDurationMs = params.Get("windowDuration").As<Napi::Number>().DoubleValue();
173
+ }
174
+ else
175
+ {
176
+ throw std::invalid_argument("ZScoreNormalize: either 'windowSize' or 'windowDuration' is required for 'moving' mode");
177
+ }
178
+ }
179
+
180
+ // Get optional epsilon, default to 1e-6
181
+ float epsilon = 1e-6f;
182
+ if (params.Has("epsilon"))
183
+ {
184
+ epsilon = params.Get("epsilon").As<Napi::Number>().FloatValue();
185
+ }
186
+
187
+ return std::make_unique<dsp::adapters::ZScoreNormalizeStage>(mode, windowSize, windowDurationMs, epsilon);
188
+ };
189
+
190
+ // Factory for Mean Absolute Value stage
191
+ m_stageFactories["meanAbsoluteValue"] = [](const Napi::Object &params)
192
+ {
193
+ std::string modeStr = params.Get("mode").As<Napi::String>().Utf8Value();
194
+ dsp::adapters::MavMode mode = (modeStr == "moving") ? dsp::adapters::MavMode::Moving : dsp::adapters::MavMode::Batch;
195
+
196
+ size_t windowSize = 0;
197
+ double windowDurationMs = 0.0;
198
+
199
+ if (mode == dsp::adapters::MavMode::Moving)
200
+ {
201
+ if (params.Has("windowSize"))
202
+ {
203
+ windowSize = params.Get("windowSize").As<Napi::Number>().Uint32Value();
204
+ }
205
+ else if (params.Has("windowDuration"))
206
+ {
207
+ windowDurationMs = params.Get("windowDuration").As<Napi::Number>().DoubleValue();
208
+ }
209
+ else
210
+ {
211
+ throw std::invalid_argument("MeanAbsoluteValue: either 'windowSize' or 'windowDuration' is required for 'moving' mode");
212
+ }
213
+ }
214
+
215
+ return std::make_unique<dsp::adapters::MeanAbsoluteValueStage>(mode, windowSize, windowDurationMs);
216
+ };
217
+
218
+ // Factory for Waveform Length stage
219
+ m_stageFactories["waveformLength"] = [](const Napi::Object &params)
220
+ {
221
+ if (!params.Has("windowSize"))
222
+ {
223
+ throw std::invalid_argument("WaveformLength: 'windowSize' is required");
224
+ }
225
+ size_t windowSize = params.Get("windowSize").As<Napi::Number>().Uint32Value();
226
+ return std::make_unique<dsp::adapters::WaveformLengthStage>(windowSize);
227
+ };
228
+
229
+ // Factory for Slope Sign Change (SSC) stage
230
+ m_stageFactories["slopeSignChange"] = [](const Napi::Object &params)
231
+ {
232
+ if (!params.Has("windowSize"))
233
+ {
234
+ throw std::invalid_argument("SlopeSignChange: 'windowSize' is required");
235
+ }
236
+ size_t windowSize = params.Get("windowSize").As<Napi::Number>().Uint32Value();
237
+
238
+ float threshold = 0.0f;
239
+ if (params.Has("threshold"))
240
+ {
241
+ threshold = params.Get("threshold").As<Napi::Number>().FloatValue();
242
+ }
243
+
244
+ return std::make_unique<dsp::adapters::SscStage>(windowSize, threshold);
245
+ };
246
+
247
+ // Factory for Willison Amplitude (WAMP) stage
248
+ m_stageFactories["willisonAmplitude"] = [](const Napi::Object &params)
249
+ {
250
+ if (!params.Has("windowSize"))
251
+ {
252
+ throw std::invalid_argument("WillisonAmplitude: 'windowSize' is required");
253
+ }
254
+ size_t windowSize = params.Get("windowSize").As<Napi::Number>().Uint32Value();
255
+
256
+ float threshold = 0.0f;
257
+ if (params.Has("threshold"))
258
+ {
259
+ threshold = params.Get("threshold").As<Napi::Number>().FloatValue();
260
+ }
261
+
262
+ return std::make_unique<dsp::adapters::WampStage>(windowSize, threshold);
263
+ };
264
+ }
265
+
266
+ /**
267
+ * This is the "Factory" method.
268
+ * TS calls: native.addStage("movingAverage", { windowSize: 100 })
269
+ */
270
+ Napi::Value DspPipeline::AddStage(const Napi::CallbackInfo &info)
271
+ {
272
+ Napi::Env env = info.Env();
273
+
274
+ // 1. Get arguments from TypeScript
275
+ std::string stageName = info[0].As<Napi::String>();
276
+ Napi::Object params = info[1].As<Napi::Object>();
277
+
278
+ // 2. Look up the stage factory in the map
279
+ auto it = m_stageFactories.find(stageName);
280
+ if (it != m_stageFactories.end())
281
+ {
282
+ try
283
+ {
284
+ // Factory found - create and add the stage
285
+ m_stages.push_back(it->second(params));
286
+ }
287
+ catch (const std::invalid_argument &e)
288
+ {
289
+ // Validation error in constructor - throw as JavaScript TypeError
290
+ Napi::TypeError::New(env, e.what()).ThrowAsJavaScriptException();
291
+ return env.Undefined();
292
+ }
293
+ catch (const std::exception &e)
294
+ {
295
+ // Other errors - throw as JavaScript Error
296
+ Napi::Error::New(env, e.what()).ThrowAsJavaScriptException();
297
+ return env.Undefined();
298
+ }
299
+ }
300
+ else
301
+ {
302
+ // Unknown stage type - throw error
303
+ Napi::TypeError::New(env, "Unknown stage type: " + stageName).ThrowAsJavaScriptException();
304
+ }
305
+
306
+ return env.Undefined();
307
+ }
308
+
309
+ /**
310
+ * AsyncWorker for processing DSP pipeline in background thread
311
+ */
312
+ class ProcessWorker : public Napi::AsyncWorker
313
+ {
314
+ public:
315
+ ProcessWorker(Napi::Env env,
316
+ Napi::Promise::Deferred deferred,
317
+ std::vector<std::unique_ptr<IDspStage>> &stages,
318
+ float *data,
319
+ float *timestamps,
320
+ size_t numSamples,
321
+ int channels,
322
+ Napi::Reference<Napi::Float32Array> &&bufferRef,
323
+ Napi::Reference<Napi::Float32Array> &&timestampRef)
324
+ : Napi::AsyncWorker(env),
325
+ m_deferred(std::move(deferred)),
326
+ m_stages(stages),
327
+ m_data(data),
328
+ m_timestamps(timestamps),
329
+ m_numSamples(numSamples),
330
+ m_channels(channels),
331
+ m_bufferRef(std::move(bufferRef)),
332
+ m_timestampRef(std::move(timestampRef))
333
+ {
334
+ }
335
+
336
+ protected:
337
+ // This runs on a worker thread (not blocking the event loop)
338
+ void Execute() override
339
+ {
340
+ try
341
+ {
342
+ // Process the buffer through all stages
343
+ // Pass timestamps to stages that support time-based processing
344
+ for (const auto &stage : m_stages)
345
+ {
346
+ stage->process(m_data, m_numSamples, m_channels, m_timestamps);
347
+ }
348
+ }
349
+ catch (const std::exception &e)
350
+ {
351
+ SetError(e.what());
352
+ }
353
+ }
354
+
355
+ // This runs on the main thread after Execute() completes
356
+ void OnOK() override
357
+ {
358
+ Napi::Env env = Env();
359
+ // Resolve the promise with the processed buffer
360
+ Napi::Float32Array buffer = m_bufferRef.Value();
361
+ m_deferred.Resolve(buffer);
362
+ }
363
+
364
+ void OnError(const Napi::Error &error) override
365
+ {
366
+ m_deferred.Reject(error.Value());
367
+ }
368
+
369
+ private:
370
+ Napi::Promise::Deferred m_deferred;
371
+ std::vector<std::unique_ptr<IDspStage>> &m_stages;
372
+ float *m_data;
373
+ float *m_timestamps;
374
+ size_t m_numSamples;
375
+ int m_channels;
376
+ Napi::Reference<Napi::Float32Array> m_bufferRef;
377
+ Napi::Reference<Napi::Float32Array> m_timestampRef;
378
+ };
379
+
380
+ /**
381
+ * This is the "Process" method.
382
+ * TS calls:
383
+ * await native.process(buffer, timestamps, { channels: 4 })
384
+ * or (legacy):
385
+ * await native.process(buffer, { sampleRate: 2000, channels: 4 })
386
+ * Returns a Promise that resolves when processing is complete.
387
+ */
388
+ Napi::Value DspPipeline::ProcessAsync(const Napi::CallbackInfo &info)
389
+ {
390
+ Napi::Env env = info.Env();
391
+
392
+ // 1. Get buffer from TypeScript (zero-copy)
393
+ Napi::Float32Array jsBuffer = info[0].As<Napi::Float32Array>();
394
+ float *data = jsBuffer.Data();
395
+ size_t numSamples = jsBuffer.ElementLength();
396
+
397
+ // 2. Get timestamps and options
398
+ // TypeScript can pass either:
399
+ // process(buffer, timestamps, options) - new time-based API
400
+ // process(buffer, options) - legacy sample-based API (timestamps = nullptr)
401
+ Napi::Float32Array jsTimestamps;
402
+ float *timestamps = nullptr;
403
+ Napi::Object options;
404
+
405
+ if (info.Length() >= 2 && info[1].IsTypedArray())
406
+ {
407
+ // New API: timestamps provided
408
+ jsTimestamps = info[1].As<Napi::Float32Array>();
409
+ timestamps = jsTimestamps.Data();
410
+ options = info[2].As<Napi::Object>();
411
+
412
+ // Validate timestamp length matches sample length
413
+ if (jsTimestamps.ElementLength() != numSamples)
414
+ {
415
+ Napi::TypeError::New(env, "Timestamp array length must match sample array length")
416
+ .ThrowAsJavaScriptException();
417
+ return env.Undefined();
418
+ }
419
+ }
420
+ else
421
+ {
422
+ // Legacy API: no timestamps (will use sample indices)
423
+ options = info[1].As<Napi::Object>();
424
+ }
425
+
426
+ int channels = options.Get("channels").As<Napi::Number>().Uint32Value();
427
+ // int sampleRate = options.Get("sampleRate").As<Napi::Number>().Uint32Value();
428
+
429
+ // 3. Create a deferred promise and get the promise before moving
430
+ Napi::Promise::Deferred deferred = Napi::Promise::Deferred::New(env);
431
+ Napi::Promise promise = deferred.Promise();
432
+
433
+ // 4. Create references to keep buffers alive during async operation
434
+ Napi::Reference<Napi::Float32Array> bufferRef = Napi::Reference<Napi::Float32Array>::New(jsBuffer, 1);
435
+ Napi::Reference<Napi::Float32Array> timestampRef;
436
+ if (timestamps != nullptr)
437
+ {
438
+ timestampRef = Napi::Reference<Napi::Float32Array>::New(jsTimestamps, 1);
439
+ }
440
+
441
+ // 5. Create and queue the worker
442
+ ProcessWorker *worker = new ProcessWorker(env, std::move(deferred), m_stages, data, timestamps, numSamples, channels, std::move(bufferRef), std::move(timestampRef));
443
+ worker->Queue();
444
+
445
+ // 6. Return the promise immediately
446
+ return promise;
447
+ }
448
+
449
+ /**
450
+ * Save current pipeline state as JSON string
451
+ * TypeScript will handle storing this in Redis
452
+ *
453
+ * Returns: JSON string with pipeline configuration and stage states
454
+ */
455
+ Napi::Value DspPipeline::SaveState(const Napi::CallbackInfo &info)
456
+ {
457
+ Napi::Env env = info.Env();
458
+ Napi::Object stateObj = Napi::Object::New(env);
459
+
460
+ // Save timestamp
461
+ stateObj.Set("timestamp", static_cast<double>(std::time(nullptr)));
462
+
463
+ // Save pipeline configuration and full state
464
+ Napi::Array stagesArray = Napi::Array::New(env, m_stages.size());
465
+
466
+ for (size_t i = 0; i < m_stages.size(); ++i)
467
+ {
468
+ Napi::Object stageConfig = Napi::Object::New(env);
469
+
470
+ stageConfig.Set("index", static_cast<uint32_t>(i));
471
+ stageConfig.Set("type", m_stages[i]->getType());
472
+
473
+ // Serialize the stage's internal state
474
+ stageConfig.Set("state", m_stages[i]->serializeState(env));
475
+
476
+ stagesArray.Set(static_cast<uint32_t>(i), stageConfig);
477
+ }
478
+
479
+ stateObj.Set("stages", stagesArray);
480
+ stateObj.Set("stageCount", static_cast<uint32_t>(m_stages.size()));
481
+
482
+ // Convert to JSON string using JavaScript's JSON.stringify
483
+ Napi::Object JSON = env.Global().Get("JSON").As<Napi::Object>();
484
+ Napi::Function stringify = JSON.Get("stringify").As<Napi::Function>();
485
+ return stringify.Call(JSON, {stateObj});
486
+ }
487
+
488
+ /**
489
+ * Load pipeline state from JSON string
490
+ * TypeScript retrieves this from Redis and passes it here
491
+ *
492
+ * Accepts: JSON string with pipeline configuration
493
+ */
494
+ Napi::Value DspPipeline::LoadState(const Napi::CallbackInfo &info)
495
+ {
496
+ Napi::Env env = info.Env();
497
+
498
+ // Validate input
499
+ if (info.Length() < 1 || !info[0].IsString())
500
+ {
501
+ Napi::TypeError::New(env, "Expected state JSON string as first argument")
502
+ .ThrowAsJavaScriptException();
503
+ return env.Undefined();
504
+ }
505
+
506
+ std::string stateJson = info[0].As<Napi::String>().Utf8Value();
507
+
508
+ try
509
+ {
510
+ // Parse JSON string using JavaScript's JSON.parse
511
+ Napi::Object JSON = env.Global().Get("JSON").As<Napi::Object>();
512
+ Napi::Function parse = JSON.Get("parse").As<Napi::Function>();
513
+ Napi::Object stateObj = parse.Call(JSON, {Napi::String::New(env, stateJson)}).As<Napi::Object>();
514
+
515
+ // Validate state object has required fields
516
+ if (!stateObj.Has("stages"))
517
+ {
518
+ Napi::Error::New(env, "Invalid state: missing 'stages' field")
519
+ .ThrowAsJavaScriptException();
520
+ return Napi::Boolean::New(env, false);
521
+ }
522
+
523
+ // Get stages array
524
+ Napi::Array stagesArray = stateObj.Get("stages").As<Napi::Array>();
525
+ uint32_t stageCount = stagesArray.Length();
526
+
527
+ // Validate stage count matches
528
+ if (stageCount != m_stages.size())
529
+ {
530
+ Napi::Error::New(env, "Stage count mismatch: expected " +
531
+ std::to_string(m_stages.size()) + " but got " + std::to_string(stageCount))
532
+ .ThrowAsJavaScriptException();
533
+ return Napi::Boolean::New(env, false);
534
+ }
535
+
536
+ // Log restoration
537
+ std::cout << "Restoring pipeline state with " << stageCount << " stages" << std::endl;
538
+
539
+ // Restore each stage's state
540
+ for (uint32_t i = 0; i < stageCount; ++i)
541
+ {
542
+ Napi::Object stageConfig = stagesArray.Get(i).As<Napi::Object>();
543
+ if (stageConfig.Has("state"))
544
+ {
545
+ Napi::Object stageState = stageConfig.Get("state").As<Napi::Object>();
546
+ m_stages[i]->deserializeState(stageState);
547
+ }
548
+ }
549
+
550
+ std::cout << "State restoration complete!" << std::endl;
551
+
552
+ return Napi::Boolean::New(env, true);
553
+ }
554
+ catch (const std::exception &e)
555
+ {
556
+ Napi::Error::New(env, std::string("Failed to load state: ") + e.what())
557
+ .ThrowAsJavaScriptException();
558
+ return Napi::Boolean::New(env, false);
559
+ }
560
+ }
561
+
562
+ /**
563
+ * Clear all pipeline state (reset all stages)
564
+ * This resets filters to their initial state without removing them
565
+ */
566
+ Napi::Value DspPipeline::ClearState(const Napi::CallbackInfo &info)
567
+ {
568
+ Napi::Env env = info.Env();
569
+
570
+ // Reset all stages
571
+ for (auto &stage : m_stages)
572
+ {
573
+ stage->reset();
574
+ }
575
+
576
+ std::cout << "Pipeline state cleared (" << m_stages.size() << " stages reset)" << std::endl;
577
+
578
+ return env.Undefined();
579
+ }
580
+
581
+ /**
582
+ * List current pipeline state (summary information)
583
+ * Returns a simplified view of the pipeline configuration
584
+ * Useful for debugging and monitoring without parsing full JSON
585
+ *
586
+ * Returns: Object with pipeline summary (stage count, types, window sizes, etc.)
587
+ */
588
+ Napi::Value DspPipeline::ListState(const Napi::CallbackInfo &info)
589
+ {
590
+ Napi::Env env = info.Env();
591
+ Napi::Object summary = Napi::Object::New(env);
592
+
593
+ // Basic pipeline info
594
+ summary.Set("stageCount", static_cast<uint32_t>(m_stages.size()));
595
+ summary.Set("timestamp", static_cast<double>(std::time(nullptr)));
596
+
597
+ // Create array of stage summaries
598
+ Napi::Array stagesArray = Napi::Array::New(env, m_stages.size());
599
+
600
+ for (size_t i = 0; i < m_stages.size(); ++i)
601
+ {
602
+ Napi::Object stageSummary = Napi::Object::New(env);
603
+
604
+ // Basic stage info
605
+ stageSummary.Set("index", static_cast<uint32_t>(i));
606
+ stageSummary.Set("type", m_stages[i]->getType());
607
+
608
+ // Get full state to extract key info
609
+ Napi::Object fullState = m_stages[i]->serializeState(env);
610
+
611
+ // Extract common fields (windowSize, numChannels, mode)
612
+ if (fullState.Has("windowSize"))
613
+ {
614
+ stageSummary.Set("windowSize", fullState.Get("windowSize"));
615
+ }
616
+
617
+ if (fullState.Has("numChannels"))
618
+ {
619
+ stageSummary.Set("numChannels", fullState.Get("numChannels"));
620
+ }
621
+
622
+ if (fullState.Has("mode"))
623
+ {
624
+ stageSummary.Set("mode", fullState.Get("mode"));
625
+ }
626
+
627
+ // Add buffer occupancy info for stateful filters
628
+ if (fullState.Has("channels"))
629
+ {
630
+ Napi::Array channels = fullState.Get("channels").As<Napi::Array>();
631
+ if (channels.Length() > 0)
632
+ {
633
+ Napi::Object firstChannel = channels.Get(uint32_t(0)).As<Napi::Object>();
634
+ if (firstChannel.Has("buffer"))
635
+ {
636
+ Napi::Array buffer = firstChannel.Get("buffer").As<Napi::Array>();
637
+ stageSummary.Set("bufferSize", buffer.Length());
638
+ }
639
+ }
640
+ stageSummary.Set("channelCount", channels.Length());
641
+ }
642
+
643
+ stagesArray.Set(static_cast<uint32_t>(i), stageSummary);
644
+ }
645
+
646
+ summary.Set("stages", stagesArray);
647
+
648
+ return summary;
649
+ }
650
+
651
+ } // namespace dsp
652
+
653
+ // Forward declare FFT bindings init
654
+ namespace dsp
655
+ {
656
+ void InitFftBindings(Napi::Env env, Napi::Object exports);
657
+ }
658
+
659
+ // This function is called by Node.js when the addon is loaded
660
+ Napi::Object InitAll(Napi::Env env, Napi::Object exports)
661
+ {
662
+ // Initialize DspPipeline class
663
+ dsp::DspPipeline::Init(env, exports);
664
+
665
+ // Initialize FFT/DFT bindings
666
+ dsp::InitFftBindings(env, exports);
667
+
668
+ // Initialize FIR/IIR filter bindings
669
+ dsp::InitFilterBindings(env, exports);
670
+
671
+ return exports;
672
+ }
673
+
674
+ // This line registers the module
675
+ NODE_API_MODULE(dsp_addon, InitAll)