//$ nobt
//$ nocpp

/**
 * @file CDSPFIRFilter.h
 *
 * @brief FIR filter generator and filter cache classes.
 *
 * This file includes low-pass FIR filter generator and filter cache.
 *
 * r8brain-free-src Copyright (c) 2013-2025 Aleksey Vaneev
 *
 * See the "LICENSE" file for license.
 */

#ifndef R8B_CDSPFIRFILTER_INCLUDED
#define R8B_CDSPFIRFILTER_INCLUDED

#include "CDSPSincFilterGen.h"
#include "CDSPRealFFT.h"

namespace r8b {

/**
 * @brief Enumeration of filter's phase responses.
 */

enum EDSPFilterPhaseResponse
{
	fprLinearPhase = 0, ///< Linear-phase response. Features a linear-phase,
		///< high-latency response, with the latency expressed as an integer
		///< value.
	fprMinPhase ///< Minimum-phase response. Features a minimal-latency
		///< response, but the response's phase is non-linear. The latency is
		///< usually expressed as a non-integer value, and is usually small,
		///< but is never equal to zero. The minimum-phase filter is obtained
		///< from a linear-phase filter. Note that since in the context of
		///< r8brain-free-src other filters (interpolation, half-band) remain
		///< linear-phase, the resulting phase will be "intermediate". The
		///< minimum-phase transformation has precision limits: this may skew
		///< both the -3 dB point and attenuation of the filter being
		///< transformed: as it was measured, the skew happens purely at
		///< random, and in most cases is within tolerable range. In a small
		///< (1%) random subset of cases the skew is bigger and cannot be
		///< predicted. Minimum-phase transform requires 64-bit floating-point
		///< FFT; results with 32-bit float FFT are far from optimal.
};

/**
 * @brief Calculation and storage class for FIR filters.
 *
 * Class that implements calculation and storing of a FIR filter (currently
 * contains low-pass filter calculation routine designed for sample rate
 * conversion). Objects of this class cannot be created directly, but can be
 * obtained via the CDSPFilterCache::getLPFilter() static function.
 */

class CDSPFIRFilter : public R8B_BASECLASS
{
	R8BNOCTOR( CDSPFIRFilter )

	friend class CDSPFIRFilterCache;

public:
	~CDSPFIRFilter()
	{
		R8BASSERT( RefCount == 0 );

		delete Next;
	}

	/**
	 * @brief Returns the minimal allowed low-pass filter's transition band,
	 * in percent.
	 */

	static double getLPMinTransBand()
	{
		return( 0.5 );
	}

	/**
	 * @brief Returns the maximal allowed low-pass filter's transition band,
	 * in percent.
	 */

	static double getLPMaxTransBand()
	{
		return( 45.0 );
	}

	/**
	 * @brief Returns the minimal allowed low-pass filter's stop-band
	 * attenuation, in decibel.
	 */

	static double getLPMinAtten()
	{
		return( 49.0 );
	}

	/**
	 * @brief Returns the maximal allowed low-pass filter's stop-band
	 * attenuation, in decibel.
	 */

	static double getLPMaxAtten()
	{
		return( 218.0 );
	}

	/**
	 * @brief Returns `true` if kernel block of *this* filter has zero-phase
	 * response.
	 */

	bool isZeroPhase() const
	{
		return( IsZeroPhase );
	}

	/**
	 * @brief Returns filter's latency, in samples (integer part).
	 */

	int getLatency() const
	{
		return( Latency );
	}

	/**
	 * @brief Returns filter's latency, in samples (fractional part). Always
	 * zero for linear-phase filters.
	 */

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

	/**
	 * @brief Returns filter kernel length, in samples. Not to be confused
	 * with the block length.
	 */

	int getKernelLen() const
	{
		return( KernelLen );
	}

	/**
	 * @brief Returns filter's block length, expressed as Nth power of 2.
	 * The actual length is twice as large due to zero-padding.
	 */

	int getBlockLenBits() const
	{
		return( BlockLenBits );
	}

