Implement additional ControlList interpolation methods.
authorRobin Gareus <robin@gareus.org>
Mon, 19 Jun 2017 13:45:47 +0000 (15:45 +0200)
committerRobin Gareus <robin@gareus.org>
Wed, 21 Jun 2017 16:12:26 +0000 (18:12 +0200)
The Control and ControlList uses the raw value (eg. coefficient for gain,
Hz for frequencies) and those Lists are stored in existing sessions.

In the vast majority of cases interpolating automation values using exp/log
scale for dB, freq makes more sense -- it's also what the fader does.

Adding additional interpolation methods is future proof (we might at allow
to even add different methods per automation point (to the next) like other
DAWs do.

Currently it's mainly used in preparation for consistent GUI automation-
lanes. Between 2 points there's always a visual straight line.

libs/ardour/enums.cc
libs/evoral/evoral/ControlList.hpp
libs/evoral/src/ControlList.cpp
libs/evoral/src/Curve.cpp

index 61a7e9a39f68cf87b9745bc88f0355cd14c12cac..748d725c09fafdb1b96a216e37b4457b31acebfd 100644 (file)
@@ -545,6 +545,8 @@ setup_enum_writer ()
        REGISTER_CLASS_ENUM (AutomationList, Discrete);
        REGISTER_CLASS_ENUM (AutomationList, Linear);
        REGISTER_CLASS_ENUM (AutomationList, Curved);
+       REGISTER_CLASS_ENUM (AutomationList, Logarithmic);
+       REGISTER_CLASS_ENUM (AutomationList, Exponential);
        REGISTER (_AutomationList_InterpolationStyle);
 
        REGISTER_CLASS_ENUM (AnyTime, Timecode);
index 99958d79fb186ffa741e6faa74de790aec2cb05c..c4051193b8b6edb68ffbd340d7c9fb95ae8e84c1 100644 (file)
@@ -275,7 +275,9 @@ public:
        enum InterpolationStyle {
                Discrete,
                Linear,
-               Curved
+               Curved, // spline, used for x-fades
+               Logarithmic,
+               Exponential // fader, gain
        };
 
        /** query interpolation style of the automation data
@@ -283,10 +285,18 @@ public:
         */
        InterpolationStyle interpolation() const { return _interpolation; }
 
-       /** set the interpolation style of the automation data
+       /** query default interpolation for parameter-descriptor */
+       virtual InterpolationStyle default_interpolation() const;
+
+       /** set the interpolation style of the automation data.
+        *
+        * This will fail when asking for Logarithmic scale and min,max crosses 0
+        * or Exponential scale with min != 0.
+        *
         * @param is interpolation style
+        * @returns true if style change was successful
         */
-       void set_interpolation (InterpolationStyle is);
+       bool set_interpolation (InterpolationStyle is);
 
        virtual bool touching() const { return false; }
        virtual bool writing() const { return false; }
@@ -339,14 +349,15 @@ protected:
 
        Curve* _curve;
 
-  private:
-    iterator   most_recent_insert_iterator;
-    double     insert_position;
-    bool       new_write_pass;
-    bool       did_write_during_pass;
-    bool       _in_write_pass;
-    void unlocked_invalidate_insert_iterator ();
-    void add_guard_point (double when);
+private:
+       iterator   most_recent_insert_iterator;
+       double     insert_position;
+       bool       new_write_pass;
+       bool       did_write_during_pass;
+       bool       _in_write_pass;
+
+       void unlocked_invalidate_insert_iterator ();
+       void add_guard_point (double when);
 };
 
 } // namespace Evoral
index 3a3737004b41af35d96877d8b970746c69365edc..516ffc56c251fb97d5ee09d836b419c8da7f8b86 100644 (file)
@@ -38,6 +38,7 @@
 #include "evoral/TypeMap.hpp"
 #include "evoral/types.hpp"
 
+#include "pbd/control_math.h"
 #include "pbd/compose.h"
 #include "pbd/debug.h"
 
@@ -54,9 +55,9 @@ inline bool event_time_less_than (ControlEvent* a, ControlEvent* b)
 ControlList::ControlList (const Parameter& id, const ParameterDescriptor& desc)
        : _parameter(id)
        , _desc(desc)
+       , _interpolation (default_interpolation ())
        , _curve(0)
 {
-       _interpolation = desc.toggled ? Discrete : Linear;
        _frozen = 0;
        _changed_when_thawed = false;
        _lookup_cache.left = -1;
@@ -90,9 +91,8 @@ ControlList::ControlList (const ControlList& other)
        insert_position = -1;
        most_recent_insert_iterator = _events.end();
 
+       // XXX copy_events() emits Dirty, but this is just assignment copy/construction
        copy_events (other);
-
-       mark_dirty ();
 }
 
 ControlList::ControlList (const ControlList& other, double start, double end)
