code:
/*
 * EnvelopeAndLfoView.cpp - widget which is m_used by envelope/lfo/filter-
 *                          tab of instrument track window
 *
 * Copyright (c) 2004-2014 Tobias Doerffel <tobydox/at/users.sourceforge.net>
 *
 * 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 "EnvelopeAndLfoView.h"
#include "EnvelopeAndLfoParameters.h"
#include "embed.h"
#include "Engine.h"
#include "gui_templates.h"
#include "Knob.h"
#include "LedCheckbox.h"
#include "Mixer.h"
#include "DataFile.h"
#include "Oscillator.h"
#include "PixmapButton.h"
#include "StringPairDrag.h"
#include "TempoSyncKnob.h"
#include "TextFloat.h"
#include "ToolTip.h"
#include "Track.h"

extern const float SECS_PER_ENV_SEGMENT;
extern const float SECS_PER_LFO_OSCILLATION;

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;

QPixmap * EnvelopeAndLfoView::s_envGraph = NULL;
QPixmap * EnvelopeAndLfoView::s_lfoGraph = NULL;

EnvelopeAndLfoView::EnvelopeAndLfoView( QWidget * _parent ) :
	QWidget( _parent ),
	ModelView( NULL, this ),
	m_params( NULL )
{
	if( s_envGraph == NULL )
	{
		s_envGraph = new QPixmap( embed::getIconPixmap( "envelope_graph" ) );
	}
	if( s_lfoGraph == NULL )
	{
		s_lfoGraph = new QPixmap( embed::getIconPixmap( "lfo_graph" ) );
	}

	m_predelayKnob = new Knob( knobBright_26, this );
	m_predelayKnob->setLabel( tr( "DEL" ) );
	m_predelayKnob->move( PREDELAY_KNOB_X, ENV_KNOBS_Y );
	m_predelayKnob->setHintText( tr( "Pre-delay:" ), "" );

	m_attackKnob = new Knob( knobBright_26, this );
	m_attackKnob->setLabel( tr( "ATT" ) );
	m_attackKnob->move( ATTACK_KNOB_X, ENV_KNOBS_Y );
	m_attackKnob->setHintText( tr( "Attack:" ), "" );

	m_holdKnob = new Knob( knobBright_26, this );
	m_holdKnob->setLabel( tr( "HOLD" ) );
	m_holdKnob->move( HOLD_KNOB_X, ENV_KNOBS_Y );
	m_holdKnob->setHintText( tr( "Hold:" ), "" );

	m_decayKnob = new Knob( knobBright_26, this );
	m_decayKnob->setLabel( tr( "DEC" ) );
	m_decayKnob->move( DECAY_KNOB_X, ENV_KNOBS_Y );
	m_decayKnob->setHintText( tr( "Decay:" ), "" );

	m_sustainKnob = new Knob( knobBright_26, this );
	m_sustainKnob->setLabel( tr( "SUST" ) );
	m_sustainKnob->move( SUSTAIN_KNOB_X, ENV_KNOBS_Y );
	m_sustainKnob->setHintText( tr( "Sustain:" ), "" );

	m_releaseKnob = new Knob( knobBright_26, this );
	m_releaseKnob->setLabel( tr( "REL" ) );
	m_releaseKnob->move( RELEASE_KNOB_X, ENV_KNOBS_Y );
    m_releaseKnob->setHintText( tr( "Release:" ), "" );

	m_amountKnob = new Knob( knobBright_26, this );
	m_amountKnob->setLabel( tr( "AMT" ) );
	m_amountKnob->move( AMOUNT_KNOB_X, ENV_GRAPH_Y );
	m_amountKnob->setHintText( tr( "Modulation amount:" ), "" );

	m_lfoPredelayKnob = new Knob( knobBright_26, this );
	m_lfoPredelayKnob->setLabel( tr( "DEL" ) );
	m_lfoPredelayKnob->move( LFO_PREDELAY_KNOB_X, LFO_KNOB_Y );
	m_lfoPredelayKnob->setHintText( tr( "Pre-delay:" ), "" );

	m_lfoAttackKnob = new Knob( knobBright_26, this );
	m_lfoAttackKnob->setLabel( tr( "ATT" ) );
	m_lfoAttackKnob->move( LFO_ATTACK_KNOB_X, LFO_KNOB_Y );
	m_lfoAttackKnob->setHintText( tr( "Attack:" ), "" );

	m_lfoSpeedKnob = new TempoSyncKnob( knobBright_26, 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( knobBright_26, this );
	m_lfoAmountKnob->setLabel( tr( "AMT" ) );
	m_lfoAmountKnob->move( LFO_AMOUNT_KNOB_X, LFO_KNOB_Y );
	m_lfoAmountKnob->setHintText( tr( "Modulation amount:" ), "" );

	PixmapButton * sin_lfo_btn = new PixmapButton( this, NULL );
	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" ) );

	PixmapButton * triangle_lfo_btn = new PixmapButton( this, NULL );
	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" ) );

	PixmapButton * saw_lfo_btn = new PixmapButton( this, NULL );
	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" ) );

	PixmapButton * sqr_lfo_btn = new PixmapButton( this, NULL );
	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, NULL );
	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() ) );

	PixmapButton * random_lfo_btn = new PixmapButton( this, NULL );
	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->setFont( pointSizeF( m_x100Cb->font(), 6.5 ) );
	m_x100Cb->move( LFO_PREDELAY_KNOB_X, LFO_GRAPH_Y + 36 );
	ToolTip::add( m_x100Cb, tr( "Multiply LFO frequency by 100" ) );

	m_controlEnvAmountCb = new LedCheckBox( tr( "MODULATE ENV AMOUNT" ),
			this );
	m_controlEnvAmountCb->move( LFO_PREDELAY_KNOB_X, LFO_GRAPH_Y + 54 );
	m_controlEnvAmountCb->setFont( pointSizeF( m_controlEnvAmountCb->font(), 6.5 ) );
	ToolTip::add( m_controlEnvAmountCb,
				tr( "Control envelope amount by this LFO" ) );

	setAcceptDrops( true );

}

EnvelopeAndLfoView::~EnvelopeAndLfoView()
{
	delete m_lfoWaveBtnGrp;
}

void EnvelopeAndLfoView::modelChanged()
{
	m_params = castModel<EnvelopeAndLfoParameters>();
	m_predelayKnob->setModel( &m_params->m_predelayModel );
	m_attackKnob->setModel( &m_params->m_attackModel );
	m_holdKnob->setModel( &m_params->m_holdModel );
	m_decayKnob->setModel( &m_params->m_decayModel );
	m_sustainKnob->setModel( &m_params->m_sustainModel );
	m_releaseKnob->setModel( &m_params->m_releaseModel );
	m_amountKnob->setModel( &m_params->m_amountModel );
	m_lfoPredelayKnob->setModel( &m_params->m_lfoPredelayModel );
	m_lfoAttackKnob->setModel( &m_params->m_lfoAttackModel );
	m_lfoSpeedKnob->setModel( &m_params->m_lfoSpeedModel );
	m_lfoAmountKnob->setModel( &m_params->m_lfoAmountModel );
	m_lfoWaveBtnGrp->setModel( &m_params->m_lfoWaveModel );
	m_x100Cb->setModel( &m_params->m_x100Model );
	m_controlEnvAmountCb->setModel( &m_params->m_controlEnvAmountModel );
}

void EnvelopeAndLfoView::mousePressEvent( QMouseEvent * _me )
{
	if( _me->button() != Qt::LeftButton )
	{
		return;
	}

	if( QRect( ENV_GRAPH_X, ENV_GRAPH_Y, s_envGraph->width(),
			s_envGraph->height() ).contains( _me->pos() ) == true )
	{
		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, s_lfoGraph->width(),
			s_lfoGraph->height() ).contains( _me->pos() ) == true )
	{
		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,
					QString( "samplefile,tco_%1" ).arg(
							Track::SampleTrack ) );
}

void EnvelopeAndLfoView::dropEvent( QDropEvent * _de )
{
	QString type = StringPairDrag::decodeKey( _de );
	QString value = StringPairDrag::decodeValue( _de );
	if( type == "samplefile" )
	{
		m_params->m_userWave.setAudioFile(
					StringPairDrag::decodeValue( _de ) );
		m_userLfoBtn->model()->setValue( true );
		m_params->m_lfoWaveModel.setValue(EnvelopeAndLfoParameters::UserDefinedWave);
		_de->accept();
		update();
	}
	else if( type == QString( "tco_%1" ).arg( Track::SampleTrack ) )
	{
		DataFile dataFile( value.toUtf8() );
		m_params->m_userWave.setAudioFile( dataFile.content().
					firstChildElement().firstChildElement().
					firstChildElement().attribute( "src" ) );
		m_userLfoBtn->model()->setValue( true );
		m_params->m_lfoWaveModel.setValue(EnvelopeAndLfoParameters::UserDefinedWave);
		_de->accept();
		update();
	}
}

void EnvelopeAndLfoView::paintEvent( QPaintEvent * )
{
	QPainter p( this );
	p.setRenderHint( QPainter::Antialiasing );

	// draw envelope-graph
	p.drawPixmap( ENV_GRAPH_X, ENV_GRAPH_Y, *s_envGraph );
	// draw LFO-graph
	p.drawPixmap( LFO_GRAPH_X, LFO_GRAPH_Y, *s_lfoGraph );

	p.setFont( pointSize<8>( p.font() ) );

	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 + s_envGraph->height() - 3;
	const int avail_height = s_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 = s_lfoGraph->width() - 3;      // subtract border
	int LFO_GRAPH_H = s_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::mixer()->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;
	}

	// userWaveSample() may be used, called out of loop for efficiency
	m_params->m_userWave.dataReadLock();
	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( m_params->m_lfoWaveModel.value() )
			{
				case EnvelopeAndLfoParameters::SineWave:
					val = Oscillator::sinSample( phase );
					break;
				case EnvelopeAndLfoParameters::TriangleWave:
					val = Oscillator::triangleSample(
								phase );
					break;
				case EnvelopeAndLfoParameters::SawWave:
					val = Oscillator::sawSample( phase );
					break;
				case EnvelopeAndLfoParameters::SquareWave:
					val = Oscillator::squareSample( phase );
					break;
				case EnvelopeAndLfoParameters::RandomWave:
					if( x % (int)( 900 * m_lfoSpeedKnob->value<float>() + 1 ) == 0 )
					{
						m_randomGraph = Oscillator::noiseSample( 0.0f );
					}
					val = m_randomGraph;
					break;
				case EnvelopeAndLfoParameters::UserDefinedWave:
					val = m_params->m_userWave.
							userWaveSample( 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;
	}
	m_params->m_userWave.dataUnlock();

	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 + s_lfoGraph->height() - 6,
							tr( "ms/LFO:" ) );
	p.drawText( LFO_GRAPH_X + 52, LFO_GRAPH_Y + s_lfoGraph->height() - 6,
						QString::number( ms_per_osc ) );

}

void EnvelopeAndLfoView::lfoUserWaveChanged()
{
	if( m_params->m_lfoWaveModel.value() ==
				EnvelopeAndLfoParameters::UserDefinedWave )
	{
		if( m_params->m_userWave.frames() <= 1 )
		{
			TextFloat::displayMessage( tr( "Hint" ),
				tr( "Drag and drop a sample into this window." ),
					embed::getIconPixmap( "hint" ), 3000 );
		}
	}
}


code:
/*
 * FadeButton.cpp - implementation of fade-button
 *
 * Copyright (c) 2005-2009 Tobias Doerffel <tobydox/at/users.sourceforge.net>
 *
 * 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 <QTimer>
#include <QPainter>

#include "embed.h"
#include "FadeButton.h"

const float FadeDuration = 300;

FadeButton::FadeButton(const QColor & _normal_color,
		const QColor & _activated_color,
		const QColor & holdColor,
		QWidget * _parent) :
	QAbstractButton( _parent ),
	m_stateTimer(),
	m_releaseTimer(),
	m_normalColor( _normal_color ),
	m_activatedColor( _activated_color ),
	m_holdColor( holdColor )
{
	setAttribute(Qt::WA_OpaquePaintEvent, true);
	setCursor(QCursor(embed::getIconPixmap("hand"), 3, 3));
	setFocusPolicy(Qt::NoFocus);
	activeNotes = 0;
}

FadeButton::~FadeButton()
{
}

void FadeButton::setActiveColor(const QColor & activated_color)
{
	m_activatedColor = activated_color;
}

void FadeButton::activate()
{
	m_stateTimer.restart();
	activeNotes++;
	update();
}

void FadeButton::activateOnce()
{
	if (activeNotes == 0) { activate(); }
}

void FadeButton::noteEnd()
{
	if (activeNotes <= 0)
	{
		qWarning("noteEnd() triggered without a corresponding activate()!");
		activeNotes = 0;
	}
	else
	{
		activeNotes--;
	}

	if (activeNotes == 0)
	{
		m_releaseTimer.restart();
	}

	update();
}

void FadeButton::paintEvent(QPaintEvent * _pe)
{
	QColor col = m_normalColor;

	if(m_stateTimer.isValid() && m_stateTimer.elapsed() < FadeDuration)
	{
		// The first part of the fade, when a note is triggered.
		col = fadeToColor(m_activatedColor, m_holdColor, m_stateTimer, FadeDuration);
		QTimer::singleShot(20, this, SLOT(update()));
	}
	else if (m_stateTimer.isValid()
		&& m_stateTimer.elapsed() >= FadeDuration
		&& activeNotes > 0)
	{
		// The fade is done, but at least one note is still held.
		col = m_holdColor;
	}
	else if (m_releaseTimer.isValid() && m_releaseTimer.elapsed() < FadeDuration)
	{
		// Last note just ended. Fade to default color.
		col = fadeToColor(m_holdColor, m_normalColor, m_releaseTimer, FadeDuration);
		QTimer::singleShot(20, this, SLOT(update()));
	}
	else
	{
		// No fade, no notes. Set to default color.
		col = m_normalColor;
	}

	QPainter p(this);
	p.fillRect(rect(), col);

	int w = rect().right();
	int h = rect().bottom();
	p.setPen(m_normalColor.darker(130));
	p.drawLine(w, 1, w, h);
	p.drawLine(1, h, w, h);
	p.setPen(m_normalColor.lighter(130));
	p.drawLine(0, 0, 0, h-1);
	p.drawLine(0, 0, w, 0);
}

QColor FadeButton::fadeToColor(QColor startCol, QColor endCol, QElapsedTimer timer, float duration)
{
	QColor col;

	const float state = 1 - timer.elapsed() / duration;
	const int r = (int)(endCol.red() * (1.0f - state)
		+ startCol.red() * state);
	const int g = (int)(endCol.green() * (1.0f - state)
		+ startCol.green() * state);
	const int b = (int)(endCol.blue() * (1.0f - state)
		+ startCol.blue() * state);
	col.setRgb(r, g, b);

	return col;
}

repo_name: CYBERDEViL/LMMS

license: mit


code:
/*
 * Fader.cpp - fader-widget used in mixer - partly taken from Hydrogen
 *
 * Copyright (c) 2008-2012 Tobias Doerffel <tobydox/at/users.sourceforge.net>
 *
 * 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.
 *
 */

