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_event.h>
30 #include <ardour/curve.h>
31 #include <ardour/dB.h>
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;
74 group = new ArdourCanvas::Group (parent);
75 group->property_x() = 0.0;
76 group->property_y() = 0.0;
78 line = new ArdourCanvas::Line (*group);
79 line->property_width_pixels() = (guint)1;
80 line->set_data ("line", this);
82 line->signal_event().connect (mem_fun (*this, &AutomationLine::event_handler));
84 alist->StateChanged.connect (mem_fun(*this, &AutomationLine::list_changed));
86 trackview.session().register_with_memento_command_factory(alist->id(), this);
88 if (alist->parameter().type() == GainAutomation)
89 set_verbose_cursor_uses_gain_mapping (true);
91 set_interpolation(alist->interpolation());
94 AutomationLine::~AutomationLine ()
96 vector_delete (&control_points);
101 AutomationLine::event_handler (GdkEvent* event)
103 return PublicEditor::instance().canvas_line_event (event, line, this);
107 AutomationLine::queue_reset ()
109 if (!update_pending) {
110 update_pending = true;
111 Gtkmm2ext::UI::instance()->call_slot (mem_fun(*this, &AutomationLine::reset));
116 AutomationLine::show ()
118 if (_interpolation != AutomationList::Discrete)
121 if (points_visible) {
122 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
131 AutomationLine::hide ()
134 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
141 AutomationLine::control_point_box_size ()
143 if (_interpolation == AutomationList::Discrete) {
144 return max((_height*4.0) / (double)(alist->parameter().max() - alist->parameter().min()),
148 if (_height > TimeAxisView::hLarger) {
150 } else if (_height > (guint32) TimeAxisView::hNormal) {
157 AutomationLine::set_y_position_and_height (guint32 y, guint32 h)
159 bool changed = false;
161 if (y != _y_position) {
169 double const bsz = control_point_box_size();
171 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
172 (*i)->set_size (bsz);
183 AutomationLine::set_line_color (uint32_t color)
186 line->property_fill_color_rgba() = color;
190 AutomationLine::set_verbose_cursor_uses_gain_mapping (bool yn)
192 if (yn != _vc_uses_gain_mapping) {
193 _vc_uses_gain_mapping = yn;
199 AutomationLine::nth (uint32_t n)
201 if (n < control_points.size()) {
202 return control_points[n];
209 AutomationLine::modify_view_point (ControlPoint& cp, double x, double y, bool with_push)
212 uint32_t last_movable = UINT_MAX;
213 double x_limit = DBL_MAX;
215 /* this just changes the current view. it does not alter
216 the model in any way at all.
219 /* clamp y-coord appropriately. y is supposed to be a normalized fraction (0.0-1.0),
220 and needs to be converted to a canvas unit distance.
225 y = _y_position + _height - (y * _height);
227 if (cp.can_slide()) {
229 /* x-coord cannot move beyond adjacent points or the start/end, and is
230 already in frames. it needs to be converted to canvas units.
233 x = trackview.editor.frame_to_unit (x);
235 /* clamp x position using view coordinates */
237 ControlPoint *before;
240 if (cp.view_index()) {
241 before = nth (cp.view_index() - 1);
242 x = max (x, before->get_x()+1.0);
249 if (cp.view_index() < control_points.size() - 1) {
251 after = nth (cp.view_index() + 1);
253 /*if it is a "spike" leave the x alone */
255 if (after->get_x() - before->get_x() < 2) {
259 x = min (x, after->get_x()-1.0);
269 /* find the first point that can't move */
271 for (uint32_t n = cp.view_index() + 1; (after = nth (n)) != 0; ++n) {
272 if (!after->can_slide()) {
273 x_limit = after->get_x() - 1.0;
274 last_movable = after->view_index();
279 delta = x - cp.get_x();
284 /* leave the x-coordinate alone */
286 x = trackview.editor.frame_to_unit ((*cp.model())->when);
292 cp.move_to (x, y, ControlPoint::Full);
293 reset_line_coords (cp);
297 uint32_t limit = min (control_points.size(), (size_t)last_movable);
299 /* move the current point to wherever the user told it to go, subject
303 cp.move_to (min (x, x_limit), y, ControlPoint::Full);
304 reset_line_coords (cp);
306 /* now move all subsequent control points, to reflect the motion.
309 for (uint32_t i = cp.view_index() + 1; i < limit; ++i) {
310 ControlPoint *p = nth (i);
313 if (p->can_slide()) {
314 new_x = min (p->get_x() + delta, x_limit);
315 p->move_to (new_x, p->get_y(), ControlPoint::Full);
316 reset_line_coords (*p);
323 AutomationLine::reset_line_coords (ControlPoint& cp)
325 if (cp.view_index() < line_points.size()) {
326 line_points[cp.view_index()].set_x (cp.get_x());
327 line_points[cp.view_index()].set_y (cp.get_y());
332 AutomationLine::sync_model_with_view_line (uint32_t start, uint32_t end)
337 update_pending = true;
339 for (uint32_t i = start; i <= end; ++i) {
341 sync_model_with_view_point (*p, false, 0);
346 AutomationLine::model_representation (ControlPoint& cp, ModelRepresentation& mr)
348 /* part one: find out where the visual control point is.
349 initial results are in canvas units. ask the
350 line to convert them to something relevant.
353 mr.xval = (nframes_t) floor (cp.get_x());
354 mr.yval = 1.0 - ( (cp.get_y() - _y_position) / _height);
356 /* if xval has not changed, set it directly from the model to avoid rounding errors */
358 if (mr.xval == trackview.editor.frame_to_unit((*cp.model())->when)) {
359 mr.xval = (nframes_t) (*cp.model())->when;
361 mr.xval = trackview.editor.unit_to_frame (mr.xval);
364 /* virtual call: this will do the right thing
365 for whatever particular type of line we are.
368 view_to_model_y (mr.yval);
370 /* part 2: find out where the model point is now
373 mr.xpos = (nframes_t) (*cp.model())->when;
374 mr.ypos = (*cp.model())->value;
376 /* part 3: get the position of the visual control
377 points before and after us.
380 ControlPoint* before;
383 if (cp.view_index()) {
384 before = nth (cp.view_index() - 1);
389 after = nth (cp.view_index() + 1);
392 mr.xmin = (nframes_t) (*before->model())->when;
393 mr.ymin = (*before->model())->value;
394 mr.start = before->model();
399 mr.start = cp.model();
403 mr.end = after->model();
413 AutomationLine::determine_visible_control_points (ALPoints& points)
415 uint32_t view_index, pi, n;
416 AutomationList::iterator model;
418 double last_control_point_x = 0.0;
419 double last_control_point_y = 0.0;
420 uint32_t this_rx = 0;
421 uint32_t prev_rx = 0;
422 uint32_t this_ry = 0;
423 uint32_t prev_ry = 0;
428 /* hide all existing points, and the line */
432 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
439 if (points.empty()) {
443 npoints = points.size();
445 /* compute derivative/slope for the entire line */
447 slope = new double[npoints];
449 for (n = 0; n < npoints - 1; ++n) {
450 double xdelta = points[n+1].x - points[n].x;
451 double ydelta = points[n+1].y - points[n].y;
452 slope[n] = ydelta/xdelta;
455 box_size = (uint32_t) control_point_box_size ();
457 /* read all points and decide which ones to show as control points */
461 for (model = alist->begin(), pi = 0; pi < npoints; ++model, ++pi) {
463 double tx = points[pi].x;
464 double ty = points[pi].y;
466 /* now ensure that the control_points vector reflects the current curve
467 state, but don't plot control points too close together. also, don't
468 plot a series of points all with the same value.
470 always plot the first and last points, of course.
473 if (invalid_point (points, pi)) {
474 /* for some reason, we are supposed to ignore this point,
475 but still keep track of the model index.
480 if (pi > 0 && pi < npoints - 1) {
481 if (slope[pi] == slope[pi-1]) {
483 /* no reason to display this point */
489 /* need to round here. the ultimate coordinates are integer
490 pixels, so tiny deltas in the coords will be eliminated
491 and we end up with "colinear" line segments. since the
492 line rendering code in libart doesn't like this very
493 much, we eliminate them here. don't do this for the first and last
497 this_rx = (uint32_t) rint (tx);
498 this_ry = (uint32_t) rint (ty);
500 if (view_index && pi != npoints && /* not the first, not the last */
501 (((this_rx == prev_rx) && (this_ry == prev_ry)) || /* same point */
502 (((this_rx - prev_rx) < (box_size + 2)) && /* not identical, but still too close horizontally */
503 (abs ((int)(this_ry - prev_ry)) < (int) (box_size + 2))))) { /* too close vertically */
507 /* ok, we should display this point */
509 if (view_index >= cpsize) {
511 /* make sure we have enough control points */
513 ControlPoint* ncp = new ControlPoint (*this);
515 ncp->set_size (box_size);
517 control_points.push_back (ncp);
521 ControlPoint::ShapeType shape;
523 if (!terminal_points_can_slide) {
525 control_points[view_index]->set_can_slide(false);
527 shape = ControlPoint::Start;
529 shape = ControlPoint::Full;
531 } else if (pi == npoints - 1) {
532 control_points[view_index]->set_can_slide(false);
533 shape = ControlPoint::End;
535 control_points[view_index]->set_can_slide(true);
536 shape = ControlPoint::Full;
539 control_points[view_index]->set_can_slide(true);
540 shape = ControlPoint::Full;
543 last_control_point_x = tx;
544 last_control_point_y = ty;
546 control_points[view_index]->reset (tx, ty, model, view_index, shape);
551 /* finally, control visibility */
553 if (_visible && points_visible) {
554 control_points[view_index]->show ();
555 control_points[view_index]->set_visible (true);
557 if (!points_visible) {
558 control_points[view_index]->set_visible (false);
565 /* discard extra CP's to avoid confusing ourselves */
567 while (control_points.size() > view_index) {
568 ControlPoint* cp = control_points.back();
569 control_points.pop_back ();
573 if (!terminal_points_can_slide) {
574 control_points.back()->set_can_slide(false);
579 if (view_index > 1) {
581 npoints = view_index;
583 /* reset the line coordinates */
585 while (line_points.size() < npoints) {
586 line_points.push_back (Art::Point (0,0));
589 while (line_points.size() > npoints) {
590 line_points.pop_back ();
593 for (view_index = 0; view_index < npoints; ++view_index) {
594 line_points[view_index].set_x (control_points[view_index]->get_x());
595 line_points[view_index].set_y (control_points[view_index]->get_y());
598 line->property_points() = line_points;
600 if (_visible && _interpolation != AutomationList::Discrete)
605 set_selected_points (trackview.editor.get_selection().points);
610 AutomationLine::get_verbose_cursor_string (double fraction)
614 if (_vc_uses_gain_mapping) {
615 if (fraction == 0.0) {
616 snprintf (buf, sizeof (buf), "-inf dB");
618 snprintf (buf, sizeof (buf), "%.1fdB", coefficient_to_dB (slider_position_to_gain (fraction)));
621 view_to_model_y(fraction);
622 if (alist->parameter().type() == MidiCCAutomation)
623 snprintf (buf, sizeof (buf), "%d", (int)fraction);
625 snprintf (buf, sizeof (buf), "%.2f", fraction);
632 AutomationLine::invalid_point (ALPoints& p, uint32_t index)
634 return p[index].x == max_frames && p[index].y == DBL_MAX;
638 AutomationLine::invalidate_point (ALPoints& p, uint32_t index)
640 p[index].x = max_frames;
641 p[index].y = DBL_MAX;
645 AutomationLine::start_drag (ControlPoint* cp, nframes_t x, float fraction)
647 if (trackview.editor.current_session() == 0) { /* how? */
654 str = _("automation event move");
656 str = _("automation range drag");
659 trackview.editor.current_session()->begin_reversible_command (str);
660 trackview.editor.current_session()->add_command (new MementoCommand<AutomationList>(*alist.get(), &get_state(), 0));
664 first_drag_fraction = fraction;
665 last_drag_fraction = fraction;
671 AutomationLine::point_drag (ControlPoint& cp, nframes_t x, float fraction, bool with_push)
674 drag_distance += (x - drag_x);
676 drag_distance -= (drag_x - x);
681 modify_view_point (cp, x, fraction, with_push);
683 if (line_points.size() > 1) {
684 line->property_points() = line_points;
688 did_push = with_push;
692 AutomationLine::line_drag (uint32_t i1, uint32_t i2, float fraction, bool with_push)
694 double const ydelta = fraction - last_drag_fraction;
696 did_push = with_push;
698 last_drag_fraction = fraction;
705 for (uint32_t i = i1 ; i <= i2; i++) {
707 modify_view_point (*cp, trackview.editor.unit_to_frame (cp->get_x()), ((_height - cp->get_y() + _y_position) /_height) + ydelta, with_push);
710 if (line_points.size() > 1) {
711 line->property_points() = line_points;
718 AutomationLine::end_drag (ControlPoint* cp)
727 sync_model_with_view_point (*cp, did_push, drag_distance);
729 sync_model_with_view_line (line_drag_cp1, line_drag_cp2);
734 update_pending = false;
736 trackview.editor.current_session()->add_command (new MementoCommand<AutomationList>(*alist.get(), 0, &alist->get_state()));
737 trackview.editor.current_session()->commit_reversible_command ();
738 trackview.editor.current_session()->set_dirty ();
743 AutomationLine::sync_model_with_view_point (ControlPoint& cp, bool did_push, int64_t distance)
745 ModelRepresentation mr;
748 model_representation (cp, mr);
750 /* how much are we changing the central point by */
752 ydelta = mr.yval - mr.ypos;
755 apply the full change to the central point, and interpolate
756 on both axes to cover all model points represented
757 by the control point.
760 /* change all points before the primary point */
762 for (AutomationList::iterator i = mr.start; i != cp.model(); ++i) {
764 double fract = ((*i)->when - mr.xmin) / (mr.xpos - mr.xmin);
765 double y_delta = ydelta * fract;
766 double x_delta = distance * fract;
770 if (y_delta || x_delta) {
771 alist->modify (i, (*i)->when + x_delta, mr.ymin + y_delta);
775 /* change the primary point */
777 update_pending = true;
778 alist->modify (cp.model(), mr.xval, mr.yval);
781 /* change later points */
783 AutomationList::iterator i = cp.model();
787 while (i != mr.end) {
789 double delta = ydelta * (mr.xmax - (*i)->when) / (mr.xmax - mr.xpos);
791 /* all later points move by the same distance along the x-axis as the main point */
794 alist->modify (i, (*i)->when + distance, (*i)->value + delta);
802 /* move all points after the range represented by the view by the same distance
803 as the main point moved.
806 alist->slide (mr.end, drag_distance);
812 AutomationLine::control_points_adjacent (double xval, uint32_t & before, uint32_t& after)
814 ControlPoint *bcp = 0;
815 ControlPoint *acp = 0;
818 /* xval is in frames */
820 unit_xval = trackview.editor.frame_to_unit (xval);
822 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
824 if ((*i)->get_x() <= unit_xval) {
826 if (!bcp || (*i)->get_x() > bcp->get_x()) {
828 before = bcp->view_index();
831 } else if ((*i)->get_x() > unit_xval) {
833 after = acp->view_index();
842 AutomationLine::is_last_point (ControlPoint& cp)
844 ModelRepresentation mr;
846 model_representation (cp, mr);
848 // If the list is not empty, and the point is the last point in the list
850 if (!alist->empty() && mr.end == alist->end()) {
858 AutomationLine::is_first_point (ControlPoint& cp)
860 ModelRepresentation mr;
862 model_representation (cp, mr);
864 // If the list is not empty, and the point is the first point in the list
866 if (!alist->empty() && mr.start == alist->begin()) {
873 // This is copied into AudioRegionGainLine
875 AutomationLine::remove_point (ControlPoint& cp)
877 ModelRepresentation mr;
879 model_representation (cp, mr);
881 trackview.editor.current_session()->begin_reversible_command (_("remove control point"));
882 XMLNode &before = alist->get_state();
884 alist->erase (mr.start, mr.end);
886 trackview.editor.current_session()->add_command(new MementoCommand<AutomationList>(
887 *alist.get(), &before, &alist->get_state()));
888 trackview.editor.current_session()->commit_reversible_command ();
889 trackview.editor.current_session()->set_dirty ();
893 AutomationLine::get_selectables (nframes_t& start, nframes_t& end,
894 double botfrac, double topfrac, list<Selectable*>& results)
901 bool collecting = false;
903 /* Curse X11 and its inverted coordinate system! */
905 bot = _y_position + (1.0 - topfrac) * _height;
906 top = _y_position + (1.0 - botfrac) * _height;
911 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
913 nframes_t when = (nframes_t) (*(*i)->model())->when;
915 if (when >= start && when <= end) {
917 if ((*i)->get_y() >= bot && (*i)->get_y() <= top) {
920 (*i)->set_visible(true);
922 nstart = min (nstart, when);
923 nend = max (nend, when);
929 results.push_back (new AutomationSelectable (nstart, nend, botfrac, topfrac, trackview));
939 results.push_back (new AutomationSelectable (nstart, nend, botfrac, topfrac, trackview));
945 AutomationLine::get_inverted_selectables (Selection&, list<Selectable*>& results)
951 AutomationLine::set_selected_points (PointSelection& points)
956 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
957 (*i)->set_selected(false);
960 if (points.empty()) {
964 for (PointSelection::iterator r = points.begin(); r != points.end(); ++r) {
966 if (&(*r).track != &trackview) {
970 /* Curse X11 and its inverted coordinate system! */
972 bot = _y_position + (1.0 - (*r).high_fract) * _height;
973 top = _y_position + (1.0 - (*r).low_fract) * _height;
975 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
979 rstart = trackview.editor.frame_to_unit ((*r).start);
980 rend = trackview.editor.frame_to_unit ((*r).end);
982 if ((*i)->get_x() >= rstart && (*i)->get_x() <= rend) {
984 if ((*i)->get_y() >= bot && (*i)->get_y() <= top) {
986 (*i)->set_selected(true);
994 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
995 (*i)->show_color (false, !points_visible);
1000 void AutomationLine::set_colors() {
1001 set_line_color( ARDOUR_UI::config()->canvasvar_AutomationLine.get() );
1002 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1003 (*i)->show_color (false, !points_visible);
1008 AutomationLine::show_selection ()
1010 TimeSelection& time (trackview.editor.get_selection().time);
1012 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1014 (*i)->set_selected(false);
1016 for (list<AudioRange>::iterator r = time.begin(); r != time.end(); ++r) {
1017 double rstart, rend;
1019 rstart = trackview.editor.frame_to_unit ((*r).start);
1020 rend = trackview.editor.frame_to_unit ((*r).end);
1022 if ((*i)->get_x() >= rstart && (*i)->get_x() <= rend) {
1023 (*i)->set_selected(true);
1028 (*i)->show_color (false, !points_visible);
1033 AutomationLine::hide_selection ()
1035 // show_selection ();
1039 AutomationLine::list_changed ()
1045 AutomationLine::reset_callback (const AutomationList& events)
1047 ALPoints tmp_points;
1048 uint32_t npoints = events.size();
1051 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1054 control_points.clear ();
1059 AutomationList::const_iterator ai;
1061 for (ai = events.const_begin(); ai != events.const_end(); ++ai) {
1063 double translated_y = (*ai)->value;
1064 model_to_view_y (translated_y);
1066 tmp_points.push_back (ALPoint (trackview.editor.frame_to_unit ((*ai)->when),
1067 _y_position + _height - (translated_y * _height)));
1070 determine_visible_control_points (tmp_points);
1074 AutomationLine::reset ()
1076 update_pending = false;
1082 alist->apply_to_points (*this, &AutomationLine::reset_callback);
1086 AutomationLine::clear ()
1088 /* parent must create command */
1089 XMLNode &before = get_state();
1091 trackview.editor.current_session()->add_command (new MementoCommand<AutomationLine>(*this, &before, &get_state()));
1092 trackview.editor.current_session()->commit_reversible_command ();
1093 trackview.editor.current_session()->set_dirty ();
1097 AutomationLine::change_model (AutomationList::iterator i, double x, double y)
1102 AutomationLine::change_model_range (AutomationList::iterator start, AutomationList::iterator end, double xdelta, float ydelta)
1104 alist->move_range (start, end, xdelta, ydelta);
1108 AutomationLine::show_all_control_points ()
1110 points_visible = true;
1112 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1113 (*i)->show_color((_interpolation != AutomationList::Discrete), false);
1115 (*i)->set_visible (true);
1120 AutomationLine::hide_all_but_selected_control_points ()
1122 if (alist->interpolation() == AutomationList::Discrete)
1125 points_visible = false;
1127 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1128 if (!(*i)->selected()) {
1129 (*i)->set_visible (false);
1135 AutomationLine::track_entered()
1137 if (alist->interpolation() != AutomationList::Discrete)
1138 show_all_control_points();
1142 AutomationLine::track_exited()
1144 if (alist->interpolation() != AutomationList::Discrete) {
1145 hide_all_but_selected_control_points();
1150 AutomationLine::get_state (void)
1152 /* function as a proxy for the model */
1153 return alist->get_state();
1157 AutomationLine::set_state (const XMLNode &node)
1159 /* function as a proxy for the model */
1160 return alist->set_state (node);
1164 AutomationLine::view_to_model_y (double& y)
1166 if (alist->parameter().type() == GainAutomation) {
1167 y = slider_position_to_gain (y);
1170 } else if (alist->parameter().type() == PanAutomation) {
1171 // vertical coordinate axis reversal
1173 } else if (alist->parameter().type() == MidiCCAutomation) {
1174 y = (int)(y * 127.0);
1175 } else if (alist->parameter().type() == PluginAutomation) {
1176 y = y * (double)(alist->get_max_y()- alist->get_min_y()) + alist->get_min_y();
1181 AutomationLine::model_to_view_y (double& y)
1183 if (alist->parameter().type() == GainAutomation) {
1184 y = gain_to_slider_position (y);
1185 } else if (alist->parameter().type() == PanAutomation) {
1186 // vertical coordinate axis reversal
1188 } else if (alist->parameter().type() == MidiCCAutomation) {
1190 } else if (alist->parameter().type() == PluginAutomation) {
1191 y = (y - alist->get_min_y()) / (double)(alist->get_max_y()- alist->get_min_y());
1197 AutomationLine::set_interpolation(AutomationList::InterpolationStyle style)
1199 _interpolation = style;
1201 if (style == AutomationList::Discrete) {
1202 show_all_control_points();
1205 hide_all_but_selected_control_points();