AutomationEditor: add option for smooth lines and curves

Besides discrete automation it's now possible to setup interpolation
modes such as linear and cubic-hermite.

Signed-off-by: Tobias Doerffel <tobias.doerffel@gmail.com>
This commit is contained in:
Joel Muzzerall
2014-01-08 00:04:18 +01:00
committed by Tobias Doerffel
parent ddad2da162
commit 6249b23f1f
8 changed files with 534 additions and 123 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 581 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 443 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 627 B

View File

@@ -94,14 +94,16 @@ protected:
virtual void wheelEvent( QWheelEvent * _we );
float getLevel( int _y );
static inline void drawValueRect( QPainter & _p, int _x, int _y,
int _width, int _height,
const bool _is_selected );
int xCoordOfTick( int _tick );
int yCoordOfLevel( float _level );
inline void drawLevelTick( QPainter & _p, int _tick,
float _value, bool _is_selected );
void removeSelection();
void selectAll();
void getSelectedValues( timeMap & _selected_values );
void drawLine( int x0, float y0, int x1, float y1 );
void disableTensionComboBox();
protected slots:
void play();
@@ -115,6 +117,11 @@ protected slots:
void selectButtonToggled();
void moveButtonToggled();
void discreteButtonToggled();
void linearButtonToggled();
void cubicHermiteButtonToggled();
void tensionChanged();
void copySelectedValues();
void cutSelectedValues();
void pasteValues();
@@ -178,6 +185,13 @@ private:
toolButton * m_selectButton;
toolButton * m_moveButton;
toolButton * m_discreteButton;
toolButton * m_linearButton;
toolButton * m_cubicHermiteButton;
comboBox * m_tensionComboBox;
ComboBoxModel m_tensionModel;
ComboBoxModel m_tensionDisabledModel;
toolButton * m_cutButton;
toolButton * m_copyButton;
toolButton * m_pasteButton;
@@ -250,4 +264,3 @@ signals:
#endif

View File

