#include <vector>
#include <fstream>
-#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"
#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"
using namespace Editing;
using namespace Gnome; // for Canvas
-static const Evoral::IdentityConverter<double, framepos_t> 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<AutomationList> al,
- const Evoral::TimeConverter<double, framepos_t>* converter)
+ Evoral::TimeConverter<double, framepos_t>* converter)
: trackview (tv)
, _name (name)
, alist (al)
+ , _time_converter (converter ? converter : new Evoral::IdentityConverter<double, framepos_t>)
, _parent_group (parent)
- , _time_converter (converter ? (*converter) : default_converter)
+ , _offset (0)
, _maximum_time (max_framepos)
{
- points_visible = false;
+ if (converter) {
+ _time_converter = converter;
+ _our_time_converter = false;
+ } else {
+ _time_converter = new Evoral::IdentityConverter<double, framepos_t>;
+ _our_time_converter = true;
+ }
+
+ _visible = Line;
+
update_pending = false;
+ have_timeout = false;
_uses_gain_mapping = false;
no_draw = false;
- _visible = true;
- _is_boolean = false;
+ _is_boolean = false;
terminal_points_can_slide = true;
_height = 0;
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 ||
{
vector_delete (&control_points);
delete group;
+
+ if (_our_time_converter) {
+ delete _time_converter;
+ }
}
bool
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<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
+ (*i)->set_visible (true);
(*i)->show ();
}
+ } else if (_visible & SelectedControlPoints) {
+ for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
+ (*i)->set_visible ((*i)->get_selected());
+ }
+ } else {
+ for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
+ (*i)->set_visible (false);
+ }
}
-
- _visible = true;
}
void
AutomationLine::hide ()
{
- line->hide();
- for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
- (*i)->hide();
- }
- _visible = false;
+ set_visibility (VisibleAspects (0));
}
double
y = min (1.0, y);
y = _height - (y * _height);
- double const x = trackview.editor().frame_to_unit (_time_converter.to((*cp.model())->when));
+ 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 (
}
alist->freeze ();
- sync_model_with_view_point (cp, false, 0);
+ sync_model_with_view_point (cp);
alist->thaw ();
update_pending = false;
trackview.editor().session()->add_command (
new MementoCommand<AutomationList> (memento_command_binder(), 0, &alist->get_state())
);
-
+
trackview.editor().session()->commit_reversible_command ();
trackview.editor().session()->set_dirty ();
}
}
void
-AutomationLine::sync_model_with_view_points (list<ControlPoint*> cp, bool did_push, int64_t distance)
+AutomationLine::sync_model_with_view_points (list<ControlPoint*> cp)
{
update_pending = true;
for (list<ControlPoint*>::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))) {
- mr.xval = (*cp.model())->when;
- } else {
- mr.xval = trackview.editor().unit_to_frame (mr.xval);
- mr.xval = _time_converter.from (mr.xval);
- }
-
- /* 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;
- 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<ControlPoint*>::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;
+ std::string d = fraction_to_relative_string (original, fraction);
- if (view_index > 1) {
+ if (!d.empty()) {
- npoints = view_index;
+ s += " (\u0394";
+ s += d;
- /* 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 && 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;
if (fraction == 0.0) {
snprintf (buf, sizeof (buf), "-inf");
} 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 {
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.
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);
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.
*
}
}
}
-
+
start_drag_common (x, fraction);
}
);
_drag_points.clear ();
+
for (uint32_t i = i1; i <= i2; i++) {
_drag_points.push_back (nth (i));
}
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<ControlPoint*>::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)
_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<double, float>
-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<ControlPoint*> 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<double,float> (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<ControlPoint*>::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<ControlPoint*>::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<ControlPoint*>::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<CCP>::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<CCP>::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<ControlPoint*>::iterator i = points.begin(); i != points.end(); ++i) {
+
+ for (list<ControlPoint*>::iterator i = _drag_points.begin(); i != _drag_points.end(); ++i) {
double const y = ((_height - (*i)->get_y()) / _height) + dy;
if (y < 0) {
dy -= y;
}
}
- pair<double, float> const clamped (_drag_x + dx, _last_drag_fraction + dy);
- _drag_distance += dx;
- _drag_x = x;
- _last_drag_fraction = fraction;
-
- for (list<ControlPoint*>::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<ControlPoint*>::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<CCP>::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<double, float> (_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<ControlPoint*> 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 ();
trackview.editor().session()->add_command (
new MementoCommand<AutomationList>(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();
+ view_to_model_coord_y (view_y);
- ++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);
- }
-
- ++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
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<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
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;
}
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;
}
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<AutomationList> (memento_command_binder (), &before, &alist->get_state())
);
-
+
trackview.editor().session()->commit_reversible_command ();
trackview.editor().session()->set_dirty ();
}
* @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<Selectable*>& results
- )
+AutomationLine::get_selectables (framepos_t start, framepos_t end, double botfrac, double topfrac, list<Selectable*>& results)
{
/* convert fractions to display coordinates with 0 at the top of the track */
double const bot_track = (1 - topfrac) * trackview.current_height ();
for (vector<ControlPoint*>::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) + _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);
// hmmm ....
}
-/** Take a PointSelection and find ControlPoints that fall within it */
-list<ControlPoint*>
-AutomationLine::point_selection_to_control_points (PointSelection const & s)
-{
- list<ControlPoint*> 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<ControlPoint*>::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<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
(*i)->set_selected (false);
}
- if (!points.empty()) {
- list<ControlPoint*> cp = point_selection_to_control_points (points);
- for (list<ControlPoint*>::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 ();
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<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
delete *i;
}
return;
}
- AutomationList::const_iterator ai;
+ /* hide all existing points, and the line */
+
+ for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
+ (*i)->hide();
+ }
+
+ line->hide ();
+ np = events.size();
+
+ Evoral::ControlList& e = const_cast<Evoral::ControlList&> (events);
+
+ for (AutomationList::iterator ai = e.begin(); ai != e.end(); ++ai, ++pi) {
- for (ai = events.begin(); ai != events.end(); ++ai) {
+ double tx = (*ai)->when;
+ double ty = (*ai)->value;
- double translated_x = (*ai)->when;
- double translated_y = (*ai)->value;
- model_to_view_coord (translated_x, translated_y);
+ /* convert from model coordinates to canonical view coordinates */
- add_model_point (tmp_points, (*ai)->when, translated_y);
+ 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++;
}
- determine_visible_control_points (tmp_points);
-}
+ /* discard extra CP's to avoid confusing ourselves */
+ while (control_points.size() > vp) {
+ ControlPoint* cp = control_points.back();
+ control_points.pop_back ();
+ delete cp;
+ }
-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)));
+ if (!terminal_points_can_slide) {
+ control_points.back()->set_can_slide(false);
+ }
+
+ if (vp > 1) {
+
+ /* reset the line coordinates given to the CanvasLine */
+
+ 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());
+ }
+
+ line->property_points() = line_points;
+
+ if (_visible && alist->interpolation() != AutomationList::Discrete) {
+ line->show();
+ }
+ }
+
+ 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;
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 ()
{
}
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<ControlPoint*>::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<ControlPoint*>::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);
}
}
AutomationLine::track_exited()
{
if (alist->interpolation() != AutomationList::Discrete) {
- hide_all_but_selected_control_points();
+ remove_visibility (ControlPoints);
}
}
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);
}
/* 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() == PanAutomation) {
- // vertical coordinate axis reversal
+ } else if (alist->parameter().type() == PanAzimuthAutomation ||
+ alist->parameter().type() == PanElevationAutomation ||
+ alist->parameter().type() == PanWidthAutomation) {
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());
+ y = rint (y * alist->parameter().max());
}
}
/* 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) {
+ 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) {
// vertical coordinate axis reversal
y = 1.0 - y;
} else if (alist->parameter().type() == PluginAutomation) {
y = y / (double)alist->parameter().max(); /* ... like this */
}
- x = _time_converter.to(x);
+ x = _time_converter->to (x) - _offset;
}
/** Called when our list has announced that its interpolation style has changed */
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 */
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()
);
* to the start of the track or region that it is on.
*/
void
-AutomationLine::set_maximum_time (framepos_t t)
+AutomationLine::set_maximum_time (framecnt_t t)
{
+ if (_maximum_time == t) {
+ return;
+ }
+
_maximum_time = t;
+ reset ();
}
pair<framepos_t, framepos_t> 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) + _time_converter.origin_b ());
- r.second = max (r.second, _time_converter.to ((*i)->when) + _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 ();
+}