Fix MIDI selection/tool issues (issue #0002415 and other bugs).
[ardour.git] / libs / ardour / automation_event.cc
index 3df7dd94f7ae0aa44104c3f9e00599774dcbce29..af390953f424aa660b773efa64501a130e2f85f4 100644 (file)
     along with this program; if not, write to the Free Software
     Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 
-    $Id$
 */
 
 #include <set>
 #include <climits>
 #include <float.h>
 #include <cmath>
+#include <sstream>
 #include <algorithm>
 #include <sigc++/bind.h>
+#include <ardour/parameter.h>
 #include <ardour/automation_event.h>
+#include <ardour/curve.h>
+#include <pbd/stacktrace.h>
+#include <pbd/enumwriter.h>
 
 #include "i18n.h"
 
@@ -33,6 +37,13 @@ using namespace ARDOUR;
 using namespace sigc;
 using namespace PBD;
 
+sigc::signal<void,AutomationList *> AutomationList::AutomationListCreated;
+
+static bool sort_events_by_time (ControlEvent* a, ControlEvent* b)
+{
+       return a->when < b->when;
+}
+
 #if 0
 static void dumpit (const AutomationList& al, string prefix = "")
 {
@@ -44,115 +55,140 @@ static void dumpit (const AutomationList& al, string prefix = "")
 }
 #endif
 
-AutomationList::AutomationList (double defval, bool with_state)
-{
-       _frozen = false;
-       changed_when_thawed = false;
+/* 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)
+       , _curve(new Curve(*this))
+{      
+       _parameter = id;
+       _frozen = 0;
+       _changed_when_thawed = false;
        _state = Off;
        _style = Absolute;
+       _min_yval = min_val;
+       _max_yval = max_val;
        _touching = false;
-       no_state = with_state;
-       min_yval = FLT_MIN;
-       max_yval = FLT_MAX;
-       max_xval = 0; // means "no limit" 
-       default_value = defval;
-       _dirty = false;
-       rt_insertion_point = events.end();
-       lookup_cache.left = -1;
-       lookup_cache.range.first = events.end();
-
-       if (!no_state) {
-               save_state (_("initial"));
-       }
+       _max_xval = 0; // means "no limit" 
+       _default_value = default_val;
+       _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();
+       _sort_pending = false;
+
+       assert(_parameter.type() != NullAutomation);
+       AutomationListCreated(this);
 }
 
 AutomationList::AutomationList (const AutomationList& other)
+       : _parameter(other._parameter)
+       , _interpolation(Linear)
+       , _curve(new Curve(*this))
 {
-       _frozen = false;
-       changed_when_thawed = false;
+       _frozen = 0;
+       _changed_when_thawed = false;
        _style = other._style;
-       min_yval = other.min_yval;
-       max_yval = other.max_yval;
-       max_xval = other.max_xval;
-       default_value = other.default_value;
+       _min_yval = other._min_yval;
+       _max_yval = other._max_yval;
+       _max_xval = other._max_xval;
+       _default_value = other._default_value;
        _state = other._state;
        _touching = other._touching;
-       _dirty = false;
-       rt_insertion_point = events.end();
-       no_state = other.no_state;
-       lookup_cache.left = -1;
-       lookup_cache.range.first = events.end();
-
-       for (const_iterator i = other.events.begin(); i != other.events.end(); ++i) {
-               /* we have to use other point_factory() because
-                  its virtual and we're in a constructor.
-               */
-               events.push_back (other.point_factory (**i));
+       _rt_insertion_point = _events.end();
+       _lookup_cache.range.first = _events.end();
+       _search_cache.range.first = _events.end();
+       _sort_pending = false;
+
+       for (const_iterator i = other._events.begin(); i != other._events.end(); ++i) {
+               _events.push_back (new ControlEvent (**i));
        }
 
        mark_dirty ();
+       assert(_parameter.type() != NullAutomation);
+       AutomationListCreated(this);
 }
 
 AutomationList::AutomationList (const AutomationList& other, double start, double end)
+       : _parameter(other._parameter)
+       , _interpolation(Linear)
+       , _curve(new Curve(*this))
 {
-       _frozen = false;
-       changed_when_thawed = false;
+       _frozen = 0;
+       _changed_when_thawed = false;
        _style = other._style;
-       min_yval = other.min_yval;
-       max_yval = other.max_yval;
-       max_xval = other.max_xval;
-       default_value = other.default_value;
+       _min_yval = other._min_yval;
+       _max_yval = other._max_yval;
+       _max_xval = other._max_xval;
+       _default_value = other._default_value;
        _state = other._state;
        _touching = other._touching;
-       _dirty = false;
-       rt_insertion_point = events.end();
-       no_state = other.no_state;
-       lookup_cache.left = -1;
-       lookup_cache.range.first = events.end();
+       _rt_insertion_point = _events.end();
+       _lookup_cache.range.first = _events.end();
+       _search_cache.range.first = _events.end();
+       _sort_pending = false;
 
        /* now grab the relevant points, and shift them back if necessary */
 
        AutomationList* section = const_cast<AutomationList*>(&other)->copy (start, end);
 
        if (!section->empty()) {
-               for (AutomationList::iterator i = section->begin(); i != section->end(); ++i) {
-                       events.push_back (other.point_factory ((*i)->when, (*i)->value));
+               for (iterator i = section->begin(); i != section->end(); ++i) {
+                       _events.push_back (new ControlEvent ((*i)->when, (*i)->value));
                }
        }
 
        delete section;
 
        mark_dirty ();
+
+       assert(_parameter.type() != NullAutomation);
+       AutomationListCreated(this);
 }
 
-AutomationList::~AutomationList()
+/** \a id is used for legacy sessions where the type is not present
+ * in or below the <AutomationList> node.  It is used if \a id is non-null.
+ */
+AutomationList::AutomationList (const XMLNode& node, Parameter id)
+       : _interpolation(Linear)
+       , _curve(new Curve(*this))
 {
-       std::set<ControlEvent*> all_events;
-       AutomationList::State* asp;
-
-       for (AutomationEventList::iterator x = events.begin(); x != events.end(); ++x) {
-               all_events.insert (*x);
-       }
+       _frozen = 0;
+       _changed_when_thawed = false;
+       _touching = false;
+       _min_yval = FLT_MIN;
+       _max_yval = FLT_MAX;
+       _max_xval = 0; // means "no limit" 
+       _state = Off;
+       _style = Absolute;
+       _rt_insertion_point = _events.end();
+       _lookup_cache.range.first = _events.end();
+       _search_cache.range.first = _events.end();
+       _sort_pending = false;
+       
+       set_state (node);
 
-       for (StateMap::iterator i = states.begin(); i != states.end(); ++i) {
+       if (id)
+               _parameter = id;
 
-               if ((asp = dynamic_cast<AutomationList::State*> (*i)) != 0) {
-                       
-                       for (AutomationEventList::iterator x = asp->events.begin(); x != asp->events.end(); ++x) {
-                               all_events.insert (*x);
-                       }
-               }
-       }
+       assert(_parameter.type() != NullAutomation);
+       AutomationListCreated(this);
+}
 
-       for (std::set<ControlEvent*>::iterator i = all_events.begin(); i != all_events.end(); ++i) {
-               delete (*i);
+AutomationList::~AutomationList()
+{
+       GoingAway ();
+       
+       for (EventList::iterator x = _events.begin(); x != _events.end(); ++x) {
+               delete (*x);
        }
 }
 
 bool
 AutomationList::operator== (const AutomationList& other)
 {
-       return events == other.events;
+       return _events == other._events;
 }
 
 AutomationList&
@@ -160,16 +196,16 @@ AutomationList::operator= (const AutomationList& other)
 {
        if (this != &other) {
                
-               events.clear ();
+               _events.clear ();
                
-               for (const_iterator i = other.events.begin(); i != other.events.end(); ++i) {
-                       events.push_back (point_factory (**i));
+               for (const_iterator i = other._events.begin(); i != other._events.end(); ++i) {
+                       _events.push_back (new ControlEvent (**i));
                }
                
-               min_yval = other.min_yval;
-               max_yval = other.max_yval;
-               max_xval = other.max_xval;
-               default_value = other.default_value;
+               _min_yval = other._min_yval;
+               _max_yval = other._max_yval;
+               _max_xval = other._max_xval;
+               _default_value = other._default_value;
                
                mark_dirty ();
                maybe_signal_changed ();
@@ -184,9 +220,9 @@ AutomationList::maybe_signal_changed ()
        mark_dirty ();
 
        if (_frozen) {
-               changed_when_thawed = true;
+               _changed_when_thawed = true;
        } else {
-               StateChanged (Change (0));
+               StateChanged ();
        }
 }
 
@@ -226,11 +262,8 @@ void
 AutomationList::clear ()
 {
        {
-               Glib::Mutex::Lock lm (lock);
-               events.clear ();
-               if (!no_state) {
-                       save_state (_("cleared"));
-               }
+               Glib::Mutex::Lock lm (_lock);
+               _events.clear ();
                mark_dirty ();
        }
 
@@ -240,40 +273,37 @@ AutomationList::clear ()
 void
 AutomationList::x_scale (double factor)
 {
-       Glib::Mutex::Lock lm (lock);
+       Glib::Mutex::Lock lm (_lock);
        _x_scale (factor);
 }
 
 bool
 AutomationList::extend_to (double when)
 {
-       Glib::Mutex::Lock lm (lock);
-       if (events.empty() || events.back()->when == when) {
+       Glib::Mutex::Lock lm (_lock);
+       if (_events.empty() || _events.back()->when == when) {
                return false;
        }
-       double factor = when / events.back()->when;
+       double factor = when / _events.back()->when;
        _x_scale (factor);
        return true;
 }
 
 void AutomationList::_x_scale (double factor)
 {
-       for (AutomationList::iterator i = events.begin(); i != events.end(); ++i) {
+       for (iterator i = _events.begin(); i != _events.end(); ++i) {
                (*i)->when = floor ((*i)->when * factor);
        }
 
-       save_state ("x-scaled");
        mark_dirty ();
 }
 
 void
 AutomationList::reposition_for_rt_add (double when)
 {
-       rt_insertion_point = events.end();
+       _rt_insertion_point = _events.end();
 }
 
-#define last_rt_insertion_point rt_insertion_point
-
 void
 AutomationList::rt_add (double when, double value)
 {
@@ -286,43 +316,42 @@ AutomationList::rt_add (double when, double value)
        // cerr << "RT: alist @ " << this << " add " << value << " @ " << when << endl;
 
        {
-               Glib::Mutex::Lock lm (lock);
+               Glib::Mutex::Lock lm (_lock);
 
                iterator where;
-               TimeComparator cmp;
                ControlEvent cp (when, 0.0);
                bool done = false;
 
-               if ((last_rt_insertion_point != events.end()) && ((*last_rt_insertion_point)->when < when) ) {
+               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 = last_rt_insertion_point;
+                       iterator after = _rt_insertion_point;
 
-                       if (++after != events.end()) {
+                       if (++after != _events.end()) {
                                iterator far = after;
 
-                               while (far != events.end()) {
+                               while (far != _events.end()) {
                                        if ((*far)->when > when) {
                                                break;
                                        }
                                        ++far;
                                }
 
-                                if(_new_touch) {
-                                        where = far;
-                                        last_rt_insertion_point = where;
-                                                                                             
-                                        if((*where)->when == when) {
-                                                (*where)->value = value;
-                                                done = true;
-                                        }
-                                } else {
-                                        where = events.erase (after, far);
-                                }
+                               if (_new_touch) {
+                                       where = far;
+                                       _rt_insertion_point = where;
+
+                                       if ((*where)->when == when) {
+                                               (*where)->value = value;
+                                               done = true;
+                                       }
+                               } else {
+                                       where = _events.erase (after, far);
+                               }
 
                        } else {
 
@@ -330,29 +359,29 @@ AutomationList::rt_add (double when, double value)
 
                        }
                        
-                       iterator previous = last_rt_insertion_point;
-                        --previous;
+                       iterator previous = _rt_insertion_point;
+                       --previous;
                        
-                       if (last_rt_insertion_point != events.begin() && (*last_rt_insertion_point)->value == value && (*previous)->value == value) {
-                               (*last_rt_insertion_point)->when = when;
+                       if (_rt_insertion_point != _events.begin() && (*_rt_insertion_point)->value == value && (*previous)->value == value) {
+                               (*_rt_insertion_point)->when = when;
                                done = true;
                                
                        }
                        
                } 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 != _events.end()) {
                                if ((*where)->when == when) {
                                        (*where)->value = value;
                                        done = true;
                                }
                        }
                }
-               
+
                if (!done) {
-                       last_rt_insertion_point = events.insert (where, point_factory (when, value));
+                       _rt_insertion_point = _events.insert (where, new ControlEvent (when, value));
                }
                
                _new_touch = false;
@@ -362,21 +391,26 @@ AutomationList::rt_add (double when, double value)
        maybe_signal_changed ();
 }
 
-#undef last_rt_insertion_point
+void
+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
-AutomationList::add (double when, double value, bool for_loading)
+AutomationList::add (double when, double value)
 {
-       /* this is for graphical editing and loading data from storage */
+       /* this is for graphical editing */
 
        {
-               Glib::Mutex::Lock lm (lock);
-               TimeComparator cmp;
+               Glib::Mutex::Lock lm (_lock);
                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 */
 
@@ -392,49 +426,37 @@ AutomationList::add (double when, double value, bool for_loading)
                }
 
                if (insert) {
-
-                       events.insert (insertion_point, point_factory (when, value));
+                       
+                       _events.insert (insertion_point, new ControlEvent (when, value));
                        reposition_for_rt_add (0);
 
                } 
 
                mark_dirty ();
-
-               if (!no_state && !for_loading) {
-                       save_state (_("added event"));
-               }
        }
 
-       if (!for_loading) {
-               maybe_signal_changed ();
-       }
+       maybe_signal_changed ();
 }
 
 void
-AutomationList::erase (AutomationList::iterator i)
+AutomationList::erase (iterator i)
 {
        {
-               Glib::Mutex::Lock lm (lock);
-               events.erase (i);
+               Glib::Mutex::Lock lm (_lock);
+               _events.erase (i);
                reposition_for_rt_add (0);
-               if (!no_state) {
-                       save_state (_("removed event"));
-               }
                mark_dirty ();
        }
        maybe_signal_changed ();
 }
 
 void
-AutomationList::erase (AutomationList::iterator start, AutomationList::iterator end)
+AutomationList::erase (iterator start, iterator end)
 {
        {
-               Glib::Mutex::Lock lm (lock);
-               events.erase (start, end);
+               Glib::Mutex::Lock lm (_lock);
+               _events.erase (start, end);
                reposition_for_rt_add (0);
-               if (!no_state) {
-                       save_state (_("removed multiple events"));
-               }
                mark_dirty ();
        }
        maybe_signal_changed ();
@@ -446,27 +468,22 @@ AutomationList::reset_range (double start, double endt)
        bool reset = false;
 
        {
-        Glib::Mutex::Lock lm (lock);
-               TimeComparator cmp;
+        Glib::Mutex::Lock lm (_lock);
                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;
+                               (*i)->value = _default_value;
                        }
                        
                        reset = true;
 
-                       if (!no_state) {
-                               save_state (_("removed range"));
-                       }
-
                        mark_dirty ();
                }
        }
@@ -482,21 +499,17 @@ AutomationList::erase_range (double start, double endt)
        bool erased = false;
 
        {
-               Glib::Mutex::Lock lm (lock);
-               TimeComparator cmp;
+               Glib::Mutex::Lock lm (_lock);
                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);
-                       events.erase (s, e);
+                       e = upper_bound (_events.begin(), _events.end(), &cp, time_comparator);
+                       _events.erase (s, e);
                        reposition_for_rt_add (0);
                        erased = true;
-                       if (!no_state) {
-                               save_state (_("removed range"));
-                       }
                        mark_dirty ();
                }
                
@@ -516,16 +529,21 @@ AutomationList::move_range (iterator start, iterator end, double xdelta, double
        */
 
        {
-               Glib::Mutex::Lock lm (lock);
+               Glib::Mutex::Lock lm (_lock);
 
                while (start != end) {
                        (*start)->when += xdelta;
                        (*start)->value += ydelta;
+                       if (isnan ((*start)->value)) {
+                               abort ();
+                       }
                        ++start;
                }
 
-               if (!no_state) {
-                       save_state (_("event range adjusted"));
+               if (!_frozen) {
+                       _events.sort (sort_events_by_time);
+               } else {
+                       _sort_pending = true;
                }
 
                mark_dirty ();
@@ -534,6 +552,25 @@ AutomationList::move_range (iterator start, iterator end, double xdelta, double
        maybe_signal_changed ();
 }
 
+void
+AutomationList::slide (iterator before, double distance)
+{
+       {
+               Glib::Mutex::Lock lm (_lock);
+
+               if (before == _events.end()) {
+                       return;
+               }
+               
+               while (before != _events.end()) {
+                       (*before)->when += distance;
+                       ++before;
+               }
+       }
+
+       maybe_signal_changed ();
+}
+
 void
 AutomationList::modify (iterator iter, double when, double val)
 {
@@ -543,36 +580,43 @@ AutomationList::modify (iterator iter, double when, double val)
        */
 
        {
-               Glib::Mutex::Lock lm (lock);
+               Glib::Mutex::Lock lm (_lock);
+
                (*iter)->when = when;
                (*iter)->value = val;
-               if (!no_state) {
-                       save_state (_("event adjusted"));
+
+               if (isnan (val)) {
+                       abort ();
+               }
+
+               if (!_frozen) {
+                       _events.sort (sort_events_by_time);
+               } else {
+                       _sort_pending = true;
                }
 
                mark_dirty ();
        }
-       
+
        maybe_signal_changed ();
 }
 
 std::pair<AutomationList::iterator,AutomationList::iterator>
 AutomationList::control_points_adjacent (double xval)
 {
-       Glib::Mutex::Lock lm (lock);
+       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();
+       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 (ret.first == _events.end()) {
                        if ((*i)->when >= xval) {
-                               if (i != events.begin()) {
+                               if (i != _events.begin()) {
                                        ret.first = i;
                                        --ret.first;
                                } else {
@@ -593,97 +637,78 @@ AutomationList::control_points_adjacent (double xval)
 void
 AutomationList::freeze ()
 {
-       _frozen = true;
+       _frozen++;
 }
 
 void
 AutomationList::thaw ()
 {
-       _frozen = false;
-       if (changed_when_thawed) {
-                StateChanged(Change(0)); /* EMIT SIGNAL */
+       if (_frozen == 0) {
+               PBD::stacktrace (cerr);
+               fatal << string_compose (_("programming error: %1"), X_("AutomationList::thaw() called while not frozen")) << endmsg;
+               /*NOTREACHED*/
        }
-}
-
-StateManager::State*
-AutomationList::state_factory (std::string why) const
-{
-       State* state = new State (why);
 
-       for (AutomationEventList::const_iterator x = events.begin(); x != events.end(); ++x) {
-               state->events.push_back (point_factory (**x));
+       if (--_frozen > 0) {
+               return;
        }
 
-       return state;
-}
-
-Change
-AutomationList::restore_state (StateManager::State& state) 
-{
        {
-               Glib::Mutex::Lock lm (lock);
-               State* lstate = dynamic_cast<State*> (&state);
+               Glib::Mutex::Lock lm (_lock);
 
-               events.clear ();
-               for (AutomationEventList::const_iterator x = lstate->events.begin(); x != lstate->events.end(); ++x) {
-                       events.push_back (point_factory (**x));
+               if (_sort_pending) {
+                       _events.sort (sort_events_by_time);
+                       _sort_pending = false;
                }
        }
 
-       return Change (0);
-}
-
-UndoAction
-AutomationList::get_memento () const
-{
-  return sigc::bind (mem_fun (*(const_cast<AutomationList*> (this)), &StateManager::use_state), _current_state_id);
+       if (_changed_when_thawed) {
+               StateChanged(); /* EMIT SIGNAL */
+       }
 }
 
 void
 AutomationList::set_max_xval (double x)
 {
-       max_xval = x;
+       _max_xval = x;
 }
 
 void 
 AutomationList::mark_dirty ()
 {
-       lookup_cache.left = -1;
-       _dirty = true;
+       _lookup_cache.left = -1;
+       _search_cache.left = -1;
+       Dirty (); /* EMIT SIGNAL */
 }
 
 void
 AutomationList::truncate_end (double last_coordinate)
 {
        {
-               Glib::Mutex::Lock lm (lock);
+               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()) {
-                       fatal << _("programming error:")
-                             << "AutomationList::truncate_end() called on an empty list"
-                             << endmsg;
-                       /*NOTREACHED*/
+               if (_events.empty()) {
                        return;
                }
 
-               if (last_coordinate == events.back()->when) {
+               if (last_coordinate == _events.back()->when) {
                        return;
                }
 
-               if (last_coordinate > events.back()->when) {
+               if (last_coordinate > _events.back()->when) {
                        
                        /* extending end:
                        */
 
-                       iterator foo = events.begin();
+                       iterator foo = _events.begin();
                        bool lessthantwo;
 
-                       if (foo == events.end()) {
+                       if (foo == _events.end()) {
                                lessthantwo = true;
-                       } else if (++foo == events.end()) {
+                       } else if (++foo == _events.end()) {
                                lessthantwo = true;
                        } else {
                                lessthantwo = false;
@@ -691,7 +716,7 @@ AutomationList::truncate_end (double last_coordinate)
 
                        if (lessthantwo) {
                                /* less than 2 points: add a new point */
-                               events.push_back (point_factory (last_coordinate, events.back()->value));
+                               _events.push_back (new ControlEvent (last_coordinate, _events.back()->value));
                        } else {
 
                                /* more than 2 points: check to see if the last 2 values
@@ -699,14 +724,14 @@ AutomationList::truncate_end (double last_coordinate)
                                   last point. otherwise, add a new point.
                                */
 
-                               iterator penultimate = events.end();
+                               iterator penultimate = _events.end();
                                --penultimate; /* points at last point */
                                --penultimate; /* points at the penultimate point */
                                
-                               if (events.back()->value == (*penultimate)->value) {
-                                       events.back()->when = last_coordinate;
+                               if (_events.back()->value == (*penultimate)->value) {
+                                       _events.back()->when = last_coordinate;
                                } else {
-                                       events.push_back (point_factory (last_coordinate, events.back()->value));
+                                       _events.push_back (new ControlEvent (last_coordinate, _events.back()->value));
                                }
                        }
 
@@ -715,10 +740,10 @@ AutomationList::truncate_end (double last_coordinate)
                        /* shortening end */
 
                        last_val = unlocked_eval (last_coordinate);
-                       last_val = max ((double) min_yval, last_val);
-                       last_val = min ((double) max_yval, last_val);
+                       last_val = max ((double) _min_yval, last_val);
+                       last_val = min ((double) _max_yval, last_val);
                        
-                       i = events.rbegin();
+                       i = _events.rbegin();
                        
                        /* make i point to the last control point */
                        
@@ -728,10 +753,10 @@ AutomationList::truncate_end (double last_coordinate)
                           beyond the new last coordinate.
                        */
 
-                       uint32_t sz = events.size();
+                       uint32_t sz = _events.size();
                        
-                       while (i != events.rend() && sz > 2) {
-                               list<ControlEvent*>::reverse_iterator tmp;
+                       while (i != _events.rend() && sz > 2) {
+                               AutomationList::reverse_iterator tmp;
                                
                                tmp = i;
                                ++tmp;
@@ -740,14 +765,14 @@ AutomationList::truncate_end (double last_coordinate)
                                        break;
                                }
                                
-                               events.erase (i.base());
+                               _events.erase (i.base());
                                --sz;
 
                                i = tmp;
                        }
                        
-                       events.back()->when = last_coordinate;
-                       events.back()->value = last_val;
+                       _events.back()->when = last_coordinate;
+                       _events.back()->value = last_val;
                }
 
                reposition_for_rt_add (0);
@@ -761,12 +786,12 @@ void
 AutomationList::truncate_start (double overall_length)
 {
        {
-               Glib::Mutex::Lock lm (lock);
-               AutomationList::iterator i;
+               Glib::Mutex::Lock lm (_lock);
+               iterator i;
                double first_legal_value;
                double first_legal_coordinate;
 
-               if (events.empty()) {
+               if (_events.empty()) {
                        fatal << _("programming error:")
                              << "AutomationList::truncate_start() called on an empty list"
                              << endmsg;
@@ -774,26 +799,26 @@ AutomationList::truncate_start (double overall_length)
                        return;
                }
                
-               if (overall_length == events.back()->when) {
+               if (overall_length == _events.back()->when) {
                        /* no change in overall length */
                        return;
                }
                
-               if (overall_length > events.back()->when) {
+               if (overall_length > _events.back()->when) {
                        
                        /* growing at front: duplicate first point. shift all others */
 
-                       double shift = overall_length - events.back()->when;
+                       double shift = overall_length - _events.back()->when;
                        uint32_t np;
 
-                       for (np = 0, i = events.begin(); i != events.end(); ++i, ++np) {
+                       for (np = 0, i = _events.begin(); i != _events.end(); ++i, ++np) {
                                (*i)->when += shift;
                        }
 
                        if (np < 2) {
 
                                /* less than 2 points: add a new point */
-                               events.push_front (point_factory (0, events.front()->value));
+                               _events.push_front (new ControlEvent (0, _events.front()->value));
 
                        } else {
 
@@ -802,15 +827,15 @@ AutomationList::truncate_start (double overall_length)
                                   first point. otherwise, add a new point.
                                */
 
-                               iterator second = events.begin();
+                               iterator second = _events.begin();
                                ++second; /* points at the second point */
                                
-                               if (events.front()->value == (*second)->value) {
+                               if (_events.front()->value == (*second)->value) {
                                        /* first segment is flat, just move start point back to zero */
-                                       events.front()->when = 0;
+                                       _events.front()->when = 0;
                                } else {
                                        /* leave non-flat segment in place, add a new leading point. */
-                                       events.push_front (point_factory (0, events.front()->value));
+                                       _events.push_front (new ControlEvent (0, _events.front()->value));
                                }
                        }
 
@@ -818,17 +843,17 @@ AutomationList::truncate_start (double overall_length)
 
                        /* shrinking at front */
                        
-                       first_legal_coordinate = events.back()->when - overall_length;
+                       first_legal_coordinate = _events.back()->when - overall_length;
                        first_legal_value = unlocked_eval (first_legal_coordinate);
-                       first_legal_value = max (min_yval, first_legal_value);
-                       first_legal_value = min (max_yval, first_legal_value);
+                       first_legal_value = max (_min_yval, first_legal_value);
+                       first_legal_value = min (_max_yval, first_legal_value);
 
                        /* remove all events earlier than the new "front" */
 
-                       i = events.begin();
+                       i = _events.begin();
                        
-                       while (i != events.end() && !events.empty()) {
-                               list<ControlEvent*>::iterator tmp;
+                       while (i != _events.end() && !_events.empty()) {
+                               AutomationList::iterator tmp;
                                
                                tmp = i;
                                ++tmp;
@@ -837,7 +862,7 @@ AutomationList::truncate_start (double overall_length)
                                        break;
                                }
                                
-                               events.erase (i);
+                               _events.erase (i);
                                
                                i = tmp;
                        }
@@ -847,13 +872,13 @@ AutomationList::truncate_start (double overall_length)
                           relative position
                        */
                        
-                       for (i = events.begin(); i != events.end(); ++i) {
+                       for (i = _events.begin(); i != _events.end(); ++i) {
                                (*i)->when -= first_legal_coordinate;
                        }
 
                        /* add a new point for the interpolated new value */
                        
-                       events.push_front (point_factory (0, first_legal_value));
+                       _events.push_front (new ControlEvent (0, first_legal_value));
                }           
 
                reposition_for_rt_add (0);
@@ -865,49 +890,46 @@ AutomationList::truncate_start (double overall_length)
 }
 
 double
-AutomationList::unlocked_eval (double x)
+AutomationList::unlocked_eval (double x) const
 {
-       return shared_eval (x);
-}
-
-double
-AutomationList::shared_eval (double x) 
-{
-       pair<AutomationEventList::iterator,AutomationEventList::iterator> range;
+       pair<EventList::iterator,EventList::iterator> range;
        int32_t npoints;
        double lpos, upos;
        double lval, uval;
        double fraction;
 
-       npoints = events.size();
+       npoints = _events.size();
 
        switch (npoints) {
        case 0:
-               return default_value;
+               return _default_value;
 
        case 1:
-               if (x >= events.front()->when) {
-                       return events.front()->value;
+               if (x >= _events.front()->when) {
+                       return _events.front()->value;
                } else {
-                       // return default_value;
-                       return events.front()->value;
+                       // return _default_value;
+                       return _events.front()->value;
                } 
                
        case 2:
-               if (x >= events.back()->when) {
-                       return events.back()->value;
-               } else if (x == events.front()->when) {
-                       return events.front()->value;
-               } else if (x < events.front()->when) {
-                       // return default_value;
-                       return events.front()->value;
+               if (x >= _events.back()->when) {
+                       return _events.back()->value;
+               } else if (x == _events.front()->when) {
+                       return _events.front()->value;
+               } else if (x < _events.front()->when) {
+                       // return _default_value;
+                       return _events.front()->value;
                }
 
-               lpos = events.front()->when;
-               lval = events.front()->value;
-               upos = events.back()->when;
-               uval = events.back()->value;
+               lpos = _events.front()->when;
+               lval = _events.front()->value;
+               upos = _events.back()->when;
+               uval = _events.back()->value;
                
+               if (_interpolation == Discrete)
+                       return lval;
+
                /* linear interpolation betweeen the two points
                */
 
@@ -916,64 +938,78 @@ AutomationList::shared_eval (double x)
 
        default:
 
-               if (x >= events.back()->when) {
-                       return events.back()->value;
-               } else if (x == events.front()->when) {
-                       return events.front()->value;
-               } else if (x < events.front()->when) {
-                       // return default_value;
-                       return events.front()->value;
+               if (x >= _events.back()->when) {
+                       return _events.back()->value;
+               } else if (x == _events.front()->when) {
+                       return _events.front()->value;
+               } else if (x < _events.front()->when) {
+                       // return _default_value;
+                       return _events.front()->value;
                }
 
                return multipoint_eval (x);
                break;
        }
+
+       /*NOTREACHED*/ /* stupid gcc */
+       return 0.0;
 }
 
 double
-AutomationList::multipoint_eval (double x) 
+AutomationList::multipoint_eval (double x) const
 {
-       pair<AutomationList::iterator,AutomationList::iterator> range;
        double upos, lpos;
        double uval, lval;
        double fraction;
+       
+       /* "Stepped" lookup (no interpolation) */
+       /* FIXME: no cache.  significant? */
+       if (_interpolation == Discrete) {
+               const ControlEvent cp (x, 0);
+               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());
+
+               if (i == _events.begin() || (*i)->when == x)
+                       return (*i)->value;
+               else
+                       return (*(--i))->value;
+       }
 
-       /* only do the range lookup if x is in a different range than last time
-          this was called (or if the lookup cache has been marked "dirty" (left<0)
-       */
-
-       if ((lookup_cache.left < 0) ||
-           ((lookup_cache.left > x) || 
-            (lookup_cache.range.first == events.end()) || 
-            ((*lookup_cache.range.second)->when < x))) {
+       /* Only do the range lookup if x is in a different range than last time
+        * this was called (or if the lookup cache has been marked "dirty" (left<0) */
+       if ((_lookup_cache.left < 0) ||
+           ((_lookup_cache.left > x) || 
+            (_lookup_cache.range.first == _events.end()) || 
+            ((*_lookup_cache.range.second)->when < x))) {
 
-               ControlEvent cp (x, 0);
-               TimeComparator cmp;
+               const ControlEvent cp (x, 0);
                
-               lookup_cache.range = equal_range (events.begin(), events.end(), &cp, cmp);
+               _lookup_cache.range = equal_range (_events.begin(), _events.end(), &cp, time_comparator);
        }
        
-       range = lookup_cache.range;
+       pair<const_iterator,const_iterator> range = _lookup_cache.range;
 
        if (range.first == range.second) {
 
                /* x does not exist within the list as a control point */
 
-               lookup_cache.left = x;
+               _lookup_cache.left = x;
 
-               if (range.first != events.begin()) {
+               if (range.first != _events.begin()) {
                        --range.first;
                        lpos = (*range.first)->when;
                        lval = (*range.first)->value;
                }  else {
                        /* we're before the first point */
-                       // return default_value;
-                       return events.front()->value;
+                       // return _default_value;
+                       return _events.front()->value;
                }
                
-               if (range.second == events.end()) {
+               if (range.second == _events.end()) {
                        /* we're after the last point */
-                       return events.back()->value;
+                       return _events.back()->value;
                }
 
                upos = (*range.second)->when;
@@ -989,17 +1025,235 @@ AutomationList::multipoint_eval (double x)
        } 
 
        /* x is a control point in the data */
-       lookup_cache.left = -1;
+       _lookup_cache.left = -1;
        return (*range.first)->value;
 }
 
+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, 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;
+       }
+
+       return rt_safe_earliest_event_unlocked(start, end, x, y, inclusive);
+} 
+
+
+/** 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);
+} 
+
+
+/** 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 (past_start >= start && first->when < end) {
+
+                       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;
+
+                       assert(x >= start);
+                       assert(x < end);
+                       return true;
+
+               } else {
+                       return false;
+               }
+       
+       /* No points in range */
+       } else {
+               return false;
+       }
+}
+
+/** 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)
 {
-       AutomationList* nal = new AutomationList (default_value);
+       AutomationList* nal = new AutomationList (_parameter, _min_yval, _max_yval, _default_value);
 
        {
-               Glib::Mutex::Lock lm (lock);
+               Glib::Mutex::Lock lm (_lock);
 
                for (iterator x = start; x != end; ) {
                        iterator tmp;
@@ -1007,8 +1261,8 @@ AutomationList::cut (iterator start, iterator end)
                        tmp = x;
                        ++tmp;
                        
-                       nal->events.push_back (point_factory (**x));
-                       events.erase (x);
+                       nal->_events.push_back (new ControlEvent (**x));
+                       _events.erase (x);
                        
                        reposition_for_rt_add (0);
 
@@ -1026,24 +1280,23 @@ AutomationList::cut (iterator start, iterator end)
 AutomationList*
 AutomationList::cut_copy_clear (double start, double end, int op)
 {
-       AutomationList* nal = new AutomationList (default_value);
+       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);
+               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 (point_factory (0, unlocked_eval (start)));
+                       nal->_events.push_back (new ControlEvent (0, unlocked_eval (start)));
                }
 
                for (iterator x = s; x != e; ) {
@@ -1059,25 +1312,22 @@ AutomationList::cut_copy_clear (double start, double end, int op)
                        */
                        
                        if (op != 2) {
-                               nal->events.push_back (point_factory ((*x)->when - start, (*x)->value));
+                               nal->_events.push_back (new ControlEvent ((*x)->when - start, (*x)->value));
                        }
 
                        if (op != 1) {
-                               events.erase (x);
+                               _events.erase (x);
                        }
                        
                        x = tmp;
                }
 
-               if (op != 2 && nal->events.back()->when != end - start) {
-                       nal->events.push_back (point_factory (end - start, unlocked_eval (end)));
+               if (op != 2 && nal->_events.back()->when != end - start) {
+                       nal->_events.push_back (new ControlEvent (end - start, unlocked_eval (end)));
                }
 
                if (changed) {
                        reposition_for_rt_add (0);
-                       if (!no_state) {
-                               save_state (_("cut/copy/clear"));
-                       }
                }
 
                mark_dirty ();
@@ -1092,10 +1342,10 @@ AutomationList::cut_copy_clear (double start, double end, int op)
 AutomationList*
 AutomationList::copy (iterator start, iterator end)
 {
-       AutomationList* nal = new AutomationList (default_value);
+       AutomationList* nal = new AutomationList (_parameter, _min_yval, _max_yval, _default_value);
 
        {
-               Glib::Mutex::Lock lm (lock);
+               Glib::Mutex::Lock lm (_lock);
                
                for (iterator x = start; x != end; ) {
                        iterator tmp;
@@ -1103,14 +1353,10 @@ AutomationList::copy (iterator start, iterator end)
                        tmp = x;
                        ++tmp;
                        
-                       nal->events.push_back (point_factory (**x));
+                       nal->_events.push_back (new ControlEvent (**x));
                        
                        x = tmp;
                }
-
-               if (!no_state) {
-                       save_state (_("copy"));
-               }
        }
 
        return nal;
@@ -1137,22 +1383,21 @@ AutomationList::clear (double start, double end)
 bool
 AutomationList::paste (AutomationList& alist, double pos, float times)
 {
-       if (alist.events.empty()) {
+       if (alist._events.empty()) {
                return false;
        }
 
        {
-               Glib::Mutex::Lock lm (lock);
+               Glib::Mutex::Lock lm (_lock);
                iterator where;
                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, point_factory( (*i)->when+pos,( *i)->value));
+                       _events.insert (where, new ControlEvent( (*i)->when+pos,( *i)->value));
                        end = (*i)->when + pos;
                }
        
@@ -1161,12 +1406,12 @@ AutomationList::paste (AutomationList& alist, double pos, float times)
                   the correct amount.
                */
 
-               while (where != events.end()) {
+               while (where != _events.end()) {
                        iterator tmp;
                        if ((*where)->when <= end) {
                                tmp = where;
                                ++tmp;
-                               events.erase(where);
+                               _events.erase(where);
                                where = tmp;
 
                        } else {
@@ -1175,11 +1420,6 @@ AutomationList::paste (AutomationList& alist, double pos, float times)
                }
 
                reposition_for_rt_add (0);
-
-               if (!no_state) {
-                       save_state (_("paste"));
-               }
-
                mark_dirty ();
        }
 
@@ -1187,62 +1427,240 @@ AutomationList::paste (AutomationList& alist, double pos, float times)
        return true;
 }
 
-ControlEvent*
-AutomationList::point_factory (double when, double val) const
+XMLNode&
+AutomationList::get_state ()
 {
-       return new ControlEvent (when, val);
+       return state (true);
 }
 
-ControlEvent*
-AutomationList::point_factory (const ControlEvent& other) const
+XMLNode&
+AutomationList::state (bool full)
 {
-       return new ControlEvent (other);
+       XMLNode* root = new XMLNode (X_("AutomationList"));
+       char buf[64];
+       LocaleGuard lg (X_("POSIX"));
+
+       root->add_property ("automation-id", _parameter.to_string());
+
+       root->add_property ("id", _id.to_s());
+
+       snprintf (buf, sizeof (buf), "%.12g", _default_value);
+       root->add_property ("default", buf);
+       snprintf (buf, sizeof (buf), "%.12g", _min_yval);
+       root->add_property ("min_yval", buf);
+       snprintf (buf, sizeof (buf), "%.12g", _max_yval);
+       root->add_property ("max_yval", buf);
+       snprintf (buf, sizeof (buf), "%.12g", _max_xval);
+       root->add_property ("max_xval", buf);
+       
+       root->add_property ("interpolation-style", enum_2_string (_interpolation));
+
+       if (full) {
+               root->add_property ("state", auto_state_to_string (_state));
+       } else {
+               /* never save anything but Off for automation state to a template */
+               root->add_property ("state", auto_state_to_string (Off));
+       }
+
+       root->add_property ("style", auto_style_to_string (_style));
+
+       if (!_events.empty()) {
+               root->add_child_nocopy (serialize_events());
+       }
+
+       return *root;
 }
 
-void
-AutomationList::store_state (XMLNode& node) const
+XMLNode&
+AutomationList::serialize_events ()
 {
-       LocaleGuard lg (X_("POSIX"));
+       XMLNode* node = new XMLNode (X_("events"));
+       stringstream str;
+
+       for (iterator xx = _events.begin(); xx != _events.end(); ++xx) {
+               str << (double) (*xx)->when;
+               str << ' ';
+               str <<(double) (*xx)->value;
+               str << '\n';
+       }
 
-       for (const_iterator i = const_begin(); i != const_end(); ++i) {
-               char buf[64];
-               
-               XMLNode *pointnode = new XMLNode ("point");
-               
-               snprintf (buf, sizeof (buf), "%" PRIu32, (jack_nframes_t) floor ((*i)->when));
-               pointnode->add_property ("x", buf);
-               snprintf (buf, sizeof (buf), "%f", (*i)->value);
-               pointnode->add_property ("y", buf);
+       /* XML is a bit wierd */
 
-               node.add_child_nocopy (*pointnode);
-       }
+       XMLNode* content_node = new XMLNode (X_("foo")); /* it gets renamed by libxml when we set content */
+       content_node->set_content (str.str());
+
+       node->add_child_nocopy (*content_node);
+
+       return *node;
 }
 
-void
-AutomationList::load_state (const XMLNode& node)
+int
+AutomationList::deserialize_events (const XMLNode& node)
 {
-       const XMLNodeList& elist = node.children();
-       XMLNodeConstIterator i;
-       XMLProperty* prop;
-       jack_nframes_t x;
-       double y;
+       if (node.children().empty()) {
+               return -1;
+       }
 
+       XMLNode* content_node = node.children().front();
+
+       if (content_node->content().empty()) {
+               return -1;
+       }
+
+       freeze ();
        clear ();
        
-       for (i = elist.begin(); i != elist.end(); ++i) {
-               
-               if ((prop = (*i)->property ("x")) == 0) {
-                       error << _("automation list: no x-coordinate stored for control point (point ignored)") << endmsg;
-                       continue;
+       stringstream str (content_node->content());
+       
+       double x;
+       double y;
+       bool ok = true;
+       
+       while (str) {
+               str >> x;
+               if (!str) {
+                       break;
+               }
+               str >> y;
+               if (!str) {
+                       ok = false;
+                       break;
                }
-               x = atoi (prop->value().c_str());
+               fast_simple_add (x, y);
+       }
+       
+       if (!ok) {
+               clear ();
+               error << _("automation list: cannot load coordinates from XML, all points ignored") << endmsg;
+       } else {
+               mark_dirty ();
+               reposition_for_rt_add (0);
+               maybe_signal_changed ();
+       }
+
+       thaw ();
+
+       return 0;
+}
+
+int
+AutomationList::set_state (const XMLNode& node)
+{
+       XMLNodeList nlist = node.children();
+       XMLNode* nsos;
+       XMLNodeIterator niter;
+       const XMLProperty* prop;
+
+       if (node.name() == X_("events")) {
+               /* partial state setting*/
+               return deserialize_events (node);
+       }
+       
+       if (node.name() == X_("Envelope") || node.name() == X_("FadeOut") || node.name() == X_("FadeIn")) {
+
+               if ((nsos = node.child (X_("AutomationList")))) {
+                       /* new school in old school clothing */
+                       return set_state (*nsos);
+               }
+
+               /* old school */
+
+               const XMLNodeList& elist = node.children();
+               XMLNodeConstIterator i;
+               XMLProperty* prop;
+               nframes_t x;
+               double y;
+               
+               freeze ();
+               clear ();
                
-               if ((prop = (*i)->property ("y")) == 0) {
-                       error << _("automation list: no y-coordinate stored for control point (point ignored)") << endmsg;
-                       continue;
+               for (i = elist.begin(); i != elist.end(); ++i) {
+                       
+                       if ((prop = (*i)->property ("x")) == 0) {
+                               error << _("automation list: no x-coordinate stored for control point (point ignored)") << endmsg;
+                               continue;
+                       }
+                       x = atoi (prop->value().c_str());
+                       
+                       if ((prop = (*i)->property ("y")) == 0) {
+                               error << _("automation list: no y-coordinate stored for control point (point ignored)") << endmsg;
+                               continue;
+                       }
+                       y = atof (prop->value().c_str());
+                       
+                       fast_simple_add (x, y);
                }
-               y = atof (prop->value().c_str());
                
-               add (x, y);
+               thaw ();
+
+               return 0;
        }
+
+       if (node.name() != X_("AutomationList") ) {
+               error << string_compose (_("AutomationList: passed XML node called %1, not \"AutomationList\" - ignored"), node.name()) << endmsg;
+               return -1;
+       }
+
+       if ((prop = node.property ("id")) != 0) {
+               _id = prop->value ();
+               /* update session AL list */
+               AutomationListCreated(this);
+       }
+       
+       if ((prop = node.property (X_("automation-id"))) != 0){ 
+               _parameter = Parameter(prop->value());
+       } else {
+               warning << "Legacy session: automation list has no automation-id property.";
+       }
+       
+       if ((prop = node.property (X_("interpolation-style"))) != 0) {
+               _interpolation = (InterpolationStyle)string_2_enum(prop->value(), _interpolation);
+       } else {
+               _interpolation = Linear;
+       }
+       
+       if ((prop = node.property (X_("default"))) != 0){ 
+               _default_value = atof (prop->value().c_str());
+       } else {
+               _default_value = 0.0;
+       }
+
+       if ((prop = node.property (X_("style"))) != 0) {
+               _style = string_to_auto_style (prop->value());
+       } else {
+               _style = Absolute;
+       }
+
+       if ((prop = node.property (X_("state"))) != 0) {
+               _state = string_to_auto_state (prop->value());
+       } else {
+               _state = Off;
+       }
+
+       if ((prop = node.property (X_("min_yval"))) != 0) {
+               _min_yval = atof (prop->value ().c_str());
+       } else {
+               _min_yval = FLT_MIN;
+       }
+
+       if ((prop = node.property (X_("max_yval"))) != 0) {
+               _max_yval = atof (prop->value ().c_str());
+       } else {
+               _max_yval = FLT_MAX;
+       }
+
+       if ((prop = node.property (X_("max_xval"))) != 0) {
+               _max_xval = atof (prop->value ().c_str());
+       } else {
+               _max_xval = 0; // means "no limit ;
+       }
+
+       for (niter = nlist.begin(); niter != nlist.end(); ++niter) {
+               if ((*niter)->name() == X_("events")) {
+                       deserialize_events (*(*niter));
+               }
+       }
+
+       return 0;
 }
+