polyphase_resampler.cpp 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220
  1. #include "polyphase_resampler.h"
  2. #include <algorithm>
  3. #include <cmath>
  4. #include <cstddef>
  5. #include <numeric>
  6. #include <tuple>
  7. #include "alnumbers.h"
  8. #include "opthelpers.h"
  9. using uint = unsigned int;
  10. namespace {
  11. constexpr double Epsilon{1e-9};
  12. #if __cpp_lib_math_special_functions >= 201603L
  13. using std::cyl_bessel_i;
  14. #else
  15. /* The zero-order modified Bessel function of the first kind, used for the
  16. * Kaiser window.
  17. *
  18. * I_0(x) = sum_{k=0}^inf (1 / k!)^2 (x / 2)^(2 k)
  19. * = sum_{k=0}^inf ((x / 2)^k / k!)^2
  20. *
  21. * This implementation only handles nu = 0, and isn't the most precise (it
  22. * starts with the largest value and accumulates successively smaller values,
  23. * compounding the rounding and precision error), but it's good enough.
  24. */
  25. template<typename T, typename U>
  26. U cyl_bessel_i(T nu, U x)
  27. {
  28. if(nu != T{0})
  29. throw std::runtime_error{"cyl_bessel_i: nu != 0"};
  30. /* Start at k=1 since k=0 is trivial. */
  31. const double x2{x/2.0};
  32. double term{1.0};
  33. double sum{1.0};
  34. int k{1};
  35. /* Let the integration converge until the term of the sum is no longer
  36. * significant.
  37. */
  38. double last_sum{};
  39. do {
  40. const double y{x2 / k};
  41. ++k;
  42. last_sum = sum;
  43. term *= y * y;
  44. sum += term;
  45. } while(sum != last_sum);
  46. return static_cast<U>(sum);
  47. }
  48. #endif
  49. /* This is the normalized cardinal sine (sinc) function.
  50. *
  51. * sinc(x) = { 1, x = 0
  52. * { sin(pi x) / (pi x), otherwise.
  53. */
  54. double Sinc(const double x)
  55. {
  56. if(std::abs(x) < Epsilon) UNLIKELY
  57. return 1.0;
  58. return std::sin(al::numbers::pi*x) / (al::numbers::pi*x);
  59. }
  60. /* Calculate a Kaiser window from the given beta value and a normalized k
  61. * [-1, 1].
  62. *
  63. * w(k) = { I_0(B sqrt(1 - k^2)) / I_0(B), -1 <= k <= 1
  64. * { 0, elsewhere.
  65. *
  66. * Where k can be calculated as:
  67. *
  68. * k = i / l, where -l <= i <= l.
  69. *
  70. * or:
  71. *
  72. * k = 2 i / M - 1, where 0 <= i <= M.
  73. */
  74. double Kaiser(const double beta, const double k, const double besseli_0_beta)
  75. {
  76. if(!(k >= -1.0 && k <= 1.0))
  77. return 0.0;
  78. return cyl_bessel_i(0, beta * std::sqrt(1.0 - k*k)) / besseli_0_beta;
  79. }
  80. /* Calculates the size (order) of the Kaiser window. Rejection is in dB and
  81. * the transition width is normalized frequency (0.5 is nyquist).
  82. *
  83. * M = { ceil((r - 7.95) / (2.285 2 pi f_t)), r > 21
  84. * { ceil(5.79 / 2 pi f_t), r <= 21.
  85. *
  86. */
  87. constexpr uint CalcKaiserOrder(const double rejection, const double transition)
  88. {
  89. const double w_t{2.0 * al::numbers::pi * transition};
  90. if(rejection > 21.0) LIKELY
  91. return static_cast<uint>(std::ceil((rejection - 7.95) / (2.285 * w_t)));
  92. return static_cast<uint>(std::ceil(5.79 / w_t));
  93. }
  94. // Calculates the beta value of the Kaiser window. Rejection is in dB.
  95. constexpr double CalcKaiserBeta(const double rejection)
  96. {
  97. if(rejection > 50.0) LIKELY
  98. return 0.1102 * (rejection - 8.7);
  99. if(rejection >= 21.0)
  100. return (0.5842 * std::pow(rejection - 21.0, 0.4)) +
  101. (0.07886 * (rejection - 21.0));
  102. return 0.0;
  103. }
  104. /* Calculates a point on the Kaiser-windowed sinc filter for the given half-
  105. * width, beta, gain, and cutoff. The point is specified in non-normalized
  106. * samples, from 0 to M, where M = (2 l + 1).
  107. *
  108. * w(k) 2 p f_t sinc(2 f_t x)
  109. *
  110. * x -- centered sample index (i - l)
  111. * k -- normalized and centered window index (x / l)
  112. * w(k) -- window function (Kaiser)
  113. * p -- gain compensation factor when sampling
  114. * f_t -- normalized center frequency (or cutoff; 0.5 is nyquist)
  115. */
  116. double SincFilter(const uint l, const double beta, const double besseli_0_beta, const double gain,
  117. const double cutoff, const uint i)
  118. {
  119. const double x{static_cast<double>(i) - l};
  120. return Kaiser(beta, x/l, besseli_0_beta) * 2.0 * gain * cutoff * Sinc(2.0 * cutoff * x);
  121. }
  122. } // namespace
  123. // Calculate the resampling metrics and build the Kaiser-windowed sinc filter
  124. // that's used to cut frequencies above the destination nyquist.
  125. void PPhaseResampler::init(const uint srcRate, const uint dstRate)
  126. {
  127. const uint gcd{std::gcd(srcRate, dstRate)};
  128. mP = dstRate / gcd;
  129. mQ = srcRate / gcd;
  130. /* The cutoff is adjusted by half the transition width, so the transition
  131. * ends before the nyquist (0.5). Both are scaled by the downsampling
  132. * factor.
  133. */
  134. const auto [cutoff, width] = (mP > mQ) ? std::make_tuple(0.475 / mP, 0.05 / mP)
  135. : std::make_tuple(0.475 / mQ, 0.05 / mQ);
  136. // A rejection of -180 dB is used for the stop band. Round up when
  137. // calculating the left offset to avoid increasing the transition width.
  138. const uint l{(CalcKaiserOrder(180.0, width)+1) / 2};
  139. const double beta{CalcKaiserBeta(180.0)};
  140. const double besseli_0_beta{cyl_bessel_i(0, beta)};
  141. mM = l*2 + 1;
  142. mL = l;
  143. mF.resize(mM);
  144. for(uint i{0};i < mM;i++)
  145. mF[i] = SincFilter(l, beta, besseli_0_beta, mP, cutoff, i);
  146. }
  147. // Perform the upsample-filter-downsample resampling operation using a
  148. // polyphase filter implementation.
  149. void PPhaseResampler::process(const al::span<const double> in, const al::span<double> out)
  150. {
  151. if(out.empty()) UNLIKELY
  152. return;
  153. // Handle in-place operation.
  154. std::vector<double> workspace;
  155. al::span work{out};
  156. if(work.data() == in.data()) UNLIKELY
  157. {
  158. workspace.resize(out.size());
  159. work = workspace;
  160. }
  161. // Resample the input.
  162. const uint p{mP}, q{mQ}, m{mM}, l{mL};
  163. const al::span<const double> f{mF};
  164. for(uint i{0};i < out.size();i++)
  165. {
  166. // Input starts at l to compensate for the filter delay. This will
  167. // drop any build-up from the first half of the filter.
  168. std::size_t j_f{(l + q*i) % p};
  169. std::size_t j_s{(l + q*i) / p};
  170. // Only take input when 0 <= j_s < in.size().
  171. double r{0.0};
  172. if(j_f < m) LIKELY
  173. {
  174. std::size_t filt_len{(m-j_f+p-1) / p};
  175. if(j_s+1 > in.size()) LIKELY
  176. {
  177. std::size_t skip{std::min(j_s+1 - in.size(), filt_len)};
  178. j_f += p*skip;
  179. j_s -= skip;
  180. filt_len -= skip;
  181. }
  182. std::size_t todo{std::min(j_s+1, filt_len)};
  183. while(todo)
  184. {
  185. r += f[j_f] * in[j_s];
  186. j_f += p; --j_s;
  187. --todo;
  188. }
  189. }
  190. work[i] = r;
  191. }
  192. // Clean up after in-place operation.
  193. if(work.data() != out.data())
  194. std::copy(work.cbegin(), work.cend(), out.begin());
  195. }