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 if (isnan (tx) || isnan (ty)) {
467 warning << string_compose (_("Ignoring illegal points on AutomationLine \"%1\""),
472 /* now ensure that the control_points vector reflects the current curve
473 state, but don't plot control points too close together. also, don't
474 plot a series of points all with the same value.
476 always plot the first and last points, of course.
479 if (invalid_point (points, pi)) {
480 /* for some reason, we are supposed to ignore this point,
481 but still keep track of the model index.
486 if (pi > 0 && pi < npoints - 1) {
487 if (slope[pi] == slope[pi-1]) {
489 /* no reason to display this point */
495 /* need to round here. the ultimate coordinates are integer
496 pixels, so tiny deltas in the coords will be eliminated
497 and we end up with "colinear" line segments. since the
498 line rendering code in libart doesn't like this very
499 much, we eliminate them here. don't do this for the first and last
503 this_rx = (uint32_t) rint (tx);
504 this_ry = (uint32_t) rint (ty);
506 if (view_index && pi != npoints && /* not the first, not the last */
507 (((this_rx == prev_rx) && (this_ry == prev_ry)) || /* same point */
508 (((this_rx - prev_rx) < (box_size + 2)) && /* not identical, but still too close horizontally */
509 (abs ((int)(this_ry - prev_ry)) < (int) (box_size + 2))))) { /* too close vertically */
513 /* ok, we should display this point */
515 if (view_index >= cpsize) {
517 /* make sure we have enough control points */
519 ControlPoint* ncp = new ControlPoint (*this);
521 ncp->set_size (box_size);
523 control_points.push_back (ncp);
527 ControlPoint::ShapeType shape;
529 if (!terminal_points_can_slide) {
531 control_points[view_index]->set_can_slide(false);
533 shape = ControlPoint::Start;
535 shape = ControlPoint::Full;
537 } else if (pi == npoints - 1) {
538 control_points[view_index]->set_can_slide(false);
539 shape = ControlPoint::End;
541 control_points[view_index]->set_can_slide(true);
542 shape = ControlPoint::Full;
545 control_points[view_index]->set_can_slide(true);
546 shape = ControlPoint::Full;
549 last_control_point_x = tx;
550 last_control_point_y = ty;
552 control_points[view_index]->reset (tx, ty, model, view_index, shape);
557 /* finally, control visibility */
559 if (_visible && points_visible) {
560 control_points[view_index]->show ();
561 control_points[view_index]->set_visible (true);
563 if (!points_visible) {
564 control_points[view_index]->set_visible (false);
571 /* discard extra CP's to avoid confusing ourselves */
573 while (control_points.size() > view_index) {
574 ControlPoint* cp = control_points.back();
575 control_points.pop_back ();
579 if (!terminal_points_can_slide) {
580 control_points.back()->set_can_slide(false);
585 if (view_index > 1) {
587 npoints = view_index;
589 /* reset the line coordinates */
591 while (line_points.size() < npoints) {
592 line_points.push_back (Art::Point (0,0));
595 while (line_points.size() > npoints) {
596 line_points.pop_back ();
599 for (view_index = 0; view_index < npoints; ++view_index) {
600 line_points[view_index].set_x (control_points[view_index]->get_x());
601 line_points[view_index].set_y (control_points[view_index]->get_y());
604 line->property_points() = line_points;
606 if (_visible && _interpolation != AutomationList::Discrete)
611 set_selected_points (trackview.editor.get_selection().points);
616 AutomationLine::get_verbose_cursor_string (double fraction)
620 if (_vc_uses_gain_mapping) {
621 if (fraction == 0.0) {
622 snprintf (buf, sizeof (buf), "-inf dB");
624 snprintf (buf, sizeof (buf), "%.1fdB", coefficient_to_dB (slider_position_to_gain (fraction)));
627 view_to_model_y(fraction);
628 if (alist->parameter().type() == MidiCCAutomation)
629 snprintf (buf, sizeof (buf), "%d", (int)fraction);
631 snprintf (buf, sizeof (buf), "%.2f", fraction);
638 AutomationLine::invalid_point (ALPoints& p, uint32_t index)
640 return p[index].x == max_frames && p[index].y == DBL_MAX;
644 AutomationLine::invalidate_point (ALPoints& p, uint32_t index)
646 p[index].x = max_frames;
647 p[index].y = DBL_MAX;
651 AutomationLine::start_drag (ControlPoint* cp, nframes_t x, float fraction)
653 if (trackview.editor.current_session() == 0) { /* how? */
660 str = _("automation event move");
662 str = _("automation range drag");
665 trackview.editor.current_session()->begin_reversible_command (str);
666 trackview.editor.current_session()->add_command (new MementoCommand<AutomationList>(*alist.get(), &get_state(), 0));
670 first_drag_fraction = fraction;
671 last_drag_fraction = fraction;
677 AutomationLine::point_drag (ControlPoint& cp, nframes_t x, float fraction, bool with_push)
680 drag_distance += (x - drag_x);
682 drag_distance -= (drag_x - x);
687 modify_view_point (cp, x, fraction, with_push);
689 if (line_points.size() > 1) {
690 line->property_points() = line_points;
694 did_push = with_push;
698 AutomationLine::line_drag (uint32_t i1, uint32_t i2, float fraction, bool with_push)
700 double const ydelta = fraction - last_drag_fraction;
702 did_push = with_push;
704 last_drag_fraction = fraction;
711 for (uint32_t i = i1 ; i <= i2; i++) {
713 modify_view_point (*cp, trackview.editor.unit_to_frame (cp->get_x()), ((_height - cp->get_y() + _y_position) /_height) + ydelta, with_push);
716 if (line_points.size() > 1) {
717 line->property_points() = line_points;
724 AutomationLine::end_drag (ControlPoint* cp)
733 sync_model_with_view_point (*cp, did_push, drag_distance);
735 sync_model_with_view_line (line_drag_cp1, line_drag_cp2);
740 update_pending = false;
742 trackview.editor.current_session()->add_command (new MementoCommand<AutomationList>(*alist.get(), 0, &alist->get_state()));
743 trackview.editor.current_session()->commit_reversible_command ();
744 trackview.editor.current_session()->set_dirty ();
749 AutomationLine::sync_model_with_view_point (ControlPoint& cp, bool did_push, int64_t distance)
751 ModelRepresentation mr;
754 model_representation (cp, mr);
756 /* how much are we changing the central point by */
758 ydelta = mr.yval - mr.ypos;
761 apply the full change to the central point, and interpolate
762 on both axes to cover all model points represented
763 by the control point.
766 /* change all points before the primary point */
768 for (AutomationList::iterator i = mr.start; i != cp.model(); ++i) {
770 double fract = ((*i)->when - mr.xmin) / (mr.xpos - mr.xmin);
771 double y_delta = ydelta * fract;
772 double x_delta = distance * fract;
776 if (y_delta || x_delta) {
777 alist->modify (i, (*i)->when + x_delta, mr.ymin + y_delta);
781 /* change the primary point */
783 update_pending = true;
784 alist->modify (cp.model(), mr.xval, mr.yval);
787 /* change later points */
789 AutomationList::iterator i = cp.model();
793 while (i != mr.end) {
795 double delta = ydelta * (mr.xmax - (*i)->when) / (mr.xmax - mr.xpos);
797 /* all later points move by the same distance along the x-axis as the main point */
800 alist->modify (i, (*i)->when + distance, (*i)->value + delta);
808 /* move all points after the range represented by the view by the same distance
809 as the main point moved.
812 alist->slide (mr.end, drag_distance);
818 AutomationLine::control_points_adjacent (double xval, uint32_t & before, uint32_t& after)
820 ControlPoint *bcp = 0;
821 ControlPoint *acp = 0;
824 /* xval is in frames */
826 unit_xval = trackview.editor.frame_to_unit (xval);
828 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
830 if ((*i)->get_x() <= unit_xval) {
832 if (!bcp || (*i)->get_x() > bcp->get_x()) {
834 before = bcp->view_index();
837 } else if ((*i)->get_x() > unit_xval) {
839 after = acp->view_index();
848 AutomationLine::is_last_point (ControlPoint& cp)
850 ModelRepresentation mr;
852 model_representation (cp, mr);
854 // If the list is not empty, and the point is the last point in the list
856 if (!alist->empty() && mr.end == alist->end()) {
864 AutomationLine::is_first_point (ControlPoint& cp)
866 ModelRepresentation mr;
868 model_representation (cp, mr);
870 // If the list is not empty, and the point is the first point in the list
872 if (!alist->empty() && mr.start == alist->begin()) {
879 // This is copied into AudioRegionGainLine
881 AutomationLine::remove_point (ControlPoint& cp)
883 ModelRepresentation mr;
885 model_representation (cp, mr);
887 trackview.editor.current_session()->begin_reversible_command (_("remove control point"));
888 XMLNode &before = alist->get_state();
890 alist->erase (mr.start, mr.end);
892 trackview.editor.current_session()->add_command(new MementoCommand<AutomationList>(
893 *alist.get(), &before, &alist->get_state()));
894 trackview.editor.current_session()->commit_reversible_command ();
895 trackview.editor.current_session()->set_dirty ();
899 AutomationLine::get_selectables (nframes_t& start, nframes_t& end,
900 double botfrac, double topfrac, list<Selectable*>& results)
907 bool collecting = false;
909 /* Curse X11 and its inverted coordinate system! */
911 bot = _y_position + (1.0 - topfrac) * _height;
912 top = _y_position + (1.0 - botfrac) * _height;
917 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
919 nframes_t when = (nframes_t) (*(*i)->model())->when;
921 if (when >= start && when <= end) {
923 if ((*i)->get_y() >= bot && (*i)->get_y() <= top) {
926 (*i)->set_visible(true);
928 nstart = min (nstart, when);
929 nend = max (nend, when);
935 results.push_back (new AutomationSelectable (nstart, nend, botfrac, topfrac, trackview));
945 results.push_back (new AutomationSelectable (nstart, nend, botfrac, topfrac, trackview));
951 AutomationLine::get_inverted_selectables (Selection&, list<Selectable*>& results)
957 AutomationLine::set_selected_points (PointSelection& points)
962 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
963 (*i)->set_selected(false);
966 if (points.empty()) {
970 for (PointSelection::iterator r = points.begin(); r != points.end(); ++r) {
972 if (&(*r).track != &trackview) {
976 /* Curse X11 and its inverted coordinate system! */
978 bot = _y_position + (1.0 - (*r).high_fract) * _height;
979 top = _y_position + (1.0 - (*r).low_fract) * _height;
981 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
985 rstart = trackview.editor.frame_to_unit ((*r).start);
986 rend = trackview.editor.frame_to_unit ((*r).end);
988 if ((*i)->get_x() >= rstart && (*i)->get_x() <= rend) {
990 if ((*i)->get_y() >= bot && (*i)->get_y() <= top) {
992 (*i)->set_selected(true);
1000 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1001 (*i)->show_color (false, !points_visible);
1006 void AutomationLine::set_colors() {
1007 set_line_color( ARDOUR_UI::config()->canvasvar_AutomationLine.get() );
1008 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1009 (*i)->show_color (false, !points_visible);
1014 AutomationLine::show_selection ()
1016 TimeSelection& time (trackview.editor.get_selection().time);
1018 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1020 (*i)->set_selected(false);
1022 for (list<AudioRange>::iterator r = time.begin(); r != time.end(); ++r) {
1023 double rstart, rend;
1025 rstart = trackview.editor.frame_to_unit ((*r).start);
1026 rend = trackview.editor.frame_to_unit ((*r).end);
1028 if ((*i)->get_x() >= rstart && (*i)->get_x() <= rend) {
1029 (*i)->set_selected(true);
1034 (*i)->show_color (false, !points_visible);
1039 AutomationLine::hide_selection ()
1041 // show_selection ();
1045 AutomationLine::list_changed ()
1051 AutomationLine::reset_callback (const AutomationList& events)
1053 ALPoints tmp_points;
1054 uint32_t npoints = events.size();
1057 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1060 control_points.clear ();
1065 AutomationList::const_iterator ai;
1067 for (ai = events.const_begin(); ai != events.const_end(); ++ai) {
1069 double translated_y = (*ai)->value;
1070 model_to_view_y (translated_y);
1072 tmp_points.push_back (ALPoint (trackview.editor.frame_to_unit ((*ai)->when),
1073 _y_position + _height - (translated_y * _height)));
1076 determine_visible_control_points (tmp_points);
1080 AutomationLine::reset ()
1082 update_pending = false;
1088 alist->apply_to_points (*this, &AutomationLine::reset_callback);
1092 AutomationLine::clear ()
1094 /* parent must create command */
1095 XMLNode &before = get_state();
1097 trackview.editor.current_session()->add_command (new MementoCommand<AutomationLine>(*this, &before, &get_state()));
1098 trackview.editor.current_session()->commit_reversible_command ();
1099 trackview.editor.current_session()->set_dirty ();
1103 AutomationLine::change_model (AutomationList::iterator i, double x, double y)
1108 AutomationLine::change_model_range (AutomationList::iterator start, AutomationList::iterator end, double xdelta, float ydelta)
1110 alist->move_range (start, end, xdelta, ydelta);
1114 AutomationLine::show_all_control_points ()
1116 points_visible = true;
1118 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1119 (*i)->show_color((_interpolation != AutomationList::Discrete), false);
1121 (*i)->set_visible (true);
1126 AutomationLine::hide_all_but_selected_control_points ()
1128 if (alist->interpolation() == AutomationList::Discrete)
1131 points_visible = false;
1133 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1134 if (!(*i)->selected()) {
1135 (*i)->set_visible (false);
1141 AutomationLine::track_entered()
1143 if (alist->interpolation() != AutomationList::Discrete)
1144 show_all_control_points();
1148 AutomationLine::track_exited()
1150 if (alist->interpolation() != AutomationList::Discrete) {
1151 hide_all_but_selected_control_points();
1156 AutomationLine::get_state (void)
1158 /* function as a proxy for the model */
1159 return alist->get_state();
1163 AutomationLine::set_state (const XMLNode &node)
1165 /* function as a proxy for the model */
1166 return alist->set_state (node);
1170 AutomationLine::view_to_model_y (double& y)
1172 if (alist->parameter().type() == GainAutomation) {
1173 y = slider_position_to_gain (y);
1176 } else if (alist->parameter().type() == PanAutomation) {
1177 // vertical coordinate axis reversal
1179 } else if (alist->parameter().type() == MidiCCAutomation) {
1180 y = (int)(y * 127.0);
1181 } else if (alist->parameter().type() == PluginAutomation) {
1182 y = y * (double)(alist->get_max_y()- alist->get_min_y()) + alist->get_min_y();
1187 AutomationLine::model_to_view_y (double& y)
1189 if (alist->parameter().type() == GainAutomation) {
1190 y = gain_to_slider_position (y);
1191 } else if (alist->parameter().type() == PanAutomation) {
1192 // vertical coordinate axis reversal
1194 } else if (alist->parameter().type() == MidiCCAutomation) {
1196 } else if (alist->parameter().type() == PluginAutomation) {
1197 y = (y - alist->get_min_y()) / (double)(alist->get_max_y()- alist->get_min_y());
1203 AutomationLine::set_interpolation(AutomationList::InterpolationStyle style)
1205 _interpolation = style;
1207 if (style == AutomationList::Discrete) {
1208 show_all_control_points();
1211 hide_all_but_selected_control_points();