Skip to content

Commit

Permalink
Scalable envelope graph (#7194)
Browse files Browse the repository at this point in the history
Make the graph scalable by adjusting the painting code of the envelope so that it does not assume fixed widths and heights anymore. Remove the setting of a fixed size from the envelope graph and only set a minimum size.

Make three scaling modes available which can be selected via a context menu in the graph:
* "Dynamic": This modes corresponds to the rendering strategy of the previous implementation. Initially 80/182 of the available width is used as the maximum width per segment. This can be interpreted like a "zoomed" version of the absolute mode. If the needed space becomes larger than the full width though then it falls back to relative rendering.
* "Absolute": Each of the five segments is assigned 1/5 of the available width. The envelopes will always fit but might appear small depending of the current settings. This is a good mode to compare envelopes though.
* "Relative": If there is at least one non-zero segment then the whole width is always used to present the envelope.

The default scaling mode is "Dynamic".

## Technical details

The new painting code is more or less divided into two parts. The first part calculates `QPointF` instances for the different points. In the second part these points are then used to draw the lines and markers. This makes the actual rendering code much more straight forward, readable and maintainable.

The interpolation between the line color of an inactive and an active envelope has also been restructured so that it is much more obvious that we are doing an interpolation in the first place. The colors at both ends of the interpolation are explicit now and can therefore be adjusted much easier. The actual color interpolation is done in the helper function `interpolateInRgb` which is provided by the new class `ColorHelper`. This class will later also be needed when the LFO graph is made scalable.

The line is rendered as a polyline instead of single line segments.

The drawing of the markers has been abstracted into a lambda (with some outside captures though) so that it can be easily adjusted if necessary. The markers are rendered as circles instead of rectangles because that looks much nicer especially when the widget is rendered at a larger size.

The width of the lines and marker outlines is determined using the size of the widget so that it scales with the size.

A `lerp` function has been added to `lmms_math.h`.
  • Loading branch information
michaelgregorius authored Apr 11, 2024
1 parent 1f5f28f commit 8e40038
Show file tree
Hide file tree
Showing 4 changed files with 251 additions and 75 deletions.
54 changes: 54 additions & 0 deletions include/ColorHelper.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
/* ColorHelper.h - Helper methods for color related algorithms, etc.
*
* Copyright (c) 2024- Michael Gregorius
*
* 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 LMMS_GUI_COLOR_HELPER_H
#define LMMS_GUI_COLOR_HELPER_H

#include <QColor>

namespace lmms::gui
{

class ColorHelper
{
public:
static QColor interpolateInRgb(const QColor& a, const QColor& b, float t)
{
qreal ar, ag, ab, aa;
a.getRgbF(&ar, &ag, &ab, &aa);

qreal br, bg, bb, ba;
b.getRgbF(&br, &bg, &bb, &ba);

const float interH = lerp(ar, br, t);
const float interS = lerp(ag, bg, t);
const float interV = lerp(ab, bb, t);
const float interA = lerp(aa, ba, t);

return QColor::fromRgbF(interH, interS, interV, interA);
}
};

} // namespace lmms::gui

#endif // LMMS_GUI_COLOR_HELPER_H
17 changes: 14 additions & 3 deletions include/EnvelopeGraph.h
Original file line number Diff line number Diff line change
Expand Up @@ -41,22 +41,33 @@ namespace gui

class EnvelopeGraph : public QWidget, public ModelView
{
public:
enum class ScalingMode
{
Dynamic,
Absolute,
Relative
};

public:
EnvelopeGraph(QWidget* parent);

protected:
void modelChanged() override;

void mousePressEvent(QMouseEvent* me) override;
void paintEvent(QPaintEvent* pe) override;
void mousePressEvent(QMouseEvent*) override;
void contextMenuEvent(QContextMenuEvent*) override;
void paintEvent(QPaintEvent*) override;

private:
void toggleAmountModel();

private:
QPixmap m_envGraph = embed::getIconPixmap("envelope_graph");

EnvelopeAndLfoParameters* m_params;
EnvelopeAndLfoParameters* m_params = nullptr;

ScalingMode m_scaling = ScalingMode::Dynamic;
};

} // namespace gui
Expand Down
7 changes: 7 additions & 0 deletions include/lmms_math.h
Original file line number Diff line number Diff line change
Expand Up @@ -327,6 +327,13 @@ static inline T absMin( T a, T b )
return std::abs(a) < std::abs(b) ? a : b;
}

//! Returns the linear interpolation of the two values
template<class T, class F>
constexpr T lerp(T a, T b, F t)
{
return (1. - t) * a + t * b;
}

// @brief Calculate number of digits which LcdSpinBox would show for a given number
// @note Once we upgrade to C++20, we could probably use std::formatted_size
static inline int numDigitsAsInt(float f)
Expand Down
Loading

0 comments on commit 8e40038

Please sign in to comment.