2002->2003
[lilypond.git] / lily / lyric-phrasing-engraver.cc
blob99eb55515b56a758d549e75de0c72d3b46849930
1 /*
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>
7 */
8 #include <string.h>
10 #include "lyric-phrasing-engraver.hh"
11 #include "note-head.hh"
12 #include "translator-group.hh"
13 #include "spanner.hh"
14 #include "warn.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
24 backend. Fixme.
28 TODO:
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
44 accordingly.
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)
51 | | | | |
52 x| x| x| x| x|
54 1: Start sentence melisma end.
55 2: x x x_____ x
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:
64 | |
65 | |
66 O O <-- second note throws a melisma score element
67 \____/
69 ^ ^
70 | |
71 Lyric (None)
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;
82 any_notehead_ = 0;
85 Lyric_phrasing_engraver::~Lyric_phrasing_engraver ()
88 No need to delete alist_; that's what Garbage collection is for.
92 void
93 Lyric_phrasing_engraver::finalize ()
96 but do need to unprotect alist_, since Engravers are gc'd now.
99 voice_alist_ = SCM_EOL;
103 Syllable_group *
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)))
112 /* match found */
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));
121 else
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));
138 void
139 Lyric_phrasing_engraver::record_notehead (const String &context_id,
140 Grob * notehead)
142 Syllable_group * v = lookup_context_id (context_id);
143 v->set_notehead (notehead);
144 if (!any_notehead_)
145 any_notehead_ = notehead;
148 void
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);
155 void
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)))
164 /* match found */
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);
176 void
177 Lyric_phrasing_engraver::record_melisma (const String &context_id)
179 Syllable_group * v = lookup_context_id (context_id);
180 v->set_melisma ();
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.
188 void
189 Lyric_phrasing_engraver::acknowledge_grob (Grob_info i)
191 SCM p = get_property ("automaticPhrasing");
192 if (!to_boolean (p))
193 return;
196 Grob *h = i.grob_;
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);
211 return;
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);
225 else
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);
231 return;
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
245 lyric).
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);
251 return;
255 String
256 get_context_id (Translator_group * ancestor, const char *type)
258 while (ancestor != 0 && ancestor->type_string_ != type)
260 ancestor = ancestor->daddy_trans_;
263 if (ancestor != 0)
265 return ancestor->id_string_;
268 return "";
271 String
272 trim_suffix (String &id)
274 int index = id.index ('-');
275 if (index >= 0)
277 return id.left_string (index);
279 return id;
283 void
284 Lyric_phrasing_engraver::process_acknowledged_grobs ()
286 SCM p = get_property ("automaticPhrasing");
287 if (!to_boolean (p))
288 return;
291 /* iterate through entries in voice_alist_
292 for each, call set_lyric_align (alignment). Issue a warning if this returns false.
294 String punc;
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 ();
331 void
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 ();
351 any_notehead_ = 0;
356 ENTER_DESCRIPTION(Lyric_phrasing_engraver,
357 /* descr */
358 "This engraver combines note heads and lyrics for alignment. "
359 "\n\n"
360 "This engraver is switched on by default. Turn it off for faster "
361 "processing of orchestral scores. ",
362 /* creats*/ "",
363 /* accepts */ "",
364 /* acks */ "lyric-syllable-interface note-head-interface lyric-extender-interface",
365 /* reads */ "automaticPhrasing melismaEngraverBusy associatedVoice phrasingPunctuation",
366 /* write */ "");