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,446 @@
1
+ /**
2
+ * Phase 6-7: RedisTimeSeries Integration Example
3
+ *
4
+ * Shows how to pipe DSP results directly to RedisTimeSeries for:
5
+ * - Real-time Grafana dashboards
6
+ * - Historical trend analysis
7
+ * - Multi-sensor correlation
8
+ *
9
+ * REQUIREMENTS:
10
+ * - Redis Stack (includes RedisTimeSeries module)
11
+ * - npm install @redis/time-series
12
+ *
13
+ * Installation:
14
+ * docker run -d -p 6379:6379 redis/redis-stack:latest
15
+ */
16
+
17
+ import { createDspPipeline } from "../../index.js";
18
+ import { createClient } from "redis";
19
+
20
+ console.log("=== Phase 6-7: RedisTimeSeries Integration ===\n");
21
+
22
+ // Simulated EMG sensor data
23
+ function generateEMGSample(index: number): {
24
+ value: number;
25
+ timestamp: number;
26
+ } {
27
+ const baseSignal = Math.sin(index * 0.05) * 100;
28
+ const noise = (Math.random() - 0.5) * 20;
29
+ const timestamp = Date.now() + index * 10; // 100 Hz
30
+ return { value: baseSignal + noise, timestamp };
31
+ }
32
+
33
+ /**
34
+ * Example 1: Basic RedisTimeSeries Integration
35
+ */
36
+ async function example1_BasicIntegration() {
37
+ console.log("\n--- Example 1: Basic RedisTimeSeries Integration ---\n");
38
+
39
+ try {
40
+ const redis = await createClient({ url: "redis://localhost:6379" })
41
+ .on("error", (err) => console.log("Redis Client Error", err))
42
+ .connect();
43
+
44
+ console.log("✅ Connected to Redis\n");
45
+
46
+ // Create DSP pipeline
47
+ const pipeline = createDspPipeline();
48
+ pipeline
49
+ .Rectify({ mode: "full" })
50
+ .MovingAverage({ mode: "moving", windowDuration: 100 }) // 100ms smoothing
51
+ .Rms({ mode: "moving", windowDuration: 50 }); // 50ms RMS
52
+
53
+ // Process 100 samples
54
+ const count = 100;
55
+ const samples = new Float32Array(count);
56
+ const timestamps = new Float32Array(count);
57
+
58
+ for (let i = 0; i < count; i++) {
59
+ const { value, timestamp } = generateEMGSample(i);
60
+ samples[i] = value;
61
+ timestamps[i] = timestamp;
62
+ }
63
+
64
+ // Process through DSP pipeline
65
+ const processed = await pipeline.process(samples, timestamps, {
66
+ channels: 1,
67
+ });
68
+
69
+ // Write results to RedisTimeSeries
70
+ const sensorId = "emg-sensor-001";
71
+ const tsKey = `myovine:${sensorId}:rms`;
72
+
73
+ console.log(`Writing ${count} samples to Redis key: ${tsKey}\n`);
74
+
75
+ for (let i = 0; i < processed.length; i++) {
76
+ // TS.ADD key timestamp value
77
+ await redis.sendCommand([
78
+ "TS.ADD",
79
+ tsKey,
80
+ Math.floor(timestamps[i]).toString(),
81
+ processed[i].toString(),
82
+ "ON_DUPLICATE",
83
+ "LAST",
84
+ ]);
85
+ }
86
+
87
+ // Query recent data
88
+ const recentData: any = await redis.sendCommand([
89
+ "TS.RANGE",
90
+ tsKey,
91
+ "-",
92
+ "+",
93
+ "COUNT",
94
+ "10",
95
+ ]);
96
+
97
+ console.log("📊 Last 10 samples from RedisTimeSeries:");
98
+ if (Array.isArray(recentData)) {
99
+ recentData.slice(0, 10).forEach((sample: any, idx: number) => {
100
+ const [ts, value] = sample;
101
+ console.log(
102
+ ` [${idx}] ${new Date(Number(ts)).toISOString()} → ${Number(
103
+ value
104
+ ).toFixed(3)}`
105
+ );
106
+ });
107
+ }
108
+
109
+ await redis.quit();
110
+ console.log("\n✅ Example 1 complete\n");
111
+ } catch (error: any) {
112
+ if (error.message.includes("ECONNREFUSED")) {
113
+ console.error("❌ Redis not running. Start Redis Stack:");
114
+ console.error(" docker run -d -p 6379:6379 redis/redis-stack:latest\n");
115
+ } else if (error.message.includes("unknown command")) {
116
+ console.error("❌ RedisTimeSeries module not loaded. Use Redis Stack:");
117
+ console.error(" docker run -d -p 6379:6379 redis/redis-stack:latest\n");
118
+ } else {
119
+ throw error;
120
+ }
121
+ }
122
+ }
123
+
124
+ /**
125
+ * Example 2: Multi-Channel EMG Monitoring
126
+ */
127
+ async function example2_MultiChannelMonitoring() {
128
+ console.log("\n--- Example 2: Multi-Channel EMG Monitoring ---\n");
129
+
130
+ try {
131
+ const redis = await createClient({ url: "redis://localhost:6379" })
132
+ .on("error", (err) => console.log("Redis Client Error", err))
133
+ .connect();
134
+
135
+ const pipeline = createDspPipeline();
136
+ pipeline
137
+ .Rectify({ mode: "full" })
138
+ .MovingAverage({ mode: "moving", windowDuration: 100 })
139
+ .Rms({ mode: "moving", windowDuration: 50 });
140
+
141
+ const channels = 4; // 4-channel EMG
142
+ const samplesPerChannel = 50;
143
+ const totalSamples = samplesPerChannel * channels;
144
+
145
+ const samples = new Float32Array(totalSamples);
146
+ const timestamps = new Float32Array(samplesPerChannel);
147
+
148
+ // Generate interleaved multi-channel data
149
+ for (let i = 0; i < samplesPerChannel; i++) {
150
+ timestamps[i] = Date.now() + i * 10;
151
+ for (let ch = 0; ch < channels; ch++) {
152
+ const { value } = generateEMGSample(i + ch * 100); // Different phases
153
+ samples[i * channels + ch] = value;
154
+ }
155
+ }
156
+
157
+ // Process all channels
158
+ const processed = await pipeline.process(samples, timestamps, { channels });
159
+
160
+ // Write each channel to separate RedisTimeSeries key
161
+ console.log("Writing multi-channel data to RedisTimeSeries:\n");
162
+
163
+ for (let ch = 0; ch < channels; ch++) {
164
+ const tsKey = `myovine:emg-array:ch${ch}:rms`;
165
+
166
+ for (let i = 0; i < samplesPerChannel; i++) {
167
+ const value = processed[i * channels + ch];
168
+ await redis.sendCommand([
169
+ "TS.ADD",
170
+ tsKey,
171
+ Math.floor(timestamps[i]).toString(),
172
+ value.toString(),
173
+ "ON_DUPLICATE",
174
+ "LAST",
175
+ ]);
176
+ }
177
+
178
+ console.log(
179
+ ` ✅ Channel ${ch}: ${samplesPerChannel} samples → ${tsKey}`
180
+ );
181
+ }
182
+
183
+ // Create aggregation rules for downsampling
184
+ console.log("\n📊 Creating aggregation rules (1-second averages):");
185
+
186
+ for (let ch = 0; ch < channels; ch++) {
187
+ const sourceKey = `myovine:emg-array:ch${ch}:rms`;
188
+ const aggKey = `${sourceKey}:avg:1s`;
189
+
190
+ try {
191
+ // TS.CREATERULE source dest AGGREGATION avg 1000
192
+ await redis.sendCommand([
193
+ "TS.CREATERULE",
194
+ sourceKey,
195
+ aggKey,
196
+ "AGGREGATION",
197
+ "AVG",
198
+ "1000", // 1 second bucket
199
+ ]);
200
+ console.log(` ✅ Channel ${ch}: ${aggKey}`);
201
+ } catch (err: any) {
202
+ if (!err.message.includes("already exists")) {
203
+ throw err;
204
+ }
205
+ }
206
+ }
207
+
208
+ await redis.quit();
209
+ console.log("\n✅ Example 2 complete");
210
+ console.log("\n💡 Query with Grafana:");
211
+ console.log(" - Data source: Redis");
212
+ console.log(" - Query: TS.RANGE myovine:emg-array:ch0:rms - +\n");
213
+ } catch (error: any) {
214
+ if (error.message.includes("ECONNREFUSED")) {
215
+ console.error("❌ Redis not running. Start Redis Stack:\n");
216
+ console.error(" docker run -d -p 6379:6379 redis/redis-stack:latest\n");
217
+ } else {
218
+ throw error;
219
+ }
220
+ }
221
+ }
222
+
223
+ /**
224
+ * Example 3: Streaming with Tap + RedisTimeSeries
225
+ */
226
+ async function example3_StreamingWithTap() {
227
+ console.log("\n--- Example 3: Streaming with Tap + RedisTimeSeries ---\n");
228
+
229
+ try {
230
+ const redis = await createClient({ url: "redis://localhost:6379" })
231
+ .on("error", (err) => console.log("Redis Client Error", err))
232
+ .connect();
233
+
234
+ const pipeline = createDspPipeline();
235
+
236
+ // Use .tap() to intercept intermediate results
237
+ pipeline
238
+ .MovingAverage({ mode: "moving", windowDuration: 100 })
239
+ .tap((result, stage) => {
240
+ // This runs after MovingAverage, before RMS
241
+ console.log(` 🔍 Tap at "${stage}": ${result[0].toFixed(3)}`);
242
+ })
243
+ .Rms({ mode: "moving", windowDuration: 50 });
244
+
245
+ const count = 10;
246
+ const samples = new Float32Array(count);
247
+ const timestamps = new Float32Array(count);
248
+
249
+ console.log("Processing samples with .tap() interception:\n");
250
+
251
+ for (let i = 0; i < count; i++) {
252
+ const { value, timestamp } = generateEMGSample(i);
253
+ samples[i] = value;
254
+ timestamps[i] = timestamp;
255
+ }
256
+
257
+ const processed = await pipeline.process(samples, timestamps, {
258
+ channels: 1,
259
+ });
260
+
261
+ console.log("\n💾 Writing final RMS values to RedisTimeSeries...");
262
+
263
+ for (let i = 0; i < processed.length; i++) {
264
+ await redis.sendCommand([
265
+ "TS.ADD",
266
+ "myovine:emg:rms:streaming",
267
+ Math.floor(timestamps[i]).toString(),
268
+ processed[i].toString(),
269
+ "ON_DUPLICATE",
270
+ "LAST",
271
+ ]);
272
+ }
273
+
274
+ await redis.quit();
275
+ console.log("✅ Example 3 complete\n");
276
+ } catch (error: any) {
277
+ if (error.message.includes("ECONNREFUSED")) {
278
+ console.error("❌ Redis not running.\n");
279
+ } else {
280
+ throw error;
281
+ }
282
+ }
283
+ }
284
+
285
+ /**
286
+ * Example 4: Production Pattern - Helper Class
287
+ */
288
+ class RedisTimeSeriesWriter {
289
+ private redis: any;
290
+ private connected: boolean = false;
291
+
292
+ constructor(private url: string = "redis://localhost:6379") {}
293
+
294
+ async connect(): Promise<void> {
295
+ this.redis = await createClient({ url: this.url })
296
+ .on("error", (err) => console.log("Redis Client Error", err))
297
+ .connect();
298
+ this.connected = true;
299
+ }
300
+
301
+ async writeSamples(
302
+ key: string,
303
+ values: Float32Array,
304
+ timestamps: Float32Array
305
+ ): Promise<void> {
306
+ if (!this.connected) {
307
+ throw new Error("Not connected to Redis");
308
+ }
309
+
310
+ for (let i = 0; i < values.length; i++) {
311
+ await this.redis.sendCommand([
312
+ "TS.ADD",
313
+ key,
314
+ Math.floor(timestamps[i]).toString(),
315
+ values[i].toString(),
316
+ "ON_DUPLICATE",
317
+ "LAST",
318
+ ]);
319
+ }
320
+ }
321
+
322
+ async createAggregation(
323
+ sourceKey: string,
324
+ aggType: "AVG" | "SUM" | "MIN" | "MAX",
325
+ bucketMs: number
326
+ ): Promise<string> {
327
+ const destKey = `${sourceKey}:${aggType.toLowerCase()}:${bucketMs}ms`;
328
+
329
+ try {
330
+ await this.redis.sendCommand([
331
+ "TS.CREATERULE",
332
+ sourceKey,
333
+ destKey,
334
+ "AGGREGATION",
335
+ aggType,
336
+ bucketMs.toString(),
337
+ ]);
338
+ } catch (err: any) {
339
+ if (!err.message.includes("already exists")) {
340
+ throw err;
341
+ }
342
+ }
343
+
344
+ return destKey;
345
+ }
346
+
347
+ async close(): Promise<void> {
348
+ if (this.connected) {
349
+ await this.redis.quit();
350
+ this.connected = false;
351
+ }
352
+ }
353
+ }
354
+
355
+ async function example4_ProductionPattern() {
356
+ console.log("\n--- Example 4: Production Pattern (Helper Class) ---\n");
357
+
358
+ try {
359
+ const writer = new RedisTimeSeriesWriter();
360
+ await writer.connect();
361
+
362
+ console.log("✅ Connected to Redis\n");
363
+
364
+ // Set up DSP pipeline
365
+ const pipeline = createDspPipeline();
366
+ pipeline
367
+ .Rectify({ mode: "full" })
368
+ .MovingAverage({ mode: "moving", windowDuration: 100 })
369
+ .Rms({ mode: "moving", windowDuration: 50 });
370
+
371
+ // Process data
372
+ const count = 50;
373
+ const samples = new Float32Array(count);
374
+ const timestamps = new Float32Array(count);
375
+
376
+ for (let i = 0; i < count; i++) {
377
+ const { value, timestamp } = generateEMGSample(i);
378
+ samples[i] = value;
379
+ timestamps[i] = timestamp;
380
+ }
381
+
382
+ const processed = await pipeline.process(samples, timestamps, {
383
+ channels: 1,
384
+ });
385
+
386
+ // Write to RedisTimeSeries
387
+ const key = "myovine:production:emg:rms";
388
+ await writer.writeSamples(key, processed, timestamps);
389
+ console.log(`📊 Wrote ${count} samples to ${key}`);
390
+
391
+ // Create aggregation rules
392
+ const avg1s = await writer.createAggregation(key, "AVG", 1000);
393
+ const avg10s = await writer.createAggregation(key, "AVG", 10000);
394
+ console.log(`\n✅ Created aggregations:`);
395
+ console.log(` - ${avg1s}`);
396
+ console.log(` - ${avg10s}`);
397
+
398
+ await writer.close();
399
+ console.log("\n✅ Example 4 complete\n");
400
+ } catch (error: any) {
401
+ if (error.message.includes("ECONNREFUSED")) {
402
+ console.error("❌ Redis not running.\n");
403
+ } else {
404
+ throw error;
405
+ }
406
+ }
407
+ }
408
+
409
+ // Run all examples
410
+ async function main() {
411
+ console.log(
412
+ "📚 These examples show how to integrate DSP results with RedisTimeSeries\n"
413
+ );
414
+ console.log("Prerequisites:");
415
+ console.log(
416
+ " 1. Install Redis Stack: docker run -d -p 6379:6379 redis/redis-stack:latest"
417
+ );
418
+ console.log(" 2. Install dependencies: npm install redis\n");
419
+ console.log("Press Ctrl+C if Redis is not available.\n");
420
+
421
+ await new Promise((resolve) => setTimeout(resolve, 2000));
422
+
423
+ await example1_BasicIntegration();
424
+ await example2_MultiChannelMonitoring();
425
+ await example3_StreamingWithTap();
426
+ await example4_ProductionPattern();
427
+
428
+ console.log("=== Phase 6-7 Complete ===\n");
429
+ console.log("✅ RedisTimeSeries integration enables:");
430
+ console.log(" • Real-time Grafana dashboards");
431
+ console.log(" • Historical trend analysis");
432
+ console.log(" • Automatic downsampling (aggregation rules)");
433
+ console.log(" • Multi-sensor correlation");
434
+ console.log(" • Production-ready monitoring\n");
435
+
436
+ console.log("💡 Next steps:");
437
+ console.log(" 1. Set up Grafana with Redis datasource");
438
+ console.log(" 2. Create dashboards for your EMG sensors");
439
+ console.log(" 3. Set up alerts for anomalies");
440
+ console.log(" 4. Use aggregation rules for long-term storage\n");
441
+ }
442
+
443
+ main().catch((error) => {
444
+ console.error("Error:", error.message);
445
+ process.exit(1);
446
+ });
@@ -0,0 +1,202 @@
1
+ /**
2
+ * Example showing how to use Redis for DSP state persistence
3
+ *
4
+ * This demonstrates:
5
+ * 1. Creating a pipeline with Redis configuration
6
+ * 2. Processing audio data
7
+ * 3. Saving/loading state (when Redis integration is fully implemented)
8
+ *
9
+ * Note: This example shows the intended API. Full C++ Redis integration
10
+ * requires implementing saveState/loadState methods in DspPipeline.cc
11
+ */
12
+
13
+ import { createClient } from "redis";
14
+ import { createDspPipeline } from "../../bindings";
15
+
16
+ async function redisExample(startFresh = false) {
17
+ console.log("=== DSP Pipeline with Redis State Persistence ===\n");
18
+
19
+ // 1. Create Redis client
20
+ const redis = await createClient({ url: "redis://localhost:6379" }).connect();
21
+
22
+ // 2. Create DSP pipeline with Redis configuration
23
+ const stateKey = "dsp:pipeline:channel1";
24
+
25
+ // Optional: Clear previous state to start fresh
26
+ if (startFresh) {
27
+ console.log("Clearing previous state to start fresh...");
28
+ await redis.del(stateKey);
29
+ console.log("State cleared\n");
30
+ }
31
+
32
+ const pipeline = createDspPipeline({
33
+ redisHost: "localhost",
34
+ redisPort: 6379,
35
+ stateKey: stateKey,
36
+ });
37
+
38
+ // 3. Build the pipeline
39
+ pipeline.MovingAverage({ mode: "moving", windowSize: 3 });
40
+
41
+ console.log("Pipeline created with moving average filter (window=3)");
42
+
43
+ // 4. Try to restore previous state (if exists)
44
+ const previousState = await redis.get(stateKey);
45
+ if (previousState) {
46
+ console.log("Found previous state in Redis, restoring...");
47
+ await pipeline.loadState(previousState);
48
+ console.log("State restored successfully!");
49
+ } else {
50
+ console.log("No previous state found in Redis");
51
+ }
52
+
53
+ // 5. First batch of audio
54
+ console.log("\n--- Processing first batch ---");
55
+ const batch1 = new Float32Array([1, 2, 3, 4, 5]);
56
+ console.log("Input batch 1:", Array.from(batch1));
57
+
58
+ const output1 = await pipeline.processCopy(batch1, {
59
+ sampleRate: 1000,
60
+ channels: 1,
61
+ });
62
+ console.log("Output batch 1:", Array.from(output1));
63
+
64
+ // 6. Save state to Redis
65
+ console.log("\n--- Saving state to Redis ---");
66
+ const state = await pipeline.saveState();
67
+ await redis.set(stateKey, state);
68
+ console.log("State saved:", state);
69
+
70
+ // 7. Simulate stopping and restarting the process
71
+ console.log("\n--- Simulating restart ---");
72
+
73
+ // 8. Create new pipeline and restore state
74
+ const pipeline2 = createDspPipeline({
75
+ redisHost: "localhost",
76
+ redisPort: 6379,
77
+ stateKey: stateKey,
78
+ });
79
+
80
+ pipeline2.MovingAverage({ mode: "moving", windowSize: 3 });
81
+
82
+ const restoredState = await redis.get(stateKey);
83
+ if (restoredState) {
84
+ console.log("Restored state from Redis");
85
+ await pipeline2.loadState(restoredState);
86
+ console.log(
87
+ "State restoration complete - continuing from where we left off!"
88
+ );
89
+ }
90
+
91
+ // 9. Continue processing with restored state
92
+ console.log("\n--- Processing second batch (after 'restart') ---");
93
+ const batch2 = new Float32Array([6, 7, 8]);
94
+ console.log("Input batch 2:", Array.from(batch2));
95
+
96
+ const output2 = await pipeline2.processCopy(batch2, {
97
+ sampleRate: 1000,
98
+ channels: 1,
99
+ });
100
+ console.log("Output batch 2:", Array.from(output2));
101
+
102
+ console.log(
103
+ "\nNote: With state restoration, batch2 continues the moving average from where batch1 left off!"
104
+ );
105
+ console.log(
106
+ "Expected output without state: [6, 6.5, 7] - but we got continuous averaging!"
107
+ );
108
+
109
+ // 10. Optional: Clear state (commented out to persist between runs)
110
+ // console.log("\n--- Clearing state ---");
111
+ // await redis.del(stateKey);
112
+ // pipeline2.clearState();
113
+ // console.log("State cleared from Redis");
114
+
115
+ // Disconnect from Redis
116
+ await redis.disconnect();
117
+ } // Real-world use case example
118
+ async function streamingExample(startFresh = false) {
119
+ console.log("\n\n=== Streaming Audio Processing Example ===\n");
120
+
121
+ const redis = await createClient({ url: "redis://localhost:6379" }).connect();
122
+ const channelId = "audio-stream-ch1";
123
+ const stateKey = `dsp:stream:${channelId}`;
124
+
125
+ // Optional: Clear previous state to start fresh
126
+ if (startFresh) {
127
+ console.log("Clearing previous streaming state to start fresh...");
128
+ await redis.del(stateKey);
129
+ console.log("Streaming state cleared\n");
130
+ }
131
+
132
+ // Create pipeline
133
+ const pipeline = createDspPipeline({
134
+ redisHost: "localhost",
135
+ redisPort: 6379,
136
+ stateKey: stateKey,
137
+ });
138
+
139
+ pipeline.MovingAverage({ mode: "moving", windowSize: 5 });
140
+
141
+ // Restore state if processing was interrupted
142
+ const savedState = await redis.get(stateKey);
143
+ if (savedState) {
144
+ console.log("Resuming from saved state...");
145
+ await pipeline.loadState(savedState);
146
+ }
147
+
148
+ // Simulate streaming audio chunks
149
+ const chunks = [
150
+ new Float32Array([1, 2, 3, 4, 5]),
151
+ new Float32Array([6, 7, 8, 9, 10]),
152
+ new Float32Array([11, 12, 13, 14, 15]),
153
+ ];
154
+
155
+ console.log("Processing audio stream in chunks...\n");
156
+
157
+ for (let i = 0; i < chunks.length; i++) {
158
+ const chunk = chunks[i];
159
+ console.log(`Chunk ${i + 1} input:`, Array.from(chunk));
160
+
161
+ const processed = await pipeline.processCopy(chunk, {
162
+ sampleRate: 44100,
163
+ channels: 1,
164
+ });
165
+
166
+ console.log(`Chunk ${i + 1} output:`, Array.from(processed));
167
+
168
+ // Save state after each chunk (for crash recovery)
169
+ const state = await pipeline.saveState();
170
+ await redis.set(stateKey, state);
171
+ console.log(`State saved after chunk ${i + 1}\n`);
172
+ }
173
+
174
+ console.log("Stream processing complete. State saved for next session.");
175
+
176
+ // Disconnect from Redis
177
+ await redis.disconnect();
178
+ }
179
+
180
+ // Run examples
181
+ console.log("Running DSP + Redis examples...\n");
182
+
183
+ // Set to true to clear previous state and start fresh
184
+ // Set to false to demonstrate state persistence across runs
185
+ const START_FRESH = false;
186
+
187
+ redisExample(START_FRESH)
188
+ .then(() => streamingExample(START_FRESH))
189
+ .then(() => {
190
+ console.log("\nAll examples completed successfully!");
191
+ console.log(
192
+ "\nTip: Set START_FRESH = true to clear Redis state and start fresh"
193
+ );
194
+ console.log(
195
+ "Tip: Set START_FRESH = false to see state persistence across runs"
196
+ );
197
+ process.exit(0);
198
+ })
199
+ .catch((error) => {
200
+ console.error("\nError:", error);
201
+ process.exit(1);
202
+ });