]> git.donarmstrong.com Git - lilypond.git/commitdiff
Standardizes X extents of beams across beam calculations.
authorMike Solomon <mike@apollinemike.com>
Thu, 10 Nov 2011 01:19:13 +0000 (02:19 +0100)
committerMike Solomon <mike@apollinemike.com>
Thu, 10 Nov 2011 01:19:13 +0000 (02:19 +0100)
This code makes sure that the same X extent is used for all beam
calculations via the X-positions property, including in spanner_length.

16 files changed:
Documentation/changes.tely
input/regression/beam-broken-classic.ly [new file with mode: 0644]
input/regression/beam-broken-difficult.ly [new file with mode: 0644]
input/regression/beam-consistent-broken-slope.ly [deleted file]
input/regression/beam-quanting-overhang.ly [new file with mode: 0644]
lily/beam-concave.cc [deleted file]
lily/beam-quanting.cc
lily/beam.cc
lily/include/beam-scoring-problem.hh
lily/include/beam.hh
lily/spanner.cc
python/convertrules.py
scm/define-grob-properties.scm
scm/define-grobs.scm
scm/layout-beam.scm
scm/output-lib.scm

index 475f6c0902e35a4adc913827a9a3c78666bb5be0..aaa792cd44c513c3aedd11f30eed621e78cb55ed 100644 (file)
@@ -73,7 +73,10 @@ simplified considerably.
 Beams can now have their slopes preserved over line breaks.
 @lilypond[fragment,quote,relative=2]
 \override Beam #'breakable = ##t
-\override Beam #'consistent-broken-slope = ##t
+a8[ b c d e f g \bar "" \break f e d c b a]
+\once \override Beam #'positions = #beam::align-with-broken-parts
+a8[ b c d e f g \bar "" \break f e d c b a]
+\once \override Beam #'positions = #beam::slope-like-broken-parts
 a8[ b c d e f g \bar "" \break f e d c b a]
 @end lilypond
 To do this, several callback functions are now deprecated.
@@ -82,9 +85,9 @@ To do this, several callback functions are now deprecated.
 @item @code{ly:beam::slope-damping}
 @item @code{ly:beam::shift-region-to-valid}
 @end itemize
