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,97 @@
1
+ /**
2
+ * Test script for RMS state management (save/load/clear)
3
+ */
4
+
5
+ import { createDspPipeline } from "../../bindings.js";
6
+
7
+ async function testRmsStateManagement() {
8
+ console.log("=== Testing RMS Pipeline State Management ===\n");
9
+
10
+ // 1. Create pipeline and add RMS stage
11
+ const pipeline = createDspPipeline();
12
+ pipeline.Rms({ mode: "moving", windowSize: 3 });
13
+
14
+ console.log("Pipeline created with RMS filter (window=3)");
15
+
16
+ // 2. Process some data (sine wave samples)
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(
25
+ "Output (RMS values):",
26
+ Array.from(output1).map((v) => v.toFixed(4))
27
+ );
28
+
29
+ // 3. Save state to JSON
30
+ console.log("\n--- Saving State ---");
31
+ const stateJson = await pipeline.saveState();
32
+ console.log("State saved:");
33
+ console.log(JSON.stringify(JSON.parse(stateJson), null, 2));
34
+
35
+ // 4. Continue processing
36
+ const input2 = new Float32Array([6, -7, 8]);
37
+ console.log("\nProcessing second batch:", Array.from(input2));
38
+
39
+ const output2 = await pipeline.processCopy(input2, {
40
+ sampleRate: 1000,
41
+ channels: 1,
42
+ });
43
+ console.log(
44
+ "Output (RMS values):",
45
+ Array.from(output2).map((v) => v.toFixed(4))
46
+ );
47
+
48
+ // 5. Save state again
49
+ const stateJson2 = await pipeline.saveState();
50
+ console.log("\nState after second batch:");
51
+ console.log(JSON.stringify(JSON.parse(stateJson2), null, 2));
52
+
53
+ // 6. Create new pipeline and load state
54
+ console.log("\n--- Creating New Pipeline ---");
55
+ const pipeline2 = createDspPipeline();
56
+ pipeline2.Rms({ mode: "moving", windowSize: 3 });
57
+
58
+ console.log("Loading previous state...");
59
+ const loaded = await pipeline2.loadState(stateJson2);
60
+ console.log("Load successful:", loaded);
61
+
62
+ // 7. Process with restored state
63
+ const input3 = new Float32Array([9, -10]);
64
+ console.log("\nProcessing with restored pipeline:", Array.from(input3));
65
+
66
+ const output3 = await pipeline2.processCopy(input3, {
67
+ sampleRate: 1000,
68
+ channels: 1,
69
+ });
70
+ console.log(
71
+ "Output (RMS values):",
72
+ Array.from(output3).map((v) => v.toFixed(4))
73
+ );
74
+
75
+ // 8. Clear state
76
+ console.log("\n--- Clearing State ---");
77
+ pipeline2.clearState();
78
+ console.log("State cleared");
79
+
80
+ // 9. Process after clear (should start fresh)
81
+ const input4 = new Float32Array([1, -2, 3]);
82
+ console.log("\nProcessing after clear:", Array.from(input4));
83
+
84
+ const output4 = await pipeline2.processCopy(input4, {
85
+ sampleRate: 1000,
86
+ channels: 1,
87
+ });
88
+ console.log(
89
+ "Output (fresh start):",
90
+ Array.from(output4).map((v) => v.toFixed(4))
91
+ );
92
+
93
+ console.log("\nRMS state management test complete!");
94
+ }
95
+
96
+ // Run test
97
+ testRmsStateManagement().catch(console.error);
@@ -0,0 +1,253 @@
1
+ /**
2
+ * Test streaming data processing with RMS and state continuity
3
+ */
4
+
5
+ import { createDspPipeline } from "../../bindings.js";
6
+
7
+ // Simulate streaming chunks of data (e.g., EMG signals, audio)
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)
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 with varying amplitude + noise)
20
+ for (let j = 0; j < remainingSamples; j++) {
21
+ const t = (i + j) / 100;
22
+ const amplitude = 1 + 0.5 * Math.sin(2 * Math.PI * 0.5 * t); // Slow amplitude modulation
23
+ chunk[j] =
24
+ amplitude * Math.sin(2 * Math.PI * 10 * t) + Math.random() * 0.1; // 10Hz carrier + noise
25
+ }
26
+
27
+ yield chunk;
28
+ }
29
+ }
30
+
31
+ async function testStreamingRmsProcessing() {
32
+ console.log("=== Testing Streaming RMS Processing ===\n");
33
+
34
+ const pipeline = createDspPipeline();
35
+ pipeline.Rms({ mode: "moving", windowSize: 20 }); // Calculate RMS over 20-sample window
36
+
37
+ console.log("Pipeline created with 20-sample RMS window");
38
+ console.log(
39
+ "Simulating real-time data stream (100 samples, 20 samples/chunk)\n"
40
+ );
41
+
42
+ let chunkNumber = 0;
43
+ let totalProcessed = 0;
44
+ const allOutputs: number[] = [];
45
+
46
+ // Process streaming data
47
+ for await (const chunk of simulateDataStream(100, 20)) {
48
+ chunkNumber++;
49
+ console.log(
50
+ `Chunk ${chunkNumber}: Received ${
51
+ chunk.length
52
+ } samples [${chunk[0].toFixed(3)}, ${chunk[1].toFixed(3)}, ...]`
53
+ );
54
+
55
+ // Process chunk - pipeline remembers state from previous chunks
56
+ const output = await pipeline.process(chunk, {
57
+ sampleRate: 100,
58
+ channels: 1,
59
+ });
60
+
61
+ console.log(
62
+ ` RMS output → [${output[0].toFixed(3)}, ${output[1].toFixed(
63
+ 3
64
+ )}, ...]\n`
65
+ );
66
+
67
+ allOutputs.push(...Array.from(output));
68
+ totalProcessed += chunk.length;
69
+ }
70
+
71
+ console.log(`Stream complete: ${totalProcessed} samples processed`);
72
+ console.log(
73
+ ` First 10 RMS values: [${allOutputs
74
+ .slice(0, 10)
75
+ .map((v) => v.toFixed(3))
76
+ .join(", ")}]`
77
+ );
78
+ console.log(
79
+ ` Last 10 RMS values: [${allOutputs
80
+ .slice(-10)
81
+ .map((v) => v.toFixed(3))
82
+ .join(", ")}]`
83
+ );
84
+ }
85
+
86
+ // Test with state persistence across stream interruption
87
+ async function testStreamInterruption() {
88
+ console.log("\n\n=== Testing RMS Stream Interruption & Recovery ===\n");
89
+
90
+ const pipeline = createDspPipeline();
91
+ pipeline.Rms({ mode: "moving", windowSize: 5 });
92
+
93
+ console.log("Processing first part of stream...");
94
+ let chunk1 = new Float32Array([1, -2, 3, -4, 5]);
95
+ let output1 = await pipeline.process(chunk1, {
96
+ sampleRate: 100,
97
+ channels: 1,
98
+ });
99
+ console.log(
100
+ `Chunk 1: [${Array.from(chunk1)}] → [${Array.from(output1).map((v) =>
101
+ v.toFixed(3)
102
+ )}]`
103
+ );
104
+
105
+ let chunk2 = new Float32Array([6, -7, 8]);
106
+ let output2 = await pipeline.process(chunk2, {
107
+ sampleRate: 100,
108
+ channels: 1,
109
+ });
110
+ console.log(
111
+ `Chunk 2: [${Array.from(chunk2)}] → [${Array.from(output2).map((v) =>
112
+ v.toFixed(3)
113
+ )}]`
114
+ );
115
+
116
+ // Save state (simulate crash/restart)
117
+ console.log("\nSaving state (simulating service restart)...");
118
+ const savedState = await pipeline.saveState();
119
+ const parsedState = JSON.parse(savedState);
120
+ console.log("State saved:");
121
+ console.log(" - Window size:", parsedState.stages[0].state.windowSize);
122
+ console.log(" - Channels:", parsedState.stages[0].state.numChannels);
123
+ console.log(
124
+ " - Buffer:",
125
+ parsedState.stages[0].state.channels[0].buffer.map((v: number) =>
126
+ v.toFixed(3)
127
+ )
128
+ );
129
+ console.log(
130
+ " - Running sum of squares:",
131
+ parsedState.stages[0].state.channels[0].runningSumOfSquares.toFixed(3)
132
+ );
133
+
134
+ // Create new pipeline and restore
135
+ console.log("\nCreating new pipeline and restoring state...");
136
+ const pipeline2 = createDspPipeline();
137
+ pipeline2.Rms({ mode: "moving", windowSize: 5 });
138
+ await pipeline2.loadState(savedState);
139
+ console.log("State restored!");
140
+
141
+ // Continue processing
142
+ console.log("\nContinuing stream processing...");
143
+ let chunk3 = new Float32Array([9, -10, 11]);
144
+ let output3 = await pipeline2.process(chunk3, {
145
+ sampleRate: 100,
146
+ channels: 1,
147
+ });
148
+ console.log(
149
+ `Chunk 3: [${Array.from(chunk3)}] → [${Array.from(output3).map((v) =>
150
+ v.toFixed(3)
151
+ )}]`
152
+ );
153
+
154
+ console.log("\nRMS stream recovered seamlessly!");
155
+ }
156
+
157
+ // Test multi-channel streaming (e.g., stereo audio, multi-sensor EMG)
158
+ async function testMultiChannelRmsStreaming() {
159
+ console.log("\n\n=== Testing Multi-Channel RMS Streaming ===\n");
160
+
161
+ const pipeline = createDspPipeline();
162
+ pipeline.Rms({ mode: "moving", windowSize: 3 });
163
+
164
+ console.log("Processing 2-channel interleaved data stream (RMS per channel)");
165
+ console.log("Format: [ch1_s1, ch2_s1, ch1_s2, ch2_s2, ...]\n");
166
+
167
+ // Simulate 3 chunks of 2-channel data (4 samples per chunk = 2 samples per channel)
168
+ // Channel 1: positive values, Channel 2: negative values
169
+ const chunks = [
170
+ new Float32Array([1, -10, 2, -20, 3, -30, 4, -40]), // 4 samples × 2 channels
171
+ new Float32Array([5, -50, 6, -60, 7, -70, 8, -80]),
172
+ new Float32Array([9, -90, 10, -100, 11, -110, 12, -120]),
173
+ ];
174
+
175
+ for (let i = 0; i < chunks.length; i++) {
176
+ console.log(`Chunk ${i + 1} input: [${Array.from(chunks[i])}]`);
177
+ const output = await pipeline.process(chunks[i], {
178
+ sampleRate: 100,
179
+ channels: 2,
180
+ });
181
+ console.log(
182
+ `Chunk ${i + 1} RMS output: [${Array.from(output).map((v) =>
183
+ v.toFixed(2)
184
+ )}]\n`
185
+ );
186
+ }
187
+
188
+ console.log("Each channel maintains independent RMS filter state!");
189
+ console.log(
190
+ "Note: RMS converts negative values to positive (magnitude only)"
191
+ );
192
+ }
193
+
194
+ // Test RMS with actual use case: envelope detection
195
+ async function testEnvelopeDetection() {
196
+ console.log("\n\n=== Testing RMS for Envelope Detection ===\n");
197
+
198
+ const pipeline = createDspPipeline();
199
+ pipeline.Rms({ mode: "moving", windowSize: 10 }); // 10-sample RMS window for envelope
200
+
201
+ console.log("Use case: Detecting signal envelope/amplitude over time");
202
+ console.log("Input: Amplitude-modulated sine wave\n");
203
+
204
+ // Generate amplitude-modulated signal: carrier × envelope
205
+ const samples = 50;
206
+ const carrierFreq = 20; // Hz
207
+ const envelopeFreq = 2; // Hz (slow amplitude change)
208
+
209
+ const input = new Float32Array(samples);
210
+ for (let i = 0; i < samples; i++) {
211
+ const t = i / 100;
212
+ const envelope = 0.5 + 0.5 * Math.sin(2 * Math.PI * envelopeFreq * t);
213
+ const carrier = Math.sin(2 * Math.PI * carrierFreq * t);
214
+ input[i] = envelope * carrier;
215
+ }
216
+
217
+ console.log(
218
+ "First 10 input samples:",
219
+ Array.from(input.slice(0, 10)).map((v) => v.toFixed(3))
220
+ );
221
+
222
+ const output = await pipeline.process(input, {
223
+ sampleRate: 100,
224
+ channels: 1,
225
+ });
226
+
227
+ console.log(
228
+ "First 10 RMS values (envelope):",
229
+ Array.from(output.slice(0, 10)).map((v) => v.toFixed(3))
230
+ );
231
+ console.log(
232
+ "Last 10 RMS values (envelope):",
233
+ Array.from(output.slice(-10)).map((v) => v.toFixed(3))
234
+ );
235
+
236
+ console.log("\nRMS successfully tracks signal envelope!");
237
+ }
238
+
239
+ // Run all tests
240
+ async function runAllTests() {
241
+ try {
242
+ await testStreamingRmsProcessing();
243
+ await testStreamInterruption();
244
+ await testMultiChannelRmsStreaming();
245
+ await testEnvelopeDetection();
246
+ console.log("\nAll RMS streaming tests passed!");
247
+ } catch (error) {
248
+ console.error("Test failed:", error);
249
+ process.exit(1);
250
+ }
251
+ }
252
+
253
+ runAllTests();
@@ -0,0 +1,107 @@
1
+ /**
2
+ * Test script for Rectify state management (save/load/clear)
3
+ */
4
+
5
+ import { createDspPipeline } from "../../bindings.js";
6
+
7
+ async function testRectifyStateManagement() {
8
+ console.log("=== Testing Rectify Pipeline State Management ===\n");
9
+
10
+ // Test 1: Full-wave rectification (default)
11
+ console.log("--- Test 1: Full-Wave Rectification (Default) ---");
12
+ const pipelineFull = createDspPipeline();
13
+ pipelineFull.Rectify(); // Default is full-wave
14
+
15
+ const inputFull = new Float32Array([1, -2, 3, -4, 5, -6]);
16
+ console.log("Input:", Array.from(inputFull));
17
+
18
+ const outputFull = await pipelineFull.processCopy(inputFull, {
19
+ sampleRate: 1000,
20
+ channels: 1,
21
+ });
22
+ console.log(
23
+ "Output (full-wave):",
24
+ Array.from(outputFull).map((v) => v.toFixed(1))
25
+ );
26
+ console.log(
27
+ "Expected: [1.0, 2.0, 3.0, 4.0, 5.0, 6.0] (all positive, absolute values)\n"
28
+ );
29
+
30
+ // Test 2: Half-wave rectification
31
+ console.log("--- Test 2: Half-Wave Rectification ---");
32
+ const pipelineHalf = createDspPipeline();
33
+ pipelineHalf.Rectify({ mode: "half" });
34
+
35
+ const inputHalf = new Float32Array([1, -2, 3, -4, 5, -6]);
36
+ console.log("Input:", Array.from(inputHalf));
37
+
38
+ const outputHalf = await pipelineHalf.processCopy(inputHalf, {
39
+ sampleRate: 1000,
40
+ channels: 1,
41
+ });
42
+ console.log(
43
+ "Output (half-wave):",
44
+ Array.from(outputHalf).map((v) => v.toFixed(1))
45
+ );
46
+ console.log(
47
+ "Expected: [1.0, 0.0, 3.0, 0.0, 5.0, 0.0] (negatives become zero)\n"
48
+ );
49
+
50
+ // Test 3: State serialization (full-wave)
51
+ console.log("--- Test 3: State Serialization (Full-Wave) ---");
52
+ const stateFull = await pipelineFull.saveState();
53
+ const parsedStateFull = JSON.parse(stateFull);
54
+ console.log("Saved state:");
55
+ console.log(JSON.stringify(parsedStateFull, null, 2));
56
+ console.log(`Mode: ${parsedStateFull.stages[0].state.mode}\n`);
57
+
58
+ // Test 4: State serialization (half-wave)
59
+ console.log("--- Test 4: State Serialization (Half-Wave) ---");
60
+ const stateHalf = await pipelineHalf.saveState();
61
+ const parsedStateHalf = JSON.parse(stateHalf);
62
+ console.log("Saved state:");
63
+ console.log(JSON.stringify(parsedStateHalf, null, 2));
64
+ console.log(`Mode: ${parsedStateHalf.stages[0].state.mode}\n`);
65
+
66
+ // Test 5: Load state and verify mode preservation
67
+ console.log("--- Test 5: Load State & Verify Mode ---");
68
+ const pipelineNew = createDspPipeline();
69
+ pipelineNew.Rectify(); // Start with full-wave
70
+
71
+ console.log("Loading half-wave state into new pipeline...");
72
+ await pipelineNew.loadState(stateHalf);
73
+
74
+ const testInput = new Float32Array([2, -3, 4, -5]);
75
+ const testOutput = await pipelineNew.processCopy(testInput, {
76
+ sampleRate: 1000,
77
+ channels: 1,
78
+ });
79
+ console.log("Input:", Array.from(testInput));
80
+ console.log(
81
+ "Output:",
82
+ Array.from(testOutput).map((v) => v.toFixed(1))
83
+ );
84
+ console.log("Expected: [2.0, 0.0, 4.0, 0.0] (half-wave mode preserved)\n");
85
+
86
+ // Test 6: Clear state (no effect on Rectify, but tests the interface)
87
+ console.log("--- Test 6: Clear State ---");
88
+ pipelineNew.clearState();
89
+ console.log("State cleared (Rectify has no internal buffers)\n");
90
+
91
+ // Process after clear - should still work with same mode
92
+ const afterClear = await pipelineNew.processCopy(new Float32Array([1, -2]), {
93
+ sampleRate: 1000,
94
+ channels: 1,
95
+ });
96
+ console.log("After clear, input: [1, -2]");
97
+ console.log(
98
+ "Output:",
99
+ Array.from(afterClear).map((v) => v.toFixed(1))
100
+ );
101
+ console.log("Mode still preserved after clear\n");
102
+
103
+ console.log("Rectify state management test complete!");
104
+ }
105
+
106
+ // Run test
107
+ testRectifyStateManagement().catch(console.error);
@@ -0,0 +1,242 @@
1
+ /**
2
+ * Test streaming data processing with Rectify
3
+ */
4
+
5
+ import { createDspPipeline } from "../../bindings.js";
6
+
7
+ // Simulate streaming chunks of data (e.g., AC audio signal, EMG with negative artifacts)
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
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 bipolar signal (positive and negative values)
20
+ for (let j = 0; j < remainingSamples; j++) {
21
+ const t = (i + j) / 100;
22
+ chunk[j] = Math.sin(2 * Math.PI * 5 * t); // 5Hz sine wave (bipolar: -1 to +1)
23
+ }
24
+
25
+ yield chunk;
26
+ }
27
+ }
28
+
29
+ async function testStreamingFullWaveRectification() {
30
+ console.log("=== Testing Streaming Full-Wave Rectification ===\n");
31
+
32
+ const pipeline = createDspPipeline();
33
+ pipeline.Rectify({ mode: "full" }); // Full-wave: absolute value
34
+
35
+ console.log("Pipeline created with full-wave rectifier");
36
+ console.log(
37
+ "Simulating real-time bipolar signal 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 - rectify converts all to positive
54
+ const output = await pipeline.process(chunk, {
55
+ sampleRate: 100,
56
+ channels: 1,
57
+ });
58
+
59
+ console.log(
60
+ ` Rectified → [${output[0].toFixed(3)}, ${output[1].toFixed(
61
+ 3
62
+ )}, ...] (all positive)\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
+ ` All values ≥ 0: ${allOutputs.every((v) => v >= 0) ? "Yes" : "No"}`
78
+ );
79
+ }
80
+
81
+ async function testStreamingHalfWaveRectification() {
82
+ console.log("\n\n=== Testing Streaming Half-Wave Rectification ===\n");
83
+
84
+ const pipeline = createDspPipeline();
85
+ pipeline.Rectify({ mode: "half" }); // Half-wave: negatives → 0
86
+
87
+ console.log("Pipeline created with half-wave rectifier");
88
+ console.log("Simulating bipolar signal stream (zeros out negative half)\n");
89
+
90
+ let chunkNumber = 0;
91
+ const allOutputs: number[] = [];
92
+
93
+ for await (const chunk of simulateDataStream(100, 20)) {
94
+ chunkNumber++;
95
+ const output = await pipeline.process(chunk, {
96
+ sampleRate: 100,
97
+ channels: 1,
98
+ });
99
+
100
+ console.log(
101
+ `Chunk ${chunkNumber}: [${output[0].toFixed(3)}, ${output[1].toFixed(
102
+ 3
103
+ )}, ...] (negatives → 0)`
104
+ );
105
+
106
+ allOutputs.push(...Array.from(output));
107
+ }
108
+
109
+ console.log(`\nStream complete`);
110
+ const zeroCount = allOutputs.filter((v) => v === 0).length;
111
+ const positiveCount = allOutputs.filter((v) => v > 0).length;
112
+ console.log(` Zero values: ${zeroCount}`);
113
+ console.log(` Positive values: ${positiveCount}`);
114
+ console.log(
115
+ ` All values ≥ 0: ${allOutputs.every((v) => v >= 0) ? "Yes" : "No"}`
116
+ );
117
+ }
118
+
119
+ // Test state persistence across stream interruption
120
+ async function testStreamInterruption() {
121
+ console.log("\n\n=== Testing Rectify Stream Interruption & Recovery ===\n");
122
+
123
+ const pipeline = createDspPipeline();
124
+ pipeline.Rectify({ mode: "half" });
125
+
126
+ console.log("Processing first part of stream (half-wave)...");
127
+ let chunk1 = new Float32Array([1, -2, 3, -4, 5]);
128
+ let output1 = await pipeline.process(chunk1, {
129
+ sampleRate: 100,
130
+ channels: 1,
131
+ });
132
+ console.log(`Chunk 1: [${Array.from(chunk1)}] → [${Array.from(output1)}]`);
133
+
134
+ // Save state (simulate crash/restart)
135
+ console.log("\nSaving state (simulating service restart)...");
136
+ const savedState = await pipeline.saveState();
137
+ const parsedState = JSON.parse(savedState);
138
+ console.log("State saved:");
139
+ console.log(` - Type: ${parsedState.stages[0].type}`);
140
+ console.log(` - Mode: ${parsedState.stages[0].state.mode}`);
141
+
142
+ // Create new pipeline and restore
143
+ console.log("\nCreating new pipeline and restoring state...");
144
+ const pipeline2 = createDspPipeline();
145
+ pipeline2.Rectify({ mode: "full" }); // Start with different mode
146
+ await pipeline2.loadState(savedState); // Should restore to half-wave
147
+ console.log("State restored (mode should be 'half')!");
148
+
149
+ // Continue processing
150
+ console.log("\nContinuing stream processing...");
151
+ let chunk2 = new Float32Array([6, -7, 8, -9]);
152
+ let output2 = await pipeline2.process(chunk2, {
153
+ sampleRate: 100,
154
+ channels: 1,
155
+ });
156
+ console.log(`Chunk 2: [${Array.from(chunk2)}] → [${Array.from(output2)}]`);
157
+ console.log(`Expected: [6, 0, 8, 0] (half-wave mode restored)`);
158
+
159
+ console.log("\nRectify stream recovered seamlessly!");
160
+ }
161
+
162
+ // Test multi-channel rectification
163
+ async function testMultiChannelRectify() {
164
+ console.log("\n\n=== Testing Multi-Channel Rectification ===\n");
165
+
166
+ const pipeline = createDspPipeline();
167
+ pipeline.Rectify({ mode: "full" });
168
+
169
+ console.log("Processing 2-channel interleaved data (full-wave)");
170
+ console.log("Format: [ch1_s1, ch2_s1, ch1_s2, ch2_s2, ...]\n");
171
+
172
+ // Channel 1: positive bias, Channel 2: negative bias
173
+ const chunks = [
174
+ new Float32Array([1, -10, 2, -20, -3, 30, -4, 40]),
175
+ new Float32Array([5, -50, -6, 60, 7, -70, -8, 80]),
176
+ ];
177
+
178
+ for (let i = 0; i < chunks.length; i++) {
179
+ console.log(`Chunk ${i + 1} input: [${Array.from(chunks[i])}]`);
180
+ const output = await pipeline.process(chunks[i], {
181
+ sampleRate: 100,
182
+ channels: 2,
183
+ });
184
+ console.log(`Chunk ${i + 1} output: [${Array.from(output)}]`);
185
+ console.log(
186
+ ` All positive: ${
187
+ Array.from(output).every((v) => v >= 0) ? "Yes" : "No"
188
+ }\n`
189
+ );
190
+ }
191
+
192
+ console.log("Multi-channel rectification works independently per sample!");
193
+ }
194
+
195
+ // Test practical use case: EMG signal pre-processing
196
+ async function testEmgPreProcessing() {
197
+ console.log("\n\n=== Testing EMG Signal Pre-Processing ===\n");
198
+
199
+ console.log("Use case: Prepare EMG for envelope detection");
200
+ console.log("Pipeline: Full-wave Rectify (converts to magnitude)\n");
201
+
202
+ const pipeline = createDspPipeline();
203
+ pipeline.Rectify({ mode: "full" });
204
+
205
+ // Simulate raw EMG with positive and negative spikes
206
+ const rawEmg = new Float32Array([
207
+ 0.1, -0.3, 0.5, -0.8, 1.2, -1.5, 0.9, -0.6, 0.3, -0.1,
208
+ ]);
209
+
210
+ console.log(
211
+ "Raw EMG (bipolar):",
212
+ Array.from(rawEmg).map((v) => v.toFixed(2))
213
+ );
214
+
215
+ const rectified = await pipeline.process(rawEmg, {
216
+ sampleRate: 2000,
217
+ channels: 1,
218
+ });
219
+
220
+ console.log(
221
+ "Rectified EMG (magnitude):",
222
+ Array.from(rectified).map((v) => v.toFixed(2))
223
+ );
224
+ console.log("\nRectified signal is ready for RMS/envelope detection!");
225
+ }
226
+
227
+ // Run all tests
228
+ async function runAllTests() {
229
+ try {
230
+ await testStreamingFullWaveRectification();
231
+ await testStreamingHalfWaveRectification();
232
+ await testStreamInterruption();
233
+ await testMultiChannelRectify();
234
+ await testEmgPreProcessing();
235
+ console.log("\nAll Rectify streaming tests passed!");
236
+ } catch (error) {
237
+ console.error("Test failed:", error);
238
+ process.exit(1);
239
+ }
240
+ }
241
+
242
+ runAllTests();