Fix MIDI selection/tool issues (issue #0002415 and other bugs).
[ardour.git] / libs / ardour / automation_event.cc
index 82d3e457cbf999c73fddbf5bde6d22e0778062ac..af390953f424aa660b773efa64501a130e2f85f4 100644 (file)
 #include <sstream>
 #include <algorithm>
 #include <sigc++/bind.h>
-#include <ardour/param_id.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"
 
@@ -54,11 +55,13 @@ static void dumpit (const AutomationList& al, string prefix = "")
 }
 #endif
 
-AutomationList::AutomationList (ParamID id, double min_val, double max_val, double default_val)
-       : _param_id(id)
+/* 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))
-{
-       _param_id = id;
+{      
+       _parameter = id;
        _frozen = 0;
        _changed_when_thawed = false;
        _state = Off;
@@ -71,14 +74,17 @@ AutomationList::AutomationList (ParamID id, double min_val, double max_val, doub
        _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)
-       : _param_id(other._param_id)
+       : _parameter(other._parameter)
+       , _interpolation(Linear)
        , _curve(new Curve(*this))
 {
        _frozen = 0;
@@ -91,8 +97,8 @@ AutomationList::AutomationList (const AutomationList& other)
        _state = other._state;
        _touching = other._touching;
        _rt_insertion_point = _events.end();
-       _lookup_cache.left = -1;
        _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) {
@@ -100,11 +106,13 @@ AutomationList::AutomationList (const AutomationList& other)
        }
 
        mark_dirty ();
+       assert(_parameter.type() != NullAutomation);
        AutomationListCreated(this);
 }
 
 AutomationList::AutomationList (const AutomationList& other, double start, double end)
-       : _param_id(other._param_id)
+       : _parameter(other._parameter)
+       , _interpolation(Linear)
        , _curve(new Curve(*this))
 {
        _frozen = 0;
@@ -117,8 +125,8 @@ AutomationList::AutomationList (const AutomationList& other, double start, doubl
        _state = other._state;
        _touching = other._touching;
        _rt_insertion_point = _events.end();
-       _lookup_cache.left = -1;
        _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 */
