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,376 @@
|
|
|
1
|
+
# Time-Series Migration Plan
|
|
2
|
+
|
|
3
|
+
**Goal:** Transform dspx from a sample-based processor to a time-aware time-series processor.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Architecture Decision
|
|
8
|
+
|
|
9
|
+
### Current (Sample-Based)
|
|
10
|
+
|
|
11
|
+
```typescript
|
|
12
|
+
// User provides raw samples, library assumes fixed intervals
|
|
13
|
+
pipeline.process(new Float32Array([1, 2, 3, 4, 5]), {
|
|
14
|
+
sampleRate: 100, // Assumes 100Hz = 10ms intervals
|
|
15
|
+
channels: 1,
|
|
16
|
+
});
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
**Problems:**
|
|
20
|
+
|
|
21
|
+
- ❌ Breaks with irregular data (network jitter, sensor lag)
|
|
22
|
+
- ❌ Window size in "samples" is unintuitive (500 samples ≠ 5 seconds without mental math)
|
|
23
|
+
- ❌ Fragile to sample rate changes
|
|
24
|
+
- ❌ Redis persistence lacks context (what _time_ is this sample from?)
|
|
25
|
+
|
|
26
|
+
### Proposed (Time-Based)
|
|
27
|
+
|
|
28
|
+
```typescript
|
|
29
|
+
// Option 1: Parallel timestamps array
|
|
30
|
+
pipeline.process(
|
|
31
|
+
new Float32Array([1, 2, 3, 4, 5]),
|
|
32
|
+
new Float32Array([1000, 1010, 1020, 1030, 1040]), // millisecond timestamps
|
|
33
|
+
{ channels: 1 }
|
|
34
|
+
);
|
|
35
|
+
|
|
36
|
+
// Option 2: Interleaved (value, timestamp) pairs
|
|
37
|
+
pipeline.process(
|
|
38
|
+
new Float32Array([1, 1000, 2, 1010, 3, 1020, 4, 1030, 5, 1040]),
|
|
39
|
+
{ format: "interleaved", channels: 1 }
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
// Option 3: Fall back to sample-based for backwards compatibility
|
|
43
|
+
pipeline.process(
|
|
44
|
+
new Float32Array([1, 2, 3, 4, 5]),
|
|
45
|
+
{ sampleRate: 100, channels: 1 } // Old API still works
|
|
46
|
+
);
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
**Benefits:**
|
|
50
|
+
|
|
51
|
+
- ✅ Handles irregular data correctly
|
|
52
|
+
- ✅ Intuitive windowing: `windowDuration: 5000` = 5 seconds (regardless of sample rate)
|
|
53
|
+
- ✅ Robust to sample rate changes
|
|
54
|
+
- ✅ Redis persistence has full context
|
|
55
|
+
- ✅ Backwards compatible (sampleRate-based still works)
|
|
56
|
+
|
|
57
|
+
---
|
|
58
|
+
|
|
59
|
+
## Implementation Phases
|
|
60
|
+
|
|
61
|
+
### Phase 1: TypeScript API Changes
|
|
62
|
+
|
|
63
|
+
#### 1.1 Update `ProcessOptions`
|
|
64
|
+
|
|
65
|
+
```typescript
|
|
66
|
+
export interface ProcessOptions {
|
|
67
|
+
// Legacy sample-based mode (backwards compatible)
|
|
68
|
+
sampleRate?: number;
|
|
69
|
+
channels?: number;
|
|
70
|
+
|
|
71
|
+
// New time-based mode (mutually exclusive with sampleRate)
|
|
72
|
+
// If omitted, assumes millisecond increments: [0, 1, 2, 3, ...]
|
|
73
|
+
}
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
#### 1.2 Update Filter Parameters
|
|
77
|
+
|
|
78
|
+
```typescript
|
|
79
|
+
// Current
|
|
80
|
+
export interface MovingAverageParams {
|
|
81
|
+
mode: "batch" | "moving";
|
|
82
|
+
windowSize?: number; // in samples
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// New (backwards compatible)
|
|
86
|
+
export interface MovingAverageParams {
|
|
87
|
+
mode: "batch" | "moving";
|
|
88
|
+
|
|
89
|
+
// Option 1: Sample-based (legacy)
|
|
90
|
+
windowSize?: number; // in samples (requires sampleRate in process())
|
|
91
|
+
|
|
92
|
+
// Option 2: Time-based (preferred)
|
|
93
|
+
windowDuration?: number; // in milliseconds
|
|
94
|
+
}
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
#### 1.3 New `process()` Signatures
|
|
98
|
+
|
|
99
|
+
```typescript
|
|
100
|
+
class DspProcessor {
|
|
101
|
+
// Legacy: Sample-based (backwards compatible)
|
|
102
|
+
process(
|
|
103
|
+
samples: Float32Array,
|
|
104
|
+
options: { sampleRate: number; channels?: number }
|
|
105
|
+
): Promise<Float32Array>;
|
|
106
|
+
|
|
107
|
+
// New: Time-based with parallel arrays
|
|
108
|
+
process(
|
|
109
|
+
samples: Float32Array,
|
|
110
|
+
timestamps: Float32Array,
|
|
111
|
+
options: { channels?: number }
|
|
112
|
+
): Promise<Float32Array>;
|
|
113
|
+
|
|
114
|
+
// Auto-generate timestamps if missing
|
|
115
|
+
process(
|
|
116
|
+
samples: Float32Array,
|
|
117
|
+
options: { channels?: number }
|
|
118
|
+
): Promise<Float32Array>; // timestamps = [0, 1, 2, ...]
|
|
119
|
+
}
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
---
|
|
123
|
+
|
|
124
|
+
### Phase 2: C++ Core Changes
|
|
125
|
+
|
|
126
|
+
#### 2.1 Update `SlidingWindowFilter` to Store Time
|
|
127
|
+
|
|
128
|
+
```cpp
|
|
129
|
+
// Current: Stores only values
|
|
130
|
+
template <typename T, typename Policy>
|
|
131
|
+
class SlidingWindowFilter {
|
|
132
|
+
CircularBuffer<T> m_buffer; // [v1, v2, v3, ...]
|
|
133
|
+
Policy m_policy;
|
|
134
|
+
size_t m_windowSize; // Number of samples
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
// New: Stores (time, value) pairs
|
|
138
|
+
template <typename T, typename Policy>
|
|
139
|
+
class SlidingWindowFilter {
|
|
140
|
+
std::deque<std::pair<uint64_t, T>> m_buffer; // [(t1,v1), (t2,v2), ...]
|
|
141
|
+
Policy m_policy;
|
|
142
|
+
uint64_t m_windowDuration; // Duration in milliseconds
|
|
143
|
+
|
|
144
|
+
void add(T value, uint64_t timestamp) {
|
|
145
|
+
// Add new sample
|
|
146
|
+
m_buffer.push_back({timestamp, value});
|
|
147
|
+
m_policy.onAdd(value);
|
|
148
|
+
|
|
149
|
+
// Remove expired samples
|
|
150
|
+
while (!m_buffer.empty() &&
|
|
151
|
+
timestamp - m_buffer.front().first > m_windowDuration) {
|
|
152
|
+
T expired = m_buffer.front().second;
|
|
153
|
+
m_buffer.pop_front();
|
|
154
|
+
m_policy.onRemove(expired);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
};
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
#### 2.2 Update All Filters
|
|
161
|
+
|
|
162
|
+
- `MovingAverageFilter` - Time-based window
|
|
163
|
+
- `RmsFilter` - Time-based window
|
|
164
|
+
- `MovingAbsoluteValueFilter` - Time-based window
|
|
165
|
+
- `MovingVarianceFilter` - Time-based window
|
|
166
|
+
- `MovingZScoreFilter` - Time-based window
|
|
167
|
+
|
|
168
|
+
#### 2.3 Update N-API Bindings
|
|
169
|
+
|
|
170
|
+
```cpp
|
|
171
|
+
// DspPipeline::ProcessAsync needs to accept optional timestamps
|
|
172
|
+
Napi::Value DspPipeline::Process(const Napi::CallbackInfo& info) {
|
|
173
|
+
Napi::Float32Array samples = info[0].As<Napi::Float32Array>();
|
|
174
|
+
|
|
175
|
+
// Check if second arg is timestamps or options
|
|
176
|
+
Napi::Float32Array timestamps;
|
|
177
|
+
if (info[1].IsTypedArray()) {
|
|
178
|
+
timestamps = info[1].As<Napi::Float32Array>();
|
|
179
|
+
// options is info[2]
|
|
180
|
+
} else {
|
|
181
|
+
// Legacy mode: auto-generate timestamps
|
|
182
|
+
// options is info[1]
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
---
|
|
188
|
+
|
|
189
|
+
### Phase 3: State Serialization Updates
|
|
190
|
+
|
|
191
|
+
#### 3.1 JSON Format Changes
|
|
192
|
+
|
|
193
|
+
```json
|
|
194
|
+
{
|
|
195
|
+
"timestamp": 1678886400000,
|
|
196
|
+
"stages": [
|
|
197
|
+
{
|
|
198
|
+
"index": 0,
|
|
199
|
+
"type": "movingAverage",
|
|
200
|
+
"state": {
|
|
201
|
+
"windowDuration": 5000, // NEW: milliseconds instead of sample count
|
|
202
|
+
"numChannels": 1,
|
|
203
|
+
"channels": [
|
|
204
|
+
{
|
|
205
|
+
"buffer": [
|
|
206
|
+
{ "time": 1678886395000, "value": 3.0 },
|
|
207
|
+
{ "time": 1678886396000, "value": 4.0 },
|
|
208
|
+
{ "time": 1678886397000, "value": 5.0 }
|
|
209
|
+
],
|
|
210
|
+
"runningSum": 12.0
|
|
211
|
+
}
|
|
212
|
+
]
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
]
|
|
216
|
+
}
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
---
|
|
220
|
+
|
|
221
|
+
### Phase 4: Migration Strategy (Backwards Compatibility)
|
|
222
|
+
|
|
223
|
+
#### 4.1 Detection Logic
|
|
224
|
+
|
|
225
|
+
```typescript
|
|
226
|
+
// In DspProcessor.process()
|
|
227
|
+
if (options.sampleRate) {
|
|
228
|
+
// Legacy sample-based mode
|
|
229
|
+
// Convert sampleRate to timestamps internally
|
|
230
|
+
const dt = 1000 / options.sampleRate; // milliseconds per sample
|
|
231
|
+
const timestamps = new Float32Array(samples.length);
|
|
232
|
+
for (let i = 0; i < samples.length; i++) {
|
|
233
|
+
timestamps[i] = i * dt;
|
|
234
|
+
}
|
|
235
|
+
return this.processWithTime(samples, timestamps, options);
|
|
236
|
+
} else {
|
|
237
|
+
// New time-based mode
|
|
238
|
+
return this.processWithTime(samples, timestamps, options);
|
|
239
|
+
}
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
#### 4.2 Filter Configuration Migration
|
|
243
|
+
|
|
244
|
+
```typescript
|
|
245
|
+
// Auto-convert windowSize to windowDuration
|
|
246
|
+
if (params.windowSize && options.sampleRate) {
|
|
247
|
+
const windowDuration = (params.windowSize / options.sampleRate) * 1000;
|
|
248
|
+
// Use windowDuration internally
|
|
249
|
+
}
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
---
|
|
253
|
+
|
|
254
|
+
## Testing Strategy
|
|
255
|
+
|
|
256
|
+
### 5.1 Backwards Compatibility Tests
|
|
257
|
+
|
|
258
|
+
- ✅ All existing tests should pass unchanged
|
|
259
|
+
- ✅ Legacy `windowSize` with `sampleRate` produces same results
|
|
260
|
+
|
|
261
|
+
### 5.2 New Time-Based Tests
|
|
262
|
+
|
|
263
|
+
- ✅ Irregular timestamps handled correctly
|
|
264
|
+
- ✅ Variable sample rates work
|
|
265
|
+
- ✅ Window duration matches expected time span
|
|
266
|
+
- ✅ State serialization includes timestamps
|
|
267
|
+
|
|
268
|
+
### 5.3 Performance Tests
|
|
269
|
+
|
|
270
|
+
- ✅ `std::deque` performance vs `CircularBuffer`
|
|
271
|
+
- ✅ Memory usage with time storage
|
|
272
|
+
- ✅ Throughput comparison (sample-based vs time-based)
|
|
273
|
+
|
|
274
|
+
---
|
|
275
|
+
|
|
276
|
+
## Documentation Updates
|
|
277
|
+
|
|
278
|
+
### 6.1 README Changes
|
|
279
|
+
|
|
280
|
+
```typescript
|
|
281
|
+
// Highlight time-based API
|
|
282
|
+
const pipeline = createDspPipeline();
|
|
283
|
+
pipeline.MovingAverage({ windowDuration: 5000 }); // 5 seconds
|
|
284
|
+
|
|
285
|
+
// Show irregular data handling
|
|
286
|
+
const samples = new Float32Array([1.2, 1.5, 1.8]);
|
|
287
|
+
const timestamps = new Float32Array([0, 100, 250]); // Irregular gaps
|
|
288
|
+
await pipeline.process(samples, timestamps, { channels: 1 });
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
### 6.2 Migration Guide
|
|
292
|
+
|
|
293
|
+
Create `docs/migration-to-time-series.md` with:
|
|
294
|
+
|
|
295
|
+
- Why time-based is better
|
|
296
|
+
- How to migrate existing code
|
|
297
|
+
- Performance implications
|
|
298
|
+
- Backwards compatibility guarantees
|
|
299
|
+
|
|
300
|
+
---
|
|
301
|
+
|
|
302
|
+
## Implementation Timeline
|
|
303
|
+
|
|
304
|
+
### Week 1: TypeScript Layer
|
|
305
|
+
|
|
306
|
+
- [ ] Update `types.ts` with new interfaces
|
|
307
|
+
- [ ] Update `bindings.ts` with new `process()` signatures
|
|
308
|
+
- [ ] Add detection logic for legacy vs new mode
|
|
309
|
+
- [ ] Add backwards compatibility tests
|
|
310
|
+
|
|
311
|
+
### Week 2: C++ Core (Simple Filters)
|
|
312
|
+
|
|
313
|
+
- [ ] Update `SlidingWindowFilter` to use `std::deque<pair<time, value>>`
|
|
314
|
+
- [ ] Update `MovingAverageFilter` to time-based windowing
|
|
315
|
+
- [ ] Update `RmsFilter` to time-based windowing
|
|
316
|
+
- [ ] Add C++ unit tests
|
|
317
|
+
|
|
318
|
+
### Week 3: C++ Core (Complex Filters)
|
|
319
|
+
|
|
320
|
+
- [ ] Update `MovingVarianceFilter`
|
|
321
|
+
- [ ] Update `MovingZScoreFilter`
|
|
322
|
+
- [ ] Update `MovingAbsoluteValueFilter`
|
|
323
|
+
- [ ] Update N-API bindings
|
|
324
|
+
|
|
325
|
+
### Week 4: State & Documentation
|
|
326
|
+
|
|
327
|
+
- [ ] Update state serialization format
|
|
328
|
+
- [ ] Add migration for old state format
|
|
329
|
+
- [ ] Update all documentation
|
|
330
|
+
- [ ] Create migration guide
|
|
331
|
+
- [ ] Performance benchmarks
|
|
332
|
+
|
|
333
|
+
---
|
|
334
|
+
|
|
335
|
+
## Open Questions
|
|
336
|
+
|
|
337
|
+
1. **Timestamp Units**: Milliseconds (standard) or allow user-defined?
|
|
338
|
+
2. **Timestamp Type**: `uint64_t` (C++) vs `number` (JS) precision?
|
|
339
|
+
3. **Memory Trade-off**: `std::deque` vs custom circular buffer with time?
|
|
340
|
+
4. **Multi-channel Time**: One timestamp per sample or per channel?
|
|
341
|
+
5. **Batch vs Moving**: Should batch mode also support time?
|
|
342
|
+
|
|
343
|
+
---
|
|
344
|
+
|
|
345
|
+
## Risk Mitigation
|
|
346
|
+
|
|
347
|
+
### Breaking Changes
|
|
348
|
+
|
|
349
|
+
- **Risk**: Changing C++ core may break existing code
|
|
350
|
+
- **Mitigation**: Full backwards compatibility via auto-conversion
|
|
351
|
+
|
|
352
|
+
### Performance Regression
|
|
353
|
+
|
|
354
|
+
- **Risk**: `std::deque` slower than `CircularBuffer`
|
|
355
|
+
- **Mitigation**: Benchmark and optimize; consider custom time-aware circular buffer
|
|
356
|
+
|
|
357
|
+
### State Migration
|
|
358
|
+
|
|
359
|
+
- **Risk**: Old Redis states won't load
|
|
360
|
+
- **Mitigation**: Add state version field and auto-upgrade logic
|
|
361
|
+
|
|
362
|
+
---
|
|
363
|
+
|
|
364
|
+
## Success Criteria
|
|
365
|
+
|
|
366
|
+
✅ All existing tests pass without modification
|
|
367
|
+
✅ New time-based API works with irregular data
|
|
368
|
+
✅ Redis state includes timestamps
|
|
369
|
+
✅ Documentation updated with examples
|
|
370
|
+
✅ Performance within 10% of current implementation
|
|
371
|
+
✅ Migration guide published
|
|
372
|
+
|
|
373
|
+
---
|
|
374
|
+
|
|
375
|
+
**Status**: 📋 Planning Phase
|
|
376
|
+
**Next Action**: Implement TypeScript API changes (Phase 1)
|
package/jest.config.js
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
export default {
|
|
2
|
+
preset: "ts-jest/presets/default-esm",
|
|
3
|
+
testEnvironment: "node",
|
|
4
|
+
extensionsToTreatAsEsm: [".ts"],
|
|
5
|
+
moduleNameMapper: {
|
|
6
|
+
"^(\\.{1,2}/.*)\\.js$": "$1",
|
|
7
|
+
},
|
|
8
|
+
transform: {
|
|
9
|
+
"^.+\\.tsx?$": [
|
|
10
|
+
"ts-jest",
|
|
11
|
+
{
|
|
12
|
+
useESM: true,
|
|
13
|
+
tsconfig: {
|
|
14
|
+
module: "ES2022",
|
|
15
|
+
moduleResolution: "bundler",
|
|
16
|
+
target: "ES2022",
|
|
17
|
+
lib: ["ES2022"],
|
|
18
|
+
allowSyntheticDefaultImports: true,
|
|
19
|
+
esModuleInterop: true,
|
|
20
|
+
},
|
|
21
|
+
},
|
|
22
|
+
],
|
|
23
|
+
},
|
|
24
|
+
testMatch: ["**/__tests__/**/*.test.ts", "**/?(*.)+(spec|test).ts"],
|
|
25
|
+
collectCoverageFrom: [
|
|
26
|
+
"src/ts/**/*.ts",
|
|
27
|
+
"!src/ts/**/*.d.ts",
|
|
28
|
+
"!src/ts/examples/**",
|
|
29
|
+
],
|
|
30
|
+
coverageDirectory: "coverage",
|
|
31
|
+
coverageReporters: ["text", "lcov", "html"],
|
|
32
|
+
globals: {
|
|
33
|
+
"ts-jest": {
|
|
34
|
+
useESM: true,
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "dspx",
|
|
3
|
+
"version": "0.1.1-alpha.0",
|
|
4
|
+
"description": "High-performance DSP library with native C++ acceleration and Redis state persistence",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"test": "node scripts/test.js",
|
|
8
|
+
"build": "node-gyp rebuild && node -e \"const fs = require('fs'); fs.cpSync('build/Release/dspx.node', 'src/build/dspx.node', {force: true}); console.log('Copied build/Release/dspx.node to src/build/dspx.node');\"",
|
|
9
|
+
"prebuildify": "prebuildify --napi --strip --target 18.0.0 --target 20.0.0 --target 22.0.0"
|
|
10
|
+
},
|
|
11
|
+
"binary": {
|
|
12
|
+
"napi_versions": [
|
|
13
|
+
8
|
|
14
|
+
]
|
|
15
|
+
},
|
|
16
|
+
"keywords": [
|
|
17
|
+
"dsp",
|
|
18
|
+
"signal-processing",
|
|
19
|
+
"native",
|
|
20
|
+
"redis",
|
|
21
|
+
"real-time",
|
|
22
|
+
"audio",
|
|
23
|
+
"biosignal"
|
|
24
|
+
],
|
|
25
|
+
"author": "",
|
|
26
|
+
"license": "MIT",
|
|
27
|
+
"type": "module",
|
|
28
|
+
"dependencies": {
|
|
29
|
+
"node-addon-api": "^8.5.0",
|
|
30
|
+
"node-gyp-build": "^4.8.0"
|
|
31
|
+
},
|
|
32
|
+
"devDependencies": {
|
|
33
|
+
"@jest/globals": "^30.2.0",
|
|
34
|
+
"@types/jest": "^30.0.0",
|
|
35
|
+
"@types/node": "^24.9.1",
|
|
36
|
+
"jest": "^30.2.0",
|
|
37
|
+
"prebuildify": "^6.0.1",
|
|
38
|
+
"redis": "^5.9.0",
|
|
39
|
+
"ts-jest": "^29.4.5",
|
|
40
|
+
"tsx": "^4.20.6"
|
|
41
|
+
}
|
|
42
|
+
}
|
|
Binary file
|
|
Binary file
|
package/scripts/test.js
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { readdirSync } from "fs";
|
|
2
|
+
import { join } from "path";
|
|
3
|
+
import { spawn } from "child_process";
|
|
4
|
+
import { fileURLToPath } from "url";
|
|
5
|
+
import { dirname } from "path";
|
|
6
|
+
|
|
7
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
8
|
+
const testDir = join(__dirname, "../src/ts/__tests__");
|
|
9
|
+
|
|
10
|
+
// Get all test files
|
|
11
|
+
const testFiles = readdirSync(testDir)
|
|
12
|
+
.filter((file) => file.endsWith(".test.ts"))
|
|
13
|
+
.map((file) => join(testDir, file));
|
|
14
|
+
|
|
15
|
+
// Run node test runner with all test files
|
|
16
|
+
const args = ["--import", "tsx", "--test", ...testFiles];
|
|
17
|
+
const child = spawn("node", args, {
|
|
18
|
+
stdio: "inherit",
|
|
19
|
+
shell: true,
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
child.on("exit", (code) => {
|
|
23
|
+
process.exit(code);
|
|
24
|
+
});
|
|
Binary file
|