//$ nobt
//$ nocpp

/**
 * @file CDSPResampler.h
 *
 * @brief The master sample rate converter (resampler) class.
 *
 * This file includes the master sample rate converter (resampler) class that
 * combines all elements of this library into a single front-end class.
 *
 * r8brain-free-src Copyright (c) 2013-2025 Aleksey Vaneev
 *
 * See the "LICENSE" file for license.
 */

#ifndef R8B_CDSPRESAMPLER_INCLUDED
#define R8B_CDSPRESAMPLER_INCLUDED

#include "CDSPBlockConvolver.h"
#include "CDSPFracInterpolator.h"
#include "CDSPHBDownsampler.h"
#include "CDSPHBUpsampler.h"

namespace r8b {

/**
 * @brief The master sample rate converter (resampler) class.
 *
 * This class can be considered the "master" sample rate converter (resampler)
 * class since it combines all functionality of this library into a single
 * front-end class that performs sample rate conversion to/from any sample
 * rate, including non-integer sample rates.
 *
 * Note that objects of this class can be constructed on the stack as it has a
 * small member data size. The default template parameters of this class are
 * suited for 27-bit fixed point resampling.
 *
 * Use the CDSPResampler16 class for 16-bit resampling.
 *
 * Use the CDSPResampler16IR class for 16-bit impulse response resampling.
 *
 * Use the CDSPResampler24 class for 24-bit resampling (including 32-bit
 * floating point resampling).
 */

class CDSPResampler : public CDSPProcessor {
 public:
  /**
	 * @brief Initalizes the resampler object.
	 *
	 * Note that increasing the transition band and decreasing attenuation
	 * reduces the filter length, this in turn reduces the "input before
	 * output" delay. However, the filter length has only a minor influence on
	 * the overall resampling speed.
	 *
	 * It should be noted that the `ReqAtten` specifies the minimal difference
	 * between the loudest input signal component and the produced aliasing
	 * artifacts during resampling. For example, if `ReqAtten=100` was
	 * specified when performing 2x upsampling, the analysis of the resulting
	 * signal may display high-frequency components which are quieter than
	 * the loudest part of the input signal by only 100 decibel meaning
	 * the high-frequency part did not become "magically" completely silent
	 * after resampling. You have to specify a higher `ReqAtten` value if you
	 * need a totally clean high-frequency content. On the other hand, it may
	 * not be reasonable to have a high-frequency content cleaner than
	 * the input signal itself: if the input signal is 16-bit, setting
	 * `ReqAtten` to 180 will make its high-frequency content 24-bit, but
	 * the original part of the signal will remain 16-bit.
	 *
	 * @param SrcSampleRate Source signal's sample rate. Both sample rates can
	 * be specified as a ratio: e.g., `SrcSampleRate = 1.0`,
	 * `DstSampleRate = 2.0`.
	 * @param DstSampleRate Destination signal's sample rate. The "power of 2"
	 * ratios between the source and destination sample rates force resampler
	 * to use several fast "power of 2" resampling steps, without using
	 * fractional interpolation at all.
	 * @param aMaxInLen The maximal planned length of the input buffer (in
	 * samples) that will be passed to the resampler. The resampler relies on
	 * this value as it allocates intermediate buffers. Input buffers longer
	 * than this value should never be supplied to the resampler. Note that
	 * upsampling produces more samples than was provided on input, so at
	 * higher upsampling ratios it is advisable to use smaller `aMaxInLen`
	 * values to reduce memory footprint. When downsampling, a larger
	 * `aMaxInLen` is suggested in order to increase downsampling performance.
	 * @param ReqTransBand Required transition band, in percent of the
	 * spectral space of the input signal (or the output signal if
	 * downsampling is performed) between filter's -3 dB point and the Nyquist
	 * frequency. The range is from CDSPFIRFilter::getLPMinTransBand() to
	 * CDSPFIRFilter::getLPMaxTransBand(), inclusive. When upsampling 88200 or
	 * 96000 audio to higher sample rates the ReqTransBand can be considerably
	 * increased, up to 30. The selection of ReqTransBand depends on the level
	 * of desire to preserve the high-frequency content. While values 0.5 to 2
	 * are extremely "greedy" settings, not necessary in most cases, values 2
	 * to 3 can be used in most cases. Values 3 to 4 are relaxed settings, but
	 * they still offer a flat frequency response up to 21kHz with 44.1k
	 * source or destination sample rate.
	 * @param ReqAtten Required stop-band attenuation in decibel, in the
	 * range CDSPFIRFilter::getLPMinAtten() to CDSPFIRFilter::getLPMaxAtten(),
	 * inclusive. The actual attenuation may be 0.40-4.46 dB higher. The
	 * general formula for selecting the `ReqAtten` is `6.02 * Bits + 40`,
	 * where `Bits` is the bit resolution (e.g., 16, 24), 40 is an added
	 * resolution for dynamic signals; this value can be decreased to 20 to
	 * 10, if the signal being resampled is non-dynamic (e.g., an impulse
	 * response or filter, with a non-steep frequency response).
	 * @param ReqPhase Required filter's phase response. Note that this
	 * setting does not affect interpolator's phase response which is always
	 * linear-phase. Also note that if the "power of 2" resampling was engaged
	 * by the resampler together with the minimum-phase response, the audio
	 * stream may become fractionally delayed, depending on the minimum-phase
	 * filter's actual fractional delay. Linear-phase filters do not have
	 * fractional delay.
	 * @see EDSPFilterPhaseResponse
	 */

