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