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
)
67 , _time_converter (converter
? (*converter
) : default_converter
)
68 , _maximum_time (max_framepos
)
70 points_visible
= false;
71 update_pending
= false;
72 _uses_gain_mapping
= false;
76 terminal_points_can_slide
= true;
79 group
= new ArdourCanvas::Group (parent
);
80 group
->property_x() = 0.0;
81 group
->property_y() = 0.0;
83 line
= new ArdourCanvas::Line (*group
);
84 line
->property_width_pixels() = (guint
)1;
85 line
->set_data ("line", this);
87 line
->signal_event().connect (sigc::mem_fun (*this, &AutomationLine::event_handler
));
91 trackview
.session()->register_with_memento_command_factory(alist
->id(), this);
93 if (alist
->parameter().type() == GainAutomation
||
94 alist
->parameter().type() == EnvelopeAutomation
) {
95 set_uses_gain_mapping (true);
98 interpolation_changed (alist
->interpolation ());
103 AutomationLine::~AutomationLine ()
105 vector_delete (&control_points
);
110 AutomationLine::event_handler (GdkEvent
* event
)
112 return PublicEditor::instance().canvas_line_event (event
, line
, this);
116 AutomationLine::queue_reset ()
118 if (!update_pending
) {
119 update_pending
= true;
120 Gtkmm2ext::UI::instance()->call_slot (invalidator (*this), boost::bind (&AutomationLine::reset
, this));
125 AutomationLine::show ()
127 if (alist
->interpolation() != AutomationList::Discrete
) {
131 if (points_visible
) {
132 for (vector
<ControlPoint
*>::iterator i
= control_points
.begin(); i
!= control_points
.end(); ++i
) {
141 AutomationLine::hide ()
144 for (vector
<ControlPoint
*>::iterator i
= control_points
.begin(); i
!= control_points
.end(); ++i
) {
151 AutomationLine::control_point_box_size ()
153 if (alist
->interpolation() == AutomationList::Discrete
) {
154 return max((_height
*4.0) / (double)(alist
->parameter().max() - alist
->parameter().min()),
158 if (_height
> TimeAxisView::preset_height (HeightLarger
)) {
160 } else if (_height
> (guint32
) TimeAxisView::preset_height (HeightNormal
)) {
167 AutomationLine::set_height (guint32 h
)
172 double bsz
= control_point_box_size();
174 for (vector
<ControlPoint
*>::iterator i
= control_points
.begin(); i
!= control_points
.end(); ++i
) {
175 (*i
)->set_size (bsz
);
183 AutomationLine::set_line_color (uint32_t color
)
186 line
->property_fill_color_rgba() = color
;
190 AutomationLine::set_uses_gain_mapping (bool yn
)
192 if (yn
!= _uses_gain_mapping
) {
193 _uses_gain_mapping
= yn
;
199 AutomationLine::nth (uint32_t n
)
201 if (n
< control_points
.size()) {
202 return control_points
[n
];
209 AutomationLine::nth (uint32_t n
) const
211 if (n
< control_points
.size()) {
212 return control_points
[n
];
219 AutomationLine::modify_point_y (ControlPoint
& cp
, double y
)
221 /* clamp y-coord appropriately. y is supposed to be a normalized fraction (0.0-1.0),
222 and needs to be converted to a canvas unit distance.
227 y
= _height
- (y
* _height
);
229 double const x
= trackview
.editor().frame_to_unit (_time_converter
.to((*cp
.model())->when
));
231 trackview
.editor().session()->begin_reversible_command (_("automation event move"));
232 trackview
.editor().session()->add_command (
233 new MementoCommand
<AutomationList
> (memento_command_binder(), &get_state(), 0)
236 cp
.move_to (x
, y
, ControlPoint::Full
);
238 reset_line_coords (cp
);
240 if (line_points
.size() > 1) {
241 line
->property_points() = line_points
;
245 sync_model_with_view_point (cp
, false, 0);
248 update_pending
= false;
250 trackview
.editor().session()->add_command (
251 new MementoCommand
<AutomationList
> (memento_command_binder(), 0, &alist
->get_state())
254 trackview
.editor().session()->commit_reversible_command ();
255 trackview
.editor().session()->set_dirty ();
259 AutomationLine::reset_line_coords (ControlPoint
& cp
)
261 if (cp
.view_index() < line_points
.size()) {
262 line_points
[cp
.view_index()].set_x (cp
.get_x());
263 line_points
[cp
.view_index()].set_y (cp
.get_y());
268 AutomationLine::sync_model_with_view_points (list
<ControlPoint
*> cp
, bool did_push
, int64_t distance
)
270 update_pending
= true;
272 for (list
<ControlPoint
*>::iterator i
= cp
.begin(); i
!= cp
.end(); ++i
) {
273 sync_model_with_view_point (**i
, did_push
, distance
);
278 AutomationLine::model_representation (ControlPoint
& cp
, ModelRepresentation
& mr
)
280 /* part one: find out where the visual control point is.
281 initial results are in canvas units. ask the
282 line to convert them to something relevant.
285 mr
.xval
= cp
.get_x();
286 mr
.yval
= 1.0 - (cp
.get_y() / _height
);
288 /* if xval has not changed, set it directly from the model to avoid rounding errors */
290 if (mr
.xval
== trackview
.editor().frame_to_unit(_time_converter
.to((*cp
.model())->when
))) {
291 mr
.xval
= (*cp
.model())->when
;
293 mr
.xval
= trackview
.editor().unit_to_frame (mr
.xval
);
294 mr
.xval
= _time_converter
.from (mr
.xval
);
297 /* convert y to model units; the x was already done above
300 view_to_model_coord_y (mr
.yval
);
302 /* part 2: find out where the model point is now
305 mr
.xpos
= (*cp
.model())->when
;
306 mr
.ypos
= (*cp
.model())->value
;
308 /* part 3: get the position of the visual control
309 points before and after us.
312 ControlPoint
* before
;
315 if (cp
.view_index()) {
316 before
= nth (cp
.view_index() - 1);
321 after
= nth (cp
.view_index() + 1);
324 mr
.xmin
= (*before
->model())->when
;
325 mr
.ymin
= (*before
->model())->value
;
326 mr
.start
= before
->model();
331 mr
.start
= cp
.model();
335 mr
.end
= after
->model();
345 AutomationLine::determine_visible_control_points (ALPoints
& points
)
347 uint32_t view_index
, pi
, n
;
348 AutomationList::iterator model
;
350 uint32_t this_rx
= 0;
351 uint32_t prev_rx
= 0;
352 uint32_t this_ry
= 0;
353 uint32_t prev_ry
= 0;
357 /* hide all existing points, and the line */
359 for (vector
<ControlPoint
*>::iterator i
= control_points
.begin(); i
!= control_points
.end(); ++i
) {
365 if (points
.empty()) {
369 npoints
= points
.size();
371 /* compute derivative/slope for the entire line */
373 slope
= new double[npoints
];
375 for (n
= 0; n
< npoints
- 1; ++n
) {
376 double xdelta
= points
[n
+1].x
- points
[n
].x
;
377 double ydelta
= points
[n
+1].y
- points
[n
].y
;
378 slope
[n
] = ydelta
/xdelta
;
381 box_size
= (uint32_t) control_point_box_size ();
383 /* read all points and decide which ones to show as control points */
387 for (model
= alist
->begin(), pi
= 0; pi
< npoints
; ++model
, ++pi
) {
389 double tx
= points
[pi
].x
;
390 double ty
= points
[pi
].y
;
392 if (find (_always_in_view
.begin(), _always_in_view
.end(), (*model
)->when
) != _always_in_view
.end()) {
393 add_visible_control_point (view_index
, pi
, tx
, ty
, model
, npoints
);
400 if (isnan (tx
) || isnan (ty
)) {
401 warning
<< string_compose (_("Ignoring illegal points on AutomationLine \"%1\""),
406 /* now ensure that the control_points vector reflects the current curve
407 state, but don't plot control points too close together. also, don't
408 plot a series of points all with the same value.
410 always plot the first and last points, of course.
413 if (invalid_point (points
, pi
)) {
414 /* for some reason, we are supposed to ignore this point,
415 but still keep track of the model index.
420 if (pi
> 0 && pi
< npoints
- 1) {
421 if (slope
[pi
] == slope
[pi
-1]) {
423 /* no reason to display this point */
429 /* need to round here. the ultimate coordinates are integer
430 pixels, so tiny deltas in the coords will be eliminated
431 and we end up with "colinear" line segments. since the
432 line rendering code in libart doesn't like this very
433 much, we eliminate them here. don't do this for the first and last
437 this_rx
= (uint32_t) rint (tx
);
438 this_ry
= (uint32_t) rint (ty
);
440 if (view_index
&& pi
!= npoints
&& /* not the first, not the last */
441 (((this_rx
== prev_rx
) && (this_ry
== prev_ry
)) || /* same point */
442 (((this_rx
- prev_rx
) < (box_size
+ 2)) && /* not identical, but still too close horizontally */
443 (abs ((int)(this_ry
- prev_ry
)) < (int) (box_size
+ 2))))) { /* too close vertically */
447 /* ok, we should display this point */
449 add_visible_control_point (view_index
, pi
, tx
, ty
, model
, npoints
);
457 /* discard extra CP's to avoid confusing ourselves */
459 while (control_points
.size() > view_index
) {
460 ControlPoint
* cp
= control_points
.back();
461 control_points
.pop_back ();
465 if (!terminal_points_can_slide
) {
466 control_points
.back()->set_can_slide(false);
471 if (view_index
> 1) {
473 npoints
= view_index
;
475 /* reset the line coordinates */
477 while (line_points
.size() < npoints
) {
478 line_points
.push_back (Art::Point (0,0));
481 while (line_points
.size() > npoints
) {
482 line_points
.pop_back ();
485 for (view_index
= 0; view_index
< npoints
; ++view_index
) {
486 line_points
[view_index
].set_x (control_points
[view_index
]->get_x());
487 line_points
[view_index
].set_y (control_points
[view_index
]->get_y());
490 line
->property_points() = line_points
;
492 if (_visible
&& alist
->interpolation() != AutomationList::Discrete
) {
498 set_selected_points (trackview
.editor().get_selection().points
);
502 AutomationLine::get_verbose_cursor_string (double fraction
) const
504 std::string s
= fraction_to_string (fraction
);
505 if (_uses_gain_mapping
) {
513 * @param fraction y fraction
514 * @return string representation of this value, using dB if appropriate.
517 AutomationLine::fraction_to_string (double fraction
) const
521 if (_uses_gain_mapping
) {
522 if (fraction
== 0.0) {
523 snprintf (buf
, sizeof (buf
), "-inf");
525 snprintf (buf
, sizeof (buf
), "%.1f", accurate_coefficient_to_dB (slider_position_to_gain (fraction
)));
529 view_to_model_coord (dummy
, 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
) + _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 add_model_point (tmp_points
, (*ai
)->when
, translated_y
);
1081 determine_visible_control_points (tmp_points
);
1086 AutomationLine::add_model_point (ALPoints
& tmp_points
, double frame
, double yfract
)
1088 tmp_points
.push_back (ALPoint (trackview
.editor().frame_to_unit (_time_converter
.to(frame
)),
1089 _height
- (yfract
* _height
)));
1093 AutomationLine::reset ()
1095 update_pending
= false;
1101 alist
->apply_to_points (*this, &AutomationLine::reset_callback
);
1105 AutomationLine::clear ()
1107 /* parent must create and commit command */
1108 XMLNode
&before
= alist
->get_state();
1111 trackview
.editor().session()->add_command (
1112 new MementoCommand
<AutomationList
> (memento_command_binder (), &before
, &alist
->get_state())
1117 AutomationLine::change_model (AutomationList::iterator
/*i*/, double /*x*/, double /*y*/)
1122 AutomationLine::set_list (boost::shared_ptr
<ARDOUR::AutomationList
> list
)
1130 AutomationLine::show_all_control_points ()
1133 // show the line but don't allow any control points
1137 points_visible
= true;
1139 for (vector
<ControlPoint
*>::iterator i
= control_points
.begin(); i
!= control_points
.end(); ++i
) {
1140 if (!(*i
)->visible()) {
1142 (*i
)->set_visible (true);
1148 AutomationLine::hide_all_but_selected_control_points ()
1150 if (alist
->interpolation() == AutomationList::Discrete
) {
1154 points_visible
= false;
1156 for (vector
<ControlPoint
*>::iterator i
= control_points
.begin(); i
!= control_points
.end(); ++i
) {
1157 if (!(*i
)->get_selected()) {
1158 (*i
)->set_visible (false);
1164 AutomationLine::track_entered()
1166 if (alist
->interpolation() != AutomationList::Discrete
) {
1167 show_all_control_points();
1172 AutomationLine::track_exited()
1174 if (alist
->interpolation() != AutomationList::Discrete
) {
1175 hide_all_but_selected_control_points();
1180 AutomationLine::get_state (void)
1182 /* function as a proxy for the model */
1183 return alist
->get_state();
1187 AutomationLine::set_state (const XMLNode
&node
, int version
)
1189 /* function as a proxy for the model */
1190 return alist
->set_state (node
, version
);
1194 AutomationLine::view_to_model_coord (double& x
, double& y
) const
1196 x
= _time_converter
.from (x
);
1197 view_to_model_coord_y (y
);
1201 AutomationLine::view_to_model_coord_y (double& y
) const
1203 /* TODO: This should be more generic ... */
1204 if (alist
->parameter().type() == GainAutomation
||
1205 alist
->parameter().type() == EnvelopeAutomation
) {
1206 y
= slider_position_to_gain (y
);
1209 } else if (alist
->parameter().type() == PanAutomation
) {
1210 // vertical coordinate axis reversal
1212 } else if (alist
->parameter().type() == PluginAutomation
) {
1213 y
= y
* (double)(alist
->get_max_y()- alist
->get_min_y()) + alist
->get_min_y();
1215 y
= rint (y
* alist
->parameter().max());
1220 AutomationLine::model_to_view_coord (double& x
, double& y
) const
1222 /* TODO: This should be more generic ... */
1223 if (alist
->parameter().type() == GainAutomation
||
1224 alist
->parameter().type() == EnvelopeAutomation
) {
1225 y
= gain_to_slider_position (y
);
1226 } else if (alist
->parameter().type() == PanAutomation
) {
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
);
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 (framepos_t t
)
1342 /** @return min and max x positions of points that are in the list, in session frames */
1343 pair
<framepos_t
, framepos_t
>
1344 AutomationLine::get_point_x_range () const
1346 pair
<framepos_t
, framepos_t
> r (max_framepos
, 0);
1348 for (AutomationList::const_iterator i
= the_list()->begin(); i
!= the_list()->end(); ++i
) {
1349 r
.first
= min (r
.first
, _time_converter
.to ((*i
)->when
) + _time_converter
.origin_b ());
1350 r
.second
= max (r
.second
, _time_converter
.to ((*i
)->when
) + _time_converter
.origin_b ());