	/**
	 * @brief Returns pointer to filter's kernel block, in complex-numbered
	 * form obtained via the CDSPRealFFT::forward() function call.
	 *
	 * Zero-padded, gain-adjusted with the CDSPRealFFT::getInvMulConst()
	 * multiplied by `ReqGain` constant, immediately suitable for convolution.
	 * Kernel block may have "zero-phase" response, depending on the
	 * isZeroPhase() function's result.
	 */

	const double* getKernelBlock() const
	{
		return( KernelBlock );
	}

	/**
	 * @brief Reduces reference count to *this* object.
	 *
	 * This function should be called when the filter obtained via the filter
	 * cache is no longer needed.
	 */

	void unref();

private:
	double ReqNormFreq; ///< Required normalized frequency, 0 to 1 inclusive.
	double ReqTransBand; ///< Required transition band in percent, as passed
		///< by the user.
	double ReqAtten; ///< Required stop-band attenuation in decibel, as passed
		///< by the user (positive value).
	EDSPFilterPhaseResponse ReqPhase; ///< Required filter's phase response.
	double ReqGain; ///< Required overall filter's gain.
	CDSPFIRFilter* Next; ///< Next FIR filter in cache's list.
	int RefCount; ///< The number of references made to *this* FIR filter.
	bool IsZeroPhase; ///< `true` if kernel block of *this* filter has
		///< zero-phase response.
	int Latency; ///< Filter's latency in samples (integer part).
	double LatencyFrac; ///< Filter's latency in samples (fractional part).
	int KernelLen; ///< Filter kernel length, in samples.
	int BlockLenBits; ///< Block length used to store *this* FIR filter,
		///< expressed as Nth power of 2. This value is used directly by the
		///< convolver.
	CFixedBuffer< double > KernelBlock; ///< FIR filter buffer, capacity
		///< equals to `1 << ( BlockLenBits + 1 )`. Second half of the buffer
		///< contains zero-padding to allow alias-free convolution.
		///< Address-aligned.

	CDSPFIRFilter()
		: RefCount( 1 )
	{
	}

	/**
	 * @brief Builds filter kernel based on the "Req" parameters.
	 *
	 * @param ExtAttenCorrs External attentuation correction table, for
	 * internal use.
	 */

