X-Git-Url: https://main.carlh.net/gitweb/?a=blobdiff_plain;f=gtk2_ardour%2Fautomation_line.cc;h=7e60bff2e34ffc0c98a125780eef574dea03776b;hb=13f51112c965677a4925bf03e8cc3413e10a572b;hp=2f91c113bd158e22f56456416ec31cab5dfd2e83;hpb=ca96b9afe85018fac3e5097f7b211a544d7689a9;p=ardour.git diff --git a/gtk2_ardour/automation_line.cc b/gtk2_ardour/automation_line.cc index 2f91c113bd..7e60bff2e3 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" @@ -77,11 +81,12 @@ AutomationLine::AutomationLine (const string& name, TimeAxisView& tv, ArdourCanv _our_time_converter = true; } - points_visible = false; + _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; @@ -124,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 @@ -238,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 ( @@ -254,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; @@ -277,12 +286,12 @@ 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); } } @@ -297,6 +306,31 @@ AutomationLine::get_verbose_cursor_string (double fraction) const return s; } +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"; + } + + std::string d = fraction_to_relative_string (original, fraction); + + if (!d.empty()) { + + s += " (\u0394"; + s += d; + + if (_uses_gain_mapping) { + s += " dB"; + } + + s += ')'; + } + + return s; +} + /** * @param fraction y fraction * @return string representation of this value, using dB if appropriate. @@ -324,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), "-inf"); + } 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. @@ -392,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)); } @@ -415,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) @@ -436,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) { - - /* clamp min x */ - double const a = (*i)->get_x() + dx; - double const b = before_x + 1; - if (a < b) { - dx += b - a; - } + if (contiguous_points.back()->empty()) { + contiguous_points.pop_back (); + } - /* 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; - } - } + 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. + */ + + 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; @@ -525,53 +661,61 @@ 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 (); update_pending = false; @@ -581,10 +725,13 @@ AutomationLine::end_drag () ); 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) { /* find out where the visual control point is. initial results are in canvas units. ask the @@ -596,7 +743,7 @@ AutomationLine::sync_model_with_view_point (ControlPoint& cp, bool did_push, int /* if xval has not changed, set it directly from the model to avoid rounding errors */ - if (view_x == trackview.editor().frame_to_unit (_time_converter->to ((*cp.model())->when)) - _offset) { + 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); @@ -608,14 +755,6 @@ AutomationLine::sync_model_with_view_point (ControlPoint& cp, bool did_push, int view_to_model_coord_y (view_y); alist->modify (cp.model(), view_x, view_y); - - if (did_push) { - - /* move all points after cp by the same distance - */ - - alist->slide (cp.model()++, distance); - } } bool @@ -625,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) { @@ -702,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 (); @@ -731,38 +868,6 @@ 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) - _offset); - double const rend = trackview.editor().frame_to_unit (_time_converter->to (i->end) - _offset); - - 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 const & points) { @@ -770,11 +875,8 @@ AutomationLine::set_selected_points (PointSelection const & points) (*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 (); @@ -791,7 +893,12 @@ 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 @@ -844,7 +951,7 @@ AutomationLine::reset_callback (const Evoral::ControlList& events) * zoom and scroll into account). */ - tx = trackview.editor().frame_to_unit (tx); + 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) @@ -898,7 +1005,9 @@ AutomationLine::reset_callback (const Evoral::ControlList& events) void AutomationLine::reset () { + DEBUG_TRACE (DEBUG::Automation, "\t\tLINE RESET\n"); update_pending = false; + have_timeout = false; if (no_draw) { return; @@ -907,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 () { @@ -933,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); } } @@ -978,7 +1095,7 @@ void AutomationLine::track_exited() { if (alist->interpolation() != AutomationList::Discrete) { - hide_all_but_selected_control_points(); + remove_visibility (ControlPoints); } } @@ -1049,11 +1166,10 @@ 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); } } @@ -1097,13 +1213,11 @@ AutomationLine::add_visible_control_point (uint32_t view_index, uint32_t pi, dou /* 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); } } @@ -1147,13 +1261,19 @@ 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) {