dspx 0.1.1-alpha.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/ci.yml +185 -0
- package/.vscode/c_cpp_properties.json +17 -0
- package/.vscode/settings.json +68 -0
- package/.vscode/tasks.json +28 -0
- package/DISCLAIMER.md +32 -0
- package/LICENSE +21 -0
- package/README.md +1803 -0
- package/ROADMAP.md +192 -0
- package/TECHNICAL_DEBT.md +165 -0
- package/binding.gyp +65 -0
- package/docs/ADVANCED_LOGGER_FEATURES.md +598 -0
- package/docs/AUTHENTICATION_SECURITY.md +396 -0
- package/docs/BACKEND_IMPROVEMENTS.md +399 -0
- package/docs/CHEBYSHEV_BIQUAD_EQ_IMPLEMENTATION.md +405 -0
- package/docs/FFT_IMPLEMENTATION.md +490 -0
- package/docs/FFT_IMPROVEMENTS_SUMMARY.md +387 -0
- package/docs/FFT_USER_GUIDE.md +494 -0
- package/docs/FILTERS_IMPLEMENTATION.md +260 -0
- package/docs/FILTER_API_GUIDE.md +418 -0
- package/docs/FIR_SIMD_OPTIMIZATION.md +175 -0
- package/docs/LOGGER_API_REFERENCE.md +350 -0
- package/docs/NOTCH_FILTER_QUICK_REF.md +121 -0
- package/docs/PHASE2_TESTS_AND_NOTCH_FILTER.md +341 -0
- package/docs/PHASES_5_7_SUMMARY.md +403 -0
- package/docs/PIPELINE_FILTER_INTEGRATION.md +446 -0
- package/docs/SIMD_OPTIMIZATIONS.md +211 -0
- package/docs/TEST_MIGRATION_SUMMARY.md +173 -0
- package/docs/TIMESERIES_IMPLEMENTATION_SUMMARY.md +322 -0
- package/docs/TIMESERIES_QUICK_REF.md +85 -0
- package/docs/advanced.md +559 -0
- package/docs/time-series-guide.md +617 -0
- package/docs/time-series-migration.md +376 -0
- package/jest.config.js +37 -0
- package/package.json +42 -0
- package/prebuilds/linux-x64/dsp-ts-redis.node +0 -0
- package/prebuilds/win32-x64/dsp-ts-redis.node +0 -0
- package/scripts/test.js +24 -0
- package/src/build/dsp-ts-redis.node +0 -0
- package/src/native/DspPipeline.cc +675 -0
- package/src/native/DspPipeline.h +44 -0
- package/src/native/FftBindings.cc +817 -0
- package/src/native/FilterBindings.cc +1001 -0
- package/src/native/IDspStage.h +53 -0
- package/src/native/adapters/InterpolatorStage.h +201 -0
- package/src/native/adapters/MeanAbsoluteValueStage.h +289 -0
- package/src/native/adapters/MovingAverageStage.h +306 -0
- package/src/native/adapters/RectifyStage.h +88 -0
- package/src/native/adapters/ResamplerStage.h +238 -0
- package/src/native/adapters/RmsStage.h +299 -0
- package/src/native/adapters/SscStage.h +121 -0
- package/src/native/adapters/VarianceStage.h +307 -0
- package/src/native/adapters/WampStage.h +114 -0
- package/src/native/adapters/WaveformLengthStage.h +115 -0
- package/src/native/adapters/ZScoreNormalizeStage.h +326 -0
- package/src/native/core/FftEngine.cc +441 -0
- package/src/native/core/FftEngine.h +224 -0
- package/src/native/core/FirFilter.cc +324 -0
- package/src/native/core/FirFilter.h +149 -0
- package/src/native/core/IirFilter.cc +576 -0
- package/src/native/core/IirFilter.h +210 -0
- package/src/native/core/MovingAbsoluteValueFilter.cc +17 -0
- package/src/native/core/MovingAbsoluteValueFilter.h +135 -0
- package/src/native/core/MovingAverageFilter.cc +18 -0
- package/src/native/core/MovingAverageFilter.h +135 -0
- package/src/native/core/MovingFftFilter.cc +291 -0
- package/src/native/core/MovingFftFilter.h +203 -0
- package/src/native/core/MovingVarianceFilter.cc +194 -0
- package/src/native/core/MovingVarianceFilter.h +114 -0
- package/src/native/core/MovingZScoreFilter.cc +215 -0
- package/src/native/core/MovingZScoreFilter.h +113 -0
- package/src/native/core/Policies.h +352 -0
- package/src/native/core/RmsFilter.cc +18 -0
- package/src/native/core/RmsFilter.h +131 -0
- package/src/native/core/SscFilter.cc +16 -0
- package/src/native/core/SscFilter.h +137 -0
- package/src/native/core/WampFilter.cc +16 -0
- package/src/native/core/WampFilter.h +101 -0
- package/src/native/core/WaveformLengthFilter.cc +17 -0
- package/src/native/core/WaveformLengthFilter.h +98 -0
- package/src/native/utils/CircularBufferArray.cc +336 -0
- package/src/native/utils/CircularBufferArray.h +62 -0
- package/src/native/utils/CircularBufferVector.cc +145 -0
- package/src/native/utils/CircularBufferVector.h +45 -0
- package/src/native/utils/NapiUtils.cc +53 -0
- package/src/native/utils/NapiUtils.h +21 -0
- package/src/native/utils/SimdOps.h +870 -0
- package/src/native/utils/SlidingWindowFilter.cc +239 -0
- package/src/native/utils/SlidingWindowFilter.h +159 -0
- package/src/native/utils/TimeSeriesBuffer.cc +205 -0
- package/src/native/utils/TimeSeriesBuffer.h +140 -0
- package/src/ts/CircularLogBuffer.ts +87 -0
- package/src/ts/DriftDetector.ts +331 -0
- package/src/ts/TopicRouter.ts +428 -0
- package/src/ts/__tests__/AdvancedDsp.test.ts +585 -0
- package/src/ts/__tests__/AuthAndEdgeCases.test.ts +241 -0
- package/src/ts/__tests__/Chaining.test.ts +387 -0
- package/src/ts/__tests__/ChebyshevBiquad.test.ts +229 -0
- package/src/ts/__tests__/CircularLogBuffer.test.ts +158 -0
- package/src/ts/__tests__/DriftDetector.test.ts +389 -0
- package/src/ts/__tests__/Fft.test.ts +484 -0
- package/src/ts/__tests__/ListState.test.ts +153 -0
- package/src/ts/__tests__/Logger.test.ts +208 -0
- package/src/ts/__tests__/LoggerAdvanced.test.ts +319 -0
- package/src/ts/__tests__/LoggerMinor.test.ts +247 -0
- package/src/ts/__tests__/MeanAbsoluteValue.test.ts +398 -0
- package/src/ts/__tests__/MovingAverage.test.ts +322 -0
- package/src/ts/__tests__/RMS.test.ts +315 -0
- package/src/ts/__tests__/Rectify.test.ts +272 -0
- package/src/ts/__tests__/Redis.test.ts +456 -0
- package/src/ts/__tests__/SlopeSignChange.test.ts +166 -0
- package/src/ts/__tests__/Tap.test.ts +164 -0
- package/src/ts/__tests__/TimeBasedExpiration.test.ts +124 -0
- package/src/ts/__tests__/TimeBasedRmsAndMav.test.ts +231 -0
- package/src/ts/__tests__/TimeBasedVarianceAndZScore.test.ts +284 -0
- package/src/ts/__tests__/TimeSeries.test.ts +254 -0
- package/src/ts/__tests__/TopicRouter.test.ts +332 -0
- package/src/ts/__tests__/TopicRouterAdvanced.test.ts +483 -0
- package/src/ts/__tests__/TopicRouterPriority.test.ts +487 -0
- package/src/ts/__tests__/Variance.test.ts +509 -0
- package/src/ts/__tests__/WaveformLength.test.ts +147 -0
- package/src/ts/__tests__/WillisonAmplitude.test.ts +197 -0
- package/src/ts/__tests__/ZScoreNormalize.test.ts +459 -0
- package/src/ts/advanced-dsp.ts +566 -0
- package/src/ts/backends.ts +1137 -0
- package/src/ts/bindings.ts +1225 -0
- package/src/ts/easter-egg.ts +42 -0
- package/src/ts/examples/MeanAbsoluteValue/test-state.ts +99 -0
- package/src/ts/examples/MeanAbsoluteValue/test-streaming.ts +269 -0
- package/src/ts/examples/MovingAverage/test-state.ts +85 -0
- package/src/ts/examples/MovingAverage/test-streaming.ts +188 -0
- package/src/ts/examples/RMS/test-state.ts +97 -0
- package/src/ts/examples/RMS/test-streaming.ts +253 -0
- package/src/ts/examples/Rectify/test-state.ts +107 -0
- package/src/ts/examples/Rectify/test-streaming.ts +242 -0
- package/src/ts/examples/Variance/test-state.ts +195 -0
- package/src/ts/examples/Variance/test-streaming.ts +260 -0
- package/src/ts/examples/ZScoreNormalize/test-state.ts +277 -0
- package/src/ts/examples/ZScoreNormalize/test-streaming.ts +306 -0
- package/src/ts/examples/advanced-dsp-examples.ts +397 -0
- package/src/ts/examples/callbacks/advanced-router-features.ts +326 -0
- package/src/ts/examples/callbacks/benchmark-circular-buffer.ts +109 -0
- package/src/ts/examples/callbacks/monitoring-example.ts +265 -0
- package/src/ts/examples/callbacks/pipeline-callbacks-example.ts +137 -0
- package/src/ts/examples/callbacks/pooled-callbacks-example.ts +274 -0
- package/src/ts/examples/callbacks/priority-routing-example.ts +277 -0
- package/src/ts/examples/callbacks/production-topic-router.ts +214 -0
- package/src/ts/examples/callbacks/topic-based-logging.ts +161 -0
- package/src/ts/examples/chaining/test-chaining-redis.ts +113 -0
- package/src/ts/examples/chaining/test-chaining.ts +52 -0
- package/src/ts/examples/emg-features-example.ts +284 -0
- package/src/ts/examples/fft-example.ts +309 -0
- package/src/ts/examples/fft-examples.ts +349 -0
- package/src/ts/examples/filter-examples.ts +320 -0
- package/src/ts/examples/list-state-example.ts +131 -0
- package/src/ts/examples/logger-example.ts +91 -0
- package/src/ts/examples/notch-filter-examples.ts +243 -0
- package/src/ts/examples/phase5/drift-detection-example.ts +290 -0
- package/src/ts/examples/phase6-7/production-observability.ts +476 -0
- package/src/ts/examples/phase6-7/redis-timeseries-integration.ts +446 -0
- package/src/ts/examples/redis/redis-example.ts +202 -0
- package/src/ts/examples/redis-example.ts +202 -0
- package/src/ts/examples/simd-benchmark.ts +126 -0
- package/src/ts/examples/tap-debugging.ts +230 -0
- package/src/ts/examples/timeseries/comparison-example.ts +290 -0
- package/src/ts/examples/timeseries/iot-sensor-example.ts +143 -0
- package/src/ts/examples/timeseries/redis-streaming-example.ts +233 -0
- package/src/ts/examples/waveform-length-example.ts +139 -0
- package/src/ts/fft.ts +722 -0
- package/src/ts/filters.ts +1078 -0
- package/src/ts/index.ts +120 -0
- package/src/ts/types.ts +589 -0
- package/tsconfig.json +15 -0
|
@@ -0,0 +1,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
|