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:
committed by
Tobias Doerffel
parent
ddad2da162
commit
6249b23f1f
@@ -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"
|
||||
|
||||
@@ -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 );
|
||||
|
||||
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user