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
package/README.md ADDED
@@ -0,0 +1,1803 @@
1
+ # Work in Progress
2
+
3
+ > The project’s in heavy development.
4
+ > Once Phases 1 and 2 (core DSP + FFT/IIR/FIR) are stable, I’ll push the first npm release.
5
+ > Expect breaking changes until then!
6
+
7
+ # dspx
8
+
9
+ > **A high-performance DSP library with a built-in micro-framework for real-time pipelines, state persistence, and time-series signal processing. Powered by C++ (via N-API) and Redis for cross-session continuity — ideal for biosignals, IoT, and edge analytics.**
10
+
11
+ A modern DSP library built for Node.js backends processing real-time biosignals, audio streams, and sensor data. Features native C++ filters with full state serialization to Redis, enabling seamless processing across service restarts and distributed workers.
12
+
13
+ ---
14
+
15
+ ## ✨ Features
16
+
17
+ - 🚀 **Native C++ Performance** – Optimized circular buffers and filters with SIMD acceleration for real-time processing
18
+ - 🎯 **SIMD Acceleration** – AVX2/SSE2/NEON optimizations provide 2-8x speedup on batch operations and rectification
19
+ - 🔧 **TypeScript-First** – Full type safety with excellent IntelliSense
20
+ - 📡 **Redis State Persistence** – Fully implemented state serialization/deserialization including circular buffer contents and running sums
21
+ - ⏱️ **Time-Series Processing** – NEW! Support for irregular timestamps and time-based windows
22
+ - 🔗 **Fluent Pipeline API** – Chain filter operations with method chaining
23
+ - 🎯 **Zero-Copy Processing** – Direct TypedArray manipulation for minimal overhead
24
+ - 📊 **Multi-Channel Support** – Process multi-channel signals (EMG, EEG, audio) with independent filter states per channel
25
+ - ⚡ **Async Processing** – Background thread processing to avoid blocking the Node.js event loop
26
+ - 🛡️ **Crash Recovery** – Resume processing from exact state after service restarts
27
+
28
+ ---
29
+
30
+ ## 🏗️ Architecture
31
+
32
+ The library uses a layered architecture with clear separation between TypeScript and C++ components:
33
+
34
+ ```mermaid
35
+ graph TB
36
+ subgraph "TypeScript Layer (src/ts/)"
37
+ TS_API["TypeScript API<br/>bindings.ts"]
38
+ TS_TYPES["Type Definitions<br/>types.ts"]
39
+ TS_UTILS["Utilities<br/>CircularLogBuffer, TopicRouter"]
40
+ TS_REDIS["Redis Backend<br/>backends.ts"]
41
+ end
42
+
43
+ subgraph "N-API Bridge Layer"
44
+ NAPI["Node-API Bindings<br/>(native module)"]
45
+ end
46
+
47
+ subgraph "C++ Layer (src/native/)"
48
+ subgraph "dsp::adapters (N-API Adapters)"
49
+ ADAPTER_EXAMPLE["Example: MovingAverageStage<br/>(supports batch/moving modes)"]
50
+ ADAPTER_STATELESS["Example: RectifyStage<br/>(stateless only)"]
51
+ end
52
+
53
+ PIPELINE["DspPipeline.cc<br/>(Stage Orchestration)"]
54
+
55
+ subgraph "dsp::core (Pure C++ Algorithms)"
56
+ CORE_ENGINE["SlidingWindowFilter<br/>(Generic Stateful Engine)"]
57
+ CORE_FILTER["Example: MovingAverageFilter<br/>(wraps SlidingWindowFilter)<br/><b>Stateful - Moving Mode</b>"]
58
+
59
+ subgraph "Statistical Policies"
60
+ POLICY_EXAMPLE["Example: MeanPolicy<br/>(pure computation)<br/><b>Stateless - Batch Mode</b>"]
61
+ end
62
+ end
63
+
64
+ subgraph "dsp::utils (Data Structures)"
65
+ CIRCULAR["CircularBuffer<br/>(Array & Vector)<br/><b>Stateful Storage</b>"]
66
+ end
67
+ end
68
+
69
+ subgraph "External Services"
70
+ REDIS_DB[("Redis<br/>(State Persistence)")]
71
+ end
72
+
73
+ %% Connections
74
+ TS_API --> NAPI
75
+ TS_REDIS --> REDIS_DB
76
+ NAPI --> PIPELINE
77
+ PIPELINE --> ADAPTER_EXAMPLE
78
+ PIPELINE --> ADAPTER_STATELESS
79
+
80
+ ADAPTER_EXAMPLE --> CORE_FILTER
81
+ ADAPTER_EXAMPLE --> POLICY_EXAMPLE
82
+
83
+ CORE_FILTER --> CORE_ENGINE
84
+
85
+ CORE_ENGINE --> POLICY_EXAMPLE
86
+
87
+ CORE_ENGINE --> CIRCULAR
88
+
89
+ %% Styling
90
+ classDef tsLayer fill:#3178c6,stroke:#235a97,color:#fff
91
+ classDef cppCore fill:#00599c,stroke:#003f6f,color:#fff
92
+ classDef cppEngine fill:#1a8cff,stroke:#0066cc,color:#fff
93
+ classDef cppPolicy fill:#66b3ff,stroke:#3399ff,color:#000
94
+ classDef cppAdapter fill:#659ad2,stroke:#4a7ba7,color:#fff
95
+ classDef cppUtils fill:#a8c5e2,stroke:#7a9fbe,color:#000
96
+ classDef external fill:#dc382d,stroke:#a82820,color:#fff
97
+
98
+ class TS_API,TS_TYPES,TS_UTILS,TS_REDIS tsLayer
99
+ class CORE_MA,CORE_RMS,CORE_MAV,CORE_VAR,CORE_ZSCORE cppCore
100
+ class CORE_ENGINE cppEngine
101
+ class POLICY_MEAN,POLICY_RMS,POLICY_MAV,POLICY_VAR,POLICY_ZSCORE cppPolicy
102
+ class ADAPTER_MA,ADAPTER_RMS,ADAPTER_RECT,ADAPTER_VAR,ADAPTER_ZSCORE cppAdapter
103
+ class CIRCULAR cppUtils
104
+ class REDIS_DB external
105
+ ```
106
+
107
+ ### Key Architectural Principles
108
+
109
+ **1. Namespace Separation**
110
+
111
+ - **`dsp::core`**: Pure C++ algorithms with no Node.js dependencies. Reusable in other C++ projects.
112
+ - **`dsp::adapters`**: N-API wrapper classes that expose core algorithms to JavaScript.
113
+ - **`dsp::utils`**: Shared data structures (circular buffers) used by core algorithms.
114
+
115
+ **2. Policy-Based Design (Zero-Cost Abstraction)**
116
+
117
+ The sliding window filters use **compile-time polymorphism** via template policies:
118
+
119
+ - **`SlidingWindowFilter<T, Policy>`**: Generic engine handling circular buffer and window logic
120
+ - **Statistical Policies**: Define specific computations (MeanPolicy, RmsPolicy, VariancePolicy, etc.)
121
+ - **Zero overhead**: Compiler inlines policy methods, achieving hand-written performance
122
+ - **Extensibility**: New statistical measures require only a new policy class
123
+
124
+ Example: `MovingAverageFilter` is a thin wrapper around `SlidingWindowFilter<float, MeanPolicy<float>>`
125
+
126
+ **2.1 Layered State Management**
127
+
128
+ State serialization follows a **3-tier delegation pattern** where each layer manages only its own state:
129
+
130
+ ```
131
+ Policy Layer → getState() returns policy-specific state (running sums, counters, etc.)
132
+
133
+ SlidingWindowFilter → getState() returns {buffer contents, policy state}
134
+
135
+ Filter Wrapper → getState() delegates to SlidingWindowFilter
136
+ ```
137
+
138
+ **Example Implementation:**
139
+
140
+ ```cpp
141
+ // Policy manages its own statistical state
142
+ template <typename T>
143
+ struct MeanPolicy {
144
+ T m_sum{}; // Policy-specific state
145
+
146
+ auto getState() const -> T { return m_sum; }
147
+ void setState(T sum) { m_sum = sum; }
148
+ };
149
+
150
+ // SlidingWindowFilter coordinates buffer + policy state
151
+ template <typename T, typename Policy>
152
+ class SlidingWindowFilter {
153
+ auto getState() const {
154
+ return std::make_pair(m_buffer.toVector(), m_policy.getState());
155
+ }
156
+
157
+ template <typename PolicyState>
158
+ void setState(const std::vector<T>& bufferData, const PolicyState& policyState) {
159
+ m_buffer.fromVector(bufferData);
160
+ m_policy.setState(policyState);
161
+ }
162
+ };
163
+
164
+ // Filter delegates without knowledge of internals
165
+ class MovingAverageFilter {
166
+ auto getState() const { return m_filter.getState(); } // Clean delegation
167
+ };
168
+ ```
169
+
170
+ **Benefits:**
171
+
172
+ - ✅ **Separation of concerns**: Each layer owns its state
173
+ - ✅ **Type safety**: Policies can have different state types
174
+ - ✅ **Extensibility**: Adding new policies doesn't change SlidingWindowFilter
175
+ - ✅ **DRY principle**: No duplicate state assembly code in each filter
176
+
177
+ **3. Layered Design**
178
+
179
+ - **TypeScript Layer**: User-facing API, type safety, Redis integration
180
+ - **N-API Bridge**: Zero-copy data marshaling between JS and C++
181
+ - **C++ Core**: High-performance DSP algorithms with optimized memory management
182
+
183
+ **4. State Management**
184
+
185
+ State serialization uses a **layered delegation pattern** (see section 2.1):
186
+
187
+ - **Policy Layer**: Manages statistical state (running sums, variance accumulators, etc.)
188
+ - **SlidingWindowFilter**: Coordinates buffer contents + policy state
189
+ - **Filter Wrapper**: Delegates to SlidingWindowFilter without knowledge of internals
190
+
191
+ Full state serialization to JSON enables:
192
+
193
+ - ✅ Redis persistence for distributed processing
194
+ - ✅ Process continuity across restarts
195
+ - ✅ State migration between workers
196
+ - ✅ Data integrity validation on deserialization
197
+
198
+ Each filter's `getState()` returns a tuple `{bufferData: T[], policyState: PolicyState}` that can be JSON-serialized through the TypeScript layer.
199
+
200
+ **5. Mode Architecture** (MovingAverage, RMS, Variance, ZScoreNormalize)
201
+
202
+ - **Batch Mode**: Stateless processing, computes over entire input
203
+ - **Moving Mode**: Stateful processing with sliding window continuity
204
+
205
+ This separation enables:
206
+
207
+ - ✅ Unit testing of C++ algorithms independently
208
+ - ✅ Reuse of core DSP code in other projects
209
+ - ✅ Type-safe TypeScript API with IntelliSense
210
+ - ✅ Zero-copy performance through N-API
211
+ - ✅ Distributed processing with Redis state sharing
212
+
213
+ **6. Native C++ Backend**
214
+
215
+ - **N-API Bindings**: Direct TypedArray access for zero-copy processing
216
+ - **Async Processing**: Uses `Napi::AsyncWorker` to avoid blocking the event loop
217
+ - **Optimized Data Structures**: Circular buffers with O(1) operations
218
+ - **Template-Based**: Generic implementation supports int, float, double
219
+
220
+ **7. Redis State Persistence**
221
+
222
+ The state serialization includes:
223
+
224
+ - **Circular buffer contents**: All samples in order (oldest to newest)
225
+ - **Running sums/squares**: Maintained for O(1) calculations (moving average uses `runningSum`, RMS uses `runningSumOfSquares`)
226
+ - **Per-channel state**: Independent state for each audio channel
227
+ - **Metadata**: Window size, channel count, timestamps, filter type
228
+
229
+ **State format examples:**
230
+
231
+ Moving Average state:
232
+
233
+ ```json
234
+ {
235
+ "timestamp": 1761156820,
236
+ "stages": [
237
+ {
238
+ "index": 0,
239
+ "type": "movingAverage",
240
+ "state": {
241
+ "windowSize": 3,
242
+ "numChannels": 1,
243
+ "channels": [
244
+ {
245
+ "buffer": [3, 4, 5],
246
+ "runningSum": 12
247
+ }
248
+ ]
249
+ }
250
+ }
251
+ ],
252
+ "stageCount": 1
253
+ }
254
+ ```
255
+
256
+ RMS state:
257
+
258
+ ```json
259
+ {
260
+ "timestamp": 1761168608,
261
+ "stages": [
262
+ {
263
+ "index": 0,
264
+ "type": "rms",
265
+ "state": {
266
+ "windowSize": 3,
267
+ "numChannels": 1,
268
+ "channels": [
269
+ {
270
+ "buffer": [6, -7, 8],
271
+ "runningSumOfSquares": 149
272
+ }
273
+ ]
274
+ }
275
+ }
276
+ ],
277
+ "stageCount": 1
278
+ }
279
+ ```
280
+
281
+ **8. Multi-Channel Processing**
282
+
283
+ Each channel maintains its own independent filter state:
284
+
285
+ ```typescript
286
+ // 4-channel interleaved data: [ch1, ch2, ch3, ch4, ch1, ch2, ...]
287
+ const input = new Float32Array(4000); // 1000 samples × 4 channels
288
+
289
+ const pipeline = createDspPipeline();
290
+ pipeline.MovingAverage({ windowSize: 50 });
291
+
292
+ const output = await pipeline.process(input, {
293
+ sampleRate: 2000,
294
+ channels: 4,
295
+ });
296
+
297
+ // Each channel has its own circular buffer and running sum
298
+ ```
299
+
300
+ ---
301
+
302
+ ## 📊 Comparison with Alternatives
303
+
304
+ | Feature | dspx | scipy/numpy | dsp.js | Web Audio API |
305
+ | ------------------ | ----------------- | ------------------- | ---------- | --------------- |
306
+ | TypeScript Support | ✅ Native | ❌ Python-only | ⚠️ Partial | ✅ Browser-only |
307
+ | Performance | ⚡⚡⚡ Native C++ | ⚡⚡⚡⚡ | ⚡ Pure JS | ⚡⚡⚡ |
308
+ | State Persistence | ✅ Redis | ❌ Manual | ❌ None | ❌ None |
309
+ | Multi-Channel | ✅ Built-in | ✅ NumPy arrays | ⚠️ Limited | ✅ AudioBuffer |
310
+ | Node.js Backend | ✅ Designed for | ❌ Context switch | ✅ Yes | ❌ Browser |
311
+ | Observability | ✅ Callbacks | ❌ Print statements | ❌ None | ⚠️ Limited |
312
+
313
+ ---
314
+
315
+ ## 🎯 Is This Library Right For You?
316
+
317
+ ### ✅ Great Fit For:
318
+
319
+ - Node.js backends processing real-time biosignals (EMG, EEG, ECG)
320
+ - Audio streaming services applying filters at scale
321
+ - IoT gateways processing sensor data
322
+ - Distributed signal processing across multiple workers
323
+ - Teams wanting native C++ performance without leaving TypeScript
324
+
325
+ ### ❌ Not Ideal For:
326
+
327
+ - Browser-only applications (use Web Audio API)
328
+ - Python-based ML pipelines (use SciPy/NumPy)
329
+ - Hard real-time embedded systems (use bare C/C++)
330
+ - Ultra-low latency (<1ms) requirements (Redis adds ~1-5ms)
331
+
332
+ ---
333
+
334
+ ## ⚡ Performance at a Glance
335
+
336
+ | Operation | Throughput | Production-Ready |
337
+ | -------------------------------- | ---------------- | --------------------------------- |
338
+ | Native processing (no callbacks) | 22M samples/sec | ✅ Maximum performance |
339
+ | Batched callbacks | 3.2M samples/sec | ✅ **Recommended** for production |
340
+ | Individual callbacks | 6.1M samples/sec | ⚠️ Development/debugging only |
341
+
342
+ **SIMD Acceleration:** Batch operations and rectification are 2-8x faster with AVX2/SSE2/NEON. See [SIMD_OPTIMIZATIONS.md](https://github.com/A-KGeorge/dsp_ts_redis/blob/main/docs/SIMD_OPTIMIZATIONS.md) for details.
343
+
344
+ **Recommendation:** Use batched callbacks in production. Individual callbacks benchmark faster but block the Node.js event loop and can't integrate with real telemetry systems (Kafka, Datadog, Loki).
345
+
346
+ ---
347
+
348
+ ## 📦 Installation
349
+
350
+ ```bash
351
+ npm install dspx redis
352
+ ```
353
+
354
+ **Note:** You'll need a C++ compiler if prebuilt binaries aren't available for your platform:
355
+
356
+ - Windows: Visual Studio 2022 or Build Tools
357
+ - macOS: Xcode Command Line Tools
358
+ - Linux: GCC/G++ 7+
359
+
360
+ ---
361
+
362
+ ## 🚀 Quick Start
363
+
364
+ ### Basic Usage (Sample-Based)
365
+
366
+ ```typescript
367
+ import { createDspPipeline } from "dspx";
368
+
369
+ // Create a processing pipeline
370
+ const pipeline = createDspPipeline();
371
+
372
+ // Add filters using method chaining
373
+ pipeline.MovingAverage({ mode: "moving", windowSize: 100 });
374
+
375
+ // Process samples (modifies input in-place for performance)
376
+ const input = new Float32Array([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);
377
+ const output = await pipeline.process(input, {
378
+ sampleRate: 2000,
379
+ channels: 1,
380
+ });
381
+
382
+ console.log(output); // Smoothed signal
383
+ ```
384
+
385
+ ### NEW: Time-Series Processing with Timestamps
386
+
387
+ ```typescript
388
+ import { createDspPipeline } from "dspx";
389
+
390
+ // Create a pipeline with time-based window
391
+ const pipeline = createDspPipeline();
392
+ pipeline.MovingAverage({ mode: "moving", windowDuration: 5000 }); // 5 seconds
393
+
394
+ // Process data with explicit timestamps (e.g., from IoT sensors with network jitter)
395
+ const samples = new Float32Array([1.2, 3.4, 2.1, 4.5, 3.3]);
396
+ const timestamps = new Float32Array([0, 100, 250, 400, 500]); // milliseconds
397
+
398
+ const smoothed = await pipeline.process(samples, timestamps, { channels: 1 });
399
+ console.log(smoothed); // Time-aware smoothing
400
+ ```
401
+
402
+ **📚 [Complete Time-Series Guide →](https://github.com/A-KGeorge/dsp_ts_redis/blob/main/docs/time-series-guide.md)**
403
+
404
+ ### Processing Without Modifying Input
405
+
406
+ ```typescript
407
+ // Use processCopy to preserve the original input
408
+ const input = new Float32Array([1, 2, 3, 4, 5]);
409
+ const output = await pipeline.processCopy(input, {
410
+ sampleRate: 2000,
411
+ channels: 1,
412
+ });
413
+
414
+ console.log(input); // [1, 2, 3, 4, 5] - unchanged
415
+ console.log(output); // [1, 1.5, 2, 3, 4] - smoothed
416
+ ```
417
+
418
+ ### With Redis State Persistence
419
+
420
+ ```typescript
421
+ import { createDspPipeline } from "dspx";
422
+ import { createClient } from "redis";
423
+
424
+ const redis = await createClient({ url: "redis://localhost:6379" }).connect();
425
+
426
+ // Create pipeline with Redis config
427
+ const pipeline = createDspPipeline({
428
+ redisHost: "localhost",
429
+ redisPort: 6379,
430
+ stateKey: "dsp:user123:channel1",
431
+ });
432
+
433
+ pipeline.MovingAverage({ windowSize: 100 });
434
+
435
+ // Try to restore previous state from Redis
436
+ const savedState = await redis.get("dsp:user123:channel1");
437
+ if (savedState) {
438
+ await pipeline.loadState(savedState);
439
+ console.log("State restored!");
440
+ }
441
+
442
+ // Process data - filter state is maintained
443
+ await pipeline.process(chunk1, { sampleRate: 2000, channels: 1 });
444
+
445
+ // Save state to Redis (includes circular buffer contents!)
446
+ const state = await pipeline.saveState();
447
+ await redis.set("dsp:user123:channel1", state);
448
+
449
+ // Continue processing - even after service restart!
450
+ await pipeline.process(chunk2, { sampleRate: 2000, channels: 1 });
451
+
452
+ // Clear state when starting fresh
453
+ pipeline.clearState();
454
+ ```
455
+
456
+ ### Multi-Channel Processing
457
+
458
+ ```typescript
459
+ import { createDspPipeline } from "dspx";
460
+
461
+ const pipeline = createDspPipeline();
462
+ pipeline.MovingAverage({ windowSize: 50 });
463
+
464
+ // Process 4-channel EMG data
465
+ // Data format: [ch1_s1, ch2_s1, ch3_s1, ch4_s1, ch1_s2, ch2_s2, ...]
466
+ const fourChannelData = new Float32Array(4000); // 1000 samples × 4 channels
467
+
468
+ const output = await pipeline.process(fourChannelData, {
469
+ sampleRate: 2000,
470
+ channels: 4, // Each channel maintains its own filter state
471
+ });
472
+
473
+ // Each channel is processed independently with its own circular buffer
474
+ ```
475
+
476
+ ---
477
+
478
+ ## 🔗 API Reference
479
+
480
+ ### Creating a Pipeline
481
+
482
+ ```typescript
483
+ import { createDspPipeline, type RedisConfig } from "dspx";
484
+
485
+ interface RedisConfig {
486
+ redisHost?: string; // Redis server hostname (optional)
487
+ redisPort?: number; // Redis server port (optional)
488
+ stateKey?: string; // Key prefix for state storage (optional)
489
+ }
490
+
491
+ const pipeline = createDspPipeline(config?: RedisConfig);
492
+ ```
493
+
494
+ ### Process Methods
495
+
496
+ The library supports three processing modes:
497
+
498
+ ```typescript
499
+ // Mode 1: Legacy sample-based (auto-generates timestamps from sample rate)
500
+ await pipeline.process(samples: Float32Array, options: {
501
+ sampleRate: number; // Hz (required for timestamp generation)
502
+ channels: number;
503
+ });
504
+
505
+ // Mode 2: Time-based with explicit timestamps
506
+ await pipeline.process(samples: Float32Array, timestamps: Float32Array, options: {
507
+ channels: number; // sampleRate not needed
508
+ });
509
+
510
+ // Mode 3: Auto-sequential (no sample rate, generates [0,1,2,3,...])
511
+ await pipeline.process(samples: Float32Array, options: {
512
+ channels: number; // sampleRate omitted
513
+ });
514
+ ```
515
+
516
+ **See [Time-Series Guide](https://github.com/A-KGeorge/dsp_ts_redis/blob/main/docs/time-series-guide.md) for detailed examples.**
517
+
518
+ ### Available Filters
519
+
520
+ #### Currently Implemented
521
+
522
+ ##### Moving Average Filter
523
+
524
+ ```typescript
525
+ // Batch Mode - Stateless, computes average over entire input
526
+ pipeline.MovingAverage({ mode: "batch" });
527
+
528
+ // Moving Mode - Stateful, sliding window with continuity
529
+ pipeline.MovingAverage({ mode: "moving", windowSize: number });
530
+
531
+ // NEW: Time-Based Window
532
+ pipeline.MovingAverage({ mode: "moving", windowDuration: number }); // milliseconds
533
+ ```
534
+
535
+ Implements a simple moving average (SMA) filter with two modes:
536
+
537
+ **Modes:**
538
+
539
+ | Mode | Description | State | Output | Use Case |
540
+ | ---------- | ---------------------------------- | --------- | ------------------------------------------- | ----------------------------------- |
541
+ | `"batch"` | Computes average over entire input | Stateless | All samples have same value (mean of input) | Quality metrics, summary statistics |
542
+ | `"moving"` | Sliding window across samples | Stateful | Each sample smoothed by window | Real-time smoothing, trend analysis |
543
+
544
+ **Parameters:**
545
+
546
+ - `mode`: `"batch"` or `"moving"` - determines computation strategy
547
+ - `windowSize`: Number of samples to average over **(optional for moving mode)**
548
+ - `windowDuration`: Time-based window in milliseconds **(optional for moving mode)**
549
+ - At least one of `windowSize` or `windowDuration` must be specified for moving mode
550
+
551
+ **Features:**
552
+
553
+ - **Batch mode**: O(n) computation, no state between calls
554
+ - **Moving mode**: O(1) per sample with circular buffer and running sum
555
+ - Per-channel state for multi-channel processing
556
+ - Full state serialization to Redis
557
+
558
+ **Example:**
559
+
560
+ ```typescript
561
+ // Batch mode: Average of [1,2,3,4,5] = 3
562
+ const batch = createDspPipeline().MovingAverage({ mode: "batch" });
563
+ const result1 = await batch.process(new Float32Array([1, 2, 3, 4, 5]));
564
+ // result1 = [3, 3, 3, 3, 3]
565
+
566
+ // Moving mode: Window size 3
567
+ const moving = createDspPipeline().MovingAverage({
568
+ mode: "moving",
569
+ windowSize: 3,
570
+ });
571
+ const result2 = await moving.process(new Float32Array([1, 2, 3, 4, 5]));
572
+ // result2 = [1, 1.5, 2, 3, 4] (sliding window averages)
573
+ ```
574
+
575
+ **Use cases:**
576
+
577
+ - **Batch**: Quality control metrics, batch statistics, data summarization
578
+ - **Moving**: Signal smoothing, noise reduction, trend analysis, low-pass filtering
579
+
580
+ ##### RMS (Root Mean Square) Filter
581
+
582
+ ```typescript
583
+ // Batch Mode - Stateless, computes RMS over entire input
584
+ pipeline.Rms({ mode: "batch" });
585
+
586
+ // Moving Mode - Stateful, sliding window with continuity
587
+ pipeline.Rms({ mode: "moving", windowSize: number });
588
+ ```
589
+
590
+ Implements an efficient RMS filter with two modes:
591
+
592
+ **Modes:**
593
+
594
+ | Mode | Description | State | Output | Use Case |
595
+ | ---------- | ------------------------------ | --------- | ------------------------------------------ | ----------------------------------- |
596
+ | `"batch"` | Computes RMS over entire input | Stateless | All samples have same value (RMS of input) | Power measurement, batch analysis |
597
+ | `"moving"` | Sliding window across samples | Stateful | Each sample is RMS of window | Envelope detection, real-time power |
598
+
599
+ **Parameters:**
600
+
601
+ - `mode`: `"batch"` or `"moving"` - determines computation strategy
602
+ - `windowSize`: Number of samples to calculate RMS over **(required for moving mode only)**
603
+
604
+ **Features:**
605
+
606
+ - **Batch mode**: O(n) computation, no state between calls
607
+ - **Moving mode**: O(1) per sample with circular buffer and running sum of squares
608
+ - Per-channel state for multi-channel processing
609
+ - Full state serialization to Redis
610
+ - Always positive output (magnitude-based)
611
+
612
+ **Example:**
613
+
614
+ ```typescript
615
+ // Batch mode: RMS of [1, -2, 3, -4, 5] = sqrt((1² + 4 + 9 + 16 + 25)/5) = 3.31
616
+ const batch = createDspPipeline().Rms({ mode: "batch" });
617
+ const result1 = await batch.process(new Float32Array([1, -2, 3, -4, 5]));
618
+ // result1 = [3.31, 3.31, 3.31, 3.31, 3.31]
619
+
620
+ // Moving mode: Window size 3
621
+ const moving = createDspPipeline().Rms({ mode: "moving", windowSize: 3 });
622
+ const result2 = await moving.process(new Float32Array([1, -2, 3, -4, 5]));
623
+ // result2 = [1.0, 1.58, 2.16, 3.11, 4.08] - sliding window RMS
624
+ ```
625
+
626
+ **Use cases:**
627
+
628
+ - **Batch**: Overall signal power, quality metrics, batch statistics
629
+ - **Moving**: Signal envelope detection, amplitude tracking, power measurement, feature extraction
630
+
631
+ ##### Mean Absolute Value (MAV) Filter
632
+
633
+ ```typescript
634
+ // Batch Mode - Stateless, computes MAV over entire input
635
+ pipeline.MeanAbsoluteValue({ mode: "batch" });
636
+
637
+ // Moving Mode - Stateful, sliding window with continuity
638
+ pipeline.MeanAbsoluteValue({ mode: "moving", windowSize: number });
639
+ ```
640
+
641
+ Implements an efficient Mean Absolute Value filter with two modes - commonly used in EMG signal analysis for muscle activity quantification.
642
+
643
+ **Modes:**
644
+
645
+ | Mode | Description | State | Output | Use Case |
646
+ | ---------- | ------------------------------ | --------- | ------------------------------------------ | ---------------------------------------- |
647
+ | `"batch"` | Computes MAV over entire input | Stateless | All samples have same value (MAV of input) | Global activity level, batch analysis |
648
+ | `"moving"` | Sliding window across samples | Stateful | Each sample is MAV of window | Real-time activity detection, transients |
649
+
650
+ **Parameters:**
651
+
652
+ - `mode`: `"batch"` or `"moving"` - determines computation strategy
653
+ - `windowSize`: Number of samples to calculate MAV over **(required for moving mode only)**
654
+
655
+ **Features:**
656
+
657
+ - **Batch mode**: O(n) computation, no state between calls
658
+ - **Moving mode**: O(1) per sample with circular buffer and running sum of absolute values
659
+ - Per-channel state for multi-channel EMG processing
660
+ - Full state serialization to Redis
661
+ - Always non-negative output
662
+ - Scale-invariant: MAV(k·x) = k·MAV(x)
663
+
664
+ **Example:**
665
+
666
+ ```typescript
667
+ // Batch mode: MAV of [1, -2, 3, -4, 5] = (|1| + |-2| + |3| + |-4| + |5|)/5 = 3.0
668
+ const batch = createDspPipeline().MeanAbsoluteValue({ mode: "batch" });
669
+ const result1 = await batch.process(new Float32Array([1, -2, 3, -4, 5]));
670
+ // result1 = [3.0, 3.0, 3.0, 3.0, 3.0]
671
+
672
+ // Moving mode: Window size 3
673
+ const moving = createDspPipeline().MeanAbsoluteValue({
674
+ mode: "moving",
675
+ windowSize: 3,
676
+ });
677
+ const result2 = await moving.process(new Float32Array([1, -2, 3, -4, 5]));
678
+ // result2 = [1.0, 1.5, 2.0, 3.0, 4.0] - sliding window MAV
679
+ ```
680
+
681
+ **Use cases:**
682
+
683
+ - **EMG Analysis**: Muscle activity quantification, fatigue detection, gesture recognition
684
+ - **Vibration Monitoring**: Equipment health monitoring, anomaly detection
685
+ - **Audio Processing**: Envelope detection, dynamic range analysis
686
+ - **Batch**: Overall signal activity level, quality metrics
687
+ - **Moving**: Real-time transient detection, activity onset/offset detection, prosthetic control
688
+
689
+ **Mathematical Properties:**
690
+
691
+ - **Non-negative**: MAV(x) ≥ 0 for all signals
692
+ - **Scale-invariant**: MAV(k·x) = k·MAV(x)
693
+ - **Bounded**: MAV(x) ≤ max(|x|) for any window
694
+ - **Reduces to mean**: For all positive signals, MAV = mean
695
+
696
+ ##### Rectify Filter
697
+
698
+ ```typescript
699
+ pipeline.Rectify(params?: { mode?: "full" | "half" });
700
+ ```
701
+
702
+ Implements signal rectification with two modes: full-wave (absolute value) and half-wave (zero out negatives). Stateless operation with no internal buffers.
703
+
704
+ **Parameters:**
705
+
706
+ - `mode` (optional): Rectification mode
707
+ - `"full"` (default): Full-wave rectification (absolute value) - converts all samples to positive
708
+ - `"half"`: Half-wave rectification - positive samples unchanged, negative samples → 0
709
+
710
+ **Features:**
711
+
712
+ - Zero overhead - simple sample-by-sample transformation
713
+ - No internal state/buffers (stateless)
714
+ - Mode is serializable for pipeline persistence
715
+ - Works independently on each sample (no windowing)
716
+
717
+ **Use cases:**
718
+
719
+ - EMG signal pre-processing before envelope detection
720
+ - AC to DC conversion in audio/biosignal processing
721
+ - Preparing signals for RMS or moving average smoothing
722
+ - Feature extraction requiring positive-only values
723
+
724
+ **Examples:**
725
+
726
+ ```typescript
727
+ // Full-wave rectification (default) - converts to absolute value
728
+ const pipeline1 = createDspPipeline();
729
+ pipeline1.Rectify(); // or Rectify({ mode: "full" })
730
+
731
+ const bipolar = new Float32Array([1, -2, 3, -4, 5]);
732
+ const fullWave = await pipeline1.process(bipolar, {
733
+ sampleRate: 1000,
734
+ channels: 1,
735
+ });
736
+ console.log(fullWave); // [1, 2, 3, 4, 5] - all positive
737
+
738
+ // Half-wave rectification - zeros out negatives
739
+ const pipeline2 = createDspPipeline();
740
+ pipeline2.Rectify({ mode: "half" });
741
+
742
+ const halfWave = await pipeline2.process(new Float32Array([1, -2, 3, -4, 5]), {
743
+ sampleRate: 1000,
744
+ channels: 1,
745
+ });
746
+ console.log(halfWave); // [1, 0, 3, 0, 5] - negatives become zero
747
+
748
+ // Common pipeline: Rectify → RMS for EMG envelope
749
+ const emgPipeline = createDspPipeline();
750
+ emgPipeline
751
+ .Rectify({ mode: "full" }) // Convert to magnitude
752
+ .Rms({ windowSize: 50 }); // Calculate envelope
753
+ ```
754
+
755
+ ##### Variance Filter
756
+
757
+ ```typescript
758
+ pipeline.Variance(params: { mode: "batch" | "moving"; windowSize?: number });
759
+ ```
760
+
761
+ Implements variance calculation to measure data spread and variability. Supports both stateless batch variance and stateful moving variance with a sliding window.
762
+
763
+ **Parameters:**
764
+
765
+ - `mode`: Variance calculation mode
766
+ - `"batch"`: Stateless - computes variance over entire batch, all output samples contain the same value
767
+ - `"moving"`: Stateful - computes variance over a sliding window, maintains state across process() calls
768
+ - `windowSize`: Required for `"moving"` mode - size of the sliding window
769
+
770
+ **Features:**
771
+
772
+ - **Batch mode**: O(1) space complexity, processes entire batch in two passes
773
+ - **Moving mode**: O(1) per-sample computation using circular buffer with running sums
774
+ - Maintains running sum and running sum of squares for efficient calculation
775
+ - Per-channel state for multi-channel processing
776
+ - Full state serialization to Redis including buffer contents and running values
777
+ - Variance is always non-negative (uses max(0, calculated) to handle floating-point errors)
778
+
779
+ **Mathematical Note:**
780
+
781
+ Variance is calculated as: `Var(X) = E[X²] - (E[X])²`
782
+
783
+ Where:
784
+
785
+ - `E[X]` is the mean (average) of values
786
+ - `E[X²]` is the mean of squared values
787
+
788
+ **Use cases:**
789
+
790
+ - Signal quality monitoring (detect signal stability)
791
+ - Anomaly detection (identify unusual variability)
792
+ - Feature extraction for machine learning (EMG, EEG analysis)
793
+ - Real-time variability tracking in biosignals
794
+ - Data consistency validation in sensor streams
795
+
796
+ **Examples:**
797
+
798
+ ```typescript
799
+ // Batch variance - stateless, entire batch → single variance value
800
+ const pipeline1 = createDspPipeline();
801
+ pipeline1.Variance({ mode: "batch" });
802
+
803
+ const data = new Float32Array([1, 2, 3, 4, 5]);
804
+ const output1 = await pipeline1.process(data, {
805
+ sampleRate: 1000,
806
+ channels: 1,
807
+ });
808
+ console.log(output1); // [2, 2, 2, 2, 2] - all values are the batch variance
809
+
810
+ // Moving variance - stateful, sliding window
811
+ const pipeline2 = createDspPipeline();
812
+ pipeline2.Variance({ mode: "moving", windowSize: 3 });
813
+
814
+ const output2 = await pipeline2.process(data, {
815
+ sampleRate: 1000,
816
+ channels: 1,
817
+ });
818
+ console.log(output2);
819
+ // [0, 0.25, 0.67, 0.67, 0.67] - variance evolves as window slides
820
+ // Window: [1] → [1,2] → [1,2,3] → [2,3,4] → [3,4,5]
821
+
822
+ // EMG variability monitoring pipeline
823
+ const emgPipeline = createDspPipeline()
824
+ .Rectify({ mode: "full" }) // Convert to magnitude
825
+ .Variance({ mode: "moving", windowSize: 100 }); // Track variability
826
+
827
+ // Multi-channel signal quality monitoring
828
+ const qualityPipeline = createDspPipeline().Variance({ mode: "batch" });
829
+
830
+ const fourChannelData = new Float32Array(4000); // 1000 samples × 4 channels
831
+ const variances = await qualityPipeline.process(fourChannelData, {
832
+ sampleRate: 2000,
833
+ channels: 4,
834
+ });
835
+ // Each channel gets its own variance value
836
+ ```
837
+
838
+ **Batch vs Moving Mode:**
839
+
840
+ | Mode | State | Output | Use Case |
841
+ | -------- | --------- | ----------------------------- | ------------------------------ |
842
+ | `batch` | Stateless | All samples = single variance | Per-chunk quality assessment |
843
+ | `moving` | Stateful | Variance per sample (sliding) | Real-time variability tracking |
844
+
845
+ **Performance:**
846
+
847
+ - Batch mode: Two-pass algorithm (sum calculation, then variance), O(n) time
848
+ - Moving mode: Single-pass with circular buffer, O(1) per sample after warmup
849
+ - State persistence includes full circular buffer + running sums (can be large for big windows)
850
+
851
+ ##### Z-Score Normalization Filter
852
+
853
+ ```typescript
854
+ pipeline.ZScoreNormalize(params: {
855
+ mode: "batch" | "moving";
856
+ windowSize?: number;
857
+ epsilon?: number;
858
+ });
859
+ ```
860
+
861
+ Implements Z-Score normalization to standardize data to have mean 0 and standard deviation 1. Supports both stateless batch normalization and stateful moving normalization with a sliding window.
862
+
863
+ **Z-Score Formula:** `(Value - Mean) / StandardDeviation`
864
+
865
+ **Parameters:**
866
+
867
+ - `mode`: Normalization calculation mode
868
+ - `"batch"`: Stateless - computes mean and stddev over entire batch, normalizes all samples
869
+ - `"moving"`: Stateful - computes mean and stddev over sliding window, maintains state across process() calls
870
+ - `windowSize`: Required for `"moving"` mode - size of the sliding window
871
+ - `epsilon`: Small value to prevent division by zero when standard deviation is 0 (default: `1e-6`)
872
+
873
+ **Features:**
874
+
875
+ - **Batch mode**: Normalizes entire dataset to mean=0, stddev=1 using global statistics
876
+ - **Moving mode**: Adaptive normalization using local window statistics
877
+ - Maintains running sum and running sum of squares for O(1) mean/stddev calculation
878
+ - Per-channel state for multi-channel processing
879
+ - Full state serialization including buffer contents and running values
880
+ - Epsilon handling prevents NaN when processing constant signals
881
+
882
+ **Use cases:**
883
+
884
+ - Machine learning preprocessing (feature standardization)
885
+ - Anomaly detection (outlier identification using ±3σ thresholds)
886
+ - Neural network input normalization
887
+ - EEG/EMG signal standardization for multi-channel processing
888
+ - Real-time data stream normalization with adaptive statistics
889
+ - Removing baseline drift and amplitude variations
890
+
891
+ **Examples:**
892
+
893
+ ```typescript
894
+ // Batch normalization - standardize entire dataset
895
+ const pipeline1 = createDspPipeline();
896
+ pipeline1.ZScoreNormalize({ mode: "batch" });
897
+
898
+ const data = new Float32Array([10, 20, 30, 40, 50]);
899
+ const output1 = await pipeline1.process(data, {
900
+ sampleRate: 1000,
901
+ channels: 1,
902
+ });
903
+ // All samples normalized to mean=0, stddev=1
904
+ // Output: [-1.414, -0.707, 0, 0.707, 1.414] (approximately)
905
+
906
+ // Moving normalization - adaptive standardization
907
+ const pipeline2 = createDspPipeline();
908
+ pipeline2.ZScoreNormalize({ mode: "moving", windowSize: 50 });
909
+
910
+ const streamData = new Float32Array(200); // Continuous stream
911
+ const output2 = await pipeline2.process(streamData, {
912
+ sampleRate: 1000,
913
+ channels: 1,
914
+ });
915
+ // Each sample normalized using local window statistics
916
+ // Adapts to changes in mean and variance over time
917
+
918
+ // Anomaly detection with z-score thresholds
919
+ const pipeline3 = createDspPipeline();
920
+ pipeline3.ZScoreNormalize({ mode: "moving", windowSize: 100 });
921
+
922
+ const sensorData = new Float32Array(500);
923
+ const zScores = await pipeline3.process(sensorData, {
924
+ sampleRate: 100,
925
+ channels: 1,
926
+ });
927
+
928
+ // Detect outliers (z-score > ±3 indicates anomaly)
929
+ const anomalies = [];
930
+ for (let i = 0; i < zScores.length; i++) {
931
+ if (Math.abs(zScores[i]) > 3.0) {
932
+ anomalies.push({ index: i, zScore: zScores[i] });
933
+ }
934
+ }
935
+
936
+ // Multi-channel EEG normalization
937
+ const eegPipeline = createDspPipeline();
938
+ eegPipeline.ZScoreNormalize({ mode: "moving", windowSize: 128 });
939
+
940
+ const fourChannelEEG = new Float32Array(2048); // 512 samples × 4 channels
941
+ const normalizedEEG = await eegPipeline.process(fourChannelEEG, {
942
+ sampleRate: 256,
943
+ channels: 4,
944
+ });
945
+ // Each EEG channel independently normalized to mean=0, stddev=1
946
+
947
+ // Custom epsilon for near-constant signals
948
+ const pipeline4 = createDspPipeline();
949
+ pipeline4.ZScoreNormalize({ mode: "batch", epsilon: 0.1 });
950
+
951
+ const nearConstant = new Float32Array([5.0, 5.001, 4.999, 5.0]);
952
+ const output4 = await pipeline4.process(nearConstant, {
953
+ sampleRate: 1000,
954
+ channels: 1,
955
+ });
956
+ // When stddev < epsilon, output is 0 (prevents division by tiny numbers)
957
+ ```
958
+
959
+ **Batch vs Moving Mode:**
960
+
961
+ | Mode | State | Output | Use Case |
962
+ | -------- | --------- | ----------------------------------------------- | --------------------------------------------------- |
963
+ | `batch` | Stateless | All samples normalized using global mean/stddev | ML preprocessing, dataset standardization |
964
+ | `moving` | Stateful | Each sample normalized using local window stats | Real-time anomaly detection, adaptive normalization |
965
+
966
+ **Performance:**
967
+
968
+ - Batch mode: Two-pass algorithm (calculate mean, then stddev, then normalize), O(n) time
969
+ - Moving mode: Single-pass with circular buffer, O(1) per sample after warmup
970
+ - Anomaly detection: O(1) threshold comparison after normalization
971
+ - State persistence includes full circular buffer + running sums
972
+
973
+ **Mathematical Properties:**
974
+
975
+ - Normalized data has mean = 0 (centered)
976
+ - Normalized data has standard deviation = 1 (scaled)
977
+ - Z-scores > ±3 represent outliers (>99.7% of data within ±3σ in normal distribution)
978
+ - Preserves relative distances and relationships in the data
979
+ - Linear transformation (reversible if original mean/stddev are known)
980
+
981
+ ##### Waveform Length (WL) Filter
982
+
983
+ ```typescript
984
+ pipeline.WaveformLength({ windowSize: number });
985
+ ```
986
+
987
+ Implements waveform length calculation to measure the total path length traveled by a signal over a sliding window. WL is the sum of absolute differences between consecutive samples, commonly used in EMG signal analysis.
988
+
989
+ **Parameters:**
990
+
991
+ - `windowSize`: Number of samples for the sliding window
992
+
993
+ **Features:**
994
+
995
+ - O(1) per-sample computation using circular buffer with running sum of differences
996
+ - Tracks previous sample to calculate absolute difference
997
+ - Per-channel state for multi-channel EMG processing
998
+ - Full state serialization including buffer and previous sample value
999
+ - Always non-negative output
1000
+ - Sensitive to signal complexity and frequency content
1001
+
1002
+ **Mathematical Definition:**
1003
+
1004
+ For window of samples `[x₁, x₂, ..., xₙ]`:
1005
+ `WL = Σ|xᵢ₊₁ - xᵢ|` for i = 1 to n-1
1006
+
1007
+ **Use cases:**
1008
+
1009
+ - EMG signal analysis and muscle activity quantification
1010
+ - Detecting signal complexity and variability
1011
+ - Gesture recognition and prosthetic control
1012
+ - Fatigue detection (WL decreases with muscle fatigue)
1013
+ - Feature extraction for classification algorithms
1014
+ - Vibration monitoring and equipment diagnostics
1015
+
1016
+ **Example:**
1017
+
1018
+ ```typescript
1019
+ // Basic waveform length computation
1020
+ const pipeline = createDspPipeline();
1021
+ pipeline.WaveformLength({ windowSize: 100 });
1022
+
1023
+ const signal = new Float32Array([1, 3, 2, 5, 4, 6]);
1024
+ // Differences: |3-1|=2, |2-3|=1, |5-2|=3, |4-5|=1, |6-4|=2
1025
+ // Cumulative WL: 0, 2, 3, 6, 7, 9
1026
+ const result = await pipeline.process(signal, {
1027
+ sampleRate: 1000,
1028
+ channels: 1,
1029
+ });
1030
+ // result = [0, 2, 3, 6, 7, 9]
1031
+
1032
+ // EMG feature extraction pipeline
1033
+ const emgPipeline = createDspPipeline();
1034
+ emgPipeline
1035
+ .Rectify({ mode: "full" }) // Convert to magnitude
1036
+ .WaveformLength({ windowSize: 250 }); // 250ms window at 1kHz
1037
+
1038
+ // Multi-channel muscle monitoring
1039
+ const multiChannelEMG = new Float32Array(4000); // 1000 samples × 4 channels
1040
+ const wlFeatures = await emgPipeline.process(multiChannelEMG, {
1041
+ sampleRate: 1000,
1042
+ channels: 4,
1043
+ });
1044
+ // Each muscle gets independent WL calculation
1045
+ ```
1046
+
1047
+ **Comparison with other features:**
1048
+
1049
+ | Feature | Measures | Sensitivity | Use Case |
1050
+ | ------- | --------------------------- | ------------------ | ---------------------- |
1051
+ | WL | Total path length | High to complexity | Complexity/variability |
1052
+ | MAV | Average magnitude | Amplitude level | Activity level |
1053
+ | RMS | Root mean square | Power/energy | Signal strength |
1054
+ | SSC | Slope direction changes | Frequency content | Oscillation rate |
1055
+ | WAMP | Amplitude threshold crosses | Burst detection | Transient events |
1056
+
1057
+ **Mathematical Properties:**
1058
+
1059
+ - **Non-negative**: WL ≥ 0 for all signals
1060
+ - **Monotonic**: WL increases with window filling
1061
+ - **Frequency-dependent**: Higher frequency → higher WL for same amplitude
1062
+ - **Scale-variant**: WL(k·x) = k·WL(x) for constant k
1063
+
1064
+ ##### Slope Sign Change (SSC) Filter
1065
+
1066
+ ```typescript
1067
+ pipeline.SlopeSignChange({ windowSize: number; threshold?: number });
1068
+ ```
1069
+
1070
+ Implements slope sign change counting to measure frequency content by detecting how many times the signal changes direction (slope changes from positive to negative or vice versa). Commonly used in EMG analysis.
1071
+
1072
+ **Parameters:**
1073
+
1074
+ - `windowSize`: Number of samples for the sliding window
1075
+ - `threshold`: Minimum absolute difference to count as significant (default: 0)
1076
+
1077
+ **Features:**
1078
+
1079
+ - Counts direction changes (increasing→decreasing or decreasing→increasing)
1080
+ - Threshold filtering to ignore noise and minor fluctuations
1081
+ - Requires 2 previous samples for slope calculation
1082
+ - O(1) per-sample computation using circular buffer
1083
+ - Per-channel state for multi-channel processing
1084
+ - Full state serialization including filter state (previous 2 samples, init count)
1085
+
1086
+ **Mathematical Definition:**
1087
+
1088
+ A slope sign change occurs at sample `xᵢ` if:
1089
+ `[(xᵢ - xᵢ₋₁) × (xᵢ - xᵢ₊₁)] ≥ threshold²`
1090
+
1091
+ Where `xᵢ₋₁` and `xᵢ₊₁` are the previous and next samples.
1092
+
1093
+ **Use cases:**
1094
+
1095
+ - EMG frequency content analysis
1096
+ - Detecting oscillations and vibrations
1097
+ - Gesture recognition based on movement patterns
1098
+ - Signal complexity measurement
1099
+ - Muscle contraction rate estimation
1100
+ - Feature extraction for pattern recognition
1101
+
1102
+ **Example:**
1103
+
1104
+ ```typescript
1105
+ // Basic SSC with zero threshold
1106
+ const pipeline = createDspPipeline();
1107
+ pipeline.SlopeSignChange({ windowSize: 100, threshold: 0 });
1108
+
1109
+ const signal = new Float32Array([1, 3, 2, 4, 3, 5]);
1110
+ // Slopes: + - + - +
1111
+ // Sign changes at indices: 2, 3, 4, 5
1112
+ const result = await pipeline.process(signal, {
1113
+ sampleRate: 1000,
1114
+ channels: 1,
1115
+ });
1116
+ // result = [0, 0, 1, 2, 3, 4]
1117
+
1118
+ // SSC with noise threshold
1119
+ const filteredPipeline = createDspPipeline();
1120
+ filteredPipeline.SlopeSignChange({ windowSize: 200, threshold: 0.1 });
1121
+
1122
+ const noisySignal = new Float32Array(500);
1123
+ // Only counts sign changes where |difference| > 0.1
1124
+ const sscCount = await filteredPipeline.process(noisySignal, {
1125
+ sampleRate: 1000,
1126
+ channels: 1,
1127
+ });
1128
+
1129
+ // Multi-channel EMG analysis
1130
+ const emgPipeline = createDspPipeline();
1131
+ emgPipeline
1132
+ .Rectify({ mode: "full" })
1133
+ .SlopeSignChange({ windowSize: 250, threshold: 0.05 });
1134
+
1135
+ const fourChannelEMG = new Float32Array(4000);
1136
+ const sscFeatures = await emgPipeline.process(fourChannelEMG, {
1137
+ sampleRate: 1000,
1138
+ channels: 4,
1139
+ });
1140
+ ```
1141
+
1142
+ **Interpretation:**
1143
+
1144
+ - **Low SSC**: Smooth, slow-changing signal (low frequency)
1145
+ - **High SSC**: Rapidly oscillating signal (high frequency)
1146
+ - **Zero SSC**: Monotonic signal (constantly increasing or decreasing)
1147
+ - **Threshold effect**: Higher threshold → fewer counted changes (filters noise)
1148
+
1149
+ **Mathematical Properties:**
1150
+
1151
+ - **Integer-valued**: SSC is always a whole number (count)
1152
+ - **Non-negative**: SSC ≥ 0
1153
+ - **Bounded**: Max SSC = window_size - 2 (alternating signal)
1154
+ - **Frequency proxy**: Approximately proportional to signal frequency
1155
+
1156
+ ##### Willison Amplitude (WAMP) Filter
1157
+
1158
+ ```typescript
1159
+ pipeline.WillisonAmplitude({ windowSize: number; threshold?: number });
1160
+ ```
1161
+
1162
+ Implements Willison Amplitude calculation to count the number of times the absolute difference between consecutive samples exceeds a threshold. Useful for detecting burst activity and transient events in EMG signals.
1163
+
1164
+ **Parameters:**
1165
+
1166
+ - `windowSize`: Number of samples for the sliding window
1167
+ - `threshold`: Minimum absolute difference to count as significant (default: 0)
1168
+
1169
+ **Features:**
1170
+
1171
+ - Counts amplitude changes exceeding threshold
1172
+ - Tracks previous sample for difference calculation
1173
+ - O(1) per-sample computation using circular buffer
1174
+ - Per-channel state for multi-channel processing
1175
+ - Full state serialization including buffer and previous sample
1176
+ - Sensitive to amplitude variations and burst activity
1177
+
1178
+ **Mathematical Definition:**
1179
+
1180
+ WAMP counts samples where:
1181
+ `|xᵢ - xᵢ₋₁| > threshold`
1182
+
1183
+ Where `xᵢ` is the current sample and `xᵢ₋₁` is the previous sample.
1184
+
1185
+ **Use cases:**
1186
+
1187
+ - EMG burst detection and muscle activation counting
1188
+ - Transient event detection in sensor data
1189
+ - Activity level quantification
1190
+ - Feature extraction for gesture recognition
1191
+ - Equipment vibration monitoring
1192
+ - Signal quality assessment (detect dropouts/spikes)
1193
+
1194
+ **Example:**
1195
+
1196
+ ```typescript
1197
+ // Basic WAMP with threshold
1198
+ const pipeline = createDspPipeline();
1199
+ pipeline.WillisonAmplitude({ windowSize: 100, threshold: 1.0 });
1200
+
1201
+ const signal = new Float32Array([0, 0.5, 2.5, 2.6, 1.0, 3.5]);
1202
+ // Diffs: 0.5, 2.0, 0.1, -1.6, 2.5
1203
+ // Exceeds: no, yes, no, yes, yes
1204
+ const result = await pipeline.process(signal, {
1205
+ sampleRate: 1000,
1206
+ channels: 1,
1207
+ });
1208
+ // result = [0, 0, 1, 1, 2, 3]
1209
+
1210
+ // EMG burst detection
1211
+ const burstPipeline = createDspPipeline();
1212
+ burstPipeline
1213
+ .Rectify({ mode: "full" })
1214
+ .WillisonAmplitude({ windowSize: 200, threshold: 0.1 });
1215
+
1216
+ const emgData = new Float32Array(1000);
1217
+ const burstCount = await burstPipeline.process(emgData, {
1218
+ sampleRate: 1000,
1219
+ channels: 1,
1220
+ });
1221
+ // High WAMP values indicate burst activity
1222
+
1223
+ // Multi-channel activity monitoring
1224
+ const activityPipeline = createDspPipeline();
1225
+ activityPipeline.WillisonAmplitude({ windowSize: 250, threshold: 0.05 });
1226
+
1227
+ const multiChannelData = new Float32Array(4000); // 4 channels
1228
+ const activityLevel = await activityPipeline.process(multiChannelData, {
1229
+ sampleRate: 1000,
1230
+ channels: 4,
1231
+ });
1232
+ // Each channel independently tracks activity bursts
1233
+ ```
1234
+
1235
+ **Interpretation:**
1236
+
1237
+ - **Low WAMP**: Smooth signal with gradual changes
1238
+ - **High WAMP**: Signal with frequent amplitude variations or bursts
1239
+ - **Zero WAMP**: Constant signal or all changes below threshold
1240
+ - **Threshold effect**: Higher threshold → fewer counted events
1241
+
1242
+ **Comparison with SSC:**
1243
+
1244
+ | Feature | Measures | Sensitivity | Best For |
1245
+ | ------- | ----------------- | ---------------------- | --------------------- |
1246
+ | WAMP | Amplitude changes | Large amplitude shifts | Burst detection |
1247
+ | SSC | Direction changes | Frequency content | Oscillation counting |
1248
+ | Both | Signal activity | Different aspects | Combined EMG features |
1249
+
1250
+ **Mathematical Properties:**
1251
+
1252
+ - **Integer-valued**: WAMP is a count (whole number)
1253
+ - **Non-negative**: WAMP ≥ 0
1254
+ - **Bounded**: Max WAMP = window_size - 1 (all samples exceed threshold)
1255
+ - **Threshold-dependent**: WAMP decreases as threshold increases
1256
+
1257
+ #### 🚧 Coming Very Soon
1258
+
1259
+ **Resampling Operations** (Expected in next few days):
1260
+
1261
+ - **`Decimate`**: Downsample by integer factor M with anti-aliasing filter
1262
+ - **`Interpolate`**: Upsample by integer factor L with anti-imaging filter
1263
+ - **`Resample`**: Rational resampling (L/M) for arbitrary rate conversion
1264
+ - All implemented with efficient polyphase FIR filtering in C++
1265
+
1266
+ **Other Planned Features:**
1267
+
1268
+ - **Transform Domain**: STFT, Hilbert transform, wavelet transforms
1269
+ - **Feature Extraction**: Zero-crossing rate, peak detection, autocorrelation
1270
+
1271
+ See the [project roadmap](https://github.com/A-KGeorge/dsp_ts_redis/blob/main/ROADMAP.md) for more details.
1272
+
1273
+ ---
1274
+
1275
+ ## 🔧 Advanced Features
1276
+
1277
+ For production deployments, the library provides comprehensive observability and monitoring capabilities:
1278
+
1279
+ ### Available Features
1280
+
1281
+ - **Pipeline Callbacks** - Monitor performance, errors, and samples with batched or individual callbacks
1282
+ - **Topic-Based Logging** - Kafka-style hierarchical filtering for selective log subscription
1283
+ - **Topic Router** - Fan-out routing to multiple backends (PagerDuty, Prometheus, Loki, etc.)
1284
+ - **Priority-Based Routing** - 10-level priority system for fine-grained log filtering
1285
+ - **`.tap()` Debugging** - Inspect intermediate pipeline results without breaking the flow
1286
+
1287
+ ### Quick Example
1288
+
1289
+ ```typescript
1290
+ import { createDspPipeline, createTopicRouter } from "dspx";
1291
+
1292
+ // Production-grade routing to multiple backends
1293
+ const router = createTopicRouter()
1294
+ .errors(async (log) => await pagerDuty.alert(log))
1295
+ .performance(async (log) => await prometheus.record(log))
1296
+ .debug(async (log) => await loki.send(log))
1297
+ .build();
1298
+
1299
+ const pipeline = createDspPipeline()
1300
+ .pipeline({ onLogBatch: (logs) => router.routeBatch(logs) })
1301
+ .MovingAverage({ windowSize: 10 })
1302
+ .Rms({ windowSize: 5 });
1303
+ ```
1304
+
1305
+ **📚 [Full Advanced Features Documentation](https://github.com/A-KGeorge/dsp_ts_redis/blob/main/docs/advanced.md)**
1306
+
1307
+ Key highlights:
1308
+
1309
+ - Batched callbacks: **3.2M samples/sec** (production-safe, non-blocking)
1310
+ - Individual callbacks: **6.1M samples/sec** (development only, blocks event loop)
1311
+ - Native processing: **22M samples/sec** (no callbacks)
1312
+
1313
+ ---
1314
+
1315
+ ### Core Methods
1316
+
1317
+ #### `process(input, options)`
1318
+
1319
+ Process data in-place (modifies input buffer for performance):
1320
+
1321
+ ```typescript
1322
+ const input = new Float32Array([1, 2, 3, 4, 5]);
1323
+ const output = await pipeline.process(input, {
1324
+ sampleRate: 2000,
1325
+ channels: 1,
1326
+ });
1327
+ // input === output (same reference)
1328
+ ```
1329
+
1330
+ #### `processCopy(input, options)`
1331
+
1332
+ Process a copy of the data (preserves original):
1333
+
1334
+ ```typescript
1335
+ const input = new Float32Array([1, 2, 3, 4, 5]);
1336
+ const output = await pipeline.processCopy(input, {
1337
+ sampleRate: 2000,
1338
+ channels: 1,
1339
+ });
1340
+ // input !== output (different references)
1341
+ ```
1342
+
1343
+ #### `saveState()`
1344
+
1345
+ Serialize the current pipeline state to JSON:
1346
+
1347
+ ```typescript
1348
+ const stateJson = await pipeline.saveState();
1349
+ // Returns: JSON string with all filter states
1350
+ await redis.set("dsp:state:key", stateJson);
1351
+ ```
1352
+
1353
+ #### `loadState(stateJson)`
1354
+
1355
+ Deserialize and restore pipeline state from JSON:
1356
+
1357
+ ```typescript
1358
+ const stateJson = await redis.get("dsp:state:key");
1359
+ if (stateJson) {
1360
+ await pipeline.loadState(stateJson);
1361
+ }
1362
+ ```
1363
+
1364
+ #### `clearState()`
1365
+
1366
+ Reset all filter states to initial values:
1367
+
1368
+ ```typescript
1369
+ pipeline.clearState();
1370
+ // All circular buffers cleared, running sums reset
1371
+ ```
1372
+
1373
+ #### `listState()`
1374
+
1375
+ Get a lightweight summary of the pipeline configuration (without full buffer data):
1376
+
1377
+ ```typescript
1378
+ const pipeline = createDspPipeline()
1379
+ .MovingAverage({ windowSize: 100 })
1380
+ .Rectify({ mode: "full" })
1381
+ .Rms({ windowSize: 50 });
1382
+
1383
+ // After processing some data
1384
+ await pipeline.process(input, { sampleRate: 1000, channels: 1 });
1385
+
1386
+ const summary = pipeline.listState();
1387
+ console.log(summary);
1388
+ // {
1389
+ // stageCount: 3,
1390
+ // timestamp: 1761234567,
1391
+ // stages: [
1392
+ // {
1393
+ // index: 0,
1394
+ // type: 'movingAverage',
1395
+ // windowSize: 100,
1396
+ // numChannels: 1,
1397
+ // bufferSize: 100,
1398
+ // channelCount: 1
1399
+ // },
1400
+ // {
1401
+ // index: 1,
1402
+ // type: 'rectify',
1403
+ // mode: 'full'
1404
+ // },
1405
+ // {
1406
+ // index: 2,
1407
+ // type: 'rms',
1408
+ // windowSize: 50,
1409
+ // numChannels: 1,
1410
+ // bufferSize: 50,
1411
+ // channelCount: 1
1412
+ // }
1413
+ // ]
1414
+ // }
1415
+ ```
1416
+
1417
+ **Use Cases:**
1418
+
1419
+ - **Monitoring dashboards**: Expose pipeline configuration via HTTP endpoint
1420
+ - **Health checks**: Verify pipeline structure and configuration
1421
+ - **Debugging**: Quick inspection without parsing full state JSON
1422
+ - **Logging**: Log pipeline configuration changes
1423
+ - **Size efficiency**: ~17-80% smaller than `saveState()` depending on buffer sizes
1424
+
1425
+ **Comparison with `saveState()`:**
1426
+
1427
+ | Method | Use Case | Contains Buffer Data | Size |
1428
+ | ------------- | --------------------------- | -------------------- | ------- |
1429
+ | `listState()` | Monitoring, debugging | No | Smaller |
1430
+ | `saveState()` | Redis persistence, recovery | Yes | Larger |
1431
+
1432
+ ---
1433
+
1434
+ ## 📊 Use Cases
1435
+
1436
+ ### NEW: IoT Sensor Processing with Irregular Timestamps
1437
+
1438
+ ```typescript
1439
+ import { createDspPipeline } from "dspx";
1440
+
1441
+ const pipeline = createDspPipeline();
1442
+ pipeline.MovingAverage({ mode: "moving", windowDuration: 10000 }); // 10 second window
1443
+
1444
+ // Sensor data with network jitter (irregular intervals)
1445
+ const sensorReadings = [
1446
+ { value: 23.5, timestamp: 1698889200000 },
1447
+ { value: 24.1, timestamp: 1698889200150 }, // 150ms later
1448
+ { value: 23.8, timestamp: 1698889200380 }, // 230ms later (jitter!)
1449
+ { value: 24.5, timestamp: 1698889200500 }, // 120ms later
1450
+ ];
1451
+
1452
+ const samples = new Float32Array(sensorReadings.map((r) => r.value));
1453
+ const timestamps = new Float32Array(sensorReadings.map((r) => r.timestamp));
1454
+
1455
+ const smoothed = await pipeline.process(samples, timestamps, { channels: 1 });
1456
+ // Properly handles irregular sampling intervals!
1457
+ ```
1458
+
1459
+ **📚 [More Time-Series Examples →](https://github.com/A-KGeorge/dsp_ts_redis/blob/main/docs/time-series-guide.md#real-world-examples)**
1460
+
1461
+ ### Streaming Data with Crash Recovery
1462
+
1463
+ ```typescript
1464
+ import { createDspPipeline } from "dspx";
1465
+ import { createClient } from "redis";
1466
+
1467
+ const redis = await createClient({ url: "redis://localhost:6379" }).connect();
1468
+ const stateKey = "dsp:stream:sensor01";
1469
+
1470
+ const pipeline = createDspPipeline({
1471
+ redisHost: "localhost",
1472
+ redisPort: 6379,
1473
+ stateKey,
1474
+ });
1475
+
1476
+ pipeline.MovingAverage({ windowSize: 100 });
1477
+
1478
+ // Restore state if processing was interrupted
1479
+ const savedState = await redis.get(stateKey);
1480
+ if (savedState) {
1481
+ await pipeline.loadState(savedState);
1482
+ console.log("Resumed from saved state");
1483
+ }
1484
+
1485
+ // Process streaming chunks
1486
+ for await (const chunk of sensorStream) {
1487
+ const smoothed = await pipeline.process(chunk, {
1488
+ sampleRate: 1000,
1489
+ channels: 1,
1490
+ });
1491
+
1492
+ // Save state after each chunk for crash recovery
1493
+ const state = await pipeline.saveState();
1494
+ await redis.set(stateKey, state);
1495
+
1496
+ await sendToAnalytics(smoothed);
1497
+ }
1498
+ ```
1499
+
1500
+ ### Multi-Channel EMG Processing
1501
+
1502
+ ```typescript
1503
+ import { createDspPipeline } from "dspx";
1504
+
1505
+ // Process 4-channel EMG with rectification + RMS envelope detection
1506
+ const pipeline = createDspPipeline();
1507
+ pipeline
1508
+ .Rectify({ mode: "full" }) // Convert bipolar EMG to magnitude
1509
+ .Rms({ windowSize: 50 }); // Calculate RMS envelope
1510
+
1511
+ // Interleaved 4-channel data
1512
+ const emgData = new Float32Array(4000); // 1000 samples × 4 channels
1513
+
1514
+ const envelope = await pipeline.process(emgData, {
1515
+ sampleRate: 2000,
1516
+ channels: 4,
1517
+ });
1518
+
1519
+ // Each channel maintains independent filter states
1520
+ // Output is smooth envelope tracking muscle activation
1521
+ ```
1522
+
1523
+ ### Distributed Processing Across Workers
1524
+
1525
+ ```typescript
1526
+ // Worker 1 processes first part
1527
+ const worker1 = createDspPipeline({
1528
+ redisHost: "redis.example.com",
1529
+ stateKey: "dsp:session:abc123",
1530
+ });
1531
+ worker1.MovingAverage({ windowSize: 100 });
1532
+
1533
+ await worker1.process(chunk1, { sampleRate: 2000, channels: 1 });
1534
+ const state = await worker1.saveState();
1535
+ await redis.set("dsp:session:abc123", state);
1536
+
1537
+ // Worker 2 continues exactly where Worker 1 left off
1538
+ const worker2 = createDspPipeline({
1539
+ redisHost: "redis.example.com",
1540
+ stateKey: "dsp:session:abc123",
1541
+ });
1542
+ worker2.MovingAverage({ windowSize: 100 });
1543
+
1544
+ const savedState = await redis.get("dsp:session:abc123");
1545
+ await worker2.loadState(savedState);
1546
+ await worker2.process(chunk2, { sampleRate: 2000, channels: 1 });
1547
+ // Processing continues seamlessly with exact buffer state
1548
+ ```
1549
+
1550
+ ---
1551
+
1552
+ ## ⚠️ Important Disclaimers
1553
+
1554
+ ### Not for Clinical Use
1555
+
1556
+ **This software has not been validated for medical diagnosis, treatment, or life-critical applications.**
1557
+
1558
+ - Not FDA/CE cleared or approved
1559
+ - No medical device certification
1560
+ - For research and development only
1561
+ - Consult regulatory experts before clinical deployment
1562
+
1563
+ ### Performance Considerations
1564
+
1565
+ - **Redis overhead**: State save/load involves JSON serialization and network I/O (~1-10ms depending on state size and network latency).
1566
+ - **In-place processing**: Use `process()` instead of `processCopy()` when you don't need to preserve the input buffer.
1567
+ - **Async processing**: Filter processing runs on a background thread via `Napi::AsyncWorker` to avoid blocking the event loop.
1568
+ - **Batch sizes**: Process reasonable chunk sizes (e.g., 512-4096 samples) to balance latency and throughput.
1569
+
1570
+ ---
1571
+
1572
+ ## 🧪 Testing & Development
1573
+
1574
+ ### Building from Source
1575
+
1576
+ ```bash
1577
+ git clone https://github.com/A-KGeorge/dsp_ts_redis.git
1578
+ cd dspx
1579
+ npm install
1580
+ npm run build # Compile C++ bindings with cmake-js
1581
+ ```
1582
+
1583
+ ### Running Examples
1584
+
1585
+ ```bash
1586
+ # Make sure Redis is running
1587
+ redis-server
1588
+
1589
+ # Run the Redis persistence example
1590
+ npx tsx ./src/ts/examples/redis/redis-example.ts
1591
+
1592
+ # Time-series examples (NEW!)
1593
+ npx tsx ./src/ts/examples/timeseries/iot-sensor-example.ts
1594
+ npx tsx ./src/ts/examples/timeseries/redis-streaming-example.ts
1595
+ npx tsx ./src/ts/examples/timeseries/comparison-example.ts
1596
+
1597
+ # Moving Average examples
1598
+ npx tsx ./src/ts/examples/MovingAverage/test-state.ts
1599
+ npx tsx ./src/ts/examples/MovingAverage/test-streaming.ts
1600
+
1601
+ # RMS examples
1602
+ npx tsx ./src/ts/examples/RMS/test-state.ts
1603
+ npx tsx ./src/ts/examples/RMS/test-streaming.ts
1604
+
1605
+ # Rectify examples
1606
+ npx tsx ./src/ts/examples/Rectify/test-state.ts
1607
+ npx tsx ./src/ts/examples/Rectify/test-streaming.ts
1608
+ ```
1609
+
1610
+ ### Running Tests
1611
+
1612
+ ```bash
1613
+ npm test # Run all tests
1614
+ ```
1615
+
1616
+ **Test Coverage:**
1617
+
1618
+ - ✅ **260+ tests** across test suites
1619
+ - ✅ Moving Average (batch & moving modes)
1620
+ - ✅ RMS (batch & moving modes)
1621
+ - ✅ Variance (batch & moving modes)
1622
+ - ✅ Z-Score Normalization (batch & moving modes)
1623
+ - ✅ Mean Absolute Value (batch & moving modes)
1624
+ - ✅ Waveform Length (EMG feature)
1625
+ - ✅ Slope Sign Change (SSC - EMG feature)
1626
+ - ✅ Willison Amplitude (WAMP - EMG feature)
1627
+ - ✅ Rectify (full-wave & half-wave)
1628
+ - ✅ Time-Series Processing (18 tests)
1629
+ - ✅ Pipeline Chaining (17 tests)
1630
+ - ✅ State Management (save/load/clear)
1631
+ - ✅ Redis Persistence (15 tests)
1632
+ - ✅ Multi-Channel Processing
1633
+ - ✅ Topic Router & Logging (54 tests)
1634
+ - ✅ Tap Debugging (8 tests)
1635
+
1636
+ ### Implementation Status
1637
+
1638
+ - ✅ **Moving Average Filter**: Fully implemented with state persistence
1639
+ - ✅ **RMS Filter**: Fully implemented with state persistence and envelope detection
1640
+ - ✅ **Rectify Filter**: Full-wave and half-wave rectification with mode persistence
1641
+ - ✅ **Variance Filter**: Batch and moving modes with state persistence
1642
+ - ✅ **Z-Score Normalization**: Batch and moving modes with adaptive normalization
1643
+ - ✅ **Mean Absolute Value**: Batch and moving modes for EMG/biosignal analysis
1644
+ - ✅ **Waveform Length**: EMG complexity and path length measurement
1645
+ - ✅ **Slope Sign Change (SSC)**: EMG frequency content and oscillation detection
1646
+ - ✅ **Willison Amplitude (WAMP)**: EMG burst detection and amplitude change counting
1647
+ - ✅ **Time-Series Processing**: Support for irregular timestamps and time-based windows
1648
+ - ✅ **Circular Buffer**: Optimized with O(1) operations
1649
+ - ✅ **Multi-Channel Support**: Independent state per channel
1650
+ - ✅ **Redis State Serialization**: Complete buffer and sum/sum-of-squares persistence
1651
+ - ✅ **Async Processing**: Background thread via Napi::AsyncWorker
1652
+ - ✅ **Pipeline Callbacks**: Batched and individual callbacks with topic routing
1653
+ - ✅ **Streaming Tests**: Comprehensive streaming validation with interruption recovery
1654
+ - ✅ **Core DSP**: IIR, FIR, FFT
1655
+ - 🚧 **Additional Filters**: polyphaseDecimate, interpolate, resample
1656
+
1657
+ ---
1658
+
1659
+ ## 📚 Examples
1660
+
1661
+ Check out the `/src/ts/examples` directory for complete working examples:
1662
+
1663
+ ### Time-Series Processing (NEW!)
1664
+
1665
+ - [`timeseries/iot-sensor-example.ts`](./src/ts/examples/timeseries/iot-sensor-example.ts) - IoT sensor processing with network jitter and irregular timestamps
1666
+ - [`timeseries/redis-streaming-example.ts`](./src/ts/examples/timeseries/redis-streaming-example.ts) - Streaming data with Redis state persistence and recovery
1667
+ - [`timeseries/comparison-example.ts`](./src/ts/examples/timeseries/comparison-example.ts) - Sample-based vs time-based processing comparison
1668
+
1669
+ ### Redis Integration
1670
+
1671
+ - [`redis/redis-example.ts`](./src/ts/examples/redis/redis-example.ts) - Full Redis integration with state persistence
1672
+
1673
+ ### Moving Average Filter
1674
+
1675
+ - [`MovingAverage/test-state.ts`](./src/ts/examples/MovingAverage/test-state.ts) - State management (save/load/clear)
1676
+ - [`MovingAverage/test-streaming.ts`](./src/ts/examples/MovingAverage/test-streaming.ts) - Streaming data processing with interruption recovery
1677
+
1678
+ ### RMS Filter
1679
+
1680
+ - [`RMS/test-state.ts`](./src/ts/examples/RMS/test-state.ts) - RMS state management with negative values
1681
+ - [`RMS/test-streaming.ts`](./src/ts/examples/RMS/test-streaming.ts) - Real-time envelope detection and multi-channel RMS
1682
+
1683
+ ### Rectify Filter
1684
+
1685
+ - [`Rectify/test-state.ts`](./src/ts/examples/Rectify/test-state.ts) - Full-wave and half-wave rectification with state persistence
1686
+ - [`Rectify/test-streaming.ts`](./src/ts/examples/Rectify/test-streaming.ts) - EMG pre-processing and multi-channel rectification
1687
+
1688
+ ---
1689
+
1690
+ ## 🤝 Contributing
1691
+
1692
+ Contributions are welcome! This project is in active development.
1693
+
1694
+ ### Priority Areas
1695
+
1696
+ 1. **Transform Domain**: FFT, STFT, wavelet transforms
1697
+ 2. **Performance**: SIMD optimizations, benchmarking
1698
+ 3. **Testing**: Unit tests, validation against SciPy/NumPy
1699
+ 4. **Documentation**: More examples, API docs, tutorials
1700
+
1701
+ ### Development Workflow
1702
+
1703
+ ```bash
1704
+ git clone https://github.com/A-KGeorge/dsp_ts_redis.git
1705
+ cd dspx
1706
+ npm install
1707
+ npm run build # Compile C++ with cmake-js
1708
+ npm run test # Run tests
1709
+ ```
1710
+
1711
+ ---
1712
+
1713
+ ## 🌐 Browser Support (Roadmap)
1714
+
1715
+ WebAssembly port is planned to enable browser-based real-time audio processing:
1716
+
1717
+ - Audio Worklet integration for non-blocking DSP
1718
+ - WASM-accelerated spectral analysis
1719
+ - Potential integration with live coding platforms (Strudel.cc, Hydra)
1720
+
1721
+ Interested in collaborating? Open an issue!
1722
+
1723
+ ---
1724
+
1725
+ ## � Recent Bug Fixes & Improvements
1726
+
1727
+ ### Critical Fixes (October 2025)
1728
+
1729
+ #### 1. **Fixed Precision Loss in Double Conversion** (C++)
1730
+
1731
+ - **Issue**: `NapiArrayToVector<double>` was using `FloatValue()` instead of `DoubleValue()`, causing 32-bit precision loss
1732
+ - **Impact**: Timestamps and high-precision data were truncated from 64-bit to 32-bit
1733
+ - **Fix**: Now uses `DoubleValue()` for `double` types and `FloatValue()` for `float` types
1734
+ - **File**: `src/native/utils/NapiUtils.cc`
1735
+
1736
+ #### 2. **Fixed DriftDetector Sample Rate Bug** (TypeScript)
1737
+
1738
+ - **Issue**: When `process()` was called with different `sampleRate` values, the existing `DriftDetector` (configured with the old sample rate) would be reused, causing incorrect drift detection
1739
+ - **Impact**: Drift detection would report false positives/negatives when processing streams with varying sample rates
1740
+ - **Fix**: Now checks if sample rate changed and recreates the detector when needed
1741
+ - **Files**: `src/ts/DriftDetector.ts`, `src/ts/bindings.ts`
1742
+
1743
+ #### 3. **Fixed Missing `<numeric>` Header** (C++)
1744
+
1745
+ - **Issue**: macOS/Linux builds failed with `error: no member named 'accumulate' in namespace 'std'`
1746
+ - **Impact**: CI builds on macOS and Ubuntu failed
1747
+ - **Fix**: Added `#include <numeric>` to `Policies.h`
1748
+ - **File**: `src/native/core/Policies.h`
1749
+
1750
+ #### 4. **Improved Build Reliability** (Build System)
1751
+
1752
+ - **Issue**: Dynamic file discovery in `binding.gyp` using `readdirSync()` wouldn't detect new `.cc` files without reconfiguration
1753
+ - **Impact**: New source files could be added but not compiled, causing mysterious build failures
1754
+ - **Fix**: Explicitly listed all source files in `binding.gyp`
1755
+ - **File**: `binding.gyp`
1756
+
1757
+ ---
1758
+
1759
+ ## ⚠️ Alpha Disclaimer
1760
+
1761
+ > **Important:** This library is currently in **alpha**.
1762
+ > It has **not been tested in production** and is primarily intended for
1763
+ > research, prototyping, and performance experimentation.
1764
+ >
1765
+ > - Use it **at your own discretion**.
1766
+ > - If you encounter bugs, crashes, or inconsistent behavior, please **open an issue**.
1767
+ > - Pull requests (PRs) are welcome — I’ll review and merge fixes or improvements as time allows.
1768
+ >
1769
+ > The goal is to build a **community-maintained** DSP framework.
1770
+ > Early adopters are encouraged to contribute benchmarks, feature requests,
1771
+ > and test results to help make this stable for real-world deployments.
1772
+
1773
+ ---
1774
+
1775
+ ### Testing
1776
+
1777
+ All 441 tests pass after these fixes. Run `npm test` to verify.
1778
+
1779
+ ---
1780
+
1781
+ ## �📄 License
1782
+
1783
+ MIT © Alan Kochukalam George
1784
+
1785
+ ---
1786
+
1787
+ ## 🙏 Acknowledgments
1788
+
1789
+ Built with:
1790
+
1791
+ - [N-API](https://nodejs.org/api/n-api.html) and [node-addon-api](https://github.com/nodejs/node-addon-api) for native bindings
1792
+ - [node-gyp](https://github.com/nodejs/node-gyp) for cross platform C++ compilation
1793
+ - [Redis](https://redis.io/) for state persistence
1794
+ - [TypeScript](https://www.typescriptlang.org/) for type safety
1795
+
1796
+ Inspired by:
1797
+
1798
+ - [SciPy](https://scipy.org/) signal processing
1799
+ - [librosa](https://librosa.org/) audio analysis
1800
+
1801
+ ---
1802
+
1803
+ **Built for real-time signal processing in Node.js** 🚀