add skeleton for i18n support
[ardour.git] / libs / evoral / src / ControlList.cpp
index 4a710cfbb063221af71d9a76c40d4d9b54f60c61..6e428144a99cf88b98b5c6cd951d7e2627f79ca5 100644 (file)
@@ -1,5 +1,5 @@
 /* This file is part of Evoral.
- * Copyright (C) 2008 Dave Robillard <http://drobilla.net>
+ * Copyright (C) 2008 David Robillard <http://drobilla.net>
  * Copyright (C) 2000-2008 Paul Davis
  *
  * Evoral is free software; you can redistribute it and/or modify it under the
@@ -45,11 +45,10 @@ ControlList::ControlList (const Parameter& id)
        _max_yval = id.max();
        _max_xval = 0; // means "no limit"
        _default_value = 0;
-       _rt_insertion_point = _events.end();
        _lookup_cache.left = -1;
        _lookup_cache.range.first = _events.end();
        _search_cache.left = -1;
-       _search_cache.range.first = _events.end();
+       _search_cache.first = _events.end();
        _sort_pending = false;
 }
 
@@ -64,9 +63,8 @@ ControlList::ControlList (const ControlList& other)
        _max_yval = other._max_yval;
        _max_xval = other._max_xval;
        _default_value = other._default_value;
-       _rt_insertion_point = _events.end();
        _lookup_cache.range.first = _events.end();
-       _search_cache.range.first = _events.end();
+       _search_cache.first = _events.end();
        _sort_pending = false;
 
        for (const_iterator i = other._events.begin(); i != other._events.end(); ++i) {
@@ -87,9 +85,8 @@ ControlList::ControlList (const ControlList& other, double start, double end)
        _max_yval = other._max_yval;
        _max_xval = other._max_xval;
        _default_value = other._default_value;
-       _rt_insertion_point = _events.end();
        _lookup_cache.range.first = _events.end();
-       _search_cache.range.first = _events.end();
+       _search_cache.first = _events.end();
        _sort_pending = false;
 
        /* now grab the relevant points, and shift them back if necessary */
@@ -110,6 +107,13 @@ ControlList::~ControlList()
        for (EventList::iterator x = _events.begin(); x != _events.end(); ++x) {
                delete (*x);
        }
+        
+       for (list<NascentInfo*>::iterator n = nascent.begin(); n != nascent.end(); ++n) {
+                for (EventList::iterator x = (*n)->events.begin(); x != (*n)->events.end(); ++x) {
+                        delete *x;
+                }
+               delete (*n);
+       }
 
        delete _curve;
 }
@@ -203,7 +207,8 @@ ControlList::extend_to (double when)
        return true;
 }
 
