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,324 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* FIR Filter Implementation with SIMD-Optimized Convolution
|
|
3
|
+
* Now uses SlidingWindowFilter infrastructure for consistency
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
#define _USE_MATH_DEFINES
|
|
7
|
+
#include "FirFilter.h"
|
|
8
|
+
#include "../utils/SimdOps.h"
|
|
9
|
+
#include <cmath>
|
|
10
|
+
#include <stdexcept>
|
|
11
|
+
#include <algorithm>
|
|
12
|
+
|
|
13
|
+
#ifndef M_PI
|
|
14
|
+
#define M_PI 3.14159265358979323846
|
|
15
|
+
#endif
|
|
16
|
+
|
|
17
|
+
namespace dsp
|
|
18
|
+
{
|
|
19
|
+
namespace core
|
|
20
|
+
{
|
|
21
|
+
|
|
22
|
+
template <typename T>
|
|
23
|
+
FirFilter<T>::FirFilter(const std::vector<T> &coefficients, bool stateful)
|
|
24
|
+
: m_coefficients(coefficients), m_stateIndex(0), m_stateful(stateful)
|
|
25
|
+
{
|
|
26
|
+
if (coefficients.empty())
|
|
27
|
+
{
|
|
28
|
+
throw std::invalid_argument("FIR filter requires at least one coefficient");
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if (stateful)
|
|
32
|
+
{
|
|
33
|
+
// Allocate state buffer (need M previous samples)
|
|
34
|
+
m_state.resize(coefficients.size(), T(0));
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
template <typename T>
|
|
39
|
+
T FirFilter<T>::processSample(T input)
|
|
40
|
+
{
|
|
41
|
+
if (!m_stateful)
|
|
42
|
+
{
|
|
43
|
+
throw std::runtime_error("processSample() requires stateful mode");
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Store input in circular buffer
|
|
47
|
+
m_state[m_stateIndex] = input;
|
|
48
|
+
|
|
49
|
+
// Compute output via SIMD-optimized convolution
|
|
50
|
+
T output = T(0);
|
|
51
|
+
|
|
52
|
+
if constexpr (std::is_same_v<T, float>)
|
|
53
|
+
{
|
|
54
|
+
// Build aligned buffer for SIMD (coefficients are in reverse order for convolution)
|
|
55
|
+
std::vector<float> aligned_samples(m_coefficients.size());
|
|
56
|
+
|
|
57
|
+
// Copy samples in correct order for dot product
|
|
58
|
+
for (size_t i = 0; i < m_coefficients.size(); ++i)
|
|
59
|
+
{
|
|
60
|
+
size_t stateIdx = (m_stateIndex + m_state.size() - i) % m_state.size();
|
|
61
|
+
aligned_samples[i] = m_state[stateIdx];
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// SIMD dot product
|
|
65
|
+
output = simd::dot_product(aligned_samples.data(), m_coefficients.data(),
|
|
66
|
+
m_coefficients.size());
|
|
67
|
+
}
|
|
68
|
+
else
|
|
69
|
+
{
|
|
70
|
+
// Scalar convolution for double
|
|
71
|
+
for (size_t i = 0; i < m_coefficients.size(); ++i)
|
|
72
|
+
{
|
|
73
|
+
size_t stateIdx = (m_stateIndex + m_state.size() - i) % m_state.size();
|
|
74
|
+
output += m_coefficients[i] * m_state[stateIdx];
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Advance circular buffer index
|
|
79
|
+
m_stateIndex = (m_stateIndex + 1) % m_state.size();
|
|
80
|
+
|
|
81
|
+
return output;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
template <typename T>
|
|
85
|
+
void FirFilter<T>::process(const T *input, T *output, size_t length, bool stateless)
|
|
86
|
+
{
|
|
87
|
+
if (stateless || !m_stateful)
|
|
88
|
+
{
|
|
89
|
+
// Stateless mode: each output depends only on current window
|
|
90
|
+
for (size_t n = 0; n < length; ++n)
|
|
91
|
+
{
|
|
92
|
+
T sum = T(0);
|
|
93
|
+
|
|
94
|
+
if constexpr (std::is_same_v<T, float>)
|
|
95
|
+
{
|
|
96
|
+
// Use SIMD for stateless convolution too
|
|
97
|
+
size_t available = std::min(m_coefficients.size(), n + 1);
|
|
98
|
+
|
|
99
|
+
if (available == m_coefficients.size())
|
|
100
|
+
{
|
|
101
|
+
// Full window available - direct SIMD dot product
|
|
102
|
+
sum = simd::dot_product(&input[n - available + 1],
|
|
103
|
+
m_coefficients.data(),
|
|
104
|
+
available);
|
|
105
|
+
}
|
|
106
|
+
else
|
|
107
|
+
{
|
|
108
|
+
// Partial window - scalar for simplicity
|
|
109
|
+
for (size_t i = 0; i < available; ++i)
|
|
110
|
+
{
|
|
111
|
+
sum += m_coefficients[i] * input[n - i];
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
else
|
|
116
|
+
{
|
|
117
|
+
// Scalar for double
|
|
118
|
+
for (size_t i = 0; i < m_coefficients.size() && i <= n; ++i)
|
|
119
|
+
{
|
|
120
|
+
sum += m_coefficients[i] * input[n - i];
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
output[n] = sum;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
else
|
|
128
|
+
{
|
|
129
|
+
// Stateful mode: use processSample for each input
|
|
130
|
+
for (size_t i = 0; i < length; ++i)
|
|
131
|
+
{
|
|
132
|
+
output[i] = processSample(input[i]);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
template <typename T>
|
|
138
|
+
void FirFilter<T>::reset()
|
|
139
|
+
{
|
|
140
|
+
if (m_stateful)
|
|
141
|
+
{
|
|
142
|
+
std::fill(m_state.begin(), m_state.end(), T(0));
|
|
143
|
+
m_stateIndex = 0;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
template <typename T>
|
|
148
|
+
void FirFilter<T>::setCoefficients(const std::vector<T> &coefficients)
|
|
149
|
+
{
|
|
150
|
+
if (coefficients.empty())
|
|
151
|
+
{
|
|
152
|
+
throw std::invalid_argument("Coefficients cannot be empty");
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
m_coefficients = coefficients;
|
|
156
|
+
|
|
157
|
+
if (m_stateful)
|
|
158
|
+
{
|
|
159
|
+
m_state.resize(coefficients.size(), T(0));
|
|
160
|
+
m_stateIndex = 0;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// ========== Filter Design Methods ==========
|
|
165
|
+
|
|
166
|
+
template <typename T>
|
|
167
|
+
std::vector<T> FirFilter<T>::generateSincLowPass(T cutoffFreq, size_t numTaps)
|
|
168
|
+
{
|
|
169
|
+
if (numTaps % 2 == 0)
|
|
170
|
+
{
|
|
171
|
+
++numTaps; // Ensure odd for symmetric impulse response
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
std::vector<T> impulse(numTaps);
|
|
175
|
+
int M = static_cast<int>(numTaps - 1);
|
|
176
|
+
int M_half = M / 2;
|
|
177
|
+
|
|
178
|
+
for (int n = 0; n < static_cast<int>(numTaps); ++n)
|
|
179
|
+
{
|
|
180
|
+
int n_shifted = n - M_half;
|
|
181
|
+
|
|
182
|
+
if (n_shifted == 0)
|
|
183
|
+
{
|
|
184
|
+
// sinc(0) = 1
|
|
185
|
+
impulse[n] = T(2) * cutoffFreq;
|
|
186
|
+
}
|
|
187
|
+
else
|
|
188
|
+
{
|
|
189
|
+
// sinc(x) = sin(πx) / (πx)
|
|
190
|
+
T x = T(2) * static_cast<T>(M_PI) * cutoffFreq * static_cast<T>(n_shifted);
|
|
191
|
+
impulse[n] = std::sin(x) / (static_cast<T>(M_PI) * static_cast<T>(n_shifted));
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
return impulse;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
template <typename T>
|
|
199
|
+
void FirFilter<T>::applyWindow(std::vector<T> &impulse, const std::string &windowType)
|
|
200
|
+
{
|
|
201
|
+
const size_t N = impulse.size();
|
|
202
|
+
const T pi = static_cast<T>(M_PI);
|
|
203
|
+
|
|
204
|
+
for (size_t n = 0; n < N; ++n)
|
|
205
|
+
{
|
|
206
|
+
T window = T(1);
|
|
207
|
+
const T nf = static_cast<T>(n);
|
|
208
|
+
const T Nf = static_cast<T>(N);
|
|
209
|
+
|
|
210
|
+
if (windowType == "hamming")
|
|
211
|
+
{
|
|
212
|
+
window = T(0.54) - T(0.46) * std::cos(T(2) * pi * nf / (Nf - T(1)));
|
|
213
|
+
}
|
|
214
|
+
else if (windowType == "hann")
|
|
215
|
+
{
|
|
216
|
+
window = T(0.5) * (T(1) - std::cos(T(2) * pi * nf / (Nf - T(1))));
|
|
217
|
+
}
|
|
218
|
+
else if (windowType == "blackman")
|
|
219
|
+
{
|
|
220
|
+
window = T(0.42) - T(0.5) * std::cos(T(2) * pi * nf / (Nf - T(1))) + T(0.08) * std::cos(T(4) * pi * nf / (Nf - T(1)));
|
|
221
|
+
}
|
|
222
|
+
else if (windowType == "bartlett")
|
|
223
|
+
{
|
|
224
|
+
window = T(1) - std::abs(T(2) * nf / (Nf - T(1)) - T(1));
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
impulse[n] *= window;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
template <typename T>
|
|
232
|
+
FirFilter<T> FirFilter<T>::createLowPass(T cutoffFreq, size_t numTaps, const std::string &windowType)
|
|
233
|
+
{
|
|
234
|
+
if (cutoffFreq <= 0 || cutoffFreq >= T(0.5))
|
|
235
|
+
{
|
|
236
|
+
throw std::invalid_argument("Cutoff frequency must be between 0 and 0.5 (normalized)");
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
auto impulse = generateSincLowPass(cutoffFreq, numTaps);
|
|
240
|
+
applyWindow(impulse, windowType);
|
|
241
|
+
|
|
242
|
+
// Normalize to unit gain at DC
|
|
243
|
+
T sum = T(0);
|
|
244
|
+
for (const auto &val : impulse)
|
|
245
|
+
{
|
|
246
|
+
sum += val;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
for (auto &val : impulse)
|
|
250
|
+
{
|
|
251
|
+
val /= sum;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
return FirFilter<T>(impulse, true);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
template <typename T>
|
|
258
|
+
FirFilter<T> FirFilter<T>::createHighPass(T cutoffFreq, size_t numTaps, const std::string &windowType)
|
|
259
|
+
{
|
|
260
|
+
// High-pass = delta - low-pass (spectral inversion)
|
|
261
|
+
auto lowPass = createLowPass(cutoffFreq, numTaps, windowType);
|
|
262
|
+
auto coeffs = lowPass.getCoefficients();
|
|
263
|
+
|
|
264
|
+
// Spectral inversion
|
|
265
|
+
for (size_t i = 0; i < coeffs.size(); ++i)
|
|
266
|
+
{
|
|
267
|
+
coeffs[i] = -coeffs[i];
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// Add impulse at center
|
|
271
|
+
coeffs[coeffs.size() / 2] += T(1);
|
|
272
|
+
|
|
273
|
+
return FirFilter<T>(coeffs, true);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
template <typename T>
|
|
277
|
+
FirFilter<T> FirFilter<T>::createBandPass(T lowCutoff, T highCutoff, size_t numTaps, const std::string &windowType)
|
|
278
|
+
{
|
|
279
|
+
if (lowCutoff >= highCutoff)
|
|
280
|
+
{
|
|
281
|
+
throw std::invalid_argument("Low cutoff must be less than high cutoff");
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
// Band-pass = low-pass(high) - low-pass(low)
|
|
285
|
+
auto lpHigh = createLowPass(highCutoff, numTaps, windowType);
|
|
286
|
+
auto lpLow = createLowPass(lowCutoff, numTaps, windowType);
|
|
287
|
+
|
|
288
|
+
auto coeffsHigh = lpHigh.getCoefficients();
|
|
289
|
+
auto coeffsLow = lpLow.getCoefficients();
|
|
290
|
+
|
|
291
|
+
std::vector<T> bandPass(coeffsHigh.size());
|
|
292
|
+
for (size_t i = 0; i < coeffsHigh.size(); ++i)
|
|
293
|
+
{
|
|
294
|
+
bandPass[i] = coeffsHigh[i] - coeffsLow[i];
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
return FirFilter<T>(bandPass, true);
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
template <typename T>
|
|
301
|
+
FirFilter<T> FirFilter<T>::createBandStop(T lowCutoff, T highCutoff, size_t numTaps, const std::string &windowType)
|
|
302
|
+
{
|
|
303
|
+
// Band-stop = low-pass(low) + high-pass(high)
|
|
304
|
+
auto lpLow = createLowPass(lowCutoff, numTaps, windowType);
|
|
305
|
+
auto hpHigh = createHighPass(highCutoff, numTaps, windowType);
|
|
306
|
+
|
|
307
|
+
auto coeffsLow = lpLow.getCoefficients();
|
|
308
|
+
auto coeffsHigh = hpHigh.getCoefficients();
|
|
309
|
+
|
|
310
|
+
std::vector<T> bandStop(coeffsLow.size());
|
|
311
|
+
for (size_t i = 0; i < coeffsLow.size(); ++i)
|
|
312
|
+
{
|
|
313
|
+
bandStop[i] = coeffsLow[i] + coeffsHigh[i];
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
return FirFilter<T>(bandStop, true);
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
// Explicit template instantiations
|
|
320
|
+
template class FirFilter<float>;
|
|
321
|
+
template class FirFilter<double>;
|
|
322
|
+
|
|
323
|
+
} // namespace core
|
|
324
|
+
} // namespace dsp
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* FIR (Finite Impulse Response) Filter
|
|
3
|
+
*
|
|
4
|
+
* A non-recursive filter defined by:
|
|
5
|
+
* y[n] = b[0]*x[n] + b[1]*x[n-1] + ... + b[M]*x[n-M]
|
|
6
|
+
*
|
|
7
|
+
* Features:
|
|
8
|
+
* - Always stable (no feedback)
|
|
9
|
+
* - Linear phase possible
|
|
10
|
+
* - Stateful (maintains sample history) and stateless modes
|
|
11
|
+
* - SIMD-optimized convolution
|
|
12
|
+
* - Efficient circular buffer for state management
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
#ifndef DSP_CORE_FIR_FILTER_H
|
|
16
|
+
#define DSP_CORE_FIR_FILTER_H
|
|
17
|
+
|
|
18
|
+
#include <vector>
|
|
19
|
+
#include <cstddef>
|
|
20
|
+
#include <memory>
|
|
21
|
+
#include "../utils/CircularBufferArray.h"
|
|
22
|
+
|
|
23
|
+
namespace dsp
|
|
24
|
+
{
|
|
25
|
+
namespace core
|
|
26
|
+
{
|
|
27
|
+
|
|
28
|
+
template <typename T = float>
|
|
29
|
+
class FirFilter
|
|
30
|
+
{
|
|
31
|
+
public:
|
|
32
|
+
/**
|
|
33
|
+
* Constructor
|
|
34
|
+
* @param coefficients Filter coefficients (b[0], b[1], ..., b[M])
|
|
35
|
+
* @param stateful If true, maintains state between process calls
|
|
36
|
+
*/
|
|
37
|
+
explicit FirFilter(const std::vector<T> &coefficients, bool stateful = true);
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Process single sample (stateful mode only)
|
|
41
|
+
* @param input Input sample
|
|
42
|
+
* @return Filtered output sample
|
|
43
|
+
*/
|
|
44
|
+
T processSample(T input);
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Process batch of samples
|
|
48
|
+
* @param input Input samples
|
|
49
|
+
* @param output Output buffer (must be same size as input)
|
|
50
|
+
* @param length Number of samples
|
|
51
|
+
* @param stateless If true, ignores internal state (batch processing)
|
|
52
|
+
*/
|
|
53
|
+
void process(const T *input, T *output, size_t length, bool stateless = false);
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Reset filter state (clear history)
|
|
57
|
+
*/
|
|
58
|
+
void reset();
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Get filter order (M = number of coefficients - 1)
|
|
62
|
+
*/
|
|
63
|
+
size_t getOrder() const { return m_coefficients.size() - 1; }
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Get number of coefficients
|
|
67
|
+
*/
|
|
68
|
+
size_t getNumCoefficients() const { return m_coefficients.size(); }
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Get coefficients
|
|
72
|
+
*/
|
|
73
|
+
const std::vector<T> &getCoefficients() const { return m_coefficients; }
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Update coefficients (resets state)
|
|
77
|
+
*/
|
|
78
|
+
void setCoefficients(const std::vector<T> &coefficients);
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Check if filter is stateful
|
|
82
|
+
*/
|
|
83
|
+
bool isStateful() const { return m_stateful; }
|
|
84
|
+
|
|
85
|
+
// ========== Common FIR Filter Designs ==========
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Create low-pass filter using windowed sinc method
|
|
89
|
+
* @param cutoffFreq Cutoff frequency (normalized: 0 to 0.5)
|
|
90
|
+
* @param numTaps Number of filter taps (higher = sharper transition)
|
|
91
|
+
* @param windowType Window function ("hamming", "hann", "blackman")
|
|
92
|
+
*/
|
|
93
|
+
static FirFilter<T> createLowPass(T cutoffFreq, size_t numTaps, const std::string &windowType = "hamming");
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Create high-pass filter
|
|
97
|
+
* @param cutoffFreq Cutoff frequency (normalized: 0 to 0.5)
|
|
98
|
+
* @param numTaps Number of filter taps
|
|
99
|
+
* @param windowType Window function
|
|
100
|
+
*/
|
|
101
|
+
static FirFilter<T> createHighPass(T cutoffFreq, size_t numTaps, const std::string &windowType = "hamming");
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Create band-pass filter
|
|
105
|
+
* @param lowCutoff Low cutoff frequency (normalized)
|
|
106
|
+
* @param highCutoff High cutoff frequency (normalized)
|
|
107
|
+
* @param numTaps Number of filter taps
|
|
108
|
+
* @param windowType Window function
|
|
109
|
+
*/
|
|
110
|
+
static FirFilter<T> createBandPass(T lowCutoff, T highCutoff, size_t numTaps, const std::string &windowType = "hamming");
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Create band-stop (notch) filter
|
|
114
|
+
* @param lowCutoff Low cutoff frequency (normalized)
|
|
115
|
+
* @param highCutoff High cutoff frequency (normalized)
|
|
116
|
+
* @param numTaps Number of filter taps
|
|
117
|
+
* @param windowType Window function
|
|
118
|
+
*/
|
|
119
|
+
static FirFilter<T> createBandStop(T lowCutoff, T highCutoff, size_t numTaps, const std::string &windowType = "hamming");
|
|
120
|
+
|
|
121
|
+
private:
|
|
122
|
+
std::vector<T> m_coefficients; // Filter coefficients (b[0], b[1], ..., b[M])
|
|
123
|
+
std::vector<T> m_state; // Sample history (x[n-1], x[n-2], ..., x[n-M])
|
|
124
|
+
size_t m_stateIndex; // Current position in circular state buffer
|
|
125
|
+
bool m_stateful; // Whether to maintain state between calls
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Compute single output sample via convolution
|
|
129
|
+
* @param input Current input sample
|
|
130
|
+
* @param history Previous samples
|
|
131
|
+
* @param historySize Number of valid history samples
|
|
132
|
+
*/
|
|
133
|
+
T convolve(T input, const T *history, size_t historySize);
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Apply window function for filter design
|
|
137
|
+
*/
|
|
138
|
+
static void applyWindow(std::vector<T> &impulse, const std::string &windowType);
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Generate ideal sinc low-pass impulse response
|
|
142
|
+
*/
|
|
143
|
+
static std::vector<T> generateSincLowPass(T cutoffFreq, size_t numTaps);
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
} // namespace core
|
|
147
|
+
} // namespace dsp
|
|
148
|
+
|
|
149
|
+
#endif // DSP_CORE_FIR_FILTER_H
|