	void buildLPFilter( const double* const ExtAttenCorrs )
	{
		const double tb = ReqTransBand * 0.01;
		double pwr;
		double fo1;
		double hl;
		double atten = -ReqAtten;

		if( tb >= 0.25 )
		{
			if( ReqAtten >= 117.0 )
			{
				atten -= 1.60;
			}
			else
			if( ReqAtten >= 60.0 )
			{
				atten -= 1.91;
			}
			else
			{
				atten -= 2.25;
			}
		}
		else
		if( tb >= 0.10 )
		{
			if( ReqAtten >= 117.0 )
			{
				atten -= 0.69;
			}
			else
			if( ReqAtten >= 60.0 )
			{
				atten -= 0.73;
			}
			else
			{
				atten -= 1.13;
			}
		}
		else
		{
			if( ReqAtten >= 117.0 )
			{
				atten -= 0.21;
			}
			else
			if( ReqAtten >= 60.0 )
			{
				atten -= 0.25;
			}
			else
			{
				atten -= 0.36;
			}
		}

		static const int AttenCorrCount = 264;
		static const double AttenCorrMin = 49.0;
		static const double AttenCorrDiff = 176.25;
		int AttenCorr = (int) floor(( -atten - AttenCorrMin ) *
			AttenCorrCount / AttenCorrDiff + 0.5 );

		AttenCorr = min( AttenCorrCount, max( 0, AttenCorr ));

		if( ExtAttenCorrs != R8B_NULL )
		{
			atten -= ExtAttenCorrs[ AttenCorr ];
		}
		else
		if( tb >= 0.25 )
		{
			static const double AttenCorrScale = 101.0;
			static const signed char AttenCorrs[] = {
				-127, -127, -125, -125, -122, -119, -115, -110, -104, -97,
				-91, -82, -75, -24, -16, -6, 4, 14, 24, 29, 30, 32, 37, 44,
				51, 57, 63, 67, 65, 50, 53, 56, 58, 60, 63, 64, 66, 68, 74,
				77, 78, 78, 78, 79, 79, 60, 60, 60, 61, 59, 52, 47, 41, 36,
				30, 24, 17, 9, 0, -8, -10, -11, -14, -13, -18, -25, -31, -38,
				-44, -50, -57, -63, -68, -74, -81, -89, -96, -101, -104, -107,
				-109, -110, -86, -84, -85, -82, -80, -77, -73, -67, -62, -55,
				-48, -42, -35, -30, -20, -11, -2, 5, 6, 6, 7, 11, 16, 21, 26,
				34, 41, 46, 49, 52, 55, 56, 48, 49, 51, 51, 52, 52, 52, 52,
				52, 51, 51, 50, 47, 47, 50, 48, 46, 42, 38, 35, 31, 27, 24,
				20, 16, 12, 11, 12, 10, 8, 4, -1, -6, -11, -16, -19, -17, -21,
				-24, -27, -32, -34, -37, -38, -40, -41, -40, -40, -42, -41,
				-44, -45, -43, -41, -34, -31, -28, -24, -21, -18, -14, -10,
				-5, -1, 2, 5, 8, 7, 4, 3, 2, 2, 4, 6, 8, 9, 9, 10, 10, 10, 10,
				9, 8, 9, 11, 14, 13, 12, 11, 10, 8, 7, 6, 5, 3, 2, 2, -1, -1,
				-3, -3, -4, -4, -5, -4, -6, -7, -9, -5, -1, -1, 0, 1, 0, -2,
				-3, -4, -5, -5, -8, -13, -13, -13, -12, -13, -12, -11, -11,
				-9, -8, -7, -5, -3, -1, 2, 4, 6, 9, 10, 11, 14, 18, 21, 24,
				27, 30, 34, 37, 37, 39, 40 };

			atten -= AttenCorrs[ AttenCorr ] / AttenCorrScale;
		}
		else
		if( tb >= 0.10 )
		{
			static const double AttenCorrScale = 210.0;
			static const signed char AttenCorrs[] = {
				-113, -118, -122, -125, -126, -97, -95, -92, -92, -89, -82,
				-75, -69, -48, -42, -36, -30, -22, -14, -5, -2, 1, 6, 13, 22,
				28, 35, 41, 48, 55, 56, 56, 61, 65, 71, 77, 81, 83, 85, 85,
				74, 74, 73, 72, 71, 70, 68, 64, 59, 56, 49, 52, 46, 42, 36,
				32, 26, 20, 13, 7, -2, -6, -10, -15, -20, -27, -33, -38, -44,
				-43, -48, -53, -57, -63, -69, -73, -75, -79, -81, -74, -76,
				-77, -77, -78, -81, -80, -80, -78, -76, -65, -62, -59, -56,
				-51, -48, -44, -38, -33, -25, -19, -13, -5, -1, 2, 7, 13, 17,
				21, 25, 30, 35, 40, 45, 50, 53, 56, 57, 55, 58, 59, 62, 64,
				67, 67, 68, 68, 62, 61, 61, 59, 59, 57, 57, 55, 52, 48, 42,
				38, 35, 31, 26, 20, 15, 13, 10, 7, 3, -2, -8, -13, -17, -23,
				-28, -34, -37, -40, -41, -45, -48, -50, -53, -57, -59, -62,
				-63, -63, -57, -57, -56, -56, -54, -54, -53, -49, -48, -41,
				-38, -33, -31, -26, -23, -18, -12, -9, -7, -7, -3, 0, 5, 9,
				14, 16, 20, 22, 21, 23, 25, 27, 28, 29, 34, 33, 35, 33, 31,
				30, 29, 29, 26, 26, 25, 24, 20, 19, 15, 10, 8, 4, 1, -2, -6,
				-10, -16, -19, -23, -26, -27, -30, -34, -39, -43, -47, -51,
				-52, -54, -56, -58, -59, -62, -63, -66, -65, -65, -64, -59,
				-57, -54, -52, -48, -44, -42, -37, -32, -22, -17, -10, -3, 5,
				13, 22, 30, 40, 50, 60, 72 };

			atten -= AttenCorrs[ AttenCorr ] / AttenCorrScale;
		}
		else
		{
			static const double AttenCorrScale = 196.0;
			static const signed char AttenCorrs[] = {
				-15, -17, -20, -20, -20, -21, -20, -16, -17, -18, -17, -13,
				-12, -11, -9, -7, -5, -4, -1, 1, 3, 4, 5, 6, 7, 9, 9, 10, 10,
				10, 11, 11, 11, 12, 12, 12, 10, 11, 10, 10, 8, 10, 11, 10, 11,
				11, 13, 14, 15, 19, 27, 26, 23, 18, 14, 8, 4, -2, -6, -12,
				-17, -23, -28, -33, -37, -42, -46, -49, -53, -57, -60, -61,
				-64, -65, -67, -66, -66, -66, -65, -64, -61, -59, -56, -52,
				-48, -42, -38, -31, -27, -19, -13, -7, -1, 8, 14, 22, 29, 37,
				45, 52, 59, 66, 73, 80, 86, 91, 96, 100, 104, 108, 111, 114,
				115, 117, 118, 120, 120, 118, 117, 114, 113, 111, 107, 103,
				99, 95, 89, 84, 78, 72, 66, 60, 52, 44, 37, 30, 21, 14, 6, -3,
				-11, -18, -26, -34, -43, -51, -58, -65, -73, -78, -85, -90,
				-97, -102, -107, -113, -115, -118, -121, -125, -125, -126,
				-126, -126, -125, -124, -121, -119, -115, -111, -109, -101,
				-102, -95, -88, -81, -73, -67, -63, -54, -47, -40, -33, -26,
				-18, -11, -5, 2, 8, 14, 19, 25, 31, 36, 37, 43, 47, 49, 51,
				52, 57, 57, 56, 57, 58, 58, 58, 57, 56, 52, 52, 50, 48, 44,
				41, 39, 37, 33, 31, 26, 24, 21, 18, 14, 11, 8, 4, 2, -2, -5,
				-7, -9, -11, -13, -15, -16, -18, -19, -20, -23, -24, -24, -25,
				-27, -26, -27, -29, -30, -31, -32, -35, -36, -39, -40, -44,
				-46, -51, -54, -59, -63, -69, -76, -83, -91, -98 };

			atten -= AttenCorrs[ AttenCorr ] / AttenCorrScale;
		}

		pwr = 7.43932822146293e-8 * sqr( atten ) + 0.000102747434588003 *
			cos( 0.00785021930010397 * atten ) * cos( 0.633854318781239 +
			0.103208573657699 * atten ) - 0.00798132247867036 -
			0.000903555213543865 * atten - 0.0969365532127236 * exp(
			0.0779275237937911 * atten ) - 1.37304948662012e-5 * atten * cos(
			0.00785021930010397 * atten );

		if( pwr <= 0.067665322581 )
		{
			if( tb >= 0.25 )
			{
				hl = 2.6778150875894 / tb + 300.547590563091 * atan( atan(
					2.68959772209918 * pwr )) / ( 5.5099277187035 * tb - tb *
					tanh( cos( asinh( atten ))));

				fo1 = 0.987205355829873 * tb + 1.00011788929851 * atan2(
					-0.321432067051302 - 6.19131357321578 * sqrt( pwr ),
					hl + -1.14861472207245 / ( hl - 14.1821147585957 ) + pow(
					0.9521145021664, pow( atan2( 1.12018764830637, tb ),
					2.10988901686912 * hl - 20.9691278378345 )));
			}
			else
			if( tb >= 0.10 )
			{
				hl = ( 1.56688617018066 + 142.064321294568 * pwr +
					0.00419441117131136 * cos( 243.633511747297 * pwr ) -
					0.022953443903576 * atten - 0.026629568860284 * cos(
					127.715550622571 * pwr )) / tb;

				fo1 = 0.982299356642411 * tb + 0.999441744774215 * asinh((
					-0.361783054039583 - 5.80540593623676 * sqrt( pwr )) /
					hl );
			}
			else
			{
				hl = ( 2.45739657014937 + 269.183679500541 * pwr * cos(
					5.73225668178813 + atan2( cosh( 0.988861169868941 -
					17.2201556280744 * pwr ), 1.08340138240431 * pwr ))) / tb;

				fo1 = 2.291956939 * tb + 0.01942450693 * sqr( tb ) * hl -
					4.67538973161837 * pwr * tb - 1.668433124 * tb *
					pow( pwr, pwr );
			}
		}
		else
		{
			if( tb >= 0.25 )
			{
				hl = ( 1.50258368698213 + 158.556968859477 * asinh( pwr ) *
					tanh( 57.9466246871383 * tanh( pwr )) -
					0.0105440479814834 * atten ) / tb;

				fo1 = 0.994024401639321 * tb + ( -0.236282717577215 -
					6.8724924545387 * sqrt( sin( pwr ))) / hl;
			}
			else
			if( tb >= 0.10 )
			{
				hl = ( 1.50277377248945 + 158.222625721046 * asinh( pwr ) *
					tanh( 1.02875299001715 + 42.072277322604 * pwr ) -
					0.0108380943845632 * atten ) / tb;

				fo1 = 0.992539376734551 * tb + ( -0.251747813037178 -
					6.74159892452584 * sqrt( tanh( tanh( tan( pwr ))))) / hl;
			}
			else
			{
				hl = ( 1.15990238966306 * pwr - 5.02124037125213 * sqr(
					pwr ) - 0.158676856669827 * atten * cos( 1.1609073390614 *
					pwr - 6.33932586197475 * pwr * sqr( pwr ))) / tb;

				fo1 = 0.867344453126885 * tb + 0.052693817907757 * tb * log(
					pwr ) + 0.0895511178735932 * tb * atan( 59.7538527741309 *
					pwr ) - 0.0745653568081453 * pwr * tb;
			}
		}

		double WinParams[ 2 ];
		WinParams[ 0 ] = 125.0;
		WinParams[ 1 ] = pwr;

		CDSPSincFilterGen sinc;
		sinc.Len2 = 0.25 * hl / ReqNormFreq;
		sinc.Freq1 = 0.0;
		sinc.Freq2 = R8B_PI * ( 1.0 - fo1 ) * ReqNormFreq;
		sinc.initBand( CDSPSincFilterGen :: wftKaiser, WinParams, true );

		KernelLen = sinc.KernelLen;
		BlockLenBits = getBitOccupancy( KernelLen - 1 ) + R8B_EXTFFT;
		const int BlockLen = 1 << BlockLenBits;

		KernelBlock.alloc( BlockLen * 2 );
		sinc.generateBand( &KernelBlock[ 0 ],
			&CDSPSincFilterGen :: calcWindowKaiser );

		if( ReqPhase == fprLinearPhase )
		{
			IsZeroPhase = true;
			Latency = sinc.fl2;
			LatencyFrac = 0.0;
		}
		else
		{
			IsZeroPhase = false;
			double DCGroupDelay;

			calcMinPhaseTransform( &KernelBlock[ 0 ], KernelLen, 16, false,
				&DCGroupDelay );

			Latency = (int) DCGroupDelay;
			LatencyFrac = DCGroupDelay - Latency;
		}

		CDSPRealFFTKeeper ffto( BlockLenBits + 1 );

		if( IsZeroPhase )
		{
			// Calculate DC gain.

			double s = 0.0;
			int i;

			for( i = 0; i < KernelLen; i++ )
			{
				s += KernelBlock[ i ];
			}

			s = ffto -> getInvMulConst() * ReqGain / s;

			// Time-shift the filter so that zero-phase response is produced.
			// Simultaneously multiply by "s".

			for( i = 0; i <= sinc.fl2; i++ )
			{
				KernelBlock[ i ] = KernelBlock[ sinc.fl2 + i ] * s;
			}

			for( i = 1; i <= sinc.fl2; i++ )
			{
				KernelBlock[ BlockLen * 2 - i ] = KernelBlock[ i ];
			}

			memset( &KernelBlock[ sinc.fl2 + 1 ], 0,
				(size_t) ( BlockLen * 2 - KernelLen ) *
				sizeof( KernelBlock[ 0 ]));

			ffto -> forward( KernelBlock );
			ffto -> convertToZP( KernelBlock );
		}
		else
		{
			normalizeFIRFilter( &KernelBlock[ 0 ], KernelLen,
				ffto -> getInvMulConst() * ReqGain );

			memset( &KernelBlock[ KernelLen ], 0,
				(size_t) ( BlockLen * 2 - KernelLen ) *
				sizeof( KernelBlock[ 0 ]));

			ffto -> forward( KernelBlock );
		}

		R8BCONSOLE( "CDSPFIRFilter: flt_len=%i latency=%i nfreq=%.4f "
			"tb=%.1f att=%.1f gain=%.3f\n", KernelLen, Latency,
			ReqNormFreq, ReqTransBand, ReqAtten, ReqGain );
	}
};

/**
 * @brief FIR filter cache class.
 *
 * Class that implements cache for calculated FIR filters. The required FIR
 * filter should be obtained via the getLPFilter() static function.
 */

class CDSPFIRFilterCache : public R8B_BASECLASS
{
	R8BNOCTOR( CDSPFIRFilterCache )

