mirror of
https://github.com/LMMS/lmms.git
synced 2025-12-24 23:28:31 -05:00
Follow-Up of 7db3fa94a1 .
This was done by setting `CMAKE_C_INCLUDE_WHAT_YOU_USE` and
`CMAKE_CXX_INCLUDE_WHAT_YOU_USE` to (broken down into multiple lines here,
note, all below `FL/x.h` is not required for C):
```
include-what-you-use;
-Xiwyu;--mapping_file=/usr/share/include-what-you-use/qt5_11.imp;
-Xiwyu;--keep=*/xmmintrin.h;
-Xiwyu;--keep=*/lmmsconfig.h;
-Xiwyu;--keep=*/weak_libjack.h;
-Xiwyu;--keep=*/sys/*;
-Xiwyu;--keep=*/debug.h;
-Xiwyu;--keep=*/SDL/*;
-Xiwyu;--keep=*/alsa/*;
-Xiwyu;--keep=*/FL/x.h;
-Xiwyu;--keep=*/MidiApple.h;
-Xiwyu;--keep=*/MidiWinMM.h;
-Xiwyu;--keep=*/AudioSoundIo.h;
-Xiwyu;--keep=*/OpulenZ/adplug/*;
-Xiwyu;--keep=QPainterPath;
-Xiwyu;--keep=QtTest
```
FAQ:
* Q: Does this speed-up a completely fresh compile?
* A: No, I measured it.
* Q: Does it speed up anything else?
* A: Yes. If you change one header, it can reduce the number of CPP files
that your compiler needs to recompile, or your IDE has to re-scan.
* Q: What other reasons are for this PR?
* A: It's idiomatic to only include headers if you need them. Also, it will
reduce output for those who want to use IWYU for new PRs.
Background:
This is just a remainder PR of what I planned. My original idea was to setup
a CI which warns you of useless includes (but not of all issues that IWYU
complains about). However, I could not see that this was favored on Discord.
A full IWYU CI also has the problem that it (possibly??) needs to compile
with `make -j 1`, which would make CI really slow.
However, for that plan, I had to fix the whole code base to be IWYU
compliant - which it now is.
844 lines
31 KiB
C++
Executable File
844 lines
31 KiB
C++
Executable File
/*
|
|
* CompressorControlDialog.cpp
|
|
*
|
|
* Copyright (c) 2020 Lost Robot <r94231@gmail.com>
|
|
*
|
|
* This file is part of LMMS - https://lmms.io
|
|
*
|
|
* This program is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU General Public
|
|
* License as published by the Free Software Foundation; either
|
|
* version 2 of the License, or (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public
|
|
* License along with this program (see COPYING); if not, write to the
|
|
* Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
|
|
* Boston, MA 02110-1301 USA.
|
|
*
|
|
*/
|
|
|
|
#include "Compressor.h"
|
|
#include "CompressorControlDialog.h"
|
|
#include "CompressorControls.h"
|
|
|
|
#include <cmath>
|
|
#include <QLabel>
|
|
#include <QPainter>
|
|
#include <QWheelEvent>
|
|
|
|
#include "AutomatableButton.h"
|
|
#include "embed.h"
|
|
#include "../Eq/EqFader.h"
|
|
#include "GuiApplication.h"
|
|
#include "Knob.h"
|
|
#include "MainWindow.h"
|
|
#include "PixmapButton.h"
|
|
|
|
namespace lmms::gui
|
|
{
|
|
|
|
|
|
CompressorControlDialog::CompressorControlDialog(CompressorControls* controls) :
|
|
EffectControlDialog(controls),
|
|
m_controls(controls)
|
|
{
|
|
setAutoFillBackground(false);
|
|
setAttribute(Qt::WA_NoSystemBackground, true);
|
|
|
|
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(KnobType::Styled, this);
|
|
makeLargeKnob(m_thresholdKnob, tr("Threshold:") , " dBFS");
|
|
m_thresholdKnob->setModel(&controls->m_thresholdModel);
|
|
m_thresholdKnob->setToolTip(tr("Volume at which the compression begins to take place"));
|
|
|
|
m_ratioKnob = new Knob(KnobType::Styled, this);
|
|
makeLargeKnob(m_ratioKnob, tr("Ratio:") , ":1");
|
|
m_ratioKnob->setModel(&controls->m_ratioModel);
|
|
m_ratioKnob->setToolTip(tr("How far the compressor must turn the volume down after crossing the threshold"));
|
|
|
|
m_attackKnob = new Knob(KnobType::Styled, this);
|
|
makeLargeKnob(m_attackKnob, tr("Attack:") , " ms");
|
|
m_attackKnob->setModel(&controls->m_attackModel);
|
|
m_attackKnob->setToolTip(tr("Speed at which the compressor starts to compress the audio"));
|
|
|
|
m_releaseKnob = new Knob(KnobType::Styled, this);
|
|
makeLargeKnob(m_releaseKnob, tr("Release:") , " ms");
|
|
m_releaseKnob->setModel(&controls->m_releaseModel);
|
|
m_releaseKnob->setToolTip(tr("Speed at which the compressor ceases to compress the audio"));
|
|
|
|
m_kneeKnob = new Knob(KnobType::Styled, this);
|
|
makeSmallKnob(m_kneeKnob, tr("Knee:") , " dB");
|
|
m_kneeKnob->setModel(&controls->m_kneeModel);
|
|
m_kneeKnob->setToolTip(tr("Smooth out the gain reduction curve around the threshold"));
|
|
|
|
m_rangeKnob = new Knob(KnobType::Styled, this);
|
|
makeSmallKnob(m_rangeKnob, tr("Range:") , " dBFS");
|
|
m_rangeKnob->setModel(&controls->m_rangeModel);
|
|
m_rangeKnob->setToolTip(tr("Maximum gain reduction"));
|
|
|
|
m_lookaheadLengthKnob = new Knob(KnobType::Styled, this);
|
|
makeSmallKnob(m_lookaheadLengthKnob, tr("Lookahead Length:") , " ms");
|
|
m_lookaheadLengthKnob->setModel(&controls->m_lookaheadLengthModel);
|
|
m_lookaheadLengthKnob->setToolTip(tr("How long the compressor has to react to the sidechain signal ahead of time"));
|
|
|
|
m_holdKnob = new Knob(KnobType::Styled, this);
|
|
makeSmallKnob(m_holdKnob, tr("Hold:") , " ms");
|
|
m_holdKnob->setModel(&controls->m_holdModel);
|
|
m_holdKnob->setToolTip(tr("Delay between attack and release stages"));
|
|
|
|
m_rmsKnob = new Knob(KnobType::Styled, this);
|
|
makeSmallKnob(m_rmsKnob, tr("RMS Size:") , "");
|
|
m_rmsKnob->setModel(&controls->m_rmsModel);
|
|
m_rmsKnob->setToolTip(tr("Size of the RMS buffer"));
|
|
|
|
m_inBalanceKnob = new Knob(KnobType::Styled, this);
|
|
makeSmallKnob(m_inBalanceKnob, tr("Input Balance:") , "");
|
|
m_inBalanceKnob->setModel(&controls->m_inBalanceModel);
|
|
m_inBalanceKnob->setToolTip(tr("Bias the input audio to the left/right or mid/side"));
|
|
|
|
m_outBalanceKnob = new Knob(KnobType::Styled, this);
|
|
makeSmallKnob(m_outBalanceKnob, tr("Output Balance:") , "");
|
|
m_outBalanceKnob->setModel(&controls->m_outBalanceModel);
|
|
m_outBalanceKnob->setToolTip(tr("Bias the output audio to the left/right or mid/side"));
|
|
|
|
m_stereoBalanceKnob = new Knob(KnobType::Styled, this);
|
|
makeSmallKnob(m_stereoBalanceKnob, tr("Stereo Balance:") , "");
|
|
m_stereoBalanceKnob->setModel(&controls->m_stereoBalanceModel);
|
|
m_stereoBalanceKnob->setToolTip(tr("Bias the sidechain signal to the left/right or mid/side"));
|
|
|
|
m_blendKnob = new Knob(KnobType::Styled, this);
|
|
makeSmallKnob(m_blendKnob, tr("Stereo Link Blend:") , "");
|
|
m_blendKnob->setModel(&controls->m_blendModel);
|
|
m_blendKnob->setToolTip(tr("Blend between unlinked/maximum/average/minimum stereo linking modes"));
|
|
|
|
m_tiltKnob = new Knob(KnobType::Styled, this);
|
|
makeSmallKnob(m_tiltKnob, tr("Tilt Gain:") , " dB");
|
|
m_tiltKnob->setModel(&controls->m_tiltModel);
|
|
m_tiltKnob->setToolTip(tr("Bias the sidechain signal to the low or high frequencies. -6 db is lowpass, 6 db is highpass."));
|
|
|
|
m_tiltFreqKnob = new Knob(KnobType::Styled, this);
|
|
makeSmallKnob(m_tiltFreqKnob, tr("Tilt Frequency:") , " Hz");
|
|
m_tiltFreqKnob->setModel(&controls->m_tiltFreqModel);
|
|
m_tiltFreqKnob->setToolTip(tr("Center frequency of sidechain tilt filter"));
|
|
|
|
m_mixKnob = new Knob(KnobType::Styled, this);
|
|
makeSmallKnob(m_mixKnob, tr("Mix:") , "%");
|
|
m_mixKnob->setModel(&controls->m_mixModel);
|
|
m_mixKnob->setToolTip(tr("Balance between wet and dry signals"));
|
|
|
|
m_autoAttackKnob = new Knob(KnobType::Styled, this);
|
|
makeSmallKnob(m_autoAttackKnob, tr("Auto Attack:") , "%");
|
|
m_autoAttackKnob->setModel(&controls->m_autoAttackModel);
|
|
m_autoAttackKnob->setToolTip(tr("Automatically control attack value depending on crest factor"));
|
|
|
|
m_autoReleaseKnob = new Knob(KnobType::Styled, this);
|
|
makeSmallKnob(m_autoReleaseKnob, tr("Auto Release:") , "%");
|
|
m_autoReleaseKnob->setModel(&controls->m_autoReleaseModel);
|
|
m_autoReleaseKnob->setToolTip(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");
|
|
m_outFader->setToolTip(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");
|
|
m_inFader->setToolTip(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"));
|
|
rmsButton->setToolTip(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"));
|
|
peakButton->setToolTip(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"));
|
|
leftRightButton->setToolTip(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"));
|
|
midSideButton->setToolTip(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"));
|
|
compressButton->setToolTip(tr("Compress the audio"));
|
|
|
|
limitButton = new PixmapButton(this, tr("Limiter"));
|
|
limitButton->setActiveGraphic(PLUGIN_NAME::getIconPixmap("limiter_sel"));
|
|
limitButton->setInactiveGraphic(PLUGIN_NAME::getIconPixmap("limiter_unsel"));
|
|
limitButton->setToolTip(tr("Set Ratio to infinity (is not guaranteed to limit audio volume)"));
|
|
|
|
compressLimitGroup = new AutomatableButtonGroup(this);
|
|
compressLimitGroup->addButton(compressButton);
|
|
compressLimitGroup->addButton(limitButton);
|
|
compressLimitGroup->setModel(&controls->m_limiterModel);
|
|
|
|
unlinkedButton = new PixmapButton(this, tr("Unlinked"));
|
|
unlinkedButton->setActiveGraphic(PLUGIN_NAME::getIconPixmap("unlinked_sel"));
|
|
unlinkedButton->setInactiveGraphic(PLUGIN_NAME::getIconPixmap("unlinked_unsel"));
|
|
unlinkedButton->setToolTip(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"));
|
|
maximumButton->setToolTip(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"));
|
|
averageButton->setToolTip(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"));
|
|
minimumButton->setToolTip(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"));
|
|
blendButton->setToolTip(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"));
|
|
autoMakeupButton->setToolTip(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"));
|
|
auditionButton->setToolTip(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"));
|
|
feedbackButton->setToolTip(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"));
|
|
lookaheadButton->setToolTip(tr("Enable Lookahead, which introduces 20 milliseconds of latency"));
|
|
lookaheadButton->setCheckable(true);
|
|
lookaheadButton->setModel(&controls->m_lookaheadModel);
|
|
|
|
connect(getGUI()->mainWindow(), SIGNAL(periodicUpdate()), this, SLOT(updateDisplay()));
|
|
|
|
connect(&m_controls->m_peakmodeModel, SIGNAL(dataChanged()), this, SLOT(peakmodeChanged()));
|
|
connect(&m_controls->m_stereoLinkModel, SIGNAL(dataChanged()), this, SLOT(stereoLinkChanged()));
|
|
connect(&m_controls->m_lookaheadModel, SIGNAL(dataChanged()), this, SLOT(lookaheadChanged()));
|
|
connect(&m_controls->m_limiterModel, SIGNAL(dataChanged()), this, SLOT(limiterChanged()));
|
|
|
|
m_timeElapsed.start();
|
|
|
|
peakmodeChanged();
|
|
stereoLinkChanged();
|
|
lookaheadChanged();
|
|
limiterChanged();
|
|
}
|
|
|
|
|
|
void CompressorControlDialog::makeLargeKnob(Knob * knob, QString hint, QString unit)
|
|
{
|
|
knob->setHintText(hint, unit);
|
|
knob->setFixedSize(56, 56);
|
|
knob->setOuterRadius(23);
|
|
knob->setInnerRadius(15);
|
|
knob->setCenterPointX(28);
|
|
knob->setCenterPointY(28);
|
|
}
|
|
|
|
void CompressorControlDialog::makeSmallKnob(Knob * knob, QString hint, QString unit)
|
|
{
|
|
knob->setHintText(hint, unit);
|
|
knob->setFixedSize(30, 30);
|
|
knob->setOuterRadius(10);
|
|
knob->setInnerRadius(4);
|
|
knob->setCenterPointX(15);
|
|
knob->setCenterPointY(15);
|
|
}
|
|
|
|
|
|
void CompressorControlDialog::peakmodeChanged()
|
|
{
|
|
m_rmsKnob->setVisible(!m_controls->m_peakmodeModel.value());
|
|
m_rmsEnabledLabel->setVisible(!m_controls->m_peakmodeModel.value());
|
|
}
|
|
|
|
|
|
void CompressorControlDialog::stereoLinkChanged()
|
|
{
|
|
m_blendKnob->setVisible(m_controls->m_stereoLinkModel.value() == 4);
|
|
m_blendEnabledLabel->setVisible(m_controls->m_stereoLinkModel.value() == 4);
|
|
}
|
|
|
|
|
|
void CompressorControlDialog::lookaheadChanged()
|
|
{
|
|
m_lookaheadLengthKnob->setVisible(m_controls->m_lookaheadModel.value());
|
|
m_lookaheadEnabledLabel->setVisible(m_controls->m_lookaheadModel.value());
|
|
feedbackButton->setVisible(!m_controls->m_lookaheadModel.value());
|
|
}
|
|
|
|
|
|
void CompressorControlDialog::limiterChanged()
|
|
{
|
|
m_ratioKnob->setVisible(!m_controls->m_limiterModel.value());
|
|
m_ratioEnabledLabel->setVisible(!m_controls->m_limiterModel.value());
|
|
}
|
|
|
|
|
|
void CompressorControlDialog::updateDisplay()
|
|
{
|
|
if (!isVisible())
|
|
{
|
|
m_timeElapsed.restart();
|
|
return;
|
|
}
|
|
|
|
int elapsedMil = m_timeElapsed.elapsed();
|
|
m_timeElapsed.restart();
|
|
m_timeSinceLastUpdate += elapsedMil;
|
|
m_compPixelMovement = int(m_timeSinceLastUpdate / COMP_MILLI_PER_PIXEL);
|
|
m_timeSinceLastUpdate %= COMP_MILLI_PER_PIXEL;
|
|
|
|
// Time Change / Daylight Savings Time protection
|
|
if (!m_compPixelMovement || m_compPixelMovement <= 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (!m_controls->m_effect->isEnabled() || !m_controls->m_effect->isRunning())
|
|
{
|
|
m_controls->m_effect->m_displayPeak[0] = COMP_NOISE_FLOOR;
|
|
m_controls->m_effect->m_displayPeak[1] = COMP_NOISE_FLOOR;
|
|
m_controls->m_effect->m_displayGain[0] = 1;
|
|
m_controls->m_effect->m_displayGain[1] = 1;
|
|
}
|
|
|
|
m_peakAvg = (m_controls->m_effect->m_displayPeak[0] + m_controls->m_effect->m_displayPeak[1]) * 0.5f;
|
|
m_gainAvg = (m_controls->m_effect->m_displayGain[0] + m_controls->m_effect->m_displayGain[1]) * 0.5f;
|
|
|
|
m_controls->m_effect->m_displayPeak[0] = m_controls->m_effect->m_yL[0];
|
|
m_controls->m_effect->m_displayPeak[1] = m_controls->m_effect->m_yL[1];
|
|
m_controls->m_effect->m_displayGain[0] = m_controls->m_effect->m_gainResult[0];
|
|
m_controls->m_effect->m_displayGain[1] = m_controls->m_effect->m_gainResult[1];
|
|
|
|
m_yPoint = dbfsToYPoint(ampToDbfs(m_peakAvg));
|
|
m_yGainPoint = dbfsToYPoint(ampToDbfs(m_gainAvg));
|
|
|
|
m_threshYPoint = dbfsToYPoint(m_controls->m_effect->m_thresholdVal);
|
|
m_threshXPoint = m_kneeWindowSizeY - m_threshYPoint;
|
|
|
|
drawVisPixmap();
|
|
|
|
if (m_controls->m_effect->m_redrawKnee)
|
|
{
|
|
redrawKnee();
|
|
}
|
|
|
|
drawKneePixmap2();
|
|
|
|
if (m_controls->m_effect->m_redrawThreshold)
|
|
{
|
|
drawMiscPixmap();
|
|
}
|
|
|
|
m_lastPoint = m_yPoint;
|
|
m_lastGainPoint = m_yGainPoint;
|
|
|
|
update();
|
|
}
|
|
|
|
|
|
void CompressorControlDialog::drawVisPixmap()
|
|
{
|
|
m_p.begin(&m_visPixmap);
|
|
|
|
// Move entire display to the left
|
|
m_p.setCompositionMode(QPainter::CompositionMode_Source);
|
|
m_p.drawPixmap(-m_compPixelMovement, 0, m_visPixmap);
|
|
m_p.fillRect(m_windowSizeX-m_compPixelMovement, 0, m_windowSizeX, m_windowSizeY, QColor("transparent"));
|
|
m_p.setCompositionMode(QPainter::CompositionMode_SourceOver);
|
|
|
|
m_p.setRenderHint(QPainter::Antialiasing, true);
|
|
|
|
// Draw translucent portion of input volume line
|
|
m_p.setPen(QPen(m_inVolAreaColor, 1));
|
|
for (int i = 0; i < m_compPixelMovement; ++i)
|
|
{
|
|
const int temp = std::lerp(
|
|
m_lastPoint,
|
|
m_yPoint,
|
|
static_cast<float>(i) / static_cast<float>(m_compPixelMovement)
|
|
);
|
|
m_p.drawLine(m_windowSizeX-m_compPixelMovement+i, temp, m_windowSizeX-m_compPixelMovement+i, m_windowSizeY);
|
|
}
|
|
|
|
// Draw input volume line
|
|
m_p.setPen(QPen(m_inVolColor, 1));
|
|
m_p.drawLine(m_windowSizeX-m_compPixelMovement-1, m_lastPoint, m_windowSizeX, m_yPoint);
|
|
|
|
// Draw translucent portion of output volume line
|
|
m_p.setPen(QPen(m_outVolAreaColor, 1));
|
|
for (int i = 0; i < m_compPixelMovement; ++i)
|
|
{
|
|
const int temp = std::lerp(
|
|
m_lastPoint + m_lastGainPoint,
|
|
m_yPoint + m_yGainPoint,
|
|
static_cast<float>(i) / static_cast<float>(m_compPixelMovement)
|
|
);
|
|
m_p.drawLine(m_windowSizeX-m_compPixelMovement+i, temp, m_windowSizeX-m_compPixelMovement+i, m_windowSizeY);
|
|
}
|
|
|
|
// Draw output volume line
|
|
m_p.setPen(QPen(m_outVolColor, 1));
|
|
m_p.drawLine(m_windowSizeX-m_compPixelMovement-1, m_lastPoint+m_lastGainPoint, m_windowSizeX, m_yPoint+m_yGainPoint);
|
|
|
|
// Draw gain reduction line
|
|
m_p.setPen(QPen(m_gainReductionColor, 2));
|
|
m_p.drawLine(m_windowSizeX-m_compPixelMovement-1, m_lastGainPoint, m_windowSizeX, m_yGainPoint);
|
|
|
|
m_p.end();
|
|
}
|
|
|
|
|
|
void CompressorControlDialog::redrawKnee()
|
|
{
|
|
m_controls->m_effect->m_redrawKnee = false;
|
|
|
|
// Start drawing knee visualizer
|
|
m_p.begin(&m_kneePixmap);
|
|
|
|
m_p.setRenderHint(QPainter::Antialiasing, false);
|
|
|
|
// Clear display
|
|
m_p.setCompositionMode(QPainter::CompositionMode_Source);
|
|
m_p.fillRect(0, 0, m_windowSizeX, m_kneeWindowSizeY, QColor("transparent"));
|
|
m_p.setCompositionMode(QPainter::CompositionMode_SourceOver);
|
|
|
|
m_p.setRenderHint(QPainter::Antialiasing, true);
|
|
|
|
m_p.setPen(QPen(m_kneeColor, 3));
|
|
|
|
// Limiter = infinite ratio
|
|
float actualRatio = m_controls->m_limiterModel.value() ? 0 : m_controls->m_effect->m_ratioVal;
|
|
|
|
// Calculate endpoints for the two straight lines
|
|
const float thresholdVal = m_controls->m_effect->m_thresholdVal;
|
|
const float kneeVal = m_controls->m_effect->m_kneeVal;
|
|
float kneePoint1 = thresholdVal - kneeVal;
|
|
float kneePoint2X = thresholdVal + kneeVal;
|
|
float kneePoint2Y = thresholdVal + kneeVal * actualRatio;
|
|
float ratioPoint = thresholdVal + (-thresholdVal * actualRatio);
|
|
|
|
// Draw two straight lines
|
|
m_p.drawLine(0, m_kneeWindowSizeY, dbfsToXPoint(kneePoint1), dbfsToYPoint(kneePoint1));
|
|
if (dbfsToXPoint(kneePoint2X) < m_kneeWindowSizeY)
|
|
{
|
|
m_p.drawLine(dbfsToXPoint(kneePoint2X), dbfsToYPoint(kneePoint2Y), m_kneeWindowSizeY, dbfsToYPoint(ratioPoint));
|
|
}
|
|
|
|
// Draw knee section
|
|
if (kneeVal)
|
|
{
|
|
m_p.setPen(QPen(m_kneeColor2, 3));
|
|
|
|
auto prevPoint = std::array{kneePoint1, kneePoint1};
|
|
auto newPoint = std::array{0.f, 0.f};
|
|
|
|
// Draw knee curve using many straight lines.
|
|
for (int i = 0; i < COMP_KNEE_LINES; ++i)
|
|
{
|
|
newPoint[0] = std::lerp(kneePoint1, kneePoint2X, (i + 1) / static_cast<float>(COMP_KNEE_LINES));
|
|
|
|
const float temp = newPoint[0] - thresholdVal + kneeVal;
|
|
newPoint[1] = (newPoint[0] + (actualRatio - 1) * temp * temp / (4 * kneeVal));
|
|
|
|
m_p.drawLine(dbfsToXPoint(prevPoint[0]), dbfsToYPoint(prevPoint[1]), dbfsToXPoint(newPoint[0]), dbfsToYPoint(newPoint[1]));
|
|
|
|
prevPoint[0] = newPoint[0];
|
|
prevPoint[1] = newPoint[1];
|
|
}
|
|
}
|
|
|
|
m_p.setRenderHint(QPainter::Antialiasing, false);
|
|
|
|
// Erase right portion
|
|
m_p.setCompositionMode(QPainter::CompositionMode_Source);
|
|
m_p.fillRect(m_kneeWindowSizeX + 1, 0, m_windowSizeX, m_kneeWindowSizeY, QColor("transparent"));
|
|
m_p.setCompositionMode(QPainter::CompositionMode_SourceOver);
|
|
|
|
m_p.end();
|
|
|
|
m_p.begin(&m_kneePixmap2);
|
|
|
|
m_p.setCompositionMode(QPainter::CompositionMode_Source);
|
|
m_p.fillRect(0, 0, m_windowSizeX, m_kneeWindowSizeY, QColor("transparent"));
|
|
m_p.setCompositionMode(QPainter::CompositionMode_SourceOver);
|
|
|
|
m_p.end();
|
|
|
|
m_lastKneePoint = 0;
|
|
}
|
|
|
|
|
|
void CompressorControlDialog::drawKneePixmap2()
|
|
{
|
|
m_p.begin(&m_kneePixmap2);
|
|
|
|
m_p.setRenderHint(QPainter::Antialiasing, false);
|
|
|
|
int kneePoint = dbfsToXPoint(ampToDbfs(m_peakAvg));
|
|
if (kneePoint > m_lastKneePoint)
|
|
{
|
|
QRectF knee2Rect = QRect(m_lastKneePoint, 0, kneePoint - m_lastKneePoint, m_kneeWindowSizeY);
|
|
m_p.drawPixmap(knee2Rect, m_kneePixmap, knee2Rect);
|
|
}
|
|
else
|
|
{
|
|
m_p.setCompositionMode(QPainter::CompositionMode_Source);
|
|
m_p.fillRect(kneePoint, 0, m_lastKneePoint, m_kneeWindowSizeY, QColor("transparent"));
|
|
m_p.setCompositionMode(QPainter::CompositionMode_SourceOver);
|
|
}
|
|
m_lastKneePoint = kneePoint;
|
|
|
|
m_p.end();
|
|
}
|
|
|
|
|
|
void CompressorControlDialog::drawMiscPixmap()
|
|
{
|
|
m_p.begin(&m_miscPixmap);
|
|
|
|
m_p.setCompositionMode(QPainter::CompositionMode_Source);
|
|
m_p.fillRect(0, 0, m_windowSizeX, m_windowSizeY, QColor("transparent"));
|
|
m_p.setCompositionMode(QPainter::CompositionMode_SourceOver);
|
|
|
|
m_p.setRenderHint(QPainter::Antialiasing, true);
|
|
|
|
// Draw threshold lines
|
|
m_p.setPen(QPen(m_threshColor, 2, Qt::DotLine));
|
|
m_p.drawLine(0, m_threshYPoint, m_windowSizeX, m_threshYPoint);
|
|
m_p.drawLine(m_threshXPoint, 0, m_threshXPoint, m_kneeWindowSizeY);
|
|
|
|
m_p.end();
|
|
|
|
m_controls->m_effect->m_redrawThreshold = false;
|
|
}
|
|
|
|
|
|
|
|
void CompressorControlDialog::paintEvent(QPaintEvent *event)
|
|
{
|
|
if (!isVisible())
|
|
{
|
|
return;
|
|
}
|
|
|
|
m_p.begin(this);
|
|
|
|
m_p.setCompositionMode(QPainter::CompositionMode_Source);
|
|
m_p.fillRect(0, 0, m_windowSizeX, m_windowSizeY, m_backgroundColor);
|
|
m_p.setCompositionMode(QPainter::CompositionMode_SourceOver);
|
|
|
|
m_p.drawPixmap(0, 0, m_graphPixmap);
|
|
m_p.drawPixmap(0, 0, m_visPixmap);
|
|
m_p.setOpacity(0.25);
|
|
m_p.drawPixmap(0, 0, m_kneePixmap);
|
|
m_p.setOpacity(1);
|
|
if (m_controls->m_effect->isEnabled() && m_controls->m_effect->isRunning())
|
|
{
|
|
m_p.drawPixmap(0, 0, m_kneePixmap2);
|
|
}
|
|
m_p.drawPixmap(0, 0, m_miscPixmap);
|
|
|
|
m_p.end();
|
|
}
|
|
|
|
|
|
inline int CompressorControlDialog::dbfsToYPoint(float inDbfs)
|
|
{
|
|
return (-((inDbfs + m_dbRange) / m_dbRange) + 1) * m_windowSizeY;
|
|
}
|
|
|
|
inline int CompressorControlDialog::dbfsToXPoint(float inDbfs)
|
|
{
|
|
return m_kneeWindowSizeY - dbfsToYPoint(inDbfs);
|
|
}
|
|
|
|
|
|
void CompressorControlDialog::resizeEvent(QResizeEvent *event)
|
|
{
|
|
resetCompressorView();
|
|
}
|
|
|
|
|
|
void CompressorControlDialog::wheelEvent(QWheelEvent * event)
|
|
{
|
|
const float temp = m_dbRange;
|
|
const float dbRangeNew = m_dbRange - copysignf(COMP_GRID_SPACING, event->angleDelta().y());
|
|
m_dbRange = round(qBound(COMP_GRID_SPACING, dbRangeNew, COMP_GRID_MAX) / COMP_GRID_SPACING) * COMP_GRID_SPACING;
|
|
|
|
// Only reset view if the scolling had an effect
|
|
if (m_dbRange != temp)
|
|
{
|
|
drawGraph();
|
|
m_controls->m_effect->m_redrawKnee = true;
|
|
m_controls->m_effect->m_redrawThreshold = true;
|
|
}
|
|
}
|
|
|
|
|
|
void CompressorControlDialog::drawGraph()
|
|
{
|
|
m_p.begin(&m_graphPixmap);
|
|
|
|
m_p.setRenderHint(QPainter::Antialiasing, false);
|
|
|
|
m_p.setCompositionMode(QPainter::CompositionMode_Source);
|
|
m_p.fillRect(0, 0, m_windowSizeX, m_windowSizeY, QColor("transparent"));
|
|
m_p.setCompositionMode(QPainter::CompositionMode_SourceOver);
|
|
|
|
m_p.setPen(QPen(m_textColor, 1));
|
|
|
|
// Arbitrary formula for increasing font size when window size increases
|
|
m_p.setFont(QFont("Arial", qMax(int(m_windowSizeY / 1080.f * 24), 12)));
|
|
|
|
// Redraw graph
|
|
m_p.setPen(QPen(m_graphColor, 1));
|
|
for (int i = 0; i < m_dbRange / COMP_GRID_SPACING + 1; ++i)
|
|
{
|
|
m_p.drawLine(0, dbfsToYPoint(-COMP_GRID_SPACING * i), m_windowSizeX, dbfsToYPoint(-COMP_GRID_SPACING * i));
|
|
m_p.drawLine(dbfsToXPoint(-COMP_GRID_SPACING * i), 0, dbfsToXPoint(-COMP_GRID_SPACING * i), m_kneeWindowSizeY);
|
|
m_p.drawText(QRectF(m_windowSizeX - 50, dbfsToYPoint(-COMP_GRID_SPACING * i), 50, 50), Qt::AlignRight | Qt::AlignTop, QString::number(i * -COMP_GRID_SPACING));
|
|
}
|
|
|
|
m_p.end();
|
|
}
|
|
|
|
|
|
void CompressorControlDialog::mouseDoubleClickEvent(QMouseEvent* event)
|
|
{
|
|
setGuiVisibility(!m_guiVisibility);
|
|
}
|
|
|
|
|
|
void CompressorControlDialog::setGuiVisibility(bool isVisible)
|
|
{
|
|
if (!isVisible)
|
|
{
|
|
m_rmsKnob->setVisible(isVisible);
|
|
m_rmsEnabledLabel->setVisible(isVisible);
|
|
|
|
m_lookaheadLengthKnob->setVisible(isVisible);
|
|
m_lookaheadEnabledLabel->setVisible(isVisible);
|
|
|
|
m_blendKnob->setVisible(isVisible);
|
|
m_blendEnabledLabel->setVisible(isVisible);
|
|
|
|
m_ratioKnob->setVisible(isVisible);
|
|
m_ratioEnabledLabel->setVisible(isVisible);
|
|
}
|
|
else
|
|
{
|
|
m_rmsKnob->setVisible(!m_controls->m_peakmodeModel.value());
|
|
m_rmsEnabledLabel->setVisible(!m_controls->m_peakmodeModel.value());
|
|
|
|
m_blendKnob->setVisible(m_controls->m_stereoLinkModel.value() == 4);
|
|
m_blendEnabledLabel->setVisible(m_controls->m_stereoLinkModel.value() == 4);
|
|
|
|
m_lookaheadLengthKnob->setVisible(m_controls->m_lookaheadModel.value());
|
|
m_lookaheadEnabledLabel->setVisible(m_controls->m_lookaheadModel.value());
|
|
|
|
m_ratioKnob->setVisible(!m_controls->m_limiterModel.value());
|
|
m_ratioEnabledLabel->setVisible(!m_controls->m_limiterModel.value());
|
|
}
|
|
m_controlsBoxLabel->setVisible(isVisible);
|
|
m_thresholdKnob->setVisible(isVisible);
|
|
m_attackKnob->setVisible(isVisible);
|
|
m_releaseKnob->setVisible(isVisible);
|
|
m_kneeKnob->setVisible(isVisible);
|
|
m_rangeKnob->setVisible(isVisible);
|
|
m_holdKnob->setVisible(isVisible);
|
|
m_inBalanceKnob->setVisible(isVisible);
|
|
m_outBalanceKnob->setVisible(isVisible);
|
|
m_stereoBalanceKnob->setVisible(isVisible);
|
|
m_tiltKnob->setVisible(isVisible);
|
|
m_tiltFreqKnob->setVisible(isVisible);
|
|
m_mixKnob->setVisible(isVisible);
|
|
m_autoAttackKnob->setVisible(isVisible);
|
|
m_autoReleaseKnob->setVisible(isVisible);
|
|
m_outFader->setVisible(isVisible);
|
|
m_inFader->setVisible(isVisible);
|
|
rmsButton->setVisible(isVisible);
|
|
peakButton->setVisible(isVisible);
|
|
rmsPeakGroup->setVisible(isVisible);
|
|
leftRightButton->setVisible(isVisible);
|
|
midSideButton->setVisible(isVisible);
|
|
compressButton->setVisible(isVisible);
|
|
limitButton->setVisible(isVisible);
|
|
unlinkedButton->setVisible(isVisible);
|
|
maximumButton->setVisible(isVisible);
|
|
averageButton->setVisible(isVisible);
|
|
minimumButton->setVisible(isVisible);
|
|
blendButton->setVisible(isVisible);
|
|
autoMakeupButton->setVisible(isVisible);
|
|
auditionButton->setVisible(isVisible);
|
|
feedbackButton->setVisible(isVisible);
|
|
lookaheadButton->setVisible(isVisible);
|
|
m_guiVisibility = isVisible;
|
|
}
|
|
|
|
|
|
void CompressorControlDialog::resetCompressorView()
|
|
{
|
|
m_windowSizeX = size().width();
|
|
m_windowSizeY = size().height();
|
|
m_kneeWindowSizeX = m_windowSizeY;
|
|
m_kneeWindowSizeY = m_windowSizeY;
|
|
m_controlsBoxX = (m_windowSizeX - COMP_BOX_X) * 0.5;
|
|
m_controlsBoxY = m_windowSizeY - 40 - COMP_BOX_Y;
|
|
|
|
m_controls->m_effect->m_redrawKnee = true;
|
|
m_controls->m_effect->m_redrawThreshold = true;
|
|
m_lastKneePoint = 0;
|
|
|
|
drawGraph();
|
|
|
|
m_p.begin(&m_visPixmap);
|
|
|
|
m_p.setCompositionMode(QPainter::CompositionMode_Source);
|
|
m_p.fillRect(0, 0, m_windowSizeX, m_windowSizeY, QColor("transparent"));
|
|
m_p.setCompositionMode(QPainter::CompositionMode_SourceOver);
|
|
|
|
// Draw line at right side, so the sudden
|
|
// content that the visualizer will display
|
|
// later on won't look too ugly
|
|
m_p.setPen(QPen(m_resetColor, 3));
|
|
m_p.drawLine(m_windowSizeX, 0, m_windowSizeX, m_windowSizeY);
|
|
|
|
m_p.end();
|
|
|
|
m_controlsBoxLabel->move(m_controlsBoxX, m_controlsBoxY);
|
|
m_rmsEnabledLabel->move(m_controlsBoxX + 429, m_controlsBoxY + 209);
|
|
m_blendEnabledLabel->move(m_controlsBoxX + 587, m_controlsBoxY + 197);
|
|
m_lookaheadEnabledLabel->move(m_controlsBoxX + 221, m_controlsBoxY + 135);
|
|
m_ratioEnabledLabel->move(m_controlsBoxX + 267, m_controlsBoxY + 21);
|
|
|
|
m_thresholdKnob->move(m_controlsBoxX + 137, m_controlsBoxY + 21);
|
|
m_ratioKnob->move(m_controlsBoxX + 267, m_controlsBoxY + 21);
|
|
m_attackKnob->move(m_controlsBoxX + 397, m_controlsBoxY + 21);
|
|
m_releaseKnob->move(m_controlsBoxX + 527, m_controlsBoxY + 21);
|
|
m_kneeKnob->move(m_controlsBoxX + 97, m_controlsBoxY + 135);
|
|
m_rangeKnob->move(m_controlsBoxX + 159, m_controlsBoxY + 135);
|
|
m_lookaheadLengthKnob->move(m_controlsBoxX + 221, m_controlsBoxY + 135);
|
|
m_holdKnob->move(m_controlsBoxX + 283, m_controlsBoxY + 135);
|
|
|
|
m_rmsKnob->move(m_controlsBoxX + 429, m_controlsBoxY + 209);
|
|
m_inBalanceKnob->move(m_controlsBoxX + 27, m_controlsBoxY + 219);
|
|
m_outBalanceKnob->move(m_controlsBoxX + 662, m_controlsBoxY + 219);
|
|
m_stereoBalanceKnob->move(m_controlsBoxX + 522, m_controlsBoxY + 137);
|
|
m_blendKnob->move(m_controlsBoxX + 587, m_controlsBoxY + 197);
|
|
m_tiltKnob->move(m_controlsBoxX + 364, m_controlsBoxY + 138);
|
|
m_tiltFreqKnob->move(m_controlsBoxX + 415, m_controlsBoxY + 138);
|
|
m_mixKnob->move(m_controlsBoxX + 27, m_controlsBoxY + 13);
|
|
|
|
m_outFader->move(m_controlsBoxX + 666, m_controlsBoxY + 91);
|
|
m_inFader->move(m_controlsBoxX + 31, m_controlsBoxY + 91);
|
|
|
|
rmsButton->move(m_controlsBoxX + 337, m_controlsBoxY + 231);
|
|
peakButton->move(m_controlsBoxX + 337, m_controlsBoxY + 248);
|
|
|
|
leftRightButton->move(m_controlsBoxX + 220, m_controlsBoxY + 231);
|
|
midSideButton->move(m_controlsBoxX + 220, m_controlsBoxY + 248);
|
|
|
|
compressButton->move(m_controlsBoxX + 98, m_controlsBoxY + 231);
|
|
limitButton->move(m_controlsBoxX + 98, m_controlsBoxY + 248);
|
|
|
|
unlinkedButton->move(m_controlsBoxX + 495, m_controlsBoxY + 180);
|
|
maximumButton->move(m_controlsBoxX + 495, m_controlsBoxY + 197);
|
|
averageButton->move(m_controlsBoxX + 495, m_controlsBoxY + 214);
|
|
minimumButton->move(m_controlsBoxX + 495, m_controlsBoxY + 231);
|
|
blendButton->move(m_controlsBoxX + 495, m_controlsBoxY + 248);
|
|
|
|
autoMakeupButton->move(m_controlsBoxX + 220, m_controlsBoxY + 206);
|
|
auditionButton->move(m_controlsBoxX + 658, m_controlsBoxY + 14);
|
|
feedbackButton->move(m_controlsBoxX + 98, m_controlsBoxY + 206);
|
|
m_autoAttackKnob->move(m_controlsBoxX + 460, m_controlsBoxY + 38);
|
|
m_autoReleaseKnob->move(m_controlsBoxX + 590, m_controlsBoxY + 38);
|
|
lookaheadButton->move(m_controlsBoxX + 202, m_controlsBoxY + 171);
|
|
}
|
|
|
|
|
|
} // namespace lmms::gui
|