/*
 * Hydrogen
 * Copyright(c) 2002-2008 by Alex >Comix< Cominu [comix@users.sourceforge.net]
 *
 * http://www.hydrogen-music.org
 *
 * 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; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 */

#include "Fader.h"

#include <QInputDialog>
#include <QMouseEvent>
#include <QPainter>

#include "lmms_math.h"
#include "embed.h"
#include "CaptionMenu.h"
#include "ConfigManager.h"
#include "TextFloat.h"
#include "MainWindow.h"

TextFloat * Fader::s_textFloat = NULL;
QPixmap * Fader::s_back = NULL;
QPixmap * Fader::s_leds = NULL;
QPixmap * Fader::s_knob = NULL;

Fader::Fader( FloatModel * _model, const QString & _name, QWidget * _parent ) :
	QWidget( _parent ),
	FloatModelView( _model, this ),
	m_fPeakValue_L( 0.0 ),
	m_fPeakValue_R( 0.0 ),
	m_persistentPeak_L( 0.0 ),
	m_persistentPeak_R( 0.0 ),
	m_fMinPeak( 0.01f ),
	m_fMaxPeak( 1.1 ),
	m_levelsDisplayedInDBFS(false),
	m_moveStartPoint( -1 ),
	m_startValue( 0 ),
	m_peakGreen( 0, 0, 0 ),
	m_peakRed( 0, 0, 0 ),
	m_peakYellow( 0, 0, 0 )
{
	if( s_textFloat == NULL )
	{
		s_textFloat = new TextFloat;
	}
	if( ! s_back )
	{
		s_back = new QPixmap( embed::getIconPixmap( "fader_background" ) );
	}
	if( ! s_leds )
	{
		s_leds = new QPixmap( embed::getIconPixmap( "fader_leds" ) );
	}
	if( ! s_knob )
	{
		s_knob = new QPixmap( embed::getIconPixmap( "fader_knob" ) );
	}

	m_back = s_back;
	m_leds = s_leds;
	m_knob = s_knob;

	init(_model, _name);

	m_conversionFactor = 100.0;
}

Fader::Fader( FloatModel * model, const QString & name, QWidget * parent, QPixmap * back, QPixmap * leds, QPixmap * knob ) :
	QWidget( parent ),
	FloatModelView( model, this ),
	m_fPeakValue_L( 0.0 ),
	m_fPeakValue_R( 0.0 ),
	m_persistentPeak_L( 0.0 ),
	m_persistentPeak_R( 0.0 ),
	m_fMinPeak( 0.01f ),
	m_fMaxPeak( 1.1 ),
	m_levelsDisplayedInDBFS(false),
	m_moveStartPoint( -1 ),
	m_startValue( 0 ),
	m_peakGreen( 0, 0, 0 ),
	m_peakRed( 0, 0, 0 )
{
	if( s_textFloat == NULL )
	{
		s_textFloat = new TextFloat;
	}

	m_back = back;
	m_leds = leds;
	m_knob = knob;

	init(model, name);
}

void Fader::init(FloatModel * model, QString const & name)
{
	setWindowTitle( name );
	setAttribute( Qt::WA_OpaquePaintEvent, false );
	QSize backgroundSize = m_back->size();
	setMinimumSize( backgroundSize );
	setMaximumSize( backgroundSize );
	resize( backgroundSize );
	setModel( model );
	setHintText( "Volume:","%");
}

void Fader::contextMenuEvent( QContextMenuEvent * _ev )
{
	CaptionMenu contextMenu( windowTitle() );
	addDefaultActions( &contextMenu );
	contextMenu.exec( QCursor::pos() );
	_ev->accept();
}

void Fader::mouseMoveEvent( QMouseEvent *mouseEvent )
{
	if( m_moveStartPoint >= 0 )
	{
		int dy = m_moveStartPoint - mouseEvent->globalY();

		float delta = dy * ( model()->maxValue() - model()->minValue() ) / (float) ( height() - ( *m_knob ).height() );

		const float step = model()->step<float>();
		float newValue = static_cast<float>( static_cast<int>( ( m_startValue + delta ) / step + 0.5 ) ) * step;
		model()->setValue( newValue );

		updateTextFloat();
	}
}

void Fader::mousePressEvent( QMouseEvent* mouseEvent )
{
	if( mouseEvent->button() == Qt::LeftButton &&
			! ( mouseEvent->modifiers() & Qt::ControlModifier ) )
	{
		AutomatableModel *thisModel = model();
		if( thisModel )
		{
			thisModel->addJournalCheckPoint();
			thisModel->saveJournallingState( false );
		}

		if( mouseEvent->y() >= knobPosY() - ( *m_knob ).height() && mouseEvent->y() < knobPosY() )
		{
			updateTextFloat();
			s_textFloat->show();

			m_moveStartPoint = mouseEvent->globalY();
			m_startValue = model()->value();

			mouseEvent->accept();
		}
		else
		{
			m_moveStartPoint = -1;
		}
	}
	else
	{
		AutomatableModelView::mousePressEvent( mouseEvent );
	}
}

void Fader::mouseDoubleClickEvent( QMouseEvent* mouseEvent )
{
	bool ok;
	float newValue;
	// TODO: dbV handling
	newValue = QInputDialog::getDouble( this, tr( "Set value" ),
				tr( "Please enter a new value between %1 and %2:" ).
						arg( model()->minValue() * m_conversionFactor ).
						arg( model()->maxValue() * m_conversionFactor ),
					model()->getRoundedValue() * m_conversionFactor,
					model()->minValue() * m_conversionFactor,
					model()->maxValue() * m_conversionFactor, model()->getDigitCount(), &ok ) / m_conversionFactor;

	if( ok )
	{
		model()->setValue( newValue );
	}
}

void Fader::mouseReleaseEvent( QMouseEvent * mouseEvent )
{
	if( mouseEvent && mouseEvent->button() == Qt::LeftButton )
	{
		AutomatableModel *thisModel = model();
		if( thisModel )
		{
			thisModel->restoreJournallingState();
		}
	}

	s_textFloat->hide();
}

void Fader::wheelEvent ( QWheelEvent *ev )
{
	ev->accept();

	if (ev->angleDelta().y() > 0)
	{
		model()->incValue( 1 );
	}
	else
	{
		model()->incValue( -1 );
	}
	updateTextFloat();
	s_textFloat->setVisibilityTimeOut( 1000 );
}