  CDSPResampler(
      const double SrcSampleRate,
      const double DstSampleRate,
      const int aMaxInLen,
      const double ReqTransBand = 2.0,
      const double ReqAtten = 206.91,
      const EDSPFilterPhaseResponse ReqPhase = fprLinearPhase)
      : StepCapacity(0),
        StepCount(0),
        MaxInLen(aMaxInLen),
        CurMaxOutLen(aMaxInLen),
        LatencyFrac(0.0) {
    R8BASSERT(SrcSampleRate > 0.0);
    R8BASSERT(DstSampleRate > 0.0);
    R8BASSERT(MaxInLen > 0);

    R8BCONSOLE(
        "* CDSPResampler: src=%.1f dst=%.1f len=%i tb=%.1f "
        "att=%.2f ph=%i\n",
        SrcSampleRate,
        DstSampleRate,
        aMaxInLen,
        ReqTransBand,
        ReqAtten,
        (int)ReqPhase);

    if (SrcSampleRate == DstSampleRate) {
      return;
    }

    TmpBufCapacities[0] = 0;
    TmpBufCapacities[1] = 0;
    CurTmpBuf = 0;

    // Try some common efficient ratios requiring only a single step.

    const int CommonRatioCount = 5;
    const int CommonRatios[CommonRatioCount][2] = {{1, 2}, {1, 3}, {2, 3}, {3, 2}, {3, 4}};

    int i;

    for (i = 0; i < CommonRatioCount; i++) {
      const int num = CommonRatios[i][0];
      const int den = CommonRatios[i][1];

      if (SrcSampleRate * num == DstSampleRate * den) {
        addProcessor(new CDSPBlockConvolver(
            CDSPFIRFilterCache ::getLPFilter(
                1.0 / (num > den ? num : den), ReqTransBand, ReqAtten, ReqPhase, num),
            num,
            den,
            LatencyFrac));

        createTmpBuffers();
        return;
      }
    }

    // Try whole-number power-of-2 or 3*power-of-2 upsampling.

    for (i = 2; i <= 3; i++) {
      bool WasFound = false;
      int c = 0;

      while (true) {
        const double NewSR = SrcSampleRate * (i << c);

        if (NewSR == DstSampleRate) {
          WasFound = true;
          break;
        }

        if (NewSR > DstSampleRate) {
          break;
        }

        c++;
      }

      if (WasFound) {
        addProcessor(new CDSPBlockConvolver(
            CDSPFIRFilterCache ::getLPFilter(1.0 / i, ReqTransBand, ReqAtten, ReqPhase, i),
            i,
            1,
            LatencyFrac));

        const bool IsThird = (i == 3);

        for (i = 0; i < c; i++) {
          addProcessor(new CDSPHBUpsampler(ReqAtten, i, IsThird, LatencyFrac));
        }

        createTmpBuffers();
        return;
      }
    }

    if (DstSampleRate * 2.0 > SrcSampleRate) {
      // Upsampling or fractional downsampling down to 2X.

      const double NormFreq =
          (DstSampleRate > SrcSampleRate ? 0.5 : 0.5 * DstSampleRate / SrcSampleRate);

      addProcessor(new CDSPBlockConvolver(
          CDSPFIRFilterCache ::getLPFilter(NormFreq, ReqTransBand, ReqAtten, ReqPhase, 2.0),
          2,
          1,
          LatencyFrac));

      // Try intermediate interpolated resampling with subsequent 2X
      // or 3X upsampling.

      const double tbw = 0.0175; // Intermediate filter's transition
                                 // band extension coefficient.
      const double ThreshSampleRate =
          SrcSampleRate / (1.0 - tbw * ReqTransBand); // Make sure intermediate
      // filter's transition band is not steeper than ReqTransBand
      // (this keeps the latency under control).

      int c = 0;
      int div = 1;

      while (true) {
        const int ndiv = div * 2;

        if (DstSampleRate < ThreshSampleRate * ndiv) {
          break;
        }

        div = ndiv;
        c++;
      }

      int c2 = 0;
      int div2 = 1;

      while (true) {
        const int ndiv = div * (c2 == 0 ? 3 : 2);

        if (DstSampleRate < ThreshSampleRate * ndiv) {
          break;
        }

        div2 = ndiv;
        c2++;
      }

      const double SrcSampleRate2 = SrcSampleRate * 2.0;
      int tmp1;
      int tmp2;

      if (c == 1 && getWholeStepping(SrcSampleRate2, DstSampleRate, tmp1, tmp2)) {
        // Do not use intermediate interpolation if whole stepping is
        // available as it performs very fast.

        c = 0;
      }

      if (c > 0) {
        // Add steps using intermediate interpolation.

        int num;

        if (c2 > 0 && div2 > div) {
          div = div2;
          c = c2;
          num = 3;
        } else {
          num = 2;
        }

        addProcessor(new CDSPFracInterpolator(
            SrcSampleRate2 * div, DstSampleRate, ReqAtten, false, LatencyFrac));

        double tb = (1.0 - SrcSampleRate * div / DstSampleRate) /
            tbw; // Divide TransBand by a constant that assures a
                 // linear response in the pass-band.

        if (tb > CDSPFIRFilter ::getLPMaxTransBand()) {
          tb = CDSPFIRFilter ::getLPMaxTransBand();
        }

        addProcessor(new CDSPBlockConvolver(
            CDSPFIRFilterCache ::getLPFilter(1.0 / num, tb, ReqAtten, ReqPhase, num),
            num,
            1,
            LatencyFrac));

        const bool IsThird = (num == 3);

        for (i = 1; i < c; i++) {
          addProcessor(new CDSPHBUpsampler(ReqAtten, i - 1, IsThird, LatencyFrac));
        }
      } else {
        addProcessor(
            new CDSPFracInterpolator(SrcSampleRate2, DstSampleRate, ReqAtten, false, LatencyFrac));
      }

      createTmpBuffers();
      return;
    }

    // Use downsampling steps, including power-of-2 downsampling.

    double CheckSR = DstSampleRate * 4.0;
    int c = 0;
    double FinGain = 1.0;

    while (CheckSR <= SrcSampleRate) {
      c++;
      CheckSR *= 2.0;
      FinGain *= 0.5;
    }

    const int SrcSRDiv = (1 << c);
    int downf;
    double NormFreq = 0.5;
    bool UseInterp = true;
    bool IsThird = false;

    for (downf = 2; downf <= 3; downf++) {
      if (DstSampleRate * SrcSRDiv * downf == SrcSampleRate) {
        NormFreq = 1.0 / downf;
        UseInterp = false;
        IsThird = (downf == 3);
        break;
      }
    }

    if (UseInterp) {
      downf = 1;
      NormFreq = DstSampleRate * SrcSRDiv / SrcSampleRate;
      IsThird = (NormFreq * 3.0 <= 1.0);
    }

    for (i = 0; i < c; i++) {
      // Use fixed, very relaxed 2X downsampling filters, that at the
      // final stage only guarantee stop-band between 0.75 and pi.
      // 0.5-0.75 range will be aliased to 0.25-0.5 range which will
      // then be filtered out by the final filter.

      addProcessor(new CDSPHBDownsampler(ReqAtten, c - 1 - i, IsThird, LatencyFrac));
    }

    addProcessor(new CDSPBlockConvolver(
        CDSPFIRFilterCache ::getLPFilter(NormFreq, ReqTransBand, ReqAtten, ReqPhase, FinGain),
        1,
        downf,
        LatencyFrac));

    if (UseInterp) {
      addProcessor(new CDSPFracInterpolator(
          SrcSampleRate, DstSampleRate * SrcSRDiv, ReqAtten, IsThird, LatencyFrac));
    }

    createTmpBuffers();
  }