-void ControlList::_x_scale (double factor)
+void 
+ControlList::_x_scale (double factor)
 {
        for (iterator i = _events.begin(); i != _events.end(); ++i) {
                (*i)->when = floor ((*i)->when * factor);
@@ -213,90 +218,180 @@ void ControlList::_x_scale (double factor)
 }
 
 void
-ControlList::reposition_for_rt_add (double /*when*/)
+ControlList::write_pass_finished (double when)
 {
-       _rt_insertion_point = _events.end();
+        merge_nascent (when);
 }
 
-void
-ControlList::rt_add (double when, double value)
-{
-       //cerr << "RT: alist " << this << " add " << value << " @ " << when << endl;
-
-       {
-               Glib::Mutex::Lock lm (_lock);
-
-               iterator where;
-               ControlEvent cp (when, 0.0);
-               bool done = false;
-
-               if ((_rt_insertion_point != _events.end()) && ((*_rt_insertion_point)->when < when) ) {
-
-                       /* we have a previous insertion point, so we should delete
-                          everything between it and the position where we are going
-                          to insert this point.
-                       */
-
-                       iterator after = _rt_insertion_point;
-
-                       if (++after != _events.end()) {
-                               iterator far = after;
-
-                               while (far != _events.end()) {
-                                       if ((*far)->when > when) {
-                                               break;
-                                       }
-                                       ++far;
-                               }
-
-                               if (_new_value) {
-                                       where = far;
-                                       _rt_insertion_point = where;
-
-                                       if ((*where)->when == when) {
-                                               (*where)->value = value;
-                                               done = true;
-                                       }
-                               } else {
-                                       where = _events.erase (after, far);
-                               }
-
-                       } else {
-
-                               where = after;
-
-                       }
-
-                       iterator previous = _rt_insertion_point;
-                       --previous;
-
-                       if (_rt_insertion_point != _events.begin() && (*_rt_insertion_point)->value == value && (*previous)->value == value) {
-                               (*_rt_insertion_point)->when = when;
-                               done = true;
 
-                       }
+struct ControlEventTimeComparator {
+       bool operator() (ControlEvent* a, ControlEvent* b) {
+               return a->when < b->when;
+       }
+};
 
-               } else {
+void
+ControlList::merge_nascent (double when)
+{
+        {
+                Glib::Mutex::Lock lm (_lock);
+
+                if (nascent.empty()) {
+                        return;
+                }
+
+                for (list<NascentInfo*>::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 ());
+                }
+        }
 
-                       where = lower_bound (_events.begin(), _events.end(), &cp, time_comparator);
+        maybe_signal_changed ();
+}
 
-                       if (where != _events.end()) {
-                               if ((*where)->when == when) {
-                                       (*where)->value = value;
-                                       done = true;
-                               }
-                       }
-               }
+void
+ControlList::rt_add (double when, double value)
+{
+        // this is for automation recording 
+        
+        if (touch_enabled() && !touching()) {
+                return;
+        }
 
-               if (!done) {
-                       _rt_insertion_point = _events.insert (where, new ControlEvent (when, value));
-               }
+       //cerr << "RT: alist " << this << " add " << value << " @ " << when << endl;
 
-               _new_value = false;
-               mark_dirty ();
-       }
+        Glib::Mutex::Lock lm (_lock, Glib::TRY_LOCK);
 
-       maybe_signal_changed ();
+        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.
+               */
+                nascent.back()->events.push_back (new ControlEvent (when, value));
+        }
 }
 
 void
@@ -310,7 +405,13 @@ ControlList::fast_simple_add (double when, double value)
 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);
@@ -336,7 +437,6 @@ ControlList::add (double when, double value)
                if (insert) {
 
                        _events.insert (insertion_point, new ControlEvent (when, value));
-                       reposition_for_rt_add (0);
 
                }
 
@@ -352,7 +452,6 @@ ControlList::erase (iterator i)
        {
                Glib::Mutex::Lock lm (_lock);
                _events.erase (i);
-               reposition_for_rt_add (0);
                mark_dirty ();
        }
        maybe_signal_changed ();
@@ -364,12 +463,33 @@ ControlList::erase (iterator start, iterator end)
        {
                Glib::Mutex::Lock lm (_lock);
                _events.erase (start, end);
-               reposition_for_rt_add (0);
                mark_dirty ();
        }
        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)
 {
@@ -411,7 +531,6 @@ ControlList::erase_range (double start, double endt)
                erased = erase_range_internal (start, endt, _events);
 
                if (erased) {
-                       reposition_for_rt_add (0);
                        mark_dirty ();
                }
 
@@ -434,7 +553,9 @@ ControlList::erase_range_internal (double start, double endt, EventList & events
                cp.when = endt;
                e = upper_bound (events.begin(), events.end(), &cp, time_comparator);
                events.erase (s, e);
-               erased = true;
+               if (s != e) {
+                       erased = true;
+               }
        }
 
        return erased;
@@ -454,6 +575,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 ();
@@ -662,7 +803,6 @@ ControlList::truncate_end (double last_coordinate)
                        _events.back()->value = last_val;
                }
 
-               reposition_for_rt_add (0);
                mark_dirty();
        }
 
@@ -762,8 +902,6 @@ ControlList::truncate_start (double overall_length)
                        _events.push_front (new ControlEvent (0, first_legal_value));
                }
 
-               reposition_for_rt_add (0);
-
                mark_dirty();
        }
 
@@ -902,29 +1040,23 @@ ControlList::multipoint_eval (double x) const
 }
 
 void
