//$ 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-2022 Aleksey Vaneev
 * See the "LICENSE" file for license.
 */

#ifndef R8B_CDSPRESAMPLER_INCLUDED
#define R8B_CDSPRESAMPLER_INCLUDED

#include "CDSPHBDownsampler.h"
#include "CDSPHBUpsampler.h"
#include "CDSPBlockConvolver.h"
#include "CDSPFracInterpolator.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 to perform 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:
	/**
	 * Constructor 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 sample rate. Both sample rates can
	 * be specified as a ratio, e.g. SrcSampleRate = 1.0, DstSampleRate = 2.0.
	 * @param DstSampleRate Destination signal 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 MaxInLen
	 * values to reduce memory footprint. When downsampling, a larger MaxInLen
	 * 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 a 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 CDSPFIRFilterCache::getLPFilter()
	 */

	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 a fixed very relaxed 2X downsampling filters, that at
			// the final stage only guarantees 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 getLatency() const
	{
		return( 0 );
	}

	virtual double getLatencyFrac() const
	{
		return( LatencyFrac );
	}

	/**
	 * This function 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 );
	}

	/**
	 * 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.
	 *
	 * 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();
		}
	}

	/**
	 * Function 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 );
	}

	/**
	 * Function performs resampling of an input sample buffer of the specified
	 * length in the "one-shot" mode. This function can be useful when impulse
	 * response 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();
	}

	/**
	 * Function obtains overall input sample count required to produce first
	 * output sample. Function works by iteratively passing 1 sample at a time
	 * until output begins. 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 it is advisable to cache the value returned by this function,
	 * for each SrcSampleRate/DstSampleRate pair, if it is called frequently.
	 */

	int getInLenBeforeOutStart()
	{
		int inc = 0;

		while( true )
		{
			double ins = 0.0;
			double* op;

			if( process( &ins, 1, op ) > 0 )
			{
				clear();
				return( inc );
			}

			inc++;
		}
	}

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.
		///<

	/**
	 * Function 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++;
	}

	/**
	 * Function 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:
	/**
	 * Constructor initializes the 16-bit resampler. See the
	 * r8b::CDSPResampler class for details.
	 *
	 * @param SrcSampleRate Source signal sample rate.
	 * @param DstSampleRate Destination signal 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:
	/**
	 * Constructor initializes the 16-bit impulse response resampler. See the
	 * r8b::CDSPResampler class for details.
	 *
	 * @param SrcSampleRate Source signal sample rate.
	 * @param DstSampleRate Destination signal 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:
	/**
	 * Constructor initializes the 24-bit resampler (including 32-bit floating
	 * point). See the r8b::CDSPResampler class for details.
	 *
	 * @param SrcSampleRate Source signal sample rate.
	 * @param DstSampleRate Destination signal 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