]> git.donarmstrong.com Git - lilypond.git/blobdiff - lily/lyric-phrasing-engraver.cc
* lily/lyric-phrasing-engraver.cc: move from
[lilypond.git] / lily / lyric-phrasing-engraver.cc
index 23b8fd4cdcd2e40b4c66388c8b3cd1858c3e0df2..c813a5a91d7bc909cd95065e33a877053805cd94 100644 (file)
-/*
-  lyric-phrasing-engraver.cc -- implement Lyric_phrasing_engraver
+/*   
+  new-phrasing-engraver.cc --  implement New_phrasing_engraver
 
-  source file of the GNU LilyPond music typesetter
-
-  (c)  2000 Glen Prideaux <glenprideaux@iname.com>
-*/
-#include <string.h>
-
-#include "lyric-phrasing-engraver.hh"
-#include "note-head.hh"
-#include "translator-group.hh"
-#include "spanner.hh"
-#include "warn.hh"
-
-
-String get_context_id (Translator_group * ancestor, const char * type);
-String trim_suffix (String &id);
+source file of the GNU LilyPond music typesetter
 
+(c) 2003--2004 Han-Wen Nienhuys <hanwen@xs4all.nl>
 
+ */
 
-/*
-  TODO: this code is too hairy, and does things that should be in the
-  backend. Fixme.
-*/
 
-/*
-  TODO:
-
-  shared lyrics should be vertically centered:
+#include "translator-group.hh"
+#include "engraver.hh"
+#include "note-head.hh"
+#include "lyric-extender.hh"
+#include "item.hh"
 
+struct Phrasing_association
+{
+  String name_;
+  Link_array<Grob> lyrics_;
+  Link_array<Grob> heads_;
+  Link_array<Spanner> past_extenders_;
+  Link_array<Spanner> new_extenders_;
   
-
-  > About lyrics, it happens that there are common words for many bars, like
-  > for a refrain say.  When there is an even number of lyrics lines, I do not
-  > know how to force the positioning of the common lyric line in the plain
-  > middle of the others, because this is in between lines.  Not a big matter,
-  > but it would be a bit nicer if this was doable.
-
-*/
-
-/*
-  We find start and end of phrases, and align lyrics of multiple stanzas
-  accordingly.
-
-  Also, lyrics at start of melismata should be left aligned.
-  (is that only lyrics that are followed by `__'?  Because
-  that seems to be the case now -- jcn)
-
-
-  |        |        |     |      |
-  x|       x|       x|    x|     x|
-
-  1:  Start  sentence  melisma      end.
-  2:  x         x         x_____       x
-
-  Only lyrics that are followed by '__' while there's a melisma,
-  are left-aligned, in this case the third x.
-
+  bool melisma_;
   
-  Alignment and melismata
-
-  I've taken [a different] approach:
-  |      |
-  |      |
-  O      O  <-- second note throws a melisma score element
-  \____/
-
-  ^      ^
-  |      |
-  Lyric (None)
-
-  Lyric_phrasing_engraver keeps track of the current and previous notes and
-  lyrics for each voice, and when it catches a melisma, it adjusts the
-  alignment of the lyrics of the previous note. I hope this isn't
-  unnecessarily convoluted.
-*/
+  Phrasing_association()
+  {
+    melisma_ = false;
+  }
+  
+};
 
-Lyric_phrasing_engraver::Lyric_phrasing_engraver ()
+class Lyric_phrasing_engraver : public Engraver
 {
-  voice_alist_ = SCM_EOL;
-  any_notehead_l_ = 0;
-}
-
-Lyric_phrasing_engraver::~Lyric_phrasing_engraver ()
+public:
+  ~Lyric_phrasing_engraver ();
+  TRANSLATOR_DECLARATIONS(Lyric_phrasing_engraver);
+protected:
+  virtual void acknowledge_grob (Grob_info);
+  virtual void process_acknowledged_grobs ();
+  virtual void stop_translation_timestep ();
+
+private:
+  void add_lyric_phrasing (Grob_info);
+  void add_voice_phrasing (Grob_info);
+  void add_lyric_extender (Grob_info);
+  Phrasing_association *get_phrasing_assoc (String nm);
+  String get_voice_name_for_lyric (Translator_group*tr);
+  Link_array<Phrasing_association> assocs_;
+};
+
+Lyric_phrasing_engraver::Lyric_phrasing_engraver()
 {
-  /*
-    No need to delete alist_; that's what Garbage collection is for.
-  */
 }
 
 void
