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_time_axis.h"
45 #include "public_editor.h"
47 #include "ardour/event_type_map.h"
48 #include "ardour/session.h"
53 using namespace ARDOUR;
55 using namespace Editing;
56 using namespace Gnome; // for Canvas
58 static const Evoral::IdentityConverter<double, framepos_t> default_converter;
60 AutomationLine::AutomationLine (const string& name, TimeAxisView& tv, ArdourCanvas::Group& parent,
61 boost::shared_ptr<AutomationList> al,
62 const Evoral::TimeConverter<double, framepos_t>* converter)
66 , _parent_group (parent)
68 , _time_converter (converter ? (*converter) : default_converter)
69 , _maximum_time (max_framepos)
71 points_visible = false;
72 update_pending = false;
73 _uses_gain_mapping = false;
77 terminal_points_can_slide = true;
80 group = new ArdourCanvas::Group (parent);
81 group->property_x() = 0.0;
82 group->property_y() = 0.0;
84 line = new ArdourCanvas::Line (*group);
85 line->property_width_pixels() = (guint)1;
86 line->set_data ("line", this);
88 line->signal_event().connect (sigc::mem_fun (*this, &AutomationLine::event_handler));
90 trackview.session()->register_with_memento_command_factory(alist->id(), this);
92 if (alist->parameter().type() == GainAutomation ||
93 alist->parameter().type() == EnvelopeAutomation) {
94 set_uses_gain_mapping (true);
97 interpolation_changed (alist->interpolation ());
102 AutomationLine::~AutomationLine ()
104 vector_delete (&control_points);
109 AutomationLine::event_handler (GdkEvent* event)
111 return PublicEditor::instance().canvas_line_event (event, line, this);
115 AutomationLine::queue_reset ()
117 if (!update_pending) {
118 update_pending = true;
119 Gtkmm2ext::UI::instance()->call_slot (invalidator (*this), boost::bind (&AutomationLine::reset, this));
124 AutomationLine::show ()
126 if (alist->interpolation() != AutomationList::Discrete) {
130 if (points_visible) {
131 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
140 AutomationLine::hide ()
143 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
150 AutomationLine::control_point_box_size ()
152 if (alist->interpolation() == AutomationList::Discrete) {
153 return max((_height*4.0) / (double)(alist->parameter().max() - alist->parameter().min()),
157 if (_height > TimeAxisView::preset_height (HeightLarger)) {
159 } else if (_height > (guint32) TimeAxisView::preset_height (HeightNormal)) {
166 AutomationLine::set_height (guint32 h)
171 double bsz = control_point_box_size();
173 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
174 (*i)->set_size (bsz);
182 AutomationLine::set_line_color (uint32_t color)
185 line->property_fill_color_rgba() = color;
189 AutomationLine::set_uses_gain_mapping (bool yn)
191 if (yn != _uses_gain_mapping) {
192 _uses_gain_mapping = yn;
198 AutomationLine::nth (uint32_t n)
200 if (n < control_points.size()) {
201 return control_points[n];
208 AutomationLine::nth (uint32_t n) const
210 if (n < control_points.size()) {
211 return control_points[n];
218 AutomationLine::modify_point_y (ControlPoint& cp, double y)
220 /* clamp y-coord appropriately. y is supposed to be a normalized fraction (0.0-1.0),
221 and needs to be converted to a canvas unit distance.
226 y = _height - (y * _height);
228 double const x = trackview.editor().frame_to_unit (_time_converter.to((*cp.model())->when) - _offset);
230 trackview.editor().session()->begin_reversible_command (_("automation event move"));
231 trackview.editor().session()->add_command (
232 new MementoCommand<AutomationList> (memento_command_binder(), &get_state(), 0)
235 cp.move_to (x, y, ControlPoint::Full);
237 reset_line_coords (cp);
239 if (line_points.size() > 1) {
240 line->property_points() = line_points;
244 sync_model_with_view_point (cp, false, 0);
247 update_pending = false;
249 trackview.editor().session()->add_command (
250 new MementoCommand<AutomationList> (memento_command_binder(), 0, &alist->get_state())
253 trackview.editor().session()->commit_reversible_command ();
254 trackview.editor().session()->set_dirty ();
258 AutomationLine::reset_line_coords (ControlPoint& cp)
260 if (cp.view_index() < line_points.size()) {
261 line_points[cp.view_index()].set_x (cp.get_x());
262 line_points[cp.view_index()].set_y (cp.get_y());
267 AutomationLine::sync_model_with_view_points (list<ControlPoint*> cp, bool did_push, int64_t distance)
269 update_pending = true;
271 for (list<ControlPoint*>::iterator i = cp.begin(); i != cp.end(); ++i) {
272 sync_model_with_view_point (**i, did_push, distance);
277 AutomationLine::model_representation (ControlPoint& cp, ModelRepresentation& mr)
279 /* part one: find out where the visual control point is.
280 initial results are in canvas units. ask the
281 line to convert them to something relevant.
284 mr.xval = cp.get_x();
285 mr.yval = 1.0 - (cp.get_y() / _height);
287 /* if xval has not changed, set it directly from the model to avoid rounding errors */
289 if (mr.xval == trackview.editor().frame_to_unit(_time_converter.to((*cp.model())->when)) - _offset) {
290 mr.xval = (*cp.model())->when - _offset;
292 mr.xval = trackview.editor().unit_to_frame (mr.xval);
293 mr.xval = _time_converter.from (mr.xval + _offset);
296 /* convert y to model units; the x was already done above
299 view_to_model_coord_y (mr.yval);
301 /* part 2: find out where the model point is now
304 mr.xpos = (*cp.model())->when - _offset;
305 mr.ypos = (*cp.model())->value;
307 /* part 3: get the position of the visual control
308 points before and after us.
311 ControlPoint* before;
314 if (cp.view_index()) {
315 before = nth (cp.view_index() - 1);
320 after = nth (cp.view_index() + 1);
323 mr.xmin = (*before->model())->when;
324 mr.ymin = (*before->model())->value;
325 mr.start = before->model();
330 mr.start = cp.model();
334 mr.end = after->model();
343 /** @param points AutomationLine points to consider. These will correspond 1-to-1 to
344 * points in the AutomationList, but will have been transformed so that they are in pixels;
345 * the x coordinate being the pixel distance from the start of the line (0, or the start
346 * of the AutomationRegionView if we are in one).
348 * @param skipped Number of points in the AutomationList that were skipped before
353 AutomationLine::determine_visible_control_points (ALPoints& points, int skipped)
355 uint32_t view_index, pi, n;
357 uint32_t this_rx = 0;
358 uint32_t prev_rx = 0;
359 uint32_t this_ry = 0;
360 uint32_t prev_ry = 0;
364 /* hide all existing points, and the line */
366 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
372 if (points.empty()) {
376 npoints = points.size();
378 /* compute derivative/slope for the entire line */
380 slope = new double[npoints];
382 for (n = 0; n < npoints - 1; ++n) {
383 double xdelta = points[n+1].x - points[n].x;
384 double ydelta = points[n+1].y - points[n].y;
385 slope[n] = ydelta/xdelta;
388 box_size = (uint32_t) control_point_box_size ();
390 /* read all points and decide which ones to show as control points */
394 /* skip over unused AutomationList points before we start */
396 AutomationList::iterator model = alist->begin ();
397 for (int i = 0; i < skipped; ++i) {
401 for (pi = 0; pi < npoints; ++model, ++pi) {
403 /* If this line is in an AutomationRegionView, this is an offset from the region position, in pixels */
404 double tx = points[pi].x;
405 double ty = points[pi].y;
407 if (find (_always_in_view.begin(), _always_in_view.end(), (*model)->when) != _always_in_view.end()) {
408 add_visible_control_point (view_index, pi, tx, ty, model, npoints);
415 if (isnan (tx) || isnan (ty)) {
416 warning << string_compose (_("Ignoring illegal points on AutomationLine \"%1\""),
421 /* now ensure that the control_points vector reflects the current curve
422 state, but don't plot control points too close together. also, don't
423 plot a series of points all with the same value.
425 always plot the first and last points, of course.
428 if (invalid_point (points, pi)) {
429 /* for some reason, we are supposed to ignore this point,
430 but still keep track of the model index.
435 if (pi > 0 && pi < npoints - 1) {
436 if (slope[pi] == slope[pi-1]) {
438 /* no reason to display this point */
444 /* need to round here. the ultimate coordinates are integer
445 pixels, so tiny deltas in the coords will be eliminated
446 and we end up with "colinear" line segments. since the
447 line rendering code in libart doesn't like this very
448 much, we eliminate them here. don't do this for the first and last
452 this_rx = (uint32_t) rint (tx);
453 this_ry = (uint32_t) rint (ty);
455 if (view_index && pi != npoints && /* not the first, not the last */
456 (((this_rx == prev_rx) && (this_ry == prev_ry)) || /* same point */
457 (((this_rx - prev_rx) < (box_size + 2)) && /* not identical, but still too close horizontally */
458 (abs ((int)(this_ry - prev_ry)) < (int) (box_size + 2))))) { /* too close vertically */
462 /* ok, we should display this point */
464 add_visible_control_point (view_index, pi, tx, ty, model, npoints);
472 /* discard extra CP's to avoid confusing ourselves */
474 while (control_points.size() > view_index) {
475 ControlPoint* cp = control_points.back();
476 control_points.pop_back ();
480 if (!terminal_points_can_slide) {
481 control_points.back()->set_can_slide(false);
486 if (view_index > 1) {
488 npoints = view_index;
490 /* reset the line coordinates */
492 while (line_points.size() < npoints) {
493 line_points.push_back (Art::Point (0,0));
496 while (line_points.size() > npoints) {
497 line_points.pop_back ();
500 for (view_index = 0; view_index < npoints; ++view_index) {
501 line_points[view_index].set_x (control_points[view_index]->get_x());
502 line_points[view_index].set_y (control_points[view_index]->get_y());
505 line->property_points() = line_points;
507 if (_visible && alist->interpolation() != AutomationList::Discrete) {
513 set_selected_points (trackview.editor().get_selection().points);
517 AutomationLine::get_verbose_cursor_string (double fraction) const
519 std::string s = fraction_to_string (fraction);
520 if (_uses_gain_mapping) {
528 * @param fraction y fraction
529 * @return string representation of this value, using dB if appropriate.
532 AutomationLine::fraction_to_string (double fraction) const
536 if (_uses_gain_mapping) {
537 if (fraction == 0.0) {
538 snprintf (buf, sizeof (buf), "-inf");
540 snprintf (buf, sizeof (buf), "%.1f", accurate_coefficient_to_dB (slider_position_to_gain_with_max (fraction, Config->get_max_gain())));
543 view_to_model_coord_y (fraction);
544 if (EventTypeMap::instance().is_integer (alist->parameter())) {
545 snprintf (buf, sizeof (buf), "%d", (int)fraction);
547 snprintf (buf, sizeof (buf), "%.2f", fraction);
556 * @param s Value string in the form as returned by fraction_to_string.
557 * @return Corresponding y fraction.
560 AutomationLine::string_to_fraction (string const & s) const
567 sscanf (s.c_str(), "%lf", &v);
569 if (_uses_gain_mapping) {
570 v = gain_to_slider_position_with_max (dB_to_coefficient (v), Config->get_max_gain());
573 model_to_view_coord (dummy, v);
580 AutomationLine::invalid_point (ALPoints& p, uint32_t index)
582 return p[index].x == max_framepos && p[index].y == DBL_MAX;
586 AutomationLine::invalidate_point (ALPoints& p, uint32_t index)
588 p[index].x = max_framepos;
589 p[index].y = DBL_MAX;
592 /** Start dragging a single point, possibly adding others if the supplied point is selected and there
593 * are other selected points.
595 * @param cp Point to drag.
596 * @param x Initial x position (units).
597 * @param fraction Initial y position (as a fraction of the track height, where 0 is the bottom and 1 the top)
600 AutomationLine::start_drag_single (ControlPoint* cp, double x, float fraction)
602 trackview.editor().session()->begin_reversible_command (_("automation event move"));
603 trackview.editor().session()->add_command (
604 new MementoCommand<AutomationList> (memento_command_binder(), &get_state(), 0)
607 _drag_points.clear ();
608 _drag_points.push_back (cp);
610 if (cp->get_selected ()) {
611 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
612 if (*i != cp && (*i)->get_selected()) {
613 _drag_points.push_back (*i);
618 start_drag_common (x, fraction);
621 /** Start dragging a line vertically (with no change in x)
622 * @param i1 Control point index of the `left' point on the line.
623 * @param i2 Control point index of the `right' point on the line.
624 * @param fraction Initial y position (as a fraction of the track height, where 0 is the bottom and 1 the top)
627 AutomationLine::start_drag_line (uint32_t i1, uint32_t i2, float fraction)
629 trackview.editor().session()->begin_reversible_command (_("automation range move"));
630 trackview.editor().session()->add_command (
631 new MementoCommand<AutomationList> (memento_command_binder (), &get_state(), 0)
634 _drag_points.clear ();
635 for (uint32_t i = i1; i <= i2; i++) {
636 _drag_points.push_back (nth (i));
639 start_drag_common (0, fraction);
642 /** Start dragging multiple points (with no change in x)
643 * @param cp Points to drag.
644 * @param fraction Initial y position (as a fraction of the track height, where 0 is the bottom and 1 the top)
647 AutomationLine::start_drag_multiple (list<ControlPoint*> cp, float fraction, XMLNode* state)
649 trackview.editor().session()->begin_reversible_command (_("automation range move"));
650 trackview.editor().session()->add_command (
651 new MementoCommand<AutomationList> (memento_command_binder(), state, 0)
655 start_drag_common (0, fraction);
659 struct ControlPointSorter
661 bool operator() (ControlPoint const * a, ControlPoint const * b) {
662 return a->get_x() < b->get_x();
666 /** Common parts of starting a drag.
667 * @param x Starting x position in units, or 0 if x is being ignored.
668 * @param fraction Starting y position (as a fraction of the track height, where 0 is the bottom and 1 the top)
671 AutomationLine::start_drag_common (double x, float fraction)
675 _last_drag_fraction = fraction;
676 _drag_had_movement = false;
679 _drag_points.sort (ControlPointSorter ());
681 /* find the additional points that will be dragged when the user is holding
685 uint32_t i = _drag_points.back()->view_index () + 1;
687 _push_points.clear ();
688 while ((p = nth (i)) != 0 && p->can_slide()) {
689 _push_points.push_back (p);
694 /** Should be called to indicate motion during a drag.
695 * @param x New x position of the drag in units, or undefined if ignore_x == true.
696 * @param fraction New y fraction.
697 * @return x position and y fraction that were actually used (once clamped).
700 AutomationLine::drag_motion (double x, float fraction, bool ignore_x, bool with_push)
702 /* setup the points that are to be moved this time round */
703 list<ControlPoint*> points = _drag_points;
705 copy (_push_points.begin(), _push_points.end(), back_inserter (points));
706 points.sort (ControlPointSorter ());
709 double dx = ignore_x ? 0 : (x - _drag_x);
710 double dy = fraction - _last_drag_fraction;
713 ControlPoint* before = 0;
714 ControlPoint* after = 0;
716 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
717 if ((*i)->get_x() < points.front()->get_x()) {
720 if ((*i)->get_x() > points.back()->get_x() && after == 0) {
725 double const before_x = before ? before->get_x() : 0;
726 double const after_x = after ? after->get_x() : DBL_MAX;
729 for (list<ControlPoint*>::iterator i = points.begin(); i != points.end(); ++i) {
731 if ((*i)->can_slide() && !ignore_x) {
734 double const a = (*i)->get_x() + dx;
735 double const b = before_x + 1;
743 if (after_x - before_x < 2) {
744 /* after and before are very close, so just leave this alone */
747 double const a = (*i)->get_x() + dx;
748 double const b = after_x - 1;
758 for (list<ControlPoint*>::iterator i = points.begin(); i != points.end(); ++i) {
759 double const y = ((_height - (*i)->get_y()) / _height) + dy;
768 pair<double, float> const clamped (_drag_x + dx, _last_drag_fraction + dy);
769 _drag_distance += dx;
771 _last_drag_fraction = fraction;
773 for (list<ControlPoint*>::iterator i = _drag_points.begin(); i != _drag_points.end(); ++i) {
774 (*i)->move_to ((*i)->get_x() + dx, (*i)->get_y() - _height * dy, ControlPoint::Full);
775 reset_line_coords (**i);
779 /* move push points, preserving their y */
780 for (list<ControlPoint*>::iterator i = _push_points.begin(); i != _push_points.end(); ++i) {
781 (*i)->move_to ((*i)->get_x() + dx, (*i)->get_y(), ControlPoint::Full);
782 reset_line_coords (**i);
786 if (line_points.size() > 1) {
787 line->property_points() = line_points;
790 _drag_had_movement = true;
791 did_push = with_push;
796 /** Should be called to indicate the end of a drag */
798 AutomationLine::end_drag ()
800 if (!_drag_had_movement) {
806 /* set up the points that were moved this time round */
807 list<ControlPoint*> points = _drag_points;
809 copy (_push_points.begin(), _push_points.end(), back_inserter (points));
810 points.sort (ControlPointSorter ());
813 sync_model_with_view_points (points, did_push, rint (_drag_distance * trackview.editor().get_current_zoom ()));
817 update_pending = false;
819 trackview.editor().session()->add_command (
820 new MementoCommand<AutomationList>(memento_command_binder (), 0, &alist->get_state())
823 trackview.editor().session()->set_dirty ();
827 AutomationLine::sync_model_with_view_point (ControlPoint& cp, bool did_push, int64_t distance)
829 ModelRepresentation mr;
832 model_representation (cp, mr);
834 /* how much are we changing the central point by */
836 ydelta = mr.yval - mr.ypos;
839 apply the full change to the central point, and interpolate
840 on both axes to cover all model points represented
841 by the control point.
844 /* change all points before the primary point */
846 for (AutomationList::iterator i = mr.start; i != cp.model(); ++i) {
848 double fract = ((*i)->when - mr.xmin) / (mr.xpos - mr.xmin);
849 double y_delta = ydelta * fract;
850 double x_delta = distance * fract;
854 if (y_delta || x_delta) {
855 alist->modify (i, (*i)->when + x_delta, mr.ymin + y_delta);
859 /* change the primary point */
861 update_pending = true;
862 alist->modify (cp.model(), mr.xval, mr.yval);
864 /* change later points */
866 AutomationList::iterator i = cp.model();
870 while (i != mr.end) {
872 double delta = ydelta * (mr.xmax - (*i)->when) / (mr.xmax - mr.xpos);
874 /* all later points move by the same distance along the x-axis as the main point */
877 alist->modify (i, (*i)->when + distance, (*i)->value + delta);
885 /* move all points after the range represented by the view by the same distance
886 as the main point moved.
889 alist->slide (mr.end, distance);
894 AutomationLine::control_points_adjacent (double xval, uint32_t & before, uint32_t& after)
896 ControlPoint *bcp = 0;
897 ControlPoint *acp = 0;
900 unit_xval = trackview.editor().frame_to_unit (xval);
902 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
904 if ((*i)->get_x() <= unit_xval) {
906 if (!bcp || (*i)->get_x() > bcp->get_x()) {
908 before = bcp->view_index();
911 } else if ((*i)->get_x() > unit_xval) {
913 after = acp->view_index();
922 AutomationLine::is_last_point (ControlPoint& cp)
924 ModelRepresentation mr;
926 model_representation (cp, mr);
928 // If the list is not empty, and the point is the last point in the list
930 if (!alist->empty() && mr.end == alist->end()) {
938 AutomationLine::is_first_point (ControlPoint& cp)
940 ModelRepresentation mr;
942 model_representation (cp, mr);
944 // If the list is not empty, and the point is the first point in the list
946 if (!alist->empty() && mr.start == alist->begin()) {
953 // This is copied into AudioRegionGainLine
955 AutomationLine::remove_point (ControlPoint& cp)
957 ModelRepresentation mr;
959 model_representation (cp, mr);
961 trackview.editor().session()->begin_reversible_command (_("remove control point"));
962 XMLNode &before = alist->get_state();
964 alist->erase (mr.start, mr.end);
966 trackview.editor().session()->add_command(
967 new MementoCommand<AutomationList> (memento_command_binder (), &before, &alist->get_state())
970 trackview.editor().session()->commit_reversible_command ();
971 trackview.editor().session()->set_dirty ();
974 /** Get selectable points within an area.
975 * @param start Start position in session frames.
976 * @param end End position in session frames.
977 * @param bot Bottom y range, as a fraction of line height, where 0 is the bottom of the line.
978 * @param top Top y range, as a fraction of line height, where 0 is the bottom of the line.
979 * @param result Filled in with selectable things; in this case, ControlPoints.
982 AutomationLine::get_selectables (
983 framepos_t start, framepos_t end, double botfrac, double topfrac, list<Selectable*>& results
986 /* convert fractions to display coordinates with 0 at the top of the track */
987 double const bot_track = (1 - topfrac) * trackview.current_height ();
988 double const top_track = (1 - botfrac) * trackview.current_height ();
990 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
991 double const model_when = (*(*i)->model())->when;
993 /* model_when is relative to the start of the source, so we just need to add on the origin_b here
994 (as it is the session frame position of the start of the source)
997 framepos_t const session_frames_when = _time_converter.to (model_when) + _time_converter.origin_b ();
999 if (session_frames_when >= start && session_frames_when <= end && (*i)->get_y() >= bot_track && (*i)->get_y() <= top_track) {
1000 results.push_back (*i);
1006 AutomationLine::get_inverted_selectables (Selection&, list<Selectable*>& /*results*/)
1011 /** Take a PointSelection and find ControlPoints that fall within it */
1013 AutomationLine::point_selection_to_control_points (PointSelection const & s)
1015 list<ControlPoint*> cp;
1017 for (PointSelection::const_iterator i = s.begin(); i != s.end(); ++i) {
1019 if (i->track != &trackview) {
1023 double const bot = (1 - i->high_fract) * trackview.current_height ();
1024 double const top = (1 - i->low_fract) * trackview.current_height ();
1026 for (vector<ControlPoint*>::iterator j = control_points.begin(); j != control_points.end(); ++j) {
1028 double const rstart = trackview.editor().frame_to_unit (_time_converter.to (i->start) - _offset);
1029 double const rend = trackview.editor().frame_to_unit (_time_converter.to (i->end) - _offset);
1031 if ((*j)->get_x() >= rstart && (*j)->get_x() <= rend) {
1032 if ((*j)->get_y() >= bot && (*j)->get_y() <= top) {
1044 AutomationLine::set_selected_points (PointSelection const & points)
1046 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1047 (*i)->set_selected (false);
1050 if (!points.empty()) {
1051 list<ControlPoint*> cp = point_selection_to_control_points (points);
1052 for (list<ControlPoint*>::iterator i = cp.begin(); i != cp.end(); ++i) {
1053 (*i)->set_selected (true);
1060 void AutomationLine::set_colors ()
1062 set_line_color (ARDOUR_UI::config()->canvasvar_AutomationLine.get());
1063 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1069 AutomationLine::list_changed ()
1075 AutomationLine::reset_callback (const Evoral::ControlList& events)
1077 ALPoints tmp_points;
1078 uint32_t npoints = events.size();
1081 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1084 control_points.clear ();
1089 AutomationList::const_iterator ai;
1092 for (ai = events.begin(); ai != events.end(); ++ai) {
1094 double translated_x = (*ai)->when;
1095 double translated_y = (*ai)->value;
1096 model_to_view_coord (translated_x, translated_y);
1098 if (translated_x >= 0 && translated_x < _maximum_time) {
1099 tmp_points.push_back (ALPoint (
1100 trackview.editor().frame_to_unit (translated_x),
1101 _height - (translated_y * _height))
1103 } else if (translated_x < 0) {
1108 determine_visible_control_points (tmp_points, skipped);
1112 AutomationLine::reset ()
1114 update_pending = false;
1120 alist->apply_to_points (*this, &AutomationLine::reset_callback);
1124 AutomationLine::clear ()
1126 /* parent must create and commit command */
1127 XMLNode &before = alist->get_state();
1130 trackview.editor().session()->add_command (
1131 new MementoCommand<AutomationList> (memento_command_binder (), &before, &alist->get_state())
1136 AutomationLine::change_model (AutomationList::iterator /*i*/, double /*x*/, double /*y*/)
1141 AutomationLine::set_list (boost::shared_ptr<ARDOUR::AutomationList> list)
1149 AutomationLine::show_all_control_points ()
1152 // show the line but don't allow any control points
1156 points_visible = true;
1158 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1159 if (!(*i)->visible()) {
1161 (*i)->set_visible (true);
1167 AutomationLine::hide_all_but_selected_control_points ()
1169 if (alist->interpolation() == AutomationList::Discrete) {
1173 points_visible = false;
1175 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1176 if (!(*i)->get_selected()) {
1177 (*i)->set_visible (false);
1183 AutomationLine::track_entered()
1185 if (alist->interpolation() != AutomationList::Discrete) {
1186 show_all_control_points();
1191 AutomationLine::track_exited()
1193 if (alist->interpolation() != AutomationList::Discrete) {
1194 hide_all_but_selected_control_points();
1199 AutomationLine::get_state (void)
1201 /* function as a proxy for the model */
1202 return alist->get_state();
1206 AutomationLine::set_state (const XMLNode &node, int version)
1208 /* function as a proxy for the model */
1209 return alist->set_state (node, version);
1213 AutomationLine::view_to_model_coord (double& x, double& y) const
1215 x = _time_converter.from (x);
1216 view_to_model_coord_y (y);
1220 AutomationLine::view_to_model_coord_y (double& y) const
1222 /* TODO: This should be more generic ... */
1223 if (alist->parameter().type() == GainAutomation ||
1224 alist->parameter().type() == EnvelopeAutomation) {
1225 y = slider_position_to_gain_with_max (y, Config->get_max_gain());
1228 } else if (alist->parameter().type() == PanAzimuthAutomation ||
1229 alist->parameter().type() == PanElevationAutomation ||
1230 alist->parameter().type() == PanWidthAutomation) {
1232 } else if (alist->parameter().type() == PluginAutomation) {
1233 y = y * (double)(alist->get_max_y()- alist->get_min_y()) + alist->get_min_y();
1235 y = rint (y * alist->parameter().max());
1240 AutomationLine::model_to_view_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 = gain_to_slider_position_with_max (y, Config->get_max_gain());
1246 } else if (alist->parameter().type() == PanAzimuthAutomation ||
1247 alist->parameter().type() == PanElevationAutomation ||
1248 alist->parameter().type() == PanWidthAutomation) {
1249 // vertical coordinate axis reversal
1251 } else if (alist->parameter().type() == PluginAutomation) {
1252 y = (y - alist->get_min_y()) / (double)(alist->get_max_y()- alist->get_min_y());
1254 y = y / (double)alist->parameter().max(); /* ... like this */
1257 x = _time_converter.to (x) - _offset;
1260 /** Called when our list has announced that its interpolation style has changed */
1262 AutomationLine::interpolation_changed (AutomationList::InterpolationStyle style)
1264 if (style == AutomationList::Discrete) {
1265 show_all_control_points();
1268 hide_all_but_selected_control_points();
1274 AutomationLine::add_visible_control_point (uint32_t view_index, uint32_t pi, double tx, double ty, AutomationList::iterator model, uint32_t npoints)
1276 if (view_index >= control_points.size()) {
1278 /* make sure we have enough control points */
1280 ControlPoint* ncp = new ControlPoint (*this);
1281 ncp->set_size (control_point_box_size ());
1283 control_points.push_back (ncp);
1286 ControlPoint::ShapeType shape;
1288 if (!terminal_points_can_slide) {
1290 control_points[view_index]->set_can_slide(false);
1292 shape = ControlPoint::Start;
1294 shape = ControlPoint::Full;
1296 } else if (pi == npoints - 1) {
1297 control_points[view_index]->set_can_slide(false);
1298 shape = ControlPoint::End;
1300 control_points[view_index]->set_can_slide(true);
1301 shape = ControlPoint::Full;
1304 control_points[view_index]->set_can_slide(true);
1305 shape = ControlPoint::Full;
1308 control_points[view_index]->reset (tx, ty, model, view_index, shape);
1310 /* finally, control visibility */
1312 if (_visible && points_visible) {
1313 control_points[view_index]->show ();
1314 control_points[view_index]->set_visible (true);
1316 if (!points_visible) {
1317 control_points[view_index]->set_visible (false);
1323 AutomationLine::add_always_in_view (double x)
1325 _always_in_view.push_back (x);
1326 alist->apply_to_points (*this, &AutomationLine::reset_callback);
1330 AutomationLine::clear_always_in_view ()
1332 _always_in_view.clear ();
1333 alist->apply_to_points (*this, &AutomationLine::reset_callback);
1337 AutomationLine::connect_to_list ()
1339 _list_connections.drop_connections ();
1341 alist->StateChanged.connect (_list_connections, invalidator (*this), boost::bind (&AutomationLine::list_changed, this), gui_context());
1343 alist->InterpolationChanged.connect (
1344 _list_connections, invalidator (*this), boost::bind (&AutomationLine::interpolation_changed, this, _1), gui_context()
1348 MementoCommandBinder<AutomationList>*
1349 AutomationLine::memento_command_binder ()
1351 return new SimpleMementoCommandBinder<AutomationList> (*alist.get());
1354 /** Set the maximum time that points on this line can be at, relative
1355 * to the start of the track or region that it is on.
1358 AutomationLine::set_maximum_time (framecnt_t t)
1360 if (_maximum_time == t) {
1369 /** @return min and max x positions of points that are in the list, in session frames */
1370 pair<framepos_t, framepos_t>
1371 AutomationLine::get_point_x_range () const
1373 pair<framepos_t, framepos_t> r (max_framepos, 0);
1375 for (AutomationList::const_iterator i = the_list()->begin(); i != the_list()->end(); ++i) {
1376 r.first = min (r.first, _time_converter.to ((*i)->when) + _offset + _time_converter.origin_b ());
1377 r.second = max (r.second, _time_converter.to ((*i)->when) + _offset + _time_converter.origin_b ());
1384 AutomationLine::set_offset (framepos_t off)
1386 if (_offset == off) {