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);
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 (ly_cdadr (s))) { // it's an old entry ... make it a new one
112 SCM val = gh_cons (gh_cons (ly_caadr (s), SCM_BOOL_F), ly_cddr (s));
113 voice_alist_ = scm_assoc_set_x (voice_alist_, ly_car (s), val);
114 return unsmob_voice_entry (ly_caar (val));
116 else { // the entry is current ... return it.
117 SCM entry_scm = ly_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 (ly_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 = ly_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 */
186 /* what's its Voice context name? */
187 String voice_context_id = get_context_id (i.origin_trans_l_->daddy_trans_l_, "Voice");
188 record_notehead (voice_context_id, h);
190 /* is it in a melisma ? */
191 if (to_boolean (i.origin_trans_l_->get_property ("melismaEngraverBusy"))) {
192 record_melisma (voice_context_id);
197 /* now try for a lyric */
198 if (h->has_interface (ly_symbol2scm ("lyric-syllable-interface"))) {
200 /* what's its LyricsVoice context name? */
201 String voice_context_id;
202 SCM voice_context_scm = i.origin_trans_l_->get_property ("associatedVoice");
203 if (gh_string_p (voice_context_scm)) {
204 voice_context_id = ly_scm2string (voice_context_scm);
207 voice_context_id = get_context_id (i.origin_trans_l_->daddy_trans_l_, "LyricsVoice");
208 voice_context_id = trim_suffix (voice_context_id);
210 record_lyric (voice_context_id, h);
214 /* Catch any extender items and then if we have a melisma,
215 set the RIGHT item of the extender spanner to the melismatic note in
216 the corresponding context (if any).
217 This has the effect of finishing the extender under the last note
218 of the melisma, instead of extending it to the next lyric.
220 Problem: the extender request is thrown at the same moment as the next lyric,
221 by which time we have already passed the last note of the melisma.
222 However, the Lyric_phrasing_engraver remembers the last note, so just
223 attach it to that, provided it was melismatic. If it was not melismatic,
224 then ignore it and let the Extender_engraver take care of it (i.e. finish at next
227 if (h->has_interface (ly_symbol2scm ("lyric-extender-interface"))) {
228 String voice_context_id = get_context_id (i.origin_trans_l_->daddy_trans_l_, "LyricsVoice");
229 record_extender (trim_suffix (voice_context_id), h);
235 get_context_id (Translator_group * ancestor, const char *type)
237 while (ancestor != 0 && ancestor->type_str_ != type) {
238 ancestor = ancestor->daddy_trans_l_;
242 return ancestor->id_str_;
249 trim_suffix (String &id)
251 int index = id.index_i ('-');
253 return id.left_str (index);
259 void Lyric_phrasing_engraver::create_grobs ()
261 /* iterate through entries in voice_alist_
262 for each, call set_lyric_align (alignment). Issue a warning if this returns false.
265 SCM sp = get_property ("phrasingPunctuation");
266 punc = gh_string_p (sp) ? ly_scm2string (sp) : ".,;:?!\"";
268 for (SCM v=voice_alist_; gh_pair_p (v); v = ly_cdr (v)) {
269 SCM v_entry = ly_cdar (v);
270 // ((current . oldflag) . previous)
271 if (!to_boolean (ly_cdar (v_entry))) { // not an old entry left over from a prior note ...
272 Syllable_group *entry = unsmob_voice_entry (ly_caar (v_entry));
275 TODO: give context for warning.
277 if (! entry->set_lyric_align (punc.ch_C (), any_notehead_l_))
278 warning (_ ("lyrics found without any matching notehead"));
280 // is this note melismatic? If so adjust alignment of previous one.
281 if (entry->get_melisma ()) {
282 if (entry->lyric_count ())
283 warning (_ ("Huh? Melismatic note found to have associated lyrics."));
284 SCM previous_scm = ly_cdr (v_entry);
285 if (previous_scm != SCM_EOL) {
286 Syllable_group *previous = unsmob_voice_entry (previous_scm);
287 if (previous->lyric_count ())
288 previous->adjust_melisma_align ();
297 Lyric_phrasing_engraver::stop_translation_timestep ()
299 for (SCM v=voice_alist_; gh_pair_p (v); v = ly_cdr (v)) {
300 SCM entry_scm = ly_cdar (v);
301 // ((alist_entry . entry_is_old) . previous_entry)
302 Syllable_group * entry = unsmob_voice_entry (ly_caar (entry_scm));
304 // set previous_entry, set entry_is_old, and resave it to alist_
305 // but only change if this current was not old.
306 if (! to_boolean (ly_cdar (entry_scm))) {
307 Syllable_group * previous_entry = unsmob_voice_entry (ly_cdr (entry_scm));
308 previous_entry->copy (entry);
309 entry_scm = gh_cons (gh_cons (ly_caar (entry_scm), SCM_BOOL_T), ly_cdr (entry_scm));
310 voice_alist_ = scm_assoc_set_x (voice_alist_, ly_caar (v), entry_scm);
312 entry->next_lyric ();
319 ENTER_DESCRIPTION(Lyric_phrasing_engraver,
322 /* acks */ "lyric-syllable-interface note-head-interface lyric-extender-interface",
323 /* reads */ "automaticPhrasing melismaEngraverBusy associatedVoice phrasingPunctuation",