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.
- package/.github/workflows/ci.yml +185 -0
- package/.vscode/c_cpp_properties.json +17 -0
- package/.vscode/settings.json +68 -0
- package/.vscode/tasks.json +28 -0
- package/DISCLAIMER.md +32 -0
- package/LICENSE +21 -0
- package/README.md +1803 -0
- package/ROADMAP.md +192 -0
- package/TECHNICAL_DEBT.md +165 -0
- package/binding.gyp +65 -0
- package/docs/ADVANCED_LOGGER_FEATURES.md +598 -0
- package/docs/AUTHENTICATION_SECURITY.md +396 -0
- package/docs/BACKEND_IMPROVEMENTS.md +399 -0
- package/docs/CHEBYSHEV_BIQUAD_EQ_IMPLEMENTATION.md +405 -0
- package/docs/FFT_IMPLEMENTATION.md +490 -0
- package/docs/FFT_IMPROVEMENTS_SUMMARY.md +387 -0
- package/docs/FFT_USER_GUIDE.md +494 -0
- package/docs/FILTERS_IMPLEMENTATION.md +260 -0
- package/docs/FILTER_API_GUIDE.md +418 -0
- package/docs/FIR_SIMD_OPTIMIZATION.md +175 -0
- package/docs/LOGGER_API_REFERENCE.md +350 -0
- package/docs/NOTCH_FILTER_QUICK_REF.md +121 -0
- package/docs/PHASE2_TESTS_AND_NOTCH_FILTER.md +341 -0
- package/docs/PHASES_5_7_SUMMARY.md +403 -0
- package/docs/PIPELINE_FILTER_INTEGRATION.md +446 -0
- package/docs/SIMD_OPTIMIZATIONS.md +211 -0
- package/docs/TEST_MIGRATION_SUMMARY.md +173 -0
- package/docs/TIMESERIES_IMPLEMENTATION_SUMMARY.md +322 -0
- package/docs/TIMESERIES_QUICK_REF.md +85 -0
- package/docs/advanced.md +559 -0
- package/docs/time-series-guide.md +617 -0
- package/docs/time-series-migration.md +376 -0
- package/jest.config.js +37 -0
- package/package.json +42 -0
- package/prebuilds/linux-x64/dsp-ts-redis.node +0 -0
- package/prebuilds/win32-x64/dsp-ts-redis.node +0 -0
- package/scripts/test.js +24 -0
- package/src/build/dsp-ts-redis.node +0 -0
- package/src/native/DspPipeline.cc +675 -0
- package/src/native/DspPipeline.h +44 -0
- package/src/native/FftBindings.cc +817 -0
- package/src/native/FilterBindings.cc +1001 -0
- package/src/native/IDspStage.h +53 -0
- package/src/native/adapters/InterpolatorStage.h +201 -0
- package/src/native/adapters/MeanAbsoluteValueStage.h +289 -0
- package/src/native/adapters/MovingAverageStage.h +306 -0
- package/src/native/adapters/RectifyStage.h +88 -0
- package/src/native/adapters/ResamplerStage.h +238 -0
- package/src/native/adapters/RmsStage.h +299 -0
- package/src/native/adapters/SscStage.h +121 -0
- package/src/native/adapters/VarianceStage.h +307 -0
- package/src/native/adapters/WampStage.h +114 -0
- package/src/native/adapters/WaveformLengthStage.h +115 -0
- package/src/native/adapters/ZScoreNormalizeStage.h +326 -0
- package/src/native/core/FftEngine.cc +441 -0
- package/src/native/core/FftEngine.h +224 -0
- package/src/native/core/FirFilter.cc +324 -0
- package/src/native/core/FirFilter.h +149 -0
- package/src/native/core/IirFilter.cc +576 -0
- package/src/native/core/IirFilter.h +210 -0
- package/src/native/core/MovingAbsoluteValueFilter.cc +17 -0
- package/src/native/core/MovingAbsoluteValueFilter.h +135 -0
- package/src/native/core/MovingAverageFilter.cc +18 -0
- package/src/native/core/MovingAverageFilter.h +135 -0
- package/src/native/core/MovingFftFilter.cc +291 -0
- package/src/native/core/MovingFftFilter.h +203 -0
- package/src/native/core/MovingVarianceFilter.cc +194 -0
- package/src/native/core/MovingVarianceFilter.h +114 -0
- package/src/native/core/MovingZScoreFilter.cc +215 -0
- package/src/native/core/MovingZScoreFilter.h +113 -0
- package/src/native/core/Policies.h +352 -0
- package/src/native/core/RmsFilter.cc +18 -0
- package/src/native/core/RmsFilter.h +131 -0
- package/src/native/core/SscFilter.cc +16 -0
- package/src/native/core/SscFilter.h +137 -0
- package/src/native/core/WampFilter.cc +16 -0
- package/src/native/core/WampFilter.h +101 -0
- package/src/native/core/WaveformLengthFilter.cc +17 -0
- package/src/native/core/WaveformLengthFilter.h +98 -0
- package/src/native/utils/CircularBufferArray.cc +336 -0
- package/src/native/utils/CircularBufferArray.h +62 -0
- package/src/native/utils/CircularBufferVector.cc +145 -0
- package/src/native/utils/CircularBufferVector.h +45 -0
- package/src/native/utils/NapiUtils.cc +53 -0
- package/src/native/utils/NapiUtils.h +21 -0
- package/src/native/utils/SimdOps.h +870 -0
- package/src/native/utils/SlidingWindowFilter.cc +239 -0
- package/src/native/utils/SlidingWindowFilter.h +159 -0
- package/src/native/utils/TimeSeriesBuffer.cc +205 -0
- package/src/native/utils/TimeSeriesBuffer.h +140 -0
- package/src/ts/CircularLogBuffer.ts +87 -0
- package/src/ts/DriftDetector.ts +331 -0
- package/src/ts/TopicRouter.ts +428 -0
- package/src/ts/__tests__/AdvancedDsp.test.ts +585 -0
- package/src/ts/__tests__/AuthAndEdgeCases.test.ts +241 -0
- package/src/ts/__tests__/Chaining.test.ts +387 -0
- package/src/ts/__tests__/ChebyshevBiquad.test.ts +229 -0
- package/src/ts/__tests__/CircularLogBuffer.test.ts +158 -0
- package/src/ts/__tests__/DriftDetector.test.ts +389 -0
- package/src/ts/__tests__/Fft.test.ts +484 -0
- package/src/ts/__tests__/ListState.test.ts +153 -0
- package/src/ts/__tests__/Logger.test.ts +208 -0
- package/src/ts/__tests__/LoggerAdvanced.test.ts +319 -0
- package/src/ts/__tests__/LoggerMinor.test.ts +247 -0
- package/src/ts/__tests__/MeanAbsoluteValue.test.ts +398 -0
- package/src/ts/__tests__/MovingAverage.test.ts +322 -0
- package/src/ts/__tests__/RMS.test.ts +315 -0
- package/src/ts/__tests__/Rectify.test.ts +272 -0
- package/src/ts/__tests__/Redis.test.ts +456 -0
- package/src/ts/__tests__/SlopeSignChange.test.ts +166 -0
- package/src/ts/__tests__/Tap.test.ts +164 -0
- package/src/ts/__tests__/TimeBasedExpiration.test.ts +124 -0
- package/src/ts/__tests__/TimeBasedRmsAndMav.test.ts +231 -0
- package/src/ts/__tests__/TimeBasedVarianceAndZScore.test.ts +284 -0
- package/src/ts/__tests__/TimeSeries.test.ts +254 -0
- package/src/ts/__tests__/TopicRouter.test.ts +332 -0
- package/src/ts/__tests__/TopicRouterAdvanced.test.ts +483 -0
- package/src/ts/__tests__/TopicRouterPriority.test.ts +487 -0
- package/src/ts/__tests__/Variance.test.ts +509 -0
- package/src/ts/__tests__/WaveformLength.test.ts +147 -0
- package/src/ts/__tests__/WillisonAmplitude.test.ts +197 -0
- package/src/ts/__tests__/ZScoreNormalize.test.ts +459 -0
- package/src/ts/advanced-dsp.ts +566 -0
- package/src/ts/backends.ts +1137 -0
- package/src/ts/bindings.ts +1225 -0
- package/src/ts/easter-egg.ts +42 -0
- package/src/ts/examples/MeanAbsoluteValue/test-state.ts +99 -0
- package/src/ts/examples/MeanAbsoluteValue/test-streaming.ts +269 -0
- package/src/ts/examples/MovingAverage/test-state.ts +85 -0
- package/src/ts/examples/MovingAverage/test-streaming.ts +188 -0
- package/src/ts/examples/RMS/test-state.ts +97 -0
- package/src/ts/examples/RMS/test-streaming.ts +253 -0
- package/src/ts/examples/Rectify/test-state.ts +107 -0
- package/src/ts/examples/Rectify/test-streaming.ts +242 -0
- package/src/ts/examples/Variance/test-state.ts +195 -0
- package/src/ts/examples/Variance/test-streaming.ts +260 -0
- package/src/ts/examples/ZScoreNormalize/test-state.ts +277 -0
- package/src/ts/examples/ZScoreNormalize/test-streaming.ts +306 -0
- package/src/ts/examples/advanced-dsp-examples.ts +397 -0
- package/src/ts/examples/callbacks/advanced-router-features.ts +326 -0
- package/src/ts/examples/callbacks/benchmark-circular-buffer.ts +109 -0
- package/src/ts/examples/callbacks/monitoring-example.ts +265 -0
- package/src/ts/examples/callbacks/pipeline-callbacks-example.ts +137 -0
- package/src/ts/examples/callbacks/pooled-callbacks-example.ts +274 -0
- package/src/ts/examples/callbacks/priority-routing-example.ts +277 -0
- package/src/ts/examples/callbacks/production-topic-router.ts +214 -0
- package/src/ts/examples/callbacks/topic-based-logging.ts +161 -0
- package/src/ts/examples/chaining/test-chaining-redis.ts +113 -0
- package/src/ts/examples/chaining/test-chaining.ts +52 -0
- package/src/ts/examples/emg-features-example.ts +284 -0
- package/src/ts/examples/fft-example.ts +309 -0
- package/src/ts/examples/fft-examples.ts +349 -0
- package/src/ts/examples/filter-examples.ts +320 -0
- package/src/ts/examples/list-state-example.ts +131 -0
- package/src/ts/examples/logger-example.ts +91 -0
- package/src/ts/examples/notch-filter-examples.ts +243 -0
- package/src/ts/examples/phase5/drift-detection-example.ts +290 -0
- package/src/ts/examples/phase6-7/production-observability.ts +476 -0
- package/src/ts/examples/phase6-7/redis-timeseries-integration.ts +446 -0
- package/src/ts/examples/redis/redis-example.ts +202 -0
- package/src/ts/examples/redis-example.ts +202 -0
- package/src/ts/examples/simd-benchmark.ts +126 -0
- package/src/ts/examples/tap-debugging.ts +230 -0
- package/src/ts/examples/timeseries/comparison-example.ts +290 -0
- package/src/ts/examples/timeseries/iot-sensor-example.ts +143 -0
- package/src/ts/examples/timeseries/redis-streaming-example.ts +233 -0
- package/src/ts/examples/waveform-length-example.ts +139 -0
- package/src/ts/fft.ts +722 -0
- package/src/ts/filters.ts +1078 -0
- package/src/ts/index.ts +120 -0
- package/src/ts/types.ts +589 -0
- 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();
|