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,131 @@
|
|
|
1
|
+
import { createDspPipeline } from "../index.js";
|
|
2
|
+
|
|
3
|
+
console.log("Pipeline State Listing Example\n");
|
|
4
|
+
|
|
5
|
+
// Example 1: Simple pipeline
|
|
6
|
+
console.log("1. Simple Pipeline");
|
|
7
|
+
{
|
|
8
|
+
const pipeline = createDspPipeline()
|
|
9
|
+
.MovingAverage({ mode: "moving", windowSize: 100 })
|
|
10
|
+
.Rectify({ mode: "full" })
|
|
11
|
+
.Rms({ mode: "moving", windowSize: 50 });
|
|
12
|
+
|
|
13
|
+
const summary = pipeline.listState();
|
|
14
|
+
console.log(" Pipeline summary:", JSON.stringify(summary, null, 2));
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// Example 2: After processing (with state)
|
|
18
|
+
console.log("\n2. Pipeline After Processing (With State)");
|
|
19
|
+
{
|
|
20
|
+
const pipeline = createDspPipeline()
|
|
21
|
+
.MovingAverage({ mode: "moving", windowSize: 10 })
|
|
22
|
+
.Rms({ mode: "moving", windowSize: 5 });
|
|
23
|
+
|
|
24
|
+
// Process some data to populate state
|
|
25
|
+
const input = new Float32Array(50).map((_, i) => Math.sin(i * 0.1));
|
|
26
|
+
await pipeline.process(input, { sampleRate: 1000, channels: 1 });
|
|
27
|
+
|
|
28
|
+
const summary = pipeline.listState();
|
|
29
|
+
console.log(" Pipeline summary after processing:");
|
|
30
|
+
console.log(` Total stages: ${summary.stageCount}`);
|
|
31
|
+
summary.stages.forEach((stage) => {
|
|
32
|
+
console.log(` Stage ${stage.index}: ${stage.type}`);
|
|
33
|
+
if (stage.windowSize)
|
|
34
|
+
console.log(` - Window size: ${stage.windowSize}`);
|
|
35
|
+
if (stage.numChannels)
|
|
36
|
+
console.log(` - Channels: ${stage.numChannels}`);
|
|
37
|
+
if (stage.mode) console.log(` - Mode: ${stage.mode}`);
|
|
38
|
+
if (stage.bufferSize)
|
|
39
|
+
console.log(` - Buffer size: ${stage.bufferSize}`);
|
|
40
|
+
if (stage.channelCount)
|
|
41
|
+
console.log(` - Channel count: ${stage.channelCount}`);
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Example 3: Multi-channel pipeline
|
|
46
|
+
console.log("\n3. Multi-Channel Pipeline");
|
|
47
|
+
{
|
|
48
|
+
const pipeline = createDspPipeline()
|
|
49
|
+
.MovingAverage({ mode: "moving", windowSize: 20 })
|
|
50
|
+
.Rectify()
|
|
51
|
+
.Rms({ mode: "moving", windowSize: 10 });
|
|
52
|
+
|
|
53
|
+
// Process 4-channel data
|
|
54
|
+
const input = new Float32Array(400).map((_, i) => Math.sin(i * 0.1));
|
|
55
|
+
await pipeline.process(input, { sampleRate: 2000, channels: 4 });
|
|
56
|
+
|
|
57
|
+
const summary = pipeline.listState();
|
|
58
|
+
console.log(" Multi-channel pipeline:");
|
|
59
|
+
summary.stages.forEach((stage) => {
|
|
60
|
+
console.log(` ${stage.type}: ${stage.channelCount} channels`);
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Example 4: Monitoring/Debugging
|
|
65
|
+
console.log("\n4. Pipeline Monitoring/Debugging");
|
|
66
|
+
{
|
|
67
|
+
const pipeline = createDspPipeline()
|
|
68
|
+
.MovingAverage({ mode: "moving", windowSize: 100 })
|
|
69
|
+
.Rectify({ mode: "half" })
|
|
70
|
+
.Rms({ mode: "moving", windowSize: 50 });
|
|
71
|
+
|
|
72
|
+
// This could be used in a monitoring endpoint
|
|
73
|
+
const summary = pipeline.listState();
|
|
74
|
+
|
|
75
|
+
console.log(" Pipeline health check:");
|
|
76
|
+
console.log(` - Pipeline has ${summary.stageCount} stages`);
|
|
77
|
+
console.log(
|
|
78
|
+
` - Configuration is valid: ${summary.stages.every((s) => s.type)}`
|
|
79
|
+
);
|
|
80
|
+
console.log(
|
|
81
|
+
` - Timestamp: ${new Date(summary.timestamp * 1000).toISOString()}`
|
|
82
|
+
);
|
|
83
|
+
|
|
84
|
+
// Check for expected configuration
|
|
85
|
+
const hasMovingAverage = summary.stages.some(
|
|
86
|
+
(s) => s.type === "movingAverage"
|
|
87
|
+
);
|
|
88
|
+
const hasRms = summary.stages.some((s) => s.type === "rms");
|
|
89
|
+
console.log(` - Has MovingAverage: ${hasMovingAverage}`);
|
|
90
|
+
console.log(` - Has RMS: ${hasRms}`);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Example 5: Comparison with saveState()
|
|
94
|
+
console.log("\n5. listState() vs saveState() Comparison");
|
|
95
|
+
{
|
|
96
|
+
const pipeline = createDspPipeline().MovingAverage({ mode: "moving", windowSize: 5 });
|
|
97
|
+
|
|
98
|
+
// Process some data
|
|
99
|
+
const input = new Float32Array([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);
|
|
100
|
+
await pipeline.process(input, { sampleRate: 1000, channels: 1 });
|
|
101
|
+
|
|
102
|
+
// Compare sizes
|
|
103
|
+
const summary = pipeline.listState();
|
|
104
|
+
const fullState = await pipeline.saveState();
|
|
105
|
+
|
|
106
|
+
console.log(" Size comparison:");
|
|
107
|
+
console.log(
|
|
108
|
+
` - listState() JSON: ${JSON.stringify(summary).length} bytes`
|
|
109
|
+
);
|
|
110
|
+
console.log(` - saveState() JSON: ${fullState.length} bytes`);
|
|
111
|
+
console.log(
|
|
112
|
+
` - Reduction: ${(
|
|
113
|
+
(1 - JSON.stringify(summary).length / fullState.length) *
|
|
114
|
+
100
|
|
115
|
+
).toFixed(1)}%`
|
|
116
|
+
);
|
|
117
|
+
|
|
118
|
+
console.log("\n listState() returns:");
|
|
119
|
+
console.log(` ${JSON.stringify(summary, null, 2)}`);
|
|
120
|
+
|
|
121
|
+
console.log("\n saveState() includes:");
|
|
122
|
+
console.log(` - Full circular buffer contents (all samples)`);
|
|
123
|
+
console.log(` - Running sums/squares for each channel`);
|
|
124
|
+
console.log(` - Complete state for restoration`);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
console.log("\nUse Cases:");
|
|
128
|
+
console.log(" - listState(): Monitoring dashboards, health checks, debugging");
|
|
129
|
+
console.log(
|
|
130
|
+
" - saveState(): Redis persistence, crash recovery, state migration"
|
|
131
|
+
);
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Example: Production-Ready Unified Logger
|
|
3
|
+
*
|
|
4
|
+
* This example demonstrates the new Logger class with multiple backend handlers,
|
|
5
|
+
* error isolation, and structured logging capabilities.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import {
|
|
9
|
+
Logger,
|
|
10
|
+
createConsoleHandler,
|
|
11
|
+
createLokiHandler,
|
|
12
|
+
createPrometheusHandler,
|
|
13
|
+
createPagerDutyHandler,
|
|
14
|
+
} from "../index.js";
|
|
15
|
+
|
|
16
|
+
// Example 1: Basic logger with console output
|
|
17
|
+
const basicLogger = new Logger([createConsoleHandler()]);
|
|
18
|
+
|
|
19
|
+
await basicLogger.info("Pipeline initialized");
|
|
20
|
+
await basicLogger.warn("High latency detected", "performance", {
|
|
21
|
+
latency: 450,
|
|
22
|
+
});
|
|
23
|
+
await basicLogger.error("Connection failed", "redis.connection", {
|
|
24
|
+
host: "localhost",
|
|
25
|
+
port: 6379,
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
// Example 2: Production logger with multiple backends
|
|
29
|
+
const productionLogger = new Logger([
|
|
30
|
+
// Console for local debugging
|
|
31
|
+
createConsoleHandler(),
|
|
32
|
+
|
|
33
|
+
// Loki for centralized log aggregation
|
|
34
|
+
createLokiHandler({
|
|
35
|
+
endpoint: "https://loki.example.com",
|
|
36
|
+
apiKey: "your-loki-api-key",
|
|
37
|
+
batchSize: 100,
|
|
38
|
+
flushInterval: 5000,
|
|
39
|
+
}),
|
|
40
|
+
|
|
41
|
+
// Prometheus for metrics
|
|
42
|
+
createPrometheusHandler({
|
|
43
|
+
endpoint: "https://prometheus-pushgateway.example.com",
|
|
44
|
+
}),
|
|
45
|
+
|
|
46
|
+
// PagerDuty for critical alerts
|
|
47
|
+
createPagerDutyHandler({
|
|
48
|
+
endpoint: "https://events.pagerduty.com/v2/enqueue",
|
|
49
|
+
apiKey: "your-routing-key",
|
|
50
|
+
}),
|
|
51
|
+
]);
|
|
52
|
+
|
|
53
|
+
// Example 3: Child logger with topic prefix
|
|
54
|
+
const pipelineLogger = productionLogger.child("dsp.pipeline");
|
|
55
|
+
const filterLogger = pipelineLogger.child("filter");
|
|
56
|
+
|
|
57
|
+
await filterLogger.info("MovingAverage initialized", "init", {
|
|
58
|
+
windowSize: 10,
|
|
59
|
+
sampleRate: 1000,
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
// Example 4: Error isolation demonstration
|
|
63
|
+
const resilientLogger = new Logger([
|
|
64
|
+
createConsoleHandler(), // This will work
|
|
65
|
+
|
|
66
|
+
// Simulate a failing handler
|
|
67
|
+
async () => {
|
|
68
|
+
throw new Error("Network timeout");
|
|
69
|
+
},
|
|
70
|
+
|
|
71
|
+
createConsoleHandler(), // This will still work!
|
|
72
|
+
]);
|
|
73
|
+
|
|
74
|
+
// All handlers are isolated - one failure doesn't break others
|
|
75
|
+
await resilientLogger.info("Resilient logging works!");
|
|
76
|
+
|
|
77
|
+
// Example 5: Structured logging with rich context
|
|
78
|
+
await productionLogger.info("DSP processing complete", "dsp.result", {
|
|
79
|
+
channels: 8,
|
|
80
|
+
samples: 10000,
|
|
81
|
+
duration_ms: 142,
|
|
82
|
+
filters: ["MovingAverage", "RMS", "ZScoreNormalize"],
|
|
83
|
+
metrics: {
|
|
84
|
+
throughput: 70422, // samples/sec
|
|
85
|
+
memory_mb: 3.2,
|
|
86
|
+
cpu_percent: 12.5,
|
|
87
|
+
},
|
|
88
|
+
tags: ["production", "high-performance"],
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
console.log("\n✅ All examples completed successfully!");
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Notch Filter Examples
|
|
3
|
+
*
|
|
4
|
+
* Demonstrates how to create and use notch filters for removing specific frequency bands.
|
|
5
|
+
* Common use cases: 50/60 Hz power line noise removal, narrow-band interference rejection
|
|
6
|
+
*
|
|
7
|
+
* IMPORTANT: Notch filters are created using band-stop filters
|
|
8
|
+
* API: FirFilter.createBandStop({ lowCutoffFrequency, highCutoffFrequency, sampleRate, order })
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { FirFilter } from "../filters.js";
|
|
12
|
+
|
|
13
|
+
// Main async function to handle Promise-based filter processing
|
|
14
|
+
async function main() {
|
|
15
|
+
console.log("=== Notch Filter Examples ===\n");
|
|
16
|
+
|
|
17
|
+
// =============================================================================
|
|
18
|
+
// Example 1: FIR Notch Filter for 60 Hz Power Line Noise
|
|
19
|
+
// =============================================================================
|
|
20
|
+
console.log("Example 1: FIR Notch Filter (60 Hz power line noise)");
|
|
21
|
+
|
|
22
|
+
const firNotch60Hz = FirFilter.createBandStop({
|
|
23
|
+
lowCutoffFrequency: 58, // Lower edge of notch
|
|
24
|
+
highCutoffFrequency: 62, // Upper edge of notch
|
|
25
|
+
sampleRate: 1000,
|
|
26
|
+
order: 101, // Higher order = sharper notch
|
|
27
|
+
windowType: "hamming",
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
// Test signal with 60 Hz interference
|
|
31
|
+
const sampleRate = 1000;
|
|
32
|
+
const duration = 1; // 1 second
|
|
33
|
+
const numSamples = sampleRate * duration;
|
|
34
|
+
const signal = new Float32Array(numSamples);
|
|
35
|
+
|
|
36
|
+
// Create signal: 10 Hz sine wave + 60 Hz noise
|
|
37
|
+
for (let i = 0; i < numSamples; i++) {
|
|
38
|
+
const t = i / sampleRate;
|
|
39
|
+
signal[i] =
|
|
40
|
+
Math.sin(2 * Math.PI * 10 * t) + // 10 Hz signal
|
|
41
|
+
0.5 * Math.sin(2 * Math.PI * 60 * t); // 60 Hz noise
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const filtered = await firNotch60Hz.process(signal);
|
|
45
|
+
|
|
46
|
+
console.log(
|
|
47
|
+
` Input samples (first 10): ${Array.from(signal.slice(0, 10))
|
|
48
|
+
.map((v: number) => v.toFixed(3))
|
|
49
|
+
.join(", ")}`
|
|
50
|
+
);
|
|
51
|
+
console.log(
|
|
52
|
+
` Filtered samples (first 10): ${Array.from(filtered.slice(0, 10))
|
|
53
|
+
.map((v: number) => v.toFixed(3))
|
|
54
|
+
.join(", ")}`
|
|
55
|
+
);
|
|
56
|
+
console.log(` 60 Hz component should be significantly reduced\n`);
|
|
57
|
+
|
|
58
|
+
// =============================================================================
|
|
59
|
+
// Example 2: Cascaded Notch Filters (Remove Multiple Harmonics)
|
|
60
|
+
// =============================================================================
|
|
61
|
+
console.log("Example 2: Cascaded Notch Filters (60 Hz + harmonics)");
|
|
62
|
+
|
|
63
|
+
// Remove 60 Hz fundamental and 2nd harmonic (120 Hz)
|
|
64
|
+
const notch60 = FirFilter.createBandStop({
|
|
65
|
+
lowCutoffFrequency: 58,
|
|
66
|
+
highCutoffFrequency: 62,
|
|
67
|
+
sampleRate: 1000,
|
|
68
|
+
order: 51,
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
const notch120 = FirFilter.createBandStop({
|
|
72
|
+
lowCutoffFrequency: 118,
|
|
73
|
+
highCutoffFrequency: 122,
|
|
74
|
+
sampleRate: 1000,
|
|
75
|
+
order: 51,
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
// Signal with multiple harmonics
|
|
79
|
+
const harmonicSignal = new Float32Array(1000);
|
|
80
|
+
for (let i = 0; i < 1000; i++) {
|
|
81
|
+
const t = i / 1000;
|
|
82
|
+
harmonicSignal[i] =
|
|
83
|
+
Math.sin(2 * Math.PI * 10 * t) + // Desired 10 Hz
|
|
84
|
+
0.3 * Math.sin(2 * Math.PI * 60 * t) + // 60 Hz noise
|
|
85
|
+
0.2 * Math.sin(2 * Math.PI * 120 * t); // 120 Hz harmonic
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Apply filters in series (must await each step)
|
|
89
|
+
const step1 = await notch60.process(harmonicSignal);
|
|
90
|
+
const step2 = await notch120.process(step1);
|
|
91
|
+
|
|
92
|
+
console.log(" Original signal: 10 Hz + 60 Hz + 120 Hz interference");
|
|
93
|
+
console.log(" After 60 Hz notch: Reduced 60 Hz component");
|
|
94
|
+
console.log(" After 120 Hz notch: Reduced both harmonics");
|
|
95
|
+
console.log(
|
|
96
|
+
` Final output (first 10): ${Array.from(step2.slice(0, 10))
|
|
97
|
+
.map((v: number) => v.toFixed(3))
|
|
98
|
+
.join(", ")}\n`
|
|
99
|
+
);
|
|
100
|
+
|
|
101
|
+
// =============================================================================
|
|
102
|
+
// Example 3: Real-time Notch Filtering (Stateful Processing)
|
|
103
|
+
// =============================================================================
|
|
104
|
+
console.log("Example 3: Real-time Notch Filtering (Streaming)");
|
|
105
|
+
|
|
106
|
+
const realtimeNotch = FirFilter.createBandStop({
|
|
107
|
+
lowCutoffFrequency: 49,
|
|
108
|
+
highCutoffFrequency: 51, // 50 Hz notch (EU power line)
|
|
109
|
+
sampleRate: 1000,
|
|
110
|
+
order: 51,
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
console.log(" Processing 5 frames of 100 samples each...");
|
|
114
|
+
|
|
115
|
+
for (let frame = 0; frame < 5; frame++) {
|
|
116
|
+
const frameData = new Float32Array(100);
|
|
117
|
+
|
|
118
|
+
// Generate frame with 50 Hz noise
|
|
119
|
+
for (let i = 0; i < 100; i++) {
|
|
120
|
+
const sampleIndex = frame * 100 + i;
|
|
121
|
+
const t = sampleIndex / 1000;
|
|
122
|
+
frameData[i] =
|
|
123
|
+
Math.sin(2 * Math.PI * 5 * t) + // 5 Hz signal
|
|
124
|
+
0.4 * Math.sin(2 * Math.PI * 50 * t); // 50 Hz noise
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const filteredFrame = await realtimeNotch.process(frameData);
|
|
128
|
+
|
|
129
|
+
const inputPower =
|
|
130
|
+
frameData.reduce((sum: number, x: number) => sum + x * x, 0) /
|
|
131
|
+
frameData.length;
|
|
132
|
+
const outputPower =
|
|
133
|
+
filteredFrame.reduce((sum: number, x: number) => sum + x * x, 0) /
|
|
134
|
+
filteredFrame.length;
|
|
135
|
+
|
|
136
|
+
console.log(
|
|
137
|
+
` Frame ${frame + 1}: Input power=${inputPower.toFixed(
|
|
138
|
+
4
|
|
139
|
+
)}, Output power=${outputPower.toFixed(4)}`
|
|
140
|
+
);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
console.log(" Filters maintain state between process() calls by default\n");
|
|
144
|
+
|
|
145
|
+
// =============================================================================
|
|
146
|
+
// Example 4: Notch Filter Design Trade-offs
|
|
147
|
+
// =============================================================================
|
|
148
|
+
console.log("Example 4: Notch Filter Design Trade-offs");
|
|
149
|
+
|
|
150
|
+
console.log(" Narrow Notch (1 Hz bandwidth, order 201):");
|
|
151
|
+
console.log(" + Minimal passband distortion");
|
|
152
|
+
console.log(" + Precise frequency rejection");
|
|
153
|
+
console.log(" - Higher computational cost");
|
|
154
|
+
console.log(" - Longer group delay");
|
|
155
|
+
console.log();
|
|
156
|
+
console.log(" Wide Notch (10 Hz bandwidth, order 51):");
|
|
157
|
+
console.log(" + Lower computational cost");
|
|
158
|
+
console.log(" + Shorter group delay");
|
|
159
|
+
console.log(" - More passband distortion near notch");
|
|
160
|
+
console.log(" - Less precise frequency rejection\n");
|
|
161
|
+
|
|
162
|
+
// =============================================================================
|
|
163
|
+
// Example 5: Verifying Notch Filter Performance
|
|
164
|
+
// =============================================================================
|
|
165
|
+
console.log("Example 5: Verifying Notch Filter Performance");
|
|
166
|
+
|
|
167
|
+
const testNotch = FirFilter.createBandStop({
|
|
168
|
+
lowCutoffFrequency: 95,
|
|
169
|
+
highCutoffFrequency: 105, // 100 Hz notch
|
|
170
|
+
sampleRate: 1000,
|
|
171
|
+
order: 101,
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
// Test with pure 100 Hz tone (should be completely removed)
|
|
175
|
+
const testSignal100Hz = new Float32Array(1000);
|
|
176
|
+
for (let i = 0; i < 1000; i++) {
|
|
177
|
+
testSignal100Hz[i] = Math.sin((2 * Math.PI * 100 * i) / 1000);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
const filtered100Hz = await testNotch.process(testSignal100Hz);
|
|
181
|
+
|
|
182
|
+
// Calculate attenuation
|
|
183
|
+
const inputPower =
|
|
184
|
+
testSignal100Hz.reduce((sum: number, x: number) => sum + x * x, 0) /
|
|
185
|
+
testSignal100Hz.length;
|
|
186
|
+
const outputPower =
|
|
187
|
+
filtered100Hz.reduce((sum: number, x: number) => sum + x * x, 0) /
|
|
188
|
+
filtered100Hz.length;
|
|
189
|
+
const attenuationDb = 10 * Math.log10(outputPower / inputPower);
|
|
190
|
+
|
|
191
|
+
console.log(` Input: Pure 100 Hz sine wave`);
|
|
192
|
+
console.log(` Input RMS power: ${inputPower.toFixed(6)}`);
|
|
193
|
+
console.log(` Output RMS power: ${outputPower.toFixed(6)}`);
|
|
194
|
+
console.log(` Attenuation: ${attenuationDb.toFixed(2)} dB`);
|
|
195
|
+
console.log(` (More negative = better rejection)\n`);
|
|
196
|
+
|
|
197
|
+
// =============================================================================
|
|
198
|
+
// Important Notes
|
|
199
|
+
// =============================================================================
|
|
200
|
+
console.log("=== Important Notes ===");
|
|
201
|
+
console.log();
|
|
202
|
+
console.log("1. CORRECT API Usage:");
|
|
203
|
+
console.log(" const notchFilter = FirFilter.createBandStop({");
|
|
204
|
+
console.log(" lowCutoffFrequency: 58,");
|
|
205
|
+
console.log(" highCutoffFrequency: 62,");
|
|
206
|
+
console.log(" sampleRate: 1000,");
|
|
207
|
+
console.log(" order: 51");
|
|
208
|
+
console.log(" });");
|
|
209
|
+
console.log();
|
|
210
|
+
console.log("2. Notch filters are implemented as band-stop filters");
|
|
211
|
+
console.log(' Mode: "bandstop" or "notch" are equivalent');
|
|
212
|
+
console.log(' Note: "notch" is a MODE, not a TYPE');
|
|
213
|
+
console.log();
|
|
214
|
+
console.log("3. Filter Type Selection:");
|
|
215
|
+
console.log(" - FIR: Linear phase, always stable, higher order needed");
|
|
216
|
+
console.log(
|
|
217
|
+
" - IIR: Sharper rolloff, lower order, may have phase distortion"
|
|
218
|
+
);
|
|
219
|
+
console.log();
|
|
220
|
+
console.log("4. Parameters:");
|
|
221
|
+
console.log(" - lowCutoffFrequency: Lower edge of rejection band");
|
|
222
|
+
console.log(" - highCutoffFrequency: Upper edge of rejection band");
|
|
223
|
+
console.log(" - Bandwidth = highCutoff - lowCutoff");
|
|
224
|
+
console.log();
|
|
225
|
+
console.log("5. Design Guidelines:");
|
|
226
|
+
console.log(" - Narrow notch: Bandwidth < 5 Hz (needs high order)");
|
|
227
|
+
console.log(" - Wide notch: Bandwidth > 10 Hz (lower order acceptable)");
|
|
228
|
+
console.log(" - Power line rejection: 2-4 Hz bandwidth typical");
|
|
229
|
+
console.log();
|
|
230
|
+
console.log("6. Common Applications:");
|
|
231
|
+
console.log(" - 50/60 Hz power line noise removal");
|
|
232
|
+
console.log(" - RF interference rejection");
|
|
233
|
+
console.log(" - Mechanical vibration artifact removal");
|
|
234
|
+
console.log(" - Narrow-band EMI filtering");
|
|
235
|
+
console.log();
|
|
236
|
+
console.log("7. Pipeline Integration:");
|
|
237
|
+
console.log(" - Filter stages in DSP pipeline not yet implemented");
|
|
238
|
+
console.log(" - Use standalone filters and manual chaining for now");
|
|
239
|
+
console.log(" - See examples above for correct usage patterns");
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// Run the examples
|
|
243
|
+
main().catch(console.error);
|