]> git.donarmstrong.com Git - lilypond.git/commitdiff
Fix issues 75 and 1256: Allow multiple concurrent slurs
authorReinhold Kainhofer <reinhold@kainhofer.com>
Sat, 2 Jul 2011 20:23:53 +0000 (22:23 +0200)
committerCarl Sorensen <c_sorensen@byu.edu>
Wed, 13 Jul 2011 18:45:35 +0000 (12:45 -0600)
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)

Update regtest versions for backport

12 files changed:
input/regression/phrasing-slur-multiple.ly [new file with mode: 0644]
input/regression/slur-grace.ly [new file with mode: 0644]
input/regression/slur-multiple-linebreak.ly [new file with mode: 0644]
input/regression/slur-multiple.ly [new file with mode: 0644]
lily/phrasing-slur-engraver.cc
lily/slur-engraver.cc
lily/spanner.cc
ly/grace-init.ly
scm/define-grob-properties.scm
scm/define-grobs.scm
scm/define-music-properties.scm
scm/define-music-types.scm

diff --git a/input/regression/phrasing-slur-multiple.ly b/input/regression/phrasing-slur-multiple.ly
new file mode 100644 (file)
index 0000000..fb7b499
--- /dev/null
@@ -0,0 +1,21 @@
+\version "2.14.2"
+
+#(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 (file)
index 0000000..eeebe45
--- /dev/null
@@ -0,0 +1,12 @@
+\version "2.14.2"
+
+\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 (file)
index 0000000..1bf370e
--- /dev/null
@@ -0,0 +1,26 @@
+\version "2.14.2"
+
+#(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
diff --git a/input/regression/slur-multiple.ly b/input/regression/slur-multiple.ly
new file mode 100644 (file)
index 0000000..7773632
--- /dev/null
@@ -0,0 +1,21 @@
+\version "2.14.2"
+
+#(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 |
+
+}
index 8ace05fe10d8bad48f7017fa989ccb65a0cf236b..839554f7e9af9a1d7f3c174503e4bad58e34a73c 100644 (file)
 
 #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<Stream_event *> events_;
-  Stream_event *running_slur_start_;
+  vector<Stream_event *> start_events_;
+  vector<Stream_event *> stop_events_;
   vector<Grob*> slurs_;
   vector<Grob*> 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);
index 2b3f5de296b666ba7eaca8dbb5971493e059a3a4..45aa121a71eebd00cf0580fbf6d33ed42febd13d 100644 (file)
@@ -48,8 +48,8 @@
 */
 class Slur_engraver : public Engraver
 {
-  Drul_array<Stream_event *> events_;
-  Stream_event *running_slur_start_;
+  vector<Stream_event *> start_events_;
+  vector<Stream_event *> stop_events_;
   vector<Grob*> slurs_;
   vector<Grob*> 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);
index 30638bfed44d64f7c44743b8a2a465d0b543ee47..22282aa231d9793d62bb2f8f483b75f8ba0d0a99 100644 (file)
@@ -547,5 +547,6 @@ ADD_INTERFACE (Spanner,
               /* properties */
               "normalized-endpoints "
               "minimum-length "
+               "spanner-id "
               "to-barline "
               );
index 1f8edca81e3344be7ced10728a505b6a0328700a..b9729f3e9360735d3f4a29aa772801a45acb4240 100644 (file)
@@ -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
 }
index 7fd0b138190bf35f20d9fcec62934c92af86839b..6afeead4624f619114f3d10f4bbe3ebf9ab37620 100644 (file)
@@ -771,6 +771,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?")
index 84c3f72f8fbb72c715ebab652bd81c2780b31998..0e7e81f0c56bdc34e34d9578150128b36cf1dcfa 100644 (file)
        (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)
        (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)
index 201efa6528e8db2563c2e5e6e3ba6e0b001377c5..5e1159363654e42981a8677b67ff785cf91d3dc9 100644 (file)
@@ -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
index d57cba811816f44214c150b8e781ae403eac8f95..8a3676b2a1ff13a497abe3959238d888bffc1fd6 100644 (file)
@@ -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))
        ))