-ControlList::build_search_cache_if_necessary(double start, double end) const
+ControlList::build_search_cache_if_necessary (double start) 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)))) {
+       if (!_events.empty() && ((_search_cache.left < 0) || (_search_cache.left > start))) {
 
                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.first = lower_bound (_events.begin(), _events.end(), &start_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.
+/** Get the earliest event after \a start using the current interpolation style.
  *
  * If an event is found, \a x and \a y are set to its coordinates.
  *
@@ -932,7 +1064,7 @@ ControlList::build_search_cache_if_necessary(double start, double end) const
  * \return true if event is found (and \a x and \a y are valid).
  */
 bool
-ControlList::rt_safe_earliest_event(double start, double end, double& x, double& y, bool inclusive) const
+ControlList::rt_safe_earliest_event (double start, double& x, double& y, bool inclusive) const
 {
        // FIXME: It would be nice if this was unnecessary..
        Glib::Mutex::Lock lm(_lock, Glib::TRY_LOCK);
@@ -940,11 +1072,11 @@ ControlList::rt_safe_earliest_event(double start, double end, double& x, double&
                return false;
        }
 
-       return rt_safe_earliest_event_unlocked(start, end, x, y, inclusive);
+       return rt_safe_earliest_event_unlocked (start, x, y, inclusive);
 }
 
 
-/** Get the earliest event between \a start and \a end, using the current interpolation style.
+/** Get the earliest event after \a start using the current interpolation style.
  *
  * If an event is found, \a x and \a y are set to its coordinates.
  *
@@ -952,17 +1084,17 @@ ControlList::rt_safe_earliest_event(double start, double end, double& x, double&
  * \return true if event is found (and \a x and \a y are valid).
  */
 bool
-ControlList::rt_safe_earliest_event_unlocked(double start, double end, double& x, double& y, bool inclusive) const
+ControlList::rt_safe_earliest_event_unlocked (double start, double& x, double& y, bool inclusive) const
 {
        if (_interpolation == Discrete) {
-               return rt_safe_earliest_event_discrete_unlocked(start, end, x, y, inclusive);
+               return rt_safe_earliest_event_discrete_unlocked(start, x, y, inclusive);
        } else {
-               return rt_safe_earliest_event_linear_unlocked(start, end, x, y, inclusive);
+               return rt_safe_earliest_event_linear_unlocked(start, x, y, inclusive);
        }
 }
 
 
-/** Get the earliest event between \a start and \a end (Discrete (lack of) interpolation)
+/** Get the earliest event after \a start without interpolation.
  *
  * If an event is found, \a x and \a y are set to its coordinates.
  *
@@ -970,19 +1102,17 @@ ControlList::rt_safe_earliest_event_unlocked(double start, double end, double& x
  * \return true if event is found (and \a x and \a y are valid).
  */
 bool
-ControlList::rt_safe_earliest_event_discrete_unlocked (double start, double end, double& x, double& y, bool inclusive) const
+ControlList::rt_safe_earliest_event_discrete_unlocked (double start, double& x, double& y, bool inclusive) const
 {
-       build_search_cache_if_necessary(start, end);
+       build_search_cache_if_necessary (start);
 
-       const pair<const_iterator,const_iterator>& range = _search_cache.range;
-
-       if (range.first != _events.end()) {
-               const ControlEvent* const first = *range.first;
+       if (_search_cache.first != _events.end()) {
+               const ControlEvent* const first = *_search_cache.first;
 
                const bool past_start = (inclusive ? first->when >= start : first->when > start);
 
                /* Earliest points is in range, return it */
-               if (past_start && first->when < end) {
+               if (past_start) {
 
                        x = first->when;
                        y = first->value;
@@ -990,10 +1120,9 @@ ControlList::rt_safe_earliest_event_discrete_unlocked (double start, double end,
                        /* Move left of cache to this point
                         * (Optimize for immediate call this cycle within range) */
                        _search_cache.left = x;
-                       ++_search_cache.range.first;
+                       ++_search_cache.first;
 
                        assert(x >= start);
-                       assert(x < end);
                        return true;
 
                } else {
@@ -1014,44 +1143,40 @@ ControlList::rt_safe_earliest_event_discrete_unlocked (double start, double end,
  * \return true if event is found (and \a x and \a y are valid).
  */
 bool
-ControlList::rt_safe_earliest_event_linear_unlocked (double start, double end, double& x, double& y, bool inclusive) const
+ControlList::rt_safe_earliest_event_linear_unlocked (double start, double& x, double& y, bool inclusive) const
 {
-       //cerr << "earliest_event(start: " << start << ", end: " << end
-       //<< ", x: " << x << ", y: " << y << ", inclusive: " << inclusive <<  ")" << endl;
+       // cout << "earliest_event(start: " << start << ", x: " << x << ", y: " << y << ", inclusive: " << inclusive <<  ")" << endl;
 
        const_iterator length_check_iter = _events.begin();
        if (_events.empty()) { // 0 events
                return false;
         } else if (_events.end() == ++length_check_iter) { // 1 event
-               return rt_safe_earliest_event_discrete_unlocked(start, end, x, y, inclusive);
+               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, end);
-
-       pair<const_iterator,const_iterator> range = _search_cache.range;
+       build_search_cache_if_necessary (start);
 
-       if (range.first != _events.end()) {
+       if (_search_cache.first != _events.end()) {
 
                const ControlEvent* first = NULL;
                const ControlEvent* next = NULL;
 
-               /* No events past start (maybe?) */
-               if (next && next->when < start)
-                       return false;
-
                /* Step is after first */
-               if (range.first == _events.begin() || (*range.first)->when == start) {
-                       first = *range.first;
-                       next = *(++range.first);
-                       ++_search_cache.range.first;
+               if (_search_cache.first == _events.begin() || (*_search_cache.first)->when <= start) {
+                       first = *_search_cache.first;
+                       ++_search_cache.first;
+                       if (_search_cache.first == _events.end()) {
+                               return false;
+                       }
+                       next = *_search_cache.first;
 
                /* Step is before first */
                } else {
-                       const_iterator prev = range.first;
+                       const_iterator prev = _search_cache.first;
                        --prev;
                        first = *prev;
-                       next = *range.first;
+                       next = *_search_cache.first;
                }
 
                if (inclusive && first->when == start) {
@@ -1066,7 +1191,7 @@ ControlList::rt_safe_earliest_event_linear_unlocked (double start, double end, d
                }
 
                if (fabs(first->value - next->value) <= 1) {
-                       if (next->when <= end && (next->when > start)) {
+                       if (next->when > start) {
                                x = next->when;
                                y = next->value;
                                /* Move left of cache to this point
@@ -1112,7 +1237,7 @@ ControlList::rt_safe_earliest_event_linear_unlocked (double start, double end, d
 
 
                const bool past_start = (inclusive ? x >= start : x > start);
-               if (past_start && x < end) {
+               if (past_start) {
                        /* Move left of cache to this point
                         * (Optimize for immediate call this cycle within range) */
                        _search_cache.left = x;
@@ -1128,50 +1253,25 @@ ControlList::rt_safe_earliest_event_linear_unlocked (double start, double end, d
        }
 }
 
-boost::shared_ptr<ControlList>
-ControlList::cut (iterator start, iterator end)
-{
-       boost::shared_ptr<ControlList> nal = create (_parameter);
-
-       {
-               Glib::Mutex::Lock lm (_lock);
-
-               for (iterator x = start; x != end; ) {
-                       iterator tmp;
-
-                       tmp = x;
-                       ++tmp;
 
-                       nal->_events.push_back (new ControlEvent (**x));
-                       _events.erase (x);
-
-                       reposition_for_rt_add (0);
-
-                       x = tmp;
-               }
-
-               mark_dirty ();
-       }
-
-       maybe_signal_changed ();
-
-       return nal;
-}
-
-/** @param op 0 = cut, 1 = copy, 2 = clear */
+/** @param start Start position in model coordinates.
+ *  @param end End position in model coordinates.
+ *  @param op 0 = cut, 1 = copy, 2 = clear.
+ */
 boost::shared_ptr<ControlList>
 ControlList::cut_copy_clear (double start, double end, int op)
 {
        boost::shared_ptr<ControlList> nal = create (_parameter);
-       
        iterator s, e;
-       bool changed = false;
+        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
+                */
 
-               /* find the first event in our list that is at or before `start' in time */
-               ControlEvent cp (start, 0.0);
                if ((s = lower_bound (_events.begin(), _events.end(), &cp, time_comparator)) == _events.end()) {
                        return nal;
                }
@@ -1180,70 +1280,79 @@ ControlList::cut_copy_clear (double start, double end, int op)
                cp.when = end;
                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)));
-               }
 
-               for (iterator x = s; x != e; ) {
-                       iterator tmp = x;
-                       ++tmp;
+                /* 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.
+                */
+
+                        
+                /* before we begin any cut/clear operations, get the value of the curve
+                   at "end".
+                */
+
+                double end_value = unlocked_eval (end);
 
-                       changed = true;
+                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));
+                        }
+                }
+
+               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));
                        }
 
                        if (op != 1) {
-                               _events.erase (x);
-                       }
-
-                       x = tmp;
+                               x = _events.erase (x);
+                       } else {
+                                ++x;
+                        }
                }
+                
+                if (e == _events.end() || (*e)->when != end) {
 
-               if (op != 2 && nal->_events.back()->when != end - start) {
-                       nal->_events.push_back (new ControlEvent (end - start, unlocked_eval (end)));
-               }
+                        /* only add a boundary point if there is a point after "end"
+                         */
 
-               if (changed) {
-                       reposition_for_rt_add (0);
+                        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));
+                        }
                }
 
-               mark_dirty ();
+                mark_dirty ();
        }
 
-       maybe_signal_changed ();
+        if (op != 1) {
+                maybe_signal_changed ();
+        }
 
        return nal;
-
 }
 
-boost::shared_ptr<ControlList>
-ControlList::copy (iterator start, iterator end)
-{
-       boost::shared_ptr<ControlList> nal = create (_parameter);
-
-       {
-               Glib::Mutex::Lock lm (_lock);
-
-               for (iterator x = start; x != end; ) {
-                       iterator tmp;
-
-                       tmp = x;
-                       ++tmp;
-
-                       nal->_events.push_back (new ControlEvent (**x));
-
-                       x = tmp;
-               }
-       }
-
-       return nal;
-}
 
 boost::shared_ptr<ControlList>
 ControlList::cut (double start, double end)
@@ -1260,9 +1369,10 @@ ControlList::copy (double start, double end)
 void
 ControlList::clear (double start, double end)
 {
-       (void) cut_copy_clear (start, end, 2);
+       cut_copy_clear (start, end, 2);
 }
 
+/** @param pos Position in model coordinates */
 bool
 ControlList::paste (ControlList& alist, double pos, float /*times*/)
 {
@@ -1302,7 +1412,6 @@ ControlList::paste (ControlList& alist, double pos, float /*times*/)
                        }
                }
 
-               reposition_for_rt_add (0);
                mark_dirty ();
        }
 