///
/// Set peak value (0.0 .. 1.0)
///
void Fader::setPeak( float fPeak, float &targetPeak, float &persistentPeak, QElapsedTimer &lastPeakTimer )
{
	if( fPeak <  m_fMinPeak )
	{
		fPeak = m_fMinPeak;
	}
	else if( fPeak > m_fMaxPeak )
	{
		fPeak = m_fMaxPeak;
	}

	if( targetPeak != fPeak)
	{
		targetPeak = fPeak;
		if( targetPeak >= persistentPeak )
		{
			persistentPeak = targetPeak;
			lastPeakTimer.restart();
		}
		update();
	}

	if( persistentPeak > 0 && lastPeakTimer.elapsed() > 1500 )
	{
		persistentPeak = qMax<float>( 0, persistentPeak-0.05 );
		update();
	}
}

void Fader::setPeak_L( float fPeak )
{
	setPeak( fPeak, m_fPeakValue_L, m_persistentPeak_L, m_lastPeakTimer_L );
}

void Fader::setPeak_R( float fPeak )
{
	setPeak( fPeak, m_fPeakValue_R, m_persistentPeak_R, m_lastPeakTimer_R );
}

// update tooltip showing value and adjust position while changing fader value
void Fader::updateTextFloat()
{
	if( ConfigManager::inst()->value( "app", "displaydbfs" ).toInt() && m_conversionFactor == 100.0 )
	{
		s_textFloat->setText( QString("Volume: %1 dBFS").
				arg( ampToDbfs( model()->value() ), 3, 'f', 2 ) );
	}
	else
	{
		s_textFloat->setText( m_description + " " + QString("%1 ").arg( model()->value() * m_conversionFactor ) + " " + m_unit );
	}
	s_textFloat->moveGlobal( this, QPoint( width() - ( *m_knob ).width() - 5, knobPosY() - 46 ) );
}

inline int Fader::calculateDisplayPeak( float fPeak )
{
	int peak = (int)( m_back->height() - ( fPeak / ( m_fMaxPeak - m_fMinPeak ) ) * m_back->height() );

	return qMin( peak, m_back->height() );
}

void Fader::paintEvent( QPaintEvent * ev)
{
	QPainter painter(this);

	// Draw the background
	painter.drawPixmap( ev->rect(), *m_back, ev->rect() );

	// Draw the levels with peaks
	if (getLevelsDisplayedInDBFS())
	{
		paintDBFSLevels(ev, painter);
	}
	else
	{
		paintLinearLevels(ev, painter);
	}

	// Draw the knob
	painter.drawPixmap( 0, knobPosY() - m_knob->height(), *m_knob );
}

void Fader::paintDBFSLevels(QPaintEvent * ev, QPainter & painter)
{
	int height = m_back->height();
	int width = m_back->width() / 2;
	int center = m_back->width() - width;

	float const maxDB(ampToDbfs(m_fMaxPeak));
	float const minDB(ampToDbfs(m_fMinPeak));

	// We will need to divide by the span between min and max several times. It's more
	// efficient to calculate the reciprocal once and then to multiply.
	float const fullSpanReciprocal = 1 / (maxDB - minDB);

	// Draw left levels
	float const leftSpan = ampToDbfs(qMax<float>(0.0001, m_fPeakValue_L)) - minDB;
	int peak_L = height * leftSpan * fullSpanReciprocal;
	QRect drawRectL( 0, height - peak_L, width, peak_L ); // Source and target are identical
	painter.drawPixmap( drawRectL, *m_leds, drawRectL );

	float const persistentLeftPeakDBFS = ampToDbfs(qMax<float>(0.0001, m_persistentPeak_L));
	int persistentPeak_L = height * (1 - (persistentLeftPeakDBFS - minDB) * fullSpanReciprocal);
	// the LED's have a  4px padding and we don't want the peaks
	// to draw on the fader background
	if( persistentPeak_L <= 4 )
	{
		persistentPeak_L = 4;
	}
	if( persistentLeftPeakDBFS > minDB )
	{
		QColor const & peakColor = clips(m_persistentPeak_L) ? peakRed() :
			persistentLeftPeakDBFS >= -6 ? peakYellow() : peakGreen();
		painter.fillRect( QRect( 2, persistentPeak_L, 7, 1 ), peakColor );
	}

	// Draw right levels
	float const rightSpan = ampToDbfs(qMax<float>(0.0001, m_fPeakValue_R)) - minDB;
	int peak_R = height * rightSpan * fullSpanReciprocal;
	QRect const drawRectR( center, height - peak_R, width, peak_R ); // Source and target are identical
	painter.drawPixmap( drawRectR, *m_leds, drawRectR );

	float const persistentRightPeakDBFS = ampToDbfs(qMax<float>(0.0001, m_persistentPeak_R));
	int persistentPeak_R = height * (1 - (persistentRightPeakDBFS - minDB) * fullSpanReciprocal);
	// the LED's have a  4px padding and we don't want the peaks
	// to draw on the fader background
	if( persistentPeak_R <= 4 )
	{
		persistentPeak_R = 4;
	}
	if( persistentRightPeakDBFS > minDB )
	{
		QColor const & peakColor = clips(m_persistentPeak_R) ? peakRed() :
			persistentRightPeakDBFS >= -6 ? peakYellow() : peakGreen();
		painter.fillRect( QRect( 14, persistentPeak_R, 7, 1 ), peakColor );
	}
}

void Fader::paintLinearLevels(QPaintEvent * ev, QPainter & painter)
{
	// peak leds
	//float fRange = abs( m_fMaxPeak ) + abs( m_fMinPeak );

	int height = m_back->height();
	int width = m_back->width() / 2;
	int center = m_back->width() - width;

	int peak_L = calculateDisplayPeak( m_fPeakValue_L - m_fMinPeak );
	int persistentPeak_L = qMax<int>( 3, calculateDisplayPeak( m_persistentPeak_L - m_fMinPeak ) );
	painter.drawPixmap( QRect( 0, peak_L, width, height - peak_L ), *m_leds, QRect( 0, peak_L, width, height - peak_L ) );

	if( m_persistentPeak_L > 0.05 )
	{
		painter.fillRect( QRect( 2, persistentPeak_L, 7, 1 ), ( m_persistentPeak_L < 1.0 )
			? peakGreen()
			: peakRed() );
	}

	int peak_R = calculateDisplayPeak( m_fPeakValue_R - m_fMinPeak );
	int persistentPeak_R = qMax<int>( 3, calculateDisplayPeak( m_persistentPeak_R - m_fMinPeak ) );
	painter.drawPixmap( QRect( center, peak_R, width, height - peak_R ), *m_leds, QRect( center, peak_R, width, height - peak_R ) );

	if( m_persistentPeak_R > 0.05 )
	{
		painter.fillRect( QRect( 14, persistentPeak_R, 7, 1 ), ( m_persistentPeak_R < 1.0 )
			? peakGreen()
			: peakRed() );
	}
}

QColor const & Fader::peakGreen() const
{
	return m_peakGreen;
}

QColor const & Fader::peakRed() const
{
	return m_peakRed;
}

QColor const & Fader::peakYellow() const
{
	return m_peakYellow;
}

void Fader::setPeakGreen( const QColor & c )
{
	m_peakGreen = c;
}

void Fader::setPeakRed( const QColor & c )
{
	m_peakRed = c;
}

void Fader::setPeakYellow( const QColor & c )
{
	m_peakYellow = c;
}


