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,290 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Comparison: Sample-Based vs Time-Based Processing
|
|
3
|
+
*
|
|
4
|
+
* This example demonstrates the difference between legacy sample-based
|
|
5
|
+
* and new time-based processing, especially with irregular data.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { createDspPipeline } from "../../bindings";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Generate uniform sensor data (ideal case)
|
|
12
|
+
*/
|
|
13
|
+
function generateUniformData(count: number, sampleRate: number) {
|
|
14
|
+
const samples = new Float32Array(count);
|
|
15
|
+
const timestamps = new Float32Array(count);
|
|
16
|
+
|
|
17
|
+
for (let i = 0; i < count; i++) {
|
|
18
|
+
samples[i] = Math.sin(i * 0.1) * 10 + (Math.random() - 0.5) * 2;
|
|
19
|
+
timestamps[i] = i * (1000 / sampleRate); // Milliseconds
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
return { samples, timestamps };
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Generate irregular sensor data (realistic case)
|
|
27
|
+
*/
|
|
28
|
+
function generateIrregularData(count: number, avgInterval: number) {
|
|
29
|
+
const samples = new Float32Array(count);
|
|
30
|
+
const timestamps = new Float32Array(count);
|
|
31
|
+
let currentTime = 0;
|
|
32
|
+
|
|
33
|
+
for (let i = 0; i < count; i++) {
|
|
34
|
+
samples[i] = Math.sin(i * 0.1) * 10 + (Math.random() - 0.5) * 2;
|
|
35
|
+
|
|
36
|
+
// Random interval: 50% to 150% of average
|
|
37
|
+
const interval = avgInterval * (0.5 + Math.random());
|
|
38
|
+
currentTime += interval;
|
|
39
|
+
timestamps[i] = currentTime;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return { samples, timestamps };
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Compare sample-based vs time-based on UNIFORM data
|
|
47
|
+
*/
|
|
48
|
+
async function compareUniformData() {
|
|
49
|
+
console.log("=== Comparison on UNIFORM Data (Ideal Case) ===\n");
|
|
50
|
+
|
|
51
|
+
const sampleRate = 100; // 100 Hz
|
|
52
|
+
const { samples, timestamps } = generateUniformData(100, sampleRate);
|
|
53
|
+
|
|
54
|
+
// Sample-based pipeline (legacy)
|
|
55
|
+
const pipelineSample = createDspPipeline();
|
|
56
|
+
pipelineSample.MovingAverage({
|
|
57
|
+
mode: "moving",
|
|
58
|
+
windowSize: 50, // 50 samples
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
// Time-based pipeline (new)
|
|
62
|
+
const pipelineTime = createDspPipeline();
|
|
63
|
+
pipelineTime.MovingAverage({
|
|
64
|
+
mode: "moving",
|
|
65
|
+
windowDuration: 500, // 500ms (equivalent to 50 samples at 100 Hz)
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
// Process with sample-based
|
|
69
|
+
const resultSample = await pipelineSample.process(samples, {
|
|
70
|
+
sampleRate,
|
|
71
|
+
channels: 1,
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
// Process with time-based
|
|
75
|
+
const resultTime = await pipelineTime.process(samples, timestamps, {
|
|
76
|
+
channels: 1,
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
// Compare results
|
|
80
|
+
console.log("Input: Uniform 100 Hz sampling, 100 samples");
|
|
81
|
+
console.log("Window: 50 samples (sample-based) vs 500ms (time-based)");
|
|
82
|
+
console.log("\nComparison (first 5 samples):");
|
|
83
|
+
console.log(" Index | Input | Sample-Based | Time-Based | Difference");
|
|
84
|
+
console.log(" ------|----------|--------------|------------|------------");
|
|
85
|
+
|
|
86
|
+
for (let i = 0; i < 5; i++) {
|
|
87
|
+
const diff = Math.abs(resultSample[i] - resultTime[i]);
|
|
88
|
+
console.log(
|
|
89
|
+
` ${i.toString().padStart(5)} | ${samples[i]
|
|
90
|
+
.toFixed(3)
|
|
91
|
+
.padStart(8)} | ` +
|
|
92
|
+
`${resultSample[i].toFixed(3).padStart(12)} | ${resultTime[i]
|
|
93
|
+
.toFixed(3)
|
|
94
|
+
.padStart(10)} | ` +
|
|
95
|
+
`${diff.toFixed(6).padStart(10)}`
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Calculate max difference
|
|
100
|
+
let maxDiff = 0;
|
|
101
|
+
for (let i = 0; i < samples.length; i++) {
|
|
102
|
+
maxDiff = Math.max(maxDiff, Math.abs(resultSample[i] - resultTime[i]));
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
console.log(
|
|
106
|
+
`\n✅ Max difference: ${maxDiff.toFixed(6)} (essentially identical)`
|
|
107
|
+
);
|
|
108
|
+
console.log(
|
|
109
|
+
" → On uniform data, both methods produce equivalent results\n"
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Compare sample-based vs time-based on IRREGULAR data
|
|
115
|
+
*/
|
|
116
|
+
async function compareIrregularData() {
|
|
117
|
+
console.log("=== Comparison on IRREGULAR Data (Realistic Case) ===\n");
|
|
118
|
+
|
|
119
|
+
const avgInterval = 10; // ~100 Hz average, but irregular
|
|
120
|
+
const { samples, timestamps } = generateIrregularData(100, avgInterval);
|
|
121
|
+
|
|
122
|
+
// Calculate actual intervals
|
|
123
|
+
const intervals: number[] = [];
|
|
124
|
+
for (let i = 1; i < timestamps.length; i++) {
|
|
125
|
+
intervals.push(timestamps[i] - timestamps[i - 1]);
|
|
126
|
+
}
|
|
127
|
+
const avgActualInterval =
|
|
128
|
+
intervals.reduce((a, b) => a + b, 0) / intervals.length;
|
|
129
|
+
const minInterval = Math.min(...intervals);
|
|
130
|
+
const maxInterval = Math.max(...intervals);
|
|
131
|
+
|
|
132
|
+
console.log("Input: Irregular sampling");
|
|
133
|
+
console.log(
|
|
134
|
+
` Average interval: ${avgActualInterval.toFixed(2)}ms (~${(
|
|
135
|
+
1000 / avgActualInterval
|
|
136
|
+
).toFixed(0)} Hz)`
|
|
137
|
+
);
|
|
138
|
+
console.log(
|
|
139
|
+
` Interval range: ${minInterval.toFixed(2)}ms - ${maxInterval.toFixed(
|
|
140
|
+
2
|
|
141
|
+
)}ms`
|
|
142
|
+
);
|
|
143
|
+
console.log(
|
|
144
|
+
` Jitter: ${(
|
|
145
|
+
((maxInterval - minInterval) / avgActualInterval) *
|
|
146
|
+
100
|
|
147
|
+
).toFixed(1)}%\n`
|
|
148
|
+
);
|
|
149
|
+
|
|
150
|
+
// Sample-based pipeline (treats irregular data as uniform)
|
|
151
|
+
const pipelineSample = createDspPipeline();
|
|
152
|
+
pipelineSample.MovingAverage({
|
|
153
|
+
mode: "moving",
|
|
154
|
+
windowSize: 50, // 50 samples (ignores timestamps)
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
// Time-based pipeline (respects timestamps)
|
|
158
|
+
const pipelineTime = createDspPipeline();
|
|
159
|
+
pipelineTime.MovingAverage({
|
|
160
|
+
mode: "moving",
|
|
161
|
+
windowDuration: 500, // 500ms (uses actual timestamps)
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
// Process with sample-based (ignores irregularity)
|
|
165
|
+
const resultSample = await pipelineSample.process(samples, {
|
|
166
|
+
channels: 1, // No sampleRate = sequential timestamps [0,1,2,...]
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
// Process with time-based (respects timestamps)
|
|
170
|
+
const resultTime = await pipelineTime.process(samples, timestamps, {
|
|
171
|
+
channels: 1,
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
// Compare results
|
|
175
|
+
console.log("Window: 50 samples (sample-based) vs 500ms (time-based)");
|
|
176
|
+
console.log("\nComparison (samples 20-24, where differences emerge):");
|
|
177
|
+
console.log(
|
|
178
|
+
" Index | Input | Sample-Based | Time-Based | Difference | Δt (ms)"
|
|
179
|
+
);
|
|
180
|
+
console.log(
|
|
181
|
+
" ------|----------|--------------|------------|------------|--------"
|
|
182
|
+
);
|
|
183
|
+
|
|
184
|
+
for (let i = 20; i < 25; i++) {
|
|
185
|
+
const diff = Math.abs(resultSample[i] - resultTime[i]);
|
|
186
|
+
const deltaT = i > 0 ? timestamps[i] - timestamps[i - 1] : 0;
|
|
187
|
+
console.log(
|
|
188
|
+
` ${i.toString().padStart(5)} | ${samples[i]
|
|
189
|
+
.toFixed(3)
|
|
190
|
+
.padStart(8)} | ` +
|
|
191
|
+
`${resultSample[i].toFixed(3).padStart(12)} | ${resultTime[i]
|
|
192
|
+
.toFixed(3)
|
|
193
|
+
.padStart(10)} | ` +
|
|
194
|
+
`${diff.toFixed(3).padStart(10)} | ${deltaT.toFixed(2).padStart(6)}`
|
|
195
|
+
);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// Calculate statistics on differences
|
|
199
|
+
let sumDiff = 0;
|
|
200
|
+
let maxDiff = 0;
|
|
201
|
+
for (let i = 0; i < samples.length; i++) {
|
|
202
|
+
const diff = Math.abs(resultSample[i] - resultTime[i]);
|
|
203
|
+
sumDiff += diff;
|
|
204
|
+
maxDiff = Math.max(maxDiff, diff);
|
|
205
|
+
}
|
|
206
|
+
const avgDiff = sumDiff / samples.length;
|
|
207
|
+
|
|
208
|
+
console.log(`\n⚠️ Average difference: ${avgDiff.toFixed(3)}`);
|
|
209
|
+
console.log(`⚠️ Max difference: ${maxDiff.toFixed(3)}`);
|
|
210
|
+
console.log(" → On irregular data, results diverge significantly!");
|
|
211
|
+
console.log(" → Time-based processing accounts for actual timing\n");
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Demonstrate when to use each approach
|
|
216
|
+
*/
|
|
217
|
+
async function showUseCases() {
|
|
218
|
+
console.log("=== When to Use Each Approach ===\n");
|
|
219
|
+
|
|
220
|
+
console.log("✅ USE SAMPLE-BASED (windowSize) when:");
|
|
221
|
+
console.log(" • Data is uniformly sampled (ADC, audio, high-rate sensors)");
|
|
222
|
+
console.log(" • Sample count is more important than time");
|
|
223
|
+
console.log(" • Maximum performance is critical");
|
|
224
|
+
console.log(" • Working with legacy code\n");
|
|
225
|
+
|
|
226
|
+
console.log("✅ USE TIME-BASED (windowDuration) when:");
|
|
227
|
+
console.log(" • Data arrives at irregular intervals (network, IoT)");
|
|
228
|
+
console.log(' • Time-based analysis is required ("last 5 seconds")');
|
|
229
|
+
console.log(" • Dealing with real-world sensor delays/jitter");
|
|
230
|
+
console.log(" • Need intuitive window specification\n");
|
|
231
|
+
|
|
232
|
+
console.log("✅ USE BOTH (windowSize + windowDuration) when:");
|
|
233
|
+
console.log(" • Need to enforce maximum sample count AND time window");
|
|
234
|
+
console.log(" • Want memory limits with time-based semantics");
|
|
235
|
+
console.log(" • Processing variable-rate data with hard limits\n");
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* Migration example: converting existing code
|
|
240
|
+
*/
|
|
241
|
+
async function showMigration() {
|
|
242
|
+
console.log("=== Migration Example ===\n");
|
|
243
|
+
|
|
244
|
+
console.log("BEFORE (Sample-Based):");
|
|
245
|
+
console.log("```typescript");
|
|
246
|
+
console.log("const pipeline = createDspPipeline();");
|
|
247
|
+
console.log("pipeline.MovingAverage({ mode: 'moving', windowSize: 100 });");
|
|
248
|
+
console.log("");
|
|
249
|
+
console.log("await pipeline.process(samples, {");
|
|
250
|
+
console.log(" sampleRate: 1000, // 1000 Hz");
|
|
251
|
+
console.log(" channels: 1");
|
|
252
|
+
console.log("});");
|
|
253
|
+
console.log("```\n");
|
|
254
|
+
|
|
255
|
+
console.log("AFTER (Time-Based):");
|
|
256
|
+
console.log("```typescript");
|
|
257
|
+
console.log("const pipeline = createDspPipeline();");
|
|
258
|
+
console.log("// 100 samples at 1000 Hz = 100ms");
|
|
259
|
+
console.log(
|
|
260
|
+
"pipeline.MovingAverage({ mode: 'moving', windowDuration: 100 });"
|
|
261
|
+
);
|
|
262
|
+
console.log("");
|
|
263
|
+
console.log("// Generate timestamps from your data source");
|
|
264
|
+
console.log("const timestamps = new Float32Array(samples.length);");
|
|
265
|
+
console.log("for (let i = 0; i < samples.length; i++) {");
|
|
266
|
+
console.log(" timestamps[i] = i; // 1ms per sample for 1000 Hz");
|
|
267
|
+
console.log("}");
|
|
268
|
+
console.log("");
|
|
269
|
+
console.log("await pipeline.process(samples, timestamps, { channels: 1 });");
|
|
270
|
+
console.log("```\n");
|
|
271
|
+
|
|
272
|
+
console.log("Conversion Formula:");
|
|
273
|
+
console.log(" windowDuration (ms) = (windowSize / sampleRate) * 1000\n");
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// Run all comparisons
|
|
277
|
+
async function main() {
|
|
278
|
+
await compareUniformData();
|
|
279
|
+
await compareIrregularData();
|
|
280
|
+
await showUseCases();
|
|
281
|
+
await showMigration();
|
|
282
|
+
|
|
283
|
+
console.log("=== Comparison Complete ===");
|
|
284
|
+
console.log("\n💡 Key Takeaway:");
|
|
285
|
+
console.log(" • Uniform data: Both methods equivalent");
|
|
286
|
+
console.log(" • Irregular data: Time-based processing essential");
|
|
287
|
+
console.log(" • Choose based on your data characteristics!\n");
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
main().catch(console.error);
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* IoT Sensor Processing with Irregular Timestamps
|
|
3
|
+
*
|
|
4
|
+
* This example demonstrates processing sensor data with network jitter,
|
|
5
|
+
* where samples arrive at irregular intervals.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { createDspPipeline } from "../../bindings";
|
|
9
|
+
|
|
10
|
+
interface SensorReading {
|
|
11
|
+
value: number;
|
|
12
|
+
timestamp: number; // Unix timestamp in milliseconds
|
|
13
|
+
sensorId: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Simulates IoT sensor data with realistic network jitter
|
|
18
|
+
*/
|
|
19
|
+
function generateIrregularSensorData(
|
|
20
|
+
count: number,
|
|
21
|
+
baseInterval: number = 100
|
|
22
|
+
): SensorReading[] {
|
|
23
|
+
const readings: SensorReading[] = [];
|
|
24
|
+
let currentTime = Date.now();
|
|
25
|
+
|
|
26
|
+
for (let i = 0; i < count; i++) {
|
|
27
|
+
// Add random jitter: ±50% of base interval
|
|
28
|
+
const jitter = (Math.random() - 0.5) * baseInterval;
|
|
29
|
+
currentTime += baseInterval + jitter;
|
|
30
|
+
|
|
31
|
+
// Simulate temperature sensor with noise
|
|
32
|
+
const baseTemp = 20 + Math.sin(i * 0.05) * 5; // Varying baseline
|
|
33
|
+
const noise = (Math.random() - 0.5) * 2; // ±1 degree noise
|
|
34
|
+
|
|
35
|
+
readings.push({
|
|
36
|
+
value: baseTemp + noise,
|
|
37
|
+
timestamp: currentTime,
|
|
38
|
+
sensorId: "TEMP-001",
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return readings;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Process sensor data with time-based moving average
|
|
47
|
+
*/
|
|
48
|
+
async function processSensorData() {
|
|
49
|
+
console.log("=== IoT Sensor Processing Example ===\n");
|
|
50
|
+
|
|
51
|
+
// Generate 50 readings with ~100ms intervals (but irregular)
|
|
52
|
+
const readings = generateIrregularSensorData(50, 100);
|
|
53
|
+
|
|
54
|
+
console.log("Raw sensor readings (first 5):");
|
|
55
|
+
readings.slice(0, 5).forEach((r, i) => {
|
|
56
|
+
const interval = i > 0 ? r.timestamp - readings[i - 1].timestamp : 0;
|
|
57
|
+
console.log(
|
|
58
|
+
` [${i}] ${r.value.toFixed(2)}°C at t=${
|
|
59
|
+
r.timestamp
|
|
60
|
+
} (Δ=${interval.toFixed(0)}ms)`
|
|
61
|
+
);
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
// Create pipeline with time-based window (5 second moving average)
|
|
65
|
+
const pipeline = createDspPipeline();
|
|
66
|
+
pipeline.MovingAverage({
|
|
67
|
+
mode: "moving",
|
|
68
|
+
windowDuration: 5000, // 5 seconds
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
// Convert to Float32Arrays
|
|
72
|
+
const samples = new Float32Array(readings.map((r) => r.value));
|
|
73
|
+
const timestamps = new Float32Array(readings.map((r) => r.timestamp));
|
|
74
|
+
|
|
75
|
+
// Process with timestamps
|
|
76
|
+
const smoothed = await pipeline.process(samples, timestamps, {
|
|
77
|
+
channels: 1,
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
console.log("\nSmoothed data (5-second moving average, first 5):");
|
|
81
|
+
for (let i = 0; i < 5; i++) {
|
|
82
|
+
console.log(
|
|
83
|
+
` [${i}] Raw: ${samples[i].toFixed(2)}°C → Smoothed: ${smoothed[
|
|
84
|
+
i
|
|
85
|
+
].toFixed(2)}°C`
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Calculate statistics
|
|
90
|
+
const rawStdDev = calculateStdDev(Array.from(samples));
|
|
91
|
+
const smoothedStdDev = calculateStdDev(Array.from(smoothed));
|
|
92
|
+
const noiseReduction = ((rawStdDev - smoothedStdDev) / rawStdDev) * 100;
|
|
93
|
+
|
|
94
|
+
console.log("\nNoise reduction statistics:");
|
|
95
|
+
console.log(` Raw data std dev: ${rawStdDev.toFixed(3)}°C`);
|
|
96
|
+
console.log(` Smoothed std dev: ${smoothedStdDev.toFixed(3)}°C`);
|
|
97
|
+
console.log(` Noise reduction: ${noiseReduction.toFixed(1)}%`);
|
|
98
|
+
|
|
99
|
+
// Demonstrate state persistence
|
|
100
|
+
const state = await pipeline.saveState();
|
|
101
|
+
console.log(`\nSaved pipeline state (${state.length} bytes)`);
|
|
102
|
+
|
|
103
|
+
// Create new pipeline and restore state
|
|
104
|
+
const pipeline2 = createDspPipeline();
|
|
105
|
+
pipeline2.MovingAverage({
|
|
106
|
+
mode: "moving",
|
|
107
|
+
windowDuration: 5000,
|
|
108
|
+
});
|
|
109
|
+
await pipeline2.loadState(state);
|
|
110
|
+
console.log("State restored to new pipeline");
|
|
111
|
+
|
|
112
|
+
// Process new data with restored state
|
|
113
|
+
const newReadings = generateIrregularSensorData(5, 100);
|
|
114
|
+
const newSamples = new Float32Array(newReadings.map((r) => r.value));
|
|
115
|
+
const newTimestamps = new Float32Array(
|
|
116
|
+
newReadings.map(
|
|
117
|
+
(r) => r.timestamp + readings[readings.length - 1].timestamp
|
|
118
|
+
)
|
|
119
|
+
);
|
|
120
|
+
const continued = await pipeline2.process(newSamples, newTimestamps, {
|
|
121
|
+
channels: 1,
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
console.log("\nContinued processing with restored state:");
|
|
125
|
+
for (let i = 0; i < continued.length; i++) {
|
|
126
|
+
console.log(
|
|
127
|
+
` [${i}] ${newSamples[i].toFixed(2)}°C → ${continued[i].toFixed(2)}°C`
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Calculate standard deviation
|
|
134
|
+
*/
|
|
135
|
+
function calculateStdDev(values: number[]): number {
|
|
136
|
+
const mean = values.reduce((sum, v) => sum + v, 0) / values.length;
|
|
137
|
+
const variance =
|
|
138
|
+
values.reduce((sum, v) => sum + Math.pow(v - mean, 2), 0) / values.length;
|
|
139
|
+
return Math.sqrt(variance);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// Run example
|
|
143
|
+
processSensorData().catch(console.error);
|
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Redis-Backed Streaming Data Processing
|
|
3
|
+
*
|
|
4
|
+
* This example shows how to process streaming sensor data with Redis state
|
|
5
|
+
* persistence, handling data chunks with timestamps.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { createDspPipeline } from "../../bindings";
|
|
9
|
+
import { createClient } from "redis";
|
|
10
|
+
|
|
11
|
+
interface DataChunk {
|
|
12
|
+
deviceId: string;
|
|
13
|
+
samples: Float32Array;
|
|
14
|
+
timestamps: Float32Array;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Streaming processor with Redis state persistence
|
|
19
|
+
*/
|
|
20
|
+
class StreamingProcessor {
|
|
21
|
+
private pipeline;
|
|
22
|
+
private redis;
|
|
23
|
+
private stateKey: string;
|
|
24
|
+
|
|
25
|
+
constructor(deviceId: string) {
|
|
26
|
+
this.pipeline = createDspPipeline();
|
|
27
|
+
|
|
28
|
+
// Multi-stage pipeline for sensor data
|
|
29
|
+
this.pipeline
|
|
30
|
+
.MovingAverage({
|
|
31
|
+
mode: "moving",
|
|
32
|
+
windowDuration: 10000, // 10 second smoothing
|
|
33
|
+
})
|
|
34
|
+
.Rms({
|
|
35
|
+
mode: "moving",
|
|
36
|
+
windowDuration: 5000, // 5 second RMS
|
|
37
|
+
})
|
|
38
|
+
.ZScoreNormalize({
|
|
39
|
+
mode: "moving",
|
|
40
|
+
windowDuration: 60000, // 1 minute normalization window
|
|
41
|
+
epsilon: 1e-6,
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
this.redis = createClient();
|
|
45
|
+
this.stateKey = `dsp:streaming:${deviceId}`;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
async initialize() {
|
|
49
|
+
await this.redis.connect();
|
|
50
|
+
console.log(`Connected to Redis for device state persistence`);
|
|
51
|
+
|
|
52
|
+
// Try to restore previous state
|
|
53
|
+
const savedState = await this.redis.get(this.stateKey);
|
|
54
|
+
if (savedState) {
|
|
55
|
+
await this.pipeline.loadState(savedState);
|
|
56
|
+
console.log(`Restored previous state for ${this.stateKey}`);
|
|
57
|
+
} else {
|
|
58
|
+
console.log(`No previous state found, starting fresh`);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Process a chunk of streaming data
|
|
64
|
+
*/
|
|
65
|
+
async processChunk(chunk: DataChunk): Promise<Float32Array> {
|
|
66
|
+
console.log(
|
|
67
|
+
`\nProcessing chunk: ${chunk.samples.length} samples from ${chunk.deviceId}`
|
|
68
|
+
);
|
|
69
|
+
console.log(
|
|
70
|
+
` Time range: ${chunk.timestamps[0]} - ${
|
|
71
|
+
chunk.timestamps[chunk.timestamps.length - 1]
|
|
72
|
+
}`
|
|
73
|
+
);
|
|
74
|
+
|
|
75
|
+
// Process data
|
|
76
|
+
const result = await this.pipeline.process(
|
|
77
|
+
chunk.samples,
|
|
78
|
+
chunk.timestamps,
|
|
79
|
+
{ channels: 1 }
|
|
80
|
+
);
|
|
81
|
+
|
|
82
|
+
// Save state to Redis
|
|
83
|
+
const newState = await this.pipeline.saveState();
|
|
84
|
+
await this.redis.set(this.stateKey, newState, {
|
|
85
|
+
EX: 3600, // 1 hour TTL
|
|
86
|
+
});
|
|
87
|
+
console.log(` Saved state to Redis (${newState.length} bytes)`);
|
|
88
|
+
|
|
89
|
+
return result;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
async close() {
|
|
93
|
+
await this.redis.quit();
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Simulate streaming sensor data chunks
|
|
99
|
+
*/
|
|
100
|
+
function generateDataChunk(
|
|
101
|
+
chunkIndex: number,
|
|
102
|
+
samplesPerChunk: number,
|
|
103
|
+
startTime: number
|
|
104
|
+
): DataChunk {
|
|
105
|
+
const samples = new Float32Array(samplesPerChunk);
|
|
106
|
+
const timestamps = new Float32Array(samplesPerChunk);
|
|
107
|
+
|
|
108
|
+
for (let i = 0; i < samplesPerChunk; i++) {
|
|
109
|
+
const globalIndex = chunkIndex * samplesPerChunk + i;
|
|
110
|
+
|
|
111
|
+
// Simulate sensor data: sine wave + noise + drift
|
|
112
|
+
const baseSignal = Math.sin(globalIndex * 0.1) * 10;
|
|
113
|
+
const noise = (Math.random() - 0.5) * 2;
|
|
114
|
+
const drift = globalIndex * 0.01;
|
|
115
|
+
|
|
116
|
+
samples[i] = baseSignal + noise + drift;
|
|
117
|
+
|
|
118
|
+
// Irregular timestamps (realistic network jitter)
|
|
119
|
+
const nominalInterval = 100; // ~100ms
|
|
120
|
+
const jitter = (Math.random() - 0.5) * 20; // ±10ms jitter
|
|
121
|
+
timestamps[i] = startTime + i * nominalInterval + jitter;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
return {
|
|
125
|
+
deviceId: "SENSOR-001",
|
|
126
|
+
samples,
|
|
127
|
+
timestamps,
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Main streaming example
|
|
133
|
+
*/
|
|
134
|
+
async function runStreamingExample() {
|
|
135
|
+
console.log("=== Redis-Backed Streaming Processing Example ===\n");
|
|
136
|
+
|
|
137
|
+
const processor = new StreamingProcessor("SENSOR-001");
|
|
138
|
+
await processor.initialize();
|
|
139
|
+
|
|
140
|
+
// Simulate 5 chunks of streaming data
|
|
141
|
+
const chunksCount = 5;
|
|
142
|
+
const samplesPerChunk = 20;
|
|
143
|
+
let startTime = Date.now();
|
|
144
|
+
|
|
145
|
+
const allResults: number[] = [];
|
|
146
|
+
|
|
147
|
+
for (let i = 0; i < chunksCount; i++) {
|
|
148
|
+
console.log(`\n--- Chunk ${i + 1}/${chunksCount} ---`);
|
|
149
|
+
|
|
150
|
+
const chunk = generateDataChunk(i, samplesPerChunk, startTime);
|
|
151
|
+
const result = await processor.processChunk(chunk);
|
|
152
|
+
|
|
153
|
+
// Show sample results
|
|
154
|
+
console.log(` Results (first 3):`);
|
|
155
|
+
for (let j = 0; j < Math.min(3, result.length); j++) {
|
|
156
|
+
console.log(
|
|
157
|
+
` [${j}] Input: ${chunk.samples[j].toFixed(2)} → Output: ${result[
|
|
158
|
+
j
|
|
159
|
+
].toFixed(3)}`
|
|
160
|
+
);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
allResults.push(...Array.from(result));
|
|
164
|
+
|
|
165
|
+
// Update start time for next chunk
|
|
166
|
+
startTime = chunk.timestamps[chunk.timestamps.length - 1] + 100;
|
|
167
|
+
|
|
168
|
+
// Simulate real-time delay between chunks
|
|
169
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
console.log(
|
|
173
|
+
`\n\nProcessed ${allResults.length} total samples across ${chunksCount} chunks`
|
|
174
|
+
);
|
|
175
|
+
console.log(
|
|
176
|
+
`Final output range: [${Math.min(...allResults).toFixed(3)}, ${Math.max(
|
|
177
|
+
...allResults
|
|
178
|
+
).toFixed(3)}]`
|
|
179
|
+
);
|
|
180
|
+
|
|
181
|
+
await processor.close();
|
|
182
|
+
console.log("\nClosed Redis connection");
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Example: Recovery from interruption
|
|
187
|
+
*/
|
|
188
|
+
async function demonstrateRecovery() {
|
|
189
|
+
console.log("\n\n=== Demonstrating Recovery After Interruption ===\n");
|
|
190
|
+
|
|
191
|
+
// Process first chunk
|
|
192
|
+
const processor1 = new StreamingProcessor("SENSOR-002");
|
|
193
|
+
await processor1.initialize();
|
|
194
|
+
|
|
195
|
+
const chunk1 = generateDataChunk(0, 20, Date.now());
|
|
196
|
+
await processor1.processChunk(chunk1);
|
|
197
|
+
console.log("Processed chunk 1, state saved");
|
|
198
|
+
|
|
199
|
+
await processor1.close();
|
|
200
|
+
console.log("Simulating process crash/restart...\n");
|
|
201
|
+
|
|
202
|
+
// Simulate restart - new processor instance
|
|
203
|
+
const processor2 = new StreamingProcessor("SENSOR-002");
|
|
204
|
+
await processor2.initialize(); // Automatically restores state
|
|
205
|
+
|
|
206
|
+
// Continue with next chunk
|
|
207
|
+
const chunk2 = generateDataChunk(
|
|
208
|
+
1,
|
|
209
|
+
20,
|
|
210
|
+
chunk1.timestamps[chunk1.timestamps.length - 1] + 100
|
|
211
|
+
);
|
|
212
|
+
const result2 = await processor2.processChunk(chunk2);
|
|
213
|
+
|
|
214
|
+
console.log("Successfully continued processing after restart!");
|
|
215
|
+
console.log(` Processed ${result2.length} samples with restored context`);
|
|
216
|
+
|
|
217
|
+
await processor2.close();
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// Run examples
|
|
221
|
+
async function main() {
|
|
222
|
+
try {
|
|
223
|
+
await runStreamingExample();
|
|
224
|
+
await demonstrateRecovery();
|
|
225
|
+
} catch (error) {
|
|
226
|
+
console.error("Error:", error);
|
|
227
|
+
if (error instanceof Error && error.message.includes("ECONNREFUSED")) {
|
|
228
|
+
console.error("\n⚠️ Make sure Redis is running: redis-server");
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
main().catch(console.error);
|