]> git.donarmstrong.com Git - lilypond.git/blob - lily/accidental-engraver.cc
(Group): change ftp.cs.uu.nl to
[lilypond.git] / lily / accidental-engraver.cc
1 /*
2   accidental-engraver.cc -- implement accidental_engraver
3
4   (c)  1997--2003 Han-Wen Nienhuys <hanwen@cs.uu.nl>
5   Modified 2001--2002 by Rune Zedeler <rz@daimi.au.dk>
6 */
7
8 #include "event.hh"
9
10 #include "item.hh"
11 #include "tie.hh"
12 #include "rhythmic-head.hh"
13 #include "engraver-group-engraver.hh"
14 #include "accidental-placement.hh"
15 #include "side-position-interface.hh"
16 #include "engraver.hh"
17 #include "arpeggio.hh"
18 #include "warn.hh"
19 #include "translator-group.hh"
20
21 /**
22
23
24 FIXME: should not compute vertical positioning of accidentals, but
25 get them from the noteheads
26
27 The algorithm for accidentals should be documented, and made
28 tweakable.
29
30 */
31
32 struct Accidental_entry {
33   bool done_;
34   Music * melodic_;
35   Grob * accidental_;
36   Translator_group *origin_;
37   Grob*  head_;
38   Accidental_entry();
39 };
40
41 Accidental_entry::Accidental_entry()
42 {
43   done_ = false;
44   melodic_ =0;
45   accidental_ = 0;
46   origin_ = 0;
47   head_ = 0;
48 }
49
50 struct Accidental_engraver : Engraver {
51 protected:
52   TRANSLATOR_DECLARATIONS (Accidental_engraver);
53   virtual void process_music ();
54   virtual void acknowledge_grob (Grob_info);
55   virtual void stop_translation_timestep ();
56   virtual void initialize ();
57   virtual void process_acknowledged_grobs ();
58   virtual void finalize ();
59 public:
60
61   Protected_scm last_keysig_;
62
63   /*
64     Urgh. Since the accidentals depend on lots of variables, we have to
65     store all information before we can really create the accidentals.
66   */
67   Link_array<Grob> left_objects_;
68   Link_array<Grob> right_objects_;
69
70   Grob * accidental_placement_;
71
72   /*
73     The next 
74    */
75   Array<Accidental_entry> accidentals_;
76   Link_array<Grob> ties_;
77 };
78
79
80 static void
81 set_property_on_children (Translator_group * trans, const char * sym, SCM val)
82 {
83   trans->set_property (sym, val);
84   for (SCM p = trans -> trans_group_list_; gh_pair_p (p); p = ly_cdr(p)) {
85     Translator_group *trg =  dynamic_cast<Translator_group*> (unsmob_translator (ly_car (p)));
86     set_property_on_children(trg,sym,ly_deep_copy(val));
87   }
88 }
89
90 Accidental_engraver::Accidental_engraver ()
91 {
92   accidental_placement_ = 0;
93   last_keysig_ = SCM_EOL;
94 }
95
96 void
97 Accidental_engraver::initialize ()
98 {
99   last_keysig_ = get_property ("keySignature");
100
101   Translator_group * trans_ = daddy_trans_;
102   while (trans_)
103     {
104       trans_ -> set_property ("localKeySignature",  ly_deep_copy (last_keysig_));
105       trans_ = trans_->daddy_trans_;
106     }
107   set_property_on_children (daddy_trans_,"localKeySignature", last_keysig_);
108 }
109
110 /*
111
112 calculates the number of accidentals on basis of the current local key sig
113   (passed as argument)
114   Returns number of accidentals (0, 1 or 2).
115     Negative (-1 or -2) if accidental has changed.
116
117 */
118 static int
119 number_accidentals (SCM sig, Music * note, Pitch *pitch, SCM curbarnum, SCM lazyness, 
120                     bool ignore_octave_b)
121 {
122   int n = pitch->get_notename ();
123   int o = pitch->get_octave();
124   int a = pitch->get_alteration ();
125   int curbarnum_i = gh_scm2int (curbarnum);
126   int accbarnum_i = 0;
127
128   SCM prev;
129   if (ignore_octave_b)
130     prev = ly_assoc_cdr (scm_int2num (n), sig);
131   else
132     prev = scm_assoc (gh_cons (scm_int2num (o), scm_int2num (n)), sig);
133
134   /* should really be true unless prev == SCM_BOOL_F */
135   if (gh_pair_p (prev) && gh_pair_p (ly_cdr (prev)))
136     {
137       accbarnum_i = gh_scm2int (ly_cddr (prev));
138       prev = gh_cons (ly_car (prev), ly_cadr (prev));
139     }
140   
141   /* If an accidental was not found or the accidental was too old */
142   if (prev == SCM_BOOL_F ||
143       (gh_number_p (lazyness) && curbarnum_i > accbarnum_i + gh_scm2int (lazyness)))
144     prev = scm_assoc (scm_int2num (n), sig);
145
146
147   SCM prev_acc = (prev == SCM_BOOL_F) ? scm_int2num (0) : ly_cdr (prev);
148
149   int p = gh_number_p (prev_acc) ? gh_scm2int (prev_acc) : 0;
150
151   int num;
152   if (a == p && gh_number_p (prev_acc))
153     num = 0;
154   else if ( (abs (a)<abs (p) || p*a<0) && a != 0 )
155     num = 2;
156   else
157     num = 1;
158   
159   return a == p ? num : -num;
160 }
161
162 static int
163 number_accidentals (Music * note, Pitch *pitch, Translator_group * origin, 
164                     SCM accidentals, SCM curbarnum)
165 {
166   int number = 0;
167
168   bool diff = false;
169   if (gh_pair_p (accidentals) && !gh_symbol_p (ly_car (accidentals)))
170     warning (_f ("Accidental typesetting list must begin with context-name: %s", 
171                  ly_scm2string (ly_car (accidentals)).to_str0 ()));
172   
173   while (gh_pair_p (accidentals) && origin)
174     {
175       // If pair then it is a new accidentals typesetting rule to be checked
176       if (gh_pair_p (ly_car (accidentals)))
177         {
178           SCM type = gh_caar (accidentals);
179           SCM lazyness = gh_cdar (accidentals);
180           SCM localsig = origin->get_property ("localKeySignature");
181           
182           bool same_octave_b = 
183             gh_eq_p (ly_symbol2scm ("same-octave"), type);
184           bool any_octave_b = 
185             gh_eq_p (ly_symbol2scm ("any-octave"), type);
186
187           if (same_octave_b || any_octave_b)
188             {
189               int n = number_accidentals
190                 (localsig, note, pitch, curbarnum, lazyness, any_octave_b);
191               diff = diff || (n < 0);
192               number = max (number, abs (n));     
193             }
194           else
195             warning (_f ("unknown accidental typesetting: %s. Ignored", 
196                          ly_symbol2string (type).to_str0 ()));
197         }
198       
199
200       /*
201         if symbol then it is a context name. Scan parent contexts to find it.
202       */
203       else if (gh_symbol_p (ly_car (accidentals)))
204         {
205           String context = ly_symbol2string (ly_car (accidentals));
206           
207           while (origin && !origin->is_alias_b (context))
208             origin = origin->daddy_trans_;
209       
210           if (!origin)
211             warning (_f ("Symbol is not a parent context: %s. Ignored", 
212                          context.to_str0 ()));
213         }
214       else warning (_f ("Accidental typesetting must be pair or context-name: %s", 
215                         ly_scm2string (ly_car (accidentals)).to_str0 ()));
216       
217       accidentals = ly_cdr (accidentals);
218     }
219   return diff ? -number : number;
220 }
221
222 void
223 Accidental_engraver::process_acknowledged_grobs ()
224 {
225   if (accidentals_.size () && !accidentals_.top().done_)
226     {
227       //SCM localsig = get_property ("localKeySignature");
228       SCM accidentals =  get_property ("autoAccidentals");
229       SCM cautionaries =  get_property ("autoCautionaries");
230       SCM barnum = get_property ("currentBarNumber");
231       SCM smp = get_property("measurePosition");
232       Moment mp = (unsmob_moment (smp)) ? *unsmob_moment (smp) : Moment (0);
233       if(mp.main_part_<Rational(0) && gh_number_p(barnum)) barnum = scm_int2num(gh_scm2int(barnum)-1);
234       bool extra_natural_b = get_property ("extraNatural") == SCM_BOOL_T;
235       for (int i = 0; i  < accidentals_.size (); i++) 
236         {
237           if (accidentals_[i].done_ )
238             continue;
239           accidentals_[i].done_  = true;
240           Grob * support = accidentals_[i].head_;
241           Music * note = accidentals_[i].melodic_;
242           Translator_group * origin = accidentals_[i].origin_;
243
244           Pitch * pitch = unsmob_pitch (note->get_mus_property ("pitch"));
245           int num = number_accidentals (note, pitch, origin, accidentals, barnum);
246           int num_caut = number_accidentals (note, pitch, origin, cautionaries, barnum);
247           bool cautionary = to_boolean (note->get_mus_property ("cautionary"));
248           
249           if (abs (num_caut) > abs (num))
250             {
251               num = num_caut;
252               cautionary = true;
253             }
254
255           if(num==0 && to_boolean (note->get_mus_property ("force-accidental")))
256              num=1;
257           
258           bool different = num < 0;
259           num = abs (num);
260
261           /* see if there's a tie that "changes" the accidental */
262           /* works because if there's a tie, the note to the left
263              is of the same pitch as the actual note */
264
265           Grob *tie_break_reminder = 0;
266           bool tie_changes = false;
267           for (int j = 0; j < ties_.size (); j++)
268             if (support == Tie::head (ties_[j], RIGHT))
269               {
270                 tie_changes = different;
271
272                 /* Enable accidentals for broken tie
273
274                 We only want an accidental on a broken tie, 
275                 if the tie changes the accidental.
276                    
277                 Maybe check property noTieBreakForceAccidental? */
278                 if (different)
279                   tie_break_reminder = ties_[j];
280                 break;
281               }
282
283           if (num)
284             {
285               Grob * a = new Item (get_property ("Accidental"));
286               a->set_parent (support, Y_AXIS);
287
288               if (!accidental_placement_)
289                 {
290                   accidental_placement_ = new Item (get_property ("AccidentalPlacement"));
291                   announce_grob (accidental_placement_, a->self_scm());
292                 }
293               
294               Accidental_placement::add_accidental (accidental_placement_, a);
295               announce_grob (a, SCM_EOL);
296
297               
298               SCM accs = gh_cons (scm_int2num (pitch->get_alteration ()), SCM_EOL);
299               if (num == 2 && extra_natural_b)
300                 accs = gh_cons (scm_int2num (0), accs);
301
302               /* TODO:
303
304               add cautionary option in accidental.
305                */
306
307               if (cautionary)
308                 {
309                   a->set_grob_property ("cautionary", SCM_BOOL_T);
310                 }
311               
312               if (tie_break_reminder)
313                 {
314                   // TODO.
315                   a->set_grob_property ("tie", tie_break_reminder->self_scm());
316                 }
317               
318               
319               support->set_grob_property ("accidental-grob", a->self_scm ());
320
321               a->set_grob_property ("accidentals", accs);
322               accidentals_[i].accidental_ = a;
323  /*
324         We add the accidentals to the support of the arpeggio, so it is put left of the
325         accidentals. 
326         
327       */
328               for (int i = 0;  i < left_objects_.size ();  i++)
329                 Side_position_interface::add_support (left_objects_[i], a);
330               for (int i = 0;  i < right_objects_.size ();  i++)
331                 Side_position_interface::add_support (a, right_objects_[i]);
332             }
333           
334
335           /*
336             We should not record the accidental if it is the first
337             note and it is tied from the previous measure.
338
339             Checking whether it is tied also works mostly, but will it
340             always do the correct thing?
341           */
342           
343
344           int n = pitch->get_notename ();
345           int o = pitch->get_octave ();
346           int a = pitch->get_alteration ();
347           SCM on_s = gh_cons (scm_int2num (o), scm_int2num (n));
348
349           /*
350             TODO: Speed this up!
351             
352             Perhaps only check translators mentioned in the auto-accidentals?
353             -rz
354
355             TODO: profile this.
356             
357             I'd be surprised if the impact of this would be
358             measurable.  Anyway, it seems localsig doesn't change
359             every time-step, but a set_property() is done every
360             time. We could save on that, probably.
361
362             --hwn.
363             
364             
365           */
366
367           while (origin)
368             {
369               SCM localsig = origin->get_property ("localKeySignature");
370               if (tie_changes)
371                 {
372                   /*
373                     Remember an alteration that is different both from
374                     that of the tied note and of the key signature.
375                   */
376                   localsig = ly_assoc_front_x
377                     (localsig, on_s, gh_cons (SCM_BOOL_T, barnum));
378                 }
379               else
380                 {
381                   /*
382                     not really really correct if there are more than one
383                     noteheads with the same notename.
384                   */
385                   localsig = ly_assoc_front_x
386                     (localsig, on_s, gh_cons (scm_int2num (a), barnum)); 
387                 }
388               origin->set_property ("localKeySignature",  localsig);
389               origin = origin->daddy_trans_;
390             }
391         }
392     }
393 }
394
395 void
396 Accidental_engraver::finalize ()
397 {
398
399 }
400
401 void
402 Accidental_engraver::stop_translation_timestep ()
403 {
404   for (int i = 0; i < accidentals_.size(); i++)
405     {
406       Grob *a = accidentals_[i].accidental_;
407       if (a)
408         {
409           typeset_grob (a);
410         }
411     }
412
413   if (accidental_placement_)
414     typeset_grob(accidental_placement_);
415   accidental_placement_ = 00;
416   
417   accidentals_.clear();
418   left_objects_.clear ();
419   right_objects_.clear ();
420   ties_.clear ();
421 }
422
423 void
424 Accidental_engraver::acknowledge_grob (Grob_info info)
425 {
426   Music * note =  info.music_cause ();
427
428   if (note
429       && note->is_mus_type("note-event")
430       && Rhythmic_head::has_interface (info.grob_))
431     {
432       Accidental_entry entry ;
433       entry.head_ = info.grob_;
434       entry.origin_ = info.origin_trans_->daddy_trans_;
435       entry.melodic_ = note;
436
437       accidentals_.push (entry);
438     }
439   else if (Tie::has_interface (info.grob_))
440     {
441       ties_.push (info.grob_);
442     }
443   else if (Arpeggio::has_interface (info.grob_))
444     {
445       left_objects_.push (info.grob_); 
446     }
447   else if (info.grob_->internal_has_interface (ly_symbol2scm("finger-interface")))
448     {
449       left_objects_.push (info.grob_); 
450     }
451 }
452
453 void
454 Accidental_engraver::process_music ()
455 {
456   SCM sig = get_property ("keySignature");
457
458   /* Detect key sig changes.
459      Update all parents and children
460   */
461   if (last_keysig_ != sig)
462     {
463       Translator_group * trans_ = daddy_trans_;
464       while (trans_)
465         {
466           trans_ -> set_property ("localKeySignature",  ly_deep_copy (sig));
467           trans_ = trans_->daddy_trans_;
468         }
469       set_property_on_children(daddy_trans_,"localKeySignature", sig);
470
471       last_keysig_ = sig;
472     }
473 }
474
475
476
477
478
479 ENTER_DESCRIPTION (Accidental_engraver,
480 "Make accidentals.  Catches note heads, ties and notices key-change "
481 " events.  Due to interaction with ties (which don't come together "
482 " with note heads), this needs to be in a context higher than Tie_engraver.",
483                "Accidental",
484 /* accepts */     "",
485                "finger-interface rhythmic-head-interface tie-interface arpeggio-interface",
486                "localKeySignature extraNatural autoAccidentals autoCautionaries",
487                    "localKeySignature");