Fix MIDI selection/tool issues (issue #0002415 and other bugs).
[ardour.git] / libs / ardour / automation_event.cc
index a024391980a3620f9b72a0f77d45819564879672..af390953f424aa660b773efa64501a130e2f85f4 100644 (file)
@@ -55,6 +55,7 @@ static void dumpit (const AutomationList& al, string prefix = "")
 }
 #endif
 
+/* XXX: min_val max_val redundant? (param.min() param.max()) */
 AutomationList::AutomationList (Parameter id, double min_val, double max_val, double default_val)
        : _parameter(id)
        , _interpolation(Linear)
@@ -318,7 +319,6 @@ AutomationList::rt_add (double when, double value)
                Glib::Mutex::Lock lm (_lock);
 
                iterator where;
-               TimeComparator cmp;
                ControlEvent cp (when, 0.0);
                bool done = false;
 
@@ -370,7 +370,7 @@ AutomationList::rt_add (double when, double value)
                        
                } else {
 
-                       where = lower_bound (_events.begin(), _events.end(), &cp, cmp);
+                       where = lower_bound (_events.begin(), _events.end(), &cp, time_comparator);
 
                        if (where != _events.end()) {
                                if ((*where)->when == when) {
@@ -379,7 +379,7 @@ AutomationList::rt_add (double when, double value)
                                }
                        }
                }
-               
+
                if (!done) {
                        _rt_insertion_point = _events.insert (where, new ControlEvent (when, value));
                }
@@ -396,6 +396,7 @@ AutomationList::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());
 }
 
 void
