X-Git-Url: https://main.carlh.net/gitweb/?a=blobdiff_plain;f=gtk2_ardour%2Fautomation_line.cc;h=92ea8cf952850b6f2a72190ba2f10bd46a038468;hb=0f1f7ca4fdaa0a186c37592ed429388a5060f624;hp=45b9573f71b388a74c320c0e81f13e38060fb6f1;hpb=15b5fce90480490455237da917167b0bcb5ce946;p=ardour.git diff --git a/gtk2_ardour/automation_line.cc b/gtk2_ardour/automation_line.cc index 45b9573f71..92ea8cf952 100644 --- a/gtk2_ardour/automation_line.cc +++ b/gtk2_ardour/automation_line.cc @@ -22,12 +22,17 @@ #include #include -#include "pbd/stl_delete.h" +#include "boost/shared_ptr.hpp" + +#include "pbd/floating.h" #include "pbd/memento_command.h" +#include "pbd/stl_delete.h" #include "pbd/stacktrace.h" #include "ardour/automation_list.h" #include "ardour/dB.h" +#include "ardour/debug.h" + #include "evoral/Curve.hpp" #include "simplerect.h" @@ -42,7 +47,6 @@ #include "time_axis_view.h" #include "point_selection.h" #include "automation_time_axis.h" -#include "public_editor.h" #include "ardour/event_type_map.h" #include "ardour/session.h" @@ -55,24 +59,34 @@ using namespace PBD; using namespace Editing; using namespace Gnome; // for Canvas -static const Evoral::IdentityConverter default_converter; - +/** @param converter A TimeConverter whose origin_b is the start time of the AutomationList in session frames. + * This will not be deleted by AutomationLine. + */ AutomationLine::AutomationLine (const string& name, TimeAxisView& tv, ArdourCanvas::Group& parent, boost::shared_ptr al, - const Evoral::TimeConverter* converter) + Evoral::TimeConverter* converter) : trackview (tv) , _name (name) , alist (al) + , _time_converter (converter ? converter : new Evoral::IdentityConverter) , _parent_group (parent) , _offset (0) - , _time_converter (converter ? (*converter) : default_converter) , _maximum_time (max_framepos) { - points_visible = false; + if (converter) { + _time_converter = converter; + _our_time_converter = false; + } else { + _time_converter = new Evoral::IdentityConverter; + _our_time_converter = true; + } + + _visible = Line; + update_pending = false; + have_timeout = false; _uses_gain_mapping = false; no_draw = false; - _visible = true; _is_boolean = false; terminal_points_can_slide = true; _height = 0; @@ -87,8 +101,6 @@ AutomationLine::AutomationLine (const string& name, TimeAxisView& tv, ArdourCanv line->signal_event().connect (sigc::mem_fun (*this, &AutomationLine::event_handler)); - connect_to_list (); - trackview.session()->register_with_memento_command_factory(alist->id(), this); if (alist->parameter().type() == GainAutomation || @@ -105,6 +117,10 @@ AutomationLine::~AutomationLine () { vector_delete (&control_points); delete group; + + if (_our_time_converter) { + delete _time_converter; + } } bool @@ -113,39 +129,43 @@ AutomationLine::event_handler (GdkEvent* event) return PublicEditor::instance().canvas_line_event (event, line, this); } -void -AutomationLine::queue_reset () -{ - if (!update_pending) { - update_pending = true; - Gtkmm2ext::UI::instance()->call_slot (invalidator (*this), boost::bind (&AutomationLine::reset, this)); - } -} - void AutomationLine::show () { - if (alist->interpolation() != AutomationList::Discrete) { - line->show(); + if (_visible & Line) { + /* Only show the line there are some points, otherwise we may show an out-of-date line + when automation points have been removed (the line will still follow the shape of the + old points). + */ + if (alist->interpolation() != AutomationList::Discrete && control_points.size() >= 2) { + line->show(); + } else { + line->hide (); + } + } else { + line->hide(); } - if (points_visible) { + if (_visible & ControlPoints) { for (vector::iterator i = control_points.begin(); i != control_points.end(); ++i) { + (*i)->set_visible (true); (*i)->show (); } + } else if (_visible & SelectedControlPoints) { + for (vector::iterator i = control_points.begin(); i != control_points.end(); ++i) { + (*i)->set_visible ((*i)->get_selected()); + } + } else { + for (vector::iterator i = control_points.begin(); i != control_points.end(); ++i) { + (*i)->set_visible (false); + } } - - _visible = true; } void AutomationLine::hide () { - line->hide(); - for (vector::iterator i = control_points.begin(); i != control_points.end(); ++i) { - (*i)->hide(); - } - _visible = false; + set_visibility (VisibleAspects (0)); } double @@ -227,7 +247,7 @@ AutomationLine::modify_point_y (ControlPoint& cp, double y) y = min (1.0, y); y = _height - (y * _height); - double const x = trackview.editor().frame_to_unit (_time_converter.to((*cp.model())->when) - _offset); + double const x = trackview.editor().frame_to_unit_unrounded (_time_converter->to((*cp.model())->when) - _offset); trackview.editor().session()->begin_reversible_command (_("automation event move")); trackview.editor().session()->add_command ( @@ -243,7 +263,7 @@ AutomationLine::modify_point_y (ControlPoint& cp, double y) } alist->freeze (); - sync_model_with_view_point (cp, false, 0); + sync_model_with_view_point (cp); alist->thaw (); update_pending = false; @@ -251,7 +271,7 @@ AutomationLine::modify_point_y (ControlPoint& cp, double y) trackview.editor().session()->add_command ( new MementoCommand (memento_command_binder(), 0, &alist->get_state()) ); - + trackview.editor().session()->commit_reversible_command (); trackview.editor().session()->set_dirty (); } @@ -266,245 +286,46 @@ AutomationLine::reset_line_coords (ControlPoint& cp) } void -AutomationLine::sync_model_with_view_points (list cp, bool did_push, int64_t distance) +AutomationLine::sync_model_with_view_points (list cp) { update_pending = true; for (list::iterator i = cp.begin(); i != cp.end(); ++i) { - sync_model_with_view_point (**i, did_push, distance); + sync_model_with_view_point (**i); } } -void -AutomationLine::model_representation (ControlPoint& cp, ModelRepresentation& mr) +string +AutomationLine::get_verbose_cursor_string (double fraction) const { - /* part one: find out where the visual control point is. - initial results are in canvas units. ask the - line to convert them to something relevant. - */ - - 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 (mr.xval == trackview.editor().frame_to_unit(_time_converter.to((*cp.model())->when)) - _offset) { - mr.xval = (*cp.model())->when - _offset; - } else { - mr.xval = trackview.editor().unit_to_frame (mr.xval); - mr.xval = _time_converter.from (mr.xval + _offset); - } - - /* convert y to model units; the x was already done above - */ - - view_to_model_coord_y (mr.yval); - - /* part 2: find out where the model point is now - */ - - mr.xpos = (*cp.model())->when - _offset; - mr.ypos = (*cp.model())->value; - - /* part 3: get the position of the visual control - points before and after us. - */ - - ControlPoint* before; - ControlPoint* after; - - if (cp.view_index()) { - before = nth (cp.view_index() - 1); - } else { - before = 0; - } - - after = nth (cp.view_index() + 1); - - if (before) { - 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(); + std::string s = fraction_to_string (fraction); + if (_uses_gain_mapping) { + s += " dB"; } - if (after) { - mr.end = after->model(); - } else { - mr.xmax = mr.xpos; - mr.ymax = mr.ypos; - mr.end = cp.model(); - ++mr.end; - } + return s; } -void -AutomationLine::determine_visible_control_points (ALPoints& points) -{ - uint32_t view_index, pi, n; - AutomationList::iterator model; - uint32_t npoints; - uint32_t this_rx = 0; - uint32_t prev_rx = 0; - uint32_t this_ry = 0; - uint32_t prev_ry = 0; - double* slope; - uint32_t box_size; - - /* hide all existing points, and the line */ - - for (vector::iterator i = control_points.begin(); i != control_points.end(); ++i) { - (*i)->hide(); - } - - line->hide (); - - if (points.empty()) { - return; - } - - npoints = points.size(); - - /* compute derivative/slope for the entire line */ - - slope = new double[npoints]; - - for (n = 0; n < npoints - 1; ++n) { - double xdelta = points[n+1].x - points[n].x; - double ydelta = points[n+1].y - points[n].y; - 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) { - - double tx = points[pi].x; - double ty = points[pi].y; - - if (find (_always_in_view.begin(), _always_in_view.end(), (*model)->when) != _always_in_view.end()) { - add_visible_control_point (view_index, pi, tx, ty, model, npoints); - prev_rx = this_rx; - prev_ry = this_ry; - ++view_index; - continue; - } - - 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. - - always plot the first and last points, of course. - */ - - if (invalid_point (points, pi)) { - /* for some reason, we are supposed to ignore this point, - but still keep track of the model index. - */ - continue; - } - - if (pi > 0 && pi < npoints - 1) { - 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 - line rendering code in libart doesn't like this very - much, we eliminate them here. don't do this for the first and last - points. - */ - - this_rx = (uint32_t) rint (tx); - 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 */ - - add_visible_control_point (view_index, pi, tx, ty, model, npoints); - - prev_rx = this_rx; - prev_ry = this_ry; - - view_index++; - } - - /* discard extra CP's to avoid confusing ourselves */ - - while (control_points.size() > view_index) { - ControlPoint* cp = control_points.back(); - control_points.pop_back (); - delete cp; - } - - if (!terminal_points_can_slide) { - control_points.back()->set_can_slide(false); +string +AutomationLine::get_verbose_cursor_relative_string (double original, double fraction) const +{ + std::string s = fraction_to_string (fraction); + if (_uses_gain_mapping) { + s += " dB"; } - delete [] slope; - - if (view_index > 1) { - - npoints = view_index; - - /* reset the line coordinates */ - - while (line_points.size() < npoints) { - line_points.push_back (Art::Point (0,0)); - } + std::string d = fraction_to_relative_string (original, fraction); - while (line_points.size() > npoints) { - line_points.pop_back (); - } + if (!d.empty()) { - 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; + s += " (\u0394"; + s += d; - if (_visible && alist->interpolation() != AutomationList::Discrete) { - line->show(); + if (_uses_gain_mapping) { + s += " dB"; } - } - - 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"; + s += ')'; } return s; @@ -521,13 +342,12 @@ AutomationLine::fraction_to_string (double fraction) const if (_uses_gain_mapping) { if (fraction == 0.0) { - snprintf (buf, sizeof (buf), "-inf"); + snprintf (buf, sizeof (buf), "-\u221e"); } else { - snprintf (buf, sizeof (buf), "%.1f", accurate_coefficient_to_dB (slider_position_to_gain (fraction))); + snprintf (buf, sizeof (buf), "%.1f", accurate_coefficient_to_dB (slider_position_to_gain_with_max (fraction, Config->get_max_gain()))); } } else { - double dummy = 0.0; - view_to_model_coord (dummy, fraction); + view_to_model_coord_y (fraction); if (EventTypeMap::instance().is_integer (alist->parameter())) { snprintf (buf, sizeof (buf), "%d", (int)fraction); } else { @@ -538,6 +358,45 @@ AutomationLine::fraction_to_string (double fraction) const return buf; } +/** + * @param original an old y-axis fraction + * @param fraction the new y fraction + * @return string representation of the difference between original and fraction, using dB if appropriate. + */ +string +AutomationLine::fraction_to_relative_string (double original, double fraction) const +{ + char buf[32]; + + if (original == fraction) { + return "0"; + } + + if (_uses_gain_mapping) { + if (original == 0.0) { + /* there is no sensible representation of a relative + change from -inf dB, so return an empty string. + */ + return ""; + } else if (fraction == 0.0) { + snprintf (buf, sizeof (buf), "-\u221e"); + } else { + double old_db = accurate_coefficient_to_dB (slider_position_to_gain_with_max (original, Config->get_max_gain())); + double new_db = accurate_coefficient_to_dB (slider_position_to_gain_with_max (fraction, Config->get_max_gain())); + snprintf (buf, sizeof (buf), "%.1f", new_db - old_db); + } + } else { + view_to_model_coord_y (original); + view_to_model_coord_y (fraction); + if (EventTypeMap::instance().is_integer (alist->parameter())) { + snprintf (buf, sizeof (buf), "%d", (int)fraction - (int)original); + } else { + snprintf (buf, sizeof (buf), "%.2f", fraction - original); + } + } + + return buf; +} /** * @param s Value string in the form as returned by fraction_to_string. @@ -546,7 +405,7 @@ AutomationLine::fraction_to_string (double fraction) const double AutomationLine::string_to_fraction (string const & s) const { - if (s == "-inf") { + if (s == "-\u221e") { return 0; } @@ -554,7 +413,7 @@ AutomationLine::string_to_fraction (string const & s) const sscanf (s.c_str(), "%lf", &v); if (_uses_gain_mapping) { - v = gain_to_slider_position (dB_to_coefficient (v)); + v = gain_to_slider_position_with_max (dB_to_coefficient (v), Config->get_max_gain()); } else { double dummy = 0.0; model_to_view_coord (dummy, v); @@ -563,19 +422,6 @@ AutomationLine::string_to_fraction (string const & s) const return v; } -bool -AutomationLine::invalid_point (ALPoints& p, uint32_t index) -{ - return p[index].x == max_framepos && p[index].y == DBL_MAX; -} - -void -AutomationLine::invalidate_point (ALPoints& p, uint32_t index) -{ - p[index].x = max_framepos; - p[index].y = DBL_MAX; -} - /** Start dragging a single point, possibly adding others if the supplied point is selected and there * are other selected points. * @@ -601,7 +447,7 @@ AutomationLine::start_drag_single (ControlPoint* cp, double x, float fraction) } } } - + start_drag_common (x, fraction); } @@ -619,6 +465,7 @@ AutomationLine::start_drag_line (uint32_t i1, uint32_t i2, float fraction) ); _drag_points.clear (); + for (uint32_t i = i1; i <= i2; i++) { _drag_points.push_back (nth (i)); } @@ -642,14 +489,82 @@ AutomationLine::start_drag_multiple (list cp, float fraction, XML start_drag_common (0, fraction); } - struct ControlPointSorter { - bool operator() (ControlPoint const * a, ControlPoint const * b) { + bool operator() (ControlPoint const * a, ControlPoint const * b) const { + if (floateq (a->get_x(), b->get_x(), 1)) { + return a->view_index() < b->view_index(); + } return a->get_x() < b->get_x(); } }; +AutomationLine::ContiguousControlPoints::ContiguousControlPoints (AutomationLine& al) + : line (al), before_x (0), after_x (DBL_MAX) +{ +} + +void +AutomationLine::ContiguousControlPoints::compute_x_bounds () +{ + uint32_t sz = size(); + + if (sz > 0 && sz < line.npoints()) { + + /* determine the limits on x-axis motion for this + contiguous range of control points + */ + + if (front()->view_index() > 0) { + before_x = line.nth (front()->view_index() - 1)->get_x(); + } + + /* if our last point has a point after it in the line, + we have an "after" bound + */ + + if (back()->view_index() < (line.npoints() - 2)) { + after_x = line.nth (back()->view_index() + 1)->get_x(); + } + } +} + +double +AutomationLine::ContiguousControlPoints::clamp_dx (double dx) +{ + if (empty()) { + return dx; + } + + /* get the maximum distance we can move any of these points along the x-axis + */ + + double tx; /* possible position a point would move to, given dx */ + ControlPoint* cp; + + if (dx > 0) { + /* check the last point, since we're moving later in time */ + cp = back(); + } else { + /* check the first point, since we're moving earlier in time */ + cp = front(); + } + + tx = cp->get_x() + dx; // new possible position if we just add the motion + tx = max (tx, before_x); // can't move later than following point + tx = min (tx, after_x); // can't move earlier than preceeding point + return tx - cp->get_x (); +} + +void +AutomationLine::ContiguousControlPoints::move (double dx, double dy) +{ + for (std::list::iterator i = begin(); i != end(); ++i) { + (*i)->move_to ((*i)->get_x() + dx, (*i)->get_y() - line.height() * dy, ControlPoint::Full); + line.reset_line_coords (**i); + } +} + /** Common parts of starting a drag. * @param x Starting x position in units, or 0 if x is being ignored. * @param fraction Starting y position (as a fraction of the track height, where 0 is the bottom and 1 the top) @@ -663,86 +578,80 @@ AutomationLine::start_drag_common (double x, float fraction) _drag_had_movement = false; did_push = false; - _drag_points.sort (ControlPointSorter ()); + /* they are probably ordered already, but we have to make sure */ - /* find the additional points that will be dragged when the user is holding - the "push" modifier - */ - - uint32_t i = _drag_points.back()->view_index () + 1; - ControlPoint* p = 0; - _push_points.clear (); - while ((p = nth (i)) != 0 && p->can_slide()) { - _push_points.push_back (p); - ++i; - } + _drag_points.sort (ControlPointSorter()); } + /** Should be called to indicate motion during a drag. - * @param x New x position of the drag in units, or undefined if ignore_x == true. + * @param x New x position of the drag in canvas units, or undefined if ignore_x == true. * @param fraction New y fraction. * @return x position and y fraction that were actually used (once clamped). */ pair -AutomationLine::drag_motion (double x, float fraction, bool ignore_x, bool with_push) +AutomationLine::drag_motion (double const x, float fraction, bool ignore_x, bool with_push, uint32_t& final_index) { - /* setup the points that are to be moved this time round */ - list points = _drag_points; - if (with_push) { - copy (_push_points.begin(), _push_points.end(), back_inserter (points)); - points.sort (ControlPointSorter ()); + if (_drag_points.empty()) { + return pair (x,fraction); } - + double dx = ignore_x ? 0 : (x - _drag_x); double dy = fraction - _last_drag_fraction; - /* find x limits */ - ControlPoint* before = 0; - ControlPoint* after = 0; + if (!_drag_had_movement) { - for (vector::iterator i = control_points.begin(); i != control_points.end(); ++i) { - if ((*i)->get_x() < points.front()->get_x()) { - before = *i; - } - if ((*i)->get_x() > points.back()->get_x() && after == 0) { - after = *i; + /* "first move" ... do some stuff that we don't want to do if + no motion ever took place, but need to do before we handle + motion. + */ + + /* partition the points we are dragging into (potentially several) + * set(s) of contiguous points. this will not happen with a normal + * drag, but if the user does a discontiguous selection, it can. + */ + + uint32_t expected_view_index = 0; + CCP contig; + + for (list::iterator i = _drag_points.begin(); i != _drag_points.end(); ++i) { + if (i == _drag_points.begin() || (*i)->view_index() != expected_view_index) { + contig.reset (new ContiguousControlPoints (*this)); + contiguous_points.push_back (contig); + } + contig->push_back (*i); + expected_view_index = (*i)->view_index() + 1; } - } - - double const before_x = before ? before->get_x() : 0; - double const after_x = after ? after->get_x() : DBL_MAX; - /* clamp x */ - for (list::iterator i = points.begin(); i != points.end(); ++i) { - - if ((*i)->can_slide() && !ignore_x) { + if (contiguous_points.back()->empty()) { + contiguous_points.pop_back (); + } - /* clamp min x */ - double const a = (*i)->get_x() + dx; - double const b = before_x + 1; - if (a < b) { - dx += b - a; - } + for (vector::iterator ccp = contiguous_points.begin(); ccp != contiguous_points.end(); ++ccp) { + (*ccp)->compute_x_bounds (); + } + } + + /* OK, now on to the stuff related to *this* motion event. First, for + * each contiguous range, figure out the maximum x-axis motion we are + * allowed (because of neighbouring points that are not moving. + * + * if we are moving forwards with push, we don't need to do this, + * since all later points will move too. + */ - /* clamp max x */ - if (after) { - - if (after_x - before_x < 2) { - /* after and before are very close, so just leave this alone */ - dx = 0; - } else { - double const a = (*i)->get_x() + dx; - double const b = after_x - 1; - if (a > b) { - dx -= a - b; - } - } + if (dx < 0 || ((dx > 0) && !with_push)) { + for (vector::iterator ccp = contiguous_points.begin(); ccp != contiguous_points.end(); ++ccp) { + double dxt = (*ccp)->clamp_dx (dx); + if (fabs (dxt) < fabs (dx)) { + dx = dxt; } } } /* clamp y */ - for (list::iterator i = points.begin(); i != points.end(); ++i) { + + for (list::iterator i = _drag_points.begin(); i != _drag_points.end(); ++i) { double const y = ((_height - (*i)->get_y()) / _height) + dy; if (y < 0) { dy -= y; @@ -752,52 +661,60 @@ AutomationLine::drag_motion (double x, float fraction, bool ignore_x, bool with_ } } - pair const clamped (_drag_x + dx, _last_drag_fraction + dy); - _drag_distance += dx; - _drag_x = x; - _last_drag_fraction = fraction; - - for (list::iterator i = _drag_points.begin(); i != _drag_points.end(); ++i) { - (*i)->move_to ((*i)->get_x() + dx, (*i)->get_y() - _height * dy, ControlPoint::Full); - reset_line_coords (**i); - } + if (dx || dy) { - if (with_push) { - /* move push points, preserving their y */ - for (list::iterator i = _push_points.begin(); i != _push_points.end(); ++i) { - (*i)->move_to ((*i)->get_x() + dx, (*i)->get_y(), ControlPoint::Full); - reset_line_coords (**i); + /* and now move each section */ + + for (vector::iterator ccp = contiguous_points.begin(); ccp != contiguous_points.end(); ++ccp) { + (*ccp)->move (dx, dy); + } + if (with_push) { + final_index = contiguous_points.back()->back()->view_index () + 1; + ControlPoint* p; + uint32_t i = final_index; + while ((p = nth (i)) != 0 && p->can_slide()) { + p->move_to (p->get_x() + dx, p->get_y(), ControlPoint::Full); + reset_line_coords (*p); + ++i; + } } - } - if (line_points.size() > 1) { - line->property_points() = line_points; - } + /* update actual line coordinates (will queue a redraw) + */ + if (line_points.size() > 1) { + line->property_points() = line_points; + } + } + + _drag_distance += dx; + _drag_x += dx; + _last_drag_fraction = fraction; _drag_had_movement = true; did_push = with_push; - return clamped; + return pair (_drag_x + dx, _last_drag_fraction + dy); } /** Should be called to indicate the end of a drag */ void -AutomationLine::end_drag () +AutomationLine::end_drag (bool with_push, uint32_t final_index) { if (!_drag_had_movement) { return; } alist->freeze (); + sync_model_with_view_points (_drag_points); - /* set up the points that were moved this time round */ - list points = _drag_points; - if (did_push) { - copy (_push_points.begin(), _push_points.end(), back_inserter (points)); - points.sort (ControlPointSorter ()); + if (with_push) { + ControlPoint* p; + uint32_t i = final_index; + while ((p = nth (i)) != 0 && p->can_slide()) { + sync_model_with_view_point (*p); + ++i; + } } - - sync_model_with_view_points (points, did_push, rint (_drag_distance * trackview.editor().get_current_zoom ())); alist->thaw (); @@ -806,75 +723,38 @@ AutomationLine::end_drag () trackview.editor().session()->add_command ( new MementoCommand(memento_command_binder (), 0, &alist->get_state()) ); - + trackview.editor().session()->set_dirty (); + did_push = false; + + contiguous_points.clear (); } void -AutomationLine::sync_model_with_view_point (ControlPoint& cp, bool did_push, int64_t distance) +AutomationLine::sync_model_with_view_point (ControlPoint& cp) { - 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. + /* find out where the visual control point is. + initial results are in canvas units. ask the + line to convert them to something relevant. */ - /* change all points before the primary point */ - - for (AutomationList::iterator i = mr.start; i != cp.model(); ++i) { + double view_x = cp.get_x(); + double view_y = 1.0 - (cp.get_y() / _height); - double fract = ((*i)->when - mr.xmin) / (mr.xpos - mr.xmin); - double y_delta = ydelta * fract; - double x_delta = distance * fract; - - /* interpolate */ + /* if xval has not changed, set it directly from the model to avoid rounding errors */ - if (y_delta || x_delta) { - alist->modify (i, (*i)->when + x_delta, mr.ymin + y_delta); - } + if (view_x == trackview.editor().frame_to_unit_unrounded (_time_converter->to ((*cp.model())->when)) - _offset) { + view_x = (*cp.model())->when - _offset; + } else { + view_x = trackview.editor().unit_to_frame (view_x); + view_x = _time_converter->from (view_x + _offset); } - /* change the primary point */ - update_pending = true; - alist->modify (cp.model(), mr.xval, mr.yval); - - /* change later points */ - - AutomationList::iterator i = cp.model(); - ++i; + view_to_model_coord_y (view_y); - 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); - } - - ++i; - } - - if (did_push) { - - /* move all points after the range represented by the view by the same distance - as the main point moved. - */ - - alist->slide (mr.end, distance); - } + alist->modify (cp.model(), view_x, view_y); } bool @@ -884,7 +764,7 @@ AutomationLine::control_points_adjacent (double xval, uint32_t & before, uint32_ ControlPoint *acp = 0; double unit_xval; - unit_xval = trackview.editor().frame_to_unit (xval); + unit_xval = trackview.editor().frame_to_unit_unrounded (xval); for (vector::iterator i = control_points.begin(); i != control_points.end(); ++i) { @@ -908,13 +788,16 @@ AutomationLine::control_points_adjacent (double xval, uint32_t & before, uint32_ bool AutomationLine::is_last_point (ControlPoint& cp) { - ModelRepresentation mr; + // If the list is not empty, and the point is the last point in the list - model_representation (cp, mr); + if (alist->empty()) { + return false; + } - // If the list is not empty, and the point is the last point in the list + AutomationList::const_iterator i = alist->end(); + --i; - if (!alist->empty() && mr.end == alist->end()) { + if (cp.model() == i) { return true; } @@ -924,13 +807,9 @@ AutomationLine::is_last_point (ControlPoint& cp) bool AutomationLine::is_first_point (ControlPoint& cp) { - ModelRepresentation mr; - - model_representation (cp, mr); - // 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() && cp.model() == alist->begin()) { return true; } @@ -941,19 +820,15 @@ AutomationLine::is_first_point (ControlPoint& cp) void AutomationLine::remove_point (ControlPoint& cp) { - ModelRepresentation mr; - - model_representation (cp, mr); - trackview.editor().session()->begin_reversible_command (_("remove control point")); XMLNode &before = alist->get_state(); - alist->erase (mr.start, mr.end); - + alist->erase (cp.model()); + trackview.editor().session()->add_command( new MementoCommand (memento_command_binder (), &before, &alist->get_state()) ); - + trackview.editor().session()->commit_reversible_command (); trackview.editor().session()->set_dirty (); } @@ -966,9 +841,7 @@ AutomationLine::remove_point (ControlPoint& cp) * @param result Filled in with selectable things; in this case, ControlPoints. */ void -AutomationLine::get_selectables ( - framepos_t start, framepos_t end, double botfrac, double topfrac, list& results - ) +AutomationLine::get_selectables (framepos_t start, framepos_t end, double botfrac, double topfrac, list& results) { /* convert fractions to display coordinates with 0 at the top of the track */ double const bot_track = (1 - topfrac) * trackview.current_height (); @@ -976,7 +849,12 @@ AutomationLine::get_selectables ( for (vector::iterator i = control_points.begin(); i != control_points.end(); ++i) { double const model_when = (*(*i)->model())->when; - framepos_t const session_frames_when = _time_converter.to (model_when - _offset) + _time_converter.origin_b (); + + /* model_when is relative to the start of the source, so we just need to add on the origin_b here + (as it is the session frame position of the start of the source) + */ + + framepos_t const session_frames_when = _time_converter->to (model_when) + _time_converter->origin_b (); if (session_frames_when >= start && session_frames_when <= end && (*i)->get_y() >= bot_track && (*i)->get_y() <= top_track) { results.push_back (*i); @@ -990,50 +868,15 @@ AutomationLine::get_inverted_selectables (Selection&, list& /*resul // hmmm .... } -/** Take a PointSelection and find ControlPoints that fall within it */ -list -AutomationLine::point_selection_to_control_points (PointSelection const & s) -{ - list cp; - - for (PointSelection::const_iterator i = s.begin(); i != s.end(); ++i) { - - if (i->track != &trackview) { - continue; - } - - double const bot = (1 - i->high_fract) * trackview.current_height (); - double const top = (1 - i->low_fract) * trackview.current_height (); - - for (vector::iterator j = control_points.begin(); j != control_points.end(); ++j) { - - double const rstart = trackview.editor().frame_to_unit (_time_converter.to (i->start)); - double const rend = trackview.editor().frame_to_unit (_time_converter.to (i->end)); - - if ((*j)->get_x() >= rstart && (*j)->get_x() <= rend) { - if ((*j)->get_y() >= bot && (*j)->get_y() <= top) { - cp.push_back (*j); - } - } - } - - } - - return cp; -} - void -AutomationLine::set_selected_points (PointSelection& points) +AutomationLine::set_selected_points (PointSelection const & points) { for (vector::iterator i = control_points.begin(); i != control_points.end(); ++i) { (*i)->set_selected (false); } - if (!points.empty()) { - list cp = point_selection_to_control_points (points); - for (list::iterator i = cp.begin(); i != cp.end(); ++i) { - (*i)->set_selected (true); - } + for (PointSelection::const_iterator i = points.begin(); i != points.end(); ++i) { + (*i)->set_selected (true); } set_colors (); @@ -1050,16 +893,22 @@ void AutomationLine::set_colors () void AutomationLine::list_changed () { - queue_reset (); + DEBUG_TRACE (DEBUG::Automation, string_compose ("\tline changed, existing update pending? %1\n", update_pending)); + + if (!update_pending) { + update_pending = true; + Gtkmm2ext::UI::instance()->call_slot (invalidator (*this), boost::bind (&AutomationLine::queue_reset, this)); + } } void AutomationLine::reset_callback (const Evoral::ControlList& events) { - ALPoints tmp_points; - uint32_t npoints = events.size(); + uint32_t vp = 0; + uint32_t pi = 0; + uint32_t np; - if (npoints == 0) { + if (events.empty()) { for (vector::iterator i = control_points.begin(); i != control_points.end(); ++i) { delete *i; } @@ -1068,29 +917,97 @@ AutomationLine::reset_callback (const Evoral::ControlList& events) return; } - AutomationList::const_iterator ai; + /* hide all existing points, and the line */ + + for (vector::iterator i = control_points.begin(); i != control_points.end(); ++i) { + (*i)->hide(); + } + + line->hide (); + np = events.size(); + + Evoral::ControlList& e = const_cast (events); + + for (AutomationList::iterator ai = e.begin(); ai != e.end(); ++ai, ++pi) { + + double tx = (*ai)->when; + double ty = (*ai)->value; + + /* convert from model coordinates to canonical view coordinates */ + + model_to_view_coord (tx, ty); + + if (std::isnan (tx) || std::isnan (ty)) { + warning << string_compose (_("Ignoring illegal points on AutomationLine \"%1\""), + _name) << endmsg; + continue; + } + + if (tx >= max_framepos || tx < 0 || tx >= _maximum_time) { + continue; + } + + /* convert x-coordinate to a canvas unit coordinate (this takes + * zoom and scroll into account). + */ + + tx = trackview.editor().frame_to_unit_unrounded (tx); + + /* convert from canonical view height (0..1.0) to actual + * height coordinates (using X11's top-left rooted system) + */ + + ty = _height - (ty * _height); + + add_visible_control_point (vp, pi, tx, ty, ai, np); + vp++; + } + + /* discard extra CP's to avoid confusing ourselves */ + + while (control_points.size() > vp) { + ControlPoint* cp = control_points.back(); + control_points.pop_back (); + delete cp; + } + + if (!terminal_points_can_slide) { + control_points.back()->set_can_slide(false); + } + + if (vp > 1) { - for (ai = events.begin(); ai != events.end(); ++ai) { + /* reset the line coordinates given to the CanvasLine */ - double translated_x = (*ai)->when; - double translated_y = (*ai)->value; - model_to_view_coord (translated_x, translated_y); + while (line_points.size() < vp) { + line_points.push_back (Art::Point (0,0)); + } + + while (line_points.size() > vp) { + line_points.pop_back (); + } + + for (uint32_t n = 0; n < vp; ++n) { + line_points[n].set_x (control_points[n]->get_x()); + line_points[n].set_y (control_points[n]->get_y()); + } - if (translated_x >= 0 && translated_x < _maximum_time) { - tmp_points.push_back (ALPoint ( - trackview.editor().frame_to_unit (translated_x), - _height - (translated_y * _height)) - ); + line->property_points() = line_points; + + if (_visible && alist->interpolation() != AutomationList::Discrete) { + line->show(); } } - determine_visible_control_points (tmp_points); + set_selected_points (trackview.editor().get_selection().points); } void AutomationLine::reset () { + DEBUG_TRACE (DEBUG::Automation, "\t\tLINE RESET\n"); update_pending = false; + have_timeout = false; if (no_draw) { return; @@ -1099,6 +1016,27 @@ AutomationLine::reset () alist->apply_to_points (*this, &AutomationLine::reset_callback); } +void +AutomationLine::queue_reset () +{ + /* this must be called from the GUI thread + */ + + if (trackview.editor().session()->transport_rolling() && alist->automation_write()) { + /* automation write pass ... defer to a timeout */ + /* redraw in 1/4 second */ + if (!have_timeout) { + DEBUG_TRACE (DEBUG::Automation, "\tqueue timeout\n"); + Glib::signal_timeout().connect (sigc::bind_return (sigc::mem_fun (*this, &AutomationLine::reset), false), 250); + have_timeout = true; + } else { + DEBUG_TRACE (DEBUG::Automation, "\ttimeout already queued, change ignored\n"); + } + } else { + reset (); + } +} + void AutomationLine::clear () { @@ -1125,44 +1063,31 @@ AutomationLine::set_list (boost::shared_ptr list) } void -AutomationLine::show_all_control_points () +AutomationLine::add_visibility (VisibleAspects va) { - if (_is_boolean) { - // show the line but don't allow any control points - return; - } - - points_visible = true; - - for (vector::iterator i = control_points.begin(); i != control_points.end(); ++i) { - if (!(*i)->visible()) { - (*i)->show (); - (*i)->set_visible (true); - } - } + _visible = VisibleAspects (_visible | va); + show (); } void -AutomationLine::hide_all_but_selected_control_points () +AutomationLine::set_visibility (VisibleAspects va) { - if (alist->interpolation() == AutomationList::Discrete) { - return; - } - - points_visible = false; + _visible = va; + show (); +} - for (vector::iterator i = control_points.begin(); i != control_points.end(); ++i) { - if (!(*i)->get_selected()) { - (*i)->set_visible (false); - } - } +void +AutomationLine::remove_visibility (VisibleAspects va) +{ + _visible = VisibleAspects (_visible & ~va); + show (); } void AutomationLine::track_entered() { if (alist->interpolation() != AutomationList::Discrete) { - show_all_control_points(); + add_visibility (ControlPoints); } } @@ -1170,7 +1095,7 @@ void AutomationLine::track_exited() { if (alist->interpolation() != AutomationList::Discrete) { - hide_all_but_selected_control_points(); + remove_visibility (ControlPoints); } } @@ -1191,7 +1116,7 @@ AutomationLine::set_state (const XMLNode &node, int version) void AutomationLine::view_to_model_coord (double& x, double& y) const { - x = _time_converter.from (x); + x = _time_converter->from (x); view_to_model_coord_y (y); } @@ -1201,7 +1126,7 @@ AutomationLine::view_to_model_coord_y (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 = slider_position_to_gain_with_max (y, Config->get_max_gain()); y = max (0.0, y); y = min (2.0, y); } else if (alist->parameter().type() == PanAzimuthAutomation || @@ -1221,7 +1146,7 @@ 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); + y = gain_to_slider_position_with_max (y, Config->get_max_gain()); } else if (alist->parameter().type() == PanAzimuthAutomation || alist->parameter().type() == PanElevationAutomation || alist->parameter().type() == PanWidthAutomation) { @@ -1233,7 +1158,7 @@ AutomationLine::model_to_view_coord (double& x, double& y) const y = y / (double)alist->parameter().max(); /* ... like this */ } - x = _time_converter.to (x) - _offset; + x = _time_converter->to (x) - _offset; } /** Called when our list has announced that its interpolation style has changed */ @@ -1241,17 +1166,19 @@ void AutomationLine::interpolation_changed (AutomationList::InterpolationStyle style) { if (style == AutomationList::Discrete) { - show_all_control_points(); + set_visibility (ControlPoints); line->hide(); } else { - hide_all_but_selected_control_points(); - line->show(); + set_visibility (Line); } } void -AutomationLine::add_visible_control_point (uint32_t view_index, uint32_t pi, double tx, double ty, AutomationList::iterator model, uint32_t npoints) +AutomationLine::add_visible_control_point (uint32_t view_index, uint32_t pi, double tx, double ty, + AutomationList::iterator model, uint32_t npoints) { + ControlPoint::ShapeType shape; + if (view_index >= control_points.size()) { /* make sure we have enough control points */ @@ -1262,63 +1189,45 @@ AutomationLine::add_visible_control_point (uint32_t view_index, uint32_t pi, dou control_points.push_back (ncp); } - ControlPoint::ShapeType shape; - if (!terminal_points_can_slide) { if (pi == 0) { - control_points[view_index]->set_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]->set_can_slide(false); + control_points[view_index]->set_can_slide (false); shape = ControlPoint::End; } else { - control_points[view_index]->set_can_slide(true); + control_points[view_index]->set_can_slide (true); shape = ControlPoint::Full; } } else { - control_points[view_index]->set_can_slide(true); + control_points[view_index]->set_can_slide (true); shape = ControlPoint::Full; } control_points[view_index]->reset (tx, ty, model, view_index, shape); /* finally, control visibility */ - - if (_visible && points_visible) { + + if (_visible & ControlPoints) { control_points[view_index]->show (); control_points[view_index]->set_visible (true); } else { - if (!points_visible) { - control_points[view_index]->set_visible (false); - } + control_points[view_index]->set_visible (false); } } -void -AutomationLine::add_always_in_view (double x) -{ - _always_in_view.push_back (x); - alist->apply_to_points (*this, &AutomationLine::reset_callback); -} - -void -AutomationLine::clear_always_in_view () -{ - _always_in_view.clear (); - alist->apply_to_points (*this, &AutomationLine::reset_callback); -} - void AutomationLine::connect_to_list () { _list_connections.drop_connections (); - + alist->StateChanged.connect (_list_connections, invalidator (*this), boost::bind (&AutomationLine::list_changed, this), gui_context()); - + alist->InterpolationChanged.connect ( _list_connections, invalidator (*this), boost::bind (&AutomationLine::interpolation_changed, this, _1), gui_context() ); @@ -1352,20 +1261,26 @@ AutomationLine::get_point_x_range () const pair r (max_framepos, 0); for (AutomationList::const_iterator i = the_list()->begin(); i != the_list()->end(); ++i) { - r.first = min (r.first, _time_converter.to ((*i)->when) + _offset + _time_converter.origin_b ()); - r.second = max (r.second, _time_converter.to ((*i)->when) + _offset + _time_converter.origin_b ()); + r.first = min (r.first, session_position (i)); + r.second = max (r.second, session_position (i)); } return r; } +framepos_t +AutomationLine::session_position (AutomationList::const_iterator p) const +{ + return _time_converter->to ((*p)->when) + _offset + _time_converter->origin_b (); +} + void AutomationLine::set_offset (framepos_t off) { if (_offset == off) { return; } - + _offset = off; reset (); }