🛠️🐜 Antkeeper superbuild with dependencies included https://antkeeper.com
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

222 lines
6.0 KiB

  1. #include "polyphase_resampler.h"
  2. #include <algorithm>
  3. #include <cmath>
  4. #include "alnumbers.h"
  5. #include "opthelpers.h"
  6. namespace {
  7. constexpr double Epsilon{1e-9};
  8. using uint = unsigned int;
  9. /* This is the normalized cardinal sine (sinc) function.
  10. *
  11. * sinc(x) = { 1, x = 0
  12. * { sin(pi x) / (pi x), otherwise.
  13. */
  14. double Sinc(const double x)
  15. {
  16. if(unlikely(std::abs(x) < Epsilon))
  17. return 1.0;
  18. return std::sin(al::numbers::pi*x) / (al::numbers::pi*x);
  19. }
  20. /* The zero-order modified Bessel function of the first kind, used for the
  21. * Kaiser window.
  22. *
  23. * I_0(x) = sum_{k=0}^inf (1 / k!)^2 (x / 2)^(2 k)
  24. * = sum_{k=0}^inf ((x / 2)^k / k!)^2
  25. */
  26. constexpr double BesselI_0(const double x)
  27. {
  28. // Start at k=1 since k=0 is trivial.
  29. const double x2{x/2.0};
  30. double term{1.0};
  31. double sum{1.0};
  32. int k{1};
  33. // Let the integration converge until the term of the sum is no longer
  34. // significant.
  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 sum;
  44. }
  45. /* Calculate a Kaiser window from the given beta value and a normalized k
  46. * [-1, 1].
  47. *
  48. * w(k) = { I_0(B sqrt(1 - k^2)) / I_0(B), -1 <= k <= 1
  49. * { 0, elsewhere.
  50. *
  51. * Where k can be calculated as:
  52. *
  53. * k = i / l, where -l <= i <= l.
  54. *
  55. * or:
  56. *
  57. * k = 2 i / M - 1, where 0 <= i <= M.
  58. */
  59. double Kaiser(const double b, const double k)
  60. {
  61. if(!(k >= -1.0 && k <= 1.0))
  62. return 0.0;
  63. return BesselI_0(b * std::sqrt(1.0 - k*k)) / BesselI_0(b);
  64. }
  65. // Calculates the greatest common divisor of a and b.
  66. constexpr uint Gcd(uint x, uint y)
  67. {
  68. while(y > 0)
  69. {
  70. const uint z{y};
  71. y = x % y;
  72. x = z;
  73. }
  74. return x;
  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 LIKELY(rejection > 21.0)
  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 LIKELY(rejection > 50.0)
  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. double SincFilter(const uint l, const double b, const double gain, const double cutoff,
  113. const uint i)
  114. {
  115. const double x{static_cast<double>(i) - l};
  116. return Kaiser(b, x / l) * 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 uint gcd{Gcd(srcRate, dstRate)};
  124. mP = dstRate / gcd;
  125. mQ = srcRate / gcd;
  126. /* The cutoff is adjusted by half the transition width, so the transition
  127. * ends before the nyquist (0.5). Both are scaled by the downsampling
  128. * factor.
  129. */
  130. double cutoff, width;
  131. if(mP > mQ)
  132. {
  133. cutoff = 0.475 / mP;
  134. width = 0.05 / mP;
  135. }
  136. else
  137. {
  138. cutoff = 0.475 / mQ;
  139. width = 0.05 / mQ;
  140. }
  141. // A rejection of -180 dB is used for the stop band. Round up when
  142. // calculating the left offset to avoid increasing the transition width.
  143. const uint l{(CalcKaiserOrder(180.0, width)+1) / 2};
  144. const double beta{CalcKaiserBeta(180.0)};
  145. mM = l*2 + 1;
  146. mL = l;
  147. mF.resize(mM);
  148. for(uint i{0};i < mM;i++)
  149. mF[i] = SincFilter(l, beta, mP, cutoff, i);
  150. }
  151. // Perform the upsample-filter-downsample resampling operation using a
  152. // polyphase filter implementation.
  153. void PPhaseResampler::process(const uint inN, const double *in, const uint outN, double *out)
  154. {
  155. if UNLIKELY(outN == 0)
  156. return;
  157. // Handle in-place operation.
  158. std::vector<double> workspace;
  159. double *work{out};
  160. if UNLIKELY(work == in)
  161. {
  162. workspace.resize(outN);
  163. work = workspace.data();
  164. }
  165. // Resample the input.
  166. const uint p{mP}, q{mQ}, m{mM}, l{mL};
  167. const double *f{mF.data()};
  168. for(uint i{0};i < outN;i++)
  169. {
  170. // Input starts at l to compensate for the filter delay. This will
  171. // drop any build-up from the first half of the filter.
  172. size_t j_f{(l + q*i) % p};
  173. size_t j_s{(l + q*i) / p};
  174. // Only take input when 0 <= j_s < inN.
  175. double r{0.0};
  176. if LIKELY(j_f < m)
  177. {
  178. size_t filt_len{(m-j_f+p-1) / p};
  179. if LIKELY(j_s+1 > inN)
  180. {
  181. size_t skip{std::min<size_t>(j_s+1 - inN, filt_len)};
  182. j_f += p*skip;
  183. j_s -= skip;
  184. filt_len -= skip;
  185. }
  186. if(size_t todo{std::min<size_t>(j_s+1, filt_len)})
  187. {
  188. do {
  189. r += f[j_f] * in[j_s];
  190. j_f += p;
  191. --j_s;
  192. } while(--todo);
  193. }
  194. }
  195. work[i] = r;
  196. }
  197. // Clean up after in-place operation.
  198. if(work != out)
  199. std::copy_n(work, outN, out);
  200. }