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