@@ -1310,8 +1419,10 @@ ControlList::paste (ControlList& alist, double pos, float /*times*/)
        return true;
 }
 
-/** Move automation around according to a list of region movements */
-void
+/** Move automation around according to a list of region movements.
+ *  @param return true if anything was changed, otherwise false (ie nothing needed changing)
+ */
+bool
 ControlList::move_ranges (const list< RangeMove<double> >& movements)
 {
        typedef list< RangeMove<double> > RangeMoveList;
@@ -1323,11 +1434,21 @@ ControlList::move_ranges (const list< RangeMove<double> >& movements)
                EventList old_events = _events;
 
                /* clear the source and destination ranges in the new list */
+               bool things_erased = false;
                for (RangeMoveList::const_iterator i = movements.begin (); i != movements.end (); ++i) {
 
-                       erase_range_internal (i->from, i->from + i->length, _events);
-                       erase_range_internal (i->to, i->to + i->length, _events);
+                       if (erase_range_internal (i->from, i->from + i->length, _events)) {
+                               things_erased = true;
+                       }
+
+                       if (erase_range_internal (i->to, i->to + i->length, _events)) {
+                               things_erased = true;
+                       }
+               }
 
+               /* if nothing was erased, there is nothing to do */
+               if (!things_erased) {
+                       return false;
                }
 
                /* copy the events into the new list */
@@ -1351,11 +1472,11 @@ ControlList::move_ranges (const list< RangeMove<double> >& movements)
                        _sort_pending = true;
                }
 
-               reposition_for_rt_add (0);
                mark_dirty ();
        }
 
        maybe_signal_changed ();
+       return true;
 }
 
 void