2 Copyright (C) 2002-2003 Paul Davis
4 This program is free software; you can redistribute it and/or modify
5 it under the terms of the GNU General Public License as published by
6 the Free Software Foundation; either version 2 of the License, or
7 (at your option) any later version.
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 GNU General Public License for more details.
14 You should have received a copy of the GNU General Public License
15 along with this program; if not, write to the Free Software
16 Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
25 #include "pbd/stl_delete.h"
26 #include "pbd/memento_command.h"
27 #include "pbd/stacktrace.h"
29 #include "ardour/automation_list.h"
30 #include "ardour/dB.h"
31 #include "evoral/Curve.hpp"
33 #include "simplerect.h"
34 #include "automation_line.h"
35 #include "control_point.h"
36 #include "gui_thread.h"
37 #include "rgb_macros.h"
38 #include "ardour_ui.h"
39 #include "public_editor.h"
41 #include "selection.h"
42 #include "time_axis_view.h"
43 #include "point_selection.h"
44 #include "automation_selectable.h"
45 #include "automation_time_axis.h"
46 #include "public_editor.h"
48 #include "ardour/event_type_map.h"
49 #include "ardour/session.h"
54 using namespace ARDOUR;
56 using namespace Editing;
57 using namespace Gnome; // for Canvas
59 static const Evoral::IdentityConverter<double, sframes_t> default_converter;
61 AutomationLine::AutomationLine (const string& name, TimeAxisView& tv, ArdourCanvas::Group& parent,
62 boost::shared_ptr<AutomationList> al,
63 const Evoral::TimeConverter<double, sframes_t>* converter)
67 , _parent_group (parent)
68 , _time_converter (converter ? (*converter) : default_converter)
70 _interpolation = al->interpolation();
71 points_visible = false;
72 update_pending = false;
73 _uses_gain_mapping = false;
76 terminal_points_can_slide = true;
79 group = new ArdourCanvas::Group (parent);
80 group->property_x() = 0.0;
81 group->property_y() = 0.0;
83 line = new ArdourCanvas::Line (*group);
84 line->property_width_pixels() = (guint)1;
85 line->set_data ("line", this);
87 line->signal_event().connect (sigc::mem_fun (*this, &AutomationLine::event_handler));
89 alist->StateChanged.connect (_state_connection, invalidator (*this), boost::bind (&AutomationLine::list_changed, this), gui_context());
91 trackview.session()->register_with_memento_command_factory(alist->id(), this);
93 if (alist->parameter().type() == GainAutomation ||
94 alist->parameter().type() == EnvelopeAutomation) {
95 set_uses_gain_mapping (true);
98 set_interpolation(alist->interpolation());
101 AutomationLine::~AutomationLine ()
103 vector_delete (&control_points);
108 AutomationLine::event_handler (GdkEvent* event)
110 return PublicEditor::instance().canvas_line_event (event, line, this);
114 AutomationLine::queue_reset ()
116 if (!update_pending) {
117 update_pending = true;
118 Gtkmm2ext::UI::instance()->call_slot (invalidator (*this), boost::bind (&AutomationLine::reset, this));
123 AutomationLine::show ()
125 if (_interpolation != AutomationList::Discrete) {
129 if (points_visible) {
130 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
139 AutomationLine::hide ()
142 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
149 AutomationLine::control_point_box_size ()
151 if (_interpolation == AutomationList::Discrete) {
152 return max((_height*4.0) / (double)(alist->parameter().max() - alist->parameter().min()),
156 if (_height > TimeAxisView::hLarger) {
158 } else if (_height > (guint32) TimeAxisView::hNormal) {
165 AutomationLine::set_height (guint32 h)
170 double bsz = control_point_box_size();
172 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
173 (*i)->set_size (bsz);
181 AutomationLine::set_line_color (uint32_t color)
184 line->property_fill_color_rgba() = color;
188 AutomationLine::set_uses_gain_mapping (bool yn)
190 if (yn != _uses_gain_mapping) {
191 _uses_gain_mapping = yn;
197 AutomationLine::nth (uint32_t n)
199 if (n < control_points.size()) {
200 return control_points[n];
207 AutomationLine::modify_point_y (ControlPoint& cp, double y)
209 /* clamp y-coord appropriately. y is supposed to be a normalized fraction (0.0-1.0),
210 and needs to be converted to a canvas unit distance.
215 y = _height - (y * _height);
217 double const x = trackview.editor().frame_to_unit (_time_converter.to((*cp.model())->when));
219 trackview.editor().session()->begin_reversible_command (_("automation event move"));
220 trackview.editor().session()->add_command (new MementoCommand<AutomationList>(*alist.get(), &get_state(), 0));
222 cp.move_to (x, y, ControlPoint::Full);
223 reset_line_coords (cp);
225 if (line_points.size() > 1) {
226 line->property_points() = line_points;
230 sync_model_with_view_point (cp, false, 0);
233 update_pending = false;
235 trackview.editor().session()->add_command (new MementoCommand<AutomationList>(*alist.get(), 0, &alist->get_state()));
236 trackview.editor().session()->commit_reversible_command ();
237 trackview.editor().session()->set_dirty ();
241 AutomationLine::reset_line_coords (ControlPoint& cp)
243 if (cp.view_index() < line_points.size()) {
244 line_points[cp.view_index()].set_x (cp.get_x());
245 line_points[cp.view_index()].set_y (cp.get_y());
250 AutomationLine::sync_model_with_view_points (list<ControlPoint*> cp, bool did_push, int64_t distance)
252 update_pending = true;
254 for (list<ControlPoint*>::iterator i = cp.begin(); i != cp.end(); ++i) {
255 sync_model_with_view_point (**i, did_push, distance);
260 AutomationLine::model_representation (ControlPoint& cp, ModelRepresentation& mr)
262 /* part one: find out where the visual control point is.
263 initial results are in canvas units. ask the
264 line to convert them to something relevant.
267 mr.xval = cp.get_x();
268 mr.yval = 1.0 - (cp.get_y() / _height);
270 /* if xval has not changed, set it directly from the model to avoid rounding errors */
272 if (mr.xval == trackview.editor().frame_to_unit(_time_converter.to((*cp.model())->when))) {
273 mr.xval = (*cp.model())->when;
275 mr.xval = trackview.editor().unit_to_frame (mr.xval);
278 /* convert to model units
281 view_to_model_coord (mr.xval, mr.yval);
283 /* part 2: find out where the model point is now
286 mr.xpos = (*cp.model())->when;
287 mr.ypos = (*cp.model())->value;
289 /* part 3: get the position of the visual control
290 points before and after us.
293 ControlPoint* before;
296 if (cp.view_index()) {
297 before = nth (cp.view_index() - 1);
302 after = nth (cp.view_index() + 1);
305 mr.xmin = (*before->model())->when;
306 mr.ymin = (*before->model())->value;
307 mr.start = before->model();
312 mr.start = cp.model();
316 mr.end = after->model();
326 AutomationLine::determine_visible_control_points (ALPoints& points)
328 uint32_t view_index, pi, n;
329 AutomationList::iterator model;
331 uint32_t this_rx = 0;
332 uint32_t prev_rx = 0;
333 uint32_t this_ry = 0;
334 uint32_t prev_ry = 0;
338 /* hide all existing points, and the line */
340 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
346 if (points.empty()) {
350 npoints = points.size();
352 /* compute derivative/slope for the entire line */
354 slope = new double[npoints];
356 for (n = 0; n < npoints - 1; ++n) {
357 double xdelta = points[n+1].x - points[n].x;
358 double ydelta = points[n+1].y - points[n].y;
359 slope[n] = ydelta/xdelta;
362 box_size = (uint32_t) control_point_box_size ();
364 /* read all points and decide which ones to show as control points */
368 for (model = alist->begin(), pi = 0; pi < npoints; ++model, ++pi) {
370 double tx = points[pi].x;
371 double ty = points[pi].y;
373 if (find (_always_in_view.begin(), _always_in_view.end(), (*model)->when) != _always_in_view.end()) {
374 add_visible_control_point (view_index, pi, tx, ty, model, npoints);
381 if (isnan (tx) || isnan (ty)) {
382 warning << string_compose (_("Ignoring illegal points on AutomationLine \"%1\""),
387 /* now ensure that the control_points vector reflects the current curve
388 state, but don't plot control points too close together. also, don't
389 plot a series of points all with the same value.
391 always plot the first and last points, of course.
394 if (invalid_point (points, pi)) {
395 /* for some reason, we are supposed to ignore this point,
396 but still keep track of the model index.
401 if (pi > 0 && pi < npoints - 1) {
402 if (slope[pi] == slope[pi-1]) {
404 /* no reason to display this point */
410 /* need to round here. the ultimate coordinates are integer
411 pixels, so tiny deltas in the coords will be eliminated
412 and we end up with "colinear" line segments. since the
413 line rendering code in libart doesn't like this very
414 much, we eliminate them here. don't do this for the first and last
418 this_rx = (uint32_t) rint (tx);
419 this_ry = (uint32_t) rint (ty);
421 if (view_index && pi != npoints && /* not the first, not the last */
422 (((this_rx == prev_rx) && (this_ry == prev_ry)) || /* same point */
423 (((this_rx - prev_rx) < (box_size + 2)) && /* not identical, but still too close horizontally */
424 (abs ((int)(this_ry - prev_ry)) < (int) (box_size + 2))))) { /* too close vertically */
428 /* ok, we should display this point */
430 add_visible_control_point (view_index, pi, tx, ty, model, npoints);
438 /* discard extra CP's to avoid confusing ourselves */
440 while (control_points.size() > view_index) {
441 ControlPoint* cp = control_points.back();
442 control_points.pop_back ();
446 if (!terminal_points_can_slide) {
447 control_points.back()->set_can_slide(false);
452 if (view_index > 1) {
454 npoints = view_index;
456 /* reset the line coordinates */
458 while (line_points.size() < npoints) {
459 line_points.push_back (Art::Point (0,0));
462 while (line_points.size() > npoints) {
463 line_points.pop_back ();
466 for (view_index = 0; view_index < npoints; ++view_index) {
467 line_points[view_index].set_x (control_points[view_index]->get_x());
468 line_points[view_index].set_y (control_points[view_index]->get_y());
471 line->property_points() = line_points;
473 if (_visible && _interpolation != AutomationList::Discrete) {
479 set_selected_points (trackview.editor().get_selection().points);
483 AutomationLine::get_verbose_cursor_string (double fraction) const
485 std::string s = fraction_to_string (fraction);
486 if (_uses_gain_mapping) {
494 * @param fraction y fraction
495 * @return string representation of this value, using dB if appropriate.
498 AutomationLine::fraction_to_string (double fraction) const
502 if (_uses_gain_mapping) {
503 if (fraction == 0.0) {
504 snprintf (buf, sizeof (buf), "-inf");
506 snprintf (buf, sizeof (buf), "%.1f", accurate_coefficient_to_dB (slider_position_to_gain (fraction)));
510 view_to_model_coord (dummy, fraction);
511 if (EventTypeMap::instance().is_integer (alist->parameter())) {
512 snprintf (buf, sizeof (buf), "%d", (int)fraction);
514 snprintf (buf, sizeof (buf), "%.2f", fraction);
523 * @param s Value string in the form as returned by fraction_to_string.
524 * @return Corresponding y fraction.
527 AutomationLine::string_to_fraction (string const & s) const
534 sscanf (s.c_str(), "%lf", &v);
536 if (_uses_gain_mapping) {
537 v = gain_to_slider_position (dB_to_coefficient (v));
540 model_to_view_coord (dummy, v);
547 AutomationLine::invalid_point (ALPoints& p, uint32_t index)
549 return p[index].x == max_frames && p[index].y == DBL_MAX;
553 AutomationLine::invalidate_point (ALPoints& p, uint32_t index)
555 p[index].x = max_frames;
556 p[index].y = DBL_MAX;
559 /** Start dragging a single point, possibly adding others if the supplied point is selected and there
560 * are other selected points.
562 * @param cp Point to drag.
563 * @param x Initial x position (units).
564 * @param fraction Initial y position (as a fraction of the track height, where 0 is the bottom and 1 the top)
567 AutomationLine::start_drag_single (ControlPoint* cp, double x, float fraction)
569 trackview.editor().session()->begin_reversible_command (_("automation event move"));
570 trackview.editor().session()->add_command (new MementoCommand<AutomationList>(*alist.get(), &get_state(), 0));
572 _drag_points.clear ();
573 _drag_points.push_back (cp);
575 if (cp->selected ()) {
576 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
577 if (*i != cp && (*i)->selected()) {
578 _drag_points.push_back (*i);
583 start_drag_common (x, fraction);
586 /** Start dragging a line vertically (with no change in x)
587 * @param i1 Control point index of the `left' point on the line.
588 * @param i2 Control point index of the `right' point on the line.
589 * @param fraction Initial y position (as a fraction of the track height, where 0 is the bottom and 1 the top)
592 AutomationLine::start_drag_line (uint32_t i1, uint32_t i2, float fraction)
594 trackview.editor().session()->begin_reversible_command (_("automation range move"));
595 trackview.editor().session()->add_command (new MementoCommand<AutomationList>(*alist.get(), &get_state(), 0));
597 _drag_points.clear ();
598 for (uint32_t i = i1; i <= i2; i++) {
599 _drag_points.push_back (nth (i));
602 start_drag_common (0, fraction);
605 /** Start dragging multiple points (with no change in x)
606 * @param cp Points to drag.
607 * @param fraction Initial y position (as a fraction of the track height, where 0 is the bottom and 1 the top)
610 AutomationLine::start_drag_multiple (list<ControlPoint*> cp, float fraction, XMLNode* state)
612 trackview.editor().session()->begin_reversible_command (_("automation range move"));
613 trackview.editor().session()->add_command (new MementoCommand<AutomationList>(*alist.get(), state, 0));
616 start_drag_common (0, fraction);
620 struct ControlPointSorter
622 bool operator() (ControlPoint const * a, ControlPoint const * b) {
623 return a->get_x() < b->get_x();
627 /** Common parts of starting a drag.
628 * @param d Description of the drag.
629 * @param x Starting x position in units, or 0 if x is being ignored.
630 * @param fraction Starting y position (as a fraction of the track height, where 0 is the bottom and 1 the top)
633 AutomationLine::start_drag_common (double x, float fraction)
637 _last_drag_fraction = fraction;
638 _drag_had_movement = false;
641 _drag_points.sort (ControlPointSorter ());
643 /* find the additional points that will be dragged when the user is holding
647 uint32_t i = _drag_points.back()->view_index () + 1;
649 _push_points.clear ();
650 while ((p = nth (i)) != 0 && p->can_slide()) {
651 _push_points.push_back (p);
656 /** Should be called to indicate motion during a drag.
657 * @param x New x position of the drag in units, or undefined if ignore_x == true.
658 * @param fraction New y fraction.
659 * @return x position and y fraction that were actually used (once clamped).
662 AutomationLine::drag_motion (double x, float fraction, bool ignore_x, bool with_push)
664 /* setup the points that are to be moved this time round */
665 list<ControlPoint*> points = _drag_points;
667 copy (_push_points.begin(), _push_points.end(), back_inserter (points));
668 points.sort (ControlPointSorter ());
671 double dx = ignore_x ? 0 : (x - _drag_x);
672 double dy = fraction - _last_drag_fraction;
675 ControlPoint* before = 0;
676 ControlPoint* after = 0;
678 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
679 if ((*i)->get_x() < points.front()->get_x()) {
682 if ((*i)->get_x() > points.back()->get_x() && after == 0) {
687 double const before_x = before ? before->get_x() : 0;
688 double const after_x = after ? after->get_x() : DBL_MAX;
691 for (list<ControlPoint*>::iterator i = points.begin(); i != points.end(); ++i) {
693 if ((*i)->can_slide() && !ignore_x) {
696 double const a = (*i)->get_x() + dx;
697 double const b = before_x + 1;
705 if (after_x - before_x < 2) {
706 /* after and before are very close, so just leave this alone */
709 double const a = (*i)->get_x() + dx;
710 double const b = after_x - 1;
720 for (list<ControlPoint*>::iterator i = points.begin(); i != points.end(); ++i) {
721 double const y = ((_height - (*i)->get_y()) / _height) + dy;
730 pair<double, float> const clamped (_drag_x + dx, _last_drag_fraction + dy);
731 _drag_distance += dx;
733 _last_drag_fraction = fraction;
735 for (list<ControlPoint*>::iterator i = _drag_points.begin(); i != _drag_points.end(); ++i) {
736 (*i)->move_to ((*i)->get_x() + dx, (*i)->get_y() - _height * dy, ControlPoint::Full);
737 reset_line_coords (**i);
741 /* move push points, preserving their y */
742 for (list<ControlPoint*>::iterator i = _push_points.begin(); i != _push_points.end(); ++i) {
743 (*i)->move_to ((*i)->get_x() + dx, (*i)->get_y(), ControlPoint::Full);
744 reset_line_coords (**i);
748 if (line_points.size() > 1) {
749 line->property_points() = line_points;
752 _drag_had_movement = true;
753 did_push = with_push;
758 /** Should be called to indicate the end of a drag */
760 AutomationLine::end_drag ()
762 if (!_drag_had_movement) {
768 /* set up the points that were moved this time round */
769 list<ControlPoint*> points = _drag_points;
771 copy (_push_points.begin(), _push_points.end(), back_inserter (points));
772 points.sort (ControlPointSorter ());
775 sync_model_with_view_points (points, did_push, rint (_drag_distance * trackview.editor().get_current_zoom ()));
779 update_pending = false;
781 trackview.editor().session()->add_command (new MementoCommand<AutomationList>(*alist.get(), 0, &alist->get_state()));
782 trackview.editor().session()->commit_reversible_command ();
783 trackview.editor().session()->set_dirty ();
787 AutomationLine::sync_model_with_view_point (ControlPoint& cp, bool did_push, int64_t distance)
789 ModelRepresentation mr;
792 model_representation (cp, mr);
794 /* how much are we changing the central point by */
796 ydelta = mr.yval - mr.ypos;
799 apply the full change to the central point, and interpolate
800 on both axes to cover all model points represented
801 by the control point.
804 /* change all points before the primary point */
806 for (AutomationList::iterator i = mr.start; i != cp.model(); ++i) {
808 double fract = ((*i)->when - mr.xmin) / (mr.xpos - mr.xmin);
809 double y_delta = ydelta * fract;
810 double x_delta = distance * fract;
814 if (y_delta || x_delta) {
815 alist->modify (i, (*i)->when + x_delta, mr.ymin + y_delta);
819 /* change the primary point */
821 update_pending = true;
822 alist->modify (cp.model(), mr.xval, mr.yval);
825 /* change later points */
827 AutomationList::iterator i = cp.model();
831 while (i != mr.end) {
833 double delta = ydelta * (mr.xmax - (*i)->when) / (mr.xmax - mr.xpos);
835 /* all later points move by the same distance along the x-axis as the main point */
838 alist->modify (i, (*i)->when + distance, (*i)->value + delta);
846 /* move all points after the range represented by the view by the same distance
847 as the main point moved.
850 alist->slide (mr.end, distance);
855 AutomationLine::control_points_adjacent (double xval, uint32_t & before, uint32_t& after)
857 ControlPoint *bcp = 0;
858 ControlPoint *acp = 0;
861 unit_xval = trackview.editor().frame_to_unit (xval);
863 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
865 if ((*i)->get_x() <= unit_xval) {
867 if (!bcp || (*i)->get_x() > bcp->get_x()) {
869 before = bcp->view_index();
872 } else if ((*i)->get_x() > unit_xval) {
874 after = acp->view_index();
883 AutomationLine::is_last_point (ControlPoint& cp)
885 ModelRepresentation mr;
887 model_representation (cp, mr);
889 // If the list is not empty, and the point is the last point in the list
891 if (!alist->empty() && mr.end == alist->end()) {
899 AutomationLine::is_first_point (ControlPoint& cp)
901 ModelRepresentation mr;
903 model_representation (cp, mr);
905 // If the list is not empty, and the point is the first point in the list
907 if (!alist->empty() && mr.start == alist->begin()) {
914 // This is copied into AudioRegionGainLine
916 AutomationLine::remove_point (ControlPoint& cp)
918 ModelRepresentation mr;
920 model_representation (cp, mr);
922 trackview.editor().session()->begin_reversible_command (_("remove control point"));
923 XMLNode &before = alist->get_state();
925 alist->erase (mr.start, mr.end);
927 trackview.editor().session()->add_command(new MementoCommand<AutomationList>(
928 *alist.get(), &before, &alist->get_state()));
929 trackview.editor().session()->commit_reversible_command ();
930 trackview.editor().session()->set_dirty ();
934 AutomationLine::get_selectables (nframes_t& start, nframes_t& end,
935 double botfrac, double topfrac, list<Selectable*>& results)
942 bool collecting = false;
944 /* Curse X11 and its inverted coordinate system! */
946 bot = (1.0 - topfrac) * _height;
947 top = (1.0 - botfrac) * _height;
952 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
953 double when = (*(*i)->model())->when;
955 if (when >= start && when <= end) {
957 if ((*i)->get_y() >= bot && (*i)->get_y() <= top) {
960 (*i)->set_visible(true);
962 nstart = min (nstart, when);
963 nend = max (nend, when);
969 results.push_back (new AutomationSelectable (nstart, nend, botfrac, topfrac, &trackview));
979 results.push_back (new AutomationSelectable (nstart, nend, botfrac, topfrac, &trackview));
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 /* Curse X11 and its inverted coordinate system! */
1004 double const bot = (1.0 - i->high_fract) * _height;
1005 double const top = (1.0 - i->low_fract) * _height;
1007 for (vector<ControlPoint*>::iterator j = control_points.begin(); j != control_points.end(); ++j) {
1009 double const rstart = trackview.editor().frame_to_unit (i->start);
1010 double const rend = trackview.editor().frame_to_unit (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 command */
1108 XMLNode &before = get_state();
1110 trackview.editor().session()->add_command (
1111 new MementoCommand<AutomationLine>(*this, &before, &get_state()));
1112 trackview.editor().session()->commit_reversible_command ();
1113 trackview.editor().session()->set_dirty ();
1117 AutomationLine::change_model (AutomationList::iterator /*i*/, double /*x*/, double /*y*/)
1122 AutomationLine::set_list(boost::shared_ptr<ARDOUR::AutomationList> list)
1129 AutomationLine::show_all_control_points ()
1131 points_visible = true;
1133 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1134 if (!(*i)->visible()) {
1136 (*i)->set_visible (true);
1142 AutomationLine::hide_all_but_selected_control_points ()
1144 if (alist->interpolation() == AutomationList::Discrete) {
1148 points_visible = false;
1150 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1151 if (!(*i)->selected()) {
1152 (*i)->set_visible (false);
1158 AutomationLine::track_entered()
1160 if (alist->interpolation() != AutomationList::Discrete) {
1161 show_all_control_points();
1166 AutomationLine::track_exited()
1168 if (alist->interpolation() != AutomationList::Discrete) {
1169 hide_all_but_selected_control_points();
1174 AutomationLine::get_state (void)
1176 /* function as a proxy for the model */
1177 return alist->get_state();
1181 AutomationLine::set_state (const XMLNode &node, int version)
1183 /* function as a proxy for the model */
1184 return alist->set_state (node, version);
1188 AutomationLine::view_to_model_coord (double& x, double& y) const
1190 /* TODO: This should be more generic ... */
1191 if (alist->parameter().type() == GainAutomation ||
1192 alist->parameter().type() == EnvelopeAutomation) {
1193 y = slider_position_to_gain (y);
1196 } else if (alist->parameter().type() == PanAutomation) {
1197 // vertical coordinate axis reversal
1199 } else if (alist->parameter().type() == PluginAutomation) {
1200 y = y * (double)(alist->get_max_y()- alist->get_min_y()) + alist->get_min_y();
1202 y = (int)(y * alist->parameter().max());
1205 x = _time_converter.from(x);
1209 AutomationLine::model_to_view_coord (double& x, double& y) const
1211 /* TODO: This should be more generic ... */
1212 if (alist->parameter().type() == GainAutomation ||
1213 alist->parameter().type() == EnvelopeAutomation) {
1214 y = gain_to_slider_position (y);
1215 } else if (alist->parameter().type() == PanAutomation) {
1216 // vertical coordinate axis reversal
1218 } else if (alist->parameter().type() == PluginAutomation) {
1219 y = (y - alist->get_min_y()) / (double)(alist->get_max_y()- alist->get_min_y());
1221 y = y / (double)alist->parameter().max(); /* ... like this */
1224 x = _time_converter.to(x);
1229 AutomationLine::set_interpolation(AutomationList::InterpolationStyle style)
1231 _interpolation = style;
1233 if (style == AutomationList::Discrete) {
1234 show_all_control_points();
1237 hide_all_but_selected_control_points();
1243 AutomationLine::add_visible_control_point (uint32_t view_index, uint32_t pi, double tx, double ty, AutomationList::iterator model, uint32_t npoints)
1245 if (view_index >= control_points.size()) {
1247 /* make sure we have enough control points */
1249 ControlPoint* ncp = new ControlPoint (*this);
1250 ncp->set_size (control_point_box_size ());
1252 control_points.push_back (ncp);
1255 ControlPoint::ShapeType shape;
1257 if (!terminal_points_can_slide) {
1259 control_points[view_index]->set_can_slide(false);
1261 shape = ControlPoint::Start;
1263 shape = ControlPoint::Full;
1265 } else if (pi == npoints - 1) {
1266 control_points[view_index]->set_can_slide(false);
1267 shape = ControlPoint::End;
1269 control_points[view_index]->set_can_slide(true);
1270 shape = ControlPoint::Full;
1273 control_points[view_index]->set_can_slide(true);
1274 shape = ControlPoint::Full;
1277 control_points[view_index]->reset (tx, ty, model, view_index, shape);
1279 /* finally, control visibility */
1281 if (_visible && points_visible) {
1282 control_points[view_index]->show ();
1283 control_points[view_index]->set_visible (true);
1285 if (!points_visible) {
1286 control_points[view_index]->set_visible (false);
1292 AutomationLine::add_always_in_view (double x)
1294 _always_in_view.push_back (x);
1295 alist->apply_to_points (*this, &AutomationLine::reset_callback);
1299 AutomationLine::clear_always_in_view ()
1301 _always_in_view.clear ();
1302 alist->apply_to_points (*this, &AutomationLine::reset_callback);