@@ -126,7 +134,7 @@ AutomationList::AutomationList (const AutomationList& other, double start, doubl
        AutomationList* section = const_cast<AutomationList*>(&other)->copy (start, end);
 
        if (!section->empty()) {
-               for (AutomationList::iterator i = section->begin(); i != section->end(); ++i) {
+               for (iterator i = section->begin(); i != section->end(); ++i) {
                        _events.push_back (new ControlEvent ((*i)->when, (*i)->value));
                }
        }
@@ -135,14 +143,16 @@ AutomationList::AutomationList (const AutomationList& other, double start, doubl
 
        mark_dirty ();
 
+       assert(_parameter.type() != NullAutomation);
        AutomationListCreated(this);
 }
 
 /** \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, ParamID id)
-       : _curve(new Curve(*this))
+AutomationList::AutomationList (const XMLNode& node, Parameter id)
+       : _interpolation(Linear)
+       , _curve(new Curve(*this))
 {
        _frozen = 0;
        _changed_when_thawed = false;
@@ -153,15 +163,16 @@ AutomationList::AutomationList (const XMLNode& node, ParamID id)
        _state = Off;
        _style = Absolute;
        _rt_insertion_point = _events.end();
-       _lookup_cache.left = -1;
        _lookup_cache.range.first = _events.end();
+       _search_cache.range.first = _events.end();
        _sort_pending = false;
        
        set_state (node);
 
        if (id)
-               _param_id = id;
+               _parameter = id;
 
+       assert(_parameter.type() != NullAutomation);
        AutomationListCreated(this);
 }
 
@@ -280,7 +291,7 @@ AutomationList::extend_to (double when)
 
 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);
        }
 
@@ -308,7 +319,6 @@ AutomationList::rt_add (double when, double value)
                Glib::Mutex::Lock lm (_lock);
 
                iterator where;
-               TimeComparator cmp;
                ControlEvent cp (when, 0.0);
                bool done = false;
 
@@ -331,17 +341,17 @@ AutomationList::rt_add (double when, double value)
                                        ++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);
-                                }
+                               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 {
 
@@ -360,7 +370,7 @@ AutomationList::rt_add (double when, double value)
                        
                } else {
 
-                       where = lower_bound (_events.begin(), _events.end(), &cp, cmp);
+                       where = lower_bound (_events.begin(), _events.end(), &cp, time_comparator);
 
                        if (where != _events.end()) {
                                if ((*where)->when == when) {
@@ -369,7 +379,7 @@ AutomationList::rt_add (double when, double value)
                                }
                        }
                }
-               
+
                if (!done) {
                        _rt_insertion_point = _events.insert (where, new ControlEvent (when, value));
                }
@@ -386,6 +396,7 @@ AutomationList::fast_simple_add (double when, double value)
 {
        /* to be used only for loading pre-sorted data from saved state */
        _events.insert (_events.end(), new ControlEvent (when, value));
+       assert(_events.back());
 }
 
 void
@@ -395,12 +406,11 @@ AutomationList::add (double when, double value)
 
        {
                Glib::Mutex::Lock lm (_lock);
-               TimeComparator cmp;
                ControlEvent cp (when, 0.0f);
                bool insert = true;
                iterator insertion_point;
 
-               for (insertion_point = lower_bound (_events.begin(), _events.end(), &cp, cmp); insertion_point != _events.end(); ++insertion_point) {
+               for (insertion_point = lower_bound (_events.begin(), _events.end(), &cp, time_comparator); insertion_point != _events.end(); ++insertion_point) {
 
                        /* only one point allowed per time point */
 
@@ -429,7 +439,7 @@ AutomationList::add (double when, double value)
 }
 
 void
-AutomationList::erase (AutomationList::iterator i)
+AutomationList::erase (iterator i)
 {
        {
                Glib::Mutex::Lock lm (_lock);
@@ -441,7 +451,7 @@ AutomationList::erase (AutomationList::iterator i)
 }
 
 void
-AutomationList::erase (AutomationList::iterator start, AutomationList::iterator end)
+AutomationList::erase (iterator start, iterator end)
 {
        {
                Glib::Mutex::Lock lm (_lock);
@@ -459,15 +469,14 @@ AutomationList::reset_range (double start, double endt)
 
        {
         Glib::Mutex::Lock lm (_lock);
-               TimeComparator cmp;
                ControlEvent cp (start, 0.0f);
                iterator s;
                iterator e;
                
-               if ((s = lower_bound (_events.begin(), _events.end(), &cp, cmp)) != _events.end()) {
+               if ((s = lower_bound (_events.begin(), _events.end(), &cp, time_comparator)) != _events.end()) {
 
                        cp.when = endt;
-                       e = upper_bound (_events.begin(), _events.end(), &cp, cmp);
+                       e = upper_bound (_events.begin(), _events.end(), &cp, time_comparator);
 
                        for (iterator i = s; i != e; ++i) {
                                (*i)->value = _default_value;
@@ -491,14 +500,13 @@ AutomationList::erase_range (double start, double endt)
 
        {
                Glib::Mutex::Lock lm (_lock);
-               TimeComparator cmp;
                ControlEvent cp (start, 0.0f);
                iterator s;
                iterator e;
 
-               if ((s = lower_bound (_events.begin(), _events.end(), &cp, cmp)) != _events.end()) {
+               if ((s = lower_bound (_events.begin(), _events.end(), &cp, time_comparator)) != _events.end()) {
                        cp.when = endt;
-                       e = upper_bound (_events.begin(), _events.end(), &cp, cmp);
+                       e = upper_bound (_events.begin(), _events.end(), &cp, time_comparator);
                        _events.erase (s, e);
                        reposition_for_rt_add (0);
                        erased = true;
@@ -598,14 +606,13 @@ AutomationList::control_points_adjacent (double xval)
 {
        Glib::Mutex::Lock lm (_lock);
        iterator i;
-       TimeComparator cmp;
        ControlEvent cp (xval, 0.0f);
        std::pair<iterator,iterator> ret;
 
        ret.first = _events.end();
        ret.second = _events.end();
 
-       for (i = lower_bound (_events.begin(), _events.end(), &cp, cmp); i != _events.end(); ++i) {
+       for (i = lower_bound (_events.begin(), _events.end(), &cp, time_comparator); i != _events.end(); ++i) {
                
                if (ret.first == _events.end()) {
                        if ((*i)->when >= xval) {
@@ -670,6 +677,7 @@ void
 AutomationList::mark_dirty ()
 {
        _lookup_cache.left = -1;
+       _search_cache.left = -1;
        Dirty (); /* EMIT SIGNAL */
 }
 
@@ -679,7 +687,7 @@ AutomationList::truncate_end (double last_coordinate)
        {
                Glib::Mutex::Lock lm (_lock);
                ControlEvent cp (last_coordinate, 0);
-               list<ControlEvent*>::reverse_iterator i;
+               AutomationList::reverse_iterator i;
                double last_val;
 
                if (_events.empty()) {
@@ -748,7 +756,7 @@ AutomationList::truncate_end (double last_coordinate)
                        uint32_t sz = _events.size();
                        
                        while (i != _events.rend() && sz > 2) {
-                               list<ControlEvent*>::reverse_iterator tmp;
+                               AutomationList::reverse_iterator tmp;
                                
                                tmp = i;
                                ++tmp;
@@ -779,7 +787,7 @@ AutomationList::truncate_start (double overall_length)
 {
        {
                Glib::Mutex::Lock lm (_lock);
-               AutomationList::iterator i;
+               iterator i;
                double first_legal_value;
                double first_legal_coordinate;
 
@@ -845,7 +853,7 @@ AutomationList::truncate_start (double overall_length)
                        i = _events.begin();
                        
                        while (i != _events.end() && !_events.empty()) {
-                               list<ControlEvent*>::iterator tmp;
+                               AutomationList::iterator tmp;
                                
                                tmp = i;
                                ++tmp;
@@ -919,6 +927,9 @@ AutomationList::unlocked_eval (double x) const
                upos = _events.back()->when;
                uval = _events.back()->value;
                
+               if (_interpolation == Discrete)
+                       return lval;
+
                /* linear interpolation betweeen the two points
                */
 
@@ -939,32 +950,46 @@ AutomationList::unlocked_eval (double x) const
                return multipoint_eval (x);
                break;
        }
+
+       /*NOTREACHED*/ /* stupid gcc */
+       return 0.0;
 }
 
 double
 AutomationList::multipoint_eval (double x) const
 {
-       pair<AutomationList::const_iterator,AutomationList::const_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)
-       */
-
+       /* 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) {
 
@@ -1004,10 +1029,228 @@ AutomationList::multipoint_eval (double x) const
        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 (_param_id, _min_yval, _max_yval, _default_value);
+       AutomationList* nal = new AutomationList (_parameter, _min_yval, _max_yval, _default_value);
 
        {
                Glib::Mutex::Lock lm (_lock);
@@ -1037,21 +1280,20 @@ AutomationList::cut (iterator start, iterator end)
 AutomationList*
 AutomationList::cut_copy_clear (double start, double end, int op)
 {
-       AutomationList* nal = new AutomationList (_param_id, _min_yval, _max_yval, _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);
 
-               if ((s = lower_bound (_events.begin(), _events.end(), &cp, cmp)) == _events.end()) {
+               if ((s = lower_bound (_events.begin(), _events.end(), &cp, time_comparator)) == _events.end()) {
                        return nal;
                }
 
                cp.when = end;
-               e = upper_bound (_events.begin(), _events.end(), &cp, cmp);
+               e = upper_bound (_events.begin(), _events.end(), &cp, time_comparator);
 
                if (op != 2 && (*s)->when != start) {
                        nal->_events.push_back (new ControlEvent (0, unlocked_eval (start)));
@@ -1100,7 +1342,7 @@ AutomationList::cut_copy_clear (double start, double end, int op)
 AutomationList*
 AutomationList::copy (iterator start, iterator end)
 {
-       AutomationList* nal = new AutomationList (_param_id, _min_yval, _max_yval, _default_value);
+       AutomationList* nal = new AutomationList (_parameter, _min_yval, _max_yval, _default_value);
 
        {
                Glib::Mutex::Lock lm (_lock);
@@ -1151,9 +1393,8 @@ AutomationList::paste (AutomationList& alist, double pos, float times)
                iterator prev;
                double end = 0;
                ControlEvent cp (pos, 0.0);
-               TimeComparator cmp;
 
-               where = upper_bound (_events.begin(), _events.end(), &cp, cmp);
+               where = upper_bound (_events.begin(), _events.end(), &cp, time_comparator);
 
                for (iterator i = alist.begin();i != alist.end(); ++i) {
                        _events.insert (where, new ControlEvent( (*i)->when+pos,( *i)->value));
@@ -1195,24 +1436,24 @@ AutomationList::get_state ()
 XMLNode&
 AutomationList::state (bool full)
 {
-       cerr << _param_id.to_string() << "->state()" << endl;
-
        XMLNode* root = new XMLNode (X_("AutomationList"));
        char buf[64];
        LocaleGuard lg (X_("POSIX"));
 
-       root->add_property ("automation-id", _param_id.to_string());
+       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);
+       root->add_property ("min_yval", buf);
        snprintf (buf, sizeof (buf), "%.12g", _max_yval);
-       root->add_property ("_max_yval", buf);
+       root->add_property ("max_yval", buf);
        snprintf (buf, sizeof (buf), "%.12g", _max_xval);
-       root->add_property ("_max_xval", buf);
+       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));
@@ -1367,15 +1608,19 @@ AutomationList::set_state (const XMLNode& node)
        }
        
        if ((prop = node.property (X_("automation-id"))) != 0){ 
-               _param_id = ParamID(prop->value());
+               _parameter = Parameter(prop->value());
        } else {
                warning << "Legacy session: automation list has no automation-id property.";
        }
        
-       cerr << "Loaded automation " << _param_id.to_string() << endl;
+       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());
+               _default_value = atof (prop->value().c_str());
        } else {
                _default_value = 0.0;
        }
@@ -1392,20 +1637,20 @@ AutomationList::set_state (const XMLNode& node)
                _state = Off;
        }
 
-       if ((prop = node.property (X_("_min_yval"))) != 0) {
-               _min_yval = atof (prop->value ());
+       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 ());
+       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 ());
+       if ((prop = node.property (X_("max_xval"))) != 0) {
+               _max_xval = atof (prop->value ().c_str());
        } else {
                _max_xval = 0; // means "no limit ;
        }