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
));
92 trackview
.session()->register_with_memento_command_factory(alist
->id(), this);
94 if (alist
->parameter().type() == GainAutomation
||
95 alist
->parameter().type() == EnvelopeAutomation
) {
96 set_uses_gain_mapping (true);
99 interpolation_changed (alist
->interpolation ());
104 AutomationLine::~AutomationLine ()
106 vector_delete (&control_points
);
111 AutomationLine::event_handler (GdkEvent
* event
)
113 return PublicEditor::instance().canvas_line_event (event
, line
, this);
117 AutomationLine::queue_reset ()
119 if (!update_pending
) {
120 update_pending
= true;
121 Gtkmm2ext::UI::instance()->call_slot (invalidator (*this), boost::bind (&AutomationLine::reset
, this));
126 AutomationLine::show ()
128 if (alist
->interpolation() != AutomationList::Discrete
) {
132 if (points_visible
) {
133 for (vector
<ControlPoint
*>::iterator i
= control_points
.begin(); i
!= control_points
.end(); ++i
) {
142 AutomationLine::hide ()
145 for (vector
<ControlPoint
*>::iterator i
= control_points
.begin(); i
!= control_points
.end(); ++i
) {
152 AutomationLine::control_point_box_size ()
154 if (alist
->interpolation() == AutomationList::Discrete
) {
155 return max((_height
*4.0) / (double)(alist
->parameter().max() - alist
->parameter().min()),
159 if (_height
> TimeAxisView::preset_height (HeightLarger
)) {
161 } else if (_height
> (guint32
) TimeAxisView::preset_height (HeightNormal
)) {
168 AutomationLine::set_height (guint32 h
)
173 double bsz
= control_point_box_size();
175 for (vector
<ControlPoint
*>::iterator i
= control_points
.begin(); i
!= control_points
.end(); ++i
) {
176 (*i
)->set_size (bsz
);
184 AutomationLine::set_line_color (uint32_t color
)
187 line
->property_fill_color_rgba() = color
;
191 AutomationLine::set_uses_gain_mapping (bool yn
)
193 if (yn
!= _uses_gain_mapping
) {
194 _uses_gain_mapping
= yn
;
200 AutomationLine::nth (uint32_t n
)
202 if (n
< control_points
.size()) {
203 return control_points
[n
];
210 AutomationLine::nth (uint32_t n
) const
212 if (n
< control_points
.size()) {
213 return control_points
[n
];
220 AutomationLine::modify_point_y (ControlPoint
& cp
, double y
)
222 /* clamp y-coord appropriately. y is supposed to be a normalized fraction (0.0-1.0),
223 and needs to be converted to a canvas unit distance.
228 y
= _height
- (y
* _height
);
230 double const x
= trackview
.editor().frame_to_unit (_time_converter
.to((*cp
.model())->when
) - _offset
);
232 trackview
.editor().session()->begin_reversible_command (_("automation event move"));
233 trackview
.editor().session()->add_command (
234 new MementoCommand
<AutomationList
> (memento_command_binder(), &get_state(), 0)
237 cp
.move_to (x
, y
, ControlPoint::Full
);
239 reset_line_coords (cp
);
241 if (line_points
.size() > 1) {
242 line
->property_points() = line_points
;
246 sync_model_with_view_point (cp
, false, 0);
249 update_pending
= false;
251 trackview
.editor().session()->add_command (
252 new MementoCommand
<AutomationList
> (memento_command_binder(), 0, &alist
->get_state())
255 trackview
.editor().session()->commit_reversible_command ();
256 trackview
.editor().session()->set_dirty ();
260 AutomationLine::reset_line_coords (ControlPoint
& cp
)
262 if (cp
.view_index() < line_points
.size()) {
263 line_points
[cp
.view_index()].set_x (cp
.get_x());
264 line_points
[cp
.view_index()].set_y (cp
.get_y());
269 AutomationLine::sync_model_with_view_points (list
<ControlPoint
*> cp
, bool did_push
, int64_t distance
)
271 update_pending
= true;
273 for (list
<ControlPoint
*>::iterator i
= cp
.begin(); i
!= cp
.end(); ++i
) {
274 sync_model_with_view_point (**i
, did_push
, distance
);
279 AutomationLine::model_representation (ControlPoint
& cp
, ModelRepresentation
& mr
)
281 /* part one: find out where the visual control point is.
282 initial results are in canvas units. ask the
283 line to convert them to something relevant.
286 mr
.xval
= cp
.get_x();
287 mr
.yval
= 1.0 - (cp
.get_y() / _height
);
289 /* if xval has not changed, set it directly from the model to avoid rounding errors */
291 if (mr
.xval
== trackview
.editor().frame_to_unit(_time_converter
.to((*cp
.model())->when
)) - _offset
) {
292 mr
.xval
= (*cp
.model())->when
- _offset
;
294 mr
.xval
= trackview
.editor().unit_to_frame (mr
.xval
);
295 mr
.xval
= _time_converter
.from (mr
.xval
+ _offset
);
298 /* convert y to model units; the x was already done above
301 view_to_model_coord_y (mr
.yval
);
303 /* part 2: find out where the model point is now
306 mr
.xpos
= (*cp
.model())->when
- _offset
;
307 mr
.ypos
= (*cp
.model())->value
;
309 /* part 3: get the position of the visual control
310 points before and after us.
313 ControlPoint
* before
;
316 if (cp
.view_index()) {
317 before
= nth (cp
.view_index() - 1);
322 after
= nth (cp
.view_index() + 1);
325 mr
.xmin
= (*before
->model())->when
;
326 mr
.ymin
= (*before
->model())->value
;
327 mr
.start
= before
->model();
332 mr
.start
= cp
.model();
336 mr
.end
= after
->model();
346 AutomationLine::determine_visible_control_points (ALPoints
& points
)
348 uint32_t view_index
, pi
, n
;
349 AutomationList::iterator model
;
351 uint32_t this_rx
= 0;
352 uint32_t prev_rx
= 0;
353 uint32_t this_ry
= 0;
354 uint32_t prev_ry
= 0;
358 /* hide all existing points, and the line */
360 for (vector
<ControlPoint
*>::iterator i
= control_points
.begin(); i
!= control_points
.end(); ++i
) {
366 if (points
.empty()) {
370 npoints
= points
.size();
372 /* compute derivative/slope for the entire line */
374 slope
= new double[npoints
];
376 for (n
= 0; n
< npoints
- 1; ++n
) {
377 double xdelta
= points
[n
+1].x
- points
[n
].x
;
378 double ydelta
= points
[n
+1].y
- points
[n
].y
;
379 slope
[n
] = ydelta
/xdelta
;
382 box_size
= (uint32_t) control_point_box_size ();
384 /* read all points and decide which ones to show as control points */
388 for (model
= alist
->begin(), pi
= 0; pi
< npoints
; ++model
, ++pi
) {
390 double tx
= points
[pi
].x
;
391 double ty
= points
[pi
].y
;
393 if (find (_always_in_view
.begin(), _always_in_view
.end(), (*model
)->when
) != _always_in_view
.end()) {
394 add_visible_control_point (view_index
, pi
, tx
, ty
, model
, npoints
);
401 if (isnan (tx
) || isnan (ty
)) {
402 warning
<< string_compose (_("Ignoring illegal points on AutomationLine \"%1\""),
407 /* now ensure that the control_points vector reflects the current curve
408 state, but don't plot control points too close together. also, don't
409 plot a series of points all with the same value.
411 always plot the first and last points, of course.
414 if (invalid_point (points
, pi
)) {
415 /* for some reason, we are supposed to ignore this point,
416 but still keep track of the model index.
421 if (pi
> 0 && pi
< npoints
- 1) {
422 if (slope
[pi
] == slope
[pi
-1]) {
424 /* no reason to display this point */
430 /* need to round here. the ultimate coordinates are integer
431 pixels, so tiny deltas in the coords will be eliminated
432 and we end up with "colinear" line segments. since the
433 line rendering code in libart doesn't like this very
434 much, we eliminate them here. don't do this for the first and last
438 this_rx
= (uint32_t) rint (tx
);
439 this_ry
= (uint32_t) rint (ty
);
441 if (view_index
&& pi
!= npoints
&& /* not the first, not the last */
442 (((this_rx
== prev_rx
) && (this_ry
== prev_ry
)) || /* same point */
443 (((this_rx
- prev_rx
) < (box_size
+ 2)) && /* not identical, but still too close horizontally */
444 (abs ((int)(this_ry
- prev_ry
)) < (int) (box_size
+ 2))))) { /* too close vertically */
448 /* ok, we should display this point */
450 add_visible_control_point (view_index
, pi
, tx
, ty
, model
, npoints
);
458 /* discard extra CP's to avoid confusing ourselves */
460 while (control_points
.size() > view_index
) {
461 ControlPoint
* cp
= control_points
.back();
462 control_points
.pop_back ();
466 if (!terminal_points_can_slide
) {
467 control_points
.back()->set_can_slide(false);
472 if (view_index
> 1) {
474 npoints
= view_index
;
476 /* reset the line coordinates */
478 while (line_points
.size() < npoints
) {
479 line_points
.push_back (Art::Point (0,0));
482 while (line_points
.size() > npoints
) {
483 line_points
.pop_back ();
486 for (view_index
= 0; view_index
< npoints
; ++view_index
) {
487 line_points
[view_index
].set_x (control_points
[view_index
]->get_x());
488 line_points
[view_index
].set_y (control_points
[view_index
]->get_y());
491 line
->property_points() = line_points
;
493 if (_visible
&& alist
->interpolation() != AutomationList::Discrete
) {
499 set_selected_points (trackview
.editor().get_selection().points
);
503 AutomationLine::get_verbose_cursor_string (double fraction
) const
505 std::string s
= fraction_to_string (fraction
);
506 if (_uses_gain_mapping
) {
514 * @param fraction y fraction
515 * @return string representation of this value, using dB if appropriate.
518 AutomationLine::fraction_to_string (double fraction
) const
522 if (_uses_gain_mapping
) {
523 if (fraction
== 0.0) {
524 snprintf (buf
, sizeof (buf
), "-inf");
526 snprintf (buf
, sizeof (buf
), "%.1f", accurate_coefficient_to_dB (slider_position_to_gain (fraction
)));
529 view_to_model_coord_y (fraction
);
530 if (EventTypeMap::instance().is_integer (alist
->parameter())) {
531 snprintf (buf
, sizeof (buf
), "%d", (int)fraction
);
533 snprintf (buf
, sizeof (buf
), "%.2f", fraction
);
542 * @param s Value string in the form as returned by fraction_to_string.
543 * @return Corresponding y fraction.
546 AutomationLine::string_to_fraction (string
const & s
) const
553 sscanf (s
.c_str(), "%lf", &v
);
555 if (_uses_gain_mapping
) {
556 v
= gain_to_slider_position (dB_to_coefficient (v
));
559 model_to_view_coord (dummy
, v
);
566 AutomationLine::invalid_point (ALPoints
& p
, uint32_t index
)
568 return p
[index
].x
== max_framepos
&& p
[index
].y
== DBL_MAX
;
572 AutomationLine::invalidate_point (ALPoints
& p
, uint32_t index
)
574 p
[index
].x
= max_framepos
;
575 p
[index
].y
= DBL_MAX
;
578 /** Start dragging a single point, possibly adding others if the supplied point is selected and there
579 * are other selected points.
581 * @param cp Point to drag.
582 * @param x Initial x position (units).
583 * @param fraction Initial y position (as a fraction of the track height, where 0 is the bottom and 1 the top)
586 AutomationLine::start_drag_single (ControlPoint
* cp
, double x
, float fraction
)
588 trackview
.editor().session()->begin_reversible_command (_("automation event move"));
589 trackview
.editor().session()->add_command (
590 new MementoCommand
<AutomationList
> (memento_command_binder(), &get_state(), 0)
593 _drag_points
.clear ();
594 _drag_points
.push_back (cp
);
596 if (cp
->get_selected ()) {
597 for (vector
<ControlPoint
*>::iterator i
= control_points
.begin(); i
!= control_points
.end(); ++i
) {
598 if (*i
!= cp
&& (*i
)->get_selected()) {
599 _drag_points
.push_back (*i
);
604 start_drag_common (x
, fraction
);
607 /** Start dragging a line vertically (with no change in x)
608 * @param i1 Control point index of the `left' point on the line.
609 * @param i2 Control point index of the `right' point on the line.
610 * @param fraction Initial y position (as a fraction of the track height, where 0 is the bottom and 1 the top)
613 AutomationLine::start_drag_line (uint32_t i1
, uint32_t i2
, float fraction
)
615 trackview
.editor().session()->begin_reversible_command (_("automation range move"));
616 trackview
.editor().session()->add_command (
617 new MementoCommand
<AutomationList
> (memento_command_binder (), &get_state(), 0)
620 _drag_points
.clear ();
621 for (uint32_t i
= i1
; i
<= i2
; i
++) {
622 _drag_points
.push_back (nth (i
));
625 start_drag_common (0, fraction
);
628 /** Start dragging multiple points (with no change in x)
629 * @param cp Points to drag.
630 * @param fraction Initial y position (as a fraction of the track height, where 0 is the bottom and 1 the top)
633 AutomationLine::start_drag_multiple (list
<ControlPoint
*> cp
, float fraction
, XMLNode
* state
)
635 trackview
.editor().session()->begin_reversible_command (_("automation range move"));
636 trackview
.editor().session()->add_command (
637 new MementoCommand
<AutomationList
> (memento_command_binder(), state
, 0)
641 start_drag_common (0, fraction
);
645 struct ControlPointSorter
647 bool operator() (ControlPoint
const * a
, ControlPoint
const * b
) {
648 return a
->get_x() < b
->get_x();
652 /** Common parts of starting a drag.
653 * @param x Starting x position in units, or 0 if x is being ignored.
654 * @param fraction Starting y position (as a fraction of the track height, where 0 is the bottom and 1 the top)
657 AutomationLine::start_drag_common (double x
, float fraction
)
661 _last_drag_fraction
= fraction
;
662 _drag_had_movement
= false;
665 _drag_points
.sort (ControlPointSorter ());
667 /* find the additional points that will be dragged when the user is holding
671 uint32_t i
= _drag_points
.back()->view_index () + 1;
673 _push_points
.clear ();
674 while ((p
= nth (i
)) != 0 && p
->can_slide()) {
675 _push_points
.push_back (p
);
680 /** Should be called to indicate motion during a drag.
681 * @param x New x position of the drag in units, or undefined if ignore_x == true.
682 * @param fraction New y fraction.
683 * @return x position and y fraction that were actually used (once clamped).
686 AutomationLine::drag_motion (double x
, float fraction
, bool ignore_x
, bool with_push
)
688 /* setup the points that are to be moved this time round */
689 list
<ControlPoint
*> points
= _drag_points
;
691 copy (_push_points
.begin(), _push_points
.end(), back_inserter (points
));
692 points
.sort (ControlPointSorter ());
695 double dx
= ignore_x
? 0 : (x
- _drag_x
);
696 double dy
= fraction
- _last_drag_fraction
;
699 ControlPoint
* before
= 0;
700 ControlPoint
* after
= 0;
702 for (vector
<ControlPoint
*>::iterator i
= control_points
.begin(); i
!= control_points
.end(); ++i
) {
703 if ((*i
)->get_x() < points
.front()->get_x()) {
706 if ((*i
)->get_x() > points
.back()->get_x() && after
== 0) {
711 double const before_x
= before
? before
->get_x() : 0;
712 double const after_x
= after
? after
->get_x() : DBL_MAX
;
715 for (list
<ControlPoint
*>::iterator i
= points
.begin(); i
!= points
.end(); ++i
) {
717 if ((*i
)->can_slide() && !ignore_x
) {
720 double const a
= (*i
)->get_x() + dx
;
721 double const b
= before_x
+ 1;
729 if (after_x
- before_x
< 2) {
730 /* after and before are very close, so just leave this alone */
733 double const a
= (*i
)->get_x() + dx
;
734 double const b
= after_x
- 1;
744 for (list
<ControlPoint
*>::iterator i
= points
.begin(); i
!= points
.end(); ++i
) {
745 double const y
= ((_height
- (*i
)->get_y()) / _height
) + dy
;
754 pair
<double, float> const clamped (_drag_x
+ dx
, _last_drag_fraction
+ dy
);
755 _drag_distance
+= dx
;
757 _last_drag_fraction
= fraction
;
759 for (list
<ControlPoint
*>::iterator i
= _drag_points
.begin(); i
!= _drag_points
.end(); ++i
) {
760 (*i
)->move_to ((*i
)->get_x() + dx
, (*i
)->get_y() - _height
* dy
, ControlPoint::Full
);
761 reset_line_coords (**i
);
765 /* move push points, preserving their y */
766 for (list
<ControlPoint
*>::iterator i
= _push_points
.begin(); i
!= _push_points
.end(); ++i
) {
767 (*i
)->move_to ((*i
)->get_x() + dx
, (*i
)->get_y(), ControlPoint::Full
);
768 reset_line_coords (**i
);
772 if (line_points
.size() > 1) {
773 line
->property_points() = line_points
;
776 _drag_had_movement
= true;
777 did_push
= with_push
;
782 /** Should be called to indicate the end of a drag */
784 AutomationLine::end_drag ()
786 if (!_drag_had_movement
) {
792 /* set up the points that were moved this time round */
793 list
<ControlPoint
*> points
= _drag_points
;
795 copy (_push_points
.begin(), _push_points
.end(), back_inserter (points
));
796 points
.sort (ControlPointSorter ());
799 sync_model_with_view_points (points
, did_push
, rint (_drag_distance
* trackview
.editor().get_current_zoom ()));
803 update_pending
= false;
805 trackview
.editor().session()->add_command (
806 new MementoCommand
<AutomationList
>(memento_command_binder (), 0, &alist
->get_state())
809 trackview
.editor().session()->set_dirty ();
813 AutomationLine::sync_model_with_view_point (ControlPoint
& cp
, bool did_push
, int64_t distance
)
815 ModelRepresentation mr
;
818 model_representation (cp
, mr
);
820 /* how much are we changing the central point by */
822 ydelta
= mr
.yval
- mr
.ypos
;
825 apply the full change to the central point, and interpolate
826 on both axes to cover all model points represented
827 by the control point.
830 /* change all points before the primary point */
832 for (AutomationList::iterator i
= mr
.start
; i
!= cp
.model(); ++i
) {
834 double fract
= ((*i
)->when
- mr
.xmin
) / (mr
.xpos
- mr
.xmin
);
835 double y_delta
= ydelta
* fract
;
836 double x_delta
= distance
* fract
;
840 if (y_delta
|| x_delta
) {
841 alist
->modify (i
, (*i
)->when
+ x_delta
, mr
.ymin
+ y_delta
);
845 /* change the primary point */
847 update_pending
= true;
848 alist
->modify (cp
.model(), mr
.xval
, mr
.yval
);
850 /* change later points */
852 AutomationList::iterator i
= cp
.model();
856 while (i
!= mr
.end
) {
858 double delta
= ydelta
* (mr
.xmax
- (*i
)->when
) / (mr
.xmax
- mr
.xpos
);
860 /* all later points move by the same distance along the x-axis as the main point */
863 alist
->modify (i
, (*i
)->when
+ distance
, (*i
)->value
+ delta
);
871 /* move all points after the range represented by the view by the same distance
872 as the main point moved.
875 alist
->slide (mr
.end
, distance
);
880 AutomationLine::control_points_adjacent (double xval
, uint32_t & before
, uint32_t& after
)
882 ControlPoint
*bcp
= 0;
883 ControlPoint
*acp
= 0;
886 unit_xval
= trackview
.editor().frame_to_unit (xval
);
888 for (vector
<ControlPoint
*>::iterator i
= control_points
.begin(); i
!= control_points
.end(); ++i
) {
890 if ((*i
)->get_x() <= unit_xval
) {
892 if (!bcp
|| (*i
)->get_x() > bcp
->get_x()) {
894 before
= bcp
->view_index();
897 } else if ((*i
)->get_x() > unit_xval
) {
899 after
= acp
->view_index();
908 AutomationLine::is_last_point (ControlPoint
& cp
)
910 ModelRepresentation mr
;
912 model_representation (cp
, mr
);
914 // If the list is not empty, and the point is the last point in the list
916 if (!alist
->empty() && mr
.end
== alist
->end()) {
924 AutomationLine::is_first_point (ControlPoint
& cp
)
926 ModelRepresentation mr
;
928 model_representation (cp
, mr
);
930 // If the list is not empty, and the point is the first point in the list
932 if (!alist
->empty() && mr
.start
== alist
->begin()) {
939 // This is copied into AudioRegionGainLine
941 AutomationLine::remove_point (ControlPoint
& cp
)
943 ModelRepresentation mr
;
945 model_representation (cp
, mr
);
947 trackview
.editor().session()->begin_reversible_command (_("remove control point"));
948 XMLNode
&before
= alist
->get_state();
950 alist
->erase (mr
.start
, mr
.end
);
952 trackview
.editor().session()->add_command(
953 new MementoCommand
<AutomationList
> (memento_command_binder (), &before
, &alist
->get_state())
956 trackview
.editor().session()->commit_reversible_command ();
957 trackview
.editor().session()->set_dirty ();
960 /** Get selectable points within an area.
961 * @param start Start position in session frames.
962 * @param end End position in session frames.
963 * @param bot Bottom y range, as a fraction of line height, where 0 is the bottom of the line.
964 * @param top Top y range, as a fraction of line height, where 0 is the bottom of the line.
965 * @param result Filled in with selectable things; in this case, ControlPoints.
968 AutomationLine::get_selectables (
969 framepos_t start
, framepos_t end
, double botfrac
, double topfrac
, list
<Selectable
*>& results
972 /* convert fractions to display coordinates with 0 at the top of the track */
973 double const bot_track
= (1 - topfrac
) * trackview
.current_height ();
974 double const top_track
= (1 - botfrac
) * trackview
.current_height ();
976 for (vector
<ControlPoint
*>::iterator i
= control_points
.begin(); i
!= control_points
.end(); ++i
) {
977 double const model_when
= (*(*i
)->model())->when
;
978 framepos_t
const session_frames_when
= _time_converter
.to (model_when
- _offset
) + _time_converter
.origin_b ();
980 if (session_frames_when
>= start
&& session_frames_when
<= end
&& (*i
)->get_y() >= bot_track
&& (*i
)->get_y() <= top_track
) {
981 results
.push_back (*i
);
987 AutomationLine::get_inverted_selectables (Selection
&, list
<Selectable
*>& /*results*/)
992 /** Take a PointSelection and find ControlPoints that fall within it */
994 AutomationLine::point_selection_to_control_points (PointSelection
const & s
)
996 list
<ControlPoint
*> cp
;
998 for (PointSelection::const_iterator i
= s
.begin(); i
!= s
.end(); ++i
) {
1000 if (i
->track
!= &trackview
) {
1004 double const bot
= (1 - i
->high_fract
) * trackview
.current_height ();
1005 double const top
= (1 - i
->low_fract
) * trackview
.current_height ();
1007 for (vector
<ControlPoint
*>::iterator j
= control_points
.begin(); j
!= control_points
.end(); ++j
) {
1009 double const rstart
= trackview
.editor().frame_to_unit (_time_converter
.to (i
->start
));
1010 double const rend
= trackview
.editor().frame_to_unit (_time_converter
.to (i
->end
));
1012 if ((*j
)->get_x() >= rstart
&& (*j
)->get_x() <= rend
) {
1013 if ((*j
)->get_y() >= bot
&& (*j
)->get_y() <= top
) {
1025 AutomationLine::set_selected_points (PointSelection
& points
)
1027 for (vector
<ControlPoint
*>::iterator i
= control_points
.begin(); i
!= control_points
.end(); ++i
) {
1028 (*i
)->set_selected (false);
1031 if (!points
.empty()) {
1032 list
<ControlPoint
*> cp
= point_selection_to_control_points (points
);
1033 for (list
<ControlPoint
*>::iterator i
= cp
.begin(); i
!= cp
.end(); ++i
) {
1034 (*i
)->set_selected (true);
1041 void AutomationLine::set_colors ()
1043 set_line_color (ARDOUR_UI::config()->canvasvar_AutomationLine
.get());
1044 for (vector
<ControlPoint
*>::iterator i
= control_points
.begin(); i
!= control_points
.end(); ++i
) {
1050 AutomationLine::list_changed ()
1056 AutomationLine::reset_callback (const Evoral::ControlList
& events
)
1058 ALPoints tmp_points
;
1059 uint32_t npoints
= events
.size();
1062 for (vector
<ControlPoint
*>::iterator i
= control_points
.begin(); i
!= control_points
.end(); ++i
) {
1065 control_points
.clear ();
1070 AutomationList::const_iterator ai
;
1072 for (ai
= events
.begin(); ai
!= events
.end(); ++ai
) {
1074 double translated_x
= (*ai
)->when
;
1075 double translated_y
= (*ai
)->value
;
1076 model_to_view_coord (translated_x
, translated_y
);
1078 if (translated_x
>= 0 && translated_x
< _maximum_time
) {
1079 tmp_points
.push_back (ALPoint (
1080 trackview
.editor().frame_to_unit (translated_x
),
1081 _height
- (translated_y
* _height
))
1086 determine_visible_control_points (tmp_points
);
1090 AutomationLine::reset ()
1092 update_pending
= false;
1098 alist
->apply_to_points (*this, &AutomationLine::reset_callback
);
1102 AutomationLine::clear ()
1104 /* parent must create and commit command */
1105 XMLNode
&before
= alist
->get_state();
1108 trackview
.editor().session()->add_command (
1109 new MementoCommand
<AutomationList
> (memento_command_binder (), &before
, &alist
->get_state())
1114 AutomationLine::change_model (AutomationList::iterator
/*i*/, double /*x*/, double /*y*/)
1119 AutomationLine::set_list (boost::shared_ptr
<ARDOUR::AutomationList
> list
)
1127 AutomationLine::show_all_control_points ()
1130 // show the line but don't allow any control points
1134 points_visible
= true;
1136 for (vector
<ControlPoint
*>::iterator i
= control_points
.begin(); i
!= control_points
.end(); ++i
) {
1137 if (!(*i
)->visible()) {
1139 (*i
)->set_visible (true);
1145 AutomationLine::hide_all_but_selected_control_points ()
1147 if (alist
->interpolation() == AutomationList::Discrete
) {
1151 points_visible
= false;
1153 for (vector
<ControlPoint
*>::iterator i
= control_points
.begin(); i
!= control_points
.end(); ++i
) {
1154 if (!(*i
)->get_selected()) {
1155 (*i
)->set_visible (false);
1161 AutomationLine::track_entered()
1163 if (alist
->interpolation() != AutomationList::Discrete
) {
1164 show_all_control_points();
1169 AutomationLine::track_exited()
1171 if (alist
->interpolation() != AutomationList::Discrete
) {
1172 hide_all_but_selected_control_points();
1177 AutomationLine::get_state (void)
1179 /* function as a proxy for the model */
1180 return alist
->get_state();
1184 AutomationLine::set_state (const XMLNode
&node
, int version
)
1186 /* function as a proxy for the model */
1187 return alist
->set_state (node
, version
);
1191 AutomationLine::view_to_model_coord (double& x
, double& y
) const
1193 x
= _time_converter
.from (x
);
1194 view_to_model_coord_y (y
);
1198 AutomationLine::view_to_model_coord_y (double& y
) const
1200 /* TODO: This should be more generic ... */
1201 if (alist
->parameter().type() == GainAutomation
||
1202 alist
->parameter().type() == EnvelopeAutomation
) {
1203 y
= slider_position_to_gain (y
);
1206 } else if (alist
->parameter().type() == PanAzimuthAutomation
||
1207 alist
->parameter().type() == PanElevationAutomation
||
1208 alist
->parameter().type() == PanWidthAutomation
) {
1210 } else if (alist
->parameter().type() == PluginAutomation
) {
1211 y
= y
* (double)(alist
->get_max_y()- alist
->get_min_y()) + alist
->get_min_y();
1213 y
= rint (y
* alist
->parameter().max());
1218 AutomationLine::model_to_view_coord (double& x
, double& y
) const
1220 /* TODO: This should be more generic ... */
1221 if (alist
->parameter().type() == GainAutomation
||
1222 alist
->parameter().type() == EnvelopeAutomation
) {
1223 y
= gain_to_slider_position (y
);
1224 } else if (alist
->parameter().type() == PanAzimuthAutomation
||
1225 alist
->parameter().type() == PanElevationAutomation
||
1226 alist
->parameter().type() == PanWidthAutomation
) {
1227 // vertical coordinate axis reversal
1229 } else if (alist
->parameter().type() == PluginAutomation
) {
1230 y
= (y
- alist
->get_min_y()) / (double)(alist
->get_max_y()- alist
->get_min_y());
1232 y
= y
/ (double)alist
->parameter().max(); /* ... like this */
1235 x
= _time_converter
.to (x
) - _offset
;
1238 /** Called when our list has announced that its interpolation style has changed */
1240 AutomationLine::interpolation_changed (AutomationList::InterpolationStyle style
)
1242 if (style
== AutomationList::Discrete
) {
1243 show_all_control_points();
1246 hide_all_but_selected_control_points();
1252 AutomationLine::add_visible_control_point (uint32_t view_index
, uint32_t pi
, double tx
, double ty
, AutomationList::iterator model
, uint32_t npoints
)
1254 if (view_index
>= control_points
.size()) {
1256 /* make sure we have enough control points */
1258 ControlPoint
* ncp
= new ControlPoint (*this);
1259 ncp
->set_size (control_point_box_size ());
1261 control_points
.push_back (ncp
);
1264 ControlPoint::ShapeType shape
;
1266 if (!terminal_points_can_slide
) {
1268 control_points
[view_index
]->set_can_slide(false);
1270 shape
= ControlPoint::Start
;
1272 shape
= ControlPoint::Full
;
1274 } else if (pi
== npoints
- 1) {
1275 control_points
[view_index
]->set_can_slide(false);
1276 shape
= ControlPoint::End
;
1278 control_points
[view_index
]->set_can_slide(true);
1279 shape
= ControlPoint::Full
;
1282 control_points
[view_index
]->set_can_slide(true);
1283 shape
= ControlPoint::Full
;
1286 control_points
[view_index
]->reset (tx
, ty
, model
, view_index
, shape
);
1288 /* finally, control visibility */
1290 if (_visible
&& points_visible
) {
1291 control_points
[view_index
]->show ();
1292 control_points
[view_index
]->set_visible (true);
1294 if (!points_visible
) {
1295 control_points
[view_index
]->set_visible (false);
1301 AutomationLine::add_always_in_view (double x
)
1303 _always_in_view
.push_back (x
);
1304 alist
->apply_to_points (*this, &AutomationLine::reset_callback
);
1308 AutomationLine::clear_always_in_view ()
1310 _always_in_view
.clear ();
1311 alist
->apply_to_points (*this, &AutomationLine::reset_callback
);
1315 AutomationLine::connect_to_list ()
1317 _list_connections
.drop_connections ();
1319 alist
->StateChanged
.connect (_list_connections
, invalidator (*this), boost::bind (&AutomationLine::list_changed
, this), gui_context());
1321 alist
->InterpolationChanged
.connect (
1322 _list_connections
, invalidator (*this), boost::bind (&AutomationLine::interpolation_changed
, this, _1
), gui_context()
1326 MementoCommandBinder
<AutomationList
>*
1327 AutomationLine::memento_command_binder ()
1329 return new SimpleMementoCommandBinder
<AutomationList
> (*alist
.get());
1332 /** Set the maximum time that points on this line can be at, relative
1333 * to the start of the track or region that it is on.
1336 AutomationLine::set_maximum_time (framecnt_t t
)
1338 if (_maximum_time
== t
) {
1347 /** @return min and max x positions of points that are in the list, in session frames */
1348 pair
<framepos_t
, framepos_t
>
1349 AutomationLine::get_point_x_range () const
1351 pair
<framepos_t
, framepos_t
> r (max_framepos
, 0);
1353 for (AutomationList::const_iterator i
= the_list()->begin(); i
!= the_list()->end(); ++i
) {
1354 r
.first
= min (r
.first
, _time_converter
.to ((*i
)->when
) + _offset
+ _time_converter
.origin_b ());
1355 r
.second
= max (r
.second
, _time_converter
.to ((*i
)->when
) + _offset
+ _time_converter
.origin_b ());
1362 AutomationLine::set_offset (framepos_t off
)
1364 if (_offset
== off
) {