]> git.donarmstrong.com Git - lilypond.git/blob - lily/lyric-phrasing-engraver.cc
release: 1.5.44
[lilypond.git] / lily / lyric-phrasing-engraver.cc
1 /*
2   lyric-phrasing-engraver.cc -- implement Lyric_phrasing_engraver
3
4   source file of the GNU LilyPond music typesetter
5
6   (c)  2000 Glen Prideaux <glenprideaux@iname.com>
7 */
8 #include <string.h>
9
10 #include "lyric-phrasing-engraver.hh"
11 #include "note-head.hh"
12 #include "translator-group.hh"
13 #include "spanner.hh"
14 #include "warn.hh"
15
16
17 String get_context_id (Translator_group * ancestor, const char * type);
18 String trim_suffix (String &id);
19
20
21
22 /*
23   TODO: this code is too hairy, and does things that should be in the
24   backend. Fixme.
25 */
26
27 /*
28   TODO:
29
30   shared lyrics should be vertically centered:
31
32   
33
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.
39
40  */
41
42 /*
43   We find start and end of phrases, and align lyrics of multiple stanzas
44   accordingly.
45
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)
49
50
51            |        |        |     |      |
52           x|       x|       x|    x|     x|
53
54      1:  Start  sentence  melisma      end.
55      2:  x         x         x_____       x
56
57   Only lyrics that are followed by '__' while there's a melisma,
58   are left-aligned, in this case the third x.
59
60   
61   Alignment and melismata
62
63   I've taken [a different] approach:
64           |      |
65           |      |
66          O      O  <-- second note throws a melisma score element
67           \____/
68
69          ^      ^
70          |      |
71        Lyric (None)
72
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.
77  */
78
79 Lyric_phrasing_engraver::Lyric_phrasing_engraver ()
80 {
81   voice_alist_ = SCM_EOL;
82   any_notehead_l_ = 0;
83 }
84
85 Lyric_phrasing_engraver::~Lyric_phrasing_engraver ()
86 {
87   /*
88     No need to delete alist_; that's what Garbage collection is for.
89    */
90 }
91
92 void
93 Lyric_phrasing_engraver::finalize ()
94 {
95   /*
96     but do need to unprotect alist_, since Engravers are gc'd now.
97    */
98
99   voice_alist_ = SCM_EOL;
100 }
101
102
103 Syllable_group * 
104 Lyric_phrasing_engraver::lookup_context_id (const String &context_id)
105 {
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))) {
110       /* match found */
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));
116       }
117       else { // the entry is current ... return it.
118         SCM entry_scm = ly_caadr (s);
119         return unsmob_voice_entry (entry_scm);
120       }
121     }
122   }
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 ()); 
126
127   voice_alist_ = scm_acons (key, val, voice_alist_);
128   return unsmob_voice_entry (ly_caar (val));
129 }
130
131
132 void 
133 Lyric_phrasing_engraver::record_notehead (const String &context_id, 
134                                          Grob * notehead)
135 {
136   Syllable_group * v = lookup_context_id (context_id);
137   v->set_notehead (notehead);
138   if (!any_notehead_l_)
139     any_notehead_l_ = notehead;
140 }
141   
142 void 
143 Lyric_phrasing_engraver::record_lyric (const String &context_id, Grob * lyric)
144 {
145   Syllable_group * v = lookup_context_id (context_id);
146   v->add_lyric (lyric);
147 }
148
149 void 
150 Lyric_phrasing_engraver::record_extender (const String &context_id, Grob * extender)
151 {
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))) {
156       /* match found */
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);
162       }
163     }
164   }
165 }
166
167 void 
168 Lyric_phrasing_engraver::record_melisma (const String &context_id)
169 {
170   Syllable_group * v = lookup_context_id (context_id);
171   v->set_melisma ();
172 }
173   
174 void
175 Lyric_phrasing_engraver::acknowledge_grob (Grob_info i)
176 {
177   SCM p = get_property ("automaticPhrasing");
178   if (!to_boolean (p))
179     return;
180
181
182   Grob *h = i.grob_l_;
183
184   if (Note_head::has_interface (h)) {
185     /* caught a note head ... do something with it */
186
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);
190
191     /* is it in a melisma ? */
192     if (to_boolean (i.origin_trans_l_->get_property ("melismaEngraverBusy"))) {
193       record_melisma (voice_context_id);
194     }
195     return;
196   }
197
198   /* now try for a lyric */
199   if (h->has_interface (ly_symbol2scm ("lyric-syllable-interface"))) {
200
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);
206     }
207     else {
208       voice_context_id = get_context_id (i.origin_trans_l_->daddy_trans_l_, "LyricsVoice");
209       voice_context_id = trim_suffix (voice_context_id);
210     }
211     record_lyric (voice_context_id, h);
212     return;
213   }
214
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.
220      
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
226      lyric).
227   */
228   if (h->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);
231     return;
232   }
233 }
234
235 String 
236 get_context_id (Translator_group * ancestor, const char *type)
237 {
238   while (ancestor != 0 && ancestor->type_str_ != type) {
239     ancestor = ancestor->daddy_trans_l_;
240   }
241
242   if (ancestor != 0) {
243     return ancestor->id_str_;
244   }
245
246   return "";
247 }
248
249 String 
250 trim_suffix (String &id)
251 {
252   int index = id.index_i ('-');
253   if (index >= 0) {
254     return id.left_str (index);
255   }
256   return id;
257 }
258
259
260 void Lyric_phrasing_engraver::create_grobs () 
261 {
262   /* iterate through entries in voice_alist_
263      for each, call set_lyric_align (alignment). Issue a warning if this returns false.
264   */
265   String punc;
266   SCM sp = get_property ("phrasingPunctuation");
267   punc = gh_string_p (sp) ? ly_scm2string (sp) : ".,;:?!\""; 
268   
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));
274
275       /*
276         TODO: give context for warning.
277        */
278       if (! entry->set_lyric_align (punc.ch_C (), any_notehead_l_))
279         warning (_ ("lyrics found without any matching notehead"));
280
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 ();
290         }
291       }
292     }
293   }
294 }
295
296
297 void
298 Lyric_phrasing_engraver::stop_translation_timestep ()
299 {
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));
304
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);
312     }
313     entry->next_lyric ();
314   }
315   any_notehead_l_ = 0;
316 }
317
318
319
320 ENTER_DESCRIPTION(Lyric_phrasing_engraver,
321 /* descr */       "",
322 /* creats*/       "",
323 /* acks  */       "lyric-syllable-interface note-head-interface lyric-extender-interface",
324 /* reads */       "automaticPhrasing melismaEngraverBusy associatedVoice phrasingPunctuation",
325 /* write */       "");