  virtual ~CDSPResampler() {
    int i;

    for (i = 0; i < StepCount; i++) {
      delete Steps[i];
    }
  }

  virtual int getInLenBeforeOutPos(const int ReqOutPos) const {
    R8BASSERT(ReqOutPos >= 0);

    int ReqInSamples = ReqOutPos;
    int c = StepCount;

    while (--c >= 0) {
      ReqInSamples = Steps[c]->getInLenBeforeOutPos(ReqInSamples);
    }

    return (ReqInSamples);
  }

  /**
	 * @brief Returns the number of input samples required to advance to
	 * the specified output sample position (so that the next process() call
	 * passes this output position), starting at the cleared or
	 * after-construction state of *this* object.
	 *
	 * This function works by iteratively passing 1 sample at a time until the
	 * overall output length passes the specified value. This is a relatively
	 * CPU-consuming operation. This function should be called after the
	 * clear() function call or after object's construction. The function
	 * itself calls the clear() function before return.
	 *
	 * Note that this function can be considered a legacy function, and is now
	 * used for testing purposes. It is advised to use "instant" (much faster)
	 * getInLenBeforeOutPos() and getInputRequiredForOutput() functions
	 * instead.
	 *
	 * @param ReqOutPos The required output position. Must be a non-negative
	 * value.
	 * @return The number of input samples required.
	 */

