DSPark 1.8.0
Header-only C++20 DSP for real-time and offline audio
Loading...
Searching...
No Matches
FFT.h
1// DSPark -- Professional Audio DSP Framework
2// Copyright (c) 2026 Cristian Moresi -- MIT License
3
4#pragma once
5
47#include "SimdOps.h"
48
49#include <cassert>
50#include <cmath>
51#include <cstddef>
52#include <cstdint>
53#include <cstring>
54#include <numbers>
55#include <type_traits>
56#include <utility>
57#include <vector>
58
59// --- Exception policy --------------------------------------------------------
60// By default invalid FFT sizes throw std::invalid_argument. For embedded and
61// plugin builds compiled without exception support, define DSPARK_NO_EXCEPTIONS
62// (auto-detected from -fno-exceptions / /EHs-c-) -- invalid sizes then assert in
63// debug and degrade to a safe minimal size in release instead of throwing.
64#if !defined(DSPARK_NO_EXCEPTIONS)
65 #if (defined(__GNUC__) || defined(__clang__)) && !defined(__EXCEPTIONS)
66 #define DSPARK_NO_EXCEPTIONS 1
67 #elif defined(_MSC_VER) && !defined(_CPPUNWIND)
68 #define DSPARK_NO_EXCEPTIONS 1
69 #endif
70#endif
71
72#if !defined(DSPARK_NO_EXCEPTIONS)
73 #include <stdexcept>
74#endif
75
76namespace dspark {
77namespace detail {
78namespace fft {
79
81template <typename T, int W> using Vec = simd::Vec<T, W>;
82template <typename T> inline constexpr int kWide = simd::kVecWidth<T>;
83template <typename T> inline constexpr int kNarrow = simd::kVecNarrowWidth<T>;
84
85// ============================================================================
86// Kernels
87// ============================================================================
88
91template <typename T>
93{
94 const T* w1r; const T* w1i;
95 const T* w2r; const T* w2i;
96 const T* w3r; const T* w3i;
97};
98
105template <typename T, int W>
106void radix4Strided(const T* xr, const T* xi, T* yr, T* yi,
107 size_t n, size_t s, const TwiddleSpan<T>& tw) noexcept
108{
109 using O = Vec<T, W>;
110 using V = typename O::V;
111 const size_t n1 = n / 4;
112 for (size_t p = 0; p < n1; ++p)
113 {
114 const V w1r = O::set1(tw.w1r[p]), w1i = O::set1(tw.w1i[p]);
115 const V w2r = O::set1(tw.w2r[p]), w2i = O::set1(tw.w2i[p]);
116 const V w3r = O::set1(tw.w3r[p]), w3i = O::set1(tw.w3i[p]);
117 const T* ar = xr + s * p; const T* ai = xi + s * p;
118 const T* br = xr + s * (p + n1); const T* bi = xi + s * (p + n1);
119 const T* cr = xr + s * (p + 2 * n1); const T* ci = xi + s * (p + 2 * n1);
120 const T* dr = xr + s * (p + 3 * n1); const T* di = xi + s * (p + 3 * n1);
121 T* y0r = yr + s * (4 * p); T* y0i = yi + s * (4 * p);
122 T* y1r = y0r + s; T* y1i = y0i + s;
123 T* y2r = y0r + 2 * s; T* y2i = y0i + 2 * s;
124 T* y3r = y0r + 3 * s; T* y3i = y0i + 3 * s;
125 for (size_t q = 0; q < s; q += W)
126 {
127 const V a_r = O::load(ar + q), a_i = O::load(ai + q);
128 const V b_r = O::load(br + q), b_i = O::load(bi + q);
129 const V c_r = O::load(cr + q), c_i = O::load(ci + q);
130 const V d_r = O::load(dr + q), d_i = O::load(di + q);
131 const V apcR = O::add(a_r, c_r), apcI = O::add(a_i, c_i);
132 const V amcR = O::sub(a_r, c_r), amcI = O::sub(a_i, c_i);
133 const V bpdR = O::add(b_r, d_r), bpdI = O::add(b_i, d_i);
134 const V bmdR = O::sub(b_r, d_r), bmdI = O::sub(b_i, d_i);
135 // t1 = amc - j bmd, t2 = apc - bpd, t3 = amc + j bmd
136 const V t1R = O::add(amcR, bmdI), t1I = O::sub(amcI, bmdR);
137 const V t2R = O::sub(apcR, bpdR), t2I = O::sub(apcI, bpdI);
138 const V t3R = O::sub(amcR, bmdI), t3I = O::add(amcI, bmdR);
139 O::store(y0r + q, O::add(apcR, bpdR));
140 O::store(y0i + q, O::add(apcI, bpdI));
141 O::store(y1r + q, O::mulSub(t1R, w1r, t1I, w1i));
142 O::store(y1i + q, O::mulAdd(t1R, w1i, t1I, w1r));
143 O::store(y2r + q, O::mulSub(t2R, w2r, t2I, w2i));
144 O::store(y2i + q, O::mulAdd(t2R, w2i, t2I, w2r));
145 O::store(y3r + q, O::mulSub(t3R, w3r, t3I, w3i));
146 O::store(y3i + q, O::mulAdd(t3R, w3i, t3I, w3r));
147 }
148 }
149}
150
157template <typename T, int W>
158void radix4First(const T* xr, const T* xi, T* yr, T* yi,
159 size_t n, const TwiddleSpan<T>& tw) noexcept
160{
161 using O = Vec<T, W>;
162 using V = typename O::V;
163 const size_t n1 = n / 4;
164 for (size_t p = 0; p < n1; p += W)
165 {
166 const V w1r = O::load(tw.w1r + p), w1i = O::load(tw.w1i + p);
167 const V w2r = O::load(tw.w2r + p), w2i = O::load(tw.w2i + p);
168 const V w3r = O::load(tw.w3r + p), w3i = O::load(tw.w3i + p);
169 const V a_r = O::load(xr + p), a_i = O::load(xi + p);
170 const V b_r = O::load(xr + p + n1), b_i = O::load(xi + p + n1);
171 const V c_r = O::load(xr + p + 2 * n1), c_i = O::load(xi + p + 2 * n1);
172 const V d_r = O::load(xr + p + 3 * n1), d_i = O::load(xi + p + 3 * n1);
173 const V apcR = O::add(a_r, c_r), apcI = O::add(a_i, c_i);
174 const V amcR = O::sub(a_r, c_r), amcI = O::sub(a_i, c_i);
175 const V bpdR = O::add(b_r, d_r), bpdI = O::add(b_i, d_i);
176 const V bmdR = O::sub(b_r, d_r), bmdI = O::sub(b_i, d_i);
177 const V t1R = O::add(amcR, bmdI), t1I = O::sub(amcI, bmdR);
178 const V t2R = O::sub(apcR, bpdR), t2I = O::sub(apcI, bpdI);
179 const V t3R = O::sub(amcR, bmdI), t3I = O::add(amcI, bmdR);
180 O::storeInterleave4(yr + 4 * p, O::add(apcR, bpdR),
181 O::mulSub(t1R, w1r, t1I, w1i),
182 O::mulSub(t2R, w2r, t2I, w2i),
183 O::mulSub(t3R, w3r, t3I, w3i));
184 O::storeInterleave4(yi + 4 * p, O::add(apcI, bpdI),
185 O::mulAdd(t1R, w1i, t1I, w1r),
186 O::mulAdd(t2R, w2i, t2I, w2r),
187 O::mulAdd(t3R, w3i, t3I, w3r));
188 }
189}
190
192template <typename T, int W>
193void radix2Last(const T* xr, const T* xi, T* yr, T* yi, size_t s) noexcept
194{
195 using O = Vec<T, W>;
196 for (size_t q = 0; q < s; q += W)
197 {
198 const auto a_r = O::load(xr + q), a_i = O::load(xi + q);
199 const auto b_r = O::load(xr + q + s), b_i = O::load(xi + q + s);
200 O::store(yr + q, O::add(a_r, b_r));
201 O::store(yi + q, O::add(a_i, b_i));
202 O::store(yr + q + s, O::sub(a_r, b_r));
203 O::store(yi + q + s, O::sub(a_i, b_i));
204 }
205}
206
212template <typename T>
214{
215public:
216 explicit SplitFFT(size_t n) : n_(n)
217 {
218 bufA_.assign(2 * n_, T(0));
219 bufB_.assign(2 * n_, T(0));
220
221 // Plan: radix-4 passes of length n, 4n', ... with stride 1, 4, ...;
222 // a radix-2 pass closes an odd log2(N).
223 size_t len = n_, stride = 1, offset = 0;
224 while (len >= 4)
225 {
226 passes_.push_back({ len, stride, offset, false });
227 offset += 6 * (len / 4);
228 len /= 4;
229 stride *= 4;
230 }
231 if (len == 2)
232 passes_.push_back({ 2, stride, offset, true });
233
234 twiddles_.assign(offset, T(0));
235 for (const Pass& pass : passes_)
236 {
237 if (pass.radix2) continue;
238 const size_t n1 = pass.len / 4;
239 T* w = twiddles_.data() + pass.twOffset;
240 for (size_t p = 0; p < n1; ++p)
241 {
242 const double a = -2.0 * std::numbers::pi_v<double> * static_cast<double>(p)
243 / static_cast<double>(pass.len);
244 w[p] = static_cast<T>(std::cos(a));
245 w[n1 + p] = static_cast<T>(std::sin(a));
246 w[2 * n1 + p] = static_cast<T>(std::cos(2.0 * a));
247 w[3 * n1 + p] = static_cast<T>(std::sin(2.0 * a));
248 w[4 * n1 + p] = static_cast<T>(std::cos(3.0 * a));
249 w[5 * n1 + p] = static_cast<T>(std::sin(3.0 * a));
250 }
251 }
252 }
253
254 [[nodiscard]] size_t size() const noexcept { return n_; }
255
257 [[nodiscard]] T* inRe() noexcept { return bufA_.data(); }
258 [[nodiscard]] T* inIm() noexcept { return bufA_.data() + n_; }
259
265 void run(const T*& outRe, const T*& outIm) noexcept
266 {
267 T* xr = bufA_.data(); T* xi = xr + n_;
268 T* yr = bufB_.data(); T* yi = yr + n_;
269 for (const Pass& pass : passes_)
270 {
271 if (pass.radix2)
272 runRadix2(xr, xi, yr, yi, pass.stride);
273 else
274 runRadix4(xr, xi, yr, yi, pass);
275 std::swap(xr, yr);
276 std::swap(xi, yi);
277 }
278 outRe = xr;
279 outIm = xi;
280 }
281
282private:
283 struct Pass { size_t len; size_t stride; size_t twOffset; bool radix2; };
284
285 void runRadix4(const T* xr, const T* xi, T* yr, T* yi, const Pass& pass) const noexcept
286 {
287 const size_t n1 = pass.len / 4;
288 const T* w = twiddles_.data() + pass.twOffset;
289 const TwiddleSpan<T> tw { w, w + n1, w + 2 * n1, w + 3 * n1, w + 4 * n1, w + 5 * n1 };
290 constexpr int wide = kWide<T>;
291 constexpr int narrow = kNarrow<T>;
292 if (pass.stride == 1)
293 {
294 if (n1 % wide == 0) radix4First<T, wide>(xr, xi, yr, yi, pass.len, tw);
295 else if (n1 % narrow == 0) radix4First<T, narrow>(xr, xi, yr, yi, pass.len, tw);
296 else radix4First<T, 1>(xr, xi, yr, yi, pass.len, tw);
297 }
298 else if (pass.stride % wide == 0) radix4Strided<T, wide>(xr, xi, yr, yi, pass.len, pass.stride, tw);
299 else if (pass.stride % narrow == 0) radix4Strided<T, narrow>(xr, xi, yr, yi, pass.len, pass.stride, tw);
300 else radix4Strided<T, 1>(xr, xi, yr, yi, pass.len, pass.stride, tw);
301 }
302
303 static void runRadix2(const T* xr, const T* xi, T* yr, T* yi, size_t s) noexcept
304 {
305 constexpr int wide = kWide<T>;
306 constexpr int narrow = kNarrow<T>;
307 if (s % wide == 0) radix2Last<T, wide>(xr, xi, yr, yi, s);
308 else if (s % narrow == 0) radix2Last<T, narrow>(xr, xi, yr, yi, s);
309 else radix2Last<T, 1>(xr, xi, yr, yi, s);
310 }
311
312 size_t n_;
313 std::vector<T> bufA_, bufB_;
314 std::vector<T> twiddles_;
315 std::vector<Pass> passes_;
316};
317
318} // namespace fft
319} // namespace detail
320
321// ============================================================================
322// FFTComplex
323// ============================================================================
324
335template <typename T>
337{
338public:
345 explicit FFTComplex(size_t size)
346 : engine_(validateSize(size))
347 {
348 }
349
354 [[nodiscard]] size_t getSize() const noexcept { return engine_.size(); }
355
361 void forward(T* data) noexcept { transform(data, false); }
362
368 void inverse(T* data) noexcept { transform(data, true); }
369
370private:
371 static size_t validateSize(size_t size)
372 {
373 if (size < 2 || (size & (size - 1)) != 0)
374 {
375#if defined(DSPARK_NO_EXCEPTIONS)
376 assert(false && "FFTComplex size must be a power of two >= 2");
377 return 2; // degrade to the minimal valid size
378#else
379 throw std::invalid_argument("FFTComplex size must be a power of two >= 2");
380#endif
381 }
382 return size;
383 }
384
385 void transform(T* data, bool inverse) noexcept
386 {
387 constexpr int W = detail::fft::kWide<T>;
388 using O = detail::fft::Vec<T, W>;
389 using O1 = detail::fft::Vec<T, 1>;
390 const size_t n = engine_.size();
391 T* re = engine_.inRe();
392 T* im = engine_.inIm();
393
394 // Split the interleaved input; the inverse conjugates on the way in.
395 size_t i = 0;
396 for (; i + W <= n; i += W)
397 {
398 typename O::V r, m;
399 O::loadDeinterleave(data + 2 * i, r, m);
400 O::store(re + i, r);
401 O::store(im + i, inverse ? O::sub(O::set1(T(0)), m) : m);
402 }
403 for (; i < n; ++i)
404 {
405 re[i] = data[2 * i];
406 im[i] = inverse ? -data[2 * i + 1] : data[2 * i + 1];
407 }
408
409 const T* outRe = nullptr;
410 const T* outIm = nullptr;
411 engine_.run(outRe, outIm);
412
413 // Interleave back; the inverse conjugates again and scales by 1/N.
414 const T scale = inverse ? T(1) / static_cast<T>(n) : T(1);
415 const T imScale = inverse ? -scale : scale;
416 const auto vs = O::set1(scale), vis = O::set1(imScale);
417 i = 0;
418 for (; i + W <= n; i += W)
419 O::storeInterleave2(data + 2 * i, O::mul(O::load(outRe + i), vs),
420 O::mul(O::load(outIm + i), vis));
421 for (; i < n; ++i)
422 O1::storeInterleave2(data + 2 * i, outRe[i] * scale, outIm[i] * imScale);
423 }
424
425 detail::fft::SplitFFT<T> engine_;
426};
427
428// ============================================================================
429// FFTReal
430// ============================================================================
431
450template <typename T>
452{
453public:
460 explicit FFTReal(size_t size)
461 : realSize_(validateSize(size)) // validates BEFORE the engine is built
462 , halfSize_(realSize_ / 2) // derived from the VALIDATED size, so the
463 , engine_(realSize_ / 2) // trio stays coherent when size degrades
464 {
465 computePostTwiddles();
466 }
467
469 [[nodiscard]] size_t getSize() const noexcept { return realSize_; }
470
472 [[nodiscard]] size_t getFrequencyDomainSize() const noexcept { return realSize_ + 2; }
473
475 [[nodiscard]] size_t getNumBins() const noexcept { return halfSize_ + 1; }
476
482 void forward(const T* timeData, T* freqData) noexcept
483 {
484 constexpr int W = detail::fft::kWide<T>;
485 using O = detail::fft::Vec<T, W>;
486 const size_t m = halfSize_;
487 T* re = engine_.inRe();
488 T* im = engine_.inIm();
489
490 // (even, odd) sample pairs are the complex input: a deinterleave.
491 size_t i = 0;
492 for (; i + W <= m; i += W)
493 {
494 typename O::V r, q;
495 O::loadDeinterleave(timeData + 2 * i, r, q);
496 O::store(re + i, r);
497 O::store(im + i, q);
498 }
499 for (; i < m; ++i)
500 {
501 re[i] = timeData[2 * i];
502 im[i] = timeData[2 * i + 1];
503 }
504
505 const T* zr = nullptr;
506 const T* zi = nullptr;
507 engine_.run(zr, zi);
508 unpackForward(zr, zi, freqData);
509 }
510
516 void inverse(const T* freqData, T* timeData) noexcept
517 {
518 constexpr int W = detail::fft::kWide<T>;
519 using O = detail::fft::Vec<T, W>;
520 const size_t m = halfSize_;
521
522 // Pack into the conjugated half-size spectrum, run the forward engine,
523 // conjugate back and scale by 1/(N/2): ifft = conj(fft(conj)) / M.
524 packInverseConj(freqData, engine_.inRe(), engine_.inIm());
525 const T* zr = nullptr;
526 const T* zi = nullptr;
527 engine_.run(zr, zi);
528
529 const T scale = T(1) / static_cast<T>(m);
530 const auto vs = O::set1(scale), vis = O::set1(-scale);
531 size_t i = 0;
532 for (; i + W <= m; i += W)
533 O::storeInterleave2(timeData + 2 * i, O::mul(O::load(zr + i), vs),
534 O::mul(O::load(zi + i), vis));
535 for (; i < m; ++i)
536 {
537 timeData[2 * i] = zr[i] * scale;
538 timeData[2 * i + 1] = -zi[i] * scale;
539 }
540 }
541
547 void computeMagnitudes(const T* freqData, T* magnitudes) const noexcept
548 {
549 for (size_t k = 0; k <= halfSize_; ++k)
550 {
551 T re = freqData[2 * k];
552 T im = freqData[2 * k + 1];
553 magnitudes[k] = std::sqrt(re * re + im * im);
554 }
555 }
556
562 void computePhases(const T* freqData, T* phases) const noexcept
563 {
564 for (size_t k = 0; k <= halfSize_; ++k)
565 {
566 T re = freqData[2 * k];
567 T im = freqData[2 * k + 1];
568 phases[k] = std::atan2(im, re);
569 }
570 }
571
577 void computePowerSpectrum(const T* freqData, T* power) const noexcept
578 {
579 for (size_t k = 0; k <= halfSize_; ++k)
580 {
581 T re = freqData[2 * k];
582 T im = freqData[2 * k + 1];
583 power[k] = re * re + im * im;
584 }
585 }
586
594 [[nodiscard]] static T binToFrequency(size_t binIndex, double sampleRate, size_t fftSize) noexcept
595 {
596 return static_cast<T>(static_cast<double>(binIndex) * sampleRate / static_cast<double>(fftSize));
597 }
598
606 [[nodiscard]] static size_t frequencyToBin(double frequency, double sampleRate, size_t fftSize) noexcept
607 {
608 return static_cast<size_t>(std::round(frequency * static_cast<double>(fftSize) / sampleRate));
609 }
610
611private:
616 static size_t validateSize(size_t size)
617 {
618 if (size < 4 || (size & (size - 1)) != 0)
619 {
620#if defined(DSPARK_NO_EXCEPTIONS)
621 assert(false && "FFTReal size must be a power of two >= 4");
622 return 4;
623#else
624 throw std::invalid_argument("FFTReal size must be a power of two >= 4");
625#endif
626 }
627 return size;
628 }
629
630 void computePostTwiddles()
631 {
632 twRe_.resize(halfSize_);
633 twIm_.resize(halfSize_);
634 for (size_t k = 0; k < halfSize_; ++k)
635 {
636 const double angle = -2.0 * std::numbers::pi_v<double> * static_cast<double>(k)
637 / static_cast<double>(realSize_);
638 twRe_[k] = static_cast<T>(std::cos(angle));
639 twIm_[k] = static_cast<T>(std::sin(angle));
640 }
641 }
642
647 void unpackForward(const T* zr, const T* zi, T* out) const noexcept
648 {
649 constexpr int W = detail::fft::kWide<T>;
650 using O = detail::fft::Vec<T, W>;
651 const size_t m = halfSize_;
652
653 const T dc = zr[0] + zi[0];
654 const T ny = zr[0] - zi[0];
655
656 const auto half = O::set1(T(0.5));
657 size_t k = 1;
658 for (; k + W <= m; k += W) // k .. k+W-1 against M-k .. M-k-W+1
659 {
660 const auto hkR = O::load(zr + k), hkI = O::load(zi + k);
661 const auto hcR = O::reverse(O::load(zr + (m - k - (W - 1))));
662 const auto hcI = O::reverse(O::load(zi + (m - k - (W - 1))));
663 const auto xeR = O::mul(half, O::add(hkR, hcR));
664 const auto xeI = O::mul(half, O::sub(hkI, hcI));
665 const auto xoR = O::mul(half, O::sub(hkR, hcR));
666 const auto xoI = O::mul(half, O::add(hkI, hcI));
667 // -j * xo = (xoI, -xoR), times w = (wr, wi)
668 const auto wr = O::load(twRe_.data() + k), wi = O::load(twIm_.data() + k);
669 const auto tR = O::mulAdd(wr, xoI, wi, xoR); // wr*xoI - wi*(-xoR)
670 const auto tI = O::mulSub(wi, xoI, wr, xoR); // wr*(-xoR) + wi*xoI
671 O::storeInterleave2(out + 2 * k, O::add(xeR, tR), O::add(xeI, tI));
672 }
673 for (; k < m; ++k)
674 {
675 const size_t c = m - k;
676 const T xeR = T(0.5) * (zr[k] + zr[c]);
677 const T xeI = T(0.5) * (zi[k] - zi[c]);
678 const T xoR = T(0.5) * (zr[k] - zr[c]);
679 const T xoI = T(0.5) * (zi[k] + zi[c]);
680 const T wr = twRe_[k], wi = twIm_[k];
681 out[2 * k] = xeR + (wr * xoI + wi * xoR);
682 out[2 * k + 1] = xeI + (wi * xoI - wr * xoR);
683 }
684 // Written last: in-place use shares the buffer with the input pairs,
685 // which the engine has already consumed.
686 out[0] = dc;
687 out[1] = T(0);
688 out[2 * m] = ny;
689 out[2 * m + 1] = T(0);
690 }
691
696 void packInverseConj(const T* in, T* zr, T* zi) const noexcept
697 {
698 constexpr int W = detail::fft::kWide<T>;
699 using O = detail::fft::Vec<T, W>;
700 const size_t m = halfSize_;
701
702 const T dc = in[0];
703 const T ny = in[2 * m];
704 zr[0] = T(0.5) * (dc + ny);
705 zi[0] = -T(0.5) * (dc - ny);
706
707 const auto half = O::set1(T(0.5));
708 const auto zero = O::set1(T(0));
709 size_t k = 1;
710 for (; k + W <= m; k += W)
711 {
712 typename O::V xkR, xkI, xcR, xcI;
713 O::loadDeinterleave(in + 2 * k, xkR, xkI);
714 O::loadDeinterleave(in + 2 * (m - k - (W - 1)), xcR, xcI);
715 xcR = O::reverse(xcR);
716 xcI = O::reverse(xcI);
717 const auto xeR = O::mul(half, O::add(xkR, xcR));
718 const auto xeI = O::mul(half, O::sub(xkI, xcI));
719 const auto dR = O::mul(half, O::sub(xkR, xcR));
720 const auto dI = O::mul(half, O::add(xkI, xcI));
721 // conj(w) * d, then j * that: (-(tI), tR)
722 const auto wr = O::load(twRe_.data() + k), wi = O::load(twIm_.data() + k);
723 const auto tR = O::mulAdd(wr, dR, wi, dI); // wr*dR - (-wi)*dI
724 const auto tI = O::mulSub(wr, dI, wi, dR); // wr*dI + (-wi)*dR
725 O::store(zr + k, O::sub(xeR, tI));
726 O::store(zi + k, O::sub(zero, O::add(xeI, tR))); // conjugated
727 }
728 for (; k < m; ++k)
729 {
730 const size_t c = m - k;
731 const T xeR = T(0.5) * (in[2 * k] + in[2 * c]);
732 const T xeI = T(0.5) * (in[2 * k + 1] - in[2 * c + 1]);
733 const T dR = T(0.5) * (in[2 * k] - in[2 * c]);
734 const T dI = T(0.5) * (in[2 * k + 1] + in[2 * c + 1]);
735 const T wr = twRe_[k], wi = twIm_[k];
736 const T tR = wr * dR + wi * dI;
737 const T tI = wr * dI - wi * dR;
738 zr[k] = xeR - tI;
739 zi[k] = -(xeI + tR);
740 }
741 }
742
743 size_t realSize_;
744 size_t halfSize_;
745 detail::fft::SplitFFT<T> engine_;
746 std::vector<T> twRe_, twIm_;
747};
748
749} // namespace dspark
Complex FFT on interleaved data (Stockham radix-4 engine, SIMD).
Definition FFT.h:337
FFTComplex(size_t size)
Constructs an FFT processor for the given size.
Definition FFT.h:345
size_t getSize() const noexcept
Returns the FFT size (number of complex points).
Definition FFT.h:354
void forward(T *data) noexcept
Performs a forward (time->frequency) FFT in-place.
Definition FFT.h:361
void inverse(T *data) noexcept
Performs an inverse (frequency->time) FFT in-place.
Definition FFT.h:368
FFT optimised for real-valued input signals (the common audio case).
Definition FFT.h:452
static size_t frequencyToBin(double frequency, double sampleRate, size_t fftSize) noexcept
Returns the bin index closest to a given frequency.
Definition FFT.h:606
void computePhases(const T *freqData, T *phases) const noexcept
Computes the phase angle of each frequency bin.
Definition FFT.h:562
size_t getNumBins() const noexcept
Returns the number of frequency bins (N/2 + 1) including DC and Nyquist.
Definition FFT.h:475
void computePowerSpectrum(const T *freqData, T *power) const noexcept
Computes the power spectrum (magnitude squared) of each bin.
Definition FFT.h:577
void forward(const T *timeData, T *freqData) noexcept
Forward transform: real time-domain -> complex frequency-domain.
Definition FFT.h:482
FFTReal(size_t size)
Constructs a real FFT processor.
Definition FFT.h:460
void inverse(const T *freqData, T *timeData) noexcept
Inverse transform: complex frequency-domain -> real time-domain.
Definition FFT.h:516
size_t getFrequencyDomainSize() const noexcept
Returns the frequency-domain buffer size in elements (N + 2).
Definition FFT.h:472
static T binToFrequency(size_t binIndex, double sampleRate, size_t fftSize) noexcept
Returns the frequency in Hz corresponding to a given bin index.
Definition FFT.h:594
size_t getSize() const noexcept
Returns the number of real input samples (N).
Definition FFT.h:469
void computeMagnitudes(const T *freqData, T *magnitudes) const noexcept
Computes the magnitude of each frequency bin.
Definition FFT.h:547
The shared Stockham engine: a forward complex FFT of size N on split (re, im) arrays,...
Definition FFT.h:214
T * inRe() noexcept
Input buffers: fill inRe() / inIm() with N values each before run().
Definition FFT.h:257
void run(const T *&outRe, const T *&outIm) noexcept
Runs the forward transform of the input buffers.
Definition FFT.h:265
T * inIm() noexcept
Definition FFT.h:258
size_t size() const noexcept
Definition FFT.h:254
void radix4Strided(const T *xr, const T *xi, T *yr, T *yi, size_t n, size_t s, const TwiddleSpan< T > &tw) noexcept
One Stockham radix-4 DIF pass with stride s >= W, vectorised over the stride (twiddles broadcast per ...
Definition FFT.h:106
void radix2Last(const T *xr, const T *xi, T *yr, T *yi, size_t s) noexcept
The closing radix-2 pass (n = 2, stride s = N / 2): no twiddles.
Definition FFT.h:193
constexpr int kNarrow
Definition FFT.h:83
constexpr int kWide
Definition FFT.h:82
void radix4First(const T *xr, const T *xi, T *yr, T *yi, size_t n, const TwiddleSpan< T > &tw) noexcept
The first (stride-1) radix-4 pass, vectorised across W butterflies (n/4 must be a multiple of W): the...
Definition FFT.h:158
Main namespace for the DSPark framework.