X-Git-Url: https://main.carlh.net/gitweb/?a=blobdiff_plain;f=libs%2Fevoral%2Fsrc%2FControlList.cpp;h=e90b28c1479fa0598942e490c77284cf17b42151;hb=9befa88debe08349deb01ebc00b605721865eda4;hp=72af04a2d14f7a7eb16275544f237acc10b6cd26;hpb=067c4458a0ade1ae95f031c98a0ab7b3a349a74f;p=ardour.git diff --git a/libs/evoral/src/ControlList.cpp b/libs/evoral/src/ControlList.cpp index 72af04a2d1..e90b28c147 100644 --- a/libs/evoral/src/ControlList.cpp +++ b/libs/evoral/src/ControlList.cpp @@ -1,5 +1,5 @@ /* This file is part of Evoral. - * Copyright (C) 2008 Dave Robillard + * Copyright (C) 2008 David Robillard * Copyright (C) 2000-2008 Paul Davis * * Evoral is free software; you can redistribute it and/or modify it under the @@ -33,6 +33,21 @@ inline bool event_time_less_than (ControlEvent* a, ControlEvent* b) return a->when < b->when; } +/* this has no units but corresponds to the area of a rectangle + computed between three points in the list. If the area is + large, it indicates significant non-linearity between the + points. + + during automation recording we thin the recorded points + using this value. if a point is sufficiently co-linear + with its neighbours (as defined by the area of the rectangle + formed by three of them), we will not include it in the + ControlList. a smaller value will exclude less points, + a larger value will exclude more points, so it effectively + measures the amount of thinning to be done. +*/ + +double ControlList::_thinning_factor = 20.0; ControlList::ControlList (const Parameter& id) : _parameter(id) @@ -43,7 +58,6 @@ ControlList::ControlList (const Parameter& id) _changed_when_thawed = false; _min_yval = id.min(); _max_yval = id.max(); - _max_xval = 0; // means "no limit" _default_value = 0; _lookup_cache.left = -1; _lookup_cache.range.first = _events.end(); @@ -61,7 +75,6 @@ ControlList::ControlList (const ControlList& other) _changed_when_thawed = false; _min_yval = other._min_yval; _max_yval = other._max_yval; - _max_xval = other._max_xval; _default_value = other._default_value; _lookup_cache.range.first = _events.end(); _search_cache.first = _events.end(); @@ -83,7 +96,6 @@ ControlList::ControlList (const ControlList& other, double start, double end) _changed_when_thawed = false; _min_yval = other._min_yval; _max_yval = other._max_yval; - _max_xval = other._max_xval; _default_value = other._default_value; _lookup_cache.range.first = _events.end(); _search_cache.first = _events.end(); @@ -107,11 +119,11 @@ ControlList::~ControlList() for (EventList::iterator x = _events.begin(); x != _events.end(); ++x) { delete (*x); } - + for (list::iterator n = nascent.begin(); n != nascent.end(); ++n) { - for (EventList::iterator x = (*n)->events.begin(); x != (*n)->events.end(); ++x) { - delete *x; - } + for (EventList::iterator x = (*n)->events.begin(); x != (*n)->events.end(); ++x) { + delete *x; + } delete (*n); } @@ -143,7 +155,6 @@ ControlList::operator= (const ControlList& other) _min_yval = other._min_yval; _max_yval = other._max_yval; - _max_xval = other._max_xval; _default_value = other._default_value; mark_dirty (); @@ -207,11 +218,11 @@ ControlList::extend_to (double when) return true; } -void +void ControlList::_x_scale (double factor) { for (iterator i = _events.begin(); i != _events.end(); ++i) { - (*i)->when = floor ((*i)->when * factor); + (*i)->when *= factor; } mark_dirty (); @@ -220,163 +231,232 @@ ControlList::_x_scale (double factor) void ControlList::write_pass_finished (double when) { - merge_nascent (when); + merge_nascent (when); } + +struct ControlEventTimeComparator { + bool operator() (ControlEvent* a, ControlEvent* b) { + return a->when < b->when; + } +}; + void ControlList::merge_nascent (double when) { - { - Glib::Mutex::Lock lm (_lock); - - if (nascent.empty()) { - return; - } - - for (list::iterator n = nascent.begin(); n != nascent.end(); ++n) { - - NascentInfo* ninfo = *n; - EventList& nascent_events (ninfo->events); - bool need_adjacent_start_clamp; - bool need_adjacent_end_clamp; - - if (nascent_events.empty()) { - delete ninfo; - continue; - } - - if (ninfo->start_time < 0.0) { - ninfo->start_time = nascent_events.front()->when; - } - - if (ninfo->end_time < 0.0) { - ninfo->end_time = when; - } - - bool preexisting = !_events.empty(); - - if (!preexisting) { - - _events = nascent_events; - - } else if (ninfo->end_time < _events.front()->when) { - - /* all points in nascent are before the first existing point */ - - _events.insert (_events.begin(), nascent_events.begin(), nascent_events.end()); - - } else if (ninfo->start_time > _events.back()->when) { - - /* all points in nascent are after the last existing point */ - - _events.insert (_events.end(), nascent_events.begin(), nascent_events.end()); - - } else { - - /* find the range that overaps with nascent events, - and insert the contents of nascent events. - */ - - iterator i; - iterator range_begin = _events.end(); - iterator range_end = _events.end(); - double end_value = unlocked_eval (ninfo->end_time); - double start_value = unlocked_eval (ninfo->start_time - 1); - - need_adjacent_end_clamp = true; - need_adjacent_start_clamp = true; - - for (i = _events.begin(); i != _events.end(); ++i) { - - if ((*i)->when == ninfo->start_time) { - /* existing point at same time, remove it - and the consider the next point instead. - */ - i = _events.erase (i); - - if (i == _events.end()) { - break; - } - - if (range_begin == _events.end()) { - range_begin = i; - need_adjacent_start_clamp = false; - } else { - need_adjacent_end_clamp = false; - } - - if ((*i)->when > ninfo->end_time) { - range_end = i; - break; - } - - } else if ((*i)->when > ninfo->start_time) { - - if (range_begin == _events.end()) { - range_begin = i; - } - - if ((*i)->when > ninfo->end_time) { - range_end = i; - break; - } - } - } - - assert (range_begin != _events.end()); - - if (range_begin != _events.begin()) { - /* clamp point before */ - if (need_adjacent_start_clamp) { - _events.insert (range_begin, new ControlEvent (ninfo->start_time, start_value)); - } - } - - _events.insert (range_begin, nascent_events.begin(), nascent_events.end()); - - if (range_end != _events.end()) { - /* clamp point after */ - if (need_adjacent_end_clamp) { - _events.insert (range_begin, new ControlEvent (ninfo->end_time, end_value)); - } - } - - _events.erase (range_begin, range_end); - } - - delete ninfo; - } - - nascent.clear (); - - if (writing()) { - nascent.push_back (new NascentInfo (false)); - } - } - - maybe_signal_changed (); + { + Glib::Mutex::Lock lm (_lock); + + if (nascent.empty()) { + return; + } + + for (list::iterator n = nascent.begin(); n != nascent.end(); ++n) { + + NascentInfo* ninfo = *n; + EventList& nascent_events (ninfo->events); + bool need_adjacent_start_clamp; + bool need_adjacent_end_clamp; + + if (nascent_events.empty()) { + delete ninfo; + continue; + } + + nascent_events.sort (ControlEventTimeComparator ()); + + if (ninfo->start_time < 0.0) { + ninfo->start_time = nascent_events.front()->when; + } + + if (ninfo->end_time < 0.0) { + ninfo->end_time = when; + } + + bool preexisting = !_events.empty(); + + if (!preexisting) { + + _events = nascent_events; + + } else if (ninfo->end_time < _events.front()->when) { + + /* all points in nascent are before the first existing point */ + + _events.insert (_events.begin(), nascent_events.begin(), nascent_events.end()); + + } else if (ninfo->start_time > _events.back()->when) { + + /* all points in nascent are after the last existing point */ + + _events.insert (_events.end(), nascent_events.begin(), nascent_events.end()); + + } else { + + /* find the range that overlaps with nascent events, + and insert the contents of nascent events. + */ + + iterator i; + iterator range_begin = _events.end(); + iterator range_end = _events.end(); + double end_value = unlocked_eval (ninfo->end_time); + double start_value = unlocked_eval (ninfo->start_time - 1); + + need_adjacent_end_clamp = true; + need_adjacent_start_clamp = true; + + for (i = _events.begin(); i != _events.end(); ++i) { + + if ((*i)->when == ninfo->start_time) { + /* existing point at same time, remove it + and the consider the next point instead. + */ + i = _events.erase (i); + + if (i == _events.end()) { + break; + } + + if (range_begin == _events.end()) { + range_begin = i; + need_adjacent_start_clamp = false; + } else { + need_adjacent_end_clamp = false; + } + + if ((*i)->when > ninfo->end_time) { + range_end = i; + break; + } + + } else if ((*i)->when > ninfo->start_time) { + + if (range_begin == _events.end()) { + range_begin = i; + } + + if ((*i)->when > ninfo->end_time) { + range_end = i; + break; + } + } + } + + /* Now: + range_begin is the first event on our list after the first nascent event + range_end is the first event on our list after the last nascent event + + range_begin may be equal to _events.end() iff the last event on our list + was at the same time as the first nascent event. + */ + + if (range_begin != _events.begin()) { + /* clamp point before */ + if (need_adjacent_start_clamp) { + _events.insert (range_begin, new ControlEvent (ninfo->start_time, start_value)); + } + } + + _events.insert (range_begin, nascent_events.begin(), nascent_events.end()); + + if (range_end != _events.end()) { + /* clamp point after */ + if (need_adjacent_end_clamp) { + _events.insert (range_begin, new ControlEvent (ninfo->end_time, end_value)); + } + } + + _events.erase (range_begin, range_end); + } + + delete ninfo; + } + + nascent.clear (); + + if (writing()) { + nascent.push_back (new NascentInfo ()); + } + } + + maybe_signal_changed (); } void ControlList::rt_add (double when, double value) { - // this is for automation recording - - if (touch_enabled() && !touching()) { - return; - } + // this is for automation recording + + if (touch_enabled() && !touching()) { + return; + } //cerr << "RT: alist " << this << " add " << value << " @ " << when << endl; - Glib::Mutex::Lock lm (_lock, Glib::TRY_LOCK); + Glib::Mutex::Lock lm (_lock, Glib::TRY_LOCK); + + if (lm.locked()) { + assert (!nascent.empty()); + /* we don't worry about adding events out of time order as we will + sort them in merge_nascent. + */ + + EventList& el (nascent.back()->events); + + if (el.size() > 1 && (when >= el.back()->when) && (value == el.back()->value)) { + /* same value, later timestamp, effective slope is + * zero, so just move the last point in nascent to our + * new time position. this avoids storing an unlimited + * number of points to represent a flat line. + */ + el.back()->when = when; + } else { + nascent.back()->events.push_back (new ControlEvent (when, value)); + } + } +} + +void +ControlList::thin () +{ + Glib::Mutex::Lock lm (_lock); + + ControlEvent* prevprev = 0; + ControlEvent* cur = 0; + ControlEvent* prev = 0; + iterator pprev; + int counter = 0; + + for (iterator i = _events.begin(); i != _events.end(); ++i) { + + cur = *i; + counter++; + + if (counter > 2) { + + double area = fabs ((prevprev->when * (prev->value - cur->value)) + + (prev->when * (cur->value - prevprev->value)) + + (cur->when * (prevprev->value - prev->value))); + + if (area < _thinning_factor) { + iterator tmp = pprev; + + /* pprev will change to current + i is incremented to the next event + */ + + pprev = i; + _events.erase (tmp); - if (lm.locked()) { - assert (!nascent.empty()); - if (!nascent.back()->events.empty()) { - assert (when > nascent.back()->events.back()->when); - } - nascent.back()->events.push_back (new ControlEvent (when, value)); - } + continue; + } + } + + prevprev = prev; + prev = cur; + pprev = i; + } } void @@ -385,12 +465,20 @@ ControlList::fast_simple_add (double when, double value) /* to be used only for loading pre-sorted data from saved state */ _events.insert (_events.end(), new ControlEvent (when, value)); assert(_events.back()); + + mark_dirty (); } void ControlList::add (double when, double value) { - /* this is for graphical editing */ + /* this is for making changes from some kind of user interface or + control surface (GUI, MIDI, OSC etc) + */ + + if (!clamp_value (when, value)) { + return; + } { Glib::Mutex::Lock lm (_lock); @@ -447,6 +535,28 @@ ControlList::erase (iterator start, iterator end) maybe_signal_changed (); } +/** Erase the first event which matches the given time and value */ +void +ControlList::erase (double when, double value) +{ + { + Glib::Mutex::Lock lm (_lock); + + iterator i = begin (); + while (i != end() && ((*i)->when != when || (*i)->value != value)) { + ++i; + } + + if (i != end ()) { + _events.erase (i); + } + + mark_dirty (); + } + + maybe_signal_changed (); +} + void ControlList::reset_range (double start, double endt) { @@ -532,6 +642,26 @@ ControlList::slide (iterator before, double distance) (*before)->when += distance; ++before; } + + mark_dirty (); + } + + maybe_signal_changed (); +} + +void +ControlList::shift (double pos, double frames) +{ + { + Glib::Mutex::Lock lm (_lock); + + for (iterator i = _events.begin(); i != _events.end(); ++i) { + if ((*i)->when >= pos) { + (*i)->when += frames; + } + } + + mark_dirty (); } maybe_signal_changed (); @@ -551,7 +681,7 @@ ControlList::modify (iterator iter, double when, double val) (*iter)->when = when; (*iter)->value = val; - if (isnan (val)) { + if (std::isnan (val)) { abort (); } @@ -600,12 +730,6 @@ ControlList::control_points_adjacent (double xval) return ret; } -void -ControlList::set_max_xval (double x) -{ - _max_xval = x; -} - void ControlList::freeze () { @@ -664,7 +788,7 @@ ControlList::truncate_end (double last_coordinate) if (last_coordinate > _events.back()->when) { /* extending end: - */ + */ iterator foo = _events.begin(); bool lessthantwo; @@ -1066,7 +1190,7 @@ ControlList::rt_safe_earliest_event_discrete_unlocked (double start, double& x, return false; } - /* No points in range */ + /* No points in range */ } else { return false; } @@ -1087,9 +1211,9 @@ ControlList::rt_safe_earliest_event_linear_unlocked (double start, double& x, do const_iterator length_check_iter = _events.begin(); if (_events.empty()) { // 0 events return false; - } else if (_events.end() == ++length_check_iter) { // 1 event + } else if (_events.end() == ++length_check_iter) { // 1 event return rt_safe_earliest_event_discrete_unlocked (start, x, y, inclusive); - } + } // Hack to avoid infinitely repeating the same event build_search_cache_if_necessary (start); @@ -1108,7 +1232,7 @@ ControlList::rt_safe_earliest_event_linear_unlocked (double start, double& x, do } next = *_search_cache.first; - /* Step is before first */ + /* Step is before first */ } else { const_iterator prev = _search_cache.first; --prev; @@ -1166,11 +1290,11 @@ ControlList::rt_safe_earliest_event_linear_unlocked (double start, double& x, do } /*cerr << first->value << " @ " << first->when << " ... " - << next->value << " @ " << next->when - << " = " << y << " @ " << x << endl;*/ + << next->value << " @ " << next->when + << " = " << y << " @ " << x << endl;*/ assert( (y >= first->value && y <= next->value) - || (y <= first->value && y >= next->value) ); + || (y <= first->value && y >= next->value) ); const bool past_start = (inclusive ? x >= start : x > start); @@ -1184,7 +1308,7 @@ ControlList::rt_safe_earliest_event_linear_unlocked (double start, double& x, do return false; } - /* No points in the future, so no steps (towards them) in the future */ + /* No points in the future, so no steps (towards them) in the future */ } else { return false; } @@ -1200,14 +1324,14 @@ ControlList::cut_copy_clear (double start, double end, int op) { boost::shared_ptr nal = create (_parameter); iterator s, e; - ControlEvent cp (start, 0.0); + ControlEvent cp (start, 0.0); { - Glib::Mutex::Lock lm (_lock); + Glib::Mutex::Lock lm (_lock); - /* first, determine s & e, two iterators that define the range of points - affected by this operation - */ + /* first, determine s & e, two iterators that define the range of points + affected by this operation + */ if ((s = lower_bound (_events.begin(), _events.end(), &cp, time_comparator)) == _events.end()) { return nal; @@ -1218,43 +1342,43 @@ ControlList::cut_copy_clear (double start, double end, int op) e = upper_bound (_events.begin(), _events.end(), &cp, time_comparator); - /* if "start" isn't the location of an existing point, - evaluate the curve to get a value for the start. Add a point to - both the existing event list, and if its not a "clear" operation, - to the copy ("nal") as well. + /* if "start" isn't the location of an existing point, + evaluate the curve to get a value for the start. Add a point to + both the existing event list, and if its not a "clear" operation, + to the copy ("nal") as well. - Note that the time positions of the points in each list are different - because we want the copy ("nal") to have a zero time reference. - */ + Note that the time positions of the points in each list are different + because we want the copy ("nal") to have a zero time reference. + */ - - /* before we begin any cut/clear operations, get the value of the curve - at "end". - */ - double end_value = unlocked_eval (end); + /* before we begin any cut/clear operations, get the value of the curve + at "end". + */ - if ((*s)->when != start) { - - double val = unlocked_eval (start); + double end_value = unlocked_eval (end); + + if ((*s)->when != start) { + + double val = unlocked_eval (start); if (op == 0) { // cut if (start > _events.front()->when) { _events.insert (s, (new ControlEvent (start, val))); } } - - if (op != 2) { // ! clear - nal->_events.push_back (new ControlEvent (0, val)); - } - } + + if (op != 2) { // ! clear + nal->_events.push_back (new ControlEvent (0, val)); + } + } for (iterator x = s; x != e; ) { /* adjust new points to be relative to start, which has been set to zero. */ - + if (op != 2) { nal->_events.push_back (new ControlEvent ((*x)->when - start, (*x)->value)); } @@ -1262,30 +1386,30 @@ ControlList::cut_copy_clear (double start, double end, int op) if (op != 1) { x = _events.erase (x); } else { - ++x; - } + ++x; + } } - - if (e == _events.end() || (*e)->when != end) { - /* only add a boundary point if there is a point after "end" - */ + if (e == _events.end() || (*e)->when != end) { + + /* only add a boundary point if there is a point after "end" + */ - if (op == 0 && (e != _events.end() && end < (*e)->when)) { // cut - _events.insert (e, new ControlEvent (end, end_value)); - } + if (op == 0 && (e != _events.end() && end < (*e)->when)) { // cut + _events.insert (e, new ControlEvent (end, end_value)); + } - if (op != 2 && (e != _events.end() && end < (*e)->when)) { // cut/copy - nal->_events.push_back (new ControlEvent (end - start, end_value)); - } + if (op != 2 && (e != _events.end() && end < (*e)->when)) { // cut/copy + nal->_events.push_back (new ControlEvent (end - start, end_value)); + } } - mark_dirty (); + mark_dirty (); } - if (op != 1) { - maybe_signal_changed (); - } + if (op != 1) { + maybe_signal_changed (); + } return nal; } @@ -1427,5 +1551,11 @@ ControlList::set_interpolation (InterpolationStyle s) InterpolationChanged (s); /* EMIT SIGNAL */ } +void +ControlList::set_thinning_factor (double v) +{ + _thinning_factor = v; +} + } // namespace Evoral