//$ nocpp /** * @file CDSPFracInterpolator.h * * @brief Fractional delay interpolator and filter bank classes. * * This file includes fractional delay interpolator class. * * r8brain-free-src Copyright (c) 2013-2014 Aleksey Vaneev * See the "License.txt" file for license. */ #ifndef R8B_CDSPFRACINTERPOLATOR_INCLUDED #define R8B_CDSPFRACINTERPOLATOR_INCLUDED #include "CDSPSincFilterGen.h" #include "CDSPProcessor.h" namespace r8b { /** * @brief Sinc function-based fractional delay filter bank class. * * Class implements storage and initialization of a bank of sinc-based * fractional delay filters, expressed as 1st, 2nd or 3rd order polynomial * interpolation coefficients. The filters are windowed by either the "Vaneev" * or "Kaiser" power-raised window function. The FilterLen and FilterFracs * parameters can be varied freely without breaking the resampler. * * @param FilterLen Specifies the number of samples (taps) each fractional * delay filter should have. This must be an even value, the minimal value for * FilterLen is 6, the maximal value is 30. To achieve a higher resampling * precision, the oversampling should be used in the first place instead of * using a higher FilterLen value. The lower this value is the lower the * signal-to-noise performance of the interpolator will be. Each FilterLen * decrease by 2 decreases SNR by approximately 12 to 14 decibel. * @param FilterFracs The number of fractional delay positions to sample. For * a high signal-to-noise ratio this has to be a larger value. The larger the * FilterLen is the larger the FilterFracs should be. Approximate FilterLen to * FilterFracs correspondence (for 2nd order interpolation only): 6:11, 8:17, * 10:23, 12:41, 14:67, 16:97, 18:137, 20:211, 22:353, 24:673, 26:1051, * 28:1733, 30:2833. The FilterFracs can be considerably reduced with 3rd * order interpolation in use. In order to get consistent results when * resampling to/from different sample rates, it is suggested to set this * parameter to a suitable prime number. * @param ElementSize The size of each filter's tap, in "double" values. This * parameter corresponds to the complexity of interpolation. 4 should be set * for 3rd order, 3 for 2nd order, 2 for linear interpolation. * @param InterpPoints The number of points the interpolation is based on. * This value should not be confused with the ElementSize. Set to 2 for linear * interpolation. */ template class CDSPFracDelayFilterBank : public R8B_BASECLASS { public: CDSPFracDelayFilterBank() { R8BASSERT(FilterLen >= 6); R8BASSERT(FilterLen <= 30); R8BASSERT(( FilterLen & 1 ) == 0); R8BASSERT(FilterFracs > 0); R8BASSERT(ElementSize >= 2 && ElementSize <= 4); R8BASSERT(InterpPoints == 2 || InterpPoints == 8); calculate(); } /** * Function calculates the filter bank. * * @param Params Window function's parameters. If NULL then the built-in * table values for the current FilterLen will be used. */ void calculate(const double* const Params = nullptr) { CDSPSincFilterGen sinc; sinc.Len2 = FilterLen / 2; double* p = Table; const int pc2 = InterpPoints / 2; int i; if (FilterLen <= 20) { for (i = -pc2 + 1; i <= FilterFracs + pc2; ++i) { sinc.FracDelay = double(FilterFracs - i) / FilterFracs; sinc.initFrac(CDSPSincFilterGen::wftVaneev, Params); sinc.generateFrac(p, &CDSPSincFilterGen::calcWindowVaneev, ElementSize); normalizeFIRFilter(p, FilterLen, 1.0, ElementSize); p += FilterSize; } } else { for (i = -pc2 + 1; i <= FilterFracs + pc2; ++i) { sinc.FracDelay = double(FilterFracs - i) / FilterFracs; sinc.initFrac(CDSPSincFilterGen::wftKaiser, Params, true); sinc.generateFrac(p, &CDSPSincFilterGen::calcWindowKaiser, ElementSize); normalizeFIRFilter(p, FilterLen, 1.0, ElementSize); p += FilterSize; } } const int TablePos2 = FilterSize; const int TablePos3 = FilterSize * 2; const int TablePos4 = FilterSize * 3; const int TablePos5 = FilterSize * 4; const int TablePos6 = FilterSize * 5; const int TablePos7 = FilterSize * 6; const int TablePos8 = FilterSize * 7; double* const TableEnd = Table + (FilterFracs + 1) * FilterSize; p = Table; if (InterpPoints == 8) { if (ElementSize == 3) { // Calculate 2nd order spline (polynomial) interpolation // coefficients using 8 points. while (p < TableEnd) { calcSpline2p8Coeffs(p, p[0], p[TablePos2], p[TablePos3], p[TablePos4], p[TablePos5], p[TablePos6], p[TablePos7], p[TablePos8]); p += ElementSize; } } else if (ElementSize == 4) { // Calculate 3rd order spline (polynomial) interpolation // coefficients using 8 points. while (p < TableEnd) { calcSpline3p8Coeffs(p, p[0], p[TablePos2], p[TablePos3], p[TablePos4], p[TablePos5], p[TablePos6], p[TablePos7], p[TablePos8]); p += ElementSize; } } } else { // Calculate linear interpolation coefficients. while (p < TableEnd) { p[1] = p[TablePos2] - p[0]; p += ElementSize; } } } /** * @param i Filter index, in the range 0 to FilterFracs, inclusive. * @return Reference to the filter. */ const double& operator [](const int i) const { R8BASSERT(i >= 0 && i <= FilterFracs); return (Table[i * FilterSize]); } private: static const int FilterSize = FilterLen * ElementSize; ///< This constant ///< specifies the "size" of a single filter in "double" elements. ///< double Table[ FilterSize * (FilterFracs + InterpPoints)]; ///< The ///< table of fractional delay filters for all discrete fractional ///< x = 0..1 sample positions, and interpolation coefficients. ///< }; /** * @brief Fractional delay filter bank-based interpolator class. * * Class implements the fractional delay interpolator. This implementation at * first puts the input signal into a ring buffer and then performs * interpolation. The interpolation is performed using sinc-based fractional * delay filters. These filters are contained in a bank, and for higher * precision they are interpolated between adjacent filters. * * To increase sample timing precision, this class uses "resettable counter" * approach. This gives less than "1 per 100 billion" sample timing error when * converting 44100 to 48000 sample rate. * * VERY IMPORTANT: the interpolation step should not exceed FilterLen / 2 + 1 * samples or the algorithm in its current form will fail. However, this * condition can be easily met if the input signal is suitably downsampled * first before the interpolation is performed. * * @param FilterLen Specifies the number of samples (taps) each fractional * delay filter should have. See the r8b::CDSPFracDelayFilterBank class for * more details. * @param FilterFracs The number of fractional delay positions to sample. See * the r8b::CDSPFracDelayFilterBank class for more details. */ template class CDSPFracInterpolator final : public CDSPProcessor { public: /** * Constructor initalizes the interpolator. It is important to call the * getMaxOutLen() function afterwards to obtain the optimal output buffer * length. * * @param aSrcSampleRate Source sample rate. * @param aDstSampleRate Destination sample rate. * @param aInitFracPos Initial fractional position, in samples, in the * range [0; 1). A non-zero value can be specified to remove the * fractional delay introduced by a minimum-phase filter. This value is * usually equal to the CDSPBlockConvolver.getLatencyFrac() value. */ CDSPFracInterpolator(const double aSrcSampleRate, const double aDstSampleRate, const double aInitFracPos) : SrcSampleRate(aSrcSampleRate) , DstSampleRate(aDstSampleRate) , InitFracPos(aInitFracPos) { R8BASSERT(SrcSampleRate > 0.0); R8BASSERT(DstSampleRate > 0.0); R8BASSERT(InitFracPos >= 0.0 && InitFracPos < 1.0); R8BASSERT(BufLenBits >= 5); R8BASSERT(( 1 << BufLenBits ) >= FilterLen * 3); clear(); } int getLatency() const override { return (0); } double getLatencyFrac() const override { return (0.0); } int getInLenBeforeOutStart(const int NextInLen) const override { return (FilterLenD2 + NextInLen); } int getMaxOutLen(const int MaxInLen) const override { R8BASSERT(MaxInLen >= 0); return ((int)ceil(MaxInLen * DstSampleRate / SrcSampleRate) + 1); } /** * Function changes the destination sample rate "on the fly". Note that * the getMaxOutLen() function may needed to be called after calling this * function as the maximal number of output samples produced by the * interpolator depends on the destination sample rate. * * It can be a useful approach to construct *this object passing the * maximal possible destination sample rate to the constructor, obtaining * the getMaxOutLen() value and then setting the destination sample rate * to whatever lower value is needed. * * It is advisable to change the sample rate in small increments, and as * rarely as possible: e.g. every several samples. * * @param NewDstSampleRate New destination sample rate. */ void setDstSampleRate(const double NewDstSampleRate) { R8BASSERT(DstSampleRate > 0.0); DstSampleRate = NewDstSampleRate; InCounter = 0; InPosInt = 0; InPosShift = InPosFrac; } /** * Function clears (resets) the state of *this object and returns it to * the state after construction. All input data accumulated in the * internal buffer so far will be discarded. * * Note that the destination sample rate will remain unchanged, even if it * was changed since the time of *this object's construction. */ void clear() override { BufLeft = 0; WritePos = 0; ReadPos = BufLen - FilterLenD2Minus1; // Set "read" position to // account for filter's latency at zero fractional delay which // equals to FilterLenD2Minus1. memset(&Buf[ReadPos], 0, FilterLenD2Minus1 * sizeof(double)); InCounter = 0; InPosInt = 0; InPosFrac = InitFracPos; InPosShift = InitFracPos; } int process(double* ip, int l, double*& op0) override { R8BASSERT(l >= 0); R8BASSERT(ip != op0 || l == 0 || SrcSampleRate > DstSampleRate); double* op = op0; while (l > 0) { // Add new input samples to both halves of the ring buffer. const int b = min(min(l, BufLen - WritePos), BufLeftMax - BufLeft); double* const wp1 = Buf + WritePos; double* const wp2 = wp1 + BufLen; int i; for (i = 0; i < b; ++i) { wp1[i] = ip[i]; wp2[i] = ip[i]; } ip += b; WritePos = (WritePos + b) & BufLenMask; l -= b; BufLeft += b; // Produce as many output samples as possible. while (BufLeft >= FilterLenD2Plus1) { double x = InPosFrac * FilterFracs; const int fti = (int)x; // Function table index. x -= fti; // Coefficient for interpolation between adjacent // fractional delay filters. const double x2 = x * x; const double* const ftp = &FilterBank[fti]; const double* const rp = Buf + ReadPos; double s = 0.0; int ii = 0; #if R8B_FLTTEST const double x3 = x2 * x; #endif // R8B_FLTTEST for (i = 0; i < FilterLen; ++i) { #if !R8B_FLTTEST s += (ftp[ii] + ftp[ii + 1] * x + ftp[ii + 2] * x2) * rp[i]; #else // !R8B_FLTTEST s += ( ftp[ ii ] + ftp[ ii + 1 ] * x + ftp[ ii + 2 ] * x2 + ftp[ ii + 3 ] * x3 ) * rp[ i ]; #endif // !R8B_FLTTEST ii += FilterElementSize; } *op = s; op++; InCounter++; const double NextInPos = InCounter * SrcSampleRate / DstSampleRate + InPosShift; const int NextInPosInt = (int)NextInPos; const int PosIncr = NextInPosInt - InPosInt; InPosInt = NextInPosInt; InPosFrac = NextInPos - NextInPosInt; ReadPos = (ReadPos + PosIncr) & BufLenMask; BufLeft -= PosIncr; } } if (InCounter > 1000) { // Reset the interpolation position counter to achieve a higher // sample timing precision. InCounter = 0; InPosInt = 0; InPosShift = InPosFrac; } return (int(op - op0)); } private: #if !R8B_FLTTEST static const int FilterElementSize = 3; ///< The number of "doubles" a ///< single filter tap consists of (includes interpolation ///< coefficients). ///< #else // !R8B_FLTTEST static const int FilterElementSize = 4; ///< The number of "doubles" a ///< single filter tap consists of (includes interpolation ///< coefficients). During filter testing a higher precision ///< interpolation is used. ///< #endif // !R8B_FLTTEST static const int FilterLenD2 = FilterLen >> 1; ///< = FilterLen / 2. ///< static const int FilterLenD2Minus1 = FilterLenD2 - 1; ///< = ///< FilterLen / 2 - 1. This value also equals to filter's latency in ///< samples (taps). ///< static const int FilterLenD2Plus1 = FilterLenD2 + 1; ///< = ///< FilterLen / 2 + 1. ///< static const int BufLenBits = 8; ///< The length of the ring buffer, ///< expressed as Nth power of 2. This value can be reduced if it is ///< known that only short input buffers will be passed to the ///< interpolator. The minimum value of this parameter is 5, and ///< 1 << BufLenBits should be at least 3 times larger than the ///< FilterLen. ///< static const int BufLen = 1 << BufLenBits; ///< The length of the ring ///< buffer. The actual length is twice as long to allow "beyond max ///< position" positioning. ///< static const int BufLenMask = BufLen - 1; ///< Mask used for quick buffer ///< position wrapping. ///< static const int BufLeftMax = BufLen - FilterLenD2Minus1; ///< The number ///< of new samples that the ring buffer can hold at most. The ///< remaining FilterLenD2Minus1 samples hold "previous" input samples ///< for the filter. ///< double Buf[ BufLen * 2 ]; ///< The ring buffer. ///< double SrcSampleRate = 0; ///< Source sample rate. ///< double DstSampleRate = 0; ///< Destination sample rate. ///< double InitFracPos = 0; ///< Initial fractional position, in samples, in the ///< range [0; 1). ///< int BufLeft = 0; ///< The number of samples left in the buffer to process. ///< When this value is below FilterLenD2Plus1, the interpolation ///< cycle ends. ///< int WritePos = 0; ///< The current buffer write position. Incremented together ///< with the BufLeft variable. ///< int ReadPos = 0; ///< The current buffer read position. ///< int InCounter = 0; ///< Interpolation step counter. ///< int InPosInt = 0; ///< Interpolation position (integer part). ///< double InPosFrac = 0; ///< Interpolation position (fractional part). ///< double InPosShift = 0; ///< Interpolation position fractional shift. ///< #if !R8B_FLTTEST static const CDSPFracDelayFilterBank FilterBank; ///< Filter bank object, defined statically if no ///< filter test takes place. ///< #else // !R8B_FLTTEST public: CDSPFracDelayFilterBank< FilterLen, FilterFracs, 4, 8 > FilterBank; ///< ///< Filter bank object, defined as a member variable to allow for ///< recalculation. ///< #endif // !R8B_FLTTEST }; // --------------------------------------------------------------------------- #if !R8B_FLTTEST template const CDSPFracDelayFilterBank CDSPFracInterpolator::FilterBank; #endif // !R8B_FLTTEST // --------------------------------------------------------------------------- } // namespace r8b #endif // R8B_CDSPFRACINTERPOLATOR_INCLUDED