fix math bug with numthreads computation
[ardour2.git] / gtk2_ardour / automation_line.cc
blob6ad5722d5f31ea5ef3bee3ba9942983d2f550f3e
1 /*
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.
20 #include <cmath>
21 #include <climits>
22 #include <vector>
23 #include <fstream>
25 #include "pbd/stl_delete.h"
26 #include "pbd/memento_command.h"
27 #include "pbd/stacktrace.h"
29 #include "ardour/automation_list.h"
30 #include "ardour/dB.h"
31 #include "evoral/Curve.hpp"
33 #include "simplerect.h"
34 #include "automation_line.h"
35 #include "control_point.h"
36 #include "gui_thread.h"
37 #include "rgb_macros.h"
38 #include "ardour_ui.h"
39 #include "public_editor.h"
40 #include "utils.h"
41 #include "selection.h"
42 #include "time_axis_view.h"
43 #include "point_selection.h"
44 #include "automation_selectable.h"
45 #include "automation_time_axis.h"
46 #include "public_editor.h"
48 #include "ardour/event_type_map.h"
49 #include "ardour/session.h"
51 #include "i18n.h"
53 using namespace std;
54 using namespace ARDOUR;
55 using namespace PBD;
56 using namespace Editing;
57 using namespace Gnome; // for Canvas
59 static const Evoral::IdentityConverter<double, sframes_t> default_converter;
61 AutomationLine::AutomationLine (const string& name, TimeAxisView& tv, ArdourCanvas::Group& parent,
62 boost::shared_ptr<AutomationList> al,
63 const Evoral::TimeConverter<double, sframes_t>* converter)
64 : trackview (tv)
65 , _name (name)
66 , alist (al)
67 , _parent_group (parent)
68 , _time_converter (converter ? (*converter) : default_converter)
70 _interpolation = al->interpolation();
71 points_visible = false;
72 update_pending = false;
73 _uses_gain_mapping = false;
74 no_draw = false;
75 _visible = true;
76 terminal_points_can_slide = true;
77 _height = 0;
79 group = new ArdourCanvas::Group (parent);
80 group->property_x() = 0.0;
81 group->property_y() = 0.0;
83 line = new ArdourCanvas::Line (*group);
84 line->property_width_pixels() = (guint)1;
85 line->set_data ("line", this);
87 line->signal_event().connect (sigc::mem_fun (*this, &AutomationLine::event_handler));
89 alist->StateChanged.connect (_state_connection, invalidator (*this), boost::bind (&AutomationLine::list_changed, this), gui_context());
91 trackview.session()->register_with_memento_command_factory(alist->id(), this);
93 if (alist->parameter().type() == GainAutomation ||
94 alist->parameter().type() == EnvelopeAutomation) {
95 set_uses_gain_mapping (true);
98 set_interpolation(alist->interpolation());
101 AutomationLine::~AutomationLine ()
103 vector_delete (&control_points);
104 delete group;
107 bool
108 AutomationLine::event_handler (GdkEvent* event)
110 return PublicEditor::instance().canvas_line_event (event, line, this);
113 void
114 AutomationLine::queue_reset ()
116 if (!update_pending) {
117 update_pending = true;
118 Gtkmm2ext::UI::instance()->call_slot (invalidator (*this), boost::bind (&AutomationLine::reset, this));
122 void
123 AutomationLine::show ()
125 if (_interpolation != AutomationList::Discrete) {
126 line->show();
129 if (points_visible) {
130 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
131 (*i)->show ();
135 _visible = true;
138 void
139 AutomationLine::hide ()
141 line->hide();
142 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
143 (*i)->hide();
145 _visible = false;
148 double
149 AutomationLine::control_point_box_size ()
151 if (_interpolation == AutomationList::Discrete) {
152 return max((_height*4.0) / (double)(alist->parameter().max() - alist->parameter().min()),
153 4.0);
156 if (_height > TimeAxisView::preset_height (HeightLarger)) {
157 return 8.0;
158 } else if (_height > (guint32) TimeAxisView::preset_height (HeightNormal)) {
159 return 6.0;
161 return 4.0;
164 void
165 AutomationLine::set_height (guint32 h)
167 if (h != _height) {
168 _height = h;
170 double bsz = control_point_box_size();
172 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
173 (*i)->set_size (bsz);
176 reset ();
180 void
181 AutomationLine::set_line_color (uint32_t color)
183 _line_color = color;
184 line->property_fill_color_rgba() = color;
187 void
188 AutomationLine::set_uses_gain_mapping (bool yn)
190 if (yn != _uses_gain_mapping) {
191 _uses_gain_mapping = yn;
192 reset ();
196 ControlPoint*
197 AutomationLine::nth (uint32_t n)
199 if (n < control_points.size()) {
200 return control_points[n];
201 } else {
202 return 0;
206 void
207 AutomationLine::modify_point_y (ControlPoint& cp, double y)
209 /* clamp y-coord appropriately. y is supposed to be a normalized fraction (0.0-1.0),
210 and needs to be converted to a canvas unit distance.
213 y = max (0.0, y);
214 y = min (1.0, y);
215 y = _height - (y * _height);
217 double const x = trackview.editor().frame_to_unit (_time_converter.to((*cp.model())->when));
219 trackview.editor().session()->begin_reversible_command (_("automation event move"));
220 trackview.editor().session()->add_command (new MementoCommand<AutomationList>(*alist.get(), &get_state(), 0));
222 cp.move_to (x, y, ControlPoint::Full);
223 reset_line_coords (cp);
225 if (line_points.size() > 1) {
226 line->property_points() = line_points;
229 alist->freeze ();
230 sync_model_with_view_point (cp, false, 0);
231 alist->thaw ();
233 update_pending = false;
235 trackview.editor().session()->add_command (new MementoCommand<AutomationList>(*alist.get(), 0, &alist->get_state()));
236 trackview.editor().session()->commit_reversible_command ();
237 trackview.editor().session()->set_dirty ();
240 void
241 AutomationLine::reset_line_coords (ControlPoint& cp)
243 if (cp.view_index() < line_points.size()) {
244 line_points[cp.view_index()].set_x (cp.get_x());
245 line_points[cp.view_index()].set_y (cp.get_y());
249 void
250 AutomationLine::sync_model_with_view_points (list<ControlPoint*> cp, bool did_push, int64_t distance)
252 update_pending = true;
254 for (list<ControlPoint*>::iterator i = cp.begin(); i != cp.end(); ++i) {
255 sync_model_with_view_point (**i, did_push, distance);
259 void
260 AutomationLine::model_representation (ControlPoint& cp, ModelRepresentation& mr)
262 /* part one: find out where the visual control point is.
263 initial results are in canvas units. ask the
264 line to convert them to something relevant.
267 mr.xval = cp.get_x();
268 mr.yval = 1.0 - (cp.get_y() / _height);
270 /* if xval has not changed, set it directly from the model to avoid rounding errors */
272 if (mr.xval == trackview.editor().frame_to_unit(_time_converter.to((*cp.model())->when))) {
273 mr.xval = (*cp.model())->when;
274 } else {
275 mr.xval = trackview.editor().unit_to_frame (mr.xval);
278 /* convert to model units
281 view_to_model_coord (mr.xval, mr.yval);
283 /* part 2: find out where the model point is now
286 mr.xpos = (*cp.model())->when;
287 mr.ypos = (*cp.model())->value;
289 /* part 3: get the position of the visual control
290 points before and after us.
293 ControlPoint* before;
294 ControlPoint* after;
296 if (cp.view_index()) {
297 before = nth (cp.view_index() - 1);
298 } else {
299 before = 0;
302 after = nth (cp.view_index() + 1);
304 if (before) {
305 mr.xmin = (*before->model())->when;
306 mr.ymin = (*before->model())->value;
307 mr.start = before->model();
308 ++mr.start;
309 } else {
310 mr.xmin = mr.xpos;
311 mr.ymin = mr.ypos;
312 mr.start = cp.model();
315 if (after) {
316 mr.end = after->model();
317 } else {
318 mr.xmax = mr.xpos;
319 mr.ymax = mr.ypos;
320 mr.end = cp.model();
321 ++mr.end;
325 void
326 AutomationLine::determine_visible_control_points (ALPoints& points)
328 uint32_t view_index, pi, n;
329 AutomationList::iterator model;
330 uint32_t npoints;
331 uint32_t this_rx = 0;
332 uint32_t prev_rx = 0;
333 uint32_t this_ry = 0;
334 uint32_t prev_ry = 0;
335 double* slope;
336 uint32_t box_size;
338 /* hide all existing points, and the line */
340 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
341 (*i)->hide();
344 line->hide ();
346 if (points.empty()) {
347 return;
350 npoints = points.size();
352 /* compute derivative/slope for the entire line */
354 slope = new double[npoints];
356 for (n = 0; n < npoints - 1; ++n) {
357 double xdelta = points[n+1].x - points[n].x;
358 double ydelta = points[n+1].y - points[n].y;
359 slope[n] = ydelta/xdelta;
362 box_size = (uint32_t) control_point_box_size ();
364 /* read all points and decide which ones to show as control points */
366 view_index = 0;
368 for (model = alist->begin(), pi = 0; pi < npoints; ++model, ++pi) {
370 double tx = points[pi].x;
371 double ty = points[pi].y;
373 if (find (_always_in_view.begin(), _always_in_view.end(), (*model)->when) != _always_in_view.end()) {
374 add_visible_control_point (view_index, pi, tx, ty, model, npoints);
375 prev_rx = this_rx;
376 prev_ry = this_ry;
377 ++view_index;
378 continue;
381 if (isnan (tx) || isnan (ty)) {
382 warning << string_compose (_("Ignoring illegal points on AutomationLine \"%1\""),
383 _name) << endmsg;
384 continue;
387 /* now ensure that the control_points vector reflects the current curve
388 state, but don't plot control points too close together. also, don't
389 plot a series of points all with the same value.
391 always plot the first and last points, of course.
394 if (invalid_point (points, pi)) {
395 /* for some reason, we are supposed to ignore this point,
396 but still keep track of the model index.
398 continue;
401 if (pi > 0 && pi < npoints - 1) {
402 if (slope[pi] == slope[pi-1]) {
404 /* no reason to display this point */
406 continue;
410 /* need to round here. the ultimate coordinates are integer
411 pixels, so tiny deltas in the coords will be eliminated
412 and we end up with "colinear" line segments. since the
413 line rendering code in libart doesn't like this very
414 much, we eliminate them here. don't do this for the first and last
415 points.
418 this_rx = (uint32_t) rint (tx);
419 this_ry = (uint32_t) rint (ty);
421 if (view_index && pi != npoints && /* not the first, not the last */
422 (((this_rx == prev_rx) && (this_ry == prev_ry)) || /* same point */
423 (((this_rx - prev_rx) < (box_size + 2)) && /* not identical, but still too close horizontally */
424 (abs ((int)(this_ry - prev_ry)) < (int) (box_size + 2))))) { /* too close vertically */
425 continue;
428 /* ok, we should display this point */
430 add_visible_control_point (view_index, pi, tx, ty, model, npoints);
432 prev_rx = this_rx;
433 prev_ry = this_ry;
435 view_index++;
438 /* discard extra CP's to avoid confusing ourselves */
440 while (control_points.size() > view_index) {
441 ControlPoint* cp = control_points.back();
442 control_points.pop_back ();
443 delete cp;
446 if (!terminal_points_can_slide) {
447 control_points.back()->set_can_slide(false);
450 delete [] slope;
452 if (view_index > 1) {
454 npoints = view_index;
456 /* reset the line coordinates */
458 while (line_points.size() < npoints) {
459 line_points.push_back (Art::Point (0,0));
462 while (line_points.size() > npoints) {
463 line_points.pop_back ();
466 for (view_index = 0; view_index < npoints; ++view_index) {
467 line_points[view_index].set_x (control_points[view_index]->get_x());
468 line_points[view_index].set_y (control_points[view_index]->get_y());
471 line->property_points() = line_points;
473 if (_visible && _interpolation != AutomationList::Discrete) {
474 line->show();
479 set_selected_points (trackview.editor().get_selection().points);
482 string
483 AutomationLine::get_verbose_cursor_string (double fraction) const
485 std::string s = fraction_to_string (fraction);
486 if (_uses_gain_mapping) {
487 s += " dB";
490 return s;
494 * @param fraction y fraction
495 * @return string representation of this value, using dB if appropriate.
497 string
498 AutomationLine::fraction_to_string (double fraction) const
500 char buf[32];
502 if (_uses_gain_mapping) {
503 if (fraction == 0.0) {
504 snprintf (buf, sizeof (buf), "-inf");
505 } else {
506 snprintf (buf, sizeof (buf), "%.1f", accurate_coefficient_to_dB (slider_position_to_gain (fraction)));
508 } else {
509 double dummy = 0.0;
510 view_to_model_coord (dummy, fraction);
511 if (EventTypeMap::instance().is_integer (alist->parameter())) {
512 snprintf (buf, sizeof (buf), "%d", (int)fraction);
513 } else {
514 snprintf (buf, sizeof (buf), "%.2f", fraction);
518 return buf;
523 * @param s Value string in the form as returned by fraction_to_string.
524 * @return Corresponding y fraction.
526 double
527 AutomationLine::string_to_fraction (string const & s) const
529 if (s == "-inf") {
530 return 0;
533 double v;
534 sscanf (s.c_str(), "%lf", &v);
536 if (_uses_gain_mapping) {
537 v = gain_to_slider_position (dB_to_coefficient (v));
538 } else {
539 double dummy = 0.0;
540 model_to_view_coord (dummy, v);
543 return v;
546 bool
547 AutomationLine::invalid_point (ALPoints& p, uint32_t index)
549 return p[index].x == max_frames && p[index].y == DBL_MAX;
552 void
553 AutomationLine::invalidate_point (ALPoints& p, uint32_t index)
555 p[index].x = max_frames;
556 p[index].y = DBL_MAX;
559 /** Start dragging a single point, possibly adding others if the supplied point is selected and there
560 * are other selected points.
562 * @param cp Point to drag.
563 * @param x Initial x position (units).
564 * @param fraction Initial y position (as a fraction of the track height, where 0 is the bottom and 1 the top)
566 void
567 AutomationLine::start_drag_single (ControlPoint* cp, double x, float fraction)
569 trackview.editor().session()->begin_reversible_command (_("automation event move"));
570 trackview.editor().session()->add_command (new MementoCommand<AutomationList>(*alist.get(), &get_state(), 0));
572 _drag_points.clear ();
573 _drag_points.push_back (cp);
575 if (cp->selected ()) {
576 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
577 if (*i != cp && (*i)->selected()) {
578 _drag_points.push_back (*i);
583 start_drag_common (x, fraction);
586 /** Start dragging a line vertically (with no change in x)
587 * @param i1 Control point index of the `left' point on the line.
588 * @param i2 Control point index of the `right' point on the line.
589 * @param fraction Initial y position (as a fraction of the track height, where 0 is the bottom and 1 the top)
591 void
592 AutomationLine::start_drag_line (uint32_t i1, uint32_t i2, float fraction)
594 trackview.editor().session()->begin_reversible_command (_("automation range move"));
595 trackview.editor().session()->add_command (new MementoCommand<AutomationList>(*alist.get(), &get_state(), 0));
597 _drag_points.clear ();
598 for (uint32_t i = i1; i <= i2; i++) {
599 _drag_points.push_back (nth (i));
602 start_drag_common (0, fraction);
605 /** Start dragging multiple points (with no change in x)
606 * @param cp Points to drag.
607 * @param fraction Initial y position (as a fraction of the track height, where 0 is the bottom and 1 the top)
609 void
610 AutomationLine::start_drag_multiple (list<ControlPoint*> cp, float fraction, XMLNode* state)
612 trackview.editor().session()->begin_reversible_command (_("automation range move"));
613 trackview.editor().session()->add_command (new MementoCommand<AutomationList>(*alist.get(), state, 0));
615 _drag_points = cp;
616 start_drag_common (0, fraction);
620 struct ControlPointSorter
622 bool operator() (ControlPoint const * a, ControlPoint const * b) {
623 return a->get_x() < b->get_x();
627 /** Common parts of starting a drag.
628 * @param x Starting x position in units, or 0 if x is being ignored.
629 * @param fraction Starting y position (as a fraction of the track height, where 0 is the bottom and 1 the top)
631 void
632 AutomationLine::start_drag_common (double x, float fraction)
634 _drag_x = x;
635 _drag_distance = 0;
636 _last_drag_fraction = fraction;
637 _drag_had_movement = false;
638 did_push = false;
640 _drag_points.sort (ControlPointSorter ());
642 /* find the additional points that will be dragged when the user is holding
643 the "push" modifier
646 uint32_t i = _drag_points.back()->view_index () + 1;
647 ControlPoint* p = 0;
648 _push_points.clear ();
649 while ((p = nth (i)) != 0 && p->can_slide()) {
650 _push_points.push_back (p);
651 ++i;
655 /** Should be called to indicate motion during a drag.
656 * @param x New x position of the drag in units, or undefined if ignore_x == true.
657 * @param fraction New y fraction.
658 * @return x position and y fraction that were actually used (once clamped).
660 pair<double, float>
661 AutomationLine::drag_motion (double x, float fraction, bool ignore_x, bool with_push)
663 /* setup the points that are to be moved this time round */
664 list<ControlPoint*> points = _drag_points;
665 if (with_push) {
666 copy (_push_points.begin(), _push_points.end(), back_inserter (points));
667 points.sort (ControlPointSorter ());
670 double dx = ignore_x ? 0 : (x - _drag_x);
671 double dy = fraction - _last_drag_fraction;
673 /* find x limits */
674 ControlPoint* before = 0;
675 ControlPoint* after = 0;
677 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
678 if ((*i)->get_x() < points.front()->get_x()) {
679 before = *i;
681 if ((*i)->get_x() > points.back()->get_x() && after == 0) {
682 after = *i;
686 double const before_x = before ? before->get_x() : 0;
687 double const after_x = after ? after->get_x() : DBL_MAX;
689 /* clamp x */
690 for (list<ControlPoint*>::iterator i = points.begin(); i != points.end(); ++i) {
692 if ((*i)->can_slide() && !ignore_x) {
694 /* clamp min x */
695 double const a = (*i)->get_x() + dx;
696 double const b = before_x + 1;
697 if (a < b) {
698 dx += b - a;
701 /* clamp max x */
702 if (after) {
704 if (after_x - before_x < 2) {
705 /* after and before are very close, so just leave this alone */
706 dx = 0;
707 } else {
708 double const a = (*i)->get_x() + dx;
709 double const b = after_x - 1;
710 if (a > b) {
711 dx -= a - b;
718 /* clamp y */
719 for (list<ControlPoint*>::iterator i = points.begin(); i != points.end(); ++i) {
720 double const y = ((_height - (*i)->get_y()) / _height) + dy;
721 if (y < 0) {
722 dy -= y;
724 if (y > 1) {
725 dy -= (y - 1);
729 pair<double, float> const clamped (_drag_x + dx, _last_drag_fraction + dy);
730 _drag_distance += dx;
731 _drag_x = x;
732 _last_drag_fraction = fraction;
734 for (list<ControlPoint*>::iterator i = _drag_points.begin(); i != _drag_points.end(); ++i) {
735 (*i)->move_to ((*i)->get_x() + dx, (*i)->get_y() - _height * dy, ControlPoint::Full);
736 reset_line_coords (**i);
739 if (with_push) {
740 /* move push points, preserving their y */
741 for (list<ControlPoint*>::iterator i = _push_points.begin(); i != _push_points.end(); ++i) {
742 (*i)->move_to ((*i)->get_x() + dx, (*i)->get_y(), ControlPoint::Full);
743 reset_line_coords (**i);
747 if (line_points.size() > 1) {
748 line->property_points() = line_points;
751 _drag_had_movement = true;
752 did_push = with_push;
754 return clamped;
757 /** Should be called to indicate the end of a drag */
758 void
759 AutomationLine::end_drag ()
761 if (!_drag_had_movement) {
762 return;
765 alist->freeze ();
767 /* set up the points that were moved this time round */
768 list<ControlPoint*> points = _drag_points;
769 if (did_push) {
770 copy (_push_points.begin(), _push_points.end(), back_inserter (points));
771 points.sort (ControlPointSorter ());
774 sync_model_with_view_points (points, did_push, rint (_drag_distance * trackview.editor().get_current_zoom ()));
776 alist->thaw ();
778 update_pending = false;
780 trackview.editor().session()->add_command (new MementoCommand<AutomationList>(*alist.get(), 0, &alist->get_state()));
781 trackview.editor().session()->commit_reversible_command ();
782 trackview.editor().session()->set_dirty ();
785 void
786 AutomationLine::sync_model_with_view_point (ControlPoint& cp, bool did_push, int64_t distance)
788 ModelRepresentation mr;
789 double ydelta;
791 model_representation (cp, mr);
793 /* how much are we changing the central point by */
795 ydelta = mr.yval - mr.ypos;
798 apply the full change to the central point, and interpolate
799 on both axes to cover all model points represented
800 by the control point.
803 /* change all points before the primary point */
805 for (AutomationList::iterator i = mr.start; i != cp.model(); ++i) {
807 double fract = ((*i)->when - mr.xmin) / (mr.xpos - mr.xmin);
808 double y_delta = ydelta * fract;
809 double x_delta = distance * fract;
811 /* interpolate */
813 if (y_delta || x_delta) {
814 alist->modify (i, (*i)->when + x_delta, mr.ymin + y_delta);
818 /* change the primary point */
820 update_pending = true;
821 alist->modify (cp.model(), mr.xval, mr.yval);
824 /* change later points */
826 AutomationList::iterator i = cp.model();
828 ++i;
830 while (i != mr.end) {
832 double delta = ydelta * (mr.xmax - (*i)->when) / (mr.xmax - mr.xpos);
834 /* all later points move by the same distance along the x-axis as the main point */
836 if (delta) {
837 alist->modify (i, (*i)->when + distance, (*i)->value + delta);
840 ++i;
843 if (did_push) {
845 /* move all points after the range represented by the view by the same distance
846 as the main point moved.
849 alist->slide (mr.end, distance);
853 bool
854 AutomationLine::control_points_adjacent (double xval, uint32_t & before, uint32_t& after)
856 ControlPoint *bcp = 0;
857 ControlPoint *acp = 0;
858 double unit_xval;
860 unit_xval = trackview.editor().frame_to_unit (xval);
862 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
864 if ((*i)->get_x() <= unit_xval) {
866 if (!bcp || (*i)->get_x() > bcp->get_x()) {
867 bcp = *i;
868 before = bcp->view_index();
871 } else if ((*i)->get_x() > unit_xval) {
872 acp = *i;
873 after = acp->view_index();
874 break;
878 return bcp && acp;
881 bool
882 AutomationLine::is_last_point (ControlPoint& cp)
884 ModelRepresentation mr;
886 model_representation (cp, mr);
888 // If the list is not empty, and the point is the last point in the list
890 if (!alist->empty() && mr.end == alist->end()) {
891 return true;
894 return false;
897 bool
898 AutomationLine::is_first_point (ControlPoint& cp)
900 ModelRepresentation mr;
902 model_representation (cp, mr);
904 // If the list is not empty, and the point is the first point in the list
906 if (!alist->empty() && mr.start == alist->begin()) {
907 return true;
910 return false;
913 // This is copied into AudioRegionGainLine
914 void
915 AutomationLine::remove_point (ControlPoint& cp)
917 ModelRepresentation mr;
919 model_representation (cp, mr);
921 trackview.editor().session()->begin_reversible_command (_("remove control point"));
922 XMLNode &before = alist->get_state();
924 alist->erase (mr.start, mr.end);
926 trackview.editor().session()->add_command(new MementoCommand<AutomationList>(
927 *alist.get(), &before, &alist->get_state()));
928 trackview.editor().session()->commit_reversible_command ();
929 trackview.editor().session()->set_dirty ();
932 void
933 AutomationLine::get_selectables (nframes_t& start, nframes_t& end,
934 double botfrac, double topfrac, list<Selectable*>& results)
937 double top;
938 double bot;
939 double nstart;
940 double nend;
941 bool collecting = false;
943 /* Curse X11 and its inverted coordinate system! */
945 bot = (1.0 - topfrac) * _height;
946 top = (1.0 - botfrac) * _height;
948 nstart = max_frames;
949 nend = 0;
951 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
952 double when = (*(*i)->model())->when;
954 if (when >= start && when <= end) {
956 if ((*i)->get_y() >= bot && (*i)->get_y() <= top) {
958 (*i)->show();
959 (*i)->set_visible(true);
960 collecting = true;
961 nstart = min (nstart, when);
962 nend = max (nend, when);
964 } else {
966 if (collecting) {
968 results.push_back (new AutomationSelectable (nstart, nend, botfrac, topfrac, &trackview));
969 collecting = false;
970 nstart = max_frames;
971 nend = 0;
977 if (collecting) {
978 results.push_back (new AutomationSelectable (nstart, nend, botfrac, topfrac, &trackview));
983 void
984 AutomationLine::get_inverted_selectables (Selection&, list<Selectable*>& /*results*/)
986 // hmmm ....
989 /** Take a PointSelection and find ControlPoints that fall within it */
990 list<ControlPoint*>
991 AutomationLine::point_selection_to_control_points (PointSelection const & s)
993 list<ControlPoint*> cp;
995 for (PointSelection::const_iterator i = s.begin(); i != s.end(); ++i) {
997 if (i->track != &trackview) {
998 continue;
1001 /* Curse X11 and its inverted coordinate system! */
1003 double const bot = (1.0 - i->high_fract) * _height;
1004 double const top = (1.0 - i->low_fract) * _height;
1006 for (vector<ControlPoint*>::iterator j = control_points.begin(); j != control_points.end(); ++j) {
1008 double const rstart = trackview.editor().frame_to_unit (i->start);
1009 double const rend = trackview.editor().frame_to_unit (i->end);
1011 if ((*j)->get_x() >= rstart && (*j)->get_x() <= rend) {
1012 if ((*j)->get_y() >= bot && (*j)->get_y() <= top) {
1013 cp.push_back (*j);
1020 return cp;
1023 void
1024 AutomationLine::set_selected_points (PointSelection& points)
1026 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1027 (*i)->set_selected (false);
1030 if (!points.empty()) {
1031 list<ControlPoint*> cp = point_selection_to_control_points (points);
1032 for (list<ControlPoint*>::iterator i = cp.begin(); i != cp.end(); ++i) {
1033 (*i)->set_selected (true);
1037 set_colors ();
1040 void AutomationLine::set_colors ()
1042 set_line_color (ARDOUR_UI::config()->canvasvar_AutomationLine.get());
1043 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1044 (*i)->set_color ();
1048 void
1049 AutomationLine::list_changed ()
1051 queue_reset ();
1054 void
1055 AutomationLine::reset_callback (const Evoral::ControlList& events)
1057 ALPoints tmp_points;
1058 uint32_t npoints = events.size();
1060 if (npoints == 0) {
1061 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1062 delete *i;
1064 control_points.clear ();
1065 line->hide();
1066 return;
1069 AutomationList::const_iterator ai;
1071 for (ai = events.begin(); ai != events.end(); ++ai) {
1073 double translated_x = (*ai)->when;
1074 double translated_y = (*ai)->value;
1075 model_to_view_coord (translated_x, translated_y);
1077 add_model_point (tmp_points, (*ai)->when, translated_y);
1080 determine_visible_control_points (tmp_points);
1084 void
1085 AutomationLine::add_model_point (ALPoints& tmp_points, double frame, double yfract)
1087 tmp_points.push_back (ALPoint (trackview.editor().frame_to_unit (_time_converter.to(frame)),
1088 _height - (yfract * _height)));
1091 void
1092 AutomationLine::reset ()
1094 update_pending = false;
1096 if (no_draw) {
1097 return;
1100 alist->apply_to_points (*this, &AutomationLine::reset_callback);
1103 void
1104 AutomationLine::clear ()
1106 /* parent must create command */
1107 XMLNode &before = alist->get_state();
1108 alist->clear();
1109 trackview.editor().session()->add_command (new MementoCommand<AutomationList>(*(alist.get()), &before, &alist->get_state()));
1110 trackview.editor().session()->commit_reversible_command ();
1111 trackview.editor().session()->set_dirty ();
1114 void
1115 AutomationLine::change_model (AutomationList::iterator /*i*/, double /*x*/, double /*y*/)
1119 void
1120 AutomationLine::set_list(boost::shared_ptr<ARDOUR::AutomationList> list)
1122 alist = list;
1123 queue_reset();
1126 void
1127 AutomationLine::show_all_control_points ()
1129 points_visible = true;
1131 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1132 if (!(*i)->visible()) {
1133 (*i)->show ();
1134 (*i)->set_visible (true);
1139 void
1140 AutomationLine::hide_all_but_selected_control_points ()
1142 if (alist->interpolation() == AutomationList::Discrete) {
1143 return;
1146 points_visible = false;
1148 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1149 if (!(*i)->selected()) {
1150 (*i)->set_visible (false);
1155 void
1156 AutomationLine::track_entered()
1158 if (alist->interpolation() != AutomationList::Discrete) {
1159 show_all_control_points();
1163 void
1164 AutomationLine::track_exited()
1166 if (alist->interpolation() != AutomationList::Discrete) {
1167 hide_all_but_selected_control_points();
1171 XMLNode &
1172 AutomationLine::get_state (void)
1174 /* function as a proxy for the model */
1175 return alist->get_state();
1179 AutomationLine::set_state (const XMLNode &node, int version)
1181 /* function as a proxy for the model */
1182 return alist->set_state (node, version);
1185 void
1186 AutomationLine::view_to_model_coord (double& x, double& y) const
1188 /* TODO: This should be more generic ... */
1189 if (alist->parameter().type() == GainAutomation ||
1190 alist->parameter().type() == EnvelopeAutomation) {
1191 y = slider_position_to_gain (y);
1192 y = max (0.0, y);
1193 y = min (2.0, y);
1194 } else if (alist->parameter().type() == PanAutomation) {
1195 // vertical coordinate axis reversal
1196 y = 1.0 - y;
1197 } else if (alist->parameter().type() == PluginAutomation) {
1198 y = y * (double)(alist->get_max_y()- alist->get_min_y()) + alist->get_min_y();
1199 } else {
1200 y = (int)(y * alist->parameter().max());
1203 x = _time_converter.from(x);
1206 void
1207 AutomationLine::model_to_view_coord (double& x, double& y) const
1209 /* TODO: This should be more generic ... */
1210 if (alist->parameter().type() == GainAutomation ||
1211 alist->parameter().type() == EnvelopeAutomation) {
1212 y = gain_to_slider_position (y);
1213 } else if (alist->parameter().type() == PanAutomation) {
1214 // vertical coordinate axis reversal
1215 y = 1.0 - y;
1216 } else if (alist->parameter().type() == PluginAutomation) {
1217 y = (y - alist->get_min_y()) / (double)(alist->get_max_y()- alist->get_min_y());
1218 } else {
1219 y = y / (double)alist->parameter().max(); /* ... like this */
1222 x = _time_converter.to(x);
1226 void
1227 AutomationLine::set_interpolation(AutomationList::InterpolationStyle style)
1229 _interpolation = style;
1231 if (style == AutomationList::Discrete) {
1232 show_all_control_points();
1233 line->hide();
1234 } else {
1235 hide_all_but_selected_control_points();
1236 line->show();
1240 void
1241 AutomationLine::add_visible_control_point (uint32_t view_index, uint32_t pi, double tx, double ty, AutomationList::iterator model, uint32_t npoints)
1243 if (view_index >= control_points.size()) {
1245 /* make sure we have enough control points */
1247 ControlPoint* ncp = new ControlPoint (*this);
1248 ncp->set_size (control_point_box_size ());
1250 control_points.push_back (ncp);
1253 ControlPoint::ShapeType shape;
1255 if (!terminal_points_can_slide) {
1256 if (pi == 0) {
1257 control_points[view_index]->set_can_slide(false);
1258 if (tx == 0) {
1259 shape = ControlPoint::Start;
1260 } else {
1261 shape = ControlPoint::Full;
1263 } else if (pi == npoints - 1) {
1264 control_points[view_index]->set_can_slide(false);
1265 shape = ControlPoint::End;
1266 } else {
1267 control_points[view_index]->set_can_slide(true);
1268 shape = ControlPoint::Full;
1270 } else {
1271 control_points[view_index]->set_can_slide(true);
1272 shape = ControlPoint::Full;
1275 control_points[view_index]->reset (tx, ty, model, view_index, shape);
1277 /* finally, control visibility */
1279 if (_visible && points_visible) {
1280 control_points[view_index]->show ();
1281 control_points[view_index]->set_visible (true);
1282 } else {
1283 if (!points_visible) {
1284 control_points[view_index]->set_visible (false);
1289 void
1290 AutomationLine::add_always_in_view (double x)
1292 _always_in_view.push_back (x);
1293 alist->apply_to_points (*this, &AutomationLine::reset_callback);
1296 void
1297 AutomationLine::clear_always_in_view ()
1299 _always_in_view.clear ();
1300 alist->apply_to_points (*this, &AutomationLine::reset_callback);