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