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,441 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* FFT/DFT Engine Implementation with SIMD Optimizations
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
#define _USE_MATH_DEFINES
|
|
6
|
+
#include <cmath>
|
|
7
|
+
#include "FftEngine.h"
|
|
8
|
+
#include "../utils/SimdOps.h"
|
|
9
|
+
#include <algorithm>
|
|
10
|
+
#include <stdexcept>
|
|
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
|
+
FftEngine<T>::FftEngine(size_t size)
|
|
23
|
+
: m_size(size), m_isPowerOfTwo(checkPowerOfTwo(size))
|
|
24
|
+
{
|
|
25
|
+
if (size == 0)
|
|
26
|
+
{
|
|
27
|
+
throw std::invalid_argument("FFT size must be > 0");
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Initialize twiddle factors and bit-reversal for FFT
|
|
31
|
+
if (m_isPowerOfTwo)
|
|
32
|
+
{
|
|
33
|
+
initTwiddleFactors();
|
|
34
|
+
initBitReversal();
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Allocate working buffer
|
|
38
|
+
m_workBuffer.resize(m_size);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// ========== Complex Transforms ==========
|
|
42
|
+
|
|
43
|
+
template <typename T>
|
|
44
|
+
void FftEngine<T>::fft(const Complex *input, Complex *output)
|
|
45
|
+
{
|
|
46
|
+
if (!m_isPowerOfTwo)
|
|
47
|
+
{
|
|
48
|
+
throw std::runtime_error("FFT requires power-of-2 size. Use DFT for arbitrary sizes.");
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Copy input to output for in-place operation
|
|
52
|
+
std::copy(input, input + m_size, output);
|
|
53
|
+
|
|
54
|
+
// Perform Cooley-Tukey FFT
|
|
55
|
+
cooleyTukeyFFT(output, false);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
template <typename T>
|
|
59
|
+
void FftEngine<T>::ifft(const Complex *input, Complex *output)
|
|
60
|
+
{
|
|
61
|
+
if (!m_isPowerOfTwo)
|
|
62
|
+
{
|
|
63
|
+
throw std::runtime_error("IFFT requires power-of-2 size. Use IDFT for arbitrary sizes.");
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Copy input to output
|
|
67
|
+
std::copy(input, input + m_size, output);
|
|
68
|
+
|
|
69
|
+
// Perform inverse FFT
|
|
70
|
+
cooleyTukeyFFT(output, true);
|
|
71
|
+
|
|
72
|
+
// Scale by 1/N
|
|
73
|
+
T scale = T(1) / static_cast<T>(m_size);
|
|
74
|
+
for (size_t i = 0; i < m_size; ++i)
|
|
75
|
+
{
|
|
76
|
+
output[i] *= scale;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
template <typename T>
|
|
81
|
+
void FftEngine<T>::dft(const Complex *input, Complex *output)
|
|
82
|
+
{
|
|
83
|
+
// Direct DFT computation: X[k] = Σ x[n] * e^(-j2πkn/N)
|
|
84
|
+
const T two_pi = static_cast<T>(2.0 * M_PI);
|
|
85
|
+
|
|
86
|
+
for (size_t k = 0; k < m_size; ++k)
|
|
87
|
+
{
|
|
88
|
+
Complex sum(0, 0);
|
|
89
|
+
|
|
90
|
+
for (size_t n = 0; n < m_size; ++n)
|
|
91
|
+
{
|
|
92
|
+
T angle = -two_pi * static_cast<T>(k * n) / static_cast<T>(m_size);
|
|
93
|
+
Complex twiddle(std::cos(angle), std::sin(angle));
|
|
94
|
+
sum += input[n] * twiddle;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
output[k] = sum;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
template <typename T>
|
|
102
|
+
void FftEngine<T>::idft(const Complex *input, Complex *output)
|
|
103
|
+
{
|
|
104
|
+
// Inverse DFT: x[n] = (1/N) * Σ X[k] * e^(j2πkn/N)
|
|
105
|
+
const T two_pi = static_cast<T>(2.0 * M_PI);
|
|
106
|
+
const T scale = T(1) / static_cast<T>(m_size);
|
|
107
|
+
|
|
108
|
+
for (size_t n = 0; n < m_size; ++n)
|
|
109
|
+
{
|
|
110
|
+
Complex sum(0, 0);
|
|
111
|
+
|
|
112
|
+
for (size_t k = 0; k < m_size; ++k)
|
|
113
|
+
{
|
|
114
|
+
T angle = two_pi * static_cast<T>(k * n) / static_cast<T>(m_size);
|
|
115
|
+
Complex twiddle(std::cos(angle), std::sin(angle));
|
|
116
|
+
sum += input[k] * twiddle;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
output[n] = sum * scale;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// ========== Real-Input Transforms ==========
|
|
124
|
+
|
|
125
|
+
template <typename T>
|
|
126
|
+
void FftEngine<T>::rfft(const T *input, Complex *output)
|
|
127
|
+
{
|
|
128
|
+
if (!m_isPowerOfTwo)
|
|
129
|
+
{
|
|
130
|
+
throw std::runtime_error("RFFT requires power-of-2 size. Use RDFT for arbitrary sizes.");
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Pack real input into complex array (imaginary part = 0)
|
|
134
|
+
for (size_t i = 0; i < m_size; ++i)
|
|
135
|
+
{
|
|
136
|
+
m_workBuffer[i] = Complex(input[i], 0);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Perform standard FFT
|
|
140
|
+
cooleyTukeyFFT(m_workBuffer.data(), false);
|
|
141
|
+
|
|
142
|
+
// Copy half spectrum (exploit Hermitian symmetry)
|
|
143
|
+
size_t halfSize = getHalfSize();
|
|
144
|
+
for (size_t i = 0; i < halfSize; ++i)
|
|
145
|
+
{
|
|
146
|
+
output[i] = m_workBuffer[i];
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
template <typename T>
|
|
151
|
+
void FftEngine<T>::irfft(const Complex *input, T *output)
|
|
152
|
+
{
|
|
153
|
+
if (!m_isPowerOfTwo)
|
|
154
|
+
{
|
|
155
|
+
throw std::runtime_error("IRFFT requires power-of-2 size. Use IRDFT for arbitrary sizes.");
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
size_t halfSize = getHalfSize();
|
|
159
|
+
|
|
160
|
+
// Reconstruct full spectrum using Hermitian symmetry: X[N-k] = X*[k]
|
|
161
|
+
m_workBuffer[0] = input[0]; // DC component
|
|
162
|
+
|
|
163
|
+
for (size_t i = 1; i < halfSize - 1; ++i)
|
|
164
|
+
{
|
|
165
|
+
m_workBuffer[i] = input[i];
|
|
166
|
+
m_workBuffer[m_size - i] = std::conj(input[i]);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// Nyquist frequency (if N is even)
|
|
170
|
+
if (m_size % 2 == 0)
|
|
171
|
+
{
|
|
172
|
+
m_workBuffer[m_size / 2] = input[halfSize - 1];
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// Perform inverse FFT
|
|
176
|
+
cooleyTukeyFFT(m_workBuffer.data(), true);
|
|
177
|
+
|
|
178
|
+
// Extract real part and scale by 1/N
|
|
179
|
+
T scale = T(1) / static_cast<T>(m_size);
|
|
180
|
+
for (size_t i = 0; i < m_size; ++i)
|
|
181
|
+
{
|
|
182
|
+
output[i] = m_workBuffer[i].real() * scale;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
template <typename T>
|
|
187
|
+
void FftEngine<T>::rdft(const T *input, Complex *output)
|
|
188
|
+
{
|
|
189
|
+
// Direct RDFT: compute only half spectrum
|
|
190
|
+
const T two_pi = static_cast<T>(2.0 * M_PI);
|
|
191
|
+
size_t halfSize = getHalfSize();
|
|
192
|
+
|
|
193
|
+
for (size_t k = 0; k < halfSize; ++k)
|
|
194
|
+
{
|
|
195
|
+
Complex sum(0, 0);
|
|
196
|
+
|
|
197
|
+
for (size_t n = 0; n < m_size; ++n)
|
|
198
|
+
{
|
|
199
|
+
T angle = -two_pi * static_cast<T>(k * n) / static_cast<T>(m_size);
|
|
200
|
+
Complex twiddle(std::cos(angle), std::sin(angle));
|
|
201
|
+
sum += Complex(input[n], 0) * twiddle;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
output[k] = sum;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
template <typename T>
|
|
209
|
+
void FftEngine<T>::irdft(const Complex *input, T *output)
|
|
210
|
+
{
|
|
211
|
+
// Inverse RDFT: reconstruct real signal from half spectrum
|
|
212
|
+
const T two_pi = static_cast<T>(2.0 * M_PI);
|
|
213
|
+
const T scale = T(1) / static_cast<T>(m_size);
|
|
214
|
+
size_t halfSize = getHalfSize();
|
|
215
|
+
|
|
216
|
+
for (size_t n = 0; n < m_size; ++n)
|
|
217
|
+
{
|
|
218
|
+
Complex sum(0, 0);
|
|
219
|
+
|
|
220
|
+
// DC component
|
|
221
|
+
sum += input[0];
|
|
222
|
+
|
|
223
|
+
// Positive frequencies
|
|
224
|
+
for (size_t k = 1; k < halfSize - 1; ++k)
|
|
225
|
+
{
|
|
226
|
+
T angle = two_pi * static_cast<T>(k * n) / static_cast<T>(m_size);
|
|
227
|
+
Complex twiddle(std::cos(angle), std::sin(angle));
|
|
228
|
+
|
|
229
|
+
// Add X[k] * e^(j2πkn/N) + X*[k] * e^(-j2πkn/N)
|
|
230
|
+
sum += input[k] * twiddle;
|
|
231
|
+
sum += std::conj(input[k]) * std::conj(twiddle);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// Nyquist frequency (if N is even)
|
|
235
|
+
if (m_size % 2 == 0 && halfSize > 1)
|
|
236
|
+
{
|
|
237
|
+
T angle = two_pi * static_cast<T>((halfSize - 1) * n) / static_cast<T>(m_size);
|
|
238
|
+
Complex twiddle(std::cos(angle), std::sin(angle));
|
|
239
|
+
sum += input[halfSize - 1] * twiddle;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
output[n] = sum.real() * scale;
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// ========== Utility Methods ==========
|
|
247
|
+
|
|
248
|
+
template <typename T>
|
|
249
|
+
void FftEngine<T>::getMagnitude(const Complex *spectrum, T *magnitudes, size_t length)
|
|
250
|
+
{
|
|
251
|
+
// MEDIUM WIN: SIMD-optimized magnitude calculation
|
|
252
|
+
if constexpr (std::is_same_v<T, float>)
|
|
253
|
+
{
|
|
254
|
+
// Extract real and imaginary parts into separate arrays for SIMD
|
|
255
|
+
std::vector<float> real(length);
|
|
256
|
+
std::vector<float> imag(length);
|
|
257
|
+
|
|
258
|
+
for (size_t i = 0; i < length; ++i)
|
|
259
|
+
{
|
|
260
|
+
real[i] = spectrum[i].real();
|
|
261
|
+
imag[i] = spectrum[i].imag();
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
dsp::simd::complex_magnitude(real.data(), imag.data(), magnitudes, length);
|
|
265
|
+
}
|
|
266
|
+
else
|
|
267
|
+
{
|
|
268
|
+
// Fallback for double precision
|
|
269
|
+
for (size_t i = 0; i < length; ++i)
|
|
270
|
+
{
|
|
271
|
+
magnitudes[i] = std::abs(spectrum[i]);
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
template <typename T>
|
|
277
|
+
void FftEngine<T>::getPhase(const Complex *spectrum, T *phases, size_t length)
|
|
278
|
+
{
|
|
279
|
+
// Phase calculation (atan2 doesn't benefit much from SIMD)
|
|
280
|
+
for (size_t i = 0; i < length; ++i)
|
|
281
|
+
{
|
|
282
|
+
phases[i] = std::arg(spectrum[i]);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
template <typename T>
|
|
287
|
+
void FftEngine<T>::getPower(const Complex *spectrum, T *power, size_t length)
|
|
288
|
+
{
|
|
289
|
+
// MEDIUM WIN: SIMD-optimized power calculation
|
|
290
|
+
if constexpr (std::is_same_v<T, float>)
|
|
291
|
+
{
|
|
292
|
+
// Extract real and imaginary parts into separate arrays for SIMD
|
|
293
|
+
std::vector<float> real(length);
|
|
294
|
+
std::vector<float> imag(length);
|
|
295
|
+
|
|
296
|
+
for (size_t i = 0; i < length; ++i)
|
|
297
|
+
{
|
|
298
|
+
real[i] = spectrum[i].real();
|
|
299
|
+
imag[i] = spectrum[i].imag();
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
dsp::simd::complex_power(real.data(), imag.data(), power, length);
|
|
303
|
+
}
|
|
304
|
+
else
|
|
305
|
+
{
|
|
306
|
+
// Fallback for double precision
|
|
307
|
+
for (size_t i = 0; i < length; ++i)
|
|
308
|
+
{
|
|
309
|
+
T mag = std::abs(spectrum[i]);
|
|
310
|
+
power[i] = mag * mag;
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
// ========== Private Methods ==========
|
|
316
|
+
|
|
317
|
+
template <typename T>
|
|
318
|
+
void FftEngine<T>::initTwiddleFactors()
|
|
319
|
+
{
|
|
320
|
+
m_twiddleFactors.resize(m_size / 2);
|
|
321
|
+
|
|
322
|
+
const T two_pi = static_cast<T>(2.0 * M_PI);
|
|
323
|
+
|
|
324
|
+
for (size_t k = 0; k < m_size / 2; ++k)
|
|
325
|
+
{
|
|
326
|
+
T angle = -two_pi * static_cast<T>(k) / static_cast<T>(m_size);
|
|
327
|
+
m_twiddleFactors[k] = Complex(std::cos(angle), std::sin(angle));
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
template <typename T>
|
|
332
|
+
void FftEngine<T>::initBitReversal()
|
|
333
|
+
{
|
|
334
|
+
m_bitReversalIndices.resize(m_size);
|
|
335
|
+
|
|
336
|
+
size_t bits = 0;
|
|
337
|
+
size_t temp = m_size;
|
|
338
|
+
while (temp > 1)
|
|
339
|
+
{
|
|
340
|
+
temp >>= 1;
|
|
341
|
+
++bits;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
for (size_t i = 0; i < m_size; ++i)
|
|
345
|
+
{
|
|
346
|
+
m_bitReversalIndices[i] = reverseBits(i, bits);
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
template <typename T>
|
|
351
|
+
void FftEngine<T>::bitReverse(Complex *data)
|
|
352
|
+
{
|
|
353
|
+
for (size_t i = 0; i < m_size; ++i)
|
|
354
|
+
{
|
|
355
|
+
size_t j = m_bitReversalIndices[i];
|
|
356
|
+
if (i < j)
|
|
357
|
+
{
|
|
358
|
+
std::swap(data[i], data[j]);
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
template <typename T>
|
|
364
|
+
void FftEngine<T>::cooleyTukeyFFT(Complex *data, bool inverse)
|
|
365
|
+
{
|
|
366
|
+
// Bit-reversal permutation
|
|
367
|
+
bitReverse(data);
|
|
368
|
+
|
|
369
|
+
// Cooley-Tukey decimation-in-time
|
|
370
|
+
for (size_t len = 2; len <= m_size; len *= 2)
|
|
371
|
+
{
|
|
372
|
+
size_t halfLen = len / 2;
|
|
373
|
+
size_t step = m_size / len;
|
|
374
|
+
|
|
375
|
+
for (size_t i = 0; i < m_size; i += len)
|
|
376
|
+
{
|
|
377
|
+
for (size_t j = 0; j < halfLen; ++j)
|
|
378
|
+
{
|
|
379
|
+
size_t twiddleIdx = j * step;
|
|
380
|
+
Complex twiddle = m_twiddleFactors[twiddleIdx];
|
|
381
|
+
|
|
382
|
+
if (inverse)
|
|
383
|
+
{
|
|
384
|
+
twiddle = std::conj(twiddle);
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
butterfly(data[i + j], data[i + j + halfLen], twiddle);
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
template <typename T>
|
|
394
|
+
inline void FftEngine<T>::butterfly(Complex &a, Complex &b, const Complex &twiddle)
|
|
395
|
+
{
|
|
396
|
+
Complex temp = b * twiddle;
|
|
397
|
+
b = a - temp;
|
|
398
|
+
a = a + temp;
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
template <typename T>
|
|
402
|
+
bool FftEngine<T>::checkPowerOfTwo(size_t n)
|
|
403
|
+
{
|
|
404
|
+
return n > 0 && (n & (n - 1)) == 0;
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
template <typename T>
|
|
408
|
+
size_t FftEngine<T>::nextPowerOfTwo(size_t n)
|
|
409
|
+
{
|
|
410
|
+
if (n == 0)
|
|
411
|
+
return 1;
|
|
412
|
+
|
|
413
|
+
--n;
|
|
414
|
+
n |= n >> 1;
|
|
415
|
+
n |= n >> 2;
|
|
416
|
+
n |= n >> 4;
|
|
417
|
+
n |= n >> 8;
|
|
418
|
+
n |= n >> 16;
|
|
419
|
+
n |= n >> 32;
|
|
420
|
+
|
|
421
|
+
return n + 1;
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
template <typename T>
|
|
425
|
+
size_t FftEngine<T>::reverseBits(size_t x, size_t bits)
|
|
426
|
+
{
|
|
427
|
+
size_t result = 0;
|
|
428
|
+
for (size_t i = 0; i < bits; ++i)
|
|
429
|
+
{
|
|
430
|
+
result = (result << 1) | (x & 1);
|
|
431
|
+
x >>= 1;
|
|
432
|
+
}
|
|
433
|
+
return result;
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
// Explicit template instantiations
|
|
437
|
+
template class FftEngine<float>;
|
|
438
|
+
template class FftEngine<double>;
|
|
439
|
+
|
|
440
|
+
} // namespace core
|
|
441
|
+
} // namespace dsp
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* FFT/DFT Engine - High-performance Fourier transforms
|
|
3
|
+
*
|
|
4
|
+
* Implements all 8 standard transforms:
|
|
5
|
+
* - DFT/IDFT: Direct Fourier Transform (O(N²))
|
|
6
|
+
* - FFT/IFFT: Fast Fourier Transform (O(N log N))
|
|
7
|
+
* - RDFT/IRDFT: Real-input DFT (outputs N/2+1 bins)
|
|
8
|
+
* - RFFT/IRFFT: Real-input FFT (fast version)
|
|
9
|
+
*
|
|
10
|
+
* Features:
|
|
11
|
+
* - Cooley-Tukey radix-2 FFT algorithm
|
|
12
|
+
* - In-place computation for memory efficiency
|
|
13
|
+
* - Bit-reversal permutation
|
|
14
|
+
* - Twiddle factor caching
|
|
15
|
+
* - SIMD optimization where possible
|
|
16
|
+
* - Hermitian symmetry exploitation for real inputs
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
#ifndef DSP_CORE_FFT_ENGINE_H
|
|
20
|
+
#define DSP_CORE_FFT_ENGINE_H
|
|
21
|
+
|
|
22
|
+
#include <complex>
|
|
23
|
+
#include <vector>
|
|
24
|
+
#include <cmath>
|
|
25
|
+
#include <memory>
|
|
26
|
+
|
|
27
|
+
namespace dsp
|
|
28
|
+
{
|
|
29
|
+
namespace core
|
|
30
|
+
{
|
|
31
|
+
|
|
32
|
+
template <typename T = float>
|
|
33
|
+
class FftEngine
|
|
34
|
+
{
|
|
35
|
+
public:
|
|
36
|
+
using Complex = std::complex<T>;
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Constructor
|
|
40
|
+
* @param size FFT size (must be power of 2 for FFT, any size for DFT)
|
|
41
|
+
*/
|
|
42
|
+
explicit FftEngine(size_t size);
|
|
43
|
+
|
|
44
|
+
~FftEngine() = default;
|
|
45
|
+
|
|
46
|
+
// ========== Complex Transforms ==========
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Forward FFT (complex -> complex)
|
|
50
|
+
* X[k] = Σ x[n] * e^(-j2πkn/N)
|
|
51
|
+
*
|
|
52
|
+
* @param input Complex input signal (size N)
|
|
53
|
+
* @param output Complex frequency spectrum (size N)
|
|
54
|
+
*/
|
|
55
|
+
void fft(const Complex *input, Complex *output);
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Inverse FFT (complex -> complex)
|
|
59
|
+
* x[n] = (1/N) * Σ X[k] * e^(j2πkn/N)
|
|
60
|
+
*
|
|
61
|
+
* @param input Complex frequency spectrum (size N)
|
|
62
|
+
* @param output Complex time-domain signal (size N)
|
|
63
|
+
*/
|
|
64
|
+
void ifft(const Complex *input, Complex *output);
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Forward DFT (complex -> complex)
|
|
68
|
+
* Direct computation, slower but works for any size
|
|
69
|
+
*
|
|
70
|
+
* @param input Complex input signal
|
|
71
|
+
* @param output Complex frequency spectrum
|
|
72
|
+
*/
|
|
73
|
+
void dft(const Complex *input, Complex *output);
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Inverse DFT (complex -> complex)
|
|
77
|
+
*
|
|
78
|
+
* @param input Complex frequency spectrum
|
|
79
|
+
* @param output Complex time-domain signal
|
|
80
|
+
*/
|
|
81
|
+
void idft(const Complex *input, Complex *output);
|
|
82
|
+
|
|
83
|
+
// ========== Real-Input Transforms ==========
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Forward RFFT (real -> complex, half spectrum)
|
|
87
|
+
* Exploits Hermitian symmetry: X[k] = X*[N-k]
|
|
88
|
+
*
|
|
89
|
+
* @param input Real input signal (size N)
|
|
90
|
+
* @param output Complex half-spectrum (size N/2+1)
|
|
91
|
+
*/
|
|
92
|
+
void rfft(const T *input, Complex *output);
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Inverse RFFT (complex half-spectrum -> real)
|
|
96
|
+
* Reconstructs real signal from half spectrum
|
|
97
|
+
*
|
|
98
|
+
* @param input Complex half-spectrum (size N/2+1)
|
|
99
|
+
* @param output Real time-domain signal (size N)
|
|
100
|
+
*/
|
|
101
|
+
void irfft(const Complex *input, T *output);
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Forward RDFT (real -> complex, half spectrum)
|
|
105
|
+
* Direct computation version of RFFT
|
|
106
|
+
*
|
|
107
|
+
* @param input Real input signal (size N)
|
|
108
|
+
* @param output Complex half-spectrum (size N/2+1)
|
|
109
|
+
*/
|
|
110
|
+
void rdft(const T *input, Complex *output);
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Inverse RDFT (complex half-spectrum -> real)
|
|
114
|
+
* Direct computation version of IRFFT
|
|
115
|
+
*
|
|
116
|
+
* @param input Complex half-spectrum (size N/2+1)
|
|
117
|
+
* @param output Real time-domain signal (size N)
|
|
118
|
+
*/
|
|
119
|
+
void irdft(const Complex *input, T *output);
|
|
120
|
+
|
|
121
|
+
// ========== Utility Methods ==========
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Get FFT size
|
|
125
|
+
*/
|
|
126
|
+
size_t getSize() const { return m_size; }
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Get half-spectrum size (for real transforms)
|
|
130
|
+
*/
|
|
131
|
+
size_t getHalfSize() const { return m_size / 2 + 1; }
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Check if size is power of 2
|
|
135
|
+
*/
|
|
136
|
+
bool isPowerOfTwo() const { return m_isPowerOfTwo; }
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Get magnitude spectrum from complex spectrum
|
|
140
|
+
* |X[k]| = sqrt(Re²(X[k]) + Im²(X[k]))
|
|
141
|
+
*
|
|
142
|
+
* @param spectrum Complex spectrum
|
|
143
|
+
* @param magnitudes Output magnitude array (same size)
|
|
144
|
+
*/
|
|
145
|
+
void getMagnitude(const Complex *spectrum, T *magnitudes, size_t length);
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Get phase spectrum from complex spectrum
|
|
149
|
+
* ∠X[k] = atan2(Im(X[k]), Re(X[k]))
|
|
150
|
+
*
|
|
151
|
+
* @param spectrum Complex spectrum
|
|
152
|
+
* @param phases Output phase array (same size)
|
|
153
|
+
*/
|
|
154
|
+
void getPhase(const Complex *spectrum, T *phases, size_t length);
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Get power spectrum (magnitude squared)
|
|
158
|
+
* P[k] = |X[k]|²
|
|
159
|
+
*
|
|
160
|
+
* @param spectrum Complex spectrum
|
|
161
|
+
* @param power Output power array (same size)
|
|
162
|
+
*/
|
|
163
|
+
void getPower(const Complex *spectrum, T *power, size_t length);
|
|
164
|
+
|
|
165
|
+
private:
|
|
166
|
+
size_t m_size; // FFT size
|
|
167
|
+
bool m_isPowerOfTwo; // Whether size is power of 2
|
|
168
|
+
|
|
169
|
+
// Cached twiddle factors for FFT
|
|
170
|
+
std::vector<Complex> m_twiddleFactors;
|
|
171
|
+
|
|
172
|
+
// Bit-reversal permutation indices
|
|
173
|
+
std::vector<size_t> m_bitReversalIndices;
|
|
174
|
+
|
|
175
|
+
// Working buffer for in-place operations
|
|
176
|
+
std::vector<Complex> m_workBuffer;
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Initialize twiddle factors: W_N^k = e^(-j2πk/N)
|
|
180
|
+
*/
|
|
181
|
+
void initTwiddleFactors();
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Initialize bit-reversal permutation table
|
|
185
|
+
*/
|
|
186
|
+
void initBitReversal();
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Perform bit-reversal permutation
|
|
190
|
+
*/
|
|
191
|
+
void bitReverse(Complex *data);
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Core Cooley-Tukey FFT algorithm (in-place)
|
|
195
|
+
* @param data Complex array (size must be power of 2)
|
|
196
|
+
* @param inverse If true, performs inverse transform
|
|
197
|
+
*/
|
|
198
|
+
void cooleyTukeyFFT(Complex *data, bool inverse);
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Butterfly operation for FFT
|
|
202
|
+
*/
|
|
203
|
+
inline void butterfly(Complex &a, Complex &b, const Complex &twiddle);
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Check if number is power of 2
|
|
207
|
+
*/
|
|
208
|
+
static bool checkPowerOfTwo(size_t n);
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Compute next power of 2
|
|
212
|
+
*/
|
|
213
|
+
static size_t nextPowerOfTwo(size_t n);
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Reverse bits of integer
|
|
217
|
+
*/
|
|
218
|
+
static size_t reverseBits(size_t x, size_t bits);
|
|
219
|
+
};
|
|
220
|
+
|
|
221
|
+
} // namespace core
|
|
222
|
+
} // namespace dsp
|
|
223
|
+
|
|
224
|
+
#endif // DSP_CORE_FFT_ENGINE_H
|