| /* |
| * Copyright (C) 2018 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| #define LOG_TAG "DPFrequency" |
| //#define LOG_NDEBUG 0 |
| |
| #include <log/log.h> |
| #include "DPFrequency.h" |
| #include <algorithm> |
| |
| namespace dp_fx { |
| |
| using Eigen::MatrixXd; |
| #define MAX_BLOCKSIZE 16384 //For this implementation |
| #define MIN_BLOCKSIZE 8 |
| |
| #define CIRCULAR_BUFFER_UPSAMPLE 4 //4 times buffer size |
| |
| static constexpr float MIN_ENVELOPE = 1e-6f; //-120 dB |
| //helper functionS |
| static inline bool isPowerOf2(unsigned long n) { |
| return (n & (n - 1)) == 0; |
| } |
| static constexpr float EPSILON = 0.0000001f; |
| |
| static inline bool isZero(float f) { |
| return fabs(f) <= EPSILON; |
| } |
| |
| template <class T> |
| bool compareEquality(T a, T b) { |
| return (a == b); |
| } |
| |
| template <> bool compareEquality<float>(float a, float b) { |
| return isZero(a - b); |
| } |
| |
| //TODO: avoid using macro for estimating change and assignment. |
| #define IS_CHANGED(c, a, b) { c |= !compareEquality(a,b); \ |
| (a) = (b); } |
| |
| float dBtoLinear(float valueDb) { |
| return pow (10, valueDb / 20.0); |
| } |
| |
| float linearToDb(float value) { |
| return 20 * log10(value); |
| } |
| |
| //ChannelBuffers helper |
| void ChannelBuffer::initBuffers(unsigned int blockSize, unsigned int overlapSize, |
| unsigned int halfFftSize, unsigned int samplingRate, DPBase &dpBase) { |
| ALOGV("ChannelBuffer::initBuffers blockSize %d, overlap %d, halfFft %d", |
| blockSize, overlapSize, halfFftSize); |
| |
| mSamplingRate = samplingRate; |
| mBlockSize = blockSize; |
| |
| cBInput.resize(mBlockSize * CIRCULAR_BUFFER_UPSAMPLE); |
| cBOutput.resize(mBlockSize * CIRCULAR_BUFFER_UPSAMPLE); |
| |
| //fill input with half block size... |
| for (unsigned int k = 0; k < mBlockSize/2; k++) { |
| cBInput.write(0); |
| } |
| |
| //temp vectors |
| input.resize(mBlockSize); |
| output.resize(mBlockSize); |
| outTail.resize(overlapSize); |
| |
| //module vectors |
| mPreEqFactorVector.resize(halfFftSize, 1.0); |
| mPostEqFactorVector.resize(halfFftSize, 1.0); |
| |
| mPreEqBands.resize(dpBase.getPreEqBandCount()); |
| mMbcBands.resize(dpBase.getMbcBandCount()); |
| mPostEqBands.resize(dpBase.getPostEqBandCount()); |
| ALOGV("mPreEqBands %zu, mMbcBands %zu, mPostEqBands %zu",mPreEqBands.size(), |
| mMbcBands.size(), mPostEqBands.size()); |
| |
| DPChannel *pChannel = dpBase.getChannel(0); |
| if (pChannel != nullptr) { |
| mPreEqInUse = pChannel->getPreEq()->isInUse(); |
| mMbcInUse = pChannel->getMbc()->isInUse(); |
| mPostEqInUse = pChannel->getPostEq()->isInUse(); |
| mLimiterInUse = pChannel->getLimiter()->isInUse(); |
| } |
| |
| mLimiterParams.linkGroup = -1; //no group. |
| } |
| |
| void ChannelBuffer::computeBinStartStop(BandParams &bp, size_t binStart) { |
| |
| bp.binStart = binStart; |
| bp.binStop = (int)(0.5 + bp.freqCutoffHz * mBlockSize / mSamplingRate); |
| } |
| |
| //== LinkedLimiters Helper |
| void LinkedLimiters::reset() { |
| mGroupsMap.clear(); |
| } |
| |
| void LinkedLimiters::update(int32_t group, int index) { |
| mGroupsMap[group].push_back(index); |
| } |
| |
| void LinkedLimiters::remove(int index) { |
| //check all groups and if index is found, remove it. |
| //if group is empty afterwards, remove it. |
| for (auto it = mGroupsMap.begin(); it != mGroupsMap.end(); ) { |
| for (auto itIndex = it->second.begin(); itIndex != it->second.end(); ) { |
| if (*itIndex == index) { |
| itIndex = it->second.erase(itIndex); |
| } else { |
| ++itIndex; |
| } |
| } |
| if (it->second.size() == 0) { |
| it = mGroupsMap.erase(it); |
| } else { |
| ++it; |
| } |
| } |
| } |
| |
| //== DPFrequency |
| void DPFrequency::reset() { |
| } |
| |
| size_t DPFrequency::getMinBockSize() { |
| return MIN_BLOCKSIZE; |
| } |
| |
| size_t DPFrequency::getMaxBockSize() { |
| return MAX_BLOCKSIZE; |
| } |
| |
| void DPFrequency::configure(size_t blockSize, size_t overlapSize, |
| size_t samplingRate) { |
| ALOGV("configure"); |
| mBlockSize = blockSize; |
| if (mBlockSize > MAX_BLOCKSIZE) { |
| mBlockSize = MAX_BLOCKSIZE; |
| } else if (mBlockSize < MIN_BLOCKSIZE) { |
| mBlockSize = MIN_BLOCKSIZE; |
| } else { |
| if (!isPowerOf2(blockSize)) { |
| //find next highest power of 2. |
| mBlockSize = 1 << (32 - __builtin_clz(blockSize)); |
| } |
| } |
| |
| mHalfFFTSize = 1 + mBlockSize / 2; //including Nyquist bin |
| mOverlapSize = std::min(overlapSize, mBlockSize/2); |
| |
| int channelcount = getChannelCount(); |
| mSamplingRate = samplingRate; |
| mChannelBuffers.resize(channelcount); |
| for (int ch = 0; ch < channelcount; ch++) { |
| mChannelBuffers[ch].initBuffers(mBlockSize, mOverlapSize, mHalfFFTSize, |
| mSamplingRate, *this); |
| } |
| |
| //effective number of frames processed per second |
| mBlocksPerSecond = (float)mSamplingRate / (mBlockSize - mOverlapSize); |
| |
| fill_window(mVWindow, RDSP_WINDOW_HANNING_FLAT_TOP, mBlockSize, mOverlapSize); |
| |
| //compute window rms for energy compensation |
| mWindowRms = 0; |
| for (size_t i = 0; i < mVWindow.size(); i++) { |
| mWindowRms += mVWindow[i] * mVWindow[i]; |
| } |
| |
| //Making sure window rms is not zero. |
| mWindowRms = std::max(sqrt(mWindowRms / mVWindow.size()), MIN_ENVELOPE); |
| } |
| |
| void DPFrequency::updateParameters(ChannelBuffer &cb, int channelIndex) { |
| DPChannel *pChannel = getChannel(channelIndex); |
| |
| if (pChannel == nullptr) { |
| ALOGE("Error: updateParameters null DPChannel %d", channelIndex); |
| return; |
| } |
| |
| //===Input Gain and preEq |
| { |
| bool changed = false; |
| IS_CHANGED(changed, cb.inputGainDb, pChannel->getInputGain()); |
| //===EqPre |
| if (cb.mPreEqInUse) { |
| DPEq *pPreEq = pChannel->getPreEq(); |
| if (pPreEq == nullptr) { |
| ALOGE("Error: updateParameters null PreEq for channel: %d", channelIndex); |
| return; |
| } |
| IS_CHANGED(changed, cb.mPreEqEnabled, pPreEq->isEnabled()); |
| if (cb.mPreEqEnabled) { |
| for (unsigned int b = 0; b < getPreEqBandCount(); b++) { |
| DPEqBand *pEqBand = pPreEq->getBand(b); |
| if (pEqBand == nullptr) { |
| ALOGE("Error: updateParameters null PreEqBand for band %d", b); |
| return; //failed. |
| } |
| ChannelBuffer::EqBandParams *pEqBandParams = &cb.mPreEqBands[b]; |
| IS_CHANGED(changed, pEqBandParams->enabled, pEqBand->isEnabled()); |
| IS_CHANGED(changed, pEqBandParams->freqCutoffHz, |
| pEqBand->getCutoffFrequency()); |
| IS_CHANGED(changed, pEqBandParams->gainDb, pEqBand->getGain()); |
| } |
| } |
| } |
| |
| if (changed) { |
| float inputGainFactor = dBtoLinear(cb.inputGainDb); |
| if (cb.mPreEqInUse && cb.mPreEqEnabled) { |
| ALOGV("preEq changed, recomputing! channel %d", channelIndex); |
| size_t binNext = 0; |
| for (unsigned int b = 0; b < getPreEqBandCount(); b++) { |
| ChannelBuffer::EqBandParams *pEqBandParams = &cb.mPreEqBands[b]; |
| |
| //frequency translation |
| cb.computeBinStartStop(*pEqBandParams, binNext); |
| binNext = pEqBandParams->binStop + 1; |
| float factor = dBtoLinear(pEqBandParams->gainDb); |
| if (!pEqBandParams->enabled) { |
| factor = inputGainFactor; |
| } |
| for (size_t k = pEqBandParams->binStart; |
| k <= pEqBandParams->binStop && k < mHalfFFTSize; k++) { |
| cb.mPreEqFactorVector[k] = factor * inputGainFactor; |
| } |
| } |
| } else { |
| ALOGV("only input gain changed, recomputing!"); |
| //populate PreEq factor with input gain factor. |
| for (size_t k = 0; k < mHalfFFTSize; k++) { |
| cb.mPreEqFactorVector[k] = inputGainFactor; |
| } |
| } |
| } |
| } //inputGain and preEq |
| |
| //===EqPost |
| if (cb.mPostEqInUse) { |
| bool changed = false; |
| |
| DPEq *pPostEq = pChannel->getPostEq(); |
| if (pPostEq == nullptr) { |
| ALOGE("Error: updateParameters null postEq for channel: %d", channelIndex); |
| return; //failed. |
| } |
| IS_CHANGED(changed, cb.mPostEqEnabled, pPostEq->isEnabled()); |
| if (cb.mPostEqEnabled) { |
| for (unsigned int b = 0; b < getPostEqBandCount(); b++) { |
| DPEqBand *pEqBand = pPostEq->getBand(b); |
| if (pEqBand == nullptr) { |
| ALOGE("Error: updateParameters PostEqBand NULL for band %d", b); |
| return; //failed. |
| } |
| ChannelBuffer::EqBandParams *pEqBandParams = &cb.mPostEqBands[b]; |
| IS_CHANGED(changed, pEqBandParams->enabled, pEqBand->isEnabled()); |
| IS_CHANGED(changed, pEqBandParams->freqCutoffHz, |
| pEqBand->getCutoffFrequency()); |
| IS_CHANGED(changed, pEqBandParams->gainDb, pEqBand->getGain()); |
| } |
| if (changed) { |
| ALOGV("postEq changed, recomputing! channel %d", channelIndex); |
| size_t binNext = 0; |
| for (unsigned int b = 0; b < getPostEqBandCount(); b++) { |
| ChannelBuffer::EqBandParams *pEqBandParams = &cb.mPostEqBands[b]; |
| |
| //frequency translation |
| cb.computeBinStartStop(*pEqBandParams, binNext); |
| binNext = pEqBandParams->binStop + 1; |
| float factor = dBtoLinear(pEqBandParams->gainDb); |
| if (!pEqBandParams->enabled) { |
| factor = 1.0; |
| } |
| for (size_t k = pEqBandParams->binStart; |
| k <= pEqBandParams->binStop && k < mHalfFFTSize; k++) { |
| cb.mPostEqFactorVector[k] = factor; |
| } |
| } |
| } |
| } //enabled |
| } |
| |
| //===MBC |
| if (cb.mMbcInUse) { |
| DPMbc *pMbc = pChannel->getMbc(); |
| if (pMbc == nullptr) { |
| ALOGE("Error: updateParameters Mbc NULL for channel: %d", channelIndex); |
| return; |
| } |
| cb.mMbcEnabled = pMbc->isEnabled(); |
| if (cb.mMbcEnabled) { |
| bool changed = false; |
| for (unsigned int b = 0; b < getMbcBandCount(); b++) { |
| DPMbcBand *pMbcBand = pMbc->getBand(b); |
| if (pMbcBand == nullptr) { |
| ALOGE("Error: updateParameters MbcBand NULL for band %d", b); |
| return; //failed. |
| } |
| ChannelBuffer::MbcBandParams *pMbcBandParams = &cb.mMbcBands[b]; |
| pMbcBandParams->enabled = pMbcBand->isEnabled(); |
| IS_CHANGED(changed, pMbcBandParams->freqCutoffHz, |
| pMbcBand->getCutoffFrequency()); |
| |
| pMbcBandParams->gainPreDb = pMbcBand->getPreGain(); |
| pMbcBandParams->gainPostDb = pMbcBand->getPostGain(); |
| pMbcBandParams->attackTimeMs = pMbcBand->getAttackTime(); |
| pMbcBandParams->releaseTimeMs = pMbcBand->getReleaseTime(); |
| pMbcBandParams->ratio = pMbcBand->getRatio(); |
| pMbcBandParams->thresholdDb = pMbcBand->getThreshold(); |
| pMbcBandParams->kneeWidthDb = pMbcBand->getKneeWidth(); |
| pMbcBandParams->noiseGateThresholdDb = pMbcBand->getNoiseGateThreshold(); |
| pMbcBandParams->expanderRatio = pMbcBand->getExpanderRatio(); |
| |
| } |
| |
| if (changed) { |
| ALOGV("mbc changed, recomputing! channel %d", channelIndex); |
| size_t binNext= 0; |
| for (unsigned int b = 0; b < getMbcBandCount(); b++) { |
| ChannelBuffer::MbcBandParams *pMbcBandParams = &cb.mMbcBands[b]; |
| |
| pMbcBandParams->previousEnvelope = 0; |
| |
| //frequency translation |
| cb.computeBinStartStop(*pMbcBandParams, binNext); |
| binNext = pMbcBandParams->binStop + 1; |
| } |
| } |
| } |
| } |
| |
| //===Limiter |
| if (cb.mLimiterInUse) { |
| bool changed = false; |
| DPLimiter *pLimiter = pChannel->getLimiter(); |
| if (pLimiter == nullptr) { |
| ALOGE("Error: updateParameters Limiter NULL for channel: %d", channelIndex); |
| return; |
| } |
| cb.mLimiterEnabled = pLimiter->isEnabled(); |
| if (cb.mLimiterEnabled) { |
| IS_CHANGED(changed, cb.mLimiterParams.linkGroup , |
| (int32_t)pLimiter->getLinkGroup()); |
| cb.mLimiterParams.attackTimeMs = pLimiter->getAttackTime(); |
| cb.mLimiterParams.releaseTimeMs = pLimiter->getReleaseTime(); |
| cb.mLimiterParams.ratio = pLimiter->getRatio(); |
| cb.mLimiterParams.thresholdDb = pLimiter->getThreshold(); |
| cb.mLimiterParams.postGainDb = pLimiter->getPostGain(); |
| } |
| |
| if (changed) { |
| ALOGV("limiter changed, recomputing linkGroups for %d", channelIndex); |
| mLinkedLimiters.remove(channelIndex); //in case it was already there. |
| mLinkedLimiters.update(cb.mLimiterParams.linkGroup, channelIndex); |
| } |
| } |
| } |
| |
| size_t DPFrequency::processSamples(const float *in, float *out, size_t samples) { |
| const float *pIn = in; |
| float *pOut = out; |
| |
| int channelCount = mChannelBuffers.size(); |
| if (channelCount < 1) { |
| ALOGW("warning: no Channels ready for processing"); |
| return 0; |
| } |
| |
| //**Check if parameters have changed and update |
| for (int ch = 0; ch < channelCount; ch++) { |
| updateParameters(mChannelBuffers[ch], ch); |
| } |
| |
| //**separate into channels |
| for (size_t k = 0; k < samples; k += channelCount) { |
| for (int ch = 0; ch < channelCount; ch++) { |
| mChannelBuffers[ch].cBInput.write(*pIn++); |
| } |
| } |
| |
| //**process all channelBuffers |
| processChannelBuffers(mChannelBuffers); |
| |
| //** estimate how much data is available in ALL channels |
| size_t available = mChannelBuffers[0].cBOutput.availableToRead(); |
| for (int ch = 1; ch < channelCount; ch++) { |
| available = std::min(available, mChannelBuffers[ch].cBOutput.availableToRead()); |
| } |
| |
| //** make sure to output just what the buffer can handle |
| if (available > samples/channelCount) { |
| available = samples/channelCount; |
| } |
| |
| //**Prepend zeroes if necessary |
| size_t fill = samples - (channelCount * available); |
| for (size_t k = 0; k < fill; k++) { |
| *pOut++ = 0; |
| } |
| |
| //**interleave channels |
| for (size_t k = 0; k < available; k++) { |
| for (int ch = 0; ch < channelCount; ch++) { |
| *pOut++ = mChannelBuffers[ch].cBOutput.read(); |
| } |
| } |
| |
| return samples; |
| } |
| |
| size_t DPFrequency::processChannelBuffers(CBufferVector &channelBuffers) { |
| const int channelCount = channelBuffers.size(); |
| size_t processedSamples = 0; |
| size_t processFrames = mBlockSize - mOverlapSize; |
| |
| size_t available = channelBuffers[0].cBInput.availableToRead(); |
| for (int ch = 1; ch < channelCount; ch++) { |
| available = std::min(available, channelBuffers[ch].cBInput.availableToRead()); |
| } |
| |
| while (available >= processFrames) { |
| //First pass |
| for (int ch = 0; ch < channelCount; ch++) { |
| ChannelBuffer * pCb = &channelBuffers[ch]; |
| //move tail of previous |
| std::copy(pCb->input.begin() + processFrames, |
| pCb->input.end(), |
| pCb->input.begin()); |
| |
| //read new available data |
| for (unsigned int k = 0; k < processFrames; k++) { |
| pCb->input[mOverlapSize + k] = pCb->cBInput.read(); |
| } |
| //first stages: fft, preEq, mbc, postEq and start of Limiter |
| processedSamples += processFirstStages(*pCb); |
| } |
| |
| //**compute linked limiters and update levels if needed |
| processLinkedLimiters(channelBuffers); |
| |
| //final pass. |
| for (int ch = 0; ch < channelCount; ch++) { |
| ChannelBuffer * pCb = &channelBuffers[ch]; |
| |
| //linked limiter and ifft |
| processLastStages(*pCb); |
| |
| //mix tail (and capture new tail |
| for (unsigned int k = 0; k < mOverlapSize; k++) { |
| pCb->output[k] += pCb->outTail[k]; |
| pCb->outTail[k] = pCb->output[processFrames + k]; //new tail |
| } |
| |
| //output data |
| for (unsigned int k = 0; k < processFrames; k++) { |
| pCb->cBOutput.write(pCb->output[k]); |
| } |
| } |
| available -= processFrames; |
| } |
| return processedSamples; |
| } |
| size_t DPFrequency::processFirstStages(ChannelBuffer &cb) { |
| |
| //##apply window |
| Eigen::Map<Eigen::VectorXf> eWindow(&mVWindow[0], mVWindow.size()); |
| Eigen::Map<Eigen::VectorXf> eInput(&cb.input[0], cb.input.size()); |
| |
| Eigen::VectorXf eWin = eInput.cwiseProduct(eWindow); //apply window |
| |
| //##fft |
| //Note: we are using eigen with the default scaling, which ensures that |
| // IFFT( FFT(x) ) = x. |
| // TODO: optimize by using the noscale option, and compensate with dB scale offsets |
| mFftServer.fwd(cb.complexTemp, eWin); |
| |
| size_t cSize = cb.complexTemp.size(); |
| size_t maxBin = std::min(cSize/2, mHalfFFTSize); |
| |
| //== EqPre (always runs) |
| for (size_t k = 0; k < maxBin; k++) { |
| cb.complexTemp[k] *= cb.mPreEqFactorVector[k]; |
| } |
| |
| //== MBC |
| if (cb.mMbcInUse && cb.mMbcEnabled) { |
| for (size_t band = 0; band < cb.mMbcBands.size(); band++) { |
| ChannelBuffer::MbcBandParams *pMbcBandParams = &cb.mMbcBands[band]; |
| float fEnergySum = 0; |
| |
| //apply pre gain. |
| float preGainFactor = dBtoLinear(pMbcBandParams->gainPreDb); |
| float preGainSquared = preGainFactor * preGainFactor; |
| |
| for (size_t k = pMbcBandParams->binStart; k <= pMbcBandParams->binStop; k++) { |
| fEnergySum += std::norm(cb.complexTemp[k]) * preGainSquared; //mag squared |
| } |
| |
| //Eigen FFT is full spectrum, even if the source was real data. |
| // Each half spectrum has half the energy. This is taken into account with the * 2 |
| // factor in the energy computations. |
| // energy = sqrt(sum_components_squared) number_points |
| // in here, the fEnergySum is duplicated to account for the second half spectrum, |
| // and the windowRms is used to normalize by the expected energy reduction |
| // caused by the window used (expected for steady state signals) |
| fEnergySum = sqrt(fEnergySum * 2) / (mBlockSize * mWindowRms); |
| |
| // updates computed per frame advance. |
| float fTheta = 0.0; |
| float fFAttSec = pMbcBandParams->attackTimeMs / 1000; //in seconds |
| float fFRelSec = pMbcBandParams->releaseTimeMs / 1000; //in seconds |
| |
| if (fEnergySum > pMbcBandParams->previousEnvelope) { |
| fTheta = exp(-1.0 / (fFAttSec * mBlocksPerSecond)); |
| } else { |
| fTheta = exp(-1.0 / (fFRelSec * mBlocksPerSecond)); |
| } |
| |
| |
| float fEnv = (1.0 - fTheta) * fEnergySum + fTheta * pMbcBandParams->previousEnvelope; |
| //preserve for next iteration |
| pMbcBandParams->previousEnvelope = fEnv; |
| |
| float fThreshold = dBtoLinear(pMbcBandParams->thresholdDb); |
| float fNoiseGateThreshold = dBtoLinear(pMbcBandParams->noiseGateThresholdDb); |
| |
| float fNewFactor = 1.0; |
| |
| if (fEnv > fThreshold) { |
| float fDbAbove = linearToDb(fThreshold / fEnv); |
| float fDbTarget = fDbAbove / pMbcBandParams->ratio; |
| float fDbChange = fDbAbove - fDbTarget; |
| fNewFactor = dBtoLinear(fDbChange); |
| } else if (fEnv < fNoiseGateThreshold) { |
| if (fEnv < MIN_ENVELOPE) { |
| fEnv = MIN_ENVELOPE; |
| } |
| float fDbBelow = linearToDb(fNoiseGateThreshold / fEnv); |
| float fDbTarget = fDbBelow / pMbcBandParams->expanderRatio; |
| float fDbChange = fDbBelow - fDbTarget; |
| fNewFactor = dBtoLinear(fDbChange); |
| } |
| |
| //apply post gain. |
| fNewFactor *= dBtoLinear(pMbcBandParams->gainPostDb); |
| |
| if (fNewFactor < 0) { |
| fNewFactor = 0; |
| } |
| |
| //apply to this band |
| for (size_t k = pMbcBandParams->binStart; k <= pMbcBandParams->binStop; k++) { |
| cb.complexTemp[k] *= fNewFactor; |
| } |
| |
| } //end per band process |
| |
| } //end MBC |
| |
| //== EqPost |
| if (cb.mPostEqInUse && cb.mPostEqEnabled) { |
| for (size_t k = 0; k < maxBin; k++) { |
| cb.complexTemp[k] *= cb.mPostEqFactorVector[k]; |
| } |
| } |
| |
| //== Limiter. First Pass |
| if (cb.mLimiterInUse && cb.mLimiterEnabled) { |
| float fEnergySum = 0; |
| for (size_t k = 0; k < maxBin; k++) { |
| fEnergySum += std::norm(cb.complexTemp[k]); |
| } |
| |
| //see explanation above for energy computation logic |
| fEnergySum = sqrt(fEnergySum * 2) / (mBlockSize * mWindowRms); |
| float fTheta = 0.0; |
| float fFAttSec = cb.mLimiterParams.attackTimeMs / 1000; //in seconds |
| float fFRelSec = cb.mLimiterParams.releaseTimeMs / 1000; //in seconds |
| |
| if (fEnergySum > cb.mLimiterParams.previousEnvelope) { |
| fTheta = exp(-1.0 / (fFAttSec * mBlocksPerSecond)); |
| } else { |
| fTheta = exp(-1.0 / (fFRelSec * mBlocksPerSecond)); |
| } |
| |
| float fEnv = (1.0 - fTheta) * fEnergySum + fTheta * cb.mLimiterParams.previousEnvelope; |
| //preserve for next iteration |
| cb.mLimiterParams.previousEnvelope = fEnv; |
| |
| float fThreshold = dBtoLinear(cb.mLimiterParams.thresholdDb); |
| |
| float fNewFactor = 1.0; |
| |
| if (fEnv > fThreshold) { |
| float fDbAbove = linearToDb(fThreshold / fEnv); |
| float fDbTarget = fDbAbove / cb.mLimiterParams.ratio; |
| float fDbChange = fDbAbove - fDbTarget; |
| fNewFactor = dBtoLinear(fDbChange); |
| } |
| |
| if (fNewFactor < 0) { |
| fNewFactor = 0; |
| } |
| |
| cb.mLimiterParams.newFactor = fNewFactor; |
| |
| } //end Limiter |
| return mBlockSize; |
| } |
| |
| void DPFrequency::processLinkedLimiters(CBufferVector &channelBuffers) { |
| |
| const int channelCount = channelBuffers.size(); |
| for (auto &groupPair : mLinkedLimiters.mGroupsMap) { |
| float minFactor = 1.0; |
| //estimate minfactor for all linked |
| for(int index : groupPair.second) { |
| if (index >= 0 && index < channelCount) { |
| minFactor = std::min(channelBuffers[index].mLimiterParams.newFactor, minFactor); |
| } |
| } |
| //apply minFactor |
| for(int index : groupPair.second) { |
| if (index >= 0 && index < channelCount) { |
| channelBuffers[index].mLimiterParams.linkFactor = minFactor; |
| } |
| } |
| } |
| } |
| |
| size_t DPFrequency::processLastStages(ChannelBuffer &cb) { |
| //== Limiter. last Pass |
| if (cb.mLimiterInUse && cb.mLimiterEnabled) { |
| const size_t cSize = cb.complexTemp.size(); |
| const size_t maxBin = std::min(cSize/2, mHalfFFTSize); |
| //compute factor, with post-gain |
| float factor = cb.mLimiterParams.linkFactor * dBtoLinear(cb.mLimiterParams.postGainDb); |
| |
| //apply to all |
| for (size_t k = 0; k < maxBin; k++) { |
| cb.complexTemp[k] *= factor; |
| } |
| } |
| |
| //##ifft directly to output. |
| Eigen::Map<Eigen::VectorXf> eOutput(&cb.output[0], cb.output.size()); |
| mFftServer.inv(eOutput, cb.complexTemp); |
| return mBlockSize; |
| } |
| |
| } //namespace dp_fx |