using namespace Editing;
using namespace Gnome; // for Canvas
-static const Evoral::IdentityConverter<double, sframes_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, sframes_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)
- , _maximum_time (max_frames)
+ , _offset (0)
+ , _maximum_time (max_framepos)
{
+ if (converter) {
+ _time_converter = converter;
+ _our_time_converter = false;
+ } else {
+ _time_converter = new Evoral::IdentityConverter<double, framepos_t>;
+ _our_time_converter = true;
+ }
+
points_visible = false;
update_pending = 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
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 (_time_converter->to((*cp.model())->when) - _offset);
trackview.editor().session()->begin_reversible_command (_("automation event move"));
trackview.editor().session()->add_command (
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::model_representation (ControlPoint& cp, ModelRepresentation& mr)
-{
- /* 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();
- }
-
- if (after) {
- mr.end = after->model();
- } else {
- mr.xmax = mr.xpos;
- mr.ymax = mr.ypos;
- mr.end = cp.model();
- ++mr.end;
- }
-}
-
-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);
- }
-
- 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));
- }
-
- 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();
- }
-
- }
-
- set_selected_points (trackview.editor().get_selection().points);
-}
-
string
AutomationLine::get_verbose_cursor_string (double fraction) const
{
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 {
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_frames && p[index].y == DBL_MAX;
-}
-
-void
-AutomationLine::invalidate_point (ALPoints& p, uint32_t index)
-{
- p[index].x = max_frames;
- 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);
}
copy (_push_points.begin(), _push_points.end(), back_inserter (points));
points.sort (ControlPointSorter ());
}
-
+
double dx = ignore_x ? 0 : (x - _drag_x);
double dy = fraction - _last_drag_fraction;
copy (_push_points.begin(), _push_points.end(), back_inserter (points));
points.sort (ControlPointSorter ());
}
-
+
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 ();
}
void
AutomationLine::sync_model_with_view_point (ControlPoint& cp, bool did_push, int64_t distance)
{
- ModelRepresentation mr;
- double ydelta;
-
- model_representation (cp, mr);
-
- /* how much are we changing the central point by */
-
- ydelta = mr.yval - mr.ypos;
-
- /*
- apply the full change to the central point, and interpolate
- on both axes to cover all model points represented
- by the control point.
+ /* 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 (_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;
- while (i != mr.end) {
+ view_to_model_coord_y (view_y);
- 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;
- }
+ alist->modify (cp.model(), view_x, view_y);
if (did_push) {
- /* move all points after the range represented by the view by the same distance
- as the main point moved.
+ /* move all points after cp by the same distance
*/
- alist->slide (mr.end, distance);
+ alist->slide (cp.model()++, distance);
}
}
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 ();
}
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);
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) {
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));
+ 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) {
}
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);
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) {
+
+ double tx = (*ai)->when;
+ double ty = (*ai)->value;
- for (ai = events.begin(); ai != events.end(); ++ai) {
+ /* convert from model coordinates to canonical view coordinates */
- double translated_x = (*ai)->when;
- double translated_y = (*ai)->value;
- model_to_view_coord (translated_x, translated_y);
+ model_to_view_coord (tx, ty);
- add_model_point (tmp_points, (*ai)->when, translated_y);
+ 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 (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;
+ }
+
+ if (!terminal_points_can_slide) {
+ control_points.back()->set_can_slide(false);
+ }
+ if (vp > 1) {
-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)));
+ /* 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
void
AutomationLine::show_all_control_points ()
{
- if (_is_boolean) {
- // show the line but don't allow any control points
- return;
- }
+ if (_is_boolean) {
+ // show the line but don't allow any control points
+ return;
+ }
points_visible = true;
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 */
}
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) {
control_points[view_index]->show ();
control_points[view_index]->set_visible (true);
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>
AutomationLine::get_point_x_range () const
{
- pair<framepos_t, framepos_t> r (max_frames, 0);
+ 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, _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 ());
}
return r;
}
+
+void
+AutomationLine::set_offset (framepos_t off)
+{
+ if (_offset == off) {
+ return;
+ }
+
+ _offset = off;
+ reset ();
+}