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:
Michael Gregorius
2024-04-05 11:25:39 +02:00
committed by GitHub
parent 20fec28bef
commit d447cb0648
10 changed files with 662 additions and 428 deletions

View File

@@ -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 );

View File

@@ -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
View 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

View File

@@ -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
View 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

View File

@@ -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

View File

@@ -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

View 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

View File

@@ -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);
}

View 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