-All of these functions are now automatically called via @code{ly:beam::quanting}.
-Furthermore, @code{ly:beam::quanting} now only takes one argument -
-the beam grob.
+Furthermore, @code{ly:beam::quanting} now takes an additional argument
+to help calculations over line breaks.  All of these functions are now
+automatically called when setting the @code{positions} parameter.
 
 @item
 In function arguments music, markups and Scheme expressions (as well
diff --git a/input/regression/beam-broken-classic.ly b/input/regression/beam-broken-classic.ly
new file mode 100644 (file)
index 0000000..9a2624e
--- /dev/null
@@ -0,0 +1,43 @@
+\version "2.15.10"
+
+\header {
+  texidoc="Some classic examples of broken beams, all taken from
+Scriabin Op. 11, No. 1.
+"
+}
+
+\paper {
+  ragged-right = ##t
+}
+
+music = \relative c'' {
+  \override Beam #'breakable = ##t
+  r2. f8[ c \break
+  e c f,] r8 r4 a'8[ e \break
+  g d g,] r8 r4 f'8[ a, \break
+  e' g, bes] r8 r4 <a' a,>8 [ d, \break
+  <g g,> d g,] r8 r4 <d' d,>8[ a \break
+  <c c,> g d] r8 r2
+  \clef bass
+  r2. d,,8[ d' \break
+  a'-4 d a] r8 r4 cis,,8[ cis' \break
+  bes' e g] r8 r4 g,,,8[ g' \break
+  f' b d ] r8 r2 |
+}
+
+\markup { "\override Beam #'positions = #beam::place-broken-parts-individually (default)" }
+{ \music }
+
+\markup { "\override Beam #'positions = #beam::align-with-broken-parts" }
+\markup { \justify { Returns y-positions at the ends of the beam such that beams align-across-breaks. } }
+{
+  \override Beam #'positions = #beam::align-with-broken-parts
+  \music
+}
+
+\markup { "\override Beam #'positions = #beam::slope-like-broken-parts" }
+\markup { \justify { Approximates broken beam positioning in turn-of-the-century Editions Peters scores. } }
+{
+  \override Beam #'positions = #beam::slope-like-broken-parts
+  \music
+}
\ No newline at end of file
diff --git a/input/regression/beam-broken-difficult.ly b/input/regression/beam-broken-difficult.ly
new file mode 100644 (file)
index 0000000..ca20a5f
--- /dev/null
@@ -0,0 +1,27 @@
+\version "2.15.16"
+
+\header {
+  texidoc = "The functions passed to the @code{positions} property should
+handle complicated cases in the same manner that they handle more normal
+cases.
+"
+}
+
+\paper { ragged-right = ##t }
+{
+  r2.
+  \override Beam #'breakable = ##t
+  r8[ g' \break a' r]
+}
+{
+  r2.
+  \override Beam #'positions = #beam::align-with-broken-parts
+  \override Beam #'breakable = ##t
+  r8[ g' \break a' r]
+}
+{
+  r2.
+  \override Beam #'positions = #beam::slope-like-broken-parts
+  \override Beam #'breakable = ##t
+  r8[ g' \break a' r]
+}
\ No newline at end of file
diff --git a/input/regression/beam-consistent-broken-slope.ly b/input/regression/beam-consistent-broken-slope.ly
deleted file mode 100644 (file)
index b9a3c9f..0000000
+++ /dev/null
@@ -1,31 +0,0 @@
-
-\version "2.15.15"
-
-\header {
-  texidoc = "The @code{consistent-broken-slope} property of @code{Beam}
-allows for slopes to be almost consistent across line breaks.  Almost
-because quanting can still cause minor differences between beams slopes.
-"
-}
-
-\relative c' {
-  \override Beam #'breakable = ##t
-  a8[ b c d e f g \bar "" \break f e d c b a]
-}
-
-\relative c' {
-  \override Beam #'breakable = ##t
-  \override Beam #'consistent-broken-slope = ##t
-  a8[ b c d e f g \bar "" \break f e d c b a]
-}
-
-\relative c' {
-  \override Beam #'breakable = ##t
-  a8[ b c d e f \bar "" \break a c e g b]
-}
-
-\relative c' {
-  \override Beam #'breakable = ##t
-  \override Beam #'consistent-broken-slope = ##t
-  a8[ b c d e f \bar "" \break a c e g b]
-}
diff --git a/input/regression/beam-quanting-overhang.ly b/input/regression/beam-quanting-overhang.ly
new file mode 100644 (file)
index 0000000..c214465
--- /dev/null
@@ -0,0 +1,13 @@
+\version "2.15.15"
+
+\header {
+  texidoc = "Beam quanting accounts for beam overhang.
+A beam ending above rests should always fall on a viable
+quant (straddle, sit, inter, or hang).
+"
+}
+
+\paper { ragged-right = ##t }
+{
+  d'8[ c' b e' r r r r r r r r r]
+}
\ No newline at end of file
diff --git a/lily/beam-concave.cc b/lily/beam-concave.cc
deleted file mode 100644 (file)
index 501b753..0000000
+++ /dev/null
@@ -1,170 +0,0 @@
-/*
-  This file is part of LilyPond, the GNU music typesetter.
-
-  Copyright (C) 2004 Han-Wen Nienhuys <hanwen@lilypond.org>
-
-  LilyPond is free software: you can redistribute it and/or modify
-  it under the terms of the GNU General Public License as published by
-  the Free Software Foundation, either version 3 of the License, or
-  (at your option) any later version.
-
-  LilyPond is distributed in the hope that it will be useful,
-  but WITHOUT ANY WARRANTY; without even the implied warranty of
-  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-  GNU General Public License for more details.
-
-  You should have received a copy of the GNU General Public License
-  along with LilyPond.  If not, see <http://www.gnu.org/licenses/>.
-*/
-
-/*
-  Determine whether a beam is concave.
-
-  A beam is concave when the middle notes get closer to the
-  beam than the left and right edge notes.
-
-  This is determined in two ways: by looking at the positions of the
-  middle notes, or by looking at the deviation of the inside notes
-  compared to the line connecting first and last.
-
-  The tricky thing is what to do with beams with chords. There are no
-  real guidelines in this case.
-*/
-
-#include "pointer-group-interface.hh"
-#include "stem.hh"
-#include "beam.hh"
-#include "grob.hh"
-#include "staff-symbol-referencer.hh"
-#include "directional-element-interface.hh"
-
-bool
-is_concave_single_notes (vector<int> const &positions, Direction beam_dir)
-{
-  Interval covering;
-  covering.add_point (positions[0]);
-  covering.add_point (positions.back ());
-
-  bool above = false;
-  bool below = false;
-  bool concave = false;
-
-  /*
-    notes above and below the interval covered by 1st and last note.
-  */
-  for (vsize i = 1; i + 1 < positions.size (); i++)
-    {
-      above = above || (positions[i] > covering[UP]);
-      below = below || (positions[i] < covering[DOWN]);
-    }
-
-  concave = concave || (above && below);
-  /*
-    A note as close or closer to the beam than begin and end, but the
-    note is reached in the opposite direction as the last-first dy
-  */
-  int dy = positions.back () - positions[0];
-  int closest = max (beam_dir * positions.back (), beam_dir * positions[0]);
-  for (vsize i = 2; !concave && i + 1 < positions.size (); i++)
-    {
-      int inner_dy = positions[i] - positions[i - 1];
-      if (sign (inner_dy) != sign (dy)
-          && (beam_dir * positions[i] >= closest
-              || beam_dir * positions[i - 1] >= closest))
-        concave = true;
-    }
-
-  bool all_closer = true;
-  for (vsize i = 1; all_closer && i + 1 < positions.size (); i++)
-    {
-      all_closer = all_closer
-                   && (beam_dir * positions[i] > closest);
-    }
-
-  concave = concave || all_closer;
-  return concave;
-}
-
-Real
-calc_positions_concaveness (vector<int> const &positions, Direction beam_dir)
-{
-  Real dy = positions.back () - positions[0];
-  Real slope = dy / Real (positions.size () - 1);
-  Real concaveness = 0.0;
-  for (vsize i = 1; i + 1 < positions.size (); i++)
-    {
-      Real line_y = slope * i + positions[0];
-
-      concaveness += max (beam_dir * (positions[i] - line_y), 0.0);
-    }
-
-  concaveness /= positions.size ();
-
-  /*
-    Normalize. For dy = 0, the slope ends up as 0 anyway, so the
-    scaling of concaveness doesn't matter much.
-  */
-  if (dy)
-    concaveness /= fabs (dy);
-  return concaveness;
-}
-
-MAKE_SCHEME_CALLBACK (Beam, calc_concaveness, 1);
-SCM
-Beam::calc_concaveness (SCM smob)
-{
-  Grob *me = unsmob_grob (smob);
-
-  vector<Grob *> stems
-    = extract_grob_array (me, "stems");
-
-  if (is_knee (me))
-    return scm_from_double (0.0);
-
-  Direction beam_dir = CENTER;
-  for (vsize i = stems.size (); i--;)
-    {
-      if (Stem::is_normal_stem (stems[i]))
-        {
-          if (Direction dir = get_grob_direction (stems[i]))
-            beam_dir = dir;
-        }
-      else
-        stems.erase (stems.begin () + i);
-    }
-
-  if (stems.size () <= 2)
-    return scm_from_int (0);
-
-  vector<int> close_positions;
-  vector<int> far_positions;
-  for (vsize i = 0; i < stems.size (); i++)
-    {
-      /*
-        For chords, we take the note head that is closest to the beam.
-
-        Hmmm.. wait, for the beams in the last measure of morgenlied,
-        this doesn't look so good. Let's try the heads farthest from
-        the beam.
-      */
-      Interval posns = Stem::head_positions (stems[i]);
-
-      close_positions.push_back ((int) rint (posns[beam_dir]));
-      far_positions.push_back ((int) rint (posns[-beam_dir]));
-    }
-
-  Real concaveness = 0.0;
-
-  if (is_concave_single_notes (beam_dir == UP ? close_positions : far_positions, beam_dir))
-    {
-      concaveness = 10000;
-    }
-  else
-    {
-      concaveness = (calc_positions_concaveness (far_positions, beam_dir)
-                     + calc_positions_concaveness (close_positions, beam_dir)) / 2;
-    }
-
-  return scm_from_double (concaveness);
-}
-
index c064b9d9337359844836aaf05836723125a202a3..774cf1b02b89e15c35bd9d8a1eb5ca3e3fa43c73 100644 (file)
@@ -181,27 +181,57 @@ void Beam_scoring_problem::add_collision (Real x, Interval y,
   collisions_.push_back (c);
 }
 
-void Beam_scoring_problem::init_stems ()
+void Beam_scoring_problem::init_instance_variables (Grob *me, Drul_array<Real> ys, bool align_broken_intos)
 {
+  beam_ = dynamic_cast<Spanner *> (me);
+  unquanted_y_ = ys;
+
+  /*
+    If 'ys' are finite, use them as starting points for y-positions of the
+    ends of the beam, instead of the best-fit through the natural ends of
+    the stems.  Otherwise, we want to do initial slope calculations.
+  */
+  do_initial_slope_calculations_ = false;
+  Direction d = LEFT;
+  do
+    do_initial_slope_calculations_ |= isinf (unquanted_y_[d]) || isnan (unquanted_y_[d]);
+  while (flip (&d) != LEFT);
+
+  /*
+    Calculations are relative to a unit-scaled staff, i.e. the quants are
+    divided by the current staff_space_.
+  */
+  staff_space_ = Staff_symbol_referencer::staff_space (beam_);
+  beam_thickness_ = Beam::get_beam_thickness (beam_) / staff_space_;
+  line_thickness_ = Staff_symbol_referencer::line_thickness (beam_) / staff_space_;
+
+  // This is the least-squares DY, corrected for concave beams.
+  musical_dy_ = robust_scm2double (beam_->get_property ("least-squares-dy"), 0);
+
   vector<Spanner *> beams;
-  if (consistent_broken_slope_)
+  align_broken_intos_ = align_broken_intos;
+  if (align_broken_intos_)
     {
       Spanner *orig = dynamic_cast<Spanner *> (beam_->original ());
       if (!orig)
-        consistent_broken_slope_ = false;
+        align_broken_intos_ = false;
       else if (!orig->broken_intos_.size ())
-        consistent_broken_slope_ = false;
+        align_broken_intos_ = false;
       else
         beams.insert (beams.end (), orig->broken_intos_.begin (), orig->broken_intos_.end ());
     }
-  if (!consistent_broken_slope_)
+  if (!align_broken_intos_)
     beams.push_back (beam_);
 
+  /*
+    x_span_ is a single scalar, cumulatively summing the length of all the
+    segments the parent beam was broken-into.
+  */
   x_span_ = 0.0;
+  is_knee_ = false;
   normal_stem_count_ = 0;
   for (vsize i = 0; i < beams.size (); i++)
     {
-      Interval local_x_span;
       extract_grob_set (beams[i], "stems", stems);
       extract_grob_set (beams[i], "covered-grobs", fake_collisions);
       vector<Grob *> collisions;
@@ -214,15 +244,18 @@ void Beam_scoring_problem::init_stems ()
       for (int a = 2; a--;)
         common[a] = common_refpoint_of_array (stems, beams[i], Axis (a));
 
-      Real x_left = beams[i]->relative_coordinate(common[X_AXIS], X_AXIS);
-
-      Drul_array<Grob *> edge_stems (Beam::first_normal_stem (beams[i]),
-                                     Beam::last_normal_stem (beams[i]));
       Direction d = LEFT;
       do
-        local_x_span[d] = edge_stems[d] ? edge_stems[d]->relative_coordinate (common[X_AXIS], X_AXIS) : 0.0;
+        common[X_AXIS] = beams[i]->get_bound (d)->common_refpoint (common[X_AXIS], X_AXIS);
       while (flip (&d) != LEFT);
 
+      // positions of the endpoints of this beam segment, including any overhangs
+      const Interval x_pos = robust_scm2interval (beams[i]->get_property ("X-positions"),
+                                                  Interval (0.0, 0.0));
+
+      Drul_array<Grob *> edge_stems (Beam::first_normal_stem (beams[i]),
+                                     Beam::last_normal_stem (beams[i]));
+
       Drul_array<bool> dirs_found (0, 0);
 
       Real my_y = beams[i]->relative_coordinate (common[Y_AXIS], Y_AXIS);
@@ -244,10 +277,10 @@ void Beam_scoring_problem::init_stems ()
           bool f = to_boolean (s->get_property ("french-beaming"))
                    && s != edge_stems[LEFT] && s != edge_stems[RIGHT];
 
-          Real y = Beam::calc_stem_y (beams[i], s, common, local_x_span[LEFT], local_x_span[RIGHT], CENTER,
+          Real y = Beam::calc_stem_y (beams[i], s, common, x_pos[LEFT], x_pos[RIGHT], CENTER,
                                       Interval (0, 0), f);
           base_lengths_.push_back (y / staff_space_);
-          stem_xpositions_.push_back (s->relative_coordinate (common[X_AXIS], X_AXIS) - x_left + x_span_);
+          stem_xpositions_.push_back (s->relative_coordinate (common[X_AXIS], X_AXIS) - x_pos[LEFT] + x_span_);
           stem_ypositions_.push_back (s->relative_coordinate (common[Y_AXIS], Y_AXIS) - my_y);
           if (is_normal_.back ())
             {
@@ -264,7 +297,7 @@ void Beam_scoring_problem::init_stems ()
                                             stem_infos_.back ().dir_);
 
       is_xstaff_ = Align_interface::has_interface (common[Y_AXIS]);
-      is_knee_ = dirs_found[LEFT] && dirs_found[RIGHT];
+      is_knee_ |= dirs_found[DOWN] && dirs_found[UP];
 
       staff_radius_ = Staff_symbol_referencer::staff_radius (beams[i]);
       edge_beam_counts_ = Drul_array<int>
@@ -291,11 +324,11 @@ void Beam_scoring_problem::init_stems ()
           quant_range_[d][-ed] = heads[ed] + stem_offset;
         }
       while (flip (&d) != LEFT);
-      Grob *common_x = NULL;
-      segments_ = Beam::get_beam_segments (beams[i], &common_x);
+
+      segments_ = Beam::get_beam_segments (beams[i]);
       vector_sort (segments_, beam_segment_less);
       for (vsize j = 0; j < segments_.size (); j++)
-        segments_[j].horizontal_ += (x_span_ - x_left);
+        segments_[j].horizontal_ += (x_span_ - x_pos[LEFT]);
 
       set<Grob *> colliding_stems;
       for (vsize j = 0; j < collisions.size (); j++)
@@ -310,10 +343,12 @@ void Beam_scoring_problem::init_stems ()
           for (Axis a = X_AXIS; a < NO_AXES; incr (a))
             b[a] = collisions[j]->extent (common[a], a);
 
+          if (b[X_AXIS][RIGHT] < x_pos[LEFT] || b[X_AXIS][LEFT] > x_pos[RIGHT])
+            continue;
           if (b[X_AXIS].is_empty () || b[Y_AXIS].is_empty ())
             continue;
 
-          b[X_AXIS] += (x_span_ - x_left);
+          b[X_AXIS] += (x_span_ - x_pos[LEFT]);
           Real width = b[X_AXIS].length ();
           Real width_factor = sqrt (width / staff_space_);
 
@@ -332,7 +367,7 @@ void Beam_scoring_problem::init_stems ()
       for (set<Grob *>::const_iterator it (colliding_stems.begin ()); it != colliding_stems.end (); it++)
         {
           Grob *s = *it;
-          Real x = (s->extent (common[X_AXIS], X_AXIS) - x_left + x_span_).center ();
+          Real x = (s->extent (common[X_AXIS], X_AXIS) - x_pos[LEFT] + x_span_).center ();
 
           Direction stem_dir = get_grob_direction (*it);
           Interval y;
@@ -347,54 +382,22 @@ void Beam_scoring_problem::init_stems ()
         }
       x_span_ += beams[i]->spanner_length ();
     }
-
-  /*
-    Here, we eliminate all extremal hangover, be it from non-normal stems
-    (like stemlets) or broken beams (if we're not calculating consistent
-    slope).
-  */
-  if (normal_stem_count_)
-    {
-      Interval trimmings (0.0, 0.0);
-      Direction d = LEFT;
-
-      do
-        {
-          vsize idx = d == LEFT ? first_normal_index () : last_normal_index ();
-          trimmings[d] = d * ((d == LEFT ? 0 : x_span_) - stem_xpositions_[idx]);
-        }
-      while (flip (&d) != LEFT);
-
-      do
-        x_span_ -= trimmings[d];
-      while (flip (&d) != LEFT);
-
-      for (vsize i = 0; i < stem_xpositions_.size (); i++)
-        stem_xpositions_[i] -= trimmings[LEFT];
-    }
 }
 
-Beam_scoring_problem::Beam_scoring_problem (Grob *me, Drul_array<Real> ys)
+Beam_scoring_problem::Beam_scoring_problem (Grob *me, Drul_array<Real> ys, bool align_broken_intos)
 {
   beam_ = dynamic_cast<Spanner *> (me);
   unquanted_y_ = ys;
-  consistent_broken_slope_ = to_boolean (me->get_property ("consistent-broken-slope"));
-  /*
-    Calculations are relative to a unit-scaled staff, i.e. the quants are
-    divided by the current staff_space_.
-  */
-  staff_space_ = Staff_symbol_referencer::staff_space (me);
-  beam_thickness_ = Beam::get_beam_thickness (me) / staff_space_;
-  line_thickness_ = Staff_symbol_referencer::line_thickness (me) / staff_space_;
-
-  // This is the least-squares DY, corrected for concave beams.
-  musical_dy_ = robust_scm2double (me->get_property ("least-squares-dy"), 0);
+  align_broken_intos_ = align_broken_intos;
 
   parameters_.fill (me);
-  init_stems ();
-  least_squares_positions ();
-  slope_damping ();
-  shift_region_to_valid ();
+  init_instance_variables (me, ys, align_broken_intos);
+  if (do_initial_slope_calculations_)
+    {
+      least_squares_positions ();
+      slope_damping ();
+      shift_region_to_valid ();
+    }
 }
 
 // Assuming V is not empty, pick a 'reasonable' point inside V.
@@ -536,11 +539,6 @@ Beam_scoring_problem::least_squares_positions ()
       else
         unquanted_y_ = ideal;
 
-      /*
-        For broken beams this doesn't work well. In this case, the
-        slope esp. of the first part of a broken beam should predict
-        where the second part goes.
-      */
       ldy = unquanted_y_[RIGHT] - unquanted_y_[LEFT];
     }
   else
