Use layouts for the instrument sound shaping tab / Extract classes for Envelope and LFO graphs (#7193)
Move the envelope and LFO graphs into their own classes. Besides the improved code organization this step had to be done to be able to use layouts in `EnvelopeAndLfoView`. The class previously had fixed layouts mixed with custom rendering in the paint event. Mouse events are now also handled in both new classes instead of in `EnvelopeAndLfoView`. ## Layouts in EnvelopeAndLfoView Use layouts to align the elements of the `EnvelopeAndLfoView`. This removes lots of hard-coded values. Add helper lambdas for the repeated creation of `Knob` and `PixmapButton` instances. The spacing that is explicitly introduced between the envelope and LFO should be removed once there is a more open layout. ## Layouts for InstrumentSoundShapingView Use layouts to align the elements of the `InstrumentSoundShapingView`. ## Info text improvements in LFO graph Draw the info text at around 20% of the LFO graph's height. This prepares the dialog to be scaled later. Write "1000 ms/LFO" instead of "ms/LFO: 1000" with a larger gap. ## Accessors for EnvelopeAndLfoParameters Make the enum `LfoShape` in `EnvelopeAndLfoParameters` public so that it can be used without friend declarations. Add accessor methods for the model of the LFO. ## Other improvements * Adjust include orders * Variable initialization in headers * Prevention of most vexing parses
This commit is contained in:
committed by
GitHub
parent
20fec28bef
commit
d447cb0648
@@ -71,7 +71,18 @@ public:
|
||||
using LfoList = QList<EnvelopeAndLfoParameters*>;
|
||||
LfoList m_lfos;
|
||||
|
||||
} ;
|
||||
};
|
||||
|
||||
enum class LfoShape
|
||||
{
|
||||
SineWave,
|
||||
TriangleWave,
|
||||
SawWave,
|
||||
SquareWave,
|
||||
UserDefinedWave,
|
||||
RandomWave,
|
||||
Count
|
||||
};
|
||||
|
||||
EnvelopeAndLfoParameters( float _value_for_zero_amount,
|
||||
Model * _parent );
|
||||
@@ -114,6 +125,28 @@ public:
|
||||
return m_rFrames;
|
||||
}
|
||||
|
||||
// Envelope
|
||||
const FloatModel& getPredelayModel() const { return m_predelayModel; }
|
||||
const FloatModel& getAttackModel() const { return m_attackModel; }
|
||||
const FloatModel& getHoldModel() const { return m_holdModel; }
|
||||
const FloatModel& getDecayModel() const { return m_decayModel; }
|
||||
const FloatModel& getSustainModel() const { return m_sustainModel; }
|
||||
const FloatModel& getReleaseModel() const { return m_releaseModel; }
|
||||
const FloatModel& getAmountModel() const { return m_amountModel; }
|
||||
FloatModel& getAmountModel() { return m_amountModel; }
|
||||
|
||||
|
||||
// LFO
|
||||
inline f_cnt_t getLfoPredelayFrames() const { return m_lfoPredelayFrames; }
|
||||
inline f_cnt_t getLfoAttackFrames() const { return m_lfoAttackFrames; }
|
||||
inline f_cnt_t getLfoOscillationFrames() const { return m_lfoOscillationFrames; }
|
||||
|
||||
const FloatModel& getLfoAmountModel() const { return m_lfoAmountModel; }
|
||||
FloatModel& getLfoAmountModel() { return m_lfoAmountModel; }
|
||||
const TempoSyncKnobModel& getLfoSpeedModel() const { return m_lfoSpeedModel; }
|
||||
const BoolModel& getX100Model() const { return m_x100Model; }
|
||||
const IntModel& getLfoWaveModel() const { return m_lfoWaveModel; }
|
||||
std::shared_ptr<const SampleBuffer> getLfoUserWave() const { return m_userWave; }
|
||||
|
||||
public slots:
|
||||
void updateSampleVars();
|
||||
@@ -170,16 +203,6 @@ private:
|
||||
bool m_bad_lfoShapeData;
|
||||
std::shared_ptr<const SampleBuffer> m_userWave = SampleBuffer::emptyBuffer();
|
||||
|
||||
enum class LfoShape
|
||||
{
|
||||
SineWave,
|
||||
TriangleWave,
|
||||
SawWave,
|
||||
SquareWave,
|
||||
UserDefinedWave,
|
||||
RandomWave,
|
||||
Count
|
||||
} ;
|
||||
constexpr static auto NumLfoShapes = static_cast<std::size_t>(LfoShape::Count);
|
||||
|
||||
sample_t lfoShapeSample( fpp_t _frame_offset );
|
||||
|
||||
@@ -29,10 +29,6 @@
|
||||
#include <QWidget>
|
||||
|
||||
#include "ModelView.h"
|
||||
#include "embed.h"
|
||||
|
||||
class QPaintEvent;
|
||||
class QPixmap;
|
||||
|
||||
namespace lmms
|
||||
{
|
||||
@@ -47,6 +43,8 @@ class Knob;
|
||||
class LedCheckBox;
|
||||
class PixmapButton;
|
||||
class TempoSyncKnob;
|
||||
class EnvelopeGraph;
|
||||
class LfoGraph;
|
||||
|
||||
|
||||
|
||||
@@ -63,8 +61,6 @@ protected:
|
||||
|
||||
void dragEnterEvent( QDragEnterEvent * _dee ) override;
|
||||
void dropEvent( QDropEvent * _de ) override;
|
||||
void mousePressEvent( QMouseEvent * _me ) override;
|
||||
void paintEvent( QPaintEvent * _pe ) override;
|
||||
|
||||
|
||||
protected slots:
|
||||
@@ -72,13 +68,10 @@ protected slots:
|
||||
|
||||
|
||||
private:
|
||||
QPixmap m_envGraph = embed::getIconPixmap("envelope_graph");
|
||||
QPixmap m_lfoGraph = embed::getIconPixmap("lfo_graph");
|
||||
|
||||
EnvelopeAndLfoParameters * m_params;
|
||||
|
||||
|
||||
// envelope stuff
|
||||
EnvelopeGraph* m_envelopeGraph;
|
||||
Knob * m_predelayKnob;
|
||||
Knob * m_attackKnob;
|
||||
Knob * m_holdKnob;
|
||||
@@ -88,6 +81,7 @@ private:
|
||||
Knob * m_amountKnob;
|
||||
|
||||
// LFO stuff
|
||||
LfoGraph* m_lfoGraph;
|
||||
Knob * m_lfoPredelayKnob;
|
||||
Knob * m_lfoAttackKnob;
|
||||
TempoSyncKnob * m_lfoSpeedKnob;
|
||||
@@ -97,8 +91,6 @@ private:
|
||||
|
||||
LedCheckBox * m_x100Cb;
|
||||
LedCheckBox * m_controlEnvAmountCb;
|
||||
|
||||
float m_randomGraph;
|
||||
} ;
|
||||
|
||||
} // namespace gui
|
||||
|
||||
66
include/EnvelopeGraph.h
Normal file
66
include/EnvelopeGraph.h
Normal file
@@ -0,0 +1,66 @@
|
||||
/*
|
||||
* EnvelopeGraph.h - Displays envelope graphs
|
||||
*
|
||||
* Copyright (c) 2004-2009 Tobias Doerffel <tobydox/at/users.sourceforge.net>
|
||||
* 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_ENVELOPE_GRAPH_H
|
||||
#define LMMS_GUI_ENVELOPE_GRAPH_H
|
||||
|
||||
#include <QWidget>
|
||||
|
||||
#include "ModelView.h"
|
||||
#include "embed.h"
|
||||
|
||||
namespace lmms
|
||||
{
|
||||
|
||||
class EnvelopeAndLfoParameters;
|
||||
|
||||
namespace gui
|
||||
{
|
||||
|
||||
class EnvelopeGraph : public QWidget, public ModelView
|
||||
{
|
||||
public:
|
||||
EnvelopeGraph(QWidget* parent);
|
||||
|
||||
protected:
|
||||
void modelChanged() override;
|
||||
|
||||
void mousePressEvent(QMouseEvent* me) override;
|
||||
void paintEvent(QPaintEvent* pe) override;
|
||||
|
||||
private:
|
||||
void toggleAmountModel();
|
||||
|
||||
private:
|
||||
QPixmap m_envGraph = embed::getIconPixmap("envelope_graph");
|
||||
|
||||
EnvelopeAndLfoParameters* m_params;
|
||||
};
|
||||
|
||||
} // namespace gui
|
||||
|
||||
} // namespace lmms
|
||||
|
||||
#endif // LMMS_GUI_ENVELOPE_GRAPH_H
|
||||
@@ -56,7 +56,7 @@ private:
|
||||
void modelChanged() override;
|
||||
|
||||
|
||||
InstrumentSoundShaping * m_ss;
|
||||
InstrumentSoundShaping * m_ss = nullptr;
|
||||
TabWidget * m_targetsTabWidget;
|
||||
EnvelopeAndLfoView * m_envLfoViews[InstrumentSoundShaping::NumTargets];
|
||||
|
||||
|
||||
68
include/LfoGraph.h
Normal file
68
include/LfoGraph.h
Normal file
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
* LfoGraph.h - Displays LFO graphs
|
||||
*
|
||||
* Copyright (c) 2004-2009 Tobias Doerffel <tobydox/at/users.sourceforge.net>
|
||||
* 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_LFO_GRAPH_H
|
||||
#define LMMS_GUI_LFO_GRAPH_H
|
||||
|
||||
#include <QWidget>
|
||||
|
||||
#include "ModelView.h"
|
||||
#include "embed.h"
|
||||
|
||||
namespace lmms
|
||||
{
|
||||
|
||||
class EnvelopeAndLfoParameters;
|
||||
|
||||
namespace gui
|
||||
{
|
||||
|
||||
class LfoGraph : public QWidget, public ModelView
|
||||
{
|
||||
public:
|
||||
LfoGraph(QWidget* parent);
|
||||
|
||||
protected:
|
||||
void modelChanged() override;
|
||||
|
||||
void mousePressEvent(QMouseEvent* me) override;
|
||||
void paintEvent(QPaintEvent* pe) override;
|
||||
|
||||
private:
|
||||
void toggleAmountModel();
|
||||
|
||||
private:
|
||||
QPixmap m_lfoGraph = embed::getIconPixmap("lfo_graph");
|
||||
|
||||
EnvelopeAndLfoParameters* m_params = nullptr;
|
||||
|
||||
float m_randomGraph {0.};
|
||||
};
|
||||
|
||||
} // namespace gui
|
||||
|
||||
} // namespace lmms
|
||||
|
||||
#endif // LMMS_GUI_LFO_GRAPH_H
|
||||
@@ -62,12 +62,14 @@ SET(LMMS_SRCS
|
||||
gui/editors/TrackContainerView.cpp
|
||||
|
||||
gui/instrument/EnvelopeAndLfoView.cpp
|
||||
gui/instrument/EnvelopeGraph.cpp
|
||||
gui/instrument/InstrumentFunctionViews.cpp
|
||||
gui/instrument/InstrumentMidiIOView.cpp
|
||||
gui/instrument/InstrumentTuningView.cpp
|
||||
gui/instrument/InstrumentSoundShapingView.cpp
|
||||
gui/instrument/InstrumentTrackWindow.cpp
|
||||
gui/instrument/InstrumentView.cpp
|
||||
gui/instrument/LfoGraph.cpp
|
||||
gui/instrument/PianoView.cpp
|
||||
|
||||
gui/menus/MidiPortMenu.cpp
|
||||
|
||||
@@ -23,202 +23,170 @@
|
||||
*
|
||||
*/
|
||||
|
||||
#include <QMouseEvent>
|
||||
#include <QPainter>
|
||||
|
||||
#include "EnvelopeAndLfoView.h"
|
||||
|
||||
#include "EnvelopeGraph.h"
|
||||
#include "LfoGraph.h"
|
||||
#include "EnvelopeAndLfoParameters.h"
|
||||
#include "SampleLoader.h"
|
||||
#include "embed.h"
|
||||
#include "Engine.h"
|
||||
#include "gui_templates.h"
|
||||
#include "Knob.h"
|
||||
#include "LedCheckBox.h"
|
||||
#include "AudioEngine.h"
|
||||
#include "DataFile.h"
|
||||
#include "Oscillator.h"
|
||||
#include "PixmapButton.h"
|
||||
#include "StringPairDrag.h"
|
||||
#include "TempoSyncKnob.h"
|
||||
#include "TextFloat.h"
|
||||
#include "Track.h"
|
||||
|
||||
#include <QBoxLayout>
|
||||
|
||||
|
||||
namespace lmms
|
||||
{
|
||||
|
||||
extern const float SECS_PER_ENV_SEGMENT;
|
||||
extern const float SECS_PER_LFO_OSCILLATION;
|
||||
|
||||
|
||||
namespace gui
|
||||
{
|
||||
|
||||
|
||||
const int ENV_GRAPH_X = 6;
|
||||
const int ENV_GRAPH_Y = 6;
|
||||
|
||||
const int ENV_KNOBS_Y = 43;
|
||||
const int ENV_KNOBS_LBL_Y = ENV_KNOBS_Y+35;
|
||||
const int KNOB_X_SPACING = 32;
|
||||
const int PREDELAY_KNOB_X = 6;
|
||||
const int ATTACK_KNOB_X = PREDELAY_KNOB_X+KNOB_X_SPACING;
|
||||
const int HOLD_KNOB_X = ATTACK_KNOB_X+KNOB_X_SPACING;
|
||||
const int DECAY_KNOB_X = HOLD_KNOB_X+KNOB_X_SPACING;
|
||||
const int SUSTAIN_KNOB_X = DECAY_KNOB_X+KNOB_X_SPACING;
|
||||
const int RELEASE_KNOB_X = SUSTAIN_KNOB_X+KNOB_X_SPACING;
|
||||
const int AMOUNT_KNOB_X = RELEASE_KNOB_X+KNOB_X_SPACING;
|
||||
|
||||
const int TIME_UNIT_WIDTH = 40;
|
||||
|
||||
const int LFO_GRAPH_X = 6;
|
||||
const int LFO_GRAPH_Y = ENV_KNOBS_LBL_Y+14;
|
||||
const int LFO_KNOB_Y = LFO_GRAPH_Y-2;
|
||||
const int LFO_PREDELAY_KNOB_X = LFO_GRAPH_X + 100;
|
||||
const int LFO_ATTACK_KNOB_X = LFO_PREDELAY_KNOB_X+KNOB_X_SPACING;
|
||||
const int LFO_SPEED_KNOB_X = LFO_ATTACK_KNOB_X+KNOB_X_SPACING;
|
||||
const int LFO_AMOUNT_KNOB_X = LFO_SPEED_KNOB_X+KNOB_X_SPACING;
|
||||
const int LFO_SHAPES_X = LFO_GRAPH_X;//PREDELAY_KNOB_X;
|
||||
const int LFO_SHAPES_Y = LFO_GRAPH_Y + 50;
|
||||
|
||||
EnvelopeAndLfoView::EnvelopeAndLfoView( QWidget * _parent ) :
|
||||
QWidget( _parent ),
|
||||
ModelView( nullptr, this ),
|
||||
m_params( nullptr )
|
||||
EnvelopeAndLfoView::EnvelopeAndLfoView(QWidget * parent) :
|
||||
QWidget(parent),
|
||||
ModelView(nullptr, this),
|
||||
m_params(nullptr)
|
||||
{
|
||||
// Helper lambdas for consistent repeated buiding of certain widgets
|
||||
auto buildKnob = [&](const QString& label, const QString& hintText)
|
||||
{
|
||||
auto knob = new Knob(KnobType::Bright26, this);
|
||||
knob->setLabel(label);
|
||||
knob->setHintText(hintText, "");
|
||||
|
||||
return knob;
|
||||
};
|
||||
|
||||
m_predelayKnob = new Knob( KnobType::Bright26, this );
|
||||
m_predelayKnob->setLabel( tr( "DEL" ) );
|
||||
m_predelayKnob->move( PREDELAY_KNOB_X, ENV_KNOBS_Y );
|
||||
m_predelayKnob->setHintText( tr( "Pre-delay:" ), "" );
|
||||
auto buildPixmapButton = [&](const QString& activePixmap, const QString& inactivePixmap)
|
||||
{
|
||||
auto button = new PixmapButton(this, nullptr);
|
||||
button->setActiveGraphic(embed::getIconPixmap(activePixmap));
|
||||
button->setInactiveGraphic(embed::getIconPixmap(inactivePixmap));
|
||||
|
||||
return button;
|
||||
};
|
||||
|
||||
QVBoxLayout* mainLayout = new QVBoxLayout(this);
|
||||
mainLayout->setContentsMargins(5, 5, 5, 5);
|
||||
|
||||
// Envelope
|
||||
|
||||
QVBoxLayout* envelopeLayout = new QVBoxLayout();
|
||||
mainLayout->addLayout(envelopeLayout);
|
||||
|
||||
QHBoxLayout* graphAndAmountLayout = new QHBoxLayout();
|
||||
envelopeLayout->addLayout(graphAndAmountLayout);
|
||||
|
||||
m_envelopeGraph = new EnvelopeGraph(this);
|
||||
graphAndAmountLayout->addWidget(m_envelopeGraph);
|
||||
|
||||
m_amountKnob = buildKnob(tr("AMT"), tr("Modulation amount:"));
|
||||
graphAndAmountLayout->addWidget(m_amountKnob, 0, Qt::AlignCenter);
|
||||
|
||||
QHBoxLayout* envKnobsLayout = new QHBoxLayout();
|
||||
envelopeLayout->addLayout(envKnobsLayout);
|
||||
|
||||
m_predelayKnob = buildKnob(tr("DEL"), tr("Pre-delay:"));
|
||||
envKnobsLayout->addWidget(m_predelayKnob);
|
||||
|
||||
m_attackKnob = buildKnob(tr("ATT"), tr("Attack:"));
|
||||
envKnobsLayout->addWidget(m_attackKnob);
|
||||
|
||||
m_holdKnob = buildKnob(tr("HOLD"), tr("Hold:"));
|
||||
envKnobsLayout->addWidget(m_holdKnob);
|
||||
|
||||
m_decayKnob = buildKnob(tr("DEC"), tr("Decay:"));
|
||||
envKnobsLayout->addWidget(m_decayKnob);
|
||||
|
||||
m_sustainKnob = buildKnob(tr("SUST"), tr("Sustain:"));
|
||||
envKnobsLayout->addWidget(m_sustainKnob);
|
||||
|
||||
m_releaseKnob = buildKnob(tr("REL"), tr("Release:"));
|
||||
envKnobsLayout->addWidget(m_releaseKnob);
|
||||
|
||||
|
||||
m_attackKnob = new Knob( KnobType::Bright26, this );
|
||||
m_attackKnob->setLabel( tr( "ATT" ) );
|
||||
m_attackKnob->move( ATTACK_KNOB_X, ENV_KNOBS_Y );
|
||||
m_attackKnob->setHintText( tr( "Attack:" ), "" );
|
||||
// Add some space between the envelope and LFO section
|
||||
mainLayout->addSpacing(10);
|
||||
|
||||
|
||||
m_holdKnob = new Knob( KnobType::Bright26, this );
|
||||
m_holdKnob->setLabel( tr( "HOLD" ) );
|
||||
m_holdKnob->move( HOLD_KNOB_X, ENV_KNOBS_Y );
|
||||
m_holdKnob->setHintText( tr( "Hold:" ), "" );
|
||||
// LFO
|
||||
|
||||
QHBoxLayout* lfoLayout = new QHBoxLayout();
|
||||
mainLayout->addLayout(lfoLayout);
|
||||
|
||||
m_decayKnob = new Knob( KnobType::Bright26, this );
|
||||
m_decayKnob->setLabel( tr( "DEC" ) );
|
||||
m_decayKnob->move( DECAY_KNOB_X, ENV_KNOBS_Y );
|
||||
m_decayKnob->setHintText( tr( "Decay:" ), "" );
|
||||
QVBoxLayout* graphAndTypesLayout = new QVBoxLayout();
|
||||
lfoLayout->addLayout(graphAndTypesLayout);
|
||||
|
||||
m_lfoGraph = new LfoGraph(this);
|
||||
graphAndTypesLayout->addWidget(m_lfoGraph);
|
||||
|
||||
m_sustainKnob = new Knob( KnobType::Bright26, this );
|
||||
m_sustainKnob->setLabel( tr( "SUST" ) );
|
||||
m_sustainKnob->move( SUSTAIN_KNOB_X, ENV_KNOBS_Y );
|
||||
m_sustainKnob->setHintText( tr( "Sustain:" ), "" );
|
||||
QHBoxLayout* typesLayout = new QHBoxLayout();
|
||||
graphAndTypesLayout->addLayout(typesLayout);
|
||||
typesLayout->setContentsMargins(0, 0, 0, 0);
|
||||
typesLayout->setSpacing(0);
|
||||
|
||||
auto sin_lfo_btn = buildPixmapButton("sin_wave_active", "sin_wave_inactive");
|
||||
auto triangle_lfo_btn = buildPixmapButton("triangle_wave_active", "triangle_wave_inactive");
|
||||
auto saw_lfo_btn = buildPixmapButton("saw_wave_active", "saw_wave_inactive");
|
||||
auto sqr_lfo_btn = buildPixmapButton("square_wave_active","square_wave_inactive");
|
||||
auto random_lfo_btn = buildPixmapButton("random_wave_active", "random_wave_inactive");
|
||||
m_userLfoBtn = buildPixmapButton("usr_wave_active", "usr_wave_inactive");
|
||||
|
||||
m_releaseKnob = new Knob( KnobType::Bright26, this );
|
||||
m_releaseKnob->setLabel( tr( "REL" ) );
|
||||
m_releaseKnob->move( RELEASE_KNOB_X, ENV_KNOBS_Y );
|
||||
m_releaseKnob->setHintText( tr( "Release:" ), "" );
|
||||
connect(m_userLfoBtn, SIGNAL(toggled(bool)), this, SLOT(lfoUserWaveChanged()));
|
||||
|
||||
typesLayout->addWidget(sin_lfo_btn);
|
||||
typesLayout->addWidget(triangle_lfo_btn);
|
||||
typesLayout->addWidget(saw_lfo_btn);
|
||||
typesLayout->addWidget(sqr_lfo_btn);
|
||||
typesLayout->addWidget(random_lfo_btn);
|
||||
typesLayout->addWidget(m_userLfoBtn);
|
||||
|
||||
m_amountKnob = new Knob( KnobType::Bright26, this );
|
||||
m_amountKnob->setLabel( tr( "AMT" ) );
|
||||
m_amountKnob->move( AMOUNT_KNOB_X, ENV_GRAPH_Y );
|
||||
m_amountKnob->setHintText( tr( "Modulation amount:" ), "" );
|
||||
m_lfoWaveBtnGrp = new automatableButtonGroup(this);
|
||||
m_lfoWaveBtnGrp->addButton(sin_lfo_btn);
|
||||
m_lfoWaveBtnGrp->addButton(triangle_lfo_btn);
|
||||
m_lfoWaveBtnGrp->addButton(saw_lfo_btn);
|
||||
m_lfoWaveBtnGrp->addButton(sqr_lfo_btn);
|
||||
m_lfoWaveBtnGrp->addButton(m_userLfoBtn);
|
||||
m_lfoWaveBtnGrp->addButton(random_lfo_btn);
|
||||
|
||||
QVBoxLayout* knobsAndCheckBoxesLayout = new QVBoxLayout();
|
||||
lfoLayout->addLayout(knobsAndCheckBoxesLayout);
|
||||
|
||||
QHBoxLayout* lfoKnobsLayout = new QHBoxLayout();
|
||||
knobsAndCheckBoxesLayout->addLayout(lfoKnobsLayout);
|
||||
|
||||
m_lfoPredelayKnob = buildKnob(tr("DEL"), tr("Pre-delay:"));
|
||||
lfoKnobsLayout->addWidget(m_lfoPredelayKnob);
|
||||
|
||||
m_lfoPredelayKnob = new Knob( KnobType::Bright26, this );
|
||||
m_lfoPredelayKnob->setLabel( tr( "DEL" ) );
|
||||
m_lfoPredelayKnob->move( LFO_PREDELAY_KNOB_X, LFO_KNOB_Y );
|
||||
m_lfoPredelayKnob->setHintText( tr( "Pre-delay:" ), "" );
|
||||
m_lfoAttackKnob = buildKnob(tr("ATT"), tr("Attack:"));
|
||||
lfoKnobsLayout->addWidget(m_lfoAttackKnob);
|
||||
|
||||
m_lfoSpeedKnob = new TempoSyncKnob(KnobType::Bright26, this);
|
||||
m_lfoSpeedKnob->setLabel(tr("SPD"));
|
||||
m_lfoSpeedKnob->setHintText(tr("Frequency:"), "");
|
||||
lfoKnobsLayout->addWidget(m_lfoSpeedKnob);
|
||||
|
||||
m_lfoAttackKnob = new Knob( KnobType::Bright26, this );
|
||||
m_lfoAttackKnob->setLabel( tr( "ATT" ) );
|
||||
m_lfoAttackKnob->move( LFO_ATTACK_KNOB_X, LFO_KNOB_Y );
|
||||
m_lfoAttackKnob->setHintText( tr( "Attack:" ), "" );
|
||||
m_lfoAmountKnob = buildKnob(tr("AMT"), tr("Modulation amount:"));
|
||||
lfoKnobsLayout->addWidget(m_lfoAmountKnob);
|
||||
|
||||
QVBoxLayout* checkBoxesLayout = new QVBoxLayout();
|
||||
knobsAndCheckBoxesLayout->addLayout(checkBoxesLayout);
|
||||
|
||||
m_lfoSpeedKnob = new TempoSyncKnob( KnobType::Bright26, this );
|
||||
m_lfoSpeedKnob->setLabel( tr( "SPD" ) );
|
||||
m_lfoSpeedKnob->move( LFO_SPEED_KNOB_X, LFO_KNOB_Y );
|
||||
m_lfoSpeedKnob->setHintText( tr( "Frequency:" ), "" );
|
||||
|
||||
|
||||
m_lfoAmountKnob = new Knob( KnobType::Bright26, this );
|
||||
m_lfoAmountKnob->setLabel( tr( "AMT" ) );
|
||||
m_lfoAmountKnob->move( LFO_AMOUNT_KNOB_X, LFO_KNOB_Y );
|
||||
m_lfoAmountKnob->setHintText( tr( "Modulation amount:" ), "" );
|
||||
|
||||
auto sin_lfo_btn = new PixmapButton(this, nullptr);
|
||||
sin_lfo_btn->move( LFO_SHAPES_X, LFO_SHAPES_Y );
|
||||
sin_lfo_btn->setActiveGraphic( embed::getIconPixmap(
|
||||
"sin_wave_active" ) );
|
||||
sin_lfo_btn->setInactiveGraphic( embed::getIconPixmap(
|
||||
"sin_wave_inactive" ) );
|
||||
|
||||
auto triangle_lfo_btn = new PixmapButton(this, nullptr);
|
||||
triangle_lfo_btn->move( LFO_SHAPES_X+15, LFO_SHAPES_Y );
|
||||
triangle_lfo_btn->setActiveGraphic( embed::getIconPixmap(
|
||||
"triangle_wave_active" ) );
|
||||
triangle_lfo_btn->setInactiveGraphic( embed::getIconPixmap(
|
||||
"triangle_wave_inactive" ) );
|
||||
|
||||
auto saw_lfo_btn = new PixmapButton(this, nullptr);
|
||||
saw_lfo_btn->move( LFO_SHAPES_X+30, LFO_SHAPES_Y );
|
||||
saw_lfo_btn->setActiveGraphic( embed::getIconPixmap(
|
||||
"saw_wave_active" ) );
|
||||
saw_lfo_btn->setInactiveGraphic( embed::getIconPixmap(
|
||||
"saw_wave_inactive" ) );
|
||||
|
||||
auto sqr_lfo_btn = new PixmapButton(this, nullptr);
|
||||
sqr_lfo_btn->move( LFO_SHAPES_X+45, LFO_SHAPES_Y );
|
||||
sqr_lfo_btn->setActiveGraphic( embed::getIconPixmap(
|
||||
"square_wave_active" ) );
|
||||
sqr_lfo_btn->setInactiveGraphic( embed::getIconPixmap(
|
||||
"square_wave_inactive" ) );
|
||||
|
||||
m_userLfoBtn = new PixmapButton( this, nullptr );
|
||||
m_userLfoBtn->move( LFO_SHAPES_X+75, LFO_SHAPES_Y );
|
||||
m_userLfoBtn->setActiveGraphic( embed::getIconPixmap(
|
||||
"usr_wave_active" ) );
|
||||
m_userLfoBtn->setInactiveGraphic( embed::getIconPixmap(
|
||||
"usr_wave_inactive" ) );
|
||||
|
||||
connect( m_userLfoBtn, SIGNAL(toggled(bool)),
|
||||
this, SLOT(lfoUserWaveChanged()));
|
||||
|
||||
auto random_lfo_btn = new PixmapButton(this, nullptr);
|
||||
random_lfo_btn->move( LFO_SHAPES_X+60, LFO_SHAPES_Y );
|
||||
random_lfo_btn->setActiveGraphic( embed::getIconPixmap(
|
||||
"random_wave_active" ) );
|
||||
random_lfo_btn->setInactiveGraphic( embed::getIconPixmap(
|
||||
"random_wave_inactive" ) );
|
||||
|
||||
m_lfoWaveBtnGrp = new automatableButtonGroup( this );
|
||||
m_lfoWaveBtnGrp->addButton( sin_lfo_btn );
|
||||
m_lfoWaveBtnGrp->addButton( triangle_lfo_btn );
|
||||
m_lfoWaveBtnGrp->addButton( saw_lfo_btn );
|
||||
m_lfoWaveBtnGrp->addButton( sqr_lfo_btn );
|
||||
m_lfoWaveBtnGrp->addButton( m_userLfoBtn );
|
||||
m_lfoWaveBtnGrp->addButton( random_lfo_btn );
|
||||
|
||||
m_x100Cb = new LedCheckBox( tr( "FREQ x 100" ), this );
|
||||
m_x100Cb->move( LFO_PREDELAY_KNOB_X, LFO_GRAPH_Y + 36 );
|
||||
m_x100Cb = new LedCheckBox(tr("FREQ x 100"), this);
|
||||
m_x100Cb->setToolTip(tr("Multiply LFO frequency by 100"));
|
||||
|
||||
checkBoxesLayout->addWidget(m_x100Cb);
|
||||
|
||||
m_controlEnvAmountCb = new LedCheckBox(tr("MOD ENV AMOUNT"), this);
|
||||
m_controlEnvAmountCb->move( LFO_PREDELAY_KNOB_X, LFO_GRAPH_Y + 54 );
|
||||
m_controlEnvAmountCb->setToolTip(
|
||||
tr( "Control envelope amount by this LFO" ) );
|
||||
|
||||
|
||||
setAcceptDrops( true );
|
||||
m_controlEnvAmountCb->setToolTip(tr("Control envelope amount by this LFO"));
|
||||
checkBoxesLayout->addWidget(m_controlEnvAmountCb);
|
||||
|
||||
setAcceptDrops(true);
|
||||
}
|
||||
|
||||
|
||||
@@ -235,6 +203,7 @@ EnvelopeAndLfoView::~EnvelopeAndLfoView()
|
||||
void EnvelopeAndLfoView::modelChanged()
|
||||
{
|
||||
m_params = castModel<EnvelopeAndLfoParameters>();
|
||||
m_envelopeGraph->setModel(m_params);
|
||||
m_predelayKnob->setModel( &m_params->m_predelayModel );
|
||||
m_attackKnob->setModel( &m_params->m_attackModel );
|
||||
m_holdKnob->setModel( &m_params->m_holdModel );
|
||||
@@ -242,6 +211,8 @@ void EnvelopeAndLfoView::modelChanged()
|
||||
m_sustainKnob->setModel( &m_params->m_sustainModel );
|
||||
m_releaseKnob->setModel( &m_params->m_releaseModel );
|
||||
m_amountKnob->setModel( &m_params->m_amountModel );
|
||||
|
||||
m_lfoGraph->setModel(m_params);
|
||||
m_lfoPredelayKnob->setModel( &m_params->m_lfoPredelayModel );
|
||||
m_lfoAttackKnob->setModel( &m_params->m_lfoAttackModel );
|
||||
m_lfoSpeedKnob->setModel( &m_params->m_lfoSpeedModel );
|
||||
@@ -254,40 +225,6 @@ void EnvelopeAndLfoView::modelChanged()
|
||||
|
||||
|
||||
|
||||
void EnvelopeAndLfoView::mousePressEvent( QMouseEvent * _me )
|
||||
{
|
||||
if( _me->button() != Qt::LeftButton )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (QRect(ENV_GRAPH_X, ENV_GRAPH_Y, m_envGraph.width(), m_envGraph.height()).contains(_me->pos()))
|
||||
{
|
||||
if( m_params->m_amountModel.value() < 1.0f )
|
||||
{
|
||||
m_params->m_amountModel.setValue( 1.0f );
|
||||
}
|
||||
else
|
||||
{
|
||||
m_params->m_amountModel.setValue( 0.0f );
|
||||
}
|
||||
}
|
||||
else if (QRect(LFO_GRAPH_X, LFO_GRAPH_Y, m_lfoGraph.width(), m_lfoGraph.height()).contains(_me->pos()))
|
||||
{
|
||||
if( m_params->m_lfoAmountModel.value() < 1.0f )
|
||||
{
|
||||
m_params->m_lfoAmountModel.setValue( 1.0f );
|
||||
}
|
||||
else
|
||||
{
|
||||
m_params->m_lfoAmountModel.setValue( 0.0f );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
void EnvelopeAndLfoView::dragEnterEvent( QDragEnterEvent * _dee )
|
||||
{
|
||||
StringPairDrag::processDragEnterEvent( _dee,
|
||||
@@ -327,167 +264,6 @@ void EnvelopeAndLfoView::dropEvent( QDropEvent * _de )
|
||||
|
||||
|
||||
|
||||
void EnvelopeAndLfoView::paintEvent( QPaintEvent * )
|
||||
{
|
||||
QPainter p( this );
|
||||
p.setRenderHint( QPainter::Antialiasing );
|
||||
|
||||
// draw envelope-graph
|
||||
p.drawPixmap(ENV_GRAPH_X, ENV_GRAPH_Y, m_envGraph);
|
||||
// draw LFO-graph
|
||||
p.drawPixmap(LFO_GRAPH_X, LFO_GRAPH_Y, m_lfoGraph);
|
||||
|
||||
p.setFont(adjustedToPixelSize(p.font(), 8));
|
||||
|
||||
const float gray_amount = 1.0f - fabsf( m_amountKnob->value<float>() );
|
||||
|
||||
p.setPen( QPen( QColor( static_cast<int>( 96 * gray_amount ),
|
||||
static_cast<int>( 255 - 159 * gray_amount ),
|
||||
static_cast<int>( 128 - 32 * gray_amount ) ),
|
||||
2 ) );
|
||||
|
||||
const QColor end_points_color( 0x99, 0xAF, 0xFF );
|
||||
const QColor end_points_bg_color( 0, 0, 2 );
|
||||
|
||||
const int y_base = ENV_GRAPH_Y + m_envGraph.height() - 3;
|
||||
const int avail_height = m_envGraph.height() - 6;
|
||||
|
||||
int x1 = static_cast<int>( m_predelayKnob->value<float>() * TIME_UNIT_WIDTH );
|
||||
int x2 = x1 + static_cast<int>( m_attackKnob->value<float>() * TIME_UNIT_WIDTH );
|
||||
int x3 = x2 + static_cast<int>( m_holdKnob->value<float>() * TIME_UNIT_WIDTH );
|
||||
int x4 = x3 + static_cast<int>( ( m_decayKnob->value<float>() *
|
||||
( 1 - m_sustainKnob->value<float>() ) ) * TIME_UNIT_WIDTH );
|
||||
int x5 = x4 + static_cast<int>( m_releaseKnob->value<float>() * TIME_UNIT_WIDTH );
|
||||
|
||||
if( x5 > 174 )
|
||||
{
|
||||
x1 = ( x1 * 174 ) / x5;
|
||||
x2 = ( x2 * 174 ) / x5;
|
||||
x3 = ( x3 * 174 ) / x5;
|
||||
x4 = ( x4 * 174 ) / x5;
|
||||
x5 = ( x5 * 174 ) / x5;
|
||||
}
|
||||
x1 += ENV_GRAPH_X + 2;
|
||||
x2 += ENV_GRAPH_X + 2;
|
||||
x3 += ENV_GRAPH_X + 2;
|
||||
x4 += ENV_GRAPH_X + 2;
|
||||
x5 += ENV_GRAPH_X + 2;
|
||||
|
||||
p.drawLine( x1, y_base, x2, y_base - avail_height );
|
||||
p.fillRect( x1 - 1, y_base - 2, 4, 4, end_points_bg_color );
|
||||
p.fillRect( x1, y_base - 1, 2, 2, end_points_color );
|
||||
|
||||
p.drawLine( x2, y_base - avail_height, x3, y_base - avail_height );
|
||||
p.fillRect( x2 - 1, y_base - 2 - avail_height, 4, 4,
|
||||
end_points_bg_color );
|
||||
p.fillRect( x2, y_base - 1 - avail_height, 2, 2, end_points_color );
|
||||
|
||||
p.drawLine( x3, y_base-avail_height, x4, static_cast<int>( y_base -
|
||||
avail_height +
|
||||
( 1 - m_sustainKnob->value<float>() ) * avail_height ) );
|
||||
p.fillRect( x3 - 1, y_base - 2 - avail_height, 4, 4,
|
||||
end_points_bg_color );
|
||||
p.fillRect( x3, y_base - 1 - avail_height, 2, 2, end_points_color );
|
||||
|
||||
p.drawLine( x4, static_cast<int>( y_base - avail_height +
|
||||
( 1 - m_sustainKnob->value<float>() ) *
|
||||
avail_height ), x5, y_base );
|
||||
p.fillRect( x4 - 1, static_cast<int>( y_base - avail_height +
|
||||
( 1 - m_sustainKnob->value<float>() ) *
|
||||
avail_height ) - 2, 4, 4,
|
||||
end_points_bg_color );
|
||||
p.fillRect( x4, static_cast<int>( y_base - avail_height +
|
||||
( 1 - m_sustainKnob->value<float>() ) *
|
||||
avail_height ) - 1, 2, 2,
|
||||
end_points_color );
|
||||
p.fillRect( x5 - 1, y_base - 2, 4, 4, end_points_bg_color );
|
||||
p.fillRect( x5, y_base - 1, 2, 2, end_points_color );
|
||||
|
||||
int LFO_GRAPH_W = m_lfoGraph.width() - 3; // subtract border
|
||||
int LFO_GRAPH_H = m_lfoGraph.height() - 6; // subtract border
|
||||
int graph_x_base = LFO_GRAPH_X + 2;
|
||||
int graph_y_base = LFO_GRAPH_Y + 3 + LFO_GRAPH_H / 2;
|
||||
|
||||
const float frames_for_graph = SECS_PER_LFO_OSCILLATION *
|
||||
Engine::audioEngine()->baseSampleRate() / 10;
|
||||
|
||||
const float lfo_gray_amount = 1.0f - fabsf( m_lfoAmountKnob->value<float>() );
|
||||
p.setPen( QPen( QColor( static_cast<int>( 96 * lfo_gray_amount ),
|
||||
static_cast<int>( 255 - 159 * lfo_gray_amount ),
|
||||
static_cast<int>( 128 - 32 *
|
||||
lfo_gray_amount ) ),
|
||||
1.5 ) );
|
||||
|
||||
|
||||
float osc_frames = m_params->m_lfoOscillationFrames;
|
||||
|
||||
if( m_params->m_x100Model.value() )
|
||||
{
|
||||
osc_frames *= 100.0f;
|
||||
}
|
||||
|
||||
float old_y = 0;
|
||||
for( int x = 0; x <= LFO_GRAPH_W; ++x )
|
||||
{
|
||||
float val = 0.0;
|
||||
float cur_sample = x * frames_for_graph / LFO_GRAPH_W;
|
||||
if( static_cast<f_cnt_t>( cur_sample ) >
|
||||
m_params->m_lfoPredelayFrames )
|
||||
{
|
||||
float phase = ( cur_sample -=
|
||||
m_params->m_lfoPredelayFrames ) /
|
||||
osc_frames;
|
||||
switch( static_cast<EnvelopeAndLfoParameters::LfoShape>(m_params->m_lfoWaveModel.value()) )
|
||||
{
|
||||
case EnvelopeAndLfoParameters::LfoShape::SineWave:
|
||||
default:
|
||||
val = Oscillator::sinSample( phase );
|
||||
break;
|
||||
case EnvelopeAndLfoParameters::LfoShape::TriangleWave:
|
||||
val = Oscillator::triangleSample(
|
||||
phase );
|
||||
break;
|
||||
case EnvelopeAndLfoParameters::LfoShape::SawWave:
|
||||
val = Oscillator::sawSample( phase );
|
||||
break;
|
||||
case EnvelopeAndLfoParameters::LfoShape::SquareWave:
|
||||
val = Oscillator::squareSample( phase );
|
||||
break;
|
||||
case EnvelopeAndLfoParameters::LfoShape::RandomWave:
|
||||
if( x % (int)( 900 * m_lfoSpeedKnob->value<float>() + 1 ) == 0 )
|
||||
{
|
||||
m_randomGraph = Oscillator::noiseSample( 0.0f );
|
||||
}
|
||||
val = m_randomGraph;
|
||||
break;
|
||||
case EnvelopeAndLfoParameters::LfoShape::UserDefinedWave:
|
||||
val = Oscillator::userWaveSample(m_params->m_userWave.get(), phase);
|
||||
break;
|
||||
}
|
||||
if( static_cast<f_cnt_t>( cur_sample ) <=
|
||||
m_params->m_lfoAttackFrames )
|
||||
{
|
||||
val *= cur_sample / m_params->m_lfoAttackFrames;
|
||||
}
|
||||
}
|
||||
float cur_y = -LFO_GRAPH_H / 2.0f * val;
|
||||
p.drawLine( QLineF( graph_x_base + x - 1, graph_y_base + old_y,
|
||||
graph_x_base + x,
|
||||
graph_y_base + cur_y ) );
|
||||
old_y = cur_y;
|
||||
}
|
||||
|
||||
p.setPen( QColor( 201, 201, 225 ) );
|
||||
int ms_per_osc = static_cast<int>( SECS_PER_LFO_OSCILLATION *
|
||||
m_lfoSpeedKnob->value<float>() *
|
||||
1000.0f );
|
||||
p.drawText(LFO_GRAPH_X + 4, LFO_GRAPH_Y + m_lfoGraph.height() - 6, tr("ms/LFO:"));
|
||||
p.drawText(LFO_GRAPH_X + 52, LFO_GRAPH_Y + m_lfoGraph.height() - 6, QString::number(ms_per_osc));
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
void EnvelopeAndLfoView::lfoUserWaveChanged()
|
||||
{
|
||||
if( static_cast<EnvelopeAndLfoParameters::LfoShape>(m_params->m_lfoWaveModel.value()) ==
|
||||
@@ -502,11 +278,6 @@ void EnvelopeAndLfoView::lfoUserWaveChanged()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
} // namespace gui
|
||||
|
||||
} // namespace lmms
|
||||
|
||||
157
src/gui/instrument/EnvelopeGraph.cpp
Normal file
157
src/gui/instrument/EnvelopeGraph.cpp
Normal file
@@ -0,0 +1,157 @@
|
||||
/*
|
||||
* EnvelopeGraph.cpp - Displays envelope graphs
|
||||
*
|
||||
* Copyright (c) 2004-2014 Tobias Doerffel <tobydox/at/users.sourceforge.net>
|
||||
* 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.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <QMouseEvent>
|
||||
#include <QPainter>
|
||||
|
||||
#include "EnvelopeGraph.h"
|
||||
|
||||
#include "EnvelopeAndLfoParameters.h"
|
||||
|
||||
namespace lmms
|
||||
{
|
||||
|
||||
namespace gui
|
||||
{
|
||||
|
||||
const int TIME_UNIT_WIDTH = 40;
|
||||
|
||||
|
||||
EnvelopeGraph::EnvelopeGraph(QWidget* parent) :
|
||||
QWidget(parent),
|
||||
ModelView(nullptr, this),
|
||||
m_params(nullptr)
|
||||
{
|
||||
setFixedSize(m_envGraph.size());
|
||||
}
|
||||
|
||||
void EnvelopeGraph::modelChanged()
|
||||
{
|
||||
m_params = castModel<EnvelopeAndLfoParameters>();
|
||||
}
|
||||
|
||||
void EnvelopeGraph::mousePressEvent(QMouseEvent* me)
|
||||
{
|
||||
if(me->button() == Qt::LeftButton)
|
||||
{
|
||||
toggleAmountModel();
|
||||
}
|
||||
}
|
||||
|
||||
void EnvelopeGraph::paintEvent(QPaintEvent*)
|
||||
{
|
||||
QPainter p(this);
|
||||
p.setRenderHint(QPainter::Antialiasing);
|
||||
|
||||
// Draw the graph background
|
||||
p.drawPixmap(rect(), m_envGraph);
|
||||
|
||||
const auto * params = castModel<EnvelopeAndLfoParameters>();
|
||||
if (!params)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const float amount = params->getAmountModel().value();
|
||||
const float predelay = params->getPredelayModel().value();
|
||||
const float attack = params->getAttackModel().value();
|
||||
const float hold = params->getHoldModel().value();
|
||||
const float decay = params->getDecayModel().value();
|
||||
const float sustain = params->getSustainModel().value();
|
||||
const float release = params->getReleaseModel().value();
|
||||
|
||||
const float gray_amount = 1.0f - fabsf(amount);
|
||||
|
||||
p.setPen(QPen(QColor(static_cast<int>(96 * gray_amount),
|
||||
static_cast<int>(255 - 159 * gray_amount),
|
||||
static_cast<int>(128 - 32 * gray_amount)),
|
||||
2));
|
||||
|
||||
const QColor end_points_color(0x99, 0xAF, 0xFF);
|
||||
const QColor end_points_bg_color(0, 0, 2);
|
||||
|
||||
const int y_base = m_envGraph.height() - 3;
|
||||
const int avail_height = m_envGraph.height() - 6;
|
||||
|
||||
int x1 = static_cast<int>(predelay * TIME_UNIT_WIDTH);
|
||||
int x2 = x1 + static_cast<int>(attack * TIME_UNIT_WIDTH);
|
||||
int x3 = x2 + static_cast<int>(hold * TIME_UNIT_WIDTH);
|
||||
int x4 = x3 + static_cast<int>((decay * (1 - sustain)) * TIME_UNIT_WIDTH);
|
||||
int x5 = x4 + static_cast<int>(release * TIME_UNIT_WIDTH);
|
||||
|
||||
if (x5 > 174)
|
||||
{
|
||||
x1 = (x1 * 174) / x5;
|
||||
x2 = (x2 * 174) / x5;
|
||||
x3 = (x3 * 174) / x5;
|
||||
x4 = (x4 * 174) / x5;
|
||||
x5 = (x5 * 174) / x5;
|
||||
}
|
||||
x1 += 2;
|
||||
x2 += 2;
|
||||
x3 += 2;
|
||||
x4 += 2;
|
||||
x5 += 2;
|
||||
|
||||
p.drawLine(x1, y_base, x2, y_base - avail_height);
|
||||
p.fillRect(x1 - 1, y_base - 2, 4, 4, end_points_bg_color);
|
||||
p.fillRect(x1, y_base - 1, 2, 2, end_points_color);
|
||||
|
||||
p.drawLine(x2, y_base - avail_height, x3, y_base - avail_height);
|
||||
p.fillRect(x2 - 1, y_base - 2 - avail_height, 4, 4,
|
||||
end_points_bg_color);
|
||||
p.fillRect(x2, y_base - 1 - avail_height, 2, 2, end_points_color);
|
||||
|
||||
const int sustainHeight = static_cast<int>(y_base - avail_height + (1 - sustain) * avail_height);
|
||||
|
||||
p.drawLine(x3, y_base-avail_height, x4, sustainHeight);
|
||||
p.fillRect(x3 - 1, y_base - 2 - avail_height, 4, 4, end_points_bg_color);
|
||||
p.fillRect(x3, y_base - 1 - avail_height, 2, 2, end_points_color);
|
||||
|
||||
p.drawLine(x4, sustainHeight, x5, y_base);
|
||||
p.fillRect(x4 - 1, sustainHeight - 2, 4, 4, end_points_bg_color);
|
||||
p.fillRect(x4, sustainHeight - 1, 2, 2, end_points_color);
|
||||
p.fillRect(x5 - 1, y_base - 2, 4, 4, end_points_bg_color);
|
||||
p.fillRect(x5, y_base - 1, 2, 2, end_points_color);
|
||||
}
|
||||
|
||||
void EnvelopeGraph::toggleAmountModel()
|
||||
{
|
||||
auto* params = castModel<EnvelopeAndLfoParameters>();
|
||||
auto& amountModel = params->getAmountModel();
|
||||
|
||||
if (amountModel.value() < 1.0f )
|
||||
{
|
||||
amountModel.setValue( 1.0f );
|
||||
}
|
||||
else
|
||||
{
|
||||
amountModel.setValue( 0.0f );
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace gui
|
||||
|
||||
} // namespace lmms
|
||||
@@ -22,9 +22,11 @@
|
||||
*
|
||||
*/
|
||||
|
||||
#include <QLabel>
|
||||
|
||||
#include "InstrumentSoundShapingView.h"
|
||||
|
||||
#include <QLabel>
|
||||
#include <QBoxLayout>
|
||||
|
||||
#include "EnvelopeAndLfoParameters.h"
|
||||
#include "EnvelopeAndLfoView.h"
|
||||
#include "ComboBox.h"
|
||||
@@ -37,69 +39,54 @@
|
||||
namespace lmms::gui
|
||||
{
|
||||
|
||||
const int TARGETS_TABWIDGET_X = 4;
|
||||
const int TARGETS_TABWIDGET_Y = 5;
|
||||
const int TARGETS_TABWIDGET_WIDTH = 242;
|
||||
const int TARGETS_TABWIDGET_HEIGTH = 175;
|
||||
|
||||
const int FILTER_GROUPBOX_X = TARGETS_TABWIDGET_X;
|
||||
const int FILTER_GROUPBOX_Y = TARGETS_TABWIDGET_Y+TARGETS_TABWIDGET_HEIGTH+5;
|
||||
const int FILTER_GROUPBOX_WIDTH = TARGETS_TABWIDGET_WIDTH;
|
||||
const int FILTER_GROUPBOX_HEIGHT = 245-FILTER_GROUPBOX_Y;
|
||||
|
||||
|
||||
|
||||
InstrumentSoundShapingView::InstrumentSoundShapingView( QWidget * _parent ) :
|
||||
QWidget( _parent ),
|
||||
ModelView( nullptr, this ),
|
||||
m_ss( nullptr )
|
||||
InstrumentSoundShapingView::InstrumentSoundShapingView(QWidget* parent) :
|
||||
QWidget(parent),
|
||||
ModelView(nullptr, this)
|
||||
{
|
||||
m_targetsTabWidget = new TabWidget( tr( "TARGET" ), this );
|
||||
m_targetsTabWidget->setGeometry( TARGETS_TABWIDGET_X,
|
||||
TARGETS_TABWIDGET_Y,
|
||||
TARGETS_TABWIDGET_WIDTH,
|
||||
TARGETS_TABWIDGET_HEIGTH );
|
||||
QVBoxLayout* mainLayout = new QVBoxLayout(this);
|
||||
mainLayout->setContentsMargins(5, 5, 5, 5);
|
||||
|
||||
for( int i = 0; i < InstrumentSoundShaping::NumTargets; ++i )
|
||||
m_targetsTabWidget = new TabWidget(tr("TARGET"), this);
|
||||
|
||||
for (int i = 0; i < InstrumentSoundShaping::NumTargets; ++i)
|
||||
{
|
||||
m_envLfoViews[i] = new EnvelopeAndLfoView( m_targetsTabWidget );
|
||||
m_targetsTabWidget->addTab( m_envLfoViews[i],
|
||||
tr( InstrumentSoundShaping::targetNames[i][0] ),
|
||||
nullptr );
|
||||
m_envLfoViews[i] = new EnvelopeAndLfoView(m_targetsTabWidget);
|
||||
m_targetsTabWidget->addTab(m_envLfoViews[i],
|
||||
tr(InstrumentSoundShaping::targetNames[i][0]), nullptr);
|
||||
}
|
||||
|
||||
|
||||
m_filterGroupBox = new GroupBox( tr( "FILTER" ), this );
|
||||
m_filterGroupBox->setGeometry( FILTER_GROUPBOX_X, FILTER_GROUPBOX_Y,
|
||||
FILTER_GROUPBOX_WIDTH,
|
||||
FILTER_GROUPBOX_HEIGHT );
|
||||
mainLayout->addWidget(m_targetsTabWidget, 1);
|
||||
|
||||
|
||||
m_filterComboBox = new ComboBox( m_filterGroupBox );
|
||||
m_filterComboBox->setGeometry( 14, 22, 120, ComboBox::DEFAULT_HEIGHT );
|
||||
m_filterGroupBox = new GroupBox(tr("FILTER"), this);
|
||||
QHBoxLayout* filterLayout = new QHBoxLayout(m_filterGroupBox);
|
||||
QMargins filterMargins = filterLayout->contentsMargins();
|
||||
filterMargins.setTop(18);
|
||||
filterLayout->setContentsMargins(filterMargins);
|
||||
|
||||
m_filterComboBox = new ComboBox(m_filterGroupBox);
|
||||
filterLayout->addWidget(m_filterComboBox);
|
||||
|
||||
m_filterCutKnob = new Knob(KnobType::Bright26, m_filterGroupBox);
|
||||
m_filterCutKnob->setLabel(tr("FREQ"));
|
||||
m_filterCutKnob->setHintText(tr("Cutoff frequency:"), " " + tr("Hz"));
|
||||
filterLayout->addWidget(m_filterCutKnob);
|
||||
|
||||
m_filterResKnob = new Knob(KnobType::Bright26, m_filterGroupBox);
|
||||
m_filterResKnob->setLabel(tr("Q/RESO"));
|
||||
m_filterResKnob->setHintText(tr("Q/Resonance:"), "");
|
||||
filterLayout->addWidget(m_filterResKnob);
|
||||
|
||||
mainLayout->addWidget(m_filterGroupBox);
|
||||
|
||||
|
||||
m_filterCutKnob = new Knob( KnobType::Bright26, m_filterGroupBox );
|
||||
m_filterCutKnob->setLabel( tr( "FREQ" ) );
|
||||
m_filterCutKnob->move( 140, 18 );
|
||||
m_filterCutKnob->setHintText( tr( "Cutoff frequency:" ), " " + tr( "Hz" ) );
|
||||
|
||||
|
||||
m_filterResKnob = new Knob( KnobType::Bright26, m_filterGroupBox );
|
||||
m_filterResKnob->setLabel( tr( "Q/RESO" ) );
|
||||
m_filterResKnob->move( 196, 18 );
|
||||
m_filterResKnob->setHintText( tr( "Q/Resonance:" ), "" );
|
||||
|
||||
|
||||
m_singleStreamInfoLabel = new QLabel( tr( "Envelopes, LFOs and filters are not supported by the current instrument." ), this );
|
||||
m_singleStreamInfoLabel->setWordWrap( true );
|
||||
m_singleStreamInfoLabel = new QLabel(tr("Envelopes, LFOs and filters are not supported by the current instrument."), this);
|
||||
m_singleStreamInfoLabel->setWordWrap(true);
|
||||
// TODO Could also be rendered in system font size...
|
||||
m_singleStreamInfoLabel->setFont(adjustedToPixelSize(m_singleStreamInfoLabel->font(), 10));
|
||||
m_singleStreamInfoLabel->setFixedWidth(242);
|
||||
|
||||
m_singleStreamInfoLabel->setGeometry( TARGETS_TABWIDGET_X,
|
||||
TARGETS_TABWIDGET_Y,
|
||||
TARGETS_TABWIDGET_WIDTH,
|
||||
TARGETS_TABWIDGET_HEIGTH );
|
||||
mainLayout->addWidget(m_singleStreamInfoLabel, 0, Qt::AlignTop);
|
||||
}
|
||||
|
||||
|
||||
|
||||
168
src/gui/instrument/LfoGraph.cpp
Normal file
168
src/gui/instrument/LfoGraph.cpp
Normal file
@@ -0,0 +1,168 @@
|
||||
/*
|
||||
* LfoGraph.cpp - Displays LFO graphs
|
||||
*
|
||||
* Copyright (c) 2004-2014 Tobias Doerffel <tobydox/at/users.sourceforge.net>
|
||||
* 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.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "LfoGraph.h"
|
||||
|
||||
#include <QMouseEvent>
|
||||
#include <QPainter>
|
||||
|
||||
#include "EnvelopeAndLfoParameters.h"
|
||||
#include "Oscillator.h"
|
||||
|
||||
#include "gui_templates.h"
|
||||
|
||||
namespace lmms
|
||||
{
|
||||
|
||||
extern const float SECS_PER_LFO_OSCILLATION;
|
||||
|
||||
namespace gui
|
||||
{
|
||||
|
||||
LfoGraph::LfoGraph(QWidget* parent) :
|
||||
QWidget(parent),
|
||||
ModelView(nullptr, this)
|
||||
{
|
||||
setFixedSize(m_lfoGraph.size());
|
||||
}
|
||||
|
||||
void LfoGraph::modelChanged()
|
||||
{
|
||||
m_params = castModel<EnvelopeAndLfoParameters>();
|
||||
}
|
||||
|
||||
void LfoGraph::mousePressEvent(QMouseEvent* me)
|
||||
{
|
||||
if (me->button() == Qt::LeftButton)
|
||||
{
|
||||
toggleAmountModel();
|
||||
}
|
||||
}
|
||||
|
||||
void LfoGraph::paintEvent(QPaintEvent*)
|
||||
{
|
||||
QPainter p{this};
|
||||
p.setRenderHint(QPainter::Antialiasing);
|
||||
|
||||
// Draw the graph background
|
||||
p.drawPixmap(rect(), m_lfoGraph);
|
||||
|
||||
const auto* params = castModel<EnvelopeAndLfoParameters>();
|
||||
if (!params) { return; }
|
||||
|
||||
const float amount = params->getLfoAmountModel().value();
|
||||
const float lfoSpeed = params->getLfoSpeedModel().value();
|
||||
const f_cnt_t predelayFrames = params->getLfoPredelayFrames();
|
||||
const f_cnt_t attackFrames = params->getLfoAttackFrames();
|
||||
const f_cnt_t oscillationFrames = params->getLfoOscillationFrames();
|
||||
const bool x100 = params->getX100Model().value();
|
||||
const int waveModel = params->getLfoWaveModel().value();
|
||||
|
||||
int LFO_GRAPH_W = m_lfoGraph.width() - 3; // subtract border
|
||||
int LFO_GRAPH_H = m_lfoGraph.height() - 6; // subtract border
|
||||
int graph_x_base = 2;
|
||||
int graph_y_base = 3 + LFO_GRAPH_H / 2;
|
||||
|
||||
const float frames_for_graph =
|
||||
SECS_PER_LFO_OSCILLATION * Engine::audioEngine()->baseSampleRate() / 10;
|
||||
|
||||
const float gray = 1.0 - fabsf(amount);
|
||||
const auto red = static_cast<int>(96 * gray);
|
||||
const auto green = static_cast<int>(255 - 159 * gray);
|
||||
const auto blue = static_cast<int>(128 - 32 * gray);
|
||||
const QColor penColor(red, green, blue);
|
||||
p.setPen(QPen(penColor, 1.5));
|
||||
|
||||
float osc_frames = oscillationFrames;
|
||||
|
||||
if (x100) { osc_frames *= 100.0f; }
|
||||
|
||||
float old_y = 0;
|
||||
for (int x = 0; x <= LFO_GRAPH_W; ++x)
|
||||
{
|
||||
float val = 0.0;
|
||||
float cur_sample = x * frames_for_graph / LFO_GRAPH_W;
|
||||
if (static_cast<f_cnt_t>(cur_sample) > predelayFrames)
|
||||
{
|
||||
float phase = (cur_sample -= predelayFrames) / osc_frames;
|
||||
switch (static_cast<EnvelopeAndLfoParameters::LfoShape>(waveModel))
|
||||
{
|
||||
case EnvelopeAndLfoParameters::LfoShape::SineWave:
|
||||
default:
|
||||
val = Oscillator::sinSample(phase);
|
||||
break;
|
||||
case EnvelopeAndLfoParameters::LfoShape::TriangleWave:
|
||||
val = Oscillator::triangleSample(phase);
|
||||
break;
|
||||
case EnvelopeAndLfoParameters::LfoShape::SawWave:
|
||||
val = Oscillator::sawSample(phase);
|
||||
break;
|
||||
case EnvelopeAndLfoParameters::LfoShape::SquareWave:
|
||||
val = Oscillator::squareSample(phase);
|
||||
break;
|
||||
case EnvelopeAndLfoParameters::LfoShape::RandomWave:
|
||||
if (x % (int)(900 * lfoSpeed + 1) == 0)
|
||||
{
|
||||
m_randomGraph = Oscillator::noiseSample(0.0);
|
||||
}
|
||||
val = m_randomGraph;
|
||||
break;
|
||||
case EnvelopeAndLfoParameters::LfoShape::UserDefinedWave:
|
||||
val = Oscillator::userWaveSample(m_params->getLfoUserWave().get(), phase);
|
||||
break;
|
||||
}
|
||||
|
||||
if (static_cast<f_cnt_t>(cur_sample) <= attackFrames)
|
||||
{
|
||||
val *= cur_sample / attackFrames;
|
||||
}
|
||||
}
|
||||
|
||||
float cur_y = -LFO_GRAPH_H / 2.0f * val;
|
||||
p.drawLine(QLineF(graph_x_base + x - 1, graph_y_base + old_y, graph_x_base + x, graph_y_base + cur_y));
|
||||
old_y = cur_y;
|
||||
}
|
||||
|
||||
// Draw the info text
|
||||
int ms_per_osc = static_cast<int>(SECS_PER_LFO_OSCILLATION * lfoSpeed * 1000.0);
|
||||
|
||||
QFont f = p.font();
|
||||
f.setPixelSize(height() * 0.2);
|
||||
p.setFont(f);
|
||||
p.setPen(QColor(201, 201, 225));
|
||||
p.drawText(4, m_lfoGraph.height() - 6, tr("%1 ms/LFO").arg(ms_per_osc));
|
||||
}
|
||||
|
||||
void LfoGraph::toggleAmountModel()
|
||||
{
|
||||
auto* params = castModel<EnvelopeAndLfoParameters>();
|
||||
auto& lfoAmountModel = params->getLfoAmountModel();
|
||||
|
||||
lfoAmountModel.setValue(lfoAmountModel.value() < 1.0 ? 1.0 : 0.0);
|
||||
}
|
||||
|
||||
} // namespace gui
|
||||
|
||||
} // namespace lmms
|
||||
Reference in New Issue
Block a user