@@ -30,6 +30,7 @@ SET(LMMS_PLUGIN_LIST
|
||||
carlabase
|
||||
carlapatchbay
|
||||
carlarack
|
||||
Compressor
|
||||
CrossoverEQ
|
||||
Delay
|
||||
DualFilter
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
3
plugins/Compressor/CMakeLists.txt
Executable 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
@@ -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
@@ -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
|
||||
762
plugins/Compressor/CompressorControlDialog.cpp
Executable 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);
|
||||
}
|
||||
218
plugins/Compressor/CompressorControlDialog.h
Executable 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
|
||||
147
plugins/Compressor/CompressorControls.cpp
Executable 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");
|
||||
}
|
||||
|
||||
|
||||
102
plugins/Compressor/CompressorControls.h
Executable 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
|
After Width: | Height: | Size: 1.4 KiB |
BIN
plugins/Compressor/audition_sel.png
Normal file
|
After Width: | Height: | Size: 7.4 KiB |
BIN
plugins/Compressor/audition_unsel.png
Normal file
|
After Width: | Height: | Size: 7.9 KiB |
BIN
plugins/Compressor/autogain_sel.png
Normal file
|
After Width: | Height: | Size: 1.2 KiB |
BIN
plugins/Compressor/autogain_unsel.png
Normal file
|
After Width: | Height: | Size: 8.8 KiB |
BIN
plugins/Compressor/average_sel.png
Executable file
|
After Width: | Height: | Size: 8.4 KiB |
BIN
plugins/Compressor/average_unsel.png
Executable file
|
After Width: | Height: | Size: 8.5 KiB |
BIN
plugins/Compressor/blend_sel.png
Executable file
|
After Width: | Height: | Size: 7.2 KiB |
BIN
plugins/Compressor/blend_unsel.png
Executable file
|
After Width: | Height: | Size: 7.5 KiB |
BIN
plugins/Compressor/compressor_sel.png
Executable file
|
After Width: | Height: | Size: 1.4 KiB |
BIN
plugins/Compressor/compressor_unsel.png
Executable file
|
After Width: | Height: | Size: 1.4 KiB |
BIN
plugins/Compressor/controlsBox.png
Executable file
|
After Width: | Height: | Size: 42 KiB |
BIN
plugins/Compressor/feedback_sel.png
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
BIN
plugins/Compressor/feedback_unsel.png
Normal file
|
After Width: | Height: | Size: 8.7 KiB |
BIN
plugins/Compressor/knob_enabled.png
Normal file
|
After Width: | Height: | Size: 1.4 KiB |
BIN
plugins/Compressor/knob_enabled_large.png
Normal file
|
After Width: | Height: | Size: 6.6 KiB |
BIN
plugins/Compressor/leftright_sel.png
Executable file
|
After Width: | Height: | Size: 8.3 KiB |
BIN
plugins/Compressor/leftright_unsel.png
Executable file
|
After Width: | Height: | Size: 8.3 KiB |
BIN
plugins/Compressor/limiter_sel.png
Executable file
|
After Width: | Height: | Size: 771 B |
BIN
plugins/Compressor/limiter_unsel.png
Executable file
|
After Width: | Height: | Size: 871 B |
BIN
plugins/Compressor/logo.png
Executable file
|
After Width: | Height: | Size: 774 B |
BIN
plugins/Compressor/lookahead_sel.png
Normal file
|
After Width: | Height: | Size: 1.3 KiB |
BIN
plugins/Compressor/lookahead_unsel.png
Normal file
|
After Width: | Height: | Size: 1.0 KiB |
BIN
plugins/Compressor/maximum_sel.png
Executable file
|
After Width: | Height: | Size: 8.2 KiB |
BIN
plugins/Compressor/maximum_unsel.png
Executable file
|
After Width: | Height: | Size: 8.3 KiB |
BIN
plugins/Compressor/midside_sel.png
Executable file
|
After Width: | Height: | Size: 7.5 KiB |
BIN
plugins/Compressor/midside_unsel.png
Executable file
|
After Width: | Height: | Size: 7.7 KiB |
BIN
plugins/Compressor/minimum_sel.png
Executable file
|
After Width: | Height: | Size: 7.9 KiB |
BIN
plugins/Compressor/minimum_unsel.png
Executable file
|
After Width: | Height: | Size: 7.9 KiB |
BIN
plugins/Compressor/peak_sel.png
Executable file
|
After Width: | Height: | Size: 7.0 KiB |
BIN
plugins/Compressor/peak_unsel.png
Executable file
|
After Width: | Height: | Size: 7.1 KiB |
BIN
plugins/Compressor/rms_sel.png
Executable file
|
After Width: | Height: | Size: 6.9 KiB |
BIN
plugins/Compressor/rms_unsel.png
Executable file
|
After Width: | Height: | Size: 7.0 KiB |
BIN
plugins/Compressor/unlinked_sel.png
Executable file
|
After Width: | Height: | Size: 8.2 KiB |
BIN
plugins/Compressor/unlinked_unsel.png
Executable file
|
After Width: | Height: | Size: 8.3 KiB |