ardour/libs/ardour/automation_event.cc
David Robillard fe13d08874 Large nasty commit in the form of a 5000 line patch chock-full of completely
unecessary changes.  (Sorry, doing a "sprint" based thing, this is the end of the first one)

Achieved MIDI track and bus creation, associated Jack port and diskstream creation, and minimal GUI stuff for creating them.  Should be set to start work on actually recording and playing midi to/from disk now.

Relevant (significant) changes:

- Creation of a Buffer class.  Base class is type agnostic so things can point to a buffer but not care what kind it is (otherwise it'd be a template).  Derived into AudioBuffer and MidiBuffer, with a type tag because checking type is necessary in parts of the code where dynamic_cast wouldn't be wise.  Originally I considered this a hack, but passing around a type proved to be a very good solution to all the other problems (below).  There is a 1:1 mapping between jack port data types and ardour Buffer types (with a conversion function), but that's easily removed if it ever becomes necessary.  Having the type scoped in the Buffer class is maybe not the best spot for it, but whatever (this is proof of concept kinda stuff right now...)

- IO now has a "default" port type (passed to the constructor and stored as a member), used by ensure_io (and similar) to create n ports.  IO::register_***_port has a type argument that defaults to the default type if not passed.  Rationale:  previous IO API is identical, no changes needed to existing code, but path is paved for multiple port types in one IO, which we will need for eg synth plugin inserts, among other things.  This is not quite ideal (best would be to only have the two port register functions and have them take a type), but the alternative is a lot of work (namely destroying the 'ensure' functions and everything that uses them) for very little gain.  (I am convinced after quite a few tries at the whiteboard that subclassing IO in any way is not a feasible option, look at it's inheritance diagram in Doxygen and you can see why)

- AudioEngine::register_audio_input_port is now register_input_port and takes a type argument.  Ditto for output.

- (Most significant change) AudioDiskstream abstracted into Distream, and sibling MidiDiskstream created.  Very much still a work in progress, but Diskstream is there to switch references over to (most already are), which is the important part.  It is still unclear what the MIDI diskstream's relation to channels is, but I'm pretty sure they will be single channel only (so SMF Type 0) since noone can come up with a reason otherwise.

- MidiTrack creation.  Same thing as AudioTrack but with a different default type basically.  No big deal here.

- Random cleanups and variable renamings etc. because I have OCD and can't help myself. :)

Known broken:  Loading of sessions containing MIDI tracks.




git-svn-id: svn://localhost/ardour2/branches/midi@641 d708f5d6-7413-0410-9779-e7cbd77b26cf
2006-06-26 16:01:34 +00:00

1248 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;
using namespace PBD;
#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 ()
{
{
Glib::Mutex::Lock lm (lock);
events.clear ();
if (!no_state) {
save_state (_("cleared"));
}
mark_dirty ();
}
maybe_signal_changed ();
}
void
AutomationList::x_scale (double factor)
{
Glib::Mutex::Lock lm (lock);
_x_scale (factor);
}
bool
AutomationList::extend_to (double when)
{
Glib::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 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;
{
Glib::Mutex::Lock lm (lock);
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 */
{
Glib::Mutex::Lock lm (lock);
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)
{
{
Glib::Mutex::Lock lm (lock);
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)
{
{
Glib::Mutex::Lock lm (lock);
events.erase (start, end);
reposition_for_rt_add (0);
if (!no_state) {
save_state (_("removed multiple events"));
}
mark_dirty ();
}
maybe_signal_changed ();
}
void
AutomationList::reset_range (double start, double endt)
{
bool reset = false;
{
Glib::Mutex::Lock lm (lock);
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);
for (iterator i = s; i != e; ++i) {
(*i)->value = default_value;
}
reset = true;
if (!no_state) {
save_state (_("removed range"));
}
mark_dirty ();
}
}
if (reset) {
maybe_signal_changed ();
}
}
void
AutomationList::erase_range (double start, double endt)
{
bool erased = false;
{
Glib::Mutex::Lock lm (lock);
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.
*/
{
Glib::Mutex::Lock lm (lock);
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.
*/
{
Glib::Mutex::Lock lm (lock);
(*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)
{
Glib::Mutex::Lock lm (lock);
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)
{
{
Glib::Mutex::Lock lm (lock);
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)
{
{
Glib::Mutex::Lock lm (lock);
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)
{
{
Glib::Mutex::Lock lm (lock);
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);
{
Glib::Mutex::Lock lm (lock);
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;
{
Glib::Mutex::Lock lm (lock);
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);
{
Glib::Mutex::Lock lm (lock);
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;
}
{
Glib::Mutex::Lock lm (lock);
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);
}
}