2 Copyright (C) 2002-2003 Paul Davis
4 This program is free software; you can redistribute it and/or modify
5 it under the terms of the GNU General Public License as published by
6 the Free Software Foundation; either version 2 of the License, or
7 (at your option) any later version.
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 GNU General Public License for more details.
14 You should have received a copy of the GNU General Public License
15 along with this program; if not, write to the Free Software
16 Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
25 #include "pbd/stl_delete.h"
26 #include "pbd/memento_command.h"
27 #include "pbd/stacktrace.h"
29 #include "ardour/automation_list.h"
30 #include "ardour/dB.h"
31 #include "evoral/Curve.hpp"
33 #include "simplerect.h"
34 #include "automation_line.h"
35 #include "control_point.h"
36 #include "gui_thread.h"
37 #include "rgb_macros.h"
38 #include "ardour_ui.h"
39 #include "public_editor.h"
41 #include "selection.h"
42 #include "time_axis_view.h"
43 #include "point_selection.h"
44 #include "automation_selectable.h"
45 #include "automation_time_axis.h"
46 #include "public_editor.h"
48 #include "ardour/event_type_map.h"
49 #include "ardour/session.h"
54 using namespace ARDOUR;
56 using namespace Editing;
57 using namespace Gnome; // for Canvas
59 static const Evoral::IdentityConverter<double, sframes_t> default_converter;
61 AutomationLine::AutomationLine (const string& name, TimeAxisView& tv, ArdourCanvas::Group& parent,
62 boost::shared_ptr<AutomationList> al,
63 const Evoral::TimeConverter<double, sframes_t>* converter)
67 , _parent_group (parent)
68 , _time_converter (converter ? (*converter) : default_converter)
70 _interpolation = al->interpolation();
71 points_visible = false;
72 update_pending = false;
73 _uses_gain_mapping = false;
76 terminal_points_can_slide = true;
79 group = new ArdourCanvas::Group (parent);
80 group->property_x() = 0.0;
81 group->property_y() = 0.0;
83 line = new ArdourCanvas::Line (*group);
84 line->property_width_pixels() = (guint)1;
85 line->set_data ("line", this);
87 line->signal_event().connect (sigc::mem_fun (*this, &AutomationLine::event_handler));
89 alist->StateChanged.connect (_state_connection, boost::bind (&AutomationLine::list_changed, this), gui_context());
91 trackview.session()->register_with_memento_command_factory(alist->id(), this);
93 if (alist->parameter().type() == GainAutomation ||
94 alist->parameter().type() == EnvelopeAutomation) {
95 set_uses_gain_mapping (true);
98 set_interpolation(alist->interpolation());
101 AutomationLine::~AutomationLine ()
103 vector_delete (&control_points);
108 AutomationLine::event_handler (GdkEvent* event)
110 return PublicEditor::instance().canvas_line_event (event, line, this);
114 AutomationLine::queue_reset ()
116 if (!update_pending) {
117 update_pending = true;
118 Gtkmm2ext::UI::instance()->call_slot (boost::bind (&AutomationLine::reset, this));
123 AutomationLine::show ()
125 if (_interpolation != AutomationList::Discrete) {
129 if (points_visible) {
130 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
139 AutomationLine::hide ()
142 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
149 AutomationLine::control_point_box_size ()
151 if (_interpolation == AutomationList::Discrete) {
152 return max((_height*4.0) / (double)(alist->parameter().max() - alist->parameter().min()),
156 if (_height > TimeAxisView::hLarger) {
158 } else if (_height > (guint32) TimeAxisView::hNormal) {
165 AutomationLine::set_height (guint32 h)
170 double bsz = control_point_box_size();
172 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
173 (*i)->set_size (bsz);
181 AutomationLine::set_line_color (uint32_t color)
184 line->property_fill_color_rgba() = color;
188 AutomationLine::set_uses_gain_mapping (bool yn)
190 if (yn != _uses_gain_mapping) {
191 _uses_gain_mapping = yn;
197 AutomationLine::nth (uint32_t n)
199 if (n < control_points.size()) {
200 return control_points[n];
207 AutomationLine::modify_point_y (ControlPoint& cp, double y)
209 /* clamp y-coord appropriately. y is supposed to be a normalized fraction (0.0-1.0),
210 and needs to be converted to a canvas unit distance.
215 y = _height - (y * _height);
217 double const x = trackview.editor().frame_to_unit (_time_converter.to((*cp.model())->when));
219 trackview.editor().session()->begin_reversible_command (_("automation event move"));
220 trackview.editor().session()->add_command (new MementoCommand<AutomationList>(*alist.get(), &get_state(), 0));
222 cp.move_to (x, y, ControlPoint::Full);
223 reset_line_coords (cp);
225 if (line_points.size() > 1) {
226 line->property_points() = line_points;
230 sync_model_with_view_point (cp, false, 0);
233 update_pending = false;
235 trackview.editor().session()->add_command (new MementoCommand<AutomationList>(*alist.get(), 0, &alist->get_state()));
236 trackview.editor().session()->commit_reversible_command ();
237 trackview.editor().session()->set_dirty ();
240 /** Move a view point to a new position (without changing the model)
241 * @param y New y position as a normalised fraction (0.0-1.0)
244 AutomationLine::modify_view_point (ControlPoint& cp, double x, double y, bool keep_x, bool with_push)
247 uint32_t last_movable = UINT_MAX;
248 double x_limit = DBL_MAX;
250 /* clamp y-coord appropriately. y is supposed to be a normalized fraction (0.0-1.0),
251 and needs to be converted to a canvas unit distance.
256 y = _height - (y * _height);
258 if (cp.can_slide() && !keep_x) {
260 /* x-coord cannot move beyond adjacent points or the start/end, and is
261 already in frames. it needs to be converted to canvas units.
264 x = trackview.editor().frame_to_unit (x);
266 /* clamp x position using view coordinates */
268 ControlPoint *before;
271 if (cp.view_index()) {
272 before = nth (cp.view_index() - 1);
273 x = max (x, before->get_x()+1.0);
280 if (cp.view_index() < control_points.size() - 1) {
282 after = nth (cp.view_index() + 1);
284 /*if it is a "spike" leave the x alone */
286 if (after->get_x() - before->get_x() < 2) {
290 x = min (x, after->get_x()-1.0);
300 /* find the first point that can't move */
302 for (uint32_t n = cp.view_index() + 1; (after = nth (n)) != 0; ++n) {
303 if (!after->can_slide()) {
304 x_limit = after->get_x() - 1.0;
305 last_movable = after->view_index();
310 delta = x - cp.get_x();
315 /* leave the x-coordinate alone */
317 x = trackview.editor().frame_to_unit (_time_converter.to((*cp.model())->when));
323 cp.move_to (x, y, ControlPoint::Full);
324 reset_line_coords (cp);
328 uint32_t limit = min (control_points.size(), (size_t)last_movable);
330 /* move the current point to wherever the user told it to go, subject
334 cp.move_to (min (x, x_limit), y, ControlPoint::Full);
335 reset_line_coords (cp);
337 /* now move all subsequent control points, to reflect the motion.
340 for (uint32_t i = cp.view_index() + 1; i < limit; ++i) {
341 ControlPoint *p = nth (i);
344 if (p->can_slide()) {
345 new_x = min (p->get_x() + delta, x_limit);
346 p->move_to (new_x, p->get_y(), ControlPoint::Full);
347 reset_line_coords (*p);
354 AutomationLine::reset_line_coords (ControlPoint& cp)
356 if (cp.view_index() < line_points.size()) {
357 line_points[cp.view_index()].set_x (cp.get_x());
358 line_points[cp.view_index()].set_y (cp.get_y());
363 AutomationLine::sync_model_with_view_points (list<ControlPoint*> cp, bool did_push, int64_t distance)
365 update_pending = true;
367 for (list<ControlPoint*>::iterator i = cp.begin(); i != cp.end(); ++i) {
368 sync_model_with_view_point (**i, did_push, distance);
373 AutomationLine::model_representation (ControlPoint& cp, ModelRepresentation& mr)
375 /* part one: find out where the visual control point is.
376 initial results are in canvas units. ask the
377 line to convert them to something relevant.
380 mr.xval = cp.get_x();
381 mr.yval = 1.0 - (cp.get_y() / _height);
383 /* if xval has not changed, set it directly from the model to avoid rounding errors */
385 if (mr.xval == trackview.editor().frame_to_unit(_time_converter.to((*cp.model())->when))) {
386 mr.xval = (*cp.model())->when;
388 mr.xval = trackview.editor().unit_to_frame (mr.xval);
391 /* convert to model units
394 view_to_model_coord (mr.xval, mr.yval);
396 /* part 2: find out where the model point is now
399 mr.xpos = (*cp.model())->when;
400 mr.ypos = (*cp.model())->value;
402 /* part 3: get the position of the visual control
403 points before and after us.
406 ControlPoint* before;
409 if (cp.view_index()) {
410 before = nth (cp.view_index() - 1);
415 after = nth (cp.view_index() + 1);
418 mr.xmin = (*before->model())->when;
419 mr.ymin = (*before->model())->value;
420 mr.start = before->model();
425 mr.start = cp.model();
429 mr.end = after->model();
439 AutomationLine::determine_visible_control_points (ALPoints& points)
441 uint32_t view_index, pi, n;
442 AutomationList::iterator model;
444 uint32_t this_rx = 0;
445 uint32_t prev_rx = 0;
446 uint32_t this_ry = 0;
447 uint32_t prev_ry = 0;
451 /* hide all existing points, and the line */
453 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
459 if (points.empty()) {
463 npoints = points.size();
465 /* compute derivative/slope for the entire line */
467 slope = new double[npoints];
469 for (n = 0; n < npoints - 1; ++n) {
470 double xdelta = points[n+1].x - points[n].x;
471 double ydelta = points[n+1].y - points[n].y;
472 slope[n] = ydelta/xdelta;
475 box_size = (uint32_t) control_point_box_size ();
477 /* read all points and decide which ones to show as control points */
481 for (model = alist->begin(), pi = 0; pi < npoints; ++model, ++pi) {
483 double tx = points[pi].x;
484 double ty = points[pi].y;
486 if (find (_always_in_view.begin(), _always_in_view.end(), (*model)->when) != _always_in_view.end()) {
487 add_visible_control_point (view_index, pi, tx, ty, model, npoints);
494 if (isnan (tx) || isnan (ty)) {
495 warning << string_compose (_("Ignoring illegal points on AutomationLine \"%1\""),
500 /* now ensure that the control_points vector reflects the current curve
501 state, but don't plot control points too close together. also, don't
502 plot a series of points all with the same value.
504 always plot the first and last points, of course.
507 if (invalid_point (points, pi)) {
508 /* for some reason, we are supposed to ignore this point,
509 but still keep track of the model index.
514 if (pi > 0 && pi < npoints - 1) {
515 if (slope[pi] == slope[pi-1]) {
517 /* no reason to display this point */
523 /* need to round here. the ultimate coordinates are integer
524 pixels, so tiny deltas in the coords will be eliminated
525 and we end up with "colinear" line segments. since the
526 line rendering code in libart doesn't like this very
527 much, we eliminate them here. don't do this for the first and last
531 this_rx = (uint32_t) rint (tx);
532 this_ry = (uint32_t) rint (ty);
534 if (view_index && pi != npoints && /* not the first, not the last */
535 (((this_rx == prev_rx) && (this_ry == prev_ry)) || /* same point */
536 (((this_rx - prev_rx) < (box_size + 2)) && /* not identical, but still too close horizontally */
537 (abs ((int)(this_ry - prev_ry)) < (int) (box_size + 2))))) { /* too close vertically */
541 /* ok, we should display this point */
543 add_visible_control_point (view_index, pi, tx, ty, model, npoints);
551 /* discard extra CP's to avoid confusing ourselves */
553 while (control_points.size() > view_index) {
554 ControlPoint* cp = control_points.back();
555 control_points.pop_back ();
559 if (!terminal_points_can_slide) {
560 control_points.back()->set_can_slide(false);
565 if (view_index > 1) {
567 npoints = view_index;
569 /* reset the line coordinates */
571 while (line_points.size() < npoints) {
572 line_points.push_back (Art::Point (0,0));
575 while (line_points.size() > npoints) {
576 line_points.pop_back ();
579 for (view_index = 0; view_index < npoints; ++view_index) {
580 line_points[view_index].set_x (control_points[view_index]->get_x());
581 line_points[view_index].set_y (control_points[view_index]->get_y());
584 line->property_points() = line_points;
586 if (_visible && _interpolation != AutomationList::Discrete) {
592 set_selected_points (trackview.editor().get_selection().points);
596 AutomationLine::get_verbose_cursor_string (double fraction) const
598 std::string s = fraction_to_string (fraction);
599 if (_uses_gain_mapping) {
607 * @param fraction y fraction
608 * @return string representation of this value, using dB if appropriate.
611 AutomationLine::fraction_to_string (double fraction) const
615 if (_uses_gain_mapping) {
616 if (fraction == 0.0) {
617 snprintf (buf, sizeof (buf), "-inf");
619 snprintf (buf, sizeof (buf), "%.1f", accurate_coefficient_to_dB (slider_position_to_gain (fraction)));
623 view_to_model_coord (dummy, fraction);
624 if (EventTypeMap::instance().is_integer (alist->parameter())) {
625 snprintf (buf, sizeof (buf), "%d", (int)fraction);
627 snprintf (buf, sizeof (buf), "%.2f", fraction);
636 * @param s Value string in the form as returned by fraction_to_string.
637 * @return Corresponding y fraction.
640 AutomationLine::string_to_fraction (string const & s) const
647 sscanf (s.c_str(), "%lf", &v);
649 if (_uses_gain_mapping) {
650 v = gain_to_slider_position (dB_to_coefficient (v));
653 model_to_view_coord (dummy, v);
660 AutomationLine::invalid_point (ALPoints& p, uint32_t index)
662 return p[index].x == max_frames && p[index].y == DBL_MAX;
666 AutomationLine::invalidate_point (ALPoints& p, uint32_t index)
668 p[index].x = max_frames;
669 p[index].y = DBL_MAX;
672 /** Start dragging a single point, possibly adding others if the supplied point is selected and there
673 * are other selected points.
675 * @param cp Point to drag.
676 * @param x Initial x position (frames).
677 * @param fraction Initial y position (as a fraction of the track height, where 0 is the bottom and 1 the top)
680 AutomationLine::start_drag_single (ControlPoint* cp, nframes_t x, float fraction)
682 trackview.editor().session()->begin_reversible_command (_("automation event move"));
683 trackview.editor().session()->add_command (new MementoCommand<AutomationList>(*alist.get(), &get_state(), 0));
685 _drag_points.clear ();
686 _drag_points.push_back (cp);
688 if (cp->selected ()) {
689 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
690 if (*i != cp && (*i)->selected()) {
691 _drag_points.push_back (*i);
696 start_drag_common (x, fraction);
699 /** Start dragging a line vertically (with no change in x)
700 * @param i1 Control point index of the `left' point on the line.
701 * @param i2 Control point index of the `right' point on the line.
702 * @param fraction Initial y position (as a fraction of the track height, where 0 is the bottom and 1 the top)
705 AutomationLine::start_drag_line (uint32_t i1, uint32_t i2, float fraction)
707 trackview.editor().session()->begin_reversible_command (_("automation range move"));
708 trackview.editor().session()->add_command (new MementoCommand<AutomationList>(*alist.get(), &get_state(), 0));
710 _drag_points.clear ();
712 // check if one of the control points on the line is in a selected range
713 bool range_found = false;
716 for (uint32_t i = i1 ; i <= i2; i++) {
718 if (cp->selected()) {
724 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
725 if ((*i)->selected()) {
726 _drag_points.push_back (*i);
730 for (uint32_t i = i1 ; i <= i2; i++) {
731 _drag_points.push_back (nth (i));
735 start_drag_common (0, fraction);
738 /** Start dragging multiple points (with no change in x)
739 * @param cp Points to drag.
740 * @param fraction Initial y position (as a fraction of the track height, where 0 is the bottom and 1 the top)
743 AutomationLine::start_drag_multiple (list<ControlPoint*> cp, float fraction, XMLNode* state)
745 trackview.editor().session()->begin_reversible_command (_("automation range move"));
746 trackview.editor().session()->add_command (new MementoCommand<AutomationList>(*alist.get(), state, 0));
749 start_drag_common (0, fraction);
752 /** Common parts of starting a drag.
753 * @param d Description of the drag.
754 * @param x Starting x position in frames, or 0 if x is being ignored.
755 * @param fraction Starting y position (as a fraction of the track height, where 0 is the bottom and 1 the top)
758 AutomationLine::start_drag_common (nframes_t x, float fraction)
762 _last_drag_fraction = fraction;
763 _drag_had_movement = false;
767 /** Should be called to indicate motion during a drag.
768 * @param x New x position of the drag in frames, or 0 if x is being ignored.
769 * @param fraction New y fraction.
770 * @return x position and y fraction that were actually used (once clamped).
772 pair<nframes_t, float>
773 AutomationLine::drag_motion (nframes_t x, float fraction, bool with_push)
775 int64_t const dx = x - drag_x;
776 double dy = fraction - _last_drag_fraction;
778 /* clamp y so that the "lowest" point hits the bottom but goes no further
779 and similarly with the "highest" and the top
781 for (list<ControlPoint*>::iterator i = _drag_points.begin(); i != _drag_points.end(); ++i) {
782 double const y = ((_height - (*i)->get_y()) / _height) + dy;
791 pair<nframes_t, float> const clamped (drag_x + dx, _last_drag_fraction + dy);
794 _last_drag_fraction += dy;
796 for (list<ControlPoint*>::iterator i = _drag_points.begin(); i != _drag_points.end(); ++i) {
800 trackview.editor().unit_to_frame ((*i)->get_x()) + dx,
801 ((_height - (*i)->get_y()) / _height) + dy,
807 if (line_points.size() > 1) {
808 line->property_points() = line_points;
811 _drag_had_movement = true;
812 did_push = with_push;
817 /** Should be called to indicate the end of a drag */
819 AutomationLine::end_drag ()
821 if (!_drag_had_movement) {
827 sync_model_with_view_points (_drag_points, did_push, drag_distance);
831 update_pending = false;
833 trackview.editor().session()->add_command (new MementoCommand<AutomationList>(*alist.get(), 0, &alist->get_state()));
834 trackview.editor().session()->commit_reversible_command ();
835 trackview.editor().session()->set_dirty ();
839 AutomationLine::sync_model_with_view_point (ControlPoint& cp, bool did_push, int64_t distance)
841 ModelRepresentation mr;
844 model_representation (cp, mr);
846 /* how much are we changing the central point by */
848 ydelta = mr.yval - mr.ypos;
851 apply the full change to the central point, and interpolate
852 on both axes to cover all model points represented
853 by the control point.
856 /* change all points before the primary point */
858 for (AutomationList::iterator i = mr.start; i != cp.model(); ++i) {
860 double fract = ((*i)->when - mr.xmin) / (mr.xpos - mr.xmin);
861 double y_delta = ydelta * fract;
862 double x_delta = distance * fract;
866 if (y_delta || x_delta) {
867 alist->modify (i, (*i)->when + x_delta, mr.ymin + y_delta);
871 /* change the primary point */
873 update_pending = true;
874 alist->modify (cp.model(), mr.xval, mr.yval);
877 /* change later points */
879 AutomationList::iterator i = cp.model();
883 while (i != mr.end) {
885 double delta = ydelta * (mr.xmax - (*i)->when) / (mr.xmax - mr.xpos);
887 /* all later points move by the same distance along the x-axis as the main point */
890 alist->modify (i, (*i)->when + distance, (*i)->value + delta);
898 /* move all points after the range represented by the view by the same distance
899 as the main point moved.
902 alist->slide (mr.end, drag_distance);
907 AutomationLine::control_points_adjacent (double xval, uint32_t & before, uint32_t& after)
909 ControlPoint *bcp = 0;
910 ControlPoint *acp = 0;
913 unit_xval = trackview.editor().frame_to_unit (xval);
915 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
917 if ((*i)->get_x() <= unit_xval) {
919 if (!bcp || (*i)->get_x() > bcp->get_x()) {
921 before = bcp->view_index();
924 } else if ((*i)->get_x() > unit_xval) {
926 after = acp->view_index();
935 AutomationLine::is_last_point (ControlPoint& cp)
937 ModelRepresentation mr;
939 model_representation (cp, mr);
941 // If the list is not empty, and the point is the last point in the list
943 if (!alist->empty() && mr.end == alist->end()) {
951 AutomationLine::is_first_point (ControlPoint& cp)
953 ModelRepresentation mr;
955 model_representation (cp, mr);
957 // If the list is not empty, and the point is the first point in the list
959 if (!alist->empty() && mr.start == alist->begin()) {
966 // This is copied into AudioRegionGainLine
968 AutomationLine::remove_point (ControlPoint& cp)
970 ModelRepresentation mr;
972 model_representation (cp, mr);
974 trackview.editor().session()->begin_reversible_command (_("remove control point"));
975 XMLNode &before = alist->get_state();
977 alist->erase (mr.start, mr.end);
979 trackview.editor().session()->add_command(new MementoCommand<AutomationList>(
980 *alist.get(), &before, &alist->get_state()));
981 trackview.editor().session()->commit_reversible_command ();
982 trackview.editor().session()->set_dirty ();
986 AutomationLine::get_selectables (nframes_t& start, nframes_t& end,
987 double botfrac, double topfrac, list<Selectable*>& results)
994 bool collecting = false;
996 /* Curse X11 and its inverted coordinate system! */
998 bot = (1.0 - topfrac) * _height;
999 top = (1.0 - botfrac) * _height;
1001 nstart = max_frames;
1004 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1005 double when = (*(*i)->model())->when;
1007 if (when >= start && when <= end) {
1009 if ((*i)->get_y() >= bot && (*i)->get_y() <= top) {
1012 (*i)->set_visible(true);
1014 nstart = min (nstart, when);
1015 nend = max (nend, when);
1021 results.push_back (new AutomationSelectable (nstart, nend, botfrac, topfrac, &trackview));
1023 nstart = max_frames;
1031 results.push_back (new AutomationSelectable (nstart, nend, botfrac, topfrac, &trackview));
1037 AutomationLine::get_inverted_selectables (Selection&, list<Selectable*>& /*results*/)
1042 /** Take a PointSelection and find ControlPoints that fall within it */
1044 AutomationLine::point_selection_to_control_points (PointSelection const & s)
1046 list<ControlPoint*> cp;
1048 for (PointSelection::const_iterator i = s.begin(); i != s.end(); ++i) {
1050 if (i->track != &trackview) {
1054 /* Curse X11 and its inverted coordinate system! */
1056 double const bot = (1.0 - i->high_fract) * _height;
1057 double const top = (1.0 - i->low_fract) * _height;
1059 for (vector<ControlPoint*>::iterator j = control_points.begin(); j != control_points.end(); ++j) {
1061 double const rstart = trackview.editor().frame_to_unit (i->start);
1062 double const rend = trackview.editor().frame_to_unit (i->end);
1064 if ((*j)->get_x() >= rstart && (*j)->get_x() <= rend) {
1065 if ((*j)->get_y() >= bot && (*j)->get_y() <= top) {
1077 AutomationLine::set_selected_points (PointSelection& points)
1079 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1080 (*i)->set_selected (false);
1083 if (!points.empty()) {
1084 list<ControlPoint*> cp = point_selection_to_control_points (points);
1085 for (list<ControlPoint*>::iterator i = cp.begin(); i != cp.end(); ++i) {
1086 (*i)->set_selected (true);
1093 void AutomationLine::set_colors ()
1095 set_line_color (ARDOUR_UI::config()->canvasvar_AutomationLine.get());
1096 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1102 AutomationLine::list_changed ()
1108 AutomationLine::reset_callback (const Evoral::ControlList& events)
1110 ALPoints tmp_points;
1111 uint32_t npoints = events.size();
1114 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1117 control_points.clear ();
1122 AutomationList::const_iterator ai;
1124 for (ai = events.begin(); ai != events.end(); ++ai) {
1126 double translated_x = (*ai)->when;
1127 double translated_y = (*ai)->value;
1128 model_to_view_coord (translated_x, translated_y);
1130 add_model_point (tmp_points, (*ai)->when, translated_y);
1133 determine_visible_control_points (tmp_points);
1138 AutomationLine::add_model_point (ALPoints& tmp_points, double frame, double yfract)
1140 tmp_points.push_back (ALPoint (trackview.editor().frame_to_unit (_time_converter.to(frame)),
1141 _height - (yfract * _height)));
1145 AutomationLine::reset ()
1147 update_pending = false;
1153 alist->apply_to_points (*this, &AutomationLine::reset_callback);
1157 AutomationLine::clear ()
1159 /* parent must create command */
1160 XMLNode &before = get_state();
1162 trackview.editor().session()->add_command (
1163 new MementoCommand<AutomationLine>(*this, &before, &get_state()));
1164 trackview.editor().session()->commit_reversible_command ();
1165 trackview.editor().session()->set_dirty ();
1169 AutomationLine::change_model (AutomationList::iterator /*i*/, double /*x*/, double /*y*/)
1174 AutomationLine::set_list(boost::shared_ptr<ARDOUR::AutomationList> list)
1181 AutomationLine::show_all_control_points ()
1183 points_visible = true;
1185 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1186 if (!(*i)->visible()) {
1188 (*i)->set_visible (true);
1194 AutomationLine::hide_all_but_selected_control_points ()
1196 if (alist->interpolation() == AutomationList::Discrete) {
1200 points_visible = false;
1202 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1203 if (!(*i)->selected()) {
1204 (*i)->set_visible (false);
1210 AutomationLine::track_entered()
1212 if (alist->interpolation() != AutomationList::Discrete) {
1213 show_all_control_points();
1218 AutomationLine::track_exited()
1220 if (alist->interpolation() != AutomationList::Discrete) {
1221 hide_all_but_selected_control_points();
1226 AutomationLine::get_state (void)
1228 /* function as a proxy for the model */
1229 return alist->get_state();
1233 AutomationLine::set_state (const XMLNode &node, int version)
1235 /* function as a proxy for the model */
1236 return alist->set_state (node, version);
1240 AutomationLine::view_to_model_coord (double& x, double& y) const
1242 /* TODO: This should be more generic ... */
1243 if (alist->parameter().type() == GainAutomation ||
1244 alist->parameter().type() == EnvelopeAutomation) {
1245 y = slider_position_to_gain (y);
1248 } else if (alist->parameter().type() == PanAutomation) {
1249 // vertical coordinate axis reversal
1251 } else if (alist->parameter().type() == PluginAutomation) {
1252 y = y * (double)(alist->get_max_y()- alist->get_min_y()) + alist->get_min_y();
1254 y = (int)(y * alist->parameter().max());
1257 x = _time_converter.from(x);
1261 AutomationLine::model_to_view_coord (double& x, double& y) const
1263 /* TODO: This should be more generic ... */
1264 if (alist->parameter().type() == GainAutomation ||
1265 alist->parameter().type() == EnvelopeAutomation) {
1266 y = gain_to_slider_position (y);
1267 } else if (alist->parameter().type() == PanAutomation) {
1268 // vertical coordinate axis reversal
1270 } else if (alist->parameter().type() == PluginAutomation) {
1271 y = (y - alist->get_min_y()) / (double)(alist->get_max_y()- alist->get_min_y());
1273 y = y / (double)alist->parameter().max(); /* ... like this */
1276 x = _time_converter.to(x);
1281 AutomationLine::set_interpolation(AutomationList::InterpolationStyle style)
1283 _interpolation = style;
1285 if (style == AutomationList::Discrete) {
1286 show_all_control_points();
1289 hide_all_but_selected_control_points();
1295 AutomationLine::add_visible_control_point (uint32_t view_index, uint32_t pi, double tx, double ty, AutomationList::iterator model, uint32_t npoints)
1297 if (view_index >= control_points.size()) {
1299 /* make sure we have enough control points */
1301 ControlPoint* ncp = new ControlPoint (*this);
1302 ncp->set_size (control_point_box_size ());
1304 control_points.push_back (ncp);
1307 ControlPoint::ShapeType shape;
1309 if (!terminal_points_can_slide) {
1311 control_points[view_index]->set_can_slide(false);
1313 shape = ControlPoint::Start;
1315 shape = ControlPoint::Full;
1317 } else if (pi == npoints - 1) {
1318 control_points[view_index]->set_can_slide(false);
1319 shape = ControlPoint::End;
1321 control_points[view_index]->set_can_slide(true);
1322 shape = ControlPoint::Full;
1325 control_points[view_index]->set_can_slide(true);
1326 shape = ControlPoint::Full;
1329 control_points[view_index]->reset (tx, ty, model, view_index, shape);
1331 /* finally, control visibility */
1333 if (_visible && points_visible) {
1334 control_points[view_index]->show ();
1335 control_points[view_index]->set_visible (true);
1337 if (!points_visible) {
1338 control_points[view_index]->set_visible (false);
1344 AutomationLine::add_always_in_view (double x)
1346 _always_in_view.push_back (x);
1347 alist->apply_to_points (*this, &AutomationLine::reset_callback);
1351 AutomationLine::clear_always_in_view ()
1353 _always_in_view.clear ();
1354 alist->apply_to_points (*this, &AutomationLine::reset_callback);