From 1d9a73b13ee576d28c0f41f5b243f2ebb1ff9fcf Mon Sep 17 00:00:00 2001 From: Mike Solomon Date: Fri, 21 Oct 2011 09:03:43 +0200 Subject: [PATCH] Implements consistent beam slopes across line breaks. This is a mostly an internal reorganization of the beam code that has several concrete impacts on the way beam slopes are calculated: * ly:beam::calc-least-squares-positions, ly:beam::calc-slope-damping, and ly:beam::shift-region-to-valid are now all instance methods of Beam_scoring_problem and thus no longer open to the users. * by being internal methods, these functions all use the same information (x positions of stems, stem infos, etc.) stored in private variables. * this sharing of information allows for broken beams to share information between their parts, which leads to the consistent slope calculation Additionally, ly:beam::quanting, which used to take the arguments of a grob and a pair of positions, now only takes the grob, as it is the sole generator of positions. All of the functions in layout-beam.scm that relied on multiple Scheme callbacks being chained now only use this function. To turn on this feature, use \override Beam #'consistent-slope = ##t. --- Documentation/changes.tely | 17 + input/regression/beam-concave-chord.ly | 16 +- input/regression/beam-concave.ly | 6 +- .../beam-consistent-broken-slope.ly | 31 + input/regression/beam-default-lengths.ly | 11 +- input/regression/beam-shortened-lengths.ly | 11 +- lily/beam-quanting.cc | 636 ++++++++---------- lily/beam.cc | 7 +- lily/include/beam-scoring-problem.hh | 24 +- lily/include/beam.hh | 6 +- ly/music-functions-init.ly | 14 +- scm/define-grob-properties.scm | 3 + scm/define-grobs.scm | 10 +- scm/layout-beam.scm | 20 +- 14 files changed, 373 insertions(+), 439 deletions(-) create mode 100644 input/regression/beam-consistent-broken-slope.ly diff --git a/Documentation/changes.tely b/Documentation/changes.tely index f7d6faf885..8728b73314 100644 --- a/Documentation/changes.tely +++ b/Documentation/changes.tely @@ -60,6 +60,23 @@ which scares away people. * only show user-visible changes. @end ignore +@item +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] +@end lilypond +To do this, several callback functions are now deprecated. +@itemize +@item @code{ly:beam::calc-least-squares-positions} +@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. + @item Music, event, and scheme functions can now be defined with optional arguments before mandatory arguments. diff --git a/input/regression/beam-concave-chord.ly b/input/regression/beam-concave-chord.ly index 5d5aa30826..5d3aa7c1ce 100644 --- a/input/regression/beam-concave-chord.ly +++ b/input/regression/beam-concave-chord.ly @@ -35,22 +35,12 @@ nonHorizontalBeams = { % cut & paste from beam-concave.ly -#(define (<> x y) (not (= x y))) +#(define (<> x y) (not (= x y))) mustBeHorizontal = { - \override Staff.Beam #'positions = - #(ly:make-simple-closure - (ly:make-simple-closure - (append - (list chain-grob-member-functions `(,cons 0 0)) - (check-slope-callbacks =)))) + \override Staff.Beam #'positions = #(check-slope-callbacks =) } mustNotBeHorizontal = { - \override Staff.Beam #'positions = - #(ly:make-simple-closure - (ly:make-simple-closure - (append - (list chain-grob-member-functions `(,cons 0 0)) - (check-slope-callbacks <>)))) + \override Staff.Beam #'positions = #(check-slope-callbacks <>) } \new Voice { diff --git a/input/regression/beam-concave.ly b/input/regression/beam-concave.ly index d47a9b1cbb..8394f9a5d1 100644 --- a/input/regression/beam-concave.ly +++ b/input/regression/beam-concave.ly @@ -58,11 +58,7 @@ rossBeams = \relative c'' { #(define (<> x y) (not (= x y))) \new Voice { - \override Beam #'positions = #(ly:make-simple-closure - (ly:make-simple-closure - (append - (list chain-grob-member-functions `(,cons 0 0)) - (check-slope-callbacks =)))) + \override Beam #'positions = #(check-slope-callbacks =) \rossBeams } diff --git a/input/regression/beam-consistent-broken-slope.ly b/input/regression/beam-consistent-broken-slope.ly new file mode 100644 index 0000000000..ddebaf5239 --- /dev/null +++ b/input/regression/beam-consistent-broken-slope.ly @@ -0,0 +1,31 @@ + +\version "2.15.15" + +\header { + texidoc = "The @code{consistent-broken-slope} property of @{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-default-lengths.ly b/input/regression/beam-default-lengths.ly index e6e3a26516..e74b08e4b8 100644 --- a/input/regression/beam-default-lengths.ly +++ b/input/regression/beam-default-lengths.ly @@ -6,15 +6,6 @@ is switched off in this example." } \relative c' { - \override Beam #'positions = - #(ly:make-simple-closure - (ly:make-simple-closure - (list chain-grob-member-functions - `(,cons 0 0) - ly:beam::calc-least-squares-positions - ly:beam::slope-damping - ly:beam::shift-region-to-valid - ))) - + \override Beam #'skip-quanting = ##t f4 f8[ f] f16[ f] f32[ f] f64[ f] f128[ f] } diff --git a/input/regression/beam-shortened-lengths.ly b/input/regression/beam-shortened-lengths.ly index 28b23fea18..a79917d323 100644 --- a/input/regression/beam-shortened-lengths.ly +++ b/input/regression/beam-shortened-lengths.ly @@ -8,16 +8,7 @@ \relative c'{ - \override Beam #'positions = - #(ly:make-simple-closure - (ly:make-simple-closure - (list chain-grob-member-functions - `(,cons 0 0) - ly:beam::calc-least-squares-positions - ly:beam::slope-damping - ly:beam::shift-region-to-valid - ))) - + \override Beam #'skip-quanting = ##t \stemUp f'4 f8[ f] f16[ f] f32[ f] f64[ f] f128[ f] } diff --git a/lily/beam-quanting.cc b/lily/beam-quanting.cc index 8baf0161b4..c064b9d933 100644 --- a/lily/beam-quanting.cc +++ b/lily/beam-quanting.cc @@ -39,7 +39,7 @@ using namespace std; #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 "stencil.hh" #include "stem.hh" @@ -132,7 +132,7 @@ Beam_configuration *Beam_configuration::new_config (Interval start, Real Beam_scoring_problem::y_at (Real x, Beam_configuration const *p) const { - return p->y[LEFT] + (x - x_span_[LEFT]) * p->y.delta () / x_span_.delta (); + return p->y[LEFT] + x * p->y.delta () / x_span_; } /****************************************************************/ @@ -159,18 +159,7 @@ LY_DEFINE (ly_beam_score_count, "ly:beam-score-count", 0, 0, 0, void Beam_scoring_problem::add_collision (Real x, Interval y, Real score_factor) { - if (edge_dirs_[LEFT] == edge_dirs_[RIGHT]) - { - Direction d = edge_dirs_[LEFT]; - - Real quant_range_y = quant_range_[LEFT][-d] - + (x - x_span_[LEFT]) * (quant_range_[RIGHT][-d] - quant_range_[LEFT][-d]) / x_span_.delta (); - - if (d * (quant_range_y - minmax (d, y[UP], y[DOWN])) > 0) - { - return; - } - } + // We used to screen for quant range, but no more. Beam_collision c; c.beam_y_.set_empty (); @@ -192,139 +181,204 @@ void Beam_scoring_problem::add_collision (Real x, Interval y, collisions_.push_back (c); } -void Beam_scoring_problem::init_collisions (vector grobs) +void Beam_scoring_problem::init_stems () { - Grob *common_x = NULL; - segments_ = Beam::get_beam_segments (beam_, &common_x); - vector_sort (segments_, beam_segment_less); - if (common_[X_AXIS] != common_x) + vector beams; + if (consistent_broken_slope_) { - programming_error ("Disagree on common x. Skipping collisions in beam scoring."); - return; + Spanner *orig = dynamic_cast (beam_->original ()); + if (!orig) + consistent_broken_slope_ = false; + else if (!orig->broken_intos_.size ()) + consistent_broken_slope_ = false; + else + beams.insert (beams.end (), orig->broken_intos_.begin (), orig->broken_intos_.end ()); } + if (!consistent_broken_slope_) + beams.push_back (beam_); - set stems; - for (vsize i = 0; i < grobs.size (); i++) + x_span_ = 0.0; + normal_stem_count_ = 0; + for (vsize i = 0; i < beams.size (); i++) { - Box b; - for (Axis a = X_AXIS; a < NO_AXES; incr (a)) - b[a] = grobs[i]->extent (common_[a], a); + Interval local_x_span; + extract_grob_set (beams[i], "stems", stems); + extract_grob_set (beams[i], "covered-grobs", fake_collisions); + vector collisions; - Real width = b[X_AXIS].length (); - Real width_factor = sqrt (width / staff_space_); + for (vsize j = 0; j < fake_collisions.size (); j++) + if (fake_collisions[j]->get_system () == beams[i]->get_system ()) + collisions.push_back (fake_collisions[j]); + Grob *common[2]; + 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 edge_stems (Beam::first_normal_stem (beams[i]), + Beam::last_normal_stem (beams[i])); Direction d = LEFT; do - add_collision (b[X_AXIS][d], b[Y_AXIS], width_factor); + local_x_span[d] = edge_stems[d] ? edge_stems[d]->relative_coordinate (common[X_AXIS], X_AXIS) : 0.0; while (flip (&d) != LEFT); - Grob *stem = unsmob_grob (grobs[i]->get_object ("stem")); - if (stem && Stem::has_interface (stem) && Stem::is_normal_stem (stem)) + Drul_array dirs_found (0, 0); + + Real my_y = beams[i]->relative_coordinate (common[Y_AXIS], Y_AXIS); + + Interval beam_width (-1.0,-1.0); + for (vsize j = 0; j < stems.size (); j++) { - stems.insert (stem); + Grob *s = stems[j]; + beam_multiplicity_.push_back (Stem::beam_multiplicity (stems[j])); + head_positions_.push_back (Stem::head_positions (stems[j])); + is_normal_.push_back (Stem::is_normal_stem (stems[j])); + + Stem_info si (Stem::get_stem_info (s)); + si.scale (1 / staff_space_); + stem_infos_.push_back (si); + chord_start_y_.push_back (Stem::chord_start_y (s)); + dirs_found[si.dir_] = true; + + 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, + 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_ypositions_.push_back (s->relative_coordinate (common[Y_AXIS], Y_AXIS) - my_y); + if (is_normal_.back ()) + { + if (beam_width[LEFT] == -1.0) + beam_width[LEFT] = stem_xpositions_.back (); + beam_width[RIGHT] = stem_xpositions_.back (); + } } - } - for (set::const_iterator it (stems.begin ()); it != stems.end (); it++) - { - Grob *s = *it; - Real x = s->extent (common_[X_AXIS], X_AXIS).center (); - - Direction stem_dir = get_grob_direction (*it); - Interval y; - y.set_full (); - y[-stem_dir] = Stem::chord_start_y (*it) + (*it)->relative_coordinate (common_[Y_AXIS], Y_AXIS) - - beam_->relative_coordinate (common_[Y_AXIS], Y_AXIS); - - Real factor = parameters_.STEM_COLLISION_FACTOR; - if (!unsmob_grob (s->get_object ("beam"))) - factor = 1.0; - add_collision (x, y, factor); - } -} + edge_dirs_ = Drul_array (CENTER, CENTER); + normal_stem_count_ += Beam::normal_stem_count (beams[i]); + if (normal_stem_count_) + edge_dirs_ = Drul_array (stem_infos_[0].dir_, + stem_infos_.back ().dir_); -void Beam_scoring_problem::init_stems () -{ - extract_grob_set (beam_, "covered-grobs", collisions); - extract_grob_set (beam_, "stems", stems); - for (int a = 2; a--;) - { - common_[a] = common_refpoint_of_array (stems, beam_, Axis (a)); - common_[a] = common_refpoint_of_array (collisions, common_[a], Axis (a)); - } + is_xstaff_ = Align_interface::has_interface (common[Y_AXIS]); + is_knee_ = dirs_found[LEFT] && dirs_found[RIGHT]; - Drul_array edge_stems (Beam::first_normal_stem (beam_), - Beam::last_normal_stem (beam_)); - Direction d = LEFT; - do - x_span_[d] = edge_stems[d] ? edge_stems[d]->relative_coordinate (common_[X_AXIS], X_AXIS) : 0.0; - while (flip (&d) != LEFT); + staff_radius_ = Staff_symbol_referencer::staff_radius (beams[i]); + edge_beam_counts_ = Drul_array + (Stem::beam_multiplicity (stems[0]).length () + 1, + Stem::beam_multiplicity (stems.back ()).length () + 1); - Drul_array dirs_found (0, 0); - for (vsize i = 0; i < stems.size (); i++) - { - Grob *s = stems[i]; - if (!Stem::is_normal_stem (s)) - continue; + // TODO - why are we dividing by staff_space_? + beam_translation_ = Beam::get_beam_translation (beams[i]) / staff_space_; - Stem_info si (Stem::get_stem_info (s)); - si.scale (1 / staff_space_); - stem_infos_.push_back (si); - dirs_found[si.dir_] = true; + d = LEFT; + do + { + quant_range_[d].set_full (); + if (!edge_stems[d]) + continue; + + Real stem_offset = edge_stems[d]->relative_coordinate (common[Y_AXIS], Y_AXIS) + - beams[i]->relative_coordinate (common[Y_AXIS], Y_AXIS); + Interval heads = Stem::head_positions (edge_stems[d]) * 0.5 * staff_space_; + + Direction ed = edge_dirs_[d]; + heads.widen (0.5 * staff_space_ + + (edge_beam_counts_[d] - 1) * beam_translation_ + beam_thickness_ * .5); + 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); + vector_sort (segments_, beam_segment_less); + for (vsize j = 0; j < segments_.size (); j++) + segments_[j].horizontal_ += (x_span_ - x_left); + + set colliding_stems; + for (vsize j = 0; j < collisions.size (); j++) + { + if (!collisions[j]->is_live ()) + continue; - bool f = to_boolean (s->get_property ("french-beaming")) - && s != edge_stems[LEFT] && s != edge_stems[RIGHT]; + if (Beam::has_interface (collisions[j]) && Beam::is_cross_staff (collisions[j])) + continue; - Real y = Beam::calc_stem_y (beam_, s, common_, x_span_[LEFT], x_span_[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)); - } + Box b; + for (Axis a = X_AXIS; a < NO_AXES; incr (a)) + b[a] = collisions[j]->extent (common[a], a); - edge_dirs_ = Drul_array (CENTER, CENTER); - if (stem_infos_.size ()) - { - edge_dirs_ = Drul_array (stem_infos_[0].dir_, - stem_infos_.back ().dir_); - } + if (b[X_AXIS].is_empty () || b[Y_AXIS].is_empty ()) + continue; - is_xstaff_ = Align_interface::has_interface (common_[Y_AXIS]); - is_knee_ = dirs_found[LEFT] && dirs_found[RIGHT]; + b[X_AXIS] += (x_span_ - x_left); + Real width = b[X_AXIS].length (); + Real width_factor = sqrt (width / staff_space_); - staff_radius_ = Staff_symbol_referencer::staff_radius (beam_); - edge_beam_counts_ = Drul_array - (Stem::beam_multiplicity (stems[0]).length () + 1, - Stem::beam_multiplicity (stems.back ()).length () + 1); + Direction d = LEFT; + do + add_collision (b[X_AXIS][d], b[Y_AXIS], width_factor); + while (flip (&d) != LEFT); - // TODO - why are we dividing by staff_space_? - beam_translation_ = Beam::get_beam_translation (beam_) / staff_space_; + Grob *stem = unsmob_grob (collisions[j]->get_object ("stem")); + if (stem && Stem::has_interface (stem) && Stem::is_normal_stem (stem)) + { + colliding_stems.insert (stem); + } + } - d = LEFT; - do + for (set::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 (); + + Direction stem_dir = get_grob_direction (*it); + Interval y; + y.set_full (); + y[-stem_dir] = Stem::chord_start_y (*it) + (*it)->relative_coordinate (common[Y_AXIS], Y_AXIS) + - beams[i]->relative_coordinate (common[Y_AXIS], Y_AXIS); + + Real factor = parameters_.STEM_COLLISION_FACTOR; + if (!unsmob_grob (s->get_object ("beam"))) + factor = 1.0; + add_collision (x, y, factor); + } + 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_) { - quant_range_[d].set_full (); - if (!edge_stems[d]) - continue; + Interval trimmings (0.0, 0.0); + Direction d = LEFT; - Real stem_offset = edge_stems[d]->relative_coordinate (common_[Y_AXIS], Y_AXIS) - - beam_->relative_coordinate (common_[Y_AXIS], Y_AXIS); - Interval heads = Stem::head_positions (edge_stems[d]) * 0.5 * staff_space_; + 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); - Direction ed = edge_dirs_[d]; - heads.widen (0.5 * staff_space_ - + (edge_beam_counts_[d] - 1) * beam_translation_ + beam_thickness_ * .5); - quant_range_[d][-ed] = heads[ed] + stem_offset; - } - while (flip (&d) != LEFT); + do + x_span_ -= trimmings[d]; + while (flip (&d) != LEFT); - init_collisions (collisions); + for (vsize i = 0; i < stem_xpositions_.size (); i++) + stem_xpositions_[i] -= trimmings[LEFT]; + } } Beam_scoring_problem::Beam_scoring_problem (Grob *me, Drul_array ys) { - beam_ = me; + beam_ = dynamic_cast (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_. @@ -338,6 +392,9 @@ Beam_scoring_problem::Beam_scoring_problem (Grob *me, Drul_array ys) parameters_.fill (me); init_stems (); + least_squares_positions (); + slope_damping (); + shift_region_to_valid (); } // Assuming V is not empty, pick a 'reasonable' point inside V. @@ -384,72 +441,74 @@ set_minimum_dy (Grob *me, Real *dy) } } -Interval -Beam::no_visible_stem_positions (Grob *me, Interval default_value) +void +Beam_scoring_problem::no_visible_stem_positions () { - extract_grob_set (me, "stems", stems); - if (stems.empty ()) - return default_value; + if (!head_positions_.size ()) + { + unquanted_y_ = Interval (0, 0); + return; + } Interval head_positions; Slice multiplicity; - for (vsize i = 0; i < stems.size (); i++) + for (vsize i = 0; i < head_positions_.size (); i++) { - head_positions.unite (Stem::head_positions (stems[i])); - multiplicity.unite (Stem::beam_multiplicity (stems[i])); + head_positions.unite (head_positions_[i]); + multiplicity.unite (beam_multiplicity_[i]); } - Direction dir = get_grob_direction (me); + Direction dir = get_grob_direction (beam_); if (!dir) programming_error ("The beam should have a direction by now."); Real y = head_positions.linear_combination (dir) - * 0.5 * Staff_symbol_referencer::staff_space (me) - + dir * get_beam_translation (me) * (multiplicity.length () + 1); + * 0.5 * staff_space_ + + dir * beam_translation_ * (multiplicity.length () + 1); - y /= Staff_symbol_referencer::staff_space (me); - return Interval (y, y); + unquanted_y_ = Interval (y, y); } -/* - Compute a first approximation to the beam slope. -*/ -MAKE_SCHEME_CALLBACK (Beam, calc_least_squares_positions, 2); -SCM -Beam::calc_least_squares_positions (SCM smob, SCM /* posns */) +vsize +Beam_scoring_problem::first_normal_index () { - Grob *me = unsmob_grob (smob); + for (vsize i = 0; i < is_normal_.size (); i++) + if (is_normal_[i]) + return i; - int count = normal_stem_count (me); - Interval pos (0, 0); - if (count < 1) - return ly_interval2scm (no_visible_stem_positions (me, pos)); + beam_->programming_error ("No normal stems, but asking for first normal stem index."); + return 0; +} - vector x_posns; - extract_grob_set (me, "normal-stems", stems); - Grob *commonx = common_refpoint_of_array (stems, me, X_AXIS); - Grob *commony = common_refpoint_of_array (stems, me, Y_AXIS); +vsize +Beam_scoring_problem::last_normal_index () +{ + for (vsize i = is_normal_.size (); i--;) + if (is_normal_[i]) + return i; - Real my_y = me->relative_coordinate (commony, Y_AXIS); + beam_->programming_error ("No normal stems, but asking for first normal stem index."); + return 0; +} - Grob *fvs = first_normal_stem (me); - Grob *lvs = last_normal_stem (me); +void +Beam_scoring_problem::least_squares_positions () +{ + if (!normal_stem_count_) + { + no_visible_stem_positions (); + return; + } - Interval ideal (Stem::get_stem_info (fvs).ideal_y_ - + fvs->relative_coordinate (commony, Y_AXIS) - my_y, - Stem::get_stem_info (lvs).ideal_y_ - + lvs->relative_coordinate (commony, Y_AXIS) - my_y); + if (stem_infos_.size () < 1) + return; - Real x0 = first_normal_stem (me)->relative_coordinate (commonx, X_AXIS); - for (vsize i = 0; i < stems.size (); i++) - { - Grob *s = stems[i]; + vsize fnx = first_normal_index (); + vsize lnx = last_normal_index (); - Real x = s->relative_coordinate (commonx, X_AXIS) - x0; - x_posns.push_back (x); - } - Real dx = last_normal_stem (me)->relative_coordinate (commonx, X_AXIS) - x0; + Interval ideal (stem_infos_[fnx].ideal_y_ + stem_ypositions_[fnx], + stem_infos_[lnx].ideal_y_ + stem_ypositions_[lnx]); Real y = 0; Real slope = 0; @@ -457,8 +516,8 @@ Beam::calc_least_squares_positions (SCM smob, SCM /* posns */) Real ldy = 0.0; if (!ideal.delta ()) { - Interval chord (Stem::chord_start_y (stems[0]), - Stem::chord_start_y (stems.back ())); + Interval chord (chord_start_y_[0], + chord_start_y_.back ()); /* Simple beams (2 stems) on middle line should be allowed to be slightly sloped. @@ -467,158 +526,86 @@ Beam::calc_least_squares_positions (SCM smob, SCM /* posns */) ideal[LEFT] == ideal[RIGHT] and ideal.delta () == 0. For that case, we apply artificial slope */ - if (!ideal[LEFT] && chord.delta () && count == 2) + if (!ideal[LEFT] && chord.delta () && stem_infos_.size () == 2) { /* FIXME. -> UP */ Direction d = (Direction) (sign (chord.delta ()) * UP); - pos[d] = get_beam_thickness (me) / 2; - pos[-d] = -pos[d]; + unquanted_y_[d] = Beam::get_beam_thickness (beam_) / 2; + unquanted_y_[-d] = -unquanted_y_[d]; } else - pos = ideal; + 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 = pos[RIGHT] - pos[LEFT]; + ldy = unquanted_y_[RIGHT] - unquanted_y_[LEFT]; } else { vector ideals; - for (vsize i = 0; i < stems.size (); i++) - { - Grob *s = stems[i]; - ideals.push_back (Offset (x_posns[i], - Stem::get_stem_info (s).ideal_y_ - + s->relative_coordinate (commony, Y_AXIS) - - my_y)); - } + for (vsize i = 0; i < stem_infos_.size (); i++) + if (is_normal_[i]) + ideals.push_back (Offset (stem_xpositions_[i], + stem_infos_[i].ideal_y_ + + stem_ypositions_[i])); minimise_least_squares (&slope, &y, ideals); - dy = slope * dx; + dy = slope * x_span_; - set_minimum_dy (me, &dy); + set_minimum_dy (beam_, &dy); ldy = dy; - pos = Interval (y, (y + dy)); + unquanted_y_ = Interval (y, (y + dy)); } - /* - "position" is relative to the staff. - */ - scale_drul (&pos, 1 / Staff_symbol_referencer::staff_space (me)); - - me->set_property ("least-squares-dy", scm_from_double (ldy)); - return ly_interval2scm (pos); + musical_dy_ = ldy; } -/* This neat trick is by Werner Lemberg, - damped = tanh (slope) - corresponds with some tables in [Wanske] CHECKME */ -MAKE_SCHEME_CALLBACK (Beam, slope_damping, 2); -SCM -Beam::slope_damping (SCM smob, SCM posns) +void +Beam_scoring_problem::slope_damping () { - Grob *me = unsmob_grob (smob); - Drul_array pos = ly_scm2interval (posns); - - if (normal_stem_count (me) <= 1) - return posns; + if (normal_stem_count_ <= 1) + return; - SCM s = me->get_property ("damping"); + SCM s = beam_->get_property ("damping"); Real damping = scm_to_double (s); - Real concaveness = robust_scm2double (me->get_property ("concaveness"), 0.0); + Real concaveness = robust_scm2double (beam_->get_property ("concaveness"), 0.0); if (concaveness >= 10000) { - pos[LEFT] = pos[RIGHT]; - me->set_property ("least-squares-dy", scm_from_double (0)); + unquanted_y_[LEFT] = unquanted_y_[RIGHT]; + musical_dy_ = 0; damping = 0; } if (damping) { - scale_drul (&pos, Staff_symbol_referencer::staff_space (me)); - - Real dy = pos[RIGHT] - pos[LEFT]; - - Grob *fvs = first_normal_stem (me); - Grob *lvs = last_normal_stem (me); - - Grob *commonx = fvs->common_refpoint (lvs, X_AXIS); - - Real dx = last_normal_stem (me)->relative_coordinate (commonx, X_AXIS) - - first_normal_stem (me)->relative_coordinate (commonx, X_AXIS); + Real dy = unquanted_y_[RIGHT] - unquanted_y_[LEFT]; - Real slope = dy && dx ? dy / dx : 0; + Real slope = dy && x_span_ ? dy / x_span_ : 0; slope = 0.6 * tanh (slope) / (damping + concaveness); - Real damped_dy = slope * dx; + Real damped_dy = slope * x_span_; - set_minimum_dy (me, &damped_dy); + set_minimum_dy (beam_, &damped_dy); - pos[LEFT] += (dy - damped_dy) / 2; - pos[RIGHT] -= (dy - damped_dy) / 2; - - scale_drul (&pos, 1 / Staff_symbol_referencer::staff_space (me)); + unquanted_y_[LEFT] += (dy - damped_dy) / 2; + unquanted_y_[RIGHT] -= (dy - damped_dy) / 2; } - - return ly_interval2scm (pos); } -/* - We can't combine with previous function, since check concave and - slope damping comes first. - - TODO: we should use the concaveness to control the amount of damping - applied. -*/ -MAKE_SCHEME_CALLBACK (Beam, shift_region_to_valid, 2); -SCM -Beam::shift_region_to_valid (SCM grob, SCM posns) +void +Beam_scoring_problem::shift_region_to_valid () { - Grob *me = unsmob_grob (grob); - - /* - Code dup. - */ - vector x_posns; - extract_grob_set (me, "stems", stems); - 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; - 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 (common[X_AXIS], X_AXIS) - x_span[LEFT]; - x_posns.push_back (x); - } - - Grob *lvs = last_normal_stem (me); - x_span[RIGHT] = lvs->relative_coordinate (common[X_AXIS], X_AXIS); + if (!normal_stem_count_) + return; - Drul_array pos = ly_scm2interval (posns); - - scale_drul (&pos, Staff_symbol_referencer::staff_space (me)); - - 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; + Real beam_dy = unquanted_y_[RIGHT] - unquanted_y_[LEFT]; + Real slope = x_span_ ? beam_dy / x_span_ : 0.0; /* Shift the positions so that we have a chance of finding good @@ -627,28 +614,21 @@ Beam::shift_region_to_valid (SCM grob, SCM posns) Interval feasible_left_point; feasible_left_point.set_full (); - for (vsize i = 0; i < stems.size (); i++) + for (vsize i = 0; i < stem_infos_.size (); i++) { - Grob *s = stems[i]; - if (Stem::is_invisible (s)) - continue; - - Direction d = get_grob_direction (s); + // TODO - check for invisible here... Real left_y - = Stem::get_stem_info (s).shortest_y_ - - slope * x_posns [i]; + = stem_infos_[i].shortest_y_ + - slope * stem_xpositions_ [i]; /* left_y is now relative to the stem S. We want relative to ourselves, so translate: */ - left_y - += + s->relative_coordinate (common[Y_AXIS], Y_AXIS) - - me->relative_coordinate (common[Y_AXIS], Y_AXIS); - + left_y += stem_ypositions_[i]; Interval flp; flp.set_full (); - flp[-d] = left_y; + flp[-stem_infos_[i].dir_] = left_y; feasible_left_point.intersect (flp); } @@ -669,82 +649,24 @@ Beam::shift_region_to_valid (SCM grob, SCM posns) // A list of intervals into which beams may not fall vector forbidden_intervals; - for (vsize i = 0; i < covered.size (); i++) + for (vsize i = 0; i < collisions_.size (); i++) { - if (!covered[i]->is_live ()) - continue; - - if (Beam::has_interface (covered[i]) && is_cross_staff (covered[i])) + if (collisions_[i].x_ < 0 || collisions_[i].x_ > x_span_) 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) + if (collisions_[i].y_.length () < min_y_size) continue; Direction d = LEFT; do { - Real x = b[X_AXIS][d] - x_span[LEFT]; - Real dy = slope * x; + Real dy = slope * collisions_[i].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); - + Real left_y = collisions_[i].y_[yd] - dy; disallowed[yd] = left_y; } while (flip (&yd) != DOWN); @@ -754,13 +676,9 @@ Beam::shift_region_to_valid (SCM grob, SCM posns) 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; + Real beam_left_y = unquanted_y_[LEFT]; Interval feasible_beam_placements (beam_left_y, beam_left_y); /* @@ -823,13 +741,10 @@ Beam::shift_region_to_valid (SCM grob, SCM posns) else { // We are completely screwed. - me->warning (_ ("no viable initial configuration found: may not find good beam slope")); + beam_->warning (_ ("no viable initial configuration found: may not find good beam slope")); } - pos = Drul_array (beam_left_y, (beam_left_y + beam_dy)); - scale_drul (&pos, 1 / Staff_symbol_referencer::staff_space (me)); - - return ly_interval2scm (pos); + unquanted_y_ = Drul_array (beam_left_y, (beam_left_y + beam_dy)); } void @@ -956,6 +871,9 @@ Beam_scoring_problem::solve () const return unquanted_y_; } + if (to_boolean (beam_->get_property ("skip-quanting"))) + return unquanted_y_; + Beam_configuration *best = NULL; bool debug @@ -1020,6 +938,15 @@ Beam_scoring_problem::solve () const #endif junk_pointers (configs); + if (consistent_broken_slope_) + { + Interval normalized_endpoints = robust_scm2interval (beam_->get_property ("normalized-endpoints"), Interval (0, 1)); + Real y_length = final_positions[RIGHT] - final_positions[LEFT]; + + final_positions[LEFT] += normalized_endpoints[LEFT] * y_length; + final_positions[RIGHT] -= (1 - normalized_endpoints[RIGHT]) * y_length; + } + return final_positions; } @@ -1032,10 +959,13 @@ Beam_scoring_problem::score_stem_lengths (Beam_configuration *config) const for (vsize i = 0; i < stem_xpositions_.size (); i++) { + if (!is_normal_[i]) + continue; + Real x = stem_xpositions_[i]; - Real dx = x_span_.delta (); + Real dx = x_span_; Real beam_y = dx - ? config->y[RIGHT] * (x - x_span_[LEFT]) / dx + config->y[LEFT] * (x_span_[RIGHT] - x) / dx + ? config->y[RIGHT] * x / dx + config->y[LEFT] * (x_span_ - x) / dx : (config->y[RIGHT] + config->y[LEFT]) / 2; Real current_y = beam_y + base_lengths_[i]; Real length_pen = parameters_.STEM_LENGTH_DEMERIT_FACTOR; @@ -1084,7 +1014,7 @@ Beam_scoring_problem::score_slope_direction (Beam_configuration *config) const { if (!dy) { - if (fabs (damped_dy / x_span_.delta ()) > parameters_.ROUND_TO_ZERO_SLOPE) + if (fabs (damped_dy / x_span_) > parameters_.ROUND_TO_ZERO_SLOPE) dem += parameters_.DAMPING_DIRECTION_PENALTY; else dem += parameters_.HINT_DIRECTION_PENALTY; diff --git a/lily/beam.cc b/lily/beam.cc index 030aa5d531..67b1019376 100644 --- a/lily/beam.cc +++ b/lily/beam.cc @@ -918,13 +918,12 @@ Beam::calc_stem_shorten (SCM smob) return scm_from_double (0.0); } -MAKE_SCHEME_CALLBACK (Beam, quanting, 2); +MAKE_SCHEME_CALLBACK (Beam, quanting, 1); SCM -Beam::quanting (SCM smob, SCM posns) +Beam::quanting (SCM smob) { Grob *me = unsmob_grob (smob); Drul_array ys (0, 0); - ys = robust_scm2drul (posns, ys); Beam_scoring_problem problem (me, ys); ys = problem.solve (); @@ -1423,6 +1422,7 @@ ADD_INTERFACE (Beam, "break-overshoot " "clip-edges " "concaveness " + "consistent-broken-slope " "collision-interfaces " "collision-voice-only " "covered-grobs " @@ -1441,5 +1441,6 @@ ADD_INTERFACE (Beam, "positions " "quantized-positions " "shorten " + "skip-quanting " "stems " ); diff --git a/lily/include/beam-scoring-problem.hh b/lily/include/beam-scoring-problem.hh index 1603a62444..fee15d7d8c 100644 --- a/lily/include/beam-scoring-problem.hh +++ b/lily/include/beam-scoring-problem.hh @@ -111,8 +111,6 @@ struct Beam_collision Parameters for a single beam. Precomputed to save time in scoring individual configurations. - TODO - use trailing _ on data members. - */ class Beam_scoring_problem { @@ -121,18 +119,19 @@ public: Drul_array solve () const; private: - Grob *beam_; + Spanner *beam_; Interval unquanted_y_; + bool consistent_broken_slope_; Real staff_space_; Real beam_thickness_; Real line_thickness_; Real musical_dy_; + int normal_stem_count_; - Interval x_span_; + Real x_span_; - vector stem_infos_; /* Do stem computations. These depend on YL and YR linearly, so we can @@ -142,10 +141,15 @@ private: affine linear in YL and YR. If YL == YR == 0, then we might have stem_y != 0.0, when we're cross staff. */ + vector stem_infos_; + vector chord_start_y_; + vector head_positions_; + vector beam_multiplicity_; + vector is_normal_; vector base_lengths_; vector stem_xpositions_; + vector stem_ypositions_; - Grob *common_[2]; bool is_xstaff_; bool is_knee_; @@ -164,9 +168,15 @@ private: vector collisions_; vector segments_; + vsize first_normal_index (); + vsize last_normal_index (); + void init_stems (); - void init_collisions (vector grobs); void add_collision (Real x, Interval y, Real factor); + void no_visible_stem_positions (); + void least_squares_positions (); + void slope_damping (); + void shift_region_to_valid (); void one_scorer (Beam_configuration *config) const; Beam_configuration *force_score (SCM inspect_quants, diff --git a/lily/include/beam.hh b/lily/include/beam.hh index f99fbf8028..425ae585a7 100644 --- a/lily/include/beam.hh +++ b/lily/include/beam.hh @@ -68,7 +68,6 @@ public: static Real get_beam_thickness (Grob *me); static void connect_beams (Grob *me); static vector get_beam_segments (Grob *me_grob, Grob **common); - static Interval no_visible_stem_positions (Grob *me, Interval default_value); DECLARE_SCHEME_CALLBACK (rest_collision_callback, (SCM element, SCM prev_off)); DECLARE_SCHEME_CALLBACK (pure_rest_collision_callback, (SCM element, SCM prev_off, SCM, SCM)); @@ -77,16 +76,13 @@ public: DECLARE_SCHEME_CALLBACK (calc_stem_shorten, (SCM)); DECLARE_SCHEME_CALLBACK (calc_direction, (SCM)); DECLARE_SCHEME_CALLBACK (calc_positions, (SCM)); - DECLARE_SCHEME_CALLBACK (calc_least_squares_positions, (SCM, 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)); /* position callbacks */ - DECLARE_SCHEME_CALLBACK (shift_region_to_valid, (SCM, SCM)); - DECLARE_SCHEME_CALLBACK (slope_damping, (SCM, SCM)); - DECLARE_SCHEME_CALLBACK (quanting, (SCM, SCM)); + DECLARE_SCHEME_CALLBACK (quanting, (SCM)); static int get_direction_beam_count (Grob *me, Direction d); diff --git a/ly/music-functions-init.ly b/ly/music-functions-init.ly index 369d62ea3f..32b4614ee2 100644 --- a/ly/music-functions-init.ly +++ b/ly/music-functions-init.ly @@ -124,23 +124,13 @@ appoggiatura = assertBeamQuant = #(define-music-function (parser location l r) (pair? pair?) (_i "Testing function: check whether the beam quants @var{l} and @var{r} are correct") - (make-grob-property-override 'Beam 'positions - (ly:make-simple-closure - (ly:make-simple-closure - (append - (list chain-grob-member-functions `(,cons 0 0)) - (check-quant-callbacks l r)))))) + (make-grob-property-override 'Beam 'positions (check-quant-callbacks l r))) % for regression testing purposes. assertBeamSlope = #(define-music-function (parser location comp) (procedure?) (_i "Testing function: check whether the slope of the beam is the same as @code{comp}") - (make-grob-property-override 'Beam 'positions - (ly:make-simple-closure - (ly:make-simple-closure - (append - (list chain-grob-member-functions `(,cons 0 0)) - (check-slope-callbacks comp)))))) + (make-grob-property-override 'Beam 'positions (check-slope-callbacks comp))) autochange = #(define-music-function (parser location music) (ly:music?) diff --git a/scm/define-grob-properties.scm b/scm/define-grob-properties.scm index 34363155f5..2c93c3a55c 100644 --- a/scm/define-grob-properties.scm +++ b/scm/define-grob-properties.scm @@ -186,6 +186,8 @@ 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.") @@ -744,6 +746,7 @@ object.") (simple-Y ,boolean? "Should the Y placement of a spanner disregard changes in system heights?") (size ,number? "Size of object, relative to standard size.") + (skip-quanting ,boolean? "Should beam quanting be skipped?") (skyline-horizontal-padding ,number? "For determining the vertical distance between two staves, it is possible to have a configuration which would result in a tight interleaving of grobs from diff --git a/scm/define-grobs.scm b/scm/define-grobs.scm index 05ba013e27..acea431f19 100644 --- a/scm/define-grobs.scm +++ b/scm/define-grobs.scm @@ -383,15 +383,7 @@ (gap . 0.8) (neutral-direction . ,DOWN) - (positions . ,(ly:make-simple-closure - (ly:make-simple-closure - (list chain-grob-member-functions - `(,cons 0 0) - ly:beam::calc-least-squares-positions - ly:beam::slope-damping - ly:beam::shift-region-to-valid - ly:beam::quanting - )))) + (positions . ,ly:beam::quanting) ;; this is a hack to set stem lengths, if positions is set. (quantized-positions . ,ly:beam::set-stem-lengths) diff --git a/scm/layout-beam.scm b/scm/layout-beam.scm index aa35be0d63..0b3c51bc4b 100644 --- a/scm/layout-beam.scm +++ b/scm/layout-beam.scm @@ -60,19 +60,15 @@ (define-public (check-quant-callbacks l r) - (list ly:beam::calc-least-squares-positions - ly:beam::slope-damping - ly:beam::shift-region-to-valid - ly:beam::quanting - (check-beam-quant l r) - )) + (lambda (grob) + ((check-beam-quant l r) + grob + (ly:beam::quanting grob)))) (define-public (check-slope-callbacks comparison) - (list ly:beam::calc-least-squares-positions - ly:beam::slope-damping - ly:beam::shift-region-to-valid - ly:beam::quanting - (check-beam-slope-sign comparison) - )) + (lambda (grob) + ((check-beam-slope-sign comparison) + grob + (ly:beam::quanting grob)))) -- 2.39.2