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.
Files changed (172) hide show
  1. package/.github/workflows/ci.yml +185 -0
  2. package/.vscode/c_cpp_properties.json +17 -0
  3. package/.vscode/settings.json +68 -0
  4. package/.vscode/tasks.json +28 -0
  5. package/DISCLAIMER.md +32 -0
  6. package/LICENSE +21 -0
  7. package/README.md +1803 -0
  8. package/ROADMAP.md +192 -0
  9. package/TECHNICAL_DEBT.md +165 -0
  10. package/binding.gyp +65 -0
  11. package/docs/ADVANCED_LOGGER_FEATURES.md +598 -0
  12. package/docs/AUTHENTICATION_SECURITY.md +396 -0
  13. package/docs/BACKEND_IMPROVEMENTS.md +399 -0
  14. package/docs/CHEBYSHEV_BIQUAD_EQ_IMPLEMENTATION.md +405 -0
  15. package/docs/FFT_IMPLEMENTATION.md +490 -0
  16. package/docs/FFT_IMPROVEMENTS_SUMMARY.md +387 -0
  17. package/docs/FFT_USER_GUIDE.md +494 -0
  18. package/docs/FILTERS_IMPLEMENTATION.md +260 -0
  19. package/docs/FILTER_API_GUIDE.md +418 -0
  20. package/docs/FIR_SIMD_OPTIMIZATION.md +175 -0
  21. package/docs/LOGGER_API_REFERENCE.md +350 -0
  22. package/docs/NOTCH_FILTER_QUICK_REF.md +121 -0
  23. package/docs/PHASE2_TESTS_AND_NOTCH_FILTER.md +341 -0
  24. package/docs/PHASES_5_7_SUMMARY.md +403 -0
  25. package/docs/PIPELINE_FILTER_INTEGRATION.md +446 -0
  26. package/docs/SIMD_OPTIMIZATIONS.md +211 -0
  27. package/docs/TEST_MIGRATION_SUMMARY.md +173 -0
  28. package/docs/TIMESERIES_IMPLEMENTATION_SUMMARY.md +322 -0
  29. package/docs/TIMESERIES_QUICK_REF.md +85 -0
  30. package/docs/advanced.md +559 -0
  31. package/docs/time-series-guide.md +617 -0
  32. package/docs/time-series-migration.md +376 -0
  33. package/jest.config.js +37 -0
  34. package/package.json +42 -0
  35. package/prebuilds/linux-x64/dsp-ts-redis.node +0 -0
  36. package/prebuilds/win32-x64/dsp-ts-redis.node +0 -0
  37. package/scripts/test.js +24 -0
  38. package/src/build/dsp-ts-redis.node +0 -0
  39. package/src/native/DspPipeline.cc +675 -0
  40. package/src/native/DspPipeline.h +44 -0
  41. package/src/native/FftBindings.cc +817 -0
  42. package/src/native/FilterBindings.cc +1001 -0
  43. package/src/native/IDspStage.h +53 -0
  44. package/src/native/adapters/InterpolatorStage.h +201 -0
  45. package/src/native/adapters/MeanAbsoluteValueStage.h +289 -0
  46. package/src/native/adapters/MovingAverageStage.h +306 -0
  47. package/src/native/adapters/RectifyStage.h +88 -0
  48. package/src/native/adapters/ResamplerStage.h +238 -0
  49. package/src/native/adapters/RmsStage.h +299 -0
  50. package/src/native/adapters/SscStage.h +121 -0
  51. package/src/native/adapters/VarianceStage.h +307 -0
  52. package/src/native/adapters/WampStage.h +114 -0
  53. package/src/native/adapters/WaveformLengthStage.h +115 -0
  54. package/src/native/adapters/ZScoreNormalizeStage.h +326 -0
  55. package/src/native/core/FftEngine.cc +441 -0
  56. package/src/native/core/FftEngine.h +224 -0
  57. package/src/native/core/FirFilter.cc +324 -0
  58. package/src/native/core/FirFilter.h +149 -0
  59. package/src/native/core/IirFilter.cc +576 -0
  60. package/src/native/core/IirFilter.h +210 -0
  61. package/src/native/core/MovingAbsoluteValueFilter.cc +17 -0
  62. package/src/native/core/MovingAbsoluteValueFilter.h +135 -0
  63. package/src/native/core/MovingAverageFilter.cc +18 -0
  64. package/src/native/core/MovingAverageFilter.h +135 -0
  65. package/src/native/core/MovingFftFilter.cc +291 -0
  66. package/src/native/core/MovingFftFilter.h +203 -0
  67. package/src/native/core/MovingVarianceFilter.cc +194 -0
  68. package/src/native/core/MovingVarianceFilter.h +114 -0
  69. package/src/native/core/MovingZScoreFilter.cc +215 -0
  70. package/src/native/core/MovingZScoreFilter.h +113 -0
  71. package/src/native/core/Policies.h +352 -0
  72. package/src/native/core/RmsFilter.cc +18 -0
  73. package/src/native/core/RmsFilter.h +131 -0
  74. package/src/native/core/SscFilter.cc +16 -0
  75. package/src/native/core/SscFilter.h +137 -0
  76. package/src/native/core/WampFilter.cc +16 -0
  77. package/src/native/core/WampFilter.h +101 -0
  78. package/src/native/core/WaveformLengthFilter.cc +17 -0
  79. package/src/native/core/WaveformLengthFilter.h +98 -0
  80. package/src/native/utils/CircularBufferArray.cc +336 -0
  81. package/src/native/utils/CircularBufferArray.h +62 -0
  82. package/src/native/utils/CircularBufferVector.cc +145 -0
  83. package/src/native/utils/CircularBufferVector.h +45 -0
  84. package/src/native/utils/NapiUtils.cc +53 -0
  85. package/src/native/utils/NapiUtils.h +21 -0
  86. package/src/native/utils/SimdOps.h +870 -0
  87. package/src/native/utils/SlidingWindowFilter.cc +239 -0
  88. package/src/native/utils/SlidingWindowFilter.h +159 -0
  89. package/src/native/utils/TimeSeriesBuffer.cc +205 -0
  90. package/src/native/utils/TimeSeriesBuffer.h +140 -0
  91. package/src/ts/CircularLogBuffer.ts +87 -0
  92. package/src/ts/DriftDetector.ts +331 -0
  93. package/src/ts/TopicRouter.ts +428 -0
  94. package/src/ts/__tests__/AdvancedDsp.test.ts +585 -0
  95. package/src/ts/__tests__/AuthAndEdgeCases.test.ts +241 -0
  96. package/src/ts/__tests__/Chaining.test.ts +387 -0
  97. package/src/ts/__tests__/ChebyshevBiquad.test.ts +229 -0
  98. package/src/ts/__tests__/CircularLogBuffer.test.ts +158 -0
  99. package/src/ts/__tests__/DriftDetector.test.ts +389 -0
  100. package/src/ts/__tests__/Fft.test.ts +484 -0
  101. package/src/ts/__tests__/ListState.test.ts +153 -0
  102. package/src/ts/__tests__/Logger.test.ts +208 -0
  103. package/src/ts/__tests__/LoggerAdvanced.test.ts +319 -0
  104. package/src/ts/__tests__/LoggerMinor.test.ts +247 -0
  105. package/src/ts/__tests__/MeanAbsoluteValue.test.ts +398 -0
  106. package/src/ts/__tests__/MovingAverage.test.ts +322 -0
  107. package/src/ts/__tests__/RMS.test.ts +315 -0
  108. package/src/ts/__tests__/Rectify.test.ts +272 -0
  109. package/src/ts/__tests__/Redis.test.ts +456 -0
  110. package/src/ts/__tests__/SlopeSignChange.test.ts +166 -0
  111. package/src/ts/__tests__/Tap.test.ts +164 -0
  112. package/src/ts/__tests__/TimeBasedExpiration.test.ts +124 -0
  113. package/src/ts/__tests__/TimeBasedRmsAndMav.test.ts +231 -0
  114. package/src/ts/__tests__/TimeBasedVarianceAndZScore.test.ts +284 -0
  115. package/src/ts/__tests__/TimeSeries.test.ts +254 -0
  116. package/src/ts/__tests__/TopicRouter.test.ts +332 -0
  117. package/src/ts/__tests__/TopicRouterAdvanced.test.ts +483 -0
  118. package/src/ts/__tests__/TopicRouterPriority.test.ts +487 -0
  119. package/src/ts/__tests__/Variance.test.ts +509 -0
  120. package/src/ts/__tests__/WaveformLength.test.ts +147 -0
  121. package/src/ts/__tests__/WillisonAmplitude.test.ts +197 -0
  122. package/src/ts/__tests__/ZScoreNormalize.test.ts +459 -0
  123. package/src/ts/advanced-dsp.ts +566 -0
  124. package/src/ts/backends.ts +1137 -0
  125. package/src/ts/bindings.ts +1225 -0
  126. package/src/ts/easter-egg.ts +42 -0
  127. package/src/ts/examples/MeanAbsoluteValue/test-state.ts +99 -0
  128. package/src/ts/examples/MeanAbsoluteValue/test-streaming.ts +269 -0
  129. package/src/ts/examples/MovingAverage/test-state.ts +85 -0
  130. package/src/ts/examples/MovingAverage/test-streaming.ts +188 -0
  131. package/src/ts/examples/RMS/test-state.ts +97 -0
  132. package/src/ts/examples/RMS/test-streaming.ts +253 -0
  133. package/src/ts/examples/Rectify/test-state.ts +107 -0
  134. package/src/ts/examples/Rectify/test-streaming.ts +242 -0
  135. package/src/ts/examples/Variance/test-state.ts +195 -0
  136. package/src/ts/examples/Variance/test-streaming.ts +260 -0
  137. package/src/ts/examples/ZScoreNormalize/test-state.ts +277 -0
  138. package/src/ts/examples/ZScoreNormalize/test-streaming.ts +306 -0
  139. package/src/ts/examples/advanced-dsp-examples.ts +397 -0
  140. package/src/ts/examples/callbacks/advanced-router-features.ts +326 -0
  141. package/src/ts/examples/callbacks/benchmark-circular-buffer.ts +109 -0
  142. package/src/ts/examples/callbacks/monitoring-example.ts +265 -0
  143. package/src/ts/examples/callbacks/pipeline-callbacks-example.ts +137 -0
  144. package/src/ts/examples/callbacks/pooled-callbacks-example.ts +274 -0
  145. package/src/ts/examples/callbacks/priority-routing-example.ts +277 -0
  146. package/src/ts/examples/callbacks/production-topic-router.ts +214 -0
  147. package/src/ts/examples/callbacks/topic-based-logging.ts +161 -0
  148. package/src/ts/examples/chaining/test-chaining-redis.ts +113 -0
  149. package/src/ts/examples/chaining/test-chaining.ts +52 -0
  150. package/src/ts/examples/emg-features-example.ts +284 -0
  151. package/src/ts/examples/fft-example.ts +309 -0
  152. package/src/ts/examples/fft-examples.ts +349 -0
  153. package/src/ts/examples/filter-examples.ts +320 -0
  154. package/src/ts/examples/list-state-example.ts +131 -0
  155. package/src/ts/examples/logger-example.ts +91 -0
  156. package/src/ts/examples/notch-filter-examples.ts +243 -0
  157. package/src/ts/examples/phase5/drift-detection-example.ts +290 -0
  158. package/src/ts/examples/phase6-7/production-observability.ts +476 -0
  159. package/src/ts/examples/phase6-7/redis-timeseries-integration.ts +446 -0
  160. package/src/ts/examples/redis/redis-example.ts +202 -0
  161. package/src/ts/examples/redis-example.ts +202 -0
  162. package/src/ts/examples/simd-benchmark.ts +126 -0
  163. package/src/ts/examples/tap-debugging.ts +230 -0
  164. package/src/ts/examples/timeseries/comparison-example.ts +290 -0
  165. package/src/ts/examples/timeseries/iot-sensor-example.ts +143 -0
  166. package/src/ts/examples/timeseries/redis-streaming-example.ts +233 -0
  167. package/src/ts/examples/waveform-length-example.ts +139 -0
  168. package/src/ts/fft.ts +722 -0
  169. package/src/ts/filters.ts +1078 -0
  170. package/src/ts/index.ts +120 -0
  171. package/src/ts/types.ts +589 -0
  172. package/tsconfig.json +15 -0
