mirror of
https://github.com/LMMS/lmms.git
synced 2026-03-18 14:08:26 -04:00
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"
|
||||
|
||||
Reference in New Issue
Block a user