X-Git-Url: https://git.donarmstrong.com/?a=blobdiff_plain;f=lily%2Fbeam-quanting.cc;h=f3505772f1a70940c4deb9014d400b9158e485a2;hb=5d84bfad4626892bcffd05adcced53c8a2329047;hp=8baf0161b40163a7d0362db70b8480d9b0322dda;hpb=3ef893f1fe182e9f6cf5841cbff0706789bd3361;p=lilypond.git diff --git a/lily/beam-quanting.cc b/lily/beam-quanting.cc index 8baf0161b4..f3505772f1 100644 --- a/lily/beam-quanting.cc +++ b/lily/beam-quanting.cc @@ -1,7 +1,7 @@ /* This file is part of LilyPond, the GNU music typesetter. - Copyright (C) 1997--2011 Han-Wen Nienhuys + Copyright (C) 1997--2015 Han-Wen Nienhuys Jan Nieuwenhuizen LilyPond is free software: you can redistribute it and/or modify @@ -33,17 +33,19 @@ using namespace std; #include "grob-array.hh" #include "item.hh" #include "international.hh" +#include "interval-minefield.hh" #include "least-squares.hh" #include "libc-extension.hh" #include "main.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 "stencil.hh" #include "stem.hh" #include "warn.hh" +#include "string-convert.hh" Real get_detail (SCM alist, SCM sym, Real def) @@ -65,7 +67,10 @@ Beam_quant_parameters::fill (Grob *him) REGION_SIZE = get_detail (details, ly_symbol2scm ("region-size"), 2); // forbidden quants - SECONDARY_BEAM_DEMERIT = get_detail (details, ly_symbol2scm ("secondary-beam-demerit"), 10.0); + SECONDARY_BEAM_DEMERIT = get_detail (details, ly_symbol2scm ("secondary-beam-demerit"), 10.0) + // For stems that are non-standard, the forbidden beam quanting + // doesn't really work, so decrease their importance. + * exp(- 8*fabs (1.0 - robust_scm2double(him->get_property ("length-fraction"), 1.0))); STEM_LENGTH_DEMERIT_FACTOR = get_detail (details, ly_symbol2scm ("stem-length-demerit-factor"), 5); HORIZONTAL_INTER_QUANT_PENALTY = get_detail (details, ly_symbol2scm ("horizontal-inter-quant"), 500); @@ -78,7 +83,14 @@ Beam_quant_parameters::fill (Grob *him) // Collisions COLLISION_PENALTY = get_detail (details, ly_symbol2scm ("collision-penalty"), 500); - COLLISION_PADDING = get_detail (details, ly_symbol2scm ("collision-padding"), 0.5); + + /* For grace notes, beams get scaled down to 80%, but glyphs go down + to 63% (magstep -4 for accidentals). To make the padding + commensurate with glyph size for grace notes, we take the square + of the length fraction, yielding a 64% decrease. + */ + COLLISION_PADDING = get_detail (details, ly_symbol2scm ("collision-padding"), 0.5) + * sqr (robust_scm2double(him->get_property ("length-fraction"), 1.0)); STEM_COLLISION_FACTOR = get_detail (details, ly_symbol2scm ("stem-collision-factor"), 0.1); } @@ -132,7 +144,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 +171,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,152 +193,217 @@ 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_instance_variables (Grob *me, Drul_array ys, bool align_broken_intos) { - Grob *common_x = NULL; - segments_ = Beam::get_beam_segments (beam_, &common_x); - vector_sort (segments_, beam_segment_less); - if (common_[X_AXIS] != common_x) + beam_ = dynamic_cast (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; + for (LEFT_and_RIGHT (d)) + do_initial_slope_calculations_ |= isinf (unquanted_y_[d]) || isnan (unquanted_y_[d]); + + /* + 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 beams; + align_broken_intos_ = align_broken_intos; + if (align_broken_intos_) { - programming_error ("Disagree on common x. Skipping collisions in beam scoring."); - return; + Spanner *orig = dynamic_cast (beam_->original ()); + if (!orig) + align_broken_intos_ = false; + else if (!orig->broken_intos_.size ()) + align_broken_intos_ = false; + else + beams.insert (beams.end (), orig->broken_intos_.begin (), orig->broken_intos_.end ()); } + if (!align_broken_intos_) + beams.push_back (beam_); - set stems; - for (vsize i = 0; i < grobs.size (); i++) + /* + 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++) { - Box b; - for (Axis a = X_AXIS; a < NO_AXES; incr (a)) - b[a] = grobs[i]->extent (common_[a], a); + extract_grob_set (beams[i], "stems", stems); + extract_grob_set (beams[i], "covered-grobs", fake_collisions); + vector collisions; + + 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]); - Real width = b[X_AXIS].length (); - Real width_factor = sqrt (width / staff_space_); + Grob *common[2]; + for (int a = 2; a--;) + common[a] = common_refpoint_of_array (stems, beams[i], Axis (a)); - Direction d = LEFT; - do - add_collision (b[X_AXIS][d], b[Y_AXIS], width_factor); - while (flip (&d) != LEFT); + for (LEFT_and_RIGHT (d)) + common[X_AXIS] = beams[i]->get_bound (d)->common_refpoint (common[X_AXIS], X_AXIS); - Grob *stem = unsmob_grob (grobs[i]->get_object ("stem")); - if (stem && Stem::has_interface (stem) && Stem::is_normal_stem (stem)) + // 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 edge_stems (Beam::first_normal_stem (beams[i]), + Beam::last_normal_stem (beams[i])); + + 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, 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_pos[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_ = has_interface (common[Y_AXIS]); + is_knee_ |= dirs_found[DOWN] && dirs_found[UP]; - 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; + for (LEFT_and_RIGHT (d)) + { + 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; + } - bool f = to_boolean (s->get_property ("french-beaming")) - && s != edge_stems[LEFT] && s != edge_stems[RIGHT]; + 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_pos[LEFT]); - 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)); - } + set colliding_stems; + for (vsize j = 0; j < collisions.size (); j++) + { + if (!collisions[j]->is_live ()) + continue; - edge_dirs_ = Drul_array (CENTER, CENTER); - if (stem_infos_.size ()) - { - edge_dirs_ = Drul_array (stem_infos_[0].dir_, - stem_infos_.back ().dir_); - } + if (has_interface (collisions[j]) && Beam::is_cross_staff (collisions[j])) + continue; - is_xstaff_ = Align_interface::has_interface (common_[Y_AXIS]); - is_knee_ = dirs_found[LEFT] && dirs_found[RIGHT]; + Box b; + for (Axis a = X_AXIS; a < NO_AXES; incr (a)) + b[a] = collisions[j]->extent (common[a], a); - 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); + 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; - // TODO - why are we dividing by staff_space_? - beam_translation_ = Beam::get_beam_translation (beam_) / staff_space_; + b[X_AXIS] += (x_span_ - x_pos[LEFT]); + b[Y_AXIS] -= my_y; + Real width = b[X_AXIS].length (); + Real width_factor = sqrt (width / staff_space_); - d = LEFT; - do - { - quant_range_[d].set_full (); - if (!edge_stems[d]) - continue; + for (LEFT_and_RIGHT (d)) + add_collision (b[X_AXIS][d], b[Y_AXIS], width_factor); - 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_; + Grob *stem = unsmob (collisions[j]->get_object ("stem")); + if (has_interface (stem) && Stem::is_normal_stem (stem)) + { + colliding_stems.insert (stem); + } + } - 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; + for (set::const_iterator it (colliding_stems.begin ()); it != colliding_stems.end (); it++) + { + Grob *s = *it; + Real x = (robust_relative_extent (s, common[X_AXIS], X_AXIS) - x_pos[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) + - my_y; + + Real factor = parameters_.STEM_COLLISION_FACTOR; + if (!unsmob (s->get_object ("beam"))) + factor = 1.0; + add_collision (x, y, factor); + } + x_span_ += beams[i]->spanner_length (); } - while (flip (&d) != LEFT); - - init_collisions (collisions); } -Beam_scoring_problem::Beam_scoring_problem (Grob *me, Drul_array ys) +Beam_scoring_problem::Beam_scoring_problem (Grob *me, Drul_array ys, bool align_broken_intos) { - beam_ = me; + beam_ = dynamic_cast (me); unquanted_y_ = ys; - - /* - 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 (); + 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. @@ -384,72 +450,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 +525,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 +535,217 @@ 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; + beam_->set_property ("least-squares-dy", scm_from_double (musical_dy_)); } -/* 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) +/* + 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 const &positions, Direction beam_dir) { - Grob *me = unsmob_grob (smob); - Drul_array pos = ly_scm2interval (posns); + Interval covering; + covering.add_point (positions[0]); + covering.add_point (positions.back ()); - if (normal_stem_count (me) <= 1) - return posns; + bool above = false; + bool below = false; + bool concave = false; - SCM s = me->get_property ("damping"); - Real damping = scm_to_double (s); - Real concaveness = robust_scm2double (me->get_property ("concaveness"), 0.0); - if (concaveness >= 10000) + /* + notes above and below the interval covered by 1st and last note. + */ + for (vsize i = 1; i + 1 < positions.size (); i++) { - pos[LEFT] = pos[RIGHT]; - me->set_property ("least-squares-dy", scm_from_double (0)); - damping = 0; + above = above || (positions[i] > covering[UP]); + below = below || (positions[i] < covering[DOWN]); } - if (damping) + 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++) { - scale_drul (&pos, Staff_symbol_referencer::staff_space (me)); + 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; + } - Real dy = pos[RIGHT] - pos[LEFT]; + bool all_closer = true; + for (vsize i = 1; all_closer && i + 1 < positions.size (); i++) + { + all_closer = all_closer + && (beam_dir * positions[i] > closest); + } - Grob *fvs = first_normal_stem (me); - Grob *lvs = last_normal_stem (me); + concave = concave || all_closer; + return concave; +} - Grob *commonx = fvs->common_refpoint (lvs, X_AXIS); +Real +calc_positions_concaveness (vector 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]; - Real dx = last_normal_stem (me)->relative_coordinate (commonx, X_AXIS) - - first_normal_stem (me)->relative_coordinate (commonx, X_AXIS); + concaveness += max (beam_dir * (positions[i] - line_y), 0.0); + } - Real slope = dy && dx ? dy / dx : 0; + concaveness /= positions.size (); - slope = 0.6 * tanh (slope) / (damping + concaveness); + /* + 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); - Real damped_dy = slope * dx; + if (is_knee_ || is_xstaff_) + return 0.0; - set_minimum_dy (me, &damped_dy); + 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 close_positions; + vector 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])); + } - pos[LEFT] += (dy - damped_dy) / 2; - pos[RIGHT] -= (dy - damped_dy) / 2; + Real concaveness = 0.0; - scale_drul (&pos, 1 / Staff_symbol_referencer::staff_space (me)); + 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 ly_interval2scm (pos); + return concaveness; } -/* - 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::slope_damping () { - Grob *me = unsmob_grob (grob); - - /* - Code dup. - */ - vector x_posns; - extract_grob_set (me, "stems", stems); - extract_grob_set (me, "covered-grobs", covered); + if (normal_stem_count_ <= 1) + return; - Grob *common[NO_AXES] = { me, me }; - for (Axis a = X_AXIS; a < NO_AXES; incr (a)) + SCM s = beam_->get_property ("damping"); + Real damping = scm_to_double (s); + Real concaveness = calc_concaveness (); + if (concaveness >= 10000) { - common[a] = common_refpoint_of_array (stems, me, a); - common[a] = common_refpoint_of_array (covered, common[a], a); + unquanted_y_[LEFT] = unquanted_y_[RIGHT]; + musical_dy_ = 0; + damping = 0; } - 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++) + if (damping) { - Grob *s = stems[i]; + Real dy = unquanted_y_[RIGHT] - unquanted_y_[LEFT]; - Real x = s->relative_coordinate (common[X_AXIS], X_AXIS) - x_span[LEFT]; - x_posns.push_back (x); - } + Real slope = dy && x_span_ ? dy / x_span_ : 0; + + slope = 0.6 * tanh (slope) / (damping + concaveness); - Grob *lvs = last_normal_stem (me); - x_span[RIGHT] = lvs->relative_coordinate (common[X_AXIS], X_AXIS); + Real damped_dy = slope * x_span_; - Drul_array pos = ly_scm2interval (posns); + set_minimum_dy (beam_, &damped_dy); - scale_drul (&pos, Staff_symbol_referencer::staff_space (me)); + unquanted_y_[LEFT] += (dy - damped_dy) / 2; + unquanted_y_[RIGHT] -= (dy - damped_dy) / 2; + } +} + +void +Beam_scoring_problem::shift_region_to_valid () +{ + if (!normal_stem_count_) + return; - 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 +754,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,142 +789,46 @@ 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])) - 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 ()) + if (collisions_[i].x_ < 0 || collisions_[i].x_ > x_span_) 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 + for (LEFT_and_RIGHT (d)) { - 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 + for (DOWN_and_UP (yd)) { - 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); 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; + Real beam_left_y = unquanted_y_[LEFT]; 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); + Interval_minefield minefield (feasible_beam_placements, 0.0); + for (vsize i = 0; i < forbidden_intervals.size (); i++) + minefield.add_forbidden_interval (forbidden_intervals[i]); + minefield.solve (); + feasible_beam_placements = minefield.feasible_placements (); // 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 + for (DOWN_and_UP (d)) { 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 ()) { @@ -823,13 +847,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 @@ -868,8 +889,7 @@ Beam_scoring_problem::generate_quants (vector *scores) con Interval (unshifted_quants[i], unshifted_quants[j])); - Direction d = LEFT; - do + for (LEFT_and_RIGHT (d)) { if (!quant_range_[d].contains (c->y[d])) { @@ -878,7 +898,6 @@ Beam_scoring_problem::generate_quants (vector *scores) con break; } } - while (flip (&d) != LEFT); if (c) scores->push_back (c); } @@ -956,6 +975,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 +1042,15 @@ Beam_scoring_problem::solve () const #endif junk_pointers (configs); + 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]; + + final_positions[LEFT] += normalized_endpoints[LEFT] * y_length; + final_positions[RIGHT] -= (1 - normalized_endpoints[RIGHT]) * y_length; + } + return final_positions; } @@ -1032,10 +1063,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; @@ -1059,10 +1093,18 @@ Beam_scoring_problem::score_stem_lengths (Beam_configuration *config) const } /* Divide by number of stems, to make the measure scale-free. */ - Direction d = DOWN; - do + for (DOWN_and_UP (d)) 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"); } @@ -1084,7 +1126,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; @@ -1159,27 +1201,29 @@ Beam_scoring_problem::score_forbidden_quants (Beam_configuration *config) const { Real dy = config->y.delta (); - Real extra_demerit = parameters_.SECONDARY_BEAM_DEMERIT - / max (edge_beam_counts_[LEFT], edge_beam_counts_[RIGHT]); - - Direction d = LEFT; + Real extra_demerit = + parameters_.SECONDARY_BEAM_DEMERIT + / max (edge_beam_counts_[LEFT], edge_beam_counts_[RIGHT]); + Real dem = 0.0; Real eps = parameters_.BEAM_EPS; - do + for (LEFT_and_RIGHT (d)) { for (int j = 1; j <= edge_beam_counts_[d]; j++) { Direction stem_dir = edge_dirs_[d]; /* - The 2.2 factor is to provide a little leniency for + The fudge_factor is to provide a little leniency for borderline cases. If we do 2.0, then the upper outer line will be in the gap of the (2, sit) quant, leading to a - false demerit. + false demerit. By increasing the fudge factor to 2.2, we + fix this case. */ - Real gap1 = config->y[d] - stem_dir * ((j - 1) * beam_translation_ + beam_thickness_ / 2 - line_thickness_ / 2.2); - Real gap2 = config->y[d] - stem_dir * (j * beam_translation_ - beam_thickness_ / 2 + line_thickness_ / 2.2); + Real fudge_factor = 2.2; + Real gap1 = config->y[d] - stem_dir * ((j - 1) * beam_translation_ + beam_thickness_ / 2 - line_thickness_ / fudge_factor); + Real gap2 = config->y[d] - stem_dir * (j * beam_translation_ - beam_thickness_ / 2 + line_thickness_ / fudge_factor); Interval gap; gap.add_point (gap1); @@ -1193,8 +1237,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 @@ -1202,8 +1253,9 @@ Beam_scoring_problem::score_forbidden_quants (Beam_configuration *config) const } } } - while ((flip (&d)) != LEFT); + config->add (dem, "Fl"); + dem = 0.0; if (max (edge_beam_counts_[LEFT], edge_beam_counts_[RIGHT]) >= 2) { Real straddle = 0.0; @@ -1211,8 +1263,7 @@ Beam_scoring_problem::score_forbidden_quants (Beam_configuration *config) const Real inter = 0.5; Real hang = 1.0 - (beam_thickness_ - line_thickness_) / 2; - Direction d = LEFT; - do + for (LEFT_and_RIGHT (d)) { if (edge_beam_counts_[d] >= 2 && fabs (config->y[d] - edge_dirs_[d] * beam_translation_) < staff_radius_ + inter) @@ -1240,10 +1291,9 @@ Beam_scoring_problem::score_forbidden_quants (Beam_configuration *config) const dem += extra_demerit; } } - while (flip (&d) != LEFT); } - config->add (dem, "F"); + config->add (dem, "Fs"); } void @@ -1261,16 +1311,20 @@ Beam_scoring_problem::score_collisions (Beam_configuration *config) const Real dist = infinity_f; if (!intersection (beam_y, collision_y).is_empty ()) dist = 0.0; - else + else dist = min (beam_y.distance (collision_y[DOWN]), beam_y.distance (collision_y[UP])); + Real scale_free - = max (parameters_.COLLISION_PADDING - dist, 0.0) / - parameters_.COLLISION_PADDING; - demerits - += collisions_[i].base_penalty_ * + = max (parameters_.COLLISION_PADDING - dist, 0.0) + / parameters_.COLLISION_PADDING; + Real collision_demerit = collisions_[i].base_penalty_ * pow (scale_free, 3) * parameters_.COLLISION_PENALTY; + + if (collision_demerit > 0) { + demerits += collision_demerit; + } } config->add (demerits, "C");