  int getInLenBeforeOutStart(const int ReqOutPos = 0) {
    R8BASSERT(ReqOutPos >= 0);

    int inc = 0;
    int outc = 0;

    while (true) {
      double ins = 0.0;
      double *op;
      outc += process(&ins, 1, op);

      if (outc > ReqOutPos) {
        clear();
        return (inc);
      }

      inc++;
    }
  }

  /**
	 * @brief Returns the number of input samples required to produce at
	 * least the specified number of output samples, starting at the cleared
	 * or after-construction state of *this* object.
	 *
	 * @param ReqOutSamples The number of output samples required. If a
	 * non-positive value was specified, the function returns 0.
	 * @return The number of input samples required.
	 */

  int getInputRequiredForOutput(const int ReqOutSamples) const {
    if (ReqOutSamples < 1) {
      return (0);
    }

    return (getInLenBeforeOutPos(ReqOutSamples - 1) + 1);
  }

  virtual int getLatency() const {
    return (0);
  }

  virtual double getLatencyFrac() const {
    return (LatencyFrac);
  }

  /**
	 * @brief This implementation ignores the supplied parameter and returns
	 * the maximal output buffer length that depends on the `MaxInLen`
	 * supplied to the constructor.
	 */

  virtual int getMaxOutLen(const int /* MaxInLen */) const {
    return (CurMaxOutLen);
  }

  /**
	 * @brief 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.
	 *
	 * This function makes it possible to use *this* object for converting
	 * separate streams from the same source sample rate to the same
	 * destination sample rate without reconstructing the object. It is more
	 * efficient to clear the state of the resampler object than to destroy it
	 * and create a new object.
	 */

  virtual void clear() {
    int i;

    for (i = 0; i < StepCount; i++) {
      Steps[i]->clear();
    }
  }

