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,202 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Example showing how to use Redis for DSP state persistence
|
|
3
|
+
*
|
|
4
|
+
* This demonstrates:
|
|
5
|
+
* 1. Creating a pipeline with Redis configuration
|
|
6
|
+
* 2. Processing audio data
|
|
7
|
+
* 3. Saving/loading state (when Redis integration is fully implemented)
|
|
8
|
+
*
|
|
9
|
+
* Note: This example shows the intended API. Full C++ Redis integration
|
|
10
|
+
* requires implementing saveState/loadState methods in DspPipeline.cc
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { createClient } from "redis";
|
|
14
|
+
import { createDspPipeline } from "../bindings";
|
|
15
|
+
|
|
16
|
+
async function redisExample(startFresh = false) {
|
|
17
|
+
console.log("=== DSP Pipeline with Redis State Persistence ===\n");
|
|
18
|
+
|
|
19
|
+
// 1. Create Redis client
|
|
20
|
+
const redis = await createClient({ url: "redis://localhost:6379" }).connect();
|
|
21
|
+
|
|
22
|
+
// 2. Create DSP pipeline with Redis configuration
|
|
23
|
+
const stateKey = "dsp:pipeline:channel1";
|
|
24
|
+
|
|
25
|
+
// Optional: Clear previous state to start fresh
|
|
26
|
+
if (startFresh) {
|
|
27
|
+
console.log("Clearing previous state to start fresh...");
|
|
28
|
+
await redis.del(stateKey);
|
|
29
|
+
console.log("State cleared\n");
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const pipeline = createDspPipeline({
|
|
33
|
+
redisHost: "localhost",
|
|
34
|
+
redisPort: 6379,
|
|
35
|
+
stateKey: stateKey,
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
// 3. Build the pipeline
|
|
39
|
+
pipeline.MovingAverage({ mode: "moving", windowSize: 3 });
|
|
40
|
+
|
|
41
|
+
console.log("Pipeline created with moving average filter (window=3)");
|
|
42
|
+
|
|
43
|
+
// 4. Try to restore previous state (if exists)
|
|
44
|
+
const previousState = await redis.get(stateKey);
|
|
45
|
+
if (previousState) {
|
|
46
|
+
console.log("Found previous state in Redis, restoring...");
|
|
47
|
+
await pipeline.loadState(previousState);
|
|
48
|
+
console.log("State restored successfully!");
|
|
49
|
+
} else {
|
|
50
|
+
console.log("No previous state found in Redis");
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// 5. First batch of audio
|
|
54
|
+
console.log("\n--- Processing first batch ---");
|
|
55
|
+
const batch1 = new Float32Array([1, 2, 3, 4, 5]);
|
|
56
|
+
console.log("Input batch 1:", Array.from(batch1));
|
|
57
|
+
|
|
58
|
+
const output1 = await pipeline.processCopy(batch1, {
|
|
59
|
+
sampleRate: 1000,
|
|
60
|
+
channels: 1,
|
|
61
|
+
});
|
|
62
|
+
console.log("Output batch 1:", Array.from(output1));
|
|
63
|
+
|
|
64
|
+
// 6. Save state to Redis
|
|
65
|
+
console.log("\n--- Saving state to Redis ---");
|
|
66
|
+
const state = await pipeline.saveState();
|
|
67
|
+
await redis.set(stateKey, state);
|
|
68
|
+
console.log("State saved:", state);
|
|
69
|
+
|
|
70
|
+
// 7. Simulate stopping and restarting the process
|
|
71
|
+
console.log("\n--- Simulating restart ---");
|
|
72
|
+
|
|
73
|
+
// 8. Create new pipeline and restore state
|
|
74
|
+
const pipeline2 = createDspPipeline({
|
|
75
|
+
redisHost: "localhost",
|
|
76
|
+
redisPort: 6379,
|
|
77
|
+
stateKey: stateKey,
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
pipeline2.MovingAverage({ mode: "moving", windowSize: 3 });
|
|
81
|
+
|
|
82
|
+
const restoredState = await redis.get(stateKey);
|
|
83
|
+
if (restoredState) {
|
|
84
|
+
console.log("Restored state from Redis");
|
|
85
|
+
await pipeline2.loadState(restoredState);
|
|
86
|
+
console.log(
|
|
87
|
+
"State restoration complete - continuing from where we left off!"
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// 9. Continue processing with restored state
|
|
92
|
+
console.log("\n--- Processing second batch (after 'restart') ---");
|
|
93
|
+
const batch2 = new Float32Array([6, 7, 8]);
|
|
94
|
+
console.log("Input batch 2:", Array.from(batch2));
|
|
95
|
+
|
|
96
|
+
const output2 = await pipeline2.processCopy(batch2, {
|
|
97
|
+
sampleRate: 1000,
|
|
98
|
+
channels: 1,
|
|
99
|
+
});
|
|
100
|
+
console.log("Output batch 2:", Array.from(output2));
|
|
101
|
+
|
|
102
|
+
console.log(
|
|
103
|
+
"\nNote: With state restoration, batch2 continues the moving average from where batch1 left off!"
|
|
104
|
+
);
|
|
105
|
+
console.log(
|
|
106
|
+
"Expected output without state: [6, 6.5, 7] - but we got continuous averaging!"
|
|
107
|
+
);
|
|
108
|
+
|
|
109
|
+
// 10. Optional: Clear state (commented out to persist between runs)
|
|
110
|
+
// console.log("\n--- Clearing state ---");
|
|
111
|
+
// await redis.del(stateKey);
|
|
112
|
+
// pipeline2.clearState();
|
|
113
|
+
// console.log("State cleared from Redis");
|
|
114
|
+
|
|
115
|
+
// Disconnect from Redis
|
|
116
|
+
await redis.disconnect();
|
|
117
|
+
} // Real-world use case example
|
|
118
|
+
async function streamingExample(startFresh = false) {
|
|
119
|
+
console.log("\n\n=== Streaming Audio Processing Example ===\n");
|
|
120
|
+
|
|
121
|
+
const redis = await createClient({ url: "redis://localhost:6379" }).connect();
|
|
122
|
+
const channelId = "audio-stream-ch1";
|
|
123
|
+
const stateKey = `dsp:stream:${channelId}`;
|
|
124
|
+
|
|
125
|
+
// Optional: Clear previous state to start fresh
|
|
126
|
+
if (startFresh) {
|
|
127
|
+
console.log("Clearing previous streaming state to start fresh...");
|
|
128
|
+
await redis.del(stateKey);
|
|
129
|
+
console.log("Streaming state cleared\n");
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Create pipeline
|
|
133
|
+
const pipeline = createDspPipeline({
|
|
134
|
+
redisHost: "localhost",
|
|
135
|
+
redisPort: 6379,
|
|
136
|
+
stateKey: stateKey,
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
pipeline.MovingAverage({ mode: "moving", windowSize: 5 });
|
|
140
|
+
|
|
141
|
+
// Restore state if processing was interrupted
|
|
142
|
+
const savedState = await redis.get(stateKey);
|
|
143
|
+
if (savedState) {
|
|
144
|
+
console.log("Resuming from saved state...");
|
|
145
|
+
await pipeline.loadState(savedState);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// Simulate streaming audio chunks
|
|
149
|
+
const chunks = [
|
|
150
|
+
new Float32Array([1, 2, 3, 4, 5]),
|
|
151
|
+
new Float32Array([6, 7, 8, 9, 10]),
|
|
152
|
+
new Float32Array([11, 12, 13, 14, 15]),
|
|
153
|
+
];
|
|
154
|
+
|
|
155
|
+
console.log("Processing audio stream in chunks...\n");
|
|
156
|
+
|
|
157
|
+
for (let i = 0; i < chunks.length; i++) {
|
|
158
|
+
const chunk = chunks[i];
|
|
159
|
+
console.log(`Chunk ${i + 1} input:`, Array.from(chunk));
|
|
160
|
+
|
|
161
|
+
const processed = await pipeline.processCopy(chunk, {
|
|
162
|
+
sampleRate: 44100,
|
|
163
|
+
channels: 1,
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
console.log(`Chunk ${i + 1} output:`, Array.from(processed));
|
|
167
|
+
|
|
168
|
+
// Save state after each chunk (for crash recovery)
|
|
169
|
+
const state = await pipeline.saveState();
|
|
170
|
+
await redis.set(stateKey, state);
|
|
171
|
+
console.log(`State saved after chunk ${i + 1}\n`);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
console.log("Stream processing complete. State saved for next session.");
|
|
175
|
+
|
|
176
|
+
// Disconnect from Redis
|
|
177
|
+
await redis.disconnect();
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// Run examples
|
|
181
|
+
console.log("Running DSP + Redis examples...\n");
|
|
182
|
+
|
|
183
|
+
// Set to true to clear previous state and start fresh
|
|
184
|
+
// Set to false to demonstrate state persistence across runs
|
|
185
|
+
const START_FRESH = false;
|
|
186
|
+
|
|
187
|
+
redisExample(START_FRESH)
|
|
188
|
+
.then(() => streamingExample(START_FRESH))
|
|
189
|
+
.then(() => {
|
|
190
|
+
console.log("\nAll examples completed successfully!");
|
|
191
|
+
console.log(
|
|
192
|
+
"\nTip: Set START_FRESH = true to clear Redis state and start fresh"
|
|
193
|
+
);
|
|
194
|
+
console.log(
|
|
195
|
+
"Tip: Set START_FRESH = false to see state persistence across runs"
|
|
196
|
+
);
|
|
197
|
+
process.exit(0);
|
|
198
|
+
})
|
|
199
|
+
.catch((error) => {
|
|
200
|
+
console.error("\nError:", error);
|
|
201
|
+
process.exit(1);
|
|
202
|
+
});
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SIMD Performance Benchmark
|
|
3
|
+
*
|
|
4
|
+
* This benchmark demonstrates the performance of SIMD-accelerated operations.
|
|
5
|
+
*
|
|
6
|
+
* Run with: npm test (to see it passes), then check operation timings
|
|
7
|
+
*
|
|
8
|
+
* Note: For accurate benchmarks, build in Release mode and run multiple times
|
|
9
|
+
* to account for V8 JIT compilation and CPU thermal throttling.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { createDspPipeline } from "../bindings.js";
|
|
13
|
+
|
|
14
|
+
console.log("🚀 SIMD Performance Demonstration");
|
|
15
|
+
console.log("===================================\n");
|
|
16
|
+
|
|
17
|
+
async function demonstrateSIMD() {
|
|
18
|
+
const BUFFER_SIZE = 100000; // 100k samples
|
|
19
|
+
const ITERATIONS = 50;
|
|
20
|
+
|
|
21
|
+
// Create test signal with mixed positive/negative values
|
|
22
|
+
const signal = new Float32Array(BUFFER_SIZE);
|
|
23
|
+
for (let i = 0; i < BUFFER_SIZE; i++) {
|
|
24
|
+
signal[i] = Math.sin(i * 0.1) * 100 + Math.cos(i * 0.05) * 50;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
console.log(`📊 Test Configuration:`);
|
|
28
|
+
console.log(` • Buffer size: ${BUFFER_SIZE.toLocaleString()} samples`);
|
|
29
|
+
console.log(` • Iterations: ${ITERATIONS}`);
|
|
30
|
+
console.log(
|
|
31
|
+
` • Total samples: ${(BUFFER_SIZE * ITERATIONS).toLocaleString()}\n`
|
|
32
|
+
);
|
|
33
|
+
|
|
34
|
+
// Test 1: Rectify (SIMD-accelerated)
|
|
35
|
+
console.log("🔧 Test 1: Rectify (Full-Wave) - SIMD Accelerated");
|
|
36
|
+
const processor1 = createDspPipeline();
|
|
37
|
+
processor1.Rectify({ mode: "full" });
|
|
38
|
+
|
|
39
|
+
const start1 = performance.now();
|
|
40
|
+
for (let i = 0; i < ITERATIONS; i++) {
|
|
41
|
+
const buffer = signal.slice();
|
|
42
|
+
await processor1.process(buffer, { channels: 1 });
|
|
43
|
+
}
|
|
44
|
+
const end1 = performance.now();
|
|
45
|
+
const time1 = end1 - start1;
|
|
46
|
+
const throughput1 = ((BUFFER_SIZE * ITERATIONS) / time1) * 1000;
|
|
47
|
+
|
|
48
|
+
console.log(` ✅ Time: ${time1.toFixed(2)} ms`);
|
|
49
|
+
console.log(
|
|
50
|
+
` ✅ Throughput: ${(throughput1 / 1_000_000).toFixed(2)} M samples/sec\n`
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
// Test 2: Batch Average (SIMD-accelerated for single channel)
|
|
54
|
+
console.log("🔧 Test 2: Batch Average - SIMD Accelerated");
|
|
55
|
+
const processor2 = createDspPipeline();
|
|
56
|
+
processor2.MovingAverage({ mode: "batch" });
|
|
57
|
+
|
|
58
|
+
const start2 = performance.now();
|
|
59
|
+
for (let i = 0; i < ITERATIONS; i++) {
|
|
60
|
+
const buffer = signal.slice();
|
|
61
|
+
await processor2.process(buffer, { channels: 1 });
|
|
62
|
+
}
|
|
63
|
+
const end2 = performance.now();
|
|
64
|
+
const time2 = end2 - start2;
|
|
65
|
+
const throughput2 = ((BUFFER_SIZE * ITERATIONS) / time2) * 1000;
|
|
66
|
+
|
|
67
|
+
console.log(` ✅ Time: ${time2.toFixed(2)} ms`);
|
|
68
|
+
console.log(
|
|
69
|
+
` ✅ Throughput: ${(throughput2 / 1_000_000).toFixed(2)} M samples/sec\n`
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
// Test 3: Batch RMS (SIMD-accelerated for single channel)
|
|
73
|
+
console.log("🔧 Test 3: Batch RMS - SIMD Accelerated");
|
|
74
|
+
const processor3 = createDspPipeline();
|
|
75
|
+
processor3.Rms({ mode: "batch" });
|
|
76
|
+
|
|
77
|
+
const start3 = performance.now();
|
|
78
|
+
for (let i = 0; i < ITERATIONS; i++) {
|
|
79
|
+
const buffer = signal.slice();
|
|
80
|
+
await processor3.process(buffer, { channels: 1 });
|
|
81
|
+
}
|
|
82
|
+
const end3 = performance.now();
|
|
83
|
+
const time3 = end3 - start3;
|
|
84
|
+
const throughput3 = ((BUFFER_SIZE * ITERATIONS) / time3) * 1000;
|
|
85
|
+
|
|
86
|
+
console.log(` ✅ Time: ${time3.toFixed(2)} ms`);
|
|
87
|
+
console.log(
|
|
88
|
+
` ✅ Throughput: ${(throughput3 / 1_000_000).toFixed(2)} M samples/sec\n`
|
|
89
|
+
);
|
|
90
|
+
|
|
91
|
+
console.log("─".repeat(60));
|
|
92
|
+
console.log("\n📈 SIMD Optimization Summary:\n");
|
|
93
|
+
console.log("✨ What makes these operations fast:");
|
|
94
|
+
console.log(" 1. Compiler optimizations (-O3, /O2, -ffast-math)");
|
|
95
|
+
console.log(" 2. SIMD intrinsics (AVX2/SSE2/NEON)");
|
|
96
|
+
console.log(" 3. Zero-copy processing (in-place modification)");
|
|
97
|
+
console.log(" 4. C++ Native code (no JavaScript overhead)\n");
|
|
98
|
+
|
|
99
|
+
console.log("🎯 Platform-specific SIMD support:");
|
|
100
|
+
console.log(" • x86/x64 + AVX2: 8 floats/cycle → 4-8x speedup");
|
|
101
|
+
console.log(" • x86/x64 + SSE2: 4 floats/cycle → 2-4x speedup");
|
|
102
|
+
console.log(" • ARM + NEON: 4 floats/cycle → 2-4x speedup");
|
|
103
|
+
console.log(
|
|
104
|
+
" • Scalar fallback: 1 float/cycle → compiler auto-vectorizes\n"
|
|
105
|
+
);
|
|
106
|
+
|
|
107
|
+
console.log("💡 Best performance tips:");
|
|
108
|
+
console.log(" • Use single-channel data when possible (contiguous memory)");
|
|
109
|
+
console.log(" • Prefer batch mode for one-time calculations");
|
|
110
|
+
console.log(" • Use moving mode only when state continuity is needed");
|
|
111
|
+
console.log(" • Process larger buffers to amortize overhead\n");
|
|
112
|
+
|
|
113
|
+
console.log("� For more details, see:");
|
|
114
|
+
console.log(" docs/SIMD_OPTIMIZATIONS.md\n");
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Run demonstration
|
|
118
|
+
demonstrateSIMD()
|
|
119
|
+
.then(() => {
|
|
120
|
+
console.log("✅ Demonstration complete\n");
|
|
121
|
+
process.exit(0);
|
|
122
|
+
})
|
|
123
|
+
.catch((error) => {
|
|
124
|
+
console.error("❌ Demonstration failed:", error);
|
|
125
|
+
process.exit(1);
|
|
126
|
+
});
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Example: Using .tap() for Pipeline Debugging
|
|
3
|
+
* Demonstrates how to inspect intermediate results at any point in the pipeline
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { createDspPipeline } from "../index.js";
|
|
7
|
+
|
|
8
|
+
console.log("🔍 Pipeline Debugging with .tap()\n");
|
|
9
|
+
|
|
10
|
+
// Example 1: Basic inspection
|
|
11
|
+
console.log("Example 1: Basic Inspection");
|
|
12
|
+
const pipeline1 = createDspPipeline()
|
|
13
|
+
.MovingAverage({ mode: "moving", windowSize: 3 })
|
|
14
|
+
.tap((samples, stage) => {
|
|
15
|
+
console.log(` After ${stage}:`);
|
|
16
|
+
console.log(
|
|
17
|
+
` First 5 samples: [${Array.from(samples.slice(0, 5))
|
|
18
|
+
.map((v) => v.toFixed(2))
|
|
19
|
+
.join(", ")}]`
|
|
20
|
+
);
|
|
21
|
+
console.log(` Length: ${samples.length}`);
|
|
22
|
+
})
|
|
23
|
+
.Rectify({ mode: "full" })
|
|
24
|
+
.tap((samples, stage) => {
|
|
25
|
+
console.log(` After ${stage}:`);
|
|
26
|
+
console.log(
|
|
27
|
+
` First 5 samples: [${Array.from(samples.slice(0, 5))
|
|
28
|
+
.map((v) => v.toFixed(2))
|
|
29
|
+
.join(", ")}]`
|
|
30
|
+
);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
const input1 = Float32Array.from(
|
|
34
|
+
{ length: 10 },
|
|
35
|
+
(_, i) => Math.sin(i * 0.5) - 0.5
|
|
36
|
+
);
|
|
37
|
+
await pipeline1.process(input1, { sampleRate: 1000 });
|
|
38
|
+
|
|
39
|
+
// Example 2: Conditional alerts based on thresholds
|
|
40
|
+
console.log("\n\nExample 2: Threshold Monitoring");
|
|
41
|
+
const THRESHOLD = 0.8;
|
|
42
|
+
let alertCount = 0;
|
|
43
|
+
|
|
44
|
+
const pipeline2 = createDspPipeline()
|
|
45
|
+
.MovingAverage({ mode: "moving", windowSize: 5 })
|
|
46
|
+
.Rectify({ mode: "full" })
|
|
47
|
+
.Rms({ mode: "moving", windowSize: 10 })
|
|
48
|
+
.tap((samples, stage) => {
|
|
49
|
+
const max = Math.max(...samples);
|
|
50
|
+
const avg = samples.reduce((a, b) => a + b, 0) / samples.length;
|
|
51
|
+
|
|
52
|
+
console.log(` Stats after ${stage}:`);
|
|
53
|
+
console.log(` Max: ${max.toFixed(4)}, Avg: ${avg.toFixed(4)}`);
|
|
54
|
+
|
|
55
|
+
if (max > THRESHOLD) {
|
|
56
|
+
alertCount++;
|
|
57
|
+
console.log(
|
|
58
|
+
` 🚨 ALERT: Max value ${max.toFixed(
|
|
59
|
+
4
|
|
60
|
+
)} exceeds threshold ${THRESHOLD}`
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
const input2 = Float32Array.from(
|
|
66
|
+
{ length: 50 },
|
|
67
|
+
(_, i) => Math.sin(i * 0.3) * (1 + Math.random() * 0.5)
|
|
68
|
+
);
|
|
69
|
+
await pipeline2.process(input2, { sampleRate: 1000 });
|
|
70
|
+
console.log(`\n Total alerts: ${alertCount}`);
|
|
71
|
+
|
|
72
|
+
// Example 3: Multiple taps in complex pipeline
|
|
73
|
+
console.log("\n\n🔗 Example 3: Multi-Stage Inspection");
|
|
74
|
+
const stages: Array<{
|
|
75
|
+
name: string;
|
|
76
|
+
stats: { min: number; max: number; mean: number };
|
|
77
|
+
}> = [];
|
|
78
|
+
|
|
79
|
+
const pipeline3 = createDspPipeline()
|
|
80
|
+
.tap((samples) => {
|
|
81
|
+
const stats = {
|
|
82
|
+
min: Math.min(...samples),
|
|
83
|
+
max: Math.max(...samples),
|
|
84
|
+
mean: samples.reduce((a, b) => a + b, 0) / samples.length,
|
|
85
|
+
};
|
|
86
|
+
stages.push({ name: "raw input", stats });
|
|
87
|
+
})
|
|
88
|
+
.MovingAverage({ mode: "moving", windowSize: 5 })
|
|
89
|
+
.tap((samples, stage) => {
|
|
90
|
+
const stats = {
|
|
91
|
+
min: Math.min(...samples),
|
|
92
|
+
max: Math.max(...samples),
|
|
93
|
+
mean: samples.reduce((a, b) => a + b, 0) / samples.length,
|
|
94
|
+
};
|
|
95
|
+
stages.push({ name: stage, stats });
|
|
96
|
+
})
|
|
97
|
+
.Rectify({ mode: "full" })
|
|
98
|
+
.tap((samples, stage) => {
|
|
99
|
+
const stats = {
|
|
100
|
+
min: Math.min(...samples),
|
|
101
|
+
max: Math.max(...samples),
|
|
102
|
+
mean: samples.reduce((a, b) => a + b, 0) / samples.length,
|
|
103
|
+
};
|
|
104
|
+
stages.push({ name: stage, stats });
|
|
105
|
+
})
|
|
106
|
+
.Rms({ mode: "moving", windowSize: 3 })
|
|
107
|
+
.tap((samples, stage) => {
|
|
108
|
+
const stats = {
|
|
109
|
+
min: Math.min(...samples),
|
|
110
|
+
max: Math.max(...samples),
|
|
111
|
+
mean: samples.reduce((a, b) => a + b, 0) / samples.length,
|
|
112
|
+
};
|
|
113
|
+
stages.push({ name: stage, stats });
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
const input3 = Float32Array.from(
|
|
117
|
+
{ length: 100 },
|
|
118
|
+
(_, i) => Math.sin(i * 0.1) * 2 - 1
|
|
119
|
+
);
|
|
120
|
+
await pipeline3.process(input3, { sampleRate: 1000 });
|
|
121
|
+
|
|
122
|
+
console.log("\n Pipeline Statistics Table:");
|
|
123
|
+
console.log(
|
|
124
|
+
" ┌─────────────────────────────────┬──────────┬──────────┬──────────┐"
|
|
125
|
+
);
|
|
126
|
+
console.log(
|
|
127
|
+
" │ Stage │ Min │ Max │ Mean │"
|
|
128
|
+
);
|
|
129
|
+
console.log(
|
|
130
|
+
" ├─────────────────────────────────┼──────────┼──────────┼──────────┤"
|
|
131
|
+
);
|
|
132
|
+
stages.forEach(({ name, stats }) => {
|
|
133
|
+
const paddedName = name.padEnd(31);
|
|
134
|
+
console.log(
|
|
135
|
+
` │ ${paddedName} │ ${stats.min.toFixed(4).padStart(8)} │ ${stats.max
|
|
136
|
+
.toFixed(4)
|
|
137
|
+
.padStart(8)} │ ${stats.mean.toFixed(4).padStart(8)} │`
|
|
138
|
+
);
|
|
139
|
+
});
|
|
140
|
+
console.log(
|
|
141
|
+
" └─────────────────────────────────┴──────────┴──────────┴──────────┘"
|
|
142
|
+
);
|
|
143
|
+
|
|
144
|
+
// Example 4: Debugging with logger integration
|
|
145
|
+
console.log("\n\nExample 4: Logger Integration");
|
|
146
|
+
const logger = {
|
|
147
|
+
debug: (msg: string, data?: any) =>
|
|
148
|
+
console.log(` [DEBUG] ${msg}`, data || ""),
|
|
149
|
+
info: (msg: string, data?: any) =>
|
|
150
|
+
console.log(` [INFO] ${msg}`, data || ""),
|
|
151
|
+
warn: (msg: string, data?: any) =>
|
|
152
|
+
console.log(` [WARN] ${msg}`, data || ""),
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
const pipeline4 = createDspPipeline()
|
|
156
|
+
.MovingAverage({ mode: "moving", windowSize: 10 })
|
|
157
|
+
.tap((samples, stage) =>
|
|
158
|
+
logger.debug(`Processed ${stage}`, { sampleCount: samples.length })
|
|
159
|
+
)
|
|
160
|
+
.Rectify()
|
|
161
|
+
.tap((samples, stage) => {
|
|
162
|
+
const hasNegative = samples.some((v) => v < 0);
|
|
163
|
+
if (hasNegative) {
|
|
164
|
+
logger.warn(`Unexpected negative values after ${stage}`);
|
|
165
|
+
} else {
|
|
166
|
+
logger.info(`${stage} completed successfully`);
|
|
167
|
+
}
|
|
168
|
+
})
|
|
169
|
+
.Rms({ mode: "moving", windowSize: 5 })
|
|
170
|
+
.tap((samples, stage) => {
|
|
171
|
+
const max = Math.max(...samples);
|
|
172
|
+
logger.debug(`Max RMS value`, { value: max.toFixed(4) });
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
const input4 = Float32Array.from({ length: 20 }, (_, i) => Math.cos(i * 0.2));
|
|
176
|
+
await pipeline4.process(input4, { sampleRate: 1000 });
|
|
177
|
+
|
|
178
|
+
// Example 5: Performance impact measurement
|
|
179
|
+
console.log("\n\nExample 5: Performance Impact of .tap()");
|
|
180
|
+
const iterations = 1000;
|
|
181
|
+
const sampleSize = 1000;
|
|
182
|
+
|
|
183
|
+
// Without tap
|
|
184
|
+
const pipelineNoTap = createDspPipeline()
|
|
185
|
+
.MovingAverage({ mode: "moving", windowSize: 10 })
|
|
186
|
+
.Rectify()
|
|
187
|
+
.Rms({ mode: "moving", windowSize: 5 });
|
|
188
|
+
|
|
189
|
+
const startNoTap = performance.now();
|
|
190
|
+
for (let i = 0; i < iterations; i++) {
|
|
191
|
+
const input = Float32Array.from({ length: sampleSize }, () => Math.random());
|
|
192
|
+
await pipelineNoTap.process(input, { sampleRate: 1000 });
|
|
193
|
+
}
|
|
194
|
+
const durationNoTap = performance.now() - startNoTap;
|
|
195
|
+
|
|
196
|
+
// With tap (minimal work)
|
|
197
|
+
const pipelineWithTap = createDspPipeline()
|
|
198
|
+
.MovingAverage({ mode: "moving", windowSize: 10 })
|
|
199
|
+
.tap(() => {}) // Empty tap
|
|
200
|
+
.Rectify()
|
|
201
|
+
.tap(() => {}) // Empty tap
|
|
202
|
+
.Rms({ mode: "moving", windowSize: 5 })
|
|
203
|
+
.tap(() => {}); // Empty tap
|
|
204
|
+
|
|
205
|
+
const startWithTap = performance.now();
|
|
206
|
+
for (let i = 0; i < iterations; i++) {
|
|
207
|
+
const input = Float32Array.from({ length: sampleSize }, () => Math.random());
|
|
208
|
+
await pipelineWithTap.process(input, { sampleRate: 1000 });
|
|
209
|
+
}
|
|
210
|
+
const durationWithTap = performance.now() - startWithTap;
|
|
211
|
+
|
|
212
|
+
console.log(
|
|
213
|
+
` Without .tap(): ${durationNoTap.toFixed(2)}ms for ${iterations} iterations`
|
|
214
|
+
);
|
|
215
|
+
console.log(
|
|
216
|
+
` With .tap(): ${durationWithTap.toFixed(
|
|
217
|
+
2
|
|
218
|
+
)}ms for ${iterations} iterations`
|
|
219
|
+
);
|
|
220
|
+
console.log(
|
|
221
|
+
` Overhead: ${(durationWithTap - durationNoTap).toFixed(2)}ms (${(
|
|
222
|
+
(durationWithTap / durationNoTap - 1) *
|
|
223
|
+
100
|
|
224
|
+
).toFixed(2)}%)`
|
|
225
|
+
);
|
|
226
|
+
console.log(
|
|
227
|
+
`\n Tip: Remove .tap() calls in production or use conditional logic`
|
|
228
|
+
);
|
|
229
|
+
|
|
230
|
+
console.log("\nAll examples completed!");
|