2 lyric-phrasing-engraver.cc -- implement Lyric_phrasing_engraver
4 source file of the GNU LilyPond music typesetter
6 (c) 2000 Glen Prideaux <glenprideaux@iname.com>
10 #include "lyric-phrasing-engraver.hh"
11 #include "note-head.hh"
12 #include "translator-group.hh"
13 #include "side-position-interface.hh"
15 String get_context_id(Translator_group * ancestor, const char * type);
16 String trim_suffix(String &id);
18 ADD_THIS_TRANSLATOR (Lyric_phrasing_engraver);
21 Lyric_phrasing_engraver::Lyric_phrasing_engraver()
23 voice_alist_ = SCM_EOL;
26 Lyric_phrasing_engraver::~Lyric_phrasing_engraver()
29 No need to delete alist_; that's what Garbage collection is for.
34 Lyric_phrasing_engraver::lookup_context_id(const String &context_id)
36 SCM key = ly_str02scm(context_id.ch_C());
37 if( ! gh_null_p(voice_alist_) ) {
38 SCM s = scm_assoc(key, voice_alist_);
39 if(! (gh_boolean_p(s) && !to_boolean(s))) {
41 return unsmob_voice_entry(gh_cdr(s));
44 SCM val = Voice_alist_entry::make_entry ();
45 voice_alist_ = scm_acons(key, val, voice_alist_);
46 return unsmob_voice_entry (val);
51 Lyric_phrasing_engraver::record_notehead(const String &context_id, Score_element * notehead)
53 Voice_alist_entry * v = lookup_context_id(context_id);
54 v->set_notehead(notehead);
56 // scm_assoc_set_x(voice_alist_, ly_str02scm(context_id.ch_C()), smobify(v));
60 Lyric_phrasing_engraver::record_lyric(const String &context_id, Score_element * lyric)
62 Voice_alist_entry * v = lookup_context_id(context_id);
65 // scm_assoc_set_x(voice_alist_, ly_str02scm(context_id.ch_C()), smobify(v));
72 Lyric_phrasing_engraver::acknowledge_element(Score_element_info i)
74 SCM p = get_property("automaticPhrasing");
79 Score_element *h = i.elem_l_;
81 if (Note_head::has_interface(h)) {
82 /* caught a note head ... do something with it */
83 /* ... but not if it's a grace note ... */
84 bool grace= to_boolean (i.elem_l_->get_elt_property ("grace"));
85 SCM wg = get_property ("weAreGraceContext");
86 bool wgb = to_boolean (wg);
90 /* what's its Voice context name? */
91 String voice_context_id = get_context_id(i.origin_trans_l_->daddy_trans_l_, "Voice");
92 record_notehead(voice_context_id, h);
95 /* now try for a lyric */
96 if (h->has_interface (ly_symbol2scm ("lyric-syllable-interface"))) {
98 /* what's its LyricVoice context name? */
99 String lyric_voice_context_id =
100 get_context_id(i.origin_trans_l_->daddy_trans_l_, "LyricVoice");
101 record_lyric(trim_suffix(lyric_voice_context_id), h);
108 get_context_id(Translator_group * ancestor, const char *type)
110 while(ancestor != 0 && ancestor->type_str_ != type) {
111 ancestor = ancestor->daddy_trans_l_;
115 return ancestor->id_str_;
122 trim_suffix(String &id)
124 int index = id.index_i('-');
126 return id.left_str(index);
132 void Lyric_phrasing_engraver::process_acknowledged ()
134 /* iterate through entries in voice_alist_
135 for each, call set_lyric_align(alignment). Issue a warning if this returns false.
137 Voice_alist_entry *entry;
139 if (punc.empty_b()) {
140 SCM sp = get_property("phrasingPunctuation");
141 punc = gh_string_p(sp) ? ly_scm2string(sp) : ".,;?!";
144 for(unsigned v=0; v < gh_length(voice_alist_); v++) {
145 entry = unsmob_voice_entry(gh_cdr(gh_list_ref(voice_alist_, gh_int2scm(v))));
146 if(! entry->set_lyric_align(punc.ch_C()))
147 warning (_ ("lyrics found without matching notehead ... aligning on self"));
153 Lyric_phrasing_engraver::do_pre_move_processing ()
155 Voice_alist_entry * entry;
156 for(unsigned v=0; v < gh_length(voice_alist_); v++) {
157 entry = unsmob_voice_entry(gh_cdr(gh_list_ref(voice_alist_, gh_int2scm(v))));
164 /*=========================================================================================*/
166 /** Voice_alist_entry is a class to be smobbed and entered as data in the association list
167 member of the Lyric_phrasing_engraver class.
170 Voice_alist_entry::Voice_alist_entry()
172 first_in_phrase_b_=true;
180 Voice_alist_entry::clear()
189 Voice_alist_entry::set_first_in_phrase(bool f)
191 first_in_phrase_b_ = f;
195 Voice_alist_entry::set_notehead(Score_element * notehead)
198 /* there should only be a single notehead, so silently ignore any extras */
199 notehead_l_=notehead;
203 Voice_alist_entry::add_lyric(Score_element * lyric)
205 int this_lyric = lyric_list_.size();
206 lyric_list_.push(lyric);
207 /* record longest and shortest lyrics */
208 if(longest_lyric_>-1) {
209 Real this_length = (lyric->extent(X_AXIS)).length();
210 if(this_length > (lyric_list_[longest_lyric_]->extent(X_AXIS)).length())
211 longest_lyric_ = this_lyric;
212 if(this_length < (lyric_list_[shortest_lyric_]->extent(X_AXIS)).length())
213 shortest_lyric_ = this_lyric;
216 longest_lyric_ = shortest_lyric_ = this_lyric;
220 Voice_alist_entry::set_lyric_align(const char *punc)
222 if(lyric_list_.size()<2) {
223 /* Only for multi-stanza songs ... if we've only a single lyric (or none at all) we
230 Score_element * lyric;
231 alignment_i_ = appropriate_alignment(punc);
233 for(int l = 0; l < lyric_list_.size(); l++) {
234 /** set the x alignment of each lyric
236 lyric = lyric_list_[l];
237 lyric->set_elt_property("self-alignment-X", gh_int2scm(alignment_i_));
239 // centre on notehead
242 /* set the parent of each lyric to the notehead,
243 set the offset callback of each lyric to centered_on_parent,
245 lyric->set_parent(notehead_l_, X_AXIS);
246 lyric->add_offset_callback (Side_position::centered_on_parent, X_AXIS);
247 /* reference is on the right of the notehead; move it left half way */
248 lyric->translate_axis (-(notehead_l_->extent(X_AXIS)).center(), X_AXIS);
251 /* No matching notehead: just align to the first lyric, and
252 issue a warning about lyric without matching notehead
255 lyric->set_parent(lyric_list_[0], X_AXIS);
256 lyric->add_offset_callback (Side_position::centered_on_parent, X_AXIS);
259 lyric->add_offset_callback (Side_position::aligned_on_self, X_AXIS);
262 if(alignment_i_ != CENTER) {
263 // right or left align ...
264 /* If length of longest lyric < 2 * length of shortest lyric,
265 - centre longest lyric on notehead
267 - move so shortest lyric just reaches notehead centre
269 // FIXME: do we really know the lyric extent here? Some font sizing comes later?
271 if((lyric_list_[longest_lyric_]->extent(X_AXIS)).length() <
272 (lyric_list_[shortest_lyric_]->extent(X_AXIS)).length() * 2 )
273 translate = alignment_i_*(lyric_list_[longest_lyric_]->extent(X_AXIS)).length()/2;
275 translate = alignment_i_*(lyric_list_[shortest_lyric_]->extent(X_AXIS)).length();
276 lyric->translate_axis (translate, X_AXIS);
280 return (notehead_l_ != 0);
283 /** determine what alignment we want.
284 Rules: if first_in_phrase_b_ is set, then alignment is LEFT.
285 otherwise if each syllable ends in punctuation, then alignment is RIGHT
286 otherwise alignment is centre.
289 Voice_alist_entry::appropriate_alignment(const char *punc)
291 if(first_in_phrase_b_)
294 Score_element * lyric;
295 bool end_phrase = true;
296 /* use a property to determine what constitutes punctuation */
298 for(int l = 0; l < lyric_list_.size() && end_phrase; l++) {
299 lyric = lyric_list_[l];
300 SCM lyric_scm = lyric->get_elt_property("text");
301 String lyric_str = gh_string_p(lyric_scm)?ly_scm2string(lyric_scm):"";
303 if(lyric_str.length_i()>1) {
304 lastchar = lyric_str[lyric_str.length_i()-2];
305 /* We look at the second last character, because lily always appends a space. */
306 /* If it doesn't end in punctuation then it ain't an end of phrase */
307 if(! strchr(punc, lastchar)) {
308 /* Special case: trailing space. Here examine the previous character and reverse the
309 sense of the test (i.e. trailing space makes a break without punctuation, or
310 suppresses a break with punctuation).
311 This behaviour can be suppressed by including a space in the
312 phrasingPunctuation property, in which case trailing space always means
313 the same as punctuation.
315 FIXME: The extra space throws alignment out a bit.
317 if(lastchar == ' ') {
318 if(lyric_str.length_i()>2) {
319 lastchar = lyric_str[lyric_str.length_i()-3];
320 if(strchr(punc, lastchar))
336 Voice_alist_entry::is_empty()
338 return lyric_list_.size()==0;
342 Voice_alist_entry::next_lyric()
344 first_in_phrase_b_ = (alignment_i_ == RIGHT);
350 #include "ly-smobs.icc"
353 Voice_alist_entry::mark_smob (SCM)
359 Voice_alist_entry::print_smob (SCM, SCM port, scm_print_state * )
361 scm_puts ("#<Voice_alist_entry>", port);
365 IMPLEMENT_UNSMOB(Voice_alist_entry, voice_entry);
366 IMPLEMENT_SIMPLE_SMOBS(Voice_alist_entry);
367 IMPLEMENT_DEFAULT_EQUAL_P(Voice_alist_entry);
370 Voice_alist_entry::make_entry ()
372 Voice_alist_entry *vi = new Voice_alist_entry;
373 return vi->smobbed_self ();