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>
10 #include "lyric-phrasing-engraver.hh"
11 #include "note-head.hh"
12 #include "translator-group.hh"
16 String get_context_id(Translator_group * ancestor, const char * type);
17 String trim_suffix(String &id);
19 ADD_THIS_TRANSLATOR (Lyric_phrasing_engraver);
22 TODO: this code is too hairy, and does things that should be in the
28 We find start and end of phrases, and align lyrics accordingly.
29 Also, lyrics at start of melismata should be left aligned.
31 Alignment and melismata
33 I've taken [a different] approach:
36 O O <-- second note throws a melisma score element
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.
49 Lyric_phrasing_engraver::Lyric_phrasing_engraver()
51 voice_alist_ = SCM_EOL;
55 Lyric_phrasing_engraver::~Lyric_phrasing_engraver()
58 No need to delete alist_; that's what Garbage collection is for.
63 Lyric_phrasing_engraver::do_removal_processing ()
66 but do need to unprotect alist_, since Engravers are gc'd now.
69 voice_alist_ = SCM_EOL;
74 Lyric_phrasing_engraver::lookup_context_id(const String &context_id)
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))) {
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));
87 else { // the entry is current ... return it.
88 SCM entry_scm = gh_caadr(s);
89 return unsmob_voice_entry(entry_scm);
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 ());
97 voice_alist_ = scm_acons(key, val, voice_alist_);
98 return unsmob_voice_entry (gh_caar(val));
103 Lyric_phrasing_engraver::record_notehead(const String &context_id,
104 Score_element * notehead)
106 Syllable_group * v = lookup_context_id(context_id);
107 v->set_notehead(notehead);
109 any_notehead_l_ = notehead;
113 Lyric_phrasing_engraver::record_lyric(const String &context_id, Score_element * lyric)
115 Syllable_group * v = lookup_context_id(context_id);
120 Lyric_phrasing_engraver::record_extender(const String &context_id, Score_element * extender)
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))) {
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);
138 Lyric_phrasing_engraver::record_melisma(const String &context_id)
140 Syllable_group * v = lookup_context_id(context_id);
145 Lyric_phrasing_engraver::acknowledge_element(Score_element_info i)
147 SCM p = get_property("automaticPhrasing");
152 Score_element *h = i.elem_l_;
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);
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);
167 /* is it in a melisma ? */
168 if(to_boolean(i.origin_trans_l_->get_property("melismaEngraverBusy"))) {
169 record_melisma(voice_context_id);
174 /* now try for a lyric */
175 if (h->has_interface (ly_symbol2scm ("lyric-syllable-interface"))) {
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);
184 voice_context_id = get_context_id(i.origin_trans_l_->daddy_trans_l_, "LyricVoice");
185 voice_context_id = trim_suffix(voice_context_id);
187 record_lyric(voice_context_id, h);
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.
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
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);
212 get_context_id(Translator_group * ancestor, const char *type)
214 while(ancestor != 0 && ancestor->type_str_ != type) {
215 ancestor = ancestor->daddy_trans_l_;
219 return ancestor->id_str_;
226 trim_suffix(String &id)
228 int index = id.index_i('-');
230 return id.left_str(index);
236 void Lyric_phrasing_engraver::process_acknowledged ()
238 /* iterate through entries in voice_alist_
239 for each, call set_lyric_align(alignment). Issue a warning if this returns false.
242 SCM sp = get_property("phrasingPunctuation");
243 punc = gh_string_p(sp) ? ly_scm2string(sp) : ".,;:?!\"";
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));
252 TODO: give context for warning.
254 if(! entry->set_lyric_align(punc.ch_C(), any_notehead_l_))
255 warning (_ ("lyrics found without any matching notehead"));
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();
274 Lyric_phrasing_engraver::do_pre_move_processing ()
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));
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);