123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820 |
- //$ nobt
- //$ nocpp
- /**
- * @file CDSPRealFFT.h
- *
- * @brief Real-valued FFT transform class.
- *
- * This file includes FFT object implementation. All created FFT objects are
- * kept in a global list after use, for a future reusal. Such approach
- * minimizes time necessary to initialize the FFT object of the required
- * length.
- *
- * r8brain-free-src Copyright (c) 2013-2022 Aleksey Vaneev
- * See the "LICENSE" file for license.
- */
- #ifndef R8B_CDSPREALFFT_INCLUDED
- #define R8B_CDSPREALFFT_INCLUDED
- #include "r8bbase.h"
- #if !R8B_IPP && !R8B_PFFFT && !R8B_PFFFT_DOUBLE
- #include "fft4g.h"
- #endif // !R8B_IPP && !R8B_PFFFT && !R8B_PFFFT_DOUBLE
- #if R8B_PFFFT
- #include "pffft.h"
- #endif // R8B_PFFFT
- #if R8B_PFFFT_DOUBLE
- #include "pffft_double/pffft_double.h"
- #endif // R8B_PFFFT_DOUBLE
- namespace r8b {
- /**
- * @brief Real-valued FFT transform class.
- *
- * Class implements a wrapper for real-valued discrete fast Fourier transform
- * functions. The object of this class can only be obtained via the
- * CDSPRealFFTKeeper class.
- *
- * Uses functions from the FFT package by: Copyright(C) 1996-2001 Takuya OOURA
- * http://www.kurims.kyoto-u.ac.jp/~ooura/fft.html
- *
- * Also uses Intel IPP library functions if available (if the R8B_IPP=1 macro
- * was defined). Note that IPP library's FFT functions are 2-3 times more
- * efficient on the modern Intel Core i7-3770K processor than Ooura's
- * functions. It may be worthwhile investing in IPP. Note, that FFT functions
- * take less than 20% of the overall sample rate conversion time. However,
- * when the "power of 2" resampling is used the performance of FFT functions
- * becomes "everything".
- */
- class CDSPRealFFT : public R8B_BASECLASS
- {
- R8BNOCTOR( CDSPRealFFT );
- friend class CDSPRealFFTKeeper;
- public:
- /**
- * @return A multiplication constant that should be used after inverse
- * transform to obtain a correct value scale.
- */
- double getInvMulConst() const
- {
- return( InvMulConst );
- }
- /**
- * @return The length (the number of real values in a transform) of *this
- * FFT object, expressed as Nth power of 2.
- */
- int getLenBits() const
- {
- return( LenBits );
- }
- /**
- * @return The length (the number of real values in a transform) of *this
- * FFT object.
- */
- int getLen() const
- {
- return( Len );
- }
- /**
- * Function performs in-place forward FFT.
- *
- * @param[in,out] p Pointer to data block to transform, length should be
- * equal to *this object's getLen().
- */
- void forward( double* const p ) const
- {
- #if R8B_FLOATFFT
- float* const op = (float*) p;
- int i;
- for( i = 0; i < Len; i++ )
- {
- op[ i ] = (float) p[ i ];
- }
- #endif // R8B_FLOATFFT
- #if R8B_IPP
- ippsFFTFwd_RToPerm_64f( p, p, SPtr, WorkBuffer );
- #elif R8B_PFFFT
- pffft_transform_ordered( setup, op, op, work, PFFFT_FORWARD );
- #elif R8B_PFFFT_DOUBLE
- pffftd_transform_ordered( setup, p, p, work, PFFFT_FORWARD );
- #else // R8B_PFFFT_DOUBLE
- ooura_fft :: rdft( Len, 1, p, wi.getPtr(), wd.getPtr() );
- #endif // R8B_IPP
- }
- /**
- * Function performs in-place inverse FFT.
- *
- * @param[in,out] p Pointer to data block to transform, length should be
- * equal to *this object's getLen().
- */
- void inverse( double* const p ) const
- {
- #if R8B_IPP
- ippsFFTInv_PermToR_64f( p, p, SPtr, WorkBuffer );
- #elif R8B_PFFFT
- pffft_transform_ordered( setup, (float*) p, (float*) p, work,
- PFFFT_BACKWARD );
- #elif R8B_PFFFT_DOUBLE
- pffftd_transform_ordered( setup, p, p, work, PFFFT_BACKWARD );
- #else // R8B_PFFFT_DOUBLE
- ooura_fft :: rdft( Len, -1, p, wi.getPtr(), wd.getPtr() );
- #endif // R8B_IPP
- #if R8B_FLOATFFT
- const float* const ip = (const float*) p;
- int i;
- for( i = Len - 1; i >= 0; i-- )
- {
- p[ i ] = ip[ i ];
- }
- #endif // R8B_FLOATFFT
- }
- /**
- * Function multiplies two complex-valued data blocks and places result in
- * a new data block. Length of all data blocks should be equal to *this
- * object's block length. Input blocks should have been produced with the
- * forward() function of *this object.
- *
- * @param aip1 Input data block 1.
- * @param aip2 Input data block 2.
- * @param[out] aop Output data block, should not be equal to aip1 nor
- * aip2.
- */
- void multiplyBlocks( const double* const aip1, const double* const aip2,
- double* const aop ) const
- {
- #if R8B_FLOATFFT
- const float* const ip1 = (const float*) aip1;
- const float* const ip2 = (const float*) aip2;
- float* const op = (float*) aop;
- #else // R8B_FLOATFFT
- const double* const ip1 = aip1;
- const double* const ip2 = aip2;
- double* const op = aop;
- #endif // R8B_FLOATFFT
- #if R8B_IPP
- ippsMulPerm_64f( (Ipp64f*) ip1, (Ipp64f*) ip2, (Ipp64f*) op, Len );
- #else // R8B_IPP
- op[ 0 ] = ip1[ 0 ] * ip2[ 0 ];
- op[ 1 ] = ip1[ 1 ] * ip2[ 1 ];
- int i = 2;
- while( i < Len )
- {
- op[ i ] = ip1[ i ] * ip2[ i ] - ip1[ i + 1 ] * ip2[ i + 1 ];
- op[ i + 1 ] = ip1[ i ] * ip2[ i + 1 ] + ip1[ i + 1 ] * ip2[ i ];
- i += 2;
- }
- #endif // R8B_IPP
- }
- /**
- * Function multiplies two complex-valued data blocks in-place. Length of
- * both data blocks should be equal to *this object's block length. Blocks
- * should have been produced with the forward() function of *this object.
- *
- * @param aip Input data block 1.
- * @param[in,out] aop Output/input data block 2.
- */
- void multiplyBlocks( const double* const aip, double* const aop ) const
- {
- #if R8B_FLOATFFT
- const float* const ip = (const float*) aip;
- float* const op = (float*) aop;
- float t;
- #else // R8B_FLOATFFT
- const double* const ip = aip;
- double* const op = aop;
- #if !R8B_IPP
- double t;
- #endif // !R8B_IPP
- #endif // R8B_FLOATFFT
- #if R8B_IPP
- ippsMulPerm_64f( (Ipp64f*) op, (Ipp64f*) ip, (Ipp64f*) op, Len );
- #else // R8B_IPP
- op[ 0 ] *= ip[ 0 ];
- op[ 1 ] *= ip[ 1 ];
- int i = 2;
- while( i < Len )
- {
- t = op[ i ] * ip[ i ] - op[ i + 1 ] * ip[ i + 1 ];
- op[ i + 1 ] = op[ i ] * ip[ i + 1 ] + op[ i + 1 ] * ip[ i ];
- op[ i ] = t;
- i += 2;
- }
- #endif // R8B_IPP
- }
- /**
- * Function multiplies two complex-valued data blocks in-place,
- * considering that the "ip" block contains "zero-phase" response. Length
- * of both data blocks should be equal to *this object's block length.
- * Blocks should have been produced with the forward() function of *this
- * object.
- *
- * @param aip Input data block 1, "zero-phase" response. This block should
- * be first transformed via the convertToZP() function.
- * @param[in,out] aop Output/input data block 2.
- */
- void multiplyBlocksZP( const double* const aip, double* const aop ) const
- {
- #if R8B_FLOATFFT
- const float* const ip = (const float*) aip;
- float* const op = (float*) aop;
- #else // R8B_FLOATFFT
- const double* ip = aip;
- double* op = aop;
- #endif // R8B_FLOATFFT
- // SIMD implementations assume that pointers are address-aligned.
- #if !R8B_FLOATFFT && defined( R8B_SSE2 )
- int c8 = Len >> 3;
- while( c8 != 0 )
- {
- const __m128d iv1 = _mm_load_pd( ip );
- const __m128d iv2 = _mm_load_pd( ip + 2 );
- const __m128d ov1 = _mm_load_pd( op );
- const __m128d ov2 = _mm_load_pd( op + 2 );
- _mm_store_pd( op, _mm_mul_pd( iv1, ov1 ));
- _mm_store_pd( op + 2, _mm_mul_pd( iv2, ov2 ));
- const __m128d iv3 = _mm_load_pd( ip + 4 );
- const __m128d ov3 = _mm_load_pd( op + 4 );
- const __m128d iv4 = _mm_load_pd( ip + 6 );
- const __m128d ov4 = _mm_load_pd( op + 6 );
- _mm_store_pd( op + 4, _mm_mul_pd( iv3, ov3 ));
- _mm_store_pd( op + 6, _mm_mul_pd( iv4, ov4 ));
- ip += 8;
- op += 8;
- c8--;
- }
- int c = Len & 7;
- while( c != 0 )
- {
- *op *= *ip;
- ip++;
- op++;
- c--;
- }
- #elif !R8B_FLOATFFT && defined( R8B_NEON )
- int c8 = Len >> 3;
- while( c8 != 0 )
- {
- const float64x2_t iv1 = vld1q_f64( ip );
- const float64x2_t iv2 = vld1q_f64( ip + 2 );
- const float64x2_t ov1 = vld1q_f64( op );
- const float64x2_t ov2 = vld1q_f64( op + 2 );
- vst1q_f64( op, vmulq_f64( iv1, ov1 ));
- vst1q_f64( op + 2, vmulq_f64( iv2, ov2 ));
- const float64x2_t iv3 = vld1q_f64( ip + 4 );
- const float64x2_t iv4 = vld1q_f64( ip + 6 );
- const float64x2_t ov3 = vld1q_f64( op + 4 );
- const float64x2_t ov4 = vld1q_f64( op + 6 );
- vst1q_f64( op + 4, vmulq_f64( iv3, ov3 ));
- vst1q_f64( op + 6, vmulq_f64( iv4, ov4 ));
- ip += 8;
- op += 8;
- c8--;
- }
- int c = Len & 7;
- while( c != 0 )
- {
- *op *= *ip;
- ip++;
- op++;
- c--;
- }
- #else // SIMD
- int i;
- for( i = 0; i < Len; i++ )
- {
- op[ i ] *= ip[ i ];
- }
- #endif // SIMD
- }
- /**
- * Function converts the specified forward-transformed block into
- * "zero-phase" form suitable for use with the multiplyBlocksZ() function.
- *
- * @param[in,out] ap Block to transform.
- */
- void convertToZP( double* const ap ) const
- {
- #if R8B_FLOATFFT
- float* const p = (float*) ap;
- #else // R8B_FLOATFFT
- double* const p = ap;
- #endif // R8B_FLOATFFT
- int i = 2;
- while( i < Len )
- {
- p[ i + 1 ] = p[ i ];
- i += 2;
- }
- }
- private:
- int LenBits; ///< Length of FFT block (expressed as Nth power of 2).
- ///<
- int Len; ///< Length of FFT block (number of real values).
- ///<
- double InvMulConst; ///< Inverse FFT multiply constant.
- ///<
- CDSPRealFFT* Next; ///< Next object in a singly-linked list.
- ///<
- #if R8B_IPP
- IppsFFTSpec_R_64f* SPtr; ///< Pointer to initialized data buffer
- ///< to be passed to IPP's FFT functions.
- ///<
- CFixedBuffer< unsigned char > SpecBuffer; ///< Working buffer.
- ///<
- CFixedBuffer< unsigned char > WorkBuffer; ///< Working buffer.
- ///<
- #elif R8B_PFFFT
- PFFFT_Setup* setup; ///< PFFFT setup object.
- ///<
- CFixedBuffer< float > work; ///< Working buffer.
- ///<
- #elif R8B_PFFFT_DOUBLE
- PFFFTD_Setup* setup; ///< PFFFTD setup object.
- ///<
- CFixedBuffer< double > work; ///< Working buffer.
- ///<
- #else // R8B_PFFFT_DOUBLE
- CFixedBuffer< int > wi; ///< Working buffer (ints).
- ///<
- CFixedBuffer< double > wd; ///< Working buffer (doubles).
- ///<
- #endif // R8B_IPP
- /**
- * A simple class that keeps the pointer to the object and deletes it
- * automatically.
- */
- class CObjKeeper
- {
- R8BNOCTOR( CObjKeeper );
- public:
- CObjKeeper()
- : Object( NULL )
- {
- }
- ~CObjKeeper()
- {
- delete Object;
- }
- CObjKeeper& operator = ( CDSPRealFFT* const aObject )
- {
- Object = aObject;
- return( *this );
- }
- operator CDSPRealFFT* () const
- {
- return( Object );
- }
- private:
- CDSPRealFFT* Object; ///< FFT object being kept.
- ///<
- };
- CDSPRealFFT()
- {
- }
- /**
- * Constructor initializes FFT object.
- *
- * @param aLenBits The length of FFT block (Nth power of 2), specifies the
- * number of real values in a block. Values from 1 to 30 inclusive are
- * supported.
- */
- CDSPRealFFT( const int aLenBits )
- : LenBits( aLenBits )
- , Len( 1 << aLenBits )
- #if R8B_IPP
- , InvMulConst( 1.0 / Len )
- #elif R8B_PFFFT
- , InvMulConst( 1.0 / Len )
- #elif R8B_PFFFT_DOUBLE
- , InvMulConst( 1.0 / Len )
- #else // R8B_PFFFT_DOUBLE
- , InvMulConst( 2.0 / Len )
- #endif // R8B_IPP
- {
- #if R8B_IPP
- int SpecSize;
- int SpecBufferSize;
- int BufferSize;
- ippsFFTGetSize_R_64f( LenBits, IPP_FFT_NODIV_BY_ANY,
- ippAlgHintFast, &SpecSize, &SpecBufferSize, &BufferSize );
- CFixedBuffer< unsigned char > InitBuffer( SpecBufferSize );
- SpecBuffer.alloc( SpecSize );
- WorkBuffer.alloc( BufferSize );
- ippsFFTInit_R_64f( &SPtr, LenBits, IPP_FFT_NODIV_BY_ANY,
- ippAlgHintFast, SpecBuffer, InitBuffer );
- #elif R8B_PFFFT
- setup = pffft_new_setup( Len, PFFFT_REAL );
- work.alloc( Len );
- #elif R8B_PFFFT_DOUBLE
- setup = pffftd_new_setup( Len, PFFFT_REAL );
- work.alloc( Len );
- #else // R8B_PFFFT_DOUBLE
- wi.alloc( (int) ceil( 2.0 + sqrt( (double) ( Len >> 1 ))));
- wi[ 0 ] = 0;
- wd.alloc( Len >> 1 );
- #endif // R8B_IPP
- }
- ~CDSPRealFFT()
- {
- #if R8B_PFFFT
- pffft_destroy_setup( setup );
- #elif R8B_PFFFT_DOUBLE
- pffftd_destroy_setup( setup );
- #endif // R8B_PFFFT_DOUBLE
- delete Next;
- }
- };
- /**
- * @brief A "keeper" class for real-valued FFT transform objects.
- *
- * Class implements "keeper" functionality for handling CDSPRealFFT objects.
- * The allocated FFT objects are placed on the global static list of objects
- * for future reuse instead of deallocation.
- */
- class CDSPRealFFTKeeper : public R8B_BASECLASS
- {
- R8BNOCTOR( CDSPRealFFTKeeper );
- public:
- CDSPRealFFTKeeper()
- : Object( NULL )
- {
- }
- /**
- * Function acquires FFT object with the specified block length.
- *
- * @param LenBits The length of FFT block (Nth power of 2), in the range
- * [1; 30] inclusive, specifies the number of real values in a FFT block.
- */
- CDSPRealFFTKeeper( const int LenBits )
- {
- Object = acquire( LenBits );
- }
- ~CDSPRealFFTKeeper()
- {
- if( Object != NULL )
- {
- release( Object );
- }
- }
- /**
- * @return Pointer to the acquired FFT object.
- */
- const CDSPRealFFT* operator -> () const
- {
- R8BASSERT( Object != NULL );
- return( Object );
- }
- /**
- * Function acquires FFT object with the specified block length. This
- * function can be called any number of times.
- *
- * @param LenBits The length of FFT block (Nth power of 2), in the range
- * [1; 30] inclusive, specifies the number of real values in a FFT block.
- */
- void init( const int LenBits )
- {
- if( Object != NULL )
- {
- if( Object -> LenBits == LenBits )
- {
- return;
- }
- release( Object );
- }
- Object = acquire( LenBits );
- }
- /**
- * Function releases a previously acquired FFT object.
- */
- void reset()
- {
- if( Object != NULL )
- {
- release( Object );
- Object = NULL;
- }
- }
- private:
- CDSPRealFFT* Object; ///< FFT object.
- ///<
- static CSyncObject StateSync; ///< FFTObjects synchronizer.
- ///<
- static CDSPRealFFT :: CObjKeeper FFTObjects[]; ///< Pool of FFT objects of
- ///< various lengths.
- ///<
- /**
- * Function acquires FFT object from the global pool.
- *
- * @param LenBits FFT block length (expressed as Nth power of 2).
- */
- CDSPRealFFT* acquire( const int LenBits )
- {
- R8BASSERT( LenBits > 0 && LenBits <= 30 );
- R8BSYNC( StateSync );
- if( FFTObjects[ LenBits ] == NULL )
- {
- return( new CDSPRealFFT( LenBits ));
- }
- CDSPRealFFT* ffto = FFTObjects[ LenBits ];
- FFTObjects[ LenBits ] = ffto -> Next;
- return( ffto );
- }
- /**
- * Function releases a previously acquired FFT object.
- *
- * @param ffto FFT object to release.
- */
- void release( CDSPRealFFT* const ffto )
- {
- R8BSYNC( StateSync );
- ffto -> Next = FFTObjects[ ffto -> LenBits ];
- FFTObjects[ ffto -> LenBits ] = ffto;
- }
- };
- /**
- * Function calculates the minimum-phase transform of the filter kernel, using
- * a discrete Hilbert transform in cepstrum domain.
- *
- * For more details, see part III.B of
- * http://www.hpl.hp.com/personal/Niranjan_Damera-Venkata/files/ComplexMinPhase.pdf
- *
- * @param[in,out] Kernel Filter kernel buffer.
- * @param KernelLen Filter kernel's length, in samples.
- * @param LenMult Kernel length multiplier. Used as a coefficient of the
- * "oversampling" in the frequency domain. Such oversampling is needed to
- * improve the precision of the minimum-phase transform. If the filter's
- * attenuation is high, this multiplier should be increased or otherwise the
- * required attenuation will not be reached due to "smoothing" effect of this
- * transform.
- * @param DoFinalMul "True" if the final multiplication after transform should
- * be performed or not. Such multiplication returns the gain of the signal to
- * its original value. This parameter can be set to "false" if normalization
- * of the resulting filter kernel is planned to be used.
- * @param[out] DCGroupDelay If not NULL, this variable receives group delay
- * at DC offset, in samples (can be a non-integer value).
- */
- inline void calcMinPhaseTransform( double* const Kernel, const int KernelLen,
- const int LenMult = 2, const bool DoFinalMul = true,
- double* const DCGroupDelay = NULL )
- {
- R8BASSERT( KernelLen > 0 );
- R8BASSERT( LenMult >= 2 );
- const int LenBits = getBitOccupancy(( KernelLen * LenMult ) - 1 );
- const int Len = 1 << LenBits;
- const int Len2 = Len >> 1;
- int i;
- CFixedBuffer< double > ip( Len );
- CFixedBuffer< double > ip2( Len2 + 1 );
- memcpy( &ip[ 0 ], Kernel, KernelLen * sizeof( ip[ 0 ]));
- memset( &ip[ KernelLen ], 0, ( Len - KernelLen ) * sizeof( ip[ 0 ]));
- CDSPRealFFTKeeper ffto( LenBits );
- ffto -> forward( ip );
- // Create the "log |c|" spectrum while saving the original power spectrum
- // in the "ip2" buffer.
- #if R8B_FLOATFFT
- float* const aip = (float*) &ip[ 0 ];
- float* const aip2 = (float*) &ip2[ 0 ];
- const float nzbias = 1e-35;
- #else // R8B_FLOATFFT
- double* const aip = &ip[ 0 ];
- double* const aip2 = &ip2[ 0 ];
- const double nzbias = 1e-300;
- #endif // R8B_FLOATFFT
- aip2[ 0 ] = aip[ 0 ];
- aip[ 0 ] = log( fabs( aip[ 0 ]) + nzbias );
- aip2[ Len2 ] = aip[ 1 ];
- aip[ 1 ] = log( fabs( aip[ 1 ]) + nzbias );
- for( i = 1; i < Len2; i++ )
- {
- aip2[ i ] = sqrt( aip[ i * 2 ] * aip[ i * 2 ] +
- aip[ i * 2 + 1 ] * aip[ i * 2 + 1 ]);
- aip[ i * 2 ] = log( aip2[ i ] + nzbias );
- aip[ i * 2 + 1 ] = 0.0;
- }
- // Convert to cepstrum and apply discrete Hilbert transform.
- ffto -> inverse( ip );
- const double m1 = ffto -> getInvMulConst();
- const double m2 = -m1;
- ip[ 0 ] = 0.0;
- for( i = 1; i < Len2; i++ )
- {
- ip[ i ] *= m1;
- }
- ip[ Len2 ] = 0.0;
- for( i = Len2 + 1; i < Len; i++ )
- {
- ip[ i ] *= m2;
- }
- // Convert Hilbert-transformed cepstrum back to the "log |c|" spectrum and
- // perform its exponentiation, multiplied by the power spectrum previously
- // saved in the "ip2" buffer.
- ffto -> forward( ip );
- aip[ 0 ] = aip2[ 0 ];
- aip[ 1 ] = aip2[ Len2 ];
- for( i = 1; i < Len2; i++ )
- {
- aip[ i * 2 + 0 ] = cos( aip[ i * 2 + 1 ]) * aip2[ i ];
- aip[ i * 2 + 1 ] = sin( aip[ i * 2 + 1 ]) * aip2[ i ];
- }
- ffto -> inverse( ip );
- if( DoFinalMul )
- {
- for( i = 0; i < KernelLen; i++ )
- {
- Kernel[ i ] = ip[ i ] * m1;
- }
- }
- else
- {
- memcpy( &Kernel[ 0 ], &ip[ 0 ], KernelLen * sizeof( Kernel[ 0 ]));
- }
- if( DCGroupDelay != NULL )
- {
- double tmp;
- calcFIRFilterResponseAndGroupDelay( Kernel, KernelLen, 0.0,
- tmp, tmp, *DCGroupDelay );
- }
- }
- } // namespace r8b
- #endif // VOX_CDSPREALFFT_INCLUDED
|