  /**
	 * @brief Performs sample rate conversion.
	 *
	 * If the source and destination sample rates are equal, the resampler
	 * will do nothing and will simply return the input buffer unchanged.
	 *
	 * You do not need to allocate an intermediate output buffer for use with
	 * this function. If required, the resampler will allocate a suitable
	 * intermediate output buffer itself.
	 *
	 * @param ip0 Input buffer. This buffer is never used as output buffer by
	 * this function. This pointer may be returned in `op0` if no resampling
	 * is happening (source sample rate equals destination sample rate).
	 * @param l The number of samples available in the input buffer. Should
	 * not exceed the `MaxInLen` supplied in the constructor.
	 * @param[out] op0 This variable receives the pointer to the resampled
	 * data. On function's return, this pointer points to *this* object's
	 * internal buffer. In real-time applications it is suggested to pass this
	 * pointer to the next output audio block and consume any data left from
	 * the previous output audio block first before calling the process()
	 * function again. The buffer pointed to by the `op0` on return is owned
	 * by the resampler, so it should not be freed by the caller.
	 * @return The number of samples available in the `op0` output buffer. If
	 * the data from the output buffer `op0` is going to be written to a
	 * bigger output buffer, it is suggested to check the returned number of
	 * samples so that no overflow of the bigger output buffer happens.
	 */

  virtual int process(double *ip0, int l, double *&op0) {
    R8BASSERT(l >= 0);

    double *ip = ip0;
    int i;

    for (i = 0; i < StepCount; i++) {
      double *op = TmpBufs[i & 1];
      l = Steps[i]->process(ip, l, op);
      ip = op;
    }

    op0 = ip;
    return (l);
  }

  /**
	 * @brief Performs resampling of an input sample buffer of the specified
	 * length in the "one-shot" mode.
	 *
	 * This function can be useful when impulse response or time-series
	 * resampling is required.
	 *
	 * @param ip Input buffer pointer.
	 * @param iplen Length of the input buffer in samples.
	 * @param[out] op Output buffer pointer.
	 * @param oplen Length of the output buffer in samples.
	 * @tparam Tin Input buffer's element type.
	 * @tparam Tout Output buffer's element type.
	 */

  template <typename Tin, typename Tout>
  void oneshot(const Tin *ip, int iplen, Tout *op, int oplen) {
    CFixedBuffer<double> Buf(MaxInLen);
    bool IsZero = false;

    while (oplen > 0) {
      int rc;
      double *p;
      int i;

      if (iplen == 0) {
        rc = MaxInLen;
        p = &Buf[0];

        if (!IsZero) {
          IsZero = true;
          memset(p, 0, MaxInLen * sizeof(p[0]));
        }
      } else {
        rc = min(iplen, MaxInLen);

        if (sizeof(Tin) == sizeof(double)) {
          p = (double *)ip;
        } else {
          p = &Buf[0];

          for (i = 0; i < rc; i++) {
            p[i] = ip[i];
          }
        }

        ip += rc;
        iplen -= rc;
      }

      double *op0;
      int wc = process(p, rc, op0);
      wc = min(oplen, wc);

      for (i = 0; i < wc; i++) {
        op[i] = (Tout)op0[i];
      }

      op += wc;
      oplen -= wc;
    }

    clear();
  }

 private:
  CFixedBuffer<CDSPProcessor *> Steps; ///< Array of processing steps.
  int StepCapacity;                    ///< The capacity of the `Steps` array.
  int StepCount;                       ///< The number of created processing steps.
  int MaxInLen;                        ///< Maximal input length.
  CFixedBuffer<double> TmpBufAll;      ///< Buffer containing both temporary
                                       ///< buffers.
  double *TmpBufs[2];                  ///< Temporary output buffers.
  int TmpBufCapacities[2];             ///< Capacities of temporary buffers, updated
                                       ///< during processing steps building.
  int CurTmpBuf;                       ///< Current temporary buffer.
  int CurMaxOutLen;                    ///< Current maximal output length.
  double LatencyFrac;                  ///< Current fractional latency. After object's
  ///< construction, equals to the remaining fractional latency in the
  ///< output.

  /**
	 * @brief Adds processor, updates `MaxOutLen` variable and adjusts length
	 * of temporary internal buffers.
	 *
	 * @param Proc Processor to add. This pointer is inherited and will be
	 * destroyed on *this* object's destruction.
	 */

  void addProcessor(CDSPProcessor *const Proc) {
    if (StepCount == StepCapacity) {
      // Reallocate and increase Steps array's capacity.

      const int NewCapacity = StepCapacity + 8;
      Steps.realloc(StepCapacity, NewCapacity);
      StepCapacity = NewCapacity;
    }

    LatencyFrac = Proc->getLatencyFrac();
    CurMaxOutLen = Proc->getMaxOutLen(CurMaxOutLen);

    if (CurMaxOutLen > TmpBufCapacities[CurTmpBuf]) {
      TmpBufCapacities[CurTmpBuf] = CurMaxOutLen;
    }

    CurTmpBuf ^= 1;

    Steps[StepCount] = Proc;
    StepCount++;
  }

