diff options
Diffstat (limited to 'libs/evoral/src/ControlList.cc')
-rw-r--r-- | libs/evoral/src/ControlList.cc | 2076 |
1 files changed, 2076 insertions, 0 deletions
diff --git a/libs/evoral/src/ControlList.cc b/libs/evoral/src/ControlList.cc new file mode 100644 index 0000000000..897a689dfc --- /dev/null +++ b/libs/evoral/src/ControlList.cc @@ -0,0 +1,2076 @@ +/* + * Copyright (C) 2008-2009 Hans Baier <hansfbaier@googlemail.com> + * Copyright (C) 2008-2012 Carl Hetherington <carl@carlh.net> + * Copyright (C) 2008-2015 David Robillard <d@drobilla.net> + * Copyright (C) 2010-2017 Paul Davis <paul@linuxaudiosystems.com> + * Copyright (C) 2014-2018 Robin Gareus <robin@gareus.org> + * Copyright (C) 2014 Ben Loftis <ben@harrisonconsoles.com> + * Copyright (C) 2015 Nick Mainsbridge <mainsbridge@gmail.com> + * + * This program 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. + * + * This program 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 more 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 Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include <cmath> + +#ifdef COMPILER_MSVC +#include <float.h> + +// 'std::isnan()' is not available in MSVC. +#define isnan_local(val) (bool)_isnan((double)val) +#else +#define isnan_local std::isnan +#endif + +#define GUARD_POINT_DELTA 64 + +#include <cassert> +#include <cmath> +#include <iostream> +#include <utility> + +#include "evoral/ControlList.h" +#include "evoral/Curve.h" +#include "evoral/ParameterDescriptor.h" +#include "evoral/TypeMap.h" +#include "evoral/types.h" + +#include "pbd/control_math.h" +#include "pbd/compose.h" +#include "pbd/debug.h" + +using namespace std; +using namespace PBD; + +namespace Evoral { + +inline bool event_time_less_than (ControlEvent* a, ControlEvent* b) +{ + return a->when < b->when; +} + +ControlList::ControlList (const Parameter& id, const ParameterDescriptor& desc) + : _parameter(id) + , _desc(desc) + , _interpolation (default_interpolation ()) + , _curve(0) +{ + _frozen = 0; + _changed_when_thawed = false; + _lookup_cache.left = -1; + _lookup_cache.range.first = _events.end(); + _lookup_cache.range.second = _events.end(); + _search_cache.left = -1; + _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) + , _desc(other._desc) + , _interpolation(other._interpolation) + , _curve(0) +{ + _frozen = 0; + _changed_when_thawed = false; + _lookup_cache.range.first = _events.end(); + _lookup_cache.range.second = _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(); + + // XXX copy_events() emits Dirty, but this is just assignment copy/construction + copy_events (other); +} + +ControlList::ControlList (const ControlList& other, double start, double end) + : _parameter(other._parameter) + , _desc(other._desc) + , _interpolation(other._interpolation) + , _curve(0) +{ + _frozen = 0; + _changed_when_thawed = false; + _lookup_cache.range.first = _events.end(); + _lookup_cache.range.second = _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()) { + // XXX copy_events() emits Dirty, but this is just assignment copy/construction + copy_events (*(section.get())); + } + + new_write_pass = true; + _in_write_pass = false; + did_write_during_pass = false; + insert_position = -1; + most_recent_insert_iterator = _events.end(); + + mark_dirty (); +} + +ControlList::~ControlList() +{ + for (EventList::iterator x = _events.begin(); x != _events.end(); ++x) { + delete (*x); + } + _events.clear (); + + delete _curve; +} + +boost::shared_ptr<ControlList> +ControlList::create(const Parameter& id, const ParameterDescriptor& desc) +{ + return boost::shared_ptr<ControlList>(new ControlList(id, desc)); +} + +bool +ControlList::operator== (const ControlList& other) +{ + return _events == other._events; +} + +ControlList& +ControlList::operator= (const ControlList& other) +{ + if (this != &other) { + /* list should be frozen before assignment */ + assert (_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; + + copy_events (other); + } + + return *this; +} + +void +ControlList::copy_events (const ControlList& other) +{ + { + Glib::Threads::RWLock::WriterLock lm (_lock); + for (EventList::iterator x = _events.begin(); x != _events.end(); ++x) { + 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)); + } + 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; +} + +ControlList::InterpolationStyle +ControlList::default_interpolation () const +{ + if (_desc.toggled) { + return Discrete; + } else if (_desc.logarithmic) { + return Logarithmic; + } + return Linear; +} + +void +ControlList::maybe_signal_changed () +{ + mark_dirty (); + + if (_frozen) { + _changed_when_thawed = true; + } +} + +void +ControlList::clear () +{ + { + Glib::Threads::RWLock::WriterLock lm (_lock); + for (EventList::iterator x = _events.begin(); x != _events.end(); ++x) { + delete (*x); + } + _events.clear (); + unlocked_invalidate_insert_iterator (); + mark_dirty (); + } + + maybe_signal_changed (); +} + +void +ControlList::x_scale (double factor) +{ + Glib::Threads::RWLock::WriterLock lm (_lock); + _x_scale (factor); +} + +bool +ControlList::extend_to (double when) +{ + Glib::Threads::RWLock::WriterLock lm (_lock); + if (_events.empty() || _events.back()->when == when) { + return false; + } + double factor = when / _events.back()->when; + _x_scale (factor); + return true; +} + +void +ControlList::y_transform (boost::function<double(double)> callback) +{ + { + Glib::Threads::RWLock::WriterLock lm (_lock); + for (iterator i = _events.begin(); i != _events.end(); ++i) { + (*i)->value = callback ((*i)->value); + } + mark_dirty (); + } + maybe_signal_changed (); +} + +void +ControlList::list_merge (ControlList const& other, boost::function<double(double, double)> callback) +{ + { + Glib::Threads::RWLock::WriterLock lm (_lock); + EventList nel; + /* First scale existing events, copy into a new list. + * The original list is needed later to interpolate + * for new events only present in the master list. + */ + for (iterator i = _events.begin(); i != _events.end(); ++i) { + float val = callback ((*i)->value, other.eval ((*i)->when)); + nel.push_back (new ControlEvent ((*i)->when , val)); + } + /* Now add events which are only present in the master-list. */ + const EventList& evl (other.events()); + for (const_iterator i = evl.begin(); i != evl.end(); ++i) { + bool found = false; + // TODO: optimize, remember last matching iterator (lists are sorted) + for (iterator j = _events.begin(); j != _events.end(); ++j) { + if ((*i)->when == (*j)->when) { + found = true; + break; + } + } + /* skip events that have already been merge in the first pass */ + if (found) { + continue; + } + float val = callback (unlocked_eval ((*i)->when), (*i)->value); + nel.push_back (new ControlEvent ((*i)->when, val)); + } + nel.sort (event_time_less_than); + + for (EventList::iterator x = _events.begin(); x != _events.end(); ++x) { + delete (*x); + } + _events.clear (); + _events = nel; + + unlocked_remove_duplicates (); + unlocked_invalidate_insert_iterator (); + mark_dirty (); + } + maybe_signal_changed (); +} + +void +ControlList::_x_scale (double factor) +{ + for (iterator i = _events.begin(); i != _events.end(); ++i) { + (*i)->when *= factor; + } + + mark_dirty (); +} + +struct ControlEventTimeComparator { + bool operator() (ControlEvent* a, ControlEvent* b) { + return a->when < b->when; + } +}; + +void +ControlList::thin (double thinning_factor) +{ + if (thinning_factor == 0.0 || _desc.toggled) { + return; + } + + assert (is_sorted ()); + + bool changed = false; + + { + Glib::Threads::RWLock::WriterLock 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())); + + if (changed) { + unlocked_invalidate_insert_iterator (); + mark_dirty (); + } + } + + if (changed) { + maybe_signal_changed (); + } +} + +void +ControlList::fast_simple_add (double when, double value) +{ + Glib::Threads::RWLock::WriterLock lm (_lock); + /* to be used only for loading pre-sorted data from saved state */ + _events.insert (_events.end(), new ControlEvent (when, value)); + + mark_dirty (); + if (_frozen) { + _sort_pending = true; + } +} + +void +ControlList::invalidate_insert_iterator () +{ + Glib::Threads::RWLock::WriterLock lm (_lock); + unlocked_invalidate_insert_iterator (); +} + +void +ControlList::unlocked_invalidate_insert_iterator () +{ + most_recent_insert_iterator = _events.end(); +} + +void +ControlList::unlocked_remove_duplicates () +{ + if (_events.size() < 2) { + return; + } + iterator i = _events.begin(); + iterator prev = i++; + while (i != _events.end()) { + if ((*prev)->when == (*i)->when && (*prev)->value == (*i)->value) { + i = _events.erase (i); + } else { + ++prev; + ++i; + } + } +} + +void +ControlList::start_write_pass (double when) +{ + Glib::Threads::RWLock::WriterLock lm (_lock); + + DEBUG_TRACE (DEBUG::ControlList, string_compose ("%1: setup write pass @ %2\n", this, when)); + + insert_position = when; + + /* 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). + */ + + unlocked_invalidate_insert_iterator (); + + /* except if we're already in an active write-pass. + * + * invalid iterator == end() the iterator is set to the correct + * position in ControlList::add IFF (_in_write_pass && new_write_pass) + */ + if (_in_write_pass && !new_write_pass) { +#if 1 + add_guard_point (when, 0); // also sets most_recent_insert_iterator +#else + const ControlEvent cp (when, 0.0); + most_recent_insert_iterator = lower_bound (_events.begin(), _events.end(), &cp, time_comparator); +#endif + } +} + +void +ControlList::write_pass_finished (double /*when*/, double thinning_factor) +{ + DEBUG_TRACE (DEBUG::ControlList, "write pass finished\n"); + + if (did_write_during_pass) { + thin (thinning_factor); + did_write_during_pass = false; + } + new_write_pass = true; + _in_write_pass = false; +} + +void +ControlList::set_in_write_pass (bool yn, bool add_point, double when) +{ + DEBUG_TRACE (DEBUG::ControlList, string_compose ("now in write pass @ %1, add point ? %2\n", when, add_point)); + + _in_write_pass = yn; + + if (yn && add_point) { + Glib::Threads::RWLock::WriterLock lm (_lock); + add_guard_point (when, 0); + } +} + +void +ControlList::add_guard_point (double when, double offset) +{ + // caller needs to hold writer-lock + if (offset < 0 && when < offset) { + return; + } + assert (offset <= 0); + + if (offset != 0) { + /* check if there are points between when + offset .. when */ + ControlEvent cp (when + offset, 0.0); + iterator s; + iterator e; + if ((s = lower_bound (_events.begin(), _events.end(), &cp, time_comparator)) != _events.end()) { + cp.when = when; + e = lower_bound (_events.begin(), _events.end(), &cp, time_comparator); + if (s != e) { + DEBUG_TRACE (DEBUG::ControlList, string_compose ("@%1 add_guard_point, none added, found event between %2 and %3\n", this, when - offset, when)); + return; + } + } + } + + /* don't do this again till the next write pass, + * unless we're not in a write-pass (transport stopped) + */ + if (_in_write_pass && new_write_pass) { + WritePassStarted (); /* EMIT SIGNAL w/WriteLock */ + new_write_pass = false; + } + + when += offset; + + ControlEvent cp (when, 0.0); + most_recent_insert_iterator = lower_bound (_events.begin(), _events.end(), &cp, time_comparator); + + double eval_value = unlocked_eval (when); + + if (most_recent_insert_iterator == _events.end()) { + + 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; + } +} + +bool +ControlList::in_write_pass () const +{ + return _in_write_pass; +} + +bool +ControlList::editor_add (double when, double value, bool with_guard) +{ + /* this is for making changes from a graphical line editor */ + { + Glib::Threads::RWLock::WriterLock lm (_lock); + + ControlEvent cp (when, 0.0f); + iterator i = lower_bound (_events.begin(), _events.end(), &cp, time_comparator); + + if (i != _events.end () && (*i)->when == when) { + return false; + } + + /* clamp new value to allowed range */ + value = std::min ((double)_desc.upper, std::max ((double)_desc.lower, value)); + + if (_events.empty()) { + + /* as long as the point we're adding is not at zero, + * add an "anchor" point there. + */ + + if (when >= 1) { + _events.insert (_events.end(), new ControlEvent (0, value)); + DEBUG_TRACE (DEBUG::ControlList, string_compose ("@%1 added value %2 at zero\n", this, value)); + } + } + + insert_position = when; + if (with_guard) { + add_guard_point (when, -GUARD_POINT_DELTA); + maybe_add_insert_guard (when); + i = lower_bound (_events.begin(), _events.end(), &cp, time_comparator); + } + + iterator result; + DEBUG_TRACE (DEBUG::ControlList, string_compose ("editor_add: actually add when= %1 value= %2\n", when, value)); + result = _events.insert (i, new ControlEvent (when, value)); + + if (i == result) { + return false; + } + + mark_dirty (); + } + maybe_signal_changed (); + + return true; +} + +void +ControlList::maybe_add_insert_guard (double when) +{ + // caller needs to hold writer-lock + if (most_recent_insert_iterator != _events.end()) { + if ((*most_recent_insert_iterator)->when - when > GUARD_POINT_DELTA) { + /* Next control point is some distance from where our new point is + going to go, so 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 + GUARD_POINT_DELTA, (*most_recent_insert_iterator)->value)); + + DEBUG_TRACE (DEBUG::ControlList, string_compose ("@%1 added insert guard point @ %2 = %3\n", + this, when + GUARD_POINT_DELTA, + (*most_recent_insert_iterator)->value)); + } + } +} + +/** If we would just be adding to a straight line, move the previous point instead. */ +bool +ControlList::maybe_insert_straight_line (double when, double value) +{ + // caller needs to hold writer-lock + if (_events.empty()) { + return false; + } + + if (_events.back()->value == value) { + // Point b at the final point, which we know exists + EventList::iterator b = _events.end(); + --b; + if (b == _events.begin()) { + return false; // No previous point + } + + // Check the previous point's value + --b; + if ((*b)->value == value) { + /* At least two points with the exact same value (straight + line), just move the final point to the new time. */ + _events.back()->when = when; + DEBUG_TRACE (DEBUG::ControlList, string_compose ("final value of %1 moved to %2\n", value, when)); + return true; + } + } + return false; +} + +ControlList::iterator +ControlList::erase_from_iterator_to (iterator iter, double when) +{ + // caller needs to hold writer-lock + while (iter != _events.end()) { + if ((*iter)->when < when) { + DEBUG_TRACE (DEBUG::ControlList, string_compose ("@%1 erase existing @ %2\n", this, (*iter)->when)); + delete *iter; + iter = _events.erase (iter); + continue; + } else if ((*iter)->when >= when) { + break; + } + ++iter; + } + return iter; +} + +/* this is for making changes from some kind of user interface or + * control surface (GUI, MIDI, OSC etc) + */ +void +ControlList::add (double when, double value, bool with_guards, bool with_initial) +{ + /* clamp new value to allowed range */ + value = std::min ((double)_desc.upper, std::max ((double)_desc.lower, value)); + + DEBUG_TRACE (DEBUG::ControlList, + string_compose ("@%1 add %2 at %3 guards = %4 write pass = %5 (new? %6) at end? %7\n", + this, value, when, with_guards, _in_write_pass, new_write_pass, + (most_recent_insert_iterator == _events.end()))); + { + Glib::Threads::RWLock::WriterLock lm (_lock); + ControlEvent cp (when, 0.0f); + iterator insertion_point; + + if (_events.empty() && with_initial) { + + /* empty: add an "anchor" point if the point we're adding past time 0 */ + + if (when >= 1) { + if (_desc.toggled) { + const double opp_val = ((value < 0.5) ? 1.0 : 0.0); + _events.insert (_events.end(), new ControlEvent (0, opp_val)); + DEBUG_TRACE (DEBUG::ControlList, string_compose ("@%1 added toggled value %2 at zero\n", this, opp_val)); + + } else { + _events.insert (_events.end(), new ControlEvent (0, value)); + DEBUG_TRACE (DEBUG::ControlList, string_compose ("@%1 added default value %2 at zero\n", this, _desc.normal)); + } + } + } + + if (_in_write_pass && new_write_pass) { + + /* first write in a write pass: add guard point if requested */ + + if (with_guards) { + add_guard_point (insert_position, 0); + did_write_during_pass = true; + } else { + /* not adding a guard, but we need to set iterator appropriately */ + const ControlEvent cp (when, 0.0); + most_recent_insert_iterator = lower_bound (_events.begin(), _events.end(), &cp, time_comparator); + } + WritePassStarted (); /* EMIT SIGNAL w/WriteLock */ + new_write_pass = false; + + } else if (_in_write_pass && + (most_recent_insert_iterator == _events.end() || when > (*most_recent_insert_iterator)->when)) { + + /* in write pass: erase from most recent insert to now */ + + if (most_recent_insert_iterator != _events.end()) { + /* advance to avoid deleting the last inserted point itself. */ + ++most_recent_insert_iterator; + } + + if (with_guards) { + most_recent_insert_iterator = erase_from_iterator_to (most_recent_insert_iterator, when + GUARD_POINT_DELTA); + maybe_add_insert_guard (when); + } else { + most_recent_insert_iterator = erase_from_iterator_to(most_recent_insert_iterator, when); + } + + } else if (!_in_write_pass) { + + /* not in a write pass: 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); + } + + /* 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)); + + const bool done = maybe_insert_straight_line (when, value); + if (!done) { + _events.push_back (new ControlEvent (when, value)); + DEBUG_TRACE (DEBUG::ControlList, string_compose ("\tactually appended, size now %1\n", _events.size())); + } + + 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 add a guard point + * before it if needed then reset the value of the point. + */ + + (*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 (_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 for possible straight line here until maybe_insert_straight_line () handles the insert iterator properly*/ + if (most_recent_insert_iterator != _events.begin ()) { + bool have_point2 = false; + --most_recent_insert_iterator; + const bool have_point1 = (*most_recent_insert_iterator)->value == value; + + if (most_recent_insert_iterator != _events.begin ()) { + --most_recent_insert_iterator; + have_point2 = (*most_recent_insert_iterator)->value == value; + ++most_recent_insert_iterator; + } + + if (have_point1 && have_point2) { + DEBUG_TRACE (DEBUG::ControlList, string_compose ("@%1 no change: move existing at %3 to %2\n", this, when, (*most_recent_insert_iterator)->when)); + (*most_recent_insert_iterator)->when = when; + done = true; + } else { + ++most_recent_insert_iterator; + } + } + + /* if the transport is stopped, add guard points */ + if (!done && !_in_write_pass) { + add_guard_point (when, -GUARD_POINT_DELTA); + maybe_add_insert_guard (when); + } else if (with_guards) { + maybe_add_insert_guard (when); + } + + 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())); + most_recent_insert_iterator = x; + } + } + + mark_dirty (); + } + + maybe_signal_changed (); +} + +void +ControlList::erase (iterator i) +{ + { + Glib::Threads::RWLock::WriterLock lm (_lock); + if (most_recent_insert_iterator == i) { + unlocked_invalidate_insert_iterator (); + } + _events.erase (i); + mark_dirty (); + } + maybe_signal_changed (); +} + +void +ControlList::erase (iterator start, iterator end) +{ + { + Glib::Threads::RWLock::WriterLock lm (_lock); + _events.erase (start, end); + unlocked_invalidate_insert_iterator (); + mark_dirty (); + } + maybe_signal_changed (); +} + +/** Erase the first event which matches the given time and value */ +void +ControlList::erase (double when, double value) +{ + { + Glib::Threads::RWLock::WriterLock lm (_lock); + + iterator i = begin (); + while (i != end() && ((*i)->when != when || (*i)->value != value)) { + ++i; + } + + if (i != end ()) { + _events.erase (i); + if (most_recent_insert_iterator == i) { + unlocked_invalidate_insert_iterator (); + } + } + + mark_dirty (); + } + + maybe_signal_changed (); +} + +void +ControlList::erase_range (double start, double endt) +{ + bool erased = false; + + { + Glib::Threads::RWLock::WriterLock lm (_lock); + erased = erase_range_internal (start, endt, _events); + + if (erased) { + mark_dirty (); + } + + } + + if (erased) { + maybe_signal_changed (); + } +} + +bool +ControlList::erase_range_internal (double start, double endt, EventList & events) +{ + bool erased = false; + ControlEvent cp (start, 0.0f); + iterator s; + iterator e; + + if ((s = lower_bound (events.begin(), events.end(), &cp, time_comparator)) != events.end()) { + cp.when = endt; + e = upper_bound (events.begin(), events.end(), &cp, time_comparator); + events.erase (s, e); + if (s != e) { + unlocked_invalidate_insert_iterator (); + erased = true; + } + } + + return erased; +} + +void +ControlList::slide (iterator before, double distance) +{ + { + Glib::Threads::RWLock::WriterLock 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::RWLock::WriterLock lm (_lock); + double v0, v1; + if (frames < 0) { + /* Route::shift () with negative shift is used + * for "remove time". The time [pos.. pos-frames] is removed. + * and everyhing after, moved backwards. + */ + v0 = unlocked_eval (pos); + v1 = unlocked_eval (pos - frames); + erase_range_internal (pos, pos - frames, _events); + } else { + v0 = v1 = unlocked_eval (pos); + } + + bool dst_guard_exists = false; + + for (iterator i = _events.begin(); i != _events.end(); ++i) { + if ((*i)->when == pos) { + dst_guard_exists = true; + } + if ((*i)->when >= pos) { + (*i)->when += frames; + } + } + + /* add guard-points to retain shape, if needed */ + if (frames > 0) { + ControlEvent cp (pos, 0.0); + iterator s = lower_bound (_events.begin(), _events.end(), &cp, time_comparator); + if (s != _events.end ()) { + _events.insert (s, new ControlEvent (pos, v0)); + } + pos += frames; + } else if (frames < 0 && pos > 0) { + ControlEvent cp (pos - 1, 0.0); + iterator s = lower_bound (_events.begin(), _events.end(), &cp, time_comparator); + if (s != _events.end ()) { + _events.insert (s, new ControlEvent (pos - 1, v0)); + } + } + if (!dst_guard_exists) { + ControlEvent cp (pos, 0.0); + iterator s = lower_bound (_events.begin(), _events.end(), &cp, time_comparator); + _events.insert (s, new ControlEvent (pos, s == _events.end () ? v0 : v1)); + } + + + mark_dirty (); + } + + maybe_signal_changed (); +} + +void +ControlList::modify (iterator iter, double when, double val) +{ + /* note: we assume higher level logic is in place to avoid this + * reordering the time-order of control events in the list. ie. all + * points after *iter are later than when. + */ + + /* catch possible float/double rounding errors from higher levels */ + val = std::min ((double)_desc.upper, std::max ((double)_desc.lower, val)); + + { + Glib::Threads::RWLock::WriterLock lm (_lock); + + (*iter)->when = when; + (*iter)->value = val; + if (isnan_local (val)) { + abort (); + } + + if (!_frozen) { + _events.sort (event_time_less_than); + unlocked_remove_duplicates (); + unlocked_invalidate_insert_iterator (); + } else { + _sort_pending = true; + } + + mark_dirty (); + } + + maybe_signal_changed (); +} + +std::pair<ControlList::iterator,ControlList::iterator> +ControlList::control_points_adjacent (double xval) +{ + Glib::Threads::RWLock::ReaderLock lm (_lock); + iterator i; + 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, time_comparator); i != _events.end(); ++i) { + + if (ret.first == _events.end()) { + if ((*i)->when >= xval) { + if (i != _events.begin()) { + ret.first = i; + --ret.first; + } else { + return ret; + } + } + } + + if ((*i)->when > xval) { + ret.second = i; + break; + } + } + + return ret; +} + +void +ControlList::freeze () +{ + _frozen++; +} + +void +ControlList::thaw () +{ + assert(_frozen > 0); + + if (--_frozen > 0) { + return; + } + + { + Glib::Threads::RWLock::WriterLock lm (_lock); + + if (_sort_pending) { + _events.sort (event_time_less_than); + unlocked_remove_duplicates (); + unlocked_invalidate_insert_iterator (); + _sort_pending = false; + } + } +} + +void +ControlList::mark_dirty () const +{ + _lookup_cache.left = -1; + _lookup_cache.range.first = _events.end(); + _lookup_cache.range.second = _events.end(); + _search_cache.left = -1; + _search_cache.first = _events.end(); + + if (_curve) { + _curve->mark_dirty(); + } + + Dirty (); /* EMIT SIGNAL */ +} + +void +ControlList::truncate_end (double last_coordinate) +{ + { + Glib::Threads::RWLock::WriterLock lm (_lock); + ControlEvent cp (last_coordinate, 0); + ControlList::reverse_iterator i; + double last_val; + + if (_events.empty()) { + return; + } + + if (last_coordinate == _events.back()->when) { + return; + } + + if (last_coordinate > _events.back()->when) { + + /* extending end: + */ + + iterator foo = _events.begin(); + bool lessthantwo; + + if (foo == _events.end()) { + lessthantwo = true; + } else if (++foo == _events.end()) { + lessthantwo = true; + } else { + lessthantwo = false; + } + + if (lessthantwo) { + /* less than 2 points: add a new point */ + _events.push_back (new ControlEvent (last_coordinate, _events.back()->value)); + } else { + + /* more than 2 points: check to see if the last 2 values + are equal. if so, just move the position of the + last point. otherwise, add a new point. + */ + + 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 { + _events.push_back (new ControlEvent (last_coordinate, _events.back()->value)); + } + } + + } else { + + /* shortening end */ + + last_val = unlocked_eval (last_coordinate); + last_val = max ((double) _desc.lower, last_val); + last_val = min ((double) _desc.upper, 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; + } + + unlocked_invalidate_insert_iterator (); + mark_dirty(); + } + + maybe_signal_changed (); +} + +void +ControlList::truncate_start (double overall_length) +{ + { + Glib::Threads::RWLock::WriterLock lm (_lock); + iterator i; + double first_legal_value; + double first_legal_coordinate; + + if (_events.empty()) { + /* nothing to truncate */ + return; + } else 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; + uint32_t np; + + for (np = 0, i = _events.begin(); i != _events.end(); ++i, ++np) { + (*i)->when += shift; + } + + if (np < 2) { + + /* less than 2 points: add a new point */ + _events.push_front (new ControlEvent (0, _events.front()->value)); + + } else { + + /* more than 2 points: check to see if the first 2 values + are equal. if so, just move the position of the + first point. otherwise, add a new point. + */ + + 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 { + /* leave non-flat segment in place, add a new leading point. */ + _events.push_front (new ControlEvent (0, _events.front()->value)); + } + } + + } else { + + /* shrinking at front */ + + first_legal_coordinate = _events.back()->when - overall_length; + first_legal_value = unlocked_eval (first_legal_coordinate); + first_legal_value = max ((double)_desc.lower, first_legal_value); + first_legal_value = min ((double)_desc.upper, 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)); + } + + unlocked_invalidate_insert_iterator (); + mark_dirty(); + } + + maybe_signal_changed (); +} + +double +ControlList::unlocked_eval (double x) const +{ + pair<EventList::iterator,EventList::iterator> range; + int32_t npoints; + double lpos, upos; + double lval, uval; + double fraction; + + const_iterator length_check_iter = _events.begin(); + for (npoints = 0; npoints < 4; ++npoints, ++length_check_iter) { + if (length_check_iter == _events.end()) { + break; + } + } + + switch (npoints) { + case 0: + return _desc.normal; + + case 1: + 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; + } + + lpos = _events.front()->when; + lval = _events.front()->value; + upos = _events.back()->when; + uval = _events.back()->value; + + fraction = (double) (x - lpos) / (double) (upos - lpos); + + 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) { + return _events.back()->value; + } else if (x <= _events.front()->when) { + return _events.front()->value; + } + + return multipoint_eval (x); + } + + abort(); /*NOTREACHED*/ /* stupid gcc */ + return _desc.normal; +} + +double +ControlList::multipoint_eval (double x) const +{ + double upos, lpos; + double uval, lval; + double fraction; + + /* "Stepped" lookup (no interpolation) */ + /* FIXME: no cache. significant? */ + if (_interpolation == Discrete) { + const ControlEvent cp (x, 0); + EventList::const_iterator i = lower_bound (_events.begin(), _events.end(), &cp, time_comparator); + + // shouldn't have made it to multipoint_eval + assert(i != _events.end()); + + if (i == _events.begin() || (*i)->when == x) + return (*i)->value; + else + return (*(--i))->value; + } + + /* Only do the range lookup if x is in a different range than last time + * this was called (or if the lookup cache has been marked "dirty" (left<0) */ + if ((_lookup_cache.left < 0) || + ((_lookup_cache.left > x) || + (_lookup_cache.range.first == _events.end()) || + ((*_lookup_cache.range.second)->when < x))) { + + 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) { + + /* x does not exist within the list as a control point */ + + _lookup_cache.left = x; + + if (range.first != _events.begin()) { + --range.first; + lpos = (*range.first)->when; + lval = (*range.first)->value; + } else { + /* we're before the first point */ + // return _default_value; + return _events.front()->value; + } + + if (range.second == _events.end()) { + /* we're after the last point */ + return _events.back()->value; + } + + upos = (*range.second)->when; + uval = (*range.second)->value; + + fraction = (double) (x - lpos) / (double) (upos - lpos); + + 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 */ + _lookup_cache.left = -1; + return (*range.first)->value; +} + +void +ControlList::build_search_cache_if_necessary (double start) const +{ + if (_events.empty()) { + /* Empty, nothing to cache, move to end. */ + _search_cache.first = _events.end(); + _search_cache.left = 0; + return; + } else if ((_search_cache.left < 0) || (_search_cache.left > start)) { + /* Marked dirty (left < 0), or we're too far forward, re-search. */ + + const ControlEvent start_point (start, 0); + + _search_cache.first = lower_bound (_events.begin(), _events.end(), &start_point, time_comparator); + _search_cache.left = start; + } + + /* We now have a search cache that is not too far right, but it may be too + far left and need to be advanced. */ + + while (_search_cache.first != end() && (*_search_cache.first)->when < start) { + ++_search_cache.first; + } + _search_cache.left = start; +} + +/** 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. + * + * \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 (double start, double& x, double& y, bool inclusive) const +{ + // FIXME: It would be nice if this was unnecessary.. + Glib::Threads::RWLock::ReaderLock lm(_lock, Glib::Threads::TRY_LOCK); + if (!lm.locked()) { + return false; + } + + return rt_safe_earliest_event_unlocked (start, x, y, inclusive); +} + + +/** 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. + * + * \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_unlocked (double start, double& x, double& y, bool inclusive) const +{ + 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 after \a start without 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 +ControlList::rt_safe_earliest_event_discrete_unlocked (double start, double& x, double& y, bool inclusive) const +{ + build_search_cache_if_necessary (start); + + 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) { + + 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.first; + + assert(x >= start); + 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 +ControlList::rt_safe_earliest_event_linear_unlocked (double start, double& x, double& y, bool inclusive) const +{ + // cout << "earliest_event(start: " << start << ", x: " << x << ", y: " << y << ", inclusive: " << inclusive << ")" << endl; + + const_iterator length_check_iter = _events.begin(); + if (_events.empty()) { // 0 events + return false; + } else if (_events.end() == ++length_check_iter) { // 1 event + return rt_safe_earliest_event_discrete_unlocked (start, x, y, inclusive); + } + + // Hack to avoid infinitely repeating the same event + build_search_cache_if_necessary (start); + + if (_search_cache.first != _events.end()) { + + const ControlEvent* first = NULL; + const ControlEvent* next = NULL; + + if (_search_cache.first == _events.begin() || (*_search_cache.first)->when <= start) { + /* Step is after first */ + first = *_search_cache.first; + ++_search_cache.first; + if (_search_cache.first == _events.end()) { + return false; + } + next = *_search_cache.first; + + } else { + /* Step is before first */ + const_iterator prev = _search_cache.first; + --prev; + first = *prev; + next = *_search_cache.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; + return true; + } else if (next->when < start || (!inclusive && next->when == start)) { + /* "Next" is before the start, no points left. */ + return false; + } + + if (fabs(first->value - next->value) <= 1) { + if (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; + 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) { + /* Move left of cache to this point + * (Optimize for immediate call this cycle within range) */ + _search_cache.left = x; + assert(inclusive ? x >= start : x > start); + return true; + } else { + if (inclusive) { + x = next->when; + } else { + x = start; + } + _search_cache.left = x; + return true; + } + + } else { + /* No points in the future, so no steps (towards them) in the future */ + return false; + } +} + + +/** @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, _desc); + iterator s, e; + ControlEvent cp (start, 0.0); + + { + Glib::Threads::RWLock::WriterLock 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 "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; ) { + + /* 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) { + x = _events.erase (x); + } else { + ++x; + } + } + + if (e == _events.end() || (*e)->when != end) { + + /* 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 (); + } + + if (op != 1) { + maybe_signal_changed (); + } + + return nal; +} + + +boost::shared_ptr<ControlList> +ControlList::cut (double start, double end) +{ + return cut_copy_clear (start, end, 0); +} + +boost::shared_ptr<ControlList> +ControlList::copy (double start, double end) +{ + return cut_copy_clear (start, end, 1); +} + +void +ControlList::clear (double start, double end) +{ + cut_copy_clear (start, end, 2); +} + +/** @param pos Position in model coordinates */ +bool +ControlList::paste (const ControlList& alist, double pos) +{ + if (alist._events.empty()) { + return false; + } + + { + Glib::Threads::RWLock::WriterLock lm (_lock); + iterator where; + iterator prev; + double end = 0; + ControlEvent cp (pos, 0.0); + + where = upper_bound (_events.begin(), _events.end(), &cp, time_comparator); + + for (const_iterator i = alist.begin();i != alist.end(); ++i) { + double value = (*i)->value; + if (alist.parameter() != parameter()) { + const ParameterDescriptor& src_desc = alist.descriptor(); + + // This does not work for logscale and will probably also not do + // the right thing for integer_step and sr_dependent parameters. + // + // TODO various flags from from ARDOUR::ParameterDescriptor + // to Evoral::ParameterDescriptor + + value -= src_desc.lower; // translate to 0-relative + value /= (src_desc.upper - src_desc.lower); // normalize range + value *= (_desc.upper - _desc.lower); // scale to our range + value += _desc.lower; // translate to our offset + if (_desc.toggled) { + value = (value < 0.5) ? 0.0 : 1.0; + } + /* catch possible rounding errors */ + value = std::min ((double)_desc.upper, std::max ((double)_desc.lower, value)); + } + _events.insert (where, new ControlEvent((*i)->when + pos, value)); + end = (*i)->when + pos; + } + + + /* move all points after the insertion along the timeline by + the correct amount. + */ + + while (where != _events.end()) { + iterator tmp; + if ((*where)->when <= end) { + tmp = where; + ++tmp; + _events.erase(where); + where = tmp; + + } else { + break; + } + } + + unlocked_invalidate_insert_iterator (); + mark_dirty (); + } + + maybe_signal_changed (); + return true; +} + +/** 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::Threads::RWLock::WriterLock 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) { + + 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 (); + 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); + ev->when += dx; + _events.push_back (ev); + } + ++j; + } + } + + if (!_frozen) { + _events.sort (event_time_less_than); + unlocked_remove_duplicates (); + unlocked_invalidate_insert_iterator (); + } else { + _sort_pending = true; + } + + mark_dirty (); + } + + maybe_signal_changed (); + return true; +} + +bool +ControlList::set_interpolation (InterpolationStyle s) +{ + if (_interpolation == s) { + 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 +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 || + _desc.lower != other._desc.lower || + _desc.upper != other._desc.upper || + _desc.normal != other._desc.normal + ); +} + +bool +ControlList::is_sorted () const +{ + Glib::Threads::RWLock::ReaderLock lm (_lock); + if (_events.size () == 0) { + return true; + } + const_iterator i = _events.begin(); + const_iterator n = i; + while (++n != _events.end ()) { + if (event_time_less_than(*n,*i)) { + return false; + } + ++i; + } + return true; +} + +void +ControlList::dump (ostream& o) +{ + /* NOT LOCKED ... for debugging only */ + + for (EventList::iterator x = _events.begin(); x != _events.end(); ++x) { + o << (*x)->value << " @ " << (uint64_t) (*x)->when << endl; + } +} + +} // namespace Evoral + |