2 #include "polyphase_resampler.h"
10 #include "alnumbers.h"
11 #include "opthelpers.h"
14 using uint
= unsigned int;
18 constexpr double Epsilon
{1e-9};
21 /* The zero-order modified Bessel function of the first kind, used for the
24 * I_0(x) = sum_{k=0}^inf (1 / k!)^2 (x / 2)^(2 k)
25 * = sum_{k=0}^inf ((x / 2)^k / k!)^2
27 * This implementation only handles nu = 0, and isn't the most precise (it
28 * starts with the largest value and accumulates successively smaller values,
29 * compounding the rounding and precision error), but it's good enough.
31 template<typename T
, typename U
>
32 constexpr auto cyl_bessel_i(T nu
, U x
) -> U
35 throw std::runtime_error
{"cyl_bessel_i: nu != 0"};
37 /* Start at k=1 since k=0 is trivial. */
38 const double x2
{x
/2.0};
43 /* Let the integration converge until the term of the sum is no longer
48 const double y
{x2
/ k
};
53 } while(sum
!= last_sum
);
54 return static_cast<U
>(sum
);
57 /* This is the normalized cardinal sine (sinc) function.
59 * sinc(x) = { 1, x = 0
60 * { sin(pi x) / (pi x), otherwise.
62 double Sinc(const double x
)
64 if(std::abs(x
) < Epsilon
) UNLIKELY
66 return std::sin(al::numbers::pi
*x
) / (al::numbers::pi
*x
);
69 /* Calculate a Kaiser window from the given beta value and a normalized k
72 * w(k) = { I_0(B sqrt(1 - k^2)) / I_0(B), -1 <= k <= 1
75 * Where k can be calculated as:
77 * k = i / l, where -l <= i <= l.
81 * k = 2 i / M - 1, where 0 <= i <= M.
83 double Kaiser(const double beta
, const double k
, const double besseli_0_beta
)
85 if(!(k
>= -1.0 && k
<= 1.0))
87 return ::cyl_bessel_i(0, beta
* std::sqrt(1.0 - k
*k
)) / besseli_0_beta
;
90 /* Calculates the size (order) of the Kaiser window. Rejection is in dB and
91 * the transition width is normalized frequency (0.5 is nyquist).
93 * M = { ceil((r - 7.95) / (2.285 2 pi f_t)), r > 21
94 * { ceil(5.79 / 2 pi f_t), r <= 21.
97 constexpr uint
CalcKaiserOrder(const double rejection
, const double transition
)
99 const double w_t
{2.0 * al::numbers::pi
* transition
};
100 if(rejection
> 21.0) LIKELY
101 return static_cast<uint
>(std::ceil((rejection
- 7.95) / (2.285 * w_t
)));
102 return static_cast<uint
>(std::ceil(5.79 / w_t
));
105 // Calculates the beta value of the Kaiser window. Rejection is in dB.
106 constexpr double CalcKaiserBeta(const double rejection
)
108 if(rejection
> 50.0) LIKELY
109 return 0.1102 * (rejection
- 8.7);
110 if(rejection
>= 21.0)
111 return (0.5842 * std::pow(rejection
- 21.0, 0.4)) +
112 (0.07886 * (rejection
- 21.0));
116 /* Calculates a point on the Kaiser-windowed sinc filter for the given half-
117 * width, beta, gain, and cutoff. The point is specified in non-normalized
118 * samples, from 0 to M, where M = (2 l + 1).
120 * w(k) 2 p f_t sinc(2 f_t x)
122 * x -- centered sample index (i - l)
123 * k -- normalized and centered window index (x / l)
124 * w(k) -- window function (Kaiser)
125 * p -- gain compensation factor when sampling
126 * f_t -- normalized center frequency (or cutoff; 0.5 is nyquist)
128 double SincFilter(const uint l
, const double beta
, const double besseli_0_beta
, const double gain
,
129 const double cutoff
, const uint i
)
131 const double x
{static_cast<double>(i
) - l
};
132 return Kaiser(beta
, x
/l
, besseli_0_beta
) * 2.0 * gain
* cutoff
* Sinc(2.0 * cutoff
* x
);
137 // Calculate the resampling metrics and build the Kaiser-windowed sinc filter
138 // that's used to cut frequencies above the destination nyquist.
139 void PPhaseResampler::init(const uint srcRate
, const uint dstRate
)
141 const uint gcd
{std::gcd(srcRate
, dstRate
)};
145 /* The cutoff is adjusted by half the transition width, so the transition
146 * ends before the nyquist (0.5). Both are scaled by the downsampling
149 const auto [cutoff
, width
] = (mP
> mQ
) ? std::make_tuple(0.475 / mP
, 0.05 / mP
)
150 : std::make_tuple(0.475 / mQ
, 0.05 / mQ
);
152 // A rejection of -180 dB is used for the stop band. Round up when
153 // calculating the left offset to avoid increasing the transition width.
154 const uint l
{(CalcKaiserOrder(180.0, width
)+1) / 2};
155 const double beta
{CalcKaiserBeta(180.0)};
156 const double besseli_0_beta
{::cyl_bessel_i(0, beta
)};
160 for(uint i
{0};i
< mM
;i
++)
161 mF
[i
] = SincFilter(l
, beta
, besseli_0_beta
, mP
, cutoff
, i
);
164 // Perform the upsample-filter-downsample resampling operation using a
165 // polyphase filter implementation.
166 void PPhaseResampler::process(const al::span
<const double> in
, const al::span
<double> out
)
168 if(out
.empty()) UNLIKELY
171 // Handle in-place operation.
172 std::vector
<double> workspace
;
174 if(work
.data() == in
.data()) UNLIKELY
176 workspace
.resize(out
.size());
180 // Resample the input.
181 const uint p
{mP
}, q
{mQ
}, m
{mM
}, l
{mL
};
182 const al::span
<const double> f
{mF
};
183 for(uint i
{0};i
< out
.size();i
++)
185 // Input starts at l to compensate for the filter delay. This will
186 // drop any build-up from the first half of the filter.
187 std::size_t j_f
{(l
+ q
*i
) % p
};
188 std::size_t j_s
{(l
+ q
*i
) / p
};
190 // Only take input when 0 <= j_s < in.size().
194 std::size_t filt_len
{(m
-j_f
+p
-1) / p
};
195 if(j_s
+1 > in
.size()) LIKELY
197 std::size_t skip
{std::min(j_s
+1 - in
.size(), filt_len
)};
202 std::size_t todo
{std::min(j_s
+1, filt_len
)};
205 r
+= f
[j_f
] * in
[j_s
];
212 // Clean up after in-place operation.
213 if(work
.data() != out
.data())
214 std::copy(work
.cbegin(), work
.cend(), out
.begin());