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_)) {
108 SCM s = scm_assoc (key, voice_alist_);
109 if (! (gh_boolean_p (s) && !to_boolean (s))) {
111 // (key . ((alist_entry . old_entry) . previous_entry))
112 if (to_boolean (ly_cdadr (s))) { // it's an old entry ... make it a new one
113 SCM val = gh_cons (gh_cons (ly_caadr (s), SCM_BOOL_F), ly_cddr (s));
114 voice_alist_ = scm_assoc_set_x (voice_alist_, ly_car (s), val);
115 return unsmob_voice_entry (ly_caar (val));
117 else { // the entry is current ... return it.
118 SCM entry_scm = ly_caadr (s);
119 return unsmob_voice_entry (entry_scm);
123 // ((alist_entry . old_entry) . previous_entry)
124 SCM val = gh_cons (gh_cons (Syllable_group::make_entry (), SCM_BOOL_F),
125 Syllable_group::make_entry ());
127 voice_alist_ = scm_acons (key, val, voice_alist_);
128 return unsmob_voice_entry (ly_caar (val));
133 Lyric_phrasing_engraver::record_notehead (const String &context_id,
136 Syllable_group * v = lookup_context_id (context_id);
137 v->set_notehead (notehead);
138 if (!any_notehead_l_)
139 any_notehead_l_ = notehead;
143 Lyric_phrasing_engraver::record_lyric (const String &context_id, Grob * lyric)
145 Syllable_group * v = lookup_context_id (context_id);
146 v->add_lyric (lyric);
150 Lyric_phrasing_engraver::record_extender (const String &context_id, Grob * extender)
152 SCM key = ly_str02scm (context_id.ch_C ());
153 if (! gh_null_p (voice_alist_)) {
154 SCM s = scm_assoc (key, voice_alist_);
155 if (! (gh_boolean_p (s) && !to_boolean (s))) {
157 // (key . ((alist_entry . old_entry) . previous_entry))
158 SCM previous_scm = ly_cddr (s);
159 if (previous_scm != SCM_EOL) {
160 Syllable_group * v = unsmob_voice_entry (previous_scm);
161 v->add_extender (extender);
168 Lyric_phrasing_engraver::record_melisma (const String &context_id)
170 Syllable_group * v = lookup_context_id (context_id);
175 Lyric_phrasing_engraver::acknowledge_grob (Grob_info i)
177 SCM p = get_property ("automaticPhrasing");
184 if (Note_head::has_interface (h)) {
185 /* caught a note head ... do something with it */
187 /* what's its Voice context name? */
188 String voice_context_id = get_context_id (i.origin_trans_l_->daddy_trans_l_, "Voice");
189 record_notehead (voice_context_id, h);
191 /* is it in a melisma ? */
192 if (to_boolean (i.origin_trans_l_->get_property ("melismaEngraverBusy"))) {
193 record_melisma (voice_context_id);
198 /* now try for a lyric */
199 if (h->internal_has_interface (ly_symbol2scm ("lyric-syllable-interface"))) {
201 /* what's its LyricsVoice context name? */
202 String voice_context_id;
203 SCM voice_context_scm = i.origin_trans_l_->get_property ("associatedVoice");
204 if (gh_string_p (voice_context_scm)) {
205 voice_context_id = ly_scm2string (voice_context_scm);
208 voice_context_id = get_context_id (i.origin_trans_l_->daddy_trans_l_, "LyricsVoice");
209 voice_context_id = trim_suffix (voice_context_id);
211 record_lyric (voice_context_id, h);
215 /* Catch any extender items and then if we have a melisma,
216 set the RIGHT item of the extender spanner to the melismatic note in
217 the corresponding context (if any).
218 This has the effect of finishing the extender under the last note
219 of the melisma, instead of extending it to the next lyric.
221 Problem: the extender request is thrown at the same moment as the next lyric,
222 by which time we have already passed the last note of the melisma.
223 However, the Lyric_phrasing_engraver remembers the last note, so just
224 attach it to that, provided it was melismatic. If it was not melismatic,
225 then ignore it and let the Extender_engraver take care of it (i.e. finish at next
228 if (h->internal_has_interface (ly_symbol2scm ("lyric-extender-interface"))) {
229 String voice_context_id = get_context_id (i.origin_trans_l_->daddy_trans_l_, "LyricsVoice");
230 record_extender (trim_suffix (voice_context_id), h);
236 get_context_id (Translator_group * ancestor, const char *type)
238 while (ancestor != 0 && ancestor->type_str_ != type) {
239 ancestor = ancestor->daddy_trans_l_;
243 return ancestor->id_str_;
250 trim_suffix (String &id)
252 int index = id.index_i ('-');
254 return id.left_str (index);
260 void Lyric_phrasing_engraver::create_grobs ()
262 /* iterate through entries in voice_alist_
263 for each, call set_lyric_align (alignment). Issue a warning if this returns false.
266 SCM sp = get_property ("phrasingPunctuation");
267 punc = gh_string_p (sp) ? ly_scm2string (sp) : ".,;:?!\"";
269 for (SCM v=voice_alist_; gh_pair_p (v); v = ly_cdr (v)) {
270 SCM v_entry = ly_cdar (v);
271 // ((current . oldflag) . previous)
272 if (!to_boolean (ly_cdar (v_entry))) { // not an old entry left over from a prior note ...
273 Syllable_group *entry = unsmob_voice_entry (ly_caar (v_entry));
276 TODO: give context for warning.
278 if (! entry->set_lyric_align (punc.ch_C (), any_notehead_l_))
279 warning (_ ("lyrics found without any matching notehead"));
281 // is this note melismatic? If so adjust alignment of previous one.
282 if (entry->get_melisma ()) {
283 if (entry->lyric_count ())
284 warning (_ ("Huh? Melismatic note found to have associated lyrics."));
285 SCM previous_scm = ly_cdr (v_entry);
286 if (previous_scm != SCM_EOL) {
287 Syllable_group *previous = unsmob_voice_entry (previous_scm);
288 if (previous->lyric_count ())
289 previous->adjust_melisma_align ();
298 Lyric_phrasing_engraver::stop_translation_timestep ()
300 for (SCM v=voice_alist_; gh_pair_p (v); v = ly_cdr (v)) {
301 SCM entry_scm = ly_cdar (v);
302 // ((alist_entry . entry_is_old) . previous_entry)
303 Syllable_group * entry = unsmob_voice_entry (ly_caar (entry_scm));
305 // set previous_entry, set entry_is_old, and resave it to alist_
306 // but only change if this current was not old.
307 if (! to_boolean (ly_cdar (entry_scm))) {
308 Syllable_group * previous_entry = unsmob_voice_entry (ly_cdr (entry_scm));
309 previous_entry->copy (entry);
310 entry_scm = gh_cons (gh_cons (ly_caar (entry_scm), SCM_BOOL_T), ly_cdr (entry_scm));
311 voice_alist_ = scm_assoc_set_x (voice_alist_, ly_caar (v), entry_scm);
313 entry->next_lyric ();
320 ENTER_DESCRIPTION(Lyric_phrasing_engraver,
323 /* acks */ "lyric-syllable-interface note-head-interface lyric-extender-interface",
324 /* reads */ "automaticPhrasing melismaEngraverBusy associatedVoice phrasingPunctuation",