]> git.donarmstrong.com Git - lilypond.git/commitdiff
partial: 1.3.74.gp
authorGlen Prideaux <glenprideaux@iname.com>
Wed, 19 Jul 2000 17:01:09 +0000 (19:01 +0200)
committerGlen Prideaux <glenprideaux@iname.com>
Wed, 19 Jul 2000 17:01:09 +0000 (19:01 +0200)
======
* Removed \interscoreline after the last line, prevents some
empty pages. (patch by Mats Bengtsson)

* Smobified Music, junked associated identifiers. Use Scheme list to
store Music_sequence.  Removed const declarations from
Music_iterators.  Added a mutable and immutable property list to
Music.  Use music property for all Scheme values in Music.  Created
and fixed a load of Garbage Collection bugs.

* Changed number font to allow automatic .mf creation.

* Output place of definition as \special{src:LOCATION}. See FAQ for
explanation how to use.

* Schemified Local_key_item and fixed minor bug with accidentals.

* new Japanese .po file.

* Bugfix for broken tie direction

* Bugfix for bar at beginning of line break

1.3

lily/include/lyric-phrasing-engraver.hh [new file with mode: 0644]
lily/lyric-phrasing-engraver.cc [new file with mode: 0644]

diff --git a/lily/include/lyric-phrasing-engraver.hh b/lily/include/lyric-phrasing-engraver.hh
new file mode 100644 (file)
index 0000000..45b0769
--- /dev/null
@@ -0,0 +1,114 @@
+/*
+  lyric-phrasing-engraver.hh -- declare Lyric_phrasing_engraver
+
+  source file of the GNU LilyPond music typesetter
+
+  (c)  2000 Glen Prideaux <glenprideaux@iname.com>
+*/
+
+
+#ifndef LYRIC_PHRASING_ENGRAVER_HH
+#define LYRIC_PHRASING_ENGRAVER_HH
+
+#include "lily-proto.hh"
+#include "engraver.hh"
+#include "item.hh"
+#include "smobs.hh"
+
+class Voice_alist_entry;
+
+
+/**
+   Align lyrics with noteheads, left aligning beginning of phrases,
+   right aligning end of phrases, centering others under their notes.
+ */
+
+
+/* 
+* Build an engraver that catches noteheads and lyrics.
+
+(It needs to be in a context above Staff and Lyrics, eg. in Score
+context.)
+
+* Determine which heads belong to which lyrics
+
+(eg. by looking at the names of their originating contexts, or maybe
+some \properties)
+
+* Attach the lyrics to the appropriate heads
+
+(by doing lyric->set_parent (head, X_AXIS), that will fix the current
+noteheadwidth guessing kludge)
+
+* Check if the lyric syllables end or start a phrase.
+
+(eg. check if the syllable ends with punctuation, and remember that
+fact for the next one.)
+
+* Adjust their alignment accordingly. 
+
+(eg. by doing lyric->add_offset_callback(centered_on_parent,X_AXIS)
+and setting self-alignment-X)
+
+* Add a property to switch on/off the engraver (for multi stanza
+  vs. single stanza music)
+
+Maybe this engraver could also take care of correct lyric alignment
+for melismas as well.
+
+
+ */
+
+
+class Lyric_phrasing_engraver : public Engraver 
+{
+protected:
+  virtual void acknowledge_element(Score_element_info);
+  virtual void process_acknowledged ();
+  virtual void do_pre_move_processing();
+  
+private:
+  void record_notehead(const String &context_id, Score_element * notehead);
+  void record_lyric(const String &context_id, Score_element * lyric);
+  Voice_alist_entry * lookup_context_id(const String &context_id);
+
+public:
+  Lyric_phrasing_engraver ();
+  ~Lyric_phrasing_engraver ();
+  VIRTUAL_COPY_CONS (Translator);
+
+private:
+  /** association list of Voice_alist_entry smobs
+  */
+  Protected_scm voice_alist_;
+};
+
+
+class Voice_alist_entry
+{
+  bool first_in_phrase_b_;
+  Score_element * notehead_l_;
+  Link_array<Score_element> lyric_list_;
+  int longest_lyric_;
+  int shortest_lyric_;
+  int alignment_i_;
+
+public:
+  static SCM make_entry();
+  void set_first_in_phrase(bool f);
+  void set_notehead(Score_element * notehead);
+  void add_lyric(Score_element * lyric);
+  void clear();
+  bool is_empty();
+  bool set_lyric_align(const char *punc);
+  int appropriate_alignment(const char *punc);
+  void next_lyric();
+private:
+  Voice_alist_entry();
+  DECLARE_SIMPLE_SMOBS(Voice_alist_entry,);
+} ;
+
+Voice_alist_entry * unsmob_voice_entry (SCM);
+
+
+#endif // LYRIC_PHRASING_ENGRAVER_HH
diff --git a/lily/lyric-phrasing-engraver.cc b/lily/lyric-phrasing-engraver.cc
new file mode 100644 (file)
index 0000000..c17755f
--- /dev/null
@@ -0,0 +1,374 @@
+/*
+  lyric-phrasing-engraver.cc -- implement Lyric_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 "side-position-interface.hh"
+
+String get_context_id(Translator_group * ancestor, const char * type);
+String trim_suffix(String &id);
+
+ADD_THIS_TRANSLATOR (Lyric_phrasing_engraver);
+
+
+Lyric_phrasing_engraver::Lyric_phrasing_engraver()
+{
+  voice_alist_ = SCM_EOL;
+}
+
+Lyric_phrasing_engraver::~Lyric_phrasing_engraver()
+{
+  /*
+    No need to delete alist_; that's what Garbage collection is for.
+   */
+}
+
+Voice_alist_entry * 
+Lyric_phrasing_engraver::lookup_context_id(const String &context_id)
+{
+  SCM key = ly_str02scm(context_id.ch_C());
+  if( ! gh_null_p(voice_alist_) ) {
+    SCM s = scm_assoc(key, voice_alist_);
+    if(! (gh_boolean_p(s) && !to_boolean(s))) {
+      /* match found */
+      return unsmob_voice_entry(gh_cdr(s));
+    }
+  }
+  SCM val = Voice_alist_entry::make_entry (); 
+  voice_alist_ = scm_acons(key, val, voice_alist_);
+  return unsmob_voice_entry (val);
+}
+
+
+void 
+Lyric_phrasing_engraver::record_notehead(const String &context_id, Score_element * notehead)
+{
+  Voice_alist_entry * v = lookup_context_id(context_id);
+  v->set_notehead(notehead);
+  //  voice_alist_ = 
+  //    scm_assoc_set_x(voice_alist_, ly_str02scm(context_id.ch_C()), smobify(v));
+}
+  
+void 
+Lyric_phrasing_engraver::record_lyric(const String &context_id, Score_element * lyric)
+{
+  Voice_alist_entry * v = lookup_context_id(context_id);
+  v->add_lyric(lyric);
+  //  voice_alist_ = 
+  //  scm_assoc_set_x(voice_alist_, ly_str02scm(context_id.ch_C()), smobify(v));
+}
+
+
+
+
+void
+Lyric_phrasing_engraver::acknowledge_element(Score_element_info i)
+{
+  SCM p = get_property("automaticPhrasing");
+  if(!to_boolean(p))
+    return;
+
+
+  Score_element *h = i.elem_l_;
+
+  if (Note_head::has_interface(h)) {
+    /* caught a note head ... do something with it */
+    /* ... but not if it's a grace note ... */
+    bool grace= to_boolean (i.elem_l_->get_elt_property ("grace"));
+    SCM wg = get_property ("weAreGraceContext");
+    bool wgb = to_boolean (wg);
+    if (grace != wgb)
+      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);
+    return;
+  }
+  /* now try for a lyric */
+  if (h->has_interface (ly_symbol2scm ("lyric-syllable-interface"))) {
+
+    /* what's its LyricVoice context name? */
+    String lyric_voice_context_id = 
+      get_context_id(i.origin_trans_l_->daddy_trans_l_, "LyricVoice");
+    record_lyric(trim_suffix(lyric_voice_context_id), h);
+    return;
+  }
+}
+
+
+String 
+get_context_id(Translator_group * ancestor, const char *type)
+{
+  while(ancestor != 0 && ancestor->type_str_ != type) {
+    ancestor = ancestor->daddy_trans_l_;
+  }
+
+  if(ancestor != 0) {
+    return ancestor->id_str_;
+  }
+
+  return "";
+}
+
+String 
+trim_suffix(String &id)
+{
+  int index = id.index_i('-');
+  if(index >= 0) {
+    return id.left_str(index);
+  }
+  return id;
+}
+
+
+void Lyric_phrasing_engraver::process_acknowledged () 
+{
+  /* iterate through entries in voice_alist_
+     for each, call set_lyric_align(alignment). Issue a warning if this returns false.
+  */
+  Voice_alist_entry *entry;
+  String punc;
+  if (punc.empty_b()) {
+    SCM sp = get_property("phrasingPunctuation");
+    punc = gh_string_p(sp) ? ly_scm2string(sp) : ".,;?!"; 
+  }
+
+  for(unsigned v=0; v < gh_length(voice_alist_); v++) {
+    entry = unsmob_voice_entry(gh_cdr(gh_list_ref(voice_alist_, gh_int2scm(v))));
+    if(! entry->set_lyric_align(punc.ch_C()))
+      warning (_ ("lyrics found without matching notehead ... aligning on self"));
+  }
+}
+
+
+void
+Lyric_phrasing_engraver::do_pre_move_processing ()
+{
+  Voice_alist_entry * entry;
+  for(unsigned v=0; v < gh_length(voice_alist_); v++) {
+    entry = unsmob_voice_entry(gh_cdr(gh_list_ref(voice_alist_, gh_int2scm(v))));
+    entry->next_lyric();
+  }
+}
+
+
+
+/*=========================================================================================*/
+
+/** Voice_alist_entry is a class to be smobbed and entered as data in the association list
+    member of the Lyric_phrasing_engraver class.
+*/
+
+Voice_alist_entry::Voice_alist_entry()
+{
+  first_in_phrase_b_=true;
+  clear();
+}
+
+
+
+
+void 
+Voice_alist_entry::clear()
+{
+  notehead_l_=0;
+  lyric_list_.clear();
+  longest_lyric_=-1;
+  shortest_lyric_=-1;
+}
+  
+void 
+Voice_alist_entry::set_first_in_phrase(bool f) 
+{ 
+  first_in_phrase_b_ = f; 
+}
+
+void 
+Voice_alist_entry::set_notehead(Score_element * notehead)
+{
+  if(!notehead_l_) 
+    /* there should only be a single notehead, so silently ignore any extras */
+    notehead_l_=notehead;
+}
+
+void 
+Voice_alist_entry::add_lyric(Score_element * lyric)
+{
+  int this_lyric = lyric_list_.size();
+  lyric_list_.push(lyric);
+  /* record longest and shortest lyrics */
+  if(longest_lyric_>-1) {
+    Real this_length = (lyric->extent(X_AXIS)).length();
+    if(this_length > (lyric_list_[longest_lyric_]->extent(X_AXIS)).length())
+      longest_lyric_ = this_lyric;
+    if(this_length < (lyric_list_[shortest_lyric_]->extent(X_AXIS)).length())
+      shortest_lyric_ = this_lyric;
+  }
+  else
+    longest_lyric_ = shortest_lyric_ = this_lyric;
+}
+  
+bool 
+Voice_alist_entry::set_lyric_align(const char *punc)
+{
+  if(lyric_list_.size()<2) {
+    /* Only for multi-stanza songs ... if we've only a single lyric (or none at all) we
+       do nothing.
+    */
+    clear();
+    return true;
+  }
+
+  Score_element * lyric;
+  alignment_i_ = appropriate_alignment(punc);
+
+  for(int l = 0; l < lyric_list_.size(); l++) {
+    /** set the x alignment of each lyric
+     */
+    lyric = lyric_list_[l];
+    lyric->set_elt_property("self-alignment-X", gh_int2scm(alignment_i_));
+
+    // centre on notehead 
+
+    if(notehead_l_) {
+      /* set the parent of each lyric to the notehead,
+        set the offset callback of each lyric to centered_on_parent,
+      */
+      lyric->set_parent(notehead_l_, X_AXIS);
+      lyric->add_offset_callback (Side_position::centered_on_parent, X_AXIS);
+      /* reference is on the right of the notehead; move it left half way */
+      lyric->translate_axis (-(notehead_l_->extent(X_AXIS)).center(), X_AXIS);
+    }
+    else {
+      /* No matching notehead: just align to the first lyric, and
+         issue a warning about lyric without matching notehead
+      */
+      if(l) {
+       lyric->set_parent(lyric_list_[0], X_AXIS);
+       lyric->add_offset_callback (Side_position::centered_on_parent, X_AXIS);
+      }
+      else
+       lyric->add_offset_callback (Side_position::aligned_on_self, X_AXIS);
+    }
+
+    if(alignment_i_ != CENTER) {
+      // right or left align ... 
+      /* If length of longest lyric < 2 * length of shortest lyric,
+          - centre longest lyric on notehead
+        Otherwise
+          - move so shortest lyric just reaches notehead centre
+      */
+      // FIXME: do we really know the lyric extent here? Some font sizing comes later?
+      Real translate;
+      if((lyric_list_[longest_lyric_]->extent(X_AXIS)).length() <
+        (lyric_list_[shortest_lyric_]->extent(X_AXIS)).length() * 2 )
+       translate = alignment_i_*(lyric_list_[longest_lyric_]->extent(X_AXIS)).length()/2;
+      else
+       translate = alignment_i_*(lyric_list_[shortest_lyric_]->extent(X_AXIS)).length();
+      lyric->translate_axis (translate, X_AXIS);         
+      }
+    }
+
+  return (notehead_l_ != 0);
+}
+
+/** determine what alignment we want.
+    Rules: if first_in_phrase_b_ is set, then alignment is LEFT.
+           otherwise if each syllable ends in punctuation, then alignment is RIGHT
+          otherwise alignment is centre.
+*/
+int 
+Voice_alist_entry::appropriate_alignment(const char *punc)
+{
+  if(first_in_phrase_b_)
+    return LEFT;
+
+  Score_element * lyric;
+  bool end_phrase = true;
+  /* use a property to determine what constitutes punctuation */
+
+  for(int l = 0; l < lyric_list_.size() && end_phrase; l++) {
+    lyric = lyric_list_[l];
+    SCM lyric_scm = lyric->get_elt_property("text");
+    String lyric_str = gh_string_p(lyric_scm)?ly_scm2string(lyric_scm):"";
+    char lastchar;
+    if(lyric_str.length_i()>1) {
+      lastchar = lyric_str[lyric_str.length_i()-2];
+      /* We look at the second last character, because lily always appends a space. */
+      /* If it doesn't end in punctuation then it ain't an end of phrase */
+      if(! strchr(punc, lastchar)) {
+       /* Special case: trailing space. Here examine the previous character and reverse the
+          sense of the test (i.e. trailing space makes a break without punctuation, or 
+          suppresses a break with punctuation).
+          This behaviour can be suppressed by including a space in the 
+          phrasingPunctuation property, in which case trailing space always means 
+          the same as punctuation.
+
+          FIXME: The extra space throws alignment out a bit.
+       */
+       if(lastchar == ' ') {
+         if(lyric_str.length_i()>2) {
+           lastchar = lyric_str[lyric_str.length_i()-3];
+           if(strchr(punc, lastchar))
+             end_phrase=false;
+         }
+       }
+       else
+         end_phrase=false;
+      }
+    }
+  }
+  if(end_phrase)
+    return RIGHT;
+
+  return CENTER;
+}
+
+bool
+Voice_alist_entry::is_empty()
+{
+  return lyric_list_.size()==0;
+}
+
+void
+Voice_alist_entry::next_lyric()
+{
+  first_in_phrase_b_ = (alignment_i_ == RIGHT);
+  clear();
+}
+
+/* SMOB */
+
+#include "ly-smobs.icc"
+
+SCM
+Voice_alist_entry::mark_smob (SCM)
+{
+  return SCM_EOL;
+}
+
+int
+Voice_alist_entry::print_smob (SCM, SCM port, scm_print_state * )
+{
+  scm_puts ("#<Voice_alist_entry>", port);
+  return 1;
+}
+
+IMPLEMENT_UNSMOB(Voice_alist_entry, voice_entry);
+IMPLEMENT_SIMPLE_SMOBS(Voice_alist_entry);
+IMPLEMENT_DEFAULT_EQUAL_P(Voice_alist_entry);
+
+SCM
+Voice_alist_entry::make_entry ()
+{
+  Voice_alist_entry *vi = new Voice_alist_entry;
+  return vi->smobbed_self ();
+}