@@ -405,12 +406,11 @@ AutomationList::add (double when, double value)
 
        {
                Glib::Mutex::Lock lm (_lock);
-               TimeComparator cmp;
                ControlEvent cp (when, 0.0f);
                bool insert = true;
                iterator insertion_point;
 
-               for (insertion_point = lower_bound (_events.begin(), _events.end(), &cp, cmp); insertion_point != _events.end(); ++insertion_point) {
+               for (insertion_point = lower_bound (_events.begin(), _events.end(), &cp, time_comparator); insertion_point != _events.end(); ++insertion_point) {
 
                        /* only one point allowed per time point */
 
@@ -469,15 +469,14 @@ AutomationList::reset_range (double start, double endt)
 
        {
         Glib::Mutex::Lock lm (_lock);
-               TimeComparator cmp;
                ControlEvent cp (start, 0.0f);
                iterator s;
                iterator e;
                
-               if ((s = lower_bound (_events.begin(), _events.end(), &cp, cmp)) != _events.end()) {
+               if ((s = lower_bound (_events.begin(), _events.end(), &cp, time_comparator)) != _events.end()) {
 
                        cp.when = endt;
-                       e = upper_bound (_events.begin(), _events.end(), &cp, cmp);
+                       e = upper_bound (_events.begin(), _events.end(), &cp, time_comparator);
 
                        for (iterator i = s; i != e; ++i) {
                                (*i)->value = _default_value;
@@ -501,14 +500,13 @@ AutomationList::erase_range (double start, double endt)
 
        {
                Glib::Mutex::Lock lm (_lock);
-               TimeComparator cmp;
                ControlEvent cp (start, 0.0f);
                iterator s;
                iterator e;
 
-               if ((s = lower_bound (_events.begin(), _events.end(), &cp, cmp)) != _events.end()) {
+               if ((s = lower_bound (_events.begin(), _events.end(), &cp, time_comparator)) != _events.end()) {
                        cp.when = endt;
-                       e = upper_bound (_events.begin(), _events.end(), &cp, cmp);
+                       e = upper_bound (_events.begin(), _events.end(), &cp, time_comparator);
                        _events.erase (s, e);
                        reposition_for_rt_add (0);
                        erased = true;
@@ -608,14 +606,13 @@ AutomationList::control_points_adjacent (double xval)
 {
        Glib::Mutex::Lock lm (_lock);
        iterator i;
-       TimeComparator cmp;
        ControlEvent cp (xval, 0.0f);
        std::pair<iterator,iterator> ret;
 
        ret.first = _events.end();
        ret.second = _events.end();
 
-       for (i = lower_bound (_events.begin(), _events.end(), &cp, cmp); i != _events.end(); ++i) {
+       for (i = lower_bound (_events.begin(), _events.end(), &cp, time_comparator); i != _events.end(); ++i) {
                
                if (ret.first == _events.end()) {
                        if ((*i)->when >= xval) {
@@ -690,7 +687,7 @@ AutomationList::truncate_end (double last_coordinate)
        {
                Glib::Mutex::Lock lm (_lock);
                ControlEvent cp (last_coordinate, 0);
-               list<ControlEvent*>::reverse_iterator i;
+               AutomationList::reverse_iterator i;
                double last_val;
 
                if (_events.empty()) {
@@ -759,7 +756,7 @@ AutomationList::truncate_end (double last_coordinate)
                        uint32_t sz = _events.size();
                        
                        while (i != _events.rend() && sz > 2) {
-                               list<ControlEvent*>::reverse_iterator tmp;
+                               AutomationList::reverse_iterator tmp;
                                
                                tmp = i;
                                ++tmp;
@@ -856,7 +853,7 @@ AutomationList::truncate_start (double overall_length)
                        i = _events.begin();
                        
                        while (i != _events.end() && !_events.empty()) {
-                               list<ControlEvent*>::iterator tmp;
+                               AutomationList::iterator tmp;
                                
                                tmp = i;
                                ++tmp;
@@ -953,6 +950,9 @@ AutomationList::unlocked_eval (double x) const
                return multipoint_eval (x);
                break;
        }
+
+       /*NOTREACHED*/ /* stupid gcc */
+       return 0.0;
 }
 
 double
@@ -966,8 +966,7 @@ AutomationList::multipoint_eval (double x) const
        /* FIXME: no cache.  significant? */
        if (_interpolation == Discrete) {
                const ControlEvent cp (x, 0);
-               TimeComparator cmp;
-               EventList::const_iterator i = lower_bound (_events.begin(), _events.end(), &cp, cmp);
+               EventList::const_iterator i = lower_bound (_events.begin(), _events.end(), &cp, time_comparator);
 
                // shouldn't have made it to multipoint_eval
                assert(i != _events.end());
@@ -986,9 +985,8 @@ AutomationList::multipoint_eval (double x) const
             ((*_lookup_cache.range.second)->when < x))) {
 
                const ControlEvent cp (x, 0);
-               TimeComparator cmp;
                
-               _lookup_cache.range = equal_range (_events.begin(), _events.end(), &cp, cmp);
+               _lookup_cache.range = equal_range (_events.begin(), _events.end(), &cp, time_comparator);
        }
        
        pair<const_iterator,const_iterator> range = _lookup_cache.range;
@@ -1031,47 +1029,87 @@ AutomationList::multipoint_eval (double x) const
        return (*range.first)->value;
 }
 
-/** Get the earliest event between \a start and \a end.
+void
+AutomationList::build_search_cache_if_necessary(double start, double end) const
+{
+       /* Only do the range lookup if x is in a different range than last time
+        * this was called (or if the search cache has been marked "dirty" (left<0) */
+       if (!_events.empty() && ((_search_cache.left < 0) ||
+                       ((_search_cache.left > start) ||
+                        (_search_cache.right < end)))) {
+
+               const ControlEvent start_point (start, 0);
+               const ControlEvent end_point (end, 0);
+
+               //cerr << "REBUILD: (" << _search_cache.left << ".." << _search_cache.right << ") := ("
+               //      << start << ".." << end << ")" << endl;
+
+               _search_cache.range.first = lower_bound (_events.begin(), _events.end(), &start_point, time_comparator);
+               _search_cache.range.second = upper_bound (_events.begin(), _events.end(), &end_point, time_comparator);
+
+               _search_cache.left = start;
+               _search_cache.right = end;
+       }
+}
+
+/** Get the earliest event between \a start and \a end, using the current interpolation style.
  *
  * If an event is found, \a x and \a y are set to its coordinates.
+ *
+ * \param inclusive Include events with timestamp exactly equal to \a start
  * \return true if event is found (and \a x and \a y are valid).
  */
 bool
-AutomationList::rt_safe_earliest_event (double start, double end, double& x, double& y) const
+AutomationList::rt_safe_earliest_event(double start, double end, double& x, double& y, bool inclusive) const
 {
        // FIXME: It would be nice if this was unnecessary..
        Glib::Mutex::Lock lm(_lock, Glib::TRY_LOCK);
        if (!lm.locked()) {
                return false;
        }
-       
-       /* Only do the range lookup if x is in a different range than last time
-        * this was called (or if the search cache has been marked "dirty" (left<0) */
-       if ((_search_cache.left < 0) ||
-           ((_search_cache.left > start) ||
-                (_search_cache.right < end))) {
 
-               const ControlEvent start_point (start, 0);
-               const ControlEvent end_point (end, 0);
-               TimeComparator cmp;
+       return rt_safe_earliest_event_unlocked(start, end, x, y, inclusive);
+} 
 
-               //cerr << "REBUILD: (" << _search_cache.left << ".." << _search_cache.right << ") -> ("
-               //      << start << ".." << end << ")" << endl;
 
-               _search_cache.range.first = lower_bound (_events.begin(), _events.end(), &start_point, cmp);
-               _search_cache.range.second = upper_bound (_events.begin(), _events.end(), &end_point, cmp);
+/** Get the earliest event between \a start and \a end, using the current interpolation style.
+ *
+ * If an event is found, \a x and \a y are set to its coordinates.
+ *
+ * \param inclusive Include events with timestamp exactly equal to \a start
+ * \return true if event is found (and \a x and \a y are valid).
+ */
+bool
+AutomationList::rt_safe_earliest_event_unlocked(double start, double end, double& x, double& y, bool inclusive) const
+{
+       if (_interpolation == Discrete)
+               return rt_safe_earliest_event_discrete_unlocked(start, end, x, y, inclusive);
+       else
+               return rt_safe_earliest_event_linear_unlocked(start, end, x, y, inclusive);
+} 
+
 
-               _search_cache.left = start;
-               _search_cache.right = end;
-       }
-       
-       pair<const_iterator,const_iterator> range = _search_cache.range;
+/** Get the earliest event between \a start and \a end (Discrete (lack of) interpolation)
+ *
+ * If an event is found, \a x and \a y are set to its coordinates.
+ *
+ * \param inclusive Include events with timestamp exactly equal to \a start
+ * \return true if event is found (and \a x and \a y are valid).
+ */
+bool
+AutomationList::rt_safe_earliest_event_discrete_unlocked (double start, double end, double& x, double& y, bool inclusive) const
+{
+       build_search_cache_if_necessary(start, end);
+
+       const pair<const_iterator,const_iterator>& range = _search_cache.range;
 
        if (range.first != _events.end()) {
                const ControlEvent* const first = *range.first;
 
+               const bool past_start = (inclusive ? first->when >= start : first->when > start);
+
                /* Earliest points is in range, return it */
-               if (first->when >= start && first->when < end) {
+               if (past_start >= start && first->when < end) {
 
                        x = first->when;
                        y = first->value;
@@ -1086,7 +1124,6 @@ AutomationList::rt_safe_earliest_event (double start, double end, double& x, dou
                        return true;
 
                } else {
-                               
                        return false;
                }
        
@@ -1096,6 +1133,120 @@ AutomationList::rt_safe_earliest_event (double start, double end, double& x, dou
        }
 }
 
+/** Get the earliest time the line crosses an integer (Linear interpolation).
+ *
+ * If an event is found, \a x and \a y are set to its coordinates.
+ *
+ * \param inclusive Include events with timestamp exactly equal to \a start
+ * \return true if event is found (and \a x and \a y are valid).
+ */
+bool
+AutomationList::rt_safe_earliest_event_linear_unlocked (double start, double end, double& x, double& y, bool inclusive) const
+{
+       //cerr << "earliest_event(" << start << ", " << end << ", " << x << ", " << y << ", " << inclusive << endl;
+
+       if (_events.size() == 0)
+               return false;
+       else if (_events.size() == 1)
+               return rt_safe_earliest_event_discrete_unlocked(start, end, x, y, inclusive);
+
+       // Hack to avoid infinitely repeating the same event
+       build_search_cache_if_necessary(start, end);
+       
+       pair<const_iterator,const_iterator> range = _search_cache.range;
+
+       if (range.first != _events.end()) {
+
+               const ControlEvent* first = NULL;
+               const ControlEvent* next = NULL;
+
+               /* Step is after first */
+               if (range.first == _events.begin() || (*range.first)->when == start) {
+                       first = *range.first;
+                       next = *(++range.first);
+                       ++_search_cache.range.first;
+
+               /* Step is before first */
+               } else {
+                       const_iterator prev = range.first;
+                       --prev;
+                       first = *prev;
+                       next = *range.first;
+               }
+               
+               if (inclusive && first->when == start) {
+                       x = first->when;
+                       y = first->value;
+                       /* Move left of cache to this point
+                        * (Optimize for immediate call this cycle within range) */
+                       _search_cache.left = x;
+                       //++_search_cache.range.first;
+                       return true;
+               }
+                       
+               if (abs(first->value - next->value) <= 1) {
+                       if (next->when <= end && (!inclusive || next->when > start)) {
+                               x = next->when;
+                               y = next->value;
+                               /* Move left of cache to this point
+                                * (Optimize for immediate call this cycle within range) */
+                               _search_cache.left = x;
+                               //++_search_cache.range.first;
+                               return true;
+                       } else {
+                               return false;
+                       }
+               }
+
+               const double slope = (next->value - first->value) / (double)(next->when - first->when);
+               //cerr << "start y: " << start_y << endl;
+
+               //y = first->value + (slope * fabs(start - first->when));
+               y = first->value;
+
+               if (first->value < next->value) // ramping up
+                       y = ceil(y);
+               else // ramping down
+                       y = floor(y);
+
+               x = first->when + (y - first->value) / (double)slope;
+               
+               while ((inclusive && x < start) || (x <= start && y != next->value)) {
+                       
+                       if (first->value < next->value) // ramping up
+                               y += 1.0;
+                       else // ramping down
+                               y -= 1.0;
+
+                       x = first->when + (y - first->value) / (double)slope;
+               }
+
+               /*cerr << first->value << " @ " << first->when << " ... "
+                               << next->value << " @ " << next->when
+                               << " = " << y << " @ " << x << endl;*/
+
+               assert(    (y >= first->value && y <= next->value)
+                               || (y <= first->value && y >= next->value) );
+
+               
+               const bool past_start = (inclusive ? x >= start : x > start);
+               if (past_start && x < end) {
+                       /* Move left of cache to this point
+                        * (Optimize for immediate call this cycle within range) */
+                       _search_cache.left = x;
+
+                       return true;
+
+               } else {
+                       return false;
+               }
+       
+       /* No points in the future, so no steps (towards them) in the future */
+       } else {
+               return false;
+       }
+}
+
 AutomationList*
 AutomationList::cut (iterator start, iterator end)
 {
@@ -1132,18 +1283,17 @@ AutomationList::cut_copy_clear (double start, double end, int op)
        AutomationList* nal = new AutomationList (_parameter, _min_yval, _max_yval, _default_value);
        iterator s, e;
        ControlEvent cp (start, 0.0);
-       TimeComparator cmp;
        bool changed = false;
        
        {
                Glib::Mutex::Lock lm (_lock);
 
-               if ((s = lower_bound (_events.begin(), _events.end(), &cp, cmp)) == _events.end()) {
+               if ((s = lower_bound (_events.begin(), _events.end(), &cp, time_comparator)) == _events.end()) {
                        return nal;
                }
 
                cp.when = end;
-               e = upper_bound (_events.begin(), _events.end(), &cp, cmp);
+               e = upper_bound (_events.begin(), _events.end(), &cp, time_comparator);
 
                if (op != 2 && (*s)->when != start) {
                        nal->_events.push_back (new ControlEvent (0, unlocked_eval (start)));
@@ -1243,9 +1393,8 @@ AutomationList::paste (AutomationList& alist, double pos, float times)
                iterator prev;
                double end = 0;
                ControlEvent cp (pos, 0.0);
-               TimeComparator cmp;
 
-               where = upper_bound (_events.begin(), _events.end(), &cp, cmp);
+               where = upper_bound (_events.begin(), _events.end(), &cp, time_comparator);
 
                for (iterator i = alist.begin();i != alist.end(); ++i) {
                        _events.insert (where, new ControlEvent( (*i)->when+pos,( *i)->value));