@@ -563,6 +561,142 @@ Beam_scoring_problem::least_squares_positions ()
     }
 
   musical_dy_ = ldy;
+  beam_->set_property ("least-squares-dy", scm_from_double (musical_dy_));
+}
+
+/*
+  Determine whether a beam is concave.
+
+  A beam is concave when the middle notes get closer to the
+  beam than the left and right edge notes.
+
+  This is determined in two ways: by looking at the positions of the
+  middle notes, or by looking at the deviation of the inside notes
+  compared to the line connecting first and last.
+
+  The tricky thing is what to do with beams with chords. There are no
+  real guidelines in this case.
+*/
+
+bool
+is_concave_single_notes (vector<int> const &positions, Direction beam_dir)
+{
+  Interval covering;
+  covering.add_point (positions[0]);
+  covering.add_point (positions.back ());
+
+  bool above = false;
+  bool below = false;
+  bool concave = false;
+
+  /*
+    notes above and below the interval covered by 1st and last note.
+  */
+  for (vsize i = 1; i + 1 < positions.size (); i++)
+    {
+      above = above || (positions[i] > covering[UP]);
+      below = below || (positions[i] < covering[DOWN]);
+    }
+
+  concave = concave || (above && below);
+  /*
+    A note as close or closer to the beam than begin and end, but the
+    note is reached in the opposite direction as the last-first dy
+  */
+  int dy = positions.back () - positions[0];
+  int closest = max (beam_dir * positions.back (), beam_dir * positions[0]);
+  for (vsize i = 2; !concave && i + 1 < positions.size (); i++)
+    {
+      int inner_dy = positions[i] - positions[i - 1];
+      if (sign (inner_dy) != sign (dy)
+          && (beam_dir * positions[i] >= closest
+              || beam_dir * positions[i - 1] >= closest))
+        concave = true;
+    }
+
+  bool all_closer = true;
+  for (vsize i = 1; all_closer && i + 1 < positions.size (); i++)
+    {
+      all_closer = all_closer
+                   && (beam_dir * positions[i] > closest);
+    }
+
+  concave = concave || all_closer;
+  return concave;
+}
+
+Real
+calc_positions_concaveness (vector<int> const &positions, Direction beam_dir)
+{
+  Real dy = positions.back () - positions[0];
+  Real slope = dy / Real (positions.size () - 1);
+  Real concaveness = 0.0;
+  for (vsize i = 1; i + 1 < positions.size (); i++)
+    {
+      Real line_y = slope * i + positions[0];
+
+      concaveness += max (beam_dir * (positions[i] - line_y), 0.0);
+    }
+
+  concaveness /= positions.size ();
+
+  /*
+    Normalize. For dy = 0, the slope ends up as 0 anyway, so the
+    scaling of concaveness doesn't matter much.
+  */
+  if (dy)
+    concaveness /= fabs (dy);
+  return concaveness;
+}
+
+Real
+Beam_scoring_problem::calc_concaveness ()
+{
+  SCM conc = beam_->get_property ("concaveness");
+  if (scm_is_number (conc))
+    return scm_to_double (conc);
+
+  if (is_knee_)
+    return 0.0;
+
+  Direction beam_dir = CENTER;
+  for (vsize i = is_normal_.size (); i--;)
+    if (is_normal_[i] && stem_infos_[i].dir_)
+      beam_dir = stem_infos_[i].dir_;
+
+  if (normal_stem_count_ <= 2)
+    return 0.0;
+
+  vector<int> close_positions;
+  vector<int> far_positions;
+  for (vsize i = 0; i < is_normal_.size (); i++)
+    if (is_normal_[i])
+      {
+        /*
+          For chords, we take the note head that is closest to the beam.
+
+          Hmmm.. wait, for the beams in the last measure of morgenlied,
+          this doesn't look so good. Let's try the heads farthest from
+          the beam.
+        */
+
+        close_positions.push_back ((int) rint (head_positions_[i][beam_dir]));
+        far_positions.push_back ((int) rint (head_positions_[i][-beam_dir]));
+    }
+
+  Real concaveness = 0.0;
+
+  if (is_concave_single_notes (beam_dir == UP ? close_positions : far_positions, beam_dir))
+    {
+      concaveness = 10000;
+    }
+  else
+    {
+      concaveness = (calc_positions_concaveness (far_positions, beam_dir)
+                     + calc_positions_concaveness (close_positions, beam_dir)) / 2;
+    }
+
+  return concaveness;
 }
 
 void
