From 6249b23f1ff2740732bf53be89a2d9d4dfd37db9 Mon Sep 17 00:00:00 2001 From: Joel Muzzerall Date: Wed, 8 Jan 2014 00:04:18 +0100 Subject: [PATCH] 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 --- .../default/progression_cubic_hermite.png | Bin 0 -> 581 bytes data/themes/default/progression_discrete.png | Bin 0 -> 443 bytes data/themes/default/progression_linear.png | Bin 0 -> 627 bytes include/AutomationEditor.h | 21 +- include/AutomationPattern.h | 37 ++ src/core/AutomationPattern.cpp | 166 +++++++- src/gui/AutomationEditor.cpp | 400 +++++++++++++----- src/gui/AutomationPatternView.cpp | 33 +- 8 files changed, 534 insertions(+), 123 deletions(-) create mode 100644 data/themes/default/progression_cubic_hermite.png create mode 100644 data/themes/default/progression_discrete.png create mode 100644 data/themes/default/progression_linear.png diff --git a/data/themes/default/progression_cubic_hermite.png b/data/themes/default/progression_cubic_hermite.png new file mode 100644 index 0000000000000000000000000000000000000000..77be5656203b18807cd0cbc36ceaeeb0b670a1bf GIT binary patch literal 581 zcmV-L0=oT)P)+qBcq_f|WLsLP#n_k;Gi`EOyUqF1we#Xuf9Vy`A}H=FNMvK@Ll^j9uQme*0ce z)N3p((wI)yt!644)2SZvo4rs*(2->bb2G&moIB5}m)jGRCD8E?5Wmyz zhKf0Z|Ve0#BNGBte0NI%DOa6aFI zf>)B$?}$bU_DdTqVu)m6k>e+N2DpgpC4qYf7))QJ*eKhNudtb(tPHqEmChNJt0&}vez?M{G&MIp(QT|r}d z9LQ|iIFp-2g@k4JkP$57ceXc}-L(NsY&QP`5}&r8 TrVwA900000NkvXXu0mjfeX;@E literal 0 HcmV?d00001 diff --git a/data/themes/default/progression_discrete.png b/data/themes/default/progression_discrete.png new file mode 100644 index 0000000000000000000000000000000000000000..b28bb29c6c127f5550e7d57991f2b63ae4639cc4 GIT binary patch literal 443 zcmV;s0Yv_ZP)sF z!lhCaN%WGn*gbNA+y&0tubG|wGqc}*v%5k8qds&_7X8O(D&c#Oq!2{Lk3(aF$h4){ zS%tCyxh;bXddqikbPRX5s{@p6AYU&4`jxuSRf892h>Am)WtddDVGAgL+0umr002ovPDHLkV1nqUy|DlQ literal 0 HcmV?d00001 diff --git a/data/themes/default/progression_linear.png b/data/themes/default/progression_linear.png new file mode 100644 index 0000000000000000000000000000000000000000..8059e793fd9e510aff19d71d7f93d413daefa1eb GIT binary patch literal 627 zcmV-(0*w8MP)LCl0^L*;rRnpwm^b5_#y2y9e&KQMIq&?s=iGa4ki+^cy;th_Hw&7g zzmL^5N>dfSJW-*gsmeO!h0Rdpph}An7N_^l;PMDBU$k~mlt9%!K>X6wwi6FAj=%PS zxkn`B33gIMm2i#phTyj7lwed8YyTD6WT}%t+%D8fBKI@?YG66?U?2V^{eA3gSD30% zj*j76j-SNm$YTm^>S%zCrNY|3$IyTBKN!K=7A*)*X2g#21To$78;hx-J>=anH6K%h z1$NyrH6Bx+YCHJ7&``icv?3U2()aeB80Ib6p;ixQm2 z9B$$sZs9~jo#5x|I$1|n`{6c^dz$sD0ng@z%01X31Y#rvV=)@e@r4v()LEocI-Tv{ z`}11R1S@Tnr!Dj><$F+R+_e^sZM;d%bh=1Z*El;^J2{hWIc_(=(dwv3!6*hDB)YOZ z*2@wsA9R4;&1w&}4k7z&*aY}G timeMap; typedef QVector > 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 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; diff --git a/src/core/AutomationPattern.cpp b/src/core/AutomationPattern.cpp index e4cb69f5b..a29ee0025 100644 --- a/src/core/AutomationPattern.cpp +++ b/src/core/AutomationPattern.cpp @@ -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( _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" diff --git a/src/gui/AutomationEditor.cpp b/src/gui/AutomationEditor.cpp index a99be753c..aef24e08c 100644 --- a/src/gui/AutomationEditor.cpp +++ b/src/gui/AutomationEditor.cpp @@ -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(); m_maxLevel = m_pattern->firstObject()->maxValue(); m_step = m_pattern->firstObject()->step(); @@ -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 ); diff --git a/src/gui/AutomationPatternView.cpp b/src/gui/AutomationPatternView.cpp index bc8db5c87..3d2638c94 100644 --- a/src/gui/AutomationPatternView.cpp +++ b/src/gui/AutomationPatternView.cpp @@ -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();