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 "rgb_macros.h"
37 #include "ardour_ui.h"
38 #include "public_editor.h"
40 #include "selection.h"
41 #include "time_axis_view.h"
42 #include "point_selection.h"
43 #include "automation_selectable.h"
44 #include "automation_time_axis.h"
45 #include "public_editor.h"
47 #include <ardour/session.h>
53 using namespace ARDOUR;
55 using namespace Editing;
56 using namespace Gnome; // for Canvas
58 AutomationLine::AutomationLine (const string& name, TimeAxisView& tv, ArdourCanvas::Group& parent, boost::shared_ptr<AutomationList> al)
62 _parent_group (parent)
64 _interpolation = al->interpolation();
65 points_visible = false;
66 update_pending = false;
67 _vc_uses_gain_mapping = false;
70 terminal_points_can_slide = true;
73 group = new ArdourCanvas::Group (parent);
74 group->property_x() = 0.0;
75 group->property_y() = 0.0;
77 line = new ArdourCanvas::Line (*group);
78 line->property_width_pixels() = (guint)1;
79 line->set_data ("line", this);
81 line->signal_event().connect (mem_fun (*this, &AutomationLine::event_handler));
83 alist->StateChanged.connect (mem_fun(*this, &AutomationLine::list_changed));
85 trackview.session().register_with_memento_command_factory(alist->id(), this);
87 if (alist->parameter().type() == GainAutomation)
88 set_verbose_cursor_uses_gain_mapping (true);
90 set_interpolation(alist->interpolation());
93 AutomationLine::~AutomationLine ()
95 vector_delete (&control_points);
100 AutomationLine::event_handler (GdkEvent* event)
102 return PublicEditor::instance().canvas_line_event (event, line, this);
106 AutomationLine::queue_reset ()
108 if (!update_pending) {
109 update_pending = true;
110 Gtkmm2ext::UI::instance()->call_slot (mem_fun(*this, &AutomationLine::reset));
115 AutomationLine::show ()
117 if (_interpolation != AutomationList::Discrete)
120 if (points_visible) {
121 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
130 AutomationLine::hide ()
133 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
140 AutomationLine::control_point_box_size ()
142 if (_interpolation == AutomationList::Discrete) {
143 return max((_height*4.0) / (double)(alist->parameter().max() - alist->parameter().min()),
147 if (_height > TimeAxisView::hLarger) {
149 } else if (_height > (guint32) TimeAxisView::hNormal) {
156 AutomationLine::set_height (guint32 h)
161 double bsz = control_point_box_size();
163 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
164 (*i)->set_size (bsz);
172 AutomationLine::set_line_color (uint32_t color)
175 line->property_fill_color_rgba() = color;
179 AutomationLine::set_verbose_cursor_uses_gain_mapping (bool yn)
181 if (yn != _vc_uses_gain_mapping) {
182 _vc_uses_gain_mapping = yn;
188 AutomationLine::nth (uint32_t n)
190 if (n < control_points.size()) {
191 return control_points[n];
198 AutomationLine::modify_view_point (ControlPoint& cp, double x, double y, bool with_push)
201 uint32_t last_movable = UINT_MAX;
202 double x_limit = DBL_MAX;
204 /* this just changes the current view. it does not alter
205 the model in any way at all.
208 /* clamp y-coord appropriately. y is supposed to be a normalized fraction (0.0-1.0),
209 and needs to be converted to a canvas unit distance.
214 y = _height - (y * _height);
216 if (cp.can_slide()) {
218 /* x-coord cannot move beyond adjacent points or the start/end, and is
219 already in frames. it needs to be converted to canvas units.
222 x = trackview.editor.frame_to_unit (x);
224 /* clamp x position using view coordinates */
226 ControlPoint *before;
229 if (cp.view_index()) {
230 before = nth (cp.view_index() - 1);
231 x = max (x, before->get_x()+1.0);
238 if (cp.view_index() < control_points.size() - 1) {
240 after = nth (cp.view_index() + 1);
242 /*if it is a "spike" leave the x alone */
244 if (after->get_x() - before->get_x() < 2) {
248 x = min (x, after->get_x()-1.0);
258 /* find the first point that can't move */
260 for (uint32_t n = cp.view_index() + 1; (after = nth (n)) != 0; ++n) {
261 if (!after->can_slide()) {
262 x_limit = after->get_x() - 1.0;
263 last_movable = after->view_index();
268 delta = x - cp.get_x();
273 /* leave the x-coordinate alone */
275 x = trackview.editor.frame_to_unit ((*cp.model())->when);
281 cp.move_to (x, y, ControlPoint::Full);
282 reset_line_coords (cp);
286 uint32_t limit = min (control_points.size(), (size_t)last_movable);
288 /* move the current point to wherever the user told it to go, subject
292 cp.move_to (min (x, x_limit), y, ControlPoint::Full);
293 reset_line_coords (cp);
295 /* now move all subsequent control points, to reflect the motion.
298 for (uint32_t i = cp.view_index() + 1; i < limit; ++i) {
299 ControlPoint *p = nth (i);
302 if (p->can_slide()) {
303 new_x = min (p->get_x() + delta, x_limit);
304 p->move_to (new_x, p->get_y(), ControlPoint::Full);
305 reset_line_coords (*p);
312 AutomationLine::reset_line_coords (ControlPoint& cp)
314 if (cp.view_index() < line_points.size()) {
315 line_points[cp.view_index()].set_x (cp.get_x());
316 line_points[cp.view_index()].set_y (cp.get_y());
321 AutomationLine::sync_model_with_view_line (uint32_t start, uint32_t end)
326 update_pending = true;
328 for (uint32_t i = start; i <= end; ++i) {
330 sync_model_with_view_point (*p, false, 0);
335 AutomationLine::model_representation (ControlPoint& cp, ModelRepresentation& mr)
337 /* part one: find out where the visual control point is.
338 initial results are in canvas units. ask the
339 line to convert them to something relevant.
342 mr.xval = (nframes_t) floor (cp.get_x());
343 mr.yval = 1.0 - (cp.get_y() / _height);
345 /* if xval has not changed, set it directly from the model to avoid rounding errors */
347 if (mr.xval == trackview.editor.frame_to_unit((*cp.model())->when)) {
348 mr.xval = (nframes_t) (*cp.model())->when;
350 mr.xval = trackview.editor.unit_to_frame (mr.xval);
353 /* virtual call: this will do the right thing
354 for whatever particular type of line we are.
357 view_to_model_y (mr.yval);
359 /* part 2: find out where the model point is now
362 mr.xpos = (nframes_t) (*cp.model())->when;
363 mr.ypos = (*cp.model())->value;
365 /* part 3: get the position of the visual control
366 points before and after us.
369 ControlPoint* before;
372 if (cp.view_index()) {
373 before = nth (cp.view_index() - 1);
378 after = nth (cp.view_index() + 1);
381 mr.xmin = (nframes_t) (*before->model())->when;
382 mr.ymin = (*before->model())->value;
383 mr.start = before->model();
388 mr.start = cp.model();
392 mr.end = after->model();
402 AutomationLine::determine_visible_control_points (ALPoints& points)
404 uint32_t view_index, pi, n;
405 AutomationList::iterator model;
407 double last_control_point_x = 0.0;
408 double last_control_point_y = 0.0;
409 uint32_t this_rx = 0;
410 uint32_t prev_rx = 0;
411 uint32_t this_ry = 0;
412 uint32_t prev_ry = 0;
417 /* hide all existing points, and the line */
421 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
428 if (points.empty()) {
432 npoints = points.size();
434 /* compute derivative/slope for the entire line */
436 slope = new double[npoints];
438 for (n = 0; n < npoints - 1; ++n) {
439 double xdelta = points[n+1].x - points[n].x;
440 double ydelta = points[n+1].y - points[n].y;
441 slope[n] = ydelta/xdelta;
444 box_size = (uint32_t) control_point_box_size ();
446 /* read all points and decide which ones to show as control points */
450 for (model = alist->begin(), pi = 0; pi < npoints; ++model, ++pi) {
452 double tx = points[pi].x;
453 double ty = points[pi].y;
455 if (isnan (tx) || isnan (ty)) {
456 warning << string_compose (_("Ignoring illegal points on AutomationLine \"%1\""),
461 /* now ensure that the control_points vector reflects the current curve
462 state, but don't plot control points too close together. also, don't
463 plot a series of points all with the same value.
465 always plot the first and last points, of course.
468 if (invalid_point (points, pi)) {
469 /* for some reason, we are supposed to ignore this point,
470 but still keep track of the model index.
475 if (pi > 0 && pi < npoints - 1) {
476 if (slope[pi] == slope[pi-1]) {
478 /* no reason to display this point */
484 /* need to round here. the ultimate coordinates are integer
485 pixels, so tiny deltas in the coords will be eliminated
486 and we end up with "colinear" line segments. since the
487 line rendering code in libart doesn't like this very
488 much, we eliminate them here. don't do this for the first and last
492 this_rx = (uint32_t) rint (tx);
493 this_ry = (uint32_t) rint (ty);
495 if (view_index && pi != npoints && /* not the first, not the last */
496 (((this_rx == prev_rx) && (this_ry == prev_ry)) || /* same point */
497 (((this_rx - prev_rx) < (box_size + 2)) && /* not identical, but still too close horizontally */
498 (abs ((int)(this_ry - prev_ry)) < (int) (box_size + 2))))) { /* too close vertically */
502 /* ok, we should display this point */
504 if (view_index >= cpsize) {
506 /* make sure we have enough control points */
508 ControlPoint* ncp = new ControlPoint (*this);
510 ncp->set_size (box_size);
512 control_points.push_back (ncp);
516 ControlPoint::ShapeType shape;
518 if (!terminal_points_can_slide) {
520 control_points[view_index]->set_can_slide(false);
522 shape = ControlPoint::Start;
524 shape = ControlPoint::Full;
526 } else if (pi == npoints - 1) {
527 control_points[view_index]->set_can_slide(false);
528 shape = ControlPoint::End;
530 control_points[view_index]->set_can_slide(true);
531 shape = ControlPoint::Full;
534 control_points[view_index]->set_can_slide(true);
535 shape = ControlPoint::Full;
538 last_control_point_x = tx;
539 last_control_point_y = ty;
541 control_points[view_index]->reset (tx, ty, model, view_index, shape);
546 /* finally, control visibility */
548 if (_visible && points_visible) {
549 control_points[view_index]->show ();
550 control_points[view_index]->set_visible (true);
552 if (!points_visible) {
553 control_points[view_index]->set_visible (false);
560 /* discard extra CP's to avoid confusing ourselves */
562 while (control_points.size() > view_index) {
563 ControlPoint* cp = control_points.back();
564 control_points.pop_back ();
568 if (!terminal_points_can_slide) {
569 control_points.back()->set_can_slide(false);
574 if (view_index > 1) {
576 npoints = view_index;
578 /* reset the line coordinates */
580 while (line_points.size() < npoints) {
581 line_points.push_back (Art::Point (0,0));
584 while (line_points.size() > npoints) {
585 line_points.pop_back ();
588 for (view_index = 0; view_index < npoints; ++view_index) {
589 line_points[view_index].set_x (control_points[view_index]->get_x());
590 line_points[view_index].set_y (control_points[view_index]->get_y());
593 line->property_points() = line_points;
595 if (_visible && _interpolation != AutomationList::Discrete)
600 set_selected_points (trackview.editor.get_selection().points);
605 AutomationLine::get_verbose_cursor_string (double fraction)
609 if (_vc_uses_gain_mapping) {
610 if (fraction == 0.0) {
611 snprintf (buf, sizeof (buf), "-inf dB");
613 snprintf (buf, sizeof (buf), "%.1fdB", coefficient_to_dB (slider_position_to_gain (fraction)));
616 view_to_model_y(fraction);
617 if (((ARDOUR::Parameter)alist->parameter()).is_integer())
618 snprintf (buf, sizeof (buf), "%d", (int)fraction);
620 snprintf (buf, sizeof (buf), "%.2f", fraction);
627 AutomationLine::invalid_point (ALPoints& p, uint32_t index)
629 return p[index].x == max_frames && p[index].y == DBL_MAX;
633 AutomationLine::invalidate_point (ALPoints& p, uint32_t index)
635 p[index].x = max_frames;
636 p[index].y = DBL_MAX;
640 AutomationLine::start_drag (ControlPoint* cp, nframes_t x, float fraction)
642 if (trackview.editor.current_session() == 0) { /* how? */
649 str = _("automation event move");
651 str = _("automation range drag");
654 trackview.editor.current_session()->begin_reversible_command (str);
655 trackview.editor.current_session()->add_command (new MementoCommand<AutomationList>(*alist.get(), &get_state(), 0));
659 first_drag_fraction = fraction;
660 last_drag_fraction = fraction;
666 AutomationLine::point_drag (ControlPoint& cp, nframes_t x, float fraction, bool with_push)
669 drag_distance += (x - drag_x);
671 drag_distance -= (drag_x - x);
676 modify_view_point (cp, x, fraction, with_push);
678 if (line_points.size() > 1) {
679 line->property_points() = line_points;
683 did_push = with_push;
687 AutomationLine::line_drag (uint32_t i1, uint32_t i2, float fraction, bool with_push)
689 double ydelta = fraction - last_drag_fraction;
691 did_push = with_push;
693 last_drag_fraction = fraction;
700 for (uint32_t i = i1 ; i <= i2; i++) {
702 modify_view_point (*cp, trackview.editor.unit_to_frame (cp->get_x()), ((_height - cp->get_y()) /_height) + ydelta, with_push);
705 if (line_points.size() > 1) {
706 line->property_points() = line_points;
713 AutomationLine::end_drag (ControlPoint* cp)
722 sync_model_with_view_point (*cp, did_push, drag_distance);
724 sync_model_with_view_line (line_drag_cp1, line_drag_cp2);
729 update_pending = false;
731 trackview.editor.current_session()->add_command (new MementoCommand<AutomationList>(*alist.get(), 0, &alist->get_state()));
732 trackview.editor.current_session()->commit_reversible_command ();
733 trackview.editor.current_session()->set_dirty ();
738 AutomationLine::sync_model_with_view_point (ControlPoint& cp, bool did_push, int64_t distance)
740 ModelRepresentation mr;
743 model_representation (cp, mr);
745 /* how much are we changing the central point by */
747 ydelta = mr.yval - mr.ypos;
750 apply the full change to the central point, and interpolate
751 on both axes to cover all model points represented
752 by the control point.
755 /* change all points before the primary point */
757 for (AutomationList::iterator i = mr.start; i != cp.model(); ++i) {
759 double fract = ((*i)->when - mr.xmin) / (mr.xpos - mr.xmin);
760 double y_delta = ydelta * fract;
761 double x_delta = distance * fract;
765 if (y_delta || x_delta) {
766 alist->modify (i, (*i)->when + x_delta, mr.ymin + y_delta);
770 /* change the primary point */
772 update_pending = true;
773 alist->modify (cp.model(), mr.xval, mr.yval);
776 /* change later points */
778 AutomationList::iterator i = cp.model();
782 while (i != mr.end) {
784 double delta = ydelta * (mr.xmax - (*i)->when) / (mr.xmax - mr.xpos);
786 /* all later points move by the same distance along the x-axis as the main point */
789 alist->modify (i, (*i)->when + distance, (*i)->value + delta);
797 /* move all points after the range represented by the view by the same distance
798 as the main point moved.
801 alist->slide (mr.end, drag_distance);
807 AutomationLine::control_points_adjacent (double xval, uint32_t & before, uint32_t& after)
809 ControlPoint *bcp = 0;
810 ControlPoint *acp = 0;
813 /* xval is in frames */
815 unit_xval = trackview.editor.frame_to_unit (xval);
817 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
819 if ((*i)->get_x() <= unit_xval) {
821 if (!bcp || (*i)->get_x() > bcp->get_x()) {
823 before = bcp->view_index();
826 } else if ((*i)->get_x() > unit_xval) {
828 after = acp->view_index();
837 AutomationLine::is_last_point (ControlPoint& cp)
839 ModelRepresentation mr;
841 model_representation (cp, mr);
843 // If the list is not empty, and the point is the last point in the list
845 if (!alist->empty() && mr.end == alist->end()) {
853 AutomationLine::is_first_point (ControlPoint& cp)
855 ModelRepresentation mr;
857 model_representation (cp, mr);
859 // If the list is not empty, and the point is the first point in the list
861 if (!alist->empty() && mr.start == alist->begin()) {
868 // This is copied into AudioRegionGainLine
870 AutomationLine::remove_point (ControlPoint& cp)
872 ModelRepresentation mr;
874 model_representation (cp, mr);
876 trackview.editor.current_session()->begin_reversible_command (_("remove control point"));
877 XMLNode &before = alist->get_state();
879 alist->erase (mr.start, mr.end);
881 trackview.editor.current_session()->add_command(new MementoCommand<AutomationList>(
882 *alist.get(), &before, &alist->get_state()));
883 trackview.editor.current_session()->commit_reversible_command ();
884 trackview.editor.current_session()->set_dirty ();
888 AutomationLine::get_selectables (nframes_t& start, nframes_t& end,
889 double botfrac, double topfrac, list<Selectable*>& results)
896 bool collecting = false;
898 /* Curse X11 and its inverted coordinate system! */
900 bot = (1.0 - topfrac) * _height;
901 top = (1.0 - botfrac) * _height;
906 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
908 nframes_t when = (nframes_t) (*(*i)->model())->when;
910 if (when >= start && when <= end) {
912 if ((*i)->get_y() >= bot && (*i)->get_y() <= top) {
915 (*i)->set_visible(true);
917 nstart = min (nstart, when);
918 nend = max (nend, when);
924 results.push_back (new AutomationSelectable (nstart, nend, botfrac, topfrac, trackview));
934 results.push_back (new AutomationSelectable (nstart, nend, botfrac, topfrac, trackview));
940 AutomationLine::get_inverted_selectables (Selection&, list<Selectable*>& results)
946 AutomationLine::set_selected_points (PointSelection& points)
951 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
952 (*i)->set_selected(false);
955 if (points.empty()) {
959 for (PointSelection::iterator r = points.begin(); r != points.end(); ++r) {
961 if (&(*r).track != &trackview) {
965 /* Curse X11 and its inverted coordinate system! */
967 bot = (1.0 - (*r).high_fract) * _height;
968 top = (1.0 - (*r).low_fract) * _height;
970 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
974 rstart = trackview.editor.frame_to_unit ((*r).start);
975 rend = trackview.editor.frame_to_unit ((*r).end);
977 if ((*i)->get_x() >= rstart && (*i)->get_x() <= rend) {
979 if ((*i)->get_y() >= bot && (*i)->get_y() <= top) {
981 (*i)->set_selected(true);
989 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
990 (*i)->show_color (false, !points_visible);
995 void AutomationLine::set_colors() {
996 set_line_color( ARDOUR_UI::config()->canvasvar_AutomationLine.get() );
997 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
998 (*i)->show_color (false, !points_visible);
1003 AutomationLine::show_selection ()
1005 TimeSelection& time (trackview.editor.get_selection().time);
1007 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1009 (*i)->set_selected(false);
1011 for (list<AudioRange>::iterator r = time.begin(); r != time.end(); ++r) {
1012 double rstart, rend;
1014 rstart = trackview.editor.frame_to_unit ((*r).start);
1015 rend = trackview.editor.frame_to_unit ((*r).end);
1017 if ((*i)->get_x() >= rstart && (*i)->get_x() <= rend) {
1018 (*i)->set_selected(true);
1023 (*i)->show_color (false, !points_visible);
1028 AutomationLine::hide_selection ()
1030 // show_selection ();
1034 AutomationLine::list_changed ()
1040 AutomationLine::reset_callback (const Evoral::ControlList& events)
1042 ALPoints tmp_points;
1043 uint32_t npoints = events.size();
1046 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1049 control_points.clear ();
1054 AutomationList::const_iterator ai;
1056 for (ai = events.begin(); ai != events.end(); ++ai) {
1058 double translated_y = (*ai)->value;
1059 model_to_view_y (translated_y);
1061 add_model_point (tmp_points, (*ai)->when, translated_y);
1064 determine_visible_control_points (tmp_points);
1069 AutomationLine::add_model_point (ALPoints& tmp_points, double frame, double yfract)
1071 tmp_points.push_back (ALPoint (trackview.editor.frame_to_unit (frame),
1072 _height - (yfract * _height)));
1076 AutomationLine::reset ()
1078 update_pending = false;
1084 alist->apply_to_points (*this, &AutomationLine::reset_callback);
1088 AutomationLine::clear ()
1090 /* parent must create command */
1091 XMLNode &before = get_state();
1093 trackview.editor.current_session()->add_command (new MementoCommand<AutomationLine>(*this, &before, &get_state()));
1094 trackview.editor.current_session()->commit_reversible_command ();
1095 trackview.editor.current_session()->set_dirty ();
1099 AutomationLine::change_model (AutomationList::iterator i, double x, double y)
1104 AutomationLine::change_model_range (AutomationList::iterator start, AutomationList::iterator end, double xdelta, float ydelta)
1106 alist->move_range (start, end, xdelta, ydelta);
1110 AutomationLine::set_list(boost::shared_ptr<ARDOUR::AutomationList> list)
1117 AutomationLine::show_all_control_points ()
1119 points_visible = true;
1121 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1122 (*i)->show_color((_interpolation != AutomationList::Discrete), false);
1124 (*i)->set_visible (true);
1129 AutomationLine::hide_all_but_selected_control_points ()
1131 if (alist->interpolation() == AutomationList::Discrete)
1134 points_visible = false;
1136 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1137 if (!(*i)->selected()) {
1138 (*i)->set_visible (false);
1144 AutomationLine::track_entered()
1146 if (alist->interpolation() != AutomationList::Discrete)
1147 show_all_control_points();
1151 AutomationLine::track_exited()
1153 if (alist->interpolation() != AutomationList::Discrete) {
1154 hide_all_but_selected_control_points();
1159 AutomationLine::get_state (void)
1161 /* function as a proxy for the model */
1162 return alist->get_state();
1166 AutomationLine::set_state (const XMLNode &node)
1168 /* function as a proxy for the model */
1169 return alist->set_state (node);
1173 AutomationLine::view_to_model_y (double& y)
1175 /* TODO: This should be more generic ... */
1176 if (alist->parameter().type() == GainAutomation) {
1177 y = slider_position_to_gain (y);
1180 } else if (alist->parameter().type() == PanAutomation) {
1181 // vertical coordinate axis reversal
1183 } else if (alist->parameter().type() == PluginAutomation) {
1184 y = y * (double)(alist->get_max_y()- alist->get_min_y()) + alist->get_min_y();
1186 y = (int)(y * alist->parameter().max());
1191 AutomationLine::model_to_view_y (double& y)
1193 /* TODO: This should be more generic ... */
1194 if (alist->parameter().type() == GainAutomation) {
1195 y = gain_to_slider_position (y);
1196 } else if (alist->parameter().type() == PanAutomation) {
1197 // vertical coordinate axis reversal
1199 } else if (alist->parameter().type() == PluginAutomation) {
1200 y = (y - alist->get_min_y()) / (double)(alist->get_max_y()- alist->get_min_y());
1202 y = y / (double)alist->parameter().max(); /* ... like this */
1208 AutomationLine::set_interpolation(AutomationList::InterpolationStyle style)
1210 _interpolation = style;
1212 if (style == AutomationList::Discrete) {
1213 show_all_control_points();
1216 hide_all_but_selected_control_points();