diff options
author | Chris Robinson <chris.kcat@gmail.com> | 2020-12-04 07:35:40 -0800 |
---|---|---|
committer | Chris Robinson <chris.kcat@gmail.com> | 2020-12-04 11:15:50 -0800 |
commit | 84d47f7d4c2d1355a6eb914dd091b39683f83c15 (patch) | |
tree | 5e8266a74e7821d47e8d1bc0e14cd7570fef6bf5 /core/bsinc_tables.cpp | |
parent | 36c1589c11cb8f197576e748e06bc5fbb6dcc8bf (diff) |
Move the bsinc tables to core
Diffstat (limited to 'core/bsinc_tables.cpp')
-rw-r--r-- | core/bsinc_tables.cpp | 288 |
1 files changed, 288 insertions, 0 deletions
diff --git a/core/bsinc_tables.cpp b/core/bsinc_tables.cpp new file mode 100644 index 00000000..315e1448 --- /dev/null +++ b/core/bsinc_tables.cpp @@ -0,0 +1,288 @@ + +#include "bsinc_tables.h" + +#include <algorithm> +#include <array> +#include <cassert> +#include <cmath> +#include <limits> +#include <memory> +#include <stdexcept> + +#include "math_defs.h" + + +namespace { + +using uint = unsigned int; + + +/* This is the normalized cardinal sine (sinc) function. + * + * sinc(x) = { 1, x = 0 + * { sin(pi x) / (pi x), otherwise. + */ +constexpr double Sinc(const double x) +{ + if(!(x > 1e-15 || x < -1e-15)) + return 1.0; + return std::sin(al::MathDefs<double>::Pi()*x) / (al::MathDefs<double>::Pi()*x); +} + +/* The zero-order modified Bessel function of the first kind, used for the + * Kaiser window. + * + * I_0(x) = sum_{k=0}^inf (1 / k!)^2 (x / 2)^(2 k) + * = sum_{k=0}^inf ((x / 2)^k / k!)^2 + */ +constexpr double BesselI_0(const double x) +{ + /* Start at k=1 since k=0 is trivial. */ + const double x2{x / 2.0}; + double term{1.0}; + double sum{1.0}; + double last_sum{}; + int k{1}; + + /* Let the integration converge until the term of the sum is no longer + * significant. + */ + do { + const double y{x2 / k}; + ++k; + last_sum = sum; + term *= y * y; + sum += term; + } while(sum != last_sum); + + return sum; +} + +/* Calculate a Kaiser window from the given beta value and a normalized k + * [-1, 1]. + * + * w(k) = { I_0(B sqrt(1 - k^2)) / I_0(B), -1 <= k <= 1 + * { 0, elsewhere. + * + * Where k can be calculated as: + * + * k = i / l, where -l <= i <= l. + * + * or: + * + * k = 2 i / M - 1, where 0 <= i <= M. + */ +constexpr double Kaiser(const double beta, const double k, const double besseli_0_beta) +{ + if(!(k >= -1.0 && k <= 1.0)) + return 0.0; + return BesselI_0(beta * std::sqrt(1.0 - k*k)) / besseli_0_beta; +} + +/* Calculates the (normalized frequency) transition width of the Kaiser window. + * Rejection is in dB. + */ +constexpr double CalcKaiserWidth(const double rejection, const uint order) +{ + if(rejection > 21.19) + return (rejection - 7.95) / (order * 2.285 * al::MathDefs<double>::Tau()); + /* This enforces a minimum rejection of just above 21.18dB */ + return 5.79 / (order * al::MathDefs<double>::Tau()); +} + +/* Calculates the beta value of the Kaiser window. Rejection is in dB. */ +constexpr double CalcKaiserBeta(const double rejection) +{ + if(rejection > 50.0) + return 0.1102 * (rejection-8.7); + else if(rejection >= 21.0) + return (0.5842 * std::pow(rejection-21.0, 0.4)) + (0.07886 * (rejection-21.0)); + return 0.0; +} + + +struct BSincHeader { + double width{}; + double beta{}; + double scaleBase{}; + double scaleRange{}; + double besseli_0_beta{}; + + uint a[BSincScaleCount]{}; + uint total_size{}; + + constexpr BSincHeader(uint Rejection, uint Order) noexcept + { + width = CalcKaiserWidth(Rejection, Order); + beta = CalcKaiserBeta(Rejection); + scaleBase = width / 2.0; + scaleRange = 1.0 - scaleBase; + besseli_0_beta = BesselI_0(beta); + + uint num_points{Order+1}; + for(uint si{0};si < BSincScaleCount;++si) + { + const double scale{scaleBase + (scaleRange * si / (BSincScaleCount-1))}; + const uint a_{std::min(static_cast<uint>(num_points / 2.0 / scale), num_points)}; + const uint m{2 * a_}; + + a[si] = a_; + total_size += 4 * BSincPhaseCount * ((m+3) & ~3u); + } + } +}; + +/* 11th and 23rd order filters (12 and 24-point respectively) with a 60dB drop + * at nyquist. Each filter will scale up the order when downsampling, to 23rd + * and 47th order respectively. + */ +constexpr BSincHeader bsinc12_hdr{60, 11}; +constexpr BSincHeader bsinc24_hdr{60, 23}; + + +/* NOTE: GCC 5 has an issue with BSincHeader objects being in an anonymous + * namespace while also being used as non-type template parameters. + */ +#if !defined(__clang__) && defined(__GNUC__) && __GNUC__ < 6 +template<size_t total_size> +struct BSincFilterArray { + alignas(16) std::array<float, total_size> mTable; + + BSincFilterArray(const BSincHeader &hdr) +#else +template<const BSincHeader &hdr> +struct BSincFilterArray { + alignas(16) std::array<float, hdr.total_size> mTable; + + BSincFilterArray() +#endif + { + using filter_type = double[][BSincPhaseCount+1][BSincPointsMax]; + auto filter = std::make_unique<filter_type>(BSincScaleCount); + + /* Calculate the Kaiser-windowed Sinc filter coefficients for each + * scale and phase index. + */ + for(uint si{0};si < BSincScaleCount;++si) + { + const uint m{hdr.a[si] * 2}; + const size_t o{(BSincPointsMax-m) / 2}; + const double scale{hdr.scaleBase + (hdr.scaleRange * si / (BSincScaleCount-1))}; + const double cutoff{scale - (hdr.scaleBase * std::max(0.5, scale) * 2.0)}; + const auto a = static_cast<double>(hdr.a[si]); + const double l{a - 1.0}; + + /* Do one extra phase index so that the phase delta has a proper + * target for its last index. + */ + for(uint pi{0};pi <= BSincPhaseCount;++pi) + { + const double phase{l + (pi/double{BSincPhaseCount})}; + + for(uint i{0};i < m;++i) + { + const double x{i - phase}; + filter[si][pi][o+i] = Kaiser(hdr.beta, x/a, hdr.besseli_0_beta) * cutoff * + Sinc(cutoff*x); + } + } + } + + size_t idx{0}; + for(size_t si{0};si < BSincScaleCount-1;++si) + { + const size_t m{((hdr.a[si]*2) + 3) & ~3u}; + const size_t o{(BSincPointsMax-m) / 2}; + + for(size_t pi{0};pi < BSincPhaseCount;++pi) + { + /* Write out the filter. Also calculate and write out the phase + * and scale deltas. + */ + for(size_t i{0};i < m;++i) + mTable[idx++] = static_cast<float>(filter[si][pi][o+i]); + + /* Linear interpolation between phases is simplified by pre- + * calculating the delta (b - a) in: x = a + f (b - a) + */ + for(size_t i{0};i < m;++i) + { + const double phDelta{filter[si][pi+1][o+i] - filter[si][pi][o+i]}; + mTable[idx++] = static_cast<float>(phDelta); + } + + /* Linear interpolation between scales is also simplified. + * + * Given a difference in points between scales, the destination + * points will be 0, thus: x = a + f (-a) + */ + for(size_t i{0};i < m;++i) + { + const double scDelta{filter[si+1][pi][o+i] - filter[si][pi][o+i]}; + mTable[idx++] = static_cast<float>(scDelta); + } + + /* This last simplification is done to complete the bilinear + * equation for the combination of phase and scale. + */ + for(size_t i{0};i < m;++i) + { + const double spDelta{(filter[si+1][pi+1][o+i] - filter[si+1][pi][o+i]) - + (filter[si][pi+1][o+i] - filter[si][pi][o+i])}; + mTable[idx++] = static_cast<float>(spDelta); + } + } + } + { + /* The last scale index doesn't have any scale or scale-phase + * deltas. + */ + constexpr size_t si{BSincScaleCount-1}; + const size_t m{((hdr.a[si]*2) + 3) & ~3u}; + const size_t o{(BSincPointsMax-m) / 2}; + + for(size_t pi{0};pi < BSincPhaseCount;++pi) + { + for(size_t i{0};i < m;++i) + mTable[idx++] = static_cast<float>(filter[si][pi][o+i]); + for(size_t i{0};i < m;++i) + { + const double phDelta{filter[si][pi+1][o+i] - filter[si][pi][o+i]}; + mTable[idx++] = static_cast<float>(phDelta); + } + for(size_t i{0};i < m;++i) + mTable[idx++] = 0.0f; + for(size_t i{0};i < m;++i) + mTable[idx++] = 0.0f; + } + } + assert(idx == hdr.total_size); + } +}; + +#if !defined(__clang__) && defined(__GNUC__) && __GNUC__ < 6 +const BSincFilterArray<bsinc12_hdr.total_size> bsinc12_filter{bsinc12_hdr}; +const BSincFilterArray<bsinc24_hdr.total_size> bsinc24_filter{bsinc24_hdr}; +#else +const BSincFilterArray<bsinc12_hdr> bsinc12_filter{}; +const BSincFilterArray<bsinc24_hdr> bsinc24_filter{}; +#endif + +constexpr BSincTable GenerateBSincTable(const BSincHeader &hdr, const float *tab) +{ + BSincTable ret{}; + ret.scaleBase = static_cast<float>(hdr.scaleBase); + ret.scaleRange = static_cast<float>(1.0 / hdr.scaleRange); + for(size_t i{0};i < BSincScaleCount;++i) + ret.m[i] = ((hdr.a[i]*2) + 3) & ~3u; + ret.filterOffset[0] = 0; + for(size_t i{1};i < BSincScaleCount;++i) + ret.filterOffset[i] = ret.filterOffset[i-1] + ret.m[i-1]*4*BSincPhaseCount; + ret.Tab = tab; + return ret; +} + +} // namespace + +const BSincTable bsinc12{GenerateBSincTable(bsinc12_hdr, &bsinc12_filter.mTable.front())}; +const BSincTable bsinc24{GenerateBSincTable(bsinc24_hdr, &bsinc24_filter.mTable.front())}; |