@@ -41,6 +41,13 @@ class EXPORT AutomationPattern : public trackContentObject
{
Q_OBJECT
public:
enum ProgressionTypes
{
DiscreteProgression,
LinearProgression,
CubicHermiteProgression
} ;
typedef QMap<int, float> timeMap;
typedef QVector<QPointer<AutomatableModel> > objectVector;
@@ -52,6 +59,19 @@ public:
const AutomatableModel * firstObject() const;
// progression-type stuff
inline ProgressionTypes progressionType() const
{
return m_progressionType;
}
void setProgressionType( ProgressionTypes _new_progression_type );
inline QString getTension() const
{
return m_tension;
}
void setTension( QString _new_tension );
virtual midiTime length() const;
midiTime putValue( const midiTime & _time, const float _value,
@@ -69,12 +89,23 @@ public:
return m_timeMap;
}
inline const timeMap & getTangents() const
{
return m_tangents;
}
inline timeMap & getTangents()
{
return m_tangents;
}
inline bool hasAutomation() const
{
return m_timeMap.isEmpty() == false;
}
float valueAt( const midiTime & _time ) const;
float *valuesAfter( const midiTime & _time ) const;
const QString name() const;
@@ -110,12 +141,18 @@ public slots:
private:
void cleanObjects();
void generateTangents();
void generateTangents( timeMap::const_iterator it, int numToGenerate );
float valueAt( timeMap::const_iterator v, int offset ) const;
AutomationTrack * m_autoTrack;
QVector<jo_id_t> m_idsToResolve;
objectVector m_objects;
timeMap m_timeMap; // actual values
timeMap m_tangents; // slope at each point for calculating spline
QString m_tension;
bool m_hasAutomation;
ProgressionTypes m_progressionType;
friend class AutomationPatternView;

View File

@@ -41,7 +41,9 @@
AutomationPattern::AutomationPattern( AutomationTrack * _auto_track ) :
trackContentObject( _auto_track ),
m_autoTrack( _auto_track ),
m_objects()
m_objects(),
m_tension( "1.0" ),
m_progressionType( DiscreteProgression )
{
changeLength( midiTime( 1, 0 ) );
}
@@ -52,12 +54,15 @@ AutomationPattern::AutomationPattern( AutomationTrack * _auto_track ) :
AutomationPattern::AutomationPattern( const AutomationPattern & _pat_to_copy ) :
trackContentObject( _pat_to_copy.m_autoTrack ),
m_autoTrack( _pat_to_copy.m_autoTrack ),
m_objects( _pat_to_copy.m_objects )
m_objects( _pat_to_copy.m_objects ),
m_tension( _pat_to_copy.m_tension ),
m_progressionType( _pat_to_copy.m_progressionType )
{
for( timeMap::const_iterator it = _pat_to_copy.m_timeMap.begin();
it != _pat_to_copy.m_timeMap.end(); ++it )
{
m_timeMap[it.key()] = it.value();
m_tangents[it.key()] = _pat_to_copy.m_tangents[it.key()];
}
}
@@ -115,6 +120,35 @@ void AutomationPattern::addObject( AutomatableModel * _obj, bool _search_dup )
void AutomationPattern::setProgressionType(
ProgressionTypes _new_progression_type )
{
if ( _new_progression_type == DiscreteProgression ||
_new_progression_type == LinearProgression ||
_new_progression_type == CubicHermiteProgression )
{
m_progressionType = _new_progression_type;
emit dataChanged();
}
}
void AutomationPattern::setTension( QString _new_tension )
{
bool ok;
float nt = _new_tension.toFloat( & ok );
if( ok && nt > -0.01 && nt < 1.01 )
{
m_tension = _new_tension;
}
}
const AutomatableModel * AutomationPattern::firstObject() const
{
AutomatableModel * m;
@@ -159,6 +193,12 @@ midiTime AutomationPattern::putValue( const midiTime & _time,
_time;
m_timeMap[newTime] = _value;
timeMap::const_iterator it = m_timeMap.find( newTime );
if( it != m_timeMap.begin() )
{
it--;
}
generateTangents(it, 3);
// we need to maximize our length in case we're part of a hidden
// automation track as the user can't resize this pattern
@@ -180,6 +220,13 @@ void AutomationPattern::removeValue( const midiTime & _time )
cleanObjects();
m_timeMap.remove( _time );
m_tangents.remove( _time );
timeMap::const_iterator it = m_timeMap.lowerBound( _time );
if( it != m_timeMap.begin() )
{
it--;
}
generateTangents(it, 3);
if( getTrack() &&
getTrack()->type() == track::HiddenAutomationTrack )
@@ -214,7 +261,68 @@ float AutomationPattern::valueAt( const midiTime & _time ) const
return 0;
}
return (v-1).value();
return valueAt( v-1, _time - (v-1).key() );
}
float AutomationPattern::valueAt( timeMap::const_iterator v, int offset ) const
{
if( m_progressionType == DiscreteProgression || v == m_timeMap.end() )
{
return v.value();
}
else if( m_progressionType == LinearProgression )
{
float slope = ((v+1).value() - v.value()) /
((v+1).key() - v.key());
return v.value() + offset * slope;
}
else /* CubicHermiteProgression */
{
// Implements a Cubic Hermite spline as explained at:
// http://en.wikipedia.org/wiki/Cubic_Hermite_spline#Unit_interval_.280.2C_1.29
//
// Note that we are not interpolating a 2 dimensional point over
// time as the article describes. We are interpolating a single
// value: y. To make this work we map the values of x that this
// segment spans to values of t for t = 0.0 -> 1.0 and scale the
// tangents _m1 and _m2
int numValues = ((v+1).key() - v.key());
float t = (float) offset / (float) numValues;
float m1 = (m_tangents[v.key()]) * numValues
* m_tension.toFloat();
float m2 = (m_tangents[(v+1).key()]) * numValues
* m_tension.toFloat();
return ( 2*pow(t,3) - 3*pow(t,2) + 1 ) * v.value()
+ ( pow(t,3) - 2*pow(t,2) + t) * m1
+ ( -2*pow(t,3) + 3*pow(t,2) ) * (v+1).value()
+ ( pow(t,3) - pow(t,2) ) * m2;
}
}
float *AutomationPattern::valuesAfter( const midiTime & _time ) const
{
timeMap::ConstIterator v = m_timeMap.lowerBound( _time );
if( v == m_timeMap.end() || (v+1) == m_timeMap.end() )
{
return NULL;
}
int numValues = (v+1).key() - v.key();
float *ret = new float[numValues];
for( int i = 0; i < numValues; i++ )
{
ret[i] = valueAt( v, i );
}
return ret;
}
@@ -225,6 +333,8 @@ void AutomationPattern::saveSettings( QDomDocument & _doc, QDomElement & _this )
_this.setAttribute( "pos", startPosition() );
_this.setAttribute( "len", trackContentObject::length() );
_this.setAttribute( "name", name() );
_this.setAttribute( "prog", QString::number( progressionType() ) );
_this.setAttribute( "tens", getTension() );
for( timeMap::const_iterator it = m_timeMap.begin();
it != m_timeMap.end(); ++it )
@@ -256,6 +366,9 @@ void AutomationPattern::loadSettings( const QDomElement & _this )
movePosition( _this.attribute( "pos" ).toInt() );
setName( _this.attribute( "name" ) );
setProgressionType( static_cast<ProgressionTypes>( _this.attribute(
"prog" ).toInt() ) );
setTension( _this.attribute( "tens" ) );
for( QDomNode node = _this.firstChild(); !node.isNull();
node = node.nextSibling() )
@@ -282,6 +395,7 @@ void AutomationPattern::loadSettings( const QDomElement & _this )
len = length();
}
changeLength( len );
generateTangents();
}
@@ -442,6 +556,7 @@ void AutomationPattern::resolveAllIDs()
void AutomationPattern::clear()
{
m_timeMap.clear();
m_tangents.clear();
emit dataChanged();
@@ -476,6 +591,7 @@ void AutomationPattern::objectDestroyed( jo_id_t _id )
void AutomationPattern::cleanObjects()
{
for( objectVector::iterator it = m_objects.begin(); it != m_objects.end(); )
@@ -492,5 +608,47 @@ void AutomationPattern::cleanObjects()
}
#include "moc_AutomationPattern.cxx"
void AutomationPattern::generateTangents()
{
generateTangents(m_timeMap.begin(), m_timeMap.size());
}
void AutomationPattern::generateTangents( timeMap::const_iterator it,
int numToGenerate )
{
if( m_timeMap.size() < 2 )
{
m_tangents[it.key()] = 0;
return;
}
for( int i = 0; i < numToGenerate; i++ )
{
if( it == m_timeMap.begin() )
{
m_tangents[it.key()] =
( (it+1).value() - (it).value() ) /
( (it+1).key() - (it).key() );
}
else if( it+1 == m_timeMap.end() )
{
m_tangents[it.key()] = 0;
return;
}
else
{
m_tangents[it.key()] =
( (it+1).value() - (it-1).value() ) /
( (it+1).key() - (it-1).key() );
}
it++;
}
}
#include "moc_AutomationPattern.cxx"

View File

@@ -169,8 +169,6 @@ AutomationEditor::AutomationEditor() :
tr( "Click here if you want to stop playing of the "
"current pattern." ) );
removeSelection();
// init scrollbars
@@ -241,6 +239,70 @@ AutomationEditor::AutomationEditor() :
"mode. You can also press 'Shift+M' on your keyboard "
"to activate this mode." ) );
m_discreteButton = new toolButton( embed::getIconPixmap(
"progression_discrete" ),
tr( "Discrete progression" ),
this, SLOT( discreteButtonToggled() ),
m_toolBar );
m_discreteButton->setCheckable( true );
m_discreteButton->setChecked( true );
m_linearButton = new toolButton( embed::getIconPixmap(
"progression_linear" ),
tr( "Linear progression" ),
this, SLOT( linearButtonToggled() ),
m_toolBar );
m_linearButton->setCheckable( true );
m_cubicHermiteButton = new toolButton( embed::getIconPixmap(
"progression_cubic_hermite" ),
tr( "Cubic Hermite progression" ),
this, SLOT(
cubicHermiteButtonToggled() ),
m_toolBar );
m_cubicHermiteButton->setCheckable( true );
// setup tension-stuff
m_tensionComboBox = new comboBox( m_toolBar );
m_tensionComboBox->setFixedSize( 60, 22 );
for( int i = 0; i < 4; ++i )
{
m_tensionModel.addItem( "0." + QString::number( 25 * i ) );
}
m_tensionModel.addItem( "1.0" );
m_tensionModel.setValue( m_tensionModel.findText( "1.0" ) );
m_tensionDisabledModel.addItem( "-----" );
disableTensionComboBox();
connect( &m_tensionModel, SIGNAL( dataChanged() ),
this, SLOT( tensionChanged() ) );
tool_button_group = new QButtonGroup( this );
tool_button_group->addButton( m_discreteButton );
tool_button_group->addButton( m_linearButton );
tool_button_group->addButton( m_cubicHermiteButton );
tool_button_group->setExclusive( true );
m_discreteButton->setWhatsThis(
tr( "Click here to choose discrete progressions for this "
"automation pattern. The value of the connected "
"object will remain constant between control points "
"and be set immediately to the new value when each "
"control point is reached." ) );
m_linearButton->setWhatsThis(
tr( "Click here to choose linear progressions for this "
"automation pattern. The value of the connected "
"object will change at a steady rate over time "
"between control points to reach the correct value at "
"each control point without a sudden change." ) );
m_cubicHermiteButton->setWhatsThis(
tr( "Click here to choose cubic hermite progressions for this "
"automation pattern. The value of the connected "
"object will change in a smooth curve and ease in to "
"the peaks and valleys." ) );
m_cutButton = new toolButton( embed::getIconPixmap( "edit_cut" ),
tr( "Cut selected values (Ctrl+X)" ),
this, SLOT( cutSelectedValues() ),
@@ -335,6 +397,12 @@ AutomationEditor::AutomationEditor() :
tb_layout->addWidget( m_selectButton );
tb_layout->addWidget( m_moveButton );
tb_layout->addSpacing( 10 );
tb_layout->addWidget( m_discreteButton );
tb_layout->addWidget( m_linearButton );
tb_layout->addWidget( m_cubicHermiteButton );
tb_layout->addSpacing( 5 );
tb_layout->addWidget( m_tensionComboBox );
tb_layout->addSpacing( 10 );
tb_layout->addWidget( m_cutButton );
tb_layout->addWidget( m_copyButton );
tb_layout->addWidget( m_pasteButton );
@@ -385,6 +453,7 @@ AutomationEditor::~AutomationEditor()
{
m_zoomingXModel.disconnect();
m_zoomingYModel.disconnect();
m_tensionModel.disconnect();
}
@@ -455,6 +524,29 @@ void AutomationEditor::updateAfterPatternChange()
return;
}
if( m_pattern->progressionType() ==
AutomationPattern::DiscreteProgression &&
!m_discreteButton->isChecked() )
{
m_discreteButton->setChecked( true );
}
if( m_pattern->progressionType() ==
AutomationPattern::LinearProgression &&
!m_linearButton->isChecked() )
{
m_linearButton->setChecked( true );
}
if( m_pattern->progressionType() ==
AutomationPattern::CubicHermiteProgression &&
!m_cubicHermiteButton->isChecked() )
{
m_cubicHermiteButton->setChecked( true );
}
m_tensionModel.setValue( m_tensionModel.findText(
m_pattern->getTension() ) );
m_minLevel = m_pattern->firstObject()->minValue<float>();
m_maxLevel = m_pattern->firstObject()->maxValue<float>();
m_step = m_pattern->firstObject()->step<float>();
@@ -487,28 +579,6 @@ void AutomationEditor::update()
inline void AutomationEditor::drawValueRect( QPainter & _p,
int _x, int _y,
int _width, int _height,
const bool _is_selected )
{
QColor current_color( 0xFF, 0xB0, 0x00 );
if( _is_selected == TRUE )
{
current_color.setRgb( 0x00, 0x40, 0xC0 );
}
_p.fillRect( _x, _y, _width, _height, current_color );
_p.drawLine( _x - 1, _y, _x + 1, _y );
_p.drawLine( _x, _y - 1, _x, _y + 1 );
// _p.setPen( QColor( 0xFF, 0x9F, 0x00 ) );
// _p.setPen( QColor( 0xFF, 0xFF, 0x40 ) );
}
void AutomationEditor::removeSelection()
{
m_selectStartTick = 0;
@@ -727,6 +797,19 @@ void AutomationEditor::drawLine( int _x0, float _y0, int _x1, float _y1 )
void AutomationEditor::disableTensionComboBox()
{
m_tensionComboBox->setEnabled( false );
m_tensionComboBox->setModel( &m_tensionDisabledModel );
m_tensionComboBox->setToolTip(
tr( "Choose Cubic Hermite progression to enable" ) );
m_tensionComboBox->setWhatsThis(
tr( "Choose Cubic Hermite progression to enable" ) );
}
void AutomationEditor::mousePressEvent( QMouseEvent * _me )
{
QMutexLocker m( &m_patternMutex );
@@ -1446,103 +1529,74 @@ void AutomationEditor::paintEvent( QPaintEvent * _pe )
if( validPattern() )
{
Sint32 len_ticks = 4;
timeMap & time_map = m_pattern->getTimeMap();
timeMap::iterator it = time_map.begin();
p.setPen( QColor( 0xFF, 0xDF, 0x20 ) );
while( it != time_map.end() )
while( it+1 != time_map.end() )
{
Sint32 len_ticks = 4;
// skip this section if it occurs completely before the
// visible area
int next_x = xCoordOfTick( (it+1).key() );
if( next_x < 0 )
{
++it;
continue;
}
const float level = it.value();
Sint32 pos_ticks = it.key();
const int x = ( pos_ticks - m_currentPosition ) *
m_ppt / DefaultTicksPerTact;
if( x > width() - VALUES_WIDTH )
int x = xCoordOfTick( it.key() );
if( x > width() )
{
break;
}
int rect_width;
if( it+1 != time_map.end() )
bool is_selected = FALSE;
// if we're in move-mode, we may only draw
// values in selected area, that have originally
// been selected and not values that are now in
// selection because the user moved it...
if( m_editMode == MOVE )
{
timeMap::iterator it_prev = it+1;
Sint32 next_pos_ticks = it_prev.key();
int next_x = ( next_pos_ticks
- m_currentPosition ) * m_ppt /
DefaultTicksPerTact;
// skip this value if not in visible area at all
/* if( next_x > width() )
{
break;
}*/
rect_width = next_x - x;
}
else
{
rect_width = width() - x;
}
// is the value in visible area?
if( ( level >= m_bottomLevel && level <= m_topLevel )
|| ( level > m_topLevel && m_topLevel >= 0 )
|| ( level < m_bottomLevel
&& m_bottomLevel <= 0 ) )
{
bool is_selected = FALSE;
// if we're in move-mode, we may only draw
// values in selected area, that have originally
// been selected and not values that are now in
// selection because the user moved it...
if( m_editMode == MOVE )
{
if( m_selValuesForMove.contains(
it.key() ) )
{
is_selected = TRUE;
}
}
else if( level >= selLevel_start &&
level <= selLevel_end &&
pos_ticks >= sel_pos_start &&
pos_ticks + len_ticks <=
sel_pos_end )
if( m_selValuesForMove.contains( it.key() ) )
{
is_selected = TRUE;
}
// we've done and checked all, lets draw the
// value
int y_start;
int rect_height;
if( m_y_auto )
{
y_start = (int)( grid_bottom
- ( grid_bottom - TOP_MARGIN )
* ( level - m_minLevel )
/ ( m_maxLevel - m_minLevel ) );
int y_end = (int)( grid_bottom
+ ( grid_bottom - TOP_MARGIN )
* m_minLevel
/ ( m_maxLevel - m_minLevel ) );
rect_height = y_end - y_start;
}
else
{
y_start = (int)( grid_bottom - ( level
- m_bottomLevel )
* m_y_delta );
rect_height = (int)( level * m_y_delta );
}
drawValueRect( p, x + VALUES_WIDTH, y_start,
rect_width, rect_height,
is_selected );
}
else printf("not in range\n");
else if( it.value() >= selLevel_start &&
it.value() <= selLevel_end &&
it.key() >= sel_pos_start &&
it.key() + len_ticks <= sel_pos_end )
{
is_selected = TRUE;
}
float *values = m_pattern->valuesAfter( it.key() );
for( int i = 0; i < (it+1).key() - it.key(); i++ )
{
drawLevelTick( p, it.key() + i, values[i],
is_selected );
}
delete [] values;
// Draw cross
int y = yCoordOfLevel( it.value() );
p.drawLine( x - 1, y, x + 1, y );
p.drawLine( x, y - 1, x, y + 1 );
// _p.setPen( QColor( 0xFF, 0x9F, 0x00 ) );
// _p.setPen( QColor( 0xFF, 0xFF, 0x40 ) );
++it;
}
for( int i = it.key(), x = xCoordOfTick( i ); x <= width();
i++, x = xCoordOfTick( i ) )
{
// TODO: Find out if the section after the last control
// point is able to be selected and if so set this
// boolean correctly
drawLevelTick( p, i, it.value(), false );
}
}
else
{
@@ -1611,6 +1665,79 @@ void AutomationEditor::paintEvent( QPaintEvent * _pe )
int AutomationEditor::xCoordOfTick( int _tick )
{
return VALUES_WIDTH + ( ( _tick - m_currentPosition )
* m_ppt / DefaultTicksPerTact );
}
int AutomationEditor::yCoordOfLevel( float _level )
{
int grid_bottom = height() - SCROLLBAR_SIZE - 1;
if( m_y_auto )
{
return (int)( grid_bottom - ( grid_bottom - TOP_MARGIN )
* ( _level - m_minLevel )
/ ( m_maxLevel - m_minLevel ) );
}
else
{
return (int)( grid_bottom - ( _level - m_bottomLevel )
* m_y_delta );
}
}
void AutomationEditor::drawLevelTick( QPainter & _p, int _tick, float _level,
bool _is_selected )
{
int grid_bottom = height() - SCROLLBAR_SIZE - 1;
const int x = xCoordOfTick( _tick );
int rect_width = xCoordOfTick( _tick+1 ) - x;
// is the level in visible area?
if( ( _level >= m_bottomLevel && _level <= m_topLevel )
|| ( _level > m_topLevel && m_topLevel >= 0 )
|| ( _level < m_bottomLevel && m_bottomLevel <= 0 ) )
{
int y_start = yCoordOfLevel( _level );
int rect_height;
if( m_y_auto )
{
int y_end = (int)( grid_bottom
+ ( grid_bottom - TOP_MARGIN )
* m_minLevel
/ ( m_maxLevel - m_minLevel ) );
rect_height = y_end - y_start;
}
else
{
rect_height = (int)( _level * m_y_delta );
}
QColor current_color( 0xFF, 0xB0, 0x00 );
if( _is_selected == TRUE )
{
current_color.setRgb( 0x00, 0x40, 0xC0 );
}
_p.fillRect( x, y_start, rect_width, rect_height, current_color );
}
else
{
printf("not in range\n");
}
}
// responsible for moving/resizing scrollbars after window-resizing
void AutomationEditor::resizeEvent( QResizeEvent * )
{
@@ -1858,6 +1985,71 @@ void AutomationEditor::moveButtonToggled()
void AutomationEditor::discreteButtonToggled()
{
if ( validPattern() )
{
QMutexLocker m( &m_patternMutex );
disableTensionComboBox();
m_pattern->setProgressionType(
AutomationPattern::DiscreteProgression );
engine::getSong()->setModified();
update();
}
}
void AutomationEditor::linearButtonToggled()
{
if ( validPattern() )
{
QMutexLocker m( &m_patternMutex );
disableTensionComboBox();
m_pattern->setProgressionType(
AutomationPattern::LinearProgression );
engine::getSong()->setModified();
update();
}
}
void AutomationEditor::cubicHermiteButtonToggled()
{
if ( validPattern() )
{
QMutexLocker m( &m_patternMutex );
m_tensionComboBox->setEnabled( true );
m_tensionComboBox->setModel( &m_tensionModel );
m_tensionComboBox->setToolTip(
tr( "Tension value for spline" ) );
m_tensionComboBox->setWhatsThis(
tr( "A higher tension value may make a smoother curve "
"but overshoot some values. A low tension "
"value will cause the slope of the curve to "
"level off at each control point." ) );
m_pattern->setProgressionType(
AutomationPattern::CubicHermiteProgression );
engine::getSong()->setModified();
update();
}
}
void AutomationEditor::tensionChanged()
{
m_pattern->setTension( m_tensionModel.currentText() );
update();
}
void AutomationEditor::selectAll()
{
QMutexLocker m( &m_patternMutex );

View File

@@ -237,28 +237,39 @@ void AutomationPatternView::paintEvent( QPaintEvent * )
QLinearGradient lin2grad( 0, min, 0, max );
const QColor cl = QColor( 255, 224, 0 );
const QColor cd = QColor( 229, 158, 0 );
lin2grad.setColorAt( 1, cl );
lin2grad.setColorAt( 0, cd );
// TODO: skip this part for patterns or parts of the pattern that will
// not be on the screen
for( AutomationPattern::timeMap::const_iterator it =
m_pat->getTimeMap().begin();
it != m_pat->getTimeMap().end(); ++it )
{
const float x1 = x_base + it.key() * ppt /
if( it+1 == m_pat->getTimeMap().end() )
{
const float x1 = x_base + it.key() * ppt /
midiTime::ticksPerTact();
float x2;
if( it+1 != m_pat->getTimeMap().end() )
{
x2 = x_base + (it+1).key() * ppt /
midiTime::ticksPerTact() + 1;
const float x2 = (float)( width() - TCO_BORDER_WIDTH );
p.fillRect( QRectF( x1, 0.0f, x2-x1, it.value() ),
lin2grad );
break;
}
else
float *values = m_pat->valuesAfter( it.key() );
for( int i = it.key(); i < (it+1).key(); i++ )
{
x2 = (float)( width() - TCO_BORDER_WIDTH );
float value = values[i - it.key()];
const float x1 = x_base + i * ppt /
midiTime::ticksPerTact();
const float x2 = x_base + (i+1) * ppt /
midiTime::ticksPerTact();
p.fillRect( QRectF( x1, 0.0f, x2-x1, value ),
lin2grad );
}
p.fillRect( QRectF( x1, 0.0f, x2-x1, it.value() ),
lin2grad );
delete [] values;
}
p.resetMatrix();