From: Mike Solomon Date: Mon, 10 Sep 2012 07:17:28 +0000 (+0200) Subject: Better approximations for cross-staff slurs X-Git-Tag: release/2.17.2-1~2 X-Git-Url: https://git.donarmstrong.com/?a=commitdiff_plain;h=89b7e75f6888d2020f7fe8b258e818c0c5e7a223;p=lilypond.git Better approximations for cross-staff slurs SlurStub grobs are used to approximate the vertical skylines of cross staff slurs by using control points generated by pure heights instead of heights. Depending on how far off the actual translations are from the minimal translations, these control points will be more or less viable. A SlurStub is generated for every VerticalAxisGroup on which a slur has encompass objects and only those residing on the extremal UP and DOWN vertical axis groups get vertical skylines. --- diff --git a/input/regression/cross-staff-slur-vertical-spacing.ly b/input/regression/cross-staff-slur-vertical-spacing.ly new file mode 100644 index 0000000000..4daa69f3b9 --- /dev/null +++ b/input/regression/cross-staff-slur-vertical-spacing.ly @@ -0,0 +1,73 @@ +\version "2.17.0" + +\header { + texidoc = "Cross-staff slurs are accounted for in vertical spacing. +" +} + +%#(ly:set-option 'debug-skylines #t) + +\new PianoStaff << + \new Staff = "up" { + 8 + \change Staff = "down" + \slurDown + g,,8 ( fis g + \change Staff = "up" + 8 ) + \change Staff = "down" + e8 dis e + \change Staff = "up" + \break + a'8 a'8 a'8^\markup \column { "f" "o" "o" } a'8 a'8 a'8 a'8 a'8 + } + \new Staff = "down" { + \clef bass + % keep staff alive + s1 s1 + } +>> + +\new PianoStaff << + \new Staff = "up" { + 8 + \change Staff = "down" + \slurDown + g,,8 ( + \change Staff = "up" + fis'' g 8 ) + \change Staff = "down" + e8 dis e + \change Staff = "up" + \break + a'8 a'8 a'8^\markup \column { "f" "o" "o" } a'8 a'8 a'8 a'8 a'8 + } + \new Staff = "down" { + \clef bass + % keep staff alive + s1 s1 + } +>> + +\new PianoStaff << + \new Staff = "up" { + R1 + 8 + \change Staff = "down" + \slurUp + g,,8 ( + \change Staff = "up" + fis'' g \override Stem #'direction = #UP + 8 ) + \change Staff = "down" + e8 dis e + \change Staff = "up" + } + \new Staff = "down" { + \clef bass + % keep staff alive + a8 a8 a8_\markup \column { "f" "o" "o" } + a8 a8 a8 a8 a8 + \break s1 + } +>> diff --git a/lily/align-interface.cc b/lily/align-interface.cc index 8a2a8d9b81..7f91eca597 100644 --- a/lily/align-interface.cc +++ b/lily/align-interface.cc @@ -83,9 +83,10 @@ get_skylines (Grob *me, if (!pure) { - Skyline_pair *skys = Skyline_pair::unsmob (g->get_property (a == Y_AXIS - ? "vertical-skylines" - : "horizontal-skylines")); + Skyline_pair *skys = a == Y_AXIS + ? Skyline_pair::unsmob (g->get_property ("vertical-skylines")) + : Skyline_pair::unsmob (g->get_property ("horizontal-skylines")); + if (skys) skylines = *skys; diff --git a/lily/axis-group-interface.cc b/lily/axis-group-interface.cc index 707e045c9b..71b2b7cfa8 100644 --- a/lily/axis-group-interface.cc +++ b/lily/axis-group-interface.cc @@ -613,28 +613,6 @@ pure_staff_priority_less (Grob *const &g1, Grob *const &g2) return priority_1 < priority_2; } -static void -add_interior_skylines (Grob *me, Grob *x_common, Grob *y_common, vector *skylines) -{ - if (Grob_array *elements = unsmob_grob_array (me->get_object ("elements"))) - { - for (vsize i = 0; i < elements->size (); i++) - add_interior_skylines (elements->grob (i), x_common, y_common, skylines); - } - else if (!scm_is_number (me->get_property ("outside-staff-priority")) - && !to_boolean (me->get_property ("cross-staff"))) - { - Skyline_pair *maybe_pair = Skyline_pair::unsmob (me->get_property ("vertical-skylines")); - if (!maybe_pair) - return; - if (maybe_pair->is_empty ()) - return; - skylines->push_back (Skyline_pair (*maybe_pair)); - skylines->back ().shift (me->relative_coordinate (x_common, X_AXIS)); - skylines->back ().raise (me->relative_coordinate (y_common, Y_AXIS)); - } -} - // Raises the grob elt (whose skylines are given by h_skyline // and v_skyline) so that it doesn't intersect with staff_skyline, // or with anything in other_h_skylines and other_v_skylines. @@ -824,6 +802,30 @@ Axis_group_interface::outside_staff_ancestor (Grob *me) return outside_staff_ancestor (parent); } +void +Axis_group_interface::add_interior_skylines +(Grob *me, Grob *x_common, Grob *y_common, vector *skylines, bool pure) +{ + if (Grob_array *elements = unsmob_grob_array (me->get_object ("elements"))) + { + for (vsize i = 0; i < elements->size (); i++) + add_interior_skylines (elements->grob (i), x_common, y_common, skylines, pure); + } + else if (pure || + (!scm_is_number (me->get_property ("outside-staff-priority")) + && !to_boolean (me->get_property ("cross-staff")))) + { + Skyline_pair *maybe_pair = Skyline_pair::unsmob (me->get_property ("vertical-skylines")); + if (!maybe_pair) + return; + if (maybe_pair->is_empty ()) + return; + skylines->push_back (Skyline_pair (*maybe_pair)); + skylines->back ().shift (me->relative_coordinate (x_common, X_AXIS)); + skylines->back ().raise (me->maybe_pure_coordinate (y_common, Y_AXIS, pure, 0, INT_MAX)); + } +} + // It is tricky to correctly handle skyline placement of cross-staff grobs. // For example, cross-staff beams cannot be formatted until the distance between // staves is known and therefore any grobs that depend on the beam cannot be placed @@ -875,8 +877,9 @@ Axis_group_interface::skyline_spacing (Grob *me, vector elements) { Grob *elt = elements[i]; Grob *ancestor = outside_staff_ancestor (elt); - if (!(to_boolean (elt->get_property ("cross-staff")) || ancestor)) - add_interior_skylines (elt, x_common, y_common, &inside_staff_skylines); + if (!(to_boolean (elt->get_property ("cross-staff")) || ancestor) + || elt->internal_has_interface (ly_symbol2scm ("cross-staff-stub-interface"))) + add_interior_skylines (elt, x_common, y_common, &inside_staff_skylines, elt->internal_has_interface (ly_symbol2scm ("cross-staff-stub-interface"))); if (ancestor) riders.insert (pair (ancestor, elt)); } diff --git a/lily/figured-bass-position-engraver.cc b/lily/figured-bass-position-engraver.cc index 9b55e04f49..a9d0a89dc0 100644 --- a/lily/figured-bass-position-engraver.cc +++ b/lily/figured-bass-position-engraver.cc @@ -102,7 +102,8 @@ Figured_bass_position_engraver::acknowledge_end_slur (Grob_info info) void Figured_bass_position_engraver::acknowledge_slur (Grob_info info) { - span_support_.push_back (info.grob ()); + if (!info.grob ()->internal_has_interface (ly_symbol2scm ("cross-staff-stub-interface"))) + span_support_.push_back (info.grob ()); } void diff --git a/lily/grob.cc b/lily/grob.cc index c515c18616..4e661ebf4e 100644 --- a/lily/grob.cc +++ b/lily/grob.cc @@ -923,6 +923,25 @@ common_refpoint_of_array (set const &arr, Grob *common, Axis a) return common; } +Interval +maybe_pure_robust_relative_extent (Grob *me, Grob *refp, Axis a, bool pure, int start, int end) +{ + if (pure && a != Y_AXIS) + programming_error ("tried to get pure X-offset"); + return (pure && a == Y_AXIS) ? pure_robust_relative_extent (me, refp, start, end) + : robust_relative_extent (me, refp, a); +} + +Interval +pure_robust_relative_extent (Grob *me, Grob *refpoint, int start, int end) +{ + Interval ext = me->pure_height (refpoint, start, end); + if (ext.is_empty ()) + ext.add_point (me->pure_relative_y_coordinate (refpoint, start, end)); + + return ext; +} + Interval robust_relative_extent (Grob *me, Grob *refpoint, Axis a) { diff --git a/lily/include/axis-group-interface.hh b/lily/include/axis-group-interface.hh index 4098a19514..8567736954 100644 --- a/lily/include/axis-group-interface.hh +++ b/lily/include/axis-group-interface.hh @@ -28,8 +28,8 @@ class Axis_group_interface { static Real default_outside_staff_padding_; - public -: + public : + static SCM generic_group_extent (Grob *me, Axis a); static Real get_default_outside_staff_padding (); static Interval generic_bound_extent (Grob *me, Grob *common, Axis a); @@ -63,6 +63,7 @@ class Axis_group_interface static Grob *outside_staff_ancestor (Grob *me); static Skyline_pair skyline_spacing (Grob *me, vector elements); + static void add_interior_skylines (Grob *me, Grob *x_common, Grob *y_common, vector *skylines, bool pure = false); static void add_element (Grob *me, Grob *); static void set_axes (Grob *, Axis, Axis); static bool has_axis (Grob *, Axis); diff --git a/lily/include/grob.hh b/lily/include/grob.hh index 09cd566ad3..05b8f278ba 100644 --- a/lily/include/grob.hh +++ b/lily/include/grob.hh @@ -177,6 +177,8 @@ Grob *common_refpoint_of_array (set const &, Grob *, Axis a); System *get_root_system (Grob *me); /* extents */ +Interval maybe_pure_robust_relative_extent (Grob *, Grob *, Axis, bool, int, int); +Interval pure_robust_relative_extent (Grob *, Grob *, int, int); Interval robust_relative_extent (Grob *, Grob *, Axis); /* offset/extent callbacks. */ diff --git a/lily/include/slur-scoring.hh b/lily/include/slur-scoring.hh index e5ff38d611..392253b49c 100644 --- a/lily/include/slur-scoring.hh +++ b/lily/include/slur-scoring.hh @@ -84,6 +84,7 @@ struct Slur_score_state { Spanner *slur_; Grob *common_[NO_AXES]; + bool stub_; bool valid_; bool edge_has_beams_; bool is_broken_; diff --git a/lily/include/slur.hh b/lily/include/slur.hh index b20311785d..61a32348c2 100644 --- a/lily/include/slur.hh +++ b/lily/include/slur.hh @@ -24,18 +24,27 @@ #include "std-vector.hh" #include "grob-interface.hh" +struct Slur_info +{ + Slur_info (Grob *slur); + Grob *slur_; + vector stubs_; +}; + class Slur { public: static void add_column (Grob *me, Grob *col); static void add_extra_encompass (Grob *me, Grob *col); + static void main_to_stub (Grob *main, Grob *stub); static void replace_breakable_encompass_objects (Grob *me); - static void auxiliary_acknowledge_extra_object (Grob_info const &, vector &, vector &); + static void auxiliary_acknowledge_extra_object (Grob_info const &, vector &, vector &); DECLARE_SCHEME_CALLBACK (print, (SCM)); DECLARE_SCHEME_CALLBACK (calc_control_points, (SCM)); DECLARE_SCHEME_CALLBACK (calc_direction, (SCM)); DECLARE_SCHEME_CALLBACK (pure_height, (SCM, SCM, SCM)); DECLARE_SCHEME_CALLBACK (height, (SCM)); + DECLARE_SCHEME_CALLBACK (extremal_stub_vertical_skylines, (SCM)); DECLARE_SCHEME_CALLBACK (vertical_skylines, (SCM)); DECLARE_SCHEME_CALLBACK (outside_slur_callback, (SCM, SCM)); DECLARE_SCHEME_CALLBACK (pure_outside_slur_callback, (SCM, SCM, SCM, SCM)); diff --git a/lily/melody-engraver.cc b/lily/melody-engraver.cc index 0ebd132450..0c9644e384 100644 --- a/lily/melody-engraver.cc +++ b/lily/melody-engraver.cc @@ -70,9 +70,10 @@ Melody_engraver::stop_translation_timestep () } void -Melody_engraver::acknowledge_slur (Grob_info /* info */) +Melody_engraver::acknowledge_slur (Grob_info info) { - melody_item_ = 0; + if (!info.grob ()->internal_has_interface (ly_symbol2scm ("cross-staff-stub-interface"))) + melody_item_ = 0; } void diff --git a/lily/phrasing-slur-engraver.cc b/lily/phrasing-slur-engraver.cc index 7e6c3c63fe..233446bd4d 100644 --- a/lily/phrasing-slur-engraver.cc +++ b/lily/phrasing-slur-engraver.cc @@ -30,6 +30,8 @@ #include "translator.icc" +#include + /* NOTE NOTE NOTE @@ -38,6 +40,8 @@ (on principle, engravers don't use inheritance for code sharing) + For info on SlurStubs, check out slur-engraver.cc. + */ /* @@ -49,8 +53,8 @@ class Phrasing_slur_engraver : public Engraver { vector start_events_; vector stop_events_; - vector slurs_; - vector end_slurs_; + vector slur_infos_; + vector end_slur_infos_; vector objects_to_acknowledge_; protected: @@ -106,10 +110,18 @@ void Phrasing_slur_engraver::acknowledge_note_column (Grob_info info) { Grob *e = info.grob (); - for (vsize i = slurs_.size (); i--;) - Slur::add_column (slurs_[i], e); - for (vsize i = end_slurs_.size (); i--;) - Slur::add_column (end_slurs_[i], e); + for (vsize i = slur_infos_.size (); i--;) + { + Slur::add_column (slur_infos_[i].slur_, e); + Grob *stub = make_spanner ("SlurStub", info.grob ()->self_scm ()); + slur_infos_[i].stubs_.push_back (stub); + } + for (vsize i = end_slur_infos_.size (); i--;) + { + Slur::add_column (end_slur_infos_[i].slur_, e); + Grob *stub = make_spanner ("SlurStub", info.grob ()->self_scm ()); + end_slur_infos_[i].stubs_.push_back (stub); + } } void @@ -146,6 +158,7 @@ void Phrasing_slur_engraver::acknowledge_script (Grob_info info) { if (!info.grob ()->internal_has_interface (ly_symbol2scm ("dynamic-interface"))) + acknowledge_extra_object (info); } @@ -164,18 +177,21 @@ Phrasing_slur_engraver::acknowledge_end_tie (Grob_info info) void Phrasing_slur_engraver::acknowledge_slur (Grob_info info) { - acknowledge_extra_object (info); + if (!info.grob ()->internal_has_interface (ly_symbol2scm ("cross-staff-stub-interface"))) + acknowledge_extra_object (info); } void Phrasing_slur_engraver::finalize () { - for (vsize i = 0; i < slurs_.size (); i++) + for (vsize i = 0; i < slur_infos_.size (); i++) { - slurs_[i]->warning (_ ("unterminated phrasing slur")); - slurs_[i]->suicide (); + slur_infos_[i].slur_->warning (_ ("unterminated phrasing slur")); + slur_infos_[i].slur_->suicide (); + for (vsize j = 0; j < slur_infos_[i].stubs_.size (); j++) + slur_infos_[i].stubs_[j]->suicide (); } - slurs_.clear (); + slur_infos_.clear (); } void @@ -188,13 +204,13 @@ Phrasing_slur_engraver::process_music () // Find the slurs that are ended with this event (by checking the spanner-id) bool ended = false; - for (vsize j = slurs_.size (); j--;) + for (vsize j = slur_infos_.size (); j--;) { - if (id == robust_scm2string (slurs_[j]->get_property ("spanner-id"), "")) + if (id == robust_scm2string (slur_infos_[j].slur_->get_property ("spanner-id"), "")) { ended = true; - end_slurs_.push_back (slurs_[j]); - slurs_.erase (slurs_.begin () + j); + end_slur_infos_.push_back (slur_infos_[j].slur_); + slur_infos_.erase (slur_infos_.begin () + j); } } if (ended) @@ -210,7 +226,7 @@ Phrasing_slur_engraver::process_music () ev->origin ()->warning (_ ("cannot end phrasing slur")); } - vsize old_slurs = slurs_.size (); + vsize old_slurs = slur_infos_.size (); for (vsize i = start_events_.size (); i--;) { Stream_event *ev = start_events_[i]; @@ -218,10 +234,10 @@ Phrasing_slur_engraver::process_music () Direction updown = to_dir (ev->get_property ("direction")); bool completed; - for (vsize j = slurs_.size (); !(completed = (j-- == 0));) + for (vsize j = slur_infos_.size (); !(completed = (j-- == 0));) { // Check if we already have a slur with the same spanner-id. - if (id == robust_scm2string (slurs_[j]->get_property ("spanner-id"), "")) + if (id == robust_scm2string (slur_infos_[j].slur_->get_property ("spanner-id"), "")) { if (j < old_slurs) { @@ -239,11 +255,11 @@ Phrasing_slur_engraver::process_music () if (!updown) break; - Stream_event *c = unsmob_stream_event (slurs_[j]->get_property ("cause")); + Stream_event *c = unsmob_stream_event (slur_infos_[j].slur_->get_property ("cause")); if (!c) { - slurs_[j]->programming_error ("phrasing slur without a cause"); + slur_infos_[j].slur_->programming_error ("phrasing slur without a cause"); continue; } @@ -254,8 +270,10 @@ Phrasing_slur_engraver::process_music () if (!slur_dir) { - slurs_[j]->suicide (); - slurs_.erase (slurs_.begin () + j); + slur_infos_[j].slur_->suicide (); + for (vsize k = 0; k < slur_infos_[i].stubs_.size (); k++) + slur_infos_[j].stubs_[k]->suicide (); + slur_infos_.erase (slur_infos_.begin () + j); continue; } @@ -272,7 +290,7 @@ Phrasing_slur_engraver::process_music () slur->set_property ("spanner-id", ly_string2scm (id)); if (updown) set_grob_direction (slur, updown); - slurs_.push_back (slur); + slur_infos_.push_back (slur); } } } @@ -282,27 +300,58 @@ Phrasing_slur_engraver::stop_translation_timestep () { if (Grob *g = unsmob_grob (get_property ("currentCommandColumn"))) { - for (vsize i = 0; i < end_slurs_.size (); i++) - Slur::add_extra_encompass (end_slurs_[i], g); + for (vsize i = 0; i < end_slur_infos_.size (); i++) + Slur::add_extra_encompass (end_slur_infos_[i].slur_, g); if (!start_events_.size ()) - for (vsize i = 0; i < slurs_.size (); i++) - Slur::add_extra_encompass (slurs_[i], g); + for (vsize i = 0; i < slur_infos_.size (); i++) + Slur::add_extra_encompass (slur_infos_[i].slur_, g); } - for (vsize i = 0; i < end_slurs_.size (); i++) + for (vsize i = 0; i < end_slur_infos_.size (); i++) { - Spanner *s = dynamic_cast (end_slurs_[i]); + Spanner *s = dynamic_cast (end_slur_infos_[i].slur_); if (!s->get_bound (RIGHT)) s->set_bound (RIGHT, unsmob_grob (get_property ("currentMusicalColumn"))); announce_end_grob (s, SCM_EOL); } for (vsize i = 0; i < objects_to_acknowledge_.size (); i++) - Slur::auxiliary_acknowledge_extra_object (objects_to_acknowledge_[i], slurs_, end_slurs_); + Slur::auxiliary_acknowledge_extra_object (objects_to_acknowledge_[i], slur_infos_, end_slur_infos_); + + for (vsize i = 0; i < end_slur_infos_.size (); i++) + { + // There are likely SlurStubs we don't need. Get rid of them. + vector vags; + vector stubs; + for (vsize j = 0; j < end_slur_infos_[i].stubs_.size (); j++) + { + Grob *stub = end_slur_infos_[i].stubs_[j]; + Grob *vag = Grob::get_vertical_axis_group (stub); + if (vag) + { + vector::const_iterator it = + find (vags.begin (), vags.end (), vag); + if (it != vags.end ()) + stub->suicide (); + else + { + vags.push_back (vag); + stubs.push_back (stub); + } + } + else + { + end_slur_infos_[i].slur_->programming_error ("Cannot find vertical axis group for NoteColumn."); + stub->suicide (); + } + } + for (vsize j = 0; j < stubs.size (); j++) + Slur::main_to_stub (end_slur_infos_[i].slur_, stubs[j]); + } objects_to_acknowledge_.clear (); - end_slurs_.clear (); + end_slur_infos_.clear (); start_events_.clear (); stop_events_.clear (); } diff --git a/lily/script-column.cc b/lily/script-column.cc index 0a014a3800..149e666c38 100644 --- a/lily/script-column.cc +++ b/lily/script-column.cc @@ -41,6 +41,13 @@ Script_column::add_side_positioned (Grob *me, Grob *script) Pointer_group_interface::add_grob (me, ly_symbol2scm ("scripts"), script); } +int +pushed_by_slur (Grob *g) +{ + return g->get_property ("avoid-slur") == ly_symbol2scm ("outside") + || g->get_property ("avoid-slur") == ly_symbol2scm ("around"); +} + LY_DEFINE (ly_grob_script_priority_less, "ly:grob-script-priority-less", 2, 0, 0, (SCM a, SCM b), "Compare two grobs by script priority. For internal use.") @@ -48,6 +55,21 @@ LY_DEFINE (ly_grob_script_priority_less, "ly:grob-script-priority-less", Grob *i1 = unsmob_grob (a); Grob *i2 = unsmob_grob (b); + /* + * avoid-staff of slur trumps script priority. If one grob is + * supposed to be printed outside a slur and another grob inside, + * we place the inside grob below the outside even if the inside + * grob has a higher script-priority. + */ + if (unsmob_grob (i1->get_object ("slur")) + && unsmob_grob (i2->get_object ("slur"))) + { + int push1 = pushed_by_slur (i1); + int push2 = pushed_by_slur (i2); + if (push1 != push2) + return push1 < push2 ? SCM_BOOL_T : SCM_BOOL_F; + } + SCM p1 = i1->get_property ("script-priority"); SCM p2 = i2->get_property ("script-priority"); diff --git a/lily/slur-configuration.cc b/lily/slur-configuration.cc index b144b2656f..02df6fe35f 100644 --- a/lily/slur-configuration.cc +++ b/lily/slur-configuration.cc @@ -47,7 +47,7 @@ avoid_staff_line (Slur_score_state const &state, Grob *staff = state.extremes_[LEFT].staff_; - Real p = 2 * (y - staff->relative_coordinate (state.common_[Y_AXIS], Y_AXIS)) + Real p = 2 * (y - staff->maybe_pure_coordinate (state.common_[Y_AXIS], Y_AXIS, state.stub_, 0, INT_MAX)) / state.staff_space_; Real const round = my_round (p); diff --git a/lily/slur-engraver.cc b/lily/slur-engraver.cc index 49f92ab0ed..05249d9176 100644 --- a/lily/slur-engraver.cc +++ b/lily/slur-engraver.cc @@ -30,6 +30,8 @@ #include "translator.icc" +#include + /* NOTE NOTE NOTE @@ -45,12 +47,18 @@ least, it is for phrasing slurs: a note can be both beginning and ending of a phrase. */ + +Slur_info::Slur_info (Grob *slur) +{ + slur_ = slur; +} + class Slur_engraver : public Engraver { vector start_events_; vector stop_events_; - vector slurs_; - vector end_slurs_; + vector slur_infos_; + vector end_slur_infos_; vector objects_to_acknowledge_; void set_melisma (bool); @@ -112,11 +120,28 @@ Slur_engraver::set_melisma (bool m) void Slur_engraver::acknowledge_note_column (Grob_info info) { + /* + * For every active slur, we create a slur stub. + * As we do not yet know what vertical axis groups note columns belong to, + * we create a stub for each note and then suicide duplicate stubs on + * axis groups. + * These slurs should be used ONLY to approximate cross-staff slurs + * in vertical skylines. + */ + Grob *e = info.grob (); - for (vsize i = slurs_.size (); i--;) - Slur::add_column (slurs_[i], e); - for (vsize i = end_slurs_.size (); i--;) - Slur::add_column (end_slurs_[i], e); + for (vsize i = slur_infos_.size (); i--;) + { + Slur::add_column (slur_infos_[i].slur_, e); + Grob *stub = make_spanner ("SlurStub", info.grob ()->self_scm ()); + slur_infos_[i].stubs_.push_back (stub); + } + for (vsize i = end_slur_infos_.size (); i--;) + { + Slur::add_column (end_slur_infos_[i].slur_, e); + Grob *stub = make_spanner ("SlurStub", info.grob ()->self_scm ()); + end_slur_infos_[i].stubs_.push_back (stub); + } } void @@ -171,12 +196,15 @@ Slur_engraver::acknowledge_end_tie (Grob_info info) void Slur_engraver::finalize () { - for (vsize i = 0; i < slurs_.size (); i++) + for (vsize i = 0; i < slur_infos_.size (); i++) { - slurs_[i]->warning (_ ("unterminated slur")); - slurs_[i]->suicide (); + slur_infos_[i].slur_->warning (_ ("unterminated slur")); + slur_infos_[i].slur_->suicide (); + for (vsize j = 0; j < slur_infos_[i].stubs_.size (); j++) + slur_infos_[i].stubs_[j]->suicide (); } - slurs_.clear (); + + slur_infos_.clear (); } void @@ -189,13 +217,13 @@ Slur_engraver::process_music () // Find the slurs that are ended with this event (by checking the spanner-id) bool ended = false; - for (vsize j = slurs_.size (); j--;) + for (vsize j = slur_infos_.size (); j--;) { - if (id == robust_scm2string (slurs_[j]->get_property ("spanner-id"), "")) + if (id == robust_scm2string (slur_infos_[j].slur_->get_property ("spanner-id"), "")) { ended = true; - end_slurs_.push_back (slurs_[j]); - slurs_.erase (slurs_.begin () + j); + end_slur_infos_.push_back (slur_infos_[j]); + slur_infos_.erase (slur_infos_.begin () + j); } } if (ended) @@ -211,7 +239,7 @@ Slur_engraver::process_music () ev->origin ()->warning (_ ("cannot end slur")); } - vsize old_slurs = slurs_.size (); + vsize old_slurs = slur_infos_.size (); for (vsize i = start_events_.size (); i--;) { Stream_event *ev = start_events_[i]; @@ -219,10 +247,10 @@ Slur_engraver::process_music () Direction updown = to_dir (ev->get_property ("direction")); bool completed; - for (vsize j = slurs_.size (); !(completed = (j-- == 0));) + for (vsize j = slur_infos_.size (); !(completed = (j-- == 0));) { // Check if we already have a slur with the same spanner-id. - if (id == robust_scm2string (slurs_[j]->get_property ("spanner-id"), "")) + if (id == robust_scm2string (slur_infos_[j].slur_->get_property ("spanner-id"), "")) { if (j < old_slurs) { @@ -240,11 +268,11 @@ Slur_engraver::process_music () if (!updown) break; - Stream_event *c = unsmob_stream_event (slurs_[j]->get_property ("cause")); + Stream_event *c = unsmob_stream_event (slur_infos_[j].slur_->get_property ("cause")); if (!c) { - slurs_[j]->programming_error ("slur without a cause"); + slur_infos_[j].slur_->programming_error ("slur without a cause"); continue; } @@ -255,8 +283,10 @@ Slur_engraver::process_music () if (!slur_dir) { - slurs_[j]->suicide (); - slurs_.erase (slurs_.begin () + j); + slur_infos_[j].slur_->suicide (); + for (vsize k = 0; k < slur_infos_[j].stubs_.size (); k++) + slur_infos_[j].stubs_[k]->suicide (); + slur_infos_.erase (slur_infos_.begin () + j); continue; } @@ -273,7 +303,7 @@ Slur_engraver::process_music () slur->set_property ("spanner-id", ly_string2scm (id)); if (updown) set_grob_direction (slur, updown); - slurs_.push_back (slur); + slur_infos_.push_back (Slur_info (slur)); if (to_boolean (get_property ("doubleSlurs"))) { @@ -281,11 +311,11 @@ Slur_engraver::process_music () slur = make_spanner ("Slur", ev->self_scm ()); slur->set_property ("spanner-id", ly_string2scm (id)); set_grob_direction (slur, UP); - slurs_.push_back (slur); + slur_infos_.push_back (Slur_info (slur)); } } } - set_melisma (slurs_.size ()); + set_melisma (slur_infos_.size ()); } void @@ -293,27 +323,59 @@ Slur_engraver::stop_translation_timestep () { if (Grob *g = unsmob_grob (get_property ("currentCommandColumn"))) { - for (vsize i = 0; i < end_slurs_.size (); i++) - Slur::add_extra_encompass (end_slurs_[i], g); + for (vsize i = 0; i < end_slur_infos_.size (); i++) + Slur::add_extra_encompass (end_slur_infos_[i].slur_, g); if (!start_events_.size ()) - for (vsize i = 0; i < slurs_.size (); i++) - Slur::add_extra_encompass (slurs_[i], g); + for (vsize i = 0; i < slur_infos_.size (); i++) + Slur::add_extra_encompass (slur_infos_[i].slur_, g); } - for (vsize i = 0; i < end_slurs_.size (); i++) + for (vsize i = 0; i < end_slur_infos_.size (); i++) { - Spanner *s = dynamic_cast (end_slurs_[i]); + Spanner *s = dynamic_cast (end_slur_infos_[i].slur_); if (!s->get_bound (RIGHT)) s->set_bound (RIGHT, unsmob_grob (get_property ("currentMusicalColumn"))); announce_end_grob (s, SCM_EOL); } for (vsize i = 0; i < objects_to_acknowledge_.size (); i++) - Slur::auxiliary_acknowledge_extra_object (objects_to_acknowledge_[i], slurs_, end_slurs_); + Slur::auxiliary_acknowledge_extra_object (objects_to_acknowledge_[i], slur_infos_, end_slur_infos_); + + for (vsize i = 0; i < end_slur_infos_.size (); i++) + { + // There are likely SlurStubs we don't need. Get rid of them + // and only keep one per VerticalAxisGroup. + vector vags; + vector stubs; + for (vsize j = 0; j < end_slur_infos_[i].stubs_.size (); j++) + { + Grob *stub = end_slur_infos_[i].stubs_[j]; + Grob *vag = Grob::get_vertical_axis_group (stub); + if (vag) + { + vector::const_iterator it = + find (vags.begin (), vags.end (), vag); + if (it != vags.end ()) + stub->suicide (); + else + { + vags.push_back (vag); + stubs.push_back (stub); + } + } + else + { + end_slur_infos_[i].slur_->programming_error ("Cannot find vertical axis group for NoteColumn."); + stub->suicide (); + } + } + for (vsize j = 0; j < stubs.size (); j++) + Slur::main_to_stub (end_slur_infos_[i].slur_, stubs[j]); + } objects_to_acknowledge_.clear (); - end_slurs_.clear (); + end_slur_infos_.clear (); start_events_.clear (); stop_events_.clear (); } diff --git a/lily/slur-scoring.cc b/lily/slur-scoring.cc index e1025fa545..3d929ca750 100644 --- a/lily/slur-scoring.cc +++ b/lily/slur-scoring.cc @@ -69,6 +69,7 @@ Slur_score_state::Slur_score_state () { musical_dy_ = 0.0; valid_ = false; + stub_ = false; edge_has_beams_ = false; has_same_beam_ = false; is_broken_ = false; @@ -114,8 +115,8 @@ Slur_score_state::get_encompass_info (Grob *col) const { 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_]; + ei.head_ = ei.stem_ = col->maybe_pure_extent (common_[Y_AXIS], + Y_AXIS, stub_, 0, INT_MAX)[dir_]; return ei; } Direction stem_dir = get_grob_direction (stem); @@ -128,16 +129,16 @@ Slur_score_state::get_encompass_info (Grob *col) const Grob *h = Stem::extremal_heads (stem)[Direction (dir_)]; if (!h) { - ei.head_ = ei.stem_ = col->extent (common_[Y_AXIS], Y_AXIS)[dir_]; + ei.head_ = ei.stem_ = col->maybe_pure_extent (common_[Y_AXIS], Y_AXIS, stub_, 0, INT_MAX)[dir_]; return ei; } - ei.head_ = h->extent (common_[Y_AXIS], Y_AXIS)[dir_]; + ei.head_ = h->maybe_pure_extent (common_[Y_AXIS], Y_AXIS, stub_, 0, INT_MAX)[dir_]; if ((stem_dir == dir_) - && !stem->extent (stem, Y_AXIS).is_empty ()) + && !stem->maybe_pure_extent (stem, Y_AXIS, stub_, 0, INT_MAX).is_empty ()) { - ei.stem_ = stem->extent (common_[Y_AXIS], Y_AXIS)[dir_]; + ei.stem_ = stem->maybe_pure_extent (common_[Y_AXIS], Y_AXIS, stub_, 0, INT_MAX)[dir_]; if (Grob *b = Stem::get_beam (stem)) ei.stem_ += stem_dir * 0.5 * Beam::get_beam_thickness (b); @@ -175,9 +176,13 @@ Slur_score_state::get_bound_info () const for (int a = X_AXIS; a < NO_AXES; a++) { Axis ax = Axis (a); - Interval s = extremes[d].stem_->extent (common_[ax], ax); + Interval s = ax == Y_AXIS + ? extremes[d].stem_->maybe_pure_extent (common_[ax], ax, stub_, 0, INT_MAX) + : extremes[d].stem_->extent (common_[ax], ax); if (extremes[d].flag_) - s.unite (extremes[d].flag_->extent (common_[ax], ax)); + s.unite (ax == Y_AXIS + ? extremes[d].flag_->maybe_pure_extent (common_[ax], ax, stub_, 0, INT_MAX) + : extremes[d].flag_->extent (common_[ax], ax)); if (s.is_empty ()) { /* @@ -185,7 +190,9 @@ Slur_score_state::get_bound_info () const whole notes. */ s = Interval (0, 0) - + extremes[d].stem_->relative_coordinate (common_[ax], ax); + + (ax == Y_AXIS + ? extremes[d].stem_->maybe_pure_coordinate (common_[ax], ax, stub_, 0, INT_MAX) + : extremes[d].stem_->relative_coordinate (common_[ax], ax)); } extremes[d].stem_extent_[ax] = s; } @@ -215,6 +222,8 @@ void Slur_score_state::fill (Grob *me) { slur_ = dynamic_cast (me); + stub_ = slur_->internal_has_interface (ly_symbol2scm ("cross-staff-stub-interface")); + columns_ = internal_extract_grob_array (me, ly_symbol2scm ("note-columns")); @@ -316,7 +325,7 @@ Slur_score_state::fill (Grob *me) if (!is_broken_ && extremes_[d].slur_head_) musical_dy_ += d - * extremes_[d].slur_head_->relative_coordinate (common_[Y_AXIS], Y_AXIS); + * extremes_[d].slur_head_->maybe_pure_coordinate (common_[Y_AXIS], Y_AXIS, stub_, 0, INT_MAX); } edge_has_beams_ @@ -333,6 +342,13 @@ Slur::calc_control_points (SCM smob) { Spanner *me = unsmob_spanner (smob); + if (!to_boolean (me->get_property ("cross-staff")) + && me->internal_has_interface (ly_symbol2scm ("cross-staff-stub-interface"))) + { + me->suicide (); + return SCM_EOL; + } + Slur_score_state state; state.fill (me); @@ -372,7 +388,9 @@ Slur::calc_control_points (SCM smob) { 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)); + me->maybe_pure_coordinate (state.common_[Y_AXIS], Y_AXIS, + state.stub_, 0, INT_MAX)); + controls = scm_cons (ly_offset2scm (o), controls); } @@ -462,7 +480,8 @@ Slur_score_state::get_y_attachment_range () const end_ys[d] = dir_ * max (max (dir_ * (base_attachments_[d][Y_AXIS] + parameters_.region_size_ * dir_), - dir_ * (dir_ + extremes_[d].note_column_->extent (common_[Y_AXIS], Y_AXIS)[dir_])), + dir_ * (dir_ + extremes_[d].note_column_->maybe_pure_extent + (common_[Y_AXIS], Y_AXIS, stub_, 0, INT_MAX)[dir_])), dir_ * base_attachments_[-d][Y_AXIS]); } else @@ -512,7 +531,7 @@ Slur_score_state::get_base_attachments () const || has_same_beam_)) y = extremes_[d].stem_extent_[Y_AXIS][dir_]; else if (head) - y = head->extent (common_[Y_AXIS], Y_AXIS)[dir_]; + y = head->maybe_pure_extent (common_[Y_AXIS], Y_AXIS, stub_, 0, INT_MAX)[dir_]; y += dir_ * 0.5 * staff_space_; y = move_away_from_staffline (y, head); @@ -544,7 +563,7 @@ Slur_score_state::get_base_attachments () const if (extremes_[-d].bound_ != col) { - y = robust_relative_extent (col, common_[Y_AXIS], Y_AXIS)[dir_]; + y = maybe_pure_robust_relative_extent (col, common_[Y_AXIS], Y_AXIS, stub_, 0, INT_MAX)[dir_]; y += dir_ * 0.5 * staff_space_; if (get_grob_direction (col) == dir_ @@ -590,8 +609,8 @@ Slur_score_state::move_away_from_staffline (Real y, return y; Real pos - = (y - staff_symbol->relative_coordinate (common_[Y_AXIS], - Y_AXIS)) + = (y - staff_symbol->maybe_pure_coordinate (common_[Y_AXIS], + Y_AXIS, stub_, 0, INT_MAX)) * 2.0 / staff_space_; if (fabs (pos - my_round (pos)) < 0.2 @@ -629,7 +648,7 @@ Slur_score_state::generate_avoid_offsets () const 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)); + small_slur->maybe_pure_coordinate (common_[Y_AXIS], Y_AXIS, stub_, 0, INT_MAX)); z[Y_AXIS] += dir_ * parameters_.free_slur_distance_; avoid.push_back (z); @@ -638,7 +657,7 @@ Slur_score_state::generate_avoid_offsets () const { Grob *g = extra_encompasses [i]; Interval xe = g->extent (common_[X_AXIS], X_AXIS); - Interval ye = g->extent (common_[Y_AXIS], Y_AXIS); + Interval ye = g->maybe_pure_extent (common_[Y_AXIS], Y_AXIS, stub_, 0, INT_MAX); if (!xe.is_empty () && !ye.is_empty ()) @@ -756,7 +775,7 @@ Slur_score_state::get_extra_encompass_infos () const 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)); + small_slur->maybe_pure_coordinate (common_[Y_AXIS], Y_AXIS, stub_, 0, INT_MAX)); for (int k = 0; k < 3; k++) { @@ -790,7 +809,7 @@ Slur_score_state::get_extra_encompass_infos () const { Grob *g = encompasses [i]; Interval xe = g->extent (common_[X_AXIS], X_AXIS); - Interval ye = g->extent (common_[Y_AXIS], Y_AXIS); + Interval ye = g->maybe_pure_extent (common_[Y_AXIS], Y_AXIS, stub_, 0, INT_MAX); if (Dots::has_interface (g)) ye.widen (0.2); diff --git a/lily/slur.cc b/lily/slur.cc index eb9913dc16..aec1e04266 100644 --- a/lily/slur.cc +++ b/lily/slur.cc @@ -145,6 +145,7 @@ SCM Slur::print (SCM smob) { Grob *me = unsmob_grob (smob); + extract_grob_set (me, "note-columns", encompasses); if (encompasses.empty ()) { @@ -251,6 +252,25 @@ Slur::add_extra_encompass (Grob *me, Grob *n) Pointer_group_interface::add_grob (me, ly_symbol2scm ("encompass-objects"), n); } +void +Slur::main_to_stub (Grob *main, Grob *stub) +{ + extract_grob_set (main, "note-columns", nc); + for (vsize i = 0; i < nc.size (); i++) + add_column (stub, nc[i]); + + extract_grob_set (main, "encompass-objects", eo); + for (vsize i = 0; i < eo.size (); i++) + add_extra_encompass (stub, eo[i]); + + stub->set_object ("surrogate", main->self_scm ()); + + dynamic_cast (stub)->set_bound + (LEFT, dynamic_cast (main)->get_bound (LEFT)); + dynamic_cast (stub)->set_bound + (RIGHT, dynamic_cast (main)->get_bound (RIGHT)); +} + MAKE_SCHEME_CALLBACK_WITH_OPTARGS (Slur, pure_outside_slur_callback, 4, 1, ""); SCM Slur::pure_outside_slur_callback (SCM grob, SCM start_scm, SCM end_scm, SCM offset_scm) @@ -387,32 +407,69 @@ Slur::vertical_skylines (SCM smob) return Skyline_pair (boxes, X_AXIS).smobbed_copy (); } +/* + * USE ME ONLY FOR CROSS STAFF SLURS! + * We only want to keep the topmost skyline of the topmost axis group(s) + * and the bottommost skyline of the bottommost axis group(s). Otherwise, + * the VerticalAxisGroups will be spaced very far apart to accommodate the + * slur, which we don't want, as it is cross staff. + * + * TODO: Currently, the code below keeps the topmost and bottommost axis + * groups and gets rid of the rest. This should be more nuanced for + * cases like ossias where the topmost staff changes over the course of + * the slur. Ditto for the bottommost staff. + */ + +MAKE_SCHEME_CALLBACK (Slur, extremal_stub_vertical_skylines, 1); +SCM +Slur::extremal_stub_vertical_skylines (SCM smob) +{ + Grob *me = unsmob_grob (smob); + Grob *my_vag = Grob::get_vertical_axis_group (me); + extract_grob_set (me, "note-columns", ro_note_columns); + vector note_columns (ro_note_columns); + vector_sort (note_columns, Grob::vertical_less); + bool highest = my_vag == Grob::get_vertical_axis_group (note_columns[0]); + bool lowest = my_vag == Grob::get_vertical_axis_group (note_columns.back ()); + if (!highest && !lowest) + return Skyline_pair ().smobbed_copy (); + + Skyline_pair sky = *Skyline_pair::unsmob (vertical_skylines (smob)); + + if (highest) + sky[DOWN] = Skyline (DOWN); + else + sky[UP] = Skyline (UP); + + return sky.smobbed_copy (); +} + /* * Used by Slur_engraver:: and Phrasing_slur_engraver:: */ void Slur::auxiliary_acknowledge_extra_object (Grob_info const &info, - vector &slurs, - vector &end_slurs) + vector &slur_infos, + vector &end_slur_infos) { - if (slurs.empty () && end_slurs.empty ()) + if (slur_infos.empty () && end_slur_infos.empty ()) return; Grob *e = info.grob (); SCM avoid = e->get_property ("avoid-slur"); Grob *slur; - if (end_slurs.size () && !slurs.size ()) - slur = end_slurs[0]; + if (end_slur_infos.size () && !slur_infos.size ()) + slur = end_slur_infos[0].slur_; else - slur = slurs[0]; + slur = slur_infos[0].slur_; if (Tie::has_interface (e) || avoid == ly_symbol2scm ("inside")) { - for (vsize i = slurs.size (); i--;) - add_extra_encompass (slurs[i], e); - for (vsize i = end_slurs.size (); i--;) - add_extra_encompass (end_slurs[i], e); + for (vsize i = slur_infos.size (); i--;) + add_extra_encompass (slur_infos[i].slur_, e); + for (vsize i = end_slur_infos.size (); i--;) + add_extra_encompass (end_slur_infos[i].slur_, e); if (slur) e->set_object ("slur", slur->self_scm ()); } diff --git a/lily/tab-tie-follow-engraver.cc b/lily/tab-tie-follow-engraver.cc index b036599014..5b4d2990a7 100644 --- a/lily/tab-tie-follow-engraver.cc +++ b/lily/tab-tie-follow-engraver.cc @@ -70,7 +70,8 @@ Tab_tie_follow_engraver::acknowledge_tab_note_head (Grob_info info) void Tab_tie_follow_engraver::acknowledge_slur (Grob_info info) { - slurs_.push_back (info.spanner ()); + if (!info.grob ()->internal_has_interface (ly_symbol2scm ("cross-staff-stub-interface"))) + slurs_.push_back (info.spanner ()); } void diff --git a/ly/property-init.ly b/ly/property-init.ly index e36c3c0465..f2fadc1d39 100644 --- a/ly/property-init.ly +++ b/ly/property-init.ly @@ -402,9 +402,9 @@ shiftOff = \revert NoteColumn #'horizontal-shift %% slurs % directions -slurUp = \override Slur #'direction = #UP -slurDown = \override Slur #'direction = #DOWN -slurNeutral = \revert Slur #'direction +slurUp = { \override Slur #'direction = #UP \override SlurStub #'direction = #UP } +slurDown = { \override Slur #'direction = #DOWN \override SlurStub #'direction = #DOWN } +slurNeutral = { \revert Slur #'direction \revert SlurStub #'direction } % dash-patterns (make-simple-dash-definition defined at top of file) slurDashPattern = diff --git a/scm/define-grob-interfaces.scm b/scm/define-grob-interfaces.scm index eb76e96e0b..f73f6d9534 100644 --- a/scm/define-grob-interfaces.scm +++ b/scm/define-grob-interfaces.scm @@ -95,6 +95,11 @@ printed, but a line break is allowed at that spot. "A doit or drop." '(thickness delta-position)) +(ly:add-interface + 'cross-staff-stub-interface + "Used to approximate vertical skylines in cross-staff grobs." + '(surrogate)) + (ly:add-interface 'dynamic-interface "Any kind of loudness sign." diff --git a/scm/define-grob-properties.scm b/scm/define-grob-properties.scm index 82102dd42e..72e9437cad 100644 --- a/scm/define-grob-properties.scm +++ b/scm/define-grob-properties.scm @@ -1155,6 +1155,7 @@ results, use @code{LEFT} and @code{RIGHT}.") (staff-symbol ,ly:grob? "The staff symbol grob that we are in.") (stem ,ly:grob? "A pointer to a @code{Stem} object.") (stems ,ly:grob-array? "An array of stem objects.") + (surrogate ,ly:grob? "The grob that a cross-staff stub is shadowing.") (tie ,ly:grob? "A pointer to a @code{Tie} object.") (ties ,ly:grob-array? "A grob array of @code{Tie} objects.") diff --git a/scm/define-grobs.scm b/scm/define-grobs.scm index cc8a735565..b23ec54e24 100644 --- a/scm/define-grobs.scm +++ b/scm/define-grobs.scm @@ -1844,6 +1844,30 @@ (meta . ((class . Spanner) (interfaces . (slur-interface)))))) + ;; Only should be used to approximate vertical skylines + ;; of a cross-staff slur. + (SlurStub + . ( + (avoid-slur . inside) + (control-points . ,ly:slur::calc-control-points) + (cross-staff . ,slur::if-not-cross-staff-suicide) + (details . ,default-slur-details) + (direction . ,ly:slur::calc-direction) + (height-limit . 2.0) + (line-thickness . 0.8) + (minimum-length . 1.5) + (ratio . 0.25) + (spanner-id . "") + (springs-and-rods . ,ly:spanner::set-spacing-rods) + (stencil . ,ly:slur::print) + (thickness . 1.2) + (transparent . #t) + (vertical-skylines . ,ly:slur::extremal-stub-vertical-skylines) + (Y-extent . ,ly:slur::height) + (meta . ((class . Spanner) + (interfaces . (cross-staff-stub-interface + slur-interface)))))) + (SostenutoPedal . ( (direction . ,RIGHT) diff --git a/scm/output-lib.scm b/scm/output-lib.scm index 3ced3354cd..d041e5d172 100644 --- a/scm/output-lib.scm +++ b/scm/output-lib.scm @@ -109,6 +109,14 @@ (ly:side-position-interface::calc-cross-staff g))) +(define-public (slur::if-not-cross-staff-suicide g) + (let ((cs (ly:slur::calc-cross-staff g))) + (if (not cs) + (begin + (ly:grob-suicide! g) + #f) + #t))) + ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; note heads @@ -356,7 +364,6 @@ and duration-log @var{log}." (define-public spanbar-begin-of-line-invisible #(#t #f #f)) - ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; neighbor-interface routines