Improve Compressor lookahead algorithm (#6953)
This commit is contained in:
@@ -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();
|
||||
|
||||
@@ -81,14 +81,10 @@ private:
|
||||
|
||||
enum class StereoLinkMode { Unlinked, Maximum, Average, Minimum, Blend };
|
||||
|
||||
std::vector<float> m_preLookaheadBuf[2];
|
||||
int m_preLookaheadBufLoc[2] = {0};
|
||||
|
||||
std::vector<float> m_lookaheadBuf[2];
|
||||
int m_lookaheadBufLoc[2] = {0};
|
||||
|
||||
std::vector<float> m_inputBuf[2];
|
||||
int m_inputBufLoc = 0;
|
||||
std::array<std::vector<float>, 2> m_inLookBuf;
|
||||
std::array<std::vector<float>, 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;
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user