13
0
livetrax/libs/ardour/automation_event.cc
Taybin Rutkin 8af0757b61 libardour added.
git-svn-id: svn://localhost/trunk/ardour2@17 d708f5d6-7413-0410-9779-e7cbd77b26cf
2005-09-24 19:13:41 +00:00

1212 lines
25 KiB
C++

/*
Copyright (C) 2002 Paul Davis
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., 675 Mass Ave, Cambridge, MA 02139, USA.
$Id$
*/
#include <set>
#include <climits>
#include <float.h>
#include <cmath>
#include <algorithm>
#include <sigc++/bind.h>
#include <ardour/automation_event.h>
#include "i18n.h"
using namespace std;
using namespace ARDOUR;
using namespace sigc;
#if 0
static void dumpit (const AutomationList& al, string prefix = "")
{
cerr << prefix << &al << endl;
for (AutomationList::const_iterator i = al.const_begin(); i != al.const_end(); ++i) {
cerr << prefix << '\t' << (*i)->when << ',' << (*i)->value << endl;
}
cerr << "\n";
}
#endif
AutomationList::AutomationList (double defval, bool with_state)
{
_frozen = false;
changed_when_thawed = false;
_state = Off;
_style = Absolute;
_touching = false;
no_state = with_state;
min_yval = FLT_MIN;
max_yval = FLT_MAX;
max_xval = 0; // means "no limit"
default_value = defval;
_dirty = false;
rt_insertion_point = events.end();
lookup_cache.left = -1;
lookup_cache.range.first = events.end();
if (!no_state) {
save_state (_("initial"));
}
}
AutomationList::AutomationList (const AutomationList& other)
{
_frozen = false;
changed_when_thawed = false;
_style = other._style;
min_yval = other.min_yval;
max_yval = other.max_yval;
max_xval = other.max_xval;
default_value = other.default_value;
_state = other._state;
_touching = other._touching;
_dirty = false;
rt_insertion_point = events.end();
no_state = other.no_state;
lookup_cache.left = -1;
lookup_cache.range.first = events.end();
for (const_iterator i = other.events.begin(); i != other.events.end(); ++i) {
/* we have to use other point_factory() because
its virtual and we're in a constructor.
*/
events.push_back (other.point_factory (**i));
}
mark_dirty ();
}
AutomationList::AutomationList (const AutomationList& other, double start, double end)
{
_frozen = false;
changed_when_thawed = false;
_style = other._style;
min_yval = other.min_yval;
max_yval = other.max_yval;
max_xval = other.max_xval;
default_value = other.default_value;
_state = other._state;
_touching = other._touching;
_dirty = false;
rt_insertion_point = events.end();
no_state = other.no_state;
lookup_cache.left = -1;
lookup_cache.range.first = events.end();
/* now grab the relevant points, and shift them back if necessary */
AutomationList* section = const_cast<AutomationList*>(&other)->copy (start, end);
if (!section->empty()) {
for (AutomationList::iterator i = section->begin(); i != section->end(); ++i) {
events.push_back (other.point_factory ((*i)->when, (*i)->value));
}
}
delete section;
mark_dirty ();
}
AutomationList::~AutomationList()
{
std::set<ControlEvent*> all_events;
AutomationList::State* asp;
for (AutomationEventList::iterator x = events.begin(); x != events.end(); ++x) {
all_events.insert (*x);
}
for (StateMap::iterator i = states.begin(); i != states.end(); ++i) {
if ((asp = dynamic_cast<AutomationList::State*> (*i)) != 0) {
for (AutomationEventList::iterator x = asp->events.begin(); x != asp->events.end(); ++x) {
all_events.insert (*x);
}
}
}
for (std::set<ControlEvent*>::iterator i = all_events.begin(); i != all_events.end(); ++i) {
delete (*i);
}
}
bool
AutomationList::operator== (const AutomationList& other)
{
return events == other.events;
}
AutomationList&
AutomationList::operator= (const AutomationList& other)
{
if (this != &other) {
events.clear ();
for (const_iterator i = other.events.begin(); i != other.events.end(); ++i) {
events.push_back (point_factory (**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 ();
}
return *this;
}
void
AutomationList::maybe_signal_changed ()
{
mark_dirty ();
if (_frozen) {
changed_when_thawed = true;
} else {
StateChanged (Change (0));
}
}
void
AutomationList::set_automation_state (AutoState s)
{
if (s != _state) {
_state = s;
automation_state_changed (); /* EMIT SIGNAL */
}
}
void
AutomationList::set_automation_style (AutoStyle s)
{
if (s != _style) {
_style = s;
automation_style_changed (); /* EMIT SIGNAL */
}
}
void
AutomationList::start_touch ()
{
_touching = true;
_new_touch = true;
}
void
AutomationList::stop_touch ()
{
_touching = false;
_new_touch = false;
}
void
AutomationList::clear ()
{
{
LockMonitor lm (lock, __LINE__, __FILE__);
events.clear ();
if (!no_state) {
save_state (_("cleared"));
}
mark_dirty ();
}
maybe_signal_changed ();
}
void
AutomationList::x_scale (double factor)
{
LockMonitor lm (lock, __LINE__, __FILE__);
_x_scale (factor);
}
bool
AutomationList::extend_to (double when)
{
LockMonitor lm (lock, __LINE__, __FILE__);
if (events.empty() || events.back()->when == when) {
return false;
}
double factor = when / events.back()->when;
_x_scale (factor);
return true;
}
void AutomationList::_x_scale (double factor)
{
for (AutomationList::iterator i = events.begin(); i != events.end(); ++i) {
(*i)->when = floor ((*i)->when * factor);
}
save_state ("x-scaled");
mark_dirty ();
}
void
AutomationList::reposition_for_rt_add (double when)
{
rt_insertion_point = events.end();
}
#define last_rt_insertion_point rt_insertion_point
void
AutomationList::rt_add (double when, double value)
{
/* this is for automation recording */
if ((_state & Touch) && !_touching) {
return;
}
// cerr << "RT: alist @ " << this << " add " << value << " @ " << when << endl;
{
LockMonitor lm (lock, __LINE__, __FILE__);
iterator where;
TimeComparator cmp;
ControlEvent cp (when, 0.0);
bool done = false;
if ((last_rt_insertion_point != events.end()) && ((*last_rt_insertion_point)->when < when) ) {
/* we have a previous insertion point, so we should delete
everything between it and the position where we are going
to insert this point.
*/
iterator after = last_rt_insertion_point;
if (++after != events.end()) {
iterator far = after;
while (far != events.end()) {
if ((*far)->when > when) {
break;
}
++far;
}
if(_new_touch) {
where = far;
last_rt_insertion_point = where;
if((*where)->when == when) {
(*where)->value = value;
done = true;
}
} else {
where = events.erase (after, far);
}
} else {
where = after;
}
iterator previous = last_rt_insertion_point;
--previous;
if (last_rt_insertion_point != events.begin() && (*last_rt_insertion_point)->value == value && (*previous)->value == value) {
(*last_rt_insertion_point)->when = when;
done = true;
}
} else {
where = lower_bound (events.begin(), events.end(), &cp, cmp);
if (where != events.end()) {
if ((*where)->when == when) {
(*where)->value = value;
done = true;
}
}
}
if (!done) {
last_rt_insertion_point = events.insert (where, point_factory (when, value));
}
_new_touch = false;
mark_dirty ();
}
maybe_signal_changed ();
}
#undef last_rt_insertion_point
void
AutomationList::add (double when, double value, bool for_loading)
{
/* this is for graphical editing and loading data from storage */
{
LockMonitor lm (lock, __LINE__, __FILE__);
TimeComparator cmp;
ControlEvent cp (when, 0.0f);
bool insert = true;
iterator insertion_point;
for (insertion_point = lower_bound (events.begin(), events.end(), &cp, cmp); insertion_point != events.end(); ++insertion_point) {
/* only one point allowed per time point */
if ((*insertion_point)->when == when) {
(*insertion_point)->value = value;
insert = false;
break;
}
if ((*insertion_point)->when >= when) {
break;
}
}
if (insert) {
events.insert (insertion_point, point_factory (when, value));
reposition_for_rt_add (0);
}
mark_dirty ();
if (!no_state && !for_loading) {
save_state (_("added event"));
}
}
if (!for_loading) {
maybe_signal_changed ();
}
}
void
AutomationList::erase (AutomationList::iterator i)
{
{
LockMonitor lm (lock, __LINE__, __FILE__);
events.erase (i);
reposition_for_rt_add (0);
if (!no_state) {
save_state (_("removed event"));
}
mark_dirty ();
}
maybe_signal_changed ();
}
void
AutomationList::erase (AutomationList::iterator start, AutomationList::iterator end)
{
{
LockMonitor lm (lock, __LINE__, __FILE__);
events.erase (start, end);
reposition_for_rt_add (0);
if (!no_state) {
save_state (_("removed multiple events"));
}
mark_dirty ();
}
maybe_signal_changed ();
}
void
AutomationList::erase_range (double start, double endt)
{
bool erased = false;
{
LockMonitor lm (lock, __LINE__, __FILE__);
TimeComparator cmp;
ControlEvent cp (start, 0.0f);
iterator s;
iterator e;
if ((s = lower_bound (events.begin(), events.end(), &cp, cmp)) != events.end()) {
cp.when = endt;
e = upper_bound (events.begin(), events.end(), &cp, cmp);
events.erase (s, e);
reposition_for_rt_add (0);
erased = true;
if (!no_state) {
save_state (_("removed range"));
}
mark_dirty ();
}
}
if (erased) {
maybe_signal_changed ();
}
}
void
AutomationList::move_range (iterator start, iterator end, double xdelta, double ydelta)
{
/* 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 end are later than (end)->when.
*/
{
LockMonitor lm (lock, __LINE__, __FILE__);
while (start != end) {
(*start)->when += xdelta;
(*start)->value += ydelta;
++start;
}
if (!no_state) {
save_state (_("event range adjusted"));
}
mark_dirty ();
}
maybe_signal_changed ();
}
void
AutomationList::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.
*/
{
LockMonitor lm (lock, __LINE__, __FILE__);
(*iter)->when = when;
(*iter)->value = val;
if (!no_state) {
save_state (_("event adjusted"));
}
mark_dirty ();
}
maybe_signal_changed ();
}
std::pair<AutomationList::iterator,AutomationList::iterator>
AutomationList::control_points_adjacent (double xval)
{
LockMonitor lm (lock, __LINE__, __FILE__);
iterator i;
TimeComparator cmp;
ControlEvent cp (xval, 0.0f);
std::pair<iterator,iterator> ret;
ret.first = events.end();
ret.second = events.end();
for (i = lower_bound (events.begin(), events.end(), &cp, cmp); i != events.end(); ++i) {
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
AutomationList::freeze ()
{
_frozen = true;
}
void
AutomationList::thaw ()
{
_frozen = false;
if (changed_when_thawed) {
StateChanged(Change(0)); /* EMIT SIGNAL */
}
}
StateManager::State*
AutomationList::state_factory (std::string why) const
{
State* state = new State (why);
for (AutomationEventList::const_iterator x = events.begin(); x != events.end(); ++x) {
state->events.push_back (point_factory (**x));
}
return state;
}
Change
AutomationList::restore_state (StateManager::State& state)
{
{
LockMonitor lm (lock, __LINE__, __FILE__);
State* lstate = dynamic_cast<State*> (&state);
events.clear ();
for (AutomationEventList::const_iterator x = lstate->events.begin(); x != lstate->events.end(); ++x) {
events.push_back (point_factory (**x));
}
}
return Change (0);
}
UndoAction
AutomationList::get_memento () const
{
return sigc::bind (mem_fun (*(const_cast<AutomationList*> (this)), &StateManager::use_state), _current_state_id);
}
void
AutomationList::set_max_xval (double x)
{
max_xval = x;
}
void
AutomationList::mark_dirty ()
{
lookup_cache.left = -1;
_dirty = true;
}
void
AutomationList::truncate_end (double last_coordinate)
{
{
LockMonitor lm (lock, __LINE__, __FILE__);
ControlEvent cp (last_coordinate, 0);
list<ControlEvent*>::reverse_iterator i;
double last_val;
if (events.empty()) {
fatal << _("programming error:")
<< "AutomationList::truncate_end() called on an empty list"
<< endmsg;
/*NOTREACHED*/
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 (point_factory (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 (point_factory (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.
*/
uint32_t sz = events.size();
while (i != events.rend() && sz > 2) {
list<ControlEvent*>::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);
mark_dirty();
}
maybe_signal_changed ();
}
void
AutomationList::truncate_start (double overall_length)
{
{
LockMonitor lm (lock, __LINE__, __FILE__);
AutomationList::iterator i;
double first_legal_value;
double first_legal_coordinate;
if (events.empty()) {
fatal << _("programming error:")
<< "AutomationList::truncate_start() called on an empty list"
<< endmsg;
/*NOTREACHED*/
return;
}
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 (point_factory (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 (point_factory (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()) {
list<ControlEvent*>::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 (point_factory (0, first_legal_value));
}
reposition_for_rt_add (0);
mark_dirty();
}
maybe_signal_changed ();
}
double
AutomationList::unlocked_eval (double x)
{
return shared_eval (x);
}
double
AutomationList::shared_eval (double x)
{
pair<AutomationEventList::iterator,AutomationEventList::iterator> range;
int32_t npoints;
double lpos, upos;
double lval, uval;
double fraction;
npoints = events.size();
switch (npoints) {
case 0:
return default_value;
case 1:
if (x >= events.front()->when) {
return events.front()->value;
} else {
// return default_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) {
// return default_value;
return events.front()->value;
}
lpos = events.front()->when;
lval = events.front()->value;
upos = events.back()->when;
uval = events.back()->value;
/* 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) {
// return default_value;
return events.front()->value;
}
return multipoint_eval (x);
break;
}
}
double
AutomationList::multipoint_eval (double x)
{
pair<AutomationList::iterator,AutomationList::iterator> range;
double upos, lpos;
double uval, lval;
double fraction;
/* only do the range lookup if x is in a different range than last time
this was called (or if the lookup cache has been marked "dirty" (left<0)
*/
if ((lookup_cache.left < 0) ||
((lookup_cache.left > x) ||
(lookup_cache.range.first == events.end()) ||
((*lookup_cache.range.second)->when < x))) {
ControlEvent cp (x, 0);
TimeComparator cmp;
lookup_cache.range = equal_range (events.begin(), events.end(), &cp, cmp);
}
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;
}
AutomationList*
AutomationList::cut (iterator start, iterator end)
{
AutomationList* nal = new AutomationList (default_value);
{
LockMonitor lm (lock, __LINE__, __FILE__);
for (iterator x = start; x != end; ) {
iterator tmp;
tmp = x;
++tmp;
nal->events.push_back (point_factory (**x));
events.erase (x);
reposition_for_rt_add (0);
x = tmp;
}
mark_dirty ();
}
maybe_signal_changed ();
return nal;
}
AutomationList*
AutomationList::cut_copy_clear (double start, double end, int op)
{
AutomationList* nal = new AutomationList (default_value);
iterator s, e;
ControlEvent cp (start, 0.0);
TimeComparator cmp;
bool changed = false;
{
LockMonitor lm (lock, __LINE__, __FILE__);
if ((s = lower_bound (events.begin(), events.end(), &cp, cmp)) == events.end()) {
return nal;
}
cp.when = end;
e = upper_bound (events.begin(), events.end(), &cp, cmp);
if (op != 2 && (*s)->when != start) {
nal->events.push_back (point_factory (0, unlocked_eval (start)));
}
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 (point_factory ((*x)->when - start, (*x)->value));
}
if (op != 1) {
events.erase (x);
}
x = tmp;
}
if (op != 2 && nal->events.back()->when != end - start) {
nal->events.push_back (point_factory (end - start, unlocked_eval (end)));
}
if (changed) {
reposition_for_rt_add (0);
if (!no_state) {
save_state (_("cut/copy/clear"));
}
}
mark_dirty ();
}
maybe_signal_changed ();
return nal;
}
AutomationList*
AutomationList::copy (iterator start, iterator end)
{
AutomationList* nal = new AutomationList (default_value);
{
LockMonitor lm (lock, __LINE__, __FILE__);
for (iterator x = start; x != end; ) {
iterator tmp;
tmp = x;
++tmp;
nal->events.push_back (point_factory (**x));
x = tmp;
}
if (!no_state) {
save_state (_("copy"));
}
}
return nal;
}
AutomationList*
AutomationList::cut (double start, double end)
{
return cut_copy_clear (start, end, 0);
}
AutomationList*
AutomationList::copy (double start, double end)
{
return cut_copy_clear (start, end, 1);
}
void
AutomationList::clear (double start, double end)
{
(void) cut_copy_clear (start, end, 2);
}
bool
AutomationList::paste (AutomationList& alist, double pos, float times)
{
if (alist.events.empty()) {
return false;
}
{
LockMonitor lm (lock, __LINE__, __FILE__);
iterator where;
iterator prev;
double end = 0;
ControlEvent cp (pos, 0.0);
TimeComparator cmp;
where = upper_bound (events.begin(), events.end(), &cp, cmp);
for (iterator i = alist.begin();i != alist.end(); ++i) {
events.insert (where, point_factory( (*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;
}
}
reposition_for_rt_add (0);
if (!no_state) {
save_state (_("paste"));
}
mark_dirty ();
}
maybe_signal_changed ();
return true;
}
ControlEvent*
AutomationList::point_factory (double when, double val) const
{
return new ControlEvent (when, val);
}
ControlEvent*
AutomationList::point_factory (const ControlEvent& other) const
{
return new ControlEvent (other);
}
void
AutomationList::store_state (XMLNode& node) const
{
LocaleGuard lg (X_("POSIX"));
for (const_iterator i = const_begin(); i != const_end(); ++i) {
char buf[64];
XMLNode *pointnode = new XMLNode ("point");
snprintf (buf, sizeof (buf), "%" PRIu32, (jack_nframes_t) floor ((*i)->when));
pointnode->add_property ("x", buf);
snprintf (buf, sizeof (buf), "%f", (*i)->value);
pointnode->add_property ("y", buf);
node.add_child_nocopy (*pointnode);
}
}
void
AutomationList::load_state (const XMLNode& node)
{
const XMLNodeList& elist = node.children();
XMLNodeConstIterator i;
XMLProperty* prop;
jack_nframes_t x;
double y;
clear ();
for (i = elist.begin(); i != elist.end(); ++i) {
if ((prop = (*i)->property ("x")) == 0) {
error << _("automation list: no x-coordinate stored for control point (point ignored)") << endmsg;
continue;
}
x = atoi (prop->value().c_str());
if ((prop = (*i)->property ("y")) == 0) {
error << _("automation list: no y-coordinate stored for control point (point ignored)") << endmsg;
continue;
}
y = atof (prop->value().c_str());
add (x, y);
}
}