From 6f7443d60aed9d8baabba2ea3ddd537bcc4c6dbe Mon Sep 17 00:00:00 2001 From: root Date: Sun, 19 Apr 2020 11:42:05 -0600 Subject: [PATCH 01/33] Add Compressor effect --- cmake/modules/PluginList.cmake | 1 + data/themes/classic/style.css | 20 + data/themes/default/style.css | 20 + plugins/Compressor/CMakeLists.txt | 3 + plugins/Compressor/Compressor.cpp | 634 +++++++++++++++ plugins/Compressor/Compressor.h | 145 ++++ .../Compressor/CompressorControlDialog.cpp | 750 ++++++++++++++++++ plugins/Compressor/CompressorControlDialog.h | 236 ++++++ plugins/Compressor/CompressorControls.cpp | 146 ++++ plugins/Compressor/CompressorControls.h | 101 +++ plugins/Compressor/artwork.png | Bin 0 -> 1405 bytes plugins/Compressor/audition_sel.png | Bin 0 -> 7609 bytes plugins/Compressor/audition_unsel.png | Bin 0 -> 8115 bytes plugins/Compressor/autogain_sel.png | Bin 0 -> 1203 bytes plugins/Compressor/autogain_unsel.png | Bin 0 -> 9013 bytes plugins/Compressor/average_sel.png | Bin 0 -> 8608 bytes plugins/Compressor/average_unsel.png | Bin 0 -> 8665 bytes plugins/Compressor/blend_sel.png | Bin 0 -> 7371 bytes plugins/Compressor/blend_unsel.png | Bin 0 -> 7725 bytes plugins/Compressor/compressor_sel.png | Bin 0 -> 1405 bytes plugins/Compressor/compressor_unsel.png | Bin 0 -> 1435 bytes plugins/Compressor/controlsBox.png | Bin 0 -> 43423 bytes plugins/Compressor/feedback_sel.png | Bin 0 -> 1131 bytes plugins/Compressor/feedback_unsel.png | Bin 0 -> 8875 bytes plugins/Compressor/knob_enabled.png | Bin 0 -> 1453 bytes plugins/Compressor/knob_enabled_large.png | Bin 0 -> 6729 bytes plugins/Compressor/leftright_sel.png | Bin 0 -> 8464 bytes plugins/Compressor/leftright_unsel.png | Bin 0 -> 8488 bytes plugins/Compressor/limiter_sel.png | Bin 0 -> 771 bytes plugins/Compressor/limiter_unsel.png | Bin 0 -> 871 bytes plugins/Compressor/logo.png | Bin 0 -> 774 bytes plugins/Compressor/lookahead_sel.png | Bin 0 -> 1287 bytes plugins/Compressor/lookahead_unsel.png | Bin 0 -> 1043 bytes plugins/Compressor/maximum_sel.png | Bin 0 -> 8447 bytes plugins/Compressor/maximum_unsel.png | Bin 0 -> 8481 bytes plugins/Compressor/midside_sel.png | Bin 0 -> 7712 bytes plugins/Compressor/midside_unsel.png | Bin 0 -> 7889 bytes plugins/Compressor/minimum_sel.png | Bin 0 -> 8058 bytes plugins/Compressor/minimum_unsel.png | Bin 0 -> 8122 bytes plugins/Compressor/peak_sel.png | Bin 0 -> 7143 bytes plugins/Compressor/peak_unsel.png | Bin 0 -> 7264 bytes plugins/Compressor/rms_sel.png | Bin 0 -> 7046 bytes plugins/Compressor/rms_unsel.png | Bin 0 -> 7219 bytes plugins/Compressor/unlinked_sel.png | Bin 0 -> 8354 bytes plugins/Compressor/unlinked_unsel.png | Bin 0 -> 8461 bytes 45 files changed, 2056 insertions(+) create mode 100755 plugins/Compressor/CMakeLists.txt create mode 100755 plugins/Compressor/Compressor.cpp create mode 100755 plugins/Compressor/Compressor.h create mode 100755 plugins/Compressor/CompressorControlDialog.cpp create mode 100755 plugins/Compressor/CompressorControlDialog.h create mode 100755 plugins/Compressor/CompressorControls.cpp create mode 100755 plugins/Compressor/CompressorControls.h create mode 100755 plugins/Compressor/artwork.png create mode 100644 plugins/Compressor/audition_sel.png create mode 100644 plugins/Compressor/audition_unsel.png create mode 100644 plugins/Compressor/autogain_sel.png create mode 100644 plugins/Compressor/autogain_unsel.png create mode 100755 plugins/Compressor/average_sel.png create mode 100755 plugins/Compressor/average_unsel.png create mode 100755 plugins/Compressor/blend_sel.png create mode 100755 plugins/Compressor/blend_unsel.png create mode 100755 plugins/Compressor/compressor_sel.png create mode 100755 plugins/Compressor/compressor_unsel.png create mode 100755 plugins/Compressor/controlsBox.png create mode 100644 plugins/Compressor/feedback_sel.png create mode 100644 plugins/Compressor/feedback_unsel.png create mode 100644 plugins/Compressor/knob_enabled.png create mode 100644 plugins/Compressor/knob_enabled_large.png create mode 100755 plugins/Compressor/leftright_sel.png create mode 100755 plugins/Compressor/leftright_unsel.png create mode 100755 plugins/Compressor/limiter_sel.png create mode 100755 plugins/Compressor/limiter_unsel.png create mode 100755 plugins/Compressor/logo.png create mode 100644 plugins/Compressor/lookahead_sel.png create mode 100644 plugins/Compressor/lookahead_unsel.png create mode 100755 plugins/Compressor/maximum_sel.png create mode 100755 plugins/Compressor/maximum_unsel.png create mode 100755 plugins/Compressor/midside_sel.png create mode 100755 plugins/Compressor/midside_unsel.png create mode 100755 plugins/Compressor/minimum_sel.png create mode 100755 plugins/Compressor/minimum_unsel.png create mode 100755 plugins/Compressor/peak_sel.png create mode 100755 plugins/Compressor/peak_unsel.png create mode 100755 plugins/Compressor/rms_sel.png create mode 100755 plugins/Compressor/rms_unsel.png create mode 100755 plugins/Compressor/unlinked_sel.png create mode 100755 plugins/Compressor/unlinked_unsel.png diff --git a/cmake/modules/PluginList.cmake b/cmake/modules/PluginList.cmake index a2871bf99ec..263cb6fe6dc 100644 --- a/cmake/modules/PluginList.cmake +++ b/cmake/modules/PluginList.cmake @@ -30,6 +30,7 @@ SET(LMMS_PLUGIN_LIST carlabase carlapatchbay carlarack + Compressor CrossoverEQ Delay DualFilter diff --git a/data/themes/classic/style.css b/data/themes/classic/style.css index 65b496617f1..50c063f6bb4 100644 --- a/data/themes/classic/style.css +++ b/data/themes/classic/style.css @@ -902,6 +902,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 { diff --git a/data/themes/default/style.css b/data/themes/default/style.css index 832da176f28..f78c14f8b33 100644 --- a/data/themes/default/style.css +++ b/data/themes/default/style.css @@ -938,6 +938,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 { diff --git a/plugins/Compressor/CMakeLists.txt b/plugins/Compressor/CMakeLists.txt new file mode 100755 index 00000000000..111d0e2ec87 --- /dev/null +++ b/plugins/Compressor/CMakeLists.txt @@ -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") diff --git a/plugins/Compressor/Compressor.cpp b/plugins/Compressor/Compressor.cpp new file mode 100755 index 00000000000..942c4f6f2ed --- /dev/null +++ b/plugins/Compressor/Compressor.cpp @@ -0,0 +1,634 @@ +/* + * Compressor.cpp + * + * Copyright (c) 2020 Lost Robot + * + * 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 "lmms_math.h" +#include "plugin_export.h" +#include "interpolation.h" + + +extern "C" +{ + +Plugin::Descriptor PLUGIN_EXPORT compressor_plugin_descriptor = +{ + STRINGIFY(PLUGIN_NAME), + "Compressor", + QT_TRANSLATE_NOOP("pluginBrowser", "A dynamic range compressor."), + "Lost Robot ", + 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; + + // These will be resized later + m_rms[0] = new RmsHelper(1); + m_rms[1] = new RmsHelper(1); + + // 200 ms + m_crestTimeConst = exp(-1.f / (0.2f * m_sampleRate)); + + connect(&m_compressorControls.m_attackModel, SIGNAL(dataChanged()), this, SLOT(calcAttack())); + connect(&m_compressorControls.m_releaseModel, SIGNAL(dataChanged()), this, SLOT(calcRelease())); + connect(&m_compressorControls.m_holdModel, SIGNAL(dataChanged()), this, SLOT(calcHold())); + connect(&m_compressorControls.m_ratioModel, SIGNAL(dataChanged()), this, SLOT(calcRatio())); + connect(&m_compressorControls.m_rangeModel, SIGNAL(dataChanged()), this, SLOT(calcRange())); + connect(&m_compressorControls.m_rmsModel, SIGNAL(dataChanged()), this, SLOT(resizeRMS())); + connect(&m_compressorControls.m_lookaheadLengthModel, SIGNAL(dataChanged()), this, SLOT(calcLookaheadLength())); + connect(&m_compressorControls.m_thresholdModel, SIGNAL(dataChanged()), this, SLOT(calcThreshold())); + connect(&m_compressorControls.m_kneeModel, SIGNAL(dataChanged()), this, SLOT(calcKnee())); + connect(&m_compressorControls.m_outGainModel, SIGNAL(dataChanged()), this, SLOT(calcOutGain())); + connect(&m_compressorControls.m_inGainModel, SIGNAL(dataChanged()), this, SLOT(calcInGain())); + connect(&m_compressorControls.m_tiltModel, SIGNAL(dataChanged()), this, SLOT(calcTiltCoeffs())); + connect(&m_compressorControls.m_tiltFreqModel, SIGNAL(dataChanged()), this, SLOT(calcTiltCoeffs())); + connect(&m_compressorControls.m_limiterModel, SIGNAL(dataChanged()), this, SLOT(redrawKnee())); + connect(&m_compressorControls.m_mixModel, SIGNAL(dataChanged()), this, SLOT(calcMix())); + + connect(&m_compressorControls.m_autoAttackModel, SIGNAL(dataChanged()), this, SLOT(calcAutoAttack())); + connect(&m_compressorControls.m_autoReleaseModel, SIGNAL(dataChanged()), this, SLOT(calcAutoRelease())); + + connect(&m_compressorControls.m_thresholdModel, SIGNAL(dataChanged()), this, SLOT(calcAutoMakeup())); + connect(&m_compressorControls.m_ratioModel, SIGNAL(dataChanged()), this, SLOT(calcAutoMakeup())); + connect(&m_compressorControls.m_kneeModel, SIGNAL(dataChanged()), this, SLOT(calcAutoMakeup())); + + connect(Engine::mixer(), SIGNAL(sampleRateChanged()), this, SLOT(changeSampleRate())); + emit changeSampleRate(); +} + + + + +CompressorEffect::~CompressorEffect() +{ + delete m_rms[0]; + delete m_rms[1]; + + src_delete(src_state[0]); + src_delete(src_state[1]); +} + + +float CompressorEffect::timeToCoeff(float time) +{ + // Convert time in milliseconds to applicable lowpass coefficient + return expf(COMP_LOG / (time * m_sampleRate * 0.001f)); +} + + + +void CompressorEffect::calcAutoMakeup() +{ + // Formulas using the compressor's Threshold, Ratio, and Knee values to estimate a good makeup gain value + + float tempGainResult; + if (0 - m_thresholdVal < -m_kneeVal) + { + tempGainResult = 0; + } + else if (abs(1 - m_thresholdVal) < m_kneeVal)// If the input is within the knee's range + { + const float temp = 0 - m_thresholdVal + m_kneeVal; + tempGainResult = 0 + ((m_compressorControls.m_limiterModel.value() ? 0 : m_ratioVal) - 1) * temp * temp / (4 * m_kneeVal); + } + else + { + tempGainResult = m_compressorControls.m_limiterModel.value() ? m_thresholdVal : (m_thresholdVal + (0 - m_thresholdVal) * m_ratioVal); + } + + m_autoMakeupVal = 1.f / dbfsToAmp(tempGainResult); +} + +void CompressorEffect::calcAttack() +{ + m_attCoeff = timeToCoeff(m_compressorControls.m_attackModel.value()); +} + +void CompressorEffect::calcRelease() +{ + m_relCoeff = timeToCoeff(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_rms[0]->setSize(m_compressorControls.m_rmsModel.value() * m_sampleRate / 44100.f); + m_rms[1]->setSize(m_compressorControls.m_rmsModel.value() * m_sampleRate / 44100.f); +} + +void CompressorEffect::calcLookaheadLength() +{ + m_lookaheadLength = qMax(m_compressorControls.m_lookaheadLengthModel.value() * 0.001f * m_sampleRate, 1.f); + + m_lookaheadBuf[0].resize(m_lookaheadLength); + m_lookaheadBuf[1].resize(m_lookaheadLength); + + m_preLookaheadLength = ceil(m_lookaheadDelayLength - m_lookaheadLength); + m_preLookaheadBuf[0].resize(m_preLookaheadLength); + m_preLookaheadBuf[1].resize(m_preLookaheadLength); +} + +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; + 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; + 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] * m_inGainVal, buf[f][1] * m_inGainVal}; + sample_t s[2] = {drySignal[0], drySignal[1]}; + + // 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); + } + + s[0] *= inBalance > 0 ? 1 - inBalance : 1; + s[1] *= inBalance < 0 ? 1 + inBalance : 1; + + float gainResult[2] = {0, 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]; + + // Grab the peak or RMS value + inputValue = qMax(COMP_NOISE_FLOOR, peakmode ? abs(inputValue) : m_rms[i]->update(inputValue)); + + // 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 + { + // Calculate attack value depending on crest factor + const float att = m_autoAttVal + ? timeToCoeff(2.f * m_compressorControls.m_attackModel.value() / ((m_crestFactorVal[i] - 1) * m_autoAttVal + 1)) + : m_attCoeff; + + m_yL[i] = m_yL[i] * att + (1 - att) * t; + m_holdTimer[i] = m_holdLength;// Reset hold timer + } + else// Release phase + { + // Calculate release value depending on crest factor + const float rel = m_autoRelVal + ? timeToCoeff(2.f * m_compressorControls.m_releaseModel.value() / ((m_crestFactorVal[i] - 1) * m_autoRelVal + 1)) + : 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] = m_yL[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 + { + gainResult[i] = currentPeakDbfs; + } + else if (abs(currentPeakDbfs - m_thresholdVal) < m_kneeVal)// Within knee + { + const float temp = currentPeakDbfs - m_thresholdVal + m_kneeVal; + gainResult[i] = currentPeakDbfs + ((limiter ? 0 : m_ratioVal) - 1) * temp * temp / (4 * m_kneeVal); + } + else// Above knee + { + gainResult[i] = limiter ? m_thresholdVal : (m_thresholdVal + (currentPeakDbfs - m_thresholdVal) * m_ratioVal); + } + + gainResult[i] = dbfsToAmp(gainResult[i]) / m_yL[i]; + gainResult[i] = qMax(m_rangeVal, gainResult[i]); + } + + switch (stereoLink) + { + case 1:// Maximum + { + gainResult[0] = gainResult[1] = qMin(gainResult[0], gainResult[1]); + break; + } + case 2:// Average + { + gainResult[0] = gainResult[1] = (gainResult[0] + gainResult[1]) * 0.5f; + break; + } + case 3:// Minimum + { + gainResult[0] = gainResult[1] = qMax(gainResult[0], gainResult[1]); + break; + } + case 4:// Blend + { + if (blend > 0)// 0 is unlinked + { + if (blend <= 1)// Blend to minimum volume + { + const float temp1 = qMin(gainResult[0], gainResult[1]); + gainResult[0] = linearInterpolate(gainResult[0], temp1, blend); + gainResult[1] = linearInterpolate(gainResult[1], temp1, blend); + } + else if (blend <= 2)// Blend to average volume + { + const float temp1 = qMin(gainResult[0], gainResult[1]); + const float temp2 = (gainResult[0] + gainResult[1]) * 0.5f; + gainResult[0] = linearInterpolate(temp1, temp2, blend - 1); + gainResult[1] = gainResult[0]; + } + else// Blend to maximum volume + { + const float temp1 = (gainResult[0] + gainResult[1]) * 0.5f; + const float temp2 = qMax(gainResult[0], gainResult[1]); + gainResult[0] = linearInterpolate(temp1, temp2, blend - 2); + gainResult[1] = gainResult[0]; + } + } + break; + } + } + + // Bias compression to the left or right (or mid or side) + if (stereoBalance) + { + gainResult[0] = 1 - ((1 - gainResult[0]) * (stereoBalance > 0 ? 1 - stereoBalance : 1)); + gainResult[1] = 1 - ((1 - gainResult[1]) * (stereoBalance < 0 ? 1 + stereoBalance : 1)); + } + + // For visualizer + m_displayGain[0] = gainResult[0]; + m_displayGain[1] = gainResult[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 trueDrySignal[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] *= gainResult[0] * m_outGainVal * (outBalance > 0 ? 1 - outBalance : 1); + s[1] *= gainResult[1] * 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]; + + if (autoMakeup) + { + s[0] *= m_autoMakeupVal; + s[1] *= m_autoMakeupVal; + } + + // Negate wet signal from dry signal + if (audition) + { + s[0] = -s[0] + trueDrySignal[0]; + s[1] = -s[1] + trueDrySignal[1]; + } + + // Calculate wet/dry value results + const float temp1 = trueDrySignal[0] / m_inGainVal; + const float temp2 = trueDrySignal[1] / m_inGainVal; + 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 += s[0] + s[1]; + + lInPeak = buf[f][0] > lInPeak ? buf[f][0] : lInPeak; + rInPeak = buf[f][1] > rInPeak ? buf[f][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(); + + // 200 ms + m_crestTimeConst = exp(-1.f / (0.2 * m_sampleRate)); + + // 20 ms + m_lookaheadDelayLength = 0.020 * m_sampleRate; + m_inputBuf[0].resize(m_lookaheadDelayLength); + m_inputBuf[1].resize(m_lookaheadDelayLength); + + emit calcAutoMakeup(); + emit calcAttack(); + emit calcRelease(); + emit calcRatio(); + emit calcRange(); + emit calcLookaheadLength(); + emit calcHold(); + emit resizeRMS(); + emit calcThreshold(); + emit calcKnee(); + emit calcOutGain(); + emit calcInGain(); + emit calcTiltCoeffs(); + emit 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(data)); +} + +} + diff --git a/plugins/Compressor/Compressor.h b/plugins/Compressor/Compressor.h new file mode 100755 index 00000000000..5f3ea4da8ff --- /dev/null +++ b/plugins/Compressor/Compressor.h @@ -0,0 +1,145 @@ +/* + * Compressor.h + * + * Copyright (c) 2020 Lost Robot + * + * 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 "Effect.h" +#include "CompressorControls.h" +#include "ValueBuffer.h" +#include "RmsHelper.h" + +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 timeToCoeff(float time); + + 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); + + int err; + SRC_STATE * src_state[2] = {src_new(SRC_SINC_FASTEST, 1, &err), src_new(SRC_SINC_FASTEST, 1, &err)}; + + std::vector m_preLookaheadBuf[2]; + int m_preLookaheadBufLoc[2] = {0}; + + std::vector m_lookaheadBuf[2]; + int m_lookaheadBufLoc[2] = {0}; + + std::vector m_inputBuf[2]; + int m_inputBufLoc = 0; + + 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; + + sampleFrame m_maxLookaheadVal = {0}; + int m_maxLookaheadTimer[2] = {1, 1}; + + RmsHelper * m_rms[2]; + + 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_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 diff --git a/plugins/Compressor/CompressorControlDialog.cpp b/plugins/Compressor/CompressorControlDialog.cpp new file mode 100755 index 00000000000..7f1df85b117 --- /dev/null +++ b/plugins/Compressor/CompressorControlDialog.cpp @@ -0,0 +1,750 @@ +/* + * CompressorControlDialog.cpp + * + * Copyright (c) 2020 Lost Robot + * + * 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 + +#include "Compressor.h" +#include "CompressorControlDialog.h" +#include "CompressorControls.h" +#include "embed.h" +#include +#include "GuiApplication.h" +#include "MainWindow.h" +#include "interpolation.h" +#include +#include +#include "ToolTip.h" +#include "gui_templates.h" + +CompressorControlDialog::CompressorControlDialog(CompressorControls* controls) : + EffectControlDialog(controls), + m_controls(controls) +{ + 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:") , " dbFs"); + 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")); + + 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(); + + emit peakmodeChanged(); + emit stereoLinkChanged(); + emit lookaheadChanged(); + emit 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()) + { + return; + } + + int elapsedMil = m_timeElapsed.elapsed(); + m_timeElapsed.restart(); + m_timeSinceLastUpdate += elapsedMil; + int compPixelMovement = int(m_timeSinceLastUpdate / COMP_MILLI_PER_PIXEL); + m_timeSinceLastUpdate %= COMP_MILLI_PER_PIXEL; + + // Time Change / Daylight Savings Time protection + if (!compPixelMovement || 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_lastPoint = dbfsToYPoint(-9999); + m_lastGainPoint = dbfsToYPoint(0); + } + + const float peakAvg = (m_controls->m_effect->m_displayPeak[0] + m_controls->m_effect->m_displayPeak[1]) * 0.5f; + const float gainAvg = (m_controls->m_effect->m_displayGain[0] + m_controls->m_effect->m_displayGain[1]) * 0.5f; + + QPainter p; + float yPoint = dbfsToYPoint(ampToDbfs(peakAvg)); + float yGainPoint = dbfsToYPoint(ampToDbfs(gainAvg)); + + int threshYPoint = dbfsToYPoint(m_controls->m_effect->m_thresholdVal); + int threshXPoint = m_kneeWindowSizeY - threshYPoint; + + p.begin(&m_visPixmap); + + // Move entire display to the left + p.setCompositionMode(QPainter::CompositionMode_Source); + p.drawPixmap(-compPixelMovement, 0, m_visPixmap); + p.fillRect(m_windowSizeX-compPixelMovement, 0, m_windowSizeX, m_windowSizeY, QColor("transparent")); + p.setCompositionMode(QPainter::CompositionMode_SourceOver); + + p.setRenderHint(QPainter::Antialiasing, true); + + // Draw translucent portion of input volume line + p.setPen(QPen(m_inVolAreaColor, 1)); + for (int i = 0; i < compPixelMovement; ++i) + { + const int temp = linearInterpolate(m_lastPoint, yPoint, float(i) / float(compPixelMovement)); + p.drawLine(m_windowSizeX-compPixelMovement+i, temp, m_windowSizeX-compPixelMovement+i, m_windowSizeY); + } + + // Draw input volume line + p.setPen(QPen(m_inVolColor, 1)); + p.drawLine(m_windowSizeX-compPixelMovement-1, m_lastPoint, m_windowSizeX, yPoint); + + // Draw translucent portion of output volume line + p.setPen(QPen(m_outVolAreaColor, 1)); + for (int i = 0; i < compPixelMovement; ++i) + { + const int temp = linearInterpolate(m_lastPoint+m_lastGainPoint, yPoint+yGainPoint, float(i) / float(compPixelMovement)); + p.drawLine(m_windowSizeX-compPixelMovement+i, temp, m_windowSizeX-compPixelMovement+i, m_windowSizeY); + } + + // Draw output volume line + p.setPen(QPen(m_outVolColor, 1)); + p.drawLine(m_windowSizeX-compPixelMovement-1, m_lastPoint+m_lastGainPoint, m_windowSizeX, yPoint+yGainPoint); + + // Draw gain reduction line + p.setPen(QPen(m_gainReductionColor, 2)); + p.drawLine(m_windowSizeX-compPixelMovement-1, m_lastGainPoint, m_windowSizeX, yGainPoint); + + p.end(); + + if (m_controls->m_effect->m_redrawKnee) + { + m_controls->m_effect->m_redrawKnee = false; + + // Start drawing knee visualizer + p.begin(&m_kneePixmap); + + p.setRenderHint(QPainter::Antialiasing, false); + + // Clear display + p.setCompositionMode(QPainter::CompositionMode_Source); + p.fillRect(0, 0, m_windowSizeX, m_kneeWindowSizeY, QColor("transparent")); + p.setCompositionMode(QPainter::CompositionMode_SourceOver); + + p.setRenderHint(QPainter::Antialiasing, true); + + 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 + p.drawLine(0, m_kneeWindowSizeY, dbfsToXPoint(kneePoint1), dbfsToYPoint(kneePoint1)); + if (dbfsToXPoint(kneePoint2X) < m_kneeWindowSizeY) + { + p.drawLine(dbfsToXPoint(kneePoint2X), dbfsToYPoint(kneePoint2Y), m_kneeWindowSizeY, dbfsToYPoint(ratioPoint)); + } + + // Draw knee section + if (m_controls->m_effect->m_kneeVal) + { + 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)); + + p.drawLine(dbfsToXPoint(prevPoint[0]), dbfsToYPoint(prevPoint[1]), dbfsToXPoint(newPoint[0]), dbfsToYPoint(newPoint[1])); + + prevPoint[0] = newPoint[0]; + prevPoint[1] = newPoint[1]; + } + } + + p.setRenderHint(QPainter::Antialiasing, false); + + // Erase right portion + p.setCompositionMode(QPainter::CompositionMode_Source); + p.fillRect(m_kneeWindowSizeX + 1, 0, m_windowSizeX, m_kneeWindowSizeY, QColor("transparent")); + p.setCompositionMode(QPainter::CompositionMode_SourceOver); + + p.end(); + + p.begin(&m_kneePixmap2); + + p.setCompositionMode(QPainter::CompositionMode_Source); + p.fillRect(0, 0, m_windowSizeX, m_kneeWindowSizeY, QColor("transparent")); + p.setCompositionMode(QPainter::CompositionMode_SourceOver); + + p.end(); + + m_lastKneePoint = 0; + } + + // Start drawing second knee layer + p.begin(&m_kneePixmap2); + + p.setRenderHint(QPainter::Antialiasing, false); + + int kneePoint = dbfsToXPoint(ampToDbfs(peakAvg)); + if (kneePoint > m_lastKneePoint) + { + QRectF knee2Rect = QRect(m_lastKneePoint, 0, kneePoint - m_lastKneePoint, m_kneeWindowSizeY); + p.drawPixmap(knee2Rect, m_kneePixmap, knee2Rect); + } + else + { + p.setCompositionMode(QPainter::CompositionMode_Source); + p.fillRect(kneePoint, 0, m_lastKneePoint, m_kneeWindowSizeY, QColor("transparent")); + p.setCompositionMode(QPainter::CompositionMode_SourceOver); + } + m_lastKneePoint = kneePoint; + + p.end(); + + if (m_controls->m_effect->m_redrawThreshold) + { + p.begin(&m_miscPixmap); + + p.setCompositionMode(QPainter::CompositionMode_Source); + p.fillRect(0, 0, m_windowSizeX, m_windowSizeY, QColor("transparent")); + p.setCompositionMode(QPainter::CompositionMode_SourceOver); + + p.setRenderHint(QPainter::Antialiasing, true); + + // Draw threshold lines + p.setPen(QPen(m_threshColor, 2, Qt::DotLine)); + p.drawLine(0, threshYPoint, m_windowSizeX, threshYPoint); + p.drawLine(threshXPoint, 0, threshXPoint, m_kneeWindowSizeY); + + p.end(); + + m_controls->m_effect->m_redrawThreshold = false; + } + + m_lastPoint = yPoint; + m_lastGainPoint = yGainPoint; + + update(); +} + + + +void CompressorControlDialog::paintEvent(QPaintEvent *event) +{ + if (isVisible()) + { + QPainter p(this); + + p.setCompositionMode(QPainter::CompositionMode_Source); + p.fillRect(0, 0, m_windowSizeX, m_windowSizeY, QColor("transparent")); + p.setCompositionMode(QPainter::CompositionMode_SourceOver); + + p.drawPixmap(0, 0, m_graphPixmap); + p.drawPixmap(0, 0, m_visPixmap); + p.setOpacity(0.25); + p.drawPixmap(0, 0, m_kneePixmap); + p.setOpacity(1); + p.drawPixmap(0, 0, m_kneePixmap2); + p.setOpacity(1); + p.drawPixmap(0, 0, m_miscPixmap); + + 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; + m_dbRange = round(qBound(3.f, m_dbRange - event->delta() / 20.f, 96.f) / 3.f) * 3.f; + + // Only reset view if the scolling had an effect + if (m_dbRange != temp) + { + resetGraph(); + m_controls->m_effect->m_redrawKnee = true; + m_controls->m_effect->m_redrawThreshold = true; + } +} + + +void CompressorControlDialog::resetGraph() +{ + QPainter p; + + p.begin(&m_graphPixmap); + + p.setRenderHint(QPainter::Antialiasing, false); + + p.setCompositionMode(QPainter::CompositionMode_Source); + p.fillRect(0, 0, m_windowSizeX, m_windowSizeY, QColor("transparent")); + p.setCompositionMode(QPainter::CompositionMode_SourceOver); + + p.setPen(QPen(m_textColor, 1)); + p.setFont(QFont("Arial", qMax(int(m_windowSizeY / 1080.f * 24), 12))); + + // Redraw graph + p.setPen(QPen(m_graphColor, 1)); + for (int i = 1; i < m_dbRange / 3.f + 1; ++i) + { + p.drawLine(0, dbfsToYPoint(-3 * i), m_windowSizeX, dbfsToYPoint(-3 * i)); + p.drawLine(dbfsToXPoint(-3 * i), 0, dbfsToXPoint(-3 * i), m_kneeWindowSizeY); + p.drawText(QRectF(m_windowSizeX - 50, dbfsToYPoint(-3 * i), 50, 50), Qt::AlignRight | Qt::AlignTop, QString::number(i * -3)); + } + + 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; + + QPainter p; + + resetGraph(); + + p.begin(&m_visPixmap); + + p.setCompositionMode(QPainter::CompositionMode_Source); + p.fillRect(0, 0, m_windowSizeX, m_windowSizeY, QColor("transparent")); + 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 + p.setPen(QPen(m_resetColor, 3)); + p.drawLine(m_windowSizeX, 0, m_windowSizeX, m_windowSizeY); + + 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); +} + +// For theming purposes +QColor const & CompressorControlDialog::inVolAreaColor() const {return m_inVolAreaColor;} +QColor const & CompressorControlDialog::inVolColor() const {return m_inVolColor;} +QColor const & CompressorControlDialog::outVolAreaColor() const {return m_outVolAreaColor;} +QColor const & CompressorControlDialog::outVolColor() const {return m_outVolColor;} +QColor const & CompressorControlDialog::gainReductionColor() const {return m_gainReductionColor;} +QColor const & CompressorControlDialog::kneeColor() const {return m_kneeColor;} +QColor const & CompressorControlDialog::kneeColor2() const {return m_kneeColor2;} +QColor const & CompressorControlDialog::threshColor() const {return m_threshColor;} +QColor const & CompressorControlDialog::textColor() const {return m_textColor;} +QColor const & CompressorControlDialog::graphColor() const {return m_graphColor;} +QColor const & CompressorControlDialog::resetColor() const {return m_resetColor;} + +void CompressorControlDialog::setInVolAreaColor(const QColor & c){m_inVolAreaColor = c;} +void CompressorControlDialog::setInVolColor(const QColor & c){m_inVolColor = c;} +void CompressorControlDialog::setOutVolAreaColor(const QColor & c){m_outVolAreaColor = c;} +void CompressorControlDialog::setOutVolColor(const QColor & c){m_outVolColor = c;} +void CompressorControlDialog::setGainReductionColor(const QColor & c){m_gainReductionColor = c;} +void CompressorControlDialog::setKneeColor(const QColor & c){m_kneeColor = c;} +void CompressorControlDialog::setKneeColor2(const QColor & c){m_kneeColor2 = c;} +void CompressorControlDialog::setThreshColor(const QColor & c){m_threshColor = c;} +void CompressorControlDialog::setTextColor(const QColor & c){m_textColor = c;} +void CompressorControlDialog::setGraphColor(const QColor & c){m_graphColor = c;} +void CompressorControlDialog::setResetColor(const QColor & c){m_resetColor = c;} diff --git a/plugins/Compressor/CompressorControlDialog.h b/plugins/Compressor/CompressorControlDialog.h new file mode 100755 index 00000000000..4aa37d33f1f --- /dev/null +++ b/plugins/Compressor/CompressorControlDialog.h @@ -0,0 +1,236 @@ +/* + * CompressorControlDialog.h + * + * Copyright (c) 2020 Lost Robot + * + * 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 "EffectControlDialog.h" +#include +#include +#include "Knob.h" +#include "../Eq/EqFader.h" +#include "MainWindow.h" +#include "GuiApplication.h" +#include "PixmapButton.h" +#include +#include + + +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 = 1000; +constexpr int COMP_SCREEN_Y = 700; +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_NOISE_FLOOR = 0.000001;// -120 dbFs + +// -2.2 seems to be the best value for this. +// I've seen some compressors choose -1, however. +constexpr float COMP_LOG = -2.2; + + + +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 READ inVolAreaColor WRITE setInVolAreaColor) + Q_PROPERTY(QColor inVolColor READ inVolColor WRITE setInVolColor) + Q_PROPERTY(QColor outVolAreaColor READ outVolAreaColor WRITE setOutVolAreaColor) + Q_PROPERTY(QColor outVolColor READ outVolColor WRITE setOutVolColor) + Q_PROPERTY(QColor gainReductionColor READ gainReductionColor WRITE setGainReductionColor) + Q_PROPERTY(QColor kneeColor READ kneeColor WRITE setKneeColor) + Q_PROPERTY(QColor kneeColor2 READ kneeColor2 WRITE setKneeColor2) + Q_PROPERTY(QColor threshColor READ threshColor WRITE setThreshColor) + Q_PROPERTY(QColor textColor READ textColor WRITE setTextColor) + Q_PROPERTY(QColor graphColor READ graphColor WRITE setGraphColor) + Q_PROPERTY(QColor resetColor READ resetColor WRITE setResetColor) + + QColor const & inVolAreaColor() const; + void setInVolAreaColor( const QColor & c ); + + QColor const & inVolColor() const; + void setInVolColor( const QColor & c ); + + QColor const & outVolAreaColor() const; + void setOutVolAreaColor( const QColor & c ); + + QColor const & outVolColor() const; + void setOutVolColor( const QColor & c ); + + QColor const & gainReductionColor() const; + void setGainReductionColor( const QColor & c ); + + QColor const & kneeColor() const; + void setKneeColor( const QColor & c ); + + QColor const & kneeColor2() const; + void setKneeColor2( const QColor & c ); + + QColor const & threshColor() const; + void setThreshColor( const QColor & c ); + + QColor const & textColor() const; + void setTextColor( const QColor & c ); + + QColor const & graphColor() const; + void setGraphColor( const QColor & c ); + + QColor const & resetColor() const; + void setResetColor( const QColor & c ); + +protected: + void resizeEvent(QResizeEvent *event) override; + void paintEvent(QPaintEvent *event) override; + void wheelEvent(QWheelEvent *event) override; + +private slots: + void updateDisplay(); + void autoAttackToggled(); + void autoReleaseToggled(); + 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 resetGraph(); + + 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); + + 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; + + QTime m_timeElapsed; + int m_timeSinceLastUpdate = 0; + + friend class CompressorControls; +} ; + +#endif diff --git a/plugins/Compressor/CompressorControls.cpp b/plugins/Compressor/CompressorControls.cpp new file mode 100755 index 00000000000..ff2b6e5e08e --- /dev/null +++ b/plugins/Compressor/CompressorControls.cpp @@ -0,0 +1,146 @@ +/* + * CompressorControls.cpp + * + * Copyright (c) 2020 Lost Robot + * + * 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 + +#include "CompressorControls.h" +#include "Compressor.h" +#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(0.25f, 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(64.0f, 1.0f, 2048.0f, 1.0f, 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"); +} + + diff --git a/plugins/Compressor/CompressorControls.h b/plugins/Compressor/CompressorControls.h new file mode 100755 index 00000000000..6cfc9663eee --- /dev/null +++ b/plugins/Compressor/CompressorControls.h @@ -0,0 +1,101 @@ +/* + * CompressorControls.h + * + * Copyright (c) 2020 Lost Robot + * + * 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 "EffectControls.h" +#include "CompressorControlDialog.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 31; + } + + 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 diff --git a/plugins/Compressor/artwork.png b/plugins/Compressor/artwork.png new file mode 100755 index 0000000000000000000000000000000000000000..8f94e3d494b7e7577ec5c34ac46d894c54a0945e GIT binary patch literal 1405 zcmeAS@N?(olHy`uVBq!ia0y~yV7kD-z$n4N1{5h}Id}s|u_bxCyDx` z7I;J!Gca%qgD@k*tT_@uLG}_)Usv`gECNgtBK3D?DFTHQGD9Ltobz*YQ}arITn2`K z{PNVI)D(rJN(FbnP<@}wywZv}fz96-7+CIkx;TbZ+P>{iZgJEM3=Udr-bDLPr zrnwyQmw%rw%fOKE#0zK!n-l{Bj|l^VL&e}4icrB&`CR}2 literal 0 HcmV?d00001 diff --git a/plugins/Compressor/audition_sel.png b/plugins/Compressor/audition_sel.png new file mode 100644 index 0000000000000000000000000000000000000000..07ad76049dac87f31585507da68d02721926c07b GIT binary patch literal 7609 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 N002ovPDHLkV1j`fyt4oR literal 0 HcmV?d00001 diff --git a/plugins/Compressor/autogain_sel.png b/plugins/Compressor/autogain_sel.png new file mode 100644 index 0000000000000000000000000000000000000000..e61054a3e298ec9de72a565ffd5ed06b9626e40d GIT binary patch literal 1203 zcmV;k1WfyhP)7{3T<p29G)NJZQqcnaP-vlAiY=vI!+y=qTpyNpx9!w*7g%5RpWKHz zC-=<%{^y=E=gvs8^5=@dfooVHlN9bQ6jdWT$AhG(nkXzP!tKsXsdAU$^%`96>_oLf zCO&_Vk+t<2$51GiBJI&m0YZzjj{U?v`hV@?K&2* z&&$NvyOFG?KZl!dz%2D(yHu26AFl5@P!61* zo=k>}xw9N|NdeY9X-Gi}wckKH-8Es}beyf4QoLJN03dsVWNiKd0OQM=aQIj1kh(?y z@O``q*TFN$4ObEuJyF@sbq}DoU&EZAi?}ZXsoR+RSR7+#J#i2K`{4q-yVj7o>C*}R zz-w!oE4ea%W_^KWxT}UV&wK#GKyt$r`CyVqWEl5B81^I5QRreEGhql29 zcDcC+J39lROH;Wl&O;vZLQ`MjoJAxrCv%prjH0#m65O)faUJoDsGTn29-;DiXdQik zK&flT%F9Bm$PN3g5YBM{YzLcgC`DqL`3}DE2Yrj%>#H#8wqw+7$G^Y&zj6LoJ_7xd z=5R@>m=l9ztTEob#F-znT$l@T5xPR4t~T;AdaM|=sSjx&0JcCZoPpHbA9j}B8{Rc- zLsRb;buC}Kj=f(>N|~O(unokLztC? z;rKB)$1K&Y!|1>KNpSPh+b!vdbuV^xDU#vGz3p&>{Ep=WSIT) z37g0Mcp)r|K&Akht4AXegk`LA%QguaMhi6=6bvbt@=8?9jkE7 zy?}J;GUzI{Cu@9v#pFC4pQvkfUlZQF8$p$)X%XV-UgGT2&1D5>zxGU+ryOZPJX(UN zDnk1D68eXy2tHqd@9oFK>3nq04`V+u{sEANiFx9@ Rk;MQ2002ovPDHLkV1mZ!IN1OI literal 0 HcmV?d00001 diff --git a/plugins/Compressor/autogain_unsel.png b/plugins/Compressor/autogain_unsel.png new file mode 100644 index 0000000000000000000000000000000000000000..6491461dfe3aacdc4db8cceff7696e2bb2458b99 GIT binary patch literal 9013 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 literal 0 HcmV?d00001 diff --git a/plugins/Compressor/average_sel.png b/plugins/Compressor/average_sel.png new file mode 100755 index 0000000000000000000000000000000000000000..a5e87918ee367cd33f1f93a4c0b6ed2608b6cfc8 GIT binary patch literal 8608 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 literal 0 HcmV?d00001 diff --git a/plugins/Compressor/average_unsel.png b/plugins/Compressor/average_unsel.png new file mode 100755 index 0000000000000000000000000000000000000000..b7b4d9bd6ed8ae553394926f23fd8e9cbf3c0bd0 GIT binary patch literal 8665 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 literal 0 HcmV?d00001 diff --git a/plugins/Compressor/blend_sel.png b/plugins/Compressor/blend_sel.png new file mode 100755 index 0000000000000000000000000000000000000000..6cc6f817c731718479db5cc487dec1adae012be8 GIT binary patch literal 7371 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 literal 0 HcmV?d00001 diff --git a/plugins/Compressor/blend_unsel.png b/plugins/Compressor/blend_unsel.png new file mode 100755 index 0000000000000000000000000000000000000000..bba695f82e868b5787a663aa11595925ac8b2d48 GIT binary patch literal 7725 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 LNkvXXu0mjfeAk(x literal 0 HcmV?d00001 diff --git a/plugins/Compressor/compressor_unsel.png b/plugins/Compressor/compressor_unsel.png new file mode 100755 index 0000000000000000000000000000000000000000..887c0f1ed73172bc3d3f326821a09212c884a1ff GIT binary patch literal 1435 zcmV;M1!Ve(P)6jNDlf?rya}Gzs7M;Ve7%!Y- zY{(cUB8MjfIjjuaI0_{aC&p3QvdOB1c7G_<(o!H9Y=89o=1qQizkQ$g`+WQ4>F+H- zB+vqA&9mQUGZ1D0$fx-FIojFVV@Cf%rBrgW_=OP2)W zsHqU{9_^g6$u^cgkX7^Zno!o$OzoxAr@Syc$4iWx+skZ=-GNlr2tZ0|IzxvJVc~-LdZKss0a@Akyz^!_Rn;|&_i#5j zQ?5{vlA6xq;2_4n;-)hlpzUup=|5fI_3%(!M~^a@6u{_FBeAr!B=7Py6iOwPRW(L8jvG4$g+hr`c1Lf$?B*?&EDlB_SfhUQ7=Y}Y zJp5+OWcCbiyr)j#z`+Fe#qFZ&*$#HLM1+SR64_8*QK`q-4H-<>@}&rZz>ShJJuYt7 z4uke`{Peekg)ZmN=Ls}6HgfM?6B5ZSE?qAI;7UP}Nxm=n^`x)Ym2P2S@vr@GbaZGa zx=~E@+BMw${jOd`J#U;wqe0%%!iiI-^pfo~RjpQ&bTpYecboL~K4~u!gP7|!Z)ahE zKNCF1^WumR_-&erkIz&NCVokWMq`ri3wqMm6_&bU>oRL=>u_{%z|(z)K}pvHz2J7B7z=GU@{|&*vC$8)CMi zR;v+6l9UWusHv5jo8^DZyH2qJN!tBHV;vYHodn@uo=pX<9002ovPDHLkV1iVRv*Q2& literal 0 HcmV?d00001 diff --git a/plugins/Compressor/controlsBox.png b/plugins/Compressor/controlsBox.png new file mode 100755 index 0000000000000000000000000000000000000000..0c46797e91066862063ce0bd071eb153ec81e3e6 GIT binary patch literal 43423 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 AWB>pF literal 0 HcmV?d00001 diff --git a/plugins/Compressor/knob_enabled.png b/plugins/Compressor/knob_enabled.png new file mode 100644 index 0000000000000000000000000000000000000000..d8bdc6e64f6f9a335210342a9644ff0911769ddc GIT binary patch literal 1453 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 literal 0 HcmV?d00001 diff --git a/plugins/Compressor/leftright_sel.png b/plugins/Compressor/leftright_sel.png new file mode 100755 index 0000000000000000000000000000000000000000..9949411da47f7635ee7e81c3c1d739597830b153 GIT binary patch literal 8464 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 literal 0 HcmV?d00001 diff --git a/plugins/Compressor/limiter_sel.png b/plugins/Compressor/limiter_sel.png new file mode 100755 index 0000000000000000000000000000000000000000..2d0e054af8c28f29bd4c542792864cc6b071da98 GIT binary patch literal 771 zcmV+e1N{7nP)*P*4mt2N4gcsE5|89*shJ zsEUeWO3+9SR?%vu_(G}ptmz|dW;`s$E@{#zW+-((7})t9^WXjNH{U$K72_fRy{KZb z%Y-2r{V{?E0PT`tQam>2F6+igcR8O&nVv}muuk^E)u&mVKIlX3_1T?N8f(j$tgrZ- zLN<42eO1?Pbp2{%=Pg*`&56rAIz012HT)g9y^BmD0A(JBO-7Q46E}a`l!nv<^6qoQ z(QET0)3tMa(bO&}x_TF&+iMXQ60mkDdi@F*P3MiXYEIjMyVj98)`GPu zoC9wKdmKMK$JRH5Zry-t+lmz*g6KfWNtaG${&k~(o*bQwy+H4%vFBb}J>uDWm{POE z6~bcncN-oZcSU{t~sn5%Km5+<8S_){DfE!6;mrxxA>5-+maM zM4r734?h^G=?2P6vAJpYMRyFm9z-OWXAJ%Tr!c+@Mv?6oVSEz3vC2Sc(bGwvLo)ak zSsRASPt4%O-yu(3N7t`GcQhH?GM2EssE{9iO=CdxK1R1x+qU}wL|xcf9dw?mai{^k z&o^US)=w9KA4x*+h7+%608v?vKF|o?#vmLk{oTvLGOv4LF(m1U@zQ6yEBOvb?md8U z(m!dXDuoa(8>QYk+a={Az)s+eq5abT-d+REKLHgM3F}&F#9sga002ovPDHLkV1fvA BPtE`U literal 0 HcmV?d00001 diff --git a/plugins/Compressor/limiter_unsel.png b/plugins/Compressor/limiter_unsel.png new file mode 100755 index 0000000000000000000000000000000000000000..415641c5f353cff1ee72acbe952d887ea8422f18 GIT binary patch literal 871 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 N002ovPDHLkV1j_a>bd{` literal 0 HcmV?d00001 diff --git a/plugins/Compressor/maximum_sel.png b/plugins/Compressor/maximum_sel.png new file mode 100755 index 0000000000000000000000000000000000000000..7e9e9ce7449591a5e32e82c7e92e3124a4afdc46 GIT binary patch literal 8447 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 literal 0 HcmV?d00001 diff --git a/plugins/Compressor/maximum_unsel.png b/plugins/Compressor/maximum_unsel.png new file mode 100755 index 0000000000000000000000000000000000000000..c389c41297f62cc81fd3509d06368c75c3cb990d GIT binary patch literal 8481 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�SBWlpGW5ATqxI%9A^W%4gpRVRf+0nmGJKF4<4^a-?ODho+6WkEG?PfmiF1J=Q88n~hBd9B7GmiW)SR zM!RfRLJTi@kUQ_y8ouw3=!$Xv#``qbHXtAivT@VgLc8Q0@6`iboJ}GzAE}Ir_fymp zl0}uZ=MD7~f(b=NJQr>pmOCG_KC!?B30yzXA*r7uuuy&Ptm$0LY~|yrO^+_(l5_J0 zV7KODv+g5ce8FvC^GSz^0e6;TArX^L9I&|A)P9E3Y?H}C_n?W$PESVqIqTG)9!Jml_N>V;+pD-1XZ?r13q#*|4ey)AKQMp!*{olW8>4*IFTdh~y}jH+ zAFd#ykCT0G!|?GryT+KbE#9LOS)pBfI~}Asc^MdZ&?Nd{&d$^Xg(G`_F?cc-0Q-7S z=t(dzsA~99FgQ1WCWZxwBu_Po*{V7TF%n))!dB4~YD&=tTu27~UVyc~85-yBhC|{d zG}PHueNl7*4}gXd^Yw7|q@sM)B(`}`^zU0{h=kZSgyyCuVP|R~rcL$&#Nc2!7z)z$ zC7ppusI!Zydf^EuD;>R`6!ejrgbR&EK|vrsK0aU{1u)r*2$4r3kq{^h0)v6*2oTlJ zlZNpHc~T{}D1LD0092e8T{#ljQ*4V9gC%>@)FdS6^I|{Aw{+F@r5k?+*cxxsQ)zgJ zA$dn#5KDC_EWQ!lQnCB`7K(6agei5sAZt;7Ax2gjIk8AONa}$Kx<~0s^D-3zeZK zm4@-e0b5jbaxjU`gH*)e}pd`KP`A^@Ry61OMlNu#vA01S=ng(j2T)g-o(65I0JZU!;c zpJk3RA>%Mx?XV37@LQeovsbh*M9B6~74lc$|G{MKLiX|ezw!JI{f$M_i{?Z2ax?cb z$GQSI+F$eh8TdCQEBY=)rFr=o{+o;XZ#dN-b!kBNC42e(65kp){bTfFD{&`nOC=_@ z-M%OcZc9Zq2`c6cfZtvMx{g1la4r~6B0%3Ke>Ta#?4&=bR}2gZMIvDc5FScEg5U}S zMUb*QLJ6cyz+zzpc>pU-5@nf~cR3TeCAHIBRbT#hnCr z{ZXfX)y@Bc`^Eofjs83LUtvGIwaFAedb7IF%zZrnGW?$ae=wMkaDXS3{8y>}3i%<+ zFEyPJ9-|_J4q~6h*SQM7q7XSSVunA}^~*Lka^&{$jJ(q=s~d*~#?CtgXJwhG3evG{m{)FeGJ zxkT^m<8O$0-|Cw;O@2-)>sahnrO;}JBhFJA_3zcx&UR7~CR0v` z(7Y`z(1pqFl=t;sonTlgXE3P+8v9cDV#dpFCdJ*6yDiGHuYVr={B`3zBUwvN4<(&? za(rs)W~rDn&Y`j@vLh#6*0ClintwD+Eg+^xw4F0$pfe+QwIC|}u0fy~SASIeNYk@V z0&(faAEGP!+n%RH)MQywi5{01EcP<@@)S9IA7xh{;9YfQHkK}yOx6)1iej%{-zTT> zevg!K`cth{?i;63ic&sO& zfy<%E$(@Ry^m2H`cXo7iJk`_7iWLGEH2ntB)g>K3P{w`-xZ5} zrKPnxSmPtAYk6H&R=0-m{N^An&}j4oeKIdR!RGN literal 0 HcmV?d00001 diff --git a/plugins/Compressor/midside_sel.png b/plugins/Compressor/midside_sel.png new file mode 100755 index 0000000000000000000000000000000000000000..4ceeff92fba13ac98aae40bce9be06e1755aab02 GIT binary patch literal 7712 zcmeHKc|4SD+a3{8WX+N?#$Jt?VTKvTPRJU{8jV>PX3UrwTTGH7R3e3xB}*yFUX}7Jw4v2TAu}lgm-`a+Hv>oZK1Q- zXO%vxIF`r{ggyrq=to%&s85B(S%WBXGpg6?;@`}!%p||Nw7lG#yEr1N7d>b`(UG+x zoNW`@{r2hPxi*`d%<6r5Yi-s`J8JcwzO)Id^QcZM7s{Ux)3~H-RO85yzj|n(Ps-*) zu3O)-G+NuW9~h`l|I%->yHb$cipW5h4lf^{ta0`P-;>D59S2cd#rI~9izi97jz2yz zjBYo;M_y@jtG%mGH+w#)_L{bOhGlD_Z*p@SOU~ znnlB0*SiCO6Z6`WjQgJkySmcSf@E~-vgZxHdUeh~npQ$PXL!o>3u;%!d3@k$%$=a< ztKLVsJ#z_y_w)gV$q)B^b@Saaw5b$sc&F@yOE+M6NPipG*b5V#@TZeJw6z`gOvl1c zyDmC5>2SPRj`7N=q$!e7`~bN>$pEw$(Gqq4TxA|*_RZZZo!#AVn@E?p?J)Vi zsgR`vpOAZQ+M~`(r+4#koYu4YPN+TDXYCrq3I42d%_boR&?)UnQgZZ&FQFd^AthUy za9LAbFIV!|-RGr+&ZI2FmOPkvP=8INH&v04(BeD7`3M}~OW~+=UMgHsU1e;$ITF*( zxm5YW?fjGk$8jralqq!iYB5ARcv2_Ci04@smmwBTqx6h~wf)^X6?Nnm>UWqbZ1Y~9xWXYVa ziGT4bOz2Xd<@dN{ynD(a<*^P2qKk$|@^jY1ZVSq;RVh^qMrR+Xmt~ye^FHm$ zSA1b0YAD7&W*Pn_W2m0dTd?vu#BajdJ z>=djko*7h=M|BAuFGHeR0RuJf_A7gR@)|PwP|MVm6yY>*5lyph5Rj-8;%bjPQZi;= zbJ;%h?lS|fEf2-fnf>xIm1L!FO7cbD1Z)DTtz{)u)54kq$MoBS>vvnt+RgrEO)vd8 zZ`tQXTd0bdz??r7_$>)Pi=Cl!oz1&8lMrQd21RQq%|DQ z&({XZ-h1l#bfDMVpD|tZjImh?<33pgjSKcYo;xomv`y7Qu%T&`*&)7iGZe?O=TN}( ztD|&L7gO2!z=4gn74na(sD z`D;V-OCoCao0sPFIY&486%`0_6zT6x!xpiEJA~6-lzn*EVH-CoB-e68 zF~3lsqv(j{d)N27#I((RTMYd2VoCX;pW@YPFQB?gN}@9ZRXNG81#ymp{MgiB?VR_{ zpQt`TdL)^V%^z26GYaF+mKLG;n&0F*w~0rm*4T=@j?D!f7Qgc9%uW1ZUYWTQd(pX* zV$W}0xMC^V5`FdF{;R6+9EQ<}rcM)*&xx}Ga~wx;Na;F<)KQHDL5P-2MPzbJJt*MG z!1z`Q7xRniqkRiVif7L>inNDrHxgRvenRmpA-=Xld)-$V%*neax5g`V?TLVT-=s|?TUyMaDKcX z=!0-R7X#`+iWN))k=2ZrbVMmI>wIE%uVn8TEl2^s>1u}>XIi30S`_MGJ-72M2cf*} z%_yIbG{eMTJ*Iuf;iR5TL_x<}u%!c4jZp&8*R5^!q-5yj$)^k0*lje! zGb_e{)Xs{IHs(t^Z3^l@IStu2Cfn(KACIVMzAub8)pfZ-MLFft{Lvk%!eNSYPhJ7- z%&Z^B2O8UP-7vk>d72xRyq}CtYp;Kme|qctsE9YIylG?IkGF`9X6A4f?~d!g{$1!> zGY!+g<)ITp+WWrLLXiInzvw1<{EPYT7q&K4dih||lZ;h5X~9A7)NUkA^X{@<27N}H z-?48z@m{LBB}jMkl|lV+167+72*Kz&2l?h1PB}iwX3RM#*-cY-=hH~pdhrN=La|qF zrb;?N!C}d7CW%y-V%P{8WE|+Uaod#C-Rf^BMku{~Gv&?}kb9P}|GptQX46d$=`Q=d z{$&z5$F3fNhAPX5rS{x;>zZz3-#|XK{Z_>VarZBJ4>P8g#E=Y0> zD50XW#zk*Av8*7Sc%5}j_59QGDUr&+vd^bFG4=cpp}d@HmI*=t5rp%N>5;qflXt5U zq~u(mDD|x5l;u+EdE+H>>-$Pd9G=!|y|a~2Bzw5Zn_CN0$DXUYuif4Qf$bIT3!ih{ z0sw4xAhJ%wPF9v$SPEGcgQIxjRfEY?Rv7>QS}&N2!TRDEKu^3E(N707Rb2-H5^*{p zM}!sBifV-SCYl|i;q8xEJ7ACbVo^Ado-Q9cSc^qK#xpR$U^2;%t`)2UTIbbbeP>TY zK)`hf!&e97WMvC9qR{X_xGG!~3N{HQ2C9K{`G9B|j-X{{y#FT!Yor76W-zE)5J*r^ zkZO>+Duw0+fuT?+2viNCrUqsq!1NG51|}HnN0(z${NOOg)3G#`c0`IFkj;tlqy#W@ zKp@sS@FzK2S(9MaC=>~Xs)5x|kd5)IRV%As(SG!w zDzfy11Y@WWm?{)PCjUzWonaFAN4`IepgXW$N+5Q4IwgRH#hV1;{TOl^lTt|m^o=+jdfg-(0m3S3J@dvGQ*$fZUomVMy@+J$dITH25sAb8!chZgq;(7q3&E4{WL6<` z7BlQ$@T~Y>0e^Zq>*VV~X&F z0s}^As$s!MxP~U&6QzNKp)@y8nfuWh7(XnYO~oQtC9-%l;7BM=T>}ZmYpNl^a0C_) z#vm|oFjNDD#G{ZXHH0Q^1BDHZ$g(zuw6Q8S6^=#asfknbBtS7>930NB299G<5wKts z4u?b$2rwuX`-6%ttd@bTxeiE86}nEoK4VM5FbEVHnbj{uKO7~9{>PF7k&L%zVA!(3 z5SnTla1Dey0tHuxtE>G1I*g~$S-xen!l0_i^#vSOYd?z;!?F*NjPb%lsD57S3#_8G zjA(cagFFPxg*m9HYrzm&>Il$}yA4D`*gNCzSBqxv z9adIa8}&f5_ZBU4w&Cmps8kXWPy1J$e)rA)g4+=P)1&_`d?V~fv=N0G!fIA;hE0&) zU#9;P;132%A{OsQr~Fmv8zDbr+3)~i)%;_Q^=ODixbU$6fQv5&p^V5C*$rM1~ zKS}Pek6eFCfxiX*$6f!(^|uuGTi}1(_5UUp-yg5act6&;FNpObEF9M)$9jq8 z_OviH2H3HG55AaaXU%M(nmN+}09DqisvJjfu3jK(l80e#Wy14eE60xQa(B>5(EtFa zp1HAsgHz)~GR3URQR1my>y*y{d{qT^{;QT9q>p^f_f<-yj7gWb%@04u8T#?>hh469 znmB!la!gLWH?6YhfwH&5;hT?^c+SOsG~~%EPlzh_$8Js2Dz9E1Mn6CXOwdZpCLbvG zL!^;w>9beg4x{U4O##<*)z6(8%udK!Guxyu0v*hke}*>DnQXfuOfOwvO3V#9l*@xw zvQ8%NO{i`}j*XfcFc)^aq{P_=?P#}$iJ4SjdZT^2(l6{%F+VC=b3A8Q;N|M!Z~38& z=`!4NU%LOHT?Pjkr8}8HsHH*6X`X~y*`dxuxK@+7XV<-EM0~=Ht+CoAW6y`qGpwxk zYl;EidPF^z{5+Nl9puy>RUO>643(+&7YpA>7{28fC9qv|=;(m6A+_h2TkQvGMbG)= z)SZi=f~p}8uf^mmH=z2X=$|#4tWDp#Z;Es0X6owQ86_YC!GwfK!RxyVxn7tP`9U21;OMqkLiXT(5Vom2_kdF>)Me_=&o zu}Eb=@=>PbCtFnB9nlqU=OH)6&pbdU`vcQ0j#;?CJLmRf^h)P&M1F6Nd{x4|IOc&2 zx(LfR9k+`AgmAbaGbDFpx5RW`Rj2{Zh_eJH!<5@v;{MR#L2QA`2@*t>=Q{_@ z`rmwub+$m7;Su3 z@3@|gd;U$s_=u`yf4LnY;GMUVQzmSP^Xq;?;`ef->za>Y(tk^(rkY7hv|9$qun z3l||i5YlbEeed9cA;TUP_GL%#N*j|WUx&PW$hzOgDb$L1bKkiMTjn00Fs?SDO8&(K zg<@sSH+4cPeLR}nDWfwhQ{`5>pi;5sK^F_DD>1ad}5 i=rsyA=K-d_G7~l(xUbdYbA{Di0CN*-<2!~&PyQEuxPXQL literal 0 HcmV?d00001 diff --git a/plugins/Compressor/midside_unsel.png b/plugins/Compressor/midside_unsel.png new file mode 100755 index 0000000000000000000000000000000000000000..8f39361950657e9e8228ee8bd3f8839b2bfd4ed2 GIT binary patch literal 7889 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 literal 0 HcmV?d00001 diff --git a/plugins/Compressor/minimum_sel.png b/plugins/Compressor/minimum_sel.png new file mode 100755 index 0000000000000000000000000000000000000000..f6d258ce3276d4b6a589df1c471a9f92a1e63f07 GIT binary patch literal 8058 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 G^1lG3pe`i< literal 0 HcmV?d00001 diff --git a/plugins/Compressor/minimum_unsel.png b/plugins/Compressor/minimum_unsel.png new file mode 100755 index 0000000000000000000000000000000000000000..4ebacd6c6570b417590b219de48e05e09ca5b701 GIT binary patch literal 8122 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 SC8?m>AeLsfrss`4BK`}OC1(5p literal 0 HcmV?d00001 diff --git a/plugins/Compressor/peak_unsel.png b/plugins/Compressor/peak_unsel.png new file mode 100755 index 0000000000000000000000000000000000000000..91673fa96f76b69372c48455051067dddc0e7bb7 GIT binary patch literal 7264 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 Z?$IUADPoJYCs4s4f`zSlk*W9o{{d^GmVN*L literal 0 HcmV?d00001 diff --git a/plugins/Compressor/rms_sel.png b/plugins/Compressor/rms_sel.png new file mode 100755 index 0000000000000000000000000000000000000000..aa082a2f1cac72591d02e645ca4d1a6e20381ab6 GIT binary patch literal 7046 zcmeHMdpwiv|6dXj9?~O~!wfmpHrp6m4q=mF&WVzmZ5Xr74oE~#q0&hRIrI?8DTL&h zPRb#jmA~v z8%04NkT}5{XAgW^1Mjt~g@Dhg{=sMvX!WrmM`x}*DFDo3Gd*Z@DwunKMFmp>X&xX@ z;ETS5J^m`?;<)*8QTSo7h=8la#eqO!w;H&EDlKCyv8ZxxAjfIv=BR>`Ac0#Fv-{@t zdlakePbr+Z(B|axcIR$dHm7G|bm|kdNdMxa+;?rv52Xg?L33xl8iEJPI=^fLr})pw z?eBYTSaV%Cso=m@ohKua(;^@D&cHqm4-_0{2l>Reg?JVCRe$aAa|`vnJm4wMGzz&1 z7m~G1glC=gKQA{kS!v-RT;A85^}fDqH1^}O^kDDSi{$l&u$zbD?>{PvntQGs8VL3ukk`Je z0`U+H<4ni6NkRs~vkgBRj=wh^85micM6y*(ND+SLS4BiP8j`md>3e>mZB?qtjA#!c zf^d2H^oT*&+AT`Zo#~kr$m_xP&pHk?sEW%=UcAmy#`O1g%Jcc|OUEa*0;mQ-PpLsr1ixmN$kjVSCx|$X3rumYs5q4qHC#M2`AVdB-w%IvuRp}~^WLSy`Q$^-Ycwp2xdHOB$IBm5 z&ee4};Z-U+V5eFP+gg*mBQtudn$ zTy5$5=AFOhxPAJ4>qt9Wj9^ln7$Vb)NeCS@sha4ZmZqqk&TdK0bHG%*xs2WBqnv*Y zIwbdGH{7B1{b*F=_2zF4hJHol4mA~-P1T=Y3}eo;#@GpbO6_`L9A6p^l_c^ z^FHNx^wrGXeWu$hg`#;D$)@VB&C8(pouk9>-Uv|aj`5W1C5^Q~4-OYv$`pmAI*B$j zh6K5LiGdq6OE)W>5Q%@-L}0@@yp8LJhR%8#$U9km+*Q#~prGe3fWZe@m+rHwSu&pl$1{rVKXIq+9?|5l_ zN^raX?LuZQ{RBSq$k*<2>ua{_-WH*Yx?(zR34}eDODjkf?*7g3*cudW@_<6vYy&SY z&MjVXX1>ql3AgX&JEoxb*-9((DKg_?*{xxy7_aI``4`leQIhYBU#kY1*k zn~(3x`V*2J5SZphHr;Etgu$=rjSqH7y_83EAG@@uwVD0>bsflu z#$l-GhV-KxuWb{0Rf&>o&J3Ke-adny9l;;DVPsl=@x}Fc0%S{nLr7;+>{+rG3c(Vu zlyn)i9JP3vI&wqvO|5&wOtc!$MDnmQLS5RL{h&4hpJ-*K6>mzIs6|OY3Qc-eO$-N` z{`#Qyc=@TjZbEjGPRBi?qrrrp_Z*M-!^BDfZ0QmV`~2M0_`A><&#;I7BILP65dTql*H)@6aNe}!|CaIAK5ooca&O$iLm`1J8$PHc^MpMV3M#}4Gx3dVG1 z63~&s1+m)Aa}lYEXFH}8YK4Sz8+|~;$vbNkPbhF;D>PUQvZ3!E$R#|Gp-pa{h(@=E z&-AUEG?0$V+?&=Z^G>-7^YrtpT#(FuI}?ffYgCd7X=sUNrOy^`{dc%3MwfdVGmXK{ z$2``e^7p3Hu?M~sC%sqvG~1cgp75nx6Y|UfAUU{

GSa)! zwi<6>YvTUHJqeA)CG+$-|P9Dq&EFOD=|2jon& z!jhT3FcO97PK5>fvVdWLK>9|3EE3s=$_2YqJ!uRB$av*V2$)7Ofb7yCY7tqcR4}JrI0U=^;rbXroQbwz zQzn}V)`n@rw4nGvnm-a^C<@kRQ#`QtII|@Rpk)B@;&NG7I6NRA02Y9PG1;DQ1O|hF zYa!uCBoshEIR_YAQXrJUQRY)Da^R>OG8@p2#$M5(8fz51b_uzeh7TODY0-!UIVxI0B{x_x1ft z1c!_F|1;kYBRG!0Mig#O_$&mR0favQEQLv?QLqb#ND5MyOmWwSl2N*7s5VN^14`1PV4z5CgtnHhI~s#Q zqLxt+7#uE%L8kJl0CE@&;30dUJ#;DV7^tqUI|8arK_Q_ST@(t6q-vpc^e|LC1VU>W zg$yd$fJ*Wr5LmR41)pLhpwE0aPw643ho`;8??jjXmSgf%v z!2p7UE$gwRleivCwl7dGGzNtkz*)$<0CuGLQXRM?zHA5`T_jo?t)ru(r>Bk5(fbqR zL}haT-||@zTCl|v6f)Kfpd)kPuKE6bT7f9{3<( zbVyJX8lg?mK~r^gC^~;-=P*6E0VFon*b}HC;5q{}v~Zol>Pt4$_$xlZi^}(q76JoB zAfQ?ZMtOG!?dU0(67=N4o2f#%JD;k-~;4uGg=*uCCvMjrS0ApV4 z18z*feGdNpMz`b=ej)uA|CZeMU(5hd|4j0a^!+KjP+4Em^4@aK@Jt8b~7Y+ynQ|7-{1ZEof z1D(QL0ue9VyJm&VFS;fDAHRS=0!0Lzv7_^y(RiY{qn&KT#aUg{sY@B;jO_No+mfmp z7U9RhnqprLzZ5i17qm0R<%(+RO5t&~Tef&nGaRGL({a$*;@juUM?J40Tkdi%_<1BB z9DgbE?3k(X@SEoT8WwtbTjarJPV;Qa`0B){k0&RJNL=E*r}L`yq8rAx3KE>eY>Zb} zXEmlH*Q`3SLa54(ve!W*T>0yVGtQ14N_e}++P_L|-w91>cJgyVf+9##iI*s#;=-BT zK8SZnoOnjPhMc^jowX6+SYxlTeNKT*XcD&aux(~!$(qq)#PKHq@sclg|2F2YbOrmg zUFEcJh^PJ5z1t3p?Ex zb0MxhC{n7#ODtJU9=BqsTUb_9scIu0^ZfXzdG3&7dE zO0cdW{3CPYf)A2zac7>k%Wqn_I-pv8-~EYwFO08{HbsBo4ek*oCe1e;xWL#se_RuE zwD82p8`GmT$L~r6t($%JNN_seSRr|P;0(DgPKJ8x>gM$4_`%GMLChKvdCj|a(Pu`z zeAec9C$xmM#6UAfs}IYzK5k@xJF!xIm)m|l3cajbWo+o;A$JF(h6#T;UYQ>{K-wkM zr+ec`m6tgap&|xGeyKF-hjuePIWxoQtxC<)l2@m8NmuO$y+}p2djx5iyH+5g-B0Qf{b=l}o! literal 0 HcmV?d00001 diff --git a/plugins/Compressor/rms_unsel.png b/plugins/Compressor/rms_unsel.png new file mode 100755 index 0000000000000000000000000000000000000000..0221c7243e94578b2bdc733d2178db45d531f8c0 GIT binary patch literal 7219 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

PI&Oos^&NUV!2EToPX|!uE3|#Ei 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 Date: Sun, 19 Apr 2020 15:44:55 -0600 Subject: [PATCH 02/33] Fix some bugs --- plugins/Compressor/Compressor.cpp | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/plugins/Compressor/Compressor.cpp b/plugins/Compressor/Compressor.cpp index 942c4f6f2ed..f2120282f4d 100755 --- a/plugins/Compressor/Compressor.cpp +++ b/plugins/Compressor/Compressor.cpp @@ -530,18 +530,17 @@ bool CompressorEffect::processAudioBuffer(sampleFrame* buf, const fpp_t frames) m_prevOut[0] = s[0]; m_prevOut[1] = s[1]; - if (autoMakeup) - { - s[0] *= m_autoMakeupVal; - s[1] *= m_autoMakeupVal; - } - // Negate wet signal from dry signal if (audition) { s[0] = -s[0] + trueDrySignal[0]; s[1] = -s[1] + trueDrySignal[1]; } + else if (autoMakeup) + { + s[0] *= m_autoMakeupVal; + s[1] *= m_autoMakeupVal; + } // Calculate wet/dry value results const float temp1 = trueDrySignal[0] / m_inGainVal; From acb3209c071980898ea2809c896faa5bc0115d21 Mon Sep 17 00:00:00 2001 From: root Date: Sun, 19 Apr 2020 15:56:54 -0600 Subject: [PATCH 03/33] Fix Compressor timer bug when display is closed --- plugins/Compressor/CompressorControlDialog.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/Compressor/CompressorControlDialog.cpp b/plugins/Compressor/CompressorControlDialog.cpp index 7f1df85b117..b9ded28b5d2 100755 --- a/plugins/Compressor/CompressorControlDialog.cpp +++ b/plugins/Compressor/CompressorControlDialog.cpp @@ -358,6 +358,7 @@ void CompressorControlDialog::updateDisplay() { if (!isVisible()) { + m_timeElapsed.restart(); return; } From bf2a18d49bf0824db0593170b8e4d70c46fbfe62 Mon Sep 17 00:00:00 2001 From: root Date: Sun, 19 Apr 2020 16:28:34 -0600 Subject: [PATCH 04/33] Remove unnecessary functions --- plugins/Compressor/CompressorControlDialog.h | 2 -- 1 file changed, 2 deletions(-) diff --git a/plugins/Compressor/CompressorControlDialog.h b/plugins/Compressor/CompressorControlDialog.h index 4aa37d33f1f..f6c57df390a 100755 --- a/plugins/Compressor/CompressorControlDialog.h +++ b/plugins/Compressor/CompressorControlDialog.h @@ -122,8 +122,6 @@ class CompressorControlDialog : public EffectControlDialog private slots: void updateDisplay(); - void autoAttackToggled(); - void autoReleaseToggled(); void peakmodeChanged(); void stereoLinkChanged(); void lookaheadChanged(); From a6ad3c76a8b790e2571935ca6f11614978a98170 Mon Sep 17 00:00:00 2001 From: root Date: Sun, 19 Apr 2020 16:39:02 -0600 Subject: [PATCH 05/33] Remove libsamplerate usage which shouldn't be there --- plugins/Compressor/Compressor.cpp | 3 --- plugins/Compressor/Compressor.h | 3 --- 2 files changed, 6 deletions(-) diff --git a/plugins/Compressor/Compressor.cpp b/plugins/Compressor/Compressor.cpp index f2120282f4d..36d5909c2b9 100755 --- a/plugins/Compressor/Compressor.cpp +++ b/plugins/Compressor/Compressor.cpp @@ -98,9 +98,6 @@ CompressorEffect::~CompressorEffect() { delete m_rms[0]; delete m_rms[1]; - - src_delete(src_state[0]); - src_delete(src_state[1]); } diff --git a/plugins/Compressor/Compressor.h b/plugins/Compressor/Compressor.h index 5f3ea4da8ff..3c80fc2a822 100755 --- a/plugins/Compressor/Compressor.h +++ b/plugins/Compressor/Compressor.h @@ -73,9 +73,6 @@ private slots: inline int realmod(int k, int n); inline float realfmod(float k, float n); - int err; - SRC_STATE * src_state[2] = {src_new(SRC_SINC_FASTEST, 1, &err), src_new(SRC_SINC_FASTEST, 1, &err)}; - std::vector m_preLookaheadBuf[2]; int m_preLookaheadBufLoc[2] = {0}; From 00d97d8aff78ef2844b00f9d166b63475b7cad33 Mon Sep 17 00:00:00 2001 From: root Date: Thu, 25 Jun 2020 12:41:34 -0600 Subject: [PATCH 06/33] Resolve stuffs --- plugins/Compressor/Compressor.cpp | 36 ++++++++++++-------- plugins/Compressor/Compressor.h | 7 +++- plugins/Compressor/CompressorControlDialog.h | 4 --- 3 files changed, 28 insertions(+), 19 deletions(-) diff --git a/plugins/Compressor/Compressor.cpp b/plugins/Compressor/Compressor.cpp index 36d5909c2b9..d1ead9eb366 100755 --- a/plugins/Compressor/Compressor.cpp +++ b/plugins/Compressor/Compressor.cpp @@ -101,10 +101,10 @@ CompressorEffect::~CompressorEffect() } -float CompressorEffect::timeToCoeff(float time) +float CompressorEffect::msToCoeff(float ms) { // Convert time in milliseconds to applicable lowpass coefficient - return expf(COMP_LOG / (time * m_sampleRate * 0.001f)); + return expf(m_coeffPrecalc / ms); } @@ -120,12 +120,14 @@ void CompressorEffect::calcAutoMakeup() } else if (abs(1 - m_thresholdVal) < m_kneeVal)// If the input is within the knee's range { - const float temp = 0 - m_thresholdVal + m_kneeVal; - tempGainResult = 0 + ((m_compressorControls.m_limiterModel.value() ? 0 : m_ratioVal) - 1) * temp * temp / (4 * 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 { - tempGainResult = m_compressorControls.m_limiterModel.value() ? m_thresholdVal : (m_thresholdVal + (0 - m_thresholdVal) * m_ratioVal); + tempGainResult = m_compressorControls.m_limiterModel.value() + ? m_thresholdVal + : (m_thresholdVal + m_thresholdVal * m_ratioVal); } m_autoMakeupVal = 1.f / dbfsToAmp(tempGainResult); @@ -133,12 +135,12 @@ void CompressorEffect::calcAutoMakeup() void CompressorEffect::calcAttack() { - m_attCoeff = timeToCoeff(m_compressorControls.m_attackModel.value()); + m_attCoeff = msToCoeff(m_compressorControls.m_attackModel.value()); } void CompressorEffect::calcRelease() { - m_relCoeff = timeToCoeff(m_compressorControls.m_releaseModel.value()); + m_relCoeff = msToCoeff(m_compressorControls.m_releaseModel.value()); } void CompressorEffect::calcAutoAttack() @@ -174,7 +176,9 @@ void CompressorEffect::calcRatio() 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; + m_rangeVal = (m_compressorControls.m_rangeModel.value() > m_compressorControls.m_rangeModel.minValue()) + ? dbfsToAmp(m_compressorControls.m_rangeModel.value()) + : 0; } void CompressorEffect::resizeRMS() @@ -247,7 +251,7 @@ void CompressorEffect::calcMix() bool CompressorEffect::processAudioBuffer(sampleFrame* buf, const fpp_t frames) { - if(!isEnabled() || !isRunning ()) + if (!isEnabled() || !isRunning ()) { // Clear lookahead buffers and other values when needed if (!m_cleanedBuffers) @@ -370,11 +374,11 @@ bool CompressorEffect::processAudioBuffer(sampleFrame* buf, const fpp_t frames) float t = inputValue; - if(t > m_yL[i])// Attack phase + if (t > m_yL[i])// Attack phase { // Calculate attack value depending on crest factor const float att = m_autoAttVal - ? timeToCoeff(2.f * m_compressorControls.m_attackModel.value() / ((m_crestFactorVal[i] - 1) * m_autoAttVal + 1)) + ? msToCoeff(2.f * m_compressorControls.m_attackModel.value() / ((m_crestFactorVal[i] - 1) * m_autoAttVal + 1)) : m_attCoeff; m_yL[i] = m_yL[i] * att + (1 - att) * t; @@ -384,7 +388,7 @@ bool CompressorEffect::processAudioBuffer(sampleFrame* buf, const fpp_t frames) { // Calculate release value depending on crest factor const float rel = m_autoRelVal - ? timeToCoeff(2.f * m_compressorControls.m_releaseModel.value() / ((m_crestFactorVal[i] - 1) * m_autoRelVal + 1)) + ? msToCoeff(2.f * m_compressorControls.m_releaseModel.value() / ((m_crestFactorVal[i] - 1) * m_autoRelVal + 1)) : m_relCoeff; if (m_holdTimer[i])// Don't change peak if hold is being applied @@ -418,7 +422,9 @@ bool CompressorEffect::processAudioBuffer(sampleFrame* buf, const fpp_t frames) } else// Above knee { - gainResult[i] = limiter ? m_thresholdVal : (m_thresholdVal + (currentPeakDbfs - m_thresholdVal) * m_ratioVal); + gainResult[i] = limiter + ? m_thresholdVal + : (m_thresholdVal + (currentPeakDbfs - m_thresholdVal) * m_ratioVal); } gainResult[i] = dbfsToAmp(gainResult[i]) / m_yL[i]; @@ -472,7 +478,7 @@ bool CompressorEffect::processAudioBuffer(sampleFrame* buf, const fpp_t frames) } // Bias compression to the left or right (or mid or side) - if (stereoBalance) + if (stereoBalance != 0) { gainResult[0] = 1 - ((1 - gainResult[0]) * (stereoBalance > 0 ? 1 - stereoBalance : 1)); gainResult[1] = 1 - ((1 - gainResult[1]) * (stereoBalance < 0 ? 1 + stereoBalance : 1)); @@ -591,6 +597,8 @@ void CompressorEffect::changeSampleRate() { m_sampleRate = Engine::mixer()->processingSampleRate(); + m_coeffPrecalc = COMP_LOG / (m_sampleRate * 0.001f); + // 200 ms m_crestTimeConst = exp(-1.f / (0.2 * m_sampleRate)); diff --git a/plugins/Compressor/Compressor.h b/plugins/Compressor/Compressor.h index 3c80fc2a822..ddb40165075 100755 --- a/plugins/Compressor/Compressor.h +++ b/plugins/Compressor/Compressor.h @@ -31,6 +31,9 @@ #include "ValueBuffer.h" #include "RmsHelper.h" + +constexpr float COMP_LOG = -2.2; + class CompressorEffect : public Effect { Q_OBJECT @@ -67,7 +70,7 @@ private slots: private: CompressorControls m_compressorControls; - float timeToCoeff(float time); + float msToCoeff(float ms); inline void calcTiltFilter(sample_t inputSample, sample_t &outputSample, int filtNum); inline int realmod(int k, int n); @@ -101,6 +104,8 @@ private slots: float m_tiltVal; float m_mixVal; + float m_coeffPrecalc; + sampleFrame m_maxLookaheadVal = {0}; int m_maxLookaheadTimer[2] = {1, 1}; diff --git a/plugins/Compressor/CompressorControlDialog.h b/plugins/Compressor/CompressorControlDialog.h index f6c57df390a..21d78baacc5 100755 --- a/plugins/Compressor/CompressorControlDialog.h +++ b/plugins/Compressor/CompressorControlDialog.h @@ -52,10 +52,6 @@ constexpr int COMP_BOX_Y = 280; constexpr float COMP_NOISE_FLOOR = 0.000001;// -120 dbFs -// -2.2 seems to be the best value for this. -// I've seen some compressors choose -1, however. -constexpr float COMP_LOG = -2.2; - class CompressorControls; From fb0a91fa35c7f6314816e70e72edf3bc6d1d7fc8 Mon Sep 17 00:00:00 2001 From: root Date: Thu, 9 Jul 2020 17:13:47 -0600 Subject: [PATCH 07/33] Replace true RMS with moving average, make RMS in terms of milliseconds rather than samples, fix autogain bugs, show correct volume on input volume fader --- plugins/Compressor/Compressor.cpp | 30 +++++++++++------------ plugins/Compressor/Compressor.h | 9 ++++--- plugins/Compressor/CompressorControls.cpp | 2 +- 3 files changed, 20 insertions(+), 21 deletions(-) diff --git a/plugins/Compressor/Compressor.cpp b/plugins/Compressor/Compressor.cpp index d1ead9eb366..6b0e345da43 100755 --- a/plugins/Compressor/Compressor.cpp +++ b/plugins/Compressor/Compressor.cpp @@ -57,10 +57,6 @@ CompressorEffect::CompressorEffect(Model* parent, const Descriptor::SubPluginFea m_yL[0] = m_yL[1] = COMP_NOISE_FLOOR; - // These will be resized later - m_rms[0] = new RmsHelper(1); - m_rms[1] = new RmsHelper(1); - // 200 ms m_crestTimeConst = exp(-1.f / (0.2f * m_sampleRate)); @@ -86,6 +82,7 @@ CompressorEffect::CompressorEffect(Model* parent, const Descriptor::SubPluginFea connect(&m_compressorControls.m_thresholdModel, SIGNAL(dataChanged()), this, SLOT(calcAutoMakeup())); connect(&m_compressorControls.m_ratioModel, SIGNAL(dataChanged()), this, SLOT(calcAutoMakeup())); connect(&m_compressorControls.m_kneeModel, SIGNAL(dataChanged()), this, SLOT(calcAutoMakeup())); + connect(&m_compressorControls.m_autoMakeupModel, SIGNAL(dataChanged()), this, SLOT(calcAutoMakeup())); connect(Engine::mixer(), SIGNAL(sampleRateChanged()), this, SLOT(changeSampleRate())); emit changeSampleRate(); @@ -96,8 +93,6 @@ CompressorEffect::CompressorEffect(Model* parent, const Descriptor::SubPluginFea CompressorEffect::~CompressorEffect() { - delete m_rms[0]; - delete m_rms[1]; } @@ -114,25 +109,27 @@ void CompressorEffect::calcAutoMakeup() // Formulas using the compressor's Threshold, Ratio, and Knee values to estimate a good makeup gain value float tempGainResult; - if (0 - m_thresholdVal < -m_kneeVal) + if (-m_thresholdVal < -m_kneeVal)// Below knee { tempGainResult = 0; } - else if (abs(1 - m_thresholdVal) < m_kneeVal)// If the input is within the knee's range + else if (abs(1 - m_thresholdVal) < m_kneeVal)// Within knee { - const float temp = 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 + else// Above knee { tempGainResult = m_compressorControls.m_limiterModel.value() ? m_thresholdVal - : (m_thresholdVal + m_thresholdVal * m_ratioVal); + : (m_thresholdVal - m_thresholdVal * m_ratioVal); } m_autoMakeupVal = 1.f / dbfsToAmp(tempGainResult); } + + void CompressorEffect::calcAttack() { m_attCoeff = msToCoeff(m_compressorControls.m_attackModel.value()); @@ -183,8 +180,7 @@ void CompressorEffect::calcRange() void CompressorEffect::resizeRMS() { - m_rms[0]->setSize(m_compressorControls.m_rmsModel.value() * m_sampleRate / 44100.f); - m_rms[1]->setSize(m_compressorControls.m_rmsModel.value() * m_sampleRate / 44100.f); + m_rmsTimeConst = exp(-1.f / (m_compressorControls.m_rmsModel.value() * 0.001f * m_sampleRate)); } void CompressorEffect::calcLookaheadLength() @@ -322,8 +318,10 @@ bool CompressorEffect::processAudioBuffer(sampleFrame* buf, const fpp_t frames) 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) : m_rms[i]->update(inputValue)); + inputValue = qMax(COMP_NOISE_FLOOR, peakmode ? abs(inputValue) : m_rmsVal[i]); // The following code uses math magic to semi-efficiently // find the largest value in the lookahead buffer. @@ -555,8 +553,8 @@ bool CompressorEffect::processAudioBuffer(sampleFrame* buf, const fpp_t frames) outSum += s[0] + s[1]; - lInPeak = buf[f][0] > lInPeak ? buf[f][0] : lInPeak; - rInPeak = buf[f][1] > rInPeak ? buf[f][1] : rInPeak; + 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; } diff --git a/plugins/Compressor/Compressor.h b/plugins/Compressor/Compressor.h index ddb40165075..2e42287e882 100755 --- a/plugins/Compressor/Compressor.h +++ b/plugins/Compressor/Compressor.h @@ -109,11 +109,12 @@ private slots: sampleFrame m_maxLookaheadVal = {0}; int m_maxLookaheadTimer[2] = {1, 1}; - RmsHelper * m_rms[2]; + 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_crestPeakVal[2] = {0, 0}; + float m_crestRmsVal[2] = {0, 0}; + float m_crestFactorVal[2] = {0, 0}; float m_crestTimeConst; float m_tiltOut[2] = {0}; diff --git a/plugins/Compressor/CompressorControls.cpp b/plugins/Compressor/CompressorControls.cpp index ff2b6e5e08e..0863d118fbe 100755 --- a/plugins/Compressor/CompressorControls.cpp +++ b/plugins/Compressor/CompressorControls.cpp @@ -41,7 +41,7 @@ CompressorControls::CompressorControls(CompressorEffect* effect) : 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(64.0f, 1.0f, 2048.0f, 1.0f, this, tr("RMS Size")), + m_rmsModel(10.0f, 0.0f, 1000.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")), From 291d6fdc24245bcc9ceed757e289caa1337af539 Mon Sep 17 00:00:00 2001 From: root Date: Thu, 9 Jul 2020 17:33:05 -0600 Subject: [PATCH 08/33] Stuff --- plugins/Compressor/Compressor.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/Compressor/Compressor.cpp b/plugins/Compressor/Compressor.cpp index 6b0e345da43..1d3e333ad00 100755 --- a/plugins/Compressor/Compressor.cpp +++ b/plugins/Compressor/Compressor.cpp @@ -600,8 +600,8 @@ void CompressorEffect::changeSampleRate() // 200 ms m_crestTimeConst = exp(-1.f / (0.2 * m_sampleRate)); - // 20 ms - m_lookaheadDelayLength = 0.020 * m_sampleRate; + // 21 ms + m_lookaheadDelayLength = 0.021 * m_sampleRate; m_inputBuf[0].resize(m_lookaheadDelayLength); m_inputBuf[1].resize(m_lookaheadDelayLength); From bf04925c167623351055564d061441dce769bce0 Mon Sep 17 00:00:00 2001 From: root Date: Thu, 9 Jul 2020 21:05:40 -0600 Subject: [PATCH 09/33] Fix stuff and things and everything else --- plugins/Compressor/Compressor.cpp | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/plugins/Compressor/Compressor.cpp b/plugins/Compressor/Compressor.cpp index 1d3e333ad00..89fc25caf0e 100755 --- a/plugins/Compressor/Compressor.cpp +++ b/plugins/Compressor/Compressor.cpp @@ -187,12 +187,7 @@ void CompressorEffect::calcLookaheadLength() { m_lookaheadLength = qMax(m_compressorControls.m_lookaheadLengthModel.value() * 0.001f * m_sampleRate, 1.f); - m_lookaheadBuf[0].resize(m_lookaheadLength); - m_lookaheadBuf[1].resize(m_lookaheadLength); - m_preLookaheadLength = ceil(m_lookaheadDelayLength - m_lookaheadLength); - m_preLookaheadBuf[0].resize(m_preLookaheadLength); - m_preLookaheadBuf[1].resize(m_preLookaheadLength); } void CompressorEffect::calcThreshold() @@ -321,7 +316,7 @@ bool CompressorEffect::processAudioBuffer(sampleFrame* buf, const fpp_t frames) 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) : m_rmsVal[i]); + 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. @@ -600,11 +595,17 @@ void CompressorEffect::changeSampleRate() // 200 ms m_crestTimeConst = exp(-1.f / (0.2 * m_sampleRate)); - // 21 ms - m_lookaheadDelayLength = 0.021 * 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); + emit calcAutoMakeup(); emit calcAttack(); emit calcRelease(); From 737b051e073e738189f1f6fc8c65a227b44eec79 Mon Sep 17 00:00:00 2001 From: root Date: Fri, 10 Jul 2020 12:52:21 -0600 Subject: [PATCH 10/33] Change some default settings --- plugins/Compressor/CompressorControls.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/Compressor/CompressorControls.cpp b/plugins/Compressor/CompressorControls.cpp index 0863d118fbe..55a281660d0 100755 --- a/plugins/Compressor/CompressorControls.cpp +++ b/plugins/Compressor/CompressorControls.cpp @@ -36,12 +36,12 @@ CompressorControls::CompressorControls(CompressorEffect* 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(0.25f, 0.005f, 250.f, 0.001f, this, tr("Attack")), + 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(10.0f, 0.0f, 1000.0f, 0.01f, this, tr("RMS Size")), + 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")), From b55a8ac6b03d0faa713a637c60c3e441497ec979 Mon Sep 17 00:00:00 2001 From: root Date: Fri, 10 Jul 2020 14:20:31 -0600 Subject: [PATCH 11/33] Remove emits --- plugins/Compressor/Compressor.cpp | 30 +++++++++---------- .../Compressor/CompressorControlDialog.cpp | 8 ++--- 2 files changed, 19 insertions(+), 19 deletions(-) diff --git a/plugins/Compressor/Compressor.cpp b/plugins/Compressor/Compressor.cpp index 89fc25caf0e..0119ca060d0 100755 --- a/plugins/Compressor/Compressor.cpp +++ b/plugins/Compressor/Compressor.cpp @@ -85,7 +85,7 @@ CompressorEffect::CompressorEffect(Model* parent, const Descriptor::SubPluginFea connect(&m_compressorControls.m_autoMakeupModel, SIGNAL(dataChanged()), this, SLOT(calcAutoMakeup())); connect(Engine::mixer(), SIGNAL(sampleRateChanged()), this, SLOT(changeSampleRate())); - emit changeSampleRate(); + changeSampleRate(); } @@ -606,20 +606,20 @@ void CompressorEffect::changeSampleRate() m_preLookaheadBuf[0].resize(m_lookaheadDelayLength); m_preLookaheadBuf[1].resize(m_lookaheadDelayLength); - emit calcAutoMakeup(); - emit calcAttack(); - emit calcRelease(); - emit calcRatio(); - emit calcRange(); - emit calcLookaheadLength(); - emit calcHold(); - emit resizeRMS(); - emit calcThreshold(); - emit calcKnee(); - emit calcOutGain(); - emit calcInGain(); - emit calcTiltCoeffs(); - emit calcMix(); + calcAutoMakeup(); + calcAttack(); + calcRelease(); + calcRatio(); + calcRange(); + calcLookaheadLength(); + calcHold(); + resizeRMS(); + calcThreshold(); + calcKnee(); + calcOutGain(); + calcInGain(); + calcTiltCoeffs(); + calcMix(); } diff --git a/plugins/Compressor/CompressorControlDialog.cpp b/plugins/Compressor/CompressorControlDialog.cpp index b9ded28b5d2..8bc4cb7edee 100755 --- a/plugins/Compressor/CompressorControlDialog.cpp +++ b/plugins/Compressor/CompressorControlDialog.cpp @@ -298,10 +298,10 @@ CompressorControlDialog::CompressorControlDialog(CompressorControls* controls) : m_timeElapsed.start(); - emit peakmodeChanged(); - emit stereoLinkChanged(); - emit lookaheadChanged(); - emit limiterChanged(); + peakmodeChanged(); + stereoLinkChanged(); + lookaheadChanged(); + limiterChanged(); } From 7b7adb2ee6d38cb5e234346ad44a5688e7f4bebb Mon Sep 17 00:00:00 2001 From: root Date: Fri, 10 Jul 2020 18:36:29 -0600 Subject: [PATCH 12/33] Improve display behavior when effect is disabled or enabled --- plugins/Compressor/Compressor.cpp | 7 ++++++- plugins/Compressor/CompressorControlDialog.cpp | 10 ++++++---- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/plugins/Compressor/Compressor.cpp b/plugins/Compressor/Compressor.cpp index 0119ca060d0..130a7a6b7f9 100755 --- a/plugins/Compressor/Compressor.cpp +++ b/plugins/Compressor/Compressor.cpp @@ -242,12 +242,14 @@ void CompressorEffect::calcMix() bool CompressorEffect::processAudioBuffer(sampleFrame* buf, const fpp_t frames) { - if (!isEnabled() || !isRunning ()) + if (!isEnabled() || !isRunning()) { // Clear lookahead buffers and other values when needed if (!m_cleanedBuffers) { m_yL[0] = m_yL[1] = COMP_NOISE_FLOOR; + 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; @@ -256,6 +258,9 @@ bool CompressorEffect::processAudioBuffer(sampleFrame* buf, const fpp_t frames) 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; diff --git a/plugins/Compressor/CompressorControlDialog.cpp b/plugins/Compressor/CompressorControlDialog.cpp index 8bc4cb7edee..68754949ed5 100755 --- a/plugins/Compressor/CompressorControlDialog.cpp +++ b/plugins/Compressor/CompressorControlDialog.cpp @@ -380,8 +380,8 @@ void CompressorControlDialog::updateDisplay() 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_lastPoint = dbfsToYPoint(-9999); - m_lastGainPoint = dbfsToYPoint(0); + //m_lastPoint = dbfsToYPoint(-9999); + //m_lastGainPoint = dbfsToYPoint(0); } const float peakAvg = (m_controls->m_effect->m_displayPeak[0] + m_controls->m_effect->m_displayPeak[1]) * 0.5f; @@ -575,8 +575,10 @@ void CompressorControlDialog::paintEvent(QPaintEvent *event) p.setOpacity(0.25); p.drawPixmap(0, 0, m_kneePixmap); p.setOpacity(1); - p.drawPixmap(0, 0, m_kneePixmap2); - p.setOpacity(1); + if (m_controls->m_effect->isEnabled() && m_controls->m_effect->isRunning()) + { + p.drawPixmap(0, 0, m_kneePixmap2); + } p.drawPixmap(0, 0, m_miscPixmap); p.end(); From ddbd8a7f85499f12a9ceb8bf882c6460f10a8282 Mon Sep 17 00:00:00 2001 From: root Date: Tue, 21 Jul 2020 11:27:07 -0600 Subject: [PATCH 13/33] Fix code style --- plugins/Compressor/Compressor.cpp | 22 ++++++++++---------- plugins/Compressor/CompressorControlDialog.h | 4 ++-- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/plugins/Compressor/Compressor.cpp b/plugins/Compressor/Compressor.cpp index 130a7a6b7f9..cf0b8c43cde 100755 --- a/plugins/Compressor/Compressor.cpp +++ b/plugins/Compressor/Compressor.cpp @@ -58,7 +58,7 @@ CompressorEffect::CompressorEffect(Model* parent, const Descriptor::SubPluginFea m_yL[0] = m_yL[1] = COMP_NOISE_FLOOR; // 200 ms - m_crestTimeConst = exp(-1.f / (0.2f * m_sampleRate)); + m_crestTimeConst = expf(-1.f / (0.2f * m_sampleRate)); connect(&m_compressorControls.m_attackModel, SIGNAL(dataChanged()), this, SLOT(calcAttack())); connect(&m_compressorControls.m_releaseModel, SIGNAL(dataChanged()), this, SLOT(calcRelease())); @@ -122,7 +122,7 @@ void CompressorEffect::calcAutoMakeup() { tempGainResult = m_compressorControls.m_limiterModel.value() ? m_thresholdVal - : (m_thresholdVal - m_thresholdVal * m_ratioVal); + : m_thresholdVal - m_thresholdVal * m_ratioVal; } m_autoMakeupVal = 1.f / dbfsToAmp(tempGainResult); @@ -180,7 +180,7 @@ void CompressorEffect::calcRange() void CompressorEffect::resizeRMS() { - m_rmsTimeConst = exp(-1.f / (m_compressorControls.m_rmsModel.value() * 0.001f * m_sampleRate)); + m_rmsTimeConst = expf(-1.f / (m_compressorControls.m_rmsModel.value() * 0.001f * m_sampleRate)); } void CompressorEffect::calcLookaheadLength() @@ -221,11 +221,11 @@ void CompressorEffect::calcTiltCoeffs() 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; + 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; + m_lgain = expf(g1 / amp) - 1; + m_hgain = expf(g2 / amp) - 1; const float omega = 2 * F_PI * m_compressorControls.m_tiltFreqModel.value(); const float n = 1 / (m_sampleRate * 3 + omega); @@ -422,7 +422,7 @@ bool CompressorEffect::processAudioBuffer(sampleFrame* buf, const fpp_t frames) { gainResult[i] = limiter ? m_thresholdVal - : (m_thresholdVal + (currentPeakDbfs - m_thresholdVal) * m_ratioVal); + : m_thresholdVal + (currentPeakDbfs - m_thresholdVal) * m_ratioVal; } gainResult[i] = dbfsToAmp(gainResult[i]) / m_yL[i]; @@ -572,13 +572,13 @@ bool CompressorEffect::processAudioBuffer(sampleFrame* buf, const fpp_t frames) // 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; + 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; + return (k = fmod(k, n)) < 0 ? k+n : k; } @@ -598,7 +598,7 @@ void CompressorEffect::changeSampleRate() m_coeffPrecalc = COMP_LOG / (m_sampleRate * 0.001f); // 200 ms - m_crestTimeConst = exp(-1.f / (0.2 * m_sampleRate)); + m_crestTimeConst = expf(-1.f / (0.2f * m_sampleRate)); // 20 ms m_lookaheadDelayLength = 0.02 * m_sampleRate; diff --git a/plugins/Compressor/CompressorControlDialog.h b/plugins/Compressor/CompressorControlDialog.h index 21d78baacc5..3af40313bbb 100755 --- a/plugins/Compressor/CompressorControlDialog.h +++ b/plugins/Compressor/CompressorControlDialog.h @@ -42,8 +42,8 @@ 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 = 1000; -constexpr int COMP_SCREEN_Y = 700; +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; From 6d5a871c191afa98cf07b1079c3018b07db0d91b Mon Sep 17 00:00:00 2001 From: root Date: Fri, 24 Jul 2020 11:31:47 -0600 Subject: [PATCH 14/33] h --- plugins/Compressor/Compressor.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/plugins/Compressor/Compressor.cpp b/plugins/Compressor/Compressor.cpp index cf0b8c43cde..80ecc3a016b 100755 --- a/plugins/Compressor/Compressor.cpp +++ b/plugins/Compressor/Compressor.cpp @@ -58,7 +58,7 @@ CompressorEffect::CompressorEffect(Model* parent, const Descriptor::SubPluginFea m_yL[0] = m_yL[1] = COMP_NOISE_FLOOR; // 200 ms - m_crestTimeConst = expf(-1.f / (0.2f * m_sampleRate)); + m_crestTimeConst = exp(-1.f / (0.2f * m_sampleRate)); connect(&m_compressorControls.m_attackModel, SIGNAL(dataChanged()), this, SLOT(calcAttack())); connect(&m_compressorControls.m_releaseModel, SIGNAL(dataChanged()), this, SLOT(calcRelease())); @@ -99,7 +99,7 @@ CompressorEffect::~CompressorEffect() float CompressorEffect::msToCoeff(float ms) { // Convert time in milliseconds to applicable lowpass coefficient - return expf(m_coeffPrecalc / ms); + return exp(m_coeffPrecalc / ms); } @@ -180,7 +180,7 @@ void CompressorEffect::calcRange() void CompressorEffect::resizeRMS() { - m_rmsTimeConst = expf(-1.f / (m_compressorControls.m_rmsModel.value() * 0.001f * m_sampleRate)); + m_rmsTimeConst = exp(-1.f / (m_compressorControls.m_rmsModel.value() * 0.001f * m_sampleRate)); } void CompressorEffect::calcLookaheadLength() @@ -224,8 +224,8 @@ void CompressorEffect::calcTiltCoeffs() const float g1 = m_tiltVal > 0 ? -gfactor * m_tiltVal : -m_tiltVal; const float g2 = m_tiltVal > 0 ? m_tiltVal : gfactor * m_tiltVal; - m_lgain = expf(g1 / amp) - 1; - m_hgain = expf(g2 / amp) - 1; + 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); @@ -598,7 +598,7 @@ void CompressorEffect::changeSampleRate() m_coeffPrecalc = COMP_LOG / (m_sampleRate * 0.001f); // 200 ms - m_crestTimeConst = expf(-1.f / (0.2f * m_sampleRate)); + m_crestTimeConst = exp(-1.f / (0.2f * m_sampleRate)); // 20 ms m_lookaheadDelayLength = 0.02 * m_sampleRate; From 4d3c53e2d98e035287f37a7d8ed3b478cb7ee6c6 Mon Sep 17 00:00:00 2001 From: Robert Daniel Black Date: Wed, 21 Oct 2020 20:57:26 -0600 Subject: [PATCH 15/33] Significantly improve automatic attack and release formulas --- plugins/Compressor/Compressor.cpp | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/plugins/Compressor/Compressor.cpp b/plugins/Compressor/Compressor.cpp index 80ecc3a016b..896eddb815a 100755 --- a/plugins/Compressor/Compressor.cpp +++ b/plugins/Compressor/Compressor.cpp @@ -29,7 +29,6 @@ #include "plugin_export.h" #include "interpolation.h" - extern "C" { @@ -374,9 +373,14 @@ bool CompressorEffect::processAudioBuffer(sampleFrame* buf, const fpp_t frames) if (t > m_yL[i])// Attack phase { + // Lowest possible value of m_crestFactorVal[i] is 2, given by a sine wave. + // So, we pull this minimum 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() / ((m_crestFactorVal[i] - 1) * m_autoAttVal + 1)) + ? msToCoeff(2.f * m_compressorControls.m_attackModel.value() / (crestFactorValTemp)) : m_attCoeff; m_yL[i] = m_yL[i] * att + (1 - att) * t; @@ -384,9 +388,10 @@ bool CompressorEffect::processAudioBuffer(sampleFrame* buf, const fpp_t frames) } else// Release phase { - // Calculate release value depending on crest factor + float crestFactorValTemp = ((m_crestFactorVal[i] - 2.f) * m_autoRelVal) + 2.f; + const float rel = m_autoRelVal - ? msToCoeff(2.f * m_compressorControls.m_releaseModel.value() / ((m_crestFactorVal[i] - 1) * m_autoRelVal + 1)) + ? msToCoeff(2.f * m_compressorControls.m_releaseModel.value() / (crestFactorValTemp)) : m_relCoeff; if (m_holdTimer[i])// Don't change peak if hold is being applied From 4c80706ea0fbb231046b37cdb76c5ab78dd1406a Mon Sep 17 00:00:00 2001 From: Robert Daniel Black Date: Wed, 21 Oct 2020 22:33:46 -0600 Subject: [PATCH 16/33] Quench systematic protests against habitude of adopting briefer array syntax implementation --- plugins/Compressor/Compressor.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/Compressor/Compressor.h b/plugins/Compressor/Compressor.h index 2e42287e882..601517c464c 100755 --- a/plugins/Compressor/Compressor.h +++ b/plugins/Compressor/Compressor.h @@ -106,7 +106,7 @@ private slots: float m_coeffPrecalc; - sampleFrame m_maxLookaheadVal = {0}; + sampleFrame m_maxLookaheadVal = {0, 0}; int m_maxLookaheadTimer[2] = {1, 1}; float m_rmsTimeConst; From ff83fb16fa23e030938fa2e4c6b64c4dd5d20503 Mon Sep 17 00:00:00 2001 From: Robert Daniel Black Date: Thu, 22 Oct 2020 10:51:21 -0600 Subject: [PATCH 17/33] Tell gcc to shut up and stand in the corner --- plugins/Compressor/Compressor.h | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/plugins/Compressor/Compressor.h b/plugins/Compressor/Compressor.h index 601517c464c..e6412f7b209 100755 --- a/plugins/Compressor/Compressor.h +++ b/plugins/Compressor/Compressor.h @@ -106,7 +106,9 @@ private slots: float m_coeffPrecalc; - sampleFrame m_maxLookaheadVal = {0, 0}; + // Using an equal sign here causes a crash for some compilers due to a bug + sampleFrame m_maxLookaheadVal {0}; + int m_maxLookaheadTimer[2] = {1, 1}; float m_rmsTimeConst; From 68ba91326a6c0aa14006e7ef88965acc055b35aa Mon Sep 17 00:00:00 2001 From: Robert Daniel Black Date: Thu, 22 Oct 2020 11:25:10 -0600 Subject: [PATCH 18/33] Repair impressive display of sleep deprivation --- plugins/Compressor/Compressor.cpp | 8 ++++++-- plugins/Compressor/Compressor.h | 3 +-- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/plugins/Compressor/Compressor.cpp b/plugins/Compressor/Compressor.cpp index 896eddb815a..bcf86f2ab8c 100755 --- a/plugins/Compressor/Compressor.cpp +++ b/plugins/Compressor/Compressor.cpp @@ -56,6 +56,9 @@ CompressorEffect::CompressorEffect(Model* parent, const Descriptor::SubPluginFea m_yL[0] = m_yL[1] = COMP_NOISE_FLOOR; + m_maxLookaheadVal[0] = 0; + m_maxLookaheadVal[1] = 0; + // 200 ms m_crestTimeConst = exp(-1.f / (0.2f * m_sampleRate)); @@ -373,8 +376,9 @@ bool CompressorEffect::processAudioBuffer(sampleFrame* buf, const fpp_t frames) if (t > m_yL[i])// Attack phase { - // Lowest possible value of m_crestFactorVal[i] is 2, given by a sine wave. - // So, we pull this minimum value down to 0, and multiply it by the percentage of + // 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; diff --git a/plugins/Compressor/Compressor.h b/plugins/Compressor/Compressor.h index e6412f7b209..a035093bc28 100755 --- a/plugins/Compressor/Compressor.h +++ b/plugins/Compressor/Compressor.h @@ -106,8 +106,7 @@ private slots: float m_coeffPrecalc; - // Using an equal sign here causes a crash for some compilers due to a bug - sampleFrame m_maxLookaheadVal {0}; + sampleFrame m_maxLookaheadVal; int m_maxLookaheadTimer[2] = {1, 1}; From 8b5db75f4de6a4caa2e7ecc41e9e07cd52f1aa14 Mon Sep 17 00:00:00 2001 From: Robert Daniel Black Date: Sat, 24 Oct 2020 22:43:38 -0600 Subject: [PATCH 19/33] Donate 5 to international jump rope competition --- plugins/Compressor/Compressor.cpp | 4 ++-- plugins/Compressor/CompressorControls.h | 2 +- src/3rdparty/ringbuffer | 2 +- src/3rdparty/rpmalloc/rpmalloc | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/plugins/Compressor/Compressor.cpp b/plugins/Compressor/Compressor.cpp index bcf86f2ab8c..cce9b57cdfe 100755 --- a/plugins/Compressor/Compressor.cpp +++ b/plugins/Compressor/Compressor.cpp @@ -553,8 +553,8 @@ bool CompressorEffect::processAudioBuffer(sampleFrame* buf, const fpp_t frames) } // Calculate wet/dry value results - const float temp1 = trueDrySignal[0] / m_inGainVal; - const float temp2 = trueDrySignal[1] / m_inGainVal; + const float temp1 = buf[f][0]; + const float temp2 = buf[f][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]; diff --git a/plugins/Compressor/CompressorControls.h b/plugins/Compressor/CompressorControls.h index 6cfc9663eee..3e28c5d71df 100755 --- a/plugins/Compressor/CompressorControls.h +++ b/plugins/Compressor/CompressorControls.h @@ -48,7 +48,7 @@ class CompressorControls : public EffectControls int controlCount() override { - return 31; + return 28; } EffectControlDialog* createView() override diff --git a/src/3rdparty/ringbuffer b/src/3rdparty/ringbuffer index ea00e1fc2b8..82ed7cfb9ad 160000 --- a/src/3rdparty/ringbuffer +++ b/src/3rdparty/ringbuffer @@ -1 +1 @@ -Subproject commit ea00e1fc2b821a0e06d78e8cc13ac06e6b4f72f5 +Subproject commit 82ed7cfb9ad40467421d8b14ca1af0350e92613c diff --git a/src/3rdparty/rpmalloc/rpmalloc b/src/3rdparty/rpmalloc/rpmalloc index 8d790d2b45e..b5bdc18051b 160000 --- a/src/3rdparty/rpmalloc/rpmalloc +++ b/src/3rdparty/rpmalloc/rpmalloc @@ -1 +1 @@ -Subproject commit 8d790d2b45e1818e531c61bf649c5225556dd07a +Subproject commit b5bdc18051bb74a22f0bde4bcc90b01cf590b496 From 08ce2f50a13770b3f6cc15f43c64996513620f5f Mon Sep 17 00:00:00 2001 From: Robert Daniel Black Date: Sat, 24 Oct 2020 22:49:13 -0600 Subject: [PATCH 20/33] Donate 15 dollars to Github to not steal dollar signs from my bad commit titles --- plugins/Compressor/Compressor.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/plugins/Compressor/Compressor.cpp b/plugins/Compressor/Compressor.cpp index cce9b57cdfe..7ea242b4f47 100755 --- a/plugins/Compressor/Compressor.cpp +++ b/plugins/Compressor/Compressor.cpp @@ -517,7 +517,7 @@ bool CompressorEffect::processAudioBuffer(sampleFrame* buf, const fpp_t frames) s[1] = drySignal[1]; } - float trueDrySignal[2] = {s[0], s[1]}; + float delayedDrySignal[2] = {s[0], s[1]}; if (midside)// Convert left/right to mid/side { @@ -543,8 +543,8 @@ bool CompressorEffect::processAudioBuffer(sampleFrame* buf, const fpp_t frames) // Negate wet signal from dry signal if (audition) { - s[0] = -s[0] + trueDrySignal[0]; - s[1] = -s[1] + trueDrySignal[1]; + s[0] = -s[0] + delayedDrySignal[0]; + s[1] = -s[1] + delayedDrySignal[1]; } else if (autoMakeup) { From 8295955b68771e0c3d6f6f91a9220f925476a85a Mon Sep 17 00:00:00 2001 From: Robert Daniel Black Date: Sat, 24 Oct 2020 22:55:54 -0600 Subject: [PATCH 21/33] Fix submodule stuff --- src/3rdparty/ringbuffer | 2 +- src/3rdparty/rpmalloc/rpmalloc | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/3rdparty/ringbuffer b/src/3rdparty/ringbuffer index 82ed7cfb9ad..ea00e1fc2b8 160000 --- a/src/3rdparty/ringbuffer +++ b/src/3rdparty/ringbuffer @@ -1 +1 @@ -Subproject commit 82ed7cfb9ad40467421d8b14ca1af0350e92613c +Subproject commit ea00e1fc2b821a0e06d78e8cc13ac06e6b4f72f5 diff --git a/src/3rdparty/rpmalloc/rpmalloc b/src/3rdparty/rpmalloc/rpmalloc index b5bdc18051b..8d790d2b45e 160000 --- a/src/3rdparty/rpmalloc/rpmalloc +++ b/src/3rdparty/rpmalloc/rpmalloc @@ -1 +1 @@ -Subproject commit b5bdc18051bb74a22f0bde4bcc90b01cf590b496 +Subproject commit 8d790d2b45e1818e531c61bf649c5225556dd07a From 25e46c20f4a574e28798f635df3d8885d3c1d43e Mon Sep 17 00:00:00 2001 From: Robert Daniel Black Date: Thu, 31 Dec 2020 21:02:17 -0700 Subject: [PATCH 22/33] Beans --- plugins/Compressor/Compressor.cpp | 14 +++++--------- plugins/Compressor/CompressorControlDialog.cpp | 8 ++++---- 2 files changed, 9 insertions(+), 13 deletions(-) diff --git a/plugins/Compressor/Compressor.cpp b/plugins/Compressor/Compressor.cpp index 7ea242b4f47..64aa976729f 100755 --- a/plugins/Compressor/Compressor.cpp +++ b/plugins/Compressor/Compressor.cpp @@ -111,16 +111,12 @@ 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)// Below knee - { - tempGainResult = 0; - } - else if (abs(1 - m_thresholdVal) < m_kneeVal)// Within knee + 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 + else { tempGainResult = m_compressorControls.m_limiterModel.value() ? m_thresholdVal @@ -422,7 +418,7 @@ bool CompressorEffect::processAudioBuffer(sampleFrame* buf, const fpp_t frames) { gainResult[i] = currentPeakDbfs; } - else if (abs(currentPeakDbfs - m_thresholdVal) < m_kneeVal)// Within knee + else if (currentPeakDbfs - m_thresholdVal < m_kneeVal)// Within knee { const float temp = currentPeakDbfs - m_thresholdVal + m_kneeVal; gainResult[i] = currentPeakDbfs + ((limiter ? 0 : m_ratioVal) - 1) * temp * temp / (4 * m_kneeVal); @@ -553,8 +549,8 @@ bool CompressorEffect::processAudioBuffer(sampleFrame* buf, const fpp_t frames) } // Calculate wet/dry value results - const float temp1 = buf[f][0]; - const float temp2 = buf[f][1]; + 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]; diff --git a/plugins/Compressor/CompressorControlDialog.cpp b/plugins/Compressor/CompressorControlDialog.cpp index 68754949ed5..fb141b7de91 100755 --- a/plugins/Compressor/CompressorControlDialog.cpp +++ b/plugins/Compressor/CompressorControlDialog.cpp @@ -79,7 +79,7 @@ CompressorControlDialog::CompressorControlDialog(CompressorControls* controls) : m_ratioEnabledLabel->setAttribute(Qt::WA_TransparentForMouseEvents); m_thresholdKnob = new Knob(knobStyled, this); - makeLargeKnob(m_thresholdKnob, tr("Threshold:") , " dbFs"); + 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")); @@ -99,12 +99,12 @@ CompressorControlDialog::CompressorControlDialog(CompressorControls* controls) : 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:") , " dbFs"); + 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"); + makeSmallKnob(m_rangeKnob, tr("Range:") , " dBFS"); m_rangeKnob->setModel(&controls->m_rangeModel); ToolTip::add(m_rangeKnob, tr("Maximum gain reduction")); @@ -144,7 +144,7 @@ CompressorControlDialog::CompressorControlDialog(CompressorControls* controls) : 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"); + 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.")); From 3d3485bf5a49b5840b664e2f8daa3a241c86d937 Mon Sep 17 00:00:00 2001 From: Robert Daniel Black Date: Thu, 31 Dec 2020 21:23:08 -0700 Subject: [PATCH 23/33] Lettuce --- plugins/Compressor/Compressor.cpp | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/plugins/Compressor/Compressor.cpp b/plugins/Compressor/Compressor.cpp index 64aa976729f..40103d529f0 100755 --- a/plugins/Compressor/Compressor.cpp +++ b/plugins/Compressor/Compressor.cpp @@ -85,6 +85,7 @@ CompressorEffect::CompressorEffect(Model* parent, const Descriptor::SubPluginFea connect(&m_compressorControls.m_ratioModel, SIGNAL(dataChanged()), this, SLOT(calcAutoMakeup())); connect(&m_compressorControls.m_kneeModel, SIGNAL(dataChanged()), this, SLOT(calcAutoMakeup())); connect(&m_compressorControls.m_autoMakeupModel, SIGNAL(dataChanged()), this, SLOT(calcAutoMakeup())); + connect(&m_compressorControls.m_inGainModel, SIGNAL(dataChanged()), this, SLOT(calcAutoMakeup())); connect(Engine::mixer(), SIGNAL(sampleRateChanged()), this, SLOT(changeSampleRate())); changeSampleRate(); @@ -111,19 +112,19 @@ 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) + 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 + else// Above knee { tempGainResult = m_compressorControls.m_limiterModel.value() ? m_thresholdVal : m_thresholdVal - m_thresholdVal * m_ratioVal; } - m_autoMakeupVal = 1.f / dbfsToAmp(tempGainResult); + m_autoMakeupVal = m_inGainVal / dbfsToAmp(tempGainResult); } @@ -292,8 +293,8 @@ bool CompressorEffect::processAudioBuffer(sampleFrame* buf, const fpp_t frames) for(fpp_t f = 0; f < frames; ++f) { - sample_t drySignal[2] = {buf[f][0] * m_inGainVal, buf[f][1] * m_inGainVal}; - sample_t s[2] = {drySignal[0], drySignal[1]}; + 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) From 743275f4b0df07f5f2933dc51adfc2b9a3392aff Mon Sep 17 00:00:00 2001 From: Robert Daniel Black Date: Thu, 31 Dec 2020 21:26:48 -0700 Subject: [PATCH 24/33] Cheese --- plugins/Compressor/Compressor.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/Compressor/Compressor.cpp b/plugins/Compressor/Compressor.cpp index 40103d529f0..fd3b29b7011 100755 --- a/plugins/Compressor/Compressor.cpp +++ b/plugins/Compressor/Compressor.cpp @@ -540,8 +540,8 @@ bool CompressorEffect::processAudioBuffer(sampleFrame* buf, const fpp_t frames) // Negate wet signal from dry signal if (audition) { - s[0] = -s[0] + delayedDrySignal[0]; - s[1] = -s[1] + delayedDrySignal[1]; + s[0] = -s[0] + delayedDrySignal[0] * m_inGainVal; + s[1] = -s[1] + delayedDrySignal[1] * m_inGainVal; } else if (autoMakeup) { From 13d3610a608f47187e66d86ddce4be5b9d9be6a9 Mon Sep 17 00:00:00 2001 From: Robert Daniel Black Date: Fri, 15 Jan 2021 11:02:31 -0700 Subject: [PATCH 25/33] I should really stop aggressively taking advantage of the fact that squash and merge makes it so commit titles don't actually matter, unless they do in which case I am absolutely doomed. --- plugins/Compressor/Compressor.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/Compressor/Compressor.cpp b/plugins/Compressor/Compressor.cpp index fd3b29b7011..26468c4ed08 100755 --- a/plugins/Compressor/Compressor.cpp +++ b/plugins/Compressor/Compressor.cpp @@ -36,7 +36,7 @@ Plugin::Descriptor PLUGIN_EXPORT compressor_plugin_descriptor = { STRINGIFY(PLUGIN_NAME), "Compressor", - QT_TRANSLATE_NOOP("pluginBrowser", "A dynamic range compressor."), + QT_TRANSLATE_NOOP("PluginBrowser", "A dynamic range compressor."), "Lost Robot ", 0x0100, Plugin::Effect, From d2f3f22ae97fa5879ff353e9fd064958ecb1c73c Mon Sep 17 00:00:00 2001 From: Robert Daniel Black Date: Wed, 27 Jan 2021 08:46:34 -0700 Subject: [PATCH 26/33] Fix oopsies --- plugins/Compressor/Compressor.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/plugins/Compressor/Compressor.cpp b/plugins/Compressor/Compressor.cpp index 26468c4ed08..06f690a0185 100755 --- a/plugins/Compressor/Compressor.cpp +++ b/plugins/Compressor/Compressor.cpp @@ -124,7 +124,7 @@ void CompressorEffect::calcAutoMakeup() : m_thresholdVal - m_thresholdVal * m_ratioVal; } - m_autoMakeupVal = m_inGainVal / dbfsToAmp(tempGainResult); + m_autoMakeupVal = 1.f / dbfsToAmp(tempGainResult); } @@ -523,8 +523,8 @@ bool CompressorEffect::processAudioBuffer(sampleFrame* buf, const fpp_t frames) s[1] = temp - s[1]; } - s[0] *= gainResult[0] * m_outGainVal * (outBalance > 0 ? 1 - outBalance : 1); - s[1] *= gainResult[1] * m_outGainVal * (outBalance < 0 ? 1 + outBalance : 1); + s[0] *= gainResult[0] * m_inGainVal * m_outGainVal * (outBalance > 0 ? 1 - outBalance : 1); + s[1] *= gainResult[1] * m_inGainVal * m_outGainVal * (outBalance < 0 ? 1 + outBalance : 1); if (midside)// Convert mid/side back to left/right { @@ -540,8 +540,8 @@ bool CompressorEffect::processAudioBuffer(sampleFrame* buf, const fpp_t frames) // Negate wet signal from dry signal if (audition) { - s[0] = -s[0] + delayedDrySignal[0] * m_inGainVal; - s[1] = -s[1] + delayedDrySignal[1] * m_inGainVal; + s[0] = (-s[0] + delayedDrySignal[0] * m_outGainVal); + s[1] = (-s[1] + delayedDrySignal[1] * m_outGainVal); } else if (autoMakeup) { From 35698d14279dd2fb66171be3d33bc771a1e031a3 Mon Sep 17 00:00:00 2001 From: Robert Daniel Black Date: Fri, 19 Feb 2021 13:00:57 -0700 Subject: [PATCH 27/33] Fix stuff and things --- plugins/Compressor/Compressor.cpp | 10 ++-- .../Compressor/CompressorControlDialog.cpp | 46 +++++++++---------- plugins/Compressor/CompressorControlDialog.h | 1 + 3 files changed, 29 insertions(+), 28 deletions(-) diff --git a/plugins/Compressor/Compressor.cpp b/plugins/Compressor/Compressor.cpp index 06f690a0185..71d646b499a 100755 --- a/plugins/Compressor/Compressor.cpp +++ b/plugins/Compressor/Compressor.cpp @@ -85,7 +85,6 @@ CompressorEffect::CompressorEffect(Model* parent, const Descriptor::SubPluginFea connect(&m_compressorControls.m_ratioModel, SIGNAL(dataChanged()), this, SLOT(calcAutoMakeup())); connect(&m_compressorControls.m_kneeModel, SIGNAL(dataChanged()), this, SLOT(calcAutoMakeup())); connect(&m_compressorControls.m_autoMakeupModel, SIGNAL(dataChanged()), this, SLOT(calcAutoMakeup())); - connect(&m_compressorControls.m_inGainModel, SIGNAL(dataChanged()), this, SLOT(calcAutoMakeup())); connect(Engine::mixer(), SIGNAL(sampleRateChanged()), this, SLOT(changeSampleRate())); changeSampleRate(); @@ -617,16 +616,17 @@ void CompressorEffect::changeSampleRate() m_preLookaheadBuf[0].resize(m_lookaheadDelayLength); m_preLookaheadBuf[1].resize(m_lookaheadDelayLength); - calcAutoMakeup(); + calcThreshold(); + calcKnee(); + calcRatio(); + calcAutoMakeup();// This should be after Threshold, Knee, and Ratio + calcAttack(); calcRelease(); - calcRatio(); calcRange(); calcLookaheadLength(); calcHold(); resizeRMS(); - calcThreshold(); - calcKnee(); calcOutGain(); calcInGain(); calcTiltCoeffs(); diff --git a/plugins/Compressor/CompressorControlDialog.cpp b/plugins/Compressor/CompressorControlDialog.cpp index fb141b7de91..ab451cc4289 100755 --- a/plugins/Compressor/CompressorControlDialog.cpp +++ b/plugins/Compressor/CompressorControlDialog.cpp @@ -380,8 +380,6 @@ void CompressorControlDialog::updateDisplay() 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_lastPoint = dbfsToYPoint(-9999); - //m_lastGainPoint = dbfsToYPoint(0); } const float peakAvg = (m_controls->m_effect->m_displayPeak[0] + m_controls->m_effect->m_displayPeak[1]) * 0.5f; @@ -562,27 +560,29 @@ void CompressorControlDialog::updateDisplay() void CompressorControlDialog::paintEvent(QPaintEvent *event) { - if (isVisible()) + if (!isVisible()) { - QPainter p(this); + return; + } - p.setCompositionMode(QPainter::CompositionMode_Source); - p.fillRect(0, 0, m_windowSizeX, m_windowSizeY, QColor("transparent")); - p.setCompositionMode(QPainter::CompositionMode_SourceOver); + QPainter p(this); - p.drawPixmap(0, 0, m_graphPixmap); - p.drawPixmap(0, 0, m_visPixmap); - p.setOpacity(0.25); - p.drawPixmap(0, 0, m_kneePixmap); - p.setOpacity(1); - if (m_controls->m_effect->isEnabled() && m_controls->m_effect->isRunning()) - { - p.drawPixmap(0, 0, m_kneePixmap2); - } - p.drawPixmap(0, 0, m_miscPixmap); + p.setCompositionMode(QPainter::CompositionMode_Source); + p.fillRect(0, 0, m_windowSizeX, m_windowSizeY, QColor("transparent")); + p.setCompositionMode(QPainter::CompositionMode_SourceOver); - p.end(); + p.drawPixmap(0, 0, m_graphPixmap); + p.drawPixmap(0, 0, m_visPixmap); + p.setOpacity(0.25); + p.drawPixmap(0, 0, m_kneePixmap); + p.setOpacity(1); + if (m_controls->m_effect->isEnabled() && m_controls->m_effect->isRunning()) + { + p.drawPixmap(0, 0, m_kneePixmap2); } + p.drawPixmap(0, 0, m_miscPixmap); + + p.end(); } @@ -606,7 +606,7 @@ void CompressorControlDialog::resizeEvent(QResizeEvent *event) void CompressorControlDialog::wheelEvent(QWheelEvent * event) { const float temp = m_dbRange; - m_dbRange = round(qBound(3.f, m_dbRange - event->delta() / 20.f, 96.f) / 3.f) * 3.f; + m_dbRange = round(qBound(COMP_GRID_SPACING, m_dbRange - event->delta() / 20.f, 96.f) / COMP_GRID_SPACING) * COMP_GRID_SPACING; // Only reset view if the scolling had an effect if (m_dbRange != temp) @@ -635,11 +635,11 @@ void CompressorControlDialog::resetGraph() // Redraw graph p.setPen(QPen(m_graphColor, 1)); - for (int i = 1; i < m_dbRange / 3.f + 1; ++i) + for (int i = 1; i < m_dbRange / COMP_GRID_SPACING + 1; ++i) { - p.drawLine(0, dbfsToYPoint(-3 * i), m_windowSizeX, dbfsToYPoint(-3 * i)); - p.drawLine(dbfsToXPoint(-3 * i), 0, dbfsToXPoint(-3 * i), m_kneeWindowSizeY); - p.drawText(QRectF(m_windowSizeX - 50, dbfsToYPoint(-3 * i), 50, 50), Qt::AlignRight | Qt::AlignTop, QString::number(i * -3)); + p.drawLine(0, dbfsToYPoint(-COMP_GRID_SPACING * i), m_windowSizeX, dbfsToYPoint(-COMP_GRID_SPACING * i)); + p.drawLine(dbfsToXPoint(-COMP_GRID_SPACING * i), 0, dbfsToXPoint(-COMP_GRID_SPACING * i), m_kneeWindowSizeY); + p.drawText(QRectF(m_windowSizeX - 50, dbfsToYPoint(-COMP_GRID_SPACING * i), 50, 50), Qt::AlignRight | Qt::AlignTop, QString::number(i * -COMP_GRID_SPACING)); } p.end(); diff --git a/plugins/Compressor/CompressorControlDialog.h b/plugins/Compressor/CompressorControlDialog.h index 3af40313bbb..ed753998b9b 100755 --- a/plugins/Compressor/CompressorControlDialog.h +++ b/plugins/Compressor/CompressorControlDialog.h @@ -49,6 +49,7 @@ 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.0;// 3 db per grid line constexpr float COMP_NOISE_FLOOR = 0.000001;// -120 dbFs From 73845a936176634765de143422653b98a2a7d279 Mon Sep 17 00:00:00 2001 From: Robert Daniel Black Date: Fri, 19 Feb 2021 13:25:56 -0700 Subject: [PATCH 28/33] Implement favorable mousewheel physics --- plugins/Compressor/CompressorControlDialog.cpp | 6 +++++- plugins/Compressor/CompressorControlDialog.h | 3 ++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/plugins/Compressor/CompressorControlDialog.cpp b/plugins/Compressor/CompressorControlDialog.cpp index ab451cc4289..bc871251ff8 100755 --- a/plugins/Compressor/CompressorControlDialog.cpp +++ b/plugins/Compressor/CompressorControlDialog.cpp @@ -606,7 +606,9 @@ void CompressorControlDialog::resizeEvent(QResizeEvent *event) void CompressorControlDialog::wheelEvent(QWheelEvent * event) { const float temp = m_dbRange; - m_dbRange = round(qBound(COMP_GRID_SPACING, m_dbRange - event->delta() / 20.f, 96.f) / COMP_GRID_SPACING) * COMP_GRID_SPACING; + m_dbRange = round( + qBound(COMP_GRID_SPACING, m_dbRange - copysignf(COMP_GRID_SPACING, event->delta()), COMP_GRID_MAX) + / COMP_GRID_SPACING) * COMP_GRID_SPACING; // Only reset view if the scolling had an effect if (m_dbRange != temp) @@ -631,6 +633,8 @@ void CompressorControlDialog::resetGraph() p.setCompositionMode(QPainter::CompositionMode_SourceOver); p.setPen(QPen(m_textColor, 1)); + + // Arbitrary formula for increasing font size when window size increases p.setFont(QFont("Arial", qMax(int(m_windowSizeY / 1080.f * 24), 12))); // Redraw graph diff --git a/plugins/Compressor/CompressorControlDialog.h b/plugins/Compressor/CompressorControlDialog.h index ed753998b9b..0a70bda5c2e 100755 --- a/plugins/Compressor/CompressorControlDialog.h +++ b/plugins/Compressor/CompressorControlDialog.h @@ -49,7 +49,8 @@ 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.0;// 3 db per grid line +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 From 994b513783216e9f4e72e49cc3aeef78e2987611 Mon Sep 17 00:00:00 2001 From: Robert Daniel Black Date: Wed, 3 Mar 2021 16:29:08 -0700 Subject: [PATCH 29/33] Resolve stuff --- plugins/Compressor/Compressor.cpp | 14 +- plugins/Compressor/Compressor.h | 5 +- .../Compressor/CompressorControlDialog.cpp | 333 +++++++++--------- plugins/Compressor/CompressorControlDialog.h | 29 +- plugins/Compressor/CompressorControls.cpp | 1 + plugins/Compressor/CompressorControls.h | 3 +- 6 files changed, 214 insertions(+), 171 deletions(-) diff --git a/plugins/Compressor/Compressor.cpp b/plugins/Compressor/Compressor.cpp index 71d646b499a..af83eb12494 100755 --- a/plugins/Compressor/Compressor.cpp +++ b/plugins/Compressor/Compressor.cpp @@ -25,9 +25,9 @@ #include "Compressor.h" #include "embed.h" +#include "interpolation.h" #include "lmms_math.h" #include "plugin_export.h" -#include "interpolation.h" extern "C" { @@ -436,22 +436,26 @@ bool CompressorEffect::processAudioBuffer(sampleFrame* buf, const fpp_t frames) switch (stereoLink) { - case 1:// Maximum + case Unlinked: + { + break; + } + case Maximum: { gainResult[0] = gainResult[1] = qMin(gainResult[0], gainResult[1]); break; } - case 2:// Average + case Average: { gainResult[0] = gainResult[1] = (gainResult[0] + gainResult[1]) * 0.5f; break; } - case 3:// Minimum + case Minimum: { gainResult[0] = gainResult[1] = qMax(gainResult[0], gainResult[1]); break; } - case 4:// Blend + case Blend: { if (blend > 0)// 0 is unlinked { diff --git a/plugins/Compressor/Compressor.h b/plugins/Compressor/Compressor.h index a035093bc28..323db40a354 100755 --- a/plugins/Compressor/Compressor.h +++ b/plugins/Compressor/Compressor.h @@ -26,8 +26,9 @@ #ifndef COMPRESSOR_H #define COMPRESSOR_H -#include "Effect.h" #include "CompressorControls.h" + +#include "Effect.h" #include "ValueBuffer.h" #include "RmsHelper.h" @@ -76,6 +77,8 @@ private slots: inline int realmod(int k, int n); inline float realfmod(float k, float n); + enum StereoLinkModes { Unlinked, Maximum, Average, Minimum, Blend }; + std::vector m_preLookaheadBuf[2]; int m_preLookaheadBufLoc[2] = {0}; diff --git a/plugins/Compressor/CompressorControlDialog.cpp b/plugins/Compressor/CompressorControlDialog.cpp index bc871251ff8..414e210ad1c 100755 --- a/plugins/Compressor/CompressorControlDialog.cpp +++ b/plugins/Compressor/CompressorControlDialog.cpp @@ -27,15 +27,16 @@ #include "Compressor.h" #include "CompressorControlDialog.h" #include "CompressorControls.h" + #include "embed.h" -#include #include "GuiApplication.h" -#include "MainWindow.h" +#include "gui_templates.h" #include "interpolation.h" +#include "MainWindow.h" #include +#include #include #include "ToolTip.h" -#include "gui_templates.h" CompressorControlDialog::CompressorControlDialog(CompressorControls* controls) : EffectControlDialog(controls), @@ -168,9 +169,6 @@ CompressorControlDialog::CompressorControlDialog(CompressorControls* controls) : 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); @@ -365,11 +363,11 @@ void CompressorControlDialog::updateDisplay() int elapsedMil = m_timeElapsed.elapsed(); m_timeElapsed.restart(); m_timeSinceLastUpdate += elapsedMil; - int compPixelMovement = int(m_timeSinceLastUpdate / COMP_MILLI_PER_PIXEL); + m_compPixelMovement = int(m_timeSinceLastUpdate / COMP_MILLI_PER_PIXEL); m_timeSinceLastUpdate %= COMP_MILLI_PER_PIXEL; // Time Change / Daylight Savings Time protection - if (!compPixelMovement || compPixelMovement <= 0) + if (!m_compPixelMovement || m_compPixelMovement <= 0) { return; } @@ -382,178 +380,200 @@ void CompressorControlDialog::updateDisplay() m_controls->m_effect->m_displayGain[1] = 1; } - const float peakAvg = (m_controls->m_effect->m_displayPeak[0] + m_controls->m_effect->m_displayPeak[1]) * 0.5f; - const float gainAvg = (m_controls->m_effect->m_displayGain[0] + m_controls->m_effect->m_displayGain[1]) * 0.5f; + 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_yPoint = dbfsToYPoint(ampToDbfs(m_peakAvg)); + m_yGainPoint = dbfsToYPoint(ampToDbfs(m_gainAvg)); - QPainter p; - float yPoint = dbfsToYPoint(ampToDbfs(peakAvg)); - float yGainPoint = dbfsToYPoint(ampToDbfs(gainAvg)); + m_threshYPoint = dbfsToYPoint(m_controls->m_effect->m_thresholdVal); + m_threshXPoint = m_kneeWindowSizeY - m_threshYPoint; - int threshYPoint = dbfsToYPoint(m_controls->m_effect->m_thresholdVal); - int threshXPoint = m_kneeWindowSizeY - threshYPoint; + drawVisPixmap(); - p.begin(&m_visPixmap); + 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 - p.setCompositionMode(QPainter::CompositionMode_Source); - p.drawPixmap(-compPixelMovement, 0, m_visPixmap); - p.fillRect(m_windowSizeX-compPixelMovement, 0, m_windowSizeX, m_windowSizeY, QColor("transparent")); - p.setCompositionMode(QPainter::CompositionMode_SourceOver); + 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); - p.setRenderHint(QPainter::Antialiasing, true); + m_p.setRenderHint(QPainter::Antialiasing, true); // Draw translucent portion of input volume line - p.setPen(QPen(m_inVolAreaColor, 1)); - for (int i = 0; i < compPixelMovement; ++i) + m_p.setPen(QPen(m_inVolAreaColor, 1)); + for (int i = 0; i < m_compPixelMovement; ++i) { - const int temp = linearInterpolate(m_lastPoint, yPoint, float(i) / float(compPixelMovement)); - p.drawLine(m_windowSizeX-compPixelMovement+i, temp, m_windowSizeX-compPixelMovement+i, m_windowSizeY); + 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 - p.setPen(QPen(m_inVolColor, 1)); - p.drawLine(m_windowSizeX-compPixelMovement-1, m_lastPoint, m_windowSizeX, yPoint); + 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 - p.setPen(QPen(m_outVolAreaColor, 1)); - for (int i = 0; i < compPixelMovement; ++i) + m_p.setPen(QPen(m_outVolAreaColor, 1)); + for (int i = 0; i < m_compPixelMovement; ++i) { - const int temp = linearInterpolate(m_lastPoint+m_lastGainPoint, yPoint+yGainPoint, float(i) / float(compPixelMovement)); - p.drawLine(m_windowSizeX-compPixelMovement+i, temp, m_windowSizeX-compPixelMovement+i, m_windowSizeY); + 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 - p.setPen(QPen(m_outVolColor, 1)); - p.drawLine(m_windowSizeX-compPixelMovement-1, m_lastPoint+m_lastGainPoint, m_windowSizeX, yPoint+yGainPoint); + 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 - p.setPen(QPen(m_gainReductionColor, 2)); - p.drawLine(m_windowSizeX-compPixelMovement-1, m_lastGainPoint, m_windowSizeX, yGainPoint); + m_p.setPen(QPen(m_gainReductionColor, 2)); + m_p.drawLine(m_windowSizeX-m_compPixelMovement-1, m_lastGainPoint, m_windowSizeX, m_yGainPoint); + + m_p.end(); +} - p.end(); - if (m_controls->m_effect->m_redrawKnee) - { - m_controls->m_effect->m_redrawKnee = false; +void CompressorControlDialog::redrawKnee() +{ + m_controls->m_effect->m_redrawKnee = false; - // Start drawing knee visualizer - p.begin(&m_kneePixmap); + // Start drawing knee visualizer + m_p.begin(&m_kneePixmap); - p.setRenderHint(QPainter::Antialiasing, false); + m_p.setRenderHint(QPainter::Antialiasing, false); - // Clear display - p.setCompositionMode(QPainter::CompositionMode_Source); - p.fillRect(0, 0, m_windowSizeX, m_kneeWindowSizeY, QColor("transparent")); - p.setCompositionMode(QPainter::CompositionMode_SourceOver); + // 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); - p.setRenderHint(QPainter::Antialiasing, true); + m_p.setRenderHint(QPainter::Antialiasing, true); - p.setPen(QPen(m_kneeColor, 3)); + m_p.setPen(QPen(m_kneeColor, 3)); - // Limiter = infinite ratio - float actualRatio = m_controls->m_limiterModel.value() ? 0 : m_controls->m_effect->m_ratioVal; + // 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); + // 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 - p.drawLine(0, m_kneeWindowSizeY, dbfsToXPoint(kneePoint1), dbfsToYPoint(kneePoint1)); - if (dbfsToXPoint(kneePoint2X) < m_kneeWindowSizeY) - { - p.drawLine(dbfsToXPoint(kneePoint2X), dbfsToYPoint(kneePoint2Y), m_kneeWindowSizeY, dbfsToYPoint(ratioPoint)); - } + // 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) - { - p.setPen(QPen(m_kneeColor2, 3)); + // 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}; + 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); + // 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)); + 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)); - p.drawLine(dbfsToXPoint(prevPoint[0]), dbfsToYPoint(prevPoint[1]), dbfsToXPoint(newPoint[0]), dbfsToYPoint(newPoint[1])); + m_p.drawLine(dbfsToXPoint(prevPoint[0]), dbfsToYPoint(prevPoint[1]), dbfsToXPoint(newPoint[0]), dbfsToYPoint(newPoint[1])); - prevPoint[0] = newPoint[0]; - prevPoint[1] = newPoint[1]; - } + prevPoint[0] = newPoint[0]; + prevPoint[1] = newPoint[1]; } + } - p.setRenderHint(QPainter::Antialiasing, false); + m_p.setRenderHint(QPainter::Antialiasing, false); - // Erase right portion - p.setCompositionMode(QPainter::CompositionMode_Source); - p.fillRect(m_kneeWindowSizeX + 1, 0, m_windowSizeX, m_kneeWindowSizeY, QColor("transparent")); - p.setCompositionMode(QPainter::CompositionMode_SourceOver); + // 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); - p.end(); + m_p.end(); - p.begin(&m_kneePixmap2); - - p.setCompositionMode(QPainter::CompositionMode_Source); - p.fillRect(0, 0, m_windowSizeX, m_kneeWindowSizeY, QColor("transparent")); - p.setCompositionMode(QPainter::CompositionMode_SourceOver); + 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); - p.end(); + m_p.end(); - m_lastKneePoint = 0; - } + m_lastKneePoint = 0; +} - // Start drawing second knee layer - p.begin(&m_kneePixmap2); - p.setRenderHint(QPainter::Antialiasing, false); +void CompressorControlDialog::drawKneePixmap2() +{ + m_p.begin(&m_kneePixmap2); + + m_p.setRenderHint(QPainter::Antialiasing, false); - int kneePoint = dbfsToXPoint(ampToDbfs(peakAvg)); + int kneePoint = dbfsToXPoint(ampToDbfs(m_peakAvg)); if (kneePoint > m_lastKneePoint) { QRectF knee2Rect = QRect(m_lastKneePoint, 0, kneePoint - m_lastKneePoint, m_kneeWindowSizeY); - p.drawPixmap(knee2Rect, m_kneePixmap, knee2Rect); + m_p.drawPixmap(knee2Rect, m_kneePixmap, knee2Rect); } else { - p.setCompositionMode(QPainter::CompositionMode_Source); - p.fillRect(kneePoint, 0, m_lastKneePoint, m_kneeWindowSizeY, QColor("transparent")); - p.setCompositionMode(QPainter::CompositionMode_SourceOver); + 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; - p.end(); - - if (m_controls->m_effect->m_redrawThreshold) - { - p.begin(&m_miscPixmap); + m_p.end(); +} - p.setCompositionMode(QPainter::CompositionMode_Source); - p.fillRect(0, 0, m_windowSizeX, m_windowSizeY, QColor("transparent")); - p.setCompositionMode(QPainter::CompositionMode_SourceOver); - p.setRenderHint(QPainter::Antialiasing, true); +void CompressorControlDialog::drawMiscPixmap() +{ + m_p.begin(&m_miscPixmap); - // Draw threshold lines - p.setPen(QPen(m_threshColor, 2, Qt::DotLine)); - p.drawLine(0, threshYPoint, m_windowSizeX, threshYPoint); - p.drawLine(threshXPoint, 0, threshXPoint, m_kneeWindowSizeY); + m_p.setCompositionMode(QPainter::CompositionMode_Source); + m_p.fillRect(0, 0, m_windowSizeX, m_windowSizeY, QColor("transparent")); + m_p.setCompositionMode(QPainter::CompositionMode_SourceOver); - p.end(); + m_p.setRenderHint(QPainter::Antialiasing, true); - m_controls->m_effect->m_redrawThreshold = false; - } + // 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_lastPoint = yPoint; - m_lastGainPoint = yGainPoint; + m_p.end(); - update(); + m_controls->m_effect->m_redrawThreshold = false; } @@ -565,24 +585,24 @@ void CompressorControlDialog::paintEvent(QPaintEvent *event) return; } - QPainter p(this); + m_p.begin(this); - p.setCompositionMode(QPainter::CompositionMode_Source); - p.fillRect(0, 0, m_windowSizeX, m_windowSizeY, QColor("transparent")); - p.setCompositionMode(QPainter::CompositionMode_SourceOver); + m_p.setCompositionMode(QPainter::CompositionMode_Source); + m_p.fillRect(0, 0, m_windowSizeX, m_windowSizeY, QColor("transparent")); + m_p.setCompositionMode(QPainter::CompositionMode_SourceOver); - p.drawPixmap(0, 0, m_graphPixmap); - p.drawPixmap(0, 0, m_visPixmap); - p.setOpacity(0.25); - p.drawPixmap(0, 0, m_kneePixmap); - p.setOpacity(1); + 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()) { - p.drawPixmap(0, 0, m_kneePixmap2); + m_p.drawPixmap(0, 0, m_kneePixmap2); } - p.drawPixmap(0, 0, m_miscPixmap); + m_p.drawPixmap(0, 0, m_miscPixmap); - p.end(); + m_p.end(); } @@ -606,47 +626,44 @@ void CompressorControlDialog::resizeEvent(QResizeEvent *event) void CompressorControlDialog::wheelEvent(QWheelEvent * event) { const float temp = m_dbRange; - m_dbRange = round( - qBound(COMP_GRID_SPACING, m_dbRange - copysignf(COMP_GRID_SPACING, event->delta()), COMP_GRID_MAX) - / COMP_GRID_SPACING) * COMP_GRID_SPACING; + 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) { - resetGraph(); + drawGraph(); m_controls->m_effect->m_redrawKnee = true; m_controls->m_effect->m_redrawThreshold = true; } } -void CompressorControlDialog::resetGraph() +void CompressorControlDialog::drawGraph() { - QPainter p; - - p.begin(&m_graphPixmap); + m_p.begin(&m_graphPixmap); - p.setRenderHint(QPainter::Antialiasing, false); + m_p.setRenderHint(QPainter::Antialiasing, false); - p.setCompositionMode(QPainter::CompositionMode_Source); - p.fillRect(0, 0, m_windowSizeX, m_windowSizeY, QColor("transparent")); - p.setCompositionMode(QPainter::CompositionMode_SourceOver); + m_p.setCompositionMode(QPainter::CompositionMode_Source); + m_p.fillRect(0, 0, m_windowSizeX, m_windowSizeY, QColor("transparent")); + m_p.setCompositionMode(QPainter::CompositionMode_SourceOver); - p.setPen(QPen(m_textColor, 1)); + m_p.setPen(QPen(m_textColor, 1)); // Arbitrary formula for increasing font size when window size increases - p.setFont(QFont("Arial", qMax(int(m_windowSizeY / 1080.f * 24), 12))); + m_p.setFont(QFont("Arial", qMax(int(m_windowSizeY / 1080.f * 24), 12))); // Redraw graph - p.setPen(QPen(m_graphColor, 1)); + m_p.setPen(QPen(m_graphColor, 1)); for (int i = 1; i < m_dbRange / COMP_GRID_SPACING + 1; ++i) { - p.drawLine(0, dbfsToYPoint(-COMP_GRID_SPACING * i), m_windowSizeX, dbfsToYPoint(-COMP_GRID_SPACING * i)); - p.drawLine(dbfsToXPoint(-COMP_GRID_SPACING * i), 0, dbfsToXPoint(-COMP_GRID_SPACING * i), m_kneeWindowSizeY); - 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.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)); } - p.end(); + m_p.end(); } @@ -663,23 +680,21 @@ void CompressorControlDialog::resetCompressorView() m_controls->m_effect->m_redrawThreshold = true; m_lastKneePoint = 0; - QPainter p; - - resetGraph(); + drawGraph(); - p.begin(&m_visPixmap); + m_p.begin(&m_visPixmap); - p.setCompositionMode(QPainter::CompositionMode_Source); - p.fillRect(0, 0, m_windowSizeX, m_windowSizeY, QColor("transparent")); - p.setCompositionMode(QPainter::CompositionMode_SourceOver); + 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 - p.setPen(QPen(m_resetColor, 3)); - p.drawLine(m_windowSizeX, 0, m_windowSizeX, m_windowSizeY); + m_p.setPen(QPen(m_resetColor, 3)); + m_p.drawLine(m_windowSizeX, 0, m_windowSizeX, m_windowSizeY); - p.end(); + m_p.end(); m_controlsBoxLabel->move(m_controlsBoxX, m_controlsBoxY); m_rmsEnabledLabel->move(m_controlsBoxX + 429, m_controlsBoxY + 209); diff --git a/plugins/Compressor/CompressorControlDialog.h b/plugins/Compressor/CompressorControlDialog.h index 0a70bda5c2e..43f860f061b 100755 --- a/plugins/Compressor/CompressorControlDialog.h +++ b/plugins/Compressor/CompressorControlDialog.h @@ -25,18 +25,20 @@ #ifndef COMPRESSOR_CONTROL_DIALOG_H #define COMPRESSOR_CONTROL_DIALOG_H +#include "../Eq/EqFader.h" #include "EffectControlDialog.h" -#include -#include +#include "GuiApplication.h" #include "Knob.h" -#include "../Eq/EqFader.h" #include "MainWindow.h" -#include "GuiApplication.h" #include "PixmapButton.h" +#include +#include #include +#include #include + constexpr int COMP_MILLI_PER_PIXEL = 6; constexpr int MIN_COMP_SCREEN_X = 800; constexpr int MIN_COMP_SCREEN_Y = 360; @@ -129,7 +131,13 @@ private slots: void makeLargeKnob(Knob * knob, QString hint, QString unit); void makeSmallKnob(Knob * knob, QString hint, QString unit); void resetCompressorView(); - void resetGraph(); + void drawVisPixmap(); + void redrawKnee(); + void drawKneePixmap2(); + void drawMiscPixmap(); + void drawGraph(); + + QPainter m_p; QBasicTimer m_updateTimer; @@ -169,6 +177,17 @@ private slots: 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; diff --git a/plugins/Compressor/CompressorControls.cpp b/plugins/Compressor/CompressorControls.cpp index 55a281660d0..0a2c5b5fa44 100755 --- a/plugins/Compressor/CompressorControls.cpp +++ b/plugins/Compressor/CompressorControls.cpp @@ -27,6 +27,7 @@ #include "CompressorControls.h" #include "Compressor.h" + #include "Engine.h" #include "Song.h" diff --git a/plugins/Compressor/CompressorControls.h b/plugins/Compressor/CompressorControls.h index 3e28c5d71df..775a3be89f5 100755 --- a/plugins/Compressor/CompressorControls.h +++ b/plugins/Compressor/CompressorControls.h @@ -25,8 +25,9 @@ #ifndef COMPRESSOR_CONTROLS_H #define COMPRESSOR_CONTROLS_H -#include "EffectControls.h" #include "CompressorControlDialog.h" + +#include "EffectControls.h" #include "Knob.h" From 795e8a79864e65dfb82a85aaaaed89ca7a6c9b61 Mon Sep 17 00:00:00 2001 From: Robert Daniel Black Date: Wed, 3 Mar 2021 16:45:32 -0700 Subject: [PATCH 30/33] Resolve more stuff --- .../Compressor/CompressorControlDialog.cpp | 38 ++++--------- plugins/Compressor/CompressorControlDialog.h | 55 ++++--------------- 2 files changed, 23 insertions(+), 70 deletions(-) diff --git a/plugins/Compressor/CompressorControlDialog.cpp b/plugins/Compressor/CompressorControlDialog.cpp index 414e210ad1c..9fed812ec3b 100755 --- a/plugins/Compressor/CompressorControlDialog.cpp +++ b/plugins/Compressor/CompressorControlDialog.cpp @@ -40,7 +40,18 @@ CompressorControlDialog::CompressorControlDialog(CompressorControls* controls) : EffectControlDialog(controls), - m_controls(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; @@ -745,28 +756,3 @@ void CompressorControlDialog::resetCompressorView() m_autoReleaseKnob->move(m_controlsBoxX + 590, m_controlsBoxY + 38); lookaheadButton->move(m_controlsBoxX + 202, m_controlsBoxY + 171); } - -// For theming purposes -QColor const & CompressorControlDialog::inVolAreaColor() const {return m_inVolAreaColor;} -QColor const & CompressorControlDialog::inVolColor() const {return m_inVolColor;} -QColor const & CompressorControlDialog::outVolAreaColor() const {return m_outVolAreaColor;} -QColor const & CompressorControlDialog::outVolColor() const {return m_outVolColor;} -QColor const & CompressorControlDialog::gainReductionColor() const {return m_gainReductionColor;} -QColor const & CompressorControlDialog::kneeColor() const {return m_kneeColor;} -QColor const & CompressorControlDialog::kneeColor2() const {return m_kneeColor2;} -QColor const & CompressorControlDialog::threshColor() const {return m_threshColor;} -QColor const & CompressorControlDialog::textColor() const {return m_textColor;} -QColor const & CompressorControlDialog::graphColor() const {return m_graphColor;} -QColor const & CompressorControlDialog::resetColor() const {return m_resetColor;} - -void CompressorControlDialog::setInVolAreaColor(const QColor & c){m_inVolAreaColor = c;} -void CompressorControlDialog::setInVolColor(const QColor & c){m_inVolColor = c;} -void CompressorControlDialog::setOutVolAreaColor(const QColor & c){m_outVolAreaColor = c;} -void CompressorControlDialog::setOutVolColor(const QColor & c){m_outVolColor = c;} -void CompressorControlDialog::setGainReductionColor(const QColor & c){m_gainReductionColor = c;} -void CompressorControlDialog::setKneeColor(const QColor & c){m_kneeColor = c;} -void CompressorControlDialog::setKneeColor2(const QColor & c){m_kneeColor2 = c;} -void CompressorControlDialog::setThreshColor(const QColor & c){m_threshColor = c;} -void CompressorControlDialog::setTextColor(const QColor & c){m_textColor = c;} -void CompressorControlDialog::setGraphColor(const QColor & c){m_graphColor = c;} -void CompressorControlDialog::setResetColor(const QColor & c){m_resetColor = c;} diff --git a/plugins/Compressor/CompressorControlDialog.h b/plugins/Compressor/CompressorControlDialog.h index 43f860f061b..84bb3820867 100755 --- a/plugins/Compressor/CompressorControlDialog.h +++ b/plugins/Compressor/CompressorControlDialog.h @@ -70,50 +70,17 @@ class CompressorControlDialog : public EffectControlDialog QSize sizeHint() const override {return QSize(COMP_SCREEN_X, COMP_SCREEN_Y);} // For theming purposes - Q_PROPERTY(QColor inVolAreaColor READ inVolAreaColor WRITE setInVolAreaColor) - Q_PROPERTY(QColor inVolColor READ inVolColor WRITE setInVolColor) - Q_PROPERTY(QColor outVolAreaColor READ outVolAreaColor WRITE setOutVolAreaColor) - Q_PROPERTY(QColor outVolColor READ outVolColor WRITE setOutVolColor) - Q_PROPERTY(QColor gainReductionColor READ gainReductionColor WRITE setGainReductionColor) - Q_PROPERTY(QColor kneeColor READ kneeColor WRITE setKneeColor) - Q_PROPERTY(QColor kneeColor2 READ kneeColor2 WRITE setKneeColor2) - Q_PROPERTY(QColor threshColor READ threshColor WRITE setThreshColor) - Q_PROPERTY(QColor textColor READ textColor WRITE setTextColor) - Q_PROPERTY(QColor graphColor READ graphColor WRITE setGraphColor) - Q_PROPERTY(QColor resetColor READ resetColor WRITE setResetColor) - - QColor const & inVolAreaColor() const; - void setInVolAreaColor( const QColor & c ); - - QColor const & inVolColor() const; - void setInVolColor( const QColor & c ); - - QColor const & outVolAreaColor() const; - void setOutVolAreaColor( const QColor & c ); - - QColor const & outVolColor() const; - void setOutVolColor( const QColor & c ); - - QColor const & gainReductionColor() const; - void setGainReductionColor( const QColor & c ); - - QColor const & kneeColor() const; - void setKneeColor( const QColor & c ); - - QColor const & kneeColor2() const; - void setKneeColor2( const QColor & c ); - - QColor const & threshColor() const; - void setThreshColor( const QColor & c ); - - QColor const & textColor() const; - void setTextColor( const QColor & c ); - - QColor const & graphColor() const; - void setGraphColor( const QColor & c ); - - QColor const & resetColor() const; - void setResetColor( const QColor & c ); + 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; From 644334f8a48752173cfa36b57124b927858687c3 Mon Sep 17 00:00:00 2001 From: Robert Daniel Black Date: Mon, 8 Mar 2021 17:21:32 -0700 Subject: [PATCH 31/33] Remove unnecessary include --- plugins/Compressor/CompressorControlDialog.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/plugins/Compressor/CompressorControlDialog.cpp b/plugins/Compressor/CompressorControlDialog.cpp index 9fed812ec3b..0c3ed857cec 100755 --- a/plugins/Compressor/CompressorControlDialog.cpp +++ b/plugins/Compressor/CompressorControlDialog.cpp @@ -33,7 +33,6 @@ #include "gui_templates.h" #include "interpolation.h" #include "MainWindow.h" -#include #include #include #include "ToolTip.h" From af144f21fde490da80018da683571ac857830a49 Mon Sep 17 00:00:00 2001 From: Robert Daniel Black Date: Wed, 10 Mar 2021 13:40:34 -0700 Subject: [PATCH 32/33] Fix bugs with input balance and mid/side compression, and other stuff and things --- plugins/Compressor/Compressor.cpp | 58 +++++++++++-------- .../Compressor/CompressorControlDialog.cpp | 2 +- plugins/Compressor/CompressorControlDialog.h | 2 +- 3 files changed, 36 insertions(+), 26 deletions(-) diff --git a/plugins/Compressor/Compressor.cpp b/plugins/Compressor/Compressor.cpp index af83eb12494..5787b219c1c 100755 --- a/plugins/Compressor/Compressor.cpp +++ b/plugins/Compressor/Compressor.cpp @@ -62,29 +62,29 @@ CompressorEffect::CompressorEffect(Model* parent, const Descriptor::SubPluginFea // 200 ms m_crestTimeConst = exp(-1.f / (0.2f * m_sampleRate)); - connect(&m_compressorControls.m_attackModel, SIGNAL(dataChanged()), this, SLOT(calcAttack())); - connect(&m_compressorControls.m_releaseModel, SIGNAL(dataChanged()), this, SLOT(calcRelease())); - connect(&m_compressorControls.m_holdModel, SIGNAL(dataChanged()), this, SLOT(calcHold())); - connect(&m_compressorControls.m_ratioModel, SIGNAL(dataChanged()), this, SLOT(calcRatio())); - connect(&m_compressorControls.m_rangeModel, SIGNAL(dataChanged()), this, SLOT(calcRange())); - connect(&m_compressorControls.m_rmsModel, SIGNAL(dataChanged()), this, SLOT(resizeRMS())); - connect(&m_compressorControls.m_lookaheadLengthModel, SIGNAL(dataChanged()), this, SLOT(calcLookaheadLength())); - connect(&m_compressorControls.m_thresholdModel, SIGNAL(dataChanged()), this, SLOT(calcThreshold())); - connect(&m_compressorControls.m_kneeModel, SIGNAL(dataChanged()), this, SLOT(calcKnee())); - connect(&m_compressorControls.m_outGainModel, SIGNAL(dataChanged()), this, SLOT(calcOutGain())); - connect(&m_compressorControls.m_inGainModel, SIGNAL(dataChanged()), this, SLOT(calcInGain())); - connect(&m_compressorControls.m_tiltModel, SIGNAL(dataChanged()), this, SLOT(calcTiltCoeffs())); - connect(&m_compressorControls.m_tiltFreqModel, SIGNAL(dataChanged()), this, SLOT(calcTiltCoeffs())); - connect(&m_compressorControls.m_limiterModel, SIGNAL(dataChanged()), this, SLOT(redrawKnee())); - connect(&m_compressorControls.m_mixModel, SIGNAL(dataChanged()), this, SLOT(calcMix())); - - connect(&m_compressorControls.m_autoAttackModel, SIGNAL(dataChanged()), this, SLOT(calcAutoAttack())); - connect(&m_compressorControls.m_autoReleaseModel, SIGNAL(dataChanged()), this, SLOT(calcAutoRelease())); - - connect(&m_compressorControls.m_thresholdModel, SIGNAL(dataChanged()), this, SLOT(calcAutoMakeup())); - connect(&m_compressorControls.m_ratioModel, SIGNAL(dataChanged()), this, SLOT(calcAutoMakeup())); - connect(&m_compressorControls.m_kneeModel, SIGNAL(dataChanged()), this, SLOT(calcAutoMakeup())); - connect(&m_compressorControls.m_autoMakeupModel, SIGNAL(dataChanged()), this, SLOT(calcAutoMakeup())); + 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(); @@ -302,6 +302,13 @@ bool CompressorEffect::processAudioBuffer(sampleFrame* buf, const fpp_t frames) 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; @@ -526,6 +533,9 @@ bool CompressorEffect::processAudioBuffer(sampleFrame* buf, const fpp_t frames) s[1] = temp - s[1]; } + s[0] *= inBalance > 0 ? 1 - inBalance : 1; + s[1] *= inBalance < 0 ? 1 + inBalance : 1; + s[0] *= gainResult[0] * m_inGainVal * m_outGainVal * (outBalance > 0 ? 1 - outBalance : 1); s[1] *= gainResult[1] * m_inGainVal * m_outGainVal * (outBalance < 0 ? 1 + outBalance : 1); @@ -560,7 +570,7 @@ bool CompressorEffect::processAudioBuffer(sampleFrame* buf, const fpp_t frames) 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 += s[0] + s[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; diff --git a/plugins/Compressor/CompressorControlDialog.cpp b/plugins/Compressor/CompressorControlDialog.cpp index 0c3ed857cec..510699dc476 100755 --- a/plugins/Compressor/CompressorControlDialog.cpp +++ b/plugins/Compressor/CompressorControlDialog.cpp @@ -229,7 +229,7 @@ CompressorControlDialog::CompressorControlDialog(CompressorControls* controls) : 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")); + ToolTip::add(limitButton, tr("Set Ratio to infinity (is not guaranteed to limit audio volume)")); compressLimitGroup = new automatableButtonGroup(this); compressLimitGroup->addButton(compressButton); diff --git a/plugins/Compressor/CompressorControlDialog.h b/plugins/Compressor/CompressorControlDialog.h index 84bb3820867..84cae6728e4 100755 --- a/plugins/Compressor/CompressorControlDialog.h +++ b/plugins/Compressor/CompressorControlDialog.h @@ -209,7 +209,7 @@ private slots: PixmapButton * feedbackButton; PixmapButton * lookaheadButton; - QTime m_timeElapsed; + QElapsedTimer m_timeElapsed; int m_timeSinceLastUpdate = 0; friend class CompressorControls; From b78ddbcee1cf2f278603f553ef83286961b4fbcd Mon Sep 17 00:00:00 2001 From: Robert Daniel Black Date: Wed, 10 Mar 2021 14:38:37 -0700 Subject: [PATCH 33/33] Vastly improve visualizer drawing logic --- plugins/Compressor/Compressor.cpp | 56 ++++++++++--------- plugins/Compressor/Compressor.h | 1 + .../Compressor/CompressorControlDialog.cpp | 13 +++-- plugins/Compressor/CompressorControlDialog.h | 12 ++-- plugins/Compressor/CompressorControls.cpp | 4 +- 5 files changed, 47 insertions(+), 39 deletions(-) diff --git a/plugins/Compressor/Compressor.cpp b/plugins/Compressor/Compressor.cpp index 5787b219c1c..0847eacdbcc 100755 --- a/plugins/Compressor/Compressor.cpp +++ b/plugins/Compressor/Compressor.cpp @@ -246,6 +246,7 @@ bool CompressorEffect::processAudioBuffer(sampleFrame* buf, const fpp_t frames) 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); @@ -312,7 +313,8 @@ bool CompressorEffect::processAudioBuffer(sampleFrame* buf, const fpp_t frames) s[0] *= inBalance > 0 ? 1 - inBalance : 1; s[1] *= inBalance < 0 ? 1 + inBalance : 1; - float gainResult[2] = {0, 0}; + m_gainResult[0] = 0; + m_gainResult[1] = 0; for (int i = 0; i < 2; i++) { @@ -415,7 +417,7 @@ bool CompressorEffect::processAudioBuffer(sampleFrame* buf, const fpp_t frames) m_yL[i] = qMax(COMP_NOISE_FLOOR, m_yL[i]); // For the visualizer - m_displayPeak[i] = m_yL[i]; + m_displayPeak[i] = qMax(m_yL[i], m_displayPeak[i]); const float currentPeakDbfs = ampToDbfs(m_yL[i]); @@ -423,22 +425,22 @@ bool CompressorEffect::processAudioBuffer(sampleFrame* buf, const fpp_t frames) // depending on the measured input value. if (currentPeakDbfs - m_thresholdVal < -m_kneeVal)// Below knee { - gainResult[i] = currentPeakDbfs; + m_gainResult[i] = currentPeakDbfs; } else if (currentPeakDbfs - m_thresholdVal < m_kneeVal)// Within knee { const float temp = currentPeakDbfs - m_thresholdVal + m_kneeVal; - gainResult[i] = currentPeakDbfs + ((limiter ? 0 : m_ratioVal) - 1) * temp * temp / (4 * m_kneeVal); + m_gainResult[i] = currentPeakDbfs + ((limiter ? 0 : m_ratioVal) - 1) * temp * temp / (4 * m_kneeVal); } else// Above knee { - gainResult[i] = limiter + m_gainResult[i] = limiter ? m_thresholdVal : m_thresholdVal + (currentPeakDbfs - m_thresholdVal) * m_ratioVal; } - gainResult[i] = dbfsToAmp(gainResult[i]) / m_yL[i]; - gainResult[i] = qMax(m_rangeVal, gainResult[i]); + m_gainResult[i] = dbfsToAmp(m_gainResult[i]) / m_yL[i]; + m_gainResult[i] = qMax(m_rangeVal, m_gainResult[i]); } switch (stereoLink) @@ -449,17 +451,17 @@ bool CompressorEffect::processAudioBuffer(sampleFrame* buf, const fpp_t frames) } case Maximum: { - gainResult[0] = gainResult[1] = qMin(gainResult[0], gainResult[1]); + m_gainResult[0] = m_gainResult[1] = qMin(m_gainResult[0], m_gainResult[1]); break; } case Average: { - gainResult[0] = gainResult[1] = (gainResult[0] + gainResult[1]) * 0.5f; + m_gainResult[0] = m_gainResult[1] = (m_gainResult[0] + m_gainResult[1]) * 0.5f; break; } case Minimum: { - gainResult[0] = gainResult[1] = qMax(gainResult[0], gainResult[1]); + m_gainResult[0] = m_gainResult[1] = qMax(m_gainResult[0], m_gainResult[1]); break; } case Blend: @@ -468,23 +470,23 @@ bool CompressorEffect::processAudioBuffer(sampleFrame* buf, const fpp_t frames) { if (blend <= 1)// Blend to minimum volume { - const float temp1 = qMin(gainResult[0], gainResult[1]); - gainResult[0] = linearInterpolate(gainResult[0], temp1, blend); - gainResult[1] = linearInterpolate(gainResult[1], temp1, blend); + 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(gainResult[0], gainResult[1]); - const float temp2 = (gainResult[0] + gainResult[1]) * 0.5f; - gainResult[0] = linearInterpolate(temp1, temp2, blend - 1); - gainResult[1] = gainResult[0]; + 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 = (gainResult[0] + gainResult[1]) * 0.5f; - const float temp2 = qMax(gainResult[0], gainResult[1]); - gainResult[0] = linearInterpolate(temp1, temp2, blend - 2); - gainResult[1] = gainResult[0]; + 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; @@ -494,13 +496,13 @@ bool CompressorEffect::processAudioBuffer(sampleFrame* buf, const fpp_t frames) // Bias compression to the left or right (or mid or side) if (stereoBalance != 0) { - gainResult[0] = 1 - ((1 - gainResult[0]) * (stereoBalance > 0 ? 1 - stereoBalance : 1)); - gainResult[1] = 1 - ((1 - gainResult[1]) * (stereoBalance < 0 ? 1 + stereoBalance : 1)); + 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] = gainResult[0]; - m_displayGain[1] = gainResult[1]; + 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) @@ -536,8 +538,8 @@ bool CompressorEffect::processAudioBuffer(sampleFrame* buf, const fpp_t frames) s[0] *= inBalance > 0 ? 1 - inBalance : 1; s[1] *= inBalance < 0 ? 1 + inBalance : 1; - s[0] *= gainResult[0] * m_inGainVal * m_outGainVal * (outBalance > 0 ? 1 - outBalance : 1); - s[1] *= gainResult[1] * m_inGainVal * m_outGainVal * (outBalance < 0 ? 1 + outBalance : 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 { diff --git a/plugins/Compressor/Compressor.h b/plugins/Compressor/Compressor.h index 323db40a354..be9cd066264 100755 --- a/plugins/Compressor/Compressor.h +++ b/plugins/Compressor/Compressor.h @@ -135,6 +135,7 @@ private slots: float m_prevOut[2] = {0}; float m_yL[2]; + float m_gainResult[2]; float m_displayPeak[2]; float m_displayGain[2]; diff --git a/plugins/Compressor/CompressorControlDialog.cpp b/plugins/Compressor/CompressorControlDialog.cpp index 510699dc476..363098eec0f 100755 --- a/plugins/Compressor/CompressorControlDialog.cpp +++ b/plugins/Compressor/CompressorControlDialog.cpp @@ -22,19 +22,19 @@ * */ -#include - #include "Compressor.h" #include "CompressorControlDialog.h" #include "CompressorControls.h" +#include +#include +#include + #include "embed.h" #include "GuiApplication.h" #include "gui_templates.h" #include "interpolation.h" #include "MainWindow.h" -#include -#include #include "ToolTip.h" CompressorControlDialog::CompressorControlDialog(CompressorControls* controls) : @@ -393,6 +393,11 @@ void CompressorControlDialog::updateDisplay() 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)); diff --git a/plugins/Compressor/CompressorControlDialog.h b/plugins/Compressor/CompressorControlDialog.h index 84cae6728e4..7eb19c5b9f1 100755 --- a/plugins/Compressor/CompressorControlDialog.h +++ b/plugins/Compressor/CompressorControlDialog.h @@ -25,18 +25,18 @@ #ifndef COMPRESSOR_CONTROL_DIALOG_H #define COMPRESSOR_CONTROL_DIALOG_H -#include "../Eq/EqFader.h" -#include "EffectControlDialog.h" -#include "GuiApplication.h" -#include "Knob.h" -#include "MainWindow.h" -#include "PixmapButton.h" #include #include #include #include #include +#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; diff --git a/plugins/Compressor/CompressorControls.cpp b/plugins/Compressor/CompressorControls.cpp index 0a2c5b5fa44..772fb5261ee 100755 --- a/plugins/Compressor/CompressorControls.cpp +++ b/plugins/Compressor/CompressorControls.cpp @@ -23,11 +23,11 @@ */ -#include - #include "CompressorControls.h" #include "Compressor.h" +#include + #include "Engine.h" #include "Song.h"