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
= scm_makfrom0str (context_id
.to_str0 ());
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
)))
116 // it's an old entry ... make it a new one
117 SCM val
= gh_cons (gh_cons (ly_caadr (s
), SCM_BOOL_F
), ly_cddr (s
));
118 voice_alist_
= scm_assoc_set_x (voice_alist_
, ly_car (s
), val
);
119 return unsmob_voice_entry (ly_caar (val
));
123 // the entry is current ... return it.
124 SCM entry_scm
= ly_caadr (s
);
125 return unsmob_voice_entry (entry_scm
);
129 // ((alist_entry . old_entry) . previous_entry)
130 SCM val
= gh_cons (gh_cons (Syllable_group::make_entry (), SCM_BOOL_F
),
131 Syllable_group::make_entry ());
133 voice_alist_
= scm_acons (key
, val
, voice_alist_
);
134 return unsmob_voice_entry (ly_caar (val
));
139 Lyric_phrasing_engraver::record_notehead (const String
&context_id
,
142 Syllable_group
* v
= lookup_context_id (context_id
);
143 v
->set_notehead (notehead
);
145 any_notehead_
= notehead
;
149 Lyric_phrasing_engraver::record_lyric (const String
&context_id
, Grob
* lyric
)
151 Syllable_group
* v
= lookup_context_id (context_id
);
152 v
->add_lyric (lyric
);
156 Lyric_phrasing_engraver::record_extender (const String
&context_id
, Grob
* extender
)
158 SCM key
= scm_makfrom0str (context_id
.to_str0 ());
159 if (! gh_null_p (voice_alist_
))
161 SCM s
= scm_assoc (key
, voice_alist_
);
162 if (! (gh_boolean_p (s
) && !to_boolean (s
)))
165 // (key . ((alist_entry . old_entry) . previous_entry))
166 SCM previous_scm
= ly_cddr (s
);
167 if (previous_scm
!= SCM_EOL
)
169 Syllable_group
* v
= unsmob_voice_entry (previous_scm
);
170 v
->add_extender (extender
);
177 Lyric_phrasing_engraver::record_melisma (const String
&context_id
)
179 Syllable_group
* v
= lookup_context_id (context_id
);
184 TODO: this engraver is always on, also for orchestral scores. That
185 is a waste of time and space. This should be switched on
186 automatically at the first Lyrics found.
189 Lyric_phrasing_engraver::acknowledge_grob (Grob_info i
)
191 SCM p
= get_property ("automaticPhrasing");
198 if (Note_head::has_interface (h
))
200 /* caught a note head ... do something with it */
202 /* what's its Voice context name? */
203 String voice_context_id
= get_context_id (i
.origin_trans_
->daddy_trans_
, "Voice");
204 record_notehead (voice_context_id
, h
);
206 /* is it in a melisma ? */
207 if (to_boolean (i
.origin_trans_
->get_property ("melismaEngraverBusy")))
209 record_melisma (voice_context_id
);
214 /* now try for a lyric */
215 if (h
->internal_has_interface (ly_symbol2scm ("lyric-syllable-interface")))
218 /* what's its LyricsVoice context name? */
219 String voice_context_id
;
220 SCM voice_context_scm
= i
.origin_trans_
->get_property ("associatedVoice");
221 if (gh_string_p (voice_context_scm
))
223 voice_context_id
= ly_scm2string (voice_context_scm
);
227 voice_context_id
= get_context_id (i
.origin_trans_
->daddy_trans_
, "LyricsVoice");
228 voice_context_id
= trim_suffix (voice_context_id
);
230 record_lyric (voice_context_id
, h
);
234 /* Catch any extender items and then if we have a melisma,
235 set the RIGHT item of the extender spanner to the melismatic note in
236 the corresponding context (if any).
237 This has the effect of finishing the extender under the last note
238 of the melisma, instead of extending it to the next lyric.
240 Problem: the extender event is thrown at the same moment as the next lyric,
241 by which time we have already passed the last note of the melisma.
242 However, the Lyric_phrasing_engraver remembers the last note, so just
243 attach it to that, provided it was melismatic. If it was not melismatic,
244 then ignore it and let the Extender_engraver take care of it (i.e. finish at next
247 if (h
->internal_has_interface (ly_symbol2scm ("lyric-extender-interface")))
249 String voice_context_id
= get_context_id (i
.origin_trans_
->daddy_trans_
, "LyricsVoice");
250 record_extender (trim_suffix (voice_context_id
), h
);
256 get_context_id (Translator_group
* ancestor
, const char *type
)
258 while (ancestor
!= 0 && ancestor
->type_string_
!= type
)
260 ancestor
= ancestor
->daddy_trans_
;
265 return ancestor
->id_string_
;
272 trim_suffix (String
&id
)
274 int index
= id
.index ('-');
277 return id
.left_string (index
);
284 Lyric_phrasing_engraver::process_acknowledged_grobs ()
286 SCM p
= get_property ("automaticPhrasing");
291 /* iterate through entries in voice_alist_
292 for each, call set_lyric_align (alignment). Issue a warning if this returns false.
295 SCM sp
= get_property ("phrasingPunctuation");
296 punc
= gh_string_p (sp
) ? ly_scm2string (sp
) : ".,;:?!\"";
298 for (SCM v
=voice_alist_
; gh_pair_p (v
); v
= ly_cdr (v
))
300 SCM v_entry
= ly_cdar (v
);
301 // ((current . oldflag) . previous)
302 if (!to_boolean (ly_cdar (v_entry
)))
304 // not an old entry left over from a prior note ...
305 Syllable_group
*entry
= unsmob_voice_entry (ly_caar (v_entry
));
308 TODO: give context for warning.
310 if (! entry
->set_lyric_align (punc
.to_str0 (), any_notehead_
))
311 warning (_ ("lyrics found without any matching notehead"));
313 // is this note melismatic? If so adjust alignment of previous one.
314 if (entry
->get_melisma ())
316 if (entry
->lyric_count ())
317 warning (_ ("Huh? Melismatic note found to have associated lyrics."));
318 SCM previous_scm
= ly_cdr (v_entry
);
319 if (previous_scm
!= SCM_EOL
)
321 Syllable_group
*previous
= unsmob_voice_entry (previous_scm
);
322 if (previous
->lyric_count ())
323 previous
->adjust_melisma_align ();
332 Lyric_phrasing_engraver::stop_translation_timestep ()
334 for (SCM v
=voice_alist_
; gh_pair_p (v
); v
= ly_cdr (v
))
336 SCM entry_scm
= ly_cdar (v
);
337 // ((alist_entry . entry_is_old) . previous_entry)
338 Syllable_group
* entry
= unsmob_voice_entry (ly_caar (entry_scm
));
340 // set previous_entry, set entry_is_old, and resave it to alist_
341 // but only change if this current was not old.
342 if (! to_boolean (ly_cdar (entry_scm
)))
344 Syllable_group
* previous_entry
= unsmob_voice_entry (ly_cdr (entry_scm
));
345 previous_entry
->copy (entry
);
346 entry_scm
= gh_cons (gh_cons (ly_caar (entry_scm
), SCM_BOOL_T
), ly_cdr (entry_scm
));
347 voice_alist_
= scm_assoc_set_x (voice_alist_
, ly_caar (v
), entry_scm
);
349 entry
->next_lyric ();
356 ENTER_DESCRIPTION(Lyric_phrasing_engraver
,
358 "This engraver combines note heads and lyrics for alignment. "
360 "This engraver is switched on by default. Turn it off for faster "
361 "processing of orchestral scores. ",
364 /* acks */ "lyric-syllable-interface note-head-interface lyric-extender-interface",
365 /* reads */ "automaticPhrasing melismaEngraverBusy associatedVoice phrasingPunctuation",