]> 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)
committerReinhold Kainhofer <reinhold@kainhofer.com>
Tue, 12 Jul 2011 17:29:13 +0000 (19:29 +0200)
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)

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..203a6ec
--- /dev/null
@@ -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 (file)
index 0000000..83d650c
--- /dev/null
@@ -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 (file)
index 0000000..763f623
--- /dev/null
@@ -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 (file)
index 0000000..21cb444
--- /dev/null
@@ -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 |
+  
+}
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 a10e62b965a25bf73c9b0ff3f89c923e3e76fe1d..5c2114338e053cb5468c5af6672d689d22d74ddf 100644 (file)
@@ -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?")
index c57675530b8c7016709b71f1b88e9c60f28c8a6f..177ebafc847426248949e66dc7d4d4c02b697415 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))
        ))