X-Git-Url: https://git.donarmstrong.com/?a=blobdiff_plain;f=lily%2Fslur-scoring.cc;h=a3dad6fb5e9785888b05b119ac5320b24c0b36b0;hb=b872748c6aa8bb721ced458691b38ac2fac5dfc8;hp=f0882d004c41db39bff03484100c49c99c186ed5;hpb=6aed3e161802dc9c575236f0516d2e44506d9be1;p=lilypond.git diff --git a/lily/slur-scoring.cc b/lily/slur-scoring.cc index f0882d004c..a3dad6fb5e 100644 --- a/lily/slur-scoring.cc +++ b/lily/slur-scoring.cc @@ -1,39 +1,55 @@ /* - slur-quanting.cc -- Score based slur formatting + This file is part of LilyPond, the GNU music typesetter. - source file of the GNU LilyPond music typesetter - - (c) 1996--2004 Han-Wen Nienhuys + Copyright (C) 1996--2015 Han-Wen Nienhuys Jan Nieuwenhuizen + + 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 . */ -#include +#include "slur-scoring.hh" + +#include +#include "axis-group-interface.hh" #include "accidental-interface.hh" #include "beam.hh" +#include "clef.hh" #include "directional-element-interface.hh" -#include "group-interface.hh" +#include "dots.hh" #include "libc-extension.hh" -#include "lily-guile.hh" -#include "slur.hh" +#include "main.hh" +#include "misc.hh" #include "note-column.hh" +#include "note-head.hh" #include "output-def.hh" +#include "paper-column.hh" #include "pitch.hh" -#include "bezier.hh" +#include "pointer-group-interface.hh" +#include "slur-configuration.hh" +#include "slur.hh" #include "spanner.hh" #include "staff-symbol-referencer.hh" #include "staff-symbol.hh" #include "stem.hh" #include "warn.hh" -#include "paper-column.hh" /* TODO: - curve around flag for y coordinate - - this file is a big mess, clean it up - - short-cut: try a smaller region first. - handle non-visible stems better. @@ -46,330 +62,89 @@ - calculate encompass scoring directly after determining slur shape. - optimize. - */ +struct Slur_score_state; -struct Slur_score -{ - Drul_array attachment_; - Real score_; - Bezier curve_; - -#if DEBUG_SLUR_QUANTING - String score_card_; -#endif - - Slur_score () - { - score_ = 0.0; - } -}; - -struct Slur_score_parameters -{ - int region_size_; - Real head_encompass_penalty_; - Real stem_encompass_penalty_; - Real closeness_factor_; - Real edge_attraction_factor_; - Real same_slope_penalty_; - Real steeper_slope_factor_; - Real non_horizontal_penalty_; - Real max_slope_; - Real max_slope_factor_; - Real extra_object_collision_; - Real accidental_collision_; - Real free_slur_distance_; - Real free_head_distance_; - Real extra_encompass_free_distance_; - Real edge_slope_exponent_; - Real head_slur_distance_max_ratio_; - Real head_slur_distance_factor_; -}; - - - -struct Extra_collision_info -{ - Real idx_; - Box extents_; - Real penalty_; - Grob * grob_; - - Extra_collision_info (Grob *g, Real idx, Interval x, Interval y, Real p) - { - idx_ = idx; - extents_[X_AXIS] = x; - extents_[Y_AXIS] = y; - penalty_ = p; - grob_ = g; - } - Extra_collision_info () - { - idx_ = 0.0; - penalty_ = 0.; - grob_ = 0; - } -}; - - -struct Encompass_info -{ - Real x_; - Real stem_; - Real head_; - Encompass_info () - { - x_ = 0.0; - stem_ = 0.0; - head_ = 0.0; - } - Real get_point (Direction dir) const - { - Interval y; - y.add_point (stem_); - y.add_point (head_); - return y[dir]; - } -}; - -struct Bound_info -{ - Box stem_extent_; - Direction stem_dir_; - Item *bound_; - Grob *note_column_; - Grob *slur_head_; - Grob *staff_; - Grob *stem_; - Interval slur_head_extent_; - Real neighbor_y_; - Real staff_space_; - - Bound_info () - { - stem_ = 0; - neighbor_y_ = 0; - staff_ = 0; - slur_head_ = 0; - stem_dir_ = CENTER; - note_column_ = 0; - } -}; - -struct Slur_score_state -{ - Spanner *slur_; - Grob *common_[NO_AXES]; - bool valid_; - Real musical_dy_; - - Link_array columns_; - Array encompass_infos_; - Array extra_encompass_infos_; - - Direction dir_; - Slur_score_parameters parameters_; - Drul_array extremes_; - Drul_array base_attachments_; - Array *scores_; - Real staff_space_; - Real thickness_; - - Slur_score_state(); - ~Slur_score_state(); -}; - -Slur_score_state::Slur_score_state() +Slur_score_state::Slur_score_state () { musical_dy_ = 0.0; valid_ = false; + edge_has_beams_ = false; + has_same_beam_ = false; + is_broken_ = false; dir_ = CENTER; slur_ = 0; common_[X_AXIS] = 0; common_[Y_AXIS] = 0; - scores_ = 0; } Slur_score_state::~Slur_score_state () { - delete scores_; + junk_pointers (configurations_); } -static Array get_extra_encompass_infos (Slur_score_state const &state); -static void score_extra_encompass (Slur_score_state const&); -static void score_slopes (Slur_score_state const&); -static void score_edges (Slur_score_state const&); -static void score_encompass (Slur_score_state const&); -static Bezier avoid_staff_line (Slur_score_state const&, - Bezier bez); -static Encompass_info get_encompass_info (Slur_score_state const&, Grob *col); -static Bezier get_bezier (Slur_score_state const&, - Drul_array, - Real r_0, Real h_inf); -static Direction get_default_dir (Grob *me); - -static void set_end_points (Grob *); -static Real broken_trend_y (Slur_score_state const&, Direction dir); -static Drul_array get_bound_info (Slur_score_state const&); - -static void generate_curves (Slur_score_state const&); -static Array *enumerate_attachments (Slur_score_state const&, - Drul_array end_ys); -static Drul_array get_base_attachments(Slur_score_state const&); -static Drul_array get_y_attachment_range(Slur_score_state const&); - - -Real -get_detail (SCM alist, SCM sym) +/* + If a slur is broken across a line break, the direction + of the post-break slur must be the same as the pre-break + slur. +*/ +Direction +Slur_score_state::slur_direction () const { - SCM entry = scm_assq (sym, alist); - return robust_scm2double (scm_is_pair (entry) - ? ly_cdr (entry) - : SCM_EOL, - 0.0); -} + Grob *left_neighbor = slur_->broken_neighbor (LEFT); -void -init_score_param (Grob *me, - Slur_score_parameters *score_param) -{ - SCM details = me->get_property ("slur-details"); - - score_param->region_size_ - = (int) get_detail (details, ly_symbol2scm ("region-size")); - score_param->head_encompass_penalty_ - = get_detail (details, ly_symbol2scm ("head-encompass-penalty")); - score_param->stem_encompass_penalty_ - = get_detail (details, ly_symbol2scm ("stem-encompass-penalty")); - score_param->closeness_factor_ - = get_detail (details, ly_symbol2scm ("closeness-factor")); - score_param->edge_attraction_factor_ - = get_detail (details, ly_symbol2scm ("edge-attraction-factor")); - score_param->same_slope_penalty_ - = get_detail (details, ly_symbol2scm ("same-slope-penalty")); - score_param->steeper_slope_factor_ - = get_detail (details, ly_symbol2scm ("steeper-slope-factor")); - score_param->non_horizontal_penalty_ - = get_detail (details, ly_symbol2scm ("non-horizontal-penalty")); - score_param->max_slope_ - = get_detail (details, ly_symbol2scm ("max-slope")); - score_param->max_slope_factor_ - = get_detail (details, ly_symbol2scm ("max-slope-factor")); - score_param->free_head_distance_ - = get_detail (details, ly_symbol2scm ("free-head-distance")); - score_param->extra_object_collision_ - = get_detail (details, ly_symbol2scm ("extra-object-collision")); - score_param->accidental_collision_ - = get_detail (details, ly_symbol2scm ("accidental-collision")); - score_param->extra_encompass_free_distance_ - = get_detail (details, ly_symbol2scm ("extra-encompass-free-distance")); - score_param->head_slur_distance_factor_ - = get_detail (details, ly_symbol2scm ("head-slur-distance-factor")); - score_param->head_slur_distance_max_ratio_ - = get_detail (details, ly_symbol2scm ("head-slur-distance-max-ratio")); - score_param->free_slur_distance_ - = get_detail (details, ly_symbol2scm ("free-slur-distance")); - score_param->edge_slope_exponent_ - = get_detail (details, ly_symbol2scm ("edge-slope-exponent")); -} + if (left_neighbor && left_neighbor->is_live ()) + return get_grob_direction (left_neighbor); + Direction dir = get_grob_direction (slur_); + if (Grob *right_neighbor = slur_->broken_neighbor (RIGHT)) + set_grob_direction (right_neighbor, dir); -/* HDIR indicates which side (left or right) we are processing here. */ -Real -broken_trend_y (Slur_score_state const &state, Direction hdir) -{ - /* A broken slur should maintain the same vertical trend - the unbroken slur would have had. */ - Real by = 0.0; - if (Spanner *mother = dynamic_cast (state.slur_->original_)) - { - int k = broken_spanner_index (state.slur_); - int j = k + hdir; - if (j < 0 || j >= mother->broken_intos_.size ()) - return by; - - Grob *neighbor = mother->broken_intos_[j]; - if (hdir == RIGHT) - neighbor->set_property ("direction", scm_from_int (state.dir_)); - - Spanner *common_mother - = dynamic_cast (state.common_[Y_AXIS]->original_); - int common_k - = broken_spanner_index (dynamic_cast (state.common_[Y_AXIS])); - int common_j = common_k + hdir; - - if (common_j < 0 || common_j >= common_mother->broken_intos_.size ()) - return by; - - Grob *common_next_system = common_mother->broken_intos_[common_j]; - Link_array neighbor_cols - = Pointer_group_interface__extract_grobs (neighbor, (Grob *)0, - "note-columns"); - - Grob *neighbor_col - = (hdir == RIGHT) ? neighbor_cols[0] : neighbor_cols.top (); - Grob *neighbor_common - = common_next_system->common_refpoint (neighbor_col, Y_AXIS); - - Direction vdir = state.dir_; - Real neighbor_y - = neighbor_col->extent (neighbor_common, Y_AXIS) - .linear_combination (int (vdir)) - - common_next_system->relative_coordinate (neighbor_common, Y_AXIS); - - Grob *extreme_col = (hdir == RIGHT) ? state.columns_.top () : state.columns_[0]; - Real y = extreme_col->extent (state.common_[Y_AXIS], Y_AXIS) - .linear_combination (int ((state.columns_.size () == 1) ? CENTER : vdir)); - by = (y*neighbor_cols.size () + neighbor_y*state.columns_.size ()) / - (state.columns_.size () + neighbor_cols.size ()); - } - return by; + return dir; } Encompass_info -get_encompass_info (Slur_score_state const &state, - Grob *col) +Slur_score_state::get_encompass_info (Grob *col) const { - Grob *stem = unsmob_grob (col->get_property ("stem")); + Grob *stem = unsmob (col->get_object ("stem")); Encompass_info ei; if (!stem) { - programming_error ("No stem for note column?"); - ei.x_ = col->relative_coordinate (state.common_[X_AXIS], X_AXIS); - ei.head_ = ei.stem_ = col->extent (state.common_[Y_AXIS], - Y_AXIS)[state.dir_]; + programming_error ("no stem for note column"); + ei.x_ = col->relative_coordinate (common_[X_AXIS], X_AXIS); + ei.head_ = ei.stem_ = col->extent (common_[Y_AXIS], + Y_AXIS)[dir_]; return ei; } Direction stem_dir = get_grob_direction (stem); if (Grob *head = Note_column::first_head (col)) - ei.x_ = head->extent (state.common_[X_AXIS], X_AXIS).center (); + ei.x_ = head->extent (common_[X_AXIS], X_AXIS).center (); else - ei.x_ = col->extent (state.common_[X_AXIS], X_AXIS).center (); + ei.x_ = col->extent (common_[X_AXIS], X_AXIS).center (); - Grob *h = Stem::extremal_heads (stem)[Direction (state.dir_)]; + Grob *h = Stem::extremal_heads (stem)[Direction (dir_)]; if (!h) { - ei.head_ = ei.stem_ = col->extent (state.common_[Y_AXIS], Y_AXIS)[state.dir_]; + ei.head_ = ei.stem_ = col->extent (common_[Y_AXIS], Y_AXIS)[dir_]; return ei; } - ei.head_ = h->extent (state.common_[Y_AXIS], Y_AXIS)[state.dir_]; + ei.head_ = h->extent (common_[Y_AXIS], Y_AXIS)[dir_]; - if ((stem_dir == state.dir_) + if ((stem_dir == dir_) && !stem->extent (stem, Y_AXIS).is_empty ()) { - ei.stem_ = stem->extent (state.common_[Y_AXIS], Y_AXIS)[state.dir_]; + ei.stem_ = stem->extent (common_[Y_AXIS], Y_AXIS)[dir_]; if (Grob *b = Stem::get_beam (stem)) - ei.stem_ += stem_dir * 0.5 * Beam::get_thickness (b); + ei.stem_ += stem_dir * 0.5 * Beam::get_beam_thickness (b); - Interval x = stem->extent (state.common_[X_AXIS], X_AXIS); + Interval x = stem->extent (common_[X_AXIS], X_AXIS); ei.x_ = x.is_empty () - ? stem->relative_coordinate (state.common_[X_AXIS], X_AXIS) - : x.center (); + ? stem->relative_coordinate (common_[X_AXIS], X_AXIS) + : x.center (); } else ei.stem_ = ei.head_; @@ -377,1016 +152,720 @@ get_encompass_info (Slur_score_state const &state, return ei; } - -Direction -get_default_dir (Grob*me) -{ - Link_array encompasses - = Pointer_group_interface__extract_grobs (me, (Grob*) 0, "note-columns"); - - Direction d = DOWN; - for (int i= 0; i < encompasses.size (); i ++) - { - if (Note_column::dir (encompasses[i]) < 0) - { - d = UP; - break; - } - } - return d; -} - - - -MAKE_SCHEME_CALLBACK (Slur, after_line_breaking,1); -SCM -Slur::after_line_breaking (SCM smob) -{ - Spanner *me = dynamic_cast (unsmob_grob (smob)); - if (!scm_ilength (me->get_property ("note-columns"))) - { - me->suicide (); - return SCM_UNSPECIFIED; - } - - if (!get_grob_direction (me)) - set_grob_direction (me, get_default_dir (me)); - - if (scm_ilength (me->get_property ("control-points")) < 4) - set_end_points (me); - - return SCM_UNSPECIFIED; -} - Drul_array -get_bound_info (Slur_score_state const &state) +Slur_score_state::get_bound_info () const { Drul_array extremes; - Direction d = RIGHT; - Direction dir = state.dir_; + Direction dir = dir_; - do + for (LEFT_and_RIGHT (d)) { - extremes[d].bound_ = state.slur_->get_bound (d); - if (Note_column::has_interface (extremes[d].bound_)) - { - extremes[d].note_column_ = extremes[d].bound_; - extremes[d].stem_ = Note_column::get_stem (extremes[d].note_column_); - extremes[d].stem_dir_ = get_grob_direction (extremes[d].stem_); - extremes[d].stem_extent_[X_AXIS] - = extremes[d].stem_->extent (state.common_[X_AXIS], X_AXIS); - extremes[d].stem_extent_[Y_AXIS] - = extremes[d].stem_->extent (state.common_[Y_AXIS], Y_AXIS); - extremes[d].slur_head_ - = Stem::extremal_heads (extremes[d].stem_)[dir]; - if (!extremes[d].slur_head_ - && Note_column::has_rests (extremes[d].bound_)) - { - extremes[d].slur_head_ = Note_column::get_rest (extremes[d].bound_); - } - - if (extremes[d].slur_head_) - extremes[d].slur_head_extent_ - = extremes[d].slur_head_->extent (state.common_[X_AXIS], X_AXIS); - - extremes[d].staff_ = Staff_symbol_referencer - ::get_staff_symbol (extremes[d].stem_); - extremes[d].staff_space_ = Staff_symbol_referencer - ::staff_space (extremes[d].stem_); - } - else if (d == RIGHT) - /* - right side anticipates on the next note. - */ - extremes[d].neighbor_y_ = broken_trend_y (state, d); + extremes[d].bound_ = slur_->get_bound (d); + if (has_interface (extremes[d].bound_)) + { + extremes[d].note_column_ = extremes[d].bound_; + extremes[d].stem_ = Note_column::get_stem (extremes[d].note_column_); + extremes[d].flag_ = Note_column::get_flag (extremes[d].note_column_); + + if (extremes[d].stem_) + { + extremes[d].stem_dir_ = get_grob_direction (extremes[d].stem_); + + for (int a = X_AXIS; a < NO_AXES; a++) + { + Axis ax = Axis (a); + Interval s = extremes[d].stem_->extent (common_[ax], ax); + if (extremes[d].flag_) + s.unite (extremes[d].flag_->extent (common_[ax], ax)); + if (s.is_empty ()) + { + /* + do not issue warning. This happens for rests and + whole notes. + */ + s = Interval (0, 0) + + extremes[d].stem_->relative_coordinate (common_[ax], ax); + } + extremes[d].stem_extent_[ax] = s; + } + + extremes[d].slur_head_ + = Stem::extremal_heads (extremes[d].stem_)[dir]; + if (!extremes[d].slur_head_ + && Note_column::has_rests (extremes[d].bound_)) + extremes[d].slur_head_ = Note_column::get_rest (extremes[d].bound_); + extremes[d].staff_ = Staff_symbol_referencer + ::get_staff_symbol (extremes[d].stem_); + extremes[d].staff_space_ = Staff_symbol_referencer + ::staff_space (extremes[d].stem_); + } + + } + else if (has_interface (extremes[d].bound_)) + { + extremes[d].slur_head_ = extremes[d].bound_; + } + if (extremes[d].slur_head_) + extremes[d].slur_head_x_extent_ + = extremes[d].slur_head_->extent (common_[X_AXIS], X_AXIS); } - while (flip (&d) != RIGHT); + return extremes; } void -fill_scoring_state (Grob *me, Slur_score_state *state_ptr) +Slur_score_state::fill (Grob *me) { - Slur_score_state &state = *state_ptr; - state.slur_ = dynamic_cast (me); - state.columns_ - = Pointer_group_interface__extract_grobs (me, (Grob *) 0, "note-columns"); - - if (state.columns_.is_empty ()) + slur_ = dynamic_cast (me); + columns_ + = internal_extract_grob_array (me, ly_symbol2scm ("note-columns")); + + if (columns_.empty ()) { me->suicide (); - return ; + return; } - state.staff_space_ = Staff_symbol_referencer::staff_space (me); - Real lt = me->get_paper ()->get_dimension (ly_symbol2scm ("linethickness")); - state.thickness_ = robust_scm2double (me->get_property ("thickness"), 1.0) * lt; - - state.dir_ = get_grob_direction (me); - init_score_param (me, &state.parameters_); - - SCM eltlist = me->get_property ("note-columns"); - SCM extra_list = me->get_property ("encompass-objects"); - Spanner *sp = dynamic_cast (me); + Slur::replace_breakable_encompass_objects (me); + staff_space_ = Staff_symbol_referencer::staff_space (me); + line_thickness_ = me->layout ()->get_dimension (ly_symbol2scm ("line-thickness")); + thickness_ = robust_scm2double (me->get_property ("thickness"), 1.0) * line_thickness_; + + dir_ = slur_direction (); + parameters_.fill (me); + + extract_grob_set (me, "note-columns", columns); + extract_grob_set (me, "encompass-objects", extra_objects); + + Spanner *sp = dynamic_cast (me); for (int i = X_AXIS; i < NO_AXES; i++) { Axis a = (Axis)i; - state.common_[a] = common_refpoint_of_list (eltlist, me, a); - state.common_[a] = common_refpoint_of_list (extra_list, state.common_[a], a); - - Direction d = LEFT; - do { - state.common_[a] = state.common_[a]->common_refpoint (sp->get_bound (d), a); - } - while (flip (&d) != LEFT); + common_[a] = common_refpoint_of_array (columns, me, a); + common_[a] = common_refpoint_of_array (extra_objects, common_[a], a); + + for (LEFT_and_RIGHT (d)) + { + /* + If bound is not in note-columns, we don't want to know about + its Y-position + */ + if (a != Y_AXIS) + common_[a] = common_[a]->common_refpoint (sp->get_bound (d), a); + } } - state.extremes_ = get_bound_info (state); - state.base_attachments_ = get_base_attachments (state); + extremes_ = get_bound_info (); + is_broken_ = (!(extremes_[LEFT].note_column_ || extremes_[LEFT].slur_head_) + || !(extremes_[RIGHT].note_column_ || extremes_[RIGHT].slur_head_)); + + has_same_beam_ + = (extremes_[LEFT].stem_ && extremes_[RIGHT].stem_ + && Stem::get_beam (extremes_[LEFT].stem_) == Stem::get_beam (extremes_[RIGHT].stem_)); + + base_attachments_ = get_base_attachments (); Drul_array end_ys - = get_y_attachment_range (state); + = get_y_attachment_range (); - state.scores_ = enumerate_attachments (state, end_ys); - for (int i = 0; i < state.columns_.size (); i++) - state.encompass_infos_.push (get_encompass_info (state, state.columns_[i])); + extra_encompass_infos_ = get_extra_encompass_infos (); - state.extra_encompass_infos_ = get_extra_encompass_infos (state); - state.valid_ = true; + Interval additional_ys (0.0, 0.0); + for (vsize i = 0; i < extra_encompass_infos_.size (); i++) + { + if (extra_encompass_infos_[i].extents_[X_AXIS].is_empty ()) + continue; + + Real y_place = linear_interpolate (extra_encompass_infos_[i].extents_[X_AXIS].center (), + base_attachments_[RIGHT][X_AXIS], + base_attachments_[LEFT][X_AXIS], + end_ys[RIGHT], + end_ys[LEFT]); + Real encompass_place = extra_encompass_infos_[i].extents_[Y_AXIS][dir_]; + if (scm_is_eq (extra_encompass_infos_[i].type_, + ly_symbol2scm ("inside")) + && minmax (dir_, encompass_place, y_place) == encompass_place + && (!extra_encompass_infos_[i].grob_->internal_has_interface (ly_symbol2scm ("key-signature-interface")) + && !has_interface (extra_encompass_infos_[i].grob_) + && !extra_encompass_infos_[i].grob_->internal_has_interface (ly_symbol2scm ("time-signature-interface")))) + { + for (LEFT_and_RIGHT (d)) + additional_ys[d] = minmax (dir_, + additional_ys[d], + (dir_ + * (parameters_.encompass_object_range_overshoot_ + + (y_place - encompass_place) + * (normalize (extra_encompass_infos_[i].extents_[X_AXIS].center (), + base_attachments_[RIGHT][X_AXIS], + base_attachments_[LEFT][X_AXIS]) + + (dir_ == LEFT ? 0 : -1))))); + } + } - state.musical_dy_ = 0.0; - Direction d = LEFT; - do + for (LEFT_and_RIGHT (d)) + end_ys[d] += additional_ys[d]; + + configurations_ = enumerate_attachments (end_ys); + for (vsize i = 0; i < columns_.size (); i++) + encompass_infos_.push_back (get_encompass_info (columns_[i])); + + valid_ = true; + + musical_dy_ = 0.0; + for (LEFT_and_RIGHT (d)) { - state.musical_dy_ += d * ((state.extremes_[d].slur_head_) - ? state.extremes_[d].slur_head_->relative_coordinate (state.common_[Y_AXIS], - Y_AXIS) - : state.extremes_[d].neighbor_y_); + if (!is_broken_ + && extremes_[d].slur_head_) + musical_dy_ += d + * extremes_[d].slur_head_->relative_coordinate (common_[Y_AXIS], Y_AXIS); } - while (flip (&d) != LEFT); + + edge_has_beams_ + = (extremes_[LEFT].stem_ && Stem::get_beam (extremes_[LEFT].stem_)) + || (extremes_[RIGHT].stem_ && Stem::get_beam (extremes_[RIGHT].stem_)); + + if (is_broken_) + musical_dy_ = 0.0; } -void -set_end_points (Grob *me) +MAKE_SCHEME_CALLBACK (Slur, calc_control_points, 1) +SCM +Slur::calc_control_points (SCM smob) { + Spanner *me = unsmob (smob); + Slur_score_state state; - fill_scoring_state (me, &state); + state.fill (me); if (!state.valid_) - return; - - generate_curves (state); - score_edges (state); - score_slopes (state); - score_encompass (state); - score_extra_encompass (state); - - Real opt = 1e6; - int opt_idx = -1; - for (int i = 0; i < state.scores_->size (); i++) - { - if ((*state.scores_)[i].score_ < opt) - { - opt = (*state.scores_)[i].score_; - opt_idx = i; - } - } + return SCM_EOL; -#if DEBUG_SLUR_QUANTING + state.generate_curves (); + + SCM end_ys = me->get_property ("positions"); SCM inspect_quants = me->get_property ("inspect-quants"); - if (to_boolean (me->get_paper () - ->lookup_variable (ly_symbol2scm ("debug-slur-scoring"))) - && scm_is_pair (inspect_quants)) + bool debug_slurs = to_boolean (me->layout () + ->lookup_variable (ly_symbol2scm ("debug-slur-scoring"))); + + if (is_number_pair (inspect_quants)) { - Drul_array ins = ly_scm2interval (inspect_quants); - Real mindist = 1e6; - for (int i = 0; i < state.scores_->size (); i ++) - { - Real d =fabs ((*state.scores_)[i].attachment_[LEFT][Y_AXIS] - ins[LEFT]) - + fabs ((*state.scores_)[i].attachment_[RIGHT][Y_AXIS] - ins[RIGHT]); - if (d < mindist) - { - opt_idx = i; - mindist= d; - } - } - if (mindist > 1e5) - programming_error ("Could not find quant."); + debug_slurs = true; + end_ys = inspect_quants; } - (*state.scores_)[opt_idx].score_card_ += to_string ("i%d", opt_idx); - // debug quanting - me->set_property ("quant-score", - scm_makfrom0str ((*state.scores_)[opt_idx].score_card_.to_str0 ())); + Slur_configuration *best = NULL; + if (is_number_pair (end_ys)) + best = state.get_forced_configuration (ly_scm2interval (end_ys)); + else + best = state.get_best_curve (); + +#if DEBUG_SLUR_SCORING + if (debug_slurs) + { + string total = best->card (); + total += to_string (" TOTAL=%.2f idx=%d", best->score (), best->index_); + me->set_property ("annotation", ly_string2scm (total)); + } #endif - Bezier b = (*state.scores_)[opt_idx].curve_; SCM controls = SCM_EOL; for (int i = 4; i--;) { - Offset o = b.control_[i] - - Offset (me->relative_coordinate (state.common_[X_AXIS], X_AXIS), - me->relative_coordinate (state.common_[Y_AXIS], Y_AXIS)); + Offset o = best->curve_.control_[i] + - Offset (me->relative_coordinate (state.common_[X_AXIS], X_AXIS), + me->relative_coordinate (state.common_[Y_AXIS], Y_AXIS)); controls = scm_cons (ly_offset2scm (o), controls); } - me->set_property ("control-points", controls); -} -/* - TODO: should analyse encompasses to determine sensible region, and - should limit slopes available. - */ + return controls; +} -Drul_array -get_y_attachment_range (Slur_score_state const &state) +Slur_configuration * +Slur_score_state::get_forced_configuration (Interval ys) const { - Drul_array end_ys; - Direction d = LEFT; - do + Slur_configuration *best = NULL; + Real mindist = 1e6; + for (vsize i = 0; i < configurations_.size (); i++) { - if (state.extremes_[d].note_column_) - { - end_ys[d] = state.dir_ - * ((state.dir_ * (state.base_attachments_[d][Y_AXIS] + state.parameters_.region_size_* state.dir_)) - >? (state.dir_ * (state.dir_ + state.extremes_[d].note_column_->extent (state.common_[Y_AXIS], - Y_AXIS)[state.dir_])) - >? (state.dir_ * state.base_attachments_[-d][Y_AXIS])); - } - else - end_ys[d] = state.base_attachments_[d][Y_AXIS] + state.parameters_.region_size_ * state.dir_; + Real d = fabs (configurations_[i]->attachment_[LEFT][Y_AXIS] - ys[LEFT]) + + fabs (configurations_[i]->attachment_[RIGHT][Y_AXIS] - ys[RIGHT]); + if (d < mindist) + { + best = configurations_[i]; + mindist = d; + } } - while (flip (&d) != LEFT); - return end_ys; -} + while (!best->done ()) + best->run_next_scorer (*this); -bool -spanner_less (Spanner *s1, Spanner* s2) -{ - Slice b1, b2; - Direction d = LEFT; - do - { - b1[d] = s1->get_bound (d)->get_column ()->rank_; - b2[d] = s2->get_bound (d)->get_column ()->rank_; - } while (flip (&d) != LEFT); + if (mindist > 1e5) + programming_error ("cannot find quant"); - return b2[LEFT] <= b1[LEFT] && b2[RIGHT] >= b1[RIGHT] - && (b2[LEFT] != b1[LEFT] || b2[RIGHT] != b1[RIGHT]); + return best; } - -Drul_array -get_base_attachments (Slur_score_state const &state) +Slur_configuration * +Slur_score_state::get_best_curve () const { - Drul_array base_attachment; - Direction d = RIGHT; - do - { - Grob *stem = state.extremes_[d].stem_; - Grob *head = state.extremes_[d].slur_head_; - - Real x, y; - if (!state.extremes_[d].note_column_) - { - if (d == RIGHT) - { - y = state.extremes_[d].neighbor_y_; - x = state.extremes_[d].bound_->extent (state.common_[X_AXIS], X_AXIS)[d]; - } - else - { - x = state.slur_->get_broken_left_end_align (); - if (state.extremes_[RIGHT].bound_ == state.columns_[0]) - { - y = base_attachment[RIGHT][Y_AXIS]; - } - else - { - y = state.columns_[0]->extent (state.common_[Y_AXIS], Y_AXIS)[state.dir_]; - } - } - } - else - { - bool same_beam = - (state.extremes_[d].stem_ && state.extremes_[-d].stem_ - && Stem::get_beam (state.extremes_[d].stem_) == Stem::get_beam (state.extremes_[-d].stem_)); - - /* - fixme: X coord should also be set in this case. - */ - if (stem - && state.extremes_[d].stem_dir_ == state.dir_ - && Stem::get_beaming (stem, -d) - && (!spanner_less (state.slur_, Stem::get_beam (stem)) - || same_beam)) - y = state.extremes_[d].stem_extent_[Y_AXIS][state.dir_]; - else if (head) - y = head->extent (state.common_[Y_AXIS], Y_AXIS)[state.dir_]; - y += state.dir_ * 0.5 * state.staff_space_; - - Real pos - = (y - state.extremes_[d].staff_->relative_coordinate (state.common_[Y_AXIS], - Y_AXIS)) - * 2.0 / state.staff_space_; - - /* start off staffline. */ - if (fabs (pos - my_round (pos)) < 0.2 - && Staff_symbol_referencer::on_staffline (head, (int) rint (pos)) - && Staff_symbol_referencer::line_count (head) - 1 >= rint (pos) - ) - // TODO: calc from slur thick & line thick, parameter. - y += 1.5 * state.staff_space_ * state.dir_ / 10; - - Grob * fh = Note_column::first_head (state.extremes_[d].note_column_); - x = - (fh ? fh->extent (state.common_[X_AXIS], X_AXIS) - : state.extremes_[d].bound_->extent (state.common_[X_AXIS], X_AXIS)) - .linear_combination (CENTER); - } - base_attachment[d] = Offset (x, y); - - } while (flip (&d) != RIGHT); + std::priority_queue < Slur_configuration *, std::vector, + Slur_configuration_less > queue; + for (vsize i = 0; i < configurations_.size (); i++) + queue.push (configurations_[i]); - return base_attachment; -} - -void -generate_curves (Slur_score_state const &state) -{ - Real r_0 = robust_scm2double (state.slur_->get_property ("ratio"), 0.33); - Real h_inf = state.staff_space_ *scm_to_double (state.slur_->get_property ("height-limit")); - for (int i = 0; i < state.scores_->size (); i++) + Slur_configuration *best = NULL; + while (true) { - Bezier bez = get_bezier (state, - (*state.scores_)[i].attachment_, r_0, h_inf); + best = queue.top (); + if (best->done ()) + break; - bez = avoid_staff_line (state, bez); - (*state.scores_)[i].attachment_[LEFT] = bez.control_[0]; - (*state.scores_)[i].attachment_[RIGHT] = bez.control_[3]; - (*state.scores_)[i].curve_ = bez; + queue.pop (); + best->run_next_scorer (*this); + queue.push (best); } -} -Bezier -avoid_staff_line (Slur_score_state const &state, - Bezier bez) -{ - Offset horiz (1,0); - Array ts = bez.solve_derivative (horiz); - - /* TODO: handle case of broken slur. */ - if (!ts.is_empty () - && (state.extremes_[LEFT].staff_ == state.extremes_[RIGHT].staff_) - && state.extremes_[LEFT].staff_ && state.extremes_[RIGHT].staff_) - { - Real y = bez.curve_point (ts[0])[Y_AXIS]; - - Grob *staff = state.extremes_[LEFT].staff_; - - Real p = 2 * (y - staff->relative_coordinate (state.common_[Y_AXIS], Y_AXIS)) - / state.staff_space_; - - Real distance = fabs (my_round (p) - p); // in halfspaces - if (distance < 4 * state.thickness_ - && (int) fabs (my_round (p)) - <= 2 * Staff_symbol_referencer::staff_radius (staff) + 0.1 - && (int (fabs (my_round (p))) % 2 - != Staff_symbol_referencer::line_count (staff) % 2)) - { - Direction resolution_dir = - (distance ? state.dir_ : Direction (sign (p - my_round (p)))); - - // TODO: parameter - Real newp = my_round (p) + resolution_dir - * 5 * state.thickness_; - - Real dy = (newp - p) * state.staff_space_ / 2.0; - - bez.control_[1][Y_AXIS] += dy; - bez.control_[2][Y_AXIS] += dy; - } - } - return bez; + return best; } -Array * -enumerate_attachments (Slur_score_state const &state, Drul_array end_ys) +Interval +Slur_score_state::breakable_bound_extent (Direction d) const { - /*ugh. */ - Array scores; + Grob *col = slur_->get_bound (d)->get_column (); + Interval ret; + ret.set_empty (); + extract_grob_set (slur_, "encompass-objects", extra_encompasses); - Drul_array os; - os[LEFT] = state.base_attachments_[LEFT]; - Real minimum_length = state.staff_space_ - * robust_scm2double (state.slur_->get_property ("minimum-length"), 2.0); - - for (int i = 0; state.dir_ * os[LEFT][Y_AXIS] <= state.dir_ * end_ys[LEFT]; i++) + for (vsize i = 0; i < extra_encompasses.size (); i++) { - os[RIGHT] = state.base_attachments_[RIGHT]; - for (int j = 0; state.dir_ * os[RIGHT][Y_AXIS] <= state.dir_ * end_ys[RIGHT]; j++) - { - Slur_score s; - Direction d = LEFT; - Drul_array attach_to_stem (false, false); - do - { - os[d][X_AXIS] = state.base_attachments_[d][X_AXIS]; - if (state.extremes_[d].stem_ - && !Stem::is_invisible (state.extremes_[d].stem_) - && state.extremes_[d].stem_dir_ == state.dir_) - { - Interval stem_y = state.extremes_[d].stem_extent_[Y_AXIS]; - stem_y.widen (0.25 * state.staff_space_); - if (state.dir_ == -d - && stem_y.contains (os[d][Y_AXIS])) - { - os[d][X_AXIS] = state.extremes_[d].slur_head_extent_[-d] - - d * 0.3; - attach_to_stem[d] = true; - } - else if (state.dir_ *state.extremes_[d].stem_extent_[Y_AXIS][state.dir_] - < state.dir_ * os[d][Y_AXIS] - && !state.extremes_[d].stem_extent_[X_AXIS].is_empty () - ) - - os[d][X_AXIS] = state.extremes_[d].stem_extent_[X_AXIS].center (); - } - } - while (flip (&d) != LEFT); - - Offset dz; - dz = os[RIGHT] - os[LEFT]; - if (dz[X_AXIS] < minimum_length - || fabs (dz[Y_AXIS] / dz[X_AXIS]) > state.parameters_.max_slope_ - ) - { - do - { - if (state.extremes_[d].slur_head_) - { - os[d][X_AXIS] = state.extremes_[d].slur_head_extent_.center (); - attach_to_stem[d] = false; - } - } - while (flip (&d) != LEFT); - } - - dz = os[RIGHT] - os[LEFT]; - do - { - if (state.extremes_[d].slur_head_ - && !attach_to_stem[d]) - { - /* Horizontally move tilted slurs a little. Move - more for bigger tilts. - - TODO: parameter */ - os[d][X_AXIS] - -= state.dir_ * state.extremes_[d].slur_head_extent_.length () - * sin (dz.arg ()) / 3; - } - } - while (flip (&d) != LEFT); - - s.attachment_ = os; - scores.push (s); - - os[RIGHT][Y_AXIS] += state.dir_ * state.staff_space_ / 2; - } - - os[LEFT][Y_AXIS] += state.dir_ * state.staff_space_ / 2; + Item *item = dynamic_cast (extra_encompasses[i]); + if (item && col == item->get_column ()) + ret.unite (robust_relative_extent (item, common_[X_AXIS], X_AXIS)); } - assert (scores.size () > 0); - return new Array (scores); -} - -inline Real -linear_interpolate (Real x, Real x1, Real x2, Real y1, Real y2) -{ - return (x2 - x) / (x2 - x1) * y1 + - (x - x1) / (x2 - x1) * y2 ; + return ret; } +/* + TODO: should analyse encompasses to determine sensible region, and + should limit slopes available. +*/ -void -score_encompass (Slur_score_state const &state) +Drul_array +Slur_score_state::get_y_attachment_range () const { - for (int i = 0; i < state.scores_->size (); i++) + Drul_array end_ys; + for (LEFT_and_RIGHT (d)) { - Slur_score &configuration = state.scores_->elem_ref (i); - Bezier const &bez (configuration.curve_); - Real demerit = 0.0; - - /* - Distances for heads that are between slur and line between - attachment points. - */ - Array convex_head_distances; - Array edge_distances; - for (int j = 0; j < state.encompass_infos_.size (); j++) - { - Real x = state.encompass_infos_[j].x_; - - bool l_edge = j==0; - bool r_edge = j==state.encompass_infos_.size ()-1; - bool edge = l_edge || r_edge; - - - if (edge) - { - edge_distances.push (fabs (configuration.attachment_[l_edge ? LEFT : RIGHT][Y_AXIS] - - state.encompass_infos_[j].get_point (state.dir_))); - } - - - if (! (x < configuration.attachment_[RIGHT][X_AXIS] - && x > configuration.attachment_[LEFT][X_AXIS])) - continue; - - Real y = bez.get_other_coordinate (X_AXIS, x); - if (!edge) - { - Real head_dy = (y - state.encompass_infos_[j].head_); - if (state.dir_ * head_dy < 0) - { - demerit += state.parameters_.head_encompass_penalty_; - convex_head_distances.push (0.0); - } - else - { - Real hd = (head_dy) - ? (1 / fabs (head_dy) - 1 / state.parameters_.free_head_distance_) - : state.parameters_.head_encompass_penalty_; - hd = (hd >? 0) state.dir_ *line_y ) - { - - Real closest = - state.dir_ * (state.dir_ * state.encompass_infos_[j].get_point (state.dir_) - >? state.dir_ *line_y - ); - Real d = fabs (closest - y); - - convex_head_distances.push (d); - } - } - - - - if (state.dir_ * (y - state.encompass_infos_[j].stem_) < 0) - { - Real stem_dem =state.parameters_.stem_encompass_penalty_ ; - if ((l_edge && state.dir_ == UP) - || (r_edge && state.dir_ == DOWN)) - stem_dem /= 5; - - demerit += stem_dem; - } - else if (!edge) - { - Interval ext; - ext.add_point (state.encompass_infos_[j].stem_); - ext.add_point (state.encompass_infos_[j].head_); - - // ? - demerit += -state.parameters_.closeness_factor_ - * (state.dir_ - * (y - (ext[state.dir_] + state.dir_ * state.parameters_.free_head_distance_)) - 0.0) - variance_penalty = ((avg_distance / (min_dist +state.parameters_.free_head_distance_)) - 1.0) - extent (common_[Y_AXIS], Y_AXIS); + if (nc_extent.is_empty ()) + slur_->warning ("slur trying to encompass an empty note column."); + else + end_ys[d] = dir_ + * max (max (dir_ * (base_attachments_[d][Y_AXIS] + + parameters_.region_size_ * dir_), + dir_ * (dir_ + nc_extent[dir_])), + dir_ * base_attachments_[-d][Y_AXIS]); + } + else if (extremes_[d].slur_head_) + { + // allow only minimal movement + end_ys[d] = base_attachments_[d][Y_AXIS] + 0.3 * dir_; + } + else + end_ys[d] = base_attachments_[d][Y_AXIS] + parameters_.region_size_ * dir_; } + + return end_ys; } -Array -get_extra_encompass_infos (Slur_score_state const &state) +bool +spanner_less (Spanner *s1, Spanner *s2) { - Link_array encompasses - = Pointer_group_interface__extract_grobs (state.slur_, (Grob *)0, - "encompass-objects"); - Array collision_infos; - for (int i = encompasses.size (); i--; ) + Slice b1, b2; + for (LEFT_and_RIGHT (d)) { - if (Slur::has_interface (encompasses[i])) - { - Spanner * small_slur = dynamic_cast (encompasses[i]); - Bezier b = Slur::get_curve (small_slur); - - Offset relative (small_slur->relative_coordinate (state.common_[X_AXIS], X_AXIS), - small_slur->relative_coordinate (state.common_[Y_AXIS], Y_AXIS)); - - for (int k = 0; k < 3; k++) - { - Direction hdir = Direction (k /2 - 1); - - /* - Only take bound into account if small slur starts - together with big slur. - */ - if (hdir && small_slur->get_bound (hdir) != state.slur_->get_bound (hdir)) - continue; - - - Offset z = b.curve_point ( k / 2.0); - z += relative; - - Interval yext; - yext.set_full (); - yext[state.dir_] = z[Y_AXIS] + state.dir_ * state.thickness_ * 1.0; - - Interval xext (-1, 1); - xext = xext * (state.thickness_*2) + z[X_AXIS]; - Extra_collision_info info (small_slur, - k - 1.0, - xext, - yext, - state.parameters_.extra_object_collision_); - collision_infos.push (info); - } - } - else - { - Grob *g = encompasses [i]; - Interval xe = g->extent (state.common_[X_AXIS], X_AXIS); - Interval ye = g->extent (state.common_[Y_AXIS], Y_AXIS); - - Real xp = 0.0; - Real penalty = state.parameters_.extra_object_collision_; - if (Accidental_interface::has_interface (g)) - { - penalty = state.parameters_.accidental_collision_; - /* Begin copy accidental.cc */ - bool parens = false; - if (to_boolean (g->get_property ("cautionary"))) - { - SCM cstyle = g->get_property ("cautionary-style"); - parens = ly_c_equal_p (cstyle, ly_symbol2scm ("parentheses")); - } - - SCM accs = g->get_property ("accidentals"); - SCM scm_style = g->get_property ("style"); - if (!scm_is_symbol (scm_style) - && !parens - && scm_ilength (accs) == 1) - { - /* End copy accidental.cc */ - switch (scm_to_int (ly_car (accs))) - { - case FLAT: - case DOUBLE_FLAT: - xp = LEFT; - break ; - case SHARP: - xp = 0.5 * state.dir_; - break ; - case NATURAL: - xp = -state.dir_; - break; - } - } - } - - ye.widen (state.thickness_ * 0.5); - xe.widen (state.thickness_ * 1.0); - Extra_collision_info info (g, xp, xe, ye, penalty); - collision_infos.push (info); - } + b1[d] = s1->get_bound (d)->get_column ()->get_rank (); + b2[d] = s2->get_bound (d)->get_column ()->get_rank (); } - return collision_infos; + return b2[LEFT] <= b1[LEFT] && b2[RIGHT] >= b1[RIGHT] + && (b2[LEFT] != b1[LEFT] || b2[RIGHT] != b1[RIGHT]); } -void -score_extra_encompass (Slur_score_state const &state) +Drul_array +Slur_score_state::get_base_attachments () const { - for (int i = 0; i < state.scores_->size (); i++) + Drul_array base_attachment; + for (LEFT_and_RIGHT (d)) { - Real demerit = 0.0; - for (int j = 0; j < state.extra_encompass_infos_.size (); j++) - { - Drul_array attachment = state.scores_->elem (i).attachment_; - Interval slur_wid (attachment[LEFT][X_AXIS], attachment[RIGHT][X_AXIS]); - - /* - to prevent numerical inaccuracies in - Bezier::get_other_coordinate (). - */ - Direction d = LEFT; - bool found = false; - Real y = 0.0; - - do - { - /* - We need to check for the bound explicitly, since the - slur-ending can be almost vertical, making the Y - coordinate a bad approximation of the object-slur - distance. - */ - Item * as_item = dynamic_cast (state.extra_encompass_infos_[j].grob_); - if ((as_item - && as_item->get_column () - == state.extremes_[d] .bound_->get_column ()) - || state.extra_encompass_infos_[j].extents_[X_AXIS].contains (attachment[d][X_AXIS])) - { - y = attachment[d][Y_AXIS]; - found = true; - } - } - while (flip (&d) != LEFT); - - if (!found) - { - Real x = state.extra_encompass_infos_[j].extents_[X_AXIS] - .linear_combination (state.extra_encompass_infos_[j].idx_); - - if (!slur_wid.contains (x)) - continue; - - y = state.scores_->elem (i).curve_.get_other_coordinate (X_AXIS, x); - } - - Real dist = state.extra_encompass_infos_[j].extents_[Y_AXIS].distance (y); - demerit += - fabs (0 >? (state.parameters_.extra_encompass_free_distance_ - dist)) / - state.parameters_.extra_encompass_free_distance_ - * state.extra_encompass_infos_[j].penalty_; - } -#if DEBUG_SLUR_QUANTING - (*state.scores_)[i].score_card_ += to_string ("X%.2f", demerit); -#endif - (*state.scores_)[i].score_ += demerit; + Grob *stem = extremes_[d].stem_; + Grob *head = extremes_[d].slur_head_; + + Real x = 0.0; + Real y = 0.0; + if (extremes_[d].note_column_) + { + + /* + fixme: X coord should also be set in this case. + */ + if (stem + && !Stem::is_invisible (stem) + && extremes_[d].stem_dir_ == dir_ + && Stem::get_beaming (stem, -d) + && Stem::get_beam (stem) + && (!spanner_less (slur_, Stem::get_beam (stem)) + || has_same_beam_)) + y = extremes_[d].stem_extent_[Y_AXIS][dir_]; + else if (head) + y = head->extent (common_[Y_AXIS], Y_AXIS)[dir_]; + y += dir_ * 0.5 * staff_space_; + + y = move_away_from_staffline (y, head); + + Grob *fh = Note_column::first_head (extremes_[d].note_column_); + x + = (fh ? fh->extent (common_[X_AXIS], X_AXIS) + : extremes_[d].bound_->extent (common_[X_AXIS], X_AXIS)) + .linear_combination (CENTER); + } + else if (head) + { + y = head->extent (common_[Y_AXIS], Y_AXIS) + .linear_combination (0.5*dir_); + + // Don't "move_away_from_staffline" because that makes it + // harder to recognize the specific attachment point + x = head->extent (common_[X_AXIS], X_AXIS)[-d]; + } + + base_attachment[d] = Offset (x, y); } -} -void -score_edges (Slur_score_state const &state) -{ - for (int i = 0; i < state.scores_->size (); i++) + for (LEFT_and_RIGHT (d)) { - Direction d = LEFT; - Slur_score &config = state.scores_->elem_ref (i); - Offset dz = config.attachment_[RIGHT] - config.attachment_[LEFT]; - Real slope = dz[Y_AXIS] / dz[X_AXIS]; - do - { - Real y = config.attachment_[d][Y_AXIS]; - Real dy = fabs (y - state.base_attachments_[d][Y_AXIS]); - - Real factor = state.parameters_.edge_attraction_factor_; - Real demerit = factor * dy; - if (state.extremes_[d].stem_ - && state.extremes_[d].stem_dir_ == state.dir_ - && !Stem::get_beaming (state.extremes_[d].stem_, -d) - ) - demerit /= 5; - - demerit *= exp (state.dir_ * d * slope - * state.parameters_.edge_slope_exponent_ ); - - (*state.scores_)[i].score_ += demerit; -#if DEBUG_SLUR_QUANTING - (*state.scores_)[i].score_card_ += to_string ("E%.2f", demerit); -#endif - } - while (flip (&d) != LEFT); + if (!extremes_[d].note_column_ && !extremes_[d].slur_head_) + { + Real x = 0; + Real y = 0; + + Interval ext = breakable_bound_extent (d); + if (ext.is_empty ()) + ext = Axis_group_interface:: + generic_bound_extent (extremes_[d].bound_, + common_[X_AXIS], X_AXIS); + x = ext[-d]; + + Grob *col = (d == LEFT) ? columns_[0] : columns_.back (); + + if (extremes_[-d].bound_ != col) + { + y = robust_relative_extent (col, common_[Y_AXIS], Y_AXIS)[dir_]; + y += dir_ * 0.5 * staff_space_; + + if (get_grob_direction (col) == dir_ + && Note_column::get_stem (col) + && !Stem::is_invisible (Note_column::get_stem (col))) + y -= dir_ * 1.5 * staff_space_; + } + else + y = base_attachment[-d][Y_AXIS]; + + y = move_away_from_staffline (y, col); + + base_attachment[d] = Offset (x, y); + } } -} -void -score_slopes (Slur_score_state const &state) -{ - bool edge_has_beams - = (state.extremes_[LEFT].stem_ && Stem::get_beam (state.extremes_[LEFT].stem_)) - || (state.extremes_[RIGHT].stem_ && Stem::get_beam (state.extremes_[RIGHT].stem_)); - - Real dy = state.musical_dy_; - for (int i = 0; i < state.scores_->size (); i++) + for (LEFT_and_RIGHT (d)) { - Offset slur_dz = (*state.scores_)[i].attachment_[RIGHT] - - (*state.scores_)[i].attachment_[LEFT]; - Real slur_dy = slur_dz[Y_AXIS]; - Real demerit = 0.0; - - demerit += ((fabs (slur_dy / slur_dz[X_AXIS]) - - state.parameters_.max_slope_) >? 0) - * state.parameters_.max_slope_factor_; - - /* 0.2: account for staffline offset. */ - Real max_dy = (fabs (dy) + 0.2); - if (edge_has_beams) - max_dy += 1.0; - - demerit += state.parameters_.steeper_slope_factor_ - * ((fabs (slur_dy) -max_dy) >? 0); - - demerit += ((fabs (slur_dy/slur_dz[X_AXIS]) - - state.parameters_.max_slope_) >? 0) - * state.parameters_.max_slope_factor_; - - if (sign (dy) == 0 - && sign (slur_dy) != 0) - demerit += state.parameters_.non_horizontal_penalty_; - - if (sign (dy) - && sign (slur_dy) - && sign (slur_dy) != sign (dy)) - demerit += edge_has_beams - ? state.parameters_.same_slope_penalty_ / 10 - : state.parameters_.same_slope_penalty_; - -#if DEBUG_SLUR_QUANTING - (*state.scores_)[i].score_card_ += to_string ("S%.2f", demerit); -#endif - (*state.scores_)[i].score_ += demerit; + for (int a = X_AXIS; a < NO_AXES; a++) + { + Real &b = base_attachment[d][Axis (a)]; + + if (isinf (b) || isnan (b)) + { + programming_error ("slur attachment is inf/nan"); + b = 0.0; + } + } } + return base_attachment; } - Real -fit_factor (Offset dz_unit, Offset dz_perp, - Bezier curve, Direction d, Array const &avoid) +Slur_score_state::move_away_from_staffline (Real y, + Grob *on_staff) const { - Real fit_factor = 0.0; - Offset x0 = curve.control_[0]; - curve.translate (-x0); - curve.rotate (-dz_unit.arg ()); - curve.scale (1, d); + if (!on_staff) + return y; - Interval curve_xext; - curve_xext.add_point (curve.control_[0][X_AXIS]); - curve_xext.add_point (curve.control_[3][X_AXIS]); + Grob *staff_symbol = Staff_symbol_referencer::get_staff_symbol (on_staff); + if (!staff_symbol) + return y; - for (int i = 0; i < avoid.size (); i++) - { - Offset z = (avoid[i] - x0) ; - Offset p (dot_product (z, dz_unit), - d* dot_product (z, dz_perp)); - if (!curve_xext.contains (p[X_AXIS])) - continue; - - Real y = curve.get_other_coordinate (X_AXIS, p[X_AXIS]); - if (y) - { - fit_factor = fit_factor >? (p[Y_AXIS] / y); - } - } - return fit_factor; + Real pos + = (y - staff_symbol->relative_coordinate (common_[Y_AXIS], + Y_AXIS)) + * 2.0 / staff_space_; + + if (fabs (pos - my_round (pos)) < 0.2 + && Staff_symbol_referencer::on_staff_line (on_staff, (int) rint (pos))) + y += 1.5 * staff_space_ * dir_ / 10; + + return y; } - -Bezier -get_bezier (Slur_score_state const &state, - Drul_array attachments, - Real r_0, Real h_inf) +vector +Slur_score_state::generate_avoid_offsets () const { - Link_array encompasses = state.columns_; + vector avoid; + vector encompasses = columns_; - Array avoid; - for (int i = 0; i < encompasses.size (); i++) + for (vsize i = 0; i < encompasses.size (); i++) { - if (state.extremes_[LEFT].note_column_ == encompasses[i] - || state.extremes_[RIGHT].note_column_ == encompasses[i]) - continue; + if (extremes_[LEFT].note_column_ == encompasses[i] + || extremes_[RIGHT].note_column_ == encompasses[i]) + continue; - Encompass_info inf (get_encompass_info (state, encompasses[i])); + Encompass_info inf (get_encompass_info (encompasses[i])); + Real y = dir_ * (max (dir_ * inf.head_, dir_ * inf.stem_)); - Real y = state.dir_ * ((state.dir_ * inf.head_) >? (state.dir_ *inf.stem_)); - - avoid.push (Offset (inf.x_, y + state.dir_ * state.parameters_.free_head_distance_)); + avoid.push_back (Offset (inf.x_, y + dir_ * parameters_.free_head_distance_)); } - Link_array extra_encompasses - = Pointer_group_interface__extract_grobs (state.slur_, (Grob *)0, "encompass-objects"); - for (int i = 0; i < extra_encompasses.size (); i++) - if (Slur::has_interface (extra_encompasses[i])) - { - Grob * small_slur = extra_encompasses[i]; - Bezier b = Slur::get_curve (small_slur); - - Offset z = b.curve_point (0.5); - z += Offset (small_slur->relative_coordinate (state.common_[X_AXIS], X_AXIS), - small_slur->relative_coordinate (state.common_[Y_AXIS], Y_AXIS)); - - z[Y_AXIS] += state.dir_ * state.parameters_.free_slur_distance_; - avoid.push (z); - } - - Offset dz = attachments[RIGHT]- attachments[LEFT];; - Offset dz_unit = dz; - dz_unit *= 1 / dz.length (); - Offset dz_perp = dz_unit * Offset (0, 1); - - Real indent, height; - get_slur_indent_height (&indent, &height, dz.length (), h_inf, r_0); - - Real excentricity = robust_scm2double (state.slur_->get_property ("excentricity"), 0); - Bezier curve; - - Real x1 = (excentricity + indent); - Real x2 = (excentricity - indent); - curve.control_[0] = attachments[LEFT]; - curve.control_[1] = attachments[LEFT] + dz_perp * height * state.dir_ + dz_unit * x1; - curve.control_[2] = attachments[RIGHT] + dz_perp * height * state.dir_ - + dz_unit * x2; - curve.control_[3] = attachments[RIGHT]; + extract_grob_set (slur_, "encompass-objects", extra_encompasses); + for (vsize i = 0; i < extra_encompasses.size (); i++) + { + if (has_interface (extra_encompasses[i])) + { + Grob *small_slur = extra_encompasses[i]; + Bezier b = Slur::get_curve (small_slur); + + Offset z = b.curve_point (0.5); + z += Offset (small_slur->relative_coordinate (common_[X_AXIS], X_AXIS), + small_slur->relative_coordinate (common_[Y_AXIS], Y_AXIS)); + + z[Y_AXIS] += dir_ * parameters_.free_slur_distance_; + avoid.push_back (z); + } + else if (scm_is_eq (extra_encompasses[i]->get_property ("avoid-slur"), + ly_symbol2scm ("inside"))) + { + Grob *g = extra_encompasses [i]; + Interval xe = g->extent (common_[X_AXIS], X_AXIS); + Interval ye = g->extent (common_[Y_AXIS], Y_AXIS); + + if (!xe.is_empty () + && !ye.is_empty ()) + avoid.push_back (Offset (xe.center (), ye[dir_])); + } + } + return avoid; +} - Real ff = fit_factor (dz_unit, dz_perp, curve, state.dir_, avoid); - Real len = dz.length (); +void +Slur_score_state::generate_curves () const +{ + Real r_0 = robust_scm2double (slur_->get_property ("ratio"), 0.33); + Real h_inf = staff_space_ * scm_to_double (slur_->get_property ("height-limit")); - /* This condition, + vector avoid = generate_avoid_offsets (); + for (vsize i = 0; i < configurations_.size (); i++) + configurations_[i]->generate_curve (*this, r_0, h_inf, avoid); +} - len^2 > 4h^2 + 3 (i 1/3len)^2 - 1/3 len^2 +vector +Slur_score_state::enumerate_attachments (Drul_array end_ys) const +{ + vector scores; - is equivalent to: + Drul_array os; + os[LEFT] = base_attachments_[LEFT]; + Real minimum_length = staff_space_ + * robust_scm2double (slur_->get_property ("minimum-length"), 2.0); - |bez' (0)| < | bez' (.5)| + for (int i = 0; dir_ * os[LEFT][Y_AXIS] <= dir_ * end_ys[LEFT]; i++) + { + os[RIGHT] = base_attachments_[RIGHT]; + for (int j = 0; dir_ * os[RIGHT][Y_AXIS] <= dir_ * end_ys[RIGHT]; j++) + { + + Drul_array attach_to_stem (false, false); + for (LEFT_and_RIGHT (d)) + { + os[d][X_AXIS] = base_attachments_[d][X_AXIS]; + if (extremes_[d].stem_ + && !Stem::is_invisible (extremes_[d].stem_) + && extremes_[d].stem_dir_ == dir_) + { + Interval stem_y = extremes_[d].stem_extent_[Y_AXIS]; + stem_y.widen (0.25 * staff_space_); + if (stem_y.contains (os[d][Y_AXIS])) + { + os[d][X_AXIS] = extremes_[d].stem_extent_[X_AXIS][-d] + - d * 0.3; + attach_to_stem[d] = true; + } + else if (dir_ * extremes_[d].stem_extent_[Y_AXIS][dir_] + < dir_ * os[d][Y_AXIS] + && !extremes_[d].stem_extent_[X_AXIS].is_empty ()) + + os[d][X_AXIS] = extremes_[d].stem_extent_[X_AXIS].center (); + } + } + + Offset dz; + dz = os[RIGHT] - os[LEFT]; + if (dz[X_AXIS] < minimum_length + || fabs (dz[Y_AXIS] / dz[X_AXIS]) > parameters_.max_slope_) + { + for (LEFT_and_RIGHT (d)) + { + if (extremes_[d].slur_head_ + && !extremes_[d].slur_head_x_extent_.is_empty ()) + { + os[d][X_AXIS] = extremes_[d].slur_head_x_extent_.center (); + attach_to_stem[d] = false; + } + } + } + + dz = (os[RIGHT] - os[LEFT]).direction (); + for (LEFT_and_RIGHT (d)) + { + if (extremes_[d].slur_head_ + && !attach_to_stem[d]) + { + /* Horizontally move tilted slurs a little. Move + more for bigger tilts. + + TODO: parameter */ + os[d][X_AXIS] + -= dir_ * extremes_[d].slur_head_x_extent_.length () + * dz[Y_AXIS] / 3; + } + } + + scores.push_back (Slur_configuration::new_config (os, scores.size ())); + + os[RIGHT][Y_AXIS] += dir_ * staff_space_ / 2; + } + + os[LEFT][Y_AXIS] += dir_ * staff_space_ / 2; + } - when (control2 - control1) has the same direction as - (control3 - control0). */ + assert (scores.size () > 0); + return scores; +} - Real a1 = sqr (len) / 3.0; - Real a2 = 0.75 * sqr (indent + len / 3.0); - Real max_h; - if (a1 >= a2) - max_h = sqrt (a1 - a2); - else +vector +Slur_score_state::get_extra_encompass_infos () const +{ + extract_grob_set (slur_, "encompass-objects", encompasses); + vector collision_infos; + for (vsize i = encompasses.size (); i--;) { - programming_error ("FIXME: max_h is broken; setting to length / 3"); - max_h = len / 3.0; + if (has_interface (encompasses[i])) + { + Spanner *small_slur = dynamic_cast (encompasses[i]); + Bezier b = Slur::get_curve (small_slur); + + Offset relative (small_slur->relative_coordinate (common_[X_AXIS], X_AXIS), + small_slur->relative_coordinate (common_[Y_AXIS], Y_AXIS)); + + for (int k = 0; k < 3; k++) + { + Direction hdir = Direction (k - 1); + + /* + Only take bound into account if small slur starts + together with big slur. + */ + if (hdir && small_slur->get_bound (hdir) != slur_->get_bound (hdir)) + continue; + + Offset z = b.curve_point (k / 2.0); + z += relative; + + Interval yext; + yext.set_full (); + yext[dir_] = z[Y_AXIS] + dir_ * thickness_ * 1.0; + + Interval xext (-1, 1); + xext = xext * (thickness_ * 2) + z[X_AXIS]; + Extra_collision_info info (small_slur, + hdir, + xext, + yext, + parameters_.extra_object_collision_penalty_); + collision_infos.push_back (info); + } + } + else + { + Grob *g = encompasses [i]; + Interval xe = g->extent (common_[X_AXIS], X_AXIS); + Interval ye = g->extent (common_[Y_AXIS], Y_AXIS); + if (has_interface (g)) + ye.widen (0.2); + + Real xp = 0.0; + Real penalty = parameters_.extra_object_collision_penalty_; + if (has_interface (g)) + { + penalty = parameters_.accidental_collision_; + + Rational alt = ly_scm2rational (g->get_property ("alteration")); + SCM scm_style = g->get_property ("style"); + if (!scm_is_symbol (scm_style) + && !to_boolean (g->get_property ("parenthesized")) + && !to_boolean (g->get_property ("restore-first"))) + { + /* End copy accidental.cc */ + if (alt == FLAT_ALTERATION + || alt == DOUBLE_FLAT_ALTERATION) + xp = LEFT; + else if (alt == SHARP_ALTERATION) + xp = 0.5 * dir_; + else if (alt == NATURAL_ALTERATION) + xp = -dir_; + } + } + + ye.widen (thickness_ * 0.5); + xe.widen (thickness_ * 1.0); + Extra_collision_info info (g, xp, xe, ye, penalty); + collision_infos.push_back (info); + } } - height = height >? ((height * ff) get_property ("avoid-slur"); +} + +Extra_collision_info::Extra_collision_info () +{ + idx_ = 0.0; + penalty_ = 0.; + grob_ = 0; + type_ = SCM_EOL; }