1 /*
2  * AutomationPattern.cpp - implementation of class AutomationPattern which
3  *                         holds dynamic values
4  *
5  * Copyright (c) 2008-2014 Tobias Doerffel <tobydox/at/users.sourceforge.net>
6  * Copyright (c) 2006-2008 Javier Serrano Polo <jasp00/at/users.sourceforge.net>
7  *
8  * This file is part of LMMS - https://lmms.io
9  *
10  * This program is free software; you can redistribute it and/or
11  * modify it under the terms of the GNU General Public
12  * License as published by the Free Software Foundation; either
13  * version 2 of the License, or (at your option) any later version.
14  *
15  * This program is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
18  * General Public License for more details.
19  *
20  * You should have received a copy of the GNU General Public
21  * License along with this program (see COPYING); if not, write to the
22  * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
23  * Boston, MA 02110-1301 USA.
24  *
25  */
26 
27 #include "AutomationPattern.h"
28 
29 #include "AutomationPatternView.h"
30 #include "AutomationTrack.h"
31 #include "LocaleHelper.h"
32 #include "Note.h"
33 #include "ProjectJournal.h"
34 #include "BBTrackContainer.h"
35 #include "Song.h"
36 
37 #include <cmath>
38 
39 int AutomationPattern::s_quantization = 1;
40 const float AutomationPattern::DEFAULT_MIN_VALUE = 0;
41 const float AutomationPattern::DEFAULT_MAX_VALUE = 1;
42 
43 
AutomationPattern(AutomationTrack * _auto_track)44 AutomationPattern::AutomationPattern( AutomationTrack * _auto_track ) :
45 	TrackContentObject( _auto_track ),
46 	m_autoTrack( _auto_track ),
47 	m_objects(),
48 	m_tension( 1.0 ),
49 	m_progressionType( DiscreteProgression ),
50 	m_dragging( false ),
51 	m_isRecording( false ),
52 	m_lastRecordedValue( 0 )
53 {
54 	changeLength( MidiTime( 1, 0 ) );
55 	if( getTrack() )
56 	{
57 		switch( getTrack()->trackContainer()->type() )
58 		{
59 			case TrackContainer::BBContainer:
60 				setAutoResize( true );
61 				break;
62 
63 			case TrackContainer::SongContainer:
64 				// move down
65 			default:
66 				setAutoResize( false );
67 				break;
68 		}
69 	}
70 }
71 
72 
73 
74 
AutomationPattern(const AutomationPattern & _pat_to_copy)75 AutomationPattern::AutomationPattern( const AutomationPattern & _pat_to_copy ) :
76 	TrackContentObject( _pat_to_copy.m_autoTrack ),
77 	m_autoTrack( _pat_to_copy.m_autoTrack ),
78 	m_objects( _pat_to_copy.m_objects ),
79 	m_tension( _pat_to_copy.m_tension ),
80 	m_progressionType( _pat_to_copy.m_progressionType )
81 {
82 	for( timeMap::const_iterator it = _pat_to_copy.m_timeMap.begin();
83 				it != _pat_to_copy.m_timeMap.end(); ++it )
84 	{
85 		m_timeMap[it.key()] = it.value();
86 		m_tangents[it.key()] = _pat_to_copy.m_tangents[it.key()];
87 	}
88 	switch( getTrack()->trackContainer()->type() )
89 	{
90 		case TrackContainer::BBContainer:
91 			setAutoResize( true );
92 			break;
93 
94 		case TrackContainer::SongContainer:
95 			// move down
96 		default:
97 			setAutoResize( false );
98 			break;
99 	}
100 }
101 
102 
103 
104 
~AutomationPattern()105 AutomationPattern::~AutomationPattern()
106 {
107 }
108 
109 
110 
111 
addObject(AutomatableModel * _obj,bool _search_dup)112 bool AutomationPattern::addObject( AutomatableModel * _obj, bool _search_dup )
113 {
114 	if( _search_dup && m_objects.contains(_obj) )
115 	{
116 		return false;
117 	}
118 
119 	// the automation track is unconnected and there is nothing in the track
120 	if( m_objects.isEmpty() && hasAutomation() == false )
121 	{
122 		// then initialize first value
123 		putValue( MidiTime(0), _obj->inverseScaledValue( _obj->value<float>() ), false );
124 	}
125 
126 	m_objects += _obj;
127 
128 	connect( _obj, SIGNAL( destroyed( jo_id_t ) ),
129 			this, SLOT( objectDestroyed( jo_id_t ) ),
130 						Qt::DirectConnection );
131 
132 	emit dataChanged();
133 
134 	return true;
135 }
136 
137 
138 
139 
setProgressionType(ProgressionTypes _new_progression_type)140 void AutomationPattern::setProgressionType(
141 					ProgressionTypes _new_progression_type )
142 {
143 	if ( _new_progression_type == DiscreteProgression ||
144 		_new_progression_type == LinearProgression ||
145 		_new_progression_type == CubicHermiteProgression )
146 	{
147 		m_progressionType = _new_progression_type;
148 		emit dataChanged();
149 	}
150 }
151 
152 
153 
154 
setTension(QString _new_tension)155 void AutomationPattern::setTension( QString _new_tension )
156 {
157 	bool ok;
158 	float nt = LocaleHelper::toFloat(_new_tension, & ok);
159 
160 	if( ok && nt > -0.01 && nt < 1.01 )
161 	{
162 		m_tension = nt;
163 	}
164 }
165 
166 
167 
168 
firstObject() const169 const AutomatableModel * AutomationPattern::firstObject() const
170 {
171 	AutomatableModel * m;
172 	if( !m_objects.isEmpty() && ( m = m_objects.first() ) != NULL )
173 	{
174 		return m;
175 	}
176 
177 	static FloatModel _fm( 0, DEFAULT_MIN_VALUE, DEFAULT_MAX_VALUE, 0.001 );
178 	return &_fm;
179 }
180 
objects() const181 const AutomationPattern::objectVector& AutomationPattern::objects() const
182 {
183 	return m_objects;
184 }
185 
186 
187 
188 
timeMapLength() const189 MidiTime AutomationPattern::timeMapLength() const
190 {
191 	if( m_timeMap.isEmpty() ) return 0;
192 	timeMap::const_iterator it = m_timeMap.end();
193 	return MidiTime( MidiTime( (it-1).key() ).nextFullTact(), 0 );
194 }
195 
196 
197 
198 
updateLength()199 void AutomationPattern::updateLength()
200 {
201 	changeLength( timeMapLength() );
202 }
203 
204 
205 
206 
putValue(const MidiTime & time,const float value,const bool quantPos,const bool ignoreSurroundingPoints)207 MidiTime AutomationPattern::putValue( const MidiTime & time,
208 					const float value,
209 					const bool quantPos,
210 					const bool ignoreSurroundingPoints )
211 {
212 	cleanObjects();
213 
214 	MidiTime newTime = quantPos ?
215 				Note::quantized( time, quantization() ) :
216 				time;
217 
218 	m_timeMap[ newTime ] = value;
219 	timeMap::const_iterator it = m_timeMap.find( newTime );
220 
221 	// Remove control points that are covered by the new points
222 	// quantization value. Control Key to override
223 	if( ! ignoreSurroundingPoints )
224 	{
225 		for( int i = newTime + 1; i < newTime + quantization(); ++i )
226 		{
227 			AutomationPattern::removeValue( i );
228 		}
229 	}
230 	if( it != m_timeMap.begin() )
231 	{
232 		--it;
233 	}
234 	generateTangents( it, 3 );
235 
236 	// we need to maximize our length in case we're part of a hidden
237 	// automation track as the user can't resize this pattern
238 	if( getTrack() && getTrack()->type() == Track::HiddenAutomationTrack )
239 	{
240 		updateLength();
241 	}
242 
243 	emit dataChanged();
244 
245 	return newTime;
246 }
247 
248 
249 
250 
removeValue(const MidiTime & time)251 void AutomationPattern::removeValue( const MidiTime & time )
252 {
253 	cleanObjects();
254 
255 	m_timeMap.remove( time );
256 	m_tangents.remove( time );
257 	timeMap::const_iterator it = m_timeMap.lowerBound( time );
258 	if( it != m_timeMap.begin() )
259 	{
260 		--it;
261 	}
262 	generateTangents(it, 3);
263 
264 	if( getTrack() && getTrack()->type() == Track::HiddenAutomationTrack )
265 	{
266 		updateLength();
267 	}
268 
269 	emit dataChanged();
270 }
271 
272 
273 
recordValue(MidiTime time,float value)274 void AutomationPattern::recordValue(MidiTime time, float value)
275 {
276 	if( value != m_lastRecordedValue )
277 	{
278 		putValue( time, value, true );
279 		m_lastRecordedValue = value;
280 	}
281 	else if( valueAt( time ) != value )
282 	{
283 		removeValue( time );
284 	}
285 }
286 
287 
288 
289 
290 /**
291  * @brief Set the position of the point that is being dragged.
292  *        Calling this function will also automatically set m_dragging to true,
293  *        which applyDragValue() have to be called to m_dragging.
294  * @param the time(x position) of the point being dragged
295  * @param the value(y position) of the point being dragged
296  * @param true to snip x position
297  * @return
298  */
setDragValue(const MidiTime & time,const float value,const bool quantPos,const bool controlKey)299 MidiTime AutomationPattern::setDragValue( const MidiTime & time,
300 						const float value,
301 						const bool quantPos,
302 						const bool controlKey )
303 {
304 	if( m_dragging == false )
305 	{
306 		MidiTime newTime = quantPos  ?
307 				Note::quantized( time, quantization() ) :
308 							time;
309 		this->removeValue( newTime );
310 		m_oldTimeMap = m_timeMap;
311 		m_dragging = true;
312 	}
313 
314 	//Restore to the state before it the point were being dragged
315 	m_timeMap = m_oldTimeMap;
316 
317 	for( timeMap::const_iterator it = m_timeMap.begin(); it != m_timeMap.end(); ++it )
318 	{
319 		generateTangents( it, 3 );
320 	}
321 
322 	return this->putValue( time, value, quantPos, controlKey );
323 
324 }
325 
326 
327 
328 
329 /**
330  * @brief After the point is dragged, this function is called to apply the change.
331  */
applyDragValue()332 void AutomationPattern::applyDragValue()
333 {
334 	m_dragging = false;
335 }
336 
337 
338 
339 
valueAt(const MidiTime & _time) const340 float AutomationPattern::valueAt( const MidiTime & _time ) const
341 {
342 	if( m_timeMap.isEmpty() )
343 	{
344 		return 0;
345 	}
346 
347 	if( m_timeMap.contains( _time ) )
348 	{
349 		return m_timeMap[_time];
350 	}
351 
352 	// lowerBound returns next value with greater key, therefore we take
353 	// the previous element to get the current value
354 	timeMap::ConstIterator v = m_timeMap.lowerBound( _time );
355 
356 	if( v == m_timeMap.begin() )
357 	{
358 		return 0;
359 	}
360 	if( v == m_timeMap.end() )
361 	{
362 		return (v-1).value();
363 	}
364 
365 	return valueAt( v-1, _time - (v-1).key() );
366 }
367 
368 
369 
370 
valueAt(timeMap::const_iterator v,int offset) const371 float AutomationPattern::valueAt( timeMap::const_iterator v, int offset ) const
372 {
373 	if( m_progressionType == DiscreteProgression || v == m_timeMap.end() )
374 	{
375 		return v.value();
376 	}
377 	else if( m_progressionType == LinearProgression )
378 	{
379 		float slope = ((v+1).value() - v.value()) /
380 							((v+1).key() - v.key());
381 		return v.value() + offset * slope;
382 	}
383 	else /* CubicHermiteProgression */
384 	{
385 		// Implements a Cubic Hermite spline as explained at:
386 		// http://en.wikipedia.org/wiki/Cubic_Hermite_spline#Unit_interval_.280.2C_1.29
387 		//
388 		// Note that we are not interpolating a 2 dimensional point over
389 		// time as the article describes.  We are interpolating a single
390 		// value: y.  To make this work we map the values of x that this
391 		// segment spans to values of t for t = 0.0 -> 1.0 and scale the
392 		// tangents _m1 and _m2
393 		int numValues = ((v+1).key() - v.key());
394 		float t = (float) offset / (float) numValues;
395 		float m1 = (m_tangents[v.key()]) * numValues * m_tension;
396 		float m2 = (m_tangents[(v+1).key()]) * numValues * m_tension;
397 
398 		return ( 2*pow(t,3) - 3*pow(t,2) + 1 ) * v.value()
399 				+ ( pow(t,3) - 2*pow(t,2) + t) * m1
400 				+ ( -2*pow(t,3) + 3*pow(t,2) ) * (v+1).value()
401 				+ ( pow(t,3) - pow(t,2) ) * m2;
402 	}
403 }
404 
405 
406 
407 
valuesAfter(const MidiTime & _time) const408 float *AutomationPattern::valuesAfter( const MidiTime & _time ) const
409 {
410 	timeMap::ConstIterator v = m_timeMap.lowerBound( _time );
411 	if( v == m_timeMap.end() || (v+1) == m_timeMap.end() )
412 	{
413 		return NULL;
414 	}
415 
416 	int numValues = (v+1).key() - v.key();
417 	float *ret = new float[numValues];
418 
419 	for( int i = 0; i < numValues; i++ )
420 	{
421 		ret[i] = valueAt( v, i );
422 	}
423 
424 	return ret;
425 }
426 
427 
428 
429 
flipY(int min,int max)430 void AutomationPattern::flipY( int min, int max )
431 {
432 	timeMap tempMap = m_timeMap;
433 	timeMap::ConstIterator iterate = m_timeMap.lowerBound(0);
434 	float tempValue = 0;
435 
436 	int numPoints = 0;
437 
438 	for( int i = 0; ( iterate + i + 1 ) != m_timeMap.end() && ( iterate + i ) != m_timeMap.end() ; i++)
439 	{
440 		numPoints++;
441 	}
442 
443 	for( int i = 0; i <= numPoints; i++ )
444 	{
445 
446 		if ( min < 0 )
447 		{
448 			tempValue = valueAt( ( iterate + i ).key() ) * -1;
449 			putValue( MidiTime( (iterate + i).key() ) , tempValue, false);
450 		}
451 		else
452 		{
453 			tempValue = max - valueAt( ( iterate + i ).key() );
454 			putValue( MidiTime( (iterate + i).key() ) , tempValue, false);
455 		}
456 	}
457 
458 	generateTangents();
459 	emit dataChanged();
460 }
461 
462 
463 
464 
flipY()465 void AutomationPattern::flipY()
466 {
467 	flipY(getMin(), getMax());
468 }
469 
470 
471 
472 
flipX(int length)473 void AutomationPattern::flipX( int length )
474 {
475 	timeMap tempMap;
476 
477 	timeMap::ConstIterator iterate = m_timeMap.lowerBound(0);
478 	float tempValue = 0;
479 	int numPoints = 0;
480 
481 	for( int i = 0; ( iterate + i + 1 ) != m_timeMap.end() && ( iterate + i ) != m_timeMap.end() ; i++)
482 	{
483 		numPoints++;
484 	}
485 
486 	float realLength = ( iterate + numPoints ).key();
487 
488 	if ( length != -1 && length != realLength)
489 	{
490 		if ( realLength < length )
491 		{
492 			tempValue = valueAt( ( iterate + numPoints ).key() );
493 			putValue( MidiTime( length ) , tempValue, false);
494 			numPoints++;
495 			for( int i = 0; i <= numPoints; i++ )
496 			{
497 				tempValue = valueAt( ( iterate + i ).key() );
498 				MidiTime newTime = MidiTime( length - ( iterate + i ).key() );
499 				tempMap[newTime] = tempValue;
500 			}
501 		}
502 		else
503 		{
504 			for( int i = 0; i <= numPoints; i++ )
505 			{
506 				tempValue = valueAt( ( iterate + i ).key() );
507 				MidiTime newTime;
508 
509 				if ( ( iterate + i ).key() <= length )
510 				{
511 					newTime = MidiTime( length - ( iterate + i ).key() );
512 				}
513 				else
514 				{
515 					newTime = MidiTime( ( iterate + i ).key() );
516 				}
517 				tempMap[newTime] = tempValue;
518 			}
519 		}
520 	}
521 	else
522 	{
523 		for( int i = 0; i <= numPoints; i++ )
524 		{
525 			tempValue = valueAt( ( iterate + i ).key() );
526 			cleanObjects();
527 			MidiTime newTime = MidiTime( realLength - ( iterate + i ).key() );
528 			tempMap[newTime] = tempValue;
529 		}
530 	}
531 
532 	m_timeMap.clear();
533 
534 	m_timeMap = tempMap;
535 
536 	generateTangents();
537 	emit dataChanged();
538 }
539 
540 
541 
542 
saveSettings(QDomDocument & _doc,QDomElement & _this)543 void AutomationPattern::saveSettings( QDomDocument & _doc, QDomElement & _this )
544 {
545 	_this.setAttribute( "pos", startPosition() );
546 	_this.setAttribute( "len", length() );
547 	_this.setAttribute( "name", name() );
548 	_this.setAttribute( "prog", QString::number( progressionType() ) );
549 	_this.setAttribute( "tens", QString::number( getTension() ) );
550 	_this.setAttribute( "mute", QString::number( isMuted() ) );
551 
552 	for( timeMap::const_iterator it = m_timeMap.begin();
553 						it != m_timeMap.end(); ++it )
554 	{
555 		QDomElement element = _doc.createElement( "time" );
556 		element.setAttribute( "pos", it.key() );
557 		element.setAttribute( "value", it.value() );
558 		_this.appendChild( element );
559 	}
560 
561 	for( objectVector::const_iterator it = m_objects.begin();
562 						it != m_objects.end(); ++it )
563 	{
564 		if( *it )
565 		{
566 			QDomElement element = _doc.createElement( "object" );
567 			element.setAttribute( "id",
568 				ProjectJournal::idToSave( ( *it )->id() ) );
569 			_this.appendChild( element );
570 		}
571 	}
572 }
573 
574 
575 
576 
loadSettings(const QDomElement & _this)577 void AutomationPattern::loadSettings( const QDomElement & _this )
578 {
579 	clear();
580 
581 	movePosition( _this.attribute( "pos" ).toInt() );
582 	setName( _this.attribute( "name" ) );
583 	setProgressionType( static_cast<ProgressionTypes>( _this.attribute(
584 							"prog" ).toInt() ) );
585 	setTension( _this.attribute( "tens" ) );
586 	setMuted(_this.attribute( "mute", QString::number( false ) ).toInt() );
587 
588 	for( QDomNode node = _this.firstChild(); !node.isNull();
589 						node = node.nextSibling() )
590 	{
591 		QDomElement element = node.toElement();
592 		if( element.isNull()  )
593 		{
594 			continue;
595 		}
596 		if( element.tagName() == "time" )
597 		{
598 			m_timeMap[element.attribute( "pos" ).toInt()]
599 				= LocaleHelper::toFloat(element.attribute("value"));
600 		}
601 		else if( element.tagName() == "object" )
602 		{
603 			m_idsToResolve << element.attribute( "id" ).toInt();
604 		}
605 	}
606 
607 	int len = _this.attribute( "len" ).toInt();
608 	if( len <= 0 )
609 	{
610 		// TODO: Handle with an upgrade method
611 		updateLength();
612 	}
613 	else
614 	{
615 		changeLength( len );
616 	}
617 	generateTangents();
618 }
619 
620 
621 
622 
name() const623 const QString AutomationPattern::name() const
624 {
625 	if( !TrackContentObject::name().isEmpty() )
626 	{
627 		return TrackContentObject::name();
628 	}
629 	if( !m_objects.isEmpty() && m_objects.first() != NULL )
630 	{
631 		return m_objects.first()->fullDisplayName();
632 	}
633 	return tr( "Drag a control while pressing <%1>" ).arg(
634 	#ifdef LMMS_BUILD_APPLE
635 		"⌘");
636 	#else
637 		"Ctrl");
638 	#endif
639 }
640 
641 
642 
643 
createView(TrackView * _tv)644 TrackContentObjectView * AutomationPattern::createView( TrackView * _tv )
645 {
646 	return new AutomationPatternView( this, _tv );
647 }
648 
649 
650 
651 
652 
isAutomated(const AutomatableModel * _m)653 bool AutomationPattern::isAutomated( const AutomatableModel * _m )
654 {
655 	TrackContainer::TrackList l;
656 	l += Engine::getSong()->tracks();
657 	l += Engine::getBBTrackContainer()->tracks();
658 	l += Engine::getSong()->globalAutomationTrack();
659 
660 	for( TrackContainer::TrackList::ConstIterator it = l.begin(); it != l.end(); ++it )
661 	{
662 		if( ( *it )->type() == Track::AutomationTrack ||
663 			( *it )->type() == Track::HiddenAutomationTrack )
664 		{
665 			const Track::tcoVector & v = ( *it )->getTCOs();
666 			for( Track::tcoVector::ConstIterator j = v.begin(); j != v.end(); ++j )
667 			{
668 				const AutomationPattern * a = dynamic_cast<const AutomationPattern *>( *j );
669 				if( a && a->hasAutomation() )
670 				{
671 					for( objectVector::const_iterator k = a->m_objects.begin(); k != a->m_objects.end(); ++k )
672 					{
673 						if( *k == _m )
674 						{
675 							return true;
676 						}
677 					}
678 				}
679 			}
680 		}
681 	}
682 	return false;
683 }
684 
685 
686 /*! \brief returns a list of all the automation patterns everywhere that are connected to a specific model
687  *  \param _m the model we want to look for
688  */
patternsForModel(const AutomatableModel * _m)689 QVector<AutomationPattern *> AutomationPattern::patternsForModel( const AutomatableModel * _m )
690 {
691 	QVector<AutomationPattern *> patterns;
692 	TrackContainer::TrackList l;
693 	l += Engine::getSong()->tracks();
694 	l += Engine::getBBTrackContainer()->tracks();
695 	l += Engine::getSong()->globalAutomationTrack();
696 
697 	// go through all tracks...
698 	for( TrackContainer::TrackList::ConstIterator it = l.begin(); it != l.end(); ++it )
699 	{
700 		// we want only automation tracks...
701 		if( ( *it )->type() == Track::AutomationTrack ||
702 			( *it )->type() == Track::HiddenAutomationTrack )
703 		{
704 			// get patterns in those tracks....
705 			const Track::tcoVector & v = ( *it )->getTCOs();
706 			// go through all the patterns...
707 			for( Track::tcoVector::ConstIterator j = v.begin(); j != v.end(); ++j )
708 			{
709 				AutomationPattern * a = dynamic_cast<AutomationPattern *>( *j );
710 				// check that the pattern has automation
711 				if( a && a->hasAutomation() )
712 				{
713 					// now check is the pattern is connected to the model we want by going through all the connections
714 					// of the pattern
715 					bool has_object = false;
716 					for( objectVector::const_iterator k = a->m_objects.begin(); k != a->m_objects.end(); ++k )
717 					{
718 						if( *k == _m )
719 						{
720 							has_object = true;
721 						}
722 					}
723 					// if the patterns is connected to the model, add it to the list
724 					if( has_object ) { patterns += a; }
725 				}
726 			}
727 		}
728 	}
729 	return patterns;
730 }
731 
732 
733 
globalAutomationPattern(AutomatableModel * _m)734 AutomationPattern * AutomationPattern::globalAutomationPattern(
735 							AutomatableModel * _m )
736 {
737 	AutomationTrack * t = Engine::getSong()->globalAutomationTrack();
738 	Track::tcoVector v = t->getTCOs();
739 	for( Track::tcoVector::const_iterator j = v.begin(); j != v.end(); ++j )
740 	{
741 		AutomationPattern * a = dynamic_cast<AutomationPattern *>( *j );
742 		if( a )
743 		{
744 			for( objectVector::const_iterator k = a->m_objects.begin();
745 												k != a->m_objects.end(); ++k )
746 			{
747 				if( *k == _m )
748 				{
749 					return a;
750 				}
751 			}
752 		}
753 	}
754 
755 	AutomationPattern * a = new AutomationPattern( t );
756 	a->addObject( _m, false );
757 	return a;
758 }
759 
760 
761 
762 
resolveAllIDs()763 void AutomationPattern::resolveAllIDs()
764 {
765 	TrackContainer::TrackList l = Engine::getSong()->tracks() +
766 				Engine::getBBTrackContainer()->tracks();
767 	l += Engine::getSong()->globalAutomationTrack();
768 	for( TrackContainer::TrackList::iterator it = l.begin();
769 							it != l.end(); ++it )
770 	{
771 		if( ( *it )->type() == Track::AutomationTrack ||
772 			 ( *it )->type() == Track::HiddenAutomationTrack )
773 		{
774 			Track::tcoVector v = ( *it )->getTCOs();
775 			for( Track::tcoVector::iterator j = v.begin();
776 							j != v.end(); ++j )
777 			{
778 				AutomationPattern * a = dynamic_cast<AutomationPattern *>( *j );
779 				if( a )
780 				{
781 					for( QVector<jo_id_t>::Iterator k = a->m_idsToResolve.begin();
782 									k != a->m_idsToResolve.end(); ++k )
783 					{
784 						JournallingObject * o = Engine::projectJournal()->
785 														journallingObject( *k );
786 						if( o && dynamic_cast<AutomatableModel *>( o ) )
787 						{
788 							a->addObject( dynamic_cast<AutomatableModel *>( o ), false );
789 						}
790 						else
791 						{
792 							// FIXME: Remove this block once the automation system gets fixed
793 							// This is a temporary fix for https://github.com/LMMS/lmms/issues/3781
794 							o = Engine::projectJournal()->journallingObject(ProjectJournal::idFromSave(*k));
795 							if( o && dynamic_cast<AutomatableModel *>( o ) )
796 							{
797 								a->addObject( dynamic_cast<AutomatableModel *>( o ), false );
798 							}
799 							else
800 							{
801 								// FIXME: Remove this block once the automation system gets fixed
802 								// This is a temporary fix for https://github.com/LMMS/lmms/issues/4781
803 								o = Engine::projectJournal()->journallingObject(ProjectJournal::idToSave(*k));
804 								if( o && dynamic_cast<AutomatableModel *>( o ) )
805 								{
806 									a->addObject( dynamic_cast<AutomatableModel *>( o ), false );
807 								}
808 							}
809 						}
810 					}
811 					a->m_idsToResolve.clear();
812 					a->dataChanged();
813 				}
814 			}
815 		}
816 	}
817 }
818 
819 
820 
821 
clear()822 void AutomationPattern::clear()
823 {
824 	m_timeMap.clear();
825 	m_tangents.clear();
826 
827 	emit dataChanged();
828 }
829 
830 
831 
832 
objectDestroyed(jo_id_t _id)833 void AutomationPattern::objectDestroyed( jo_id_t _id )
834 {
835 	// TODO: distict between temporary removal (e.g. LADSPA controls
836 	// when switching samplerate) and real deletions because in the latter
837 	// case we had to remove ourselves if we're the global automation
838 	// pattern of the destroyed object
839 	m_idsToResolve += _id;
840 
841 	for( objectVector::Iterator objIt = m_objects.begin();
842 		objIt != m_objects.end(); objIt++ )
843 	{
844 		Q_ASSERT( !(*objIt).isNull() );
845 		if( (*objIt)->id() == _id )
846 		{
847 			//Assign to objIt so that this loop work even break; is removed.
848 			objIt = m_objects.erase( objIt );
849 			break;
850 		}
851 	}
852 
853 	emit dataChanged();
854 }
855 
856 
857 
858 
cleanObjects()859 void AutomationPattern::cleanObjects()
860 {
861 	for( objectVector::iterator it = m_objects.begin(); it != m_objects.end(); )
862 	{
863 		if( *it )
864 		{
865 			++it;
866 		}
867 		else
868 		{
869 			it = m_objects.erase( it );
870 		}
871 	}
872 }
873 
874 
875 
876 
generateTangents()877 void AutomationPattern::generateTangents()
878 {
879 	generateTangents(m_timeMap.begin(), m_timeMap.size());
880 }
881 
882 
883 
884 
generateTangents(timeMap::const_iterator it,int numToGenerate)885 void AutomationPattern::generateTangents( timeMap::const_iterator it,
886 							int numToGenerate )
887 {
888 	if( m_timeMap.size() < 2 && numToGenerate > 0 )
889 	{
890 		m_tangents[it.key()] = 0;
891 		return;
892 	}
893 
894 	for( int i = 0; i < numToGenerate; i++ )
895 	{
896 		if( it == m_timeMap.begin() )
897 		{
898 			m_tangents[it.key()] =
899 					( (it+1).value() - (it).value() ) /
900 						( (it+1).key() - (it).key() );
901 		}
902 		else if( it+1 == m_timeMap.end() )
903 		{
904 			m_tangents[it.key()] = 0;
905 			return;
906 		}
907 		else
908 		{
909 			m_tangents[it.key()] =
910 					( (it+1).value() - (it-1).value() ) /
911 						( (it+1).key() - (it-1).key() );
912 		}
913 		it++;
914 	}
915 }
916 
917 
918 
919 
920 
921