Add Compressor effect to LMMS (#5458)

* Add Compressor effect
This commit is contained in:
Lost Robot
2021-03-10 23:17:32 -07:00
committed by GitHub
parent 00ac4f58f5
commit 459948f8cd
45 changed files with 2090 additions and 0 deletions

View File

@@ -30,6 +30,7 @@ SET(LMMS_PLUGIN_LIST
carlabase
carlapatchbay
carlarack
Compressor
CrossoverEQ
Delay
DualFilter

View File

@@ -920,6 +920,26 @@ NesInstrumentView Knob {
qproperty-lineWidth: 2;
}
CompressorControlDialog {
qproperty-inVolAreaColor: rgba(209, 216, 228, 17);
qproperty-inVolColor: rgba(209, 216, 228, 100);
qproperty-outVolAreaColor: rgba(209, 216, 228, 30);
qproperty-outVolColor: rgba(209, 216, 228, 240);
qproperty-gainReductionColor: rgba(180, 100, 100, 210);
qproperty-kneeColor: rgba(39, 171, 95, 255);
qproperty-kneeColor2: rgba(9, 171, 160, 255);
qproperty-threshColor: rgba(39, 171, 95, 100);
qproperty-textColor: rgba(209, 216, 228, 50);
qproperty-graphColor: rgba(209, 216, 228, 50);
qproperty-resetColor: rgba(200, 100, 15, 200);
}
CompressorControlDialog Knob {
color: #2fcc71;
qproperty-outerColor: #2fcc71;
qproperty-lineWidth: 2;
}
/* palette information */
LmmsPalette {

View File

@@ -964,6 +964,26 @@ NesInstrumentView Knob {
qproperty-lineWidth: 2;
}
CompressorControlDialog {
qproperty-inVolAreaColor: rgba(209, 216, 228, 17);
qproperty-inVolColor: rgba(209, 216, 228, 100);
qproperty-outVolAreaColor: rgba(209, 216, 228, 30);
qproperty-outVolColor: rgba(209, 216, 228, 240);
qproperty-gainReductionColor: rgba(180, 100, 100, 210);
qproperty-kneeColor: rgba(39, 171, 95, 255);
qproperty-kneeColor2: rgba(9, 171, 160, 255);
qproperty-threshColor: rgba(39, 171, 95, 100);
qproperty-textColor: rgba(209, 216, 228, 50);
qproperty-graphColor: rgba(209, 216, 228, 50);
qproperty-resetColor: rgba(200, 100, 15, 200);
}
CompressorControlDialog Knob {
color: #2fcc71;
qproperty-outerColor: #2fcc71;
qproperty-lineWidth: 2;
}
/* palette information */
LmmsPalette {

View File

@@ -0,0 +1,3 @@
INCLUDE(BuildPlugin)
BUILD_PLUGIN(compressor Compressor.cpp CompressorControls.cpp CompressorControlDialog.cpp MOCFILES Compressor.h CompressorControls.h CompressorControlDialog.h ../Eq/EqFader.h EMBEDDED_RESOURCES "${CMAKE_CURRENT_SOURCE_DIR}/*.png")

664
plugins/Compressor/Compressor.cpp Executable file
View File

@@ -0,0 +1,664 @@
/*
* Compressor.cpp
*
* Copyright (c) 2020 Lost Robot <r94231@gmail.com>
*
* This file is part of LMMS - https://lmms.io
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public
* License along with this program (see COPYING); if not, write to the
* Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301 USA.
*
*/
#include "Compressor.h"
#include "embed.h"
#include "interpolation.h"
#include "lmms_math.h"
#include "plugin_export.h"
extern "C"
{
Plugin::Descriptor PLUGIN_EXPORT compressor_plugin_descriptor =
{
STRINGIFY(PLUGIN_NAME),
"Compressor",
QT_TRANSLATE_NOOP("PluginBrowser", "A dynamic range compressor."),
"Lost Robot <r94231@gmail.com>",
0x0100,
Plugin::Effect,
new PluginPixmapLoader("logo"),
NULL,
NULL
} ;
}
CompressorEffect::CompressorEffect(Model* parent, const Descriptor::SubPluginFeatures::Key* key) :
Effect(&compressor_plugin_descriptor, parent, key),
m_compressorControls(this)
{
m_sampleRate = Engine::mixer()->processingSampleRate();
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));
connect(&m_compressorControls.m_attackModel, SIGNAL(dataChanged()), this, SLOT(calcAttack()), Qt::DirectConnection);
connect(&m_compressorControls.m_releaseModel, SIGNAL(dataChanged()), this, SLOT(calcRelease()), Qt::DirectConnection);
connect(&m_compressorControls.m_holdModel, SIGNAL(dataChanged()), this, SLOT(calcHold()), Qt::DirectConnection);
connect(&m_compressorControls.m_ratioModel, SIGNAL(dataChanged()), this, SLOT(calcRatio()), Qt::DirectConnection);
connect(&m_compressorControls.m_rangeModel, SIGNAL(dataChanged()), this, SLOT(calcRange()), Qt::DirectConnection);
connect(&m_compressorControls.m_rmsModel, SIGNAL(dataChanged()), this, SLOT(resizeRMS()), Qt::DirectConnection);
connect(&m_compressorControls.m_lookaheadLengthModel, SIGNAL(dataChanged()), this, SLOT(calcLookaheadLength()), Qt::DirectConnection);
connect(&m_compressorControls.m_thresholdModel, SIGNAL(dataChanged()), this, SLOT(calcThreshold()), Qt::DirectConnection);
connect(&m_compressorControls.m_kneeModel, SIGNAL(dataChanged()), this, SLOT(calcKnee()), Qt::DirectConnection);
connect(&m_compressorControls.m_outGainModel, SIGNAL(dataChanged()), this, SLOT(calcOutGain()), Qt::DirectConnection);
connect(&m_compressorControls.m_inGainModel, SIGNAL(dataChanged()), this, SLOT(calcInGain()), Qt::DirectConnection);
connect(&m_compressorControls.m_tiltModel, SIGNAL(dataChanged()), this, SLOT(calcTiltCoeffs()), Qt::DirectConnection);
connect(&m_compressorControls.m_tiltFreqModel, SIGNAL(dataChanged()), this, SLOT(calcTiltCoeffs()), Qt::DirectConnection);
connect(&m_compressorControls.m_limiterModel, SIGNAL(dataChanged()), this, SLOT(redrawKnee()), Qt::DirectConnection);
connect(&m_compressorControls.m_mixModel, SIGNAL(dataChanged()), this, SLOT(calcMix()), Qt::DirectConnection);
connect(&m_compressorControls.m_autoAttackModel, SIGNAL(dataChanged()), this, SLOT(calcAutoAttack()), Qt::DirectConnection);
connect(&m_compressorControls.m_autoReleaseModel, SIGNAL(dataChanged()), this, SLOT(calcAutoRelease()), Qt::DirectConnection);
connect(&m_compressorControls.m_thresholdModel, SIGNAL(dataChanged()), this, SLOT(calcAutoMakeup()), Qt::DirectConnection);
connect(&m_compressorControls.m_ratioModel, SIGNAL(dataChanged()), this, SLOT(calcAutoMakeup()), Qt::DirectConnection);
connect(&m_compressorControls.m_kneeModel, SIGNAL(dataChanged()), this, SLOT(calcAutoMakeup()), Qt::DirectConnection);
connect(&m_compressorControls.m_autoMakeupModel, SIGNAL(dataChanged()), this, SLOT(calcAutoMakeup()), Qt::DirectConnection);
connect(Engine::mixer(), SIGNAL(sampleRateChanged()), this, SLOT(changeSampleRate()));
changeSampleRate();
}
CompressorEffect::~CompressorEffect()
{
}
float CompressorEffect::msToCoeff(float ms)
{
// Convert time in milliseconds to applicable lowpass coefficient
return exp(m_coeffPrecalc / ms);
}
void CompressorEffect::calcAutoMakeup()
{
// Formulas using the compressor's Threshold, Ratio, and Knee values to estimate a good makeup gain value
float tempGainResult;
if (-m_thresholdVal < m_kneeVal)
{
const float temp = -m_thresholdVal + m_kneeVal;
tempGainResult = ((m_compressorControls.m_limiterModel.value() ? 0 : m_ratioVal) - 1) * temp * temp / (4 * m_kneeVal);
}
else// Above knee
{
tempGainResult = m_compressorControls.m_limiterModel.value()
? m_thresholdVal
: m_thresholdVal - m_thresholdVal * m_ratioVal;
}
m_autoMakeupVal = 1.f / dbfsToAmp(tempGainResult);
}
void CompressorEffect::calcAttack()
{
m_attCoeff = msToCoeff(m_compressorControls.m_attackModel.value());
}
void CompressorEffect::calcRelease()
{
m_relCoeff = msToCoeff(m_compressorControls.m_releaseModel.value());
}
void CompressorEffect::calcAutoAttack()
{
m_autoAttVal = m_compressorControls.m_autoAttackModel.value() * 0.01f;
}
void CompressorEffect::calcAutoRelease()
{
m_autoRelVal = m_compressorControls.m_autoReleaseModel.value() * 0.01f;
}
void CompressorEffect::calcHold()
{
m_holdLength = m_compressorControls.m_holdModel.value() * 0.001f * m_sampleRate;
m_holdTimer[0] = 0;
m_holdTimer[1] = 0;
}
void CompressorEffect::calcOutGain()
{
// 0.999 is needed to keep the values from crossing the threshold all the time
// (most commonly for limiters specifically), and is kept across all modes for consistency.
m_outGainVal = dbfsToAmp(m_compressorControls.m_outGainModel.value()) * 0.999;
}
void CompressorEffect::calcRatio()
{
m_ratioVal = 1.f / m_compressorControls.m_ratioModel.value();
m_redrawKnee = true;
}
void CompressorEffect::calcRange()
{
// Range is inactive when turned all the way down
m_rangeVal = (m_compressorControls.m_rangeModel.value() > m_compressorControls.m_rangeModel.minValue())
? dbfsToAmp(m_compressorControls.m_rangeModel.value())
: 0;
}
void CompressorEffect::resizeRMS()
{
m_rmsTimeConst = exp(-1.f / (m_compressorControls.m_rmsModel.value() * 0.001f * m_sampleRate));
}
void CompressorEffect::calcLookaheadLength()
{
m_lookaheadLength = qMax(m_compressorControls.m_lookaheadLengthModel.value() * 0.001f * m_sampleRate, 1.f);
m_preLookaheadLength = ceil(m_lookaheadDelayLength - m_lookaheadLength);
}
void CompressorEffect::calcThreshold()
{
m_thresholdVal = m_compressorControls.m_thresholdModel.value();
m_thresholdAmpVal = dbfsToAmp(m_thresholdVal);
m_redrawKnee = true;
m_redrawThreshold = true;
}
void CompressorEffect::calcKnee()
{
m_kneeVal = m_compressorControls.m_kneeModel.value() * 0.5f;
m_redrawKnee = true;
}
void CompressorEffect::calcInGain()
{
m_inGainVal = dbfsToAmp(m_compressorControls.m_inGainModel.value());
}
void CompressorEffect::redrawKnee()
{
m_redrawKnee = true;
}
void CompressorEffect::calcTiltCoeffs()
{
m_tiltVal = m_compressorControls.m_tiltModel.value();
const float amp = 6 / log(2);
const float gfactor = 5;
const float g1 = m_tiltVal > 0 ? -gfactor * m_tiltVal : -m_tiltVal;
const float g2 = m_tiltVal > 0 ? m_tiltVal : gfactor * m_tiltVal;
m_lgain = exp(g1 / amp) - 1;
m_hgain = exp(g2 / amp) - 1;
const float omega = 2 * F_PI * m_compressorControls.m_tiltFreqModel.value();
const float n = 1 / (m_sampleRate * 3 + omega);
m_a0 = 2 * omega * n;
m_b1 = (m_sampleRate * 3 - omega) * n;
}
void CompressorEffect::calcMix()
{
m_mixVal = m_compressorControls.m_mixModel.value() * 0.01;
}
bool CompressorEffect::processAudioBuffer(sampleFrame* buf, const fpp_t frames)
{
if (!isEnabled() || !isRunning())
{
// Clear lookahead buffers and other values when needed
if (!m_cleanedBuffers)
{
m_yL[0] = m_yL[1] = COMP_NOISE_FLOOR;
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;
m_cleanedBuffers = true;
}
return false;
}
else
{
m_cleanedBuffers = false;
}
float outSum = 0.0;
const float d = dryLevel();
const float w = wetLevel();
float lOutPeak = 0.0;
float rOutPeak = 0.0;
float lInPeak = 0.0;
float rInPeak = 0.0;
const bool midside = m_compressorControls.m_midsideModel.value();
const bool peakmode = m_compressorControls.m_peakmodeModel.value();
const float inBalance = m_compressorControls.m_inBalanceModel.value();
const float outBalance = m_compressorControls.m_outBalanceModel.value();
const bool limiter = m_compressorControls.m_limiterModel.value();
const float blend = m_compressorControls.m_blendModel.value();
const float stereoBalance = m_compressorControls.m_stereoBalanceModel.value();
const bool autoMakeup = m_compressorControls.m_autoMakeupModel.value();
const int stereoLink = m_compressorControls.m_stereoLinkModel.value();
const bool audition = m_compressorControls.m_auditionModel.value();
const bool feedback = m_compressorControls.m_feedbackModel.value();
const bool lookahead = m_compressorControls.m_lookaheadModel.value();
for(fpp_t f = 0; f < frames; ++f)
{
sample_t drySignal[2] = {buf[f][0], buf[f][1]};
sample_t s[2] = {drySignal[0] * m_inGainVal, drySignal[1] * m_inGainVal};
// Calculate tilt filters, to bias the sidechain to the low or high frequencies
if (m_tiltVal)
{
calcTiltFilter(s[0], s[0], 0);
calcTiltFilter(s[1], s[1], 1);
}
if (midside)// Convert left/right to mid/side
{
const float temp = s[0];
s[0] = (temp + s[1]) * 0.5;
s[1] = temp - s[1];
}
s[0] *= inBalance > 0 ? 1 - inBalance : 1;
s[1] *= inBalance < 0 ? 1 + inBalance : 1;
m_gainResult[0] = 0;
m_gainResult[1] = 0;
for (int i = 0; i < 2; i++)
{
float inputValue = feedback ? m_prevOut[i] : s[i];
// Calculate the crest factor of the audio by diving the peak by the RMS
m_crestPeakVal[i] = qMax(inputValue * inputValue, m_crestTimeConst * m_crestPeakVal[i] + (1 - m_crestTimeConst) * (inputValue * inputValue));
m_crestRmsVal[i] = m_crestTimeConst * m_crestRmsVal[i] + ((1 - m_crestTimeConst) * (inputValue * inputValue));
m_crestFactorVal[i] = m_crestPeakVal[i] / m_crestRmsVal[i];
m_rmsVal[i] = m_rmsTimeConst * m_rmsVal[i] + ((1 - m_rmsTimeConst) * (inputValue * inputValue));
// Grab the peak or RMS value
inputValue = qMax(COMP_NOISE_FLOOR, peakmode ? abs(inputValue) : 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
{
// We want the "resting value" of our crest factor to be with a sine wave,
// which with this variable has a value of 2.
// So, we pull this value down to 0, and multiply it by the percentage of
// automatic attack control that is applied. We then add 2 back to it.
float crestFactorValTemp = ((m_crestFactorVal[i] - 2.f) * m_autoAttVal) + 2.f;
// Calculate attack value depending on crest factor
const float att = m_autoAttVal
? msToCoeff(2.f * m_compressorControls.m_attackModel.value() / (crestFactorValTemp))
: m_attCoeff;
m_yL[i] = m_yL[i] * att + (1 - att) * t;
m_holdTimer[i] = m_holdLength;// Reset hold timer
}
else// Release phase
{
float crestFactorValTemp = ((m_crestFactorVal[i] - 2.f) * m_autoRelVal) + 2.f;
const float rel = m_autoRelVal
? msToCoeff(2.f * m_compressorControls.m_releaseModel.value() / (crestFactorValTemp))
: m_relCoeff;
if (m_holdTimer[i])// Don't change peak if hold is being applied
{
--m_holdTimer[i];
}
else
{
m_yL[i] = m_yL[i] * rel + (1 - rel) * t;
}
}
// Keep it above the noise floor
m_yL[i] = qMax(COMP_NOISE_FLOOR, m_yL[i]);
// For the visualizer
m_displayPeak[i] = qMax(m_yL[i], m_displayPeak[i]);
const float currentPeakDbfs = ampToDbfs(m_yL[i]);
// Now find the gain change that should be applied,
// depending on the measured input value.
if (currentPeakDbfs - m_thresholdVal < -m_kneeVal)// Below knee
{
m_gainResult[i] = currentPeakDbfs;
}
else if (currentPeakDbfs - m_thresholdVal < m_kneeVal)// Within knee
{
const float temp = currentPeakDbfs - m_thresholdVal + m_kneeVal;
m_gainResult[i] = currentPeakDbfs + ((limiter ? 0 : m_ratioVal) - 1) * temp * temp / (4 * m_kneeVal);
}
else// Above knee
{
m_gainResult[i] = limiter
? m_thresholdVal
: m_thresholdVal + (currentPeakDbfs - m_thresholdVal) * m_ratioVal;
}
m_gainResult[i] = dbfsToAmp(m_gainResult[i]) / m_yL[i];
m_gainResult[i] = qMax(m_rangeVal, m_gainResult[i]);
}
switch (stereoLink)
{
case Unlinked:
{
break;
}
case Maximum:
{
m_gainResult[0] = m_gainResult[1] = qMin(m_gainResult[0], m_gainResult[1]);
break;
}
case Average:
{
m_gainResult[0] = m_gainResult[1] = (m_gainResult[0] + m_gainResult[1]) * 0.5f;
break;
}
case Minimum:
{
m_gainResult[0] = m_gainResult[1] = qMax(m_gainResult[0], m_gainResult[1]);
break;
}
case Blend:
{
if (blend > 0)// 0 is unlinked
{
if (blend <= 1)// Blend to minimum volume
{
const float temp1 = qMin(m_gainResult[0], m_gainResult[1]);
m_gainResult[0] = linearInterpolate(m_gainResult[0], temp1, blend);
m_gainResult[1] = linearInterpolate(m_gainResult[1], temp1, blend);
}
else if (blend <= 2)// Blend to average volume
{
const float temp1 = qMin(m_gainResult[0], m_gainResult[1]);
const float temp2 = (m_gainResult[0] + m_gainResult[1]) * 0.5f;
m_gainResult[0] = linearInterpolate(temp1, temp2, blend - 1);
m_gainResult[1] = m_gainResult[0];
}
else// Blend to maximum volume
{
const float temp1 = (m_gainResult[0] + m_gainResult[1]) * 0.5f;
const float temp2 = qMax(m_gainResult[0], m_gainResult[1]);
m_gainResult[0] = linearInterpolate(temp1, temp2, blend - 2);
m_gainResult[1] = m_gainResult[0];
}
}
break;
}
}
// Bias compression to the left or right (or mid or side)
if (stereoBalance != 0)
{
m_gainResult[0] = 1 - ((1 - m_gainResult[0]) * (stereoBalance > 0 ? 1 - stereoBalance : 1));
m_gainResult[1] = 1 - ((1 - m_gainResult[1]) * (stereoBalance < 0 ? 1 + stereoBalance : 1));
}
// For visualizer
m_displayGain[0] = qMax(m_gainResult[0], m_displayGain[0]);
m_displayGain[1] = qMax(m_gainResult[1], m_displayGain[1]);
// 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 float temp[2] = {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];
}
else
{
s[0] = drySignal[0];
s[1] = drySignal[1];
}
float delayedDrySignal[2] = {s[0], s[1]};
if (midside)// Convert left/right to mid/side
{
const float temp = s[0];
s[0] = (temp + s[1]) * 0.5;
s[1] = temp - s[1];
}
s[0] *= inBalance > 0 ? 1 - inBalance : 1;
s[1] *= inBalance < 0 ? 1 + inBalance : 1;
s[0] *= m_gainResult[0] * m_inGainVal * m_outGainVal * (outBalance > 0 ? 1 - outBalance : 1);
s[1] *= m_gainResult[1] * m_inGainVal * m_outGainVal * (outBalance < 0 ? 1 + outBalance : 1);
if (midside)// Convert mid/side back to left/right
{
const float temp1 = s[0];
const float temp2 = s[1] * 0.5;
s[0] = temp1 + temp2;
s[1] = temp1 - temp2;
}
m_prevOut[0] = s[0];
m_prevOut[1] = s[1];
// Negate wet signal from dry signal
if (audition)
{
s[0] = (-s[0] + delayedDrySignal[0] * m_outGainVal);
s[1] = (-s[1] + delayedDrySignal[1] * m_outGainVal);
}
else if (autoMakeup)
{
s[0] *= m_autoMakeupVal;
s[1] *= m_autoMakeupVal;
}
// Calculate wet/dry value results
const float temp1 = delayedDrySignal[0];
const float temp2 = delayedDrySignal[1];
buf[f][0] = d * temp1 + w * s[0];
buf[f][1] = d * temp2 + w * s[1];
buf[f][0] = (1 - m_mixVal) * temp1 + m_mixVal * buf[f][0];
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];
lInPeak = drySignal[0] > lInPeak ? drySignal[0] : lInPeak;
rInPeak = drySignal[1] > rInPeak ? drySignal[1] : rInPeak;
lOutPeak = s[0] > lOutPeak ? s[0] : lOutPeak;
rOutPeak = s[1] > rOutPeak ? s[1] : rOutPeak;
}
checkGate(outSum / frames);
m_compressorControls.m_outPeakL = lOutPeak;
m_compressorControls.m_outPeakR = rOutPeak;
m_compressorControls.m_inPeakL = lInPeak;
m_compressorControls.m_inPeakR = rInPeak;
return isRunning();
}
// Regular modulo doesn't handle negative numbers correctly. This does.
inline int CompressorEffect::realmod(int k, int n)
{
return (k %= n) < 0 ? k+n : k;
}
// Regular fmod doesn't handle negative numbers correctly. This does.
inline float CompressorEffect::realfmod(float k, float n)
{
return (k = fmod(k, n)) < 0 ? k+n : k;
}
inline void CompressorEffect::calcTiltFilter(sample_t inputSample, sample_t &outputSample, int filtNum)
{
m_tiltOut[filtNum] = m_a0 * inputSample + m_b1 * m_tiltOut[filtNum];
outputSample = inputSample + m_lgain * m_tiltOut[filtNum] + m_hgain * (inputSample - m_tiltOut[filtNum]);
}
void CompressorEffect::changeSampleRate()
{
m_sampleRate = Engine::mixer()->processingSampleRate();
m_coeffPrecalc = COMP_LOG / (m_sampleRate * 0.001f);
// 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);
calcThreshold();
calcKnee();
calcRatio();
calcAutoMakeup();// This should be after Threshold, Knee, and Ratio
calcAttack();
calcRelease();
calcRange();
calcLookaheadLength();
calcHold();
resizeRMS();
calcOutGain();
calcInGain();
calcTiltCoeffs();
calcMix();
}
extern "C"
{
// necessary for getting instance out of shared lib
PLUGIN_EXPORT Plugin * lmms_plugin_main(Model* parent, void* data)
{
return new CompressorEffect(parent, static_cast<const Plugin::Descriptor::SubPluginFeatures::Key *>(data));
}
}

153
plugins/Compressor/Compressor.h Executable file
View File

@@ -0,0 +1,153 @@
/*
* Compressor.h
*
* Copyright (c) 2020 Lost Robot <r94231@gmail.com>
*
* This file is part of LMMS - https://lmms.io
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public
* License along with this program (see COPYING); if not, write to the
* Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301 USA.
*
*/
#ifndef COMPRESSOR_H
#define COMPRESSOR_H
#include "CompressorControls.h"
#include "Effect.h"
#include "ValueBuffer.h"
#include "RmsHelper.h"
constexpr float COMP_LOG = -2.2;
class CompressorEffect : public Effect
{
Q_OBJECT
public:
CompressorEffect(Model* parent, const Descriptor::SubPluginFeatures::Key* key);
~CompressorEffect() override;
bool processAudioBuffer(sampleFrame* buf, const fpp_t frames) override;
EffectControls* controls() override
{
return &m_compressorControls;
}
private slots:
void calcAutoMakeup();
void calcAttack();
void calcRelease();
void calcAutoAttack();
void calcAutoRelease();
void calcHold();
void calcOutGain();
void calcRatio();
void calcRange();
void resizeRMS();
void calcLookaheadLength();
void calcThreshold();
void calcKnee();
void calcInGain();
void calcTiltCoeffs();
void calcMix();
void changeSampleRate();
void redrawKnee();
private:
CompressorControls m_compressorControls;
float msToCoeff(float ms);
inline void calcTiltFilter(sample_t inputSample, sample_t &outputSample, int filtNum);
inline int realmod(int k, int n);
inline float realfmod(float k, float n);
enum StereoLinkModes { 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;
float m_attCoeff;
float m_relCoeff;
float m_autoAttVal;
float m_autoRelVal;
int m_holdLength = 0;
int m_holdTimer[2] = {0, 0};
int m_lookaheadLength;
int m_lookaheadDelayLength;
int m_preLookaheadLength;
float m_thresholdAmpVal;
float m_autoMakeupVal;
float m_outGainVal;
float m_inGainVal;
float m_rangeVal;
float m_tiltVal;
float m_mixVal;
float m_coeffPrecalc;
sampleFrame m_maxLookaheadVal;
int m_maxLookaheadTimer[2] = {1, 1};
float m_rmsTimeConst;
float m_rmsVal[2] = {0, 0};
float m_crestPeakVal[2] = {0, 0};
float m_crestRmsVal[2] = {0, 0};
float m_crestFactorVal[2] = {0, 0};
float m_crestTimeConst;
float m_tiltOut[2] = {0};
bool m_cleanedBuffers = false;
float m_sampleRate;
float m_lgain;
float m_hgain;
float m_a0;
float m_b1;
float m_prevOut[2] = {0};
float m_yL[2];
float m_gainResult[2];
float m_displayPeak[2];
float m_displayGain[2];
float m_kneeVal;
float m_thresholdVal;
float m_ratioVal;
bool m_redrawKnee = true;
bool m_redrawThreshold = true;
friend class CompressorControls;
friend class CompressorControlDialog;
} ;
#endif

View File

@@ -0,0 +1,762 @@
/*
* CompressorControlDialog.cpp
*
* Copyright (c) 2020 Lost Robot <r94231@gmail.com>
*
* This file is part of LMMS - https://lmms.io
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public
* License along with this program (see COPYING); if not, write to the
* Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301 USA.
*
*/
#include "Compressor.h"
#include "CompressorControlDialog.h"
#include "CompressorControls.h"
#include <QLabel>
#include <QLayout>
#include <QPainter>
#include "embed.h"
#include "GuiApplication.h"
#include "gui_templates.h"
#include "interpolation.h"
#include "MainWindow.h"
#include "ToolTip.h"
CompressorControlDialog::CompressorControlDialog(CompressorControls* controls) :
EffectControlDialog(controls),
m_controls(controls),
m_inVolAreaColor(209, 216, 228, 17),
m_inVolColor(209, 216, 228, 100),
m_outVolAreaColor(209, 216, 228, 30),
m_outVolColor(209, 216, 228, 240),
m_gainReductionColor(180, 100, 100, 210),
m_kneeColor(39, 171, 95, 255),
m_kneeColor2(9, 171, 160, 255),
m_threshColor(39, 171, 95, 100),
m_textColor(209, 216, 228, 50),
m_graphColor(209, 216, 228, 50),
m_resetColor(200, 100, 15, 200)
{
setAutoFillBackground(true);
QPalette pal;
pal.setBrush(backgroundRole(), PLUGIN_NAME::getIconPixmap("artwork"));
setPalette(pal);
setMinimumSize(MIN_COMP_SCREEN_X, MIN_COMP_SCREEN_Y);
resize(COMP_SCREEN_X, COMP_SCREEN_Y);
m_graphPixmap.fill(QColor("transparent"));
m_visPixmap.fill(QColor("transparent"));
m_kneePixmap.fill(QColor("transparent"));
m_kneePixmap2.fill(QColor("transparent"));
m_miscPixmap.fill(QColor("transparent"));
m_controlsBoxLabel = new QLabel(this);
m_controlsBoxLabel->setPixmap(PLUGIN_NAME::getIconPixmap("controlsBox"));
m_controlsBoxLabel->setAttribute(Qt::WA_TransparentForMouseEvents);
m_rmsEnabledLabel = new QLabel(this);
m_rmsEnabledLabel->setPixmap(PLUGIN_NAME::getIconPixmap("knob_enabled"));
m_rmsEnabledLabel->setAttribute(Qt::WA_TransparentForMouseEvents);
m_blendEnabledLabel = new QLabel(this);
m_blendEnabledLabel->setPixmap(PLUGIN_NAME::getIconPixmap("knob_enabled"));
m_blendEnabledLabel->setAttribute(Qt::WA_TransparentForMouseEvents);
m_lookaheadEnabledLabel = new QLabel(this);
m_lookaheadEnabledLabel->setPixmap(PLUGIN_NAME::getIconPixmap("knob_enabled"));
m_lookaheadEnabledLabel->setAttribute(Qt::WA_TransparentForMouseEvents);
m_ratioEnabledLabel = new QLabel(this);
m_ratioEnabledLabel->setPixmap(PLUGIN_NAME::getIconPixmap("knob_enabled_large"));
m_ratioEnabledLabel->setAttribute(Qt::WA_TransparentForMouseEvents);
m_thresholdKnob = new Knob(knobStyled, this);
makeLargeKnob(m_thresholdKnob, tr("Threshold:") , " dBFS");
m_thresholdKnob->setModel(&controls->m_thresholdModel);
ToolTip::add(m_thresholdKnob, tr("Volume at which the compression begins to take place"));
m_ratioKnob = new Knob(knobStyled, this);
makeLargeKnob(m_ratioKnob, tr("Ratio:") , ":1");
m_ratioKnob->setModel(&controls->m_ratioModel);
ToolTip::add(m_ratioKnob, tr("How far the compressor must turn the volume down after crossing the threshold"));
m_attackKnob = new Knob(knobStyled, this);
makeLargeKnob(m_attackKnob, tr("Attack:") , " ms");
m_attackKnob->setModel(&controls->m_attackModel);
ToolTip::add(m_attackKnob, tr("Speed at which the compressor starts to compress the audio"));
m_releaseKnob = new Knob(knobStyled, this);
makeLargeKnob(m_releaseKnob, tr("Release:") , " ms");
m_releaseKnob->setModel(&controls->m_releaseModel);
ToolTip::add(m_releaseKnob, tr("Speed at which the compressor ceases to compress the audio"));
m_kneeKnob = new Knob(knobStyled, this);
makeSmallKnob(m_kneeKnob, tr("Knee:") , " dB");
m_kneeKnob->setModel(&controls->m_kneeModel);
ToolTip::add(m_kneeKnob, tr("Smooth out the gain reduction curve around the threshold"));
m_rangeKnob = new Knob(knobStyled, this);
makeSmallKnob(m_rangeKnob, tr("Range:") , " dBFS");
m_rangeKnob->setModel(&controls->m_rangeModel);
ToolTip::add(m_rangeKnob, tr("Maximum gain reduction"));
m_lookaheadLengthKnob = new Knob(knobStyled, this);
makeSmallKnob(m_lookaheadLengthKnob, tr("Lookahead Length:") , " ms");
m_lookaheadLengthKnob->setModel(&controls->m_lookaheadLengthModel);
ToolTip::add(m_lookaheadLengthKnob, tr("How long the compressor has to react to the sidechain signal ahead of time"));
m_holdKnob = new Knob(knobStyled, this);
makeSmallKnob(m_holdKnob, tr("Hold:") , " ms");
m_holdKnob->setModel(&controls->m_holdModel);
ToolTip::add(m_holdKnob, tr("Delay between attack and release stages"));
m_rmsKnob = new Knob(knobStyled, this);
makeSmallKnob(m_rmsKnob, tr("RMS Size:") , "");
m_rmsKnob->setModel(&controls->m_rmsModel);
ToolTip::add(m_rmsKnob, tr("Size of the RMS buffer"));
m_inBalanceKnob = new Knob(knobStyled, this);
makeSmallKnob(m_inBalanceKnob, tr("Input Balance:") , "");
m_inBalanceKnob->setModel(&controls->m_inBalanceModel);
ToolTip::add(m_inBalanceKnob, tr("Bias the input audio to the left/right or mid/side"));
m_outBalanceKnob = new Knob(knobStyled, this);
makeSmallKnob(m_outBalanceKnob, tr("Output Balance:") , "");
m_outBalanceKnob->setModel(&controls->m_outBalanceModel);
ToolTip::add(m_outBalanceKnob, tr("Bias the output audio to the left/right or mid/side"));
m_stereoBalanceKnob = new Knob(knobStyled, this);
makeSmallKnob(m_stereoBalanceKnob, tr("Stereo Balance:") , "");
m_stereoBalanceKnob->setModel(&controls->m_stereoBalanceModel);
ToolTip::add(m_stereoBalanceKnob, tr("Bias the sidechain signal to the left/right or mid/side"));
m_blendKnob = new Knob(knobStyled, this);
makeSmallKnob(m_blendKnob, tr("Stereo Link Blend:") , "");
m_blendKnob->setModel(&controls->m_blendModel);
ToolTip::add(m_blendKnob, tr("Blend between unlinked/maximum/average/minimum stereo linking modes"));
m_tiltKnob = new Knob(knobStyled, this);
makeSmallKnob(m_tiltKnob, tr("Tilt Gain:") , " dB");
m_tiltKnob->setModel(&controls->m_tiltModel);
ToolTip::add(m_tiltKnob, tr("Bias the sidechain signal to the low or high frequencies. -6 db is lowpass, 6 db is highpass."));
m_tiltFreqKnob = new Knob(knobStyled, this);
makeSmallKnob(m_tiltFreqKnob, tr("Tilt Frequency:") , " Hz");
m_tiltFreqKnob->setModel(&controls->m_tiltFreqModel);
ToolTip::add(m_tiltFreqKnob, tr("Center frequency of sidechain tilt filter"));
m_mixKnob = new Knob(knobStyled, this);
makeSmallKnob(m_mixKnob, tr("Mix:") , "%");
m_mixKnob->setModel(&controls->m_mixModel);
ToolTip::add(m_mixKnob, tr("Balance between wet and dry signals"));
m_autoAttackKnob = new Knob(knobStyled, this);
makeSmallKnob(m_autoAttackKnob, tr("Auto Attack:") , "%");
m_autoAttackKnob->setModel(&controls->m_autoAttackModel);
ToolTip::add(m_autoAttackKnob, tr("Automatically control attack value depending on crest factor"));
m_autoReleaseKnob = new Knob(knobStyled, this);
makeSmallKnob(m_autoReleaseKnob, tr("Auto Release:") , "%");
m_autoReleaseKnob->setModel(&controls->m_autoReleaseModel);
ToolTip::add(m_autoReleaseKnob, tr("Automatically control release value depending on crest factor"));
m_outFader = new EqFader(&controls->m_outGainModel,tr("Output gain"),
this, &controls->m_outPeakL, &controls->m_outPeakR);
m_outFader->setDisplayConversion(false);
m_outFader->setHintText(tr("Gain"), "dBFS");
ToolTip::add(m_outFader, tr("Output volume"));
m_inFader = new EqFader(&controls->m_inGainModel,tr("Input gain"),
this, &controls->m_inPeakL, &controls->m_inPeakR);
m_inFader->setDisplayConversion(false);
m_inFader->setHintText(tr("Gain"), "dBFS");
ToolTip::add(m_inFader, tr("Input volume"));
rmsButton = new PixmapButton(this, tr("Root Mean Square"));
rmsButton->setActiveGraphic(PLUGIN_NAME::getIconPixmap("rms_sel"));
rmsButton->setInactiveGraphic(PLUGIN_NAME::getIconPixmap("rms_unsel"));
ToolTip::add(rmsButton, tr("Use RMS of the input"));
peakButton = new PixmapButton(this, tr("Peak"));
peakButton->setActiveGraphic(PLUGIN_NAME::getIconPixmap("peak_sel"));
peakButton->setInactiveGraphic(PLUGIN_NAME::getIconPixmap("peak_unsel"));
ToolTip::add(peakButton, tr("Use absolute value of the input"));
rmsPeakGroup = new automatableButtonGroup(this);
rmsPeakGroup->addButton(rmsButton);
rmsPeakGroup->addButton(peakButton);
rmsPeakGroup->setModel(&controls->m_peakmodeModel);
leftRightButton = new PixmapButton(this, tr("Left/Right"));
leftRightButton->setActiveGraphic(PLUGIN_NAME::getIconPixmap("leftright_sel"));
leftRightButton->setInactiveGraphic(PLUGIN_NAME::getIconPixmap("leftright_unsel"));
ToolTip::add(leftRightButton, tr("Compress left and right audio"));
midSideButton = new PixmapButton(this, tr("Mid/Side"));
midSideButton->setActiveGraphic(PLUGIN_NAME::getIconPixmap("midside_sel"));
midSideButton->setInactiveGraphic(PLUGIN_NAME::getIconPixmap("midside_unsel"));
ToolTip::add(midSideButton, tr("Compress mid and side audio"));
leftRightMidSideGroup = new automatableButtonGroup(this);
leftRightMidSideGroup->addButton(leftRightButton);
leftRightMidSideGroup->addButton(midSideButton);
leftRightMidSideGroup->setModel(&controls->m_midsideModel);
compressButton = new PixmapButton(this, tr("Compressor"));
compressButton->setActiveGraphic(PLUGIN_NAME::getIconPixmap("compressor_sel"));
compressButton->setInactiveGraphic(PLUGIN_NAME::getIconPixmap("compressor_unsel"));
ToolTip::add(compressButton, tr("Compress the audio"));
limitButton = new PixmapButton(this, tr("Limiter"));
limitButton->setActiveGraphic(PLUGIN_NAME::getIconPixmap("limiter_sel"));
limitButton->setInactiveGraphic(PLUGIN_NAME::getIconPixmap("limiter_unsel"));
ToolTip::add(limitButton, tr("Set Ratio to infinity (is not guaranteed to limit audio volume)"));
compressLimitGroup = new automatableButtonGroup(this);
compressLimitGroup->addButton(compressButton);
compressLimitGroup->addButton(limitButton);
compressLimitGroup->setModel(&controls->m_limiterModel);
unlinkedButton = new PixmapButton(this, tr("Unlinked"));
unlinkedButton->setActiveGraphic(PLUGIN_NAME::getIconPixmap("unlinked_sel"));
unlinkedButton->setInactiveGraphic(PLUGIN_NAME::getIconPixmap("unlinked_unsel"));
ToolTip::add(unlinkedButton, tr("Compress each channel separately"));
maximumButton = new PixmapButton(this, tr("Maximum"));
maximumButton->setActiveGraphic(PLUGIN_NAME::getIconPixmap("maximum_sel"));
maximumButton->setInactiveGraphic(PLUGIN_NAME::getIconPixmap("maximum_unsel"));
ToolTip::add(maximumButton, tr("Compress based on the loudest channel"));
averageButton = new PixmapButton(this, tr("Average"));
averageButton->setActiveGraphic(PLUGIN_NAME::getIconPixmap("average_sel"));
averageButton->setInactiveGraphic(PLUGIN_NAME::getIconPixmap("average_unsel"));
ToolTip::add(averageButton, tr("Compress based on the averaged channel volume"));
minimumButton = new PixmapButton(this, tr("Minimum"));
minimumButton->setActiveGraphic(PLUGIN_NAME::getIconPixmap("minimum_sel"));
minimumButton->setInactiveGraphic(PLUGIN_NAME::getIconPixmap("minimum_unsel"));
ToolTip::add(minimumButton, tr("Compress based on the quietest channel"));
blendButton = new PixmapButton(this, tr("Blend"));
blendButton->setActiveGraphic(PLUGIN_NAME::getIconPixmap("blend_sel"));
blendButton->setInactiveGraphic(PLUGIN_NAME::getIconPixmap("blend_unsel"));
ToolTip::add(blendButton, tr("Blend between stereo linking modes"));
stereoLinkGroup = new automatableButtonGroup(this);
stereoLinkGroup->addButton(unlinkedButton);
stereoLinkGroup->addButton(maximumButton);
stereoLinkGroup->addButton(averageButton);
stereoLinkGroup->addButton(minimumButton);
stereoLinkGroup->addButton(blendButton);
stereoLinkGroup->setModel(&controls->m_stereoLinkModel);
autoMakeupButton = new PixmapButton(this, tr("Auto Makeup Gain"));
autoMakeupButton->setActiveGraphic(PLUGIN_NAME::getIconPixmap("autogain_sel"));
autoMakeupButton->setInactiveGraphic(PLUGIN_NAME::getIconPixmap("autogain_unsel"));
ToolTip::add(autoMakeupButton, tr("Automatically change makeup gain depending on threshold, knee, and ratio settings"));
autoMakeupButton->setCheckable(true);
autoMakeupButton->setModel(&controls->m_autoMakeupModel);
auditionButton = new PixmapButton(this, tr("Soft Clip"));
auditionButton->setActiveGraphic(PLUGIN_NAME::getIconPixmap("audition_sel"));
auditionButton->setInactiveGraphic(PLUGIN_NAME::getIconPixmap("audition_unsel"));
ToolTip::add(auditionButton, tr("Play the delta signal"));
auditionButton->setCheckable(true);
auditionButton->setModel(&controls->m_auditionModel);
feedbackButton = new PixmapButton(this, tr("Soft Clip"));
feedbackButton->setActiveGraphic(PLUGIN_NAME::getIconPixmap("feedback_sel"));
feedbackButton->setInactiveGraphic(PLUGIN_NAME::getIconPixmap("feedback_unsel"));
ToolTip::add(feedbackButton, tr("Use the compressor's output as the sidechain input"));
feedbackButton->setCheckable(true);
feedbackButton->setModel(&controls->m_feedbackModel);
lookaheadButton = new PixmapButton(this, tr("Lookahead Enabled"));
lookaheadButton->setActiveGraphic(PLUGIN_NAME::getIconPixmap("lookahead_sel"));
lookaheadButton->setInactiveGraphic(PLUGIN_NAME::getIconPixmap("lookahead_unsel"));
ToolTip::add(lookaheadButton, tr("Enable Lookahead, which introduces 20 milliseconds of latency"));
lookaheadButton->setCheckable(true);
lookaheadButton->setModel(&controls->m_lookaheadModel);
connect(gui->mainWindow(), SIGNAL(periodicUpdate()), this, SLOT(updateDisplay()));
connect(&m_controls->m_peakmodeModel, SIGNAL(dataChanged()), this, SLOT(peakmodeChanged()));
connect(&m_controls->m_stereoLinkModel, SIGNAL(dataChanged()), this, SLOT(stereoLinkChanged()));
connect(&m_controls->m_lookaheadModel, SIGNAL(dataChanged()), this, SLOT(lookaheadChanged()));
connect(&m_controls->m_limiterModel, SIGNAL(dataChanged()), this, SLOT(limiterChanged()));
m_timeElapsed.start();
peakmodeChanged();
stereoLinkChanged();
lookaheadChanged();
limiterChanged();
}
void CompressorControlDialog::makeLargeKnob(Knob * knob, QString hint, QString unit)
{
knob->setHintText(hint, unit);
knob->setFixedSize(56, 56);
knob->setOuterRadius(23);
knob->setInnerRadius(15);
knob->setCenterPointX(28);
knob->setCenterPointY(28);
}
void CompressorControlDialog::makeSmallKnob(Knob * knob, QString hint, QString unit)
{
knob->setHintText(hint, unit);
knob->setFixedSize(30, 30);
knob->setOuterRadius(10);
knob->setInnerRadius(4);
knob->setCenterPointX(15);
knob->setCenterPointY(15);
}
void CompressorControlDialog::peakmodeChanged()
{
m_rmsKnob->setVisible(!m_controls->m_peakmodeModel.value());
m_rmsEnabledLabel->setVisible(!m_controls->m_peakmodeModel.value());
}
void CompressorControlDialog::stereoLinkChanged()
{
m_blendKnob->setVisible(m_controls->m_stereoLinkModel.value() == 4);
m_blendEnabledLabel->setVisible(m_controls->m_stereoLinkModel.value() == 4);
}
void CompressorControlDialog::lookaheadChanged()
{
m_lookaheadLengthKnob->setVisible(m_controls->m_lookaheadModel.value());
m_lookaheadEnabledLabel->setVisible(m_controls->m_lookaheadModel.value());
}
void CompressorControlDialog::limiterChanged()
{
m_ratioKnob->setVisible(!m_controls->m_limiterModel.value());
m_ratioEnabledLabel->setVisible(!m_controls->m_limiterModel.value());
}
void CompressorControlDialog::updateDisplay()
{
if (!isVisible())
{
m_timeElapsed.restart();
return;
}
int elapsedMil = m_timeElapsed.elapsed();
m_timeElapsed.restart();
m_timeSinceLastUpdate += elapsedMil;
m_compPixelMovement = int(m_timeSinceLastUpdate / COMP_MILLI_PER_PIXEL);
m_timeSinceLastUpdate %= COMP_MILLI_PER_PIXEL;
// Time Change / Daylight Savings Time protection
if (!m_compPixelMovement || m_compPixelMovement <= 0)
{
return;
}
if (!m_controls->m_effect->isEnabled() || !m_controls->m_effect->isRunning())
{
m_controls->m_effect->m_displayPeak[0] = COMP_NOISE_FLOOR;
m_controls->m_effect->m_displayPeak[1] = COMP_NOISE_FLOOR;
m_controls->m_effect->m_displayGain[0] = 1;
m_controls->m_effect->m_displayGain[1] = 1;
}
m_peakAvg = (m_controls->m_effect->m_displayPeak[0] + m_controls->m_effect->m_displayPeak[1]) * 0.5f;
m_gainAvg = (m_controls->m_effect->m_displayGain[0] + m_controls->m_effect->m_displayGain[1]) * 0.5f;
m_controls->m_effect->m_displayPeak[0] = m_controls->m_effect->m_yL[0];
m_controls->m_effect->m_displayPeak[1] = m_controls->m_effect->m_yL[1];
m_controls->m_effect->m_displayGain[0] = m_controls->m_effect->m_gainResult[0];
m_controls->m_effect->m_displayGain[1] = m_controls->m_effect->m_gainResult[1];
m_yPoint = dbfsToYPoint(ampToDbfs(m_peakAvg));
m_yGainPoint = dbfsToYPoint(ampToDbfs(m_gainAvg));
m_threshYPoint = dbfsToYPoint(m_controls->m_effect->m_thresholdVal);
m_threshXPoint = m_kneeWindowSizeY - m_threshYPoint;
drawVisPixmap();
if (m_controls->m_effect->m_redrawKnee)
{
redrawKnee();
}
drawKneePixmap2();
if (m_controls->m_effect->m_redrawThreshold)
{
drawMiscPixmap();
}
m_lastPoint = m_yPoint;
m_lastGainPoint = m_yGainPoint;
update();
}
void CompressorControlDialog::drawVisPixmap()
{
m_p.begin(&m_visPixmap);
// Move entire display to the left
m_p.setCompositionMode(QPainter::CompositionMode_Source);
m_p.drawPixmap(-m_compPixelMovement, 0, m_visPixmap);
m_p.fillRect(m_windowSizeX-m_compPixelMovement, 0, m_windowSizeX, m_windowSizeY, QColor("transparent"));
m_p.setCompositionMode(QPainter::CompositionMode_SourceOver);
m_p.setRenderHint(QPainter::Antialiasing, true);
// Draw translucent portion of input volume line
m_p.setPen(QPen(m_inVolAreaColor, 1));
for (int i = 0; i < m_compPixelMovement; ++i)
{
const int temp = linearInterpolate(m_lastPoint, m_yPoint, float(i) / float(m_compPixelMovement));
m_p.drawLine(m_windowSizeX-m_compPixelMovement+i, temp, m_windowSizeX-m_compPixelMovement+i, m_windowSizeY);
}
// Draw input volume line
m_p.setPen(QPen(m_inVolColor, 1));
m_p.drawLine(m_windowSizeX-m_compPixelMovement-1, m_lastPoint, m_windowSizeX, m_yPoint);
// Draw translucent portion of output volume line
m_p.setPen(QPen(m_outVolAreaColor, 1));
for (int i = 0; i < m_compPixelMovement; ++i)
{
const int temp = linearInterpolate(m_lastPoint+m_lastGainPoint, m_yPoint+m_yGainPoint, float(i) / float(m_compPixelMovement));
m_p.drawLine(m_windowSizeX-m_compPixelMovement+i, temp, m_windowSizeX-m_compPixelMovement+i, m_windowSizeY);
}
// Draw output volume line
m_p.setPen(QPen(m_outVolColor, 1));
m_p.drawLine(m_windowSizeX-m_compPixelMovement-1, m_lastPoint+m_lastGainPoint, m_windowSizeX, m_yPoint+m_yGainPoint);
// Draw gain reduction line
m_p.setPen(QPen(m_gainReductionColor, 2));
m_p.drawLine(m_windowSizeX-m_compPixelMovement-1, m_lastGainPoint, m_windowSizeX, m_yGainPoint);
m_p.end();
}
void CompressorControlDialog::redrawKnee()
{
m_controls->m_effect->m_redrawKnee = false;
// Start drawing knee visualizer
m_p.begin(&m_kneePixmap);
m_p.setRenderHint(QPainter::Antialiasing, false);
// Clear display
m_p.setCompositionMode(QPainter::CompositionMode_Source);
m_p.fillRect(0, 0, m_windowSizeX, m_kneeWindowSizeY, QColor("transparent"));
m_p.setCompositionMode(QPainter::CompositionMode_SourceOver);
m_p.setRenderHint(QPainter::Antialiasing, true);
m_p.setPen(QPen(m_kneeColor, 3));
// Limiter = infinite ratio
float actualRatio = m_controls->m_limiterModel.value() ? 0 : m_controls->m_effect->m_ratioVal;
// Calculate endpoints for the two straight lines
float kneePoint1 = m_controls->m_effect->m_thresholdVal - m_controls->m_effect->m_kneeVal;
float kneePoint2X = m_controls->m_effect->m_thresholdVal + m_controls->m_effect->m_kneeVal;
float kneePoint2Y = (m_controls->m_effect->m_thresholdVal + (-m_controls->m_effect->m_thresholdVal * (actualRatio * (m_controls->m_effect->m_kneeVal / -m_controls->m_effect->m_thresholdVal))));
float ratioPoint = m_controls->m_effect->m_thresholdVal + (-m_controls->m_effect->m_thresholdVal * actualRatio);
// Draw two straight lines
m_p.drawLine(0, m_kneeWindowSizeY, dbfsToXPoint(kneePoint1), dbfsToYPoint(kneePoint1));
if (dbfsToXPoint(kneePoint2X) < m_kneeWindowSizeY)
{
m_p.drawLine(dbfsToXPoint(kneePoint2X), dbfsToYPoint(kneePoint2Y), m_kneeWindowSizeY, dbfsToYPoint(ratioPoint));
}
// Draw knee section
if (m_controls->m_effect->m_kneeVal)
{
m_p.setPen(QPen(m_kneeColor2, 3));
float prevPoint[2] = {kneePoint1, kneePoint1};
float newPoint[2] = {0, 0};
// Draw knee curve using many straight lines.
for (int i = 0; i < COMP_KNEE_LINES; ++i)
{
newPoint[0] = linearInterpolate(kneePoint1, kneePoint2X, (i + 1) / (float)COMP_KNEE_LINES);
const float temp = newPoint[0] - m_controls->m_effect->m_thresholdVal + m_controls->m_effect->m_kneeVal;
newPoint[1] = (newPoint[0] + (actualRatio - 1) * temp * temp / (4 * m_controls->m_effect->m_kneeVal));
m_p.drawLine(dbfsToXPoint(prevPoint[0]), dbfsToYPoint(prevPoint[1]), dbfsToXPoint(newPoint[0]), dbfsToYPoint(newPoint[1]));
prevPoint[0] = newPoint[0];
prevPoint[1] = newPoint[1];
}
}
m_p.setRenderHint(QPainter::Antialiasing, false);
// Erase right portion
m_p.setCompositionMode(QPainter::CompositionMode_Source);
m_p.fillRect(m_kneeWindowSizeX + 1, 0, m_windowSizeX, m_kneeWindowSizeY, QColor("transparent"));
m_p.setCompositionMode(QPainter::CompositionMode_SourceOver);
m_p.end();
m_p.begin(&m_kneePixmap2);
m_p.setCompositionMode(QPainter::CompositionMode_Source);
m_p.fillRect(0, 0, m_windowSizeX, m_kneeWindowSizeY, QColor("transparent"));
m_p.setCompositionMode(QPainter::CompositionMode_SourceOver);
m_p.end();
m_lastKneePoint = 0;
}
void CompressorControlDialog::drawKneePixmap2()
{
m_p.begin(&m_kneePixmap2);
m_p.setRenderHint(QPainter::Antialiasing, false);
int kneePoint = dbfsToXPoint(ampToDbfs(m_peakAvg));
if (kneePoint > m_lastKneePoint)
{
QRectF knee2Rect = QRect(m_lastKneePoint, 0, kneePoint - m_lastKneePoint, m_kneeWindowSizeY);
m_p.drawPixmap(knee2Rect, m_kneePixmap, knee2Rect);
}
else
{
m_p.setCompositionMode(QPainter::CompositionMode_Source);
m_p.fillRect(kneePoint, 0, m_lastKneePoint, m_kneeWindowSizeY, QColor("transparent"));
m_p.setCompositionMode(QPainter::CompositionMode_SourceOver);
}
m_lastKneePoint = kneePoint;
m_p.end();
}
void CompressorControlDialog::drawMiscPixmap()
{
m_p.begin(&m_miscPixmap);
m_p.setCompositionMode(QPainter::CompositionMode_Source);
m_p.fillRect(0, 0, m_windowSizeX, m_windowSizeY, QColor("transparent"));
m_p.setCompositionMode(QPainter::CompositionMode_SourceOver);
m_p.setRenderHint(QPainter::Antialiasing, true);
// Draw threshold lines
m_p.setPen(QPen(m_threshColor, 2, Qt::DotLine));
m_p.drawLine(0, m_threshYPoint, m_windowSizeX, m_threshYPoint);
m_p.drawLine(m_threshXPoint, 0, m_threshXPoint, m_kneeWindowSizeY);
m_p.end();
m_controls->m_effect->m_redrawThreshold = false;
}
void CompressorControlDialog::paintEvent(QPaintEvent *event)
{
if (!isVisible())
{
return;
}
m_p.begin(this);
m_p.setCompositionMode(QPainter::CompositionMode_Source);
m_p.fillRect(0, 0, m_windowSizeX, m_windowSizeY, QColor("transparent"));
m_p.setCompositionMode(QPainter::CompositionMode_SourceOver);
m_p.drawPixmap(0, 0, m_graphPixmap);
m_p.drawPixmap(0, 0, m_visPixmap);
m_p.setOpacity(0.25);
m_p.drawPixmap(0, 0, m_kneePixmap);
m_p.setOpacity(1);
if (m_controls->m_effect->isEnabled() && m_controls->m_effect->isRunning())
{
m_p.drawPixmap(0, 0, m_kneePixmap2);
}
m_p.drawPixmap(0, 0, m_miscPixmap);
m_p.end();
}
inline int CompressorControlDialog::dbfsToYPoint(float inDbfs)
{
return (-((inDbfs + m_dbRange) / m_dbRange) + 1) * m_windowSizeY;
}
inline int CompressorControlDialog::dbfsToXPoint(float inDbfs)
{
return m_kneeWindowSizeY - dbfsToYPoint(inDbfs);
}
void CompressorControlDialog::resizeEvent(QResizeEvent *event)
{
resetCompressorView();
}
void CompressorControlDialog::wheelEvent(QWheelEvent * event)
{
const float temp = m_dbRange;
const float dbRangeNew = m_dbRange - copysignf(COMP_GRID_SPACING, event->delta());
m_dbRange = round(qBound(COMP_GRID_SPACING, dbRangeNew, COMP_GRID_MAX) / COMP_GRID_SPACING) * COMP_GRID_SPACING;
// Only reset view if the scolling had an effect
if (m_dbRange != temp)
{
drawGraph();
m_controls->m_effect->m_redrawKnee = true;
m_controls->m_effect->m_redrawThreshold = true;
}
}
void CompressorControlDialog::drawGraph()
{
m_p.begin(&m_graphPixmap);
m_p.setRenderHint(QPainter::Antialiasing, false);
m_p.setCompositionMode(QPainter::CompositionMode_Source);
m_p.fillRect(0, 0, m_windowSizeX, m_windowSizeY, QColor("transparent"));
m_p.setCompositionMode(QPainter::CompositionMode_SourceOver);
m_p.setPen(QPen(m_textColor, 1));
// Arbitrary formula for increasing font size when window size increases
m_p.setFont(QFont("Arial", qMax(int(m_windowSizeY / 1080.f * 24), 12)));
// Redraw graph
m_p.setPen(QPen(m_graphColor, 1));
for (int i = 1; i < m_dbRange / COMP_GRID_SPACING + 1; ++i)
{
m_p.drawLine(0, dbfsToYPoint(-COMP_GRID_SPACING * i), m_windowSizeX, dbfsToYPoint(-COMP_GRID_SPACING * i));
m_p.drawLine(dbfsToXPoint(-COMP_GRID_SPACING * i), 0, dbfsToXPoint(-COMP_GRID_SPACING * i), m_kneeWindowSizeY);
m_p.drawText(QRectF(m_windowSizeX - 50, dbfsToYPoint(-COMP_GRID_SPACING * i), 50, 50), Qt::AlignRight | Qt::AlignTop, QString::number(i * -COMP_GRID_SPACING));
}
m_p.end();
}
void CompressorControlDialog::resetCompressorView()
{
m_windowSizeX = size().width();
m_windowSizeY = size().height();
m_kneeWindowSizeX = m_windowSizeY;
m_kneeWindowSizeY = m_windowSizeY;
m_controlsBoxX = (m_windowSizeX - COMP_BOX_X) * 0.5;
m_controlsBoxY = m_windowSizeY - 40 - COMP_BOX_Y;
m_controls->m_effect->m_redrawKnee = true;
m_controls->m_effect->m_redrawThreshold = true;
m_lastKneePoint = 0;
drawGraph();
m_p.begin(&m_visPixmap);
m_p.setCompositionMode(QPainter::CompositionMode_Source);
m_p.fillRect(0, 0, m_windowSizeX, m_windowSizeY, QColor("transparent"));
m_p.setCompositionMode(QPainter::CompositionMode_SourceOver);
// Draw line at right side, so the sudden
// content that the visualizer will display
// later on won't look too ugly
m_p.setPen(QPen(m_resetColor, 3));
m_p.drawLine(m_windowSizeX, 0, m_windowSizeX, m_windowSizeY);
m_p.end();
m_controlsBoxLabel->move(m_controlsBoxX, m_controlsBoxY);
m_rmsEnabledLabel->move(m_controlsBoxX + 429, m_controlsBoxY + 209);
m_blendEnabledLabel->move(m_controlsBoxX + 587, m_controlsBoxY + 197);
m_lookaheadEnabledLabel->move(m_controlsBoxX + 221, m_controlsBoxY + 135);
m_ratioEnabledLabel->move(m_controlsBoxX + 267, m_controlsBoxY + 21);
m_thresholdKnob->move(m_controlsBoxX + 137, m_controlsBoxY + 21);
m_ratioKnob->move(m_controlsBoxX + 267, m_controlsBoxY + 21);
m_attackKnob->move(m_controlsBoxX + 397, m_controlsBoxY + 21);
m_releaseKnob->move(m_controlsBoxX + 527, m_controlsBoxY + 21);
m_kneeKnob->move(m_controlsBoxX + 97, m_controlsBoxY + 135);
m_rangeKnob->move(m_controlsBoxX + 159, m_controlsBoxY + 135);
m_lookaheadLengthKnob->move(m_controlsBoxX + 221, m_controlsBoxY + 135);
m_holdKnob->move(m_controlsBoxX + 283, m_controlsBoxY + 135);
m_rmsKnob->move(m_controlsBoxX + 429, m_controlsBoxY + 209);
m_inBalanceKnob->move(m_controlsBoxX + 27, m_controlsBoxY + 219);
m_outBalanceKnob->move(m_controlsBoxX + 662, m_controlsBoxY + 219);
m_stereoBalanceKnob->move(m_controlsBoxX + 522, m_controlsBoxY + 137);
m_blendKnob->move(m_controlsBoxX + 587, m_controlsBoxY + 197);
m_tiltKnob->move(m_controlsBoxX + 364, m_controlsBoxY + 138);
m_tiltFreqKnob->move(m_controlsBoxX + 415, m_controlsBoxY + 138);
m_mixKnob->move(m_controlsBoxX + 27, m_controlsBoxY + 13);
m_outFader->move(m_controlsBoxX + 666, m_controlsBoxY + 91);
m_inFader->move(m_controlsBoxX + 31, m_controlsBoxY + 91);
rmsButton->move(m_controlsBoxX + 337, m_controlsBoxY + 231);
peakButton->move(m_controlsBoxX + 337, m_controlsBoxY + 248);
leftRightButton->move(m_controlsBoxX + 220, m_controlsBoxY + 231);
midSideButton->move(m_controlsBoxX + 220, m_controlsBoxY + 248);
compressButton->move(m_controlsBoxX + 98, m_controlsBoxY + 231);
limitButton->move(m_controlsBoxX + 98, m_controlsBoxY + 248);
unlinkedButton->move(m_controlsBoxX + 495, m_controlsBoxY + 180);
maximumButton->move(m_controlsBoxX + 495, m_controlsBoxY + 197);
averageButton->move(m_controlsBoxX + 495, m_controlsBoxY + 214);
minimumButton->move(m_controlsBoxX + 495, m_controlsBoxY + 231);
blendButton->move(m_controlsBoxX + 495, m_controlsBoxY + 248);
autoMakeupButton->move(m_controlsBoxX + 220, m_controlsBoxY + 206);
auditionButton->move(m_controlsBoxX + 658, m_controlsBoxY + 14);
feedbackButton->move(m_controlsBoxX + 98, m_controlsBoxY + 206);
m_autoAttackKnob->move(m_controlsBoxX + 460, m_controlsBoxY + 38);
m_autoReleaseKnob->move(m_controlsBoxX + 590, m_controlsBoxY + 38);
lookaheadButton->move(m_controlsBoxX + 202, m_controlsBoxY + 171);
}

View File

@@ -0,0 +1,218 @@
/*
* CompressorControlDialog.h
*
* Copyright (c) 2020 Lost Robot <r94231@gmail.com>
*
* This file is part of LMMS - https://lmms.io
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public
* License along with this program (see COPYING); if not, write to the
* Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301 USA.
*
*/
#ifndef COMPRESSOR_CONTROL_DIALOG_H
#define COMPRESSOR_CONTROL_DIALOG_H
#include <QBasicTimer>
#include <QLabel>
#include <QMouseEvent>
#include <QPainter>
#include <QTime>
#include "../Eq/EqFader.h"
#include "EffectControlDialog.h"
#include "GuiApplication.h"
#include "Knob.h"
#include "MainWindow.h"
#include "PixmapButton.h"
constexpr int COMP_MILLI_PER_PIXEL = 6;
constexpr int MIN_COMP_SCREEN_X = 800;
constexpr int MIN_COMP_SCREEN_Y = 360;
constexpr int MAX_COMP_SCREEN_X = 1920;
constexpr int MAX_COMP_SCREEN_Y = 1080;
constexpr int COMP_SCREEN_X = 800;
constexpr int COMP_SCREEN_Y = 560;
constexpr int KNEE_SCREEN_X = COMP_SCREEN_Y;
constexpr int KNEE_SCREEN_Y = COMP_SCREEN_Y;
constexpr int COMP_KNEE_LINES = 20;
constexpr int COMP_BOX_X = 720;
constexpr int COMP_BOX_Y = 280;
constexpr float COMP_GRID_SPACING = 3.f;// 3 db per grid line
constexpr float COMP_GRID_MAX = 96.f;// Can't zoom out past 96 db
constexpr float COMP_NOISE_FLOOR = 0.000001;// -120 dbFs
class CompressorControls;
class CompressorControlDialog : public EffectControlDialog
{
Q_OBJECT
public:
CompressorControlDialog(CompressorControls* controls);
bool isResizable() const override {return true;}
QSize sizeHint() const override {return QSize(COMP_SCREEN_X, COMP_SCREEN_Y);}
// For theming purposes
Q_PROPERTY(QColor inVolAreaColor MEMBER m_inVolAreaColor)
Q_PROPERTY(QColor inVolColor MEMBER m_inVolColor)
Q_PROPERTY(QColor outVolAreaColor MEMBER m_outVolAreaColor)
Q_PROPERTY(QColor outVolColor MEMBER m_outVolColor)
Q_PROPERTY(QColor gainReductionColor MEMBER m_gainReductionColor)
Q_PROPERTY(QColor kneeColor MEMBER m_kneeColor)
Q_PROPERTY(QColor kneeColor2 MEMBER m_kneeColor2)
Q_PROPERTY(QColor threshColor MEMBER m_threshColor)
Q_PROPERTY(QColor textColor MEMBER m_textColor)
Q_PROPERTY(QColor graphColor MEMBER m_graphColor)
Q_PROPERTY(QColor resetColor MEMBER m_resetColor)
protected:
void resizeEvent(QResizeEvent *event) override;
void paintEvent(QPaintEvent *event) override;
void wheelEvent(QWheelEvent *event) override;
private slots:
void updateDisplay();
void peakmodeChanged();
void stereoLinkChanged();
void lookaheadChanged();
void limiterChanged();
private:
void makeLargeKnob(Knob * knob, QString hint, QString unit);
void makeSmallKnob(Knob * knob, QString hint, QString unit);
void resetCompressorView();
void drawVisPixmap();
void redrawKnee();
void drawKneePixmap2();
void drawMiscPixmap();
void drawGraph();
QPainter m_p;
QBasicTimer m_updateTimer;
CompressorControls * m_controls;
inline int dbfsToYPoint(float inDbfs);
inline int dbfsToXPoint(float inDbfs);
QPixmap m_visPixmap = QPixmap(MAX_COMP_SCREEN_X, MAX_COMP_SCREEN_Y);
QPixmap m_kneePixmap = QPixmap(MAX_COMP_SCREEN_X, MAX_COMP_SCREEN_Y);
QPixmap m_kneePixmap2 = QPixmap(MAX_COMP_SCREEN_X, MAX_COMP_SCREEN_Y);
QPixmap m_miscPixmap = QPixmap(MAX_COMP_SCREEN_X, MAX_COMP_SCREEN_Y);
QPixmap m_graphPixmap = QPixmap(MAX_COMP_SCREEN_X, MAX_COMP_SCREEN_Y);
int m_lastPoint;
int m_lastGainPoint;
int m_lastKneePoint = 0;
int m_windowSizeX = size().width();
int m_windowSizeY = size().height();
int m_kneeWindowSizeX = m_windowSizeY;
int m_kneeWindowSizeY = m_windowSizeY;
int m_controlsBoxX = 0;
int m_controlsBoxY = 0;
float m_dbRange = 36;
QColor m_inVolAreaColor = QColor(209, 216, 228, 17);
QColor m_inVolColor = QColor(209, 216, 228, 100);
QColor m_outVolAreaColor = QColor(209, 216, 228, 30);
QColor m_outVolColor = QColor(209, 216, 228, 240);
QColor m_gainReductionColor = QColor(180, 100, 100, 210);
QColor m_kneeColor = QColor(39, 171, 95, 255);
QColor m_kneeColor2 = QColor(9, 171, 160, 255);
QColor m_threshColor = QColor(39, 171, 95, 100);
QColor m_textColor = QColor(209, 216, 228, 50);
QColor m_graphColor = QColor(209, 216, 228, 50);
QColor m_resetColor = QColor(200, 100, 15, 200);
float m_peakAvg;
float m_gainAvg;
float m_yPoint;
float m_yGainPoint;
int m_threshYPoint;
int m_threshXPoint;
int m_compPixelMovement;
QLabel * m_controlsBoxLabel;
QLabel * m_rmsEnabledLabel;
QLabel * m_blendEnabledLabel;
QLabel * m_lookaheadEnabledLabel;
QLabel * m_ratioEnabledLabel;
Knob * m_thresholdKnob;
Knob * m_ratioKnob;
Knob * m_attackKnob;
Knob * m_releaseKnob;
Knob * m_kneeKnob;
Knob * m_rangeKnob;
Knob * m_lookaheadLengthKnob;
Knob * m_holdKnob;
Knob * m_rmsKnob;
Knob * m_inBalanceKnob;
Knob * m_outBalanceKnob;
Knob * m_stereoBalanceKnob;
Knob * m_blendKnob;
Knob * m_tiltKnob;
Knob * m_tiltFreqKnob;
Knob * m_mixKnob;
Knob * m_autoAttackKnob;
Knob * m_autoReleaseKnob;
EqFader * m_outFader;
EqFader * m_inFader;
PixmapButton * rmsButton;
PixmapButton * peakButton;
automatableButtonGroup * rmsPeakGroup;
PixmapButton * leftRightButton;
PixmapButton * midSideButton;
automatableButtonGroup * leftRightMidSideGroup;
PixmapButton * compressButton;
PixmapButton * limitButton;
automatableButtonGroup * compressLimitGroup;
PixmapButton * unlinkedButton;
PixmapButton * maximumButton;
PixmapButton * averageButton;
PixmapButton * minimumButton;
PixmapButton * blendButton;
automatableButtonGroup * stereoLinkGroup;
PixmapButton * autoMakeupButton;
PixmapButton * auditionButton;
PixmapButton * feedbackButton;
PixmapButton * lookaheadButton;
QElapsedTimer m_timeElapsed;
int m_timeSinceLastUpdate = 0;
friend class CompressorControls;
} ;
#endif

View File

@@ -0,0 +1,147 @@
/*
* CompressorControls.cpp
*
* Copyright (c) 2020 Lost Robot <r94231@gmail.com>
*
* This file is part of LMMS - https://lmms.io
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public
* License along with this program (see COPYING); if not, write to the
* Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301 USA.
*
*/
#include "CompressorControls.h"
#include "Compressor.h"
#include <QDomElement>
#include "Engine.h"
#include "Song.h"
CompressorControls::CompressorControls(CompressorEffect* effect) :
EffectControls(effect),
m_effect(effect),
m_thresholdModel(-8.0f, -60.0f, 0.0f, 0.001f, this, tr("Threshold")),
m_ratioModel(1.8f, 1.0f, 20.0f, 0.001f, this, tr("Ratio")),
m_attackModel(10.0f, 0.005f, 250.f, 0.001f, this, tr("Attack")),
m_releaseModel(100.0f, 1.f, 2500.f, 0.001f, this, tr("Release")),
m_kneeModel(12.0f, 0.0f, 96.0f, 0.01f, this, tr("Knee")),
m_holdModel(0.0f, 0.0f, 500.0f, 0.01f, this, tr("Hold")),
m_rangeModel(-240.0f, -240.0f, 0.0f, 0.01f, this, tr("Range")),
m_rmsModel(1.0f, 0.0f, 250.0f, 0.01f, this, tr("RMS Size")),
m_midsideModel(0.0f, 0.0f, 1.0f, this, tr("Mid/Side")),
m_peakmodeModel(0.0f, 0.0f, 1.0f, this, tr("Peak Mode")),
m_lookaheadLengthModel(0.0f, 0.0f, 20.0f, 0.0001f, this, tr("Lookahead Length")),
m_inBalanceModel(0.0f, -1.0f, 1.0f, 0.0001f, this, tr("Input Balance")),
m_outBalanceModel(0.0f, -1.0f, 1.0f, 0.0001f, this, tr("Output Balance")),
m_limiterModel(0.f, 0.f, 1.0f, this, tr("Limiter")),
m_outGainModel(0.f, -60.f, 30.f, 0.01f, this, tr("Output Gain")),
m_inGainModel(0.f, -60.f, 30.f, 0.01f, this, tr("Input Gain")),
m_blendModel(1.f, 0.f, 3.f, 0.0001f, this, tr("Blend")),
m_stereoBalanceModel(0.0f, -1.0f, 1.0f, 0.0001f, this, tr("Stereo Balance")),
m_autoMakeupModel(false, this, tr("Auto Makeup Gain")),
m_auditionModel(false, this, tr("Audition")),
m_feedbackModel(false, this, tr("Feedback")),
m_autoAttackModel(0.0f, 0.f, 100.0f, 0.01f, this, tr("Auto Attack")),
m_autoReleaseModel(0.0f, 0.f, 100.0f, 0.01f, this, tr("Auto Release")),
m_lookaheadModel(false, this, tr("Lookahead")),
m_tiltModel(0.0f, -6.0f, 6.0f, 0.0001f, this, tr("Tilt")),
m_tiltFreqModel(150.0f, 20.0f, 20000.0f, 0.1f, this, tr("Tilt Frequency")),
m_stereoLinkModel(1.0f, 0.0f, 4.0f, this, tr("Stereo Link")),
m_mixModel(100.0f, 0.f, 100.0f, 0.01f, this, tr("Mix"))
{
m_ratioModel.setScaleLogarithmic(true);
m_holdModel.setScaleLogarithmic(true);
m_attackModel.setScaleLogarithmic(true);
m_releaseModel.setScaleLogarithmic(true);
m_thresholdModel.setScaleLogarithmic(true);
m_rangeModel.setScaleLogarithmic(true);
m_lookaheadLengthModel.setScaleLogarithmic(true);
m_rmsModel.setScaleLogarithmic(true);
m_kneeModel.setScaleLogarithmic(true);
m_tiltFreqModel.setScaleLogarithmic(true);
m_rangeModel.setScaleLogarithmic(true);
}
void CompressorControls::saveSettings(QDomDocument& doc, QDomElement& _this)
{
m_thresholdModel.saveSettings(doc, _this, "threshold");
m_ratioModel.saveSettings(doc, _this, "ratio");
m_attackModel.saveSettings(doc, _this, "attack");
m_releaseModel.saveSettings(doc, _this, "release");
m_kneeModel.saveSettings(doc, _this, "knee");
m_holdModel.saveSettings(doc, _this, "hold");
m_rangeModel.saveSettings(doc, _this, "range");
m_rmsModel.saveSettings(doc, _this, "rms");
m_midsideModel.saveSettings(doc, _this, "midside");
m_peakmodeModel.saveSettings(doc, _this, "peakmode");
m_lookaheadLengthModel.saveSettings(doc, _this, "lookaheadLength");
m_inBalanceModel.saveSettings(doc, _this, "inBalance");
m_outBalanceModel.saveSettings(doc, _this, "outBalance");
m_limiterModel.saveSettings(doc, _this, "limiter");
m_outGainModel.saveSettings(doc, _this, "outGain");
m_inGainModel.saveSettings(doc, _this, "inGain");
m_blendModel.saveSettings(doc, _this, "blend");
m_stereoBalanceModel.saveSettings(doc, _this, "stereoBalance");
m_autoMakeupModel.saveSettings(doc, _this, "autoMakeup");
m_auditionModel.saveSettings(doc, _this, "audition");
m_feedbackModel.saveSettings(doc, _this, "feedback");
m_autoAttackModel.saveSettings(doc, _this, "autoAttack");
m_autoReleaseModel.saveSettings(doc, _this, "autoRelease");
m_lookaheadModel.saveSettings(doc, _this, "lookahead");
m_tiltModel.saveSettings(doc, _this, "tilt");
m_tiltFreqModel.saveSettings(doc, _this, "tiltFreq");
m_stereoLinkModel.saveSettings(doc, _this, "stereoLink");
m_mixModel.saveSettings(doc, _this, "mix");
}
void CompressorControls::loadSettings(const QDomElement& _this)
{
m_thresholdModel.loadSettings(_this, "threshold");
m_ratioModel.loadSettings(_this, "ratio");
m_attackModel.loadSettings(_this, "attack");
m_releaseModel.loadSettings(_this, "release");
m_kneeModel.loadSettings(_this, "knee");
m_holdModel.loadSettings(_this, "hold");
m_rangeModel.loadSettings(_this, "range");
m_rmsModel.loadSettings(_this, "rms");
m_midsideModel.loadSettings(_this, "midside");
m_peakmodeModel.loadSettings(_this, "peakmode");
m_lookaheadLengthModel.loadSettings(_this, "lookaheadLength");
m_inBalanceModel.loadSettings(_this, "inBalance");
m_outBalanceModel.loadSettings(_this, "outBalance");
m_limiterModel.loadSettings(_this, "limiter");
m_outGainModel.loadSettings(_this, "outGain");
m_inGainModel.loadSettings(_this, "inGain");
m_blendModel.loadSettings(_this, "blend");
m_stereoBalanceModel.loadSettings(_this, "stereoBalance");
m_autoMakeupModel.loadSettings(_this, "autoMakeup");
m_auditionModel.loadSettings(_this, "audition");
m_feedbackModel.loadSettings(_this, "feedback");
m_autoAttackModel.loadSettings(_this, "autoAttack");
m_autoReleaseModel.loadSettings(_this, "autoRelease");
m_lookaheadModel.loadSettings(_this, "lookahead");
m_tiltModel.loadSettings(_this, "tilt");
m_tiltFreqModel.loadSettings(_this, "tiltFreq");
m_stereoLinkModel.loadSettings(_this, "stereoLink");
m_mixModel.loadSettings(_this, "mix");
}

View File

@@ -0,0 +1,102 @@
/*
* CompressorControls.h
*
* Copyright (c) 2020 Lost Robot <r94231@gmail.com>
*
* This file is part of LMMS - https://lmms.io
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public
* License along with this program (see COPYING); if not, write to the
* Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301 USA.
*
*/
#ifndef COMPRESSOR_CONTROLS_H
#define COMPRESSOR_CONTROLS_H
#include "CompressorControlDialog.h"
#include "EffectControls.h"
#include "Knob.h"
class CompressorEffect;
class CompressorControls : public EffectControls
{
Q_OBJECT
public:
CompressorControls(CompressorEffect* effect);
void saveSettings(QDomDocument & _doc, QDomElement & _parent) override;
void loadSettings(const QDomElement & _this) override;
inline QString nodeName() const override
{
return "CompressorControls";
}
int controlCount() override
{
return 28;
}
EffectControlDialog* createView() override
{
return new CompressorControlDialog(this);
}
private:
CompressorEffect * m_effect;
FloatModel m_thresholdModel;
FloatModel m_ratioModel;
FloatModel m_attackModel;
FloatModel m_releaseModel;
FloatModel m_kneeModel;
FloatModel m_holdModel;
FloatModel m_rangeModel;
FloatModel m_rmsModel;
IntModel m_midsideModel;
IntModel m_peakmodeModel;
FloatModel m_lookaheadLengthModel;
FloatModel m_inBalanceModel;
FloatModel m_outBalanceModel;
IntModel m_limiterModel;
FloatModel m_outGainModel;
FloatModel m_inGainModel;
FloatModel m_blendModel;
FloatModel m_stereoBalanceModel;
BoolModel m_autoMakeupModel;
BoolModel m_auditionModel;
BoolModel m_feedbackModel;
FloatModel m_autoAttackModel;
FloatModel m_autoReleaseModel;
BoolModel m_lookaheadModel;
FloatModel m_tiltModel;
FloatModel m_tiltFreqModel;
IntModel m_stereoLinkModel;
FloatModel m_mixModel;
float m_inPeakL;
float m_inPeakR;
float m_outPeakL;
float m_outPeakR;
friend class CompressorControlDialog;
friend class CompressorEffect;
} ;
#endif

BIN
plugins/Compressor/artwork.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.5 KiB

BIN
plugins/Compressor/blend_sel.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 771 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 871 B

BIN
plugins/Compressor/logo.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 774 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.9 KiB

BIN
plugins/Compressor/peak_sel.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.0 KiB

BIN
plugins/Compressor/peak_unsel.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.1 KiB

BIN
plugins/Compressor/rms_sel.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.9 KiB

BIN
plugins/Compressor/rms_unsel.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.3 KiB