code:
/*
 * FxLine.cpp - FX line widget
 *
 * Copyright (c) 2009 Andrew Kelley <superjoe30/at/gmail/dot/com>
 * Copyright (c) 2014 Tobias Doerffel <tobydox/at/users.sourceforge.net>
 *
 * 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 "FxLine.h"

#include <cstdlib>

#include <QGraphicsProxyWidget>

#include "CaptionMenu.h"
#include "FxMixer.h"
#include "gui_templates.h"
#include "GuiApplication.h"
#include "Song.h"

bool FxLine::eventFilter( QObject *dist, QEvent *event )
{
	// If we are in a rename, capture the enter/return events and handle them
	if ( event->type() == QEvent::KeyPress )
	{
		QKeyEvent * keyEvent = static_cast<QKeyEvent*>(event);
		if( keyEvent->key() == Qt::Key_Enter || keyEvent->key() == Qt::Key_Return )
		{
			if( m_inRename )
			{
				renameFinished();
				event->accept(); // Stop the event from propagating
				return true;
			}
		}
	}
	return false;
}

const int FxLine::FxLineHeight = 287;
QPixmap * FxLine::s_sendBgArrow = NULL;
QPixmap * FxLine::s_receiveBgArrow = NULL;

FxLine::FxLine( QWidget * _parent, FxMixerView * _mv, int _channelIndex ) :
	QWidget( _parent ),
	m_mv( _mv ),
	m_channelIndex( _channelIndex ),
	m_backgroundActive( Qt::SolidPattern ),
	m_strokeOuterActive( 0, 0, 0 ),
	m_strokeOuterInactive( 0, 0, 0 ),
	m_strokeInnerActive( 0, 0, 0 ),
	m_strokeInnerInactive( 0, 0, 0 ),
	m_inRename( false )
{
	if( !s_sendBgArrow )
	{
		s_sendBgArrow = new QPixmap( embed::getIconPixmap( "send_bg_arrow", 29, 56 ) );
	}
	if( !s_receiveBgArrow )
	{
		s_receiveBgArrow = new QPixmap( embed::getIconPixmap( "receive_bg_arrow", 29, 56 ) );
	}

	setFixedSize( 33, FxLineHeight );
	setAttribute( Qt::WA_OpaquePaintEvent, true );
	setCursor( QCursor( embed::getIconPixmap( "hand" ), 3, 3 ) );

	// mixer sends knob
	m_sendKnob = new Knob( knobBright_26, this, tr( "Channel send amount" ) );
	m_sendKnob->move( 3, 22 );
	m_sendKnob->setVisible( false );

	// send button indicator
	m_sendBtn = new SendButtonIndicator( this, this, m_mv );
	m_sendBtn->move( 2, 2 );

	// channel number
	m_lcd = new LcdWidget( 2, this );
	m_lcd->setValue( m_channelIndex );
	m_lcd->move( 4, 58 );
	m_lcd->setMarginWidth( 1 );

	QString name = Engine::fxMixer()->effectChannel( m_channelIndex )->m_name;
	setToolTip( name );

	m_renameLineEdit = new QLineEdit();
	m_renameLineEdit->setText( name );
	m_renameLineEdit->setFixedWidth( 65 );
	m_renameLineEdit->setFont( pointSizeF( font(), 7.5f ) );
	m_renameLineEdit->setReadOnly( true );
	m_renameLineEdit->installEventFilter( this );

	QGraphicsScene * scene = new QGraphicsScene();
	scene->setSceneRect( 0, 0, 33, FxLineHeight );

	m_view = new QGraphicsView( this );
	m_view->setStyleSheet( "border-style: none; background: transparent;" );
	m_view->setHorizontalScrollBarPolicy( Qt::ScrollBarAlwaysOff );
	m_view->setVerticalScrollBarPolicy( Qt::ScrollBarAlwaysOff );
	m_view->setAttribute( Qt::WA_TransparentForMouseEvents, true );
	m_view->setScene( scene );

	QGraphicsProxyWidget * proxyWidget = scene->addWidget( m_renameLineEdit );
	proxyWidget->setRotation( -90 );
	proxyWidget->setPos( 8, 145 );

	connect( m_renameLineEdit, SIGNAL( editingFinished() ), this, SLOT( renameFinished() ) );
	connect( &Engine::fxMixer()->effectChannel( m_channelIndex )->m_muteModel, SIGNAL( dataChanged() ), this, SLOT( update() ) );

}

FxLine::~FxLine()
{
	delete m_sendKnob;
	delete m_sendBtn;
	delete m_lcd;
}

void FxLine::setChannelIndex( int index )
{
	m_channelIndex = index;
	m_lcd->setValue( m_channelIndex );
	m_lcd->update();
}

void FxLine::drawFxLine( QPainter* p, const FxLine *fxLine, bool isActive, bool sendToThis, bool receiveFromThis )
{
	auto channel = Engine::fxMixer()->effectChannel( m_channelIndex );
	bool muted = channel->m_muteModel.value();
	QString name = channel->m_name;
	QString elidedName = elideName( name );
	if( !m_inRename && m_renameLineEdit->text() != elidedName )
	{
		m_renameLineEdit->setText( elidedName );
	}

	int width = fxLine->rect().width();
	int height = fxLine->rect().height();

	if( channel->m_hasColor && !muted )
	{
		p->fillRect( fxLine->rect(), channel->m_color.darker( isActive ? 120 : 150 ) );
	}
	else
	{
		p->fillRect( fxLine->rect(),
					 isActive ? fxLine->backgroundActive().color() : p->background().color() );
	}

	// inner border
	p->setPen( isActive ? fxLine->strokeInnerActive() : fxLine->strokeInnerInactive() );
	p->drawRect( 1, 1, width-3, height-3 );

	// outer border
	p->setPen( isActive ? fxLine->strokeOuterActive() : fxLine->strokeOuterInactive() );
	p->drawRect( 0, 0, width-1, height-1 );

	// draw the mixer send background
	if( sendToThis )
	{
		p->drawPixmap( 2, 0, 29, 56, *s_sendBgArrow );
	}
	else if( receiveFromThis )
	{
		p->drawPixmap( 2, 0, 29, 56, *s_receiveBgArrow );
	}
}

QString FxLine::elideName( const QString & name )
{
	const int maxTextHeight = 60;
	QFontMetrics metrics( m_renameLineEdit->font() );
	QString elidedName = metrics.elidedText( name, Qt::ElideRight, maxTextHeight );
	return elidedName;
}

void FxLine::paintEvent( QPaintEvent * )
{
	bool sendToThis = Engine::fxMixer()->channelSendModel( m_mv->currentFxLine()->m_channelIndex, m_channelIndex ) != NULL;
	bool receiveFromThis = Engine::fxMixer()->channelSendModel( m_channelIndex, m_mv->currentFxLine()->m_channelIndex ) != NULL;
	QPainter painter;
	painter.begin( this );
	drawFxLine( &painter, this, m_mv->currentFxLine() == this, sendToThis, receiveFromThis );
	painter.end();
}

void FxLine::mousePressEvent( QMouseEvent * )
{
	m_mv->setCurrentFxLine( this );
}

void FxLine::mouseDoubleClickEvent( QMouseEvent * )
{
	renameChannel();
}

void FxLine::contextMenuEvent( QContextMenuEvent * )
{
	QPointer<CaptionMenu> contextMenu = new CaptionMenu( Engine::fxMixer()->effectChannel( m_channelIndex )->m_name, this );
	if( m_channelIndex != 0 ) // no move-options in master
	{
		contextMenu->addAction( tr( "Move &left" ),	this, SLOT( moveChannelLeft() ) );
		contextMenu->addAction( tr( "Move &right" ), this, SLOT( moveChannelRight() ) );
	}
	contextMenu->addAction( tr( "Rename &channel" ), this, SLOT( renameChannel() ) );
	contextMenu->addSeparator();

	if( m_channelIndex != 0 ) // no remove-option in master
	{
		contextMenu->addAction( embed::getIconPixmap( "cancel" ), tr( "R&emove channel" ), this, SLOT( removeChannel() ) );
		contextMenu->addSeparator();
	}
	contextMenu->addAction( embed::getIconPixmap( "cancel" ), tr( "Remove &unused channels" ), this, SLOT( removeUnusedChannels() ) );
	contextMenu->addSeparator();
	contextMenu->addAction( embed::getIconPixmap( "colorize" ), tr( "Set channel color" ), this, SLOT( changeColor() ) );
	contextMenu->addAction( embed::getIconPixmap( "colorize" ), tr( "Remove channel color" ), this, SLOT( resetColor() ) );
	contextMenu->addAction( embed::getIconPixmap( "colorize" ), tr( "Pick random channel color" ), this, SLOT( randomColor() ) );
	contextMenu->exec( QCursor::pos() );
	delete contextMenu;
}

void FxLine::renameChannel()
{
	m_inRename = true;
	setToolTip( "" );
	m_renameLineEdit->setReadOnly( false );
	m_lcd->hide();
	m_renameLineEdit->setFixedWidth( 135 );
	m_renameLineEdit->setText( Engine::fxMixer()->effectChannel( m_channelIndex )->m_name );
	m_view->setFocus();
	m_renameLineEdit->selectAll();
	m_renameLineEdit->setFocus();
}

void FxLine::renameFinished()
{
	m_inRename = false;
	m_renameLineEdit->deselect();
	m_renameLineEdit->setReadOnly( true );
	m_renameLineEdit->setFixedWidth( 65 );
	m_lcd->show();
	QString newName = m_renameLineEdit->text();
	setFocus();
	if( !newName.isEmpty() && Engine::fxMixer()->effectChannel( m_channelIndex )->m_name != newName )
	{
		Engine::fxMixer()->effectChannel( m_channelIndex )->m_name = newName;
		m_renameLineEdit->setText( elideName( newName ) );
		Engine::getSong()->setModified();
	}
	QString name = Engine::fxMixer()->effectChannel( m_channelIndex )->m_name;
	setToolTip( name );
}

void FxLine::removeChannel()
{
	FxMixerView * mix = gui->fxMixerView();
	mix->deleteChannel( m_channelIndex );
}

void FxLine::removeUnusedChannels()
{
	FxMixerView * mix = gui->fxMixerView();
	mix->deleteUnusedChannels();
}

void FxLine::moveChannelLeft()
{
	FxMixerView * mix = gui->fxMixerView();
	mix->moveChannelLeft( m_channelIndex );
}

void FxLine::moveChannelRight()
{
	FxMixerView * mix = gui->fxMixerView();
	mix->moveChannelRight( m_channelIndex );
}

QBrush FxLine::backgroundActive() const
{
	return m_backgroundActive;
}

void FxLine::setBackgroundActive( const QBrush & c )
{
	m_backgroundActive = c;
}

QColor FxLine::strokeOuterActive() const
{
	return m_strokeOuterActive;
}

void FxLine::setStrokeOuterActive( const QColor & c )
{
	m_strokeOuterActive = c;
}

QColor FxLine::strokeOuterInactive() const
{
	return m_strokeOuterInactive;
}

void FxLine::setStrokeOuterInactive( const QColor & c )
{
	m_strokeOuterInactive = c;
}

QColor FxLine::strokeInnerActive() const
{
	return m_strokeInnerActive;
}

void FxLine::setStrokeInnerActive( const QColor & c )
{
	m_strokeInnerActive = c;
}

QColor FxLine::strokeInnerInactive() const
{
	return m_strokeInnerInactive;
}

void FxLine::setStrokeInnerInactive( const QColor & c )
{
	m_strokeInnerInactive = c;
}

// Ask user for a color, and set it as the mixer line color
void FxLine::changeColor()
{
	auto channel = Engine::fxMixer()->effectChannel( m_channelIndex );
	auto new_color = ColorChooser(this).withPalette(ColorChooser::Palette::Mixer)->getColor(channel->m_color);
	if(!new_color.isValid()) { return; }
	channel->setColor (new_color);
	update();
}

// Disable the usage of color on this mixer line
void FxLine::resetColor()
{
	Engine::fxMixer()->effectChannel( m_channelIndex )->m_hasColor = false;
	update();
}

// Pick a random color from the mixer palette and set it as our color
void FxLine::randomColor()
{
	auto channel = Engine::fxMixer()->effectChannel( m_channelIndex );
	channel->setColor (ColorChooser::getPalette(ColorChooser::Palette::Mixer)[rand() % 48]);
	update();
}


code:
/*
 * FxLineLcdSpinBox.cpp - a specialization of LcdSpnBox for setting FX channels
 *
 * Copyright (c) 2004-2014 Tobias Doerffel <tobydox/at/users.sourceforge.net>
 *
 * 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 "FxLineLcdSpinBox.h"

#include "CaptionMenu.h"
#include "FxMixerView.h"
#include "GuiApplication.h"
#include "TrackView.h"

void FxLineLcdSpinBox::setTrackView(TrackView * tv)
{
	m_tv = tv;
}

void FxLineLcdSpinBox::mouseDoubleClickEvent(QMouseEvent* event)
{
	gui->fxMixerView()->setCurrentFxLine(model()->value());

	gui->fxMixerView()->parentWidget()->show();
	gui->fxMixerView()->show();// show fxMixer window
	gui->fxMixerView()->setFocus();// set focus to fxMixer window
	//engine::getFxMixerView()->raise();
}

void FxLineLcdSpinBox::contextMenuEvent(QContextMenuEvent* event)
{
	// for the case, the user clicked right while pressing left mouse-
	// button, the context-menu appears while mouse-cursor is still hidden
	// and it isn't shown again until user does something which causes
	// an QApplication::restoreOverrideCursor()-call...
	mouseReleaseEvent(nullptr);

	QPointer<CaptionMenu> contextMenu = new CaptionMenu(model()->displayName(), this);

	if (QMenu *fxMenu = m_tv->createFxMenu(
		tr("Assign to:"), tr("New FX Channel")))
	{
		contextMenu->addMenu(fxMenu);

		contextMenu->addSeparator();
	}
	addDefaultActions(contextMenu);
	contextMenu->exec(QCursor::pos());
}


code:
/*
 * Graph.cpp - a QT widget for displaying and manipulating waveforms
 *
 * Copyright (c) 2006-2007 Andreas Brandmaier <andy/at/brandmaier/dot/de>
 *               2008 Paul Giblock            <drfaygo/at/gmail/dot/com>
 *
 * 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 <QPaintEvent>
#include <QFontMetrics>
#include <QPainter>

#include "Graph.h"
#include "StringPairDrag.h"
#include "SampleBuffer.h"
#include "Oscillator.h"

Graph::Graph( QWidget * _parent, graphStyle _style, int _width,
		int _height ) :
	QWidget( _parent ),
	/* TODO: size, background? */
	ModelView( new graphModel( -1.0, 1.0, 128, NULL, true ), this ),
	m_graphStyle( _style )
{
	m_mouseDown = false;
	m_graphColor = QColor( 0xFF, 0xAA, 0x00 );

	resize( _width, _height );
	setAcceptDrops( true );
	setCursor( Qt::CrossCursor );

	graphModel * gModel = castModel<graphModel>();

	QObject::connect( gModel, SIGNAL( samplesChanged( int, int ) ),
			this, SLOT( updateGraph( int, int ) ) );

	QObject::connect( gModel, SIGNAL( lengthChanged( ) ),
			this, SLOT( updateGraph( ) ) );
}