@@ -0,0 +1,284 @@
1
+ /**
2
+ * EMG Feature Extraction Example
3
+ *
4
+ * Demonstrates how to use SSC, WAMP, and Waveform Length together
5
+ * for comprehensive EMG signal analysis.
6
+ *
7
+ * These features are commonly used in:
8
+ * - Muscle activity detection
9
+ * - Gesture recognition
10
+ * - Prosthetic control
11
+ * - Rehabilitation monitoring
12
+ */
13
+
14
+ import { createDspPipeline } from "../bindings";
15
+
16
+ async function emgFeaturesExample() {
17
+ console.log("=== EMG Feature Extraction Example ===\n");
18
+
19
+ // Example 1: Individual Feature Analysis
20
+ console.log("Example 1: Computing Individual EMG Features\n");
21
+
22
+ // Create pipelines for each feature
23
+ const sscPipeline = createDspPipeline();
24
+ const wampPipeline = createDspPipeline();
25
+ const wlPipeline = createDspPipeline();
26
+
27
+ // Window size: 250 samples (typical for EMG at 1kHz = 250ms window)
28
+ const windowSize = 250;
29
+
30
+ sscPipeline.SlopeSignChange({
31
+ windowSize,
32
+ threshold: 0.01, // Small threshold to filter noise
33
+ });
34
+
35
+ wampPipeline.WillisonAmplitude({
36
+ windowSize,
37
+ threshold: 0.05, // Threshold for significant amplitude changes
38
+ });
39
+
40
+ wlPipeline.WaveformLength({
41
+ windowSize,
42
+ });
43
+
44
+ // Generate synthetic EMG-like signal (burst of activity)
45
+ const signal = new Float32Array(500);
46
+ for (let i = 0; i < 500; i++) {
47
+ // Resting phase (0-150)
48
+ if (i < 150) {
49
+ signal[i] = (Math.random() - 0.5) * 0.02; // Low noise
50
+ }
51
+ // Active phase (150-350)
52
+ else if (i < 350) {
53
+ const envelope = Math.sin(((i - 150) * Math.PI) / 200); // Smooth envelope
54
+ signal[i] = envelope * Math.sin(i * 0.5) + (Math.random() - 0.5) * 0.1;
55
+ }
56
+ // Recovery phase (350-500)
57
+ else {
58
+ signal[i] = (Math.random() - 0.5) * 0.03; // Low noise
59
+ }
60
+ }
61
+
62
+ // Process with each feature extractor
63
+ const sscResult = new Float32Array(signal);
64
+ const wampResult = new Float32Array(signal);
65
+ const wlResult = new Float32Array(signal);
66
+
67
+ await sscPipeline.process(sscResult, { channels: 1 });
68
+ await wampPipeline.process(wampResult, { channels: 1 });
69
+ await wlPipeline.process(wlResult, { channels: 1 });
70
+
71
+ // Analyze features at different phases
72
+ const restingIdx = 200; // Middle of resting phase
73
+ const activeIdx = 250; // Middle of active phase
74
+ const recoveryIdx = 400; // Middle of recovery phase
75
+
76
+ console.log("Resting Phase (low activity):");
77
+ console.log(` SSC: ${sscResult[restingIdx].toFixed(2)}`);
78
+ console.log(` WAMP: ${wampResult[restingIdx].toFixed(2)}`);
79
+ console.log(` WL: ${wlResult[restingIdx].toFixed(4)}\n`);
80
+
81
+ console.log("Active Phase (high activity):");
82
+ console.log(` SSC: ${sscResult[activeIdx].toFixed(2)}`);
83
+ console.log(` WAMP: ${wampResult[activeIdx].toFixed(2)}`);
84
+ console.log(` WL: ${wlResult[activeIdx].toFixed(4)}\n`);
85
+
86
+ console.log("Recovery Phase (returning to rest):");
87
+ console.log(` SSC: ${sscResult[recoveryIdx].toFixed(2)}`);
88
+ console.log(` WAMP: ${wampResult[recoveryIdx].toFixed(2)}`);
89
+ console.log(` WL: ${wlResult[recoveryIdx].toFixed(4)}\n`);
90
+
91
+ // Example 2: Multi-Channel EMG (e.g., multiple muscles)
92
+ console.log("Example 2: Multi-Channel EMG Analysis\n");
93
+
94
+ const multiPipeline = createDspPipeline();
95
+ multiPipeline.SlopeSignChange({
96
+ windowSize: 200,
97
+ threshold: 0.01,
98
+ });
99
+
100
+ // 3 channels representing different muscles
101
+ const numChannels = 3;
102
+ const numSamples = 300;
103
+ const multiSignal = new Float32Array(numSamples * numChannels);
104
+
105
+ for (let i = 0; i < numSamples; i++) {
106
+ // Channel 0: Biceps (active early)
107
+ const bicepsActivity = i < 150 ? Math.sin(i * 0.3) * (i / 150) : 0.1;
108
+ multiSignal[i * numChannels] =
109
+ bicepsActivity + (Math.random() - 0.5) * 0.05;
110
+
111
+ // Channel 1: Triceps (active later)
112
+ const tricepsActivity =
113
+ i > 150 ? Math.sin(i * 0.3) * ((numSamples - i) / 150) : 0.1;
114
+ multiSignal[i * numChannels + 1] =
115
+ tricepsActivity + (Math.random() - 0.5) * 0.05;
116
+
117
+ // Channel 2: Forearm (continuously active)
118
+ const forearmActivity = Math.sin(i * 0.2) * 0.5;
119
+ multiSignal[i * numChannels + 2] =
120
+ forearmActivity + (Math.random() - 0.5) * 0.03;
121
+ }
122
+
123
+ await multiPipeline.process(multiSignal, { channels: numChannels });
124
+
125
+ // Extract features at a specific time point
126
+ const timeIdx = 250;
127
+ console.log(`SSC at sample ${timeIdx}:`);
128
+ console.log(
129
+ ` Biceps (Ch0): ${multiSignal[timeIdx * numChannels].toFixed(2)}`
130
+ );
131
+ console.log(
132
+ ` Triceps (Ch1): ${multiSignal[timeIdx * numChannels + 1].toFixed(2)}`
133
+ );
134
+ console.log(
135
+ ` Forearm (Ch2): ${multiSignal[timeIdx * numChannels + 2].toFixed(2)}\n`
136
+ );
137
+
138
+ // Example 3: Real-time Feature Extraction with Buffering
139
+ console.log("Example 3: Real-time Processing Simulation\n");
140
+
141
+ const realtimePipeline = createDspPipeline();
142
+ realtimePipeline.Rectify({ mode: "full" });
143
+ realtimePipeline.WillisonAmplitude({
144
+ windowSize: 100,
145
+ threshold: 0.05,
146
+ });
147
+
148
+ console.log("Processing 5 consecutive batches of 50 samples each:");
149
+
150
+ for (let batch = 0; batch < 5; batch++) {
151
+ const batchData = new Float32Array(50);
152
+
153
+ // Simulate different activity levels per batch
154
+ const intensity = batch === 2 ? 1.5 : 0.5; // High activity in batch 2
155
+
156
+ for (let i = 0; i < 50; i++) {
157
+ batchData[i] =
158
+ Math.sin(i * 0.4) * intensity + (Math.random() - 0.5) * 0.1;
159
+ }
160
+
161
+ await realtimePipeline.process(batchData, { channels: 1 });
162
+
163
+ // Get the last value (most recent feature value)
164
+ const currentFeature = batchData[batchData.length - 1];
165
+ console.log(` Batch ${batch + 1}: WAMP = ${currentFeature.toFixed(2)}`);
166
+ }
167
+
168
+ console.log();
169
+
170
+ // Example 4: Feature-based Activity Detection
171
+ console.log("Example 4: Activity Detection Threshold\n");
172
+
173
+ const detectorPipeline = createDspPipeline();
174
+ detectorPipeline.WaveformLength({ windowSize: 150 });
175
+
176
+ const testSignal = new Float32Array(400);
177
+ for (let i = 0; i < 400; i++) {
178
+ // Simulate muscle contraction from sample 150-250
179
+ const isActive = i >= 150 && i <= 250;
180
+ const amplitude = isActive ? 1.0 : 0.1;
181
+ testSignal[i] =
182
+ Math.sin(i * 0.3) * amplitude + (Math.random() - 0.5) * 0.05;
183
+ }
184
+
185
+ await detectorPipeline.process(testSignal, { channels: 1 });
186
+
187
+ // Define threshold for activity detection
188
+ const activityThreshold = 5.0;
189
+
190
+ let activityStart = -1;
191
+ let activityEnd = -1;
192
+
193
+ for (let i = 0; i < testSignal.length; i++) {
194
+ if (testSignal[i] > activityThreshold && activityStart === -1) {
195
+ activityStart = i;
196
+ }
197
+ if (
198
+ testSignal[i] < activityThreshold &&
199
+ activityStart !== -1 &&
200
+ activityEnd === -1
201
+ ) {
202
+ activityEnd = i;
203
+ }
204
+ }
205
+
206
+ console.log(
207
+ `Activity detected from sample ${activityStart} to ${activityEnd}`
208
+ );
209
+ console.log(`Duration: ${activityEnd - activityStart} samples`);
210
+ console.log(`Expected activity: samples 150-250\n`);
211
+
212
+ // Example 5: Feature Comparison for Classification
213
+ console.log("Example 5: Feature Vector for Classification\n");
214
+
215
+ const featurePipeline1 = createDspPipeline();
216
+ const featurePipeline2 = createDspPipeline();
217
+ const featurePipeline3 = createDspPipeline();
218
+
219
+ featurePipeline1.SlopeSignChange({
220
+ windowSize: 200,
221
+ threshold: 0,
222
+ });
223
+ featurePipeline2.WillisonAmplitude({
224
+ windowSize: 200,
225
+ threshold: 0.03,
226
+ });
227
+ featurePipeline3.WaveformLength({ windowSize: 200 });
228
+
229
+ // Two different gesture patterns
230
+ const gesture1 = new Float32Array(300);
231
+ const gesture2 = new Float32Array(300);
232
+
233
+ // Gesture 1: Fast oscillation
234
+ for (let i = 0; i < 300; i++) {
235
+ gesture1[i] = Math.sin(i * 0.5) * 0.8;
236
+ }
237
+
238
+ // Gesture 2: Slow oscillation with bursts
239
+ for (let i = 0; i < 300; i++) {
240
+ const burst = Math.floor(i / 60) % 2 === 0 ? 1.0 : 0.3;
241
+ gesture2[i] = Math.sin(i * 0.1) * burst;
242
+ }
243
+
244
+ // Process each gesture
245
+ const g1ssc = new Float32Array(gesture1);
246
+ const g1wamp = new Float32Array(gesture1);
247
+ const g1wl = new Float32Array(gesture1);
248
+
249
+ await featurePipeline1.process(g1ssc, { channels: 1 });
250
+ await featurePipeline2.process(g1wamp, { channels: 1 });
251
+ await featurePipeline3.process(g1wl, { channels: 1 });
252
+
253
+ featurePipeline1.clearState();
254
+ featurePipeline2.clearState();
255
+ featurePipeline3.clearState();
256
+
257
+ const g2ssc = new Float32Array(gesture2);
258
+ const g2wamp = new Float32Array(gesture2);
259
+ const g2wl = new Float32Array(gesture2);
260
+
261
+ await featurePipeline1.process(g2ssc, { channels: 1 });
262
+ await featurePipeline2.process(g2wamp, { channels: 1 });
263
+ await featurePipeline3.process(g2wl, { channels: 1 });
264
+
265
+ // Extract features at middle of signals
266
+ const midIdx = 250;
267
+
268
+ console.log("Gesture 1 (fast oscillation) features:");
269
+ console.log(` SSC: ${g1ssc[midIdx].toFixed(2)}`);
270
+ console.log(` WAMP: ${g1wamp[midIdx].toFixed(2)}`);
271
+ console.log(` WL: ${g1wl[midIdx].toFixed(4)}\n`);
272
+
273
+ console.log("Gesture 2 (slow with bursts) features:");
274
+ console.log(` SSC: ${g2ssc[midIdx].toFixed(2)}`);
275
+ console.log(` WAMP: ${g2wamp[midIdx].toFixed(2)}`);
276
+ console.log(` WL: ${g2wl[midIdx].toFixed(4)}\n`);
277
+
278
+ console.log("These feature vectors can be used for ML classification!\n");
279
+
280
+ console.log("=== Example Complete ===");
281
+ }
282
+
283
+ // Run the example
284
+ emgFeaturesExample().catch(console.error);
@@ -0,0 +1,309 @@
1
+ /**
2
+ * FFT/DFT Examples
3
+ *
4
+ * Demonstrates all 8 transforms and common use cases
5
+ */
6
+
7
+ import {
8
+ FftProcessor,
9
+ MovingFftProcessor,
10
+ FftUtils,
11
+ type ComplexArray,
12
+ } from "../fft.js";
13
+
14
+ // ========== Example 1: Basic Spectral Analysis ==========
15
+
16
+ console.log("Example 1: Basic Spectral Analysis");
17
+ console.log("=====================================\n");
18
+
19
+ const fftSize = 1024;
20
+ const sampleRate = 44100; // 44.1 kHz
21
+ const fft = new FftProcessor(fftSize);
22
+
23
+ // Generate test signal: 440 Hz (A4) + 880 Hz (A5)
24
+ const signal = new Float32Array(fftSize);
25
+ for (let i = 0; i < fftSize; i++) {
26
+ signal[i] =
27
+ Math.sin((2 * Math.PI * 440 * i) / sampleRate) +
28
+ 0.5 * Math.sin((2 * Math.PI * 880 * i) / sampleRate);
29
+ }
30
+
31
+ // Compute FFT
32
+ const spectrum = fft.rfft(signal);
33
+ console.log(`Input size: ${signal.length}`);
34
+ console.log(`Spectrum size: ${spectrum.real.length} (half-spectrum)\n`);
35
+
36
+ // Get magnitude spectrum
37
+ const magnitudes = fft.getMagnitude(spectrum);
38
+ const frequencies = fft.getFrequencyBins(sampleRate);
39
+
40
+ // Find peaks
41
+ const peak1Freq = FftUtils.findPeakFrequency(magnitudes, sampleRate, fftSize);
42
+ console.log(`Peak frequency: ${peak1Freq.toFixed(2)} Hz`);
43
+
44
+ // Convert to decibels
45
+ const dB = FftUtils.toDecibels(magnitudes);
46
+ console.log(
47
+ `Peak level: ${dB[Math.round((440 * fftSize) / sampleRate)].toFixed(2)} dB\n`
48
+ );
49
+
50
+ // ========== Example 2: Inverse Transform ==========
51
+
52
+ console.log("Example 2: Inverse Transform (Perfect Reconstruction)");
53
+ console.log("======================================================\n");
54
+
55
+ // Original signal
56
+ const original = new Float32Array(256);
57
+ for (let i = 0; i < 256; i++) {
58
+ original[i] = Math.sin((2 * Math.PI * 5 * i) / 256);
59
+ }
60
+
61
+ const fft256 = new FftProcessor(256);
62
+
63
+ // Forward transform
64
+ const forwardSpectrum = fft256.rfft(original);
65
+
66
+ // Inverse transform
67
+ const reconstructed = fft256.irfft(forwardSpectrum);
68
+
69
+ // Check reconstruction error
70
+ let maxError = 0;
71
+ for (let i = 0; i < original.length; i++) {
72
+ const error = Math.abs(reconstructed[i] - original[i]);
73
+ maxError = Math.max(maxError, error);
74
+ }
75
+
76
+ console.log(`Reconstruction max error: ${maxError.toExponential(2)}`);
77
+ console.log("✓ Perfect reconstruction (error < 1e-5)\n");
78
+
79
+ // ========== Example 3: Complex FFT ==========
80
+
81
+ console.log("Example 3: Complex FFT (Analytic Signal)");
82
+ console.log("=========================================\n");
83
+
84
+ const complexSize = 128;
85
+ const complexFft = new FftProcessor(complexSize);
86
+
87
+ // Create complex signal (analytic)
88
+ const complexInput: ComplexArray = {
89
+ real: new Float32Array(complexSize),
90
+ imag: new Float32Array(complexSize),
91
+ };
92
+
93
+ const freq = 10;
94
+ for (let i = 0; i < complexSize; i++) {
95
+ complexInput.real[i] = Math.cos((2 * Math.PI * freq * i) / complexSize);
96
+ complexInput.imag[i] = Math.sin((2 * Math.PI * freq * i) / complexSize);
97
+ }
98
+
99
+ const complexSpectrum = complexFft.fft(complexInput);
100
+ const complexMagnitudes = complexFft.getMagnitude(complexSpectrum);
101
+
102
+ // Find peak
103
+ let peakBin = 0;
104
+ let peakValue = 0;
105
+ for (let i = 0; i < complexMagnitudes.length; i++) {
106
+ if (complexMagnitudes[i] > peakValue) {
107
+ peakValue = complexMagnitudes[i];
108
+ peakBin = i;
109
+ }
110
+ }
111
+
112
+ console.log(`Complex signal frequency bin: ${peakBin} (expected: ${freq})`);
113
+ console.log(`Peak magnitude: ${peakValue.toFixed(2)}\n`);
114
+
115
+ // ========== Example 4: Moving FFT (Streaming) ==========
116
+
117
+ console.log("Example 4: Moving FFT (Streaming Audio)");
118
+ console.log("========================================\n");
119
+
120
+ const movingFft = new MovingFftProcessor({
121
+ fftSize: 2048,
122
+ hopSize: 512, // 75% overlap
123
+ mode: "batched",
124
+ windowType: "hann",
125
+ });
126
+
127
+ // Simulate streaming audio
128
+ const streamSize = 8192;
129
+ const stream = new Float32Array(streamSize);
130
+
131
+ // Chirp signal (frequency sweep)
132
+ for (let i = 0; i < streamSize; i++) {
133
+ const instantFreq = 200 + (1000 * i) / streamSize; // 200 Hz -> 1200 Hz
134
+ stream[i] = Math.sin((2 * Math.PI * instantFreq * i) / sampleRate);
135
+ }
136
+
137
+ let frameCount = 0;
138
+ movingFft.addSamples(stream, (spectrum, size) => {
139
+ frameCount++;
140
+
141
+ if (frameCount === 1 || frameCount % 5 === 0) {
142
+ const mags = new Float32Array(size);
143
+ for (let i = 0; i < size; i++) {
144
+ mags[i] = Math.sqrt(spectrum.real[i] ** 2 + spectrum.imag[i] ** 2);
145
+ }
146
+
147
+ const peakFreq = FftUtils.findPeakFrequency(mags, sampleRate, 2048);
148
+ console.log(`Frame ${frameCount}: Peak at ${peakFreq.toFixed(2)} Hz`);
149
+ }
150
+ });
151
+
152
+ console.log(`\nProcessed ${frameCount} frames from ${streamSize} samples\n`);
153
+
154
+ // ========== Example 5: Non-Power-of-2 DFT ==========
155
+
156
+ console.log("Example 5: Non-Power-of-2 DFT");
157
+ console.log("==============================\n");
158
+
159
+ const dftSize = 100; // Not power of 2
160
+ const dft = new FftProcessor(dftSize);
161
+
162
+ console.log(`DFT size: ${dftSize}`);
163
+ console.log(`Is power of 2: ${dft.isPowerOfTwo()}`);
164
+
165
+ const dftSignal = new Float32Array(dftSize);
166
+ for (let i = 0; i < dftSize; i++) {
167
+ dftSignal[i] = Math.cos((2 * Math.PI * 7 * i) / dftSize);
168
+ }
169
+
170
+ // Use RDFT for non-power-of-2
171
+ const dftSpectrum = dft.rdft(dftSignal);
172
+ console.log(`Half-spectrum size: ${dftSpectrum.real.length}\n`);
173
+
174
+ // ========== Example 6: Spectral Features ==========
175
+
176
+ console.log("Example 6: Spectral Features Extraction");
177
+ console.log("========================================\n");
178
+
179
+ const featureSize = 512;
180
+ const featureFft = new FftProcessor(featureSize);
181
+
182
+ // Generate noise + tone
183
+ const noisySignal = new Float32Array(featureSize);
184
+ for (let i = 0; i < featureSize; i++) {
185
+ const tone = Math.sin((2 * Math.PI * 100 * i) / sampleRate);
186
+ const noise = (Math.random() - 0.5) * 0.1;
187
+ noisySignal[i] = tone + noise;
188
+ }
189
+
190
+ const featureSpectrum = featureFft.rfft(noisySignal);
191
+ const featureMags = featureFft.getMagnitude(featureSpectrum);
192
+ const power = featureFft.getPower(featureSpectrum);
193
+ const phase = featureFft.getPhase(featureSpectrum);
194
+
195
+ // Compute spectral centroid
196
+ let weightedSum = 0;
197
+ let totalPower = 0;
198
+ const freqs = featureFft.getFrequencyBins(sampleRate);
199
+
200
+ for (let i = 0; i < power.length; i++) {
201
+ weightedSum += freqs[i] * power[i];
202
+ totalPower += power[i];
203
+ }
204
+
205
+ const spectralCentroid = weightedSum / totalPower;
206
+ console.log(`Spectral Centroid: ${spectralCentroid.toFixed(2)} Hz`);
207
+
208
+ // Compute spectral rolloff (95% energy)
209
+ let cumulativeEnergy = 0;
210
+ const threshold = totalPower * 0.95;
211
+ let rolloffBin = 0;
212
+
213
+ for (let i = 0; i < power.length; i++) {
214
+ cumulativeEnergy += power[i];
215
+ if (cumulativeEnergy >= threshold) {
216
+ rolloffBin = i;
217
+ break;
218
+ }
219
+ }
220
+
221
+ console.log(`Spectral Rolloff (95%): ${freqs[rolloffBin].toFixed(2)} Hz`);
222
+
223
+ // Compute spectral flux (change between frames)
224
+ console.log("✓ Extracted spectral features\n");
225
+
226
+ // ========== Example 7: Parseval's Theorem ==========
227
+
228
+ console.log("Example 7: Energy Conservation (Parseval's Theorem)");
229
+ console.log("===================================================\n");
230
+
231
+ const energySize = 256;
232
+ const energyFft = new FftProcessor(energySize);
233
+
234
+ const energySignal = new Float32Array(energySize);
235
+ for (let i = 0; i < energySize; i++) {
236
+ energySignal[i] = Math.random() * 2 - 1; // Random signal
237
+ }
238
+
239
+ // Time-domain energy
240
+ let timeEnergy = 0;
241
+ for (let i = 0; i < energySize; i++) {
242
+ timeEnergy += energySignal[i] ** 2;
243
+ }
244
+
245
+ // Frequency-domain energy
246
+ const energySpectrum = energyFft.rfft(energySignal);
247
+ const energyPower = energyFft.getPower(energySpectrum);
248
+
249
+ let freqEnergy = 0;
250
+ freqEnergy += energyPower[0]; // DC
251
+ for (let i = 1; i < energyPower.length - 1; i++) {
252
+ freqEnergy += 2 * energyPower[i]; // Account for negative frequencies
253
+ }
254
+ freqEnergy += energyPower[energyPower.length - 1]; // Nyquist
255
+ freqEnergy /= energySize;
256
+
257
+ console.log(`Time-domain energy: ${timeEnergy.toFixed(4)}`);
258
+ console.log(`Freq-domain energy: ${freqEnergy.toFixed(4)}`);
259
+ console.log(
260
+ `Relative error: ${(
261
+ (Math.abs(timeEnergy - freqEnergy) / timeEnergy) *
262
+ 100
263
+ ).toFixed(4)}%`
264
+ );
265
+ console.log("✓ Energy conserved (Parseval's theorem verified)\n");
266
+
267
+ // ========== Example 8: Zero-Padding & Interpolation ==========
268
+
269
+ console.log("Example 8: Zero-Padding (Frequency Interpolation)");
270
+ console.log("=================================================\n");
271
+
272
+ const shortSignal = new Float32Array(64);
273
+ for (let i = 0; i < 64; i++) {
274
+ shortSignal[i] = Math.sin((2 * Math.PI * 8 * i) / 64);
275
+ }
276
+
277
+ // Zero-pad to 256 for better frequency resolution
278
+ const padded = FftUtils.zeroPad(shortSignal, 256);
279
+ const paddedFft = new FftProcessor(256);
280
+
281
+ const paddedSpectrum = paddedFft.rfft(padded);
282
+ const paddedMags = paddedFft.getMagnitude(paddedSpectrum);
283
+
284
+ console.log(`Original size: ${shortSignal.length}`);
285
+ console.log(`Padded size: ${padded.length}`);
286
+ console.log(`Spectrum bins: ${paddedMags.length}`);
287
+ console.log("✓ Zero-padding increases frequency resolution (interpolation)\n");
288
+
289
+ // ========== Summary ==========
290
+
291
+ console.log("========== Summary ==========");
292
+ console.log("✓ All 8 transforms demonstrated:");
293
+ console.log(" - FFT/IFFT (complex, fast)");
294
+ console.log(" - DFT/IDFT (complex, any size)");
295
+ console.log(" - RFFT/IRFFT (real, fast)");
296
+ console.log(" - RDFT/IRDFT (real, any size)");
297
+ console.log("\n✓ Key features:");
298
+ console.log(" - Perfect reconstruction");
299
+ console.log(" - Hermitian symmetry");
300
+ console.log(" - Energy conservation");
301
+ console.log(" - Moving/batched processing");
302
+ console.log(" - Windowing functions");
303
+ console.log(" - Spectral analysis utilities");
304
+ console.log("\n✓ Common use cases:");
305
+ console.log(" - Audio frequency analysis");
306
+ console.log(" - Signal filtering");
307
+ console.log(" - Feature extraction");
308
+ console.log(" - Compression");
309
+ console.log(" - Convolution (via FFT)");