From 32776795a1be5c0ec472f59694510705607673b0 Mon Sep 17 00:00:00 2001 From: Reinhold Kainhofer Date: Sat, 2 Jul 2011 22:23:53 +0200 Subject: [PATCH] Fix issues 75 and 1256: Allow multiple concurrent slurs Rewrite the Slur_engraver and the Phrasing_slur_engraver to support multiple concurrent slurs. The default lilypond syntax using parentheses still supports only one slur at a time, but by adding a spanner-id property to the (Phrasing)SlurEvent music expression, one can create multiple concurrent slurs, each with a different spanner-id. This finally allows appoggiaturas and acciaccaturas (which both create a slur from the grace note the the next note) to be placed inside a slur. If we observe a new slur start while a slur is already present, we now totally ignore the new slur event, so it does not influence the appearance of the existing slur (bug 1256) --- input/regression/phrasing-slur-multiple.ly | 21 ++++ input/regression/slur-grace.ly | 12 +++ input/regression/slur-multiple-linebreak.ly | 26 +++++ input/regression/slur-multiple.ly | 21 ++++ lily/phrasing-slur-engraver.cc | 105 +++++++++++++------- lily/slur-engraver.cc | 99 +++++++++++------- lily/spanner.cc | 1 + ly/grace-init.ly | 11 +- scm/define-grob-properties.scm | 1 + scm/define-grobs.scm | 2 + scm/define-music-properties.scm | 1 + scm/define-music-types.scm | 2 + 12 files changed, 223 insertions(+), 79 deletions(-) create mode 100644 input/regression/phrasing-slur-multiple.ly create mode 100644 input/regression/slur-grace.ly create mode 100644 input/regression/slur-multiple-linebreak.ly create mode 100644 input/regression/slur-multiple.ly diff --git a/input/regression/phrasing-slur-multiple.ly b/input/regression/phrasing-slur-multiple.ly new file mode 100644 index 0000000000..203a6eceee --- /dev/null +++ b/input/regression/phrasing-slur-multiple.ly @@ -0,0 +1,21 @@ +\version "2.15.4" + +#(ly:set-option 'warning-as-error #f) + +\header { + texidoc = "LilyPond does not support multiple concurrent phrasing slurs with the +parentheses syntax. In this case, warnings will be given and the nested +slur will not be generated. However, one can can create a second slur with +a different spanner-id." +} + +altPhSlur = #(make-music 'PhrasingSlurEvent 'span-direction START 'spanner-id "alt") +altPhSlurEnd = #(make-music 'PhrasingSlurEvent 'span-direction STOP 'spanner-id "alt") + +\relative c'' { + % This will give warnings ("Already have phrasing slur" and "Cannot end phrasing slur") + c4\(\( d4\)\( e4\) f\) | + % This will give two overlapping slurs: + d\( d\altPhSlur e\) f\altPhSlurEnd | + +} diff --git a/input/regression/slur-grace.ly b/input/regression/slur-grace.ly new file mode 100644 index 0000000000..83d650c1b0 --- /dev/null +++ b/input/regression/slur-grace.ly @@ -0,0 +1,12 @@ +\version "2.15.4" + +\header { + texidoc = "Appoggiatura and acciaccaturas use a different slur than the +default, so they produce a nested slur without warnings." +} + +\relative c'' { + c4( \acciaccatura e8 d4 e4 f) | + c4( \appoggiatura e8 d4 e4 f) | + c4 \appoggiatura e8 d4 e4 f | +} diff --git a/input/regression/slur-multiple-linebreak.ly b/input/regression/slur-multiple-linebreak.ly new file mode 100644 index 0000000000..763f623922 --- /dev/null +++ b/input/regression/slur-multiple-linebreak.ly @@ -0,0 +1,26 @@ +\version "2.15.5" + +#(ly:set-option 'warning-as-error #f) + +\header { + texidoc = "An additional opening slur during a running slur should be ignored +(and a warning printed), but never influence the slur's extents." +} + +\paper { ragged-right = ##t } + +\relative c' { + \key fis \major + c1( + \break + a2 b4 c) +} + +\relative c' { + \key fis \major + c1( + \break + a2( b4 c) +% ^ extra SlurEvent +} +%% END \ No newline at end of file diff --git a/input/regression/slur-multiple.ly b/input/regression/slur-multiple.ly new file mode 100644 index 0000000000..21cb444071 --- /dev/null +++ b/input/regression/slur-multiple.ly @@ -0,0 +1,21 @@ +\version "2.15.5" + +#(ly:set-option 'warning-as-error #f) + +\header { + texidoc = "LilyPond does not support multiple concurrent slurs with the +parentheses syntax. In this case, warnings will be given and the nested +slur will not be generated. However, one can can create a second slur with +a different spanner-id." +} + +altSlur = #(make-music 'SlurEvent 'span-direction START 'spanner-id "alt") +altSlurEnd = #(make-music 'SlurEvent 'span-direction STOP 'spanner-id "alt") + +\relative c'' { + % This will give warnings ("Already have slur" and "Cannot end slur") + c4(( d4)( e4) f) | + % This will give two overlapping slurs: + d( d\altSlur e) f\altSlurEnd | + +} diff --git a/lily/phrasing-slur-engraver.cc b/lily/phrasing-slur-engraver.cc index 8ace05fe10..839554f7e9 100644 --- a/lily/phrasing-slur-engraver.cc +++ b/lily/phrasing-slur-engraver.cc @@ -30,30 +30,32 @@ #include "translator.icc" -/* - It is possible that a slur starts and ends on the same note. At - least, it is for phrasing slurs: a note can be both beginning and - ending of a phrase. - -*/ /* NOTE NOTE NOTE - This is largely similar to Slur_engraver. Check if fixes apply there too. + This is largely similar to Slur_engraver. Check if fixes + apply there too. (on principle, engravers don't use inheritance for code sharing) */ + +/* + It is possible that a slur starts and ends on the same note. At + least, it is for phrasing slurs: a note can be both beginning and + ending of a phrase. + +*/ class Phrasing_slur_engraver : public Engraver { - Drul_array events_; - Stream_event *running_slur_start_; + vector start_events_; + vector stop_events_; vector slurs_; vector end_slurs_; protected: - void acknowledge_extra_object (Grob_info); + DECLARE_TRANSLATOR_LISTENER (phrasing_slur); DECLARE_ACKNOWLEDGER (accidental); DECLARE_ACKNOWLEDGER (fingering); DECLARE_ACKNOWLEDGER (note_column); @@ -62,33 +64,33 @@ protected: DECLARE_ACKNOWLEDGER (text_script); DECLARE_ACKNOWLEDGER (tie); DECLARE_ACKNOWLEDGER (tuplet_number); - DECLARE_TRANSLATOR_LISTENER (phrasing_slur); + void acknowledge_extra_object (Grob_info); void stop_translation_timestep (); - virtual void finalize (); void process_music (); + virtual void finalize (); + + public: TRANSLATOR_DECLARATIONS (Phrasing_slur_engraver); }; Phrasing_slur_engraver::Phrasing_slur_engraver () { - events_[START] = events_[STOP] = 0; } IMPLEMENT_TRANSLATOR_LISTENER (Phrasing_slur_engraver, phrasing_slur); void Phrasing_slur_engraver::listen_phrasing_slur (Stream_event *ev) { - /* - Let's not start more than one slur per moment. - */ Direction d = to_dir (ev->get_property ("span-direction")); if (d == START) - ASSIGN_EVENT_ONCE (events_[START], ev); - else if (d == STOP && !slurs_.empty ()) - ASSIGN_EVENT_ONCE (events_[STOP], ev); + start_events_.push_back(ev); + else if (d == STOP) + stop_events_.push_back(ev); + else ev->origin ()->warning (_f ("direction of %s invalid: %d", + "phrasing-slur-event", int (d))); } void @@ -120,7 +122,7 @@ Phrasing_slur_engraver::acknowledge_fingering (Grob_info info) } void -Phrasing_slur_engraver::acknowledge_text_script (Grob_info info) +Phrasing_slur_engraver::acknowledge_tuplet_number (Grob_info info) { acknowledge_extra_object (info); } @@ -133,13 +135,13 @@ Phrasing_slur_engraver::acknowledge_script (Grob_info info) } void -Phrasing_slur_engraver::acknowledge_tie (Grob_info info) +Phrasing_slur_engraver::acknowledge_text_script (Grob_info info) { acknowledge_extra_object (info); } void -Phrasing_slur_engraver::acknowledge_tuplet_number (Grob_info info) +Phrasing_slur_engraver::acknowledge_tie (Grob_info info) { acknowledge_extra_object (info); } @@ -153,29 +155,57 @@ Phrasing_slur_engraver::acknowledge_slur (Grob_info info) void Phrasing_slur_engraver::finalize () { - if (slurs_.size ()) - slurs_[0]->warning (_ ("unterminated phrasing slur")); + for (vsize i = 0; i < slurs_.size (); i++) + { + slurs_[i]->warning (_ ("unterminated phrasing slur")); + slurs_[i]->suicide (); + } } void Phrasing_slur_engraver::process_music () { - if (events_[STOP]) + for (vsize i = 0; i < stop_events_.size (); i++) { - end_slurs_ = slurs_; - slurs_.clear (); + Stream_event *ev = stop_events_[i]; + string id = robust_scm2string (ev->get_property ("spanner-id"), ""); + + // Find the slur that is ended with this event (by checking the spanner-id) + bool ended = false; + for (vsize j = slurs_.size (); j--;) + { + if (id == robust_scm2string (slurs_[j]->get_property ("spanner-id"), "")) + { + ended = true; + end_slurs_.push_back (slurs_[j]); + slurs_.erase (slurs_.begin () + j); + } + } + if (!ended) + ev->origin ()->warning (_ ("cannot end phrasing slur")); } - if (events_[START] && slurs_.empty ()) + for (vsize i = 0; i < start_events_.size (); i++) { - Stream_event *ev = events_[START]; - - Grob *slur = make_spanner ("PhrasingSlur", events_[START]->self_scm ()); - Direction updown = to_dir (ev->get_property ("direction")); - if (updown) - set_grob_direction (slur, updown); - - slurs_.push_back (slur); + Stream_event *ev = start_events_[i]; + string id = robust_scm2string (ev->get_property ("spanner-id"), ""); + bool have_slur = false; + // Check if we already have a slur with the same spanner-id. + // In that case, don't create a new slur, but print a warning + for (vsize i = 0; i < slurs_.size (); i++) + have_slur = have_slur || (id == robust_scm2string (slurs_[i]->get_property ("spanner-id"), "")); + + if (have_slur) + ev->origin ()->warning(_ ("already have phrasing slur")); + else + { + Grob *slur = make_spanner ("PhrasingSlur", ev->self_scm ()); + Direction updown = to_dir (ev->get_property ("direction")); + slur->set_property ("spanner-id", ly_string2scm (id)); + if (updown) + set_grob_direction (slur, updown); + slurs_.push_back (slur); + } } } @@ -183,7 +213,8 @@ void Phrasing_slur_engraver::stop_translation_timestep () { end_slurs_.clear (); - events_[START] = events_[STOP] = 0; + start_events_.clear (); + stop_events_.clear (); } ADD_ACKNOWLEDGER (Phrasing_slur_engraver, accidental); diff --git a/lily/slur-engraver.cc b/lily/slur-engraver.cc index 2b3f5de296..45aa121a71 100644 --- a/lily/slur-engraver.cc +++ b/lily/slur-engraver.cc @@ -48,8 +48,8 @@ */ class Slur_engraver : public Engraver { - Drul_array events_; - Stream_event *running_slur_start_; + vector start_events_; + vector stop_events_; vector slurs_; vector end_slurs_; @@ -78,7 +78,6 @@ public: Slur_engraver::Slur_engraver () { - events_[START] = events_[STOP] = 0; } IMPLEMENT_TRANSLATOR_LISTENER (Slur_engraver, slur); @@ -87,9 +86,9 @@ Slur_engraver::listen_slur (Stream_event *ev) { Direction d = to_dir (ev->get_property ("span-direction")); if (d == START) - ASSIGN_EVENT_ONCE (events_[START], ev); + start_events_.push_back(ev); else if (d == STOP) - ASSIGN_EVENT_ONCE (events_[STOP], ev); + stop_events_.push_back(ev); else ev->origin ()->warning (_f ("direction of %s invalid: %d", "slur-event", int (d))); } @@ -134,7 +133,6 @@ Slur_engraver::acknowledge_tuplet_number (Grob_info info) acknowledge_extra_object (info); } - void Slur_engraver::acknowledge_script (Grob_info info) { @@ -157,47 +155,71 @@ Slur_engraver::acknowledge_tie (Grob_info info) void Slur_engraver::finalize () { - if (slurs_.size ()) + for (vsize i = 0; i < slurs_.size (); i++) { - slurs_[0]->warning (_ ("unterminated slur")); - for (vsize i = 0; i < slurs_.size (); i++) - slurs_[i]->suicide (); + slurs_[i]->warning (_ ("unterminated slur")); + slurs_[i]->suicide (); } } void Slur_engraver::process_music () { - if (events_[STOP]) + for (vsize i = 0; i < stop_events_.size (); i++) { - if (slurs_.size () == 0) - events_[STOP]->origin ()->warning (_ ("cannot end slur")); - - - end_slurs_ = slurs_; - slurs_.clear (); + Stream_event *ev = stop_events_[i]; + string id = robust_scm2string (ev->get_property ("spanner-id"), ""); + + // Find the slur that is ended with this event (by checking the spanner-id) + bool ended = false; + for (vsize j = slurs_.size (); j--;) + { + if (id == robust_scm2string (slurs_[j]->get_property ("spanner-id"), "")) + { + ended = true; + end_slurs_.push_back (slurs_[j]); + slurs_.erase (slurs_.begin () + j); + } + } + if (!ended) + ev->origin ()->warning (_ ("cannot end slur")); } - if (events_[START] && slurs_.empty ()) + for (vsize i = start_events_.size (); i--;) { - Stream_event *ev = events_[START]; - - bool double_slurs = to_boolean (get_property ("doubleSlurs")); - - Grob *slur = make_spanner ("Slur", events_[START]->self_scm ()); - Direction updown = to_dir (ev->get_property ("direction")); - if (updown && !double_slurs) - set_grob_direction (slur, updown); - - slurs_.push_back (slur); - - if (double_slurs) - { - set_grob_direction (slur, DOWN); - slur = make_spanner ("Slur", events_[START]->self_scm ()); - set_grob_direction (slur, UP); - slurs_.push_back (slur); - } + Stream_event *ev = start_events_[i]; + string id = robust_scm2string (ev->get_property ("spanner-id"), ""); + bool have_slur = false; + // Check if we already have a slur with the same spanner-id. + // In that case, don't create a new slur, but print a warning + for (vsize j = 0; j < slurs_.size (); j++) + have_slur = have_slur || (id == robust_scm2string (slurs_[j]->get_property ("spanner-id"), "")); + + if (have_slur) + { + // We already have a slur, so give a warning and completely ignore + // the new slur. + ev->origin ()->warning(_ ("already have slur")); + start_events_.erase (start_events_.begin () + i); + } + else + { + Grob *slur = make_spanner ("Slur", ev->self_scm ()); + Direction updown = to_dir (ev->get_property ("direction")); + slur->set_property ("spanner-id", ly_string2scm (id)); + if (updown) + set_grob_direction (slur, updown); + slurs_.push_back (slur); + + if (to_boolean (get_property ("doubleSlurs"))) + { + set_grob_direction (slur, DOWN); + slur = make_spanner ("Slur", ev->self_scm ()); + slur->set_property ("spanner-id", ly_string2scm (id)); + set_grob_direction (slur, UP); + slurs_.push_back (slur); + } + } } set_melisma (slurs_.size ()); } @@ -210,7 +232,7 @@ Slur_engraver::stop_translation_timestep () for (vsize i = 0; i < end_slurs_.size (); i++) Slur::add_extra_encompass (end_slurs_[i], g); - if (!events_[START]) + if (!start_events_.size ()) for (vsize i = 0; i < slurs_.size (); i++) Slur::add_extra_encompass (slurs_[i], g); } @@ -224,7 +246,8 @@ Slur_engraver::stop_translation_timestep () announce_end_grob (s, SCM_EOL); } end_slurs_.clear (); - events_[START] = events_[STOP] = 0; + start_events_.clear (); + stop_events_.clear (); } ADD_ACKNOWLEDGER (Slur_engraver, accidental); diff --git a/lily/spanner.cc b/lily/spanner.cc index 30638bfed4..22282aa231 100644 --- a/lily/spanner.cc +++ b/lily/spanner.cc @@ -547,5 +547,6 @@ ADD_INTERFACE (Spanner, /* properties */ "normalized-endpoints " "minimum-length " + "spanner-id " "to-barline " ); diff --git a/ly/grace-init.ly b/ly/grace-init.ly index 1f8edca81e..b9729f3e93 100644 --- a/ly/grace-init.ly +++ b/ly/grace-init.ly @@ -1,5 +1,8 @@ \version "2.14.0" +startGraceSlur = #(make-music 'SlurEvent 'span-direction START 'spanner-id "grace") +stopGraceSlur = #(make-music 'SlurEvent 'span-direction STOP 'spanner-id "grace") + startGraceMusic = { } @@ -9,19 +12,19 @@ stopGraceMusic = { startAppoggiaturaMusic = { - s1*0( + s1*0\startGraceSlur } stopAppoggiaturaMusic = { - s1*0) + s1*0\stopGraceSlur } startAcciaccaturaMusic = { - s1*0( + s1*0\startGraceSlur \override Stem #'stroke-style = #"grace" } stopAcciaccaturaMusic = { \revert Stem #'stroke-style - s1*0) + s1*0\stopGraceSlur } diff --git a/scm/define-grob-properties.scm b/scm/define-grob-properties.scm index a10e62b965..5c2114338e 100644 --- a/scm/define-grob-properties.scm +++ b/scm/define-grob-properties.scm @@ -777,6 +777,7 @@ override: @example \\override MultiMeasureRest #'spacing-pair = #'(staff-bar . staff-bar) @end example") + (spanner-id ,string? "An identifier to distinguish concurrent spanners.") (springs-and-rods ,boolean? "Dummy variable for triggering spacing routines.") (stacking-dir ,ly:dir? "Stack objects in which direction?") diff --git a/scm/define-grobs.scm b/scm/define-grobs.scm index c57675530b..177ebafc84 100644 --- a/scm/define-grobs.scm +++ b/scm/define-grobs.scm @@ -1589,6 +1589,7 @@ (height-limit . 2.0) (minimum-length . 1.5) (ratio . 0.333) + (spanner-id . "") (springs-and-rods . ,ly:spanner::set-spacing-rods) (stencil . ,ly:slur::print) (thickness . 1.1) @@ -1743,6 +1744,7 @@ (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) diff --git a/scm/define-music-properties.scm b/scm/define-music-properties.scm index 201efa6528..5e11593636 100644 --- a/scm/define-music-properties.scm +++ b/scm/define-music-properties.scm @@ -167,6 +167,7 @@ If zero, signals a beat containing varying durations.") Options are @code{'text} and @code{'hairpin}.") (span-text ,markup? "The displayed text for dynamic text spanners (e.g., cresc.)") + (spanner-id ,string? "Identifier to distinguish concurrent spanners.") (split-list ,list? "Splitting moments for part combiner.") (start-callback ,procedure? "Function to compute the negative length of starting grace notes. This property can only be defined as initializer diff --git a/scm/define-music-types.scm b/scm/define-music-types.scm index d57cba8118..8a3676b2a1 100644 --- a/scm/define-music-types.scm +++ b/scm/define-music-types.scm @@ -403,6 +403,7 @@ goes down).") . ((description . "Start or end phrasing slur. Syntax: @var{note}@code{\\(} and @var{note}@code{\\)}") + (spanner-id . "") (types . (general-music span-event event phrasing-slur-event)) )) @@ -534,6 +535,7 @@ Syntax: @code{\\skip} @var{duration}") . ((description . "Start or end slur. Syntax: @var{note}@code{(} and @var{note}@code{)}") + (spanner-id . "") (types . (general-music span-event event slur-event)) )) -- 2.39.2