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,576 @@
1
+ /**
2
+ * IIR Filter Implementation
3
+ */
4
+
5
+ #define _USE_MATH_DEFINES
6
+ #include "IirFilter.h"
7
+ #include <cmath>
8
+ #include <stdexcept>
9
+ #include <algorithm>
10
+ #include <complex>
11
+
12
+ #ifndef M_PI
13
+ #define M_PI 3.14159265358979323846
14
+ #endif
15
+
16
+ namespace dsp
17
+ {
18
+ namespace core
19
+ {
20
+
21
+ template <typename T>
22
+ IirFilter<T>::IirFilter(const std::vector<T> &b_coeffs, const std::vector<T> &a_coeffs, bool stateful)
23
+ : m_b_coeffs(b_coeffs), m_a_coeffs(a_coeffs), m_stateful(stateful)
24
+ {
25
+ if (b_coeffs.empty())
26
+ {
27
+ throw std::invalid_argument("IIR filter requires at least one feedforward coefficient");
28
+ }
29
+
30
+ if (stateful)
31
+ {
32
+ // Allocate state buffers
33
+ m_x_state.resize(b_coeffs.size(), T(0));
34
+ m_y_state.resize(a_coeffs.size(), T(0));
35
+ }
36
+ }
37
+
38
+ template <typename T>
39
+ T IirFilter<T>::processSample(T input)
40
+ {
41
+ if (!m_stateful)
42
+ {
43
+ throw std::runtime_error("processSample() requires stateful mode");
44
+ }
45
+
46
+ // Direct Form II implementation
47
+ // Compute feedforward (numerator)
48
+ T output = m_b_coeffs[0] * input;
49
+
50
+ for (size_t i = 1; i < m_b_coeffs.size(); ++i)
51
+ {
52
+ if (i - 1 < m_x_state.size())
53
+ {
54
+ output += m_b_coeffs[i] * m_x_state[i - 1];
55
+ }
56
+ }
57
+
58
+ // Compute feedback (denominator)
59
+ for (size_t i = 0; i < m_a_coeffs.size(); ++i)
60
+ {
61
+ if (i < m_y_state.size())
62
+ {
63
+ output -= m_a_coeffs[i] * m_y_state[i];
64
+ }
65
+ }
66
+
67
+ // Update state buffers (shift history)
68
+ // Input history: x[n-1] <- x[n-2] <- ... <- x[n-M] <- input
69
+ for (size_t i = m_x_state.size() - 1; i > 0; --i)
70
+ {
71
+ m_x_state[i] = m_x_state[i - 1];
72
+ }
73
+ if (!m_x_state.empty())
74
+ {
75
+ m_x_state[0] = input;
76
+ }
77
+
78
+ // Output history: y[n-1] <- y[n-2] <- ... <- y[n-N] <- output
79
+ for (size_t i = m_y_state.size() - 1; i > 0; --i)
80
+ {
81
+ m_y_state[i] = m_y_state[i - 1];
82
+ }
83
+ if (!m_y_state.empty())
84
+ {
85
+ m_y_state[0] = output;
86
+ }
87
+
88
+ return output;
89
+ }
90
+
91
+ template <typename T>
92
+ void IirFilter<T>::process(const T *input, T *output, size_t length, bool stateless)
93
+ {
94
+ if (stateless || !m_stateful)
95
+ {
96
+ // Stateless mode: use temporary state for batch
97
+ std::vector<T> x_temp(m_b_coeffs.size(), T(0));
98
+ std::vector<T> y_temp(m_a_coeffs.size(), T(0));
99
+
100
+ for (size_t n = 0; n < length; ++n)
101
+ {
102
+ // Feedforward
103
+ T y = m_b_coeffs[0] * input[n];
104
+ for (size_t i = 1; i < m_b_coeffs.size(); ++i)
105
+ {
106
+ if (i - 1 < x_temp.size())
107
+ {
108
+ y += m_b_coeffs[i] * x_temp[i - 1];
109
+ }
110
+ }
111
+
112
+ // Feedback
113
+ for (size_t i = 0; i < m_a_coeffs.size(); ++i)
114
+ {
115
+ if (i < y_temp.size())
116
+ {
117
+ y -= m_a_coeffs[i] * y_temp[i];
118
+ }
119
+ }
120
+
121
+ output[n] = y;
122
+
123
+ // Update temporary state
124
+ for (size_t i = x_temp.size() - 1; i > 0; --i)
125
+ {
126
+ x_temp[i] = x_temp[i - 1];
127
+ }
128
+ if (!x_temp.empty())
129
+ {
130
+ x_temp[0] = input[n];
131
+ }
132
+
133
+ for (size_t i = y_temp.size() - 1; i > 0; --i)
134
+ {
135
+ y_temp[i] = y_temp[i - 1];
136
+ }
137
+ if (!y_temp.empty())
138
+ {
139
+ y_temp[0] = y;
140
+ }
141
+ }
142
+ }
143
+ else
144
+ {
145
+ // Stateful mode: use processSample
146
+ for (size_t i = 0; i < length; ++i)
147
+ {
148
+ output[i] = processSample(input[i]);
149
+ }
150
+ }
151
+ }
152
+
153
+ template <typename T>
154
+ void IirFilter<T>::reset()
155
+ {
156
+ if (m_stateful)
157
+ {
158
+ std::fill(m_x_state.begin(), m_x_state.end(), T(0));
159
+ std::fill(m_y_state.begin(), m_y_state.end(), T(0));
160
+ }
161
+ }
162
+
163
+ template <typename T>
164
+ void IirFilter<T>::setCoefficients(const std::vector<T> &b_coeffs, const std::vector<T> &a_coeffs)
165
+ {
166
+ if (b_coeffs.empty())
167
+ {
168
+ throw std::invalid_argument("B coefficients cannot be empty");
169
+ }
170
+
171
+ m_b_coeffs = b_coeffs;
172
+ m_a_coeffs = a_coeffs;
173
+
174
+ if (m_stateful)
175
+ {
176
+ m_x_state.resize(b_coeffs.size(), T(0));
177
+ m_y_state.resize(a_coeffs.size(), T(0));
178
+ }
179
+ }
180
+
181
+ template <typename T>
182
+ bool IirFilter<T>::isStable() const
183
+ {
184
+ // Basic stability check: sum of absolute feedback coefficients < 1
185
+ // This is a necessary but not sufficient condition
186
+ T sum = T(0);
187
+ for (const auto &a : m_a_coeffs)
188
+ {
189
+ sum += std::abs(a);
190
+ }
191
+ return sum < T(1);
192
+ }
193
+
194
+ // ========== Filter Design Methods ==========
195
+
196
+ template <typename T>
197
+ IirFilter<T> IirFilter<T>::createFirstOrderLowPass(T cutoffFreq)
198
+ {
199
+ if (cutoffFreq <= 0 || cutoffFreq >= T(0.5))
200
+ {
201
+ throw std::invalid_argument("Cutoff frequency must be between 0 and 0.5");
202
+ }
203
+
204
+ // First-order low-pass: H(z) = (b0 + b1*z^-1) / (1 + a1*z^-1)
205
+ // Using bilinear transform from analog RC filter
206
+ T omega_c = T(2) * static_cast<T>(M_PI) * cutoffFreq;
207
+ T K = std::tan(omega_c / T(2));
208
+
209
+ T b0 = K / (T(1) + K);
210
+ T b1 = K / (T(1) + K);
211
+ T a1 = (K - T(1)) / (T(1) + K);
212
+
213
+ return IirFilter<T>({b0, b1}, {a1}, true);
214
+ }
215
+
216
+ template <typename T>
217
+ IirFilter<T> IirFilter<T>::createFirstOrderHighPass(T cutoffFreq)
218
+ {
219
+ if (cutoffFreq <= 0 || cutoffFreq >= T(0.5))
220
+ {
221
+ throw std::invalid_argument("Cutoff frequency must be between 0 and 0.5");
222
+ }
223
+
224
+ // First-order high-pass
225
+ T omega_c = T(2) * static_cast<T>(M_PI) * cutoffFreq;
226
+ T K = std::tan(omega_c / T(2));
227
+
228
+ T b0 = T(1) / (T(1) + K);
229
+ T b1 = -T(1) / (T(1) + K);
230
+ T a1 = (K - T(1)) / (T(1) + K);
231
+
232
+ return IirFilter<T>({b0, b1}, {a1}, true);
233
+ }
234
+
235
+ template <typename T>
236
+ IirFilter<T> IirFilter<T>::createBiquad(T b0, T b1, T b2, T a1, T a2)
237
+ {
238
+ return IirFilter<T>({b0, b1, b2}, {a1, a2}, true);
239
+ }
240
+
241
+ template <typename T>
242
+ IirFilter<T> IirFilter<T>::createButterworthLowPass(T cutoffFreq, int order)
243
+ {
244
+ if (cutoffFreq <= 0 || cutoffFreq >= T(0.5))
245
+ {
246
+ throw std::invalid_argument("Cutoff frequency must be between 0 and 0.5");
247
+ }
248
+
249
+ if (order < 1 || order > 8)
250
+ {
251
+ throw std::invalid_argument("Order must be between 1 and 8");
252
+ }
253
+
254
+ // For simplicity, implement 2nd-order Butterworth (biquad)
255
+ if (order == 1)
256
+ {
257
+ return createFirstOrderLowPass(cutoffFreq);
258
+ }
259
+
260
+ // 2nd-order Butterworth low-pass
261
+ T omega_c = T(2) * static_cast<T>(M_PI) * cutoffFreq;
262
+ T K = std::tan(omega_c / T(2));
263
+ T K2 = K * K;
264
+ T sqrt2 = static_cast<T>(std::sqrt(2.0));
265
+
266
+ T norm = T(1) / (T(1) + sqrt2 * K + K2);
267
+
268
+ T b0 = K2 * norm;
269
+ T b1 = T(2) * b0;
270
+ T b2 = b0;
271
+
272
+ T a1 = T(2) * (K2 - T(1)) * norm;
273
+ T a2 = (T(1) - sqrt2 * K + K2) * norm;
274
+
275
+ return IirFilter<T>({b0, b1, b2}, {a1, a2}, true);
276
+ }
277
+
278
+ template <typename T>
279
+ IirFilter<T> IirFilter<T>::createButterworthHighPass(T cutoffFreq, int order)
280
+ {
281
+ if (cutoffFreq <= 0 || cutoffFreq >= T(0.5))
282
+ {
283
+ throw std::invalid_argument("Cutoff frequency must be between 0 and 0.5");
284
+ }
285
+
286
+ if (order < 1 || order > 8)
287
+ {
288
+ throw std::invalid_argument("Order must be between 1 and 8");
289
+ }
290
+
291
+ if (order == 1)
292
+ {
293
+ return createFirstOrderHighPass(cutoffFreq);
294
+ }
295
+
296
+ // 2nd-order Butterworth high-pass
297
+ T omega_c = T(2) * static_cast<T>(M_PI) * cutoffFreq;
298
+ T K = std::tan(omega_c / T(2));
299
+ T K2 = K * K;
300
+ T sqrt2 = static_cast<T>(std::sqrt(2.0));
301
+
302
+ T norm = T(1) / (T(1) + sqrt2 * K + K2);
303
+
304
+ T b0 = norm;
305
+ T b1 = -T(2) * norm;
306
+ T b2 = norm;
307
+
308
+ T a1 = T(2) * (K2 - T(1)) * norm;
309
+ T a2 = (T(1) - sqrt2 * K + K2) * norm;
310
+
311
+ return IirFilter<T>({b0, b1, b2}, {a1, a2}, true);
312
+ }
313
+
314
+ template <typename T>
315
+ IirFilter<T> IirFilter<T>::createButterworthBandPass(T lowCutoff, T highCutoff, int order)
316
+ {
317
+ if (lowCutoff >= highCutoff)
318
+ {
319
+ throw std::invalid_argument("Low cutoff must be less than high cutoff");
320
+ }
321
+
322
+ // Simplified: cascade low-pass and high-pass
323
+ // (For true band-pass, would need proper transformation)
324
+ auto hp = createButterworthHighPass(lowCutoff, order);
325
+ auto lp = createButterworthLowPass(highCutoff, order);
326
+
327
+ // Convolve coefficients (simplified - in practice need proper cascade)
328
+ auto b_hp = hp.getBCoefficients();
329
+ auto a_hp = hp.getACoefficients();
330
+ auto b_lp = lp.getBCoefficients();
331
+ auto a_lp = lp.getACoefficients();
332
+
333
+ // For now, return the high-pass filter as placeholder
334
+ // Full implementation would cascade the filters properly
335
+ return hp;
336
+ }
337
+
338
+ template <typename T>
339
+ IirFilter<T> IirFilter<T>::createChebyshevLowPass(T cutoffFreq, int order, T rippleDb)
340
+ {
341
+ if (cutoffFreq <= 0 || cutoffFreq >= T(0.5))
342
+ {
343
+ throw std::invalid_argument("Cutoff frequency must be between 0 and 0.5");
344
+ }
345
+
346
+ if (order < 1 || order > 8)
347
+ {
348
+ throw std::invalid_argument("Order must be between 1 and 8");
349
+ }
350
+
351
+ if (rippleDb <= 0 || rippleDb > T(3.0))
352
+ {
353
+ throw std::invalid_argument("Ripple must be between 0 and 3 dB");
354
+ }
355
+
356
+ // For simplicity, implement 2nd-order Chebyshev Type I
357
+ if (order == 1)
358
+ {
359
+ // First-order Chebyshev is same as Butterworth
360
+ return createFirstOrderLowPass(cutoffFreq);
361
+ }
362
+
363
+ // 2nd-order Chebyshev Type I low-pass
364
+ T omega_c = T(2) * static_cast<T>(M_PI) * cutoffFreq;
365
+ T epsilon = std::sqrt(std::pow(T(10), rippleDb / T(10)) - T(1));
366
+
367
+ // Chebyshev pole calculation
368
+ T sinh_val = std::sinh(std::asinh(T(1) / epsilon) / T(2));
369
+ T cosh_val = std::cosh(std::asinh(T(1) / epsilon) / T(2));
370
+
371
+ T K = std::tan(omega_c / T(2));
372
+ T K2 = K * K;
373
+
374
+ // Pole positions for 2nd-order Chebyshev
375
+ T wp = T(2) * sinh_val; // Pole width
376
+ T rp = cosh_val; // Pole radius
377
+
378
+ T norm = T(1) / (T(1) + wp * K + rp * K2);
379
+
380
+ T b0 = rp * K2 * norm;
381
+ T b1 = T(2) * b0;
382
+ T b2 = b0;
383
+
384
+ T a1 = T(2) * (rp * K2 - T(1)) * norm;
385
+ T a2 = (T(1) - wp * K + rp * K2) * norm;
386
+
387
+ return IirFilter<T>({b0, b1, b2}, {a1, a2}, true);
388
+ }
389
+
390
+ template <typename T>
391
+ IirFilter<T> IirFilter<T>::createChebyshevHighPass(T cutoffFreq, int order, T rippleDb)
392
+ {
393
+ if (cutoffFreq <= 0 || cutoffFreq >= T(0.5))
394
+ {
395
+ throw std::invalid_argument("Cutoff frequency must be between 0 and 0.5");
396
+ }
397
+
398
+ if (order < 1 || order > 8)
399
+ {
400
+ throw std::invalid_argument("Order must be between 1 and 8");
401
+ }
402
+
403
+ if (rippleDb <= 0 || rippleDb > T(3.0))
404
+ {
405
+ throw std::invalid_argument("Ripple must be between 0 and 3 dB");
406
+ }
407
+
408
+ if (order == 1)
409
+ {
410
+ return createFirstOrderHighPass(cutoffFreq);
411
+ }
412
+
413
+ // 2nd-order Chebyshev Type I high-pass
414
+ T omega_c = T(2) * static_cast<T>(M_PI) * cutoffFreq;
415
+ T epsilon = std::sqrt(std::pow(T(10), rippleDb / T(10)) - T(1));
416
+
417
+ T sinh_val = std::sinh(std::asinh(T(1) / epsilon) / T(2));
418
+ T cosh_val = std::cosh(std::asinh(T(1) / epsilon) / T(2));
419
+
420
+ T K = std::tan(omega_c / T(2));
421
+ T K2 = K * K;
422
+
423
+ T wp = T(2) * sinh_val;
424
+ T rp = cosh_val;
425
+
426
+ T norm = T(1) / (T(1) + wp * K + rp * K2);
427
+
428
+ T b0 = norm;
429
+ T b1 = T(-2) * norm;
430
+ T b2 = norm;
431
+
432
+ T a1 = T(2) * (rp * K2 - T(1)) * norm;
433
+ T a2 = (T(1) - wp * K + rp * K2) * norm;
434
+
435
+ return IirFilter<T>({b0, b1, b2}, {a1, a2}, true);
436
+ }
437
+
438
+ template <typename T>
439
+ IirFilter<T> IirFilter<T>::createChebyshevBandPass(T lowCutoff, T highCutoff, int order, T rippleDb)
440
+ {
441
+ if (lowCutoff <= 0 || highCutoff >= T(0.5) || lowCutoff >= highCutoff)
442
+ {
443
+ throw std::invalid_argument("Invalid cutoff frequencies");
444
+ }
445
+
446
+ if (order < 1 || order > 8)
447
+ {
448
+ throw std::invalid_argument("Order must be between 1 and 8");
449
+ }
450
+
451
+ // Cascade high-pass and low-pass Chebyshev filters
452
+ auto hp = createChebyshevHighPass(lowCutoff, order, rippleDb);
453
+ auto lp = createChebyshevLowPass(highCutoff, order, rippleDb);
454
+
455
+ // Return high-pass as placeholder (proper implementation would cascade)
456
+ return hp;
457
+ }
458
+
459
+ template <typename T>
460
+ IirFilter<T> IirFilter<T>::createPeakingEQ(T centerFreq, T Q, T gainDb)
461
+ {
462
+ if (centerFreq <= 0 || centerFreq >= T(0.5))
463
+ {
464
+ throw std::invalid_argument("Center frequency must be between 0 and 0.5");
465
+ }
466
+
467
+ if (Q <= 0)
468
+ {
469
+ throw std::invalid_argument("Q must be positive");
470
+ }
471
+
472
+ // Peaking EQ biquad filter (Robert Bristow-Johnson's Audio EQ Cookbook)
473
+ T omega = T(2) * static_cast<T>(M_PI) * centerFreq;
474
+ T A = std::pow(T(10), gainDb / T(40)); // Linear gain
475
+ T alpha = std::sin(omega) / (T(2) * Q);
476
+ T cos_omega = std::cos(omega);
477
+
478
+ T b0 = T(1) + alpha * A;
479
+ T b1 = T(-2) * cos_omega;
480
+ T b2 = T(1) - alpha * A;
481
+ T a0 = T(1) + alpha / A;
482
+ T a1 = T(-2) * cos_omega;
483
+ T a2 = T(1) - alpha / A;
484
+
485
+ // Normalize by a0
486
+ b0 /= a0;
487
+ b1 /= a0;
488
+ b2 /= a0;
489
+ a1 /= a0;
490
+ a2 /= a0;
491
+
492
+ return IirFilter<T>({b0, b1, b2}, {a1, a2}, true);
493
+ }
494
+
495
+ template <typename T>
496
+ IirFilter<T> IirFilter<T>::createLowShelf(T cutoffFreq, T gainDb, T Q)
497
+ {
498
+ if (cutoffFreq <= 0 || cutoffFreq >= T(0.5))
499
+ {
500
+ throw std::invalid_argument("Cutoff frequency must be between 0 and 0.5");
501
+ }
502
+
503
+ if (Q <= 0)
504
+ {
505
+ throw std::invalid_argument("Q must be positive");
506
+ }
507
+
508
+ // Low-shelf biquad filter (Audio EQ Cookbook)
509
+ T omega = T(2) * static_cast<T>(M_PI) * cutoffFreq;
510
+ T A = std::pow(T(10), gainDb / T(40));
511
+ T cos_omega = std::cos(omega);
512
+ T sin_omega = std::sin(omega);
513
+ T alpha = sin_omega / (T(2) * Q);
514
+ T beta = std::sqrt(A) / Q;
515
+
516
+ T b0 = A * ((A + T(1)) - (A - T(1)) * cos_omega + beta * sin_omega);
517
+ T b1 = T(2) * A * ((A - T(1)) - (A + T(1)) * cos_omega);
518
+ T b2 = A * ((A + T(1)) - (A - T(1)) * cos_omega - beta * sin_omega);
519
+ T a0 = (A + T(1)) + (A - T(1)) * cos_omega + beta * sin_omega;
520
+ T a1 = T(-2) * ((A - T(1)) + (A + T(1)) * cos_omega);
521
+ T a2 = (A + T(1)) + (A - T(1)) * cos_omega - beta * sin_omega;
522
+
523
+ // Normalize by a0
524
+ b0 /= a0;
525
+ b1 /= a0;
526
+ b2 /= a0;
527
+ a1 /= a0;
528
+ a2 /= a0;
529
+
530
+ return IirFilter<T>({b0, b1, b2}, {a1, a2}, true);
531
+ }
532
+
533
+ template <typename T>
534
+ IirFilter<T> IirFilter<T>::createHighShelf(T cutoffFreq, T gainDb, T Q)
535
+ {
536
+ if (cutoffFreq <= 0 || cutoffFreq >= T(0.5))
537
+ {
538
+ throw std::invalid_argument("Cutoff frequency must be between 0 and 0.5");
539
+ }
540
+
541
+ if (Q <= 0)
542
+ {
543
+ throw std::invalid_argument("Q must be positive");
544
+ }
545
+
546
+ // High-shelf biquad filter (Audio EQ Cookbook)
547
+ T omega = T(2) * static_cast<T>(M_PI) * cutoffFreq;
548
+ T A = std::pow(T(10), gainDb / T(40));
549
+ T cos_omega = std::cos(omega);
550
+ T sin_omega = std::sin(omega);
551
+ T alpha = sin_omega / (T(2) * Q);
552
+ T beta = std::sqrt(A) / Q;
553
+
554
+ T b0 = A * ((A + T(1)) + (A - T(1)) * cos_omega + beta * sin_omega);
555
+ T b1 = T(-2) * A * ((A - T(1)) + (A + T(1)) * cos_omega);
556
+ T b2 = A * ((A + T(1)) + (A - T(1)) * cos_omega - beta * sin_omega);
557
+ T a0 = (A + T(1)) - (A - T(1)) * cos_omega + beta * sin_omega;
558
+ T a1 = T(2) * ((A - T(1)) - (A + T(1)) * cos_omega);
559
+ T a2 = (A + T(1)) - (A - T(1)) * cos_omega - beta * sin_omega;
560
+
561
+ // Normalize by a0
562
+ b0 /= a0;
563
+ b1 /= a0;
564
+ b2 /= a0;
565
+ a1 /= a0;
566
+ a2 /= a0;
567
+
568
+ return IirFilter<T>({b0, b1, b2}, {a1, a2}, true);
569
+ }
570
+
571
+ // Explicit template instantiations
572
+ template class IirFilter<float>;
573
+ template class IirFilter<double>;
574
+
575
+ } // namespace core
576
+ } // namespace dsp