void Graph::setForeground( const QPixmap &_pixmap )
{
	m_foreground = _pixmap;
}

void Graph::setGraphColor( QColor _graphcol )
{
	m_graphColor = _graphcol;
}

/*
void graph::loadSampleFromFile( const QString & _filename )
{

	int i;

	// zero sample_shape
	for( i = 0; i < sampleLength; i++ )
	{
		samplePointer[i] = 0;
	}

	// load user shape
	sampleBuffer buffer( _filename );

	// copy buffer data
	int trimSize = fmin( size(), static_cast<int>(buffer.frames()) );

	for( i = 0; i < trimSize; i++ )
	{
		samplePointer[i] = (float)*buffer.data()[i];
	}
}
*/

void Graph::mouseMoveEvent ( QMouseEvent * _me )
{
	// get position
	int x = _me->x();
	int y = _me->y();

/*	static bool skip = false;

	if( skip )
	{
		skip = false;
		return;
	}
*/
	// avoid mouse leaps
	int diff = x - m_lastCursorX;

/*	if( diff >= 1 )
	{
		x = qMin( width() - 3, m_lastCursorX + 1 );
	}
	else if( diff <= -1 )
	{
		x = qMax( 2, m_lastCursorX - 1 );
	}*/

	x = qMax( 2, qMin( x, width()-3 ) );
	y = qMax( 2, qMin( y, height()-3 ) );

	drawLineAt( x, y, m_lastCursorX );

	// update mouse
	if( diff != 0 )
	{
		m_lastCursorX = x;

		QPoint pt = mapToGlobal( QPoint( x, y ) );

		QCursor::setPos( pt.x(), pt.y() );
	}

//	skip = true;
}

void Graph::mousePressEvent( QMouseEvent * _me )
{
	if( _me->button() == Qt::LeftButton )
	{
		if ( !( _me->modifiers() & Qt::ShiftModifier ) )
		{
			// get position
			int x = _me->x();
			int y = _me->y();

			changeSampleAt( x, y );

			// toggle mouse state
			m_mouseDown = true;
			setCursor( Qt::BlankCursor );
			m_lastCursorX = x;
		}
		else
		{
			//when shift-clicking, draw a line from last position to current
			//position
			int x = _me->x();
			int y = _me->y();

			drawLineAt( x, y, m_lastCursorX );

			m_mouseDown = true;
			setCursor( Qt::BlankCursor );
			m_lastCursorX = x;
		}

	}
}

void Graph::drawLineAt( int _x, int _y, int _lastx )
{
	float minVal = model()->minValue();
	float maxVal = model()->maxValue();
	if ( width() <= 4 )
	{
		return;
	}

	float xscale = static_cast<float>( model()->length() ) /
					( width()-4 );

	//consider border
	_x -= 2;
	_y -= 2;
	_lastx -= 2;

	_lastx = qMax( 0, qMin( _lastx, width()-5 ) );

	float range = minVal - maxVal;
	float val = ( _y*range/( height()-5 ) ) + maxVal;

	int sample_begin, sample_end;
	float lastval;
	float val_begin, val_end;

	if (_lastx > _x)
	{
		sample_begin = (int)((_x) * xscale);
		sample_end = (int)ceil((_lastx+1) * xscale);
		lastval = model() -> m_samples[ (int)( sample_end - 1 ) ];
		val_begin = val;
		val_end = lastval;

	}
	else
	{
		sample_begin = (int)(_lastx * xscale);
		sample_end =  (int)ceil((_x+1) * xscale);
		lastval = model() -> m_samples[ (int)( sample_begin ) ];
		val_begin = lastval;
		val_end = val;

	}

	// calculate line drawing variables
	int linelen = sample_end - sample_begin;
	if (linelen == 1)
	{
		val_begin = val;
	}
	//int xstep = _x > _lastx ? -1 : 1;
	float ystep = ( val_end - val_begin ) / linelen;

	// draw a line
	for ( int i = 0 ; i < linelen; i++ )
	{
		model()->drawSampleAt( sample_begin + i , val_begin + ((i ) * ystep));
	}

	// We've changed [sample_end, sample_begin)
	// However, samplesChanged expects two end points
	model()->samplesChanged(sample_begin, sample_end - 1);
}

void Graph::changeSampleAt( int _x, int _y )
{
	float minVal = model()->minValue();
	float maxVal = model()->maxValue();

	if ( width() <= 4 )
	{
		return;
	}

	float xscale = static_cast<float>( model()->length() ) /
					( width()-4 );

	// consider border of background image
	_x -= 2;
	_y -= 2;

	// subtract max from min because Qt's Y-axis is backwards
	float range = minVal - maxVal;
	float val = ( _y*range/( height()-5 ) ) + maxVal;

	model()->setSampleAt( (int)( _x*xscale ), val );
}

void Graph::mouseReleaseEvent( QMouseEvent * _me )
{
	if( _me->button() == Qt::LeftButton )
	{
		// toggle mouse state
		m_mouseDown = false;
		setCursor( Qt::CrossCursor );
		update();
		emit drawn();
	}
}

void Graph::paintEvent( QPaintEvent * )
{
	QPainter p( this );

	p.setPen( QPen( m_graphColor, 1 ) );
	QColor gcol = QColor( m_graphColor.red(), m_graphColor.green(), m_graphColor.blue(), 100 );

	QVector<float> * samps = &(model()->m_samples);
	int length = model()->length();
	const float maxVal = model()->maxValue();
	const float minVal = model()->minValue();

	float xscale = (float)( width()-4 ) / length;
	float yscale = (float)( height()-4 ) / ( minVal - maxVal );

	// Max index, more useful below
	length--;

	switch( m_graphStyle )
	{
		case Graph::LinearStyle:
			p.setRenderHints( QPainter::Antialiasing, true );

			for( int i=0; i < length; i++ )
			{
				// Needs to be rewritten
				p.drawLine(
					2+static_cast<int>(i*xscale),
					2+static_cast<int>( ( (*samps)[i] - maxVal ) * yscale ),
					2+static_cast<int>((i+1)*xscale),
					2+static_cast<int>( ( (*samps)[i+1] - maxVal ) * yscale )
					);
			}

			// Draw last segment wrapped around
			p.drawLine(2+static_cast<int>(length*xscale),
				2+static_cast<int>( ( (*samps)[length] - maxVal ) * yscale ),
				width()-3,
				2+static_cast<int>( ( (*samps)[0] - maxVal ) * yscale ) );

			p.setRenderHints( QPainter::Antialiasing, false );
			break;

		case Graph::NearestStyle:
			for( int i=0; i < length; i++ )
			{
				p.drawLine(2+static_cast<int>(i*xscale),
					2+static_cast<int>( ( (*samps)[i] - maxVal ) * yscale ),
					2+static_cast<int>((i+1)*xscale),
					2+static_cast<int>( ( (*samps)[i] - maxVal ) * yscale )
					);
				p.drawLine(2+static_cast<int>((i+1)*xscale),
					2+static_cast<int>( ( (*samps)[i] - maxVal ) * yscale ),
					2+static_cast<int>((i+1)*xscale),
					2+static_cast<int>( ( (*samps)[i+1] - maxVal ) * yscale )
					);
			}

			p.drawLine(2+static_cast<int>(length*xscale),
				2+static_cast<int>( ( (*samps)[length] - maxVal ) * yscale ),
				width()-3,
				2+static_cast<int>( ( (*samps)[length] - maxVal ) * yscale ) );
			break;

		case Graph::LinearNonCyclicStyle:
			p.setRenderHints( QPainter::Antialiasing, true );

			for( int i=0; i < length; i++ )
			{
				// Needs to be rewritten
				p.drawLine(
					2+static_cast<int>(i*xscale),
					2+static_cast<int>( ( (*samps)[i] - maxVal ) * yscale ),
					2+static_cast<int>((i+1)*xscale),
					2+static_cast<int>( ( (*samps)[i+1] - maxVal ) * yscale )
					);
			}

			// Do not draw last segment wrapped around - hence, "non-cyclic"

			p.setRenderHints( QPainter::Antialiasing, false );
			break;

		case Graph::BarStyle:
			for( int i=0; i <= length; i++ )
			{
				p.fillRect( 2+static_cast<int>( i*xscale ),
							2+static_cast<int>( ( (*samps)[i] - maxVal ) * yscale ),
							qMax( static_cast<int>(xscale) - 1, 1 ),
							qMax( static_cast<int>( ( minVal - maxVal ) * yscale ) - static_cast<int>( ( (*samps)[i] - maxVal ) * yscale ), 1 ),
							gcol );

				p.setPen( QPen( m_graphColor, 1.0 ) );

				p.drawLine( 2+static_cast<int>(i*xscale),
					2+static_cast<int>( ( (*samps)[i] - maxVal ) * yscale ),
					qMax( static_cast<int>(i*xscale) + static_cast<int>(xscale), 2+static_cast<int>(i*xscale) ),
					2+static_cast<int>( ( (*samps)[i] - maxVal ) * yscale )
					);
			}

			break;

		default:
			break;
	}

	// draw Pointer
	if( m_mouseDown )
	{
		QPoint cursor = mapFromGlobal( QCursor::pos() );
		p.setPen( QColor( 0x70, 0x7C, 0x91 ) );
		p.drawLine( 2, cursor.y(), width()-2, cursor.y() );
		p.drawLine( cursor.x(), 2, cursor.x(), height()-2 );
	}
	p.drawPixmap( 0, 0, m_foreground );
}

void Graph::dropEvent( QDropEvent * _de )
{
	QString type = StringPairDrag::decodeKey( _de );
	QString value = StringPairDrag::decodeValue( _de );

	if( type == "samplefile" )
	{
		// TODO: call setWaveToUser
		// loadSampleFromFile( value );
		_de->accept();
	}
}

