From ea7145d74aeb645b05903f8b4a33d0aed43a698b Mon Sep 17 00:00:00 2001 From: Glen Prideaux Date: Wed, 19 Jul 2000 19:01:09 +0200 Subject: [PATCH] partial: 1.3.74.gp ====== * 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 | 114 ++++++++ lily/lyric-phrasing-engraver.cc | 374 ++++++++++++++++++++++++ 2 files changed, 488 insertions(+) create mode 100644 lily/include/lyric-phrasing-engraver.hh create mode 100644 lily/lyric-phrasing-engraver.cc diff --git a/lily/include/lyric-phrasing-engraver.hh b/lily/include/lyric-phrasing-engraver.hh new file mode 100644 index 0000000000..45b0769de2 --- /dev/null +++ b/lily/include/lyric-phrasing-engraver.hh @@ -0,0 +1,114 @@ +/* + lyric-phrasing-engraver.hh -- declare Lyric_phrasing_engraver + + source file of the GNU LilyPond music typesetter + + (c) 2000 Glen Prideaux +*/ + + +#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 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 index 0000000000..c17755f745 --- /dev/null +++ b/lily/lyric-phrasing-engraver.cc @@ -0,0 +1,374 @@ +/* + lyric-phrasing-engraver.cc -- implement Lyric_phrasing_engraver + + source file of the GNU LilyPond music typesetter + + (c) 2000 Glen Prideaux +*/ +#include + +#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 ("#", 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 (); +} -- 2.39.2