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

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"