-Lyric_phrasing_engraver::finalize ()
+Lyric_phrasing_engraver::acknowledge_grob (Grob_info i)
 {
-  /*
-    but do need to unprotect alist_, since Engravers are gc'd now.
-  */
+  Grob *h = i.grob_;
 
-  voice_alist_ = SCM_EOL;
+  if (Note_head::has_interface (h))
+    add_voice_phrasing (i);
+  else if (h->internal_has_interface (ly_symbol2scm ("lyric-syllable-interface")))
+    add_lyric_phrasing (i);
+  else if (Lyric_extender::has_interface (h))
+    add_lyric_extender (i);
 }
 
-
-Syllable_group * 
-Lyric_phrasing_engraver::lookup_context_id (const String &context_id)
+Phrasing_association *
+Lyric_phrasing_engraver::get_phrasing_assoc (String nm)
 {
-  SCM key = ly_str02scm (context_id.ch_C ());
-  if (! gh_null_p (voice_alist_))
+  Phrasing_association * a=0;
+  for (int i=0 ; !a && i < assocs_.size (); i++)
     {
-      SCM s = scm_assoc (key, voice_alist_);
-      if (! (gh_boolean_p (s) && !to_boolean (s)))
-       {
-         /* match found */
-         // (key . ((alist_entry . old_entry) . previous_entry))
-         if (to_boolean (ly_cdadr (s)))
-           { // it's an old entry ... make it a new one
-             SCM val = gh_cons (gh_cons (ly_caadr (s), SCM_BOOL_F), ly_cddr (s)); 
-             voice_alist_ = scm_assoc_set_x (voice_alist_, ly_car (s), val);
-             return unsmob_voice_entry (ly_caar (val));
-           }
-         else { // the entry is current ... return it.
-           SCM entry_scm = ly_caadr (s);
-           return unsmob_voice_entry (entry_scm);
-         }
-       }
+      if (assocs_[i]->name_ == nm)
+       a = assocs_[i];
     }
-  // ((alist_entry . old_entry) . previous_entry)
-  SCM val = gh_cons (gh_cons (Syllable_group::make_entry (), SCM_BOOL_F), 
-                    Syllable_group::make_entry ()); 
 
-  voice_alist_ = scm_acons (key, val, voice_alist_);
-  return unsmob_voice_entry (ly_caar (val));
+  if (!a)
+    {
+      a = new Phrasing_association ;
+      a->name_ = nm;
+      assocs_.push (a);
+    }
+  return a;
 }
 
 
-void 
-Lyric_phrasing_engraver::record_notehead (const String &context_id, 
-                                         Grob * notehead)
-{
-  Syllable_group * v = lookup_context_id (context_id);
-  v->set_notehead (notehead);
-  if (!any_notehead_l_)
-    any_notehead_l_ = notehead;
-}
-  
-void 
-Lyric_phrasing_engraver::record_lyric (const String &context_id, Grob * lyric)
-{
-  Syllable_group * v = lookup_context_id (context_id);
-  v->add_lyric (lyric);
-}
 
-void 
-Lyric_phrasing_engraver::record_extender (const String &context_id, Grob * extender)
+String
+Lyric_phrasing_engraver::get_voice_name_for_lyric (Translator_group*tr)
 {
-  SCM key = ly_str02scm (context_id.ch_C ());
-  if (! gh_null_p (voice_alist_))
+  SCM voice = tr->get_property ("associatedVoice");
+  String nm = tr->id_string_;
+  if (gh_string_p (voice))
+    nm = ly_scm2string (voice);
+  else
     {
-      SCM s = scm_assoc (key, voice_alist_);
-      if (! (gh_boolean_p (s) && !to_boolean (s)))
-       {
-         /* match found */
-         // (key . ((alist_entry . old_entry) . previous_entry))
-         SCM previous_scm = ly_cddr (s);
-         if (previous_scm != SCM_EOL)
-           {
-             Syllable_group * v = unsmob_voice_entry (previous_scm);
-             v->add_extender (extender);
-           }
-       }
+      int idx = nm.index_last ('-');
+      if (idx >= 0)
+       nm = nm.left_string (idx);
     }
-}
 
-void 
-Lyric_phrasing_engraver::record_melisma (const String &context_id)
-{
-  Syllable_group * v = lookup_context_id (context_id);
-  v->set_melisma ();
+  return nm;
 }
