ardour
ControlList.cpp
Go to the documentation of this file.
1 /* This file is part of Evoral.
2  * Copyright (C) 2008 David Robillard <http://drobilla.net>
3  * Copyright (C) 2000-2008 Paul Davis
4  *
5  * Evoral is free software; you can redistribute it and/or modify it under the
6  * terms of the GNU General Public License as published by the Free Software
7  * Foundation; either version 2 of the License, or (at your option) any later
8  * version.
9  *
10  * Evoral is distributed in the hope that it will be useful, but WITHOUT ANY
11  * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
12  * FOR A PARTICULAR PURPOSE. See the GNU General Public License for details.
13  *
14  * You should have received a copy of the GNU General Public License along
15  * with this program; if not, write to the Free Software Foundation, Inc.,
16  * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
17  */
18 
19 #include <cmath>
20 
21 #ifdef COMPILER_MSVC
22 #include <float.h>
23 
24 // 'std::isnan()' is not available in MSVC.
25 #define isnan_local(val) (bool)_isnan((double)val)
26 #else
27 #define isnan_local std::isnan
28 #endif
29 
30 #include <cassert>
31 #include <cmath>
32 #include <iostream>
33 #include <utility>
34 
35 #include "evoral/ControlList.hpp"
36 #include "evoral/Curve.hpp"
38 #include "evoral/TypeMap.hpp"
39 
40 #include "pbd/compose.h"
41 #include "pbd/debug.h"
42 
43 using namespace std;
44 using namespace PBD;
45 
46 namespace Evoral {
47 
49 {
50  return a->when < b->when;
51 }
52 
54  : _parameter(id)
55  , _desc(desc)
56  , _curve(0)
57 {
59  _frozen = 0;
60  _changed_when_thawed = false;
61  _min_yval = desc.lower;
62  _max_yval = desc.upper;
63  _default_value = desc.normal;
64  _lookup_cache.left = -1;
65  _lookup_cache.range.first = _events.end();
66  _lookup_cache.range.second = _events.end();
67  _search_cache.left = -1;
68  _search_cache.first = _events.end();
69  _sort_pending = false;
70  new_write_pass = true;
71  _in_write_pass = false;
72  did_write_during_pass = false;
73  insert_position = -1;
75 }
76 
78  : _parameter(other._parameter)
79  , _desc(other._desc)
80  , _interpolation(other._interpolation)
81  , _curve(0)
82 {
83  _frozen = 0;
84  _changed_when_thawed = false;
85  _min_yval = other._min_yval;
86  _max_yval = other._max_yval;
88  _lookup_cache.range.first = _events.end();
89  _lookup_cache.range.second = _events.end();
90  _search_cache.first = _events.end();
91  _sort_pending = false;
92  new_write_pass = true;
93  _in_write_pass = false;
94  did_write_during_pass = false;
95  insert_position = -1;
97 
98  copy_events (other);
99 
100  mark_dirty ();
101 }
102 
103 ControlList::ControlList (const ControlList& other, double start, double end)
104  : _parameter(other._parameter)
105  , _desc(other._desc)
106  , _interpolation(other._interpolation)
107  , _curve(0)
108 {
109  _frozen = 0;
110  _changed_when_thawed = false;
111  _min_yval = other._min_yval;
112  _max_yval = other._max_yval;
114  _lookup_cache.range.first = _events.end();
115  _lookup_cache.range.second = _events.end();
116  _search_cache.first = _events.end();
117  _sort_pending = false;
118 
119  /* now grab the relevant points, and shift them back if necessary */
120 
121  boost::shared_ptr<ControlList> section = const_cast<ControlList*>(&other)->copy (start, end);
122 
123  if (!section->empty()) {
124  copy_events (*(section.get()));
125  }
126 
127  new_write_pass = false;
128  _in_write_pass = false;
129  did_write_during_pass = false;
130  insert_position = -1;
132 
133  mark_dirty ();
134 }
135 
137 {
138  for (EventList::iterator x = _events.begin(); x != _events.end(); ++x) {
139  delete (*x);
140  }
141 
142  delete _curve;
143 }
144 
147 {
148  return boost::shared_ptr<ControlList>(new ControlList(id, desc));
149 }
150 
151 bool
153 {
154  return _events == other._events;
155 }
156 
159 {
160  if (this != &other) {
161 
162  _min_yval = other._min_yval;
163  _max_yval = other._max_yval;
164 
165 
168 
169  copy_events (other);
170  }
171 
172  return *this;
173 }
174 
175 void
177 {
178  {
179  Glib::Threads::RWLock::WriterLock lm (_lock);
180  _events.clear ();
181  for (const_iterator i = other.begin(); i != other.end(); ++i) {
182  _events.push_back (new ControlEvent ((*i)->when, (*i)->value));
183  }
185  mark_dirty ();
186  }
188 }
189 
190 void
192 {
193  _curve = new Curve(*this);
194 }
195 
196 void
198 {
199  delete _curve;
200  _curve = NULL;
201 }
202 
203 void
205 {
206  mark_dirty ();
207 
208  if (_frozen) {
209  _changed_when_thawed = true;
210  }
211 }
212 
213 void
215 {
216  {
217  Glib::Threads::RWLock::WriterLock lm (_lock);
218  _events.clear ();
220  mark_dirty ();
221  }
222 
224 }
225 
226 void
227 ControlList::x_scale (double factor)
228 {
229  Glib::Threads::RWLock::WriterLock lm (_lock);
230  _x_scale (factor);
231 }
232 
233 bool
235 {
236  Glib::Threads::RWLock::WriterLock lm (_lock);
237  if (_events.empty() || _events.back()->when == when) {
238  return false;
239  }
240  double factor = when / _events.back()->when;
241  _x_scale (factor);
242  return true;
243 }
244 
245 void
246 ControlList::_x_scale (double factor)
247 {
248  for (iterator i = _events.begin(); i != _events.end(); ++i) {
249  (*i)->when *= factor;
250  }
251 
252  mark_dirty ();
253 }
254 
257  return a->when < b->when;
258  }
259 };
260 
261 void
262 ControlList::thin (double thinning_factor)
263 {
264  if (thinning_factor == 0.0 || _desc.toggled) {
265  return;
266  }
267 
268  bool changed = false;
269 
270  {
271  Glib::Threads::RWLock::WriterLock lm (_lock);
272 
273  ControlEvent* prevprev = 0;
274  ControlEvent* cur = 0;
275  ControlEvent* prev = 0;
276  iterator pprev;
277  int counter = 0;
278 
279  DEBUG_TRACE (DEBUG::ControlList, string_compose ("@%1 thin from %2 events\n", this, _events.size()));
280 
281  for (iterator i = _events.begin(); i != _events.end(); ++i) {
282 
283  cur = *i;
284  counter++;
285 
286  if (counter > 2) {
287 
288  /* compute the area of the triangle formed by 3 points
289  */
290 
291  double area = fabs ((prevprev->when * (prev->value - cur->value)) +
292  (prev->when * (cur->value - prevprev->value)) +
293  (cur->when * (prevprev->value - prev->value)));
294 
295  if (area < thinning_factor) {
296  iterator tmp = pprev;
297 
298  /* pprev will change to current
299  i is incremented to the next event
300  as we loop.
301  */
302 
303  pprev = i;
304  _events.erase (tmp);
305  changed = true;
306  continue;
307  }
308  }
309 
310  prevprev = prev;
311  prev = cur;
312  pprev = i;
313  }
314 
315  DEBUG_TRACE (DEBUG::ControlList, string_compose ("@%1 thin => %2 events\n", this, _events.size()));
316 
317  if (changed) {
319  mark_dirty ();
320  }
321  }
322 
323  if (changed) {
325  }
326 }
327 
328 void
329 ControlList::fast_simple_add (double when, double value)
330 {
331  Glib::Threads::RWLock::WriterLock lm (_lock);
332  /* to be used only for loading pre-sorted data from saved state */
333  _events.insert (_events.end(), new ControlEvent (when, value));
334 
335  mark_dirty ();
336 }
337 
338 void
340 {
341  Glib::Threads::RWLock::WriterLock lm (_lock);
343 }
344 
345 void
347 {
349 }
350 
351 void
353 {
354  Glib::Threads::RWLock::WriterLock lm (_lock);
355 
356  DEBUG_TRACE (DEBUG::ControlList, string_compose ("%1: setup write pass @ %2\n", this, when));
357 
358  new_write_pass = true;
359  did_write_during_pass = false;
360  insert_position = when;
361 
362  /* leave the insert iterator invalid, so that we will do the lookup
363  of where it should be in a "lazy" way - deferring it until
364  we actually add the first point (which may never happen).
365  */
366 
368 }
369 
370 void
371 ControlList::write_pass_finished (double /*when*/, double thinning_factor)
372 {
373  DEBUG_TRACE (DEBUG::ControlList, "write pass finished\n");
374 
375  if (did_write_during_pass) {
376  thin (thinning_factor);
377  did_write_during_pass = false;
378  }
379  new_write_pass = true;
380  _in_write_pass = false;
381 }
382 
383 void
384 ControlList::set_in_write_pass (bool yn, bool add_point, double when)
385 {
386  DEBUG_TRACE (DEBUG::ControlList, string_compose ("now in write pass @ %1, add point ? %2\n", when, add_point));
387 
388  _in_write_pass = yn;
389 
390  if (yn && add_point) {
391  add_guard_point (when);
392  }
393 }
394 
395 void
397 {
398  ControlEvent cp (when, 0.0);
399  most_recent_insert_iterator = lower_bound (_events.begin(), _events.end(), &cp, time_comparator);
400 
401  double eval_value = unlocked_eval (insert_position);
402 
403  if (most_recent_insert_iterator == _events.end()) {
404 
405  DEBUG_TRACE (DEBUG::ControlList, string_compose ("@%1 insert iterator at end, adding eval-value there %2\n", this, eval_value));
406  _events.push_back (new ControlEvent (when, eval_value));
407  /* leave insert iterator at the end */
408 
409  } else if ((*most_recent_insert_iterator)->when == when) {
410 
411  DEBUG_TRACE (DEBUG::ControlList, string_compose ("@%1 insert iterator at existing point, setting eval-value there %2\n", this, eval_value));
412 
413  /* most_recent_insert_iterator points to a control event
414  already at the insert position, so there is
415  nothing to do.
416 
417  ... except ...
418 
419  advance most_recent_insert_iterator so that the "real"
420  insert occurs in the right place, since it
421  points to the control event just inserted.
422  */
423 
425  } else {
426 
427  /* insert a new control event at the right spot
428  */
429 
430  DEBUG_TRACE (DEBUG::ControlList, string_compose ("@%1 insert eval-value %2 just before iterator @ %3\n",
431  this, eval_value, (*most_recent_insert_iterator)->when));
432 
434 
435  /* advance most_recent_insert_iterator so that the "real"
436  * insert occurs in the right place, since it
437  * points to the control event just inserted.
438  */
439 
441  }
442 
443  /* don't do this again till the next write pass */
444 
445  new_write_pass = false;
446 }
447 
448 bool
450 {
451  return _in_write_pass;
452 }
453 
454 void
455 ControlList::editor_add (double when, double value)
456 {
457  /* this is for making changes from a graphical line editor
458  */
459 
460  if (_events.empty()) {
461 
462  /* as long as the point we're adding is not at zero,
463  * add an "anchor" point there.
464  */
465 
466  if (when >= 1) {
467  _events.insert (_events.end(), new ControlEvent (0, _default_value));
468  DEBUG_TRACE (DEBUG::ControlList, string_compose ("@%1 added default value %2 at zero\n", this, _default_value));
469  }
470  }
471 
472  ControlEvent cp (when, 0.0f);
473  iterator i = lower_bound (_events.begin(), _events.end(), &cp, time_comparator);
474  DEBUG_TRACE (DEBUG::ControlList, string_compose ("editor_add: actually add when= %1 value= %2\n", when, value));
475  _events.insert (i, new ControlEvent (when, value));
476 
477  mark_dirty ();
478 
480 
481 }
482 
483 void
485 {
486  if (most_recent_insert_iterator != _events.end()) {
487  if ((*most_recent_insert_iterator)->when - when > 64) {
488  /* Next control point is some distance from where our new point is
489  going to go, so add a new point to avoid changing the shape of
490  the line too much. The insert iterator needs to point to the
491  new control point so that our insert will happen correctly. */
494  new ControlEvent (when + 1, (*most_recent_insert_iterator)->value));
495  DEBUG_TRACE (DEBUG::ControlList, string_compose ("@%1 added insert guard point @ %2 = %3\n",
496  this, when+1,
497  (*most_recent_insert_iterator)->value));
498  }
499  }
500 }
501 
503 bool
504 ControlList::maybe_insert_straight_line (double when, double value)
505 {
506  if (_events.empty()) {
507  return false;
508  }
509 
510  if (_events.back()->value == value) {
511  // Point b at the final point, which we know exists
512  EventList::iterator b = _events.end();
513  --b;
514  if (b == _events.begin()) {
515  return false; // No previous point
516  }
517 
518  // Check the previous point's value
519  --b;
520  if ((*b)->value == value) {
521  /* At least two points with the exact same value (straight
522  line), just move the final point to the new time. */
523  _events.back()->when = when;
524  DEBUG_TRACE (DEBUG::ControlList, string_compose ("final value of %1 moved to %2\n", value, when));
525  return true;
526  }
527  }
528  return false;
529 }
530 
533 {
534  while (iter != _events.end()) {
535  if ((*iter)->when < when) {
536  DEBUG_TRACE (DEBUG::ControlList, string_compose ("@%1 erase existing @ %2\n", this, (*iter)->when));
537  delete *iter;
538  iter = _events.erase (iter);
539  continue;
540  } else if ((*iter)->when >= when) {
541  break;
542  }
543  ++iter;
544  }
545  return iter;
546 }
547 
548 void
549 ControlList::add (double when, double value, bool with_guards, bool with_default)
550 {
551  /* this is for making changes from some kind of user interface or
552  control surface (GUI, MIDI, OSC etc)
553  */
554 
556  string_compose ("@%1 add %2 at %3 guards = %4 write pass = %5 (new? %6) at end? %7\n",
557  this, value, when, with_guards, _in_write_pass, new_write_pass,
559  {
560  Glib::Threads::RWLock::WriterLock lm (_lock);
561  ControlEvent cp (when, 0.0f);
562  iterator insertion_point;
563 
564  if (_events.empty() && with_default) {
565 
566  /* empty: add an "anchor" point if the point we're adding past time 0 */
567 
568  if (when >= 1) {
569  _events.insert (_events.end(), new ControlEvent (0, _default_value));
570  DEBUG_TRACE (DEBUG::ControlList, string_compose ("@%1 added default value %2 at zero\n", this, _default_value));
571  }
572  }
573 
575 
576  /* first write in a write pass: add guard point if requested */
577 
578  if (with_guards) {
580  did_write_during_pass = true;
581  } else {
582  /* not adding a guard, but we need to set iterator appropriately */
583  const ControlEvent cp (when, 0.0);
584  most_recent_insert_iterator = lower_bound (_events.begin(), _events.end(), &cp, time_comparator);
585  }
586  new_write_pass = false;
587 
588  } else if (_in_write_pass &&
589  (most_recent_insert_iterator == _events.end() || when > (*most_recent_insert_iterator)->when)) {
590 
591  /* in write pass: erase from most recent insert to now */
592 
593  if (most_recent_insert_iterator != _events.end()) {
594  /* advance to avoid deleting the last inserted point itself. */
596  }
597 
599  if (with_guards) {
600  maybe_add_insert_guard (when);
601  }
602 
603  } else if (!_in_write_pass) {
604 
605  /* not in a write pass: figure out the iterator we should insert in front of */
606 
607  DEBUG_TRACE (DEBUG::ControlList, string_compose ("compute(b) MRI for position %1\n", when));
608  ControlEvent cp (when, 0.0f);
609  most_recent_insert_iterator = lower_bound (_events.begin(), _events.end(), &cp, time_comparator);
610  }
611 
612  /* OK, now we're really ready to add a new point */
613 
614  if (most_recent_insert_iterator == _events.end()) {
615  DEBUG_TRACE (DEBUG::ControlList, string_compose ("@%1 appending new point at end\n", this));
616 
617  const bool done = maybe_insert_straight_line (when, value);
618  if (!done) {
619  _events.push_back (new ControlEvent (when, value));
620  DEBUG_TRACE (DEBUG::ControlList, string_compose ("\tactually appended, size now %1\n", _events.size()));
621  }
622 
625 
626  } else if ((*most_recent_insert_iterator)->when == when) {
627 
628  if ((*most_recent_insert_iterator)->value != value) {
629  DEBUG_TRACE (DEBUG::ControlList, string_compose ("@%1 reset existing point to new value %2\n", this, value));
630 
631  /* only one point allowed per time point, so just
632  * reset the value here.
633  */
634 
635  (*most_recent_insert_iterator)->value = value;
636 
637  /* if we modified the final value, then its as
638  * if we inserted a new point as far as the
639  * next addition, so make sure we know that.
640  */
641 
642  if (_events.back()->when == when) {
644  }
645 
646  } else {
647  DEBUG_TRACE (DEBUG::ControlList, string_compose ("@%1 same time %2, same value value %3\n", this, when, value));
648  }
649 
650  } else {
651  DEBUG_TRACE (DEBUG::ControlList, string_compose ("@%1 insert new point at %2 at iterator at %3\n", this, when, (*most_recent_insert_iterator)->when));
652 
653  const bool done = maybe_insert_straight_line (when, value);
654  if (with_guards) {
656  }
657 
658  if (!done) {
659  EventList::iterator x = _events.insert (most_recent_insert_iterator, new ControlEvent (when, value));
660  DEBUG_TRACE (DEBUG::ControlList, string_compose ("@%1 inserted new value before MRI, size now %2\n", this, _events.size()));
662  }
663  }
664 
665  mark_dirty ();
666  }
667 
669 }
670 
671 void
673 {
674  {
675  Glib::Threads::RWLock::WriterLock lm (_lock);
676  if (most_recent_insert_iterator == i) {
678  }
679  _events.erase (i);
680  mark_dirty ();
681  }
683 }
684 
685 void
687 {
688  {
689  Glib::Threads::RWLock::WriterLock lm (_lock);
690  _events.erase (start, end);
692  mark_dirty ();
693  }
695 }
696 
698 void
699 ControlList::erase (double when, double value)
700 {
701  {
702  Glib::Threads::RWLock::WriterLock lm (_lock);
703 
704  iterator i = begin ();
705  while (i != end() && ((*i)->when != when || (*i)->value != value)) {
706  ++i;
707  }
708 
709  if (i != end ()) {
710  _events.erase (i);
711  if (most_recent_insert_iterator == i) {
713  }
714  }
715 
716  mark_dirty ();
717  }
718 
720 }
721 
722 void
723 ControlList::erase_range (double start, double endt)
724 {
725  bool erased = false;
726 
727  {
728  Glib::Threads::RWLock::WriterLock lm (_lock);
729  erased = erase_range_internal (start, endt, _events);
730 
731  if (erased) {
732  mark_dirty ();
733  }
734 
735  }
736 
737  if (erased) {
739  }
740 }
741 
742 bool
743 ControlList::erase_range_internal (double start, double endt, EventList & events)
744 {
745  bool erased = false;
746  ControlEvent cp (start, 0.0f);
747  iterator s;
748  iterator e;
749 
750  if ((s = lower_bound (events.begin(), events.end(), &cp, time_comparator)) != events.end()) {
751  cp.when = endt;
752  e = upper_bound (events.begin(), events.end(), &cp, time_comparator);
753  events.erase (s, e);
754  if (s != e) {
756  erased = true;
757  }
758  }
759 
760  return erased;
761 }
762 
763 void
764 ControlList::slide (iterator before, double distance)
765 {
766  {
767  Glib::Threads::RWLock::WriterLock lm (_lock);
768 
769  if (before == _events.end()) {
770  return;
771  }
772 
773  while (before != _events.end()) {
774  (*before)->when += distance;
775  ++before;
776  }
777 
778  mark_dirty ();
779  }
780 
782 }
783 
784 void
785 ControlList::shift (double pos, double frames)
786 {
787  {
788  Glib::Threads::RWLock::WriterLock lm (_lock);
789 
790  for (iterator i = _events.begin(); i != _events.end(); ++i) {
791  if ((*i)->when >= pos) {
792  (*i)->when += frames;
793  }
794  }
795 
796  mark_dirty ();
797  }
798 
800 }
801 
802 void
803 ControlList::modify (iterator iter, double when, double val)
804 {
805  /* note: we assume higher level logic is in place to avoid this
806  reordering the time-order of control events in the list. ie. all
807  points after *iter are later than when.
808  */
809 
810  {
811  Glib::Threads::RWLock::WriterLock lm (_lock);
812 
813  (*iter)->when = when;
814  (*iter)->value = val;
815  if (isnan_local (val)) {
816  abort ();
817  }
818 
819  if (!_frozen) {
822  } else {
823  _sort_pending = true;
824  }
825 
826  mark_dirty ();
827  }
828 
830 }
831 
832 std::pair<ControlList::iterator,ControlList::iterator>
834 {
835  Glib::Threads::RWLock::ReaderLock lm (_lock);
836  iterator i;
837  ControlEvent cp (xval, 0.0f);
838  std::pair<iterator,iterator> ret;
839 
840  ret.first = _events.end();
841  ret.second = _events.end();
842 
843  for (i = lower_bound (_events.begin(), _events.end(), &cp, time_comparator); i != _events.end(); ++i) {
844 
845  if (ret.first == _events.end()) {
846  if ((*i)->when >= xval) {
847  if (i != _events.begin()) {
848  ret.first = i;
849  --ret.first;
850  } else {
851  return ret;
852  }
853  }
854  }
855 
856  if ((*i)->when > xval) {
857  ret.second = i;
858  break;
859  }
860  }
861 
862  return ret;
863 }
864 
865 void
867 {
868  _frozen++;
869 }
870 
871 void
873 {
874  assert(_frozen > 0);
875 
876  if (--_frozen > 0) {
877  return;
878  }
879 
880  {
881  Glib::Threads::RWLock::WriterLock lm (_lock);
882 
883  if (_sort_pending) {
886  _sort_pending = false;
887  }
888  }
889 }
890 
891 void
893 {
894  _lookup_cache.left = -1;
895  _lookup_cache.range.first = _events.end();
896  _lookup_cache.range.second = _events.end();
897  _search_cache.left = -1;
898  _search_cache.first = _events.end();
899 
900  if (_curve) {
901  _curve->mark_dirty();
902  }
903 
904  Dirty (); /* EMIT SIGNAL */
905 }
906 
907 void
908 ControlList::truncate_end (double last_coordinate)
909 {
910  {
911  Glib::Threads::RWLock::WriterLock lm (_lock);
912  ControlEvent cp (last_coordinate, 0);
914  double last_val;
915 
916  if (_events.empty()) {
917  return;
918  }
919 
920  if (last_coordinate == _events.back()->when) {
921  return;
922  }
923 
924  if (last_coordinate > _events.back()->when) {
925 
926  /* extending end:
927  */
928 
929  iterator foo = _events.begin();
930  bool lessthantwo;
931 
932  if (foo == _events.end()) {
933  lessthantwo = true;
934  } else if (++foo == _events.end()) {
935  lessthantwo = true;
936  } else {
937  lessthantwo = false;
938  }
939 
940  if (lessthantwo) {
941  /* less than 2 points: add a new point */
942  _events.push_back (new ControlEvent (last_coordinate, _events.back()->value));
943  } else {
944 
945  /* more than 2 points: check to see if the last 2 values
946  are equal. if so, just move the position of the
947  last point. otherwise, add a new point.
948  */
949 
950  iterator penultimate = _events.end();
951  --penultimate; /* points at last point */
952  --penultimate; /* points at the penultimate point */
953 
954  if (_events.back()->value == (*penultimate)->value) {
955  _events.back()->when = last_coordinate;
956  } else {
957  _events.push_back (new ControlEvent (last_coordinate, _events.back()->value));
958  }
959  }
960 
961  } else {
962 
963  /* shortening end */
964 
965  last_val = unlocked_eval (last_coordinate);
966  last_val = max ((double) _min_yval, last_val);
967  last_val = min ((double) _max_yval, last_val);
968 
969  i = _events.rbegin();
970 
971  /* make i point to the last control point */
972 
973  ++i;
974 
975  /* now go backwards, removing control points that are
976  beyond the new last coordinate.
977  */
978 
979  // FIXME: SLOW! (size() == O(n))
980 
981  uint32_t sz = _events.size();
982 
983  while (i != _events.rend() && sz > 2) {
985 
986  tmp = i;
987  ++tmp;
988 
989  if ((*i)->when < last_coordinate) {
990  break;
991  }
992 
993  _events.erase (i.base());
994  --sz;
995 
996  i = tmp;
997  }
998 
999  _events.back()->when = last_coordinate;
1000  _events.back()->value = last_val;
1001  }
1002 
1004  mark_dirty();
1005  }
1006 
1008 }
1009 
1010 void
1011 ControlList::truncate_start (double overall_length)
1012 {
1013  {
1014  Glib::Threads::RWLock::WriterLock lm (_lock);
1015  iterator i;
1016  double first_legal_value;
1017  double first_legal_coordinate;
1018 
1019  if (_events.empty()) {
1020  /* nothing to truncate */
1021  return;
1022  } else if (overall_length == _events.back()->when) {
1023  /* no change in overall length */
1024  return;
1025  }
1026 
1027  if (overall_length > _events.back()->when) {
1028 
1029  /* growing at front: duplicate first point. shift all others */
1030 
1031  double shift = overall_length - _events.back()->when;
1032  uint32_t np;
1033 
1034  for (np = 0, i = _events.begin(); i != _events.end(); ++i, ++np) {
1035  (*i)->when += shift;
1036  }
1037 
1038  if (np < 2) {
1039 
1040  /* less than 2 points: add a new point */
1041  _events.push_front (new ControlEvent (0, _events.front()->value));
1042 
1043  } else {
1044 
1045  /* more than 2 points: check to see if the first 2 values
1046  are equal. if so, just move the position of the
1047  first point. otherwise, add a new point.
1048  */
1049 
1050  iterator second = _events.begin();
1051  ++second; /* points at the second point */
1052 
1053  if (_events.front()->value == (*second)->value) {
1054  /* first segment is flat, just move start point back to zero */
1055  _events.front()->when = 0;
1056  } else {
1057  /* leave non-flat segment in place, add a new leading point. */
1058  _events.push_front (new ControlEvent (0, _events.front()->value));
1059  }
1060  }
1061 
1062  } else {
1063 
1064  /* shrinking at front */
1065 
1066  first_legal_coordinate = _events.back()->when - overall_length;
1067  first_legal_value = unlocked_eval (first_legal_coordinate);
1068  first_legal_value = max (_min_yval, first_legal_value);
1069  first_legal_value = min (_max_yval, first_legal_value);
1070 
1071  /* remove all events earlier than the new "front" */
1072 
1073  i = _events.begin();
1074 
1075  while (i != _events.end() && !_events.empty()) {
1077 
1078  tmp = i;
1079  ++tmp;
1080 
1081  if ((*i)->when > first_legal_coordinate) {
1082  break;
1083  }
1084 
1085  _events.erase (i);
1086 
1087  i = tmp;
1088  }
1089 
1090 
1091  /* shift all remaining points left to keep their same
1092  relative position
1093  */
1094 
1095  for (i = _events.begin(); i != _events.end(); ++i) {
1096  (*i)->when -= first_legal_coordinate;
1097  }
1098 
1099  /* add a new point for the interpolated new value */
1100 
1101  _events.push_front (new ControlEvent (0, first_legal_value));
1102  }
1103 
1105  mark_dirty();
1106  }
1107 
1109 }
1110 
1111 double
1113 {
1114  pair<EventList::iterator,EventList::iterator> range;
1115  int32_t npoints;
1116  double lpos, upos;
1117  double lval, uval;
1118  double fraction;
1119 
1120  const_iterator length_check_iter = _events.begin();
1121  for (npoints = 0; npoints < 4; ++npoints, ++length_check_iter) {
1122  if (length_check_iter == _events.end()) {
1123  break;
1124  }
1125  }
1126 
1127  switch (npoints) {
1128  case 0:
1129  return _default_value;
1130 
1131  case 1:
1132  return _events.front()->value;
1133 
1134  case 2:
1135  if (x >= _events.back()->when) {
1136  return _events.back()->value;
1137  } else if (x <= _events.front()->when) {
1138  return _events.front()->value;
1139  }
1140 
1141  lpos = _events.front()->when;
1142  lval = _events.front()->value;
1143  upos = _events.back()->when;
1144  uval = _events.back()->value;
1145 
1146  if (_interpolation == Discrete) {
1147  return lval;
1148  }
1149 
1150  /* linear interpolation betweeen the two points */
1151  fraction = (double) (x - lpos) / (double) (upos - lpos);
1152  return lval + (fraction * (uval - lval));
1153 
1154  default:
1155  if (x >= _events.back()->when) {
1156  return _events.back()->value;
1157  } else if (x <= _events.front()->when) {
1158  return _events.front()->value;
1159  }
1160 
1161  return multipoint_eval (x);
1162  }
1163 
1164  abort(); /*NOTREACHED*/ /* stupid gcc */
1165  return _default_value;
1166 }
1167 
1168 double
1170 {
1171  double upos, lpos;
1172  double uval, lval;
1173  double fraction;
1174 
1175  /* "Stepped" lookup (no interpolation) */
1176  /* FIXME: no cache. significant? */
1177  if (_interpolation == Discrete) {
1178  const ControlEvent cp (x, 0);
1179  EventList::const_iterator i = lower_bound (_events.begin(), _events.end(), &cp, time_comparator);
1180 
1181  // shouldn't have made it to multipoint_eval
1182  assert(i != _events.end());
1183 
1184  if (i == _events.begin() || (*i)->when == x)
1185  return (*i)->value;
1186  else
1187  return (*(--i))->value;
1188  }
1189 
1190  /* Only do the range lookup if x is in a different range than last time
1191  * this was called (or if the lookup cache has been marked "dirty" (left<0) */
1192  if ((_lookup_cache.left < 0) ||
1193  ((_lookup_cache.left > x) ||
1194  (_lookup_cache.range.first == _events.end()) ||
1195  ((*_lookup_cache.range.second)->when < x))) {
1196 
1197  const ControlEvent cp (x, 0);
1198 
1199  _lookup_cache.range = equal_range (_events.begin(), _events.end(), &cp, time_comparator);
1200  }
1201 
1202  pair<const_iterator,const_iterator> range = _lookup_cache.range;
1203 
1204  if (range.first == range.second) {
1205 
1206  /* x does not exist within the list as a control point */
1207 
1208  _lookup_cache.left = x;
1209 
1210  if (range.first != _events.begin()) {
1211  --range.first;
1212  lpos = (*range.first)->when;
1213  lval = (*range.first)->value;
1214  } else {
1215  /* we're before the first point */
1216  // return _default_value;
1217  return _events.front()->value;
1218  }
1219 
1220  if (range.second == _events.end()) {
1221  /* we're after the last point */
1222  return _events.back()->value;
1223  }
1224 
1225  upos = (*range.second)->when;
1226  uval = (*range.second)->value;
1227 
1228  /* linear interpolation betweeen the two points
1229  on either side of x
1230  */
1231 
1232  fraction = (double) (x - lpos) / (double) (upos - lpos);
1233  return lval + (fraction * (uval - lval));
1234 
1235  }
1236 
1237  /* x is a control point in the data */
1238  _lookup_cache.left = -1;
1239  return (*range.first)->value;
1240 }
1241 
1242 void
1244 {
1245  if (_events.empty()) {
1246  /* Empty, nothing to cache, move to end. */
1247  _search_cache.first = _events.end();
1248  _search_cache.left = 0;
1249  return;
1250  } else if ((_search_cache.left < 0) || (_search_cache.left > start)) {
1251  /* Marked dirty (left < 0), or we're too far forward, re-search. */
1252 
1253  const ControlEvent start_point (start, 0);
1254 
1255  _search_cache.first = lower_bound (_events.begin(), _events.end(), &start_point, time_comparator);
1257  }
1258 
1259  /* We now have a search cache that is not too far right, but it may be too
1260  far left and need to be advanced. */
1261 
1262  while (_search_cache.first != end() && (*_search_cache.first)->when < start) {
1263  ++_search_cache.first;
1264  }
1266 }
1267 
1275 bool
1276 ControlList::rt_safe_earliest_event (double start, double& x, double& y, bool inclusive) const
1277 {
1278  // FIXME: It would be nice if this was unnecessary..
1279  Glib::Threads::RWLock::ReaderLock lm(_lock, Glib::Threads::TRY_LOCK);
1280  if (!lm.locked()) {
1281  return false;
1282  }
1283 
1284  return rt_safe_earliest_event_unlocked (start, x, y, inclusive);
1285 }
1286 
1287 
1295 bool
1296 ControlList::rt_safe_earliest_event_unlocked (double start, double& x, double& y, bool inclusive) const
1297 {
1298  if (_interpolation == Discrete) {
1299  return rt_safe_earliest_event_discrete_unlocked(start, x, y, inclusive);
1300  } else {
1301  return rt_safe_earliest_event_linear_unlocked(start, x, y, inclusive);
1302  }
1303 }
1304 
1305 
1313 bool
1314 ControlList::rt_safe_earliest_event_discrete_unlocked (double start, double& x, double& y, bool inclusive) const
1315 {
1317 
1318  if (_search_cache.first != _events.end()) {
1319  const ControlEvent* const first = *_search_cache.first;
1320 
1321  const bool past_start = (inclusive ? first->when >= start : first->when > start);
1322 
1323  /* Earliest points is in range, return it */
1324  if (past_start) {
1325 
1326  x = first->when;
1327  y = first->value;
1328 
1329  /* Move left of cache to this point
1330  * (Optimize for immediate call this cycle within range) */
1331  _search_cache.left = x;
1332  ++_search_cache.first;
1333 
1334  assert(x >= start);
1335  return true;
1336 
1337  } else {
1338  return false;
1339  }
1340 
1341  /* No points in range */
1342  } else {
1343  return false;
1344  }
1345 }
1346 
1354 bool
1355 ControlList::rt_safe_earliest_event_linear_unlocked (double start, double& x, double& y, bool inclusive) const
1356 {
1357  // cout << "earliest_event(start: " << start << ", x: " << x << ", y: " << y << ", inclusive: " << inclusive << ")" << endl;
1358 
1359  const_iterator length_check_iter = _events.begin();
1360  if (_events.empty()) { // 0 events
1361  return false;
1362  } else if (_events.end() == ++length_check_iter) { // 1 event
1363  return rt_safe_earliest_event_discrete_unlocked (start, x, y, inclusive);
1364  }
1365 
1366  // Hack to avoid infinitely repeating the same event
1368 
1369  if (_search_cache.first != _events.end()) {
1370 
1371  const ControlEvent* first = NULL;
1372  const ControlEvent* next = NULL;
1373 
1374  if (_search_cache.first == _events.begin() || (*_search_cache.first)->when <= start) {
1375  /* Step is after first */
1376  first = *_search_cache.first;
1377  ++_search_cache.first;
1378  if (_search_cache.first == _events.end()) {
1379  return false;
1380  }
1381  next = *_search_cache.first;
1382 
1383  } else {
1384  /* Step is before first */
1386  --prev;
1387  first = *prev;
1388  next = *_search_cache.first;
1389  }
1390 
1391  if (inclusive && first->when == start) {
1392  x = first->when;
1393  y = first->value;
1394  /* Move left of cache to this point
1395  * (Optimize for immediate call this cycle within range) */
1396  _search_cache.left = x;
1397  return true;
1398  } else if (next->when < start || (!inclusive && next->when == start)) {
1399  /* "Next" is before the start, no points left. */
1400  return false;
1401  }
1402 
1403  if (fabs(first->value - next->value) <= 1) {
1404  if (next->when > start) {
1405  x = next->when;
1406  y = next->value;
1407  /* Move left of cache to this point
1408  * (Optimize for immediate call this cycle within range) */
1409  _search_cache.left = x;
1410  return true;
1411  } else {
1412  return false;
1413  }
1414  }
1415 
1416  const double slope = (next->value - first->value) / (double)(next->when - first->when);
1417  //cerr << "start y: " << start_y << endl;
1418 
1419  //y = first->value + (slope * fabs(start - first->when));
1420  y = first->value;
1421 
1422  if (first->value < next->value) // ramping up
1423  y = ceil(y);
1424  else // ramping down
1425  y = floor(y);
1426 
1427  x = first->when + (y - first->value) / (double)slope;
1428 
1429  while ((inclusive && x < start) || (x <= start && y != next->value)) {
1430 
1431  if (first->value < next->value) // ramping up
1432  y += 1.0;
1433  else // ramping down
1434  y -= 1.0;
1435 
1436  x = first->when + (y - first->value) / (double)slope;
1437  }
1438 
1439  /*cerr << first->value << " @ " << first->when << " ... "
1440  << next->value << " @ " << next->when
1441  << " = " << y << " @ " << x << endl;*/
1442 
1443  assert( (y >= first->value && y <= next->value)
1444  || (y <= first->value && y >= next->value) );
1445 
1446 
1447  const bool past_start = (inclusive ? x >= start : x > start);
1448  if (past_start) {
1449  /* Move left of cache to this point
1450  * (Optimize for immediate call this cycle within range) */
1451  _search_cache.left = x;
1452  assert(inclusive ? x >= start : x > start);
1453  return true;
1454  } else {
1455  if (inclusive) {
1456  x = next->when;
1457  } else {
1458  x = start;
1459  }
1460  _search_cache.left = x;
1461  return true;
1462  }
1463 
1464  } else {
1465  /* No points in the future, so no steps (towards them) in the future */
1466  return false;
1467  }
1468 }
1469 
1470 
1476 ControlList::cut_copy_clear (double start, double end, int op)
1477 {
1479  iterator s, e;
1480  ControlEvent cp (start, 0.0);
1481 
1482  {
1483  Glib::Threads::RWLock::WriterLock lm (_lock);
1484 
1485  /* first, determine s & e, two iterators that define the range of points
1486  affected by this operation
1487  */
1488 
1489  if ((s = lower_bound (_events.begin(), _events.end(), &cp, time_comparator)) == _events.end()) {
1490  return nal;
1491  }
1492 
1493  /* and the last that is at or after `end' */
1494  cp.when = end;
1495  e = upper_bound (_events.begin(), _events.end(), &cp, time_comparator);
1496 
1497 
1498  /* if "start" isn't the location of an existing point,
1499  evaluate the curve to get a value for the start. Add a point to
1500  both the existing event list, and if its not a "clear" operation,
1501  to the copy ("nal") as well.
1502 
1503  Note that the time positions of the points in each list are different
1504  because we want the copy ("nal") to have a zero time reference.
1505  */
1506 
1507 
1508  /* before we begin any cut/clear operations, get the value of the curve
1509  at "end".
1510  */
1511 
1512  double end_value = unlocked_eval (end);
1513 
1514  if ((*s)->when != start) {
1515 
1516  double val = unlocked_eval (start);
1517 
1518  if (op == 0) { // cut
1519  if (start > _events.front()->when) {
1520  _events.insert (s, (new ControlEvent (start, val)));
1521  }
1522  }
1523 
1524  if (op != 2) { // ! clear
1525  nal->_events.push_back (new ControlEvent (0, val));
1526  }
1527  }
1528 
1529  for (iterator x = s; x != e; ) {
1530 
1531  /* adjust new points to be relative to start, which
1532  has been set to zero.
1533  */
1534 
1535  if (op != 2) {
1536  nal->_events.push_back (new ControlEvent ((*x)->when - start, (*x)->value));
1537  }
1538 
1539  if (op != 1) {
1540  x = _events.erase (x);
1541  } else {
1542  ++x;
1543  }
1544  }
1545 
1546  if (e == _events.end() || (*e)->when != end) {
1547 
1548  /* only add a boundary point if there is a point after "end"
1549  */
1550 
1551  if (op == 0 && (e != _events.end() && end < (*e)->when)) { // cut
1552  _events.insert (e, new ControlEvent (end, end_value));
1553  }
1554 
1555  if (op != 2 && (e != _events.end() && end < (*e)->when)) { // cut/copy
1556  nal->_events.push_back (new ControlEvent (end - start, end_value));
1557  }
1558  }
1559 
1561  mark_dirty ();
1562  }
1563 
1564  if (op != 1) {
1566  }
1567 
1568  return nal;
1569 }
1570 
1571 
1573 ControlList::cut (double start, double end)
1574 {
1575  return cut_copy_clear (start, end, 0);
1576 }
1577 
1579 ControlList::copy (double start, double end)
1580 {
1581  return cut_copy_clear (start, end, 1);
1582 }
1583 
1584 void
1585 ControlList::clear (double start, double end)
1586 {
1587  cut_copy_clear (start, end, 2);
1588 }
1589 
1591 bool
1592 ControlList::paste (const ControlList& alist, double pos, float /*times*/)
1593 {
1594  if (alist._events.empty()) {
1595  return false;
1596  }
1597 
1598  {
1599  Glib::Threads::RWLock::WriterLock lm (_lock);
1600  iterator where;
1601  iterator prev;
1602  double end = 0;
1603  ControlEvent cp (pos, 0.0);
1604 
1605  where = upper_bound (_events.begin(), _events.end(), &cp, time_comparator);
1606 
1607  for (const_iterator i = alist.begin();i != alist.end(); ++i) {
1608  double value = (*i)->value;
1609  if (alist.parameter() != parameter()) {
1610  const ParameterDescriptor& src_desc = alist.descriptor();
1611 
1612  value -= src_desc.lower; // translate to 0-relative
1613  value /= (src_desc.upper - src_desc.lower); // normalize range
1614  value *= (_desc.upper - _desc.lower); // scale to our range
1615  value += _desc.lower; // translate to our offset
1616  }
1617  _events.insert (where, new ControlEvent((*i)->when + pos, value));
1618  end = (*i)->when + pos;
1619  }
1620 
1621 
1622  /* move all points after the insertion along the timeline by
1623  the correct amount.
1624  */
1625 
1626  while (where != _events.end()) {
1627  iterator tmp;
1628  if ((*where)->when <= end) {
1629  tmp = where;
1630  ++tmp;
1631  _events.erase(where);
1632  where = tmp;
1633 
1634  } else {
1635  break;
1636  }
1637  }
1638 
1640  mark_dirty ();
1641  }
1642 
1644  return true;
1645 }
1646 
1650 bool
1652 {
1653  typedef list< RangeMove<double> > RangeMoveList;
1654 
1655  {
1656  Glib::Threads::RWLock::WriterLock lm (_lock);
1657 
1658  /* a copy of the events list before we started moving stuff around */
1659  EventList old_events = _events;
1660 
1661  /* clear the source and destination ranges in the new list */
1662  bool things_erased = false;
1663  for (RangeMoveList::const_iterator i = movements.begin (); i != movements.end (); ++i) {
1664 
1665  if (erase_range_internal (i->from, i->from + i->length, _events)) {
1666  things_erased = true;
1667  }
1668 
1669  if (erase_range_internal (i->to, i->to + i->length, _events)) {
1670  things_erased = true;
1671  }
1672  }
1673 
1674  /* if nothing was erased, there is nothing to do */
1675  if (!things_erased) {
1676  return false;
1677  }
1678 
1679  /* copy the events into the new list */
1680  for (RangeMoveList::const_iterator i = movements.begin (); i != movements.end (); ++i) {
1681  iterator j = old_events.begin ();
1682  const double limit = i->from + i->length;
1683  const double dx = i->to - i->from;
1684  while (j != old_events.end () && (*j)->when <= limit) {
1685  if ((*j)->when >= i->from) {
1686  ControlEvent* ev = new ControlEvent (**j);
1687  ev->when += dx;
1688  _events.push_back (ev);
1689  }
1690  ++j;
1691  }
1692  }
1693 
1694  if (!_frozen) {
1697  } else {
1698  _sort_pending = true;
1699  }
1700 
1701  mark_dirty ();
1702  }
1703 
1705  return true;
1706 }
1707 
1708 void
1710 {
1711  if (_interpolation == s) {
1712  return;
1713  }
1714 
1715  _interpolation = s;
1716  InterpolationChanged (s); /* EMIT SIGNAL */
1717 }
1718 
1719 bool
1721 {
1722  if (_events.size() != other._events.size()) {
1723  return true;
1724  }
1725 
1726  EventList::const_iterator i = _events.begin ();
1727  EventList::const_iterator j = other._events.begin ();
1728 
1729  while (i != _events.end() && (*i)->when == (*j)->when && (*i)->value == (*j)->value) {
1730  ++i;
1731  ++j;
1732  }
1733 
1734  if (i != _events.end ()) {
1735  return true;
1736  }
1737 
1738  return (
1739  _parameter != other._parameter ||
1740  _interpolation != other._interpolation ||
1741  _min_yval != other._min_yval ||
1742  _max_yval != other._max_yval ||
1744  );
1745 }
1746 
1747 void
1748 ControlList::dump (ostream& o)
1749 {
1750  /* NOT LOCKED ... for debugging only */
1751 
1752  for (EventList::iterator x = _events.begin(); x != _events.end(); ++x) {
1753  o << (*x)->value << " @ " << (uint64_t) (*x)->when << endl;
1754  }
1755 }
1756 
1757 } // namespace Evoral
1758 
bool extend_to(double)
#define isnan_local
Definition: ControlList.cpp:27
const ParameterDescriptor & descriptor() const
bool operator()(ControlEvent *a, ControlEvent *b)
boost::shared_ptr< ControlList > cut_copy_clear(double, double, int op)
float lower
Minimum value (in Hz, for frequencies)
void write_pass_finished(double when, double thinning_factor=0.0)
bool rt_safe_earliest_event_discrete_unlocked(double start, double &x, double &y, bool inclusive) const
Glib::Threads::RWLock _lock
void x_scale(double factor)
std::list< ControlEvent * > EventList
Definition: ControlList.hpp:81
bool in_write_pass() const
ControlList::const_iterator first
bool rt_safe_earliest_event_linear_unlocked(double start, double &x, double &y, bool inclusive) const
void build_search_cache_if_necessary(double start) const
virtual void freeze()
void set_interpolation(InterpolationStyle)
void mark_dirty() const
tuple f
Definition: signals.py:35
Definition: Beats.hpp:239
void start_write_pass(double time)
void unlocked_invalidate_insert_iterator()
LIBARDOUR_API PBD::PropertyDescriptor< framepos_t > start
Definition: region.cc:63
void add_guard_point(double when)
PBD::Signal0< void > Dirty
virtual void maybe_signal_changed()
bool maybe_insert_straight_line(double when, double value)
virtual void thaw()
void modify(iterator, double, double)
bool erase_range_internal(double start, double end, EventList &)
void dump(std::ostream &)
bool operator!=(ControlList const &) const
ParameterDescriptor _desc
float upper
Maximum value (in Hz, for frequencies)
void mark_dirty() const
Definition: Curve.hpp:41
void thin(double thinning_factor)
virtual boost::shared_ptr< ControlList > create(const Parameter &id, const ParameterDescriptor &desc)
bool event_time_less_than(ControlEvent *a, ControlEvent *b)
Definition: ControlList.cpp:48
virtual void editor_add(double when, double value)
void copy_events(const ControlList &)
#define DEBUG_TRACE(bits, str)
Definition: debug.h:55
void fast_simple_add(double when, double value)
boost::shared_ptr< ControlList > cut(double, double)
void erase(iterator)
PBD::Signal1< void, InterpolationStyle > InterpolationChanged
const Parameter & parameter() const
T * get() const
Definition: shared_ptr.hpp:268
double unlocked_eval(double x) const
void set_in_write_pass(bool, bool add_point=false, double when=0.0)
iterator most_recent_insert_iterator
void truncate_start(double length)
double multipoint_eval(double x) const
SearchCache _search_cache
std::pair< ControlList::const_iterator, ControlList::const_iterator > range
void shift(double before, double distance)
boost::shared_ptr< ControlList > copy(double, double)
bool operator==(const ControlList &)
bool rt_safe_earliest_event_unlocked(double start, double &x, double &y, bool start_inclusive=false) const
LIBEVORAL_API uint64_t ControlList
Definition: debug.cpp:5
bool toggled
True iff parameter is boolean.
void _x_scale(double factor)
Definition: debug.h:30
bool move_ranges(std::list< RangeMove< double > > const &)
bool paste(const ControlList &, double position, float times)
EventList::const_iterator const_iterator
Definition: ControlList.hpp:84
void maybe_add_insert_guard(double when)
EventList::reverse_iterator reverse_iterator
Definition: ControlList.hpp:83
ControlList(const Parameter &id, const ParameterDescriptor &desc)
Definition: ControlList.cpp:53
void erase_range(double start, double end)
void slide(iterator before, double distance)
iterator erase_from_iterator_to(iterator iter, double when)
bool rt_safe_earliest_event(double start, double &x, double &y, bool start_inclusive=false) const
LookupCache _lookup_cache
virtual void add(double when, double value, bool with_guards=true, bool with_default=true)
void truncate_end(double length)
ControlList & operator=(const ControlList &)
void invalidate_insert_iterator()
static bool time_comparator(const ControlEvent *a, const ControlEvent *b)
std::pair< ControlList::iterator, ControlList::iterator > control_points_adjacent(double when)
EventList::iterator iterator
Definition: ControlList.hpp:82
std::string string_compose(const std::string &fmt, const T1 &o1)
Definition: compose.h:208
InterpolationStyle _interpolation