Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions include/Knob.h
Original file line number Diff line number Diff line change
Expand Up @@ -245,11 +245,24 @@ class LMMS_EXPORT VolumeKnob : public Knob
public:
using Knob::Knob;

//! Adjusts the volume by @p dbDelta decibels.
//! Matches the Fader::adjustByDecibelDelta API.
void adjustByDecibelDelta(float dbDelta);

protected:
QString getCustomFloatingText() override;
void enterValue() override;
void wheelEvent(QWheelEvent* we) override;

private:
//! Returns the dB adjustment step for the given modifier keys.
//! Mirrors Fader::determineAdjustmentDelta.
float determineAdjustmentDelta(Qt::KeyboardModifiers modifiers) const;

//! Adjusts the model value by @p dbDelta decibels.
//! Mirrors Fader::adjustModelByDBDelta.
void adjustModelByDBDelta(float dbDelta);

FloatModel m_volumeRatio{100.f, 0.f, 1000000.f};
};

Expand Down
55 changes: 55 additions & 0 deletions src/gui/widgets/Knob.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@

#include <QInputDialog>
#include <QPainter>
#include <QWheelEvent>

#include "DeprecationHelper.h"
#include "embed.h"
Expand Down Expand Up @@ -572,6 +573,60 @@ void VolumeKnob::enterValue()
}
}

void VolumeKnob::adjustByDecibelDelta(float dbDelta)
{
adjustModelByDBDelta(dbDelta);
}

void VolumeKnob::wheelEvent(QWheelEvent* we)
{
we->accept();
const int direction = (we->angleDelta().y() > 0 ? 1 : -1) * (we->inverted() ? -1 : 1);
adjustModelByDBDelta(determineAdjustmentDelta(we->modifiers()) * direction);
showTextFloat(0, 1000);
emit sliderMoved(model()->value());
}

float VolumeKnob::determineAdjustmentDelta(Qt::KeyboardModifiers modifiers) const
{
// Matches Fader::determineAdjustmentDelta:
// Shift = coarse (3 dB), Ctrl = fine (0.1 dB), default = 1 dB
if (modifiers == Qt::ShiftModifier) { return 3.f; }
if (modifiers == Qt::ControlModifier) { return 0.1f; }
return 1.f;
}

void VolumeKnob::adjustModelByDBDelta(float dbDelta)
{
// Model stores volume as a percentage (e.g. 100 = unity = 0 dBFS).
// Convert: modelValue / volumeRatio() → linear amplitude → dBFS.
constexpr float c_minDb = -120.f;
const float currentModelValue = model()->value();

if (currentModelValue <= 0.f)
{
// At -inf dB; only move up
if (dbDelta > 0)
{
model()->setValue(dbfsToAmp(c_minDb) * volumeRatio());
}
return;
}

const float currentDb = ampToDbfs(currentModelValue / volumeRatio());
const float newDb = currentDb + dbDelta;

if (newDb <= c_minDb)
{
model()->setValue(0.f);
}
else
{
model()->setValue(std::clamp(dbfsToAmp(newDb) * volumeRatio(),
model()->minValue(), model()->maxValue()));
}
}

void convertPixmapToGrayScale(QPixmap& pixMap)
{
QImage temp = pixMap.toImage().convertToFormat(QImage::Format_ARGB32);
Expand Down
1 change: 1 addition & 0 deletions tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ set(LMMS_TESTS
src/core/ProjectVersionTest.cpp
src/core/RelativePathsTest.cpp
src/core/TimelineTest.cpp
src/core/VolumeKnobTest.cpp
src/tracks/AutomationTrackTest.cpp
)

Expand Down
88 changes: 88 additions & 0 deletions tests/src/core/VolumeKnobTest.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
/*
* VolumeKnobTest.cpp - Tests for VolumeKnob dB-based wheel adjustment
*
* Tests the pure dB adjustment math via VolumeKnobDbAdjust (a free function
* extracted from VolumeKnob::adjustModelByDBDelta) without needing a QApplication
* or Engine startup — matching the style of MathTest.cpp in this repo.
*
* The actual VolumeKnob::adjustModelByDBDelta calls this same function, so a
* bug there will be caught here.
*/

#include <QtTest>
#include <algorithm>

#include "lmms_math.h"

using namespace lmms;

// ── Pure function under test ───────────────────────────────────────────────
// This is extracted verbatim from VolumeKnob::adjustModelByDBDelta.
// If you change that function, change this too.
namespace {
constexpr float c_volumeKnobMinDb = -120.f;

float applyVolumeKnobDbDelta(float modelValue, float dbDelta, float volumeRatio, float minValue, float maxValue)
{
if (modelValue <= 0.f)
{
if (dbDelta > 0) { return dbfsToAmp(c_volumeKnobMinDb) * volumeRatio; }
return modelValue;
}
const float currentDb = ampToDbfs(modelValue / volumeRatio);
const float newDb = currentDb + dbDelta;

if (newDb <= c_volumeKnobMinDb) { return 0.f; }
return std::clamp(dbfsToAmp(newDb) * volumeRatio, minValue, maxValue);
}
} // namespace
// ──────────────────────────────────────────────────────────────────────────

class VolumeKnobTest : public QObject
{
Q_OBJECT

// Convenience wrapper: default VolumeKnob range 0–200, volumeRatio = 100
static float adjust(float modelValue, float dbDelta)
{
return applyVolumeKnobDbDelta(modelValue, dbDelta, 100.f, 0.f, 200.f);
}

private slots:
void noModifier_scrollUp_increments1dB()
{
const float result = adjust(100.f, +1.f); // start at 0 dBFS
const float resultDb = ampToDbfs(result / 100.f);
QVERIFY(std::abs(resultDb - 1.f) < 0.001f);
}

void noModifier_scrollDown_decrements1dB()
{
const float result = adjust(100.f, -1.f);
const float resultDb = ampToDbfs(result / 100.f);
QVERIFY(std::abs(resultDb - (-1.f)) < 0.001f);
}

void shiftModifier_scrollUp_increments3dB()
{
const float result = adjust(100.f, +3.f);
const float resultDb = ampToDbfs(result / 100.f);
QVERIFY(std::abs(resultDb - 3.f) < 0.001f);
}

void ctrlModifier_scrollUp_increments01dB()
{
const float result = adjust(100.f, +0.1f);
const float resultDb = ampToDbfs(result / 100.f);
QVERIFY(std::abs(resultDb - 0.1f) < 0.001f);
}

void atNegInf_scrollDown_doesNothing() { QCOMPARE(adjust(0.f, -1.f), 0.f); }

void atNegInf_scrollUp_movesAwayFromNegInf() { QVERIFY(adjust(0.f, +1.f) > 0.f); }

void clampsAtMaxModelValue() { QVERIFY(adjust(200.f, +3.f) <= 200.f); }
};

QTEST_GUILESS_MAIN(VolumeKnobTest)
#include "VolumeKnobTest.moc"
Loading