Archived
1
0
This repository has been archived on 2024-10-17. You can view files and clone it, but cannot push or open issues or pull requests.
winamp/Src/external_dependencies/openmpt-trunk/include/r8brain/CDSPResampler.h
2024-09-24 14:54:57 +02:00

770 lines
21 KiB
C++
Vendored

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