-  
-void
-Lyric_phrasing_engraver::acknowledge_grob (Grob_info i)
-{
-  SCM p = get_property ("automaticPhrasing");
-  if (!to_boolean (p))
-    return;
 
 
-  Grob *h = i.grob_l_;
+void
+Lyric_phrasing_engraver::add_lyric_extender (Grob_info inf)
+{
+  Translator_group * tr = inf.origin_trans_->daddy_trans_;
+  while (tr && !tr->is_alias (ly_symbol2scm ("LyricsVoice")))
+    tr = tr->daddy_trans_;
 
-  if (Note_head::has_interface (h))
-    {
-      /* caught a note head ... do something with it */
+  if (!tr)
+    return;
 
-      /* what's its Voice context name? */
-      String voice_context_id = get_context_id (i.origin_trans_l_->daddy_trans_l_, "Voice");
-      record_notehead (voice_context_id, h);
+  
+  Phrasing_association *a =  get_phrasing_assoc (get_voice_name_for_lyric (tr));
+  a->new_extenders_.push (dynamic_cast<Spanner*> (inf.grob_));  
+}
 
-      /* is it in a melisma ? */
-      if (to_boolean (i.origin_trans_l_->get_property ("melismaEngraverBusy")))
-       {
-         record_melisma (voice_context_id);
-       }
-      return;
-    }
 
-  /* now try for a lyric */
-  if (h->internal_has_interface (ly_symbol2scm ("lyric-syllable-interface")))
-    {
+void
+Lyric_phrasing_engraver::add_voice_phrasing (Grob_info inf)
+{
+  Translator_group * tr = inf.origin_trans_->daddy_trans_;
+  while (tr && !tr->is_alias (ly_symbol2scm ("Voice")))
+    tr = tr->daddy_trans_;
 
-      /* what's its LyricsVoice context name? */
-      String voice_context_id;
-      SCM voice_context_scm = i.origin_trans_l_->get_property ("associatedVoice");
-      if (gh_string_p (voice_context_scm))
-       {
-         voice_context_id = ly_scm2string (voice_context_scm);
-       }
-      else {
-       voice_context_id = get_context_id (i.origin_trans_l_->daddy_trans_l_, "LyricsVoice");
-       voice_context_id = trim_suffix (voice_context_id);
-      }
-      record_lyric (voice_context_id, h);
-      return;
-    }
+  if (!tr)
+    return;
 
-  /* Catch any extender items and then if we have a melisma, 
-     set the RIGHT item of the extender spanner to the melismatic note in 
-     the corresponding context (if any).
-     This has the effect of finishing the extender under the last note
-     of the melisma, instead of extending it to the next lyric.
-     
-     Problem: the extender request is thrown at the same moment as the next lyric,
-     by which time we have already passed the last note of the melisma.
-     However, the Lyric_phrasing_engraver remembers the last note, so just 
-     attach it to that, provided it was melismatic. If it was not melismatic, 
-     then ignore it and let the Extender_engraver take care of it (i.e. finish at next
-     lyric).
-  */
-  if (h->internal_has_interface (ly_symbol2scm ("lyric-extender-interface")))
-    {
-      String voice_context_id = get_context_id (i.origin_trans_l_->daddy_trans_l_, "LyricsVoice");
-      record_extender (trim_suffix (voice_context_id), h);
-      return;
-    }
+  Phrasing_association *a =  get_phrasing_assoc (tr->id_string_);
+  a->heads_.push (inf.grob_);
+  a->melisma_ = melisma_busy (inf.origin_trans_);
 }
 
-String 
-get_context_id (Translator_group * ancestor, const char *type)
+void
+Lyric_phrasing_engraver::add_lyric_phrasing (Grob_info inf)
 {
-  while (ancestor != 0 && ancestor->type_str_ != type)
-    {
-      ancestor = ancestor->daddy_trans_l_;
-    }
+  Translator_group * tr = inf.origin_trans_->daddy_trans_;
+  while (tr && !tr->is_alias (ly_symbol2scm ("LyricsVoice")))
+    tr = tr->daddy_trans_;
 
-  if (ancestor != 0)
-    {
-      return ancestor->id_str_;
-    }
+  if (!tr)
+    return;
 
-  return "";
+
+  Phrasing_association *a =  get_phrasing_assoc (get_voice_name_for_lyric (tr));
+  a->lyrics_.push (inf.grob_);
+  a->past_extenders_.clear ();
 }
 
-String 
-trim_suffix (String &id)
+void
+Lyric_phrasing_engraver::stop_translation_timestep ()
 {
-  int index = id.index_i ('-');
-  if (index >= 0)
+  for (int i = assocs_.size ();  i--; )
     {
-      return id.left_str (index);
+      assocs_[i]->heads_.clear ();
+      assocs_[i]->lyrics_.clear ();
+      assocs_[i]->past_extenders_.concat (assocs_[i]->new_extenders_) ;
+      assocs_[i]->new_extenders_.clear ();
     }
-  return id;
 }
 
-
 void
-Lyric_phrasing_engraver::create_grobs () 
+Lyric_phrasing_engraver::process_acknowledged_grobs ()
 {
-  SCM p = get_property ("automaticPhrasing");
-  if (!to_boolean (p))
-    return;
-
-  
-  /* iterate through entries in voice_alist_
-     for each, call set_lyric_align (alignment). Issue a warning if this returns false.
-  */
-  String punc;
-  SCM sp = get_property ("phrasingPunctuation");
-  punc = gh_string_p (sp) ? ly_scm2string (sp) : ".,;:?!\""; 
-  
-  for (SCM v=voice_alist_; gh_pair_p (v); v = ly_cdr (v))
+  for (int i = 0; i < assocs_.size ();  i++)
     {
-      SCM v_entry = ly_cdar (v);
-      // ((current . oldflag) . previous)
-      if (!to_boolean (ly_cdar (v_entry)))
-       { // not an old entry left over from a prior note ...
-         Syllable_group *entry = unsmob_voice_entry (ly_caar (v_entry));
-
-         /*
-           TODO: give context for warning.
-         */
-         if (! entry->set_lyric_align (punc.ch_C (), any_notehead_l_))
-           warning (_ ("lyrics found without any matching notehead"));
-
-         // is this note melismatic? If so adjust alignment of previous one.
-         if (entry->get_melisma ())
+      Phrasing_association * a = assocs_[i];
+      if (! (a->heads_.size()  && (a->lyrics_.size () || a->past_extenders_.size ())))
+       continue;
+
+      Grob *h = a->heads_[0];  
+      Direction alignment = CENTER;
+      if (a->melisma_)
+       alignment = LEFT;
+      
+      for (int j = 0; j < a->lyrics_.size (); j++)
+       {
+         Grob *l = a->lyrics_[j];
+         if (!l->get_parent (X_AXIS))
            {
-             if (entry->lyric_count ())
-               warning (_ ("Huh? Melismatic note found to have associated lyrics."));
-             SCM previous_scm = ly_cdr (v_entry);
-             if (previous_scm != SCM_EOL)
-               {
-                 Syllable_group *previous = unsmob_voice_entry (previous_scm);
-                 if (previous->lyric_count ())
-                   previous->adjust_melisma_align ();
-               }
+             l->set_parent (h, X_AXIS);
+             if (alignment)
+               l->set_grob_property ("self-alignment-X", gh_int2scm (alignment));
            }
        }
+
+      for (int j = a->past_extenders_.size(); j--;)
+       a->past_extenders_[j]->set_bound (RIGHT, dynamic_cast<Item*> (h));
     }
 }
 
-
-void
-Lyric_phrasing_engraver::stop_translation_timestep ()
+Lyric_phrasing_engraver::~Lyric_phrasing_engraver ()
 {
-  for (SCM v=voice_alist_; gh_pair_p (v); v = ly_cdr (v))
-    {
-      SCM entry_scm = ly_cdar (v);
-      // ((alist_entry . entry_is_old) . previous_entry)
-      Syllable_group * entry = unsmob_voice_entry (ly_caar (entry_scm));
-
-      // set previous_entry, set entry_is_old, and resave it to alist_
-      // but only change if this current was not old.
-      if (! to_boolean (ly_cdar (entry_scm)))
-       { 
-         Syllable_group * previous_entry = unsmob_voice_entry (ly_cdr (entry_scm));
-         previous_entry->copy (entry);
-         entry_scm = gh_cons (gh_cons (ly_caar (entry_scm), SCM_BOOL_T), ly_cdr (entry_scm));
-         voice_alist_ = scm_assoc_set_x (voice_alist_, ly_caar (v), entry_scm);
-       }
-      entry->next_lyric ();
-    }
-  any_notehead_l_ = 0;
+  for (int i =assocs_.size(); i--;)
+    delete assocs_[i];
 }
 
-
-
 ENTER_DESCRIPTION(Lyric_phrasing_engraver,
-                 /* descr */       "",
-                 /* creats*/       "",
-                 /* acks  */       "lyric-syllable-interface note-head-interface lyric-extender-interface",
-                 /* reads */       "automaticPhrasing melismaEngraverBusy associatedVoice phrasingPunctuation",
-                 /* write */       "");
+                 "This engraver combines note heads and lyrics for alignment. ",
+                 "",
+                 "",
+                 "lyric-syllable-interface note-head-interface lyric-extender-interface",
+                 "automaticPhrasing associatedVoice",
+                 "");
+