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"
16 String get_context_id (Translator_group * ancestor, const char * type);
17 String trim_suffix (String &id);
19 ADD_THIS_TRANSLATOR (Lyric_phrasing_engraver);
22 TODO: this code is too hairy, and does things that should be in the
29 shared lyrics should be vertically centered:
33 > About lyrics, it happens that there are common words for many bars, like
34 > for a refrain say. When there is an even number of lyrics lines, I do not
35 > know how to force the positioning of the common lyric line in the plain
36 > middle of the others, because this is in between lines. Not a big matter,
37 > but it would be a bit nicer if this was doable.
42 We find start and end of phrases, and align lyrics of multiple stanzas
45 Also, lyrics at start of melismata should be left aligned.
46 (is that only lyrics that are followed by `__'? Because
47 that seems to be the case now -- jcn)
53 1: Start sentence melisma end.
56 Only lyrics that are followed by '__' while there's a melisma,
57 are left-aligned, in this case the third x.
60 Alignment and melismata
62 I've taken [a different] approach:
65 O O <-- second note throws a melisma score element
72 Lyric_phrasing_engraver keeps track of the current and previous notes and
73 lyrics for each voice, and when it catches a melisma, it adjusts the
74 alignment of the lyrics of the previous note. I hope this isn't
75 unnecessarily convoluted.
78 Lyric_phrasing_engraver::Lyric_phrasing_engraver ()
80 voice_alist_ = SCM_EOL;
84 Lyric_phrasing_engraver::~Lyric_phrasing_engraver ()
87 No need to delete alist_; that's what Garbage collection is for.
92 Lyric_phrasing_engraver::finalize ()
95 but do need to unprotect alist_, since Engravers are gc'd now.
98 voice_alist_ = SCM_EOL;
103 Lyric_phrasing_engraver::lookup_context_id (const String &context_id)
105 SCM key = ly_str02scm (context_id.ch_C ());
106 if (! gh_null_p (voice_alist_)) {
107 SCM s = scm_assoc (key, voice_alist_);
108 if (! (gh_boolean_p (s) && !to_boolean (s))) {
110 // (key . ((alist_entry . old_entry) . previous_entry))
111 if (to_boolean (gh_cdadr (s))) { // it's an old entry ... make it a new one
112 SCM val = gh_cons (gh_cons (gh_caadr (s), SCM_BOOL_F), gh_cddr (s));
113 voice_alist_ = scm_assoc_set_x (voice_alist_, gh_car (s), val);
114 return unsmob_voice_entry (gh_caar (val));
116 else { // the entry is current ... return it.
117 SCM entry_scm = gh_caadr (s);
118 return unsmob_voice_entry (entry_scm);
122 // ((alist_entry . old_entry) . previous_entry)
123 SCM val = gh_cons (gh_cons (Syllable_group::make_entry (), SCM_BOOL_F),
124 Syllable_group::make_entry ());
126 voice_alist_ = scm_acons (key, val, voice_alist_);
127 return unsmob_voice_entry (gh_caar (val));
132 Lyric_phrasing_engraver::record_notehead (const String &context_id,
135 Syllable_group * v = lookup_context_id (context_id);
136 v->set_notehead (notehead);
137 if (!any_notehead_l_)
138 any_notehead_l_ = notehead;
142 Lyric_phrasing_engraver::record_lyric (const String &context_id, Grob * lyric)
144 Syllable_group * v = lookup_context_id (context_id);
145 v->add_lyric (lyric);
149 Lyric_phrasing_engraver::record_extender (const String &context_id, Grob * extender)
151 SCM key = ly_str02scm (context_id.ch_C ());
152 if (! gh_null_p (voice_alist_)) {
153 SCM s = scm_assoc (key, voice_alist_);
154 if (! (gh_boolean_p (s) && !to_boolean (s))) {
156 // (key . ((alist_entry . old_entry) . previous_entry))
157 SCM previous_scm = gh_cddr (s);
158 if (previous_scm != SCM_EOL) {
159 Syllable_group * v = unsmob_voice_entry (previous_scm);
160 v->add_extender (extender);
167 Lyric_phrasing_engraver::record_melisma (const String &context_id)
169 Syllable_group * v = lookup_context_id (context_id);
174 Lyric_phrasing_engraver::acknowledge_grob (Grob_info i)
176 SCM p = get_property ("automaticPhrasing");
183 if (Note_head::has_interface (h)) {
184 /* caught a note head ... do something with it */
185 /* ... but not if it's a grace note ... */
186 bool grace= to_boolean (i.elem_l_->get_grob_property ("grace"));
187 SCM wg = get_property ("weAreGraceContext");
188 bool wgb = to_boolean (wg);
192 /* what's its Voice context name? */
193 String voice_context_id = get_context_id (i.origin_trans_l_->daddy_trans_l_, "Voice");
194 record_notehead (voice_context_id, h);
196 /* is it in a melisma ? */
197 if (to_boolean (i.origin_trans_l_->get_property ("melismaEngraverBusy"))) {
198 record_melisma (voice_context_id);
203 /* now try for a lyric */
204 if (h->has_interface (ly_symbol2scm ("lyric-syllable-interface"))) {
206 /* what's its LyricsVoice context name? */
207 String voice_context_id;
208 SCM voice_context_scm = i.origin_trans_l_->get_property ("associatedVoice");
209 if (gh_string_p (voice_context_scm)) {
210 voice_context_id = ly_scm2string (voice_context_scm);
213 voice_context_id = get_context_id (i.origin_trans_l_->daddy_trans_l_, "LyricsVoice");
214 voice_context_id = trim_suffix (voice_context_id);
216 record_lyric (voice_context_id, h);
220 /* Catch any extender items and then if we have a melisma,
221 set the RIGHT item of the extender spanner to the melismatic note in
222 the corresponding context (if any).
223 This has the effect of finishing the extender under the last note
224 of the melisma, instead of extending it to the next lyric.
226 Problem: the extender request is thrown at the same moment as the next lyric,
227 by which time we have already passed the last note of the melisma.
228 However, the Lyric_phrasing_engraver remembers the last note, so just
229 attach it to that, provided it was melismatic. If it was not melismatic,
230 then ignore it and let the Extender_engraver take care of it (i.e. finish at next
233 if (h->has_interface (ly_symbol2scm ("lyric-extender-interface"))) {
234 String voice_context_id = get_context_id (i.origin_trans_l_->daddy_trans_l_, "LyricsVoice");
235 record_extender (trim_suffix (voice_context_id), h);
241 get_context_id (Translator_group * ancestor, const char *type)
243 while (ancestor != 0 && ancestor->type_str_ != type) {
244 ancestor = ancestor->daddy_trans_l_;
248 return ancestor->id_str_;
255 trim_suffix (String &id)
257 int index = id.index_i ('-');
259 return id.left_str (index);
265 void Lyric_phrasing_engraver::create_grobs ()
267 /* iterate through entries in voice_alist_
268 for each, call set_lyric_align (alignment). Issue a warning if this returns false.
271 SCM sp = get_property ("phrasingPunctuation");
272 punc = gh_string_p (sp) ? ly_scm2string (sp) : ".,;:?!\"";
274 for (SCM v=voice_alist_; gh_pair_p (v); v = gh_cdr (v)) {
275 SCM v_entry = gh_cdar (v);
276 // ((current . oldflag) . previous)
277 if (!to_boolean (gh_cdar (v_entry))) { // not an old entry left over from a prior note ...
278 Syllable_group *entry = unsmob_voice_entry (gh_caar (v_entry));
281 TODO: give context for warning.
283 if (! entry->set_lyric_align (punc.ch_C (), any_notehead_l_))
284 warning (_ ("lyrics found without any matching notehead"));
286 // is this note melismatic? If so adjust alignment of previous one.
287 if (entry->get_melisma ()) {
288 if (entry->lyric_count ())
289 warning (_ ("Huh? Melismatic note found to have associated lyrics."));
290 SCM previous_scm = gh_cdr (v_entry);
291 if (previous_scm != SCM_EOL) {
292 Syllable_group *previous = unsmob_voice_entry (previous_scm);
293 if (previous->lyric_count ())
294 previous->adjust_melisma_align ();
303 Lyric_phrasing_engraver::stop_translation_timestep ()
305 for (SCM v=voice_alist_; gh_pair_p (v); v = gh_cdr (v)) {
306 SCM entry_scm = gh_cdar (v);
307 // ((alist_entry . entry_is_old) . previous_entry)
308 Syllable_group * entry = unsmob_voice_entry (gh_caar (entry_scm));
310 // set previous_entry, set entry_is_old, and resave it to alist_
311 // but only change if this current was not old.
312 if (! to_boolean (gh_cdar (entry_scm))) {
313 Syllable_group * previous_entry = unsmob_voice_entry (gh_cdr (entry_scm));
314 previous_entry->copy (entry);
315 entry_scm = gh_cons (gh_cons (gh_caar (entry_scm), SCM_BOOL_T), gh_cdr (entry_scm));
316 voice_alist_ = scm_assoc_set_x (voice_alist_, gh_caar (v), entry_scm);
318 entry->next_lyric ();