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,349 @@
1
+ /**
2
+ * FFT Examples - Demonstrating Radix-2 Handling and Windowing
3
+ *
4
+ * This example demonstrates:
5
+ * 1. Power-of-2 requirement and auto-padding
6
+ * 2. FFT vs DFT performance comparison
7
+ * 3. Windowing for spectral leakage reduction
8
+ * 4. Practical audio analysis scenarios
9
+ */
10
+
11
+ import {
12
+ FftProcessor,
13
+ MovingFftProcessor,
14
+ FftUtils,
15
+ type ComplexArray,
16
+ } from "../fft.js";
17
+
18
+ console.log("=== FFT Examples: Radix-2 and Windowing ===\n");
19
+
20
+ // ============================================================
21
+ // Example 1: Power-of-2 Requirement
22
+ // ============================================================
23
+
24
+ console.log("--- Example 1: Power-of-2 Requirement ---");
25
+
26
+ // Generate a signal with non-power-of-2 length
27
+ const signalLength = 1000; // Not a power of 2!
28
+ const rawSignal = new Float32Array(signalLength);
29
+ for (let i = 0; i < signalLength; i++) {
30
+ rawSignal[i] = Math.sin((2 * Math.PI * 10 * i) / 1000); // 10 Hz sine wave
31
+ }
32
+
33
+ console.log(`Original signal length: ${signalLength}`);
34
+ console.log(`Is power of 2: ${FftUtils.isPowerOfTwo(signalLength)}`);
35
+
36
+ // Try to use FFT directly (this will fail)
37
+ try {
38
+ const badFft = new FftProcessor(signalLength);
39
+ badFft.rfft(rawSignal);
40
+ console.log("❌ This should have thrown an error!");
41
+ } catch (error) {
42
+ console.log(`✅ Expected error: ${(error as Error).message}`);
43
+ }
44
+
45
+ // Solution 1: Auto-padding (recommended)
46
+ console.log("\n✅ Solution 1: Auto-padding");
47
+ const padded = FftUtils.padToPowerOfTwo(rawSignal);
48
+ console.log(`Padded length: ${padded.length}`);
49
+ console.log(`Is power of 2: ${FftUtils.isPowerOfTwo(padded.length)}`);
50
+
51
+ const fftWithPadding = new FftProcessor(padded.length);
52
+ const spectrum1 = fftWithPadding.rfft(padded);
53
+ console.log(`Spectrum size: ${spectrum1.real.length} bins`);
54
+
55
+ // Solution 2: Use DFT (slower but exact)
56
+ console.log("\n✅ Solution 2: Use DFT");
57
+ const dftProcessor = new FftProcessor(signalLength);
58
+ const spectrum2 = dftProcessor.rdft(rawSignal);
59
+ console.log(`Spectrum size: ${spectrum2.real.length} bins`);
60
+
61
+ // ============================================================
62
+ // Example 2: FFT vs DFT Performance Comparison
63
+ // ============================================================
64
+
65
+ console.log("\n--- Example 2: FFT vs DFT Performance ---");
66
+
67
+ const sizes = [256, 1024, 4096];
68
+
69
+ for (const size of sizes) {
70
+ const testSignal = new Float32Array(size);
71
+ for (let i = 0; i < size; i++) {
72
+ testSignal[i] = Math.random();
73
+ }
74
+
75
+ // FFT timing
76
+ const fft = new FftProcessor(size);
77
+ const fftStart = performance.now();
78
+ for (let i = 0; i < 100; i++) {
79
+ fft.rfft(testSignal);
80
+ }
81
+ const fftTime = (performance.now() - fftStart) / 100;
82
+
83
+ // DFT timing
84
+ const dft = new FftProcessor(size);
85
+ const dftStart = performance.now();
86
+ for (let i = 0; i < 10; i++) {
87
+ // Only 10 iterations for DFT (it's slow!)
88
+ dft.rdft(testSignal);
89
+ }
90
+ const dftTime = (performance.now() - dftStart) / 10;
91
+
92
+ const speedup = dftTime / fftTime;
93
+ console.log(
94
+ `Size ${size}: FFT=${fftTime.toFixed(3)}ms, DFT=${dftTime.toFixed(
95
+ 3
96
+ )}ms, Speedup=${speedup.toFixed(1)}x`
97
+ );
98
+ }
99
+
100
+ // ============================================================
101
+ // Example 3: Windowing for Spectral Leakage Reduction
102
+ // ============================================================
103
+
104
+ console.log("\n--- Example 3: Windowing Effects ---");
105
+
106
+ const fftSize = 1024;
107
+ const sampleRate = 8000; // 8 kHz
108
+
109
+ // Generate a pure tone (100 Hz) with some noise
110
+ const pureSignal = new Float32Array(fftSize);
111
+ const targetFreq = 100; // Hz
112
+ for (let i = 0; i < fftSize; i++) {
113
+ pureSignal[i] =
114
+ Math.sin((2 * Math.PI * targetFreq * i) / sampleRate) + 0.1 * Math.random(); // Add 10% noise
115
+ }
116
+
117
+ console.log(
118
+ `\nAnalyzing ${targetFreq} Hz tone at ${sampleRate} Hz sample rate`
119
+ );
120
+
121
+ // Test different window types
122
+ const windowTypes = ["none", "hann", "hamming", "blackman"] as const;
123
+
124
+ for (const windowType of windowTypes) {
125
+ const fft = new MovingFftProcessor({
126
+ fftSize,
127
+ windowType,
128
+ realInput: true,
129
+ });
130
+
131
+ // Add all samples at once
132
+ fft.addSamples(pureSignal, () => {});
133
+
134
+ // Force computation
135
+ fft.computeSpectrum();
136
+ const magnitudes = fft.getMagnitudeSpectrum();
137
+ const freqs = fft.getFrequencyBins(sampleRate);
138
+
139
+ // Find peak
140
+ let maxIdx = 0;
141
+ let maxMag = 0;
142
+ for (let i = 0; i < magnitudes.length; i++) {
143
+ if (magnitudes[i] > maxMag) {
144
+ maxMag = magnitudes[i];
145
+ maxIdx = i;
146
+ }
147
+ }
148
+
149
+ const peakFreq = freqs[maxIdx];
150
+
151
+ // Calculate total power in neighboring bins (leakage indicator)
152
+ let leakagePower = 0;
153
+ const peakBin = Math.round((targetFreq * fftSize) / sampleRate);
154
+ for (let i = peakBin - 5; i <= peakBin + 5; i++) {
155
+ if (i >= 0 && i < magnitudes.length && i !== peakBin) {
156
+ leakagePower += magnitudes[i] * magnitudes[i];
157
+ }
158
+ }
159
+
160
+ console.log(
161
+ `Window: ${windowType.padEnd(8)} | Peak: ${peakFreq.toFixed(
162
+ 2
163
+ )} Hz | Leakage: ${leakagePower.toFixed(6)}`
164
+ );
165
+ }
166
+
167
+ // ============================================================
168
+ // Example 4: Practical Audio Spectral Analysis
169
+ // ============================================================
170
+
171
+ console.log("\n--- Example 4: Audio Spectral Analysis ---");
172
+
173
+ // Simulate audio with multiple frequencies
174
+ const audioLength = 4096;
175
+ const audioSampleRate = 44100; // 44.1 kHz
176
+ const audioSignal = new Float32Array(audioLength);
177
+
178
+ // Mix: 440 Hz (A4) + 880 Hz (A5) + 1320 Hz (E6)
179
+ const frequencies = [440, 880, 1320];
180
+ for (let i = 0; i < audioLength; i++) {
181
+ audioSignal[i] = 0;
182
+ for (const freq of frequencies) {
183
+ audioSignal[i] +=
184
+ Math.sin((2 * Math.PI * freq * i) / audioSampleRate) / frequencies.length;
185
+ }
186
+ }
187
+
188
+ // Analyze with Hann windowing (standard for audio)
189
+ const audioFft = new MovingFftProcessor({
190
+ fftSize: audioLength,
191
+ windowType: "hann", // ✅ Recommended for audio
192
+ realInput: true,
193
+ });
194
+
195
+ audioFft.addSamples(audioSignal, () => {});
196
+ audioFft.computeSpectrum();
197
+
198
+ const audioMagnitudes = audioFft.getMagnitudeSpectrum();
199
+ const audioFreqs = audioFft.getFrequencyBins(audioSampleRate);
200
+ const audioDb = FftUtils.toDecibels(audioMagnitudes);
201
+
202
+ // Find top 3 peaks
203
+ interface Peak {
204
+ frequency: number;
205
+ magnitude: number;
206
+ db: number;
207
+ }
208
+
209
+ const peaks: Peak[] = [];
210
+ for (let i = 1; i < audioMagnitudes.length - 1; i++) {
211
+ // Local maxima
212
+ if (
213
+ audioMagnitudes[i] > audioMagnitudes[i - 1] &&
214
+ audioMagnitudes[i] > audioMagnitudes[i + 1]
215
+ ) {
216
+ if (audioMagnitudes[i] > 0.01) {
217
+ // Threshold
218
+ peaks.push({
219
+ frequency: audioFreqs[i],
220
+ magnitude: audioMagnitudes[i],
221
+ db: audioDb[i],
222
+ });
223
+ }
224
+ }
225
+ }
226
+
227
+ peaks.sort((a, b) => b.magnitude - a.magnitude);
228
+
229
+ console.log("\nDetected frequencies:");
230
+ for (let i = 0; i < Math.min(3, peaks.length); i++) {
231
+ console.log(
232
+ `${i + 1}. ${peaks[i].frequency.toFixed(2)} Hz (${peaks[i].db.toFixed(
233
+ 2
234
+ )} dB, mag=${peaks[i].magnitude.toFixed(4)})`
235
+ );
236
+ }
237
+
238
+ // ============================================================
239
+ // Example 5: Streaming Spectrogram
240
+ // ============================================================
241
+
242
+ console.log("\n--- Example 5: Streaming Spectrogram ---");
243
+
244
+ const spectrogramFft = new MovingFftProcessor({
245
+ fftSize: 512,
246
+ hopSize: 128, // 75% overlap
247
+ mode: "batched",
248
+ windowType: "hann",
249
+ realInput: true,
250
+ });
251
+
252
+ // Simulate streaming audio (2 seconds at 8 kHz)
253
+ const streamDuration = 2.0; // seconds
254
+ const streamSampleRate = 8000;
255
+ const totalSamples = streamDuration * streamSampleRate;
256
+ const chunkSize = 512;
257
+
258
+ console.log(
259
+ `\nStreaming ${streamDuration}s of audio at ${streamSampleRate} Hz`
260
+ );
261
+ console.log(`Chunk size: ${chunkSize}, FFT size: 512, Hop: 128 (75% overlap)`);
262
+
263
+ let frameCount = 0;
264
+ for (let offset = 0; offset < totalSamples; offset += chunkSize) {
265
+ const chunk = new Float32Array(chunkSize);
266
+
267
+ // Generate chunk: sweep from 100 Hz to 2000 Hz over 2 seconds
268
+ for (let i = 0; i < chunkSize; i++) {
269
+ const t = (offset + i) / streamSampleRate;
270
+ const freq = 100 + (1900 * t) / streamDuration; // Linear sweep
271
+ chunk[i] = Math.sin(2 * Math.PI * freq * t);
272
+ }
273
+
274
+ // Process chunk
275
+ const numFrames = spectrogramFft.addSamples(
276
+ chunk,
277
+ (_spectrum: ComplexArray, _size: number) => {
278
+ frameCount++;
279
+ }
280
+ );
281
+
282
+ if (numFrames > 0) {
283
+ const mags = spectrogramFft.getMagnitudeSpectrum();
284
+ const freqs = spectrogramFft.getFrequencyBins(streamSampleRate);
285
+
286
+ // Find peak in current frame
287
+ let maxIdx = 0;
288
+ let maxMag = 0;
289
+ for (let i = 0; i < mags.length; i++) {
290
+ if (mags[i] > maxMag) {
291
+ maxMag = mags[i];
292
+ maxIdx = i;
293
+ }
294
+ }
295
+
296
+ const peakFreq = freqs[maxIdx];
297
+
298
+ // Only print every 10th frame
299
+ if (frameCount % 10 === 0) {
300
+ console.log(`Frame ${frameCount}: Peak at ${peakFreq.toFixed(2)} Hz`);
301
+ }
302
+ }
303
+ }
304
+
305
+ console.log(`Total spectrogram frames: ${frameCount}`);
306
+
307
+ // ============================================================
308
+ // Example 6: Zero-Padding Effects
309
+ // ============================================================
310
+
311
+ console.log("\n--- Example 6: Zero-Padding Effects ---");
312
+
313
+ // Short signal
314
+ const shortSignal = new Float32Array(100);
315
+ for (let i = 0; i < 100; i++) {
316
+ shortSignal[i] = Math.sin((2 * Math.PI * 5 * i) / 100);
317
+ }
318
+
319
+ console.log("\nOriginal signal: 100 samples");
320
+ const paddedSizes = [128, 256, 512, 1024];
321
+
322
+ for (const padSize of paddedSizes) {
323
+ const padded = FftUtils.zeroPad(shortSignal, padSize);
324
+ const fft = new FftProcessor(padded.length);
325
+ const spectrum = fft.rfft(padded);
326
+
327
+ console.log(
328
+ `Padded to ${padSize}: ${spectrum.real.length} frequency bins (spectral interpolation)`
329
+ );
330
+ }
331
+
332
+ console.log(
333
+ "\n⚠️ Note: More bins ≠ better resolution! Resolution is determined by original signal length."
334
+ );
335
+
336
+ // ============================================================
337
+ // Summary
338
+ // ============================================================
339
+
340
+ console.log("\n=== Summary ===");
341
+ console.log("✅ Use FftUtils.padToPowerOfTwo() for non-power-of-2 signals");
342
+ console.log("✅ FFT is 100-1000x faster than DFT for large signals");
343
+ console.log(
344
+ "✅ Always use windowing (hann, hamming, blackman) to reduce spectral leakage"
345
+ );
346
+ console.log("✅ Hann window is recommended for general audio analysis");
347
+ console.log("✅ Use 50-75% overlap for smooth spectrograms");
348
+ console.log("✅ Zero-padding increases bins but NOT spectral resolution");
349
+ console.log("\n📚 See docs/FFT_USER_GUIDE.md for detailed explanations");
@@ -0,0 +1,320 @@
1
+ /**
2
+ * Filter Design Examples
3
+ *
4
+ * Demonstrates the filter API:
5
+ * - FIR filters (low-pass, high-pass, band-pass, band-stop)
6
+ * - IIR filters (Butterworth, Chebyshev, Biquad EQ)
7
+ * - Direct class methods for creating filters
8
+ */
9
+
10
+ import { FirFilter, IirFilter } from "../filters.js";
11
+
12
+ console.log("=== Filter Design Examples ===\n");
13
+
14
+ const sampleRate = 8000; // 8 kHz
15
+
16
+ // ============================================================
17
+ // Example 1: FIR Low-Pass Filter
18
+ // ============================================================
19
+
20
+ console.log("--- Example 1: FIR Low-Pass Filter ---");
21
+
22
+ // Using class static method
23
+ const firLowpass1 = FirFilter.createLowPass({
24
+ cutoffFrequency: 1000,
25
+ sampleRate,
26
+ order: 51,
27
+ windowType: "hamming",
28
+ });
29
+
30
+ console.log(`✅ FIR Low-pass (1000 Hz, order 51)`);
31
+ console.log(` Coefficients: ${firLowpass1.getCoefficients().length} taps`);
32
+
33
+ // Method 2: Using class static method (direct)
34
+ const firLowpass2 = FirFilter.createLowPass({
35
+ cutoffFrequency: 1000,
36
+ sampleRate,
37
+ order: 51,
38
+ windowType: "hamming",
39
+ });
40
+
41
+ console.log(`✅ Same filter using FirFilter.createLowPass()`);
42
+
43
+ // Test with sample data
44
+ const testSignal = new Float32Array(100);
45
+ for (let i = 0; i < testSignal.length; i++) {
46
+ // Mix 500 Hz (pass) + 2000 Hz (reject)
47
+ testSignal[i] =
48
+ Math.sin((2 * Math.PI * 500 * i) / sampleRate) +
49
+ Math.sin((2 * Math.PI * 2000 * i) / sampleRate);
50
+ }
51
+
52
+ const filtered = await firLowpass1.process(testSignal);
53
+ console.log(
54
+ ` Filtered ${testSignal.length} samples (2 kHz component attenuated)\n`
55
+ );
56
+
57
+ // ============================================================
58
+ // Example 2: FIR High-Pass Filter
59
+ // ============================================================
60
+
61
+ console.log("--- Example 2: FIR High-Pass Filter ---");
62
+
63
+ const firHighpass = FirFilter.createHighPass({
64
+ cutoffFrequency: 2000,
65
+ sampleRate,
66
+ order: 61,
67
+ windowType: "hann",
68
+ });
69
+
70
+ console.log(`✅ FIR High-pass (2000 Hz, order 61, Hann window)`);
71
+ console.log(` Coefficients: ${firHighpass.getCoefficients().length} taps\n`);
72
+
73
+ // ============================================================
74
+ // Example 3: FIR Band-Pass Filter (Voice Band)
75
+ // ============================================================
76
+
77
+ console.log("--- Example 3: FIR Band-Pass Filter (Voice Band) ---");
78
+
79
+ const voiceBandpass = FirFilter.createBandPass({
80
+ lowCutoffFrequency: 300,
81
+ highCutoffFrequency: 3400,
82
+ sampleRate,
83
+ order: 101,
84
+ windowType: "blackman",
85
+ });
86
+
87
+ console.log(`✅ Voice band-pass (300-3400 Hz, order 101, Blackman window)`);
88
+ console.log(` Coefficients: ${voiceBandpass.getCoefficients().length} taps`);
89
+ console.log(` Use case: Telephone/voice communication\n`);
90
+
91
+ // ============================================================
92
+ // Example 4: FIR Band-Stop (Notch) Filter
93
+ // ============================================================
94
+
95
+ console.log("--- Example 4: FIR Band-Stop (Notch) Filter ---");
96
+
97
+ // Remove 50 Hz powerline hum
98
+ const notch50Hz = FirFilter.createBandStop({
99
+ lowCutoffFrequency: 48,
100
+ highCutoffFrequency: 52,
101
+ sampleRate,
102
+ order: 201,
103
+ windowType: "hamming",
104
+ });
105
+
106
+ console.log(`✅ 50 Hz notch filter (48-52 Hz, order 201)`);
107
+ console.log(` Coefficients: ${notch50Hz.getCoefficients().length} taps`);
108
+ console.log(` Use case: Remove powerline interference\n`);
109
+
110
+ // ============================================================
111
+ // Example 5: Butterworth Low-Pass Filter
112
+ // ============================================================
113
+
114
+ console.log("--- Example 5: Butterworth Low-Pass Filter ---");
115
+
116
+ const butterworthLowpass = IirFilter.createButterworthLowPass({
117
+ cutoffFrequency: 1000,
118
+ sampleRate,
119
+ order: 4,
120
+ });
121
+
122
+ console.log(`✅ Butterworth low-pass (1000 Hz, 4th-order)`);
123
+ console.log(
124
+ ` B coefficients: ${butterworthLowpass.getBCoefficients().length}`
125
+ );
126
+ console.log(
127
+ ` A coefficients: ${butterworthLowpass.getACoefficients().length}`
128
+ );
129
+ console.log(` Stable: ${butterworthLowpass.isStable()}`);
130
+ console.log(` Characteristics: Maximally flat passband\n`);
131
+
132
+ // ============================================================
133
+ // Example 6: Butterworth High-Pass Filter
134
+ // ============================================================
135
+
136
+ console.log("--- Example 6: Butterworth High-Pass Filter ---");
137
+
138
+ const butterworthHighpass = IirFilter.createButterworthHighPass({
139
+ cutoffFrequency: 500,
140
+ sampleRate,
141
+ order: 2,
142
+ });
143
+
144
+ console.log(`✅ Butterworth high-pass (500 Hz, 2nd-order)`);
145
+ console.log(` Use case: Remove DC offset and low-frequency drift\n`);
146
+
147
+ // ============================================================
148
+ // Example 7: Butterworth Band-Pass Filter
149
+ // ============================================================
150
+
151
+ console.log("--- Example 7: Butterworth Band-Pass Filter ---");
152
+
153
+ const butterworthBandpass = IirFilter.createButterworthBandPass({
154
+ lowCutoffFrequency: 1000,
155
+ highCutoffFrequency: 2000,
156
+ sampleRate,
157
+ order: 3,
158
+ });
159
+
160
+ console.log(`✅ Butterworth band-pass (1000-2000 Hz, 3rd-order)`);
161
+ console.log(
162
+ ` Total order: ${butterworthBandpass.getOrder()} (2x3 = 6th-order)`
163
+ );
164
+ console.log(` Use case: Extract specific frequency band\n`);
165
+
166
+ // ============================================================
167
+ // Example 8: First-Order IIR Filters
168
+ // ============================================================
169
+
170
+ console.log("--- Example 8: First-Order IIR Filters ---");
171
+
172
+ const simpleLP = IirFilter.createFirstOrderLowPass({
173
+ cutoffFrequency: 1000,
174
+ sampleRate,
175
+ });
176
+
177
+ const simpleHP = IirFilter.createFirstOrderHighPass({
178
+ cutoffFrequency: 100,
179
+ sampleRate,
180
+ });
181
+
182
+ console.log(`✅ First-order low-pass (1000 Hz)`);
183
+ console.log(` Simple RC filter, gentle rolloff (-20 dB/decade)`);
184
+ console.log(`✅ First-order high-pass (100 Hz)`);
185
+ console.log(` Use case: Fast, low-latency filtering\n`);
186
+
187
+ // ============================================================
188
+ // Example 9: Comparing FIR vs IIR Performance
189
+ // ============================================================
190
+
191
+ console.log("--- Example 9: FIR vs IIR Performance Comparison ---");
192
+
193
+ const fir = FirFilter.createLowPass({
194
+ cutoffFrequency: 1000,
195
+ sampleRate,
196
+ order: 51,
197
+ });
198
+
199
+ const iir = IirFilter.createButterworthLowPass({
200
+ cutoffFrequency: 1000,
201
+ sampleRate,
202
+ order: 4,
203
+ });
204
+
205
+ console.log("FIR (51 taps):");
206
+ console.log(
207
+ ` - Coefficients: ${
208
+ fir.getCoefficients().length
209
+ } (multiply-accumulates per sample)`
210
+ );
211
+ console.log(` - Always stable`);
212
+ console.log(` - Linear phase possible`);
213
+
214
+ console.log("\nIIR (4th-order Butterworth):");
215
+ console.log(
216
+ ` - B coefficients: ${iir.getBCoefficients().length}, A coefficients: ${
217
+ iir.getACoefficients().length
218
+ }`
219
+ );
220
+ console.log(` - Much more efficient (fewer operations)`);
221
+ console.log(` - Non-linear phase`);
222
+ console.log(` - Stable: ${iir.isStable()}\n`);
223
+
224
+ // ============================================================
225
+ // Example 10: Real-Time Filtering
226
+ // ============================================================
227
+
228
+ console.log("--- Example 10: Real-Time Filtering ---");
229
+
230
+ const realtimeFilter = IirFilter.createButterworthLowPass({
231
+ cutoffFrequency: 1000,
232
+ sampleRate,
233
+ order: 4,
234
+ });
235
+
236
+ console.log("Simulating real-time sample-by-sample processing:");
237
+
238
+ // Simulate streaming samples
239
+ for (let i = 0; i < 10; i++) {
240
+ const input = Math.sin((2 * Math.PI * 500 * i) / sampleRate);
241
+ const output = await realtimeFilter.processSample(input);
242
+
243
+ if (i < 5) {
244
+ console.log(
245
+ ` Sample ${i}: in=${input.toFixed(4)}, out=${output.toFixed(4)}`
246
+ );
247
+ }
248
+ }
249
+ console.log(" ...");
250
+ console.log(`✅ Processed 10 samples sample-by-sample\n`);
251
+
252
+ // ============================================================
253
+ // Example 11: Filter State Management
254
+ // ============================================================
255
+
256
+ console.log("--- Example 11: Filter State Management ---");
257
+
258
+ const statefulFilter = FirFilter.createLowPass({
259
+ cutoffFrequency: 1000,
260
+ sampleRate,
261
+ order: 31,
262
+ });
263
+
264
+ // Process first batch
265
+ const batch1 = new Float32Array(50);
266
+ for (let i = 0; i < batch1.length; i++) {
267
+ batch1[i] = Math.sin((2 * Math.PI * 500 * i) / sampleRate);
268
+ }
269
+
270
+ const out1 = await statefulFilter.process(batch1);
271
+ console.log(`✅ Processed batch 1: ${batch1.length} samples`);
272
+
273
+ // Process second batch (state is maintained)
274
+ const batch2 = new Float32Array(50);
275
+ for (let i = 0; i < batch2.length; i++) {
276
+ batch2[i] = Math.sin((2 * Math.PI * 500 * (i + 50)) / sampleRate);
277
+ }
278
+
279
+ const out2 = await statefulFilter.process(batch2);
280
+ console.log(
281
+ `✅ Processed batch 2: ${batch2.length} samples (state maintained)`
282
+ );
283
+
284
+ // Reset state
285
+ statefulFilter.reset();
286
+ console.log(`✅ Reset filter state\n`);
287
+
288
+ // ============================================================
289
+ // Summary
290
+ // ============================================================
291
+
292
+ console.log("=== Summary ===");
293
+ console.log("\n✅ FIR Filters:");
294
+ console.log(" - Low-pass, High-pass, Band-pass, Band-stop/Notch");
295
+ console.log(" - Window types: Hamming, Hann, Blackman, Bartlett");
296
+ console.log(" - Always stable, linear phase possible");
297
+ console.log(" - More taps = sharper transition, more computation");
298
+
299
+ console.log("\n✅ IIR Filters:");
300
+ console.log(" - Butterworth: Maximally flat passband");
301
+ console.log(" - Chebyshev: Sharper rolloff with passband ripple");
302
+ console.log(" - First-order: Simple, fast, gentle rolloff");
303
+ console.log(" - More efficient than FIR (fewer coefficients)");
304
+ console.log(" - Non-linear phase, can be unstable");
305
+
306
+ console.log("\n✅ Filter API:");
307
+ console.log(" - FirFilter.createLowPass/HighPass/BandPass/BandStop()");
308
+ console.log(" - IirFilter.createButterworthX() - Maximally flat");
309
+ console.log(" - IirFilter.createChebyshevX() - Sharp rolloff");
310
+ console.log(" - IirFilter.createPeakingEQ/LowShelf/HighShelf() - Biquad EQ");
311
+
312
+ console.log("\n✅ Use Cases:");
313
+ console.log(" - Audio: FIR with Hann/Hamming window");
314
+ console.log(" - Real-time: IIR Butterworth (low latency)");
315
+ console.log(" - Voice band: 300-3400 Hz band-pass");
316
+ console.log(" - Powerline removal: 50/60 Hz notch filter");
317
+ console.log(" - DC removal: High-pass filter");
318
+
319
+ console.log("\n📚 All filter design math is done in C++ for performance!");
320
+ console.log("🚀 SIMD-optimized convolution for FIR filters!");