]> git.donarmstrong.com Git - lilypond.git/blob - lily/lyric-phrasing-engraver.cc
release: 1.3.121
[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 ADD_THIS_TRANSLATOR (Lyric_phrasing_engraver);
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 accordingly.
43   Also, lyrics at start of melismata should be left aligned.
44
45   Alignment and melismata
46
47   I've taken [a different] approach:
48           |      |
49           |      |
50          O      O  <-- second note throws a melisma score element
51           \____/
52
53          ^      ^
54          |      |
55        Lyric (None)
56
57   Lyric_phrasing_engraver keeps track of the current and previous notes and
58   lyrics for each voice, and when it catches a melisma, it adjusts the
59   alignment of the lyrics of the previous note. I hope this isn't
60   unnecessarily convoluted.
61  */
62
63 Lyric_phrasing_engraver::Lyric_phrasing_engraver()
64 {
65   voice_alist_ = SCM_EOL;
66   any_notehead_l_ = 0;
67 }
68
69 Lyric_phrasing_engraver::~Lyric_phrasing_engraver()
70 {
71   /*
72     No need to delete alist_; that's what Garbage collection is for.
73    */
74 }
75
76 void
77 Lyric_phrasing_engraver::finalize ()
78 {
79   /*
80     but do need to unprotect alist_, since Engravers are gc'd now.
81    */
82
83   voice_alist_ = SCM_EOL;
84 }
85
86
87 Syllable_group * 
88 Lyric_phrasing_engraver::lookup_context_id(const String &context_id)
89 {
90   SCM key = ly_str02scm(context_id.ch_C());
91   if( ! gh_null_p(voice_alist_) ) {
92     SCM s = scm_assoc(key, voice_alist_);
93     if(! (gh_boolean_p(s) && !to_boolean(s))) {
94       /* match found */
95       // ( key . ( (alist_entry . old_entry) . previous_entry) )
96       if(to_boolean(gh_cdadr(s))) { // it's an old entry ... make it a new one
97         SCM val = gh_cons(gh_cons(gh_caadr(s), SCM_BOOL_F), gh_cddr(s)); 
98         voice_alist_ = scm_assoc_set_x(voice_alist_, gh_car(s), val);
99         return unsmob_voice_entry (gh_caar(val));
100       }
101       else { // the entry is current ... return it.
102         SCM entry_scm = gh_caadr(s);
103         return unsmob_voice_entry(entry_scm);
104       }
105     }
106   }
107   // ( ( alist_entry . old_entry ) . previous_entry )
108   SCM val = gh_cons(gh_cons(Syllable_group::make_entry (), SCM_BOOL_F), 
109                     Syllable_group::make_entry ()); 
110
111   voice_alist_ = scm_acons(key, val, voice_alist_);
112   return unsmob_voice_entry (gh_caar(val));
113 }
114
115
116 void 
117 Lyric_phrasing_engraver::record_notehead(const String &context_id, 
118                                          Grob * notehead)
119 {
120   Syllable_group * v = lookup_context_id(context_id);
121   v->set_notehead(notehead);
122   if(!any_notehead_l_)
123     any_notehead_l_ = notehead;
124 }
125   
126 void 
127 Lyric_phrasing_engraver::record_lyric(const String &context_id, Grob * lyric)
128 {
129   Syllable_group * v = lookup_context_id(context_id);
130   v->add_lyric(lyric);
131 }
132
133 void 
134 Lyric_phrasing_engraver::record_extender(const String &context_id, Grob * extender)
135 {
136   SCM key = ly_str02scm(context_id.ch_C());
137   if( ! gh_null_p(voice_alist_) ) {
138     SCM s = scm_assoc(key, voice_alist_);
139     if(! (gh_boolean_p(s) && !to_boolean(s))) {
140       /* match found */
141       // ( key . ( (alist_entry . old_entry) . previous_entry) )
142       SCM previous_scm = gh_cddr(s);
143       if(previous_scm != SCM_EOL) {
144         Syllable_group * v = unsmob_voice_entry(previous_scm);
145         v->add_extender(extender);
146       }
147     }
148   }
149 }
150
151 void 
152 Lyric_phrasing_engraver::record_melisma(const String &context_id)
153 {
154   Syllable_group * v = lookup_context_id(context_id);
155   v->set_melisma();
156 }
157   
158 void
159 Lyric_phrasing_engraver::acknowledge_grob(Grob_info i)
160 {
161   SCM p = get_property("automaticPhrasing");
162   if(!to_boolean(p))
163     return;
164
165
166   Grob *h = i.elem_l_;
167
168   if (Note_head::has_interface(h)) {
169     /* caught a note head ... do something with it */
170     /* ... but not if it's a grace note ... */
171     bool grace= to_boolean (i.elem_l_->get_grob_property ("grace"));
172     SCM wg = get_property ("weAreGraceContext");
173     bool wgb = to_boolean (wg);
174     if (grace != wgb)
175       return;
176
177     /* what's its Voice context name? */
178     String voice_context_id = get_context_id(i.origin_trans_l_->daddy_trans_l_, "Voice");
179     record_notehead(voice_context_id, h);
180
181     /* is it in a melisma ? */
182     if(to_boolean(i.origin_trans_l_->get_property("melismaEngraverBusy"))) {
183       record_melisma(voice_context_id);
184     }
185     return;
186   }
187
188   /* now try for a lyric */
189   if (h->has_interface (ly_symbol2scm ("lyric-syllable-interface"))) {
190
191     /* what's its LyricsVoice context name? */
192     String voice_context_id;
193     SCM voice_context_scm = i.origin_trans_l_->get_property("associatedVoice");
194     if (gh_string_p (voice_context_scm)) {
195       voice_context_id = ly_scm2string(voice_context_scm);
196     }
197     else {
198       voice_context_id = get_context_id(i.origin_trans_l_->daddy_trans_l_, "LyricsVoice");
199       voice_context_id = trim_suffix(voice_context_id);
200     }
201     record_lyric(voice_context_id, h);
202     return;
203   }
204
205   /* Catch any extender items and then if we have a melisma, 
206      set the RIGHT item of the extender spanner to the melismatic note in 
207      the corresponding context (if any).
208      This has the effect of finishing the extender under the last note
209      of the melisma, instead of extending it to the next lyric.
210      
211      Problem: the extender request is thrown at the same moment as the next lyric,
212      by which time we have already passed the last note of the melisma.
213      However, the Lyric_phrasing_engraver remembers the last note, so just 
214      attach it to that, provided it was melismatic. If it was not melismatic, 
215      then ignore it and let the Extender_engraver take care of it (i.e. finish at next
216      lyric).
217   */
218   if(h->has_interface (ly_symbol2scm ("lyric-extender-interface"))) {
219     String voice_context_id = get_context_id(i.origin_trans_l_->daddy_trans_l_, "LyricsVoice");
220     record_extender(trim_suffix(voice_context_id), h);
221     return;
222   }
223 }
224
225 String 
226 get_context_id(Translator_group * ancestor, const char *type)
227 {
228   while(ancestor != 0 && ancestor->type_str_ != type) {
229     ancestor = ancestor->daddy_trans_l_;
230   }
231
232   if(ancestor != 0) {
233     return ancestor->id_str_;
234   }
235
236   return "";
237 }
238
239 String 
240 trim_suffix(String &id)
241 {
242   int index = id.index_i('-');
243   if(index >= 0) {
244     return id.left_str(index);
245   }
246   return id;
247 }
248
249
250 void Lyric_phrasing_engraver::create_grobs () 
251 {
252   /* iterate through entries in voice_alist_
253      for each, call set_lyric_align(alignment). Issue a warning if this returns false.
254   */
255   String punc;
256   SCM sp = get_property("phrasingPunctuation");
257   punc = gh_string_p(sp) ? ly_scm2string(sp) : ".,;:?!\""; 
258   
259   for(SCM v=voice_alist_; gh_pair_p(v); v = gh_cdr(v)) {
260     SCM v_entry = gh_cdar(v);
261     // ((current . oldflag) . previous)
262     if(!to_boolean(gh_cdar(v_entry))) { // not an old entry left over from a prior note ...
263       Syllable_group *entry = unsmob_voice_entry(gh_caar(v_entry));
264
265       /*
266         TODO: give context for warning.
267        */
268       if(! entry->set_lyric_align(punc.ch_C(), any_notehead_l_))
269         warning (_ ("lyrics found without any matching notehead"));
270
271       // is this note melismatic? If so adjust alignment of previous one.
272       if(entry->get_melisma()) {
273         if(entry->lyric_count())
274           warning (_ ("Huh? Melismatic note found to have associated lyrics."));
275         SCM previous_scm = gh_cdr(v_entry);
276         if(previous_scm != SCM_EOL) {
277           Syllable_group *previous = unsmob_voice_entry(previous_scm);
278           if (previous->lyric_count())
279             previous->adjust_melisma_align();
280         }
281       }
282     }
283   }
284 }
285
286
287 void
288 Lyric_phrasing_engraver::stop_translation_timestep ()
289 {
290   for(SCM v=voice_alist_; gh_pair_p(v); v = gh_cdr(v)) {
291     SCM entry_scm = gh_cdar(v);
292     // ((alist_entry . entry_is_old) . previous_entry)
293     Syllable_group * entry = unsmob_voice_entry(gh_caar(entry_scm));
294
295     // set previous_entry, set entry_is_old, and resave it to alist_
296     // but only change if this current was not old.
297     if(! to_boolean(gh_cdar(entry_scm))) { 
298       Syllable_group * previous_entry = unsmob_voice_entry(gh_cdr(entry_scm));
299       previous_entry->copy(entry);
300       entry_scm = gh_cons(gh_cons(gh_caar(entry_scm), SCM_BOOL_T), gh_cdr(entry_scm));
301       voice_alist_ = scm_assoc_set_x(voice_alist_, gh_caar(v), entry_scm);
302     }
303     entry->next_lyric();
304   }
305   any_notehead_l_ = 0;
306 }
307
308
309