@@ -113,6 +113,7 @@ ControlList::ControlList (const ControlList& other, double start, double end)
        boost::shared_ptr<ControlList> section = const_cast<ControlList*>(&other)->copy (start, end);
 
        if (!section->empty()) {
+               // XXX copy_events() emits Dirty, but this is just assignment copy/construction
                copy_events (*(section.get()));
        }
 
@@ -151,10 +152,21 @@ ControlList&
 ControlList::operator= (const ControlList& other)
 {
        if (this != &other) {
+               _frozen = 0;
+               _changed_when_thawed = false;
+               _sort_pending = false;
 
+               insert_position = other.insert_position;
+               new_write_pass = true;
+               _in_write_pass = false;
+               did_write_during_pass = false;
+               insert_position = -1;
 
+               _parameter = other._parameter;
+               _desc = other._desc;
                _interpolation = other._interpolation;
 
+               // XXX copy_events() emits Dirty, but this is just assignment copy/construction
                copy_events (other);
        }
 
@@ -170,6 +182,7 @@ ControlList::copy_events (const ControlList& other)
                        delete (*x);
                }
                _events.clear ();
+               Glib::Threads::RWLock::ReaderLock olm (other._lock);
                for (const_iterator i = other.begin(); i != other.end(); ++i) {
                        _events.push_back (new ControlEvent ((*i)->when, (*i)->value));
                }
@@ -192,6 +205,17 @@ ControlList::destroy_curve()
        _curve = NULL;
 }
 
+ControlList::InterpolationStyle
+ControlList::default_interpolation () const
+{
+       if (_desc.toggled) {
+               return Discrete;
+       } else if (_desc.logarithmic) {
+               return Logarithmic;
+       }
+       return Linear;
+}
+
 void
 ControlList::maybe_signal_changed ()
 {
@@ -1252,13 +1276,21 @@ ControlList::unlocked_eval (double x) const
                upos = _events.back()->when;
                uval = _events.back()->value;
 
-               if (_interpolation == Discrete) {
-                       return lval;
-               }
-
-               /* linear interpolation between the two points */
                fraction = (double) (x - lpos) / (double) (upos - lpos);
-               return lval + (fraction * (uval - lval));
+
+               switch (_interpolation) {
+                       case Discrete:
+                               return lval;
+                       case Logarithmic:
+                               return interpolate_logarithmic (lval, uval, fraction, _desc.lower, _desc.upper);
+                       case Exponential:
+                               return interpolate_gain (lval, uval, fraction, _desc.upper);
+                       case Curved:
+                               /* only used x-fade curves, never direct eval */
+                               assert (0);
+                       default: // Linear
+                               return interpolate_linear (lval, uval, fraction);
+               }
 
        default:
                if (x >= _events.back()->when) {
@@ -1334,13 +1366,24 @@ ControlList::multipoint_eval (double x) const
                upos = (*range.second)->when;
                uval = (*range.second)->value;
 
-               /* linear interpolation betweeen the two points
-                  on either side of x
-               */
-
                fraction = (double) (x - lpos) / (double) (upos - lpos);
-               return lval + (fraction * (uval - lval));
 
+               switch (_interpolation) {
+                       case Logarithmic:
+                               return interpolate_logarithmic (lval, uval, fraction, _desc.lower, _desc.upper);
+                       case Exponential:
+                               return interpolate_gain (lval, uval, fraction, _desc.upper);
+                       case Discrete:
+                               /* should not reach here */
+                               assert (0);
+                       case Curved:
+                               /* only used x-fade curves, never direct eval */
+                               assert (0);
+                       default: // Linear
+                               return interpolate_linear (lval, uval, fraction);
+                               break;
+               }
+               assert (0);
        }
 
        /* x is a control point in the data */
@@ -1823,15 +1866,30 @@ ControlList::move_ranges (const list< RangeMove<double> >& movements)
        return true;
 }
 
-void
+bool
 ControlList::set_interpolation (InterpolationStyle s)
 {
        if (_interpolation == s) {
-               return;
+               return true;
+       }
+
+       switch (s) {
+               case Logarithmic:
+                       if (_desc.lower * _desc.upper <= 0 || _desc.upper <= _desc.lower) {
+                               return false;
+                       }
+                       break;
+               case Exponential:
+                       if (_desc.lower != 0 || _desc.upper <= _desc.lower) {
+                               return false;
+                       }
+               default:
+                       break;
        }
 
        _interpolation = s;
        InterpolationChanged (s); /* EMIT SIGNAL */
+       return true;
 }
 
 bool
index 07dd158079570df0fbafd893f15f11c79a36e4f4..e7eeeccb9fc0c0f72de721a7f0387249ef3e948e 100644 (file)
@@ -26,6 +26,8 @@
 
 #include <glibmm/threads.h>
 
+#include "pbd/control_math.h"
+
 #include "evoral/Curve.hpp"
 #include "evoral/ControlList.hpp"
 
