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,42 @@
1
+ /**
2
+ * Easter egg - ASCII art for curious developers
3
+ * @internal
4
+ */
5
+ export function egg(): void {
6
+ const art = `
7
+ ████████
8
+ ████░░░░░░░░████
9
+ ██░░░░░░░░░░░░░░░░██
10
+ ██░░░░░░░░░░░░░░░░░░░░██
11
+ ██░░░░░░░░░░░░░░░░░░░░░░░░██
12
+ ██░░░░░░░░░░░░░░░░░░░░░░░░░░░░██
13
+ ██░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░██
14
+ ██░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░██
15
+ ██░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░██
16
+ ██░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░██
17
+ ██░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░██
18
+ ██░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░██
19
+ ██░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░██
20
+ ██░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░██
21
+ ██░░░░░░░░░░░░░░░░░░ FFT ░░░░░░░░░░░░░░░██
22
+ ██░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░██
23
+ ██░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░██
24
+ ██░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░██
25
+ ██░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░██
26
+ ██░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░██
27
+ ██░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░██
28
+ ██░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░██
29
+ ██░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░██
30
+ ██░░░░░░░░░░░░░░░░░░░░░░░░░░░░██
31
+ ██░░░░░░░░░░░░░░░░░░░░░░░░██
32
+ ██░░░░░░░░░░░░░░░░░░░░██
33
+ ██░░░░░░░░░░░░░░░░██
34
+ ████░░░░░░░░████
35
+ ████████
36
+
37
+ dspx - Digital Signal Processing for TypeScript
38
+ You found the Easter egg!
39
+ `;
40
+
41
+ console.log(art);
42
+ }
@@ -0,0 +1,99 @@
1
+ /**
2
+ * Test script for Mean Absolute Value filter state management
3
+ * Demonstrates save/load/clear operations for stateful MAV processing
4
+ */
5
+
6
+ import { createDspPipeline } from "../../bindings";
7
+
8
+ async function testMavStateManagement() {
9
+ console.log("=== Testing MAV Filter State Management ===\n");
10
+
11
+ // 1. Create pipeline with MAV filter
12
+ const pipeline = createDspPipeline();
13
+ pipeline.MeanAbsoluteValue({ mode: "moving", windowSize: 3 });
14
+
15
+ console.log("Pipeline created with MAV filter (window=3)");
16
+
17
+ // 2. Process EMG-like signal data
18
+ const input1 = new Float32Array([1, -2, 3, -4, 5]);
19
+ console.log("\nProcessing first batch (simulated EMG):", Array.from(input1));
20
+
21
+ const output1 = await pipeline.processCopy(input1, {
22
+ sampleRate: 1000,
23
+ channels: 1,
24
+ });
25
+ console.log(
26
+ "MAV Output:",
27
+ Array.from(output1).map((v) => v.toFixed(2))
28
+ );
29
+
30
+ // 3. Save state to JSON
31
+ console.log("\n--- Saving State ---");
32
+ const stateJson = await pipeline.saveState();
33
+ const state = JSON.parse(stateJson);
34
+ console.log("State saved:");
35
+ console.log(JSON.stringify(state, null, 2));
36
+
37
+ // 4. Continue processing
38
+ const input2 = new Float32Array([-6, 7, -8]);
39
+ console.log("\nProcessing second batch:", Array.from(input2));
40
+
41
+ const output2 = await pipeline.processCopy(input2, {
42
+ sampleRate: 1000,
43
+ channels: 1,
44
+ });
45
+ console.log(
46
+ "MAV Output:",
47
+ Array.from(output2).map((v) => v.toFixed(2))
48
+ );
49
+
50
+ // 5. Save state again
51
+ const stateJson2 = await pipeline.saveState();
52
+ console.log("\nState after second batch:");
53
+ console.log(JSON.stringify(JSON.parse(stateJson2), null, 2));
54
+
55
+ // 6. Create new pipeline and load state
56
+ console.log("\n--- Creating New Pipeline ---");
57
+ const pipeline2 = createDspPipeline();
58
+ pipeline2.MeanAbsoluteValue({ mode: "moving", windowSize: 3 });
59
+
60
+ console.log("Loading previous state...");
61
+ const loaded = await pipeline2.loadState(stateJson2);
62
+ console.log("Load successful:", loaded);
63
+
64
+ // 7. Process with restored state
65
+ const input3 = new Float32Array([9, -10]);
66
+ console.log("\nProcessing with restored pipeline:", Array.from(input3));
67
+
68
+ const output3 = await pipeline2.processCopy(input3, {
69
+ sampleRate: 1000,
70
+ channels: 1,
71
+ });
72
+ console.log(
73
+ "MAV Output:",
74
+ Array.from(output3).map((v) => v.toFixed(2))
75
+ );
76
+
77
+ // 8. Clear state
78
+ console.log("\n--- Clearing State ---");
79
+ pipeline2.clearState();
80
+ console.log("State cleared");
81
+
82
+ // 9. Process after clear (should start fresh)
83
+ const input4 = new Float32Array([1, -2, 3]);
84
+ console.log("\nProcessing after clear:", Array.from(input4));
85
+
86
+ const output4 = await pipeline2.processCopy(input4, {
87
+ sampleRate: 1000,
88
+ channels: 1,
89
+ });
90
+ console.log(
91
+ "MAV Output (fresh start):",
92
+ Array.from(output4).map((v) => v.toFixed(2))
93
+ );
94
+
95
+ console.log("\n✓ State management test complete!");
96
+ }
97
+
98
+ // Run test
99
+ testMavStateManagement().catch(console.error);
@@ -0,0 +1,269 @@
1
+ /**
2
+ * Test streaming MAV processing - ideal for EMG signal analysis
3
+ * Demonstrates real-time activity detection from streaming bioelectric signals
4
+ */
5
+
6
+ import { createDspPipeline } from "../../bindings";
7
+
8
+ // Simulate streaming EMG data
9
+ async function* simulateEmgStream(
10
+ totalSamples: number,
11
+ chunkSize: number
12
+ ): AsyncGenerator<Float32Array> {
13
+ for (let i = 0; i < totalSamples; i += chunkSize) {
14
+ // Simulate data arriving over time (e.g., from EMG sensor)
15
+ await new Promise((resolve) => setTimeout(resolve, 50)); // 50ms delay
16
+
17
+ const remainingSamples = Math.min(chunkSize, totalSamples - i);
18
+ const chunk = new Float32Array(remainingSamples);
19
+
20
+ // Generate simulated EMG signal
21
+ // Low amplitude baseline + occasional bursts (muscle contractions)
22
+ for (let j = 0; j < remainingSamples; j++) {
23
+ const t = (i + j) / 1000;
24
+ // Baseline noise
25
+ let signal = (Math.random() - 0.5) * 0.1;
26
+
27
+ // Add muscle contraction bursts every ~200 samples
28
+ if (Math.floor((i + j) / 200) % 2 === 1) {
29
+ signal += Math.sin(2 * Math.PI * 100 * t) * 0.8; // High-frequency burst
30
+ }
31
+
32
+ chunk[j] = signal;
33
+ }
34
+
35
+ yield chunk;
36
+ }
37
+ }
38
+
39
+ async function testMavStreaming() {
40
+ console.log("=== Testing MAV for Streaming EMG Analysis ===\n");
41
+
42
+ const pipeline = createDspPipeline();
43
+ pipeline.MeanAbsoluteValue({ mode: "moving", windowSize: 20 }); // 20ms window at 1kHz
44
+
45
+ console.log("Pipeline created with MAV filter (20-sample window)");
46
+ console.log("Use case: Real-time muscle activity detection from EMG");
47
+ console.log("Simulating EMG stream (500 samples, 50 samples/chunk)\n");
48
+
49
+ let chunkNumber = 0;
50
+ let totalProcessed = 0;
51
+ const allOutputs: number[] = [];
52
+
53
+ // Process streaming EMG data
54
+ for await (const chunk of simulateEmgStream(500, 50)) {
55
+ chunkNumber++;
56
+ const chunkPreview = Array.from(chunk.slice(0, 3))
57
+ .map((v) => v.toFixed(3))
58
+ .join(", ");
59
+ console.log(
60
+ `Chunk ${chunkNumber}: Received ${chunk.length} samples [${chunkPreview}, ...]`
61
+ );
62
+
63
+ // Process chunk - MAV tracks activity level
64
+ const output = await pipeline.process(chunk, {
65
+ sampleRate: 1000,
66
+ channels: 1,
67
+ });
68
+
69
+ const mavPreview = Array.from(output.slice(0, 3))
70
+ .map((v) => v.toFixed(3))
71
+ .join(", ");
72
+ const maxMav = Math.max(...Array.from(output));
73
+ console.log(
74
+ ` MAV → [${mavPreview}, ...] (max: ${maxMav.toFixed(3)})\n`
75
+ );
76
+
77
+ allOutputs.push(...Array.from(output));
78
+ totalProcessed += chunk.length;
79
+ }
80
+
81
+ console.log(`Stream complete: ${totalProcessed} samples processed`);
82
+ console.log(
83
+ ` First 10 MAV: [${allOutputs
84
+ .slice(0, 10)
85
+ .map((v) => v.toFixed(3))
86
+ .join(", ")}]`
87
+ );
88
+ console.log(
89
+ ` Last 10 MAV: [${allOutputs
90
+ .slice(-10)
91
+ .map((v) => v.toFixed(3))
92
+ .join(", ")}]`
93
+ );
94
+ }
95
+
96
+ // Test with state persistence across stream interruption
97
+ async function testStreamInterruption() {
98
+ console.log("\n\n=== Testing Stream Interruption & Recovery ===\n");
99
+
100
+ const pipeline = createDspPipeline();
101
+ pipeline.MeanAbsoluteValue({ mode: "moving", windowSize: 5 });
102
+
103
+ console.log("Processing first part of EMG stream...");
104
+ let chunk1 = new Float32Array([0.1, -0.2, 0.3, -0.4, 0.5]);
105
+ let output1 = await pipeline.process(chunk1, {
106
+ sampleRate: 1000,
107
+ channels: 1,
108
+ });
109
+ console.log(
110
+ `Chunk 1: [${Array.from(chunk1).map((v) =>
111
+ v.toFixed(1)
112
+ )}] → MAV: [${Array.from(output1).map((v) => v.toFixed(2))}]`
113
+ );
114
+
115
+ let chunk2 = new Float32Array([-0.6, 0.7, -0.8]);
116
+ let output2 = await pipeline.process(chunk2, {
117
+ sampleRate: 1000,
118
+ channels: 1,
119
+ });
120
+ console.log(
121
+ `Chunk 2: [${Array.from(chunk2).map((v) =>
122
+ v.toFixed(1)
123
+ )}] → MAV: [${Array.from(output2).map((v) => v.toFixed(2))}]`
124
+ );
125
+
126
+ // Save state (simulate sensor disconnect/reconnect)
127
+ console.log("\nSaving state (simulating sensor reconnection)...");
128
+ const savedState = await pipeline.saveState();
129
+ const state = JSON.parse(savedState);
130
+ console.log(
131
+ "State saved - window contains:",
132
+ state.stages[0].state.channels[0].buffer.length,
133
+ "samples"
134
+ );
135
+
136
+ // Create new pipeline and restore
137
+ console.log("\nCreating new pipeline and restoring state...");
138
+ const pipeline2 = createDspPipeline();
139
+ pipeline2.MeanAbsoluteValue({ mode: "moving", windowSize: 5 });
140
+ await pipeline2.loadState(savedState);
141
+ console.log("State restored!");
142
+
143
+ // Continue processing
144
+ console.log("\nContinuing stream processing...");
145
+ let chunk3 = new Float32Array([0.9, -1.0, 1.1]);
146
+ let output3 = await pipeline2.process(chunk3, {
147
+ sampleRate: 1000,
148
+ channels: 1,
149
+ });
150
+ console.log(
151
+ `Chunk 3: [${Array.from(chunk3).map((v) =>
152
+ v.toFixed(1)
153
+ )}] → MAV: [${Array.from(output3).map((v) => v.toFixed(2))}]`
154
+ );
155
+
156
+ console.log("\nStream recovered seamlessly!");
157
+ }
158
+
159
+ // Test multi-channel streaming (e.g., multi-electrode EMG)
160
+ async function testMultiChannelEmg() {
161
+ console.log("\n\n=== Testing Multi-Electrode EMG Streaming ===\n");
162
+
163
+ const pipeline = createDspPipeline();
164
+ pipeline.MeanAbsoluteValue({ mode: "moving", windowSize: 3 });
165
+
166
+ console.log("Processing 2-channel EMG data stream");
167
+ console.log("Channel 0: Biceps, Channel 1: Triceps");
168
+ console.log("Format: [biceps_s1, triceps_s1, biceps_s2, triceps_s2, ...]\n");
169
+
170
+ // Simulate 3 chunks of 2-channel EMG data
171
+ const chunks = [
172
+ new Float32Array([0.1, -0.05, -0.2, 0.1, 0.3, -0.15, -0.4, 0.2]), // Biceps active
173
+ new Float32Array([-0.05, 0.5, 0.1, -0.6, -0.15, 0.7, 0.2, -0.8]), // Triceps active
174
+ new Float32Array([0.3, 0.3, -0.4, -0.4, 0.5, 0.5, -0.6, -0.6]), // Both active
175
+ ];
176
+
177
+ const labels = [
178
+ "Biceps contraction",
179
+ "Triceps contraction",
180
+ "Co-contraction",
181
+ ];
182
+
183
+ for (let i = 0; i < chunks.length; i++) {
184
+ console.log(`\n${labels[i]}:`);
185
+ console.log(` Input: [${Array.from(chunks[i]).map((v) => v.toFixed(2))}]`);
186
+
187
+ const output = await pipeline.process(chunks[i], {
188
+ sampleRate: 1000,
189
+ channels: 2,
190
+ });
191
+
192
+ console.log(` MAV: [${Array.from(output).map((v) => v.toFixed(2))}]`);
193
+
194
+ // Calculate average MAV per channel
195
+ const bicepsMav = [];
196
+ const tricepsMav = [];
197
+ for (let j = 0; j < output.length; j += 2) {
198
+ bicepsMav.push(output[j]);
199
+ tricepsMav.push(output[j + 1]);
200
+ }
201
+
202
+ const avgBiceps = bicepsMav.reduce((a, b) => a + b, 0) / bicepsMav.length;
203
+ const avgTriceps =
204
+ tricepsMav.reduce((a, b) => a + b, 0) / tricepsMav.length;
205
+
206
+ console.log(
207
+ ` Avg MAV → Biceps: ${avgBiceps.toFixed(
208
+ 3
209
+ )}, Triceps: ${avgTriceps.toFixed(3)}`
210
+ );
211
+ }
212
+
213
+ console.log("\n✓ Each muscle maintains independent activity tracking!");
214
+ }
215
+
216
+ // Test batch vs moving mode comparison
217
+ async function testBatchVsMovingMode() {
218
+ console.log("\n\n=== Batch vs Moving Mode Comparison ===\n");
219
+
220
+ const signal = new Float32Array([0.1, -0.5, 0.3, -0.8, 0.2, -0.6, 0.4, -0.7]);
221
+ console.log(
222
+ "Input signal:",
223
+ Array.from(signal).map((v) => v.toFixed(1))
224
+ );
225
+
226
+ // Batch mode - compute global MAV
227
+ const batchPipeline = createDspPipeline();
228
+ batchPipeline.MeanAbsoluteValue({ mode: "batch" });
229
+ const batchOutput = await batchPipeline.process(signal, {
230
+ sampleRate: 1000,
231
+ channels: 1,
232
+ });
233
+ console.log("\nBatch mode (global MAV):");
234
+ console.log(
235
+ " Output:",
236
+ Array.from(batchOutput).map((v) => v.toFixed(3))
237
+ );
238
+ console.log(" → All values equal (entire signal average)");
239
+
240
+ // Moving mode - local sliding window
241
+ const movingPipeline = createDspPipeline();
242
+ movingPipeline.MeanAbsoluteValue({ mode: "moving", windowSize: 3 });
243
+ const movingOutput = await movingPipeline.process(signal, {
244
+ sampleRate: 1000,
245
+ channels: 1,
246
+ });
247
+ console.log("\nMoving mode (window=3):");
248
+ console.log(
249
+ " Output:",
250
+ Array.from(movingOutput).map((v) => v.toFixed(3))
251
+ );
252
+ console.log(" → Local activity tracking (better for transient detection)");
253
+ }
254
+
255
+ // Run all tests
256
+ async function runAllTests() {
257
+ try {
258
+ await testMavStreaming();
259
+ await testStreamInterruption();
260
+ await testMultiChannelEmg();
261
+ await testBatchVsMovingMode();
262
+ console.log("\n✓ All MAV streaming tests passed!");
263
+ } catch (error) {
264
+ console.error("Test failed:", error);
265
+ process.exit(1);
266
+ }
267
+ }
268
+
269
+ runAllTests();
@@ -0,0 +1,85 @@
1
+ /**
2
+ * Test script for state management (save/load/clear)
3
+ */
4
+
5
+ import { createDspPipeline } from "../../bindings";
6
+
7
+ async function testStateManagement() {
8
+ console.log("=== Testing DSP Pipeline State Management ===\n");
9
+
10
+ // 1. Create pipeline and add stages
11
+ const pipeline = createDspPipeline();
12
+ pipeline.MovingAverage({ mode: "moving", windowSize: 3 });
13
+
14
+ console.log("Pipeline created with moving average filter (window=3)");
15
+
16
+ // 2. Process some data
17
+ const input1 = new Float32Array([1, 2, 3, 4, 5]);
18
+ console.log("\nProcessing first batch:", Array.from(input1));
19
+
20
+ const output1 = await pipeline.processCopy(input1, {
21
+ sampleRate: 1000,
22
+ channels: 1,
23
+ });
24
+ console.log("Output:", Array.from(output1));
25
+
26
+ // 3. Save state to JSON
27
+ console.log("\n--- Saving State ---");
28
+ const stateJson = await pipeline.saveState();
29
+ console.log("State saved:");
30
+ console.log(JSON.stringify(JSON.parse(stateJson), null, 2));
31
+
32
+ // 4. Continue processing
33
+ const input2 = new Float32Array([6, 7, 8]);
34
+ console.log("\nProcessing second batch:", Array.from(input2));
35
+
36
+ const output2 = await pipeline.processCopy(input2, {
37
+ sampleRate: 1000,
38
+ channels: 1,
39
+ });
40
+ console.log("Output:", Array.from(output2));
41
+
42
+ // 5. Save state again
43
+ const stateJson2 = await pipeline.saveState();
44
+ console.log("\nState after second batch:");
45
+ console.log(JSON.stringify(JSON.parse(stateJson2), null, 2));
46
+
47
+ // 6. Create new pipeline and load state
48
+ console.log("\n--- Creating New Pipeline ---");
49
+ const pipeline2 = createDspPipeline();
50
+ pipeline2.MovingAverage({ mode: "moving", windowSize: 3 });
51
+
52
+ console.log("Loading previous state...");
53
+ const loaded = await pipeline2.loadState(stateJson2);
54
+ console.log("Load successful:", loaded);
55
+
56
+ // 7. Process with restored state
57
+ const input3 = new Float32Array([9, 10]);
58
+ console.log("\nProcessing with restored pipeline:", Array.from(input3));
59
+
60
+ const output3 = await pipeline2.processCopy(input3, {
61
+ sampleRate: 1000,
62
+ channels: 1,
63
+ });
64
+ console.log("Output:", Array.from(output3));
65
+
66
+ // 8. Clear state
67
+ console.log("\n--- Clearing State ---");
68
+ pipeline2.clearState();
69
+ console.log("State cleared");
70
+
71
+ // 9. Process after clear (should start fresh)
72
+ const input4 = new Float32Array([1, 2, 3]);
73
+ console.log("\nProcessing after clear:", Array.from(input4));
74
+
75
+ const output4 = await pipeline2.processCopy(input4, {
76
+ sampleRate: 1000,
77
+ channels: 1,
78
+ });
79
+ console.log("Output (fresh start):", Array.from(output4));
80
+
81
+ console.log("\nState management test complete!");
82
+ }
83
+
84
+ // Run test
85
+ testStateManagement().catch(console.error);
@@ -0,0 +1,188 @@
1
+ /**
2
+ * Test streaming data processing with state continuity
3
+ */
4
+
5
+ import { createDspPipeline } from "../../bindings";
6
+
7
+ // Simulate streaming chunks of data
8
+ async function* simulateDataStream(
9
+ totalSamples: number,
10
+ chunkSize: number
11
+ ): AsyncGenerator<Float32Array> {
12
+ for (let i = 0; i < totalSamples; i += chunkSize) {
13
+ // Simulate data arriving over time (e.g., from sensor, audio device, network)
14
+ await new Promise((resolve) => setTimeout(resolve, 100)); // 100ms delay
15
+
16
+ const remainingSamples = Math.min(chunkSize, totalSamples - i);
17
+ const chunk = new Float32Array(remainingSamples);
18
+
19
+ // Generate sample data (e.g., sine wave + noise)
20
+ for (let j = 0; j < remainingSamples; j++) {
21
+ const t = (i + j) / 100;
22
+ chunk[j] = Math.sin(2 * Math.PI * 5 * t) + Math.random() * 0.2; // 5Hz sine + noise
23
+ }
24
+
25
+ yield chunk;
26
+ }
27
+ }
28
+
29
+ async function testStreamingProcessing() {
30
+ console.log("=== Testing Streaming Data Processing ===\n");
31
+
32
+ const pipeline = createDspPipeline();
33
+ pipeline.MovingAverage({ mode: "moving", windowSize: 10 }); // Smooth with 10-sample window
34
+
35
+ console.log("Pipeline created with 10-sample moving average");
36
+ console.log(
37
+ "Simulating real-time data stream (100 samples, 20 samples/chunk)\n"
38
+ );
39
+
40
+ let chunkNumber = 0;
41
+ let totalProcessed = 0;
42
+ const allOutputs: number[] = [];
43
+
44
+ // Process streaming data
45
+ for await (const chunk of simulateDataStream(100, 20)) {
46
+ chunkNumber++;
47
+ console.log(
48
+ `Chunk ${chunkNumber}: Received ${
49
+ chunk.length
50
+ } samples [${chunk[0].toFixed(3)}, ${chunk[1].toFixed(3)}, ...]`
51
+ );
52
+
53
+ // Process chunk - pipeline remembers state from previous chunks
54
+ const output = await pipeline.process(chunk, {
55
+ sampleRate: 100,
56
+ channels: 1,
57
+ });
58
+
59
+ console.log(
60
+ ` Processed → [${output[0].toFixed(3)}, ${output[1].toFixed(
61
+ 3
62
+ )}, ...]\n`
63
+ );
64
+
65
+ allOutputs.push(...Array.from(output));
66
+ totalProcessed += chunk.length;
67
+ }
68
+
69
+ console.log(`Stream complete: ${totalProcessed} samples processed`);
70
+ console.log(
71
+ ` First 10 outputs: [${allOutputs
72
+ .slice(0, 10)
73
+ .map((v) => v.toFixed(3))
74
+ .join(", ")}]`
75
+ );
76
+ console.log(
77
+ ` Last 10 outputs: [${allOutputs
78
+ .slice(-10)
79
+ .map((v) => v.toFixed(3))
80
+ .join(", ")}]`
81
+ );
82
+ }
83
+
84
+ // Test with state persistence across stream interruption
85
+ async function testStreamInterruption() {
86
+ console.log("\n\n=== Testing Stream Interruption & Recovery ===\n");
87
+
88
+ const pipeline = createDspPipeline();
89
+ pipeline.MovingAverage({ mode: "moving", windowSize: 5 });
90
+
91
+ console.log("Processing first part of stream...");
92
+ let chunk1 = new Float32Array([1, 2, 3, 4, 5]);
93
+ let output1 = await pipeline.process(chunk1, {
94
+ sampleRate: 100,
95
+ channels: 1,
96
+ });
97
+ console.log(
98
+ `Chunk 1: [${Array.from(chunk1)}] → [${Array.from(output1).map((v) =>
99
+ v.toFixed(2)
100
+ )}]`
101
+ );
102
+
103
+ let chunk2 = new Float32Array([6, 7, 8]);
104
+ let output2 = await pipeline.process(chunk2, {
105
+ sampleRate: 100,
106
+ channels: 1,
107
+ });
108
+ console.log(
109
+ `Chunk 2: [${Array.from(chunk2)}] → [${Array.from(output2).map((v) =>
110
+ v.toFixed(2)
111
+ )}]`
112
+ );
113
+
114
+ // Save state (simulate crash/restart)
115
+ console.log("\nSaving state (simulating service restart)...");
116
+ const savedState = await pipeline.saveState();
117
+ console.log("State saved:", JSON.parse(savedState).stages[0].state);
118
+
119
+ // Create new pipeline and restore
120
+ console.log("\nCreating new pipeline and restoring state...");
121
+ const pipeline2 = createDspPipeline();
122
+ pipeline2.MovingAverage({ mode: "moving", windowSize: 5 });
123
+ await pipeline2.loadState(savedState);
124
+ console.log("State restored!");
125
+
126
+ // Continue processing
127
+ console.log("\nContinuing stream processing...");
128
+ let chunk3 = new Float32Array([9, 10, 11]);
129
+ let output3 = await pipeline2.process(chunk3, {
130
+ sampleRate: 100,
131
+ channels: 1,
132
+ });
133
+ console.log(
134
+ `Chunk 3: [${Array.from(chunk3)}] → [${Array.from(output3).map((v) =>
135
+ v.toFixed(2)
136
+ )}]`
137
+ );
138
+
139
+ console.log("\nStream recovered seamlessly!");
140
+ }
141
+
142
+ // Test multi-channel streaming (e.g., stereo audio, multi-sensor EMG)
143
+ async function testMultiChannelStreaming() {
144
+ console.log("\n\n=== Testing Multi-Channel Streaming ===\n");
145
+
146
+ const pipeline = createDspPipeline();
147
+ pipeline.MovingAverage({ mode: "moving", windowSize: 3 });
148
+
149
+ console.log("Processing 2-channel interleaved data stream");
150
+ console.log("Format: [ch1_s1, ch2_s1, ch1_s2, ch2_s2, ...]\n");
151
+
152
+ // Simulate 3 chunks of 2-channel data (4 samples per chunk = 2 samples per channel)
153
+ const chunks = [
154
+ new Float32Array([1, 10, 2, 20, 3, 30, 4, 40]), // 4 samples × 2 channels
155
+ new Float32Array([5, 50, 6, 60, 7, 70, 8, 80]),
156
+ new Float32Array([9, 90, 10, 100, 11, 110, 12, 120]),
157
+ ];
158
+
159
+ for (let i = 0; i < chunks.length; i++) {
160
+ console.log(`Chunk ${i + 1} input: [${Array.from(chunks[i])}]`);
161
+ const output = await pipeline.process(chunks[i], {
162
+ sampleRate: 100,
163
+ channels: 2,
164
+ });
165
+ console.log(
166
+ `Chunk ${i + 1} output: [${Array.from(output).map((v) =>
167
+ v.toFixed(2)
168
+ )}]\n`
169
+ );
170
+ }
171
+
172
+ console.log("Each channel maintains independent filter state!");
173
+ }
174
+
175
+ // Run all tests
176
+ async function runAllTests() {
177
+ try {
178
+ await testStreamingProcessing();
179
+ await testStreamInterruption();
180
+ await testMultiChannelStreaming();
181
+ console.log("\nAll streaming tests passed!");
182
+ } catch (error) {
183
+ console.error("Test failed:", error);
184
+ process.exit(1);
185
+ }
186
+ }
187
+
188
+ runAllTests();