From 8c194fc29cd2cf67b56460c9e2f37fafffb2b850 Mon Sep 17 00:00:00 2001 From: Lost Robot <34612565+LostRobotMusic@users.noreply.github.com> Date: Wed, 8 Nov 2023 12:53:59 -0800 Subject: [PATCH] Improve Compressor lookahead algorithm (#6953) --- plugins/Compressor/Compressor.cpp | 123 +++++------------- plugins/Compressor/Compressor.h | 14 +- .../Compressor/CompressorControlDialog.cpp | 1 + 3 files changed, 38 insertions(+), 100 deletions(-) diff --git a/plugins/Compressor/Compressor.cpp b/plugins/Compressor/Compressor.cpp index 0fe139420..e59562b02 100755 --- a/plugins/Compressor/Compressor.cpp +++ b/plugins/Compressor/Compressor.cpp @@ -60,9 +60,6 @@ CompressorEffect::CompressorEffect(Model* parent, const Descriptor::SubPluginFea m_yL[0] = m_yL[1] = COMP_NOISE_FLOOR; - m_maxLookaheadVal[0] = 0; - m_maxLookaheadVal[1] = 0; - // 200 ms m_crestTimeConst = exp(-1.f / (0.2f * m_sampleRate)); @@ -183,9 +180,7 @@ void CompressorEffect::resizeRMS() void CompressorEffect::calcLookaheadLength() { - m_lookaheadLength = qMax(m_compressorControls.m_lookaheadLengthModel.value() * 0.001f * m_sampleRate, 1.f); - - m_preLookaheadLength = ceil(m_lookaheadDelayLength - m_lookaheadLength); + m_lookaheadLength = std::ceil((m_compressorControls.m_lookaheadLengthModel.value() / 1000.f) * m_sampleRate); } void CompressorEffect::calcThreshold() @@ -249,17 +244,10 @@ bool CompressorEffect::processAudioBuffer(sampleFrame* buf, const fpp_t frames) m_gainResult[0] = m_gainResult[1] = 1; m_displayPeak[0] = m_displayPeak[1] = COMP_NOISE_FLOOR; m_displayGain[0] = m_displayGain[1] = COMP_NOISE_FLOOR; - std::fill(std::begin(m_lookaheadBuf[0]), std::end(m_lookaheadBuf[0]), 0); - std::fill(std::begin(m_lookaheadBuf[1]), std::end(m_lookaheadBuf[1]), 0); - m_lookaheadBufLoc[0] = 0; - m_lookaheadBufLoc[1] = 0; - std::fill(std::begin(m_preLookaheadBuf[0]), std::end(m_preLookaheadBuf[0]), 0); - std::fill(std::begin(m_preLookaheadBuf[1]), std::end(m_preLookaheadBuf[1]), 0); - m_preLookaheadBufLoc[0] = 0; - m_preLookaheadBufLoc[1] = 0; - std::fill(std::begin(m_inputBuf[0]), std::end(m_inputBuf[0]), 0); - std::fill(std::begin(m_inputBuf[1]), std::end(m_inputBuf[1]), 0); - m_inputBufLoc = 0; + std::fill(std::begin(m_scLookBuf[0]), std::end(m_scLookBuf[0]), COMP_NOISE_FLOOR); + std::fill(std::begin(m_scLookBuf[1]), std::end(m_scLookBuf[1]), COMP_NOISE_FLOOR); + std::fill(std::begin(m_inLookBuf[0]), std::end(m_inLookBuf[0]), 0); + std::fill(std::begin(m_inLookBuf[1]), std::end(m_inLookBuf[1]), 0); m_cleanedBuffers = true; } return false; @@ -318,7 +306,7 @@ bool CompressorEffect::processAudioBuffer(sampleFrame* buf, const fpp_t frames) for (int i = 0; i < 2; i++) { - float inputValue = feedback ? m_prevOut[i] : s[i]; + float inputValue = (feedback && !lookahead) ? m_prevOut[i] : s[i]; // Calculate the crest factor of the audio by diving the peak by the RMS m_crestPeakVal[i] = qMax(qMax(COMP_NOISE_FLOOR, inputValue * inputValue), m_crestTimeConst * m_crestPeakVal[i] + (1 - m_crestTimeConst) * (inputValue * inputValue)); @@ -330,53 +318,6 @@ bool CompressorEffect::processAudioBuffer(sampleFrame* buf, const fpp_t frames) // Grab the peak or RMS value inputValue = qMax(COMP_NOISE_FLOOR, peakmode ? std::abs(inputValue) : std::sqrt(m_rmsVal[i])); - // The following code uses math magic to semi-efficiently - // find the largest value in the lookahead buffer. - // This can probably be improved. - if (lookahead) - { - // Pre-lookahead delay, so the total delay always matches 20 ms - ++m_preLookaheadBufLoc[i]; - if (m_preLookaheadBufLoc[i] >= m_preLookaheadLength) - { - m_preLookaheadBufLoc[i] = 0; - } - const float tempInputValue = inputValue; - inputValue = m_preLookaheadBuf[i][m_preLookaheadBufLoc[i]]; - m_preLookaheadBuf[i][m_preLookaheadBufLoc[i]] = tempInputValue; - - - // Increment ring buffer location - ++m_lookaheadBufLoc[i]; - if (m_lookaheadBufLoc[i] >= m_lookaheadLength) - { - m_lookaheadBufLoc[i] = 0; - } - - m_lookaheadBuf[i][m_lookaheadBufLoc[i]] = inputValue; - - // If the new input value is larger than the stored maximum, - // store that as the maximum - if (inputValue >= m_maxLookaheadVal[i]) - { - m_maxLookaheadVal[i] = inputValue; - m_maxLookaheadTimer[i] = m_lookaheadLength; - } - - // Decrement timer. When the timer reaches 0, that means the - // stored maximum value has left the buffer and a new - // maximum value must be found. - if (--m_maxLookaheadTimer[i] <= 0) - { - m_maxLookaheadTimer[i] = std::distance(std::begin(m_lookaheadBuf[i]), - std::max_element(std::begin(m_lookaheadBuf[i]), std::begin(m_lookaheadBuf[i]) + m_lookaheadLength)); - m_maxLookaheadVal[i] = m_lookaheadBuf[i][m_maxLookaheadTimer[i]]; - m_maxLookaheadTimer[i] = realmod(m_maxLookaheadTimer[i] - m_lookaheadBufLoc[i], m_lookaheadLength); - } - - inputValue = m_maxLookaheadVal[i]; - } - float t = inputValue; if (t > m_yL[i])// Attack phase @@ -415,11 +356,22 @@ bool CompressorEffect::processAudioBuffer(sampleFrame* buf, const fpp_t frames) // Keep it above the noise floor m_yL[i] = qMax(COMP_NOISE_FLOOR, m_yL[i]); + + float scVal = m_yL[i]; + + if (lookahead) + { + const float temp = scVal; + // Lookahead is calculated by picking the largest value between + // the current sidechain signal and the delayed sidechain signal. + scVal = std::max(m_scLookBuf[i][m_lookWrite], m_scLookBuf[i][(m_lookWrite + m_lookBufLength - m_lookaheadLength) % m_lookBufLength]); + m_scLookBuf[i][m_lookWrite] = temp; + } // For the visualizer - m_displayPeak[i] = qMax(m_yL[i], m_displayPeak[i]); + m_displayPeak[i] = qMax(scVal, m_displayPeak[i]); - const float currentPeakDbfs = ampToDbfs(m_yL[i]); + const float currentPeakDbfs = ampToDbfs(scVal); // Now find the gain change that should be applied, // depending on the measured input value. @@ -439,7 +391,7 @@ bool CompressorEffect::processAudioBuffer(sampleFrame* buf, const fpp_t frames) : m_thresholdVal + (currentPeakDbfs - m_thresholdVal) * m_ratioVal; } - m_gainResult[i] = dbfsToAmp(m_gainResult[i]) / m_yL[i]; + m_gainResult[i] = dbfsToAmp(m_gainResult[i]) / scVal; m_gainResult[i] = qMax(m_rangeVal, m_gainResult[i]); } @@ -507,18 +459,10 @@ bool CompressorEffect::processAudioBuffer(sampleFrame* buf, const fpp_t frames) // Delay the signal by 20 ms via ring buffer if lookahead is enabled if (lookahead) { - ++m_inputBufLoc; - if (m_inputBufLoc >= m_lookaheadDelayLength) - { - m_inputBufLoc = 0; - } - - const auto temp = std::array{drySignal[0], drySignal[1]}; - s[0] = m_inputBuf[0][m_inputBufLoc]; - s[1] = m_inputBuf[1][m_inputBufLoc]; - - m_inputBuf[0][m_inputBufLoc] = temp[0]; - m_inputBuf[1][m_inputBufLoc] = temp[1]; + s[0] = m_inLookBuf[0][m_lookWrite]; + s[1] = m_inLookBuf[1][m_lookWrite]; + m_inLookBuf[0][m_lookWrite] = drySignal[0]; + m_inLookBuf[1][m_lookWrite] = drySignal[1]; } else { @@ -573,6 +517,8 @@ bool CompressorEffect::processAudioBuffer(sampleFrame* buf, const fpp_t frames) buf[f][1] = (1 - m_mixVal) * temp2 + m_mixVal * buf[f][1]; outSum += buf[f][0] * buf[f][0] + buf[f][1] * buf[f][1]; + + if (--m_lookWrite < 0) { m_lookWrite = m_lookBufLength - 1; } lInPeak = drySignal[0] > lInPeak ? drySignal[0] : lInPeak; rInPeak = drySignal[1] > rInPeak ? drySignal[1] : rInPeak; @@ -621,16 +567,13 @@ void CompressorEffect::changeSampleRate() // 200 ms m_crestTimeConst = exp(-1.f / (0.2f * m_sampleRate)); - // 20 ms - m_lookaheadDelayLength = 0.02 * m_sampleRate; - m_inputBuf[0].resize(m_lookaheadDelayLength); - m_inputBuf[1].resize(m_lookaheadDelayLength); - - m_lookaheadBuf[0].resize(m_lookaheadDelayLength); - m_lookaheadBuf[1].resize(m_lookaheadDelayLength); - - m_preLookaheadBuf[0].resize(m_lookaheadDelayLength); - m_preLookaheadBuf[1].resize(m_lookaheadDelayLength); + m_lookBufLength = std::ceil((20.f / 1000.f) * m_sampleRate) + 2; + for (int i = 0; i < 2; ++i) + { + m_inLookBuf[i].resize(m_lookBufLength); + m_scLookBuf[i].resize(m_lookBufLength, COMP_NOISE_FLOOR); + } + m_lookWrite = 0; calcThreshold(); calcKnee(); diff --git a/plugins/Compressor/Compressor.h b/plugins/Compressor/Compressor.h index da6ab52bc..3fc90b752 100755 --- a/plugins/Compressor/Compressor.h +++ b/plugins/Compressor/Compressor.h @@ -81,14 +81,10 @@ private: enum class StereoLinkMode { Unlinked, Maximum, Average, Minimum, Blend }; - std::vector m_preLookaheadBuf[2]; - int m_preLookaheadBufLoc[2] = {0}; - - std::vector m_lookaheadBuf[2]; - int m_lookaheadBufLoc[2] = {0}; - - std::vector m_inputBuf[2]; - int m_inputBufLoc = 0; + std::array, 2> m_inLookBuf; + std::array, 2> m_scLookBuf; + int m_lookWrite; + int m_lookBufLength; float m_attCoeff; float m_relCoeff; @@ -99,8 +95,6 @@ private: int m_holdTimer[2] = {0, 0}; int m_lookaheadLength; - int m_lookaheadDelayLength; - int m_preLookaheadLength; float m_thresholdAmpVal; float m_autoMakeupVal; float m_outGainVal; diff --git a/plugins/Compressor/CompressorControlDialog.cpp b/plugins/Compressor/CompressorControlDialog.cpp index 1516456a2..917054c6b 100755 --- a/plugins/Compressor/CompressorControlDialog.cpp +++ b/plugins/Compressor/CompressorControlDialog.cpp @@ -358,6 +358,7 @@ void CompressorControlDialog::lookaheadChanged() { m_lookaheadLengthKnob->setVisible(m_controls->m_lookaheadModel.value()); m_lookaheadEnabledLabel->setVisible(m_controls->m_lookaheadModel.value()); + feedbackButton->setVisible(!m_controls->m_lookaheadModel.value()); }