]> git.donarmstrong.com Git - lilypond.git/blob - lily/lyric-phrasing-engraver.cc
partial: 1.3.74.gp
[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 "side-position-interface.hh"
14
15 String get_context_id(Translator_group * ancestor, const char * type);
16 String trim_suffix(String &id);
17
18 ADD_THIS_TRANSLATOR (Lyric_phrasing_engraver);
19
20
21 Lyric_phrasing_engraver::Lyric_phrasing_engraver()
22 {
23   voice_alist_ = SCM_EOL;
24 }
25
26 Lyric_phrasing_engraver::~Lyric_phrasing_engraver()
27 {
28   /*
29     No need to delete alist_; that's what Garbage collection is for.
30    */
31 }
32
33 Voice_alist_entry * 
34 Lyric_phrasing_engraver::lookup_context_id(const String &context_id)
35 {
36   SCM key = ly_str02scm(context_id.ch_C());
37   if( ! gh_null_p(voice_alist_) ) {
38     SCM s = scm_assoc(key, voice_alist_);
39     if(! (gh_boolean_p(s) && !to_boolean(s))) {
40       /* match found */
41       return unsmob_voice_entry(gh_cdr(s));
42     }
43   }
44   SCM val = Voice_alist_entry::make_entry (); 
45   voice_alist_ = scm_acons(key, val, voice_alist_);
46   return unsmob_voice_entry (val);
47 }
48
49
50 void 
51 Lyric_phrasing_engraver::record_notehead(const String &context_id, Score_element * notehead)
52 {
53   Voice_alist_entry * v = lookup_context_id(context_id);
54   v->set_notehead(notehead);
55   //  voice_alist_ = 
56   //    scm_assoc_set_x(voice_alist_, ly_str02scm(context_id.ch_C()), smobify(v));
57 }
58   
59 void 
60 Lyric_phrasing_engraver::record_lyric(const String &context_id, Score_element * lyric)
61 {
62   Voice_alist_entry * v = lookup_context_id(context_id);
63   v->add_lyric(lyric);
64   //  voice_alist_ = 
65   //  scm_assoc_set_x(voice_alist_, ly_str02scm(context_id.ch_C()), smobify(v));
66 }
67
68
69
70
71 void
72 Lyric_phrasing_engraver::acknowledge_element(Score_element_info i)
73 {
74   SCM p = get_property("automaticPhrasing");
75   if(!to_boolean(p))
76     return;
77
78
79   Score_element *h = i.elem_l_;
80
81   if (Note_head::has_interface(h)) {
82     /* caught a note head ... do something with it */
83     /* ... but not if it's a grace note ... */
84     bool grace= to_boolean (i.elem_l_->get_elt_property ("grace"));
85     SCM wg = get_property ("weAreGraceContext");
86     bool wgb = to_boolean (wg);
87     if (grace != wgb)
88       return;
89
90     /* what's its Voice context name? */
91     String voice_context_id = get_context_id(i.origin_trans_l_->daddy_trans_l_, "Voice");
92     record_notehead(voice_context_id, h);
93     return;
94   }
95   /* now try for a lyric */
96   if (h->has_interface (ly_symbol2scm ("lyric-syllable-interface"))) {
97
98     /* what's its LyricVoice context name? */
99     String lyric_voice_context_id = 
100       get_context_id(i.origin_trans_l_->daddy_trans_l_, "LyricVoice");
101     record_lyric(trim_suffix(lyric_voice_context_id), h);
102     return;
103   }
104 }
105
106
107 String 
108 get_context_id(Translator_group * ancestor, const char *type)
109 {
110   while(ancestor != 0 && ancestor->type_str_ != type) {
111     ancestor = ancestor->daddy_trans_l_;
112   }
113
114   if(ancestor != 0) {
115     return ancestor->id_str_;
116   }
117
118   return "";
119 }
120
121 String 
122 trim_suffix(String &id)
123 {
124   int index = id.index_i('-');
125   if(index >= 0) {
126     return id.left_str(index);
127   }
128   return id;
129 }
130
131
132 void Lyric_phrasing_engraver::process_acknowledged () 
133 {
134   /* iterate through entries in voice_alist_
135      for each, call set_lyric_align(alignment). Issue a warning if this returns false.
136   */
137   Voice_alist_entry *entry;
138   String punc;
139   if (punc.empty_b()) {
140     SCM sp = get_property("phrasingPunctuation");
141     punc = gh_string_p(sp) ? ly_scm2string(sp) : ".,;?!"; 
142   }
143
144   for(unsigned v=0; v < gh_length(voice_alist_); v++) {
145     entry = unsmob_voice_entry(gh_cdr(gh_list_ref(voice_alist_, gh_int2scm(v))));
146     if(! entry->set_lyric_align(punc.ch_C()))
147       warning (_ ("lyrics found without matching notehead ... aligning on self"));
148   }
149 }
150
151
152 void
153 Lyric_phrasing_engraver::do_pre_move_processing ()
154 {
155   Voice_alist_entry * entry;
156   for(unsigned v=0; v < gh_length(voice_alist_); v++) {
157     entry = unsmob_voice_entry(gh_cdr(gh_list_ref(voice_alist_, gh_int2scm(v))));
158     entry->next_lyric();
159   }
160 }
161
162
163
164 /*=========================================================================================*/
165
166 /** Voice_alist_entry is a class to be smobbed and entered as data in the association list
167     member of the Lyric_phrasing_engraver class.
168 */
169
170 Voice_alist_entry::Voice_alist_entry()
171 {
172   first_in_phrase_b_=true;
173   clear();
174 }
175
176
177
178
179 void 
180 Voice_alist_entry::clear()
181 {
182   notehead_l_=0;
183   lyric_list_.clear();
184   longest_lyric_=-1;
185   shortest_lyric_=-1;
186 }
187   
188 void 
189 Voice_alist_entry::set_first_in_phrase(bool f) 
190
191   first_in_phrase_b_ = f; 
192 }
193
194 void 
195 Voice_alist_entry::set_notehead(Score_element * notehead)
196 {
197   if(!notehead_l_) 
198     /* there should only be a single notehead, so silently ignore any extras */
199     notehead_l_=notehead;
200 }
201
202 void 
203 Voice_alist_entry::add_lyric(Score_element * lyric)
204 {
205   int this_lyric = lyric_list_.size();
206   lyric_list_.push(lyric);
207   /* record longest and shortest lyrics */
208   if(longest_lyric_>-1) {
209     Real this_length = (lyric->extent(X_AXIS)).length();
210     if(this_length > (lyric_list_[longest_lyric_]->extent(X_AXIS)).length())
211       longest_lyric_ = this_lyric;
212     if(this_length < (lyric_list_[shortest_lyric_]->extent(X_AXIS)).length())
213       shortest_lyric_ = this_lyric;
214   }
215   else
216     longest_lyric_ = shortest_lyric_ = this_lyric;
217 }
218   
219 bool 
220 Voice_alist_entry::set_lyric_align(const char *punc)
221 {
222   if(lyric_list_.size()<2) {
223     /* Only for multi-stanza songs ... if we've only a single lyric (or none at all) we
224        do nothing.
225     */
226     clear();
227     return true;
228   }
229
230   Score_element * lyric;
231   alignment_i_ = appropriate_alignment(punc);
232
233   for(int l = 0; l < lyric_list_.size(); l++) {
234     /** set the x alignment of each lyric
235      */
236     lyric = lyric_list_[l];
237     lyric->set_elt_property("self-alignment-X", gh_int2scm(alignment_i_));
238
239     // centre on notehead 
240
241     if(notehead_l_) {
242       /* set the parent of each lyric to the notehead,
243          set the offset callback of each lyric to centered_on_parent,
244       */
245       lyric->set_parent(notehead_l_, X_AXIS);
246       lyric->add_offset_callback (Side_position::centered_on_parent, X_AXIS);
247       /* reference is on the right of the notehead; move it left half way */
248       lyric->translate_axis (-(notehead_l_->extent(X_AXIS)).center(), X_AXIS);
249     }
250     else {
251       /* No matching notehead: just align to the first lyric, and
252           issue a warning about lyric without matching notehead
253       */
254       if(l) {
255         lyric->set_parent(lyric_list_[0], X_AXIS);
256         lyric->add_offset_callback (Side_position::centered_on_parent, X_AXIS);
257       }
258       else
259         lyric->add_offset_callback (Side_position::aligned_on_self, X_AXIS);
260     }
261
262     if(alignment_i_ != CENTER) {
263       // right or left align ... 
264       /* If length of longest lyric < 2 * length of shortest lyric,
265            - centre longest lyric on notehead
266          Otherwise
267            - move so shortest lyric just reaches notehead centre
268       */
269       // FIXME: do we really know the lyric extent here? Some font sizing comes later?
270       Real translate;
271       if((lyric_list_[longest_lyric_]->extent(X_AXIS)).length() <
272          (lyric_list_[shortest_lyric_]->extent(X_AXIS)).length() * 2 )
273         translate = alignment_i_*(lyric_list_[longest_lyric_]->extent(X_AXIS)).length()/2;
274       else
275         translate = alignment_i_*(lyric_list_[shortest_lyric_]->extent(X_AXIS)).length();
276       lyric->translate_axis (translate, X_AXIS);          
277       }
278     }
279
280   return (notehead_l_ != 0);
281 }
282
283 /** determine what alignment we want.
284     Rules: if first_in_phrase_b_ is set, then alignment is LEFT.
285            otherwise if each syllable ends in punctuation, then alignment is RIGHT
286            otherwise alignment is centre.
287 */
288 int 
289 Voice_alist_entry::appropriate_alignment(const char *punc)
290 {
291   if(first_in_phrase_b_)
292     return LEFT;
293
294   Score_element * lyric;
295   bool end_phrase = true;
296   /* use a property to determine what constitutes punctuation */
297
298   for(int l = 0; l < lyric_list_.size() && end_phrase; l++) {
299     lyric = lyric_list_[l];
300     SCM lyric_scm = lyric->get_elt_property("text");
301     String lyric_str = gh_string_p(lyric_scm)?ly_scm2string(lyric_scm):"";
302     char lastchar;
303     if(lyric_str.length_i()>1) {
304       lastchar = lyric_str[lyric_str.length_i()-2];
305       /* We look at the second last character, because lily always appends a space. */
306       /* If it doesn't end in punctuation then it ain't an end of phrase */
307       if(! strchr(punc, lastchar)) {
308         /* Special case: trailing space. Here examine the previous character and reverse the
309            sense of the test (i.e. trailing space makes a break without punctuation, or 
310            suppresses a break with punctuation).
311            This behaviour can be suppressed by including a space in the 
312            phrasingPunctuation property, in which case trailing space always means 
313            the same as punctuation.
314
315            FIXME: The extra space throws alignment out a bit.
316         */
317         if(lastchar == ' ') {
318           if(lyric_str.length_i()>2) {
319             lastchar = lyric_str[lyric_str.length_i()-3];
320             if(strchr(punc, lastchar))
321               end_phrase=false;
322           }
323         }
324         else
325           end_phrase=false;
326       }
327     }
328   }
329   if(end_phrase)
330     return RIGHT;
331
332   return CENTER;
333 }
334
335 bool
336 Voice_alist_entry::is_empty()
337 {
338   return lyric_list_.size()==0;
339 }
340
341 void
342 Voice_alist_entry::next_lyric()
343 {
344   first_in_phrase_b_ = (alignment_i_ == RIGHT);
345   clear();
346 }
347
348 /* SMOB */
349
350 #include "ly-smobs.icc"
351
352 SCM
353 Voice_alist_entry::mark_smob (SCM)
354 {
355   return SCM_EOL;
356 }
357
358 int
359 Voice_alist_entry::print_smob (SCM, SCM port, scm_print_state * )
360 {
361   scm_puts ("#<Voice_alist_entry>", port);
362   return 1;
363 }
364
365 IMPLEMENT_UNSMOB(Voice_alist_entry, voice_entry);
366 IMPLEMENT_SIMPLE_SMOBS(Voice_alist_entry);
367 IMPLEMENT_DEFAULT_EQUAL_P(Voice_alist_entry);
368
369 SCM
370 Voice_alist_entry::make_entry ()
371 {
372   Voice_alist_entry *vi = new Voice_alist_entry;
373   return vi->smobbed_self ();
374 }