	friend class CDSPFIRFilter;

public:
	/**
	 * @brief Returns the number of filters present in the cache now. This
	 * value can be monitored for debugging "forgotten" filters.
	 */

	static int getObjCount()
	{
		R8BSYNC( getStateSync() );

		return( getObjCountStatic() );
	}

	/**
	 * @brief Calculates or returns reference to a previously calculated
	 * (cached) low-pass FIR filter.
	 *
	 * Note that the real transition band and attenuation achieved by the
	 * filter varies with the magnitude of the required attenuation, and are
	 * never 100% exact.
	 *
	 * @param ReqNormFreq Required normalized frequency, in the range 0 to 1,
	 * inclusive. This is the point after which the stop-band spans.
	 * @param ReqTransBand Required transition band, in percent of the
	 * 0 to `ReqNormFreq` spectral bandwidth, in the range
	 * CDSPFIRFilter::getLPMinTransBand() to
	 * CDSPFIRFilter::getLPMaxTransBand(), inclusive. The transition band
	 * specifies the part of the spectrum between the -3 dB and `ReqNormFreq`
	 * points. The real resulting -3 dB point varies in the range from -3.00
	 * to -3.05 dB, but is generally very close to -3 dB.
	 * @param ReqAtten Required stop-band attenuation in decibel, in the range
	 * CDSPFIRFilter::getLPMinAtten() to CDSPFIRFilter::getLPMaxAtten(),
	 * inclusive. Note that the actual stop-band attenuation of the resulting
	 * filter may be 0.40-4.46 dB higher.
	 * @param ReqPhase Required filter's phase response.
	 * @param ReqGain Required overall filter's gain (1.0 for unity gain).
	 * @param AttenCorrs Attentuation correction table, to pass to the filter
	 * generation function. For internal use.
	 * @see EDSPFilterPhaseResponse
	 * @return A reference to a new or a previously calculated low-pass FIR
	 * filter object with the required characteristics. A reference count is
	 * incremented in the returned filter object which should be released
	 * after use via the CDSPFIRFilter::unref() function.
	 */

