#include "beam.hh"
+#include "align-interface.hh"
#include "beam-scoring-problem.hh"
#include "beaming-pattern.hh"
#include "directional-element-interface.hh"
#include "lookup.hh"
#include "main.hh"
#include "misc.hh"
+#include "note-head.hh"
#include "output-def.hh"
#include "pointer-group-interface.hh"
+#include "rhythmic-head.hh"
#include "spanner.hh"
#include "staff-symbol-referencer.hh"
#include "stem.hh"
dir_ = CENTER;
}
+bool
+beam_segment_less (Beam_segment const& a, Beam_segment const& b)
+{
+ return a.horizontal_[LEFT] < b.horizontal_[LEFT];
+}
+
Beam_segment::Beam_segment ()
{
vertical_count_ = 0;
typedef map<int, vector<Beam_stem_segment> > Position_stem_segments_map;
+// TODO - should store result in a property?
vector<Beam_segment>
Beam::get_beam_segments (Grob *me_grob, Grob **common)
{
Direction feather_dir = to_dir (me->get_property ("grow-direction"));
+ Interval placements = robust_scm2interval (me->get_property ("normalized-endpoints"), Interval (0.0, 0.0));
+
Stencil the_beam;
+
+ int extreme = (segments[0].vertical_count_ == 0
+ ? segments[0].vertical_count_
+ : segments.back ().vertical_count_);
+
for (vsize i = 0; i < segments.size (); i ++)
{
Real local_slope = slope;
+ /*
+ Makes local slope proportional to the ratio of the length of this beam
+ to the total length.
+ */
if (feather_dir)
- {
- local_slope += feather_dir * segments[i].vertical_count_ * beam_dy / span.length ();
- }
+ local_slope += (feather_dir * segments[i].vertical_count_
+ * beam_dy
+ * placements.length ()
+ / span.length ());
Stencil b = Lookup::beam (local_slope, segments[i].horizontal_.length (), beam_thickness, blot);
b.translate_axis (segments[i].horizontal_[LEFT], X_AXIS);
+ Real multiplier = feather_dir ? placements[LEFT] : 1.0;
+
+ Interval weights (1 - multiplier, multiplier);
+
+ if (feather_dir != LEFT)
+ weights.swap ();
+
+ // we need two translations: the normal one and
+ // the one of the lowest segment
+ int idx[] = {i, extreme};
+ Real translations[2];
+
+ for (int j = 0; j < 2; j++)
+ translations[j] = slope
+ * (segments[idx[j]].horizontal_[LEFT] - span.linear_combination (CENTER))
+ + pos.linear_combination (CENTER)
+ + beam_dy * segments[idx[j]].vertical_count_;
+
+ Real weighted_average = translations[0] * weights[LEFT] + translations[1] * weights[RIGHT];
+
+ /*
+ Tricky. The manipulation of the variable `weighted_average' below ensures
+ that beams with a RIGHT grow direction will start from the position of the
+ lowest segment at 0, and this error will decrease and decrease over the
+ course of the beam. Something with a LEFT grow direction, on the other
+ hand, will always start in the correct place but progressively accrue
+ error at broken places. This code shifts beams up given where they are
+ in the total span length (controlled by the variable `multiplier'). To
+ better understand what it does, try commenting it out: you'll see that
+ all of the RIGHT growing beams immediately start too low and get better
+ over line breaks, whereas all of the LEFT growing beams start just right
+ and get worse over line breaks.
+ */
+ Real factor = Interval (multiplier, 1 - multiplier).linear_combination (feather_dir);
+
+ if (segments[0].vertical_count_ < 0 && feather_dir)
+ weighted_average += beam_dy * (segments.size () - 1) * factor;
+
+ b.translate_axis (weighted_average, Y_AXIS);
- b.translate_axis (local_slope
- * (segments[i].horizontal_[LEFT] - span.linear_combination (feather_dir))
- + pos.linear_combination (feather_dir)
- + beam_dy * segments[i].vertical_count_, Y_AXIS);
the_beam.add_stencil (b);
+
}
#if (DEBUG_BEAM_SCORING)
SCM annotation = me->get_property ("annotation");
- if (!scm_is_string (annotation))
- {
- SCM debug = me->layout ()->lookup_variable (ly_symbol2scm ("debug-beam-scoring"));
- if (to_boolean (debug))
- annotation = me->get_property ("quant-score");
- }
-
if (scm_is_string (annotation))
{
extract_grob_set (me, "stems", stems);
string str;
SCM properties = Font_interface::text_font_alist_chain (me);
- properties = scm_cons(scm_acons (ly_symbol2scm ("font-size"), scm_from_int (-3), SCM_EOL),
+ properties = scm_cons(scm_acons (ly_symbol2scm ("font-size"), scm_from_int (-5), SCM_EOL),
properties);
-
+
Direction stem_dir = stems.size () ? to_dir (stems[0]->get_property ("direction")) : UP;
Stencil score = *unsmob_stencil (Text_interface::interpret_markup
return ly_interval2scm (pos);
}
+
+// Assuming V is not empty, pick a 'reasonable' point inside V.
+static Real
+point_in_interval (Interval v, Real dist)
+{
+ if (isinf (v[DOWN]))
+ return v[UP] - dist;
+ else if (isinf (v[UP]))
+ return v[DOWN] + dist;
+ else
+ return v.center ();
+}
+
/*
We can't combine with previous function, since check concave and
slope damping comes first.
Beam::shift_region_to_valid (SCM grob, SCM posns)
{
Grob *me = unsmob_grob (grob);
+
/*
Code dup.
*/
vector<Real> x_posns;
extract_grob_set (me, "stems", stems);
- Grob *commonx = common_refpoint_of_array (stems, me, X_AXIS);
- Grob *commony = common_refpoint_of_array (stems, me, Y_AXIS);
+ extract_grob_set (me, "covered-grobs", covered);
+ Grob *common[NO_AXES] = { me, me };
+ for (Axis a = X_AXIS; a < NO_AXES; incr (a)) {
+ common[a] = common_refpoint_of_array (stems, me, a);
+ common[a] = common_refpoint_of_array (covered, common[a], a);
+ }
Grob *fvs = first_normal_stem (me);
if (!fvs)
return posns;
-
- Real x0 = fvs->relative_coordinate (commonx, X_AXIS);
+ Interval x_span;
+ x_span[LEFT] = fvs->relative_coordinate (common[X_AXIS], X_AXIS);
for (vsize i = 0; i < stems.size (); i++)
{
Grob *s = stems[i];
- Real x = s->relative_coordinate (commonx, X_AXIS) - x0;
+ Real x = s->relative_coordinate (common[X_AXIS], X_AXIS) - x_span[LEFT];
x_posns.push_back (x);
}
Grob *lvs = last_normal_stem (me);
- if (!lvs)
- return posns;
-
- Real dx = lvs->relative_coordinate (commonx, X_AXIS) - x0;
+ x_span[RIGHT] = lvs->relative_coordinate (common[X_AXIS], X_AXIS);
Drul_array<Real> pos = ly_scm2interval (posns);
scale_drul (&pos, Staff_symbol_referencer::staff_space (me));
- Real dy = pos[RIGHT] - pos[LEFT];
- Real y = pos[LEFT];
- Real slope = dx ? (dy / dx) : 0.0;
+ Real beam_dy = pos[RIGHT] - pos[LEFT];
+ Real beam_left_y = pos[LEFT];
+ Real slope = x_span.delta () ? (beam_dy / x_span.delta ()) : 0.0;
/*
Shift the positions so that we have a chance of finding good
*/
Interval feasible_left_point;
feasible_left_point.set_full ();
+
for (vsize i = 0; i < stems.size (); i++)
{
Grob *s = stems[i];
continue;
Direction d = get_grob_direction (s);
-
Real left_y
= Stem::get_stem_info (s).shortest_y_
- slope * x_posns [i];
ourselves, so translate:
*/
left_y
- += + s->relative_coordinate (commony, Y_AXIS)
- - me->relative_coordinate (commony, Y_AXIS);
+ += + s->relative_coordinate (common[Y_AXIS], Y_AXIS)
+ - me->relative_coordinate (common[Y_AXIS], Y_AXIS);
Interval flp;
flp.set_full ();
feasible_left_point.intersect (flp);
}
- if (feasible_left_point.is_empty ())
- warning (_ ("no viable initial configuration found: may not find good beam slope"));
- else if (!feasible_left_point.contains (y))
+ vector<Grob*> filtered;
+ /*
+ We only update these for objects that are too large for quanting
+ to find a workaround. Typically, these are notes with
+ stems, and timesig/keysig/clef, which take out the entire area
+ inside the staff as feasible.
+
+ The code below disregards the thickness and multiplicity of the
+ beam. This should not be a problem, as the beam quanting will
+ take care of computing the impact those exactly.
+ */
+ Real min_y_size = 2.0;
+
+ // A list of intervals into which beams may not fall
+ vector<Interval> forbidden_intervals;
+
+ for (vsize i = 0; i < covered.size(); i++)
+ {
+ if (!covered[i]->is_live())
+ continue;
+
+ if (Beam::has_interface (covered[i]) && is_cross_staff (covered[i]))
+ continue;
+
+ Box b;
+ for (Axis a = X_AXIS; a < NO_AXES; incr (a))
+ b[a] = covered[i]->extent (common[a], a);
+
+ if (b[X_AXIS].is_empty () || b[Y_AXIS].is_empty ())
+ continue;
+
+ if (intersection (b[X_AXIS], x_span).is_empty ())
+ continue;
+
+ filtered.push_back (covered[i]);
+ Grob *head_stem = Rhythmic_head::get_stem (covered[i]);
+ if (head_stem && Stem::is_normal_stem (head_stem)
+ && Note_head::has_interface (covered[i]))
+ {
+ if (Stem::get_beam (head_stem))
+ {
+ /*
+ We must assume that stems are infinitely long in this
+ case, as asking for the length of the stem typically
+ leads to circular dependencies.
+
+ This strategy assumes that we don't want to handle the
+ collision of beams in opposite non-forced directions
+ with this code, where shortening the stems of both
+ would resolve the problem, eg.
+
+ x x
+ | |
+ =====
+
+ =====
+ | |
+ x x
+
+ Such beams would need a coordinating grob to resolve
+ the collision, since both will likely want to occupy
+ the centerline.
+ */
+ Direction stemdir = get_grob_direction (head_stem);
+ b[Y_AXIS][stemdir] = stemdir * infinity_f;
+ }
+ else
+ {
+ // TODO - should we include the extent of the stem here?
+ }
+ }
+
+ if (b[Y_AXIS].length () < min_y_size)
+ continue;
+
+ Direction d = LEFT;
+ do
+ {
+ Real x = b[X_AXIS][d] - x_span[LEFT];
+ Real dy = slope * x;
+
+ Direction yd = DOWN;
+ Interval disallowed;
+ do
+ {
+ Real left_y = b[Y_AXIS][yd];
+
+ left_y -= dy;
+
+ // Translate back to beam as ref point.
+ left_y -= me->relative_coordinate (common[Y_AXIS], Y_AXIS);
+
+ disallowed[yd] = left_y;
+ }
+ while (flip (&yd) != DOWN);
+
+ forbidden_intervals.push_back (disallowed);
+ }
+ while (flip (&d) != LEFT);
+ }
+
+ Grob_array *arr =
+ Pointer_group_interface::get_grob_array (me,
+ ly_symbol2scm ("covered-grobs"));
+ arr->set_array (filtered);
+
+ vector_sort (forbidden_intervals, Interval::left_less);
+ Real epsilon = 1.0e-10;
+ Interval feasible_beam_placements (beam_left_y, beam_left_y);
+
+ /*
+ forbidden_intervals contains a vector of intervals in which
+ the beam cannot start. it iterates through these intervals,
+ pushing feasible_beam_placements epsilon over or epsilon under a
+ collision. when this type of change happens, the loop is marked
+ as "dirty" and re-iterated.
+
+ TODO: figure out a faster ways that this loop can happen via
+ a better search algorithm and/or OOP.
+ */
+
+ bool dirty = false;
+ do
+ {
+ dirty = false;
+ for (vsize i = 0; i < forbidden_intervals.size (); i++)
+ {
+ Direction d = DOWN;
+ do
+ {
+ if (forbidden_intervals[i][d] == d * infinity_f)
+ feasible_beam_placements[d] = d * infinity_f;
+ else if (forbidden_intervals[i].contains (feasible_beam_placements[d]))
+ {
+ feasible_beam_placements[d] = d * epsilon + forbidden_intervals[i][d];
+ dirty = true;
+ }
+ }
+ while (flip (&d) != DOWN);
+ }
+ }
+ while (dirty);
+
+ // if the beam placement falls out of the feasible region, we push it
+ // to infinity so that it can never be a feasible candidate below
+ Direction d = DOWN;
+ do
{
- const int REGION_SIZE = 2; // UGH UGH
- if (isinf (feasible_left_point[DOWN]))
- y = feasible_left_point[UP] - REGION_SIZE;
- else if (isinf (feasible_left_point[UP]))
- y = feasible_left_point[DOWN]+ REGION_SIZE;
+ if (!feasible_left_point.contains (feasible_beam_placements[d]))
+ feasible_beam_placements[d] = d*infinity_f;
+ }
+ while (flip (&d) != DOWN);
+
+ if ((feasible_beam_placements[UP] == infinity_f && feasible_beam_placements[DOWN] == -infinity_f) && !feasible_left_point.is_empty ())
+ {
+ // We are somewhat screwed: we have a collision, but at least
+ // there is a way to satisfy stem length constraints.
+ beam_left_y = point_in_interval (feasible_left_point, 2.0);
+ }
+ else if (!feasible_left_point.is_empty ())
+ {
+ // Only one of them offers is feasible solution. Pick that one.
+ if (abs (beam_left_y - feasible_beam_placements[DOWN]) > abs (beam_left_y - feasible_beam_placements[UP]))
+ beam_left_y = feasible_beam_placements[UP];
else
- y = feasible_left_point.center ();
+ beam_left_y = feasible_beam_placements[DOWN];
+ }
+ else
+ {
+ // We are completely screwed.
+ me->warning (_ ("no viable initial configuration found: may not find good beam slope"));
}
- pos = Drul_array<Real> (y, (y + dy));
+ pos = Drul_array<Real> (beam_left_y, (beam_left_y + beam_dy));
scale_drul (&pos, 1 / Staff_symbol_referencer::staff_space (me));
return ly_interval2scm (pos);
"break-overshoot "
"clip-edges "
"concaveness "
+ "collision-interfaces "
+ "collision-voice-only "
+ "covered-grobs "
"damping "
"details "
"direction "
"neutral-direction "
"normal-stems "
"positions "
- "quant-score "
"quantized-positions "
"shorten "
"stems "