@@ -282,31 +284,66 @@ Curve::_get_vector (double x0, double x1, float *vec, int32_t veclen) const
 
        if (npoints == 2) {
 
-               /* linear interpolation between 2 points */
-
-               /* XXX: this numerator / denominator stuff is pretty grim, but it's the only
-                  way I could get the maths to be accurate; doing everything with pure doubles
-                  gives ~1e-17 errors in the vec[i] computation.
-               */
-
-               /* gradient of the line */
-               double const m_num = _list.events().back()->value - _list.events().front()->value;
-               double const m_den = _list.events().back()->when - _list.events().front()->when;
-
-               /* y intercept of the line */
-               double const c = double (_list.events().back()->value) - (m_num * _list.events().back()->when / m_den);
+               const double lpos = _list.events().front()->when;
+               const double lval = _list.events().front()->value;
+               const double upos = _list.events().back()->when;
+               const double uval = _list.events().back()->value;
 
                /* dx that we are using */
-               double dx_num = 0;
-               double dx_den = 1;
                if (veclen > 1) {
-                       dx_num = hx - lx;
-                       dx_den = veclen - 1;
-                       for (int i = 0; i < veclen; ++i) {
-                               vec[i] = (lx * (m_num / m_den) + m_num * i * dx_num / (m_den * dx_den)) + c;
+                       const double dx_num = hx - lx;
+                       const double dx_den = veclen - 1;
+                       const double lower = _list.descriptor().lower;
+                       const double upper = _list.descriptor().upper;
+
+                       /* gradient of the line */
+                       const double m_num = uval - lval;
+                       const double m_den = upos - lpos;
+                       /* y intercept of the line */
+                       const double c = uval - (m_num * upos / m_den);
+
+                       switch (_list.interpolation()) {
+                               case ControlList::Logarithmic:
+                                       for (int i = 0; i < veclen; ++i) {
+                                               const double fraction = (lx - lpos + i * dx_num / dx_den) / m_den;
+                                               vec[i] = interpolate_logarithmic (lval, uval, fraction, lower, upper);
+                                       }
+                                       break;
+                               case ControlList::Exponential:
+                                       for (int i = 0; i < veclen; ++i) {
+                                               const double fraction = (lx - lpos + i * dx_num / dx_den) / m_den;
+                                               vec[i] = interpolate_gain (lval, uval, fraction, upper);
+                                       }
+                                       break;
+                               case ControlList::Discrete:
+                                       // any discrete vector curves somewhere?
+                                       assert (0);
+                               case ControlList::Curved:
+                                       // fallthrough, no 2 point spline
+                               default: // Linear:
+                                       for (int i = 0; i < veclen; ++i) {
+                                               vec[i] = (lx * (m_num / m_den) + m_num * i * dx_num / (m_den * dx_den)) + c;
+                                       }
+                                       break;
                        }
                } else {
-                       vec[0] = lx * (m_num / m_den) + c;
+                       double fraction = (lx - lpos) / (upos - lpos);
+                       switch (_list.interpolation()) {
+                               case ControlList::Logarithmic:
+                                       vec[0] = interpolate_logarithmic (lval, uval, fraction, _list.descriptor().lower, _list.descriptor().upper);
+                                       break;
+                               case ControlList::Exponential:
+                                       vec[0] = interpolate_gain (lval, uval, fraction, _list.descriptor().upper);
+                                       break;
+                               case ControlList::Discrete:
+                                       // any discrete vector curves somewhere?
+                                       assert (0);
+                               case ControlList::Curved:
+                                       // fallthrough, no 2 point spline
+                               default: // Linear:
+                                       vec[0] = interpolate_linear (lval, uval, fraction);
+                                       break;
+                       }
                }
 
                return;
@@ -388,12 +425,22 @@ Curve::multipoint_eval (double x) const
                double tdelta = x - before->when;
                double trange = after->when - before->when;
 
-               if (_list.interpolation() == ControlList::Curved && after->coeff) {
-                               ControlEvent* ev = after;
-                               double x2 = x * x;
-                               return ev->coeff[0] + (ev->coeff[1] * x) + (ev->coeff[2] * x2) + (ev->coeff[3] * x2 * x);
-               } else {
-                       return before->value + (vdelta * (tdelta / trange));
+               switch (_list.interpolation()) {
+                       case ControlList::Discrete:
+                               return before->value;
+                       case ControlList::Logarithmic:
+                               return interpolate_logarithmic (before->value, after->value, tdelta / trange, _list.descriptor().lower, _list.descriptor().upper);
+                       case ControlList::Exponential:
+                               return interpolate_gain (before->value, after->value, tdelta / trange, _list.descriptor().upper);
+                       case ControlList::Curved:
+                               if (after->coeff) {
+                                       ControlEvent* ev = after;
+                                       double x2 = x * x;
+                                       return ev->coeff[0] + (ev->coeff[1] * x) + (ev->coeff[2] * x2) + (ev->coeff[3] * x2 * x);
+                               }
+                               // no break, fallthru
+                       default: // Linear
+                               return before->value + (vdelta * (tdelta / trange));
                }
        }