diff --git a/include/EnvelopeAndLfoParameters.h b/include/EnvelopeAndLfoParameters.h index 2a8d3a685e6..50bfdf78709 100644 --- a/include/EnvelopeAndLfoParameters.h +++ b/include/EnvelopeAndLfoParameters.h @@ -71,7 +71,18 @@ class LMMS_EXPORT EnvelopeAndLfoParameters : public Model, public JournallingObj using LfoList = QList; 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 @@ class LMMS_EXPORT EnvelopeAndLfoParameters : public Model, public JournallingObj 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 getLfoUserWave() const { return m_userWave; } public slots: void updateSampleVars(); @@ -170,16 +203,6 @@ public slots: bool m_bad_lfoShapeData; std::shared_ptr m_userWave = SampleBuffer::emptyBuffer(); - enum class LfoShape - { - SineWave, - TriangleWave, - SawWave, - SquareWave, - UserDefinedWave, - RandomWave, - Count - } ; constexpr static auto NumLfoShapes = static_cast(LfoShape::Count); sample_t lfoShapeSample( fpp_t _frame_offset ); diff --git a/include/EnvelopeAndLfoView.h b/include/EnvelopeAndLfoView.h index d545aaa0687..0063dc78815 100644 --- a/include/EnvelopeAndLfoView.h +++ b/include/EnvelopeAndLfoView.h @@ -29,10 +29,6 @@ #include #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 @@ class EnvelopeAndLfoView : public QWidget, public ModelView 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 @@ protected slots: Knob * m_amountKnob; // LFO stuff + LfoGraph* m_lfoGraph; Knob * m_lfoPredelayKnob; Knob * m_lfoAttackKnob; TempoSyncKnob * m_lfoSpeedKnob; @@ -97,8 +91,6 @@ protected slots: LedCheckBox * m_x100Cb; LedCheckBox * m_controlEnvAmountCb; - - float m_randomGraph; } ; } // namespace gui diff --git a/include/EnvelopeGraph.h b/include/EnvelopeGraph.h new file mode 100644 index 00000000000..8cfeaf11f2d --- /dev/null +++ b/include/EnvelopeGraph.h @@ -0,0 +1,66 @@ +/* + * EnvelopeGraph.h - Displays envelope graphs + * + * Copyright (c) 2004-2009 Tobias Doerffel + * 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 + +#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 diff --git a/include/InstrumentSoundShapingView.h b/include/InstrumentSoundShapingView.h index 8f671514a89..c9caea28c37 100644 --- a/include/InstrumentSoundShapingView.h +++ b/include/InstrumentSoundShapingView.h @@ -56,7 +56,7 @@ class InstrumentSoundShapingView : public QWidget, public ModelView void modelChanged() override; - InstrumentSoundShaping * m_ss; + InstrumentSoundShaping * m_ss = nullptr; TabWidget * m_targetsTabWidget; EnvelopeAndLfoView * m_envLfoViews[InstrumentSoundShaping::NumTargets]; diff --git a/include/LfoGraph.h b/include/LfoGraph.h new file mode 100644 index 00000000000..733db3a349c --- /dev/null +++ b/include/LfoGraph.h @@ -0,0 +1,68 @@ +/* + * LfoGraph.h - Displays LFO graphs + * + * Copyright (c) 2004-2009 Tobias Doerffel + * 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 + +#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 diff --git a/src/gui/CMakeLists.txt b/src/gui/CMakeLists.txt index e2342b46548..791d6fbff1d 100644 --- a/src/gui/CMakeLists.txt +++ b/src/gui/CMakeLists.txt @@ -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 diff --git a/src/gui/instrument/EnvelopeAndLfoView.cpp b/src/gui/instrument/EnvelopeAndLfoView.cpp index 7f30f3ac197..c2e642b015e 100644 --- a/src/gui/instrument/EnvelopeAndLfoView.cpp +++ b/src/gui/instrument/EnvelopeAndLfoView.cpp @@ -23,202 +23,170 @@ * */ -#include -#include - #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" -namespace lmms -{ +#include -extern const float SECS_PER_ENV_SEGMENT; -extern const float SECS_PER_LFO_OSCILLATION; +namespace lmms +{ 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; + }; + + auto buildPixmapButton = [&](const QString& activePixmap, const QString& inactivePixmap) + { + auto button = new PixmapButton(this, nullptr); + button->setActiveGraphic(embed::getIconPixmap(activePixmap)); + button->setInactiveGraphic(embed::getIconPixmap(inactivePixmap)); - 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:" ), "" ); - + return button; + }; - 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:" ), "" ); + QVBoxLayout* mainLayout = new QVBoxLayout(this); + mainLayout->setContentsMargins(5, 5, 5, 5); + // Envelope - 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:" ), "" ); + QVBoxLayout* envelopeLayout = new QVBoxLayout(); + mainLayout->addLayout(envelopeLayout); + QHBoxLayout* graphAndAmountLayout = new QHBoxLayout(); + envelopeLayout->addLayout(graphAndAmountLayout); - 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:" ), "" ); + m_envelopeGraph = new EnvelopeGraph(this); + graphAndAmountLayout->addWidget(m_envelopeGraph); + m_amountKnob = buildKnob(tr("AMT"), tr("Modulation amount:")); + graphAndAmountLayout->addWidget(m_amountKnob, 0, Qt::AlignCenter); - 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* envKnobsLayout = new QHBoxLayout(); + envelopeLayout->addLayout(envKnobsLayout); + m_predelayKnob = buildKnob(tr("DEL"), tr("Pre-delay:")); + envKnobsLayout->addWidget(m_predelayKnob); - 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:" ), "" ); + m_attackKnob = buildKnob(tr("ATT"), tr("Attack:")); + envKnobsLayout->addWidget(m_attackKnob); + m_holdKnob = buildKnob(tr("HOLD"), tr("Hold:")); + envKnobsLayout->addWidget(m_holdKnob); - 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_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_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:" ), "" ); + // Add some space between the envelope and LFO section + mainLayout->addSpacing(10); - 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:" ), "" ); + // LFO + QHBoxLayout* lfoLayout = new QHBoxLayout(); + mainLayout->addLayout(lfoLayout); - 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:" ), "" ); + QVBoxLayout* graphAndTypesLayout = new QVBoxLayout(); + lfoLayout->addLayout(graphAndTypesLayout); + m_lfoGraph = new LfoGraph(this); + graphAndTypesLayout->addWidget(m_lfoGraph); - 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:" ), "" ); + QHBoxLayout* typesLayout = new QHBoxLayout(); + graphAndTypesLayout->addLayout(typesLayout); + typesLayout->setContentsMargins(0, 0, 0, 0); + typesLayout->setSpacing(0); - 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 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"); - 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" ) ); + connect(m_userLfoBtn, SIGNAL(toggled(bool)), this, SLOT(lfoUserWaveChanged())); - 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" ) ); + 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); - 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_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_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" ) ); + QVBoxLayout* knobsAndCheckBoxesLayout = new QVBoxLayout(); + lfoLayout->addLayout(knobsAndCheckBoxesLayout); - connect( m_userLfoBtn, SIGNAL(toggled(bool)), - this, SLOT(lfoUserWaveChanged())); + QHBoxLayout* lfoKnobsLayout = new QHBoxLayout(); + knobsAndCheckBoxesLayout->addLayout(lfoKnobsLayout); - 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_lfoPredelayKnob = buildKnob(tr("DEL"), tr("Pre-delay:")); + lfoKnobsLayout->addWidget(m_lfoPredelayKnob); - 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_lfoAttackKnob = buildKnob(tr("ATT"), tr("Attack:")); + lfoKnobsLayout->addWidget(m_lfoAttackKnob); - m_x100Cb = new LedCheckBox( tr( "FREQ x 100" ), this ); - m_x100Cb->move( LFO_PREDELAY_KNOB_X, LFO_GRAPH_Y + 36 ); - m_x100Cb->setToolTip(tr("Multiply LFO frequency by 100")); + m_lfoSpeedKnob = new TempoSyncKnob(KnobType::Bright26, this); + m_lfoSpeedKnob->setLabel(tr("SPD")); + m_lfoSpeedKnob->setHintText(tr("Frequency:"), ""); + lfoKnobsLayout->addWidget(m_lfoSpeedKnob); + m_lfoAmountKnob = buildKnob(tr("AMT"), tr("Modulation amount:")); + lfoKnobsLayout->addWidget(m_lfoAmountKnob); - 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" ) ); + QVBoxLayout* checkBoxesLayout = new QVBoxLayout(); + knobsAndCheckBoxesLayout->addLayout(checkBoxesLayout); + m_x100Cb = new LedCheckBox(tr("FREQ x 100"), this); + m_x100Cb->setToolTip(tr("Multiply LFO frequency by 100")); + checkBoxesLayout->addWidget(m_x100Cb); - setAcceptDrops( true ); + m_controlEnvAmountCb = new LedCheckBox(tr("MOD ENV AMOUNT"), this); + 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(); + 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() ); - - p.setPen( QPen( QColor( static_cast( 96 * gray_amount ), - static_cast( 255 - 159 * gray_amount ), - static_cast( 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( m_predelayKnob->value() * TIME_UNIT_WIDTH ); - int x2 = x1 + static_cast( m_attackKnob->value() * TIME_UNIT_WIDTH ); - int x3 = x2 + static_cast( m_holdKnob->value() * TIME_UNIT_WIDTH ); - int x4 = x3 + static_cast( ( m_decayKnob->value() * - ( 1 - m_sustainKnob->value() ) ) * TIME_UNIT_WIDTH ); - int x5 = x4 + static_cast( m_releaseKnob->value() * 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( y_base - - avail_height + - ( 1 - m_sustainKnob->value() ) * 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( y_base - avail_height + - ( 1 - m_sustainKnob->value() ) * - avail_height ), x5, y_base ); - p.fillRect( x4 - 1, static_cast( y_base - avail_height + - ( 1 - m_sustainKnob->value() ) * - avail_height ) - 2, 4, 4, - end_points_bg_color ); - p.fillRect( x4, static_cast( y_base - avail_height + - ( 1 - m_sustainKnob->value() ) * - 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() ); - p.setPen( QPen( QColor( static_cast( 96 * lfo_gray_amount ), - static_cast( 255 - 159 * lfo_gray_amount ), - static_cast( 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( cur_sample ) > - m_params->m_lfoPredelayFrames ) - { - float phase = ( cur_sample -= - m_params->m_lfoPredelayFrames ) / - osc_frames; - switch( static_cast(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() + 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( 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( SECS_PER_LFO_OSCILLATION * - m_lfoSpeedKnob->value() * - 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(m_params->m_lfoWaveModel.value()) == @@ -502,11 +278,6 @@ void EnvelopeAndLfoView::lfoUserWaveChanged() } } - - - - - } // namespace gui } // namespace lmms diff --git a/src/gui/instrument/EnvelopeGraph.cpp b/src/gui/instrument/EnvelopeGraph.cpp new file mode 100644 index 00000000000..4d866b3ccd1 --- /dev/null +++ b/src/gui/instrument/EnvelopeGraph.cpp @@ -0,0 +1,157 @@ +/* + * EnvelopeGraph.cpp - Displays envelope graphs + * + * Copyright (c) 2004-2014 Tobias Doerffel + * 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 +#include + +#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(); +} + +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(); + 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(96 * gray_amount), + static_cast(255 - 159 * gray_amount), + static_cast(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(predelay * TIME_UNIT_WIDTH); + int x2 = x1 + static_cast(attack * TIME_UNIT_WIDTH); + int x3 = x2 + static_cast(hold * TIME_UNIT_WIDTH); + int x4 = x3 + static_cast((decay * (1 - sustain)) * TIME_UNIT_WIDTH); + int x5 = x4 + static_cast(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(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(); + auto& amountModel = params->getAmountModel(); + + if (amountModel.value() < 1.0f ) + { + amountModel.setValue( 1.0f ); + } + else + { + amountModel.setValue( 0.0f ); + } +} + +} // namespace gui + +} // namespace lmms diff --git a/src/gui/instrument/InstrumentSoundShapingView.cpp b/src/gui/instrument/InstrumentSoundShapingView.cpp index b96db8495a9..a3a78e25670 100644 --- a/src/gui/instrument/InstrumentSoundShapingView.cpp +++ b/src/gui/instrument/InstrumentSoundShapingView.cpp @@ -22,9 +22,11 @@ * */ +#include "InstrumentSoundShapingView.h" + #include +#include -#include "InstrumentSoundShapingView.h" #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->move( 140, 18 ); - m_filterCutKnob->setHintText( tr( "Cutoff frequency:" ), " " + tr( "Hz" ) ); + 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); - 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:" ), "" ); + mainLayout->addWidget(m_filterGroupBox); - 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); } diff --git a/src/gui/instrument/LfoGraph.cpp b/src/gui/instrument/LfoGraph.cpp new file mode 100644 index 00000000000..d02f583d0c7 --- /dev/null +++ b/src/gui/instrument/LfoGraph.cpp @@ -0,0 +1,168 @@ +/* + * LfoGraph.cpp - Displays LFO graphs + * + * Copyright (c) 2004-2014 Tobias Doerffel + * 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 +#include + +#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(); +} + +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(); + 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(96 * gray); + const auto green = static_cast(255 - 159 * gray); + const auto blue = static_cast(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(cur_sample) > predelayFrames) + { + float phase = (cur_sample -= predelayFrames) / osc_frames; + switch (static_cast(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(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(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(); + auto& lfoAmountModel = params->getLfoAmountModel(); + + lfoAmountModel.setValue(lfoAmountModel.value() < 1.0 ? 1.0 : 0.0); +} + +} // namespace gui + +} // namespace lmms