void Graph::dragEnterEvent( QDragEnterEvent * _dee )
{
	if( StringPairDrag::processDragEnterEvent( _dee,
		QString( "samplefile" ) ) == false )
	{
		_dee->ignore();
	}
}

void Graph::modelChanged()
{
	graphModel * gModel = castModel<graphModel>();

	QObject::connect( gModel, SIGNAL( samplesChanged( int, int ) ),
			this, SLOT( updateGraph( int, int ) ) );

	QObject::connect( gModel, SIGNAL( lengthChanged( ) ),
			this, SLOT( updateGraph( ) ) );
}

void Graph::updateGraph( int _startPos, int _endPos )
{
	// Can optimize by only drawing changed position
	update();
}

void Graph::updateGraph()
{
    updateGraph( 0, model()->length() - 1 );
}

graphModel::graphModel( float _min, float _max, int _length,
			::Model * _parent, bool _default_constructed,  float _step ) :
	Model( _parent, tr( "Graph" ), _default_constructed ),
	m_samples( _length ),
	m_length( _length ),
	m_minValue( _min ),
	m_maxValue( _max ),
	m_step( _step )
{
}

void graphModel::setRange( float _min, float _max )
{
	if( _min != m_minValue || _max != m_maxValue )
	{
		m_minValue = _min;
		m_maxValue = _max;

		if( !m_samples.isEmpty() )
		{
			// Trim existing values
			for( int i=0; i < length(); i++ )
			{
				m_samples[i] = fmaxf( _min, fminf( m_samples[i], _max ) );
			}
		}

		emit rangeChanged();
	}
}

void graphModel::setLength( int _length )
{
	if( _length != m_length )
	{
		m_length = _length;
		if( m_samples.size() < m_length )
		{
			m_samples.resize( m_length );
		}
		emit lengthChanged();
	}
}

void graphModel::setSampleAt( int x, float val )
{
	drawSampleAt( x, val );
	emit samplesChanged( x, x );
}

void graphModel::setSamples( const float * _samples )
{
	std::copy( _samples, _samples + length(), m_samples.begin());

	emit samplesChanged( 0, length()-1 );
}

void graphModel::setWaveToSine()
{
	for( int i = 0; i < length(); i++ )
	{
		m_samples[i] = Oscillator::sinSample(
				i / static_cast<float>( length() ) );
	}

	emit samplesChanged( 0, length() - 1 );
};

void graphModel::setWaveToTriangle()
{
	for( int i = 0; i < length(); i++ )
	{
		m_samples[i] = Oscillator::triangleSample(
				i / static_cast<float>( length() ) );
	}

	emit samplesChanged( 0, length() - 1 );
};

void graphModel::setWaveToSaw()
{
	for( int i = 0; i < length(); i++ )
	{
		m_samples[i] = Oscillator::sawSample(
				i / static_cast<float>( length() ) );
	}

	emit samplesChanged( 0, length() - 1 );
};

void graphModel::setWaveToSquare()
{
	for( int i = 0; i < length(); i++ )
	{
		m_samples[i] = Oscillator::squareSample(
				i / static_cast<float>( length() ) );
	}

	emit samplesChanged( 0, length() - 1 );
};

void graphModel::setWaveToNoise()
{
	for( int i = 0; i < length(); i++ )
	{
		m_samples[i] = Oscillator::noiseSample(
				i / static_cast<float>( length() ) );
	}

	emit samplesChanged( 0, length() - 1 );
};

QString graphModel::setWaveToUser()
{
	SampleBuffer * sampleBuffer = new SampleBuffer;
	QString fileName = sampleBuffer->openAndSetWaveformFile();
	if( fileName.isEmpty() == false )
	{
		sampleBuffer->dataReadLock();
		for( int i = 0; i < length(); i++ )
		{
			m_samples[i] = sampleBuffer->userWaveSample(
					i / static_cast<float>( length() ) );
		}
		sampleBuffer->dataUnlock();
	}

	sharedObject::unref( sampleBuffer );

	emit samplesChanged( 0, length() - 1 );
	return fileName;
};

void graphModel::smooth()
{
	// store values in temporary array
	QVector<float> temp = m_samples;

	// Smoothing
	m_samples[0] = ( temp[length()-1] + ( temp[0] * 2 ) + temp[1] ) * 0.25f;
	for ( int i=1; i < ( length()-1 ); i++ )
	{
		m_samples[i] = ( temp[i-1] + ( temp[i] * 2 ) + temp[i+1] ) * 0.25f;
	}
	m_samples[length()-1] = ( temp[length()-2] + ( temp[length()-1] * 2 ) + temp[0] ) * 0.25f;

	emit samplesChanged(0, length()-1);
}

void graphModel::smoothNonCyclic()
{
	// store values in temporary array
	QVector<float> temp = m_samples;

	// Smoothing
	//m_samples[0] = ( ( temp[0] * 3 ) + temp[1] ) * 0.25f;
	for ( int i=1; i < ( length()-1 ); i++ )
	{
		m_samples[i] = ( temp[i-1] + ( temp[i] * 2 ) + temp[i+1] ) * 0.25f;
	}
	//m_samples[length()-1] = ( temp[length()-2] + ( temp[length()-1] * 3 ) ) * 0.25f;

	emit samplesChanged(0, length()-1);
}

void graphModel::convolve(const float *convolution,
	const int convolutionLength, const int centerOffset)
{
	// store values in temporary array
	QVector<float> temp = m_samples;
	const int graphLength = length();
	float sum;
	// make a cyclic convolution
	for ( int i = 0; i <  graphLength; i++ )
	{
		sum = 0;
		for ( int j = 0; j < convolutionLength; j++ )
		{
			sum += convolution[j] * temp[( i + j ) % graphLength];
		}
		m_samples[( i + centerOffset ) % graphLength] = sum;
	}
	emit samplesChanged(0, graphLength - 1);
}

void graphModel::normalize()
{
	float max = 0.0001f;
	float avg = 0.0f;

	// first correct dc offset by normalizing to average
	for( int i = 0; i < length(); i++ )
		avg += m_samples[i];
	avg /= length();
	for( int i = 0; i < length(); i++ )
		m_samples[i] -= avg;

	// then maximize
	for( int i = 0; i < length(); i++ )
		max = qMax( max, qAbs( m_samples[i] ) );

	for( int i = 0; i < length(); i++ )
		m_samples[i] = qBound( m_minValue, m_samples[i] / max, m_maxValue );

	// signal changes if any
	if( max != 1.0f || avg != 0.0f )
		emit samplesChanged( 0, length()-1 );
}

void graphModel::invert()
{
	const float range = m_maxValue - m_minValue;

	for( int i = 0; i < length(); i++ )
		m_samples[i] = m_minValue + ( range - ( m_samples[i] - m_minValue ) );

	emit samplesChanged( 0, length()-1 );
}

void graphModel::shiftPhase( int _deg )
{
	// calculate offset in samples
	const int offset = ( _deg * length() ) / 360; //multiply first because integers

	// store values in temporary array
	QVector<float> temp = m_samples;

	// shift phase
	for( int i = 0; i < length(); i++ )
	{
		int o = ( i + offset ) % length();
		while( o < 0 ) o += length();
		m_samples[i] = temp[o];
	}

	emit samplesChanged( 0, length()-1 );
}

void graphModel::clear()
{
	const int graph_length = length();
	for( int i = 0; i < graph_length; i++ )
		m_samples[i] = 0;
	emit samplesChanged( 0, graph_length - 1 );
}

// Clear any part of the graph that isn't displayed
void graphModel::clearInvisible()
{
	const int graph_length = length();
	const int full_graph_length = m_samples.size();
	for( int i = graph_length; i < full_graph_length; i++ )
		m_samples[i] = 0;
	emit samplesChanged( graph_length, full_graph_length - 1 );
}

void graphModel::drawSampleAt( int x, float val )
{
	//snap to the grid
	val -= ( m_step != 0.0 ) ? fmod( val, m_step ) * m_step : 0;

	// boundary crop
	x = qMax( 0, qMin( length()-1, x ) );
	val = qMax( minValue(), qMin( maxValue(), val ) );

	// change sample shape
	m_samples[x] = val;
}


