This commit is contained in:
2021-10-14 13:47:35 +02:00
commit 6625a8dfaa
4026 changed files with 844291 additions and 0 deletions
@@ -0,0 +1,600 @@
//$ nocpp
/**
* @file CDSPBlockConvolver.h
*
* @brief Single-block overlap-save convolution processor class.
*
* This file includes single-block overlap-save convolution processor class.
*
* r8brain-free-src Copyright (c) 2013-2014 Aleksey Vaneev
* See the "License.txt" file for license.
*/
#ifndef R8B_CDSPBLOCKCONVOLVER_INCLUDED
#define R8B_CDSPBLOCKCONVOLVER_INCLUDED
#include "CDSPFIRFilter.h"
#include "CDSPProcessor.h"
namespace r8b
{
/**
* @brief Single-block overlap-save convolution processing class.
*
* Class that implements single-block overlap-save convolution processing. The
* length of a single FFT block used depends on the length of the filter
* kernel.
*
* The rationale behind "single-block" processing is that increasing the FFT
* block length by 2 is more efficient than performing convolution at the same
* FFT block length but using two blocks.
*
* This class also implements a built-in resampling by any whole-number
* factor, which simplifies the overall resampling objects topology.
*/
class CDSPBlockConvolver final : public CDSPProcessor
{
public:
/**
* Constructor initializes internal variables and constants of *this
* object.
*
* @param aFilter Pre-calculated filter data. Reference to this object is
* inhertied by *this object, and the object will be released when *this
* object is destroyed. If upsampling is used, filter's gain should be
* equal to the upsampling factor.
* @param aUpFactor The upsampling factor, positive value. E.g. value of 2
* means 2x upsampling should be performed over the input data.
* @param aDownFactor The downsampling factor, positive value. E.g. value
* of 2 means 2x downsampling should be performed over the output data.
* @param PrevLatency Latency, in samples (any value >=0), which was left
* in the output signal by a previous process. This value is usually
* non-zero if the minimum-phase filters are in use. This value is always
* zero if the linear-phase filters are in use.
* @param aDoConsumeLatency "True" if the output latency should be
* consumed. Does not apply to the fractional part of the latency (if such
* part is available).
*/
CDSPBlockConvolver(CDSPFIRFilter& aFilter, const int aUpFactor,
const int aDownFactor, const double PrevLatency = 0.0,
const bool aDoConsumeLatency = true)
: Filter(&aFilter)
, UpFactor(aUpFactor)
, DownFactor(aDownFactor)
, DoConsumeLatency(aDoConsumeLatency)
, BlockLen2(2 << Filter->getBlockLenBits())
{
R8BASSERT(UpFactor > 0);
R8BASSERT(DownFactor > 0);
R8BASSERT(PrevLatency >= 0.0);
int fftinBits;
UpShift = getBitOccupancy(UpFactor) - 1;
if ((1 << UpShift) == UpFactor)
{
fftinBits = Filter->getBlockLenBits() + 1 - UpShift;
PrevInputLen = (Filter->getKernelLen() - 1) / UpFactor;
InputLen = BlockLen2 - PrevInputLen * UpFactor;
}
else
{
UpShift = -1;
fftinBits = Filter->getBlockLenBits() + 1;
PrevInputLen = Filter->getKernelLen() - 1;
InputLen = BlockLen2 - PrevInputLen;
}
OutOffset = Filter->getLatency();
LatencyFrac = Filter->getLatencyFrac() + PrevLatency * UpFactor;
Latency = (int)LatencyFrac;
LatencyFrac -= Latency;
LatencyFrac /= DownFactor;
Latency += InputLen + OutOffset;
int fftoutBits;
InputDelay = 0;
UpSkipInit = 0;
DownSkipInit = 0;
DownShift = getBitOccupancy(DownFactor) - 1;
if ((1 << DownShift) == DownFactor)
{
fftoutBits = Filter->getBlockLenBits() + 1 - DownShift;
if (DownFactor > 1)
{
if (UpShift > 0)
{
// This case never happens in practice due to mutual
// exclusion of "power of 2" DownFactor and UpFactor
// values.
R8BASSERT(UpShift == 0);
}
else
{
int Delay = Latency & (DownFactor - 1);
if (Delay > 0)
{
Delay = DownFactor - Delay;
Latency += Delay;
if (Delay < UpFactor) { UpSkipInit = Delay; }
else
{
UpSkipInit = UpFactor - 1;
InputDelay = Delay - UpSkipInit;
}
}
if (!DoConsumeLatency) { Latency /= DownFactor; }
}
}
}
else
{
fftoutBits = Filter->getBlockLenBits() + 1;
DownShift = -1;
if (!DoConsumeLatency && DownFactor > 1)
{
DownSkipInit = Latency % DownFactor;
Latency /= DownFactor;
}
}
fftin = new CDSPRealFFTKeeper(fftinBits);
if (fftoutBits == fftinBits) { fftout = fftin; }
else
{
ffto2 = new CDSPRealFFTKeeper(fftoutBits);
fftout = ffto2;
}
WorkBlocks.alloc(BlockLen2 * 2 + PrevInputLen);
CurInput = &WorkBlocks[0];
CurOutput = &WorkBlocks[BlockLen2];
PrevInput = &WorkBlocks[BlockLen2 * 2];
clear();
R8BCONSOLE("CDSPBlockConvolver: flt_len=%i in_len=%i io=%i/%i "
"fft=%i/%i latency=%i\n", Filter -> getKernelLen(), InputLen,
UpFactor, DownFactor, (*fftin) -> getLen(), (*fftout) -> getLen(),
getLatency());
}
~CDSPBlockConvolver() override { Filter->unref(); }
int getLatency() const override { return (DoConsumeLatency ? 0 : Latency); }
double getLatencyFrac() const override { return (LatencyFrac); }
int getInLenBeforeOutStart(const int NextInLen) const override
{
return ((InputLen - InputDelay + NextInLen * DownFactor) /
UpFactor);
}
int getMaxOutLen(const int MaxInLen) const override
{
R8BASSERT(MaxInLen >= 0);
return ((MaxInLen * UpFactor + InputDelay + DownFactor - 1) /
DownFactor);
}
void clear() override
{
memset(&PrevInput[0], 0, PrevInputLen * sizeof(double));
if (DoConsumeLatency) { LatencyLeft = Latency; }
else
{
LatencyLeft = 0;
if (DownShift > 0)
{
memset(&CurOutput[0], 0, (BlockLen2 >> DownShift) *
sizeof(double));
}
else
{
memset(&CurOutput[BlockLen2 - OutOffset], 0, OutOffset *
sizeof(double));
memset(&CurOutput[0], 0, (InputLen - OutOffset) *
sizeof(double));
}
}
memset(CurInput, 0, InputDelay * sizeof(double));
InDataLeft = InputLen - InputDelay;
UpSkip = UpSkipInit;
DownSkip = DownSkipInit;
}
int process(double* ip, int l0, double*& op0) override
{
R8BASSERT(l0 >= 0);
R8BASSERT(UpFactor / DownFactor <= 1 || ip != op0 || l0 == 0);
double* op = op0;
int l = l0 * UpFactor;
l0 = 0;
while (l > 0)
{
const int Offs = InputLen - InDataLeft;
if (l < InDataLeft)
{
InDataLeft -= l;
if (UpShift >= 0)
{
memcpy(&CurInput[Offs >> UpShift], ip,
(l >> UpShift) * sizeof(double));
}
else { copyUpsample(ip, &CurInput[Offs], l); }
copyToOutput(Offs - OutOffset, op, l, l0);
break;
}
const int b = InDataLeft;
l -= b;
InDataLeft = InputLen;
int ilu;
if (UpShift >= 0)
{
const int bu = b >> UpShift;
memcpy(&CurInput[Offs >> UpShift], ip,
bu * sizeof(double));
ip += bu;
ilu = InputLen >> UpShift;
}
else
{
copyUpsample(ip, &CurInput[Offs], b);
ilu = InputLen;
}
const int pil = int(PrevInputLen * sizeof(double));
memcpy(&CurInput[ilu], PrevInput, pil);
memcpy(PrevInput, &CurInput[ilu - PrevInputLen], pil);
(*fftin)->forward(CurInput);
if (UpShift > 0) { mirrorInputSpectrum(); }
if (Filter->isZeroPhase())
{
(*fftout)->multiplyBlocksZ(Filter->getKernelBlock(),
CurInput);
}
else
{
(*fftout)->multiplyBlocks(Filter->getKernelBlock(),
CurInput);
}
if (DownShift > 0)
{
const int z = BlockLen2 >> DownShift;
CurInput[1] = Filter->getKernelBlock()[z] *
CurInput[z];
}
(*fftout)->inverse(CurInput);
copyToOutput(Offs - OutOffset, op, b, l0);
double* const tmp = CurInput;
CurInput = CurOutput;
CurOutput = tmp;
}
return (l0);
}
private:
CDSPFIRFilter* Filter = nullptr; ///< Filter in use.
///<
CPtrKeeper<CDSPRealFFTKeeper*> fftin; ///< FFT object 1, used to produce
///< the input spectrum (can embed the "power of 2" upsampling).
///<
CPtrKeeper<CDSPRealFFTKeeper*> ffto2; ///< FFT object 2 (can be NULL).
///<
CDSPRealFFTKeeper* fftout = nullptr; ///< FFT object used to produce the output
///< signal (can embed the "power of 2" downsampling), may point to
///< either "fftin" or "ffto2".
///<
int UpFactor = 0; ///< Upsampling factor.
///<
int DownFactor = 0; ///< Downsampling factor.
///<
bool DoConsumeLatency; ///< "True" if the output latency should be
///< consumed. Does not apply to the fractional part of the latency
///< (if such part is available).
///<
int BlockLen2 = 0; ///< Equals block length * 2.
///<
int OutOffset = 0; ///< Output offset, depends on filter's introduced latency.
///<
int PrevInputLen = 0; ///< The length of previous input data saved, used for
///< overlap.
///<
int InputLen = 0; ///< The number of input samples that should be accumulated
///< before the input block is processed.
///<
int Latency = 0; ///< Processing latency, in samples.
///<
double LatencyFrac = 0; ///< Fractional latency, in samples, that is left in
///< the output signal.
///<
int UpShift = 0; ///< "Power of 2" upsampling shift. Equals -1 if UpFactor is
///< not a "power of 2" value. Equals 0 if UpFactor equals 1.
///<
int DownShift = 0; ///< "Power of 2" downsampling shift. Equals -1 if
///< DownFactor is not a "power of 2". Equals 0 if DownFactor equals
///< 1.
///<
int InputDelay = 0; ///< Additional input delay, in samples. Used to make the
///< output latency divisible by DownShift. Used only if UpShift <= 0
///< and DownShift > 0.
///<
CFixedBuffer<double> WorkBlocks; ///< Previous input data, input and
///< output data blocks, overall capacity = BlockLen2 * 2 +
///< PrevInputLen. Used in the flip-flop manner.
///<
double* PrevInput = nullptr; ///< Previous input data buffer, capacity = BlockLen.
///<
double* CurInput = nullptr; ///< Input data buffer, capacity = BlockLen2.
///<
double* CurOutput = nullptr; ///< Output data buffer, capacity = BlockLen2.
///<
int InDataLeft = 0; ///< Samples left before processing input and output FFT
///< blocks. Initialized to InputLen on clear.
///<
int LatencyLeft = 0; ///< Latency in samples left to skip.
///<
int UpSkip = 0; ///< The current upsampling sample skip (value in the range
///< 0 to UpFactor - 1).
///<
int UpSkipInit = 0; ///< The initial UpSkip value after clear().
///<
int DownSkip = 0; ///< The current downsampling sample skip (value in the
///< range 0 to DownFactor - 1). Not used if DownShift > 0.
///<
int DownSkipInit = 0; ///< The initial DownSkip value after clear().
///<
/**
* Function copies samples from the input buffer to the output buffer
* while inserting zeros inbetween them to perform the whole-numbered
* upsampling.
*
* @param[in,out] ip0 Input buffer. Will be advanced on function's return.
* @param[out] op Output buffer.
* @param l0 The number of samples to fill in the output buffer, including
* both input samples and interpolation (zero) samples.
*/
void copyUpsample(double*& ip0, double* op, int l0)
{
int b = min(UpSkip, l0);
if (b > 0)
{
l0 -= b;
UpSkip -= b;
*op = 0.0;
op++;
b--;
while (b > 0)
{
*op = 0.0;
op++;
b--;
}
}
double* ip = ip0;
int l = l0 / UpFactor;
int lz = l0 - l * UpFactor;
if (UpFactor == 3)
{
while (l > 0)
{
op[0] = *ip;
op[1] = 0.0;
op[2] = 0.0;
ip++;
op += UpFactor;
l--;
}
}
else if (UpFactor == 5)
{
while (l > 0)
{
op[0] = *ip;
op[1] = 0.0;
op[2] = 0.0;
op[3] = 0.0;
op[4] = 0.0;
ip++;
op += UpFactor;
l--;
}
}
else
{
while (l > 0)
{
op[0] = *ip;
for (int j = 1; j < UpFactor; ++j) { op[j] = 0.0; }
ip++;
op += UpFactor;
l--;
}
}
if (lz > 0)
{
*op = *ip;
op++;
ip++;
UpSkip = UpFactor - lz;
while (lz > 1)
{
*op = 0.0;
op++;
lz--;
}
}
ip0 = ip;
}
/**
* Function copies sample data from the CurOutput buffer to the specified
* output buffer and advances its position. If necessary, this function
* "consumes" latency and performs downsampling.
*
* @param Offs CurOutput buffer offset, can be negative.
* @param[out] op0 Output buffer pointer, will be advanced.
* @param b The number of output samples available, including those which
* are discarded during whole-number downsampling.
* @param l0 The overall output sample count, will be increased.
*/
void copyToOutput(int Offs, double*& op0, int b, int& l0)
{
if (Offs < 0)
{
if (Offs + b <= 0) { Offs += BlockLen2; }
else
{
copyToOutput(Offs + BlockLen2, op0, -Offs, l0);
b += Offs;
Offs = 0;
}
}
if (LatencyLeft > 0)
{
if (LatencyLeft >= b)
{
LatencyLeft -= b;
return;
}
Offs += LatencyLeft;
b -= LatencyLeft;
LatencyLeft = 0;
}
const int df = DownFactor;
if (DownShift > 0)
{
int Skip = Offs & (df - 1);
if (Skip > 0)
{
Skip = df - Skip;
b -= Skip;
Offs += Skip;
}
if (b > 0)
{
b = (b + df - 1) >> DownShift;
memcpy(op0, &CurOutput[Offs >> DownShift],
b * sizeof(double));
op0 += b;
l0 += b;
}
}
else
{
if (df > 1)
{
const double* ip = &CurOutput[Offs + DownSkip];
int l = (b + df - 1 - DownSkip) / df;
DownSkip += l * df - b;
double* op = op0;
l0 += l;
op0 += l;
while (l > 0)
{
*op = *ip;
op++;
ip += df;
l--;
}
}
else
{
memcpy(op0, &CurOutput[Offs], b * sizeof(double));
op0 += b;
l0 += b;
}
}
}
/**
* Function performs input spectrum mirroring which is used to perform a
* fast "power of 2" upsampling. Such mirroring is equivalent to insertion
* of zeros into the input signal.
*/
void mirrorInputSpectrum()
{
const int bl1 = BlockLen2 >> UpShift;
const int bl2 = bl1 + bl1;
int i;
for (i = bl1 + 2; i < bl2; i += 2)
{
CurInput[i] = CurInput[bl2 - i];
CurInput[i + 1] = -CurInput[bl2 - i + 1];
}
CurInput[bl1] = CurInput[1];
CurInput[bl1 + 1] = 0.0;
CurInput[1] = CurInput[0];
for (i = 1; i < UpShift; ++i)
{
const int z = bl1 << i;
memcpy(&CurInput[z], CurInput, z * sizeof(double));
CurInput[z + 1] = 0.0;
}
}
};
} // namespace r8b
#endif // R8B_CDSPBLOCKCONVOLVER_INCLUDED
@@ -0,0 +1,557 @@
//$ 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-2014 Aleksey Vaneev
* See the "License.txt" file for license.
*/
#ifndef R8B_CDSPFIRFILTER_INCLUDED
#define R8B_CDSPFIRFILTER_INCLUDED
#include "CDSPSincFilterGen.h"
#include "CDSPRealFFT.h"
namespace r8b
{
/**
* 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 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 non-integer value, and usually is small, but
///< is never equal to zero. The minimum-phase filter is transformed
///< from the linear-phase filter. The transformation has precision
///< limits which 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 it is within tolerable range.
///< In a small (1%) random subset of cases the skew is bigger and
///< cannot be predicted.
};
/**
* @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;
}
/**
* @return The minimal allowed low-pass filter's transition band, in percent.
*/
static double getLPMinTransBand() { return (0.5); }
/**
* @return The maximal allowed low-pass filter's transition band, in percent.
*/
static double getLPMaxTransBand() { return (45.0); }
/**
* @return The minimal allowed low-pass filter's stop-band attenuation, in decibel.
*/
static double getLPMinAtten() { return (49.0); }
/**
* @return The maximal allowed low-pass filter's stop-band attenuation, in decibel.
*/
static double getLPMaxAtten() { return (218.0); }
/**
* @return "True" if kernel block of *this filter has zero-phase response.
*/
bool isZeroPhase() const { return (IsZeroPhase); }
/**
* @return Filter's latency, in samples (integer part).
*/
int getLatency() const { return (Latency); }
/**
* @return Filter's latency, in samples (fractional part). Always zero for linear-phase filters.
*/
double getLatencyFrac() const { return (LatencyFrac); }
/**
* @return Filter kernel length, in samples. Not to be confused with the block length.
*/
int getKernelLen() const { return (KernelLen); }
/**
* @return Filter's block length, espressed as Nth power of 2. The actual length is twice as large due to zero-padding.
*/
int getBlockLenBits() const { return (BlockLenBits); }
/**
* @return Filter's kernel block, in complex-numbered form obtained via
* the CDSPRealFFT::forward() function call, zero-padded, gain-adjusted
* with the CDSPRealFFT::getInvMulConst() * 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); }
/**
* This function should be called when the filter obtained via the
* filter cache is no longer needed.
*/
void unref();
private:
double ReqNormFreq = 0; ///< Required normalized frequency, 0 to 1 inclusive.
double ReqTransBand = 0; ///< Required transition band in percent, as passed by the user.
double ReqAtten = 0; ///< Required stop-band attenuation in decibel, as passed by the user (positive value).
EDSPFilterPhaseResponse ReqPhase = fprLinearPhase; ///< Required filter's phase response.
double ReqGain = 0; ///< Required overall filter's gain.
CDSPFIRFilter* Next = nullptr; ///< Next FIR filter in cache's list.
int RefCount = 1; ///< The number of references made to *this FIR filter.
bool IsZeroPhase = false; ///< "True" if kernel block of *this filter has zero-phase response.
int Latency = 0; ///< Filter's latency in samples (integer part).
double LatencyFrac = 0; ///< Filter's latency in samples (fractional part).
int KernelLen = 0; ///< Filter kernel length, in samples.
int BlockLenBits =
0; ///< 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 part of the buffer contains zero-padding to allow alias-free convolution.
CDSPFIRFilter() { }
/**
* Function 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 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 != nullptr) { 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;
}
double 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 = M_PI * (1.0 - fo1) * ReqNormFreq;
sinc.initBand(CDSPSincFilterGen::wftKaiser, WinParams, true);
KernelLen = sinc.KernelLen;
BlockLenBits = getBitOccupancy(KernelLen - 1);
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, 3, 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, (BlockLen * 2 - KernelLen) * sizeof(double));
}
else
{
normalizeFIRFilter(&KernelBlock[0], KernelLen, ffto->getInvMulConst() * ReqGain);
memset(&KernelBlock[KernelLen], 0, (BlockLen * 2 - KernelLen) * sizeof(double));
}
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
{
friend class CDSPFIRFilter;
public:
/**
* @return The number of filters present in the cache now. This value can
* be monitored for debugging "forgotten" filters.
*/
static int getObjCount()
{
R8BSYNC(StateSync);
return (ObjCount);
}
/**
* Function 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.
* @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 = nullptr)
{
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);
R8BSYNC(StateSync);
CDSPFIRFilter* PrevObj = nullptr;
CDSPFIRFilter* CurObj = Objects;
while (CurObj != nullptr)
{
if (CurObj->ReqNormFreq == ReqNormFreq &&
CurObj->ReqTransBand == ReqTransBand &&
CurObj->ReqAtten == ReqAtten &&
CurObj->ReqPhase == ReqPhase &&
CurObj->ReqGain == ReqGain) { break; }
if (CurObj->Next == nullptr && ObjCount >= R8B_FILTER_CACHE_MAX)
{
if (CurObj->RefCount == 0)
{
// Delete the last filter which is not used.
PrevObj->Next = nullptr;
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 = nullptr;
CurObj->Next = Objects.unkeep();
Objects = CurObj;
}
CurObj = nullptr;
break;
}
PrevObj = CurObj;
CurObj = CurObj->Next;
}
if (CurObj != nullptr)
{
CurObj->RefCount++;
if (PrevObj == nullptr) { 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);
}
private:
static CSyncObject StateSync; ///< Cache state synchronizer.
///<
static CPtrKeeper<CDSPFIRFilter*> Objects; ///< The chain of cached
///< objects.
///<
static int ObjCount; ///< The number of objects currently preset in the
///< cache.
///<
};
// ---------------------------------------------------------------------------
// CDSPFIRFilter PUBLIC
// ---------------------------------------------------------------------------
inline void CDSPFIRFilter::unref()
{
R8BSYNC(CDSPFIRFilterCache :: StateSync);
RefCount--;
}
// ---------------------------------------------------------------------------
} // namespace r8b
#endif // R8B_CDSPFIRFILTER_INCLUDED
@@ -0,0 +1,495 @@
//$ nocpp
/**
* @file CDSPFracInterpolator.h
*
* @brief Fractional delay interpolator and filter bank classes.
*
* This file includes fractional delay interpolator class.
*
* r8brain-free-src Copyright (c) 2013-2014 Aleksey Vaneev
* See the "License.txt" file for license.
*/
#ifndef R8B_CDSPFRACINTERPOLATOR_INCLUDED
#define R8B_CDSPFRACINTERPOLATOR_INCLUDED
#include "CDSPSincFilterGen.h"
#include "CDSPProcessor.h"
namespace r8b
{
/**
* @brief Sinc function-based fractional delay filter bank class.
*
* Class implements storage and initialization of a bank of sinc-based
* fractional delay filters, expressed as 1st, 2nd or 3rd order polynomial
* interpolation coefficients. The filters are windowed by either the "Vaneev"
* or "Kaiser" power-raised window function. The FilterLen and FilterFracs
* parameters can be varied freely without breaking the resampler.
*
* @param FilterLen Specifies the number of samples (taps) each fractional
* delay filter should have. This must be an even value, the minimal value for
* FilterLen is 6, the maximal value is 30. To achieve a higher resampling
* precision, the oversampling should be used in the first place instead of
* using a higher FilterLen value. The lower this value is the lower the
* signal-to-noise performance of the interpolator will be. Each FilterLen
* decrease by 2 decreases SNR by approximately 12 to 14 decibel.
* @param FilterFracs The number of fractional delay positions to sample. For
* a high signal-to-noise ratio this has to be a larger value. The larger the
* FilterLen is the larger the FilterFracs should be. Approximate FilterLen to
* FilterFracs correspondence (for 2nd order interpolation only): 6:11, 8:17,
* 10:23, 12:41, 14:67, 16:97, 18:137, 20:211, 22:353, 24:673, 26:1051,
* 28:1733, 30:2833. The FilterFracs can be considerably reduced with 3rd
* order interpolation in use. In order to get consistent results when
* resampling to/from different sample rates, it is suggested to set this
* parameter to a suitable prime number.
* @param ElementSize The size of each filter's tap, in "double" values. This
* parameter corresponds to the complexity of interpolation. 4 should be set
* for 3rd order, 3 for 2nd order, 2 for linear interpolation.
* @param InterpPoints The number of points the interpolation is based on.
* This value should not be confused with the ElementSize. Set to 2 for linear
* interpolation.
*/
template <int FilterLen, int FilterFracs, int ElementSize, int InterpPoints>
class CDSPFracDelayFilterBank : public R8B_BASECLASS
{
public:
CDSPFracDelayFilterBank()
{
R8BASSERT(FilterLen >= 6);
R8BASSERT(FilterLen <= 30);
R8BASSERT(( FilterLen & 1 ) == 0);
R8BASSERT(FilterFracs > 0);
R8BASSERT(ElementSize >= 2 && ElementSize <= 4);
R8BASSERT(InterpPoints == 2 || InterpPoints == 8);
calculate();
}
/**
* Function calculates the filter bank.
*
* @param Params Window function's parameters. If NULL then the built-in
* table values for the current FilterLen will be used.
*/
void calculate(const double* const Params = nullptr)
{
CDSPSincFilterGen sinc;
sinc.Len2 = FilterLen / 2;
double* p = Table;
const int pc2 = InterpPoints / 2;
int i;
if (FilterLen <= 20)
{
for (i = -pc2 + 1; i <= FilterFracs + pc2; ++i)
{
sinc.FracDelay = double(FilterFracs - i) / FilterFracs;
sinc.initFrac(CDSPSincFilterGen::wftVaneev, Params);
sinc.generateFrac(p, &CDSPSincFilterGen::calcWindowVaneev,
ElementSize);
normalizeFIRFilter(p, FilterLen, 1.0, ElementSize);
p += FilterSize;
}
}
else
{
for (i = -pc2 + 1; i <= FilterFracs + pc2; ++i)
{
sinc.FracDelay = double(FilterFracs - i) / FilterFracs;
sinc.initFrac(CDSPSincFilterGen::wftKaiser, Params, true);
sinc.generateFrac(p, &CDSPSincFilterGen::calcWindowKaiser,
ElementSize);
normalizeFIRFilter(p, FilterLen, 1.0, ElementSize);
p += FilterSize;
}
}
const int TablePos2 = FilterSize;
const int TablePos3 = FilterSize * 2;
const int TablePos4 = FilterSize * 3;
const int TablePos5 = FilterSize * 4;
const int TablePos6 = FilterSize * 5;
const int TablePos7 = FilterSize * 6;
const int TablePos8 = FilterSize * 7;
double* const TableEnd = Table + (FilterFracs + 1) * FilterSize;
p = Table;
if (InterpPoints == 8)
{
if (ElementSize == 3)
{
// Calculate 2nd order spline (polynomial) interpolation
// coefficients using 8 points.
while (p < TableEnd)
{
calcSpline2p8Coeffs(p, p[0], p[TablePos2],
p[TablePos3], p[TablePos4], p[TablePos5],
p[TablePos6], p[TablePos7], p[TablePos8]);
p += ElementSize;
}
}
else if (ElementSize == 4)
{
// Calculate 3rd order spline (polynomial) interpolation
// coefficients using 8 points.
while (p < TableEnd)
{
calcSpline3p8Coeffs(p, p[0], p[TablePos2],
p[TablePos3], p[TablePos4], p[TablePos5],
p[TablePos6], p[TablePos7], p[TablePos8]);
p += ElementSize;
}
}
}
else
{
// Calculate linear interpolation coefficients.
while (p < TableEnd)
{
p[1] = p[TablePos2] - p[0];
p += ElementSize;
}
}
}
/**
* @param i Filter index, in the range 0 to FilterFracs, inclusive.
* @return Reference to the filter.
*/
const double& operator [](const int i) const
{
R8BASSERT(i >= 0 && i <= FilterFracs);
return (Table[i * FilterSize]);
}
private:
static const int FilterSize = FilterLen * ElementSize; ///< This constant
///< specifies the "size" of a single filter in "double" elements.
///<
double Table[ FilterSize * (FilterFracs + InterpPoints)]; ///< The
///< table of fractional delay filters for all discrete fractional
///< x = 0..1 sample positions, and interpolation coefficients.
///<
};
/**
* @brief Fractional delay filter bank-based interpolator class.
*
* Class implements the fractional delay interpolator. This implementation at
* first puts the input signal into a ring buffer and then performs
* interpolation. The interpolation is performed using sinc-based fractional
* delay filters. These filters are contained in a bank, and for higher
* precision they are interpolated between adjacent filters.
*
* To increase sample timing precision, this class uses "resettable counter"
* approach. This gives less than "1 per 100 billion" sample timing error when
* converting 44100 to 48000 sample rate.
*
* VERY IMPORTANT: the interpolation step should not exceed FilterLen / 2 + 1
* samples or the algorithm in its current form will fail. However, this
* condition can be easily met if the input signal is suitably downsampled
* first before the interpolation is performed.
*
* @param FilterLen Specifies the number of samples (taps) each fractional
* delay filter should have. See the r8b::CDSPFracDelayFilterBank class for
* more details.
* @param FilterFracs The number of fractional delay positions to sample. See
* the r8b::CDSPFracDelayFilterBank class for more details.
*/
template <int FilterLen, int FilterFracs>
class CDSPFracInterpolator final : public CDSPProcessor
{
public:
/**
* Constructor initalizes the interpolator. It is important to call the
* getMaxOutLen() function afterwards to obtain the optimal output buffer
* length.
*
* @param aSrcSampleRate Source sample rate.
* @param aDstSampleRate Destination sample rate.
* @param aInitFracPos Initial fractional position, in samples, in the
* range [0; 1). A non-zero value can be specified to remove the
* fractional delay introduced by a minimum-phase filter. This value is
* usually equal to the CDSPBlockConvolver.getLatencyFrac() value.
*/
CDSPFracInterpolator(const double aSrcSampleRate,
const double aDstSampleRate, const double aInitFracPos)
: SrcSampleRate(aSrcSampleRate)
, DstSampleRate(aDstSampleRate)
, InitFracPos(aInitFracPos)
{
R8BASSERT(SrcSampleRate > 0.0);
R8BASSERT(DstSampleRate > 0.0);
R8BASSERT(InitFracPos >= 0.0 && InitFracPos < 1.0);
R8BASSERT(BufLenBits >= 5);
R8BASSERT(( 1 << BufLenBits ) >= FilterLen * 3);
clear();
}
int getLatency() const override { return (0); }
double getLatencyFrac() const override { return (0.0); }
int getInLenBeforeOutStart(const int NextInLen) const override { return (FilterLenD2 + NextInLen); }
int getMaxOutLen(const int MaxInLen) const override
{
R8BASSERT(MaxInLen >= 0);
return ((int)ceil(MaxInLen * DstSampleRate / SrcSampleRate) + 1);
}
/**
* Function changes the destination sample rate "on the fly". Note that
* the getMaxOutLen() function may needed to be called after calling this
* function as the maximal number of output samples produced by the
* interpolator depends on the destination sample rate.
*
* It can be a useful approach to construct *this object passing the
* maximal possible destination sample rate to the constructor, obtaining
* the getMaxOutLen() value and then setting the destination sample rate
* to whatever lower value is needed.
*
* It is advisable to change the sample rate in small increments, and as
* rarely as possible: e.g. every several samples.
*
* @param NewDstSampleRate New destination sample rate.
*/
void setDstSampleRate(const double NewDstSampleRate)
{
R8BASSERT(DstSampleRate > 0.0);
DstSampleRate = NewDstSampleRate;
InCounter = 0;
InPosInt = 0;
InPosShift = InPosFrac;
}
/**
* 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.
*
* Note that the destination sample rate will remain unchanged, even if it
* was changed since the time of *this object's construction.
*/
void clear() override
{
BufLeft = 0;
WritePos = 0;
ReadPos = BufLen - FilterLenD2Minus1; // Set "read" position to
// account for filter's latency at zero fractional delay which
// equals to FilterLenD2Minus1.
memset(&Buf[ReadPos], 0, FilterLenD2Minus1 * sizeof(double));
InCounter = 0;
InPosInt = 0;
InPosFrac = InitFracPos;
InPosShift = InitFracPos;
}
int process(double* ip, int l, double*& op0) override
{
R8BASSERT(l >= 0);
R8BASSERT(ip != op0 || l == 0 || SrcSampleRate > DstSampleRate);
double* op = op0;
while (l > 0)
{
// Add new input samples to both halves of the ring buffer.
const int b = min(min(l, BufLen - WritePos),
BufLeftMax - BufLeft);
double* const wp1 = Buf + WritePos;
double* const wp2 = wp1 + BufLen;
int i;
for (i = 0; i < b; ++i)
{
wp1[i] = ip[i];
wp2[i] = ip[i];
}
ip += b;
WritePos = (WritePos + b) & BufLenMask;
l -= b;
BufLeft += b;
// Produce as many output samples as possible.
while (BufLeft >= FilterLenD2Plus1)
{
double x = InPosFrac * FilterFracs;
const int fti = (int)x; // Function table index.
x -= fti; // Coefficient for interpolation between adjacent
// fractional delay filters.
const double x2 = x * x;
const double* const ftp = &FilterBank[fti];
const double* const rp = Buf + ReadPos;
double s = 0.0;
int ii = 0;
#if R8B_FLTTEST
const double x3 = x2 * x;
#endif // R8B_FLTTEST
for (i = 0; i < FilterLen; ++i)
{
#if !R8B_FLTTEST
s += (ftp[ii] + ftp[ii + 1] * x +
ftp[ii + 2] * x2) * rp[i];
#else // !R8B_FLTTEST
s += ( ftp[ ii ] + ftp[ ii + 1 ] * x +
ftp[ ii + 2 ] * x2 + ftp[ ii + 3 ] * x3 ) * rp[ i ];
#endif // !R8B_FLTTEST
ii += FilterElementSize;
}
*op = s;
op++;
InCounter++;
const double NextInPos =
InCounter * SrcSampleRate / DstSampleRate + InPosShift;
const int NextInPosInt = (int)NextInPos;
const int PosIncr = NextInPosInt - InPosInt;
InPosInt = NextInPosInt;
InPosFrac = NextInPos - NextInPosInt;
ReadPos = (ReadPos + PosIncr) & BufLenMask;
BufLeft -= PosIncr;
}
}
if (InCounter > 1000)
{
// Reset the interpolation position counter to achieve a higher
// sample timing precision.
InCounter = 0;
InPosInt = 0;
InPosShift = InPosFrac;
}
return (int(op - op0));
}
private:
#if !R8B_FLTTEST
static const int FilterElementSize = 3; ///< The number of "doubles" a
///< single filter tap consists of (includes interpolation
///< coefficients).
///<
#else // !R8B_FLTTEST
static const int FilterElementSize = 4; ///< The number of "doubles" a
///< single filter tap consists of (includes interpolation
///< coefficients). During filter testing a higher precision
///< interpolation is used.
///<
#endif // !R8B_FLTTEST
static const int FilterLenD2 = FilterLen >> 1; ///< = FilterLen / 2.
///<
static const int FilterLenD2Minus1 = FilterLenD2 - 1; ///< =
///< FilterLen / 2 - 1. This value also equals to filter's latency in
///< samples (taps).
///<
static const int FilterLenD2Plus1 = FilterLenD2 + 1; ///< =
///< FilterLen / 2 + 1.
///<
static const int BufLenBits = 8; ///< The length of the ring buffer,
///< expressed as Nth power of 2. This value can be reduced if it is
///< known that only short input buffers will be passed to the
///< interpolator. The minimum value of this parameter is 5, and
///< 1 << BufLenBits should be at least 3 times larger than the
///< FilterLen.
///<
static const int BufLen = 1 << BufLenBits; ///< The length of the ring
///< buffer. The actual length is twice as long to allow "beyond max
///< position" positioning.
///<
static const int BufLenMask = BufLen - 1; ///< Mask used for quick buffer
///< position wrapping.
///<
static const int BufLeftMax = BufLen - FilterLenD2Minus1; ///< The number
///< of new samples that the ring buffer can hold at most. The
///< remaining FilterLenD2Minus1 samples hold "previous" input samples
///< for the filter.
///<
double Buf[ BufLen * 2 ]; ///< The ring buffer.
///<
double SrcSampleRate = 0; ///< Source sample rate.
///<
double DstSampleRate = 0; ///< Destination sample rate.
///<
double InitFracPos = 0; ///< Initial fractional position, in samples, in the
///< range [0; 1).
///<
int BufLeft = 0; ///< The number of samples left in the buffer to process.
///< When this value is below FilterLenD2Plus1, the interpolation
///< cycle ends.
///<
int WritePos = 0; ///< The current buffer write position. Incremented together
///< with the BufLeft variable.
///<
int ReadPos = 0; ///< The current buffer read position.
///<
int InCounter = 0; ///< Interpolation step counter.
///<
int InPosInt = 0; ///< Interpolation position (integer part).
///<
double InPosFrac = 0; ///< Interpolation position (fractional part).
///<
double InPosShift = 0; ///< Interpolation position fractional shift.
///<
#if !R8B_FLTTEST
static const CDSPFracDelayFilterBank<FilterLen, FilterFracs, 3,
8> FilterBank; ///< Filter bank object, defined statically if no
///< filter test takes place.
///<
#else // !R8B_FLTTEST
public:
CDSPFracDelayFilterBank< FilterLen, FilterFracs, 4, 8 > FilterBank; ///<
///< Filter bank object, defined as a member variable to allow for
///< recalculation.
///<
#endif // !R8B_FLTTEST
};
// ---------------------------------------------------------------------------
#if !R8B_FLTTEST
template <int FilterLen, int FilterFracs>
const CDSPFracDelayFilterBank<FilterLen, FilterFracs, 3, 8>
CDSPFracInterpolator<FilterLen, FilterFracs>::FilterBank;
#endif // !R8B_FLTTEST
// ---------------------------------------------------------------------------
} // namespace r8b
#endif // R8B_CDSPFRACINTERPOLATOR_INCLUDED
@@ -0,0 +1,105 @@
//$ nocpp
/**
* @file CDSPProcessor.h
*
* @brief The base virtual class for DSP processing algorithms.
*
* This file includes the base virtual class for DSP processing algorithm
* classes like FIR filtering and interpolation.
*
* r8brain-free-src Copyright (c) 2013-2014 Aleksey Vaneev
* See the "License.txt" file for license.
*/
#pragma once
#include "r8bbase.h"
namespace r8b
{
/**
* @brief The base virtual class for DSP processing algorithms.
*
* This class can be used as a base class for various DSP processing
* algorithms (processors). DSP processors that are derived from this class
* can be seamlessly integrated into various DSP processing graphs.
*/
class CDSPProcessor : public R8B_BASECLASS
{
R8BNOCTOR(CDSPProcessor)
public:
CDSPProcessor() { }
virtual ~CDSPProcessor() { }
/**
* @return The latency, in samples, which is present in the output signal.
* This value is usually zero if the DSP processor "consumes" the latency
* automatically.
*/
virtual int getLatency() const = 0;
/**
* @return Fractional latency, in samples, which is present in the output
* signal. This value is usually zero if a linear-phase filtering is used.
* With minimum-phase filters in use, this value can be non-zero even if
* the getLatency() function returns zero.
*/
virtual double getLatencyFrac() const = 0;
/**
* @param NextInLen The number of input samples required before the output
* starts on the next resampling step.
* @return The cumulative number of samples that should be passed to *this
* object before the actual output starts. This value includes latencies
* induced by all processors which run after *this processor in chain.
*/
virtual int getInLenBeforeOutStart(const int NextInLen) const = 0;
/**
* @param MaxInLen The number of samples planned to process at once, at
* most.
* @return The maximal length of the output buffer required when
* processing the "MaxInLen" number of input samples.
*/
virtual int getMaxOutLen(const int MaxInLen) const = 0;
/**
* 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.
*/
virtual void clear() = 0;
/**
* Function performs DSP processing.
*
* @param ip Input data pointer.
* @param l0 How many samples to process.
* @param[out] op0 Output data pointer. The capacity of this buffer should
* be equal to the value returned by the getMaxOutLen() function for the
* given "l0". This buffer can be equal to "ip" only if the
* getMaxOutLen( l0 ) function returned a value lesser than "l0". This
* pointer can be incremented on function's return if latency compensation
* was performed by the processor. Note that on function's return, this
* pointer may point to some internal buffers, including the "ip" buffer,
* ignoring the originally passed value.
* @return The number of output samples written to the "op0" buffer and
* available after processing. This value can be smaller or larger in
* comparison to the original "l0" value due to processing and filter's
* latency compensation that took place, and due to resampling if it was
* performed.
*/
virtual int process(double* ip, int l0, double*& op0) = 0;
};
} // namespace r8b
@@ -0,0 +1,592 @@
//$ 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 future reusal. Such approach minimizes
* time necessary to initialize the FFT object of the required length.
*
* r8brain-free-src Copyright (c) 2013-2014 Aleksey Vaneev
* See the "License.txt" file for license.
*/
#ifndef R8B_CDSPREALFFT_INCLUDED
#define R8B_CDSPREALFFT_INCLUDED
#include "r8bbase.h"
#if !R8B_IPP
#include "fft4g.h"
#endif // !R8B_IPP
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 (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_IPP
ippsFFTFwd_RToPerm_64f( p, p, SPtr, WorkBuffer );
#else // R8B_IPP
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 );
#else // R8B_IPP
ooura_fft::rdft(Len, -1, p, wi.getPtr(), wd.getPtr());
#endif // R8B_IPP
}
/**
* 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 ip1 Input data block 1.
* @param ip2 Input data block 2.
* @param[out] op Output data block, should not be equal to ip1 nor ip2.
*/
void multiplyBlocks(const double* const ip1, const double* const ip2, double* const op) const
{
#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 is similar to the multiplyBlocks() function, but instead of
* replacing data in the output buffer, the data is summed with the output
* buffer.
*
* @param ip1 Input data block 1.
* @param ip2 Input data block 2.
* @param[out] op Output data block, should not be equal to ip1 nor ip2.
*/
void multiplyBlocksAdd(const double* const ip1, const double* const ip2, double* const op) const
{
op[0] += ip1[0] * ip2[0];
op[1] += ip1[1] * ip2[1];
#if R8B_IPP
ippsAddProduct_64fc( (const Ipp64fc*) ( ip1 + 2 ), (const Ipp64fc*) ( ip2 + 2 ), (Ipp64fc*) ( op + 2 ), ( Len >> 1 ) - 1 );
#else // R8B_IPP
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 ip Input data block 1.
* @param[in,out] op Output/input data block 2.
*/
void multiplyBlocks(const double* const ip, double* const op) const
{
#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)
{
const double 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 ip Input data block 1, "zero-phase" response.
* @param[in,out] op Output/input data block 2.
*/
void multiplyBlocksZ(const double* const ip, double* const op) const
{
op[0] *= ip[0];
op[1] *= ip[1];
int i = 2;
while (i < Len)
{
op[i] *= ip[i];
op[i + 1] *= ip[i];
i += 2;
}
}
/**
* Function performs in-place spectrum squaring. May cause aliasing
* if the filter was not zero-padded before the forward() function call.
*
* @param[in,out] p Pointer to data block to square, length should be
* equal to *this object's getLen(). This data block should contain
* complex spectrum data, previously obtained via the forward() function.
*/
void sqr(double* const p) const
{
p[0] *= p[0];
p[1] *= p[1];
#if R8B_IPP
ippsSqr_64fc( (Ipp64fc*) ( p + 2 ), (Ipp64fc*) ( p + 2 ),
( Len >> 1 ) - 1 );
#else // R8B_IPP
int i = 2;
while (i < Len)
{
const double r = p[i] * p[i] - p[i + 1] * p[i + 1];
p[i + 1] = p[i] * (p[i + 1] + p[i + 1]);
p[i] = r;
i += 2;
}
#endif // R8B_IPP
}
private:
int LenBits = 0; ///< Length of FFT block (expressed as Nth power of 2).
///<
int Len = 0; ///< Length of FFT block (number of real values).
///<
double InvMulConst = 0; ///< Inverse FFT multiply constant.
///<
CDSPRealFFT* Next = nullptr; ///< Next object in a singly-linked list.
///<
#if R8B_IPP
IppsFFTSpec_R_64f* SPtr = nullptr; ///< 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.
///<
#else // R8B_IPP
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() { }
~CObjKeeper() { delete Object; }
CObjKeeper& operator =(CDSPRealFFT* const aObject)
{
Object = aObject;
return (*this);
}
operator CDSPRealFFT*() const { return (Object); }
private:
CDSPRealFFT* Object = nullptr; ///< 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 )
#else // R8B_IPP
, InvMulConst(2.0 / Len)
#endif // R8B_IPP
{
#if R8B_IPP
int SpecSize = 0;
int SpecBufferSize = 0;
int BufferSize = 0;
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 );
#else // R8B_IPP
wi.alloc((int)ceil(2.0 + sqrt(double(Len >> 1))));
wi[0] = 0;
wd.alloc(Len >> 1);
#endif // R8B_IPP
}
~CDSPRealFFT() { 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() { }
/**
* 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 != nullptr) { release(Object); } }
/**
* @return Pointer to the acquired FFT object.
*/
const CDSPRealFFT* operator ->() const
{
R8BASSERT(Object != nullptr);
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 != nullptr)
{
if (Object->LenBits == LenBits) { return; }
release(Object);
}
Object = acquire(LenBits);
}
/**
* Function releases a previously acquired FFT object.
*/
void reset()
{
if (Object != nullptr)
{
release(Object);
Object = nullptr;
}
}
private:
CDSPRealFFT* Object = nullptr; ///< 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] == nullptr) { 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 = nullptr)
{
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(double));
memset(&ip[KernelLen], 0, (Len - KernelLen) * sizeof(double));
CDSPRealFFTKeeper ffto(LenBits);
ffto->forward(ip);
// Create the "log |c|" spectrum while saving the original power spectrum
// in the "ip2" buffer.
ip2[0] = ip[0];
ip[0] = log(fabs(ip[0]) + 1e-50);
ip2[Len2] = ip[1];
ip[1] = log(fabs(ip[1]) + 1e-50);
for (i = 1; i < Len2; ++i)
{
ip2[i] = sqrt(ip[i * 2] * ip[i * 2] +
ip[i * 2 + 1] * ip[i * 2 + 1]);
ip[i * 2] = log(ip2[i] + 1e-50);
ip[i * 2 + 1] = 0.0;
}
// Convert to cepstrum and apply discrete Hilbert transform.
ffto->inverse(ip);
ip[0] = 0.0;
for (i = 1; i < Len2; ++i) { ip[i] *= ffto->getInvMulConst(); }
ip[Len2] = 0.0;
for (i = Len2 + 1; i < Len; ++i) { ip[i] *= -ffto->getInvMulConst(); }
// 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);
ip[0] = ip2[0];
ip[1] = ip2[Len2];
for (i = 1; i < Len2; ++i)
{
const double p = ip2[i];
ip[i * 2 + 0] = cos(ip[i * 2 + 1]) * p;
ip[i * 2 + 1] = sin(ip[i * 2 + 1]) * p;
}
ffto->inverse(ip);
if (DoFinalMul) { for (i = 0; i < KernelLen; ++i) { Kernel[i] = ip[i] * ffto->getInvMulConst(); } }
else { memcpy(&Kernel[0], &ip[0], KernelLen * sizeof(double)); }
if (DCGroupDelay != nullptr)
{
double tmp;
calcFIRFilterResponseAndGroupDelay(Kernel, KernelLen, 0.0,
tmp, tmp, *DCGroupDelay);
}
}
} // namespace r8b
#endif // VOX_CDSPREALFFT_INCLUDED
@@ -0,0 +1,521 @@
//$ 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-2014 Aleksey Vaneev
* See the "License.txt" file for license.
*/
#ifndef R8B_CDSPRESAMPLER_INCLUDED
#define R8B_CDSPRESAMPLER_INCLUDED
#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).
*
* @param CInterpClass Interpolator class that should be used by the
* resampler. The desired interpolation quality can be defined via the
* template parameters of the interpolator class. See
* r8b::CDSPFracInterpolator and r8b::CDSPFracDelayFilterBank for description
* of the template parameters.
*/
template <class CInterpClass =
CDSPFracInterpolator<R8B_FLTLEN, R8B_FLTFRACS>>
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 150 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. Note that the "power of 2" upsampling
* (but not downsampling) requires a lot of buffer memory: e.g. upsampling
* by a factor of 16 requires an intermediate buffer MaxInLen*(16+8)
* samples long. So, when doing the "power of 2" upsampling it is highly
* recommended to do it in small steps, e.g. no more than 256 samples at
* once (also set the MaxInLen to 256).
* @param MaxInLen 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
* the resampler may use the input buffer itself for intermediate sample
* data storage.
* @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 stationary signals, this value can be decreased to 20 to 10 if the
* signal being resampled is mostly non-stationary (e.g. impulse
* 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 by up to 1 sample, depending on
* the minimum-phase filter's actual fractional delay. If the output
* stream should always start at "time zero" offset with minimum-phase
* filters the UsePower2 should be set to "false". Linear-phase filters
* do not have fractional delay.
* @param UsePower2 "True" if the "power of 2" resampling optimization
* should be used when possible. This value should be set to "false" if
* the access to interpolator is needed in any case (also the source and
* destination sample rates should not be equal).
* @see CDSPFIRFilterCache::getLPFilter()
*/
CDSPResampler(const double SrcSampleRate, const double DstSampleRate,
const int MaxInLen, const double ReqTransBand = 2.0,
const double ReqAtten = 206.91,
const EDSPFilterPhaseResponse ReqPhase = fprLinearPhase,
const bool UsePower2 = true)
{
R8BASSERT(SrcSampleRate > 0.0);
R8BASSERT(DstSampleRate > 0.0);
R8BASSERT(MaxInLen > 0);
if (SrcSampleRate == DstSampleRate)
{
ConvCount = 0;
return;
}
int SrcSRMult;
int SrcSRDiv = 1;
int MaxOutLen = MaxInLen;
int ConvBufCapacities[ 2 ];
double PrevLatencyFrac = 0.0;
if (DstSampleRate * 2 > SrcSampleRate)
{
// Only a single convolver with 2X upsampling is required.
SrcSRMult = 2;
const double NormFreq = (DstSampleRate > SrcSampleRate ? 0.5 : 0.5 * DstSampleRate / SrcSampleRate);
Convs[0] = new CDSPBlockConvolver(CDSPFIRFilterCache::getLPFilter(NormFreq, ReqTransBand, ReqAtten, ReqPhase, 2.0), 2, 1, 0.0);
ConvCount = 1;
MaxOutLen = Convs[0]->getMaxOutLen(MaxOutLen);
ConvBufCapacities[0] = MaxOutLen;
PrevLatencyFrac = Convs[0]->getLatencyFrac();
// Find if the destination to source sample rate ratio is
// a "power of 2" value.
int UseConvCount = 1;
while (true)
{
const double TestSR = SrcSampleRate * (1 << UseConvCount);
if (TestSR > DstSampleRate)
{
UseConvCount = 0; // Power of 2 not found.
break;
}
if (TestSR == DstSampleRate)
{
break; // Power of 2 found.
}
UseConvCount++;
}
if (UsePower2 && UseConvCount > 0)
{
R8BASSERT(UseConvCount <= ConvCountMax);
ConvBufCapacities[1] = 0;
ConvCount = UseConvCount;
for (int i = 1; i < UseConvCount; ++i)
{
const double tb = (i >= 2 ? 45.0 : 34.0);
Convs[i] = new CDSPBlockConvolver(CDSPFIRFilterCache::getLPFilter(0.5, tb, ReqAtten, ReqPhase, 2.0), 2, 1, PrevLatencyFrac);
MaxOutLen = Convs[i]->getMaxOutLen(MaxOutLen);
ConvBufCapacities[i & 1] = MaxOutLen;
PrevLatencyFrac = Convs[i]->getLatencyFrac();
}
ConvBufs[0].alloc(ConvBufCapacities[0]);
if (ConvBufCapacities[1] > 0) { ConvBufs[1].alloc(ConvBufCapacities[1]); }
return; // No interpolator is needed.
}
ConvBufs[0].alloc(ConvBufCapacities[0]);
}
else
{
SrcSRMult = 1;
ConvBufCapacities[0] = 0;
ConvCount = 0;
const double CheckSR = DstSampleRate * 4;
while (CheckSR * SrcSRDiv <= SrcSampleRate)
{
SrcSRDiv *= 2;
// If downsampling is even deeper, use a less steep filter at
// this step.
const double tb =
(CheckSR * SrcSRDiv <= SrcSampleRate ? 45.0 : 34.0);
Convs[ConvCount] = new CDSPBlockConvolver(CDSPFIRFilterCache::getLPFilter(0.5, tb, ReqAtten, ReqPhase, 1.0), 1, 2, PrevLatencyFrac);
MaxOutLen = Convs[ConvCount]->getMaxOutLen(MaxOutLen);
PrevLatencyFrac = Convs[ConvCount]->getLatencyFrac();
ConvCount++;
R8BASSERT(ConvCount < ConvCountMax);
}
const double NormFreq = DstSampleRate * SrcSRDiv / SrcSampleRate;
const int downf = (UsePower2 && NormFreq == 0.5 ? 2 : 1);
Convs[ConvCount] = new CDSPBlockConvolver(CDSPFIRFilterCache::getLPFilter(NormFreq, ReqTransBand, ReqAtten, ReqPhase, 1.0), 1, downf,
PrevLatencyFrac);
MaxOutLen = Convs[ConvCount]->getMaxOutLen(MaxOutLen);
PrevLatencyFrac = Convs[ConvCount]->getLatencyFrac();
ConvCount++;
if (downf > 1)
{
return; // No interpolator is needed.
}
}
Interp = new CInterpClass(SrcSampleRate * SrcSRMult / SrcSRDiv, DstSampleRate, PrevLatencyFrac);
MaxOutLen = Interp->getMaxOutLen(MaxOutLen);
if (MaxOutLen <= ConvBufCapacities[0]) { InterpBuf = ConvBufs[0]; }
else if (MaxOutLen <= MaxInLen) { InterpBuf = nullptr; }
else
{
TmpBuf.alloc(MaxOutLen);
InterpBuf = TmpBuf;
}
}
int getLatency() const override { return (0); }
double getLatencyFrac() const override { return (0.0); }
int getInLenBeforeOutStart(const int NextInLen) const override
{
int l = (Interp == nullptr ? 0 : Interp->getInLenBeforeOutStart(NextInLen));
for (int i = ConvCount - 1; i >= 0; i--) { l = Convs[i]->getInLenBeforeOutStart(l); }
return (l);
}
int getMaxOutLen(const int/* MaxInLen */) const override { return (0); }
/**
* 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.
*/
void clear() override
{
for (int i = 0; i < ConvCount; ++i) { Convs[i]->clear(); }
if (Interp != nullptr) { Interp->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 may be used as output buffer by
* this function.
* @param l The number of samples available in the input buffer. Should
* not exceed the MaxInLen supplied to the constructor.
* @param[out] op0 This variable receives the pointer to the resampled
* data. On function's return, this pointer may point to the address
* within the "ip0" input buffer, or 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 may be 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.
*/
int process(double* ip0, int l, double*& op0) override
{
R8BASSERT(l >= 0);
if (ConvCount == 0)
{
op0 = ip0;
return (l);
}
double* ip = ip0;
double* op = nullptr;
for (int i = 0; i < ConvCount; ++i)
{
op = (ConvBufs[i & 1] == nullptr ? ip0 : ConvBufs[i & 1]);
l = Convs[i]->process(ip, l, op);
ip = op;
}
if (Interp == nullptr)
{
op0 = op;
return (l);
}
op = (InterpBuf == nullptr ? ip0 : InterpBuf);
op0 = op;
return (Interp->process(ip, l, op));
}
/**
* 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 MaxInLen The max input length value which was previously passed
* to the constructor.
* @param ip Input buffer pointer.
* @param iplen Length of the input buffer in samples.
* @param op Output buffer pointer.
* @param oplen Length of the output buffer in samples.
*/
void oneshot(const int MaxInLen, const double* ip, int iplen,
double* op, int oplen)
{
const CFixedBuffer<double> ZeroBuf(MaxInLen);
memset(&ZeroBuf[0], 0, MaxInLen * sizeof(double));
while (oplen > 0)
{
int rc;
double* p;
if (iplen == 0)
{
rc = MaxInLen;
p = static_cast<double*>(&ZeroBuf[0]);
}
else
{
rc = min(iplen, MaxInLen);
p = const_cast<double*>(ip);
ip += rc;
iplen -= rc;
}
double* op0;
int wc = process(p, rc, op0);
wc = min(oplen, wc);
memcpy(op, op0, wc * sizeof(double));
op += wc;
oplen -= wc;
}
clear();
}
private:
static const int ConvCountMax = 8; ///< 8 convolvers with the
///< built-in 2x up- or downsampling is enough for 256x up- or
///< downsampling.
///<
CPtrKeeper<CDSPBlockConvolver*> Convs[ ConvCountMax ]; ///< Convolvers.
///<
int ConvCount = 0; ///< The number of objects defined in the Convs[] array.
///< Equals to 0 if sample rate conversion is not needed.
///<
CPtrKeeper<CInterpClass*> Interp; ///< Fractional interpolator object.
///< Equals NULL if no fractional interpolation is required meaning
///< the "power of 2" resampling is performed or no resampling is
///< performed at all.
///<
CFixedBuffer<double> ConvBufs[ 2 ]; ///< Intermediate convolution
///< buffers to use, used only when at least 2x upsampling is
///< performed. These buffers are used in flip-flop manner. If NULL
///< then the input buffer will be used instead.
///<
CFixedBuffer<double> TmpBuf; ///< Additional output buffer, can be
///< addressed by the InterpBuf pointer.
///<
double* InterpBuf = nullptr; ///< Final output interpolation buffer to use. If NULL
///< then the input buffer will be used instead. Otherwise this
///< pointer points to either ConvBufs or TmpBuf.
///<
};
/**
* @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 final : public CDSPResampler<CDSPFracInterpolator<18, 137>>
{
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 MaxInLen 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 MaxInLen, const double ReqTransBand = 2.0)
: CDSPResampler<CDSPFracInterpolator<18, 137>>(SrcSampleRate, DstSampleRate, MaxInLen, ReqTransBand, 136.45, fprLinearPhase, true) { }
};
/**
* @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
* usually do not feature stationary signal components and thus need resampler
* with a less SNR. See the r8b::CDSPResampler class for details.
*/
class CDSPResampler16IR final : public CDSPResampler<CDSPFracInterpolator<14, 67>>
{
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 MaxInLen 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 MaxInLen, const double ReqTransBand = 2.0)
: CDSPResampler<CDSPFracInterpolator<14, 67>>(SrcSampleRate, DstSampleRate, MaxInLen, ReqTransBand, 109.56, fprLinearPhase, true) { }
};
/**
* @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 final : public CDSPResampler<CDSPFracInterpolator<24, 673>>
{
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 MaxInLen 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 MaxInLen, const double ReqTransBand = 2.0)
: CDSPResampler<CDSPFracInterpolator<24, 673>>(SrcSampleRate, DstSampleRate, MaxInLen, ReqTransBand, 180.15, fprLinearPhase, true) { }
};
} // namespace r8b
#endif // R8B_CDSPRESAMPLER_INCLUDED
@@ -0,0 +1,783 @@
//$ nobt
//$ nocpp
/**
* @file CDSPSincFilterGen.h
*
* @brief Sinc function-based FIR filter generator class.
*
* This file includes the CDSPSincFilterGen class implementation that
* generates FIR filters.
*
* r8brain-free-src Copyright (c) 2013-2014 Aleksey Vaneev
* See the "License.txt" file for license.
*/
#ifndef R8B_CDSPSINCFILTERGEN_INCLUDED
#define R8B_CDSPSINCFILTERGEN_INCLUDED
#include "r8bbase.h"
namespace r8b
{
/**
* @brief Sinc function-based FIR filter generator class.
*
* Structure that holds state used to perform generation of sinc functions of
* various types, windowed by the Blackman window by default (but the window
* function can be changed if necessary).
*/
class CDSPSincFilterGen
{
public:
double Len2 = 0; ///< Required half filter kernel's length in samples (can be
///< a fractional value). Final physical kernel length will be
///< provided in the KernelLen variable. Len2 should be >= 2.
///<
int KernelLen = 0; ///< Resulting length of the filter kernel, this variable
///< is set after the call to one of the "init" functions.
///<
int fl2 = 0; ///< Internal "half kernel length" value. This value can be used
///< as filter's latency in samples (taps), this variable is set after
///< the call to one of the "init" functions.
///<
union
{
struct
{
double Freq1; ///< Required corner circular frequency 1 [0; pi].
///< Used only in the generateBand() function.
///<
double Freq2; ///< Required corner circular frequency 2 [0; pi].
///< Used only in the generateBand() function. The range
///< [Freq1; Freq2] defines a pass band for the generateBand()
///< function.
///<
};
struct
{
double FracDelay; ///< Fractional delay in the range [0; 1], used
///< only in the generateFrac() function. Note that the
///< FracDelay parameter is actually inversed. At 0.0 value it
///< produces 1 sample delay (with the latency equal to fl2),
///< at 1.0 value it produces 0 sample delay (with the latency
///< equal to fl2 - 1).
///<
};
};
/**
* Window function type.
*/
enum EWindowFunctionType
{
wftCosine, ///< Generalized cosine window function. No parameters
///< required. The "Power" parameter is optional.
///<
wftKaiser, ///< Kaiser window function. Requires the "Beta" parameter.
///< The "Power" parameter is optional.
///<
wftGaussian, ///< Gaussian window function. Requires the "Sigma"
///< parameter. The "Power" parameter is optional.
///<
wftVaneev ///< Vaneev window function, mainly used for short
///< fractional delay filters, requires 4 cosine width parameters,
///< plus the "Power" parameter which is mandatory.
///<
};
typedef double ( CDSPSincFilterGen::*CWindowFunc )(); ///< Window
///< calculation function pointer type.
///<
/**
* Function initializes *this structure for generation of a window
* function, odd-sized.
*
* @param WinType Window function type.
* @param Params Window function's parameters. If NULL, the table values
* may be used.
* @param UsePower "True" if the power factor should be used to raise the
* window function. If "true", the power factor should be specified as the
* last value in the Params array. If Params is NULL, the table or default
* value of -1.0 (off) will be used.
*/
void initWindow(const EWindowFunctionType WinType = wftCosine,
const double* const Params = nullptr, const bool UsePower = false)
{
R8BASSERT(Len2 >= 2.0);
fl2 = (int)floor(Len2);
KernelLen = fl2 + fl2 + 1;
setWindow(WinType, Params, UsePower, true);
}
/**
* Function initializes *this structure for generation of band-limited
* sinc filter kernel. The generateBand() or generateBandPow() functions
* should be used to calculate the filter.
*
* @param WinType Window function type.
* @param Params Window function's parameters. If NULL, the table values
* may be used.
* @param UsePower "True" if the power factor should be used to raise the
* window function. If "true", the power factor should be specified as the
* last value in the Params array. If Params is NULL, the table or default
* value of -1.0 (off) will be used.
*/
void initBand(const EWindowFunctionType WinType = wftCosine,
const double* const Params = nullptr, const bool UsePower = false)
{
R8BASSERT(Len2 >= 2.0);
fl2 = (int)floor(Len2);
KernelLen = fl2 + fl2 + 1;
f1.init(Freq1, 0.0);
f2.init(Freq2, 0.0);
setWindow(WinType, Params, UsePower, true);
}
/**
* Function initializes *this structure for Hilbert transformation filter
* calculation. Freq1 and Freq2 variables are not used.
* The generateHilbert() function should be used to calculate the filter.
*
* @param WinType Window function type.
* @param Params Window function's parameters. If NULL, the table values
* may be used.
* @param UsePower "True" if the power factor should be used to raise the
* window function. If "true", the power factor should be specified as the
* last value in the Params array. If Params is NULL, the table or default
* value of -1.0 (off) will be used.
*/
void initHilbert(const EWindowFunctionType WinType = wftCosine,
const double* const Params = nullptr, const bool UsePower = false)
{
R8BASSERT(Len2 >= 2.0);
fl2 = (int)floor(Len2);
KernelLen = fl2 + fl2 + 1;
setWindow(WinType, Params, UsePower, true);
}
/**
* Function initializes *this structure for generation of full-bandwidth
* fractional delay sinc filter kernel. Freq1 and Freq2 variables are not
* used. The generateFrac() function should be used to calculate the
* filter.
*
* @param WinType Window function type.
* @param Params Window function's parameters. If NULL, the table values
* may be used.
* @param UsePower "True" if the power factor should be used to raise the
* window function. If "true", the power factor should be specified as the
* last value in the Params array. If Params is NULL, the table or default
* value of -1.0 (off) will be used.
*/
void initFrac(const EWindowFunctionType WinType = wftCosine,
const double* const Params = nullptr, const bool UsePower = false)
{
R8BASSERT(Len2 >= 2.0);
fl2 = (int)ceil(Len2);
KernelLen = fl2 + fl2;
setWindow(WinType, Params, UsePower, false, FracDelay);
}
/**
* @return The next "Hann" window function coefficient.
*/
double calcWindowHann() { return (0.5 + 0.5 * w1.generate()); }
/**
* @return The next "Hamming" window function coefficient.
*/
double calcWindowHamming() { return (0.54 + 0.46 * w1.generate()); }
/**
* @return The next "Blackman" window function coefficient.
*/
double calcWindowBlackman() { return (0.42 + 0.5 * w1.generate() + 0.08 * w2.generate()); }
/**
* @return The next "Nuttall" window function coefficient.
*/
double calcWindowNuttall()
{
return (0.355768 + 0.487396 * w1.generate() +
0.144232 * w2.generate() + 0.012604 * w3.generate());
}
/**
* @return The next "Blackman-Nuttall" window function coefficient.
*/
double calcWindowBlackmanNuttall()
{
return (0.3635819 + 0.4891775 * w1.generate() +
0.1365995 * w2.generate() + 0.0106411 * w3.generate());
}
/**
* @return The next "Kaiser" window function coefficient.
*/
double calcWindowKaiser()
{
const double n = 1.0 - sqr(wn / Len2 + KaiserLen2Frac);
wn++;
if (n < 0.0) { return (0.0); }
return (besselI0(KaiserBeta * sqrt(n)) / KaiserDiv);
}
/**
* @return The next "Gaussian" window function coefficient.
*/
double calcWindowGaussian()
{
const double f = exp(-0.5 * sqr(wn / GaussianSigma +
GaussianSigmaFrac));
wn++;
return (f);
}
/**
* @return The next "Vaneev" windowing function coefficient, for use with
* the fractional delay filters.
*/
double calcWindowVaneev()
{
const double v1 = 0.5 + 0.5 * w1.generate();
const double v2 = 0.5 + 0.5 * w2.generate();
const double v3 = 0.5 + 0.5 * w3.generate();
const double v4 = 0.5 + 0.5 * w4.generate();
return (v1 * sqr(v2) * sqr(sqr(v3)) * sqr(sqr(sqr(v4))));
}
/**
* Function calculates window function only.
*
* @param[out] op Output buffer, length = KernelLen.
* @param wfunc Window calculation function to use.
*/
template <class T>
void generateWindow(T* op,
CWindowFunc wfunc = &CDSPSincFilterGen::calcWindowBlackman)
{
op += fl2;
T* op2 = op;
int l = fl2;
if (Power < 0.0)
{
*op = (*this.*wfunc)();
while (l > 0)
{
const double v = (*this.*wfunc)();
++op;
--op2;
*op = v;
*op2 = v;
l--;
}
}
else
{
*op = pows((*this.*wfunc)(), Power);
while (l > 0)
{
const double v = pows((*this.*wfunc)(), Power);
++op;
--op2;
*op = v;
*op2 = v;
l--;
}
}
}
/**
* Function calculates band-limited windowed sinc function-based filter
* kernel.
*
* @param[out] op Output buffer, length = KernelLen.
* @param wfunc Window calculation function to use.
*/
template <class T>
void generateBand(T* op,
CWindowFunc wfunc = &CDSPSincFilterGen::calcWindowBlackman)
{
op += fl2;
T* op2 = op;
f1.generate();
f2.generate();
int t = 1;
if (Power < 0.0)
{
*op = (Freq2 - Freq1) * (*this.*wfunc)() / M_PI;
while (t <= fl2)
{
const double v = (f2.generate() - f1.generate()) *
(*this.*wfunc)() / t / M_PI;
++op;
--op2;
*op = v;
*op2 = v;
t++;
}
}
else
{
*op = (Freq2 - Freq1) * pows((*this.*wfunc)(), Power) / M_PI;
while (t <= fl2)
{
const double v = (f2.generate() - f1.generate()) *
pows((*this.*wfunc)(), Power) / t / M_PI;
++op;
--op2;
*op = v;
*op2 = v;
t++;
}
}
}
/**
* Function calculates windowed Hilbert transformer filter kernel.
*
* @param[out] op Output buffer, length = KernelLen.
* @param wfunc Window calculation function to use.
*/
template <class T>
void generateHilbert(T* op,
CWindowFunc wfunc = &CDSPSincFilterGen::calcWindowBlackman)
{
static const double fvalues[ 2 ] = { 0.0, 2.0 };
op += fl2;
T* op2 = op;
(*this.*wfunc)();
*op = 0.0;
int t = 1;
if (Power < 0.0)
{
while (t <= fl2)
{
const double v = fvalues[t & 1] *
(*this.*wfunc)() / t / M_PI;
++op;
--op2;
*op = v;
*op2 = -v;
t++;
}
}
else
{
while (t <= fl2)
{
const double v = fvalues[t & 1] *
pows((*this.*wfunc)(), Power) / t / M_PI;
++op;
--op2;
*op = v;
*op2 = -v;
t++;
}
}
}
/**
* Function calculates windowed fractional delay filter kernel.
*
* @param[out] op Output buffer, length = KernelLen.
* @param wfunc Window calculation function to use.
* @param opinc Output buffer increment, in "op" elements.
*/
template <class T>
void generateFrac(T* op,
CWindowFunc wfunc = &CDSPSincFilterGen::calcWindowBlackman,
const int opinc = 1)
{
R8BASSERT(opinc != 0);
double f[ 2 ];
f[0] = sin(FracDelay * M_PI);
f[1] = -f[0];
int t = -fl2;
if (t + FracDelay < -Len2)
{
(*this.*wfunc)();
*op = 0.0;
op += opinc;
t++;
}
int mt = (FracDelay >= 1.0 - 1e-13 && FracDelay <= 1.0 + 1e-13 ? -1 : 0);
if (Power < 0.0)
{
while (t < mt)
{
*op = f[t & 1] * (*this.*wfunc)() / (t + FracDelay) /
M_PI;
op += opinc;
t++;
}
double ut = t + FracDelay;
*op = (fabs(ut) <= 1e-13 ? (*this.*wfunc)() : f[t & 1] * (*this.*wfunc)() / ut / M_PI);
mt = fl2 - 2;
while (t < mt)
{
op += opinc;
t++;
*op = f[t & 1] * (*this.*wfunc)() / (t + FracDelay) /
M_PI;
}
op += opinc;
t++;
ut = t + FracDelay;
*op = (ut > Len2 ? 0.0 : f[t & 1] * (*this.*wfunc)() / ut / M_PI);
}
else
{
while (t < mt)
{
*op = f[t & 1] * pows((*this.*wfunc)(), Power) /
(t + FracDelay) / M_PI;
op += opinc;
t++;
}
double ut = t + FracDelay;
*op = (fabs(ut) <= 1e-13 ? pows((*this.*wfunc)(), Power) : f[t & 1] * pows((*this.*wfunc)(), Power) / ut / M_PI);
mt = fl2 - 2;
while (t < mt)
{
op += opinc;
t++;
*op = f[t & 1] * pows((*this.*wfunc)(), Power) /
(t + FracDelay) / M_PI;
}
op += opinc;
t++;
ut = t + FracDelay;
*op = (ut > Len2 ? 0.0 : f[t & 1] * pows((*this.*wfunc)(), Power) / ut / M_PI);
}
}
private:
double Power = 0; ///< The power factor used to raise the window function.
///< Equals a negative value if the power factor should not be used.
///<
CSineGen f1; ///< Sine function 1. Used in the generateBand() function.
///<
CSineGen f2; ///< Sine function 2. Used in the generateBand() function.
///<
int wn = 0; ///< Window function integer position. 0 - center of the window
///< function. This variable may not be used by some window functions.
///<
CSineGen w1; ///< Cosine wave 1 for window function.
///<
CSineGen w2; ///< Cosine wave 2 for window function.
///<
CSineGen w3; ///< Cosine wave 3 for window function.
///<
CSineGen w4; ///< Cosine wave 4 for window function.
///<
union
{
struct
{
double KaiserBeta; // Kaiser window function's "Beta" coefficient.
double KaiserDiv; // Kaiser window function's divisor.
double KaiserLen2Frac; // Equals FracDelay / Len2.
};
struct
{
double GaussianSigma; // Gaussian window function's "Sigma" coefficient.
double GaussianSigmaFrac; // Equals FracDelay / GaussianSigma.
};
};
/**
* @param FilterLen2 Half filter length in samples (taps).
* @return The Kaiser power-raised window function parameters for the
* specified filter length.
*/
static const double* getKaiserParams(const int FilterLen2)
{
R8BASSERT(FilterLen2 >= 3 && FilterLen2 <= 15);
static const double Coeffs[][ 2 ] = {
{ 3.41547411, 1.41275111 }, // 6 @ 51.38
{ 3.72300147, 1.75212634 }, // 8 @ 67.60
{ 4.34839223, 1.85801372 }, // 10 @ 79.86
{ 4.90860405, 1.97194591 }, // 12 @ 93.29
{ 5.17430411, 2.20609617 }, // 14 @ 106.74
{ 21.08445389, 0.59684098 }, // 16 @ 119.71
{ 9.14552738, 1.57619894 }, // 18 @ 134.53
{ 22.02344341, 0.71669064 }, // 20 @ 148.44
{ 16.41763757, 1.05884118 }, // 22 @ 164.61
{ 12.55262798, 1.51553897 }, // 24 @ 180.15
{ 9.84861210, 2.09912671 }, // 26 @ 194.15
{ 9.73150659, 2.29079494 }, // 28 @ 206.91
{ 10.42657217, 2.29183875 }, // 30 @ 218.20
};
return (Coeffs[FilterLen2 - 3]);
}
/**
* Function initializes Kaiser window function calculation. The FracDelay
* variable should be initialized when using this window function.
*
* @param Params Function parameters. If NULL, the table values will be
* used. If not NULL, the first parameter should specify the "Beta" value.
* @param UsePower "True" if the power factor should be used to raise the
* window function.
* @param IsCentered "True" if centered window should be used. This
* parameter usually equals to "false" for fractional delay filters only.
*/
void setWindowKaiser(const double* Params, const bool UsePower,
const bool IsCentered)
{
wn = (IsCentered ? 0 : -fl2);
if (Params == nullptr)
{
Params = getKaiserParams(fl2);
KaiserBeta = Params[0];
Power = (UsePower ? Params[1] : -1.0);
}
else
{
KaiserBeta = clampr(Params[0], 1.0, 350.0);
Power = (UsePower ? fabs(Params[1]) : -1.0);
}
KaiserDiv = besselI0(KaiserBeta);
KaiserLen2Frac = FracDelay / Len2;
}
/**
* Function initializes Gaussian window function calculation. The FracDelay
* variable should be initialized when using this window function.
*
* @param Params Function parameters. If NULL, the table values will be
* used. If not NULL, the first parameter should specify the "Sigma"
* value.
* @param UsePower "True" if the power factor should be used to raise the
* window function.
* @param IsCentered "True" if centered window should be used. This
* parameter usually equals to "false" for fractional delay filters only.
*/
void setWindowGaussian(const double* Params, const bool UsePower,
const bool IsCentered)
{
wn = (IsCentered ? 0 : -fl2);
if (Params == nullptr)
{
GaussianSigma = 1.0;
Power = -1.0;
}
else
{
GaussianSigma = clampr(fabs(Params[0]), 1e-1, 100.0);
Power = (UsePower ? fabs(Params[1]) : -1.0);
}
GaussianSigma *= Len2;
GaussianSigmaFrac = FracDelay / GaussianSigma;
}
/**
* Function initializes "Vaneev" window function calculation.
*
* @param Params Function parameters. If NULL, the table values will be
* used. If not NULL, the first 4 parameters should specify the cosine
* multipliers while the fifth parameter should specify the "Power" value.
* @param IsCentered "True" if centered window should be used. This
* parameter usually equals to "false" for fractional delay filters only.
*/
void setWindowVaneev(const double* Params, const bool IsCentered)
{
R8BASSERT(fl2 >= 3 && fl2 <= 15);
// This set of parameters was obtained via probabilistic optimization.
// The number after @ shows the approximate (+/- 1 dB) signal-to-noise
// ratio for the given filter. SNR can be also decreased by using a
// filter bank with suboptimal number of sampled fractional delay
// filters: thus the FilterFracs should be selected with care.
static const double Coeffs[][ 5 ] = {
{ 0.35926104, 0.66154037, 0.79264845, 0.31897879, 0.18844972 }, // 6 @ 51.91
{ 0.81690764, 0.39409966, 0.01546567, 0.02067949, 1.15143000 }, // 8 @ 67.87
{ 0.26545140, 0.84346586, 0.12114879, 0.23640230, 0.72659219 }, // 10 @ 81.82
{ 0.56254211, 0.32615646, 0.88375690, 0.46944169, 0.32862728 }, // 12 @ 95.36
{ 0.51926261, 0.41265523, 0.89552919, 0.47699008, 0.37308306 }, // 14 @ 109.60
{ 0.55650321, 0.92583533, 0.58934379, 0.16399064, 0.67129777 }, // 16 @ 122.89
{ 0.27930548, 0.94898807, 0.70335882, 0.32080180, 0.59102482 }, // 18 @ 136.45
{ 0.12620836, 0.94993219, 0.70209891, 0.34747431, 0.64429174 }, // 20 @ 150.52
{ 0.83595860, 0.95040751, 0.64127591, 0.30856013, 0.69692727 }, // 22 @ 163.41
{ 0.41252871, 0.96236749, 0.74895429, 0.41669175, 0.65996102 }, // 24 @ 174.32
{ 0.98567539, 0.88907131, 0.65652775, 0.34585902, 0.77265757 }, // 26 @ 191.26
{ 0.64526843, 0.67729329, 0.91813705, 0.43972488, 0.68332682 }, // 28 @ 195.77
{ 0.65310281, 0.66723395, 0.91751074, 0.43956737, 0.73651421 }, // 30 @ 207.04
};
double p[ 4 ];
if (Params == nullptr)
{
Params = Coeffs[fl2 - 3];
Power = Params[4];
}
else
{
p[0] = clampr(Params[0], -4.0, 4.0);
p[1] = clampr(Params[1], -4.0, 4.0);
p[2] = clampr(Params[2], -4.0, 4.0);
p[3] = clampr(Params[3], -4.0, 4.0);
Power = fabs(Params[4]);
Params = p;
}
if (IsCentered)
{
w1.init(Params[0] * M_PI / Len2, M_PI * 0.5);
w2.init(Params[1] * M_PI / Len2, M_PI * 0.5);
w3.init(Params[2] * M_PI / Len2, M_PI * 0.5);
w4.init(Params[3] * M_PI / Len2, M_PI * 0.5);
}
else
{
const double step1 = Params[0] * M_PI / Len2;
w1.init(step1, M_PI * 0.5 - step1 * fl2 + step1 * FracDelay);
const double step2 = Params[1] * M_PI / Len2;
w2.init(step2, M_PI * 0.5 - step2 * fl2 + step2 * FracDelay);
const double step3 = Params[2] * M_PI / Len2;
w3.init(step3, M_PI * 0.5 - step3 * fl2 + step3 * FracDelay);
const double step4 = Params[3] * M_PI / Len2;
w4.init(step4, M_PI * 0.5 - step4 * fl2 + step4 * FracDelay);
}
}
/**
* Function initializes calculation of window function of the specified
* type.
*
* @param WinType Window function type.
* @param Params Window function's parameters. If NULL, the table values
* may be used.
* @param UsePower "True" if the power factor should be used to raise the
* window function. If "true", the power factor should be specified as the
* last value in the Params array. If Params is NULL, the table or default
* value of -1.0 (off) will be used.
* @param IsCentered "True" if centered window should be used. This
* parameter usually equals to "false" for fractional delay filters only.
* @param UseFracDelay Fractional delay to use.
*/
void setWindow(const EWindowFunctionType WinType,
const double* const Params, const bool UsePower,
const bool IsCentered, const double UseFracDelay = 0.0)
{
FracDelay = UseFracDelay;
if (WinType == wftCosine)
{
if (IsCentered)
{
w1.init(M_PI / Len2, M_PI * 0.5);
w2.init(M_2PI / Len2, M_PI * 0.5);
w3.init(M_3PI / Len2, M_PI * 0.5);
}
else
{
const double step1 = M_PI / Len2;
w1.init(step1, M_PI * 0.5 - step1 * fl2 +
step1 * FracDelay);
const double step2 = M_2PI / Len2;
w2.init(step2, M_PI * 0.5 - step2 * fl2 +
step2 * FracDelay);
const double step3 = M_3PI / Len2;
w3.init(step3, M_PI * 0.5 - step3 * fl2 +
step3 * FracDelay);
}
Power = (UsePower && Params != nullptr ? Params[0] : -1.0);
}
else if (WinType == wftKaiser) { setWindowKaiser(Params, UsePower, IsCentered); }
else if (WinType == wftGaussian) { setWindowGaussian(Params, UsePower, IsCentered); }
else { setWindowVaneev(Params, IsCentered); }
}
};
} // namespace r8b
#endif // R8B_CDSPSINCFILTERGEN_INCLUDED
@@ -0,0 +1,55 @@
# r8brain-free-src #
## Introduction ##
Open source (under the MIT license) high-quality professional audio sample rate converter (SRC) (resampling) library. Features routines for SRC, both up- and downsampling, to/from any sample rate, including non-integer sample rates: it can be also used for conversion to/from SACD sample rate and even go beyond that. SRC routines were implemented in multi-platform C++ code, and have a high level of optimality.
The structure of this library's objects is such that they can be frequently created and destroyed in large applications with a minimal performance impact due to a high level of reusability of its most "initialization-expensive" objects: the fast Fourier transform and FIR filter objects.
The SRC algorithm at first produces 2X oversampled (relative to the source sample rate, or the destination sample rate if the downsampling is performed) signal and then performs interpolation using a bank of short (14 to 28 taps, depending on the required precision) polynomial-interpolated sinc function-based fractional delay filters. This puts the algorithm into the league of the fastest among the most precise SRC algorithms. The more precise alternative being only the whole number-factored SRC, which can be slower.
## Requirements ##
C++ compiler and system with the "double" floating point type (53-bit mantissa) support. No explicit code for the "float" type is present in this library, because as practice has shown the "float"-based code performs considerably slower on a modern processor, at least in this library. However, if the "double" type really represents the "float" type (24-bit mantissa) in a given compiler, on a given system, the library won't become broken, only the conversion quality may become degraded. This library always uses the "sizeof( double )" operator to obtain "double" floating point type's size in bytes. This library does not have dependencies beside the standard C library, the "windows.h" on Windows and the "pthread.h" on Mac OS X and Linux.
## Links ##
* [Documentation](https://c16f948c1577658f1b05f6c1d146730273eb6285.googledrive.com/host/0BwakvlMNBQdwUXhLMDFJLWdBSlU/Documentation/)
* [Discussion](http://www.kvraudio.com/forum/viewtopic.php?t=389711)
* [r8brain-free-src-1.6-dll.zip](https://drive.google.com/open?id=0BwakvlMNBQdwR1JlZ3pKcVBpaWc&authuser=0)
## Usage Information ##
The sample rate converter (resampler) is represented by the **r8b::CDSPResampler<>** class, which is a single front-end class for the whole library. You do not basically need to use nor understand any other classes beside this class. Several derived classes that have varying levels of precision are also available.
The code of the library resides in the "r8b" C++ namespace, effectively isolating it from all other code. The code is thread-safe. A separate resampler object should be created for each audio channel or stream being processed.
Note that you will need to compile the "r8bbase.cpp" source file and include the resulting object file into your application build. This source file includes definitions of several global static objects used by the library. You may also need to include to your project: the "Kernel32" library (on Windows) and the "pthread" library on Mac OS X and Linux.
The library is able to process signal of any scale and loudness: it is not limited to just a "usual" -1.0 to 1.0 range.
The code of this library was commented in the [Doxygen](http://www.doxygen.org/) style. To generate the documentation locally you may run the "doxygen ./other/r8bdoxy.txt" command from the library's directory.
Preliminary tests show that the r8b::CDSPResampler24 resampler class achieves 15.6\*n\_cores Mflops when converting 1 channel of audio from 44100 to 96000 sample rate, on a typical Intel Core i7-4770K processor-based system without overclocking. This approximately translates to a real-time resampling of 160\*n\_cores audio streams, at 100% CPU load. When comparing performance of this resampler library to another library make sure that the competing library is also tuned to produce a fully linear-phase response.
## Dynamic Link Library ##
The functions of this SRC library are also accessible in simplified form via the DLL file on Windows, requiring a processor with SSE2 support. Delphi Pascal interface unit file for the DLL file is available. DLL and C LIB files are distributed in a separate ZIP file on the project's home page. On non-Windows systems it is preferrable to use the C++ library directly.
## Real-time Applications ##
The resampler class of this library was designed as asynchronous processor: it may produce any number of output samples, depending on the input sample data length and the resampling parameters. The resampler must be fed with the input sample data until enough output sample data was produced, with any excess output samples used before feeding the resampler with more input data. A "relief" factor here is that the resampler removes the initial processing latency automatically, and that after initial moments of processing the output becomes steady, with only minor output sample data length fluctuations.
Note that the r8b::CDSPResampler::getInLenBeforeOutStart() function can be used to estimate the number of input samples that should be provided to the resampler before the actual output starts.
## Notes ##
When using the r8b::CDSPResampler<> class directly, you may select the transition band/steepness of the low-pass (reconstruction) filter, expressed as a percentage of the full spectral bandwidth of the input signal (or the output signal if the downsampling is performed), and the desired stop-band attenuation in decibel.
The transition band is specified as the normalized spectral space of the input signal (or the output signal if the downsampling is performed) between the low-pass filter's -3 dB point and the Nyquist frequency, and ranges from 0.5% to 45%. Stop-band attenuation can be specified in the range 49 to 218 decibel.
This SRC library also implements a faster "power of 2" resampling (e.g. 2X, 4X, 8X, 16X, etc. upsampling and downsampling).
This library was tested for compatibility with [GNU C++](http://gcc.gnu.org/), [Microsoft Visual C++](http://www.microsoft.com/visualstudio/eng/products/visual-studio-express-products) and [Intel C++](http://software.intel.com/en-us/c-compilers) compilers, on 32- and 64-bit Windows, Mac OS X and CentOS Linux.
All code is fully "inline", without the need to compile many source files. The memory footprint is quite modest.
## Users ##
This library is used by:
* [Combo Model V VSTi instrument](http://www.martinic.com/combov/)
* [WDM Asio Link Driver](http://midithru.net/Home/AsioLink)
* [Boogex Guitar Amp audio plugin](http://www.voxengo.com/product/boogex/)
* [OpenMPT](http://openmpt.org/)
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,24 @@
The MIT License (MIT)
r8brain-free-src Copyright (c) 2013-2014 Aleksey Vaneev
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
Please credit the creator of this library in your documentation in the
following way: "Sample rate converter designed by Aleksey Vaneev of Voxengo"
Binary file not shown.

After

Width:  |  Height:  |  Size: 9.0 KiB

@@ -0,0 +1,17 @@
PROJECT_NAME = "r8brain-free-src"
PROJECT_BRIEF = "High-quality pro audio sample rate converter library"
PROJECT_LOGO = ./other/icon.png
OUTPUT_DIRECTORY = ./
BRIEF_MEMBER_DESC = NO
SHORT_NAMES = YES
TAB_SIZE = 4
OPTIMIZE_OUTPUT_FOR_C = NO
INLINE_INFO = NO
SORT_BRIEF_DOCS = YES
SORT_MEMBERS_CTORS_1ST = YES
SHOW_USED_FILES = NO
SHOW_NAMESPACES = NO
WARN_NO_PARAMDOC = YES
INPUT = ./
HTML_OUTPUT = Documentation
GENERATE_LATEX = NO
@@ -0,0 +1,27 @@
/**
* @file r8bbase.cpp
*
* @brief C++ file that should be compiled and included into your application.
*
* This is a single library file that should be compiled and included into the
* project that uses the "r8brain-free-src" sample rate converter. This file
* defines several global static objects used by the library.
*
* You may also need to include to your project: the "Kernel32" library
* (on Windows) and the "pthread" library on Mac OS X and Linux.
*
* r8brain-free-src Copyright (c) 2013-2014 Aleksey Vaneev
* See the "License.txt" file for license.
*/
#include "CDSPFIRFilter.h"
#include "CDSPFracInterpolator.h"
namespace r8b
{
CSyncObject CDSPRealFFTKeeper::StateSync;
CDSPRealFFT::CObjKeeper CDSPRealFFTKeeper::FFTObjects[ 31 ];
CSyncObject CDSPFIRFilterCache::StateSync;
CPtrKeeper<CDSPFIRFilter*> CDSPFIRFilterCache::Objects;
int CDSPFIRFilterCache::ObjCount = 0;
} // namespace r8b
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,131 @@
//$ nobt
//$ nocpp
/**
* @file r8bconf.h
*
* @brief The "configuration" inclusion file you can modify.
*
* This is the "configuration" inclusion file for the "r8brain-free-src"
* sample rate converter. You may redefine the macros here as you see fit.
*
* r8brain-free-src Copyright (c) 2013-2014 Aleksey Vaneev
* See the "License.txt" file for license.
*/
#ifndef R8BCONF_INCLUDED
#define R8BCONF_INCLUDED
#if defined( _WIN32 ) || defined( _WIN64 )
#define R8B_WIN 1
#elif defined( __APPLE__ )
#define R8B_MAC 1
#else // defined( __APPLE__ )
#define R8B_LNX 1 // Assume Linux (Unix) platform by default.
#endif // defined( __APPLE__ )
#if !defined( R8B_FLTLEN )
/**
* This macro defines the default fractional delay filter length. Macro is
* used by the r8b::CDSPResampler class.
*/
#define R8B_FLTLEN 28
#endif // !defined( R8B_FLTLEN )
#if !defined( R8B_FLTFRACS )
/**
* This macro defines the default number of fractional delay filters that
* are sampled by the filter bank. Macro is used by the r8b::CDSPResampler
* class. In order to get consistent results when resampling to/from
* different sample rates, it is suggested to set this macro to a suitable
* prime number.
*/
#define R8B_FLTFRACS 1733
#endif // !defined( R8B_FLTFRACS )
#if !defined( R8B_IPP )
/**
* Set the R8B_IPP macro definition to 1 to enable the use of Intel IPP's
* fast Fourier transform functions. Also uncomment and correct the IPP
* header inclusion macros.
*
* Do not forget to call the ippInit() function at the start of the
* application, before using this library's functions.
*/
#define R8B_IPP 0
// #include <ippcore.h>
// #include <ipps.h>
#endif // !defined( R8B_IPP )
#if !defined( R8BASSERT )
/**
* Assertion macro used to check for certain run-time conditions. By
* default no action is taken if assertion fails.
*
* @param e Expression to check.
*/
#define R8BASSERT( e )
#endif // !defined( R8BASSERT )
#if !defined( R8BCONSOLE )
/**
* Console output macro, used to output various resampler status strings,
* including filter design parameters, convolver parameters.
*
* @param e Expression to send to the console, usually consists of a
* standard "printf" format string followed by several parameters
* (__VA_ARGS__).
*/
#define R8BCONSOLE( ... )
#endif // !defined( R8BCONSOLE )
#if !defined( R8B_BASECLASS )
/**
* Macro defines the name of the class from which all classes that are
* designed to be created on heap are derived. The default
* r8b::CStdClassAllocator class uses "stdlib" memory allocation
* functions.
*
* The classes that are best placed on stack or as class members are not
* derived from any class.
*/
#define R8B_BASECLASS :: r8b :: CStdClassAllocator
#endif // !defined( R8B_BASECLASS )
#if !defined( R8B_MEMALLOCCLASS )
/**
* Macro defines the name of the class that implements raw memory
* allocation functions, see the r8b::CStdMemAllocator class for details.
*/
#define R8B_MEMALLOCCLASS :: r8b :: CStdMemAllocator
#endif // !defined( R8B_MEMALLOCCLASS )
#if !defined( R8B_FILTER_CACHE_MAX )
/**
* This macro specifies the number of filters kept in the cache at most.
* The actual number can be higher if many different filters are in use at
* the same time.
*/
#define R8B_FILTER_CACHE_MAX 96
#endif // !defined( R8B_FILTER_CACHE_MAX )
#if !defined( R8B_FLTTEST )
/**
* This macro, when equal to 1, enables fractional delay filter bank
* testing: in this mode the filter bank becomes dynamic member of the
* CDSPFracInterpolator object instead of being a global static object.
*/
#define R8B_FLTTEST 0
#endif // !defined( R8B_FLTTEST )
#endif // R8BCONF_INCLUDED
@@ -0,0 +1,284 @@
//$ nobt
//$ nocpp
/**
* @file r8butil.h
*
* @brief The inclusion file with several utility functions.
*
* This file includes several utility functions used by various utility
* programs like "calcErrorTable.cpp".
*
* r8brain-free-src Copyright (c) 2013-2014 Aleksey Vaneev
* See the "License.txt" file for license.
*/
#ifndef R8BUTIL_INCLUDED
#define R8BUTIL_INCLUDED
#include "r8bbase.h"
namespace r8b
{
/**
* @param re Real part of the frequency response.
* @param im Imaginary part of the frequency response.
* @return A magnitude response value converted from the linear scale to the
* logarithmic scale.
*/
inline double convertResponseToLog(const double re, const double im) { return (4.34294481903251828 * log(re * re + im * im + 1e-100)); }
/**
* An utility function that performs frequency response scanning step update
* based on the current magnitude response's slope.
*
* @param[in,out] step The current scanning step. Will be updated on
* function's return. Must be a positive value.
* @param curg Squared magnitude response at the current frequency point.
* @param[in,out] prevg_log Previous magnitude response, log scale. Will be
* updated on function's return.
* @param prec Precision multiplier, affects the size of the step.
* @param maxstep The maximal allowed step.
* @param minstep The minimal allowed step.
*/
inline void updateScanStep(double& step, const double curg, double& prevg_log, const double prec, const double maxstep, const double minstep = 1e-11)
{
double curg_log = 4.34294481903251828 * log(curg + 1e-100);
curg_log += (prevg_log - curg_log) * 0.7;
const double slope = fabs(curg_log - prevg_log);
prevg_log = curg_log;
if (slope > 0.0)
{
step /= prec * slope;
step = max(min(step, maxstep), minstep);
}
}
/**
* Function locates normalized frequency at which the minimum filter gain
* is reached. The scanning is performed from lower (left) to higher
* (right) frequencies, the whole range is scanned.
*
* Function expects that the magnitude response is always reducing from lower
* to high frequencies, starting at "minth".
*
* @param flt Filter response.
* @param fltlen Filter response's length in samples (taps).
* @param[out] ming The current minimal gain (squared). On function's return
* will contain the minimal gain value found (squared).
* @param[out] minth The normalized frequency where the minimal gain is
* currently at. On function's return will point to the normalized frequency
* where the new minimum was found.
* @param thend The ending frequency, inclusive.
*/
inline void findFIRFilterResponseMinLtoR(const double* const flt,
const int fltlen, double& ming, double& minth, const double thend)
{
const double maxstep = minth * 2e-3;
double curth = minth;
double re;
double im;
calcFIRFilterResponse(flt, fltlen, M_PI * curth, re, im);
double prevg_log = convertResponseToLog(re, im);
double step = 1e-11;
while (true)
{
curth += step;
if (curth > thend) { break; }
calcFIRFilterResponse(flt, fltlen, M_PI * curth, re, im);
const double curg = re * re + im * im;
if (curg > ming)
{
ming = curg;
minth = curth;
break;
}
ming = curg;
minth = curth;
updateScanStep(step, curg, prevg_log, 0.31, maxstep);
}
}
/**
* Function locates normalized frequency at which the maximal filter gain
* is reached. The scanning is performed from lower (left) to higher
* (right) frequencies, the whole range is scanned.
*
* Note: this function may "stall" in very rare cases if the magnitude
* response happens to be "saw-tooth" like, requiring a very small stepping to
* be used. If this happens, it may take dozens of seconds to complete.
*
* @param flt Filter response.
* @param fltlen Filter response's length in samples (taps).
* @param[out] maxg The current maximal gain (squared). On function's return
* will contain the maximal gain value (squared).
* @param[out] maxth The normalized frequency where the maximal gain is
* currently at. On function's return will point to the normalized frequency
* where the maximum was reached.
* @param thend The ending frequency, inclusive.
*/
inline void findFIRFilterResponseMaxLtoR(const double* const flt,
const int fltlen, double& maxg, double& maxth, const double thend)
{
const double maxstep = maxth * 1e-4;
double premaxth = maxth;
double premaxg = maxg;
double postmaxth = maxth;
double postmaxg = maxg;
double prevth = maxth;
double prevg = maxg;
double curth = maxth;
double re;
double im;
calcFIRFilterResponse(flt, fltlen, M_PI * curth, re, im);
double prevg_log = convertResponseToLog(re, im);
double step = 1e-11;
bool WasPeak = false;
int AfterPeakCount = 0;
while (true)
{
curth += step;
if (curth > thend) { break; }
calcFIRFilterResponse(flt, fltlen, M_PI * curth, re, im);
const double curg = re * re + im * im;
if (curg > maxg)
{
premaxth = prevth;
premaxg = prevg;
maxg = curg;
maxth = curth;
WasPeak = true;
AfterPeakCount = 0;
}
else if (WasPeak)
{
if (AfterPeakCount == 0)
{
postmaxth = curth;
postmaxg = curg;
}
if (AfterPeakCount == 5)
{
// Perform 2 approximate binary searches.
for (int k = 0; k < 2; ++k)
{
double l = (k == 0 ? premaxth : maxth);
double curgl = (k == 0 ? premaxg : maxg);
double r = (k == 0 ? maxth : postmaxth);
double curgr = (k == 0 ? maxg : postmaxg);
while (true)
{
const double c = (l + r) * 0.5;
calcFIRFilterResponse(flt, fltlen, M_PI * c, re, im);
const double curgTmp = re * re + im * im;
if (curgl > curgr)
{
r = c;
curgr = curgTmp;
}
else
{
l = c;
curgl = curgTmp;
}
if (r - l < 1e-11)
{
if (curgl > curgr)
{
maxth = l;
maxg = curgl;
}
else
{
maxth = r;
maxg = curgr;
}
break;
}
}
}
break;
}
AfterPeakCount++;
}
prevth = curth;
prevg = curg;
updateScanStep(step, curg, prevg_log, 1.0, maxstep);
}
}
/**
* Function locates normalized frequency at which the specified maximum
* filter gain is reached. The scanning is performed from higher (right)
* to lower (left) frequencies, scanning stops when the required gain
* value was crossed. Function uses an extremely efficient binary search and
* thus expects that the magnitude response has the "main lobe" form produced
* by windowing, with a minimal pass-band ripple.
*
* @param flt Filter response.
* @param fltlen Filter response's length in samples (taps).
* @param maxg Maximal gain (squared).
* @param[out] th The current normalized frequency. On function's return will
* point to the normalized frequency where "maxg" is reached.
* @param thend The leftmost frequency to scan, inclusive.
*/
inline void findFIRFilterResponseLevelRtoL(const double* const flt, const int fltlen, const double maxg, double& th, const double thend)
{
// Perform exact binary search.
double l = thend;
double r = th;
while (true)
{
const double c = (l + r) * 0.5;
if (r - l < 1e-14)
{
th = c;
break;
}
double re;
double im;
calcFIRFilterResponse(flt, fltlen, M_PI * c, re, im);
const double curg = re * re + im * im;
if (curg > maxg) { l = c; }
else { r = c; }
}
}
} // namespace r8b
#endif // R8BUTIL_INCLUDED