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,566 @@
1
+ /**
2
+ * Advanced DSP Operations
3
+ *
4
+ * This module contains TypeScript implementations of advanced DSP operations:
5
+ * - Hjorth Parameters (Activity, Mobility, Complexity)
6
+ * - Spectral Features (Centroid, Rolloff, Flux)
7
+ * - Entropy Measures (Shannon, Sample Entropy, Approximate Entropy)
8
+ *
9
+ * Note: Resampling operations (decimate, interpolate, resample) are being
10
+ * implemented in C++ with polyphase FIR filtering for maximum efficiency.
11
+ * Expected release: within next few days.
12
+ */
13
+
14
+ import type {
15
+ HjorthParameters,
16
+ HjorthParams,
17
+ SpectralFeatures,
18
+ SpectralFeaturesParams,
19
+ EntropyParams,
20
+ SampleEntropyParams,
21
+ ApproximateEntropyParams,
22
+ } from "./types.js";
23
+
24
+ /**
25
+ * Calculate Hjorth parameters for signal complexity analysis
26
+ *
27
+ * Hjorth parameters describe three characteristics of a signal:
28
+ * - Activity: Variance (spread) of the signal
29
+ * - Mobility: How much the signal changes (based on first derivative)
30
+ * - Complexity: How much the signal's change changes (based on second derivative)
31
+ *
32
+ * @param signal - Input signal samples
33
+ * @returns Hjorth parameters object
34
+ *
35
+ * @example
36
+ * const signal = new Float32Array([1, 2, 3, 4, 5, 4, 3, 2, 1]);
37
+ * const hjorth = calculateHjorthParameters(signal);
38
+ * console.log(`Activity: ${hjorth.activity}`);
39
+ * console.log(`Mobility: ${hjorth.mobility}`);
40
+ * console.log(`Complexity: ${hjorth.complexity}`);
41
+ */
42
+ export function calculateHjorthParameters(
43
+ signal: Float32Array
44
+ ): HjorthParameters {
45
+ const n = signal.length;
46
+ if (n < 3) {
47
+ throw new Error(
48
+ "Signal must have at least 3 samples for Hjorth parameters"
49
+ );
50
+ }
51
+
52
+ // Calculate variance of signal (Activity)
53
+ let mean = 0;
54
+ for (let i = 0; i < n; i++) {
55
+ mean += signal[i];
56
+ }
57
+ mean /= n;
58
+
59
+ let variance = 0;
60
+ for (let i = 0; i < n; i++) {
61
+ const diff = signal[i] - mean;
62
+ variance += diff * diff;
63
+ }
64
+ variance /= n;
65
+
66
+ // Calculate first derivative
67
+ const firstDeriv = new Float32Array(n - 1);
68
+ for (let i = 0; i < n - 1; i++) {
69
+ firstDeriv[i] = signal[i + 1] - signal[i];
70
+ }
71
+
72
+ // Calculate variance of first derivative
73
+ let derivMean = 0;
74
+ for (let i = 0; i < firstDeriv.length; i++) {
75
+ derivMean += firstDeriv[i];
76
+ }
77
+ derivMean /= firstDeriv.length;
78
+
79
+ let derivVariance = 0;
80
+ for (let i = 0; i < firstDeriv.length; i++) {
81
+ const diff = firstDeriv[i] - derivMean;
82
+ derivVariance += diff * diff;
83
+ }
84
+ derivVariance /= firstDeriv.length;
85
+
86
+ // Calculate Mobility
87
+ const mobility = Math.sqrt(derivVariance / variance);
88
+
89
+ // Calculate second derivative
90
+ const secondDeriv = new Float32Array(n - 2);
91
+ for (let i = 0; i < n - 2; i++) {
92
+ secondDeriv[i] = firstDeriv[i + 1] - firstDeriv[i];
93
+ }
94
+
95
+ // Calculate variance of second derivative
96
+ let secondDerivMean = 0;
97
+ for (let i = 0; i < secondDeriv.length; i++) {
98
+ secondDerivMean += secondDeriv[i];
99
+ }
100
+ secondDerivMean /= secondDeriv.length;
101
+
102
+ let secondDerivVariance = 0;
103
+ for (let i = 0; i < secondDeriv.length; i++) {
104
+ const diff = secondDeriv[i] - secondDerivMean;
105
+ secondDerivVariance += diff * diff;
106
+ }
107
+ secondDerivVariance /= secondDeriv.length;
108
+
109
+ // Calculate Mobility of first derivative
110
+ const derivMobility = Math.sqrt(secondDerivVariance / derivVariance);
111
+
112
+ // Calculate Complexity
113
+ const complexity = derivMobility / mobility;
114
+
115
+ return {
116
+ activity: variance,
117
+ mobility,
118
+ complexity,
119
+ };
120
+ }
121
+
122
+ /**
123
+ * Calculate spectral centroid (center of mass of spectrum)
124
+ *
125
+ * @param magnitudeSpectrum - FFT magnitude spectrum
126
+ * @param sampleRate - Sample rate in Hz
127
+ * @returns Spectral centroid in Hz
128
+ */
129
+ export function calculateSpectralCentroid(
130
+ magnitudeSpectrum: Float32Array,
131
+ sampleRate: number
132
+ ): number {
133
+ const n = magnitudeSpectrum.length;
134
+ const freqResolution = sampleRate / (2 * n);
135
+
136
+ let weightedSum = 0;
137
+ let magnitudeSum = 0;
138
+
139
+ for (let i = 0; i < n; i++) {
140
+ const frequency = i * freqResolution;
141
+ const magnitude = magnitudeSpectrum[i];
142
+ weightedSum += frequency * magnitude;
143
+ magnitudeSum += magnitude;
144
+ }
145
+
146
+ return magnitudeSum > 0 ? weightedSum / magnitudeSum : 0;
147
+ }
148
+
149
+ /**
150
+ * Calculate spectral rolloff (frequency below which X% of energy is contained)
151
+ *
152
+ * @param magnitudeSpectrum - FFT magnitude spectrum
153
+ * @param sampleRate - Sample rate in Hz
154
+ * @param percentage - Rolloff percentage (0-100, default: 85)
155
+ * @returns Rolloff frequency in Hz
156
+ */
157
+ export function calculateSpectralRolloff(
158
+ magnitudeSpectrum: Float32Array,
159
+ sampleRate: number,
160
+ percentage: number = 85
161
+ ): number {
162
+ const n = magnitudeSpectrum.length;
163
+ const freqResolution = sampleRate / (2 * n);
164
+
165
+ // Calculate total energy
166
+ let totalEnergy = 0;
167
+ for (let i = 0; i < n; i++) {
168
+ totalEnergy += magnitudeSpectrum[i];
169
+ }
170
+
171
+ const threshold = (percentage / 100) * totalEnergy;
172
+ let cumulativeEnergy = 0;
173
+
174
+ for (let i = 0; i < n; i++) {
175
+ cumulativeEnergy += magnitudeSpectrum[i];
176
+ if (cumulativeEnergy >= threshold) {
177
+ return i * freqResolution;
178
+ }
179
+ }
180
+
181
+ return (n - 1) * freqResolution;
182
+ }
183
+
184
+ /**
185
+ * Calculate spectral flux (change in spectrum from previous frame)
186
+ *
187
+ * @param currentSpectrum - Current frame's magnitude spectrum
188
+ * @param previousSpectrum - Previous frame's magnitude spectrum (or null for first frame)
189
+ * @returns Spectral flux value
190
+ */
191
+ export function calculateSpectralFlux(
192
+ currentSpectrum: Float32Array,
193
+ previousSpectrum: Float32Array | null
194
+ ): number {
195
+ if (!previousSpectrum || previousSpectrum.length !== currentSpectrum.length) {
196
+ return 0; // First frame or size mismatch
197
+ }
198
+
199
+ let flux = 0;
200
+ for (let i = 0; i < currentSpectrum.length; i++) {
201
+ const diff = currentSpectrum[i] - previousSpectrum[i];
202
+ flux += diff * diff;
203
+ }
204
+
205
+ return Math.sqrt(flux);
206
+ }
207
+
208
+ /**
209
+ * Calculate all spectral features from magnitude spectrum
210
+ *
211
+ * @param magnitudeSpectrum - FFT magnitude spectrum
212
+ * @param sampleRate - Sample rate in Hz
213
+ * @param previousSpectrum - Previous frame's spectrum for flux calculation
214
+ * @param rolloffPercentage - Rolloff percentage (default: 85)
215
+ * @returns Object containing all spectral features
216
+ *
217
+ * @example
218
+ * const fft = performFFT(signal, 2048);
219
+ * const features = calculateSpectralFeatures(fft.magnitude, 44100, null, 85);
220
+ * console.log(`Centroid: ${features.centroid} Hz`);
221
+ * console.log(`Rolloff: ${features.rolloff} Hz`);
222
+ * console.log(`Flux: ${features.flux}`);
223
+ */
224
+ export function calculateSpectralFeatures(
225
+ magnitudeSpectrum: Float32Array,
226
+ sampleRate: number,
227
+ previousSpectrum: Float32Array | null = null,
228
+ rolloffPercentage: number = 85
229
+ ): SpectralFeatures {
230
+ return {
231
+ centroid: calculateSpectralCentroid(magnitudeSpectrum, sampleRate),
232
+ rolloff: calculateSpectralRolloff(
233
+ magnitudeSpectrum,
234
+ sampleRate,
235
+ rolloffPercentage
236
+ ),
237
+ flux: calculateSpectralFlux(magnitudeSpectrum, previousSpectrum),
238
+ };
239
+ }
240
+
241
+ /**
242
+ * Calculate Shannon entropy of a signal
243
+ * Measures the uncertainty/randomness in the signal's amplitude distribution
244
+ *
245
+ * @param signal - Input signal samples
246
+ * @param numBins - Number of histogram bins (default: 256)
247
+ * @returns Shannon entropy value
248
+ *
249
+ * @example
250
+ * const signal = new Float32Array(1000);
251
+ * // ... fill signal with data
252
+ * const entropy = calculateShannonEntropy(signal, 256);
253
+ * console.log(`Entropy: ${entropy} bits`);
254
+ */
255
+ export function calculateShannonEntropy(
256
+ signal: Float32Array,
257
+ numBins: number = 256
258
+ ): number {
259
+ const n = signal.length;
260
+ if (n === 0) return 0;
261
+
262
+ // Find min and max for binning
263
+ let min = signal[0];
264
+ let max = signal[0];
265
+ for (let i = 1; i < n; i++) {
266
+ if (signal[i] < min) min = signal[i];
267
+ if (signal[i] > max) max = signal[i];
268
+ }
269
+
270
+ if (min === max) return 0; // Constant signal has zero entropy
271
+
272
+ // Create histogram
273
+ const histogram = new Array(numBins).fill(0);
274
+ const range = max - min;
275
+ const binWidth = range / numBins;
276
+
277
+ for (let i = 0; i < n; i++) {
278
+ let binIndex = Math.floor((signal[i] - min) / binWidth);
279
+ if (binIndex >= numBins) binIndex = numBins - 1; // Handle edge case
280
+ histogram[binIndex]++;
281
+ }
282
+
283
+ // Calculate entropy
284
+ let entropy = 0;
285
+ for (let i = 0; i < numBins; i++) {
286
+ if (histogram[i] > 0) {
287
+ const probability = histogram[i] / n;
288
+ entropy -= probability * Math.log2(probability);
289
+ }
290
+ }
291
+
292
+ return entropy;
293
+ }
294
+
295
+ /**
296
+ * Calculate Sample Entropy (SampEn)
297
+ * Measures the likelihood that similar patterns remain similar on next increment
298
+ *
299
+ * @param signal - Input signal samples
300
+ * @param m - Pattern length (default: 2)
301
+ * @param r - Tolerance for matching (default: 0.2 * std deviation)
302
+ * @returns Sample entropy value
303
+ *
304
+ * @example
305
+ * const signal = new Float32Array(1000);
306
+ * // ... fill signal with data
307
+ * const sampEn = calculateSampleEntropy(signal, 2, 0.2);
308
+ * console.log(`Sample Entropy: ${sampEn}`);
309
+ */
310
+ export function calculateSampleEntropy(
311
+ signal: Float32Array,
312
+ m: number = 2,
313
+ r?: number
314
+ ): number {
315
+ const n = signal.length;
316
+ if (n < m + 1) {
317
+ throw new Error(`Signal too short for pattern length ${m}`);
318
+ }
319
+
320
+ // Calculate tolerance if not provided
321
+ if (r === undefined) {
322
+ // Calculate standard deviation
323
+ let mean = 0;
324
+ for (let i = 0; i < n; i++) {
325
+ mean += signal[i];
326
+ }
327
+ mean /= n;
328
+
329
+ let variance = 0;
330
+ for (let i = 0; i < n; i++) {
331
+ const diff = signal[i] - mean;
332
+ variance += diff * diff;
333
+ }
334
+ const stdDev = Math.sqrt(variance / n);
335
+ r = 0.2 * stdDev;
336
+ }
337
+
338
+ // Count matches for patterns of length m and m+1
339
+ let countM = 0;
340
+ let countMplus1 = 0;
341
+
342
+ for (let i = 0; i < n - m; i++) {
343
+ for (let j = i + 1; j < n - m; j++) {
344
+ // Check if patterns of length m match
345
+ let matchM = true;
346
+ for (let k = 0; k < m; k++) {
347
+ if (Math.abs(signal[i + k] - signal[j + k]) > r) {
348
+ matchM = false;
349
+ break;
350
+ }
351
+ }
352
+
353
+ if (matchM) {
354
+ countM++;
355
+
356
+ // Check if patterns of length m+1 also match
357
+ if (
358
+ i < n - m - 1 &&
359
+ j < n - m - 1 &&
360
+ Math.abs(signal[i + m] - signal[j + m]) <= r
361
+ ) {
362
+ countMplus1++;
363
+ }
364
+ }
365
+ }
366
+ }
367
+
368
+ // Calculate SampEn
369
+ if (countM === 0 || countMplus1 === 0) {
370
+ return Infinity; // Patterns don't repeat
371
+ }
372
+
373
+ return -Math.log(countMplus1 / countM);
374
+ }
375
+
376
+ /**
377
+ * Calculate Approximate Entropy (ApEn)
378
+ * Similar to SampEn but includes self-matches
379
+ *
380
+ * @param signal - Input signal samples
381
+ * @param m - Pattern length (default: 2)
382
+ * @param r - Tolerance for matching (default: 0.2 * std deviation)
383
+ * @returns Approximate entropy value
384
+ *
385
+ * @example
386
+ * const signal = new Float32Array(1000);
387
+ * // ... fill signal with data
388
+ * const apEn = calculateApproximateEntropy(signal, 2, 0.2);
389
+ * console.log(`Approximate Entropy: ${apEn}`);
390
+ */
391
+ export function calculateApproximateEntropy(
392
+ signal: Float32Array,
393
+ m: number = 2,
394
+ r?: number
395
+ ): number {
396
+ const n = signal.length;
397
+ if (n < m + 1) {
398
+ throw new Error(`Signal too short for pattern length ${m}`);
399
+ }
400
+
401
+ // Calculate tolerance if not provided
402
+ if (r === undefined) {
403
+ let mean = 0;
404
+ for (let i = 0; i < n; i++) {
405
+ mean += signal[i];
406
+ }
407
+ mean /= n;
408
+
409
+ let variance = 0;
410
+ for (let i = 0; i < n; i++) {
411
+ const diff = signal[i] - mean;
412
+ variance += diff * diff;
413
+ }
414
+ const stdDev = Math.sqrt(variance / n);
415
+ r = 0.2 * stdDev;
416
+ }
417
+
418
+ // Helper function to calculate phi
419
+ const phi = (patternLength: number): number => {
420
+ const counts = new Array(n - patternLength + 1).fill(0);
421
+
422
+ for (let i = 0; i <= n - patternLength; i++) {
423
+ for (let j = 0; j <= n - patternLength; j++) {
424
+ let match = true;
425
+ for (let k = 0; k < patternLength; k++) {
426
+ if (Math.abs(signal[i + k] - signal[j + k]) > r) {
427
+ match = false;
428
+ break;
429
+ }
430
+ }
431
+ if (match) {
432
+ counts[i]++;
433
+ }
434
+ }
435
+ }
436
+
437
+ let sum = 0;
438
+ for (let i = 0; i <= n - patternLength; i++) {
439
+ if (counts[i] > 0) {
440
+ sum += Math.log(counts[i] / (n - patternLength + 1));
441
+ }
442
+ }
443
+
444
+ return sum / (n - patternLength + 1);
445
+ };
446
+
447
+ return phi(m) - phi(m + 1);
448
+ }
449
+
450
+ /**
451
+ * Stateful class for tracking Hjorth parameters over a sliding window
452
+ */
453
+ export class HjorthTracker {
454
+ private buffer: Float32Array;
455
+ private writeIndex: number = 0;
456
+ private isFull: boolean = false;
457
+
458
+ constructor(private windowSize: number) {
459
+ this.buffer = new Float32Array(windowSize);
460
+ }
461
+
462
+ /**
463
+ * Add a new sample and get updated Hjorth parameters
464
+ */
465
+ update(sample: number): HjorthParameters | null {
466
+ this.buffer[this.writeIndex] = sample;
467
+ this.writeIndex = (this.writeIndex + 1) % this.windowSize;
468
+
469
+ if (this.writeIndex === 0) {
470
+ this.isFull = true;
471
+ }
472
+
473
+ if (!this.isFull) {
474
+ return null; // Not enough samples yet
475
+ }
476
+
477
+ // Create properly ordered view of circular buffer
478
+ const orderedBuffer = new Float32Array(this.windowSize);
479
+ for (let i = 0; i < this.windowSize; i++) {
480
+ orderedBuffer[i] = this.buffer[(this.writeIndex + i) % this.windowSize];
481
+ }
482
+
483
+ return calculateHjorthParameters(orderedBuffer);
484
+ }
485
+
486
+ reset(): void {
487
+ this.writeIndex = 0;
488
+ this.isFull = false;
489
+ this.buffer.fill(0);
490
+ }
491
+ }
492
+
493
+ /**
494
+ * Stateful class for tracking spectral features with previous frame memory
495
+ */
496
+ export class SpectralFeaturesTracker {
497
+ private previousSpectrum: Float32Array | null = null;
498
+
499
+ /**
500
+ * Calculate spectral features, maintaining state for flux calculation
501
+ */
502
+ calculate(
503
+ magnitudeSpectrum: Float32Array,
504
+ sampleRate: number,
505
+ rolloffPercentage: number = 85
506
+ ): SpectralFeatures {
507
+ const features = calculateSpectralFeatures(
508
+ magnitudeSpectrum,
509
+ sampleRate,
510
+ this.previousSpectrum,
511
+ rolloffPercentage
512
+ );
513
+
514
+ // Store current spectrum for next call
515
+ this.previousSpectrum = new Float32Array(magnitudeSpectrum);
516
+
517
+ return features;
518
+ }
519
+
520
+ reset(): void {
521
+ this.previousSpectrum = null;
522
+ }
523
+ }
524
+
525
+ /**
526
+ * Stateful class for tracking entropy over a sliding window
527
+ */
528
+ export class EntropyTracker {
529
+ private buffer: Float32Array;
530
+ private writeIndex: number = 0;
531
+ private isFull: boolean = false;
532
+
533
+ constructor(private windowSize: number, private numBins: number = 256) {
534
+ this.buffer = new Float32Array(windowSize);
535
+ }
536
+
537
+ /**
538
+ * Add a new sample and get updated Shannon entropy
539
+ */
540
+ update(sample: number): number | null {
541
+ this.buffer[this.writeIndex] = sample;
542
+ this.writeIndex = (this.writeIndex + 1) % this.windowSize;
543
+
544
+ if (this.writeIndex === 0) {
545
+ this.isFull = true;
546
+ }
547
+
548
+ if (!this.isFull) {
549
+ return null; // Not enough samples yet
550
+ }
551
+
552
+ // Create properly ordered view of circular buffer
553
+ const orderedBuffer = new Float32Array(this.windowSize);
554
+ for (let i = 0; i < this.windowSize; i++) {
555
+ orderedBuffer[i] = this.buffer[(this.writeIndex + i) % this.windowSize];
556
+ }
557
+
558
+ return calculateShannonEntropy(orderedBuffer, this.numBins);
559
+ }
560
+
561
+ reset(): void {
562
+ this.writeIndex = 0;
563
+ this.isFull = false;
564
+ this.buffer.fill(0);
565
+ }
566
+ }