From def44aee63c0247b39c16b2b4e7493621ff005b0 Mon Sep 17 00:00:00 2001 From: Michael Gregorius Date: Tue, 24 Dec 2024 13:46:02 +0100 Subject: [PATCH] Support for dB models Add support for models that are in dB. There's a new member `m_modelIsLinear` which can be set via the constructor. The default value in the constructor is `true`. Add the method `modelIsLinear` which can be used to query the parameter. It is used in `calculateKnobPosYFromModel` and `setVolumeByLocalPixelValue`. Both methods got extended to deal with models in dB. They were also refactored to extract code that is common for both cases. Ensure that the constructor of `Fader` is called with `false` for `CrossoverEQControlDialog` and `EqFader` because these use models that are in dB. --- include/Fader.h | 7 +- .../CrossoverEQ/CrossoverEQControlDialog.cpp | 8 +- plugins/Eq/EqFader.h | 2 +- src/gui/widgets/Fader.cpp | 131 +++++++++++------- 4 files changed, 91 insertions(+), 57 deletions(-) diff --git a/include/Fader.h b/include/Fader.h index 3612a44df..1fa8f4882 100644 --- a/include/Fader.h +++ b/include/Fader.h @@ -74,8 +74,8 @@ public: Q_PROPERTY(bool renderUnityLine READ getRenderUnityLine WRITE setRenderUnityLine) Q_PROPERTY(QColor unityMarker MEMBER m_unityMarker) - Fader(FloatModel* model, const QString& name, QWidget* parent); - Fader(FloatModel* model, const QString& name, QWidget* parent, const QPixmap& knob); + Fader(FloatModel* model, const QString& name, QWidget* parent, bool modelIsLinear = true); + Fader(FloatModel* model, const QString& name, QWidget* parent, const QPixmap& knob, bool modelIsLinear = true); ~Fader() override = default; void setPeak_L(float fPeak); @@ -126,6 +126,8 @@ private: void updateTextFloat(); + bool modelIsLinear() const { return m_modelIsLinear; } + // Private members private: float m_fPeakValue_L {0.}; @@ -141,6 +143,7 @@ private: QPixmap m_knob {embed::getIconPixmap("fader_knob")}; bool m_levelsDisplayedInDBFS {true}; + bool m_modelIsLinear {false}; // The dbFS amount after which we drop down to -inf dbFS float const m_faderMinDb {-120.}; diff --git a/plugins/CrossoverEQ/CrossoverEQControlDialog.cpp b/plugins/CrossoverEQ/CrossoverEQControlDialog.cpp index a4f44f5d3..e7202556b 100644 --- a/plugins/CrossoverEQ/CrossoverEQControlDialog.cpp +++ b/plugins/CrossoverEQ/CrossoverEQControlDialog.cpp @@ -70,25 +70,25 @@ CrossoverEQControlDialog::CrossoverEQControlDialog( CrossoverEQControls * contro QPixmap const fader_knob(PLUGIN_NAME::getIconPixmap("fader_knob2")); // faders - auto gain1 = new Fader(&controls->m_gain1, tr("Band 1 gain"), this, fader_knob); + auto gain1 = new Fader(&controls->m_gain1, tr("Band 1 gain"), this, fader_knob, false); gain1->move( 7, 56 ); gain1->setDisplayConversion( false ); gain1->setHintText( tr( "Band 1 gain:" ), " dBFS" ); gain1->setRenderUnityLine(false); - auto gain2 = new Fader(&controls->m_gain2, tr("Band 2 gain"), this, fader_knob); + auto gain2 = new Fader(&controls->m_gain2, tr("Band 2 gain"), this, fader_knob, false); gain2->move( 47, 56 ); gain2->setDisplayConversion( false ); gain2->setHintText( tr( "Band 2 gain:" ), " dBFS" ); gain2->setRenderUnityLine(false); - auto gain3 = new Fader(&controls->m_gain3, tr("Band 3 gain"), this, fader_knob); + auto gain3 = new Fader(&controls->m_gain3, tr("Band 3 gain"), this, fader_knob, false); gain3->move( 87, 56 ); gain3->setDisplayConversion( false ); gain3->setHintText( tr( "Band 3 gain:" ), " dBFS" ); gain3->setRenderUnityLine(false); - auto gain4 = new Fader(&controls->m_gain4, tr("Band 4 gain"), this, fader_knob); + auto gain4 = new Fader(&controls->m_gain4, tr("Band 4 gain"), this, fader_knob, false); gain4->move( 127, 56 ); gain4->setDisplayConversion( false ); gain4->setHintText( tr( "Band 4 gain:" ), " dBFS" ); diff --git a/plugins/Eq/EqFader.h b/plugins/Eq/EqFader.h index 3185d0879..5c9aa5e5d 100644 --- a/plugins/Eq/EqFader.h +++ b/plugins/Eq/EqFader.h @@ -43,7 +43,7 @@ public: Q_OBJECT public: EqFader( FloatModel * model, const QString & name, QWidget * parent, float* lPeak, float* rPeak ) : - Fader( model, name, parent ) + Fader(model, name, parent, false) { setMinimumSize( 23, 116 ); setMaximumSize( 23, 116 ); diff --git a/src/gui/widgets/Fader.cpp b/src/gui/widgets/Fader.cpp index f21465e50..398b4a31b 100644 --- a/src/gui/widgets/Fader.cpp +++ b/src/gui/widgets/Fader.cpp @@ -62,9 +62,10 @@ namespace lmms::gui SimpleTextFloat* Fader::s_textFloat = nullptr; -Fader::Fader(FloatModel* model, const QString& name, QWidget* parent) : +Fader::Fader(FloatModel* model, const QString& name, QWidget* parent, bool modelIsLinear) : QWidget(parent), - FloatModelView(model, this) + FloatModelView(model, this), + m_modelIsLinear(modelIsLinear) { if (s_textFloat == nullptr) { @@ -85,8 +86,8 @@ Fader::Fader(FloatModel* model, const QString& name, QWidget* parent) : } -Fader::Fader(FloatModel* model, const QString& name, QWidget* parent, const QPixmap& knob) : - Fader(model, name, parent) +Fader::Fader(FloatModel* model, const QString& name, QWidget* parent, const QPixmap& knob, bool modelIsLinear) : + Fader(model, name, parent, modelIsLinear) { m_knob = knob; } @@ -191,37 +192,55 @@ void Fader::wheelEvent (QWheelEvent* ev) int Fader::calculateKnobPosYFromModel() const { - // This method calculates the pixel position where the lower end of - // the fader knob should be for the amplification value in the model. - // - // The following assumes that the model describes an amplification, - // i.e. that values are in [0, max] and that 1 is unity, i.e. 0 dbFS. + auto* m = model(); - auto const minV = model()->minValue(); - auto const maxV = model()->maxValue(); - auto const value = model()->value(); + auto const minV = m->minValue(); + auto const maxV = m->maxValue(); + auto const value = m->value(); - auto const distanceToMin = value - minV; - - // Prevent dbFS calculations with zero or negative values - if (distanceToMin <= 0) + if (modelIsLinear()) { - return height(); + // This method calculates the pixel position where the lower end of + // the fader knob should be for the amplification value in the model. + // + // The following assumes that the model describes an amplification, + // i.e. that values are in [0, max] and that 1 is unity, i.e. 0 dbFS. + + auto const distanceToMin = value - minV; + + // Prevent dbFS calculations with zero or negative values + if (distanceToMin <= 0) + { + return height(); + } + else + { + auto const maxDb = ampToDbfs(maxV); + + // Make sure that we do not get values less that the minimum fader dbFS + // for the calculations that will follow. + auto const actualDb = std::max(m_faderMinDb, ampToDbfs(value)); + + auto const ratio = (actualDb - m_faderMinDb) / (maxDb - m_faderMinDb); + + // This returns results between: + // * m_knob.height() for a ratio of 1 + // * height() for a ratio of 0 + return height() - (height() - m_knob.height()) * std::pow(ratio, 3.); + } } else { - auto const maxDb = ampToDbfs(maxV); + // The model is in dB so we just show that in a linear fashion + + auto const clampedValue = std::clamp(value, minV, maxV); - // Make sure that we do not get values less that the minimum fader dbFS - // for the calculations that will follow. - auto const actualDb = std::max(m_faderMinDb, ampToDbfs(value)); - - auto const ratio = (actualDb - m_faderMinDb) / (maxDb - m_faderMinDb); + auto const ratio = (clampedValue - minV) / (maxV - minV); // This returns results between: // * m_knob.height() for a ratio of 1 // * height() for a ratio of 0 - return height() - (height() - m_knob.height()) * std::pow(ratio, 3.); + return height() - (height() - m_knob.height()) * ratio; } } @@ -234,37 +253,49 @@ void Fader::setVolumeByLocalPixelValue(int y) // Assume that the middle of the fader should go there. int const lowerFaderKnob = y + (m_knob.height() / 2); - if (lowerFaderKnob >= height()) + // In some cases we need the clamped lower position of the fader knob so we can ensure + // that we only set allowed values in the model range. + int const clampedLowerFaderKnob = std::clamp(lowerFaderKnob, m_knob.height(), height()); + + if (modelIsLinear()) { - // We have to check this before clamping because otherwise we wouldn't be able to set -inf dB! - model()->setValue(0); + if (lowerFaderKnob >= height()) + { + // Check the non-clamped value because otherwise we wouldn't be able to set -inf dB! + model()->setValue(0); + } + else + { + // We are in the case where we set a value that's different from -inf dB so we use the clamped value + // of the lower knob position so that we only set allowed values in the model range. + + // First map the lower knob position to [0, 1] so that we can apply some curve mapping, e.g. + // square, cube, etc. + LinearMap knobMap(float(m_knob.height()), 1., float(height()), 0.); + + // Apply the inverse of what is done in calculateKnobPosYFromModel + auto const knobPos = std::pow(knobMap.map(clampedLowerFaderKnob), 1./3.); + + float const maxDb = ampToDbfs(m->maxValue()); + + LinearMap dbMap(1., maxDb, 0., m_faderMinDb); + + float const dbValue = dbMap.map(knobPos); + + // Pull everything that's quieter than the minimum fader dbFS value down to 0 amplification. + // This should not happen due to the steps above but let's be sure. + // Otherwise compute the amplification value from the mapped dbFS value but make sure that we + // do not exceed the maximum dbValue of the model + float ampValue = dbValue < m_faderMinDb ? 0. : dbfsToAmp(std::min(maxDb, dbValue)); + + model()->setValue(ampValue); + } } else { - // We are in the case where we set a value different from -inf dB so we first clamp the - // lower position of the fader knob so that we only set allowed values in the model range. - int const clampedLowerFaderKnob = std::clamp(lowerFaderKnob, m_knob.height(), height()); + LinearMap valueMap(float(m_knob.height()), model()->maxValue(), float(height()), model()->minValue()); - // First map the lower knob position to [0, 1] so that we can apply some curved mapping, e.g. - // square, cube, etc. - LinearMap knobMap(float(m_knob.height()), 1., float(height()), 0.); - - // Apply the inverse of what is done in calculateKnobPosYFromModel - auto const knobPos = std::pow(knobMap.map(clampedLowerFaderKnob), 1./3.); - - float const maxDb = ampToDbfs(m->maxValue()); - - LinearMap dbMap(1., maxDb, 0., m_faderMinDb); - - float const dbValue = dbMap.map(knobPos); - - // Pull everything that's quieter than the minimum fader dbFS value down to 0 amplification. - // This should not happen due to the steps above but let's be sure. - // Otherwise compute the amplification value from the mapped dbFS value but make sure that we - // do not exceed the maximum dbValue of the model - float ampValue = dbValue < m_faderMinDb ? 0. : dbfsToAmp(std::min(maxDb, dbValue)); - - model()->setValue(ampValue); + model()->setValue(valueMap.map(clampedLowerFaderKnob)); } }