Aleph-w 3.0
A C++ Library for Data Structures and Algorithms
Loading...
Searching...
No Matches
fft.H
Go to the documentation of this file.
1/*
2 Aleph_w
3
4 Data structures & Algorithms
5 version 2.0.0b
6 https://github.com/lrleon/Aleph-w
7
8 This file is part of Aleph-w library
9
10 Copyright (c) 2002-2026 Leandro Rabindranath Leon
11
12 Permission is hereby granted, free of charge, to any person obtaining a copy
13 of this software and associated documentation files (the "Software"), to deal
14 in the Software without restriction, including without limitation the rights
15 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
16 copies of the Software, and to permit persons to whom the Software is
17 furnished to do so, subject to the following conditions:
18
19 The above copyright notice and this permission notice shall be included in all
20 copies or substantial portions of the Software.
21
22 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
23 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
24 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
25 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
26 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
27 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
28 SOFTWARE.
29*/
30
60# ifndef FFT_H
61# define FFT_H
62
63# include <algorithm>
64# include <cmath>
65# include <complex>
66# include <concepts>
67# include <cstdlib>
68# include <iterator>
69# include <limits>
70# include <memory>
71# include <numeric>
72# include <numbers>
73# include <string_view>
74# include <type_traits>
75
76# if (defined(__GNUC__) or defined(__clang__)) \
77 and (defined(__x86_64__) or defined(__i386__) \
78 or defined(_M_X64) or defined(_M_IX86)) \
79 and !defined(_WIN32)
80 // On Windows (MSVC/clang-cl), __builtin_cpu_supports() requires
81 // compiler-rt's __cpu_model/__cpu_indicator_init which are not linked
82 // by default. Disable AVX2 dispatch and fall back to the generic path.
83# include <immintrin.h>
84# define ALEPH_FFT_HAS_X86_AVX2_DISPATCH 1
85# define ALEPH_FFT_AVX2_TARGET __attribute__((target("avx2")))
86# else
87# define ALEPH_FFT_HAS_X86_AVX2_DISPATCH 0
88# endif
89
90# if (defined(__GNUC__) or defined(__clang__)) \
91 and (defined(__aarch64__) or defined(__arm__) or defined(_M_ARM64) \
92 or defined(_M_ARM))
93# if defined(__ARM_NEON) or defined(__ARM_NEON__)
94# include <arm_neon.h>
95# define ALEPH_FFT_HAS_ARM_NEON_DISPATCH 1
96# else
97# define ALEPH_FFT_HAS_ARM_NEON_DISPATCH 0
98# endif
99# else
100# define ALEPH_FFT_HAS_ARM_NEON_DISPATCH 0
101# endif
102
103# if ALEPH_FFT_HAS_ARM_NEON_DISPATCH and defined(__aarch64__)
104# define ALEPH_FFT_HAS_ARM_NEON_DOUBLE 1
105# else
106# define ALEPH_FFT_HAS_ARM_NEON_DOUBLE 0
107# endif
108
109# if ALEPH_FFT_HAS_ARM_NEON_DISPATCH and defined(__linux__)
110# include <sys/auxv.h>
111# include <asm/hwcap.h>
112# endif
113
114# include <ah-errors.H>
115# include <thread_pool.H>
116# include <tpl_array.H>
117
118namespace Aleph
119{
156 template <std::floating_point Real = double>
157 class FFT
158 {
159 public:
161 using Complex = std::complex<Real>;
162
168 class Plan;
169
171 enum class SimdBackend
172 {
173 scalar,
174 avx2,
175 neon
176 };
177
179 enum class SimdPreference
180 {
181 automatic,
183 avx2_only,
184 neon_only
185 };
186
195
200 {
207
210 numerator() const
211 {
212 return {b0, b1, b2};
213 }
214
218 {
219 return {a0, a1, a2};
220 }
221 };
222
225 {
228
230 magnitude() const
231 {
233 }
234
236 power() const
237 {
239 }
240
242 phase() const
243 {
245 }
246 };
247
250 {
251 Real radians = std::numeric_limits<Real>::infinity();
252 Real degrees = std::numeric_limits<Real>::infinity();
253 Real crossover_omega = std::numeric_limits<Real>::quiet_NaN();
254 bool found = false;
255 };
256
259 {
260 Real ratio = std::numeric_limits<Real>::infinity();
261 Real decibels = std::numeric_limits<Real>::infinity();
262 Real crossover_omega = std::numeric_limits<Real>::quiet_NaN();
263 bool found = false;
264 };
265
268 {
271 Real distance = std::numeric_limits<Real>::infinity();
272 bool has_zero = false;
273 bool has_pole = false;
274
275 [[nodiscard]] bool
276 is_cancellation(const Real tolerance) const noexcept
277 {
278 return has_zero and has_pole and distance <= tolerance;
279 }
280 };
281
284 {
285 size_t hop_size = 0;
286 bool pad_end = true;
287 bool centered = false;
288 size_t fft_size = 0;
289 bool validate_nola = false;
290 bool validate_cola = false;
291 };
292
295 {
296 size_t hop_size = 0;
297 size_t signal_length = 0;
298 bool centered = false;
299 bool validate_nola = true;
300 bool validate_cola = false;
301 };
302
305 {
306 size_t hop_size = 0;
307 size_t fft_size = 0;
308 bool pad_end = true;
309 bool detrend = true;
310 };
311
318
325
332
335 {
336 size_t taps_per_phase = 10;
338 };
339
346
349 {
352 };
353
354 private:
355 template <typename T>
356 struct Is_Std_Complex : std::false_type
357 {};
358
359 template <typename T>
360 struct Is_Std_Complex<std::complex<T>> : std::true_type
361 {};
362
363 template <typename Container>
364 static constexpr bool Is_Complex_Container = requires(const Container & c)
365 {
366 std::begin(c);
367 std::end(c);
368 requires Is_Std_Complex<std::remove_cvref_t<decltype(*std::begin(c))>>::value;
369 };
370
371 template <typename Container>
372 static constexpr bool Is_Real_Container = requires(const Container & c)
373 {
374 std::begin(c);
375 std::end(c);
376 requires std::convertible_to<decltype(*std::begin(c)), Real>;
377 requires not Is_Std_Complex<std::remove_cvref_t<decltype(*std::begin(c))>>::value;
378 };
379
380 template <typename Container>
381 static constexpr bool Is_Biquad_Container = requires(const Container & c)
382 {
383 std::begin(c);
384 std::end(c);
385 requires std::convertible_to<std::remove_cvref_t<decltype(*std::begin(c))>,
387 };
388
389 template <typename Container>
390 static constexpr bool Is_Real_Batch_Container = requires(const Container & c)
391 {
392 std::begin(c);
393 std::end(c);
394 requires Is_Real_Container<std::remove_cvref_t<decltype(*std::begin(c))>>;
395 };
396
397 static constexpr size_t twiddle_refresh_period = 32;
398
400 [[nodiscard]] static Complex
401 twiddle_at(const Real angle, const size_t index)
402 {
403 return std::polar(Real(1), angle * static_cast<Real>(index));
404 }
405
407 [[nodiscard]] static Real
408 transform_stages(const size_t n) noexcept
409 {
410 return std::max(Real(1),
411 std::log2(static_cast<Real>(std::max(n, size_t(2)))));
412 }
413
417 [[nodiscard]] static Real
418 real_projection_tolerance(const Complex & value, const size_t n) noexcept
419 {
420 const Real scale = std::abs(value.real()) + std::abs(value.imag()) + Real(1);
421 return Real(128) * transform_stages(n)
422 * std::numeric_limits<Real>::epsilon() * scale;
423 }
424
428 [[nodiscard]] static Real
429 hermitian_tolerance(const Complex & lhs, const Complex & rhs,
430 const size_t n) noexcept
431 {
432 const Real scale = std::abs(lhs.real()) + std::abs(lhs.imag())
433 + std::abs(rhs.real()) + std::abs(rhs.imag())
434 + Real(1);
435 return Real(256) * transform_stages(n)
436 * std::numeric_limits<Real>::epsilon() * scale;
437 }
438
442 static void
444 {
445 const size_t n = input.size();
447 << ctx << ": input must be non-empty";
448
449 ah_domain_error_if(std::abs(input[0].imag())
451 << ctx << ": coefficient 0 has non-negligible imaginary part "
452 << input[0].imag();
453
454 if (n == 1)
455 return;
456
457 const size_t half = n / 2;
458 if ((n & 1) == 0)
459 ah_domain_error_if(std::abs(input[half].imag())
461 << ctx << ": coefficient " << half
462 << " has non-negligible imaginary part " << input[half].imag();
463
464 const size_t limit = (n & 1) == 0 ? half : half + 1;
465 for (size_t k = 1; k < limit; ++k)
466 {
467 const Complex expected = std::conj(input[n - k]);
469 > hermitian_tolerance(input[k], input[n - k], n))
470 << ctx << ": spectrum is not Hermitian at k=" << k;
471 }
472 }
473
475 [[nodiscard]] static Array<Real>
476 cosine_sum_window(const size_t n, const Real a0,
477 const Real a1 = Real(0),
478 const Real a2 = Real(0))
479 {
480 if (n == 0)
481 return {};
482
483 if (n == 1)
484 return {Real(1)};
485
486 Array<Real> window;
487 window.reserve(n);
488 const Real denom = static_cast<Real>(n - 1);
489 for (size_t i = 0; i < n; ++i)
490 {
491 const Real phase = Real(2) * std::numbers::pi_v<Real>
492 * static_cast<Real>(i) / denom;
493 const Real value = a0 - a1 * std::cos(phase)
494 + a2 * std::cos(Real(2) * phase);
495 window.append(value);
496 }
497
498 return window;
499 }
500
502 template <typename T>
503 [[nodiscard]] static Array<T>
505 {
507 output.reserve(input.size());
508 for (size_t i = input.size(); i > 0; --i)
509 output.append(input[i - 1]);
510 return output;
511 }
512
514 template <typename T>
515 [[nodiscard]] static Array<T>
516 prefix_copy(const Array<T> & input, const size_t length)
517 {
518 ah_invalid_argument_if(length > input.size())
519 << "FFT::prefix_copy: length " << length
520 << " exceeds input size " << input.size();
521
523 output.reserve(length);
524 for (size_t i = 0; i < length; ++i)
525 output.append(input[i]);
526 return output;
527 }
528
530 template <typename T>
531 [[nodiscard]] static Array<T>
533 const size_t offset,
534 const size_t length)
535 {
537 << "FFT::slice_copy: offset " << offset << " exceeds input size " << input.size();
538 ah_invalid_argument_if(length > input.size() - offset)
539 << "FFT::slice_copy: length " << length << " exceeds slice capacity " << (input.size() - offset);
540
542 output.reserve(length);
543 for (size_t i = 0; i < length; ++i)
544 output.append(input[offset + i]);
545 return output;
546 }
547
549 [[nodiscard]] static size_t
551 const size_t coeff_size) noexcept
552 {
553 if (signal_size <= 1 or coeff_size <= 1)
554 return 0;
555
556 const size_t taps_minus_one = coeff_size - 1;
557 const size_t suggested = taps_minus_one
558 > std::numeric_limits<size_t>::max() / 3 ?
559 std::numeric_limits<size_t>::max() :
560 taps_minus_one * 3;
561 return std::min(suggested, signal_size - 1);
562 }
563
567 [[nodiscard]] static Array<Real>
568 reflect_pad_signal(const Array<Real> & signal, const size_t pad_len)
569 {
570 if (pad_len == 0)
571 return signal;
572
573 ah_invalid_argument_if(signal.size() < 2)
574 << "FFT::reflect_pad_signal: signal size must be >= 2 when pad_len > 0";
576 << "FFT::reflect_pad_signal: pad length " << pad_len
577 << " must be smaller than signal size " << signal.size();
578
580 output.reserve(signal.size() + pad_len * 2);
581
582 for (size_t i = 0; i < pad_len; ++i)
583 output.append(signal[pad_len - i]);
584
585 for (size_t i = 0; i < signal.size(); ++i)
586 output.append(signal[i]);
587
588 for (size_t i = 0; i < pad_len; ++i)
589 output.append(signal[signal.size() - 2 - i]);
590
591 return output;
592 }
593
595 [[nodiscard]] static Array<Real>
597 const size_t left_pad,
598 const size_t right_pad)
599 {
601 output.reserve(left_pad + signal.size() + right_pad);
602
603 for (size_t i = 0; i < left_pad; ++i)
604 output.append(Real(0));
605 for (size_t i = 0; i < signal.size(); ++i)
606 output.append(signal[i]);
607 for (size_t i = 0; i < right_pad; ++i)
608 output.append(Real(0));
609
610 return output;
611 }
612
616 [[nodiscard]] static Array<Real>
619 const size_t hop_size,
620 const char *ctx)
621 {
623 << ctx << ": analysis window must be non-empty";
625 << ctx << ": synthesis window must be non-empty";
627 << ctx << ": analysis window size " << analysis_window.size()
628 << " does not match synthesis window size "
629 << synthesis_window.size();
630 ah_invalid_argument_if(hop_size == 0) << ctx << ": hop size must be positive";
631
632 Array<Real> profile = Array<Real>::create(hop_size);
633 for (size_t i = 0; i < hop_size; ++i)
634 profile(i) = Real(0);
635
636 for (size_t i = 0; i < analysis_window.size(); ++i)
637 profile(i % hop_size) += analysis_window[i] * synthesis_window[i];
638
639 return profile;
640 }
641
645 [[nodiscard]] static bool
646 overlap_profile_has_nola(const Array<Real> & profile) noexcept
647 {
648 const Real tol = (max_abs_value(profile) + Real(1))
649 * Real(128) * std::numeric_limits<Real>::epsilon();
650 for (size_t i = 0; i < profile.size(); ++i)
651 if (std::abs(profile[i]) <= tol)
652 return false;
653 return true;
654 }
655
659 [[nodiscard]] static bool
660 overlap_profile_has_cola(const Array<Real> & profile) noexcept
661 {
662 if (profile.is_empty())
663 return false;
664
665 const Real mean = profile[0];
666 const Real tol = (max_abs_value(profile) + Real(1))
667 * Real(256) * std::numeric_limits<Real>::epsilon();
668 for (size_t i = 0; i < profile.size(); ++i)
669 if (std::abs(profile[i] - mean) > tol)
670 return false;
671 return true;
672 }
673
675 static void
678 const size_t hop_size,
679 const bool validate_nola,
680 const bool validate_cola,
681 const char *ctx)
682 {
683 if (not validate_nola and not validate_cola)
684 return;
685
688 hop_size,
689 ctx);
690
691 if (validate_nola)
693 << ctx << ": window pair does not satisfy NOLA for hop size "
694 << hop_size;
695
696 if (validate_cola)
698 << ctx << ": window pair does not satisfy COLA for hop size "
699 << hop_size;
700 }
701
703 [[nodiscard]] static Array<size_t>
705 const size_t frame_size,
706 const size_t hop_size,
707 const bool pad_end,
708 const char *ctx)
709 {
710 ah_invalid_argument_if(frame_size == 0) << ctx << ": frame size must be positive";
711 ah_invalid_argument_if(hop_size == 0) << ctx << ": hop size must be positive";
712
713 Array<size_t> offsets;
714 if (signal_size == 0)
715 return offsets;
716
717 for (size_t offset = 0; offset < signal_size; offset += hop_size)
718 {
719 if (const size_t length = std::min(frame_size, signal_size - offset); not pad_end and length < frame_size)
720 break;
721
722 offsets.append(offset);
723
724 if (not pad_end and offset + frame_size >= signal_size)
725 break;
726 }
727
728 return offsets;
729 }
730
732 [[nodiscard]] static Real
734 {
735 Real max_value = Real(0);
736 for (size_t i = 0; i < input.size(); ++i)
737 max_value = std::max(max_value, std::abs(input[i]));
738 return max_value;
739 }
740
742 [[nodiscard]] static Real
743 sum_values(const Array<Real> & input) noexcept
744 {
745 Real total = Real(0);
746 for (size_t i = 0; i < input.size(); ++i)
747 total += input[i];
748 return total;
749 }
750
752 [[nodiscard]] static Real
753 sum_squares(const Array<Real> & input) noexcept
754 {
755 Real total = Real(0);
756 for (size_t i = 0; i < input.size(); ++i)
757 total += input[i] * input[i];
758 return total;
759 }
760
762 [[nodiscard]] static Array<Real>
764 {
765 if (input.is_empty())
766 return {};
767
768 const Real mean = sum_values(input) / static_cast<Real>(input.size());
770 for (size_t i = 0; i < input.size(); ++i)
771 output(i) = input[i] - mean;
772 return output;
773 }
774
776 [[nodiscard]] static Array<Real>
777 one_sided_frequency_grid(const size_t fft_size,
778 const Real sample_rate,
779 const char *ctx)
780 {
781 ah_invalid_argument_if(fft_size == 0) << ctx << ": FFT size must be positive";
783 << ctx << ": sample rate " << sample_rate << " must be strictly positive";
784
785 Array<Real> frequency = Array<Real>::create(fft_size / 2 + 1);
786 for (size_t k = 0; k < frequency.size(); ++k)
787 frequency(k) = static_cast<Real>(k) * sample_rate
788 / static_cast<Real>(fft_size);
789 return frequency;
790 }
791
793 [[nodiscard]] static bool
795 const size_t fft_size) noexcept
796 {
797 if (bin == 0)
798 return false;
799 if (fft_size % 2 == 0 and bin == fft_size / 2)
800 return false;
801 return true;
802 }
803
805 [[nodiscard]] static size_t
807 const size_t frame_size,
808 const char *ctx)
809 {
810 const size_t hop_size =
811 options.hop_size == 0 ? std::max(static_cast<size_t>(1), frame_size / 2) : options.hop_size;
812 ah_invalid_argument_if(hop_size == 0) << ctx << ": hop size must be positive";
813 return hop_size;
814 }
815
817 [[nodiscard]] static size_t
819 const size_t frame_size,
820 const char *ctx)
821 {
822 const size_t fft_size =
823 options.fft_size == 0 ? next_power_of_two(frame_size) : options.fft_size;
824 ah_invalid_argument_if(fft_size < frame_size)
825 << ctx << ": FFT size " << fft_size << " is smaller than frame size " << frame_size;
826 return fft_size;
827 }
828
832 [[nodiscard]] static Array<Array<Real>>
834 const Array<Real> & window,
835 const WelchOptions & options,
836 const char *ctx)
837 {
838 ah_invalid_argument_if(signal.is_empty()) << ctx << ": signal must be non-empty";
839 ah_invalid_argument_if(window.is_empty()) << ctx << ": window must be non-empty";
840
841 const size_t hop_size = resolve_welch_hop_size(options, window.size(), ctx);
842 Array<Array<Real>> frames =
843 frame_signal(signal, window.size(), hop_size, options.pad_end);
845 << ctx << ": framing produced no analysis frames";
846
847 for (size_t i = 0; i < frames.size(); ++i)
848 {
849 frames(i) = options.detrend ? demean_copy(frames[i]) : frames[i];
850 frames(i) = apply_window(frames[i], window);
851 }
852 return frames;
853 }
854
858 template <typename Density>
859 static void
861 const size_t bin,
862 const size_t fft_size) noexcept
863 {
864 if (is_one_sided_interior_bin(bin, fft_size))
865 value *= Real(2);
866 }
867
869 [[nodiscard]] static size_t
871 {
872 if (input.is_empty())
873 return 0;
874
875 const Real tol = (max_abs_value(input) + Real(1))
876 * Real(64) * std::numeric_limits<Real>::epsilon();
877 size_t n = input.size();
878 while (n > 1 and std::abs(input[n - 1]) <= tol)
879 --n;
880 return n;
881 }
882
884 [[nodiscard]] static Array<Real>
885 scaled_copy(const Array<Real> & input, const Real factor)
886 {
888 for (size_t i = 0; i < input.size(); ++i)
889 output(i) = input[i] * factor;
890 return output;
891 }
892
901
908
914
915 [[nodiscard]] static Real
916 scaled_tolerance(const Real reference,
917 const Real multiplier = Real(256)) noexcept
918 {
919 return (std::abs(reference) + Real(1))
920 * multiplier * std::numeric_limits<Real>::epsilon();
921 }
922
923 [[nodiscard]] static Real
925 const Real multiplier = Real(256)) noexcept
926 {
927 return multiplier
928 * static_cast<Real>(coeff_count + 1)
929 * std::numeric_limits<Real>::epsilon();
930 }
931
932 [[nodiscard]] static IIRCoefficients
934 const Array<Real> & denominator,
935 const char *ctx)
936 {
937 const size_t num_length = effective_coeff_length(numerator);
938 const size_t den_length = effective_coeff_length(denominator);
939
941 << ctx << ": numerator must be non-empty";
943 << ctx << ": denominator must be non-empty";
944
945 const Real a0 = denominator[0];
946 const Real tol = (max_abs_value(denominator) + Real(1))
947 * Real(64) * std::numeric_limits<Real>::epsilon();
948 ah_domain_error_if(std::abs(a0) <= tol)
949 << ctx << ": leading denominator coefficient must be non-zero";
950
951 const size_t order = std::max(num_length, den_length) - 1;
952 IIRCoefficients coeffs;
953 coeffs.numerator = Array<Real>::create(order + 1);
954 coeffs.denominator = Array<Real>::create(order + 1);
955
956 for (size_t i = 0; i <= order; ++i)
957 {
958 coeffs.numerator(i) = Real(0);
959 coeffs.denominator(i) = Real(0);
960 }
961
962 for (size_t i = 0; i < num_length; ++i)
963 coeffs.numerator(i) = numerator[i] / a0;
964 for (size_t i = 0; i < den_length; ++i)
965 coeffs.denominator(i) = denominator[i] / a0;
966 coeffs.denominator(0) = Real(1);
967
968 return coeffs;
969 }
970
971 [[nodiscard]] static Array<Real>
973 Array<Real> rhs,
974 const size_t n,
975 const char *ctx)
976 {
977 if (n == 0)
978 return {};
979
980 ah_invalid_argument_if(matrix.size() != n * n)
981 << ctx << ": matrix size " << matrix.size()
982 << " does not match a dense " << n << "x" << n << " system";
983 ah_invalid_argument_if(rhs.size() != n)
984 << ctx << ": rhs size " << rhs.size() << " does not match system size " << n;
985
986 Real max_entry = Real(0);
987 for (size_t i = 0; i < matrix.size(); ++i)
988 max_entry = std::max(max_entry, std::abs(matrix[i]));
989 for (size_t i = 0; i < rhs.size(); ++i)
990 max_entry = std::max(max_entry, std::abs(rhs[i]));
991
992 const Real pivot_tol = (max_entry + Real(1))
993 * Real(128) * std::numeric_limits<Real>::epsilon();
994
995 auto coeff = [&matrix, n](const size_t row, const size_t col) -> Real &
996 {
997 return matrix(row * n + col);
998 };
999
1000 for (size_t col = 0; col < n; ++col)
1001 {
1002 size_t pivot_row = col;
1003 Real pivot_abs = std::abs(coeff(col, col));
1004 for (size_t row = col + 1; row < n; ++row)
1005 {
1006 const Real candidate = std::abs(coeff(row, col));
1007 if (candidate > pivot_abs)
1008 {
1010 pivot_row = row;
1011 }
1012 }
1013
1015 << ctx << ": singular or ill-conditioned linear system";
1016
1017 if (pivot_row != col)
1018 {
1019 for (size_t k = col; k < n; ++k)
1020 std::swap(coeff(col, k), coeff(pivot_row, k));
1021 std::swap(rhs(col), rhs(pivot_row));
1022 }
1023
1024 const Real pivot = coeff(col, col);
1025 for (size_t row = col + 1; row < n; ++row)
1026 {
1027 const Real factor = coeff(row, col) / pivot;
1028 if (std::abs(factor) <= pivot_tol)
1029 {
1030 coeff(row, col) = Real(0);
1031 continue;
1032 }
1033
1034 coeff(row, col) = Real(0);
1035 for (size_t k = col + 1; k < n; ++k)
1036 coeff(row, k) -= factor * coeff(col, k);
1037 rhs(row) -= factor * rhs[col];
1038 }
1039 }
1040
1041 Array<Real> solution = Array<Real>::create(n);
1042 for (size_t i = 0; i < n; ++i)
1043 solution(i) = Real(0);
1044
1045 for (size_t row = n; row > 0; --row)
1046 {
1047 const size_t i = row - 1;
1048 Real sum = rhs[i];
1049 for (size_t col = i + 1; col < n; ++col)
1050 sum -= coeff(i, col) * solution[col];
1051
1052 const Real pivot = coeff(i, i);
1053 ah_domain_error_if(std::abs(pivot) <= pivot_tol)
1054 << ctx << ": singular or ill-conditioned linear system";
1055 solution(i) = sum / pivot;
1056 }
1057
1058 return solution;
1059 }
1060
1061 [[nodiscard]] static Array<Real>
1063 const Array<Real> & denominator,
1064 const char *ctx)
1065 {
1066 ah_invalid_argument_if(numerator.size() != denominator.size())
1067 << ctx << ": numerator size " << numerator.size()
1068 << " does not match denominator size " << denominator.size();
1069
1070 const size_t order = denominator.size() - 1;
1071 if (order == 0)
1072 return {};
1073
1074 Array<Real> system = Array<Real>::create(order * order);
1075 for (size_t i = 0; i < system.size(); ++i)
1076 system(i) = Real(0);
1077
1078 auto coeff = [&system, order](const size_t row, const size_t col) -> Real &
1079 {
1080 return system(row * order + col);
1081 };
1082
1083 for (size_t i = 0; i < order; ++i)
1084 coeff(i, i) = Real(1);
1085
1086 for (size_t i = 0; i < order; ++i)
1087 {
1088 coeff(i, 0) += denominator[i + 1];
1089 if (i + 1 < order)
1090 coeff(i, i + 1) -= Real(1);
1091 }
1092
1093 Array<Real> rhs = Array<Real>::create(order);
1094 for (size_t i = 0; i < order; ++i)
1095 rhs(i) = numerator[i + 1] - denominator[i + 1] * numerator[0];
1096
1097 return solve_dense_system(system, rhs, order, ctx);
1098 }
1099
1100 [[nodiscard]] static Array<Real>
1102 const Array<Real> & numerator,
1103 const Array<Real> & denominator,
1104 const Array<Real> & initial_state,
1105 const char *ctx,
1106 Array<Real> *final_state = nullptr)
1107 {
1108 ah_invalid_argument_if(numerator.size() != denominator.size())
1109 << ctx << ": numerator size " << numerator.size()
1110 << " does not match denominator size " << denominator.size();
1111
1112 const size_t order = denominator.size() - 1;
1114 and initial_state.size() != order)
1115 << ctx << ": initial state size " << initial_state.size()
1116 << " does not match filter order " << order;
1117
1118 if (signal.is_empty())
1119 return {};
1120
1122 if (order == 0)
1123 {
1124 for (size_t i = 0; i < signal.size(); ++i)
1125 output(i) = numerator[0] * signal[i];
1126 return output;
1127 }
1128
1129 Array<Real> state = Array<Real>::create(order);
1130 for (size_t i = 0; i < order; ++i)
1131 state(i) = initial_state.is_empty() ? Real(0) : initial_state[i];
1132
1133 for (size_t n = 0; n < signal.size(); ++n)
1134 {
1135 const Real x = signal[n];
1136 const Real y = numerator[0] * x + state[0];
1137 output(n) = y;
1138
1139 for (size_t i = 0; i + 1 < order; ++i)
1140 state(i) = state[i + 1] + numerator[i + 1] * x
1141 - denominator[i + 1] * y;
1142 state(order - 1) = numerator[order] * x - denominator[order] * y;
1143 }
1144
1145 if (final_state != nullptr)
1146 *final_state = state;
1147
1148 return output;
1149 }
1150
1151 [[nodiscard]] static Array<Real>
1153 const Array<Real> & numerator,
1154 const Array<Real> & denominator,
1155 const char *ctx)
1156 {
1157 if (signal.is_empty() or numerator.is_empty() or denominator.is_empty())
1158 return {};
1159
1160 const IIRCoefficients coeffs =
1161 normalize_iir_coefficients(numerator, denominator, ctx);
1162
1163 if (const size_t order = coeffs.denominator.size() - 1; order == 0)
1164 {
1166 const Real gain = coeffs.numerator[0] * coeffs.numerator[0];
1167 for (size_t i = 0; i < signal.size(); ++i)
1168 output(i) = signal[i] * gain;
1169 return output;
1170 }
1171
1172 const size_t pad_len = default_filtfilt_pad_length(signal.size(),
1173 coeffs.denominator.size());
1176 coeffs.denominator,
1177 ctx);
1178
1179 const Array<Real> forward = iir_filter_impl(padded,
1180 coeffs.numerator,
1181 coeffs.denominator,
1182 scaled_copy(zi, padded[0]),
1183 ctx);
1184 const Array<Real> reversed = reverse_copy(forward);
1185 const Array<Real> backward = iir_filter_impl(reversed,
1186 coeffs.numerator,
1187 coeffs.denominator,
1188 scaled_copy(zi, reversed[0]),
1189 ctx);
1190 const Array<Real> zero_phase = reverse_copy(backward);
1191
1192 return slice_copy(zero_phase, pad_len, signal.size());
1193 }
1194
1195 [[nodiscard]] static Array<Real>
1198 const char *ctx)
1199 {
1200 if (signal.is_empty() or sections.is_empty())
1201 return {};
1202
1205 size_t total_order = 0;
1206 for (size_t i = 0; i < sections.size(); ++i)
1207 {
1208 const IIRCoefficients coeffs =
1209 normalize_iir_coefficients(sections[i].numerator(),
1210 sections[i].denominator(),
1211 ctx);
1212 total_order += coeffs.denominator.size() - 1;
1213 normalized_sections.append(coeffs);
1214 }
1215
1216 if (total_order == 0)
1217 {
1218 Array<Real> output = signal;
1219 for (size_t i = 0; i < normalized_sections.size(); ++i)
1220 {
1221 const Real gain = normalized_sections[i].numerator[0]
1222 * normalized_sections[i].numerator[0];
1223 for (size_t j = 0; j < output.size(); ++j)
1224 output(j) *= gain;
1225 }
1226 return output;
1227 }
1228
1229 const size_t pad_len = default_filtfilt_pad_length(signal.size(),
1230 total_order + 1);
1232
1233 for (size_t i = 0; i < normalized_sections.size(); ++i)
1234 {
1235 const auto & coeffs = normalized_sections[i];
1236 const Array<Real> zi = iir_steady_state(coeffs.numerator,
1237 coeffs.denominator,
1238 ctx);
1240 coeffs.numerator,
1241 coeffs.denominator,
1242 scaled_copy(zi, stage[0]),
1243 ctx);
1244 }
1245
1247
1248 for (size_t i = 0; i < normalized_sections.size(); ++i)
1249 {
1250 const auto & coeffs = normalized_sections[i];
1251 const Array<Real> zi = iir_steady_state(coeffs.numerator,
1252 coeffs.denominator,
1253 ctx);
1255 coeffs.numerator,
1256 coeffs.denominator,
1257 scaled_copy(zi, stage[0]),
1258 ctx);
1259 }
1260
1261 return slice_copy(reverse_copy(stage), pad_len, signal.size());
1262 }
1263
1264 template <typename T>
1265 static void
1267 {
1268 dst.reserve(dst.size() + src.size());
1269 for (size_t i = 0; i < src.size(); ++i)
1270 dst.append(src[i]);
1271 }
1272
1273 template <typename T>
1274 static void
1276 {
1278 << "FFT::drop_prefix: count " << count << " exceeds input size " << input.size();
1279
1280 if (count == 0)
1281 return;
1282
1283 if (count == input.size())
1284 {
1285 input.empty();
1286 return;
1287 }
1288
1290 trimmed.reserve(input.size() - count);
1291 for (size_t i = count; i < input.size(); ++i)
1292 trimmed.append(input[i]);
1293 input = std::move(trimmed);
1294 }
1295
1296 [[nodiscard]] static constexpr size_t
1297 saturating_product(const size_t lhs, const size_t rhs) noexcept
1298 {
1299 return lhs == 0 or rhs == 0 ?
1300 0 :
1301 lhs > std::numeric_limits<size_t>::max() / rhs ?
1302 std::numeric_limits<size_t>::max() :
1303 lhs * rhs;
1304 }
1305
1306 [[nodiscard]] static bool
1308 const size_t batch_size,
1309 const size_t transform_size,
1310 const size_t min_work = 8192) noexcept
1311 {
1312 return pool != nullptr
1313 and pool->num_threads() > 1
1314 and batch_size > 1
1316 }
1317
1318 [[nodiscard]] static size_t
1320 {
1321 size_t count = 0;
1322 for (size_t i = 0; i < batch.size(); ++i)
1323 if (not batch[i].is_empty())
1324 ++count;
1325 return count;
1326 }
1327
1328 [[nodiscard]] static size_t
1330 const size_t batch_size,
1331 ThreadPool *pool) noexcept
1332 {
1333 constexpr size_t target_bytes = 32 * 1024;
1334 const size_t bytes_per_item = std::max(sizeof(Complex), sizeof(Real))
1335 * std::max<size_t>(1, transform_size);
1336 size_t tile = std::max<size_t>(1, target_bytes / bytes_per_item);
1337 tile = std::max<size_t>(4, tile);
1338 tile = std::min(tile, std::max<size_t>(static_cast<size_t>(1), batch_size));
1339 if (pool != nullptr and pool->num_threads() > 1)
1340 tile = std::max(tile, pool->num_threads() * 2);
1341 return std::min(tile, std::max<size_t>(static_cast<size_t>(1), batch_size));
1342 }
1343
1344 [[nodiscard]] static Array<Real>
1346 const size_t frame_size)
1347 {
1348 Array<Real> frame = Array<Real>::create(frame_size);
1349 for (size_t i = 0; i < frame_size; ++i)
1350 frame(i) = Real(0);
1351
1352 const size_t length = std::min(frame_size, input.size());
1353 for (size_t i = 0; i < length; ++i)
1354 frame(i) = input[i];
1355
1356 return frame;
1357 }
1358
1359 [[nodiscard]] static size_t
1361 const STFTOptions & options,
1362 const char *ctx)
1363 {
1364 ah_invalid_argument_if(window.is_empty()) << ctx << ": window must be non-empty";
1365 ah_invalid_argument_if(options.hop_size == 0) << ctx << ": hop size must be positive";
1366
1367 const size_t fft_size = options.fft_size == 0 ? next_power_of_two(window.size()) : options.fft_size;
1368 ah_invalid_argument_if(fft_size < window.size())
1369 << ctx << ": FFT size " << fft_size << " is smaller than window size " << window.size();
1370
1372 window,
1373 options.hop_size,
1374 options.validate_nola,
1375 options.validate_cola,
1376 ctx);
1377 return fft_size;
1378 }
1379
1380 [[nodiscard]] static Array<Complex>
1382 const Array<Real> & window,
1383 const size_t fft_size)
1384 {
1385 return lift_real_input(zero_padded_copy(apply_window(frame, window), fft_size));
1386 }
1387
1390 const Array<Real> & window,
1391 const size_t fft_size,
1392 const Plan & plan,
1393 ThreadPool *pool,
1394 const size_t chunk_size)
1395 {
1396 if (frames.is_empty())
1397 return {};
1398
1399 const bool prefer_batch_simd =
1400 pool == nullptr or pool->num_threads() <= 1 or fft_size >= 256;
1401 const size_t tile_size =
1402 recommended_cache_tile_size(fft_size, frames.size(), pool);
1403
1404 Array<Array<Complex>> output(frames.size(), Array<Complex>());
1405 for (size_t offset = 0; offset < frames.size(); offset += tile_size)
1406 {
1407 const size_t count = std::min(tile_size, frames.size() - offset);
1409 const bool parallelize_preparation =
1410 should_parallelize_batch_work(pool, count, fft_size);
1411
1412 auto prepare_one = [&frames, &window, fft_size, offset, &tile](const size_t i)
1413 {
1414 tile(i) = prepare_stft_frame_input(frames[offset + i], window, fft_size);
1415 };
1416
1418 parallel_for_index(*pool, 0, count, prepare_one, chunk_size);
1419 else
1420 for (size_t i = 0; i < count; ++i)
1421 prepare_one(i);
1422
1423 const bool parallelize_transforms =
1424 should_parallelize_batch_work(pool, count, fft_size);
1426 plan.ptransform_batch(*pool, tile, false, chunk_size, prefer_batch_simd);
1427 else
1428 plan.transform_batch(tile, false, prefer_batch_simd);
1429
1430 for (size_t i = 0; i < count; ++i)
1431 output(offset + i) = std::move(tile(i));
1432 }
1433
1434 return output;
1435 }
1436
1438 stft_impl(const Array<Real> & signal,
1439 const Array<Real> & window,
1440 const STFTOptions & options,
1441 ThreadPool *pool = nullptr,
1442 const size_t chunk_size = 0)
1443 {
1444 if (signal.is_empty())
1445 return {};
1446
1447 const size_t fft_size =
1448 validate_stft_options(window, options, "FFT::stft");
1449
1450 const size_t center_pad = options.centered ? window.size() / 2 : 0;
1451 const Array<Real> prepared_signal = options.centered ? zero_pad_edges(signal, center_pad, center_pad) : signal;
1452 const auto frames = frame_signal(prepared_signal,
1453 window.size(),
1454 options.hop_size,
1455 options.pad_end);
1456
1457 if (frames.is_empty())
1458 return {};
1459
1460 const Plan plan(fft_size);
1461 return transform_stft_frames(frames,
1462 window,
1463 fft_size,
1464 plan,
1465 pool,
1466 chunk_size);
1467 }
1468
1469 [[nodiscard]] static Complex
1471 const Array<Real> & denominator,
1472 const Real omega,
1473 const char *ctx)
1474 {
1475 const IIRCoefficients coeffs =
1476 normalize_iir_coefficients(numerator, denominator, ctx);
1478 evaluate_series_at_unit_circle(coeffs.numerator, omega, ctx);
1480 evaluate_series_at_unit_circle(coeffs.denominator, omega, ctx);
1481 const Real den_tol =
1483 ah_domain_error_if(std::abs(denominator_terms.value) <= den_tol)
1484 << ctx << ": transfer response is singular at omega=" << omega;
1485
1486 return (numerator_terms.value * numerator_terms.coefficient_scale)
1487 / (denominator_terms.value * denominator_terms.coefficient_scale);
1488 }
1489
1490 [[nodiscard]] static FrequencyResponse
1491 freqz_impl(const Array<Real> & numerator,
1492 const Array<Real> & denominator,
1493 const size_t num_points,
1494 const bool whole,
1495 const char *ctx)
1496 {
1498 << ctx << ": number of frequency samples must be positive";
1499
1503
1504 const Real max_omega = whole ? Real(2) * std::numbers::pi_v<Real> : std::numbers::pi_v<Real>;
1505 const Real denom = whole ? static_cast<Real>(num_points) : static_cast<Real>(std::max(size_t(1), num_points - 1));
1506
1507 for (size_t i = 0; i < num_points; ++i)
1508 {
1509 const Real omega = num_points == 1 ? Real(0) : max_omega * static_cast<Real>(i) / denom;
1510 output.omega(i) = omega;
1511 output.response(i) = evaluate_transfer_at(numerator,
1512 denominator,
1513 omega,
1514 ctx);
1515 }
1516
1517 return output;
1518 }
1519
1520 [[nodiscard]] static SeriesEvaluation
1522 const Real omega,
1523 const char *ctx)
1524 {
1525 const size_t n = effective_coeff_length(coefficients);
1527 << ctx << ": coefficient series must be non-empty";
1528
1529 const Real scale = max_abs_value(coefficients);
1530 const Real inv_scale = scale > std::numeric_limits<Real>::min() ? Real(1) / scale : Real(1);
1531 const Complex z = std::polar(Real(1), -omega);
1532 Complex value(coefficients[n - 1] * inv_scale, Real(0));
1534
1535 for (size_t idx = n - 1; idx-- > 0;)
1536 {
1538 value = value * z + Complex(coefficients[idx] * inv_scale, Real(0));
1539 }
1540
1542 output.value = value;
1543 output.derivative = Complex(Real(0), Real(-1)) * z * derivative_poly;
1544 output.coefficient_scale = scale > std::numeric_limits<Real>::min() ? scale : Real(1);
1545 return output;
1546 }
1547
1548 [[nodiscard]] static PolynomialEvaluation
1550 const Complex & x,
1551 const char *ctx)
1552 {
1553 const size_t n = effective_coeff_length(coefficients);
1554 ah_invalid_argument_if(n == 0) << ctx << ": polynomial coefficients must be non-empty";
1555
1557 output.value = Complex(coefficients[0], Real(0));
1558 for (size_t i = 1; i < n; ++i)
1559 {
1560 output.derivative = output.derivative * x + output.value;
1561 output.value = output.value * x + Complex(coefficients[i], Real(0));
1562 }
1563 return output;
1564 }
1565
1566 [[nodiscard]] static std::pair<Array<Real>, Real>
1568 const Real root,
1569 const char *ctx)
1570 {
1571 const size_t n = effective_coeff_length(coefficients);
1572 ah_invalid_argument_if(n <= 1) << ctx << ": polynomial degree must be at least one";
1573
1575 quotient(0) = coefficients[0];
1576 for (size_t i = 1; i + 1 < n; ++i)
1577 quotient(i) = coefficients[i] + root * quotient[i - 1];
1578
1579 const Real remainder = coefficients[n - 1] + root * quotient[n - 2];
1580 return {std::move(quotient), remainder};
1581 }
1582
1583 [[nodiscard]] static std::pair<Array<Real>, Array<Complex>>
1585 const char *ctx)
1586 {
1589 bool changed = true;
1591 {
1592 changed = false;
1593 const Real coeff_tol =
1595
1596 for (const Real root: {Real(1), Real(-1)})
1597 {
1598 const PolynomialEvaluation eval =
1600 Complex(root, Real(0)),
1601 ctx);
1602 if (std::abs(eval.value) > coeff_tol)
1603 continue;
1604
1605 const auto [quotient, remainder] =
1607 if (std::abs(remainder) > coeff_tol)
1608 continue;
1609
1610 extracted.append(Complex(root, Real(0)));
1612 changed = true;
1613 break;
1614 }
1615 }
1616
1617 return {std::move(reduced), std::move(extracted)};
1618 }
1619
1620 [[nodiscard]] static Real
1622 {
1623 Real bound = Real(1);
1624 const Real leading = std::max(std::abs(monic[0]),
1625 std::numeric_limits<Real>::min());
1626 for (size_t i = 1; i < monic.size(); ++i)
1627 bound = std::max(bound, Real(1) + std::abs(monic[i]) / leading);
1628 return bound;
1629 }
1630
1631 [[nodiscard]] static Real
1633 {
1634 if (monic.size() <= 1)
1635 return Real(0);
1636
1637 const Real constant = std::abs(monic[monic.size() - 1]);
1638 if (constant <= scaled_tolerance(max_abs_value(monic), Real(1024)))
1639 return Real(0);
1640
1641 Real inverse_bound = Real(1);
1642 for (size_t i = 0; i + 1 < monic.size(); ++i)
1643 inverse_bound = std::max(inverse_bound,
1644 Real(1) + std::abs(monic[i]) / constant);
1645 return Real(1) / inverse_bound;
1646 }
1647
1648 [[nodiscard]] static BalancedPolynomial
1650 const char *ctx)
1651 {
1652 const size_t n = effective_coeff_length(coefficients);
1654 << ctx << ": polynomial degree must be at least one";
1655
1656 const Real leading = coefficients[0];
1657 const Real coeff_max = max_abs_value(coefficients);
1658 const Real tol = scaled_tolerance(coeff_max, Real(2048));
1659 ah_domain_error_if(std::abs(leading) <= tol)
1660 << ctx << ": leading polynomial coefficient must be non-zero";
1661
1664 for (size_t i = 0; i < n; ++i)
1665 output.monic(i) = coefficients[i] / leading;
1666
1669 Real root_scale = Real(1);
1670 if (lower > tol and std::isfinite(lower) and std::isfinite(upper))
1671 root_scale = std::sqrt(lower * upper);
1672 else if (upper > Real(1) and std::isfinite(upper))
1673 root_scale = upper;
1674
1675 if (not std::isfinite(root_scale)
1676 or root_scale <= std::numeric_limits<Real>::min())
1677 root_scale = Real(1);
1678
1679 output.balanced = Array<Real>::create(n);
1680 output.balanced(0) = Real(1);
1681 Real inv_power = Real(1);
1682 const Real inv_scale = Real(1) / root_scale;
1683 for (size_t i = 1; i < n; ++i)
1684 {
1686 output.balanced(i) = output.monic[i] * inv_power;
1687 }
1688
1689 output.root_scale = root_scale;
1690 output.tol = scaled_tolerance(max_abs_value(output.balanced), Real(4096));
1691 output.residual_tol =
1693 return output;
1694 }
1695
1696 [[nodiscard]] static Array<Complex>
1698 {
1699 const size_t degree = effective_coeff_length(coefficients) - 1;
1700 Array<Complex> roots = Array<Complex>::create(degree);
1701
1702 const Real upper = polynomial_root_upper_bound(coefficients);
1703 const Real lower = polynomial_root_lower_bound(coefficients);
1704 Real base_radius = Real(1);
1705 if (lower > Real(0) and std::isfinite(lower) and std::isfinite(upper))
1706 base_radius = std::sqrt(lower * upper);
1707 else if (std::isfinite(upper) and upper > Real(0))
1709
1710 if (not std::isfinite(base_radius)
1711 or base_radius <= std::numeric_limits<Real>::min())
1712 base_radius = Real(1);
1713
1714 for (size_t i = 0; i < degree; ++i)
1715 {
1716 const Real angle =
1717 Real(2) * std::numbers::pi_v<Real>
1718 * (static_cast<Real>(i) + Real(0.5))
1719 / static_cast<Real>(degree);
1720 const Real radius =
1722 * (Real(0.9)
1723 + Real(0.2) * static_cast<Real>(i + 1)
1724 / static_cast<Real>(degree + 1));
1725 roots(i) = std::polar(radius, angle);
1726 }
1727
1728 return roots;
1729 }
1730
1731 static bool
1733 const Real tol,
1734 Array<Complex> & roots,
1735 const size_t max_iterations = 192) noexcept
1736 {
1737 const size_t degree = roots.size();
1738 for (size_t iter = 0; iter < max_iterations; ++iter)
1739 {
1740 bool converged = true;
1741 for (size_t i = 0; i < degree; ++i)
1742 {
1743 const PolynomialEvaluation eval =
1745 roots[i],
1746 "FFT::root_solver");
1747 Complex correction(Real(0), Real(0));
1748 for (size_t j = 0; j < degree; ++j)
1749 if (j != i)
1750 {
1751 const Complex delta = roots[i] - roots[j];
1752 if (std::abs(delta) > tol)
1753 correction += Complex(Real(1), Real(0)) / delta;
1754 }
1755
1756 Complex denom = eval.derivative - eval.value * correction;
1757 if (std::abs(denom) <= tol)
1758 {
1759 if (std::abs(eval.derivative) <= tol)
1760 {
1761 const Real nudge = tol * (Real(4) + std::abs(roots[i]));
1762 roots(i) += std::polar(nudge,
1763 Real(2)
1764 * std::numbers::pi_v<Real>
1765 * static_cast<Real>(iter + i + 1)
1766 / static_cast<Real>(degree + 1));
1767 converged = false;
1768 continue;
1769 }
1770 denom = eval.derivative;
1771 }
1772
1773 const Complex step = eval.value / denom;
1774 roots(i) -= step;
1775 if (std::abs(step) > tol * (Real(1) + std::abs(roots[i])))
1776 converged = false;
1777 }
1778
1779 if (converged)
1780 return true;
1781 }
1782
1783 return false;
1784 }
1785
1786 static bool
1788 const Real tol,
1789 Array<Complex> & roots,
1790 const size_t max_iterations = 192) noexcept
1791 {
1792 const size_t degree = roots.size();
1793 for (size_t iter = 0; iter < max_iterations; ++iter)
1794 {
1795 bool converged = true;
1796 for (size_t i = 0; i < degree; ++i)
1797 {
1798 const PolynomialEvaluation eval =
1800 roots[i],
1801 "FFT::root_solver");
1802 Complex denom(Real(1), Real(0));
1803 for (size_t j = 0; j < degree; ++j)
1804 if (j != i)
1805 {
1806 const Complex delta = roots[i] - roots[j];
1807 if (std::abs(delta) <= tol)
1808 {
1809 const Real nudge = tol * (Real(4) + std::abs(roots[i]));
1810 roots(i) += std::polar(nudge,
1811 Real(2)
1812 * std::numbers::pi_v<Real>
1813 * static_cast<Real>(iter + i + 1)
1814 / static_cast<Real>(degree + 1));
1815 denom = Complex(Real(0), Real(0));
1816 break;
1817 }
1818 denom *= delta;
1819 }
1820
1821 if (std::abs(denom) <= tol)
1822 {
1823 converged = false;
1824 continue;
1825 }
1826
1827 const Complex step = eval.value / denom;
1828 roots(i) -= step;
1829 if (std::abs(step) > tol * (Real(1) + std::abs(roots[i])))
1830 converged = false;
1831 }
1832
1833 if (converged)
1834 return true;
1835 }
1836
1837 return false;
1838 }
1839
1840 static void
1842 Array<Complex> & roots,
1843 const Real tol,
1844 const size_t iterations) noexcept
1845 {
1846 for (size_t i = 0; i < roots.size(); ++i)
1847 for (size_t iter = 0; iter < iterations; ++iter)
1848 {
1849 const PolynomialEvaluation eval =
1851 roots[i],
1852 "FFT::root_solver");
1853 if (std::abs(eval.derivative) <= tol)
1854 break;
1855
1856 const Complex step = eval.value / eval.derivative;
1857 roots(i) -= step;
1858 if (std::abs(step) <= tol * (Real(1) + std::abs(roots[i])))
1859 break;
1860 }
1861 }
1862
1863 static void
1865 const Real tol) noexcept
1866 {
1867 Array<bool> used(roots.size(), false);
1868 for (size_t i = 0; i < roots.size(); ++i)
1869 {
1870 if (used[i])
1871 continue;
1872
1873 if (std::abs(roots[i].imag()) <= tol * (Real(8) + std::abs(roots[i])))
1874 {
1875 roots(i) = Complex(roots[i].real(), Real(0));
1876 used[i] = true;
1877 continue;
1878 }
1879
1880 const Complex target = std::conj(roots[i]);
1881 size_t best = roots.size();
1882 Real best_error = std::numeric_limits<Real>::infinity();
1883 for (size_t j = i + 1; j < roots.size(); ++j)
1884 {
1885 if (used[j])
1886 continue;
1887 const Real error = std::abs(roots[j] - target);
1888 if (error < best_error)
1889 {
1890 best_error = error;
1891 best = j;
1892 }
1893 }
1894
1895 if (best == roots.size()
1896 or best_error > tol * (Real(16) + std::abs(roots[i])))
1897 continue;
1898
1899 const Complex center = (roots[i] + std::conj(roots[best])) / Real(2);
1900 const Real imag = std::abs(center.imag());
1901 roots(i) = Complex(center.real(),
1902 roots[i].imag() >= Real(0) ? imag : -imag);
1903 roots(best) = std::conj(roots[i]);
1904 used[i] = true;
1905 used[best] = true;
1906 }
1907 }
1908
1909 [[nodiscard]] static bool
1911 const Array<Complex> & roots,
1912 const Real tol) noexcept
1913 {
1914 for (size_t i = 0; i < roots.size(); ++i)
1915 {
1916 const PolynomialEvaluation eval =
1918 roots[i],
1919 "FFT::root_solver");
1920 Real bound = Real(0);
1921 const Real radius = std::abs(roots[i]);
1922 for (size_t k = 0; k < coefficients.size(); ++k)
1923 bound = bound * radius + std::abs(coefficients[k]);
1924
1925 if (std::abs(eval.value) > tol * (bound + Real(1)))
1926 return false;
1927 }
1928
1929 return true;
1930 }
1931
1932 static bool
1933 try_laguerre_roots(const Array<Real> & coefficients,
1934 const Real tol,
1935 Array<Complex> & roots) noexcept
1936 {
1937 const size_t n = coefficients.size();
1938 const size_t degree = n - 1;
1939
1941 for (size_t i = 0; i < n; ++i)
1942 deflated(i) = Complex(coefficients[i], Real(0));
1943
1944 for (size_t k = 0; k < degree; ++k)
1945 {
1946 const size_t m = degree - k;
1947 const Real m_r = static_cast<Real>(m);
1948 Complex z = std::polar(Real(0.8) + Real(0.4)
1949 * static_cast<Real>(k)
1950 / static_cast<Real>(degree + 1),
1951 Real(2) * std::numbers::pi_v<Real>
1952 * (static_cast<Real>(k) + Real(0.5))
1953 / static_cast<Real>(degree));
1954
1955 bool found = false;
1956 for (size_t iter = 0; iter < 512; ++iter)
1957 {
1958 Complex p = deflated[0];
1959 Complex dp(Real(0), Real(0));
1960 Complex d2p(Real(0), Real(0));
1961 for (size_t i = 1; i <= m; ++i)
1962 {
1963 d2p = z * d2p + dp;
1964 dp = z * dp + p;
1965 p = z * p + deflated[i];
1966 }
1967 d2p *= Complex(Real(2), Real(0));
1968
1969 if (std::abs(p) <= tol)
1970 {
1971 found = true;
1972 break;
1973 }
1974
1975 const Complex G = dp / p;
1976 const Complex H = G * G - d2p / p;
1977 const Complex disc =
1978 std::sqrt(Complex(m_r - Real(1), Real(0))
1979 * (Complex(m_r, Real(0)) * H - G * G));
1980
1981 const Complex denom1 = G + disc;
1982 const Complex denom2 = G - disc;
1983 const Complex & best_denom =
1984 std::abs(denom1) >= std::abs(denom2) ?
1985 denom1 :
1986 denom2;
1987
1988 Complex step;
1989 if (std::abs(best_denom) > tol)
1990 step = Complex(m_r, Real(0)) / best_denom;
1991 else
1992 step = (Real(1) + std::abs(z))
1993 * std::polar(Real(1),
1994 Real(2) * std::numbers::pi_v<Real>
1995 * static_cast<Real>(iter * 2 + 1)
1996 / Real(97));
1997
1998 z -= step;
1999 if (std::abs(step) <= tol * (Real(1) + std::abs(z)))
2000 {
2001 found = true;
2002 break;
2003 }
2004 }
2005
2006 if (not found)
2007 return false;
2008
2009 roots(k) = z;
2010
2012 reduced(0) = deflated[0];
2013 for (size_t i = 1; i < m; ++i)
2014 reduced(i) = deflated[i] + z * reduced[i - 1];
2015
2016 deflated = std::move(reduced);
2017 }
2018
2019 return true;
2020 }
2021
2027 [[nodiscard]] static Array<Complex>
2029 const char *ctx)
2030 {
2031 const size_t n = effective_coeff_length(coefficients);
2032 if (n <= 1)
2033 return {};
2034
2035 const Real leading = coefficients[0];
2036 const Real tol = scaled_tolerance(max_abs_value(coefficients), Real(2048));
2037 ah_domain_error_if(std::abs(leading) <= tol)
2038 << ctx << ": leading polynomial coefficient must be non-zero";
2039
2040 const size_t degree = n - 1;
2041 if (degree == 1)
2042 {
2044 roots(0) = Complex(-coefficients[1] / coefficients[0], Real(0));
2045 return roots;
2046 }
2047
2048 if (degree == 2)
2049 {
2050 const Real a = coefficients[0];
2051 const Real b = coefficients[1];
2052 const Real c = coefficients[2];
2053 const Complex disc =
2054 std::sqrt(Complex(b * b - Real(4) * a * c, Real(0)));
2055 const Complex signed_disc = b >= Real(0) ? disc : -disc;
2056 const Complex q = Complex(Real(-0.5), Real(0))
2057 * (Complex(b, Real(0)) + signed_disc);
2059 if (std::abs(q) <= tol)
2060 {
2061 const Complex repeated(-b / (Real(2) * a), Real(0));
2062 roots(0) = repeated;
2063 roots(1) = repeated;
2064 }
2065 else
2066 {
2067 roots(0) = q / a;
2068 roots(1) = c / q;
2069 }
2071 return roots;
2072 }
2073
2075 balance_polynomial_for_roots(coefficients, ctx);
2077 bool converged =
2078 try_aberth_ehrlich_roots(problem.balanced, problem.tol, roots);
2079 if (not converged)
2080 {
2081 roots = initialize_root_guesses(problem.balanced);
2082 converged =
2084 problem.tol * Real(4),
2085 roots);
2086 }
2087
2088 if (not converged)
2089 converged =
2090 try_laguerre_roots(problem.balanced,
2091 problem.tol * Real(4),
2092 roots);
2093
2094 ah_domain_error_if(not converged) << ctx << ": root solver did not converge";
2095
2096 polish_roots_with_newton(problem.balanced, roots, problem.tol, 8);
2097 for (size_t i = 0; i < roots.size(); ++i)
2098 roots(i) *= problem.root_scale;
2100 roots,
2101 problem.residual_tol,
2102 8);
2103 enforce_real_polynomial_symmetry(roots, problem.residual_tol);
2105 roots,
2106 problem.residual_tol))
2107 << ctx << ": root solver residual check failed";
2108
2109 return roots;
2110 }
2111
2115 [[nodiscard]] static Array<Real>
2117 {
2118 if (input.is_empty())
2119 return {};
2120
2121 size_t first = 0;
2122 while (first < input.size() and input[first] == Real(0))
2123 ++first;
2124
2125 if (first == input.size())
2126 return {};
2127
2129 trimmed.reserve(input.size() - first);
2130 for (size_t i = first; i < input.size(); ++i)
2131 trimmed.append(input[i]);
2132 return trimmed;
2133 }
2134
2143
2145 [[nodiscard]] static TransferTerms
2147 const Array<Real> & denominator,
2148 const Real omega,
2149 const char *ctx)
2150 {
2151 const IIRCoefficients coeffs =
2152 normalize_iir_coefficients(numerator, denominator, ctx);
2153 TransferTerms terms;
2154
2156 evaluate_series_at_unit_circle(coeffs.numerator, omega, ctx);
2158 evaluate_series_at_unit_circle(coeffs.denominator, omega, ctx);
2159
2160 terms.numerator = numerator_terms.value;
2161 terms.numerator_derivative = numerator_terms.derivative;
2162 terms.denominator = denominator_terms.value;
2163 terms.denominator_derivative = denominator_terms.derivative;
2164
2165 const Real den_tol =
2167 ah_domain_error_if(std::abs(terms.denominator) <= den_tol)
2168 << ctx << ": transfer response is singular at omega=" << omega;
2169
2170 return terms;
2171 }
2172
2177 [[nodiscard]] static Array<Real>
2179 const Array<Real> & denominator,
2180 const size_t num_points,
2181 const bool whole,
2182 const char *ctx)
2183 {
2184 const FrequencyResponse response =
2185 freqz_impl(numerator, denominator, num_points, whole, ctx);
2187 const Array<Real> magnitude = response.magnitude();
2189 const Real tol = (max_abs_value(magnitude) + Real(1))
2190 * Real(256) * std::numeric_limits<Real>::epsilon();
2191
2192 for (size_t i = 0; i < num_points; ++i)
2193 {
2194 const TransferTerms terms =
2195 evaluate_transfer_terms_at(numerator, denominator, response.omega[i], ctx);
2196 if (std::abs(terms.numerator)
2197 <= std::max(tol,
2199 Real(512))))
2200 delay(i) = numeric_fallback[i];
2201 else
2202 delay(i) = -std::imag(terms.numerator_derivative / terms.numerator
2204 / terms.denominator);
2205 }
2206
2207 return delay;
2208 }
2209
2211 [[nodiscard]] static Array<Real>
2213 const FrequencyResponse & response,
2214 const char *ctx)
2215 {
2217 << ctx << ": at least one biquad section is required";
2218
2221 for (size_t i = 0; i < response.omega.size(); ++i)
2222 {
2223 Real total = Real(0);
2224 bool analytic = true;
2225 for (size_t j = 0; j < sections.size(); ++j)
2226 {
2227 const TransferTerms terms =
2228 evaluate_transfer_terms_at(sections[j].numerator(),
2229 sections[j].denominator(),
2230 response.omega[i],
2231 ctx);
2232 const Real num_tol =
2233 series_zero_tolerance(3, Real(512));
2234 if (std::abs(terms.numerator) <= num_tol)
2235 {
2236 analytic = false;
2237 break;
2238 }
2239
2240 total += -std::imag(terms.numerator_derivative / terms.numerator
2242 / terms.denominator);
2243 }
2245 }
2246
2247 return delay;
2248 }
2249
2251 [[nodiscard]] static Array<Real>
2253 const Array<Real> & denominator,
2254 const size_t num_points,
2255 const bool whole,
2256 const char *ctx)
2257 {
2258 const FrequencyResponse response =
2259 freqz_impl(numerator, denominator, num_points, whole, ctx);
2260 const Array<Real> phase = unwrap_phase(phase_spectrum(response.response));
2261 const Array<Real> group =
2262 analytic_group_delay_impl(numerator, denominator, num_points, whole, ctx);
2264 const Real tol = (response.omega[response.omega.size() - 1] + Real(1))
2265 * Real(128) * std::numeric_limits<Real>::epsilon();
2266
2267 for (size_t i = 0; i < num_points; ++i)
2268 delay(i) = std::abs(response.omega[i]) <= tol ? group[i] : -phase[i] / response.omega[i];
2269
2270 return delay;
2271 }
2272
2274 [[nodiscard]] static Real
2275 max_root_radius(const Array<Complex> & roots) noexcept
2276 {
2277 Real radius = Real(0);
2278 for (size_t i = 0; i < roots.size(); ++i)
2279 radius = std::max(radius, static_cast<Real>(std::abs(roots[i])));
2280 return radius;
2281 }
2282
2288 const Array<Complex> & poles)
2289 {
2290 Array<PoleZeroPair> pairs;
2291 Array<bool> zero_used(zeros.size(), false);
2292 Array<bool> pole_used(poles.size(), false);
2293 const size_t matched = std::min(zeros.size(), poles.size());
2294
2295 for (size_t count = 0; count < matched; ++count)
2296 {
2297 bool found = false;
2298 size_t best_zero = 0;
2299 size_t best_pole = 0;
2300 Real best_distance = std::numeric_limits<Real>::infinity();
2301
2302 for (size_t i = 0; i < zeros.size(); ++i)
2303 {
2304 if (zero_used[i])
2305 continue;
2306
2307 for (size_t j = 0; j < poles.size(); ++j)
2308 {
2309 if (pole_used[j])
2310 continue;
2311
2312 const Real distance = std::abs(zeros[i] - poles[j]);
2313 if (distance < best_distance)
2314 {
2315 best_distance = distance;
2316 best_zero = i;
2317 best_pole = j;
2318 found = true;
2319 }
2320 }
2321 }
2322
2323 if (not found)
2324 break;
2325
2328 pair.pole = poles[best_pole];
2329 pair.distance = best_distance;
2330 pair.has_zero = true;
2331 pair.has_pole = true;
2332 pairs.append(pair);
2333 zero_used[best_zero] = true;
2334 pole_used[best_pole] = true;
2335 }
2336
2337 for (size_t i = 0; i < zeros.size(); ++i)
2338 if (not zero_used[i])
2339 {
2341 pair.zero = zeros[i];
2342 pair.has_zero = true;
2343 pairs.append(pair);
2344 }
2345
2346 for (size_t i = 0; i < poles.size(); ++i)
2347 if (not pole_used[i])
2348 {
2350 pair.pole = poles[i];
2351 pair.has_pole = true;
2352 pairs.append(pair);
2353 }
2354
2355 return pairs;
2356 }
2357
2361 [[nodiscard]] static Real
2363 {
2364 Real best = std::numeric_limits<Real>::infinity();
2365 for (size_t i = 0; i < pairs.size(); ++i)
2366 if (pairs[i].has_zero and pairs[i].has_pole)
2367 best = std::min(best, pairs[i].distance);
2368 return best;
2369 }
2370
2379
2387
2390 {
2394 };
2395
2403
2405 template <typename T>
2406 [[nodiscard]] static Array<T>
2407 polynomial_multiply(const Array<T> & lhs, const Array<T> & rhs)
2408 {
2409 if (lhs.is_empty() or rhs.is_empty())
2410 return {};
2411
2412 Array<T> output = Array<T>::create(lhs.size() + rhs.size() - 1);
2413 for (size_t i = 0; i < output.size(); ++i)
2414 output(i) = T(0);
2415
2416 for (size_t i = 0; i < lhs.size(); ++i)
2417 for (size_t j = 0; j < rhs.size(); ++j)
2418 output(i + j) += lhs[i] * rhs[j];
2419
2420 return output;
2421 }
2422
2424 template <typename T>
2425 static void
2427 const Array<T> & src,
2428 const T scale)
2429 {
2430 ah_invalid_argument_if(dst.size() < src.size())
2431 << "FFT::add_scaled_polynomial: destination size "
2432 << dst.size() << " is smaller than source size " << src.size();
2433
2434 for (size_t i = 0; i < src.size(); ++i)
2435 dst(i) += src[i] * scale;
2436 }
2437
2439 [[nodiscard]] static Real
2440 integer_power(const Real base, const size_t exponent) noexcept
2441 {
2442 Real result = Real(1);
2443 for (size_t i = 0; i < exponent; ++i)
2444 result *= base;
2445 return result;
2446 }
2447
2449 [[nodiscard]] static Array<Real>
2450 signed_binomial(const size_t power, const Real sign)
2451 {
2452 Array<Real> coeffs = Array<Real>::create(power + 1);
2453 coeffs(0) = Real(1);
2454 for (size_t k = 1; k <= power; ++k)
2455 coeffs(k) = coeffs[k - 1]
2456 * static_cast<Real>(power - k + 1)
2457 / static_cast<Real>(k)
2458 * sign;
2459 return coeffs;
2460 }
2461
2465 [[nodiscard]] static Array<Real>
2467 const size_t order,
2468 const Real sample_rate,
2469 const char *ctx)
2470 {
2471 ah_invalid_argument_if(analog.is_empty())
2472 << ctx << ": analog polynomial must be non-empty";
2474 << ctx << ": sample rate " << sample_rate << " must be positive";
2475
2476 const size_t degree = analog.size() - 1;
2477 ah_invalid_argument_if(degree > order)
2478 << ctx << ": analog degree " << degree << " exceeds requested bilinear order " << order;
2479
2481 for (size_t i = 0; i < output.size(); ++i)
2482 output(i) = Real(0);
2483
2484 const Real fs2 = Real(2) * sample_rate;
2485 for (size_t i = 0; i < analog.size(); ++i)
2486 {
2487 const size_t power = degree - i;
2488 const Array<Real> one_minus = signed_binomial(power, Real(-1));
2489 const Array<Real> one_plus = signed_binomial(order - power, Real(1));
2492 term,
2493 analog[i] * integer_power(fs2, power));
2494 }
2495
2496 return output;
2497 }
2498
2500 [[nodiscard]] static Array<Complex>
2502 {
2504 coeffs(0) = Complex(Real(1), Real(0));
2505
2506 for (size_t i = 0; i < roots.size(); ++i)
2507 {
2509 for (size_t j = 0; j < next.size(); ++j)
2510 next(j) = Complex(Real(0), Real(0));
2511
2512 for (size_t j = 0; j < coeffs.size(); ++j)
2513 {
2514 next(j) += coeffs[j];
2515 next(j + 1) -= coeffs[j] * roots[i];
2516 }
2517
2518 coeffs = std::move(next);
2519 }
2520
2521 return coeffs;
2522 }
2523
2525 [[nodiscard]] static Array<Real>
2527 const char *ctx)
2528 {
2530 ah_invalid_argument_if(trimmed.is_empty()) << ctx << ": polynomial vanished after trimming";
2531 return trimmed;
2532 }
2533
2535 [[nodiscard]] static Array<Real>
2537 const size_t exponent,
2538 const char *ctx)
2539 {
2540 ah_invalid_argument_if(poly.is_empty()) << ctx << ": polynomial base must be non-empty";
2541
2542 Array<Real> output = {Real(1)};
2543 for (size_t i = 0; i < exponent; ++i)
2545 return output;
2546 }
2547
2552 [[nodiscard]] static Array<Real>
2554 const Array<Real> & numerator,
2555 const Array<Real> & denominator,
2556 const char *ctx)
2557 {
2558 const size_t degree = effective_coeff_length(poly);
2559 ah_invalid_argument_if(degree == 0)
2560 << ctx << ": polynomial must be non-empty";
2561 ah_invalid_argument_if(numerator.is_empty() or denominator.is_empty())
2562 << ctx << ": rational substitution polynomials must be non-empty";
2563
2564 const size_t order = degree - 1;
2567 numerator_powers(0) = {Real(1)};
2568 denominator_powers(0) = {Real(1)};
2569 for (size_t i = 1; i <= order; ++i)
2570 {
2571 numerator_powers(i) =
2572 polynomial_multiply(numerator_powers[i - 1], numerator);
2574 polynomial_multiply(denominator_powers[i - 1], denominator);
2575 }
2576
2577 Array<Real> output = {Real(0)};
2578 for (size_t i = 0; i <= order; ++i)
2579 {
2580 const Array<Real> term =
2583 if (output.size() < term.size())
2584 {
2586 for (size_t j = 0; j < grown.size(); ++j)
2587 grown(j) = Real(0);
2588 for (size_t j = 0; j < output.size(); ++j)
2589 grown(j + (term.size() - output.size())) = output[j];
2590 output = std::move(grown);
2591 }
2592
2594 for (size_t j = 0; j < aligned.size(); ++j)
2595 aligned(j) = Real(0);
2596 for (size_t j = 0; j < term.size(); ++j)
2597 aligned(j + (aligned.size() - term.size())) = term[j];
2598 for (size_t j = 0; j < output.size(); ++j)
2599 output(j) += poly[i] * aligned[j];
2600 }
2601
2603 }
2604
2609 [[nodiscard]] static IIRCoefficients
2611 const Array<Real> & denominator,
2612 const Array<Real> & map_numerator,
2614 const char *ctx)
2615 {
2616 const size_t num_degree = effective_coeff_length(numerator) - 1;
2617 const size_t den_degree = effective_coeff_length(denominator) - 1;
2622 ctx);
2627 ctx);
2628
2629 if (den_degree > num_degree)
2634 ctx));
2635 else if (num_degree > den_degree)
2640 ctx));
2641
2646 const Real a0 = compact_denominator[0];
2647 const Real tol = scaled_tolerance(a0, Real(64));
2648 ah_domain_error_if(std::abs(a0) <= tol)
2649 << ctx << ": transformed analog denominator leading coefficient "
2650 << a0 << " is too small";
2651 for (size_t i = 0; i < compact_numerator.size(); ++i)
2652 compact_numerator(i) /= a0;
2653 for (size_t i = 0; i < compact_denominator.size(); ++i)
2654 compact_denominator(i) /= a0;
2655
2656 IIRCoefficients coeffs;
2657 coeffs.numerator = std::move(compact_numerator);
2658 coeffs.denominator = std::move(compact_denominator);
2659 return coeffs;
2660 }
2661
2665 [[nodiscard]] static Array<Real>
2667 const char *ctx)
2668 {
2669 const Array<Complex> coeffs = polynomial_from_roots_complex(roots);
2670 Real max_coeff = Real(0);
2671 for (size_t i = 0; i < coeffs.size(); ++i)
2672 max_coeff = std::max(max_coeff,
2673 static_cast<Real>(std::abs(coeffs[i])));
2674
2675 const Real tol = (max_coeff + Real(1))
2676 * Real(1024) * std::numeric_limits<Real>::epsilon();
2678 for (size_t i = 0; i < coeffs.size(); ++i)
2679 {
2680 ah_domain_error_if(std::abs(coeffs[i].imag()) > tol)
2681 << ctx << ": root set does not generate a real polynomial";
2682 real_coeffs(i) = coeffs[i].real();
2683 }
2684
2685 return real_coeffs;
2686 }
2687
2689 [[nodiscard]] static Complex
2690 group_center(const Array<Complex> & roots) noexcept
2691 {
2692 if (roots.is_empty())
2693 return Complex(Real(0), Real(0));
2694
2695 Complex center(Real(0), Real(0));
2696 for (size_t i = 0; i < roots.size(); ++i)
2697 center += roots[i];
2698 return center / static_cast<Real>(roots.size());
2699 }
2700
2705 [[nodiscard]] static Array<Complex>
2707 {
2708 const Real tol = (max_root_radius(roots) + Real(1))
2709 * Real(2048) * std::numeric_limits<Real>::epsilon();
2710 Array<Complex> result = roots;
2711 Array<bool> paired(roots.size(), false);
2712
2713 for (size_t i = 0; i < result.size(); ++i)
2714 {
2715 if (paired[i] or std::abs(result[i].imag()) <= tol)
2716 continue;
2717
2718 size_t best = result.size();
2719 Real best_error = std::numeric_limits<Real>::infinity();
2720 const Complex target = std::conj(result[i]);
2721 for (size_t j = i + 1; j < result.size(); ++j)
2722 {
2723 if (paired[j])
2724 continue;
2725 const Real error = std::abs(result[j] - target);
2726 if (error < best_error)
2727 {
2728 best_error = error;
2729 best = j;
2730 }
2731 }
2732
2733 if (best < result.size())
2734 {
2735 const Real avg_re =
2736 (result[i].real() + result[best].real()) / Real(2);
2737 const Real avg_im =
2738 (std::abs(result[i].imag())
2739 + std::abs(result[best].imag())) / Real(2);
2740 result(i) = Complex(avg_re, avg_im);
2741 result(best) = Complex(avg_re, -avg_im);
2742 paired[i] = true;
2743 paired[best] = true;
2744 }
2745 }
2746
2747 return result;
2748 }
2749
2753 [[nodiscard]] static Array<RootGroup>
2755 const char *ctx)
2756 {
2757 if (raw_roots.is_empty())
2758 return {};
2759
2761
2762 const Real tol = (max_root_radius(roots) + Real(1))
2763 * Real(2048) * std::numeric_limits<Real>::epsilon();
2764 Array<bool> used(roots.size(), false);
2766
2767 for (size_t i = 0; i < roots.size(); ++i)
2768 {
2769 if (used[i] or std::abs(roots[i].imag()) <= tol)
2770 continue;
2771
2772 size_t best = roots.size();
2773 Real best_error = std::numeric_limits<Real>::infinity();
2774 const Complex target = std::conj(roots[i]);
2775 for (size_t j = i + 1; j < roots.size(); ++j)
2776 {
2777 if (used[j])
2778 continue;
2779
2780 const Real error = std::abs(roots[j] - target);
2781 if (error < best_error)
2782 {
2783 best_error = error;
2784 best = j;
2785 }
2786 }
2787
2788 ah_domain_error_if(best == roots.size()
2789 or best_error > tol * (Real(16)
2790 + std::abs(roots[i])))
2791 << ctx << ": complex root " << roots[i] << " does not have a matching conjugate";
2792
2794 group.roots = {roots[i], roots[best]};
2795 group.center = group_center(group.roots);
2796 groups.append(group);
2797 used[i] = true;
2798 used[best] = true;
2799 }
2800
2802 for (size_t i = 0; i < roots.size(); ++i)
2803 {
2804 if (used[i])
2805 continue;
2806
2807 ah_domain_error_if(std::abs(roots[i].imag()) > tol)
2808 << ctx << ": root " << roots[i] << " was not paired into a real section";
2809 real_roots.append(Complex(roots[i].real(), Real(0)));
2810 }
2811
2812 Array<bool> real_used(real_roots.size(), false);
2813 while (true)
2814 {
2815 size_t first = real_roots.size();
2816 for (size_t i = 0; i < real_roots.size(); ++i)
2817 if (not real_used[i])
2818 {
2819 first = i;
2820 break;
2821 }
2822
2823 if (first == real_roots.size())
2824 break;
2825
2826 size_t second = real_roots.size();
2827 Real best_distance = std::numeric_limits<Real>::infinity();
2828 for (size_t j = first + 1; j < real_roots.size(); ++j)
2829 {
2830 if (real_used[j])
2831 continue;
2832
2833 const Real distance = std::abs(real_roots[first] - real_roots[j]);
2834 if (distance < best_distance)
2835 {
2836 best_distance = distance;
2837 second = j;
2838 }
2839 }
2840
2842 group.roots.append(real_roots[first]);
2843 real_used[first] = true;
2844 if (second != real_roots.size())
2845 {
2846 group.roots.append(real_roots[second]);
2847 real_used[second] = true;
2848 }
2849 group.center = group_center(group.roots);
2850 groups.append(group);
2851 }
2852
2853 return groups;
2854 }
2855
2859 [[nodiscard]] static BiquadSection
2861 const Array<Real> & denominator,
2862 const char *ctx)
2863 {
2864 const IIRCoefficients coeffs =
2865 normalize_iir_coefficients(numerator, denominator, ctx);
2867 << ctx << ": section denominator order " << (coeffs.denominator.size() - 1)
2868 << " exceeds second order";
2869
2873 for (size_t i = 0; i < 3; ++i)
2874 {
2875 b(i) = Real(0);
2876 a(i) = Real(0);
2877 }
2878
2879 for (size_t i = 0; i < coeffs.numerator.size(); ++i)
2880 b(i) = coeffs.numerator[i];
2881 for (size_t i = 0; i < coeffs.denominator.size(); ++i)
2882 a(i) = coeffs.denominator[i];
2883
2884 section.b0 = b[0];
2885 section.b1 = b[1];
2886 section.b2 = b[2];
2887 section.a0 = a[0];
2888 section.a1 = a[1];
2889 section.a2 = a[2];
2890 return section;
2891 }
2892
2894 [[nodiscard]] static IIRCoefficients
2897 const Real sample_rate,
2898 const char *ctx)
2899 {
2901 << ctx << ": analog numerator must be non-empty";
2903 << ctx << ": analog denominator must be non-empty";
2904
2905 const size_t order =
2906 std::max(analog_numerator.size(), analog_denominator.size()) - 1;
2909 order,
2911 ctx);
2914 order,
2916 ctx);
2919 ctx);
2920 }
2921
2925 [[nodiscard]] static Real
2927 const Real sample_rate,
2928 const char *ctx)
2929 {
2931 << ctx << ": sample rate " << sample_rate << " must be positive";
2933 << ctx << ": cutoff frequency " << cutoff_frequency
2934 << " must be positive";
2936 << ctx << ": cutoff frequency " << cutoff_frequency
2937 << " must be smaller than Nyquist " << (sample_rate / Real(2));
2938
2939 return Real(2) * sample_rate
2940 * std::tan(std::numbers::pi_v<Real>
2942 }
2943
2944 // -----------------------------------------------------------------
2945 // Elliptic integral portability layer.
2946 //
2947 // std::comp_ellint_1 and std::ellint_1 are C++17 special math
2948 // functions fully supported in libstdc++ but absent from libc++.
2949 // The fallback uses the Arithmetic-Geometric Mean (AGM) iteration,
2950 // which converges quadratically and is accurate to machine epsilon
2951 // in a handful of iterations.
2952 // -----------------------------------------------------------------
2953
2954# if defined(__GLIBCXX__) or defined(_MSVC_STL_VERSION)
2955
2956 [[nodiscard]] static Real
2957 ellint_1_impl(const Real k, const Real phi)
2958 {
2959 return std::ellint_1(k, phi);
2960 }
2961
2962 [[nodiscard]] static Real
2964 {
2965 return std::comp_ellint_1(k);
2966 }
2967
2968# else // libc++ or other stdlib without C++17 special math
2969
2970 [[nodiscard]] static Real
2972 {
2973 // Complete elliptic integral K(k) via AGM iteration.
2974 Real a = Real(1);
2975 Real b = std::sqrt(std::max(Real(0), Real(1) - k * k));
2976 for (size_t i = 0; i < 64 and std::abs(a - b) >
2977 std::numeric_limits<Real>::epsilon() * a; ++i)
2978 {
2979 const Real next_a = (a + b) / Real(2);
2980 b = std::sqrt(a * b);
2981 a = next_a;
2982 }
2983 return std::numbers::pi_v<Real> / (Real(2) * a);
2984 }
2985
2986 [[nodiscard]] static Real
2988 {
2989 // Carlson's symmetric elliptic integral R_F(x, y, z) via the
2990 // duplication theorem (Carlson 1979). Converges cubically —
2991 // typically 5-6 iterations for double precision.
2992 const Real errtol =
2993 std::pow(std::numeric_limits<Real>::epsilon() / Real(3),
2994 Real(1) / Real(6));
2995 for (size_t i = 0; i < 64; ++i)
2996 {
2997 const Real mu = (x + y + z) / Real(3);
2998 const Real dx = Real(1) - x / mu;
2999 const Real dy = Real(1) - y / mu;
3000 const Real dz = Real(1) - z / mu;
3001 if (std::max({std::abs(dx), std::abs(dy), std::abs(dz)})
3002 < errtol)
3003 {
3004 const Real e2 = dx * dy - dz * dz;
3005 const Real e3 = dx * dy * dz;
3006 return (Real(1) - e2 / Real(10) + e3 / Real(14)
3007 + e2 * e2 / Real(24)
3008 - Real(3) * e2 * e3 / Real(44))
3009 / std::sqrt(mu);
3010 }
3011 const Real lam = std::sqrt(x * y) + std::sqrt(x * z)
3012 + std::sqrt(y * z);
3013 x = (x + lam) / Real(4);
3014 y = (y + lam) / Real(4);
3015 z = (z + lam) / Real(4);
3016 }
3017 return Real(1) / std::sqrt((x + y + z) / Real(3));
3018 }
3019
3020 [[nodiscard]] static Real
3022 {
3023 // Incomplete elliptic integral F(phi, k) via Carlson's R_F.
3024 // F(phi, k) = sin(phi) * R_F(cos^2(phi), 1 - k^2 sin^2(phi), 1).
3025 // Uses the identity F(pi/2, k) = K(k) for the complete case.
3026 const Real half_pi = std::numbers::pi_v<Real> / Real(2);
3027 if (std::abs(phi - half_pi) <=
3028 std::numeric_limits<Real>::epsilon() * Real(4))
3029 return comp_ellint_1_impl(k);
3030
3031 if (std::abs(phi) <=
3032 std::numeric_limits<Real>::epsilon() * Real(4))
3033 return Real(0);
3034
3035 const Real s = std::sin(phi);
3036 const Real c = std::cos(phi);
3037 return s * carlson_rf_impl(c * c,
3038 std::max(Real(0),
3039 Real(1) - k * k * s * s),
3040 Real(1));
3041 }
3042
3043# endif // stdlib dispatch
3044
3048 [[nodiscard]] static Real
3050 const char *ctx)
3051 {
3053 << ctx << ": elliptic modulus " << modulus << " must lie in [0, 1)";
3055 }
3056
3060 [[nodiscard]] static JacobiValues
3062 const Real modulus,
3063 const char *ctx)
3064 {
3066 << ctx << ": elliptic modulus " << modulus << " must lie in [0, 1)";
3067
3068 if (std::abs(argument) <= scaled_tolerance(argument, Real(4096)))
3069 return {};
3070
3071 const bool negative = argument < Real(0);
3072 const Real u = std::abs(argument);
3073 if (modulus <= scaled_tolerance(modulus, Real(4096)))
3074 {
3075 JacobiValues values;
3076 values.sn = std::sin(u);
3077 values.cn = std::cos(u);
3078 values.dn = Real(1);
3079 if (negative)
3080 values.sn = -values.sn;
3081 return values;
3082 }
3083
3085 const Real endpoint_tol = scaled_tolerance(K, Real(4096));
3086 if (std::abs(u - K) <= endpoint_tol)
3087 {
3088 JacobiValues values;
3089 values.sn = Real(1);
3090 values.cn = Real(0);
3091 values.dn = std::sqrt(std::max(Real(0),
3092 Real(1) - modulus * modulus));
3093 if (negative)
3094 values.sn = -values.sn;
3095 return values;
3096 }
3097
3099 << ctx << ": Jacobi argument " << u
3100 << " exceeds the supported quarter period " << K;
3101
3102 Real lower = Real(0);
3103 Real upper = std::numbers::pi_v<Real> / Real(2);
3104 Real phi = upper * (u / K);
3105 phi = std::clamp(phi,
3106 std::numeric_limits<Real>::epsilon(),
3107 upper - std::numeric_limits<Real>::epsilon());
3108
3109 for (size_t iter = 0; iter < 96; ++iter)
3110 {
3112 const Real residual = value - u;
3113 if (std::abs(residual) <= scaled_tolerance(u, Real(8192)))
3114 break;
3115
3116 if (residual > Real(0))
3117 upper = phi;
3118 else
3119 lower = phi;
3120
3121 const Real sin_phi = std::sin(phi);
3122 const Real derivative =
3123 Real(1) / std::sqrt(std::max(Real(0),
3124 Real(1)
3125 - modulus * modulus
3126 * sin_phi * sin_phi));
3127 Real candidate = phi - residual / derivative;
3129 or not std::isfinite(candidate))
3130 candidate = (lower + upper) / Real(2);
3131 phi = candidate;
3132 }
3133
3134 JacobiValues values;
3135 values.sn = std::sin(phi);
3136 values.cn = std::cos(phi);
3137 values.dn = std::sqrt(std::max(Real(0),
3138 Real(1)
3139 - modulus * modulus
3140 * values.sn * values.sn));
3141 if (negative)
3142 values.sn = -values.sn;
3143 return values;
3144 }
3145
3146 [[nodiscard]] static Real
3148 const Real modulus,
3149 const char *ctx)
3150 {
3152 << ctx << ": inverse Jacobi sc value " << value
3153 << " must be non-negative";
3155 << ctx << ": elliptic modulus " << modulus
3156 << " must lie in [0, 1)";
3157 return ellint_1_impl(modulus, std::atan(value));
3158 }
3159
3160 [[nodiscard]] static Real
3162 const size_t order,
3163 const char *ctx)
3164 {
3165 ah_invalid_argument_if(order == 0)
3166 << ctx << ": order must be positive";
3167 ah_invalid_argument_if(k1 <= Real(0) or k1 >= Real(1))
3168 << ctx << ": selectivity modulus " << k1
3169 << " must lie in (0, 1)";
3170
3171 const Real k1_complement =
3172 std::sqrt(std::max(Real(0), Real(1) - k1 * k1));
3173 const Real target =
3175 / (static_cast<Real>(order)
3177
3178 Real lower = Real(0);
3179 Real upper = Real(1) - std::sqrt(std::numeric_limits<Real>::epsilon());
3180 for (size_t iter = 0; iter < 128; ++iter)
3181 {
3182 const Real mid = (lower + upper) / Real(2);
3183 const Real mid_complement =
3184 std::sqrt(std::max(Real(0), Real(1) - mid * mid));
3185 const Real ratio =
3188 if (ratio > target)
3189 lower = mid;
3190 else
3191 upper = mid;
3192 }
3193
3194 return (lower + upper) / Real(2);
3195 }
3196
3197 [[nodiscard]] static Complex
3199 const Real v,
3200 const Real modulus,
3201 const char *ctx)
3202 {
3203 const JacobiValues uv = jacobi_sn_cn_dn(u, modulus, ctx);
3204 const Real complement =
3205 std::sqrt(std::max(Real(0), Real(1) - modulus * modulus));
3206 const JacobiValues vv = jacobi_sn_cn_dn(v, complement, ctx);
3207 const Complex cn(uv.cn * vv.cn,
3208 uv.sn * uv.dn * vv.sn * vv.dn);
3209 const Complex dn(uv.dn * vv.cn * vv.dn,
3210 modulus * modulus * uv.sn * uv.cn * vv.sn);
3211 ah_domain_error_if(std::abs(dn) <= scaled_tolerance(std::abs(dn), Real(4096)))
3212 << ctx << ": elliptic cd denominator vanished";
3213 return cn / dn;
3214 }
3215
3216 [[nodiscard]] static AnalogPrototype
3217 butterworth_prototype(const size_t order,
3218 const char *ctx)
3219 {
3220 ah_invalid_argument_if(order == 0)
3221 << ctx << ": order must be positive";
3222
3225 prototype.gain = Real(1);
3226 for (size_t k = 0; k < order; ++k)
3227 {
3228 const Real angle =
3229 std::numbers::pi_v<Real>
3230 * (Real(2) * static_cast<Real>(k)
3231 + Real(1)
3232 + static_cast<Real>(order))
3233 / (Real(2) * static_cast<Real>(order));
3234 prototype.poles(k) = std::polar(Real(1), angle);
3235 }
3236
3237 return prototype;
3238 }
3239
3240 [[nodiscard]] static AnalogPrototype
3241 chebyshev1_prototype(const size_t order,
3242 const Real ripple_db,
3243 const char *ctx)
3244 {
3245 ah_invalid_argument_if(order == 0)
3246 << ctx << ": order must be positive";
3248 << ctx << ": passband ripple " << ripple_db
3249 << " must be positive in decibels";
3250
3251 const Real epsilon =
3252 std::sqrt(std::pow(Real(10), ripple_db / Real(10)) - Real(1));
3253 const Real mu = std::asinh(Real(1) / epsilon)
3254 / static_cast<Real>(order);
3255
3258 for (size_t k = 0; k < order; ++k)
3259 {
3260 const Real theta =
3261 std::numbers::pi_v<Real>
3262 * (Real(2) * static_cast<Real>(k)
3263 + Real(1))
3264 / (Real(2) * static_cast<Real>(order));
3265 prototype.poles(k) =
3266 Complex(-std::sinh(mu) * std::sin(theta),
3267 std::cosh(mu) * std::cos(theta));
3268 }
3269
3270 const Array<Real> denominator =
3272 prototype.gain = denominator[denominator.size() - 1];
3273 if (order % 2 == 0)
3274 prototype.gain /= std::sqrt(Real(1) + epsilon * epsilon);
3275
3276 return prototype;
3277 }
3278
3279 [[nodiscard]] static AnalogPrototype
3280 chebyshev2_prototype(const size_t order,
3281 const Real attenuation_db,
3282 const char *ctx)
3283 {
3284 ah_invalid_argument_if(order == 0)
3285 << ctx << ": order must be positive";
3286 ah_invalid_argument_if(attenuation_db <= Real(0))
3287 << ctx << ": stopband attenuation " << attenuation_db
3288 << " must be positive in decibels";
3289
3290 const Real epsilon =
3291 Real(1) / std::sqrt(std::pow(Real(10), attenuation_db / Real(10))
3292 - Real(1));
3293 const Real mu = std::asinh(Real(1) / epsilon)
3294 / static_cast<Real>(order);
3295
3297 prototype.zeros.reserve(order);
3298 prototype.poles = Array<Complex>::create(order);
3299 for (size_t k = 0; k < order; ++k)
3300 {
3301 const Real theta =
3302 std::numbers::pi_v<Real>
3303 * (Real(2) * static_cast<Real>(k) + Real(1))
3304 / (Real(2) * static_cast<Real>(order));
3305 const Complex inverse_pole(-std::sinh(mu) * std::sin(theta),
3306 std::cosh(mu) * std::cos(theta));
3307 prototype.poles(k) = Complex(Real(1), Real(0)) / inverse_pole;
3308 }
3309
3310 for (size_t k = 0; k < order / 2; ++k)
3311 {
3312 const Real theta =
3313 std::numbers::pi_v<Real>
3314 * (Real(2) * static_cast<Real>(k) + Real(1))
3315 / (Real(2) * static_cast<Real>(order));
3316 const Real imag = Real(1) / std::cos(theta);
3317 prototype.zeros.append(Complex(Real(0), imag));
3318 prototype.zeros.append(Complex(Real(0), -imag));
3319 }
3320
3321 Array<Real> numerator = {Real(1)};
3322 if (not prototype.zeros.is_empty())
3323 numerator = real_polynomial_from_roots(prototype.zeros, ctx);
3324 const Array<Real> denominator =
3326 prototype.gain =
3327 denominator[denominator.size() - 1] / numerator[numerator.size() - 1];
3328 return prototype;
3329 }
3330
3331 [[nodiscard]] static AnalogPrototype
3332 elliptic_prototype(const size_t order,
3333 const Real ripple_db,
3334 const Real attenuation_db,
3335 const char *ctx)
3336 {
3337 ah_invalid_argument_if(order == 0)
3338 << ctx << ": order must be positive";
3340 << ctx << ": passband ripple " << ripple_db
3341 << " must be positive in decibels";
3342 ah_invalid_argument_if(attenuation_db <= ripple_db)
3343 << ctx << ": stopband attenuation " << attenuation_db
3344 << " must exceed passband ripple " << ripple_db;
3345
3346 const Real epsilon =
3347 std::sqrt(std::pow(Real(10), ripple_db / Real(10)) - Real(1));
3348 const Real stop_term =
3349 std::pow(Real(10), attenuation_db / Real(10)) - Real(1);
3351 << ctx << ": stopband attenuation produced a non-positive stop term";
3352
3353 const Real k1 = epsilon / std::sqrt(stop_term);
3354 const Real k1_tol = scaled_tolerance(k1, Real(4096));
3355 if (k1 <= k1_tol)
3356 return chebyshev1_prototype(order, ripple_db, ctx);
3357
3360 if (modulus <= modulus_tol)
3361 return chebyshev1_prototype(order, ripple_db, ctx);
3362
3364 const Real K1 = complete_elliptic_first_kind(k1, ctx);
3365 const Real k1_complement =
3366 std::sqrt(std::max(Real(0), Real(1) - k1 * k1));
3367 const Real r = inverse_jacobi_sc(Real(1) / epsilon,
3369 ctx);
3370 const Real v0 =
3371 K * r / (static_cast<Real>(order) * K1);
3372
3374 prototype.zeros.reserve(order);
3375 prototype.poles.reserve(order);
3376
3377 const size_t pair_count = order / 2;
3378 for (size_t i = 0; i < pair_count; ++i)
3379 {
3380 const Real u =
3381 (Real(2) * static_cast<Real>(i) + Real(1)) * K
3382 / static_cast<Real>(order);
3383 const JacobiValues values = jacobi_sn_cn_dn(u, modulus, ctx);
3384 const Real zero_scale = modulus * values.sn;
3386 <= scaled_tolerance(zero_scale, Real(4096)))
3387 << ctx << ": elliptic prototype zero scale vanished";
3388 const Real zero_imag = Real(1) / zero_scale;
3389 prototype.zeros.append(Complex(Real(0), zero_imag));
3390 prototype.zeros.append(Complex(Real(0), -zero_imag));
3391
3392 Complex pole =
3393 Complex(Real(0), Real(1))
3395 if (pole.real() > Real(0))
3396 pole = -pole;
3397 if (pole.imag() < Real(0))
3398 pole = std::conj(pole);
3399
3400 prototype.poles.append(pole);
3401 prototype.poles.append(std::conj(pole));
3402 }
3403
3404 if ((order & 1) != 0)
3405 {
3406 const Real complement =
3407 std::sqrt(std::max(Real(0), Real(1) - modulus * modulus));
3408 const JacobiValues values = jacobi_sn_cn_dn(v0, complement, ctx);
3409 ah_domain_error_if(std::abs(values.cn)
3410 <= scaled_tolerance(values.cn, Real(4096)))
3411 << ctx << ": elliptic prototype real pole denominator vanished";
3412 prototype.poles.append(Complex(-values.sn / values.cn, Real(0)));
3413 }
3414
3415 Array<Real> numerator = {Real(1)};
3416 if (not prototype.zeros.is_empty())
3417 numerator = real_polynomial_from_roots(prototype.zeros, ctx);
3418 const Array<Real> denominator =
3420 const Real target_gain =
3421 (order & 1) == 0 ?
3422 Real(1) / std::sqrt(Real(1) + epsilon * epsilon) :
3423 Real(1);
3424 prototype.gain =
3425 target_gain * denominator[denominator.size() - 1]
3426 / numerator[numerator.size() - 1];
3427 return prototype;
3428 }
3429
3431 [[nodiscard]] static Real
3433 const Array<Real> & denominator,
3434 const Real omega,
3435 const char *ctx)
3436 {
3439 Complex(Real(0), omega),
3440 ctx);
3443 Complex(Real(0), omega),
3444 ctx);
3445 const Real den_tol = scaled_tolerance(max_abs_value(denominator),
3446 Real(4096));
3447 ah_domain_error_if(std::abs(den_eval.value) <= den_tol)
3448 << ctx << ": analog transfer response is singular at omega="
3449 << omega;
3450 return std::abs(num_eval.value / den_eval.value);
3451 }
3452
3456 [[nodiscard]] static Array<Real>
3457 reverse_bessel_polynomial(const size_t order,
3458 const char *ctx)
3459 {
3460 ah_invalid_argument_if(order == 0)
3461 << ctx << ": order must be positive";
3462
3463 Array<Real> coeffs = Array<Real>::create(order + 1);
3464 for (size_t k = 0; k <= order; ++k)
3465 {
3466 const long double numerator =
3467 std::tgamma(static_cast<long double>(2 * order - k + 1));
3468 const long double denominator =
3469 std::pow(2.0L, static_cast<long double>(order - k))
3470 * std::tgamma(static_cast<long double>(order - k + 1))
3471 * std::tgamma(static_cast<long double>(k + 1));
3472 coeffs(order - k) =
3473 static_cast<Real>(numerator / denominator);
3474 }
3475 return coeffs;
3476 }
3477
3479 [[nodiscard]] static AnalogPrototype
3480 bessel_prototype(const size_t order,
3481 const char *ctx)
3482 {
3484 reverse_bessel_polynomial(order, ctx);
3485 const Real base_gain =
3487 const Real target = Real(1) / std::sqrt(Real(2));
3488
3489 Real upper = Real(1);
3492 upper,
3493 ctx) > target)
3494 {
3495 upper *= Real(2);
3497 << ctx << ": could not bracket the -3 dB normalization point";
3498 }
3499
3500 Real lower = Real(0);
3501 for (size_t iter = 0; iter < 96; ++iter)
3502 {
3503 const Real mid = (lower + upper) / Real(2);
3504 const Real magnitude =
3507 mid,
3508 ctx);
3509 if (magnitude > target)
3510 lower = mid;
3511 else
3512 upper = mid;
3513 }
3514
3515 const Real scale = upper;
3517 for (size_t i = 0; i < poles.size(); ++i)
3518 poles(i) *= scale;
3519
3521 prototype.poles = std::move(poles);
3525 return prototype;
3526 }
3527
3531 const Real cutoff_frequency,
3532 const Real sample_rate,
3533 const bool highpass,
3534 const char *ctx)
3535 {
3536 const Real warped_cutoff =
3542 ctx);
3543 }
3544
3547 const size_t order,
3548 const Real cutoff_frequency,
3549 const Real sample_rate,
3550 const bool highpass,
3551 const char *ctx)
3552 {
3553 ah_invalid_argument_if(prototype.poles.size() != order)
3554 << ctx << ": prototype order " << prototype.poles.size()
3555 << " does not match requested order " << order;
3556
3557 const Real warped_cutoff =
3561 const Real prototype_constant =
3565 prototype.gain * integer_power(warped_cutoff, order);
3566
3568 for (size_t i = 0; i < order; ++i)
3570 Complex(warped_cutoff, Real(0)) / prototype.poles[i] :
3571 Complex(warped_cutoff, Real(0)) * prototype.poles[i];
3572
3577 for (size_t i = 0; i < pole_groups.size(); ++i)
3578 {
3579 const size_t degree = pole_groups[i].roots.size();
3583 for (size_t j = 0; j < analog_numerator.size(); ++j)
3584 analog_numerator(j) = Real(0);
3585
3586 if (highpass)
3587 analog_numerator(0) = i == 0 ? first_section_gain : Real(1);
3588 else
3589 analog_numerator(degree) = i == 0 ? first_section_gain : Real(1);
3590
3591 const IIRCoefficients digital =
3595 ctx);
3597 digital.denominator,
3598 ctx));
3599 }
3600
3601 return sections;
3602 }
3603
3605 [[nodiscard]] static std::pair<Real, Real>
3608 const Real sample_rate,
3609 const char *ctx)
3610 {
3612 << ctx << ": low cutoff " << low_cutoff_frequency
3613 << " must be positive";
3615 << ctx << ": high cutoff " << high_cutoff_frequency
3616 << " must be greater than low cutoff " << low_cutoff_frequency;
3618 << ctx << ": high cutoff " << high_cutoff_frequency
3619 << " must be smaller than Nyquist " << (sample_rate / Real(2));
3620
3621 return {
3624 };
3625 }
3626
3633 const char *ctx)
3634 {
3637
3638 Array<bool> zero_used(zero_groups.size(), false);
3641 for (size_t i = 0; i < pole_groups.size(); ++i)
3642 {
3643 size_t best = zero_groups.size();
3644 Real best_cost = std::numeric_limits<Real>::infinity();
3645 for (size_t j = 0; j < zero_groups.size(); ++j)
3646 {
3647 if (zero_used[j]
3648 or zero_groups[j].roots.size() > pole_groups[i].roots.size())
3649 continue;
3650
3651 const Real size_penalty =
3652 zero_groups[j].roots.size() == pole_groups[i].roots.size() ? Real(0) : Real(16);
3653 const Real cost =
3654 std::abs(zero_groups[j].center - pole_groups[i].center)
3655 + size_penalty;
3656 if (cost < best_cost)
3657 {
3658 best = j;
3659 best_cost = cost;
3660 }
3661 }
3662
3664 if (best != zero_groups.size())
3665 {
3668 zero_used(best) = true;
3669 }
3670
3673 if (i == 0)
3674 for (size_t k = 0; k < section_numerator.size(); ++k)
3675 section_numerator(k) *= coeffs.numerator[0];
3676
3679 ctx));
3680 }
3681
3682 for (size_t i = 0; i < zero_groups.size(); ++i)
3684 << ctx << ": could not assign finite-zero group " << i
3685 << " to any SOS section";
3686
3687 return sections;
3688 }
3689
3693 const Array<Real> & denominator,
3694 const char *ctx)
3695 {
3696 const IIRCoefficients coeffs =
3697 normalize_iir_coefficients(numerator, denominator, ctx);
3698 const Array<Complex> zero_roots = zeros(coeffs.numerator);
3701 return transfer_function_to_sections(coeffs, zero_groups, ctx);
3702 }
3703
3707 const size_t order,
3710 const Real sample_rate,
3711 const char *ctx)
3712 {
3713 ah_invalid_argument_if(not prototype.zeros.is_empty())
3714 << ctx << ": specialized band-stop path expects prototypes without zeros";
3715
3716 const auto [warped_low, warped_high] =
3720 ctx);
3722 const Real center = std::sqrt(warped_low * warped_high);
3725 const IIRCoefficients analog =
3728 {bandwidth, Real(0)},
3729 {Real(1), Real(0), center * center},
3730 ctx);
3731 const IIRCoefficients digital =
3733 analog.denominator,
3735 ctx);
3736
3737 const Complex analog_zero(Real(0), center);
3738 const Complex digital_zero =
3740 / (Complex(Real(2) * sample_rate, Real(0)) + analog_zero);
3741
3743 zero_groups.reserve(order);
3744 for (size_t i = 0; i < order; ++i)
3745 {
3747 group.roots = {digital_zero, std::conj(digital_zero)};
3748 group.center = group_center(group.roots);
3749 zero_groups.append(std::move(group));
3750 }
3751
3753 }
3754
3758 const Array<Real> & map_numerator,
3760 const Real sample_rate,
3761 const char *ctx)
3762 {
3764 if (not prototype.zeros.is_empty())
3765 {
3768 for (size_t i = 0; i < prototype_numerator.size(); ++i)
3769 prototype_numerator(i) *= prototype.gain;
3770 }
3773 const IIRCoefficients analog =
3778 ctx);
3779 const IIRCoefficients digital =
3781 analog.denominator,
3783 ctx);
3784 return transfer_function_to_sections(digital.numerator,
3785 digital.denominator,
3786 ctx);
3787 }
3788
3790 [[nodiscard]] static Real
3791 modified_bessel_i0(const Real x) noexcept
3792 {
3793 const Real half_x = x / Real(2);
3794 const Real half_x_sq = half_x * half_x;
3795 Real sum = Real(1);
3796 Real term = Real(1);
3797 for (size_t k = 1; k < 80; ++k)
3798 {
3799 term *= half_x_sq
3800 / (static_cast<Real>(k) * static_cast<Real>(k));
3801 sum += term;
3802 if (std::abs(term) <= std::numeric_limits<Real>::epsilon() * sum)
3803 break;
3804 }
3805 return sum;
3806 }
3807
3809 [[nodiscard]] static Real
3810 normalized_sinc(const Real x) noexcept
3811 {
3812 const Real pix = std::numbers::pi_v<Real> * x;
3813 if (std::abs(pix) <= std::numeric_limits<Real>::epsilon())
3814 return Real(1);
3815 return std::sin(pix) / pix;
3816 }
3817
3819 [[nodiscard]] static Real
3821 const Real omega,
3822 const char *ctx)
3823 {
3824 return std::abs(evaluate_transfer_at(coeffs,
3825 Array<Real>({Real(1)}),
3826 omega,
3827 ctx));
3828 }
3829
3831 static void
3833 const Real omega,
3834 const char *ctx)
3835 {
3836 const Real gain = evaluate_fir_response_magnitude(coeffs, omega, ctx);
3837 const Real tol = scaled_tolerance(gain, Real(4096));
3838 ah_domain_error_if(gain <= tol)
3839 << ctx << ": FIR gain vanished at normalization omega=" << omega;
3840 for (size_t i = 0; i < coeffs.size(); ++i)
3841 coeffs(i) /= gain;
3842 }
3843
3845 [[nodiscard]] static Array<Real>
3847 const Real cutoff_frequency,
3848 const Real sample_rate,
3849 const Array<Real> & window,
3850 const char *ctx)
3851 {
3853 << ctx << ": num_taps must be positive";
3855 << ctx << ": window size " << window.size()
3856 << " does not match num_taps " << num_taps;
3858 << ctx << ": sample rate " << sample_rate << " must be positive";
3860 << ctx << ": cutoff frequency " << cutoff_frequency
3861 << " must be positive";
3863 << ctx << ": cutoff frequency " << cutoff_frequency
3864 << " must be smaller than Nyquist " << (sample_rate / Real(2));
3865
3867 const Real center = static_cast<Real>(num_taps - 1) / Real(2);
3869 for (size_t n = 0; n < num_taps; ++n)
3870 {
3871 const Real m = static_cast<Real>(n) - center;
3872 const Real ideal =
3875 coeffs(n) = ideal * window[n];
3876 }
3877
3878 normalize_fir_at_omega(coeffs, Real(0), ctx);
3879 return coeffs;
3880 }
3881
3883 [[nodiscard]] static Real
3885 const Real omega_hi,
3886 const size_t harmonic) noexcept
3887 {
3888 if (harmonic == 0)
3889 return omega_hi - omega_lo;
3890
3891 const Real k = static_cast<Real>(harmonic);
3892 return (std::sin(k * omega_hi) - std::sin(k * omega_lo)) / k;
3893 }
3894
3896 [[nodiscard]] static Real
3898 const Real omega_hi,
3899 const size_t lhs_harmonic,
3900 const size_t rhs_harmonic) noexcept
3901 {
3903 return Real(0.5) * (integrate_cos_basis(omega_lo, omega_hi, delta)
3905 omega_hi,
3907 }
3908
3910 [[nodiscard]] static Real
3912 const Real omega_hi,
3913 const size_t harmonic) noexcept
3914 {
3915 const Real width = omega_hi - omega_lo;
3916 if (harmonic == 0)
3917 return Real(0.5) * width * width;
3918
3919 const Real k = static_cast<Real>(harmonic);
3920 return width * std::sin(k * omega_hi) / k
3921 + (std::cos(k * omega_hi) - std::cos(k * omega_lo)) / (k * k);
3922 }
3923
3925 [[nodiscard]] static Real
3927 const Real omega_hi,
3928 const Real desired_lo,
3929 const Real desired_hi,
3930 const size_t harmonic) noexcept
3931 {
3932 const Real width = omega_hi - omega_lo;
3933 if (width <= Real(0))
3934 return Real(0);
3935
3936 const Real slope = (desired_hi - desired_lo) / width;
3939 omega_hi,
3940 harmonic);
3941 }
3942
3944 [[nodiscard]] static Array<Real>
3945 firls_impl(const size_t num_taps,
3946 const Array<Real> & bands,
3947 const Array<Real> & desired,
3948 const Real sample_rate,
3949 const Array<Real> & weights,
3950 const char *ctx)
3951 {
3953 << ctx << ": num_taps must be positive";
3955 << ctx << ": num_taps must be odd for a Type-I linear-phase design";
3957 << ctx << ": sample rate " << sample_rate << " must be positive";
3958 ah_invalid_argument_if(bands.is_empty() or (bands.size() & 1) != 0)
3959 << ctx << ": bands must contain an even number of band edges";
3960 ah_invalid_argument_if(desired.size() != bands.size())
3961 << ctx << ": desired size " << desired.size()
3962 << " does not match band edge count " << bands.size();
3963
3964 const size_t band_count = bands.size() / 2;
3965 ah_invalid_argument_if(not weights.is_empty() and weights.size() != band_count)
3966 << ctx << ": weights size " << weights.size()
3967 << " does not match band count " << band_count;
3968
3969 const Real nyquist = sample_rate / Real(2);
3971
3972 ah_invalid_argument_if(std::abs(bands[0]) > edge_tol)
3973 << ctx << ": first band edge " << bands[0] << " must start at 0";
3974 ah_invalid_argument_if(std::abs(bands[bands.size() - 1] - nyquist) > edge_tol)
3975 << ctx << ": last band edge " << bands[bands.size() - 1]
3976 << " must reach Nyquist " << nyquist;
3977
3980 if (weights.is_empty())
3981 for (size_t i = 0; i < band_count; ++i)
3982 normalized_weights.append(Real(1));
3983 else
3984 for (size_t i = 0; i < band_count; ++i)
3985 {
3986 ah_invalid_argument_if(weights[i] <= Real(0))
3987 << ctx << ": weight " << weights[i]
3988 << " at band " << i << " must be positive";
3989 normalized_weights.append(weights[i]);
3990 }
3991
3992 Array<Real> omega = Array<Real>::create(bands.size());
3993 Real previous_edge = Real(0);
3994 for (size_t i = 0; i < bands.size(); ++i)
3995 {
3996 const Real edge = i == 0 ? Real(0) : (i + 1 == bands.size() ? nyquist : bands[i]);
3998 << ctx << ": band edge " << edge << " lies outside [0, Nyquist]";
4000 << ctx << ": band edges must be non-decreasing";
4001 if ((i & 1) == 1)
4003 << ctx << ": band " << (i / 2)
4004 << " has non-positive width";
4005
4006 omega(i) = Real(2) * std::numbers::pi_v<Real> * edge / sample_rate;
4007 previous_edge = edge;
4008 }
4009
4010 const size_t order = (num_taps - 1) / 2;
4011 const size_t dimension = order + 1;
4012 Array<Real> system = Array<Real>::create(dimension * dimension);
4013 Array<Real> rhs = Array<Real>::create(dimension);
4014 for (size_t i = 0; i < system.size(); ++i)
4015 system(i) = Real(0);
4016 for (size_t i = 0; i < rhs.size(); ++i)
4017 rhs(i) = Real(0);
4018
4019 for (size_t band = 0; band < band_count; ++band)
4020 {
4021 const Real omega_lo = omega[band * 2];
4022 const Real omega_hi = omega[band * 2 + 1];
4023 const Real desired_lo = desired[band * 2];
4024 const Real desired_hi = desired[band * 2 + 1];
4025 const Real weight = normalized_weights[band];
4026
4027 for (size_t row = 0; row < dimension; ++row)
4028 {
4029 rhs(row) += weight * integrate_linear_cos_basis(omega_lo,
4030 omega_hi,
4031 desired_lo,
4032 desired_hi,
4033 row);
4034 for (size_t col = row; col < dimension; ++col)
4035 {
4036 const Real contribution =
4038 omega_hi,
4039 row,
4040 col);
4041 system(row * dimension + col) += contribution;
4042 if (col != row)
4043 system(col * dimension + row) += contribution;
4044 }
4045 }
4046 }
4047
4049 solve_dense_system(system, rhs, dimension, ctx);
4051 for (size_t i = 0; i < coeffs.size(); ++i)
4052 coeffs(i) = Real(0);
4053
4054 const size_t center = order;
4055 coeffs(center) = cosine_series[0];
4056 for (size_t k = 1; k < cosine_series.size(); ++k)
4057 {
4058 const Real tap = cosine_series[k] / Real(2);
4059 coeffs(center - k) = tap;
4060 coeffs(center + k) = tap;
4061 }
4062
4063 return coeffs;
4064 }
4065
4066 [[nodiscard]] static WeightedFrequencyGrid
4068 const Array<Real> & bands,
4069 const Array<Real> & desired,
4070 const Real sample_rate,
4071 const Array<Real> & weights,
4072 const size_t grid_density,
4073 const char *ctx)
4074 {
4076 << ctx << ": num_taps must be positive";
4078 << ctx << ": num_taps must be odd for a Type-I linear-phase design";
4080 << ctx << ": sample rate " << sample_rate << " must be positive";
4082 << ctx << ": grid density " << grid_density
4083 << " must be at least 8";
4084 ah_invalid_argument_if(bands.is_empty() or (bands.size() & 1) != 0)
4085 << ctx << ": bands must contain an even number of band edges";
4086 ah_invalid_argument_if(desired.size() != bands.size())
4087 << ctx << ": desired size " << desired.size()
4088 << " does not match band edge count " << bands.size();
4089
4090 const size_t band_count = bands.size() / 2;
4091 ah_invalid_argument_if(not weights.is_empty() and weights.size() != band_count)
4092 << ctx << ": weights size " << weights.size()
4093 << " does not match band count " << band_count;
4094
4095 const Real nyquist = sample_rate / Real(2);
4097 ah_invalid_argument_if(std::abs(bands[0]) > edge_tol)
4098 << ctx << ": first band edge " << bands[0] << " must start at 0";
4099 ah_invalid_argument_if(std::abs(bands[bands.size() - 1] - nyquist) > edge_tol)
4100 << ctx << ": last band edge " << bands[bands.size() - 1]
4101 << " must reach Nyquist " << nyquist;
4102
4104 const size_t half_order = (num_taps - 1) / 2;
4105 const size_t target_points = grid_density * (half_order + 2);
4106
4107 Real previous_edge = Real(0);
4108 for (size_t band = 0; band < band_count; ++band)
4109 {
4110 const Real low = bands[band * 2];
4111 const Real high = bands[band * 2 + 1];
4113 << ctx << ": band edges must lie in [0, Nyquist]";
4115 << ctx << ": band edges must be non-decreasing";
4117 << ctx << ": band " << band << " has non-positive width";
4118
4119 const Real desired_low = desired[band * 2];
4120 const Real desired_high = desired[band * 2 + 1];
4121 const Real weight = weights.is_empty() ? Real(1) : weights[band];
4122 ah_invalid_argument_if(weight <= Real(0))
4123 << ctx << ": weight " << weight << " at band " << band
4124 << " must be positive";
4125
4126 const Real omega_low =
4127 Real(2) * std::numbers::pi_v<Real> * low / sample_rate;
4128 const Real omega_high =
4129 Real(2) * std::numbers::pi_v<Real> * high / sample_rate;
4131 size_t sample_count =
4132 static_cast<size_t>(std::ceil(target_points
4133 * omega_width
4134 / std::numbers::pi_v<Real>));
4135 sample_count = std::max<size_t>(sample_count, 2);
4136
4137 for (size_t i = 0; i < sample_count; ++i)
4138 {
4139 if (band > 0 and i == 0)
4140 continue;
4141
4142 const Real t =
4143 sample_count == 1 ?
4144 Real(0) :
4145 static_cast<Real>(i)
4146 / static_cast<Real>(sample_count - 1);
4147 const Real omega = omega_low + t * omega_width;
4148 const Real desired_value =
4150 grid.omega.append(omega);
4151 grid.desired.append(desired_value);
4152 grid.weight.append(weight);
4153 }
4154
4155 previous_edge = high;
4156 }
4157
4158 ah_runtime_error_if(grid.omega.size() < half_order + 2)
4159 << ctx << ": weighted frequency grid is too small";
4160 return grid;
4161 }
4162
4163 [[nodiscard]] static Real
4165 const Real omega) noexcept
4166 {
4167 Real value = Real(0);
4168 for (size_t k = 0; k < coefficients.size(); ++k)
4169 value += coefficients[k] * std::cos(static_cast<Real>(k) * omega);
4170 return value;
4171 }
4172
4173 [[nodiscard]] static Array<size_t>
4175 const size_t extremal_count)
4176 {
4179 for (size_t i = 0; i < extremal_count; ++i)
4180 {
4181 const Real position =
4182 extremal_count == 1 ?
4183 Real(0) :
4184 static_cast<Real>(i)
4185 * static_cast<Real>(grid_size - 1)
4186 / static_cast<Real>(extremal_count - 1);
4187 extrema.append(static_cast<size_t>(std::llround(position)));
4188 }
4189 return extrema;
4190 }
4191
4192 [[nodiscard]] static Array<size_t>
4194 const size_t required_count,
4195 const char *ctx)
4196 {
4198 << ctx << ": weighted error grid has only " << weighted_error.size()
4199 << " points for " << required_count << " required extrema";
4200
4201 struct Candidate
4202 {
4203 size_t index = 0;
4204 Real magnitude = Real(0);
4205 int sign = 0;
4206 };
4207
4210 for (size_t i = 0; i < weighted_error.size(); ++i)
4211 {
4212 const Real current = weighted_error[i];
4213 const Real current_abs = std::abs(current);
4215 continue;
4216
4217 const Real left_abs =
4218 i == 0 ? Real(-1) : std::abs(weighted_error[i - 1]);
4219 const Real right_abs =
4220 i + 1 == weighted_error.size() ? Real(-1) : std::abs(weighted_error[i + 1]);
4223 continue;
4224
4225 Candidate candidate;
4226 candidate.index = i;
4227 candidate.magnitude = current_abs;
4228 candidate.sign = current >= Real(0) ? 1 : -1;
4229 candidates.append(candidate);
4230 }
4231
4232 if (candidates.size() < required_count)
4234
4235 Array<Candidate> compressed;
4236 compressed.reserve(candidates.size());
4237 compressed.append(candidates[0]);
4238 for (size_t i = 1; i < candidates.size(); ++i)
4239 {
4240 if (candidates[i].sign == compressed[compressed.size() - 1].sign)
4241 {
4242 if (candidates[i].magnitude > compressed[compressed.size() - 1].magnitude)
4243 compressed(compressed.size() - 1) = candidates[i];
4244 }
4245 else
4246 compressed.append(candidates[i]);
4247 }
4248
4249 if (compressed.size() < required_count)
4251 if (compressed.size() == required_count)
4252 {
4255 for (size_t i = 0; i < compressed.size(); ++i)
4256 extrema.append(compressed[i].index);
4257 return extrema;
4258 }
4259
4260 size_t best_start = 0;
4261 Real best_floor = Real(-1);
4262 Real best_sum = Real(-1);
4263 for (size_t start = 0; start + required_count <= compressed.size(); ++start)
4264 {
4265 Real floor = std::numeric_limits<Real>::infinity();
4266 Real sum = Real(0);
4267 for (size_t i = 0; i < required_count; ++i)
4268 {
4269 floor = std::min(floor, compressed[start + i].magnitude);
4270 sum += compressed[start + i].magnitude;
4271 }
4272
4273 if (floor > best_floor
4274 or (std::abs(floor - best_floor)
4275 <= scaled_tolerance(floor, Real(4096))
4276 and sum > best_sum))
4277 {
4278 best_floor = floor;
4279 best_sum = sum;
4280 best_start = start;
4281 }
4282 }
4283
4286 for (size_t i = 0; i < required_count; ++i)
4287 extrema.append(compressed[best_start + i].index);
4288 return extrema;
4289 }
4290
4291 [[nodiscard]] static Array<Real>
4293 const Array<Real> & desired,
4294 const Array<Real> & weight,
4295 const Array<size_t> & extrema,
4296 const size_t half_order,
4297 const char *ctx)
4298 {
4299 const size_t dimension = half_order + 2;
4300 ah_invalid_argument_if(extrema.size() != dimension)
4301 << ctx << ": expected " << dimension << " extrema but got "
4302 << extrema.size();
4303
4304 Array<Real> matrix = Array<Real>::create(dimension * dimension);
4305 Array<Real> rhs = Array<Real>::create(dimension);
4306 for (size_t i = 0; i < matrix.size(); ++i)
4307 matrix(i) = Real(0);
4308 for (size_t i = 0; i < rhs.size(); ++i)
4309 rhs(i) = Real(0);
4310
4311 for (size_t row = 0; row < dimension; ++row)
4312 {
4313 const size_t index = extrema[row];
4314 for (size_t col = 0; col <= half_order; ++col)
4315 matrix(row * dimension + col) =
4316 std::cos(static_cast<Real>(col) * omega[index]);
4317 matrix(row * dimension + half_order + 1) =
4318 ((row & 1) == 0 ? Real(1) : Real(-1)) / weight[index];
4319 rhs(row) = desired[index];
4320 }
4321
4322 return solve_dense_system(matrix, rhs, dimension, ctx);
4323 }
4324
4325 [[nodiscard]] static Array<Real>
4326 remez_impl(const size_t num_taps,
4327 const Array<Real> & bands,
4328 const Array<Real> & desired,
4329 const Real sample_rate,
4330 const Array<Real> & weights,
4331 const size_t grid_density,
4332 const size_t max_iterations,
4333 const char *ctx)
4334 {
4336 << ctx << ": max_iterations must be positive";
4337
4340 bands,
4341 desired,
4343 weights,
4345 ctx);
4346 const size_t half_order = (num_taps - 1) / 2;
4347 const size_t required_extrema = half_order + 2;
4350
4352 for (size_t i = 0; i < cosine.size(); ++i)
4353 cosine(i) = Real(0);
4354
4355 for (size_t iter = 0; iter < max_iterations; ++iter)
4356 {
4357 const Array<Real> solution =
4359 grid.desired,
4360 grid.weight,
4361 extrema,
4362 half_order,
4363 ctx);
4364 for (size_t i = 0; i < cosine.size(); ++i)
4365 cosine(i) = solution[i];
4366
4368 for (size_t i = 0; i < grid.omega.size(); ++i)
4369 {
4370 const Real approx =
4372 weighted_error(i) =
4373 grid.weight[i] * (approx - grid.desired[i]);
4374 }
4375
4379 ctx);
4380 bool unchanged = next_extrema.size() == extrema.size();
4381 for (size_t i = 0; unchanged and i < extrema.size(); ++i)
4382 unchanged = next_extrema[i] == extrema[i];
4384 if (unchanged)
4385 break;
4386 }
4387
4389 for (size_t i = 0; i < coeffs.size(); ++i)
4390 coeffs(i) = Real(0);
4391
4392 const size_t center = half_order;
4393 coeffs(center) = cosine[0];
4394 for (size_t k = 1; k < cosine.size(); ++k)
4395 {
4396 const Real tap = cosine[k] / Real(2);
4397 coeffs(center - k) = tap;
4398 coeffs(center + k) = tap;
4399 }
4400
4401 return coeffs;
4402 }
4403
4404 [[nodiscard]] static Real
4406 const Real y0,
4407 const Real x1,
4408 const Real y1,
4409 const Real target) noexcept
4410 {
4411 const Real dy = y1 - y0;
4412 if (std::abs(dy) <= std::numeric_limits<Real>::epsilon())
4413 return x0;
4414 const Real t = (target - y0) / dy;
4415 return x0 + t * (x1 - x0);
4416 }
4417
4418 [[nodiscard]] static Real
4420 const Real y0,
4421 const Real x1,
4422 const Real y1,
4423 const Real x) noexcept
4424 {
4425 const Real dx = x1 - x0;
4426 if (std::abs(dx) <= std::numeric_limits<Real>::epsilon())
4427 return y0;
4428 const Real t = (x - x0) / dx;
4429 return y0 + t * (y1 - y0);
4430 }
4431
4432 [[nodiscard]] static Real
4434 const Real reference_phase) noexcept
4435 {
4436 const Real turns =
4437 std::round((reference_phase - raw_phase)
4438 / (Real(2) * std::numbers::pi_v<Real>));
4439 return raw_phase + turns * Real(2) * std::numbers::pi_v<Real>;
4440 }
4441
4442 template <typename Evaluator>
4443 [[nodiscard]] static Real
4445 const Real x0,
4446 const Real y0,
4447 const Real x1,
4448 const Real y1,
4449 const Real target)
4450 {
4451 Real left_x = x0;
4452 Real right_x = x1;
4453 Real left_y = y0 - target;
4454 Real right_y = y1 - target;
4455
4456 if (std::abs(left_y) <= std::numeric_limits<Real>::epsilon())
4457 return left_x;
4458 if (std::abs(right_y) <= std::numeric_limits<Real>::epsilon())
4459 return right_x;
4460 if ((left_y < Real(0) and right_y < Real(0))
4461 or (left_y > Real(0) and right_y > Real(0)))
4462 return interpolate_crossing(x0, y0, x1, y1, target);
4463
4464 const Real xtol =
4465 scaled_tolerance(std::max(std::abs(x0), std::abs(x1)), Real(4096));
4466 for (size_t iter = 0; iter < 64; ++iter)
4467 {
4468 const Real mid_x = (left_x + right_x) / Real(2);
4469 const Real mid_y = evaluator(mid_x) - target;
4470 if (std::abs(mid_y) <= std::numeric_limits<Real>::epsilon()
4471 or std::abs(right_x - left_x) <= xtol)
4472 return mid_x;
4473
4474 if ((left_y < Real(0) and mid_y < Real(0))
4475 or (left_y > Real(0) and mid_y > Real(0)))
4476 {
4477 left_x = mid_x;
4478 left_y = mid_y;
4479 }
4480 else
4481 {
4482 right_x = mid_x;
4483 right_y = mid_y;
4484 }
4485 }
4486
4487 return (left_x + right_x) / Real(2);
4488 }
4489
4490 [[nodiscard]] static Complex
4492 const Real omega,
4493 const char *ctx)
4494 {
4496 << ctx << ": at least one biquad section is required";
4497
4498 Complex response(Real(1), Real(0));
4499 for (size_t i = 0; i < sections.size(); ++i)
4500 response *= evaluate_transfer_at(sections[i].numerator(),
4501 sections[i].denominator(),
4502 omega,
4503 ctx);
4504 return response;
4505 }
4506
4507 [[nodiscard]] static PhaseMarginInfo
4509 {
4510 ah_invalid_argument_if(response.omega.size() != response.response.size())
4511 << "FFT::phase_margin: omega size " << response.omega.size()
4512 << " does not match response size " << response.response.size();
4513
4515 if (response.omega.size() < 2)
4516 return info;
4517
4518 const Array<Real> magnitude = response.magnitude();
4519 const Array<Real> phase = unwrap_phase(phase_spectrum(response.response));
4520 bool found = false;
4521 Real best_margin = std::numeric_limits<Real>::infinity();
4522 Real best_omega = std::numeric_limits<Real>::quiet_NaN();
4523
4524 for (size_t i = 1; i < response.omega.size(); ++i)
4525 {
4526 const Real left = magnitude[i - 1] - Real(1);
4527 const Real right = magnitude[i] - Real(1);
4528 if ((left < Real(0) and right < Real(0))
4529 or (left > Real(0) and right > Real(0)))
4530 continue;
4531
4532 const Real omega = interpolate_crossing(response.omega[i - 1],
4533 left,
4534 response.omega[i],
4535 right,
4536 Real(0));
4537 const Real phi = interpolate_value_at(response.omega[i - 1],
4538 phase[i - 1],
4539 response.omega[i],
4540 phase[i],
4541 omega);
4542 const Real margin = std::numbers::pi_v<Real> + phi;
4543 if (not found or margin < best_margin)
4544 {
4546 best_omega = omega;
4547 found = true;
4548 }
4549 }
4550
4551 if (found)
4552 {
4553 info.radians = best_margin;
4554 info.degrees = best_margin * Real(180) / std::numbers::pi_v<Real>;
4555 info.crossover_omega = best_omega;
4556 info.found = true;
4557 }
4558
4559 return info;
4560 }
4561
4563 [[nodiscard]] static GainMarginInfo
4565 {
4566 ah_invalid_argument_if(response.omega.size() != response.response.size())
4567 << "FFT::gain_margin: omega size " << response.omega.size()
4568 << " does not match response size " << response.response.size();
4569
4571 if (response.omega.size() < 2)
4572 return info;
4573
4574 const Array<Real> magnitude = response.magnitude();
4575 const Array<Real> phase = unwrap_phase(phase_spectrum(response.response));
4576 bool found = false;
4577 Real best_ratio = std::numeric_limits<Real>::infinity();
4578 Real best_omega = std::numeric_limits<Real>::quiet_NaN();
4579
4580 for (size_t i = 1; i < response.omega.size(); ++i)
4581 {
4582 const Real p0 = phase[i - 1];
4583 const Real p1 = phase[i];
4584 const Real lower = std::min(p0, p1);
4585 const Real upper = std::max(p0, p1);
4586 const auto first_target =
4587 static_cast<long long>(std::ceil((-upper - std::numbers::pi_v<Real>)
4588 / (Real(2)
4589 * std::numbers::pi_v<Real>)));
4590 const auto last_target =
4591 static_cast<long long>(std::floor((-lower - std::numbers::pi_v<Real>)
4592 / (Real(2)
4593 * std::numbers::pi_v<Real>)));
4594
4595 for (long long m = first_target; m <= last_target; ++m)
4596 {
4597 const Real target =
4598 -(Real(2) * static_cast<Real>(m) + Real(1))
4599 * std::numbers::pi_v<Real>;
4600 const Real omega = interpolate_crossing(response.omega[i - 1],
4601 p0,
4602 response.omega[i],
4603 p1,
4604 target);
4605 const Real mag = interpolate_value_at(response.omega[i - 1],
4606 magnitude[i - 1],
4607 response.omega[i],
4608 magnitude[i],
4609 omega);
4610 const Real ratio = std::abs(mag) <= std::numeric_limits<Real>::epsilon() ?
4611 std::numeric_limits<Real>::infinity() :
4612 Real(1) / std::abs(mag);
4613 if (not found or ratio < best_ratio)
4614 {
4615 best_ratio = ratio;
4616 best_omega = omega;
4617 found = true;
4618 }
4619 }
4620 }
4621
4622 if (found)
4623 {
4624 info.ratio = best_ratio;
4625 info.decibels = std::isinf(best_ratio) ?
4626 std::numeric_limits<Real>::infinity() :
4627 Real(20) * std::log10(best_ratio);
4628 info.crossover_omega = best_omega;
4629 info.found = true;
4630 }
4631
4632 return info;
4633 }
4634
4636 template <typename Evaluator>
4637 [[nodiscard]] static PhaseMarginInfo
4639 const Evaluator & evaluator)
4640 {
4641 ah_invalid_argument_if(response.omega.size() != response.response.size())
4642 << "FFT::phase_margin: omega size " << response.omega.size()
4643 << " does not match response size " << response.response.size();
4644
4646 if (response.omega.size() < 2)
4647 return info;
4648
4649 const Array<Real> magnitude = response.magnitude();
4650 const Array<Real> phase = unwrap_phase(phase_spectrum(response.response));
4651 bool found = false;
4652 Real best_margin = std::numeric_limits<Real>::infinity();
4653 Real best_omega = std::numeric_limits<Real>::quiet_NaN();
4654
4655 for (size_t i = 1; i < response.omega.size(); ++i)
4656 {
4657 const Real left = magnitude[i - 1] - Real(1);
4658 const Real right = magnitude[i] - Real(1);
4659 if ((left < Real(0) and right < Real(0))
4660 or (left > Real(0) and right > Real(0)))
4661 continue;
4662
4663 const Real omega =
4664 refine_scalar_crossing([&](const Real w)
4665 {
4666 return std::abs(evaluator(w));
4667 },
4668 response.omega[i - 1],
4669 magnitude[i - 1],
4670 response.omega[i],
4671 magnitude[i],
4672 Real(1));
4673 const Real phase_ref =
4674 interpolate_value_at(response.omega[i - 1],
4675 phase[i - 1],
4676 response.omega[i],
4677 phase[i],
4678 omega);
4679 const Real phi =
4681 const Real margin = std::numbers::pi_v<Real> + phi;
4682 if (not found or margin < best_margin)
4683 {
4685 best_omega = omega;
4686 found = true;
4687 }
4688 }
4689
4690 if (found)
4691 {
4692 info.radians = best_margin;
4693 info.degrees = best_margin * Real(180) / std::numbers::pi_v<Real>;
4694 info.crossover_omega = best_omega;
4695 info.found = true;
4696 }
4697
4698 return info;
4699 }
4700
4702 template <typename Evaluator>
4703 [[nodiscard]] static GainMarginInfo
4705 const Evaluator & evaluator)
4706 {
4707 ah_invalid_argument_if(response.omega.size() != response.response.size())
4708 << "FFT::gain_margin: omega size " << response.omega.size()
4709 << " does not match response size " << response.response.size();
4710
4712 if (response.omega.size() < 2)
4713 return info;
4714
4715 const Array<Real> magnitude = response.magnitude();
4716 const Array<Real> phase = unwrap_phase(phase_spectrum(response.response));
4717 bool found = false;
4718 Real best_ratio = std::numeric_limits<Real>::infinity();
4719 Real best_omega = std::numeric_limits<Real>::quiet_NaN();
4720
4721 for (size_t i = 1; i < response.omega.size(); ++i)
4722 {
4723 const Real p0 = phase[i - 1];
4724 const Real p1 = phase[i];
4725 const Real lower = std::min(p0, p1);
4726 const Real upper = std::max(p0, p1);
4727 const auto first_target =
4728 static_cast<long long>(std::ceil((-upper - std::numbers::pi_v<Real>)
4729 / (Real(2)
4730 * std::numbers::pi_v<Real>)));
4731 const auto last_target =
4732 static_cast<long long>(std::floor((-lower - std::numbers::pi_v<Real>)
4733 / (Real(2)
4734 * std::numbers::pi_v<Real>)));
4735
4736 for (long long m = first_target; m <= last_target; ++m)
4737 {
4738 const Real target =
4739 -(Real(2) * static_cast<Real>(m) + Real(1))
4740 * std::numbers::pi_v<Real>;
4741 const Real omega =
4742 refine_scalar_crossing([&](const Real w)
4743 {
4744 const Real reference =
4745 interpolate_value_at(response.omega[i - 1],
4746 p0,
4747 response.omega[i],
4748 p1,
4749 w);
4750 return align_phase_near_reference(std::arg(evaluator(w)),
4751 reference);
4752 },
4753 response.omega[i - 1],
4754 p0,
4755 response.omega[i],
4756 p1,
4757 target);
4758 const Real mag = std::abs(evaluator(omega));
4759 const Real ratio =
4760 std::abs(mag) <= std::numeric_limits<Real>::epsilon() ?
4761 std::numeric_limits<Real>::infinity() :
4762 Real(1) / std::abs(mag);
4763 if (not found or ratio < best_ratio)
4764 {
4765 best_ratio = ratio;
4766 best_omega = omega;
4767 found = true;
4768 }
4769 }
4770 }
4771
4772 if (found)
4773 {
4774 info.ratio = best_ratio;
4775 info.decibels = std::isinf(best_ratio) ?
4776 std::numeric_limits<Real>::infinity() :
4777 Real(20) * std::log10(best_ratio);
4778 info.crossover_omega = best_omega;
4779 info.found = true;
4780 }
4781
4782 return info;
4783 }
4784
4786 [[nodiscard]] static Array<Real>
4788 {
4789 if (phase.is_empty())
4790 return {};
4791
4793 output(0) = phase[0];
4794 Real offset = Real(0);
4795 for (size_t i = 1; i < phase.size(); ++i)
4796 {
4797 const Real delta = phase[i] - phase[i - 1];
4798 if (delta > std::numbers::pi_v<Real>)
4799 offset -= Real(2) * std::numbers::pi_v<Real>;
4800 else if (delta < -std::numbers::pi_v<Real>)
4801 offset += Real(2) * std::numbers::pi_v<Real>;
4802 output(i) = phase[i] + offset;
4803 }
4804 return output;
4805 }
4806
4808 [[nodiscard]] static Array<Real>
4810 {
4811 ah_invalid_argument_if(response.omega.size() != response.response.size())
4812 << "FFT::group_delay: omega size " << response.omega.size()
4813 << " does not match response size " << response.response.size();
4814
4815 if (response.omega.is_empty())
4816 return {};
4817
4818 const Array<Real> phase = unwrap_phase(phase_spectrum(response.response));
4820 if (response.omega.size() == 1)
4821 {
4822 delay(0) = Real(0);
4823 return delay;
4824 }
4825
4826 for (size_t i = 0; i < response.omega.size(); ++i)
4827 {
4828 const size_t left = i == 0 ? 0 : i - 1;
4829 const size_t right = i + 1 == response.omega.size() ? i : i + 1;
4830 const Real domega = response.omega[right] - response.omega[left];
4831 if (std::abs(domega) <= std::numeric_limits<Real>::epsilon())
4832 delay(i) = Real(0);
4833 else
4834 delay(i) = -(phase[right] - phase[left]) / domega;
4835 }
4836
4837 return delay;
4838 }
4839
4841 [[nodiscard]] static Array<Real>
4843 {
4844 ah_invalid_argument_if(response.omega.size() != response.response.size())
4845 << "FFT::phase_delay: omega size " << response.omega.size()
4846 << " does not match response size " << response.response.size();
4847
4848 if (response.omega.is_empty())
4849 return {};
4850
4851 const Array<Real> phase = unwrap_phase(phase_spectrum(response.response));
4852 const Array<Real> group = group_delay_impl(response);
4854 const Real tol = (response.omega[response.omega.size() - 1] + Real(1))
4855 * Real(128) * std::numeric_limits<Real>::epsilon();
4856
4857 for (size_t i = 0; i < response.omega.size(); ++i)
4858 delay(i) = std::abs(response.omega[i]) <= tol ? group[i] : -phase[i] / response.omega[i];
4859
4860 return delay;
4861 }
4862
4866 static void
4869 const size_t fft_size,
4870 const ISTFTOptions & options,
4871 const char *ctx)
4872 {
4874 << ctx << ": analysis window must be non-empty";
4876 << ctx << ": synthesis window must be non-empty";
4878 << ctx << ": analysis window size " << analysis_window.size()
4879 << " does not match synthesis window size "
4880 << synthesis_window.size();
4881 ah_invalid_argument_if(options.hop_size == 0)
4882 << ctx << ": hop size must be positive";
4883 ah_invalid_argument_if(fft_size == 0)
4884 << ctx << ": FFT size must be positive";
4885 ah_invalid_argument_if(fft_size < analysis_window.size())
4886 << ctx << ": frame FFT size " << fft_size
4887 << " is smaller than window size " << analysis_window.size();
4888
4891 options.hop_size,
4892 options.validate_nola,
4893 options.validate_cola,
4894 ctx);
4895 }
4896
4898 [[nodiscard]] static Array<Real>
4902 const ISTFTOptions & options,
4903 ThreadPool *pool,
4904 const size_t chunk_size)
4905 {
4906 if (spectrogram.is_empty())
4907 return {};
4908
4909 const size_t fft_size = spectrogram[0].size();
4912 fft_size,
4913 options,
4914 "FFT::istft");
4915
4916 for (size_t i = 1; i < spectrogram.size(); ++i)
4917 ah_invalid_argument_if(spectrogram[i].size() != fft_size)
4918 << "FFT::istft: frame " << i << " has size "
4919 << spectrogram[i].size() << " but expected " << fft_size;
4920
4921 const size_t raw_length = (spectrogram.size() - 1) * options.hop_size
4922 + analysis_window.size();
4923 const size_t center_pad = options.centered ? analysis_window.size() / 2 : 0;
4924 const size_t output_length = options.signal_length == 0 ?
4925 (options.centered and raw_length >= 2 * center_pad ?
4926 raw_length - 2 * center_pad :
4927 raw_length) :
4928 options.signal_length;
4930 << "FFT::istft: requested signal length " << output_length
4931 << " exceeds overlap-add length " << raw_length
4932 << " after centered trimming";
4933
4934 Plan plan(fft_size);
4937 for (size_t i = 0; i < raw_length; ++i)
4938 {
4939 output(i) = Real(0);
4940 normalization(i) = Real(0);
4941 }
4942
4943 for (size_t frame_idx = 0; frame_idx < spectrogram.size(); ++frame_idx)
4944 {
4945 const Array<Real> frame = (pool != nullptr) ?
4946 plan.pinverse_transform_real(*pool, spectrogram[frame_idx], chunk_size) :
4947 plan.inverse_transform_real(spectrogram[frame_idx]);
4948 const size_t offset = frame_idx * options.hop_size;
4949
4950 for (size_t i = 0; i < analysis_window.size(); ++i)
4951 {
4952 output(offset + i) += frame[i] * synthesis_window[i];
4954 }
4955 }
4956
4957 const Real tol = (max_abs_value(normalization) + Real(1))
4958 * Real(256) * std::numeric_limits<Real>::epsilon();
4960 for (size_t i = 0; i < raw_length; ++i)
4961 {
4962 if (std::abs(normalization[i]) <= tol)
4963 {
4964 ah_domain_error_if(std::abs(output[i]) > tol)
4965 << "FFT::istft: overlap-add normalization vanished at sample " << i;
4966 normalized(i) = Real(0);
4967 }
4968 else
4969 normalized(i) = output[i] / normalization[i];
4970 }
4971
4972 return options.centered ?
4975 }
4976
4978 [[nodiscard]] static Array<Complex>
4979 zero_padded_copy(const Array<Complex> & input, const size_t n)
4980 {
4981 ah_invalid_argument_if(input.size() > n)
4982 << "FFT::zero_padded_copy: target size " << n
4983 << " is smaller than input size " << input.size();
4984
4986 output.reserve(n);
4987 for (size_t i = 0; i < input.size(); ++i)
4988 output.append(input[i]);
4989 for (size_t i = input.size(); i < n; ++i)
4990 output.append(Complex(Real(0), Real(0)));
4991 return output;
4992 }
4993
4995 [[nodiscard]] static Array<Real>
4996 zero_padded_copy(const Array<Real> & input, const size_t n)
4997 {
4998 ah_invalid_argument_if(input.size() > n)
4999 << "FFT::zero_padded_copy: target size " << n
5000 << " is smaller than input size " << input.size();
5001
5003 output.reserve(n);
5004 for (size_t i = 0; i < input.size(); ++i)
5005 output.append(input[i]);
5006 for (size_t i = input.size(); i < n; ++i)
5007 output.append(Real(0));
5008 return output;
5009 }
5010
5012 static void
5014 {
5015 ah_invalid_argument_if(n > input.size())
5016 << "FFT::trim_to_size: target size " << n
5017 << " exceeds input size " << input.size();
5018
5019 while (input.size() > n)
5020 static_cast<void>(input.remove_last());
5021 }
5022
5023 template <typename T, typename Container>
5024 requires requires(const Container & c)
5025 {
5026 std::begin(c);
5027 std::end(c);
5028 requires std::convertible_to<decltype(*std::begin(c)), T>;
5029 }
5030 [[nodiscard]] static Array<T>
5032 {
5034
5035 if constexpr (requires { input.size(); })
5036 output.reserve(input.size());
5037
5038 for (auto it = std::begin(input); it != std::end(input); ++it)
5039 output.append(static_cast<T>(*it));
5040
5041 return output;
5042 }
5043
5044 template <typename Container>
5046 [[nodiscard]] static Array<Complex>
5048 {
5049 return to_array<Complex>(input);
5050 }
5051
5052 template <typename Container>
5054 [[nodiscard]] static Array<Real>
5056 {
5057 return to_array<Real>(input);
5058 }
5059
5060 [[nodiscard]] static size_t
5062 const char *ctx)
5063 {
5065 << ctx << ": tensor shape must be non-empty";
5066
5067 size_t total = 1;
5068 for (size_t i = 0; i < shape.size(); ++i)
5069 {
5070 ah_invalid_argument_if(shape[i] == 0)
5071 << ctx << ": shape dimension " << i << " must be positive";
5072 total = saturating_product(total, shape[i]);
5073 }
5074 return total;
5075 }
5076
5077 [[nodiscard]] static Array<size_t>
5079 const char *ctx)
5080 {
5081 static_cast<void>(tensor_element_count(shape, ctx));
5082 Array<size_t> strides = Array<size_t>::create(shape.size());
5083 size_t stride = 1;
5084 for (size_t i = shape.size(); i > 0; --i)
5085 {
5086 strides(i - 1) = stride;
5087 stride = saturating_product(stride, shape[i - 1]);
5088 }
5089 return strides;
5090 }
5091
5092 [[nodiscard]] static size_t
5094 const Array<size_t> & strides,
5095 const char *ctx)
5096 {
5097 ah_invalid_argument_if(shape.size() != strides.size())
5098 << ctx << ": shape rank " << shape.size()
5099 << " does not match strides rank " << strides.size();
5100
5101 size_t max_offset = 0;
5102 for (size_t i = 0; i < shape.size(); ++i)
5103 {
5104 ah_invalid_argument_if(shape[i] == 0)
5105 << ctx << ": shape dimension " << i << " must be positive";
5106 max_offset += saturating_product(shape[i] - 1, strides[i]);
5107 }
5108 return max_offset;
5109 }
5110
5115 [[nodiscard]] static TensorLayout
5117 const TensorLayout & layout,
5118 const char *ctx)
5119 {
5121 normalized.shape = layout.shape;
5122 ah_invalid_argument_if(normalized.shape.is_empty())
5123 << ctx << ": tensor shape must be non-empty";
5124
5125 const size_t logical_size = tensor_element_count(normalized.shape, ctx);
5126 if (layout.strides.is_empty())
5127 {
5128 normalized.strides = row_major_strides(normalized.shape, ctx);
5130 << ctx << ": flat data size " << data.size()
5131 << " does not match row-major tensor size " << logical_size;
5132 return normalized;
5133 }
5134
5135 normalized.strides = layout.strides;
5136 ah_invalid_argument_if(normalized.strides.size() != normalized.shape.size())
5137 << ctx << ": strides rank " << normalized.strides.size()
5138 << " does not match shape rank " << normalized.shape.size();
5139
5140 const size_t max_offset =
5141 tensor_max_offset(normalized.shape, normalized.strides, ctx);
5142 ah_invalid_argument_if(data.is_empty() or max_offset >= data.size())
5143 << ctx << ": tensor layout touches offset " << max_offset
5144 << " but flat buffer size is " << data.size();
5145 return normalized;
5146 }
5147
5151 [[nodiscard]] static Array<size_t>
5153 const size_t rank,
5154 const char *ctx)
5155 {
5156 ah_invalid_argument_if(axes.is_empty())
5157 << ctx << ": at least one axis is required";
5158
5159 Array<bool> used(rank, false);
5161 normalized.reserve(axes.size());
5162 for (size_t i = 0; i < axes.size(); ++i)
5163 {
5164 ah_out_of_range_error_if(axes[i] >= rank)
5165 << ctx << ": axis " << axes[i] << " is out of range for rank "
5166 << rank;
5168 << ctx << ": axis " << axes[i] << " appears more than once";
5169 used(axes[i]) = true;
5170 normalized.append(axes[i]);
5171 }
5172
5173 return normalized;
5174 }
5175
5179 [[nodiscard]] static Array<size_t>
5181 const Array<size_t> & strides,
5182 const size_t axis)
5183 {
5185 other_dims.reserve(shape.size());
5186 size_t slice_count = 1;
5187 for (size_t i = 0; i < shape.size(); ++i)
5188 if (i != axis)
5189 {
5190 other_dims.append(i);
5192 }
5193
5194 Array<size_t> offsets;
5195 offsets.reserve(slice_count);
5196 if (other_dims.is_empty())
5197 {
5198 offsets.append(0);
5199 return offsets;
5200 }
5201
5203 for (size_t i = 0; i < counters.size(); ++i)
5204 counters(i) = 0;
5205
5206 bool done = false;
5207 while (not done)
5208 {
5209 size_t offset = 0;
5210 for (size_t i = 0; i < other_dims.size(); ++i)
5211 offset += counters[i] * strides[other_dims[i]];
5212 offsets.append(offset);
5213
5214 for (size_t rev = other_dims.size(); rev > 0; --rev)
5215 {
5216 const size_t idx = rev - 1;
5217 ++counters(idx);
5218 if (counters[idx] < shape[other_dims[idx]])
5219 break;
5220
5221 counters(idx) = 0;
5222 if (idx == 0)
5223 done = true;
5224 }
5225 }
5226
5227 return offsets;
5228 }
5229
5233 [[nodiscard]] static Array<Complex>
5235 const size_t base_offset,
5236 const size_t axis_length,
5237 const size_t axis_stride)
5238 {
5240 for (size_t i = 0; i < axis_length; ++i)
5241 slice(i) = data[base_offset + i * axis_stride];
5242 return slice;
5243 }
5244
5248 static void
5250 const size_t base_offset,
5251 const size_t axis_stride,
5252 const Array<Complex> & slice)
5253 {
5254 for (size_t i = 0; i < slice.size(); ++i)
5255 data(base_offset + i * axis_stride) = slice[i];
5256 }
5257
5261 static void
5263 const TensorLayout & layout,
5264 const size_t axis,
5265 const bool invert,
5266 ThreadPool *pool = nullptr,
5267 const size_t chunk_size = 0)
5268 {
5269 const TensorLayout normalized =
5270 normalize_tensor_layout(data, layout, "FFT::transform_axis");
5271 const Array<size_t> axes =
5273 normalized.shape.size(),
5274 "FFT::transform_axis");
5275 const size_t axis_index = axes[0];
5276 const size_t axis_length = normalized.shape[axis_index];
5277 const size_t axis_stride = normalized.strides[axis_index];
5278 const Array<size_t> offsets =
5281
5282 if (offsets.size() == 1)
5283 {
5284 Array<Complex> slice =
5285 gather_axis_slice(data, offsets[0], axis_length, axis_stride);
5286 if (pool != nullptr and pool->num_threads() > 1)
5287 plan.ptransform(*pool, slice, invert, chunk_size);
5288 else
5289 plan.transform(slice, invert);
5290 scatter_axis_slice(data, offsets[0], axis_stride, slice);
5291 return;
5292 }
5293
5294 auto transform_one =
5295 [&data, &offsets, axis_length, axis_stride, invert, &plan](const size_t i)
5296 {
5297 Array<Complex> slice =
5298 gather_axis_slice(data, offsets[i], axis_length, axis_stride);
5299 plan.transform(slice, invert);
5300 scatter_axis_slice(data, offsets[i], axis_stride, slice);
5301 };
5302
5303 if (pool != nullptr
5304 and pool->num_threads() > 1
5305 and offsets.size() > 1)
5306 parallel_for_index(*pool, 0, offsets.size(), transform_one, chunk_size);
5307 else
5308 for (size_t i = 0; i < offsets.size(); ++i)
5309 transform_one(i);
5310 }
5311
5315 static void
5317 const TensorLayout & layout,
5318 const Array<size_t> & axes,
5319 const bool invert,
5320 ThreadPool *pool = nullptr,
5321 const size_t chunk_size = 0)
5322 {
5323 const TensorLayout normalized =
5324 normalize_tensor_layout(data, layout, "FFT::transform_axes");
5326 normalize_axes(axes, normalized.shape.size(), "FFT::transform_axes");
5327 for (size_t i = 0; i < normalized_axes.size(); ++i)
5329 normalized,
5330 normalized_axes[i],
5331 invert,
5332 pool,
5333 chunk_size);
5334 }
5335
5339 [[nodiscard]] static Array<size_t>
5341 const char *ctx)
5342 {
5343 ah_invalid_argument_if(input.is_empty()) << ctx << ": matrix must be non-empty";
5344
5345 const size_t rows = input.size();
5346 const size_t cols = input[0].size();
5347 ah_invalid_argument_if(cols == 0) << ctx << ": matrix rows must be non-empty";
5348
5349 for (size_t i = 1; i < rows; ++i)
5351 << ctx << ": row " << i << " has size " << input[i].size()
5352 << " but expected " << cols;
5353
5354 return {rows, cols};
5355 }
5356
5358 [[nodiscard]] static Array<Complex>
5360 const char *ctx)
5361 {
5362 const Array<size_t> shape = matrix_shape(input, ctx);
5364 flat.reserve(shape[0] * shape[1]);
5365 for (size_t row = 0; row < shape[0]; ++row)
5366 for (size_t col = 0; col < shape[1]; ++col)
5367 flat.append(input[row][col]);
5368 return flat;
5369 }
5370
5374 const size_t rows,
5375 const size_t cols,
5376 const char *ctx)
5377 {
5379 << ctx << ": matrix shape must be positive";
5381 << ctx << ": flat size " << input.size()
5382 << " does not match matrix shape " << rows << "x" << cols;
5383
5385 for (size_t row = 0; row < rows; ++row)
5386 {
5388 for (size_t col = 0; col < cols; ++col)
5389 output(row)(col) = input[row * cols + col];
5390 }
5391 return output;
5392 }
5393
5397 [[nodiscard]] static Array<size_t>
5399 const char *ctx)
5400 {
5401 ah_invalid_argument_if(input.is_empty())
5402 << ctx << ": tensor must be non-empty";
5403 const size_t dim0 = input.size();
5404 const size_t dim1 = input[0].size();
5406 << ctx << ": tensor middle dimension must be positive";
5407 const size_t dim2 = input[0][0].size();
5409 << ctx << ": tensor innermost dimension must be positive";
5410
5411 for (size_t i = 0; i < dim0; ++i)
5412 {
5414 << ctx << ": slab " << i << " has size " << input[i].size()
5415 << " but expected " << dim1;
5416 for (size_t j = 0; j < dim1; ++j)
5418 << ctx << ": slab " << i << ", row " << j
5419 << " has size " << input[i][j].size()
5420 << " but expected " << dim2;
5421 }
5422
5423 return {dim0, dim1, dim2};
5424 }
5425
5427 [[nodiscard]] static Array<Complex>
5429 const char *ctx)
5430 {
5431 const Array<size_t> shape = tensor3_shape(input, ctx);
5433 flat.reserve(shape[0] * shape[1] * shape[2]);
5434 for (size_t i = 0; i < shape[0]; ++i)
5435 for (size_t j = 0; j < shape[1]; ++j)
5436 for (size_t k = 0; k < shape[2]; ++k)
5437 flat.append(input[i][j][k]);
5438 return flat;
5439 }
5440
5444 const size_t dim0,
5445 const size_t dim1,
5446 const size_t dim2,
5447 const char *ctx)
5448 {
5449 ah_invalid_argument_if(dim0 == 0 or dim1 == 0 or dim2 == 0)
5450 << ctx << ": tensor shape must be positive";
5452 << ctx << ": flat size " << input.size()
5453 << " does not match tensor shape";
5454
5456 size_t index = 0;
5457 for (size_t i = 0; i < dim0; ++i)
5458 {
5460 for (size_t j = 0; j < dim1; ++j)
5461 {
5463 for (size_t k = 0; k < dim2; ++k, ++index)
5464 output(i)(j)(k) = input[index];
5465 }
5466 }
5467 return output;
5468 }
5469
5475 const SpectrogramLayout source,
5476 const SpectrogramLayout target,
5477 const char *ctx)
5478 {
5479 if (source == target or input.is_empty())
5480 return input;
5481
5483 {
5484 const size_t channels = input.size();
5485 const size_t frames = input[0].size();
5486 const size_t bins =
5487 frames == 0 ? 0 : input[0][0].size();
5488
5489 for (size_t ch = 0; ch < channels; ++ch)
5490 {
5491 ah_invalid_argument_if(input[ch].size() != frames)
5492 << ctx << ": channel " << ch << " has " << input[ch].size()
5493 << " frames but expected " << frames;
5494 for (size_t frame = 0; frame < frames; ++frame)
5496 << ctx << ": channel " << ch << ", frame " << frame
5497 << " has " << input[ch][frame].size()
5498 << " bins but expected " << bins;
5499 }
5500
5502 for (size_t frame = 0; frame < frames; ++frame)
5503 {
5505 for (size_t ch = 0; ch < channels; ++ch)
5506 output(frame)(ch) = input[ch][frame];
5507 }
5508 return output;
5509 }
5510
5511 const size_t frames = input.size();
5512 const size_t channels = input[0].size();
5513 const size_t bins = channels == 0 ? 0 : input[0][0].size();
5514 for (size_t frame = 0; frame < frames; ++frame)
5515 {
5517 << ctx << ": frame " << frame << " has " << input[frame].size()
5518 << " channels but expected " << channels;
5519 for (size_t ch = 0; ch < channels; ++ch)
5521 << ctx << ": frame " << frame << ", channel " << ch
5522 << " has " << input[frame][ch].size()
5523 << " bins but expected " << bins;
5524 }
5525
5527 for (size_t ch = 0; ch < channels; ++ch)
5528 {
5530 for (size_t frame = 0; frame < frames; ++frame)
5531 output(ch)(frame) = input[frame][ch];
5532 }
5533 return output;
5534 }
5535
5544 [[nodiscard]] static Array<Complex>
5546 {
5547 return to_array<Complex>(input);
5548 }
5549
5554 [[nodiscard]] static Array<Complex>
5556 ThreadPool *pool = nullptr,
5557 const size_t chunk_size = 0)
5558 {
5559 const size_t n = input.size();
5561 << "FFT::transform: input must be non-empty";
5562
5563 if (n == 1)
5564 return {Complex(input[0], Real(0))};
5565
5566 if (not is_power_of_two(n))
5567 {
5569 transform_any_size_impl(spectrum, false, pool, chunk_size);
5570 return spectrum;
5571 }
5572
5573 const size_t half = n / 2;
5576 for (size_t i = 0; i < half; ++i)
5577 packed.append(Complex(input[2 * i], input[2 * i + 1]));
5578
5579 transform_impl(packed, false, pool, chunk_size);
5580
5582 const Complex half_i(Real(0), Real(-0.5));
5583 const Real base_angle = -Real(2) * std::numbers::pi_v<Real>
5584 / static_cast<Real>(n);
5585 auto recombine = [&packed, &spectrum, half_i, half, base_angle](const size_t k)
5586 {
5587 const size_t mirror = (half - k) % half;
5588 const Complex zk = packed[k];
5589 const Complex zmk = std::conj(packed[mirror]);
5590 const Complex even = (zk + zmk) * Real(0.5);
5591 const Complex odd = half_i * (zk - zmk);
5593 spectrum(k) = even + twiddle * odd;
5594 spectrum(k + half) = even - twiddle * odd;
5595 };
5596
5597 if (pool != nullptr and pool->num_threads() > 1 and half > 1)
5598 parallel_for_index(*pool, 0, half, recombine, chunk_size);
5599 else
5600 for (size_t k = 0; k < half; ++k)
5601 recombine(k);
5602
5603 return spectrum;
5604 }
5605
5610 template <typename InversePackedTransform>
5611 [[nodiscard]] static Array<Real>
5613 const char *ctx,
5614 ThreadPool *pool,
5615 const size_t chunk_size,
5617 {
5618 const size_t n = input.size();
5620 << ctx << ": input must be non-empty";
5621
5622 if (n == 1)
5623 {
5625 return {input[0].real()};
5626 }
5627
5629 << ctx << ": input size " << n << " must be a power of two";
5630
5632
5633 const size_t half = n / 2;
5635 const Real base_angle = -Real(2) * std::numbers::pi_v<Real>
5636 / static_cast<Real>(n);
5637
5639 (const size_t k)
5640 {
5641 const Complex even = (input[k] + input[k + half]) * Real(0.5);
5642 const Complex odd = (input[k] - input[k + half])
5643 * std::conj(twiddle_at(base_angle, k))
5644 * Real(0.5);
5645 packed_spectrum(k) = even + Complex(-odd.imag(), odd.real());
5646 };
5647
5648 if (pool != nullptr and pool->num_threads() > 1 and half > 1)
5649 parallel_for_index(*pool, 0, half, build_packed_spectrum, chunk_size);
5650 else
5651 for (size_t k = 0; k < half; ++k)
5653
5654 inverse_packed(packed_spectrum, pool, chunk_size);
5655
5657 auto unpack_output = [&packed_spectrum, &output](const size_t i)
5658 {
5659 output(2 * i) = packed_spectrum[i].real();
5660 output(2 * i + 1) = packed_spectrum[i].imag();
5661 };
5662
5663 if (pool != nullptr and pool->num_threads() > 1 and half > 1)
5664 parallel_for_index(*pool, 0, half, unpack_output, chunk_size);
5665 else
5666 for (size_t i = 0; i < half; ++i)
5667 unpack_output(i);
5668
5669 return output;
5670 }
5671
5676 [[nodiscard]] static Array<Real>
5678 ThreadPool *pool = nullptr,
5679 const size_t chunk_size = 0)
5680 {
5681 if (a.is_empty() or b.is_empty())
5682 return {};
5683
5684 ah_invalid_argument_if(a.size() > std::numeric_limits<size_t>::max() - b.size() + 1)
5685 << "FFT::multiply: product size exceeds size_t capacity";
5686
5687 const size_t required = a.size() + b.size() - 1;
5688 const size_t n = next_power_of_two(required);
5689 const Array<Real> pa = zero_padded_copy(a, n);
5690 const Array<Real> pb = zero_padded_copy(b, n);
5691
5693 packed.reserve(n);
5694 for (size_t i = 0; i < n; ++i)
5695 packed.append(Complex(pa[i], pb[i]));
5696
5697 transform_impl(packed, false, pool, chunk_size);
5699
5700 const Complex half_i(Real(0), Real(-0.5));
5701 auto build_product = [&packed, &packed_spectrum, half_i, n](const size_t k)
5702 {
5703 const size_t mirror = (n - k) % n;
5704 const Complex pk = packed_spectrum[k];
5705 const Complex pm = std::conj(packed_spectrum[mirror]);
5706 const Complex fa = (pk + pm) * Real(0.5);
5707 const Complex fb = half_i * (pk - pm);
5708 packed(k) = fa * fb;
5709 };
5710
5711 if (pool != nullptr and pool->num_threads() > 1 and n > 1)
5712 parallel_for_index(*pool, 0, n, build_product, chunk_size);
5713 else
5714 for (size_t k = 0; k < n; ++k)
5716
5717 transform_impl(packed, true, pool, chunk_size);
5719 return project_real_output(packed, "FFT::multiply");
5720 }
5721
5723 [[nodiscard]] static Array<Complex>
5725 ThreadPool *pool = nullptr,
5726 const size_t chunk_size = 0)
5727 {
5728 if (a.is_empty() or b.is_empty())
5729 return {};
5730
5731 ah_invalid_argument_if(a.size() > std::numeric_limits<size_t>::max() - b.size() + 1)
5732 << "FFT::multiply: product size exceeds size_t capacity";
5733
5734 const size_t required = a.size() + b.size() - 1;
5735 const size_t n = next_power_of_two(required);
5736
5739
5740 transform_impl(fa, false, pool, chunk_size);
5741 transform_impl(fb, false, pool, chunk_size);
5742
5743 auto pointwise_product = [&fa, &fb](const size_t i)
5744 {
5745 fa(i) *= fb[i];
5746 };
5747
5748 if (pool != nullptr and pool->num_threads() > 1 and n > 1)
5749 parallel_for_index(*pool, 0, n, pointwise_product, chunk_size);
5750 else
5751 for (size_t i = 0; i < n; ++i)
5753
5754 transform_impl(fa, true, pool, chunk_size);
5755
5757 return fa;
5758 }
5759
5771 [[nodiscard]] static Array<Real>
5772 project_real_output(const Array<Complex> & input, const char *ctx)
5773 {
5775 output.reserve(input.size());
5776 const Real stages = transform_stages(input.size());
5777
5778 for (size_t i = 0; i < input.size(); ++i)
5779 {
5780 const Real imag = std::abs(input[i].imag());
5781 const Real scale = std::abs(input[i].real()) + std::abs(input[i].imag()) + Real(1);
5782 const Real tolerance = Real(128) * stages
5783 * std::numeric_limits<Real>::epsilon() * scale;
5784
5785 ah_domain_error_if(imag > tolerance)
5786 << ctx << ": coefficient " << i
5787 << " has non-negligible imaginary part " << input[i].imag();
5788
5789 output.append(input[i].real());
5790 }
5791
5792 return output;
5793 }
5794
5803 static void
5805 {
5806 const size_t n = a.size();
5807 for (size_t i = 1, j = 0; i < n; ++i)
5808 {
5809 size_t bit = n >> 1;
5810 for (; j & bit; bit >>= 1)
5811 j ^= bit;
5812 j ^= bit;
5813 if (i < j)
5814 std::swap(a(i), a(j));
5815 }
5816 }
5817
5826 [[nodiscard]] static size_t
5827 next_power_of_two(const size_t n)
5828 {
5830 << "FFT::next_power_of_two: input size must be positive";
5831
5832 size_t power = 1;
5833 while (power < n)
5834 {
5835 ah_invalid_argument_if(power > std::numeric_limits<size_t>::max() / 2)
5836 << "FFT::next_power_of_two: size overflow for input " << n;
5837 power <<= 1;
5838 }
5839
5840 return power;
5841 }
5842
5844 static void
5845 transform_impl(Array<Complex> & a, const bool invert,
5846 ThreadPool *pool = nullptr,
5847 const size_t chunk_size = 0)
5848 {
5849 const size_t n = a.size();
5851 << "FFT::transform: input must be non-empty";
5852
5853 if (n == 1)
5854 return;
5855
5857 << "FFT::transform: input size " << n << " must be a power of two";
5858
5859 bit_reverse(a);
5860
5861 size_t log_n = 0;
5862 for (size_t tmp = n; tmp > 1; tmp >>= 1)
5863 ++log_n;
5864
5865 size_t r4_start_len;
5866
5867 // If odd number of radix-2 stages, do one radix-2 pass first
5868 // (twiddle for len=2 is always 1)
5869 if (log_n & 1)
5870 {
5871 const size_t blocks = n >> 1;
5872 auto r2_block = [&a](const size_t block)
5873 {
5874 const size_t base = block << 1;
5875 const Complex u = a[base];
5876 const Complex v = a[base + 1];
5877 a(base) = u + v;
5878 a(base + 1) = u - v;
5879 };
5880
5881 if (pool != nullptr and pool->num_threads() > 1 and blocks > 1)
5882 parallel_for_index(*pool, 0, blocks, r2_block, chunk_size);
5883 else
5884 for (size_t block = 0; block < blocks; ++block)
5885 r2_block(block);
5886
5887 r4_start_len = 8;
5888 }
5889 else
5890 r4_start_len = 4;
5891
5892 // Radix-4 stages: each fuses two radix-2 stages.
5893 // Block size quadruples each iteration.
5894 for (size_t len = r4_start_len; len <= n; len <<= 2)
5895 {
5896 const size_t quarter = len >> 2;
5897 const size_t blocks = n / len;
5898 const Real angle = (invert ? Real(2) : Real(-2))
5899 * std::numbers::pi_v<Real>
5900 / static_cast<Real>(len);
5901 const Complex w_step = std::polar(Real(1), angle);
5902
5903 auto r4_block = [&a, quarter, angle, w_step, invert]
5904 (const size_t block)
5905 {
5906 const size_t base = block * (quarter << 2);
5907 Complex w1(Real(1), Real(0));
5908 for (size_t j = 0; j < quarter; ++j)
5909 {
5910 const Complex w2 = w1 * w1;
5911 const Complex w3 = w2 * w1;
5912
5913 const size_t i0 = base + j;
5914 const size_t i1 = i0 + quarter;
5915 const size_t i2 = i1 + quarter;
5916 const size_t i3 = i2 + quarter;
5917
5918 const Complex a0 = a[i0];
5919 const Complex a1 = a[i1] * w2;
5920 const Complex a2 = a[i2] * w1;
5921 const Complex a3 = a[i3] * w3;
5922
5923 const Complex p = a0 + a1;
5924 const Complex q = a0 - a1;
5925 const Complex r = a2 + a3;
5926 const Complex s = a2 - a3;
5927
5928 // -i*s (forward) or +i*s (inverse), no real multiplications
5929 const Complex js = invert ? Complex(-s.imag(), s.real()) : Complex(s.imag(), -s.real());
5930
5931 a(i0) = p + r;
5932 a(i1) = q + js;
5933 a(i2) = p - r;
5934 a(i3) = q - js;
5935
5936 if (j + 1 < quarter)
5937 {
5938 const size_t next_j = j + 1;
5939 if (next_j % twiddle_refresh_period == 0)
5941 else
5942 w1 *= w_step;
5943 }
5944 }
5945 };
5946
5947 if (pool != nullptr and pool->num_threads() > 1 and blocks > 1)
5948 parallel_for_index(*pool, 0, blocks, r4_block, chunk_size);
5949 else
5950 for (size_t block = 0; block < blocks; ++block)
5951 r4_block(block);
5952 }
5953
5954 if (invert)
5955 {
5956 const Real inv_n = Real(1) / static_cast<Real>(n);
5957 for (size_t i = 0; i < n; ++i)
5958 a(i) *= inv_n;
5959 }
5960 }
5961
5965 [[nodiscard]] static Array<size_t>
5967 {
5969 while (n > 1)
5970 {
5971 size_t factor = 0;
5972 if (n % 4 == 0)
5973 factor = 4;
5974 else if (n % 5 == 0)
5975 factor = 5;
5976 else if (n % 3 == 0)
5977 factor = 3;
5978 else if (n % 2 == 0)
5979 factor = 2;
5980 else
5981 break;
5982
5983 factors.append(factor);
5984 n /= factor;
5985 }
5986
5987 if (n != 1)
5988 factors.empty();
5989
5990 return factors;
5991 }
5992
5993 public:
5999 [[nodiscard]] static constexpr bool
6000 is_power_of_two(const size_t n) noexcept
6001 {
6002 return n > 0 and (n & (n - 1)) == 0;
6003 }
6004
6006 [[nodiscard]] static constexpr const char *
6008 {
6009 switch (backend)
6010 {
6011 case SimdBackend::neon:
6012 return "neon";
6013 case SimdBackend::avx2:
6014 return "avx2";
6016 default:
6017 return "scalar";
6018 }
6019 }
6020
6022 [[nodiscard]] static constexpr const char *
6024 {
6025 switch (preference)
6026 {
6028 return "neon";
6030 return "avx2";
6032 return "scalar";
6034 default:
6035 return "auto";
6036 }
6037 }
6038
6040 [[nodiscard]] static constexpr bool
6042 {
6043# if ALEPH_FFT_HAS_X86_AVX2_DISPATCH
6044 return std::same_as<Real, double>;
6045# else
6046 return false;
6047# endif
6048 }
6049
6051 [[nodiscard]] static constexpr bool
6053 {
6054# if ALEPH_FFT_HAS_ARM_NEON_DISPATCH
6055 if constexpr (std::same_as<Real, float>)
6056 return true;
6057# if ALEPH_FFT_HAS_ARM_NEON_DOUBLE
6058 if constexpr (std::same_as<Real, double>)
6059 return true;
6060# endif
6061# endif
6062 return false;
6063 }
6064
6068 [[nodiscard]] static bool
6070 {
6071# if ALEPH_FFT_HAS_X86_AVX2_DISPATCH
6072 if constexpr (std::same_as<Real, double>)
6073 {
6074 static const bool available = []() noexcept
6075 {
6077 return static_cast<bool>(__builtin_cpu_supports("avx2"));
6078 }();
6079 return available;
6080 }
6081# endif
6082 return false;
6083 }
6084
6088 [[nodiscard]] static bool
6090 {
6091# if ALEPH_FFT_HAS_ARM_NEON_DISPATCH
6092 if constexpr (std::same_as<Real, float>)
6093 {
6094
6095
6096
6097# if defined(__linux__)
6098 static const bool available = []() noexcept
6099 {
6100
6101
6102
6103# if defined(__aarch64__) and defined(HWCAP_ASIMD)
6104 return (getauxval(AT_HWCAP) & HWCAP_ASIMD) != 0;
6105# elif defined(__arm__) and defined(HWCAP_NEON)
6106 return (getauxval(AT_HWCAP) & HWCAP_NEON) != 0;
6107# else
6108 return true;
6109# endif
6110 }();
6111 return available;
6112# else
6113 return true;
6114# endif
6115 }
6116
6117# if ALEPH_FFT_HAS_ARM_NEON_DOUBLE
6118 if constexpr (std::same_as<Real, double>)
6119 {
6120
6121
6122
6123# if defined(__linux__) and defined(HWCAP_ASIMD)
6124 static const bool available = []() noexcept
6125 {
6126 return (getauxval(AT_HWCAP) & HWCAP_ASIMD) != 0;
6127 }();
6128 return available;
6129# else
6130 return true;
6131# endif
6132 }
6133# endif
6134# endif
6135 return false;
6136 }
6137
6141 [[nodiscard]] static SimdBackend
6150
6152 [[nodiscard]] static const char *
6157
6164 [[nodiscard]] static SimdPreference
6166 {
6167 static const SimdPreference preference = []() noexcept
6168 {
6169 const char *disabled = std::getenv("ALEPH_FFT_DISABLE_AVX2");
6170 if (disabled != nullptr and disabled[0] != '\0' and disabled[0] != '0')
6172
6173 if (const char *mode = std::getenv("ALEPH_FFT_SIMD");
6174 mode != nullptr and mode[0] != '\0')
6175 {
6176 const std::string_view value(mode);
6177 if (value == "scalar")
6179 if (value == "avx2")
6181 if (value == "neon")
6184 }
6185
6186 const char *enabled = std::getenv("ALEPH_FFT_ENABLE_AVX2");
6187 if (enabled != nullptr and enabled[0] != '\0' and enabled[0] != '0')
6189
6191 }();
6192 return preference;
6193 }
6194
6198 [[nodiscard]] static SimdBackend
6213
6215 [[nodiscard]] static const char *
6220
6224 [[nodiscard]] static SimdBackend
6244
6246 [[nodiscard]] static const char *
6251
6253 [[nodiscard]] static const char *
6258
6260 [[nodiscard]] static bool
6265
6267 [[nodiscard]] static bool
6272
6293 class Plan
6294 {
6295 enum class Strategy
6296 {
6297 empty,
6300 bluestein
6301 };
6302
6303 size_t n_ = 0;
6305 size_t log_n_ = 0;
6308 std::shared_ptr<const Plan> half_plan_;
6315 std::shared_ptr<const Plan> bluestein_plan_;
6316
6317 void
6319 {
6321
6322 log_n_ = 0;
6323 for (size_t tmp = n_; tmp > 1; tmp >>= 1)
6324 ++log_n_;
6325
6327 bit_rev_(0) = 0;
6328 for (size_t i = 1, j = 0; i < n_; ++i)
6329 {
6330 size_t bit = n_ >> 1;
6331 for (; j & bit; bit >>= 1)
6332 j ^= bit;
6333 j ^= bit;
6334 bit_rev_(i) = j;
6335 }
6336
6338 for (size_t stage = 0; stage < log_n_; ++stage)
6339 {
6340 const size_t half = static_cast<size_t>(1) << stage;
6341 const size_t len = half << 1;
6342 const Real angle = Real(-2) * std::numbers::pi_v<Real>
6343 / static_cast<Real>(len);
6344 const size_t offset = half - 1;
6345 for (size_t j = 0; j < half; ++j)
6346 twiddles_(offset + j) = std::polar(Real(1),
6347 angle * static_cast<Real>(j));
6348 }
6349
6350 half_plan_ = std::make_shared<Plan>(n_ / 2);
6351 }
6352
6353 void
6355 {
6358 for (size_t i = 0; i < n_; ++i)
6359 roots_(i) = std::polar(Real(1),
6360 Real(-2) * std::numbers::pi_v<Real>
6361 * static_cast<Real>(i)
6362 / static_cast<Real>(n_));
6363 }
6364
6365 void
6367 {
6370 bluestein_plan_ = std::make_shared<Plan>(bluestein_size_);
6371
6373 for (size_t i = 0; i < n_; ++i)
6374 {
6375 const Real value = static_cast<Real>(i);
6376 const Real phase = std::numbers::pi_v<Real> * value * value
6377 / static_cast<Real>(n_);
6378 bluestein_chirp_(i) = std::polar(Real(1), phase);
6379 }
6380
6383 for (size_t i = 0; i < bluestein_size_; ++i)
6384 {
6385 forward_kernel(i) = Complex(Real(0), Real(0));
6386 inverse_kernel(i) = Complex(Real(0), Real(0));
6387 }
6388
6389 forward_kernel(0) = Complex(Real(1), Real(0));
6390 inverse_kernel(0) = Complex(Real(1), Real(0));
6391 for (size_t i = 1; i < n_; ++i)
6392 {
6395
6396 inverse_kernel(i) = std::conj(bluestein_chirp_[i]);
6397 inverse_kernel(bluestein_size_ - i) = std::conj(bluestein_chirp_[i]);
6398 }
6399
6402 bluestein_plan_->transform(bluestein_kernel_forward_, false);
6403 bluestein_plan_->transform(bluestein_kernel_inverse_, false);
6404 }
6405
6406 void apply_bit_reversal(Array<Complex> & a) const noexcept
6407 {
6408 for (size_t i = 0; i < n_; ++i)
6409 if (i < bit_rev_(i))
6410 std::swap(a(i), a(bit_rev_(i)));
6411 }
6412
6413# if ALEPH_FFT_HAS_X86_AVX2_DISPATCH
6415 avx2_load_complex_pair(const Complex *ptr) requires std::same_as<Real, double>
6416 {
6417 static_assert(sizeof(Complex) == sizeof(double) * 2);
6418 return _mm256_loadu_pd(reinterpret_cast<const double *>(ptr));
6419 }
6420
6421 static ALEPH_FFT_AVX2_TARGET void
6423 const __m256d value) requires std::same_as<Real, double>
6424 {
6425 static_assert(sizeof(Complex) == sizeof(double) * 2);
6426 _mm256_storeu_pd(reinterpret_cast<double *>(ptr), value);
6427 }
6428
6430 avx2_conjugate_pair(const __m256d value) requires std::same_as<Real, double>
6431 {
6432 const __m256d mask = _mm256_set_pd(-0.0, 0.0, -0.0, 0.0);
6433 return _mm256_xor_pd(value, mask);
6434 }
6435
6438 const __m256d rhs) requires std::same_as<Real, double>
6439 {
6440 const __m256d rhs_real = _mm256_movedup_pd(rhs);
6441 const __m256d rhs_imag = _mm256_permute_pd(rhs, 0xF);
6442 const __m256d lhs_swapped = _mm256_permute_pd(lhs, 0x5);
6445 }
6446
6449 const bool invert) requires std::same_as<Real, double>
6450 {
6451 const __m256d swapped = _mm256_permute_pd(value, 0x5);
6452 const __m256d forward_mask = _mm256_set_pd(-0.0, 0.0, -0.0, 0.0);
6453 const __m256d inverse_mask = _mm256_set_pd(0.0, -0.0, 0.0, -0.0);
6454 return _mm256_xor_pd(swapped, invert ? inverse_mask : forward_mask);
6455 }
6456
6457 static ALEPH_FFT_AVX2_TARGET void
6458 avx2_apply_r2_block(Array<Complex> & a, const size_t base)
6459 requires std::same_as<Real, double>
6460 {
6461 const __m256d uv = avx2_load_complex_pair(&a[base]);
6463 const __m128d v = _mm256_extractf128_pd(uv, 1);
6464 const __m128d sum = _mm_add_pd(u, v);
6465 const __m128d diff = _mm_sub_pd(u, v);
6467 }
6468
6469 static ALEPH_FFT_AVX2_TARGET void
6470 avx2_apply_r2_stage(Array<Complex> & a, const size_t blocks)
6471 requires std::same_as<Real, double>
6472 {
6473 for (size_t block = 0; block < blocks; ++block)
6474 avx2_apply_r2_block(a, block << 1);
6475 }
6476
6479 const size_t quarter,
6480 const size_t tw_w1_off,
6481 const size_t tw_w2_off,
6482 const bool invert,
6483 const size_t block) const
6484 requires std::same_as<Real, double>
6485 {
6486 const size_t base = block * (quarter << 2);
6487 size_t j = 0;
6488 for (; j + 1 < quarter; j += 2)
6489 {
6492 if (invert)
6493 {
6496 }
6498
6499 const size_t i0 = base + j;
6500 const size_t i1 = i0 + quarter;
6501 const size_t i2 = i1 + quarter;
6502 const size_t i3 = i2 + quarter;
6503
6504 const __m256d a0 = avx2_load_complex_pair(&a[i0]);
6508
6509 const __m256d p = _mm256_add_pd(a0, a1);
6510 const __m256d q = _mm256_sub_pd(a0, a1);
6511 const __m256d r = _mm256_add_pd(a2, a3);
6512 const __m256d s = _mm256_sub_pd(a2, a3);
6513 const __m256d js = avx2_rotate_js_pair(s, invert);
6514
6519 }
6520
6521 for (; j < quarter; ++j)
6522 {
6523 const Complex tw1 = twiddles_(tw_w1_off + j);
6524 const Complex tw2 = twiddles_(tw_w2_off + j);
6525 const Complex w1 = invert ? std::conj(tw1) : tw1;
6526 const Complex w2 = invert ? std::conj(tw2) : tw2;
6527 const Complex w3 = w1 * w2;
6528
6529 const size_t i0 = base + j;
6530 const size_t i1 = i0 + quarter;
6531 const size_t i2 = i1 + quarter;
6532 const size_t i3 = i2 + quarter;
6533
6534 const Complex a0 = a[i0];
6535 const Complex a1 = a[i1] * w2;
6536 const Complex a2 = a[i2] * w1;
6537 const Complex a3 = a[i3] * w3;
6538
6539 const Complex p = a0 + a1;
6540 const Complex q = a0 - a1;
6541 const Complex r = a2 + a3;
6542 const Complex s = a2 - a3;
6543
6544 const Complex js = invert ? Complex(-s.imag(), s.real()) : Complex(s.imag(), -s.real());
6545
6546 a(i0) = p + r;
6547 a(i1) = q + js;
6548 a(i2) = p - r;
6549 a(i3) = q - js;
6550 }
6551 }
6552
6553 static ALEPH_FFT_AVX2_TARGET void
6555 const Real inv_n) requires std::same_as<Real, double>
6556 {
6558 size_t i = 0;
6559 for (; i + 1 < a.size(); i += 2)
6562 for (; i < a.size(); ++i)
6563 a(i) *= inv_n;
6564 }
6565
6568 const size_t quarter,
6569 const size_t blocks,
6570 const size_t tw_w1_off,
6571 const size_t tw_w2_off,
6572 const bool invert) const
6573 requires std::same_as<Real, double>
6574 {
6575 for (size_t block = 0; block < blocks; ++block)
6576 avx2_apply_r4_block(a, quarter, tw_w1_off, tw_w2_off, invert, block);
6577 }
6578# endif
6579
6580# if ALEPH_FFT_HAS_ARM_NEON_DISPATCH
6582 {
6585 };
6586
6587# if ALEPH_FFT_HAS_ARM_NEON_DOUBLE
6589 {
6592 };
6593# endif
6594
6596 neon_load_complex_pair(const Complex *ptr) requires std::same_as<Real, float>
6597 {
6598 static_assert(sizeof(Complex) == sizeof(float) * 2);
6600 vld2_f32(reinterpret_cast<const float *>(ptr));
6601 return {interleaved.val[0], interleaved.val[1]};
6602 }
6603
6604 static void
6606 const NeonComplexPairFloat value) requires std::same_as<Real, float>
6607 {
6608 static_assert(sizeof(Complex) == sizeof(float) * 2);
6609 const float32x2x2_t interleaved = {value.re, value.im};
6610 vst2_f32(reinterpret_cast<float *>(ptr), interleaved);
6611 }
6612
6614 neon_conjugate_pair(const NeonComplexPairFloat value) requires std::same_as<Real, float>
6615 {
6616 return {value.re, vneg_f32(value.im)};
6617 }
6618
6621 const NeonComplexPairFloat rhs) requires std::same_as<Real, float>
6622 {
6623 return {
6624 vsub_f32(vmul_f32(lhs.re, rhs.re), vmul_f32(lhs.im, rhs.im)),
6625 vadd_f32(vmul_f32(lhs.re, rhs.im), vmul_f32(lhs.im, rhs.re))
6626 };
6627 }
6628
6631 const NeonComplexPairFloat rhs) requires std::same_as<Real, float>
6632 {
6633 return {vadd_f32(lhs.re, rhs.re), vadd_f32(lhs.im, rhs.im)};
6634 }
6635
6638 const NeonComplexPairFloat rhs) requires std::same_as<Real, float>
6639 {
6640 return {vsub_f32(lhs.re, rhs.re), vsub_f32(lhs.im, rhs.im)};
6641 }
6642
6645 const bool invert) requires std::same_as<Real, float>
6646 {
6647 return invert ?
6650 }
6651
6652# if ALEPH_FFT_HAS_ARM_NEON_DOUBLE
6654 neon_load_complex_pair(const Complex *ptr) requires std::same_as<Real, double>
6655 {
6656 static_assert(sizeof(Complex) == sizeof(double) * 2);
6658 vld2q_f64(reinterpret_cast<const double *>(ptr));
6659 return {interleaved.val[0], interleaved.val[1]};
6660 }
6661
6662 static void
6664 const NeonComplexPairDouble value) requires std::same_as<Real, double>
6665 {
6666 static_assert(sizeof(Complex) == sizeof(double) * 2);
6667 const float64x2x2_t interleaved = {value.re, value.im};
6668 vst2q_f64(reinterpret_cast<double *>(ptr), interleaved);
6669 }
6670
6672 neon_conjugate_pair(const NeonComplexPairDouble value) requires std::same_as<Real, double>
6673 {
6674 return {value.re, vnegq_f64(value.im)};
6675 }
6676
6679 const NeonComplexPairDouble rhs) requires std::same_as<Real, double>
6680 {
6681 return {
6682 vsubq_f64(vmulq_f64(lhs.re, rhs.re), vmulq_f64(lhs.im, rhs.im)),
6683 vaddq_f64(vmulq_f64(lhs.re, rhs.im), vmulq_f64(lhs.im, rhs.re))
6684 };
6685 }
6686
6689 const NeonComplexPairDouble rhs) requires std::same_as<Real, double>
6690 {
6691 return {vaddq_f64(lhs.re, rhs.re), vaddq_f64(lhs.im, rhs.im)};
6692 }
6693
6696 const NeonComplexPairDouble rhs) requires std::same_as<Real, double>
6697 {
6698 return {vsubq_f64(lhs.re, rhs.re), vsubq_f64(lhs.im, rhs.im)};
6699 }
6700
6703 const bool invert) requires std::same_as<Real, double>
6704 {
6705 return invert ?
6708 }
6709# endif
6710
6711 void
6713 const size_t quarter,
6714 const size_t tw_w1_off,
6715 const size_t tw_w2_off,
6716 const bool invert,
6717 const size_t block) const
6718 {
6719 const size_t base = block * (quarter << 2);
6720 size_t j = 0;
6721 for (; j + 1 < quarter; j += 2)
6722 {
6725 if (invert)
6726 {
6729 }
6730 const auto w3 = neon_complex_mul_pair(w1, w2);
6731
6732 const size_t i0 = base + j;
6733 const size_t i1 = i0 + quarter;
6734 const size_t i2 = i1 + quarter;
6735 const size_t i3 = i2 + quarter;
6736
6737 const auto a0 = neon_load_complex_pair(&a[i0]);
6738 const auto a1 = neon_complex_mul_pair(neon_load_complex_pair(&a[i1]), w2);
6739 const auto a2 = neon_complex_mul_pair(neon_load_complex_pair(&a[i2]), w1);
6740 const auto a3 = neon_complex_mul_pair(neon_load_complex_pair(&a[i3]), w3);
6741
6742 const auto p = neon_add_pair(a0, a1);
6743 const auto q = neon_sub_pair(a0, a1);
6744 const auto r = neon_add_pair(a2, a3);
6745 const auto s = neon_sub_pair(a2, a3);
6746 const auto js = neon_rotate_js_pair(s, invert);
6747
6752 }
6753
6754 for (; j < quarter; ++j)
6755 {
6756 const Complex tw1 = twiddles_(tw_w1_off + j);
6757 const Complex tw2 = twiddles_(tw_w2_off + j);
6758 const Complex w1 = invert ? std::conj(tw1) : tw1;
6759 const Complex w2 = invert ? std::conj(tw2) : tw2;
6760 const Complex w3 = w1 * w2;
6761
6762 const size_t i0 = base + j;
6763 const size_t i1 = i0 + quarter;
6764 const size_t i2 = i1 + quarter;
6765 const size_t i3 = i2 + quarter;
6766
6767 const Complex a0 = a[i0];
6768 const Complex a1 = a[i1] * w2;
6769 const Complex a2 = a[i2] * w1;
6770 const Complex a3 = a[i3] * w3;
6771
6772 const Complex p = a0 + a1;
6773 const Complex q = a0 - a1;
6774 const Complex r = a2 + a3;
6775 const Complex s = a2 - a3;
6776
6777 const Complex js = invert ? Complex(-s.imag(), s.real()) : Complex(s.imag(), -s.real());
6778
6779 a(i0) = p + r;
6780 a(i1) = q + js;
6781 a(i2) = p - r;
6782 a(i3) = q - js;
6783 }
6784 }
6785
6786 static void
6788 {
6789 size_t i = 0;
6790 if constexpr (std::same_as<Real, float>)
6791 {
6793 for (; i + 1 < a.size(); i += 2)
6794 {
6795 auto pair = neon_load_complex_pair(&a[i]);
6796 pair.re = vmul_f32(pair.re, scale);
6797 pair.im = vmul_f32(pair.im, scale);
6799 }
6800 }
6801# if ALEPH_FFT_HAS_ARM_NEON_DOUBLE
6802 else if constexpr (std::same_as<Real, double>)
6803 {
6805 for (; i + 1 < a.size(); i += 2)
6806 {
6807 auto pair = neon_load_complex_pair(&a[i]);
6808 pair.re = vmulq_f64(pair.re, scale);
6809 pair.im = vmulq_f64(pair.im, scale);
6811 }
6812 }
6813# endif
6814 for (; i<a.size();++i)
6815 a (i)*= inv_n;
6816 }
6817
6818 void
6820 const size_t quarter,
6821 const size_t blocks,
6822 const size_t tw_w1_off,
6823 const size_t tw_w2_off,
6824 const bool invert) const
6825 {
6826 for (size_t block = 0; block < blocks; ++block)
6827 neon_apply_r4_block(a, quarter, tw_w1_off, tw_w2_off, invert, block);
6828 }
6829# endif
6830
6831 [[nodiscard]] bool
6832 automatic_batch_simd_candidate(const size_t batch_size) const noexcept
6833 {
6834 return batch_size >= 4
6836 and n_ >= 128
6837 and (log_n_ & 1) == 1;
6838 }
6839
6840 [[nodiscard]] bool
6842 const bool prefer_simd) const noexcept
6843 {
6844 if constexpr (not std::same_as<Real, double>)
6845 return false;
6846
6847 if (strategy_ != Strategy::power_of_two or (log_n_ & 1) == 0)
6848 return false;
6849
6850 if (pool != nullptr and pool->num_threads() > 1)
6851 return false;
6852
6854 return false;
6855
6856 switch (FFT::simd_preference())
6857 {
6860 return false;
6862 return true;
6864 default:
6865 return prefer_simd;
6866 }
6867 }
6868
6869 [[nodiscard]] bool
6871 const bool prefer_simd) const noexcept
6872 {
6873 // NEON kernels are only implemented for float and double; long
6874 // double must always take the scalar path (mirrors should_use_avx2).
6875 if constexpr (not (std::same_as<Real, double> or std::same_as<Real, float>))
6876 return false;
6877
6878 if (strategy_ != Strategy::power_of_two or (log_n_ & 1) == 0)
6879 return false;
6880
6881 if (pool != nullptr and pool->num_threads() > 1)
6882 return false;
6883
6885 return false;
6886
6887 switch (FFT::simd_preference())
6888 {
6891 return false;
6893 return true;
6895 default:
6896 return prefer_simd;
6897 }
6898 }
6899
6900 void apply_butterflies(Array<Complex> & a, const bool invert,
6901 ThreadPool *pool,
6902 const size_t chunk_size,
6903 const bool prefer_simd) const
6904 {
6905 [[maybe_unused]] const bool use_avx2 = should_use_avx2(pool, prefer_simd);
6906 [[maybe_unused]] const bool use_neon = should_use_neon(pool, prefer_simd);
6907
6908# if ALEPH_FFT_HAS_X86_AVX2_DISPATCH
6909 if constexpr (std::same_as<Real, double>)
6910 if (use_avx2 and (pool == nullptr or pool->num_threads() <= 1))
6911 {
6912 size_t stage = 0;
6913
6914 if (log_n_ & 1)
6915 {
6916 avx2_apply_r2_stage(a, n_ >> 1);
6917 stage = 1;
6918 }
6919
6920 while (stage + 1 < log_n_)
6921 {
6922 const size_t s_low = stage;
6923 const size_t s_high = stage + 1;
6924 const size_t quarter = static_cast<size_t>(1) << s_low;
6925 const size_t blocks = n_ >> (s_high + 1);
6926 const size_t tw_w1_off = (static_cast<size_t>(1) << s_high) - 1;
6927 const size_t tw_w2_off = quarter - 1;
6928 avx2_apply_r4_stage(a, quarter, blocks,
6929 tw_w1_off, tw_w2_off, invert);
6930 stage += 2;
6931 }
6932
6933 if (invert)
6934 avx2_scale_output(a, Real(1) / static_cast<Real>(n_));
6935 return;
6936 }
6937# endif
6938
6939# if ALEPH_FFT_HAS_ARM_NEON_DISPATCH
6940 // NEON helpers are constrained to `Real ∈ {float, double}`
6941 // (see `neon_load_complex_pair` overloads). Without this
6942 // `if constexpr` gate, instantiating `FFT<long double>` would
6943 // force the compiler to instantiate `neon_apply_r4_stage` for
6944 // `Real = long double`, where no helper overload matches and
6945 // the build fails on AArch64.
6946 if constexpr (std::same_as<Real, double> or std::same_as<Real, float>)
6947 if (use_neon and (pool == nullptr or pool->num_threads() <= 1))
6948 {
6949 size_t stage = 0;
6950
6951 if (log_n_ & 1)
6952 {
6953 const size_t blocks = n_ >> 1;
6954 for (size_t block = 0; block < blocks; ++block)
6955 {
6956 const size_t base = block << 1;
6957 const Complex u = a[base];
6958 const Complex v = a[base + 1];
6959 a(base) = u + v;
6960 a(base + 1) = u - v;
6961 }
6962 stage = 1;
6963 }
6964
6965 while (stage + 1 < log_n_)
6966 {
6967 const size_t s_low = stage;
6968 const size_t s_high = stage + 1;
6969 const size_t quarter = size_t(1) << s_low;
6970 const size_t blocks = n_ >> (s_high + 1);
6971 const size_t tw_w1_off = (size_t(1) << s_high) - 1;
6972 const size_t tw_w2_off = quarter - 1;
6973 neon_apply_r4_stage(a, quarter, blocks,
6974 tw_w1_off, tw_w2_off, invert);
6975 stage += 2;
6976 }
6977
6978 if (invert)
6979 neon_scale_output(a, Real(1) / static_cast<Real>(n_));
6980 return;
6981 }
6982# endif
6983
6984 size_t stage = 0;
6985
6986 // If odd number of stages, one radix-2 pass first (twiddle = 1)
6987 if (log_n_ & 1)
6988 {
6989 const size_t blocks = n_ >> 1;
6990 auto r2_block = [&a](const size_t block)
6991 {
6992 const size_t base = block << 1;
6993 const Complex u = a[base];
6994 const Complex v = a[base + 1];
6995 a(base) = u + v;
6996 a(base + 1) = u - v;
6997 };
6998
6999 if (pool != nullptr and pool->num_threads() > 1 and blocks > 1)
7000 parallel_for_index(*pool, 0, blocks, r2_block, chunk_size);
7001 else
7002 for (size_t block = 0; block < blocks; ++block)
7003 r2_block(block);
7004
7005 stage = 1;
7006 }
7007
7008 // Radix-4 stages: each fuses two radix-2 stages.
7009 // w1 and w2 are looked up directly from the precomputed table;
7010 // w3 = w1 * w2 requires only one multiplication.
7011 while (stage + 1 < log_n_)
7012 {
7013 const size_t s_low = stage;
7014 const size_t s_high = stage + 1;
7015 const size_t quarter = static_cast<size_t>(1) << s_low;
7016 const size_t blocks = n_ >> (s_high + 1);
7017 const size_t tw_w1_off = (static_cast<size_t>(1) << s_high) - 1;
7018 const size_t tw_w2_off = quarter - 1;
7019
7020 auto r4_block = [this, &a, quarter, tw_w1_off, tw_w2_off, invert]
7021 (const size_t block)
7022 {
7023 const size_t base = block * (quarter << 2);
7024 for (size_t j = 0; j < quarter; ++j)
7025 {
7026 const Complex tw1 = twiddles_(tw_w1_off + j);
7027 const Complex tw2 = twiddles_(tw_w2_off + j);
7028 const Complex w1 = invert ? std::conj(tw1) : tw1;
7029 const Complex w2 = invert ? std::conj(tw2) : tw2;
7030 const Complex w3 = w1 * w2;
7031
7032 const size_t i0 = base + j;
7033 const size_t i1 = i0 + quarter;
7034 const size_t i2 = i1 + quarter;
7035 const size_t i3 = i2 + quarter;
7036
7037 const Complex a0 = a[i0];
7038 const Complex a1 = a[i1] * w2;
7039 const Complex a2 = a[i2] * w1;
7040 const Complex a3 = a[i3] * w3;
7041
7042 const Complex p = a0 + a1;
7043 const Complex q = a0 - a1;
7044 const Complex r = a2 + a3;
7045 const Complex s = a2 - a3;
7046
7047 const Complex js = invert ? Complex(-s.imag(), s.real()) : Complex(s.imag(), -s.real());
7048
7049 a(i0) = p + r;
7050 a(i1) = q + js;
7051 a(i2) = p - r;
7052 a(i3) = q - js;
7053 }
7054 };
7055
7056 if (pool != nullptr and pool->num_threads() > 1 and blocks > 1)
7057 parallel_for_index(*pool, 0, blocks, r4_block, chunk_size);
7058 else
7059 for (size_t block = 0; block < blocks; ++block)
7060 r4_block(block);
7061
7062 stage += 2;
7063 }
7064
7065 if (invert)
7066 {
7067 const Real inv_n = Real(1) / static_cast<Real>(n_);
7068 for (size_t i = 0; i < n_; ++i)
7069 a(i) *= inv_n;
7070 }
7071 }
7072
7073 void
7075 const bool invert,
7076 ThreadPool *pool,
7077 const size_t chunk_size,
7078 const bool prefer_simd) const
7079 {
7081 << "FFT::Plan::transform: input size " << a.size()
7082 << " does not match plan size " << n_;
7083
7084 if (n_ <= 1)
7085 return;
7086
7087 switch (strategy_)
7088 {
7091 apply_butterflies(a, invert, pool, chunk_size, prefer_simd);
7092 break;
7094 apply_mixed_radix_transform(a, invert);
7095 break;
7097 apply_bluestein_transform(a, invert, pool, chunk_size);
7098 break;
7099 case Strategy::empty:
7100 break;
7101 }
7102 }
7103
7104 [[nodiscard]] Complex
7105 root_for_length(const size_t length,
7106 const size_t exponent,
7107 const bool invert) const
7108 {
7109 if (length <= 1)
7110 return Complex(Real(1), Real(0));
7111
7112 const size_t stride = n_ / length;
7113 const size_t index = ((exponent % length) * stride) % n_;
7114 const Complex root = roots_[index];
7115 return invert ? std::conj(root) : root;
7116 }
7117
7120 const size_t factor_index,
7121 const size_t current_size,
7122 const bool invert) const
7123 {
7124 if (current_size <= 1)
7125 return input;
7126
7127 const size_t factor = factors_[factor_index];
7128 const size_t sub_size = current_size / factor;
7129
7131 subtransforms.reserve(factor);
7132 for (size_t q = 0; q < factor; ++q)
7133 {
7134 Array<Complex> subsequence;
7135 subsequence.reserve(sub_size);
7136 for (size_t t = 0; t < sub_size; ++t)
7137 subsequence.append(input[factor * t + q]);
7139 factor_index + 1,
7140 sub_size,
7141 invert));
7142 }
7143
7145 for (size_t k1 = 0; k1 < sub_size; ++k1)
7146 for (size_t p = 0; p < factor; ++p)
7147 {
7148 Complex sum(Real(0), Real(0));
7149 const size_t target = k1 + sub_size * p;
7150 for (size_t q = 0; q < factor; ++q)
7151 sum += subtransforms[q][k1]
7152 * root_for_length(current_size, q * target, invert);
7153 output(target) = sum;
7154 }
7155
7156 return output;
7157 }
7158
7159 void
7160 apply_mixed_radix_transform(Array<Complex> & a, const bool invert) const
7161 {
7163 if (invert)
7164 {
7165 const Real inv_n = Real(1) / static_cast<Real>(n_);
7166 for (size_t i = 0; i < n_; ++i)
7167 output(i) *= inv_n;
7168 }
7169 a = std::move(output);
7170 }
7171
7172 void
7174 const bool invert,
7175 ThreadPool *pool,
7176 const size_t chunk_size) const
7177 {
7179
7180 auto initialize = [this, &a, &work, invert](const size_t i)
7181 {
7182 if (i >= n_)
7183 {
7184 work(i) = Complex(Real(0), Real(0));
7185 return;
7186 }
7187
7188 const Complex chirp = invert ? bluestein_chirp_[i] : std::conj(bluestein_chirp_[i]);
7189 work(i) = a[i] * chirp;
7190 };
7191
7192 if (pool != nullptr and pool->num_threads() > 1 and bluestein_size_ > 1)
7193 parallel_for_index(*pool, 0, bluestein_size_, initialize, chunk_size);
7194 else
7195 for (size_t i = 0; i < bluestein_size_; ++i)
7196 initialize(i);
7197
7198 if (pool != nullptr)
7199 bluestein_plan_->ptransform(*pool, work, false, chunk_size);
7200 else
7201 bluestein_plan_->transform(work, false);
7202
7204
7205 auto pointwise = [&work, &kernel](const size_t i)
7206 {
7207 work(i) *= kernel[i];
7208 };
7209
7210 if (pool != nullptr and pool->num_threads() > 1 and bluestein_size_ > 1)
7211 parallel_for_index(*pool, 0, bluestein_size_, pointwise, chunk_size);
7212 else
7213 for (size_t i = 0; i < bluestein_size_; ++i)
7214 pointwise(i);
7215
7216 if (pool != nullptr)
7217 bluestein_plan_->ptransform(*pool, work, true, chunk_size);
7218 else
7219 bluestein_plan_->transform(work, true);
7220
7221 auto finalize = [this, &a, &work, invert](const size_t i)
7222 {
7223 const Complex chirp = invert ? bluestein_chirp_[i] : std::conj(bluestein_chirp_[i]);
7224 a(i) = work[i] * chirp;
7225 if (invert)
7226 a(i) /= static_cast<Real>(n_);
7227 };
7228
7229 if (pool != nullptr and pool->num_threads() > 1 and n_ > 1)
7230 parallel_for_index(*pool, 0, n_, finalize, chunk_size);
7231 else
7232 for (size_t i = 0; i < n_; ++i)
7233 finalize(i);
7234 }
7235
7236 [[nodiscard]] bool
7243
7244 public:
7246 Plan() = default;
7247
7256 explicit Plan(const size_t n) : n_(n)
7257 {
7259 << "FFT::Plan: size must be positive";
7260
7261 if (n == 1)
7262 {
7264 return;
7265 }
7266
7267 if (is_power_of_two(n_))
7268 {
7270 return;
7271 }
7272
7274 if (not factors_.is_empty())
7275 {
7277 return;
7278 }
7279
7281 }
7282
7284 [[nodiscard]] size_t size() const noexcept { return n_; }
7285
7292 void transform(Array<Complex> & a, const bool invert) const
7293 {
7294 apply_transform(a, invert, nullptr, 0, false);
7295 }
7296
7305 const bool invert = false) const
7306 {
7308 transform(output, invert);
7309 return output;
7310 }
7311
7315 {
7316 return transformed(input, true);
7317 }
7318
7326 {
7328 << "FFT::Plan::inverse_transform_real: input size " << input.size()
7329 << " does not match plan size " << n_;
7330
7331 if (n_ == 0)
7332 return {};
7333
7336 input, "FFT::Plan::inverse_transform_real", nullptr, 0,
7337 [this](Array<Complex> & packed, ThreadPool *, const size_t)
7338 {
7339 half_plan_->transform(packed, true);
7340 });
7341
7342 FFT::validate_real_spectrum(input, "FFT::Plan::inverse_transform_real");
7344 transform(output, true);
7345 return FFT::project_real_output(output, "FFT::Plan::inverse_transform_real");
7346 }
7347
7351 const size_t chunk_size = 0) const
7352 {
7354 << "FFT::Plan::pinverse_transform_real: input size "
7355 << input.size() << " does not match plan size " << n_;
7356
7357 if (n_ == 0)
7358 return {};
7359
7362 input, "FFT::Plan::inverse_transform_real", &pool,
7363 chunk_size,
7365 const size_t inner_chunk_size)
7366 {
7367 if (inner_pool != nullptr)
7368 half_plan_->ptransform(*inner_pool, packed, true,
7370 else
7371 half_plan_->transform(packed, true);
7372 });
7373
7374 FFT::validate_real_spectrum(input, "FFT::Plan::inverse_transform_real");
7376 ptransform(pool, output, true, chunk_size);
7377 return FFT::project_real_output(output, "FFT::Plan::inverse_transform_real");
7378 }
7379
7388 const bool invert,
7389 const size_t chunk_size = 0) const
7390 {
7391 apply_transform(a, invert, &pool, chunk_size, false);
7392 }
7393
7397 const bool invert = false,
7398 const size_t chunk_size = 0) const
7399 {
7401 ptransform(pool, output, invert, chunk_size);
7402 return output;
7403 }
7404
7408 const size_t chunk_size = 0) const
7409 {
7410 return ptransformed(pool, input, true, chunk_size);
7411 }
7412
7414 void
7416 const bool invert,
7417 const bool prefer_simd = true) const
7418 {
7419 const bool effective_prefer_simd =
7421 for (size_t i = 0; i < batch.size(); ++i)
7422 {
7424 << "FFT::Plan::transform_batch: batch item " << i
7425 << " has size " << batch[i].size()
7426 << " but plan size is " << n_;
7427 apply_transform(batch(i), invert, nullptr, 0, effective_prefer_simd);
7428 }
7429 }
7430
7437 void
7440 const bool invert,
7441 const size_t chunk_size = 0,
7442 const bool prefer_simd = true) const
7443 {
7444 const bool effective_prefer_simd =
7446 for (size_t i = 0; i < batch.size(); ++i)
7448 << "FFT::Plan::ptransform_batch: batch item " << i
7449 << " has size " << batch[i].size()
7450 << " but plan size is " << n_;
7451
7452 if (batch.is_empty())
7453 return;
7454
7455 if (batch.size() == 1 or pool.num_threads() <= 1)
7456 {
7457 for (size_t i = 0; i < batch.size(); ++i)
7459 invert,
7460 &pool,
7461 chunk_size,
7463 return;
7464 }
7465
7466 auto transform_one = [this, &batch, invert, effective_prefer_simd](const size_t i)
7467 {
7468 apply_transform(batch(i), invert, nullptr, 0, effective_prefer_simd);
7469 };
7470 parallel_for_index(pool, 0, batch.size(), transform_one, chunk_size);
7471 }
7472
7476 const bool invert = false,
7477 const bool prefer_simd = true) const
7478 {
7481 return output;
7482 }
7483
7487 const Array<Array<Complex>> & input,
7488 const bool invert = false,
7489 const size_t chunk_size = 0,
7490 const bool prefer_simd = true) const
7491 {
7493 ptransform_batch(pool, output, invert, chunk_size, prefer_simd);
7494 return output;
7495 }
7496
7500 {
7501 return transformed_batch(input, true);
7502 }
7503
7507 const Array<Array<Complex>> & input,
7508 const size_t chunk_size = 0,
7509 const bool prefer_simd = true) const
7510 {
7511 return ptransformed_batch(pool, input, true, chunk_size, prefer_simd);
7512 }
7513
7519 {
7521 for (size_t i = 0; i < input.size(); ++i)
7522 {
7524 << "FFT::Plan::inverse_transform_real_batch: batch item " << i
7525 << " has size " << input[i].size()
7526 << " but plan size is " << n_;
7528 }
7529 return output;
7530 }
7531
7537 const Array<Array<Complex>> & input,
7538 const size_t chunk_size = 0,
7539 const bool prefer_simd [[maybe_unused]] = true) const
7540 {
7541 for (size_t i = 0; i < input.size(); ++i)
7543 << "FFT::Plan::pinverse_transform_real_batch: batch item " << i
7544 << " has size " << input[i].size()
7545 << " but plan size is " << n_;
7546
7547 if (input.is_empty())
7548 return {};
7549
7551 if (input.size() == 1 or pool.num_threads() <= 1)
7552 {
7553 for (size_t i = 0; i < input.size(); ++i)
7554 output(i) = pinverse_transform_real(pool, input[i], chunk_size);
7555 return output;
7556 }
7557
7558 auto inverse_one = [this, &input, &output](const size_t i)
7559 {
7561 };
7562 parallel_for_index(pool, 0, input.size(), inverse_one, chunk_size);
7563 return output;
7564 }
7565
7574 rfft(const Array<Real> & input) const
7575 {
7577 << "FFT::Plan::rfft: input size " << input.size()
7578 << " does not match plan size " << n_;
7580 }
7581
7585 const size_t chunk_size = 0) const
7586 {
7588 << "FFT::Plan::prfft: input size " << input.size()
7589 << " does not match plan size " << n_;
7591 input,
7592 &pool,
7593 chunk_size));
7594 }
7595
7603 {
7605 n_,
7606 "FFT::Plan::irfft"));
7607 }
7608
7612 const size_t chunk_size = 0) const
7613 {
7614 return pinverse_transform_real(pool,
7616 n_,
7617 "FFT::Plan::pirfft"),
7618 chunk_size);
7619 }
7620
7628
7632 const Array<Array<Real>> & input,
7633 const size_t chunk_size = 0) const
7634 {
7636 FFT::project_to_plan_batch_spectra(*this, input, &pool, chunk_size));
7637 }
7638
7642 {
7644 spectra, n_, "FFT::Plan::irfft_batch"));
7645 }
7646
7650 const Array<Array<Complex>> & spectra,
7651 const size_t chunk_size = 0) const
7652 {
7655 spectra,
7656 n_,
7657 "FFT::Plan::pirfft_batch"),
7658 chunk_size);
7659 }
7660 };
7661
7662 private:
7663 [[nodiscard]] static Array<Complex>
7665 const Array<Real> & input,
7666 ThreadPool *pool = nullptr,
7667 const size_t chunk_size = 0)
7668 {
7670 if (pool != nullptr)
7671 plan.ptransform(*pool, lifted, false, chunk_size);
7672 else
7673 plan.transform(lifted, false);
7674 return lifted;
7675 }
7676
7679 const Array<Array<Real>> & input,
7680 ThreadPool *pool = nullptr,
7681 const size_t chunk_size = 0)
7682 {
7684 lifted.reserve(input.size());
7685 for (size_t i = 0; i < input.size(); ++i)
7686 lifted.append(lift_real_input(input[i]));
7687
7688 if (pool != nullptr)
7689 plan.ptransform_batch(*pool, lifted, false, chunk_size);
7690 else
7691 plan.transform_batch(lifted, false);
7692 return lifted;
7693 }
7694
7695 [[nodiscard]] static Array<Complex>
7697 {
7699 << "FFT::compact_real_spectrum: spectrum must be non-empty";
7700
7702 compact.reserve(full_spectrum.size() / 2 + 1);
7703 for (size_t i = 0; i <= full_spectrum.size() / 2; ++i)
7704 compact.append(full_spectrum[i]);
7705 return compact;
7706 }
7707
7710 {
7713 for (size_t i = 0; i < full_spectra.size(); ++i)
7715 return compact;
7716 }
7717
7718 [[nodiscard]] static size_t
7720 const size_t signal_size,
7721 const char *ctx)
7722 {
7724 << ctx << ": compact spectrum must be non-empty";
7725
7726 if (signal_size != 0)
7727 {
7729 << ctx << ": compact spectrum size " << spectrum.size()
7730 << " does not match signal size " << signal_size;
7731 return signal_size;
7732 }
7733
7734 if (spectrum.size() == 1)
7735 return 1;
7736
7738 > std::numeric_limits<size_t>::max() / 2)
7739 << ctx << ": inferred signal size overflows size_t";
7740 return 2 * (spectrum.size() - 1);
7741 }
7742
7743 [[nodiscard]] static Array<Complex>
7745 const size_t signal_size,
7746 const char *ctx)
7747 {
7748 const size_t n = resolve_irfft_signal_size(spectrum, signal_size, ctx);
7749 const size_t half = n / 2;
7750
7752 for (size_t i = 0; i < n; ++i)
7753 full(i) = Complex(Real(0), Real(0));
7754
7755 for (size_t i = 0; i <= half; ++i)
7756 full(i) = spectrum[i];
7757 for (size_t i = half + 1; i < n; ++i)
7758 full(i) = std::conj(full[n - i]);
7759
7760 return full;
7761 }
7762
7765 const size_t signal_size,
7766 const char *ctx)
7767 {
7769 full.reserve(spectra.size());
7770 for (size_t i = 0; i < spectra.size(); ++i)
7772 return full;
7773 }
7774
7775 [[nodiscard]] static Array<Array<Real>>
7777 const char *ctx)
7778 {
7780 output.reserve(input.size());
7781 for (size_t i = 0; i < input.size(); ++i)
7782 output.append(project_real_output(input[i], ctx));
7783 return output;
7784 }
7785
7786 static void
7788 const bool invert,
7789 ThreadPool *pool = nullptr,
7790 const size_t chunk_size = 0)
7791 {
7792 const size_t n = a.size();
7793 ah_invalid_argument_if(n == 0) << "FFT::transform: input must be non-empty";
7794
7795 if (n == 1)
7796 return;
7797
7798 if (is_power_of_two(n))
7799 {
7800 transform_impl(a, invert, pool, chunk_size);
7801 return;
7802 }
7803
7804 const Plan plan(n);
7805 if (pool != nullptr)
7806 plan.ptransform(*pool, a, invert, chunk_size);
7807 else
7808 plan.transform(a, invert);
7809 }
7810
7811 [[nodiscard]] static Array<Real>
7813 const char *ctx,
7814 ThreadPool *pool,
7815 const size_t chunk_size)
7816 {
7819 transform_any_size_impl(output, true, pool, chunk_size);
7820 return project_real_output(output, ctx);
7821 }
7822
7823 public:
7837 static void
7838 transform(Array<Complex> & a, const bool invert)
7839 {
7840 transform_any_size_impl(a, invert);
7841 }
7842
7856 static void
7857 ptransform(ThreadPool & pool, Array<Complex> & a, const bool invert,
7858 const size_t chunk_size = 0)
7859 {
7860 transform_any_size_impl(a, invert, &pool, chunk_size);
7861 }
7862
7874 [[nodiscard]] static Array<Complex>
7875 transformed(const Array<Complex> & input, const bool invert = false)
7876 {
7878 transform(output, invert);
7879 return output;
7880 }
7881
7894 [[nodiscard]] static Array<Complex>
7896 const bool invert = false, const size_t chunk_size = 0)
7897 {
7899 ptransform(pool, output, invert, chunk_size);
7900 return output;
7901 }
7902
7904 static void
7906 {
7907 if (batch.is_empty())
7908 return;
7909
7910 Plan plan(batch[0].size());
7911 plan.transform_batch(batch, invert);
7912 }
7913
7915 static void
7918 const bool invert,
7919 const size_t chunk_size = 0)
7920 {
7921 if (batch.is_empty())
7922 return;
7923
7924 Plan plan(batch[0].size());
7925 plan.ptransform_batch(pool, batch, invert, chunk_size);
7926 }
7927
7931 const bool invert = false)
7932 {
7934 transform_batch(output, invert);
7935 return output;
7936 }
7937
7941 const Array<Array<Complex>> & input,
7942 const bool invert = false,
7943 const size_t chunk_size = 0)
7944 {
7946 ptransform_batch(pool, output, invert, chunk_size);
7947 return output;
7948 }
7949
7953 {
7954 return transformed_batch(input, true);
7955 }
7956
7960 const Array<Array<Complex>> & input,
7961 const size_t chunk_size = 0)
7962 {
7963 return ptransformed_batch(pool, input, true, chunk_size);
7964 }
7965
7969 {
7970 if (input.is_empty())
7971 return {};
7972
7973 Plan plan(input[0].size());
7974 return plan.rfft_batch(input);
7975 }
7976
7980 const Array<Array<Real>> & input,
7981 const size_t chunk_size = 0)
7982 {
7983 if (input.is_empty())
7984 return {};
7985
7986 Plan plan(input[0].size());
7987 return plan.prfft_batch(pool, input, chunk_size);
7988 }
7989
7991 [[nodiscard]] static Array<Array<Real>>
7993 const size_t signal_size)
7994 {
7995 if (spectra.is_empty())
7996 return {};
7997
7999 return plan.irfft_batch(spectra);
8000 }
8001
8003 [[nodiscard]] static Array<Array<Real>>
8005 const Array<Array<Complex>> & spectra,
8006 const size_t signal_size,
8007 const size_t chunk_size = 0)
8008 {
8009 if (spectra.is_empty())
8010 return {};
8011
8013 return plan.pirfft_batch(pool, spectra, chunk_size);
8014 }
8015
8017 [[nodiscard]] static TensorLayout
8019 {
8021 layout.shape = shape;
8022 layout.strides = row_major_strides(shape, "FFT::row_major_layout");
8023 return layout;
8024 }
8025
8027 static void
8029 const TensorLayout & layout,
8030 const size_t axis,
8031 const bool invert)
8032 {
8033 transform_axis_impl(data, layout, axis, invert);
8034 }
8035
8037 static void
8039 Array<Complex> & data,
8040 const TensorLayout & layout,
8041 const size_t axis,
8042 const bool invert,
8043 const size_t chunk_size = 0)
8044 {
8045 transform_axis_impl(data, layout, axis, invert, &pool, chunk_size);
8046 }
8047
8049 static void
8051 const TensorLayout & layout,
8052 const Array<size_t> & axes,
8053 const bool invert)
8054 {
8055 transform_axes_impl(data, layout, axes, invert);
8056 }
8057
8059 static void
8061 Array<Complex> & data,
8062 const TensorLayout & layout,
8063 const Array<size_t> & axes,
8064 const bool invert,
8065 const size_t chunk_size = 0)
8066 {
8067 transform_axes_impl(data, layout, axes, invert, &pool, chunk_size);
8068 }
8069
8071 [[nodiscard]] static Array<Complex>
8073 const TensorLayout & layout,
8074 const Array<size_t> & axes,
8075 const bool invert = false)
8076 {
8078 transform_axes(output, layout, axes, invert);
8079 return output;
8080 }
8081
8083 [[nodiscard]] static Array<Complex>
8085 const Array<Complex> & input,
8086 const TensorLayout & layout,
8087 const Array<size_t> & axes,
8088 const bool invert = false,
8089 const size_t chunk_size = 0)
8090 {
8092 ptransform_axes(pool, output, layout, axes, invert, chunk_size);
8093 return output;
8094 }
8095
8099 const bool invert = false)
8100 {
8101 const Array<size_t> shape = matrix_shape(input, "FFT::transformed2d");
8102 Array<Complex> flat = flatten_matrix_row_major(input, "FFT::transformed2d");
8103 transform_axes(flat, row_major_layout(shape), Array<size_t>({0, 1}), invert);
8105 shape[0],
8106 shape[1],
8107 "FFT::transformed2d");
8108 }
8109
8113 const Array<Array<Complex>> & input,
8114 const bool invert = false,
8115 const size_t chunk_size = 0)
8116 {
8117 const Array<size_t> shape = matrix_shape(input, "FFT::ptransformed2d");
8118 Array<Complex> flat = flatten_matrix_row_major(input, "FFT::ptransformed2d");
8119 ptransform_axes(pool,
8120 flat,
8121 row_major_layout(shape),
8122 Array<size_t>({0, 1}),
8123 invert,
8124 chunk_size);
8126 shape[0],
8127 shape[1],
8128 "FFT::ptransformed2d");
8129 }
8130
8134 {
8135 return transformed2d(input, true);
8136 }
8137
8141 const Array<Array<Complex>> & input,
8142 const size_t chunk_size = 0)
8143 {
8144 return ptransformed2d(pool, input, true, chunk_size);
8145 }
8146
8150 const bool invert = false)
8151 {
8152 const Array<size_t> shape = tensor3_shape(input, "FFT::transformed2d_batch");
8154 "FFT::transformed2d_batch");
8155 transform_axes(flat, row_major_layout(shape), Array<size_t>({1, 2}), invert);
8157 shape[0],
8158 shape[1],
8159 shape[2],
8160 "FFT::transformed2d_batch");
8161 }
8162
8166 const Array<Array<Array<Complex>>> & input,
8167 const bool invert = false,
8168 const size_t chunk_size = 0)
8169 {
8170 const Array<size_t> shape = tensor3_shape(input, "FFT::ptransformed2d_batch");
8172 "FFT::ptransformed2d_batch");
8173 ptransform_axes(pool,
8174 flat,
8175 row_major_layout(shape),
8176 Array<size_t>({1, 2}),
8177 invert,
8178 chunk_size);
8180 shape[0],
8181 shape[1],
8182 shape[2],
8183 "FFT::ptransformed2d_batch");
8184 }
8185
8189 const bool invert = false)
8190 {
8191 const Array<size_t> shape = tensor3_shape(input, "FFT::transformed3d");
8192 Array<Complex> flat = flatten_tensor3_row_major(input, "FFT::transformed3d");
8194 row_major_layout(shape),
8195 Array<size_t>({0, 1, 2}),
8196 invert);
8198 shape[0],
8199 shape[1],
8200 shape[2],
8201 "FFT::transformed3d");
8202 }
8203
8207 const Array<Array<Array<Complex>>> & input,
8208 const bool invert = false,
8209 const size_t chunk_size = 0)
8210 {
8211 const Array<size_t> shape = tensor3_shape(input, "FFT::ptransformed3d");
8212 Array<Complex> flat = flatten_tensor3_row_major(input, "FFT::ptransformed3d");
8213 ptransform_axes(pool,
8214 flat,
8215 row_major_layout(shape),
8216 Array<size_t>({0, 1, 2}),
8217 invert,
8218 chunk_size);
8220 shape[0],
8221 shape[1],
8222 shape[2],
8223 "FFT::ptransformed3d");
8224 }
8225
8229 {
8230 return transformed3d(input, true);
8231 }
8232
8236 const Array<Array<Array<Complex>>> & input,
8237 const size_t chunk_size = 0)
8238 {
8239 return ptransformed3d(pool, input, true, chunk_size);
8240 }
8241
8247 const SpectrogramLayout source,
8248 const SpectrogramLayout target)
8249 {
8251 source,
8252 target,
8253 "FFT::transpose_spectrogram_layout");
8254 }
8255
8274
8278 const Array<Array<Real>> & signals,
8279 const Array<Real> & window,
8280 const STFTOptions & options = {},
8283 const size_t chunk_size = 0)
8284 {
8286 pbatched_stft(pool, signals, window, options, chunk_size);
8291 layout);
8292 }
8293
8297 [[nodiscard]] static Array<Array<Real>>
8318
8320 [[nodiscard]] static Array<Array<Real>>
8345
8346 [[nodiscard]] static Array<Complex>
8353
8354 [[nodiscard]] static Array<Complex>
8356 const size_t chunk_size = 0)
8357 {
8359 ptransform(pool, output, false, chunk_size);
8360 return output;
8361 }
8362
8377 template <typename Container>
8379 [[nodiscard]] static Array<Complex>
8384
8395 template <typename Container>
8397 [[nodiscard]] static Array<Complex>
8399 const size_t chunk_size = 0)
8400 {
8401 return ptransform_padded(pool, build_complex_input(input), chunk_size);
8402 }
8403
8417 template <typename Container>
8419 [[nodiscard]] static Array<Complex>
8420 transformed(const Container & input, const bool invert = false)
8421 {
8422 return transformed(build_complex_input(input), invert);
8423 }
8424
8436 template <typename Container>
8438 [[nodiscard]] static Array<Complex>
8440 const bool invert = false, const size_t chunk_size = 0)
8441 {
8442 return ptransformed(pool, build_complex_input(input), invert, chunk_size);
8443 }
8444
8455 [[nodiscard]] static Array<Complex>
8457 {
8459 }
8460
8472 [[nodiscard]] static Array<Complex>
8474 const size_t chunk_size = 0)
8475 {
8476 return transform_real_optimized(input, &pool, chunk_size);
8477 }
8478
8485 [[nodiscard]] static Array<Complex>
8487 {
8489 }
8490
8492 [[nodiscard]] static Array<Complex>
8494 const size_t chunk_size = 0)
8495 {
8496 return compact_real_spectrum(ptransform(pool, input, chunk_size));
8497 }
8498
8500 [[nodiscard]] static Array<Complex>
8505
8507 [[nodiscard]] static Array<Complex>
8509 const size_t chunk_size = 0)
8510 {
8512 chunk_size);
8513 }
8514
8516 template <typename Container>
8518 [[nodiscard]] static Array<Complex>
8520 {
8521 return transformed(input, false);
8522 }
8523
8525 template <typename Container>
8527 [[nodiscard]] static Array<Complex>
8529 const size_t chunk_size = 0)
8530 {
8531 return ptransformed(pool, input, false, chunk_size);
8532 }
8533
8535 template <typename Container>
8537 [[nodiscard]] static Array<Complex>
8542
8544 template <typename Container>
8546 [[nodiscard]] static Array<Complex>
8548 const size_t chunk_size = 0)
8549 {
8550 return ptransform_padded(pool, build_real_input(input), chunk_size);
8551 }
8552
8554 template <typename Container>
8556 [[nodiscard]] static Array<Complex>
8558 {
8560 }
8561
8563 template <typename Container>
8565 [[nodiscard]] static Array<Complex>
8567 const size_t chunk_size = 0)
8568 {
8569 return ptransform(pool, build_real_input(input), chunk_size);
8570 }
8571
8573 template <typename Container>
8575 [[nodiscard]] static Array<Complex>
8577 {
8578 return rfft(build_real_input(input));
8579 }
8580
8582 template <typename Container>
8584 [[nodiscard]] static Array<Complex>
8586 const size_t chunk_size = 0)
8587 {
8588 return prfft(pool, build_real_input(input), chunk_size);
8589 }
8590
8592 [[nodiscard]] static Array<Complex>
8594 {
8595 return transformed(input, false);
8596 }
8597
8599 [[nodiscard]] static Array<Complex>
8601 const size_t chunk_size = 0)
8602 {
8603 return ptransformed(pool, input, false, chunk_size);
8604 }
8605
8607 [[nodiscard]] static Array<Complex>
8609 {
8610 return transform(input);
8611 }
8612
8614 [[nodiscard]] static Array<Complex>
8616 const size_t chunk_size = 0)
8617 {
8618 return ptransform(pool, input, chunk_size);
8619 }
8620
8622 template <typename Container>
8624 [[nodiscard]] static Array<Complex>
8626 {
8627 return transformed(input, false);
8628 }
8629
8631 template <typename Container>
8633 [[nodiscard]] static Array<Complex>
8635 const size_t chunk_size = 0)
8636 {
8637 return ptransformed(pool, input, false, chunk_size);
8638 }
8639
8641 template <typename Container>
8643 [[nodiscard]] static Array<Complex>
8645 {
8646 return transform(input);
8647 }
8648
8650 template <typename Container>
8652 [[nodiscard]] static Array<Complex>
8654 const size_t chunk_size = 0)
8655 {
8656 return ptransform(pool, input, chunk_size);
8657 }
8658
8669 [[nodiscard]] static Array<Complex>
8671 {
8672 return transformed(input, true);
8673 }
8674
8676 [[nodiscard]] static Array<Complex>
8678 const size_t chunk_size = 0)
8679 {
8680 return ptransformed(pool, input, true, chunk_size);
8681 }
8682
8684 template <typename Container>
8686 [[nodiscard]] static Array<Complex>
8688 {
8689 return transformed(input, true);
8690 }
8691
8693 template <typename Container>
8695 [[nodiscard]] static Array<Complex>
8697 const size_t chunk_size = 0)
8698 {
8699 return ptransformed(pool, input, true, chunk_size);
8700 }
8701
8713 [[nodiscard]] static Array<Real>
8715 {
8716 return is_power_of_two(input.size()) ?
8718 "FFT::inverse_transform_real", nullptr, 0,
8720 const size_t inner_chunk_size)
8721 {
8723 }) :
8725 "FFT::inverse_transform_real",
8726 nullptr,
8727 0);
8728 }
8729
8731 [[nodiscard]] static Array<Real>
8733 const size_t chunk_size = 0)
8734 {
8735 return is_power_of_two(input.size()) ?
8737 "FFT::inverse_transform_real", &pool, chunk_size,
8739 const size_t inner_chunk_size)
8740 {
8742 }) :
8744 "FFT::inverse_transform_real",
8745 &pool,
8746 chunk_size);
8747 }
8748
8755 [[nodiscard]] static Array<Real>
8756 irfft(const Array<Complex> & spectrum, const size_t signal_size = 0)
8757 {
8760 "FFT::irfft"));
8761 }
8762
8764 [[nodiscard]] static Array<Real>
8766 const Array<Complex> & spectrum,
8767 const size_t signal_size = 0,
8768 const size_t chunk_size = 0)
8769 {
8770 return pinverse_transform_real(pool,
8773 "FFT::pirfft"),
8774 chunk_size);
8775 }
8776
8778 template <typename Container>
8780 [[nodiscard]] static Array<Real>
8785
8787 template <typename Container>
8789 [[nodiscard]] static Array<Real>
8791 const size_t chunk_size = 0)
8792 {
8793 return pinverse_transform_real(pool, build_complex_input(input), chunk_size);
8794 }
8795
8797 template <typename Container>
8799 [[nodiscard]] static Array<Real>
8800 irfft(const Container & spectrum, const size_t signal_size = 0)
8801 {
8803 }
8804
8806 template <typename Container>
8808 [[nodiscard]] static Array<Real>
8810 const Container & spectrum,
8811 const size_t signal_size = 0,
8812 const size_t chunk_size = 0)
8813 {
8814 return pirfft(pool,
8817 chunk_size);
8818 }
8819
8821 [[nodiscard]] static Array<Real>
8823 {
8825 output.reserve(input.size());
8826 for (size_t i = 0; i < input.size(); ++i)
8827 output.append(std::abs(input[i]));
8828 return output;
8829 }
8830
8832 template <typename Container>
8834 [[nodiscard]] static Array<Real>
8839
8841 [[nodiscard]] static Array<Real>
8843 {
8845 output.reserve(input.size());
8846 for (size_t i = 0; i < input.size(); ++i)
8847 output.append(std::norm(input[i]));
8848 return output;
8849 }
8850
8851 template <typename Container>
8853 [[nodiscard]] static Array<Real>
8858
8860 [[nodiscard]] static Array<Real>
8862 {
8864 output.reserve(input.size());
8865 for (size_t i = 0; i < input.size(); ++i)
8866 output.append(std::arg(input[i]));
8867 return output;
8868 }
8869
8870 template <typename Container>
8872 [[nodiscard]] static Array<Real>
8877
8879 [[nodiscard]] static Array<Real>
8880 hann_window(const size_t n)
8881 {
8882 return cosine_sum_window(n, Real(0.5), Real(0.5));
8883 }
8884
8886 [[nodiscard]] static Array<Real>
8887 hamming_window(const size_t n)
8888 {
8889 return cosine_sum_window(n, Real(0.54), Real(0.46));
8890 }
8891
8893 [[nodiscard]] static Array<Real>
8894 blackman_window(const size_t n)
8895 {
8896 return cosine_sum_window(n, Real(0.42), Real(0.5), Real(0.08));
8897 }
8898
8900 [[nodiscard]] static Real
8901 kaiser_beta(const Real attenuation_db)
8902 {
8903 ah_invalid_argument_if(attenuation_db < Real(0))
8904 << "FFT::kaiser_beta: attenuation " << attenuation_db
8905 << " must be non-negative";
8906
8907 if (attenuation_db > Real(50))
8908 return Real(0.1102) * (attenuation_db - Real(8.7));
8909 if (attenuation_db >= Real(21))
8910 return Real(0.5842) * std::pow(attenuation_db - Real(21), Real(0.4))
8911 + Real(0.07886) * (attenuation_db - Real(21));
8912 return Real(0);
8913 }
8914
8916 [[nodiscard]] static Array<Real>
8917 kaiser_window(const size_t n,
8918 const Real beta)
8919 {
8920 ah_invalid_argument_if(beta < Real(0))
8921 << "FFT::kaiser_window: beta " << beta << " must be non-negative";
8922
8923 if (n == 0)
8924 return {};
8925 if (n == 1)
8926 return {Real(1)};
8927
8928 const Real denom = modified_bessel_i0(beta);
8929 const Real half = static_cast<Real>(n - 1) / Real(2);
8930 Array<Real> window;
8931 window.reserve(n);
8932 for (size_t i = 0; i < n; ++i)
8933 {
8934 const Real x = (static_cast<Real>(i) - half) / half;
8935 const Real arg = beta * std::sqrt(std::max(Real(0), Real(1) - x * x));
8937 }
8938 return window;
8939 }
8940
8942 [[nodiscard]] static Array<Real>
8943 apply_window(const Array<Real> & signal, const Array<Real> & window)
8944 {
8945 ah_invalid_argument_if(signal.size() != window.size())
8946 << "FFT::apply_window: signal size " << signal.size()
8947 << " does not match window size " << window.size();
8948
8950 for (size_t i = 0; i < signal.size(); ++i)
8951 output(i) = signal[i] * window[i];
8952 return output;
8953 }
8954
8956 [[nodiscard]] static Array<Complex>
8957 apply_window(const Array<Complex> & signal, const Array<Real> & window)
8958 {
8959 ah_invalid_argument_if(signal.size() != window.size())
8960 << "FFT::apply_window: signal size " << signal.size()
8961 << " does not match window size " << window.size();
8962
8964 for (size_t i = 0; i < signal.size(); ++i)
8965 output(i) = signal[i] * window[i];
8966 return output;
8967 }
8968
8969 template <typename SignalContainer, typename WindowContainer>
8971 [[nodiscard]] static Array<Real>
8972 apply_window(const SignalContainer & signal, const WindowContainer & window)
8973 {
8974 return apply_window(build_real_input(signal), build_real_input(window));
8975 }
8976
8977 template <typename SignalContainer, typename WindowContainer>
8979 [[nodiscard]] static Array<Complex>
8980 apply_window(const SignalContainer & signal, const WindowContainer & window)
8981 {
8982 return apply_window(build_complex_input(signal), build_real_input(window));
8983 }
8984
8986 [[nodiscard]] static Array<Real>
8988 {
8989 return apply_window(signal, hann_window(signal.size()));
8990 }
8991
8993 [[nodiscard]] static Array<Real>
8995 {
8996 return apply_window(signal, hamming_window(signal.size()));
8997 }
8998
9000 [[nodiscard]] static Array<Real>
9002 {
9003 return apply_window(signal, blackman_window(signal.size()));
9004 }
9005
9007 [[nodiscard]] static Array<Complex>
9009 {
9010 return apply_window(signal, hann_window(signal.size()));
9011 }
9012
9014 [[nodiscard]] static Array<Complex>
9016 {
9017 return apply_window(signal, hamming_window(signal.size()));
9018 }
9019
9021 [[nodiscard]] static Array<Complex>
9023 {
9024 return apply_window(signal, blackman_window(signal.size()));
9025 }
9026
9028 [[nodiscard]] static Array<Real>
9030 const Real cutoff_frequency,
9031 const Real sample_rate,
9032 const Array<Real> & window)
9033 {
9037 window,
9038 "FFT::firwin_lowpass");
9039 }
9040
9042 [[nodiscard]] static Array<Real>
9044 const Real cutoff_frequency,
9045 const Real sample_rate,
9046 const Real attenuation_db)
9047 {
9048 return firwin_lowpass(num_taps,
9051 kaiser_window(num_taps, kaiser_beta(attenuation_db)));
9052 }
9053
9055 [[nodiscard]] static Array<Real>
9057 const Real cutoff_frequency,
9058 const Real sample_rate,
9059 const Array<Real> & window)
9060 {
9061 Array<Real> coeffs =
9065 window,
9066 "FFT::firwin_highpass");
9067 const size_t center = (num_taps - 1) / 2;
9068 for (size_t i = 0; i < coeffs.size(); ++i)
9069 coeffs(i) = -coeffs[i];
9070 coeffs(center) += Real(1);
9072 std::numbers::pi_v<Real>,
9073 "FFT::firwin_highpass");
9074 return coeffs;
9075 }
9076
9078 [[nodiscard]] static Array<Real>
9080 const Real cutoff_frequency,
9081 const Real sample_rate,
9082 const Real attenuation_db)
9083 {
9087 kaiser_window(num_taps, kaiser_beta(attenuation_db)));
9088 }
9089
9091 [[nodiscard]] static Array<Real>
9095 const Real sample_rate,
9096 const Array<Real> & window)
9097 {
9099 << "FFT::firwin_bandpass: low cutoff " << low_cutoff_frequency
9100 << " must be positive";
9102 << "FFT::firwin_bandpass: high cutoff " << high_cutoff_frequency
9103 << " must be greater than low cutoff " << low_cutoff_frequency;
9105 << "FFT::firwin_bandpass: high cutoff " << high_cutoff_frequency
9106 << " must be smaller than Nyquist " << (sample_rate / Real(2));
9107
9112 window,
9113 "FFT::firwin_bandpass");
9114 Array<Real> high =
9118 window,
9119 "FFT::firwin_bandpass");
9121 for (size_t i = 0; i < num_taps; ++i)
9122 coeffs(i) = low[i] - high[i];
9123
9125 / (Real(2) * sample_rate);
9127 Real(2) * std::numbers::pi_v<Real> * center_frequency,
9128 "FFT::firwin_bandpass");
9129 return coeffs;
9130 }
9131
9133 [[nodiscard]] static Array<Real>
9137 const Real sample_rate,
9138 const Real attenuation_db)
9139 {
9144 kaiser_window(num_taps, kaiser_beta(attenuation_db)));
9145 }
9146
9148 [[nodiscard]] static Array<Real>
9152 const Real sample_rate,
9153 const Array<Real> & window)
9154 {
9155 Array<Real> coeffs =
9160 window);
9161 const size_t center = (num_taps - 1) / 2;
9162 for (size_t i = 0; i < coeffs.size(); ++i)
9163 coeffs(i) = -coeffs[i];
9164 coeffs(center) += Real(1);
9165 normalize_fir_at_omega(coeffs, Real(0), "FFT::firwin_bandstop");
9166 return coeffs;
9167 }
9168
9170 [[nodiscard]] static Array<Real>
9174 const Real sample_rate,
9175 const Real attenuation_db)
9176 {
9181 kaiser_window(num_taps, kaiser_beta(attenuation_db)));
9182 }
9183
9185 [[nodiscard]] static Array<Real>
9186 firls(const size_t num_taps,
9187 const Array<Real> & bands,
9188 const Array<Real> & desired,
9189 const Real sample_rate,
9190 const Array<Real> & weights = {})
9191 {
9192 return firls_impl(num_taps,
9193 bands,
9194 desired,
9196 weights,
9197 "FFT::firls");
9198 }
9199
9200 template <typename BandContainer, typename DesiredContainer>
9203 [[nodiscard]] static Array<Real>
9204 firls(const size_t num_taps,
9205 const BandContainer & bands,
9206 const DesiredContainer & desired,
9207 const Real sample_rate)
9208 {
9209 return firls(num_taps,
9211 build_real_input(desired),
9213 Array<Real>());
9214 }
9215
9216 template <typename BandContainer, typename DesiredContainer,
9217 typename WeightContainer>
9221 [[nodiscard]] static Array<Real>
9222 firls(const size_t num_taps,
9223 const BandContainer & bands,
9224 const DesiredContainer & desired,
9225 const Real sample_rate,
9226 const WeightContainer & weights)
9227 {
9228 return firls(num_taps,
9230 build_real_input(desired),
9232 build_real_input(weights));
9233 }
9234
9236 [[nodiscard]] static Array<Real>
9237 remez(const size_t num_taps,
9238 const Array<Real> & bands,
9239 const Array<Real> & desired,
9240 const Real sample_rate,
9241 const Array<Real> & weights = {},
9242 const size_t grid_density = 32,
9243 const size_t max_iterations = 64)
9244 {
9245 return remez_impl(num_taps,
9246 bands,
9247 desired,
9249 weights,
9252 "FFT::remez");
9253 }
9254
9255 template <typename BandContainer, typename DesiredContainer>
9258 [[nodiscard]] static Array<Real>
9259 remez(const size_t num_taps,
9260 const BandContainer & bands,
9261 const DesiredContainer & desired,
9262 const Real sample_rate)
9263 {
9264 return remez(num_taps,
9266 build_real_input(desired),
9268 Array<Real>());
9269 }
9270
9271 template <typename BandContainer, typename DesiredContainer,
9272 typename WeightContainer>
9276 [[nodiscard]] static Array<Real>
9277 remez(const size_t num_taps,
9278 const BandContainer & bands,
9279 const DesiredContainer & desired,
9280 const Real sample_rate,
9281 const WeightContainer & weights,
9282 const size_t grid_density = 32,
9283 const size_t max_iterations = 64)
9284 {
9285 return remez(num_taps,
9287 build_real_input(desired),
9289 build_real_input(weights),
9292 }
9293
9295 [[nodiscard]] static Array<Real>
9296 upfirdn(const Array<Real> & signal,
9297 const Array<Real> & coeffs,
9298 const size_t up = 1,
9299 const size_t down = 1)
9300 {
9302 << "FFT::upfirdn: up factor must be positive";
9304 << "FFT::upfirdn: down factor must be positive";
9306 << "FFT::upfirdn: FIR coefficients must be non-empty";
9307 if (signal.is_empty())
9308 return {};
9309
9310 ah_invalid_argument_if(signal.size() > std::numeric_limits<size_t>::max() / up)
9311 << "FFT::upfirdn: upsampled signal length overflows size_t";
9312 const size_t convolved_length = signal.size() * up + coeffs.size() - 1;
9313 const size_t output_size = (convolved_length + down - 1) / down;
9314
9316 for (size_t i = 0; i < convolved_length; ++i)
9317 filtered(i) = Real(0);
9318
9319 for (size_t n = 0; n < signal.size(); ++n)
9320 {
9321 const size_t base = n * up;
9322 for (size_t k = 0; k < coeffs.size(); ++k)
9323 filtered(base + k) += signal[n] * coeffs[k];
9324 }
9325
9327 for (size_t i = 0; i < output_size; ++i)
9328 output(i) = filtered[i * down];
9329 return output;
9330 }
9331
9332 template <typename SignalContainer, typename CoeffContainer>
9334 [[nodiscard]] static Array<Real>
9335 upfirdn(const SignalContainer & signal,
9336 const CoeffContainer & coeffs,
9337 const size_t up = 1,
9338 const size_t down = 1)
9339 {
9340 return upfirdn(build_real_input(signal),
9341 build_real_input(coeffs),
9342 up,
9343 down);
9344 }
9345
9347 [[nodiscard]] static Array<Real>
9349 const size_t up,
9350 const size_t down,
9351 const Array<Real> & coeffs)
9352 {
9354 << "FFT::resample_poly: up factor must be positive";
9356 << "FFT::resample_poly: down factor must be positive";
9358 << "FFT::resample_poly: FIR coefficients must be non-empty";
9359 if (signal.is_empty())
9360 return {};
9361
9362 const size_t gcd = std::gcd(up, down);
9363 const size_t reduced_up = up / gcd;
9364 const size_t reduced_down = down / gcd;
9365 const Array<Real> raw = upfirdn(signal, coeffs, reduced_up, reduced_down);
9366
9367 const size_t half_len = effective_coeff_length(coeffs) / 2;
9368 const size_t start = (half_len + reduced_down - 1) / reduced_down;
9369 const size_t expected_size =
9370 (signal.size() * reduced_up + reduced_down - 1) / reduced_down;
9371
9372 ah_runtime_error_if(start > raw.size())
9373 << "FFT::resample_poly: internal trim offset exceeds raw output";
9374
9375 const size_t available = raw.size() - start;
9376 const size_t trimmed_size = std::min(expected_size, available);
9378 if (output.size() == expected_size)
9379 return output;
9380
9381 Array<Real> padded = Array<Real>::create(expected_size);
9382 for (size_t i = 0; i < expected_size; ++i)
9383 padded(i) = i < output.size() ? output[i] : Real(0);
9384 return padded;
9385 }
9386
9388 [[nodiscard]] static Array<Real>
9390 const size_t up,
9391 const size_t down,
9392 const ResamplePolyOptions & options = {})
9393 {
9394 ah_invalid_argument_if(options.taps_per_phase == 0)
9395 << "FFT::resample_poly: taps_per_phase must be positive";
9396 ah_invalid_argument_if(options.attenuation_db <= Real(0))
9397 << "FFT::resample_poly: attenuation " << options.attenuation_db
9398 << " must be strictly positive";
9399 if (signal.is_empty())
9400 return {};
9401
9402 const size_t gcd = std::gcd(up, down);
9403 const size_t reduced_up = up / gcd;
9404 const size_t reduced_down = down / gcd;
9405 const size_t rate = std::max(reduced_up, reduced_down);
9406 const size_t num_taps = 2 * options.taps_per_phase * rate + 1;
9407 const Real effective_rate = static_cast<Real>(reduced_up);
9408 const Real cutoff = effective_rate / (Real(2) * static_cast<Real>(rate));
9409
9410 Array<Real> coeffs =
9412 cutoff,
9414 options.attenuation_db);
9415 for (size_t i = 0; i < coeffs.size(); ++i)
9416 coeffs(i) *= static_cast<Real>(reduced_up);
9417
9418 return resample_poly(signal, reduced_up, reduced_down, coeffs);
9419 }
9420
9421 template <typename SignalContainer, typename CoeffContainer>
9423 [[nodiscard]] static Array<Real>
9425 const size_t up,
9426 const size_t down,
9427 const CoeffContainer & coeffs)
9428 {
9429 return resample_poly(build_real_input(signal),
9430 up,
9431 down,
9432 build_real_input(coeffs));
9433 }
9434
9435 template <typename SignalContainer>
9437 [[nodiscard]] static Array<Real>
9439 const size_t up,
9440 const size_t down,
9441 const ResamplePolyOptions & options = {})
9442 {
9443 return resample_poly(build_real_input(signal), up, down, options);
9444 }
9445
9446 template <typename Container>
9448 [[nodiscard]] static Array<Real>
9450 {
9451 return apply_hann_window(build_real_input(signal));
9452 }
9453
9454 template <typename Container>
9456 [[nodiscard]] static Array<Real>
9458 {
9459 return apply_hamming_window(build_real_input(signal));
9460 }
9461
9462 template <typename Container>
9464 [[nodiscard]] static Array<Real>
9466 {
9468 }
9469
9470 template <typename Container>
9472 [[nodiscard]] static Array<Complex>
9474 {
9475 return apply_hann_window(build_complex_input(signal));
9476 }
9477
9478 template <typename Container>
9480 [[nodiscard]] static Array<Complex>
9482 {
9484 }
9485
9486 template <typename Container>
9488 [[nodiscard]] static Array<Complex>
9490 {
9492 }
9493
9495 [[nodiscard]] static Array<Complex>
9496 windowed_spectrum(const Array<Real> & signal, const Array<Real> & window)
9497 {
9498 return spectrum(apply_window(signal, window));
9499 }
9500
9502 [[nodiscard]] static Array<Complex>
9503 windowed_spectrum(const Array<Complex> & signal, const Array<Real> & window)
9504 {
9505 return spectrum(apply_window(signal, window));
9506 }
9507
9508 template <typename SignalContainer, typename WindowContainer>
9510 [[nodiscard]] static Array<Complex>
9512 const WindowContainer & window)
9513 {
9514 return windowed_spectrum(build_real_input(signal), build_real_input(window));
9515 }
9516
9517 template <typename SignalContainer, typename WindowContainer>
9519 [[nodiscard]] static Array<Complex>
9521 const WindowContainer & window)
9522 {
9524 }
9525
9527 [[nodiscard]] static Real
9529 {
9530 ah_invalid_argument_if(window.is_empty()) << "FFT::window_energy: window must be non-empty";
9531 return sum_squares(window);
9532 }
9533
9535 [[nodiscard]] static Real
9537 {
9539 << "FFT::window_coherent_gain: window must be non-empty";
9540 return sum_values(window) / static_cast<Real>(window.size());
9541 }
9542
9544 [[nodiscard]] static Real
9545 window_enbw(const Array<Real> & window)
9546 {
9548 << "FFT::window_enbw: window must be non-empty";
9549 const Real coherent_sum = sum_values(window);
9550 ah_domain_error_if(std::abs(coherent_sum) <= std::numeric_limits<Real>::epsilon())
9551 << "FFT::window_enbw: coherent gain is zero";
9552 return static_cast<Real>(window.size()) * sum_squares(window)
9554 }
9555
9556 template <typename Container>
9558 [[nodiscard]] static Real
9559 window_energy(const Container & window)
9560 {
9561 return window_energy(build_real_input(window));
9562 }
9563
9564 template <typename Container>
9566 [[nodiscard]] static Real
9568 {
9569 return window_coherent_gain(build_real_input(window));
9570 }
9571
9572 template <typename Container>
9574 [[nodiscard]] static Real
9575 window_enbw(const Container & window)
9576 {
9577 return window_enbw(build_real_input(window));
9578 }
9579
9581 [[nodiscard]] static Array<size_t>
9583 const size_t frame_size,
9584 const size_t hop_size,
9585 const bool pad_end = true)
9586 {
9588 frame_size,
9589 hop_size,
9590 pad_end,
9591 "FFT::frame_offsets");
9592 }
9593
9595 [[nodiscard]] static Array<Real>
9597 const size_t hop_size,
9598 const size_t signal_length = 0)
9599 {
9600 ah_invalid_argument_if(hop_size == 0)
9601 << "FFT::overlap_add_frames: hop size must be positive";
9602 if (frames.is_empty())
9603 return {};
9604
9605 const size_t frame_size = frames[0].size();
9606 ah_invalid_argument_if(frame_size == 0)
9607 << "FFT::overlap_add_frames: frames must be non-empty";
9608 for (size_t i = 1; i < frames.size(); ++i)
9609 ah_invalid_argument_if(frames[i].size() != frame_size)
9610 << "FFT::overlap_add_frames: frame " << i
9611 << " has size " << frames[i].size()
9612 << " but expected " << frame_size;
9613
9614 const size_t total_length = hop_size * (frames.size() - 1) + frame_size;
9615 ah_invalid_argument_if(signal_length > total_length)
9616 << "FFT::overlap_add_frames: requested signal length "
9617 << signal_length << " exceeds overlap-add extent " << total_length;
9618
9620 for (size_t i = 0; i < total_length; ++i)
9621 output(i) = Real(0);
9622
9623 for (size_t frame = 0; frame < frames.size(); ++frame)
9624 {
9625 const size_t offset = frame * hop_size;
9626 for (size_t i = 0; i < frame_size; ++i)
9627 output(offset + i) += frames[frame][i];
9628 }
9629
9630 return signal_length == 0 ?
9631 output :
9632 slice_copy(output, 0, signal_length);
9633 }
9634
9636 [[nodiscard]] static PowerSpectralDensity
9637 welch(const Array<Real> & signal,
9638 const Array<Real> & window,
9639 const Real sample_rate,
9640 const WelchOptions & options = {})
9641 {
9642 const char *ctx = "FFT::welch";
9643 const size_t fft_size = resolve_welch_fft_size(options, window.size(), ctx);
9644 const Array<Array<Real>> frames = prepare_welch_frames(signal, window, options, ctx);
9645 const Real energy = window_energy(window);
9646 ah_domain_error_if(energy <= Real(0))
9647 << ctx << ": window energy must be strictly positive";
9648
9649 Plan plan(fft_size);
9650 Array<Real> density = Array<Real>::create(fft_size / 2 + 1);
9651 for (size_t k = 0; k < density.size(); ++k)
9652 density(k) = Real(0);
9653
9654 for (size_t i = 0; i < frames.size(); ++i)
9655 {
9656 Array<Real> padded = zero_padded_copy(frames[i], fft_size);
9657 const Array<Complex> spectrum = plan.rfft(padded);
9658 for (size_t k = 0; k < spectrum.size(); ++k)
9659 {
9660 Real value = std::norm(spectrum[k]);
9662 density(k) += value;
9663 }
9664 }
9665
9666 const Real scale = Real(1)
9667 / (sample_rate
9668 * energy
9669 * static_cast<Real>(frames.size()));
9670 for (size_t k = 0; k < density.size(); ++k)
9671 density(k) *= scale;
9672
9673 return {one_sided_frequency_grid(fft_size, sample_rate, ctx), density};
9674 }
9675
9677 [[nodiscard]] static PowerSpectralDensity
9678 welch(const Array<Real> & signal,
9679 const size_t frame_size,
9680 const Real sample_rate,
9681 const WelchOptions & options = {})
9682 {
9683 return welch(signal, hann_window(frame_size), sample_rate, options);
9684 }
9685
9687 [[nodiscard]] static CrossSpectralDensity
9688 csd(const Array<Real> & x,
9689 const Array<Real> & y,
9690 const Array<Real> & window,
9691 const Real sample_rate,
9692 const WelchOptions & options = {})
9693 {
9694 const auto ctx = "FFT::csd";
9695 ah_invalid_argument_if(x.size() != y.size())
9696 << ctx << ": signal sizes " << x.size()
9697 << " and " << y.size() << " do not match";
9698
9699 const size_t fft_size = resolve_welch_fft_size(options, window.size(), ctx);
9700 const Array<Array<Real>> x_frames = prepare_welch_frames(x, window, options, ctx);
9702 ah_runtime_error_if(x_frames.size() != y_frames.size())
9703 << ctx << ": frame count mismatch after preparation";
9704
9705 const Real energy = window_energy(window);
9706 ah_domain_error_if(energy <= Real(0))
9707 << ctx << ": window energy must be strictly positive";
9708
9709 Plan plan(fft_size);
9710 Array<Complex> density = Array<Complex>::create(fft_size / 2 + 1);
9711 for (size_t k = 0; k < density.size(); ++k)
9712 density(k) = Complex(Real(0), Real(0));
9713
9714 for (size_t i = 0; i < x_frames.size(); ++i)
9715 {
9716 const Array<Complex> x_spectrum = plan.rfft(zero_padded_copy(x_frames[i], fft_size));
9717 const Array<Complex> y_spectrum = plan.rfft(zero_padded_copy(y_frames[i], fft_size));
9718 for (size_t k = 0; k < density.size(); ++k)
9719 {
9720 Complex value = x_spectrum[k] * std::conj(y_spectrum[k]);
9722 density(k) += value;
9723 }
9724 }
9725
9726 const Real scale = Real(1)
9727 / (sample_rate
9728 * energy
9729 * static_cast<Real>(x_frames.size()));
9730 for (size_t k = 0; k < density.size(); ++k)
9731 density(k) *= scale;
9732
9733 return {one_sided_frequency_grid(fft_size, sample_rate, ctx), density};
9734 }
9735
9737 [[nodiscard]] static CrossSpectralDensity
9738 csd(const Array<Real> & x,
9739 const Array<Real> & y,
9740 const size_t frame_size,
9741 const Real sample_rate,
9742 const WelchOptions & options = {})
9743 {
9744 return csd(x, y, hann_window(frame_size), sample_rate, options);
9745 }
9746
9748 [[nodiscard]] static CoherenceEstimate
9750 const Array<Real> & y,
9751 const Array<Real> & window,
9752 const Real sample_rate,
9753 const WelchOptions & options = {})
9754 {
9755 const CrossSpectralDensity pxy = csd(x, y, window, sample_rate, options);
9756 const PowerSpectralDensity pxx = welch(x, window, sample_rate, options);
9757 const PowerSpectralDensity pyy = welch(y, window, sample_rate, options);
9758
9759 ah_runtime_error_if(pxy.density.size() != pxx.density.size()
9760 or pxy.density.size() != pyy.density.size())
9761 << "FFT::coherence: inconsistent spectrum sizes";
9762
9763 CoherenceEstimate output;
9764 output.frequency = pxy.frequency;
9765 output.magnitude_squared = Array<Real>::create(pxy.density.size());
9766 const Real tol = Real(1024) * std::numeric_limits<Real>::epsilon();
9767 for (size_t k = 0; k < pxy.density.size(); ++k)
9768 {
9769 const Real denom = pxx.density[k] * pyy.density[k];
9770 output.magnitude_squared(k) =
9771 denom <= tol ? Real(0) : std::norm(pxy.density[k]) / denom;
9772 }
9773 return output;
9774 }
9775
9777 [[nodiscard]] static CoherenceEstimate
9779 const Array<Real> & y,
9780 const size_t frame_size,
9781 const Real sample_rate,
9782 const WelchOptions & options = {})
9783 {
9784 return coherence(x, y, hann_window(frame_size), sample_rate, options);
9785 }
9786
9787 template <typename SignalContainer, typename WindowContainer>
9789 [[nodiscard]] static PowerSpectralDensity
9790 welch(const SignalContainer & signal,
9791 const WindowContainer & window,
9792 const Real sample_rate,
9793 const WelchOptions & options = {})
9794 {
9795 return welch(build_real_input(signal),
9796 build_real_input(window),
9798 options);
9799 }
9800
9801 template <typename ContainerX, typename ContainerY, typename WindowContainer>
9805 [[nodiscard]] static CrossSpectralDensity
9806 csd(const ContainerX & x,
9807 const ContainerY & y,
9808 const WindowContainer & window,
9809 const Real sample_rate,
9810 const WelchOptions & options = {})
9811 {
9812 return csd(build_real_input(x),
9814 build_real_input(window),
9816 options);
9817 }
9818
9819 template <typename ContainerX, typename ContainerY, typename WindowContainer>
9823 [[nodiscard]] static CoherenceEstimate
9825 const ContainerY & y,
9826 const WindowContainer & window,
9827 const Real sample_rate,
9828 const WelchOptions & options = {})
9829 {
9830 return coherence(build_real_input(x),
9832 build_real_input(window),
9834 options);
9835 }
9836
9838 [[nodiscard]] static Array<Real>
9841 const size_t hop_size)
9842 {
9845 hop_size,
9846 "FFT::window_overlap_profile");
9847 }
9848
9850 [[nodiscard]] static bool
9853 const size_t hop_size)
9854 {
9857 hop_size));
9858 }
9859
9861 [[nodiscard]] static bool
9864 const size_t hop_size)
9865 {
9868 hop_size));
9869 }
9870
9872 [[nodiscard]] static bool
9873 satisfies_nola(const Array<Real> & window, const size_t hop_size)
9874 {
9875 return satisfies_nola(window, window, hop_size);
9876 }
9877
9879 [[nodiscard]] static bool
9880 satisfies_cola(const Array<Real> & window, const size_t hop_size)
9881 {
9882 return satisfies_cola(window, window, hop_size);
9883 }
9884
9890 [[nodiscard]] static Array<Array<Real>>
9892 const size_t frame_size,
9893 const size_t hop_size,
9894 const bool pad_end = true)
9895 {
9896 Array<Array<Real>> frames;
9897 if (signal.is_empty())
9898 return frames;
9899
9900 const Array<size_t> offsets =
9901 frame_offsets_impl(signal.size(),
9902 frame_size,
9903 hop_size,
9904 pad_end,
9905 "FFT::frame_signal");
9906
9907 for (size_t item = 0; item < offsets.size(); ++item)
9908 {
9909 const size_t offset = offsets[item];
9910 const size_t length = std::min(frame_size, signal.size() - offset);
9911
9912 Array<Real> frame = Array<Real>::create(frame_size);
9913 for (size_t i = 0; i < frame_size; ++i)
9914 frame(i) = Real(0);
9915 for (size_t i = 0; i < length; ++i)
9916 frame(i) = signal[offset + i];
9917
9918 frames.append(frame);
9919 }
9920
9921 return frames;
9922 }
9923
9924 template <typename Container>
9926 [[nodiscard]] static Array<Array<Real>>
9927 frame_signal(const Container & signal,
9928 const size_t frame_size,
9929 const size_t hop_size,
9930 const bool pad_end = true)
9931 {
9932 return frame_signal(build_real_input(signal), frame_size, hop_size, pad_end);
9933 }
9934
9941 stft(const Array<Real> & signal,
9942 const Array<Real> & window,
9943 const size_t hop_size,
9944 const bool pad_end = true)
9945 {
9947 options.hop_size = hop_size;
9948 options.pad_end = pad_end;
9949 return stft_impl(signal, window, options);
9950 }
9951
9954 stft(const Array<Real> & signal,
9955 const size_t frame_size,
9956 const size_t hop_size,
9957 const bool pad_end = true)
9958 {
9959 return stft(signal, hann_window(frame_size), hop_size, pad_end);
9960 }
9961
9962 template <typename SignalContainer, typename WindowContainer>
9965 stft(const SignalContainer & signal,
9966 const WindowContainer & window,
9967 const size_t hop_size,
9968 const bool pad_end = true)
9969 {
9970 return stft(build_real_input(signal), build_real_input(window), hop_size, pad_end);
9971 }
9972
9973 template <typename Container>
9976 stft(const Container & signal,
9977 const size_t frame_size,
9978 const size_t hop_size,
9979 const bool pad_end = true)
9980 {
9981 return stft(build_real_input(signal), frame_size, hop_size, pad_end);
9982 }
9983
9986 stft(const Array<Real> & signal,
9987 const Array<Real> & window,
9988 const STFTOptions & options)
9989 {
9990 return stft_impl(signal, window, options);
9991 }
9992
9996 const Array<Real> & signal,
9997 const Array<Real> & window,
9998 const STFTOptions & options,
9999 const size_t chunk_size = 0)
10000 {
10001 return stft_impl(signal, window, options, &pool, chunk_size);
10002 }
10003
10006 stft(const Array<Real> & signal,
10007 const size_t frame_size,
10008 const STFTOptions & options)
10009 {
10010 return stft(signal, hann_window(frame_size), options);
10011 }
10012
10016 const Array<Real> & signal,
10017 const size_t frame_size,
10018 const STFTOptions & options,
10019 const size_t chunk_size = 0)
10020 {
10021 return pstft(pool, signal, hann_window(frame_size), options, chunk_size);
10022 }
10023
10024 template <typename SignalContainer, typename WindowContainer>
10027 stft(const SignalContainer & signal,
10028 const WindowContainer & window,
10029 const STFTOptions & options)
10030 {
10031 return stft(build_real_input(signal), build_real_input(window), options);
10032 }
10033
10034 template <typename SignalContainer, typename WindowContainer>
10038 const SignalContainer & signal,
10039 const WindowContainer & window,
10040 const STFTOptions & options,
10041 const size_t chunk_size = 0)
10042 {
10043 return pstft(pool,
10044 build_real_input(signal),
10045 build_real_input(window),
10046 options,
10047 chunk_size);
10048 }
10049
10050 template <typename Container>
10053 stft(const Container & signal,
10054 const size_t frame_size,
10055 const STFTOptions & options)
10056 {
10057 return stft(build_real_input(signal), frame_size, options);
10058 }
10059
10060 template <typename Container>
10064 const Container & signal,
10065 const size_t frame_size,
10066 const STFTOptions & options,
10067 const size_t chunk_size = 0)
10068 {
10069 return pstft(pool, build_real_input(signal), frame_size, options, chunk_size);
10070 }
10071
10079 [[nodiscard]] static Array<Real>
10083 const size_t hop_size,
10084 const size_t signal_length = 0)
10085 {
10087 options.hop_size = hop_size;
10088 options.signal_length = signal_length;
10089 return istft_impl(spectrogram,
10092 options,
10093 nullptr,
10094 0);
10095 }
10096
10100 [[nodiscard]] static Array<Real>
10102 const Array<Real> & window,
10103 const size_t hop_size,
10104 const size_t signal_length = 0)
10105 {
10106 return istft(spectrogram, window, window, hop_size, signal_length);
10107 }
10108
10110 [[nodiscard]] static Array<Real>
10112 const size_t frame_size,
10113 const size_t hop_size,
10114 const size_t signal_length = 0)
10115 {
10116 const Array<Real> window = hann_window(frame_size);
10117 return istft(spectrogram, window, window, hop_size, signal_length);
10118 }
10119
10121 [[nodiscard]] static Array<Real>
10126 const size_t hop_size,
10127 const size_t signal_length = 0,
10128 const size_t chunk_size = 0)
10129 {
10131 options.hop_size = hop_size;
10132 options.signal_length = signal_length;
10133 return istft_impl(spectrogram,
10136 options,
10137 &pool,
10138 chunk_size);
10139 }
10140
10142 [[nodiscard]] static Array<Real>
10145 const Array<Real> & window,
10146 const size_t hop_size,
10147 const size_t signal_length = 0,
10148 const size_t chunk_size = 0)
10149 {
10150 return pistft(pool,
10152 window,
10153 window,
10154 hop_size,
10155 signal_length,
10156 chunk_size);
10157 }
10158
10160 [[nodiscard]] static Array<Real>
10163 const size_t frame_size,
10164 const size_t hop_size,
10165 const size_t signal_length = 0,
10166 const size_t chunk_size = 0)
10167 {
10168 const Array<Real> window = hann_window(frame_size);
10169 return pistft(pool,
10171 window,
10172 window,
10173 hop_size,
10174 signal_length,
10175 chunk_size);
10176 }
10177
10179 [[nodiscard]] static Array<Real>
10183 const ISTFTOptions & options)
10184 {
10185 return istft_impl(spectrogram,
10188 options,
10189 nullptr,
10190 0);
10191 }
10192
10194 [[nodiscard]] static Array<Real>
10196 const Array<Real> & window,
10197 const ISTFTOptions & options)
10198 {
10199 return istft(spectrogram, window, window, options);
10200 }
10201
10203 [[nodiscard]] static Array<Real>
10205 const size_t frame_size,
10206 const ISTFTOptions & options)
10207 {
10208 const Array<Real> window = hann_window(frame_size);
10209 return istft(spectrogram, window, window, options);
10210 }
10211
10213 [[nodiscard]] static Array<Real>
10218 const ISTFTOptions & options,
10219 const size_t chunk_size = 0)
10220 {
10221 return istft_impl(spectrogram,
10224 options,
10225 &pool,
10226 chunk_size);
10227 }
10228
10230 [[nodiscard]] static Array<Real>
10233 const Array<Real> & window,
10234 const ISTFTOptions & options,
10235 const size_t chunk_size = 0)
10236 {
10237 return pistft(pool, spectrogram, window, window, options, chunk_size);
10238 }
10239
10241 [[nodiscard]] static Array<Real>
10244 const size_t frame_size,
10245 const ISTFTOptions & options,
10246 const size_t chunk_size = 0)
10247 {
10248 const Array<Real> window = hann_window(frame_size);
10249 return pistft(pool, spectrogram, window, window, options, chunk_size);
10250 }
10251
10254 {
10257 size_t fft_size_ = 0;
10258 size_t center_pad_ = 0;
10261 bool configured_ = false;
10262 bool has_seen_input_ = false;
10263 bool finalized_ = false;
10264
10265 void
10266 require_configured(const char *ctx) const
10267 {
10268 ah_runtime_error_if(not configured_) << ctx << ": processor is not configured";
10269 }
10270
10271 void
10273 {
10274 pending_.empty();
10276 for (size_t i = 0; i < center_pad_; ++i)
10277 pending_.append(Real(0));
10278 }
10279
10280 void
10282 {
10284 > std::numeric_limits<size_t>::max() - block.size())
10285 << "FFT::STFTProcessor::process_block: pending buffer overflow";
10286 FFT::append_all(pending_, block);
10287 }
10288
10291 const size_t chunk_size,
10292 const bool allow_partial_frames)
10293 {
10294 Array<Array<Real>> frames;
10295 while (not pending_.is_empty())
10296 {
10298 break;
10299
10301 window_.size()));
10303 }
10304
10305 return FFT::transform_stft_frames(frames,
10306 window_,
10307 fft_size_,
10308 plan_,
10309 pool,
10310 chunk_size);
10311 }
10312
10313 public:
10315 STFTProcessor() = default;
10316
10323 const STFTOptions & options)
10324 : window_(window),
10327 options,
10328 "FFT::STFTProcessor")),
10329 center_pad_(options.centered ? window.size() / 2 : 0),
10332 {
10334 }
10335
10341 explicit STFTProcessor(const size_t frame_size,
10342 const STFTOptions & options)
10344 {}
10345
10352 template <typename WindowContainer>
10358
10360 {
10361 return window_.size();
10362 }
10363
10365 {
10366 return configured_;
10367 }
10368
10370 {
10371 return options_.hop_size;
10372 }
10373
10375 {
10376 return fft_size_;
10377 }
10378
10380 {
10381 return options_.centered;
10382 }
10383
10385 {
10386 return options_.pad_end;
10387 }
10388
10390 {
10391 return finalized_;
10392 }
10393
10395 {
10396 return window_;
10397 }
10398
10399 void reset()
10400 {
10401 require_configured("FFT::STFTProcessor::reset");
10402 has_seen_input_ = false;
10403 finalized_ = false;
10405 }
10406
10409 {
10410 require_configured("FFT::STFTProcessor::process_block");
10412 << "FFT::STFTProcessor::process_block: processor already flushed";
10413
10414 if (block.is_empty())
10415 return {};
10416
10417 has_seen_input_ = true;
10418 append_samples(block);
10419 return emit_ready_frames(nullptr, 0, false);
10420 }
10421
10424 const Array<Real> & block,
10425 const size_t chunk_size = 0)
10426 {
10427 require_configured("FFT::STFTProcessor::pprocess_block");
10429 << "FFT::STFTProcessor::process_block: processor already flushed";
10430
10431 if (block.is_empty())
10432 return {};
10433
10434 has_seen_input_ = true;
10435 append_samples(block);
10436 return emit_ready_frames(&pool, chunk_size, false);
10437 }
10438
10441 {
10442 require_configured("FFT::STFTProcessor::flush");
10443 if (finalized_)
10444 return {};
10445
10446 finalized_ = true;
10447 if (not has_seen_input_)
10448 {
10449 pending_.empty();
10450 return {};
10451 }
10452
10453 if (center_pad_ != 0)
10454 {
10456 for (size_t i = 0; i < center_pad_; ++i)
10457 right_pad(i) = Real(0);
10459 }
10460
10461 Array<Array<Complex>> tail =
10462 emit_ready_frames(nullptr, 0, options_.pad_end);
10463 pending_.empty();
10464 return tail;
10465 }
10466
10468 pflush(ThreadPool & pool, const size_t chunk_size = 0)
10469 {
10470 require_configured("FFT::STFTProcessor::pflush");
10471 if (finalized_)
10472 return {};
10473
10474 finalized_ = true;
10475 if (not has_seen_input_)
10476 {
10477 pending_.empty();
10478 return {};
10479 }
10480
10481 if (center_pad_ != 0)
10482 {
10484 for (size_t i = 0; i < center_pad_; ++i)
10485 right_pad(i) = Real(0);
10487 }
10488
10489 Array<Array<Complex>> tail =
10490 emit_ready_frames(&pool, chunk_size, options_.pad_end);
10491 pending_.empty();
10492 return tail;
10493 }
10494
10495 template <typename Container>
10499 {
10500 return process_block(FFT::build_real_input(block));
10501 }
10502
10503 template <typename Container>
10507 const Container & block,
10508 const size_t chunk_size = 0)
10509 {
10510 return pprocess_block(pool, FFT::build_real_input(block), chunk_size);
10511 }
10512 };
10513
10516 {
10520 size_t fft_size_ = 0;
10521 size_t center_pad_ = 0;
10529 bool configured_ = false;
10530 bool has_seen_frame_ = false;
10531 bool finalized_ = false;
10532
10533 void
10534 require_configured(const char *ctx) const
10535 {
10537 << ctx << ": processor is not configured";
10538 }
10539
10540 void
10542 {
10549 has_seen_frame_ = false;
10550 finalized_ = false;
10551 }
10552
10553 void
10555 {
10556 if (pending_output_.size() >= size)
10557 return;
10558
10561 while (pending_output_.size() < size)
10562 {
10565 }
10566 }
10567
10568 void
10570 {
10573 > std::numeric_limits<size_t>::max()
10575 << "FFT::ISTFTProcessor::process_frame: frame offset overflow";
10576
10579 << "FFT::ISTFTProcessor::process_frame: invalid overlap-add state";
10580
10581 const size_t offset = frame_offset - emitted_raw_samples_;
10583
10584 for (size_t i = 0; i < analysis_window_.size(); ++i)
10585 {
10586 pending_output_(offset + i) += frame[i] * synthesis_window_[i];
10588 }
10589
10591 }
10592
10595 {
10597 << "FFT::ISTFTProcessor::normalize_prefix: count " << count
10598 << " exceeds pending size " << pending_output_.size();
10599
10600 if (count == 0)
10601 return {};
10602
10603 const Real tol = (max_abs_value(pending_norm_) + Real(1))
10604 * Real(256) * std::numeric_limits<Real>::epsilon();
10606 for (size_t i = 0; i < count; ++i)
10607 {
10608 if (std::abs(pending_norm_[i]) <= tol)
10609 {
10610 ah_domain_error_if(std::abs(pending_output_[i]) > tol)
10611 << "FFT::ISTFTProcessor: overlap-add normalization vanished at sample "
10612 << (emitted_raw_samples_ + i);
10613 normalized(i) = Real(0);
10614 }
10615 else
10617 }
10618
10622 return normalized;
10623 }
10624
10627 const bool final_flush)
10628 {
10629 if (normalized.is_empty())
10630 return {};
10631
10632 const size_t start = std::min(left_trim_remaining_, normalized.size());
10633 left_trim_remaining_ -= start;
10634 size_t end = normalized.size();
10635
10637 end = end > center_pad_ ? end - center_pad_ : start;
10638
10639 if (start >= end)
10640 return {};
10641
10642 size_t available = end - start;
10643 if (options_.signal_length != 0)
10644 {
10646 return {};
10647
10648 available = std::min(available,
10650 }
10651
10653 emitted.reserve(available);
10654 for (size_t i = 0; i < available; ++i)
10655 emitted.append(normalized[start + i]);
10657 return emitted;
10658 }
10659
10662 {
10663 size_t ready_raw = 0;
10664 if (final_flush)
10666 else
10667 {
10670 << "FFT::ISTFTProcessor: invalid finalized-sample accounting";
10672 const size_t right_hold =
10675 }
10676
10678 }
10679
10682 ThreadPool *pool,
10683 const size_t chunk_size)
10684 {
10685 require_configured("FFT::ISTFTProcessor::process_frame");
10687 << "FFT::ISTFTProcessor::process_frame: processor already flushed";
10689 << "FFT::ISTFTProcessor::process_frame: frame size " << spectrum.size()
10690 << " does not match configured FFT size " << fft_size_;
10691
10692 has_seen_frame_ = true;
10693 const Array<Real> frame = pool != nullptr ?
10694 plan_.pinverse_transform_real(*pool, spectrum, chunk_size) :
10696 accumulate_frame(frame);
10697 return drain_ready_samples(false);
10698 }
10699
10700 public:
10702 ISTFTProcessor() = default;
10703
10711 explicit ISTFTProcessor(const size_t fft_size,
10714 const ISTFTOptions & options)
10720 plan_(fft_size),
10722 {
10725 fft_size_,
10726 options_,
10727 "FFT::ISTFTProcessor");
10729 }
10730
10737 explicit ISTFTProcessor(const size_t fft_size,
10738 const Array<Real> & window,
10739 const ISTFTOptions & options)
10740 : ISTFTProcessor(fft_size, window, window, options)
10741 {}
10742
10749 explicit ISTFTProcessor(const size_t fft_size,
10750 const size_t frame_size,
10751 const ISTFTOptions & options)
10755 options)
10756 {}
10757
10767 template <typename AnalysisContainer, typename SynthesisContainer>
10779
10781 {
10782 return analysis_window_.size();
10783 }
10784
10786 {
10787 return configured_;
10788 }
10789
10791 {
10792 return fft_size_;
10793 }
10794
10796 {
10797 return options_.hop_size;
10798 }
10799
10801 {
10802 return options_.centered;
10803 }
10804
10806 {
10807 return finalized_;
10808 }
10809
10810 void reset()
10811 {
10812 require_configured("FFT::ISTFTProcessor::reset");
10814 }
10815
10818 {
10819 return process_frame_impl(spectrum, nullptr, 0);
10820 }
10821
10824 const Array<Complex> & spectrum,
10825 const size_t chunk_size = 0)
10826 {
10827 return process_frame_impl(spectrum, &pool, chunk_size);
10828 }
10829
10832 {
10834 for (size_t i = 0; i < spectrogram_block.size(); ++i)
10836 return emitted;
10837 }
10838
10842 const size_t chunk_size = 0)
10843 {
10845 for (size_t i = 0; i < spectrogram_block.size(); ++i)
10847 pprocess_frame(pool, spectrogram_block[i], chunk_size));
10848 return emitted;
10849 }
10850
10853 {
10854 require_configured("FFT::ISTFTProcessor::flush");
10855 if (finalized_)
10856 return {};
10857
10858 finalized_ = true;
10859 if (not has_seen_frame_)
10860 {
10863 return {};
10864 }
10865
10866 Array<Real> tail = drain_ready_samples(true);
10869 return tail;
10870 }
10871
10873 pflush(ThreadPool &, const size_t = 0)
10874 {
10875 require_configured("FFT::ISTFTProcessor::pflush");
10876 return flush();
10877 }
10878
10879 template <typename Container>
10886
10887 template <typename Container>
10891 const Container & spectrum,
10892 const size_t chunk_size = 0)
10893 {
10894 return pprocess_frame(pool, FFT::build_complex_input(spectrum), chunk_size);
10895 }
10896 };
10897
10900 {
10902
10903 void
10904 validate_batch_size(const size_t size, const char *ctx) const
10905 {
10907 << ctx << ": batch size " << size
10908 << " does not match configured channel count " << processors_.size();
10909 }
10910
10911 public:
10912 explicit BatchedSTFTProcessor(const size_t num_channels,
10913 const Array<Real> & window,
10914 const STFTOptions & options)
10915 {
10917 << "FFT::BatchedSTFTProcessor: at least one channel is required";
10918 processors_.reserve(num_channels);
10919 for (size_t i = 0; i < num_channels; ++i)
10920 processors_.append(STFTProcessor(window, options));
10921 }
10922
10923 explicit BatchedSTFTProcessor(const size_t num_channels,
10924 const size_t frame_size,
10925 const STFTOptions & options)
10927 {}
10928
10929 template <typename WindowContainer>
10931 explicit BatchedSTFTProcessor(const size_t num_channels,
10932 const WindowContainer & window,
10933 const STFTOptions & options)
10935 FFT::build_real_input(window),
10936 options)
10937 {}
10938
10940 {
10941 return processors_.size();
10942 }
10943
10945 channel(const size_t index)
10946 {
10947 ah_out_of_range_error_if(index >= processors_.size())
10948 << "FFT::BatchedSTFTProcessor::channel: index " << index
10949 << " out of range for " << processors_.size() << " channels";
10950 return processors_[index];
10951 }
10952
10953 [[nodiscard]] const STFTProcessor &
10954 channel(const size_t index) const
10955 {
10956 ah_out_of_range_error_if(index >= processors_.size())
10957 << "FFT::BatchedSTFTProcessor::channel: index " << index
10958 << " out of range for " << processors_.size() << " channels";
10959 return processors_[index];
10960 }
10961
10962 void reset()
10963 {
10964 for (size_t i = 0; i < processors_.size(); ++i)
10965 processors_(i).reset();
10966 }
10967
10970 {
10971 validate_batch_size(block.size(), "FFT::BatchedSTFTProcessor::process_block");
10974 for (size_t i = 0; i < processors_.size(); ++i)
10975 output(i) = processors_[i].process_block(block[i]);
10976 return output;
10977 }
10978
10981 const Array<Array<Real>> & block,
10982 const size_t chunk_size = 0)
10983 {
10984 validate_batch_size(block.size(),
10985 "FFT::BatchedSTFTProcessor::pprocess_block");
10988 parallel_for_index(pool,
10989 0,
10990 processors_.size(),
10991 [this, &block, &pool, chunk_size, &output](const size_t i)
10992 {
10993 output(i) =
10994 processors_[i].pprocess_block(pool,
10995 block[i],
10996 chunk_size);
10997 },
10998 chunk_size);
10999 return output;
11000 }
11001
11004 {
11007 for (size_t i = 0; i < processors_.size(); ++i)
11008 output(i) = processors_[i].flush();
11009 return output;
11010 }
11011
11013 pflush(ThreadPool & pool, const size_t chunk_size = 0)
11014 {
11017 parallel_for_index(pool,
11018 0,
11019 processors_.size(),
11020 [this, &pool, chunk_size, &output](const size_t i)
11021 {
11022 output(i) = processors_[i].pflush(pool, chunk_size);
11023 },
11024 chunk_size);
11025 return output;
11026 }
11027 };
11028
11031 {
11033
11034 [[nodiscard]] static ISTFTOptions
11037 const size_t index)
11038 {
11040 if (not signal_lengths.is_empty())
11041 channel.signal_length = signal_lengths[index];
11042 return channel;
11043 }
11044
11045 void
11046 validate_batch_size(const size_t size, const char *ctx) const
11047 {
11049 << ctx << ": batch size " << size
11050 << " does not match configured channel count " << processors_.size();
11051 }
11052
11053 public:
11055 const size_t fft_size,
11058 const ISTFTOptions & options)
11060 fft_size,
11063 options,
11064 Array<size_t>())
11065 {}
11066
11068 const size_t fft_size,
11071 const ISTFTOptions & options,
11073 {
11075 << "FFT::BatchedISTFTProcessor: at least one channel is required";
11078 << "FFT::BatchedISTFTProcessor: signal length count "
11079 << signal_lengths.size()
11080 << " does not match channel count " << num_channels;
11081 processors_.reserve(num_channels);
11082 for (size_t i = 0; i < num_channels; ++i)
11083 processors_.append(ISTFTProcessor(fft_size,
11088 i)));
11089 }
11090
11092 const size_t fft_size,
11093 const Array<Real> & window,
11094 const ISTFTOptions & options)
11096 fft_size,
11097 window,
11098 window,
11099 options,
11100 Array<size_t>())
11101 {}
11102
11104 const size_t fft_size,
11105 const Array<Real> & window,
11106 const ISTFTOptions & options,
11109 fft_size,
11110 window,
11111 window,
11112 options,
11114 {}
11115
11117 const size_t fft_size,
11118 const size_t frame_size,
11119 const ISTFTOptions & options)
11121 fft_size,
11122 FFT::hann_window(frame_size),
11123 FFT::hann_window(frame_size),
11124 options,
11125 Array<size_t>())
11126 {}
11127
11129 const size_t fft_size,
11130 const size_t frame_size,
11131 const ISTFTOptions & options,
11134 fft_size,
11135 FFT::hann_window(frame_size),
11136 FFT::hann_window(frame_size),
11137 options,
11139 {}
11140
11141 template <typename AnalysisContainer, typename SynthesisContainer>
11145 const size_t fft_size,
11148 const ISTFTOptions & options)
11150 fft_size,
11153 options,
11154 Array<size_t>())
11155 {}
11156
11157 template <typename AnalysisContainer, typename SynthesisContainer>
11161 const size_t fft_size,
11164 const ISTFTOptions & options,
11167 fft_size,
11170 options,
11172 {}
11173
11175 {
11176 return processors_.size();
11177 }
11178
11180 channel(const size_t index)
11181 {
11182 ah_out_of_range_error_if(index >= processors_.size())
11183 << "FFT::BatchedISTFTProcessor::channel: index " << index
11184 << " out of range for " << processors_.size() << " channels";
11185 return processors_[index];
11186 }
11187
11188 [[nodiscard]] const ISTFTProcessor &
11189 channel(const size_t index) const
11190 {
11191 ah_out_of_range_error_if(index >= processors_.size())
11192 << "FFT::BatchedISTFTProcessor::channel: index " << index
11193 << " out of range for " << processors_.size() << " channels";
11194 return processors_[index];
11195 }
11196
11197 void reset()
11198 {
11199 for (size_t i = 0; i < processors_.size(); ++i)
11200 processors_(i).reset();
11201 }
11202
11205 {
11206 validate_batch_size(block.size(),
11207 "FFT::BatchedISTFTProcessor::process_block");
11209 for (size_t i = 0; i < processors_.size(); ++i)
11210 output(i) = processors_[i].process_block(block[i]);
11211 return output;
11212 }
11213
11216 const Array<Array<Array<Complex>>> & block,
11217 const size_t chunk_size = 0)
11218 {
11219 validate_batch_size(block.size(),
11220 "FFT::BatchedISTFTProcessor::pprocess_block");
11222 parallel_for_index(pool,
11223 0,
11224 processors_.size(),
11225 [this, &block, &pool, chunk_size, &output](const size_t i)
11226 {
11227 output(i) =
11228 processors_[i].pprocess_block(pool,
11229 block[i],
11230 chunk_size);
11231 },
11232 chunk_size);
11233 return output;
11234 }
11235
11238 {
11240 for (size_t i = 0; i < processors_.size(); ++i)
11241 output(i) = processors_[i].flush();
11242 return output;
11243 }
11244
11246 pflush(ThreadPool & pool, const size_t chunk_size = 0)
11247 {
11249 parallel_for_index(pool,
11250 0,
11251 processors_.size(),
11252 [this, &pool, chunk_size, &output](const size_t i)
11253 {
11254 output(i) = processors_[i].pflush(pool, chunk_size);
11255 },
11256 chunk_size);
11257 return output;
11258 }
11259 };
11260
11264 const Array<Real> & window,
11265 const STFTOptions & options)
11266 {
11267 if (signals.is_empty())
11268 return {};
11269
11271 for (size_t i = 0; i < signals.size(); ++i)
11272 output(i) = stft_impl(signals[i], window, options);
11273 return output;
11274 }
11275
11279 const Array<Array<Real>> & signals,
11280 const Array<Real> & window,
11281 const STFTOptions & options,
11282 const size_t chunk_size = 0)
11283 {
11284 if (signals.is_empty())
11285 return {};
11286
11288 const bool parallelize = pool.num_threads() > 1 and signals.size() > 1;
11289 auto transform_one = [&signals, &window, &options, &output](const size_t i)
11290 {
11291 output(i) = stft_impl(signals[i], window, options);
11292 };
11293
11294 if (parallelize)
11295 parallel_for_index(pool, 0, signals.size(), transform_one, chunk_size);
11296 else
11297 for (size_t i = 0; i < signals.size(); ++i)
11298 output(i) = stft_impl(signals[i], window, options, &pool, chunk_size);
11299
11300 return output;
11301 }
11302
11306 const size_t frame_size,
11307 const STFTOptions & options)
11308 {
11309 return batched_stft(signals, hann_window(frame_size), options);
11310 }
11311
11315 const Array<Array<Real>> & signals,
11316 const size_t frame_size,
11317 const STFTOptions & options,
11318 const size_t chunk_size = 0)
11319 {
11320 return pbatched_stft(pool,
11321 signals,
11322 hann_window(frame_size),
11323 options,
11324 chunk_size);
11325 }
11326
11328 [[nodiscard]] static Array<Array<Real>>
11332 const ISTFTOptions & options,
11333 const Array<size_t> & signal_lengths = {})
11334 {
11335 if (spectrograms.is_empty())
11336 return {};
11337
11339 and signal_lengths.size() != spectrograms.size())
11340 << "FFT::batched_istft: signal_lengths size "
11341 << signal_lengths.size()
11342 << " does not match batch size " << spectrograms.size();
11343
11345 for (size_t i = 0; i < spectrograms.size(); ++i)
11346 {
11347 ISTFTOptions local_options = options;
11348 if (not signal_lengths.is_empty())
11349 local_options.signal_length = signal_lengths[i];
11354 nullptr,
11355 0);
11356 }
11357 return output;
11358 }
11359
11361 [[nodiscard]] static Array<Array<Real>>
11366 const ISTFTOptions & options,
11367 const Array<size_t> & signal_lengths = {},
11368 const size_t chunk_size = 0)
11369 {
11370 if (spectrograms.is_empty())
11371 return {};
11372
11374 and signal_lengths.size() != spectrograms.size())
11375 << "FFT::pbatched_istft: signal_lengths size "
11376 << signal_lengths.size()
11377 << " does not match batch size " << spectrograms.size();
11378
11380 const bool parallelize = pool.num_threads() > 1 and spectrograms.size() > 1;
11381 auto reconstruct_one =
11383 &signal_lengths, &output](const size_t i)
11384 {
11385 ISTFTOptions local_options = options;
11386 if (not signal_lengths.is_empty())
11387 local_options.signal_length = signal_lengths[i];
11392 nullptr,
11393 0);
11394 };
11395
11396 if (parallelize)
11397 parallel_for_index(pool, 0, spectrograms.size(), reconstruct_one, chunk_size);
11398 else
11399 for (size_t i = 0; i < spectrograms.size(); ++i)
11400 {
11401 ISTFTOptions local_options = options;
11402 if (not signal_lengths.is_empty())
11403 local_options.signal_length = signal_lengths[i];
11408 &pool,
11409 chunk_size);
11410 }
11411
11412 return output;
11413 }
11414
11416 [[nodiscard]] static Array<Array<Real>>
11418 const Array<Real> & window,
11419 const ISTFTOptions & options,
11420 const Array<size_t> & signal_lengths = {})
11421 {
11423 window,
11424 window,
11425 options,
11427 }
11428
11430 [[nodiscard]] static Array<Array<Real>>
11433 const Array<Real> & window,
11434 const ISTFTOptions & options,
11435 const Array<size_t> & signal_lengths = {},
11436 const size_t chunk_size = 0)
11437 {
11438 return pbatched_istft(pool,
11440 window,
11441 window,
11442 options,
11444 chunk_size);
11445 }
11446
11448 [[nodiscard]] static Array<Array<Real>>
11450 const size_t frame_size,
11451 const ISTFTOptions & options,
11452 const Array<size_t> & signal_lengths = {})
11453 {
11454 const Array<Real> window = hann_window(frame_size);
11455 return batched_istft(spectrograms, window, window, options, signal_lengths);
11456 }
11457
11459 [[nodiscard]] static Array<Array<Real>>
11462 const size_t frame_size,
11463 const ISTFTOptions & options,
11464 const Array<size_t> & signal_lengths = {},
11465 const size_t chunk_size = 0)
11466 {
11467 const Array<Real> window = hann_window(frame_size);
11468 return pbatched_istft(pool,
11470 window,
11471 window,
11472 options,
11474 chunk_size);
11475 }
11476
11479 {
11482 bool configured_ = false;
11483
11484 void
11485 require_configured(const char *ctx) const
11486 {
11488 << ctx << ": filter is not configured";
11489 }
11490
11491 void
11493 {
11494 const size_t order = coeffs_.denominator.size() == 0 ? 0 : coeffs_.denominator.size() - 1;
11496 for (size_t i = 0; i < order; ++i)
11497 state_(i) = Real(0);
11498 }
11499
11500 public:
11502 LFilter() = default;
11503
11511 explicit LFilter(const Array<Real> & numerator,
11512 const Array<Real> & denominator)
11514 denominator,
11515 "FFT::LFilter")),
11517 {
11519 }
11520
11523 explicit LFilter(const IIRCoefficients & coeffs)
11524 : LFilter(coeffs.numerator, coeffs.denominator)
11525 {}
11526
11530 : LFilter(section.numerator(), section.denominator())
11531 {}
11532
11540 template <typename NumContainer, typename DenContainer>
11542 explicit LFilter(const NumContainer & numerator,
11543 const DenContainer & denominator)
11544 : LFilter(build_real_input(numerator), build_real_input(denominator))
11545 {}
11546
11549 {
11550 return coeffs_.denominator.is_empty() ? 0 : coeffs_.denominator.size() - 1;
11551 }
11552
11555 {
11556 return configured_;
11557 }
11558
11561 {
11562 return coeffs_;
11563 }
11564
11567 {
11568 return state_;
11569 }
11570
11572 void reset()
11573 {
11574 require_configured("FFT::LFilter::reset");
11575 for (size_t i = 0; i < state_.size(); ++i)
11576 state_(i) = Real(0);
11577 }
11578
11581 {
11582 require_configured("FFT::LFilter::set_state");
11584 << "FFT::LFilter::set_state: state size " << new_state.size()
11585 << " does not match filter order " << state_.size();
11586 state_ = new_state;
11587 }
11588
11591 filter(const Array<Real> & signal)
11592 {
11593 require_configured("FFT::LFilter::filter");
11594 return FFT::iir_filter_impl(signal,
11597 state_,
11598 "FFT::LFilter::filter",
11599 &state_);
11600 }
11601
11603 template <typename Container>
11606 filter(const Container & signal)
11607 {
11608 return filter(build_real_input(signal));
11609 }
11610 };
11611
11614 {
11618 bool configured_ = false;
11619
11620 void
11621 require_configured(const char *ctx) const
11622 {
11624 << ctx << ": filter is not configured";
11625 }
11626
11627 void
11629 {
11630 states_ = {};
11631 states_.reserve(coeffs_.size());
11632 for (size_t i = 0; i < coeffs_.size(); ++i)
11633 {
11634 Array<Real> state = Array<Real>::create(coeffs_[i].denominator.size() - 1);
11635 for (size_t j = 0; j < state.size(); ++j)
11636 state(j) = Real(0);
11637 states_.append(state);
11638 }
11639 }
11640
11641 public:
11643 SOSFilter() = default;
11644
11650 {
11652 << "FFT::SOSFilter: at least one biquad section is required";
11653
11654 coeffs_.reserve(sections_.size());
11655 for (size_t i = 0; i < sections_.size(); ++i)
11657 sections_[i].denominator(),
11658 "FFT::SOSFilter"));
11660 }
11661
11665 template <typename SectionsContainer>
11670
11673 {
11674 return sections_.size();
11675 }
11676
11679 {
11680 return configured_;
11681 }
11682
11684 [[nodiscard]] const Array<Real> &
11685 state(const size_t index) const
11686 {
11687 ah_out_of_range_error_if(index >= states_.size())
11688 << "FFT::SOSFilter::state: index " << index
11689 << " out of range for " << states_.size() << " sections";
11690 return states_[index];
11691 }
11692
11694 void reset()
11695 {
11696 require_configured("FFT::SOSFilter::reset");
11697 for (size_t i = 0; i < states_.size(); ++i)
11698 for (size_t j = 0; j < states_[i].size(); ++j)
11699 states_(i)(j) = Real(0);
11700 }
11701
11704 filter(const Array<Real> & signal)
11705 {
11706 require_configured("FFT::SOSFilter::filter");
11707 Array<Real> stage = signal;
11708 for (size_t i = 0; i < coeffs_.size(); ++i)
11709 {
11712 coeffs_[i].numerator,
11713 coeffs_[i].denominator,
11714 states_[i],
11715 "FFT::SOSFilter::filter",
11716 &final_state);
11717 states_(i) = final_state;
11718 }
11719 return stage;
11720 }
11721
11723 template <typename Container>
11726 filter(const Container & signal)
11727 {
11728 return filter(build_real_input(signal));
11729 }
11730 };
11731
11734 {
11736
11738 void
11739 validate_channel_count(const size_t count, const char *ctx) const
11740 {
11742 << ctx << ": batch size " << count
11743 << " does not match configured channel count " << filters_.size();
11744 }
11745
11746 public:
11747 explicit LFilterBank(const size_t num_channels,
11748 const Array<Real> & numerator,
11749 const Array<Real> & denominator)
11750 {
11752 << "FFT::LFilterBank: at least one channel is required";
11753 filters_.reserve(num_channels);
11754 for (size_t i = 0; i < num_channels; ++i)
11755 filters_.append(LFilter(numerator, denominator));
11756 }
11757
11758 explicit LFilterBank(const size_t num_channels,
11759 const IIRCoefficients & coeffs)
11760 : LFilterBank(num_channels, coeffs.numerator, coeffs.denominator)
11761 {}
11762
11763 explicit LFilterBank(const size_t num_channels,
11764 const BiquadSection & section)
11765 : LFilterBank(num_channels, section.numerator(), section.denominator())
11766 {}
11767
11768 template <typename NumContainer, typename DenContainer>
11770 explicit LFilterBank(const size_t num_channels,
11771 const NumContainer & numerator,
11772 const DenContainer & denominator)
11774 FFT::build_real_input(numerator),
11775 FFT::build_real_input(denominator))
11776 {}
11777
11779 {
11780 return filters_.size();
11781 }
11782
11783 [[nodiscard]] LFilter &
11784 channel(const size_t index)
11785 {
11786 ah_out_of_range_error_if(index >= filters_.size())
11787 << "FFT::LFilterBank::channel: index " << index
11788 << " out of range for " << filters_.size() << " channels";
11789 return filters_[index];
11790 }
11791
11792 [[nodiscard]] const LFilter &
11793 channel(const size_t index) const
11794 {
11795 ah_out_of_range_error_if(index >= filters_.size())
11796 << "FFT::LFilterBank::channel: index " << index
11797 << " out of range for " << filters_.size() << " channels";
11798 return filters_[index];
11799 }
11800
11801 void reset()
11802 {
11803 for (size_t i = 0; i < filters_.size(); ++i)
11804 filters_(i).reset();
11805 }
11806
11808 filter_channel(const size_t index,
11809 const Array<Real> & signal)
11810 {
11811 return channel(index).filter(signal);
11812 }
11813
11814 template <typename Container>
11817 filter_channel(const size_t index,
11818 const Container & signal)
11819 {
11820 return channel(index).filter(signal);
11821 }
11822
11825 {
11826 validate_channel_count(signals.size(), "FFT::LFilterBank::filter");
11828 for (size_t i = 0; i < filters_.size(); ++i)
11829 output(i) = filters_[i].filter(signals[i]);
11830 return output;
11831 }
11832
11835 const Array<Array<Real>> & signals,
11836 const size_t chunk_size = 0)
11837 {
11838 validate_channel_count(signals.size(), "FFT::LFilterBank::pfilter");
11840 parallel_for_index(pool,
11841 0,
11842 filters_.size(),
11843 [this, &signals, &output](const size_t i)
11844 {
11845 output(i) = filters_[i].filter(signals[i]);
11846 },
11847 chunk_size);
11848 return output;
11849 }
11850 };
11851
11854 {
11856
11858 void
11859 validate_channel_count(const size_t count, const char *ctx) const
11860 {
11862 << ctx << ": batch size " << count
11863 << " does not match configured channel count " << filters_.size();
11864 }
11865
11866 public:
11867 explicit SOSFilterBank(const size_t num_channels,
11869 {
11871 << "FFT::SOSFilterBank: at least one channel is required";
11872 filters_.reserve(num_channels);
11873 for (size_t i = 0; i < num_channels; ++i)
11874 filters_.append(SOSFilter(sections));
11875 }
11876
11877 template <typename SectionsContainer>
11883
11885 {
11886 return filters_.size();
11887 }
11888
11889 [[nodiscard]] SOSFilter &
11890 channel(const size_t index)
11891 {
11892 ah_out_of_range_error_if(index >= filters_.size())
11893 << "FFT::SOSFilterBank::channel: index " << index
11894 << " out of range for " << filters_.size() << " channels";
11895 return filters_[index];
11896 }
11897
11898 [[nodiscard]] const SOSFilter &
11899 channel(const size_t index) const
11900 {
11901 ah_out_of_range_error_if(index >= filters_.size())
11902 << "FFT::SOSFilterBank::channel: index " << index
11903 << " out of range for " << filters_.size() << " channels";
11904 return filters_[index];
11905 }
11906
11907 void reset()
11908 {
11909 for (size_t i = 0; i < filters_.size(); ++i)
11910 filters_(i).reset();
11911 }
11912
11914 filter_channel(const size_t index,
11915 const Array<Real> & signal)
11916 {
11917 return channel(index).filter(signal);
11918 }
11919
11920 template <typename Container>
11923 filter_channel(const size_t index,
11924 const Container & signal)
11925 {
11926 return channel(index).filter(signal);
11927 }
11928
11931 {
11932 validate_channel_count(signals.size(), "FFT::SOSFilterBank::filter");
11934 for (size_t i = 0; i < filters_.size(); ++i)
11935 output(i) = filters_[i].filter(signals[i]);
11936 return output;
11937 }
11938
11941 const Array<Array<Real>> & signals,
11942 const size_t chunk_size = 0)
11943 {
11944 validate_channel_count(signals.size(), "FFT::SOSFilterBank::pfilter");
11946 parallel_for_index(pool,
11947 0,
11948 filters_.size(),
11949 [this, &signals, &output](const size_t i)
11950 {
11951 output(i) = filters_[i].filter(signals[i]);
11952 },
11953 chunk_size);
11954 return output;
11955 }
11956 };
11957
11959 [[nodiscard]] static Array<Real>
11960 lfilter(const Array<Real> & signal,
11961 const Array<Real> & numerator,
11962 const Array<Real> & denominator,
11963 const Array<Real> & initial_state = {})
11964 {
11965 const IIRCoefficients coeffs =
11966 normalize_iir_coefficients(numerator, denominator, "FFT::lfilter");
11967 return iir_filter_impl(signal,
11968 coeffs.numerator,
11969 coeffs.denominator,
11971 "FFT::lfilter");
11972 }
11973
11974 [[nodiscard]] static Array<Real>
11975 lfilter(const Array<Real> & signal,
11976 const IIRCoefficients & coeffs,
11977 const Array<Real> & initial_state = {})
11978 {
11979 return lfilter(signal, coeffs.numerator, coeffs.denominator, initial_state);
11980 }
11981
11982 [[nodiscard]] static Array<Real>
11983 lfilter(const Array<Real> & signal,
11984 const BiquadSection & section,
11985 const Array<Real> & initial_state = {})
11986 {
11987 return lfilter(signal, section.numerator(), section.denominator(), initial_state);
11988 }
11989
11990 template <typename SignalContainer, typename NumContainer, typename DenContainer>
11994 [[nodiscard]] static Array<Real>
11995 lfilter(const SignalContainer & signal,
11996 const NumContainer & numerator,
11997 const DenContainer & denominator,
11998 const Array<Real> & initial_state = {})
11999 {
12000 return lfilter(build_real_input(signal),
12001 build_real_input(numerator),
12002 build_real_input(denominator),
12004 }
12005
12007 [[nodiscard]] static Array<Array<Real>>
12009 const Array<Real> & numerator,
12010 const Array<Real> & denominator,
12011 const Array<Array<Real>> & initial_states = {})
12012 {
12013 if (signals.is_empty())
12014 return {};
12015
12016 const IIRCoefficients coeffs =
12017 normalize_iir_coefficients(numerator, denominator, "FFT::batched_lfilter");
12019 and initial_states.size() != signals.size())
12020 << "FFT::batched_lfilter: initial state count "
12021 << initial_states.size()
12022 << " does not match channel count " << signals.size();
12023
12025 for (size_t i = 0; i < signals.size(); ++i)
12027 coeffs.numerator,
12028 coeffs.denominator,
12029 initial_states.is_empty() ? Array<Real>() : initial_states[i],
12030 "FFT::batched_lfilter");
12031 return output;
12032 }
12033
12034 [[nodiscard]] static Array<Array<Real>>
12036 const Array<Array<Real>> & signals,
12037 const Array<Real> & numerator,
12038 const Array<Real> & denominator,
12039 const Array<Array<Real>> & initial_states = {},
12040 const size_t chunk_size = 0)
12041 {
12042 if (signals.is_empty())
12043 return {};
12044
12045 const IIRCoefficients coeffs =
12046 normalize_iir_coefficients(numerator, denominator, "FFT::pbatched_lfilter");
12048 and initial_states.size() != signals.size())
12049 << "FFT::pbatched_lfilter: initial state count "
12050 << initial_states.size()
12051 << " does not match channel count " << signals.size();
12052
12054 parallel_for_index(pool,
12055 0,
12056 signals.size(),
12057 [&signals, &coeffs, &initial_states, &output](const size_t i)
12058 {
12059 output(i) = iir_filter_impl(signals[i],
12060 coeffs.numerator,
12061 coeffs.denominator,
12062 initial_states.is_empty() ? Array<Real>() : initial_states[i],
12063 "FFT::pbatched_lfilter");
12064 },
12065 chunk_size);
12066 return output;
12067 }
12068
12069 template <typename SignalsContainer, typename NumContainer, typename DenContainer>
12073 [[nodiscard]] static Array<Array<Real>>
12075 const NumContainer & numerator,
12076 const DenContainer & denominator,
12077 const Array<Array<Real>> & initial_states = {})
12078 {
12080 for (const auto & signal: signals)
12081 batch.append(build_real_input(signal));
12082 return batched_lfilter(batch,
12083 build_real_input(numerator),
12084 build_real_input(denominator),
12086 }
12087
12089 [[nodiscard]] static Array<Real>
12090 sosfilt(const Array<Real> & signal,
12092 {
12093 return SOSFilter(sections).filter(signal);
12094 }
12095
12096 template <typename SignalContainer, typename SectionsContainer>
12099 [[nodiscard]] static Array<Real>
12100 sosfilt(const SignalContainer & signal,
12102 {
12104 }
12105
12107 [[nodiscard]] static Array<Array<Real>>
12110 {
12111 if (signals.is_empty())
12112 return {};
12113
12115 for (size_t i = 0; i < signals.size(); ++i)
12116 output(i) = sosfilt(signals[i], sections);
12117 return output;
12118 }
12119
12120 [[nodiscard]] static Array<Array<Real>>
12122 const Array<Array<Real>> & signals,
12124 const size_t chunk_size = 0)
12125 {
12126 if (signals.is_empty())
12127 return {};
12128
12130 parallel_for_index(pool,
12131 0,
12132 signals.size(),
12133 [&signals, &sections, &output](const size_t i)
12134 {
12135 output(i) = sosfilt(signals[i], sections);
12136 },
12137 chunk_size);
12138 return output;
12139 }
12140
12141 template <typename SignalsContainer, typename SectionsContainer>
12144 [[nodiscard]] static Array<Array<Real>>
12147 {
12149 for (const auto & signal: signals)
12150 batch.append(build_real_input(signal));
12152 }
12153
12155 [[nodiscard]] static FrequencyResponse
12156 freqz(const Array<Real> & numerator,
12157 const Array<Real> & denominator,
12158 const size_t num_points = 512,
12159 const bool whole = false)
12160 {
12161 return freqz_impl(numerator, denominator, num_points, whole, "FFT::freqz");
12162 }
12163
12165 [[nodiscard]] static FrequencyResponse
12166 freqz(const Array<Real> & numerator,
12167 const size_t num_points = 512,
12168 const bool whole = false)
12169 {
12170 return freqz(numerator, Array<Real>({Real(1)}), num_points, whole);
12171 }
12172
12173 [[nodiscard]] static FrequencyResponse
12174 freqz(const IIRCoefficients & coeffs,
12175 const size_t num_points = 512,
12176 const bool whole = false)
12177 {
12178 return freqz(coeffs.numerator, coeffs.denominator, num_points, whole);
12179 }
12180
12181 [[nodiscard]] static FrequencyResponse
12183 const size_t num_points = 512,
12184 const bool whole = false)
12185 {
12186 return freqz(section.numerator(), section.denominator(), num_points, whole);
12187 }
12188
12189 [[nodiscard]] static FrequencyResponse
12191 const size_t num_points = 512,
12192 const bool whole = false)
12193 {
12195 << "FFT::freqz: at least one biquad section is required";
12197 << "FFT::freqz: number of frequency samples must be positive";
12198
12202
12203 const Real max_omega = whole ? Real(2) * std::numbers::pi_v<Real> : std::numbers::pi_v<Real>;
12204 const Real denom = whole ? static_cast<Real>(num_points) : static_cast<Real>(std::max(size_t(1), num_points - 1));
12205
12206 for (size_t i = 0; i < num_points; ++i)
12207 {
12208 const Real omega = num_points == 1 ? Real(0) : max_omega * static_cast<Real>(i) / denom;
12209 Complex response(Real(1), Real(0));
12210 for (size_t j = 0; j < sections.size(); ++j)
12211 response *= evaluate_transfer_at(sections[j].numerator(),
12212 sections[j].denominator(),
12213 omega,
12214 "FFT::freqz(SOS)");
12215 output.omega(i) = omega;
12216 output.response(i) = response;
12217 }
12218
12219 return output;
12220 }
12221
12222 template <typename NumContainer, typename DenContainer>
12224 [[nodiscard]] static FrequencyResponse
12225 freqz(const NumContainer & numerator,
12226 const DenContainer & denominator,
12227 const size_t num_points = 512,
12228 const bool whole = false)
12229 {
12230 return freqz(build_real_input(numerator),
12231 build_real_input(denominator),
12232 num_points,
12233 whole);
12234 }
12235
12236 template <typename SectionsContainer>
12238 [[nodiscard]] static FrequencyResponse
12240 const size_t num_points = 512,
12241 const bool whole = false)
12242 {
12244 }
12245
12247 [[nodiscard]] static Array<Complex>
12248 zeros(const Array<Real> & numerator)
12249 {
12250 const Array<Real> trimmed = trim_leading_zeros_copy(numerator);
12251 if (trimmed.is_empty())
12252 return {};
12253 const auto [deflated, extracted_roots] =
12254 extract_repeated_unit_roots(trimmed, "FFT::zeros");
12255 Array<Complex> roots =
12256 polynomial_roots_impl(deflated, "FFT::zeros");
12258 return roots;
12259 }
12260
12262 [[nodiscard]] static Array<Complex>
12263 poles(const Array<Real> & denominator)
12264 {
12267 denominator,
12268 "FFT::poles");
12269 return polynomial_roots_impl(normalized.denominator, "FFT::poles");
12270 }
12271
12272 [[nodiscard]] static Array<Complex>
12273 zeros(const IIRCoefficients & coeffs)
12274 {
12275 return zeros(coeffs.numerator);
12276 }
12277
12278 [[nodiscard]] static Array<Complex>
12279 poles(const IIRCoefficients & coeffs)
12280 {
12281 return poles(coeffs.denominator);
12282 }
12283
12284 [[nodiscard]] static Array<Complex>
12286 {
12287 return zeros(section.numerator());
12288 }
12289
12290 [[nodiscard]] static Array<Complex>
12292 {
12293 return poles(section.denominator());
12294 }
12295
12296 [[nodiscard]] static Array<Complex>
12298 {
12300 for (size_t i = 0; i < sections.size(); ++i)
12302 return output;
12303 }
12304
12305 [[nodiscard]] static Array<Complex>
12307 {
12309 for (size_t i = 0; i < sections.size(); ++i)
12311 return output;
12312 }
12313
12314 template <typename Container>
12316 [[nodiscard]] static Array<Complex>
12317 zeros(const Container & numerator)
12318 {
12319 return zeros(build_real_input(numerator));
12320 }
12321
12322 template <typename Container>
12324 [[nodiscard]] static Array<Complex>
12325 poles(const Container & denominator)
12326 {
12327 return poles(build_real_input(denominator));
12328 }
12329
12330 template <typename SectionsContainer>
12332 [[nodiscard]] static Array<Complex>
12334 {
12336 }
12337
12338 template <typename SectionsContainer>
12340 [[nodiscard]] static Array<Complex>
12342 {
12344 }
12345
12349 const Array<Complex> & poles)
12350 {
12352 }
12353
12356 const Array<Real> & denominator)
12357 {
12358 return pair_poles_and_zeros(zeros(numerator), poles(denominator));
12359 }
12360
12363 {
12364 return pair_poles_and_zeros(coeffs.numerator, coeffs.denominator);
12365 }
12366
12369 {
12370 return pair_poles_and_zeros(section.numerator(), section.denominator());
12371 }
12372
12378
12379 template <typename NumContainer, typename DenContainer>
12383 const DenContainer & denominator)
12384 {
12385 return pair_poles_and_zeros(build_real_input(numerator),
12386 build_real_input(denominator));
12387 }
12388
12389 template <typename SectionsContainer>
12396
12398 [[nodiscard]] static Real
12404
12405 [[nodiscard]] static Real
12407 const Array<Real> & denominator)
12408 {
12409 return minimum_pole_zero_distance(zeros(numerator), poles(denominator));
12410 }
12411
12412 [[nodiscard]] static Real
12414 {
12415 return minimum_pole_zero_distance(coeffs.numerator, coeffs.denominator);
12416 }
12417
12418 [[nodiscard]] static Real
12420 {
12421 return minimum_pole_zero_distance(section.numerator(), section.denominator());
12422 }
12423
12424 [[nodiscard]] static Real
12429
12430 template <typename NumContainer, typename DenContainer>
12432 [[nodiscard]] static Real
12434 const DenContainer & denominator)
12435 {
12437 build_real_input(denominator));
12438 }
12439
12440 template <typename SectionsContainer>
12442 [[nodiscard]] static Real
12447
12449 [[nodiscard]] static Real
12450 stability_margin(const Array<Real> & denominator)
12451 {
12452 return Real(1) - max_root_radius(poles(denominator));
12453 }
12454
12455 [[nodiscard]] static Real
12457 {
12458 return stability_margin(coeffs.denominator);
12459 }
12460
12461 [[nodiscard]] static Real
12463 {
12464 return stability_margin(section.denominator());
12465 }
12466
12467 [[nodiscard]] static Real
12469 {
12470 if (sections.is_empty())
12471 return Real(1);
12472
12473 Real margin = std::numeric_limits<Real>::infinity();
12474 for (size_t i = 0; i < sections.size(); ++i)
12475 margin = std::min(margin, stability_margin(sections[i]));
12476 return margin;
12477 }
12478
12479 template <typename Container>
12481 [[nodiscard]] static Real
12482 stability_margin(const Container & denominator)
12483 {
12484 return stability_margin(build_real_input(denominator));
12485 }
12486
12487 template <typename SectionsContainer>
12489 [[nodiscard]] static Real
12494
12496 [[nodiscard]] static bool
12497 is_stable(const Array<Real> & denominator)
12498 {
12499 const Real tol = Real(1024) * std::numeric_limits<Real>::epsilon();
12500 return stability_margin(denominator) > tol;
12501 }
12502
12503 [[nodiscard]] static bool
12505 {
12506 return is_stable(coeffs.denominator);
12507 }
12508
12509 [[nodiscard]] static bool
12511 {
12512 return is_stable(section.denominator());
12513 }
12514
12515 [[nodiscard]] static bool
12517 {
12518 for (size_t i = 0; i < sections.size(); ++i)
12519 if (not is_stable(sections[i]))
12520 return false;
12521 return true;
12522 }
12523
12524 template <typename Container>
12526 [[nodiscard]] static bool
12527 is_stable(const Container & denominator)
12528 {
12529 return is_stable(build_real_input(denominator));
12530 }
12531
12532 template <typename SectionsContainer>
12534 [[nodiscard]] static bool
12539
12541 [[nodiscard]] static bool
12543 const Array<Complex> & poles,
12544 const Real tolerance)
12545 {
12547 for (size_t i = 0; i < pairs.size(); ++i)
12548 if (pairs[i].is_cancellation(tolerance))
12549 return true;
12550 return false;
12551 }
12552
12553 [[nodiscard]] static bool
12555 const Array<Real> & denominator,
12556 const Real tolerance)
12557 {
12558 return has_near_pole_zero_cancellation(zeros(numerator),
12559 poles(denominator),
12560 tolerance);
12561 }
12562
12563 [[nodiscard]] static bool
12565 const Real tolerance)
12566 {
12568 coeffs.denominator,
12569 tolerance);
12570 }
12571
12572 [[nodiscard]] static bool
12574 const Real tolerance)
12575 {
12576 return has_near_pole_zero_cancellation(section.numerator(),
12577 section.denominator(),
12578 tolerance);
12579 }
12580
12581 [[nodiscard]] static bool
12583 const Real tolerance)
12584 {
12586 poles(sections),
12587 tolerance);
12588 }
12589
12590 template <typename NumContainer, typename DenContainer>
12592 [[nodiscard]] static bool
12594 const DenContainer & denominator,
12595 const Real tolerance)
12596 {
12598 build_real_input(denominator),
12599 tolerance);
12600 }
12601
12602 template <typename SectionsContainer>
12604 [[nodiscard]] static bool
12606 const Real tolerance)
12607 {
12609 tolerance);
12610 }
12611
12612 static void
12613 validate_stable(const Array<Real> & denominator)
12614 {
12615 const Real tol = Real(1024) * std::numeric_limits<Real>::epsilon();
12616 const Array<Complex> roots = poles(denominator);
12617 const Real margin = stability_margin(denominator);
12619 << "FFT::validate_stable: stability margin " << margin
12620 << " is not strictly positive";
12621 for (size_t i = 0; i < roots.size(); ++i)
12622 ah_domain_error_if(std::abs(roots[i]) >= Real(1) - tol)
12623 << "FFT::validate_stable: unstable pole at " << roots[i]
12624 << " with magnitude " << std::abs(roots[i]);
12625 }
12626
12627 static void
12629 {
12631 }
12632
12633 static void
12635 {
12636 validate_stable(section.denominator());
12637 }
12638
12639 static void
12641 {
12642 for (size_t i = 0; i < sections.size(); ++i)
12644 }
12645
12646 template <typename Container>
12648 static void
12649 validate_stable(const Container & denominator)
12650 {
12651 validate_stable(build_real_input(denominator));
12652 }
12653
12654 template <typename SectionsContainer>
12656 static void
12661
12662 static void
12663 validate_stable(const Array<Real> & denominator,
12664 const Real min_margin)
12665 {
12666 const Real margin = stability_margin(denominator);
12668 << "FFT::validate_stable: stability margin " << margin
12669 << " is smaller than required margin " << min_margin;
12670 validate_stable(denominator);
12671 }
12672
12673 static void
12675 const Real min_margin)
12676 {
12678 }
12679
12680 static void
12682 const Real min_margin)
12683 {
12684 validate_stable(section.denominator(), min_margin);
12685 }
12686
12687 static void
12689 const Real min_margin)
12690 {
12693 << "FFT::validate_stable: SOS stability margin "
12694 << margin
12695 << " is smaller than required margin " << min_margin;
12697 }
12698
12699 template <typename Container>
12701 static void
12702 validate_stable(const Container & denominator,
12703 const Real min_margin)
12704 {
12706 }
12707
12708 template <typename SectionsContainer>
12710 static void
12716
12717 static void
12719 const Array<Complex> & poles,
12720 const Real tolerance)
12721 {
12723 for (size_t i = 0; i < pairs.size(); ++i)
12724 if (pairs[i].is_cancellation(tolerance))
12725 ah_domain_error_if(true)
12726 << "FFT::validate_no_near_pole_zero_cancellation: zero "
12727 << pairs[i].zero << " and pole " << pairs[i].pole
12728 << " are only " << pairs[i].distance << " apart";
12729 }
12730
12731 static void
12733 const Array<Real> & denominator,
12734 const Real tolerance)
12735 {
12737 poles(denominator),
12738 tolerance);
12739 }
12740
12741 static void
12743 const Real tolerance)
12744 {
12746 coeffs.denominator,
12747 tolerance);
12748 }
12749
12750 static void
12752 const Real tolerance)
12753 {
12755 section.denominator(),
12756 tolerance);
12757 }
12758
12759 static void
12767
12768 template <typename NumContainer, typename DenContainer>
12770 static void
12772 const DenContainer & denominator,
12773 const Real tolerance)
12774 {
12776 build_real_input(denominator),
12777 tolerance);
12778 }
12779
12780 template <typename SectionsContainer>
12782 static void
12789
12791 [[nodiscard]] static Array<Real>
12793 {
12794 return group_delay_impl(response);
12795 }
12796
12798 [[nodiscard]] static Array<Real>
12800 {
12801 return phase_delay_impl(response);
12802 }
12803
12804 [[nodiscard]] static Array<Real>
12805 group_delay(const Array<Real> & numerator,
12806 const Array<Real> & denominator,
12807 const size_t num_points = 512,
12808 const bool whole = false)
12809 {
12810 return analytic_group_delay_impl(numerator,
12811 denominator,
12812 num_points,
12813 whole,
12814 "FFT::group_delay");
12815 }
12816
12817 [[nodiscard]] static Array<Real>
12818 phase_delay(const Array<Real> & numerator,
12819 const Array<Real> & denominator,
12820 const size_t num_points = 512,
12821 const bool whole = false)
12822 {
12823 return analytic_phase_delay_impl(numerator,
12824 denominator,
12825 num_points,
12826 whole,
12827 "FFT::phase_delay");
12828 }
12829
12830 [[nodiscard]] static Array<Real>
12831 group_delay(const Array<Real> & numerator,
12832 const size_t num_points = 512,
12833 const bool whole = false)
12834 {
12835 return analytic_group_delay_impl(numerator,
12836 Array<Real>({Real(1)}),
12837 num_points,
12838 whole,
12839 "FFT::group_delay");
12840 }
12841
12842 [[nodiscard]] static Array<Real>
12843 phase_delay(const Array<Real> & numerator,
12844 const size_t num_points = 512,
12845 const bool whole = false)
12846 {
12847 return analytic_phase_delay_impl(numerator,
12848 Array<Real>({Real(1)}),
12849 num_points,
12850 whole,
12851 "FFT::phase_delay");
12852 }
12853
12854 [[nodiscard]] static Array<Real>
12856 const size_t num_points = 512,
12857 const bool whole = false)
12858 {
12859 return group_delay(coeffs.numerator, coeffs.denominator, num_points, whole);
12860 }
12861
12862 [[nodiscard]] static Array<Real>
12864 const size_t num_points = 512,
12865 const bool whole = false)
12866 {
12867 return phase_delay(coeffs.numerator, coeffs.denominator, num_points, whole);
12868 }
12869
12870 [[nodiscard]] static Array<Real>
12872 const size_t num_points = 512,
12873 const bool whole = false)
12874 {
12875 return group_delay(section.numerator(),
12876 section.denominator(),
12877 num_points,
12878 whole);
12879 }
12880
12881 [[nodiscard]] static Array<Real>
12883 const size_t num_points = 512,
12884 const bool whole = false)
12885 {
12886 return phase_delay(section.numerator(),
12887 section.denominator(),
12888 num_points,
12889 whole);
12890 }
12891
12892 [[nodiscard]] static Array<Real>
12894 const size_t num_points = 512,
12895 const bool whole = false)
12896 {
12897 const FrequencyResponse response = freqz(sections, num_points, whole);
12899 response,
12900 "FFT::group_delay(SOS)");
12901 }
12902
12903 [[nodiscard]] static Array<Real>
12905 const size_t num_points = 512,
12906 const bool whole = false)
12907 {
12908 const FrequencyResponse response = freqz(sections, num_points, whole);
12909 const Array<Real> phase = unwrap_phase(phase_spectrum(response.response));
12910 const Array<Real> group =
12912 response,
12913 "FFT::phase_delay(SOS)");
12915 const Real tol = (response.omega[response.omega.size() - 1] + Real(1))
12916 * Real(128) * std::numeric_limits<Real>::epsilon();
12917
12918 for (size_t i = 0; i < response.omega.size(); ++i)
12919 delay(i) = std::abs(response.omega[i]) <= tol ? group[i] : -phase[i] / response.omega[i];
12920
12921 return delay;
12922 }
12923
12924 template <typename NumContainer, typename DenContainer>
12926 [[nodiscard]] static Array<Real>
12927 group_delay(const NumContainer & numerator,
12928 const DenContainer & denominator,
12929 const size_t num_points = 512,
12930 const bool whole = false)
12931 {
12932 return group_delay(build_real_input(numerator),
12933 build_real_input(denominator),
12934 num_points,
12935 whole);
12936 }
12937
12938 template <typename NumContainer, typename DenContainer>
12940 [[nodiscard]] static Array<Real>
12941 phase_delay(const NumContainer & numerator,
12942 const DenContainer & denominator,
12943 const size_t num_points = 512,
12944 const bool whole = false)
12945 {
12946 return phase_delay(build_real_input(numerator),
12947 build_real_input(denominator),
12948 num_points,
12949 whole);
12950 }
12951
12952 template <typename Container>
12954 [[nodiscard]] static Array<Real>
12955 group_delay(const Container & numerator,
12956 const size_t num_points = 512,
12957 const bool whole = false)
12958 {
12959 return group_delay(build_real_input(numerator), num_points, whole);
12960 }
12961
12962 template <typename Container>
12964 [[nodiscard]] static Array<Real>
12965 phase_delay(const Container & numerator,
12966 const size_t num_points = 512,
12967 const bool whole = false)
12968 {
12969 return phase_delay(build_real_input(numerator), num_points, whole);
12970 }
12971
12972 template <typename SectionsContainer>
12974 [[nodiscard]] static Array<Real>
12976 const size_t num_points = 512,
12977 const bool whole = false)
12978 {
12980 }
12981
12982 template <typename SectionsContainer>
12984 [[nodiscard]] static Array<Real>
12986 const size_t num_points = 512,
12987 const bool whole = false)
12988 {
12990 }
12991
12993 [[nodiscard]] static PhaseMarginInfo
12995 {
12996 return phase_margin_impl(response);
12997 }
12998
13000 [[nodiscard]] static GainMarginInfo
13002 {
13003 return gain_margin_impl(response);
13004 }
13005
13006 [[nodiscard]] static PhaseMarginInfo
13007 phase_margin(const Array<Real> & numerator,
13008 const Array<Real> & denominator,
13009 const size_t num_points = 1024,
13010 const bool whole = false)
13011 {
13012 const size_t seed_points = std::max(num_points, size_t(4097));
13013 const FrequencyResponse response = freqz(numerator,
13014 denominator,
13016 whole);
13017 return phase_margin_refined_impl(response,
13018 [&](const Real omega)
13019 {
13020 return evaluate_transfer_at(numerator,
13021 denominator,
13022 omega,
13023 "FFT::phase_margin");
13024 });
13025 }
13026
13027 [[nodiscard]] static GainMarginInfo
13028 gain_margin(const Array<Real> & numerator,
13029 const Array<Real> & denominator,
13030 const size_t num_points = 1024,
13031 const bool whole = false)
13032 {
13033 const size_t seed_points = std::max(num_points, size_t(4097));
13034 const FrequencyResponse response = freqz(numerator,
13035 denominator,
13037 whole);
13038 return gain_margin_refined_impl(response,
13039 [&](const Real omega)
13040 {
13041 return evaluate_transfer_at(numerator,
13042 denominator,
13043 omega,
13044 "FFT::gain_margin");
13045 });
13046 }
13047
13048 [[nodiscard]] static PhaseMarginInfo
13050 const size_t num_points = 1024,
13051 const bool whole = false)
13052 {
13053 return phase_margin(coeffs.numerator, coeffs.denominator, num_points, whole);
13054 }
13055
13056 [[nodiscard]] static GainMarginInfo
13058 const size_t num_points = 1024,
13059 const bool whole = false)
13060 {
13061 return gain_margin(coeffs.numerator, coeffs.denominator, num_points, whole);
13062 }
13063
13064 [[nodiscard]] static PhaseMarginInfo
13066 const size_t num_points = 1024,
13067 const bool whole = false)
13068 {
13069 return phase_margin(section.numerator(),
13070 section.denominator(),
13071 num_points,
13072 whole);
13073 }
13074
13075 [[nodiscard]] static GainMarginInfo
13077 const size_t num_points = 1024,
13078 const bool whole = false)
13079 {
13080 return gain_margin(section.numerator(),
13081 section.denominator(),
13082 num_points,
13083 whole);
13084 }
13085
13086 [[nodiscard]] static PhaseMarginInfo
13088 const size_t num_points = 1024,
13089 const bool whole = false)
13090 {
13091 const size_t seed_points = std::max(num_points, size_t(4097));
13092 const FrequencyResponse response = freqz(sections, seed_points, whole);
13093 return phase_margin_refined_impl(response,
13094 [&](const Real omega)
13095 {
13097 omega,
13098 "FFT::phase_margin(SOS)");
13099 });
13100 }
13101
13102 [[nodiscard]] static GainMarginInfo
13104 const size_t num_points = 1024,
13105 const bool whole = false)
13106 {
13107 const size_t seed_points = std::max(num_points, size_t(4097));
13108 const FrequencyResponse response = freqz(sections, seed_points, whole);
13109 return gain_margin_refined_impl(response,
13110 [&](const Real omega)
13111 {
13113 omega,
13114 "FFT::gain_margin(SOS)");
13115 });
13116 }
13117
13118 template <typename NumContainer, typename DenContainer>
13120 [[nodiscard]] static PhaseMarginInfo
13121 phase_margin(const NumContainer & numerator,
13122 const DenContainer & denominator,
13123 const size_t num_points = 1024,
13124 const bool whole = false)
13125 {
13126 return phase_margin(build_real_input(numerator),
13127 build_real_input(denominator),
13128 num_points,
13129 whole);
13130 }
13131
13132 template <typename NumContainer, typename DenContainer>
13134 [[nodiscard]] static GainMarginInfo
13135 gain_margin(const NumContainer & numerator,
13136 const DenContainer & denominator,
13137 const size_t num_points = 1024,
13138 const bool whole = false)
13139 {
13140 return gain_margin(build_real_input(numerator),
13141 build_real_input(denominator),
13142 num_points,
13143 whole);
13144 }
13145
13146 template <typename SectionsContainer>
13148 [[nodiscard]] static PhaseMarginInfo
13150 const size_t num_points = 1024,
13151 const bool whole = false)
13152 {
13154 }
13155
13156 template <typename SectionsContainer>
13158 [[nodiscard]] static GainMarginInfo
13160 const size_t num_points = 1024,
13161 const bool whole = false)
13162 {
13164 }
13165
13167 [[nodiscard]] static IIRCoefficients
13170 const Real sample_rate)
13171 {
13175 "FFT::bilinear_transform");
13176 }
13177
13178 template <typename NumContainer, typename DenContainer>
13180 [[nodiscard]] static IIRCoefficients
13189
13192 butterworth_lowpass(const size_t order,
13193 const Real cutoff_frequency,
13194 const Real sample_rate)
13195 {
13197 "FFT::butterworth_lowpass"),
13198 order,
13201 false,
13202 "FFT::butterworth_lowpass");
13203 }
13204
13207 butterworth_highpass(const size_t order,
13208 const Real cutoff_frequency,
13209 const Real sample_rate)
13210 {
13212 "FFT::butterworth_highpass"),
13213 order,
13216 true,
13217 "FFT::butterworth_highpass");
13218 }
13219
13222 butterworth_bandpass(const size_t order,
13225 const Real sample_rate)
13226 {
13227 const auto [warped_low, warped_high] =
13231 "FFT::butterworth_bandpass");
13233 const Real center = std::sqrt(warped_low * warped_high);
13235 "FFT::butterworth_bandpass"),
13236 {Real(1), Real(0), center * center},
13237 {bandwidth, Real(0)},
13239 "FFT::butterworth_bandpass");
13240 }
13241
13244 butterworth_bandstop(const size_t order,
13247 const Real sample_rate)
13248 {
13249 return design_bandstop_sections_without_numerator_roots(butterworth_prototype(order, "FFT::butterworth_bandstop"),
13250 order,
13254 "FFT::butterworth_bandstop");
13255 }
13256
13259 chebyshev1_lowpass(const size_t order,
13260 const Real ripple_db,
13261 const Real cutoff_frequency,
13262 const Real sample_rate)
13263 {
13265 ripple_db,
13266 "FFT::chebyshev1_lowpass"),
13267 order,
13270 false,
13271 "FFT::chebyshev1_lowpass");
13272 }
13273
13276 chebyshev1_highpass(const size_t order,
13277 const Real ripple_db,
13278 const Real cutoff_frequency,
13279 const Real sample_rate)
13280 {
13282 ripple_db,
13283 "FFT::chebyshev1_highpass"),
13284 order,
13287 true,
13288 "FFT::chebyshev1_highpass");
13289 }
13290
13293 chebyshev1_bandpass(const size_t order,
13294 const Real ripple_db,
13297 const Real sample_rate)
13298 {
13299 const auto [warped_low, warped_high] =
13303 "FFT::chebyshev1_bandpass");
13305 const Real center = std::sqrt(warped_low * warped_high);
13307 ripple_db,
13308 "FFT::chebyshev1_bandpass"),
13309 {Real(1), Real(0), center * center},
13310 {bandwidth, Real(0)},
13312 "FFT::chebyshev1_bandpass");
13313 }
13314
13317 chebyshev1_bandstop(const size_t order,
13318 const Real ripple_db,
13321 const Real sample_rate)
13322 {
13325 ripple_db,
13326 "FFT::chebyshev1_bandstop"),
13327 order,
13331 "FFT::chebyshev1_bandstop");
13332 }
13333
13336 chebyshev2_lowpass(const size_t order,
13337 const Real attenuation_db,
13338 const Real cutoff_frequency,
13339 const Real sample_rate)
13340 {
13342 attenuation_db,
13343 "FFT::chebyshev2_lowpass"),
13346 false,
13347 "FFT::chebyshev2_lowpass");
13348 }
13349
13352 chebyshev2_highpass(const size_t order,
13353 const Real attenuation_db,
13354 const Real cutoff_frequency,
13355 const Real sample_rate)
13356 {
13358 attenuation_db,
13359 "FFT::chebyshev2_highpass"),
13362 true,
13363 "FFT::chebyshev2_highpass");
13364 }
13365
13368 chebyshev2_bandpass(const size_t order,
13369 const Real attenuation_db,
13372 const Real sample_rate)
13373 {
13374 const auto [warped_low, warped_high] =
13378 "FFT::chebyshev2_bandpass");
13380 const Real center = std::sqrt(warped_low * warped_high);
13382 attenuation_db,
13383 "FFT::chebyshev2_bandpass"),
13384 {Real(1), Real(0), center * center},
13385 {bandwidth, Real(0)},
13387 "FFT::chebyshev2_bandpass");
13388 }
13389
13392 chebyshev2_bandstop(const size_t order,
13393 const Real attenuation_db,
13396 const Real sample_rate)
13397 {
13398 const auto [warped_low, warped_high] =
13402 "FFT::chebyshev2_bandstop");
13404 const Real center = std::sqrt(warped_low * warped_high);
13406 attenuation_db,
13407 "FFT::chebyshev2_bandstop"),
13408 {bandwidth, Real(0)},
13409 {Real(1), Real(0), center * center},
13411 "FFT::chebyshev2_bandstop");
13412 }
13413
13420 bessel_lowpass(const size_t order,
13421 const Real cutoff_frequency,
13422 const Real sample_rate)
13423 {
13425 "FFT::bessel_lowpass"),
13428 false,
13429 "FFT::bessel_lowpass");
13430 }
13431
13434 bessel_highpass(const size_t order,
13435 const Real cutoff_frequency,
13436 const Real sample_rate)
13437 {
13439 "FFT::bessel_highpass"),
13442 true,
13443 "FFT::bessel_highpass");
13444 }
13445
13448 bessel_bandpass(const size_t order,
13451 const Real sample_rate)
13452 {
13453 const auto [warped_low, warped_high] =
13457 "FFT::bessel_bandpass");
13459 const Real center = std::sqrt(warped_low * warped_high);
13461 "FFT::bessel_bandpass"),
13462 {Real(1), Real(0), center * center},
13463 {bandwidth, Real(0)},
13465 "FFT::bessel_bandpass");
13466 }
13467
13470 bessel_bandstop(const size_t order,
13473 const Real sample_rate)
13474 {
13475 return design_bandstop_sections_without_numerator_roots(bessel_prototype(order, "FFT::bessel_bandstop"),
13476 order,
13480 "FFT::bessel_bandstop");
13481 }
13482
13485 elliptic_lowpass(const size_t order,
13486 const Real ripple_db,
13487 const Real attenuation_db,
13488 const Real cutoff_frequency,
13489 const Real sample_rate)
13490 {
13492 ripple_db,
13493 attenuation_db,
13494 "FFT::elliptic_lowpass"),
13497 false,
13498 "FFT::elliptic_lowpass");
13499 }
13500
13503 elliptic_highpass(const size_t order,
13504 const Real ripple_db,
13505 const Real attenuation_db,
13506 const Real cutoff_frequency,
13507 const Real sample_rate)
13508 {
13510 ripple_db,
13511 attenuation_db,
13512 "FFT::elliptic_highpass"),
13515 true,
13516 "FFT::elliptic_highpass");
13517 }
13518
13521 elliptic_bandpass(const size_t order,
13522 const Real ripple_db,
13523 const Real attenuation_db,
13526 const Real sample_rate)
13527 {
13528 const auto [warped_low, warped_high] =
13532 "FFT::elliptic_bandpass");
13534 const Real center = std::sqrt(warped_low * warped_high);
13536 ripple_db,
13537 attenuation_db,
13538 "FFT::elliptic_bandpass"),
13539 {Real(1), Real(0), center * center},
13540 {bandwidth, Real(0)},
13542 "FFT::elliptic_bandpass");
13543 }
13544
13547 elliptic_bandstop(const size_t order,
13548 const Real ripple_db,
13549 const Real attenuation_db,
13552 const Real sample_rate)
13553 {
13554 const auto [warped_low, warped_high] =
13558 "FFT::elliptic_bandstop");
13560 const Real center = std::sqrt(warped_low * warped_high);
13562 ripple_db,
13563 attenuation_db,
13564 "FFT::elliptic_bandstop"),
13565 {bandwidth, Real(0)},
13566 {Real(1), Real(0), center * center},
13568 "FFT::elliptic_bandstop");
13569 }
13570
13577 [[nodiscard]] static Array<Real>
13578 filtfilt(const Array<Real> & signal,
13579 const Array<Real> & coeffs,
13580 const size_t block_size = 0)
13581 {
13582 if (signal.is_empty() or coeffs.is_empty())
13583 return {};
13584
13585 if (coeffs.size() == 1)
13586 {
13588 const Real gain = coeffs[0] * coeffs[0];
13589 for (size_t i = 0; i < signal.size(); ++i)
13590 output(i) = signal[i] * gain;
13591 return output;
13592 }
13593
13594 const size_t pad_len = default_filtfilt_pad_length(signal.size(), coeffs.size());
13596 const OverlapAdd convolver(coeffs, block_size);
13597
13598 const Array<Real> forward = prefix_copy(convolver.convolve(padded), padded.size());
13599 const Array<Real> reversed = reverse_copy(forward);
13600 const Array<Real> backward = prefix_copy(convolver.convolve(reversed), padded.size());
13601 const Array<Real> zero_phase = reverse_copy(backward);
13602
13603 return slice_copy(zero_phase, pad_len, signal.size());
13604 }
13605
13607 [[nodiscard]] static Array<Real>
13609 const Array<Real> & signal,
13610 const Array<Real> & coeffs,
13611 const size_t block_size = 0,
13612 const size_t chunk_size = 0)
13613 {
13614 if (signal.is_empty() or coeffs.is_empty())
13615 return {};
13616
13617 if (coeffs.size() == 1)
13618 {
13620 const Real gain = coeffs[0] * coeffs[0];
13621 for (size_t i = 0; i < signal.size(); ++i)
13622 output(i) = signal[i] * gain;
13623 return output;
13624 }
13625
13626 const size_t pad_len = default_filtfilt_pad_length(signal.size(), coeffs.size());
13628 const OverlapAdd convolver(coeffs, block_size);
13629
13630 const Array<Real> forward = prefix_copy(convolver.pconvolve(pool, padded, chunk_size),
13631 padded.size());
13632 const Array<Real> reversed = reverse_copy(forward);
13633 const Array<Real> backward = prefix_copy(convolver.pconvolve(pool, reversed, chunk_size),
13634 padded.size());
13635 const Array<Real> zero_phase = reverse_copy(backward);
13636
13637 return slice_copy(zero_phase, pad_len, signal.size());
13638 }
13639
13640 template <typename SignalContainer, typename CoeffContainer>
13642 [[nodiscard]] static Array<Real>
13644 const CoeffContainer & coeffs,
13645 const size_t block_size = 0)
13646 {
13647 return filtfilt(build_real_input(signal), build_real_input(coeffs), block_size);
13648 }
13649
13650 template <typename SignalContainer, typename CoeffContainer>
13652 [[nodiscard]] static Array<Real>
13654 const SignalContainer & signal,
13655 const CoeffContainer & coeffs,
13656 const size_t block_size = 0,
13657 const size_t chunk_size = 0)
13658 {
13659 return pfiltfilt(pool,
13660 build_real_input(signal),
13661 build_real_input(coeffs),
13662 block_size,
13663 chunk_size);
13664 }
13665
13673 [[nodiscard]] static Array<Real>
13674 filtfilt(const Array<Real> & signal,
13675 const Array<Real> & numerator,
13676 const Array<Real> & denominator)
13677 {
13678 return iir_filtfilt_impl(signal,
13679 numerator,
13680 denominator,
13681 "FFT::filtfilt(IIR)");
13682 }
13683
13685 [[nodiscard]] static Array<Real>
13686 filtfilt(const Array<Real> & signal,
13687 const IIRCoefficients & coeffs)
13688 {
13689 return filtfilt(signal, coeffs.numerator, coeffs.denominator);
13690 }
13691
13693 [[nodiscard]] static Array<Real>
13694 filtfilt(const Array<Real> & signal,
13695 const BiquadSection & section)
13696 {
13697 return filtfilt(signal, section.numerator(), section.denominator());
13698 }
13699
13701 [[nodiscard]] static Array<Real>
13702 filtfilt(const Array<Real> & signal,
13704 {
13705 return sos_filtfilt_impl(signal, sections, "FFT::filtfilt(SOS)");
13706 }
13707
13708 template <typename SignalContainer, typename NumContainer, typename DenContainer>
13712 [[nodiscard]] static Array<Real>
13714 const NumContainer & numerator,
13715 const DenContainer & denominator)
13716 {
13717 return filtfilt(build_real_input(signal),
13718 build_real_input(numerator),
13719 build_real_input(denominator));
13720 }
13721
13722 template <typename SignalContainer>
13724 [[nodiscard]] static Array<Real>
13726 const IIRCoefficients & coeffs)
13727 {
13728 return filtfilt(build_real_input(signal), coeffs);
13729 }
13730
13731 template <typename SignalContainer>
13733 [[nodiscard]] static Array<Real>
13735 const BiquadSection & section)
13736 {
13737 return filtfilt(build_real_input(signal), section);
13738 }
13739
13740 template <typename SignalContainer, typename SectionsContainer>
13743 [[nodiscard]] static Array<Real>
13746 {
13747 return filtfilt(build_real_input(signal),
13749 }
13750
13758 {
13760 size_t block_size_ = 0;
13761 size_t fft_size_ = 0;
13765 bool has_pending_tail_ = false;
13766
13767 [[nodiscard]] static size_t
13769 {
13771 << "FFT::OverlapAdd: kernel size must be positive";
13772 return next_power_of_two(std::max(kernel_size, size_t(1)));
13773 }
13774
13780 const size_t offset,
13781 const size_t length) const
13782 {
13784 for (size_t i = 0; i < fft_size_; ++i)
13785 block(i) = Complex(Real(0), Real(0));
13786
13787 for (size_t i = 0; i < length; ++i)
13788 block(i) = Complex(signal[offset + i], Real(0));
13789
13790 return block;
13791 }
13792
13796 void
13798 ThreadPool *pool,
13799 const size_t chunk_size) const
13800 {
13801 auto pointwise = [this, &spectrum](const size_t i)
13802 {
13803 spectrum(i) *= kernel_spectrum_[i];
13804 };
13805
13806 if (pool != nullptr and pool->num_threads() > 1 and fft_size_ > 1)
13807 parallel_for_index(*pool, 0, fft_size_, pointwise, chunk_size);
13808 else
13809 for (size_t i = 0; i < fft_size_; ++i)
13810 pointwise(i);
13811 }
13812
13814 void
13816 {
13817 for (size_t i = 0; i < overlap_.size(); ++i)
13818 overlap_(i) = Real(0);
13819 has_pending_tail_ = false;
13820 }
13821
13831 ThreadPool *pool,
13832 const size_t chunk_size)
13833 {
13834 if (block.is_empty())
13835 return {};
13836
13837 ah_invalid_argument_if(block.size() > block_size_)
13838 << "FFT::OverlapAdd::process_block: block size " << block.size()
13839 << " exceeds configured block size " << block_size_;
13840
13841 Array<Complex> spectrum = build_signal_block(block, 0, block.size());
13842
13843 if (pool != nullptr)
13844 plan_.ptransform(*pool, spectrum, false, chunk_size);
13845 else
13846 plan_.transform(spectrum, false);
13847
13848 pointwise_multiply(spectrum, pool, chunk_size);
13849
13850 const Array<Real> convolved = (pool != nullptr) ?
13851 plan_.pinverse_transform_real(*pool, spectrum, chunk_size) :
13853
13854 Array<Real> emitted = Array<Real>::create(block.size());
13855 for (size_t i = 0; i < block.size(); ++i)
13856 {
13857 const Real overlap = i < overlap_.size() ? overlap_[i] : Real(0);
13858 emitted(i) = convolved[i] + overlap;
13859 }
13860
13861 if (not overlap_.is_empty())
13862 {
13864 for (size_t i = 0; i < overlap_.size(); ++i)
13865 {
13866 const size_t src = block.size() + i;
13867 const Real from_current = src < convolved.size() ? convolved[src] : Real(0);
13868 const Real from_previous = src < overlap_.size() ? overlap_[src] : Real(0);
13870 }
13872 }
13873
13874 has_pending_tail_ = true;
13875 return emitted;
13876 }
13877
13881 ThreadPool *pool,
13882 const size_t chunk_size) const
13883 {
13884 if (signal.is_empty())
13885 return {};
13886
13888 > std::numeric_limits<size_t>::max()
13889 - kernel_.size() + 1)
13890 << "FFT::OverlapAdd::convolve: product size exceeds size_t capacity";
13891
13893 for (size_t i = 0; i < output.size(); ++i)
13894 output(i) = Real(0);
13895
13896 for (size_t offset = 0; offset < signal.size(); offset += block_size_)
13897 {
13898 const size_t length = std::min(block_size_, signal.size() - offset);
13900
13901 if (pool != nullptr)
13902 plan_.ptransform(*pool, spectrum, false, chunk_size);
13903 else
13904 plan_.transform(spectrum, false);
13905
13906 pointwise_multiply(spectrum, pool, chunk_size);
13907
13908 const Array<Real> block = (pool != nullptr) ?
13909 plan_.pinverse_transform_real(*pool, spectrum, chunk_size) :
13911
13912 const size_t valid = length + kernel_.size() - 1;
13913 for (size_t i = 0; i < valid; ++i)
13914 output(offset + i) += block[i];
13915 }
13916
13917 return output;
13918 }
13919
13920 public:
13922 const size_t block_size = 0)
13923 : kernel_(kernel)
13924 {
13926 << "FFT::OverlapAdd: kernel must be non-empty";
13927
13930 << "FFT::OverlapAdd: block size must be positive";
13931
13933 > std::numeric_limits<size_t>::max()
13934 - kernel_.size() + 1)
13935 << "FFT::OverlapAdd: FFT size overflow";
13936
13938 plan_ = Plan(fft_size_);
13940 clear_overlap();
13941
13943 for (size_t i = 0; i < fft_size_; ++i)
13944 padded_kernel(i) = Complex(Real(0), Real(0));
13945 for (size_t i = 0; i < kernel_.size(); ++i)
13946 padded_kernel(i) = Complex(kernel_[i], Real(0));
13947
13950 }
13951
13953 [[nodiscard]] size_t fft_size() const noexcept { return fft_size_; }
13956
13957 void reset()
13958 {
13959 clear_overlap();
13960 }
13961
13963 convolve(const Array<Real> & signal) const
13964 {
13965 return convolve_impl(signal, nullptr, 0);
13966 }
13967
13969 pconvolve(ThreadPool & pool, const Array<Real> & signal,
13970 const size_t chunk_size = 0) const
13971 {
13972 return convolve_impl(signal, &pool, chunk_size);
13973 }
13974
13977 {
13978 if (block.is_empty())
13979 return {};
13980
13982 emitted.reserve(block.size());
13983 for (size_t offset = 0; offset < block.size(); offset += block_size_)
13984 {
13985 const size_t length = std::min(block_size_, block.size() - offset);
13986 Array<Real> chunk;
13987 chunk.reserve(length);
13988 for (size_t i = 0; i < length; ++i)
13989 chunk.append(block[offset + i]);
13990
13991 const Array<Real> partial = process_chunk_impl(chunk, nullptr, 0);
13992 for (size_t i = 0; i < partial.size(); ++i)
13993 emitted.append(partial[i]);
13994 }
13995
13996 return emitted;
13997 }
13998
14001 const size_t chunk_size = 0)
14002 {
14003 if (block.is_empty())
14004 return {};
14005
14007 emitted.reserve(block.size());
14008 for (size_t offset = 0; offset < block.size(); offset += block_size_)
14009 {
14010 const size_t length = std::min(block_size_, block.size() - offset);
14011 Array<Real> chunk;
14012 chunk.reserve(length);
14013 for (size_t i = 0; i < length; ++i)
14014 chunk.append(block[offset + i]);
14015
14016 const Array<Real> partial = process_chunk_impl(chunk, &pool, chunk_size);
14017 for (size_t i = 0; i < partial.size(); ++i)
14018 emitted.append(partial[i]);
14019 }
14020
14021 return emitted;
14022 }
14023
14026 {
14028 {
14029 has_pending_tail_ = false;
14030 return {};
14031 }
14032
14033 Array<Real> tail = overlap_;
14034 clear_overlap();
14035 return tail;
14036 }
14037
14038 template <typename Container>
14041 convolve(const Container & signal) const
14042 {
14043 return convolve(build_real_input(signal));
14044 }
14045
14046 template <typename Container>
14049 pconvolve(ThreadPool & pool, const Container & signal,
14050 const size_t chunk_size = 0) const
14051 {
14052 return pconvolve(pool, build_real_input(signal), chunk_size);
14053 }
14054
14055 template <typename Container>
14059 {
14060 return process_block(build_real_input(block));
14061 }
14062
14063 template <typename Container>
14066 pprocess_block(ThreadPool & pool, const Container & block,
14067 const size_t chunk_size = 0)
14068 {
14069 return pprocess_block(pool, build_real_input(block), chunk_size);
14070 }
14071 };
14072
14080 {
14082 size_t block_size_ = 0;
14083 size_t fft_size_ = 0;
14088
14092 [[nodiscard]] static size_t
14094 {
14096 << "FFT::OverlapAddBank: kernel size must be positive";
14097 return next_power_of_two(std::max(kernel_size, size_t(1)));
14098 }
14099
14101 void
14102 validate_channel_count(const size_t count, const char *ctx) const
14103 {
14105 << ctx << ": batch size " << count
14106 << " does not match configured channel count " << overlaps_.size();
14107 }
14108
14110 [[nodiscard]] static size_t
14112 {
14113 size_t length = 0;
14114 for (size_t i = 0; i < signals.size(); ++i)
14115 length = std::max(length, signals[i].size());
14116 return length;
14117 }
14118
14120 [[nodiscard]] static Array<Real>
14122 const size_t offset,
14123 const size_t length)
14124 {
14125 Array<Real> chunk;
14126 chunk.reserve(length);
14127 for (size_t i = 0; i < length; ++i)
14128 chunk.append(input[offset + i]);
14129 return chunk;
14130 }
14131
14135 const size_t offset = 0,
14136 const size_t length = std::numeric_limits<size_t>::max()) const
14137 {
14139 << "FFT::OverlapAddBank::build_signal_block: offset " << offset
14140 << " exceeds signal size " << signal.size();
14141 const size_t effective_length =
14142 std::min(length, signal.size() - offset);
14144 << "FFT::OverlapAddBank::build_signal_block: block size "
14145 << effective_length << " exceeds configured block size " << block_size_;
14146
14148 for (size_t i = 0; i < fft_size_; ++i)
14149 block(i) = Complex(Real(0), Real(0));
14150 for (size_t i = 0; i < effective_length; ++i)
14151 block(i) = Complex(signal[offset + i], Real(0));
14152 return block;
14153 }
14154
14158 {
14159 Array<Array<Complex>> lifted(block.size(), Array<Complex>());
14160 for (size_t channel = 0; channel < block.size(); ++channel)
14161 lifted(channel) = build_signal_block(block[channel]);
14162 return lifted;
14163 }
14164
14168 const Array<size_t> & channels,
14169 const Array<size_t> & lengths,
14170 const size_t offset = 0) const
14171 {
14173 for (size_t item = 0; item < channels.size(); ++item)
14174 lifted(item) = build_signal_block(source[channels[item]],
14175 offset,
14176 lengths[item]);
14177 return lifted;
14178 }
14179
14183 void
14185 ThreadPool *pool,
14186 const size_t chunk_size) const
14187 {
14188 auto multiply_one = [this, &spectra](const size_t channel)
14189 {
14190 for (size_t bin = 0; bin < fft_size_; ++bin)
14191 spectra(channel)(bin) *= kernel_spectrum_[bin];
14192 };
14193
14194 if (pool != nullptr and pool->num_threads() > 1 and spectra.size() > 1)
14195 parallel_for_index(*pool, 0, spectra.size(), multiply_one, chunk_size);
14196 else
14197 for (size_t channel = 0; channel < spectra.size(); ++channel)
14198 multiply_one(channel);
14199 }
14200
14202 void
14203 clear_channel_overlap(const size_t channel)
14204 {
14205 for (size_t i = 0; i < overlaps_[channel].size(); ++i)
14206 overlaps_(channel)(i) = Real(0);
14207 has_pending_tail_(channel) = false;
14208 }
14209
14210 void
14212 {
14213 for (size_t channel = 0; channel < overlaps_.size(); ++channel)
14214 clear_channel_overlap(channel);
14215 }
14216
14219 ThreadPool *pool,
14220 const size_t chunk_size)
14221 {
14222 validate_channel_count(block.size(), "FFT::OverlapAddBank::process_block");
14223
14224 const size_t active_count = FFT::count_non_empty_real_batch(block);
14227 active_channels.reserve(active_count);
14228 active_lengths.reserve(active_count);
14229 for (size_t channel = 0; channel < block.size(); ++channel)
14230 if (not block[channel].is_empty())
14231 {
14232 active_channels.append(channel);
14233 active_lengths.append(block[channel].size());
14234 }
14235
14236 Array<Array<Real>> emitted(block.size(), Array<Real>());
14237 if (active_channels.is_empty())
14238 return emitted;
14239
14243 const bool parallelize_fft =
14245 if (parallelize_fft)
14246 plan_.ptransform_batch(*pool, spectra, false, chunk_size, false);
14247 else
14248 plan_.transform_batch(spectra, false, false);
14249
14250 pointwise_multiply_batch(spectra, parallelize_fft ? pool : nullptr, chunk_size);
14251
14253 plan_.pinverse_transform_real_batch(*pool, spectra, chunk_size) :
14255
14256 for (size_t item = 0; item < active_channels.size(); ++item)
14257 {
14258 const size_t channel = active_channels[item];
14259 emitted(channel) = Array<Real>::create(active_lengths[item]);
14260 for (size_t i = 0; i < active_lengths[item]; ++i)
14261 {
14262 const Real overlap = i < overlaps_[channel].size() ? overlaps_[channel][i] : Real(0);
14263 emitted(channel)(i) = convolved[item][i] + overlap;
14264 }
14265
14266 if (not overlaps_[channel].is_empty())
14267 {
14269 for (size_t i = 0; i < overlaps_[channel].size(); ++i)
14270 {
14271 const size_t src = active_lengths[item] + i;
14272 const Real from_current = src < convolved[item].size() ? convolved[item][src] : Real(0);
14273 const Real from_previous = src < overlaps_[channel].size() ? overlaps_[channel][src] : Real(0);
14275 }
14276 overlaps_(channel) = next_overlap;
14277 }
14278
14279 has_pending_tail_(channel) = true;
14280 }
14281
14282 return emitted;
14283 }
14284
14287 ThreadPool *pool,
14288 const size_t chunk_size) const
14289 {
14290 if (signals.is_empty())
14291 return {};
14292
14294 for (size_t channel = 0; channel < signals.size(); ++channel)
14295 {
14296 if (signals[channel].is_empty())
14297 continue;
14298 output(channel) =
14299 Array<Real>::create(signals[channel].size() + kernel_.size() - 1);
14300 for (size_t i = 0; i < output[channel].size(); ++i)
14301 output(channel)(i) = Real(0);
14302 }
14303
14304 const size_t max_length = max_batch_length(signals);
14305 for (size_t offset = 0; offset < max_length; offset += block_size_)
14306 {
14310 active_lengths.reserve(signals.size());
14311 for (size_t channel = 0; channel < signals.size(); ++channel)
14312 {
14313 if (offset >= signals[channel].size())
14314 continue;
14315
14316 const size_t length =
14317 std::min(block_size_, signals[channel].size() - offset);
14318 active_channels.append(channel);
14319 active_lengths.append(length);
14320 }
14321
14322 if (active_channels.is_empty())
14323 continue;
14324
14325 const bool parallelize_fft =
14330 offset);
14331 if (parallelize_fft)
14332 plan_.ptransform_batch(*pool, spectra, false, chunk_size, false);
14333 else
14334 plan_.transform_batch(spectra, false, false);
14335
14337 parallelize_fft ? pool : nullptr,
14338 chunk_size);
14339
14340 const Array<Array<Real>> blocks = parallelize_fft ?
14341 plan_.pinverse_transform_real_batch(*pool, spectra, chunk_size) :
14343
14344 for (size_t item = 0; item < active_channels.size(); ++item)
14345 {
14346 const size_t channel = active_channels[item];
14347 const size_t valid = active_lengths[item] + kernel_.size() - 1;
14348 for (size_t i = 0; i < valid; ++i)
14349 output(channel)(offset + i) += blocks[item][i];
14350 }
14351 }
14352
14353 return output;
14354 }
14355
14356 public:
14357 explicit OverlapAddBank(const size_t num_channels,
14358 const Array<Real> & kernel,
14359 const size_t block_size = 0)
14360 : kernel_(kernel)
14361 {
14363 << "FFT::OverlapAddBank: at least one channel is required";
14365 << "FFT::OverlapAddBank: kernel must be non-empty";
14366
14369 << "FFT::OverlapAddBank: block size must be positive";
14371 > std::numeric_limits<size_t>::max()
14372 - kernel_.size() + 1)
14373 << "FFT::OverlapAddBank: FFT size overflow";
14374
14376 plan_ = Plan(fft_size_);
14377
14378 overlaps_.reserve(num_channels);
14380 for (size_t channel = 0; channel < num_channels; ++channel)
14381 {
14384 }
14386
14388 for (size_t i = 0; i < fft_size_; ++i)
14389 padded_kernel(i) = Complex(Real(0), Real(0));
14390 for (size_t i = 0; i < kernel_.size(); ++i)
14391 padded_kernel(i) = Complex(kernel_[i], Real(0));
14392
14395 }
14396
14398 {
14399 return overlaps_.size();
14400 }
14401
14403 [[nodiscard]] size_t fft_size() const noexcept { return fft_size_; }
14404 [[nodiscard]] size_t overlap_size() const noexcept { return kernel_.size() - 1; }
14406
14407 void reset()
14408 {
14410 }
14411
14414 {
14415 validate_channel_count(signals.size(), "FFT::OverlapAddBank::convolve");
14416 return convolve_impl(signals, nullptr, 0);
14417 }
14418
14421 const Array<Array<Real>> & signals,
14422 const size_t chunk_size = 0) const
14423 {
14424 validate_channel_count(signals.size(), "FFT::OverlapAddBank::pconvolve");
14425 return convolve_impl(signals, &pool, chunk_size);
14426 }
14427
14430 {
14431 validate_channel_count(block.size(), "FFT::OverlapAddBank::process_block");
14432 const size_t max_length = max_batch_length(block);
14433 Array<Array<Real>> emitted(block.size(), Array<Real>());
14434 for (size_t channel = 0; channel < emitted.size(); ++channel)
14435 emitted(channel).reserve(block[channel].size());
14436 for (size_t offset = 0; offset < max_length; offset += block_size_)
14437 {
14438 Array<Array<Real>> chunk_batch(block.size(), Array<Real>());
14439 bool any_pending = false;
14440 for (size_t channel = 0; channel < block.size(); ++channel)
14441 {
14442 if (offset >= block[channel].size())
14443 continue;
14444 const size_t length =
14445 std::min(block_size_, block[channel].size() - offset);
14446 chunk_batch(channel) = slice_chunk(block[channel], offset, length);
14447 any_pending = true;
14448 }
14449
14450 if (not any_pending)
14451 break;
14452
14453 const Array<Array<Real>> partial =
14455 for (size_t channel = 0; channel < emitted.size(); ++channel)
14456 for (size_t i = 0; i < partial[channel].size(); ++i)
14457 emitted(channel).append(partial[channel][i]);
14458 }
14459
14460 return emitted;
14461 }
14462
14465 const Array<Array<Real>> & block,
14466 const size_t chunk_size = 0)
14467 {
14468 validate_channel_count(block.size(), "FFT::OverlapAddBank::pprocess_block");
14469 const size_t max_length = max_batch_length(block);
14470 Array<Array<Real>> emitted(block.size(), Array<Real>());
14471 for (size_t channel = 0; channel < emitted.size(); ++channel)
14472 emitted(channel).reserve(block[channel].size());
14473 for (size_t offset = 0; offset < max_length; offset += block_size_)
14474 {
14475 Array<Array<Real>> chunk_batch(block.size(), Array<Real>());
14476 bool any_pending = false;
14477 for (size_t channel = 0; channel < block.size(); ++channel)
14478 {
14479 if (offset >= block[channel].size())
14480 continue;
14481 const size_t length =
14482 std::min(block_size_, block[channel].size() - offset);
14483 chunk_batch(channel) = slice_chunk(block[channel], offset, length);
14484 any_pending = true;
14485 }
14486
14487 if (not any_pending)
14488 break;
14489
14490 const Array<Array<Real>> partial =
14491 process_chunk_batch_impl(chunk_batch, &pool, chunk_size);
14492 for (size_t channel = 0; channel < emitted.size(); ++channel)
14493 for (size_t i = 0; i < partial[channel].size(); ++i)
14494 emitted(channel).append(partial[channel][i]);
14495 }
14496
14497 return emitted;
14498 }
14499
14502 {
14503 Array<Array<Real>> tail(overlaps_.size(), Array<Real>());
14504 for (size_t channel = 0; channel < overlaps_.size(); ++channel)
14505 {
14506 if (has_pending_tail_[channel] and not overlaps_[channel].is_empty())
14507 tail(channel) = overlaps_[channel];
14508 clear_channel_overlap(channel);
14509 }
14510 return tail;
14511 }
14512
14514 pflush(ThreadPool & pool, const size_t chunk_size = 0)
14515 {
14516 Array<Array<Real>> tail(overlaps_.size(), Array<Real>());
14517 auto flush_one = [this, &tail](const size_t channel)
14518 {
14519 if (has_pending_tail_[channel] and not overlaps_[channel].is_empty())
14520 tail(channel) = overlaps_[channel];
14521 clear_channel_overlap(channel);
14522 };
14523
14524 if (pool.num_threads() > 1 and overlaps_.size() > 1)
14525 parallel_for_index(pool, 0, overlaps_.size(), flush_one, chunk_size);
14526 else
14527 for (size_t channel = 0; channel < overlaps_.size(); ++channel)
14528 flush_one(channel);
14529
14530 return tail;
14531 }
14532 };
14533
14540 {
14542 size_t block_size_ = 0;
14543 size_t fft_size_ = 0;
14550
14554 [[nodiscard]] static size_t
14556 {
14558 << "FFT::OverlapSave: kernel size must be positive";
14559 return next_power_of_two(std::max(size_t(2), kernel_size * 2))
14560 - kernel_size + 1;
14561 }
14562
14564 void
14566 {
14567 for (size_t i = 0; i < history_.size(); ++i)
14568 history_(i) = Real(0);
14569 }
14570
14575 void
14577 {
14578 if (history_.is_empty())
14579 return;
14580
14581 if (padded_chunk.size() >= history_.size())
14582 {
14583 const size_t start = padded_chunk.size() - history_.size();
14584 for (size_t i = 0; i < history_.size(); ++i)
14585 history_(i) = padded_chunk[start + i];
14586 return;
14587 }
14588
14590 const size_t preserved = history_.size() - padded_chunk.size();
14591 for (size_t i = 0; i < preserved; ++i)
14593 for (size_t i = 0; i < padded_chunk.size(); ++i)
14596 }
14597
14603 {
14605 << "FFT::OverlapSave::process_block: block size " << chunk.size()
14606 << " exceeds configured block size " << block_size_;
14607
14609 for (size_t i = 0; i < block_size_; ++i)
14610 padded_chunk(i) = i < chunk.size() ? chunk[i] : Real(0);
14611
14613 for (size_t i = 0; i < fft_size_; ++i)
14614 segment(i) = Complex(Real(0), Real(0));
14615 for (size_t i = 0; i < history_.size(); ++i)
14616 segment(i) = Complex(history_[i], Real(0));
14617 for (size_t i = 0; i < padded_chunk.size(); ++i)
14618 segment(history_.size() + i) = Complex(padded_chunk[i], Real(0));
14619 return segment;
14620 }
14621
14624 padded_chunk_copy(const Array<Real> & chunk) const
14625 {
14627 for (size_t i = 0; i < block_size_; ++i)
14628 padded_chunk(i) = i < chunk.size() ? chunk[i] : Real(0);
14629 return padded_chunk;
14630 }
14631
14642 {
14643 if (chunk.is_empty())
14644 return {};
14645
14648 for (size_t i = 0; i < fft_size_; ++i)
14649 segment(i) = Complex(Real(0), Real(0));
14650 for (size_t i = 0; i < history_.size(); ++i)
14651 segment(i) = Complex(history_[i], Real(0));
14652 for (size_t i = 0; i < padded_chunk.size(); ++i)
14653 segment(history_.size() + i) = Complex(padded_chunk[i], Real(0));
14654
14655 plan_.transform(segment, false);
14656 for (size_t i = 0; i < fft_size_; ++i)
14657 segment(i) *= kernel_spectrum_[i];
14659
14663 }
14664
14665 public:
14667 const size_t block_size = 0)
14668 : kernel_(kernel)
14669 {
14671 << "FFT::OverlapSave: kernel must be non-empty";
14672
14675 << "FFT::OverlapSave: block size must be positive";
14677 > std::numeric_limits<size_t>::max()
14678 - kernel_.size() + 1)
14679 << "FFT::OverlapSave: FFT size overflow";
14680
14682 plan_ = Plan(fft_size_);
14684 clear_history();
14685
14687 for (size_t i = 0; i < fft_size_; ++i)
14688 padded_kernel(i) = Complex(Real(0), Real(0));
14689 for (size_t i = 0; i < kernel_.size(); ++i)
14690 padded_kernel(i) = Complex(kernel_[i], Real(0));
14693 }
14694
14697
14699 [[nodiscard]] size_t fft_size() const noexcept { return fft_size_; }
14700
14703
14706
14708 void reset()
14709 {
14710 clear_history();
14711 pending_ = {};
14714 }
14715
14726 {
14727 if (block.is_empty())
14728 return {};
14729
14730 total_input_size_ += block.size();
14731 append_all(pending_, block);
14732
14734 while (pending_.size() >= block_size_)
14735 {
14736 const Array<Real> chunk = slice_copy(pending_, 0, block_size_);
14741 }
14742
14743 return emitted;
14744 }
14745
14755 {
14756 if (total_input_size_ == 0)
14757 {
14758 reset();
14759 return {};
14760 }
14761
14762 const size_t target_output_size = total_input_size_ + kernel_.size() - 1;
14763 const size_t emitted_before = emitted_output_size_;
14765
14766 if (not pending_.is_empty())
14767 {
14769 pending_ = {};
14770 }
14771
14773 {
14775 for (size_t i = 0; i < block_size_; ++i)
14776 zeros(i) = Real(0);
14778 }
14779
14782 reset();
14783 return output;
14784 }
14785
14788 convolve(const Array<Real> & signal)
14789 {
14790 reset();
14793 return output;
14794 }
14795
14797 template <typename Container>
14801 {
14802 return process_block(build_real_input(block));
14803 }
14804
14806 template <typename Container>
14809 convolve(const Container & signal)
14810 {
14811 return convolve(build_real_input(signal));
14812 }
14813 };
14814
14821 {
14824 size_t fft_size_ = 0;
14828 size_t head_ = 0;
14833
14837 [[nodiscard]] static size_t
14839 {
14841 << "FFT::PartitionedConvolver: kernel size must be positive";
14842 return next_power_of_two(std::min(kernel_size, size_t(256)));
14843 }
14844
14846 void
14848 {
14849 for (size_t i = 0; i < input_history_.size(); ++i)
14850 for (size_t j = 0; j < input_history_[i].size(); ++j)
14851 input_history_(i)(j) = Complex(Real(0), Real(0));
14852 for (size_t i = 0; i < overlap_.size(); ++i)
14853 overlap_(i) = Real(0);
14854 pending_ = {};
14855 head_ = 0;
14858 }
14859
14863 {
14865 for (size_t i = 0; i < fft_size_; ++i)
14866 spectrum(i) = Complex(Real(0), Real(0));
14867 return spectrum;
14868 }
14869
14872 padded_partition(const Array<Real> & chunk) const
14873 {
14875 for (size_t i = 0; i < partition_size_; ++i)
14876 padded(i) = i < chunk.size() ? chunk[i] : Real(0);
14877 return padded;
14878 }
14879
14889 {
14890 if (chunk.is_empty())
14891 return {};
14892
14893 const Array<Real> padded = padded_partition(chunk);
14895 for (size_t i = 0; i < padded.size(); ++i)
14896 input(i) = Complex(padded[i], Real(0));
14897 plan_.transform(input, false);
14899
14901 for (size_t part = 0; part < kernel_partitions_.size(); ++part)
14902 {
14903 const size_t history_index =
14904 (head_ + kernel_partitions_.size() - part) % kernel_partitions_.size();
14905 for (size_t bin = 0; bin < fft_size_; ++bin)
14908 }
14909
14912 for (size_t i = 0; i < partition_size_; ++i)
14913 emitted(i) = convolved[i] + overlap_[i];
14914 for (size_t i = 0; i < partition_size_; ++i)
14916
14917 head_ = (head_ + 1) % kernel_partitions_.size();
14919 return emitted;
14920 }
14921
14922 public:
14924 const size_t partition_size = 0)
14925 : kernel_(kernel)
14926 {
14928 << "FFT::PartitionedConvolver: kernel must be non-empty";
14929
14933 << "FFT::PartitionedConvolver: partition size must be positive";
14934
14936 plan_ = Plan(fft_size_);
14938 for (size_t i = 0; i < overlap_.size(); ++i)
14939 overlap_(i) = Real(0);
14940
14941 const size_t num_partitions =
14943 kernel_partitions_.reserve(num_partitions);
14944 input_history_.reserve(num_partitions);
14945 for (size_t part = 0; part < num_partitions; ++part)
14946 {
14948 const size_t offset = part * partition_size_;
14949 const size_t length =
14950 std::min(partition_size_, kernel_.size() - offset);
14951 for (size_t i = 0; i < length; ++i)
14952 partition(i) = Complex(kernel_[offset + i], Real(0));
14953 plan_.transform(partition, false);
14955 input_history_.append(zero_spectrum());
14956 }
14957
14958 clear_state();
14959 }
14960
14962 {
14963 return partition_size_;
14964 }
14965
14967 {
14968 return fft_size_;
14969 }
14970
14972 {
14973 return kernel_;
14974 }
14975
14976 void reset()
14977 {
14978 clear_state();
14979 }
14980
14983 {
14984 if (block.is_empty())
14985 return {};
14986
14987 total_input_size_ += block.size();
14988 append_all(pending_, block);
14989
14991 while (pending_.size() >= partition_size_)
14992 {
14998 }
14999
15000 return emitted;
15001 }
15002
15005 {
15006 if (total_input_size_ == 0)
15007 {
15008 reset();
15009 return {};
15010 }
15011
15012 const size_t target_output_size = total_input_size_ + kernel_.size() - 1;
15013 const size_t emitted_before = emitted_output_size_;
15015
15016 if (not pending_.is_empty())
15017 {
15019 pending_ = {};
15020 }
15021
15023 {
15025 for (size_t i = 0; i < partition_size_; ++i)
15026 zeros(i) = Real(0);
15028 }
15029
15032 reset();
15033 return output;
15034 }
15035
15037 convolve(const Array<Real> & signal)
15038 {
15039 reset();
15042 return output;
15043 }
15044
15045 template <typename Container>
15049 {
15050 return process_block(build_real_input(block));
15051 }
15052
15053 template <typename Container>
15056 convolve(const Container & signal)
15057 {
15058 return convolve(build_real_input(signal));
15059 }
15060 };
15061
15063 [[nodiscard]] static Array<Real>
15065 const Array<Real> & kernel,
15066 const size_t block_size = 0)
15067 {
15068 if (signal.is_empty() or kernel.is_empty())
15069 return {};
15070 return OverlapAdd(kernel, block_size).convolve(signal);
15071 }
15072
15075 [[nodiscard]] static Array<Real>
15077 const Array<Real> & signal,
15078 const Array<Real> & kernel,
15079 const size_t block_size = 0,
15080 const size_t chunk_size = 0)
15081 {
15082 if (signal.is_empty() or kernel.is_empty())
15083 return {};
15084 return OverlapAdd(kernel, block_size).pconvolve(pool, signal, chunk_size);
15085 }
15086
15088 [[nodiscard]] static Array<Array<Real>>
15090 const Array<Real> & kernel,
15091 const size_t block_size = 0)
15092 {
15093 if (signals.is_empty() or kernel.is_empty())
15094 return {};
15095 return OverlapAddBank(signals.size(), kernel, block_size).convolve(signals);
15096 }
15097
15099 [[nodiscard]] static Array<Array<Real>>
15101 const Array<Array<Real>> & signals,
15102 const Array<Real> & kernel,
15103 const size_t block_size = 0,
15104 const size_t chunk_size = 0)
15105 {
15106 if (signals.is_empty() or kernel.is_empty())
15107 return {};
15108 return OverlapAddBank(signals.size(), kernel, block_size)
15109 .pconvolve(pool, signals, chunk_size);
15110 }
15111
15113 [[nodiscard]] static Array<Real>
15115 const Array<Real> & kernel,
15116 const size_t block_size = 0)
15117 {
15118 if (signal.is_empty() or kernel.is_empty())
15119 return {};
15120 return OverlapSave(kernel, block_size).convolve(signal);
15121 }
15122
15124 [[nodiscard]] static Array<Real>
15126 const Array<Real> & kernel,
15127 const size_t partition_size = 0)
15128 {
15129 if (signal.is_empty() or kernel.is_empty())
15130 return {};
15131 return PartitionedConvolver(kernel, partition_size).convolve(signal);
15132 }
15133
15135 template <typename Container1, typename Container2>
15137 [[nodiscard]] static Array<Real>
15139 const Container2 & kernel,
15140 const size_t block_size = 0)
15141 {
15143 build_real_input(kernel),
15144 block_size);
15145 }
15146
15148 template <typename Container1, typename Container2>
15150 [[nodiscard]] static Array<Real>
15152 const Container1 & signal,
15153 const Container2 & kernel,
15154 const size_t block_size = 0,
15155 const size_t chunk_size = 0)
15156 {
15157 return poverlap_add_convolution(pool,
15158 build_real_input(signal),
15159 build_real_input(kernel),
15160 block_size,
15161 chunk_size);
15162 }
15163
15165 template <typename Container1, typename Container2>
15167 [[nodiscard]] static Array<Real>
15169 const Container2 & kernel,
15170 const size_t block_size = 0)
15171 {
15173 build_real_input(kernel),
15174 block_size);
15175 }
15176
15178 template <typename Container1, typename Container2>
15180 [[nodiscard]] static Array<Real>
15182 const Container2 & kernel,
15183 const size_t partition_size = 0)
15184 {
15186 build_real_input(kernel),
15187 partition_size);
15188 }
15189
15209 [[nodiscard]] static Array<Complex>
15211 {
15212 return multiply_complex_impl(a, b);
15213 }
15214
15216 [[nodiscard]] static Array<Complex>
15218 const size_t chunk_size = 0)
15219 {
15220 return multiply_complex_impl(a, b, &pool, chunk_size);
15221 }
15222
15224 template <typename Container1, typename Container2>
15226 [[nodiscard]] static Array<Complex>
15227 multiply(const Container1 & a, const Container2 & b)
15228 {
15230 }
15231
15233 template <typename Container1, typename Container2>
15235 [[nodiscard]] static Array<Complex>
15236 pmultiply(ThreadPool & pool, const Container1 & a, const Container2 & b,
15237 const size_t chunk_size = 0)
15238 {
15239 return pmultiply(pool, build_complex_input(a), build_complex_input(b), chunk_size);
15240 }
15241
15255 [[nodiscard]] static Array<Real>
15256 multiply(const Array<Real> & a, const Array<Real> & b)
15257 {
15258 return multiply_real_optimized(a, b);
15259 }
15260
15262 [[nodiscard]] static Array<Real>
15263 pmultiply(ThreadPool & pool, const Array<Real> & a, const Array<Real> & b,
15264 const size_t chunk_size = 0)
15265 {
15266 return multiply_real_optimized(a, b, &pool, chunk_size);
15267 }
15268
15270 template <typename Container1, typename Container2>
15272 [[nodiscard]] static Array<Real>
15273 multiply(const Container1 & a, const Container2 & b)
15274 {
15276 }
15277
15279 template <typename Container1, typename Container2>
15281 [[nodiscard]] static Array<Real>
15282 pmultiply(ThreadPool & pool, const Container1 & a, const Container2 & b,
15283 const size_t chunk_size = 0)
15284 {
15285 return pmultiply(pool, build_real_input(a), build_real_input(b), chunk_size);
15286 }
15287 };
15288} // namespace Aleph
15289
15290# endif // FFT_H
Exception handling system with formatted messages for Aleph-w.
#define ah_out_of_range_error_if(C)
Throws std::out_of_range if condition holds.
Definition ah-errors.H:584
#define ah_domain_error_if(C)
Throws std::domain_error if condition holds.
Definition ah-errors.H:527
#define ah_runtime_error_if(C)
Throws std::runtime_error if condition holds.
Definition ah-errors.H:271
#define ah_invalid_argument_if(C)
Throws std::invalid_argument if condition holds.
Definition ah-errors.H:644
long double w
Definition btreepic.C:153
size_t size_t int32_t value
Definition ca-c-api.h:116
size_t row
Definition ca-c-api.h:115
size_t * rows
Definition ca-c-api.h:112
size_t size_t col
Definition ca-c-api.h:116
size_t cols
Definition ca-c-api.h:105
Simple dynamic array with automatic resizing and functional operations.
Definition tpl_array.H:138
static Array create(size_t n)
Create an array with n logical elements.
Definition tpl_array.H:196
constexpr size_t size() const noexcept
Return the number of elements stored in the stack.
Definition tpl_array.H:365
void empty() noexcept
Empties the container.
Definition tpl_array.H:341
constexpr bool is_empty() const noexcept
Checks if the container is empty.
Definition tpl_array.H:359
T & append(const T &data)
Append a copy of data
Definition tpl_array.H:250
void reserve(size_t cap)
Reserves cap cells into the array.
Definition tpl_array.H:320
Stateful multichannel ISTFT processor with one synthesizer per channel.
Definition fft.H:11031
ISTFTProcessor & channel(const size_t index)
Definition fft.H:11180
void validate_batch_size(const size_t size, const char *ctx) const
Definition fft.H:11046
BatchedISTFTProcessor(const size_t num_channels, const size_t fft_size, const size_t frame_size, const ISTFTOptions &options)
Definition fft.H:11116
BatchedISTFTProcessor(const size_t num_channels, const size_t fft_size, const Array< Real > &window, const ISTFTOptions &options)
Definition fft.H:11091
size_t num_channels() const noexcept
Definition fft.H:11174
static ISTFTOptions channel_options(const ISTFTOptions &options, const Array< size_t > &signal_lengths, const size_t index)
Definition fft.H:11035
BatchedISTFTProcessor(const size_t num_channels, const size_t fft_size, const Array< Real > &window, const ISTFTOptions &options, const Array< size_t > &signal_lengths)
Definition fft.H:11103
and Is_Real_Container< SynthesisContainer > BatchedISTFTProcessor(const size_t num_channels, const size_t fft_size, const AnalysisContainer &analysis_window, const SynthesisContainer &synthesis_window, const ISTFTOptions &options, const Array< size_t > &signal_lengths)
Definition fft.H:11160
Array< Array< Real > > pprocess_block(ThreadPool &pool, const Array< Array< Array< Complex > > > &block, const size_t chunk_size=0)
Definition fft.H:11215
const ISTFTProcessor & channel(const size_t index) const
Definition fft.H:11189
BatchedISTFTProcessor(const size_t num_channels, const size_t fft_size, const size_t frame_size, const ISTFTOptions &options, const Array< size_t > &signal_lengths)
Definition fft.H:11128
Array< Array< Real > > flush()
Definition fft.H:11237
and Is_Real_Container< SynthesisContainer > BatchedISTFTProcessor(const size_t num_channels, const size_t fft_size, const AnalysisContainer &analysis_window, const SynthesisContainer &synthesis_window, const ISTFTOptions &options)
Definition fft.H:11144
BatchedISTFTProcessor(const size_t num_channels, const size_t fft_size, const Array< Real > &analysis_window, const Array< Real > &synthesis_window, const ISTFTOptions &options)
Definition fft.H:11054
Array< Array< Real > > pflush(ThreadPool &pool, const size_t chunk_size=0)
Definition fft.H:11246
Array< ISTFTProcessor > processors_
Definition fft.H:11032
Array< Array< Real > > process_block(const Array< Array< Array< Complex > > > &block)
Definition fft.H:11204
BatchedISTFTProcessor(const size_t num_channels, const size_t fft_size, const Array< Real > &analysis_window, const Array< Real > &synthesis_window, const ISTFTOptions &options, const Array< size_t > &signal_lengths)
Definition fft.H:11067
Stateful multichannel STFT processor with one analyzer per channel.
Definition fft.H:10900
Array< Array< Array< Complex > > > pflush(ThreadPool &pool, const size_t chunk_size=0)
Definition fft.H:11013
BatchedSTFTProcessor(const size_t num_channels, const size_t frame_size, const STFTOptions &options)
Definition fft.H:10923
Array< STFTProcessor > processors_
Definition fft.H:10901
Array< Array< Array< Complex > > > process_block(const Array< Array< Real > > &block)
Definition fft.H:10969
const STFTProcessor & channel(const size_t index) const
Definition fft.H:10954
size_t num_channels() const noexcept
Definition fft.H:10939
Array< Array< Array< Complex > > > flush()
Definition fft.H:11003
STFTProcessor & channel(const size_t index)
Definition fft.H:10945
void validate_batch_size(const size_t size, const char *ctx) const
Definition fft.H:10904
Array< Array< Array< Complex > > > pprocess_block(ThreadPool &pool, const Array< Array< Real > > &block, const size_t chunk_size=0)
Definition fft.H:10980
BatchedSTFTProcessor(const size_t num_channels, const Array< Real > &window, const STFTOptions &options)
Definition fft.H:10912
BatchedSTFTProcessor(const size_t num_channels, const WindowContainer &window, const STFTOptions &options)
Definition fft.H:10931
Stateful ISTFT processor for chunked frame-by-frame synthesis.
Definition fft.H:10516
Array< Real > analysis_window_
Definition fft.H:10517
ISTFTProcessor(const size_t fft_size, const size_t frame_size, const ISTFTOptions &options)
Construct an inverse STFT processor with a Hann window.
Definition fft.H:10749
Array< Real > synthesis_window_
Definition fft.H:10518
bool centered() const noexcept
Definition fft.H:10800
ISTFTOptions options_
Definition fft.H:10519
Array< Real > flush()
Definition fft.H:10852
Array< Real > pprocess_frame(ThreadPool &pool, const Container &spectrum, const size_t chunk_size=0)
Definition fft.H:10890
Array< Real > pprocess_block(ThreadPool &pool, const Array< Array< Complex > > &spectrogram_block, const size_t chunk_size=0)
Definition fft.H:10840
size_t fft_size() const noexcept
Definition fft.H:10790
Array< Real > drain_ready_samples(const bool final_flush)
Definition fft.H:10661
size_t hop_size() const noexcept
Definition fft.H:10795
Array< Real > process_frame_impl(const Array< Complex > &spectrum, ThreadPool *pool, const size_t chunk_size)
Definition fft.H:10681
Array< Real > process_frame(const Array< Complex > &spectrum)
Definition fft.H:10817
and Is_Real_Container< SynthesisContainer > ISTFTProcessor(const size_t fft_size, const AnalysisContainer &analysis_window, const SynthesisContainer &synthesis_window, const ISTFTOptions &options)
Construct an inverse STFT processor from any real containers.
Definition fft.H:10770
Array< Real > emit_samples(const Array< Real > &normalized, const bool final_flush)
Definition fft.H:10626
Array< Real > pprocess_frame(ThreadPool &pool, const Array< Complex > &spectrum, const size_t chunk_size=0)
Definition fft.H:10823
void require_configured(const char *ctx) const
Definition fft.H:10534
Array< Real > pflush(ThreadPool &, const size_t=0)
Definition fft.H:10873
Array< Real > process_block(const Array< Array< Complex > > &spectrogram_block)
Definition fft.H:10831
Array< Real > pending_norm_
Definition fft.H:10524
Array< Real > pending_output_
Definition fft.H:10523
void accumulate_frame(const Array< Real > &frame)
Definition fft.H:10569
ISTFTProcessor(const size_t fft_size, const Array< Real > &window, const ISTFTOptions &options)
Construct an inverse STFT processor with a single window.
Definition fft.H:10737
Array< Real > process_frame(const Container &spectrum)
Definition fft.H:10882
ISTFTProcessor(const size_t fft_size, const Array< Real > &analysis_window, const Array< Real > &synthesis_window, const ISTFTOptions &options)
Construct an inverse STFT processor with custom windows.
Definition fft.H:10711
ISTFTProcessor()=default
Default constructor for an unconfigured inverse processor.
void ensure_pending_size(const size_t size)
Definition fft.H:10554
bool finalized() const noexcept
Definition fft.H:10805
Array< Real > normalize_prefix(const size_t count)
Definition fft.H:10594
size_t frame_size() const noexcept
Definition fft.H:10780
bool configured() const noexcept
Definition fft.H:10785
Stateful bank of direct-form II transposed IIR filters.
Definition fft.H:11734
LFilterBank(const size_t num_channels, const IIRCoefficients &coeffs)
Definition fft.H:11758
void validate_channel_count(const size_t count, const char *ctx) const
Rationale: Internal safety guard.
Definition fft.H:11739
LFilter & channel(const size_t index)
Definition fft.H:11784
size_t num_channels() const noexcept
Definition fft.H:11778
Array< Real > filter_channel(const size_t index, const Array< Real > &signal)
Definition fft.H:11808
Array< LFilter > filters_
Definition fft.H:11735
and Is_Real_Container< DenContainer > LFilterBank(const size_t num_channels, const NumContainer &numerator, const DenContainer &denominator)
Definition fft.H:11770
LFilterBank(const size_t num_channels, const Array< Real > &numerator, const Array< Real > &denominator)
Definition fft.H:11747
Array< Array< Real > > pfilter(ThreadPool &pool, const Array< Array< Real > > &signals, const size_t chunk_size=0)
Definition fft.H:11834
Array< Array< Real > > filter(const Array< Array< Real > > &signals)
Definition fft.H:11824
LFilterBank(const size_t num_channels, const BiquadSection &section)
Definition fft.H:11763
const LFilter & channel(const size_t index) const
Definition fft.H:11793
Array< Real > filter_channel(const size_t index, const Container &signal)
Definition fft.H:11817
Stateful direct-form II transposed IIR filter.
Definition fft.H:11479
void initialize_state()
Definition fft.H:11492
LFilter(const IIRCoefficients &coeffs)
Construct a linear filter from IIRCoefficients.
Definition fft.H:11523
bool configured() const noexcept
Returns whether the filter has been initialized with coefficients.
Definition fft.H:11554
and Is_Real_Container< DenContainer > LFilter(const NumContainer &numerator, const DenContainer &denominator)
Construct a linear filter from any real containers.
Definition fft.H:11542
void require_configured(const char *ctx) const
Definition fft.H:11485
const Array< Real > & state() const noexcept
Returns the current internal delay-line state.
Definition fft.H:11566
const IIRCoefficients & coefficients() const noexcept
Returns the active normalized filter coefficients.
Definition fft.H:11560
Array< Real > state_
Definition fft.H:11481
void reset()
Resets the delay-line state to zero.
Definition fft.H:11572
LFilter(const Array< Real > &numerator, const Array< Real > &denominator)
Construct a linear filter from numerator and denominator arrays.
Definition fft.H:11511
IIRCoefficients coeffs_
Definition fft.H:11480
Array< Real > filter(const Container &signal)
Processes a real-valued container through the stateful filter.
Definition fft.H:11606
void set_state(const Array< Real > &new_state)
Manually overrides the delay-line state.
Definition fft.H:11580
LFilter(const BiquadSection &section)
Construct a linear filter from a single BiquadSection.
Definition fft.H:11529
size_t order() const noexcept
Returns the filter order (number of feedback coefficients - 1).
Definition fft.H:11548
LFilter()=default
Default constructor for an unconfigured LFilter.
Array< Real > filter(const Array< Real > &signal)
Processes a signal block through the stateful filter.
Definition fft.H:11591
Multichannel overlap-add convolver with one shared kernel FFT.
Definition fft.H:14080
Array< bool > has_pending_tail_
Definition fft.H:14087
void validate_channel_count(const size_t count, const char *ctx) const
Rationale: Internal safety guard.
Definition fft.H:14102
Array< Array< Real > > convolve(const Array< Array< Real > > &signals) const
Definition fft.H:14413
const Array< Real > & kernel() const noexcept
Definition fft.H:14405
Array< Array< Real > > convolve_impl(const Array< Array< Real > > &signals, ThreadPool *pool, const size_t chunk_size) const
Definition fft.H:14286
static Array< Real > slice_chunk(const Array< Real > &input, const size_t offset, const size_t length)
Returns a contiguous slice of input.
Definition fft.H:14121
Array< Array< Real > > pflush(ThreadPool &pool, const size_t chunk_size=0)
Definition fft.H:14514
Array< Array< Real > > process_chunk_batch_impl(const Array< Array< Real > > &block, ThreadPool *pool, const size_t chunk_size)
Definition fft.H:14218
Array< Array< Real > > process_block(const Array< Array< Real > > &block)
Definition fft.H:14429
Array< Array< Real > > overlaps_
Definition fft.H:14086
static size_t max_batch_length(const Array< Array< Real > > &signals) noexcept
Returns the maximum signal length in a batch.
Definition fft.H:14111
size_t block_size() const noexcept
Definition fft.H:14402
size_t overlap_size() const noexcept
Definition fft.H:14404
Array< Array< Real > > pconvolve(ThreadPool &pool, const Array< Array< Real > > &signals, const size_t chunk_size=0) const
Definition fft.H:14420
Array< Complex > kernel_spectrum_
Definition fft.H:14085
void pointwise_multiply_batch(Array< Array< Complex > > &spectra, ThreadPool *pool, const size_t chunk_size) const
Rationale: Parallel pointwise multiplication of multiple signal spectra with the shared kernel spectr...
Definition fft.H:14184
Array< Array< Real > > pprocess_block(ThreadPool &pool, const Array< Array< Real > > &block, const size_t chunk_size=0)
Definition fft.H:14464
void clear_channel_overlap(const size_t channel)
Rationale: Resets the overlap buffer for a single channel.
Definition fft.H:14203
static size_t default_block_size(const size_t kernel_size)
Rationale: Selects a block size that is at least as large as the kernel to ensure efficient FFT proce...
Definition fft.H:14093
Array< Array< Complex > > build_signal_batch(const Array< Array< Real > > &source, const Array< size_t > &channels, const Array< size_t > &lengths, const size_t offset=0) const
implementation helper for preparing a subset of signal blocks.
Definition fft.H:14167
OverlapAddBank(const size_t num_channels, const Array< Real > &kernel, const size_t block_size=0)
Definition fft.H:14357
size_t fft_size() const noexcept
Definition fft.H:14403
size_t num_channels() const noexcept
Definition fft.H:14397
Array< Real > kernel_
Definition fft.H:14081
Array< Complex > build_signal_block(const Array< Real > &signal, const size_t offset=0, const size_t length=std::numeric_limits< size_t >::max()) const
implementation helper for preparing a signal block for FFT.
Definition fft.H:14134
Array< Array< Complex > > build_signal_batch(const Array< Array< Real > > &block) const
implementation helper for preparing a batch of signal blocks.
Definition fft.H:14157
Array< Array< Real > > flush()
Definition fft.H:14501
Reusable overlap-add convolver for long real sequences.
Definition fft.H:13758
Array< Real > convolve(const Container &signal) const
Definition fft.H:14041
void clear_overlap()
Rationale: Resets the overlap buffer between independent convolution runs.
Definition fft.H:13815
Array< Real > convolve_impl(const Array< Real > &signal, ThreadPool *pool, const size_t chunk_size) const
implementation of one-shot linear convolution.
Definition fft.H:13880
Array< Complex > kernel_spectrum_
Definition fft.H:13763
Array< Real > process_block(const Array< Real > &block)
Definition fft.H:13976
size_t block_size() const noexcept
Definition fft.H:13952
const Array< Real > & kernel() const noexcept
Definition fft.H:13954
Array< Real > pprocess_block(ThreadPool &pool, const Array< Real > &block, const size_t chunk_size=0)
Definition fft.H:14000
Array< Real > process_chunk_impl(const Array< Real > &block, ThreadPool *pool, const size_t chunk_size)
Rationale: Implements the core Overlap-Add logic for a single block:
Definition fft.H:13830
Array< Real > pprocess_block(ThreadPool &pool, const Container &block, const size_t chunk_size=0)
Definition fft.H:14066
OverlapAdd(const Array< Real > &kernel, const size_t block_size=0)
Definition fft.H:13921
Array< Real > pconvolve(ThreadPool &pool, const Container &signal, const size_t chunk_size=0) const
Definition fft.H:14049
Array< Real > convolve(const Array< Real > &signal) const
Definition fft.H:13963
size_t overlap_size() const noexcept
Definition fft.H:13955
Array< Real > process_block(const Container &block)
Definition fft.H:14058
Array< Real > flush()
Definition fft.H:14025
Array< Real > kernel_
Definition fft.H:13759
size_t fft_size() const noexcept
Definition fft.H:13953
static size_t default_block_size(const size_t kernel_size)
Definition fft.H:13768
Array< Real > pconvolve(ThreadPool &pool, const Array< Real > &signal, const size_t chunk_size=0) const
Definition fft.H:13969
void pointwise_multiply(Array< Complex > &spectrum, ThreadPool *pool, const size_t chunk_size) const
Rationale: Applies the precomputed FIR kernel in the frequency domain via element-wise multiplication...
Definition fft.H:13797
Array< Complex > build_signal_block(const Array< Real > &signal, const size_t offset, const size_t length) const
Rationale: Prepares a chunk of signal for FFT by zero-padding it to the internal fft_size().
Definition fft.H:13779
Array< Real > overlap_
Definition fft.H:13764
Reusable overlap-save convolver for streaming real FIR filtering.
Definition fft.H:14540
Array< Real > history_
Definition fft.H:14546
Array< Real > process_block(const Array< Real > &block)
Processes an input block and returns the corresponding filtered output.
Definition fft.H:14725
void update_history(const Array< Real > &padded_chunk)
Rationale: Maintains the sliding window of historical samples required to correctly compute the linea...
Definition fft.H:14576
size_t fft_size() const noexcept
Returns the FFT size used internally (block_size + kernel_size - 1 padded to power of 2).
Definition fft.H:14699
Array< Real > process_block(const Container &block)
Processes an input block from a real-valued container.
Definition fft.H:14800
void clear_history()
Rationale: Resets the state for a new filtering process.
Definition fft.H:14565
Array< Complex > kernel_spectrum_
Definition fft.H:14545
void reset()
Resets the internal filter state and history.
Definition fft.H:14708
Array< Real > pending_
Definition fft.H:14547
static size_t default_block_size(const size_t kernel_size)
Rationale: Heuristic for choosing a block size that provides a good balance between algorithmic laten...
Definition fft.H:14555
Array< Real > process_chunk_impl(const Array< Real > &chunk)
Rationale: Implements the core Overlap-Save logic for one block:
Definition fft.H:14641
Array< Real > kernel_
Definition fft.H:14541
Array< Complex > build_segment_spectrum(const Array< Real > &chunk) const
Rationale: Prepares a frequency-domain segment by combining historical samples with the current signa...
Definition fft.H:14602
Array< Real > flush()
Flushes all buffered samples and returns the final tail of the convolution.
Definition fft.H:14754
const Array< Real > & kernel() const noexcept
Returns the current FIR kernel.
Definition fft.H:14702
size_t overlap_size() const noexcept
Returns the internal overlap history size.
Definition fft.H:14705
size_t block_size() const noexcept
Returns the processing block size (the hop size).
Definition fft.H:14696
Array< Real > convolve(const Container &signal)
Convolves an input signal from a real-valued container.
Definition fft.H:14809
size_t emitted_output_size_
Definition fft.H:14549
OverlapSave(const Array< Real > &kernel, const size_t block_size=0)
Definition fft.H:14666
Array< Real > convolve(const Array< Real > &signal)
Convolves the input signal with the kernel (one-shot convenience).
Definition fft.H:14788
Array< Real > padded_chunk_copy(const Array< Real > &chunk) const
Internal helper for zero-padding chunks.
Definition fft.H:14624
Uniform partitioned FIR convolver for low-latency streaming.
Definition fft.H:14821
Array< Real > convolve(const Container &signal)
Definition fft.H:15056
Array< Real > process_block(const Container &block)
Definition fft.H:15048
size_t fft_size() const noexcept
Definition fft.H:14966
const Array< Real > & kernel() const noexcept
Definition fft.H:14971
void clear_state()
Rationale: Resets all delay-line history and overlap buffers.
Definition fft.H:14847
PartitionedConvolver(const Array< Real > &kernel, const size_t partition_size=0)
Definition fft.H:14923
Array< Array< Complex > > input_history_
Definition fft.H:14827
Array< Real > padded_partition(const Array< Real > &chunk) const
implementation helper for preparing a padded partition.
Definition fft.H:14872
Array< Real > process_partition_impl(const Array< Real > &chunk)
Rationale: Implements the core frequency-domain partitioned convolution logic:
Definition fft.H:14888
Array< Real > convolve(const Array< Real > &signal)
Definition fft.H:15037
Array< Real > process_block(const Array< Real > &block)
Definition fft.H:14982
static size_t default_partition_size(const size_t kernel_size)
Rationale: Selects a partition size that minimizes latency while maintaining FFT efficiency (capped a...
Definition fft.H:14838
size_t partition_size() const noexcept
Definition fft.H:14961
Array< Complex > zero_spectrum() const
implementation helper for creating zero-filled complex spectra.
Definition fft.H:14862
Array< Array< Complex > > kernel_partitions_
Definition fft.H:14826
Precomputed FFT plan for repeated transforms of the same size.
Definition fft.H:6294
Array< Complex > rfft(const Array< Real > &input) const
Computes the Real-to-Complex FFT (RFFT).
Definition fft.H:7574
std::shared_ptr< const Plan > half_plan_
Definition fft.H:6308
size_t log_n_
Definition fft.H:6305
size_t n_
Definition fft.H:6303
void initialize_mixed_radix_plan()
Definition fft.H:6354
void apply_bit_reversal(Array< Complex > &a) const noexcept
Definition fft.H:6406
Array< Array< Complex > > ptransformed_batch(ThreadPool &pool, const Array< Array< Complex > > &input, const bool invert=false, const size_t chunk_size=0, const bool prefer_simd=true) const
Returns the parallel batch FFT/IFFT as a new array of arrays.
Definition fft.H:7486
Array< Complex > ptransformed(ThreadPool &pool, const Array< Complex > &input, const bool invert=false, const size_t chunk_size=0) const
Parallel transform returning a new array.
Definition fft.H:7396
void transform_batch(Array< Array< Complex > > &batch, const bool invert, const bool prefer_simd=true) const
In-place batch transform for equal-length complex inputs.
Definition fft.H:7415
Strategy strategy_
Definition fft.H:6304
Array< Real > inverse_transform_real(const Array< Complex > &input) const
Returns the IFFT projected back to real values.
Definition fft.H:7325
void initialize_power_of_two_plan()
Definition fft.H:6318
void initialize_bluestein_plan()
Definition fft.H:6366
Plan(const size_t n)
Constructs a plan for transforms of size n.
Definition fft.H:7256
Array< Complex > prfft(ThreadPool &pool, const Array< Real > &input, const size_t chunk_size=0) const
Parallel Real-to-Complex FFT (RFFT).
Definition fft.H:7584
Array< Real > pirfft(ThreadPool &pool, const Array< Complex > &spectrum, const size_t chunk_size=0) const
Parallel Complex-to-Real Inverse FFT (IRFFT).
Definition fft.H:7611
Complex root_for_length(const size_t length, const size_t exponent, const bool invert) const
Definition fft.H:7105
void transform(Array< Complex > &a, const bool invert) const
Computes the FFT or IFFT in-place using precomputed tables.
Definition fft.H:7292
void apply_bluestein_transform(Array< Complex > &a, const bool invert, ThreadPool *pool, const size_t chunk_size) const
Definition fft.H:7173
size_t size() const noexcept
Returns the transform size this plan was built for.
Definition fft.H:7284
void apply_transform(Array< Complex > &a, const bool invert, ThreadPool *pool, const size_t chunk_size, const bool prefer_simd) const
Definition fft.H:7074
bool should_use_neon(ThreadPool *pool, const bool prefer_simd) const noexcept
Definition fft.H:6870
Array< Array< Real > > irfft_batch(const Array< Array< Complex > > &spectra) const
Computes a batch of Complex-to-Real Inverse FFTs.
Definition fft.H:7641
Array< Array< Complex > > prfft_batch(ThreadPool &pool, const Array< Array< Real > > &input, const size_t chunk_size=0) const
Computes a parallel batch of Real-to-Complex FFTs.
Definition fft.H:7631
Array< Complex > bluestein_kernel_forward_
Definition fft.H:6313
void apply_mixed_radix_transform(Array< Complex > &a, const bool invert) const
Definition fft.H:7160
std::shared_ptr< const Plan > bluestein_plan_
Definition fft.H:6315
Array< Real > pinverse_transform_real(ThreadPool &pool, const Array< Complex > &input, const size_t chunk_size=0) const
Parallel IFFT projected back to real values.
Definition fft.H:7350
Array< Complex > bluestein_kernel_inverse_
Definition fft.H:6314
void apply_butterflies(Array< Complex > &a, const bool invert, ThreadPool *pool, const size_t chunk_size, const bool prefer_simd) const
Definition fft.H:6900
Array< Complex > transformed(const Array< Complex > &input, const bool invert=false) const
Computes the FFT or IFFT returning a new array.
Definition fft.H:7304
Array< Complex > inverse_transform(const Array< Complex > &input) const
Returns the inverse transform as a new array.
Definition fft.H:7314
bool automatic_batch_simd_candidate(const size_t batch_size) const noexcept
Definition fft.H:6832
Array< Array< Real > > pinverse_transform_real_batch(ThreadPool &pool, const Array< Array< Complex > > &input, const size_t chunk_size=0, const bool prefer_simd=true) const
Returns the parallel inverse batch transform projected back to real values.
Definition fft.H:7536
bool supports_power_of_two_real_optimization() const noexcept
Definition fft.H:7237
Array< Complex > pinverse_transform(ThreadPool &pool, const Array< Complex > &input, const size_t chunk_size=0) const
Parallel inverse transform returning a new array.
Definition fft.H:7407
size_t bluestein_size_
Definition fft.H:6311
Array< Array< Real > > pirfft_batch(ThreadPool &pool, const Array< Array< Complex > > &spectra, const size_t chunk_size=0) const
Computes a parallel batch of Complex-to-Real Inverse FFTs.
Definition fft.H:7649
Array< Complex > mixed_radix_transform_recursive(const Array< Complex > &input, const size_t factor_index, const size_t current_size, const bool invert) const
Definition fft.H:7119
Array< Array< Complex > > inverse_transform_batch(const Array< Array< Complex > > &input) const
Returns the inverse batch transform as a new array of arrays.
Definition fft.H:7499
bool should_use_avx2(ThreadPool *pool, const bool prefer_simd) const noexcept
Definition fft.H:6841
Array< Complex > twiddles_
Definition fft.H:6307
Array< Complex > roots_
Definition fft.H:6310
void ptransform_batch(ThreadPool &pool, Array< Array< Complex > > &batch, const bool invert, const size_t chunk_size=0, const bool prefer_simd=true) const
Parallel batch transform for equal-length complex inputs.
Definition fft.H:7438
Array< size_t > factors_
Definition fft.H:6309
Array< size_t > bit_rev_
Definition fft.H:6306
Array< Real > irfft(const Array< Complex > &spectrum) const
Computes the Complex-to-Real Inverse FFT (IRFFT).
Definition fft.H:7602
Array< Array< Complex > > transformed_batch(const Array< Array< Complex > > &input, const bool invert=false, const bool prefer_simd=true) const
Returns the batch FFT/IFFT as a new array of arrays.
Definition fft.H:7475
Array< Array< Complex > > pinverse_transform_batch(ThreadPool &pool, const Array< Array< Complex > > &input, const size_t chunk_size=0, const bool prefer_simd=true) const
Returns the parallel inverse batch transform.
Definition fft.H:7506
Array< Array< Complex > > rfft_batch(const Array< Array< Real > > &input) const
Computes a batch of Real-to-Complex FFTs.
Definition fft.H:7623
Array< Array< Real > > inverse_transform_real_batch(const Array< Array< Complex > > &input) const
Returns the inverse batch transform projected back to real values.
Definition fft.H:7518
Array< Complex > bluestein_chirp_
Definition fft.H:6312
void ptransform(ThreadPool &pool, Array< Complex > &a, const bool invert, const size_t chunk_size=0) const
Parallel in-place transform using a thread pool.
Definition fft.H:7387
Plan()=default
Constructs an empty plan (size 0).
Stateful bank of SOS cascades, one per channel.
Definition fft.H:11854
SOSFilter & channel(const size_t index)
Definition fft.H:11890
SOSFilterBank(const size_t num_channels, const SectionsContainer &sections)
Definition fft.H:11879
Array< Array< Real > > filter(const Array< Array< Real > > &signals)
Definition fft.H:11930
Array< Real > filter_channel(const size_t index, const Container &signal)
Definition fft.H:11923
const SOSFilter & channel(const size_t index) const
Definition fft.H:11899
Array< Array< Real > > pfilter(ThreadPool &pool, const Array< Array< Real > > &signals, const size_t chunk_size=0)
Definition fft.H:11940
Array< Real > filter_channel(const size_t index, const Array< Real > &signal)
Definition fft.H:11914
Array< SOSFilter > filters_
Definition fft.H:11855
size_t num_channels() const noexcept
Definition fft.H:11884
void validate_channel_count(const size_t count, const char *ctx) const
Rationale: Internal safety guard.
Definition fft.H:11859
SOSFilterBank(const size_t num_channels, const Array< BiquadSection > &sections)
Definition fft.H:11867
Stateful cascade of second-order sections.
Definition fft.H:11614
void initialize_states()
Definition fft.H:11628
Array< Array< Real > > states_
Definition fft.H:11617
size_t num_sections() const noexcept
Returns the number of sections in the cascade.
Definition fft.H:11672
SOSFilter(const SectionsContainer &sections)
Construct a cascade of SOS from any biquad container.
Definition fft.H:11667
SOSFilter(const Array< BiquadSection > &sections)
Construct a cascade of second-order sections (SOS).
Definition fft.H:11647
bool configured() const noexcept
Returns whether the filter has been configured.
Definition fft.H:11678
SOSFilter()=default
Default constructor for an unconfigured SOSFilter.
const Array< Real > & state(const size_t index) const
Returns the current delay-line state of section index.
Definition fft.H:11685
Array< BiquadSection > sections_
Definition fft.H:11615
Array< IIRCoefficients > coeffs_
Definition fft.H:11616
Array< Real > filter(const Array< Real > &signal)
Processes a signal block through the stateful SOS cascade.
Definition fft.H:11704
Array< Real > filter(const Container &signal)
Processes a real-valued container through the stateful SOS cascade.
Definition fft.H:11726
void reset()
Resets all internal delay lines to zero.
Definition fft.H:11694
void require_configured(const char *ctx) const
Definition fft.H:11621
Stateful STFT processor for chunked real-time analysis.
Definition fft.H:10254
Array< Array< Complex > > pflush(ThreadPool &pool, const size_t chunk_size=0)
Definition fft.H:10468
size_t frame_size() const noexcept
Definition fft.H:10359
void append_samples(const Array< Real > &block)
Definition fft.H:10281
size_t fft_size() const noexcept
Definition fft.H:10374
bool centered() const noexcept
Definition fft.H:10379
Array< Real > pending_
Definition fft.H:10260
Array< Real > window_
Definition fft.H:10255
STFTProcessor(const Array< Real > &window, const STFTOptions &options)
Construct an STFT processor with a fixed analysis window.
Definition fft.H:10322
bool finalized() const noexcept
Definition fft.H:10389
Array< Array< Complex > > pprocess_block(ThreadPool &pool, const Container &block, const size_t chunk_size=0)
Definition fft.H:10506
Array< Array< Complex > > flush()
Definition fft.H:10440
Array< Array< Complex > > process_block(const Container &block)
Definition fft.H:10498
void require_configured(const char *ctx) const
Definition fft.H:10266
STFTProcessor(const size_t frame_size, const STFTOptions &options)
Construct an STFT processor using a Hann window.
Definition fft.H:10341
STFTOptions options_
Definition fft.H:10256
Array< Array< Complex > > process_block(const Array< Real > &block)
Definition fft.H:10408
const Array< Real > & window() const noexcept
Definition fft.H:10394
Array< Array< Complex > > emit_ready_frames(ThreadPool *pool, const size_t chunk_size, const bool allow_partial_frames)
Definition fft.H:10290
Array< Array< Complex > > pprocess_block(ThreadPool &pool, const Array< Real > &block, const size_t chunk_size=0)
Definition fft.H:10423
STFTProcessor(const WindowContainer &window, const STFTOptions &options)
Construct an STFT processor from any real container.
Definition fft.H:10354
size_t hop_size() const noexcept
Definition fft.H:10369
STFTProcessor()=default
Default constructor for an unconfigured processor.
bool pad_end() const noexcept
Definition fft.H:10384
bool configured() const noexcept
Definition fft.H:10364
Fast Fourier Transform (FFT) and DSP Toolkit.
Definition fft.H:158
static Array< Array< Array< Complex > > > inverse_transform3d(const Array< Array< Array< Complex > > > &input)
Functional inverse 3-D FFT wrapper.
Definition fft.H:8228
static Array< Array< Complex > > rfft_batch(const Array< Array< Real > > &input)
Functional compact real FFT for equal-length real batches.
Definition fft.H:7968
static Array< Real > group_delay(const SectionsContainer &sections, const size_t num_points=512, const bool whole=false)
Definition fft.H:12975
static Array< Complex > spectrum(const Container &input)
DSP alias for complex-valued container forward FFT.
Definition fft.H:8625
static Array< Real > istft(const Array< Array< Complex > > &spectrogram, const Array< Real > &analysis_window, const Array< Real > &synthesis_window, const ISTFTOptions &options)
Reconstructs a real signal using explicit ISTFT options.
Definition fft.H:10180
static Array< Real > signed_binomial(const size_t power, const Real sign)
Rationale: Computes (1 + sign*x)^power coefficients.
Definition fft.H:2450
static Array< Complex > transform_padded(const Container &input)
Forward FFT with padding for real-valued containers.
Definition fft.H:8538
static void validate_real_spectrum(const Array< Complex > &input, const char *ctx)
Rationale: Ensures a complex spectrum has the Hermitian symmetry (X[k] = X[N-k]*) required to be the ...
Definition fft.H:443
static Array< BiquadSection > bessel_bandstop(const size_t order, const Real low_cutoff_frequency, const Real high_cutoff_frequency, const Real sample_rate)
Digital Bessel band-stop design returned as SOS.
Definition fft.H:13470
static bool has_near_pole_zero_cancellation(const Array< Real > &numerator, const Array< Real > &denominator, const Real tolerance)
Definition fft.H:12554
static Array< Array< Complex > > stft(const Array< Real > &signal, const Array< Real > &window, const size_t hop_size, const bool pad_end=true)
Computes a basic STFT for a real signal using a custom window.
Definition fft.H:9941
static WeightedFrequencyGrid build_weighted_frequency_grid(const size_t num_taps, const Array< Real > &bands, const Array< Real > &desired, const Real sample_rate, const Array< Real > &weights, const size_t grid_density, const char *ctx)
Definition fft.H:4067
static Real hermitian_tolerance(const Complex &lhs, const Complex &rhs, const size_t n) noexcept
Rationale: Numerical tolerance for Hermitian symmetry checks.
Definition fft.H:429
static Array< Array< Real > > frame_signal(const Array< Real > &signal, const size_t frame_size, const size_t hop_size, const bool pad_end=true)
Splits a real signal into frames using a fixed hop size.
Definition fft.H:9891
static Array< Real > apply_blackman_window(const Array< Real > &signal)
Applies a Blackman window of matching size to a real signal.
Definition fft.H:9001
static Array< Real > phase_delay(const Array< Real > &numerator, const size_t num_points=512, const bool whole=false)
Definition fft.H:12843
static Array< Array< Real > > pbatched_sosfilt(ThreadPool &pool, const Array< Array< Real > > &signals, const Array< BiquadSection > &sections, const size_t chunk_size=0)
Definition fft.H:12121
and static Is_Real_Container< Container2 > Array< Real > overlap_save_convolution(const Container1 &signal, const Container2 &kernel, const size_t block_size=0)
Overlap-save convolution for real-valued containers.
Definition fft.H:15168
static Array< Real > trim_leading_zeros_copy(const Array< Real > &input) noexcept
Rationale: Copy utility that removes leading zero coefficients, effectively normalizing the polynomia...
Definition fft.H:2116
static Array< Real > hamming_window(const size_t n)
Returns a Hamming window of length n.
Definition fft.H:8887
and Is_Real_Container< DesiredContainer > and static Is_Real_Container< WeightContainer > Array< Real > remez(const size_t num_taps, const BandContainer &bands, const DesiredContainer &desired, const Real sample_rate, const WeightContainer &weights, const size_t grid_density=32, const size_t max_iterations=64)
Definition fft.H:9277
SimdBackend
SIMD hardware acceleration backends.
Definition fft.H:172
@ avx2
Intel/AMD AVX2.
@ scalar
Generic implementation.
static Array< size_t > frame_offsets(const size_t signal_size, const size_t frame_size, const size_t hop_size, const bool pad_end=true)
Returns the frame start offsets used by frame_signal.
Definition fft.H:9582
static std::pair< Real, Real > prewarp_band_edges(const Real low_cutoff_frequency, const Real high_cutoff_frequency, const Real sample_rate, const char *ctx)
Prewarps a pair of band edges for bilinear transform.
Definition fft.H:3606
static Array< Real > sos_filtfilt_impl(const Array< Real > &signal, const Array< BiquadSection > &sections, const char *ctx)
Definition fft.H:1196
static void validate_no_near_pole_zero_cancellation(const Array< BiquadSection > &sections, const Real tolerance)
Definition fft.H:12760
static Array< Array< Complex > > compact_real_batch_spectra(const Array< Array< Complex > > &full_spectra)
Definition fft.H:7709
static Array< Real > project_real_output(const Array< Complex > &input, const char *ctx)
Projects a complex-valued output array back to the real domain.
Definition fft.H:5772
static Array< Complex > build_complex_input(const Container &input)
Definition fft.H:5047
static Array< Real > phase_delay(const BiquadSection &section, const size_t num_points=512, const bool whole=false)
Definition fft.H:12882
static Array< Complex > ptransformed(ThreadPool &pool, const Array< Complex > &input, const bool invert=false, const size_t chunk_size=0)
Parallel FFT/IFFT that returns a new array.
Definition fft.H:7895
and static Is_Real_Container< DenContainer > GainMarginInfo gain_margin(const NumContainer &numerator, const DenContainer &denominator, const size_t num_points=1024, const bool whole=false)
Definition fft.H:13135
static Array< Real > lfilter(const Array< Real > &signal, const Array< Real > &numerator, const Array< Real > &denominator, const Array< Real > &initial_state={})
One-shot causal IIR filtering.
Definition fft.H:11960
and static Is_Real_Container< DenContainer > Real minimum_pole_zero_distance(const NumContainer &numerator, const DenContainer &denominator)
Definition fft.H:12433
static constexpr const char * simd_preference_name(const SimdPreference preference) noexcept
Returns the human-readable name of a SIMD preference.
Definition fft.H:6023
static Array< Array< Complex > > stft(const Array< Real > &signal, const Array< Real > &window, const STFTOptions &options)
Computes an STFT with explicit analysis options.
Definition fft.H:9986
static FrequencyResponse freqz_impl(const Array< Real > &numerator, const Array< Real > &denominator, const size_t num_points, const bool whole, const char *ctx)
Definition fft.H:1491
static Array< Real > apply_blackman_window(const Container &signal)
Definition fft.H:9465
SimdPreference
User-selected SIMD dispatch preference.
Definition fft.H:180
@ scalar_only
Force the portable scalar implementation.
@ automatic
Use the fastest available hardware.
@ neon_only
Force NEON (must be supported by CPU).
@ avx2_only
Force AVX2 (must be supported by CPU).
static Array< Real > phase_delay_impl(const FrequencyResponse &response)
implementation of numeric phase delay calculation.
Definition fft.H:4842
static PhaseMarginInfo phase_margin(const BiquadSection &section, const size_t num_points=1024, const bool whole=false)
Definition fft.H:13065
and static Is_Real_Container< Container2 > Array< Real > multiply(const Container1 &a, const Container2 &b)
Multiplication for real-valued containers.
Definition fft.H:15273
static Real modified_bessel_i0(const Real x) noexcept
Standard Zeroth-order Modified Bessel function of the first kind.
Definition fft.H:3791
static Array< Complex > rfft(const Container &input)
Compact real FFT for real-valued containers.
Definition fft.H:8576
static constexpr size_t saturating_product(const size_t lhs, const size_t rhs) noexcept
Definition fft.H:1297
static Array< BiquadSection > butterworth_highpass(const size_t order, const Real cutoff_frequency, const Real sample_rate)
Digital Butterworth high-pass design returned as SOS.
Definition fft.H:13207
static PhaseMarginInfo phase_margin(const Array< Real > &numerator, const Array< Real > &denominator, const size_t num_points=1024, const bool whole=false)
Definition fft.H:13007
static size_t resolve_irfft_signal_size(const Array< Complex > &spectrum, const size_t signal_size, const char *ctx)
Definition fft.H:7719
static Array< Real > filtfilt(const Array< Real > &signal, const BiquadSection &section)
Zero-phase filtering of one biquad section.
Definition fft.H:13694
static Array< Real > analytic_phase_delay_impl(const Array< Real > &numerator, const Array< Real > &denominator, const size_t num_points, const bool whole, const char *ctx)
implementation of analytic phase delay calculation.
Definition fft.H:2252
static Real evaluate_cosine_series(const Array< Real > &coefficients, const Real omega) noexcept
Definition fft.H:4164
static Array< BiquadSection > bessel_lowpass(const size_t order, const Real cutoff_frequency, const Real sample_rate)
Digital Bessel low-pass design returned as SOS.
Definition fft.H:13420
static Array< Complex > pspectrum(ThreadPool &pool, const Container &input, const size_t chunk_size=0)
Parallel version of spectrum(const Container&).
Definition fft.H:8634
static Array< Complex > poles(const IIRCoefficients &coeffs)
Definition fft.H:12279
static void validate_stable(const Array< Real > &denominator, const Real min_margin)
Definition fft.H:12663
and static Is_Real_Container< WindowContainer > PowerSpectralDensity welch(const SignalContainer &signal, const WindowContainer &window, const Real sample_rate, const WelchOptions &options={})
Definition fft.H:9790
static Array< Complex > apply_hann_window(const Array< Complex > &signal)
Applies a Hann window of matching size to a complex signal.
Definition fft.H:9008
and Is_Real_Container< ContainerY > and static Is_Real_Container< WindowContainer > CoherenceEstimate coherence(const ContainerX &x, const ContainerY &y, const WindowContainer &window, const Real sample_rate, const WelchOptions &options={})
Definition fft.H:9824
static Array< Real > solve_dense_system(Array< Real > matrix, Array< Real > rhs, const size_t n, const char *ctx)
Definition fft.H:972
static Array< Real > filtfilt(const Array< Real > &signal, const IIRCoefficients &coeffs)
Zero-phase IIR filtering with explicit coefficient groups.
Definition fft.H:13686
static void validate_no_near_pole_zero_cancellation(const IIRCoefficients &coeffs, const Real tolerance)
Definition fft.H:12742
static BiquadSection section_from_coefficients(const Array< Real > &numerator, const Array< Real > &denominator, const char *ctx)
Rationale: Converts generic IIR coefficients into a stabilized BiquadSection structure,...
Definition fft.H:2860
static bool roots_pass_residual_check(const Array< Real > &coefficients, const Array< Complex > &roots, const Real tol) noexcept
Definition fft.H:1910
static Array< Real > firwin_bandstop(const size_t num_taps, const Real low_cutoff_frequency, const Real high_cutoff_frequency, const Real sample_rate, const Array< Real > &window)
FIR band-stop design via the window method.
Definition fft.H:9149
static Array< Real > solve_remez_cosine_series(const Array< Real > &omega, const Array< Real > &desired, const Array< Real > &weight, const Array< size_t > &extrema, const size_t half_order, const char *ctx)
Definition fft.H:4292
static Array< Real > pistft(ThreadPool &pool, const Array< Array< Complex > > &spectrogram, const Array< Real > &analysis_window, const Array< Real > &synthesis_window, const size_t hop_size, const size_t signal_length=0, const size_t chunk_size=0)
Parallel STFT inversion using a thread pool.
Definition fft.H:10122
static Array< Array< Real > > overlap_add_convolution_batch(const Array< Array< Real > > &signals, const Array< Real > &kernel, const size_t block_size=0)
Multichannel overlap-add convolution with a shared kernel FFT.
Definition fft.H:15089
static void validate_no_near_pole_zero_cancellation(const BiquadSection &section, const Real tolerance)
Definition fft.H:12751
static Array< Complex > apply_window(const Array< Complex > &signal, const Array< Real > &window)
Applies a real window sample-by-sample to a complex signal.
Definition fft.H:8957
static Real stability_margin(const SectionsContainer &sections)
Definition fft.H:12490
static Array< Real > lfilter(const Array< Real > &signal, const IIRCoefficients &coeffs, const Array< Real > &initial_state={})
Definition fft.H:11975
static bool has_near_pole_zero_cancellation(const Array< Complex > &zeros, const Array< Complex > &poles, const Real tolerance)
Detects near pole/zero cancellations under a tolerance.
Definition fft.H:12542
static Array< Real > overlap_save_convolution(const Array< Real > &signal, const Array< Real > &kernel, const size_t block_size=0)
Convenience wrapper for overlap-save real convolution.
Definition fft.H:15114
static Array< Complex > zeros(const BiquadSection &section)
Definition fft.H:12285
static Array< Real > phase_spectrum(const Container &input)
Definition fft.H:8873
static constexpr bool is_power_of_two(const size_t n) noexcept
Checks if a given number is a power of two.
Definition fft.H:6000
static Array< BiquadSection > chebyshev2_lowpass(const size_t order, const Real attenuation_db, const Real cutoff_frequency, const Real sample_rate)
Digital Chebyshev-II low-pass design returned as SOS.
Definition fft.H:13336
static void validate_stable(const SectionsContainer &sections)
Definition fft.H:12657
static void append_all(Array< T > &dst, const Array< T > &src)
Definition fft.H:1266
static SeriesEvaluation evaluate_series_at_unit_circle(const Array< Real > &coefficients, const Real omega, const char *ctx)
Definition fft.H:1521
static void ptransform(ThreadPool &pool, Array< Complex > &a, const bool invert, const size_t chunk_size=0)
Parallel in-place FFT/IFFT using a thread pool.
Definition fft.H:7857
static Array< Array< Complex > > pstft(ThreadPool &pool, const Array< Real > &signal, const size_t frame_size, const STFTOptions &options, const size_t chunk_size=0)
Parallel Hann-window STFT with explicit analysis options.
Definition fft.H:10015
static Array< Real > firwin_bandpass(const size_t num_taps, const Real low_cutoff_frequency, const Real high_cutoff_frequency, const Real sample_rate, const Real attenuation_db)
FIR band-pass design using a Kaiser window.
Definition fft.H:9134
static bool try_durand_kerner_roots(const Array< Real > &coefficients, const Real tol, Array< Complex > &roots, const size_t max_iterations=192) noexcept
Definition fft.H:1787
static bool try_aberth_ehrlich_roots(const Array< Real > &coefficients, const Real tol, Array< Complex > &roots, const size_t max_iterations=192) noexcept
Definition fft.H:1732
static Array< Complex > zeros(const Array< Real > &numerator)
Returns the finite zeros of a transfer numerator.
Definition fft.H:12248
static Array< Array< Array< Complex > > > batched_stft(const Array< Array< Real > > &signals, const size_t frame_size, const STFTOptions &options)
Hann-window batched STFT.
Definition fft.H:11305
and static Is_Real_Container< WindowContainer > Array< Array< Complex > > stft(const SignalContainer &signal, const WindowContainer &window, const STFTOptions &options)
Definition fft.H:10027
static IIRCoefficients apply_analog_rational_transform(const Array< Real > &numerator, const Array< Real > &denominator, const Array< Real > &map_numerator, const Array< Real > &map_denominator, const char *ctx)
Rationale: Transforms an analog transfer function H(s) via rational substitution s = N(z)/D(z),...
Definition fft.H:2610
static Real refine_scalar_crossing(const Evaluator &evaluator, const Real x0, const Real y0, const Real x1, const Real y1, const Real target)
Definition fft.H:4444
static Array< BiquadSection > transfer_function_to_sections(const IIRCoefficients &coeffs, const Array< RootGroup > &zero_groups, const char *ctx)
Rationale: Decomposes a general IIR transfer function into a cascade of Second-Order Sections (SOS) b...
Definition fft.H:3631
static Array< Real > multiply_real_optimized(const Array< Real > &a, const Array< Real > &b, ThreadPool *pool=nullptr, const size_t chunk_size=0)
Rationale: Efficiently multiplies two real sequences using a single N-point complex FFT by packing on...
Definition fft.H:5677
static GainMarginInfo gain_margin(const FrequencyResponse &response)
Estimates gain margin around the -pi phase crossover.
Definition fft.H:13001
static Real comp_ellint_1_impl(const Real k)
Definition fft.H:2971
static Array< Array< Complex > > stft(const Array< Real > &signal, const size_t frame_size, const size_t hop_size, const bool pad_end=true)
Computes a basic STFT for a real signal using a Hann window.
Definition fft.H:9954
static Array< Real > phase_delay(const FrequencyResponse &response)
Estimates phase delay from a sampled frequency response.
Definition fft.H:12799
static void validate_overlap_constraints(const Array< Real > &analysis_window, const Array< Real > &synthesis_window, const size_t hop_size, const bool validate_nola, const bool validate_cola, const char *ctx)
Rationale: Internal validator for STFT/ISTFT windowing parameters.
Definition fft.H:676
static Real stability_margin(const Array< Real > &denominator)
Signed stability margin relative to the unit circle.
Definition fft.H:12450
static Array< Real > firwin_lowpass(const size_t num_taps, const Real cutoff_frequency, const Real sample_rate, const Real attenuation_db)
FIR low-pass design via the window method using a Kaiser window.
Definition fft.H:9043
static Array< Real > istft_impl(const Array< Array< Complex > > &spectrogram, const Array< Real > &analysis_window, const Array< Real > &synthesis_window, const ISTFTOptions &options, ThreadPool *pool, const size_t chunk_size)
implementation of the Inverse Short-Time Fourier Transform.
Definition fft.H:4899
static Real minimum_pole_zero_distance(const Array< Complex > &zeros, const Array< Complex > &poles)
Returns the minimum pole/zero distance in a greedy pairing.
Definition fft.H:12399
static Array< Complex > zeros(const IIRCoefficients &coeffs)
Definition fft.H:12273
static Real window_enbw(const Container &window)
Definition fft.H:9575
static Real align_phase_near_reference(const Real raw_phase, const Real reference_phase) noexcept
Definition fft.H:4433
static Array< Real > pistft(ThreadPool &pool, const Array< Array< Complex > > &spectrogram, const size_t frame_size, const size_t hop_size, const size_t signal_length=0, const size_t chunk_size=0)
Parallel STFT inversion using a Hann window pair.
Definition fft.H:10161
static Array< Complex > apply_hamming_window(const Array< Complex > &signal)
Applies a Hamming window of matching size to a complex signal.
Definition fft.H:9015
static PhaseMarginInfo phase_margin_impl(const FrequencyResponse &response)
Definition fft.H:4508
static Array< Real > inverse_transform_real_general_impl(const Array< Complex > &input, const char *ctx, ThreadPool *pool, const size_t chunk_size)
Definition fft.H:7812
static Array< Real > build_real_input(const Container &input)
Definition fft.H:5055
static Array< Real > firls_impl(const size_t num_taps, const Array< Real > &bands, const Array< Real > &desired, const Real sample_rate, const Array< Real > &weights, const char *ctx)
implementation of Least-Squares FIR design.
Definition fft.H:3945
static Array< Real > filtfilt(const Array< Real > &signal, const Array< BiquadSection > &sections)
Zero-phase filtering of a cascade of biquad sections.
Definition fft.H:13702
and static Is_Real_Container< Container2 > Array< Real > poverlap_add_convolution(ThreadPool &pool, const Container1 &signal, const Container2 &kernel, const size_t block_size=0, const size_t chunk_size=0)
Parallel version of overlap_add_convolution(const Container1&, const Container2&, size_t).
Definition fft.H:15151
static FrequencyResponse freqz(const BiquadSection &section, const size_t num_points=512, const bool whole=false)
Definition fft.H:12182
static Array< Real > analytic_group_delay_impl(const Array< Real > &numerator, const Array< Real > &denominator, const size_t num_points, const bool whole, const char *ctx)
Rationale: Analytic calculation of group delay (-dphi/domega) using the complex derivative of the tra...
Definition fft.H:2178
static Array< Real > pirfft(ThreadPool &pool, const Array< Complex > &spectrum, const size_t signal_size=0, const size_t chunk_size=0)
Parallel version of irfft(const Array<Complex>&, size_t).
Definition fft.H:8765
static Array< Complex > zeros(const Container &numerator)
Definition fft.H:12317
static CoherenceEstimate coherence(const Array< Real > &x, const Array< Real > &y, const Array< Real > &window, const Real sample_rate, const WelchOptions &options={})
Magnitude-squared coherence estimate using Welch averages.
Definition fft.H:9749
static Array< Real > unwrap_phase(const Array< Real > &phase)
implementation of phase unwrapping (removes +/- 2*pi jumps).
Definition fft.H:4787
static Array< Real > polynomial_power(const Array< Real > &poly, const size_t exponent, const char *ctx)
implementation of P(x)^n via successive multiplication.
Definition fft.H:2536
static bool is_stable(const Container &denominator)
Definition fft.H:12527
static constexpr bool Is_Real_Batch_Container
Definition fft.H:390
static Array< Real > phase_delay(const Array< BiquadSection > &sections, const size_t num_points=512, const bool whole=false)
Definition fft.H:12904
static bool neon_dispatch_available() noexcept
Returns whether the runtime CPU can execute the NEON kernel when it has been compiled in.
Definition fft.H:6089
static Array< Real > build_padded_frame_from_prefix(const Array< Real > &input, const size_t frame_size)
Definition fft.H:1345
static Array< Array< Complex > > ptransformed2d(ThreadPool &pool, const Array< Array< Complex > > &input, const bool invert=false, const size_t chunk_size=0)
Parallel functional 2-D FFT/IFFT wrapper for matrices.
Definition fft.H:8112
static void scatter_axis_slice(Array< Complex > &data, const size_t base_offset, const size_t axis_stride, const Array< Complex > &slice)
Rationale: Writes back a transformed contiguous slice into its original (potentially non-contiguous) ...
Definition fft.H:5249
static Array< Array< Real > > pbatched_lfilter(ThreadPool &pool, const Array< Array< Real > > &signals, const Array< Real > &numerator, const Array< Real > &denominator, const Array< Array< Real > > &initial_states={}, const size_t chunk_size=0)
Definition fft.H:12035
static constexpr const char * simd_backend_name(const SimdBackend backend) noexcept
Returns the human-readable name of a SIMD backend.
Definition fft.H:6007
and static Is_Real_Container< WindowContainer > Array< Array< Complex > > stft(const SignalContainer &signal, const WindowContainer &window, const size_t hop_size, const bool pad_end=true)
Definition fft.H:9965
static Array< Complex > poles(const Array< BiquadSection > &sections)
Definition fft.H:12306
static Array< Array< Real > > pbatched_istft(ThreadPool &pool, const Array< Array< Array< Complex > > > &spectrograms, const Array< Real > &analysis_window, const Array< Real > &synthesis_window, const ISTFTOptions &options, const Array< size_t > &signal_lengths={}, const size_t chunk_size=0)
Parallel batched ISTFT across signals.
Definition fft.H:11362
static Array< Array< Complex > > pinverse_transform2d(ThreadPool &pool, const Array< Array< Complex > > &input, const size_t chunk_size=0)
Parallel functional inverse 2-D FFT wrapper.
Definition fft.H:8140
static Array< BiquadSection > chebyshev2_highpass(const size_t order, const Real attenuation_db, const Real cutoff_frequency, const Real sample_rate)
Digital Chebyshev-II high-pass design returned as SOS.
Definition fft.H:13352
static Array< Real > irfft(const Array< Complex > &spectrum, const size_t signal_size=0)
Reconstructs a real signal from a compact rfft() spectrum.
Definition fft.H:8756
static Array< BiquadSection > design_transformed_sections(const AnalogPrototype &prototype, const Array< Real > &map_numerator, const Array< Real > &map_denominator, const Real sample_rate, const char *ctx)
Generic implementation for designing transformed SOS cascades.
Definition fft.H:3757
static size_t resolve_welch_fft_size(const WelchOptions &options, const size_t frame_size, const char *ctx)
Default FFT size for Welch analysis (zero-padded to power of 2).
Definition fft.H:818
static Array< BiquadSection > butterworth_lowpass(const size_t order, const Real cutoff_frequency, const Real sample_rate)
Digital Butterworth low-pass design returned as SOS.
Definition fft.H:13192
static Array< Array< Array< Complex > > > pbatched_stft(ThreadPool &pool, const Array< Array< Real > > &signals, const size_t frame_size, const STFTOptions &options, const size_t chunk_size=0)
Parallel Hann-window batched STFT.
Definition fft.H:11314
static bool is_stable(const Array< Real > &denominator)
Checks BIBO stability from denominator roots.
Definition fft.H:12497
static Array< Array< Complex > > prfft_batch(ThreadPool &pool, const Array< Array< Real > > &input, const size_t chunk_size=0)
Parallel compact real FFT for equal-length real batches.
Definition fft.H:7979
static Array< Real > group_delay(const Array< Real > &numerator, const size_t num_points=512, const bool whole=false)
Definition fft.H:12831
static BalancedPolynomial balance_polynomial_for_roots(const Array< Real > &coefficients, const char *ctx)
Definition fft.H:1649
static Array< Complex > polynomial_roots_impl(const Array< Real > &coefficients, const char *ctx)
Rationale: Master implementation for polynomial root-finding.
Definition fft.H:2028
static Real minimum_cancellation_distance_impl(const Array< PoleZeroPair > &pairs) noexcept
Rationale: Estimates the minimum distance between any matched pole and zero to identify potential can...
Definition fft.H:2362
static Array< Real > phase_delay(const SectionsContainer &sections, const size_t num_points=512, const bool whole=false)
Definition fft.H:12985
static Array< Array< Real > > pmultichannel_istft(ThreadPool &pool, const Array< Array< Array< Complex > > > &spectrograms, const Array< Real > &analysis_window, const Array< Real > &synthesis_window, const ISTFTOptions &options={}, const Array< size_t > &signal_lengths={}, const SpectrogramLayout layout=SpectrogramLayout::channel_frame_bin, const size_t chunk_size=0)
Parallel multichannel ISTFT from either supported layout.
Definition fft.H:8321
static TensorLayout row_major_layout(const Array< size_t > &shape)
Builds a default row-major tensor layout for a flat buffer.
Definition fft.H:8018
static constexpr size_t twiddle_refresh_period
Definition fft.H:397
static Array< Real > hann_window(const size_t n)
Returns a Hann window of length n.
Definition fft.H:8880
static Array< Complex > rfft(const Array< Real > &input)
Computes the compact real FFT of size floor(N/2) + 1.
Definition fft.H:8486
static Real integrate_offset_cos_basis(const Real omega_lo, const Real omega_hi, const size_t harmonic) noexcept
implementation helper for firls slope integration.
Definition fft.H:3911
static Array< Real > remez(const size_t num_taps, const Array< Real > &bands, const Array< Real > &desired, const Real sample_rate, const Array< Real > &weights={}, const size_t grid_density=32, const size_t max_iterations=64)
FIR equiripple design via a dense-grid Remez exchange.
Definition fft.H:9237
static Array< Array< Array< Complex > > > pinverse_transform3d(ThreadPool &pool, const Array< Array< Array< Complex > > > &input, const size_t chunk_size=0)
Parallel functional inverse 3-D FFT wrapper.
Definition fft.H:8235
static Array< Complex > pspectrum(ThreadPool &pool, const Array< Complex > &input, const size_t chunk_size=0)
Parallel version of spectrum(const Array<Complex>&).
Definition fft.H:8600
static Array< Real > pistft(ThreadPool &pool, const Array< Array< Complex > > &spectrogram, const Array< Real > &analysis_window, const Array< Real > &synthesis_window, const ISTFTOptions &options, const size_t chunk_size=0)
Parallel ISTFT with explicit options.
Definition fft.H:10214
static Real minimum_pole_zero_distance(const Array< BiquadSection > &sections)
Definition fft.H:12425
static Array< Real > analytic_sos_group_delay_impl(const Array< BiquadSection > &sections, const FrequencyResponse &response, const char *ctx)
implementation of analytic group delay for SOS cascades.
Definition fft.H:2212
static Array< size_t > evenly_spaced_extrema(const size_t grid_size, const size_t extremal_count)
Definition fft.H:4174
static Array< Real > trim_polynomial_leading_zeros(const Array< Real > &input, const char *ctx)
Validation wrapper for trim_leading_zeros_copy.
Definition fft.H:2526
static Array< Real > power_spectrum(const Container &input)
Definition fft.H:8854
static Real stability_margin(const Array< BiquadSection > &sections)
Definition fft.H:12468
static Array< Complex > pinverse_transform(ThreadPool &pool, const Container &input, const size_t chunk_size=0)
Parallel version of inverse_transform(const Container&).
Definition fft.H:8696
and static Is_Real_Container< CoeffContainer > Array< Real > resample_poly(const SignalContainer &signal, const size_t up, const size_t down, const CoeffContainer &coeffs)
Definition fft.H:9424
static Array< Array< Real > > project_real_batch_output(const Array< Array< Complex > > &input, const char *ctx)
Definition fft.H:7776
static Real evaluate_fir_response_magnitude(const Array< Real > &coeffs, const Real omega, const char *ctx)
implementation helper for FIR frequency response magnitude.
Definition fft.H:3820
and static Is_Real_Container< DesiredContainer > Array< Real > remez(const size_t num_taps, const BandContainer &bands, const DesiredContainer &desired, const Real sample_rate)
Definition fft.H:9259
static Array< Real > irfft(const Container &spectrum, const size_t signal_size=0)
Inverse real transform (compact) for complex-valued containers.
Definition fft.H:8800
static Array< Array< Array< Complex > > > ptransformed2d_batch(ThreadPool &pool, const Array< Array< Array< Complex > > > &input, const bool invert=false, const size_t chunk_size=0)
Parallel functional 2-D batched FFT/IFFT wrapper.
Definition fft.H:8165
and static Is_Real_Container< Container2 > Array< Real > pmultiply(ThreadPool &pool, const Container1 &a, const Container2 &b, const size_t chunk_size=0)
Parallel version of multiply(const Container1&, const Container2&).
Definition fft.H:15282
static AnalogPrototype butterworth_prototype(const size_t order, const char *ctx)
Definition fft.H:3217
static Array< Real > overlap_profile(const Array< Real > &analysis_window, const Array< Real > &synthesis_window, const size_t hop_size, const char *ctx)
Rationale: Computes the effective window product for a windowed overlap-add process to validate recon...
Definition fft.H:617
static Array< Array< Real > > batched_istft(const Array< Array< Array< Complex > > > &spectrograms, const Array< Real > &window, const ISTFTOptions &options, const Array< size_t > &signal_lengths={})
Batched ISTFT using a shared analysis/synthesis window.
Definition fft.H:11417
static Real max_root_radius(const Array< Complex > &roots) noexcept
Returns the magnitude of the largest root in the set.
Definition fft.H:2275
static Array< Array< Array< Complex > > > ptransformed3d(ThreadPool &pool, const Array< Array< Array< Complex > > > &input, const bool invert=false, const size_t chunk_size=0)
Parallel functional 3-D FFT/IFFT wrapper for tensors.
Definition fft.H:8206
static Array< Complex > prfft(ThreadPool &pool, const Container &input, const size_t chunk_size=0)
Parallel version of rfft(const Container&).
Definition fft.H:8585
static SimdBackend simd_backend() noexcept
Returns the default SIMD backend used by standalone plan transforms for this precision.
Definition fft.H:6199
static Array< Array< Real > > poverlap_add_convolution_batch(ThreadPool &pool, const Array< Array< Real > > &signals, const Array< Real > &kernel, const size_t block_size=0, const size_t chunk_size=0)
Parallel version of overlap_add_convolution_batch.
Definition fft.H:15100
static Real normalized_sinc(const Real x) noexcept
Standard normalized sinc function sin(pi*x)/(pi*x).
Definition fft.H:3810
static Real ellint_1_impl(const Real k, const Real phi)
Definition fft.H:3021
static Array< Real > demean_copy(const Array< Real > &input)
Returns a copy with DC offset removed.
Definition fft.H:763
static Array< Array< Real > > frame_signal(const Container &signal, const size_t frame_size, const size_t hop_size, const bool pad_end=true)
Definition fft.H:9927
static const char * detected_simd_backend_name() noexcept
Returns the detected hardware SIMD backend name.
Definition fft.H:6153
static Array< Array< Complex > > expand_real_batch_spectra(const Array< Array< Complex > > &spectra, const size_t signal_size, const char *ctx)
Definition fft.H:7764
static Array< Real > inverse_transform_real_optimized_impl(const Array< Complex > &input, const char *ctx, ThreadPool *pool, const size_t chunk_size, InversePackedTransform inverse_packed)
Rationale: Optimized Inverse FFT for real signals of size N (power-of-two).
Definition fft.H:5612
static Array< Real > resample_poly(const SignalContainer &signal, const size_t up, const size_t down, const ResamplePolyOptions &options={})
Definition fft.H:9438
static Array< Real > lfilter(const Array< Real > &signal, const BiquadSection &section, const Array< Real > &initial_state={})
Definition fft.H:11983
static Array< Real > istft(const Array< Array< Complex > > &spectrogram, const Array< Real > &analysis_window, const Array< Real > &synthesis_window, const size_t hop_size, const size_t signal_length=0)
Reconstructs a real signal from an STFT using overlap-add.
Definition fft.H:10080
static Array< PoleZeroPair > pair_poles_and_zeros(const Array< BiquadSection > &sections)
Definition fft.H:12374
static Array< Real > pinverse_transform_real(ThreadPool &pool, const Container &input, const size_t chunk_size=0)
Parallel version of inverse_transform_real(const Container&).
Definition fft.H:8790
static Real stability_margin(const Container &denominator)
Definition fft.H:12482
static CrossSpectralDensity csd(const Array< Real > &x, const Array< Real > &y, const Array< Real > &window, const Real sample_rate, const WelchOptions &options={})
One-sided cross-spectral density estimate using a custom window.
Definition fft.H:9688
static Array< Complex > zeros(const SectionsContainer &sections)
Definition fft.H:12333
static Array< Array< Real > > prepare_welch_frames(const Array< Real > &signal, const Array< Real > &window, const WelchOptions &options, const char *ctx)
Rationale: Encapsulates frame extraction, detrending and windowing common to Welch and other spectral...
Definition fft.H:833
static Complex evaluate_transfer_at(const Array< Real > &numerator, const Array< Real > &denominator, const Real omega, const char *ctx)
Definition fft.H:1470
static Array< BiquadSection > elliptic_bandstop(const size_t order, const Real ripple_db, const Real attenuation_db, const Real low_cutoff_frequency, const Real high_cutoff_frequency, const Real sample_rate)
Digital elliptic/Cauer band-stop design returned as SOS.
Definition fft.H:13547
static bool is_stable(const SectionsContainer &sections)
Definition fft.H:12535
static Array< Real > one_sided_frequency_grid(const size_t fft_size, const Real sample_rate, const char *ctx)
Maps bin indices to frequency in Hz.
Definition fft.H:777
static Array< Complex > lift_real_input(const Array< Real > &input)
Lifts a real-valued input array to the complex domain.
Definition fft.H:5545
static Array< Real > pfiltfilt(ThreadPool &pool, const Array< Real > &signal, const Array< Real > &coeffs, const size_t block_size=0, const size_t chunk_size=0)
Parallel zero-phase FIR filtering via forward-backward convolution.
Definition fft.H:13608
static constexpr bool Is_Biquad_Container
Definition fft.H:381
static Array< Array< Real > > batched_istft(const Array< Array< Array< Complex > > > &spectrograms, const size_t frame_size, const ISTFTOptions &options, const Array< size_t > &signal_lengths={})
Hann-window batched ISTFT.
Definition fft.H:11449
static Array< PoleZeroPair > pair_poles_and_zeros(const Array< Real > &numerator, const Array< Real > &denominator)
Definition fft.H:12355
static Array< Array< Real > > pbatched_istft(ThreadPool &pool, const Array< Array< Array< Complex > > > &spectrograms, const size_t frame_size, const ISTFTOptions &options, const Array< size_t > &signal_lengths={}, const size_t chunk_size=0)
Parallel Hann-window batched ISTFT.
Definition fft.H:11460
static size_t validate_stft_options(const Array< Real > &window, const STFTOptions &options, const char *ctx)
Definition fft.H:1360
static CoherenceEstimate coherence(const Array< Real > &x, const Array< Real > &y, const size_t frame_size, const Real sample_rate, const WelchOptions &options={})
Magnitude-squared coherence estimate using a Hann window.
Definition fft.H:9778
static Array< BiquadSection > elliptic_highpass(const size_t order, const Real ripple_db, const Real attenuation_db, const Real cutoff_frequency, const Real sample_rate)
Digital elliptic/Cauer high-pass design returned as SOS.
Definition fft.H:13503
static void bit_reverse(Array< Complex > &a) noexcept
Performs bit-reversal permutation in-place.
Definition fft.H:5804
static Complex evaluate_sos_transfer_at(const Array< BiquadSection > &sections, const Real omega, const char *ctx)
Definition fft.H:4491
static Array< BiquadSection > bessel_highpass(const size_t order, const Real cutoff_frequency, const Real sample_rate)
Digital Bessel high-pass design returned as SOS.
Definition fft.H:13434
static Array< Array< Complex > > pstft(ThreadPool &pool, const Container &signal, const size_t frame_size, const STFTOptions &options, const size_t chunk_size=0)
Definition fft.H:10063
static GainMarginInfo gain_margin(const SectionsContainer &sections, const size_t num_points=1024, const bool whole=false)
Definition fft.H:13159
static Array< Real > apply_hann_window(const Array< Real > &signal)
Applies a Hann window of matching size to a real signal.
Definition fft.H:8987
static Array< Array< Array< Complex > > > transpose_spectrogram_layout_impl(const Array< Array< Array< Complex > > > &input, const SpectrogramLayout source, const SpectrogramLayout target, const char *ctx)
Rationale: Internal implementation for transposing between multichannel spectrogram layouts (e....
Definition fft.H:5474
static Array< Array< Complex > > pstft(ThreadPool &pool, const Array< Real > &signal, const Array< Real > &window, const STFTOptions &options, const size_t chunk_size=0)
Parallel STFT using explicit analysis options.
Definition fft.H:9995
static void normalize_fir_at_omega(Array< Real > &coeffs, const Real omega, const char *ctx)
Normalizes FIR coefficients to have unit gain at frequency omega.
Definition fft.H:3832
static Array< Complex > spectrum(const Array< Complex > &input)
DSP alias for the forward FFT.
Definition fft.H:8593
static Array< Real > group_delay(const Array< BiquadSection > &sections, const size_t num_points=512, const bool whole=false)
Definition fft.H:12893
static Real stability_margin(const IIRCoefficients &coeffs)
Definition fft.H:12456
static Real interpolate_crossing(const Real x0, const Real y0, const Real x1, const Real y1, const Real target) noexcept
Definition fft.H:4405
static Array< Real > pirfft(ThreadPool &pool, const Container &spectrum, const size_t signal_size=0, const size_t chunk_size=0)
Parallel version of irfft(const Container&, size_t).
Definition fft.H:8809
static Array< Real > apply_hamming_window(const Container &signal)
Definition fft.H:9457
static Array< Array< Complex > > stft(const Container &signal, const size_t frame_size, const STFTOptions &options)
Definition fft.H:10053
static Array< Real > sosfilt(const Array< Real > &signal, const Array< BiquadSection > &sections)
One-shot causal filtering of a cascade of second-order sections.
Definition fft.H:12090
static Array< Complex > transform(const Container &input)
Forward FFT for complex-valued containers.
Definition fft.H:8519
static Array< Real > upfirdn(const Array< Real > &signal, const Array< Real > &coeffs, const size_t up=1, const size_t down=1)
Polyphase upsample-filter-downsample for real signals.
Definition fft.H:9296
static void transform_axes_impl(Array< Complex > &data, const TensorLayout &layout, const Array< size_t > &axes, const bool invert, ThreadPool *pool=nullptr, const size_t chunk_size=0)
Rationale: Internal implementation of a multi-axis tensor transform.
Definition fft.H:5316
static SimdBackend detected_simd_backend() noexcept
Returns the best SIMD backend supported by the current hardware among the kernels compiled for this p...
Definition fft.H:6142
static Array< BiquadSection > butterworth_bandpass(const size_t order, const Real low_cutoff_frequency, const Real high_cutoff_frequency, const Real sample_rate)
Digital Butterworth band-pass design returned as SOS.
Definition fft.H:13222
static Array< Complex > inverse_transform(const Container &input)
Forward IFFT for complex-valued containers.
Definition fft.H:8687
static Array< Complex > ptransformed_axes(ThreadPool &pool, const Array< Complex > &input, const TensorLayout &layout, const Array< size_t > &axes, const bool invert=false, const size_t chunk_size=0)
Parallel functional tensor FFT/IFFT wrapper for flat buffers.
Definition fft.H:8084
static Array< T > prefix_copy(const Array< T > &input, const size_t length)
Functional prefix extraction.
Definition fft.H:516
static Array< Array< Real > > batched_lfilter(const Array< Array< Real > > &signals, const Array< Real > &numerator, const Array< Real > &denominator, const Array< Array< Real > > &initial_states={})
One-shot batched causal IIR filtering across real channels.
Definition fft.H:12008
and static Is_Real_Container< WindowContainer > Array< Complex > windowed_spectrum(const SignalContainer &signal, const WindowContainer &window)
Definition fft.H:9511
static Real window_energy(const Container &window)
Definition fft.H:9559
static Array< Array< Real > > multichannel_istft(const Array< Array< Array< Complex > > > &spectrograms, const Array< Real > &analysis_window, const Array< Real > &synthesis_window, const ISTFTOptions &options={}, const Array< size_t > &signal_lengths={}, const SpectrogramLayout layout=SpectrogramLayout::channel_frame_bin)
Multichannel ISTFT from either channel-major or frame-major spectrogram layouts.
Definition fft.H:8298
static constexpr bool avx2_kernel_compiled() noexcept
Returns whether the AVX2 kernel was compiled for this precision.
Definition fft.H:6041
static Array< Real > group_delay(const FrequencyResponse &response)
Estimates group delay from a sampled frequency response.
Definition fft.H:12792
static FrequencyResponse freqz(const Array< BiquadSection > &sections, const size_t num_points=512, const bool whole=false)
Definition fft.H:12190
static Real interpolate_value_at(const Real x0, const Real y0, const Real x1, const Real y1, const Real x) noexcept
Definition fft.H:4419
static Complex group_center(const Array< Complex > &roots) noexcept
Geometric center of a root set.
Definition fft.H:2690
static Array< Real > istft(const Array< Array< Complex > > &spectrogram, const Array< Real > &window, const ISTFTOptions &options)
Reconstructs a real signal using one shared analysis/synthesis window.
Definition fft.H:10195
and Is_Real_Container< ContainerY > and static Is_Real_Container< WindowContainer > CrossSpectralDensity csd(const ContainerX &x, const ContainerY &y, const WindowContainer &window, const Real sample_rate, const WelchOptions &options={})
Definition fft.H:9806
static Array< Complex > apply_blackman_window(const Array< Complex > &signal)
Applies a Blackman window of matching size to a complex signal.
Definition fft.H:9022
static Array< Real > firwin_highpass(const size_t num_taps, const Real cutoff_frequency, const Real sample_rate, const Real attenuation_db)
FIR high-pass design using a Kaiser window.
Definition fft.H:9079
static void validate_stable(const Array< BiquadSection > &sections)
Definition fft.H:12640
static constexpr bool neon_kernel_compiled() noexcept
Returns whether the NEON kernel was compiled for this precision.
Definition fft.H:6052
static FrequencyResponse freqz(const IIRCoefficients &coeffs, const size_t num_points=512, const bool whole=false)
Definition fft.H:12174
static void validate_stable(const IIRCoefficients &coeffs, const Real min_margin)
Definition fft.H:12674
static Array< BiquadSection > elliptic_bandpass(const size_t order, const Real ripple_db, const Real attenuation_db, const Real low_cutoff_frequency, const Real high_cutoff_frequency, const Real sample_rate)
Digital elliptic/Cauer band-pass design returned as SOS.
Definition fft.H:13521
static GainMarginInfo gain_margin(const BiquadSection &section, const size_t num_points=1024, const bool whole=false)
Definition fft.H:13076
and static Is_Real_Container< CoeffContainer > Array< Real > pfiltfilt(ThreadPool &pool, const SignalContainer &signal, const CoeffContainer &coeffs, const size_t block_size=0, const size_t chunk_size=0)
Definition fft.H:13653
static bool avx2_runtime_available() noexcept
Returns whether AVX2 dispatch is supported at runtime.
Definition fft.H:6261
static bool avx2_dispatch_available() noexcept
Returns whether the runtime CPU can execute the AVX2 double kernel when it has been compiled in.
Definition fft.H:6069
static SimdBackend batched_plan_simd_backend() noexcept
Returns the SIMD backend selected for throughput-oriented batch plan paths under the current runtime ...
Definition fft.H:6225
static Array< Real > filtfilt(const SignalContainer &signal, const BiquadSection &section)
Definition fft.H:13734
static Real window_coherent_gain(const Container &window)
Definition fft.H:9567
static Array< Complex > multiply(const Array< Complex > &a, const Array< Complex > &b)
Multiplies two complex-valued sequences using convolution.
Definition fft.H:15210
static std::pair< Array< Real >, Real > divide_polynomial_by_linear_root(const Array< Real > &coefficients, const Real root, const char *ctx)
Definition fft.H:1567
and Is_Real_Container< NumContainer > and static Is_Real_Container< DenContainer > Array< Real > lfilter(const SignalContainer &signal, const NumContainer &numerator, const DenContainer &denominator, const Array< Real > &initial_state={})
Definition fft.H:11995
static Array< Complex > prepare_stft_frame_input(const Array< Real > &frame, const Array< Real > &window, const size_t fft_size)
Definition fft.H:1381
static Array< Array< Array< Complex > > > pbatched_stft(ThreadPool &pool, const Array< Array< Real > > &signals, const Array< Real > &window, const STFTOptions &options, const size_t chunk_size=0)
Parallel batched STFT across signals.
Definition fft.H:11278
static Array< Array< Complex > > stft(const Container &signal, const size_t frame_size, const size_t hop_size, const bool pad_end=true)
Definition fft.H:9976
static Array< Real > filtfilt(const Array< Real > &signal, const Array< Real > &coeffs, const size_t block_size=0)
Zero-phase FIR filtering via forward-backward convolution.
Definition fft.H:13578
static Array< T > to_array(const Container &input)
Definition fft.H:5031
static Array< Complex > compact_real_spectrum(const Array< Complex > &full_spectrum)
Definition fft.H:7696
static Array< size_t > axis_base_offsets(const Array< size_t > &shape, const Array< size_t > &strides, const size_t axis)
Rationale: Generates the base memory offsets for all 1D slices along a specific axis in a multidimens...
Definition fft.H:5180
static Real kaiser_beta(const Real attenuation_db)
Returns the Kaiser beta that corresponds to an attenuation goal.
Definition fft.H:8901
static Array< PoleZeroPair > pair_poles_and_zeros(const BiquadSection &section)
Definition fft.H:12368
static Array< Complex > pspectrum(ThreadPool &pool, const Array< Real > &input, const size_t chunk_size=0)
Parallel version of spectrum(const Array<Real>&).
Definition fft.H:8615
static Array< T > slice_copy(const Array< T > &input, const size_t offset, const size_t length)
Functional slice extraction.
Definition fft.H:532
static Array< PoleZeroPair > pair_poles_and_zeros(const Array< Complex > &zeros, const Array< Complex > &poles)
Greedily pairs zeros and poles by nearest distance.
Definition fft.H:12348
static void transform_axes(Array< Complex > &data, const TensorLayout &layout, const Array< size_t > &axes, const bool invert)
In-place FFT/IFFT along multiple tensor axes of a flat buffer.
Definition fft.H:8050
static Array< size_t > tensor3_shape(const Array< Array< Array< Complex > > > &input, const char *ctx)
Rationale: Validates that a nested Array represents a 3D tensor and returns its {d0,...
Definition fft.H:5398
and static Is_Real_Container< CoeffContainer > Array< Real > filtfilt(const SignalContainer &signal, const CoeffContainer &coeffs, const size_t block_size=0)
Definition fft.H:13643
and static Is_Biquad_Container< SectionsContainer > Array< Array< Real > > batched_sosfilt(const SignalsContainer &signals, const SectionsContainer &sections)
Definition fft.H:12145
static Array< BiquadSection > transfer_function_to_sections(const Array< Real > &numerator, const Array< Real > &denominator, const char *ctx)
overload of transfer_function_to_sections using raw coefficients.
Definition fft.H:3692
static Array< Array< Real > > batched_sosfilt(const Array< Array< Real > > &signals, const Array< BiquadSection > &sections)
One-shot batched causal SOS filtering across real channels.
Definition fft.H:12108
static const char * simd_backend_name() noexcept
Returns the active default SIMD backend name for this precision.
Definition fft.H:6216
static Array< Array< Real > > pirfft_batch(ThreadPool &pool, const Array< Array< Complex > > &spectra, const size_t signal_size, const size_t chunk_size=0)
Parallel compact real inverse FFT for equal-length batches.
Definition fft.H:8004
static Array< Real > magnitude_spectrum(const Array< Complex > &input)
Returns |X[k]| for each frequency bin in an FFT output.
Definition fft.H:8822
static Array< Complex > initialize_root_guesses(const Array< Real > &coefficients)
Definition fft.H:1697
static Array< Real > phase_delay(const Array< Real > &numerator, const Array< Real > &denominator, const size_t num_points=512, const bool whole=false)
Definition fft.H:12818
static FrequencyResponse freqz(const SectionsContainer &sections, const size_t num_points=512, const bool whole=false)
Definition fft.H:12239
static Array< Complex > polynomial_from_roots_complex(const Array< Complex > &roots)
Rationale: Polynomial expansion (z-r1)(z-r2)...
Definition fft.H:2501
static Array< Real > multiply(const Array< Real > &a, const Array< Real > &b)
Multiplies two real-valued sequences using convolution.
Definition fft.H:15256
and static Is_Complex_Container< Container2 > Array< Complex > multiply(const Container1 &a, const Container2 &b)
Multiplication for complex-valued containers.
Definition fft.H:15227
static void validate_stable(const IIRCoefficients &coeffs)
Definition fft.H:12628
and static Is_Real_Container< DenContainer > void validate_no_near_pole_zero_cancellation(const NumContainer &numerator, const DenContainer &denominator, const Real tolerance)
Definition fft.H:12771
static AnalogPrototype chebyshev2_prototype(const size_t order, const Real attenuation_db, const char *ctx)
Definition fft.H:3280
static Array< Complex > transform(const Array< Real > &input)
Computes the FFT of a real-valued input sequence.
Definition fft.H:8456
and Is_Real_Container< NumContainer > and static Is_Real_Container< DenContainer > Array< Array< Real > > batched_lfilter(const SignalsContainer &signals, const NumContainer &numerator, const DenContainer &denominator, const Array< Array< Real > > &initial_states={})
Definition fft.H:12074
static Array< Complex > ptransform_padded(ThreadPool &pool, const Array< Complex > &input, const size_t chunk_size=0)
Definition fft.H:8355
static Array< RootGroup > root_groups_impl(const Array< Complex > &raw_roots, const char *ctx)
Rationale: Partitions a set of roots into real sections and conjugate pairs to facilitate SOS decompo...
Definition fft.H:2754
static Array< Array< Complex > > project_to_plan_batch_spectra(const Plan &plan, const Array< Array< Real > > &input, ThreadPool *pool=nullptr, const size_t chunk_size=0)
Definition fft.H:7678
static AnalogPrototype bessel_prototype(const size_t order, const char *ctx)
implementation of the Bessel filter analog prototype.
Definition fft.H:3480
static size_t next_power_of_two(const size_t n)
Calculates the smallest power of two greater than or equal to n.
Definition fft.H:5827
static Array< BiquadSection > elliptic_lowpass(const size_t order, const Real ripple_db, const Real attenuation_db, const Real cutoff_frequency, const Real sample_rate)
Digital elliptic/Cauer low-pass design returned as SOS.
Definition fft.H:13485
static PhaseMarginInfo phase_margin(const Array< BiquadSection > &sections, const size_t num_points=1024, const bool whole=false)
Definition fft.H:13087
static Array< Real > pistft(ThreadPool &pool, const Array< Array< Complex > > &spectrogram, const Array< Real > &window, const ISTFTOptions &options, const size_t chunk_size=0)
Parallel ISTFT using a shared analysis/synthesis window.
Definition fft.H:10231
static Array< Array< Complex > > stft_impl(const Array< Real > &signal, const Array< Real > &window, const STFTOptions &options, ThreadPool *pool=nullptr, const size_t chunk_size=0)
Definition fft.H:1438
static Array< Real > real_polynomial_from_roots(const Array< Complex > &roots, const char *ctx)
Rationale: Constructs a real-coefficient polynomial from a set of complex roots, verifying that roots...
Definition fft.H:2666
static const char * batched_plan_simd_backend_name() noexcept
Returns the active batch-plan SIMD backend name.
Definition fft.H:6247
static Array< Complex > ptransform_padded(ThreadPool &pool, const Container &input, const size_t chunk_size=0)
Parallel version of transform_padded(const Container&).
Definition fft.H:8547
static bool has_near_pole_zero_cancellation(const SectionsContainer &sections, const Real tolerance)
Definition fft.H:12605
static Array< Real > kaiser_window(const size_t n, const Real beta)
Returns a Kaiser window of length n.
Definition fft.H:8917
static Array< Real > group_delay(const IIRCoefficients &coeffs, const size_t num_points=512, const bool whole=false)
Definition fft.H:12855
static Array< T > polynomial_multiply(const Array< T > &lhs, const Array< T > &rhs)
Rationale: Standard polynomial multiplication (O(N*M)).
Definition fft.H:2407
static Array< Real > power_spectrum(const Array< Complex > &input)
Returns |X[k]|^2 for each frequency bin in an FFT output.
Definition fft.H:8842
static Array< Array< Complex > > inverse_transform_batch(const Array< Array< Complex > > &input)
Functional batch IFFT wrapper.
Definition fft.H:7952
static Array< size_t > factor_small_radices(size_t n)
Rationale: Factors a number into small primes (2, 3, 4, 5) to determine if a composite FFT kernel is ...
Definition fft.H:5966
static void transform_axis_impl(Array< Complex > &data, const TensorLayout &layout, const size_t axis, const bool invert, ThreadPool *pool=nullptr, const size_t chunk_size=0)
Rationale: Internal implementation of a 1D FFT along a single tensor axis.
Definition fft.H:5262
static Array< Complex > poles(const BiquadSection &section)
Definition fft.H:12291
static Array< Real > resample_poly(const Array< Real > &signal, const size_t up, const size_t down, const ResamplePolyOptions &options={})
Polyphase resampling with an internally designed Kaiser FIR.
Definition fft.H:9389
static Array< Complex > poles(const Container &denominator)
Definition fft.H:12325
static PolynomialEvaluation evaluate_polynomial_and_derivative(const Array< Real > &coefficients, const Complex &x, const char *ctx)
Definition fft.H:1549
static Array< Real > iir_filtfilt_impl(const Array< Real > &signal, const Array< Real > &numerator, const Array< Real > &denominator, const char *ctx)
Definition fft.H:1152
static Array< Complex > pspectrum(ThreadPool &pool, const Container &input, const size_t chunk_size=0)
Parallel version of spectrum(const Container&).
Definition fft.H:8653
static Array< PoleZeroPair > pair_poles_and_zeros(const IIRCoefficients &coeffs)
Definition fft.H:12362
and static Is_Real_Container< DenContainer > Array< Real > group_delay(const NumContainer &numerator, const DenContainer &denominator, const size_t num_points=512, const bool whole=false)
Definition fft.H:12927
static Real transform_stages(const size_t n) noexcept
Returns log2(N) stages, used for numerical error scaling.
Definition fft.H:408
static Array< PoleZeroPair > pole_zero_pairs_impl(const Array< Complex > &zeros, const Array< Complex > &poles)
Rationale: Matches zeros and poles into pairs by minimizing Euclidean distance, aiding in SOS decompo...
Definition fft.H:2287
static Array< Real > firwin_lowpass(const size_t num_taps, const Real cutoff_frequency, const Real sample_rate, const Array< Real > &window)
FIR low-pass design via the window method.
Definition fft.H:9029
static Real window_coherent_gain(const Array< Real > &window)
Returns the average window gain.
Definition fft.H:9536
static void drop_prefix(Array< T > &input, const size_t count)
Definition fft.H:1275
and static Is_Real_Container< DenContainer > Array< PoleZeroPair > pair_poles_and_zeros(const NumContainer &numerator, const DenContainer &denominator)
Definition fft.H:12382
static Array< Real > istft(const Array< Array< Complex > > &spectrogram, const size_t frame_size, const size_t hop_size, const size_t signal_length=0)
Reconstructs a real signal using a Hann window pair.
Definition fft.H:10111
static Array< Real > firwin_bandpass(const size_t num_taps, const Real low_cutoff_frequency, const Real high_cutoff_frequency, const Real sample_rate, const Array< Real > &window)
FIR band-pass design via the window method.
Definition fft.H:9092
static size_t count_non_empty_real_batch(const Array< Array< Real > > &batch) noexcept
Definition fft.H:1319
static Real solve_elliptic_selectivity_modulus(const Real k1, const size_t order, const char *ctx)
Definition fft.H:3161
static bool should_parallelize_batch_work(ThreadPool *pool, const size_t batch_size, const size_t transform_size, const size_t min_work=8192) noexcept
Definition fft.H:1307
static Real carlson_rf_impl(Real x, Real y, Real z)
Definition fft.H:2987
static bool satisfies_nola(const Array< Real > &analysis_window, const Array< Real > &synthesis_window, const size_t hop_size)
Returns true when the window pair satisfies NOLA.
Definition fft.H:9851
static std::pair< Array< Real >, Array< Complex > > extract_repeated_unit_roots(const Array< Real > &coefficients, const char *ctx)
Definition fft.H:1584
static const char * simd_preference_name() noexcept
Returns the requested SIMD policy name.
Definition fft.H:6254
static PowerSpectralDensity welch(const Array< Real > &signal, const Array< Real > &window, const Real sample_rate, const WelchOptions &options={})
Welch one-sided PSD estimate using a custom window.
Definition fft.H:9637
static Array< Real > iir_steady_state(const Array< Real > &numerator, const Array< Real > &denominator, const char *ctx)
Definition fft.H:1062
static Array< BiquadSection > chebyshev2_bandpass(const size_t order, const Real attenuation_db, const Real low_cutoff_frequency, const Real high_cutoff_frequency, const Real sample_rate)
Digital Chebyshev-II band-pass design returned as SOS.
Definition fft.H:13368
static Array< Complex > apply_hamming_window(const Container &signal)
Definition fft.H:9481
and static Is_Real_Container< WindowContainer > Array< Real > apply_window(const SignalContainer &signal, const WindowContainer &window)
Definition fft.H:8972
static Real window_energy(const Array< Real > &window)
Returns the sum of squared window samples.
Definition fft.H:9528
and static Is_Real_Container< Container2 > Array< Real > partitioned_convolution(const Container1 &signal, const Container2 &kernel, const size_t partition_size=0)
Partitioned convolution for real-valued containers.
Definition fft.H:15181
static GainMarginInfo gain_margin(const Array< BiquadSection > &sections, const size_t num_points=1024, const bool whole=false)
Definition fft.H:13103
static Complex elliptic_cd_minus_imaginary(const Real u, const Real v, const Real modulus, const char *ctx)
Definition fft.H:3198
static Real scaled_tolerance(const Real reference, const Real multiplier=Real(256)) noexcept
Definition fft.H:916
static size_t tensor_element_count(const Array< size_t > &shape, const char *ctx)
Definition fft.H:5061
static Array< Complex > spectrum(const Array< Real > &input)
DSP alias for the real forward FFT.
Definition fft.H:8608
static PhaseMarginInfo phase_margin(const IIRCoefficients &coeffs, const size_t num_points=1024, const bool whole=false)
Definition fft.H:13049
and Is_Real_Container< DesiredContainer > and static Is_Real_Container< WeightContainer > Array< Real > firls(const size_t num_taps, const BandContainer &bands, const DesiredContainer &desired, const Real sample_rate, const WeightContainer &weights)
Definition fft.H:9222
static Real minimum_pole_zero_distance(const IIRCoefficients &coeffs)
Definition fft.H:12413
static GainMarginInfo gain_margin_impl(const FrequencyResponse &response)
implementation helper for Gain Margin calculation.
Definition fft.H:4564
static Array< Complex > flatten_tensor3_row_major(const Array< Array< Array< Complex > > > &input, const char *ctx)
Rationale: Flattens a 3D tensor into a 1D row-major array.
Definition fft.H:5428
static bool is_one_sided_interior_bin(const size_t bin, const size_t fft_size) noexcept
Identifies non-redundant spectrum bins (excluding DC and Nyquist).
Definition fft.H:794
and static Is_Complex_Container< Container2 > Array< Complex > pmultiply(ThreadPool &pool, const Container1 &a, const Container2 &b, const size_t chunk_size=0)
Parallel version of multiply(const Container1&, const Container2&).
Definition fft.H:15236
static Array< T > reverse_copy(const Array< T > &input)
Functional reverse.
Definition fft.H:504
static Array< Complex > transform(const Container &input)
Forward FFT for real-valued containers.
Definition fft.H:8557
static void enforce_real_polynomial_symmetry(Array< Complex > &roots, const Real tol) noexcept
Definition fft.H:1864
static Array< Real > group_delay_impl(const FrequencyResponse &response)
implementation of numeric group delay calculation.
Definition fft.H:4809
static Real minimum_pole_zero_distance(const SectionsContainer &sections)
Definition fft.H:12443
static Array< Real > blackman_window(const size_t n)
Returns a Blackman window of length n.
Definition fft.H:8894
static size_t default_filtfilt_pad_length(const size_t signal_size, const size_t coeff_size) noexcept
Rationale: Rule-of-thumb for filtfilt padding to allow transients to decay.
Definition fft.H:550
and static Is_Real_Container< DenContainer > bool has_near_pole_zero_cancellation(const NumContainer &numerator, const DenContainer &denominator, const Real tolerance)
Definition fft.H:12593
static Array< Complex > inverse_transform(const Array< Complex > &input)
Computes the Inverse Fast Fourier Transform (IFFT).
Definition fft.H:8670
static void polish_roots_with_newton(const Array< Real > &coefficients, Array< Complex > &roots, const Real tol, const size_t iterations) noexcept
Definition fft.H:1841
static Array< Complex > spectrum(const Container &input)
DSP alias for real-valued container forward FFT.
Definition fft.H:8644
static AnalogPrototype elliptic_prototype(const size_t order, const Real ripple_db, const Real attenuation_db, const char *ctx)
Definition fft.H:3332
static Real sum_squares(const Array< Real > &input) noexcept
L2 norm squared.
Definition fft.H:753
static void ptransform_batch(ThreadPool &pool, Array< Array< Complex > > &batch, const bool invert, const size_t chunk_size=0)
Parallel in-place batch transform for equal-length inputs.
Definition fft.H:7916
static Array< Real > zero_padded_copy(const Array< Real > &input, const size_t n)
Returns a copy of input zero-padded to size n.
Definition fft.H:4996
static void validate_no_near_pole_zero_cancellation(const Array< Complex > &zeros, const Array< Complex > &poles, const Real tolerance)
Definition fft.H:12718
static PhaseMarginInfo phase_margin(const FrequencyResponse &response)
Estimates phase margin around the unity-gain crossover.
Definition fft.H:12994
static Array< Complex > transform_real_optimized(const Array< Real > &input, ThreadPool *pool=nullptr, const size_t chunk_size=0)
Rationale: Optimized FFT for real signals of size N (power-of-two).
Definition fft.H:5555
and static Is_Real_Container< Container2 > Array< Real > overlap_add_convolution(const Container1 &signal, const Container2 &kernel, const size_t block_size=0)
Overlap-add convolution for real-valued containers.
Definition fft.H:15138
static Real minimum_pole_zero_distance(const Array< Real > &numerator, const Array< Real > &denominator)
Definition fft.H:12406
static Array< BiquadSection > chebyshev1_bandpass(const size_t order, const Real ripple_db, const Real low_cutoff_frequency, const Real high_cutoff_frequency, const Real sample_rate)
Digital Chebyshev-I band-pass design returned as SOS.
Definition fft.H:13293
static Real series_zero_tolerance(const size_t coeff_count, const Real multiplier=Real(256)) noexcept
Definition fft.H:924
static Array< Complex > transformed(const Array< Complex > &input, const bool invert=false)
Computes the FFT or IFFT and returns a new array.
Definition fft.H:7875
static constexpr bool Is_Real_Container
Definition fft.H:372
static bool overlap_profile_has_nola(const Array< Real > &profile) noexcept
Rationale: Non-zero Overlap-Add (NOLA) ensures the signal can be reconstructed (it's never multiplied...
Definition fft.H:646
static Real integrate_linear_cos_basis(const Real omega_lo, const Real omega_hi, const Real desired_lo, const Real desired_hi, const size_t harmonic) noexcept
implementation helper for firls linear band integration.
Definition fft.H:3926
static bool has_near_pole_zero_cancellation(const IIRCoefficients &coeffs, const Real tolerance)
Definition fft.H:12564
static IIRCoefficients normalize_iir_coefficients(const Array< Real > &numerator, const Array< Real > &denominator, const char *ctx)
Definition fft.H:933
static Array< Array< Complex > > transformed_batch(const Array< Array< Complex > > &input, const bool invert=false)
Functional batch FFT/IFFT wrapper.
Definition fft.H:7930
static Array< Real > istft(const Array< Array< Complex > > &spectrogram, const size_t frame_size, const ISTFTOptions &options)
Reconstructs a Hann-window STFT with explicit options.
Definition fft.H:10204
static bool try_laguerre_roots(const Array< Real > &coefficients, const Real tol, Array< Complex > &roots) noexcept
Definition fft.H:1933
static Array< BiquadSection > design_prototype_sections(const AnalogPrototype &prototype, const size_t order, const Real cutoff_frequency, const Real sample_rate, const bool highpass, const char *ctx)
Definition fft.H:3546
static FrequencyResponse freqz(const Array< Real > &numerator, const Array< Real > &denominator, const size_t num_points=512, const bool whole=false)
Samples the discrete-time transfer response on the unit circle.
Definition fft.H:12156
static Real max_abs_value(const Array< Real > &input) noexcept
L-infinity norm.
Definition fft.H:733
static Array< Real > bilinear_substitute_polynomial(const Array< Real > &analog, const size_t order, const Real sample_rate, const char *ctx)
Rationale: Performs the bilinear substitution mapping from the S-plane to the Z-domain: H(z) = H(s)|s...
Definition fft.H:2466
static Array< Complex > zero_padded_copy(const Array< Complex > &input, const size_t n)
Returns a copy of input zero-padded to size n.
Definition fft.H:4979
static bool has_near_pole_zero_cancellation(const BiquadSection &section, const Real tolerance)
Definition fft.H:12573
static Real integrate_cos_basis(const Real omega_lo, const Real omega_hi, const size_t harmonic) noexcept
Rationale: Evaluates integral of cos(k*w) from omega_lo to omega_hi.
Definition fft.H:3884
static void validate_no_near_pole_zero_cancellation(const Array< Real > &numerator, const Array< Real > &denominator, const Real tolerance)
Definition fft.H:12732
static Array< BiquadSection > design_low_high_sections(const AnalogPrototype &prototype, const Real cutoff_frequency, const Real sample_rate, const bool highpass, const char *ctx)
internal helper for designing standard lowpass/highpass SOS filters.
Definition fft.H:3530
static Array< BiquadSection > chebyshev1_highpass(const size_t order, const Real ripple_db, const Real cutoff_frequency, const Real sample_rate)
Digital Chebyshev-I high-pass design returned as SOS.
Definition fft.H:13276
and static Is_Real_Container< DesiredContainer > Array< Real > firls(const size_t num_taps, const BandContainer &bands, const DesiredContainer &desired, const Real sample_rate)
Definition fft.H:9204
static CrossSpectralDensity csd(const Array< Real > &x, const Array< Real > &y, const size_t frame_size, const Real sample_rate, const WelchOptions &options={})
One-sided cross-spectral density estimate using a Hann window.
Definition fft.H:9738
static Array< Real > firwin_lowpass_impl(const size_t num_taps, const Real cutoff_frequency, const Real sample_rate, const Array< Real > &window, const char *ctx)
implementation of the window-method FIR design.
Definition fft.H:3846
and static Is_Real_Container< WindowContainer > Array< Complex > apply_window(const SignalContainer &signal, const WindowContainer &window)
Definition fft.H:8980
static void apply_one_sided_density_scaling(Density &value, const size_t bin, const size_t fft_size) noexcept
Rationale: Multiplies spectral densities by 2 for interior bins to account for energy in the omitted ...
Definition fft.H:860
static Array< Real > pmultiply(ThreadPool &pool, const Array< Real > &a, const Array< Real > &b, const size_t chunk_size=0)
Parallel version of multiply(const Array<Real>&, const Array<Real>&).
Definition fft.H:15263
static Array< Complex > ptransform_padded(ThreadPool &pool, const Container &input, const size_t chunk_size=0)
Parallel zero-pad and forward-FFT for a generic complex iterable.
Definition fft.H:8398
static Array< Array< Array< Complex > > > multichannel_stft(const Array< Array< Real > > &signals, const Array< Real > &window, const STFTOptions &options={}, const SpectrogramLayout layout=SpectrogramLayout::channel_frame_bin)
Multichannel STFT with explicit channel-major or frame-major output layout.
Definition fft.H:8260
static Real minimum_pole_zero_distance(const BiquadSection &section)
Definition fft.H:12419
static bool satisfies_cola(const Array< Real > &analysis_window, const Array< Real > &synthesis_window, const size_t hop_size)
Returns true when the window pair satisfies COLA.
Definition fft.H:9862
static void validate_no_near_pole_zero_cancellation(const SectionsContainer &sections, const Real tolerance)
Definition fft.H:12783
and static Is_Real_Container< DenContainer > FrequencyResponse freqz(const NumContainer &numerator, const DenContainer &denominator, const size_t num_points=512, const bool whole=false)
Definition fft.H:12225
static bool neon_runtime_available() noexcept
Returns whether NEON dispatch is supported at runtime.
Definition fft.H:6268
and static Is_Real_Container< WindowContainer > Array< Complex > windowed_spectrum(const SignalContainer &signal, const WindowContainer &window)
Definition fft.H:9520
static Array< Real > substitute_rational_polynomial(const Array< Real > &poly, const Array< Real > &numerator, const Array< Real > &denominator, const char *ctx)
Rationale: Evaluates P(N(z)/D(z)) by scaling by the common denominator D(z)^degree and evaluating the...
Definition fft.H:2553
static TransferTerms evaluate_transfer_terms_at(const Array< Real > &numerator, const Array< Real > &denominator, const Real omega, const char *ctx)
implementation helper for evaluating H(z) and its derivatives on the unit circle.
Definition fft.H:2146
static AnalogPrototype chebyshev1_prototype(const size_t order, const Real ripple_db, const char *ctx)
Definition fft.H:3241
static Array< Complex > ptransform(ThreadPool &pool, const Container &input, const size_t chunk_size=0)
Parallel version of transform(const Container&).
Definition fft.H:8566
static Array< Real > iir_filter_impl(const Array< Real > &signal, const Array< Real > &numerator, const Array< Real > &denominator, const Array< Real > &initial_state, const char *ctx, Array< Real > *final_state=nullptr)
Definition fft.H:1101
static void validate_stable(const BiquadSection &section)
Definition fft.H:12634
static Array< Complex > windowed_spectrum(const Array< Real > &signal, const Array< Real > &window)
Returns the FFT of a real signal after applying a window.
Definition fft.H:9496
static Array< size_t > choose_remez_extrema(const Array< Real > &weighted_error, const size_t required_count, const char *ctx)
Definition fft.H:4193
static Array< Complex > transformed_axes(const Array< Complex > &input, const TensorLayout &layout, const Array< size_t > &axes, const bool invert=false)
Functional tensor FFT/IFFT wrapper for flat buffers.
Definition fft.H:8072
static Array< Complex > ptransform_padded(ThreadPool &pool, const Array< Real > &input, const size_t chunk_size=0)
Parallel version of transform_padded(const Array<Real>&).
Definition fft.H:8508
and static Is_Real_Container< DenContainer > PhaseMarginInfo phase_margin(const NumContainer &numerator, const DenContainer &denominator, const size_t num_points=1024, const bool whole=false)
Definition fft.H:13121
static GainMarginInfo gain_margin(const IIRCoefficients &coeffs, const size_t num_points=1024, const bool whole=false)
Definition fft.H:13057
static void validate_istft_configuration(const Array< Real > &analysis_window, const Array< Real > &synthesis_window, const size_t fft_size, const ISTFTOptions &options, const char *ctx)
Rationale: Validates and normalizes ISTFT parameters, checking window constraints and FFT size.
Definition fft.H:4867
static Array< Real > firwin_highpass(const size_t num_taps, const Real cutoff_frequency, const Real sample_rate, const Array< Real > &window)
FIR high-pass design via spectral inversion of a low-pass design.
Definition fft.H:9056
static GainMarginInfo gain_margin(const Array< Real > &numerator, const Array< Real > &denominator, const size_t num_points=1024, const bool whole=false)
Definition fft.H:13028
and static Is_Real_Container< DenContainer > IIRCoefficients bilinear_transform(const NumContainer &analog_numerator, const DenContainer &analog_denominator, const Real sample_rate)
Definition fft.H:13181
static Array< Real > group_delay(const Container &numerator, const size_t num_points=512, const bool whole=false)
Definition fft.H:12955
static Array< Complex > enforce_conjugate_symmetry(const Array< Complex > &roots)
Enforce conjugate symmetry on roots of a real-coefficient polynomial.
Definition fft.H:2706
static Real real_projection_tolerance(const Complex &value, const size_t n) noexcept
Rationale: Estimates the maximum expected numerical noise for a value after an N-point transform,...
Definition fft.H:418
static Array< Complex > prfft(ThreadPool &pool, const Array< Real > &input, const size_t chunk_size=0)
Parallel version of rfft(const Array<Real>&).
Definition fft.H:8493
static Array< Array< Complex > > reshape_matrix_row_major(const Array< Complex > &input, const size_t rows, const size_t cols, const char *ctx)
Rationale: Reshapes a 1D row-major array back into a 2D matrix.
Definition fft.H:5373
static void ptransform_axis(ThreadPool &pool, Array< Complex > &data, const TensorLayout &layout, const size_t axis, const bool invert, const size_t chunk_size=0)
Parallel in-place 1-D FFT/IFFT along one tensor axis.
Definition fft.H:8038
static Array< Complex > poles(const SectionsContainer &sections)
Definition fft.H:12341
and Is_Real_Container< NumContainer > and static Is_Real_Container< DenContainer > Array< Real > filtfilt(const SignalContainer &signal, const NumContainer &numerator, const DenContainer &denominator)
Definition fft.H:13713
static void transform_impl(Array< Complex > &a, const bool invert, ThreadPool *pool=nullptr, const size_t chunk_size=0)
implementation of the power-of-two iterative FFT using mixed radix-2/4.
Definition fft.H:5845
static Array< BiquadSection > chebyshev2_bandstop(const size_t order, const Real attenuation_db, const Real low_cutoff_frequency, const Real high_cutoff_frequency, const Real sample_rate)
Digital Chebyshev-II band-stop design returned as SOS.
Definition fft.H:13392
static bool is_stable(const BiquadSection &section)
Definition fft.H:12510
static Array< Real > pinverse_transform_real(ThreadPool &pool, const Array< Complex > &input, const size_t chunk_size=0)
Parallel version of inverse_transform_real(const Array<Complex>&).
Definition fft.H:8732
static Array< Complex > ptransform(ThreadPool &pool, const Array< Real > &input, const size_t chunk_size=0)
Parallel real-input FFT.
Definition fft.H:8473
static Real sum_values(const Array< Real > &input) noexcept
Sum reduction.
Definition fft.H:743
static FrequencyResponse freqz(const Array< Real > &numerator, const size_t num_points=512, const bool whole=false)
FIR frequency response.
Definition fft.H:12166
static Array< Real > cosine_sum_window(const size_t n, const Real a0, const Real a1=Real(0), const Real a2=Real(0))
Internal generator for generalized cosine windows (Hann, Hamming, Blackman).
Definition fft.H:476
static void validate_stable(const Array< BiquadSection > &sections, const Real min_margin)
Definition fft.H:12688
static void transform_batch(Array< Array< Complex > > &batch, const bool invert)
In-place batch transform for equal-length complex inputs.
Definition fft.H:7905
static void transform_axis(Array< Complex > &data, const TensorLayout &layout, const size_t axis, const bool invert)
In-place 1-D FFT/IFFT along one tensor axis of a flat buffer.
Definition fft.H:8028
static Array< Array< Array< Complex > > > pmultichannel_stft(ThreadPool &pool, const Array< Array< Real > > &signals, const Array< Real > &window, const STFTOptions &options={}, const SpectrogramLayout layout=SpectrogramLayout::channel_frame_bin, const size_t chunk_size=0)
Parallel multichannel STFT with selectable output layout.
Definition fft.H:8277
static bool satisfies_cola(const Array< Real > &window, const size_t hop_size)
Returns true when one window satisfies COLA by itself.
Definition fft.H:9880
static GainMarginInfo gain_margin_refined_impl(const FrequencyResponse &response, const Evaluator &evaluator)
implementation of refined Gain Margin calculation using analytic evaluation.
Definition fft.H:4704
static bool has_near_pole_zero_cancellation(const Array< BiquadSection > &sections, const Real tolerance)
Definition fft.H:12582
static void validate_stable(const SectionsContainer &sections, const Real min_margin)
Definition fft.H:12711
static void validate_stable(const Array< Real > &denominator)
Definition fft.H:12613
static Array< Complex > ptransform(ThreadPool &pool, const Container &input, const size_t chunk_size=0)
Parallel version of transform(const Container&).
Definition fft.H:8528
static Array< Real > apply_window(const Array< Real > &signal, const Array< Real > &window)
Applies a real window sample-by-sample to a real signal.
Definition fft.H:8943
static Real complete_elliptic_first_kind(const Real modulus, const char *ctx)
Rationale: Complete elliptic integral K(k) using the modulus convention required by elliptic filter d...
Definition fft.H:3049
static PowerSpectralDensity welch(const Array< Real > &signal, const size_t frame_size, const Real sample_rate, const WelchOptions &options={})
Welch one-sided PSD estimate using a Hann window.
Definition fft.H:9678
static Real integer_power(const Real base, const size_t exponent) noexcept
Fast integer power utility.
Definition fft.H:2440
static Array< Real > group_delay(const Array< Real > &numerator, const Array< Real > &denominator, const size_t num_points=512, const bool whole=false)
Definition fft.H:12805
static Array< BiquadSection > chebyshev1_bandstop(const size_t order, const Real ripple_db, const Real low_cutoff_frequency, const Real high_cutoff_frequency, const Real sample_rate)
Digital Chebyshev-I band-stop design returned as SOS.
Definition fft.H:13317
static Array< Array< Real > > batched_istft(const Array< Array< Array< Complex > > > &spectrograms, const Array< Real > &analysis_window, const Array< Real > &synthesis_window, const ISTFTOptions &options, const Array< size_t > &signal_lengths={})
Batched ISTFT with optional per-signal output lengths.
Definition fft.H:11329
static size_t tensor_max_offset(const Array< size_t > &shape, const Array< size_t > &strides, const char *ctx)
Definition fft.H:5093
static Array< Real > poverlap_add_convolution(ThreadPool &pool, const Array< Real > &signal, const Array< Real > &kernel, const size_t block_size=0, const size_t chunk_size=0)
Parallel convenience wrapper for overlap-add convolution.
Definition fft.H:15076
and static Is_Real_Container< DenContainer > Array< Real > phase_delay(const NumContainer &numerator, const DenContainer &denominator, const size_t num_points=512, const bool whole=false)
Definition fft.H:12941
static PhaseMarginInfo phase_margin(const SectionsContainer &sections, const size_t num_points=1024, const bool whole=false)
Definition fft.H:13149
static Array< Real > remez_impl(const size_t num_taps, const Array< Real > &bands, const Array< Real > &desired, const Real sample_rate, const Array< Real > &weights, const size_t grid_density, const size_t max_iterations, const char *ctx)
Definition fft.H:4326
static Array< Complex > ptransformed(ThreadPool &pool, const Container &input, const bool invert=false, const size_t chunk_size=0)
Parallel FFT/IFFT for a generic complex iterable.
Definition fft.H:8439
static bool is_stable(const IIRCoefficients &coeffs)
Definition fft.H:12504
static Array< BiquadSection > bessel_bandpass(const size_t order, const Real low_cutoff_frequency, const Real high_cutoff_frequency, const Real sample_rate)
Digital Bessel band-pass design returned as SOS.
Definition fft.H:13448
static void validate_stable(const Container &denominator, const Real min_margin)
Definition fft.H:12702
static Array< Real > apply_hann_window(const Container &signal)
Definition fft.H:9449
static IIRCoefficients bilinear_transform_impl(const Array< Real > &analog_numerator, const Array< Real > &analog_denominator, const Real sample_rate, const char *ctx)
implementation helper for the bilinear transform.
Definition fft.H:2895
static Array< BiquadSection > design_bandstop_sections_without_numerator_roots(const AnalogPrototype &prototype, const size_t order, const Real low_cutoff_frequency, const Real high_cutoff_frequency, const Real sample_rate, const char *ctx)
Specialization for band-stop designs to handle zeros explicitly.
Definition fft.H:3706
and static Is_Real_Container< WindowContainer > Array< Array< Complex > > pstft(ThreadPool &pool, const SignalContainer &signal, const WindowContainer &window, const STFTOptions &options, const size_t chunk_size=0)
Definition fft.H:10037
static Array< size_t > row_major_strides(const Array< size_t > &shape, const char *ctx)
Definition fft.H:5078
SpectrogramLayout
Logical layout for multichannel spectrograms.
Definition fft.H:349
static Array< Real > phase_delay(const Container &numerator, const size_t num_points=512, const bool whole=false)
Definition fft.H:12965
static Array< Complex > transformed(const Container &input, const bool invert=false)
FFT/IFFT for a generic complex iterable.
Definition fft.H:8420
static Array< Complex > transform_padded(const Array< Real > &input)
Computes the FFT after zero-padding the input to the next power of two.
Definition fft.H:8501
static void transform(Array< Complex > &a, const bool invert)
Computes the Fast Fourier Transform (FFT) in-place.
Definition fft.H:7838
static Real window_enbw(const Array< Real > &window)
Returns the equivalent noise bandwidth of a window in bins.
Definition fft.H:9545
static Array< Array< Complex > > ptransformed_batch(ThreadPool &pool, const Array< Array< Complex > > &input, const bool invert=false, const size_t chunk_size=0)
Parallel functional batch FFT/IFFT wrapper.
Definition fft.H:7940
static Array< Real > phase_delay(const IIRCoefficients &coeffs, const size_t num_points=512, const bool whole=false)
Definition fft.H:12863
static Array< Complex > project_to_plan_spectrum(const Plan &plan, const Array< Real > &input, ThreadPool *pool=nullptr, const size_t chunk_size=0)
Definition fft.H:7664
static Real prewarp_frequency(const Real cutoff_frequency, const Real sample_rate, const char *ctx)
Rationale: Prewarps an analog cutoff frequency to compensate for the non-linear frequency mapping of ...
Definition fft.H:2926
static Array< Real > apply_hamming_window(const Array< Real > &signal)
Applies a Hamming window of matching size to a real signal.
Definition fft.H:8994
static JacobiValues jacobi_sn_cn_dn(const Real argument, const Real modulus, const char *ctx)
Rationale: Simultanous calculation of Jacobi sn, cn, dn functions using the descending Landen transfo...
Definition fft.H:3061
static Array< size_t > matrix_shape(const Array< Array< Complex > > &input, const char *ctx)
Rationale: Validates that an Array of Arrays represents a rectangular matrix and returns its {rows,...
Definition fft.H:5340
static Array< Complex > flatten_matrix_row_major(const Array< Array< Complex > > &input, const char *ctx)
Rationale: Flattens a 2D matrix into a 1D row-major array.
Definition fft.H:5359
static Array< Real > partitioned_convolution(const Array< Real > &signal, const Array< Real > &kernel, const size_t partition_size=0)
Convenience wrapper for low-latency partitioned convolution.
Definition fft.H:15125
static Array< Array< Array< Complex > > > transpose_spectrogram_layout(const Array< Array< Array< Complex > > > &input, const SpectrogramLayout source, const SpectrogramLayout target)
Converts multichannel spectrograms between channel-major and frame-major layouts.
Definition fft.H:8246
static Array< Real > filtfilt(const Array< Real > &signal, const Array< Real > &numerator, const Array< Real > &denominator)
Zero-phase IIR filtering using transfer-function coefficients.
Definition fft.H:13674
static Array< Complex > expand_real_spectrum(const Array< Complex > &spectrum, const size_t signal_size, const char *ctx)
Definition fft.H:7744
static void transform_any_size_impl(Array< Complex > &a, const bool invert, ThreadPool *pool=nullptr, const size_t chunk_size=0)
Definition fft.H:7787
static Array< Array< Complex > > transformed2d(const Array< Array< Complex > > &input, const bool invert=false)
Functional 2-D FFT/IFFT wrapper for rectangular complex matrices.
Definition fft.H:8098
static Array< Real > pistft(ThreadPool &pool, const Array< Array< Complex > > &spectrogram, const Array< Real > &window, const size_t hop_size, const size_t signal_length=0, const size_t chunk_size=0)
Parallel STFT inversion using a shared analysis/synthesis window.
Definition fft.H:10143
static Array< Real > inverse_transform_real(const Array< Complex > &input)
Computes the IFFT and projects the result back to real values.
Definition fft.H:8714
static Array< Complex > pmultiply(ThreadPool &pool, const Array< Complex > &a, const Array< Complex > &b, const size_t chunk_size=0)
Parallel version of multiply(const Array<Complex>&, const Array<Complex>&).
Definition fft.H:15217
static constexpr bool Is_Complex_Container
Definition fft.H:364
static Array< Real > scaled_copy(const Array< Real > &input, const Real factor)
Scalar scaling.
Definition fft.H:885
static Array< Complex > gather_axis_slice(const Array< Complex > &data, const size_t base_offset, const size_t axis_length, const size_t axis_stride)
Rationale: Extracts a non-contiguous slice of data along a tensor axis into a contiguous array for FF...
Definition fft.H:5234
static Array< Real > istft(const Array< Array< Complex > > &spectrogram, const Array< Real > &window, const size_t hop_size, const size_t signal_length=0)
Reconstructs a real signal using the same window for analysis and synthesis.
Definition fft.H:10101
static Array< Array< Array< Complex > > > transformed3d(const Array< Array< Array< Complex > > > &input, const bool invert=false)
Functional 3-D FFT/IFFT wrapper for rectangular complex tensors.
Definition fft.H:8188
static size_t recommended_cache_tile_size(const size_t transform_size, const size_t batch_size, ThreadPool *pool) noexcept
Definition fft.H:1329
static Array< Complex > pinverse_transform(ThreadPool &pool, const Array< Complex > &input, const size_t chunk_size=0)
Parallel version of inverse_transform(const Array<Complex>&).
Definition fft.H:8677
static IIRCoefficients bilinear_transform(const Array< Real > &analog_numerator, const Array< Real > &analog_denominator, const Real sample_rate)
Bilinear transform of an analog transfer function.
Definition fft.H:13168
static size_t resolve_welch_hop_size(const WelchOptions &options, const size_t frame_size, const char *ctx)
Default hop size for Welch analysis (50% overlap).
Definition fft.H:806
and static Is_Real_Container< CoeffContainer > Array< Real > upfirdn(const SignalContainer &signal, const CoeffContainer &coeffs, const size_t up=1, const size_t down=1)
Definition fft.H:9335
static SimdPreference simd_preference() noexcept
Returns the runtime SIMD preference requested via environment.
Definition fft.H:6165
static Array< Complex > apply_hann_window(const Container &signal)
Definition fft.H:9473
static void trim_to_size(Array< Complex > &input, const size_t n)
Trims input to size n.
Definition fft.H:5013
static void validate_stable(const BiquadSection &section, const Real min_margin)
Definition fft.H:12681
static Real polynomial_root_lower_bound(const Array< Real > &monic) noexcept
Definition fft.H:1632
and static Is_Biquad_Container< SectionsContainer > Array< Real > filtfilt(const SignalContainer &signal, const SectionsContainer &sections)
Definition fft.H:13744
static Array< Array< Array< Complex > > > reshape_tensor3_row_major(const Array< Complex > &input, const size_t dim0, const size_t dim1, const size_t dim2, const char *ctx)
Rationale: Reshapes a 1D row-major array back into a 3D tensor.
Definition fft.H:5443
static Real stability_margin(const BiquadSection &section)
Definition fft.H:12462
static Array< BiquadSection > butterworth_bandstop(const size_t order, const Real low_cutoff_frequency, const Real high_cutoff_frequency, const Real sample_rate)
Digital Butterworth band-stop design returned as SOS.
Definition fft.H:13244
static PhaseMarginInfo phase_margin_refined_impl(const FrequencyResponse &response, const Evaluator &evaluator)
implementation of refined Phase Margin calculation using analytic evaluation.
Definition fft.H:4638
static Array< Real > firls(const size_t num_taps, const Array< Real > &bands, const Array< Real > &desired, const Real sample_rate, const Array< Real > &weights={})
FIR design by weighted least squares over piecewise-linear bands.
Definition fft.H:9186
static Array< Array< Complex > > inverse_transform2d(const Array< Array< Complex > > &input)
Functional inverse 2-D FFT wrapper.
Definition fft.H:8133
static Array< Array< Real > > pbatched_istft(ThreadPool &pool, const Array< Array< Array< Complex > > > &spectrograms, const Array< Real > &window, const ISTFTOptions &options, const Array< size_t > &signal_lengths={}, const size_t chunk_size=0)
Parallel batched ISTFT using a shared analysis/synthesis window.
Definition fft.H:11431
Real value_type
Definition fft.H:160
static Array< Array< Real > > irfft_batch(const Array< Array< Complex > > &spectra, const size_t signal_size)
Functional compact real inverse FFT for equal-length batches.
Definition fft.H:7992
static Array< size_t > frame_offsets_impl(const size_t signal_size, const size_t frame_size, const size_t hop_size, const bool pad_end, const char *ctx)
Computes framing offsets.
Definition fft.H:704
and static Is_Biquad_Container< SectionsContainer > Array< Real > sosfilt(const SignalContainer &signal, const SectionsContainer &sections)
Definition fft.H:12100
static Array< Real > zero_pad_edges(const Array< Real > &signal, const size_t left_pad, const size_t right_pad)
functional zero-padding at both ends.
Definition fft.H:596
static Real polynomial_root_upper_bound(const Array< Real > &monic) noexcept
Definition fft.H:1621
static Array< PoleZeroPair > pair_poles_and_zeros(const SectionsContainer &sections)
Definition fft.H:12392
static Array< Array< Complex > > pinverse_transform_batch(ThreadPool &pool, const Array< Array< Complex > > &input, const size_t chunk_size=0)
Parallel functional batch IFFT wrapper.
Definition fft.H:7959
static Real integrate_cos_product(const Real omega_lo, const Real omega_hi, const size_t lhs_harmonic, const size_t rhs_harmonic) noexcept
Rationale: Evaluates integral of cos(m*w)*cos(n*w).
Definition fft.H:3897
static Array< Real > reverse_bessel_polynomial(const size_t order, const char *ctx)
Rationale: Generates coefficients for the Reverse Bessel Polynomial used in Bessel filter prototypes.
Definition fft.H:3457
static Array< Real > phase_spectrum(const Array< Complex > &input)
Returns arg(X[k]) for each frequency bin in an FFT output.
Definition fft.H:8861
static Array< Real > magnitude_spectrum(const Container &input)
Magnitude spectrum for complex-valued containers.
Definition fft.H:8835
static void ptransform_axes(ThreadPool &pool, Array< Complex > &data, const TensorLayout &layout, const Array< size_t > &axes, const bool invert, const size_t chunk_size=0)
Parallel in-place FFT/IFFT along multiple tensor axes.
Definition fft.H:8060
static Array< size_t > normalize_axes(const Array< size_t > &axes, const size_t rank, const char *ctx)
Rationale: Validates axis indices against the tensor rank and ensures no axis is specified more than ...
Definition fft.H:5152
static Array< Real > window_overlap_profile(const Array< Real > &analysis_window, const Array< Real > &synthesis_window, const size_t hop_size)
Returns the overlap-add normalization profile for one hop period.
Definition fft.H:9839
static TensorLayout normalize_tensor_layout(const Array< Complex > &data, const TensorLayout &layout, const char *ctx)
Rationale: Validates a tensor layout and infers row-major strides if they are not provided.
Definition fft.H:5116
static Array< Array< Complex > > transform_stft_frames(const Array< Array< Real > > &frames, const Array< Real > &window, const size_t fft_size, const Plan &plan, ThreadPool *pool, const size_t chunk_size)
Definition fft.H:1389
static Array< Complex > transform_padded(const Container &input)
Zero-pad and forward-FFT a generic complex iterable.
Definition fft.H:8380
static void validate_stable(const Container &denominator)
Definition fft.H:12649
static Array< BiquadSection > chebyshev1_lowpass(const size_t order, const Real ripple_db, const Real cutoff_frequency, const Real sample_rate)
Digital Chebyshev-I low-pass design returned as SOS.
Definition fft.H:13259
static Array< Array< Complex > > stft(const Array< Real > &signal, const size_t frame_size, const STFTOptions &options)
Computes a Hann-window STFT with explicit analysis options.
Definition fft.H:10006
static Array< Array< Array< Complex > > > batched_stft(const Array< Array< Real > > &signals, const Array< Real > &window, const STFTOptions &options)
Batched STFT over a collection of real signals.
Definition fft.H:11263
static Array< Real > filtfilt(const SignalContainer &signal, const IIRCoefficients &coeffs)
Definition fft.H:13725
static Real inverse_jacobi_sc(const Real value, const Real modulus, const char *ctx)
Definition fft.H:3147
static Array< Real > inverse_transform_real(const Container &input)
Inverse real transform for complex-valued containers.
Definition fft.H:8781
static Array< Array< Array< Complex > > > transformed2d_batch(const Array< Array< Array< Complex > > > &input, const bool invert=false)
Functional 2-D batched FFT/IFFT wrapper over a matrix stack.
Definition fft.H:8149
static Array< Complex > zeros(const Array< BiquadSection > &sections)
Definition fft.H:12297
static Array< Real > group_delay(const BiquadSection &section, const size_t num_points=512, const bool whole=false)
Definition fft.H:12871
static Complex twiddle_at(const Real angle, const size_t index)
Computes a single twiddle factor exp(j * angle * index).
Definition fft.H:401
static Array< Real > reflect_pad_signal(const Array< Real > &signal, const size_t pad_len)
Rationale: Applies reflection padding at signal edges to reduce boundary artifacts during filtering.
Definition fft.H:568
std::complex< Real > Complex
Definition fft.H:161
static Array< Complex > apply_blackman_window(const Container &signal)
Definition fft.H:9489
static Real evaluate_analog_transfer_magnitude(const Array< Real > &numerator, const Array< Real > &denominator, const Real omega, const char *ctx)
Rationale: Direct evaluation of |H(j*omega)| for an analog transfer function.
Definition fft.H:3432
static Array< Real > overlap_add_frames(const Array< Array< Real > > &frames, const size_t hop_size, const size_t signal_length=0)
Overlap-adds a frame sequence with a fixed hop size.
Definition fft.H:9596
static Array< Complex > transform_padded(const Array< Complex > &input)
Definition fft.H:8347
static Array< Real > overlap_add_convolution(const Array< Real > &signal, const Array< Real > &kernel, const size_t block_size=0)
Convenience wrapper for long real convolution via overlap-add.
Definition fft.H:15064
static Array< Complex > poles(const Array< Real > &denominator)
Returns the poles of a transfer denominator.
Definition fft.H:12263
static bool satisfies_nola(const Array< Real > &window, const size_t hop_size)
Returns true when one window satisfies NOLA by itself.
Definition fft.H:9873
static Array< Real > pistft(ThreadPool &pool, const Array< Array< Complex > > &spectrogram, const size_t frame_size, const ISTFTOptions &options, const size_t chunk_size=0)
Parallel Hann-window ISTFT with explicit options.
Definition fft.H:10242
static bool is_stable(const Array< BiquadSection > &sections)
Definition fft.H:12516
static bool overlap_profile_has_cola(const Array< Real > &profile) noexcept
Rationale: Constant Overlap-Add (COLA) ensures that OLA reconstruction has no amplitude modulation ar...
Definition fft.H:660
static Array< Real > firwin_bandstop(const size_t num_taps, const Real low_cutoff_frequency, const Real high_cutoff_frequency, const Real sample_rate, const Real attenuation_db)
FIR band-stop design using a Kaiser window.
Definition fft.H:9171
static Array< Real > resample_poly(const Array< Real > &signal, const size_t up, const size_t down, const Array< Real > &coeffs)
Polyphase resampling with explicit FIR coefficients.
Definition fft.H:9348
static void add_scaled_polynomial(Array< T > &dst, const Array< T > &src, const T scale)
Rationale: In-place addition of a scaled polynomial.
Definition fft.H:2426
static Array< Complex > windowed_spectrum(const Array< Complex > &signal, const Array< Real > &window)
Returns the FFT of a complex signal after applying a window.
Definition fft.H:9503
static size_t effective_coeff_length(const Array< Real > &input) noexcept
Returns length excluding trailing insignificant zeros.
Definition fft.H:870
static Array< Complex > multiply_complex_impl(const Array< Complex > &a, const Array< Complex > &b, ThreadPool *pool=nullptr, const size_t chunk_size=0)
Rationale: Linear convolution of two complex sequences via FFT.
Definition fft.H:5724
Graph implemented with double-linked adjacency lists.
Definition tpl_graph.H:429
A reusable thread pool for efficient parallel task execution.
size_t num_threads() const noexcept
Get the number of worker threads.
Minimal std::expected-style result type for C++20.
__gmp_expr< T, __gmp_unary_expr< __gmp_expr< T, U >, __gmp_y1_function > > y1(const __gmp_expr< T, U > &expr)
Definition gmpfrxx.h:4114
__gmp_expr< T, __gmp_unary_expr< __gmp_expr< T, U >, __gmp_floor_function > > floor(const __gmp_expr< T, U > &expr)
Definition gmpfrxx.h:4066
__gmp_expr< typename __gmp_resolve_expr< T, V >::value_type, __gmp_binary_expr< __gmp_expr< T, U >, __gmp_expr< V, W >, __gmp_remainder_function > > remainder(const __gmp_expr< T, U > &expr1, const __gmp_expr< V, W > &expr2)
Definition gmpfrxx.h:4126
__gmp_expr< T, __gmp_unary_expr< __gmp_expr< T, U >, __gmp_y0_function > > y0(const __gmp_expr< T, U > &expr)
Definition gmpfrxx.h:4113
__gmp_expr< T, __gmp_binary_expr< __gmp_expr< T, U >, unsigned long int, __gmp_root_function > > root(const __gmp_expr< T, U > &expr, unsigned long int l)
Definition gmpfrxx.h:4071
size_t blossom_maximum_cardinality_matching(const GT &g, DynDlist< typename GT::Arc * > &matching, SA sa=SA())
Alias of compute_maximum_cardinality_general_matching().
Definition Blossom.H:466
const long double offset[]
Offset values indexed by symbol string length (bounded by MAX_OFFSET_INDEX)
static mpfr_t y
Definition mpfr_mul_d.c:3
constexpr State invert(const State &s) noexcept
Toggle a cell value between its zero and a non-zero counterpart.
double density(const Lattice &lat, const typename Lattice::state_type &s)
Definition ca-metrics.H:212
Compiler_SSA_Block & block(Compiler_SSA_Function &function, const Compiler_SSA_Block_Id id)
Definition SSA.H:636
Main namespace for Aleph-w library functions.
Definition ah-arena.H:89
size_t size(Node *root) noexcept
std::pair< TgtContainer< typename SrcContainer::Item_Type >, TgtContainer< typename SrcContainer::Item_Type > > partition(const SrcContainer &c, std::function< bool(const typename SrcContainer::Item_Type &)> operation)
Partition a container into two based on a predicate.
static long & low(typename GT::Node *p)
Internal helper: low-link value stored directly in NODE_COOKIE(p).
DynList< T > repeated(const Container< T > &c)
Return elements that appear more than once in the container.
void parallel_for_index(ThreadPool &pool, size_t start, size_t end, F &&f, size_t chunk_size=0)
Apply a function to each element in parallel (index-based).
and
Check uniqueness with explicit hash + equality functors.
std::decay_t< typename HeadC::Item_Type > T
Definition ah-zip.H:105
auto mean(const Container &data) -> std::decay_t< decltype(*std::begin(data))>
Compute the arithmetic mean.
Definition stat_utils.H:190
bool diff(const C1 &c1, const C2 &c2, Eq e=Eq())
Check if two containers differ.
std::pair< First, Second > pair
Alias to std::pair kept for backwards compatibility.
Definition ahPair.H:89
void error(const char *file, int line, const char *format,...)
Print an error message with file and line info.
Definition ahDefs.C:100
void next()
Advance all underlying iterators (bounds-checked).
Definition ah-zip.H:171
auto mode(const Container &data) -> std::decay_t< decltype(*std::begin(data))>
Compute the mode (most frequent value).
Definition stat_utils.H:447
auto max_value(const Container &data) -> std::decay_t< decltype(*std::begin(data))>
Compute maximum value.
Definition stat_utils.H:294
Itor::difference_type count(const Itor &beg, const Itor &end, const T &value)
Count elements equal to a value.
Definition ahAlgo.H:127
T sum(const Container &container, const T &init=T{})
Compute sum of all elements.
STL namespace.
static struct argp_option options[]
Definition ntreepic.C:1886
static void section(const string &title)
Parameters for an analog filter prototype (S-domain).
Definition fft.H:2382
Array< Complex > poles
Definition fft.H:2384
Array< Complex > zeros
Definition fft.H:2383
Array< Real > balanced
Definition fft.H:896
A stable Second-Order Section (SOS) building block.
Definition fft.H:200
Array< Real > denominator() const
Definition fft.H:217
Array< Real > numerator() const
Definition fft.H:210
Magnitude-squared coherence sampled in Hertz.
Definition fft.H:328
Array< Real > magnitude_squared
Definition fft.H:330
Array< Real > frequency
Definition fft.H:329
One-sided cross-spectral density estimate sampled in Hertz.
Definition fft.H:321
Array< Complex > density
Definition fft.H:323
Discrete frequency response sampled on a fixed angular grid.
Definition fft.H:225
Array< Complex > response
Definition fft.H:227
Array< Real > power() const
Definition fft.H:236
Array< Real > omega
Definition fft.H:226
Array< Real > magnitude() const
Definition fft.H:230
Array< Real > phase() const
Definition fft.H:242
Gain-margin estimate around a phase crossover.
Definition fft.H:259
Coefficients for an Infinite Impulse Response (IIR) filter.
Definition fft.H:191
Array< Real > denominator
Feed-backward coefficients (a).
Definition fft.H:193
Array< Real > numerator
Feed-forward coefficients (b).
Definition fft.H:192
Options for ISTFT reconstruction.
Definition fft.H:295
Value triplet for Jacobi elliptic functions (sn, cn, dn).
Definition fft.H:2390
Phase-margin estimate around a gain crossover.
Definition fft.H:250
Greedy nearest-neighbor pole/zero pairing entry.
Definition fft.H:268
bool is_cancellation(const Real tolerance) const noexcept
Definition fft.H:276
One-sided power spectral density estimate sampled in Hertz.
Definition fft.H:314
Options for default polyphase resampling filter design.
Definition fft.H:335
Rationale: Group of one or more roots (typically a conjugate pair) sharing a common geometric center.
Definition fft.H:2375
Array< Complex > roots
Definition fft.H:2376
Options for STFT analysis.
Definition fft.H:284
Layout descriptor for a flat tensor buffer.
Definition fft.H:342
Array< size_t > strides
Definition fft.H:344
Array< size_t > shape
Definition fft.H:343
Internal storage for complex transfer function evaluation terms.
Definition fft.H:2137
Complex numerator_derivative
Definition fft.H:2140
Complex denominator_derivative
Definition fft.H:2141
Frequency-domain grid with target values and importance weights.
Definition fft.H:2398
Options for Welch PSD, CSD, and coherence estimation.
Definition fft.H:305
FooMap m(5, fst_unit_pair_hash, snd_unit_pair_hash)
static int * k
gsl_rng * r
A modern, efficient thread pool for parallel task execution.
Dynamic array container with automatic resizing.
ofstream output
Definition writeHeap.C:215