  /**
	 * @brief Creates temporary buffers.
	 */

  void createTmpBuffers() {
    const int ol = TmpBufCapacities[0] + TmpBufCapacities[1];

    if (ol > 0) {
      TmpBufAll.alloc(ol);
      TmpBufs[0] = &TmpBufAll[0];
      TmpBufs[1] = &TmpBufAll[TmpBufCapacities[0]];
    }

    R8BCONSOLE("* CDSPResampler: init done\n");
  }
};

/**
 * @brief The resampler class for 16-bit resampling.
 *
 * This class defines resampling parameters suitable for 16-bit resampling,
 * using linear-phase low-pass filter. See the r8b::CDSPResampler class for
 * details.
 */

class CDSPResampler16 : public CDSPResampler {
 public:
  /**
	 * @brief Initializes the 16-bit resampler. See the r8b::CDSPResampler
	 * class for details.
	 *
	 * @param SrcSampleRate Source signal's sample rate.
	 * @param DstSampleRate Destination signal's sample rate.
	 * @param aMaxInLen The maximal planned length of the input buffer (in
	 * samples) that will be passed to the resampler.
	 * @param ReqTransBand Required transition band, in percent.
	 */

  CDSPResampler16(
      const double SrcSampleRate,
      const double DstSampleRate,
      const int aMaxInLen,
      const double ReqTransBand = 2.0)
      : CDSPResampler(
            SrcSampleRate,
            DstSampleRate,
            aMaxInLen,
            ReqTransBand,
            136.45,
            fprLinearPhase) {}
};

/**
 * @brief The resampler class for 16-bit impulse response resampling.
 *
 * This class defines resampling parameters suitable for 16-bit impulse
 * response resampling, using linear-phase low-pass filter. Impulse responses
 * are non-dynamic signals, and thus need resampler with a lesser SNR. See the
 * r8b::CDSPResampler class for details.
 */

class CDSPResampler16IR : public CDSPResampler {
 public:
  /**
	 * @brief Initializes the 16-bit impulse response resampler. See the
	 * r8b::CDSPResampler class for details.
	 *
	 * @param SrcSampleRate Source signal's sample rate.
	 * @param DstSampleRate Destination signal's sample rate.
	 * @param aMaxInLen The maximal planned length of the input buffer (in
	 * samples) that will be passed to the resampler.
	 * @param ReqTransBand Required transition band, in percent.
	 */

  CDSPResampler16IR(
      const double SrcSampleRate,
      const double DstSampleRate,
      const int aMaxInLen,
      const double ReqTransBand = 2.0)
      : CDSPResampler(
            SrcSampleRate,
            DstSampleRate,
            aMaxInLen,
            ReqTransBand,
            109.56,
            fprLinearPhase) {}
};

/**
 * @brief The resampler class for 24-bit resampling.
 *
 * This class defines resampling parameters suitable for 24-bit resampling
 * (including 32-bit floating point resampling), using linear-phase low-pass
 * filter. See the r8b::CDSPResampler class for details.
 */

class CDSPResampler24 : public CDSPResampler {
 public:
  /**
	 * @brief Initializes the 24-bit resampler (including 32-bit floating
	 * point). See the r8b::CDSPResampler class for details.
	 *
	 * @param SrcSampleRate Source signal's sample rate.
	 * @param DstSampleRate Destination signal's sample rate.
	 * @param aMaxInLen The maximal planned length of the input buffer (in
	 * samples) that will be passed to the resampler.
	 * @param ReqTransBand Required transition band, in percent.
	 */

  CDSPResampler24(
      const double SrcSampleRate,
      const double DstSampleRate,
      const int aMaxInLen,
      const double ReqTransBand = 2.0)
      : CDSPResampler(
            SrcSampleRate,
            DstSampleRate,
            aMaxInLen,
            ReqTransBand,
            180.15,
            fprLinearPhase) {}
};

} // namespace r8b

#endif // R8B_CDSPRESAMPLER_INCLUDED
