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"
17 String get_context_id (Translator_group * ancestor, const char * type);
18 String trim_suffix (String &id);
23 TODO: this code is too hairy, and does things that should be in the
30 shared lyrics should be vertically centered:
34 > About lyrics, it happens that there are common words for many bars, like
35 > for a refrain say. When there is an even number of lyrics lines, I do not
36 > know how to force the positioning of the common lyric line in the plain
37 > middle of the others, because this is in between lines. Not a big matter,
38 > but it would be a bit nicer if this was doable.
43 We find start and end of phrases, and align lyrics of multiple stanzas
46 Also, lyrics at start of melismata should be left aligned.
47 (is that only lyrics that are followed by `__'? Because
48 that seems to be the case now -- jcn)
54 1: Start sentence melisma end.
57 Only lyrics that are followed by '__' while there's a melisma,
58 are left-aligned, in this case the third x.
61 Alignment and melismata
63 I've taken [a different] approach:
66 O O <-- second note throws a melisma score element
73 Lyric_phrasing_engraver keeps track of the current and previous notes and
74 lyrics for each voice, and when it catches a melisma, it adjusts the
75 alignment of the lyrics of the previous note. I hope this isn't
76 unnecessarily convoluted.
79 Lyric_phrasing_engraver::Lyric_phrasing_engraver ()
81 voice_alist_ = SCM_EOL;
85 Lyric_phrasing_engraver::~Lyric_phrasing_engraver ()
88 No need to delete alist_; that's what Garbage collection is for.
93 Lyric_phrasing_engraver::finalize ()
96 but do need to unprotect alist_, since Engravers are gc'd now.
99 voice_alist_ = SCM_EOL;
104 Lyric_phrasing_engraver::lookup_context_id (const String &context_id)
106 SCM key = ly_str02scm (context_id.ch_C ());
107 if (! gh_null_p (voice_alist_))
109 SCM s = scm_assoc (key, voice_alist_);
110 if (! (gh_boolean_p (s) && !to_boolean (s)))
113 // (key . ((alist_entry . old_entry) . previous_entry))
114 if (to_boolean (ly_cdadr (s)))
115 { // it's an old entry ... make it a new one
116 SCM val = gh_cons (gh_cons (ly_caadr (s), SCM_BOOL_F), ly_cddr (s));
117 voice_alist_ = scm_assoc_set_x (voice_alist_, ly_car (s), val);
118 return unsmob_voice_entry (ly_caar (val));
120 else { // the entry is current ... return it.
121 SCM entry_scm = ly_caadr (s);
122 return unsmob_voice_entry (entry_scm);
126 // ((alist_entry . old_entry) . previous_entry)
127 SCM val = gh_cons (gh_cons (Syllable_group::make_entry (), SCM_BOOL_F),
128 Syllable_group::make_entry ());
130 voice_alist_ = scm_acons (key, val, voice_alist_);
131 return unsmob_voice_entry (ly_caar (val));
136 Lyric_phrasing_engraver::record_notehead (const String &context_id,
139 Syllable_group * v = lookup_context_id (context_id);
140 v->set_notehead (notehead);
141 if (!any_notehead_l_)
142 any_notehead_l_ = notehead;
146 Lyric_phrasing_engraver::record_lyric (const String &context_id, Grob * lyric)
148 Syllable_group * v = lookup_context_id (context_id);
149 v->add_lyric (lyric);
153 Lyric_phrasing_engraver::record_extender (const String &context_id, Grob * extender)
155 SCM key = ly_str02scm (context_id.ch_C ());
156 if (! gh_null_p (voice_alist_))
158 SCM s = scm_assoc (key, voice_alist_);
159 if (! (gh_boolean_p (s) && !to_boolean (s)))
162 // (key . ((alist_entry . old_entry) . previous_entry))
163 SCM previous_scm = ly_cddr (s);
164 if (previous_scm != SCM_EOL)
166 Syllable_group * v = unsmob_voice_entry (previous_scm);
167 v->add_extender (extender);
174 Lyric_phrasing_engraver::record_melisma (const String &context_id)
176 Syllable_group * v = lookup_context_id (context_id);
181 Lyric_phrasing_engraver::acknowledge_grob (Grob_info i)
183 SCM p = get_property ("automaticPhrasing");
190 if (Note_head::has_interface (h))
192 /* caught a note head ... do something with it */
194 /* what's its Voice context name? */
195 String voice_context_id = get_context_id (i.origin_trans_l_->daddy_trans_l_, "Voice");
196 record_notehead (voice_context_id, h);
198 /* is it in a melisma ? */
199 if (to_boolean (i.origin_trans_l_->get_property ("melismaEngraverBusy")))
201 record_melisma (voice_context_id);
206 /* now try for a lyric */
207 if (h->internal_has_interface (ly_symbol2scm ("lyric-syllable-interface")))
210 /* what's its LyricsVoice context name? */
211 String voice_context_id;
212 SCM voice_context_scm = i.origin_trans_l_->get_property ("associatedVoice");
213 if (gh_string_p (voice_context_scm))
215 voice_context_id = ly_scm2string (voice_context_scm);
218 voice_context_id = get_context_id (i.origin_trans_l_->daddy_trans_l_, "LyricsVoice");
219 voice_context_id = trim_suffix (voice_context_id);
221 record_lyric (voice_context_id, h);
225 /* Catch any extender items and then if we have a melisma,
226 set the RIGHT item of the extender spanner to the melismatic note in
227 the corresponding context (if any).
228 This has the effect of finishing the extender under the last note
229 of the melisma, instead of extending it to the next lyric.
231 Problem: the extender request is thrown at the same moment as the next lyric,
232 by which time we have already passed the last note of the melisma.
233 However, the Lyric_phrasing_engraver remembers the last note, so just
234 attach it to that, provided it was melismatic. If it was not melismatic,
235 then ignore it and let the Extender_engraver take care of it (i.e. finish at next
238 if (h->internal_has_interface (ly_symbol2scm ("lyric-extender-interface")))
240 String voice_context_id = get_context_id (i.origin_trans_l_->daddy_trans_l_, "LyricsVoice");
241 record_extender (trim_suffix (voice_context_id), h);
247 get_context_id (Translator_group * ancestor, const char *type)
249 while (ancestor != 0 && ancestor->type_str_ != type)
251 ancestor = ancestor->daddy_trans_l_;
256 return ancestor->id_str_;
263 trim_suffix (String &id)
265 int index = id.index_i ('-');
268 return id.left_str (index);
275 Lyric_phrasing_engraver::process_acknowledged_grobs ()
277 SCM p = get_property ("automaticPhrasing");
282 /* iterate through entries in voice_alist_
283 for each, call set_lyric_align (alignment). Issue a warning if this returns false.
286 SCM sp = get_property ("phrasingPunctuation");
287 punc = gh_string_p (sp) ? ly_scm2string (sp) : ".,;:?!\"";
289 for (SCM v=voice_alist_; gh_pair_p (v); v = ly_cdr (v))
291 SCM v_entry = ly_cdar (v);
292 // ((current . oldflag) . previous)
293 if (!to_boolean (ly_cdar (v_entry)))
294 { // not an old entry left over from a prior note ...
295 Syllable_group *entry = unsmob_voice_entry (ly_caar (v_entry));
298 TODO: give context for warning.
300 if (! entry->set_lyric_align (punc.ch_C (), any_notehead_l_))
301 warning (_ ("lyrics found without any matching notehead"));
303 // is this note melismatic? If so adjust alignment of previous one.
304 if (entry->get_melisma ())
306 if (entry->lyric_count ())
307 warning (_ ("Huh? Melismatic note found to have associated lyrics."));
308 SCM previous_scm = ly_cdr (v_entry);
309 if (previous_scm != SCM_EOL)
311 Syllable_group *previous = unsmob_voice_entry (previous_scm);
312 if (previous->lyric_count ())
313 previous->adjust_melisma_align ();
322 Lyric_phrasing_engraver::stop_translation_timestep ()
324 for (SCM v=voice_alist_; gh_pair_p (v); v = ly_cdr (v))
326 SCM entry_scm = ly_cdar (v);
327 // ((alist_entry . entry_is_old) . previous_entry)
328 Syllable_group * entry = unsmob_voice_entry (ly_caar (entry_scm));
330 // set previous_entry, set entry_is_old, and resave it to alist_
331 // but only change if this current was not old.
332 if (! to_boolean (ly_cdar (entry_scm)))
334 Syllable_group * previous_entry = unsmob_voice_entry (ly_cdr (entry_scm));
335 previous_entry->copy (entry);
336 entry_scm = gh_cons (gh_cons (ly_caar (entry_scm), SCM_BOOL_T), ly_cdr (entry_scm));
337 voice_alist_ = scm_assoc_set_x (voice_alist_, ly_caar (v), entry_scm);
339 entry->next_lyric ();
346 ENTER_DESCRIPTION(Lyric_phrasing_engraver,
349 /* acks */ "lyric-syllable-interface note-head-interface lyric-extender-interface",
350 /* reads */ "automaticPhrasing melismaEngraverBusy associatedVoice phrasingPunctuation",