X-Git-Url: https://main.carlh.net/gitweb/?a=blobdiff_plain;f=gtk2_ardour%2Fautomation_line.cc;h=7f9bd6b45551666bd2f68417ea561bd3cacd608b;hb=f6fdd8dcbf41f864e9f0cc32dabe81fe3533ddfe;hp=b87a71ca875aa1699dc861659856f08371a4ec0b;hpb=fe13d08874f08b723df53116e5655c3d229a657e;p=ardour.git diff --git a/gtk2_ardour/automation_line.cc b/gtk2_ardour/automation_line.cc index b87a71ca87..7f9bd6b455 100644 --- a/gtk2_ardour/automation_line.cc +++ b/gtk2_ardour/automation_line.cc @@ -1,5 +1,5 @@ /* - Copyright (C) 2002-2003 Paul Davis + Copyright (C) 2002-2003 Paul Davis This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -15,21 +15,24 @@ along with this program; if not, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. - $Id$ */ #include #include #include +#include -#include +#include "pbd/stl_delete.h" +#include "pbd/memento_command.h" +#include "pbd/stacktrace.h" -#include -#include -#include +#include "ardour/automation_list.h" +#include "ardour/dB.h" +#include "evoral/Curve.hpp" #include "simplerect.h" #include "automation_line.h" +#include "control_point.h" #include "rgb_macros.h" #include "ardour_ui.h" #include "public_editor.h" @@ -41,192 +44,32 @@ #include "automation_time_axis.h" #include "public_editor.h" -#include +#include "ardour/event_type_map.h" +#include "ardour/session.h" #include "i18n.h" using namespace std; -using namespace sigc; using namespace ARDOUR; using namespace PBD; using namespace Editing; using namespace Gnome; // for Canvas -ControlPoint::ControlPoint (AutomationLine& al) - : line (al) -{ - model = al.the_list().end(); - view_index = 0; - can_slide = true; - _x = 0; - _y = 0; - _shape = Full; - _size = 4.0; - selected = false; - - item = new Canvas::SimpleRect (line.canvas_group()); - item->property_draw() = true; - item->property_fill() = false; - item->property_fill_color_rgba() = color_map[cControlPointFill]; - item->property_outline_color_rgba() = color_map[cControlPointOutline]; - item->property_outline_pixels() = 1; - item->set_data ("control_point", this); - item->signal_event().connect (mem_fun (this, &ControlPoint::event_handler)); - - hide (); - set_visible (false); -} - -ControlPoint::ControlPoint (const ControlPoint& other, bool dummy_arg_to_force_special_copy_constructor) - : line (other.line) -{ - if (&other == this) { - return; - } - - model = other.model; - view_index = other.view_index; - can_slide = other.can_slide; - _x = other._x; - _y = other._y; - _shape = other._shape; - _size = other._size; - selected = false; - - item = new Canvas::SimpleRect (line.canvas_group()); - item->property_fill() = false; - item->property_outline_color_rgba() = color_map[cEnteredControlPointOutline]; - item->property_outline_pixels() = 1; - - /* NOTE: no event handling in copied ControlPoints */ - - hide (); - set_visible (false); -} - -ControlPoint::~ControlPoint () -{ - delete item; -} - -bool -ControlPoint::event_handler (GdkEvent* event) -{ - return PublicEditor::instance().canvas_control_point_event (event, item, this); -} - -void -ControlPoint::hide () -{ - item->hide(); -} - -void -ControlPoint::show() -{ - item->show(); -} - -void -ControlPoint::set_visible (bool yn) -{ - item->property_draw() = (gboolean) yn; -} - -void -ControlPoint::reset (double x, double y, AutomationList::iterator mi, uint32_t vi, ShapeType shape) -{ - model = mi; - view_index = vi; - move_to (x, y, shape); -} +static const Evoral::IdentityConverter default_converter; -void -ControlPoint::show_color (bool entered, bool hide_too) -{ - if (entered) { - if (selected) { - item->property_outline_color_rgba() = color_map[cEnteredControlPointSelected]; - set_visible(true); - } else { - item->property_outline_color_rgba() = color_map[cEnteredControlPoint]; - if (hide_too) { - set_visible(false); - } - } - - } else { - if (selected) { - item->property_outline_color_rgba() = color_map[cControlPointSelected]; - set_visible(true); - } else { - item->property_outline_color_rgba() = color_map[cControlPoint]; - if (hide_too) { - set_visible(false); - } - } - } -} - -void -ControlPoint::set_size (double sz) -{ - _size = sz; - -#if 0 - if (_size > 6.0) { - item->property_fill() = (gboolean) TRUE; - } else { - item->property_fill() = (gboolean) FALSE; - } -#endif - - move_to (_x, _y, _shape); -} - -void -ControlPoint::move_to (double x, double y, ShapeType shape) -{ - double x1 = 0; - double x2 = 0; - double half_size = rint(_size/2.0); - - switch (shape) { - case Full: - x1 = x - half_size; - x2 = x + half_size; - break; - case Start: - x1 = x; - x2 = x + half_size; - break; - case End: - x1 = x - half_size; - x2 = x; - break; - } - - item->property_x1() = x1; - item->property_x2() = x2; - item->property_y1() = y - half_size; - item->property_y2() = y + half_size; - - _x = x; - _y = y; - _shape = shape; -} - -/*****/ - -AutomationLine::AutomationLine (const string & name, TimeAxisView& tv, ArdourCanvas::Group& parent, AutomationList& al) - : trackview (tv), - _name (name), - alist (al), - _parent_group (parent) +AutomationLine::AutomationLine (const string& name, TimeAxisView& tv, ArdourCanvas::Group& parent, + boost::shared_ptr al, + const Evoral::TimeConverter* converter) + : trackview (tv) + , _name (name) + , alist (al) + , _parent_group (parent) + , _time_converter (converter ? (*converter) : default_converter) { + _interpolation = al->interpolation(); points_visible = false; update_pending = false; - _vc_uses_gain_mapping = false; + _uses_gain_mapping = false; no_draw = false; _visible = true; terminal_points_can_slide = true; @@ -240,9 +83,18 @@ AutomationLine::AutomationLine (const string & name, TimeAxisView& tv, ArdourCan line->property_width_pixels() = (guint)1; line->set_data ("line", this); - line->signal_event().connect (mem_fun (*this, &AutomationLine::event_handler)); + line->signal_event().connect (sigc::mem_fun (*this, &AutomationLine::event_handler)); - alist.StateChanged.connect (mem_fun(*this, &AutomationLine::list_changed)); + alist->StateChanged.connect (sigc::mem_fun(*this, &AutomationLine::list_changed)); + + trackview.session()->register_with_memento_command_factory(alist->id(), this); + + if (alist->parameter().type() == GainAutomation || + alist->parameter().type() == EnvelopeAutomation) { + set_uses_gain_mapping (true); + } + + set_interpolation(alist->interpolation()); } AutomationLine::~AutomationLine () @@ -262,22 +114,16 @@ AutomationLine::queue_reset () { if (!update_pending) { update_pending = true; - Gtkmm2ext::UI::instance()->call_slot (mem_fun(*this, &AutomationLine::reset)); + Gtkmm2ext::UI::instance()->call_slot (boost::bind (&AutomationLine::reset, this)); } } void -AutomationLine::set_point_size (double sz) +AutomationLine::show () { - for (vector::iterator i = control_points.begin(); i != control_points.end(); ++i) { - (*i)->set_size (sz); + if (_interpolation != AutomationList::Discrete) { + line->show(); } -} - -void -AutomationLine::show () -{ - line->show(); if (points_visible) { for (vector::iterator i = control_points.begin(); i != control_points.end(); ++i) { @@ -289,7 +135,7 @@ AutomationLine::show () } void -AutomationLine::hide () +AutomationLine::hide () { line->hide(); for (vector::iterator i = control_points.begin(); i != control_points.end(); ++i) { @@ -298,18 +144,32 @@ AutomationLine::hide () _visible = false; } +double +AutomationLine::control_point_box_size () +{ + if (_interpolation == AutomationList::Discrete) { + return max((_height*4.0) / (double)(alist->parameter().max() - alist->parameter().min()), + 4.0); + } + + if (_height > TimeAxisView::hLarger) { + return 8.0; + } else if (_height > (guint32) TimeAxisView::hNormal) { + return 6.0; + } + return 4.0; +} + void AutomationLine::set_height (guint32 h) { if (h != _height) { _height = h; - if (_height > (guint32) TimeAxisView::Larger) { - set_point_size (8.0); - } else if (_height > (guint32) TimeAxisView::Normal) { - set_point_size (6.0); - } else { - set_point_size (4.0); + double bsz = control_point_box_size(); + + for (vector::iterator i = control_points.begin(); i != control_points.end(); ++i) { + (*i)->set_size (bsz); } reset (); @@ -324,10 +184,10 @@ AutomationLine::set_line_color (uint32_t color) } void -AutomationLine::set_verbose_cursor_uses_gain_mapping (bool yn) +AutomationLine::set_uses_gain_mapping (bool yn) { - if (yn != _vc_uses_gain_mapping) { - _vc_uses_gain_mapping = yn; + if (yn != _uses_gain_mapping) { + _uses_gain_mapping = yn; reset (); } } @@ -343,12 +203,40 @@ AutomationLine::nth (uint32_t n) } void -AutomationLine::modify_view (ControlPoint& cp, double x, double y, bool with_push) +AutomationLine::modify_point_y (ControlPoint& cp, double y) { - modify_view_point (cp, x, y, with_push); - update_line (); + /* clamp y-coord appropriately. y is supposed to be a normalized fraction (0.0-1.0), + and needs to be converted to a canvas unit distance. + */ + + y = max (0.0, y); + y = min (1.0, y); + y = _height - (y * _height); + + double const x = trackview.editor().frame_to_unit (_time_converter.to((*cp.model())->when)); + + trackview.editor().session()->begin_reversible_command (_("automation event move")); + trackview.editor().session()->add_command (new MementoCommand(*alist.get(), &get_state(), 0)); + + cp.move_to (x, y, ControlPoint::Full); + reset_line_coords (cp); + + if (line_points.size() > 1) { + line->property_points() = line_points; + } + + alist->freeze (); + sync_model_with_view_point (cp, false, 0); + alist->thaw (); + + update_pending = false; + + trackview.editor().session()->add_command (new MementoCommand(*alist.get(), 0, &alist->get_state())); + trackview.editor().session()->commit_reversible_command (); + trackview.editor().session()->set_dirty (); } + void AutomationLine::modify_view_point (ControlPoint& cp, double x, double y, bool with_push) { @@ -368,21 +256,21 @@ AutomationLine::modify_view_point (ControlPoint& cp, double x, double y, bool wi y = min (1.0, y); y = _height - (y * _height); - if (cp.can_slide) { + if (cp.can_slide()) { /* x-coord cannot move beyond adjacent points or the start/end, and is already in frames. it needs to be converted to canvas units. */ - - x = trackview.editor.frame_to_unit (x); + + x = trackview.editor().frame_to_unit (x); /* clamp x position using view coordinates */ ControlPoint *before; ControlPoint *after; - if (cp.view_index) { - before = nth (cp.view_index - 1); + if (cp.view_index()) { + before = nth (cp.view_index() - 1); x = max (x, before->get_x()+1.0); } else { before = &cp; @@ -390,15 +278,15 @@ AutomationLine::modify_view_point (ControlPoint& cp, double x, double y, bool wi if (!with_push) { - if (cp.view_index < control_points.size() - 1) { - - after = nth (cp.view_index + 1); - + if (cp.view_index() < control_points.size() - 1) { + + after = nth (cp.view_index() + 1); + /*if it is a "spike" leave the x alone */ - + if (after->get_x() - before->get_x() < 2) { x = cp.get_x(); - + } else { x = min (x, after->get_x()-1.0); } @@ -409,25 +297,25 @@ AutomationLine::modify_view_point (ControlPoint& cp, double x, double y, bool wi } else { ControlPoint* after; - + /* find the first point that can't move */ - - for (uint32_t n = cp.view_index + 1; (after = nth (n)) != 0; ++n) { - if (!after->can_slide) { + + for (uint32_t n = cp.view_index() + 1; (after = nth (n)) != 0; ++n) { + if (!after->can_slide()) { x_limit = after->get_x() - 1.0; - last_movable = after->view_index; + last_movable = after->view_index(); break; } } - + delta = x - cp.get_x(); } - + } else { /* leave the x-coordinate alone */ - x = trackview.editor.frame_to_unit ((*cp.model)->when); + x = trackview.editor().frame_to_unit (_time_converter.to((*cp.model())->when)); } @@ -439,22 +327,22 @@ AutomationLine::modify_view_point (ControlPoint& cp, double x, double y, bool wi } else { uint32_t limit = min (control_points.size(), (size_t)last_movable); - + /* move the current point to wherever the user told it to go, subject to x_limit. */ - + cp.move_to (min (x, x_limit), y, ControlPoint::Full); reset_line_coords (cp); - + /* now move all subsequent control points, to reflect the motion. */ - - for (uint32_t i = cp.view_index + 1; i < limit; ++i) { + + for (uint32_t i = cp.view_index() + 1; i < limit; ++i) { ControlPoint *p = nth (i); double new_x; - if (p->can_slide) { + if (p->can_slide()) { new_x = min (p->get_x() + delta, x_limit); p->move_to (new_x, p->get_y(), ControlPoint::Full); reset_line_coords (*p); @@ -465,31 +353,24 @@ AutomationLine::modify_view_point (ControlPoint& cp, double x, double y, bool wi void AutomationLine::reset_line_coords (ControlPoint& cp) -{ - line_points[cp.view_index].set_x (cp.get_x()); - line_points[cp.view_index].set_y (cp.get_y()); -} - -void -AutomationLine::update_line () { - line->property_points() = line_points; + if (cp.view_index() < line_points.size()) { + line_points[cp.view_index()].set_x (cp.get_x()); + line_points[cp.view_index()].set_y (cp.get_y()); + } } void AutomationLine::sync_model_with_view_line (uint32_t start, uint32_t end) { - ControlPoint *p; update_pending = true; for (uint32_t i = start; i <= end; ++i) { p = nth(i); - sync_model_with_view_point(*p); + sync_model_with_view_point (*p, false, 0); } - - } void @@ -499,31 +380,28 @@ AutomationLine::model_representation (ControlPoint& cp, ModelRepresentation& mr) initial results are in canvas units. ask the line to convert them to something relevant. */ - - mr.xval = (jack_nframes_t) floor (cp.get_x()); - mr.yval = 1.0 - (cp.get_y() / _height); + mr.xval = cp.get_x(); + mr.yval = 1.0 - (cp.get_y() / _height); - /* if xval has not changed, set it directly from the model to avoid rounding errors */ + /* if xval has not changed, set it directly from the model to avoid rounding errors */ - if (mr.xval == trackview.editor.frame_to_unit((*cp.model)->when)) { - mr.xval = (jack_nframes_t) (*cp.model)->when; + if (mr.xval == trackview.editor().frame_to_unit(_time_converter.to((*cp.model())->when))) { + mr.xval = (*cp.model())->when; } else { - mr.xval = trackview.editor.unit_to_frame (mr.xval); + mr.xval = trackview.editor().unit_to_frame (mr.xval); } - - /* virtual call: this will do the right thing - for whatever particular type of line we are. + /* convert to model units */ - - view_to_model_y (mr.yval); + + view_to_model_coord (mr.xval, mr.yval); /* part 2: find out where the model point is now */ - mr.xpos = (jack_nframes_t) (*cp.model)->when; - mr.ypos = (*cp.model)->value; + mr.xpos = (*cp.model())->when; + mr.ypos = (*cp.model())->value; /* part 3: get the position of the visual control points before and after us. @@ -532,127 +410,35 @@ AutomationLine::model_representation (ControlPoint& cp, ModelRepresentation& mr) ControlPoint* before; ControlPoint* after; - if (cp.view_index) { - before = nth (cp.view_index - 1); + if (cp.view_index()) { + before = nth (cp.view_index() - 1); } else { before = 0; } - after = nth (cp.view_index + 1); + after = nth (cp.view_index() + 1); if (before) { - mr.xmin = (jack_nframes_t) (*before->model)->when; - mr.ymin = (*before->model)->value; - mr.start = before->model; + mr.xmin = (*before->model())->when; + mr.ymin = (*before->model())->value; + mr.start = before->model(); ++mr.start; } else { mr.xmin = mr.xpos; mr.ymin = mr.ypos; - mr.start = cp.model; + mr.start = cp.model(); } if (after) { - mr.end = after->model; + mr.end = after->model(); } else { mr.xmax = mr.xpos; mr.ymax = mr.ypos; - mr.end = cp.model; + mr.end = cp.model(); ++mr.end; } } -void -AutomationLine::sync_model_from (ControlPoint& cp) -{ - ControlPoint* p; - uint32_t lasti; - - sync_model_with_view_point (cp); - - /* we might have moved all points after `cp' by some amount - if we pressed the with_push modifyer some of the time during the drag - so all subsequent points have to be resynced - */ - - lasti = control_points.size() - 1; - p = nth (lasti); - - update_pending = true; - - while (p != &cp && lasti) { - sync_model_with_view_point (*p); - --lasti; - p = nth (lasti); - } -} - -void -AutomationLine::sync_model_with_view_point (ControlPoint& cp) -{ - ModelRepresentation mr; - double ydelta; - - model_representation (cp, mr); - - /* part 4: how much are we changing the central point by */ - - ydelta = mr.yval - mr.ypos; - - /* IMPORTANT: changing the model when the x-coordinate changes - may invalidate the iterators that we are using. this means that we have - to change the points before+after the one corresponding to the visual CP - first (their x-coordinate doesn't change). then we change the - "main" point. - - apply the full change to the central point, and interpolate - in each direction to cover all model points represented - by the control point. - */ - - /* part 5: change all points before the primary point */ - - for (AutomationList::iterator i = mr.start; i != cp.model; ++i) { - - double delta; - - delta = ydelta * ((*i)->when - mr.xmin) / (mr.xpos - mr.xmin); - - /* x-coordinate (generally time) stays where it is, - y-coordinate moves by a certain amount. - */ - - update_pending = true; - change_model (i, (*i)->when, mr.yval + delta); - } - - /* part 6: change later points */ - - AutomationList::iterator i = cp.model; - - ++i; - - while (i != mr.end) { - - double delta; - - delta = ydelta * (mr.xmax - (*i)->when) / (mr.xmax - mr.xpos); - - /* x-coordinate (generally time) stays where it is, - y-coordinate moves by a certain amount. - */ - - update_pending = true; - change_model (i, (*i)->when, (*i)->value + delta); - - ++i; - } - - /* part 7: change the primary point */ - - update_pending = true; - change_model (cp.model, mr.xval, mr.yval); -} - void AutomationLine::determine_visible_control_points (ALPoints& points) { @@ -664,13 +450,18 @@ AutomationLine::determine_visible_control_points (ALPoints& points) uint32_t this_rx = 0; uint32_t prev_rx = 0; uint32_t this_ry = 0; - uint32_t prev_ry = 0; + uint32_t prev_ry = 0; double* slope; + uint32_t box_size; + uint32_t cpsize; /* hide all existing points, and the line */ - + + cpsize = 0; + for (vector::iterator i = control_points.begin(); i != control_points.end(); ++i) { (*i)->hide(); + ++cpsize; } line->hide (); @@ -691,15 +482,23 @@ AutomationLine::determine_visible_control_points (ALPoints& points) slope[n] = ydelta/xdelta; } + box_size = (uint32_t) control_point_box_size (); + /* read all points and decide which ones to show as control points */ view_index = 0; - for (model = alist.begin(), pi = 0; pi < npoints; ++model, ++pi) { + for (model = alist->begin(), pi = 0; pi < npoints; ++model, ++pi) { double tx = points[pi].x; double ty = points[pi].y; + if (isnan (tx) || isnan (ty)) { + warning << string_compose (_("Ignoring illegal points on AutomationLine \"%1\""), + _name) << endmsg; + continue; + } + /* now ensure that the control_points vector reflects the current curve state, but don't plot control points too close together. also, don't plot a series of points all with the same value. @@ -718,11 +517,11 @@ AutomationLine::determine_visible_control_points (ALPoints& points) if (slope[pi] == slope[pi-1]) { /* no reason to display this point */ - + continue; } } - + /* need to round here. the ultimate coordinates are integer pixels, so tiny deltas in the coords will be eliminated and we end up with "colinear" line segments. since the @@ -732,50 +531,48 @@ AutomationLine::determine_visible_control_points (ALPoints& points) */ this_rx = (uint32_t) rint (tx); - this_ry = (unsigned long) rint (ty); - - if (view_index && pi != npoints && (this_rx == prev_rx) && (this_ry == prev_ry)) { - - continue; - } + this_ry = (uint32_t) rint (ty); + + if (view_index && pi != npoints && /* not the first, not the last */ + (((this_rx == prev_rx) && (this_ry == prev_ry)) || /* same point */ + (((this_rx - prev_rx) < (box_size + 2)) && /* not identical, but still too close horizontally */ + (abs ((int)(this_ry - prev_ry)) < (int) (box_size + 2))))) { /* too close vertically */ + continue; + } /* ok, we should display this point */ - if (view_index >= control_points.size()) { + if (view_index >= cpsize) { + /* make sure we have enough control points */ ControlPoint* ncp = new ControlPoint (*this); - if (_height > (guint32) TimeAxisView::Larger) { - ncp->set_size (8.0); - } else if (_height > (guint32) TimeAxisView::Normal) { - ncp->set_size (6.0); - } else { - ncp->set_size (4.0); - } + ncp->set_size (box_size); control_points.push_back (ncp); + ++cpsize; } ControlPoint::ShapeType shape; if (!terminal_points_can_slide) { if (pi == 0) { - control_points[view_index]->can_slide = false; + control_points[view_index]->set_can_slide(false); if (tx == 0) { shape = ControlPoint::Start; } else { shape = ControlPoint::Full; } } else if (pi == npoints - 1) { - control_points[view_index]->can_slide = false; + control_points[view_index]->set_can_slide(false); shape = ControlPoint::End; } else { - control_points[view_index]->can_slide = true; + control_points[view_index]->set_can_slide(true); shape = ControlPoint::Full; } } else { - control_points[view_index]->can_slide = true; + control_points[view_index]->set_can_slide(true); shape = ControlPoint::Full; } @@ -788,7 +585,7 @@ AutomationLine::determine_visible_control_points (ALPoints& points) prev_ry = this_ry; /* finally, control visibility */ - + if (_visible && points_visible) { control_points[view_index]->show (); control_points[view_index]->set_visible (true); @@ -810,7 +607,7 @@ AutomationLine::determine_visible_control_points (ALPoints& points) } if (!terminal_points_can_slide) { - control_points.back()->can_slide = false; + control_points.back()->set_can_slide(false); } delete [] slope; @@ -818,46 +615,98 @@ AutomationLine::determine_visible_control_points (ALPoints& points) if (view_index > 1) { npoints = view_index; - + /* reset the line coordinates */ while (line_points.size() < npoints) { line_points.push_back (Art::Point (0,0)); } + while (line_points.size() > npoints) { + line_points.pop_back (); + } + for (view_index = 0; view_index < npoints; ++view_index) { line_points[view_index].set_x (control_points[view_index]->get_x()); line_points[view_index].set_y (control_points[view_index]->get_y()); } - + line->property_points() = line_points; - if (_visible) { - line->show (); + if (_visible && _interpolation != AutomationList::Discrete) { + line->show(); } - } - set_selected_points (trackview.editor.get_selection().points); + } + + set_selected_points (trackview.editor().get_selection().points); + +} + +string +AutomationLine::get_verbose_cursor_string (double fraction) const +{ + std::string s = fraction_to_string (fraction); + if (_uses_gain_mapping) { + s += " dB"; + } + + return s; } +/** + * @param fraction y fraction + * @return string representation of this value, using dB if appropriate. + */ string -AutomationLine::get_verbose_cursor_string (float fraction) +AutomationLine::fraction_to_string (double fraction) const { char buf[32]; - if (_vc_uses_gain_mapping) { + if (_uses_gain_mapping) { if (fraction == 0.0) { - snprintf (buf, sizeof (buf), "-inf dB"); + snprintf (buf, sizeof (buf), "-inf"); } else { - snprintf (buf, sizeof (buf), "%.1fdB", coefficient_to_dB (slider_position_to_gain (fraction))); + snprintf (buf, sizeof (buf), "%.1f", accurate_coefficient_to_dB (slider_position_to_gain (fraction))); } } else { - snprintf (buf, sizeof (buf), "%.2f", fraction); + double dummy = 0.0; + view_to_model_coord (dummy, fraction); + if (EventTypeMap::instance().is_integer (alist->parameter())) { + snprintf (buf, sizeof (buf), "%d", (int)fraction); + } else { + snprintf (buf, sizeof (buf), "%.2f", fraction); + } } return buf; } + +/** + * @param s Value string in the form as returned by fraction_to_string. + * @return Corresponding y fraction. + */ +double +AutomationLine::string_to_fraction (string const & s) const +{ + if (s == "-inf") { + return 0; + } + + double v; + sscanf (s.c_str(), "%lf", &v); + + if (_uses_gain_mapping) { + v = gain_to_slider_position (dB_to_coefficient (v)); + } else { + double dummy = 0.0; + model_to_view_coord (dummy, v); + } + + return v; +} + bool AutomationLine::invalid_point (ALPoints& p, uint32_t index) { @@ -872,9 +721,9 @@ AutomationLine::invalidate_point (ALPoints& p, uint32_t index) } void -AutomationLine::start_drag (ControlPoint* cp, float fraction) +AutomationLine::start_drag (ControlPoint* cp, nframes_t x, float fraction) { - if (trackview.editor.current_session() == 0) { /* how? */ + if (trackview.editor().session() == 0) { /* how? */ return; } @@ -886,85 +735,197 @@ AutomationLine::start_drag (ControlPoint* cp, float fraction) str = _("automation range drag"); } - trackview.editor.current_session()->begin_reversible_command (str); - trackview.editor.current_session()->add_undo (get_memento()); - + trackview.editor().session()->begin_reversible_command (str); + trackview.editor().session()->add_command (new MementoCommand(*alist.get(), &get_state(), 0)); + + drag_x = x; + drag_distance = 0; first_drag_fraction = fraction; last_drag_fraction = fraction; drags = 0; + did_push = false; } void -AutomationLine::point_drag (ControlPoint& cp, jack_nframes_t x, float fraction, bool with_push) +AutomationLine::point_drag (ControlPoint& cp, nframes_t x, float fraction, bool with_push) { - modify_view (cp, x, fraction, with_push); + if (x > drag_x) { + drag_distance += (x - drag_x); + } else { + drag_distance -= (drag_x - x); + } + + drag_x = x; + + modify_view_point (cp, x, fraction, with_push); + + if (line_points.size() > 1) { + line->property_points() = line_points; + } + drags++; + did_push = with_push; } void -AutomationLine::line_drag (uint32_t i1, uint32_t i2, float fraction, bool with_push) +AutomationLine::line_drag (uint32_t i1, uint32_t i2, float fraction, bool with_push) { double ydelta = fraction - last_drag_fraction; - + + did_push = with_push; + last_drag_fraction = fraction; line_drag_cp1 = i1; line_drag_cp2 = i2; - + + //check if one of the control points on the line is in a selected range + bool range_found = false; ControlPoint *cp; for (uint32_t i = i1 ; i <= i2; i++) { cp = nth (i); - modify_view_point (*cp, trackview.editor.unit_to_frame (cp->get_x()), ((_height - cp->get_y()) /_height) + ydelta, with_push); + if (cp->selected()) { + range_found = true; + } } - update_line (); + if (range_found) { + for (vector::iterator i = control_points.begin(); i != control_points.end(); ++i) { + if ((*i)->selected()) { + modify_view_point (*(*i), trackview.editor().unit_to_frame ((*i)->get_x()), ((_height - (*i)->get_y()) /_height) + ydelta, with_push); + } + } + } else { + ControlPoint *cp; + for (uint32_t i = i1 ; i <= i2; i++) { + cp = nth (i); + modify_view_point (*cp, trackview.editor().unit_to_frame (cp->get_x()), ((_height - cp->get_y()) /_height) + ydelta, with_push); + } + } + + if (line_points.size() > 1) { + line->property_points() = line_points; + } drags++; } void -AutomationLine::end_drag (ControlPoint* cp) +AutomationLine::end_drag (ControlPoint* cp) { - if (drags) { + if (!drags) { + return; + } - if (cp) { - sync_model_from (*cp); - } else { - sync_model_with_view_line (line_drag_cp1, line_drag_cp2); + alist->freeze (); + + if (cp) { + sync_model_with_view_point (*cp, did_push, drag_distance); + } else { + sync_model_with_view_line (line_drag_cp1, line_drag_cp2); + } + + alist->thaw (); + + update_pending = false; + + trackview.editor().session()->add_command (new MementoCommand(*alist.get(), 0, &alist->get_state())); + trackview.editor().session()->commit_reversible_command (); + trackview.editor().session()->set_dirty (); +} + + +void +AutomationLine::sync_model_with_view_point (ControlPoint& cp, bool did_push, int64_t distance) +{ + ModelRepresentation mr; + double ydelta; + + model_representation (cp, mr); + + /* how much are we changing the central point by */ + + ydelta = mr.yval - mr.ypos; + + /* + apply the full change to the central point, and interpolate + on both axes to cover all model points represented + by the control point. + */ + + /* change all points before the primary point */ + + for (AutomationList::iterator i = mr.start; i != cp.model(); ++i) { + + double fract = ((*i)->when - mr.xmin) / (mr.xpos - mr.xmin); + double y_delta = ydelta * fract; + double x_delta = distance * fract; + + /* interpolate */ + + if (y_delta || x_delta) { + alist->modify (i, (*i)->when + x_delta, mr.ymin + y_delta); + } + } + + /* change the primary point */ + + update_pending = true; + alist->modify (cp.model(), mr.xval, mr.yval); + + + /* change later points */ + + AutomationList::iterator i = cp.model(); + + ++i; + + while (i != mr.end) { + + double delta = ydelta * (mr.xmax - (*i)->when) / (mr.xmax - mr.xpos); + + /* all later points move by the same distance along the x-axis as the main point */ + + if (delta) { + alist->modify (i, (*i)->when + distance, (*i)->value + delta); } - update_pending = false; + ++i; + } + + if (did_push) { - trackview.editor.current_session()->add_redo_no_execute (get_memento()); - trackview.editor.current_session()->commit_reversible_command (); - trackview.editor.current_session()->set_dirty (); + /* move all points after the range represented by the view by the same distance + as the main point moved. + */ + + alist->slide (mr.end, drag_distance); } + } -bool +bool AutomationLine::control_points_adjacent (double xval, uint32_t & before, uint32_t& after) { ControlPoint *bcp = 0; ControlPoint *acp = 0; double unit_xval; - /* xval is in frames */ - - unit_xval = trackview.editor.frame_to_unit (xval); + unit_xval = trackview.editor().frame_to_unit (xval); for (vector::iterator i = control_points.begin(); i != control_points.end(); ++i) { - + if ((*i)->get_x() <= unit_xval) { if (!bcp || (*i)->get_x() > bcp->get_x()) { bcp = *i; - before = bcp->view_index; - } + before = bcp->view_index(); + } } else if ((*i)->get_x() > unit_xval) { acp = *i; - after = acp->view_index; + after = acp->view_index(); break; } } @@ -981,10 +942,10 @@ AutomationLine::is_last_point (ControlPoint& cp) // If the list is not empty, and the point is the last point in the list - if (!alist.empty() && mr.end == alist.end()) { + if (!alist->empty() && mr.end == alist->end()) { return true; } - + return false; } @@ -997,10 +958,10 @@ AutomationLine::is_first_point (ControlPoint& cp) // If the list is not empty, and the point is the first point in the list - if (!alist.empty() && mr.start == alist.begin()) { + if (!alist->empty() && mr.start == alist->begin()) { return true; } - + return false; } @@ -1012,41 +973,41 @@ AutomationLine::remove_point (ControlPoint& cp) model_representation (cp, mr); - trackview.editor.current_session()->begin_reversible_command (_("remove control point")); - trackview.editor.current_session()->add_undo (get_memento()); + trackview.editor().session()->begin_reversible_command (_("remove control point")); + XMLNode &before = alist->get_state(); - alist.erase (mr.start, mr.end); + alist->erase (mr.start, mr.end); - trackview.editor.current_session()->add_redo_no_execute (get_memento()); - trackview.editor.current_session()->commit_reversible_command (); - trackview.editor.current_session()->set_dirty (); + trackview.editor().session()->add_command(new MementoCommand( + *alist.get(), &before, &alist->get_state())); + trackview.editor().session()->commit_reversible_command (); + trackview.editor().session()->set_dirty (); } void -AutomationLine::get_selectables (jack_nframes_t& start, jack_nframes_t& end, - double botfrac, double topfrac, list& results) +AutomationLine::get_selectables (nframes_t& start, nframes_t& end, + double botfrac, double topfrac, list& results) { double top; double bot; - jack_nframes_t nstart; - jack_nframes_t nend; + double nstart; + double nend; bool collecting = false; /* Curse X11 and its inverted coordinate system! */ - + bot = (1.0 - topfrac) * _height; top = (1.0 - botfrac) * _height; - + nstart = max_frames; nend = 0; for (vector::iterator i = control_points.begin(); i != control_points.end(); ++i) { - - jack_nframes_t when = (jack_nframes_t) (*(*i)->model)->when; + double when = (*(*i)->model())->when; if (when >= start && when <= end) { - + if ((*i)->get_y() >= bot && (*i)->get_y() <= top) { (*i)->show(); @@ -1056,10 +1017,10 @@ AutomationLine::get_selectables (jack_nframes_t& start, jack_nframes_t& end, nend = max (nend, when); } else { - + if (collecting) { - results.push_back (new AutomationSelectable (nstart, nend, botfrac, topfrac, trackview)); + results.push_back (new AutomationSelectable (nstart, nend, botfrac, topfrac, &trackview)); collecting = false; nstart = max_frames; nend = 0; @@ -1069,13 +1030,13 @@ AutomationLine::get_selectables (jack_nframes_t& start, jack_nframes_t& end, } if (collecting) { - results.push_back (new AutomationSelectable (nstart, nend, botfrac, topfrac, trackview)); + results.push_back (new AutomationSelectable (nstart, nend, botfrac, topfrac, &trackview)); } } void -AutomationLine::get_inverted_selectables (Selection&, list& results) +AutomationLine::get_inverted_selectables (Selection&, list& /*results*/) { // hmmm .... } @@ -1087,16 +1048,16 @@ AutomationLine::set_selected_points (PointSelection& points) double bot; for (vector::iterator i = control_points.begin(); i != control_points.end(); ++i) { - (*i)->selected = false; + (*i)->set_selected(false); } if (points.empty()) { goto out; - } - + } + for (PointSelection::iterator r = points.begin(); r != points.end(); ++r) { - - if (&(*r).track != &trackview) { + + if ((*r).track != &trackview) { continue; } @@ -1106,17 +1067,17 @@ AutomationLine::set_selected_points (PointSelection& points) top = (1.0 - (*r).low_fract) * _height; for (vector::iterator i = control_points.begin(); i != control_points.end(); ++i) { - + double rstart, rend; - - rstart = trackview.editor.frame_to_unit ((*r).start); - rend = trackview.editor.frame_to_unit ((*r).end); - + + rstart = trackview.editor().frame_to_unit ((*r).start); + rend = trackview.editor().frame_to_unit ((*r).end); + if ((*i)->get_x() >= rstart && (*i)->get_x() <= rend) { - + if ((*i)->get_y() >= bot && (*i)->get_y() <= top) { - - (*i)->selected = true; + + (*i)->set_selected(true); } } @@ -1130,29 +1091,34 @@ AutomationLine::set_selected_points (PointSelection& points) } +void AutomationLine::set_colors() { + set_line_color( ARDOUR_UI::config()->canvasvar_AutomationLine.get() ); + for (vector::iterator i = control_points.begin(); i != control_points.end(); ++i) { + (*i)->show_color (false, !points_visible); + } +} + void AutomationLine::show_selection () { - TimeSelection& time (trackview.editor.get_selection().time); - - // cerr << "show selection\n"; + TimeSelection& time (trackview.editor().get_selection().time); for (vector::iterator i = control_points.begin(); i != control_points.end(); ++i) { - - (*i)->selected = false; + + (*i)->set_selected(false); for (list::iterator r = time.begin(); r != time.end(); ++r) { double rstart, rend; - - rstart = trackview.editor.frame_to_unit ((*r).start); - rend = trackview.editor.frame_to_unit ((*r).end); - + + rstart = trackview.editor().frame_to_unit ((*r).start); + rend = trackview.editor().frame_to_unit ((*r).end); + if ((*i)->get_x() >= rstart && (*i)->get_x() <= rend) { - (*i)->selected = true; + (*i)->set_selected(true); break; } } - + (*i)->show_color (false, !points_visible); } } @@ -1160,28 +1126,19 @@ AutomationLine::show_selection () void AutomationLine::hide_selection () { - // cerr << "hide selection\n"; // show_selection (); } - -// This is copied into AudioRegionGainLine -UndoAction -AutomationLine::get_memento () -{ - return alist.get_memento(); -} - void -AutomationLine::list_changed (Change ignored) +AutomationLine::list_changed () { queue_reset (); } void -AutomationLine::reset_callback (const AutomationList& events) +AutomationLine::reset_callback (const Evoral::ControlList& events) { - ALPoints tmp_points; + ALPoints tmp_points; uint32_t npoints = events.size(); if (npoints == 0) { @@ -1195,20 +1152,26 @@ AutomationLine::reset_callback (const AutomationList& events) AutomationList::const_iterator ai; - for (ai = events.const_begin(); ai != events.const_end(); ++ai) { + for (ai = events.begin(); ai != events.end(); ++ai) { - double translated_y; - - translated_y = (*ai)->value; - model_to_view_y (translated_y); + double translated_x = (*ai)->when; + double translated_y = (*ai)->value; + model_to_view_coord (translated_x, translated_y); - tmp_points.push_back (ALPoint (trackview.editor.frame_to_unit ((*ai)->when), - _height - (translated_y * _height))); + add_model_point (tmp_points, (*ai)->when, translated_y); } - + determine_visible_control_points (tmp_points); } + +void +AutomationLine::add_model_point (ALPoints& tmp_points, double frame, double yfract) +{ + tmp_points.push_back (ALPoint (trackview.editor().frame_to_unit (_time_converter.to(frame)), + _height - (yfract * _height))); +} + void AutomationLine::reset () { @@ -1218,30 +1181,31 @@ AutomationLine::reset () return; } - alist.apply_to_points (*this, &AutomationLine::reset_callback); + alist->apply_to_points (*this, &AutomationLine::reset_callback); } void AutomationLine::clear () { /* parent must create command */ - trackview.editor.current_session()->add_undo (get_memento()); - alist.clear(); - trackview.editor.current_session()->add_redo_no_execute (get_memento()); - trackview.editor.current_session()->commit_reversible_command (); - trackview.editor.current_session()->set_dirty (); + XMLNode &before = get_state(); + alist->clear(); + trackview.editor().session()->add_command ( + new MementoCommand(*this, &before, &get_state())); + trackview.editor().session()->commit_reversible_command (); + trackview.editor().session()->set_dirty (); } void -AutomationLine::change_model (AutomationList::iterator i, double x, double y) +AutomationLine::change_model (AutomationList::iterator /*i*/, double /*x*/, double /*y*/) { - alist.modify (i, (jack_nframes_t) x, y); } void -AutomationLine::change_model_range (AutomationList::iterator start, AutomationList::iterator end, double xdelta, float ydelta) +AutomationLine::set_list(boost::shared_ptr list) { - alist.move_range (start, end, xdelta, ydelta); + alist = list; + queue_reset(); } void @@ -1250,6 +1214,7 @@ AutomationLine::show_all_control_points () points_visible = true; for (vector::iterator i = control_points.begin(); i != control_points.end(); ++i) { + (*i)->show_color((_interpolation != AutomationList::Discrete), false); (*i)->show (); (*i)->set_visible (true); } @@ -1258,11 +1223,101 @@ AutomationLine::show_all_control_points () void AutomationLine::hide_all_but_selected_control_points () { + if (alist->interpolation() == AutomationList::Discrete) { + return; + } + points_visible = false; for (vector::iterator i = control_points.begin(); i != control_points.end(); ++i) { - if (!(*i)->selected) { + if (!(*i)->selected()) { (*i)->set_visible (false); } } } + +void +AutomationLine::track_entered() +{ + if (alist->interpolation() != AutomationList::Discrete) { + show_all_control_points(); + } +} + +void +AutomationLine::track_exited() +{ + if (alist->interpolation() != AutomationList::Discrete) { + hide_all_but_selected_control_points(); + } +} + +XMLNode & +AutomationLine::get_state (void) +{ + /* function as a proxy for the model */ + return alist->get_state(); +} + +int +AutomationLine::set_state (const XMLNode &node, int version) +{ + /* function as a proxy for the model */ + return alist->set_state (node, version); +} + +void +AutomationLine::view_to_model_coord (double& x, double& y) const +{ + /* TODO: This should be more generic ... */ + if (alist->parameter().type() == GainAutomation || + alist->parameter().type() == EnvelopeAutomation) { + y = slider_position_to_gain (y); + y = max (0.0, y); + y = min (2.0, y); + } else if (alist->parameter().type() == PanAutomation) { + // vertical coordinate axis reversal + y = 1.0 - y; + } else if (alist->parameter().type() == PluginAutomation) { + y = y * (double)(alist->get_max_y()- alist->get_min_y()) + alist->get_min_y(); + } else { + y = (int)(y * alist->parameter().max()); + } + + x = _time_converter.from(x); +} + +void +AutomationLine::model_to_view_coord (double& x, double& y) const +{ + /* TODO: This should be more generic ... */ + if (alist->parameter().type() == GainAutomation || + alist->parameter().type() == EnvelopeAutomation) { + y = gain_to_slider_position (y); + } else if (alist->parameter().type() == PanAutomation) { + // vertical coordinate axis reversal + y = 1.0 - y; + } else if (alist->parameter().type() == PluginAutomation) { + y = (y - alist->get_min_y()) / (double)(alist->get_max_y()- alist->get_min_y()); + } else { + y = y / (double)alist->parameter().max(); /* ... like this */ + } + + x = _time_converter.to(x); +} + + +void +AutomationLine::set_interpolation(AutomationList::InterpolationStyle style) +{ + _interpolation = style; + + if (style == AutomationList::Discrete) { + show_all_control_points(); + line->hide(); + } else { + hide_all_but_selected_control_points(); + line->show(); + } +} +