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