polyphase_resampler.cpp 6.7 KB

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