Support cut / copy / paste of MIDI automation.

git-svn-id: svn://localhost/ardour2/branches/3.0@7545 d708f5d6-7413-0410-9779-e7cbd77b26cf
This commit is contained in:
Carl Hetherington 2010-08-05 13:36:38 +00:00
parent e7a2b99f3d
commit 5e3ca4db5c
16 changed files with 169 additions and 67 deletions

View file

@ -947,15 +947,25 @@ AutomationLine::remove_point (ControlPoint& cp)
trackview.editor().session()->set_dirty ();
}
/** Get selectable points within an area.
* @param start Start position in session frames.
* @param end End position in session frames.
* @param botfrac Bottom of area, as a fraction of the line height.
* @param topfrac Bottom of area, as a fraction of the line height.
*/
void
AutomationLine::get_selectables (nframes_t start, nframes_t end,
double botfrac, double topfrac, list<Selectable*>& results)
AutomationLine::get_selectables (
framepos_t start, framepos_t end, double botfrac, double topfrac, list<Selectable*>& results
)
{
double top;
double bot;
sframes_t nstart;
sframes_t nend;
/* these two are in AutomationList model coordinates */
double nstart;
double nend;
bool collecting = false;
/* Curse X11 and its inverted coordinate system! */
@ -963,21 +973,22 @@ AutomationLine::get_selectables (nframes_t start, nframes_t end,
bot = (1.0 - topfrac) * _height;
top = (1.0 - botfrac) * _height;
nstart = max_frames;
nstart = DBL_MAX;
nend = 0;
for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
sframes_t const when = _time_converter.to ((*(*i)->model())->when);
double const model_when = (*(*i)->model())->when;
framepos_t const session_frames_when = _time_converter.to (model_when) + _time_converter.origin_b ();
if (when >= start && when <= end) {
if (session_frames_when >= start && session_frames_when <= end) {
if ((*i)->get_y() >= bot && (*i)->get_y() <= top) {
(*i)->show();
(*i)->set_visible(true);
collecting = true;
nstart = min (nstart, when);
nend = max (nend, when);
nstart = min (nstart, model_when);
nend = max (nend, model_when);
} else {
@ -985,7 +996,7 @@ AutomationLine::get_selectables (nframes_t start, nframes_t end,
results.push_back (new AutomationSelectable (nstart, nend, botfrac, topfrac, &trackview));
collecting = false;
nstart = max_frames;
nstart = DBL_MAX;
nend = 0;
}
}
@ -1023,8 +1034,8 @@ AutomationLine::point_selection_to_control_points (PointSelection const & s)
for (vector<ControlPoint*>::iterator j = control_points.begin(); j != control_points.end(); ++j) {
double const rstart = trackview.editor().frame_to_unit (i->start);
double const rend = trackview.editor().frame_to_unit (i->end);
double const rstart = trackview.editor().frame_to_unit (_time_converter.to (i->start));
double const rend = trackview.editor().frame_to_unit (_time_converter.to (i->end));
if ((*j)->get_x() >= rstart && (*j)->get_x() <= rend) {
if ((*j)->get_y() >= bot && (*j)->get_y() <= top) {

View file

@ -68,7 +68,7 @@ class AutomationLine : public sigc::trackable, public PBD::StatefulDestructible
std::list<ControlPoint*> point_selection_to_control_points (PointSelection const &);
void set_selected_points (PointSelection&);
void get_selectables (nframes_t start, nframes_t end,
void get_selectables (ARDOUR::framepos_t start, ARDOUR::framepos_t end,
double botfrac, double topfrac,
std::list<Selectable*>& results);
void get_inverted_selectables (Selection&, std::list<Selectable*>& results);
@ -136,6 +136,10 @@ class AutomationLine : public sigc::trackable, public PBD::StatefulDestructible
virtual MementoCommandBinder<ARDOUR::AutomationList>* memento_command_binder ();
const Evoral::TimeConverter<double, ARDOUR::sframes_t>& time_converter () const {
return _time_converter;
}
protected:
std::string _name;

View file

@ -20,35 +20,37 @@
#ifndef __ardour_gtk_automation_selectable_h__
#define __ardour_gtk_automation_selectable_h__
#include "ardour/types.h"
#include "selectable.h"
class TimeAxisView;
/** A selected automation point, expressed as a rectangle on a track (so that x coordinates
* are frames and y coordinates are a fraction of track height). This representation falls
* between the visible GUI control points and the back-end "actual" automation points,
* some of which may not be visible; it is not trivial to convert from one of these to the other,
* so the AutomationSelectable is a kind of "best and worst of both worlds".
/** A selected automation point, expressed as a rectangle.
* x coordinates start/end are in AutomationList model coordinates.
* y coordinates are a expressed as a fraction of track height.
* This representation falls between the visible GUI control points and
* the back-end "actual" automation points, some of which may not be
* visible; it is not trivial to convert from one of these to the
* other, so the AutomationSelectable is a kind of "best and worst of
* both worlds".
*/
struct AutomationSelectable : public Selectable
{
nframes_t start;
nframes_t end;
double low_fract;
double high_fract;
TimeAxisView* track; // ref would be better, but ARDOUR::SessionHandlePtr is non-assignable
AutomationSelectable (nframes_t s, nframes_t e, double l, double h, TimeAxisView* atv)
: start (s), end (e), low_fract (l), high_fract (h), track (atv) {}
bool operator== (const AutomationSelectable& other) {
return start == other.start &&
end == other.end &&
low_fract == other.low_fract &&
high_fract == other.high_fract &&
track == other.track;
}
double start;
double end;
double low_fract;
double high_fract;
TimeAxisView* track; // ref would be better, but ARDOUR::SessionHandlePtr is non-assignable
AutomationSelectable (double s, double e, double l, double h, TimeAxisView* atv)
: start (s), end (e), low_fract (l), high_fract (h), track (atv) {}
bool operator== (const AutomationSelectable& other) {
return start == other.start &&
end == other.end &&
low_fract == other.low_fract &&
high_fract == other.high_fract &&
track == other.track;
}
};
#endif /* __ardour_gtk_automation_selectable_h__ */

View file

@ -274,13 +274,16 @@ AutomationStreamView::clear ()
}
}
/** @param start Start position in session frames.
* @param end End position in session frames.
*/
void
AutomationStreamView::get_selectables (nframes_t start, nframes_t end, double botfrac, double topfrac, list<Selectable*>& results)
AutomationStreamView::get_selectables (framepos_t start, framepos_t end, double botfrac, double topfrac, list<Selectable*>& results)
{
for (list<RegionView*>::iterator i = region_views.begin(); i != region_views.end(); ++i) {
AutomationRegionView* arv = dynamic_cast<AutomationRegionView*> (*i);
assert (arv);
arv->line()->get_selectables (start - (*i)->region()->position(), end - (*i)->region()->position(), botfrac, topfrac, results);
arv->line()->get_selectables (start, end, botfrac, topfrac, results);
}
}
@ -307,3 +310,46 @@ AutomationStreamView::get_lines () const
return lines;
}
struct RegionPositionSorter {
bool operator() (RegionView* a, RegionView* b) {
return a->region()->position() < b->region()->position();
}
};
/** @param pos Position, in session frames.
* @return AutomationLine to paste to for that position, or 0 if there is none appropriate.
*/
boost::shared_ptr<AutomationLine>
AutomationStreamView::paste_line (framepos_t pos)
{
/* XXX: not sure how best to pick this; for now, just use the last region which starts before pos */
if (region_views.empty()) {
return boost::shared_ptr<AutomationLine> ();
}
region_views.sort (RegionPositionSorter ());
list<RegionView*>::const_iterator prev = region_views.begin ();
for (list<RegionView*>::const_iterator i = region_views.begin(); i != region_views.end(); ++i) {
if ((*i)->region()->position() > pos) {
break;
}
prev = i;
}
boost::shared_ptr<Region> r = (*prev)->region ();
/* If *prev doesn't cover pos, it's no good */
if (r->position() > pos || ((r->position() + r->length()) < pos)) {
return boost::shared_ptr<AutomationLine> ();
}
AutomationRegionView* arv = dynamic_cast<AutomationRegionView*> (*prev);
assert (arv);
return arv->line ();
}

View file

@ -61,10 +61,11 @@ class AutomationStreamView : public StreamView
void clear ();
void get_selectables (nframes_t, nframes_t, double, double, std::list<Selectable*> &);
void get_selectables (ARDOUR::framepos_t, ARDOUR::framepos_t, double, double, std::list<Selectable*> &);
void set_selected_points (PointSelection &);
std::list<boost::shared_ptr<AutomationLine> > get_lines () const;
boost::shared_ptr<AutomationLine> paste_line (ARDOUR::framepos_t);
private:
void setup_rec_box ();

View file

@ -634,21 +634,27 @@ AutomationTimeAxisView::cut_copy_clear_one (AutomationLine& line, Selection& sel
XMLNode &before = alist->get_state();
/* convert time selection to automation list model coordinates */
const Evoral::TimeConverter<double, ARDOUR::sframes_t>& tc = line.time_converter ();
double const start = tc.from (selection.time.front().start - tc.origin_b ());
double const end = tc.from (selection.time.front().end - tc.origin_b ());
switch (op) {
case Cut:
if ((what_we_got = alist->cut (selection.time.front().start, selection.time.front().end)) != 0) {
if ((what_we_got = alist->cut (start, end)) != 0) {
_editor.get_cut_buffer().add (what_we_got);
_session->add_command(new MementoCommand<AutomationList>(*alist.get(), &before, &alist->get_state()));
}
break;
case Copy:
if ((what_we_got = alist->copy (selection.time.front().start, selection.time.front().end)) != 0) {
if ((what_we_got = alist->copy (start, end)) != 0) {
_editor.get_cut_buffer().add (what_we_got);
}
break;
case Clear:
if ((what_we_got = alist->cut (selection.time.front().start, selection.time.front().end)) != 0) {
if ((what_we_got = alist->cut (start, end)) != 0) {
_session->add_command(new MementoCommand<AutomationList>(*alist.get(), &before, &alist->get_state()));
}
break;
@ -740,8 +746,6 @@ AutomationTimeAxisView::cut_copy_clear_objects_one (AutomationLine& line, PointS
delete &before;
cout << "CCC objects " << what_we_got->size() << "\n";
if (what_we_got) {
for (AutomationList::iterator x = what_we_got->begin(); x != what_we_got->end(); ++x) {
double when = (*x)->when;
@ -753,14 +757,32 @@ AutomationTimeAxisView::cut_copy_clear_objects_one (AutomationLine& line, PointS
}
}
/** Paste a selection.
* @param pos Position to paste to (session frames).
* @param times Number of times to paste.
* @param selection Selection to paste.
* @param nth Index of the AutomationList within the selection to paste from.
*/
bool
AutomationTimeAxisView::paste (nframes_t pos, float times, Selection& selection, size_t nth)
AutomationTimeAxisView::paste (framepos_t pos, float times, Selection& selection, size_t nth)
{
return paste_one (*_line, pos, times, selection, nth);
boost::shared_ptr<AutomationLine> line;
if (_line) {
line = _line;
} else if (_view) {
line = _view->paste_line (pos);
}
if (!line) {
return false;
}
return paste_one (*line, pos, times, selection, nth);
}
bool
AutomationTimeAxisView::paste_one (AutomationLine& line, nframes_t pos, float times, Selection& selection, size_t nth)
AutomationTimeAxisView::paste_one (AutomationLine& line, framepos_t pos, float times, Selection& selection, size_t nth)
{
AutomationSelection::iterator p;
boost::shared_ptr<AutomationList> alist(line.the_list());
@ -786,8 +808,10 @@ AutomationTimeAxisView::paste_one (AutomationLine& line, nframes_t pos, float ti
(*x)->value = val;
}
double const model_pos = line.time_converter().from (pos - line.time_converter().origin_b ());
XMLNode &before = alist->get_state();
alist->paste (copy, pos, times);
alist->paste (copy, model_pos, times);
_session->add_command (new MementoCommand<AutomationList>(*alist.get(), &before, &alist->get_state()));
return true;

View file

@ -88,7 +88,7 @@ class AutomationTimeAxisView : public TimeAxisView {
void cut_copy_clear (Selection&, Editing::CutCopyOp);
void cut_copy_clear_objects (PointSelection&, Editing::CutCopyOp);
bool paste (nframes_t, float times, Selection&, size_t nth);
bool paste (ARDOUR::framepos_t, float times, Selection&, size_t nth);
void reset_objects (PointSelection&);
int set_state (const XMLNode&, int version);
@ -148,7 +148,7 @@ class AutomationTimeAxisView : public TimeAxisView {
void cut_copy_clear_one (AutomationLine&, Selection&, Editing::CutCopyOp);
void cut_copy_clear_objects_one (AutomationLine&, PointSelection&, Editing::CutCopyOp);
bool paste_one (AutomationLine&, nframes_t, float times, Selection&, size_t nth);
bool paste_one (AutomationLine&, ARDOUR::framepos_t, float times, Selection&, size_t nth);
void reset_objects_one (AutomationLine&, PointSelection&);
void set_automation_state (ARDOUR::AutoState);

View file

@ -977,11 +977,13 @@ Editor::invert_selection ()
selection->set (touched);
}
/** @param top Top (lower) y limit in trackview coordinates.
/** @param start Start time in session frames.
* @param end End time in session frames.
* @param top Top (lower) y limit in trackview coordinates.
* @param bottom Bottom (higher) y limit in trackview coordinates.
*/
bool
Editor::select_all_within (nframes64_t start, nframes64_t end, double top, double bot, const TrackViewList& tracklist, Selection::Operation op)
Editor::select_all_within (framepos_t start, framepos_t end, double top, double bot, const TrackViewList& tracklist, Selection::Operation op)
{
list<Selectable*> found;

View file

@ -269,7 +269,7 @@ RegionView::region_resized (const PropertyChange& what_changed)
if (what_changed.contains (ARDOUR::Properties::position)) {
set_position (_region->position(), 0);
_time_converter.set_origin(_region->position());
_time_converter.set_origin_b (_region->position());
}
PropertyChange s_and_l;

View file

@ -1363,7 +1363,7 @@ RouteTimeAxisView::cut_copy_clear (Selection& selection, CutCopyOp op)
}
bool
RouteTimeAxisView::paste (nframes_t pos, float times, Selection& selection, size_t nth)
RouteTimeAxisView::paste (framepos_t pos, float times, Selection& selection, size_t nth)
{
if (!is_track()) {
return false;

View file

@ -93,7 +93,7 @@ public:
/* Editing operations */
void cut_copy_clear (Selection&, Editing::CutCopyOp);
bool paste (nframes_t, float times, Selection&, size_t nth);
bool paste (ARDOUR::framepos_t, float times, Selection&, size_t nth);
TimeAxisView::Children get_child_list();

View file

@ -178,7 +178,7 @@ class TimeAxisView : public virtual AxisView, public PBD::Stateful
/* editing operations */
virtual void cut_copy_clear (Selection&, Editing::CutCopyOp) {}
virtual bool paste (nframes_t, float /*times*/, Selection&, size_t /*nth*/) { return false; }
virtual bool paste (ARDOUR::framepos_t, float /*times*/, Selection&, size_t /*nth*/) { return false; }
virtual void set_selected_regionviews (RegionSelection&) {}
virtual void set_selected_points (PointSelection&) {}

View file

@ -32,19 +32,15 @@ class TempoMap;
class BeatsFramesConverter : public Evoral::TimeConverter<double,sframes_t> {
public:
BeatsFramesConverter(const TempoMap& tempo_map, sframes_t origin)
: _tempo_map(tempo_map)
, _origin(origin)
: Evoral::TimeConverter<double, sframes_t> (origin)
, _tempo_map(tempo_map)
{}
sframes_t to(double beats) const;
double from(sframes_t frames) const;
sframes_t origin() const { return _origin; }
void set_origin(sframes_t origin) { _origin = origin; }
private:
const TempoMap& _tempo_map;
sframes_t _origin;
};
} /* namespace ARDOUR */

View file

@ -28,10 +28,10 @@ sframes_t
BeatsFramesConverter::to(double beats) const
{
// FIXME: assumes tempo never changes after origin
const Tempo& tempo = _tempo_map.tempo_at(_origin);
const Tempo& tempo = _tempo_map.tempo_at (_origin_b);
const double frames_per_beat = tempo.frames_per_beat(
_tempo_map.frame_rate(),
_tempo_map.meter_at(_origin));
_tempo_map.meter_at (_origin_b));
return lrint(beats * frames_per_beat);
}
@ -40,10 +40,10 @@ double
BeatsFramesConverter::from(sframes_t frames) const
{
// FIXME: assumes tempo never changes after origin
const Tempo& tempo = _tempo_map.tempo_at(_origin);
const Tempo& tempo = _tempo_map.tempo_at (_origin_b);
const double frames_per_beat = tempo.frames_per_beat(
_tempo_map.frame_rate(),
_tempo_map.meter_at(_origin));
_tempo_map.meter_at (_origin_b));
return frames / frames_per_beat;
}

View file

@ -29,6 +29,7 @@ namespace Evoral {
template<typename A, typename B>
class TimeConverter {
public:
TimeConverter (B ob = 0) : _origin_b (ob) {}
virtual ~TimeConverter() {}
/** Convert A time to B time (A to B) */
@ -36,6 +37,17 @@ public:
/** Convert B time to A time (A from B) */
virtual A from(B b) const = 0;
B origin_b () const {
return _origin_b;
}
void set_origin_b (B o) {
_origin_b = o;
}
protected:
B _origin_b;
};

View file

@ -1145,7 +1145,10 @@ ControlList::cut (iterator start, iterator end)
return nal;
}
/** @param op 0 = cut, 1 = copy, 2 = clear */
/** @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)
{
@ -1247,9 +1250,10 @@ ControlList::copy (double start, double end)
void
ControlList::clear (double start, double end)
{
(void) cut_copy_clear (start, end, 2);
cut_copy_clear (start, end, 2);
}
/** @param pos Position in model coordinates */
bool
ControlList::paste (ControlList& alist, double pos, float /*times*/)
{