177 static_assert(std::is_floating_point_v<SampleType>,
178 "FftT requires a floating-point sample type");
187 : size_(other.size_), twiddles_(std::move(other.twiddles_)),
188 real_scratch_(std::move(other.real_scratch_))
191 log2n_(other.log2n_), vdsp_setup_(other.vdsp_setup_), split_(std::move(other.split_)),
192 split_base_(other.split_base_)
194#if PULP_FFT_USE_MUFFT
196 mufft_(other.mufft_), mufft_alignment_(other.mufft_alignment_)
200 other.vdsp_setup_ =
nullptr;
202#if PULP_FFT_USE_MUFFT
203 other.mufft_ =
nullptr;
209 if (
this != &other) {
211 if (vdsp_setup_) vDSP_destroy_fftsetup(vdsp_setup_);
212 vdsp_setup_ = other.vdsp_setup_;
213 other.vdsp_setup_ =
nullptr;
214 log2n_ = other.log2n_;
215 split_ = std::move(other.split_);
216 split_base_ = other.split_base_;
218#if PULP_FFT_USE_MUFFT
220 pulp_mufft_destroy(mufft_);
221 mufft_ = other.mufft_;
222 mufft_alignment_ = other.mufft_alignment_;
223 other.mufft_ =
nullptr;
227 twiddles_ = std::move(other.twiddles_);
228 real_scratch_ = std::move(other.real_scratch_);
244 if constexpr (std::is_same_v<SampleType, float>) {
246 for (
int n =
size; n > 1; n >>= 1) ++log2n_;
250 const auto address =
reinterpret_cast<std::uintptr_t
>(split_.data());
251 split_base_ = ((64u - address % 64u) % 64u) /
sizeof(
float);
255 twiddles_.resize(
size / 2);
256 for (
int i = 0;
i <
size / 2; ++
i) {
257 double angle = -2.0 * std::numbers::pi *
i /
size;
258 twiddles_[
i] = {std::cos(angle), std::sin(angle)};
262 real_scratch_.resize(
static_cast<std::size_t
>(
size / 2 + 1));
264 if constexpr (std::is_same_v<SampleType, float>) {
268 vdsp_setup_ = vDSP_create_fftsetup(log2n_, kFFTRadix2);
271#if PULP_FFT_USE_MUFFT
274 if constexpr (std::is_same_v<SampleType, float>) {
275 mufft_alignment_ = pulp_mufft_alignment();
276 mufft_ = pulp_mufft_create(
size);
283 if (vdsp_setup_) vDSP_destroy_fftsetup(vdsp_setup_);
285#if PULP_FFT_USE_MUFFT
287 pulp_mufft_destroy(mufft_);
291 int size()
const {
return size_; }
296 [[nodiscard]]
bool ready() const noexcept {
297 if (size_ <= 0 || twiddles_.size() !=
static_cast<std::size_t
>(size_ / 2))
300 real_scratch_.size() !=
static_cast<std::size_t
>(size_ / 2 + 1))
303 if constexpr (std::is_same_v<SampleType, float>) {
304 return vdsp_setup_ !=
nullptr &&
317 if constexpr (std::is_same_v<SampleType, float>) {
320 const DSPSplitComplex z = real_transform ? real_split() : split();
321 const int half = real_transform ? size_ / 2 : size_;
322 return static_cast<int>(z.imagp - z.realp) - half;
325 static_cast<void>(real_transform);
336 if constexpr (std::is_same_v<SampleType, float>) {
339 const DSPSplitComplex z = real_transform ? real_split() : split();
340 const auto re =
static_cast<int>(
reinterpret_cast<std::uintptr_t
>(z.realp) % 64u);
341 const auto im =
static_cast<int>(
reinterpret_cast<std::uintptr_t
>(z.imagp) % 64u);
342 return re == im ? re : -2;
345 static_cast<void>(real_transform);
350 void forward(std::complex<SampleType>* data)
const {
352#if PULP_FFT_USE_MUFFT
353 if constexpr (std::is_same_v<SampleType, float>) {
355 complex_mufft(data,
true, 1.0f);
361 if constexpr (std::is_same_v<SampleType, float>) {
364 forward_fallback(data);
367 forward_fallback(data);
372 void inverse(std::complex<SampleType>* data)
const {
374#if PULP_FFT_USE_MUFFT
375 if constexpr (std::is_same_v<SampleType, float>) {
377 complex_mufft(data,
false, 1.0f /
static_cast<float>(size_));
383 if constexpr (std::is_same_v<SampleType, float>) {
386 inverse_fallback(data);
389 inverse_fallback(data);
394 void forward_real(
const SampleType* input, std::complex<SampleType>* output)
const {
396#if PULP_FFT_USE_MUFFT
397 if constexpr (std::is_same_v<SampleType, float>) {
399 forward_real_full_mufft(input, output);
405 if constexpr (std::is_same_v<SampleType, float>) {
406 if (size_ >= kVdspMinRealSize) {
407 forward_real_vdsp(input, output);
410 forward_real_fallback(input, output);
412 forward_real_fallback(input, output);
415 forward_real_fallback(input, output);
424 return size_ / 2 + 1;
434 SampleType scale)
const {
436 assert(size_ >=
kMinRealSize &&
"real transforms need at least two points");
437#if PULP_FFT_USE_MUFFT
438 if constexpr (std::is_same_v<SampleType, float>) {
440 forward_real_half_mufft(input, output, scale);
446 if constexpr (std::is_same_v<SampleType, float>) {
447 if (size_ >= kVdspMinRealSize) {
448 forward_real_half_vdsp(input, output, scale);
453 forward_real_half_fallback(input, output, scale);
470 SampleType scale)
const {
472 assert(size_ >=
kMinRealSize &&
"real transforms need at least two points");
473#if PULP_FFT_USE_MUFFT
474 if constexpr (std::is_same_v<SampleType, float>) {
476 inverse_real_half_mufft(input, output, scale);
482 if constexpr (std::is_same_v<SampleType, float>) {
483 if (size_ >= kVdspMinRealSize) {
484 inverse_real_half_vdsp(input, output, scale);
489 inverse_real_half_fallback(input, output, scale);
493 inverse_real_half(input, output, SampleType{1} /
static_cast<SampleType
>(size_));
499 int num_bins)
const {
500 for (
int i = 0;
i < num_bins; ++
i) {
501 SampleType mag = std::abs(freq[
i]);
502 out[
i] = SampleType{20.0f} *
503 std::log10(std::max(mag, SampleType{1e-10f}));
510 int num_bins)
const {
511 for (
int i = 0;
i < num_bins; ++
i) {
512 out[
i] = std::abs(freq[
i]);
525 void forward_real_split(
const SampleType* input, SampleType* re, SampleType* im,
526 SampleType scale)
const {
528 assert(size_ >=
kMinRealSize &&
"real transforms need at least two points");
529#if PULP_FFT_USE_MUFFT
530 if constexpr (std::is_same_v<SampleType, float>) {
532 forward_real_split_mufft(input, re, im, scale);
538 if constexpr (std::is_same_v<SampleType, float>) {
539 if (size_ >= kVdspMinRealSize) {
540 forward_real_split_vdsp(input, re, im, scale);
545 forward_real_split_fallback(input, re, im, scale);
553 void inverse_real_split(
const SampleType* re,
const SampleType* im, SampleType* output,
554 SampleType scale)
const {
556 assert(size_ >=
kMinRealSize &&
"real transforms need at least two points");
557#if PULP_FFT_USE_MUFFT
558 if constexpr (std::is_same_v<SampleType, float>) {
560 inverse_real_split_mufft(re, im, output, scale);
566 if constexpr (std::is_same_v<SampleType, float>) {
567 if (size_ >= kVdspMinRealSize) {
568 inverse_real_split_vdsp(re, im, output, scale);
573 inverse_real_split_fallback(re, im, output, scale);
577 std::vector<std::complex<double>> twiddles_;
580 mutable std::vector<std::complex<SampleType>> real_scratch_;
581#if PULP_FFT_USE_MUFFT
584 pulp_mufft_plan* mufft_ =
nullptr;
585 std::size_t mufft_alignment_ = 1;
589 FFTSetup vdsp_setup_ =
nullptr;
631 mutable std::vector<float> split_;
632 std::size_t split_base_ = 0;
633 static constexpr std::size_t kVdspSplitOffsetFloats = 4;
634 static constexpr std::size_t kVdspSplitPlaneGapFloats = 64;
635 static_assert(kVdspSplitPlaneGapFloats % (64 /
sizeof(float)) == 0,
636 "the plane gap must be whole 64-byte lines to keep both halves' offset");
638 64 /
sizeof(float) - 1 + kVdspSplitOffsetFloats + kVdspSplitPlaneGapFloats,
639 "split padding must cover the 64-byte alignment slack, the 16-byte step "
640 "and the gap between the halves");
647 DSPSplitComplex split()
const {
648 float* base = split_.data() + split_base_ + kVdspSplitOffsetFloats;
649 return {base, base + size_ + kVdspSplitPlaneGapFloats};
653 DSPSplitComplex real_split()
const {
654 float* base = split_.data() + split_base_ + kVdspSplitOffsetFloats;
655 return {base, base + size_ / 2 + kVdspSplitPlaneGapFloats};
658 void forward_vdsp(std::complex<float>* data)
const {
659 DSPSplitComplex
z = split();
660 const auto n =
static_cast<vDSP_Length
>(size_);
661 vDSP_ctoz(
reinterpret_cast<const DSPComplex*
>(data), 2, &z, 1, n);
662 vDSP_fft_zip(vdsp_setup_, &z, 1, log2n_, kFFTDirection_Forward);
663 vDSP_ztoc(&z, 1,
reinterpret_cast<DSPComplex*
>(data), 2, n);
666 void inverse_vdsp(std::complex<float>* data)
const {
667 DSPSplitComplex
z = split();
668 vDSP_ctoz(
reinterpret_cast<const DSPComplex*
>(data), 2, &z, 1,
669 static_cast<vDSP_Length
>(size_));
670 vDSP_fft_zip(vdsp_setup_, &z, 1, log2n_, kFFTDirection_Inverse);
672 interleave_scaled(
z.realp,
z.imagp,
reinterpret_cast<float*
>(data), size_,
673 1.0f /
static_cast<float>(size_));
678 static void interleave_scaled(
const float* re,
const float* im,
float* out,
int count,
680 for (
int k = 0;
k <
count; ++
k) {
682 out[2 *
k + 1] =
scale * im[
k];
689 void forward_real_packed_vdsp(
const float* input)
const {
690 DSPSplitComplex
z = real_split();
691 vDSP_ctoz(
reinterpret_cast<const DSPComplex*
>(input), 2, &z, 1,
692 static_cast<vDSP_Length
>(size_ / 2));
693 vDSP_fft_zrip(vdsp_setup_, &z, 1, log2n_, kFFTDirection_Forward);
696 void forward_real_half_vdsp(
const float* input, std::complex<float>* output,
698 forward_real_packed_vdsp(input);
699 const int half = size_ / 2;
700 const float s = 0.5f *
scale;
701 auto* out =
reinterpret_cast<float*
>(output);
702 const DSPSplitComplex
z = real_split();
703 interleave_scaled(
z.realp,
z.imagp, out, half, s);
704 out[2 *
half] =
s *
z.imagp[0];
705 out[2 *
half + 1] = 0.0f;
709 void inverse_real_half_vdsp(
const std::complex<float>* input,
float* output,
711 const int half = size_ / 2;
712 const auto* in =
reinterpret_cast<const float*
>(input);
713 DSPSplitComplex
z = real_split();
716 for (
int k = 0;
k <
half; ++
k) {
718 im[
k] = in[2 *
k + 1];
720 im[0] = in[2 *
half];
721 vDSP_fft_zrip(vdsp_setup_, &z, 1, log2n_, kFFTDirection_Inverse);
724 interleave_scaled(re, im, output, half, scale);
727 void forward_real_vdsp(
const float* input, std::complex<float>* output)
const {
728 forward_real_packed_vdsp(input);
731 const int half = size_ / 2;
732 const DSPSplitComplex
z = real_split();
733 output[0] = {
z.realp[0] * 0.5f, 0.0f};
734 output[
half] = {
z.imagp[0] * 0.5f, 0.0f};
735 for (
int i = 1;
i <
half; ++
i) {
736 const float re =
z.realp[
i] * 0.5f;
737 const float im =
z.imagp[
i] * 0.5f;
738 output[
i] = {re, im};
740 output[size_ -
i] = {re, -im};
746 void forward_real_split_vdsp(
const float* input,
float* re,
float* im,
float scale)
const {
747 forward_real_packed_vdsp(input);
748 const DSPSplitComplex
z = real_split();
749 const float s = 0.5f *
scale;
750 const auto half =
static_cast<vDSP_Length
>(size_ / 2);
751 vDSP_vsmul(
z.realp, 1, &s, re, 1, half);
752 vDSP_vsmul(
z.imagp, 1, &s, im, 1, half);
755 void inverse_real_split_vdsp(
const float* re,
const float* im,
float* output,
757 const int half = size_ / 2;
758 DSPSplitComplex
z = real_split();
759 std::copy_n(re, half,
z.realp);
760 std::copy_n(im, half,
z.imagp);
761 vDSP_fft_zrip(vdsp_setup_, &z, 1, log2n_, kFFTDirection_Inverse);
763 vDSP_ztoc(&z, 1,
reinterpret_cast<DSPComplex*
>(output), 2,
764 static_cast<vDSP_Length
>(half));
766 interleave_scaled(
z.realp,
z.imagp, output, half, scale);
770#if PULP_FFT_USE_MUFFT
776 bool mufft_aligned(
const void* p)
const noexcept {
777 return reinterpret_cast<std::uintptr_t
>(
p) % mufft_alignment_ == 0;
780 static bool mufft_disjoint(
const float*
a, std::size_t a_count,
const float* b,
781 std::size_t b_count)
noexcept {
782 const auto a0 =
reinterpret_cast<std::uintptr_t
>(
a);
783 const auto b0 =
reinterpret_cast<std::uintptr_t
>(
b);
784 return a0 + a_count *
sizeof(float) <= b0 || b0 + b_count *
sizeof(
float) <= a0;
787 static void mufft_scale(
float* data, std::size_t count,
float scale)
noexcept {
789 for (std::size_t
i = 0;
i <
count; ++
i)
795 void complex_mufft(std::complex<float>* data,
bool forward,
float scale)
const {
796 const auto floats = 2 *
static_cast<std::size_t
>(size_);
797 auto* dst =
reinterpret_cast<float*
>(data);
798 float* in = pulp_mufft_in(mufft_);
799 std::memcpy(in, dst, floats *
sizeof(
float));
800 if (mufft_aligned(dst)) {
801 pulp_mufft_complex(mufft_,
forward ? 1 : 0, dst, in);
802 mufft_scale(dst, floats, scale);
805 const float* out = pulp_mufft_out(mufft_);
806 pulp_mufft_complex(mufft_,
forward ? 1 : 0, pulp_mufft_out(mufft_), in);
807 for (std::size_t
i = 0;
i < floats; ++
i)
808 dst[
i] = scale * out[
i];
812 const float* mufft_real_input(
const float* input)
const {
813 if (mufft_aligned(input))
815 float* in = pulp_mufft_in(mufft_);
816 std::memcpy(in, input,
static_cast<std::size_t
>(size_) *
sizeof(
float));
823 const float* forward_real_bins_mufft(
const float* input,
float* out)
const {
824 const float* in = mufft_real_input(input);
825 const auto bins = 2 *
static_cast<std::size_t
>(size_ / 2 + 1);
826 float* dst = (out && mufft_aligned(out) &&
827 mufft_disjoint(out, bins, in,
static_cast<std::size_t
>(size_)))
829 : pulp_mufft_out(mufft_);
830 pulp_mufft_real_forward(mufft_, dst, in);
834 void forward_real_full_mufft(
const float* input, std::complex<float>* output)
const {
835 auto* out =
reinterpret_cast<float*
>(output);
836 const float* bins = forward_real_bins_mufft(input, out);
837 const int half = size_ / 2;
839 std::memcpy(out, bins, 2 *
static_cast<std::size_t
>(half + 1) *
sizeof(
float));
841 out[2 *
half + 1] = 0.0f;
842 for (
int k = 1;
k <
half; ++
k)
843 output[size_ - k] = std::conj(output[k]);
846 void forward_real_half_mufft(
const float* input, std::complex<float>* output,
848 auto* out =
reinterpret_cast<float*
>(output);
849 const float* bins = forward_real_bins_mufft(input, out);
850 const int half = size_ / 2;
851 const auto count = 2 *
static_cast<std::size_t
>(
half + 1);
853 mufft_scale(out, count, scale);
855 for (std::size_t
i = 0;
i <
count; ++
i)
856 out[
i] = scale * bins[
i];
859 out[2 *
half + 1] = 0.0f;
865 void inverse_real_from_mufft(
const float* in,
float* output,
float scale)
const {
866 const auto n =
static_cast<std::size_t
>(size_);
867 const auto bins = 2 *
static_cast<std::size_t
>(size_ / 2 + 1);
868 if (mufft_aligned(output) && mufft_disjoint(output, n, in, bins)) {
869 pulp_mufft_real_inverse(mufft_, output, in);
870 mufft_scale(output, n, scale);
873 const float* out = pulp_mufft_out(mufft_);
874 pulp_mufft_real_inverse(mufft_, pulp_mufft_out(mufft_), in);
875 for (std::size_t
i = 0;
i <
n; ++
i)
876 output[
i] = scale * out[
i];
879 void inverse_real_half_mufft(
const std::complex<float>* input,
float* output,
881 const int half = size_ / 2;
882 const auto* src =
reinterpret_cast<const float*
>(input);
885 if (mufft_aligned(src) && src[1] == 0.0f && src[2 * half + 1] == 0.0f) {
886 inverse_real_from_mufft(src, output, scale);
889 float* in = pulp_mufft_in(mufft_);
890 std::memcpy(in, src, 2 *
static_cast<std::size_t
>(half + 1) *
sizeof(
float));
892 in[2 *
half + 1] = 0.0f;
893 inverse_real_from_mufft(in, output, scale);
898 void forward_real_split_mufft(
const float* input,
float* re,
float* im,
float scale)
const {
899 const float* bins = forward_real_bins_mufft(input,
nullptr);
900 const int half = size_ / 2;
901 for (
int k = 0;
k <
half; ++
k) {
903 im[
k] =
scale * bins[2 *
k + 1];
908 void inverse_real_split_mufft(
const float* re,
const float* im,
float* output,
910 const int half = size_ / 2;
911 float* in = pulp_mufft_in(mufft_);
912 for (
int k = 0;
k <
half; ++
k) {
914 in[2 *
k + 1] = im[
k];
917 in[2 *
half] = im[0];
918 in[2 *
half + 1] = 0.0f;
919 inverse_real_from_mufft(in, output, scale);
923 void forward_fallback(std::complex<SampleType>* data)
const {
924 transform_fallback(data, size_);
929 void transform_fallback(std::complex<SampleType>* data,
int n)
const {
930 bit_reverse(data, n);
931 for (
int len = 2; len <=
n; len <<= 1) {
933 int step = size_ / len;
934 for (
int i = 0;
i <
n;
i += len) {
935 for (
int j = 0;
j <
half; ++
j) {
936 auto w = std::complex<SampleType>(
938 auto u = data[
i +
j];
947 void inverse_fallback(std::complex<SampleType>* data)
const {
948 for (
int i = 0;
i < size_; ++
i) data[
i] = std::conj(data[
i]);
949 forward_fallback(data);
950 SampleType
scale = SampleType{1.0f} /
static_cast<SampleType
>(size_);
951 for (
int i = 0;
i < size_; ++
i) data[
i] = std::conj(data[
i]) *
scale;
954 void forward_real_fallback(
const SampleType* input,
955 std::complex<SampleType>* output)
const {
957 output[0] = {input[0], SampleType{0}};
962 forward_real_half_fallback(input, output, SampleType{1});
963 for (
int i = 1;
i < size_ / 2; ++
i)
964 output[size_ -
i] = std::conj(output[
i]);
973 static constexpr int kVdspMinRealSize = 4;
975 void forward_real_half_fallback(
const SampleType* input, std::complex<SampleType>* output,
976 SampleType scale)
const {
978 const int half = size_ / 2;
979 for (
int k = 0;
k <
half; ++
k)
980 output[k] = {input[2 *
k], input[2 *
k + 1]};
981 transform_fallback(output, half);
982 const SampleType
h{0.5f};
983 const auto z0 = output[0];
984 for (
int k = 1;
k <=
half / 2; ++
k) {
985 const auto a = output[
k];
986 const auto b = std::conj(output[half - k]);
987 const std::complex<SampleType>
e =
h * (
a +
b);
988 const std::complex<SampleType>
d =
h * (
a -
b);
989 const std::complex<SampleType>
o{
d.imag(), -
d.real()};
990 const std::complex<SampleType>
w(twiddles_[
static_cast<std::size_t
>(k)]);
991 const auto wo =
w *
o;
996 output[0] = {
scale * (z0.real() + z0.imag()), SampleType{0}};
997 output[
half] = {
scale * (z0.real() - z0.imag()), SampleType{0}};
1005 void inverse_real_half_fallback(
const std::complex<SampleType>* input, SampleType* output,
1006 SampleType scale)
const {
1008 const int half = size_ / 2;
1009 const SampleType nyquist = input[
half].real();
1010 const SampleType dc = input[0].real();
1014 auto*
z =
reinterpret_cast<std::complex<SampleType>*
>(output);
1016 for (
int k = 1;
k <=
half / 2; ++
k) {
1017 const auto xk = input[
k];
1018 const auto xm = input[
half -
k];
1019 const std::complex<SampleType>
w(twiddles_[
static_cast<std::size_t
>(k)]);
1022 const auto ek = xk + std::conj(xm);
1023 const auto ok = (xk - std::conj(xm)) * std::conj(w);
1024 const auto em = xm + std::conj(xk);
1025 const auto om = (xm - std::conj(xk)) * (-w);
1026 const std::complex<SampleType>
i{SampleType{0}, SampleType{1}};
1027 z[
k] = std::conj(scale * (ek +
i *
ok));
1029 z[
half -
k] = std::conj(scale * (em +
i * om));
1031 z[0] = std::conj(std::complex<SampleType>(scale * (dc + nyquist), scale * (dc - nyquist)));
1032 transform_fallback(z, half);
1033 for (
int k = 0;
k <
half; ++
k)
1034 z[k] = std::conj(z[k]);
1037 void forward_real_split_fallback(
const SampleType* input, SampleType* re, SampleType* im,
1038 SampleType scale)
const {
1039 std::complex<SampleType>* bins = real_scratch_.data();
1040 forward_real_half_fallback(input, bins, scale);
1041 const int half = size_ / 2;
1042 for (
int k = 0;
k <
half; ++
k) {
1043 re[
k] = bins[
k].real();
1044 im[
k] = bins[
k].imag();
1046 im[0] = bins[
half].real();
1049 void inverse_real_split_fallback(
const SampleType* re,
const SampleType* im, SampleType* output,
1050 SampleType scale)
const {
1051 std::complex<SampleType>* bins = real_scratch_.data();
1052 const int half = size_ / 2;
1053 for (
int k = 0;
k <
half; ++
k)
1054 bins[k] = {re[
k], im[
k]};
1055 bins[0] = {re[0], SampleType{0}};
1056 bins[
half] = {im[0], SampleType{0}};
1057 inverse_real_half_fallback(bins, output, scale);
1060 void bit_reverse(std::complex<SampleType>* data)
const {
1061 bit_reverse(data, size_);
1064 void bit_reverse(std::complex<SampleType>* data,
int count)
const {
1066 for (
int n = count;
n > 1;
n >>= 1)
1071 for (
int b = 0;
b < bits; ++
b)
1072 if (
i & (1 << b))
j |= 1 << (bits - 1 -
b);
1073 if (
i < j) std::swap(data[
i], data[j]);