@@ -573,7 +707,7 @@ Beam_scoring_problem::slope_damping ()
 
   SCM s = beam_->get_property ("damping");
   Real damping = scm_to_double (s);
-  Real concaveness = robust_scm2double (beam_->get_property ("concaveness"), 0.0);
+  Real concaveness = calc_concaveness ();
   if (concaveness >= 10000)
     {
       unquanted_y_[LEFT] = unquanted_y_[RIGHT];
@@ -938,7 +1072,7 @@ Beam_scoring_problem::solve () const
 #endif
 
   junk_pointers (configs);
-  if (consistent_broken_slope_)
+  if (align_broken_intos_)
     {
       Interval normalized_endpoints = robust_scm2interval (beam_->get_property ("normalized-endpoints"), Interval (0, 1));
       Real y_length = final_positions[RIGHT] - final_positions[LEFT];
@@ -994,6 +1128,16 @@ Beam_scoring_problem::score_stem_lengths (Beam_configuration *config) const
     score[d] /= max (count[d], 1);
   while (flip (&d) != DOWN);
 
+  /*
+    sometimes, two perfectly symmetric kneed beams will have the same score
+    and can either be quanted up or down.
+
+    we choose the quanting in the direction of the slope so that the first stem
+    always seems longer, reaching to the second, rather than squashed.
+  */
+  if (is_knee_ && count[LEFT] == count[RIGHT] && count[LEFT] == 1 && unquanted_y_.delta ())
+    score[Direction (sign (unquanted_y_.delta ()))] += score[Direction (sign (unquanted_y_.delta ()))] < 1.0 ? 0.01 : 0.0;
+
   config->add (score[LEFT] + score[RIGHT], "L");
 }
 
@@ -1123,8 +1267,15 @@ Beam_scoring_problem::score_forbidden_quants (Beam_configuration *config) const
 
                 /*
                   this parameter is tuned to grace-stem-length.ly
+                  retuned from 0.40 to 0.39 by MS because of slight increases
+                  in gap.length () resulting from measuring beams at real ends
+                  instead of from the middle of stems.
+
+                  TODO:
+                  This function needs better comments so we know what is forbidden
+                  and why.
                 */
-                Real fixed_demerit = 0.4;
+                Real fixed_demerit = 0.39;
 
                 dem += extra_demerit
                        * (fixed_demerit
index 67b1019376100cca3dd68586eda4979fe31024e9..4e810ce18da04508a4ce08253ebb3c1b380268a4 100644 (file)
@@ -336,23 +336,24 @@ operator <(Beam_stem_segment const &a,
 
 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)
+MAKE_SCHEME_CALLBACK (Beam, calc_beam_segments, 1);
+SCM
+Beam::calc_beam_segments (SCM smob)
 {
   /* ugh, this has a side-effect that we need to ensure that
      Stem #'beaming is correct */
+  Grob *me_grob = unsmob_grob (smob);
   (void) me_grob->get_property ("beaming");
 
   Spanner *me = dynamic_cast<Spanner *> (me_grob);
 
   extract_grob_set (me, "stems", stems);
-  Grob *commonx = common_refpoint_of_array (stems, me, X_AXIS);
-
-  commonx = me->get_bound (LEFT)->common_refpoint (commonx, X_AXIS);
-  commonx = me->get_bound (RIGHT)->common_refpoint (commonx, X_AXIS);
 
-  *common = commonx;
+  Grob *commonx = common_refpoint_of_array (stems, me, X_AXIS);
+  Direction d = LEFT;
+  do
+    commonx = me->get_bound (d)->common_refpoint (commonx, X_AXIS);
+  while (flip (&d) != LEFT);
 
   int gap_count = robust_scm2int (me->get_property ("gap-count"), 0);
   Real gap_length = robust_scm2double (me->get_property ("gap"), 0.0);
@@ -373,7 +374,7 @@ Beam::get_beam_segments (Grob *me_grob, Grob **common)
       Real stem_width = robust_scm2double (stem->get_property ("thickness"), 1.0) * lt;
       Real stem_x = stem->relative_coordinate (commonx, X_AXIS);
       SCM beaming = stem->get_property ("beaming");
-      Direction d = LEFT;
+
       do
         {
           // Find the maximum and minimum beam ranks.
@@ -542,6 +543,62 @@ Beam::get_beam_segments (Grob *me_grob, Grob **common)
 
     }
 
+  SCM segments_scm = SCM_EOL;
+  SCM *tail = &segments_scm;
+
+  for (vsize i = 0; i < segments.size (); i++)
+    {
+      *tail = scm_cons (scm_list_2 (scm_cons (ly_symbol2scm ("vertical-count"),
+                                              scm_from_int (segments[i].vertical_count_)),
+                                    scm_cons (ly_symbol2scm ("horizontal"),
+                                              ly_interval2scm (segments[i].horizontal_))),
+                        SCM_EOL);
+      tail = SCM_CDRLOC (*tail);
+    }
+
+  return segments_scm;
+}
+
+MAKE_SCHEME_CALLBACK (Beam, calc_x_positions, 1);
+SCM
+Beam::calc_x_positions (SCM smob)
+{
+  Spanner *me = unsmob_spanner (smob);
+  SCM segments = me->get_property ("beam-segments");
+  Interval x_positions;
+  x_positions.set_empty ();
+  for (SCM s = segments; scm_is_pair (s); s = scm_cdr (s))
+    x_positions.unite (robust_scm2interval (ly_assoc_get (ly_symbol2scm ("horizontal"),
+                                                          scm_car (s),
+                                                          SCM_EOL),
+                                            Interval (0.0, 0.0)));
+
+  // Case for beams without segments (i.e. uniting two skips with a beam)
+  // TODO: should issue a warning?  warning likely issued downstream, but couldn't hurt...
+  if (x_positions.is_empty ())
+    {
+      extract_grob_set (me, "stems", stems);
+      Grob *common_x = common_refpoint_of_array (stems, me, X_AXIS);
+      Direction d = LEFT;
+      do
+        x_positions[d] = me->relative_coordinate (common_x, X_AXIS);
+      while (flip (&d) != LEFT);
+    }
+  return ly_interval2scm (x_positions);
+}
+
+vector<Beam_segment>
+Beam::get_beam_segments (Grob *me)
+{
+  SCM segments_scm = me->get_property ("beam-segments");
+  vector<Beam_segment> segments;
+  for (SCM s = segments_scm; scm_is_pair (s); s = scm_cdr (s))
+    {
+      segments.push_back (Beam_segment ());
+      segments.back ().vertical_count_ = robust_scm2int (ly_assoc_get (ly_symbol2scm ("vertical-count"), scm_car (s), SCM_EOL), 0);
+      segments.back ().horizontal_ = robust_scm2interval (ly_assoc_get (ly_symbol2scm ("horizontal"), scm_car (s), SCM_EOL), Interval (0.0, 0.0));
+    }
+
   return segments;
 }
 
@@ -550,27 +607,30 @@ SCM
 Beam::print (SCM grob)
 {
   Spanner *me = unsmob_spanner (grob);
-  Grob *commonx = 0;
-  vector<Beam_segment> segments = get_beam_segments (me, &commonx);
+  /*
+    TODO - mild code dup for all the commonx calls.
+    Some use just common_refpoint_of_array, some (in print and
+    calc_beam_segments) use this plus calls to get_bound.
+
+    Figure out if there is any particular reason for this and
+    consolidate in one Beam::get_common function.
+  */
+  extract_grob_set (me, "stems", stems);
+  Grob *commonx = common_refpoint_of_array (stems, me, X_AXIS);
+  Direction d = LEFT;
+  do
+    commonx = me->get_bound (d)->common_refpoint (commonx, X_AXIS);
+  while (flip (&d) != LEFT);
+
+  vector<Beam_segment> segments = get_beam_segments (me);
+
   if (!segments.size ())
     return SCM_EOL;
 
-  Interval span;
-  if (normal_stem_count (me))
-    {
-      span[LEFT] = first_normal_stem (me)->relative_coordinate (commonx, X_AXIS);
-      span[RIGHT] = last_normal_stem (me)->relative_coordinate (commonx, X_AXIS);
-    }
-  else
-    {
-      extract_grob_set (me, "stems", stems);
-      span[LEFT] = stems[0]->relative_coordinate (commonx, X_AXIS);
-      span[RIGHT] = stems.back ()->relative_coordinate (commonx, X_AXIS);
-    }
-
   Real blot = me->layout ()->get_dimension (ly_symbol2scm ("blot-diameter"));
 
   SCM posns = me->get_property ("quantized-positions");
+  Interval span = robust_scm2interval (me->get_property ("X-positions"), Interval (0, 0));
   Interval pos;
   if (!is_number_pair (posns))
     {
@@ -593,7 +653,6 @@ Beam::print (SCM grob)
   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_);
@@ -918,15 +977,17 @@ Beam::calc_stem_shorten (SCM smob)
   return scm_from_double (0.0);
 }
 
-MAKE_SCHEME_CALLBACK (Beam, quanting, 1);
+MAKE_SCHEME_CALLBACK (Beam, quanting, 3);
 SCM
-Beam::quanting (SCM smob)
+Beam::quanting (SCM smob, SCM ys_scm, SCM align_broken_intos)
 {
   Grob *me = unsmob_grob (smob);
-  Drul_array<Real> ys (0, 0);
-  Beam_scoring_problem problem (me, ys);
+  Drul_array<Real> ys = robust_scm2drul (ys_scm, Drul_array<Real> (infinity_f, -infinity_f));
+  bool cbs = to_boolean (align_broken_intos);
 
+  Beam_scoring_problem problem (me, ys, cbs);
   ys = problem.solve ();
+
   return ly_interval2scm (ys);
 }
 
@@ -1033,8 +1094,7 @@ Beam::set_stem_lengths (SCM smob)
   Grob *fvs = first_normal_stem (me);
   Grob *lvs = last_normal_stem (me);
 
-  Real xl = fvs ? fvs->relative_coordinate (common[X_AXIS], X_AXIS) : 0.0;
-  Real xr = lvs ? lvs->relative_coordinate (common[X_AXIS], X_AXIS) : 0.0;
+  Interval x_span = robust_scm2interval (me->get_property ("X-positions"), Interval (0,0));
   Direction feather_dir = to_dir (me->get_property ("grow-direction"));
 
   for (vsize i = 0; i < stems.size (); i++)
@@ -1043,7 +1103,7 @@ Beam::set_stem_lengths (SCM smob)
 
       bool french = to_boolean (s->get_property ("french-beaming"));
       Real stem_y = calc_stem_y (me, s, common,
-                                 xl, xr, feather_dir,
+                                 x_span[LEFT], x_span[RIGHT], feather_dir,
                                  pos, french && s != lvs && s != fvs);
 
       /*
@@ -1183,14 +1243,13 @@ Beam::rest_collision_callback (SCM smob, SCM prev_offset)
 
   Real dy = pos[RIGHT] - pos[LEFT];
 
-  Drul_array<Grob *> visible_stems (first_normal_stem (beam),
-                                    last_normal_stem (beam));
   extract_grob_set (beam, "stems", stems);
-
   Grob *common = common_refpoint_of_array (stems, beam, X_AXIS);
 
-  Real x0 = visible_stems[LEFT]->relative_coordinate (common, X_AXIS);
-  Real dx = visible_stems[RIGHT]->relative_coordinate (common, X_AXIS) - x0;
+  Interval x_span = robust_scm2interval (beam->get_property ("X-positions"),
+                                         Interval (0.0, 0.0));
+  Real x0 = x_span[LEFT];
+  Real dx = x_span.length ();
   Real slope = dy && dx ? dy / dx : 0;
 
   Direction d = get_grob_direction (stem);
@@ -1378,7 +1437,6 @@ ADD_INTERFACE (Beam,
                " measured in staffspace.  The @code{direction} property is"
                " not user-serviceable.  Use the @code{direction} property"
                " of @code{Stem} instead.\n"
-               "\n"
                "The following properties may be set in the @code{details}"
                " list.\n"
                "\n"
@@ -1418,11 +1476,11 @@ ADD_INTERFACE (Beam,
                "auto-knee-gap "
                "beamed-stem-shorten "
                "beaming "
+               "beam-segments "
                "beam-thickness "
                "break-overshoot "
                "clip-edges "
                "concaveness "
-               "consistent-broken-slope "
                "collision-interfaces "
                "collision-voice-only "
                "covered-grobs "
@@ -1443,4 +1501,5 @@ ADD_INTERFACE (Beam,
                "shorten "
                "skip-quanting "
                "stems "
+               "X-positions "
               );
index fee15d7d8c7540b127dca7ebb196830e48d7e65b..d60c50f0a68d16e8a9c5070d1de16703596ceb9d 100644 (file)
@@ -115,21 +115,21 @@ struct Beam_collision
 class Beam_scoring_problem
 {
 public:
-  Beam_scoring_problem (Grob *me, Drul_array<Real> ys);
+  Beam_scoring_problem (Grob *me, Drul_array<Real> ys, bool);
   Drul_array<Real> solve () const;
 
 private:
   Spanner *beam_;
 
   Interval unquanted_y_;
-  bool consistent_broken_slope_;
+  bool align_broken_intos_;
+  bool do_initial_slope_calculations_;
 
   Real staff_space_;
   Real beam_thickness_;
   Real line_thickness_;
   Real musical_dy_;
   int normal_stem_count_;
-
   Real x_span_;
 
 
@@ -171,10 +171,11 @@ private:
   vsize first_normal_index ();
   vsize last_normal_index ();
 
-  void init_stems ();
+  void init_instance_variables (Grob *me, Drul_array<Real> ys, bool align_broken_intos);
   void add_collision (Real x, Interval y, Real factor);
   void no_visible_stem_positions ();
   void least_squares_positions ();
+  Real calc_concaveness ();
   void slope_damping ();
   void shift_region_to_valid ();
 
index 425ae585a790d8de8e8829f1b75f17c4fa722888..b78882c4d619ba340de7fbbd81fe013da5158c99 100644 (file)
@@ -77,12 +77,12 @@ public:
   DECLARE_SCHEME_CALLBACK (calc_direction, (SCM));
   DECLARE_SCHEME_CALLBACK (calc_positions, (SCM));
   DECLARE_SCHEME_CALLBACK (calc_normal_stems, (SCM));
-  DECLARE_SCHEME_CALLBACK (calc_concaveness, (SCM));
   DECLARE_SCHEME_CALLBACK (set_stem_lengths, (SCM));
   DECLARE_SCHEME_CALLBACK (calc_cross_staff, (SCM));
-
+  DECLARE_SCHEME_CALLBACK (calc_beam_segments, (SCM));
   /* position callbacks */
-  DECLARE_SCHEME_CALLBACK (quanting, (SCM));
+  DECLARE_SCHEME_CALLBACK (quanting, (SCM, SCM, SCM));
+  DECLARE_SCHEME_CALLBACK (calc_x_positions, (SCM));
 
   static int get_direction_beam_count (Grob *me, Direction d);
 
@@ -90,6 +90,7 @@ private:
   friend class Beam_scoring_problem;
 
   static Direction get_default_dir (Grob *);
+  static vector<Beam_segment> get_beam_segments (Grob *);
   static void set_stem_directions (Grob *, Direction);
   static void consider_auto_knees (Grob *);
   static void set_stem_shorten (Grob *);
index 822a7203aa427d62d92cdc93e66497a7dd4a2746..ba44e83e19627a69facda2f2f17a532ca2c0f78b 100644 (file)
@@ -233,22 +233,39 @@ Spanner::Spanner (Spanner const &s)
   pure_property_cache_ = SCM_UNDEFINED;
 }
 
+/*
+  Certain spanners have pre-computed X values that lie either in
+  X-positions or the X key of the alists returned for left-bound-info
+  and right-bound-info.  These are calculated to give the real length
+  of a spanner (which, because of various padding or overhang properties,
+  can extend pass or arrive short of a given bound).  If possible, we
+  use these to calculate the spanner's length, and otherwise, we use
+  the bound.
+
+  For those writing a new spanner, DO NOT use both X-positions and
+  left-bound-info/right-bound-info.
+*/
 Real
 Spanner::spanner_length () const
 {
-  Interval lr;
+  Interval lr = robust_scm2interval (get_property ("X-positions"),
+                                     Interval (1,-1));
 
-  Drul_array<SCM> bounds (get_property ("left-bound-info"),
-                          get_property ("right-bound-info"));
+  if (lr.is_empty ())
+    {
+      Drul_array<SCM> bounds (get_property ("left-bound-info"),
+                              get_property ("right-bound-info"));
 
-  Direction d = LEFT;
-  do
-    lr[d] = robust_scm2double (ly_assoc_get (ly_symbol2scm ("X"),
+      Direction d = LEFT;
+      do
+        lr[d] = robust_scm2double (ly_assoc_get (ly_symbol2scm ("X"),
                                              bounds[d], SCM_BOOL_F), -d);
-  while (flip (&d) != LEFT);
+      while (flip (&d) != LEFT);
+    }
 
   if (lr.is_empty ())
     {
+      Direction d = LEFT;
       do
         lr[d] = spanned_drul_[d]->relative_coordinate (0, X_AXIS);
       while (flip (&d) != LEFT);
index bbba578e48b8c7e9a3f1ec0dc0d16b817e8d1176..6ee120df7b2717288f7210f2a2288f112524d0e7 100644 (file)
@@ -3257,7 +3257,8 @@ def conv (str):
                   r"#(define \g<2> #{ \\stringTuning\g<3> #})", str)
     return str
 
-@rule ((2, 15, 17), "\\markuplines -> \\markuplist")
+@rule ((2, 15, 17), "\\markuplines -> \\markuplist\n\
+Change Beam broken slope syntax.")
 def conv (str):
     str = re.sub (r"""
 \\markuplines( +)([^ ].*)
@@ -3266,6 +3267,11 @@ def conv (str):
            \g<1>\g<3>""", str)
     str = re.sub (r"\\markuplines", r"\\markuplist", str)
     str = re.sub (r"@funindex markuplines", r"@funindex markuplist", str)
+    if re.search (r'consistent-broken-slope', str):
+        stderr_write ("\n")
+        stderr_write (NOT_SMART % _("consistent-broken-slope, which is now handled through the positions callback.\n"))
+        stderr_write (_ ("input/regression/beam-broken-classic.ly shows how broken beams are now handled.\n"))
+        stderr_write (UPDATE_MANUALLY)
     return str
 
 # Guidelines to write rules (please keep this at the end of this file)
index a6273dd3078bec4815054ba64d73c2c852d1e7cc..c7960fe76a6cf483efab30742c6a97baf2fc3724 100644 (file)
@@ -163,7 +163,6 @@ stick out of its bounds?")
 @code{#t} means visible, @code{#f} means killed.")
      (breakable ,boolean? "Allow breaks here.")
 
-
 ;;
 ;; c
 ;;
@@ -189,8 +188,6 @@ measure of the closeness of the inner stems.  It is used for damping
 the slope of the beam.")
      (connect-to-neighbor ,pair? "Pair of booleans, indicating whether
 this grob looks as a continued break.")
-     (consistent-broken-slope ,boolean? "Keep a beam's slope across line
-breaks.")
      (control-points ,list? "List of offsets (number pairs) that form
 control points for the tie, slur, or bracket shape.  For B@'eziers,
 this should list the control points of a third-order B@'ezier curve.")
@@ -1006,6 +1003,7 @@ function is to protect objects from being garbage collected.")
 
      (bars ,ly:grob-array? "An array of bar line pointers.")
      (beam ,ly:grob? "A pointer to the beam, if applicable.")
+     (beam-segments ,list? "Internal representation of beam segments.")
      (bound-alignment-interfaces ,list "Interfaces to be used
 for positioning elements that align with a column.")
      (bounded-by-me ,ly:grob-array? "An array of spanners that have this
index 36340f4a26b170911bf0fd69323d24f6b454e657..85dd188f5118c961cd3c15144ac60ff858b4fc66 100644 (file)
        ;; todo: clean this up a bit: the list is getting
        ;; rather long.
        (auto-knee-gap . 5.5)
+       (beam-segments . ,ly:beam::calc-beam-segments)
        (beam-thickness . 0.48) ; in staff-space
 
        ;; We have some unreferenced problems here.
                                 note-head-interface
                                 stem-interface
                                 time-signature-interface))
-       (concaveness . ,ly:beam::calc-concaveness)
        (cross-staff . ,ly:beam::calc-cross-staff)
        (damping . 1)
        (details
 
        (gap . 0.8)
        (neutral-direction . ,DOWN)
-       (positions . ,ly:beam::quanting)
+       (positions . ,beam::place-broken-parts-individually)
+       (X-positions . ,ly:beam::calc-x-positions)
 
        ;; this is a hack to set stem lengths, if positions is set.
        (quantized-positions . ,ly:beam::set-stem-lengths)
index 0b3c51bc4b0b48c21a0eaa632e9b56bd732089c5..810d4a6ab05225baaf1079946ef0583097fa2309 100644 (file)
   (lambda (grob)
     ((check-beam-quant l r)
        grob
-       (ly:beam::quanting grob))))
+       (beam::place-broken-parts-individually grob))))
 
 
 (define-public (check-slope-callbacks comparison)
   (lambda (grob)
     ((check-beam-slope-sign comparison)
        grob
-       (ly:beam::quanting grob))))
+       (beam::place-broken-parts-individually grob))))
 
index 5a15551b340998e43b5df97164680359a5044edd..82703e14ac1f0bc9ae336ce5f227aa274edcae41 100644 (file)
     (ly:text-interface::interpret-markup layout props text)))
 
 
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; beam slope
+
+;; calculates each slope of a broken beam individually
+(define-public (beam::place-broken-parts-individually grob)
+  (ly:beam::quanting grob '(+inf.0 . -inf.0) #f))
+
+;; calculates the slope of a beam as a single unit,
+;; even if it is broken.  this assures that the beam
+;; will pick up where it left off after a line break
+(define-public (beam::align-with-broken-parts grob)
+  (ly:beam::quanting grob '(+inf.0 . -inf.0) #t))
+
+;; uses the broken beam style from edition peters combines the
+;; values of place-broken-parts-individually and align-with-broken-parts above,
+;; favoring place-broken-parts-individually when the beam naturally has a steeper
+;; incline and align-with-broken-parts when the beam is flat
+(define-public (beam::slope-like-broken-parts grob)
+  (define (slope y x)
+    (/ (- (cdr y) (car y)) (- (cdr x) (car x))))
+  (let* ((quant1 (ly:beam::quanting grob '(+inf.0 . -inf.0) #t))
+         (original (ly:grob-original grob))
+         (siblings (if (ly:grob? original)
+                       (ly:spanner-broken-into original)
+                       '())))
+    (if (null? siblings)
+        quant1
+        (let* ((quant2 (ly:beam::quanting grob '(+inf.0 . -inf.0) #f))
+               (x-span (ly:grob-property grob 'X-positions))
+               (slope1 (slope quant1 x-span))
+               (slope2 (slope quant2 x-span))
+               (quant2 (if (not (= (sign slope1) (sign slope2)))
+                           '(0 . 0)
+                           quant2))
+               (factor (/ (atan (abs slope1)) PI-OVER-TWO))
+               (base (cons-map
+                       (lambda (x)
+                         (+ (* (x quant1) (- 1 factor))
+                            (* (x quant2) factor)))
+                       (cons car cdr))))
+          (ly:beam::quanting grob base #f)))))
+
 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 ;; cross-staff stuff