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.
26 #include <pbd/stl_delete.h>
27 #include <pbd/memento_command.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 "rgb_macros.h"
36 #include "ardour_ui.h"
37 #include "public_editor.h"
39 #include "selection.h"
40 #include "time_axis_view.h"
41 #include "point_selection.h"
42 #include "automation_selectable.h"
43 #include "automation_time_axis.h"
44 #include "public_editor.h"
46 #include <ardour/session.h>
52 using namespace ARDOUR;
54 using namespace Editing;
55 using namespace Gnome; // for Canvas
57 ControlPoint::ControlPoint (AutomationLine& al)
60 model = al.the_list().end();
69 item = new Canvas::SimpleRect (line.canvas_group());
70 item->property_draw() = true;
71 item->property_fill() = false;
72 item->property_fill_color_rgba() = color_map[cControlPointFill];
73 item->property_outline_color_rgba() = color_map[cControlPointOutline];
74 item->property_outline_pixels() = 1;
75 item->set_data ("control_point", this);
76 item->signal_event().connect (mem_fun (this, &ControlPoint::event_handler));
82 ControlPoint::ControlPoint (const ControlPoint& other, bool dummy_arg_to_force_special_copy_constructor)
90 view_index = other.view_index;
91 can_slide = other.can_slide;
94 _shape = other._shape;
98 item = new Canvas::SimpleRect (line.canvas_group());
99 item->property_fill() = false;
100 item->property_outline_color_rgba() = color_map[cEnteredControlPointOutline];
101 item->property_outline_pixels() = 1;
103 /* NOTE: no event handling in copied ControlPoints */
109 ControlPoint::~ControlPoint ()
115 ControlPoint::event_handler (GdkEvent* event)
117 return PublicEditor::instance().canvas_control_point_event (event, item, this);
121 ControlPoint::hide ()
133 ControlPoint::set_visible (bool yn)
135 item->property_draw() = (gboolean) yn;
139 ControlPoint::reset (double x, double y, AutomationList::iterator mi, uint32_t vi, ShapeType shape)
143 move_to (x, y, shape);
147 ControlPoint::show_color (bool entered, bool hide_too)
151 item->property_outline_color_rgba() = color_map[cEnteredControlPointSelected];
154 item->property_outline_color_rgba() = color_map[cEnteredControlPoint];
162 item->property_outline_color_rgba() = color_map[cControlPointSelected];
165 item->property_outline_color_rgba() = color_map[cControlPoint];
174 ControlPoint::set_size (double sz)
180 item->property_fill() = (gboolean) TRUE;
182 item->property_fill() = (gboolean) FALSE;
186 move_to (_x, _y, _shape);
190 ControlPoint::move_to (double x, double y, ShapeType shape)
194 double half_size = rint(_size/2.0);
211 item->property_x1() = x1;
212 item->property_x2() = x2;
213 item->property_y1() = y - half_size;
214 item->property_y2() = y + half_size;
223 AutomationLine::AutomationLine (const string & name, TimeAxisView& tv, ArdourCanvas::Group& parent, AutomationList& al)
227 _parent_group (parent)
229 points_visible = false;
230 update_pending = false;
231 _vc_uses_gain_mapping = false;
234 terminal_points_can_slide = true;
237 group = new ArdourCanvas::Group (parent);
238 group->property_x() = 0.0;
239 group->property_y() = 0.0;
241 line = new ArdourCanvas::Line (*group);
242 line->property_width_pixels() = (guint)1;
243 line->set_data ("line", this);
245 line->signal_event().connect (mem_fun (*this, &AutomationLine::event_handler));
247 alist.StateChanged.connect (mem_fun(*this, &AutomationLine::list_changed));
249 trackview.session().register_with_memento_command_factory(alist.id(), this);
253 AutomationLine::~AutomationLine ()
255 vector_delete (&control_points);
260 AutomationLine::event_handler (GdkEvent* event)
262 return PublicEditor::instance().canvas_line_event (event, line, this);
266 AutomationLine::queue_reset ()
268 if (!update_pending) {
269 update_pending = true;
270 Gtkmm2ext::UI::instance()->call_slot (mem_fun(*this, &AutomationLine::reset));
275 AutomationLine::show ()
279 if (points_visible) {
280 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
289 AutomationLine::hide ()
292 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
299 AutomationLine::control_point_box_size ()
301 if (_height > TimeAxisView::hLarger) {
303 } else if (_height > (guint32) TimeAxisView::hNormal) {
310 AutomationLine::set_height (guint32 h)
315 double bsz = control_point_box_size();
317 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
318 (*i)->set_size (bsz);
326 AutomationLine::set_line_color (uint32_t color)
329 line->property_fill_color_rgba() = color;
333 AutomationLine::set_verbose_cursor_uses_gain_mapping (bool yn)
335 if (yn != _vc_uses_gain_mapping) {
336 _vc_uses_gain_mapping = yn;
342 AutomationLine::nth (uint32_t n)
344 if (n < control_points.size()) {
345 return control_points[n];
352 AutomationLine::modify_view_point (ControlPoint& cp, double x, double y, bool with_push)
355 uint32_t last_movable = UINT_MAX;
356 double x_limit = DBL_MAX;
358 /* this just changes the current view. it does not alter
359 the model in any way at all.
362 /* clamp y-coord appropriately. y is supposed to be a normalized fraction (0.0-1.0),
363 and needs to be converted to a canvas unit distance.
368 y = _height - (y * _height);
372 /* x-coord cannot move beyond adjacent points or the start/end, and is
373 already in frames. it needs to be converted to canvas units.
376 x = trackview.editor.frame_to_unit (x);
378 /* clamp x position using view coordinates */
380 ControlPoint *before;
384 before = nth (cp.view_index - 1);
385 x = max (x, before->get_x()+1.0);
392 if (cp.view_index < control_points.size() - 1) {
394 after = nth (cp.view_index + 1);
396 /*if it is a "spike" leave the x alone */
398 if (after->get_x() - before->get_x() < 2) {
402 x = min (x, after->get_x()-1.0);
412 /* find the first point that can't move */
414 for (uint32_t n = cp.view_index + 1; (after = nth (n)) != 0; ++n) {
415 if (!after->can_slide) {
416 x_limit = after->get_x() - 1.0;
417 last_movable = after->view_index;
422 delta = x - cp.get_x();
427 /* leave the x-coordinate alone */
429 x = trackview.editor.frame_to_unit ((*cp.model)->when);
435 cp.move_to (x, y, ControlPoint::Full);
436 reset_line_coords (cp);
440 uint32_t limit = min (control_points.size(), (size_t)last_movable);
442 /* move the current point to wherever the user told it to go, subject
446 cp.move_to (min (x, x_limit), y, ControlPoint::Full);
447 reset_line_coords (cp);
449 /* now move all subsequent control points, to reflect the motion.
452 for (uint32_t i = cp.view_index + 1; i < limit; ++i) {
453 ControlPoint *p = nth (i);
457 new_x = min (p->get_x() + delta, x_limit);
458 p->move_to (new_x, p->get_y(), ControlPoint::Full);
459 reset_line_coords (*p);
466 AutomationLine::reset_line_coords (ControlPoint& cp)
468 line_points[cp.view_index].set_x (cp.get_x());
469 line_points[cp.view_index].set_y (cp.get_y());
473 AutomationLine::sync_model_with_view_line (uint32_t start, uint32_t end)
478 update_pending = true;
480 for (uint32_t i = start; i <= end; ++i) {
482 sync_model_with_view_point (*p, false, 0);
487 AutomationLine::model_representation (ControlPoint& cp, ModelRepresentation& mr)
489 /* part one: find out where the visual control point is.
490 initial results are in canvas units. ask the
491 line to convert them to something relevant.
494 mr.xval = (nframes_t) floor (cp.get_x());
495 mr.yval = 1.0 - (cp.get_y() / _height);
497 /* if xval has not changed, set it directly from the model to avoid rounding errors */
499 if (mr.xval == trackview.editor.frame_to_unit((*cp.model)->when)) {
500 mr.xval = (nframes_t) (*cp.model)->when;
502 mr.xval = trackview.editor.unit_to_frame (mr.xval);
505 /* virtual call: this will do the right thing
506 for whatever particular type of line we are.
509 view_to_model_y (mr.yval);
511 /* part 2: find out where the model point is now
514 mr.xpos = (nframes_t) (*cp.model)->when;
515 mr.ypos = (*cp.model)->value;
517 /* part 3: get the position of the visual control
518 points before and after us.
521 ControlPoint* before;
525 before = nth (cp.view_index - 1);
530 after = nth (cp.view_index + 1);
533 mr.xmin = (nframes_t) (*before->model)->when;
534 mr.ymin = (*before->model)->value;
535 mr.start = before->model;
544 mr.end = after->model;
554 AutomationLine::determine_visible_control_points (ALPoints& points)
556 uint32_t view_index, pi, n;
557 AutomationList::iterator model;
559 double last_control_point_x = 0.0;
560 double last_control_point_y = 0.0;
561 uint32_t this_rx = 0;
562 uint32_t prev_rx = 0;
563 uint32_t this_ry = 0;
564 uint32_t prev_ry = 0;
569 /* hide all existing points, and the line */
573 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
580 if (points.empty()) {
584 npoints = points.size();
586 /* compute derivative/slope for the entire line */
588 slope = new double[npoints];
590 for (n = 0; n < npoints - 1; ++n) {
591 double xdelta = points[n+1].x - points[n].x;
592 double ydelta = points[n+1].y - points[n].y;
593 slope[n] = ydelta/xdelta;
596 box_size = (uint32_t) control_point_box_size ();
598 /* read all points and decide which ones to show as control points */
602 ofstream oout ("orig_coordinates");
604 for (model = alist.begin(), pi = 0; pi < npoints; ++model, ++pi) {
606 double tx = points[pi].x;
607 double ty = points[pi].y;
609 oout << tx << ' ' << ty << endl;
611 /* now ensure that the control_points vector reflects the current curve
612 state, but don't plot control points too close together. also, don't
613 plot a series of points all with the same value.
615 always plot the first and last points, of course.
618 if (invalid_point (points, pi)) {
619 /* for some reason, we are supposed to ignore this point,
620 but still keep track of the model index.
625 if (pi > 0 && pi < npoints - 1) {
626 if (slope[pi] == slope[pi-1]) {
628 /* no reason to display this point */
634 /* need to round here. the ultimate coordinates are integer
635 pixels, so tiny deltas in the coords will be eliminated
636 and we end up with "colinear" line segments. since the
637 line rendering code in libart doesn't like this very
638 much, we eliminate them here. don't do this for the first and last
642 this_rx = (uint32_t) rint (tx);
643 this_ry = (uint32_t) rint (ty);
645 if (view_index && pi != npoints && /* not the first, not the last */
646 (((this_rx == prev_rx) && (this_ry == prev_ry)) || /* same point */
647 (this_rx == prev_rx) || /* identical x coordinate */
648 (((this_rx - prev_rx) < (box_size + 2)) && /* not identical, but still too close horizontally */
649 ((abs ((int)(this_ry - prev_ry)) < (int) (box_size + 2)))))) { /* too close vertically */
653 /* ok, we should display this point */
655 if (view_index >= cpsize) {
657 /* make sure we have enough control points */
659 ControlPoint* ncp = new ControlPoint (*this);
661 ncp->set_size (box_size);
663 control_points.push_back (ncp);
667 ControlPoint::ShapeType shape;
669 if (!terminal_points_can_slide) {
671 control_points[view_index]->can_slide = false;
673 shape = ControlPoint::Start;
675 shape = ControlPoint::Full;
677 } else if (pi == npoints - 1) {
678 control_points[view_index]->can_slide = false;
679 shape = ControlPoint::End;
681 control_points[view_index]->can_slide = true;
682 shape = ControlPoint::Full;
685 control_points[view_index]->can_slide = true;
686 shape = ControlPoint::Full;
689 last_control_point_x = tx;
690 last_control_point_y = ty;
692 control_points[view_index]->reset (tx, ty, model, view_index, shape);
697 /* finally, control visibility */
699 if (_visible && points_visible) {
700 control_points[view_index]->show ();
701 control_points[view_index]->set_visible (true);
703 if (!points_visible) {
704 control_points[view_index]->set_visible (false);
713 /* discard extra CP's to avoid confusing ourselves */
715 while (control_points.size() > view_index) {
716 ControlPoint* cp = control_points.back();
717 control_points.pop_back ();
721 if (!terminal_points_can_slide) {
722 control_points.back()->can_slide = false;
727 if (view_index > 1) {
729 npoints = view_index;
731 /* reset the line coordinates */
733 while (line_points.size() < npoints) {
734 line_points.push_back (Art::Point (0,0));
737 while (line_points.size() > npoints) {
738 line_points.pop_back ();
741 for (view_index = 0; view_index < npoints; ++view_index) {
742 line_points[view_index].set_x (control_points[view_index]->get_x());
743 line_points[view_index].set_y (control_points[view_index]->get_y());
746 line->property_points() = line_points;
754 set_selected_points (trackview.editor.get_selection().points);
759 AutomationLine::get_verbose_cursor_string (float fraction)
763 if (_vc_uses_gain_mapping) {
764 if (fraction == 0.0) {
765 snprintf (buf, sizeof (buf), "-inf dB");
767 snprintf (buf, sizeof (buf), "%.1fdB", coefficient_to_dB (slider_position_to_gain (fraction)));
770 snprintf (buf, sizeof (buf), "%.2f", fraction);
777 AutomationLine::invalid_point (ALPoints& p, uint32_t index)
779 return p[index].x == max_frames && p[index].y == DBL_MAX;
783 AutomationLine::invalidate_point (ALPoints& p, uint32_t index)
785 p[index].x = max_frames;
786 p[index].y = DBL_MAX;
790 AutomationLine::start_drag (ControlPoint* cp, nframes_t x, float fraction)
792 if (trackview.editor.current_session() == 0) { /* how? */
799 str = _("automation event move");
801 str = _("automation range drag");
804 trackview.editor.current_session()->begin_reversible_command (str);
805 trackview.editor.current_session()->add_command (new MementoCommand<AutomationList>(alist, &get_state(), 0));
809 first_drag_fraction = fraction;
810 last_drag_fraction = fraction;
816 AutomationLine::point_drag (ControlPoint& cp, nframes_t x, float fraction, bool with_push)
819 drag_distance += (x - drag_x);
821 drag_distance -= (drag_x - x);
826 modify_view_point (cp, x, fraction, with_push);
828 if (line_points.size() > 1) {
829 line->property_points() = line_points;
833 did_push = with_push;
837 AutomationLine::line_drag (uint32_t i1, uint32_t i2, float fraction, bool with_push)
839 double ydelta = fraction - last_drag_fraction;
841 did_push = with_push;
843 last_drag_fraction = fraction;
850 for (uint32_t i = i1 ; i <= i2; i++) {
852 modify_view_point (*cp, trackview.editor.unit_to_frame (cp->get_x()), ((_height - cp->get_y()) /_height) + ydelta, with_push);
855 if (line_points.size() > 1) {
856 line->property_points() = line_points;
863 AutomationLine::end_drag (ControlPoint* cp)
872 sync_model_with_view_point (*cp, did_push, drag_distance);
874 sync_model_with_view_line (line_drag_cp1, line_drag_cp2);
879 update_pending = false;
881 trackview.editor.current_session()->add_command (new MementoCommand<AutomationList>(alist, 0, &alist.get_state()));
882 trackview.editor.current_session()->commit_reversible_command ();
883 trackview.editor.current_session()->set_dirty ();
888 AutomationLine::sync_model_with_view_point (ControlPoint& cp, bool did_push, int64_t distance)
890 ModelRepresentation mr;
893 model_representation (cp, mr);
895 /* how much are we changing the central point by */
897 ydelta = mr.yval - mr.ypos;
900 apply the full change to the central point, and interpolate
901 on both axes to cover all model points represented
902 by the control point.
905 /* change all points before the primary point */
907 for (AutomationList::iterator i = mr.start; i != cp.model; ++i) {
909 double fract = ((*i)->when - mr.xmin) / (mr.xpos - mr.xmin);
910 double y_delta = ydelta * fract;
911 double x_delta = distance * fract;
915 if (y_delta || x_delta) {
916 alist.modify (i, (*i)->when + x_delta, mr.ymin + y_delta);
920 /* change the primary point */
922 update_pending = true;
923 alist.modify (cp.model, mr.xval, mr.yval);
926 /* change later points */
928 AutomationList::iterator i = cp.model;
932 while (i != mr.end) {
934 double delta = ydelta * (mr.xmax - (*i)->when) / (mr.xmax - mr.xpos);
936 /* all later points move by the same distance along the x-axis as the main point */
939 alist.modify (i, (*i)->when + distance, (*i)->value + delta);
947 /* move all points after the range represented by the view by the same distance
948 as the main point moved.
951 alist.slide (mr.end, drag_distance);
957 AutomationLine::control_points_adjacent (double xval, uint32_t & before, uint32_t& after)
959 ControlPoint *bcp = 0;
960 ControlPoint *acp = 0;
963 /* xval is in frames */
965 unit_xval = trackview.editor.frame_to_unit (xval);
967 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
969 if ((*i)->get_x() <= unit_xval) {
971 if (!bcp || (*i)->get_x() > bcp->get_x()) {
973 before = bcp->view_index;
976 } else if ((*i)->get_x() > unit_xval) {
978 after = acp->view_index;
987 AutomationLine::is_last_point (ControlPoint& cp)
989 ModelRepresentation mr;
991 model_representation (cp, mr);
993 // If the list is not empty, and the point is the last point in the list
995 if (!alist.empty() && mr.end == alist.end()) {
1003 AutomationLine::is_first_point (ControlPoint& cp)
1005 ModelRepresentation mr;
1007 model_representation (cp, mr);
1009 // If the list is not empty, and the point is the first point in the list
1011 if (!alist.empty() && mr.start == alist.begin()) {
1018 // This is copied into AudioRegionGainLine
1020 AutomationLine::remove_point (ControlPoint& cp)
1022 ModelRepresentation mr;
1024 model_representation (cp, mr);
1026 trackview.editor.current_session()->begin_reversible_command (_("remove control point"));
1027 XMLNode &before = alist.get_state();
1029 alist.erase (mr.start, mr.end);
1031 trackview.editor.current_session()->add_command(new MementoCommand<AutomationList>(alist, &before, &alist.get_state()));
1032 trackview.editor.current_session()->commit_reversible_command ();
1033 trackview.editor.current_session()->set_dirty ();
1037 AutomationLine::get_selectables (nframes_t& start, nframes_t& end,
1038 double botfrac, double topfrac, list<Selectable*>& results)
1045 bool collecting = false;
1047 /* Curse X11 and its inverted coordinate system! */
1049 bot = (1.0 - topfrac) * _height;
1050 top = (1.0 - botfrac) * _height;
1052 nstart = max_frames;
1055 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1057 nframes_t when = (nframes_t) (*(*i)->model)->when;
1059 if (when >= start && when <= end) {
1061 if ((*i)->get_y() >= bot && (*i)->get_y() <= top) {
1064 (*i)->set_visible(true);
1066 nstart = min (nstart, when);
1067 nend = max (nend, when);
1073 results.push_back (new AutomationSelectable (nstart, nend, botfrac, topfrac, trackview));
1075 nstart = max_frames;
1083 results.push_back (new AutomationSelectable (nstart, nend, botfrac, topfrac, trackview));
1089 AutomationLine::get_inverted_selectables (Selection&, list<Selectable*>& results)
1095 AutomationLine::set_selected_points (PointSelection& points)
1100 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1101 (*i)->selected = false;
1104 if (points.empty()) {
1108 for (PointSelection::iterator r = points.begin(); r != points.end(); ++r) {
1110 if (&(*r).track != &trackview) {
1114 /* Curse X11 and its inverted coordinate system! */
1116 bot = (1.0 - (*r).high_fract) * _height;
1117 top = (1.0 - (*r).low_fract) * _height;
1119 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1121 double rstart, rend;
1123 rstart = trackview.editor.frame_to_unit ((*r).start);
1124 rend = trackview.editor.frame_to_unit ((*r).end);
1126 if ((*i)->get_x() >= rstart && (*i)->get_x() <= rend) {
1128 if ((*i)->get_y() >= bot && (*i)->get_y() <= top) {
1130 (*i)->selected = true;
1138 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1139 (*i)->show_color (false, !points_visible);
1145 AutomationLine::show_selection ()
1147 TimeSelection& time (trackview.editor.get_selection().time);
1149 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1151 (*i)->selected = false;
1153 for (list<AudioRange>::iterator r = time.begin(); r != time.end(); ++r) {
1154 double rstart, rend;
1156 rstart = trackview.editor.frame_to_unit ((*r).start);
1157 rend = trackview.editor.frame_to_unit ((*r).end);
1159 if ((*i)->get_x() >= rstart && (*i)->get_x() <= rend) {
1160 (*i)->selected = true;
1165 (*i)->show_color (false, !points_visible);
1170 AutomationLine::hide_selection ()
1172 // show_selection ();
1176 AutomationLine::list_changed ()
1182 AutomationLine::reset_callback (const AutomationList& events)
1184 ALPoints tmp_points;
1185 uint32_t npoints = events.size();
1188 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1191 control_points.clear ();
1196 AutomationList::const_iterator ai;
1198 for (ai = events.const_begin(); ai != events.const_end(); ++ai) {
1200 double translated_y = (*ai)->value;
1201 model_to_view_y (translated_y);
1203 tmp_points.push_back (ALPoint (trackview.editor.frame_to_unit ((*ai)->when),
1204 _height - (translated_y * _height)));
1207 determine_visible_control_points (tmp_points);
1211 AutomationLine::reset ()
1213 update_pending = false;
1219 alist.apply_to_points (*this, &AutomationLine::reset_callback);
1223 AutomationLine::clear ()
1225 /* parent must create command */
1226 XMLNode &before = get_state();
1228 trackview.editor.current_session()->add_command (new MementoCommand<AutomationLine>(*this, &before, &get_state()));
1229 trackview.editor.current_session()->commit_reversible_command ();
1230 trackview.editor.current_session()->set_dirty ();
1234 AutomationLine::change_model (AutomationList::iterator i, double x, double y)
1239 AutomationLine::change_model_range (AutomationList::iterator start, AutomationList::iterator end, double xdelta, float ydelta)
1241 alist.move_range (start, end, xdelta, ydelta);
1245 AutomationLine::show_all_control_points ()
1247 points_visible = true;
1249 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1251 (*i)->set_visible (true);
1256 AutomationLine::hide_all_but_selected_control_points ()
1258 points_visible = false;
1260 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1261 if (!(*i)->selected) {
1262 (*i)->set_visible (false);
1268 AutomationLine::get_state (void)
1270 /* function as a proxy for the model */
1271 return alist.get_state();
1275 AutomationLine::set_state (const XMLNode &node)
1277 /* function as a proxy for the model */
1278 return alist.set_state (node);