code:
/*
 * GroupBox.cpp - groupbox for LMMS
 *
 * Copyright (c) 2005-2009 Tobias Doerffel <tobydox/at/users.sourceforge.net>
 *
 * 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>

#ifndef __USE_XOPEN
#define __USE_XOPEN
#endif

#include "GroupBox.h"
#include "embed.h"
#include "gui_templates.h"

GroupBox::GroupBox( const QString & _caption, QWidget * _parent ) :
	QWidget( _parent ),
	BoolModelView( NULL, this ),
	m_caption( _caption ),
	m_titleBarHeight( 11 )
{
	m_led = new PixmapButton( this, _caption );
	m_led->setCheckable( true );
	m_led->move( 3, 0 );
	m_led->setActiveGraphic( embed::getIconPixmap( "led_green" ) );
	m_led->setInactiveGraphic( embed::getIconPixmap( "led_off" ) );

	setModel( new BoolModel( false, NULL, _caption, true ) );
	setAutoFillBackground( true );
	unsetCursor();
}

GroupBox::~GroupBox()
{
	delete m_led;
}

void GroupBox::modelChanged()
{
	m_led->setModel( model() );
}

void GroupBox::mousePressEvent( QMouseEvent * _me )
{
	if( _me->y() > 1 && _me->y() < 13 && _me->button() == Qt::LeftButton )
	{
		model()->setValue( !model()->value() );
	}
}

void GroupBox::paintEvent( QPaintEvent * pe )
{
	QPainter p( this );

	// Draw background
	p.fillRect( 0, 0, width() - 1, height() - 1, p.background() );

	// outer rect
	p.setPen( p.background().color().darker( 150 ) );
	p.drawRect( 0, 0, width() - 1, height() - 1 );

	// draw line below titlebar
	p.fillRect( 1, 1, width() - 2, m_titleBarHeight + 1, p.background().color().darker( 150 ) );

	// draw text
	p.setPen( palette().color( QPalette::Active, QPalette::Text ) );
	p.setFont( pointSize<8>( font() ) );
	p.drawText( 22, m_titleBarHeight, m_caption );
}


code:
/*
 * InstrumentFunctionViews.cpp - view for instrument-functions-tab
 *
 * Copyright (c) 2004-2014 Tobias Doerffel <tobydox/at/users.sourceforge.net>
 *
 * 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 <QLabel>
#include <QLayout>

#include "InstrumentFunctions.h"
#include "InstrumentFunctionViews.h"
#include "ComboBox.h"
#include "GroupBox.h"
#include "gui_templates.h"
#include "Knob.h"
#include "TempoSyncKnob.h"

InstrumentFunctionNoteStackingView::InstrumentFunctionNoteStackingView( InstrumentFunctionNoteStacking* cc, QWidget* parent ) :
	QWidget( parent ),
	ModelView( NULL, this ),
	m_cc( cc ),
	m_chordsGroupBox( new GroupBox( tr( "STACKING" ) ) ),
	m_chordsComboBox( new ComboBox() ),
	m_chordRangeKnob( new Knob( knobBright_26 ) )
{
	QHBoxLayout* topLayout = new QHBoxLayout( this );
	topLayout->setMargin( 0 );
	topLayout->addWidget( m_chordsGroupBox );

	QGridLayout* mainLayout = new QGridLayout( m_chordsGroupBox );
	mainLayout->setContentsMargins( 8, 18, 8, 8 );
	mainLayout->setColumnStretch( 0, 1 );
	mainLayout->setHorizontalSpacing( 20 );
	mainLayout->setVerticalSpacing( 1 );

	QLabel* chordLabel = new QLabel( tr( "Chord:" ) );
	chordLabel->setFont( pointSize<8>( chordLabel->font() ) );

	m_chordRangeKnob->setLabel( tr( "RANGE" ) );
	m_chordRangeKnob->setHintText( tr( "Chord range:" ), " " + tr( "octave(s)" ) );

	mainLayout->addWidget( chordLabel, 0, 0 );
	mainLayout->addWidget( m_chordsComboBox, 1, 0 );
	mainLayout->addWidget( m_chordRangeKnob, 0, 1, 2, 1, Qt::AlignHCenter );
}

InstrumentFunctionNoteStackingView::~InstrumentFunctionNoteStackingView()
{
	delete m_chordsGroupBox;
}

void InstrumentFunctionNoteStackingView::modelChanged()
{
	m_cc = castModel<InstrumentFunctionNoteStacking>();
	m_chordsGroupBox->setModel( &m_cc->m_chordsEnabledModel );
	m_chordsComboBox->setModel( &m_cc->m_chordsModel );
	m_chordRangeKnob->setModel( &m_cc->m_chordRangeModel );
}

InstrumentFunctionArpeggioView::InstrumentFunctionArpeggioView( InstrumentFunctionArpeggio* arp, QWidget* parent ) :
	QWidget( parent ),
	ModelView( NULL, this ),
	m_a( arp ),
	m_arpGroupBox( new GroupBox( tr( "ARPEGGIO" ) ) ),
	m_arpComboBox( new ComboBox() ),
	m_arpRangeKnob( new Knob( knobBright_26 ) ),
	m_arpRepeatsKnob( new Knob( knobBright_26 ) ),
	m_arpCycleKnob( new Knob( knobBright_26 ) ),
	m_arpSkipKnob( new Knob( knobBright_26 ) ),
	m_arpMissKnob( new Knob( knobBright_26 ) ),
	m_arpTimeKnob( new TempoSyncKnob( knobBright_26 ) ),
	m_arpGateKnob( new Knob( knobBright_26 ) ),
	m_arpDirectionComboBox( new ComboBox() ),
	m_arpModeComboBox( new ComboBox() )
{
	QHBoxLayout* topLayout = new QHBoxLayout( this );
	topLayout->setMargin( 0 );
	topLayout->addWidget( m_arpGroupBox );

	QGridLayout* mainLayout = new QGridLayout( m_arpGroupBox );
	mainLayout->setContentsMargins( 8, 18, 8, 8 );
	mainLayout->setColumnStretch( 0, 1 );
	mainLayout->setHorizontalSpacing( 20 );
	mainLayout->setVerticalSpacing( 1 );

	m_arpRangeKnob->setLabel( tr( "RANGE" ) );
	m_arpRangeKnob->setHintText( tr( "Arpeggio range:" ), " " + tr( "octave(s)" ) );

	m_arpRepeatsKnob->setLabel( tr( "REP" ) );
	m_arpRepeatsKnob->setHintText( tr( "Note repeats:" ) + " ", " " + tr( "time(s)" ) );

	m_arpCycleKnob->setLabel( tr( "CYCLE" ) );
	m_arpCycleKnob->setHintText( tr( "Cycle notes:" ) + " ", " " + tr( "note(s)" ) );

	m_arpSkipKnob->setLabel( tr( "SKIP" ) );
	m_arpSkipKnob->setHintText( tr( "Skip rate:" ), tr( "%" ) );

	m_arpMissKnob->setLabel( tr( "MISS" ) );
	m_arpMissKnob->setHintText( tr( "Miss rate:" ), tr( "%" ) );

	m_arpTimeKnob->setLabel( tr( "TIME" ) );
	m_arpTimeKnob->setHintText( tr( "Arpeggio time:" ), " " + tr( "ms" ) );

	m_arpGateKnob->setLabel( tr( "GATE" ) );
	m_arpGateKnob->setHintText( tr( "Arpeggio gate:" ), tr( "%" ) );

	QLabel* arpChordLabel = new QLabel( tr( "Chord:" ) );
	arpChordLabel->setFont( pointSize<8>( arpChordLabel->font() ) );

	QLabel* arpDirectionLabel = new QLabel( tr( "Direction:" ) );
	arpDirectionLabel->setFont( pointSize<8>( arpDirectionLabel->font() ) );

	QLabel* arpModeLabel = new QLabel( tr( "Mode:" ) );
	arpModeLabel->setFont( pointSize<8>( arpModeLabel->font() ) );

	mainLayout->addWidget( arpChordLabel, 0, 0 );
	mainLayout->addWidget( m_arpComboBox, 1, 0 );
	mainLayout->addWidget( arpDirectionLabel, 3, 0 );
	mainLayout->addWidget( m_arpDirectionComboBox, 4, 0 );
	mainLayout->addWidget( arpModeLabel, 6, 0 );
	mainLayout->addWidget( m_arpModeComboBox, 7, 0 );

	mainLayout->addWidget( m_arpRangeKnob, 0, 1, 2, 1, Qt::AlignHCenter );
	mainLayout->addWidget( m_arpRepeatsKnob, 0, 2, 2, 1, Qt::AlignHCenter );
	mainLayout->addWidget( m_arpCycleKnob, 0, 3, 2, 1, Qt::AlignHCenter );
	mainLayout->addWidget( m_arpSkipKnob, 3, 2, 2, 1, Qt::AlignHCenter );
	mainLayout->addWidget( m_arpMissKnob, 3, 3, 2, 1, Qt::AlignHCenter );
	mainLayout->addWidget( m_arpGateKnob, 6, 2, 2, 1, Qt::AlignHCenter );
	mainLayout->addWidget( m_arpTimeKnob, 6, 3, 2, 1, Qt::AlignHCenter );

	mainLayout->setRowMinimumHeight( 2, 10 );
	mainLayout->setRowMinimumHeight( 5, 10 );
}

InstrumentFunctionArpeggioView::~InstrumentFunctionArpeggioView()
{
	delete m_arpGroupBox;
}

void InstrumentFunctionArpeggioView::modelChanged()
{
	m_a = castModel<InstrumentFunctionArpeggio>();
	m_arpGroupBox->setModel( &m_a->m_arpEnabledModel );
	m_arpComboBox->setModel( &m_a->m_arpModel );
	m_arpRangeKnob->setModel( &m_a->m_arpRangeModel );
	m_arpRepeatsKnob->setModel( &m_a->m_arpRepeatsModel );
	m_arpCycleKnob->setModel( &m_a->m_arpCycleModel );
	m_arpSkipKnob->setModel( &m_a->m_arpSkipModel );
	m_arpMissKnob->setModel( &m_a->m_arpMissModel );
	m_arpTimeKnob->setModel( &m_a->m_arpTimeModel );
	m_arpGateKnob->setModel( &m_a->m_arpGateModel );
	m_arpDirectionComboBox->setModel( &m_a->m_arpDirectionModel );
	m_arpModeComboBox->setModel( &m_a->m_arpModeModel );
}


code:
/*
 * InstrumentMidiIOView.cpp - MIDI-IO-View
 *
 * Copyright (c) 2005-2014 Tobias Doerffel <tobydox/at/users.sourceforge.net>
 *
 * 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 <QMenu>
#include <QToolButton>
#include <QLabel>
#include <QLayout>

#include "InstrumentMidiIOView.h"
#include "MidiPortMenu.h"
#include "Engine.h"
#include "embed.h"
#include "GroupBox.h"
#include "gui_templates.h"
#include "LcdSpinBox.h"
#include "MidiClient.h"
#include "Mixer.h"
#include "InstrumentTrack.h"
#include "LedCheckbox.h"

InstrumentMidiIOView::InstrumentMidiIOView( QWidget* parent ) :
	QWidget( parent ),
	ModelView( NULL, this ),
	m_rpBtn( NULL ),
	m_wpBtn( NULL )
{
	QVBoxLayout* layout = new QVBoxLayout( this );
	layout->setMargin( 5 );
	m_midiInputGroupBox = new GroupBox( tr( "ENABLE MIDI INPUT" ) );
	layout->addWidget( m_midiInputGroupBox );

	QHBoxLayout* midiInputLayout = new QHBoxLayout( m_midiInputGroupBox );
	midiInputLayout->setContentsMargins( 8, 18, 8, 8 );
	midiInputLayout->setSpacing( 4 );

	m_inputChannelSpinBox = new LcdSpinBox( 2, m_midiInputGroupBox );
	m_inputChannelSpinBox->addTextForValue( 0, "--" );
	/*: This string must be be short, its width must be less than
	 *  width of LCD spin-box of two digits */
	m_inputChannelSpinBox->setLabel( tr( "CHAN" ) );
	m_inputChannelSpinBox->setEnabled( false );
	midiInputLayout->addWidget( m_inputChannelSpinBox );

	m_fixedInputVelocitySpinBox = new LcdSpinBox( 3, m_midiInputGroupBox );
	m_fixedInputVelocitySpinBox->setDisplayOffset( 1 );
	m_fixedInputVelocitySpinBox->addTextForValue( 0, "---" );
	/*: This string must be be short, its width must be less than
	 *  width of LCD spin-box of three digits */
	m_fixedInputVelocitySpinBox->setLabel( tr( "VELOC" ) );
	m_fixedInputVelocitySpinBox->setEnabled( false );
	midiInputLayout->addWidget( m_fixedInputVelocitySpinBox );
	midiInputLayout->addStretch();

	connect( m_midiInputGroupBox->ledButton(), SIGNAL( toggled( bool ) ),
			m_inputChannelSpinBox, SLOT( setEnabled( bool ) ) );
	connect( m_midiInputGroupBox->ledButton(), SIGNAL( toggled( bool ) ),
		m_fixedInputVelocitySpinBox, SLOT( setEnabled( bool ) ) );

	m_midiOutputGroupBox = new GroupBox( tr( "ENABLE MIDI OUTPUT" ) );
	layout->addWidget( m_midiOutputGroupBox );

	QHBoxLayout* midiOutputLayout = new QHBoxLayout( m_midiOutputGroupBox );
	midiOutputLayout->setContentsMargins( 8, 18, 8, 8 );
	midiOutputLayout->setSpacing( 4 );

	m_outputChannelSpinBox = new LcdSpinBox( 2, m_midiOutputGroupBox );
	m_outputChannelSpinBox->addTextForValue( 0, "--" );
	/*: This string must be be short, its width must be less than
	 *  width of LCD spin-box of two digits */
	m_outputChannelSpinBox->setLabel( tr( "CHAN" ) );
	midiOutputLayout->addWidget( m_outputChannelSpinBox );

	m_fixedOutputVelocitySpinBox = new LcdSpinBox( 3, m_midiOutputGroupBox );
	m_fixedOutputVelocitySpinBox->setDisplayOffset( 1 );
	m_fixedOutputVelocitySpinBox->addTextForValue( 0, "---" );
	/*: This string must be be short, its width must be less than
	 *  width of LCD spin-box of three digits */
	m_fixedOutputVelocitySpinBox->setLabel( tr( "VELOC" ) );
	m_fixedOutputVelocitySpinBox->setEnabled( false );
	midiOutputLayout->addWidget( m_fixedOutputVelocitySpinBox );

	m_outputProgramSpinBox = new LcdSpinBox( 3, m_midiOutputGroupBox );
	/*: This string must be be short, its width must be less than the
	 *  width of LCD spin-box of three digits */
	m_outputProgramSpinBox->setLabel( tr( "PROG" ) );
	m_outputProgramSpinBox->setEnabled( false );
	midiOutputLayout->addWidget( m_outputProgramSpinBox );

	m_fixedOutputNoteSpinBox = new LcdSpinBox( 3, m_midiOutputGroupBox );
	m_fixedOutputNoteSpinBox->setDisplayOffset( 1 );
	m_fixedOutputNoteSpinBox->addTextForValue( 0, "---" );
	/*: This string must be be short, its width must be less than
	 *  width of LCD spin-box of three digits */
	m_fixedOutputNoteSpinBox->setLabel( tr( "NOTE" ) );
	m_fixedOutputNoteSpinBox->setEnabled( false );
	midiOutputLayout->addWidget( m_fixedOutputNoteSpinBox );
	midiOutputLayout->addStretch();

	connect( m_midiOutputGroupBox->ledButton(), SIGNAL( toggled( bool ) ),
		m_fixedOutputVelocitySpinBox, SLOT( setEnabled( bool ) ) );
	connect( m_midiOutputGroupBox->ledButton(), SIGNAL( toggled( bool ) ),
			m_outputProgramSpinBox, SLOT( setEnabled( bool ) ) );
	connect( m_midiOutputGroupBox->ledButton(), SIGNAL( toggled( bool ) ),
		m_fixedOutputNoteSpinBox, SLOT( setEnabled( bool ) ) );

	if( !Engine::mixer()->midiClient()->isRaw() )
	{
		m_rpBtn = new QToolButton;
		m_rpBtn->setMinimumSize( 32, 32 );
		m_rpBtn->setText( tr( "MIDI devices to receive MIDI events from" ) );
		m_rpBtn->setIcon( embed::getIconPixmap( "piano" ) );
		m_rpBtn->setPopupMode( QToolButton::InstantPopup );
		midiInputLayout->insertSpacing( 0, 4 );
		midiInputLayout->insertWidget( 0, m_rpBtn );

		m_wpBtn = new QToolButton;
		m_wpBtn->setMinimumSize( 32, 32 );
		m_wpBtn->setText( tr( "MIDI devices to send MIDI events to" ) );
		m_wpBtn->setIcon( embed::getIconPixmap( "piano" ) );
		m_wpBtn->setPopupMode( QToolButton::InstantPopup );
		midiOutputLayout->insertSpacing( 0, 4 );
		midiOutputLayout->insertWidget( 0, m_wpBtn );
	}