	static CDSPFIRFilter& getLPFilter( const double ReqNormFreq,
		const double ReqTransBand, const double ReqAtten,
		const EDSPFilterPhaseResponse ReqPhase, const double ReqGain,
		const double* const AttenCorrs = R8B_NULL )
	{
		R8BASSERT( ReqNormFreq > 0.0 && ReqNormFreq <= 1.0 );
		R8BASSERT( ReqTransBand >= CDSPFIRFilter :: getLPMinTransBand() );
		R8BASSERT( ReqTransBand <= CDSPFIRFilter :: getLPMaxTransBand() );
		R8BASSERT( ReqAtten >= CDSPFIRFilter :: getLPMinAtten() );
		R8BASSERT( ReqAtten <= CDSPFIRFilter :: getLPMaxAtten() );
		R8BASSERT( ReqGain > 0.0 );

		R8B_EXITDTOR static CPtrKeeper< CDSPFIRFilter > Objects; // The chain
			// of cached objects.

		R8BSYNC( getStateSync() );

		int& ObjCount = getObjCountStatic();
		CDSPFIRFilter* PrevObj = R8B_NULL;
		CDSPFIRFilter* CurObj = Objects;

		while( CurObj != R8B_NULL )
		{
			if( CurObj -> ReqNormFreq == ReqNormFreq &&
				CurObj -> ReqTransBand == ReqTransBand &&
				CurObj -> ReqGain == ReqGain &&
				CurObj -> ReqAtten == ReqAtten &&
				CurObj -> ReqPhase == ReqPhase )
			{
				break;
			}

			if( CurObj -> Next == R8B_NULL &&
				ObjCount >= R8B_FILTER_CACHE_MAX )
			{
				if( CurObj -> RefCount == 0 )
				{
					// Delete the last filter which is not used.

					PrevObj -> Next = R8B_NULL;
					delete CurObj;
					ObjCount--;
				}
				else
				{
					// Move the last filter to the top of the list since it
					// seems to be in use for a long time.

					PrevObj -> Next = R8B_NULL;
					CurObj -> Next = Objects.unkeep();
					Objects = CurObj;
				}

				CurObj = R8B_NULL;
				break;
			}

			PrevObj = CurObj;
			CurObj = CurObj -> Next;
		}

		if( CurObj != R8B_NULL )
		{
			CurObj -> RefCount++;

			if( PrevObj == R8B_NULL )
			{
				return( *CurObj );
			}

			// Remove the filter from the list temporarily.

			PrevObj -> Next = CurObj -> Next;
		}
		else
		{
			// Create a new filter object (with RefCount == 1) and build the
			// filter kernel.

			CurObj = new CDSPFIRFilter();
			CurObj -> ReqNormFreq = ReqNormFreq;
			CurObj -> ReqTransBand = ReqTransBand;
			CurObj -> ReqAtten = ReqAtten;
			CurObj -> ReqPhase = ReqPhase;
			CurObj -> ReqGain = ReqGain;
			ObjCount++;

			CurObj -> buildLPFilter( AttenCorrs );
		}

		// Insert the filter at the start of the list.

		CurObj -> Next = Objects.unkeep();
		Objects = CurObj;

		return( *CurObj );
	}

protected:
	/**
	 * @brief Returns reference to filter cache sync object.
	 */

	static CSyncObject& getStateSync()
	{
		R8B_EXITDTOR static CSyncObject StateSync;

		return( StateSync );
	}

	/**
	 * @brief Returns reference to variable containing cache object count.
	 */

	static int& getObjCountStatic()
	{
		R8B_EXITDTOR static int ObjCount = 0; // The number of objects
			// currently preset in the cache.

		return( ObjCount );
	}
};

// ---------------------------------------------------------------------------
// CDSPFIRFilter PUBLIC
// ---------------------------------------------------------------------------

inline void CDSPFIRFilter :: unref()
{
	R8BSYNC( CDSPFIRFilterCache :: getStateSync() );

	RefCount--;
}

// ---------------------------------------------------------------------------

} // namespace r8b

#endif // R8B_CDSPFIRFILTER_INCLUDED
