/* This file is part of Evoral. * Copyright (C) 2008 David Robillard * 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 #include #include #include #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(0) { _frozen = 0; _changed_when_thawed = false; _min_yval = id.min(); _max_yval = id.max(); _default_value = 0; _lookup_cache.left = -1; _lookup_cache.range.first = _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) , _interpolation(Linear) , _curve(0) { _frozen = 0; _changed_when_thawed = false; _min_yval = other._min_yval; _max_yval = other._max_yval; _default_value = other._default_value; _lookup_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(); copy_events (other); mark_dirty (); } ControlList::ControlList (const ControlList& other, double start, double end) : _parameter(other._parameter) , _interpolation(Linear) , _curve(0) { _frozen = 0; _changed_when_thawed = false; _min_yval = other._min_yval; _max_yval = other._max_yval; _default_value = other._default_value; _lookup_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 section = const_cast(&other)->copy (start, end); if (!section->empty()) { 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 (); } ControlList::~ControlList() { for (EventList::iterator x = _events.begin(); x != _events.end(); ++x) { delete (*x); } delete _curve; } boost::shared_ptr ControlList::create(Parameter id) { return boost::shared_ptr(new ControlList(id)); } bool ControlList::operator== (const ControlList& other) { return _events == other._events; } ControlList& ControlList::operator= (const ControlList& other) { if (this != &other) { _min_yval = other._min_yval; _max_yval = other._max_yval; _default_value = other._default_value; 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) { _changed_when_thawed = true; } } void ControlList::clear () { { Glib::Threads::Mutex::Lock lm (_lock); _events.clear (); unlocked_invalidate_insert_iterator (); mark_dirty (); } maybe_signal_changed (); } void ControlList::x_scale (double factor) { Glib::Threads::Mutex::Lock lm (_lock); _x_scale (factor); } bool ControlList::extend_to (double when) { Glib::Threads::Mutex::Lock lm (_lock); if (_events.empty() || _events.back()->when == when) { return false; } double factor = when / _events.back()->when; _x_scale (factor); return true; } 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 () { bool changed = false; { 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())); if (changed) { unlocked_invalidate_insert_iterator (); mark_dirty (); } } if (changed) { maybe_signal_changed (); } } 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()); mark_dirty (); } void ControlList::invalidate_insert_iterator () { Glib::Threads::Mutex::Lock lm (_lock); unlocked_invalidate_insert_iterator (); } void ControlList::unlocked_invalidate_insert_iterator () { most_recent_insert_iterator = _events.end(); } void ControlList::start_write_pass (double when) { Glib::Threads::Mutex::Lock lm (_lock); new_write_pass = true; did_write_during_pass = false; 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 (); } 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; } void ControlList::set_in_write_pass (bool yn) { _in_write_pass = yn; } bool ControlList::in_write_pass () const { return _in_write_pass; } void ControlList::add (double when, double value) { /* 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::Threads::Mutex::Lock lm (_lock); ControlEvent cp (when, 0.0f); iterator insertion_point; 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, _default_value)); DEBUG_TRACE (DEBUG::ControlList, string_compose ("@%1 added default value %2 at zero\n", this, _default_value)); } } if (_in_write_pass && new_write_pass) { DEBUG_TRACE (DEBUG::ControlList, string_compose ("@%1 new write pass, insert pos = %2\n", this, insert_position)); /* The first addition of a new control event during a * write pass. * * We need to add a new point at insert_position * corresponding the value there. */ /* the insert_iterator is not set, figure out where * it needs to be. */ ControlEvent cp (insert_position, 0.0); most_recent_insert_iterator = lower_bound (_events.begin(), _events.end(), &cp, time_comparator); DEBUG_TRACE (DEBUG::ControlList, string_compose ("@%1 looked up insert iterator for new write pass\n", this)); double eval_value = unlocked_eval (insert_position); 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 (insert_position, 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 (insert_position, 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; } /* don't do this again till the next write pass */ new_write_pass = false; did_write_during_pass = true; } else if (most_recent_insert_iterator == _events.end() || when > (*most_recent_insert_iterator)->when) { /* this is NOT the first point to be added after the start of a write pass, and we have a bit of work to do figuring out where to add the new point, as well as potentially erasing existing data between the most recently added point and wherever this one will end up. */ DEBUG_TRACE (DEBUG::ControlList, string_compose ("@%1 need to discover insert iterator (@end ? %2)\n", this, (most_recent_insert_iterator == _events.end()))); /* this means that we either *know* we want to insert * at the end, or that we don't know where to insert. * * so ... lets perform some quick checks before we * go doing binary search to figure out where to * insert. */ if (_events.back()->when == when) { /* we need to modify the final point, so make most_recent_insert_iterator point to it. */ DEBUG_TRACE (DEBUG::ControlList, string_compose ("@%1 modify final value\n", this)); most_recent_insert_iterator = _events.end(); --most_recent_insert_iterator; } else if (_events.back()->when < when) { /* the new point is beyond the end of the * current list */ DEBUG_TRACE (DEBUG::ControlList, string_compose ("@%1 plan to append to list\n", this)); if (_in_write_pass) { /* remove the final point, because we're adding one beyond it. */ delete _events.back(); _events.pop_back(); } /* leaving this here will force an append */ most_recent_insert_iterator = _events.end(); } else { DEBUG_TRACE (DEBUG::ControlList, string_compose ("@%1 erase %2 from existing iterator (@end ? %3\n", this, _in_write_pass, (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; } } else { /* not in a write pass: figure out the iterator we should insert in front of */ 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)); 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; } } } } if (!done) { _events.push_back (new ControlEvent (when, value)); } if (!_in_write_pass) { most_recent_insert_iterator = _events.end(); --most_recent_insert_iterator; } } else if ((*most_recent_insert_iterator)->when == when) { 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; } 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; } done = true; } } } } if (!done) { EventList::iterator x = _events.insert (most_recent_insert_iterator, new ControlEvent (when, value)); if (!_in_write_pass) { most_recent_insert_iterator = x; } } } mark_dirty (); } maybe_signal_changed (); } void ControlList::erase (iterator i) { { Glib::Threads::Mutex::Lock 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::Mutex::Lock 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::Mutex::Lock 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::Mutex::Lock 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::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 (); } 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. */ { Glib::Threads::Mutex::Lock lm (_lock); (*iter)->when = when; (*iter)->value = val; if (std::isnan (val)) { abort (); } if (!_frozen) { _events.sort (event_time_less_than); unlocked_invalidate_insert_iterator (); } else { _sort_pending = true; } mark_dirty (); } maybe_signal_changed (); } std::pair ControlList::control_points_adjacent (double xval) { Glib::Threads::Mutex::Lock lm (_lock); iterator i; ControlEvent cp (xval, 0.0f); std::pair 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::Mutex::Lock lm (_lock); if (_sort_pending) { _events.sort (event_time_less_than); unlocked_invalidate_insert_iterator (); _sort_pending = false; } } } void ControlList::mark_dirty () const { _lookup_cache.left = -1; _search_cache.left = -1; if (_curve) { _curve->mark_dirty(); } Dirty (); /* EMIT SIGNAL */ } void ControlList::truncate_end (double last_coordinate) { { Glib::Threads::Mutex::Lock 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) _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; } unlocked_invalidate_insert_iterator (); mark_dirty(); } maybe_signal_changed (); } void ControlList::truncate_start (double overall_length) { { 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; 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 (_min_yval, first_legal_value); first_legal_value = min (_max_yval, first_legal_value); /* remove all events earlier than the new "front" */ i = _events.begin(); 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 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 _default_value; 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; if (_interpolation == Discrete) { return lval; } /* 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; } return multipoint_eval (x); } /*NOTREACHED*/ /* stupid gcc */ return _default_value; } 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 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; /* 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; return (*range.first)->value; } void 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))) { const ControlEvent start_point (start, 0); //cerr << "REBUILD: (" << _search_cache.left << ".." << _search_cache.right << ") := (" // << start << ".." << end << ")" << endl; _search_cache.first = lower_bound (_events.begin(), _events.end(), &start_point, time_comparator); _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::Mutex::Lock 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; /* Step is after 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 */ } else { 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; //++_search_cache.range.first; assert(x >= start); return true; } 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; //++_search_cache.range.first; assert(inclusive ? x >= start : x > start); 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 { return false; } /* No points in the future, so no steps (towards them) in the future */ } else { 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::cut_copy_clear (double start, double end, int op) { boost::shared_ptr nal = create (_parameter); iterator s, e; ControlEvent cp (start, 0.0); { 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 "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::cut (double start, double end) { return cut_copy_clear (start, end, 0); } boost::shared_ptr 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 (ControlList& alist, double pos, float /*times*/) { if (alist._events.empty()) { return false; } { Glib::Threads::Mutex::Lock 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 (iterator i = alist.begin();i != alist.end(); ++i) { _events.insert (where, new ControlEvent( (*i)->when+pos,( *i)->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 >& movements) { typedef list< RangeMove > RangeMoveList; { 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) { 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_invalidate_insert_iterator (); } else { _sort_pending = true; } 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 ); } } // namespace Evoral