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>
27 #include <ardour/automation_event.h>
28 #include <ardour/curve.h>
29 #include <ardour/dB.h>
31 #include "simplerect.h"
32 #include "automation_line.h"
33 #include "rgb_macros.h"
34 #include "ardour_ui.h"
35 #include "public_editor.h"
37 #include "selection.h"
38 #include "time_axis_view.h"
39 #include "point_selection.h"
40 #include "automation_selectable.h"
41 #include "automation_time_axis.h"
42 #include "public_editor.h"
44 #include <ardour/session.h>
50 using namespace ARDOUR;
51 using namespace Editing;
52 using namespace Gnome; // for Canvas
54 ControlPoint::ControlPoint (AutomationLine& al)
57 model = al.the_list().end();
66 item = new Canvas::SimpleRect (line.canvas_group());
67 item->property_draw() = true;
68 item->property_fill() = false;
69 item->property_fill_color_rgba() = color_map[cControlPointFill];
70 item->property_outline_color_rgba() = color_map[cControlPointOutline];
71 item->property_outline_pixels() = 1;
72 item->set_data ("control_point", this);
73 item->signal_event().connect (mem_fun (this, &ControlPoint::event_handler));
79 ControlPoint::ControlPoint (const ControlPoint& other, bool dummy_arg_to_force_special_copy_constructor)
87 view_index = other.view_index;
88 can_slide = other.can_slide;
91 _shape = other._shape;
95 item = new Canvas::SimpleRect (line.canvas_group());
96 item->property_fill() = false;
97 item->property_outline_color_rgba() = color_map[cEnteredControlPointOutline];
98 item->property_outline_pixels() = 1;
100 /* NOTE: no event handling in copied ControlPoints */
106 ControlPoint::~ControlPoint ()
112 ControlPoint::event_handler (GdkEvent* event)
114 return PublicEditor::instance().canvas_control_point_event (event, item, this);
118 ControlPoint::hide ()
130 ControlPoint::set_visible (bool yn)
132 item->property_draw() = (gboolean) yn;
136 ControlPoint::reset (double x, double y, AutomationList::iterator mi, uint32_t vi, ShapeType shape)
140 move_to (x, y, shape);
144 ControlPoint::show_color (bool entered, bool hide_too)
148 item->property_outline_color_rgba() = color_map[cEnteredControlPointSelected];
151 item->property_outline_color_rgba() = color_map[cEnteredControlPoint];
159 item->property_outline_color_rgba() = color_map[cControlPointSelected];
162 item->property_outline_color_rgba() = color_map[cControlPoint];
171 ControlPoint::set_size (double sz)
177 item->property_fill() = (gboolean) TRUE;
179 item->property_fill() = (gboolean) FALSE;
183 move_to (_x, _y, _shape);
187 ControlPoint::move_to (double x, double y, ShapeType shape)
191 double half_size = rint(_size/2.0);
208 item->property_x1() = x1;
209 item->property_x2() = x2;
210 item->property_y1() = y - half_size;
211 item->property_y2() = y + half_size;
220 AutomationLine::AutomationLine (string name, TimeAxisView& tv, ArdourCanvas::Group& parent, AutomationList& al)
224 _parent_group (parent)
226 points_visible = false;
227 update_pending = false;
228 _vc_uses_gain_mapping = false;
231 terminal_points_can_slide = true;
234 group = new ArdourCanvas::Group (parent);
235 group->property_x() = 0.0;
236 group->property_y() = 0.0;
238 line = new ArdourCanvas::Line (*group);
239 line->property_width_pixels() = (guint)1;
241 line->signal_event().connect (mem_fun (*this, &AutomationLine::event_handler));
243 alist.StateChanged.connect (mem_fun(*this, &AutomationLine::list_changed));
246 AutomationLine::~AutomationLine ()
248 vector_delete (&control_points);
253 AutomationLine::event_handler (GdkEvent* event)
255 return PublicEditor::instance().canvas_line_event (event, line, this);
259 AutomationLine::queue_reset ()
261 if (!update_pending) {
262 update_pending = true;
263 Gtkmm2ext::UI::instance()->call_slot (mem_fun(*this, &AutomationLine::reset));
268 AutomationLine::set_point_size (double sz)
270 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
276 AutomationLine::show ()
280 if (points_visible) {
281 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
290 AutomationLine::hide ()
293 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
300 AutomationLine::set_height (guint32 h)
305 if (_height > (guint32) TimeAxisView::Larger) {
306 set_point_size (8.0);
307 } else if (_height > (guint32) TimeAxisView::Normal) {
308 set_point_size (6.0);
310 set_point_size (4.0);
318 AutomationLine::set_line_color (uint32_t color)
321 line->property_fill_color_rgba() = color;
325 AutomationLine::set_verbose_cursor_uses_gain_mapping (bool yn)
327 if (yn != _vc_uses_gain_mapping) {
328 _vc_uses_gain_mapping = yn;
334 AutomationLine::nth (uint32_t n)
336 if (n < control_points.size()) {
337 return control_points[n];
344 AutomationLine::modify_view (ControlPoint& cp, double x, double y, bool with_push)
346 modify_view_point (cp, x, y, with_push);
351 AutomationLine::modify_view_point (ControlPoint& cp, double x, double y, bool with_push)
354 uint32_t last_movable = UINT_MAX;
355 double x_limit = DBL_MAX;
357 /* this just changes the current view. it does not alter
358 the model in any way at all.
361 /* clamp y-coord appropriately. y is supposed to be a normalized fraction (0.0-1.0),
362 and needs to be converted to a canvas unit distance.
367 y = _height - (y * _height);
371 /* x-coord cannot move beyond adjacent points or the start/end, and is
372 already in frames. it needs to be converted to canvas units.
375 x = trackview.editor.frame_to_unit (x);
377 /* clamp x position using view coordinates */
379 ControlPoint *before;
383 before = nth (cp.view_index - 1);
384 x = max (x, before->get_x()+1.0);
391 if (cp.view_index < control_points.size() - 1) {
393 after = nth (cp.view_index + 1);
395 /*if it is a "spike" leave the x alone */
397 if (after->get_x() - before->get_x() < 2) {
401 x = min (x, after->get_x()-1.0);
411 /* find the first point that can't move */
413 for (uint32_t n = cp.view_index + 1; (after = nth (n)) != 0; ++n) {
414 if (!after->can_slide) {
415 x_limit = after->get_x() - 1.0;
416 last_movable = after->view_index;
421 delta = x - cp.get_x();
426 /* leave the x-coordinate alone */
428 x = trackview.editor.frame_to_unit ((*cp.model)->when);
434 cp.move_to (x, y, ControlPoint::Full);
435 reset_line_coords (cp);
439 uint32_t limit = min (control_points.size(), (size_t)last_movable);
441 /* move the current point to wherever the user told it to go, subject
445 cp.move_to (min (x, x_limit), y, ControlPoint::Full);
446 reset_line_coords (cp);
448 /* now move all subsequent control points, to reflect the motion.
451 for (uint32_t i = cp.view_index + 1; i < limit; ++i) {
452 ControlPoint *p = nth (i);
456 new_x = min (p->get_x() + delta, x_limit);
457 p->move_to (new_x, p->get_y(), ControlPoint::Full);
458 reset_line_coords (*p);
465 AutomationLine::reset_line_coords (ControlPoint& cp)
467 line_points[cp.view_index].set_x (cp.get_x());
468 line_points[cp.view_index].set_y (cp.get_y());
472 AutomationLine::update_line ()
474 line->property_points() = line_points;
478 AutomationLine::sync_model_with_view_line (uint32_t start, uint32_t end)
483 update_pending = true;
485 for (uint32_t i = start; i <= end; ++i) {
487 sync_model_with_view_point(*p);
494 AutomationLine::model_representation (ControlPoint& cp, ModelRepresentation& mr)
496 /* part one: find out where the visual control point is.
497 initial results are in canvas units. ask the
498 line to convert them to something relevant.
501 mr.xval = (jack_nframes_t) floor (cp.get_x());
502 mr.yval = 1.0 - (cp.get_y() / _height);
505 /* if xval has not changed, set it directly from the model to avoid rounding errors */
507 if (mr.xval == trackview.editor.frame_to_unit((*cp.model)->when)) {
508 mr.xval = (jack_nframes_t) (*cp.model)->when;
510 mr.xval = trackview.editor.unit_to_frame (mr.xval);
514 /* virtual call: this will do the right thing
515 for whatever particular type of line we are.
518 view_to_model_y (mr.yval);
520 /* part 2: find out where the model point is now
523 mr.xpos = (jack_nframes_t) (*cp.model)->when;
524 mr.ypos = (*cp.model)->value;
526 /* part 3: get the position of the visual control
527 points before and after us.
530 ControlPoint* before;
534 before = nth (cp.view_index - 1);
539 after = nth (cp.view_index + 1);
542 mr.xmin = (jack_nframes_t) (*before->model)->when;
543 mr.ymin = (*before->model)->value;
544 mr.start = before->model;
553 mr.end = after->model;
563 AutomationLine::sync_model_from (ControlPoint& cp)
568 sync_model_with_view_point (cp);
570 /* we might have moved all points after `cp' by some amount
571 if we pressed the with_push modifyer some of the time during the drag
572 so all subsequent points have to be resynced
575 lasti = control_points.size() - 1;
578 update_pending = true;
580 while (p != &cp && lasti) {
581 sync_model_with_view_point (*p);
588 AutomationLine::sync_model_with_view_point (ControlPoint& cp)
590 ModelRepresentation mr;
593 model_representation (cp, mr);
595 /* part 4: how much are we changing the central point by */
597 ydelta = mr.yval - mr.ypos;
599 /* IMPORTANT: changing the model when the x-coordinate changes
600 may invalidate the iterators that we are using. this means that we have
601 to change the points before+after the one corresponding to the visual CP
602 first (their x-coordinate doesn't change). then we change the
605 apply the full change to the central point, and interpolate
606 in each direction to cover all model points represented
607 by the control point.
610 /* part 5: change all points before the primary point */
612 for (AutomationList::iterator i = mr.start; i != cp.model; ++i) {
616 delta = ydelta * ((*i)->when - mr.xmin) / (mr.xpos - mr.xmin);
618 /* x-coordinate (generally time) stays where it is,
619 y-coordinate moves by a certain amount.
622 update_pending = true;
623 change_model (i, (*i)->when, mr.yval + delta);
626 /* part 6: change later points */
628 AutomationList::iterator i = cp.model;
632 while (i != mr.end) {
636 delta = ydelta * (mr.xmax - (*i)->when) / (mr.xmax - mr.xpos);
638 /* x-coordinate (generally time) stays where it is,
639 y-coordinate moves by a certain amount.
642 update_pending = true;
643 change_model (i, (*i)->when, (*i)->value + delta);
648 /* part 7: change the primary point */
650 update_pending = true;
651 change_model (cp.model, mr.xval, mr.yval);
655 AutomationLine::determine_visible_control_points (ALPoints& points)
657 uint32_t view_index, pi, n;
658 AutomationList::iterator model;
660 double last_control_point_x = 0.0;
661 double last_control_point_y = 0.0;
662 uint32_t this_rx = 0;
663 uint32_t prev_rx = 0;
664 uint32_t this_ry = 0;
665 uint32_t prev_ry = 0;
668 /* hide all existing points, and the line */
670 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
676 if (points.empty()) {
680 npoints = points.size();
682 /* compute derivative/slope for the entire line */
684 slope = new double[npoints];
686 for (n = 0; n < npoints - 1; ++n) {
687 double xdelta = points[n+1].x - points[n].x;
688 double ydelta = points[n+1].y - points[n].y;
689 slope[n] = ydelta/xdelta;
692 /* read all points and decide which ones to show as control points */
696 for (model = alist.begin(), pi = 0; pi < npoints; ++model, ++pi) {
698 double tx = points[pi].x;
699 double ty = points[pi].y;
701 /* now ensure that the control_points vector reflects the current curve
702 state, but don't plot control points too close together. also, don't
703 plot a series of points all with the same value.
705 always plot the first and last points, of course.
708 if (invalid_point (points, pi)) {
709 /* for some reason, we are supposed to ignore this point,
710 but still keep track of the model index.
715 if (pi > 0 && pi < npoints - 1) {
716 if (slope[pi] == slope[pi-1]) {
718 /* no reason to display this point */
724 /* need to round here. the ultimate coordinates are integer
725 pixels, so tiny deltas in the coords will be eliminated
726 and we end up with "colinear" line segments. since the
727 line rendering code in libart doesn't like this very
728 much, we eliminate them here. don't do this for the first and last
732 this_rx = (uint32_t) rint (tx);
733 this_ry = (unsigned long) rint (ty);
735 if (view_index && pi != npoints && (this_rx == prev_rx) && (this_ry == prev_ry)) {
740 /* ok, we should display this point */
742 if (view_index >= control_points.size()) {
743 /* make sure we have enough control points */
745 ControlPoint* ncp = new ControlPoint (*this);
747 if (_height > (guint32) TimeAxisView::Larger) {
749 } else if (_height > (guint32) TimeAxisView::Normal) {
755 control_points.push_back (ncp);
758 ControlPoint::ShapeType shape;
760 if (!terminal_points_can_slide) {
762 control_points[view_index]->can_slide = false;
764 shape = ControlPoint::Start;
766 shape = ControlPoint::Full;
768 } else if (pi == npoints - 1) {
769 control_points[view_index]->can_slide = false;
770 shape = ControlPoint::End;
772 control_points[view_index]->can_slide = true;
773 shape = ControlPoint::Full;
776 control_points[view_index]->can_slide = true;
777 shape = ControlPoint::Full;
780 last_control_point_x = tx;
781 last_control_point_y = ty;
783 control_points[view_index]->reset (tx, ty, model, view_index, shape);
788 /* finally, control visibility */
790 if (_visible && points_visible) {
791 control_points[view_index]->show ();
792 control_points[view_index]->set_visible (true);
794 if (!points_visible) {
795 control_points[view_index]->set_visible (false);
802 /* discard extra CP's to avoid confusing ourselves */
804 while (control_points.size() > view_index) {
805 ControlPoint* cp = control_points.back();
806 control_points.pop_back ();
810 if (!terminal_points_can_slide) {
811 control_points.back()->can_slide = false;
816 if (view_index > 1) {
818 npoints = view_index;
820 /* reset the line coordinates */
822 while (line_points.size() < npoints) {
823 line_points.push_back (Art::Point (0,0));
826 for (view_index = 0; view_index < npoints; ++view_index) {
827 line_points[view_index].set_x (control_points[view_index]->get_x());
828 line_points[view_index].set_y (control_points[view_index]->get_y());
831 line->property_points() = line_points;
838 set_selected_points (trackview.editor.get_selection().points);
842 AutomationLine::get_verbose_cursor_string (float fraction)
846 if (_vc_uses_gain_mapping) {
847 if (fraction == 0.0) {
848 snprintf (buf, sizeof (buf), "-inf dB");
850 snprintf (buf, sizeof (buf), "%.1fdB", coefficient_to_dB (slider_position_to_gain (fraction)));
853 snprintf (buf, sizeof (buf), "%.2f", fraction);
860 AutomationLine::invalid_point (ALPoints& p, uint32_t index)
862 return p[index].x == max_frames && p[index].y == DBL_MAX;
866 AutomationLine::invalidate_point (ALPoints& p, uint32_t index)
868 p[index].x = max_frames;
869 p[index].y = DBL_MAX;
873 AutomationLine::start_drag (ControlPoint* cp, float fraction)
875 if (trackview.editor.current_session() == 0) { /* how? */
882 str = _("automation event move");
884 str = _("automation range drag");
887 trackview.editor.current_session()->begin_reversible_command (str);
888 trackview.editor.current_session()->add_undo (get_memento());
890 first_drag_fraction = fraction;
891 last_drag_fraction = fraction;
896 AutomationLine::point_drag (ControlPoint& cp, jack_nframes_t x, float fraction, bool with_push)
898 modify_view (cp, x, fraction, with_push);
903 AutomationLine::line_drag (uint32_t i1, uint32_t i2, float fraction, bool with_push)
905 double ydelta = fraction - last_drag_fraction;
907 last_drag_fraction = fraction;
914 for (uint32_t i = i1 ; i <= i2; i++) {
916 modify_view_point (*cp, trackview.editor.unit_to_frame (cp->get_x()), ((_height - cp->get_y()) /_height) + ydelta, with_push);
925 AutomationLine::end_drag (ControlPoint* cp)
930 sync_model_from (*cp);
932 sync_model_with_view_line (line_drag_cp1, line_drag_cp2);
935 update_pending = false;
937 trackview.editor.current_session()->add_redo_no_execute (get_memento());
938 trackview.editor.current_session()->commit_reversible_command ();
939 trackview.editor.current_session()->set_dirty ();
944 AutomationLine::control_points_adjacent (double xval, uint32_t & before, uint32_t& after)
946 ControlPoint *bcp = 0;
947 ControlPoint *acp = 0;
950 /* xval is in frames */
952 unit_xval = trackview.editor.frame_to_unit (xval);
954 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
956 if ((*i)->get_x() <= unit_xval) {
958 if (!bcp || (*i)->get_x() > bcp->get_x()) {
960 before = bcp->view_index;
963 } else if ((*i)->get_x() > unit_xval) {
965 after = acp->view_index;
974 AutomationLine::is_last_point (ControlPoint& cp)
976 ModelRepresentation mr;
978 model_representation (cp, mr);
980 // If the list is not empty, and the point is the last point in the list
982 if (!alist.empty() && mr.end == alist.end()) {
990 AutomationLine::is_first_point (ControlPoint& cp)
992 ModelRepresentation mr;
994 model_representation (cp, mr);
996 // If the list is not empty, and the point is the first point in the list
998 if (!alist.empty() && mr.start == alist.begin()) {
1005 // This is copied into AudioRegionGainLine
1007 AutomationLine::remove_point (ControlPoint& cp)
1009 ModelRepresentation mr;
1011 model_representation (cp, mr);
1013 trackview.editor.current_session()->begin_reversible_command (_("remove control point"));
1014 trackview.editor.current_session()->add_undo (get_memento());
1016 alist.erase (mr.start, mr.end);
1018 trackview.editor.current_session()->add_redo_no_execute (get_memento());
1019 trackview.editor.current_session()->commit_reversible_command ();
1020 trackview.editor.current_session()->set_dirty ();
1024 AutomationLine::get_selectables (jack_nframes_t& start, jack_nframes_t& end,
1025 double botfrac, double topfrac, list<Selectable*>& results)
1030 jack_nframes_t nstart;
1031 jack_nframes_t nend;
1032 bool collecting = false;
1034 /* Curse X11 and its inverted coordinate system! */
1036 bot = (1.0 - topfrac) * _height;
1037 top = (1.0 - botfrac) * _height;
1039 nstart = max_frames;
1042 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1044 jack_nframes_t when = (jack_nframes_t) (*(*i)->model)->when;
1046 if (when >= start && when <= end) {
1048 if ((*i)->get_y() >= bot && (*i)->get_y() <= top) {
1051 (*i)->set_visible(true);
1053 nstart = min (nstart, when);
1054 nend = max (nend, when);
1060 results.push_back (new AutomationSelectable (nstart, nend, botfrac, topfrac, trackview));
1062 nstart = max_frames;
1070 results.push_back (new AutomationSelectable (nstart, nend, botfrac, topfrac, trackview));
1076 AutomationLine::get_inverted_selectables (Selection&, list<Selectable*>& results)
1082 AutomationLine::set_selected_points (PointSelection& points)
1087 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1088 (*i)->selected = false;
1091 if (points.empty()) {
1095 for (PointSelection::iterator r = points.begin(); r != points.end(); ++r) {
1097 if (&(*r).track != &trackview) {
1101 /* Curse X11 and its inverted coordinate system! */
1103 bot = (1.0 - (*r).high_fract) * _height;
1104 top = (1.0 - (*r).low_fract) * _height;
1106 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1108 double rstart, rend;
1110 rstart = trackview.editor.frame_to_unit ((*r).start);
1111 rend = trackview.editor.frame_to_unit ((*r).end);
1113 if ((*i)->get_x() >= rstart && (*i)->get_x() <= rend) {
1115 if ((*i)->get_y() >= bot && (*i)->get_y() <= top) {
1117 (*i)->selected = true;
1125 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1126 (*i)->show_color (false, !points_visible);
1132 AutomationLine::show_selection ()
1134 TimeSelection& time (trackview.editor.get_selection().time);
1136 // cerr << "show selection\n";
1138 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1140 (*i)->selected = false;
1142 for (list<AudioRange>::iterator r = time.begin(); r != time.end(); ++r) {
1143 double rstart, rend;
1145 rstart = trackview.editor.frame_to_unit ((*r).start);
1146 rend = trackview.editor.frame_to_unit ((*r).end);
1148 if ((*i)->get_x() >= rstart && (*i)->get_x() <= rend) {
1149 (*i)->selected = true;
1154 (*i)->show_color (false, !points_visible);
1159 AutomationLine::hide_selection ()
1161 // cerr << "hide selection\n";
1162 // show_selection ();
1166 // This is copied into AudioRegionGainLine
1168 AutomationLine::get_memento ()
1170 return alist.get_memento();
1174 AutomationLine::list_changed (Change ignored)
1180 AutomationLine::reset_callback (const AutomationList& events)
1182 ALPoints tmp_points;
1183 uint32_t npoints = events.size();
1186 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1189 control_points.clear ();
1194 AutomationList::const_iterator ai;
1196 for (ai = events.const_begin(); ai != events.const_end(); ++ai) {
1198 double translated_y;
1200 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 trackview.editor.current_session()->add_undo (get_memento());
1228 trackview.editor.current_session()->add_redo_no_execute (get_memento());
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)
1236 alist.modify (i, (jack_nframes_t) x, y);
1240 AutomationLine::change_model_range (AutomationList::iterator start, AutomationList::iterator end, double xdelta, float ydelta)
1242 alist.move_range (start, end, xdelta, ydelta);
1246 AutomationLine::show_all_control_points ()
1248 points_visible = true;
1250 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1252 (*i)->set_visible (true);
1257 AutomationLine::hide_all_but_selected_control_points ()
1259 points_visible = false;
1261 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1262 if (!(*i)->selected) {
1263 (*i)->set_visible (false);