17{
18 if (src.empty() || kernel.empty())
19 return std::vector<double>(src.size(), 0.0);
20
21 const size_t conv_len = src.size() + kernel.size() - 1;
22 const size_t fft_size = std::bit_ceil(std::max(size_t { 256 }, conv_len));
23 const size_t bins = fft_size / 2 + 1;
24
25 Eigen::VectorXd padded_src = Eigen::VectorXd::Zero(static_cast<Eigen::Index>(fft_size));
26 Eigen::VectorXd padded_kernel = Eigen::VectorXd::Zero(static_cast<Eigen::Index>(fft_size));
27 std::ranges::copy(src, padded_src.begin());
28 std::ranges::copy(kernel, padded_kernel.begin());
29
30 Eigen::FFT<double> fft;
31 Eigen::VectorXcd src_fft, ker_fft;
32 fft.fwd(src_fft, padded_src);
33 fft.fwd(ker_fft, padded_kernel);
34
35 std::vector<std::complex<double>> sig(bins), ker(bins), res(bins);
36 P::for_each(P::par_unseq,
37 std::views::iota(size_t { 0 }, bins).begin(),
38 std::views::iota(size_t { 0 }, bins).end(),
40 sig[
b] = src_fft(
static_cast<Eigen::Index
>(
b));
41 ker[
b] = ker_fft(
static_cast<Eigen::Index
>(
b));
42 });
43
44 processor(sig, ker, res);
45
46 Eigen::VectorXcd full_fft(static_cast<Eigen::Index>(fft_size));
47 P::for_each(P::par_unseq,
48 std::views::iota(size_t { 0 }, fft_size).begin(),
49 std::views::iota(size_t { 0 }, fft_size).end(),
51 full_fft(
static_cast<Eigen::Index
>(
b)) = (
b < bins)
53 :
std::conj(res[fft_size -
b]);
54 });
55
56 const double inv = 1.0 / static_cast<double>(fft_size);
57 full_fft *= inv;
58
59 Eigen::VectorXd time_result;
60 fft.inv(time_result, full_fft);
61
62 const size_t out_len = full_size ? conv_len : src.size();
63 std::vector<double> out(out_len);
64 P::for_each(P::par_unseq,
65 std::views::iota(size_t { 0 }, out_len).begin(),
66 std::views::iota(size_t { 0 }, out_len).end(),
67 [&](size_t i) { out[i] = time_result(static_cast<Eigen::Index>(i)); });
68 return out;
69}