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();
344 AutomationLine::determine_visible_control_points (ALPoints
& points
)
346 uint32_t view_index
, pi
, n
;
347 AutomationList::iterator model
;
349 uint32_t this_rx
= 0;
350 uint32_t prev_rx
= 0;
351 uint32_t this_ry
= 0;
352 uint32_t prev_ry
= 0;
356 /* hide all existing points, and the line */
358 for (vector
<ControlPoint
*>::iterator i
= control_points
.begin(); i
!= control_points
.end(); ++i
) {
364 if (points
.empty()) {
368 npoints
= points
.size();
370 /* compute derivative/slope for the entire line */
372 slope
= new double[npoints
];
374 for (n
= 0; n
< npoints
- 1; ++n
) {
375 double xdelta
= points
[n
+1].x
- points
[n
].x
;
376 double ydelta
= points
[n
+1].y
- points
[n
].y
;
377 slope
[n
] = ydelta
/xdelta
;
380 box_size
= (uint32_t) control_point_box_size ();
382 /* read all points and decide which ones to show as control points */
386 for (model
= alist
->begin(), pi
= 0; pi
< npoints
; ++model
, ++pi
) {
388 double tx
= points
[pi
].x
;
389 double ty
= points
[pi
].y
;
391 if (find (_always_in_view
.begin(), _always_in_view
.end(), (*model
)->when
) != _always_in_view
.end()) {
392 add_visible_control_point (view_index
, pi
, tx
, ty
, model
, npoints
);
399 if (isnan (tx
) || isnan (ty
)) {
400 warning
<< string_compose (_("Ignoring illegal points on AutomationLine \"%1\""),
405 /* now ensure that the control_points vector reflects the current curve
406 state, but don't plot control points too close together. also, don't
407 plot a series of points all with the same value.
409 always plot the first and last points, of course.
412 if (invalid_point (points
, pi
)) {
413 /* for some reason, we are supposed to ignore this point,
414 but still keep track of the model index.
419 if (pi
> 0 && pi
< npoints
- 1) {
420 if (slope
[pi
] == slope
[pi
-1]) {
422 /* no reason to display this point */
428 /* need to round here. the ultimate coordinates are integer
429 pixels, so tiny deltas in the coords will be eliminated
430 and we end up with "colinear" line segments. since the
431 line rendering code in libart doesn't like this very
432 much, we eliminate them here. don't do this for the first and last
436 this_rx
= (uint32_t) rint (tx
);
437 this_ry
= (uint32_t) rint (ty
);
439 if (view_index
&& pi
!= npoints
&& /* not the first, not the last */
440 (((this_rx
== prev_rx
) && (this_ry
== prev_ry
)) || /* same point */
441 (((this_rx
- prev_rx
) < (box_size
+ 2)) && /* not identical, but still too close horizontally */
442 (abs ((int)(this_ry
- prev_ry
)) < (int) (box_size
+ 2))))) { /* too close vertically */
446 /* ok, we should display this point */
448 add_visible_control_point (view_index
, pi
, tx
, ty
, model
, npoints
);
456 /* discard extra CP's to avoid confusing ourselves */
458 while (control_points
.size() > view_index
) {
459 ControlPoint
* cp
= control_points
.back();
460 control_points
.pop_back ();
464 if (!terminal_points_can_slide
) {
465 control_points
.back()->set_can_slide(false);
470 if (view_index
> 1) {
472 npoints
= view_index
;
474 /* reset the line coordinates */
476 while (line_points
.size() < npoints
) {
477 line_points
.push_back (Art::Point (0,0));
480 while (line_points
.size() > npoints
) {
481 line_points
.pop_back ();
484 for (view_index
= 0; view_index
< npoints
; ++view_index
) {
485 line_points
[view_index
].set_x (control_points
[view_index
]->get_x());
486 line_points
[view_index
].set_y (control_points
[view_index
]->get_y());
489 line
->property_points() = line_points
;
491 if (_visible
&& alist
->interpolation() != AutomationList::Discrete
) {
497 set_selected_points (trackview
.editor().get_selection().points
);
501 AutomationLine::get_verbose_cursor_string (double fraction
) const
503 std::string s
= fraction_to_string (fraction
);
504 if (_uses_gain_mapping
) {
512 * @param fraction y fraction
513 * @return string representation of this value, using dB if appropriate.
516 AutomationLine::fraction_to_string (double fraction
) const
520 if (_uses_gain_mapping
) {
521 if (fraction
== 0.0) {
522 snprintf (buf
, sizeof (buf
), "-inf");
524 snprintf (buf
, sizeof (buf
), "%.1f", accurate_coefficient_to_dB (slider_position_to_gain (fraction
)));
527 view_to_model_coord_y (fraction
);
528 if (EventTypeMap::instance().is_integer (alist
->parameter())) {
529 snprintf (buf
, sizeof (buf
), "%d", (int)fraction
);
531 snprintf (buf
, sizeof (buf
), "%.2f", fraction
);
540 * @param s Value string in the form as returned by fraction_to_string.
541 * @return Corresponding y fraction.
544 AutomationLine::string_to_fraction (string
const & s
) const
551 sscanf (s
.c_str(), "%lf", &v
);
553 if (_uses_gain_mapping
) {
554 v
= gain_to_slider_position (dB_to_coefficient (v
));
557 model_to_view_coord (dummy
, v
);
564 AutomationLine::invalid_point (ALPoints
& p
, uint32_t index
)
566 return p
[index
].x
== max_framepos
&& p
[index
].y
== DBL_MAX
;
570 AutomationLine::invalidate_point (ALPoints
& p
, uint32_t index
)
572 p
[index
].x
= max_framepos
;
573 p
[index
].y
= DBL_MAX
;
576 /** Start dragging a single point, possibly adding others if the supplied point is selected and there
577 * are other selected points.
579 * @param cp Point to drag.
580 * @param x Initial x position (units).
581 * @param fraction Initial y position (as a fraction of the track height, where 0 is the bottom and 1 the top)
584 AutomationLine::start_drag_single (ControlPoint
* cp
, double x
, float fraction
)
586 trackview
.editor().session()->begin_reversible_command (_("automation event move"));
587 trackview
.editor().session()->add_command (
588 new MementoCommand
<AutomationList
> (memento_command_binder(), &get_state(), 0)
591 _drag_points
.clear ();
592 _drag_points
.push_back (cp
);
594 if (cp
->get_selected ()) {
595 for (vector
<ControlPoint
*>::iterator i
= control_points
.begin(); i
!= control_points
.end(); ++i
) {
596 if (*i
!= cp
&& (*i
)->get_selected()) {
597 _drag_points
.push_back (*i
);
602 start_drag_common (x
, fraction
);
605 /** Start dragging a line vertically (with no change in x)
606 * @param i1 Control point index of the `left' point on the line.
607 * @param i2 Control point index of the `right' point on the line.
608 * @param fraction Initial y position (as a fraction of the track height, where 0 is the bottom and 1 the top)
611 AutomationLine::start_drag_line (uint32_t i1
, uint32_t i2
, float fraction
)
613 trackview
.editor().session()->begin_reversible_command (_("automation range move"));
614 trackview
.editor().session()->add_command (
615 new MementoCommand
<AutomationList
> (memento_command_binder (), &get_state(), 0)
618 _drag_points
.clear ();
619 for (uint32_t i
= i1
; i
<= i2
; i
++) {
620 _drag_points
.push_back (nth (i
));
623 start_drag_common (0, fraction
);
626 /** Start dragging multiple points (with no change in x)
627 * @param cp Points to drag.
628 * @param fraction Initial y position (as a fraction of the track height, where 0 is the bottom and 1 the top)
631 AutomationLine::start_drag_multiple (list
<ControlPoint
*> cp
, float fraction
, XMLNode
* state
)
633 trackview
.editor().session()->begin_reversible_command (_("automation range move"));
634 trackview
.editor().session()->add_command (
635 new MementoCommand
<AutomationList
> (memento_command_binder(), state
, 0)
639 start_drag_common (0, fraction
);
643 struct ControlPointSorter
645 bool operator() (ControlPoint
const * a
, ControlPoint
const * b
) {
646 return a
->get_x() < b
->get_x();
650 /** Common parts of starting a drag.
651 * @param x Starting x position in units, or 0 if x is being ignored.
652 * @param fraction Starting y position (as a fraction of the track height, where 0 is the bottom and 1 the top)
655 AutomationLine::start_drag_common (double x
, float fraction
)
659 _last_drag_fraction
= fraction
;
660 _drag_had_movement
= false;
663 _drag_points
.sort (ControlPointSorter ());
665 /* find the additional points that will be dragged when the user is holding
669 uint32_t i
= _drag_points
.back()->view_index () + 1;
671 _push_points
.clear ();
672 while ((p
= nth (i
)) != 0 && p
->can_slide()) {
673 _push_points
.push_back (p
);
678 /** Should be called to indicate motion during a drag.
679 * @param x New x position of the drag in units, or undefined if ignore_x == true.
680 * @param fraction New y fraction.
681 * @return x position and y fraction that were actually used (once clamped).
684 AutomationLine::drag_motion (double x
, float fraction
, bool ignore_x
, bool with_push
)
686 /* setup the points that are to be moved this time round */
687 list
<ControlPoint
*> points
= _drag_points
;
689 copy (_push_points
.begin(), _push_points
.end(), back_inserter (points
));
690 points
.sort (ControlPointSorter ());
693 double dx
= ignore_x
? 0 : (x
- _drag_x
);
694 double dy
= fraction
- _last_drag_fraction
;
697 ControlPoint
* before
= 0;
698 ControlPoint
* after
= 0;
700 for (vector
<ControlPoint
*>::iterator i
= control_points
.begin(); i
!= control_points
.end(); ++i
) {
701 if ((*i
)->get_x() < points
.front()->get_x()) {
704 if ((*i
)->get_x() > points
.back()->get_x() && after
== 0) {
709 double const before_x
= before
? before
->get_x() : 0;
710 double const after_x
= after
? after
->get_x() : DBL_MAX
;
713 for (list
<ControlPoint
*>::iterator i
= points
.begin(); i
!= points
.end(); ++i
) {
715 if ((*i
)->can_slide() && !ignore_x
) {
718 double const a
= (*i
)->get_x() + dx
;
719 double const b
= before_x
+ 1;
727 if (after_x
- before_x
< 2) {
728 /* after and before are very close, so just leave this alone */
731 double const a
= (*i
)->get_x() + dx
;
732 double const b
= after_x
- 1;
742 for (list
<ControlPoint
*>::iterator i
= points
.begin(); i
!= points
.end(); ++i
) {
743 double const y
= ((_height
- (*i
)->get_y()) / _height
) + dy
;
752 pair
<double, float> const clamped (_drag_x
+ dx
, _last_drag_fraction
+ dy
);
753 _drag_distance
+= dx
;
755 _last_drag_fraction
= fraction
;
757 for (list
<ControlPoint
*>::iterator i
= _drag_points
.begin(); i
!= _drag_points
.end(); ++i
) {
758 (*i
)->move_to ((*i
)->get_x() + dx
, (*i
)->get_y() - _height
* dy
, ControlPoint::Full
);
759 reset_line_coords (**i
);
763 /* move push points, preserving their y */
764 for (list
<ControlPoint
*>::iterator i
= _push_points
.begin(); i
!= _push_points
.end(); ++i
) {
765 (*i
)->move_to ((*i
)->get_x() + dx
, (*i
)->get_y(), ControlPoint::Full
);
766 reset_line_coords (**i
);
770 if (line_points
.size() > 1) {
771 line
->property_points() = line_points
;
774 _drag_had_movement
= true;
775 did_push
= with_push
;
780 /** Should be called to indicate the end of a drag */
782 AutomationLine::end_drag ()
784 if (!_drag_had_movement
) {
790 /* set up the points that were moved this time round */
791 list
<ControlPoint
*> points
= _drag_points
;
793 copy (_push_points
.begin(), _push_points
.end(), back_inserter (points
));
794 points
.sort (ControlPointSorter ());
797 sync_model_with_view_points (points
, did_push
, rint (_drag_distance
* trackview
.editor().get_current_zoom ()));
801 update_pending
= false;
803 trackview
.editor().session()->add_command (
804 new MementoCommand
<AutomationList
>(memento_command_binder (), 0, &alist
->get_state())
807 trackview
.editor().session()->set_dirty ();
811 AutomationLine::sync_model_with_view_point (ControlPoint
& cp
, bool did_push
, int64_t distance
)
813 ModelRepresentation mr
;
816 model_representation (cp
, mr
);
818 /* how much are we changing the central point by */
820 ydelta
= mr
.yval
- mr
.ypos
;
823 apply the full change to the central point, and interpolate
824 on both axes to cover all model points represented
825 by the control point.
828 /* change all points before the primary point */
830 for (AutomationList::iterator i
= mr
.start
; i
!= cp
.model(); ++i
) {
832 double fract
= ((*i
)->when
- mr
.xmin
) / (mr
.xpos
- mr
.xmin
);
833 double y_delta
= ydelta
* fract
;
834 double x_delta
= distance
* fract
;
838 if (y_delta
|| x_delta
) {
839 alist
->modify (i
, (*i
)->when
+ x_delta
, mr
.ymin
+ y_delta
);
843 /* change the primary point */
845 update_pending
= true;
846 alist
->modify (cp
.model(), mr
.xval
, mr
.yval
);
848 /* change later points */
850 AutomationList::iterator i
= cp
.model();
854 while (i
!= mr
.end
) {
856 double delta
= ydelta
* (mr
.xmax
- (*i
)->when
) / (mr
.xmax
- mr
.xpos
);
858 /* all later points move by the same distance along the x-axis as the main point */
861 alist
->modify (i
, (*i
)->when
+ distance
, (*i
)->value
+ delta
);
869 /* move all points after the range represented by the view by the same distance
870 as the main point moved.
873 alist
->slide (mr
.end
, distance
);
878 AutomationLine::control_points_adjacent (double xval
, uint32_t & before
, uint32_t& after
)
880 ControlPoint
*bcp
= 0;
881 ControlPoint
*acp
= 0;
884 unit_xval
= trackview
.editor().frame_to_unit (xval
);
886 for (vector
<ControlPoint
*>::iterator i
= control_points
.begin(); i
!= control_points
.end(); ++i
) {
888 if ((*i
)->get_x() <= unit_xval
) {
890 if (!bcp
|| (*i
)->get_x() > bcp
->get_x()) {
892 before
= bcp
->view_index();
895 } else if ((*i
)->get_x() > unit_xval
) {
897 after
= acp
->view_index();
906 AutomationLine::is_last_point (ControlPoint
& cp
)
908 ModelRepresentation mr
;
910 model_representation (cp
, mr
);
912 // If the list is not empty, and the point is the last point in the list
914 if (!alist
->empty() && mr
.end
== alist
->end()) {
922 AutomationLine::is_first_point (ControlPoint
& cp
)
924 ModelRepresentation mr
;
926 model_representation (cp
, mr
);
928 // If the list is not empty, and the point is the first point in the list
930 if (!alist
->empty() && mr
.start
== alist
->begin()) {
937 // This is copied into AudioRegionGainLine
939 AutomationLine::remove_point (ControlPoint
& cp
)
941 ModelRepresentation mr
;
943 model_representation (cp
, mr
);
945 trackview
.editor().session()->begin_reversible_command (_("remove control point"));
946 XMLNode
&before
= alist
->get_state();
948 alist
->erase (mr
.start
, mr
.end
);
950 trackview
.editor().session()->add_command(
951 new MementoCommand
<AutomationList
> (memento_command_binder (), &before
, &alist
->get_state())
954 trackview
.editor().session()->commit_reversible_command ();
955 trackview
.editor().session()->set_dirty ();
958 /** Get selectable points within an area.
959 * @param start Start position in session frames.
960 * @param end End position in session frames.
961 * @param bot Bottom y range, as a fraction of line height, where 0 is the bottom of the line.
962 * @param top Top y range, as a fraction of line height, where 0 is the bottom of the line.
963 * @param result Filled in with selectable things; in this case, ControlPoints.
966 AutomationLine::get_selectables (
967 framepos_t start
, framepos_t end
, double botfrac
, double topfrac
, list
<Selectable
*>& results
970 /* convert fractions to display coordinates with 0 at the top of the track */
971 double const bot_track
= (1 - topfrac
) * trackview
.current_height ();
972 double const top_track
= (1 - botfrac
) * trackview
.current_height ();
974 for (vector
<ControlPoint
*>::iterator i
= control_points
.begin(); i
!= control_points
.end(); ++i
) {
975 double const model_when
= (*(*i
)->model())->when
;
976 framepos_t
const session_frames_when
= _time_converter
.to (model_when
- _offset
) + _time_converter
.origin_b ();
978 if (session_frames_when
>= start
&& session_frames_when
<= end
&& (*i
)->get_y() >= bot_track
&& (*i
)->get_y() <= top_track
) {
979 results
.push_back (*i
);
985 AutomationLine::get_inverted_selectables (Selection
&, list
<Selectable
*>& /*results*/)
990 /** Take a PointSelection and find ControlPoints that fall within it */
992 AutomationLine::point_selection_to_control_points (PointSelection
const & s
)
994 list
<ControlPoint
*> cp
;
996 for (PointSelection::const_iterator i
= s
.begin(); i
!= s
.end(); ++i
) {
998 if (i
->track
!= &trackview
) {
1002 double const bot
= (1 - i
->high_fract
) * trackview
.current_height ();
1003 double const top
= (1 - i
->low_fract
) * trackview
.current_height ();
1005 for (vector
<ControlPoint
*>::iterator j
= control_points
.begin(); j
!= control_points
.end(); ++j
) {
1007 double const rstart
= trackview
.editor().frame_to_unit (_time_converter
.to (i
->start
));
1008 double const rend
= trackview
.editor().frame_to_unit (_time_converter
.to (i
->end
));
1010 if ((*j
)->get_x() >= rstart
&& (*j
)->get_x() <= rend
) {
1011 if ((*j
)->get_y() >= bot
&& (*j
)->get_y() <= top
) {
1023 AutomationLine::set_selected_points (PointSelection
& points
)
1025 for (vector
<ControlPoint
*>::iterator i
= control_points
.begin(); i
!= control_points
.end(); ++i
) {
1026 (*i
)->set_selected (false);
1029 if (!points
.empty()) {
1030 list
<ControlPoint
*> cp
= point_selection_to_control_points (points
);
1031 for (list
<ControlPoint
*>::iterator i
= cp
.begin(); i
!= cp
.end(); ++i
) {
1032 (*i
)->set_selected (true);
1039 void AutomationLine::set_colors ()
1041 set_line_color (ARDOUR_UI::config()->canvasvar_AutomationLine
.get());
1042 for (vector
<ControlPoint
*>::iterator i
= control_points
.begin(); i
!= control_points
.end(); ++i
) {
1048 AutomationLine::list_changed ()
1054 AutomationLine::reset_callback (const Evoral::ControlList
& events
)
1056 ALPoints tmp_points
;
1057 uint32_t npoints
= events
.size();
1060 for (vector
<ControlPoint
*>::iterator i
= control_points
.begin(); i
!= control_points
.end(); ++i
) {
1063 control_points
.clear ();
1068 AutomationList::const_iterator ai
;
1070 for (ai
= events
.begin(); ai
!= events
.end(); ++ai
) {
1072 double translated_x
= (*ai
)->when
;
1073 double translated_y
= (*ai
)->value
;
1074 model_to_view_coord (translated_x
, translated_y
);
1076 if (translated_x
>= 0 && translated_x
< _maximum_time
) {
1077 tmp_points
.push_back (ALPoint (
1078 trackview
.editor().frame_to_unit (translated_x
),
1079 _height
- (translated_y
* _height
))
1084 determine_visible_control_points (tmp_points
);
1088 AutomationLine::reset ()
1090 update_pending
= false;
1096 alist
->apply_to_points (*this, &AutomationLine::reset_callback
);
1100 AutomationLine::clear ()
1102 /* parent must create and commit command */
1103 XMLNode
&before
= alist
->get_state();
1106 trackview
.editor().session()->add_command (
1107 new MementoCommand
<AutomationList
> (memento_command_binder (), &before
, &alist
->get_state())
1112 AutomationLine::change_model (AutomationList::iterator
/*i*/, double /*x*/, double /*y*/)
1117 AutomationLine::set_list (boost::shared_ptr
<ARDOUR::AutomationList
> list
)
1125 AutomationLine::show_all_control_points ()
1128 // show the line but don't allow any control points
1132 points_visible
= true;
1134 for (vector
<ControlPoint
*>::iterator i
= control_points
.begin(); i
!= control_points
.end(); ++i
) {
1135 if (!(*i
)->visible()) {
1137 (*i
)->set_visible (true);
1143 AutomationLine::hide_all_but_selected_control_points ()
1145 if (alist
->interpolation() == AutomationList::Discrete
) {
1149 points_visible
= false;
1151 for (vector
<ControlPoint
*>::iterator i
= control_points
.begin(); i
!= control_points
.end(); ++i
) {
1152 if (!(*i
)->get_selected()) {
1153 (*i
)->set_visible (false);
1159 AutomationLine::track_entered()
1161 if (alist
->interpolation() != AutomationList::Discrete
) {
1162 show_all_control_points();
1167 AutomationLine::track_exited()
1169 if (alist
->interpolation() != AutomationList::Discrete
) {
1170 hide_all_but_selected_control_points();
1175 AutomationLine::get_state (void)
1177 /* function as a proxy for the model */
1178 return alist
->get_state();
1182 AutomationLine::set_state (const XMLNode
&node
, int version
)
1184 /* function as a proxy for the model */
1185 return alist
->set_state (node
, version
);
1189 AutomationLine::view_to_model_coord (double& x
, double& y
) const
1191 x
= _time_converter
.from (x
);
1192 view_to_model_coord_y (y
);
1196 AutomationLine::view_to_model_coord_y (double& y
) const
1198 /* TODO: This should be more generic ... */
1199 if (alist
->parameter().type() == GainAutomation
||
1200 alist
->parameter().type() == EnvelopeAutomation
) {
1201 y
= slider_position_to_gain (y
);
1204 } else if (alist
->parameter().type() == PanAzimuthAutomation
||
1205 alist
->parameter().type() == PanElevationAutomation
||
1206 alist
->parameter().type() == PanWidthAutomation
) {
1208 } else if (alist
->parameter().type() == PluginAutomation
) {
1209 y
= y
* (double)(alist
->get_max_y()- alist
->get_min_y()) + alist
->get_min_y();
1211 y
= rint (y
* alist
->parameter().max());
1216 AutomationLine::model_to_view_coord (double& x
, double& y
) const
1218 /* TODO: This should be more generic ... */
1219 if (alist
->parameter().type() == GainAutomation
||
1220 alist
->parameter().type() == EnvelopeAutomation
) {
1221 y
= gain_to_slider_position (y
);
1222 } else if (alist
->parameter().type() == PanAzimuthAutomation
||
1223 alist
->parameter().type() == PanElevationAutomation
||
1224 alist
->parameter().type() == PanWidthAutomation
) {
1225 // vertical coordinate axis reversal
1227 } else if (alist
->parameter().type() == PluginAutomation
) {
1228 y
= (y
- alist
->get_min_y()) / (double)(alist
->get_max_y()- alist
->get_min_y());
1230 y
= y
/ (double)alist
->parameter().max(); /* ... like this */
1233 x
= _time_converter
.to (x
) - _offset
;
1236 /** Called when our list has announced that its interpolation style has changed */
1238 AutomationLine::interpolation_changed (AutomationList::InterpolationStyle style
)
1240 if (style
== AutomationList::Discrete
) {
1241 show_all_control_points();
1244 hide_all_but_selected_control_points();
1250 AutomationLine::add_visible_control_point (uint32_t view_index
, uint32_t pi
, double tx
, double ty
, AutomationList::iterator model
, uint32_t npoints
)
1252 if (view_index
>= control_points
.size()) {
1254 /* make sure we have enough control points */
1256 ControlPoint
* ncp
= new ControlPoint (*this);
1257 ncp
->set_size (control_point_box_size ());
1259 control_points
.push_back (ncp
);
1262 ControlPoint::ShapeType shape
;
1264 if (!terminal_points_can_slide
) {
1266 control_points
[view_index
]->set_can_slide(false);
1268 shape
= ControlPoint::Start
;
1270 shape
= ControlPoint::Full
;
1272 } else if (pi
== npoints
- 1) {
1273 control_points
[view_index
]->set_can_slide(false);
1274 shape
= ControlPoint::End
;
1276 control_points
[view_index
]->set_can_slide(true);
1277 shape
= ControlPoint::Full
;
1280 control_points
[view_index
]->set_can_slide(true);
1281 shape
= ControlPoint::Full
;
1284 control_points
[view_index
]->reset (tx
, ty
, model
, view_index
, shape
);
1286 /* finally, control visibility */
1288 if (_visible
&& points_visible
) {
1289 control_points
[view_index
]->show ();
1290 control_points
[view_index
]->set_visible (true);
1292 if (!points_visible
) {
1293 control_points
[view_index
]->set_visible (false);
1299 AutomationLine::add_always_in_view (double x
)
1301 _always_in_view
.push_back (x
);
1302 alist
->apply_to_points (*this, &AutomationLine::reset_callback
);
1306 AutomationLine::clear_always_in_view ()
1308 _always_in_view
.clear ();
1309 alist
->apply_to_points (*this, &AutomationLine::reset_callback
);
1313 AutomationLine::connect_to_list ()
1315 _list_connections
.drop_connections ();
1317 alist
->StateChanged
.connect (_list_connections
, invalidator (*this), boost::bind (&AutomationLine::list_changed
, this), gui_context());
1319 alist
->InterpolationChanged
.connect (
1320 _list_connections
, invalidator (*this), boost::bind (&AutomationLine::interpolation_changed
, this, _1
), gui_context()
1324 MementoCommandBinder
<AutomationList
>*
1325 AutomationLine::memento_command_binder ()
1327 return new SimpleMementoCommandBinder
<AutomationList
> (*alist
.get());
1330 /** Set the maximum time that points on this line can be at, relative
1331 * to the start of the track or region that it is on.
1334 AutomationLine::set_maximum_time (framecnt_t t
)
1336 if (_maximum_time
== t
) {
1345 /** @return min and max x positions of points that are in the list, in session frames */
1346 pair
<framepos_t
, framepos_t
>
1347 AutomationLine::get_point_x_range () const
1349 pair
<framepos_t
, framepos_t
> r (max_framepos
, 0);
1351 for (AutomationList::const_iterator i
= the_list()->begin(); i
!= the_list()->end(); ++i
) {
1352 r
.first
= min (r
.first
, _time_converter
.to ((*i
)->when
) + _offset
+ _time_converter
.origin_b ());
1353 r
.second
= max (r
.second
, _time_converter
.to ((*i
)->when
) + _offset
+ _time_converter
.origin_b ());
1360 AutomationLine::set_offset (framepos_t off
)
1362 if (_offset
== off
) {