/* This file is part of Evoral.
- * Copyright (C) 2008 Dave Robillard <http://drobilla.net>
+ * Copyright (C) 2008 David Robillard <http://drobilla.net>
* Copyright (C) 2000-2008 Paul Davis
- *
+ *
* Evoral is free software; you can redistribute it and/or modify it under the
* terms of the GNU General Public License as published by the Free Software
* Foundation; either version 2 of the License, or (at your option) any later
* version.
- *
+ *
* Evoral is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU General Public License for details.
- *
+ *
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
#include <utility>
#include <iostream>
#include "evoral/ControlList.hpp"
+#include "evoral/Curve.hpp"
+
+#include "pbd/compose.h"
using namespace std;
+using namespace PBD;
namespace Evoral {
-
inline bool event_time_less_than (ControlEvent* a, ControlEvent* b)
{
return a->when < b->when;
}
+/* this has no units but corresponds to the area of a rectangle
+ computed between three points in the list. If the area is
+ large, it indicates significant non-linearity between the
+ points.
+
+ during automation recording we thin the recorded points
+ using this value. if a point is sufficiently co-linear
+ with its neighbours (as defined by the area of the rectangle
+ formed by three of them), we will not include it in the
+ ControlList. a smaller value will exclude less points,
+ a larger value will exclude more points, so it effectively
+ measures the amount of thinning to be done.
+*/
+
+double ControlList::_thinning_factor = 20.0;
ControlList::ControlList (const Parameter& id)
: _parameter(id)
, _interpolation(Linear)
- , _curve(new Curve(*this))
-{
+ , _curve(0)
+{
_frozen = 0;
_changed_when_thawed = false;
_min_yval = id.min();
_max_yval = id.max();
- _max_xval = 0; // means "no limit"
- _rt_insertion_point = _events.end();
+ _default_value = id.normal();
_lookup_cache.left = -1;
_lookup_cache.range.first = _events.end();
_search_cache.left = -1;
- _search_cache.range.first = _events.end();
+ _search_cache.first = _events.end();
_sort_pending = false;
+ new_write_pass = true;
+ _in_write_pass = false;
+ did_write_during_pass = false;
+ insert_position = -1;
+ most_recent_insert_iterator = _events.end();
}
ControlList::ControlList (const ControlList& other)
: _parameter(other._parameter)
, _interpolation(Linear)
- , _curve(new Curve(*this))
+ , _curve(0)
{
_frozen = 0;
_changed_when_thawed = false;
_min_yval = other._min_yval;
_max_yval = other._max_yval;
- _max_xval = other._max_xval;
_default_value = other._default_value;
- _rt_insertion_point = _events.end();
_lookup_cache.range.first = _events.end();
- _search_cache.range.first = _events.end();
+ _search_cache.first = _events.end();
_sort_pending = false;
+ new_write_pass = true;
+ _in_write_pass = false;
+ did_write_during_pass = false;
+ insert_position = -1;
+ most_recent_insert_iterator = _events.end();
- for (const_iterator i = other._events.begin(); i != other._events.end(); ++i) {
- _events.push_back (new ControlEvent (**i));
- }
+ copy_events (other);
mark_dirty ();
}
ControlList::ControlList (const ControlList& other, double start, double end)
: _parameter(other._parameter)
, _interpolation(Linear)
- , _curve(new Curve(*this))
+ , _curve(0)
{
_frozen = 0;
_changed_when_thawed = false;
_min_yval = other._min_yval;
_max_yval = other._max_yval;
- _max_xval = other._max_xval;
_default_value = other._default_value;
- _rt_insertion_point = _events.end();
_lookup_cache.range.first = _events.end();
- _search_cache.range.first = _events.end();
+ _search_cache.first = _events.end();
_sort_pending = false;
/* now grab the relevant points, and shift them back if necessary */
boost::shared_ptr<ControlList> section = const_cast<ControlList*>(&other)->copy (start, end);
if (!section->empty()) {
- for (iterator i = section->begin(); i != section->end(); ++i) {
- _events.push_back (new ControlEvent ((*i)->when, (*i)->value));
- }
+ copy_events (*(section.get()));
}
+ new_write_pass = false;
+ _in_write_pass = false;
+ did_write_during_pass = false;
+ insert_position = -1;
+ most_recent_insert_iterator = _events.end();
+
mark_dirty ();
}
for (EventList::iterator x = _events.begin(); x != _events.end(); ++x) {
delete (*x);
}
+
+ delete _curve;
}
boost::shared_ptr<ControlList>
ControlList::operator= (const ControlList& other)
{
if (this != &other) {
-
- _events.clear ();
-
- 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;
- mark_dirty ();
- maybe_signal_changed ();
+ copy_events (other);
}
return *this;
}
+void
+ControlList::copy_events (const ControlList& other)
+{
+ {
+ Glib::Threads::Mutex::Lock lm (_lock);
+ _events.clear ();
+ for (const_iterator i = other.begin(); i != other.end(); ++i) {
+ _events.push_back (new ControlEvent ((*i)->when, (*i)->value));
+ }
+ unlocked_invalidate_insert_iterator ();
+ mark_dirty ();
+ }
+ maybe_signal_changed ();
+}
+
+void
+ControlList::create_curve()
+{
+ _curve = new Curve(*this);
+}
+
+void
+ControlList::destroy_curve()
+{
+ delete _curve;
+ _curve = NULL;
+}
+
void
ControlList::maybe_signal_changed ()
{
mark_dirty ();
-
- if (_frozen)
+
+ if (_frozen) {
_changed_when_thawed = true;
+ }
}
void
ControlList::clear ()
{
{
- Glib::Mutex::Lock lm (_lock);
+ Glib::Threads::Mutex::Lock lm (_lock);
_events.clear ();
+ unlocked_invalidate_insert_iterator ();
mark_dirty ();
}
void
ControlList::x_scale (double factor)
{
- Glib::Mutex::Lock lm (_lock);
+ Glib::Threads::Mutex::Lock lm (_lock);
_x_scale (factor);
}
bool
ControlList::extend_to (double when)
{
- Glib::Mutex::Lock lm (_lock);
+ Glib::Threads::Mutex::Lock lm (_lock);
if (_events.empty() || _events.back()->when == when) {
return false;
}
return true;
}
-void ControlList::_x_scale (double factor)
+void
+ControlList::_x_scale (double factor)
{
for (iterator i = _events.begin(); i != _events.end(); ++i) {
- (*i)->when = floor ((*i)->when * factor);
+ (*i)->when *= factor;
}
mark_dirty ();
}
-void
-ControlList::reposition_for_rt_add (double when)
-{
- _rt_insertion_point = _events.end();
-}
+struct ControlEventTimeComparator {
+ bool operator() (ControlEvent* a, ControlEvent* b) {
+ return a->when < b->when;
+ }
+};
void
-ControlList::rt_add (double when, double value)
+ControlList::thin ()
{
- //cerr << "RT: alist " << this << " add " << value << " @ " << when << endl;
+ bool changed = false;
{
- Glib::Mutex::Lock lm (_lock);
+ Glib::Threads::Mutex::Lock lm (_lock);
+
+ ControlEvent* prevprev = 0;
+ ControlEvent* cur = 0;
+ ControlEvent* prev = 0;
+ iterator pprev;
+ int counter = 0;
+
+ DEBUG_TRACE (DEBUG::ControlList, string_compose ("@%1 thin from %2 events\n", this, _events.size()));
+
+ for (iterator i = _events.begin(); i != _events.end(); ++i) {
+
+ cur = *i;
+ counter++;
+
+ if (counter > 2) {
+
+ /* compute the area of the triangle formed by 3 points
+ */
+
+ double area = fabs ((prevprev->when * (prev->value - cur->value)) +
+ (prev->when * (cur->value - prevprev->value)) +
+ (cur->when * (prevprev->value - prev->value)));
+
+ if (area < _thinning_factor) {
+ iterator tmp = pprev;
+
+ /* pprev will change to current
+ i is incremented to the next event
+ as we loop.
+ */
+
+ pprev = i;
+ _events.erase (tmp);
+ changed = true;
+ continue;
+ }
+ }
+
+ prevprev = prev;
+ prev = cur;
+ pprev = i;
+ }
+
+ DEBUG_TRACE (DEBUG::ControlList, string_compose ("@%1 thin => %2 events\n", this, _events.size()));
- iterator where;
- ControlEvent cp (when, 0.0);
- bool done = false;
+ if (changed) {
+ unlocked_invalidate_insert_iterator ();
+ mark_dirty ();
+ }
+ }
- if ((_rt_insertion_point != _events.end()) && ((*_rt_insertion_point)->when < when) ) {
+ if (changed) {
+ maybe_signal_changed ();
+ }
+}
- /* we have a previous insertion point, so we should delete
- everything between it and the position where we are going
- to insert this point.
- */
+void
+ControlList::fast_simple_add (double when, double value)
+{
+ Glib::Threads::Mutex::Lock lm (_lock);
+ /* to be used only for loading pre-sorted data from saved state */
+ _events.insert (_events.end(), new ControlEvent (when, value));
+ assert(_events.back());
- iterator after = _rt_insertion_point;
+ mark_dirty ();
+}
- if (++after != _events.end()) {
- iterator far = after;
+void
+ControlList::invalidate_insert_iterator ()
+{
+ Glib::Threads::Mutex::Lock lm (_lock);
+ unlocked_invalidate_insert_iterator ();
+}
- while (far != _events.end()) {
- if ((*far)->when > when) {
- break;
- }
- ++far;
- }
+void
+ControlList::unlocked_invalidate_insert_iterator ()
+{
+ most_recent_insert_iterator = _events.end();
+}
- if (_new_value) {
- where = far;
- _rt_insertion_point = where;
+void
+ControlList::start_write_pass (double when)
+{
+ Glib::Threads::Mutex::Lock lm (_lock);
- if ((*where)->when == when) {
- (*where)->value = value;
- done = true;
- }
- } else {
- where = _events.erase (after, far);
- }
+ new_write_pass = true;
+ did_write_during_pass = false;
+ insert_position = when;
- } else {
+ /* leave the insert iterator invalid, so that we will do the lookup
+ of where it should be in a "lazy" way - deferring it until
+ we actually add the first point (which may never happen).
+ */
- where = after;
+ unlocked_invalidate_insert_iterator ();
+}
- }
-
- iterator previous = _rt_insertion_point;
- --previous;
-
- if (_rt_insertion_point != _events.begin() && (*_rt_insertion_point)->value == value && (*previous)->value == value) {
- (*_rt_insertion_point)->when = when;
- done = true;
-
- }
-
- } else {
+void
+ControlList::write_pass_finished (double /*when*/)
+{
+ if (did_write_during_pass) {
+ thin ();
+ did_write_during_pass = false;
+ }
+ new_write_pass = true;
+ _in_write_pass = false;
+}
- where = lower_bound (_events.begin(), _events.end(), &cp, time_comparator);
+void
+ControlList::set_in_write_pass (bool yn, bool add_point, double when)
+{
+ _in_write_pass = yn;
- if (where != _events.end()) {
- if ((*where)->when == when) {
- (*where)->value = value;
- done = true;
- }
- }
- }
+ if (yn && add_point) {
+ add_guard_point (when);
+ }
+}
- if (!done) {
- _rt_insertion_point = _events.insert (where, new ControlEvent (when, value));
- }
+void
+ControlList::add_guard_point (double when)
+{
+ ControlEvent cp (when, 0.0);
+ most_recent_insert_iterator = lower_bound (_events.begin(), _events.end(), &cp, time_comparator);
+
+ DEBUG_TRACE (DEBUG::ControlList, string_compose ("@%1 ADD GUARD POINT @ %2looked up insert iterator for new write pass\n", this, when));
+
+ double eval_value = unlocked_eval (insert_position);
+
+ if (most_recent_insert_iterator == _events.end()) {
- _new_value = false;
- mark_dirty ();
+ DEBUG_TRACE (DEBUG::ControlList, string_compose ("@%1 insert iterator at end, adding eval-value there %2\n", this, eval_value));
+ _events.push_back (new ControlEvent (when, eval_value));
+ /* leave insert iterator at the end */
+
+ } else if ((*most_recent_insert_iterator)->when == when) {
+
+ DEBUG_TRACE (DEBUG::ControlList, string_compose ("@%1 insert iterator at existing point, setting eval-value there %2\n", this, eval_value));
+
+ /* most_recent_insert_iterator points to a control event
+ already at the insert position, so there is
+ nothing to do.
+
+ ... except ...
+
+ advance most_recent_insert_iterator so that the "real"
+ insert occurs in the right place, since it
+ points to the control event just inserted.
+ */
+
+ ++most_recent_insert_iterator;
+ } else {
+
+ /* insert a new control event at the right spot
+ */
+
+ DEBUG_TRACE (DEBUG::ControlList, string_compose ("@%1 insert eval-value %2 just before iterator @ %3\n",
+ this, eval_value, (*most_recent_insert_iterator)->when));
+
+ most_recent_insert_iterator = _events.insert (most_recent_insert_iterator, new ControlEvent (when, eval_value));
+
+ /* advance most_recent_insert_iterator so that the "real"
+ * insert occurs in the right place, since it
+ * points to the control event just inserted.
+ */
+
+ ++most_recent_insert_iterator;
}
-
- maybe_signal_changed ();
+
+ /* don't do this again till the next write pass */
+
+ new_write_pass = false;
}
-void
-ControlList::fast_simple_add (double when, double value)
+bool
+ControlList::in_write_pass () const
{
- /* to be used only for loading pre-sorted data from saved state */
- _events.insert (_events.end(), new ControlEvent (when, value));
- assert(_events.back());
+ return _in_write_pass;
}
void
ControlList::add (double when, double value)
{
- /* this is for graphical editing */
+ /* this is for making changes from some kind of user interface or
+ control surface (GUI, MIDI, OSC etc)
+ */
+ if (!clamp_value (when, value)) {
+ return;
+ }
+
+ DEBUG_TRACE (DEBUG::ControlList, string_compose ("@%1 add %2 at %3 w/erase = %4 at end ? %5\n",
+ this, value, when, _in_write_pass, (most_recent_insert_iterator == _events.end())));
{
- Glib::Mutex::Lock lm (_lock);
+ Glib::Threads::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, time_comparator); insertion_point != _events.end(); ++insertion_point) {
+ if (_events.empty()) {
+
+ /* as long as the point we're adding is not at zero,
+ * add an "anchor" point there.
+ */
- /* only one point allowed per time point */
+ if (when > 1) {
+ _events.insert (_events.end(), new ControlEvent (0, _default_value));
+ DEBUG_TRACE (DEBUG::ControlList, string_compose ("@%1 added default value %2 at zero\n", this, _default_value));
+ }
+ }
- if ((*insertion_point)->when == when) {
- (*insertion_point)->value = value;
- insert = false;
- break;
- }
+ if (_in_write_pass && new_write_pass) {
- if ((*insertion_point)->when >= when) {
- break;
+ add_guard_point (insert_position);
+ did_write_during_pass = true;
+
+ } else if (most_recent_insert_iterator == _events.end() || when > (*most_recent_insert_iterator)->when) {
+
+ DEBUG_TRACE (DEBUG::ControlList, string_compose ("@%1 %2 erase from existing iterator (@end ? %3)\n",
+ this, (_in_write_pass ? "DO" : "DON'T"),
+ (most_recent_insert_iterator == _events.end())));
+
+ if (_in_write_pass) {
+ while (most_recent_insert_iterator != _events.end()) {
+ if ((*most_recent_insert_iterator)->when < when) {
+ if (_in_write_pass) {
+ DEBUG_TRACE (DEBUG::ControlList, string_compose ("@%1 erase existing @ %2\n", this, (*most_recent_insert_iterator)));
+ delete *most_recent_insert_iterator;
+ most_recent_insert_iterator = _events.erase (most_recent_insert_iterator);
+ continue;
+ }
+ } else if ((*most_recent_insert_iterator)->when >= when) {
+ break;
+ }
+ ++most_recent_insert_iterator;
+ }
+
+ if (most_recent_insert_iterator != _events.end()) {
+ if ((*most_recent_insert_iterator)->when - when > 64) {
+ /* next control point is some
+ * distance from where our new
+ * point is going to go - add a
+ * new point to avoid changing
+ * the shape of the line too
+ * much. the insert iterator needs
+ * to point to the new control
+ * point so that our insert
+ * will happen correctly.
+ */
+ most_recent_insert_iterator = _events.insert (most_recent_insert_iterator,
+ new ControlEvent (when+1, (*most_recent_insert_iterator)->value));
+ DEBUG_TRACE (DEBUG::ControlList, string_compose ("@%1 added post-erase guard point @ %2 = %3\n",
+ this, when+1,
+ (*most_recent_insert_iterator)->value));
+ }
+ }
+
+ } else {
+
+ /* not in a write pass: figure out the iterator we should insert in front of */
+
+ DEBUG_TRACE (DEBUG::ControlList, string_compose ("compute MRI for position %1\n", when));
+ ControlEvent cp (when, 0.0f);
+ most_recent_insert_iterator = lower_bound (_events.begin(), _events.end(), &cp, time_comparator);
}
+
+ } else {
+
+ /* not in a write pass, adding a point within existing
+ * data: figure out the iterator we should insert in
+ * front of
+ */
+
+ DEBUG_TRACE (DEBUG::ControlList, string_compose ("compute(b) MRI for position %1\n", when));
+ ControlEvent cp (when, 0.0f);
+ most_recent_insert_iterator = lower_bound (_events.begin(), _events.end(), &cp, time_comparator);
}
- if (insert) {
+ /* OK, now we're really ready to add a new point
+ */
+
+ if (most_recent_insert_iterator == _events.end()) {
+ DEBUG_TRACE (DEBUG::ControlList, string_compose ("@%1 appending new point at end\n", this));
- _events.insert (insertion_point, new ControlEvent (when, value));
- reposition_for_rt_add (0);
+ bool done = false;
+
+ /* check if would just be adding to a straight line,
+ * and don't add another point if so
+ */
+
+ if (!_events.empty()) { // avoid O(N) _events.size() here
+ if (_events.back()->value == value) {
+ EventList::iterator b = _events.end();
+ --b; // final point, which we know exists
+ if (b != _events.begin()) { // step back again, but check first that it is legal
+ --b; // penultimate-point
+ if ((*b)->value == value) {
+ /* there are at least two points with the exact same value ...
+ * straight line - just move the final
+ * point to the new time
+ */
+ _events.back()->when = when;
+ done = true;
+ DEBUG_TRACE (DEBUG::ControlList, string_compose ("final value of %1 moved to %2\n", value, when));
+ }
+ }
+ }
+ }
+
+ if (!done) {
+ _events.push_back (new ControlEvent (when, value));
+ DEBUG_TRACE (DEBUG::ControlList, string_compose ("\tactually appended, size now %1\n", _events.size()));
+ }
+
+ if (!_in_write_pass) {
+ most_recent_insert_iterator = _events.end();
+ --most_recent_insert_iterator;
+ }
+
+ } else if ((*most_recent_insert_iterator)->when == when) {
- }
+ if ((*most_recent_insert_iterator)->value != value) {
+ DEBUG_TRACE (DEBUG::ControlList, string_compose ("@%1 reset existing point to new value %2\n", this, value));
+
+ /* only one point allowed per time point, so just
+ * reset the value here.
+ */
+
+ (*most_recent_insert_iterator)->value = value;
+
+ /* if we modified the final value, then its as
+ * if we inserted a new point as far as the
+ * next addition, so make sure we know that.
+ */
+
+ if (_in_write_pass && _events.back()->when == when) {
+ most_recent_insert_iterator = _events.end();
+ }
+
+ } else {
+ DEBUG_TRACE (DEBUG::ControlList, string_compose ("@%1 same time %2, same value value %3\n", this, when, value));
+ }
+
+ } else {
+ DEBUG_TRACE (DEBUG::ControlList, string_compose ("@%1 insert new point at %2 at iterator at %3\n", this, when, (*most_recent_insert_iterator)->when));
+
+ bool done = false;
+
+ /* check if would just be adding to a straight line,
+ * and don't add another point if so
+ */
+
+ if (most_recent_insert_iterator != _events.begin()) {
+ EventList::iterator b = most_recent_insert_iterator;
+ --b; // prior point, which we know exists
+ if ((*b)->value == value) { // same value as the point we plan to insert
+ if (b != _events.begin()) { // step back again, which may not be possible
+ EventList::iterator bb = b;
+ --bb; // next-to-prior-point
+ if ((*bb)->value == value) {
+ /* straight line - just move the prior
+ * point to the new time
+ */
+ (*b)->when = when;
+
+ if (!_in_write_pass) {
+ most_recent_insert_iterator = b;
+ }
+
+ DEBUG_TRACE (DEBUG::ControlList, string_compose ("final value of %1 moved to %2\n", value, when));
+ done = true;
+ }
+ }
+ }
+ }
+
+ if (most_recent_insert_iterator != _events.end()) {
+ if ((*most_recent_insert_iterator)->when - when > 64) {
+ /* next control point is some
+ * distance from where our new
+ * point is going to go - add a
+ * new point to avoid changing
+ * the shape of the line too
+ * much. the insert iterator needs
+ * to point to the new control
+ * point so that our insert
+ * will happen correctly.
+ */
+ most_recent_insert_iterator = _events.insert (most_recent_insert_iterator,
+ new ControlEvent (when+1, (*most_recent_insert_iterator)->value));
+ DEBUG_TRACE (DEBUG::ControlList, string_compose ("@%1 added insert-post-erase guard point @ %2 = %3\n",
+ this, when+1,
+ (*most_recent_insert_iterator)->value));
+ }
+ }
+
+ if (!done) {
+ EventList::iterator x = _events.insert (most_recent_insert_iterator, new ControlEvent (when, value));
+ DEBUG_TRACE (DEBUG::ControlList, string_compose ("@%1 inserted new value before MRI, size now %2\n", this, _events.size()));
+
+ if (!_in_write_pass) {
+ most_recent_insert_iterator = x;
+ }
+ }
+ }
mark_dirty ();
}
ControlList::erase (iterator i)
{
{
- Glib::Mutex::Lock lm (_lock);
+ Glib::Threads::Mutex::Lock lm (_lock);
+ if (most_recent_insert_iterator == i) {
+ unlocked_invalidate_insert_iterator ();
+ }
_events.erase (i);
- reposition_for_rt_add (0);
mark_dirty ();
}
maybe_signal_changed ();
ControlList::erase (iterator start, iterator end)
{
{
- Glib::Mutex::Lock lm (_lock);
+ Glib::Threads::Mutex::Lock lm (_lock);
_events.erase (start, end);
- reposition_for_rt_add (0);
+ unlocked_invalidate_insert_iterator ();
mark_dirty ();
}
maybe_signal_changed ();
-}
+}
+/** Erase the first event which matches the given time and value */
void
-ControlList::reset_range (double start, double endt)
+ControlList::erase (double when, double value)
{
- bool reset = false;
-
{
- Glib::Mutex::Lock lm (_lock);
- ControlEvent cp (start, 0.0f);
- iterator s;
- iterator e;
-
- if ((s = lower_bound (_events.begin(), _events.end(), &cp, time_comparator)) != _events.end()) {
+ Glib::Threads::Mutex::Lock lm (_lock);
- cp.when = endt;
- e = upper_bound (_events.begin(), _events.end(), &cp, time_comparator);
+ iterator i = begin ();
+ while (i != end() && ((*i)->when != when || (*i)->value != value)) {
+ ++i;
+ }
- for (iterator i = s; i != e; ++i) {
- (*i)->value = _default_value;
+ if (i != end ()) {
+ _events.erase (i);
+ if (most_recent_insert_iterator == i) {
+ unlocked_invalidate_insert_iterator ();
}
-
- reset = true;
-
- mark_dirty ();
}
- }
- if (reset) {
- maybe_signal_changed ();
+ mark_dirty ();
}
+
+ maybe_signal_changed ();
}
void
bool erased = false;
{
- Glib::Mutex::Lock lm (_lock);
+ Glib::Threads::Mutex::Lock lm (_lock);
erased = erase_range_internal (start, endt, _events);
if (erased) {
- reposition_for_rt_add (0);
mark_dirty ();
}
-
+
}
if (erased) {
cp.when = endt;
e = upper_bound (events.begin(), events.end(), &cp, time_comparator);
events.erase (s, e);
- erased = true;
+ if (s != e) {
+ unlocked_invalidate_insert_iterator ();
+ erased = true;
+ }
}
return erased;
ControlList::slide (iterator before, double distance)
{
{
- Glib::Mutex::Lock lm (_lock);
+ Glib::Threads::Mutex::Lock lm (_lock);
if (before == _events.end()) {
return;
}
-
+
while (before != _events.end()) {
(*before)->when += distance;
++before;
}
+
+ mark_dirty ();
+ }
+
+ maybe_signal_changed ();
+}
+
+void
+ControlList::shift (double pos, double frames)
+{
+ {
+ Glib::Threads::Mutex::Lock lm (_lock);
+
+ for (iterator i = _events.begin(); i != _events.end(); ++i) {
+ if ((*i)->when >= pos) {
+ (*i)->when += frames;
+ }
+ }
+
+ mark_dirty ();
}
maybe_signal_changed ();
*/
{
- Glib::Mutex::Lock lm (_lock);
+ Glib::Threads::Mutex::Lock lm (_lock);
(*iter)->when = when;
(*iter)->value = val;
- if (isnan (val)) {
+ if (std::isnan (val)) {
abort ();
}
if (!_frozen) {
_events.sort (event_time_less_than);
+ unlocked_invalidate_insert_iterator ();
} else {
_sort_pending = true;
}
std::pair<ControlList::iterator,ControlList::iterator>
ControlList::control_points_adjacent (double xval)
{
- Glib::Mutex::Lock lm (_lock);
+ Glib::Threads::Mutex::Lock lm (_lock);
iterator i;
ControlEvent cp (xval, 0.0f);
std::pair<iterator,iterator> ret;
ret.second = _events.end();
for (i = lower_bound (_events.begin(), _events.end(), &cp, time_comparator); i != _events.end(); ++i) {
-
+
if (ret.first == _events.end()) {
if ((*i)->when >= xval) {
if (i != _events.begin()) {
return ret;
}
}
- }
-
+ }
+
if ((*i)->when > xval) {
ret.second = i;
break;
return ret;
}
-void
-ControlList::set_max_xval (double x)
-{
- _max_xval = x;
-}
-
void
ControlList::freeze ()
{
}
{
- Glib::Mutex::Lock lm (_lock);
+ Glib::Threads::Mutex::Lock lm (_lock);
if (_sort_pending) {
_events.sort (event_time_less_than);
+ unlocked_invalidate_insert_iterator ();
_sort_pending = false;
}
}
}
-void
+void
ControlList::mark_dirty () const
{
_lookup_cache.left = -1;
_search_cache.left = -1;
- if (_curve)
+
+ if (_curve) {
_curve->mark_dirty();
+ }
+
+ Dirty (); /* EMIT SIGNAL */
}
void
ControlList::truncate_end (double last_coordinate)
{
{
- Glib::Mutex::Lock lm (_lock);
+ Glib::Threads::Mutex::Lock lm (_lock);
ControlEvent cp (last_coordinate, 0);
ControlList::reverse_iterator i;
double last_val;
}
if (last_coordinate > _events.back()->when) {
-
+
/* extending end:
- */
+ */
iterator foo = _events.begin();
bool lessthantwo;
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;
} else {
last_val = unlocked_eval (last_coordinate);
last_val = max ((double) _min_yval, last_val);
last_val = min ((double) _max_yval, last_val);
-
+
i = _events.rbegin();
-
+
/* make i point to the last control point */
-
+
++i;
-
+
/* now go backwards, removing control points that are
beyond the new last coordinate.
*/
// FIXME: SLOW! (size() == O(n))
uint32_t sz = _events.size();
-
+
while (i != _events.rend() && sz > 2) {
ControlList::reverse_iterator tmp;
-
+
tmp = i;
++tmp;
-
+
if ((*i)->when < last_coordinate) {
break;
}
-
+
_events.erase (i.base());
--sz;
i = tmp;
}
-
+
_events.back()->when = last_coordinate;
_events.back()->value = last_val;
}
-
- reposition_for_rt_add (0);
+
+ unlocked_invalidate_insert_iterator ();
mark_dirty();
}
ControlList::truncate_start (double overall_length)
{
{
- Glib::Mutex::Lock lm (_lock);
+ Glib::Threads::Mutex::Lock lm (_lock);
iterator i;
double first_legal_value;
double first_legal_coordinate;
assert(!_events.empty());
-
+
if (overall_length == _events.back()->when) {
/* no change in overall length */
return;
}
-
+
if (overall_length > _events.back()->when) {
-
+
/* growing at front: duplicate first point. shift all others */
double shift = overall_length - _events.back()->when;
iterator second = _events.begin();
++second; /* points at the second point */
-
+
if (_events.front()->value == (*second)->value) {
/* first segment is flat, just move start point back to zero */
_events.front()->when = 0;
} else {
/* shrinking at front */
-
+
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);
/* remove all events earlier than the new "front" */
i = _events.begin();
-
+
while (i != _events.end() && !_events.empty()) {
ControlList::iterator tmp;
-
+
tmp = i;
++tmp;
-
+
if ((*i)->when > first_legal_coordinate) {
break;
}
-
+
_events.erase (i);
-
+
i = tmp;
}
-
+
/* shift all remaining points left to keep their same
relative position
*/
-
+
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 (new ControlEvent (0, first_legal_value));
- }
- reposition_for_rt_add (0);
+ _events.push_front (new ControlEvent (0, first_legal_value));
+ }
+ unlocked_invalidate_insert_iterator ();
mark_dirty();
}
double fraction;
const_iterator length_check_iter = _events.begin();
- for (npoints = 0; npoints < 4; ++npoints, ++length_check_iter)
- if (length_check_iter == _events.end())
+ for (npoints = 0; npoints < 4; ++npoints, ++length_check_iter) {
+ if (length_check_iter == _events.end()) {
break;
+ }
+ }
switch (npoints) {
case 0:
return _default_value;
case 1:
- if (x >= _events.front()->when) {
- return _events.front()->value;
- } else {
- // hansfbaier: v--------- Why commented ???
- // return _default_value;
- return _events.front()->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) {
- // hansfbaier: v--------- Why commented ???
- // return _default_value;
+ } else if (x <= _events.front()->when) {
return _events.front()->value;
}
lval = _events.front()->value;
upos = _events.back()->when;
uval = _events.back()->value;
-
+
if (_interpolation == Discrete) {
return lval;
}
- /* linear interpolation betweeen the two points
- */
-
+ /* linear interpolation betweeen the two points */
fraction = (double) (x - lpos) / (double) (upos - lpos);
return lval + (fraction * (uval - lval));
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) {
- // hansfbaier: v--------- Why commented ???
- // return _default_value;
+ } else if (x <= _events.front()->when) {
return _events.front()->value;
}
return multipoint_eval (x);
- break;
}
/*NOTREACHED*/ /* stupid gcc */
- return 0.0;
+ return _default_value;
}
double
double upos, lpos;
double uval, lval;
double fraction;
-
+
/* "Stepped" lookup (no interpolation) */
/* FIXME: no cache. significant? */
if (_interpolation == Discrete) {
/* 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.left > x) ||
+ (_lookup_cache.range.first == _events.end()) ||
((*_lookup_cache.range.second)->when < x))) {
const ControlEvent cp (x, 0);
-
+
_lookup_cache.range = equal_range (_events.begin(), _events.end(), &cp, time_comparator);
}
-
+
pair<const_iterator,const_iterator> range = _lookup_cache.range;
if (range.first == range.second) {
// return _default_value;
return _events.front()->value;
}
-
+
if (range.second == _events.end()) {
/* we're after the last point */
return _events.back()->value;
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));
- }
+ }
/* x is a control point in the data */
_lookup_cache.left = -1;
}
void
-ControlList::build_search_cache_if_necessary(double start, double end) const
+ControlList::build_search_cache_if_necessary (double start) const
{
/* Only do the range lookup if x is in a different range than last time
* this was called (or if the search cache has been marked "dirty" (left<0) */
- if (!_events.empty() && ((_search_cache.left < 0) ||
- ((_search_cache.left > start) ||
- (_search_cache.right < end)))) {
+ if (!_events.empty() && ((_search_cache.left < 0) || (_search_cache.left > start))) {
const ControlEvent start_point (start, 0);
- const ControlEvent end_point (end, 0);
//cerr << "REBUILD: (" << _search_cache.left << ".." << _search_cache.right << ") := ("
// << start << ".." << end << ")" << endl;
- _search_cache.range.first = lower_bound (_events.begin(), _events.end(), &start_point, time_comparator);
- _search_cache.range.second = upper_bound (_events.begin(), _events.end(), &end_point, time_comparator);
-
+ _search_cache.first = lower_bound (_events.begin(), _events.end(), &start_point, time_comparator);
_search_cache.left = start;
- _search_cache.right = end;
}
}
-/** Get the earliest event between \a start and \a end, using the current interpolation style.
+/** Get the earliest event after \a start using the current interpolation style.
*
* If an event is found, \a x and \a y are set to its coordinates.
*
* \return true if event is found (and \a x and \a y are valid).
*/
bool
-ControlList::rt_safe_earliest_event(double start, double end, double& x, double& y, bool inclusive) const
+ControlList::rt_safe_earliest_event (double start, double& x, double& y, bool inclusive) const
{
// FIXME: It would be nice if this was unnecessary..
- Glib::Mutex::Lock lm(_lock, Glib::TRY_LOCK);
+ Glib::Threads::Mutex::Lock lm(_lock, Glib::Threads::TRY_LOCK);
if (!lm.locked()) {
return false;
}
- return rt_safe_earliest_event_unlocked(start, end, x, y, inclusive);
-}
+ return rt_safe_earliest_event_unlocked (start, x, y, inclusive);
+}
-/** Get the earliest event between \a start and \a end, using the current interpolation style.
+/** Get the earliest event after \a start using the current interpolation style.
*
* If an event is found, \a x and \a y are set to its coordinates.
*
* \return true if event is found (and \a x and \a y are valid).
*/
bool
-ControlList::rt_safe_earliest_event_unlocked(double start, double end, double& x, double& y, bool inclusive) const
+ControlList::rt_safe_earliest_event_unlocked (double start, double& x, double& y, bool inclusive) const
{
- if (_interpolation == Discrete)
- return rt_safe_earliest_event_discrete_unlocked(start, end, x, y, inclusive);
- else
- return rt_safe_earliest_event_linear_unlocked(start, end, x, y, inclusive);
-}
+ if (_interpolation == Discrete) {
+ return rt_safe_earliest_event_discrete_unlocked(start, x, y, inclusive);
+ } else {
+ return rt_safe_earliest_event_linear_unlocked(start, x, y, inclusive);
+ }
+}
-/** Get the earliest event between \a start and \a end (Discrete (lack of) interpolation)
+/** Get the earliest event after \a start without interpolation.
*
* If an event is found, \a x and \a y are set to its coordinates.
*
* \return true if event is found (and \a x and \a y are valid).
*/
bool
-ControlList::rt_safe_earliest_event_discrete_unlocked (double start, double end, double& x, double& y, bool inclusive) const
+ControlList::rt_safe_earliest_event_discrete_unlocked (double start, double& x, double& y, bool inclusive) const
{
- build_search_cache_if_necessary(start, end);
+ build_search_cache_if_necessary (start);
- const pair<const_iterator,const_iterator>& range = _search_cache.range;
-
- if (range.first != _events.end()) {
- const ControlEvent* const first = *range.first;
+ if (_search_cache.first != _events.end()) {
+ const ControlEvent* const first = *_search_cache.first;
const bool past_start = (inclusive ? first->when >= start : first->when > start);
/* Earliest points is in range, return it */
- if (past_start && first->when < end) {
+ if (past_start) {
x = first->when;
y = first->value;
/* Move left of cache to this point
* (Optimize for immediate call this cycle within range) */
_search_cache.left = x;
- ++_search_cache.range.first;
+ ++_search_cache.first;
assert(x >= start);
- assert(x < end);
return true;
} else {
return false;
}
-
- /* No points in range */
+
+ /* No points in range */
} else {
return false;
}
/** Get the earliest time the line crosses an integer (Linear interpolation).
*
- * In other words: send out multiple events to interpolate the line
- * defined by its control points
* 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
-ControlList::rt_safe_earliest_event_linear_unlocked (double start, double end, double& x, double& y, bool inclusive) const
+ControlList::rt_safe_earliest_event_linear_unlocked (double start, double& x, double& y, bool inclusive) const
{
- cerr << "earliest_event(start: " << start << ", end: " << end << ", x: " << x << ", y: " << y << ", inclusive: " << inclusive << ")" << endl;
+ // cout << "earliest_event(start: " << start << ", x: " << x << ", y: " << y << ", inclusive: " << inclusive << ")" << endl;
const_iterator length_check_iter = _events.begin();
- if (_events.empty()) // 0 events
+ if (_events.empty()) { // 0 events
return false;
- else if (_events.end() == ++length_check_iter) // 1 event
- return rt_safe_earliest_event_discrete_unlocked(start, end, x, y, inclusive);
+ } else if (_events.end() == ++length_check_iter) { // 1 event
+ return rt_safe_earliest_event_discrete_unlocked (start, x, y, inclusive);
+ }
// Hack to avoid infinitely repeating the same event
- build_search_cache_if_necessary(start, end);
-
- pair<const_iterator,const_iterator> range = _search_cache.range;
+ build_search_cache_if_necessary (start);
- if (range.first != _events.end()) {
+ if (_search_cache.first != _events.end()) {
const ControlEvent* first = NULL;
const ControlEvent* next = NULL;
- /* No events past start (maybe?) */
- if (next && next->when < start)
- return false;
-
/* Step is after first */
- if (range.first == _events.begin() || (*range.first)->when == start) {
- first = *range.first;
- next = *(++range.first);
- ++_search_cache.range.first;
+ if (_search_cache.first == _events.begin() || (*_search_cache.first)->when <= start) {
+ first = *_search_cache.first;
+ ++_search_cache.first;
+ if (_search_cache.first == _events.end()) {
+ return false;
+ }
+ next = *_search_cache.first;
- /* Step is before first */
+ /* Step is before first */
} else {
- const_iterator prev = range.first;
+ const_iterator prev = _search_cache.first;
--prev;
first = *prev;
- next = *range.first;
+ next = *_search_cache.first;
}
-
+
if (inclusive && first->when == start) {
x = first->when;
y = first->value;
assert(x >= start);
return true;
}
-
+
if (fabs(first->value - next->value) <= 1) {
- if (next->when <= end && (next->when > start)) {
+ if (next->when > start) {
x = next->when;
y = next->value;
/* Move left of cache to this point
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
x = first->when + (y - first->value) / (double)slope;
}
- cerr << first->value << " @ " << first->when << " ... "
- << next->value << " @ " << next->when
- << " = " << y << " @ " << x << endl;
+ /*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) );
+ || (y <= first->value && y >= next->value) );
+
-
const bool past_start = (inclusive ? x >= start : x > start);
- if (past_start && x < end) {
+ if (past_start) {
/* Move left of cache to this point
* (Optimize for immediate call this cycle within range) */
_search_cache.left = x;
} else {
return false;
}
-
- /* No points in the future, so no steps (towards them) in the future */
+
+ /* No points in the future, so no steps (towards them) in the future */
} else {
return false;
}
}
-boost::shared_ptr<ControlList>
-ControlList::cut (iterator start, iterator end)
-{
- boost::shared_ptr<ControlList> nal = create (_parameter);
-
- {
- Glib::Mutex::Lock lm (_lock);
-
- for (iterator x = start; x != end; ) {
- iterator tmp;
-
- tmp = x;
- ++tmp;
-
- nal->_events.push_back (new ControlEvent (**x));
- _events.erase (x);
-
- reposition_for_rt_add (0);
-
- x = tmp;
- }
-
- mark_dirty ();
- }
-
- maybe_signal_changed ();
-
- return nal;
-}
+/** @param start Start position in model coordinates.
+ * @param end End position in model coordinates.
+ * @param op 0 = cut, 1 = copy, 2 = clear.
+ */
boost::shared_ptr<ControlList>
ControlList::cut_copy_clear (double start, double end, int op)
{
boost::shared_ptr<ControlList> nal = create (_parameter);
iterator s, e;
ControlEvent cp (start, 0.0);
- bool changed = false;
-
+
{
- Glib::Mutex::Lock lm (_lock);
+ Glib::Threads::Mutex::Lock lm (_lock);
+
+ /* first, determine s & e, two iterators that define the range of points
+ affected by this operation
+ */
if ((s = lower_bound (_events.begin(), _events.end(), &cp, time_comparator)) == _events.end()) {
return nal;
}
+ /* and the last that is at or after `end' */
cp.when = end;
e = upper_bound (_events.begin(), _events.end(), &cp, time_comparator);
- if (op != 2 && (*s)->when != start) {
- nal->_events.push_back (new ControlEvent (0, unlocked_eval (start)));
+
+ /* if "start" isn't the location of an existing point,
+ evaluate the curve to get a value for the start. Add a point to
+ both the existing event list, and if its not a "clear" operation,
+ to the copy ("nal") as well.
+
+ Note that the time positions of the points in each list are different
+ because we want the copy ("nal") to have a zero time reference.
+ */
+
+
+ /* before we begin any cut/clear operations, get the value of the curve
+ at "end".
+ */
+
+ double end_value = unlocked_eval (end);
+
+ if ((*s)->when != start) {
+
+ double val = unlocked_eval (start);
+
+ if (op == 0) { // cut
+ if (start > _events.front()->when) {
+ _events.insert (s, (new ControlEvent (start, val)));
+ }
+ }
+
+ if (op != 2) { // ! clear
+ nal->_events.push_back (new ControlEvent (0, val));
+ }
}
for (iterator x = s; x != e; ) {
- iterator tmp;
-
- tmp = x;
- ++tmp;
- changed = true;
-
/* adjust new points to be relative to start, which
has been set to zero.
*/
-
+
if (op != 2) {
nal->_events.push_back (new ControlEvent ((*x)->when - start, (*x)->value));
}
if (op != 1) {
- _events.erase (x);
+ x = _events.erase (x);
+ } else {
+ ++x;
}
-
- x = tmp;
}
- if (op != 2 && nal->_events.back()->when != end - start) {
- nal->_events.push_back (new ControlEvent (end - start, unlocked_eval (end)));
- }
+ if (e == _events.end() || (*e)->when != end) {
- if (changed) {
- reposition_for_rt_add (0);
+ /* only add a boundary point if there is a point after "end"
+ */
+
+ if (op == 0 && (e != _events.end() && end < (*e)->when)) { // cut
+ _events.insert (e, new ControlEvent (end, end_value));
+ }
+
+ if (op != 2 && (e != _events.end() && end < (*e)->when)) { // cut/copy
+ nal->_events.push_back (new ControlEvent (end - start, end_value));
+ }
}
+ unlocked_invalidate_insert_iterator ();
mark_dirty ();
}
- maybe_signal_changed ();
-
- return nal;
-
-}
-
-boost::shared_ptr<ControlList>
-ControlList::copy (iterator start, iterator end)
-{
- boost::shared_ptr<ControlList> nal = create (_parameter);
-
- {
- Glib::Mutex::Lock lm (_lock);
-
- for (iterator x = start; x != end; ) {
- iterator tmp;
-
- tmp = x;
- ++tmp;
-
- nal->_events.push_back (new ControlEvent (**x));
-
- x = tmp;
- }
+ if (op != 1) {
+ maybe_signal_changed ();
}
return nal;
}
+
boost::shared_ptr<ControlList>
ControlList::cut (double start, double end)
{
void
ControlList::clear (double start, double end)
{
- (void) cut_copy_clear (start, end, 2);
+ cut_copy_clear (start, end, 2);
}
+/** @param pos Position in model coordinates */
bool
-ControlList::paste (ControlList& alist, double pos, float times)
+ControlList::paste (ControlList& alist, double pos, float /*times*/)
{
if (alist._events.empty()) {
return false;
}
{
- Glib::Mutex::Lock lm (_lock);
+ Glib::Threads::Mutex::Lock lm (_lock);
iterator where;
iterator prev;
double end = 0;
_events.insert (where, new ControlEvent( (*i)->when+pos,( *i)->value));
end = (*i)->when + pos;
}
-
-
- /* move all points after the insertion along the timeline by
+
+
+ /* move all points after the insertion along the timeline by
the correct amount.
*/
}
}
- reposition_for_rt_add (0);
+ unlocked_invalidate_insert_iterator ();
mark_dirty ();
}
return true;
}
-/** Move automation around according to a list of region movements */
-void
-ControlList::move_ranges (RangeMoveList const & movements)
+/** Move automation around according to a list of region movements.
+ * @param return true if anything was changed, otherwise false (ie nothing needed changing)
+ */
+bool
+ControlList::move_ranges (const list< RangeMove<double> >& movements)
{
+ typedef list< RangeMove<double> > RangeMoveList;
+
{
- Glib::Mutex::Lock lm (_lock);
+ Glib::Threads::Mutex::Lock lm (_lock);
/* a copy of the events list before we started moving stuff around */
EventList old_events = _events;
/* clear the source and destination ranges in the new list */
+ bool things_erased = false;
for (RangeMoveList::const_iterator i = movements.begin (); i != movements.end (); ++i) {
- erase_range_internal (i->from, i->from + i->length, _events);
- erase_range_internal (i->to, i->to + i->length, _events);
+ if (erase_range_internal (i->from, i->from + i->length, _events)) {
+ things_erased = true;
+ }
+ if (erase_range_internal (i->to, i->to + i->length, _events)) {
+ things_erased = true;
+ }
+ }
+
+ /* if nothing was erased, there is nothing to do */
+ if (!things_erased) {
+ return false;
}
/* copy the events into the new list */
for (RangeMoveList::const_iterator i = movements.begin (); i != movements.end (); ++i) {
iterator j = old_events.begin ();
- EventTime const limit = i->from + i->length;
- EventTime const dx = i->to - i->from;
+ const double limit = i->from + i->length;
+ const double dx = i->to - i->from;
while (j != old_events.end () && (*j)->when <= limit) {
if ((*j)->when >= i->from) {
ControlEvent* ev = new ControlEvent (**j);
if (!_frozen) {
_events.sort (event_time_less_than);
+ unlocked_invalidate_insert_iterator ();
} else {
_sort_pending = true;
}
- reposition_for_rt_add (0);
mark_dirty ();
}
maybe_signal_changed ();
+ return true;
+}
+
+void
+ControlList::set_interpolation (InterpolationStyle s)
+{
+ if (_interpolation == s) {
+ return;
+ }
+
+ _interpolation = s;
+ InterpolationChanged (s); /* EMIT SIGNAL */
+}
+
+void
+ControlList::set_thinning_factor (double v)
+{
+ _thinning_factor = v;
+}
+
+bool
+ControlList::operator!= (ControlList const & other) const
+{
+ if (_events.size() != other._events.size()) {
+ return true;
+ }
+
+ EventList::const_iterator i = _events.begin ();
+ EventList::const_iterator j = other._events.begin ();
+
+ while (i != _events.end() && (*i)->when == (*j)->when && (*i)->value == (*j)->value) {
+ ++i;
+ ++j;
+ }
+
+ if (i != _events.end ()) {
+ return true;
+ }
+
+ return (
+ _parameter != other._parameter ||
+ _interpolation != other._interpolation ||
+ _min_yval != other._min_yval ||
+ _max_yval != other._max_yval ||
+ _default_value != other._default_value
+ );
+}
+
+void
+ControlList::dump (ostream& o)
+{
+ /* NOT LOCKED ... for debugging only */
+
+ for (EventList::iterator x = _events.begin(); x != _events.end(); ++x) {
+ o << (*x)->value << " @ " << (*x)->when << endl;
+ }
}
} // namespace Evoral