#define PROVIDE_CUSTOM_BASE_VELOCITY_UI
#ifdef PROVIDE_CUSTOM_BASE_VELOCITY_UI
	GroupBox* baseVelocityGroupBox = new GroupBox( tr( "CUSTOM BASE VELOCITY" ) );
	layout->addWidget( baseVelocityGroupBox );

	QVBoxLayout* baseVelocityLayout = new QVBoxLayout( baseVelocityGroupBox );
	baseVelocityLayout->setContentsMargins( 8, 18, 8, 8 );
	baseVelocityLayout->setSpacing( 6 );

	QLabel* baseVelocityHelp = new QLabel( tr( "Specify the velocity normalization base for MIDI-based instruments at 100% note velocity." ) );
	baseVelocityHelp->setWordWrap( true );
    baseVelocityHelp->setFont( pointSize<8>( baseVelocityHelp->font() ) );

	baseVelocityLayout->addWidget( baseVelocityHelp );

	m_baseVelocitySpinBox = new LcdSpinBox( 3, baseVelocityGroupBox );
	m_baseVelocitySpinBox->setLabel( tr( "BASE VELOCITY" ) );
	m_baseVelocitySpinBox->setEnabled( false );
	baseVelocityLayout->addWidget( m_baseVelocitySpinBox );

	connect( baseVelocityGroupBox->ledButton(), SIGNAL( toggled( bool ) ),
			m_baseVelocitySpinBox, SLOT( setEnabled( bool ) ) );
#endif

	layout->addStretch();
}

InstrumentMidiIOView::~InstrumentMidiIOView()
{
}

void InstrumentMidiIOView::modelChanged()
{
	MidiPort * mp = castModel<MidiPort>();

	m_midiInputGroupBox->setModel( &mp->m_readableModel );
	m_inputChannelSpinBox->setModel( &mp->m_inputChannelModel );
	m_fixedInputVelocitySpinBox->setModel( &mp->m_fixedInputVelocityModel );

	m_midiOutputGroupBox->setModel( &mp->m_writableModel );
	m_outputChannelSpinBox->setModel( &mp->m_outputChannelModel );
	m_fixedOutputVelocitySpinBox->setModel( &mp->m_fixedOutputVelocityModel );
	m_fixedOutputNoteSpinBox->setModel( &mp->m_fixedOutputNoteModel );
	m_outputProgramSpinBox->setModel( &mp->m_outputProgramModel );

#ifdef PROVIDE_CUSTOM_BASE_VELOCITY_UI
	m_baseVelocitySpinBox->setModel( &mp->m_baseVelocityModel );
#endif

	if( m_rpBtn )
	{
		m_rpBtn->setMenu( mp->m_readablePortsMenu );
	}
	if( m_wpBtn )
	{
		m_wpBtn->setMenu( mp->m_writablePortsMenu );
	}
}

InstrumentMiscView::InstrumentMiscView(InstrumentTrack *it, QWidget *parent) :
	QWidget( parent )
{
	QVBoxLayout* layout = new QVBoxLayout( this );
	layout->setMargin( 5 );
	m_pitchGroupBox = new GroupBox( tr ( "MASTER PITCH" ) );
	layout->addWidget( m_pitchGroupBox );
	QHBoxLayout* masterPitchLayout = new QHBoxLayout( m_pitchGroupBox );
	masterPitchLayout->setContentsMargins( 8, 18, 8, 8 );
	QLabel *tlabel = new QLabel(tr( "Enables the use of master pitch" ) );
	tlabel->setFont( pointSize<8>( tlabel->font() ) );
	m_pitchGroupBox->setModel( &it->m_useMasterPitchModel );
	masterPitchLayout->addWidget( tlabel );
	layout->addStretch();
}

InstrumentMiscView::~InstrumentMiscView()
{

}


code:
/*
 * InstrumentSoundShapingView.cpp - view for InstrumentSoundShaping class
 *
 * Copyright (c) 2004-2014 Tobias Doerffel <tobydox/at/users.sourceforge.net>
 *
 * 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 <QLabel>

#include "InstrumentSoundShapingView.h"
#include "EnvelopeAndLfoParameters.h"
#include "EnvelopeAndLfoView.h"
#include "ComboBox.h"
#include "GroupBox.h"
#include "gui_templates.h"
#include "Knob.h"
#include "TabWidget.h"

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( NULL, this ),
	m_ss( NULL )
{
	m_targetsTabWidget = new TabWidget( tr( "TARGET" ), this );
	m_targetsTabWidget->setGeometry( TARGETS_TABWIDGET_X,
						TARGETS_TABWIDGET_Y,
						TARGETS_TABWIDGET_WIDTH,
						TARGETS_TABWIDGET_HEIGTH );

	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] ),
                                                NULL );
	}

	m_filterGroupBox = new GroupBox( tr( "FILTER" ), this );
	m_filterGroupBox->setGeometry( FILTER_GROUPBOX_X, FILTER_GROUPBOX_Y,
						FILTER_GROUPBOX_WIDTH,
						FILTER_GROUPBOX_HEIGHT );

	m_filterComboBox = new ComboBox( m_filterGroupBox );
	m_filterComboBox->setGeometry( 14, 22, 120, ComboBox::DEFAULT_HEIGHT );
	m_filterComboBox->setFont( pointSize<8>( m_filterComboBox->font() ) );

	m_filterCutKnob = new Knob( knobBright_26, m_filterGroupBox );
	m_filterCutKnob->setLabel( tr( "FREQ" ) );
	m_filterCutKnob->move( 140, 18 );
	m_filterCutKnob->setHintText( tr( "Cutoff frequency:" ), " " + tr( "Hz" ) );

	m_filterResKnob = new Knob( knobBright_26, 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->setFont( pointSize<8>( m_singleStreamInfoLabel->font() ) );

	m_singleStreamInfoLabel->setGeometry( TARGETS_TABWIDGET_X,
						TARGETS_TABWIDGET_Y,
						TARGETS_TABWIDGET_WIDTH,
						TARGETS_TABWIDGET_HEIGTH );
}

InstrumentSoundShapingView::~InstrumentSoundShapingView()
{
	delete m_targetsTabWidget;
}

void InstrumentSoundShapingView::setFunctionsHidden( bool hidden )
{
	m_targetsTabWidget->setHidden( hidden );
	m_filterGroupBox->setHidden( hidden );
	m_singleStreamInfoLabel->setHidden( !hidden );
}

void InstrumentSoundShapingView::modelChanged()
{
	m_ss = castModel<InstrumentSoundShaping>();
	m_filterGroupBox->setModel( &m_ss->m_filterEnabledModel );
	m_filterComboBox->setModel( &m_ss->m_filterModel );
	m_filterCutKnob->setModel( &m_ss->m_filterCutModel );
	m_filterResKnob->setModel( &m_ss->m_filterResModel );
	for( int i = 0; i < InstrumentSoundShaping::NumTargets; ++i )
	{
		m_envLfoViews[i]->setModel( m_ss->m_envLfoParameters[i] );
	}
}